From c9e039a2967741e2a7cb7c0707620d1bad57978f Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Tue, 25 Jul 2023 21:17:12 +0900 Subject: [PATCH 001/124] =?UTF-8?q?feat:=20=EA=B3=B5=EC=A7=80=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EC=83=9D=EC=84=B1,=20=EA=B3=B5=EC=A7=80=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EC=9D=BD=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :sparkles: 패키지 및 엔티티 생성 * :sparkles: BaseTimeEntity 생성, PostEntity 기본 내용 작성 * :sparkles: PostController 생성 및 기본 내용 작성 * :sparkles: postService 생성 * :sparkles: Exceptions.kt 생성 * :sparkles: postDto 생성 * :sparkles: postRepository 생성 및 기본 내용 작성 * :green_heart: application.yaml 로컬 환경에서 작동하도록 설정 * feat: createPost 기능 생성 * refactor: 리뷰 주신 거 수정 * refactor: post -> notice 수정 등 --- .gitignore | 3 ++ .idea/.gitignore | 8 ++++ .idea/.name | 1 + .idea/compiler.xml | 6 +++ .idea/jarRepositories.xml | 20 +++++++++ .idea/kotlinc.xml | 6 +++ .idea/misc.xml | 8 ++++ .idea/vcs.xml | 6 +++ build.gradle.kts | 11 +++++ .../csereal/CserealApplication.kt | 2 + .../wafflestudio/csereal/common/Exceptions.kt | 7 ++++ .../csereal/common/config/BaseTimeEntity.kt | 23 ++++++++++ .../csereal/common/config/SecurityConfig.kt | 22 ++++++++-- .../common/controller/CommonController.kt | 2 +- .../core/notice/api/NoticeController.kt | 26 ++++++++++++ .../core/notice/database/NoticeEntity.kt | 17 ++++++++ .../core/notice/database/NoticeRepository.kt | 6 +++ .../core/notice/dto/CreateNoticeRequest.kt | 7 ++++ .../csereal/core/notice/dto/NoticeDto.kt | 32 ++++++++++++++ .../core/notice/service/NoticeService.kt | 42 +++++++++++++++++++ 20 files changed, 250 insertions(+), 5 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/.name create mode 100644 .idea/compiler.xml create mode 100644 .idea/jarRepositories.xml create mode 100644 .idea/kotlinc.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/vcs.xml create mode 100644 src/main/kotlin/com/wafflestudio/csereal/common/Exceptions.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/common/config/BaseTimeEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/CreateNoticeRequest.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt diff --git a/.gitignore b/.gitignore index 7186db51..37848be6 100644 --- a/.gitignore +++ b/.gitignore @@ -277,6 +277,9 @@ gradle-app.setting ######################## Custom +### IntelliJ IDEA ### +.idea + # local db save files db/ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..13566b81 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 00000000..7d15ed6b --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +csereal \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 00000000..b589d56e --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 00000000..fdc392fe --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 00000000..4251b727 --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..de0c4286 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..35eb1ddf --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index f36c805c..b81e8ec6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -30,6 +30,17 @@ dependencies { testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.springframework.security:spring-security-test") } +noArg { + annotation("javax.persistence.Entity") + annotation("javax.persistence.MappedSuperclass") + annotation("javax.persistence.Embeddable") +} + +allOpen { + annotation("javax.persistence.Entity") + annotation("javax.persistence.MappedSuperclass") + annotation("javax.persistence.Embeddable") +} tasks.withType { kotlinOptions { diff --git a/src/main/kotlin/com/wafflestudio/csereal/CserealApplication.kt b/src/main/kotlin/com/wafflestudio/csereal/CserealApplication.kt index c1b5837f..648cca3c 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/CserealApplication.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/CserealApplication.kt @@ -2,7 +2,9 @@ package com.wafflestudio.csereal import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication +import org.springframework.data.jpa.repository.config.EnableJpaAuditing +@EnableJpaAuditing @SpringBootApplication class CserealApplication diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/Exceptions.kt b/src/main/kotlin/com/wafflestudio/csereal/common/Exceptions.kt new file mode 100644 index 00000000..3ef42a8a --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/common/Exceptions.kt @@ -0,0 +1,7 @@ +package com.wafflestudio.csereal.common + +import org.springframework.http.HttpStatus + +open class CserealException(msg: String, val status: HttpStatus) : RuntimeException(msg) { + class Csereal400(msg: String) : CserealException(msg, HttpStatus.BAD_REQUEST) +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/BaseTimeEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/BaseTimeEntity.kt new file mode 100644 index 00000000..ecd53933 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/BaseTimeEntity.kt @@ -0,0 +1,23 @@ +package com.wafflestudio.csereal.common.config + +import jakarta.persistence.* +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 +@EntityListeners(AuditingEntityListener::class) +abstract class BaseTimeEntity { + @CreatedDate + @Column(columnDefinition = "datetime(6) default '1999-01-01'") + var createdAt: LocalDateTime? = null + + @LastModifiedDate + @Column(columnDefinition = "datetime(6) default '1999-01-01'") + var modifiedAt: LocalDateTime? = null + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long = 0L +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt index 3d1ad4c2..5ec132d7 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt @@ -3,14 +3,28 @@ package com.wafflestudio.csereal.common.config import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.config.http.SessionCreationPolicy import org.springframework.security.web.SecurityFilterChain @Configuration class SpringSecurityConfig { + + // 확인 바람 @Bean - fun filterChain(http: HttpSecurity): SecurityFilterChain { - http.httpBasic().disable() - return http.build() - } + fun securityFilterChain(httpSecurity: HttpSecurity): SecurityFilterChain = + httpSecurity + .csrf().disable() + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .authorizeRequests() + .anyRequest().permitAll() + .and() + .build() + +// @Bean +// fun filterChain(http: HttpSecurity): SecurityFilterChain { +// http.httpBasic().disable() +// return http.build() +// } } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/controller/CommonController.kt b/src/main/kotlin/com/wafflestudio/csereal/common/controller/CommonController.kt index 754c0a41..e21bbe7a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/controller/CommonController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/controller/CommonController.kt @@ -5,7 +5,7 @@ import org.springframework.web.bind.annotation.RestController @RestController class CommonController { - @GetMapping("/hello_world") + @GetMapping("/helloworld") fun helloWorld(): String { return "Hello, world!" } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt new file mode 100644 index 00000000..91a9231d --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt @@ -0,0 +1,26 @@ +package com.wafflestudio.csereal.core.notice.api + +import com.wafflestudio.csereal.core.notice.dto.CreateNoticeRequest +import com.wafflestudio.csereal.core.notice.dto.NoticeDto +import com.wafflestudio.csereal.core.notice.service.NoticeService +import org.springframework.web.bind.annotation.* + +@RequestMapping("/notice") +@RestController +class NoticeController( + private val noticeService: NoticeService, +) { + @GetMapping("/{noticeId}") + fun readNotice( + @PathVariable noticeId: Long, + ) : NoticeDto { + return noticeService.readNotice(noticeId) + } + + @PostMapping + fun createNotice( + @RequestBody request: CreateNoticeRequest + ) : NoticeDto { + return noticeService.createNotice(request) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt new file mode 100644 index 00000000..5b55d6a6 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt @@ -0,0 +1,17 @@ +package com.wafflestudio.csereal.core.notice.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import jakarta.persistence.Column +import jakarta.persistence.Entity + + +@Entity(name = "notice") +class NoticeEntity( + @Column + var title: String, + + @Column(columnDefinition = "text") + var description: String +): BaseTimeEntity() { + +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt new file mode 100644 index 00000000..0ec7f774 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt @@ -0,0 +1,6 @@ +package com.wafflestudio.csereal.core.notice.database + +import org.springframework.data.jpa.repository.JpaRepository + +interface NoticeRepository : JpaRepository { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/CreateNoticeRequest.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/CreateNoticeRequest.kt new file mode 100644 index 00000000..be251915 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/CreateNoticeRequest.kt @@ -0,0 +1,7 @@ +package com.wafflestudio.csereal.core.notice.dto + +data class CreateNoticeRequest( + val title: String, + val description: String +) { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt new file mode 100644 index 00000000..65e1120c --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt @@ -0,0 +1,32 @@ +package com.wafflestudio.csereal.core.notice.dto + +import com.wafflestudio.csereal.core.notice.database.NoticeEntity +import java.time.LocalDateTime + +data class NoticeDto( + val id: Long, + val title: String, + val description: String, + // val postType: String, + // val authorId: Int, + val createdAt: LocalDateTime?, + val modifiedAt: LocalDateTime?, + // val isPublic: Boolean, + // val isSlide: Boolean, + // val isPinned: Boolean, +) { + + companion object { + fun of(entity: NoticeEntity): NoticeDto = entity.run { + NoticeDto( + id = this.id, + title = this.title, + description = this.description, + createdAt = this.createdAt, + modifiedAt = this.modifiedAt + ) + } + + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt new file mode 100644 index 00000000..ca321282 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt @@ -0,0 +1,42 @@ +package com.wafflestudio.csereal.core.notice.service + +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.notice.database.NoticeEntity +import com.wafflestudio.csereal.core.notice.database.NoticeRepository +import com.wafflestudio.csereal.core.notice.dto.CreateNoticeRequest +import com.wafflestudio.csereal.core.notice.dto.NoticeDto +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +interface NoticeService { + fun readNotice(noticeId: Long): NoticeDto + fun createNotice(request: CreateNoticeRequest): NoticeDto +} + +@Service +class NoticeServiceImpl( + private val noticeRepository: NoticeRepository, +) : NoticeService { + + @Transactional(readOnly = true) + override fun readNotice(noticeId: Long): NoticeDto { + val notice: NoticeEntity = noticeRepository.findByIdOrNull(noticeId) + ?: throw CserealException.Csereal400("존재하지 않는 공지사항입니다.(noticeId: $noticeId)") + return NoticeDto.of(notice) + } + + @Transactional + override fun createNotice(request: CreateNoticeRequest): NoticeDto { + // TODO:"아직 날짜가 제대로 안 뜸" + val newNotice = NoticeEntity( + title = request.title, + description = request.description, + ) + + noticeRepository.save(newNotice) + + return NoticeDto.of(newNotice) + + } +} \ No newline at end of file From f849a636364450e45b72c6e257fdd7c7117a74aa Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Wed, 26 Jul 2023 15:11:09 +0900 Subject: [PATCH 002/124] =?UTF-8?q?chore:=20.idea=20=EB=94=94=EB=A0=89?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/.gitignore | 8 -------- .idea/.name | 1 - .idea/compiler.xml | 6 ------ .idea/jarRepositories.xml | 20 -------------------- .idea/kotlinc.xml | 6 ------ .idea/misc.xml | 8 -------- .idea/vcs.xml | 6 ------ 7 files changed, 55 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/.name delete mode 100644 .idea/compiler.xml delete mode 100644 .idea/jarRepositories.xml delete mode 100644 .idea/kotlinc.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b81..00000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index 7d15ed6b..00000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -csereal \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index b589d56e..00000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml deleted file mode 100644 index fdc392fe..00000000 --- a/.idea/jarRepositories.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml deleted file mode 100644 index 4251b727..00000000 --- a/.idea/kotlinc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index de0c4286..00000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1ddf..00000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From 54ef587e099f2c425a889546412d26a22ac9fd1e Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Sun, 23 Jul 2023 17:44:19 +0900 Subject: [PATCH 003/124] =?UTF-8?q?chore:=20PR=20=ED=85=9C=ED=94=8C?= =?UTF-8?q?=EB=A6=BF=20=EC=83=9D=EC=84=B1=20(#2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/pull_request_template.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..4d85e3ed --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,15 @@ +## 제목 + +### 한줄 요약 + +PR 요약해주세요 + +### 상세 설명 + +[`Commit Hash1`]: 커밋 설명1 + +[`Commit Hash2`]: 커밋 설명2 + +### TODO + +TODO가 있다면 작성해주시고, 없다면 생략해주세요. From 3a9290674d6897ffbead3c1d9407e770eb451112 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Wed, 26 Jul 2023 15:35:35 +0900 Subject: [PATCH 004/124] =?UTF-8?q?feat:=20=EB=A1=9C=EC=BB=AC=20db?= =?UTF-8?q?=EC=9A=A9=20docker-compose=20=ED=8C=8C=EC=9D=BC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20application.yaml=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=20(#4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose-local.yml | 17 +++++++++++++++++ src/main/resources/application.yaml | 14 ++++++++++---- 2 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 docker-compose-local.yml diff --git a/docker-compose-local.yml b/docker-compose-local.yml new file mode 100644 index 00000000..ad08265a --- /dev/null +++ b/docker-compose-local.yml @@ -0,0 +1,17 @@ +version: '3.8' +services: + db: + image: mysql:8.0 + cap_add: + - SYS_NICE + environment: + - MYSQL_DATABASE=csereal + - MYSQL_ROOT_PASSWORD=password + ports: + - '3306:3306' + volumes: + - db:/var/lib/mysql + - $PWD/db/init.sql:/docker-entrypoint-initdb.d/init.sql +volumes: + db: + driver: local diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 62f21be6..d3c5150f 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1,14 +1,19 @@ spring: - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver + profiles: + active: local --- spring: config.activate.on-profile: local jpa: hibernate: - ddl-auto: create + ddl-auto: update show-sql: true + open-in-view: false + datasource: + url: jdbc:mysql://localhost:3306/csereal + username: root + password: password logging.level: default: INFO @@ -17,4 +22,5 @@ spring: config.activate.on-profile: prod jpa: hibernate: - ddl-auto: none \ No newline at end of file + ddl-auto: none + open-in-view: false From c2f7ef8b85343625a91fb24dedc4a6c924cac000 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Fri, 28 Jul 2023 20:26:16 +0900 Subject: [PATCH 005/124] =?UTF-8?q?feat:=20=EA=B3=B5=EC=A7=80=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EC=88=98=EC=A0=95,=20=EC=82=AD=EC=A0=9C,=20?= =?UTF-8?q?=ED=83=9C=EA=B7=B8=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?(#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: ExceptionHandler 추가 * feat: updateNotice 추가, valid 추가 * feat: deleteNotice 추가 * feat: enrollTag 기능 추가, noticeTag 연관 엔티티 추가 * feat: 공지사항 작성할 때 태그 생성 및 수정 * fix: 로컬 db 없앰 * fix: pr 리뷰 수정 * fix: pr 리뷰 수정 * fix: noticeTag assign --- build.gradle.kts | 1 + .../common/config/CserealExceptionHandler.kt | 33 +++++++++++ .../core/notice/api/NoticeController.kt | 29 +++++++++- .../core/notice/database/NoticeEntity.kt | 21 ++++++- .../core/notice/database/NoticeTagEntity.kt | 29 ++++++++++ .../notice/database/NoticeTagRepository.kt | 7 +++ .../csereal/core/notice/database/TagEntity.kt | 15 +++++ .../core/notice/database/TagRepository.kt | 6 ++ .../core/notice/dto/CreateNoticeRequest.kt | 9 ++- .../csereal/core/notice/dto/NoticeDto.kt | 8 ++- .../core/notice/dto/UpdateNoticeRequest.kt | 8 +++ .../core/notice/service/NoticeService.kt | 57 ++++++++++++++++++- src/main/resources/application.yaml | 5 ++ 13 files changed, 221 insertions(+), 7 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/common/config/CserealExceptionHandler.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeTagEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeTagRepository.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagRepository.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/UpdateNoticeRequest.kt diff --git a/build.gradle.kts b/build.gradle.kts index b81e8ec6..7d5145d6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -24,6 +24,7 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-oauth2-client") implementation("org.springframework.boot:spring-boot-starter-security") implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.springframework.boot:spring-boot-starter-validation") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.jetbrains.kotlin:kotlin-reflect") runtimeOnly("com.mysql:mysql-connector-j") diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/CserealExceptionHandler.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/CserealExceptionHandler.kt new file mode 100644 index 00000000..6cf3d136 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/CserealExceptionHandler.kt @@ -0,0 +1,33 @@ +package com.wafflestudio.csereal.common.config + +import com.wafflestudio.csereal.common.CserealException +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.validation.BindingResult +import org.springframework.web.bind.MethodArgumentNotValidException +import org.springframework.web.bind.annotation.ExceptionHandler +import org.springframework.web.bind.annotation.RestControllerAdvice +import java.sql.SQLIntegrityConstraintViolationException + +@RestControllerAdvice +class CserealExceptionHandler { + + // @Valid로 인해 오류 떴을 때 메시지 전송 + @ExceptionHandler(value = [MethodArgumentNotValidException::class]) + fun handle(e: MethodArgumentNotValidException): ResponseEntity { + val bindingResult: BindingResult = e.bindingResult + return ResponseEntity(bindingResult.fieldError?.defaultMessage, HttpStatus.BAD_REQUEST) + } + + // csereal 내부 규정 오류 + @ExceptionHandler(value = [CserealException::class]) + fun handle(e: CserealException): ResponseEntity { + return ResponseEntity(e.message, e.status) + } + + // db에서 중복된 값 있을 때 + @ExceptionHandler(value = [SQLIntegrityConstraintViolationException::class]) + fun handle(e: SQLIntegrityConstraintViolationException): ResponseEntity { + return ResponseEntity("중복된 값이 있습니다.", HttpStatus.CONFLICT) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt index 91a9231d..21131bff 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt @@ -2,7 +2,11 @@ package com.wafflestudio.csereal.core.notice.api import com.wafflestudio.csereal.core.notice.dto.CreateNoticeRequest import com.wafflestudio.csereal.core.notice.dto.NoticeDto +import com.wafflestudio.csereal.core.notice.dto.UpdateNoticeRequest import com.wafflestudio.csereal.core.notice.service.NoticeService +import jakarta.validation.Valid +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* @RequestMapping("/notice") @@ -19,8 +23,31 @@ class NoticeController( @PostMapping fun createNotice( - @RequestBody request: CreateNoticeRequest + @Valid @RequestBody request: CreateNoticeRequest ) : NoticeDto { return noticeService.createNotice(request) } + + @PatchMapping("/{noticeId}") + fun updateNotice( + @PathVariable noticeId: Long, + @Valid @RequestBody request: UpdateNoticeRequest, + ) : NoticeDto { + return noticeService.updateNotice(noticeId, request) + } + + @DeleteMapping("/{noticeId}") + fun deleteNotice( + @PathVariable noticeId: Long + ) { + noticeService.deleteNotice(noticeId) + } + + @PostMapping("/tag") + fun enrollTag( + @RequestBody tagName: Map + ) : ResponseEntity { + noticeService.enrollTag(tagName["name"]!!) + return ResponseEntity("등록되었습니다. (tagName: ${tagName["name"]})", HttpStatus.OK) + } } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt index 5b55d6a6..9fdc013a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt @@ -1,17 +1,36 @@ package com.wafflestudio.csereal.core.notice.database import com.wafflestudio.csereal.common.config.BaseTimeEntity +import jakarta.persistence.CascadeType import jakarta.persistence.Column import jakarta.persistence.Entity +import jakarta.persistence.OneToMany @Entity(name = "notice") class NoticeEntity( + + @Column + var isDeleted: Boolean = false, + @Column var title: String, @Column(columnDefinition = "text") - var description: String + var description: String, + +// var postType: String, +// +// var isPublic: Boolean, +// +// var isSlide: Boolean, +// +// var isPinned: Boolean, + + @OneToMany(mappedBy = "notice", cascade = [CascadeType.ALL]) + var noticeTags: MutableSet = mutableSetOf() ): BaseTimeEntity() { + + } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeTagEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeTagEntity.kt new file mode 100644 index 00000000..c910dabd --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeTagEntity.kt @@ -0,0 +1,29 @@ +package com.wafflestudio.csereal.core.notice.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import jakarta.persistence.* + +@Entity(name = "noticeTag") +class NoticeTagEntity( + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "notice_id") + var notice: NoticeEntity, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "tag_id") + var tag: TagEntity, + + ) : BaseTimeEntity() { + companion object { + + fun createNoticeTag(notice: NoticeEntity, tag: TagEntity) { + val noticeTag = NoticeTagEntity(notice, tag) + notice.noticeTags.add(noticeTag) + tag.noticeTags.add(noticeTag) + } + } + + + +} + diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeTagRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeTagRepository.kt new file mode 100644 index 00000000..39a6ce98 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeTagRepository.kt @@ -0,0 +1,7 @@ +package com.wafflestudio.csereal.core.notice.database + +import org.springframework.data.jpa.repository.JpaRepository + +interface NoticeTagRepository : JpaRepository { + fun deleteAllByNoticeId(noticeId: Long) +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagEntity.kt new file mode 100644 index 00000000..67c108b9 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagEntity.kt @@ -0,0 +1,15 @@ +package com.wafflestudio.csereal.core.notice.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import jakarta.persistence.CascadeType +import jakarta.persistence.Entity +import jakarta.persistence.OneToMany + +@Entity(name = "tag") +class TagEntity( + var name: String, + + @OneToMany(mappedBy = "tag") + val noticeTags: MutableSet = mutableSetOf() +) : BaseTimeEntity() { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagRepository.kt new file mode 100644 index 00000000..34a50416 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagRepository.kt @@ -0,0 +1,6 @@ +package com.wafflestudio.csereal.core.notice.database + +import org.springframework.data.jpa.repository.JpaRepository + +interface TagRepository : JpaRepository { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/CreateNoticeRequest.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/CreateNoticeRequest.kt index be251915..2c660447 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/CreateNoticeRequest.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/CreateNoticeRequest.kt @@ -1,7 +1,14 @@ package com.wafflestudio.csereal.core.notice.dto +import jakarta.validation.constraints.NotBlank + data class CreateNoticeRequest( + @field:NotBlank(message = "제목은 비어있을 수 없습니다") val title: String, - val description: String + + @field:NotBlank(message = "내용은 비어있을 수 없습니다") + val description: String, + + val tags: List = emptyList() ) { } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt index 65e1120c..a858c1dc 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt @@ -5,6 +5,7 @@ import java.time.LocalDateTime data class NoticeDto( val id: Long, + val isDeleted: Boolean, val title: String, val description: String, // val postType: String, @@ -20,10 +21,15 @@ data class NoticeDto( fun of(entity: NoticeEntity): NoticeDto = entity.run { NoticeDto( id = this.id, + isDeleted = false, title = this.title, description = this.description, + // postType = this.postType, createdAt = this.createdAt, - modifiedAt = this.modifiedAt + modifiedAt = this.modifiedAt, +// isPublic = this.isPublic, +// isSlide = this.isSlide, +// isPinned = this.isPinned, ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/UpdateNoticeRequest.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/UpdateNoticeRequest.kt new file mode 100644 index 00000000..ea45d723 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/UpdateNoticeRequest.kt @@ -0,0 +1,8 @@ +package com.wafflestudio.csereal.core.notice.dto + +data class UpdateNoticeRequest( + val title: String?, + val description: String?, + val tags: List? +) { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt index ca321282..168bed68 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt @@ -1,10 +1,10 @@ package com.wafflestudio.csereal.core.notice.service import com.wafflestudio.csereal.common.CserealException -import com.wafflestudio.csereal.core.notice.database.NoticeEntity -import com.wafflestudio.csereal.core.notice.database.NoticeRepository +import com.wafflestudio.csereal.core.notice.database.* import com.wafflestudio.csereal.core.notice.dto.CreateNoticeRequest import com.wafflestudio.csereal.core.notice.dto.NoticeDto +import com.wafflestudio.csereal.core.notice.dto.UpdateNoticeRequest import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -12,31 +12,82 @@ import org.springframework.transaction.annotation.Transactional interface NoticeService { fun readNotice(noticeId: Long): NoticeDto fun createNotice(request: CreateNoticeRequest): NoticeDto + fun updateNotice(noticeId: Long, request: UpdateNoticeRequest): NoticeDto + fun deleteNotice(noticeId: Long) + fun enrollTag(tagName: String) } @Service class NoticeServiceImpl( private val noticeRepository: NoticeRepository, + private val tagRepository: TagRepository, + private val noticeTagRepository: NoticeTagRepository ) : NoticeService { @Transactional(readOnly = true) override fun readNotice(noticeId: Long): NoticeDto { val notice: NoticeEntity = noticeRepository.findByIdOrNull(noticeId) ?: throw CserealException.Csereal400("존재하지 않는 공지사항입니다.(noticeId: $noticeId)") + if (notice.isDeleted) throw CserealException.Csereal400("삭제된 공지사항입니다.(noticeId: $noticeId)") return NoticeDto.of(notice) } @Transactional override fun createNotice(request: CreateNoticeRequest): NoticeDto { - // TODO:"아직 날짜가 제대로 안 뜸" val newNotice = NoticeEntity( title = request.title, description = request.description, ) + for (tagId in request.tags) { + val tag = tagRepository.findByIdOrNull(tagId) ?: throw CserealException.Csereal400("해당하는 태그가 없습니다") + NoticeTagEntity.createNoticeTag(newNotice, tag) + } + noticeRepository.save(newNotice) return NoticeDto.of(newNotice) } + + @Transactional + override fun updateNotice(noticeId: Long, request: UpdateNoticeRequest): NoticeDto { + val notice: NoticeEntity = noticeRepository.findByIdOrNull(noticeId) + ?: throw CserealException.Csereal400("존재하지 않는 공지사항입니다.(noticeId: $noticeId") + if (notice.isDeleted) throw CserealException.Csereal400("삭제된 공지사항입니다.(noticeId: $noticeId") + + notice.title = request.title ?: notice.title + notice.description = request.description ?: notice.description + + if (request.tags != null) { + noticeTagRepository.deleteAllByNoticeId(noticeId) + notice.noticeTags.clear() + for (tagId in request.tags) { + val tag = tagRepository.findByIdOrNull(tagId) ?: throw CserealException.Csereal400("해당하는 태그가 없습니다") + NoticeTagEntity.createNoticeTag(notice, tag) + } + } + + + + return NoticeDto.of(notice) + } + + @Transactional + override fun deleteNotice(noticeId: Long) { + val notice: NoticeEntity = noticeRepository.findByIdOrNull(noticeId) + ?: throw CserealException.Csereal400("존재하지 않는 공지사항입니다.(noticeId: $noticeId)") + + notice.isDeleted = true + + } + + override fun enrollTag(tagName: String) { + val newTag = TagEntity( + name = tagName + ) + tagRepository.save(newTag) + } + + //TODO: 이미지 등록, 페이지네이션, 검색 } \ No newline at end of file diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index d3c5150f..35b3da2a 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -5,6 +5,11 @@ spring: --- spring: config.activate.on-profile: local + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/csereal_local?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Seoul + username: root + password: toor jpa: hibernate: ddl-auto: update From 227a3854a8bbf2f67349cfd69c73723d8c2faac2 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Mon, 31 Jul 2023 20:32:27 +0900 Subject: [PATCH 006/124] =?UTF-8?q?feat:=20=EA=B5=AC=EC=84=B1=EC=9B=90(?= =?UTF-8?q?=EA=B5=90=EC=88=98)=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20API=20=EA=B5=AC=ED=98=84=20=20(#8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 교수 엔티티 및 DTO 설계 * feat: 교수 생성 및 조회 --- .../wafflestudio/csereal/common/Exceptions.kt | 3 +- .../core/member/api/ProfessorController.kt | 35 ++++++++ .../core/member/database/CareerEntity.kt | 32 ++++++++ .../core/member/database/EducationEntity.kt | 38 +++++++++ .../core/member/database/ProfessorEntity.kt | 65 +++++++++++++++ .../member/database/ProfessorRepository.kt | 8 ++ .../member/database/ResearchAreaEntity.kt | 27 +++++++ .../csereal/core/member/dto/CareerDto.kt | 19 +++++ .../csereal/core/member/dto/EducationDto.kt | 22 ++++++ .../csereal/core/member/dto/ProfessorDto.kt | 46 +++++++++++ .../core/member/dto/SimpleProfessorDto.kt | 28 +++++++ .../core/member/service/ProfessorService.kt | 79 +++++++++++++++++++ .../core/research/database/LabEntity.kt | 15 ++++ .../core/research/database/LabRepository.kt | 6 ++ 14 files changed, 422 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/database/CareerEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/database/EducationEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorRepository.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/database/ResearchAreaEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/dto/CareerDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/dto/EducationDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/dto/ProfessorDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/dto/SimpleProfessorDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabRepository.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/Exceptions.kt b/src/main/kotlin/com/wafflestudio/csereal/common/Exceptions.kt index 3ef42a8a..cb0d7b08 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/Exceptions.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/Exceptions.kt @@ -4,4 +4,5 @@ import org.springframework.http.HttpStatus open class CserealException(msg: String, val status: HttpStatus) : RuntimeException(msg) { class Csereal400(msg: String) : CserealException(msg, HttpStatus.BAD_REQUEST) -} \ No newline at end of file + class Csereal404(msg: String) : CserealException(msg, HttpStatus.NOT_FOUND) +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt new file mode 100644 index 00000000..437c2544 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt @@ -0,0 +1,35 @@ +package com.wafflestudio.csereal.core.member.api + +import com.wafflestudio.csereal.core.member.dto.ProfessorDto +import com.wafflestudio.csereal.core.member.dto.SimpleProfessorDto +import com.wafflestudio.csereal.core.member.service.ProfessorService +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* + +@RequestMapping("/professor") +@RestController +class ProfessorController( + private val professorService: ProfessorService +) { + + @PostMapping + fun createProfessor(@RequestBody createProfessorRequest: ProfessorDto): ResponseEntity { + return ResponseEntity.ok(professorService.createProfessor(createProfessorRequest)) + } + + @GetMapping("/{professorId}") + fun getProfessor(@PathVariable professorId: Long): ResponseEntity { + return ResponseEntity.ok(professorService.getProfessor(professorId)) + } + + @GetMapping("/active") + fun getActiveProfessors(): ResponseEntity> { + return ResponseEntity.ok(professorService.getActiveProfessors()) + } + + @GetMapping("/inactive") + fun getInactiveProfessors(): ResponseEntity> { + return ResponseEntity.ok(professorService.getInactiveProfessors()) + } + +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/CareerEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/CareerEntity.kt new file mode 100644 index 00000000..30df5357 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/CareerEntity.kt @@ -0,0 +1,32 @@ +package com.wafflestudio.csereal.core.member.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.core.member.dto.CareerDto +import jakarta.persistence.Entity +import jakarta.persistence.FetchType +import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne + +@Entity(name = "career") +class CareerEntity( + val duration: String, + val name: String, + val workplace: String, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "professor_id") + val professor: ProfessorEntity +) : BaseTimeEntity() { + companion object { + fun create(career: CareerDto, professor: ProfessorEntity): CareerEntity { + val careerEntity = CareerEntity( + duration = career.duration, + name = career.name, + workplace = career.workplace, + professor = professor + ) + professor.careers.add(careerEntity) + return careerEntity + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/EducationEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/EducationEntity.kt new file mode 100644 index 00000000..7bdae723 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/EducationEntity.kt @@ -0,0 +1,38 @@ +package com.wafflestudio.csereal.core.member.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.core.member.dto.EducationDto +import jakarta.persistence.* + +@Entity(name = "education") +class EducationEntity( + val university: String, + val major: String, + + @Enumerated(EnumType.STRING) + val degree: Degree, + + val year: Int, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "professor_id") + val professor: ProfessorEntity +) : BaseTimeEntity() { + companion object { + fun create(education: EducationDto, professor: ProfessorEntity): EducationEntity { + val educationEntity = EducationEntity( + university = education.university, + major = education.major, + degree = education.degree, + year = education.year, + professor = professor + ) + professor.educations.add(educationEntity) + return educationEntity + } + } +} + +enum class Degree { + Bachelor, Master, PhD +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt new file mode 100644 index 00000000..09efde16 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt @@ -0,0 +1,65 @@ +package com.wafflestudio.csereal.core.member.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.core.member.dto.ProfessorDto +import com.wafflestudio.csereal.core.research.database.LabEntity +import jakarta.persistence.CascadeType +import jakarta.persistence.Entity +import jakarta.persistence.FetchType +import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne +import jakarta.persistence.OneToMany +import java.time.LocalDate + +@Entity(name = "professor") +class ProfessorEntity( + val name: String, + //val profileImage:File + var isActive: Boolean, + var academicRank: String, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "lab_id") + var lab: LabEntity? = null, + + var startDate: LocalDate?, + var endDate: LocalDate?, + val office: String?, + var phone: String?, + var fax: String?, + var email: String?, + var website: String?, + + @OneToMany(mappedBy = "professor", cascade = [CascadeType.ALL], orphanRemoval = true) + val educations: MutableList = mutableListOf(), + + @OneToMany(mappedBy = "professor", cascade = [CascadeType.ALL], orphanRemoval = true) + val researchAreas: MutableSet = mutableSetOf(), + + @OneToMany(mappedBy = "professor", cascade = [CascadeType.ALL], orphanRemoval = true) + val careers: MutableList = mutableListOf(), + + ) : BaseTimeEntity() { + + companion object { + fun of(professorDto: ProfessorDto): ProfessorEntity { + return ProfessorEntity( + name = professorDto.name, + isActive = professorDto.isActive, + academicRank = professorDto.academicRank, + startDate = professorDto.startDate, + endDate = professorDto.endDate, + office = professorDto.office, + phone = professorDto.phone, + fax = professorDto.fax, + email = professorDto.email, + website = professorDto.website + ) + } + } + + fun addLab(lab: LabEntity) { + this.lab = lab + lab.professors.add(this) + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorRepository.kt new file mode 100644 index 00000000..d8ae890c --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorRepository.kt @@ -0,0 +1,8 @@ +package com.wafflestudio.csereal.core.member.database + +import org.springframework.data.jpa.repository.JpaRepository + +interface ProfessorRepository : JpaRepository { + fun findByIsActiveTrue(): List + fun findByIsActiveFalse(): List +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ResearchAreaEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ResearchAreaEntity.kt new file mode 100644 index 00000000..6f6c8ab4 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ResearchAreaEntity.kt @@ -0,0 +1,27 @@ +package com.wafflestudio.csereal.core.member.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import jakarta.persistence.Entity +import jakarta.persistence.FetchType +import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne + +@Entity(name = "research_area") +class ResearchAreaEntity( + val name: String, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "professor_id") + val professor: ProfessorEntity +) : BaseTimeEntity() { + companion object { + fun create(name: String, professor: ProfessorEntity): ResearchAreaEntity { + val researchArea = ResearchAreaEntity( + name = name, + professor = professor + ) + professor.researchAreas.add(researchArea) + return researchArea + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/CareerDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/CareerDto.kt new file mode 100644 index 00000000..ff14cfb2 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/CareerDto.kt @@ -0,0 +1,19 @@ +package com.wafflestudio.csereal.core.member.dto + +import com.wafflestudio.csereal.core.member.database.CareerEntity + +data class CareerDto( + val duration: String, + val name: String, + val workplace: String +) { + companion object { + fun of(careerEntity: CareerEntity): CareerDto { + return CareerDto( + duration = careerEntity.duration, + name = careerEntity.name, + workplace = careerEntity.workplace + ) + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/EducationDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/EducationDto.kt new file mode 100644 index 00000000..48e2c968 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/EducationDto.kt @@ -0,0 +1,22 @@ +package com.wafflestudio.csereal.core.member.dto + +import com.wafflestudio.csereal.core.member.database.Degree +import com.wafflestudio.csereal.core.member.database.EducationEntity + +data class EducationDto( + val university: String, + val major: String, + val degree: Degree, + val year: Int +) { + companion object { + fun of(educationEntity: EducationEntity): EducationDto { + return EducationDto( + university = educationEntity.university, + major = educationEntity.major, + degree = educationEntity.degree, + year = educationEntity.year + ) + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/ProfessorDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/ProfessorDto.kt new file mode 100644 index 00000000..d4a52290 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/ProfessorDto.kt @@ -0,0 +1,46 @@ +package com.wafflestudio.csereal.core.member.dto + +import com.fasterxml.jackson.annotation.JsonInclude +import com.wafflestudio.csereal.core.member.database.ProfessorEntity +import java.time.LocalDate + +data class ProfessorDto( + @JsonInclude(JsonInclude.Include.NON_NULL) + var id: Long? = null, + val name: String, + val isActive: Boolean, + val academicRank: String, + val labId: Long?, + val startDate: LocalDate?, + val endDate: LocalDate?, + val office: String?, + val phone: String?, + val fax: String?, + val email: String?, + val website: String?, + val educations: List, + val researchAreas: List, + val careers: List +) { + companion object { + fun of(professorEntity: ProfessorEntity): ProfessorDto { + return ProfessorDto( + id = professorEntity.id, + name = professorEntity.name, + isActive = professorEntity.isActive, + academicRank = professorEntity.academicRank, + labId = professorEntity.lab?.id, + startDate = professorEntity.startDate, + endDate = professorEntity.endDate, + office = professorEntity.office, + phone = professorEntity.phone, + fax = professorEntity.fax, + email = professorEntity.email, + website = professorEntity.website, + educations = professorEntity.educations.map { EducationDto.of(it) }, + researchAreas = professorEntity.researchAreas.map { it.name }, + careers = professorEntity.careers.map { CareerDto.of(it) } + ) + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/SimpleProfessorDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/SimpleProfessorDto.kt new file mode 100644 index 00000000..6e070667 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/SimpleProfessorDto.kt @@ -0,0 +1,28 @@ +package com.wafflestudio.csereal.core.member.dto + +import com.wafflestudio.csereal.core.member.database.ProfessorEntity + +data class SimpleProfessorDto( + val id: Long, + val name: String, + val academicRank: String, + val labId: Long?, + val labName: String?, + val phone: String?, + val email: String?, + // val imageUri: String +) { + companion object { + fun of(professorEntity: ProfessorEntity): SimpleProfessorDto { + return SimpleProfessorDto( + id = professorEntity.id, + name = professorEntity.name, + academicRank = professorEntity.academicRank, + labId = professorEntity.lab?.id, + labName = professorEntity.lab?.name, + phone = professorEntity.phone, + email = professorEntity.email + ) + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt new file mode 100644 index 00000000..d2f8b582 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt @@ -0,0 +1,79 @@ +package com.wafflestudio.csereal.core.member.service + +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.member.database.* +import com.wafflestudio.csereal.core.member.dto.ProfessorDto +import com.wafflestudio.csereal.core.member.dto.SimpleProfessorDto +import com.wafflestudio.csereal.core.research.database.LabRepository +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +interface ProfessorService { + fun createProfessor(professorDto: ProfessorDto): ProfessorDto + fun getProfessor(professorId: Long): ProfessorDto + fun getActiveProfessors(): List + fun getInactiveProfessors(): List + fun updateProfessor(updateProfessorRequest: ProfessorDto): ProfessorDto + fun deleteProfessor(professorId: Long) +} + +@Service +@Transactional +class ProfessorServiceImpl( + private val labRepository: LabRepository, + private val professorRepository: ProfessorRepository +) : ProfessorService { + + override fun createProfessor(professorDto: ProfessorDto): ProfessorDto { + val professor = ProfessorEntity.of(professorDto) + + if (professorDto.labId != null) { + val lab = labRepository.findByIdOrNull(professorDto.labId) + ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다. LabId: ${professorDto.labId}") + professor.addLab(lab) + } + + for (education in professorDto.educations) { + EducationEntity.create(education, professor) + } + + for (researchArea in professorDto.researchAreas) { + ResearchAreaEntity.create(researchArea, professor) + } + + for (career in professorDto.careers) { + CareerEntity.create(career, professor) + } + + professorRepository.save(professor) + + return ProfessorDto.of(professor) + } + + @Transactional(readOnly = true) + override fun getProfessor(professorId: Long): ProfessorDto { + val professor = professorRepository.findByIdOrNull(professorId) + ?: throw CserealException.Csereal404("해당 교수님을 찾을 수 없습니다. professorId: ${professorId}") + return ProfessorDto.of(professor) + } + + @Transactional(readOnly = true) + override fun getActiveProfessors(): List { + return professorRepository.findByIsActiveTrue().map { SimpleProfessorDto.of(it) } + } + + @Transactional(readOnly = true) + override fun getInactiveProfessors(): List { + return professorRepository.findByIsActiveFalse().map { SimpleProfessorDto.of(it) } + } + + override fun updateProfessor(updateProfessorRequest: ProfessorDto): ProfessorDto { + TODO("Not yet implemented") + } + + override fun deleteProfessor(professorId: Long) { + TODO("Not yet implemented") + } + +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabEntity.kt new file mode 100644 index 00000000..06b88e10 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabEntity.kt @@ -0,0 +1,15 @@ +package com.wafflestudio.csereal.core.research.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.core.member.database.ProfessorEntity +import jakarta.persistence.Entity +import jakarta.persistence.OneToMany + +@Entity(name = "lab") +class LabEntity( + + val name: String, + + @OneToMany(mappedBy = "lab") + val professors: MutableSet = mutableSetOf() +) : BaseTimeEntity() diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabRepository.kt new file mode 100644 index 00000000..84bf190a --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabRepository.kt @@ -0,0 +1,6 @@ +package com.wafflestudio.csereal.core.research.database + +import org.springframework.data.jpa.repository.JpaRepository + +interface LabRepository : JpaRepository { +} From 92a8e8b5b3b991a78f82c43be376eded8cb228a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=ED=98=81=EC=A4=80=20=28HyukJoon=20Woo=29?= Date: Tue, 1 Aug 2023 01:07:57 +0900 Subject: [PATCH 007/124] =?UTF-8?q?Docs:=20Swagger=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?(#7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Docs: Add swagger dependency * Docs: Add basic config for swagger * Docs: Add basic configuration for swagger. --- build.gradle.kts | 1 + .../csereal/common/config/OpenApiConfig.kt | 21 +++++++++++++++++++ src/main/resources/application.yaml | 11 +++++----- 3 files changed, 28 insertions(+), 5 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/common/config/OpenApiConfig.kt diff --git a/build.gradle.kts b/build.gradle.kts index 7d5145d6..a0b28802 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -27,6 +27,7 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-validation") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.jetbrains.kotlin:kotlin-reflect") + implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0") runtimeOnly("com.mysql:mysql-connector-j") testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.springframework.security:spring-security-test") diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/OpenApiConfig.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/OpenApiConfig.kt new file mode 100644 index 00000000..d78341ae --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/OpenApiConfig.kt @@ -0,0 +1,21 @@ +package com.wafflestudio.csereal.common.config + +import io.swagger.v3.oas.models.Components +import io.swagger.v3.oas.models.OpenAPI +import io.swagger.v3.oas.models.info.Info +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class OpenApiConfig { + @Bean + fun openAPI(): OpenAPI { + val info = Info() + .title("컴퓨터공학부 홈페이지 백엔드 API") + .description("컴퓨터공학부 홈페이지 백엔드 API 명세서입니다.") + + return OpenAPI() + .components(Components()) + .info(info) + } +} \ No newline at end of file diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 35b3da2a..6af687e6 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -2,14 +2,15 @@ spring: profiles: active: local +springdoc: + swagger-ui: + path: index.html + api-docs: + path: /api-docs/json + --- spring: config.activate.on-profile: local - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://127.0.0.1:3306/csereal_local?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Seoul - username: root - password: toor jpa: hibernate: ddl-auto: update From 00566f2d4d36b67613856184629ac04514fd8671 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Tue, 1 Aug 2023 21:21:59 +0900 Subject: [PATCH 008/124] =?UTF-8?q?feat:=20=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EC=85=98+=EA=B2=80=EC=83=89=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80=20(#5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: isPublic, isSlide, isPinned 추가 * feat: queryDsl 적용 위해 gradle 추가 fix: javax -> jakarta 변경 * feat: queryDsl 도입 * feat: 키워드+태그 검색 추가 * feat: search query에 isDeleted, isPublic 추가 및 isPinned 우선순위 설정 * fix: requestBody -> requestParam 수정 * feat: 페이지네이션 추가 * fix: 키워드 booleanBuilder 추가, application.yaml 수정 * fix: searchNotice readOnly 추가 * fix: SearchRequest 삭제 * fix: NoticeDto tags 추가 * fix: pr 리뷰 수정 / feat: 검색 기능 보강 및 수정 * fix:코드 수정 * fix: SearchResponse isPinned 추가 * fix: SearchResponse에 total 추가 * fix: 페이지 개수 수정 * fix: searchNotice queryDsl 오류 수정 * fix: local 설정 변경 --- build.gradle.kts | 19 +++-- .../csereal/common/config/QueryDslConfig.kt | 17 ++++ .../core/notice/api/NoticeController.kt | 24 +++--- .../core/notice/database/NoticeEntity.kt | 12 +-- .../core/notice/database/NoticeRepository.kt | 77 ++++++++++++++++++- .../core/notice/dto/CreateNoticeRequest.kt | 8 +- .../csereal/core/notice/dto/NoticeDto.kt | 14 ++-- .../csereal/core/notice/dto/SearchDto.kt | 13 ++++ .../csereal/core/notice/dto/SearchResponse.kt | 8 ++ .../core/notice/dto/UpdateNoticeRequest.kt | 5 +- .../core/notice/service/NoticeService.kt | 38 +++++++-- src/main/resources/application.yaml | 10 ++- 12 files changed, 203 insertions(+), 42 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/common/config/QueryDslConfig.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/SearchDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/SearchResponse.kt diff --git a/build.gradle.kts b/build.gradle.kts index a0b28802..4acfd1ae 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,6 +6,7 @@ plugins { kotlin("jvm") version "1.7.22" kotlin("plugin.spring") version "1.7.22" kotlin("plugin.jpa") version "1.7.22" + kotlin("kapt") version "1.7.10" } group = "com.wafflestudio" @@ -31,17 +32,23 @@ dependencies { runtimeOnly("com.mysql:mysql-connector-j") testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.springframework.security:spring-security-test") + + //queryDsl + implementation ("com.querydsl:querydsl-jpa:5.0.0:jakarta") + kapt("com.querydsl:querydsl-apt:5.0.0:jakarta") + kapt("jakarta.annotation:jakarta.annotation-api") + kapt("jakarta.persistence:jakarta.persistence-api") } noArg { - annotation("javax.persistence.Entity") - annotation("javax.persistence.MappedSuperclass") - annotation("javax.persistence.Embeddable") + annotation("jakarta.persistence.Entity") + annotation("jakarta.persistence.MappedSuperclass") + annotation("jakarta.persistence.Embeddable") } allOpen { - annotation("javax.persistence.Entity") - annotation("javax.persistence.MappedSuperclass") - annotation("javax.persistence.Embeddable") + annotation("jakarta.persistence.Entity") + annotation("jakarta.persistence.MappedSuperclass") + annotation("jakarta.persistence.Embeddable") } tasks.withType { diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/QueryDslConfig.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/QueryDslConfig.kt new file mode 100644 index 00000000..ff32458d --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/QueryDslConfig.kt @@ -0,0 +1,17 @@ +package com.wafflestudio.csereal.common.config + +import com.querydsl.jpa.impl.JPAQueryFactory +import jakarta.persistence.EntityManager +import jakarta.persistence.PersistenceContext +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class QueryDslConfig( + @PersistenceContext + val entityManager: EntityManager, +) { + @Bean + fun jpaQueryFactory(): JPAQueryFactory = + JPAQueryFactory(entityManager) +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt index 21131bff..82e47c0d 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt @@ -1,8 +1,6 @@ package com.wafflestudio.csereal.core.notice.api -import com.wafflestudio.csereal.core.notice.dto.CreateNoticeRequest -import com.wafflestudio.csereal.core.notice.dto.NoticeDto -import com.wafflestudio.csereal.core.notice.dto.UpdateNoticeRequest +import com.wafflestudio.csereal.core.notice.dto.* import com.wafflestudio.csereal.core.notice.service.NoticeService import jakarta.validation.Valid import org.springframework.http.HttpStatus @@ -14,26 +12,34 @@ import org.springframework.web.bind.annotation.* class NoticeController( private val noticeService: NoticeService, ) { + @GetMapping + fun searchNotice( + @RequestParam(required = false) tag : List?, + @RequestParam(required = false) keyword: String?, + @RequestParam(required = false, defaultValue = "0") pageNum: Long + ): ResponseEntity { + return ResponseEntity.ok(noticeService.searchNotice(tag, keyword, pageNum)) + } @GetMapping("/{noticeId}") fun readNotice( @PathVariable noticeId: Long, - ) : NoticeDto { - return noticeService.readNotice(noticeId) + ) : ResponseEntity { + return ResponseEntity.ok(noticeService.readNotice(noticeId)) } @PostMapping fun createNotice( @Valid @RequestBody request: CreateNoticeRequest - ) : NoticeDto { - return noticeService.createNotice(request) + ) : ResponseEntity { + return ResponseEntity.ok(noticeService.createNotice(request)) } @PatchMapping("/{noticeId}") fun updateNotice( @PathVariable noticeId: Long, @Valid @RequestBody request: UpdateNoticeRequest, - ) : NoticeDto { - return noticeService.updateNotice(noticeId, request) + ) : ResponseEntity { + return ResponseEntity.ok(noticeService.updateNotice(noticeId, request)) } @DeleteMapping("/{noticeId}") diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt index 9fdc013a..2268cb16 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt @@ -20,12 +20,12 @@ class NoticeEntity( var description: String, // var postType: String, -// -// var isPublic: Boolean, -// -// var isSlide: Boolean, -// -// var isPinned: Boolean, + + var isPublic: Boolean, + + var isSlide: Boolean, + + var isPinned: Boolean, @OneToMany(mappedBy = "notice", cascade = [CascadeType.ALL]) var noticeTags: MutableSet = mutableSetOf() diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt index 0ec7f774..56661cae 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt @@ -1,6 +1,81 @@ package com.wafflestudio.csereal.core.notice.database +import com.querydsl.core.BooleanBuilder +import com.querydsl.core.types.Projections +import com.querydsl.jpa.impl.JPAQueryFactory +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.notice.database.QNoticeEntity.noticeEntity +import com.wafflestudio.csereal.core.notice.database.QNoticeTagEntity.noticeTagEntity +import com.wafflestudio.csereal.core.notice.dto.NoticeDto +import com.wafflestudio.csereal.core.notice.dto.SearchDto +import com.wafflestudio.csereal.core.notice.dto.SearchResponse import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Component + +interface NoticeRepository : JpaRepository, CustomNoticeRepository { +} + +interface CustomNoticeRepository { + fun searchNotice(tag: List?, keyword: String?, pageNum: Long): SearchResponse +} + +@Component +class NoticeRepositoryImpl( + private val queryFactory: JPAQueryFactory, +) : CustomNoticeRepository { + override fun searchNotice(tag: List?, keyword: String?, pageNum: Long): SearchResponse { + val keywordBooleanBuilder = BooleanBuilder() + val tagsBooleanBuilder = BooleanBuilder() + + if (!keyword.isNullOrEmpty()) { + + val keywordList = keyword.split("[^a-zA-Z0-9가-힣]".toRegex()) + keywordList.forEach { + if (it.length == 1) { + throw CserealException.Csereal400("각각의 키워드는 한글자 이상이어야 합니다.") + } else { + keywordBooleanBuilder.and( + noticeEntity.title.contains(it) + .or(noticeEntity.description.contains(it)) + ) + } + } + + } + if (!tag.isNullOrEmpty()) { + tag.forEach { + tagsBooleanBuilder.or( + noticeTagEntity.tag.id.eq(it) + ) + } + } + + val total = queryFactory.select(noticeEntity) + .from(noticeEntity).leftJoin(noticeTagEntity).on(noticeTagEntity.notice.eq(noticeEntity)) + .where(noticeEntity.isDeleted.eq(false), noticeEntity.isPublic.eq(true)) + .where(keywordBooleanBuilder).where(tagsBooleanBuilder).fetch().size + + val list = queryFactory.select( + Projections.constructor( + SearchDto::class.java, + noticeEntity.id, + noticeEntity.title, + noticeEntity.createdAt, + noticeEntity.isPinned + ) + ).from(noticeEntity) + .leftJoin(noticeTagEntity).on(noticeTagEntity.notice.eq(noticeEntity)) + .where(noticeEntity.isDeleted.eq(false), noticeEntity.isPublic.eq(true)) + .where(keywordBooleanBuilder) + .where(tagsBooleanBuilder) + .orderBy(noticeEntity.isPinned.desc()) + .orderBy(noticeEntity.createdAt.desc()) + .offset(20*pageNum) + .limit(20) + .distinct() + .fetch() + + return SearchResponse(total, list) + } -interface NoticeRepository : JpaRepository { } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/CreateNoticeRequest.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/CreateNoticeRequest.kt index 2c660447..0b0fda52 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/CreateNoticeRequest.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/CreateNoticeRequest.kt @@ -9,6 +9,12 @@ data class CreateNoticeRequest( @field:NotBlank(message = "내용은 비어있을 수 없습니다") val description: String, - val tags: List = emptyList() + val tags: List = emptyList(), + + val isPublic: Boolean, + + val isSlide: Boolean, + + val isPinned: Boolean, ) { } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt index a858c1dc..7aeccae8 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt @@ -10,11 +10,12 @@ data class NoticeDto( val description: String, // val postType: String, // val authorId: Int, + val tags: List, val createdAt: LocalDateTime?, val modifiedAt: LocalDateTime?, - // val isPublic: Boolean, - // val isSlide: Boolean, - // val isPinned: Boolean, + val isPublic: Boolean, + val isSlide: Boolean, + val isPinned: Boolean, ) { companion object { @@ -25,11 +26,12 @@ data class NoticeDto( title = this.title, description = this.description, // postType = this.postType, + tags = this.noticeTags.map { it.tag.id }, createdAt = this.createdAt, modifiedAt = this.modifiedAt, -// isPublic = this.isPublic, -// isSlide = this.isSlide, -// isPinned = this.isPinned, + isPublic = this.isPublic, + isSlide = this.isSlide, + isPinned = this.isPinned, ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/SearchDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/SearchDto.kt new file mode 100644 index 00000000..b05e5c11 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/SearchDto.kt @@ -0,0 +1,13 @@ +package com.wafflestudio.csereal.core.notice.dto + +import com.querydsl.core.annotations.QueryProjection +import java.time.LocalDateTime + +data class SearchDto @QueryProjection constructor( + val noticeId: Long, + val title: String, + val createdDate: LocalDateTime, + val isPinned: Boolean, +) { + +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/SearchResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/SearchResponse.kt new file mode 100644 index 00000000..105a25cf --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/SearchResponse.kt @@ -0,0 +1,8 @@ +package com.wafflestudio.csereal.core.notice.dto + +data class SearchResponse( + val total: Int, + val searchList: List +) { + +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/UpdateNoticeRequest.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/UpdateNoticeRequest.kt index ea45d723..e7fd1fa9 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/UpdateNoticeRequest.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/UpdateNoticeRequest.kt @@ -3,6 +3,9 @@ package com.wafflestudio.csereal.core.notice.dto data class UpdateNoticeRequest( val title: String?, val description: String?, - val tags: List? + val tags: List?, + val isPublic: Boolean?, + val isSlide: Boolean?, + val isPinned: Boolean?, ) { } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt index 168bed68..36956733 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt @@ -2,14 +2,13 @@ package com.wafflestudio.csereal.core.notice.service import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.core.notice.database.* -import com.wafflestudio.csereal.core.notice.dto.CreateNoticeRequest -import com.wafflestudio.csereal.core.notice.dto.NoticeDto -import com.wafflestudio.csereal.core.notice.dto.UpdateNoticeRequest +import com.wafflestudio.csereal.core.notice.dto.* import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional interface NoticeService { + fun searchNotice(tag: List?, keyword: String?, pageNum: Long): SearchResponse fun readNotice(noticeId: Long): NoticeDto fun createNotice(request: CreateNoticeRequest): NoticeDto fun updateNotice(noticeId: Long, request: UpdateNoticeRequest): NoticeDto @@ -24,10 +23,20 @@ class NoticeServiceImpl( private val noticeTagRepository: NoticeTagRepository ) : NoticeService { + @Transactional(readOnly = true) + override fun searchNotice( + tag: List?, + keyword: String?, + pageNum: Long + ): SearchResponse { + return noticeRepository.searchNotice(tag, keyword, pageNum) + } + @Transactional(readOnly = true) override fun readNotice(noticeId: Long): NoticeDto { val notice: NoticeEntity = noticeRepository.findByIdOrNull(noticeId) ?: throw CserealException.Csereal400("존재하지 않는 공지사항입니다.(noticeId: $noticeId)") + if (notice.isDeleted) throw CserealException.Csereal400("삭제된 공지사항입니다.(noticeId: $noticeId)") return NoticeDto.of(notice) } @@ -37,6 +46,9 @@ class NoticeServiceImpl( val newNotice = NoticeEntity( title = request.title, description = request.description, + isPublic = request.isPublic, + isSlide = request.isSlide, + isPinned = request.isPinned, ) for (tagId in request.tags) { @@ -58,19 +70,29 @@ class NoticeServiceImpl( notice.title = request.title ?: notice.title notice.description = request.description ?: notice.description + notice.isPublic = request.isPublic ?: notice.isPublic + notice.isSlide = request.isSlide ?: notice.isSlide + notice.isPinned = request.isPinned ?: notice.isPinned if (request.tags != null) { noticeTagRepository.deleteAllByNoticeId(noticeId) - notice.noticeTags.clear() + + // 원래 태그에서 겹치는 태그만 남기고, 나머지는 없애기 + notice.noticeTags = notice.noticeTags.filter { request.tags.contains(it.tag.id) }.toMutableSet() for (tagId in request.tags) { - val tag = tagRepository.findByIdOrNull(tagId) ?: throw CserealException.Csereal400("해당하는 태그가 없습니다") - NoticeTagEntity.createNoticeTag(notice, tag) + // 겹치는 거 말고, 새로운 태그만 추가 + if(!notice.noticeTags.map { it.tag.id }.contains(tagId)) { + val tag = tagRepository.findByIdOrNull(tagId) ?: throw CserealException.Csereal400("해당하는 태그가 없습니다") + NoticeTagEntity.createNoticeTag(notice, tag) + } } } + return NoticeDto.of(notice) + + - return NoticeDto.of(notice) } @Transactional @@ -89,5 +111,5 @@ class NoticeServiceImpl( tagRepository.save(newTag) } - //TODO: 이미지 등록, 페이지네이션, 검색 + //TODO: 이미지 등록, 글쓴이 함께 조회 } \ No newline at end of file diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 6af687e6..02d1312c 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -11,15 +11,17 @@ springdoc: --- spring: config.activate.on-profile: local + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/csereal_local?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Seoul + username: root + password: password + jpa: hibernate: ddl-auto: update show-sql: true open-in-view: false - datasource: - url: jdbc:mysql://localhost:3306/csereal - username: root - password: password logging.level: default: INFO From 38de652f4b9e7eb0abd5f8e64054f252b3563366 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=ED=98=81=EC=A4=80=20=28HyukJoon=20Woo=29?= Date: Tue, 1 Aug 2023 21:23:20 +0900 Subject: [PATCH 009/124] =?UTF-8?q?CICD:=20=EB=B0=B0=ED=8F=AC=20=EC=9E=90?= =?UTF-8?q?=EB=8F=99=ED=99=94=20(#6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * CICD: Change expose port and added image tag * CICD: Change ddl-auto to create in prod profile for test * CICD: Added Deploy github action * CICD: Merge jobs to one job * Fix: Change checkout order to first step * CICD: Add context for docker build action * Fix: Change spring profile arg position * CICD: Change openjdk version to 17 * CICD: Change docker compose build image tag to latest * CICD: Change to use ghcr repository. * Fix: change list to string in docker push tags. * Fix: Change registry to ghcr.io * Fix: change env to pass to github action instead of ssh export command * Fix: unwrap bracket. * Fix: wrap Profile with "" * CICD: Add .env file * CICD: Change prod ddl-auto to create (for developing), and add TODO comment. * CICD: Remove cicd/deploy branch for condition. --- .github/workflows/deploy.yaml | 82 +++++++++++++++++++++++++++++ Dockerfile | 4 +- docker-compose.yml | 3 +- src/main/resources/application.yaml | 10 ++-- 4 files changed, 91 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/deploy.yaml diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml new file mode 100644 index 00000000..43e5f370 --- /dev/null +++ b/.github/workflows/deploy.yaml @@ -0,0 +1,82 @@ +on: + push: + branches: + - main + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + + steps: + - + name: Checkout + uses: actions/checkout@v3 + + - + name: Setup Java JDK + uses: actions/setup-java@v3.12.0 + with: + java-version: '17' + distribution: 'adopt' + + - + run: ./gradlew clean bootJar -x test + + - + name: Log in to the Container Registry + uses: docker/login-action@v2.2.0 + with: + registry: ghcr.io + username: ${{github.actor}} + password: ${{secrets.GITHUB_TOKEN}} + + - + name: Build and push Docker image + uses: docker/build-push-action@v4.1.1 + with: + context: . + push: true + tags: | + ghcr.io/wafflestudio/csereal-server/server_image:latest + ghcr.io/wafflestudio/csereal-server/server_image:${{github.sha}} + + - + name: Create .env file + run: | + echo "MYSQL_ROOT_PASSWORD=${{secrets.MYSQL_ROOT_PASSWORD}}" > .env + echo "MYSQL_USER=${{secrets.MYSQL_USER}}" >> .env + echo "MYSQL_PASSWORD=${{secrets.MYSQL_PASSWORD}}" >> .env + echo "MYSQL_DATABASE=${{secrets.MYSQL_DATABASE}}" >> .env + echo "PROFILE=prod" >> .env + + - + name: SCP Command to Transfer Files + uses: appleboy/scp-action@v0.1.4 + with: + host: ${{secrets.SSH_HOST}} + username: ${{secrets.SSH_USER}} + key: ${{secrets.SSH_KEY}} + source: "docker-compose.yml, .env" + target: "~/app" + overwrite: true + - + name: SSH Remote Commands + uses: appleboy/ssh-action@v1.0.0 + env: + MYSQL_ROOT_PASSWORD: ${{secrets.MYSQL_ROOT_PASSWORD}} + MYSQL_USER: ${{secrets.MYSQL_USER}} + MYSQL_PASSWORD: ${{secrets.MYSQL_PASSWORD}} + MYSQL_DATABASE: ${{secrets.MYSQL_DATABASE}} + PROFILE: "prod" + with: + host: ${{secrets.SSH_HOST}} + username: ${{secrets.SSH_USER}} + key: ${{secrets.SSH_KEY}} + script: | + cd ~/app + docker-compose down + docker-compose pull + docker-compose up -d \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 92e86d2b..cecb2839 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM openjdk:18-slim +FROM openjdk:17-slim ARG PROFILE=prod @@ -9,4 +9,4 @@ COPY ./build/libs/*.jar /app/app.jar EXPOSE 8080 -ENTRYPOINT ["java", "-jar", "app.jar", "-Dspring.profiles.active=${PROFILE}"] \ No newline at end of file +ENTRYPOINT ["java", "-jar", "-Dspring.profiles.active=$PROFILE", "app.jar"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 40e187c7..9cf741c8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,7 @@ services: args: PROFILE: ${PROFILE} ports: - - 8080:8080 + - 80:8080 environment: SPRING_DATASOURCE_URL: "jdbc:mysql://csereal_db_container:3306/${MYSQL_DATABASE}?serverTimezone=Asia/Seoul&useSSL=false&allowPublicKeyRetrieval=true" SPRING_DATASOURCE_USERNAME: ${MYSQL_USER} @@ -15,6 +15,7 @@ services: - db networks: - csereal_network + image: ghcr.io/wafflestudio/csereal-server/server_image:latest db: container_name: csereal_db_container image: mysql:8.0 diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 02d1312c..8ffade6f 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -2,6 +2,8 @@ spring: profiles: active: local +datasource: + driver-class-name: com.mysql.cj.jdbc.Driver springdoc: swagger-ui: path: index.html @@ -12,11 +14,9 @@ springdoc: spring: config.activate.on-profile: local datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://127.0.0.1:3306/csereal_local?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Seoul + url: jdbc:mysql://127.0.0.1:3306/csereal?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Seoul username: root password: password - jpa: hibernate: ddl-auto: update @@ -30,5 +30,5 @@ spring: config.activate.on-profile: prod jpa: hibernate: - ddl-auto: none - open-in-view: false + ddl-auto: create # TODO: change to validate (or none) when save actual data to server + open-in-view: false \ No newline at end of file From 1a75adfc8d4cbd5abd9ac7a3f6b0adb8c10b080a Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Sat, 5 Aug 2023 12:51:50 +0900 Subject: [PATCH 010/124] =?UTF-8?q?feat:=20=EA=B5=AC=EC=84=B1=EC=9B=90(?= =?UTF-8?q?=EA=B5=90=EC=88=98)=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20API=20(#9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 교수 조회시 최종학력이 앞으로 오게끔 정렬 * feat: 교수 수정 및 삭제 API * feat: 학력과 경력 엔티티 필드 변경, 수정 API 구현 --- .../core/member/api/ProfessorController.kt | 14 ++++ .../core/member/database/CareerEntity.kt | 9 +-- .../core/member/database/EducationEntity.kt | 20 +---- .../core/member/database/ProfessorEntity.kt | 20 ++++- .../csereal/core/member/dto/CareerDto.kt | 19 ----- .../csereal/core/member/dto/EducationDto.kt | 22 ------ .../csereal/core/member/dto/ProfessorDto.kt | 8 +- .../core/member/service/ProfessorService.kt | 74 +++++++++++++++---- 8 files changed, 101 insertions(+), 85 deletions(-) delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/dto/CareerDto.kt delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/dto/EducationDto.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt index 437c2544..993ddba8 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt @@ -32,4 +32,18 @@ class ProfessorController( return ResponseEntity.ok(professorService.getInactiveProfessors()) } + @PatchMapping("/{professorId}") + fun updateProfessor( + @PathVariable professorId: Long, + @RequestBody updateProfessorRequest: ProfessorDto + ): ResponseEntity { + return ResponseEntity.ok(professorService.updateProfessor(professorId, updateProfessorRequest)) + } + + @DeleteMapping("/{professorId}") + fun deleteProfessor(@PathVariable professorId: Long): ResponseEntity { + professorService.deleteProfessor(professorId) + return ResponseEntity.ok().build() + } + } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/CareerEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/CareerEntity.kt index 30df5357..103d42a9 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/CareerEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/CareerEntity.kt @@ -1,7 +1,6 @@ package com.wafflestudio.csereal.core.member.database import com.wafflestudio.csereal.common.config.BaseTimeEntity -import com.wafflestudio.csereal.core.member.dto.CareerDto import jakarta.persistence.Entity import jakarta.persistence.FetchType import jakarta.persistence.JoinColumn @@ -9,20 +8,16 @@ import jakarta.persistence.ManyToOne @Entity(name = "career") class CareerEntity( - val duration: String, val name: String, - val workplace: String, @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "professor_id") val professor: ProfessorEntity ) : BaseTimeEntity() { companion object { - fun create(career: CareerDto, professor: ProfessorEntity): CareerEntity { + fun create(name: String, professor: ProfessorEntity): CareerEntity { val careerEntity = CareerEntity( - duration = career.duration, - name = career.name, - workplace = career.workplace, + name = name, professor = professor ) professor.careers.add(careerEntity) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/EducationEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/EducationEntity.kt index 7bdae723..ff4559ce 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/EducationEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/EducationEntity.kt @@ -1,30 +1,20 @@ package com.wafflestudio.csereal.core.member.database import com.wafflestudio.csereal.common.config.BaseTimeEntity -import com.wafflestudio.csereal.core.member.dto.EducationDto import jakarta.persistence.* @Entity(name = "education") class EducationEntity( - val university: String, - val major: String, - - @Enumerated(EnumType.STRING) - val degree: Degree, - - val year: Int, + val name: String, @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "professor_id") val professor: ProfessorEntity ) : BaseTimeEntity() { companion object { - fun create(education: EducationDto, professor: ProfessorEntity): EducationEntity { + fun create(name: String, professor: ProfessorEntity): EducationEntity { val educationEntity = EducationEntity( - university = education.university, - major = education.major, - degree = education.degree, - year = education.year, + name = name, professor = professor ) professor.educations.add(educationEntity) @@ -32,7 +22,3 @@ class EducationEntity( } } } - -enum class Degree { - Bachelor, Master, PhD -} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt index 09efde16..8e72b964 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt @@ -13,7 +13,7 @@ import java.time.LocalDate @Entity(name = "professor") class ProfessorEntity( - val name: String, + var name: String, //val profileImage:File var isActive: Boolean, var academicRank: String, @@ -24,7 +24,7 @@ class ProfessorEntity( var startDate: LocalDate?, var endDate: LocalDate?, - val office: String?, + var office: String?, var phone: String?, var fax: String?, var email: String?, @@ -34,7 +34,7 @@ class ProfessorEntity( val educations: MutableList = mutableListOf(), @OneToMany(mappedBy = "professor", cascade = [CascadeType.ALL], orphanRemoval = true) - val researchAreas: MutableSet = mutableSetOf(), + val researchAreas: MutableList = mutableListOf(), @OneToMany(mappedBy = "professor", cascade = [CascadeType.ALL], orphanRemoval = true) val careers: MutableList = mutableListOf(), @@ -59,7 +59,21 @@ class ProfessorEntity( } fun addLab(lab: LabEntity) { + this.lab?.professors?.remove(this) this.lab = lab lab.professors.add(this) } + + fun update(updateProfessorRequest: ProfessorDto) { + this.name = updateProfessorRequest.name + this.isActive = updateProfessorRequest.isActive + this.academicRank = updateProfessorRequest.academicRank + this.startDate = updateProfessorRequest.startDate + this.endDate = updateProfessorRequest.endDate + this.office = updateProfessorRequest.office + this.phone = updateProfessorRequest.phone + this.fax = updateProfessorRequest.fax + this.email = updateProfessorRequest.email + this.website = updateProfessorRequest.website + } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/CareerDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/CareerDto.kt deleted file mode 100644 index ff14cfb2..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/CareerDto.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.wafflestudio.csereal.core.member.dto - -import com.wafflestudio.csereal.core.member.database.CareerEntity - -data class CareerDto( - val duration: String, - val name: String, - val workplace: String -) { - companion object { - fun of(careerEntity: CareerEntity): CareerDto { - return CareerDto( - duration = careerEntity.duration, - name = careerEntity.name, - workplace = careerEntity.workplace - ) - } - } -} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/EducationDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/EducationDto.kt deleted file mode 100644 index 48e2c968..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/EducationDto.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.wafflestudio.csereal.core.member.dto - -import com.wafflestudio.csereal.core.member.database.Degree -import com.wafflestudio.csereal.core.member.database.EducationEntity - -data class EducationDto( - val university: String, - val major: String, - val degree: Degree, - val year: Int -) { - companion object { - fun of(educationEntity: EducationEntity): EducationDto { - return EducationDto( - university = educationEntity.university, - major = educationEntity.major, - degree = educationEntity.degree, - year = educationEntity.year - ) - } - } -} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/ProfessorDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/ProfessorDto.kt index d4a52290..84372eee 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/ProfessorDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/ProfessorDto.kt @@ -18,9 +18,9 @@ data class ProfessorDto( val fax: String?, val email: String?, val website: String?, - val educations: List, + val educations: List, val researchAreas: List, - val careers: List + val careers: List ) { companion object { fun of(professorEntity: ProfessorEntity): ProfessorDto { @@ -37,9 +37,9 @@ data class ProfessorDto( fax = professorEntity.fax, email = professorEntity.email, website = professorEntity.website, - educations = professorEntity.educations.map { EducationDto.of(it) }, + educations = professorEntity.educations.map { it.name }, researchAreas = professorEntity.researchAreas.map { it.name }, - careers = professorEntity.careers.map { CareerDto.of(it) } + careers = professorEntity.careers.map { it.name } ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt index d2f8b582..9adb7df6 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt @@ -10,11 +10,11 @@ import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional interface ProfessorService { - fun createProfessor(professorDto: ProfessorDto): ProfessorDto + fun createProfessor(createProfessorRequest: ProfessorDto): ProfessorDto fun getProfessor(professorId: Long): ProfessorDto fun getActiveProfessors(): List fun getInactiveProfessors(): List - fun updateProfessor(updateProfessorRequest: ProfessorDto): ProfessorDto + fun updateProfessor(professorId: Long, updateProfessorRequest: ProfessorDto): ProfessorDto fun deleteProfessor(professorId: Long) } @@ -25,24 +25,24 @@ class ProfessorServiceImpl( private val professorRepository: ProfessorRepository ) : ProfessorService { - override fun createProfessor(professorDto: ProfessorDto): ProfessorDto { - val professor = ProfessorEntity.of(professorDto) + override fun createProfessor(createProfessorRequest: ProfessorDto): ProfessorDto { + val professor = ProfessorEntity.of(createProfessorRequest) - if (professorDto.labId != null) { - val lab = labRepository.findByIdOrNull(professorDto.labId) - ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다. LabId: ${professorDto.labId}") + if (createProfessorRequest.labId != null) { + val lab = labRepository.findByIdOrNull(createProfessorRequest.labId) + ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다. LabId: ${createProfessorRequest.labId}") professor.addLab(lab) } - for (education in professorDto.educations) { + for (education in createProfessorRequest.educations) { EducationEntity.create(education, professor) } - for (researchArea in professorDto.researchAreas) { + for (researchArea in createProfessorRequest.researchAreas) { ResearchAreaEntity.create(researchArea, professor) } - for (career in professorDto.careers) { + for (career in createProfessorRequest.careers) { CareerEntity.create(career, professor) } @@ -68,12 +68,60 @@ class ProfessorServiceImpl( return professorRepository.findByIsActiveFalse().map { SimpleProfessorDto.of(it) } } - override fun updateProfessor(updateProfessorRequest: ProfessorDto): ProfessorDto { - TODO("Not yet implemented") + override fun updateProfessor(professorId: Long, updateProfessorRequest: ProfessorDto): ProfessorDto { + + val professor = professorRepository.findByIdOrNull(professorId) + ?: throw CserealException.Csereal404("해당 교수님을 찾을 수 없습니다. professorId: ${professorId}") + + if (updateProfessorRequest.labId != null && updateProfessorRequest.labId != professor.lab?.id) { + val lab = labRepository.findByIdOrNull(updateProfessorRequest.labId) + ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다. LabId: ${updateProfessorRequest.labId}") + professor.addLab(lab) + } + + professor.update(updateProfessorRequest) + + // 학력 업데이트 + val oldEducations = professor.educations.map { it.name } + + val educationsToRemove = oldEducations - updateProfessorRequest.educations + val educationsToAdd = updateProfessorRequest.educations - oldEducations + + professor.educations.removeIf { it.name in educationsToRemove } + + for (education in educationsToAdd) { + EducationEntity.create(education, professor) + } + + // 연구 분야 업데이트 + val oldResearchAreas = professor.researchAreas.map { it.name } + + val researchAreasToRemove = oldResearchAreas - updateProfessorRequest.researchAreas + val researchAreasToAdd = updateProfessorRequest.researchAreas - oldResearchAreas + + professor.researchAreas.removeIf { it.name in researchAreasToRemove } + + for (researchArea in researchAreasToAdd) { + ResearchAreaEntity.create(researchArea, professor) + } + + // 경력 업데이트 + val oldCareers = professor.careers.map { it.name } + + val careersToRemove = oldCareers - updateProfessorRequest.careers + val careersToAdd = updateProfessorRequest.careers - oldCareers + + professor.careers.removeIf { it.name in careersToRemove } + + for (career in careersToAdd) { + CareerEntity.create(career, professor) + } + + return ProfessorDto.of(professor) } override fun deleteProfessor(professorId: Long) { - TODO("Not yet implemented") + professorRepository.deleteById(professorId) } } From 42249ba6885f34634e80ecd3e5d7d2640b0327c8 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Sat, 5 Aug 2023 12:52:25 +0900 Subject: [PATCH 011/124] =?UTF-8?q?feat:=20=EA=B5=AC=EC=84=B1=EC=9B=90(?= =?UTF-8?q?=ED=96=89=EC=A0=95=EC=A7=81=EC=9B=90)=20CRUD=20API=20(#10)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 행정직원 엔티티 및 DTO 설계 * feat: 행정직원 CRUD * feat: 교수 조회시 이름순 정렬 * fix: 교수 연구 분야 Set -> List 로 변경 * feat: 행정직원 주요업무 업데이트 구현 --- .../core/member/api/StaffController.kt | 46 +++++++++++ .../core/member/database/StaffEntity.kt | 44 +++++++++++ .../core/member/database/StaffRepository.kt | 6 ++ .../core/member/database/TaskEntity.kt | 27 +++++++ .../csereal/core/member/dto/StaffDto.kt | 29 +++++++ .../core/member/service/ProfessorService.kt | 4 +- .../core/member/service/StaffService.kt | 76 +++++++++++++++++++ 7 files changed, 230 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffRepository.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/database/TaskEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/dto/StaffDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt new file mode 100644 index 00000000..f10aae6f --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt @@ -0,0 +1,46 @@ +package com.wafflestudio.csereal.core.member.api + +import com.wafflestudio.csereal.core.member.dto.StaffDto +import com.wafflestudio.csereal.core.member.service.StaffService +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.DeleteMapping +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PatchMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RequestMapping("/staff") +@RestController +class StaffController( + private val staffService: StaffService +) { + + @PostMapping + fun createStaff(@RequestBody createStaffRequest: StaffDto): ResponseEntity { + return ResponseEntity.ok(staffService.createStaff(createStaffRequest)) + } + + @GetMapping("/{staffId}") + fun getStaff(@PathVariable staffId: Long): ResponseEntity { + return ResponseEntity.ok(staffService.getStaff(staffId)) + } + + @GetMapping + fun getAllStaff(): ResponseEntity> { + return ResponseEntity.ok(staffService.getAllStaff()) + } + + @PatchMapping("/{staffId}") + fun updateStaff(@PathVariable staffId: Long, @RequestBody updateStaffRequest: StaffDto): ResponseEntity { + return ResponseEntity.ok(staffService.updateStaff(staffId, updateStaffRequest)) + } + + @DeleteMapping("/{staffId}") + fun deleteStaff(@PathVariable staffId: Long): ResponseEntity { + staffService.deleteStaff(staffId) + return ResponseEntity.ok().build() + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffEntity.kt new file mode 100644 index 00000000..f950ed43 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffEntity.kt @@ -0,0 +1,44 @@ +package com.wafflestudio.csereal.core.member.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.core.member.dto.StaffDto +import jakarta.persistence.CascadeType +import jakarta.persistence.Entity +import jakarta.persistence.OneToMany + +@Entity(name = "staff") +class StaffEntity( + var name: String, + var role: String, + + // profileImage + + var office: String, + var phone: String, + var email: String, + + @OneToMany(mappedBy = "staff", cascade = [CascadeType.ALL], orphanRemoval = true) + val tasks: MutableList = mutableListOf() + +) : BaseTimeEntity() { + + companion object { + fun of(staffDto: StaffDto): StaffEntity { + return StaffEntity( + name = staffDto.name, + role = staffDto.role, + office = staffDto.office, + phone = staffDto.phone, + email = staffDto.email + ) + } + } + + fun update(staffDto: StaffDto) { + this.name = staffDto.name + this.role = staffDto.role + this.office = staffDto.office + this.phone = staffDto.phone + this.email = staffDto.email + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffRepository.kt new file mode 100644 index 00000000..d85ab2d9 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffRepository.kt @@ -0,0 +1,6 @@ +package com.wafflestudio.csereal.core.member.database + +import org.springframework.data.jpa.repository.JpaRepository + +interface StaffRepository : JpaRepository { +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/TaskEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/TaskEntity.kt new file mode 100644 index 00000000..745365dd --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/TaskEntity.kt @@ -0,0 +1,27 @@ +package com.wafflestudio.csereal.core.member.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import jakarta.persistence.Entity +import jakarta.persistence.FetchType +import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne + +@Entity +class TaskEntity( + val name: String, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "staff_id") + val staff: StaffEntity +) : BaseTimeEntity() { + companion object { + fun create(name: String, staff: StaffEntity): TaskEntity { + val task = TaskEntity( + name = name, + staff = staff + ) + staff.tasks.add(task) + return task + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/StaffDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/StaffDto.kt new file mode 100644 index 00000000..dfdec200 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/StaffDto.kt @@ -0,0 +1,29 @@ +package com.wafflestudio.csereal.core.member.dto + +import com.fasterxml.jackson.annotation.JsonInclude +import com.wafflestudio.csereal.core.member.database.StaffEntity + +data class StaffDto( + @JsonInclude(JsonInclude.Include.NON_NULL) + var id: Long? = null, + val name: String, + val role: String, + val office: String, + val phone: String, + val email: String, + val tasks: List +) { + companion object { + fun of(staffEntity: StaffEntity): StaffDto { + return StaffDto( + id = staffEntity.id, + name = staffEntity.name, + role = staffEntity.role, + office = staffEntity.office, + phone = staffEntity.phone, + email = staffEntity.email, + tasks = staffEntity.tasks.map { it.name } + ) + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt index 9adb7df6..ad025f8f 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt @@ -60,12 +60,12 @@ class ProfessorServiceImpl( @Transactional(readOnly = true) override fun getActiveProfessors(): List { - return professorRepository.findByIsActiveTrue().map { SimpleProfessorDto.of(it) } + return professorRepository.findByIsActiveTrue().map { SimpleProfessorDto.of(it) }.sortedBy { it.name } } @Transactional(readOnly = true) override fun getInactiveProfessors(): List { - return professorRepository.findByIsActiveFalse().map { SimpleProfessorDto.of(it) } + return professorRepository.findByIsActiveFalse().map { SimpleProfessorDto.of(it) }.sortedBy { it.name } } override fun updateProfessor(professorId: Long, updateProfessorRequest: ProfessorDto): ProfessorDto { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt new file mode 100644 index 00000000..e7249113 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt @@ -0,0 +1,76 @@ +package com.wafflestudio.csereal.core.member.service + +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.member.database.StaffEntity +import com.wafflestudio.csereal.core.member.database.StaffRepository +import com.wafflestudio.csereal.core.member.database.TaskEntity +import com.wafflestudio.csereal.core.member.dto.StaffDto +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +interface StaffService { + fun createStaff(createStaffRequest: StaffDto): StaffDto + fun getStaff(staffId: Long): StaffDto + fun getAllStaff(): List + fun updateStaff(staffId: Long, updateStaffRequest: StaffDto): StaffDto + fun deleteStaff(staffId: Long) +} + +@Service +@Transactional +class StaffServiceImpl( + private val staffRepository: StaffRepository +) : StaffService { + override fun createStaff(createStaffRequest: StaffDto): StaffDto { + val staff = StaffEntity.of(createStaffRequest) + + for (task in createStaffRequest.tasks) { + TaskEntity.create(task, staff) + } + + staffRepository.save(staff) + + return StaffDto.of(staff) + } + + @Transactional(readOnly = true) + override fun getStaff(staffId: Long): StaffDto { + val staff = staffRepository.findByIdOrNull(staffId) + ?: throw CserealException.Csereal404("해당 행정직원을 찾을 수 없습니다. staffId: ${staffId}") + return StaffDto.of(staff) + } + + @Transactional(readOnly = true) + override fun getAllStaff(): List { + return staffRepository.findAll().map { StaffDto.of(it) }.sortedBy { it.name } + } + + override fun updateStaff(staffId: Long, updateStaffRequest: StaffDto): StaffDto { + + val staff = staffRepository.findByIdOrNull(staffId) + ?: throw CserealException.Csereal404("해당 행정직원을 찾을 수 없습니다. staffId: ${staffId}") + + staff.update(updateStaffRequest) + + // 주요 업무 업데이트 + val oldTasks = staff.tasks.map { it.name } + + val tasksToRemove = oldTasks - updateStaffRequest.tasks + val tasksToAdd = updateStaffRequest.tasks - oldTasks + + staff.tasks.removeIf { it.name in tasksToRemove } + + for (task in tasksToAdd) { + TaskEntity.create(task, staff) + } + + return StaffDto.of(staff) + } + + override fun deleteStaff(staffId: Long) { + staffRepository.deleteById(staffId) + } + + +} From 41bb4fdcd1388ad5b225ad8260ad32dff0fb8d62 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Tue, 8 Aug 2023 21:12:40 +0900 Subject: [PATCH 012/124] =?UTF-8?q?feat:=20news=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=20=EC=B6=94=EA=B0=80,=20=EB=94=94=EB=B2=A8=EB=A1=AD?= =?UTF-8?q?=20=EB=B0=8F=20=ED=94=84=EB=A1=A0=ED=8A=B8=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EA=B2=8C=20=EC=97=94=ED=8B=B0=ED=8B=B0=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: news 패키지 생성 * feat: readNews 생성, news 패키지 추가로 인한 명칭 변경 * feat: createNews, enrollTag(새소식) 추가, news 패키지로 인한 명칭 추가 변경 * feat: updateNews, deleteNews 추가 * fix: searchNotice 관련 명칭 변경 * feat: searchNews 추가 * fix: develop 브랜치 반영, 프론트 요구사항 반영 * feat: readNotice, readNews에 이전글 다음글 추가 * 태그 업데이트 코드 리팩터링중 * refactor: 코드 수정, 이전제목 추가 * fix: 게시글 하나일때 read 가능 * fix: prevNext null 없애기 * fix: 이전글 다음글 null 수정 --- build.gradle.kts | 3 + .../csereal/core/news/api/NewsController.kt | 64 ++++++++ .../csereal/core/news/database/NewsEntity.kt | 37 +++++ .../core/news/database/NewsRepository.kt | 139 ++++++++++++++++++ .../core/news/database/NewsTagEntity.kt | 28 ++++ .../core/news/database/NewsTagRepository.kt | 9 ++ .../core/news/database/TagInNewsEntity.kt | 14 ++ .../news/database/TageInNewsRepository.kt | 7 + .../csereal/core/news/dto/NewsDto.kt | 41 ++++++ .../csereal/core/news/dto/NewsSearchDto.kt | 12 ++ .../core/news/dto/NewsSearchResponse.kt | 10 ++ .../csereal/core/news/service/NewsService.kt | 113 ++++++++++++++ .../core/notice/api/NoticeController.kt | 12 +- .../core/notice/database/NoticeEntity.kt | 12 +- .../core/notice/database/NoticeRepository.kt | 101 ++++++++++--- .../core/notice/database/NoticeTagEntity.kt | 4 +- .../notice/database/NoticeTagRepository.kt | 1 + .../{TagEntity.kt => TagInNoticeEntity.kt} | 5 +- .../notice/database/TagInNoticeRepository.kt | 7 + .../core/notice/database/TagRepository.kt | 6 - .../core/notice/dto/CreateNoticeRequest.kt | 20 --- .../csereal/core/notice/dto/NoticeDto.kt | 18 ++- .../dto/{SearchDto.kt => NoticeSearchDto.kt} | 6 +- ...rchResponse.kt => NoticeSearchResponse.kt} | 4 +- .../core/notice/dto/UpdateNoticeRequest.kt | 11 -- .../core/notice/service/NoticeService.kt | 87 +++++------ 26 files changed, 643 insertions(+), 128 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsTagEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsTagRepository.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/news/database/TagInNewsEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/news/database/TageInNewsRepository.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchResponse.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt rename src/main/kotlin/com/wafflestudio/csereal/core/notice/database/{TagEntity.kt => TagInNoticeEntity.kt} (81%) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeRepository.kt delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagRepository.kt delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/CreateNoticeRequest.kt rename src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/{SearchDto.kt => NoticeSearchDto.kt} (63%) rename src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/{SearchResponse.kt => NoticeSearchResponse.kt} (50%) delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/UpdateNoticeRequest.kt diff --git a/build.gradle.kts b/build.gradle.kts index 4acfd1ae..3e5a2ca5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -38,6 +38,9 @@ dependencies { kapt("com.querydsl:querydsl-apt:5.0.0:jakarta") kapt("jakarta.annotation:jakarta.annotation-api") kapt("jakarta.persistence:jakarta.persistence-api") + + // 태그 제거 + implementation("org.jsoup:jsoup:1.15.4") } noArg { annotation("jakarta.persistence.Entity") diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt new file mode 100644 index 00000000..4dea9962 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt @@ -0,0 +1,64 @@ +package com.wafflestudio.csereal.core.news.api + +import com.wafflestudio.csereal.core.news.dto.NewsDto +import com.wafflestudio.csereal.core.news.dto.NewsSearchResponse +import com.wafflestudio.csereal.core.news.service.NewsService +import jakarta.validation.Valid +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* + +@RequestMapping("/news") +@RestController +class NewsController( + private val newsService: NewsService, +) { + @GetMapping + fun searchNews( + @RequestParam(required = false) tag: List?, + @RequestParam(required = false) keyword: String?, + @RequestParam(required = false, defaultValue = "0") pageNum:Long + ) : ResponseEntity { + return ResponseEntity.ok(newsService.searchNews(tag, keyword, pageNum)) + } + @GetMapping("/{newsId}") + fun readNews( + @PathVariable newsId: Long, + @RequestParam(required = false) tag : List?, + @RequestParam(required = false) keyword: String?, + ) : ResponseEntity { + return ResponseEntity.ok(newsService.readNews(newsId, tag, keyword)) + } + + @PostMapping + fun createNews( + @Valid @RequestBody request: NewsDto + ) : ResponseEntity { + return ResponseEntity.ok(newsService.createNews(request)) + } + + @PatchMapping("/{newsId}") + fun updateNews( + @PathVariable newsId: Long, + @Valid @RequestBody request: NewsDto, + ) : ResponseEntity { + return ResponseEntity.ok(newsService.updateNews(newsId, request)) + } + + @DeleteMapping("/{newsId}") + fun deleteNews( + @PathVariable newsId: Long + ) { + newsService.deleteNews(newsId) + } + + @PostMapping("/tag") + fun enrollTag( + @RequestBody tagName: Map + ) : ResponseEntity { + newsService.enrollTag(tagName["name"]!!) + return ResponseEntity("등록되었습니다. (tagName: ${tagName["name"]})", HttpStatus.OK) + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt new file mode 100644 index 00000000..a2944a97 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt @@ -0,0 +1,37 @@ +package com.wafflestudio.csereal.core.news.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.core.news.dto.NewsDto +import jakarta.persistence.CascadeType +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.OneToMany + +@Entity(name = "news") +class NewsEntity( + + var isDeleted: Boolean = false, + + var title: String, + + var description: String, + + var isPublic: Boolean, + + var isSlide: Boolean, + + // 새소식 작성란에도 "가장 위에 표시"가 있더라고요, 혹시 쓸지도 모르니까 남겼습니다 + var isPinned: Boolean, + + @OneToMany(mappedBy = "news", cascade = [CascadeType.ALL]) + var newsTags: MutableSet = mutableSetOf() + +): BaseTimeEntity() { + fun update(updateNewsRequest: NewsDto) { + this.title = updateNewsRequest.title + this.description = updateNewsRequest.description + this.isPublic = updateNewsRequest.isPublic + this.isSlide = updateNewsRequest.isSlide + this.isPinned = updateNewsRequest.isPinned + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt new file mode 100644 index 00000000..3edf6858 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt @@ -0,0 +1,139 @@ +package com.wafflestudio.csereal.core.news.database + +import com.querydsl.core.BooleanBuilder +import com.querydsl.jpa.impl.JPAQueryFactory +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.news.database.QNewsEntity.newsEntity +import com.wafflestudio.csereal.core.news.database.QNewsTagEntity.newsTagEntity +import com.wafflestudio.csereal.core.news.dto.NewsSearchDto +import com.wafflestudio.csereal.core.news.dto.NewsSearchResponse +import org.jsoup.Jsoup +import org.jsoup.parser.Parser +import org.jsoup.safety.Safelist +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Component + +interface NewsRepository : JpaRepository, CustomNewsRepository { + +} + +interface CustomNewsRepository { + fun searchNews(tag: List?, keyword: String?, pageNum: Long): NewsSearchResponse + fun findPrevNextId(newsId: Long, tag: List?, keyword: String?): Array? +} + +@Component +class NewsRepositoryImpl( + private val queryFactory: JPAQueryFactory, +) : CustomNewsRepository { + override fun searchNews(tag: List?, keyword: String?, pageNum: Long): NewsSearchResponse { + val keywordBooleanBuilder = BooleanBuilder() + val tagsBooleanBuilder = BooleanBuilder() + + if(!keyword.isNullOrEmpty()) { + val keywordList = keyword.split("[^a-zA-Z0-9가-힣]".toRegex()) + keywordList.forEach { + if(it.length == 1) { + throw CserealException.Csereal400("각각의 키워드는 한글자 이상이어야 합니다.") + } else { + keywordBooleanBuilder.and( + newsEntity.title.contains(it) + .or(newsEntity.description.contains(it)) + ) + } + } + } + if(!tag.isNullOrEmpty()) { + tag.forEach { + tagsBooleanBuilder.or( + newsTagEntity.tag.name.eq(it) + ) + } + } + + val jpaQuery = queryFactory.select(newsEntity).from(newsEntity) + .leftJoin(newsTagEntity).on(newsTagEntity.news.eq(newsEntity)) + .where(newsEntity.isDeleted.eq(false), newsEntity.isPublic.eq(true)) + .where(keywordBooleanBuilder).where(tagsBooleanBuilder) + + val total = jpaQuery.distinct().fetch().size + + val newsEntityList = jpaQuery.orderBy(newsEntity.isPinned.desc()) + .orderBy(newsEntity.createdAt.desc()) + .offset(20*pageNum) //로컬 테스트를 위해 잠시 5로 둘 것, 원래는 20 + .limit(20) + .distinct() + .fetch() + + val newsSearchDtoList : List = newsEntityList.map { + NewsSearchDto( + id = it.id, + title = it.title, + summary = summary(it.description), + createdAt = it.createdAt, + tags = it.newsTags.map { newsTagEntity -> newsTagEntity.tag.id } + ) + } + return NewsSearchResponse(total, newsSearchDtoList) + } + + override fun findPrevNextId(newsId: Long, tag: List?, keyword: String?): Array? { + val keywordBooleanBuilder = BooleanBuilder() + val tagsBooleanBuilder = BooleanBuilder() + + if (!keyword.isNullOrEmpty()) { + val keywordList = keyword.split("[^a-zA-Z0-9가-힣]".toRegex()) + keywordList.forEach { + if(it.length == 1) { + throw CserealException.Csereal400("각각의 키워드는 한글자 이상이어야 합니다.") + } else { + keywordBooleanBuilder.and( + newsEntity.title.contains(it) + .or(newsEntity.description.contains(it)) + ) + } + + } + } + if(!tag.isNullOrEmpty()) { + tag.forEach { + tagsBooleanBuilder.or( + newsTagEntity.tag.name.eq(it) + ) + } + } + + val newsSearchDtoList = queryFactory.select(newsEntity).from(newsEntity) + .leftJoin(newsTagEntity).on(newsTagEntity.news.eq(newsEntity)) + .where(newsEntity.isDeleted.eq(false), newsEntity.isPublic.eq(true)) + .where(keywordBooleanBuilder).where(tagsBooleanBuilder) + .orderBy(newsEntity.isPinned.desc()) + .orderBy(newsEntity.createdAt.desc()) + .distinct() + .fetch() + + + val findingId = newsSearchDtoList.indexOfFirst { it.id == newsId } + + val prevNext: Array? + if(findingId == -1) { + prevNext = null + } else if(findingId != 0 && findingId != newsSearchDtoList.size-1) { + prevNext = arrayOf(newsSearchDtoList[findingId+1], newsSearchDtoList[findingId-1]) + } else if(findingId == 0) { + if(newsSearchDtoList.size == 1) { + prevNext = arrayOf(null, null) + } else { + prevNext = arrayOf(newsSearchDtoList[1], null) + } + } else { + prevNext = arrayOf(null, newsSearchDtoList[newsSearchDtoList.size-2]) + } + + return prevNext + } + private fun summary(description: String): String { + val summary = Jsoup.clean(description, Safelist.none()) + return Parser.unescapeEntities(summary, false) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsTagEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsTagEntity.kt new file mode 100644 index 00000000..d328f109 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsTagEntity.kt @@ -0,0 +1,28 @@ +package com.wafflestudio.csereal.core.news.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import jakarta.persistence.Entity +import jakarta.persistence.FetchType +import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne + +@Entity(name = "newsTag") +class NewsTagEntity( + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "news_id") + val news: NewsEntity, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "tag_id") + var tag: TagInNewsEntity, + + ) : BaseTimeEntity() { + + companion object { + fun createNewsTag(news: NewsEntity, tag: TagInNewsEntity) { + val newsTag = NewsTagEntity(news, tag) + news.newsTags.add(newsTag) + tag.newsTags.add(newsTag) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsTagRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsTagRepository.kt new file mode 100644 index 00000000..3a947ac1 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsTagRepository.kt @@ -0,0 +1,9 @@ +package com.wafflestudio.csereal.core.news.database + +import org.springframework.data.jpa.repository.JpaRepository + +interface NewsTagRepository : JpaRepository { + fun deleteAllByNewsId(newsId: Long) + fun deleteByNewsIdAndTagId(newsId: Long, tagId: Long) + +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/TagInNewsEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/TagInNewsEntity.kt new file mode 100644 index 00000000..16487e95 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/TagInNewsEntity.kt @@ -0,0 +1,14 @@ +package com.wafflestudio.csereal.core.news.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import jakarta.persistence.Entity +import jakarta.persistence.OneToMany + +@Entity(name = "tag_in_news") +class TagInNewsEntity( + var name: String, + + @OneToMany(mappedBy = "tag") + val newsTags: MutableSet = mutableSetOf() +) : BaseTimeEntity() { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/TageInNewsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/TageInNewsRepository.kt new file mode 100644 index 00000000..db4e2c6f --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/TageInNewsRepository.kt @@ -0,0 +1,7 @@ +package com.wafflestudio.csereal.core.news.database + +import org.springframework.data.jpa.repository.JpaRepository + +interface TagInNewsRepository : JpaRepository { + fun findByName(tagName: String): TagInNewsEntity? +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt new file mode 100644 index 00000000..da7f987c --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt @@ -0,0 +1,41 @@ +package com.wafflestudio.csereal.core.news.dto + +import com.wafflestudio.csereal.core.news.database.NewsEntity +import java.time.LocalDateTime + +data class NewsDto( + val id: Long, + val title: String, + val description: String, + val tags: List, + val createdAt: LocalDateTime?, + val modifiedAt: LocalDateTime?, + val isPublic: Boolean, + val isSlide: Boolean, + val isPinned: Boolean, + val prevId: Long?, + val prevTitle: String?, + val nextId: Long?, + val nextTitle: String?, +) { + companion object { + fun of(entity: NewsEntity, prevNext: Array?) : NewsDto = entity.run { + NewsDto( + id = this.id, + title = this.title, + description = this.description, + tags = this.newsTags.map { it.tag.name }, + createdAt = this.createdAt, + modifiedAt = this.modifiedAt, + isPublic = this.isPublic, + isSlide = this.isSlide, + isPinned = this.isPinned, + prevId = prevNext?.get(0)?.id, + prevTitle = prevNext?.get(0)?.title, + nextId = prevNext?.get(1)?.id, + nextTitle = prevNext?.get(1)?.title, + + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchDto.kt new file mode 100644 index 00000000..0d3cae9f --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchDto.kt @@ -0,0 +1,12 @@ +package com.wafflestudio.csereal.core.news.dto + +import com.querydsl.core.annotations.QueryProjection +import java.time.LocalDateTime + +data class NewsSearchDto @QueryProjection constructor( + val id: Long, + val title: String, + var summary: String, + val createdAt: LocalDateTime?, + var tags: List? +) \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchResponse.kt new file mode 100644 index 00000000..92a3a63c --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchResponse.kt @@ -0,0 +1,10 @@ +package com.wafflestudio.csereal.core.news.dto + +import com.querydsl.core.annotations.QueryProjection +import java.time.LocalDateTime + +class NewsSearchResponse @QueryProjection constructor( + val total: Int, + val searchList: List +) { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt new file mode 100644 index 00000000..eb638dfb --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt @@ -0,0 +1,113 @@ +package com.wafflestudio.csereal.core.news.service + +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.news.database.* +import com.wafflestudio.csereal.core.news.dto.NewsDto +import com.wafflestudio.csereal.core.news.dto.NewsSearchResponse +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.util.* + +interface NewsService { + fun searchNews(tag: List?, keyword: String?, pageNum: Long): NewsSearchResponse + fun readNews(newsId: Long, tag: List?, keyword: String?): NewsDto + fun createNews(request: NewsDto): NewsDto + fun updateNews(newsId: Long, request: NewsDto): NewsDto + fun deleteNews(newsId: Long) + fun enrollTag(tagName: String) +} + +@Service +class NewsServiceImpl( + private val newsRepository: NewsRepository, + private val tagInNewsRepository: TagInNewsRepository, + private val newsTagRepository: NewsTagRepository +) : NewsService { + @Transactional(readOnly = true) + override fun searchNews( + tag: List?, + keyword: String?, + pageNum: Long + ): NewsSearchResponse { + return newsRepository.searchNews(tag, keyword, pageNum) + } + + @Transactional(readOnly = true) + override fun readNews( + newsId: Long, + tag: List?, + keyword: String? + ): NewsDto { + val news: NewsEntity = newsRepository.findByIdOrNull(newsId) + ?: throw CserealException.Csereal404("존재하지 않는 새소식입니다.(newsId: $newsId)") + + if (news.isDeleted) throw CserealException.Csereal404("삭제된 새소식입니다.(newsId: $newsId)") + + val prevNext = newsRepository.findPrevNextId(newsId, tag, keyword) + ?: throw CserealException.Csereal400("이전글 다음글이 존재하지 않습니다.(newsId=$newsId)") + + return NewsDto.of(news, prevNext) + } + + @Transactional + override fun createNews(request: NewsDto): NewsDto { + val newNews = NewsEntity( + title = request.title, + description = request.description, + isPublic = request.isPublic, + isSlide = request.isSlide, + isPinned = request.isPinned + ) + + for (tagName in request.tags) { + val tag = tagInNewsRepository.findByName(tagName) ?: throw CserealException.Csereal404("해당하는 태그가 없습니다") + NewsTagEntity.createNewsTag(newNews, tag) + } + + newsRepository.save(newNews) + + return NewsDto.of(newNews, null) + } + + @Transactional + override fun updateNews(newsId: Long, request: NewsDto): NewsDto { + val news: NewsEntity = newsRepository.findByIdOrNull(newsId) + ?: throw CserealException.Csereal404("존재하지 않는 새소식입니다. (newsId: $newsId)") + if (news.isDeleted) throw CserealException.Csereal404("삭제된 새소식입니다.") + news.update(request) + + val oldTags = news.newsTags.map { it.tag.name } + + val tagsToRemove = oldTags - request.tags + val tagsToAdd = request.tags - oldTags + + for(tagName in tagsToRemove) { + val tagId = tagInNewsRepository.findByName(tagName)!!.id + news.newsTags.removeIf { it.tag.name == tagName } + newsTagRepository.deleteByNewsIdAndTagId(newsId, tagId) + } + + for (tagName in tagsToAdd) { + val tag = tagInNewsRepository.findByName(tagName) ?: throw CserealException.Csereal404("해당하는 태그가 없습니다") + NewsTagEntity.createNewsTag(news,tag) + } + + return NewsDto.of(news, null) + } + + @Transactional + override fun deleteNews(newsId: Long) { + val news: NewsEntity = newsRepository.findByIdOrNull(newsId) + ?: throw CserealException.Csereal404("존재하지 않는 새소식입니다.(newsId: $newsId") + + news.isDeleted = true + } + + override fun enrollTag(tagName: String) { + val newTag = TagInNewsEntity( + name = tagName + ) + tagInNewsRepository.save(newTag) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt index 82e47c0d..cb7919bf 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt @@ -14,22 +14,24 @@ class NoticeController( ) { @GetMapping fun searchNotice( - @RequestParam(required = false) tag : List?, + @RequestParam(required = false) tag : List?, @RequestParam(required = false) keyword: String?, @RequestParam(required = false, defaultValue = "0") pageNum: Long - ): ResponseEntity { + ): ResponseEntity { return ResponseEntity.ok(noticeService.searchNotice(tag, keyword, pageNum)) } @GetMapping("/{noticeId}") fun readNotice( @PathVariable noticeId: Long, + @RequestParam(required = false) tag : List?, + @RequestParam(required = false) keyword: String?, ) : ResponseEntity { - return ResponseEntity.ok(noticeService.readNotice(noticeId)) + return ResponseEntity.ok(noticeService.readNotice(noticeId,tag,keyword)) } @PostMapping fun createNotice( - @Valid @RequestBody request: CreateNoticeRequest + @Valid @RequestBody request: NoticeDto ) : ResponseEntity { return ResponseEntity.ok(noticeService.createNotice(request)) } @@ -37,7 +39,7 @@ class NoticeController( @PatchMapping("/{noticeId}") fun updateNotice( @PathVariable noticeId: Long, - @Valid @RequestBody request: UpdateNoticeRequest, + @Valid @RequestBody request: NoticeDto, ) : ResponseEntity { return ResponseEntity.ok(noticeService.updateNotice(noticeId, request)) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt index 2268cb16..2a543517 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt @@ -1,6 +1,7 @@ package com.wafflestudio.csereal.core.notice.database import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.core.notice.dto.NoticeDto import jakarta.persistence.CascadeType import jakarta.persistence.Column import jakarta.persistence.Entity @@ -10,13 +11,10 @@ import jakarta.persistence.OneToMany @Entity(name = "notice") class NoticeEntity( - @Column var isDeleted: Boolean = false, - @Column var title: String, - @Column(columnDefinition = "text") var description: String, // var postType: String, @@ -31,6 +29,12 @@ class NoticeEntity( var noticeTags: MutableSet = mutableSetOf() ): BaseTimeEntity() { - + fun update(updateNoticeRequest: NoticeDto) { + this.title = updateNoticeRequest.title + this.description = updateNoticeRequest.description + this.isPublic = updateNoticeRequest.isPublic + this.isSlide = updateNoticeRequest.isSlide + this.isPinned = updateNoticeRequest.isPinned + } } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt index 56661cae..bf3f8471 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt @@ -1,14 +1,12 @@ package com.wafflestudio.csereal.core.notice.database import com.querydsl.core.BooleanBuilder -import com.querydsl.core.types.Projections import com.querydsl.jpa.impl.JPAQueryFactory import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.core.notice.database.QNoticeEntity.noticeEntity import com.wafflestudio.csereal.core.notice.database.QNoticeTagEntity.noticeTagEntity -import com.wafflestudio.csereal.core.notice.dto.NoticeDto -import com.wafflestudio.csereal.core.notice.dto.SearchDto -import com.wafflestudio.csereal.core.notice.dto.SearchResponse +import com.wafflestudio.csereal.core.notice.dto.NoticeSearchDto +import com.wafflestudio.csereal.core.notice.dto.NoticeSearchResponse import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Component @@ -16,14 +14,15 @@ interface NoticeRepository : JpaRepository, CustomNoticeRepo } interface CustomNoticeRepository { - fun searchNotice(tag: List?, keyword: String?, pageNum: Long): SearchResponse + fun searchNotice(tag: List?, keyword: String?, pageNum: Long): NoticeSearchResponse + fun findPrevNextId(noticeId: Long, tag: List?, keyword: String?): Array? } @Component class NoticeRepositoryImpl( private val queryFactory: JPAQueryFactory, ) : CustomNoticeRepository { - override fun searchNotice(tag: List?, keyword: String?, pageNum: Long): SearchResponse { + override fun searchNotice(tag: List?, keyword: String?, pageNum: Long): NoticeSearchResponse { val keywordBooleanBuilder = BooleanBuilder() val tagsBooleanBuilder = BooleanBuilder() @@ -45,37 +44,91 @@ class NoticeRepositoryImpl( if (!tag.isNullOrEmpty()) { tag.forEach { tagsBooleanBuilder.or( - noticeTagEntity.tag.id.eq(it) + noticeTagEntity.tag.name.eq(it) ) } } - val total = queryFactory.select(noticeEntity) - .from(noticeEntity).leftJoin(noticeTagEntity).on(noticeTagEntity.notice.eq(noticeEntity)) + val jpaQuery = queryFactory.select(noticeEntity).from(noticeEntity) + .leftJoin(noticeTagEntity).on(noticeTagEntity.notice.eq(noticeEntity)) .where(noticeEntity.isDeleted.eq(false), noticeEntity.isPublic.eq(true)) - .where(keywordBooleanBuilder).where(tagsBooleanBuilder).fetch().size - - val list = queryFactory.select( - Projections.constructor( - SearchDto::class.java, - noticeEntity.id, - noticeEntity.title, - noticeEntity.createdAt, - noticeEntity.isPinned + .where(keywordBooleanBuilder).where(tagsBooleanBuilder) + + val total = jpaQuery.distinct().fetch().size + + val noticeEntityList = jpaQuery.orderBy(noticeEntity.isPinned.desc()) + .orderBy(noticeEntity.createdAt.desc()) + .offset(20*pageNum) //로컬 테스트를 위해 잠시 5로 둘 것, 원래는 20 + .limit(20) + .distinct() + .fetch() + + val noticeSearchDtoList : List = noticeEntityList.map { + NoticeSearchDto( + id = it.id, + title = it.title, + createdAt = it.createdAt, + isPinned = it.isPinned, ) - ).from(noticeEntity) + } + + return NoticeSearchResponse(total, noticeSearchDtoList) + } + + override fun findPrevNextId(noticeId: Long, tag: List?, keyword: String?): Array? { + val keywordBooleanBuilder = BooleanBuilder() + val tagsBooleanBuilder = BooleanBuilder() + + if (!keyword.isNullOrEmpty()) { + val keywordList = keyword.split("[^a-zA-Z0-9가-힣]".toRegex()) + keywordList.forEach { + if(it.length == 1) { + throw CserealException.Csereal400("각각의 키워드는 한글자 이상이어야 합니다.") + } else { + keywordBooleanBuilder.and( + noticeEntity.title.contains(it) + .or(noticeEntity.description.contains(it)) + ) + } + + } + } + if(!tag.isNullOrEmpty()) { + tag.forEach { + tagsBooleanBuilder.or( + noticeTagEntity.tag.name.eq(it) + ) + } + } + + val noticeSearchDtoList = queryFactory.select(noticeEntity).from(noticeEntity) .leftJoin(noticeTagEntity).on(noticeTagEntity.notice.eq(noticeEntity)) .where(noticeEntity.isDeleted.eq(false), noticeEntity.isPublic.eq(true)) - .where(keywordBooleanBuilder) - .where(tagsBooleanBuilder) + .where(keywordBooleanBuilder).where(tagsBooleanBuilder) .orderBy(noticeEntity.isPinned.desc()) .orderBy(noticeEntity.createdAt.desc()) - .offset(20*pageNum) - .limit(20) .distinct() .fetch() - return SearchResponse(total, list) + val findingId = noticeSearchDtoList.indexOfFirst {it.id == noticeId} + + val prevNext : Array? + if(findingId == -1) { + prevNext = arrayOf(null, null) + } else if(findingId != 0 && findingId != noticeSearchDtoList.size-1) { + prevNext = arrayOf(noticeSearchDtoList[findingId+1], noticeSearchDtoList[findingId-1]) + } else if(findingId == 0) { + if(noticeSearchDtoList.size == 1) { + prevNext = arrayOf(null, null) + } else { + prevNext = arrayOf(noticeSearchDtoList[1],null) + } + } else { + prevNext = arrayOf(null, noticeSearchDtoList[noticeSearchDtoList.size-2]) + } + + return prevNext + } } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeTagEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeTagEntity.kt index c910dabd..34ba5df3 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeTagEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeTagEntity.kt @@ -11,12 +11,12 @@ class NoticeTagEntity( @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "tag_id") - var tag: TagEntity, + var tag: TagInNoticeEntity, ) : BaseTimeEntity() { companion object { - fun createNoticeTag(notice: NoticeEntity, tag: TagEntity) { + fun createNoticeTag(notice: NoticeEntity, tag: TagInNoticeEntity) { val noticeTag = NoticeTagEntity(notice, tag) notice.noticeTags.add(noticeTag) tag.noticeTags.add(noticeTag) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeTagRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeTagRepository.kt index 39a6ce98..fc5e3f6b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeTagRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeTagRepository.kt @@ -4,4 +4,5 @@ import org.springframework.data.jpa.repository.JpaRepository interface NoticeTagRepository : JpaRepository { fun deleteAllByNoticeId(noticeId: Long) + fun deleteByNoticeIdAndTagId(noticeId: Long, tagId: Long) } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeEntity.kt similarity index 81% rename from src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagEntity.kt rename to src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeEntity.kt index 67c108b9..1eb44da7 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeEntity.kt @@ -1,12 +1,11 @@ package com.wafflestudio.csereal.core.notice.database import com.wafflestudio.csereal.common.config.BaseTimeEntity -import jakarta.persistence.CascadeType import jakarta.persistence.Entity import jakarta.persistence.OneToMany -@Entity(name = "tag") -class TagEntity( +@Entity(name = "tag_in_notice") +class TagInNoticeEntity( var name: String, @OneToMany(mappedBy = "tag") diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeRepository.kt new file mode 100644 index 00000000..d576b129 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeRepository.kt @@ -0,0 +1,7 @@ +package com.wafflestudio.csereal.core.notice.database + +import org.springframework.data.jpa.repository.JpaRepository + +interface TagInNoticeRepository : JpaRepository { + fun findByName(tagName: String): TagInNoticeEntity? +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagRepository.kt deleted file mode 100644 index 34a50416..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagRepository.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.wafflestudio.csereal.core.notice.database - -import org.springframework.data.jpa.repository.JpaRepository - -interface TagRepository : JpaRepository { -} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/CreateNoticeRequest.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/CreateNoticeRequest.kt deleted file mode 100644 index 0b0fda52..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/CreateNoticeRequest.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.wafflestudio.csereal.core.notice.dto - -import jakarta.validation.constraints.NotBlank - -data class CreateNoticeRequest( - @field:NotBlank(message = "제목은 비어있을 수 없습니다") - val title: String, - - @field:NotBlank(message = "내용은 비어있을 수 없습니다") - val description: String, - - val tags: List = emptyList(), - - val isPublic: Boolean, - - val isSlide: Boolean, - - val isPinned: Boolean, -) { -} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt index 7aeccae8..baf6e9fc 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt @@ -5,33 +5,37 @@ import java.time.LocalDateTime data class NoticeDto( val id: Long, - val isDeleted: Boolean, val title: String, val description: String, - // val postType: String, // val authorId: Int, - val tags: List, + val tags: List, val createdAt: LocalDateTime?, val modifiedAt: LocalDateTime?, val isPublic: Boolean, val isSlide: Boolean, val isPinned: Boolean, + val prevId: Long?, + val prevTitle: String?, + val nextId: Long?, + val nextTitle: String? ) { companion object { - fun of(entity: NoticeEntity): NoticeDto = entity.run { + fun of(entity: NoticeEntity, prevNext: Array?): NoticeDto = entity.run { NoticeDto( id = this.id, - isDeleted = false, title = this.title, description = this.description, - // postType = this.postType, - tags = this.noticeTags.map { it.tag.id }, + tags = this.noticeTags.map { it.tag.name }, createdAt = this.createdAt, modifiedAt = this.modifiedAt, isPublic = this.isPublic, isSlide = this.isSlide, isPinned = this.isPinned, + prevId = prevNext?.get(0)?.id, + prevTitle = prevNext?.get(0)?.title, + nextId = prevNext?.get(1)?.id, + nextTitle = prevNext?.get(1)?.title ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/SearchDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeSearchDto.kt similarity index 63% rename from src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/SearchDto.kt rename to src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeSearchDto.kt index b05e5c11..89898ebb 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/SearchDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeSearchDto.kt @@ -3,10 +3,10 @@ package com.wafflestudio.csereal.core.notice.dto import com.querydsl.core.annotations.QueryProjection import java.time.LocalDateTime -data class SearchDto @QueryProjection constructor( - val noticeId: Long, +data class NoticeSearchDto @QueryProjection constructor( + val id: Long, val title: String, - val createdDate: LocalDateTime, + val createdAt: LocalDateTime?, val isPinned: Boolean, ) { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/SearchResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeSearchResponse.kt similarity index 50% rename from src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/SearchResponse.kt rename to src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeSearchResponse.kt index 105a25cf..c11e21b4 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/SearchResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeSearchResponse.kt @@ -1,8 +1,8 @@ package com.wafflestudio.csereal.core.notice.dto -data class SearchResponse( +data class NoticeSearchResponse( val total: Int, - val searchList: List + val searchList: List ) { } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/UpdateNoticeRequest.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/UpdateNoticeRequest.kt deleted file mode 100644 index e7fd1fa9..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/UpdateNoticeRequest.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.wafflestudio.csereal.core.notice.dto - -data class UpdateNoticeRequest( - val title: String?, - val description: String?, - val tags: List?, - val isPublic: Boolean?, - val isSlide: Boolean?, - val isPinned: Boolean?, -) { -} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt index 36956733..8bae04ae 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt @@ -8,10 +8,10 @@ import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional interface NoticeService { - fun searchNotice(tag: List?, keyword: String?, pageNum: Long): SearchResponse - fun readNotice(noticeId: Long): NoticeDto - fun createNotice(request: CreateNoticeRequest): NoticeDto - fun updateNotice(noticeId: Long, request: UpdateNoticeRequest): NoticeDto + fun searchNotice(tag: List?, keyword: String?, pageNum: Long): NoticeSearchResponse + fun readNotice(noticeId: Long, tag: List?, keyword: String?): NoticeDto + fun createNotice(request: NoticeDto): NoticeDto + fun updateNotice(noticeId: Long, request: NoticeDto): NoticeDto fun deleteNotice(noticeId: Long) fun enrollTag(tagName: String) } @@ -19,30 +19,37 @@ interface NoticeService { @Service class NoticeServiceImpl( private val noticeRepository: NoticeRepository, - private val tagRepository: TagRepository, + private val tagInNoticeRepository: TagInNoticeRepository, private val noticeTagRepository: NoticeTagRepository ) : NoticeService { @Transactional(readOnly = true) override fun searchNotice( - tag: List?, + tag: List?, keyword: String?, pageNum: Long - ): SearchResponse { + ): NoticeSearchResponse { return noticeRepository.searchNotice(tag, keyword, pageNum) } @Transactional(readOnly = true) - override fun readNotice(noticeId: Long): NoticeDto { + override fun readNotice( + noticeId: Long, + tag: List?, + keyword: String? + ): NoticeDto { val notice: NoticeEntity = noticeRepository.findByIdOrNull(noticeId) - ?: throw CserealException.Csereal400("존재하지 않는 공지사항입니다.(noticeId: $noticeId)") + ?: throw CserealException.Csereal404("존재하지 않는 공지사항입니다.(noticeId: $noticeId)") - if (notice.isDeleted) throw CserealException.Csereal400("삭제된 공지사항입니다.(noticeId: $noticeId)") - return NoticeDto.of(notice) + if (notice.isDeleted) throw CserealException.Csereal404("삭제된 공지사항입니다.(noticeId: $noticeId)") + + val prevNext = noticeRepository.findPrevNextId(noticeId, tag, keyword) + + return NoticeDto.of(notice, prevNext) } @Transactional - override fun createNotice(request: CreateNoticeRequest): NoticeDto { + override fun createNotice(request: NoticeDto): NoticeDto { val newNotice = NoticeEntity( title = request.title, description = request.description, @@ -51,44 +58,42 @@ class NoticeServiceImpl( isPinned = request.isPinned, ) - for (tagId in request.tags) { - val tag = tagRepository.findByIdOrNull(tagId) ?: throw CserealException.Csereal400("해당하는 태그가 없습니다") + for (tagName in request.tags) { + val tag = tagInNoticeRepository.findByName(tagName) ?: throw CserealException.Csereal404("해당하는 태그가 없습니다") NoticeTagEntity.createNoticeTag(newNotice, tag) } noticeRepository.save(newNotice) - return NoticeDto.of(newNotice) + return NoticeDto.of(newNotice, null) } @Transactional - override fun updateNotice(noticeId: Long, request: UpdateNoticeRequest): NoticeDto { + override fun updateNotice(noticeId: Long, request: NoticeDto): NoticeDto { val notice: NoticeEntity = noticeRepository.findByIdOrNull(noticeId) - ?: throw CserealException.Csereal400("존재하지 않는 공지사항입니다.(noticeId: $noticeId") - if (notice.isDeleted) throw CserealException.Csereal400("삭제된 공지사항입니다.(noticeId: $noticeId") - - notice.title = request.title ?: notice.title - notice.description = request.description ?: notice.description - notice.isPublic = request.isPublic ?: notice.isPublic - notice.isSlide = request.isSlide ?: notice.isSlide - notice.isPinned = request.isPinned ?: notice.isPinned - - if (request.tags != null) { - noticeTagRepository.deleteAllByNoticeId(noticeId) - - // 원래 태그에서 겹치는 태그만 남기고, 나머지는 없애기 - notice.noticeTags = notice.noticeTags.filter { request.tags.contains(it.tag.id) }.toMutableSet() - for (tagId in request.tags) { - // 겹치는 거 말고, 새로운 태그만 추가 - if(!notice.noticeTags.map { it.tag.id }.contains(tagId)) { - val tag = tagRepository.findByIdOrNull(tagId) ?: throw CserealException.Csereal400("해당하는 태그가 없습니다") - NoticeTagEntity.createNoticeTag(notice, tag) - } - } + ?: throw CserealException.Csereal404("존재하지 않는 공지사항입니다.(noticeId: $noticeId)") + if (notice.isDeleted) throw CserealException.Csereal404("삭제된 공지사항입니다.(noticeId: $noticeId)") + + notice.update(request) + + val oldTags = notice.noticeTags.map { it.tag.name } + + val tagsToRemove = oldTags - request.tags + val tagsToAdd = request.tags - oldTags + + for(tagName in tagsToRemove) { + val tagId = tagInNoticeRepository.findByName(tagName)!!.id + notice.noticeTags.removeIf { it.tag.name == tagName } + noticeTagRepository.deleteByNoticeIdAndTagId(noticeId, tagId) + } + + for(tagName in tagsToAdd) { + val tag = tagInNoticeRepository.findByName(tagName) ?: throw CserealException.Csereal404("해당하는 태그가 없습니다") + NoticeTagEntity.createNoticeTag(notice, tag) } - return NoticeDto.of(notice) + return NoticeDto.of(notice, null) @@ -98,17 +103,17 @@ class NoticeServiceImpl( @Transactional override fun deleteNotice(noticeId: Long) { val notice: NoticeEntity = noticeRepository.findByIdOrNull(noticeId) - ?: throw CserealException.Csereal400("존재하지 않는 공지사항입니다.(noticeId: $noticeId)") + ?: throw CserealException.Csereal404("존재하지 않는 공지사항입니다.(noticeId: $noticeId)") notice.isDeleted = true } override fun enrollTag(tagName: String) { - val newTag = TagEntity( + val newTag = TagInNoticeEntity( name = tagName ) - tagRepository.save(newTag) + tagInNoticeRepository.save(newTag) } //TODO: 이미지 등록, 글쓴이 함께 조회 From 7e3ea8e118a334e94de7516a9b277e2d6482159d Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Thu, 10 Aug 2023 00:37:14 +0900 Subject: [PATCH 013/124] =?UTF-8?q?fix:=20main=EC=97=90=EC=84=9C=20develop?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20pr=20(#16)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: merge develop to main (#13) * feat: 공지사항 생성, 공지사항 읽기 기능 추가 (#1) * :sparkles: 패키지 및 엔티티 생성 * :sparkles: BaseTimeEntity 생성, PostEntity 기본 내용 작성 * :sparkles: PostController 생성 및 기본 내용 작성 * :sparkles: postService 생성 * :sparkles: Exceptions.kt 생성 * :sparkles: postDto 생성 * :sparkles: postRepository 생성 및 기본 내용 작성 * :green_heart: application.yaml 로컬 환경에서 작동하도록 설정 * feat: createPost 기능 생성 * refactor: 리뷰 주신 거 수정 * refactor: post -> notice 수정 등 * chore: .idea 디렉토리 삭제 * chore: PR 템플릿 생성 (#2) * feat: 로컬 db용 docker-compose 파일 추가 및 application.yaml 수정 (#4) * feat: 공지사항 수정, 삭제, 태그 기능 추가 (#3) * fix: ExceptionHandler 추가 * feat: updateNotice 추가, valid 추가 * feat: deleteNotice 추가 * feat: enrollTag 기능 추가, noticeTag 연관 엔티티 추가 * feat: 공지사항 작성할 때 태그 생성 및 수정 * fix: 로컬 db 없앰 * fix: pr 리뷰 수정 * fix: pr 리뷰 수정 * fix: noticeTag assign * feat: 구성원(교수) 생성 및 조회 API 구현 (#8) * feat: 교수 엔티티 및 DTO 설계 * feat: 교수 생성 및 조회 * Docs: Swagger 추가 (#7) * Docs: Add swagger dependency * Docs: Add basic config for swagger * Docs: Add basic configuration for swagger. * feat: 페이지네이션+검색 기능 추가 (#5) * feat: isPublic, isSlide, isPinned 추가 * feat: queryDsl 적용 위해 gradle 추가 fix: javax -> jakarta 변경 * feat: queryDsl 도입 * feat: 키워드+태그 검색 추가 * feat: search query에 isDeleted, isPublic 추가 및 isPinned 우선순위 설정 * fix: requestBody -> requestParam 수정 * feat: 페이지네이션 추가 * fix: 키워드 booleanBuilder 추가, application.yaml 수정 * fix: searchNotice readOnly 추가 * fix: SearchRequest 삭제 * fix: NoticeDto tags 추가 * fix: pr 리뷰 수정 / feat: 검색 기능 보강 및 수정 * fix:코드 수정 * fix: SearchResponse isPinned 추가 * fix: SearchResponse에 total 추가 * fix: 페이지 개수 수정 * fix: searchNotice queryDsl 오류 수정 * fix: local 설정 변경 * CICD: 배포 자동화 (#6) * CICD: Change expose port and added image tag * CICD: Change ddl-auto to create in prod profile for test * CICD: Added Deploy github action * CICD: Merge jobs to one job * Fix: Change checkout order to first step * CICD: Add context for docker build action * Fix: Change spring profile arg position * CICD: Change openjdk version to 17 * CICD: Change docker compose build image tag to latest * CICD: Change to use ghcr repository. * Fix: change list to string in docker push tags. * Fix: Change registry to ghcr.io * Fix: change env to pass to github action instead of ssh export command * Fix: unwrap bracket. * Fix: wrap Profile with "" * CICD: Add .env file * CICD: Change prod ddl-auto to create (for developing), and add TODO comment. * CICD: Remove cicd/deploy branch for condition. * feat: 구성원(교수) 수정 및 삭제 API (#9) * feat: 교수 조회시 최종학력이 앞으로 오게끔 정렬 * feat: 교수 수정 및 삭제 API * feat: 학력과 경력 엔티티 필드 변경, 수정 API 구현 * feat: 구성원(행정직원) CRUD API (#10) * feat: 행정직원 엔티티 및 DTO 설계 * feat: 행정직원 CRUD * feat: 교수 조회시 이름순 정렬 * fix: 교수 연구 분야 Set -> List 로 변경 * feat: 행정직원 주요업무 업데이트 구현 * feat: news 패키지 추가, 디벨롭 및 프론트에 맞게 엔티티 변경 (#12) * feat: news 패키지 생성 * feat: readNews 생성, news 패키지 추가로 인한 명칭 변경 * feat: createNews, enrollTag(새소식) 추가, news 패키지로 인한 명칭 추가 변경 * feat: updateNews, deleteNews 추가 * fix: searchNotice 관련 명칭 변경 * feat: searchNews 추가 * fix: develop 브랜치 반영, 프론트 요구사항 반영 * feat: readNotice, readNews에 이전글 다음글 추가 * 태그 업데이트 코드 리팩터링중 * refactor: 코드 수정, 이전제목 추가 * fix: 게시글 하나일때 read 가능 * fix: prevNext null 없애기 * fix: 이전글 다음글 null 수정 --------- Co-authored-by: Jo Seonggyu Co-authored-by: 우혁준 (HyukJoon Woo) * hotfix: 사용하지않는 Dto 및 엔티티 삭제 (#14) --------- Co-authored-by: Junhyeong Kim Co-authored-by: 우혁준 (HyukJoon Woo) --- .../core/member/database/CareerEntity.kt | 1 - .../core/member/database/EducationEntity.kt | 1 - .../csereal/core/member/dto/CareerDto.kt | 19 ---------------- .../csereal/core/member/dto/EducationDto.kt | 22 ------------------- .../csereal/core/notice/database/TagEntity.kt | 15 ------------- .../core/notice/database/TagRepository.kt | 6 ----- 6 files changed, 64 deletions(-) delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/dto/CareerDto.kt delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/dto/EducationDto.kt delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagEntity.kt delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagRepository.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/CareerEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/CareerEntity.kt index c394d9b8..a6a40da0 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/CareerEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/CareerEntity.kt @@ -1,7 +1,6 @@ package com.wafflestudio.csereal.core.member.database import com.wafflestudio.csereal.common.config.BaseTimeEntity -import com.wafflestudio.csereal.core.member.dto.CareerDto import jakarta.persistence.Entity import jakarta.persistence.FetchType import jakarta.persistence.JoinColumn diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/EducationEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/EducationEntity.kt index a52589d0..ff4559ce 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/EducationEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/EducationEntity.kt @@ -1,7 +1,6 @@ package com.wafflestudio.csereal.core.member.database import com.wafflestudio.csereal.common.config.BaseTimeEntity -import com.wafflestudio.csereal.core.member.dto.EducationDto import jakarta.persistence.* @Entity(name = "education") diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/CareerDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/CareerDto.kt deleted file mode 100644 index ff14cfb2..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/CareerDto.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.wafflestudio.csereal.core.member.dto - -import com.wafflestudio.csereal.core.member.database.CareerEntity - -data class CareerDto( - val duration: String, - val name: String, - val workplace: String -) { - companion object { - fun of(careerEntity: CareerEntity): CareerDto { - return CareerDto( - duration = careerEntity.duration, - name = careerEntity.name, - workplace = careerEntity.workplace - ) - } - } -} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/EducationDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/EducationDto.kt deleted file mode 100644 index 48e2c968..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/EducationDto.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.wafflestudio.csereal.core.member.dto - -import com.wafflestudio.csereal.core.member.database.Degree -import com.wafflestudio.csereal.core.member.database.EducationEntity - -data class EducationDto( - val university: String, - val major: String, - val degree: Degree, - val year: Int -) { - companion object { - fun of(educationEntity: EducationEntity): EducationDto { - return EducationDto( - university = educationEntity.university, - major = educationEntity.major, - degree = educationEntity.degree, - year = educationEntity.year - ) - } - } -} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagEntity.kt deleted file mode 100644 index 67c108b9..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagEntity.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.wafflestudio.csereal.core.notice.database - -import com.wafflestudio.csereal.common.config.BaseTimeEntity -import jakarta.persistence.CascadeType -import jakarta.persistence.Entity -import jakarta.persistence.OneToMany - -@Entity(name = "tag") -class TagEntity( - var name: String, - - @OneToMany(mappedBy = "tag") - val noticeTags: MutableSet = mutableSetOf() -) : BaseTimeEntity() { -} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagRepository.kt deleted file mode 100644 index 34a50416..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagRepository.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.wafflestudio.csereal.core.notice.database - -import org.springframework.data.jpa.repository.JpaRepository - -interface TagRepository : JpaRepository { -} \ No newline at end of file From 6c5afe298e92b2cc937396ed4d7196547ee4ff84 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Fri, 11 Aug 2023 20:31:30 +0900 Subject: [PATCH 014/124] =?UTF-8?q?feat:=20seminar=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=20=EC=B6=94=EA=B0=80=20(#17)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: createSeminar, readSeminar, updateSeminar 추가 * feat: deleteSeminar, searchSeminar 추가 * fix: distinct 삭제 --- .../core/seminar/api/SeminarController.kt | 51 ++++++++ .../core/seminar/database/SeminarEntity.kt | 96 ++++++++++++++ .../seminar/database/SeminarRepository.kt | 122 ++++++++++++++++++ .../csereal/core/seminar/dto/SeminarDto.kt | 64 +++++++++ .../core/seminar/dto/SeminarSearchDto.kt | 14 ++ .../core/seminar/dto/SeminarSearchResponse.kt | 7 + .../core/seminar/service/SeminarService.kt | 67 ++++++++++ 7 files changed, 421 insertions(+) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarSearchDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarSearchResponse.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt new file mode 100644 index 00000000..8b2699d7 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt @@ -0,0 +1,51 @@ +package com.wafflestudio.csereal.core.seminar.api + +import com.wafflestudio.csereal.core.seminar.dto.SeminarDto +import com.wafflestudio.csereal.core.seminar.dto.SeminarSearchResponse +import com.wafflestudio.csereal.core.seminar.service.SeminarService +import jakarta.validation.Valid +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* + +@RequestMapping("/seminar") +@RestController +class SeminarController ( + private val seminarService: SeminarService, +) { + @GetMapping + fun searchSeminar( + @RequestParam(required = false) keyword: String?, + @RequestParam(required = false, defaultValue = "0") pageNum: Long + ) : ResponseEntity { + return ResponseEntity.ok(seminarService.searchSeminar(keyword, pageNum)) + } + @PostMapping + fun createSeminar( + @Valid @RequestBody request: SeminarDto + ) : ResponseEntity { + return ResponseEntity.ok(seminarService.createSeminar(request)) + } + + @GetMapping("/{seminarId}") + fun readSeminar( + @PathVariable seminarId: Long, + @RequestParam(required = false) keyword: String?, + ) : ResponseEntity { + return ResponseEntity.ok(seminarService.readSeminar(seminarId, keyword)) + } + + @PatchMapping("/{seminarId}") + fun updateSeminar( + @PathVariable seminarId: Long, + @Valid @RequestBody request: SeminarDto, + ) : ResponseEntity { + return ResponseEntity.ok(seminarService.updateSeminar(seminarId, request)) + } + + @DeleteMapping("/{seminarId}") + fun deleteSeminar( + @PathVariable seminarId: Long + ) { + seminarService.deleteSeminar(seminarId) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt new file mode 100644 index 00000000..bc098643 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt @@ -0,0 +1,96 @@ +package com.wafflestudio.csereal.core.seminar.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.core.seminar.dto.SeminarDto +import jakarta.persistence.Column +import jakarta.persistence.Entity + +@Entity(name = "seminar") +class SeminarEntity( + + var isDeleted: Boolean = false, + + var title: String, + + @Column(columnDefinition = "text") + var description: String, + + @Column(columnDefinition = "text") + var introduction: String, + + var category: String, + + // 연사 정보 + var name: String, + var speakerUrl: String?, + var speakerTitle: String?, + var affiliation: String, + var affiliationUrl: String?, + + var startDate: String?, + var startTime: String?, + var endDate: String?, + var endTime: String?, + + var location: String, + + var host: String?, + + // var profileImage: File, + + // var seminarFile: File, + + var isPublic: Boolean, + + var isSlide: Boolean, + + var additionalNote: String? +): BaseTimeEntity() { + + companion object { + fun of(seminarDto: SeminarDto): SeminarEntity { + return SeminarEntity( + title = seminarDto.title, + description = seminarDto.description, + introduction = seminarDto.introduction, + category = seminarDto.category, + name = seminarDto.name, + speakerUrl = seminarDto.speakerUrl, + speakerTitle = seminarDto.speakerTitle, + affiliation = seminarDto.affiliation, + affiliationUrl = seminarDto.affiliationUrl, + startDate = seminarDto.startDate, + startTime = seminarDto.startTime, + endDate = seminarDto.endDate, + endTime = seminarDto.endTime, + location = seminarDto.location, + host = seminarDto.host, + additionalNote = seminarDto.additionalNote, + isPublic = seminarDto.isPublic, + isSlide = seminarDto.isSlide + ) + } + + } + + fun update(updateSeminarRequest: SeminarDto) { + title = updateSeminarRequest.title + description = updateSeminarRequest.description + introduction = updateSeminarRequest.introduction + category = updateSeminarRequest.category + name = updateSeminarRequest.name + speakerUrl = updateSeminarRequest.speakerUrl + speakerTitle = updateSeminarRequest.speakerTitle + affiliation = updateSeminarRequest.affiliation + affiliationUrl = updateSeminarRequest.affiliationUrl + startDate = updateSeminarRequest.startDate + startTime = updateSeminarRequest.startTime + endDate = updateSeminarRequest.endDate + endTime = updateSeminarRequest.endTime + location = updateSeminarRequest.location + host = updateSeminarRequest.host + additionalNote = updateSeminarRequest.additionalNote + isPublic = updateSeminarRequest.isPublic + isSlide = updateSeminarRequest.isSlide + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt new file mode 100644 index 00000000..e3df3a59 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt @@ -0,0 +1,122 @@ +package com.wafflestudio.csereal.core.seminar.database + +import com.querydsl.core.BooleanBuilder +import com.querydsl.jpa.impl.JPAQueryFactory +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.seminar.database.QSeminarEntity.seminarEntity +import com.wafflestudio.csereal.core.seminar.dto.SeminarSearchDto +import com.wafflestudio.csereal.core.seminar.dto.SeminarSearchResponse +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Component + +interface SeminarRepository : JpaRepository, CustomSeminarRepository { +} + +interface CustomSeminarRepository { + fun searchSeminar(keyword: String?, pageNum: Long): SeminarSearchResponse + fun findPrevNextId(seminarId: Long, keyword: String?): Array? +} + +@Component +class SeminarRepositoryImpl( + private val queryFactory: JPAQueryFactory, +) : CustomSeminarRepository { + override fun searchSeminar(keyword: String?, pageNum: Long): SeminarSearchResponse { + val keywordBooleanBuilder = BooleanBuilder() + + if (!keyword.isNullOrEmpty()) { + val keywordList = keyword.split("[^a-zA-Z0-9가-힣]".toRegex()) + keywordList.forEach { + if (it.length == 1) { + throw CserealException.Csereal400("각각의 키워드는 한글자 이상이어야 합니다.") + } else { + keywordBooleanBuilder.and( + seminarEntity.title.contains(it) + .or(seminarEntity.name.contains(it)) + .or(seminarEntity.affiliation.contains(it)) + .or(seminarEntity.location.contains(it)) + ) + } + } + } + + val jpaQuery = queryFactory.select(seminarEntity).from(seminarEntity) + .where(seminarEntity.isDeleted.eq(false)) + .where(keywordBooleanBuilder) + + val total = jpaQuery.fetch().size + + val seminarEntityList = jpaQuery.orderBy(seminarEntity.createdAt.desc()) + .offset(10*pageNum) + .limit(20) + .fetch() + + val seminarSearchDtoList : MutableList = mutableListOf() + + for(i: Int in 0 until seminarEntityList.size) { + var isYearLast = false + if(i == seminarEntityList.size-1) { + isYearLast = true + } else if(seminarEntityList[i].startDate?.substring(0,4) != seminarEntityList[i+1].startDate?.substring(0,4)) { + isYearLast = true + } + + seminarSearchDtoList.add( + SeminarSearchDto( + id = seminarEntityList[i].id, + title = seminarEntityList[i].title, + startDate = seminarEntityList[i].startDate, + isYearLast = isYearLast, + name = seminarEntityList[i].name, + affiliation = seminarEntityList[i].affiliation, + location = seminarEntityList[i].location + ) + ) + } + + return SeminarSearchResponse(total, seminarSearchDtoList) + } + override fun findPrevNextId(seminarId: Long, keyword: String?): Array? { + val keywordBooleanBuilder = BooleanBuilder() + + if (!keyword.isNullOrEmpty()) { + val keywordList = keyword.split("[^a-zA-Z0-9가-힣]".toRegex()) + keywordList.forEach { + if (it.length == 1) { + throw CserealException.Csereal400("각각의 키워드는 한글자 이상이어야 합니다.") + } else { + keywordBooleanBuilder.and( + seminarEntity.title.contains(it) + .or(seminarEntity.description.contains(it)) + ) + } + } + } + val seminarSearchDtoList = queryFactory.select(seminarEntity).from(seminarEntity) + .where(seminarEntity.isDeleted.eq(false), seminarEntity.isPublic.eq(true)) + .where(keywordBooleanBuilder) + .orderBy(seminarEntity.createdAt.desc()) + .fetch() + + val findingId = seminarSearchDtoList.indexOfFirst { it.id == seminarId } + + val prevNext: Array? + + if (findingId == -1) { + return null + } else if (findingId != 0 && findingId != seminarSearchDtoList.size - 1) { + prevNext = arrayOf(seminarSearchDtoList[findingId + 1], seminarSearchDtoList[findingId - 1]) + } else if (findingId == 0) { + if (seminarSearchDtoList.size == 1) { + prevNext = arrayOf(null, null) + } else { + prevNext = arrayOf(seminarSearchDtoList[1], null) + } + } else { + prevNext = arrayOf(null, seminarSearchDtoList[seminarSearchDtoList.size - 2]) + } + + return prevNext + + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt new file mode 100644 index 00000000..b2563385 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt @@ -0,0 +1,64 @@ +package com.wafflestudio.csereal.core.seminar.dto + +import com.wafflestudio.csereal.core.seminar.database.SeminarEntity + +data class SeminarDto( + val id: Long, + val title: String, + val description: String, + val introduction: String, + val category: String, + val name: String, + val speakerUrl: String?, + val speakerTitle: String?, + val affiliation: String, + val affiliationUrl: String?, + val startDate: String?, + val startTime: String?, + val endDate: String?, + val endTime: String?, + val location: String, + val host: String?, + // val profileImage: File, + // val seminarFile: File, + val additionalNote: String?, + val isPublic: Boolean, + val isSlide: Boolean, + val prevId: Long?, + val prevTitle: String?, + val nextId: Long?, + val nextTitle: String? +) { + + companion object { + fun of(entity: SeminarEntity, prevNext: Array?): SeminarDto = entity.run { + SeminarDto( + id = this.id, + title = this.title, + description = this.description, + introduction = this.introduction, + category = this.category, + name = this.name, + speakerUrl = this.speakerUrl, + speakerTitle = this.speakerTitle, + affiliation = this.affiliation, + affiliationUrl = this.affiliationUrl, + startDate = this.startDate, + startTime = this.startTime, + endDate = this.endDate, + endTime = this.endTime, + location = this.location, + host = this.host, + additionalNote = this.additionalNote, + isPublic = this.isPublic, + isSlide = this.isSlide, + prevId = prevNext?.get(0)?.id, + prevTitle = prevNext?.get(0)?.title, + nextId = prevNext?.get(1)?.id, + nextTitle = prevNext?.get(1)?.title + ) + } + + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarSearchDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarSearchDto.kt new file mode 100644 index 00000000..69ada786 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarSearchDto.kt @@ -0,0 +1,14 @@ +package com.wafflestudio.csereal.core.seminar.dto + +import com.querydsl.core.annotations.QueryProjection + +data class SeminarSearchDto @QueryProjection constructor( + val id: Long, + val title: String, + val startDate: String?, + val isYearLast: Boolean, + val name: String, + val affiliation: String?, + val location: String +) { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarSearchResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarSearchResponse.kt new file mode 100644 index 00000000..b591ac7c --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarSearchResponse.kt @@ -0,0 +1,7 @@ +package com.wafflestudio.csereal.core.seminar.dto + +data class SeminarSearchResponse( + val total: Int, + val searchList: List +) { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt new file mode 100644 index 00000000..cf6a5cba --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt @@ -0,0 +1,67 @@ +package com.wafflestudio.csereal.core.seminar.service + +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.seminar.database.SeminarEntity +import com.wafflestudio.csereal.core.seminar.database.SeminarRepository +import com.wafflestudio.csereal.core.seminar.dto.SeminarDto +import com.wafflestudio.csereal.core.seminar.dto.SeminarSearchResponse +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +interface SeminarService { + fun searchSeminar(keyword: String?, pageNum: Long): SeminarSearchResponse + fun createSeminar(request: SeminarDto): SeminarDto + fun readSeminar(seminarId: Long, keyword: String?): SeminarDto + fun updateSeminar(seminarId: Long, request: SeminarDto): SeminarDto + fun deleteSeminar(seminarId: Long) +} + +@Service +class SeminarServiceImpl( + private val seminarRepository: SeminarRepository +) : SeminarService { + @Transactional(readOnly = true) + override fun searchSeminar(keyword: String?, pageNum: Long): SeminarSearchResponse { + return seminarRepository.searchSeminar(keyword, pageNum) + } + + @Transactional + override fun createSeminar(request: SeminarDto): SeminarDto { + val newSeminar = SeminarEntity.of(request) + + seminarRepository.save(newSeminar) + + return SeminarDto.of(newSeminar, null) + } + + @Transactional(readOnly = true) + override fun readSeminar(seminarId: Long, keyword: String?): SeminarDto { + val seminar: SeminarEntity = seminarRepository.findByIdOrNull(seminarId) + ?: throw CserealException.Csereal404("존재하지 않는 세미나입니다.(seminarId: $seminarId)") + + if (seminar.isDeleted) throw CserealException.Csereal400("삭제된 세미나입니다. (seminarId: $seminarId)") + + val prevNext = seminarRepository.findPrevNextId(seminarId, keyword) + + return SeminarDto.of(seminar, prevNext) + } + + @Transactional + override fun updateSeminar(seminarId: Long, request: SeminarDto): SeminarDto { + val seminar: SeminarEntity = seminarRepository.findByIdOrNull(seminarId) + ?: throw CserealException.Csereal404("존재하지 않는 세미나입니다") + if(seminar.isDeleted) throw CserealException.Csereal404("삭제된 세미나입니다. (seminarId: $seminarId)") + + seminar.update(request) + + return SeminarDto.of(seminar, null) + } + @Transactional + override fun deleteSeminar(seminarId: Long) { + val seminar: SeminarEntity = seminarRepository.findByIdOrNull(seminarId) + ?: throw CserealException.Csereal404("존재하지 않는 세미나입니다.(seminarId=$seminarId") + + seminar.isDeleted = true + } +} \ No newline at end of file From a550011fff0b99561c0a3cceca2f5be29c5e0e74 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Sat, 12 Aug 2023 00:28:47 +0900 Subject: [PATCH 015/124] =?UTF-8?q?hotfix:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20dto=20=EC=82=AD=EC=A0=9C=20(#20)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * hotfix: 불필요한 dto 삭제 * build.gradle 수정 --- .../core/notice/dto/CreateNoticeRequest.kt | 20 ------------------- .../csereal/core/notice/dto/SearchDto.kt | 13 ------------ .../csereal/core/notice/dto/SearchResponse.kt | 8 -------- .../core/notice/dto/UpdateNoticeRequest.kt | 11 ---------- 4 files changed, 52 deletions(-) delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/CreateNoticeRequest.kt delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/SearchDto.kt delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/SearchResponse.kt delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/UpdateNoticeRequest.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/CreateNoticeRequest.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/CreateNoticeRequest.kt deleted file mode 100644 index 0b0fda52..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/CreateNoticeRequest.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.wafflestudio.csereal.core.notice.dto - -import jakarta.validation.constraints.NotBlank - -data class CreateNoticeRequest( - @field:NotBlank(message = "제목은 비어있을 수 없습니다") - val title: String, - - @field:NotBlank(message = "내용은 비어있을 수 없습니다") - val description: String, - - val tags: List = emptyList(), - - val isPublic: Boolean, - - val isSlide: Boolean, - - val isPinned: Boolean, -) { -} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/SearchDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/SearchDto.kt deleted file mode 100644 index b05e5c11..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/SearchDto.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.wafflestudio.csereal.core.notice.dto - -import com.querydsl.core.annotations.QueryProjection -import java.time.LocalDateTime - -data class SearchDto @QueryProjection constructor( - val noticeId: Long, - val title: String, - val createdDate: LocalDateTime, - val isPinned: Boolean, -) { - -} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/SearchResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/SearchResponse.kt deleted file mode 100644 index 105a25cf..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/SearchResponse.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.wafflestudio.csereal.core.notice.dto - -data class SearchResponse( - val total: Int, - val searchList: List -) { - -} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/UpdateNoticeRequest.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/UpdateNoticeRequest.kt deleted file mode 100644 index e7fd1fa9..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/UpdateNoticeRequest.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.wafflestudio.csereal.core.notice.dto - -data class UpdateNoticeRequest( - val title: String?, - val description: String?, - val tags: List?, - val isPublic: Boolean?, - val isSlide: Boolean?, - val isPinned: Boolean?, -) { -} \ No newline at end of file From e2b3decfcf4c9345347bd89027b489a8a0d97ec1 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Mon, 14 Aug 2023 13:17:27 +0900 Subject: [PATCH 016/124] =?UTF-8?q?fix:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20ur?= =?UTF-8?q?i=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20?= =?UTF-8?q?=ED=94=84=EB=A1=A0=ED=8A=B8=20=EC=9A=94=EA=B5=AC=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EB=B0=98=EC=98=81=20(#21)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 이미지 uri 필드 추가 및 isActive 대신 status 추가 * fix: 행정직원 전체 조회 응답에서 task 삭제 * fix: 교수진 페이지 응답 수정 --- .../core/member/api/ProfessorController.kt | 3 +- .../core/member/api/StaffController.kt | 3 +- .../core/member/database/ProfessorEntity.kt | 23 +++++++-------- .../member/database/ProfessorRepository.kt | 4 +-- .../core/member/database/StaffEntity.kt | 5 ++-- .../csereal/core/member/dto/ProfessorDto.kt | 14 +++++++--- .../core/member/dto/ProfessorPageDto.kt | 6 ++++ .../core/member/dto/SimpleProfessorDto.kt | 5 ++-- .../csereal/core/member/dto/SimpleStaffDto.kt | 28 +++++++++++++++++++ .../csereal/core/member/dto/StaffDto.kt | 7 +++-- .../core/member/service/ProfessorService.kt | 18 +++++++++--- .../core/member/service/StaffService.kt | 7 +++-- 12 files changed, 90 insertions(+), 33 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/dto/ProfessorPageDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/dto/SimpleStaffDto.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt index 993ddba8..6caff3a0 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt @@ -1,6 +1,7 @@ package com.wafflestudio.csereal.core.member.api import com.wafflestudio.csereal.core.member.dto.ProfessorDto +import com.wafflestudio.csereal.core.member.dto.ProfessorPageDto import com.wafflestudio.csereal.core.member.dto.SimpleProfessorDto import com.wafflestudio.csereal.core.member.service.ProfessorService import org.springframework.http.ResponseEntity @@ -23,7 +24,7 @@ class ProfessorController( } @GetMapping("/active") - fun getActiveProfessors(): ResponseEntity> { + fun getActiveProfessors(): ResponseEntity { return ResponseEntity.ok(professorService.getActiveProfessors()) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt index f10aae6f..391a3e58 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt @@ -1,5 +1,6 @@ package com.wafflestudio.csereal.core.member.api +import com.wafflestudio.csereal.core.member.dto.SimpleStaffDto import com.wafflestudio.csereal.core.member.dto.StaffDto import com.wafflestudio.csereal.core.member.service.StaffService import org.springframework.http.ResponseEntity @@ -29,7 +30,7 @@ class StaffController( } @GetMapping - fun getAllStaff(): ResponseEntity> { + fun getAllStaff(): ResponseEntity> { return ResponseEntity.ok(staffService.getAllStaff()) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt index 4f18b176..10b06484 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt @@ -3,12 +3,7 @@ package com.wafflestudio.csereal.core.member.database import com.wafflestudio.csereal.common.config.BaseTimeEntity import com.wafflestudio.csereal.core.member.dto.ProfessorDto import com.wafflestudio.csereal.core.research.database.LabEntity -import jakarta.persistence.CascadeType -import jakarta.persistence.Entity -import jakarta.persistence.FetchType -import jakarta.persistence.JoinColumn -import jakarta.persistence.ManyToOne -import jakarta.persistence.OneToMany +import jakarta.persistence.* import java.time.LocalDate @Entity(name = "professor") @@ -16,8 +11,9 @@ class ProfessorEntity( var name: String, - //val profileImage:File - var isActive: Boolean, + @Enumerated(EnumType.STRING) + var status: ProfessorStatus, + var academicRank: String, @ManyToOne(fetch = FetchType.LAZY) @@ -43,13 +39,14 @@ class ProfessorEntity( @OneToMany(mappedBy = "professor", cascade = [CascadeType.ALL], orphanRemoval = true) val careers: MutableList = mutableListOf(), - ) : BaseTimeEntity() { + var imageUri: String? = null +) : BaseTimeEntity() { companion object { fun of(professorDto: ProfessorDto): ProfessorEntity { return ProfessorEntity( name = professorDto.name, - isActive = professorDto.isActive, + status = professorDto.status, academicRank = professorDto.academicRank, startDate = professorDto.startDate, endDate = professorDto.endDate, @@ -70,7 +67,7 @@ class ProfessorEntity( fun update(updateProfessorRequest: ProfessorDto) { this.name = updateProfessorRequest.name - this.isActive = updateProfessorRequest.isActive + this.status = updateProfessorRequest.status this.academicRank = updateProfessorRequest.academicRank this.startDate = updateProfessorRequest.startDate this.endDate = updateProfessorRequest.endDate @@ -81,3 +78,7 @@ class ProfessorEntity( this.website = updateProfessorRequest.website } } + +enum class ProfessorStatus { + ACTIVE, INACTIVE, VISITING +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorRepository.kt index d8ae890c..f190d236 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorRepository.kt @@ -3,6 +3,6 @@ package com.wafflestudio.csereal.core.member.database import org.springframework.data.jpa.repository.JpaRepository interface ProfessorRepository : JpaRepository { - fun findByIsActiveTrue(): List - fun findByIsActiveFalse(): List + fun findByStatus(status: ProfessorStatus): List + fun findByStatusNot(status: ProfessorStatus): List } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffEntity.kt index f950ed43..0f243b5e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffEntity.kt @@ -11,15 +11,14 @@ class StaffEntity( var name: String, var role: String, - // profileImage - var office: String, var phone: String, var email: String, @OneToMany(mappedBy = "staff", cascade = [CascadeType.ALL], orphanRemoval = true) - val tasks: MutableList = mutableListOf() + val tasks: MutableList = mutableListOf(), + var imageUri: String? = null ) : BaseTimeEntity() { companion object { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/ProfessorDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/ProfessorDto.kt index 84372eee..75fee843 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/ProfessorDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/ProfessorDto.kt @@ -2,15 +2,17 @@ package com.wafflestudio.csereal.core.member.dto import com.fasterxml.jackson.annotation.JsonInclude import com.wafflestudio.csereal.core.member.database.ProfessorEntity +import com.wafflestudio.csereal.core.member.database.ProfessorStatus import java.time.LocalDate data class ProfessorDto( @JsonInclude(JsonInclude.Include.NON_NULL) var id: Long? = null, val name: String, - val isActive: Boolean, + val status: ProfessorStatus, val academicRank: String, val labId: Long?, + val labName: String?, val startDate: LocalDate?, val endDate: LocalDate?, val office: String?, @@ -20,16 +22,19 @@ data class ProfessorDto( val website: String?, val educations: List, val researchAreas: List, - val careers: List + val careers: List, + @JsonInclude(JsonInclude.Include.NON_NULL) + var imageUri: String? = null ) { companion object { fun of(professorEntity: ProfessorEntity): ProfessorDto { return ProfessorDto( id = professorEntity.id, name = professorEntity.name, - isActive = professorEntity.isActive, + status = professorEntity.status, academicRank = professorEntity.academicRank, labId = professorEntity.lab?.id, + labName = professorEntity.lab?.name, startDate = professorEntity.startDate, endDate = professorEntity.endDate, office = professorEntity.office, @@ -39,7 +44,8 @@ data class ProfessorDto( website = professorEntity.website, educations = professorEntity.educations.map { it.name }, researchAreas = professorEntity.researchAreas.map { it.name }, - careers = professorEntity.careers.map { it.name } + careers = professorEntity.careers.map { it.name }, + imageUri = professorEntity.imageUri ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/ProfessorPageDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/ProfessorPageDto.kt new file mode 100644 index 00000000..467c171e --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/ProfessorPageDto.kt @@ -0,0 +1,6 @@ +package com.wafflestudio.csereal.core.member.dto + +data class ProfessorPageDto( + val description: String, + val professors: List +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/SimpleProfessorDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/SimpleProfessorDto.kt index 6e070667..b3c3682b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/SimpleProfessorDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/SimpleProfessorDto.kt @@ -10,7 +10,7 @@ data class SimpleProfessorDto( val labName: String?, val phone: String?, val email: String?, - // val imageUri: String + val imageUri: String? ) { companion object { fun of(professorEntity: ProfessorEntity): SimpleProfessorDto { @@ -21,7 +21,8 @@ data class SimpleProfessorDto( labId = professorEntity.lab?.id, labName = professorEntity.lab?.name, phone = professorEntity.phone, - email = professorEntity.email + email = professorEntity.email, + imageUri = professorEntity.imageUri ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/SimpleStaffDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/SimpleStaffDto.kt new file mode 100644 index 00000000..750f9801 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/SimpleStaffDto.kt @@ -0,0 +1,28 @@ +package com.wafflestudio.csereal.core.member.dto + +import com.wafflestudio.csereal.core.member.database.StaffEntity + +data class SimpleStaffDto( + val id: Long, + val name: String, + val role: String, + val office: String, + val phone: String, + val email: String, + val imageUri: String? +) { + + companion object { + fun of(staffEntity: StaffEntity): SimpleStaffDto { + return SimpleStaffDto( + id = staffEntity.id, + name = staffEntity.name, + role = staffEntity.role, + office = staffEntity.office, + phone = staffEntity.phone, + email = staffEntity.email, + imageUri = staffEntity.imageUri + ) + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/StaffDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/StaffDto.kt index dfdec200..8cd1ee06 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/StaffDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/StaffDto.kt @@ -11,7 +11,9 @@ data class StaffDto( val office: String, val phone: String, val email: String, - val tasks: List + val tasks: List, + @JsonInclude(JsonInclude.Include.NON_NULL) + val imageUri: String? = null ) { companion object { fun of(staffEntity: StaffEntity): StaffDto { @@ -22,7 +24,8 @@ data class StaffDto( office = staffEntity.office, phone = staffEntity.phone, email = staffEntity.email, - tasks = staffEntity.tasks.map { it.name } + tasks = staffEntity.tasks.map { it.name }, + imageUri = staffEntity.imageUri ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt index a2a1b1a4..5710d416 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt @@ -3,6 +3,7 @@ package com.wafflestudio.csereal.core.member.service import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.core.member.database.* import com.wafflestudio.csereal.core.member.dto.ProfessorDto +import com.wafflestudio.csereal.core.member.dto.ProfessorPageDto import com.wafflestudio.csereal.core.member.dto.SimpleProfessorDto import com.wafflestudio.csereal.core.research.database.LabRepository import org.springframework.data.repository.findByIdOrNull @@ -13,7 +14,7 @@ interface ProfessorService { fun createProfessor(createProfessorRequest: ProfessorDto): ProfessorDto fun getProfessor(professorId: Long): ProfessorDto - fun getActiveProfessors(): List + fun getActiveProfessors(): ProfessorPageDto fun getInactiveProfessors(): List fun updateProfessor(professorId: Long, updateProfessorRequest: ProfessorDto): ProfessorDto fun deleteProfessor(professorId: Long) @@ -60,13 +61,22 @@ class ProfessorServiceImpl( } @Transactional(readOnly = true) - override fun getActiveProfessors(): List { - return professorRepository.findByIsActiveTrue().map { SimpleProfessorDto.of(it) }.sortedBy { it.name } + override fun getActiveProfessors(): ProfessorPageDto { + val description = "컴퓨터공학부는 35명의 훌륭한 교수진과 최신 시설을 갖추고 400여 명의 학부생과 350여 명의 대학원생에게 세계 최고 " + + "수준의 교육 연구 환경을 제공하고 있다. 2005년에는 서울대학교 최초로 외국인 정교수인 Robert Ian McKay 교수를 임용한 것을 " + + "시작으로 교내에서 가장 국제화가 활발하게 이루어지고 있는 학부로 평가받고 있다. 현재 훌륭한 외국인 교수님 두 분이 학부 학생들의 " + + "교육 및 연구 지도에 총력을 기울이고 있다.\n\n다수의 외국인 학부생, 대학원생이 재학 중에 있으며 매 학기 전공 필수 과목을 비롯한 " + + "30% 이상의 과목이 영어로 개설되고 있어 외국인 학생의 학업을 돕는 동시에 한국인 학생이 세계로 진출하는 초석이 되고 있다. 또한 " + + "CSE int’l Luncheon을 개최하여 학부 내 외국인 구성원의 화합과 생활의 불편함을 최소화하는 등 학부 차원에서 최선을 다하고 있다." + val professors = professorRepository.findByStatusNot(ProfessorStatus.INACTIVE).map { SimpleProfessorDto.of(it) } + .sortedBy { it.name } + return ProfessorPageDto(description, professors) } @Transactional(readOnly = true) override fun getInactiveProfessors(): List { - return professorRepository.findByIsActiveFalse().map { SimpleProfessorDto.of(it) }.sortedBy { it.name } + return professorRepository.findByStatus(ProfessorStatus.INACTIVE).map { SimpleProfessorDto.of(it) } + .sortedBy { it.name } } override fun updateProfessor(professorId: Long, updateProfessorRequest: ProfessorDto): ProfessorDto { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt index e7249113..d76137fb 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt @@ -4,6 +4,7 @@ import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.core.member.database.StaffEntity import com.wafflestudio.csereal.core.member.database.StaffRepository import com.wafflestudio.csereal.core.member.database.TaskEntity +import com.wafflestudio.csereal.core.member.dto.SimpleStaffDto import com.wafflestudio.csereal.core.member.dto.StaffDto import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service @@ -12,7 +13,7 @@ import org.springframework.transaction.annotation.Transactional interface StaffService { fun createStaff(createStaffRequest: StaffDto): StaffDto fun getStaff(staffId: Long): StaffDto - fun getAllStaff(): List + fun getAllStaff(): List fun updateStaff(staffId: Long, updateStaffRequest: StaffDto): StaffDto fun deleteStaff(staffId: Long) } @@ -42,8 +43,8 @@ class StaffServiceImpl( } @Transactional(readOnly = true) - override fun getAllStaff(): List { - return staffRepository.findAll().map { StaffDto.of(it) }.sortedBy { it.name } + override fun getAllStaff(): List { + return staffRepository.findAll().map { SimpleStaffDto.of(it) }.sortedBy { it.name } } override fun updateStaff(staffId: Long, updateStaffRequest: StaffDto): StaffDto { From 0813562e2c5606530a6232c05eeeb0dd0e01d5e2 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Tue, 15 Aug 2023 21:29:32 +0900 Subject: [PATCH 017/124] =?UTF-8?q?feat:=20introduction=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80,=20undergraduate=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=20=EC=B6=94=EA=B0=80=20(#22)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: createUndergraduate, readUndergraduate 추가 * feat: readAllCourses, createCourse, readCourse 추가 * introduction 패키지 추가 * fix: dto에서 postType 삭제 * fix: postType pathVariable->requestBody 수정, 오타 수정 * fix: 프론트와 협의하여 이름 등 변경 * feat: academics 패키지 대학원도 가능하도록 추가 * feat: 장학제도 세부 장학금 create, read 추가 * fix: 수정 * fix:수정 --- .../csereal/core/about/api/AboutController.kt | 58 +++++++++++++ .../core/about/database/AboutEntity.kt | 38 +++++++++ .../core/about/database/AboutRepository.kt | 8 ++ .../core/about/database/LocationEntity.kt | 27 +++++++ .../csereal/core/about/dto/AboutDto.kt | 35 ++++++++ .../core/about/service/AboutService.kt | 70 ++++++++++++++++ .../core/academics/api/AcademicsController.kt | 81 +++++++++++++++++++ .../academics/database/AcademicsEntity.kt | 37 +++++++++ .../academics/database/AcademicsRepository.kt | 8 ++ .../core/academics/database/CourseEntity.kt | 41 ++++++++++ .../academics/database/CourseRepository.kt | 8 ++ .../core/academics/database/StudentType.kt | 5 ++ .../core/academics/dto/AcademicsDto.kt | 30 +++++++ .../csereal/core/academics/dto/CourseDto.kt | 29 +++++++ .../academics/service/AcademicsService.kt | 70 ++++++++++++++++ .../core/notice/database/NoticeEntity.kt | 2 - 16 files changed, 545 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutRepository.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/database/LocationEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsRepository.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseRepository.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/database/StudentType.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/CourseDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt new file mode 100644 index 00000000..09e902db --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt @@ -0,0 +1,58 @@ +package com.wafflestudio.csereal.core.about.api + +import com.wafflestudio.csereal.core.about.dto.AboutDto +import com.wafflestudio.csereal.core.about.service.AboutService +import jakarta.validation.Valid +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RequestMapping("/about") +@RestController +class AboutController( + private val aboutService: AboutService +) { + // postType -> 학부 소개: overview, 연혁: history, 졸업생 진로: future-careers, 연락처: contact + // 위에 있는 항목은 name = null + + // postType: student-clubs / name -> 가디언, 바쿠스, 사커301, 슈타인, 스눕스, 와플스튜디오, 유피넬 + // postType: facilities / name -> 학부-행정실, S-Lab, 소프트웨어-실습실, 하드웨어-실습실, 해동학술정보실, 학생-공간-및-동아리-방, 세미나실, 서버실 + // postType: directions / name -> by-public-transit, by-car, from-far-away + + // Todo: 전체 image, file, 학부장 인사말(greetings) signature + @PostMapping + fun createAbout( + @Valid @RequestBody request: AboutDto + ) : ResponseEntity { + return ResponseEntity.ok(aboutService.createAbout(request)) + } + + // read 목록이 하나 + @GetMapping("/{postType}") + fun readAbout( + @PathVariable postType: String, + ) : ResponseEntity { + return ResponseEntity.ok(aboutService.readAbout(postType)) + } + + @GetMapping("/student-clubs") + fun readAllClubs() : ResponseEntity> { + return ResponseEntity.ok(aboutService.readAllClubs()) + } + + @GetMapping("/facilities") + fun readAllFacilities() : ResponseEntity> { + return ResponseEntity.ok(aboutService.readAllFacilities()) + } + + + @GetMapping("/directions") + fun readAllDirections() : ResponseEntity> { + return ResponseEntity.ok(aboutService.readAllDirections()) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt new file mode 100644 index 00000000..5952e2ff --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt @@ -0,0 +1,38 @@ +package com.wafflestudio.csereal.core.about.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.core.about.dto.AboutDto +import jakarta.persistence.CascadeType +import jakarta.persistence.Entity +import jakarta.persistence.OneToMany + +@Entity(name = "about") +class AboutEntity( + var postType: String, + + var name: String, + + var engName: String?, + + var description: String, + + var year: Int?, + + var isPublic: Boolean, + + @OneToMany(mappedBy = "about", cascade = [CascadeType.ALL], orphanRemoval = true) + val locations: MutableList = mutableListOf() +) : BaseTimeEntity() { + companion object { + fun of(aboutDto: AboutDto): AboutEntity { + return AboutEntity( + postType = aboutDto.postType, + name = aboutDto.name, + engName = aboutDto.engName, + description = aboutDto.description, + year = aboutDto.year, + isPublic = aboutDto.isPublic, + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutRepository.kt new file mode 100644 index 00000000..fe269e39 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutRepository.kt @@ -0,0 +1,8 @@ +package com.wafflestudio.csereal.core.about.database + +import org.springframework.data.jpa.repository.JpaRepository + +interface AboutRepository : JpaRepository { + fun findAllByPostTypeOrderByName(postType: String): List + fun findByPostType(postType: String): AboutEntity +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/LocationEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/LocationEntity.kt new file mode 100644 index 00000000..3e3ad0de --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/LocationEntity.kt @@ -0,0 +1,27 @@ +package com.wafflestudio.csereal.core.about.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import jakarta.persistence.Entity +import jakarta.persistence.FetchType +import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne + +@Entity(name = "location") +class LocationEntity( + val name: String, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "about_id") + val about: AboutEntity +) : BaseTimeEntity() { + companion object { + fun create(name: String, about: AboutEntity): LocationEntity { + val locationEntity = LocationEntity( + name = name, + about = about + ) + about.locations.add(locationEntity) + return locationEntity + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutDto.kt new file mode 100644 index 00000000..83a466f6 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutDto.kt @@ -0,0 +1,35 @@ +package com.wafflestudio.csereal.core.about.dto + + +import com.wafflestudio.csereal.core.about.database.AboutEntity +import java.time.LocalDateTime + +data class AboutDto( + val id: Long, + val postType: String, + val name: String, + val engName: String?, + val description: String, + val year: Int?, + val createdAt: LocalDateTime?, + val modifiedAt: LocalDateTime?, + val isPublic: Boolean, + val locations: List? +) { + companion object { + fun of(entity: AboutEntity) : AboutDto = entity.run { + AboutDto( + id = this.id, + postType = this.postType, + name = this.name, + engName = this.engName, + description = this.description, + year = this.year, + createdAt = this.createdAt, + modifiedAt = this.modifiedAt, + isPublic = this.isPublic, + locations = this.locations.map { it.name } + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt new file mode 100644 index 00000000..77b5b5ba --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt @@ -0,0 +1,70 @@ +package com.wafflestudio.csereal.core.about.service + +import com.wafflestudio.csereal.core.about.database.AboutEntity +import com.wafflestudio.csereal.core.about.database.AboutRepository +import com.wafflestudio.csereal.core.about.database.LocationEntity +import com.wafflestudio.csereal.core.about.dto.AboutDto +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +interface AboutService { + fun createAbout(request: AboutDto): AboutDto + fun readAbout(postType: String): AboutDto + fun readAllClubs() : List + fun readAllFacilities() : List + fun readAllDirections(): List +} + +@Service +class AboutServiceImpl( + private val aboutRepository: AboutRepository +) : AboutService { + @Transactional + override fun createAbout(request: AboutDto): AboutDto { + val newAbout = AboutEntity.of(request) + + if(request.locations != null) { + for (location in request.locations) { + LocationEntity.create(location, newAbout) + } + } + + aboutRepository.save(newAbout) + + return AboutDto.of(newAbout) + } + + @Transactional(readOnly = true) + override fun readAbout(postType: String): AboutDto { + val about = aboutRepository.findByPostType(postType) + + return AboutDto.of(about) + } + + @Transactional(readOnly = true) + override fun readAllClubs(): List { + val clubs = aboutRepository.findAllByPostTypeOrderByName("student-clubs").map { + AboutDto.of(it) + } + + return clubs + } + + @Transactional(readOnly = true) + override fun readAllFacilities(): List { + val facilities = aboutRepository.findAllByPostTypeOrderByName("facilities").map { + AboutDto.of(it) + } + + return facilities + } + + @Transactional(readOnly = true) + override fun readAllDirections(): List { + val directions = aboutRepository.findAllByPostTypeOrderByName("directions").map { + AboutDto.of(it) + } + + return directions + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt new file mode 100644 index 00000000..047a8850 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt @@ -0,0 +1,81 @@ +package com.wafflestudio.csereal.core.academics.api + +import com.wafflestudio.csereal.core.academics.database.StudentType +import com.wafflestudio.csereal.core.academics.dto.CourseDto +import com.wafflestudio.csereal.core.academics.dto.AcademicsDto +import com.wafflestudio.csereal.core.academics.service.AcademicsService +import jakarta.validation.Valid +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RequestMapping("/academics") +@RestController +class AcademicsController( + private val academicsService: AcademicsService +) { + + // postType -> 학부 안내: guide, 필수 교양 과목: general-studies-requirements, + // 전공 이수 표준 형태: curriculum, 졸업 규청: degree-requirements, + // 교과목 변경 내역: course-changes, 장학제도: scholarship + //Todo: 이미지, 파일 추가 필요 + @PostMapping("/{studentType}") + fun createAcademics( + @PathVariable studentType: StudentType, + @Valid @RequestBody request: AcademicsDto + ) : ResponseEntity { + return ResponseEntity.ok(academicsService.createAcademics(studentType, request)) + } + + @GetMapping("/{studentType}/{postType}") + fun readAcademics( + @PathVariable studentType: StudentType, + @PathVariable postType: String, + ): ResponseEntity { + return ResponseEntity.ok(academicsService.readAcademics(studentType, postType)) + } + + //교과목 정보: courses + @PostMapping("/{studentType}/course") + fun createCourse( + @PathVariable studentType: StudentType, + @Valid @RequestBody request: CourseDto + ) : ResponseEntity { + return ResponseEntity.ok(academicsService.createCourse(studentType, request)) + } + + @GetMapping("/{studentType}/courses") + fun readAllCourses( + @PathVariable studentType: StudentType, + ) : ResponseEntity> { + return ResponseEntity.ok(academicsService.readAllCourses(studentType)) + } + + @GetMapping("/course/{name}") + fun readCourse( + @PathVariable name: String + ): ResponseEntity { + return ResponseEntity.ok(academicsService.readCourse(name)) + } + + // 장학금 + @PostMapping("/{studentType}/scholarship") + fun createScholarship( + @PathVariable studentType: StudentType, + @Valid @RequestBody request: AcademicsDto + ) : ResponseEntity { + return ResponseEntity.ok(academicsService.createAcademics(studentType, request)) + } + + @GetMapping("/scholarship/{name}") + fun readScholarship( + @PathVariable name: String + ) : ResponseEntity { + return ResponseEntity.ok(academicsService.readScholarship(name)) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsEntity.kt new file mode 100644 index 00000000..2f70cb24 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsEntity.kt @@ -0,0 +1,37 @@ +package com.wafflestudio.csereal.core.academics.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.core.academics.dto.AcademicsDto +import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated + +@Entity(name = "academics") +class AcademicsEntity( + @Enumerated(EnumType.STRING) + var studentType: StudentType, + + var postType: String, + + var name: String, + + var description: String, + + var year: Int?, + + var isPublic: Boolean, + +): BaseTimeEntity() { + companion object { + fun of(studentType: StudentType, academicsDto: AcademicsDto): AcademicsEntity { + return AcademicsEntity( + studentType = studentType, + postType = academicsDto.postType, + name = academicsDto.name, + description = academicsDto.description, + year = academicsDto.year, + isPublic = academicsDto.isPublic, + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsRepository.kt new file mode 100644 index 00000000..fcd58f5c --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsRepository.kt @@ -0,0 +1,8 @@ +package com.wafflestudio.csereal.core.academics.database + +import org.springframework.data.jpa.repository.JpaRepository + +interface AcademicsRepository : JpaRepository { + fun findByStudentTypeAndPostType(studentType: StudentType, postType: String) : AcademicsEntity + fun findByName(name: String): AcademicsEntity +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseEntity.kt new file mode 100644 index 00000000..74f4b0bb --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseEntity.kt @@ -0,0 +1,41 @@ +package com.wafflestudio.csereal.core.academics.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.core.academics.dto.CourseDto +import jakarta.persistence.Entity + +@Entity(name = "course") +class CourseEntity( + var isDeleted: Boolean = false, + + var studentType: StudentType, + + var classification: String, + + var number: String, + + var name: String, + + var credit: Int, + + var year: String, + + var courseURL: String?, + + var description: String? +): BaseTimeEntity() { + companion object { + fun of(studentType: StudentType, courseDto: CourseDto): CourseEntity { + return CourseEntity( + studentType = studentType, + classification = courseDto.classification, + number = courseDto.number, + name = courseDto.name.replace(" ","-"), + credit = courseDto.credit, + year = courseDto.year, + courseURL = courseDto.courseURL, + description = courseDto.description + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseRepository.kt new file mode 100644 index 00000000..777c3961 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseRepository.kt @@ -0,0 +1,8 @@ +package com.wafflestudio.csereal.core.academics.database + +import org.springframework.data.jpa.repository.JpaRepository + +interface CourseRepository : JpaRepository { + fun findAllByStudentTypeOrderByYearAsc(studentType: StudentType) : List + fun findByName(name: String) : CourseEntity +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/StudentType.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/StudentType.kt new file mode 100644 index 00000000..ebc750af --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/StudentType.kt @@ -0,0 +1,5 @@ +package com.wafflestudio.csereal.core.academics.database + +enum class StudentType { + GRADUATE,UNDERGRADUATE +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsDto.kt new file mode 100644 index 00000000..f62b2f4f --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsDto.kt @@ -0,0 +1,30 @@ +package com.wafflestudio.csereal.core.academics.dto + +import com.wafflestudio.csereal.core.academics.database.AcademicsEntity +import java.time.LocalDateTime + +data class AcademicsDto( + val id: Long, + val postType: String, + val name: String, + val description: String, + val year: Int?, + val createdAt: LocalDateTime?, + val modifiedAt: LocalDateTime?, + val isPublic: Boolean, +) { + companion object { + fun of(entity: AcademicsEntity) : AcademicsDto = entity.run { + AcademicsDto( + id = this.id, + postType = this.postType, + name = this.name, + description = this.description, + year = this.year, + createdAt = this.createdAt, + modifiedAt = this.modifiedAt, + isPublic = this.isPublic, + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/CourseDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/CourseDto.kt new file mode 100644 index 00000000..bb2fffc3 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/CourseDto.kt @@ -0,0 +1,29 @@ +package com.wafflestudio.csereal.core.academics.dto + +import com.wafflestudio.csereal.core.academics.database.CourseEntity + +data class CourseDto( + val id: Long, + val classification: String, + val number: String, + val name: String, + val credit: Int, + val year: String, + val courseURL: String?, + val description: String? +) { + companion object { + fun of(entity: CourseEntity): CourseDto = entity.run { + CourseDto( + id = this.id, + classification = this.classification, + number = this.number, + name = this.name, + credit = this.credit, + year = this.year, + courseURL = this.courseURL, + description = this.description, + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt new file mode 100644 index 00000000..8e9e7105 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt @@ -0,0 +1,70 @@ +package com.wafflestudio.csereal.core.academics.service + +import com.wafflestudio.csereal.core.academics.database.* +import com.wafflestudio.csereal.core.academics.dto.CourseDto +import com.wafflestudio.csereal.core.academics.dto.AcademicsDto +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +interface AcademicsService { + fun createAcademics(studentType: StudentType, request: AcademicsDto): AcademicsDto + fun readAcademics(studentType: StudentType, postType: String): AcademicsDto + fun readAllCourses(studentType: StudentType): List + fun createCourse(studentType: StudentType, request: CourseDto): CourseDto + fun readCourse(name: String): CourseDto + fun readScholarship(name:String): AcademicsDto +} + +@Service +class AcademicsServiceImpl( + private val academicsRepository: AcademicsRepository, + private val courseRepository: CourseRepository, +) : AcademicsService { + @Transactional + override fun createAcademics(studentType: StudentType, request: AcademicsDto): AcademicsDto { + val newAcademics = AcademicsEntity.of(studentType, request) + + academicsRepository.save(newAcademics) + + return AcademicsDto.of(newAcademics) + } + + @Transactional(readOnly = true) + override fun readAcademics(studentType: StudentType, postType: String): AcademicsDto { + val academics : AcademicsEntity = academicsRepository.findByStudentTypeAndPostType(studentType, postType) + + return AcademicsDto.of(academics) + } + + @Transactional + override fun readAllCourses(studentType: StudentType): List { + val courseDtoList = courseRepository.findAllByStudentTypeOrderByYearAsc(studentType).map { + CourseDto.of(it) + } + return courseDtoList + } + @Transactional + override fun createCourse(studentType: StudentType, request: CourseDto): CourseDto { + val course = CourseEntity.of(studentType, request) + + courseRepository.save(course) + + return CourseDto.of(course) + } + + @Transactional + override fun readCourse(name: String): CourseDto { + val course : CourseEntity = courseRepository.findByName(name) + + return CourseDto.of(course) + } + + @Transactional + override fun readScholarship(name: String): AcademicsDto { + val scholarship : AcademicsEntity = academicsRepository.findByName(name) + + return AcademicsDto.of(scholarship) + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt index 2a543517..79075550 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt @@ -17,8 +17,6 @@ class NoticeEntity( var description: String, -// var postType: String, - var isPublic: Boolean, var isSlide: Boolean, From cffac4108c7c00e7ec53b170fa634ef79327ffd8 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Tue, 15 Aug 2023 22:43:08 +0900 Subject: [PATCH 018/124] =?UTF-8?q?feat:=20admissions,=20research=20?= =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=B6=94=EA=B0=80=20(#23)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: admissions-학부 에서 create, read 추가 * feat: admissions-대학원도 작성 가능하도록 추가 * feat: createResearch 추가 * feat: createLab 추가 * fix: 다른 패키지에 맞게 수정 * research-groups, research-centers에서 read, update 추가 * fix: admissions, research에서 프론트와 협의하여 이름 등 수정 * fix: 오타 수정 * fix: enum 추가, pr 리뷰 반영 --- .../admissions/api/AdmissionsController.kt | 44 ++++++ .../admissions/database/AdmissionPostType.kt | 5 + .../admissions/database/AdmissionsEntity.kt | 30 ++++ .../database/AdmissionsRepository.kt | 8 ++ .../core/admissions/database/StudentType.kt | 5 + .../core/admissions/dto/AdmissionsDto.kt | 29 ++++ .../admissions/service/AdmissionsService.kt | 50 +++++++ .../core/research/api/ResearchController.kt | 52 +++++++ .../core/research/database/LabEntity.kt | 41 +++++- .../core/research/database/LabRepository.kt | 1 + .../core/research/database/ResearchEntity.kt | 34 +++++ .../research/database/ResearchPostType.kt | 5 + .../research/database/ResearchRepository.kt | 8 ++ .../csereal/core/research/dto/LabDto.kt | 36 +++++ .../csereal/core/research/dto/ResearchDto.kt | 33 +++++ .../research/dto/ResearchGroupResponse.kt | 7 + .../core/research/service/ResearchService.kt | 133 ++++++++++++++++++ .../dto/introductionMaterialDto.kt | 8 ++ 18 files changed, 525 insertions(+), 4 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/AdmissionsController.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionPostType.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsRepository.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/StudentType.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/admissions/dto/AdmissionsDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/admissions/service/AdmissionsService.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchPostType.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchRepository.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/research/dto/LabDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchGroupResponse.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/resource/introductionMaterial/dto/introductionMaterialDto.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/AdmissionsController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/AdmissionsController.kt new file mode 100644 index 00000000..0eee0bcc --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/AdmissionsController.kt @@ -0,0 +1,44 @@ +package com.wafflestudio.csereal.core.admissions.api + +import com.wafflestudio.csereal.core.admissions.database.AdmissionPostType +import com.wafflestudio.csereal.core.admissions.database.StudentType +import com.wafflestudio.csereal.core.admissions.dto.AdmissionsDto +import com.wafflestudio.csereal.core.admissions.service.AdmissionsService +import jakarta.validation.Valid +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController + +@RequestMapping("/admissions") +@RestController +class AdmissionsController( + private val admissionsService: AdmissionsService +) { + @PostMapping + fun createAdmissions( + @RequestParam studentType: StudentType, + @Valid @RequestBody request: AdmissionsDto + ) : AdmissionsDto { + return admissionsService.createAdmissions(studentType, request) + } + + @GetMapping + fun readAdmissionsMain( + @RequestParam studentType: StudentType, + ) : ResponseEntity { + return ResponseEntity.ok(admissionsService.readAdmissionsMain(studentType)) + } + @GetMapping("/undergraduate") + fun readUndergraduateAdmissions( + @RequestParam postType: AdmissionPostType + ) : ResponseEntity { + return ResponseEntity.ok(admissionsService.readUndergraduateAdmissions(postType)) + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionPostType.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionPostType.kt new file mode 100644 index 00000000..493f01c0 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionPostType.kt @@ -0,0 +1,5 @@ +package com.wafflestudio.csereal.core.admissions.database + +enum class AdmissionPostType { + MAIN, EARLY_ADMISSION, REGULAR_ADMISSION, +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsEntity.kt new file mode 100644 index 00000000..b6fd07d5 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsEntity.kt @@ -0,0 +1,30 @@ +package com.wafflestudio.csereal.core.admissions.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.core.admissions.dto.AdmissionsDto +import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated + +@Entity(name = "admissions") +class AdmissionsEntity( + @Enumerated(EnumType.STRING) + var studentType: StudentType, + @Enumerated(EnumType.STRING) + val postType: AdmissionPostType, + val title: String, + val description: String, + val isPublic: Boolean, +): BaseTimeEntity() { + companion object { + fun of(studentType: StudentType, admissionsDto: AdmissionsDto) : AdmissionsEntity { + return AdmissionsEntity( + studentType = studentType, + postType = admissionsDto.postType, + title = admissionsDto.title, + description = admissionsDto.description, + isPublic = admissionsDto.isPublic + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsRepository.kt new file mode 100644 index 00000000..74c3f1e8 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsRepository.kt @@ -0,0 +1,8 @@ +package com.wafflestudio.csereal.core.admissions.database + +import org.springframework.data.jpa.repository.JpaRepository + +interface AdmissionsRepository : JpaRepository { + fun findByStudentTypeAndPostType(studentType: StudentType, postType: AdmissionPostType): AdmissionsEntity + fun findByPostType(postType: AdmissionPostType) : AdmissionsEntity +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/StudentType.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/StudentType.kt new file mode 100644 index 00000000..b549da95 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/StudentType.kt @@ -0,0 +1,5 @@ +package com.wafflestudio.csereal.core.admissions.database + +enum class StudentType { + GRADUATE, UNDERGRADUATE +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/dto/AdmissionsDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/dto/AdmissionsDto.kt new file mode 100644 index 00000000..cce8196b --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/dto/AdmissionsDto.kt @@ -0,0 +1,29 @@ +package com.wafflestudio.csereal.core.admissions.dto + +import com.wafflestudio.csereal.core.admissions.database.AdmissionPostType +import com.wafflestudio.csereal.core.admissions.database.AdmissionsEntity +import java.time.LocalDateTime + +data class AdmissionsDto( + val id: Long, + val postType: AdmissionPostType, + val title: String, + val description: String, + val createdAt: LocalDateTime?, + val modifiedAt: LocalDateTime?, + val isPublic: Boolean, +) { + companion object { + fun of(entity: AdmissionsEntity) : AdmissionsDto = entity.run { + AdmissionsDto( + id = this.id, + postType = this.postType, + title = this.title, + description = this.description, + createdAt = this.createdAt, + modifiedAt = this.modifiedAt, + isPublic = this.isPublic + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/service/AdmissionsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/service/AdmissionsService.kt new file mode 100644 index 00000000..d8289811 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/service/AdmissionsService.kt @@ -0,0 +1,50 @@ +package com.wafflestudio.csereal.core.admissions.service + +import com.wafflestudio.csereal.core.admissions.database.AdmissionPostType +import com.wafflestudio.csereal.core.admissions.database.AdmissionsEntity +import com.wafflestudio.csereal.core.admissions.database.AdmissionsRepository +import com.wafflestudio.csereal.core.admissions.database.StudentType +import com.wafflestudio.csereal.core.admissions.dto.AdmissionsDto +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +interface AdmissionsService { + fun createAdmissions(studentType: StudentType, request: AdmissionsDto): AdmissionsDto + fun readAdmissionsMain(studentType: StudentType): AdmissionsDto + fun readUndergraduateAdmissions(postType: AdmissionPostType): AdmissionsDto + +} + +@Service +class AdmissionsServiceImpl( + private val admissionsRepository: AdmissionsRepository +) : AdmissionsService { + @Transactional + override fun createAdmissions(studentType: StudentType, request: AdmissionsDto): AdmissionsDto { + val newAdmissions: AdmissionsEntity = AdmissionsEntity.of(studentType, request) + + admissionsRepository.save(newAdmissions) + + return AdmissionsDto.of(newAdmissions) + } + + @Transactional(readOnly = true) + override fun readAdmissionsMain(studentType: StudentType): AdmissionsDto { + return if (studentType == StudentType.UNDERGRADUATE) { + AdmissionsDto.of(admissionsRepository.findByStudentTypeAndPostType(StudentType.UNDERGRADUATE, AdmissionPostType.MAIN)) + } else { + AdmissionsDto.of(admissionsRepository.findByStudentTypeAndPostType(StudentType.GRADUATE, AdmissionPostType.MAIN)) + } + } + + @Transactional(readOnly = true) + override fun readUndergraduateAdmissions(postType: AdmissionPostType): AdmissionsDto { + return if (postType == AdmissionPostType.EARLY_ADMISSION) { + AdmissionsDto.of(admissionsRepository.findByPostType(AdmissionPostType.EARLY_ADMISSION)) + } else { + AdmissionsDto.of(admissionsRepository.findByPostType(AdmissionPostType.REGULAR_ADMISSION)) + } + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt new file mode 100644 index 00000000..ff5607f7 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt @@ -0,0 +1,52 @@ +package com.wafflestudio.csereal.core.research.api + +import com.wafflestudio.csereal.core.research.dto.LabDto +import com.wafflestudio.csereal.core.research.dto.ResearchDto +import com.wafflestudio.csereal.core.research.dto.ResearchGroupResponse +import com.wafflestudio.csereal.core.research.service.ResearchService +import jakarta.validation.Valid +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* + +@RequestMapping("/research") +@RestController +class ResearchController( + private val researchService: ResearchService +) { + @PostMapping + fun createResearchDetail( + @Valid @RequestBody request: ResearchDto + ) : ResponseEntity { + return ResponseEntity.ok(researchService.createResearchDetail(request)) + } + + @GetMapping("/groups") + fun readAllResearchGroups() : ResponseEntity { + return ResponseEntity.ok(researchService.readAllResearchGroups()) + } + + @GetMapping("/centers") + fun readAllResearchCenters() : ResponseEntity> { + return ResponseEntity.ok(researchService.readAllResearchCenters()) + } + + @PatchMapping("/{researchId}") + fun updateResearchDetail( + @PathVariable researchId: Long, + @Valid @RequestBody request: ResearchDto + ) : ResponseEntity { + return ResponseEntity.ok(researchService.updateResearchDetail(researchId, request)) + } + + @PostMapping("/lab") + fun createLab( + @Valid @RequestBody request: LabDto + ) : ResponseEntity { + return ResponseEntity.ok(researchService.createLab(request)) + } + + @GetMapping("/labs") + fun readAllLabs() : ResponseEntity> { + return ResponseEntity.ok(researchService.readAllLabs()) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabEntity.kt index 06b88e10..3720f60e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabEntity.kt @@ -2,8 +2,8 @@ package com.wafflestudio.csereal.core.research.database import com.wafflestudio.csereal.common.config.BaseTimeEntity import com.wafflestudio.csereal.core.member.database.ProfessorEntity -import jakarta.persistence.Entity -import jakarta.persistence.OneToMany +import com.wafflestudio.csereal.core.research.dto.LabDto +import jakarta.persistence.* @Entity(name = "lab") class LabEntity( @@ -11,5 +11,38 @@ class LabEntity( val name: String, @OneToMany(mappedBy = "lab") - val professors: MutableSet = mutableSetOf() -) : BaseTimeEntity() + val professors: MutableSet = mutableSetOf(), + + val location: String?, + val tel: String?, + val acronym: String?, + val pdf: String?, + val youtube: String?, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "research_id") + var research: ResearchEntity, + + val description: String?, + + val websiteURL: String?, + val isPublic: Boolean, + +) : BaseTimeEntity() { + companion object { + fun of(researchGroup: ResearchEntity, labDto: LabDto) : LabEntity { + return LabEntity( + name = labDto.name, + location = labDto.location, + tel = labDto.tel, + acronym = labDto.acronym, + pdf = labDto.introductionMaterials?.pdf, + youtube = labDto.introductionMaterials?.youtube, + research = researchGroup, + description = labDto.description, + websiteURL = labDto.websiteURL, + isPublic = labDto.isPublic, + ) + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabRepository.kt index 84bf190a..c7e15692 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabRepository.kt @@ -3,4 +3,5 @@ package com.wafflestudio.csereal.core.research.database import org.springframework.data.jpa.repository.JpaRepository interface LabRepository : JpaRepository { + fun findAllByOrderByName(): List } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchEntity.kt new file mode 100644 index 00000000..ad3d294f --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchEntity.kt @@ -0,0 +1,34 @@ +package com.wafflestudio.csereal.core.research.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.core.research.dto.ResearchDto +import jakarta.persistence.* + +@Entity(name = "research") +class ResearchEntity( + @Enumerated(EnumType.STRING) + var postType: ResearchPostType, + + var name: String, + + var description: String?, + + var websiteURL: String?, + + var isPublic: Boolean, + + @OneToMany(mappedBy = "research", cascade = [CascadeType.ALL], orphanRemoval = true) + var labs: MutableList = mutableListOf() +): BaseTimeEntity() { + companion object { + fun of(researchDto: ResearchDto) : ResearchEntity { + return ResearchEntity( + postType = researchDto.postType, + name = researchDto.name, + description = researchDto.description, + websiteURL = researchDto.websiteURL, + isPublic = researchDto.isPublic + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchPostType.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchPostType.kt new file mode 100644 index 00000000..c86016b0 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchPostType.kt @@ -0,0 +1,5 @@ +package com.wafflestudio.csereal.core.research.database + +enum class ResearchPostType { + GROUPS, CENTERS, LABS +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchRepository.kt new file mode 100644 index 00000000..e9a9fd8a --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchRepository.kt @@ -0,0 +1,8 @@ +package com.wafflestudio.csereal.core.research.database + +import org.springframework.data.jpa.repository.JpaRepository + +interface ResearchRepository : JpaRepository { + fun findByName(name: String): ResearchEntity? + fun findAllByPostTypeOrderByName(postType: ResearchPostType): List +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/LabDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/LabDto.kt new file mode 100644 index 00000000..82f95b23 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/LabDto.kt @@ -0,0 +1,36 @@ +package com.wafflestudio.csereal.core.research.dto + +import com.wafflestudio.csereal.core.research.database.LabEntity +import com.wafflestudio.csereal.core.resource.introductionMaterial.dto.IntroductionMaterialDto + +data class LabDto( + val id: Long, + val name: String, + val professors: List?, + val location: String?, + val tel: String?, + val acronym: String?, + val introductionMaterials: IntroductionMaterialDto?, + val group: String, + val description: String?, + val websiteURL: String?, + val isPublic: Boolean, +) { + companion object { + fun of(entity: LabEntity): LabDto = entity.run { + LabDto( + id = this.id, + name = this.name, + professors = this.professors.map { it.name }, + location = this.location, + tel = this.tel, + acronym = this.acronym, + introductionMaterials = IntroductionMaterialDto(this.pdf, this.youtube), + group = this.research.name, + description = this.description, + websiteURL = this.websiteURL, + isPublic = this.isPublic + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchDto.kt new file mode 100644 index 00000000..e81ba4fd --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchDto.kt @@ -0,0 +1,33 @@ +package com.wafflestudio.csereal.core.research.dto + +import com.wafflestudio.csereal.core.research.database.ResearchEntity +import com.wafflestudio.csereal.core.research.database.ResearchPostType +import java.time.LocalDateTime + +data class ResearchDto( + val id: Long, + val postType: ResearchPostType, + val name: String, + val description: String?, + val websiteURL: String?, + val createdAt: LocalDateTime?, + val modifiedAt: LocalDateTime?, + val isPublic: Boolean, + val labsId: List? +) { + companion object { + fun of(entity: ResearchEntity) = entity.run { + ResearchDto( + id = this.id, + postType = this.postType, + name = this.name, + description = this.description, + websiteURL = this.websiteURL, + createdAt = this.createdAt, + modifiedAt = this.modifiedAt, + isPublic = this.isPublic, + labsId = this.labs.map { it.id } + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchGroupResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchGroupResponse.kt new file mode 100644 index 00000000..6bd08a2c --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchGroupResponse.kt @@ -0,0 +1,7 @@ +package com.wafflestudio.csereal.core.research.dto + +data class ResearchGroupResponse( + val description: String, + val groups: List +) { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt new file mode 100644 index 00000000..e42bc7c7 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt @@ -0,0 +1,133 @@ +package com.wafflestudio.csereal.core.research.service + +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.member.database.ProfessorRepository +import com.wafflestudio.csereal.core.research.database.* +import com.wafflestudio.csereal.core.research.dto.LabDto +import com.wafflestudio.csereal.core.research.dto.ResearchDto +import com.wafflestudio.csereal.core.research.dto.ResearchGroupResponse +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +interface ResearchService { + fun createResearchDetail(request: ResearchDto): ResearchDto + fun readAllResearchGroups(): ResearchGroupResponse + fun readAllResearchCenters(): List + fun updateResearchDetail(researchId: Long, request: ResearchDto): ResearchDto + fun createLab(request: LabDto): LabDto + + fun readAllLabs(): List +} + +@Service +class ResearchServiceImpl( + private val researchRepository: ResearchRepository, + private val labRepository: LabRepository, + private val professorRepository: ProfessorRepository +) : ResearchService { + @Transactional + override fun createResearchDetail(request: ResearchDto): ResearchDto { + val newResearch = ResearchEntity.of(request) + if(request.labsId != null) { + + for(labId in request.labsId) { + val lab = labRepository.findByIdOrNull(labId) + ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다.(labId=$labId)") + newResearch.labs.add(lab) + lab.research = newResearch + } + } + + researchRepository.save(newResearch) + + return ResearchDto.of(newResearch) + } + + @Transactional(readOnly = true) + override fun readAllResearchGroups(): ResearchGroupResponse { + // Todo: description 수정 필요 + val description = "세계가 주목하는 컴퓨터공학부의 많은 교수들은 ACM, IEEE 등 " + + "세계적인 컴퓨터관련 주요 학회에서 국제학술지 편집위원, 국제학술회의 위원장, 기조연설자 등으로 활발하게 활동하고 있습니다. " + + "정부 지원과제, 민간 산업체 지원 연구과제 등도 성공적으로 수행, 우수한 성과들을 내놓고 있으며, " + + "오늘도 인류가 꿈꾸는 행복하고 편리한 세상을 위해 변화와 혁신, 연구와 도전을 계속하고 있습니다." + + val researchGroups = researchRepository.findAllByPostTypeOrderByName(ResearchPostType.GROUPS).map { + ResearchDto.of(it) + } + + return ResearchGroupResponse(description, researchGroups) + } + + @Transactional(readOnly = true) + override fun readAllResearchCenters(): List { + val researchCenters = researchRepository.findAllByPostTypeOrderByName(ResearchPostType.CENTERS).map { + ResearchDto.of(it) + } + + return researchCenters + } + @Transactional + override fun updateResearchDetail(researchId: Long, request: ResearchDto): ResearchDto { + val research = researchRepository.findByIdOrNull(researchId) + ?: throw CserealException.Csereal404("해당 게시글을 찾을 수 없습니다.(researchId=$researchId)") + + if(request.labsId != null) { + for(labId in request.labsId) { + val lab = labRepository.findByIdOrNull(labId) + ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다.(labId=$labId)") + + } + + val oldLabs = research.labs.map { it.id } + + val labsToRemove = oldLabs - request.labsId + val labsToAdd = request.labsId - oldLabs + + research.labs.removeIf { it.id in labsToRemove} + + for(labsToAddId in labsToAdd) { + val lab = labRepository.findByIdOrNull(labsToAddId)!! + research.labs.add(lab) + lab.research = research + + } + } + + return ResearchDto.of(research) + } + + @Transactional + override fun createLab(request: LabDto): LabDto { + val researchGroup = researchRepository.findByName(request.group) + ?: throw CserealException.Csereal404("해당 연구그룹을 찾을 수 없습니다.(researchGroupId = ${request.group}") + + if(researchGroup.postType != ResearchPostType.GROUPS) { + throw CserealException.Csereal404("해당 게시글은 연구그룹이어야 합니다.") + } + + // get을 우선 구현하기 위해 빼겠습니다 + /* + if(request.professorsId != null) { + for(professorId in request.professorsId) { + val professor = professorRepository.findByIdOrNull(professorId) + ?: throw CserealException.Csereal404("해당 교수님을 찾을 수 없습니다.(professorId = $professorId") + } + } + + */ + val newLab = LabEntity.of(researchGroup, request) + + labRepository.save(newLab) + return LabDto.of(newLab) + } + + @Transactional(readOnly = true) + override fun readAllLabs(): List { + val labs = labRepository.findAllByOrderByName().map { + LabDto.of(it) + } + + return labs + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/introductionMaterial/dto/introductionMaterialDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/introductionMaterial/dto/introductionMaterialDto.kt new file mode 100644 index 00000000..f4c9873c --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/introductionMaterial/dto/introductionMaterialDto.kt @@ -0,0 +1,8 @@ +package com.wafflestudio.csereal.core.resource.introductionMaterial.dto + +// Todo: IntroductionMaterial이 연구실에만 쓰이는지 확인할 것 +data class IntroductionMaterialDto( + val pdf: String?, + val youtube: String?, +) { +} \ No newline at end of file From d2c341c06e23cde7a6937fb36788bf06d2649c29 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Tue, 22 Aug 2023 14:35:29 +0900 Subject: [PATCH 019/124] =?UTF-8?q?feat:=20oidc=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20(#27)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: oidc 로그인 * feat: TaskEntity name 추가 * feat: 배포 설정 * feat: 유저 정보에 학번 추가, 로그인 시 sub claim 확인 * feat: 배포 테스트 위해 redirect-uri 변경 * fix: groups claim 소문자로 수정 * feat: idsnucse 다운 되었을때 에러 처리 * feat: idsnucse 다운 되었을때 에러 처리 --- .github/workflows/deploy.yaml | 4 +- docker-compose.yml | 11 ++- .../wafflestudio/csereal/common/Exceptions.kt | 1 + .../common/config/CserealExceptionHandler.kt | 10 +- .../csereal/common/config/JwtConfig.kt | 20 ++++ .../common/config/RestTemplateConfig.kt | 15 +++ .../csereal/common/config/SecurityConfig.kt | 55 ++++++++--- .../core/member/database/TaskEntity.kt | 2 +- .../csereal/core/user/database/UserEntity.kt | 23 +++++ .../core/user/database/UserRepository.kt | 7 ++ .../user/service/CustomOidcUserService.kt | 96 +++++++++++++++++++ src/main/resources/application.yaml | 28 +++++- 12 files changed, 247 insertions(+), 25 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/common/config/JwtConfig.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/common/config/RestTemplateConfig.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/user/database/UserEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/user/database/UserRepository.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/user/service/CustomOidcUserService.kt diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 5b3d3600..4a002e96 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -53,6 +53,7 @@ jobs: echo "MYSQL_PASSWORD=${{secrets.MYSQL_PASSWORD}}" >> .env echo "MYSQL_DATABASE=${{secrets.MYSQL_DATABASE}}" >> .env echo "PROFILE=prod" >> .env + echo "OIDC_CLIENT_SECRET_DEV=${{secrets.OIDC_CLIENT_SECRET_DEV}}" >> .env - name: SCP Command to Transfer Files @@ -73,6 +74,7 @@ jobs: MYSQL_PASSWORD: ${{secrets.MYSQL_PASSWORD}} MYSQL_DATABASE: ${{secrets.MYSQL_DATABASE}} PROFILE: "prod" + OIDC_CLIENT_SECRET_DEV: ${{secrets.OIDC_CLIENT_SECRET_DEV}} with: host: ${{secrets.SSH_HOST}} username: ${{secrets.SSH_USER}} @@ -82,4 +84,4 @@ jobs: source .env docker-compose down docker-compose pull - docker-compose up -d \ No newline at end of file + docker-compose up -d diff --git a/docker-compose.yml b/docker-compose.yml index 9cf741c8..ac6b0f24 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,6 +11,7 @@ services: SPRING_DATASOURCE_URL: "jdbc:mysql://csereal_db_container:3306/${MYSQL_DATABASE}?serverTimezone=Asia/Seoul&useSSL=false&allowPublicKeyRetrieval=true" SPRING_DATASOURCE_USERNAME: ${MYSQL_USER} SPRING_DATASOURCE_PASSWORD: ${MYSQL_PASSWORD} + OIDC_CLIENT_SECRET_DEV: ${OIDC_CLIENT_SECRET_DEV} depends_on: - db networks: @@ -24,11 +25,11 @@ services: volumes: - ./db:/var/lib/mysql environment: - MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} - MYSQL_DATABASE: ${MYSQL_DATABASE} - MYSQL_USER: ${MYSQL_USER} - MYSQL_PASSWORD: ${MYSQL_PASSWORD} + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} + MYSQL_DATABASE: ${MYSQL_DATABASE} + MYSQL_USER: ${MYSQL_USER} + MYSQL_PASSWORD: ${MYSQL_PASSWORD} networks: - csereal_network networks: - csereal_network: \ No newline at end of file + csereal_network: diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/Exceptions.kt b/src/main/kotlin/com/wafflestudio/csereal/common/Exceptions.kt index cb0d7b08..2809d4f6 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/Exceptions.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/Exceptions.kt @@ -5,4 +5,5 @@ import org.springframework.http.HttpStatus open class CserealException(msg: String, val status: HttpStatus) : RuntimeException(msg) { class Csereal400(msg: String) : CserealException(msg, HttpStatus.BAD_REQUEST) class Csereal404(msg: String) : CserealException(msg, HttpStatus.NOT_FOUND) + class Csereal401(msg: String) : CserealException(msg, HttpStatus.UNAUTHORIZED) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/CserealExceptionHandler.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/CserealExceptionHandler.kt index 6cf3d136..c93f7b80 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/CserealExceptionHandler.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/CserealExceptionHandler.kt @@ -7,6 +7,8 @@ import org.springframework.validation.BindingResult import org.springframework.web.bind.MethodArgumentNotValidException import org.springframework.web.bind.annotation.ExceptionHandler import org.springframework.web.bind.annotation.RestControllerAdvice +import org.springframework.web.client.ResourceAccessException +import org.springframework.web.client.RestClientException import java.sql.SQLIntegrityConstraintViolationException @RestControllerAdvice @@ -30,4 +32,10 @@ class CserealExceptionHandler { fun handle(e: SQLIntegrityConstraintViolationException): ResponseEntity { return ResponseEntity("중복된 값이 있습니다.", HttpStatus.CONFLICT) } -} \ No newline at end of file + + // oidc provider 서버에 문제가 있을때 + @ExceptionHandler(value = [RestClientException::class]) + fun handle(e: RestClientException): ResponseEntity { + return ResponseEntity("idsnucse error: ${e.message}", HttpStatus.BAD_GATEWAY) + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/JwtConfig.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/JwtConfig.kt new file mode 100644 index 00000000..76af30f3 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/JwtConfig.kt @@ -0,0 +1,20 @@ +package com.wafflestudio.csereal.common.config + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.security.oauth2.client.oidc.authentication.OidcIdTokenDecoderFactory +import org.springframework.security.oauth2.client.registration.ClientRegistration +import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm +import org.springframework.security.oauth2.jwt.JwtDecoderFactory + +@Configuration +class JwtConfig { + + @Bean + fun idTokenDecoderFactory(): JwtDecoderFactory { + val idTokenDecoderFactory = OidcIdTokenDecoderFactory() + idTokenDecoderFactory.setJwsAlgorithmResolver { SignatureAlgorithm.ES256 } + return idTokenDecoderFactory + } + +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/RestTemplateConfig.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/RestTemplateConfig.kt new file mode 100644 index 00000000..e7497867 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/RestTemplateConfig.kt @@ -0,0 +1,15 @@ +package com.wafflestudio.csereal.common.config + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.web.client.RestTemplate + +@Configuration +class RestTemplateConfig { + + @Bean + fun restTemplate(): RestTemplate { + return RestTemplate() + } + +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt index 5ec132d7..5dbb4636 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt @@ -1,30 +1,57 @@ package com.wafflestudio.csereal.common.config +import com.wafflestudio.csereal.core.user.service.CustomOidcUserService +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.security.config.annotation.web.builders.HttpSecurity -import org.springframework.security.config.http.SessionCreationPolicy +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity +import org.springframework.security.core.Authentication import org.springframework.security.web.SecurityFilterChain +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler +import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler +import org.springframework.web.client.RestTemplate @Configuration -class SpringSecurityConfig { +@EnableWebSecurity +class SecurityConfig( + private val customOidcUserService: CustomOidcUserService +) { - // 확인 바람 @Bean - fun securityFilterChain(httpSecurity: HttpSecurity): SecurityFilterChain = - httpSecurity - .csrf().disable() - .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) + fun filterChain(http: HttpSecurity): SecurityFilterChain { + return http.csrf().disable() + .oauth2Login() + .loginPage("/oauth2/authorization/idsnucse") + .userInfoEndpoint().oidcUserService(customOidcUserService).and() .and() - .authorizeRequests() + .logout() + .logoutSuccessHandler(oidcLogoutSuccessHandler()) + .invalidateHttpSession(true) + .clearAuthentication(true) + .deleteCookies("JSESSIONID") + .and() + .authorizeHttpRequests() + .requestMatchers("/login").authenticated() .anyRequest().permitAll() .and() .build() + } + + @Bean + fun oidcLogoutSuccessHandler(): LogoutSuccessHandler { + return object : SimpleUrlLogoutSuccessHandler() { + override fun onLogoutSuccess( + request: HttpServletRequest?, + response: HttpServletResponse?, + authentication: Authentication? + ) { + super.setDefaultTargetUrl("/") + super.onLogoutSuccess(request, response, authentication) + } + } + } -// @Bean -// fun filterChain(http: HttpSecurity): SecurityFilterChain { -// http.httpBasic().disable() -// return http.build() -// } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/TaskEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/TaskEntity.kt index 745365dd..d30a96b3 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/TaskEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/TaskEntity.kt @@ -6,7 +6,7 @@ import jakarta.persistence.FetchType import jakarta.persistence.JoinColumn import jakarta.persistence.ManyToOne -@Entity +@Entity(name = "task") class TaskEntity( val name: String, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/user/database/UserEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/user/database/UserEntity.kt new file mode 100644 index 00000000..2dca2073 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/user/database/UserEntity.kt @@ -0,0 +1,23 @@ +package com.wafflestudio.csereal.core.user.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated + +@Entity(name = "users") +class UserEntity( + + val username: String, + val name: String, + val email: String, + val studentId: String, + + @Enumerated(EnumType.STRING) + val role: Role?, + + ) : BaseTimeEntity() + +enum class Role { + ROLE_STAFF, ROLE_GRADUATE, ROLE_PROFESSOR +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/user/database/UserRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/user/database/UserRepository.kt new file mode 100644 index 00000000..38b8b367 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/user/database/UserRepository.kt @@ -0,0 +1,7 @@ +package com.wafflestudio.csereal.core.user.database + +import org.springframework.data.jpa.repository.JpaRepository + +interface UserRepository : JpaRepository { + fun findByUsername(username: String): UserEntity? +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/user/service/CustomOidcUserService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/user/service/CustomOidcUserService.kt new file mode 100644 index 00000000..551ce684 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/user/service/CustomOidcUserService.kt @@ -0,0 +1,96 @@ +package com.wafflestudio.csereal.core.user.service + +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.user.database.Role +import com.wafflestudio.csereal.core.user.database.UserEntity +import com.wafflestudio.csereal.core.user.database.UserRepository +import org.springframework.http.HttpEntity +import org.springframework.http.HttpHeaders +import org.springframework.http.HttpMethod +import org.springframework.http.MediaType +import org.springframework.security.core.authority.SimpleGrantedAuthority +import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest +import org.springframework.security.oauth2.client.userinfo.OAuth2UserService +import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser +import org.springframework.security.oauth2.core.oidc.user.OidcUser +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import org.springframework.util.LinkedMultiValueMap +import org.springframework.web.client.RestTemplate +import org.springframework.web.client.exchange + +@Service +class CustomOidcUserService( + private val userRepository: UserRepository, + private val restTemplate: RestTemplate +) : OAuth2UserService { + + override fun loadUser(userRequest: OidcUserRequest): OidcUser { + val oidcUser = DefaultOidcUser( + userRequest.clientRegistration.scopes.map { SimpleGrantedAuthority("SCOPE_$it") }, + userRequest.idToken + ) + + val username = oidcUser.idToken.getClaim("username") + val user = userRepository.findByUsername(username) + + if (user == null) { + val userInfoAttributes = fetchUserInfo(userRequest) + createUser(username, userInfoAttributes) + } + + return oidcUser + } + + private fun fetchUserInfo(userRequest: OidcUserRequest): Map { + val headers = HttpHeaders().apply { + contentType = MediaType.APPLICATION_FORM_URLENCODED + } + + val body = LinkedMultiValueMap().apply { + add("access_token", userRequest.accessToken.tokenValue) + } + + val requestEntity = HttpEntity(body, headers) + + val userInfoResponse = restTemplate.exchange>( + userRequest.clientRegistration.providerDetails.userInfoEndpoint.uri, + HttpMethod.POST, requestEntity, Map::class.java + ) + + if (userInfoResponse.body?.get("sub") != userRequest.idToken.getClaim("sub")) { + throw CserealException.Csereal401("Authentication failed") + } + + return userInfoResponse.body ?: emptyMap() + } + + @Transactional + fun createUser(username: String, userInfo: Map) { + + val name = userInfo["name"] as String + val email = userInfo["email"] as String + val studentId = userInfo["student_id"] as String + + val groups = userInfo["groups"] as List + val role = if ("staff" in groups) { + Role.ROLE_STAFF + } else if ("professor" in groups) { + Role.ROLE_PROFESSOR + } else if ("graduate" in groups) { + Role.ROLE_GRADUATE + } else { + null + } + + val newUser = UserEntity( + username = username, + name = name, + email = email, + studentId = studentId, + role = role + ) + + userRepository.save(newUser) + } +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 8ffade6f..6f1e5d75 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1,9 +1,28 @@ spring: profiles: active: local + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + security: + oauth2: + client: + registration: + idsnucse: + client-id: waffle-dev-local-testing + client-secret: ${OIDC_CLIENT_SECRET_DEV} + authorization-grant-type: authorization_code + redirect-uri: http://cse-dev-waffle.bacchus.io/login/oauth2/code/idsnucse + scope: openid, profile, email + provider: + idsnucse: + issuer-uri: https://id-dev.bacchus.io/o + jwk-set-uri: https://id-dev.bacchus.io/o/jwks + +server: + servlet: + session: + timeout: 7200 # 2시간 -datasource: - driver-class-name: com.mysql.cj.jdbc.Driver springdoc: swagger-ui: path: index.html @@ -24,6 +43,9 @@ spring: open-in-view: false logging.level: default: INFO + org: + springframework: + security: DEBUG --- spring: @@ -31,4 +53,4 @@ spring: jpa: hibernate: ddl-auto: create # TODO: change to validate (or none) when save actual data to server - open-in-view: false \ No newline at end of file + open-in-view: false From e05b5ad2ab7c23d6ff4ab2d70ca1d9d5ea1b8a8e Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Tue, 22 Aug 2023 15:43:10 +0900 Subject: [PATCH 020/124] =?UTF-8?q?feat:=20cors=20=EC=84=A4=EC=A0=95=20(#3?= =?UTF-8?q?0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../csereal/common/config/SecurityConfig.kt | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt index 5dbb4636..8c38d9d2 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt @@ -11,7 +11,9 @@ import org.springframework.security.core.Authentication import org.springframework.security.web.SecurityFilterChain import org.springframework.security.web.authentication.logout.LogoutSuccessHandler import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler -import org.springframework.web.client.RestTemplate +import org.springframework.web.cors.CorsConfiguration +import org.springframework.web.cors.CorsConfigurationSource +import org.springframework.web.cors.UrlBasedCorsConfigurationSource @Configuration @@ -23,6 +25,8 @@ class SecurityConfig( @Bean fun filterChain(http: HttpSecurity): SecurityFilterChain { return http.csrf().disable() + .cors() + .and() .oauth2Login() .loginPage("/oauth2/authorization/idsnucse") .userInfoEndpoint().oidcUserService(customOidcUserService).and() @@ -54,4 +58,16 @@ class SecurityConfig( } } + @Bean + fun corsConfigurationSource(): CorsConfigurationSource { + val configuration = CorsConfiguration() + configuration.allowedOrigins = listOf("http://localhost:3000", "http://cse-dev-waffle.bacchus.io:3000") + configuration.allowedMethods = listOf("*") + configuration.allowedHeaders = listOf("*") + configuration.maxAge = 3000 + val source = UrlBasedCorsConfigurationSource() + source.registerCorsConfiguration("/**", configuration) + return source + } + } From 28fa788f5495186cdcedd25065565414b4fd082c Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Tue, 22 Aug 2023 16:52:07 +0900 Subject: [PATCH 021/124] =?UTF-8?q?fix:=20cors=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: cors 설정 * feat: cors 설정 --- .../csereal/common/config/WebConfig.kt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/common/config/WebConfig.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/WebConfig.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/WebConfig.kt new file mode 100644 index 00000000..3c1a296c --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/WebConfig.kt @@ -0,0 +1,18 @@ +package com.wafflestudio.csereal.common.config + +import org.springframework.context.annotation.Configuration +import org.springframework.web.servlet.config.annotation.CorsRegistry +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer + +@Configuration +class WebConfig : WebMvcConfigurer { + + override fun addCorsMappings(registry: CorsRegistry) { + registry.addMapping("/**") + .allowedOrigins("http://localhost:3000", "http://cse-dev-waffle.bacchus.io:3000") + .allowedMethods("*") + .allowedHeaders("*") + .maxAge(3000) + } + +} From 3e0f8e7ec04226784f5c95ada2e2714ba5d28e2f Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Wed, 23 Aug 2023 15:29:39 +0900 Subject: [PATCH 022/124] fix: CORS (#34) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: cors 설정 * feat: cors 설정 * feat: cors 설정 --- .../com/wafflestudio/csereal/common/config/SecurityConfig.kt | 2 +- .../kotlin/com/wafflestudio/csereal/common/config/WebConfig.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt index 2468a89a..f34e8c9a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt @@ -62,7 +62,7 @@ class SecurityConfig( @Bean fun corsConfigurationSource(): CorsConfigurationSource { val configuration = CorsConfiguration() - configuration.allowedOrigins = listOf("http://localhost:3000", "http://cse-dev-waffle.bacchus.io:3000") + configuration.allowedOrigins = listOf("*") configuration.allowedMethods = listOf("*") configuration.allowedHeaders = listOf("*") configuration.maxAge = 3000 diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/WebConfig.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/WebConfig.kt index 3c1a296c..02795670 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/WebConfig.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/WebConfig.kt @@ -9,7 +9,7 @@ class WebConfig : WebMvcConfigurer { override fun addCorsMappings(registry: CorsRegistry) { registry.addMapping("/**") - .allowedOrigins("http://localhost:3000", "http://cse-dev-waffle.bacchus.io:3000") + .allowedOrigins("*") .allowedMethods("*") .allowedHeaders("*") .maxAge(3000) From a86542c339925a9111850de67ad1a91ff73d84be Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Thu, 24 Aug 2023 21:14:22 +0900 Subject: [PATCH 023/124] =?UTF-8?q?fix:=20about,=20academics,=20admissions?= =?UTF-8?q?=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=88=98=EC=A0=95=20(#25)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: admissions, about 컨트롤러 변경 * fix: academics 컨트롤러 수정 * 커밋중 * fix: pr 리뷰 반영 * feat: readMain 추가 * pr 리뷰 반영 --- .../csereal/core/about/api/AboutController.kt | 9 +-- .../core/about/database/AboutEntity.kt | 20 ++--- .../core/about/database/AboutPostType.kt | 5 ++ .../core/about/database/AboutRepository.kt | 4 +- .../csereal/core/about/dto/AboutDto.kt | 6 +- .../core/about/service/AboutService.kt | 28 +++++-- .../core/academics/api/AcademicsController.kt | 32 ++++---- .../academics/database/AcademicsEntity.kt | 12 ++- .../academics/database/AcademicsPostType.kt | 5 ++ .../academics/database/AcademicsRepository.kt | 2 +- .../database/AcademicsStudentType.kt | 5 ++ .../core/academics/database/CourseEntity.kt | 4 +- .../academics/database/CourseRepository.kt | 2 +- .../core/academics/database/StudentType.kt | 5 -- .../core/academics/dto/AcademicsDto.kt | 2 - .../academics/service/AcademicsService.kt | 73 +++++++++++++------ .../admissions/api/AdmissionsController.kt | 30 +++++--- .../admissions/database/AdmissionPostType.kt | 5 -- .../admissions/database/AdmissionsEntity.kt | 11 +-- .../admissions/database/AdmissionsPostType.kt | 5 ++ .../database/AdmissionsRepository.kt | 3 +- .../core/admissions/database/StudentType.kt | 5 -- .../core/admissions/dto/AdmissionsDto.kt | 5 -- .../admissions/service/AdmissionsService.kt | 52 ++++++++----- .../csereal/core/main/api/MainController.kt | 18 +++++ .../core/main/database/MainRepository.kt | 70 ++++++++++++++++++ .../csereal/core/main/dto/MainResponse.kt | 11 +++ .../csereal/core/main/dto/NewsResponse.kt | 12 +++ .../csereal/core/main/dto/NoticeResponse.kt | 11 +++ .../csereal/core/main/service/MainService.kt | 25 +++++++ 30 files changed, 333 insertions(+), 144 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutPostType.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsPostType.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsStudentType.kt delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/database/StudentType.kt delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionPostType.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsPostType.kt delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/StudentType.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/main/api/MainController.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainResponse.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/main/dto/NewsResponse.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/main/dto/NoticeResponse.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/main/service/MainService.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt index 09e902db..4a1c87c5 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt @@ -9,6 +9,7 @@ import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController @RequestMapping("/about") @@ -16,19 +17,17 @@ import org.springframework.web.bind.annotation.RestController class AboutController( private val aboutService: AboutService ) { - // postType -> 학부 소개: overview, 연혁: history, 졸업생 진로: future-careers, 연락처: contact - // 위에 있는 항목은 name = null - // postType: student-clubs / name -> 가디언, 바쿠스, 사커301, 슈타인, 스눕스, 와플스튜디오, 유피넬 // postType: facilities / name -> 학부-행정실, S-Lab, 소프트웨어-실습실, 하드웨어-실습실, 해동학술정보실, 학생-공간-및-동아리-방, 세미나실, 서버실 // postType: directions / name -> by-public-transit, by-car, from-far-away // Todo: 전체 image, file, 학부장 인사말(greetings) signature - @PostMapping + @PostMapping("/{postType}") fun createAbout( + @PathVariable postType: String, @Valid @RequestBody request: AboutDto ) : ResponseEntity { - return ResponseEntity.ok(aboutService.createAbout(request)) + return ResponseEntity.ok(aboutService.createAbout(postType, request)) } // read 목록이 하나 diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt index 5952e2ff..369e06a9 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt @@ -2,36 +2,28 @@ package com.wafflestudio.csereal.core.about.database import com.wafflestudio.csereal.common.config.BaseTimeEntity import com.wafflestudio.csereal.core.about.dto.AboutDto -import jakarta.persistence.CascadeType -import jakarta.persistence.Entity -import jakarta.persistence.OneToMany +import jakarta.persistence.* @Entity(name = "about") class AboutEntity( - var postType: String, - - var name: String, - + @Enumerated(EnumType.STRING) + var postType: AboutPostType, + var name: String?, var engName: String?, - var description: String, - var year: Int?, - var isPublic: Boolean, - @OneToMany(mappedBy = "about", cascade = [CascadeType.ALL], orphanRemoval = true) val locations: MutableList = mutableListOf() ) : BaseTimeEntity() { companion object { - fun of(aboutDto: AboutDto): AboutEntity { + fun of(postType: AboutPostType, aboutDto: AboutDto): AboutEntity { return AboutEntity( - postType = aboutDto.postType, + postType = postType, name = aboutDto.name, engName = aboutDto.engName, description = aboutDto.description, year = aboutDto.year, - isPublic = aboutDto.isPublic, ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutPostType.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutPostType.kt new file mode 100644 index 00000000..ab12f058 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutPostType.kt @@ -0,0 +1,5 @@ +package com.wafflestudio.csereal.core.about.database + +enum class AboutPostType { + OVERVIEW, HISTORY, FUTURE_CAREERS, CONTACT, STUDENT_CLUBS, FACILITIES, DIRECTIONS, +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutRepository.kt index fe269e39..a66fe46b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutRepository.kt @@ -3,6 +3,6 @@ package com.wafflestudio.csereal.core.about.database import org.springframework.data.jpa.repository.JpaRepository interface AboutRepository : JpaRepository { - fun findAllByPostTypeOrderByName(postType: String): List - fun findByPostType(postType: String): AboutEntity + fun findAllByPostTypeOrderByName(postType: AboutPostType): List + fun findByPostType(postType: AboutPostType): AboutEntity } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutDto.kt index 83a466f6..63652e47 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutDto.kt @@ -6,28 +6,24 @@ import java.time.LocalDateTime data class AboutDto( val id: Long, - val postType: String, - val name: String, + val name: String?, val engName: String?, val description: String, val year: Int?, val createdAt: LocalDateTime?, val modifiedAt: LocalDateTime?, - val isPublic: Boolean, val locations: List? ) { companion object { fun of(entity: AboutEntity) : AboutDto = entity.run { AboutDto( id = this.id, - postType = this.postType, name = this.name, engName = this.engName, description = this.description, year = this.year, createdAt = this.createdAt, modifiedAt = this.modifiedAt, - isPublic = this.isPublic, locations = this.locations.map { it.name } ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt index 77b5b5ba..79b6c8a2 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt @@ -1,6 +1,8 @@ package com.wafflestudio.csereal.core.about.service +import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.core.about.database.AboutEntity +import com.wafflestudio.csereal.core.about.database.AboutPostType import com.wafflestudio.csereal.core.about.database.AboutRepository import com.wafflestudio.csereal.core.about.database.LocationEntity import com.wafflestudio.csereal.core.about.dto.AboutDto @@ -8,7 +10,7 @@ import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional interface AboutService { - fun createAbout(request: AboutDto): AboutDto + fun createAbout(postType: String, request: AboutDto): AboutDto fun readAbout(postType: String): AboutDto fun readAllClubs() : List fun readAllFacilities() : List @@ -20,8 +22,9 @@ class AboutServiceImpl( private val aboutRepository: AboutRepository ) : AboutService { @Transactional - override fun createAbout(request: AboutDto): AboutDto { - val newAbout = AboutEntity.of(request) + override fun createAbout(postType: String, request: AboutDto): AboutDto { + val enumPostType = makeStringToEnum(postType) + val newAbout = AboutEntity.of(enumPostType, request) if(request.locations != null) { for (location in request.locations) { @@ -36,14 +39,15 @@ class AboutServiceImpl( @Transactional(readOnly = true) override fun readAbout(postType: String): AboutDto { - val about = aboutRepository.findByPostType(postType) + val enumPostType = makeStringToEnum(postType) + val about = aboutRepository.findByPostType(enumPostType) return AboutDto.of(about) } @Transactional(readOnly = true) override fun readAllClubs(): List { - val clubs = aboutRepository.findAllByPostTypeOrderByName("student-clubs").map { + val clubs = aboutRepository.findAllByPostTypeOrderByName(AboutPostType.STUDENT_CLUBS).map { AboutDto.of(it) } @@ -52,7 +56,7 @@ class AboutServiceImpl( @Transactional(readOnly = true) override fun readAllFacilities(): List { - val facilities = aboutRepository.findAllByPostTypeOrderByName("facilities").map { + val facilities = aboutRepository.findAllByPostTypeOrderByName(AboutPostType.FACILITIES).map { AboutDto.of(it) } @@ -61,10 +65,20 @@ class AboutServiceImpl( @Transactional(readOnly = true) override fun readAllDirections(): List { - val directions = aboutRepository.findAllByPostTypeOrderByName("directions").map { + val directions = aboutRepository.findAllByPostTypeOrderByName(AboutPostType.DIRECTIONS).map { AboutDto.of(it) } return directions } + + private fun makeStringToEnum(postType: String) : AboutPostType { + try { + val upperPostType = postType.replace("-","_").uppercase() + return AboutPostType.valueOf(upperPostType) + + } catch (e: IllegalArgumentException) { + throw CserealException.Csereal400("해당하는 enum을 찾을 수 없습니다") + } + } } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt index 047a8850..c56e332b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt @@ -1,6 +1,5 @@ package com.wafflestudio.csereal.core.academics.api -import com.wafflestudio.csereal.core.academics.database.StudentType import com.wafflestudio.csereal.core.academics.dto.CourseDto import com.wafflestudio.csereal.core.academics.dto.AcademicsDto import com.wafflestudio.csereal.core.academics.service.AcademicsService @@ -11,6 +10,7 @@ import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController @RequestMapping("/academics") @@ -19,30 +19,28 @@ class AcademicsController( private val academicsService: AcademicsService ) { - // postType -> 학부 안내: guide, 필수 교양 과목: general-studies-requirements, - // 전공 이수 표준 형태: curriculum, 졸업 규청: degree-requirements, - // 교과목 변경 내역: course-changes, 장학제도: scholarship //Todo: 이미지, 파일 추가 필요 - @PostMapping("/{studentType}") + @PostMapping("/{studentType}/{postType}") fun createAcademics( - @PathVariable studentType: StudentType, + @PathVariable studentType: String, + @PathVariable postType: String, @Valid @RequestBody request: AcademicsDto ) : ResponseEntity { - return ResponseEntity.ok(academicsService.createAcademics(studentType, request)) + return ResponseEntity.ok(academicsService.createAcademics(studentType, postType, request)) } @GetMapping("/{studentType}/{postType}") fun readAcademics( - @PathVariable studentType: StudentType, + @PathVariable studentType: String, @PathVariable postType: String, ): ResponseEntity { return ResponseEntity.ok(academicsService.readAcademics(studentType, postType)) } - //교과목 정보: courses + //교과목 정보 @PostMapping("/{studentType}/course") fun createCourse( - @PathVariable studentType: StudentType, + @PathVariable studentType: String, @Valid @RequestBody request: CourseDto ) : ResponseEntity { return ResponseEntity.ok(academicsService.createCourse(studentType, request)) @@ -50,14 +48,14 @@ class AcademicsController( @GetMapping("/{studentType}/courses") fun readAllCourses( - @PathVariable studentType: StudentType, + @PathVariable studentType: String, ) : ResponseEntity> { return ResponseEntity.ok(academicsService.readAllCourses(studentType)) } - @GetMapping("/course/{name}") + @GetMapping("/course") fun readCourse( - @PathVariable name: String + @RequestParam name: String ): ResponseEntity { return ResponseEntity.ok(academicsService.readCourse(name)) } @@ -65,15 +63,15 @@ class AcademicsController( // 장학금 @PostMapping("/{studentType}/scholarship") fun createScholarship( - @PathVariable studentType: StudentType, + @PathVariable studentType: String, @Valid @RequestBody request: AcademicsDto ) : ResponseEntity { - return ResponseEntity.ok(academicsService.createAcademics(studentType, request)) + return ResponseEntity.ok(academicsService.createAcademics(studentType, "scholarship", request)) } - @GetMapping("/scholarship/{name}") + @GetMapping("/scholarship") fun readScholarship( - @PathVariable name: String + @RequestParam name: String ) : ResponseEntity { return ResponseEntity.ok(academicsService.readScholarship(name)) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsEntity.kt index 2f70cb24..18bc1a08 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsEntity.kt @@ -9,24 +9,22 @@ import jakarta.persistence.Enumerated @Entity(name = "academics") class AcademicsEntity( @Enumerated(EnumType.STRING) - var studentType: StudentType, + var studentType: AcademicsStudentType, - var postType: String, + @Enumerated(EnumType.STRING) + var postType: AcademicsPostType, var name: String, - var description: String, - var year: Int?, - var isPublic: Boolean, ): BaseTimeEntity() { companion object { - fun of(studentType: StudentType, academicsDto: AcademicsDto): AcademicsEntity { + fun of(studentType: AcademicsStudentType, postType: AcademicsPostType, academicsDto: AcademicsDto): AcademicsEntity { return AcademicsEntity( studentType = studentType, - postType = academicsDto.postType, + postType = postType, name = academicsDto.name, description = academicsDto.description, year = academicsDto.year, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsPostType.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsPostType.kt new file mode 100644 index 00000000..02a4a8ad --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsPostType.kt @@ -0,0 +1,5 @@ +package com.wafflestudio.csereal.core.academics.database + +enum class AcademicsPostType { + GUIDE, GENERAL_STUDIES_REQUIREMENTS, CURRICULUM, DEGREE_REQUIREMENTS, COURSE_CHANGES, SCHOLARSHIP +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsRepository.kt index fcd58f5c..e1f251c3 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsRepository.kt @@ -3,6 +3,6 @@ package com.wafflestudio.csereal.core.academics.database import org.springframework.data.jpa.repository.JpaRepository interface AcademicsRepository : JpaRepository { - fun findByStudentTypeAndPostType(studentType: StudentType, postType: String) : AcademicsEntity + fun findByStudentTypeAndPostType(studentType: AcademicsStudentType, postType: AcademicsPostType) : AcademicsEntity fun findByName(name: String): AcademicsEntity } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsStudentType.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsStudentType.kt new file mode 100644 index 00000000..c153b2a6 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsStudentType.kt @@ -0,0 +1,5 @@ +package com.wafflestudio.csereal.core.academics.database + +enum class AcademicsStudentType { + UNDERGRADUATE, GRADUATE +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseEntity.kt index 74f4b0bb..5d410cad 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseEntity.kt @@ -8,7 +8,7 @@ import jakarta.persistence.Entity class CourseEntity( var isDeleted: Boolean = false, - var studentType: StudentType, + var studentType: AcademicsStudentType, var classification: String, @@ -25,7 +25,7 @@ class CourseEntity( var description: String? ): BaseTimeEntity() { companion object { - fun of(studentType: StudentType, courseDto: CourseDto): CourseEntity { + fun of(studentType: AcademicsStudentType, courseDto: CourseDto): CourseEntity { return CourseEntity( studentType = studentType, classification = courseDto.classification, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseRepository.kt index 777c3961..f8e017cc 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseRepository.kt @@ -3,6 +3,6 @@ package com.wafflestudio.csereal.core.academics.database import org.springframework.data.jpa.repository.JpaRepository interface CourseRepository : JpaRepository { - fun findAllByStudentTypeOrderByYearAsc(studentType: StudentType) : List + fun findAllByStudentTypeOrderByYearAsc(studentType: AcademicsStudentType) : List fun findByName(name: String) : CourseEntity } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/StudentType.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/StudentType.kt deleted file mode 100644 index ebc750af..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/StudentType.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.wafflestudio.csereal.core.academics.database - -enum class StudentType { - GRADUATE,UNDERGRADUATE -} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsDto.kt index f62b2f4f..fadb6c5b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsDto.kt @@ -5,7 +5,6 @@ import java.time.LocalDateTime data class AcademicsDto( val id: Long, - val postType: String, val name: String, val description: String, val year: Int?, @@ -17,7 +16,6 @@ data class AcademicsDto( fun of(entity: AcademicsEntity) : AcademicsDto = entity.run { AcademicsDto( id = this.id, - postType = this.postType, name = this.name, description = this.description, year = this.year, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt index 8e9e7105..4a2b7609 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt @@ -1,5 +1,7 @@ package com.wafflestudio.csereal.core.academics.service +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.about.database.AboutPostType import com.wafflestudio.csereal.core.academics.database.* import com.wafflestudio.csereal.core.academics.dto.CourseDto import com.wafflestudio.csereal.core.academics.dto.AcademicsDto @@ -7,10 +9,10 @@ import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional interface AcademicsService { - fun createAcademics(studentType: StudentType, request: AcademicsDto): AcademicsDto - fun readAcademics(studentType: StudentType, postType: String): AcademicsDto - fun readAllCourses(studentType: StudentType): List - fun createCourse(studentType: StudentType, request: CourseDto): CourseDto + fun createAcademics(studentType: String, postType: String, request: AcademicsDto): AcademicsDto + fun readAcademics(studentType: String, postType: String): AcademicsDto + fun createCourse(studentType: String, request: CourseDto): CourseDto + fun readAllCourses(studentType: String): List fun readCourse(name: String): CourseDto fun readScholarship(name:String): AcademicsDto } @@ -21,8 +23,11 @@ class AcademicsServiceImpl( private val courseRepository: CourseRepository, ) : AcademicsService { @Transactional - override fun createAcademics(studentType: StudentType, request: AcademicsDto): AcademicsDto { - val newAcademics = AcademicsEntity.of(studentType, request) + override fun createAcademics(studentType: String, postType: String, request: AcademicsDto): AcademicsDto { + val enumStudentType = makeStringToAcademicsStudentType(studentType) + val enumPostType = makeStringToAcademicsPostType(postType) + + val newAcademics = AcademicsEntity.of(enumStudentType, enumPostType, request) academicsRepository.save(newAcademics) @@ -30,41 +35,67 @@ class AcademicsServiceImpl( } @Transactional(readOnly = true) - override fun readAcademics(studentType: StudentType, postType: String): AcademicsDto { - val academics : AcademicsEntity = academicsRepository.findByStudentTypeAndPostType(studentType, postType) + override fun readAcademics(studentType: String, postType: String): AcademicsDto { + + val enumStudentType = makeStringToAcademicsStudentType(studentType) + val enumPostType = makeStringToAcademicsPostType(postType) + + val academics = academicsRepository.findByStudentTypeAndPostType(enumStudentType, enumPostType) return AcademicsDto.of(academics) } @Transactional - override fun readAllCourses(studentType: StudentType): List { - val courseDtoList = courseRepository.findAllByStudentTypeOrderByYearAsc(studentType).map { - CourseDto.of(it) - } - return courseDtoList - } - @Transactional - override fun createCourse(studentType: StudentType, request: CourseDto): CourseDto { - val course = CourseEntity.of(studentType, request) + override fun createCourse(studentType: String, request: CourseDto): CourseDto { + val enumStudentType = makeStringToAcademicsStudentType(studentType) + val course = CourseEntity.of(enumStudentType, request) courseRepository.save(course) return CourseDto.of(course) } - @Transactional + @Transactional(readOnly = true) + override fun readAllCourses(studentType: String): List { + val enumStudentType = makeStringToAcademicsStudentType(studentType) + + val courseDtoList = courseRepository.findAllByStudentTypeOrderByYearAsc(enumStudentType).map { + CourseDto.of(it) + } + return courseDtoList + } + + @Transactional(readOnly = true) override fun readCourse(name: String): CourseDto { - val course : CourseEntity = courseRepository.findByName(name) + val course = courseRepository.findByName(name) return CourseDto.of(course) } - @Transactional + @Transactional(readOnly = true) override fun readScholarship(name: String): AcademicsDto { - val scholarship : AcademicsEntity = academicsRepository.findByName(name) + val scholarship = academicsRepository.findByName(name) return AcademicsDto.of(scholarship) } + private fun makeStringToAcademicsStudentType(postType: String) : AcademicsStudentType { + try { + val upperPostType = postType.replace("-","_").uppercase() + return AcademicsStudentType.valueOf(upperPostType) + } catch (e: IllegalArgumentException) { + throw CserealException.Csereal400("해당하는 enum을 찾을 수 없습니다") + } + } + + private fun makeStringToAcademicsPostType(postType: String) : AcademicsPostType { + try { + val upperPostType = postType.replace("-","_").uppercase() + return AcademicsPostType.valueOf(upperPostType) + + } catch (e: IllegalArgumentException) { + throw CserealException.Csereal400("해당하는 enum을 찾을 수 없습니다") + } + } } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/AdmissionsController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/AdmissionsController.kt index 0eee0bcc..de4f581e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/AdmissionsController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/AdmissionsController.kt @@ -1,7 +1,5 @@ package com.wafflestudio.csereal.core.admissions.api -import com.wafflestudio.csereal.core.admissions.database.AdmissionPostType -import com.wafflestudio.csereal.core.admissions.database.StudentType import com.wafflestudio.csereal.core.admissions.dto.AdmissionsDto import com.wafflestudio.csereal.core.admissions.service.AdmissionsService import jakarta.validation.Valid @@ -19,26 +17,34 @@ import org.springframework.web.bind.annotation.RestController class AdmissionsController( private val admissionsService: AdmissionsService ) { - @PostMapping - fun createAdmissions( - @RequestParam studentType: StudentType, + @PostMapping("/undergraduate") + fun createUndergraduateAdmissions( + @RequestParam postType: String, @Valid @RequestBody request: AdmissionsDto ) : AdmissionsDto { - return admissionsService.createAdmissions(studentType, request) + return admissionsService.createUndergraduateAdmissions(postType, request) } - @GetMapping - fun readAdmissionsMain( - @RequestParam studentType: StudentType, - ) : ResponseEntity { - return ResponseEntity.ok(admissionsService.readAdmissionsMain(studentType)) + @PostMapping("/graduate") + fun createGraduateAdmissions( + @Valid @RequestBody request: AdmissionsDto + ) : AdmissionsDto { + return admissionsService.createGraduateAdmissions(request) } + @GetMapping("/undergraduate") fun readUndergraduateAdmissions( - @RequestParam postType: AdmissionPostType + @RequestParam postType: String ) : ResponseEntity { return ResponseEntity.ok(admissionsService.readUndergraduateAdmissions(postType)) } + @GetMapping("/graduate") + fun readGraduateAdmissions() : ResponseEntity { + return ResponseEntity.ok(admissionsService.readGraduateAdmissions()) + } + + + } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionPostType.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionPostType.kt deleted file mode 100644 index 493f01c0..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionPostType.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.wafflestudio.csereal.core.admissions.database - -enum class AdmissionPostType { - MAIN, EARLY_ADMISSION, REGULAR_ADMISSION, -} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsEntity.kt index b6fd07d5..e12e9716 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsEntity.kt @@ -9,21 +9,16 @@ import jakarta.persistence.Enumerated @Entity(name = "admissions") class AdmissionsEntity( @Enumerated(EnumType.STRING) - var studentType: StudentType, - @Enumerated(EnumType.STRING) - val postType: AdmissionPostType, + val postType: AdmissionsPostType, val title: String, val description: String, - val isPublic: Boolean, ): BaseTimeEntity() { companion object { - fun of(studentType: StudentType, admissionsDto: AdmissionsDto) : AdmissionsEntity { + fun of(postType: AdmissionsPostType, admissionsDto: AdmissionsDto) : AdmissionsEntity { return AdmissionsEntity( - studentType = studentType, - postType = admissionsDto.postType, + postType = postType, title = admissionsDto.title, description = admissionsDto.description, - isPublic = admissionsDto.isPublic ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsPostType.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsPostType.kt new file mode 100644 index 00000000..104cf01b --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsPostType.kt @@ -0,0 +1,5 @@ +package com.wafflestudio.csereal.core.admissions.database + +enum class AdmissionsPostType { + GRADUATE, UNDERGRADUATE_EARLY_ADMISSION, UNDERGRADUATE_REGULAR_ADMISSION, +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsRepository.kt index 74c3f1e8..f4e2c1b0 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsRepository.kt @@ -3,6 +3,5 @@ package com.wafflestudio.csereal.core.admissions.database import org.springframework.data.jpa.repository.JpaRepository interface AdmissionsRepository : JpaRepository { - fun findByStudentTypeAndPostType(studentType: StudentType, postType: AdmissionPostType): AdmissionsEntity - fun findByPostType(postType: AdmissionPostType) : AdmissionsEntity + fun findByPostType(postType: AdmissionsPostType) : AdmissionsEntity } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/StudentType.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/StudentType.kt deleted file mode 100644 index b549da95..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/StudentType.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.wafflestudio.csereal.core.admissions.database - -enum class StudentType { - GRADUATE, UNDERGRADUATE -} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/dto/AdmissionsDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/dto/AdmissionsDto.kt index cce8196b..c49c7e7a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/dto/AdmissionsDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/dto/AdmissionsDto.kt @@ -1,28 +1,23 @@ package com.wafflestudio.csereal.core.admissions.dto -import com.wafflestudio.csereal.core.admissions.database.AdmissionPostType import com.wafflestudio.csereal.core.admissions.database.AdmissionsEntity import java.time.LocalDateTime data class AdmissionsDto( val id: Long, - val postType: AdmissionPostType, val title: String, val description: String, val createdAt: LocalDateTime?, val modifiedAt: LocalDateTime?, - val isPublic: Boolean, ) { companion object { fun of(entity: AdmissionsEntity) : AdmissionsDto = entity.run { AdmissionsDto( id = this.id, - postType = this.postType, title = this.title, description = this.description, createdAt = this.createdAt, modifiedAt = this.modifiedAt, - isPublic = this.isPublic ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/service/AdmissionsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/service/AdmissionsService.kt index d8289811..e79a67cf 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/service/AdmissionsService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/service/AdmissionsService.kt @@ -1,17 +1,18 @@ package com.wafflestudio.csereal.core.admissions.service -import com.wafflestudio.csereal.core.admissions.database.AdmissionPostType +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.admissions.database.AdmissionsPostType import com.wafflestudio.csereal.core.admissions.database.AdmissionsEntity import com.wafflestudio.csereal.core.admissions.database.AdmissionsRepository -import com.wafflestudio.csereal.core.admissions.database.StudentType import com.wafflestudio.csereal.core.admissions.dto.AdmissionsDto import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional interface AdmissionsService { - fun createAdmissions(studentType: StudentType, request: AdmissionsDto): AdmissionsDto - fun readAdmissionsMain(studentType: StudentType): AdmissionsDto - fun readUndergraduateAdmissions(postType: AdmissionPostType): AdmissionsDto + fun createUndergraduateAdmissions(postType: String, request: AdmissionsDto): AdmissionsDto + fun createGraduateAdmissions(request: AdmissionsDto): AdmissionsDto + fun readUndergraduateAdmissions(postType: String): AdmissionsDto + fun readGraduateAdmissions(): AdmissionsDto } @@ -20,8 +21,19 @@ class AdmissionsServiceImpl( private val admissionsRepository: AdmissionsRepository ) : AdmissionsService { @Transactional - override fun createAdmissions(studentType: StudentType, request: AdmissionsDto): AdmissionsDto { - val newAdmissions: AdmissionsEntity = AdmissionsEntity.of(studentType, request) + override fun createUndergraduateAdmissions(postType: String, request: AdmissionsDto): AdmissionsDto { + val enumPostType = makeStringToAdmissionsPostType(postType) + + val newAdmissions = AdmissionsEntity.of(enumPostType, request) + + admissionsRepository.save(newAdmissions) + + return AdmissionsDto.of(newAdmissions) + } + + @Transactional + override fun createGraduateAdmissions(request: AdmissionsDto): AdmissionsDto { + val newAdmissions: AdmissionsEntity = AdmissionsEntity.of(AdmissionsPostType.GRADUATE, request) admissionsRepository.save(newAdmissions) @@ -29,22 +41,26 @@ class AdmissionsServiceImpl( } @Transactional(readOnly = true) - override fun readAdmissionsMain(studentType: StudentType): AdmissionsDto { - return if (studentType == StudentType.UNDERGRADUATE) { - AdmissionsDto.of(admissionsRepository.findByStudentTypeAndPostType(StudentType.UNDERGRADUATE, AdmissionPostType.MAIN)) - } else { - AdmissionsDto.of(admissionsRepository.findByStudentTypeAndPostType(StudentType.GRADUATE, AdmissionPostType.MAIN)) + override fun readUndergraduateAdmissions(postType: String): AdmissionsDto { + return when (postType) { + "early" -> AdmissionsDto.of(admissionsRepository.findByPostType(AdmissionsPostType.UNDERGRADUATE_EARLY_ADMISSION)) + "regular" -> AdmissionsDto.of(admissionsRepository.findByPostType(AdmissionsPostType.UNDERGRADUATE_REGULAR_ADMISSION)) + else -> throw CserealException.Csereal404("해당하는 페이지를 찾을 수 없습니다.") } } @Transactional(readOnly = true) - override fun readUndergraduateAdmissions(postType: AdmissionPostType): AdmissionsDto { - return if (postType == AdmissionPostType.EARLY_ADMISSION) { - AdmissionsDto.of(admissionsRepository.findByPostType(AdmissionPostType.EARLY_ADMISSION)) - } else { - AdmissionsDto.of(admissionsRepository.findByPostType(AdmissionPostType.REGULAR_ADMISSION)) - } + override fun readGraduateAdmissions(): AdmissionsDto { + return AdmissionsDto.of(admissionsRepository.findByPostType(AdmissionsPostType.GRADUATE)) } + private fun makeStringToAdmissionsPostType(postType: String) : AdmissionsPostType { + try { + val upperPostType = postType.replace("-","_").uppercase() + return AdmissionsPostType.valueOf(upperPostType) + } catch (e: IllegalArgumentException) { + throw CserealException.Csereal400("해당하는 enum을 찾을 수 없습니다") + } + } } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/api/MainController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/api/MainController.kt new file mode 100644 index 00000000..7aa259d1 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/api/MainController.kt @@ -0,0 +1,18 @@ +package com.wafflestudio.csereal.core.main.api + +import com.wafflestudio.csereal.core.main.dto.MainResponse +import com.wafflestudio.csereal.core.main.service.MainService +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RequestMapping +@RestController +class MainController( + private val mainService: MainService, +) { + @GetMapping + fun readMain() : MainResponse { + return mainService.readMain() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt new file mode 100644 index 00000000..f7fcf906 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt @@ -0,0 +1,70 @@ +package com.wafflestudio.csereal.core.main.database + +import com.querydsl.core.QueryFactory +import com.querydsl.core.types.Projections +import com.querydsl.jpa.impl.JPAQueryFactory +import com.sun.tools.javac.Main +import com.wafflestudio.csereal.core.main.dto.MainResponse +import com.wafflestudio.csereal.core.main.dto.NewsResponse +import com.wafflestudio.csereal.core.main.dto.NoticeResponse +import com.wafflestudio.csereal.core.news.database.QNewsEntity.newsEntity +import com.wafflestudio.csereal.core.notice.database.QNoticeEntity.noticeEntity +import com.wafflestudio.csereal.core.notice.database.QNoticeTagEntity.noticeTagEntity +import com.wafflestudio.csereal.core.notice.database.QTagInNoticeEntity.tagInNoticeEntity + +import org.springframework.stereotype.Component + +interface MainRepository { + fun readMainSlide(): List + fun readMainNoticeTotal(): List + fun readMainNoticeTag(tag: String): List +} + +@Component +class MainRepositoryImpl( + private val queryFactory: JPAQueryFactory, +) : MainRepository { + override fun readMainSlide(): List { + return queryFactory.select( + Projections.constructor( + NewsResponse::class.java, + newsEntity.id, + newsEntity.title, + newsEntity.createdAt + ) + ).from(newsEntity) + .where(newsEntity.isDeleted.eq(false), newsEntity.isPublic.eq(true), newsEntity.isSlide.eq(true)) + .orderBy(newsEntity.isPinned.desc()).orderBy(newsEntity.createdAt.desc()) + .limit(20).fetch() + } + + override fun readMainNoticeTotal(): List { + return queryFactory.select( + Projections.constructor( + NoticeResponse::class.java, + noticeEntity.id, + noticeEntity.title, + noticeEntity.createdAt + ) + ).from(noticeEntity) + .where(noticeEntity.isDeleted.eq(false), noticeEntity.isPublic.eq(true)) + .orderBy(noticeEntity.isPinned.desc()).orderBy(noticeEntity.createdAt.desc()) + .limit(6).fetch() + } + override fun readMainNoticeTag(tag: String): List { + return queryFactory.select( + Projections.constructor( + NoticeResponse::class.java, + noticeTagEntity.notice.id, + noticeTagEntity.notice.title, + noticeTagEntity.notice.createdAt, + ) + ).from(noticeTagEntity) + .rightJoin(noticeEntity).on(noticeTagEntity.notice.eq(noticeEntity)) + .rightJoin(tagInNoticeEntity).on(noticeTagEntity.tag.eq(tagInNoticeEntity)) + .where(noticeTagEntity.tag.name.eq(tag)) + .where(noticeEntity.isDeleted.eq(false), noticeEntity.isPublic.eq(true)) + .orderBy(noticeEntity.isPinned.desc()).orderBy(noticeEntity.createdAt.desc()) + .limit(6).distinct().fetch() + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainResponse.kt new file mode 100644 index 00000000..dc7a98ce --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainResponse.kt @@ -0,0 +1,11 @@ +package com.wafflestudio.csereal.core.main.dto + +data class MainResponse( + val slide: List, + val noticeTotal: List, + val noticeAdmissions: List, + val noticeUndergraduate: List, + val noticeGraduate: List +) { + +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/NewsResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/NewsResponse.kt new file mode 100644 index 00000000..688c9627 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/NewsResponse.kt @@ -0,0 +1,12 @@ +package com.wafflestudio.csereal.core.main.dto + +import com.querydsl.core.annotations.QueryProjection +import java.time.LocalDateTime + +data class NewsResponse @QueryProjection constructor( + val id: Long, + val title: String, + val createdAt: LocalDateTime? +) { + +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/NoticeResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/NoticeResponse.kt new file mode 100644 index 00000000..ed64b596 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/NoticeResponse.kt @@ -0,0 +1,11 @@ +package com.wafflestudio.csereal.core.main.dto + +import com.querydsl.core.annotations.QueryProjection +import java.time.LocalDateTime + +data class NoticeResponse @QueryProjection constructor( + val id: Long, + val title: String, + val createdAt: LocalDateTime?, +){ +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/service/MainService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/service/MainService.kt new file mode 100644 index 00000000..67a05b9b --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/service/MainService.kt @@ -0,0 +1,25 @@ +package com.wafflestudio.csereal.core.main.service + +import com.wafflestudio.csereal.core.main.database.MainRepository +import com.wafflestudio.csereal.core.main.dto.MainResponse +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +interface MainService { + fun readMain() : MainResponse +} + +@Service +class MainServiceImpl( + private val mainRepository: MainRepository +) : MainService { + @Transactional(readOnly = true) + override fun readMain(): MainResponse { + val slide = mainRepository.readMainSlide() + val noticeTotal = mainRepository.readMainNoticeTotal() + val noticeAdmissions = mainRepository.readMainNoticeTag("admissions") + val noticeUndergraduate = mainRepository.readMainNoticeTag("undergraduate") + val noticeGraduate = mainRepository.readMainNoticeTag("graduate") + return MainResponse(slide, noticeTotal, noticeAdmissions, noticeUndergraduate, noticeGraduate) + } +} \ No newline at end of file From cc256bff5736b43f150281c11ae9d8991a46827a Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Fri, 25 Aug 2023 00:45:15 +0900 Subject: [PATCH 024/124] =?UTF-8?q?feat:=20=EC=9D=BC=EB=B0=98=20=EC=98=88?= =?UTF-8?q?=EC=95=BD=20=EB=B0=8F=20=EC=A0=95=EA=B8=B0=20=EC=98=88=EC=95=BD?= =?UTF-8?q?=20API=20(#28)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: oidc 로그인 * feat: TaskEntity name 추가 * feat: 배포 설정 * feat: 유저 정보에 학번 추가, 로그인 시 sub claim 확인 * feat: 배포 테스트 위해 redirect-uri 변경 * fix: groups claim 소문자로 수정 * feat: 예약 엔티티 및 DTO 설계 * feat: 일반 예약 및 정기 예약 --- .../wafflestudio/csereal/common/Exceptions.kt | 1 + .../reservation/api/ReservceController.kt | 53 ++++++++++++ .../reservation/database/ReservationEntity.kt | 69 ++++++++++++++++ .../database/ReservationRepository.kt | 15 ++++ .../core/reservation/database/RoomEntity.kt | 21 +++++ .../reservation/database/RoomRepository.kt | 6 ++ .../core/reservation/dto/ReservationDto.kt | 34 ++++++++ .../core/reservation/dto/ReserveRequest.kt | 14 ++++ .../reservation/service/ReservationService.kt | 81 +++++++++++++++++++ 9 files changed, 294 insertions(+) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservceController.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/ReservationEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/ReservationRepository.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/RoomEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/RoomRepository.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReservationDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReserveRequest.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/reservation/service/ReservationService.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/Exceptions.kt b/src/main/kotlin/com/wafflestudio/csereal/common/Exceptions.kt index 2809d4f6..ead4693b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/Exceptions.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/Exceptions.kt @@ -6,4 +6,5 @@ open class CserealException(msg: String, val status: HttpStatus) : RuntimeExcept class Csereal400(msg: String) : CserealException(msg, HttpStatus.BAD_REQUEST) class Csereal404(msg: String) : CserealException(msg, HttpStatus.NOT_FOUND) class Csereal401(msg: String) : CserealException(msg, HttpStatus.UNAUTHORIZED) + class Csereal409(msg: String) : CserealException(msg, HttpStatus.CONFLICT) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservceController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservceController.kt new file mode 100644 index 00000000..82e9962a --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservceController.kt @@ -0,0 +1,53 @@ +package com.wafflestudio.csereal.core.reservation.api + +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.reservation.dto.ReservationDto +import com.wafflestudio.csereal.core.reservation.dto.ReserveRequest +import com.wafflestudio.csereal.core.reservation.service.ReservationService +import org.springframework.security.core.annotation.AuthenticationPrincipal +import org.springframework.security.oauth2.core.oidc.user.OidcUser +import org.springframework.web.bind.annotation.DeleteMapping +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController +import java.time.LocalDateTime +import java.util.UUID + +@RequestMapping("/reservation") +@RestController +class ReservationController( + private val reservationService: ReservationService +) { + + @PostMapping + fun reserveRoom( + @AuthenticationPrincipal principal: OidcUser?, + @RequestBody reserveRequest: ReserveRequest + ): List { + if (principal == null) { + throw CserealException.Csereal401("로그인이 필요합니다.") + } + val username = principal.idToken.getClaim("username") + return reservationService.reserveRoom(username, reserveRequest) + } + + @DeleteMapping("/{reservationId}") + fun cancelSpecific(@AuthenticationPrincipal principal: OidcUser?, @PathVariable reservationId: Long) { + if (principal == null) { + throw CserealException.Csereal401("로그인이 필요합니다.") + } + reservationService.cancelSpecific(reservationId) + } + + @DeleteMapping("/recurring/{recurrenceId}") + fun cancelRecurring(@AuthenticationPrincipal principal: OidcUser?, @PathVariable recurrenceId: UUID) { + if (principal == null) { + throw CserealException.Csereal401("로그인이 필요합니다.") + } + reservationService.cancelRecurring(recurrenceId) + } + +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/ReservationEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/ReservationEntity.kt new file mode 100644 index 00000000..8412f229 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/ReservationEntity.kt @@ -0,0 +1,69 @@ +package com.wafflestudio.csereal.core.reservation.database + +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.core.reservation.dto.ReserveRequest +import com.wafflestudio.csereal.core.user.database.UserEntity +import jakarta.persistence.Entity +import jakarta.persistence.FetchType +import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne +import jakarta.persistence.PrePersist +import jakarta.persistence.PreUpdate +import java.time.LocalDateTime +import java.util.* + +@Entity(name = "reservation") +class ReservationEntity( + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + val user: UserEntity, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "room_id") + val room: RoomEntity, + + val title: String, + val contactEmail: String, + val contactPhone: String, + val purpose: String, + val startTime: LocalDateTime, + val endTime: LocalDateTime, + + val recurrenceId: UUID? = null + +) : BaseTimeEntity() { + + @PrePersist + @PreUpdate + fun validateDates() { + if (startTime.isAfter(endTime) || startTime.isEqual(endTime)) { + throw CserealException.Csereal400("종료 시각은 시작 시각 이후여야 합니다.") + } + } + + companion object { + fun create( + user: UserEntity, + room: RoomEntity, + reserveRequest: ReserveRequest, + start: LocalDateTime, + end: LocalDateTime, + recurrenceId: UUID + ): ReservationEntity { + return ReservationEntity( + user = user, + room = room, + title = reserveRequest.title, + contactEmail = reserveRequest.contactEmail, + contactPhone = reserveRequest.contactPhone, + purpose = reserveRequest.purpose, + startTime = start, + endTime = end, + recurrenceId = recurrenceId + ) + } + } + +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/ReservationRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/ReservationRepository.kt new file mode 100644 index 00000000..827db80d --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/ReservationRepository.kt @@ -0,0 +1,15 @@ +package com.wafflestudio.csereal.core.reservation.database + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query +import java.time.LocalDateTime +import java.util.UUID + +interface ReservationRepository : JpaRepository { + + @Query("SELECT r FROM reservation r WHERE r.room.id = :roomId AND ((:start <= r.startTime AND r.startTime < :end) OR (:start < r.endTime AND r.endTime <= :end) OR (r.startTime <= :start AND r.endTime >= :end))") + fun findByRoomIdAndTimeOverlap(roomId: Long, start: LocalDateTime, end: LocalDateTime): List + + fun deleteAllByRecurrenceId(recurrenceId: UUID) + +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/RoomEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/RoomEntity.kt new file mode 100644 index 00000000..f76d1c48 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/RoomEntity.kt @@ -0,0 +1,21 @@ +package com.wafflestudio.csereal.core.reservation.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated + +@Entity(name = "room") +class RoomEntity( + val name: String?, + val location: String, + + val capacity: Int, + + @Enumerated(EnumType.STRING) + val type: RoomType +) : BaseTimeEntity() + +enum class RoomType { + SEMINAR, LAB, LECTURE +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/RoomRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/RoomRepository.kt new file mode 100644 index 00000000..c7c9b562 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/RoomRepository.kt @@ -0,0 +1,6 @@ +package com.wafflestudio.csereal.core.reservation.database + +import org.springframework.data.jpa.repository.JpaRepository + +interface RoomRepository : JpaRepository { +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReservationDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReservationDto.kt new file mode 100644 index 00000000..b868d4b2 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReservationDto.kt @@ -0,0 +1,34 @@ +package com.wafflestudio.csereal.core.reservation.dto + +import com.wafflestudio.csereal.core.reservation.database.ReservationEntity +import java.time.LocalDateTime + +data class ReservationDto( + val id: Long, + val title: String, + val purpose: String, + val startTime: LocalDateTime, + val endTime: LocalDateTime, + val roomName: String?, + val roomLocation: String, + val userName: String, + val contactEmail: String, + val contactPhone: String +) { + companion object { + fun of(reservationEntity: ReservationEntity): ReservationDto { + return ReservationDto( + id = reservationEntity.id, + title = reservationEntity.title, + purpose = reservationEntity.purpose, + startTime = reservationEntity.startTime, + endTime = reservationEntity.endTime, + roomName = reservationEntity.room.name, + roomLocation = reservationEntity.room.location, + userName = reservationEntity.user.username, + contactEmail = reservationEntity.contactEmail, + contactPhone = reservationEntity.contactPhone + ) + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReserveRequest.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReserveRequest.kt new file mode 100644 index 00000000..55befd76 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReserveRequest.kt @@ -0,0 +1,14 @@ +package com.wafflestudio.csereal.core.reservation.dto + +import java.time.LocalDateTime + +data class ReserveRequest( + val roomId: Long, + val title: String, + val contactEmail: String, + val contactPhone: String, + val purpose: String, + val startTime: LocalDateTime, + val endTime: LocalDateTime, + val recurringWeeks: Int? = null +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/service/ReservationService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/service/ReservationService.kt new file mode 100644 index 00000000..8adba80c --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/service/ReservationService.kt @@ -0,0 +1,81 @@ +package com.wafflestudio.csereal.core.reservation.service + +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.reservation.database.* +import com.wafflestudio.csereal.core.reservation.dto.ReservationDto +import com.wafflestudio.csereal.core.reservation.dto.ReserveRequest +import com.wafflestudio.csereal.core.user.database.Role +import com.wafflestudio.csereal.core.user.database.UserRepository +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.util.* + +interface ReservationService { + fun reserveRoom( + username: String, + reserveRequest: ReserveRequest + ): List + + fun cancelSpecific(reservationId: Long) + fun cancelRecurring(recurrenceId: UUID) +} + +@Service +@Transactional +class ReservationServiceImpl( + private val reservationRepository: ReservationRepository, + private val userRepository: UserRepository, + private val roomRepository: RoomRepository +) : ReservationService { + + override fun reserveRoom( + username: String, + reserveRequest: ReserveRequest + ): List { + val user = userRepository.findByUsername(username) ?: throw CserealException.Csereal404("재로그인이 필요합니다.") + val room = + roomRepository.findByIdOrNull(reserveRequest.roomId) ?: throw CserealException.Csereal404("Room Not Found") + + if (user.role == null) { + throw CserealException.Csereal401("권한이 없습니다.") + } + + val reservations = mutableListOf() + + val recurrenceId = UUID.randomUUID() + + val numberOfWeeks = reserveRequest.recurringWeeks ?: 1 + + for (week in 0 until numberOfWeeks) { + val start = reserveRequest.startTime.plusWeeks(week.toLong()) + val end = reserveRequest.endTime.plusWeeks(week.toLong()) + + // 중복 예약 방지 + val overlappingReservations = reservationRepository.findByRoomIdAndTimeOverlap( + reserveRequest.roomId, + start, + end + ) + if (overlappingReservations.isNotEmpty()) { + throw CserealException.Csereal409("${week}주차 해당 시간에 이미 예약이 있습니다.") + } + + val newReservation = ReservationEntity.create(user, room, reserveRequest, start, end, recurrenceId) + reservations.add(newReservation) + } + + reservationRepository.saveAll(reservations) + + return reservations.map { ReservationDto.of(it) } + } + + override fun cancelSpecific(reservationId: Long) { + reservationRepository.deleteById(reservationId) + } + + override fun cancelRecurring(recurrenceId: UUID) { + reservationRepository.deleteAllByRecurrenceId(recurrenceId) + } + +} From 13161c9c580474376abb1259d7d2c0b2a908b0ca Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Tue, 29 Aug 2023 03:00:43 +0900 Subject: [PATCH 025/124] =?UTF-8?q?feat:=20=EC=98=88=EC=95=BD=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20API=20(#39)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 권한 관리 위해 커스텀 어노테이션 생성 * feat: 예약 단건, 주별, 월별 조회 API * fix: 로컬 DB 설정 수정 * feat: 유저 조회 중복 쿼리 방지 * feat: 예약 응답에 recurrenceId 추가 * feat: 동시 예약 방지 * fix: 시큐리티 로그 다시 로컬에서만 보이도록 변경 * feat: 목데이터 날라가지 않도록 ddl-auto 수정 --- docker-compose-local.yml | 1 - .../csereal/common/aop/Authenticated.kt | 9 +++ .../csereal/common/aop/SecurityAspect.kt | 53 +++++++++++++++ .../reservation/api/ReservceController.kt | 66 +++++++++++++------ .../database/ReservationRepository.kt | 9 +++ .../core/reservation/dto/ReservationDto.kt | 3 + .../reservation/service/ReservationService.kt | 54 ++++++++++----- src/main/resources/application.yaml | 14 ++-- 8 files changed, 166 insertions(+), 43 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/common/aop/Authenticated.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/common/aop/SecurityAspect.kt diff --git a/docker-compose-local.yml b/docker-compose-local.yml index ad08265a..39e075e9 100644 --- a/docker-compose-local.yml +++ b/docker-compose-local.yml @@ -11,7 +11,6 @@ services: - '3306:3306' volumes: - db:/var/lib/mysql - - $PWD/db/init.sql:/docker-entrypoint-initdb.d/init.sql volumes: db: driver: local diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/aop/Authenticated.kt b/src/main/kotlin/com/wafflestudio/csereal/common/aop/Authenticated.kt new file mode 100644 index 00000000..fc1dc109 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/common/aop/Authenticated.kt @@ -0,0 +1,9 @@ +package com.wafflestudio.csereal.common.aop + +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +annotation class AuthenticatedStaff + +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +annotation class AuthenticatedForReservation diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/aop/SecurityAspect.kt b/src/main/kotlin/com/wafflestudio/csereal/common/aop/SecurityAspect.kt new file mode 100644 index 00000000..99dfe6e2 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/common/aop/SecurityAspect.kt @@ -0,0 +1,53 @@ +package com.wafflestudio.csereal.common.aop + +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.user.database.Role +import com.wafflestudio.csereal.core.user.database.UserEntity +import com.wafflestudio.csereal.core.user.database.UserRepository +import org.aspectj.lang.annotation.Aspect +import org.aspectj.lang.annotation.Before +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.security.oauth2.core.oidc.user.OidcUser +import org.springframework.stereotype.Component +import org.springframework.web.context.request.RequestAttributes +import org.springframework.web.context.request.RequestContextHolder + +@Aspect +@Component +class SecurityAspect(private val userRepository: UserRepository) { + + @Before("@annotation(AuthenticatedStaff)") + fun checkStaffAuthentication() { + val user = getLoginUser() + + if (user.role != Role.ROLE_STAFF) { + throw CserealException.Csereal401("권한이 없습니다.") + } + } + + @Before("@annotation(AuthenticatedForReservation)") + fun checkReservationAuthentication() { + val user = getLoginUser() + + if (user.role == null) { + throw CserealException.Csereal401("권한이 없습니다.") + } + } + + private fun getLoginUser(): UserEntity { + val authentication = SecurityContextHolder.getContext().authentication + val principal = authentication.principal + + if (principal !is OidcUser) { + throw CserealException.Csereal401("로그인이 필요합니다.") + } + + val username = principal.idToken.getClaim("username") + val user = userRepository.findByUsername(username) ?: throw CserealException.Csereal404("재로그인이 필요합니다.") + + RequestContextHolder.getRequestAttributes()?.setAttribute("loggedInUser", user, RequestAttributes.SCOPE_REQUEST) + + return user + } + +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservceController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservceController.kt index 82e9962a..e0dff854 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservceController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservceController.kt @@ -1,18 +1,21 @@ package com.wafflestudio.csereal.core.reservation.api -import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.common.aop.AuthenticatedForReservation +import com.wafflestudio.csereal.common.aop.AuthenticatedStaff import com.wafflestudio.csereal.core.reservation.dto.ReservationDto import com.wafflestudio.csereal.core.reservation.dto.ReserveRequest import com.wafflestudio.csereal.core.reservation.service.ReservationService -import org.springframework.security.core.annotation.AuthenticationPrincipal -import org.springframework.security.oauth2.core.oidc.user.OidcUser +import org.springframework.format.annotation.DateTimeFormat +import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.DeleteMapping import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController +import java.time.LocalDate import java.time.LocalDateTime import java.util.UUID @@ -22,32 +25,55 @@ class ReservationController( private val reservationService: ReservationService ) { + @GetMapping("/month") + @AuthenticatedForReservation + fun getMonthlyReservations( + @RequestParam roomId: Long, + @RequestParam year: Int, + @RequestParam month: Int + ): ResponseEntity> { + val start = LocalDateTime.of(year, month, 1, 0, 0) + val end = start.plusMonths(1) + return ResponseEntity.ok(reservationService.getRoomReservationsBetween(roomId, start, end)) + } + + @GetMapping("/week") + @AuthenticatedForReservation + fun getWeeklyReservations( + @RequestParam roomId: Long, + @RequestParam year: Int, + @RequestParam month: Int, + @RequestParam day: Int, + ): ResponseEntity> { + val start = LocalDateTime.of(year, month, day, 0, 0) + val end = start.plusDays(7) + return ResponseEntity.ok(reservationService.getRoomReservationsBetween(roomId, start, end)) + } + + @GetMapping("/{reservationId}") + @AuthenticatedStaff + fun getReservation(@PathVariable reservationId: Long): ResponseEntity { + return ResponseEntity.ok(reservationService.getReservation(reservationId)) + } + @PostMapping + @AuthenticatedForReservation fun reserveRoom( - @AuthenticationPrincipal principal: OidcUser?, @RequestBody reserveRequest: ReserveRequest - ): List { - if (principal == null) { - throw CserealException.Csereal401("로그인이 필요합니다.") - } - val username = principal.idToken.getClaim("username") - return reservationService.reserveRoom(username, reserveRequest) + ): ResponseEntity> { + return ResponseEntity.ok(reservationService.reserveRoom(reserveRequest)) } @DeleteMapping("/{reservationId}") - fun cancelSpecific(@AuthenticationPrincipal principal: OidcUser?, @PathVariable reservationId: Long) { - if (principal == null) { - throw CserealException.Csereal401("로그인이 필요합니다.") - } - reservationService.cancelSpecific(reservationId) + @AuthenticatedForReservation + fun cancelSpecific(@PathVariable reservationId: Long): ResponseEntity { + return ResponseEntity.ok(reservationService.cancelSpecific(reservationId)) } @DeleteMapping("/recurring/{recurrenceId}") - fun cancelRecurring(@AuthenticationPrincipal principal: OidcUser?, @PathVariable recurrenceId: UUID) { - if (principal == null) { - throw CserealException.Csereal401("로그인이 필요합니다.") - } - reservationService.cancelRecurring(recurrenceId) + @AuthenticatedForReservation + fun cancelRecurring(@PathVariable recurrenceId: UUID): ResponseEntity { + return ResponseEntity.ok(reservationService.cancelRecurring(recurrenceId)) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/ReservationRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/ReservationRepository.kt index 827db80d..dd750501 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/ReservationRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/ReservationRepository.kt @@ -1,15 +1,24 @@ package com.wafflestudio.csereal.core.reservation.database +import jakarta.persistence.LockModeType import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Lock import org.springframework.data.jpa.repository.Query import java.time.LocalDateTime import java.util.UUID interface ReservationRepository : JpaRepository { + @Lock(LockModeType.PESSIMISTIC_WRITE) @Query("SELECT r FROM reservation r WHERE r.room.id = :roomId AND ((:start <= r.startTime AND r.startTime < :end) OR (:start < r.endTime AND r.endTime <= :end) OR (r.startTime <= :start AND r.endTime >= :end))") fun findByRoomIdAndTimeOverlap(roomId: Long, start: LocalDateTime, end: LocalDateTime): List + fun findByRoomIdAndStartTimeBetweenOrderByStartTimeAsc( + roomId: Long, + start: LocalDateTime, + end: LocalDateTime + ): List + fun deleteAllByRecurrenceId(recurrenceId: UUID) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReservationDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReservationDto.kt index b868d4b2..f3c3ff3e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReservationDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReservationDto.kt @@ -2,9 +2,11 @@ package com.wafflestudio.csereal.core.reservation.dto import com.wafflestudio.csereal.core.reservation.database.ReservationEntity import java.time.LocalDateTime +import java.util.UUID data class ReservationDto( val id: Long, + val recurrenceId: UUID?, val title: String, val purpose: String, val startTime: LocalDateTime, @@ -19,6 +21,7 @@ data class ReservationDto( fun of(reservationEntity: ReservationEntity): ReservationDto { return ReservationDto( id = reservationEntity.id, + recurrenceId = reservationEntity.recurrenceId, title = reservationEntity.title, purpose = reservationEntity.purpose, startTime = reservationEntity.startTime, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/service/ReservationService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/service/ReservationService.kt index 8adba80c..5941bd4b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/service/ReservationService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/service/ReservationService.kt @@ -4,19 +4,22 @@ import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.core.reservation.database.* import com.wafflestudio.csereal.core.reservation.dto.ReservationDto import com.wafflestudio.csereal.core.reservation.dto.ReserveRequest -import com.wafflestudio.csereal.core.user.database.Role +import com.wafflestudio.csereal.core.user.database.UserEntity import com.wafflestudio.csereal.core.user.database.UserRepository import org.springframework.data.repository.findByIdOrNull +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.security.oauth2.core.oidc.user.OidcUser import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional +import org.springframework.web.context.request.RequestAttributes +import org.springframework.web.context.request.RequestContextHolder +import java.time.LocalDateTime import java.util.* interface ReservationService { - fun reserveRoom( - username: String, - reserveRequest: ReserveRequest - ): List - + fun reserveRoom(reserveRequest: ReserveRequest): List + fun getRoomReservationsBetween(roomId: Long, start: LocalDateTime, end: LocalDateTime): List + fun getReservation(reservationId: Long): ReservationDto fun cancelSpecific(reservationId: Long) fun cancelRecurring(recurrenceId: UUID) } @@ -29,18 +32,22 @@ class ReservationServiceImpl( private val roomRepository: RoomRepository ) : ReservationService { - override fun reserveRoom( - username: String, - reserveRequest: ReserveRequest - ): List { - val user = userRepository.findByUsername(username) ?: throw CserealException.Csereal404("재로그인이 필요합니다.") - val room = - roomRepository.findByIdOrNull(reserveRequest.roomId) ?: throw CserealException.Csereal404("Room Not Found") + override fun reserveRoom(reserveRequest: ReserveRequest): List { + var user = RequestContextHolder.getRequestAttributes()?.getAttribute( + "loggedInUser", + RequestAttributes.SCOPE_REQUEST + ) as UserEntity? - if (user.role == null) { - throw CserealException.Csereal401("권한이 없습니다.") + if (user == null) { + val oidcUser = SecurityContextHolder.getContext().authentication.principal as OidcUser + val username = oidcUser.idToken.getClaim("username") + + user = userRepository.findByUsername(username) ?: throw CserealException.Csereal404("재로그인이 필요합니다.") } + val room = + roomRepository.findByIdOrNull(reserveRequest.roomId) ?: throw CserealException.Csereal404("Room Not Found") + val reservations = mutableListOf() val recurrenceId = UUID.randomUUID() @@ -70,6 +77,23 @@ class ReservationServiceImpl( return reservations.map { ReservationDto.of(it) } } + @Transactional(readOnly = true) + override fun getRoomReservationsBetween( + roomId: Long, + start: LocalDateTime, + end: LocalDateTime + ): List { + return reservationRepository.findByRoomIdAndStartTimeBetweenOrderByStartTimeAsc(roomId, start, end) + .map { ReservationDto.of(it) } + } + + @Transactional(readOnly = true) + override fun getReservation(reservationId: Long): ReservationDto { + val reservationEntity = + reservationRepository.findByIdOrNull(reservationId) ?: throw CserealException.Csereal404("예약을 찾을 수 없습니다.") + return ReservationDto.of(reservationEntity) + } + override fun cancelSpecific(reservationId: Long) { reservationRepository.deleteById(reservationId) } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index b49fa8e6..20bcae44 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -29,12 +29,6 @@ springdoc: api-docs: path: /api-docs/json -logging.level: - default: INFO - org: - springframework: - security: DEBUG - --- spring: config.activate.on-profile: local @@ -48,10 +42,16 @@ spring: show-sql: true open-in-view: false +logging.level: + default: INFO + org: + springframework: + security: DEBUG + --- spring: config.activate.on-profile: prod jpa: hibernate: - ddl-auto: create # TODO: change to validate (or none) when save actual data to server + ddl-auto: update # TODO: change to validate (or none) when save actual data to server open-in-view: false From 191f6f6bd3140e3c5e9b0b6f1a645e9ea0da83ba Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Tue, 29 Aug 2023 22:15:45 +0900 Subject: [PATCH 026/124] =?UTF-8?q?feat:=20about,=20member,=20news,=20semi?= =?UTF-8?q?nar=20=EB=A9=94=EC=9D=B8=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EC=B6=94=EA=B0=80=20(#38)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: uploadImage 추가 * feat: about, member 사진 업로드 추가 * feat: news, seminar 사진 업로드 추가 * refactor: imageEntity 추가 리팩토링 * fix: gif 삭제 * fix: application.yaml 수정 * fix: newsService 태그 -> 이미지로 순서 변경 * fix: pr 리뷰 수정 * fix: extension 없애고, mainImage로 바꾸고, uuid 없애기 * fix: var 삭제 --------- Co-authored-by: Junhyeong Kim --- build.gradle.kts | 6 + .../common/controller/ContentEntityType.kt | 7 ++ .../csereal/core/about/api/AboutController.kt | 14 +-- .../core/about/database/AboutEntity.kt | 12 +- .../csereal/core/about/dto/AboutDto.kt | 8 +- .../core/about/service/AboutService.kt | 28 +++-- .../core/member/api/ProfessorController.kt | 8 +- .../core/member/api/StaffController.kt | 17 ++- .../core/member/database/ProfessorEntity.kt | 11 +- .../core/member/database/StaffEntity.kt | 11 +- .../csereal/core/member/dto/ProfessorDto.kt | 7 +- .../core/member/dto/SimpleProfessorDto.kt | 7 +- .../csereal/core/member/dto/SimpleStaffDto.kt | 6 +- .../csereal/core/member/dto/StaffDto.kt | 7 +- .../core/member/service/ProfessorService.kt | 40 ++++-- .../core/member/service/StaffService.kt | 31 +++-- .../csereal/core/news/api/NewsController.kt | 6 +- .../csereal/core/news/database/NewsEntity.kt | 24 +++- .../csereal/core/news/dto/NewsDto.kt | 5 +- .../csereal/core/news/service/NewsService.kt | 35 +++--- .../mainImage/api/MainImageController.kt | 13 ++ .../mainImage/database/MainImageEntity.kt | 19 +++ .../mainImage/database/MainImageRepository.kt | 8 ++ .../resource/mainImage/dto/MainImageDto.kt | 8 ++ .../mainImage/service/MainImageService.kt | 118 ++++++++++++++++++ .../core/seminar/api/SeminarController.kt | 7 +- .../core/seminar/database/SeminarEntity.kt | 16 ++- .../csereal/core/seminar/dto/SeminarDto.kt | 9 +- .../core/seminar/service/SeminarService.kt | 26 +++- src/main/resources/application.yaml | 18 +++ 30 files changed, 419 insertions(+), 113 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/common/controller/ContentEntityType.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/MainImageController.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/database/MainImageEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/database/MainImageRepository.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/dto/MainImageDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt diff --git a/build.gradle.kts b/build.gradle.kts index 575460ed..60751723 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -42,6 +42,12 @@ dependencies { // 태그 제거 implementation("org.jsoup:jsoup:1.15.4") + // 이미지 업로드 + implementation("commons-io:commons-io:2.11.0") + + // 썸네일 보여주기 + implementation("net.coobird:thumbnailator:0.4.19") + } noArg { annotation("jakarta.persistence.Entity") diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/controller/ContentEntityType.kt b/src/main/kotlin/com/wafflestudio/csereal/common/controller/ContentEntityType.kt new file mode 100644 index 00000000..0631bd6e --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/common/controller/ContentEntityType.kt @@ -0,0 +1,7 @@ +package com.wafflestudio.csereal.common.controller + +import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity + +interface ContentEntityType { + fun bringMainImage(): MainImageEntity? +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt index 4a1c87c5..07e25126 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt @@ -4,13 +4,8 @@ import com.wafflestudio.csereal.core.about.dto.AboutDto import com.wafflestudio.csereal.core.about.service.AboutService import jakarta.validation.Valid import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.PostMapping -import org.springframework.web.bind.annotation.RequestBody -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RequestParam -import org.springframework.web.bind.annotation.RestController +import org.springframework.web.bind.annotation.* +import org.springframework.web.multipart.MultipartFile @RequestMapping("/about") @RestController @@ -25,9 +20,10 @@ class AboutController( @PostMapping("/{postType}") fun createAbout( @PathVariable postType: String, - @Valid @RequestBody request: AboutDto + @Valid @RequestPart("request") request: AboutDto, + @RequestPart("image") image: MultipartFile?, ) : ResponseEntity { - return ResponseEntity.ok(aboutService.createAbout(postType, request)) + return ResponseEntity.ok(aboutService.createAbout(postType, request, image)) } // read 목록이 하나 diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt index 369e06a9..55f23e6b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt @@ -1,7 +1,9 @@ package com.wafflestudio.csereal.core.about.database import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.common.controller.ContentEntityType import com.wafflestudio.csereal.core.about.dto.AboutDto +import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity import jakarta.persistence.* @Entity(name = "about") @@ -14,8 +16,14 @@ class AboutEntity( var year: Int?, @OneToMany(mappedBy = "about", cascade = [CascadeType.ALL], orphanRemoval = true) - val locations: MutableList = mutableListOf() -) : BaseTimeEntity() { + val locations: MutableList = mutableListOf(), + + @OneToOne + var mainImage: MainImageEntity? = null, + +) : BaseTimeEntity(), ContentEntityType { + override fun bringMainImage(): MainImageEntity? = mainImage + companion object { fun of(postType: AboutPostType, aboutDto: AboutDto): AboutEntity { return AboutEntity( diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutDto.kt index 63652e47..fc0612e4 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutDto.kt @@ -12,10 +12,11 @@ data class AboutDto( val year: Int?, val createdAt: LocalDateTime?, val modifiedAt: LocalDateTime?, - val locations: List? + val locations: List?, + val imageURL: String? ) { companion object { - fun of(entity: AboutEntity) : AboutDto = entity.run { + fun of(entity: AboutEntity, imageURL: String?) : AboutDto = entity.run { AboutDto( id = this.id, name = this.name, @@ -24,7 +25,8 @@ data class AboutDto( year = this.year, createdAt = this.createdAt, modifiedAt = this.modifiedAt, - locations = this.locations.map { it.name } + locations = this.locations.map { it.name }, + imageURL = imageURL ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt index 79b6c8a2..5cac574a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt @@ -6,11 +6,13 @@ import com.wafflestudio.csereal.core.about.database.AboutPostType import com.wafflestudio.csereal.core.about.database.AboutRepository import com.wafflestudio.csereal.core.about.database.LocationEntity import com.wafflestudio.csereal.core.about.dto.AboutDto +import com.wafflestudio.csereal.core.resource.mainImage.service.ImageService import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional +import org.springframework.web.multipart.MultipartFile interface AboutService { - fun createAbout(postType: String, request: AboutDto): AboutDto + fun createAbout(postType: String, request: AboutDto, image: MultipartFile?): AboutDto fun readAbout(postType: String): AboutDto fun readAllClubs() : List fun readAllFacilities() : List @@ -19,10 +21,11 @@ interface AboutService { @Service class AboutServiceImpl( - private val aboutRepository: AboutRepository + private val aboutRepository: AboutRepository, + private val imageService: ImageService, ) : AboutService { @Transactional - override fun createAbout(postType: String, request: AboutDto): AboutDto { + override fun createAbout(postType: String, request: AboutDto, image: MultipartFile?): AboutDto { val enumPostType = makeStringToEnum(postType) val newAbout = AboutEntity.of(enumPostType, request) @@ -32,23 +35,30 @@ class AboutServiceImpl( } } + if(image != null) { + imageService.uploadImage(newAbout, image) + } aboutRepository.save(newAbout) - return AboutDto.of(newAbout) + val imageURL = imageService.createImageURL(newAbout.mainImage) + + return AboutDto.of(newAbout, imageURL) } @Transactional(readOnly = true) override fun readAbout(postType: String): AboutDto { val enumPostType = makeStringToEnum(postType) val about = aboutRepository.findByPostType(enumPostType) + val imageURL = imageService.createImageURL(about.mainImage) - return AboutDto.of(about) + return AboutDto.of(about, imageURL) } @Transactional(readOnly = true) override fun readAllClubs(): List { val clubs = aboutRepository.findAllByPostTypeOrderByName(AboutPostType.STUDENT_CLUBS).map { - AboutDto.of(it) + val imageURL = imageService.createImageURL(it.mainImage) + AboutDto.of(it, imageURL) } return clubs @@ -57,7 +67,8 @@ class AboutServiceImpl( @Transactional(readOnly = true) override fun readAllFacilities(): List { val facilities = aboutRepository.findAllByPostTypeOrderByName(AboutPostType.FACILITIES).map { - AboutDto.of(it) + val imageURL = imageService.createImageURL(it.mainImage) + AboutDto.of(it, imageURL) } return facilities @@ -66,7 +77,8 @@ class AboutServiceImpl( @Transactional(readOnly = true) override fun readAllDirections(): List { val directions = aboutRepository.findAllByPostTypeOrderByName(AboutPostType.DIRECTIONS).map { - AboutDto.of(it) + val imageURL = imageService.createImageURL(it.mainImage) + AboutDto.of(it, imageURL) } return directions diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt index 6caff3a0..b1ee1d72 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt @@ -6,6 +6,7 @@ import com.wafflestudio.csereal.core.member.dto.SimpleProfessorDto import com.wafflestudio.csereal.core.member.service.ProfessorService import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* +import org.springframework.web.multipart.MultipartFile @RequestMapping("/professor") @RestController @@ -14,8 +15,11 @@ class ProfessorController( ) { @PostMapping - fun createProfessor(@RequestBody createProfessorRequest: ProfessorDto): ResponseEntity { - return ResponseEntity.ok(professorService.createProfessor(createProfessorRequest)) + fun createProfessor( + @RequestPart("request") createProfessorRequest: ProfessorDto, + @RequestPart("image") image: MultipartFile?, + ): ResponseEntity { + return ResponseEntity.ok(professorService.createProfessor(createProfessorRequest, image)) } @GetMapping("/{professorId}") diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt index 391a3e58..26e0ac86 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt @@ -4,14 +4,8 @@ import com.wafflestudio.csereal.core.member.dto.SimpleStaffDto import com.wafflestudio.csereal.core.member.dto.StaffDto import com.wafflestudio.csereal.core.member.service.StaffService import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.DeleteMapping -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.PatchMapping -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.PostMapping -import org.springframework.web.bind.annotation.RequestBody -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RestController +import org.springframework.web.bind.annotation.* +import org.springframework.web.multipart.MultipartFile @RequestMapping("/staff") @RestController @@ -20,8 +14,11 @@ class StaffController( ) { @PostMapping - fun createStaff(@RequestBody createStaffRequest: StaffDto): ResponseEntity { - return ResponseEntity.ok(staffService.createStaff(createStaffRequest)) + fun createStaff( + @RequestPart("request") createStaffRequest: StaffDto, + @RequestPart("image") image: MultipartFile?, + ): ResponseEntity { + return ResponseEntity.ok(staffService.createStaff(createStaffRequest,image)) } @GetMapping("/{staffId}") diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt index 9e1a24e4..15000bec 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt @@ -1,8 +1,10 @@ package com.wafflestudio.csereal.core.member.database import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.common.controller.ContentEntityType import com.wafflestudio.csereal.core.member.dto.ProfessorDto import com.wafflestudio.csereal.core.research.database.LabEntity +import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity import jakarta.persistence.* import java.time.LocalDate @@ -39,8 +41,11 @@ class ProfessorEntity( @OneToMany(mappedBy = "professor", cascade = [CascadeType.ALL], orphanRemoval = true) val careers: MutableList = mutableListOf(), - var imageUri: String? = null -) : BaseTimeEntity() { + @OneToOne + var mainImage: MainImageEntity? = null, + +) : BaseTimeEntity(), ContentEntityType { + override fun bringMainImage(): MainImageEntity? = mainImage companion object { fun of(professorDto: ProfessorDto): ProfessorEntity { @@ -54,7 +59,7 @@ class ProfessorEntity( phone = professorDto.phone, fax = professorDto.fax, email = professorDto.email, - website = professorDto.website + website = professorDto.website, ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffEntity.kt index e4c37f82..abfafbe0 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffEntity.kt @@ -1,10 +1,13 @@ package com.wafflestudio.csereal.core.member.database import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.common.controller.ContentEntityType import com.wafflestudio.csereal.core.member.dto.StaffDto +import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity import jakarta.persistence.CascadeType import jakarta.persistence.Entity import jakarta.persistence.OneToMany +import jakarta.persistence.OneToOne @Entity(name = "staff") class StaffEntity( @@ -18,9 +21,11 @@ class StaffEntity( @OneToMany(mappedBy = "staff", cascade = [CascadeType.ALL], orphanRemoval = true) val tasks: MutableList = mutableListOf(), - var imageUri: String? = null + @OneToOne + var mainImage: MainImageEntity? = null, -) : BaseTimeEntity() { + ) : BaseTimeEntity(), ContentEntityType { + override fun bringMainImage(): MainImageEntity? = mainImage companion object { fun of(staffDto: StaffDto): StaffEntity { @@ -29,7 +34,7 @@ class StaffEntity( role = staffDto.role, office = staffDto.office, phone = staffDto.phone, - email = staffDto.email + email = staffDto.email, ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/ProfessorDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/ProfessorDto.kt index d6a2204b..c48f0a5b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/ProfessorDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/ProfessorDto.kt @@ -24,11 +24,11 @@ data class ProfessorDto( val researchAreas: List, val careers: List, @JsonInclude(JsonInclude.Include.NON_NULL) - var imageUri: String? = null + var imageURL: String? = null ) { companion object { - fun of(professorEntity: ProfessorEntity): ProfessorDto { + fun of(professorEntity: ProfessorEntity, imageURL: String?): ProfessorDto { return ProfessorDto( id = professorEntity.id, name = professorEntity.name, @@ -46,8 +46,9 @@ data class ProfessorDto( educations = professorEntity.educations.map { it.name }, researchAreas = professorEntity.researchAreas.map { it.name }, careers = professorEntity.careers.map { it.name }, - imageUri = professorEntity.imageUri + imageURL = imageURL, ) } + } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/SimpleProfessorDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/SimpleProfessorDto.kt index b3c3682b..bfe05446 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/SimpleProfessorDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/SimpleProfessorDto.kt @@ -10,10 +10,10 @@ data class SimpleProfessorDto( val labName: String?, val phone: String?, val email: String?, - val imageUri: String? + val imageURL: String? ) { companion object { - fun of(professorEntity: ProfessorEntity): SimpleProfessorDto { + fun of(professorEntity: ProfessorEntity, imageURL: String?): SimpleProfessorDto { return SimpleProfessorDto( id = professorEntity.id, name = professorEntity.name, @@ -22,8 +22,9 @@ data class SimpleProfessorDto( labName = professorEntity.lab?.name, phone = professorEntity.phone, email = professorEntity.email, - imageUri = professorEntity.imageUri + imageURL = imageURL ) } + } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/SimpleStaffDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/SimpleStaffDto.kt index 750f9801..c46c5067 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/SimpleStaffDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/SimpleStaffDto.kt @@ -9,11 +9,11 @@ data class SimpleStaffDto( val office: String, val phone: String, val email: String, - val imageUri: String? + val imageURL: String? ) { companion object { - fun of(staffEntity: StaffEntity): SimpleStaffDto { + fun of(staffEntity: StaffEntity, imageURL: String?): SimpleStaffDto { return SimpleStaffDto( id = staffEntity.id, name = staffEntity.name, @@ -21,7 +21,7 @@ data class SimpleStaffDto( office = staffEntity.office, phone = staffEntity.phone, email = staffEntity.email, - imageUri = staffEntity.imageUri + imageURL = imageURL ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/StaffDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/StaffDto.kt index 8cd1ee06..69a1d9fd 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/StaffDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/StaffDto.kt @@ -13,10 +13,10 @@ data class StaffDto( val email: String, val tasks: List, @JsonInclude(JsonInclude.Include.NON_NULL) - val imageUri: String? = null + val imageURL: String? = null ) { companion object { - fun of(staffEntity: StaffEntity): StaffDto { + fun of(staffEntity: StaffEntity, imageURL: String?): StaffDto { return StaffDto( id = staffEntity.id, name = staffEntity.name, @@ -25,8 +25,9 @@ data class StaffDto( phone = staffEntity.phone, email = staffEntity.email, tasks = staffEntity.tasks.map { it.name }, - imageUri = staffEntity.imageUri + imageURL = imageURL ) } + } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt index 5710d416..c53746b1 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt @@ -6,14 +6,14 @@ import com.wafflestudio.csereal.core.member.dto.ProfessorDto import com.wafflestudio.csereal.core.member.dto.ProfessorPageDto import com.wafflestudio.csereal.core.member.dto.SimpleProfessorDto import com.wafflestudio.csereal.core.research.database.LabRepository +import com.wafflestudio.csereal.core.resource.mainImage.service.ImageService import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional +import org.springframework.web.multipart.MultipartFile interface ProfessorService { - - fun createProfessor(createProfessorRequest: ProfessorDto): ProfessorDto - fun getProfessor(professorId: Long): ProfessorDto + fun createProfessor(createProfessorRequest: ProfessorDto, image: MultipartFile?): ProfessorDto fun getProfessor(professorId: Long): ProfessorDto fun getActiveProfessors(): ProfessorPageDto fun getInactiveProfessors(): List fun updateProfessor(professorId: Long, updateProfessorRequest: ProfessorDto): ProfessorDto @@ -24,12 +24,11 @@ interface ProfessorService { @Transactional class ProfessorServiceImpl( private val labRepository: LabRepository, - private val professorRepository: ProfessorRepository + private val professorRepository: ProfessorRepository, + private val imageService: ImageService, ) : ProfessorService { - - override fun createProfessor(createProfessorRequest: ProfessorDto): ProfessorDto { + override fun createProfessor(createProfessorRequest: ProfessorDto, image: MultipartFile?): ProfessorDto { val professor = ProfessorEntity.of(createProfessorRequest) - if (createProfessorRequest.labId != null) { val lab = labRepository.findByIdOrNull(createProfessorRequest.labId) ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다. LabId: ${createProfessorRequest.labId}") @@ -48,16 +47,25 @@ class ProfessorServiceImpl( CareerEntity.create(career, professor) } + if(image != null) { + imageService.uploadImage(professor, image) + } + professorRepository.save(professor) - return ProfessorDto.of(professor) + val imageURL = imageService.createImageURL(professor.mainImage) + + return ProfessorDto.of(professor, imageURL) } @Transactional(readOnly = true) override fun getProfessor(professorId: Long): ProfessorDto { val professor = professorRepository.findByIdOrNull(professorId) ?: throw CserealException.Csereal404("해당 교수님을 찾을 수 없습니다. professorId: ${professorId}") - return ProfessorDto.of(professor) + + val imageURL = imageService.createImageURL(professor.mainImage) + + return ProfessorDto.of(professor, imageURL) } @Transactional(readOnly = true) @@ -68,14 +76,20 @@ class ProfessorServiceImpl( "교육 및 연구 지도에 총력을 기울이고 있다.\n\n다수의 외국인 학부생, 대학원생이 재학 중에 있으며 매 학기 전공 필수 과목을 비롯한 " + "30% 이상의 과목이 영어로 개설되고 있어 외국인 학생의 학업을 돕는 동시에 한국인 학생이 세계로 진출하는 초석이 되고 있다. 또한 " + "CSE int’l Luncheon을 개최하여 학부 내 외국인 구성원의 화합과 생활의 불편함을 최소화하는 등 학부 차원에서 최선을 다하고 있다." - val professors = professorRepository.findByStatusNot(ProfessorStatus.INACTIVE).map { SimpleProfessorDto.of(it) } + val professors = professorRepository.findByStatusNot(ProfessorStatus.INACTIVE).map { + val imageURL = imageService.createImageURL(it.mainImage) + SimpleProfessorDto.of(it, imageURL) + } .sortedBy { it.name } return ProfessorPageDto(description, professors) } @Transactional(readOnly = true) override fun getInactiveProfessors(): List { - return professorRepository.findByStatus(ProfessorStatus.INACTIVE).map { SimpleProfessorDto.of(it) } + return professorRepository.findByStatus(ProfessorStatus.INACTIVE).map { + val imageURL = imageService.createImageURL(it.mainImage) + SimpleProfessorDto.of(it, imageURL) + } .sortedBy { it.name } } @@ -128,7 +142,9 @@ class ProfessorServiceImpl( CareerEntity.create(career, professor) } - return ProfessorDto.of(professor) + val imageURL = imageService.createImageURL(professor.mainImage) + + return ProfessorDto.of(professor, imageURL) } override fun deleteProfessor(professorId: Long) { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt index d76137fb..722b29fd 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt @@ -6,12 +6,14 @@ import com.wafflestudio.csereal.core.member.database.StaffRepository import com.wafflestudio.csereal.core.member.database.TaskEntity import com.wafflestudio.csereal.core.member.dto.SimpleStaffDto import com.wafflestudio.csereal.core.member.dto.StaffDto +import com.wafflestudio.csereal.core.resource.mainImage.service.ImageService import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional +import org.springframework.web.multipart.MultipartFile interface StaffService { - fun createStaff(createStaffRequest: StaffDto): StaffDto + fun createStaff(createStaffRequest: StaffDto, image: MultipartFile?): StaffDto fun getStaff(staffId: Long): StaffDto fun getAllStaff(): List fun updateStaff(staffId: Long, updateStaffRequest: StaffDto): StaffDto @@ -21,30 +23,43 @@ interface StaffService { @Service @Transactional class StaffServiceImpl( - private val staffRepository: StaffRepository + private val staffRepository: StaffRepository, + private val imageService: ImageService, ) : StaffService { - override fun createStaff(createStaffRequest: StaffDto): StaffDto { + override fun createStaff(createStaffRequest: StaffDto, image: MultipartFile?): StaffDto { val staff = StaffEntity.of(createStaffRequest) for (task in createStaffRequest.tasks) { TaskEntity.create(task, staff) } + if(image != null) { + imageService.uploadImage(staff, image) + } + staffRepository.save(staff) - return StaffDto.of(staff) + val imageURL = imageService.createImageURL(staff.mainImage) + + return StaffDto.of(staff, imageURL) } @Transactional(readOnly = true) override fun getStaff(staffId: Long): StaffDto { val staff = staffRepository.findByIdOrNull(staffId) ?: throw CserealException.Csereal404("해당 행정직원을 찾을 수 없습니다. staffId: ${staffId}") - return StaffDto.of(staff) + + val imageURL = imageService.createImageURL(staff.mainImage) + + return StaffDto.of(staff, imageURL) } @Transactional(readOnly = true) override fun getAllStaff(): List { - return staffRepository.findAll().map { SimpleStaffDto.of(it) }.sortedBy { it.name } + return staffRepository.findAll().map { + val imageURL = imageService.createImageURL(it.mainImage) + SimpleStaffDto.of(it, imageURL) + }.sortedBy { it.name } } override fun updateStaff(staffId: Long, updateStaffRequest: StaffDto): StaffDto { @@ -66,7 +81,9 @@ class StaffServiceImpl( TaskEntity.create(task, staff) } - return StaffDto.of(staff) + val imageURL = imageService.createImageURL(staff.mainImage) + + return StaffDto.of(staff, imageURL) } override fun deleteStaff(staffId: Long) { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt index 4dea9962..62fd590b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt @@ -7,6 +7,7 @@ import jakarta.validation.Valid import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* +import org.springframework.web.multipart.MultipartFile @RequestMapping("/news") @RestController @@ -32,9 +33,10 @@ class NewsController( @PostMapping fun createNews( - @Valid @RequestBody request: NewsDto + @Valid @RequestPart("request") request: NewsDto, + @RequestPart("image") image: MultipartFile?, ) : ResponseEntity { - return ResponseEntity.ok(newsService.createNews(request)) + return ResponseEntity.ok(newsService.createNews(request,image)) } @PatchMapping("/{newsId}") diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt index a2944a97..541f0d34 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt @@ -1,11 +1,10 @@ package com.wafflestudio.csereal.core.news.database import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.common.controller.ContentEntityType import com.wafflestudio.csereal.core.news.dto.NewsDto -import jakarta.persistence.CascadeType -import jakarta.persistence.Column -import jakarta.persistence.Entity -import jakarta.persistence.OneToMany +import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity +import jakarta.persistence.* @Entity(name = "news") class NewsEntity( @@ -23,10 +22,25 @@ class NewsEntity( // 새소식 작성란에도 "가장 위에 표시"가 있더라고요, 혹시 쓸지도 모르니까 남겼습니다 var isPinned: Boolean, + @OneToOne + var mainImage: MainImageEntity? = null, + @OneToMany(mappedBy = "news", cascade = [CascadeType.ALL]) var newsTags: MutableSet = mutableSetOf() -): BaseTimeEntity() { +): BaseTimeEntity(), ContentEntityType { + override fun bringMainImage() = mainImage + companion object { + fun of(newsDto: NewsDto): NewsEntity { + return NewsEntity( + title = newsDto.title, + description = newsDto.description, + isPublic = newsDto.isPublic, + isSlide = newsDto.isSlide, + isPinned = newsDto.isPinned, + ) + } + } fun update(updateNewsRequest: NewsDto) { this.title = updateNewsRequest.title this.description = updateNewsRequest.description diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt index da7f987c..effec738 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt @@ -17,9 +17,10 @@ data class NewsDto( val prevTitle: String?, val nextId: Long?, val nextTitle: String?, + val imageURL: String?, ) { companion object { - fun of(entity: NewsEntity, prevNext: Array?) : NewsDto = entity.run { + fun of(entity: NewsEntity, imageURL: String?, prevNext: Array?) : NewsDto = entity.run { NewsDto( id = this.id, title = this.title, @@ -34,7 +35,7 @@ data class NewsDto( prevTitle = prevNext?.get(0)?.title, nextId = prevNext?.get(1)?.id, nextTitle = prevNext?.get(1)?.title, - + imageURL = imageURL, ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt index eb638dfb..fb07aada 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt @@ -4,15 +4,16 @@ import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.core.news.database.* import com.wafflestudio.csereal.core.news.dto.NewsDto import com.wafflestudio.csereal.core.news.dto.NewsSearchResponse +import com.wafflestudio.csereal.core.resource.mainImage.service.ImageService import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional -import java.util.* +import org.springframework.web.multipart.MultipartFile interface NewsService { fun searchNews(tag: List?, keyword: String?, pageNum: Long): NewsSearchResponse fun readNews(newsId: Long, tag: List?, keyword: String?): NewsDto - fun createNews(request: NewsDto): NewsDto + fun createNews(request: NewsDto, image: MultipartFile?): NewsDto fun updateNews(newsId: Long, request: NewsDto): NewsDto fun deleteNews(newsId: Long) fun enrollTag(tagName: String) @@ -22,7 +23,8 @@ interface NewsService { class NewsServiceImpl( private val newsRepository: NewsRepository, private val tagInNewsRepository: TagInNewsRepository, - private val newsTagRepository: NewsTagRepository + private val newsTagRepository: NewsTagRepository, + private val imageService: ImageService, ) : NewsService { @Transactional(readOnly = true) override fun searchNews( @@ -44,30 +46,32 @@ class NewsServiceImpl( if (news.isDeleted) throw CserealException.Csereal404("삭제된 새소식입니다.(newsId: $newsId)") + val imageURL = imageService.createImageURL(news.mainImage) + val prevNext = newsRepository.findPrevNextId(newsId, tag, keyword) ?: throw CserealException.Csereal400("이전글 다음글이 존재하지 않습니다.(newsId=$newsId)") - return NewsDto.of(news, prevNext) + return NewsDto.of(news, imageURL, prevNext) } @Transactional - override fun createNews(request: NewsDto): NewsDto { - val newNews = NewsEntity( - title = request.title, - description = request.description, - isPublic = request.isPublic, - isSlide = request.isSlide, - isPinned = request.isPinned - ) + override fun createNews(request: NewsDto, image: MultipartFile?): NewsDto { + val newNews = NewsEntity.of(request) for (tagName in request.tags) { val tag = tagInNewsRepository.findByName(tagName) ?: throw CserealException.Csereal404("해당하는 태그가 없습니다") NewsTagEntity.createNewsTag(newNews, tag) } + if(image != null) { + imageService.uploadImage(newNews, image) + } + newsRepository.save(newNews) - return NewsDto.of(newNews, null) + val imageURL = imageService.createImageURL(newNews.mainImage) + + return NewsDto.of(newNews, imageURL, null) } @Transactional @@ -93,7 +97,10 @@ class NewsServiceImpl( NewsTagEntity.createNewsTag(news,tag) } - return NewsDto.of(news, null) + val imageURL = imageService.createImageURL(news.mainImage) + + + return NewsDto.of(news, imageURL, null) } @Transactional diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/MainImageController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/MainImageController.kt new file mode 100644 index 00000000..e74a26da --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/MainImageController.kt @@ -0,0 +1,13 @@ +package com.wafflestudio.csereal.core.resource.mainImage.api + +import com.wafflestudio.csereal.core.resource.mainImage.service.ImageService +import org.springframework.web.bind.annotation.* + + +@RequestMapping("/image") +@RestController +class MainImageController( + private val imageService: ImageService +) { + +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/database/MainImageEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/database/MainImageEntity.kt new file mode 100644 index 00000000..28273401 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/database/MainImageEntity.kt @@ -0,0 +1,19 @@ +package com.wafflestudio.csereal.core.resource.mainImage.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import jakarta.persistence.* + + +@Entity(name = "mainImage") +class MainImageEntity( + val isDeleted : Boolean? = true, + + @Column(unique = true) + val filename: String, + + val imagesOrder: Int, + val size: Long, + + ) : BaseTimeEntity() { + +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/database/MainImageRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/database/MainImageRepository.kt new file mode 100644 index 00000000..f67b9dbc --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/database/MainImageRepository.kt @@ -0,0 +1,8 @@ +package com.wafflestudio.csereal.core.resource.mainImage.database + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface MainImageRepository : JpaRepository { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/dto/MainImageDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/dto/MainImageDto.kt new file mode 100644 index 00000000..6ca97407 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/dto/MainImageDto.kt @@ -0,0 +1,8 @@ +package com.wafflestudio.csereal.core.resource.mainImage.dto + +data class MainImageDto( + val filename: String, + val imagesOrder: Int, + val size: Long, +) { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt new file mode 100644 index 00000000..ec75e77f --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt @@ -0,0 +1,118 @@ +package com.wafflestudio.csereal.core.resource.mainImage.service + +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.common.controller.ContentEntityType +import com.wafflestudio.csereal.core.about.database.AboutEntity +import com.wafflestudio.csereal.core.member.database.ProfessorEntity +import com.wafflestudio.csereal.core.member.database.StaffEntity +import com.wafflestudio.csereal.core.news.database.NewsEntity +import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageRepository +import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity +import com.wafflestudio.csereal.core.resource.mainImage.dto.MainImageDto +import com.wafflestudio.csereal.core.seminar.database.SeminarEntity +import net.coobird.thumbnailator.Thumbnailator +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import org.springframework.web.multipart.MultipartFile +import org.apache.commons.io.FilenameUtils +import java.lang.invoke.WrongMethodTypeException +import java.nio.file.Files +import java.nio.file.Paths +import kotlin.io.path.fileSize +import kotlin.io.path.name + + +interface ImageService { + fun uploadImage( + contentEntityType: ContentEntityType, + requestImage: MultipartFile, + ): MainImageDto + fun createImageURL(image: MainImageEntity?) : String? +} + +@Service +class ImageServiceImpl( + private val imageRepository: MainImageRepository, + @Value("\${csereal.upload.path}") + private val path: String, +) : ImageService { + + @Transactional + override fun uploadImage( + contentEntity: ContentEntityType, + requestImage: MultipartFile, + ): MainImageDto { + Files.createDirectories(Paths.get(path)) + + val extension = FilenameUtils.getExtension(requestImage.originalFilename) + + if(!listOf("jpg", "jpeg", "png").contains(extension)) { + throw CserealException.Csereal400("파일의 형식은 jpg, jpeg, png 중 하나여야 합니다.") + } + + val timeMillis = System.currentTimeMillis() + + val filename = "${timeMillis}_${requestImage.originalFilename}" + val totalFilename = path + filename + val saveFile = Paths.get("$totalFilename.$extension") + requestImage.transferTo(saveFile) + + val totalThumbnailFilename = "${path}thumbnail_$filename" + val thumbnailFile = Paths.get("$totalThumbnailFilename.$extension") + Thumbnailator.createThumbnail(saveFile.toFile(), thumbnailFile.toFile(), 100, 100); + + val image = MainImageEntity( + filename = filename, + imagesOrder = 1, + size = requestImage.size, + ) + + val thumbnail = MainImageEntity( + filename = thumbnailFile.name, + imagesOrder = 1, + size = thumbnailFile.fileSize() + ) + + connectImageToEntity(contentEntity, image) + imageRepository.save(image) + imageRepository.save(thumbnail) + + return MainImageDto( + filename = filename, + imagesOrder = 1, + size = requestImage.size + ) + } + + @Transactional + override fun createImageURL(image: MainImageEntity?) : String? { + return if(image != null) { + "http://cse-dev-waffle.bacchus.io/image/${image.filename}" + } else null + } + + private fun connectImageToEntity(contentEntity: ContentEntityType, image: MainImageEntity) { + when (contentEntity) { + is NewsEntity -> { + contentEntity.mainImage = image + } + is SeminarEntity -> { + contentEntity.mainImage = image + } + is AboutEntity -> { + contentEntity.mainImage = image + } + is ProfessorEntity -> { + contentEntity.mainImage = image + } + is StaffEntity -> { + contentEntity.mainImage = image + } + else -> { + throw WrongMethodTypeException("해당하는 엔티티가 없습니다") + } + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt index 8b2699d7..f46cca7f 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt @@ -6,6 +6,7 @@ import com.wafflestudio.csereal.core.seminar.service.SeminarService import jakarta.validation.Valid import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* +import org.springframework.web.multipart.MultipartFile @RequestMapping("/seminar") @RestController @@ -21,10 +22,10 @@ class SeminarController ( } @PostMapping fun createSeminar( - @Valid @RequestBody request: SeminarDto + @Valid @RequestPart("request") request: SeminarDto, + @RequestPart("image") image: MultipartFile? ) : ResponseEntity { - return ResponseEntity.ok(seminarService.createSeminar(request)) - } + return ResponseEntity.ok(seminarService.createSeminar(request,image)) } @GetMapping("/{seminarId}") fun readSeminar( diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt index bc098643..e7b48fe0 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt @@ -1,9 +1,12 @@ package com.wafflestudio.csereal.core.seminar.database import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.common.controller.ContentEntityType +import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity import com.wafflestudio.csereal.core.seminar.dto.SeminarDto import jakarta.persistence.Column import jakarta.persistence.Entity +import jakarta.persistence.OneToOne @Entity(name = "seminar") class SeminarEntity( @@ -36,16 +39,18 @@ class SeminarEntity( var host: String?, - // var profileImage: File, - // var seminarFile: File, var isPublic: Boolean, var isSlide: Boolean, - var additionalNote: String? -): BaseTimeEntity() { + var additionalNote: String?, + + @OneToOne + var mainImage: MainImageEntity? = null, +): BaseTimeEntity(), ContentEntityType { + override fun bringMainImage(): MainImageEntity? = mainImage companion object { fun of(seminarDto: SeminarDto): SeminarEntity { @@ -67,10 +72,9 @@ class SeminarEntity( host = seminarDto.host, additionalNote = seminarDto.additionalNote, isPublic = seminarDto.isPublic, - isSlide = seminarDto.isSlide + isSlide = seminarDto.isSlide, ) } - } fun update(updateSeminarRequest: SeminarDto) { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt index b2563385..2eda7a96 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt @@ -19,7 +19,6 @@ data class SeminarDto( val endTime: String?, val location: String, val host: String?, - // val profileImage: File, // val seminarFile: File, val additionalNote: String?, val isPublic: Boolean, @@ -27,11 +26,12 @@ data class SeminarDto( val prevId: Long?, val prevTitle: String?, val nextId: Long?, - val nextTitle: String? + val nextTitle: String?, + val imageURL: String?, ) { companion object { - fun of(entity: SeminarEntity, prevNext: Array?): SeminarDto = entity.run { + fun of(entity: SeminarEntity, imageURL: String?, prevNext: Array?): SeminarDto = entity.run { SeminarDto( id = this.id, title = this.title, @@ -55,7 +55,8 @@ data class SeminarDto( prevId = prevNext?.get(0)?.id, prevTitle = prevNext?.get(0)?.title, nextId = prevNext?.get(1)?.id, - nextTitle = prevNext?.get(1)?.title + nextTitle = prevNext?.get(1)?.title, + imageURL = imageURL, ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt index cf6a5cba..274a178e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt @@ -1,6 +1,7 @@ package com.wafflestudio.csereal.core.seminar.service import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.resource.mainImage.service.ImageService import com.wafflestudio.csereal.core.seminar.database.SeminarEntity import com.wafflestudio.csereal.core.seminar.database.SeminarRepository import com.wafflestudio.csereal.core.seminar.dto.SeminarDto @@ -8,10 +9,11 @@ import com.wafflestudio.csereal.core.seminar.dto.SeminarSearchResponse import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional +import org.springframework.web.multipart.MultipartFile interface SeminarService { fun searchSeminar(keyword: String?, pageNum: Long): SeminarSearchResponse - fun createSeminar(request: SeminarDto): SeminarDto + fun createSeminar(request: SeminarDto, image: MultipartFile?): SeminarDto fun readSeminar(seminarId: Long, keyword: String?): SeminarDto fun updateSeminar(seminarId: Long, request: SeminarDto): SeminarDto fun deleteSeminar(seminarId: Long) @@ -19,7 +21,8 @@ interface SeminarService { @Service class SeminarServiceImpl( - private val seminarRepository: SeminarRepository + private val seminarRepository: SeminarRepository, + private val imageService: ImageService, ) : SeminarService { @Transactional(readOnly = true) override fun searchSeminar(keyword: String?, pageNum: Long): SeminarSearchResponse { @@ -27,12 +30,19 @@ class SeminarServiceImpl( } @Transactional - override fun createSeminar(request: SeminarDto): SeminarDto { + override fun createSeminar(request: SeminarDto, image: MultipartFile?): SeminarDto { val newSeminar = SeminarEntity.of(request) + if(image != null) { + imageService.uploadImage(newSeminar, image) + } + seminarRepository.save(newSeminar) - return SeminarDto.of(newSeminar, null) + val imageURL = imageService.createImageURL(newSeminar.mainImage) + + + return SeminarDto.of(newSeminar, imageURL, null) } @Transactional(readOnly = true) @@ -42,9 +52,11 @@ class SeminarServiceImpl( if (seminar.isDeleted) throw CserealException.Csereal400("삭제된 세미나입니다. (seminarId: $seminarId)") + val imageURL = imageService.createImageURL(seminar.mainImage) + val prevNext = seminarRepository.findPrevNextId(seminarId, keyword) - return SeminarDto.of(seminar, prevNext) + return SeminarDto.of(seminar, imageURL, prevNext) } @Transactional @@ -55,7 +67,9 @@ class SeminarServiceImpl( seminar.update(request) - return SeminarDto.of(seminar, null) + val imageURL = imageService.createImageURL(seminar.mainImage) + + return SeminarDto.of(seminar, imageURL, null) } @Transactional override fun deleteSeminar(seminarId: Long) { diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 20bcae44..9990d0e5 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -23,12 +23,20 @@ server: session: timeout: 7200 # 2시간 + springdoc: swagger-ui: path: index.html api-docs: path: /api-docs/json +servlet: + multipart: + enabled: true + max-request-size: 100MB + max-file-size: 10MB + + --- spring: config.activate.on-profile: local @@ -48,6 +56,9 @@ logging.level: springframework: security: DEBUG +csereal: + upload: + path: /app/image/ --- spring: config.activate.on-profile: prod @@ -55,3 +66,10 @@ spring: hibernate: ddl-auto: update # TODO: change to validate (or none) when save actual data to server open-in-view: false + + + +csereal: + upload: + path: /app/image/ + From 455ded86bb2f341f8a683ac8765d0d84d87576ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=ED=98=81=EC=A4=80=20=28HyukJoon=20Woo=29?= Date: Tue, 29 Aug 2023 22:49:04 +0900 Subject: [PATCH 027/124] CICD: Change deploy port to 8080 (#40) --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index ac6b0f24..f129819c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,7 @@ services: args: PROFILE: ${PROFILE} ports: - - 80:8080 + - 8080:8080 environment: SPRING_DATASOURCE_URL: "jdbc:mysql://csereal_db_container:3306/${MYSQL_DATABASE}?serverTimezone=Asia/Seoul&useSSL=false&allowPublicKeyRetrieval=true" SPRING_DATASOURCE_USERNAME: ${MYSQL_USER} From f629d47da48aacdc183f629cf95abd82fed5bf97 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Tue, 29 Aug 2023 23:21:50 +0900 Subject: [PATCH 028/124] [Merge] (#41) (#42) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 공지사항 생성, 공지사항 읽기 기능 추가 (#1) * :sparkles: 패키지 및 엔티티 생성 * :sparkles: BaseTimeEntity 생성, PostEntity 기본 내용 작성 * :sparkles: PostController 생성 및 기본 내용 작성 * :sparkles: postService 생성 * :sparkles: Exceptions.kt 생성 * :sparkles: postDto 생성 * :sparkles: postRepository 생성 및 기본 내용 작성 * :green_heart: application.yaml 로컬 환경에서 작동하도록 설정 * feat: createPost 기능 생성 * refactor: 리뷰 주신 거 수정 * refactor: post -> notice 수정 등 * chore: .idea 디렉토리 삭제 * chore: PR 템플릿 생성 (#2) * feat: 로컬 db용 docker-compose 파일 추가 및 application.yaml 수정 (#4) * feat: 공지사항 수정, 삭제, 태그 기능 추가 (#3) * fix: ExceptionHandler 추가 * feat: updateNotice 추가, valid 추가 * feat: deleteNotice 추가 * feat: enrollTag 기능 추가, noticeTag 연관 엔티티 추가 * feat: 공지사항 작성할 때 태그 생성 및 수정 * fix: 로컬 db 없앰 * fix: pr 리뷰 수정 * fix: pr 리뷰 수정 * fix: noticeTag assign * feat: 구성원(교수) 생성 및 조회 API 구현 (#8) * feat: 교수 엔티티 및 DTO 설계 * feat: 교수 생성 및 조회 * Docs: Swagger 추가 (#7) * Docs: Add swagger dependency * Docs: Add basic config for swagger * Docs: Add basic configuration for swagger. * feat: 페이지네이션+검색 기능 추가 (#5) * feat: isPublic, isSlide, isPinned 추가 * feat: queryDsl 적용 위해 gradle 추가 fix: javax -> jakarta 변경 * feat: queryDsl 도입 * feat: 키워드+태그 검색 추가 * feat: search query에 isDeleted, isPublic 추가 및 isPinned 우선순위 설정 * fix: requestBody -> requestParam 수정 * feat: 페이지네이션 추가 * fix: 키워드 booleanBuilder 추가, application.yaml 수정 * fix: searchNotice readOnly 추가 * fix: SearchRequest 삭제 * fix: NoticeDto tags 추가 * fix: pr 리뷰 수정 / feat: 검색 기능 보강 및 수정 * fix:코드 수정 * fix: SearchResponse isPinned 추가 * fix: SearchResponse에 total 추가 * fix: 페이지 개수 수정 * fix: searchNotice queryDsl 오류 수정 * fix: local 설정 변경 * CICD: 배포 자동화 (#6) * CICD: Change expose port and added image tag * CICD: Change ddl-auto to create in prod profile for test * CICD: Added Deploy github action * CICD: Merge jobs to one job * Fix: Change checkout order to first step * CICD: Add context for docker build action * Fix: Change spring profile arg position * CICD: Change openjdk version to 17 * CICD: Change docker compose build image tag to latest * CICD: Change to use ghcr repository. * Fix: change list to string in docker push tags. * Fix: Change registry to ghcr.io * Fix: change env to pass to github action instead of ssh export command * Fix: unwrap bracket. * Fix: wrap Profile with "" * CICD: Add .env file * CICD: Change prod ddl-auto to create (for developing), and add TODO comment. * CICD: Remove cicd/deploy branch for condition. * feat: 구성원(교수) 수정 및 삭제 API (#9) * feat: 교수 조회시 최종학력이 앞으로 오게끔 정렬 * feat: 교수 수정 및 삭제 API * feat: 학력과 경력 엔티티 필드 변경, 수정 API 구현 * feat: 구성원(행정직원) CRUD API (#10) * feat: 행정직원 엔티티 및 DTO 설계 * feat: 행정직원 CRUD * feat: 교수 조회시 이름순 정렬 * fix: 교수 연구 분야 Set -> List 로 변경 * feat: 행정직원 주요업무 업데이트 구현 * feat: news 패키지 추가, 디벨롭 및 프론트에 맞게 엔티티 변경 (#12) * feat: news 패키지 생성 * feat: readNews 생성, news 패키지 추가로 인한 명칭 변경 * feat: createNews, enrollTag(새소식) 추가, news 패키지로 인한 명칭 추가 변경 * feat: updateNews, deleteNews 추가 * fix: searchNotice 관련 명칭 변경 * feat: searchNews 추가 * fix: develop 브랜치 반영, 프론트 요구사항 반영 * feat: readNotice, readNews에 이전글 다음글 추가 * 태그 업데이트 코드 리팩터링중 * refactor: 코드 수정, 이전제목 추가 * fix: 게시글 하나일때 read 가능 * fix: prevNext null 없애기 * fix: 이전글 다음글 null 수정 * fix: main에서 develop으로 pr (#16) * feat: merge develop to main (#13) * feat: 공지사항 생성, 공지사항 읽기 기능 추가 (#1) * :sparkles: 패키지 및 엔티티 생성 * :sparkles: BaseTimeEntity 생성, PostEntity 기본 내용 작성 * :sparkles: PostController 생성 및 기본 내용 작성 * :sparkles: postService 생성 * :sparkles: Exceptions.kt 생성 * :sparkles: postDto 생성 * :sparkles: postRepository 생성 및 기본 내용 작성 * :green_heart: application.yaml 로컬 환경에서 작동하도록 설정 * feat: createPost 기능 생성 * refactor: 리뷰 주신 거 수정 * refactor: post -> notice 수정 등 * chore: .idea 디렉토리 삭제 * chore: PR 템플릿 생성 (#2) * feat: 로컬 db용 docker-compose 파일 추가 및 application.yaml 수정 (#4) * feat: 공지사항 수정, 삭제, 태그 기능 추가 (#3) * fix: ExceptionHandler 추가 * feat: updateNotice 추가, valid 추가 * feat: deleteNotice 추가 * feat: enrollTag 기능 추가, noticeTag 연관 엔티티 추가 * feat: 공지사항 작성할 때 태그 생성 및 수정 * fix: 로컬 db 없앰 * fix: pr 리뷰 수정 * fix: pr 리뷰 수정 * fix: noticeTag assign * feat: 구성원(교수) 생성 및 조회 API 구현 (#8) * feat: 교수 엔티티 및 DTO 설계 * feat: 교수 생성 및 조회 * Docs: Swagger 추가 (#7) * Docs: Add swagger dependency * Docs: Add basic config for swagger * Docs: Add basic configuration for swagger. * feat: 페이지네이션+검색 기능 추가 (#5) * feat: isPublic, isSlide, isPinned 추가 * feat: queryDsl 적용 위해 gradle 추가 fix: javax -> jakarta 변경 * feat: queryDsl 도입 * feat: 키워드+태그 검색 추가 * feat: search query에 isDeleted, isPublic 추가 및 isPinned 우선순위 설정 * fix: requestBody -> requestParam 수정 * feat: 페이지네이션 추가 * fix: 키워드 booleanBuilder 추가, application.yaml 수정 * fix: searchNotice readOnly 추가 * fix: SearchRequest 삭제 * fix: NoticeDto tags 추가 * fix: pr 리뷰 수정 / feat: 검색 기능 보강 및 수정 * fix:코드 수정 * fix: SearchResponse isPinned 추가 * fix: SearchResponse에 total 추가 * fix: 페이지 개수 수정 * fix: searchNotice queryDsl 오류 수정 * fix: local 설정 변경 * CICD: 배포 자동화 (#6) * CICD: Change expose port and added image tag * CICD: Change ddl-auto to create in prod profile for test * CICD: Added Deploy github action * CICD: Merge jobs to one job * Fix: Change checkout order to first step * CICD: Add context for docker build action * Fix: Change spring profile arg position * CICD: Change openjdk version to 17 * CICD: Change docker compose build image tag to latest * CICD: Change to use ghcr repository. * Fix: change list to string in docker push tags. * Fix: Change registry to ghcr.io * Fix: change env to pass to github action instead of ssh export command * Fix: unwrap bracket. * Fix: wrap Profile with "" * CICD: Add .env file * CICD: Change prod ddl-auto to create (for developing), and add TODO comment. * CICD: Remove cicd/deploy branch for condition. * feat: 구성원(교수) 수정 및 삭제 API (#9) * feat: 교수 조회시 최종학력이 앞으로 오게끔 정렬 * feat: 교수 수정 및 삭제 API * feat: 학력과 경력 엔티티 필드 변경, 수정 API 구현 * feat: 구성원(행정직원) CRUD API (#10) * feat: 행정직원 엔티티 및 DTO 설계 * feat: 행정직원 CRUD * feat: 교수 조회시 이름순 정렬 * fix: 교수 연구 분야 Set -> List 로 변경 * feat: 행정직원 주요업무 업데이트 구현 * feat: news 패키지 추가, 디벨롭 및 프론트에 맞게 엔티티 변경 (#12) * feat: news 패키지 생성 * feat: readNews 생성, news 패키지 추가로 인한 명칭 변경 * feat: createNews, enrollTag(새소식) 추가, news 패키지로 인한 명칭 추가 변경 * feat: updateNews, deleteNews 추가 * fix: searchNotice 관련 명칭 변경 * feat: searchNews 추가 * fix: develop 브랜치 반영, 프론트 요구사항 반영 * feat: readNotice, readNews에 이전글 다음글 추가 * 태그 업데이트 코드 리팩터링중 * refactor: 코드 수정, 이전제목 추가 * fix: 게시글 하나일때 read 가능 * fix: prevNext null 없애기 * fix: 이전글 다음글 null 수정 --------- * hotfix: 사용하지않는 Dto 및 엔티티 삭제 (#14) --------- * feat: seminar 패키지 추가 (#17) * feat: createSeminar, readSeminar, updateSeminar 추가 * feat: deleteSeminar, searchSeminar 추가 * fix: distinct 삭제 * hotfix: 불필요한 dto 삭제 (#20) * hotfix: 불필요한 dto 삭제 * build.gradle 수정 * fix: 이미지 uri 필드 추가 및 프론트 요구사항 반영 (#21) * feat: 이미지 uri 필드 추가 및 isActive 대신 status 추가 * fix: 행정직원 전체 조회 응답에서 task 삭제 * fix: 교수진 페이지 응답 수정 * feat: introduction 패키지, undergraduate 패키지 추가 (#22) * feat: createUndergraduate, readUndergraduate 추가 * feat: readAllCourses, createCourse, readCourse 추가 * introduction 패키지 추가 * fix: dto에서 postType 삭제 * fix: postType pathVariable->requestBody 수정, 오타 수정 * fix: 프론트와 협의하여 이름 등 변경 * feat: academics 패키지 대학원도 가능하도록 추가 * feat: 장학제도 세부 장학금 create, read 추가 * fix: 수정 * fix:수정 * feat: admissions, research 패키지 추가 (#23) * feat: admissions-학부 에서 create, read 추가 * feat: admissions-대학원도 작성 가능하도록 추가 * feat: createResearch 추가 * feat: createLab 추가 * fix: 다른 패키지에 맞게 수정 * research-groups, research-centers에서 read, update 추가 * fix: admissions, research에서 프론트와 협의하여 이름 등 수정 * fix: 오타 수정 * fix: enum 추가, pr 리뷰 반영 * feat: oidc 로그인 (#27) * feat: oidc 로그인 * feat: TaskEntity name 추가 * feat: 배포 설정 * feat: 유저 정보에 학번 추가, 로그인 시 sub claim 확인 * feat: 배포 테스트 위해 redirect-uri 변경 * fix: groups claim 소문자로 수정 * feat: idsnucse 다운 되었을때 에러 처리 * feat: idsnucse 다운 되었을때 에러 처리 * feat: cors 설정 (#30) * fix: cors 추가 설정 (#32) * feat: cors 설정 * feat: cors 설정 * fix: CORS (#34) * feat: cors 설정 * feat: cors 설정 * feat: cors 설정 * fix: about, academics, admissions 패키지 수정 (#25) * fix: admissions, about 컨트롤러 변경 * fix: academics 컨트롤러 수정 * 커밋중 * fix: pr 리뷰 반영 * feat: readMain 추가 * pr 리뷰 반영 * feat: 일반 예약 및 정기 예약 API (#28) * feat: oidc 로그인 * feat: TaskEntity name 추가 * feat: 배포 설정 * feat: 유저 정보에 학번 추가, 로그인 시 sub claim 확인 * feat: 배포 테스트 위해 redirect-uri 변경 * fix: groups claim 소문자로 수정 * feat: 예약 엔티티 및 DTO 설계 * feat: 일반 예약 및 정기 예약 * feat: 예약 조회 API (#39) * feat: 권한 관리 위해 커스텀 어노테이션 생성 * feat: 예약 단건, 주별, 월별 조회 API * fix: 로컬 DB 설정 수정 * feat: 유저 조회 중복 쿼리 방지 * feat: 예약 응답에 recurrenceId 추가 * feat: 동시 예약 방지 * fix: 시큐리티 로그 다시 로컬에서만 보이도록 변경 * feat: 목데이터 날라가지 않도록 ddl-auto 수정 * feat: about, member, news, seminar 메인 이미지 업로드 추가 (#38) * feat: uploadImage 추가 * feat: about, member 사진 업로드 추가 * feat: news, seminar 사진 업로드 추가 * refactor: imageEntity 추가 리팩토링 * fix: gif 삭제 * fix: application.yaml 수정 * fix: newsService 태그 -> 이미지로 순서 변경 * fix: pr 리뷰 수정 * fix: extension 없애고, mainImage로 바꾸고, uuid 없애기 * fix: var 삭제 --------- * CICD: Change deploy port to 8080 (#40) --------- Co-authored-by: 우혁준 (HyukJoon Woo) Co-authored-by: Jo Seonggyu From 7b78310a8491b0dc73f8d75f6b5313beb3987915 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Wed, 30 Aug 2023 12:12:31 +0900 Subject: [PATCH 029/124] =?UTF-8?q?feat:=20=EC=9E=A5=ED=95=99=EC=A0=9C?= =?UTF-8?q?=EB=8F=84=20GET=20API=20=EB=B0=8F=20=EC=9E=A5=ED=95=99=EC=A0=9C?= =?UTF-8?q?=EB=8F=84=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#44)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 장학제도 GET API * feat: 장학제도 메인페이지 GET API 응답 수정 --- .../core/academics/api/AcademicsController.kt | 13 +++++---- .../academics/dto/ScholarshipPageResponse.kt | 24 ++++++++++++++++ .../academics/service/AcademicsService.kt | 21 ++++++++------ .../scholarship/api/ScholarshipController.kt | 21 ++++++++++++++ .../scholarship/database/ScholarshipEntity.kt | 17 +++++++++++ .../database/ScholarshipRepository.kt | 6 ++++ .../core/scholarship/dto/ScholarshipDto.kt | 19 +++++++++++++ .../scholarship/dto/SimpleScholarshipDto.kt | 17 +++++++++++ .../scholarship/service/ScholarshipService.kt | 28 +++++++++++++++++++ 9 files changed, 152 insertions(+), 14 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/ScholarshipPageResponse.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/scholarship/api/ScholarshipController.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/scholarship/database/ScholarshipEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/scholarship/database/ScholarshipRepository.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/scholarship/dto/ScholarshipDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/scholarship/dto/SimpleScholarshipDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/scholarship/service/ScholarshipService.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt index c56e332b..e4682467 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt @@ -2,6 +2,7 @@ package com.wafflestudio.csereal.core.academics.api import com.wafflestudio.csereal.core.academics.dto.CourseDto import com.wafflestudio.csereal.core.academics.dto.AcademicsDto +import com.wafflestudio.csereal.core.academics.dto.ScholarshipPageResponse import com.wafflestudio.csereal.core.academics.service.AcademicsService import jakarta.validation.Valid import org.springframework.http.ResponseEntity @@ -25,7 +26,7 @@ class AcademicsController( @PathVariable studentType: String, @PathVariable postType: String, @Valid @RequestBody request: AcademicsDto - ) : ResponseEntity { + ): ResponseEntity { return ResponseEntity.ok(academicsService.createAcademics(studentType, postType, request)) } @@ -42,14 +43,14 @@ class AcademicsController( fun createCourse( @PathVariable studentType: String, @Valid @RequestBody request: CourseDto - ) : ResponseEntity { + ): ResponseEntity { return ResponseEntity.ok(academicsService.createCourse(studentType, request)) } @GetMapping("/{studentType}/courses") fun readAllCourses( @PathVariable studentType: String, - ) : ResponseEntity> { + ): ResponseEntity> { return ResponseEntity.ok(academicsService.readAllCourses(studentType)) } @@ -65,15 +66,15 @@ class AcademicsController( fun createScholarship( @PathVariable studentType: String, @Valid @RequestBody request: AcademicsDto - ) : ResponseEntity { + ): ResponseEntity { return ResponseEntity.ok(academicsService.createAcademics(studentType, "scholarship", request)) } @GetMapping("/scholarship") fun readScholarship( @RequestParam name: String - ) : ResponseEntity { + ): ResponseEntity { return ResponseEntity.ok(academicsService.readScholarship(name)) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/ScholarshipPageResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/ScholarshipPageResponse.kt new file mode 100644 index 00000000..1fe1e62f --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/ScholarshipPageResponse.kt @@ -0,0 +1,24 @@ +package com.wafflestudio.csereal.core.academics.dto + +import com.wafflestudio.csereal.core.academics.database.AcademicsEntity +import com.wafflestudio.csereal.core.scholarship.database.ScholarshipEntity +import com.wafflestudio.csereal.core.scholarship.dto.SimpleScholarshipDto +import java.time.LocalDateTime + +class ScholarshipPageResponse( + val id: Long, + val name: String, + val description: String, + val scholarships: List +) { + companion object { + fun of(scholarship: AcademicsEntity, scholarships: List): ScholarshipPageResponse { + return ScholarshipPageResponse( + id = scholarship.id, + name = scholarship.name, + description = scholarship.description, + scholarships = scholarships.map { SimpleScholarshipDto.of(it) } + ) + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt index 4a2b7609..f0ae37ff 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt @@ -5,6 +5,9 @@ import com.wafflestudio.csereal.core.about.database.AboutPostType import com.wafflestudio.csereal.core.academics.database.* import com.wafflestudio.csereal.core.academics.dto.CourseDto import com.wafflestudio.csereal.core.academics.dto.AcademicsDto +import com.wafflestudio.csereal.core.academics.dto.ScholarshipPageResponse +import com.wafflestudio.csereal.core.scholarship.database.ScholarshipRepository +import com.wafflestudio.csereal.core.scholarship.dto.SimpleScholarshipDto import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -14,13 +17,14 @@ interface AcademicsService { fun createCourse(studentType: String, request: CourseDto): CourseDto fun readAllCourses(studentType: String): List fun readCourse(name: String): CourseDto - fun readScholarship(name:String): AcademicsDto + fun readScholarship(name: String): ScholarshipPageResponse } @Service class AcademicsServiceImpl( private val academicsRepository: AcademicsRepository, private val courseRepository: CourseRepository, + private val scholarshipRepository: ScholarshipRepository ) : AcademicsService { @Transactional override fun createAcademics(studentType: String, postType: String, request: AcademicsDto): AcademicsDto { @@ -73,15 +77,16 @@ class AcademicsServiceImpl( } @Transactional(readOnly = true) - override fun readScholarship(name: String): AcademicsDto { + override fun readScholarship(name: String): ScholarshipPageResponse { val scholarship = academicsRepository.findByName(name) + val scholarships = scholarshipRepository.findAll() - return AcademicsDto.of(scholarship) + return ScholarshipPageResponse.of(scholarship, scholarships) } - private fun makeStringToAcademicsStudentType(postType: String) : AcademicsStudentType { + private fun makeStringToAcademicsStudentType(postType: String): AcademicsStudentType { try { - val upperPostType = postType.replace("-","_").uppercase() + val upperPostType = postType.replace("-", "_").uppercase() return AcademicsStudentType.valueOf(upperPostType) } catch (e: IllegalArgumentException) { @@ -89,13 +94,13 @@ class AcademicsServiceImpl( } } - private fun makeStringToAcademicsPostType(postType: String) : AcademicsPostType { + private fun makeStringToAcademicsPostType(postType: String): AcademicsPostType { try { - val upperPostType = postType.replace("-","_").uppercase() + val upperPostType = postType.replace("-", "_").uppercase() return AcademicsPostType.valueOf(upperPostType) } catch (e: IllegalArgumentException) { throw CserealException.Csereal400("해당하는 enum을 찾을 수 없습니다") } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/api/ScholarshipController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/api/ScholarshipController.kt new file mode 100644 index 00000000..4531d2f0 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/api/ScholarshipController.kt @@ -0,0 +1,21 @@ +package com.wafflestudio.csereal.core.scholarship.api + +import com.wafflestudio.csereal.core.scholarship.dto.ScholarshipDto +import com.wafflestudio.csereal.core.scholarship.service.ScholarshipService +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RequestMapping("/scholarship") +@RestController +class ScholarshipController( + private val scholarshipService: ScholarshipService +) { + + @GetMapping("/{scholarshipId}") + fun getScholarship(@PathVariable scholarshipId: Long): ResponseEntity { + return ResponseEntity.ok(scholarshipService.getScholarship(scholarshipId)) + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/database/ScholarshipEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/database/ScholarshipEntity.kt new file mode 100644 index 00000000..b4342372 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/database/ScholarshipEntity.kt @@ -0,0 +1,17 @@ +package com.wafflestudio.csereal.core.scholarship.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import jakarta.persistence.Column +import jakarta.persistence.Entity + +@Entity(name = "scholarship") +class ScholarshipEntity( + + val title: String, + + @Column(columnDefinition = "text") + val description: String + +) : BaseTimeEntity() { + +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/database/ScholarshipRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/database/ScholarshipRepository.kt new file mode 100644 index 00000000..21f6e63a --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/database/ScholarshipRepository.kt @@ -0,0 +1,6 @@ +package com.wafflestudio.csereal.core.scholarship.database + +import org.springframework.data.jpa.repository.JpaRepository + +interface ScholarshipRepository : JpaRepository { +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/dto/ScholarshipDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/dto/ScholarshipDto.kt new file mode 100644 index 00000000..ba4a2af0 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/dto/ScholarshipDto.kt @@ -0,0 +1,19 @@ +package com.wafflestudio.csereal.core.scholarship.dto + +import com.wafflestudio.csereal.core.scholarship.database.ScholarshipEntity + +data class ScholarshipDto( + val id: Long, + val title: String, + val description: String +) { + companion object { + fun of(scholarshipEntity: ScholarshipEntity): ScholarshipDto { + return ScholarshipDto( + id = scholarshipEntity.id, + title = scholarshipEntity.title, + description = scholarshipEntity.description + ) + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/dto/SimpleScholarshipDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/dto/SimpleScholarshipDto.kt new file mode 100644 index 00000000..b751b0b7 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/dto/SimpleScholarshipDto.kt @@ -0,0 +1,17 @@ +package com.wafflestudio.csereal.core.scholarship.dto + +import com.wafflestudio.csereal.core.scholarship.database.ScholarshipEntity + +data class SimpleScholarshipDto( + val id: Long, + val title: String +) { + companion object { + fun of(scholarshipEntity: ScholarshipEntity): SimpleScholarshipDto { + return SimpleScholarshipDto( + id = scholarshipEntity.id, + title = scholarshipEntity.title + ) + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/service/ScholarshipService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/service/ScholarshipService.kt new file mode 100644 index 00000000..c0c17182 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/service/ScholarshipService.kt @@ -0,0 +1,28 @@ +package com.wafflestudio.csereal.core.scholarship.service + +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.scholarship.database.ScholarshipRepository +import com.wafflestudio.csereal.core.scholarship.dto.ScholarshipDto +import com.wafflestudio.csereal.core.scholarship.dto.SimpleScholarshipDto +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +interface ScholarshipService { + fun getScholarship(scholarshipId: Long): ScholarshipDto +} + +@Service +@Transactional +class ScholarshipServiceImpl( + private val scholarshipRepository: ScholarshipRepository +) : ScholarshipService { + + @Transactional(readOnly = true) + override fun getScholarship(scholarshipId: Long): ScholarshipDto { + val scholarship = scholarshipRepository.findByIdOrNull(scholarshipId) + ?: throw CserealException.Csereal404("id: $scholarshipId 에 해당하는 장학제도를 찾을 수 없습니다") + return ScholarshipDto.of(scholarship) + } + +} From 233a8e6de9a85776e0ca9de7ee3a982abc15812a Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Wed, 30 Aug 2023 12:12:52 +0900 Subject: [PATCH 030/124] =?UTF-8?q?feat:=20custom=20metadata=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20+=20=EB=A6=AC=EB=8B=A4=EC=9D=B4=EB=A0=89=ED=8A=B8?= =?UTF-8?q?=20=EC=97=94=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: yml 파일에 custom metadata endpoint 추가 * feat: 로그인/로그아웃 성공 리다이렉트 엔드포인트 변경 --- build.gradle.kts | 85 ++++++++++--------- .../CustomAuthenticationSuccessHandler.kt | 6 +- .../csereal/common/config/SecurityConfig.kt | 12 ++- .../common/properties/EndpointProperties.kt | 10 +++ .../mainImage/service/MainImageService.kt | 20 +++-- src/main/resources/application.yaml | 24 ++++-- 6 files changed, 99 insertions(+), 58 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/common/properties/EndpointProperties.kt diff --git a/build.gradle.kts b/build.gradle.kts index 60751723..5ac2bc18 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,73 +1,76 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - id("org.springframework.boot") version "3.0.8" - id("io.spring.dependency-management") version "1.1.0" - kotlin("jvm") version "1.7.22" - kotlin("plugin.spring") version "1.7.22" - kotlin("plugin.jpa") version "1.7.22" - kotlin("kapt") version "1.7.10" + id("org.springframework.boot") version "3.0.8" + id("io.spring.dependency-management") version "1.1.0" + kotlin("jvm") version "1.7.22" + kotlin("plugin.spring") version "1.7.22" + kotlin("plugin.jpa") version "1.7.22" + kotlin("kapt") version "1.7.10" } group = "com.wafflestudio" version = "0.0.1-SNAPSHOT" java { - sourceCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_17 } repositories { - mavenCentral() + mavenCentral() } dependencies { - implementation("org.springframework.boot:spring-boot-starter-data-jpa") - implementation("org.springframework.boot:spring-boot-starter-oauth2-client") - implementation("org.springframework.boot:spring-boot-starter-security") - implementation("org.springframework.boot:spring-boot-starter-web") - implementation("org.springframework.boot:spring-boot-starter-validation") - implementation("com.fasterxml.jackson.module:jackson-module-kotlin") - implementation("org.jetbrains.kotlin:kotlin-reflect") - implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0") - runtimeOnly("com.mysql:mysql-connector-j") - testImplementation("org.springframework.boot:spring-boot-starter-test") - testImplementation("org.springframework.security:spring-security-test") + implementation("org.springframework.boot:spring-boot-starter-data-jpa") + implementation("org.springframework.boot:spring-boot-starter-oauth2-client") + implementation("org.springframework.boot:spring-boot-starter-security") + implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.springframework.boot:spring-boot-starter-validation") + implementation("com.fasterxml.jackson.module:jackson-module-kotlin") + implementation("org.jetbrains.kotlin:kotlin-reflect") + implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0") + runtimeOnly("com.mysql:mysql-connector-j") + testImplementation("org.springframework.boot:spring-boot-starter-test") + testImplementation("org.springframework.security:spring-security-test") - //queryDsl - implementation ("com.querydsl:querydsl-jpa:5.0.0:jakarta") - kapt("com.querydsl:querydsl-apt:5.0.0:jakarta") - kapt("jakarta.annotation:jakarta.annotation-api") - kapt("jakarta.persistence:jakarta.persistence-api") + //queryDsl + implementation("com.querydsl:querydsl-jpa:5.0.0:jakarta") + kapt("com.querydsl:querydsl-apt:5.0.0:jakarta") + kapt("jakarta.annotation:jakarta.annotation-api") + kapt("jakarta.persistence:jakarta.persistence-api") - // 태그 제거 - implementation("org.jsoup:jsoup:1.15.4") + // 태그 제거 + implementation("org.jsoup:jsoup:1.15.4") - // 이미지 업로드 - implementation("commons-io:commons-io:2.11.0") + // 이미지 업로드 + implementation("commons-io:commons-io:2.11.0") - // 썸네일 보여주기 - implementation("net.coobird:thumbnailator:0.4.19") + // 썸네일 보여주기 + implementation("net.coobird:thumbnailator:0.4.19") + + // Custom Metadata + annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") } noArg { - annotation("jakarta.persistence.Entity") - annotation("jakarta.persistence.MappedSuperclass") - annotation("jakarta.persistence.Embeddable") + annotation("jakarta.persistence.Entity") + annotation("jakarta.persistence.MappedSuperclass") + annotation("jakarta.persistence.Embeddable") } allOpen { - annotation("jakarta.persistence.Entity") - annotation("jakarta.persistence.MappedSuperclass") - annotation("jakarta.persistence.Embeddable") + annotation("jakarta.persistence.Entity") + annotation("jakarta.persistence.MappedSuperclass") + annotation("jakarta.persistence.Embeddable") } tasks.withType { - kotlinOptions { - freeCompilerArgs += "-Xjsr305=strict" - jvmTarget = "17" - } + kotlinOptions { + freeCompilerArgs += "-Xjsr305=strict" + jvmTarget = "17" + } } tasks.withType { - useJUnitPlatform() + useJUnitPlatform() } diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/CustomAuthenticationSuccessHandler.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/CustomAuthenticationSuccessHandler.kt index 6dfbf8c1..52d1d027 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/CustomAuthenticationSuccessHandler.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/CustomAuthenticationSuccessHandler.kt @@ -5,13 +5,15 @@ import jakarta.servlet.http.HttpServletResponse import org.springframework.security.core.Authentication import org.springframework.security.web.authentication.AuthenticationSuccessHandler -class CustomAuthenticationSuccessHandler : AuthenticationSuccessHandler { +class CustomAuthenticationSuccessHandler( + private val frontendEndpoint: String +) : AuthenticationSuccessHandler { override fun onAuthenticationSuccess( request: HttpServletRequest, response: HttpServletResponse, authentication: Authentication ) { - val redirectUrl = "http://cse-dev-waffle.bacchus.io:3000/" + val redirectUrl = "${frontendEndpoint}/login/success" response.sendRedirect(redirectUrl) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt index c19d0548..cb144cac 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt @@ -1,8 +1,10 @@ package com.wafflestudio.csereal.common.config +import com.wafflestudio.csereal.common.properties.EndpointProperties import com.wafflestudio.csereal.core.user.service.CustomOidcUserService import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse +import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.security.config.annotation.web.builders.HttpSecurity @@ -16,11 +18,12 @@ import org.springframework.web.cors.CorsConfigurationSource import org.springframework.web.cors.UrlBasedCorsConfigurationSource - @Configuration @EnableWebSecurity +@EnableConfigurationProperties(EndpointProperties::class) class SecurityConfig( - private val customOidcUserService: CustomOidcUserService + private val customOidcUserService: CustomOidcUserService, + private val endpointProperties: EndpointProperties ) { @Bean @@ -31,7 +34,7 @@ class SecurityConfig( .oauth2Login() .loginPage("/oauth2/authorization/idsnucse") .userInfoEndpoint().oidcUserService(customOidcUserService).and() - .successHandler(CustomAuthenticationSuccessHandler()).and() + .successHandler(CustomAuthenticationSuccessHandler(endpointProperties.frontend)).and() .logout() .logoutSuccessHandler(oidcLogoutSuccessHandler()) .invalidateHttpSession(true) @@ -51,7 +54,8 @@ class SecurityConfig( response: HttpServletResponse?, authentication: Authentication? ) { - super.setDefaultTargetUrl("http://cse-dev-waffle.bacchus.io:3000/") + val redirectUrl = "${endpointProperties.frontend}/logout/success" + super.setDefaultTargetUrl(redirectUrl) super.onLogoutSuccess(request, response, authentication) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/properties/EndpointProperties.kt b/src/main/kotlin/com/wafflestudio/csereal/common/properties/EndpointProperties.kt new file mode 100644 index 00000000..471c0270 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/common/properties/EndpointProperties.kt @@ -0,0 +1,10 @@ +package com.wafflestudio.csereal.common.properties + +import org.springframework.boot.context.properties.ConfigurationProperties + + +@ConfigurationProperties("endpoint") +data class EndpointProperties( + val frontend: String, + val backend: String +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt index ec75e77f..c6d769e5 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt @@ -2,6 +2,7 @@ package com.wafflestudio.csereal.core.resource.mainImage.service import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.common.controller.ContentEntityType +import com.wafflestudio.csereal.common.properties.EndpointProperties import com.wafflestudio.csereal.core.about.database.AboutEntity import com.wafflestudio.csereal.core.member.database.ProfessorEntity import com.wafflestudio.csereal.core.member.database.StaffEntity @@ -28,7 +29,8 @@ interface ImageService { contentEntityType: ContentEntityType, requestImage: MultipartFile, ): MainImageDto - fun createImageURL(image: MainImageEntity?) : String? + + fun createImageURL(image: MainImageEntity?): String? } @Service @@ -36,6 +38,7 @@ class ImageServiceImpl( private val imageRepository: MainImageRepository, @Value("\${csereal.upload.path}") private val path: String, + private val endpointProperties: EndpointProperties ) : ImageService { @Transactional @@ -47,7 +50,7 @@ class ImageServiceImpl( val extension = FilenameUtils.getExtension(requestImage.originalFilename) - if(!listOf("jpg", "jpeg", "png").contains(extension)) { + if (!listOf("jpg", "jpeg", "png").contains(extension)) { throw CserealException.Csereal400("파일의 형식은 jpg, jpeg, png 중 하나여야 합니다.") } @@ -86,9 +89,9 @@ class ImageServiceImpl( } @Transactional - override fun createImageURL(image: MainImageEntity?) : String? { - return if(image != null) { - "http://cse-dev-waffle.bacchus.io/image/${image.filename}" + override fun createImageURL(image: MainImageEntity?): String? { + return if (image != null) { + "${endpointProperties.backend}/image/${image.filename}" } else null } @@ -97,22 +100,27 @@ class ImageServiceImpl( is NewsEntity -> { contentEntity.mainImage = image } + is SeminarEntity -> { contentEntity.mainImage = image } + is AboutEntity -> { contentEntity.mainImage = image } + is ProfessorEntity -> { contentEntity.mainImage = image } + is StaffEntity -> { contentEntity.mainImage = image } + else -> { throw WrongMethodTypeException("해당하는 엔티티가 없습니다") } } } -} \ No newline at end of file +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 9990d0e5..e07ed799 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -11,7 +11,6 @@ spring: client-id: waffle-dev-local-testing client-secret: ${OIDC_CLIENT_SECRET_DEV} authorization-grant-type: authorization_code - redirect-uri: http://cse-dev-waffle.bacchus.io/login/oauth2/code/idsnucse scope: openid, profile, email provider: idsnucse: @@ -23,7 +22,6 @@ server: session: timeout: 7200 # 2시간 - springdoc: swagger-ui: path: index.html @@ -36,7 +34,6 @@ servlet: max-request-size: 100MB max-file-size: 10MB - --- spring: config.activate.on-profile: local @@ -49,6 +46,12 @@ spring: ddl-auto: update show-sql: true open-in-view: false + security: + oauth2: + client: + registration: + idsnucse: + redirect-uri: http://localhost:8080/login/oauth2/code/idsnucse logging.level: default: INFO @@ -59,6 +62,10 @@ logging.level: csereal: upload: path: /app/image/ + +endpoint: + backend: http://localhost:8080 + frontend: http://localhost:3000 --- spring: config.activate.on-profile: prod @@ -66,10 +73,17 @@ spring: hibernate: ddl-auto: update # TODO: change to validate (or none) when save actual data to server open-in-view: false - - + security: + oauth2: + client: + registration: + idsnucse: + redirect-uri: http://cse-dev-waffle.bacchus.io:8080/login/oauth2/code/idsnucse csereal: upload: path: /app/image/ +endpoint: + backend: http://cse-dev-waffle.bacchus.io:8080 + frontend: http://cse-dev-waffle.bacchus.io:3000 From b86741f6b4d7c18cbc4e8257901698335d264789 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Wed, 30 Aug 2023 22:18:46 +0900 Subject: [PATCH 031/124] =?UTF-8?q?feat:=20attachments=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20=EC=B6=94=EA=B0=80,=20news=EC=99=80=20semi?= =?UTF-8?q?nar=20request=EC=97=90=20attachments=20=EC=B6=94=EA=B0=80=20(#4?= =?UTF-8?q?3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: attachments 추가, news request에 attachments 추가 * feat: seminar request에 attachments 추가 * fix: mappedBy 추가 + bringAttachments null 없앰 * fix: 경로 분리 * fix: isDeleted false로 수정 --------- Co-authored-by: Junhyeong Kim --- .../controller/AttachmentContentEntityType.kt | 7 ++ ...ntityType.kt => ImageContentEntityType.kt} | 2 +- .../core/about/database/AboutEntity.kt | 4 +- .../core/member/database/ProfessorEntity.kt | 4 +- .../core/member/database/StaffEntity.kt | 4 +- .../csereal/core/news/api/NewsController.kt | 3 +- .../csereal/core/news/database/NewsEntity.kt | 11 +- .../csereal/core/news/dto/NewsDto.kt | 5 +- .../csereal/core/news/service/NewsService.kt | 20 ++-- .../attachment/api/AttachmentController.kt | 13 +++ .../attachment/database/AttachmentEntity.kt | 29 +++++ .../database/AttachmentRepository.kt | 8 ++ .../resource/attachment/dto/AttachmentDto.kt | 8 ++ .../attachment/dto/AttachmentResponse.kt | 8 ++ .../attachment/service/AttachmentService.kt | 100 ++++++++++++++++++ .../mainImage/database/MainImageEntity.kt | 2 +- .../mainImage/service/MainImageService.kt | 10 +- .../core/seminar/api/SeminarController.kt | 6 +- .../core/seminar/database/SeminarEntity.kt | 15 ++- .../csereal/core/seminar/dto/SeminarDto.kt | 5 +- .../core/seminar/service/SeminarService.kt | 21 ++-- src/main/resources/application.yaml | 13 ++- 22 files changed, 259 insertions(+), 39 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/common/controller/AttachmentContentEntityType.kt rename src/main/kotlin/com/wafflestudio/csereal/common/controller/{ContentEntityType.kt => ImageContentEntityType.kt} (83%) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/api/AttachmentController.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentRepository.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/dto/AttachmentDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/dto/AttachmentResponse.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/controller/AttachmentContentEntityType.kt b/src/main/kotlin/com/wafflestudio/csereal/common/controller/AttachmentContentEntityType.kt new file mode 100644 index 00000000..a2eae43a --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/common/controller/AttachmentContentEntityType.kt @@ -0,0 +1,7 @@ +package com.wafflestudio.csereal.common.controller + +import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity + +interface AttachmentContentEntityType { + fun bringAttachments(): List +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/controller/ContentEntityType.kt b/src/main/kotlin/com/wafflestudio/csereal/common/controller/ImageContentEntityType.kt similarity index 83% rename from src/main/kotlin/com/wafflestudio/csereal/common/controller/ContentEntityType.kt rename to src/main/kotlin/com/wafflestudio/csereal/common/controller/ImageContentEntityType.kt index 0631bd6e..8f58c860 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/controller/ContentEntityType.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/controller/ImageContentEntityType.kt @@ -2,6 +2,6 @@ package com.wafflestudio.csereal.common.controller import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity -interface ContentEntityType { +interface ImageContentEntityType { fun bringMainImage(): MainImageEntity? } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt index 55f23e6b..316de78a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt @@ -1,7 +1,7 @@ package com.wafflestudio.csereal.core.about.database import com.wafflestudio.csereal.common.config.BaseTimeEntity -import com.wafflestudio.csereal.common.controller.ContentEntityType +import com.wafflestudio.csereal.common.controller.ImageContentEntityType import com.wafflestudio.csereal.core.about.dto.AboutDto import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity import jakarta.persistence.* @@ -21,7 +21,7 @@ class AboutEntity( @OneToOne var mainImage: MainImageEntity? = null, -) : BaseTimeEntity(), ContentEntityType { +) : BaseTimeEntity(), ImageContentEntityType { override fun bringMainImage(): MainImageEntity? = mainImage companion object { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt index 15000bec..1414a0ae 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt @@ -1,7 +1,7 @@ package com.wafflestudio.csereal.core.member.database import com.wafflestudio.csereal.common.config.BaseTimeEntity -import com.wafflestudio.csereal.common.controller.ContentEntityType +import com.wafflestudio.csereal.common.controller.ImageContentEntityType import com.wafflestudio.csereal.core.member.dto.ProfessorDto import com.wafflestudio.csereal.core.research.database.LabEntity import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity @@ -44,7 +44,7 @@ class ProfessorEntity( @OneToOne var mainImage: MainImageEntity? = null, -) : BaseTimeEntity(), ContentEntityType { +) : BaseTimeEntity(), ImageContentEntityType { override fun bringMainImage(): MainImageEntity? = mainImage companion object { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffEntity.kt index abfafbe0..d4e5ec3a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffEntity.kt @@ -1,7 +1,7 @@ package com.wafflestudio.csereal.core.member.database import com.wafflestudio.csereal.common.config.BaseTimeEntity -import com.wafflestudio.csereal.common.controller.ContentEntityType +import com.wafflestudio.csereal.common.controller.ImageContentEntityType import com.wafflestudio.csereal.core.member.dto.StaffDto import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity import jakarta.persistence.CascadeType @@ -24,7 +24,7 @@ class StaffEntity( @OneToOne var mainImage: MainImageEntity? = null, - ) : BaseTimeEntity(), ContentEntityType { + ) : BaseTimeEntity(), ImageContentEntityType { override fun bringMainImage(): MainImageEntity? = mainImage companion object { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt index 62fd590b..25c2dc57 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt @@ -35,8 +35,9 @@ class NewsController( fun createNews( @Valid @RequestPart("request") request: NewsDto, @RequestPart("image") image: MultipartFile?, + @RequestPart("attachments") attachments: List? ) : ResponseEntity { - return ResponseEntity.ok(newsService.createNews(request,image)) + return ResponseEntity.ok(newsService.createNews(request,image, attachments)) } @PatchMapping("/{newsId}") diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt index 541f0d34..4aba2d75 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt @@ -1,8 +1,10 @@ package com.wafflestudio.csereal.core.news.database import com.wafflestudio.csereal.common.config.BaseTimeEntity -import com.wafflestudio.csereal.common.controller.ContentEntityType +import com.wafflestudio.csereal.common.controller.AttachmentContentEntityType +import com.wafflestudio.csereal.common.controller.ImageContentEntityType import com.wafflestudio.csereal.core.news.dto.NewsDto +import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity import jakarta.persistence.* @@ -25,11 +27,16 @@ class NewsEntity( @OneToOne var mainImage: MainImageEntity? = null, + @OneToMany(mappedBy = "news", cascade = [CascadeType.ALL], orphanRemoval = true) + var attachments: MutableList = mutableListOf(), + @OneToMany(mappedBy = "news", cascade = [CascadeType.ALL]) var newsTags: MutableSet = mutableSetOf() -): BaseTimeEntity(), ContentEntityType { +): BaseTimeEntity(), ImageContentEntityType, AttachmentContentEntityType { override fun bringMainImage() = mainImage + override fun bringAttachments() = attachments + companion object { fun of(newsDto: NewsDto): NewsEntity { return NewsEntity( diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt index effec738..2d7189b7 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt @@ -1,6 +1,7 @@ package com.wafflestudio.csereal.core.news.dto import com.wafflestudio.csereal.core.news.database.NewsEntity +import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse import java.time.LocalDateTime data class NewsDto( @@ -18,9 +19,10 @@ data class NewsDto( val nextId: Long?, val nextTitle: String?, val imageURL: String?, + val attachments: List?, ) { companion object { - fun of(entity: NewsEntity, imageURL: String?, prevNext: Array?) : NewsDto = entity.run { + fun of(entity: NewsEntity, imageURL: String?, attachments: List?, prevNext: Array?) : NewsDto = entity.run { NewsDto( id = this.id, title = this.title, @@ -36,6 +38,7 @@ data class NewsDto( nextId = prevNext?.get(1)?.id, nextTitle = prevNext?.get(1)?.title, imageURL = imageURL, + attachments = attachments, ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt index fb07aada..b5a53ce1 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt @@ -4,6 +4,7 @@ import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.core.news.database.* import com.wafflestudio.csereal.core.news.dto.NewsDto import com.wafflestudio.csereal.core.news.dto.NewsSearchResponse +import com.wafflestudio.csereal.core.resource.attachment.service.AttachmentService import com.wafflestudio.csereal.core.resource.mainImage.service.ImageService import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service @@ -13,7 +14,7 @@ import org.springframework.web.multipart.MultipartFile interface NewsService { fun searchNews(tag: List?, keyword: String?, pageNum: Long): NewsSearchResponse fun readNews(newsId: Long, tag: List?, keyword: String?): NewsDto - fun createNews(request: NewsDto, image: MultipartFile?): NewsDto + fun createNews(request: NewsDto, image: MultipartFile?, attachments: List?): NewsDto fun updateNews(newsId: Long, request: NewsDto): NewsDto fun deleteNews(newsId: Long) fun enrollTag(tagName: String) @@ -25,6 +26,7 @@ class NewsServiceImpl( private val tagInNewsRepository: TagInNewsRepository, private val newsTagRepository: NewsTagRepository, private val imageService: ImageService, + private val attachmentService: AttachmentService, ) : NewsService { @Transactional(readOnly = true) override fun searchNews( @@ -47,15 +49,16 @@ class NewsServiceImpl( if (news.isDeleted) throw CserealException.Csereal404("삭제된 새소식입니다.(newsId: $newsId)") val imageURL = imageService.createImageURL(news.mainImage) + val attachments = attachmentService.createAttachments(news.attachments) val prevNext = newsRepository.findPrevNextId(newsId, tag, keyword) ?: throw CserealException.Csereal400("이전글 다음글이 존재하지 않습니다.(newsId=$newsId)") - return NewsDto.of(news, imageURL, prevNext) + return NewsDto.of(news, imageURL, attachments, prevNext) } @Transactional - override fun createNews(request: NewsDto, image: MultipartFile?): NewsDto { + override fun createNews(request: NewsDto, image: MultipartFile?, attachments: List?): NewsDto { val newNews = NewsEntity.of(request) for (tagName in request.tags) { @@ -67,11 +70,16 @@ class NewsServiceImpl( imageService.uploadImage(newNews, image) } + if(attachments != null) { + attachmentService.uploadAttachments(newNews, attachments) + } + newsRepository.save(newNews) val imageURL = imageService.createImageURL(newNews.mainImage) + val attachments = attachmentService.createAttachments(newNews.attachments) - return NewsDto.of(newNews, imageURL, null) + return NewsDto.of(newNews, imageURL, attachments, null) } @Transactional @@ -98,9 +106,9 @@ class NewsServiceImpl( } val imageURL = imageService.createImageURL(news.mainImage) + val attachments = attachmentService.createAttachments(news.attachments) - - return NewsDto.of(news, imageURL, null) + return NewsDto.of(news, imageURL, attachments, null) } @Transactional diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/api/AttachmentController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/api/AttachmentController.kt new file mode 100644 index 00000000..1d011b39 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/api/AttachmentController.kt @@ -0,0 +1,13 @@ +package com.wafflestudio.csereal.core.resource.attachment.api + +import com.wafflestudio.csereal.core.resource.attachment.service.AttachmentService +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RequestMapping("/attachment") +@RestController +class AttachmentController( + private val attachmentService: AttachmentService +) { + +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentEntity.kt new file mode 100644 index 00000000..2a31daa0 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentEntity.kt @@ -0,0 +1,29 @@ +package com.wafflestudio.csereal.core.resource.attachment.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.core.news.database.NewsEntity +import com.wafflestudio.csereal.core.notice.database.TagInNoticeEntity +import com.wafflestudio.csereal.core.seminar.database.SeminarEntity +import jakarta.persistence.* + +@Entity(name = "attachment") +class AttachmentEntity( + val isDeleted : Boolean? = false, + + @Column(unique = true) + val filename: String, + + val attachmentsOrder: Int, + val size: Long, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "news_id") + var news: NewsEntity? = null, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "seminar_id") + var seminar: SeminarEntity? = null, + + ) : BaseTimeEntity() { + +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentRepository.kt new file mode 100644 index 00000000..4ccfcaed --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentRepository.kt @@ -0,0 +1,8 @@ +package com.wafflestudio.csereal.core.resource.attachment.database + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface AttachmentRepository: JpaRepository { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/dto/AttachmentDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/dto/AttachmentDto.kt new file mode 100644 index 00000000..1d07c47f --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/dto/AttachmentDto.kt @@ -0,0 +1,8 @@ +package com.wafflestudio.csereal.core.resource.attachment.dto + +data class AttachmentDto( + val filename: String, + val attachmentsOrder: Int, + val size: Long, +) { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/dto/AttachmentResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/dto/AttachmentResponse.kt new file mode 100644 index 00000000..dd226bd2 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/dto/AttachmentResponse.kt @@ -0,0 +1,8 @@ +package com.wafflestudio.csereal.core.resource.attachment.dto + +data class AttachmentResponse( + val name: String, + val url: String, + val bytes: Long, +) { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt new file mode 100644 index 00000000..7361a572 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt @@ -0,0 +1,100 @@ +package com.wafflestudio.csereal.core.resource.attachment.service + +import com.wafflestudio.csereal.common.controller.AttachmentContentEntityType +import com.wafflestudio.csereal.core.news.database.NewsEntity +import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity +import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentRepository +import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentDto +import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse +import com.wafflestudio.csereal.core.seminar.database.SeminarEntity +import org.apache.commons.io.FilenameUtils +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import org.springframework.web.multipart.MultipartFile +import java.nio.file.Files +import java.nio.file.Paths + +interface AttachmentService { + fun uploadAttachments( + contentEntityType: AttachmentContentEntityType, + requestAttachments: List, + ): List + + fun createAttachments(attachments: List?): List? +} + +@Service +class AttachmentServiceImpl( + private val attachmentRepository: AttachmentRepository, + @Value("\${csereal_attachment.upload.path}") + private val path: String, +) : AttachmentService { + @Transactional + override fun uploadAttachments( + contentEntity: AttachmentContentEntityType, + requestAttachments: List, + ): List { + Files.createDirectories(Paths.get(path)) + + val attachmentsList = mutableListOf() + + for ((index, requestAttachment) in requestAttachments.withIndex()) { + val extension = FilenameUtils.getExtension(requestAttachment.originalFilename) + + val timeMillis = System.currentTimeMillis() + + val filename = "${timeMillis}_${requestAttachment.originalFilename}" + val totalFilename = path + filename + val saveFile = Paths.get("$totalFilename.$extension") + requestAttachment.transferTo(saveFile) + + val attachment = AttachmentEntity( + filename = filename, + attachmentsOrder = index + 1, + size = requestAttachment.size, + ) + + connectAttachmentToEntity(contentEntity, attachment) + attachmentRepository.save(attachment) + + attachmentsList.add( + AttachmentDto( + filename = filename, + attachmentsOrder = index + 1, + size = requestAttachment.size + ) + ) + } + return attachmentsList + } + + @Transactional + override fun createAttachments(attachments: List?): List? { + val list = mutableListOf() + if (attachments != null) { + for (attachment in attachments) { + val attachmentDto = AttachmentResponse( + name = attachment.filename, + url = "http://cse-dev-waffle.bacchus.io/attachment/${attachment.filename}", + bytes = attachment.size, + ) + list.add(attachmentDto) + } + } + return list + } + + private fun connectAttachmentToEntity(contentEntity: AttachmentContentEntityType, attachment: AttachmentEntity) { + when (contentEntity) { + is NewsEntity -> { + contentEntity.attachments.add(attachment) + attachment.news = contentEntity + } + is SeminarEntity -> { + contentEntity.attachments.add(attachment) + attachment.seminar = contentEntity + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/database/MainImageEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/database/MainImageEntity.kt index 28273401..6575d80f 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/database/MainImageEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/database/MainImageEntity.kt @@ -6,7 +6,7 @@ import jakarta.persistence.* @Entity(name = "mainImage") class MainImageEntity( - val isDeleted : Boolean? = true, + val isDeleted : Boolean? = false, @Column(unique = true) val filename: String, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt index c6d769e5..349d3581 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt @@ -1,7 +1,7 @@ package com.wafflestudio.csereal.core.resource.mainImage.service import com.wafflestudio.csereal.common.CserealException -import com.wafflestudio.csereal.common.controller.ContentEntityType +import com.wafflestudio.csereal.common.controller.ImageContentEntityType import com.wafflestudio.csereal.common.properties.EndpointProperties import com.wafflestudio.csereal.core.about.database.AboutEntity import com.wafflestudio.csereal.core.member.database.ProfessorEntity @@ -26,7 +26,7 @@ import kotlin.io.path.name interface ImageService { fun uploadImage( - contentEntityType: ContentEntityType, + contentEntityType: ImageContentEntityType, requestImage: MultipartFile, ): MainImageDto @@ -36,14 +36,14 @@ interface ImageService { @Service class ImageServiceImpl( private val imageRepository: MainImageRepository, - @Value("\${csereal.upload.path}") + @Value("\${csereal_image.upload.path}") private val path: String, private val endpointProperties: EndpointProperties ) : ImageService { @Transactional override fun uploadImage( - contentEntity: ContentEntityType, + contentEntity: ImageContentEntityType, requestImage: MultipartFile, ): MainImageDto { Files.createDirectories(Paths.get(path)) @@ -95,7 +95,7 @@ class ImageServiceImpl( } else null } - private fun connectImageToEntity(contentEntity: ContentEntityType, image: MainImageEntity) { + private fun connectImageToEntity(contentEntity: ImageContentEntityType, image: MainImageEntity) { when (contentEntity) { is NewsEntity -> { contentEntity.mainImage = image diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt index f46cca7f..31dfdd8a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt @@ -23,9 +23,11 @@ class SeminarController ( @PostMapping fun createSeminar( @Valid @RequestPart("request") request: SeminarDto, - @RequestPart("image") image: MultipartFile? + @RequestPart("image") image: MultipartFile?, + @RequestPart("attachments") attachments: List? ) : ResponseEntity { - return ResponseEntity.ok(seminarService.createSeminar(request,image)) } + return ResponseEntity.ok(seminarService.createSeminar(request, image, attachments)) + } @GetMapping("/{seminarId}") fun readSeminar( diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt index e7b48fe0..4489375a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt @@ -1,12 +1,12 @@ package com.wafflestudio.csereal.core.seminar.database import com.wafflestudio.csereal.common.config.BaseTimeEntity -import com.wafflestudio.csereal.common.controller.ContentEntityType +import com.wafflestudio.csereal.common.controller.AttachmentContentEntityType +import com.wafflestudio.csereal.common.controller.ImageContentEntityType +import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity import com.wafflestudio.csereal.core.seminar.dto.SeminarDto -import jakarta.persistence.Column -import jakarta.persistence.Entity -import jakarta.persistence.OneToOne +import jakarta.persistence.* @Entity(name = "seminar") class SeminarEntity( @@ -49,8 +49,13 @@ class SeminarEntity( @OneToOne var mainImage: MainImageEntity? = null, -): BaseTimeEntity(), ContentEntityType { + + @OneToMany(mappedBy = "seminar", cascade = [CascadeType.ALL], orphanRemoval = true) + var attachments: MutableList = mutableListOf(), + + ): BaseTimeEntity(), ImageContentEntityType, AttachmentContentEntityType { override fun bringMainImage(): MainImageEntity? = mainImage + override fun bringAttachments() = attachments companion object { fun of(seminarDto: SeminarDto): SeminarEntity { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt index 2eda7a96..42c2307f 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt @@ -1,5 +1,6 @@ package com.wafflestudio.csereal.core.seminar.dto +import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse import com.wafflestudio.csereal.core.seminar.database.SeminarEntity data class SeminarDto( @@ -28,10 +29,11 @@ data class SeminarDto( val nextId: Long?, val nextTitle: String?, val imageURL: String?, + val attachments: List?, ) { companion object { - fun of(entity: SeminarEntity, imageURL: String?, prevNext: Array?): SeminarDto = entity.run { + fun of(entity: SeminarEntity, imageURL: String?, attachments: List?, prevNext: Array?): SeminarDto = entity.run { SeminarDto( id = this.id, title = this.title, @@ -57,6 +59,7 @@ data class SeminarDto( nextId = prevNext?.get(1)?.id, nextTitle = prevNext?.get(1)?.title, imageURL = imageURL, + attachments = attachments, ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt index 274a178e..e1a40f94 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt @@ -1,6 +1,7 @@ package com.wafflestudio.csereal.core.seminar.service import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.resource.attachment.service.AttachmentService import com.wafflestudio.csereal.core.resource.mainImage.service.ImageService import com.wafflestudio.csereal.core.seminar.database.SeminarEntity import com.wafflestudio.csereal.core.seminar.database.SeminarRepository @@ -13,7 +14,7 @@ import org.springframework.web.multipart.MultipartFile interface SeminarService { fun searchSeminar(keyword: String?, pageNum: Long): SeminarSearchResponse - fun createSeminar(request: SeminarDto, image: MultipartFile?): SeminarDto + fun createSeminar(request: SeminarDto, image: MultipartFile?, attachments: List?): SeminarDto fun readSeminar(seminarId: Long, keyword: String?): SeminarDto fun updateSeminar(seminarId: Long, request: SeminarDto): SeminarDto fun deleteSeminar(seminarId: Long) @@ -23,6 +24,7 @@ interface SeminarService { class SeminarServiceImpl( private val seminarRepository: SeminarRepository, private val imageService: ImageService, + private val attachmentService: AttachmentService, ) : SeminarService { @Transactional(readOnly = true) override fun searchSeminar(keyword: String?, pageNum: Long): SeminarSearchResponse { @@ -30,19 +32,23 @@ class SeminarServiceImpl( } @Transactional - override fun createSeminar(request: SeminarDto, image: MultipartFile?): SeminarDto { + override fun createSeminar(request: SeminarDto, image: MultipartFile?, attachments: List?): SeminarDto { val newSeminar = SeminarEntity.of(request) if(image != null) { imageService.uploadImage(newSeminar, image) } + if(attachments != null) { + attachmentService.uploadAttachments(newSeminar, attachments) + } + seminarRepository.save(newSeminar) val imageURL = imageService.createImageURL(newSeminar.mainImage) + val attachments = attachmentService.createAttachments(newSeminar.attachments) - - return SeminarDto.of(newSeminar, imageURL, null) + return SeminarDto.of(newSeminar, imageURL, attachments, null) } @Transactional(readOnly = true) @@ -53,10 +59,11 @@ class SeminarServiceImpl( if (seminar.isDeleted) throw CserealException.Csereal400("삭제된 세미나입니다. (seminarId: $seminarId)") val imageURL = imageService.createImageURL(seminar.mainImage) + val attachments = attachmentService.createAttachments(seminar.attachments) val prevNext = seminarRepository.findPrevNextId(seminarId, keyword) - return SeminarDto.of(seminar, imageURL, prevNext) + return SeminarDto.of(seminar, imageURL, attachments, prevNext) } @Transactional @@ -68,8 +75,10 @@ class SeminarServiceImpl( seminar.update(request) val imageURL = imageService.createImageURL(seminar.mainImage) + val attachments = attachmentService.createAttachments(seminar.attachments) + - return SeminarDto.of(seminar, imageURL, null) + return SeminarDto.of(seminar, imageURL, attachments, null) } @Transactional override fun deleteSeminar(seminarId: Long) { diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index e07ed799..ff78a879 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -59,13 +59,18 @@ logging.level: springframework: security: DEBUG -csereal: +csereal_image: upload: path: /app/image/ +csereal_attachment: + upload: + path: /app/attachment/ + endpoint: backend: http://localhost:8080 frontend: http://localhost:3000 + --- spring: config.activate.on-profile: prod @@ -80,10 +85,14 @@ spring: idsnucse: redirect-uri: http://cse-dev-waffle.bacchus.io:8080/login/oauth2/code/idsnucse -csereal: +csereal_image: upload: path: /app/image/ +csereal_attachment: + upload: + path: /app/attachment/ + endpoint: backend: http://cse-dev-waffle.bacchus.io:8080 frontend: http://cse-dev-waffle.bacchus.io:3000 From fb5193fa5dc71848f86ac6e1a7df7f96c9c50aa2 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Wed, 30 Aug 2023 22:55:33 +0900 Subject: [PATCH 032/124] feat: Top Conference List GET API (#47) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Top Conference List GET API * feat: db에서 가져올때 정렬 --- .../conference/api/ConferenceController.kt | 21 +++++++++++++++ .../conference/database/ConferenceEntity.kt | 19 ++++++++++++++ .../database/ConferencePageEntity.kt | 18 +++++++++++++ .../database/ConferencePageRepository.kt | 6 +++++ .../core/conference/dto/ConferenceDto.kt | 19 ++++++++++++++ .../core/conference/dto/ConferencePage.kt | 22 ++++++++++++++++ .../conference/service/ConferenceService.kt | 26 +++++++++++++++++++ 7 files changed, 131 insertions(+) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/conference/api/ConferenceController.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferenceEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferencePageEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferencePageRepository.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferencePage.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/conference/service/ConferenceService.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/conference/api/ConferenceController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/conference/api/ConferenceController.kt new file mode 100644 index 00000000..7c78df5d --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/conference/api/ConferenceController.kt @@ -0,0 +1,21 @@ +package com.wafflestudio.csereal.core.conference.api + +import com.wafflestudio.csereal.core.conference.dto.ConferencePage +import com.wafflestudio.csereal.core.conference.service.ConferenceService +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RequestMapping("/conference") +@RestController +class ConferenceController( + private val conferenceService: ConferenceService +) { + + @GetMapping("/page") + fun getConferencePage(): ResponseEntity { + return ResponseEntity.ok(conferenceService.getConferencePage()) + } + +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferenceEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferenceEntity.kt new file mode 100644 index 00000000..9c811d3d --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferenceEntity.kt @@ -0,0 +1,19 @@ +package com.wafflestudio.csereal.core.conference.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import jakarta.persistence.Entity +import jakarta.persistence.FetchType +import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne + +@Entity(name = "conference") +class ConferenceEntity( + val code: String, + val abbreviation: String, + val name: String, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "conference_page_id") + val conferencePage: ConferencePageEntity + +) : BaseTimeEntity() diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferencePageEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferencePageEntity.kt new file mode 100644 index 00000000..4fdc2e14 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferencePageEntity.kt @@ -0,0 +1,18 @@ +package com.wafflestudio.csereal.core.conference.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.core.user.database.UserEntity +import jakarta.persistence.* + +@Entity(name = "conferencePage") +class ConferencePageEntity( + + @OneToOne + @JoinColumn(name = "author_id") + val author: UserEntity, + + @OneToMany(mappedBy = "conferencePage") + @OrderBy("code ASC") + val conferences: List = mutableListOf() + +) : BaseTimeEntity() diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferencePageRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferencePageRepository.kt new file mode 100644 index 00000000..43416e17 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferencePageRepository.kt @@ -0,0 +1,6 @@ +package com.wafflestudio.csereal.core.conference.database + +import org.springframework.data.jpa.repository.JpaRepository + +interface ConferencePageRepository : JpaRepository { +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceDto.kt new file mode 100644 index 00000000..03178adc --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceDto.kt @@ -0,0 +1,19 @@ +package com.wafflestudio.csereal.core.conference.dto + +import com.wafflestudio.csereal.core.conference.database.ConferenceEntity + +data class ConferenceDto( + val code: String, + val abbreviation: String, + val name: String +) { + companion object { + fun of(conferenceEntity: ConferenceEntity): ConferenceDto { + return ConferenceDto( + code = conferenceEntity.code, + abbreviation = conferenceEntity.abbreviation, + name = conferenceEntity.name + ) + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferencePage.kt b/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferencePage.kt new file mode 100644 index 00000000..dfb38fb4 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferencePage.kt @@ -0,0 +1,22 @@ +package com.wafflestudio.csereal.core.conference.dto + +import com.wafflestudio.csereal.core.conference.database.ConferencePageEntity +import java.time.LocalDateTime + +data class ConferencePage( + val createdAt: LocalDateTime, + val modifiedAt: LocalDateTime, + val author: String, + val conferenceList: List +) { + companion object { + fun of(conferencePageEntity: ConferencePageEntity): ConferencePage { + return ConferencePage( + createdAt = conferencePageEntity.createdAt!!, + modifiedAt = conferencePageEntity.modifiedAt!!, + author = conferencePageEntity.author.name, + conferenceList = conferencePageEntity.conferences.map { ConferenceDto.of(it) } + ) + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/conference/service/ConferenceService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/conference/service/ConferenceService.kt new file mode 100644 index 00000000..2bd56077 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/conference/service/ConferenceService.kt @@ -0,0 +1,26 @@ +package com.wafflestudio.csereal.core.conference.service + +import com.wafflestudio.csereal.core.conference.database.ConferencePageRepository +import com.wafflestudio.csereal.core.conference.dto.ConferenceDto +import com.wafflestudio.csereal.core.conference.dto.ConferencePage +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + + +interface ConferenceService { + fun getConferencePage(): ConferencePage +} + +@Service +@Transactional +class ConferenceServiceImpl( + private val conferencePageRepository: ConferencePageRepository +) : ConferenceService { + + @Transactional(readOnly = true) + override fun getConferencePage(): ConferencePage { + val conferencePage = conferencePageRepository.findAll()[0] + return ConferencePage.of(conferencePage) + } + +} From d479520703e854f9d42533953a687e1891174ed0 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Thu, 31 Aug 2023 09:24:55 +0900 Subject: [PATCH 033/124] =?UTF-8?q?feat:=20=ED=8C=8C=EC=9D=BC=20=EC=84=9C?= =?UTF-8?q?=EB=B9=99,=20=EB=8B=A4=EC=9A=B4=EB=A1=9C=EB=93=9C,=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20API=20(#48)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 파일 서빙, 다운로드, 삭제 API * fix: extension 중복, 경로 수정 --- .../resource/mainImage/api/FileController.kt | 68 +++++++++++++++++++ .../mainImage/api/MainImageController.kt | 13 ---- .../mainImage/service/MainImageService.kt | 6 +- 3 files changed, 71 insertions(+), 16 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/FileController.kt delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/MainImageController.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/FileController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/FileController.kt new file mode 100644 index 00000000..57e372d4 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/FileController.kt @@ -0,0 +1,68 @@ +package com.wafflestudio.csereal.core.resource.mainImage.api + +import jakarta.servlet.http.HttpServletRequest +import org.springframework.beans.factory.annotation.Value +import org.springframework.core.io.Resource +import org.springframework.core.io.UrlResource +import org.springframework.http.HttpHeaders +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* +import java.net.URLEncoder +import java.nio.file.Files +import java.nio.file.Paths +import kotlin.text.Charsets.UTF_8 + + +@RequestMapping("/file") +@RestController +class FileController( + @Value("\${csereal.upload.path}") + private val uploadPath: String +) { + + @GetMapping("/{filename:.+}") + fun serveFile( + @PathVariable filename: String, + @RequestParam(defaultValue = "false") download: Boolean, + request: HttpServletRequest + ): ResponseEntity { + val file = Paths.get(uploadPath, filename) + val resource = UrlResource(file.toUri()) + + if (resource.exists() || resource.isReadable) { + val contentType: String? = request.servletContext.getMimeType(resource.file.absolutePath) + val headers = HttpHeaders() + + headers.contentType = + org.springframework.http.MediaType.parseMediaType(contentType ?: "application/octet-stream") + + if (download) { + val originalFilename = filename.substringAfter("_") + + val encodedFilename = URLEncoder.encode(originalFilename, UTF_8.toString()).replace("+", "%20") + + headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''$encodedFilename") + } + + return ResponseEntity.ok() + .headers(headers) + .body(resource) + } else { + return ResponseEntity.status(HttpStatus.NOT_FOUND).build() + } + } + + @DeleteMapping("/{filename:.+}") + fun deleteFile(@PathVariable filename: String): ResponseEntity { + val file = Paths.get(uploadPath, filename) + + if (Files.exists(file)) { + Files.delete(file) + return ResponseEntity.status(HttpStatus.NO_CONTENT).build() + } else { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body("파일을 찾을 수 없습니다.") + } + } + +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/MainImageController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/MainImageController.kt deleted file mode 100644 index e74a26da..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/MainImageController.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.wafflestudio.csereal.core.resource.mainImage.api - -import com.wafflestudio.csereal.core.resource.mainImage.service.ImageService -import org.springframework.web.bind.annotation.* - - -@RequestMapping("/image") -@RestController -class MainImageController( - private val imageService: ImageService -) { - -} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt index 349d3581..da986275 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt @@ -58,11 +58,11 @@ class ImageServiceImpl( val filename = "${timeMillis}_${requestImage.originalFilename}" val totalFilename = path + filename - val saveFile = Paths.get("$totalFilename.$extension") + val saveFile = Paths.get(totalFilename) requestImage.transferTo(saveFile) val totalThumbnailFilename = "${path}thumbnail_$filename" - val thumbnailFile = Paths.get("$totalThumbnailFilename.$extension") + val thumbnailFile = Paths.get(totalThumbnailFilename) Thumbnailator.createThumbnail(saveFile.toFile(), thumbnailFile.toFile(), 100, 100); val image = MainImageEntity( @@ -91,7 +91,7 @@ class ImageServiceImpl( @Transactional override fun createImageURL(image: MainImageEntity?): String? { return if (image != null) { - "${endpointProperties.backend}/image/${image.filename}" + "${endpointProperties.backend}/file/${image.filename}" } else null } From 3b5d67a0c1b8393c4db05396203e0cfb3d2fa6d8 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Thu, 31 Aug 2023 09:25:23 +0900 Subject: [PATCH 034/124] =?UTF-8?q?feat:=20=EA=B3=B5=EC=A7=80=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EA=B8=80=EC=93=B4=EC=9D=B4=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?(#49)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/notice/api/NoticeController.kt | 19 ++++++---- .../core/notice/database/NoticeEntity.kt | 16 ++++---- .../csereal/core/notice/dto/NoticeDto.kt | 5 ++- .../core/notice/service/NoticeService.kt | 38 ++++++++++++++----- 4 files changed, 51 insertions(+), 27 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt index cb7919bf..4cb440ee 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt @@ -1,5 +1,6 @@ package com.wafflestudio.csereal.core.notice.api +import com.wafflestudio.csereal.common.aop.AuthenticatedStaff import com.wafflestudio.csereal.core.notice.dto.* import com.wafflestudio.csereal.core.notice.service.NoticeService import jakarta.validation.Valid @@ -14,25 +15,27 @@ class NoticeController( ) { @GetMapping fun searchNotice( - @RequestParam(required = false) tag : List?, + @RequestParam(required = false) tag: List?, @RequestParam(required = false) keyword: String?, @RequestParam(required = false, defaultValue = "0") pageNum: Long ): ResponseEntity { return ResponseEntity.ok(noticeService.searchNotice(tag, keyword, pageNum)) } + @GetMapping("/{noticeId}") fun readNotice( @PathVariable noticeId: Long, - @RequestParam(required = false) tag : List?, + @RequestParam(required = false) tag: List?, @RequestParam(required = false) keyword: String?, - ) : ResponseEntity { - return ResponseEntity.ok(noticeService.readNotice(noticeId,tag,keyword)) + ): ResponseEntity { + return ResponseEntity.ok(noticeService.readNotice(noticeId, tag, keyword)) } + @AuthenticatedStaff @PostMapping fun createNotice( @Valid @RequestBody request: NoticeDto - ) : ResponseEntity { + ): ResponseEntity { return ResponseEntity.ok(noticeService.createNotice(request)) } @@ -40,7 +43,7 @@ class NoticeController( fun updateNotice( @PathVariable noticeId: Long, @Valid @RequestBody request: NoticeDto, - ) : ResponseEntity { + ): ResponseEntity { return ResponseEntity.ok(noticeService.updateNotice(noticeId, request)) } @@ -54,8 +57,8 @@ class NoticeController( @PostMapping("/tag") fun enrollTag( @RequestBody tagName: Map - ) : ResponseEntity { + ): ResponseEntity { noticeService.enrollTag(tagName["name"]!!) return ResponseEntity("등록되었습니다. (tagName: ${tagName["name"]})", HttpStatus.OK) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt index 79075550..d3c8a7a0 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt @@ -2,10 +2,8 @@ package com.wafflestudio.csereal.core.notice.database import com.wafflestudio.csereal.common.config.BaseTimeEntity import com.wafflestudio.csereal.core.notice.dto.NoticeDto -import jakarta.persistence.CascadeType -import jakarta.persistence.Column -import jakarta.persistence.Entity -import jakarta.persistence.OneToMany +import com.wafflestudio.csereal.core.user.database.UserEntity +import jakarta.persistence.* @Entity(name = "notice") @@ -24,8 +22,12 @@ class NoticeEntity( var isPinned: Boolean, @OneToMany(mappedBy = "notice", cascade = [CascadeType.ALL]) - var noticeTags: MutableSet = mutableSetOf() -): BaseTimeEntity() { + var noticeTags: MutableSet = mutableSetOf(), + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "users_id") + val author: UserEntity +) : BaseTimeEntity() { fun update(updateNoticeRequest: NoticeDto) { this.title = updateNoticeRequest.title @@ -35,4 +37,4 @@ class NoticeEntity( this.isPinned = updateNoticeRequest.isPinned } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt index baf6e9fc..7329952c 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt @@ -7,7 +7,7 @@ data class NoticeDto( val id: Long, val title: String, val description: String, - // val authorId: Int, + val author: String, val tags: List, val createdAt: LocalDateTime?, val modifiedAt: LocalDateTime?, @@ -26,6 +26,7 @@ data class NoticeDto( id = this.id, title = this.title, description = this.description, + author = this.author.name, tags = this.noticeTags.map { it.tag.name }, createdAt = this.createdAt, modifiedAt = this.modifiedAt, @@ -41,4 +42,4 @@ data class NoticeDto( } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt index 8bae04ae..59ea7ca5 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt @@ -3,9 +3,15 @@ package com.wafflestudio.csereal.core.notice.service import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.core.notice.database.* import com.wafflestudio.csereal.core.notice.dto.* +import com.wafflestudio.csereal.core.user.database.UserEntity +import com.wafflestudio.csereal.core.user.database.UserRepository import org.springframework.data.repository.findByIdOrNull +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.security.oauth2.core.oidc.user.OidcUser import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional +import org.springframework.web.context.request.RequestAttributes +import org.springframework.web.context.request.RequestContextHolder interface NoticeService { fun searchNotice(tag: List?, keyword: String?, pageNum: Long): NoticeSearchResponse @@ -20,7 +26,8 @@ interface NoticeService { class NoticeServiceImpl( private val noticeRepository: NoticeRepository, private val tagInNoticeRepository: TagInNoticeRepository, - private val noticeTagRepository: NoticeTagRepository + private val noticeTagRepository: NoticeTagRepository, + private val userRepository: UserRepository ) : NoticeService { @Transactional(readOnly = true) @@ -28,9 +35,9 @@ class NoticeServiceImpl( tag: List?, keyword: String?, pageNum: Long - ): NoticeSearchResponse { - return noticeRepository.searchNotice(tag, keyword, pageNum) - } + ): NoticeSearchResponse { + return noticeRepository.searchNotice(tag, keyword, pageNum) + } @Transactional(readOnly = true) override fun readNotice( @@ -40,7 +47,7 @@ class NoticeServiceImpl( ): NoticeDto { val notice: NoticeEntity = noticeRepository.findByIdOrNull(noticeId) ?: throw CserealException.Csereal404("존재하지 않는 공지사항입니다.(noticeId: $noticeId)") - + if (notice.isDeleted) throw CserealException.Csereal404("삭제된 공지사항입니다.(noticeId: $noticeId)") val prevNext = noticeRepository.findPrevNextId(noticeId, tag, keyword) @@ -50,12 +57,25 @@ class NoticeServiceImpl( @Transactional override fun createNotice(request: NoticeDto): NoticeDto { + var user = RequestContextHolder.getRequestAttributes()?.getAttribute( + "loggedInUser", + RequestAttributes.SCOPE_REQUEST + ) as UserEntity? + + if (user == null) { + val oidcUser = SecurityContextHolder.getContext().authentication.principal as OidcUser + val username = oidcUser.idToken.getClaim("username") + + user = userRepository.findByUsername(username) ?: throw CserealException.Csereal404("재로그인이 필요합니다.") + } + val newNotice = NoticeEntity( title = request.title, description = request.description, isPublic = request.isPublic, isSlide = request.isSlide, isPinned = request.isPinned, + author = user ) for (tagName in request.tags) { @@ -82,13 +102,13 @@ class NoticeServiceImpl( val tagsToRemove = oldTags - request.tags val tagsToAdd = request.tags - oldTags - for(tagName in tagsToRemove) { + for (tagName in tagsToRemove) { val tagId = tagInNoticeRepository.findByName(tagName)!!.id notice.noticeTags.removeIf { it.tag.name == tagName } noticeTagRepository.deleteByNoticeIdAndTagId(noticeId, tagId) } - for(tagName in tagsToAdd) { + for (tagName in tagsToAdd) { val tag = tagInNoticeRepository.findByName(tagName) ?: throw CserealException.Csereal404("해당하는 태그가 없습니다") NoticeTagEntity.createNoticeTag(notice, tag) } @@ -96,8 +116,6 @@ class NoticeServiceImpl( return NoticeDto.of(notice, null) - - } @Transactional @@ -117,4 +135,4 @@ class NoticeServiceImpl( } //TODO: 이미지 등록, 글쓴이 함께 조회 -} \ No newline at end of file +} From b8fca7356a4e9afcd4dadd574d329c5815c4cade Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=ED=98=81=EC=A4=80=20=28HyukJoon=20Woo=29?= Date: Thu, 31 Aug 2023 20:55:32 +0900 Subject: [PATCH 035/124] =?UTF-8?q?CI/CD:=20Https=20=EC=A0=81=EC=9A=A9=20?= =?UTF-8?q?=EB=B0=8F=20=EB=B0=B1=EC=97=94=EB=93=9C,=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=EB=B2=A0=EC=9D=B4=EC=8A=A4,=20=ED=94=84=EB=A1=9D?= =?UTF-8?q?=EC=8B=9C=20=EC=84=9C=EB=B2=84=20=EB=B0=B0=ED=8F=AC=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=20(#50)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Config: Add gitignore for caddy files. * CICD: Add caddy config file * CICD: Seperate docker compose file for db and backend server. * CICD: Docker Compose file for caddy proxy server. * CICD: Add path condition for only act when source file has changed. * CICD: Add workflow for deploying database changes. * CICD: Add workflow for proxy server changes. * Refactor: Add /api/v1 in front of all endpoints (except swagger-ui) * Refactor: Add v1 to Main Image url. * Refactor: change backend, frontend, login redirect uri to get from env. * CICD: Add url to .env * Fix: Change job name of database workflow, fix indent level. * Fix: Remove duplicated restcontroller mapping. * Fix: Set reverse proxy for swagger-ui/* for swagger. * Docs: Set swagger to only scan /api/** * Fix: add caddy to reverse api-docs/* for springdoc. * Fix: Add login uris for reverse proxying to backend server. * CICD: Remove testing branch condition. * CICD: Add logout uri to reverse proxy to backend server. * CICD: Remove test branch --- .github/workflows/database.yaml | 51 +++++++++++++++++++ .github/workflows/deploy.yaml | 27 ++++++---- .github/workflows/proxy.yaml | 46 +++++++++++++++++ .gitignore | 4 ++ caddy/Caddyfile | 18 +++++++ docker-compose-backend.yml | 36 +++++++++++++ docker-compose-caddy.yml | 16 ++++++ docker-compose-db.yml | 14 +++++ docker-compose.yml | 35 ------------- .../common/controller/CommonController.kt | 2 +- .../csereal/core/about/api/AboutController.kt | 2 +- .../core/academics/api/AcademicsController.kt | 2 +- .../admissions/api/AdmissionsController.kt | 2 +- .../csereal/core/main/api/MainController.kt | 2 +- .../core/member/api/ProfessorController.kt | 2 +- .../core/member/api/StaffController.kt | 2 +- .../csereal/core/news/api/NewsController.kt | 2 +- .../core/notice/api/NoticeController.kt | 2 +- .../core/research/api/ResearchController.kt | 2 +- .../reservation/api/ReservceController.kt | 2 +- .../attachment/api/AttachmentController.kt | 2 +- .../mainImage/api/MainImageController.kt | 13 +++++ .../mainImage/service/MainImageService.kt | 2 +- .../scholarship/api/ScholarshipController.kt | 2 +- .../core/seminar/api/SeminarController.kt | 2 +- src/main/resources/application.yaml | 8 +-- 26 files changed, 234 insertions(+), 64 deletions(-) create mode 100644 .github/workflows/database.yaml create mode 100644 .github/workflows/proxy.yaml create mode 100644 caddy/Caddyfile create mode 100644 docker-compose-backend.yml create mode 100644 docker-compose-caddy.yml create mode 100644 docker-compose-db.yml delete mode 100644 docker-compose.yml create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/MainImageController.kt diff --git a/.github/workflows/database.yaml b/.github/workflows/database.yaml new file mode 100644 index 00000000..35f1e975 --- /dev/null +++ b/.github/workflows/database.yaml @@ -0,0 +1,51 @@ +on: + push: + branches: + - main + paths: + - docker-compose-db.yml + - .github/workflows/database.yaml + +jobs: + database-deploy: + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + + steps: + - + name: Checkout + uses: actions/checkout@v3 + + - + name: Create .env file + run: | + echo "MYSQL_ROOT_PASSWORD=${{secrets.MYSQL_ROOT_PASSWORD}}" > .env + echo "MYSQL_USER=${{secrets.MYSQL_USER}}" >> .env + echo "MYSQL_PASSWORD=${{secrets.MYSQL_PASSWORD}}" >> .env + echo "MYSQL_DATABASE=${{secrets.MYSQL_DATABASE}}" >> .env + + - + name: SCP Command to Transfer Files + uses: appleboy/scp-action@v0.1.4 + with: + host: ${{secrets.SSH_HOST}} + username: ${{secrets.SSH_USER}} + key: ${{secrets.SSH_KEY}} + source: "docker-compose-db.yml, .env" + target: "~/app" + overwrite: true + + - + name: SSH Remote Commands + uses: appleboy/ssh-action@v1.0.0 + with: + host: ${{secrets.SSH_HOST}} + username: ${{secrets.SSH_USER}} + key: ${{secrets.SSH_KEY}} + script: | + cd ~/app + source .env + docker-compose -f docker-compose-db.yml down + docker-compose -f docker-compose-db.yml up -d diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 4a002e96..adb61060 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -2,6 +2,17 @@ on: push: branches: - main + paths: + - docker-compose-backend.yml + - Dockerfile + - gradle.properties + - gradlew + - gradlew.bat + - build.gradle.kts + - settings.gradle.kts + - src/** + - gradle/** + - .github/workflows/deploy.yaml jobs: build-and-deploy: @@ -54,6 +65,7 @@ jobs: echo "MYSQL_DATABASE=${{secrets.MYSQL_DATABASE}}" >> .env echo "PROFILE=prod" >> .env echo "OIDC_CLIENT_SECRET_DEV=${{secrets.OIDC_CLIENT_SECRET_DEV}}" >> .env + echo "URL=${{secrets.URL}}" >> .env - name: SCP Command to Transfer Files @@ -62,26 +74,19 @@ jobs: host: ${{secrets.SSH_HOST}} username: ${{secrets.SSH_USER}} key: ${{secrets.SSH_KEY}} - source: "docker-compose.yml, .env" + source: "docker-compose-backend.yml, .env" target: "~/app" overwrite: true - name: SSH Remote Commands uses: appleboy/ssh-action@v1.0.0 - env: - MYSQL_ROOT_PASSWORD: ${{secrets.MYSQL_ROOT_PASSWORD}} - MYSQL_USER: ${{secrets.MYSQL_USER}} - MYSQL_PASSWORD: ${{secrets.MYSQL_PASSWORD}} - MYSQL_DATABASE: ${{secrets.MYSQL_DATABASE}} - PROFILE: "prod" - OIDC_CLIENT_SECRET_DEV: ${{secrets.OIDC_CLIENT_SECRET_DEV}} with: host: ${{secrets.SSH_HOST}} username: ${{secrets.SSH_USER}} key: ${{secrets.SSH_KEY}} - script: | + script: | # TODO: Change to blue-green deployment cd ~/app source .env - docker-compose down + docker-compose -f docker-compose-backend.yml down docker-compose pull - docker-compose up -d + docker-compose -f docker-compose-backend.yml up -d diff --git a/.github/workflows/proxy.yaml b/.github/workflows/proxy.yaml new file mode 100644 index 00000000..0e26609d --- /dev/null +++ b/.github/workflows/proxy.yaml @@ -0,0 +1,46 @@ +on: + push: + branches: + - main + paths: + - docker-compose-caddy.yml + - caddy/Caddyfile + - .github/workflows/proxy.yaml + +jobs: + proxy-initialize: + runs-on: ubuntu-latest + + steps: + - + name: Checkout + uses: actions/checkout@v3 + + - + name: Create .env file + run: | + echo "URL=${{secrets.URL}}" > .env + + - + name: SCP Command to Transfer Files + uses: appleboy/scp-action@v0.1.4 + with: + host: ${{secrets.SSH_HOST}} + username: ${{secrets.SSH_USER}} + key: ${{secrets.SSH_KEY}} + source: "docker-compose-caddy.yml, .env, caddy/Caddyfile" + target: "~/proxy" + overwrite: true + + - + name: SSH Command to Run Docker Compose + uses: appleboy/ssh-action@v1.0.0 + with: + host: ${{secrets.SSH_HOST}} + username: ${{secrets.SSH_USER}} + key: ${{secrets.SSH_KEY}} + script: | + cd ~/proxy + source .env + docker-compose -f docker-compose-caddy.yml down + docker-compose -f docker-compose-caddy.yml up -d \ No newline at end of file diff --git a/.gitignore b/.gitignore index 37848be6..38b301f6 100644 --- a/.gitignore +++ b/.gitignore @@ -285,3 +285,7 @@ db/ # .env file .env + +# caddy server +caddy/* +!caddy/Caddyfile \ No newline at end of file diff --git a/caddy/Caddyfile b/caddy/Caddyfile new file mode 100644 index 00000000..f406408e --- /dev/null +++ b/caddy/Caddyfile @@ -0,0 +1,18 @@ +{$URL} { + # Frontend + reverse_proxy host.docker.internal:3000 + + # Backend + reverse_proxy /api/* host.docker.internal:8080 #host.docker.internal:8081 # For blue/green + + # Login + reverse_proxy /login/oauth2/code/idsnucse host.docker.internal:8080 #host.docker.internal:8081 # For blue/green + reverse_proxy /login/oauth2/code/idsnucse/* host.docker.internal:8080 #host.docker.internal:8081 # For blue/green + reverse_proxy /logout host.docker.internal:8080 #host.docker.internal:8081 # For blue/green + reverse_proxy /logout/* host.docker.internal:8080 #host.docker.internal:8081 # For blue/green + + + # Swagger + reverse_proxy /swagger-ui/* host.docker.internal:8080 #host.docker.internal:8081 # For blue/green + reverse_proxy /api-docs/* host.docker.internal:8080 #host.docker.internal:8081 # For blue/green +} \ No newline at end of file diff --git a/docker-compose-backend.yml b/docker-compose-backend.yml new file mode 100644 index 00000000..344c10be --- /dev/null +++ b/docker-compose-backend.yml @@ -0,0 +1,36 @@ +services: + green: + container_name: csereal_server_green + build: + context: ./ + args: + PROFILE: ${PROFILE} + ports: + - 8080:8080 + environment: + SPRING_DATASOURCE_URL: "jdbc:mysql://host.docker.internal:3306/${MYSQL_DATABASE}?serverTimezone=Asia/Seoul&useSSL=false&allowPublicKeyRetrieval=true" + SPRING_DATASOURCE_USERNAME: ${MYSQL_USER} + SPRING_DATASOURCE_PASSWORD: ${MYSQL_PASSWORD} + OIDC_CLIENT_SECRET_DEV: ${OIDC_CLIENT_SECRET_DEV} + extra_hosts: + - host.docker.internal:host-gateway + restart: always + image: ghcr.io/wafflestudio/csereal-server/server_image:latest + # TODO: Activate after implementing health check +# blue: +# container_name: csereal_server_blue +# build: +# context: ./ +# args: +# PROFILE: ${PROFILE} +# ports: +# - 8081:8080 +# environment: +# SPRING_DATASOURCE_URL: "jdbc:mysql://host.docker.internal:3306/${MYSQL_DATABASE}?serverTimezone=Asia/Seoul&useSSL=false&allowPublicKeyRetrieval=true" +# SPRING_DATASOURCE_USERNAME: ${MYSQL_USER} +# SPRING_DATASOURCE_PASSWORD: ${MYSQL_PASSWORD} +# OIDC_CLIENT_SECRET_DEV: ${OIDC_CLIENT_SECRET_DEV} +# extra_hosts: +# - host.docker.internal:host-gateway +# restart: always +# image: ghcr.io/wafflestudio/csereal-server/server_image:latest \ No newline at end of file diff --git a/docker-compose-caddy.yml b/docker-compose-caddy.yml new file mode 100644 index 00000000..19614d2b --- /dev/null +++ b/docker-compose-caddy.yml @@ -0,0 +1,16 @@ +services: + caddy: + container_name: csereal_caddy + image: caddy:2.7.4-alpine + ports: + - 80:80 + - 443:443 + volumes: + - ./caddy/Caddyfile:/etc/caddy/Caddyfile + - ./caddy/data:/data + - ./caddy/config:/config + environment: + URL: ${URL} + extra_hosts: + - host.docker.internal:host-gateway + restart: always \ No newline at end of file diff --git a/docker-compose-db.yml b/docker-compose-db.yml new file mode 100644 index 00000000..b9586817 --- /dev/null +++ b/docker-compose-db.yml @@ -0,0 +1,14 @@ +services: + db: + container_name: csereal_db_container + image: mysql:8.0 + ports: + - 3306:3306 + volumes: + - ./db:/var/lib/mysql + environment: + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} + MYSQL_DATABASE: ${MYSQL_DATABASE} + MYSQL_USER: ${MYSQL_USER} + MYSQL_PASSWORD: ${MYSQL_PASSWORD} + restart: always \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index f129819c..00000000 --- a/docker-compose.yml +++ /dev/null @@ -1,35 +0,0 @@ -services: - web: - container_name: csereal_server_container - build: - context: ./ - args: - PROFILE: ${PROFILE} - ports: - - 8080:8080 - environment: - SPRING_DATASOURCE_URL: "jdbc:mysql://csereal_db_container:3306/${MYSQL_DATABASE}?serverTimezone=Asia/Seoul&useSSL=false&allowPublicKeyRetrieval=true" - SPRING_DATASOURCE_USERNAME: ${MYSQL_USER} - SPRING_DATASOURCE_PASSWORD: ${MYSQL_PASSWORD} - OIDC_CLIENT_SECRET_DEV: ${OIDC_CLIENT_SECRET_DEV} - depends_on: - - db - networks: - - csereal_network - image: ghcr.io/wafflestudio/csereal-server/server_image:latest - db: - container_name: csereal_db_container - image: mysql:8.0 - ports: - - 3306:3306 - volumes: - - ./db:/var/lib/mysql - environment: - MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} - MYSQL_DATABASE: ${MYSQL_DATABASE} - MYSQL_USER: ${MYSQL_USER} - MYSQL_PASSWORD: ${MYSQL_PASSWORD} - networks: - - csereal_network -networks: - csereal_network: diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/controller/CommonController.kt b/src/main/kotlin/com/wafflestudio/csereal/common/controller/CommonController.kt index e21bbe7a..62eb3a9f 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/controller/CommonController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/controller/CommonController.kt @@ -5,7 +5,7 @@ import org.springframework.web.bind.annotation.RestController @RestController class CommonController { - @GetMapping("/helloworld") + @GetMapping("/api/helloworld") fun helloWorld(): String { return "Hello, world!" } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt index 07e25126..00c81ea7 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt @@ -7,7 +7,7 @@ import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile -@RequestMapping("/about") +@RequestMapping("/api/v1/about") @RestController class AboutController( private val aboutService: AboutService diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt index e4682467..bdf45440 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt @@ -14,7 +14,7 @@ import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController -@RequestMapping("/academics") +@RequestMapping("/api/v1/academics") @RestController class AcademicsController( private val academicsService: AcademicsService diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/AdmissionsController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/AdmissionsController.kt index de4f581e..36ab19c8 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/AdmissionsController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/AdmissionsController.kt @@ -12,7 +12,7 @@ import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController -@RequestMapping("/admissions") +@RequestMapping("/api/v1/admissions") @RestController class AdmissionsController( private val admissionsService: AdmissionsService diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/api/MainController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/api/MainController.kt index 7aa259d1..b633fc34 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/main/api/MainController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/api/MainController.kt @@ -7,7 +7,7 @@ import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController @RequestMapping -@RestController +@RestController("/api/v1") class MainController( private val mainService: MainService, ) { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt index b1ee1d72..7b83b5a8 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt @@ -8,7 +8,7 @@ import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile -@RequestMapping("/professor") +@RequestMapping("/api/v1/professor") @RestController class ProfessorController( private val professorService: ProfessorService diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt index 26e0ac86..d8e863db 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt @@ -7,7 +7,7 @@ import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile -@RequestMapping("/staff") +@RequestMapping("/api/v1/staff") @RestController class StaffController( private val staffService: StaffService diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt index 25c2dc57..8fdd29a0 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt @@ -9,7 +9,7 @@ import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile -@RequestMapping("/news") +@RequestMapping("/api/v1/news") @RestController class NewsController( private val newsService: NewsService, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt index 4cb440ee..a42a9b7d 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt @@ -8,7 +8,7 @@ import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* -@RequestMapping("/notice") +@RequestMapping("/api/v1/notice") @RestController class NoticeController( private val noticeService: NoticeService, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt index ff5607f7..187623d6 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt @@ -8,7 +8,7 @@ import jakarta.validation.Valid import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* -@RequestMapping("/research") +@RequestMapping("/api/v1/research") @RestController class ResearchController( private val researchService: ResearchService diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservceController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservceController.kt index e0dff854..440049e3 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservceController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservceController.kt @@ -19,7 +19,7 @@ import java.time.LocalDate import java.time.LocalDateTime import java.util.UUID -@RequestMapping("/reservation") +@RequestMapping("/api/v1/reservation") @RestController class ReservationController( private val reservationService: ReservationService diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/api/AttachmentController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/api/AttachmentController.kt index 1d011b39..f2b39677 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/api/AttachmentController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/api/AttachmentController.kt @@ -4,7 +4,7 @@ import com.wafflestudio.csereal.core.resource.attachment.service.AttachmentServi import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -@RequestMapping("/attachment") +@RequestMapping("/api/v1/attachment") @RestController class AttachmentController( private val attachmentService: AttachmentService diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/MainImageController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/MainImageController.kt new file mode 100644 index 00000000..e74a26da --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/MainImageController.kt @@ -0,0 +1,13 @@ +package com.wafflestudio.csereal.core.resource.mainImage.api + +import com.wafflestudio.csereal.core.resource.mainImage.service.ImageService +import org.springframework.web.bind.annotation.* + + +@RequestMapping("/image") +@RestController +class MainImageController( + private val imageService: ImageService +) { + +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt index da986275..db4da329 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt @@ -91,7 +91,7 @@ class ImageServiceImpl( @Transactional override fun createImageURL(image: MainImageEntity?): String? { return if (image != null) { - "${endpointProperties.backend}/file/${image.filename}" + "${endpointProperties.backend}/v1/file/${image.filename}" } else null } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/api/ScholarshipController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/api/ScholarshipController.kt index 4531d2f0..ba70ba00 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/api/ScholarshipController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/api/ScholarshipController.kt @@ -8,7 +8,7 @@ import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -@RequestMapping("/scholarship") +@RequestMapping("/api/v1/scholarship") @RestController class ScholarshipController( private val scholarshipService: ScholarshipService diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt index 31dfdd8a..126bd80b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt @@ -8,7 +8,7 @@ import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile -@RequestMapping("/seminar") +@RequestMapping("/api/v1/seminar") @RestController class SeminarController ( private val seminarService: SeminarService, diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index ff78a879..454ddb8e 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -23,6 +23,8 @@ server: timeout: 7200 # 2시간 springdoc: + paths-to-match: + - /api/** swagger-ui: path: index.html api-docs: @@ -83,7 +85,7 @@ spring: client: registration: idsnucse: - redirect-uri: http://cse-dev-waffle.bacchus.io:8080/login/oauth2/code/idsnucse + redirect-uri: http://${URL}/login/oauth2/code/idsnucse csereal_image: upload: @@ -94,5 +96,5 @@ csereal_attachment: path: /app/attachment/ endpoint: - backend: http://cse-dev-waffle.bacchus.io:8080 - frontend: http://cse-dev-waffle.bacchus.io:3000 + backend: http://${URL}/api + frontend: http://${URL} From 8b38588d31f4013fe5c9a5b62c48e9102a74520b Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Fri, 1 Sep 2023 13:57:09 +0900 Subject: [PATCH 036/124] =?UTF-8?q?fix:=20=ED=94=84=EB=A1=A0=ED=8A=B8?= =?UTF-8?q?=EB=9E=91=20=ED=98=91=EC=9D=98=ED=95=98=EC=97=AC=20=EB=82=B4?= =?UTF-8?q?=EC=9A=A9=20=EB=B3=80=EA=B2=BD=20+=20news,=20seminar=EC=97=90?= =?UTF-8?q?=20image,=20attachments=20update=20=EC=B6=94=EA=B0=80=20(#51)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: attachments 추가, news request에 attachments 추가 * feat: seminar request에 attachments 추가 * feat: about request에 attachments 추가 * feat: academics request에 attachments 추가 * fix: isPinned 삭제 * fix: image -> mainImage 변경, about에 greetings 추가 * fix: news, seminar update 수정 * fix: 전체적으로 image -> mainImage 변경 --- .../csereal/core/about/api/AboutController.kt | 5 ++- .../core/about/database/AboutEntity.kt | 8 +++- .../core/about/database/AboutPostType.kt | 2 +- .../csereal/core/about/dto/AboutDto.kt | 9 ++-- .../core/about/service/AboutService.kt | 44 ++++++++++++------- .../core/academics/api/AcademicsController.kt | 30 ++++++------- .../academics/database/AcademicsEntity.kt | 16 ++++--- .../core/academics/database/CourseEntity.kt | 20 +++++---- .../academics/database/CourseRepository.kt | 2 +- .../core/academics/dto/AcademicsDto.kt | 7 +-- .../csereal/core/academics/dto/CourseDto.kt | 17 +++---- .../academics/service/AcademicsService.kt | 36 ++++++++++----- .../core/main/database/MainRepository.kt | 2 +- .../core/member/api/ProfessorController.kt | 4 +- .../core/member/api/StaffController.kt | 4 +- .../core/member/service/ProfessorService.kt | 23 +++++----- .../core/member/service/StaffService.kt | 20 ++++----- .../csereal/core/news/api/NewsController.kt | 10 +++-- .../csereal/core/news/database/NewsEntity.kt | 5 --- .../core/news/database/NewsRepository.kt | 3 +- .../csereal/core/news/dto/NewsDto.kt | 2 - .../csereal/core/news/service/NewsService.kt | 35 ++++++++++----- .../core/notice/database/NoticeEntity.kt | 4 -- .../csereal/core/notice/dto/NoticeDto.kt | 2 - .../core/notice/service/NoticeService.kt | 1 - .../attachment/database/AttachmentEntity.kt | 17 ++++++- .../attachment/service/AttachmentService.kt | 16 ++++++- .../resource/mainImage/api/FileController.kt | 2 +- .../mainImage/api/MainImageController.kt | 5 +-- .../mainImage/database/MainImageEntity.kt | 2 +- .../mainImage/service/MainImageService.kt | 43 +++++++++--------- .../core/seminar/api/SeminarController.kt | 10 +++-- .../core/seminar/database/SeminarEntity.kt | 22 +++------- .../csereal/core/seminar/dto/SeminarDto.kt | 19 ++++---- .../core/seminar/service/SeminarService.kt | 32 +++++++++----- src/main/resources/application.yaml | 10 +++-- 36 files changed, 282 insertions(+), 207 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt index 00c81ea7..183e4a5e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt @@ -21,9 +21,10 @@ class AboutController( fun createAbout( @PathVariable postType: String, @Valid @RequestPart("request") request: AboutDto, - @RequestPart("image") image: MultipartFile?, + @RequestPart("mainImage") mainImage: MultipartFile?, + @RequestPart("attachments") attachments: List?, ) : ResponseEntity { - return ResponseEntity.ok(aboutService.createAbout(postType, request, image)) + return ResponseEntity.ok(aboutService.createAbout(postType, request, mainImage, attachments)) } // read 목록이 하나 diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt index 316de78a..8dd8b967 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt @@ -1,8 +1,10 @@ package com.wafflestudio.csereal.core.about.database import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.common.controller.AttachmentContentEntityType import com.wafflestudio.csereal.common.controller.ImageContentEntityType import com.wafflestudio.csereal.core.about.dto.AboutDto +import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity import jakarta.persistence.* @@ -18,11 +20,15 @@ class AboutEntity( @OneToMany(mappedBy = "about", cascade = [CascadeType.ALL], orphanRemoval = true) val locations: MutableList = mutableListOf(), + @OneToMany(mappedBy = "") + var attachments: MutableList = mutableListOf(), + @OneToOne var mainImage: MainImageEntity? = null, -) : BaseTimeEntity(), ImageContentEntityType { + ) : BaseTimeEntity(), ImageContentEntityType, AttachmentContentEntityType { override fun bringMainImage(): MainImageEntity? = mainImage + override fun bringAttachments(): List = attachments companion object { fun of(postType: AboutPostType, aboutDto: AboutDto): AboutEntity { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutPostType.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutPostType.kt index ab12f058..9dec0a30 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutPostType.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutPostType.kt @@ -1,5 +1,5 @@ package com.wafflestudio.csereal.core.about.database enum class AboutPostType { - OVERVIEW, HISTORY, FUTURE_CAREERS, CONTACT, STUDENT_CLUBS, FACILITIES, DIRECTIONS, + OVERVIEW, GREETINGS, HISTORY, FUTURE_CAREERS, CONTACT, STUDENT_CLUBS, FACILITIES, DIRECTIONS, } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutDto.kt index fc0612e4..2d7c96e2 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutDto.kt @@ -2,6 +2,7 @@ package com.wafflestudio.csereal.core.about.dto import com.wafflestudio.csereal.core.about.database.AboutEntity +import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse import java.time.LocalDateTime data class AboutDto( @@ -13,10 +14,11 @@ data class AboutDto( val createdAt: LocalDateTime?, val modifiedAt: LocalDateTime?, val locations: List?, - val imageURL: String? + val imageURL: String?, + val attachments: List?, ) { companion object { - fun of(entity: AboutEntity, imageURL: String?) : AboutDto = entity.run { + fun of(entity: AboutEntity, imageURL: String?, attachments: List?) : AboutDto = entity.run { AboutDto( id = this.id, name = this.name, @@ -26,7 +28,8 @@ data class AboutDto( createdAt = this.createdAt, modifiedAt = this.modifiedAt, locations = this.locations.map { it.name }, - imageURL = imageURL + imageURL = imageURL, + attachments = attachments, ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt index 5cac574a..9a3af02b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt @@ -6,13 +6,14 @@ import com.wafflestudio.csereal.core.about.database.AboutPostType import com.wafflestudio.csereal.core.about.database.AboutRepository import com.wafflestudio.csereal.core.about.database.LocationEntity import com.wafflestudio.csereal.core.about.dto.AboutDto -import com.wafflestudio.csereal.core.resource.mainImage.service.ImageService +import com.wafflestudio.csereal.core.resource.attachment.service.AttachmentService +import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import org.springframework.web.multipart.MultipartFile interface AboutService { - fun createAbout(postType: String, request: AboutDto, image: MultipartFile?): AboutDto + fun createAbout(postType: String, request: AboutDto, mainImage: MultipartFile?, attachments: List?): AboutDto fun readAbout(postType: String): AboutDto fun readAllClubs() : List fun readAllFacilities() : List @@ -22,10 +23,11 @@ interface AboutService { @Service class AboutServiceImpl( private val aboutRepository: AboutRepository, - private val imageService: ImageService, + private val mainImageService: MainImageService, + private val attachmentService: AttachmentService, ) : AboutService { @Transactional - override fun createAbout(postType: String, request: AboutDto, image: MultipartFile?): AboutDto { + override fun createAbout(postType: String, request: AboutDto, mainImage: MultipartFile?, attachments: List?): AboutDto { val enumPostType = makeStringToEnum(postType) val newAbout = AboutEntity.of(enumPostType, request) @@ -35,30 +37,38 @@ class AboutServiceImpl( } } - if(image != null) { - imageService.uploadImage(newAbout, image) + if(mainImage != null) { + mainImageService.uploadMainImage(newAbout, mainImage) + } + + if(attachments != null) { + attachmentService.uploadAttachments(newAbout, attachments) } aboutRepository.save(newAbout) - val imageURL = imageService.createImageURL(newAbout.mainImage) + val imageURL = mainImageService.createImageURL(newAbout.mainImage) + val attachments = attachmentService.createAttachments(newAbout.attachments) - return AboutDto.of(newAbout, imageURL) + return AboutDto.of(newAbout, imageURL, attachments) } @Transactional(readOnly = true) override fun readAbout(postType: String): AboutDto { val enumPostType = makeStringToEnum(postType) val about = aboutRepository.findByPostType(enumPostType) - val imageURL = imageService.createImageURL(about.mainImage) + val imageURL = mainImageService.createImageURL(about.mainImage) + val attachments = attachmentService.createAttachments(about.attachments) + - return AboutDto.of(about, imageURL) + return AboutDto.of(about, imageURL, attachments) } @Transactional(readOnly = true) override fun readAllClubs(): List { val clubs = aboutRepository.findAllByPostTypeOrderByName(AboutPostType.STUDENT_CLUBS).map { - val imageURL = imageService.createImageURL(it.mainImage) - AboutDto.of(it, imageURL) + val imageURL = mainImageService.createImageURL(it.mainImage) + val attachments = attachmentService.createAttachments(it.attachments) + AboutDto.of(it, imageURL, attachments) } return clubs @@ -67,8 +77,9 @@ class AboutServiceImpl( @Transactional(readOnly = true) override fun readAllFacilities(): List { val facilities = aboutRepository.findAllByPostTypeOrderByName(AboutPostType.FACILITIES).map { - val imageURL = imageService.createImageURL(it.mainImage) - AboutDto.of(it, imageURL) + val imageURL = mainImageService.createImageURL(it.mainImage) + val attachments = attachmentService.createAttachments(it.attachments) + AboutDto.of(it, imageURL, attachments) } return facilities @@ -77,8 +88,9 @@ class AboutServiceImpl( @Transactional(readOnly = true) override fun readAllDirections(): List { val directions = aboutRepository.findAllByPostTypeOrderByName(AboutPostType.DIRECTIONS).map { - val imageURL = imageService.createImageURL(it.mainImage) - AboutDto.of(it, imageURL) + val imageURL = mainImageService.createImageURL(it.mainImage) + val attachments = attachmentService.createAttachments(it.attachments) + AboutDto.of(it, imageURL, attachments) } return directions diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt index bdf45440..ccc0420b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt @@ -6,13 +6,8 @@ import com.wafflestudio.csereal.core.academics.dto.ScholarshipPageResponse import com.wafflestudio.csereal.core.academics.service.AcademicsService import jakarta.validation.Valid import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.PostMapping -import org.springframework.web.bind.annotation.RequestBody -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RequestParam -import org.springframework.web.bind.annotation.RestController +import org.springframework.web.bind.annotation.* +import org.springframework.web.multipart.MultipartFile @RequestMapping("/api/v1/academics") @RestController @@ -25,9 +20,10 @@ class AcademicsController( fun createAcademics( @PathVariable studentType: String, @PathVariable postType: String, - @Valid @RequestBody request: AcademicsDto - ): ResponseEntity { - return ResponseEntity.ok(academicsService.createAcademics(studentType, postType, request)) + @Valid @RequestPart("request") request: AcademicsDto, + @RequestPart("attachments") attachments: List? + ) : ResponseEntity { + return ResponseEntity.ok(academicsService.createAcademics(studentType, postType, request, attachments)) } @GetMapping("/{studentType}/{postType}") @@ -42,9 +38,10 @@ class AcademicsController( @PostMapping("/{studentType}/course") fun createCourse( @PathVariable studentType: String, - @Valid @RequestBody request: CourseDto - ): ResponseEntity { - return ResponseEntity.ok(academicsService.createCourse(studentType, request)) + @Valid @RequestPart("request") request: CourseDto, + @RequestPart("attachments") attachments: List?, + ) : ResponseEntity { + return ResponseEntity.ok(academicsService.createCourse(studentType, request, attachments)) } @GetMapping("/{studentType}/courses") @@ -65,9 +62,10 @@ class AcademicsController( @PostMapping("/{studentType}/scholarship") fun createScholarship( @PathVariable studentType: String, - @Valid @RequestBody request: AcademicsDto - ): ResponseEntity { - return ResponseEntity.ok(academicsService.createAcademics(studentType, "scholarship", request)) + @Valid @RequestPart("request") request: AcademicsDto, + @RequestPart("attachments") attachments: List?, + ) : ResponseEntity { + return ResponseEntity.ok(academicsService.createAcademics(studentType, "scholarship", request, attachments)) } @GetMapping("/scholarship") diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsEntity.kt index 18bc1a08..3156d6b1 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsEntity.kt @@ -1,10 +1,10 @@ package com.wafflestudio.csereal.core.academics.database import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.common.controller.AttachmentContentEntityType import com.wafflestudio.csereal.core.academics.dto.AcademicsDto -import jakarta.persistence.Entity -import jakarta.persistence.EnumType -import jakarta.persistence.Enumerated +import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity +import jakarta.persistence.* @Entity(name = "academics") class AcademicsEntity( @@ -17,9 +17,14 @@ class AcademicsEntity( var name: String, var description: String, var year: Int?, - var isPublic: Boolean, -): BaseTimeEntity() { + @OneToMany(mappedBy = "academics", cascade = [CascadeType.ALL], orphanRemoval = true) + var attachments: MutableList = mutableListOf(), + + + ): BaseTimeEntity(), AttachmentContentEntityType { + override fun bringAttachments() = attachments + companion object { fun of(studentType: AcademicsStudentType, postType: AcademicsPostType, academicsDto: AcademicsDto): AcademicsEntity { return AcademicsEntity( @@ -28,7 +33,6 @@ class AcademicsEntity( name = academicsDto.name, description = academicsDto.description, year = academicsDto.year, - isPublic = academicsDto.isPublic, ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseEntity.kt index 5d410cad..94b6feee 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseEntity.kt @@ -2,7 +2,10 @@ package com.wafflestudio.csereal.core.academics.database import com.wafflestudio.csereal.common.config.BaseTimeEntity import com.wafflestudio.csereal.core.academics.dto.CourseDto +import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity +import jakarta.persistence.CascadeType import jakarta.persistence.Entity +import jakarta.persistence.OneToMany @Entity(name = "course") class CourseEntity( @@ -12,28 +15,29 @@ class CourseEntity( var classification: String, - var number: String, + var code: String, var name: String, var credit: Int, - var year: String, + var grade: String, - var courseURL: String?, + var description: String?, - var description: String? -): BaseTimeEntity() { + @OneToMany(mappedBy = "course", cascade = [CascadeType.ALL], orphanRemoval = true) + var attachments: MutableList = mutableListOf(), + + ): BaseTimeEntity() { companion object { fun of(studentType: AcademicsStudentType, courseDto: CourseDto): CourseEntity { return CourseEntity( studentType = studentType, classification = courseDto.classification, - number = courseDto.number, + code = courseDto.code, name = courseDto.name.replace(" ","-"), credit = courseDto.credit, - year = courseDto.year, - courseURL = courseDto.courseURL, + grade = courseDto.grade, description = courseDto.description ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseRepository.kt index f8e017cc..61e3d550 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseRepository.kt @@ -3,6 +3,6 @@ package com.wafflestudio.csereal.core.academics.database import org.springframework.data.jpa.repository.JpaRepository interface CourseRepository : JpaRepository { - fun findAllByStudentTypeOrderByYearAsc(studentType: AcademicsStudentType) : List + fun findAllByStudentTypeOrderByNameAsc(studentType: AcademicsStudentType) : List fun findByName(name: String) : CourseEntity } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsDto.kt index fadb6c5b..cc16ce4d 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsDto.kt @@ -1,6 +1,7 @@ package com.wafflestudio.csereal.core.academics.dto import com.wafflestudio.csereal.core.academics.database.AcademicsEntity +import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse import java.time.LocalDateTime data class AcademicsDto( @@ -10,10 +11,10 @@ data class AcademicsDto( val year: Int?, val createdAt: LocalDateTime?, val modifiedAt: LocalDateTime?, - val isPublic: Boolean, + val attachments: List?, ) { companion object { - fun of(entity: AcademicsEntity) : AcademicsDto = entity.run { + fun of(entity: AcademicsEntity, attachments: List?) : AcademicsDto = entity.run { AcademicsDto( id = this.id, name = this.name, @@ -21,7 +22,7 @@ data class AcademicsDto( year = this.year, createdAt = this.createdAt, modifiedAt = this.modifiedAt, - isPublic = this.isPublic, + attachments = attachments, ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/CourseDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/CourseDto.kt index bb2fffc3..07ab25b3 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/CourseDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/CourseDto.kt @@ -1,28 +1,29 @@ package com.wafflestudio.csereal.core.academics.dto import com.wafflestudio.csereal.core.academics.database.CourseEntity +import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse data class CourseDto( val id: Long, val classification: String, - val number: String, + val code: String, val name: String, val credit: Int, - val year: String, - val courseURL: String?, - val description: String? + val grade: String, + val description: String?, + val attachments: List?, ) { companion object { - fun of(entity: CourseEntity): CourseDto = entity.run { + fun of(entity: CourseEntity, attachments: List?): CourseDto = entity.run { CourseDto( id = this.id, classification = this.classification, - number = this.number, + code = this.code, name = this.name, credit = this.credit, - year = this.year, - courseURL = this.courseURL, + grade = this.grade, description = this.description, + attachments = attachments, ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt index f0ae37ff..4421c6f5 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt @@ -5,16 +5,18 @@ import com.wafflestudio.csereal.core.about.database.AboutPostType import com.wafflestudio.csereal.core.academics.database.* import com.wafflestudio.csereal.core.academics.dto.CourseDto import com.wafflestudio.csereal.core.academics.dto.AcademicsDto +import com.wafflestudio.csereal.core.resource.attachment.service.AttachmentService import com.wafflestudio.csereal.core.academics.dto.ScholarshipPageResponse import com.wafflestudio.csereal.core.scholarship.database.ScholarshipRepository import com.wafflestudio.csereal.core.scholarship.dto.SimpleScholarshipDto import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional +import org.springframework.web.multipart.MultipartFile interface AcademicsService { - fun createAcademics(studentType: String, postType: String, request: AcademicsDto): AcademicsDto + fun createAcademics(studentType: String, postType: String, request: AcademicsDto, attachments: List?): AcademicsDto fun readAcademics(studentType: String, postType: String): AcademicsDto - fun createCourse(studentType: String, request: CourseDto): CourseDto + fun createCourse(studentType: String, request: CourseDto, attachments: List?): CourseDto fun readAllCourses(studentType: String): List fun readCourse(name: String): CourseDto fun readScholarship(name: String): ScholarshipPageResponse @@ -24,18 +26,26 @@ interface AcademicsService { class AcademicsServiceImpl( private val academicsRepository: AcademicsRepository, private val courseRepository: CourseRepository, + private val attachmentService: AttachmentService, private val scholarshipRepository: ScholarshipRepository ) : AcademicsService { @Transactional - override fun createAcademics(studentType: String, postType: String, request: AcademicsDto): AcademicsDto { + override fun createAcademics(studentType: String, postType: String, request: AcademicsDto, attachments: List?): AcademicsDto { val enumStudentType = makeStringToAcademicsStudentType(studentType) val enumPostType = makeStringToAcademicsPostType(postType) val newAcademics = AcademicsEntity.of(enumStudentType, enumPostType, request) + if(attachments != null) { + attachmentService.uploadAttachments(newAcademics, attachments) + } + academicsRepository.save(newAcademics) - return AcademicsDto.of(newAcademics) + val attachments = attachmentService.createAttachments(newAcademics.attachments) + + + return AcademicsDto.of(newAcademics, attachments) } @Transactional(readOnly = true) @@ -46,25 +56,30 @@ class AcademicsServiceImpl( val academics = academicsRepository.findByStudentTypeAndPostType(enumStudentType, enumPostType) - return AcademicsDto.of(academics) + val attachments = attachmentService.createAttachments(academics.attachments) + + return AcademicsDto.of(academics, attachments) } @Transactional - override fun createCourse(studentType: String, request: CourseDto): CourseDto { + override fun createCourse(studentType: String, request: CourseDto, attachments: List?): CourseDto { val enumStudentType = makeStringToAcademicsStudentType(studentType) val course = CourseEntity.of(enumStudentType, request) courseRepository.save(course) - return CourseDto.of(course) + val attachments = attachmentService.createAttachments(course.attachments) + + return CourseDto.of(course, attachments) } @Transactional(readOnly = true) override fun readAllCourses(studentType: String): List { val enumStudentType = makeStringToAcademicsStudentType(studentType) - val courseDtoList = courseRepository.findAllByStudentTypeOrderByYearAsc(enumStudentType).map { - CourseDto.of(it) + val courseDtoList = courseRepository.findAllByStudentTypeOrderByNameAsc(enumStudentType).map { + val attachments = attachmentService.createAttachments(it.attachments) + CourseDto.of(it, attachments) } return courseDtoList } @@ -72,8 +87,9 @@ class AcademicsServiceImpl( @Transactional(readOnly = true) override fun readCourse(name: String): CourseDto { val course = courseRepository.findByName(name) + val attachments = attachmentService.createAttachments(course.attachments) - return CourseDto.of(course) + return CourseDto.of(course, attachments) } @Transactional(readOnly = true) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt index f7fcf906..22a9ab24 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt @@ -34,7 +34,7 @@ class MainRepositoryImpl( ) ).from(newsEntity) .where(newsEntity.isDeleted.eq(false), newsEntity.isPublic.eq(true), newsEntity.isSlide.eq(true)) - .orderBy(newsEntity.isPinned.desc()).orderBy(newsEntity.createdAt.desc()) + .orderBy(newsEntity.createdAt.desc()) .limit(20).fetch() } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt index 7b83b5a8..6431351f 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt @@ -17,9 +17,9 @@ class ProfessorController( @PostMapping fun createProfessor( @RequestPart("request") createProfessorRequest: ProfessorDto, - @RequestPart("image") image: MultipartFile?, + @RequestPart("mainImage") mainImage: MultipartFile?, ): ResponseEntity { - return ResponseEntity.ok(professorService.createProfessor(createProfessorRequest, image)) + return ResponseEntity.ok(professorService.createProfessor(createProfessorRequest, mainImage)) } @GetMapping("/{professorId}") diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt index d8e863db..611d2afd 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt @@ -16,9 +16,9 @@ class StaffController( @PostMapping fun createStaff( @RequestPart("request") createStaffRequest: StaffDto, - @RequestPart("image") image: MultipartFile?, + @RequestPart("mainImage") mainImage: MultipartFile?, ): ResponseEntity { - return ResponseEntity.ok(staffService.createStaff(createStaffRequest,image)) + return ResponseEntity.ok(staffService.createStaff(createStaffRequest,mainImage)) } @GetMapping("/{staffId}") diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt index c53746b1..86e6f8cf 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt @@ -6,14 +6,15 @@ import com.wafflestudio.csereal.core.member.dto.ProfessorDto import com.wafflestudio.csereal.core.member.dto.ProfessorPageDto import com.wafflestudio.csereal.core.member.dto.SimpleProfessorDto import com.wafflestudio.csereal.core.research.database.LabRepository -import com.wafflestudio.csereal.core.resource.mainImage.service.ImageService +import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import org.springframework.web.multipart.MultipartFile interface ProfessorService { - fun createProfessor(createProfessorRequest: ProfessorDto, image: MultipartFile?): ProfessorDto fun getProfessor(professorId: Long): ProfessorDto + fun createProfessor(createProfessorRequest: ProfessorDto, mainImage: MultipartFile?): ProfessorDto + fun getProfessor(professorId: Long): ProfessorDto fun getActiveProfessors(): ProfessorPageDto fun getInactiveProfessors(): List fun updateProfessor(professorId: Long, updateProfessorRequest: ProfessorDto): ProfessorDto @@ -25,9 +26,9 @@ interface ProfessorService { class ProfessorServiceImpl( private val labRepository: LabRepository, private val professorRepository: ProfessorRepository, - private val imageService: ImageService, + private val mainImageService: MainImageService, ) : ProfessorService { - override fun createProfessor(createProfessorRequest: ProfessorDto, image: MultipartFile?): ProfessorDto { + override fun createProfessor(createProfessorRequest: ProfessorDto, mainImage: MultipartFile?): ProfessorDto { val professor = ProfessorEntity.of(createProfessorRequest) if (createProfessorRequest.labId != null) { val lab = labRepository.findByIdOrNull(createProfessorRequest.labId) @@ -47,13 +48,13 @@ class ProfessorServiceImpl( CareerEntity.create(career, professor) } - if(image != null) { - imageService.uploadImage(professor, image) + if(mainImage != null) { + mainImageService.uploadMainImage(professor, mainImage) } professorRepository.save(professor) - val imageURL = imageService.createImageURL(professor.mainImage) + val imageURL = mainImageService.createImageURL(professor.mainImage) return ProfessorDto.of(professor, imageURL) } @@ -63,7 +64,7 @@ class ProfessorServiceImpl( val professor = professorRepository.findByIdOrNull(professorId) ?: throw CserealException.Csereal404("해당 교수님을 찾을 수 없습니다. professorId: ${professorId}") - val imageURL = imageService.createImageURL(professor.mainImage) + val imageURL = mainImageService.createImageURL(professor.mainImage) return ProfessorDto.of(professor, imageURL) } @@ -77,7 +78,7 @@ class ProfessorServiceImpl( "30% 이상의 과목이 영어로 개설되고 있어 외국인 학생의 학업을 돕는 동시에 한국인 학생이 세계로 진출하는 초석이 되고 있다. 또한 " + "CSE int’l Luncheon을 개최하여 학부 내 외국인 구성원의 화합과 생활의 불편함을 최소화하는 등 학부 차원에서 최선을 다하고 있다." val professors = professorRepository.findByStatusNot(ProfessorStatus.INACTIVE).map { - val imageURL = imageService.createImageURL(it.mainImage) + val imageURL = mainImageService.createImageURL(it.mainImage) SimpleProfessorDto.of(it, imageURL) } .sortedBy { it.name } @@ -87,7 +88,7 @@ class ProfessorServiceImpl( @Transactional(readOnly = true) override fun getInactiveProfessors(): List { return professorRepository.findByStatus(ProfessorStatus.INACTIVE).map { - val imageURL = imageService.createImageURL(it.mainImage) + val imageURL = mainImageService.createImageURL(it.mainImage) SimpleProfessorDto.of(it, imageURL) } .sortedBy { it.name } @@ -142,7 +143,7 @@ class ProfessorServiceImpl( CareerEntity.create(career, professor) } - val imageURL = imageService.createImageURL(professor.mainImage) + val imageURL = mainImageService.createImageURL(professor.mainImage) return ProfessorDto.of(professor, imageURL) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt index 722b29fd..a68630d7 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt @@ -6,14 +6,14 @@ import com.wafflestudio.csereal.core.member.database.StaffRepository import com.wafflestudio.csereal.core.member.database.TaskEntity import com.wafflestudio.csereal.core.member.dto.SimpleStaffDto import com.wafflestudio.csereal.core.member.dto.StaffDto -import com.wafflestudio.csereal.core.resource.mainImage.service.ImageService +import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import org.springframework.web.multipart.MultipartFile interface StaffService { - fun createStaff(createStaffRequest: StaffDto, image: MultipartFile?): StaffDto + fun createStaff(createStaffRequest: StaffDto, mainImage: MultipartFile?): StaffDto fun getStaff(staffId: Long): StaffDto fun getAllStaff(): List fun updateStaff(staffId: Long, updateStaffRequest: StaffDto): StaffDto @@ -24,22 +24,22 @@ interface StaffService { @Transactional class StaffServiceImpl( private val staffRepository: StaffRepository, - private val imageService: ImageService, + private val mainImageService: MainImageService, ) : StaffService { - override fun createStaff(createStaffRequest: StaffDto, image: MultipartFile?): StaffDto { + override fun createStaff(createStaffRequest: StaffDto, mainImage: MultipartFile?): StaffDto { val staff = StaffEntity.of(createStaffRequest) for (task in createStaffRequest.tasks) { TaskEntity.create(task, staff) } - if(image != null) { - imageService.uploadImage(staff, image) + if(mainImage != null) { + mainImageService.uploadMainImage(staff, mainImage) } staffRepository.save(staff) - val imageURL = imageService.createImageURL(staff.mainImage) + val imageURL = mainImageService.createImageURL(staff.mainImage) return StaffDto.of(staff, imageURL) } @@ -49,7 +49,7 @@ class StaffServiceImpl( val staff = staffRepository.findByIdOrNull(staffId) ?: throw CserealException.Csereal404("해당 행정직원을 찾을 수 없습니다. staffId: ${staffId}") - val imageURL = imageService.createImageURL(staff.mainImage) + val imageURL = mainImageService.createImageURL(staff.mainImage) return StaffDto.of(staff, imageURL) } @@ -57,7 +57,7 @@ class StaffServiceImpl( @Transactional(readOnly = true) override fun getAllStaff(): List { return staffRepository.findAll().map { - val imageURL = imageService.createImageURL(it.mainImage) + val imageURL = mainImageService.createImageURL(it.mainImage) SimpleStaffDto.of(it, imageURL) }.sortedBy { it.name } } @@ -81,7 +81,7 @@ class StaffServiceImpl( TaskEntity.create(task, staff) } - val imageURL = imageService.createImageURL(staff.mainImage) + val imageURL = mainImageService.createImageURL(staff.mainImage) return StaffDto.of(staff, imageURL) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt index 8fdd29a0..2de2d69e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt @@ -34,18 +34,20 @@ class NewsController( @PostMapping fun createNews( @Valid @RequestPart("request") request: NewsDto, - @RequestPart("image") image: MultipartFile?, + @RequestPart("mainImage") mainImage: MultipartFile?, @RequestPart("attachments") attachments: List? ) : ResponseEntity { - return ResponseEntity.ok(newsService.createNews(request,image, attachments)) + return ResponseEntity.ok(newsService.createNews(request,mainImage, attachments)) } @PatchMapping("/{newsId}") fun updateNews( @PathVariable newsId: Long, - @Valid @RequestBody request: NewsDto, + @Valid @RequestPart("request") request: NewsDto, + @RequestPart("mainImage") mainImage: MultipartFile?, + @RequestPart("attachments") attachments: List? ) : ResponseEntity { - return ResponseEntity.ok(newsService.updateNews(newsId, request)) + return ResponseEntity.ok(newsService.updateNews(newsId, request, mainImage, attachments)) } @DeleteMapping("/{newsId}") diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt index 4aba2d75..8349a433 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt @@ -21,9 +21,6 @@ class NewsEntity( var isSlide: Boolean, - // 새소식 작성란에도 "가장 위에 표시"가 있더라고요, 혹시 쓸지도 모르니까 남겼습니다 - var isPinned: Boolean, - @OneToOne var mainImage: MainImageEntity? = null, @@ -44,7 +41,6 @@ class NewsEntity( description = newsDto.description, isPublic = newsDto.isPublic, isSlide = newsDto.isSlide, - isPinned = newsDto.isPinned, ) } } @@ -53,6 +49,5 @@ class NewsEntity( this.description = updateNewsRequest.description this.isPublic = updateNewsRequest.isPublic this.isSlide = updateNewsRequest.isSlide - this.isPinned = updateNewsRequest.isPinned } } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt index 3edf6858..80045bb0 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt @@ -58,7 +58,7 @@ class NewsRepositoryImpl( val total = jpaQuery.distinct().fetch().size - val newsEntityList = jpaQuery.orderBy(newsEntity.isPinned.desc()) + val newsEntityList = jpaQuery .orderBy(newsEntity.createdAt.desc()) .offset(20*pageNum) //로컬 테스트를 위해 잠시 5로 둘 것, 원래는 20 .limit(20) @@ -107,7 +107,6 @@ class NewsRepositoryImpl( .leftJoin(newsTagEntity).on(newsTagEntity.news.eq(newsEntity)) .where(newsEntity.isDeleted.eq(false), newsEntity.isPublic.eq(true)) .where(keywordBooleanBuilder).where(tagsBooleanBuilder) - .orderBy(newsEntity.isPinned.desc()) .orderBy(newsEntity.createdAt.desc()) .distinct() .fetch() diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt index 2d7189b7..240a9a87 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt @@ -13,7 +13,6 @@ data class NewsDto( val modifiedAt: LocalDateTime?, val isPublic: Boolean, val isSlide: Boolean, - val isPinned: Boolean, val prevId: Long?, val prevTitle: String?, val nextId: Long?, @@ -32,7 +31,6 @@ data class NewsDto( modifiedAt = this.modifiedAt, isPublic = this.isPublic, isSlide = this.isSlide, - isPinned = this.isPinned, prevId = prevNext?.get(0)?.id, prevTitle = prevNext?.get(0)?.title, nextId = prevNext?.get(1)?.id, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt index b5a53ce1..38745afa 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt @@ -5,7 +5,7 @@ import com.wafflestudio.csereal.core.news.database.* import com.wafflestudio.csereal.core.news.dto.NewsDto import com.wafflestudio.csereal.core.news.dto.NewsSearchResponse import com.wafflestudio.csereal.core.resource.attachment.service.AttachmentService -import com.wafflestudio.csereal.core.resource.mainImage.service.ImageService +import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -14,8 +14,8 @@ import org.springframework.web.multipart.MultipartFile interface NewsService { fun searchNews(tag: List?, keyword: String?, pageNum: Long): NewsSearchResponse fun readNews(newsId: Long, tag: List?, keyword: String?): NewsDto - fun createNews(request: NewsDto, image: MultipartFile?, attachments: List?): NewsDto - fun updateNews(newsId: Long, request: NewsDto): NewsDto + fun createNews(request: NewsDto, mainImage: MultipartFile?, attachments: List?): NewsDto + fun updateNews(newsId: Long, request: NewsDto, mainImage: MultipartFile?, attachments: List?): NewsDto fun deleteNews(newsId: Long) fun enrollTag(tagName: String) } @@ -25,7 +25,7 @@ class NewsServiceImpl( private val newsRepository: NewsRepository, private val tagInNewsRepository: TagInNewsRepository, private val newsTagRepository: NewsTagRepository, - private val imageService: ImageService, + private val mainImageService: MainImageService, private val attachmentService: AttachmentService, ) : NewsService { @Transactional(readOnly = true) @@ -48,7 +48,7 @@ class NewsServiceImpl( if (news.isDeleted) throw CserealException.Csereal404("삭제된 새소식입니다.(newsId: $newsId)") - val imageURL = imageService.createImageURL(news.mainImage) + val imageURL = mainImageService.createImageURL(news.mainImage) val attachments = attachmentService.createAttachments(news.attachments) val prevNext = newsRepository.findPrevNextId(newsId, tag, keyword) @@ -58,7 +58,7 @@ class NewsServiceImpl( } @Transactional - override fun createNews(request: NewsDto, image: MultipartFile?, attachments: List?): NewsDto { + override fun createNews(request: NewsDto, mainImage: MultipartFile?, attachments: List?): NewsDto { val newNews = NewsEntity.of(request) for (tagName in request.tags) { @@ -66,8 +66,12 @@ class NewsServiceImpl( NewsTagEntity.createNewsTag(newNews, tag) } - if(image != null) { - imageService.uploadImage(newNews, image) + if(mainImage != null) { + mainImageService.uploadMainImage(newNews, mainImage) + } + + if(attachments != null) { + attachmentService.uploadAttachments(newNews, attachments) } if(attachments != null) { @@ -76,19 +80,28 @@ class NewsServiceImpl( newsRepository.save(newNews) - val imageURL = imageService.createImageURL(newNews.mainImage) + val imageURL = mainImageService.createImageURL(newNews.mainImage) val attachments = attachmentService.createAttachments(newNews.attachments) return NewsDto.of(newNews, imageURL, attachments, null) } @Transactional - override fun updateNews(newsId: Long, request: NewsDto): NewsDto { + override fun updateNews(newsId: Long, request: NewsDto, mainImage: MultipartFile?, attachments: List?): NewsDto { val news: NewsEntity = newsRepository.findByIdOrNull(newsId) ?: throw CserealException.Csereal404("존재하지 않는 새소식입니다. (newsId: $newsId)") if (news.isDeleted) throw CserealException.Csereal404("삭제된 새소식입니다.") news.update(request) + if(mainImage != null) { + mainImageService.uploadMainImage(news, mainImage) + } + + if(attachments != null) { + news.attachments.clear() + attachmentService.uploadAttachments(news, attachments) + } + val oldTags = news.newsTags.map { it.tag.name } val tagsToRemove = oldTags - request.tags @@ -105,7 +118,7 @@ class NewsServiceImpl( NewsTagEntity.createNewsTag(news,tag) } - val imageURL = imageService.createImageURL(news.mainImage) + val imageURL = mainImageService.createImageURL(news.mainImage) val attachments = attachmentService.createAttachments(news.attachments) return NewsDto.of(news, imageURL, attachments, null) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt index d3c8a7a0..ddf937a8 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt @@ -17,8 +17,6 @@ class NoticeEntity( var isPublic: Boolean, - var isSlide: Boolean, - var isPinned: Boolean, @OneToMany(mappedBy = "notice", cascade = [CascadeType.ALL]) @@ -28,12 +26,10 @@ class NoticeEntity( @JoinColumn(name = "users_id") val author: UserEntity ) : BaseTimeEntity() { - fun update(updateNoticeRequest: NoticeDto) { this.title = updateNoticeRequest.title this.description = updateNoticeRequest.description this.isPublic = updateNoticeRequest.isPublic - this.isSlide = updateNoticeRequest.isSlide this.isPinned = updateNoticeRequest.isPinned } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt index 7329952c..97d7bb42 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt @@ -12,7 +12,6 @@ data class NoticeDto( val createdAt: LocalDateTime?, val modifiedAt: LocalDateTime?, val isPublic: Boolean, - val isSlide: Boolean, val isPinned: Boolean, val prevId: Long?, val prevTitle: String?, @@ -31,7 +30,6 @@ data class NoticeDto( createdAt = this.createdAt, modifiedAt = this.modifiedAt, isPublic = this.isPublic, - isSlide = this.isSlide, isPinned = this.isPinned, prevId = prevNext?.get(0)?.id, prevTitle = prevNext?.get(0)?.title, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt index 59ea7ca5..cef490cc 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt @@ -73,7 +73,6 @@ class NoticeServiceImpl( title = request.title, description = request.description, isPublic = request.isPublic, - isSlide = request.isSlide, isPinned = request.isPinned, author = user ) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentEntity.kt index 2a31daa0..afb4844b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentEntity.kt @@ -1,8 +1,10 @@ package com.wafflestudio.csereal.core.resource.attachment.database import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.core.about.database.AboutEntity +import com.wafflestudio.csereal.core.academics.database.AcademicsEntity +import com.wafflestudio.csereal.core.academics.database.CourseEntity import com.wafflestudio.csereal.core.news.database.NewsEntity -import com.wafflestudio.csereal.core.notice.database.TagInNoticeEntity import com.wafflestudio.csereal.core.seminar.database.SeminarEntity import jakarta.persistence.* @@ -24,6 +26,17 @@ class AttachmentEntity( @JoinColumn(name = "seminar_id") var seminar: SeminarEntity? = null, - ) : BaseTimeEntity() { + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "about_id") + var about: AboutEntity? = null, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "academics_id") + var academics: AcademicsEntity? = null, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "course_id") + var course: CourseEntity? = null, +) : BaseTimeEntity() { } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt index 7361a572..b70e17a0 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt @@ -1,6 +1,9 @@ package com.wafflestudio.csereal.core.resource.attachment.service import com.wafflestudio.csereal.common.controller.AttachmentContentEntityType +import com.wafflestudio.csereal.core.about.database.AboutEntity +import com.wafflestudio.csereal.core.academics.database.AcademicsEntity +import com.wafflestudio.csereal.core.academics.database.CourseEntity import com.wafflestudio.csereal.core.news.database.NewsEntity import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentRepository @@ -20,7 +23,6 @@ interface AttachmentService { contentEntityType: AttachmentContentEntityType, requestAttachments: List, ): List - fun createAttachments(attachments: List?): List? } @@ -95,6 +97,18 @@ class AttachmentServiceImpl( contentEntity.attachments.add(attachment) attachment.seminar = contentEntity } + is AboutEntity -> { + contentEntity.attachments.add(attachment) + attachment.about = contentEntity + } + is AcademicsEntity -> { + contentEntity.attachments.add(attachment) + attachment.academics = contentEntity + } + is CourseEntity -> { + contentEntity.attachments.add(attachment) + attachment.course = contentEntity + } } } } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/FileController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/FileController.kt index 57e372d4..153b36fc 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/FileController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/FileController.kt @@ -17,7 +17,7 @@ import kotlin.text.Charsets.UTF_8 @RequestMapping("/file") @RestController class FileController( - @Value("\${csereal.upload.path}") + @Value("\${csereal_mainImage.upload.path}") private val uploadPath: String ) { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/MainImageController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/MainImageController.kt index e74a26da..7695fff7 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/MainImageController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/MainImageController.kt @@ -1,13 +1,12 @@ package com.wafflestudio.csereal.core.resource.mainImage.api -import com.wafflestudio.csereal.core.resource.mainImage.service.ImageService +import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService import org.springframework.web.bind.annotation.* - @RequestMapping("/image") @RestController class MainImageController( - private val imageService: ImageService + private val mainImageService: MainImageService ) { } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/database/MainImageEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/database/MainImageEntity.kt index 6575d80f..9f86cc41 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/database/MainImageEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/database/MainImageEntity.kt @@ -6,7 +6,7 @@ import jakarta.persistence.* @Entity(name = "mainImage") class MainImageEntity( - val isDeleted : Boolean? = false, + var isDeleted : Boolean? = false, @Column(unique = true) val filename: String, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt index db4da329..0a5690f1 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt @@ -23,9 +23,8 @@ import java.nio.file.Paths import kotlin.io.path.fileSize import kotlin.io.path.name - -interface ImageService { - fun uploadImage( +interface MainImageService { + fun uploadMainImage( contentEntityType: ImageContentEntityType, requestImage: MultipartFile, ): MainImageDto @@ -34,16 +33,16 @@ interface ImageService { } @Service -class ImageServiceImpl( - private val imageRepository: MainImageRepository, - @Value("\${csereal_image.upload.path}") +class MainImageServiceImpl( + private val mainImageRepository: MainImageRepository, + @Value("\${csereal_mainImage.upload.path}") private val path: String, private val endpointProperties: EndpointProperties -) : ImageService { +) : MainImageService { @Transactional - override fun uploadImage( - contentEntity: ImageContentEntityType, + override fun uploadMainImage( + contentEntityType: ImageContentEntityType, requestImage: MultipartFile, ): MainImageDto { Files.createDirectories(Paths.get(path)) @@ -65,7 +64,7 @@ class ImageServiceImpl( val thumbnailFile = Paths.get(totalThumbnailFilename) Thumbnailator.createThumbnail(saveFile.toFile(), thumbnailFile.toFile(), 100, 100); - val image = MainImageEntity( + val mainImage = MainImageEntity( filename = filename, imagesOrder = 1, size = requestImage.size, @@ -77,9 +76,9 @@ class ImageServiceImpl( size = thumbnailFile.fileSize() ) - connectImageToEntity(contentEntity, image) - imageRepository.save(image) - imageRepository.save(thumbnail) + connectMainImageToEntity(contentEntityType, mainImage) + mainImageRepository.save(mainImage) + mainImageRepository.save(thumbnail) return MainImageDto( filename = filename, @@ -89,32 +88,32 @@ class ImageServiceImpl( } @Transactional - override fun createImageURL(image: MainImageEntity?): String? { - return if (image != null) { - "${endpointProperties.backend}/v1/file/${image.filename}" + override fun createImageURL(mainImage: MainImageEntity?): String? { + return if (mainImage != null) { + "${endpointProperties.backend}/v1/file/${mainImage.filename}" } else null } - private fun connectImageToEntity(contentEntity: ImageContentEntityType, image: MainImageEntity) { + private fun connectMainImageToEntity(contentEntity: ImageContentEntityType, mainImage: MainImageEntity) { when (contentEntity) { is NewsEntity -> { - contentEntity.mainImage = image + contentEntity.mainImage = mainImage } is SeminarEntity -> { - contentEntity.mainImage = image + contentEntity.mainImage = mainImage } is AboutEntity -> { - contentEntity.mainImage = image + contentEntity.mainImage = mainImage } is ProfessorEntity -> { - contentEntity.mainImage = image + contentEntity.mainImage = mainImage } is StaffEntity -> { - contentEntity.mainImage = image + contentEntity.mainImage = mainImage } else -> { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt index 126bd80b..eb400578 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt @@ -23,10 +23,10 @@ class SeminarController ( @PostMapping fun createSeminar( @Valid @RequestPart("request") request: SeminarDto, - @RequestPart("image") image: MultipartFile?, + @RequestPart("mainImage") mainImage: MultipartFile?, @RequestPart("attachments") attachments: List? ) : ResponseEntity { - return ResponseEntity.ok(seminarService.createSeminar(request, image, attachments)) + return ResponseEntity.ok(seminarService.createSeminar(request, mainImage, attachments)) } @GetMapping("/{seminarId}") @@ -40,9 +40,11 @@ class SeminarController ( @PatchMapping("/{seminarId}") fun updateSeminar( @PathVariable seminarId: Long, - @Valid @RequestBody request: SeminarDto, + @Valid @RequestPart("request") request: SeminarDto, + @RequestPart("mainImage") mainImage: MultipartFile?, + @RequestPart("attachments") attachments: List? ) : ResponseEntity { - return ResponseEntity.ok(seminarService.updateSeminar(seminarId, request)) + return ResponseEntity.ok(seminarService.updateSeminar(seminarId, request, mainImage, attachments)) } @DeleteMapping("/{seminarId}") diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt index 4489375a..04c6359d 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt @@ -21,14 +21,12 @@ class SeminarEntity( @Column(columnDefinition = "text") var introduction: String, - var category: String, - // 연사 정보 var name: String, - var speakerUrl: String?, + var speakerURL: String?, var speakerTitle: String?, var affiliation: String, - var affiliationUrl: String?, + var affiliationURL: String?, var startDate: String?, var startTime: String?, @@ -39,12 +37,8 @@ class SeminarEntity( var host: String?, - // var seminarFile: File, - var isPublic: Boolean, - var isSlide: Boolean, - var additionalNote: String?, @OneToOne @@ -63,12 +57,11 @@ class SeminarEntity( title = seminarDto.title, description = seminarDto.description, introduction = seminarDto.introduction, - category = seminarDto.category, name = seminarDto.name, - speakerUrl = seminarDto.speakerUrl, + speakerURL = seminarDto.speakerURL, speakerTitle = seminarDto.speakerTitle, affiliation = seminarDto.affiliation, - affiliationUrl = seminarDto.affiliationUrl, + affiliationURL = seminarDto.affiliationURL, startDate = seminarDto.startDate, startTime = seminarDto.startTime, endDate = seminarDto.endDate, @@ -77,7 +70,6 @@ class SeminarEntity( host = seminarDto.host, additionalNote = seminarDto.additionalNote, isPublic = seminarDto.isPublic, - isSlide = seminarDto.isSlide, ) } } @@ -86,12 +78,11 @@ class SeminarEntity( title = updateSeminarRequest.title description = updateSeminarRequest.description introduction = updateSeminarRequest.introduction - category = updateSeminarRequest.category name = updateSeminarRequest.name - speakerUrl = updateSeminarRequest.speakerUrl + speakerURL = updateSeminarRequest.speakerURL speakerTitle = updateSeminarRequest.speakerTitle affiliation = updateSeminarRequest.affiliation - affiliationUrl = updateSeminarRequest.affiliationUrl + affiliationURL = updateSeminarRequest.affiliationURL startDate = updateSeminarRequest.startDate startTime = updateSeminarRequest.startTime endDate = updateSeminarRequest.endDate @@ -100,6 +91,5 @@ class SeminarEntity( host = updateSeminarRequest.host additionalNote = updateSeminarRequest.additionalNote isPublic = updateSeminarRequest.isPublic - isSlide = updateSeminarRequest.isSlide } } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt index 42c2307f..c0a20380 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt @@ -2,28 +2,29 @@ package com.wafflestudio.csereal.core.seminar.dto import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse import com.wafflestudio.csereal.core.seminar.database.SeminarEntity +import java.time.LocalDate +import java.time.LocalDateTime data class SeminarDto( val id: Long, val title: String, val description: String, val introduction: String, - val category: String, val name: String, - val speakerUrl: String?, + val speakerURL: String?, val speakerTitle: String?, val affiliation: String, - val affiliationUrl: String?, + val affiliationURL: String?, val startDate: String?, val startTime: String?, val endDate: String?, val endTime: String?, val location: String, val host: String?, - // val seminarFile: File, val additionalNote: String?, + val createdAt: LocalDateTime?, + val modifiedAt: LocalDateTime?, val isPublic: Boolean, - val isSlide: Boolean, val prevId: Long?, val prevTitle: String?, val nextId: Long?, @@ -39,12 +40,11 @@ data class SeminarDto( title = this.title, description = this.description, introduction = this.introduction, - category = this.category, name = this.name, - speakerUrl = this.speakerUrl, + speakerURL = this.speakerURL, speakerTitle = this.speakerTitle, affiliation = this.affiliation, - affiliationUrl = this.affiliationUrl, + affiliationURL = this.affiliationURL, startDate = this.startDate, startTime = this.startTime, endDate = this.endDate, @@ -52,8 +52,9 @@ data class SeminarDto( location = this.location, host = this.host, additionalNote = this.additionalNote, + createdAt = this.createdAt, + modifiedAt = this.modifiedAt, isPublic = this.isPublic, - isSlide = this.isSlide, prevId = prevNext?.get(0)?.id, prevTitle = prevNext?.get(0)?.title, nextId = prevNext?.get(1)?.id, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt index e1a40f94..2d28eef1 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt @@ -2,7 +2,7 @@ package com.wafflestudio.csereal.core.seminar.service import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.core.resource.attachment.service.AttachmentService -import com.wafflestudio.csereal.core.resource.mainImage.service.ImageService +import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService import com.wafflestudio.csereal.core.seminar.database.SeminarEntity import com.wafflestudio.csereal.core.seminar.database.SeminarRepository import com.wafflestudio.csereal.core.seminar.dto.SeminarDto @@ -14,16 +14,16 @@ import org.springframework.web.multipart.MultipartFile interface SeminarService { fun searchSeminar(keyword: String?, pageNum: Long): SeminarSearchResponse - fun createSeminar(request: SeminarDto, image: MultipartFile?, attachments: List?): SeminarDto + fun createSeminar(request: SeminarDto, mainImage: MultipartFile?, attachments: List?): SeminarDto fun readSeminar(seminarId: Long, keyword: String?): SeminarDto - fun updateSeminar(seminarId: Long, request: SeminarDto): SeminarDto + fun updateSeminar(seminarId: Long, request: SeminarDto, mainImage: MultipartFile?, attachments: List?): SeminarDto fun deleteSeminar(seminarId: Long) } @Service class SeminarServiceImpl( private val seminarRepository: SeminarRepository, - private val imageService: ImageService, + private val mainImageService: MainImageService, private val attachmentService: AttachmentService, ) : SeminarService { @Transactional(readOnly = true) @@ -32,11 +32,11 @@ class SeminarServiceImpl( } @Transactional - override fun createSeminar(request: SeminarDto, image: MultipartFile?, attachments: List?): SeminarDto { + override fun createSeminar(request: SeminarDto, mainImage: MultipartFile?, attachments: List?): SeminarDto { val newSeminar = SeminarEntity.of(request) - if(image != null) { - imageService.uploadImage(newSeminar, image) + if(mainImage != null) { + mainImageService.uploadMainImage(newSeminar, mainImage) } if(attachments != null) { @@ -45,9 +45,8 @@ class SeminarServiceImpl( seminarRepository.save(newSeminar) - val imageURL = imageService.createImageURL(newSeminar.mainImage) + val imageURL = mainImageService.createImageURL(newSeminar.mainImage) val attachments = attachmentService.createAttachments(newSeminar.attachments) - return SeminarDto.of(newSeminar, imageURL, attachments, null) } @@ -58,7 +57,7 @@ class SeminarServiceImpl( if (seminar.isDeleted) throw CserealException.Csereal400("삭제된 세미나입니다. (seminarId: $seminarId)") - val imageURL = imageService.createImageURL(seminar.mainImage) + val imageURL = mainImageService.createImageURL(seminar.mainImage) val attachments = attachmentService.createAttachments(seminar.attachments) val prevNext = seminarRepository.findPrevNextId(seminarId, keyword) @@ -67,14 +66,23 @@ class SeminarServiceImpl( } @Transactional - override fun updateSeminar(seminarId: Long, request: SeminarDto): SeminarDto { + override fun updateSeminar(seminarId: Long, request: SeminarDto, mainImage: MultipartFile?, attachments: List?): SeminarDto { val seminar: SeminarEntity = seminarRepository.findByIdOrNull(seminarId) ?: throw CserealException.Csereal404("존재하지 않는 세미나입니다") if(seminar.isDeleted) throw CserealException.Csereal404("삭제된 세미나입니다. (seminarId: $seminarId)") seminar.update(request) - val imageURL = imageService.createImageURL(seminar.mainImage) + if(mainImage != null) { + mainImageService.uploadMainImage(seminar, mainImage) + } + + if(attachments != null) { + seminar.attachments.clear() + attachmentService.uploadAttachments(seminar, attachments) + } + + val imageURL = mainImageService.createImageURL(seminar.mainImage) val attachments = attachmentService.createAttachments(seminar.attachments) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 454ddb8e..5880b2a7 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -61,9 +61,9 @@ logging.level: springframework: security: DEBUG -csereal_image: +csereal_mainImage: upload: - path: /app/image/ + path: /app/mainImage/ csereal_attachment: upload: @@ -87,9 +87,11 @@ spring: idsnucse: redirect-uri: http://${URL}/login/oauth2/code/idsnucse -csereal_image: + + +csereal_mainImage: upload: - path: /app/image/ + path: /app/mainImage/ csereal_attachment: upload: From 35657041f9f0242875493c6449f0ebb271e3dcdc Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Sat, 2 Sep 2023 14:24:14 +0900 Subject: [PATCH 037/124] =?UTF-8?q?fix:=20research=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=20=ED=94=84=EB=A1=A0=ED=8A=B8=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EC=B6=B0=20=ED=98=91=EC=9D=98=20(#52)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: professor, staff에 uploadImage 추가 * fix: research에서 isPublic 제거, feat: lab에서 attachments 추가 * fix: attachments -> attachmentResponses 변경 * feat: LabProfessorResponse 추가 * fix: pdf를 list가 아닌 단일항목으로 수정 * feat: readLab 추가 * feat: ResearchLabReponse 추가 및 주석 삭제 * fix: researchDetail에 사진, 첨부파일 업로드 추가 * fix: pr 리뷰 수정 * fix: 오타 수정 --- ...yType.kt => MainImageContentEntityType.kt} | 2 +- .../core/about/database/AboutEntity.kt | 4 +- .../csereal/core/about/dto/AboutDto.kt | 4 +- .../core/about/service/AboutService.kt | 20 +-- .../core/academics/dto/AcademicsDto.kt | 4 +- .../academics/service/AcademicsService.kt | 22 +-- .../core/member/api/ProfessorController.kt | 5 +- .../core/member/api/StaffController.kt | 9 +- .../core/member/database/ProfessorEntity.kt | 4 +- .../core/member/database/StaffEntity.kt | 4 +- .../core/member/service/ProfessorService.kt | 9 +- .../core/member/service/StaffService.kt | 9 +- .../csereal/core/news/database/NewsEntity.kt | 4 +- .../csereal/core/news/dto/NewsDto.kt | 4 +- .../csereal/core/news/service/NewsService.kt | 20 +-- .../core/research/api/ResearchController.kt | 37 +++-- .../core/research/database/LabEntity.kt | 16 +- .../core/research/database/ResearchEntity.kt | 23 ++- .../csereal/core/research/dto/LabDto.kt | 15 +- .../core/research/dto/LabProfessorResponse.kt | 7 + .../csereal/core/research/dto/ResearchDto.kt | 15 +- .../core/research/dto/ResearchLabResponse.kt | 7 + .../core/research/service/ResearchService.kt | 140 +++++++++++++----- .../attachment/database/AttachmentEntity.kt | 10 ++ .../attachment/service/AttachmentService.kt | 50 ++++++- .../dto/introductionMaterialDto.kt | 8 - .../mainImage/service/MainImageService.kt | 13 +- .../core/seminar/database/SeminarEntity.kt | 4 +- .../csereal/core/seminar/dto/SeminarDto.kt | 4 +- .../core/seminar/service/SeminarService.kt | 19 ++- 30 files changed, 320 insertions(+), 172 deletions(-) rename src/main/kotlin/com/wafflestudio/csereal/common/controller/{ImageContentEntityType.kt => MainImageContentEntityType.kt} (82%) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/research/dto/LabProfessorResponse.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchLabResponse.kt delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/resource/introductionMaterial/dto/introductionMaterialDto.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/controller/ImageContentEntityType.kt b/src/main/kotlin/com/wafflestudio/csereal/common/controller/MainImageContentEntityType.kt similarity index 82% rename from src/main/kotlin/com/wafflestudio/csereal/common/controller/ImageContentEntityType.kt rename to src/main/kotlin/com/wafflestudio/csereal/common/controller/MainImageContentEntityType.kt index 8f58c860..eae7403c 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/controller/ImageContentEntityType.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/controller/MainImageContentEntityType.kt @@ -2,6 +2,6 @@ package com.wafflestudio.csereal.common.controller import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity -interface ImageContentEntityType { +interface MainImageContentEntityType { fun bringMainImage(): MainImageEntity? } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt index 8dd8b967..a77058a7 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt @@ -2,7 +2,7 @@ package com.wafflestudio.csereal.core.about.database import com.wafflestudio.csereal.common.config.BaseTimeEntity import com.wafflestudio.csereal.common.controller.AttachmentContentEntityType -import com.wafflestudio.csereal.common.controller.ImageContentEntityType +import com.wafflestudio.csereal.common.controller.MainImageContentEntityType import com.wafflestudio.csereal.core.about.dto.AboutDto import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity @@ -26,7 +26,7 @@ class AboutEntity( @OneToOne var mainImage: MainImageEntity? = null, - ) : BaseTimeEntity(), ImageContentEntityType, AttachmentContentEntityType { + ) : BaseTimeEntity(), MainImageContentEntityType, AttachmentContentEntityType { override fun bringMainImage(): MainImageEntity? = mainImage override fun bringAttachments(): List = attachments diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutDto.kt index 2d7c96e2..60233a13 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutDto.kt @@ -18,7 +18,7 @@ data class AboutDto( val attachments: List?, ) { companion object { - fun of(entity: AboutEntity, imageURL: String?, attachments: List?) : AboutDto = entity.run { + fun of(entity: AboutEntity, imageURL: String?, attachmentResponses: List) : AboutDto = entity.run { AboutDto( id = this.id, name = this.name, @@ -29,7 +29,7 @@ data class AboutDto( modifiedAt = this.modifiedAt, locations = this.locations.map { it.name }, imageURL = imageURL, - attachments = attachments, + attachments = attachmentResponses, ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt index 9a3af02b..f973a5c5 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt @@ -42,14 +42,14 @@ class AboutServiceImpl( } if(attachments != null) { - attachmentService.uploadAttachments(newAbout, attachments) + attachmentService.uploadAllAttachments(newAbout, attachments) } aboutRepository.save(newAbout) val imageURL = mainImageService.createImageURL(newAbout.mainImage) - val attachments = attachmentService.createAttachments(newAbout.attachments) + val attachmentResponses = attachmentService.createAttachmentResponses(newAbout.attachments) - return AboutDto.of(newAbout, imageURL, attachments) + return AboutDto.of(newAbout, imageURL, attachmentResponses) } @Transactional(readOnly = true) @@ -57,18 +57,18 @@ class AboutServiceImpl( val enumPostType = makeStringToEnum(postType) val about = aboutRepository.findByPostType(enumPostType) val imageURL = mainImageService.createImageURL(about.mainImage) - val attachments = attachmentService.createAttachments(about.attachments) + val attachmentResponses = attachmentService.createAttachmentResponses(about.attachments) - return AboutDto.of(about, imageURL, attachments) + return AboutDto.of(about, imageURL, attachmentResponses) } @Transactional(readOnly = true) override fun readAllClubs(): List { val clubs = aboutRepository.findAllByPostTypeOrderByName(AboutPostType.STUDENT_CLUBS).map { val imageURL = mainImageService.createImageURL(it.mainImage) - val attachments = attachmentService.createAttachments(it.attachments) - AboutDto.of(it, imageURL, attachments) + val attachmentResponses = attachmentService.createAttachmentResponses(it.attachments) + AboutDto.of(it, imageURL, attachmentResponses) } return clubs @@ -78,8 +78,8 @@ class AboutServiceImpl( override fun readAllFacilities(): List { val facilities = aboutRepository.findAllByPostTypeOrderByName(AboutPostType.FACILITIES).map { val imageURL = mainImageService.createImageURL(it.mainImage) - val attachments = attachmentService.createAttachments(it.attachments) - AboutDto.of(it, imageURL, attachments) + val attachmentResponses = attachmentService.createAttachmentResponses(it.attachments) + AboutDto.of(it, imageURL, attachmentResponses) } return facilities @@ -89,7 +89,7 @@ class AboutServiceImpl( override fun readAllDirections(): List { val directions = aboutRepository.findAllByPostTypeOrderByName(AboutPostType.DIRECTIONS).map { val imageURL = mainImageService.createImageURL(it.mainImage) - val attachments = attachmentService.createAttachments(it.attachments) + val attachments = attachmentService.createAttachmentResponses(it.attachments) AboutDto.of(it, imageURL, attachments) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsDto.kt index cc16ce4d..0df98fd5 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsDto.kt @@ -14,7 +14,7 @@ data class AcademicsDto( val attachments: List?, ) { companion object { - fun of(entity: AcademicsEntity, attachments: List?) : AcademicsDto = entity.run { + fun of(entity: AcademicsEntity, attachmentResponses: List) : AcademicsDto = entity.run { AcademicsDto( id = this.id, name = this.name, @@ -22,7 +22,7 @@ data class AcademicsDto( year = this.year, createdAt = this.createdAt, modifiedAt = this.modifiedAt, - attachments = attachments, + attachments = attachmentResponses, ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt index 4421c6f5..024565a3 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt @@ -37,15 +37,15 @@ class AcademicsServiceImpl( val newAcademics = AcademicsEntity.of(enumStudentType, enumPostType, request) if(attachments != null) { - attachmentService.uploadAttachments(newAcademics, attachments) + attachmentService.uploadAllAttachments(newAcademics, attachments) } academicsRepository.save(newAcademics) - val attachments = attachmentService.createAttachments(newAcademics.attachments) + val attachmentResponses = attachmentService.createAttachmentResponses(newAcademics.attachments) - return AcademicsDto.of(newAcademics, attachments) + return AcademicsDto.of(newAcademics, attachmentResponses) } @Transactional(readOnly = true) @@ -56,9 +56,9 @@ class AcademicsServiceImpl( val academics = academicsRepository.findByStudentTypeAndPostType(enumStudentType, enumPostType) - val attachments = attachmentService.createAttachments(academics.attachments) + val attachmentResponses = attachmentService.createAttachmentResponses(academics.attachments) - return AcademicsDto.of(academics, attachments) + return AcademicsDto.of(academics, attachmentResponses) } @Transactional @@ -68,9 +68,9 @@ class AcademicsServiceImpl( courseRepository.save(course) - val attachments = attachmentService.createAttachments(course.attachments) + val attachmentResponses = attachmentService.createAttachmentResponses(course.attachments) - return CourseDto.of(course, attachments) + return CourseDto.of(course, attachmentResponses) } @Transactional(readOnly = true) @@ -78,8 +78,8 @@ class AcademicsServiceImpl( val enumStudentType = makeStringToAcademicsStudentType(studentType) val courseDtoList = courseRepository.findAllByStudentTypeOrderByNameAsc(enumStudentType).map { - val attachments = attachmentService.createAttachments(it.attachments) - CourseDto.of(it, attachments) + val attachmentResponses = attachmentService.createAttachmentResponses(it.attachments) + CourseDto.of(it, attachmentResponses) } return courseDtoList } @@ -87,9 +87,9 @@ class AcademicsServiceImpl( @Transactional(readOnly = true) override fun readCourse(name: String): CourseDto { val course = courseRepository.findByName(name) - val attachments = attachmentService.createAttachments(course.attachments) + val attachmentResponses = attachmentService.createAttachmentResponses(course.attachments) - return CourseDto.of(course, attachments) + return CourseDto.of(course, attachmentResponses) } @Transactional(readOnly = true) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt index 6431351f..fa71dd42 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt @@ -40,9 +40,10 @@ class ProfessorController( @PatchMapping("/{professorId}") fun updateProfessor( @PathVariable professorId: Long, - @RequestBody updateProfessorRequest: ProfessorDto + @RequestPart("request") updateProfessorRequest: ProfessorDto, + @RequestPart("mainImage") mainImage: MultipartFile?, ): ResponseEntity { - return ResponseEntity.ok(professorService.updateProfessor(professorId, updateProfessorRequest)) + return ResponseEntity.ok(professorService.updateProfessor(professorId, updateProfessorRequest, mainImage)) } @DeleteMapping("/{professorId}") diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt index 611d2afd..b9584fb0 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt @@ -31,9 +31,12 @@ class StaffController( return ResponseEntity.ok(staffService.getAllStaff()) } - @PatchMapping("/{staffId}") - fun updateStaff(@PathVariable staffId: Long, @RequestBody updateStaffRequest: StaffDto): ResponseEntity { - return ResponseEntity.ok(staffService.updateStaff(staffId, updateStaffRequest)) + fun updateStaff( + @PathVariable staffId: Long, + @RequestPart("request") updateStaffRequest: StaffDto, + @RequestPart("mainImage") mainImage: MultipartFile?, + ): ResponseEntity { + return ResponseEntity.ok(staffService.updateStaff(staffId, updateStaffRequest, mainImage)) } @DeleteMapping("/{staffId}") diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt index 1414a0ae..4036a9dd 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt @@ -1,7 +1,7 @@ package com.wafflestudio.csereal.core.member.database import com.wafflestudio.csereal.common.config.BaseTimeEntity -import com.wafflestudio.csereal.common.controller.ImageContentEntityType +import com.wafflestudio.csereal.common.controller.MainImageContentEntityType import com.wafflestudio.csereal.core.member.dto.ProfessorDto import com.wafflestudio.csereal.core.research.database.LabEntity import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity @@ -44,7 +44,7 @@ class ProfessorEntity( @OneToOne var mainImage: MainImageEntity? = null, -) : BaseTimeEntity(), ImageContentEntityType { +) : BaseTimeEntity(), MainImageContentEntityType { override fun bringMainImage(): MainImageEntity? = mainImage companion object { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffEntity.kt index d4e5ec3a..355d4125 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffEntity.kt @@ -1,7 +1,7 @@ package com.wafflestudio.csereal.core.member.database import com.wafflestudio.csereal.common.config.BaseTimeEntity -import com.wafflestudio.csereal.common.controller.ImageContentEntityType +import com.wafflestudio.csereal.common.controller.MainImageContentEntityType import com.wafflestudio.csereal.core.member.dto.StaffDto import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity import jakarta.persistence.CascadeType @@ -24,7 +24,7 @@ class StaffEntity( @OneToOne var mainImage: MainImageEntity? = null, - ) : BaseTimeEntity(), ImageContentEntityType { + ) : BaseTimeEntity(), MainImageContentEntityType { override fun bringMainImage(): MainImageEntity? = mainImage companion object { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt index 86e6f8cf..4988369a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt @@ -17,7 +17,7 @@ interface ProfessorService { fun getProfessor(professorId: Long): ProfessorDto fun getActiveProfessors(): ProfessorPageDto fun getInactiveProfessors(): List - fun updateProfessor(professorId: Long, updateProfessorRequest: ProfessorDto): ProfessorDto + fun updateProfessor(professorId: Long, updateProfessorRequest: ProfessorDto, mainImage: MultipartFile?): ProfessorDto fun deleteProfessor(professorId: Long) } @@ -94,8 +94,7 @@ class ProfessorServiceImpl( .sortedBy { it.name } } - override fun updateProfessor(professorId: Long, updateProfessorRequest: ProfessorDto): ProfessorDto { - + override fun updateProfessor(professorId: Long, updateProfessorRequest: ProfessorDto, mainImage: MultipartFile?): ProfessorDto { val professor = professorRepository.findByIdOrNull(professorId) ?: throw CserealException.Csereal404("해당 교수님을 찾을 수 없습니다. professorId: ${professorId}") @@ -107,6 +106,10 @@ class ProfessorServiceImpl( professor.update(updateProfessorRequest) + if(mainImage != null) { + mainImageService.uploadMainImage(professor, mainImage) + } + // 학력 업데이트 val oldEducations = professor.educations.map { it.name } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt index a68630d7..a08ca026 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt @@ -16,7 +16,7 @@ interface StaffService { fun createStaff(createStaffRequest: StaffDto, mainImage: MultipartFile?): StaffDto fun getStaff(staffId: Long): StaffDto fun getAllStaff(): List - fun updateStaff(staffId: Long, updateStaffRequest: StaffDto): StaffDto + fun updateStaff(staffId: Long, updateStaffRequest: StaffDto, mainImage: MultipartFile?): StaffDto fun deleteStaff(staffId: Long) } @@ -62,13 +62,16 @@ class StaffServiceImpl( }.sortedBy { it.name } } - override fun updateStaff(staffId: Long, updateStaffRequest: StaffDto): StaffDto { - + override fun updateStaff(staffId: Long, updateStaffRequest: StaffDto, mainImage: MultipartFile?): StaffDto { val staff = staffRepository.findByIdOrNull(staffId) ?: throw CserealException.Csereal404("해당 행정직원을 찾을 수 없습니다. staffId: ${staffId}") staff.update(updateStaffRequest) + if(mainImage != null) { + mainImageService.uploadMainImage(staff, mainImage) + } + // 주요 업무 업데이트 val oldTasks = staff.tasks.map { it.name } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt index 8349a433..327ebcd3 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt @@ -2,7 +2,7 @@ package com.wafflestudio.csereal.core.news.database import com.wafflestudio.csereal.common.config.BaseTimeEntity import com.wafflestudio.csereal.common.controller.AttachmentContentEntityType -import com.wafflestudio.csereal.common.controller.ImageContentEntityType +import com.wafflestudio.csereal.common.controller.MainImageContentEntityType import com.wafflestudio.csereal.core.news.dto.NewsDto import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity @@ -30,7 +30,7 @@ class NewsEntity( @OneToMany(mappedBy = "news", cascade = [CascadeType.ALL]) var newsTags: MutableSet = mutableSetOf() -): BaseTimeEntity(), ImageContentEntityType, AttachmentContentEntityType { +): BaseTimeEntity(), MainImageContentEntityType, AttachmentContentEntityType { override fun bringMainImage() = mainImage override fun bringAttachments() = attachments diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt index 240a9a87..3232d84f 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt @@ -21,7 +21,7 @@ data class NewsDto( val attachments: List?, ) { companion object { - fun of(entity: NewsEntity, imageURL: String?, attachments: List?, prevNext: Array?) : NewsDto = entity.run { + fun of(entity: NewsEntity, imageURL: String?, attachmentResponses: List, prevNext: Array?) : NewsDto = entity.run { NewsDto( id = this.id, title = this.title, @@ -36,7 +36,7 @@ data class NewsDto( nextId = prevNext?.get(1)?.id, nextTitle = prevNext?.get(1)?.title, imageURL = imageURL, - attachments = attachments, + attachments = attachmentResponses, ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt index 38745afa..e2904321 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt @@ -49,12 +49,12 @@ class NewsServiceImpl( if (news.isDeleted) throw CserealException.Csereal404("삭제된 새소식입니다.(newsId: $newsId)") val imageURL = mainImageService.createImageURL(news.mainImage) - val attachments = attachmentService.createAttachments(news.attachments) + val attachmentResponses = attachmentService.createAttachmentResponses(news.attachments) val prevNext = newsRepository.findPrevNextId(newsId, tag, keyword) ?: throw CserealException.Csereal400("이전글 다음글이 존재하지 않습니다.(newsId=$newsId)") - return NewsDto.of(news, imageURL, attachments, prevNext) + return NewsDto.of(news, imageURL, attachmentResponses, prevNext) } @Transactional @@ -71,19 +71,15 @@ class NewsServiceImpl( } if(attachments != null) { - attachmentService.uploadAttachments(newNews, attachments) - } - - if(attachments != null) { - attachmentService.uploadAttachments(newNews, attachments) + attachmentService.uploadAllAttachments(newNews, attachments) } newsRepository.save(newNews) val imageURL = mainImageService.createImageURL(newNews.mainImage) - val attachments = attachmentService.createAttachments(newNews.attachments) + val attachmentResponses = attachmentService.createAttachmentResponses(newNews.attachments) - return NewsDto.of(newNews, imageURL, attachments, null) + return NewsDto.of(newNews, imageURL, attachmentResponses, null) } @Transactional @@ -99,7 +95,7 @@ class NewsServiceImpl( if(attachments != null) { news.attachments.clear() - attachmentService.uploadAttachments(news, attachments) + attachmentService.uploadAllAttachments(news, attachments) } val oldTags = news.newsTags.map { it.tag.name } @@ -119,9 +115,9 @@ class NewsServiceImpl( } val imageURL = mainImageService.createImageURL(news.mainImage) - val attachments = attachmentService.createAttachments(news.attachments) + val attachmentResponses = attachmentService.createAttachmentResponses(news.attachments) - return NewsDto.of(news, imageURL, attachments, null) + return NewsDto.of(news, imageURL, attachmentResponses, null) } @Transactional diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt index 187623d6..af093051 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt @@ -7,6 +7,7 @@ import com.wafflestudio.csereal.core.research.service.ResearchService import jakarta.validation.Valid import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* +import org.springframework.web.multipart.MultipartFile @RequestMapping("/api/v1/research") @RestController @@ -15,38 +16,50 @@ class ResearchController( ) { @PostMapping fun createResearchDetail( - @Valid @RequestBody request: ResearchDto - ) : ResponseEntity { - return ResponseEntity.ok(researchService.createResearchDetail(request)) + @Valid @RequestPart("request") request: ResearchDto, + @RequestPart("mainImage") mainImage: MultipartFile?, + @RequestPart("attachments") attachments: List? + ): ResponseEntity { + return ResponseEntity.ok(researchService.createResearchDetail(request, mainImage, attachments)) } @GetMapping("/groups") - fun readAllResearchGroups() : ResponseEntity { + fun readAllResearchGroups(): ResponseEntity { return ResponseEntity.ok(researchService.readAllResearchGroups()) } @GetMapping("/centers") - fun readAllResearchCenters() : ResponseEntity> { + fun readAllResearchCenters(): ResponseEntity> { return ResponseEntity.ok(researchService.readAllResearchCenters()) } @PatchMapping("/{researchId}") fun updateResearchDetail( @PathVariable researchId: Long, - @Valid @RequestBody request: ResearchDto - ) : ResponseEntity { - return ResponseEntity.ok(researchService.updateResearchDetail(researchId, request)) + @Valid @RequestPart("request") request: ResearchDto, + @RequestPart("mainImage") mainImage: MultipartFile?, + @RequestPart("attachments") attachments: List? + ): ResponseEntity { + return ResponseEntity.ok(researchService.updateResearchDetail(researchId, request, mainImage, attachments)) } @PostMapping("/lab") fun createLab( - @Valid @RequestBody request: LabDto - ) : ResponseEntity { - return ResponseEntity.ok(researchService.createLab(request)) + @Valid @RequestPart("request") request: LabDto, + @RequestPart("pdf") pdf: MultipartFile? + ): ResponseEntity { + return ResponseEntity.ok(researchService.createLab(request, pdf)) } @GetMapping("/labs") - fun readAllLabs() : ResponseEntity> { + fun readAllLabs(): ResponseEntity> { return ResponseEntity.ok(researchService.readAllLabs()) } + + @GetMapping("/lab/{labId}") + fun readLab( + @PathVariable labId: Long, + ): ResponseEntity { + return ResponseEntity.ok(researchService.readLab(labId)) + } } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabEntity.kt index 3720f60e..63aaa866 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabEntity.kt @@ -1,13 +1,14 @@ package com.wafflestudio.csereal.core.research.database import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.common.controller.AttachmentContentEntityType import com.wafflestudio.csereal.core.member.database.ProfessorEntity import com.wafflestudio.csereal.core.research.dto.LabDto +import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity import jakarta.persistence.* @Entity(name = "lab") class LabEntity( - val name: String, @OneToMany(mappedBy = "lab") @@ -16,7 +17,10 @@ class LabEntity( val location: String?, val tel: String?, val acronym: String?, - val pdf: String?, + + @OneToOne + var pdf: AttachmentEntity? = null, + val youtube: String?, @ManyToOne(fetch = FetchType.LAZY) @@ -24,24 +28,20 @@ class LabEntity( var research: ResearchEntity, val description: String?, - val websiteURL: String?, - val isPublic: Boolean, ) : BaseTimeEntity() { companion object { - fun of(researchGroup: ResearchEntity, labDto: LabDto) : LabEntity { + fun of(labDto: LabDto, researchGroup: ResearchEntity) : LabEntity { return LabEntity( name = labDto.name, location = labDto.location, tel = labDto.tel, acronym = labDto.acronym, - pdf = labDto.introductionMaterials?.pdf, - youtube = labDto.introductionMaterials?.youtube, + youtube = labDto.youtube, research = researchGroup, description = labDto.description, websiteURL = labDto.websiteURL, - isPublic = labDto.isPublic, ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchEntity.kt index ad3d294f..70cbca2b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchEntity.kt @@ -1,7 +1,11 @@ package com.wafflestudio.csereal.core.research.database import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.common.controller.AttachmentContentEntityType +import com.wafflestudio.csereal.common.controller.MainImageContentEntityType import com.wafflestudio.csereal.core.research.dto.ResearchDto +import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity +import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity import jakarta.persistence.* @Entity(name = "research") @@ -10,24 +14,27 @@ class ResearchEntity( var postType: ResearchPostType, var name: String, - var description: String?, - var websiteURL: String?, + @OneToMany(mappedBy = "research", cascade = [CascadeType.ALL], orphanRemoval = true) + var labs: MutableList = mutableListOf(), - var isPublic: Boolean, + @OneToOne + var mainImage: MainImageEntity? = null, @OneToMany(mappedBy = "research", cascade = [CascadeType.ALL], orphanRemoval = true) - var labs: MutableList = mutableListOf() -): BaseTimeEntity() { + var attachments: MutableList = mutableListOf(), + + ) : BaseTimeEntity(), MainImageContentEntityType, AttachmentContentEntityType { + override fun bringMainImage() = mainImage + override fun bringAttachments() = attachments + companion object { - fun of(researchDto: ResearchDto) : ResearchEntity { + fun of(researchDto: ResearchDto): ResearchEntity { return ResearchEntity( postType = researchDto.postType, name = researchDto.name, description = researchDto.description, - websiteURL = researchDto.websiteURL, - isPublic = researchDto.isPublic ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/LabDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/LabDto.kt index 82f95b23..55e19ea0 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/LabDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/LabDto.kt @@ -1,35 +1,34 @@ package com.wafflestudio.csereal.core.research.dto import com.wafflestudio.csereal.core.research.database.LabEntity -import com.wafflestudio.csereal.core.resource.introductionMaterial.dto.IntroductionMaterialDto data class LabDto( val id: Long, val name: String, - val professors: List?, + val professors: List?, val location: String?, val tel: String?, val acronym: String?, - val introductionMaterials: IntroductionMaterialDto?, + val pdf: String?, + val youtube: String?, val group: String, val description: String?, val websiteURL: String?, - val isPublic: Boolean, ) { companion object { - fun of(entity: LabEntity): LabDto = entity.run { + fun of(entity: LabEntity, pdfURL: String): LabDto = entity.run { LabDto( id = this.id, name = this.name, - professors = this.professors.map { it.name }, + professors = this.professors.map { LabProfessorResponse(id = it.id, name = it.name) }, location = this.location, tel = this.tel, acronym = this.acronym, - introductionMaterials = IntroductionMaterialDto(this.pdf, this.youtube), + pdf = pdfURL, + youtube = this.youtube, group = this.research.name, description = this.description, websiteURL = this.websiteURL, - isPublic = this.isPublic ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/LabProfessorResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/LabProfessorResponse.kt new file mode 100644 index 00000000..9ebbdd0a --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/LabProfessorResponse.kt @@ -0,0 +1,7 @@ +package com.wafflestudio.csereal.core.research.dto + +data class LabProfessorResponse( + val id: Long, + val name: String, +) { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchDto.kt index e81ba4fd..0f694e81 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchDto.kt @@ -2,6 +2,7 @@ package com.wafflestudio.csereal.core.research.dto import com.wafflestudio.csereal.core.research.database.ResearchEntity import com.wafflestudio.csereal.core.research.database.ResearchPostType +import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse import java.time.LocalDateTime data class ResearchDto( @@ -9,24 +10,24 @@ data class ResearchDto( val postType: ResearchPostType, val name: String, val description: String?, - val websiteURL: String?, val createdAt: LocalDateTime?, val modifiedAt: LocalDateTime?, - val isPublic: Boolean, - val labsId: List? + val labs: List?, + val imageURL: String?, + val attachments: List?, ) { companion object { - fun of(entity: ResearchEntity) = entity.run { + fun of(entity: ResearchEntity, imageURL: String?, attachmentResponse: List) = entity.run { ResearchDto( id = this.id, postType = this.postType, name = this.name, description = this.description, - websiteURL = this.websiteURL, createdAt = this.createdAt, modifiedAt = this.modifiedAt, - isPublic = this.isPublic, - labsId = this.labs.map { it.id } + labs = this.labs.map { ResearchLabResponse(id = it.id, name = it.name) }, + imageURL = imageURL, + attachments = attachmentResponse ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchLabResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchLabResponse.kt new file mode 100644 index 00000000..2761c2cb --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchLabResponse.kt @@ -0,0 +1,7 @@ +package com.wafflestudio.csereal.core.research.dto + +data class ResearchLabResponse( + val id: Long, + val name: String, +) { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt index e42bc7c7..96c26303 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt @@ -1,47 +1,65 @@ package com.wafflestudio.csereal.core.research.service import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.common.properties.EndpointProperties import com.wafflestudio.csereal.core.member.database.ProfessorRepository import com.wafflestudio.csereal.core.research.database.* -import com.wafflestudio.csereal.core.research.dto.LabDto -import com.wafflestudio.csereal.core.research.dto.ResearchDto -import com.wafflestudio.csereal.core.research.dto.ResearchGroupResponse +import com.wafflestudio.csereal.core.research.dto.* +import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity +import com.wafflestudio.csereal.core.resource.attachment.service.AttachmentService +import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional +import org.springframework.web.multipart.MultipartFile interface ResearchService { - fun createResearchDetail(request: ResearchDto): ResearchDto + fun createResearchDetail(request: ResearchDto, mainImage: MultipartFile?, attachments: List?): ResearchDto fun readAllResearchGroups(): ResearchGroupResponse fun readAllResearchCenters(): List - fun updateResearchDetail(researchId: Long, request: ResearchDto): ResearchDto - fun createLab(request: LabDto): LabDto - + fun updateResearchDetail(researchId: Long, request: ResearchDto, mainImage: MultipartFile?, attachments: List?): ResearchDto + fun createLab(request: LabDto, pdf: MultipartFile?): LabDto fun readAllLabs(): List + fun readLab(labId: Long): LabDto } @Service class ResearchServiceImpl( private val researchRepository: ResearchRepository, private val labRepository: LabRepository, - private val professorRepository: ProfessorRepository + private val professorRepository: ProfessorRepository, + private val mainImageService: MainImageService, + private val attachmentService: AttachmentService, + private val endpointProperties: EndpointProperties, ) : ResearchService { @Transactional - override fun createResearchDetail(request: ResearchDto): ResearchDto { + override fun createResearchDetail(request: ResearchDto, mainImage: MultipartFile?, attachments: List?): ResearchDto { val newResearch = ResearchEntity.of(request) - if(request.labsId != null) { - for(labId in request.labsId) { - val lab = labRepository.findByIdOrNull(labId) - ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다.(labId=$labId)") - newResearch.labs.add(lab) - lab.research = newResearch + if(request.labs != null) { + + for(lab in request.labs) { + val labEntity = labRepository.findByIdOrNull(lab.id) + ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다.(labId=${lab.id})") + newResearch.labs.add(labEntity) + labEntity.research = newResearch } } + if(mainImage != null) { + mainImageService.uploadMainImage(newResearch, mainImage) + } + + if(attachments != null) { + attachmentService.uploadAllAttachments(newResearch, attachments) + } + researchRepository.save(newResearch) - return ResearchDto.of(newResearch) + val imageURL = mainImageService.createImageURL(newResearch.mainImage) + val attachmentResponses = attachmentService.createAttachmentResponses(newResearch.attachments) + + return ResearchDto.of(newResearch, imageURL, attachmentResponses) } @Transactional(readOnly = true) @@ -53,7 +71,10 @@ class ResearchServiceImpl( "오늘도 인류가 꿈꾸는 행복하고 편리한 세상을 위해 변화와 혁신, 연구와 도전을 계속하고 있습니다." val researchGroups = researchRepository.findAllByPostTypeOrderByName(ResearchPostType.GROUPS).map { - ResearchDto.of(it) + val imageURL = mainImageService.createImageURL(it.mainImage) + val attachmentResponses = attachmentService.createAttachmentResponses(it.attachments) + + ResearchDto.of(it, imageURL, attachmentResponses) } return ResearchGroupResponse(description, researchGroups) @@ -62,27 +83,30 @@ class ResearchServiceImpl( @Transactional(readOnly = true) override fun readAllResearchCenters(): List { val researchCenters = researchRepository.findAllByPostTypeOrderByName(ResearchPostType.CENTERS).map { - ResearchDto.of(it) + val imageURL = mainImageService.createImageURL(it.mainImage) + val attachmentResponses = attachmentService.createAttachmentResponses(it.attachments) + + ResearchDto.of(it, imageURL, attachmentResponses) } return researchCenters } @Transactional - override fun updateResearchDetail(researchId: Long, request: ResearchDto): ResearchDto { + override fun updateResearchDetail(researchId: Long, request: ResearchDto, mainImage: MultipartFile?, attachments: List?): ResearchDto { val research = researchRepository.findByIdOrNull(researchId) ?: throw CserealException.Csereal404("해당 게시글을 찾을 수 없습니다.(researchId=$researchId)") - if(request.labsId != null) { - for(labId in request.labsId) { - val lab = labRepository.findByIdOrNull(labId) - ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다.(labId=$labId)") + if(request.labs != null) { + for(lab in request.labs) { + val labEntity = labRepository.findByIdOrNull(lab.id) + ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다.(labId=${lab.id})") } val oldLabs = research.labs.map { it.id } - val labsToRemove = oldLabs - request.labsId - val labsToAdd = request.labsId - oldLabs + val labsToRemove = oldLabs - request.labs.map { it.id } + val labsToAdd = request.labs.map { it.id } - oldLabs research.labs.removeIf { it.id in labsToRemove} @@ -94,40 +118,80 @@ class ResearchServiceImpl( } } - return ResearchDto.of(research) + if(mainImage != null) { + mainImageService.uploadMainImage(research, mainImage) + } + + if(attachments != null) { + research.attachments.clear() + attachmentService.uploadAllAttachments(research, attachments) + } + + val imageURL = mainImageService.createImageURL(research.mainImage) + val attachmentResponses = attachmentService.createAttachmentResponses(research.attachments) + + + return ResearchDto.of(research, imageURL, attachmentResponses) } @Transactional - override fun createLab(request: LabDto): LabDto { + override fun createLab(request: LabDto, pdf: MultipartFile?): LabDto { val researchGroup = researchRepository.findByName(request.group) - ?: throw CserealException.Csereal404("해당 연구그룹을 찾을 수 없습니다.(researchGroupId = ${request.group}") + ?: throw CserealException.Csereal404("해당 연구그룹을 찾을 수 없습니다.(researchGroupId = ${request.group})") if(researchGroup.postType != ResearchPostType.GROUPS) { throw CserealException.Csereal404("해당 게시글은 연구그룹이어야 합니다.") } - // get을 우선 구현하기 위해 빼겠습니다 - /* - if(request.professorsId != null) { - for(professorId in request.professorsId) { - val professor = professorRepository.findByIdOrNull(professorId) - ?: throw CserealException.Csereal404("해당 교수님을 찾을 수 없습니다.(professorId = $professorId") + val newLab = LabEntity.of(request, researchGroup) + + if(request.professors != null) { + for(professor in request.professors) { + val professorEntity = professorRepository.findByIdOrNull(professor.id) + ?: throw CserealException.Csereal404("해당 교수님을 찾을 수 없습니다.(professorId = ${professor.id}") + + newLab.professors.add(professorEntity) + professorEntity.lab = newLab } } - */ - val newLab = LabEntity.of(researchGroup, request) + var pdfURL = "" + if(pdf != null) { + val attachmentDto = attachmentService.uploadAttachmentInLabEntity(newLab, pdf) + pdfURL = "${endpointProperties.backend}/v1/attachment/${attachmentDto.filename}" + } labRepository.save(newLab) - return LabDto.of(newLab) + + return LabDto.of(newLab, pdfURL) } @Transactional(readOnly = true) override fun readAllLabs(): List { val labs = labRepository.findAllByOrderByName().map { - LabDto.of(it) + var pdfURL = "" + if(it.pdf != null) { + pdfURL = createPdfURL(it.pdf!!) + } + LabDto.of(it, pdfURL) } return labs } + + @Transactional + override fun readLab(labId: Long): LabDto { + val lab = labRepository.findByIdOrNull(labId) + ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다.(labId=$labId)") + var pdfURL = "" + if(lab.pdf != null) { + pdfURL = createPdfURL(lab.pdf!!) + } + + return LabDto.of(lab, pdfURL) + } + + private fun createPdfURL(pdf: AttachmentEntity) : String{ + return "${endpointProperties.backend}/v1/attachment/${pdf.filename}" + } } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentEntity.kt index afb4844b..1c79236a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentEntity.kt @@ -5,6 +5,8 @@ import com.wafflestudio.csereal.core.about.database.AboutEntity import com.wafflestudio.csereal.core.academics.database.AcademicsEntity import com.wafflestudio.csereal.core.academics.database.CourseEntity import com.wafflestudio.csereal.core.news.database.NewsEntity +import com.wafflestudio.csereal.core.research.database.LabEntity +import com.wafflestudio.csereal.core.research.database.ResearchEntity import com.wafflestudio.csereal.core.seminar.database.SeminarEntity import jakarta.persistence.* @@ -37,6 +39,14 @@ class AttachmentEntity( @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "course_id") var course: CourseEntity? = null, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "lab_id") + var lab: LabEntity? = null, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "research_id") + var research: ResearchEntity? = null, ) : BaseTimeEntity() { } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt index b70e17a0..d6654def 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt @@ -1,10 +1,13 @@ package com.wafflestudio.csereal.core.resource.attachment.service import com.wafflestudio.csereal.common.controller.AttachmentContentEntityType +import com.wafflestudio.csereal.common.properties.EndpointProperties import com.wafflestudio.csereal.core.about.database.AboutEntity import com.wafflestudio.csereal.core.academics.database.AcademicsEntity import com.wafflestudio.csereal.core.academics.database.CourseEntity import com.wafflestudio.csereal.core.news.database.NewsEntity +import com.wafflestudio.csereal.core.research.database.LabEntity +import com.wafflestudio.csereal.core.research.database.ResearchEntity import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentRepository import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentDto @@ -19,11 +22,15 @@ import java.nio.file.Files import java.nio.file.Paths interface AttachmentService { - fun uploadAttachments( + fun uploadAttachmentInLabEntity( + labEntity: LabEntity, + requestAttachment: MultipartFile + ): AttachmentDto + fun uploadAllAttachments( contentEntityType: AttachmentContentEntityType, requestAttachments: List, ): List - fun createAttachments(attachments: List?): List? + fun createAttachmentResponses(attachments: List?): List } @Service @@ -31,9 +38,38 @@ class AttachmentServiceImpl( private val attachmentRepository: AttachmentRepository, @Value("\${csereal_attachment.upload.path}") private val path: String, + private val endpointProperties: EndpointProperties, ) : AttachmentService { + override fun uploadAttachmentInLabEntity(labEntity: LabEntity, requestAttachment: MultipartFile): AttachmentDto { + Files.createDirectories(Paths.get(path)) + + val extension = FilenameUtils.getExtension(requestAttachment.originalFilename) + + val timeMillis = System.currentTimeMillis() + + val filename = "${timeMillis}_${requestAttachment.originalFilename}" + val totalFilename = path + filename + val saveFile = Paths.get("$totalFilename.$extension") + requestAttachment.transferTo(saveFile) + + val attachment = AttachmentEntity( + filename = filename, + attachmentsOrder = 1, + size = requestAttachment.size, + ) + + labEntity.pdf = attachment + attachmentRepository.save(attachment) + + return AttachmentDto( + filename = filename, + attachmentsOrder = 1, + size = requestAttachment.size + ) + + } @Transactional - override fun uploadAttachments( + override fun uploadAllAttachments( contentEntity: AttachmentContentEntityType, requestAttachments: List, ): List { @@ -72,13 +108,13 @@ class AttachmentServiceImpl( } @Transactional - override fun createAttachments(attachments: List?): List? { + override fun createAttachmentResponses(attachments: List?): List{ val list = mutableListOf() if (attachments != null) { for (attachment in attachments) { val attachmentDto = AttachmentResponse( name = attachment.filename, - url = "http://cse-dev-waffle.bacchus.io/attachment/${attachment.filename}", + url = "${endpointProperties.backend}/v1/attachment/${attachment.filename}", bytes = attachment.size, ) list.add(attachmentDto) @@ -109,6 +145,10 @@ class AttachmentServiceImpl( contentEntity.attachments.add(attachment) attachment.course = contentEntity } + is ResearchEntity -> { + contentEntity.attachments.add(attachment) + attachment.research = contentEntity + } } } } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/introductionMaterial/dto/introductionMaterialDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/introductionMaterial/dto/introductionMaterialDto.kt deleted file mode 100644 index f4c9873c..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/introductionMaterial/dto/introductionMaterialDto.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.wafflestudio.csereal.core.resource.introductionMaterial.dto - -// Todo: IntroductionMaterial이 연구실에만 쓰이는지 확인할 것 -data class IntroductionMaterialDto( - val pdf: String?, - val youtube: String?, -) { -} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt index 0a5690f1..4a6e32eb 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt @@ -1,12 +1,13 @@ package com.wafflestudio.csereal.core.resource.mainImage.service import com.wafflestudio.csereal.common.CserealException -import com.wafflestudio.csereal.common.controller.ImageContentEntityType +import com.wafflestudio.csereal.common.controller.MainImageContentEntityType import com.wafflestudio.csereal.common.properties.EndpointProperties import com.wafflestudio.csereal.core.about.database.AboutEntity import com.wafflestudio.csereal.core.member.database.ProfessorEntity import com.wafflestudio.csereal.core.member.database.StaffEntity import com.wafflestudio.csereal.core.news.database.NewsEntity +import com.wafflestudio.csereal.core.research.database.ResearchEntity import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageRepository import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity import com.wafflestudio.csereal.core.resource.mainImage.dto.MainImageDto @@ -25,10 +26,9 @@ import kotlin.io.path.name interface MainImageService { fun uploadMainImage( - contentEntityType: ImageContentEntityType, + contentEntityType: MainImageContentEntityType, requestImage: MultipartFile, ): MainImageDto - fun createImageURL(image: MainImageEntity?): String? } @@ -42,7 +42,7 @@ class MainImageServiceImpl( @Transactional override fun uploadMainImage( - contentEntityType: ImageContentEntityType, + contentEntityType: MainImageContentEntityType, requestImage: MultipartFile, ): MainImageDto { Files.createDirectories(Paths.get(path)) @@ -94,7 +94,7 @@ class MainImageServiceImpl( } else null } - private fun connectMainImageToEntity(contentEntity: ImageContentEntityType, mainImage: MainImageEntity) { + private fun connectMainImageToEntity(contentEntity: MainImageContentEntityType, mainImage: MainImageEntity) { when (contentEntity) { is NewsEntity -> { contentEntity.mainImage = mainImage @@ -116,6 +116,9 @@ class MainImageServiceImpl( contentEntity.mainImage = mainImage } + is ResearchEntity -> { + contentEntity.mainImage = mainImage + } else -> { throw WrongMethodTypeException("해당하는 엔티티가 없습니다") } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt index 04c6359d..9e2ba977 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt @@ -2,7 +2,7 @@ package com.wafflestudio.csereal.core.seminar.database import com.wafflestudio.csereal.common.config.BaseTimeEntity import com.wafflestudio.csereal.common.controller.AttachmentContentEntityType -import com.wafflestudio.csereal.common.controller.ImageContentEntityType +import com.wafflestudio.csereal.common.controller.MainImageContentEntityType import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity import com.wafflestudio.csereal.core.seminar.dto.SeminarDto @@ -47,7 +47,7 @@ class SeminarEntity( @OneToMany(mappedBy = "seminar", cascade = [CascadeType.ALL], orphanRemoval = true) var attachments: MutableList = mutableListOf(), - ): BaseTimeEntity(), ImageContentEntityType, AttachmentContentEntityType { + ): BaseTimeEntity(), MainImageContentEntityType, AttachmentContentEntityType { override fun bringMainImage(): MainImageEntity? = mainImage override fun bringAttachments() = attachments diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt index c0a20380..899a5b0d 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt @@ -34,7 +34,7 @@ data class SeminarDto( ) { companion object { - fun of(entity: SeminarEntity, imageURL: String?, attachments: List?, prevNext: Array?): SeminarDto = entity.run { + fun of(entity: SeminarEntity, imageURL: String?, attachmentResponses: List, prevNext: Array?): SeminarDto = entity.run { SeminarDto( id = this.id, title = this.title, @@ -60,7 +60,7 @@ data class SeminarDto( nextId = prevNext?.get(1)?.id, nextTitle = prevNext?.get(1)?.title, imageURL = imageURL, - attachments = attachments, + attachments = attachmentResponses, ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt index 2d28eef1..ebba6b47 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt @@ -40,14 +40,14 @@ class SeminarServiceImpl( } if(attachments != null) { - attachmentService.uploadAttachments(newSeminar, attachments) + attachmentService.uploadAllAttachments(newSeminar, attachments) } seminarRepository.save(newSeminar) val imageURL = mainImageService.createImageURL(newSeminar.mainImage) - val attachments = attachmentService.createAttachments(newSeminar.attachments) - return SeminarDto.of(newSeminar, imageURL, attachments, null) + val attachmentResponses = attachmentService.createAttachmentResponses(newSeminar.attachments) + return SeminarDto.of(newSeminar, imageURL, attachmentResponses, null) } @Transactional(readOnly = true) @@ -58,11 +58,11 @@ class SeminarServiceImpl( if (seminar.isDeleted) throw CserealException.Csereal400("삭제된 세미나입니다. (seminarId: $seminarId)") val imageURL = mainImageService.createImageURL(seminar.mainImage) - val attachments = attachmentService.createAttachments(seminar.attachments) + val attachmentResponses = attachmentService.createAttachmentResponses(seminar.attachments) val prevNext = seminarRepository.findPrevNextId(seminarId, keyword) - return SeminarDto.of(seminar, imageURL, attachments, prevNext) + return SeminarDto.of(seminar, imageURL, attachmentResponses, prevNext) } @Transactional @@ -79,14 +79,13 @@ class SeminarServiceImpl( if(attachments != null) { seminar.attachments.clear() - attachmentService.uploadAttachments(seminar, attachments) + attachmentService.uploadAllAttachments(seminar, attachments) } val imageURL = mainImageService.createImageURL(seminar.mainImage) - val attachments = attachmentService.createAttachments(seminar.attachments) - - - return SeminarDto.of(seminar, imageURL, attachments, null) + val attachmentResponses = attachmentService.createAttachmentResponses(seminar.attachments) + + return SeminarDto.of(seminar, imageURL, attachmentResponses, null) } @Transactional override fun deleteSeminar(seminarId: Long) { From 79ca736a7edc4063ec36f1ae2f05a4dc8b97eb68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=ED=98=81=EC=A4=80=20=28HyukJoon=20Woo=29?= Date: Sat, 2 Sep 2023 16:22:39 +0900 Subject: [PATCH 038/124] =?UTF-8?q?[Refactor]=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8,=20file=20uri=20=EB=B0=B0=ED=8F=AC=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?(#53)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Refactor: Change mapping of file to append /api/v1 in front. * Refactor: Change login, logout redirect uri --- .../wafflestudio/csereal/common/config/SecurityConfig.kt | 6 +++--- .../core/resource/mainImage/api/FileController.kt | 2 +- src/main/resources/application.yaml | 9 +++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt index cb144cac..287a17d4 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt @@ -32,7 +32,7 @@ class SecurityConfig( .cors().and() .csrf().disable() .oauth2Login() - .loginPage("/oauth2/authorization/idsnucse") + .loginPage("/api/v1/login/oauth2/authorization/idsnucse") .userInfoEndpoint().oidcUserService(customOidcUserService).and() .successHandler(CustomAuthenticationSuccessHandler(endpointProperties.frontend)).and() .logout() @@ -41,7 +41,7 @@ class SecurityConfig( .clearAuthentication(true) .deleteCookies("JSESSIONID").and() .authorizeHttpRequests() - .requestMatchers("/login").authenticated() + .requestMatchers("/api/v1/login").permitAll() .anyRequest().permitAll().and() .build() } @@ -54,7 +54,7 @@ class SecurityConfig( response: HttpServletResponse?, authentication: Authentication? ) { - val redirectUrl = "${endpointProperties.frontend}/logout/success" + val redirectUrl = "${endpointProperties.frontend}/api/v1/logout/success" super.setDefaultTargetUrl(redirectUrl) super.onLogoutSuccess(request, response, authentication) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/FileController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/FileController.kt index 153b36fc..e63dbac5 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/FileController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/FileController.kt @@ -14,7 +14,7 @@ import java.nio.file.Paths import kotlin.text.Charsets.UTF_8 -@RequestMapping("/file") +@RequestMapping("/api/v1/file") @RestController class FileController( @Value("\${csereal_mainImage.upload.path}") diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 5880b2a7..c65dc283 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -53,7 +53,7 @@ spring: client: registration: idsnucse: - redirect-uri: http://localhost:8080/login/oauth2/code/idsnucse + redirect-uri: http://localhost:8080/api/v1/login/oauth2/code/idsnucse logging.level: default: INFO @@ -61,13 +61,14 @@ logging.level: springframework: security: DEBUG + csereal_mainImage: upload: - path: /app/mainImage/ + path: ./attachment csereal_attachment: upload: - path: /app/attachment/ + path: ./attachment endpoint: backend: http://localhost:8080 @@ -85,7 +86,7 @@ spring: client: registration: idsnucse: - redirect-uri: http://${URL}/login/oauth2/code/idsnucse + redirect-uri: http://${URL}/api/v1/login/oauth2/code/idsnucse From 46cee1c5fe639f8b7e3e2b93c2d1071751cad749 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Sat, 2 Sep 2023 21:43:44 +0900 Subject: [PATCH 039/124] =?UTF-8?q?fix:=20https=20=EC=A0=81=EC=9A=A9=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#56)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- caddy/Caddyfile | 8 ++------ .../wafflestudio/csereal/common/config/SecurityConfig.kt | 6 ++++-- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/caddy/Caddyfile b/caddy/Caddyfile index f406408e..5891b637 100644 --- a/caddy/Caddyfile +++ b/caddy/Caddyfile @@ -6,13 +6,9 @@ reverse_proxy /api/* host.docker.internal:8080 #host.docker.internal:8081 # For blue/green # Login - reverse_proxy /login/oauth2/code/idsnucse host.docker.internal:8080 #host.docker.internal:8081 # For blue/green - reverse_proxy /login/oauth2/code/idsnucse/* host.docker.internal:8080 #host.docker.internal:8081 # For blue/green - reverse_proxy /logout host.docker.internal:8080 #host.docker.internal:8081 # For blue/green - reverse_proxy /logout/* host.docker.internal:8080 #host.docker.internal:8081 # For blue/green - + reverse_proxy /oauth2/authorization/idsnucse host.docker.internal:8080 #host.docker.internal:8081 # For blue/green # Swagger reverse_proxy /swagger-ui/* host.docker.internal:8080 #host.docker.internal:8081 # For blue/green reverse_proxy /api-docs/* host.docker.internal:8080 #host.docker.internal:8081 # For blue/green -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt index 287a17d4..22b49d63 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt @@ -32,7 +32,9 @@ class SecurityConfig( .cors().and() .csrf().disable() .oauth2Login() - .loginPage("/api/v1/login/oauth2/authorization/idsnucse") + .loginPage("/oauth2/authorization/idsnucse") + .redirectionEndpoint() + .baseUri("/api/v1/login/oauth2/code/idsnucse").and() .userInfoEndpoint().oidcUserService(customOidcUserService).and() .successHandler(CustomAuthenticationSuccessHandler(endpointProperties.frontend)).and() .logout() @@ -41,7 +43,7 @@ class SecurityConfig( .clearAuthentication(true) .deleteCookies("JSESSIONID").and() .authorizeHttpRequests() - .requestMatchers("/api/v1/login").permitAll() + .requestMatchers("/api/v1/login").authenticated() .anyRequest().permitAll().and() .build() } From 6d8a2fc4cb078f08ab0a9adf8f191e8cc966e865 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Sun, 3 Sep 2023 01:47:42 +0900 Subject: [PATCH 040/124] =?UTF-8?q?fix:=20notice=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=20=ED=94=84=EB=A1=A0=ED=8A=B8=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EA=B2=8C=20=ED=98=91=EC=9D=98=20(#54)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: professor, staff에 uploadImage 추가 * fix: research에서 isPublic 제거, feat: lab에서 attachments 추가 * fix: attachments -> attachmentResponses 변경 * feat: LabProfessorResponse 추가 * fix: pdf를 list가 아닌 단일항목으로 수정 * feat: readLab 추가 * feat: ResearchLabReponse 추가 및 주석 삭제 * fix: researchDetail에 사진, 첨부파일 업로드 추가 * feat: notice에 attachments 추가 * fix: fix_with_front1 변경 사항에 맞게 수정 * feat: isImportant 추가 * feat: NoticeSearchDto에 hasAttachment 추가 * fix: update에서 attachmetnts가 null일 때 빈 목록 반환 * feat: 공지사항 선택 고정해제, 선택 삭제 추가 * fix: news, seminar에 isImportant 추가 * fix: pr 리뷰 수정 * fix: 수정했던거 다시 복구 --- .../core/member/service/ProfessorService.kt | 2 + .../core/member/service/StaffService.kt | 2 + .../csereal/core/news/database/NewsEntity.kt | 4 ++ .../csereal/core/news/dto/NewsDto.kt | 2 + .../csereal/core/news/service/NewsService.kt | 4 ++ .../core/notice/api/NoticeController.kt | 24 ++++++-- .../core/notice/database/NoticeEntity.kt | 19 ++++--- .../core/notice/database/NoticeRepository.kt | 5 +- .../csereal/core/notice/dto/NoticeDto.kt | 11 +++- .../core/notice/dto/NoticeIdListRequest.kt | 7 +++ .../core/notice/dto/NoticeSearchDto.kt | 1 + .../core/notice/service/NoticeService.kt | 56 ++++++++++++++++--- .../core/research/service/ResearchService.kt | 4 ++ .../attachment/database/AttachmentEntity.kt | 7 ++- .../database/AttachmentRepository.kt | 1 + .../attachment/service/AttachmentService.kt | 42 ++++++++++++-- .../core/seminar/api/SeminarController.kt | 8 ++- .../core/seminar/database/SeminarEntity.kt | 7 ++- .../csereal/core/seminar/dto/SeminarDto.kt | 2 + .../core/seminar/service/SeminarService.kt | 50 ++++++++++++----- 20 files changed, 210 insertions(+), 48 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeIdListRequest.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt index 4988369a..4e0e9618 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt @@ -108,6 +108,8 @@ class ProfessorServiceImpl( if(mainImage != null) { mainImageService.uploadMainImage(professor, mainImage) + } else { + professor.mainImage = null } // 학력 업데이트 diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt index a08ca026..90a2778b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt @@ -70,6 +70,8 @@ class StaffServiceImpl( if(mainImage != null) { mainImageService.uploadMainImage(staff, mainImage) + } else { + staff.mainImage = null } // 주요 업무 업데이트 diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt index 327ebcd3..d61709d6 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt @@ -21,6 +21,8 @@ class NewsEntity( var isSlide: Boolean, + var isImportant: Boolean, + @OneToOne var mainImage: MainImageEntity? = null, @@ -41,6 +43,7 @@ class NewsEntity( description = newsDto.description, isPublic = newsDto.isPublic, isSlide = newsDto.isSlide, + isImportant = newsDto.isImportant, ) } } @@ -49,5 +52,6 @@ class NewsEntity( this.description = updateNewsRequest.description this.isPublic = updateNewsRequest.isPublic this.isSlide = updateNewsRequest.isSlide + this.isImportant = updateNewsRequest.isImportant } } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt index 3232d84f..0cff4703 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt @@ -13,6 +13,7 @@ data class NewsDto( val modifiedAt: LocalDateTime?, val isPublic: Boolean, val isSlide: Boolean, + val isImportant: Boolean, val prevId: Long?, val prevTitle: String?, val nextId: Long?, @@ -31,6 +32,7 @@ data class NewsDto( modifiedAt = this.modifiedAt, isPublic = this.isPublic, isSlide = this.isSlide, + isImportant = this.isImportant, prevId = prevNext?.get(0)?.id, prevTitle = prevNext?.get(0)?.title, nextId = prevNext?.get(1)?.id, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt index e2904321..2c2f2a43 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt @@ -91,11 +91,15 @@ class NewsServiceImpl( if(mainImage != null) { mainImageService.uploadMainImage(news, mainImage) + } else { + news.mainImage = null } if(attachments != null) { news.attachments.clear() attachmentService.uploadAllAttachments(news, attachments) + } else { + news.attachments.clear() } val oldTags = news.newsTags.map { it.tag.name } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt index a42a9b7d..094c495f 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt @@ -7,6 +7,7 @@ import jakarta.validation.Valid import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* +import org.springframework.web.multipart.MultipartFile @RequestMapping("/api/v1/notice") @RestController @@ -34,17 +35,19 @@ class NoticeController( @AuthenticatedStaff @PostMapping fun createNotice( - @Valid @RequestBody request: NoticeDto + @Valid @RequestPart("request") request: NoticeDto, + @RequestPart("attachments") attachments: List? ): ResponseEntity { - return ResponseEntity.ok(noticeService.createNotice(request)) + return ResponseEntity.ok(noticeService.createNotice(request, attachments)) } @PatchMapping("/{noticeId}") fun updateNotice( @PathVariable noticeId: Long, - @Valid @RequestBody request: NoticeDto, + @Valid @RequestPart("request") request: NoticeDto, + @RequestPart("attachments") attachments: List?, ): ResponseEntity { - return ResponseEntity.ok(noticeService.updateNotice(noticeId, request)) + return ResponseEntity.ok(noticeService.updateNotice(noticeId, request, attachments)) } @DeleteMapping("/{noticeId}") @@ -54,6 +57,19 @@ class NoticeController( noticeService.deleteNotice(noticeId) } + @PatchMapping + fun unpinManyNotices( + @RequestBody request: NoticeIdListRequest + ) { + noticeService.unpinManyNotices(request.idList) + } + @DeleteMapping + fun deleteManyNotices( + @RequestBody request: NoticeIdListRequest + ) { + noticeService.deleteManyNotices(request.idList) + } + @PostMapping("/tag") fun enrollTag( @RequestBody tagName: Map diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt index ddf937a8..5113b115 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt @@ -1,36 +1,41 @@ package com.wafflestudio.csereal.core.notice.database import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.common.controller.AttachmentContentEntityType import com.wafflestudio.csereal.core.notice.dto.NoticeDto +import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity import com.wafflestudio.csereal.core.user.database.UserEntity import jakarta.persistence.* @Entity(name = "notice") class NoticeEntity( - var isDeleted: Boolean = false, - var title: String, - var description: String, - var isPublic: Boolean, - var isPinned: Boolean, + var isImportant: Boolean, @OneToMany(mappedBy = "notice", cascade = [CascadeType.ALL]) var noticeTags: MutableSet = mutableSetOf(), @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "users_id") - val author: UserEntity -) : BaseTimeEntity() { + val author: UserEntity, + + @OneToMany(mappedBy = "notice", cascade = [CascadeType.ALL], orphanRemoval = true) + var attachments: MutableList = mutableListOf(), + +) : BaseTimeEntity(), AttachmentContentEntityType { + override fun bringAttachments() = attachments + fun update(updateNoticeRequest: NoticeDto) { this.title = updateNoticeRequest.title this.description = updateNoticeRequest.description this.isPublic = updateNoticeRequest.isPublic this.isPinned = updateNoticeRequest.isPinned + this.isImportant = updateNoticeRequest.isImportant } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt index bf3f8471..c1af0c87 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt @@ -58,17 +58,20 @@ class NoticeRepositoryImpl( val noticeEntityList = jpaQuery.orderBy(noticeEntity.isPinned.desc()) .orderBy(noticeEntity.createdAt.desc()) - .offset(20*pageNum) //로컬 테스트를 위해 잠시 5로 둘 것, 원래는 20 + .offset(20*pageNum) .limit(20) .distinct() .fetch() val noticeSearchDtoList : List = noticeEntityList.map { + val hasAttachment : Boolean = it.attachments.isNotEmpty() + NoticeSearchDto( id = it.id, title = it.title, createdAt = it.createdAt, isPinned = it.isPinned, + hasAttachment = hasAttachment ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt index 97d7bb42..43b85e65 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt @@ -1,6 +1,7 @@ package com.wafflestudio.csereal.core.notice.dto import com.wafflestudio.csereal.core.notice.database.NoticeEntity +import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse import java.time.LocalDateTime data class NoticeDto( @@ -13,14 +14,16 @@ data class NoticeDto( val modifiedAt: LocalDateTime?, val isPublic: Boolean, val isPinned: Boolean, + val isImportant: Boolean, val prevId: Long?, val prevTitle: String?, val nextId: Long?, - val nextTitle: String? + val nextTitle: String?, + val attachments: List?, ) { companion object { - fun of(entity: NoticeEntity, prevNext: Array?): NoticeDto = entity.run { + fun of(entity: NoticeEntity, attachmentResponses: List, prevNext: Array?): NoticeDto = entity.run { NoticeDto( id = this.id, title = this.title, @@ -31,10 +34,12 @@ data class NoticeDto( modifiedAt = this.modifiedAt, isPublic = this.isPublic, isPinned = this.isPinned, + isImportant = this.isImportant, prevId = prevNext?.get(0)?.id, prevTitle = prevNext?.get(0)?.title, nextId = prevNext?.get(1)?.id, - nextTitle = prevNext?.get(1)?.title + nextTitle = prevNext?.get(1)?.title, + attachments = attachmentResponses, ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeIdListRequest.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeIdListRequest.kt new file mode 100644 index 00000000..b84f7947 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeIdListRequest.kt @@ -0,0 +1,7 @@ +package com.wafflestudio.csereal.core.notice.dto + +data class NoticeIdListRequest( + val idList: List +) { + +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeSearchDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeSearchDto.kt index 89898ebb..7881d082 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeSearchDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeSearchDto.kt @@ -8,6 +8,7 @@ data class NoticeSearchDto @QueryProjection constructor( val title: String, val createdAt: LocalDateTime?, val isPinned: Boolean, + val hasAttachment: Boolean, ) { } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt index cef490cc..fd176609 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt @@ -3,6 +3,7 @@ package com.wafflestudio.csereal.core.notice.service import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.core.notice.database.* import com.wafflestudio.csereal.core.notice.dto.* +import com.wafflestudio.csereal.core.resource.attachment.service.AttachmentService import com.wafflestudio.csereal.core.user.database.UserEntity import com.wafflestudio.csereal.core.user.database.UserRepository import org.springframework.data.repository.findByIdOrNull @@ -12,13 +13,16 @@ import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import org.springframework.web.context.request.RequestAttributes import org.springframework.web.context.request.RequestContextHolder +import org.springframework.web.multipart.MultipartFile interface NoticeService { fun searchNotice(tag: List?, keyword: String?, pageNum: Long): NoticeSearchResponse fun readNotice(noticeId: Long, tag: List?, keyword: String?): NoticeDto - fun createNotice(request: NoticeDto): NoticeDto - fun updateNotice(noticeId: Long, request: NoticeDto): NoticeDto + fun createNotice(request: NoticeDto, attachments: List?): NoticeDto + fun updateNotice(noticeId: Long, request: NoticeDto, attachments: List?): NoticeDto fun deleteNotice(noticeId: Long) + fun unpinManyNotices(idList: List) + fun deleteManyNotices(idList: List) fun enrollTag(tagName: String) } @@ -27,7 +31,8 @@ class NoticeServiceImpl( private val noticeRepository: NoticeRepository, private val tagInNoticeRepository: TagInNoticeRepository, private val noticeTagRepository: NoticeTagRepository, - private val userRepository: UserRepository + private val userRepository: UserRepository, + private val attachmentService: AttachmentService, ) : NoticeService { @Transactional(readOnly = true) @@ -50,13 +55,15 @@ class NoticeServiceImpl( if (notice.isDeleted) throw CserealException.Csereal404("삭제된 공지사항입니다.(noticeId: $noticeId)") + val attachmentResponses = attachmentService.createAttachmentResponses(notice.attachments) + val prevNext = noticeRepository.findPrevNextId(noticeId, tag, keyword) - return NoticeDto.of(notice, prevNext) + return NoticeDto.of(notice, attachmentResponses, prevNext) } @Transactional - override fun createNotice(request: NoticeDto): NoticeDto { + override fun createNotice(request: NoticeDto, attachments: List?): NoticeDto { var user = RequestContextHolder.getRequestAttributes()?.getAttribute( "loggedInUser", RequestAttributes.SCOPE_REQUEST @@ -74,6 +81,7 @@ class NoticeServiceImpl( description = request.description, isPublic = request.isPublic, isPinned = request.isPinned, + isImportant = request.isImportant, author = user ) @@ -82,20 +90,33 @@ class NoticeServiceImpl( NoticeTagEntity.createNoticeTag(newNotice, tag) } + if(attachments != null) { + attachmentService.uploadAllAttachments(newNotice, attachments) + } + noticeRepository.save(newNotice) - return NoticeDto.of(newNotice, null) + val attachmentResponses = attachmentService.createAttachmentResponses(newNotice.attachments) + + return NoticeDto.of(newNotice, attachmentResponses, null) } @Transactional - override fun updateNotice(noticeId: Long, request: NoticeDto): NoticeDto { + override fun updateNotice(noticeId: Long, request: NoticeDto, attachments: List?): NoticeDto { val notice: NoticeEntity = noticeRepository.findByIdOrNull(noticeId) ?: throw CserealException.Csereal404("존재하지 않는 공지사항입니다.(noticeId: $noticeId)") if (notice.isDeleted) throw CserealException.Csereal404("삭제된 공지사항입니다.(noticeId: $noticeId)") notice.update(request) + if(attachments != null) { + notice.attachments.clear() + attachmentService.uploadAllAttachments(notice, attachments) + } else { + notice.attachments.clear() + } + val oldTags = notice.noticeTags.map { it.tag.name } val tagsToRemove = oldTags - request.tags @@ -112,7 +133,9 @@ class NoticeServiceImpl( NoticeTagEntity.createNoticeTag(notice, tag) } - return NoticeDto.of(notice, null) + val attachmentResponses = attachmentService.createAttachmentResponses(notice.attachments) + + return NoticeDto.of(notice, attachmentResponses, null) } @@ -126,6 +149,23 @@ class NoticeServiceImpl( } + @Transactional + override fun unpinManyNotices(idList: List) { + for(noticeId in idList) { + val notice: NoticeEntity = noticeRepository.findByIdOrNull(noticeId) + ?: throw CserealException.Csereal404("존재하지 않는 공지사항을 입력하였습니다.(noticeId: $noticeId)") + notice.isPinned = false + } + } + @Transactional + override fun deleteManyNotices(idList: List) { + for(noticeId in idList) { + val notice: NoticeEntity = noticeRepository.findByIdOrNull(noticeId) + ?: throw CserealException.Csereal404("존재하지 않는 공지사항을 입력하였습니다.(noticeId: $noticeId)") + notice.isDeleted = true + } + } + override fun enrollTag(tagName: String) { val newTag = TagInNoticeEntity( name = tagName diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt index 96c26303..986649e6 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt @@ -120,11 +120,15 @@ class ResearchServiceImpl( if(mainImage != null) { mainImageService.uploadMainImage(research, mainImage) + } else { + research.mainImage = null } if(attachments != null) { research.attachments.clear() attachmentService.uploadAllAttachments(research, attachments) + } else { + research.attachments.clear() } val imageURL = mainImageService.createImageURL(research.mainImage) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentEntity.kt index 1c79236a..988ece95 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentEntity.kt @@ -5,6 +5,7 @@ import com.wafflestudio.csereal.core.about.database.AboutEntity import com.wafflestudio.csereal.core.academics.database.AcademicsEntity import com.wafflestudio.csereal.core.academics.database.CourseEntity import com.wafflestudio.csereal.core.news.database.NewsEntity +import com.wafflestudio.csereal.core.notice.database.NoticeEntity import com.wafflestudio.csereal.core.research.database.LabEntity import com.wafflestudio.csereal.core.research.database.ResearchEntity import com.wafflestudio.csereal.core.seminar.database.SeminarEntity @@ -12,7 +13,7 @@ import jakarta.persistence.* @Entity(name = "attachment") class AttachmentEntity( - val isDeleted : Boolean? = false, + var isDeleted : Boolean? = false, @Column(unique = true) val filename: String, @@ -24,6 +25,10 @@ class AttachmentEntity( @JoinColumn(name = "news_id") var news: NewsEntity? = null, + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "notice_id") + var notice: NoticeEntity? = null, + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "seminar_id") var seminar: SeminarEntity? = null, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentRepository.kt index 4ccfcaed..a90f9a30 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentRepository.kt @@ -5,4 +5,5 @@ import org.springframework.stereotype.Repository @Repository interface AttachmentRepository: JpaRepository { + fun findByFilename(filename: String): AttachmentEntity } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt index d6654def..42dbc2bc 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt @@ -6,6 +6,7 @@ import com.wafflestudio.csereal.core.about.database.AboutEntity import com.wafflestudio.csereal.core.academics.database.AcademicsEntity import com.wafflestudio.csereal.core.academics.database.CourseEntity import com.wafflestudio.csereal.core.news.database.NewsEntity +import com.wafflestudio.csereal.core.notice.database.NoticeEntity import com.wafflestudio.csereal.core.research.database.LabEntity import com.wafflestudio.csereal.core.research.database.ResearchEntity import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity @@ -31,6 +32,10 @@ interface AttachmentService { requestAttachments: List, ): List fun createAttachmentResponses(attachments: List?): List + fun updateAttachmentResponses( + contentEntity: AttachmentContentEntityType, + attachmentsList: List + ) } @Service @@ -94,6 +99,7 @@ class AttachmentServiceImpl( ) connectAttachmentToEntity(contentEntity, attachment) + //Todo: update에서도 uploadAllAttachments 사용, 이에 따른 attachmentsOrder에 대한 조정 필요 attachmentRepository.save(attachment) attachmentsList.add( @@ -112,23 +118,47 @@ class AttachmentServiceImpl( val list = mutableListOf() if (attachments != null) { for (attachment in attachments) { - val attachmentDto = AttachmentResponse( - name = attachment.filename, - url = "${endpointProperties.backend}/v1/attachment/${attachment.filename}", - bytes = attachment.size, - ) - list.add(attachmentDto) + if(attachment.isDeleted == false) { + val attachmentDto = AttachmentResponse( + name = attachment.filename, + url = "${endpointProperties.backend}/v1/attachment/${attachment.filename}", + bytes = attachment.size, + ) + list.add(attachmentDto) + } + } } return list } + @Transactional + override fun updateAttachmentResponses(contentEntity: AttachmentContentEntityType, attachmentsList: List) { + val oldAttachments = contentEntity.bringAttachments().map { it.filename } + + val attachmentsToRemove = oldAttachments - attachmentsList.map { it.name } + + when(contentEntity) { + is SeminarEntity -> { + for (attachmentFilename in attachmentsToRemove) { + val attachmentEntity = attachmentRepository.findByFilename(attachmentFilename) + attachmentEntity.isDeleted = true + attachmentEntity.seminar = null + } + } + } + } + private fun connectAttachmentToEntity(contentEntity: AttachmentContentEntityType, attachment: AttachmentEntity) { when (contentEntity) { is NewsEntity -> { contentEntity.attachments.add(attachment) attachment.news = contentEntity } + is NoticeEntity -> { + contentEntity.attachments.add(attachment) + attachment.notice = contentEntity + } is SeminarEntity -> { contentEntity.attachments.add(attachment) attachment.seminar = contentEntity diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt index eb400578..01714fd0 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt @@ -1,5 +1,6 @@ package com.wafflestudio.csereal.core.seminar.api +import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse import com.wafflestudio.csereal.core.seminar.dto.SeminarDto import com.wafflestudio.csereal.core.seminar.dto.SeminarSearchResponse import com.wafflestudio.csereal.core.seminar.service.SeminarService @@ -41,10 +42,11 @@ class SeminarController ( fun updateSeminar( @PathVariable seminarId: Long, @Valid @RequestPart("request") request: SeminarDto, - @RequestPart("mainImage") mainImage: MultipartFile?, - @RequestPart("attachments") attachments: List? + @RequestPart("newMainImage") newMainImage: MultipartFile?, + @RequestPart("newAttachments") newAttachments: List?, + @RequestPart("attachmentsList") attachmentsList: List, ) : ResponseEntity { - return ResponseEntity.ok(seminarService.updateSeminar(seminarId, request, mainImage, attachments)) + return ResponseEntity.ok(seminarService.updateSeminar(seminarId, request, newMainImage, newAttachments, attachmentsList)) } @DeleteMapping("/{seminarId}") diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt index 9e2ba977..9a8742a5 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt @@ -38,6 +38,7 @@ class SeminarEntity( var host: String?, var isPublic: Boolean, + var isImportant: Boolean, var additionalNote: String?, @@ -68,8 +69,9 @@ class SeminarEntity( endTime = seminarDto.endTime, location = seminarDto.location, host = seminarDto.host, - additionalNote = seminarDto.additionalNote, isPublic = seminarDto.isPublic, + isImportant = seminarDto.isImportant, + additionalNote = seminarDto.additionalNote, ) } } @@ -89,7 +91,8 @@ class SeminarEntity( endTime = updateSeminarRequest.endTime location = updateSeminarRequest.location host = updateSeminarRequest.host - additionalNote = updateSeminarRequest.additionalNote isPublic = updateSeminarRequest.isPublic + isImportant = updateSeminarRequest.isImportant + additionalNote = updateSeminarRequest.additionalNote } } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt index 899a5b0d..c6c2d9c9 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt @@ -25,6 +25,7 @@ data class SeminarDto( val createdAt: LocalDateTime?, val modifiedAt: LocalDateTime?, val isPublic: Boolean, + val isImportant: Boolean, val prevId: Long?, val prevTitle: String?, val nextId: Long?, @@ -55,6 +56,7 @@ data class SeminarDto( createdAt = this.createdAt, modifiedAt = this.modifiedAt, isPublic = this.isPublic, + isImportant = this.isImportant, prevId = prevNext?.get(0)?.id, prevTitle = prevNext?.get(0)?.title, nextId = prevNext?.get(1)?.id, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt index ebba6b47..a0f19155 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt @@ -1,6 +1,7 @@ package com.wafflestudio.csereal.core.seminar.service import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse import com.wafflestudio.csereal.core.resource.attachment.service.AttachmentService import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService import com.wafflestudio.csereal.core.seminar.database.SeminarEntity @@ -16,7 +17,14 @@ interface SeminarService { fun searchSeminar(keyword: String?, pageNum: Long): SeminarSearchResponse fun createSeminar(request: SeminarDto, mainImage: MultipartFile?, attachments: List?): SeminarDto fun readSeminar(seminarId: Long, keyword: String?): SeminarDto - fun updateSeminar(seminarId: Long, request: SeminarDto, mainImage: MultipartFile?, attachments: List?): SeminarDto + fun updateSeminar( + seminarId: Long, + request: SeminarDto, + newMainImage: MultipartFile?, + newAttachments: List?, + attachmentsList: List, + ): SeminarDto + fun deleteSeminar(seminarId: Long) } @@ -32,14 +40,18 @@ class SeminarServiceImpl( } @Transactional - override fun createSeminar(request: SeminarDto, mainImage: MultipartFile?, attachments: List?): SeminarDto { + override fun createSeminar( + request: SeminarDto, + mainImage: MultipartFile?, + attachments: List? + ): SeminarDto { val newSeminar = SeminarEntity.of(request) - if(mainImage != null) { + if (mainImage != null) { mainImageService.uploadMainImage(newSeminar, mainImage) } - if(attachments != null) { + if (attachments != null) { attachmentService.uploadAllAttachments(newSeminar, attachments) } @@ -66,27 +78,39 @@ class SeminarServiceImpl( } @Transactional - override fun updateSeminar(seminarId: Long, request: SeminarDto, mainImage: MultipartFile?, attachments: List?): SeminarDto { + override fun updateSeminar( + seminarId: Long, + request: SeminarDto, + newMainImage: MultipartFile?, + newAttachments: List?, + attachmentsList: List, + ): SeminarDto { val seminar: SeminarEntity = seminarRepository.findByIdOrNull(seminarId) ?: throw CserealException.Csereal404("존재하지 않는 세미나입니다") - if(seminar.isDeleted) throw CserealException.Csereal404("삭제된 세미나입니다. (seminarId: $seminarId)") + if (seminar.isDeleted) throw CserealException.Csereal404("삭제된 세미나입니다. (seminarId: $seminarId)") seminar.update(request) - if(mainImage != null) { - mainImageService.uploadMainImage(seminar, mainImage) + if (newMainImage != null) { + seminar.mainImage!!.isDeleted = true + mainImageService.uploadMainImage(seminar, newMainImage) } - if(attachments != null) { - seminar.attachments.clear() - attachmentService.uploadAllAttachments(seminar, attachments) + var attachmentResponses: List = listOf() + + if (newAttachments != null) { + attachmentService.updateAttachmentResponses(seminar, attachmentsList) + attachmentService.uploadAllAttachments(seminar, newAttachments) + + attachmentResponses = attachmentService.createAttachmentResponses(seminar.attachments) + } else { + attachmentService.updateAttachmentResponses(seminar, attachmentsList) } val imageURL = mainImageService.createImageURL(seminar.mainImage) - val attachmentResponses = attachmentService.createAttachmentResponses(seminar.attachments) - return SeminarDto.of(seminar, imageURL, attachmentResponses, null) } + @Transactional override fun deleteSeminar(seminarId: Long) { val seminar: SeminarEntity = seminarRepository.findByIdOrNull(seminarId) From 068a38b9bfd30c8f9c09d0a1a3548adeb1630055 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Sun, 3 Sep 2023 02:10:45 +0900 Subject: [PATCH 041/124] =?UTF-8?q?feat:=20=EC=8B=A0=EC=9E=84=EA=B5=90?= =?UTF-8?q?=EC=88=98=EC=B4=88=EB=B9=99=20(#59)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/recruit/api/RecruitController.kt | 21 +++++++++++++++++ .../core/recruit/database/RecruitEntity.kt | 14 +++++++++++ .../recruit/database/RecruitRepository.kt | 6 +++++ .../csereal/core/recruit/dto/RecruitPage.kt | 20 ++++++++++++++++ .../core/recruit/service/RecruitService.kt | 23 +++++++++++++++++++ 5 files changed, 84 insertions(+) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/recruit/api/RecruitController.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/recruit/database/RecruitEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/recruit/database/RecruitRepository.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/recruit/dto/RecruitPage.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/recruit/service/RecruitService.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/recruit/api/RecruitController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/recruit/api/RecruitController.kt new file mode 100644 index 00000000..68b2a35b --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/recruit/api/RecruitController.kt @@ -0,0 +1,21 @@ +package com.wafflestudio.csereal.core.recruit.api + +import com.wafflestudio.csereal.core.recruit.dto.RecruitPage +import com.wafflestudio.csereal.core.recruit.service.RecruitService +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RequestMapping("/api/v1/recruit") +@RestController +class RecruitController( + private val recruitService: RecruitService +) { + + @GetMapping + fun getRecruitPage(): ResponseEntity { + return ResponseEntity.ok(recruitService.getRecruitPage()) + } + +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/recruit/database/RecruitEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/recruit/database/RecruitEntity.kt new file mode 100644 index 00000000..4d49f302 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/recruit/database/RecruitEntity.kt @@ -0,0 +1,14 @@ +package com.wafflestudio.csereal.core.recruit.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import jakarta.persistence.Column +import jakarta.persistence.Entity + +@Entity(name = "recruit") +class RecruitEntity( + val latestRecruitTitle: String, + val latestRecruitUrl: String, + + @Column(columnDefinition = "text") + val description: String +) : BaseTimeEntity() diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/recruit/database/RecruitRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/recruit/database/RecruitRepository.kt new file mode 100644 index 00000000..795ef256 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/recruit/database/RecruitRepository.kt @@ -0,0 +1,6 @@ +package com.wafflestudio.csereal.core.recruit.database + +import org.springframework.data.jpa.repository.JpaRepository + +interface RecruitRepository : JpaRepository { +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/recruit/dto/RecruitPage.kt b/src/main/kotlin/com/wafflestudio/csereal/core/recruit/dto/RecruitPage.kt new file mode 100644 index 00000000..ca631293 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/recruit/dto/RecruitPage.kt @@ -0,0 +1,20 @@ +package com.wafflestudio.csereal.core.recruit.dto + +import com.wafflestudio.csereal.core.recruit.database.RecruitEntity + + +data class RecruitPage( + val latestRecruitTitle: String, + val latestRecruitUrl: String, + val description: String +) { + companion object { + fun of(recruitEntity: RecruitEntity): RecruitPage { + return RecruitPage( + latestRecruitTitle = recruitEntity.latestRecruitTitle, + latestRecruitUrl = recruitEntity.latestRecruitUrl, + description = recruitEntity.description + ) + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/recruit/service/RecruitService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/recruit/service/RecruitService.kt new file mode 100644 index 00000000..b7c19e05 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/recruit/service/RecruitService.kt @@ -0,0 +1,23 @@ +package com.wafflestudio.csereal.core.recruit.service + +import com.wafflestudio.csereal.core.recruit.database.RecruitRepository +import com.wafflestudio.csereal.core.recruit.dto.RecruitPage +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +interface RecruitService { + fun getRecruitPage(): RecruitPage +} + +@Service +@Transactional +class RecruitServiceImpl( + private val recruitRepository: RecruitRepository +) : RecruitService { + + @Transactional(readOnly = true) + override fun getRecruitPage(): RecruitPage { + val recruit = recruitRepository.findAll()[0] + return RecruitPage.of(recruit) + } +} From d546a9fd41ee46ec5168284cbf1fec36773c251c Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Sun, 3 Sep 2023 02:10:56 +0900 Subject: [PATCH 042/124] =?UTF-8?q?feat:=20=ED=96=89=EC=A0=95=EC=8B=A4=20?= =?UTF-8?q?=EA=B6=8C=ED=95=9C=20=EC=B2=B4=ED=81=AC=20API=20(#60)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../csereal/core/user/api/UserController.kt | 31 +++++++++++++++++++ .../core/user/dto/StaffAuthResponse.kt | 5 +++ .../csereal/core/user/service/UserService.kt | 24 ++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/user/api/UserController.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/user/dto/StaffAuthResponse.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/user/service/UserService.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/user/api/UserController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/user/api/UserController.kt new file mode 100644 index 00000000..828bbade --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/user/api/UserController.kt @@ -0,0 +1,31 @@ +package com.wafflestudio.csereal.core.user.api + +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.user.dto.StaffAuthResponse +import com.wafflestudio.csereal.core.user.service.UserService +import org.springframework.http.ResponseEntity +import org.springframework.security.core.annotation.AuthenticationPrincipal +import org.springframework.security.oauth2.core.oidc.user.OidcUser +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RequestMapping("/api/v1/user") +@RestController +class UserController( + private val userService: UserService +) { + + @GetMapping("/is-staff") + fun isStaff(@AuthenticationPrincipal oidcUser: OidcUser?): ResponseEntity { + if (oidcUser == null) { + throw CserealException.Csereal401("로그인이 필요합니다.") + } + val username = oidcUser.idToken.getClaim("username") + if (userService.checkStaffAuth(username)) { + return ResponseEntity.ok(StaffAuthResponse(true)) + } else { + return ResponseEntity.ok(StaffAuthResponse(false)) + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/user/dto/StaffAuthResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/user/dto/StaffAuthResponse.kt new file mode 100644 index 00000000..3d980614 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/user/dto/StaffAuthResponse.kt @@ -0,0 +1,5 @@ +package com.wafflestudio.csereal.core.user.dto + +data class StaffAuthResponse( + val isStaff: Boolean +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/user/service/UserService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/user/service/UserService.kt new file mode 100644 index 00000000..150d1ea7 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/user/service/UserService.kt @@ -0,0 +1,24 @@ +package com.wafflestudio.csereal.core.user.service + +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.user.database.Role +import com.wafflestudio.csereal.core.user.database.UserRepository +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +interface UserService { + fun checkStaffAuth(username: String): Boolean +} + +@Service +@Transactional +class UserServiceImpl( + private val userRepository: UserRepository +) : UserService { + + @Transactional(readOnly = true) + override fun checkStaffAuth(username: String): Boolean { + val user = userRepository.findByUsername(username) ?: throw CserealException.Csereal404("재로그인이 필요합니다.") + return user.role == Role.ROLE_STAFF + } +} From 6f23b0e76571d628eac9bac81875744472d13153 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=ED=98=81=EC=A4=80=20=28HyukJoon=20Woo=29?= Date: Sun, 3 Sep 2023 11:36:06 +0900 Subject: [PATCH 043/124] =?UTF-8?q?[CICD]=20mainImage,=20attachment=20?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C=20mount=20=EC=84=A4=EC=A0=95=20(#55)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * CICD: Add mainImage, attachment directory for mounting * CICD: Add volumes to mount with server directory. (for attachment) --- Dockerfile | 8 ++++++++ docker-compose-backend.yml | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/Dockerfile b/Dockerfile index 5b233b1f..d93df27d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,21 @@ FROM openjdk:17-slim +# Set profile ARG PROFILE ENV profile=$PROFILE +# Set workdir as /app RUN mkdir /app WORKDIR /app +# Copy jar file COPY ./build/libs/*.jar /app/app.jar +# Make directories to mount +RUN mkdir /app/mainImage +RUN mkdir /app/attachment + +# Expose port 8080 EXPOSE 8080 ENTRYPOINT java -Dspring.profiles.active=$profile -jar app.jar \ No newline at end of file diff --git a/docker-compose-backend.yml b/docker-compose-backend.yml index 344c10be..1b05cc27 100644 --- a/docker-compose-backend.yml +++ b/docker-compose-backend.yml @@ -7,6 +7,9 @@ services: PROFILE: ${PROFILE} ports: - 8080:8080 + volumes: + - ./mainImage:/app/mainImage + - ./attachment:/app/attachment environment: SPRING_DATASOURCE_URL: "jdbc:mysql://host.docker.internal:3306/${MYSQL_DATABASE}?serverTimezone=Asia/Seoul&useSSL=false&allowPublicKeyRetrieval=true" SPRING_DATASOURCE_USERNAME: ${MYSQL_USER} @@ -25,6 +28,9 @@ services: # PROFILE: ${PROFILE} # ports: # - 8081:8080 +# volumes: +# - ./mainImage:/app/mainImage +# - ./attachment:/app/attachment # environment: # SPRING_DATASOURCE_URL: "jdbc:mysql://host.docker.internal:3306/${MYSQL_DATABASE}?serverTimezone=Asia/Seoul&useSSL=false&allowPublicKeyRetrieval=true" # SPRING_DATASOURCE_USERNAME: ${MYSQL_USER} From d806f6c47946a25ae325c72ede95b530ab746b4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=ED=98=81=EC=A4=80=20=28HyukJoon=20Woo=29?= Date: Sun, 3 Sep 2023 11:47:42 +0900 Subject: [PATCH 044/124] =?UTF-8?q?[Feat]=20=EC=98=88=EC=A0=84=20url?= =?UTF-8?q?=EA=B3=BC=20=EB=B9=84=EC=8A=B7=ED=95=98=EA=B2=8C=20=EC=9D=B4?= =?UTF-8?q?=EC=A0=84=20=ED=8C=8C=EC=9D=BC(=EC=82=AC=EC=A7=84)=EB=93=A4=20?= =?UTF-8?q?=EC=A0=91=EA=B7=BC=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20controller=20=EC=83=9D=EC=84=B1=20(#58)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Config: Add oldFiles path for properties. * Feat: Add deprecated file controller for getting old files temporarily. * CICD: Add volume mounting for old files. * CICD: Add dir to mount for old files. * Refactor: Change api to /sites/default/files/{PATH} * CICD: Add reverse proxy for old file serving. --- Dockerfile | 1 + caddy/Caddyfile | 3 + docker-compose-backend.yml | 5 +- .../mainImage/api/DeprecatedFileController.kt | 63 +++++++++++++++++++ src/main/resources/application.yaml | 7 ++- 5 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/DeprecatedFileController.kt diff --git a/Dockerfile b/Dockerfile index d93df27d..63e3df63 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,6 +14,7 @@ COPY ./build/libs/*.jar /app/app.jar # Make directories to mount RUN mkdir /app/mainImage RUN mkdir /app/attachment +RUN mkdir /app/cse-files # Expose port 8080 EXPOSE 8080 diff --git a/caddy/Caddyfile b/caddy/Caddyfile index 5891b637..bb907834 100644 --- a/caddy/Caddyfile +++ b/caddy/Caddyfile @@ -5,6 +5,9 @@ # Backend reverse_proxy /api/* host.docker.internal:8080 #host.docker.internal:8081 # For blue/green + # Old file serving + reverse_proxy /sites/default/files/* host.docker.internal:8080 #host.docker.internal:8081 # For blue/green + # Login reverse_proxy /oauth2/authorization/idsnucse host.docker.internal:8080 #host.docker.internal:8081 # For blue/green diff --git a/docker-compose-backend.yml b/docker-compose-backend.yml index 1b05cc27..c90fc061 100644 --- a/docker-compose-backend.yml +++ b/docker-compose-backend.yml @@ -8,8 +8,9 @@ services: ports: - 8080:8080 volumes: - - ./mainImage:/app/mainImage + - ./cse-files:/app/cse-files - ./attachment:/app/attachment + environment: SPRING_DATASOURCE_URL: "jdbc:mysql://host.docker.internal:3306/${MYSQL_DATABASE}?serverTimezone=Asia/Seoul&useSSL=false&allowPublicKeyRetrieval=true" SPRING_DATASOURCE_USERNAME: ${MYSQL_USER} @@ -29,7 +30,7 @@ services: # ports: # - 8081:8080 # volumes: -# - ./mainImage:/app/mainImage +# - ./cse-files:/app/cse-files # - ./attachment:/app/attachment # environment: # SPRING_DATASOURCE_URL: "jdbc:mysql://host.docker.internal:3306/${MYSQL_DATABASE}?serverTimezone=Asia/Seoul&useSSL=false&allowPublicKeyRetrieval=true" diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/DeprecatedFileController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/DeprecatedFileController.kt new file mode 100644 index 00000000..0e7b6c83 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/DeprecatedFileController.kt @@ -0,0 +1,63 @@ +package com.wafflestudio.csereal.core.resource.mainImage.api + +import jakarta.servlet.http.HttpServletRequest +import org.springframework.beans.factory.annotation.Value +import org.springframework.core.io.Resource +import org.springframework.core.io.UrlResource +import org.springframework.http.HttpHeaders +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.util.AntPathMatcher +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController +import java.net.URLEncoder +import java.nio.file.Paths + +@RestController +@RequestMapping("/sites/default/files") +class DeprecatedFileController ( + @Value("\${oldFiles.path}") + private val oldFilesPath: String, +) { + @GetMapping("/{map}/**") + fun serveOldFile( + @PathVariable map: String, // Just for ensure at least one path variable + @RequestParam(defaultValue = "false") download: Boolean, + request: HttpServletRequest + ): ResponseEntity { + // Extract path from pattern + val fileSubDir = AntPathMatcher().extractPathWithinPattern( + "/api/sites/default/files/**", + request.servletPath + ).substringAfter("/api/sites/default/files/") + + val file = Paths.get(oldFilesPath, fileSubDir) + val resource = UrlResource(file.toUri()) + + return if (resource.exists() || resource.isReadable) { + val contentType: String? = request.servletContext.getMimeType(resource.file.absolutePath) + val headers = HttpHeaders() + + headers.contentType = + org.springframework.http.MediaType.parseMediaType(contentType ?: "application/octet-stream") + + if (download) { + val originalFilename = fileSubDir.substringAfterLast("/") + + val encodedFilename = URLEncoder.encode(originalFilename, Charsets.UTF_8.toString()).replace("+", "%20") + + headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''$encodedFilename") + } + + ResponseEntity.ok() + .headers(headers) + .body(resource) + } else { + ResponseEntity.status(HttpStatus.NOT_FOUND).build() + } + } + +} \ No newline at end of file diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index c65dc283..71bb5982 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -70,6 +70,9 @@ csereal_attachment: upload: path: ./attachment +oldFiles: + path: ./cse-files + endpoint: backend: http://localhost:8080 frontend: http://localhost:3000 @@ -89,7 +92,6 @@ spring: redirect-uri: http://${URL}/api/v1/login/oauth2/code/idsnucse - csereal_mainImage: upload: path: /app/mainImage/ @@ -98,6 +100,9 @@ csereal_attachment: upload: path: /app/attachment/ +oldFiles: + path: /app/cse-files + endpoint: backend: http://${URL}/api frontend: http://${URL} From 433a2bfbe9f5fc20d914e4f00e47a842f4638e29 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Sun, 3 Sep 2023 14:02:17 +0900 Subject: [PATCH 045/124] =?UTF-8?q?fix:=20=EC=98=88=EC=95=BD=20dto?= =?UTF-8?q?=EC=97=90=20=EC=A7=80=EB=8F=84=EA=B5=90=EC=88=98,=20=EB=B0=98?= =?UTF-8?q?=EB=B3=B5=20=ED=9A=9F=EC=88=98=20=EC=B6=94=EA=B0=80=20(#61)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/reservation/database/ReservationEntity.kt | 4 ++++ .../csereal/core/reservation/dto/ReservationDto.kt | 8 ++++++-- .../csereal/core/reservation/dto/ReserveRequest.kt | 3 ++- .../core/reservation/service/ReservationService.kt | 2 +- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/ReservationEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/ReservationEntity.kt index 8412f229..32bdf87d 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/ReservationEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/ReservationEntity.kt @@ -30,6 +30,8 @@ class ReservationEntity( val purpose: String, val startTime: LocalDateTime, val endTime: LocalDateTime, + val professor: String, + val recurringWeeks: Int = 1, val recurrenceId: UUID? = null @@ -61,6 +63,8 @@ class ReservationEntity( purpose = reserveRequest.purpose, startTime = start, endTime = end, + professor = reserveRequest.professor, + recurringWeeks = reserveRequest.recurringWeeks, recurrenceId = recurrenceId ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReservationDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReservationDto.kt index f3c3ff3e..9ca83d30 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReservationDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReservationDto.kt @@ -11,11 +11,13 @@ data class ReservationDto( val purpose: String, val startTime: LocalDateTime, val endTime: LocalDateTime, + val recurringWeeks: Int = 1, val roomName: String?, val roomLocation: String, val userName: String, val contactEmail: String, - val contactPhone: String + val contactPhone: String, + val professor: String ) { companion object { fun of(reservationEntity: ReservationEntity): ReservationDto { @@ -26,11 +28,13 @@ data class ReservationDto( purpose = reservationEntity.purpose, startTime = reservationEntity.startTime, endTime = reservationEntity.endTime, + recurringWeeks = reservationEntity.recurringWeeks, roomName = reservationEntity.room.name, roomLocation = reservationEntity.room.location, userName = reservationEntity.user.username, contactEmail = reservationEntity.contactEmail, - contactPhone = reservationEntity.contactPhone + contactPhone = reservationEntity.contactPhone, + professor = reservationEntity.professor ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReserveRequest.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReserveRequest.kt index 55befd76..b3e0655e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReserveRequest.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReserveRequest.kt @@ -7,8 +7,9 @@ data class ReserveRequest( val title: String, val contactEmail: String, val contactPhone: String, + val professor: String, val purpose: String, val startTime: LocalDateTime, val endTime: LocalDateTime, - val recurringWeeks: Int? = null + val recurringWeeks: Int = 1 ) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/service/ReservationService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/service/ReservationService.kt index 5941bd4b..f0b93136 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/service/ReservationService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/service/ReservationService.kt @@ -52,7 +52,7 @@ class ReservationServiceImpl( val recurrenceId = UUID.randomUUID() - val numberOfWeeks = reserveRequest.recurringWeeks ?: 1 + val numberOfWeeks = reserveRequest.recurringWeeks for (week in 0 until numberOfWeeks) { val start = reserveRequest.startTime.plusWeeks(week.toLong()) From 878b4f4b3eead437b99e9f596583219b3305dd42 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Sun, 3 Sep 2023 15:20:03 +0900 Subject: [PATCH 046/124] =?UTF-8?q?fix:=20=EB=A1=9C=EA=B7=B8=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EC=97=94=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20(#64)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 로그아웃 엔드포인트 변경 * fix: 로그아웃 성공 리다이렉트 엔드포인트 변경 --- .../com/wafflestudio/csereal/common/config/SecurityConfig.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt index 22b49d63..bee84ad8 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt @@ -38,6 +38,7 @@ class SecurityConfig( .userInfoEndpoint().oidcUserService(customOidcUserService).and() .successHandler(CustomAuthenticationSuccessHandler(endpointProperties.frontend)).and() .logout() + .logoutUrl("/api/v1/logout") .logoutSuccessHandler(oidcLogoutSuccessHandler()) .invalidateHttpSession(true) .clearAuthentication(true) @@ -56,7 +57,7 @@ class SecurityConfig( response: HttpServletResponse?, authentication: Authentication? ) { - val redirectUrl = "${endpointProperties.frontend}/api/v1/logout/success" + val redirectUrl = "${endpointProperties.frontend}/logout/success" super.setDefaultTargetUrl(redirectUrl) super.onLogoutSuccess(request, response, authentication) } From f79edc9bf3da8779644287ccde663968696031d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=ED=98=81=EC=A4=80=20=28HyukJoon=20Woo=29?= Date: Mon, 4 Sep 2023 15:05:45 +0900 Subject: [PATCH 047/124] =?UTF-8?q?[Fix]=20Notice,=20News=20Description=20?= =?UTF-8?q?TEXT=20type=EC=9C=BC=EB=A1=9C=20=EB=B3=80=ED=99=98=20(#65)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix: Change notice and news entity description type as TEXT. * Fix: Change to mediumtext --- .../com/wafflestudio/csereal/core/news/database/NewsEntity.kt | 1 + .../wafflestudio/csereal/core/notice/database/NoticeEntity.kt | 3 +++ .../csereal/core/seminar/database/SeminarEntity.kt | 4 ++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt index d61709d6..accb21f5 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt @@ -15,6 +15,7 @@ class NewsEntity( var title: String, + @Column(columnDefinition = "mediumtext") var description: String, var isPublic: Boolean, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt index 5113b115..35bd5a09 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt @@ -12,7 +12,10 @@ import jakarta.persistence.* class NoticeEntity( var isDeleted: Boolean = false, var title: String, + + @Column(columnDefinition = "mediumtext") var description: String, + var isPublic: Boolean, var isPinned: Boolean, var isImportant: Boolean, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt index 9a8742a5..60ecee67 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt @@ -15,10 +15,10 @@ class SeminarEntity( var title: String, - @Column(columnDefinition = "text") + @Column(columnDefinition = "mediumtext") var description: String, - @Column(columnDefinition = "text") + @Column(columnDefinition = "mediumtext") var introduction: String, // 연사 정보 From f47b84553d374f7eb7ef11662ca05af97d84313a Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Mon, 4 Sep 2023 16:18:29 +0900 Subject: [PATCH 048/124] =?UTF-8?q?fix:=20newsSearchResponse,=20seminarSea?= =?UTF-8?q?rchResponse=20=EC=88=98=EC=A0=95=20(#66)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: newsSearchResponse 수정 * fix: SeminarSearchResponse 수정 * fix: 오타수정 --- .../core/news/database/NewsRepository.kt | 14 ++++++++----- .../csereal/core/news/dto/NewsSearchDto.kt | 5 +++-- .../seminar/database/SeminarRepository.kt | 20 ++++++++++++++++--- .../core/seminar/dto/SeminarSearchDto.kt | 8 +++++--- 4 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt index 80045bb0..5a51e533 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt @@ -7,6 +7,7 @@ import com.wafflestudio.csereal.core.news.database.QNewsEntity.newsEntity import com.wafflestudio.csereal.core.news.database.QNewsTagEntity.newsTagEntity import com.wafflestudio.csereal.core.news.dto.NewsSearchDto import com.wafflestudio.csereal.core.news.dto.NewsSearchResponse +import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService import org.jsoup.Jsoup import org.jsoup.parser.Parser import org.jsoup.safety.Safelist @@ -25,6 +26,7 @@ interface CustomNewsRepository { @Component class NewsRepositoryImpl( private val queryFactory: JPAQueryFactory, + private val mainImageService: MainImageService, ) : CustomNewsRepository { override fun searchNews(tag: List?, keyword: String?, pageNum: Long): NewsSearchResponse { val keywordBooleanBuilder = BooleanBuilder() @@ -66,12 +68,14 @@ class NewsRepositoryImpl( .fetch() val newsSearchDtoList : List = newsEntityList.map { + val imageURL = mainImageService.createImageURL(it.mainImage) NewsSearchDto( id = it.id, title = it.title, - summary = summary(it.description), + description = clean(it.description), createdAt = it.createdAt, - tags = it.newsTags.map { newsTagEntity -> newsTagEntity.tag.id } + tags = it.newsTags.map { newsTagEntity -> newsTagEntity.tag.name }, + imageURL = imageURL ) } return NewsSearchResponse(total, newsSearchDtoList) @@ -131,8 +135,8 @@ class NewsRepositoryImpl( return prevNext } - private fun summary(description: String): String { - val summary = Jsoup.clean(description, Safelist.none()) - return Parser.unescapeEntities(summary, false) + private fun clean(description: String): String { + val cleanDescription = Jsoup.clean(description, Safelist.none()) + return Parser.unescapeEntities(cleanDescription, false) } } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchDto.kt index 0d3cae9f..2825753b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchDto.kt @@ -6,7 +6,8 @@ import java.time.LocalDateTime data class NewsSearchDto @QueryProjection constructor( val id: Long, val title: String, - var summary: String, + var description: String, val createdAt: LocalDateTime?, - var tags: List? + var tags: List?, + var imageURL: String?, ) \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt index e3df3a59..f3ada06f 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt @@ -3,9 +3,13 @@ package com.wafflestudio.csereal.core.seminar.database import com.querydsl.core.BooleanBuilder import com.querydsl.jpa.impl.JPAQueryFactory import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService import com.wafflestudio.csereal.core.seminar.database.QSeminarEntity.seminarEntity import com.wafflestudio.csereal.core.seminar.dto.SeminarSearchDto import com.wafflestudio.csereal.core.seminar.dto.SeminarSearchResponse +import org.jsoup.Jsoup +import org.jsoup.parser.Parser +import org.jsoup.safety.Safelist import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Component @@ -20,6 +24,7 @@ interface CustomSeminarRepository { @Component class SeminarRepositoryImpl( private val queryFactory: JPAQueryFactory, + private val mainImageService: MainImageService, ) : CustomSeminarRepository { override fun searchSeminar(keyword: String?, pageNum: Long): SeminarSearchResponse { val keywordBooleanBuilder = BooleanBuilder() @@ -61,15 +66,19 @@ class SeminarRepositoryImpl( isYearLast = true } + val imageURL = mainImageService.createImageURL(seminarEntityList[i].mainImage) + seminarSearchDtoList.add( SeminarSearchDto( id = seminarEntityList[i].id, title = seminarEntityList[i].title, - startDate = seminarEntityList[i].startDate, - isYearLast = isYearLast, + description = clean(seminarEntityList[i].description), name = seminarEntityList[i].name, affiliation = seminarEntityList[i].affiliation, - location = seminarEntityList[i].location + startDate = seminarEntityList[i].startDate, + location = seminarEntityList[i].location, + imageURL = imageURL, + isYearLast = isYearLast, ) ) } @@ -119,4 +128,9 @@ class SeminarRepositoryImpl( return prevNext } + + private fun clean(description: String): String { + val cleanDescription = Jsoup.clean(description, Safelist.none()) + return Parser.unescapeEntities(cleanDescription, false) + } } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarSearchDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarSearchDto.kt index 69ada786..483f8513 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarSearchDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarSearchDto.kt @@ -5,10 +5,12 @@ import com.querydsl.core.annotations.QueryProjection data class SeminarSearchDto @QueryProjection constructor( val id: Long, val title: String, + val description: String, + val name: String, + val affiliation: String, val startDate: String?, + val location: String, + val imageURL: String?, val isYearLast: Boolean, - val name: String, - val affiliation: String?, - val location: String ) { } \ No newline at end of file From 0c83efb439b4241898336b6068aacfb4c4c775e7 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Mon, 4 Sep 2023 17:58:21 +0900 Subject: [PATCH 049/124] =?UTF-8?q?fix:=20=ED=8C=8C=EC=9D=BC=20=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=20=EA=B2=BD=EB=A1=9C=20=ED=86=B5=EC=9D=BC=20?= =?UTF-8?q?(#68)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../csereal/core/notice/dto/NoticeDto.kt | 8 ++++-- .../attachment/service/AttachmentService.kt | 26 ++++++++++++++----- .../resource/mainImage/api/FileController.kt | 2 +- .../mainImage/service/MainImageService.kt | 4 ++- src/main/resources/application.yaml | 18 +++---------- 5 files changed, 33 insertions(+), 25 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt index 43b85e65..f9491548 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt @@ -8,7 +8,7 @@ data class NoticeDto( val id: Long, val title: String, val description: String, - val author: String, + val author: String?, val tags: List, val createdAt: LocalDateTime?, val modifiedAt: LocalDateTime?, @@ -23,7 +23,11 @@ data class NoticeDto( ) { companion object { - fun of(entity: NoticeEntity, attachmentResponses: List, prevNext: Array?): NoticeDto = entity.run { + fun of( + entity: NoticeEntity, + attachmentResponses: List, + prevNext: Array? + ): NoticeDto = entity.run { NoticeDto( id = this.id, title = this.title, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt index 42dbc2bc..b23b58e3 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt @@ -27,10 +27,12 @@ interface AttachmentService { labEntity: LabEntity, requestAttachment: MultipartFile ): AttachmentDto + fun uploadAllAttachments( contentEntityType: AttachmentContentEntityType, requestAttachments: List, ): List + fun createAttachmentResponses(attachments: List?): List fun updateAttachmentResponses( contentEntity: AttachmentContentEntityType, @@ -41,7 +43,7 @@ interface AttachmentService { @Service class AttachmentServiceImpl( private val attachmentRepository: AttachmentRepository, - @Value("\${csereal_attachment.upload.path}") + @Value("\${csereal.upload.path}") private val path: String, private val endpointProperties: EndpointProperties, ) : AttachmentService { @@ -73,6 +75,7 @@ class AttachmentServiceImpl( ) } + @Transactional override fun uploadAllAttachments( contentEntity: AttachmentContentEntityType, @@ -114,14 +117,14 @@ class AttachmentServiceImpl( } @Transactional - override fun createAttachmentResponses(attachments: List?): List{ + override fun createAttachmentResponses(attachments: List?): List { val list = mutableListOf() if (attachments != null) { for (attachment in attachments) { - if(attachment.isDeleted == false) { + if (attachment.isDeleted == false) { val attachmentDto = AttachmentResponse( name = attachment.filename, - url = "${endpointProperties.backend}/v1/attachment/${attachment.filename}", + url = "${endpointProperties.backend}/v1/file/${attachment.filename}", bytes = attachment.size, ) list.add(attachmentDto) @@ -133,12 +136,15 @@ class AttachmentServiceImpl( } @Transactional - override fun updateAttachmentResponses(contentEntity: AttachmentContentEntityType, attachmentsList: List) { + override fun updateAttachmentResponses( + contentEntity: AttachmentContentEntityType, + attachmentsList: List + ) { val oldAttachments = contentEntity.bringAttachments().map { it.filename } val attachmentsToRemove = oldAttachments - attachmentsList.map { it.name } - when(contentEntity) { + when (contentEntity) { is SeminarEntity -> { for (attachmentFilename in attachmentsToRemove) { val attachmentEntity = attachmentRepository.findByFilename(attachmentFilename) @@ -155,30 +161,36 @@ class AttachmentServiceImpl( contentEntity.attachments.add(attachment) attachment.news = contentEntity } + is NoticeEntity -> { contentEntity.attachments.add(attachment) attachment.notice = contentEntity } + is SeminarEntity -> { contentEntity.attachments.add(attachment) attachment.seminar = contentEntity } + is AboutEntity -> { contentEntity.attachments.add(attachment) attachment.about = contentEntity } + is AcademicsEntity -> { contentEntity.attachments.add(attachment) attachment.academics = contentEntity } + is CourseEntity -> { contentEntity.attachments.add(attachment) attachment.course = contentEntity } + is ResearchEntity -> { contentEntity.attachments.add(attachment) attachment.research = contentEntity } } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/FileController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/FileController.kt index e63dbac5..a8ffc8d2 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/FileController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/FileController.kt @@ -17,7 +17,7 @@ import kotlin.text.Charsets.UTF_8 @RequestMapping("/api/v1/file") @RestController class FileController( - @Value("\${csereal_mainImage.upload.path}") + @Value("\${csereal.upload.path}") private val uploadPath: String ) { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt index 4a6e32eb..34ee2323 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt @@ -29,13 +29,14 @@ interface MainImageService { contentEntityType: MainImageContentEntityType, requestImage: MultipartFile, ): MainImageDto + fun createImageURL(image: MainImageEntity?): String? } @Service class MainImageServiceImpl( private val mainImageRepository: MainImageRepository, - @Value("\${csereal_mainImage.upload.path}") + @Value("\${csereal.upload.path}") private val path: String, private val endpointProperties: EndpointProperties ) : MainImageService { @@ -119,6 +120,7 @@ class MainImageServiceImpl( is ResearchEntity -> { contentEntity.mainImage = mainImage } + else -> { throw WrongMethodTypeException("해당하는 엔티티가 없습니다") } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 71bb5982..fe727fe6 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -61,14 +61,9 @@ logging.level: springframework: security: DEBUG - -csereal_mainImage: - upload: - path: ./attachment - -csereal_attachment: +csereal: upload: - path: ./attachment + path: ./files/ oldFiles: path: ./cse-files @@ -91,14 +86,9 @@ spring: idsnucse: redirect-uri: http://${URL}/api/v1/login/oauth2/code/idsnucse - -csereal_mainImage: - upload: - path: /app/mainImage/ - -csereal_attachment: +csereal: upload: - path: /app/attachment/ + path: /app/files/ oldFiles: path: /app/cse-files From b1985bbd157267f1a097e89056fcaf6f956b6d85 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Tue, 5 Sep 2023 10:56:01 +0900 Subject: [PATCH 050/124] =?UTF-8?q?fix:=20=EA=B0=AF=EC=88=98=EB=A7=8C=20fe?= =?UTF-8?q?tch=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=B5=9C=EC=A0=81=ED=99=94=20?= =?UTF-8?q?(#70)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/news/database/NewsRepository.kt | 32 +++++++++-------- .../core/news/dto/NewsSearchResponse.kt | 4 +-- .../core/notice/database/NoticeRepository.kt | 35 ++++++++++--------- .../core/notice/dto/NoticeSearchResponse.kt | 4 +-- .../seminar/database/SeminarRepository.kt | 22 +++++++----- .../core/seminar/dto/SeminarSearchResponse.kt | 4 +-- 6 files changed, 55 insertions(+), 46 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt index 5a51e533..331d3cd8 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt @@ -32,10 +32,10 @@ class NewsRepositoryImpl( val keywordBooleanBuilder = BooleanBuilder() val tagsBooleanBuilder = BooleanBuilder() - if(!keyword.isNullOrEmpty()) { + if (!keyword.isNullOrEmpty()) { val keywordList = keyword.split("[^a-zA-Z0-9가-힣]".toRegex()) keywordList.forEach { - if(it.length == 1) { + if (it.length == 1) { throw CserealException.Csereal400("각각의 키워드는 한글자 이상이어야 합니다.") } else { keywordBooleanBuilder.and( @@ -45,7 +45,7 @@ class NewsRepositoryImpl( } } } - if(!tag.isNullOrEmpty()) { + if (!tag.isNullOrEmpty()) { tag.forEach { tagsBooleanBuilder.or( newsTagEntity.tag.name.eq(it) @@ -58,11 +58,12 @@ class NewsRepositoryImpl( .where(newsEntity.isDeleted.eq(false), newsEntity.isPublic.eq(true)) .where(keywordBooleanBuilder).where(tagsBooleanBuilder) - val total = jpaQuery.distinct().fetch().size + val countQuery = jpaQuery.clone() + val total = countQuery.select(newsEntity.countDistinct()).fetchOne() val newsEntityList = jpaQuery .orderBy(newsEntity.createdAt.desc()) - .offset(20*pageNum) //로컬 테스트를 위해 잠시 5로 둘 것, 원래는 20 + .offset(20 * pageNum) //로컬 테스트를 위해 잠시 5로 둘 것, 원래는 20 .limit(20) .distinct() .fetch() @@ -78,7 +79,7 @@ class NewsRepositoryImpl( imageURL = imageURL ) } - return NewsSearchResponse(total, newsSearchDtoList) + return NewsSearchResponse(total!!, newsSearchDtoList) } override fun findPrevNextId(newsId: Long, tag: List?, keyword: String?): Array? { @@ -88,7 +89,7 @@ class NewsRepositoryImpl( if (!keyword.isNullOrEmpty()) { val keywordList = keyword.split("[^a-zA-Z0-9가-힣]".toRegex()) keywordList.forEach { - if(it.length == 1) { + if (it.length == 1) { throw CserealException.Csereal400("각각의 키워드는 한글자 이상이어야 합니다.") } else { keywordBooleanBuilder.and( @@ -99,7 +100,7 @@ class NewsRepositoryImpl( } } - if(!tag.isNullOrEmpty()) { + if (!tag.isNullOrEmpty()) { tag.forEach { tagsBooleanBuilder.or( newsTagEntity.tag.name.eq(it) @@ -119,24 +120,25 @@ class NewsRepositoryImpl( val findingId = newsSearchDtoList.indexOfFirst { it.id == newsId } val prevNext: Array? - if(findingId == -1) { + if (findingId == -1) { prevNext = null - } else if(findingId != 0 && findingId != newsSearchDtoList.size-1) { - prevNext = arrayOf(newsSearchDtoList[findingId+1], newsSearchDtoList[findingId-1]) - } else if(findingId == 0) { - if(newsSearchDtoList.size == 1) { + } else if (findingId != 0 && findingId != newsSearchDtoList.size - 1) { + prevNext = arrayOf(newsSearchDtoList[findingId + 1], newsSearchDtoList[findingId - 1]) + } else if (findingId == 0) { + if (newsSearchDtoList.size == 1) { prevNext = arrayOf(null, null) } else { prevNext = arrayOf(newsSearchDtoList[1], null) } } else { - prevNext = arrayOf(null, newsSearchDtoList[newsSearchDtoList.size-2]) + prevNext = arrayOf(null, newsSearchDtoList[newsSearchDtoList.size - 2]) } return prevNext } + private fun clean(description: String): String { val cleanDescription = Jsoup.clean(description, Safelist.none()) return Parser.unescapeEntities(cleanDescription, false) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchResponse.kt index 92a3a63c..81c530cc 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchResponse.kt @@ -4,7 +4,7 @@ import com.querydsl.core.annotations.QueryProjection import java.time.LocalDateTime class NewsSearchResponse @QueryProjection constructor( - val total: Int, + val total: Long, val searchList: List ) { -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt index c1af0c87..d00d0fe1 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt @@ -54,17 +54,18 @@ class NoticeRepositoryImpl( .where(noticeEntity.isDeleted.eq(false), noticeEntity.isPublic.eq(true)) .where(keywordBooleanBuilder).where(tagsBooleanBuilder) - val total = jpaQuery.distinct().fetch().size + val countQuery = jpaQuery.clone() + val total = countQuery.select(noticeEntity.countDistinct()).fetchOne() val noticeEntityList = jpaQuery.orderBy(noticeEntity.isPinned.desc()) .orderBy(noticeEntity.createdAt.desc()) - .offset(20*pageNum) + .offset(20 * pageNum) .limit(20) .distinct() .fetch() - val noticeSearchDtoList : List = noticeEntityList.map { - val hasAttachment : Boolean = it.attachments.isNotEmpty() + val noticeSearchDtoList: List = noticeEntityList.map { + val hasAttachment: Boolean = it.attachments.isNotEmpty() NoticeSearchDto( id = it.id, @@ -75,7 +76,7 @@ class NoticeRepositoryImpl( ) } - return NoticeSearchResponse(total, noticeSearchDtoList) + return NoticeSearchResponse(total!!, noticeSearchDtoList) } override fun findPrevNextId(noticeId: Long, tag: List?, keyword: String?): Array? { @@ -85,7 +86,7 @@ class NoticeRepositoryImpl( if (!keyword.isNullOrEmpty()) { val keywordList = keyword.split("[^a-zA-Z0-9가-힣]".toRegex()) keywordList.forEach { - if(it.length == 1) { + if (it.length == 1) { throw CserealException.Csereal400("각각의 키워드는 한글자 이상이어야 합니다.") } else { keywordBooleanBuilder.and( @@ -96,7 +97,7 @@ class NoticeRepositoryImpl( } } - if(!tag.isNullOrEmpty()) { + if (!tag.isNullOrEmpty()) { tag.forEach { tagsBooleanBuilder.or( noticeTagEntity.tag.name.eq(it) @@ -113,25 +114,25 @@ class NoticeRepositoryImpl( .distinct() .fetch() - val findingId = noticeSearchDtoList.indexOfFirst {it.id == noticeId} + val findingId = noticeSearchDtoList.indexOfFirst { it.id == noticeId } - val prevNext : Array? - if(findingId == -1) { + val prevNext: Array? + if (findingId == -1) { prevNext = arrayOf(null, null) - } else if(findingId != 0 && findingId != noticeSearchDtoList.size-1) { - prevNext = arrayOf(noticeSearchDtoList[findingId+1], noticeSearchDtoList[findingId-1]) - } else if(findingId == 0) { - if(noticeSearchDtoList.size == 1) { + } else if (findingId != 0 && findingId != noticeSearchDtoList.size - 1) { + prevNext = arrayOf(noticeSearchDtoList[findingId + 1], noticeSearchDtoList[findingId - 1]) + } else if (findingId == 0) { + if (noticeSearchDtoList.size == 1) { prevNext = arrayOf(null, null) } else { - prevNext = arrayOf(noticeSearchDtoList[1],null) + prevNext = arrayOf(noticeSearchDtoList[1], null) } } else { - prevNext = arrayOf(null, noticeSearchDtoList[noticeSearchDtoList.size-2]) + prevNext = arrayOf(null, noticeSearchDtoList[noticeSearchDtoList.size - 2]) } return prevNext } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeSearchResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeSearchResponse.kt index c11e21b4..c4fdec97 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeSearchResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeSearchResponse.kt @@ -1,8 +1,8 @@ package com.wafflestudio.csereal.core.notice.dto data class NoticeSearchResponse( - val total: Int, + val total: Long, val searchList: List ) { -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt index f3ada06f..8f665e61 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt @@ -49,20 +49,25 @@ class SeminarRepositoryImpl( .where(seminarEntity.isDeleted.eq(false)) .where(keywordBooleanBuilder) - val total = jpaQuery.fetch().size + val countQuery = jpaQuery.clone() + val total = countQuery.select(seminarEntity.countDistinct()).fetchOne() val seminarEntityList = jpaQuery.orderBy(seminarEntity.createdAt.desc()) - .offset(10*pageNum) + .offset(10 * pageNum) .limit(20) .fetch() - val seminarSearchDtoList : MutableList = mutableListOf() + val seminarSearchDtoList: MutableList = mutableListOf() - for(i: Int in 0 until seminarEntityList.size) { + for (i: Int in 0 until seminarEntityList.size) { var isYearLast = false - if(i == seminarEntityList.size-1) { + if (i == seminarEntityList.size - 1) { isYearLast = true - } else if(seminarEntityList[i].startDate?.substring(0,4) != seminarEntityList[i+1].startDate?.substring(0,4)) { + } else if (seminarEntityList[i].startDate?.substring(0, 4) != seminarEntityList[i + 1].startDate?.substring( + 0, + 4 + ) + ) { isYearLast = true } @@ -83,8 +88,9 @@ class SeminarRepositoryImpl( ) } - return SeminarSearchResponse(total, seminarSearchDtoList) + return SeminarSearchResponse(total!!, seminarSearchDtoList) } + override fun findPrevNextId(seminarId: Long, keyword: String?): Array? { val keywordBooleanBuilder = BooleanBuilder() @@ -133,4 +139,4 @@ class SeminarRepositoryImpl( val cleanDescription = Jsoup.clean(description, Safelist.none()) return Parser.unescapeEntities(cleanDescription, false) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarSearchResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarSearchResponse.kt index b591ac7c..2f2039da 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarSearchResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarSearchResponse.kt @@ -1,7 +1,7 @@ package com.wafflestudio.csereal.core.seminar.dto data class SeminarSearchResponse( - val total: Int, + val total: Long, val searchList: List ) { -} \ No newline at end of file +} From 46320ffff8fe208a96dd5a55a99313dbfa140d6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=ED=98=81=EC=A4=80=20=28HyukJoon=20Woo=29?= Date: Tue, 5 Sep 2023 11:42:06 +0900 Subject: [PATCH 051/124] Fix: Change type of additionalNote column on SeminarEntity to "TEXT" (#71) --- .../wafflestudio/csereal/core/seminar/database/SeminarEntity.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt index 60ecee67..06218223 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt @@ -40,6 +40,7 @@ class SeminarEntity( var isPublic: Boolean, var isImportant: Boolean, + @Column(columnDefinition = "text") var additionalNote: String?, @OneToOne From a3e803fe8ba1ee5827ad2917653245b86167d136 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Tue, 5 Sep 2023 22:27:01 +0900 Subject: [PATCH 052/124] =?UTF-8?q?fix:=20academics=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=20=ED=94=84=EB=A1=A0=ED=8A=B8=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EC=B6=B0=20=ED=98=91=EC=9D=98,=20admin=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=20=EC=B6=94=EA=B0=80=20(#69)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: professor, staff에 uploadImage 추가 * fix: research에서 isPublic 제거, feat: lab에서 attachments 추가 * fix: attachments -> attachmentResponses 변경 * feat: LabProfessorResponse 추가 * fix: pdf를 list가 아닌 단일항목으로 수정 * feat: readLab 추가 * feat: ResearchLabReponse 추가 및 주석 삭제 * fix: researchDetail에 사진, 첨부파일 업로드 추가 * feat: notice에 attachments 추가 * fix: fix_with_front1 변경 사항에 맞게 수정 * feat: isImportant 추가 * feat: NoticeSearchDto에 hasAttachment 추가 * fix: update에서 attachmetnts가 null일 때 빈 목록 반환 * feat: 공지사항 선택 고정해제, 선택 삭제 추가 * fix: news, seminar에 isImportant 추가 * fix: pr 리뷰 수정 * fix: 수정했던거 다시 복구 * fix: course response 수정 * feat: academics에서 연도별로 주는 response 추가 * feat: academics YearResponses 추가 * fix: createScholarship 합치기 * fix: scholarship 패키지의 내용을 academics 하위로 옮김 * feat: createScholarshipDetail 추가 * fix: 필수 교양과목 response 변경 * feat: admin 패키지에서 readAllSlides 추가 * feat: admin 패키지 readAllImportants 추가 * feat: admin 패키지 중요안내 추가 --- .../core/academics/api/AcademicsController.kt | 50 +++++--- .../academics/database/AcademicsEntity.kt | 4 +- .../academics/database/AcademicsPostType.kt | 2 +- .../academics/database/AcademicsRepository.kt | 3 +- .../core/academics/database/CourseEntity.kt | 7 +- .../academics/database/ScholarshipEntity.kt | 30 +++++ .../database/ScholarshipRepository.kt | 8 ++ .../core/academics/dto/AcademicsDto.kt | 4 +- .../academics/dto/AcademicsYearResponse.kt | 20 ++++ .../csereal/core/academics/dto/CourseDto.kt | 4 +- .../dto/GeneralStudiesPageResponse.kt | 17 +++ .../core/academics/dto/GuidePageResponse.kt | 18 +++ .../dto/ScholarshipDto.kt | 8 +- .../academics/dto/ScholarshipPageResponse.kt | 8 +- .../dto/SimpleScholarshipDto.kt | 8 +- .../core/academics/dto/SubjectChangesDto.kt | 17 +++ .../academics/service/AcademicsService.kt | 87 ++++++++++---- .../csereal/core/admin/api/AdminController.kt | 47 ++++++++ .../core/admin/database/AdminRepository.kt | 38 ++++++ .../csereal/core/admin/dto/ImportantDto.kt | 7 ++ .../core/admin/dto/ImportantRequest.kt | 6 + .../core/admin/dto/ImportantResponse.kt | 11 ++ .../core/admin/dto/NewsIdListRequest.kt | 7 ++ .../csereal/core/admin/dto/SlideResponse.kt | 10 ++ .../core/admin/service/AdminService.kt | 109 ++++++++++++++++++ .../core/news/database/NewsRepository.kt | 4 +- .../core/notice/database/NoticeEntity.kt | 2 - .../core/notice/database/NoticeRepository.kt | 1 + .../attachment/database/AttachmentEntity.kt | 5 + .../attachment/service/AttachmentService.kt | 1 + .../mainImage/service/MainImageService.kt | 1 + .../scholarship/api/ScholarshipController.kt | 21 ---- .../scholarship/database/ScholarshipEntity.kt | 17 --- .../database/ScholarshipRepository.kt | 6 - .../scholarship/service/ScholarshipService.kt | 28 ----- .../seminar/database/SeminarRepository.kt | 1 + 36 files changed, 480 insertions(+), 137 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/database/ScholarshipEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/database/ScholarshipRepository.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsYearResponse.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GeneralStudiesPageResponse.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GuidePageResponse.kt rename src/main/kotlin/com/wafflestudio/csereal/core/{scholarship => academics}/dto/ScholarshipDto.kt (62%) rename src/main/kotlin/com/wafflestudio/csereal/core/{scholarship => academics}/dto/SimpleScholarshipDto.kt (57%) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/SubjectChangesDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/admin/api/AdminController.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/admin/database/AdminRepository.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/ImportantDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/ImportantRequest.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/ImportantResponse.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/NewsIdListRequest.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/SlideResponse.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/admin/service/AdminService.kt delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/scholarship/api/ScholarshipController.kt delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/scholarship/database/ScholarshipEntity.kt delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/scholarship/database/ScholarshipRepository.kt delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/scholarship/service/ScholarshipService.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt index ccc0420b..28f9d1ab 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt @@ -1,9 +1,8 @@ package com.wafflestudio.csereal.core.academics.api -import com.wafflestudio.csereal.core.academics.dto.CourseDto -import com.wafflestudio.csereal.core.academics.dto.AcademicsDto -import com.wafflestudio.csereal.core.academics.dto.ScholarshipPageResponse +import com.wafflestudio.csereal.core.academics.dto.* import com.wafflestudio.csereal.core.academics.service.AcademicsService +import com.wafflestudio.csereal.core.academics.dto.ScholarshipDto import jakarta.validation.Valid import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* @@ -14,8 +13,6 @@ import org.springframework.web.multipart.MultipartFile class AcademicsController( private val academicsService: AcademicsService ) { - - //Todo: 이미지, 파일 추가 필요 @PostMapping("/{studentType}/{postType}") fun createAcademics( @PathVariable studentType: String, @@ -26,12 +23,19 @@ class AcademicsController( return ResponseEntity.ok(academicsService.createAcademics(studentType, postType, request, attachments)) } + @GetMapping("/{studentType}/guide") + fun readGuide( + @PathVariable studentType: String + ): ResponseEntity { + return ResponseEntity.ok(academicsService.readGuide(studentType)) + } + @GetMapping("/{studentType}/{postType}") - fun readAcademics( + fun readAcademicsYearResponses( @PathVariable studentType: String, @PathVariable postType: String, - ): ResponseEntity { - return ResponseEntity.ok(academicsService.readAcademics(studentType, postType)) + ): ResponseEntity> { + return ResponseEntity.ok(academicsService.readAcademicsYearResponses(studentType, postType)) } //교과목 정보 @@ -58,21 +62,29 @@ class AcademicsController( return ResponseEntity.ok(academicsService.readCourse(name)) } - // 장학금 - @PostMapping("/{studentType}/scholarship") - fun createScholarship( + @GetMapping("/undergraduate/general-studies-requirements") + fun readGeneralStudiesRequirements() : ResponseEntity { + return ResponseEntity.ok(academicsService.readGeneralStudies()) + } + + @PostMapping("/{studentType}/scholarshipDetail") + fun createScholarshipDetail( @PathVariable studentType: String, - @Valid @RequestPart("request") request: AcademicsDto, - @RequestPart("attachments") attachments: List?, - ) : ResponseEntity { - return ResponseEntity.ok(academicsService.createAcademics(studentType, "scholarship", request, attachments)) + @Valid @RequestBody request: ScholarshipDto, + ) : ResponseEntity { + return ResponseEntity.ok(academicsService.createScholarshipDetail(studentType, request)) } - @GetMapping("/scholarship") - fun readScholarship( - @RequestParam name: String + @GetMapping("/{studentType}/scholarship") + fun readAllScholarship( + @PathVariable studentType: String ): ResponseEntity { - return ResponseEntity.ok(academicsService.readScholarship(name)) + return ResponseEntity.ok(academicsService.readAllScholarship(studentType)) + } + + @GetMapping("/scholarship/{scholarshipId}") + fun getScholarship(@PathVariable scholarshipId: Long): ResponseEntity { + return ResponseEntity.ok(academicsService.readScholarship(scholarshipId)) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsEntity.kt index 3156d6b1..437c3134 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsEntity.kt @@ -14,9 +14,10 @@ class AcademicsEntity( @Enumerated(EnumType.STRING) var postType: AcademicsPostType, - var name: String, + var name: String?, var description: String, var year: Int?, + var time: String?, @OneToMany(mappedBy = "academics", cascade = [CascadeType.ALL], orphanRemoval = true) var attachments: MutableList = mutableListOf(), @@ -33,6 +34,7 @@ class AcademicsEntity( name = academicsDto.name, description = academicsDto.description, year = academicsDto.year, + time = academicsDto.time, ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsPostType.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsPostType.kt index 02a4a8ad..85684f7a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsPostType.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsPostType.kt @@ -1,5 +1,5 @@ package com.wafflestudio.csereal.core.academics.database enum class AcademicsPostType { - GUIDE, GENERAL_STUDIES_REQUIREMENTS, CURRICULUM, DEGREE_REQUIREMENTS, COURSE_CHANGES, SCHOLARSHIP + GUIDE, GENERAL_STUDIES_REQUIREMENTS, GENERAL_STUDIES_REQUIREMENTS_SUBJECT_CHANGES, CURRICULUM, DEGREE_REQUIREMENTS, COURSE_CHANGES, SCHOLARSHIP } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsRepository.kt index e1f251c3..3026acc9 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsRepository.kt @@ -4,5 +4,6 @@ import org.springframework.data.jpa.repository.JpaRepository interface AcademicsRepository : JpaRepository { fun findByStudentTypeAndPostType(studentType: AcademicsStudentType, postType: AcademicsPostType) : AcademicsEntity - fun findByName(name: String): AcademicsEntity + fun findAllByStudentTypeAndPostTypeOrderByYearDesc(studentType: AcademicsStudentType, postType: AcademicsPostType): List + fun findAllByStudentTypeAndPostTypeOrderByTimeDesc(studentType: AcademicsStudentType, postType: AcademicsPostType): List } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseEntity.kt index 94b6feee..dee58f66 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseEntity.kt @@ -1,11 +1,15 @@ package com.wafflestudio.csereal.core.academics.database import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.common.controller.AttachmentContentEntityType +import com.wafflestudio.csereal.common.controller.MainImageContentEntityType import com.wafflestudio.csereal.core.academics.dto.CourseDto import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity +import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity import jakarta.persistence.CascadeType import jakarta.persistence.Entity import jakarta.persistence.OneToMany +import jakarta.persistence.OneToOne @Entity(name = "course") class CourseEntity( @@ -28,7 +32,8 @@ class CourseEntity( @OneToMany(mappedBy = "course", cascade = [CascadeType.ALL], orphanRemoval = true) var attachments: MutableList = mutableListOf(), - ): BaseTimeEntity() { +): BaseTimeEntity(), AttachmentContentEntityType { + override fun bringAttachments() = attachments companion object { fun of(studentType: AcademicsStudentType, courseDto: CourseDto): CourseEntity { return CourseEntity( diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/ScholarshipEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/ScholarshipEntity.kt new file mode 100644 index 00000000..8eb6ed37 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/ScholarshipEntity.kt @@ -0,0 +1,30 @@ +package com.wafflestudio.csereal.core.academics.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.common.controller.AttachmentContentEntityType +import com.wafflestudio.csereal.core.academics.dto.ScholarshipDto +import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity +import jakarta.persistence.* + +@Entity(name = "scholarship") +class ScholarshipEntity( + @Enumerated(EnumType.STRING) + var studentType: AcademicsStudentType, + + val name: String, + + @Column(columnDefinition = "text") + val description: String, + +) : BaseTimeEntity() { + + companion object { + fun of(studentType: AcademicsStudentType, scholarshipDto: ScholarshipDto): ScholarshipEntity { + return ScholarshipEntity( + studentType = studentType, + name = scholarshipDto.name, + description = scholarshipDto.description, + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/ScholarshipRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/ScholarshipRepository.kt new file mode 100644 index 00000000..f54998a1 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/ScholarshipRepository.kt @@ -0,0 +1,8 @@ +package com.wafflestudio.csereal.core.academics.database + +import com.wafflestudio.csereal.core.academics.database.ScholarshipEntity +import org.springframework.data.jpa.repository.JpaRepository + +interface ScholarshipRepository : JpaRepository { + fun findAllByStudentType(studentType: AcademicsStudentType): List +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsDto.kt index 0df98fd5..ed0e433a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsDto.kt @@ -6,9 +6,10 @@ import java.time.LocalDateTime data class AcademicsDto( val id: Long, - val name: String, + val name: String?, val description: String, val year: Int?, + val time: String?, val createdAt: LocalDateTime?, val modifiedAt: LocalDateTime?, val attachments: List?, @@ -20,6 +21,7 @@ data class AcademicsDto( name = this.name, description = this.description, year = this.year, + time = this.time, createdAt = this.createdAt, modifiedAt = this.modifiedAt, attachments = attachmentResponses, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsYearResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsYearResponse.kt new file mode 100644 index 00000000..b71d5eed --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsYearResponse.kt @@ -0,0 +1,20 @@ +package com.wafflestudio.csereal.core.academics.dto + +import com.wafflestudio.csereal.core.academics.database.AcademicsEntity +import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse + +class AcademicsYearResponse( + val year: Int, + val description: String, + val attachments: List +) { + companion object { + fun of(entity: AcademicsEntity, attachmentResponses: List) = entity.run { + AcademicsYearResponse( + year = entity.year!!, + description = entity.description, + attachments = attachmentResponses + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/CourseDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/CourseDto.kt index 07ab25b3..057e7db0 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/CourseDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/CourseDto.kt @@ -14,7 +14,7 @@ data class CourseDto( val attachments: List?, ) { companion object { - fun of(entity: CourseEntity, attachments: List?): CourseDto = entity.run { + fun of(entity: CourseEntity, attachmentResponses: List): CourseDto = entity.run { CourseDto( id = this.id, classification = this.classification, @@ -23,7 +23,7 @@ data class CourseDto( credit = this.credit, grade = this.grade, description = this.description, - attachments = attachments, + attachments = attachmentResponses, ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GeneralStudiesPageResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GeneralStudiesPageResponse.kt new file mode 100644 index 00000000..0a13e3f5 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GeneralStudiesPageResponse.kt @@ -0,0 +1,17 @@ +package com.wafflestudio.csereal.core.academics.dto + +import com.wafflestudio.csereal.core.academics.database.AcademicsEntity + +class GeneralStudiesPageResponse( + val subjectChanges: List, + val description: String, +) { + companion object { + fun of(entity: AcademicsEntity, subjectChangesEntity: List) = entity.run { + GeneralStudiesPageResponse( + subjectChanges = subjectChangesEntity.map { SubjectChangesDto.of(it) }, + description = this.description + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GuidePageResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GuidePageResponse.kt new file mode 100644 index 00000000..dce8fd54 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GuidePageResponse.kt @@ -0,0 +1,18 @@ +package com.wafflestudio.csereal.core.academics.dto + +import com.wafflestudio.csereal.core.academics.database.AcademicsEntity +import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse + +class GuidePageResponse( + val description: String, + val attachments: List +) { + companion object { + fun of(entity: AcademicsEntity, attachmentResponses: List): GuidePageResponse = entity.run { + GuidePageResponse( + description = this.description, + attachments = attachmentResponses + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/dto/ScholarshipDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/ScholarshipDto.kt similarity index 62% rename from src/main/kotlin/com/wafflestudio/csereal/core/scholarship/dto/ScholarshipDto.kt rename to src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/ScholarshipDto.kt index ba4a2af0..533f9395 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/dto/ScholarshipDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/ScholarshipDto.kt @@ -1,17 +1,17 @@ -package com.wafflestudio.csereal.core.scholarship.dto +package com.wafflestudio.csereal.core.academics.dto -import com.wafflestudio.csereal.core.scholarship.database.ScholarshipEntity +import com.wafflestudio.csereal.core.academics.database.ScholarshipEntity data class ScholarshipDto( val id: Long, - val title: String, + val name: String, val description: String ) { companion object { fun of(scholarshipEntity: ScholarshipEntity): ScholarshipDto { return ScholarshipDto( id = scholarshipEntity.id, - title = scholarshipEntity.title, + name = scholarshipEntity.name, description = scholarshipEntity.description ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/ScholarshipPageResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/ScholarshipPageResponse.kt index 1fe1e62f..926c7b77 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/ScholarshipPageResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/ScholarshipPageResponse.kt @@ -1,21 +1,15 @@ package com.wafflestudio.csereal.core.academics.dto import com.wafflestudio.csereal.core.academics.database.AcademicsEntity -import com.wafflestudio.csereal.core.scholarship.database.ScholarshipEntity -import com.wafflestudio.csereal.core.scholarship.dto.SimpleScholarshipDto -import java.time.LocalDateTime +import com.wafflestudio.csereal.core.academics.database.ScholarshipEntity class ScholarshipPageResponse( - val id: Long, - val name: String, val description: String, val scholarships: List ) { companion object { fun of(scholarship: AcademicsEntity, scholarships: List): ScholarshipPageResponse { return ScholarshipPageResponse( - id = scholarship.id, - name = scholarship.name, description = scholarship.description, scholarships = scholarships.map { SimpleScholarshipDto.of(it) } ) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/dto/SimpleScholarshipDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/SimpleScholarshipDto.kt similarity index 57% rename from src/main/kotlin/com/wafflestudio/csereal/core/scholarship/dto/SimpleScholarshipDto.kt rename to src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/SimpleScholarshipDto.kt index b751b0b7..91662cc9 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/dto/SimpleScholarshipDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/SimpleScholarshipDto.kt @@ -1,16 +1,16 @@ -package com.wafflestudio.csereal.core.scholarship.dto +package com.wafflestudio.csereal.core.academics.dto -import com.wafflestudio.csereal.core.scholarship.database.ScholarshipEntity +import com.wafflestudio.csereal.core.academics.database.ScholarshipEntity data class SimpleScholarshipDto( val id: Long, - val title: String + val name: String ) { companion object { fun of(scholarshipEntity: ScholarshipEntity): SimpleScholarshipDto { return SimpleScholarshipDto( id = scholarshipEntity.id, - title = scholarshipEntity.title + name = scholarshipEntity.name ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/SubjectChangesDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/SubjectChangesDto.kt new file mode 100644 index 00000000..33cf95d3 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/SubjectChangesDto.kt @@ -0,0 +1,17 @@ +package com.wafflestudio.csereal.core.academics.dto + +import com.wafflestudio.csereal.core.academics.database.AcademicsEntity + +class SubjectChangesDto( + val time: String, + val description: String, +) { + companion object { + fun of(entity: AcademicsEntity) = entity.run { + SubjectChangesDto( + time = this.time!!, + description = this.description + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt index 024565a3..eb3e3f9c 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt @@ -1,25 +1,27 @@ package com.wafflestudio.csereal.core.academics.service import com.wafflestudio.csereal.common.CserealException -import com.wafflestudio.csereal.core.about.database.AboutPostType import com.wafflestudio.csereal.core.academics.database.* -import com.wafflestudio.csereal.core.academics.dto.CourseDto -import com.wafflestudio.csereal.core.academics.dto.AcademicsDto +import com.wafflestudio.csereal.core.academics.dto.* import com.wafflestudio.csereal.core.resource.attachment.service.AttachmentService -import com.wafflestudio.csereal.core.academics.dto.ScholarshipPageResponse -import com.wafflestudio.csereal.core.scholarship.database.ScholarshipRepository -import com.wafflestudio.csereal.core.scholarship.dto.SimpleScholarshipDto +import com.wafflestudio.csereal.core.academics.database.ScholarshipRepository +import com.wafflestudio.csereal.core.academics.dto.ScholarshipDto +import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import org.springframework.web.multipart.MultipartFile interface AcademicsService { fun createAcademics(studentType: String, postType: String, request: AcademicsDto, attachments: List?): AcademicsDto - fun readAcademics(studentType: String, postType: String): AcademicsDto + fun readGuide(studentType: String): GuidePageResponse + fun readAcademicsYearResponses(studentType: String, postType: String): List + fun readGeneralStudies(): GeneralStudiesPageResponse fun createCourse(studentType: String, request: CourseDto, attachments: List?): CourseDto fun readAllCourses(studentType: String): List fun readCourse(name: String): CourseDto - fun readScholarship(name: String): ScholarshipPageResponse + fun createScholarshipDetail(studentType: String, request: ScholarshipDto): ScholarshipDto + fun readAllScholarship(studentType: String): ScholarshipPageResponse + fun readScholarship(scholarshipId: Long): ScholarshipDto } @Service @@ -49,28 +51,54 @@ class AcademicsServiceImpl( } @Transactional(readOnly = true) - override fun readAcademics(studentType: String, postType: String): AcademicsDto { + override fun readGuide(studentType: String): GuidePageResponse { val enumStudentType = makeStringToAcademicsStudentType(studentType) + + val academicsEntity = academicsRepository.findByStudentTypeAndPostType(enumStudentType, AcademicsPostType.GUIDE) + val attachmentResponses = attachmentService.createAttachmentResponses(academicsEntity.attachments) + return GuidePageResponse.of(academicsEntity, attachmentResponses) + } + + @Transactional(readOnly = true) + override fun readAcademicsYearResponses(studentType: String, postType: String): List { + val enumStudentType = makeStringToAcademicsStudentType(studentType) val enumPostType = makeStringToAcademicsPostType(postType) - val academics = academicsRepository.findByStudentTypeAndPostType(enumStudentType, enumPostType) + val academicsEntityList = academicsRepository.findAllByStudentTypeAndPostTypeOrderByYearDesc(enumStudentType, enumPostType) - val attachmentResponses = attachmentService.createAttachmentResponses(academics.attachments) + val academicsYearResponses = academicsEntityList.map { + val attachments = attachmentService.createAttachmentResponses(it.attachments) + AcademicsYearResponse.of(it, attachments) + } - return AcademicsDto.of(academics, attachmentResponses) + return academicsYearResponses + } + + override fun readGeneralStudies(): GeneralStudiesPageResponse { + val academicsEntity = academicsRepository.findByStudentTypeAndPostType(AcademicsStudentType.UNDERGRADUATE, AcademicsPostType.GENERAL_STUDIES_REQUIREMENTS) + val subjectChangesList = academicsRepository.findAllByStudentTypeAndPostTypeOrderByTimeDesc( + AcademicsStudentType.UNDERGRADUATE, + AcademicsPostType.GENERAL_STUDIES_REQUIREMENTS_SUBJECT_CHANGES + ) + + return GeneralStudiesPageResponse.of(academicsEntity, subjectChangesList) } @Transactional override fun createCourse(studentType: String, request: CourseDto, attachments: List?): CourseDto { val enumStudentType = makeStringToAcademicsStudentType(studentType) - val course = CourseEntity.of(enumStudentType, request) + val newCourse = CourseEntity.of(enumStudentType, request) - courseRepository.save(course) + if(attachments != null) { + attachmentService.uploadAllAttachments(newCourse, attachments) + } - val attachmentResponses = attachmentService.createAttachmentResponses(course.attachments) + courseRepository.save(newCourse) - return CourseDto.of(course, attachmentResponses) + val attachmentResponses = attachmentService.createAttachmentResponses(newCourse.attachments) + + return CourseDto.of(newCourse, attachmentResponses) } @Transactional(readOnly = true) @@ -92,12 +120,31 @@ class AcademicsServiceImpl( return CourseDto.of(course, attachmentResponses) } + @Transactional + override fun createScholarshipDetail(studentType: String, request: ScholarshipDto): ScholarshipDto { + val enumStudentType = makeStringToAcademicsStudentType(studentType) + val newScholarship = ScholarshipEntity.of(enumStudentType, request) + + scholarshipRepository.save(newScholarship) + + return ScholarshipDto.of(newScholarship) + } + @Transactional(readOnly = true) - override fun readScholarship(name: String): ScholarshipPageResponse { - val scholarship = academicsRepository.findByName(name) - val scholarships = scholarshipRepository.findAll() + override fun readAllScholarship(studentType: String): ScholarshipPageResponse { + val enumStudentType = makeStringToAcademicsStudentType(studentType) + + val academicsEntity = academicsRepository.findByStudentTypeAndPostType(enumStudentType, AcademicsPostType.SCHOLARSHIP) + val scholarshipEntityList = scholarshipRepository.findAllByStudentType(enumStudentType) - return ScholarshipPageResponse.of(scholarship, scholarships) + return ScholarshipPageResponse.of(academicsEntity, scholarshipEntityList) + } + + @Transactional(readOnly = true) + override fun readScholarship(scholarshipId: Long): ScholarshipDto { + val scholarship = scholarshipRepository.findByIdOrNull(scholarshipId) + ?: throw CserealException.Csereal404("id: $scholarshipId 에 해당하는 장학제도를 찾을 수 없습니다") + return ScholarshipDto.of(scholarship) } private fun makeStringToAcademicsStudentType(postType: String): AcademicsStudentType { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/api/AdminController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/api/AdminController.kt new file mode 100644 index 00000000..73229cbb --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/api/AdminController.kt @@ -0,0 +1,47 @@ +package com.wafflestudio.csereal.core.admin.api + +import com.wafflestudio.csereal.core.admin.dto.* +import com.wafflestudio.csereal.core.admin.service.AdminService +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PatchMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController + +@RequestMapping("/api/v1/admin") +@RestController +class AdminController( + private val adminService: AdminService +) { + @GetMapping("/slide") + fun readAllSlides( + @RequestParam(required = false, defaultValue = "0") pageNum: Long + ): ResponseEntity> { + return ResponseEntity.ok(adminService.readAllSlides(pageNum)) + } + + @PatchMapping("/slide") + fun unSlideManyNews( + @RequestBody request: NewsIdListRequest + ) { + adminService.unSlideManyNews(request.newsIdList) + } + + @GetMapping("/important") + fun readAllImportants( + @RequestParam(required = false, defaultValue = "0") pageNum: Long + ): ResponseEntity> { + return ResponseEntity.ok(adminService.readAllImportants(pageNum)) + } + + @PatchMapping("/important") + fun makeNotImportants( + @RequestBody request: ImportantRequest + ) { + adminService.makeNotImportants(request.targetInfos) + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/database/AdminRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/database/AdminRepository.kt new file mode 100644 index 00000000..df3effa5 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/database/AdminRepository.kt @@ -0,0 +1,38 @@ +package com.wafflestudio.csereal.core.admin.database + +import com.querydsl.core.types.Projections +import com.querydsl.jpa.impl.JPAQueryFactory +import com.wafflestudio.csereal.core.admin.dto.ImportantResponse +import com.wafflestudio.csereal.core.admin.dto.SlideResponse +import com.wafflestudio.csereal.core.news.database.QNewsEntity.newsEntity +import org.springframework.stereotype.Component + +interface AdminRepository { + fun readAllSlides(pageNum: Long): List + fun readAllImportants(pageNum: Long): List +} + +@Component +class AdminRepositoryImpl( + private val queryFactory: JPAQueryFactory, +): AdminRepository { + override fun readAllSlides(pageNum: Long): List { + return queryFactory.select( + Projections.constructor( + SlideResponse::class.java, + newsEntity.id, + newsEntity.title, + newsEntity.createdAt + ) + ).from(newsEntity) + .where(newsEntity.isDeleted.eq(false), newsEntity.isPublic.eq(true), newsEntity.isSlide.eq(true)) + .orderBy(newsEntity.createdAt.desc()) + .offset(40*pageNum) + .limit(40) + .fetch() + } + + override fun readAllImportants(pageNum: Long): List { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/ImportantDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/ImportantDto.kt new file mode 100644 index 00000000..b056f38d --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/ImportantDto.kt @@ -0,0 +1,7 @@ +package com.wafflestudio.csereal.core.admin.dto + +class ImportantDto( + val id: Long, + val category: String, +) { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/ImportantRequest.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/ImportantRequest.kt new file mode 100644 index 00000000..ff93f215 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/ImportantRequest.kt @@ -0,0 +1,6 @@ +package com.wafflestudio.csereal.core.admin.dto + +class ImportantRequest( + val targetInfos: List +) { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/ImportantResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/ImportantResponse.kt new file mode 100644 index 00000000..639a768e --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/ImportantResponse.kt @@ -0,0 +1,11 @@ +package com.wafflestudio.csereal.core.admin.dto + +import java.time.LocalDateTime + +class ImportantResponse( + val id: Long, + val title: String, + val createdAt: LocalDateTime?, + val category: String, +) { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/NewsIdListRequest.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/NewsIdListRequest.kt new file mode 100644 index 00000000..f59210cb --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/NewsIdListRequest.kt @@ -0,0 +1,7 @@ +package com.wafflestudio.csereal.core.admin.dto + +data class NewsIdListRequest( + val newsIdList: List +) { + +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/SlideResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/SlideResponse.kt new file mode 100644 index 00000000..e4de274f --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/SlideResponse.kt @@ -0,0 +1,10 @@ +package com.wafflestudio.csereal.core.admin.dto + +import java.time.LocalDateTime + +class SlideResponse( + val id: Long, + val title: String, + val createdAt: LocalDateTime?, +) { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/service/AdminService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/service/AdminService.kt new file mode 100644 index 00000000..4bc2d899 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/service/AdminService.kt @@ -0,0 +1,109 @@ +package com.wafflestudio.csereal.core.admin.service + +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.admin.database.AdminRepository +import com.wafflestudio.csereal.core.admin.dto.ImportantDto +import com.wafflestudio.csereal.core.admin.dto.ImportantRequest +import com.wafflestudio.csereal.core.admin.dto.ImportantResponse +import com.wafflestudio.csereal.core.admin.dto.SlideResponse +import com.wafflestudio.csereal.core.news.database.NewsEntity +import com.wafflestudio.csereal.core.news.database.NewsRepository +import com.wafflestudio.csereal.core.notice.database.NoticeRepository +import com.wafflestudio.csereal.core.seminar.database.SeminarRepository +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.time.LocalDateTime + +interface AdminService { + fun readAllSlides(pageNum: Long): List + fun unSlideManyNews(request: List) + fun readAllImportants(pageNum: Long): List + fun makeNotImportants(request: List) +} + +@Service +class AdminServiceImpl( + private val adminRepository: AdminRepository, + private val noticeRepository: NoticeRepository, + private val newsRepository: NewsRepository, + private val seminarRepository: SeminarRepository, +) : AdminService { + @Transactional + override fun readAllSlides(pageNum: Long): List { + return adminRepository.readAllSlides(pageNum) + } + + @Transactional + override fun unSlideManyNews(request: List) { + for (newsId in request) { + val news: NewsEntity = newsRepository.findByIdOrNull(newsId) + ?: throw CserealException.Csereal404("존재하지 않는 새소식입니다.(newsId=$newsId)") + news.isSlide = false + } + } + + @Transactional + override fun readAllImportants(pageNum: Long): List { + val importantResponses: MutableList = mutableListOf() + noticeRepository.findAllByIsImportant(true).forEach { + importantResponses.add( + ImportantResponse( + id = it.id, + title = it.title, + createdAt = it.createdAt, + category = "notice" + ) + ) + } + + newsRepository.findAllByIsImportant(true).forEach { + importantResponses.add( + ImportantResponse( + id = it.id, + title = it.title, + createdAt = it.createdAt, + category = "news" + ) + ) + } + + seminarRepository.findAllByIsImportant(true).forEach { + importantResponses.add( + ImportantResponse( + id = it.id, + title = it.title, + createdAt = it.createdAt, + category = "seminar" + ) + ) + } + importantResponses.sortByDescending { it.createdAt } + + return importantResponses + } + + @Transactional + override fun makeNotImportants(request: List) { + for(important in request) { + when(important.category) { + "notice" -> { + val notice = noticeRepository.findByIdOrNull(important.id) + ?: throw CserealException.Csereal404("해당하는 공지사항을 찾을 수 없습니다.(noticeId=${important.id})") + notice.isImportant = false + } + "news" -> { + val news = newsRepository.findByIdOrNull(important.id) + ?: throw CserealException.Csereal404("해당하는 새소식을 찾을 수 없습니다.(noticeId=${important.id})") + news.isImportant = false + } + "seminar" -> { + val seminar = seminarRepository.findByIdOrNull(important.id) + ?: throw CserealException.Csereal404("해당하는 세미나를 찾을 수 없습니다.(noticeId=${important.id})") + seminar.isImportant = false + } + } + } + } +} + diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt index 331d3cd8..e76c838e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt @@ -15,7 +15,7 @@ import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Component interface NewsRepository : JpaRepository, CustomNewsRepository { - + fun findAllByIsImportant(isImportant: Boolean): List } interface CustomNewsRepository { @@ -63,7 +63,7 @@ class NewsRepositoryImpl( val newsEntityList = jpaQuery .orderBy(newsEntity.createdAt.desc()) - .offset(20 * pageNum) //로컬 테스트를 위해 잠시 5로 둘 것, 원래는 20 + .offset(20*pageNum) .limit(20) .distinct() .fetch() diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt index 35bd5a09..46291415 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt @@ -12,10 +12,8 @@ import jakarta.persistence.* class NoticeEntity( var isDeleted: Boolean = false, var title: String, - @Column(columnDefinition = "mediumtext") var description: String, - var isPublic: Boolean, var isPinned: Boolean, var isImportant: Boolean, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt index d00d0fe1..97821a0c 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt @@ -11,6 +11,7 @@ import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Component interface NoticeRepository : JpaRepository, CustomNoticeRepository { + fun findAllByIsImportant(isImportant: Boolean): List } interface CustomNoticeRepository { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentEntity.kt index 988ece95..befcb2d6 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentEntity.kt @@ -4,6 +4,7 @@ import com.wafflestudio.csereal.common.config.BaseTimeEntity import com.wafflestudio.csereal.core.about.database.AboutEntity import com.wafflestudio.csereal.core.academics.database.AcademicsEntity import com.wafflestudio.csereal.core.academics.database.CourseEntity +import com.wafflestudio.csereal.core.academics.database.ScholarshipEntity import com.wafflestudio.csereal.core.news.database.NewsEntity import com.wafflestudio.csereal.core.notice.database.NoticeEntity import com.wafflestudio.csereal.core.research.database.LabEntity @@ -52,6 +53,10 @@ class AttachmentEntity( @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "research_id") var research: ResearchEntity? = null, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "scholarship_id") + var scholarship: ScholarshipEntity? = null, ) : BaseTimeEntity() { } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt index b23b58e3..49c6aa4f 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt @@ -5,6 +5,7 @@ import com.wafflestudio.csereal.common.properties.EndpointProperties import com.wafflestudio.csereal.core.about.database.AboutEntity import com.wafflestudio.csereal.core.academics.database.AcademicsEntity import com.wafflestudio.csereal.core.academics.database.CourseEntity +import com.wafflestudio.csereal.core.academics.database.ScholarshipEntity import com.wafflestudio.csereal.core.news.database.NewsEntity import com.wafflestudio.csereal.core.notice.database.NoticeEntity import com.wafflestudio.csereal.core.research.database.LabEntity diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt index 34ee2323..b0e128c0 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt @@ -4,6 +4,7 @@ import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.common.controller.MainImageContentEntityType import com.wafflestudio.csereal.common.properties.EndpointProperties import com.wafflestudio.csereal.core.about.database.AboutEntity +import com.wafflestudio.csereal.core.academics.database.CourseEntity import com.wafflestudio.csereal.core.member.database.ProfessorEntity import com.wafflestudio.csereal.core.member.database.StaffEntity import com.wafflestudio.csereal.core.news.database.NewsEntity diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/api/ScholarshipController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/api/ScholarshipController.kt deleted file mode 100644 index ba70ba00..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/api/ScholarshipController.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.wafflestudio.csereal.core.scholarship.api - -import com.wafflestudio.csereal.core.scholarship.dto.ScholarshipDto -import com.wafflestudio.csereal.core.scholarship.service.ScholarshipService -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RestController - -@RequestMapping("/api/v1/scholarship") -@RestController -class ScholarshipController( - private val scholarshipService: ScholarshipService -) { - - @GetMapping("/{scholarshipId}") - fun getScholarship(@PathVariable scholarshipId: Long): ResponseEntity { - return ResponseEntity.ok(scholarshipService.getScholarship(scholarshipId)) - } -} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/database/ScholarshipEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/database/ScholarshipEntity.kt deleted file mode 100644 index b4342372..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/database/ScholarshipEntity.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.wafflestudio.csereal.core.scholarship.database - -import com.wafflestudio.csereal.common.config.BaseTimeEntity -import jakarta.persistence.Column -import jakarta.persistence.Entity - -@Entity(name = "scholarship") -class ScholarshipEntity( - - val title: String, - - @Column(columnDefinition = "text") - val description: String - -) : BaseTimeEntity() { - -} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/database/ScholarshipRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/database/ScholarshipRepository.kt deleted file mode 100644 index 21f6e63a..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/database/ScholarshipRepository.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.wafflestudio.csereal.core.scholarship.database - -import org.springframework.data.jpa.repository.JpaRepository - -interface ScholarshipRepository : JpaRepository { -} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/service/ScholarshipService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/service/ScholarshipService.kt deleted file mode 100644 index c0c17182..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/scholarship/service/ScholarshipService.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.wafflestudio.csereal.core.scholarship.service - -import com.wafflestudio.csereal.common.CserealException -import com.wafflestudio.csereal.core.scholarship.database.ScholarshipRepository -import com.wafflestudio.csereal.core.scholarship.dto.ScholarshipDto -import com.wafflestudio.csereal.core.scholarship.dto.SimpleScholarshipDto -import org.springframework.data.repository.findByIdOrNull -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional - -interface ScholarshipService { - fun getScholarship(scholarshipId: Long): ScholarshipDto -} - -@Service -@Transactional -class ScholarshipServiceImpl( - private val scholarshipRepository: ScholarshipRepository -) : ScholarshipService { - - @Transactional(readOnly = true) - override fun getScholarship(scholarshipId: Long): ScholarshipDto { - val scholarship = scholarshipRepository.findByIdOrNull(scholarshipId) - ?: throw CserealException.Csereal404("id: $scholarshipId 에 해당하는 장학제도를 찾을 수 없습니다") - return ScholarshipDto.of(scholarship) - } - -} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt index 8f665e61..c2b0c474 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt @@ -14,6 +14,7 @@ import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Component interface SeminarRepository : JpaRepository, CustomSeminarRepository { + fun findAllByIsImportant(isImportant: Boolean): List } interface CustomSeminarRepository { From a3514fb79a87afb981d9d184d62861096aeb772e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=ED=98=81=EC=A4=80=20=28HyukJoon=20Woo=29?= Date: Wed, 6 Sep 2023 17:34:32 +0900 Subject: [PATCH 053/124] =?UTF-8?q?Refactor:=20HTML=EB=A1=9C=EB=B6=80?= =?UTF-8?q?=ED=84=B0=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EC=B6=9C=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20Utils=EB=A1=9C=20=EC=9D=B4=EB=8F=99=20(#73?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat: Add cleanTextFromHtml. * Refactor: Remove private clean method, replace to Utils.cleanTextFromHtml. * Refactor: Remove unused imports. --- .../kotlin/com/wafflestudio/csereal/common/Utils.kt | 10 ++++++++++ .../csereal/core/news/database/NewsRepository.kt | 11 ++--------- .../core/seminar/database/SeminarRepository.kt | 11 ++--------- 3 files changed, 14 insertions(+), 18 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/common/Utils.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/Utils.kt b/src/main/kotlin/com/wafflestudio/csereal/common/Utils.kt new file mode 100644 index 00000000..0ec330f2 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/common/Utils.kt @@ -0,0 +1,10 @@ +package com.wafflestudio.csereal.common + +import org.jsoup.Jsoup +import org.jsoup.parser.Parser +import org.jsoup.safety.Safelist + +fun cleanTextFromHtml(description: String): String { + val cleanDescription = Jsoup.clean(description, Safelist.none()) + return Parser.unescapeEntities(cleanDescription, false) +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt index e76c838e..bb1d5740 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt @@ -3,14 +3,12 @@ package com.wafflestudio.csereal.core.news.database import com.querydsl.core.BooleanBuilder import com.querydsl.jpa.impl.JPAQueryFactory import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.common.cleanTextFromHtml import com.wafflestudio.csereal.core.news.database.QNewsEntity.newsEntity import com.wafflestudio.csereal.core.news.database.QNewsTagEntity.newsTagEntity import com.wafflestudio.csereal.core.news.dto.NewsSearchDto import com.wafflestudio.csereal.core.news.dto.NewsSearchResponse import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService -import org.jsoup.Jsoup -import org.jsoup.parser.Parser -import org.jsoup.safety.Safelist import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Component @@ -73,7 +71,7 @@ class NewsRepositoryImpl( NewsSearchDto( id = it.id, title = it.title, - description = clean(it.description), + description = cleanTextFromHtml(it.description), createdAt = it.createdAt, tags = it.newsTags.map { newsTagEntity -> newsTagEntity.tag.name }, imageURL = imageURL @@ -136,9 +134,4 @@ class NewsRepositoryImpl( return prevNext } - - private fun clean(description: String): String { - val cleanDescription = Jsoup.clean(description, Safelist.none()) - return Parser.unescapeEntities(cleanDescription, false) - } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt index c2b0c474..c2f8f0e4 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt @@ -3,13 +3,11 @@ package com.wafflestudio.csereal.core.seminar.database import com.querydsl.core.BooleanBuilder import com.querydsl.jpa.impl.JPAQueryFactory import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.common.cleanTextFromHtml import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService import com.wafflestudio.csereal.core.seminar.database.QSeminarEntity.seminarEntity import com.wafflestudio.csereal.core.seminar.dto.SeminarSearchDto import com.wafflestudio.csereal.core.seminar.dto.SeminarSearchResponse -import org.jsoup.Jsoup -import org.jsoup.parser.Parser -import org.jsoup.safety.Safelist import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Component @@ -78,7 +76,7 @@ class SeminarRepositoryImpl( SeminarSearchDto( id = seminarEntityList[i].id, title = seminarEntityList[i].title, - description = clean(seminarEntityList[i].description), + description = cleanTextFromHtml(seminarEntityList[i].description), name = seminarEntityList[i].name, affiliation = seminarEntityList[i].affiliation, startDate = seminarEntityList[i].startDate, @@ -135,9 +133,4 @@ class SeminarRepositoryImpl( return prevNext } - - private fun clean(description: String): String { - val cleanDescription = Jsoup.clean(description, Safelist.none()) - return Parser.unescapeEntities(cleanDescription, false) - } } From 9f406af06a7996fa87c07ba992494dfa8536c057 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=ED=98=81=EC=A4=80=20=28HyukJoon=20Woo=29?= Date: Thu, 7 Sep 2023 20:04:39 +0900 Subject: [PATCH 054/124] Feat: Add plain text description for notice (#74) * Feat: Add cleanTextFromHtml. * Refactor: Remove private clean method, replace to Utils.cleanTextFromHtml. * Refactor: Remove unused imports. * Feat: Add plainTextDescription. * Feat: Add plainTextDescription in createNotice. * Feat: add updating plainTextDescription when updating description. --- .../csereal/core/notice/database/NoticeEntity.kt | 10 ++++++++++ .../csereal/core/notice/service/NoticeService.kt | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt index 46291415..7f688cde 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt @@ -1,5 +1,6 @@ package com.wafflestudio.csereal.core.notice.database +import com.wafflestudio.csereal.common.cleanTextFromHtml import com.wafflestudio.csereal.common.config.BaseTimeEntity import com.wafflestudio.csereal.common.controller.AttachmentContentEntityType import com.wafflestudio.csereal.core.notice.dto.NoticeDto @@ -14,6 +15,10 @@ class NoticeEntity( var title: String, @Column(columnDefinition = "mediumtext") var description: String, + + @Column(columnDefinition = "mediumtext") + var plainTextDescription: String, + var isPublic: Boolean, var isPinned: Boolean, var isImportant: Boolean, @@ -32,6 +37,11 @@ class NoticeEntity( override fun bringAttachments() = attachments fun update(updateNoticeRequest: NoticeDto) { + // Update plainTextDescription if description is changed + if (updateNoticeRequest.description != this.description) { + this.plainTextDescription = cleanTextFromHtml(updateNoticeRequest.description) + } + this.title = updateNoticeRequest.title this.description = updateNoticeRequest.description this.isPublic = updateNoticeRequest.isPublic diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt index fd176609..eb0a17db 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt @@ -1,6 +1,7 @@ package com.wafflestudio.csereal.core.notice.service import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.common.cleanTextFromHtml import com.wafflestudio.csereal.core.notice.database.* import com.wafflestudio.csereal.core.notice.dto.* import com.wafflestudio.csereal.core.resource.attachment.service.AttachmentService @@ -79,6 +80,7 @@ class NoticeServiceImpl( val newNotice = NoticeEntity( title = request.title, description = request.description, + plainTextDescription = cleanTextFromHtml(request.description), isPublic = request.isPublic, isPinned = request.isPinned, isImportant = request.isImportant, @@ -136,8 +138,6 @@ class NoticeServiceImpl( val attachmentResponses = attachmentService.createAttachmentResponses(notice.attachments) return NoticeDto.of(notice, attachmentResponses, null) - - } @Transactional From 0ce419990541260c174ab7cdd1465eec7af82ce0 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Fri, 8 Sep 2023 22:40:37 +0900 Subject: [PATCH 055/124] =?UTF-8?q?fix:=20admissions=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=20=ED=94=84=EB=A1=A0=ED=8A=B8=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EA=B2=8C=20=ED=98=91=EC=9D=98=20(#76)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: admissions 프론트에 맞게 협의 * fix: seminarEntity time 삭제 * fix: admissionsEntity pageName 추가 * fix: 불필요한 파일 삭제 * 커밋 --- .../csereal/core/academics/dto/SubjectChangesDto.kt | 1 + .../core/admissions/api/AdmissionsController.kt | 8 ++++---- .../core/admissions/database/AdmissionsEntity.kt | 6 +++--- .../csereal/core/admissions/dto/AdmissionsDto.kt | 2 -- .../core/admissions/service/AdmissionsService.kt | 10 ++++++++-- .../csereal/core/seminar/database/SeminarEntity.kt | 6 ------ .../csereal/core/seminar/dto/SeminarDto.kt | 4 ---- 7 files changed, 16 insertions(+), 21 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/SubjectChangesDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/SubjectChangesDto.kt index 33cf95d3..28754dc7 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/SubjectChangesDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/SubjectChangesDto.kt @@ -7,6 +7,7 @@ class SubjectChangesDto( val description: String, ) { companion object { + fun of(entity: AcademicsEntity) = entity.run { SubjectChangesDto( time = this.time!!, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/AdmissionsController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/AdmissionsController.kt index 36ab19c8..25798f87 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/AdmissionsController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/AdmissionsController.kt @@ -17,9 +17,9 @@ import org.springframework.web.bind.annotation.RestController class AdmissionsController( private val admissionsService: AdmissionsService ) { - @PostMapping("/undergraduate") + @PostMapping("/undergraduate/{postType}") fun createUndergraduateAdmissions( - @RequestParam postType: String, + @PathVariable postType: String, @Valid @RequestBody request: AdmissionsDto ) : AdmissionsDto { return admissionsService.createUndergraduateAdmissions(postType, request) @@ -32,9 +32,9 @@ class AdmissionsController( return admissionsService.createGraduateAdmissions(request) } - @GetMapping("/undergraduate") + @GetMapping("/undergraduate/{postType}") fun readUndergraduateAdmissions( - @RequestParam postType: String + @PathVariable postType: String ) : ResponseEntity { return ResponseEntity.ok(admissionsService.readUndergraduateAdmissions(postType)) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsEntity.kt index e12e9716..7d7640e8 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsEntity.kt @@ -10,14 +10,14 @@ import jakarta.persistence.Enumerated class AdmissionsEntity( @Enumerated(EnumType.STRING) val postType: AdmissionsPostType, - val title: String, + val pageName: String, val description: String, ): BaseTimeEntity() { companion object { - fun of(postType: AdmissionsPostType, admissionsDto: AdmissionsDto) : AdmissionsEntity { + fun of(postType: AdmissionsPostType, pageName: String, admissionsDto: AdmissionsDto) : AdmissionsEntity { return AdmissionsEntity( postType = postType, - title = admissionsDto.title, + pageName = pageName, description = admissionsDto.description, ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/dto/AdmissionsDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/dto/AdmissionsDto.kt index c49c7e7a..c5bb7dbe 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/dto/AdmissionsDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/dto/AdmissionsDto.kt @@ -5,7 +5,6 @@ import java.time.LocalDateTime data class AdmissionsDto( val id: Long, - val title: String, val description: String, val createdAt: LocalDateTime?, val modifiedAt: LocalDateTime?, @@ -14,7 +13,6 @@ data class AdmissionsDto( fun of(entity: AdmissionsEntity) : AdmissionsDto = entity.run { AdmissionsDto( id = this.id, - title = this.title, description = this.description, createdAt = this.createdAt, modifiedAt = this.modifiedAt, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/service/AdmissionsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/service/AdmissionsService.kt index e79a67cf..67a59ae8 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/service/AdmissionsService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/service/AdmissionsService.kt @@ -24,7 +24,13 @@ class AdmissionsServiceImpl( override fun createUndergraduateAdmissions(postType: String, request: AdmissionsDto): AdmissionsDto { val enumPostType = makeStringToAdmissionsPostType(postType) - val newAdmissions = AdmissionsEntity.of(enumPostType, request) + val pageName = when(enumPostType) { + AdmissionsPostType.UNDERGRADUATE_EARLY_ADMISSION -> "수시 모집" + AdmissionsPostType.UNDERGRADUATE_REGULAR_ADMISSION -> "정시 모집" + else -> throw CserealException.Csereal404("해당하는 페이지를 찾을 수 없습니다.") + } + + val newAdmissions = AdmissionsEntity.of(enumPostType, pageName, request) admissionsRepository.save(newAdmissions) @@ -33,7 +39,7 @@ class AdmissionsServiceImpl( @Transactional override fun createGraduateAdmissions(request: AdmissionsDto): AdmissionsDto { - val newAdmissions: AdmissionsEntity = AdmissionsEntity.of(AdmissionsPostType.GRADUATE, request) + val newAdmissions: AdmissionsEntity = AdmissionsEntity.of(AdmissionsPostType.GRADUATE, "전기/후기 모집", request) admissionsRepository.save(newAdmissions) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt index 06218223..bd175855 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt @@ -29,9 +29,7 @@ class SeminarEntity( var affiliationURL: String?, var startDate: String?, - var startTime: String?, var endDate: String?, - var endTime: String?, var location: String, @@ -65,9 +63,7 @@ class SeminarEntity( affiliation = seminarDto.affiliation, affiliationURL = seminarDto.affiliationURL, startDate = seminarDto.startDate, - startTime = seminarDto.startTime, endDate = seminarDto.endDate, - endTime = seminarDto.endTime, location = seminarDto.location, host = seminarDto.host, isPublic = seminarDto.isPublic, @@ -87,9 +83,7 @@ class SeminarEntity( affiliation = updateSeminarRequest.affiliation affiliationURL = updateSeminarRequest.affiliationURL startDate = updateSeminarRequest.startDate - startTime = updateSeminarRequest.startTime endDate = updateSeminarRequest.endDate - endTime = updateSeminarRequest.endTime location = updateSeminarRequest.location host = updateSeminarRequest.host isPublic = updateSeminarRequest.isPublic diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt index c6c2d9c9..3ca22770 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt @@ -16,9 +16,7 @@ data class SeminarDto( val affiliation: String, val affiliationURL: String?, val startDate: String?, - val startTime: String?, val endDate: String?, - val endTime: String?, val location: String, val host: String?, val additionalNote: String?, @@ -47,9 +45,7 @@ data class SeminarDto( affiliation = this.affiliation, affiliationURL = this.affiliationURL, startDate = this.startDate, - startTime = this.startTime, endDate = this.endDate, - endTime = this.endTime, location = this.location, host = this.host, additionalNote = this.additionalNote, From 6876bd61346ae61a36bcf8a6e888c44b83b4f42e Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Fri, 8 Sep 2023 23:14:24 +0900 Subject: [PATCH 056/124] =?UTF-8?q?fix:=20=EC=9D=B4=EC=A0=84=20=EA=B8=80?= =?UTF-8?q?=20=EB=8B=A4=EC=9D=8C=20=EA=B8=80=EB=A7=8C=20fetch=20=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=BF=BC=EB=A6=AC=20=EC=B5=9C=EC=A0=81?= =?UTF-8?q?=ED=99=94=20(#75)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 이전 글 다음 글만 fetch 하도록 쿼리 최적화 * fix: distinct 수정 * fix: 쿼리 최적화 * fix: total Long 타입으로 수정 --- .../core/notice/api/NoticeController.kt | 15 ++- .../core/notice/database/NoticeRepository.kt | 110 ++++++++---------- .../csereal/core/notice/dto/NoticeDto.kt | 11 +- .../core/notice/service/NoticeService.kt | 42 ++++--- 4 files changed, 87 insertions(+), 91 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt index 094c495f..4633d647 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt @@ -4,6 +4,7 @@ import com.wafflestudio.csereal.common.aop.AuthenticatedStaff import com.wafflestudio.csereal.core.notice.dto.* import com.wafflestudio.csereal.core.notice.service.NoticeService import jakarta.validation.Valid +import org.springframework.data.domain.PageRequest import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* @@ -18,18 +19,19 @@ class NoticeController( fun searchNotice( @RequestParam(required = false) tag: List?, @RequestParam(required = false) keyword: String?, - @RequestParam(required = false, defaultValue = "0") pageNum: Long + @RequestParam(required = false, defaultValue = "1") pageNum: Int, + @RequestParam(required = false, defaultValue = "false") usePageBtn: Boolean ): ResponseEntity { - return ResponseEntity.ok(noticeService.searchNotice(tag, keyword, pageNum)) + val pageSize = 20 + val pageRequest = PageRequest.of(pageNum - 1, pageSize) + return ResponseEntity.ok(noticeService.searchNotice(tag, keyword, pageRequest, usePageBtn)) } @GetMapping("/{noticeId}") fun readNotice( - @PathVariable noticeId: Long, - @RequestParam(required = false) tag: List?, - @RequestParam(required = false) keyword: String?, + @PathVariable noticeId: Long ): ResponseEntity { - return ResponseEntity.ok(noticeService.readNotice(noticeId, tag, keyword)) + return ResponseEntity.ok(noticeService.readNotice(noticeId)) } @AuthenticatedStaff @@ -63,6 +65,7 @@ class NoticeController( ) { noticeService.unpinManyNotices(request.idList) } + @DeleteMapping fun deleteManyNotices( @RequestBody request: NoticeIdListRequest diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt index 97821a0c..4a0cb84a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt @@ -7,28 +7,42 @@ import com.wafflestudio.csereal.core.notice.database.QNoticeEntity.noticeEntity import com.wafflestudio.csereal.core.notice.database.QNoticeTagEntity.noticeTagEntity import com.wafflestudio.csereal.core.notice.dto.NoticeSearchDto import com.wafflestudio.csereal.core.notice.dto.NoticeSearchResponse +import org.springframework.data.domain.PageRequest +import org.springframework.data.domain.Pageable import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Component +import java.time.LocalDateTime +import kotlin.math.ceil interface NoticeRepository : JpaRepository, CustomNoticeRepository { fun findAllByIsImportant(isImportant: Boolean): List + fun findFirstByCreatedAtLessThanOrderByCreatedAtDesc(timestamp: LocalDateTime): NoticeEntity? + fun findFirstByCreatedAtGreaterThanOrderByCreatedAtAsc(timestamp: LocalDateTime): NoticeEntity? } interface CustomNoticeRepository { - fun searchNotice(tag: List?, keyword: String?, pageNum: Long): NoticeSearchResponse - fun findPrevNextId(noticeId: Long, tag: List?, keyword: String?): Array? + fun searchNotice( + tag: List?, + keyword: String?, + pageable: Pageable, + usePageBtn: Boolean + ): NoticeSearchResponse } @Component class NoticeRepositoryImpl( private val queryFactory: JPAQueryFactory, ) : CustomNoticeRepository { - override fun searchNotice(tag: List?, keyword: String?, pageNum: Long): NoticeSearchResponse { + override fun searchNotice( + tag: List?, + keyword: String?, + pageable: Pageable, + usePageBtn: Boolean + ): NoticeSearchResponse { val keywordBooleanBuilder = BooleanBuilder() val tagsBooleanBuilder = BooleanBuilder() if (!keyword.isNullOrEmpty()) { - val keywordList = keyword.split("[^a-zA-Z0-9가-힣]".toRegex()) keywordList.forEach { if (it.length == 1) { @@ -39,8 +53,8 @@ class NoticeRepositoryImpl( .or(noticeEntity.description.contains(it)) ) } - } + } } if (!tag.isNullOrEmpty()) { tag.forEach { @@ -50,18 +64,27 @@ class NoticeRepositoryImpl( } } - val jpaQuery = queryFactory.select(noticeEntity).from(noticeEntity) + val jpaQuery = queryFactory.selectFrom(noticeEntity) .leftJoin(noticeTagEntity).on(noticeTagEntity.notice.eq(noticeEntity)) .where(noticeEntity.isDeleted.eq(false), noticeEntity.isPublic.eq(true)) - .where(keywordBooleanBuilder).where(tagsBooleanBuilder) + .where(keywordBooleanBuilder, tagsBooleanBuilder) - val countQuery = jpaQuery.clone() - val total = countQuery.select(noticeEntity.countDistinct()).fetchOne() + val total: Long + var pageRequest = pageable + + if (usePageBtn) { + val countQuery = jpaQuery.clone() + total = countQuery.select(noticeEntity.countDistinct()).fetchOne()!! + pageRequest = exchangePageRequest(pageable, total) + } else { + total = (10 * pageable.pageSize).toLong() // 10개 페이지 고정 + } - val noticeEntityList = jpaQuery.orderBy(noticeEntity.isPinned.desc()) + val noticeEntityList = jpaQuery + .orderBy(noticeEntity.isPinned.desc()) .orderBy(noticeEntity.createdAt.desc()) - .offset(20 * pageNum) - .limit(20) + .offset(pageRequest.offset) + .limit(pageRequest.pageSize.toLong()) .distinct() .fetch() @@ -77,62 +100,25 @@ class NoticeRepositoryImpl( ) } - return NoticeSearchResponse(total!!, noticeSearchDtoList) - } - - override fun findPrevNextId(noticeId: Long, tag: List?, keyword: String?): Array? { - val keywordBooleanBuilder = BooleanBuilder() - val tagsBooleanBuilder = BooleanBuilder() + return NoticeSearchResponse(total, noticeSearchDtoList) - if (!keyword.isNullOrEmpty()) { - val keywordList = keyword.split("[^a-zA-Z0-9가-힣]".toRegex()) - keywordList.forEach { - if (it.length == 1) { - throw CserealException.Csereal400("각각의 키워드는 한글자 이상이어야 합니다.") - } else { - keywordBooleanBuilder.and( - noticeEntity.title.contains(it) - .or(noticeEntity.description.contains(it)) - ) - } + } - } - } - if (!tag.isNullOrEmpty()) { - tag.forEach { - tagsBooleanBuilder.or( - noticeTagEntity.tag.name.eq(it) - ) - } - } + private fun exchangePageRequest(pageable: Pageable, total: Long): Pageable { + /** + * 요청한 페이지 번호가 기존 데이터 사이즈 초과할 경우 마지막 페이지 데이터 반환 + */ - val noticeSearchDtoList = queryFactory.select(noticeEntity).from(noticeEntity) - .leftJoin(noticeTagEntity).on(noticeTagEntity.notice.eq(noticeEntity)) - .where(noticeEntity.isDeleted.eq(false), noticeEntity.isPublic.eq(true)) - .where(keywordBooleanBuilder).where(tagsBooleanBuilder) - .orderBy(noticeEntity.isPinned.desc()) - .orderBy(noticeEntity.createdAt.desc()) - .distinct() - .fetch() + val pageNum = pageable.pageNumber + val pageSize = pageable.pageSize + val requestCount = (pageNum - 1) * pageSize - val findingId = noticeSearchDtoList.indexOfFirst { it.id == noticeId } - - val prevNext: Array? - if (findingId == -1) { - prevNext = arrayOf(null, null) - } else if (findingId != 0 && findingId != noticeSearchDtoList.size - 1) { - prevNext = arrayOf(noticeSearchDtoList[findingId + 1], noticeSearchDtoList[findingId - 1]) - } else if (findingId == 0) { - if (noticeSearchDtoList.size == 1) { - prevNext = arrayOf(null, null) - } else { - prevNext = arrayOf(noticeSearchDtoList[1], null) - } - } else { - prevNext = arrayOf(null, noticeSearchDtoList[noticeSearchDtoList.size - 2]) + if (total > requestCount) { + return pageable } - return prevNext + val requestPageNum = ceil(total.toDouble() / pageNum).toInt() + return PageRequest.of(requestPageNum, pageSize) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt index f9491548..528c4f0f 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt @@ -26,7 +26,8 @@ data class NoticeDto( fun of( entity: NoticeEntity, attachmentResponses: List, - prevNext: Array? + prevNotice: NoticeEntity? = null, + nextNotice: NoticeEntity? = null ): NoticeDto = entity.run { NoticeDto( id = this.id, @@ -39,10 +40,10 @@ data class NoticeDto( isPublic = this.isPublic, isPinned = this.isPinned, isImportant = this.isImportant, - prevId = prevNext?.get(0)?.id, - prevTitle = prevNext?.get(0)?.title, - nextId = prevNext?.get(1)?.id, - nextTitle = prevNext?.get(1)?.title, + prevId = prevNotice?.id, + prevTitle = prevNotice?.title, + nextId = nextNotice?.id, + nextTitle = nextNotice?.title, attachments = attachmentResponses, ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt index eb0a17db..93f028fa 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt @@ -7,6 +7,7 @@ import com.wafflestudio.csereal.core.notice.dto.* import com.wafflestudio.csereal.core.resource.attachment.service.AttachmentService import com.wafflestudio.csereal.core.user.database.UserEntity import com.wafflestudio.csereal.core.user.database.UserRepository +import org.springframework.data.domain.Pageable import org.springframework.data.repository.findByIdOrNull import org.springframework.security.core.context.SecurityContextHolder import org.springframework.security.oauth2.core.oidc.user.OidcUser @@ -17,8 +18,14 @@ import org.springframework.web.context.request.RequestContextHolder import org.springframework.web.multipart.MultipartFile interface NoticeService { - fun searchNotice(tag: List?, keyword: String?, pageNum: Long): NoticeSearchResponse - fun readNotice(noticeId: Long, tag: List?, keyword: String?): NoticeDto + fun searchNotice( + tag: List?, + keyword: String?, + pageable: Pageable, + usePageBtn: Boolean + ): NoticeSearchResponse + + fun readNotice(noticeId: Long): NoticeDto fun createNotice(request: NoticeDto, attachments: List?): NoticeDto fun updateNotice(noticeId: Long, request: NoticeDto, attachments: List?): NoticeDto fun deleteNotice(noticeId: Long) @@ -40,27 +47,25 @@ class NoticeServiceImpl( override fun searchNotice( tag: List?, keyword: String?, - pageNum: Long + pageable: Pageable, + usePageBtn: Boolean ): NoticeSearchResponse { - return noticeRepository.searchNotice(tag, keyword, pageNum) + return noticeRepository.searchNotice(tag, keyword, pageable, usePageBtn) } @Transactional(readOnly = true) - override fun readNotice( - noticeId: Long, - tag: List?, - keyword: String? - ): NoticeDto { - val notice: NoticeEntity = noticeRepository.findByIdOrNull(noticeId) + override fun readNotice(noticeId: Long): NoticeDto { + val notice = noticeRepository.findByIdOrNull(noticeId) ?: throw CserealException.Csereal404("존재하지 않는 공지사항입니다.(noticeId: $noticeId)") if (notice.isDeleted) throw CserealException.Csereal404("삭제된 공지사항입니다.(noticeId: $noticeId)") val attachmentResponses = attachmentService.createAttachmentResponses(notice.attachments) - val prevNext = noticeRepository.findPrevNextId(noticeId, tag, keyword) + val prevNotice = noticeRepository.findFirstByCreatedAtLessThanOrderByCreatedAtDesc(notice.createdAt!!) + val nextNotice = noticeRepository.findFirstByCreatedAtGreaterThanOrderByCreatedAtAsc(notice.createdAt!!) - return NoticeDto.of(notice, attachmentResponses, prevNext) + return NoticeDto.of(notice, attachmentResponses, prevNotice, nextNotice) } @Transactional @@ -92,7 +97,7 @@ class NoticeServiceImpl( NoticeTagEntity.createNoticeTag(newNotice, tag) } - if(attachments != null) { + if (attachments != null) { attachmentService.uploadAllAttachments(newNotice, attachments) } @@ -100,7 +105,7 @@ class NoticeServiceImpl( val attachmentResponses = attachmentService.createAttachmentResponses(newNotice.attachments) - return NoticeDto.of(newNotice, attachmentResponses, null) + return NoticeDto.of(newNotice, attachmentResponses) } @@ -112,7 +117,7 @@ class NoticeServiceImpl( notice.update(request) - if(attachments != null) { + if (attachments != null) { notice.attachments.clear() attachmentService.uploadAllAttachments(notice, attachments) } else { @@ -137,7 +142,7 @@ class NoticeServiceImpl( val attachmentResponses = attachmentService.createAttachmentResponses(notice.attachments) - return NoticeDto.of(notice, attachmentResponses, null) + return NoticeDto.of(notice, attachmentResponses) } @Transactional @@ -151,15 +156,16 @@ class NoticeServiceImpl( @Transactional override fun unpinManyNotices(idList: List) { - for(noticeId in idList) { + for (noticeId in idList) { val notice: NoticeEntity = noticeRepository.findByIdOrNull(noticeId) ?: throw CserealException.Csereal404("존재하지 않는 공지사항을 입력하였습니다.(noticeId: $noticeId)") notice.isPinned = false } } + @Transactional override fun deleteManyNotices(idList: List) { - for(noticeId in idList) { + for (noticeId in idList) { val notice: NoticeEntity = noticeRepository.findByIdOrNull(noticeId) ?: throw CserealException.Csereal404("존재하지 않는 공지사항을 입력하였습니다.(noticeId: $noticeId)") notice.isDeleted = true From b56ff8abb0d3dc706f4684aee8f359a78a7f6012 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=ED=98=81=EC=A4=80=20=28HyukJoon=20Woo=29?= Date: Fri, 8 Sep 2023 23:29:16 +0900 Subject: [PATCH 057/124] =?UTF-8?q?CI/CD:=20Test=20=EC=84=A4=EC=A0=95=20(#?= =?UTF-8?q?77)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Config: Add kotest, mockk, h2 db for testing. * Config: Add test profile. * Config: Add testconfig for jpaqueryfactory. * CICD: Add build, test workflow * Fix: Revert local datasource url change. --- .github/workflows/build_and_test.yaml | 36 ++++++++++++++++ build.gradle.kts | 17 +++++++- src/main/resources/application.yaml | 42 +++++++++++++++++++ .../csereal/global/config/TestConfig.kt | 16 +++++++ 4 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/build_and_test.yaml create mode 100644 src/test/kotlin/com/wafflestudio/csereal/global/config/TestConfig.kt diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml new file mode 100644 index 00000000..daef1551 --- /dev/null +++ b/.github/workflows/build_and_test.yaml @@ -0,0 +1,36 @@ +on: + pull_request: + branches: + - main + - develop + +jobs: + build_and_test: + runs-on: ubuntu-latest + + permissions: + contents: read + issues: read + checks: write + pull-requests: write + + steps: + - uses: 'actions/checkout@v3' + + - name: Setup Java JDK + uses: actions/setup-java@v3.12.0 + with: + java-version: '17' + distribution: 'adopt' + + - name: Build with Gradle + run: ./gradlew clean build -x test + + - name: Run Tests with Gradle + run: ./gradlew test + + - name: Publish Unit Test Results + uses: EnricoMi/publish-unit-test-result-action@v2.9.0 + if: always() + with: + files: build/test-results/**/*.xml diff --git a/build.gradle.kts b/build.gradle.kts index e2de60b1..21e16595 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -33,6 +33,20 @@ dependencies { testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.springframework.security:spring-security-test") + // kotest + testImplementation("io.kotest:kotest-runner-junit5-jvm:5.7.1") + testImplementation("io.kotest:kotest-assertions-core-jvm:5.7.1") + testImplementation("io.kotest:kotest-property-jvm:5.7.1") + implementation("io.kotest.extensions:kotest-extensions-spring:1.1.3") + + // mockk + testImplementation("io.mockk:mockk:1.13.7") + testImplementation("com.ninja-squad:springmockk:4.0.2") + + // h2 database + implementation("org.springframework.boot:spring-boot-starter-jdbc") + testImplementation("com.h2database:h2") + //queryDsl implementation("com.querydsl:querydsl-jpa:5.0.0:jakarta") kapt("com.querydsl:querydsl-apt:5.0.0:jakarta") @@ -79,4 +93,5 @@ tasks.withType { tasks.withType { useJUnitPlatform() -} + systemProperty("spring.profiles.active", "test") +} \ No newline at end of file diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index fe727fe6..d8a513cc 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -96,3 +96,45 @@ oldFiles: endpoint: backend: http://${URL}/api frontend: http://${URL} + +--- +spring: + config.activate.on-profile: test + datasource: + driver-class-name: org.h2.Driver + url: jdbc:h2:mem:testdb;MODE=MYSQL;DB_CLOSE_DELAY=-1; + username: sa + password: + jpa: + database: h2 + database-platform: org.hibernate.dialect.H2Dialect + show-sql: true + open-in-view: false + hibernate: + ddl-auto: create-drop + h2: + console: + enabled: true + security: + oauth2: + client: + registration: + idsnucse: + redirect-uri: http://localhost:8080/api/v1/login/oauth2/code/idsnucse + +logging.level: + default: INFO + org: + springframework: + security: DEBUG + +csereal: + upload: + path: ./files/ + +oldFiles: + path: ./cse-files + +endpoint: + backend: http://localhost:8080 + frontend: http://localhost:3000 \ No newline at end of file diff --git a/src/test/kotlin/com/wafflestudio/csereal/global/config/TestConfig.kt b/src/test/kotlin/com/wafflestudio/csereal/global/config/TestConfig.kt new file mode 100644 index 00000000..38606705 --- /dev/null +++ b/src/test/kotlin/com/wafflestudio/csereal/global/config/TestConfig.kt @@ -0,0 +1,16 @@ +package com.wafflestudio.csereal.global.config + +import com.querydsl.jpa.impl.JPAQueryFactory +import jakarta.persistence.EntityManager +import jakarta.persistence.PersistenceContext +import org.springframework.context.annotation.Bean +import org.springframework.boot.test.context.TestConfiguration + +@TestConfiguration +class TestConfig ( + @PersistenceContext + private val entityManager: EntityManager, +) { + @Bean + fun jpaQueryFactory() = JPAQueryFactory(entityManager) +} \ No newline at end of file From b882c3a0c0cfc9f491a3c423fe4194ed5ce5a6f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=ED=98=81=EC=A4=80=20=28HyukJoon=20Woo=29?= Date: Fri, 8 Sep 2023 23:37:13 +0900 Subject: [PATCH 058/124] =?UTF-8?q?[Test]=20Notice=20create,=20update=20?= =?UTF-8?q?=EC=8B=9C=20plainTextDescription=20=EB=8F=99=EC=9E=91=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20(#78)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Config: Add test profile. * Test: Add Tests for Create, Update Notice, in perspective of plainTextDescription * Fix: Change life cycle hook resolution to container. --- src/main/resources/application.yaml | 2 +- .../core/notice/service/NoticeServiceTest.kt | 136 ++++++++++++++++++ 2 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 src/test/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeServiceTest.kt diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index d8a513cc..f3a7aa87 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -40,7 +40,7 @@ servlet: spring: config.activate.on-profile: local datasource: - url: jdbc:mysql://127.0.0.1:3306/csereal?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Seoul + url: jdbc:mysql://127.0.0.1:3307/csereal?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Seoul username: root password: password jpa: diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeServiceTest.kt new file mode 100644 index 00000000..fd72d6a2 --- /dev/null +++ b/src/test/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeServiceTest.kt @@ -0,0 +1,136 @@ +package com.wafflestudio.csereal.core.notice.service + +import com.wafflestudio.csereal.core.notice.database.NoticeEntity +import com.wafflestudio.csereal.core.notice.database.NoticeRepository +import com.wafflestudio.csereal.core.notice.dto.NoticeDto +import com.wafflestudio.csereal.core.user.database.Role +import com.wafflestudio.csereal.core.user.database.UserEntity +import com.wafflestudio.csereal.core.user.database.UserRepository +import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.data.repository.findByIdOrNull +import org.springframework.web.context.request.RequestAttributes +import org.springframework.web.context.request.RequestContextHolder + +@SpringBootTest +class NoticeServiceTest( + private val noticeService: NoticeService, + private val userRepository: UserRepository, + private val noticeRepository: NoticeRepository, +) : BehaviorSpec() { + init { + beforeContainer { + userRepository.save( + UserEntity( + "username", + "name", + "email", + "studentId", + Role.ROLE_STAFF + ) + ) + } + + afterContainer { + noticeRepository.deleteAll() + userRepository.deleteAll() + } + + Given("간단한 공지사항을 생성하려고 할 때") { + val userEntity = userRepository.findByUsername("username")!! + + mockkStatic(RequestContextHolder::class) + val mockRequestAttributes = mockk() + every { + RequestContextHolder.getRequestAttributes() + } returns mockRequestAttributes + every { + mockRequestAttributes.getAttribute( + "loggedInUser", + RequestAttributes.SCOPE_REQUEST + ) + } returns userEntity + + val noticeDto = NoticeDto( + id = -1, + title = "title", + description = """ +

Hello, World!

+

This is a test notice.

+

Goodbye, World!

+ """.trimIndent(), + author = "username", + tags = emptyList(), + createdAt = null, + modifiedAt = null, + isPublic = false, + isPinned = false, + isImportant = false, + prevId = null, + prevTitle = null, + nextId = null, + nextTitle = null, + attachments = null, + ) + + When("공지사항을 생성하면") { + val createdNoticeDto = noticeService.createNotice(noticeDto, null) + + Then("새 공지사항이 잘 생성되어야 한다.") { + noticeRepository.count() shouldBe 1 + noticeRepository.findByIdOrNull(createdNoticeDto.id) shouldNotBe null + } + Then("plainTextDescription이 잘 생성되어야 한다.") { + val noticeEntity = noticeRepository.findByIdOrNull(createdNoticeDto.id) + noticeEntity?.plainTextDescription shouldBe "Hello, World! This is a test notice. Goodbye, World!" + } + } + } + + Given("기존 간단한 공지사항의 Description을 수정하려고 할 때") { + val noticeEntity = noticeRepository.save ( + NoticeEntity( + title = "title", + description = """ +

Hello, World!

+

This is a test notice.

+

Goodbye, World!

+ """.trimIndent(), + plainTextDescription = "Hello, World! This is a test notice. Goodbye, World!", + isPublic = false, + isPinned = false, + isImportant = false, + author = userRepository.findByUsername("username")!!, + ) + ) + val modifiedRequest = NoticeDto.of( + noticeEntity, emptyList(), null + ).copy( + description = """ +

Hello, World!

+

This is a modified test notice.

+

Goodbye, World!

+

And this is a new line.

+ """.trimIndent() + ) + + When("수정된 DTO를 이용하여 수정하면") { + val modifiedNoticeDto = noticeService.updateNotice( + modifiedRequest.id, + modifiedRequest, + null + ) + + Then("plainTextDescription이 잘 수정되어야 한다.") { + val noticeEntity = noticeRepository.findByIdOrNull(modifiedNoticeDto.id) + noticeEntity?.plainTextDescription shouldBe "Hello, World! This is a modified test notice. Goodbye, World! And this is a new line." + } + } + } + } +} \ No newline at end of file From 8d5627cbd471350d5f28e2162f2ccd6d6de733d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=ED=98=81=EC=A4=80=20=28HyukJoon=20Woo=29?= Date: Sat, 9 Sep 2023 00:11:58 +0900 Subject: [PATCH 059/124] =?UTF-8?q?[Feat]=20Seminar=20plain=20text=20field?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20(#79)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat: Add plain text column for description, introduction, additionalnote. * Feat: Add description, introduction, additionalnote for dto -> entity. * Feat: Change to update plain description, introduction, additional note. * Feat: Use plainTextDesription for searchdto. * Test: Test for create, update of plain texts. * Test: Fix seminar service test. --- .../core/seminar/database/SeminarEntity.kt | 34 +++- .../seminar/database/SeminarRepository.kt | 2 +- .../seminar/service/SeminarServiceTest.kt | 168 ++++++++++++++++++ 3 files changed, 201 insertions(+), 3 deletions(-) create mode 100644 src/test/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarServiceTest.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt index bd175855..03a1ac6d 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt @@ -1,5 +1,6 @@ package com.wafflestudio.csereal.core.seminar.database +import com.wafflestudio.csereal.common.cleanTextFromHtml import com.wafflestudio.csereal.common.config.BaseTimeEntity import com.wafflestudio.csereal.common.controller.AttachmentContentEntityType import com.wafflestudio.csereal.common.controller.MainImageContentEntityType @@ -18,9 +19,15 @@ class SeminarEntity( @Column(columnDefinition = "mediumtext") var description: String, + @Column(columnDefinition = "mediumtext") + var plainTextDescription: String, + @Column(columnDefinition = "mediumtext") var introduction: String, + @Column(columnDefinition = "mediumtext") + var plainTextIntroduction: String, + // 연사 정보 var name: String, var speakerURL: String?, @@ -41,6 +48,9 @@ class SeminarEntity( @Column(columnDefinition = "text") var additionalNote: String?, + @Column(columnDefinition = "text") + var plainTextAdditionalNote: String?, + @OneToOne var mainImage: MainImageEntity? = null, @@ -53,10 +63,16 @@ class SeminarEntity( companion object { fun of(seminarDto: SeminarDto): SeminarEntity { + val plainTextDescription = cleanTextFromHtml(seminarDto.description) + val palinTextIntroduction = cleanTextFromHtml(seminarDto.introduction) + val plainTextAdditionalNote = seminarDto.additionalNote?.let { cleanTextFromHtml(it) } + return SeminarEntity( title = seminarDto.title, description = seminarDto.description, + plainTextDescription = plainTextDescription, introduction = seminarDto.introduction, + plainTextIntroduction = palinTextIntroduction, name = seminarDto.name, speakerURL = seminarDto.speakerURL, speakerTitle = seminarDto.speakerTitle, @@ -69,13 +85,28 @@ class SeminarEntity( isPublic = seminarDto.isPublic, isImportant = seminarDto.isImportant, additionalNote = seminarDto.additionalNote, + plainTextAdditionalNote = plainTextAdditionalNote, ) } } fun update(updateSeminarRequest: SeminarDto) { + if (updateSeminarRequest.description != description) { + description = updateSeminarRequest.description + plainTextDescription = cleanTextFromHtml(updateSeminarRequest.description) + } + + if (updateSeminarRequest.introduction != introduction) { + introduction = updateSeminarRequest.introduction + plainTextIntroduction = cleanTextFromHtml(updateSeminarRequest.introduction) + } + + if (updateSeminarRequest.additionalNote != additionalNote) { + additionalNote = updateSeminarRequest.additionalNote + plainTextAdditionalNote = updateSeminarRequest.additionalNote?.let { cleanTextFromHtml(it) } + } + title = updateSeminarRequest.title - description = updateSeminarRequest.description introduction = updateSeminarRequest.introduction name = updateSeminarRequest.name speakerURL = updateSeminarRequest.speakerURL @@ -88,6 +119,5 @@ class SeminarEntity( host = updateSeminarRequest.host isPublic = updateSeminarRequest.isPublic isImportant = updateSeminarRequest.isImportant - additionalNote = updateSeminarRequest.additionalNote } } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt index c2f8f0e4..ad3b1b66 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt @@ -76,7 +76,7 @@ class SeminarRepositoryImpl( SeminarSearchDto( id = seminarEntityList[i].id, title = seminarEntityList[i].title, - description = cleanTextFromHtml(seminarEntityList[i].description), + description = seminarEntityList[i].plainTextDescription, name = seminarEntityList[i].name, affiliation = seminarEntityList[i].affiliation, startDate = seminarEntityList[i].startDate, diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarServiceTest.kt new file mode 100644 index 00000000..03669627 --- /dev/null +++ b/src/test/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarServiceTest.kt @@ -0,0 +1,168 @@ +package com.wafflestudio.csereal.core.seminar.service + +import com.wafflestudio.csereal.core.seminar.database.SeminarEntity +import com.wafflestudio.csereal.core.seminar.database.SeminarRepository +import com.wafflestudio.csereal.core.seminar.dto.SeminarDto +import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import jakarta.transaction.Transactional +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.data.repository.findByIdOrNull + +@SpringBootTest +@Transactional +class SeminarServiceTest ( + private val seminarService: SeminarService, + private val seminarRepository: SeminarRepository, +): BehaviorSpec() { + init { + + beforeContainer { + } + + afterContainer { + seminarRepository.deleteAll() + } + + Given("세미나를 생성하려고 할 때") { + val seminarDTO = SeminarDto( + id = -1, + title = "title", + description = """ +

Hello, World!

+

This is seminar description.

+

Goodbye, World!

+ """.trimIndent(), + introduction = """ +

Hello, World!

+

This is seminar introduction.

+

Goodbye, World!

+ """.trimIndent(), + name = "name", + speakerURL = "speakerURL", + speakerTitle = "speakerTitle", + affiliation = "affiliation", + affiliationURL = "affiliationURL", + startDate = "startDate", + endDate = "endDate", + location = "location", + host = "host", + additionalNote = """ +

Hello, World!

+

This is seminar additionalNote.

+

Goodbye, World!

+ """.trimIndent(), + createdAt = null, + modifiedAt = null, + isPublic = false, + isImportant = false, + prevId = null, + prevTitle = null, + nextId = null, + nextTitle = null, + imageURL = null, + attachments = null + ) + When("간단한 세미나 DTO가 주어지면") { + val resultSeminarDTO = seminarService.createSeminar(seminarDTO, null, null) + + Then("세미나가 생성된다") { + seminarRepository.count() shouldBe 1 + seminarRepository.findByIdOrNull(resultSeminarDTO.id) shouldNotBe null + } + + Then("plain text 값들이 잘 생성되어야 한다.") { + val seminarEntity = seminarRepository.findByIdOrNull(resultSeminarDTO.id)!! + seminarEntity.plainTextDescription shouldBe "Hello, World! This is seminar description. Goodbye, World!" + seminarEntity.plainTextIntroduction shouldBe "Hello, World! This is seminar introduction. Goodbye, World!" + seminarEntity.plainTextAdditionalNote shouldBe "Hello, World! This is seminar additionalNote. Goodbye, World!" + } + } + } + + Given("기존 간단한 세미나의 Description을 수정하려고 할 때") { + val originalSeminar = seminarRepository.save( + SeminarEntity( + title = "title", + description = """ +

Hello, World!

+

This is seminar description.

+

Goodbye, World!

+ """.trimIndent(), + plainTextDescription = "Hello, World! This is seminar description. Goodbye, World!", + introduction = """ +

Hello, World!

+

This is seminar introduction.

+

Goodbye, World!

+ """.trimIndent(), + plainTextIntroduction = "Hello, World! This is seminar introduction. Goodbye, World!", + name = "name", + speakerURL = "speakerURL", + speakerTitle = "speakerTitle", + affiliation = "affiliation", + affiliationURL = "affiliationURL", + startDate = "startDate", + endDate = "endDate", + location = "location", + host = "host", + additionalNote = """ +

Hello, World!

+

This is seminar additionalNote.

+

Goodbye, World!

+ """.trimIndent(), + plainTextAdditionalNote = "Hello, World! This is seminar additionalNote. Goodbye, World!", + isPublic = false, + isImportant = false, + ) + ) + val originalId = originalSeminar.id + + When("수정된 DTO를 이용하여 수정하면") { + val modifiedSeminarDTO = SeminarDto.of( + originalSeminar, null, emptyList(), null + ).copy( + description = """ +

Hello, World!

+

This is modified seminar description.

+

Goodbye, World!

+

And this is a new line.

+ """.trimIndent(), + introduction = """ +

Hello, World!

+

This is modified seminar introduction.

+

Goodbye, World!

+

And this is a new line.

+ """.trimIndent(), + additionalNote = """ +

Hello, World!

+

This is modified seminar additionalNote.

+

Goodbye, World!

+

And this is a new line.

+ """.trimIndent(), + ) + + val modifiedSeminarDto = seminarService.updateSeminar( + originalSeminar.id, + modifiedSeminarDTO, + null, + null, + emptyList() + ) + + Then("같은 Entity가 수정되어야 한다.") { + seminarRepository.count() shouldBe 1 + val modifiedSeminarEntity = seminarRepository.findByIdOrNull(modifiedSeminarDto.id)!! + modifiedSeminarEntity.id shouldBe originalId + } + + Then("plain text 값들이 잘 수정되어야 한다.") { + val modifiedSeminarEntity = seminarRepository.findByIdOrNull(modifiedSeminarDto.id)!! + modifiedSeminarEntity.plainTextDescription shouldBe "Hello, World! This is modified seminar description. Goodbye, World! And this is a new line." + modifiedSeminarEntity.plainTextIntroduction shouldBe "Hello, World! This is modified seminar introduction. Goodbye, World! And this is a new line." + modifiedSeminarEntity.plainTextAdditionalNote shouldBe "Hello, World! This is modified seminar additionalNote. Goodbye, World! And this is a new line." + } + } + } + } +} \ No newline at end of file From d2c0366ca79da46f38a97d85d037ae50bd703fe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=ED=98=81=EC=A4=80=20=28HyukJoon=20Woo=29?= Date: Sat, 9 Sep 2023 00:26:03 +0900 Subject: [PATCH 060/124] =?UTF-8?q?Feat:=20News=20plain=20text=20field=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat: Add plainTextDescription field for news entity * Feat: Add to create, and update plainTextDescription. * Feat: Change to use plainTextDescription when making newssearchdto. * Test: Test for create/update plainTextDescription. --- .../csereal/core/news/database/NewsEntity.kt | 11 +- .../core/news/database/NewsRepository.kt | 2 +- .../core/notice/news/NewsServiceTest.kt | 106 ++++++++++++++++++ 3 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 src/test/kotlin/com/wafflestudio/csereal/core/notice/news/NewsServiceTest.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt index accb21f5..1bba922e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt @@ -1,5 +1,6 @@ package com.wafflestudio.csereal.core.news.database +import com.wafflestudio.csereal.common.cleanTextFromHtml import com.wafflestudio.csereal.common.config.BaseTimeEntity import com.wafflestudio.csereal.common.controller.AttachmentContentEntityType import com.wafflestudio.csereal.common.controller.MainImageContentEntityType @@ -18,6 +19,9 @@ class NewsEntity( @Column(columnDefinition = "mediumtext") var description: String, + @Column(columnDefinition = "mediumtext") + var plainTextDescription: String, + var isPublic: Boolean, var isSlide: Boolean, @@ -42,6 +46,7 @@ class NewsEntity( return NewsEntity( title = newsDto.title, description = newsDto.description, + plainTextDescription = cleanTextFromHtml(newsDto.description), isPublic = newsDto.isPublic, isSlide = newsDto.isSlide, isImportant = newsDto.isImportant, @@ -49,8 +54,12 @@ class NewsEntity( } } fun update(updateNewsRequest: NewsDto) { + if (updateNewsRequest.description != this.description) { + this.description = updateNewsRequest.description + this.plainTextDescription = cleanTextFromHtml(updateNewsRequest.description) + } + this.title = updateNewsRequest.title - this.description = updateNewsRequest.description this.isPublic = updateNewsRequest.isPublic this.isSlide = updateNewsRequest.isSlide this.isImportant = updateNewsRequest.isImportant diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt index bb1d5740..9158f495 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt @@ -71,7 +71,7 @@ class NewsRepositoryImpl( NewsSearchDto( id = it.id, title = it.title, - description = cleanTextFromHtml(it.description), + description = it.plainTextDescription, createdAt = it.createdAt, tags = it.newsTags.map { newsTagEntity -> newsTagEntity.tag.name }, imageURL = imageURL diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/notice/news/NewsServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/notice/news/NewsServiceTest.kt new file mode 100644 index 00000000..7da09dc7 --- /dev/null +++ b/src/test/kotlin/com/wafflestudio/csereal/core/notice/news/NewsServiceTest.kt @@ -0,0 +1,106 @@ +package com.wafflestudio.csereal.core.notice.news + +import com.wafflestudio.csereal.core.news.database.NewsEntity +import com.wafflestudio.csereal.core.news.database.NewsRepository +import com.wafflestudio.csereal.core.news.dto.NewsDto +import com.wafflestudio.csereal.core.news.service.NewsService +import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.data.repository.findByIdOrNull + +@SpringBootTest +class NewsServiceTest( + private val newsService: NewsService, + private val newsRepository: NewsRepository, +) : BehaviorSpec() { + init { + + afterSpec { + newsRepository.deleteAll() + } + + Given("뉴스를 생성하려고 할 때 간단한 뉴스가 주어지면") { + val newsDTO = NewsDto( + id = -1, + title = "title", + description = """ +

Hello, World!

+

This is news description.

+

Goodbye, World!

+ """.trimIndent(), + tags = emptyList(), + createdAt = null, + modifiedAt = null, + isPublic = false, + isSlide = false, + isImportant = false, + prevId = null, + prevTitle = null, + nextId = null, + nextTitle = null, + imageURL = null, + attachments = null, + ) + + When("DTO를 이용하여 뉴스를 생성하면") { + val createdNewsDTO = newsService.createNews(newsDTO, null, null) + + Then("뉴스가 생성되어야 한다.") { + newsRepository.count() shouldBe 1 + newsRepository.findByIdOrNull(createdNewsDTO.id) shouldNotBe null + } + + Then("plainTextDescription이 생성되었어야 한다.") { + val createdNewsEntity = newsRepository.findByIdOrNull(createdNewsDTO.id)!! + createdNewsEntity.plainTextDescription shouldBe "Hello, World! This is news description. Goodbye, World!" + } + } + } + + Given("간단한 뉴스가 저장되어 있을 때") { + val newsEntity = newsRepository.save( + NewsEntity( + title = "title", + description = """ +

Hello, World!

+

This is news description.

+

Goodbye, World!

+ """.trimIndent(), + plainTextDescription = "Hello, World! This is news description. Goodbye, World!", + isPublic = false, + isSlide = false, + isImportant = false, + ) + ) + + When("저장된 뉴스의 Description을 수정하면") { + newsService.updateNews( + newsEntity.id, + NewsDto.of(newsEntity, null, emptyList(), null) + .copy(description = """ +

Hello, World!

+

This is modified news description.

+

Goodbye, World!

+

This is additional description.

+ """.trimIndent() + ), + null, + null + ) + + Then("description, plainTextDescription이 수정되어야 한다.") { + val updatedNewsEntity = newsRepository.findByIdOrNull(newsEntity.id)!! + updatedNewsEntity.description shouldBe """ +

Hello, World!

+

This is modified news description.

+

Goodbye, World!

+

This is additional description.

+ """.trimIndent() + updatedNewsEntity.plainTextDescription shouldBe "Hello, World! This is modified news description. Goodbye, World! This is additional description." + } + } + } + } +} \ No newline at end of file From a409fa8be47775d09dc4ce9892bf998dba715fe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=ED=98=81=EC=A4=80=20=28HyukJoon=20Woo=29?= Date: Sat, 9 Sep 2023 00:35:59 +0900 Subject: [PATCH 061/124] =?UTF-8?q?Feat:=20=EA=B5=AC=EC=84=B1=EC=9B=90=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=20=EC=9C=84=ED=95=9C=20table=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80.=20(#81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat: MemberSearchEntity 추가. * Feat: MemberSearchRepository 추가 * Feat: MemberSearchService 추가 * Feat: ProfessorEntity, StaffEntity에 MemberSearch 추가. * Feat: ProfessorStatus에 한글 값 추가. * Feat: professor create, update 시 memberSearch도 반영하도록 추가. * Feat: seminar create, update 시 memberSearch도 반영하도록 추가. * Test: ProfessorService 테스트 추가. * Test: StaffServiceTest 추가. --- .../member/database/MemberSearchEntity.kt | 101 +++++++ .../member/database/MemberSearchRepository.kt | 19 ++ .../core/member/database/ProfessorEntity.kt | 10 +- .../core/member/database/StaffEntity.kt | 4 +- .../member/service/MemberSearchService.kt | 16 ++ .../core/member/service/ProfessorService.kt | 5 + .../core/member/service/StaffService.kt | 6 + .../member/service/ProfessorServiceTest.kt | 264 ++++++++++++++++++ .../core/member/service/StaffServiceTest.kt | 131 +++++++++ 9 files changed, 553 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/database/MemberSearchEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/database/MemberSearchRepository.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/service/MemberSearchService.kt create mode 100644 src/test/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorServiceTest.kt create mode 100644 src/test/kotlin/com/wafflestudio/csereal/core/member/service/StaffServiceTest.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/MemberSearchEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/MemberSearchEntity.kt new file mode 100644 index 00000000..4b669528 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/MemberSearchEntity.kt @@ -0,0 +1,101 @@ +package com.wafflestudio.csereal.core.member.database + +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import jakarta.persistence.* + +@Entity(name = "member_search") +class MemberSearchEntity ( + @Column(columnDefinition = "TEXT") + var content: String, + + @OneToOne + @JoinColumn(name = "professor_id") + val professor: ProfessorEntity? = null, + + @OneToOne + @JoinColumn(name = "staff_id") + val staff: StaffEntity? = null, +): BaseTimeEntity() { + companion object { + fun create(professor: ProfessorEntity): MemberSearchEntity { + return MemberSearchEntity( + content = createContent(professor), + professor = professor + ) + } + + fun create(staff: StaffEntity): MemberSearchEntity { + return MemberSearchEntity( + content = createContent(staff), + staff = staff + ) + } + + fun createContent(professor: ProfessorEntity): String { + val stringBuilder = StringBuilder() + stringBuilder.appendLine(professor.name) + stringBuilder.appendLine(professor.status.krValue) + stringBuilder.appendLine(professor.academicRank) + professor.lab?.let { stringBuilder.appendLine(it.name) } + professor.startDate?.let { stringBuilder.appendLine(it) } + professor.endDate?.let { stringBuilder.appendLine(it) } + professor.office?.let { stringBuilder.appendLine(it) } + professor.phone?.let { stringBuilder.appendLine(it) } + professor.fax?.let { stringBuilder.appendLine(it) } + professor.email?.let { stringBuilder.appendLine(it) } + professor.website?.let { stringBuilder.appendLine(it) } + professor.educations.forEach { stringBuilder.appendLine(it.name) } + professor.researchAreas.forEach { stringBuilder.appendLine(it.name) } + professor.careers.forEach { stringBuilder.appendLine(it.name) } + + return stringBuilder.toString() + } + + fun createContent(staff: StaffEntity): String { + val stringBuilder = StringBuilder() + stringBuilder.appendLine(staff.name) + stringBuilder.appendLine(staff.role) + stringBuilder.appendLine(staff.office) + stringBuilder.appendLine(staff.phone) + stringBuilder.appendLine(staff.email) + staff.tasks.forEach { stringBuilder.appendLine(it.name) } + + return stringBuilder.toString() + } + } + + @PrePersist + @PreUpdate + fun checkType() { + if ( + (professor != null && staff != null) + || (professor == null && staff == null) + ) { + throw RuntimeException("MemberSearchEntity must have either professor or staff") + } + } + + fun ofType(): MemberSearchType { + return if (professor != null) { + MemberSearchType.PROFESSOR + } else if (staff != null) { + MemberSearchType.STAFF + } else { + throw RuntimeException("MemberSearchEntity must have either professor or staff") + } + } + + fun update(professor: ProfessorEntity) { + this.content = createContent(professor) + } + + fun update(staff: StaffEntity) { + this.content = createContent(staff) + } +} + +enum class MemberSearchType { + PROFESSOR, + STAFF +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/MemberSearchRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/MemberSearchRepository.kt new file mode 100644 index 00000000..a6089892 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/MemberSearchRepository.kt @@ -0,0 +1,19 @@ +package com.wafflestudio.csereal.core.member.database + +import com.querydsl.jpa.impl.JPAQueryFactory +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +interface MemberSearchRepository + : JpaRepository, MemberSearchRepositoryCustom { +} + +interface MemberSearchRepositoryCustom { + +} + +@Repository +class MemberSearchRepositoryCustomImpl ( + private val queryFactory: JPAQueryFactory, +): MemberSearchRepositoryCustom { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt index 4036a9dd..377d7b3a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt @@ -44,6 +44,8 @@ class ProfessorEntity( @OneToOne var mainImage: MainImageEntity? = null, + @OneToOne(mappedBy = "professor", cascade = [CascadeType.ALL], orphanRemoval = true) + var memberSearch: MemberSearchEntity? = null, ) : BaseTimeEntity(), MainImageContentEntityType { override fun bringMainImage(): MainImageEntity? = mainImage @@ -85,6 +87,10 @@ class ProfessorEntity( } -enum class ProfessorStatus { - ACTIVE, INACTIVE, VISITING +enum class ProfessorStatus ( + val krValue: String +){ + ACTIVE("교수"), + INACTIVE("역대 교수"), + VISITING("객원교수"); } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffEntity.kt index 355d4125..a41d1951 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffEntity.kt @@ -24,7 +24,9 @@ class StaffEntity( @OneToOne var mainImage: MainImageEntity? = null, - ) : BaseTimeEntity(), MainImageContentEntityType { + @OneToOne(mappedBy = "staff", cascade = [CascadeType.ALL], orphanRemoval = true) + var memberSearch: MemberSearchEntity? = null, +) : BaseTimeEntity(), MainImageContentEntityType { override fun bringMainImage(): MainImageEntity? = mainImage companion object { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/MemberSearchService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/MemberSearchService.kt new file mode 100644 index 00000000..02b09546 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/MemberSearchService.kt @@ -0,0 +1,16 @@ +package com.wafflestudio.csereal.core.member.service + +import com.wafflestudio.csereal.core.member.database.MemberSearchRepository +import com.wafflestudio.csereal.core.member.database.ProfessorEntity +import jakarta.transaction.Transactional +import org.springframework.stereotype.Service + +interface MemberSearchService { + +} + +@Service +class MemberSearchServiceImpl ( + private val memberSearchRepository: MemberSearchRepository, +): MemberSearchService { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt index 4e0e9618..d0f19e2c 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt @@ -52,6 +52,8 @@ class ProfessorServiceImpl( mainImageService.uploadMainImage(professor, mainImage) } + professor.memberSearch = MemberSearchEntity.create(professor) + professorRepository.save(professor) val imageURL = mainImageService.createImageURL(professor.mainImage) @@ -148,6 +150,9 @@ class ProfessorServiceImpl( CareerEntity.create(career, professor) } + // 검색 엔티티 업데이트 + professor.memberSearch!!.update(professor) + val imageURL = mainImageService.createImageURL(professor.mainImage) return ProfessorDto.of(professor, imageURL) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt index 90a2778b..7514c5e3 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt @@ -1,6 +1,7 @@ package com.wafflestudio.csereal.core.member.service import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.member.database.MemberSearchEntity import com.wafflestudio.csereal.core.member.database.StaffEntity import com.wafflestudio.csereal.core.member.database.StaffRepository import com.wafflestudio.csereal.core.member.database.TaskEntity @@ -37,6 +38,8 @@ class StaffServiceImpl( mainImageService.uploadMainImage(staff, mainImage) } + staff.memberSearch = MemberSearchEntity.create(staff) + staffRepository.save(staff) val imageURL = mainImageService.createImageURL(staff.mainImage) @@ -86,6 +89,9 @@ class StaffServiceImpl( TaskEntity.create(task, staff) } + // 검색 엔티티 업데이트 + staff.memberSearch?.update(staff) + val imageURL = mainImageService.createImageURL(staff.mainImage) return StaffDto.of(staff, imageURL) diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorServiceTest.kt new file mode 100644 index 00000000..8d71962a --- /dev/null +++ b/src/test/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorServiceTest.kt @@ -0,0 +1,264 @@ +package com.wafflestudio.csereal.core.member.service + +import com.wafflestudio.csereal.core.member.database.MemberSearchRepository +import com.wafflestudio.csereal.core.member.database.ProfessorRepository +import com.wafflestudio.csereal.core.member.database.ProfessorStatus +import com.wafflestudio.csereal.core.member.dto.ProfessorDto +import com.wafflestudio.csereal.core.research.database.* +import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.extensions.spring.SpringTestExtension +import io.kotest.extensions.spring.SpringTestLifecycleMode +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import jakarta.transaction.Transactional +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.data.repository.findByIdOrNull +import java.time.LocalDate + +@SpringBootTest +@Transactional +class ProfessorServiceTest ( + private val professorService: ProfessorService, + private val professorRepository: ProfessorRepository, + private val labRepository: LabRepository, + private val memberSearchRepository: MemberSearchRepository, + private val researchRepository: ResearchRepository, +): BehaviorSpec({ + extensions(SpringTestExtension(SpringTestLifecycleMode.Root)) + + afterContainer { + professorRepository.deleteAll() + researchRepository.deleteAll() + } + + Given("이미지 없는 교수를 생성하려고 할 때") { + val date = LocalDate.now() + + val researchEntity = ResearchEntity( + name = "researchName", + description = null, + postType = ResearchPostType.LABS, + ) + var labEntity = LabEntity( + name = "labName", + location = null, + tel = null, + acronym = null, + youtube = null, + description = null, + websiteURL = null, + research = researchEntity, + ) + researchEntity.labs.add(labEntity) + researchRepository.save(researchEntity) + labEntity = labRepository.save(labEntity) + + val professorDto = ProfessorDto( + name = "name", + email = "email", + status = ProfessorStatus.ACTIVE, + academicRank = "academicRank", + labId = labEntity.id, + labName = null, + startDate = date, + endDate = date, + office = "office", + phone = "phone", + fax = "fax", + website = "website", + educations = listOf("education1", "education2"), + researchAreas = listOf("researchArea1", "researchArea2"), + careers = listOf("career1", "career2") + ) + + When("교수를 생성한다면") { + val createdProfessorDto = professorService.createProfessor(professorDto, null) + + Then("교수가 생성되어야 한다") { + professorRepository.count() shouldBe 1 + professorRepository.findByIdOrNull(createdProfessorDto.id) shouldNotBe null + } + + + Then("교수의 정보가 일치해야 한다") { + val professorEntity = professorRepository.findByIdOrNull(createdProfessorDto.id)!! + + professorEntity.name shouldBe professorDto.name + professorEntity.email shouldBe professorDto.email + professorEntity.status shouldBe professorDto.status + professorEntity.academicRank shouldBe professorDto.academicRank + professorEntity.lab shouldBe labEntity + professorEntity.startDate shouldBe professorDto.startDate + professorEntity.endDate shouldBe professorDto.endDate + professorEntity.office shouldBe professorDto.office + professorEntity.phone shouldBe professorDto.phone + professorEntity.fax shouldBe professorDto.fax + professorEntity.website shouldBe professorDto.website + professorEntity.educations.map { it.name } shouldBe professorDto.educations + professorEntity.researchAreas.map { it.name } shouldBe professorDto.researchAreas + professorEntity.careers.map { it.name } shouldBe professorDto.careers + } + + + Then("교수의 검색 정보가 생성되어야 한다") { + memberSearchRepository.count() shouldBe 1 + val memberSearchEntity = memberSearchRepository.findAll()[0] + + memberSearchEntity.professor?.id shouldBe createdProfessorDto.id + + val contentExpected = """ + name + 교수 + academicRank + labName + ${date} + ${date} + office + phone + fax + email + website + education1 + education2 + researchArea1 + researchArea2 + career1 + career2 + + """.trimIndent() + + memberSearchEntity.content shouldBe contentExpected + } + } + } + + Given("생성되어 있는 간단한 교수에 대하여") { + val date = LocalDate.now() + val researchEntity = ResearchEntity( + name = "researchName", + description = null, + postType = ResearchPostType.LABS, + ) + val labEntity1 = LabEntity( + name = "labName1", + location = null, + tel = null, + acronym = null, + youtube = null, + description = null, + websiteURL = null, + research = researchEntity, + ) + val labEntity2 = LabEntity( + name = "labName2", + location = null, + tel = null, + acronym = null, + youtube = null, + description = null, + websiteURL = null, + research = researchEntity, + ) + researchEntity.labs.addAll(listOf(labEntity1, labEntity2)) + researchRepository.save(researchEntity) + + val createdProfessorDto = professorService.createProfessor( + ProfessorDto( + name = "name", + email = "email", + status = ProfessorStatus.ACTIVE, + academicRank = "academicRank", + labId = labEntity1.id, + labName = null, + startDate = date, + endDate = date, + office = "office", + phone = "phone", + fax = "fax", + website = "website", + educations = listOf("education1", "education2"), + researchAreas = listOf("researchArea1", "researchArea2"), + careers = listOf("career1", "career2") + ), + null + ) + + When("교수 정보를 수정하면") { + val toModifyProfessorDto = createdProfessorDto.copy( + name = "modifiedName", + email = "modifiedEmail", + status = ProfessorStatus.INACTIVE, + academicRank = "modifiedAcademicRank", + labId = labEntity2.id, + startDate = date.plusDays(1), + endDate = date.plusDays(1), + office = "modifiedOffice", + phone = "modifiedPhone", + fax = "modifiedFax", + website = "modifiedWebsite", + educations = listOf("education1", "modifiedEducation2", "modifiedEducation3"), + researchAreas = listOf("researchArea1", "modifiedResearchArea2", "modifiedResearchArea3"), + careers = listOf("career1", "modifiedCareer2", "modifiedCareer3") + ) + + val modifiedProfessorDto = professorService.updateProfessor( + toModifyProfessorDto.id!!, + toModifyProfessorDto, + null + ) + + Then("교수 정보가 수정되어야 한다.") { + professorRepository.count() shouldBe 1 + val professorEntity = professorRepository.findByIdOrNull(modifiedProfessorDto.id) + professorEntity shouldNotBe null + + professorEntity!!.name shouldBe toModifyProfessorDto.name + professorEntity.email shouldBe toModifyProfessorDto.email + professorEntity.status shouldBe toModifyProfessorDto.status + professorEntity.academicRank shouldBe toModifyProfessorDto.academicRank + professorEntity.lab shouldBe labEntity2 + professorEntity.startDate shouldBe toModifyProfessorDto.startDate + professorEntity.endDate shouldBe toModifyProfessorDto.endDate + professorEntity.office shouldBe toModifyProfessorDto.office + professorEntity.phone shouldBe toModifyProfessorDto.phone + professorEntity.fax shouldBe toModifyProfessorDto.fax + professorEntity.website shouldBe toModifyProfessorDto.website + professorEntity.educations.map { it.name } shouldBe toModifyProfessorDto.educations + professorEntity.researchAreas.map { it.name } shouldBe toModifyProfessorDto.researchAreas + professorEntity.careers.map { it.name } shouldBe toModifyProfessorDto.careers + } + + Then("검색 정보가 수정되어야 한다.") { + memberSearchRepository.count() shouldBe 1 + + val professorEntity = professorRepository.findByIdOrNull(modifiedProfessorDto.id)!! + val memberSearchEntity = professorEntity.memberSearch + memberSearchEntity shouldNotBe null + + memberSearchEntity?.content shouldBe """ + modifiedName + 역대 교수 + modifiedAcademicRank + labName2 + ${date.plusDays(1)} + ${date.plusDays(1)} + modifiedOffice + modifiedPhone + modifiedFax + modifiedEmail + modifiedWebsite + education1 + modifiedEducation2 + modifiedEducation3 + researchArea1 + modifiedResearchArea2 + modifiedResearchArea3 + career1 + modifiedCareer2 + modifiedCareer3 + + """.trimIndent() + } + } + } +}) \ No newline at end of file diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/member/service/StaffServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/member/service/StaffServiceTest.kt new file mode 100644 index 00000000..cd56a5af --- /dev/null +++ b/src/test/kotlin/com/wafflestudio/csereal/core/member/service/StaffServiceTest.kt @@ -0,0 +1,131 @@ +package com.wafflestudio.csereal.core.member.service + +import com.wafflestudio.csereal.core.member.database.MemberSearchRepository +import com.wafflestudio.csereal.core.member.database.StaffRepository +import com.wafflestudio.csereal.core.member.dto.StaffDto +import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.extensions.spring.SpringTestExtension +import io.kotest.extensions.spring.SpringTestLifecycleMode +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import jakarta.transaction.Transactional +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.data.repository.findByIdOrNull + +@SpringBootTest +@Transactional +class StaffServiceTest( + private val staffService: StaffService, + private val staffRepository: StaffRepository, + private val memberSearchRepository: MemberSearchRepository, +): BehaviorSpec({ + extensions(SpringTestExtension(SpringTestLifecycleMode.Root)) + + afterSpec { + staffRepository.deleteAll() + } + + Given("이미지 없는 행정직원을 생성하려고 할 떄") { + val staffDto = StaffDto( + name = "name", + role = "role", + office = "office", + phone = "phone", + email = "email", + tasks = listOf("task1", "task2"), + ) + + When("행정직원을 생성하면") { + val createdStaffDto = staffService.createStaff(staffDto, null) + + Then("행정직원이 생성된다") { + staffRepository.count() shouldBe 1 + staffRepository.findByIdOrNull(createdStaffDto.id!!) shouldNotBe null + } + + Then("행정직원의 정보가 일치한다") { + val staffEntity = staffRepository.findByIdOrNull(createdStaffDto.id!!)!! + staffEntity.name shouldBe staffDto.name + staffEntity.role shouldBe staffDto.role + staffEntity.office shouldBe staffDto.office + staffEntity.phone shouldBe staffDto.phone + staffEntity.email shouldBe staffDto.email + staffEntity.tasks.map { it.name } shouldBe staffDto.tasks + } + + Then("검색 정보가 생성된다") { + memberSearchRepository.count() shouldBe 1 + + val staffEntity = staffRepository.findByIdOrNull(createdStaffDto.id!!)!! + val memberSearch = staffEntity.memberSearch!! + + memberSearch.content shouldBe """ + name + role + office + phone + email + task1 + task2 + + """.trimIndent() + } + } + } + + Given("이미지 없는 행정직원을 수정할 때") { + val staffDto = StaffDto( + name = "name", + role = "role", + office = "office", + phone = "phone", + email = "email", + tasks = listOf("task1", "task2"), + ) + + val createdStaffDto = staffService.createStaff(staffDto, null) + + When("행정직원을 수정하면") { + val updateStaffDto = StaffDto( + name = "name2", + role = "role2", + office = "office2", + phone = "phone2", + email = "email2", + tasks = listOf("task1", "task3", "task4"), + ) + + val updatedStaffDto = staffService.updateStaff(createdStaffDto.id!!, updateStaffDto, null) + + Then("행정직원의 정보가 일치한다") { + staffRepository.count() shouldBe 1 + val staffEntity = staffRepository.findByIdOrNull(updatedStaffDto.id!!)!! + staffEntity.name shouldBe updateStaffDto.name + staffEntity.role shouldBe updateStaffDto.role + staffEntity.office shouldBe updateStaffDto.office + staffEntity.phone shouldBe updateStaffDto.phone + staffEntity.email shouldBe updateStaffDto.email + staffEntity.tasks.map { it.name } shouldBe updateStaffDto.tasks + } + + Then("검색 정보가 수정된다") { + memberSearchRepository.count() shouldBe 1 + + val staffEntity = staffRepository.findByIdOrNull(updatedStaffDto.id!!)!! + val memberSearch = staffEntity.memberSearch!! + + memberSearch.content shouldBe """ + name2 + role2 + office2 + phone2 + email2 + task1 + task3 + task4 + + """.trimIndent() + } + } + } +}) \ No newline at end of file From b60f716715600ca64b2287a7402530933f7a8552 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Sat, 9 Sep 2023 16:59:08 +0900 Subject: [PATCH 062/124] =?UTF-8?q?fix:=20=EC=83=88=EC=86=8C=EC=8B=9D=20?= =?UTF-8?q?=EC=84=B8=EB=AF=B8=EB=82=98=20=EC=BF=BC=EB=A6=AC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#82)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 유틸 패키지 생성 및 FixedPageRequest 분리 * feat: 새소식 세미나 이전 다음 글 및 total 쿼리 최적화 * fix: pageNum 값으로 usePageBtn 값 설정 * fix: import 경로 수정 * fix: local db port 3306 * 중복 삭제 * fix: prod endpoint https로 변경 --- build.gradle.kts | 8 +- .../csereal/common/utils/FixedPageRequest.kt | 24 +++++ .../csereal/common/{ => utils}/Utils.kt | 2 +- .../csereal/core/news/api/NewsController.kt | 29 +++--- .../csereal/core/news/database/NewsEntity.kt | 7 +- .../core/news/database/NewsRepository.kt | 92 ++++++------------- .../csereal/core/news/dto/NewsDto.kt | 18 ++-- .../csereal/core/news/service/NewsService.kt | 55 ++++++----- .../core/notice/api/NoticeController.kt | 4 +- .../core/notice/database/NoticeEntity.kt | 4 +- .../core/notice/database/NoticeRepository.kt | 21 +---- .../core/notice/service/NoticeService.kt | 2 +- .../core/seminar/api/SeminarController.kt | 36 +++++--- .../core/seminar/database/SeminarEntity.kt | 10 +- .../seminar/database/SeminarRepository.kt | 81 +++++----------- .../csereal/core/seminar/dto/SeminarDto.kt | 18 ++-- .../core/seminar/service/SeminarService.kt | 22 +++-- src/main/resources/application.yaml | 10 +- 18 files changed, 207 insertions(+), 236 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/common/utils/FixedPageRequest.kt rename src/main/kotlin/com/wafflestudio/csereal/common/{ => utils}/Utils.kt (85%) diff --git a/build.gradle.kts b/build.gradle.kts index 21e16595..3948e7d1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -65,12 +65,6 @@ dependencies { // Custom Metadata annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") - // 이미지 업로드 - implementation("commons-io:commons-io:2.11.0") - - // 썸네일 보여주기 - implementation("net.coobird:thumbnailator:0.4.19") - } noArg { annotation("jakarta.persistence.Entity") @@ -94,4 +88,4 @@ tasks.withType { tasks.withType { useJUnitPlatform() systemProperty("spring.profiles.active", "test") -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/utils/FixedPageRequest.kt b/src/main/kotlin/com/wafflestudio/csereal/common/utils/FixedPageRequest.kt new file mode 100644 index 00000000..1005949e --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/common/utils/FixedPageRequest.kt @@ -0,0 +1,24 @@ +package com.wafflestudio.csereal.common.utils + +import org.springframework.data.domain.PageRequest +import org.springframework.data.domain.Pageable +import kotlin.math.floor + +class FixedPageRequest(pageable: Pageable, total: Long) : + PageRequest(getPageNum(pageable, total), pageable.pageSize, pageable.sort) { + + companion object { + private fun getPageNum(pageable: Pageable, total: Long): Int { + val pageNum = pageable.pageNumber + val pageSize = pageable.pageSize + val requestCount = pageNum * pageSize + + if (total > requestCount) { + return pageNum + } + + return floor(total.toDouble() / pageSize).toInt() + } + } + +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/Utils.kt b/src/main/kotlin/com/wafflestudio/csereal/common/utils/Utils.kt similarity index 85% rename from src/main/kotlin/com/wafflestudio/csereal/common/Utils.kt rename to src/main/kotlin/com/wafflestudio/csereal/common/utils/Utils.kt index 0ec330f2..757d94a7 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/Utils.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/utils/Utils.kt @@ -1,4 +1,4 @@ -package com.wafflestudio.csereal.common +package com.wafflestudio.csereal.common.utils import org.jsoup.Jsoup import org.jsoup.parser.Parser diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt index 2de2d69e..802ce6dc 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt @@ -4,6 +4,7 @@ import com.wafflestudio.csereal.core.news.dto.NewsDto import com.wafflestudio.csereal.core.news.dto.NewsSearchResponse import com.wafflestudio.csereal.core.news.service.NewsService import jakarta.validation.Valid +import org.springframework.data.domain.PageRequest import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* @@ -18,17 +19,19 @@ class NewsController( fun searchNews( @RequestParam(required = false) tag: List?, @RequestParam(required = false) keyword: String?, - @RequestParam(required = false, defaultValue = "0") pageNum:Long - ) : ResponseEntity { - return ResponseEntity.ok(newsService.searchNews(tag, keyword, pageNum)) + @RequestParam(required = false, defaultValue = "1") pageNum: Int + ): ResponseEntity { + val pageSize = 10 + val pageRequest = PageRequest.of(pageNum - 1, pageSize) + val usePageBtn = pageNum != 1 + return ResponseEntity.ok(newsService.searchNews(tag, keyword, pageRequest, usePageBtn)) } + @GetMapping("/{newsId}") fun readNews( - @PathVariable newsId: Long, - @RequestParam(required = false) tag : List?, - @RequestParam(required = false) keyword: String?, - ) : ResponseEntity { - return ResponseEntity.ok(newsService.readNews(newsId, tag, keyword)) + @PathVariable newsId: Long + ): ResponseEntity { + return ResponseEntity.ok(newsService.readNews(newsId)) } @PostMapping @@ -36,8 +39,8 @@ class NewsController( @Valid @RequestPart("request") request: NewsDto, @RequestPart("mainImage") mainImage: MultipartFile?, @RequestPart("attachments") attachments: List? - ) : ResponseEntity { - return ResponseEntity.ok(newsService.createNews(request,mainImage, attachments)) + ): ResponseEntity { + return ResponseEntity.ok(newsService.createNews(request, mainImage, attachments)) } @PatchMapping("/{newsId}") @@ -46,7 +49,7 @@ class NewsController( @Valid @RequestPart("request") request: NewsDto, @RequestPart("mainImage") mainImage: MultipartFile?, @RequestPart("attachments") attachments: List? - ) : ResponseEntity { + ): ResponseEntity { return ResponseEntity.ok(newsService.updateNews(newsId, request, mainImage, attachments)) } @@ -60,10 +63,10 @@ class NewsController( @PostMapping("/tag") fun enrollTag( @RequestBody tagName: Map - ) : ResponseEntity { + ): ResponseEntity { newsService.enrollTag(tagName["name"]!!) return ResponseEntity("등록되었습니다. (tagName: ${tagName["name"]})", HttpStatus.OK) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt index 1bba922e..3a753bab 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt @@ -1,9 +1,9 @@ package com.wafflestudio.csereal.core.news.database -import com.wafflestudio.csereal.common.cleanTextFromHtml import com.wafflestudio.csereal.common.config.BaseTimeEntity import com.wafflestudio.csereal.common.controller.AttachmentContentEntityType import com.wafflestudio.csereal.common.controller.MainImageContentEntityType +import com.wafflestudio.csereal.common.utils.cleanTextFromHtml import com.wafflestudio.csereal.core.news.dto.NewsDto import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity @@ -37,7 +37,7 @@ class NewsEntity( @OneToMany(mappedBy = "news", cascade = [CascadeType.ALL]) var newsTags: MutableSet = mutableSetOf() -): BaseTimeEntity(), MainImageContentEntityType, AttachmentContentEntityType { +) : BaseTimeEntity(), MainImageContentEntityType, AttachmentContentEntityType { override fun bringMainImage() = mainImage override fun bringAttachments() = attachments @@ -53,6 +53,7 @@ class NewsEntity( ) } } + fun update(updateNewsRequest: NewsDto) { if (updateNewsRequest.description != this.description) { this.description = updateNewsRequest.description @@ -64,4 +65,4 @@ class NewsEntity( this.isSlide = updateNewsRequest.isSlide this.isImportant = updateNewsRequest.isImportant } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt index 9158f495..e5e0ba11 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt @@ -3,22 +3,26 @@ package com.wafflestudio.csereal.core.news.database import com.querydsl.core.BooleanBuilder import com.querydsl.jpa.impl.JPAQueryFactory import com.wafflestudio.csereal.common.CserealException -import com.wafflestudio.csereal.common.cleanTextFromHtml +import com.wafflestudio.csereal.common.utils.FixedPageRequest +import com.wafflestudio.csereal.common.utils.cleanTextFromHtml import com.wafflestudio.csereal.core.news.database.QNewsEntity.newsEntity import com.wafflestudio.csereal.core.news.database.QNewsTagEntity.newsTagEntity import com.wafflestudio.csereal.core.news.dto.NewsSearchDto import com.wafflestudio.csereal.core.news.dto.NewsSearchResponse import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService +import org.springframework.data.domain.Pageable import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Component +import java.time.LocalDateTime interface NewsRepository : JpaRepository, CustomNewsRepository { fun findAllByIsImportant(isImportant: Boolean): List + fun findFirstByCreatedAtLessThanOrderByCreatedAtDesc(timestamp: LocalDateTime): NewsEntity? + fun findFirstByCreatedAtGreaterThanOrderByCreatedAtAsc(timestamp: LocalDateTime): NewsEntity? } interface CustomNewsRepository { - fun searchNews(tag: List?, keyword: String?, pageNum: Long): NewsSearchResponse - fun findPrevNextId(newsId: Long, tag: List?, keyword: String?): Array? + fun searchNews(tag: List?, keyword: String?, pageable: Pageable, usePageBtn: Boolean): NewsSearchResponse } @Component @@ -26,7 +30,12 @@ class NewsRepositoryImpl( private val queryFactory: JPAQueryFactory, private val mainImageService: MainImageService, ) : CustomNewsRepository { - override fun searchNews(tag: List?, keyword: String?, pageNum: Long): NewsSearchResponse { + override fun searchNews( + tag: List?, + keyword: String?, + pageable: Pageable, + usePageBtn: Boolean + ): NewsSearchResponse { val keywordBooleanBuilder = BooleanBuilder() val tagsBooleanBuilder = BooleanBuilder() @@ -56,17 +65,25 @@ class NewsRepositoryImpl( .where(newsEntity.isDeleted.eq(false), newsEntity.isPublic.eq(true)) .where(keywordBooleanBuilder).where(tagsBooleanBuilder) - val countQuery = jpaQuery.clone() - val total = countQuery.select(newsEntity.countDistinct()).fetchOne() + val total: Long + var pageRequest = pageable + + if (usePageBtn) { + val countQuery = jpaQuery.clone() + total = countQuery.select(newsEntity.countDistinct()).fetchOne()!! + pageRequest = FixedPageRequest(pageable, total) + } else { + total = (10 * pageable.pageSize).toLong() // 10개 페이지 고정 + } val newsEntityList = jpaQuery .orderBy(newsEntity.createdAt.desc()) - .offset(20*pageNum) - .limit(20) + .offset(pageRequest.offset) + .limit(pageRequest.pageSize.toLong()) .distinct() .fetch() - val newsSearchDtoList : List = newsEntityList.map { + val newsSearchDtoList: List = newsEntityList.map { val imageURL = mainImageService.createImageURL(it.mainImage) NewsSearchDto( id = it.id, @@ -77,61 +94,6 @@ class NewsRepositoryImpl( imageURL = imageURL ) } - return NewsSearchResponse(total!!, newsSearchDtoList) - } - - override fun findPrevNextId(newsId: Long, tag: List?, keyword: String?): Array? { - val keywordBooleanBuilder = BooleanBuilder() - val tagsBooleanBuilder = BooleanBuilder() - - if (!keyword.isNullOrEmpty()) { - val keywordList = keyword.split("[^a-zA-Z0-9가-힣]".toRegex()) - keywordList.forEach { - if (it.length == 1) { - throw CserealException.Csereal400("각각의 키워드는 한글자 이상이어야 합니다.") - } else { - keywordBooleanBuilder.and( - newsEntity.title.contains(it) - .or(newsEntity.description.contains(it)) - ) - } - - } - } - if (!tag.isNullOrEmpty()) { - tag.forEach { - tagsBooleanBuilder.or( - newsTagEntity.tag.name.eq(it) - ) - } - } - - val newsSearchDtoList = queryFactory.select(newsEntity).from(newsEntity) - .leftJoin(newsTagEntity).on(newsTagEntity.news.eq(newsEntity)) - .where(newsEntity.isDeleted.eq(false), newsEntity.isPublic.eq(true)) - .where(keywordBooleanBuilder).where(tagsBooleanBuilder) - .orderBy(newsEntity.createdAt.desc()) - .distinct() - .fetch() - - - val findingId = newsSearchDtoList.indexOfFirst { it.id == newsId } - - val prevNext: Array? - if (findingId == -1) { - prevNext = null - } else if (findingId != 0 && findingId != newsSearchDtoList.size - 1) { - prevNext = arrayOf(newsSearchDtoList[findingId + 1], newsSearchDtoList[findingId - 1]) - } else if (findingId == 0) { - if (newsSearchDtoList.size == 1) { - prevNext = arrayOf(null, null) - } else { - prevNext = arrayOf(newsSearchDtoList[1], null) - } - } else { - prevNext = arrayOf(null, newsSearchDtoList[newsSearchDtoList.size - 2]) - } - - return prevNext + return NewsSearchResponse(total, newsSearchDtoList) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt index 0cff4703..a2c5412d 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt @@ -22,7 +22,13 @@ data class NewsDto( val attachments: List?, ) { companion object { - fun of(entity: NewsEntity, imageURL: String?, attachmentResponses: List, prevNext: Array?) : NewsDto = entity.run { + fun of( + entity: NewsEntity, + imageURL: String?, + attachmentResponses: List, + prevNews: NewsEntity? = null, + nextNews: NewsEntity? = null + ): NewsDto = entity.run { NewsDto( id = this.id, title = this.title, @@ -33,13 +39,13 @@ data class NewsDto( isPublic = this.isPublic, isSlide = this.isSlide, isImportant = this.isImportant, - prevId = prevNext?.get(0)?.id, - prevTitle = prevNext?.get(0)?.title, - nextId = prevNext?.get(1)?.id, - nextTitle = prevNext?.get(1)?.title, + prevId = prevNews?.id, + prevTitle = prevNews?.title, + nextId = nextNews?.id, + nextTitle = nextNews?.title, imageURL = imageURL, attachments = attachmentResponses, ) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt index 2c2f2a43..333ce35c 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt @@ -6,16 +6,23 @@ import com.wafflestudio.csereal.core.news.dto.NewsDto import com.wafflestudio.csereal.core.news.dto.NewsSearchResponse import com.wafflestudio.csereal.core.resource.attachment.service.AttachmentService import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService +import org.springframework.data.domain.Pageable import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import org.springframework.web.multipart.MultipartFile interface NewsService { - fun searchNews(tag: List?, keyword: String?, pageNum: Long): NewsSearchResponse - fun readNews(newsId: Long, tag: List?, keyword: String?): NewsDto + fun searchNews(tag: List?, keyword: String?, pageable: Pageable, usePageBtn: Boolean): NewsSearchResponse + fun readNews(newsId: Long): NewsDto fun createNews(request: NewsDto, mainImage: MultipartFile?, attachments: List?): NewsDto - fun updateNews(newsId: Long, request: NewsDto, mainImage: MultipartFile?, attachments: List?): NewsDto + fun updateNews( + newsId: Long, + request: NewsDto, + mainImage: MultipartFile?, + attachments: List? + ): NewsDto + fun deleteNews(newsId: Long) fun enrollTag(tagName: String) } @@ -32,17 +39,14 @@ class NewsServiceImpl( override fun searchNews( tag: List?, keyword: String?, - pageNum: Long + pageable: Pageable, + usePageBtn: Boolean ): NewsSearchResponse { - return newsRepository.searchNews(tag, keyword, pageNum) + return newsRepository.searchNews(tag, keyword, pageable, usePageBtn) } @Transactional(readOnly = true) - override fun readNews( - newsId: Long, - tag: List?, - keyword: String? - ): NewsDto { + override fun readNews(newsId: Long): NewsDto { val news: NewsEntity = newsRepository.findByIdOrNull(newsId) ?: throw CserealException.Csereal404("존재하지 않는 새소식입니다.(newsId: $newsId)") @@ -51,10 +55,10 @@ class NewsServiceImpl( val imageURL = mainImageService.createImageURL(news.mainImage) val attachmentResponses = attachmentService.createAttachmentResponses(news.attachments) - val prevNext = newsRepository.findPrevNextId(newsId, tag, keyword) - ?: throw CserealException.Csereal400("이전글 다음글이 존재하지 않습니다.(newsId=$newsId)") + val prevNews = newsRepository.findFirstByCreatedAtLessThanOrderByCreatedAtDesc(news.createdAt!!) + val nextNews = newsRepository.findFirstByCreatedAtGreaterThanOrderByCreatedAtAsc(news.createdAt!!) - return NewsDto.of(news, imageURL, attachmentResponses, prevNext) + return NewsDto.of(news, imageURL, attachmentResponses, prevNews, nextNews) } @Transactional @@ -66,11 +70,11 @@ class NewsServiceImpl( NewsTagEntity.createNewsTag(newNews, tag) } - if(mainImage != null) { + if (mainImage != null) { mainImageService.uploadMainImage(newNews, mainImage) } - if(attachments != null) { + if (attachments != null) { attachmentService.uploadAllAttachments(newNews, attachments) } @@ -79,23 +83,28 @@ class NewsServiceImpl( val imageURL = mainImageService.createImageURL(newNews.mainImage) val attachmentResponses = attachmentService.createAttachmentResponses(newNews.attachments) - return NewsDto.of(newNews, imageURL, attachmentResponses, null) + return NewsDto.of(newNews, imageURL, attachmentResponses) } @Transactional - override fun updateNews(newsId: Long, request: NewsDto, mainImage: MultipartFile?, attachments: List?): NewsDto { + override fun updateNews( + newsId: Long, + request: NewsDto, + mainImage: MultipartFile?, + attachments: List? + ): NewsDto { val news: NewsEntity = newsRepository.findByIdOrNull(newsId) ?: throw CserealException.Csereal404("존재하지 않는 새소식입니다. (newsId: $newsId)") if (news.isDeleted) throw CserealException.Csereal404("삭제된 새소식입니다.") news.update(request) - if(mainImage != null) { + if (mainImage != null) { mainImageService.uploadMainImage(news, mainImage) } else { news.mainImage = null } - if(attachments != null) { + if (attachments != null) { news.attachments.clear() attachmentService.uploadAllAttachments(news, attachments) } else { @@ -107,7 +116,7 @@ class NewsServiceImpl( val tagsToRemove = oldTags - request.tags val tagsToAdd = request.tags - oldTags - for(tagName in tagsToRemove) { + for (tagName in tagsToRemove) { val tagId = tagInNewsRepository.findByName(tagName)!!.id news.newsTags.removeIf { it.tag.name == tagName } newsTagRepository.deleteByNewsIdAndTagId(newsId, tagId) @@ -115,13 +124,13 @@ class NewsServiceImpl( for (tagName in tagsToAdd) { val tag = tagInNewsRepository.findByName(tagName) ?: throw CserealException.Csereal404("해당하는 태그가 없습니다") - NewsTagEntity.createNewsTag(news,tag) + NewsTagEntity.createNewsTag(news, tag) } val imageURL = mainImageService.createImageURL(news.mainImage) val attachmentResponses = attachmentService.createAttachmentResponses(news.attachments) - return NewsDto.of(news, imageURL, attachmentResponses, null) + return NewsDto.of(news, imageURL, attachmentResponses) } @Transactional @@ -138,4 +147,4 @@ class NewsServiceImpl( ) tagInNewsRepository.save(newTag) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt index 4633d647..a45c6b33 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt @@ -19,11 +19,11 @@ class NoticeController( fun searchNotice( @RequestParam(required = false) tag: List?, @RequestParam(required = false) keyword: String?, - @RequestParam(required = false, defaultValue = "1") pageNum: Int, - @RequestParam(required = false, defaultValue = "false") usePageBtn: Boolean + @RequestParam(required = false, defaultValue = "1") pageNum: Int ): ResponseEntity { val pageSize = 20 val pageRequest = PageRequest.of(pageNum - 1, pageSize) + val usePageBtn = pageNum != 1 return ResponseEntity.ok(noticeService.searchNotice(tag, keyword, pageRequest, usePageBtn)) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt index 7f688cde..d1c3eb9d 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt @@ -1,6 +1,6 @@ package com.wafflestudio.csereal.core.notice.database -import com.wafflestudio.csereal.common.cleanTextFromHtml +import com.wafflestudio.csereal.common.utils.cleanTextFromHtml import com.wafflestudio.csereal.common.config.BaseTimeEntity import com.wafflestudio.csereal.common.controller.AttachmentContentEntityType import com.wafflestudio.csereal.core.notice.dto.NoticeDto @@ -33,7 +33,7 @@ class NoticeEntity( @OneToMany(mappedBy = "notice", cascade = [CascadeType.ALL], orphanRemoval = true) var attachments: MutableList = mutableListOf(), -) : BaseTimeEntity(), AttachmentContentEntityType { + ) : BaseTimeEntity(), AttachmentContentEntityType { override fun bringAttachments() = attachments fun update(updateNoticeRequest: NoticeDto) { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt index 4a0cb84a..a63f4837 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt @@ -3,6 +3,7 @@ package com.wafflestudio.csereal.core.notice.database import com.querydsl.core.BooleanBuilder import com.querydsl.jpa.impl.JPAQueryFactory import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.common.utils.FixedPageRequest import com.wafflestudio.csereal.core.notice.database.QNoticeEntity.noticeEntity import com.wafflestudio.csereal.core.notice.database.QNoticeTagEntity.noticeTagEntity import com.wafflestudio.csereal.core.notice.dto.NoticeSearchDto @@ -75,7 +76,7 @@ class NoticeRepositoryImpl( if (usePageBtn) { val countQuery = jpaQuery.clone() total = countQuery.select(noticeEntity.countDistinct()).fetchOne()!! - pageRequest = exchangePageRequest(pageable, total) + pageRequest = FixedPageRequest(pageable, total) } else { total = (10 * pageable.pageSize).toLong() // 10개 페이지 고정 } @@ -104,22 +105,4 @@ class NoticeRepositoryImpl( } - private fun exchangePageRequest(pageable: Pageable, total: Long): Pageable { - /** - * 요청한 페이지 번호가 기존 데이터 사이즈 초과할 경우 마지막 페이지 데이터 반환 - */ - - val pageNum = pageable.pageNumber - val pageSize = pageable.pageSize - val requestCount = (pageNum - 1) * pageSize - - if (total > requestCount) { - return pageable - } - - val requestPageNum = ceil(total.toDouble() / pageNum).toInt() - return PageRequest.of(requestPageNum, pageSize) - - } - } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt index 93f028fa..f770363f 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt @@ -1,7 +1,7 @@ package com.wafflestudio.csereal.core.notice.service import com.wafflestudio.csereal.common.CserealException -import com.wafflestudio.csereal.common.cleanTextFromHtml +import com.wafflestudio.csereal.common.utils.cleanTextFromHtml import com.wafflestudio.csereal.core.notice.database.* import com.wafflestudio.csereal.core.notice.dto.* import com.wafflestudio.csereal.core.resource.attachment.service.AttachmentService diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt index 01714fd0..2f5aebee 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt @@ -5,37 +5,41 @@ import com.wafflestudio.csereal.core.seminar.dto.SeminarDto import com.wafflestudio.csereal.core.seminar.dto.SeminarSearchResponse import com.wafflestudio.csereal.core.seminar.service.SeminarService import jakarta.validation.Valid +import org.springframework.data.domain.PageRequest import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile @RequestMapping("/api/v1/seminar") @RestController -class SeminarController ( +class SeminarController( private val seminarService: SeminarService, ) { @GetMapping fun searchSeminar( @RequestParam(required = false) keyword: String?, - @RequestParam(required = false, defaultValue = "0") pageNum: Long - ) : ResponseEntity { - return ResponseEntity.ok(seminarService.searchSeminar(keyword, pageNum)) + @RequestParam(required = false, defaultValue = "1") pageNum: Int + ): ResponseEntity { + val pageSize = 10 + val pageRequest = PageRequest.of(pageNum - 1, pageSize) + val usePageBtn = pageNum != 1 + return ResponseEntity.ok(seminarService.searchSeminar(keyword, pageRequest, usePageBtn)) } + @PostMapping fun createSeminar( @Valid @RequestPart("request") request: SeminarDto, @RequestPart("mainImage") mainImage: MultipartFile?, @RequestPart("attachments") attachments: List? - ) : ResponseEntity { + ): ResponseEntity { return ResponseEntity.ok(seminarService.createSeminar(request, mainImage, attachments)) } @GetMapping("/{seminarId}") fun readSeminar( - @PathVariable seminarId: Long, - @RequestParam(required = false) keyword: String?, - ) : ResponseEntity { - return ResponseEntity.ok(seminarService.readSeminar(seminarId, keyword)) + @PathVariable seminarId: Long + ): ResponseEntity { + return ResponseEntity.ok(seminarService.readSeminar(seminarId)) } @PatchMapping("/{seminarId}") @@ -45,8 +49,16 @@ class SeminarController ( @RequestPart("newMainImage") newMainImage: MultipartFile?, @RequestPart("newAttachments") newAttachments: List?, @RequestPart("attachmentsList") attachmentsList: List, - ) : ResponseEntity { - return ResponseEntity.ok(seminarService.updateSeminar(seminarId, request, newMainImage, newAttachments, attachmentsList)) + ): ResponseEntity { + return ResponseEntity.ok( + seminarService.updateSeminar( + seminarId, + request, + newMainImage, + newAttachments, + attachmentsList + ) + ) } @DeleteMapping("/{seminarId}") @@ -55,4 +67,4 @@ class SeminarController ( ) { seminarService.deleteSeminar(seminarId) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt index 03a1ac6d..9c680a60 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt @@ -1,9 +1,9 @@ package com.wafflestudio.csereal.core.seminar.database -import com.wafflestudio.csereal.common.cleanTextFromHtml import com.wafflestudio.csereal.common.config.BaseTimeEntity import com.wafflestudio.csereal.common.controller.AttachmentContentEntityType import com.wafflestudio.csereal.common.controller.MainImageContentEntityType +import com.wafflestudio.csereal.common.utils.cleanTextFromHtml import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity import com.wafflestudio.csereal.core.seminar.dto.SeminarDto @@ -57,14 +57,14 @@ class SeminarEntity( @OneToMany(mappedBy = "seminar", cascade = [CascadeType.ALL], orphanRemoval = true) var attachments: MutableList = mutableListOf(), - ): BaseTimeEntity(), MainImageContentEntityType, AttachmentContentEntityType { + ) : BaseTimeEntity(), MainImageContentEntityType, AttachmentContentEntityType { override fun bringMainImage(): MainImageEntity? = mainImage override fun bringAttachments() = attachments companion object { fun of(seminarDto: SeminarDto): SeminarEntity { val plainTextDescription = cleanTextFromHtml(seminarDto.description) - val palinTextIntroduction = cleanTextFromHtml(seminarDto.introduction) + val plainTextIntroduction = cleanTextFromHtml(seminarDto.introduction) val plainTextAdditionalNote = seminarDto.additionalNote?.let { cleanTextFromHtml(it) } return SeminarEntity( @@ -72,7 +72,7 @@ class SeminarEntity( description = seminarDto.description, plainTextDescription = plainTextDescription, introduction = seminarDto.introduction, - plainTextIntroduction = palinTextIntroduction, + plainTextIntroduction = plainTextIntroduction, name = seminarDto.name, speakerURL = seminarDto.speakerURL, speakerTitle = seminarDto.speakerTitle, @@ -120,4 +120,4 @@ class SeminarEntity( isPublic = updateSeminarRequest.isPublic isImportant = updateSeminarRequest.isImportant } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt index ad3b1b66..cc0a68ee 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt @@ -3,21 +3,25 @@ package com.wafflestudio.csereal.core.seminar.database import com.querydsl.core.BooleanBuilder import com.querydsl.jpa.impl.JPAQueryFactory import com.wafflestudio.csereal.common.CserealException -import com.wafflestudio.csereal.common.cleanTextFromHtml +import com.wafflestudio.csereal.common.utils.FixedPageRequest +import com.wafflestudio.csereal.common.utils.cleanTextFromHtml import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService import com.wafflestudio.csereal.core.seminar.database.QSeminarEntity.seminarEntity import com.wafflestudio.csereal.core.seminar.dto.SeminarSearchDto import com.wafflestudio.csereal.core.seminar.dto.SeminarSearchResponse +import org.springframework.data.domain.Pageable import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Component +import java.time.LocalDateTime interface SeminarRepository : JpaRepository, CustomSeminarRepository { fun findAllByIsImportant(isImportant: Boolean): List + fun findFirstByCreatedAtLessThanOrderByCreatedAtDesc(timestamp: LocalDateTime): SeminarEntity? + fun findFirstByCreatedAtGreaterThanOrderByCreatedAtAsc(timestamp: LocalDateTime): SeminarEntity? } interface CustomSeminarRepository { - fun searchSeminar(keyword: String?, pageNum: Long): SeminarSearchResponse - fun findPrevNextId(seminarId: Long, keyword: String?): Array? + fun searchSeminar(keyword: String?, pageable: Pageable, usePageBtn: Boolean): SeminarSearchResponse } @Component @@ -25,7 +29,7 @@ class SeminarRepositoryImpl( private val queryFactory: JPAQueryFactory, private val mainImageService: MainImageService, ) : CustomSeminarRepository { - override fun searchSeminar(keyword: String?, pageNum: Long): SeminarSearchResponse { + override fun searchSeminar(keyword: String?, pageable: Pageable, usePageBtn: Boolean): SeminarSearchResponse { val keywordBooleanBuilder = BooleanBuilder() if (!keyword.isNullOrEmpty()) { @@ -44,16 +48,25 @@ class SeminarRepositoryImpl( } } - val jpaQuery = queryFactory.select(seminarEntity).from(seminarEntity) - .where(seminarEntity.isDeleted.eq(false)) + val jpaQuery = queryFactory.selectFrom(seminarEntity) + .where(seminarEntity.isDeleted.eq(false), seminarEntity.isPublic.eq(true)) .where(keywordBooleanBuilder) - val countQuery = jpaQuery.clone() - val total = countQuery.select(seminarEntity.countDistinct()).fetchOne() + val total: Long + var pageRequest = pageable - val seminarEntityList = jpaQuery.orderBy(seminarEntity.createdAt.desc()) - .offset(10 * pageNum) - .limit(20) + if (usePageBtn) { + val countQuery = jpaQuery.clone() + total = countQuery.select(seminarEntity.count()).fetchOne()!! + pageRequest = FixedPageRequest(pageable, total) + } else { + total = (10 * pageable.pageSize).toLong() // 10개 페이지 고정 + } + + val seminarEntityList = jpaQuery + .orderBy(seminarEntity.createdAt.desc()) + .offset(pageRequest.offset) + .limit(pageRequest.pageSize.toLong()) .fetch() val seminarSearchDtoList: MutableList = mutableListOf() @@ -87,50 +100,6 @@ class SeminarRepositoryImpl( ) } - return SeminarSearchResponse(total!!, seminarSearchDtoList) - } - - override fun findPrevNextId(seminarId: Long, keyword: String?): Array? { - val keywordBooleanBuilder = BooleanBuilder() - - if (!keyword.isNullOrEmpty()) { - val keywordList = keyword.split("[^a-zA-Z0-9가-힣]".toRegex()) - keywordList.forEach { - if (it.length == 1) { - throw CserealException.Csereal400("각각의 키워드는 한글자 이상이어야 합니다.") - } else { - keywordBooleanBuilder.and( - seminarEntity.title.contains(it) - .or(seminarEntity.description.contains(it)) - ) - } - } - } - val seminarSearchDtoList = queryFactory.select(seminarEntity).from(seminarEntity) - .where(seminarEntity.isDeleted.eq(false), seminarEntity.isPublic.eq(true)) - .where(keywordBooleanBuilder) - .orderBy(seminarEntity.createdAt.desc()) - .fetch() - - val findingId = seminarSearchDtoList.indexOfFirst { it.id == seminarId } - - val prevNext: Array? - - if (findingId == -1) { - return null - } else if (findingId != 0 && findingId != seminarSearchDtoList.size - 1) { - prevNext = arrayOf(seminarSearchDtoList[findingId + 1], seminarSearchDtoList[findingId - 1]) - } else if (findingId == 0) { - if (seminarSearchDtoList.size == 1) { - prevNext = arrayOf(null, null) - } else { - prevNext = arrayOf(seminarSearchDtoList[1], null) - } - } else { - prevNext = arrayOf(null, seminarSearchDtoList[seminarSearchDtoList.size - 2]) - } - - return prevNext - + return SeminarSearchResponse(total, seminarSearchDtoList) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt index 3ca22770..3de25d31 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt @@ -33,7 +33,13 @@ data class SeminarDto( ) { companion object { - fun of(entity: SeminarEntity, imageURL: String?, attachmentResponses: List, prevNext: Array?): SeminarDto = entity.run { + fun of( + entity: SeminarEntity, + imageURL: String?, + attachmentResponses: List, + prevSeminar: SeminarEntity? = null, + nextSeminar: SeminarEntity? = null + ): SeminarDto = entity.run { SeminarDto( id = this.id, title = this.title, @@ -53,10 +59,10 @@ data class SeminarDto( modifiedAt = this.modifiedAt, isPublic = this.isPublic, isImportant = this.isImportant, - prevId = prevNext?.get(0)?.id, - prevTitle = prevNext?.get(0)?.title, - nextId = prevNext?.get(1)?.id, - nextTitle = prevNext?.get(1)?.title, + prevId = prevSeminar?.id, + prevTitle = prevSeminar?.title, + nextId = nextSeminar?.id, + nextTitle = nextSeminar?.title, imageURL = imageURL, attachments = attachmentResponses, ) @@ -64,4 +70,4 @@ data class SeminarDto( } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt index a0f19155..c9b23317 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt @@ -8,15 +8,16 @@ import com.wafflestudio.csereal.core.seminar.database.SeminarEntity import com.wafflestudio.csereal.core.seminar.database.SeminarRepository import com.wafflestudio.csereal.core.seminar.dto.SeminarDto import com.wafflestudio.csereal.core.seminar.dto.SeminarSearchResponse +import org.springframework.data.domain.Pageable import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import org.springframework.web.multipart.MultipartFile interface SeminarService { - fun searchSeminar(keyword: String?, pageNum: Long): SeminarSearchResponse + fun searchSeminar(keyword: String?, pageable: Pageable, usePageBtn: Boolean): SeminarSearchResponse fun createSeminar(request: SeminarDto, mainImage: MultipartFile?, attachments: List?): SeminarDto - fun readSeminar(seminarId: Long, keyword: String?): SeminarDto + fun readSeminar(seminarId: Long): SeminarDto fun updateSeminar( seminarId: Long, request: SeminarDto, @@ -35,8 +36,8 @@ class SeminarServiceImpl( private val attachmentService: AttachmentService, ) : SeminarService { @Transactional(readOnly = true) - override fun searchSeminar(keyword: String?, pageNum: Long): SeminarSearchResponse { - return seminarRepository.searchSeminar(keyword, pageNum) + override fun searchSeminar(keyword: String?, pageable: Pageable, usePageBtn: Boolean): SeminarSearchResponse { + return seminarRepository.searchSeminar(keyword, pageable, usePageBtn) } @Transactional @@ -59,11 +60,11 @@ class SeminarServiceImpl( val imageURL = mainImageService.createImageURL(newSeminar.mainImage) val attachmentResponses = attachmentService.createAttachmentResponses(newSeminar.attachments) - return SeminarDto.of(newSeminar, imageURL, attachmentResponses, null) + return SeminarDto.of(newSeminar, imageURL, attachmentResponses) } @Transactional(readOnly = true) - override fun readSeminar(seminarId: Long, keyword: String?): SeminarDto { + override fun readSeminar(seminarId: Long): SeminarDto { val seminar: SeminarEntity = seminarRepository.findByIdOrNull(seminarId) ?: throw CserealException.Csereal404("존재하지 않는 세미나입니다.(seminarId: $seminarId)") @@ -72,9 +73,10 @@ class SeminarServiceImpl( val imageURL = mainImageService.createImageURL(seminar.mainImage) val attachmentResponses = attachmentService.createAttachmentResponses(seminar.attachments) - val prevNext = seminarRepository.findPrevNextId(seminarId, keyword) + val prevSeminar = seminarRepository.findFirstByCreatedAtLessThanOrderByCreatedAtDesc(seminar.createdAt!!) + val nextSeminar = seminarRepository.findFirstByCreatedAtGreaterThanOrderByCreatedAtAsc(seminar.createdAt!!) - return SeminarDto.of(seminar, imageURL, attachmentResponses, prevNext) + return SeminarDto.of(seminar, imageURL, attachmentResponses, prevSeminar, nextSeminar) } @Transactional @@ -108,7 +110,7 @@ class SeminarServiceImpl( } val imageURL = mainImageService.createImageURL(seminar.mainImage) - return SeminarDto.of(seminar, imageURL, attachmentResponses, null) + return SeminarDto.of(seminar, imageURL, attachmentResponses) } @Transactional @@ -118,4 +120,4 @@ class SeminarServiceImpl( seminar.isDeleted = true } -} \ No newline at end of file +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index f3a7aa87..a316b261 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -40,7 +40,7 @@ servlet: spring: config.activate.on-profile: local datasource: - url: jdbc:mysql://127.0.0.1:3307/csereal?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Seoul + url: jdbc:mysql://127.0.0.1:3306/csereal?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Seoul username: root password: password jpa: @@ -84,7 +84,7 @@ spring: client: registration: idsnucse: - redirect-uri: http://${URL}/api/v1/login/oauth2/code/idsnucse + redirect-uri: https://${URL}/api/v1/login/oauth2/code/idsnucse csereal: upload: @@ -94,8 +94,8 @@ oldFiles: path: /app/cse-files endpoint: - backend: http://${URL}/api - frontend: http://${URL} + backend: https://${URL}/api + frontend: https://${URL} --- spring: @@ -137,4 +137,4 @@ oldFiles: endpoint: backend: http://localhost:8080 - frontend: http://localhost:3000 \ No newline at end of file + frontend: http://localhost:3000 From 6f726899a66c316cdf4930172420d34c21f8a373 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Sun, 10 Sep 2023 17:06:19 +0900 Subject: [PATCH 063/124] =?UTF-8?q?fix:=20main=20=ED=94=84=EB=A1=A0?= =?UTF-8?q?=ED=8A=B8=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=ED=98=91=EC=9D=98,?= =?UTF-8?q?=20notice=20=ED=83=9C=EA=B7=B8=20enum=20=EC=B6=94=EA=B0=80=20(#?= =?UTF-8?q?83)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: mainResponse 다시 작성 * feat: TagInNoticeEnum 추가 * feat: notice enum 추가 * fix: 코드 수정 --- .../csereal/core/main/api/MainController.kt | 4 +- .../core/main/database/MainRepository.kt | 95 ++++++++++++++----- ...ceResponse.kt => MainImportantResponse.kt} | 6 +- .../core/main/dto/MainNoticeResponse.kt | 10 ++ .../csereal/core/main/dto/MainResponse.kt | 9 +- .../{NewsResponse.kt => MainSlideResponse.kt} | 3 +- .../csereal/core/main/dto/NoticesResponse.kt | 11 +++ .../csereal/core/main/service/MainService.kt | 18 +++- .../core/notice/database/NoticeRepository.kt | 3 +- .../core/notice/database/TagInNoticeEntity.kt | 5 +- .../core/notice/database/TagInNoticeEnum.kt | 33 +++++++ .../notice/database/TagInNoticeRepository.kt | 2 +- .../csereal/core/notice/dto/NoticeDto.kt | 3 +- .../core/notice/service/NoticeService.kt | 25 ++--- 14 files changed, 173 insertions(+), 54 deletions(-) rename src/main/kotlin/com/wafflestudio/csereal/core/main/dto/{NoticeResponse.kt => MainImportantResponse.kt} (58%) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainNoticeResponse.kt rename src/main/kotlin/com/wafflestudio/csereal/core/main/dto/{NewsResponse.kt => MainSlideResponse.kt} (71%) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/main/dto/NoticesResponse.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeEnum.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/api/MainController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/api/MainController.kt index b633fc34..49c80bee 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/main/api/MainController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/api/MainController.kt @@ -6,8 +6,8 @@ import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -@RequestMapping -@RestController("/api/v1") +@RequestMapping("/api/v1") +@RestController class MainController( private val mainService: MainService, ) { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt index 22a9ab24..b174054e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt @@ -1,47 +1,58 @@ package com.wafflestudio.csereal.core.main.database -import com.querydsl.core.QueryFactory import com.querydsl.core.types.Projections import com.querydsl.jpa.impl.JPAQueryFactory -import com.sun.tools.javac.Main -import com.wafflestudio.csereal.core.main.dto.MainResponse -import com.wafflestudio.csereal.core.main.dto.NewsResponse -import com.wafflestudio.csereal.core.main.dto.NoticeResponse +import com.wafflestudio.csereal.core.main.dto.MainImportantResponse +import com.wafflestudio.csereal.core.main.dto.MainNoticeResponse +import com.wafflestudio.csereal.core.main.dto.MainSlideResponse +import com.wafflestudio.csereal.core.news.database.NewsRepository import com.wafflestudio.csereal.core.news.database.QNewsEntity.newsEntity +import com.wafflestudio.csereal.core.notice.database.NoticeRepository import com.wafflestudio.csereal.core.notice.database.QNoticeEntity.noticeEntity import com.wafflestudio.csereal.core.notice.database.QNoticeTagEntity.noticeTagEntity import com.wafflestudio.csereal.core.notice.database.QTagInNoticeEntity.tagInNoticeEntity +import com.wafflestudio.csereal.core.notice.database.TagInNoticeEnum +import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService +import com.wafflestudio.csereal.core.seminar.database.SeminarRepository import org.springframework.stereotype.Component interface MainRepository { - fun readMainSlide(): List - fun readMainNoticeTotal(): List - fun readMainNoticeTag(tag: String): List + fun readMainSlide(): List + fun readMainNoticeTotal(): List + fun readMainNoticeTag(tagEnum: TagInNoticeEnum): List + fun readMainImportant(): List } @Component class MainRepositoryImpl( private val queryFactory: JPAQueryFactory, + private val mainImageService: MainImageService, + private val noticeRepository: NoticeRepository, + private val newsRepository: NewsRepository, + private val seminarRepository: SeminarRepository, ) : MainRepository { - override fun readMainSlide(): List { - return queryFactory.select( - Projections.constructor( - NewsResponse::class.java, - newsEntity.id, - newsEntity.title, - newsEntity.createdAt - ) - ).from(newsEntity) + override fun readMainSlide(): List { + val newsEntityList = queryFactory.select(newsEntity).from(newsEntity) .where(newsEntity.isDeleted.eq(false), newsEntity.isPublic.eq(true), newsEntity.isSlide.eq(true)) .orderBy(newsEntity.createdAt.desc()) .limit(20).fetch() + + return newsEntityList.map { + val imageURL = mainImageService.createImageURL(it.mainImage) + MainSlideResponse( + id = it.id, + title = it.title, + imageURL = imageURL, + createdAt = it.createdAt + ) + } } - override fun readMainNoticeTotal(): List { + override fun readMainNoticeTotal(): List { return queryFactory.select( Projections.constructor( - NoticeResponse::class.java, + MainNoticeResponse::class.java, noticeEntity.id, noticeEntity.title, noticeEntity.createdAt @@ -51,10 +62,11 @@ class MainRepositoryImpl( .orderBy(noticeEntity.isPinned.desc()).orderBy(noticeEntity.createdAt.desc()) .limit(6).fetch() } - override fun readMainNoticeTag(tag: String): List { + + override fun readMainNoticeTag(tagEnum: TagInNoticeEnum): List { return queryFactory.select( Projections.constructor( - NoticeResponse::class.java, + MainNoticeResponse::class.java, noticeTagEntity.notice.id, noticeTagEntity.notice.title, noticeTagEntity.notice.createdAt, @@ -62,9 +74,48 @@ class MainRepositoryImpl( ).from(noticeTagEntity) .rightJoin(noticeEntity).on(noticeTagEntity.notice.eq(noticeEntity)) .rightJoin(tagInNoticeEntity).on(noticeTagEntity.tag.eq(tagInNoticeEntity)) - .where(noticeTagEntity.tag.name.eq(tag)) + .where(noticeTagEntity.tag.name.eq(tagEnum)) .where(noticeEntity.isDeleted.eq(false), noticeEntity.isPublic.eq(true)) .orderBy(noticeEntity.isPinned.desc()).orderBy(noticeEntity.createdAt.desc()) .limit(6).distinct().fetch() } + + override fun readMainImportant(): List { + val mainImportantResponses: MutableList = mutableListOf() + noticeRepository.findAllByIsImportant(true).forEach { + mainImportantResponses.add( + MainImportantResponse( + id = it.id, + title = it.title, + createdAt = it.createdAt, + category = "notice" + ) + ) + } + + newsRepository.findAllByIsImportant(true).forEach { + mainImportantResponses.add( + MainImportantResponse( + id = it.id, + title = it.title, + createdAt = it.createdAt, + category = "news" + ) + ) + } + + seminarRepository.findAllByIsImportant(true).forEach { + mainImportantResponses.add( + MainImportantResponse( + id = it.id, + title = it.title, + createdAt = it.createdAt, + category = "seminar" + ) + ) + } + mainImportantResponses.sortByDescending { it.createdAt } + + return mainImportantResponses + } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/NoticeResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainImportantResponse.kt similarity index 58% rename from src/main/kotlin/com/wafflestudio/csereal/core/main/dto/NoticeResponse.kt rename to src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainImportantResponse.kt index ed64b596..dc11f667 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/NoticeResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainImportantResponse.kt @@ -1,11 +1,11 @@ package com.wafflestudio.csereal.core.main.dto -import com.querydsl.core.annotations.QueryProjection import java.time.LocalDateTime -data class NoticeResponse @QueryProjection constructor( +data class MainImportantResponse( val id: Long, val title: String, val createdAt: LocalDateTime?, -){ + val category: String, +) { } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainNoticeResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainNoticeResponse.kt new file mode 100644 index 00000000..b90d7c60 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainNoticeResponse.kt @@ -0,0 +1,10 @@ +package com.wafflestudio.csereal.core.main.dto + +import java.time.LocalDateTime + +data class MainNoticeResponse( + val id: Long, + val title: String, + val createdAt: LocalDateTime? +) { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainResponse.kt index dc7a98ce..06c6dc8b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainResponse.kt @@ -1,11 +1,10 @@ package com.wafflestudio.csereal.core.main.dto + data class MainResponse( - val slide: List, - val noticeTotal: List, - val noticeAdmissions: List, - val noticeUndergraduate: List, - val noticeGraduate: List + val slides: List, + val notices: NoticesResponse, + val importants: List ) { } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/NewsResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainSlideResponse.kt similarity index 71% rename from src/main/kotlin/com/wafflestudio/csereal/core/main/dto/NewsResponse.kt rename to src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainSlideResponse.kt index 688c9627..483723d9 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/NewsResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainSlideResponse.kt @@ -3,9 +3,10 @@ package com.wafflestudio.csereal.core.main.dto import com.querydsl.core.annotations.QueryProjection import java.time.LocalDateTime -data class NewsResponse @QueryProjection constructor( +data class MainSlideResponse @QueryProjection constructor( val id: Long, val title: String, + val imageURL: String?, val createdAt: LocalDateTime? ) { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/NoticesResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/NoticesResponse.kt new file mode 100644 index 00000000..9b9a2aaf --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/NoticesResponse.kt @@ -0,0 +1,11 @@ +package com.wafflestudio.csereal.core.main.dto + +import com.querydsl.core.annotations.QueryProjection + +data class NoticesResponse @QueryProjection constructor( + val all: List, + val scholarship: List, + val undergraduate: List, + val graduate: List +){ +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/service/MainService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/service/MainService.kt index 67a05b9b..0c5c8951 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/main/service/MainService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/service/MainService.kt @@ -2,8 +2,11 @@ package com.wafflestudio.csereal.core.main.service import com.wafflestudio.csereal.core.main.database.MainRepository import com.wafflestudio.csereal.core.main.dto.MainResponse +import com.wafflestudio.csereal.core.main.dto.NoticesResponse +import com.wafflestudio.csereal.core.notice.database.TagInNoticeEnum import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional +import javax.swing.text.html.HTML.Tag interface MainService { fun readMain() : MainResponse @@ -15,11 +18,16 @@ class MainServiceImpl( ) : MainService { @Transactional(readOnly = true) override fun readMain(): MainResponse { - val slide = mainRepository.readMainSlide() + val slides = mainRepository.readMainSlide() + val noticeTotal = mainRepository.readMainNoticeTotal() - val noticeAdmissions = mainRepository.readMainNoticeTag("admissions") - val noticeUndergraduate = mainRepository.readMainNoticeTag("undergraduate") - val noticeGraduate = mainRepository.readMainNoticeTag("graduate") - return MainResponse(slide, noticeTotal, noticeAdmissions, noticeUndergraduate, noticeGraduate) + val noticeScholarship = mainRepository.readMainNoticeTag(TagInNoticeEnum.SCHOLARSHIP) + val noticeUndergraduate = mainRepository.readMainNoticeTag(TagInNoticeEnum.UNDERGRADUATE) + val noticeGraduate = mainRepository.readMainNoticeTag(TagInNoticeEnum.GRADUATE) + val notices = NoticesResponse(noticeTotal, noticeScholarship, noticeUndergraduate, noticeGraduate) + + val importants = mainRepository.readMainImportant() + + return MainResponse(slides, notices, importants) } } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt index a63f4837..0b974821 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt @@ -59,8 +59,9 @@ class NoticeRepositoryImpl( } if (!tag.isNullOrEmpty()) { tag.forEach { + val tagEnum = TagInNoticeEnum.getTagEnum(it) tagsBooleanBuilder.or( - noticeTagEntity.tag.name.eq(it) + noticeTagEntity.tag.name.eq(tagEnum) ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeEntity.kt index 1eb44da7..811f38e7 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeEntity.kt @@ -2,11 +2,14 @@ package com.wafflestudio.csereal.core.notice.database import com.wafflestudio.csereal.common.config.BaseTimeEntity import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated import jakarta.persistence.OneToMany @Entity(name = "tag_in_notice") class TagInNoticeEntity( - var name: String, + @Enumerated(EnumType.STRING) + var name: TagInNoticeEnum, @OneToMany(mappedBy = "tag") val noticeTags: MutableSet = mutableSetOf() diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeEnum.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeEnum.kt new file mode 100644 index 00000000..a15c56ab --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeEnum.kt @@ -0,0 +1,33 @@ +package com.wafflestudio.csereal.core.notice.database + +import com.wafflestudio.csereal.common.CserealException + +enum class TagInNoticeEnum { + CLASS, SCHOLARSHIP, UNDERGRADUATE, GRADUATE, MINOR, REGISTRATIONS, ADMISSIONS, GRADUATIONS, + RECRUIT, STUDENT_EXCHANGE, EVENTS_PROGRAMS, FOREIGN; + + companion object { + fun getTagEnum(t: String) : TagInNoticeEnum{ + return when(t) { + "수업" -> CLASS + "장학" -> SCHOLARSHIP + "학사(학부)" -> UNDERGRADUATE + "학사(대학원)" -> GRADUATE + "다전공/전과" -> MINOR + "등록/복학/휴학/재입학" -> REGISTRATIONS + "입학" -> ADMISSIONS + "졸업" -> GRADUATIONS + "채용정보" -> RECRUIT + "교환학생/유학" -> STUDENT_EXCHANGE + "행사/프로그램" -> EVENTS_PROGRAMS + "foreign" -> FOREIGN + else -> throw CserealException.Csereal404("태그를 찾을 수 없습니다") + } + } + } + + +} + + + diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeRepository.kt index d576b129..baefb68e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeRepository.kt @@ -3,5 +3,5 @@ package com.wafflestudio.csereal.core.notice.database import org.springframework.data.jpa.repository.JpaRepository interface TagInNoticeRepository : JpaRepository { - fun findByName(tagName: String): TagInNoticeEntity? + fun findByName(tagName: TagInNoticeEnum): TagInNoticeEntity } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt index 528c4f0f..7e28e3d6 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt @@ -1,6 +1,7 @@ package com.wafflestudio.csereal.core.notice.dto import com.wafflestudio.csereal.core.notice.database.NoticeEntity +import com.wafflestudio.csereal.core.notice.database.TagInNoticeEnum import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse import java.time.LocalDateTime @@ -34,7 +35,7 @@ data class NoticeDto( title = this.title, description = this.description, author = this.author.name, - tags = this.noticeTags.map { it.tag.name }, + tags = this.noticeTags.map { it.tag.name.name }, createdAt = this.createdAt, modifiedAt = this.modifiedAt, isPublic = this.isPublic, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt index f770363f..4f2ccf32 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt @@ -92,9 +92,10 @@ class NoticeServiceImpl( author = user ) - for (tagName in request.tags) { - val tag = tagInNoticeRepository.findByName(tagName) ?: throw CserealException.Csereal404("해당하는 태그가 없습니다") - NoticeTagEntity.createNoticeTag(newNotice, tag) + for (tag in request.tags) { + val tagEnum = TagInNoticeEnum.getTagEnum(tag) + val tagEntity = tagInNoticeRepository.findByName(tagEnum) + NoticeTagEntity.createNoticeTag(newNotice, tagEntity) } if (attachments != null) { @@ -126,17 +127,17 @@ class NoticeServiceImpl( val oldTags = notice.noticeTags.map { it.tag.name } - val tagsToRemove = oldTags - request.tags - val tagsToAdd = request.tags - oldTags + val tagsToRemove = oldTags - request.tags.map { TagInNoticeEnum.getTagEnum(it) } + val tagsToAdd = request.tags.map { TagInNoticeEnum.getTagEnum(it) } - oldTags - for (tagName in tagsToRemove) { - val tagId = tagInNoticeRepository.findByName(tagName)!!.id - notice.noticeTags.removeIf { it.tag.name == tagName } + for (tagEnum in tagsToRemove) { + val tagId = tagInNoticeRepository.findByName(tagEnum)!!.id + notice.noticeTags.removeIf { it.tag.name == tagEnum } noticeTagRepository.deleteByNoticeIdAndTagId(noticeId, tagId) } - for (tagName in tagsToAdd) { - val tag = tagInNoticeRepository.findByName(tagName) ?: throw CserealException.Csereal404("해당하는 태그가 없습니다") + for (tagEnum in tagsToAdd) { + val tag = tagInNoticeRepository.findByName(tagEnum) ?: throw CserealException.Csereal404("해당하는 태그가 없습니다") NoticeTagEntity.createNoticeTag(notice, tag) } @@ -174,10 +175,10 @@ class NoticeServiceImpl( override fun enrollTag(tagName: String) { val newTag = TagInNoticeEntity( - name = tagName + name = TagInNoticeEnum.getTagEnum(tagName) ) tagInNoticeRepository.save(newTag) } - //TODO: 이미지 등록, 글쓴이 함께 조회 + } From cb9e3a1d8453d3a5b452be557f3264de0a08debc Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Mon, 11 Sep 2023 13:52:32 +0900 Subject: [PATCH 064/124] =?UTF-8?q?fix:=20isPublic=20->=20isPrivate?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=A0=95=EB=A6=AC=20(#86)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: rebase 정리 * fix: 오타 수정 --- .../csereal/core/admin/database/AdminRepository.kt | 2 +- .../csereal/core/main/database/MainRepository.kt | 8 ++++---- .../wafflestudio/csereal/core/news/database/NewsEntity.kt | 6 +++--- .../csereal/core/news/database/NewsRepository.kt | 2 +- .../com/wafflestudio/csereal/core/news/dto/NewsDto.kt | 4 ++-- .../csereal/core/notice/database/NoticeEntity.kt | 4 ++-- .../csereal/core/notice/database/NoticeRepository.kt | 2 +- .../com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt | 4 ++-- .../csereal/core/notice/service/NoticeService.kt | 2 +- .../csereal/core/seminar/database/SeminarEntity.kt | 6 +++--- .../csereal/core/seminar/database/SeminarRepository.kt | 2 +- .../wafflestudio/csereal/core/seminar/dto/SeminarDto.kt | 4 ++-- .../csereal/core/notice/news/NewsServiceTest.kt | 4 ++-- .../csereal/core/notice/service/NoticeServiceTest.kt | 4 ++-- .../csereal/core/seminar/service/SeminarServiceTest.kt | 4 ++-- 15 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/database/AdminRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/database/AdminRepository.kt index df3effa5..f47c1092 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admin/database/AdminRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/database/AdminRepository.kt @@ -25,7 +25,7 @@ class AdminRepositoryImpl( newsEntity.createdAt ) ).from(newsEntity) - .where(newsEntity.isDeleted.eq(false), newsEntity.isPublic.eq(true), newsEntity.isSlide.eq(true)) + .where(newsEntity.isDeleted.eq(false), newsEntity.isPrivate.eq(false), newsEntity.isSlide.eq(true)) .orderBy(newsEntity.createdAt.desc()) .offset(40*pageNum) .limit(40) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt index b174054e..a7fd29d7 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt @@ -33,8 +33,8 @@ class MainRepositoryImpl( private val seminarRepository: SeminarRepository, ) : MainRepository { override fun readMainSlide(): List { - val newsEntityList = queryFactory.select(newsEntity).from(newsEntity) - .where(newsEntity.isDeleted.eq(false), newsEntity.isPublic.eq(true), newsEntity.isSlide.eq(true)) + val newsEntityList = queryFactory.selectFrom(newsEntity) + .where(newsEntity.isDeleted.eq(false), newsEntity.isPrivate.eq(false), newsEntity.isSlide.eq(true)) .orderBy(newsEntity.createdAt.desc()) .limit(20).fetch() @@ -58,7 +58,7 @@ class MainRepositoryImpl( noticeEntity.createdAt ) ).from(noticeEntity) - .where(noticeEntity.isDeleted.eq(false), noticeEntity.isPublic.eq(true)) + .where(noticeEntity.isDeleted.eq(false), noticeEntity.isPrivate.eq(false)) .orderBy(noticeEntity.isPinned.desc()).orderBy(noticeEntity.createdAt.desc()) .limit(6).fetch() } @@ -75,7 +75,7 @@ class MainRepositoryImpl( .rightJoin(noticeEntity).on(noticeTagEntity.notice.eq(noticeEntity)) .rightJoin(tagInNoticeEntity).on(noticeTagEntity.tag.eq(tagInNoticeEntity)) .where(noticeTagEntity.tag.name.eq(tagEnum)) - .where(noticeEntity.isDeleted.eq(false), noticeEntity.isPublic.eq(true)) + .where(noticeEntity.isDeleted.eq(false), noticeEntity.isPrivate.eq(true)) .orderBy(noticeEntity.isPinned.desc()).orderBy(noticeEntity.createdAt.desc()) .limit(6).distinct().fetch() } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt index 3a753bab..9304f224 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt @@ -22,7 +22,7 @@ class NewsEntity( @Column(columnDefinition = "mediumtext") var plainTextDescription: String, - var isPublic: Boolean, + var isPrivate: Boolean, var isSlide: Boolean, @@ -47,7 +47,7 @@ class NewsEntity( title = newsDto.title, description = newsDto.description, plainTextDescription = cleanTextFromHtml(newsDto.description), - isPublic = newsDto.isPublic, + isPrivate = newsDto.isPrivate, isSlide = newsDto.isSlide, isImportant = newsDto.isImportant, ) @@ -61,7 +61,7 @@ class NewsEntity( } this.title = updateNewsRequest.title - this.isPublic = updateNewsRequest.isPublic + this.isPrivate = updateNewsRequest.isPrivate this.isSlide = updateNewsRequest.isSlide this.isImportant = updateNewsRequest.isImportant } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt index e5e0ba11..d2ee0793 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt @@ -62,7 +62,7 @@ class NewsRepositoryImpl( val jpaQuery = queryFactory.select(newsEntity).from(newsEntity) .leftJoin(newsTagEntity).on(newsTagEntity.news.eq(newsEntity)) - .where(newsEntity.isDeleted.eq(false), newsEntity.isPublic.eq(true)) + .where(newsEntity.isDeleted.eq(false)) .where(keywordBooleanBuilder).where(tagsBooleanBuilder) val total: Long diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt index a2c5412d..bf707a83 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt @@ -11,7 +11,7 @@ data class NewsDto( val tags: List, val createdAt: LocalDateTime?, val modifiedAt: LocalDateTime?, - val isPublic: Boolean, + val isPrivate: Boolean, val isSlide: Boolean, val isImportant: Boolean, val prevId: Long?, @@ -36,7 +36,7 @@ data class NewsDto( tags = this.newsTags.map { it.tag.name }, createdAt = this.createdAt, modifiedAt = this.modifiedAt, - isPublic = this.isPublic, + isPrivate = this.isPrivate, isSlide = this.isSlide, isImportant = this.isImportant, prevId = prevNews?.id, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt index d1c3eb9d..fb46b9ae 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt @@ -19,7 +19,7 @@ class NoticeEntity( @Column(columnDefinition = "mediumtext") var plainTextDescription: String, - var isPublic: Boolean, + var isPrivate: Boolean, var isPinned: Boolean, var isImportant: Boolean, @@ -44,7 +44,7 @@ class NoticeEntity( this.title = updateNoticeRequest.title this.description = updateNoticeRequest.description - this.isPublic = updateNoticeRequest.isPublic + this.isPrivate = updateNoticeRequest.isPrivate this.isPinned = updateNoticeRequest.isPinned this.isImportant = updateNoticeRequest.isImportant } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt index 0b974821..ae7874d4 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt @@ -68,7 +68,7 @@ class NoticeRepositoryImpl( val jpaQuery = queryFactory.selectFrom(noticeEntity) .leftJoin(noticeTagEntity).on(noticeTagEntity.notice.eq(noticeEntity)) - .where(noticeEntity.isDeleted.eq(false), noticeEntity.isPublic.eq(true)) + .where(noticeEntity.isDeleted.eq(false)) .where(keywordBooleanBuilder, tagsBooleanBuilder) val total: Long diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt index 7e28e3d6..a747b934 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt @@ -13,7 +13,7 @@ data class NoticeDto( val tags: List, val createdAt: LocalDateTime?, val modifiedAt: LocalDateTime?, - val isPublic: Boolean, + val isPrivate: Boolean, val isPinned: Boolean, val isImportant: Boolean, val prevId: Long?, @@ -38,7 +38,7 @@ data class NoticeDto( tags = this.noticeTags.map { it.tag.name.name }, createdAt = this.createdAt, modifiedAt = this.modifiedAt, - isPublic = this.isPublic, + isPrivate = this.isPrivate, isPinned = this.isPinned, isImportant = this.isImportant, prevId = prevNotice?.id, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt index 4f2ccf32..d8fca1d5 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt @@ -86,7 +86,7 @@ class NoticeServiceImpl( title = request.title, description = request.description, plainTextDescription = cleanTextFromHtml(request.description), - isPublic = request.isPublic, + isPrivate = request.isPrivate, isPinned = request.isPinned, isImportant = request.isImportant, author = user diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt index 9c680a60..d5d63b6b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt @@ -42,7 +42,7 @@ class SeminarEntity( var host: String?, - var isPublic: Boolean, + var isPrivate: Boolean, var isImportant: Boolean, @Column(columnDefinition = "text") @@ -82,7 +82,7 @@ class SeminarEntity( endDate = seminarDto.endDate, location = seminarDto.location, host = seminarDto.host, - isPublic = seminarDto.isPublic, + isPrivate = seminarDto.isPrivate, isImportant = seminarDto.isImportant, additionalNote = seminarDto.additionalNote, plainTextAdditionalNote = plainTextAdditionalNote, @@ -117,7 +117,7 @@ class SeminarEntity( endDate = updateSeminarRequest.endDate location = updateSeminarRequest.location host = updateSeminarRequest.host - isPublic = updateSeminarRequest.isPublic + isPrivate = updateSeminarRequest.isPrivate isImportant = updateSeminarRequest.isImportant } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt index cc0a68ee..13ef466a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt @@ -49,7 +49,7 @@ class SeminarRepositoryImpl( } val jpaQuery = queryFactory.selectFrom(seminarEntity) - .where(seminarEntity.isDeleted.eq(false), seminarEntity.isPublic.eq(true)) + .where(seminarEntity.isDeleted.eq(false)) .where(keywordBooleanBuilder) val total: Long diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt index 3de25d31..91c58b78 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt @@ -22,7 +22,7 @@ data class SeminarDto( val additionalNote: String?, val createdAt: LocalDateTime?, val modifiedAt: LocalDateTime?, - val isPublic: Boolean, + val isPrivate: Boolean, val isImportant: Boolean, val prevId: Long?, val prevTitle: String?, @@ -57,7 +57,7 @@ data class SeminarDto( additionalNote = this.additionalNote, createdAt = this.createdAt, modifiedAt = this.modifiedAt, - isPublic = this.isPublic, + isPrivate = this.isPrivate, isImportant = this.isImportant, prevId = prevSeminar?.id, prevTitle = prevSeminar?.title, diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/notice/news/NewsServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/notice/news/NewsServiceTest.kt index 7da09dc7..0971633f 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/notice/news/NewsServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/notice/news/NewsServiceTest.kt @@ -33,7 +33,7 @@ class NewsServiceTest( tags = emptyList(), createdAt = null, modifiedAt = null, - isPublic = false, + isPrivate = false, isSlide = false, isImportant = false, prevId = null, @@ -69,7 +69,7 @@ class NewsServiceTest(

Goodbye, World!

""".trimIndent(), plainTextDescription = "Hello, World! This is news description. Goodbye, World!", - isPublic = false, + isPrivate = false, isSlide = false, isImportant = false, ) diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeServiceTest.kt index fd72d6a2..173be98a 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeServiceTest.kt @@ -68,7 +68,7 @@ class NoticeServiceTest( tags = emptyList(), createdAt = null, modifiedAt = null, - isPublic = false, + isPrivate = false, isPinned = false, isImportant = false, prevId = null, @@ -102,7 +102,7 @@ class NoticeServiceTest(

Goodbye, World!

""".trimIndent(), plainTextDescription = "Hello, World! This is a test notice. Goodbye, World!", - isPublic = false, + isPrivate = false, isPinned = false, isImportant = false, author = userRepository.findByUsername("username")!!, diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarServiceTest.kt index 03669627..0ff9573a 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarServiceTest.kt @@ -55,7 +55,7 @@ class SeminarServiceTest ( """.trimIndent(), createdAt = null, modifiedAt = null, - isPublic = false, + isPrivate = false, isImportant = false, prevId = null, prevTitle = null, @@ -112,7 +112,7 @@ class SeminarServiceTest (

Goodbye, World!

""".trimIndent(), plainTextAdditionalNote = "Hello, World! This is seminar additionalNote. Goodbye, World!", - isPublic = false, + isPrivate = false, isImportant = false, ) ) From 471b128ba65e4de59cc9cddb7e78efaeee757cbb Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Mon, 11 Sep 2023 14:04:40 +0900 Subject: [PATCH 065/124] =?UTF-8?q?feat:=20=EB=A1=9C=EC=BB=AC=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=9A=A9=20?= =?UTF-8?q?=EC=97=94=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=EB=A1=9C=20?= =?UTF-8?q?=EC=9E=84=EC=8B=9C=20=EB=B3=80=EA=B2=BD=20(#87)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../csereal/common/config/CustomAuthenticationSuccessHandler.kt | 2 +- .../com/wafflestudio/csereal/common/config/SecurityConfig.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/CustomAuthenticationSuccessHandler.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/CustomAuthenticationSuccessHandler.kt index 52d1d027..94a5495d 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/CustomAuthenticationSuccessHandler.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/CustomAuthenticationSuccessHandler.kt @@ -13,7 +13,7 @@ class CustomAuthenticationSuccessHandler( response: HttpServletResponse, authentication: Authentication ) { - val redirectUrl = "${frontendEndpoint}/login/success" + val redirectUrl = "http://localhost:3000/login/success" response.sendRedirect(redirectUrl) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt index bee84ad8..88c34783 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt @@ -57,7 +57,7 @@ class SecurityConfig( response: HttpServletResponse?, authentication: Authentication? ) { - val redirectUrl = "${endpointProperties.frontend}/logout/success" + val redirectUrl = "http://localhost:3000/logout/success" super.setDefaultTargetUrl(redirectUrl) super.onLogoutSuccess(request, response, authentication) } From c06e73b19170c1fb5c7343e575aa72968dbe278b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=ED=98=81=EC=A4=80=20=28HyukJoon=20Woo=29?= Date: Mon, 11 Sep 2023 14:17:03 +0900 Subject: [PATCH 066/124] =?UTF-8?q?Feat:=20=EA=B5=AC=EC=84=B1=EC=9B=90=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=20API=20=EC=B6=94=EA=B0=80=20(#85)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat: Add mysql custom dialect with MATCH AGAINST BOOLEAN MODE function. * CICD: Add config for mysql db * CICD: Add volumes for config file. * CICD: Add db_config for condition for db deploy * Feat: Add search top, and search query. * Feat: Add DTOs * Feat: Add Controller for member search only top, and page. * Feat: Add dialect config for application.yaml * Feat: Add controller for member search. * Refactor: Change exception message to korean. --------- Co-authored-by: Jo Seonggyu --- .github/workflows/database.yaml | 1 + db_config/config.cnf | 2 + docker-compose-db.yml | 1 + docker-compose-local.yml | 33 +++++---- .../common/config/MySQLDialectCustom.kt | 38 ++++++++++ .../core/member/api/MemberSearchController.kt | 27 +++++++ .../member/database/MemberSearchRepository.kt | 74 ++++++++++++++++++- .../member/dto/MemberSearchPageResponse.kt | 20 +++++ .../member/dto/MemberSearchResponseElement.kt | 42 +++++++++++ .../member/dto/MemberSearchTopResponse.kt | 19 +++++ .../member/service/MemberSearchService.kt | 22 +++++- src/main/resources/application.yaml | 9 +++ 12 files changed, 268 insertions(+), 20 deletions(-) create mode 100644 db_config/config.cnf create mode 100644 src/main/kotlin/com/wafflestudio/csereal/common/config/MySQLDialectCustom.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/api/MemberSearchController.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchPageResponse.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchResponseElement.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchTopResponse.kt diff --git a/.github/workflows/database.yaml b/.github/workflows/database.yaml index 35f1e975..86818a91 100644 --- a/.github/workflows/database.yaml +++ b/.github/workflows/database.yaml @@ -4,6 +4,7 @@ on: - main paths: - docker-compose-db.yml + - db_config/* - .github/workflows/database.yaml jobs: diff --git a/db_config/config.cnf b/db_config/config.cnf new file mode 100644 index 00000000..25554cc3 --- /dev/null +++ b/db_config/config.cnf @@ -0,0 +1,2 @@ +[mysqld] +ngram_token_size=2 \ No newline at end of file diff --git a/docker-compose-db.yml b/docker-compose-db.yml index b9586817..0a38e1e6 100644 --- a/docker-compose-db.yml +++ b/docker-compose-db.yml @@ -6,6 +6,7 @@ services: - 3306:3306 volumes: - ./db:/var/lib/mysql + - ./db_config:/etc/mysql/conf.d environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_DATABASE: ${MYSQL_DATABASE} diff --git a/docker-compose-local.yml b/docker-compose-local.yml index 39e075e9..84742e7f 100644 --- a/docker-compose-local.yml +++ b/docker-compose-local.yml @@ -1,16 +1,17 @@ -version: '3.8' -services: - db: - image: mysql:8.0 - cap_add: - - SYS_NICE - environment: - - MYSQL_DATABASE=csereal - - MYSQL_ROOT_PASSWORD=password - ports: - - '3306:3306' - volumes: - - db:/var/lib/mysql -volumes: - db: - driver: local +version: '3.8' +services: + db: + image: mysql:8.0 + cap_add: + - SYS_NICE + environment: + - MYSQL_DATABASE=csereal + - MYSQL_ROOT_PASSWORD=password + ports: + - '3306:3306' + volumes: + - ./db:/var/lib/mysql + - ./db_config:/etc/mysql/conf.d +volumes: + db: + driver: local diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/MySQLDialectCustom.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/MySQLDialectCustom.kt new file mode 100644 index 00000000..aa73d8bb --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/MySQLDialectCustom.kt @@ -0,0 +1,38 @@ +package com.wafflestudio.csereal.common.config + +import org.hibernate.dialect.DatabaseVersion +import org.hibernate.dialect.MySQLDialect +import org.hibernate.query.spi.QueryEngine +import org.hibernate.type.StandardBasicTypes + + +class MySQLDialectCustom: MySQLDialect( + DatabaseVersion.make(8) +) { + override fun initializeFunctionRegistry(queryEngine: QueryEngine?) { + super.initializeFunctionRegistry(queryEngine) + + val basicTypeRegistry = queryEngine?.typeConfiguration?.basicTypeRegistry + val functionRegistry = queryEngine?.sqmFunctionRegistry + + if (basicTypeRegistry != null && functionRegistry != null) { + functionRegistry.registerPattern( + "match", + "match (?1) against (?2 in boolean mode)", + basicTypeRegistry.resolve(StandardBasicTypes.DOUBLE) + ) + + functionRegistry.registerPattern( + "match2", + "match (?1, ?2) against (?3 in boolean mode)", + basicTypeRegistry.resolve(StandardBasicTypes.DOUBLE) + ) + + functionRegistry.registerPattern( + "match3", + "match (?1, ?2, ?3) against (?4 in boolean mode)", + basicTypeRegistry.resolve(StandardBasicTypes.DOUBLE) + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/MemberSearchController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/MemberSearchController.kt new file mode 100644 index 00000000..bd13a673 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/MemberSearchController.kt @@ -0,0 +1,27 @@ +package com.wafflestudio.csereal.core.member.api + +import com.wafflestudio.csereal.core.member.service.MemberSearchService +import org.springframework.data.jpa.repository.Query +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/api/v1/member/search") +class MemberSearchController( + private val memberSearchService: MemberSearchService, +) { + @GetMapping("/top") + fun searchTop( + @RequestParam(required = true) keyword: String, + @RequestParam(required = true) number: Int, + ) = memberSearchService.searchTopMember(keyword, number) + + @GetMapping + fun searchPage( + @RequestParam(required = true) keyword: String, + @RequestParam(required = true) pageSize: Int, + @RequestParam(required = true) pageNum: Int, + )= memberSearchService.searchMember(keyword, pageSize, pageNum) +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/MemberSearchRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/MemberSearchRepository.kt index a6089892..a6403c70 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/MemberSearchRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/MemberSearchRepository.kt @@ -1,6 +1,11 @@ package com.wafflestudio.csereal.core.member.database +import com.querydsl.core.types.dsl.Expressions +import com.querydsl.jpa.impl.JPAQuery import com.querydsl.jpa.impl.JPAQueryFactory +import com.wafflestudio.csereal.core.member.database.QMemberSearchEntity.memberSearchEntity +import com.wafflestudio.csereal.core.member.database.QProfessorEntity.professorEntity +import com.wafflestudio.csereal.core.member.database.QStaffEntity.staffEntity import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository @@ -9,11 +14,78 @@ interface MemberSearchRepository } interface MemberSearchRepositoryCustom { - + fun searchTopMember(keyword: String, number: Int): List + fun searchMember(keyword: String, pageSize: Int, pageNum: Int): Pair, Long> } @Repository class MemberSearchRepositoryCustomImpl ( private val queryFactory: JPAQueryFactory, ): MemberSearchRepositoryCustom { + + override fun searchTopMember(keyword: String, number: Int): List { + return searchQuery(keyword) + .limit(number.toLong()) + .fetch() + } + + override fun searchMember(keyword: String, pageSize: Int, pageNum: Int): Pair, Long> { + val query = searchQuery(keyword) + val total = getSearchCount(keyword) + + val validPageNum = exchangePageNum(pageSize, pageNum, total) + val queryResult = query + .offset((validPageNum-1) * pageSize.toLong()) + .limit(pageSize.toLong()) + .fetch() + + return queryResult to total + } + + fun searchFullTextTemplate(keyword: String) = + Expressions.numberTemplate( + Double::class.javaObjectType, + "function('match',{0},{1})", + memberSearchEntity.content, + keyword + ) + + fun searchQuery(keyword: String): JPAQuery { + val searchDoublTemplate = searchFullTextTemplate(keyword) + + return queryFactory.select( + memberSearchEntity + ).from( + memberSearchEntity + ).leftJoin( + memberSearchEntity.professor, professorEntity + ).fetchJoin() + .leftJoin( + memberSearchEntity.staff, staffEntity + ).fetchJoin() + .where( + searchDoublTemplate.gt(0.0) + ) + } + + fun getSearchCount(keyword: String): Long { + val searchDoubleTemplate = searchFullTextTemplate(keyword) + + return queryFactory.select( + memberSearchEntity + .countDistinct() + ).from( + memberSearchEntity + ).where( + searchDoubleTemplate.gt(0.0) + ).fetchOne()!! + } + + fun exchangePageNum(pageSize: Int, pageNum: Int, total: Long): Int { + return if ((pageNum - 1) * pageSize < total) { + pageNum + } else { + Math.ceil(total.toDouble() / pageSize).toInt() + } + } } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchPageResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchPageResponse.kt new file mode 100644 index 00000000..430d9024 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchPageResponse.kt @@ -0,0 +1,20 @@ +package com.wafflestudio.csereal.core.member.dto + +import com.wafflestudio.csereal.core.member.database.MemberSearchEntity +import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity + +data class MemberSearchPageResponse( + val members: List, + val total: Long, +) { + companion object { + fun of( + members: List, + total: Long, + imageURLMaker: (MainImageEntity?) -> String? + ) = MemberSearchPageResponse( + members = members.map { MemberSearchResponseElement.of(it, imageURLMaker) }, + total = total, + ) + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchResponseElement.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchResponseElement.kt new file mode 100644 index 00000000..a79fe255 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchResponseElement.kt @@ -0,0 +1,42 @@ +package com.wafflestudio.csereal.core.member.dto + +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.member.database.MemberSearchEntity +import com.wafflestudio.csereal.core.member.database.MemberSearchType +import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity + +data class MemberSearchResponseElement( + val id: Long, + val name: String, + val academicRankOrRole: String, + val imageURL: String?, + val memberType: MemberSearchType, +) { + companion object { + fun of( + memberSearch: MemberSearchEntity, + imageURLMaker: (MainImageEntity?) -> String? + ): MemberSearchResponseElement = + when { + memberSearch.professor != null && memberSearch.staff == null -> + MemberSearchResponseElement( + id = memberSearch.professor!!.id, + name = memberSearch.professor!!.name, + academicRankOrRole = memberSearch.professor!!.academicRank, + imageURL = imageURLMaker(memberSearch.professor!!.mainImage), + memberType = MemberSearchType.PROFESSOR + ) + memberSearch.professor == null && memberSearch.staff != null -> + MemberSearchResponseElement( + id = memberSearch.staff!!.id, + name = memberSearch.staff!!.name, + academicRankOrRole = memberSearch.staff!!.role, + imageURL = imageURLMaker(memberSearch.staff!!.mainImage), + memberType = MemberSearchType.STAFF + ) + else -> throw CserealException.Csereal401( + "MemberSearchEntity는 professor 혹은 staff 중 하나와만 연결되어있어야 합니다." + ) + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchTopResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchTopResponse.kt new file mode 100644 index 00000000..d60b0d55 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchTopResponse.kt @@ -0,0 +1,19 @@ +package com.wafflestudio.csereal.core.member.dto + +import com.wafflestudio.csereal.core.member.database.MemberSearchEntity +import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity + +data class MemberSearchTopResponse( + val topMembers: List +) { + companion object { + fun of( + topMembers: List, + imageURLMaker: (MainImageEntity?) -> String? + ) = MemberSearchTopResponse( + topMembers = topMembers.map { + MemberSearchResponseElement.of(it, imageURLMaker) + } + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/MemberSearchService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/MemberSearchService.kt index 02b09546..4d8cb40e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/MemberSearchService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/MemberSearchService.kt @@ -1,16 +1,32 @@ package com.wafflestudio.csereal.core.member.service import com.wafflestudio.csereal.core.member.database.MemberSearchRepository -import com.wafflestudio.csereal.core.member.database.ProfessorEntity -import jakarta.transaction.Transactional +import com.wafflestudio.csereal.core.member.dto.MemberSearchPageResponse +import com.wafflestudio.csereal.core.member.dto.MemberSearchResponseElement +import com.wafflestudio.csereal.core.member.dto.MemberSearchTopResponse +import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional interface MemberSearchService { - + fun searchTopMember(keyword: String, number: Int): MemberSearchTopResponse + fun searchMember(keyword: String, pageSize: Int, pageNum: Int): MemberSearchPageResponse } @Service class MemberSearchServiceImpl ( private val memberSearchRepository: MemberSearchRepository, + private val mainImageService: MainImageService, ): MemberSearchService { + @Transactional(readOnly = true) + override fun searchTopMember(keyword: String, number: Int): MemberSearchTopResponse { + val entityResults = memberSearchRepository.searchTopMember(keyword, number) + return MemberSearchTopResponse.of(entityResults, mainImageService::createImageURL) + } + + @Transactional(readOnly = true) + override fun searchMember(keyword: String, pageSize: Int, pageNum: Int): MemberSearchPageResponse { + val (entityResults, total) = memberSearchRepository.searchMember(keyword, pageSize, pageNum) + return MemberSearchPageResponse.of(entityResults, total, mainImageService::createImageURL) + } } \ No newline at end of file diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index a316b261..aa951515 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -48,6 +48,9 @@ spring: ddl-auto: update show-sql: true open-in-view: false + properties: + hibernate: + dialect: com.wafflestudio.csereal.common.config.MySQLDialectCustom security: oauth2: client: @@ -79,6 +82,9 @@ spring: hibernate: ddl-auto: update # TODO: change to validate (or none) when save actual data to server open-in-view: false + properties: + hibernate: + dialect: com.wafflestudio.csereal.common.config.MySQLDialectCustom security: oauth2: client: @@ -112,6 +118,9 @@ spring: open-in-view: false hibernate: ddl-auto: create-drop + properties: + hibernate: + dialect: org.hibernate.dialect.H2Dialect h2: console: enabled: true From cba2428fc2dcc7d8cdbd08f09d4b8ba48a258b2c Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Mon, 11 Sep 2023 18:28:59 +0900 Subject: [PATCH 067/124] =?UTF-8?q?fix:=20=EC=84=B8=EB=AF=B8=EB=82=98=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=20=ED=83=80=EC=9E=85=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?(#91)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 세미나 시간 LocalDateTime으로 변경 * test: 테스트 수정 --- .../core/seminar/database/SeminarEntity.kt | 5 +- .../seminar/database/SeminarRepository.kt | 9 +- .../csereal/core/seminar/dto/SeminarDto.kt | 4 +- .../core/seminar/dto/SeminarSearchDto.kt | 5 +- .../seminar/service/SeminarServiceTest.kt | 113 +++++++++--------- 5 files changed, 67 insertions(+), 69 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt index d5d63b6b..264c13d6 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt @@ -8,6 +8,7 @@ import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEnti import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity import com.wafflestudio.csereal.core.seminar.dto.SeminarDto import jakarta.persistence.* +import java.time.LocalDateTime @Entity(name = "seminar") class SeminarEntity( @@ -35,8 +36,8 @@ class SeminarEntity( var affiliation: String, var affiliationURL: String?, - var startDate: String?, - var endDate: String?, + var startDate: LocalDateTime?, + var endDate: LocalDateTime?, var location: String, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt index 46bb4981..4043fd4b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt @@ -4,7 +4,6 @@ import com.querydsl.core.BooleanBuilder import com.querydsl.jpa.impl.JPAQueryFactory import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.common.utils.FixedPageRequest -import com.wafflestudio.csereal.common.utils.cleanTextFromHtml import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService import com.wafflestudio.csereal.core.seminar.database.QSeminarEntity.seminarEntity import com.wafflestudio.csereal.core.seminar.dto.SeminarSearchDto @@ -75,11 +74,7 @@ class SeminarRepositoryImpl( var isYearLast = false if (i == seminarEntityList.size - 1) { isYearLast = true - } else if (seminarEntityList[i].startDate?.substring(0, 4) != seminarEntityList[i + 1].startDate?.substring( - 0, - 4 - ) - ) { + } else if (seminarEntityList[i].startDate?.year != seminarEntityList[i + 1].startDate?.year) { isYearLast = true } @@ -100,6 +95,6 @@ class SeminarRepositoryImpl( ) } - return SeminarSearchResponse(total!!, seminarSearchDtoList) + return SeminarSearchResponse(total, seminarSearchDtoList) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt index 91c58b78..2855ec2a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt @@ -15,8 +15,8 @@ data class SeminarDto( val speakerTitle: String?, val affiliation: String, val affiliationURL: String?, - val startDate: String?, - val endDate: String?, + val startDate: LocalDateTime?, + val endDate: LocalDateTime?, val location: String, val host: String?, val additionalNote: String?, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarSearchDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarSearchDto.kt index 483f8513..6bd7c055 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarSearchDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarSearchDto.kt @@ -1,6 +1,7 @@ package com.wafflestudio.csereal.core.seminar.dto import com.querydsl.core.annotations.QueryProjection +import java.time.LocalDateTime data class SeminarSearchDto @QueryProjection constructor( val id: Long, @@ -8,9 +9,9 @@ data class SeminarSearchDto @QueryProjection constructor( val description: String, val name: String, val affiliation: String, - val startDate: String?, + val startDate: LocalDateTime?, val location: String, val imageURL: String?, val isYearLast: Boolean, ) { -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarServiceTest.kt index 0ff9573a..64359c52 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarServiceTest.kt @@ -9,13 +9,14 @@ import io.kotest.matchers.shouldNotBe import jakarta.transaction.Transactional import org.springframework.boot.test.context.SpringBootTest import org.springframework.data.repository.findByIdOrNull +import java.time.LocalDateTime @SpringBootTest @Transactional -class SeminarServiceTest ( +class SeminarServiceTest( private val seminarService: SeminarService, private val seminarRepository: SeminarRepository, -): BehaviorSpec() { +) : BehaviorSpec() { init { beforeContainer { @@ -27,42 +28,42 @@ class SeminarServiceTest ( Given("세미나를 생성하려고 할 때") { val seminarDTO = SeminarDto( - id = -1, - title = "title", - description = """ + id = -1, + title = "title", + description = """

Hello, World!

This is seminar description.

Goodbye, World!

""".trimIndent(), - introduction = """ + introduction = """

Hello, World!

This is seminar introduction.

Goodbye, World!

""".trimIndent(), - name = "name", - speakerURL = "speakerURL", - speakerTitle = "speakerTitle", - affiliation = "affiliation", - affiliationURL = "affiliationURL", - startDate = "startDate", - endDate = "endDate", - location = "location", - host = "host", - additionalNote = """ + name = "name", + speakerURL = "speakerURL", + speakerTitle = "speakerTitle", + affiliation = "affiliation", + affiliationURL = "affiliationURL", + startDate = LocalDateTime.now(), + endDate = LocalDateTime.now(), + location = "location", + host = "host", + additionalNote = """

Hello, World!

This is seminar additionalNote.

Goodbye, World!

""".trimIndent(), - createdAt = null, - modifiedAt = null, - isPrivate = false, - isImportant = false, - prevId = null, - prevTitle = null, - nextId = null, - nextTitle = null, - imageURL = null, - attachments = null + createdAt = null, + modifiedAt = null, + isPrivate = false, + isImportant = false, + prevId = null, + prevTitle = null, + nextId = null, + nextTitle = null, + imageURL = null, + attachments = null ) When("간단한 세미나 DTO가 주어지면") { val resultSeminarDTO = seminarService.createSeminar(seminarDTO, null, null) @@ -83,58 +84,58 @@ class SeminarServiceTest ( Given("기존 간단한 세미나의 Description을 수정하려고 할 때") { val originalSeminar = seminarRepository.save( - SeminarEntity( - title = "title", - description = """ + SeminarEntity( + title = "title", + description = """

Hello, World!

This is seminar description.

Goodbye, World!

""".trimIndent(), - plainTextDescription = "Hello, World! This is seminar description. Goodbye, World!", - introduction = """ + plainTextDescription = "Hello, World! This is seminar description. Goodbye, World!", + introduction = """

Hello, World!

This is seminar introduction.

Goodbye, World!

""".trimIndent(), - plainTextIntroduction = "Hello, World! This is seminar introduction. Goodbye, World!", - name = "name", - speakerURL = "speakerURL", - speakerTitle = "speakerTitle", - affiliation = "affiliation", - affiliationURL = "affiliationURL", - startDate = "startDate", - endDate = "endDate", - location = "location", - host = "host", - additionalNote = """ + plainTextIntroduction = "Hello, World! This is seminar introduction. Goodbye, World!", + name = "name", + speakerURL = "speakerURL", + speakerTitle = "speakerTitle", + affiliation = "affiliation", + affiliationURL = "affiliationURL", + startDate = LocalDateTime.now(), + endDate = LocalDateTime.now(), + location = "location", + host = "host", + additionalNote = """

Hello, World!

This is seminar additionalNote.

Goodbye, World!

""".trimIndent(), - plainTextAdditionalNote = "Hello, World! This is seminar additionalNote. Goodbye, World!", - isPrivate = false, - isImportant = false, - ) + plainTextAdditionalNote = "Hello, World! This is seminar additionalNote. Goodbye, World!", + isPrivate = false, + isImportant = false, + ) ) val originalId = originalSeminar.id When("수정된 DTO를 이용하여 수정하면") { val modifiedSeminarDTO = SeminarDto.of( - originalSeminar, null, emptyList(), null + originalSeminar, null, emptyList(), null ).copy( - description = """ + description = """

Hello, World!

This is modified seminar description.

Goodbye, World!

And this is a new line.

""".trimIndent(), - introduction = """ + introduction = """

Hello, World!

This is modified seminar introduction.

Goodbye, World!

And this is a new line.

""".trimIndent(), - additionalNote = """ + additionalNote = """

Hello, World!

This is modified seminar additionalNote.

Goodbye, World!

@@ -143,11 +144,11 @@ class SeminarServiceTest ( ) val modifiedSeminarDto = seminarService.updateSeminar( - originalSeminar.id, - modifiedSeminarDTO, - null, - null, - emptyList() + originalSeminar.id, + modifiedSeminarDTO, + null, + null, + emptyList() ) Then("같은 Entity가 수정되어야 한다.") { @@ -165,4 +166,4 @@ class SeminarServiceTest ( } } } -} \ No newline at end of file +} From 0a57a08c22a51061541ffe3be14cb60f2e4a3b84 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Mon, 11 Sep 2023 18:39:35 +0900 Subject: [PATCH 068/124] =?UTF-8?q?fix:=20=ED=83=9C=EA=B7=B8=20=ED=95=AD?= =?UTF-8?q?=EB=AA=A9=20=EC=88=98=EC=A0=95=20(#92)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../csereal/core/notice/database/TagInNoticeEnum.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeEnum.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeEnum.kt index a15c56ab..a661a130 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeEnum.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeEnum.kt @@ -4,11 +4,11 @@ import com.wafflestudio.csereal.common.CserealException enum class TagInNoticeEnum { CLASS, SCHOLARSHIP, UNDERGRADUATE, GRADUATE, MINOR, REGISTRATIONS, ADMISSIONS, GRADUATIONS, - RECRUIT, STUDENT_EXCHANGE, EVENTS_PROGRAMS, FOREIGN; + RECRUIT, STUDENT_EXCHANGE, INNER_EVENTS_PROGRAMS, OUTER_EVENTS_PROGRAMS, FOREIGN; companion object { - fun getTagEnum(t: String) : TagInNoticeEnum{ - return when(t) { + fun getTagEnum(t: String): TagInNoticeEnum { + return when (t) { "수업" -> CLASS "장학" -> SCHOLARSHIP "학사(학부)" -> UNDERGRADUATE @@ -19,7 +19,8 @@ enum class TagInNoticeEnum { "졸업" -> GRADUATIONS "채용정보" -> RECRUIT "교환학생/유학" -> STUDENT_EXCHANGE - "행사/프로그램" -> EVENTS_PROGRAMS + "내부행사/프로그램" -> INNER_EVENTS_PROGRAMS + "외부행사/프로그램" -> OUTER_EVENTS_PROGRAMS "foreign" -> FOREIGN else -> throw CserealException.Csereal404("태그를 찾을 수 없습니다") } From e37b804afeeef5e7740d041c8f717c899c780814 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Mon, 11 Sep 2023 18:49:37 +0900 Subject: [PATCH 069/124] =?UTF-8?q?fix:=20news=20tag=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20(#90)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: news tag 추가 * fix: 일반인은 isPrivate = false인 것만, 직원은 모든 게시글 보여주기 --- .../core/admin/database/AdminRepository.kt | 5 --- .../core/news/database/NewsRepository.kt | 7 ++-- .../core/news/database/TagInNewsEntity.kt | 5 ++- .../core/news/database/TagInNewsEnum.kt | 40 +++++++++++++++++++ ...wsRepository.kt => TagInNewsRepository.kt} | 2 +- .../csereal/core/news/dto/NewsDto.kt | 3 +- .../csereal/core/news/service/NewsService.kt | 25 ++++++------ .../core/notice/database/NoticeRepository.kt | 34 +++++++++++++++- .../core/notice/service/NoticeService.kt | 6 +-- 9 files changed, 99 insertions(+), 28 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/news/database/TagInNewsEnum.kt rename src/main/kotlin/com/wafflestudio/csereal/core/news/database/{TageInNewsRepository.kt => TagInNewsRepository.kt} (75%) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/database/AdminRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/database/AdminRepository.kt index f47c1092..236a80c9 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admin/database/AdminRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/database/AdminRepository.kt @@ -9,7 +9,6 @@ import org.springframework.stereotype.Component interface AdminRepository { fun readAllSlides(pageNum: Long): List - fun readAllImportants(pageNum: Long): List } @Component @@ -31,8 +30,4 @@ class AdminRepositoryImpl( .limit(40) .fetch() } - - override fun readAllImportants(pageNum: Long): List { - TODO("Not yet implemented") - } } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt index 0246b586..eff46859 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt @@ -54,13 +54,14 @@ class NewsRepositoryImpl( } if (!tag.isNullOrEmpty()) { tag.forEach { + val tagEnum = TagInNewsEnum.getTagEnum(it) tagsBooleanBuilder.or( - newsTagEntity.tag.name.eq(it) + newsTagEntity.tag.name.eq(tagEnum) ) } } - val jpaQuery = queryFactory.select(newsEntity).from(newsEntity) + val jpaQuery = queryFactory.selectFrom(newsEntity) .leftJoin(newsTagEntity).on(newsTagEntity.news.eq(newsEntity)) .where(newsEntity.isDeleted.eq(false)) .where(keywordBooleanBuilder).where(tagsBooleanBuilder) @@ -90,7 +91,7 @@ class NewsRepositoryImpl( title = it.title, description = it.plainTextDescription, createdAt = it.createdAt, - tags = it.newsTags.map { newsTagEntity -> newsTagEntity.tag.name }, + tags = it.newsTags.map { newsTagEntity -> TagInNewsEnum.getTagString(newsTagEntity.tag.name) }, imageURL = imageURL ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/TagInNewsEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/TagInNewsEntity.kt index 16487e95..46331249 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/TagInNewsEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/TagInNewsEntity.kt @@ -2,11 +2,14 @@ package com.wafflestudio.csereal.core.news.database import com.wafflestudio.csereal.common.config.BaseTimeEntity import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated import jakarta.persistence.OneToMany @Entity(name = "tag_in_news") class TagInNewsEntity( - var name: String, + @Enumerated(EnumType.STRING) + var name: TagInNewsEnum, @OneToMany(mappedBy = "tag") val newsTags: MutableSet = mutableSetOf() diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/TagInNewsEnum.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/TagInNewsEnum.kt new file mode 100644 index 00000000..2e5749a7 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/TagInNewsEnum.kt @@ -0,0 +1,40 @@ +package com.wafflestudio.csereal.core.news.database + +import com.wafflestudio.csereal.common.CserealException + +enum class TagInNewsEnum { + EVENT, RESEARCH, AWARDS, RECRUIT, COLUMN, LECTURE, EDUCATION, INTERVIEW, CAREER, UNCLASSIFIED; + + companion object { + fun getTagEnum(t: String) : TagInNewsEnum { + return when (t) { + "행사" -> EVENT + "연구" -> RESEARCH + "수상" -> AWARDS + "채용" -> RECRUIT + "칼럼" -> COLUMN + "강연" -> LECTURE + "교육" -> EDUCATION + "인터뷰" -> INTERVIEW + "진로" -> CAREER + "과거 미분류" -> UNCLASSIFIED + else -> throw CserealException.Csereal404("태그를 찾을 수 없습니다") + } + } + + fun getTagString(t: TagInNewsEnum): String { + return when (t) { + EVENT -> "행사" + RESEARCH -> "연구" + AWARDS -> "수상" + RECRUIT -> "채용" + COLUMN -> "칼럼" + LECTURE -> "강연" + EDUCATION -> "교육" + INTERVIEW -> "인터뷰" + CAREER -> "진로" + UNCLASSIFIED -> "과거 미분류" + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/TageInNewsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/TagInNewsRepository.kt similarity index 75% rename from src/main/kotlin/com/wafflestudio/csereal/core/news/database/TageInNewsRepository.kt rename to src/main/kotlin/com/wafflestudio/csereal/core/news/database/TagInNewsRepository.kt index db4e2c6f..fea69c4d 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/TageInNewsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/TagInNewsRepository.kt @@ -3,5 +3,5 @@ package com.wafflestudio.csereal.core.news.database import org.springframework.data.jpa.repository.JpaRepository interface TagInNewsRepository : JpaRepository { - fun findByName(tagName: String): TagInNewsEntity? + fun findByName(tagName: TagInNewsEnum): TagInNewsEntity } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt index bf707a83..76b0ee6f 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt @@ -1,6 +1,7 @@ package com.wafflestudio.csereal.core.news.dto import com.wafflestudio.csereal.core.news.database.NewsEntity +import com.wafflestudio.csereal.core.news.database.TagInNewsEnum import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse import java.time.LocalDateTime @@ -33,7 +34,7 @@ data class NewsDto( id = this.id, title = this.title, description = this.description, - tags = this.newsTags.map { it.tag.name }, + tags = this.newsTags.map { TagInNewsEnum.getTagString(it.tag.name) }, createdAt = this.createdAt, modifiedAt = this.modifiedAt, isPrivate = this.isPrivate, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt index 333ce35c..d4d2cbce 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt @@ -65,9 +65,10 @@ class NewsServiceImpl( override fun createNews(request: NewsDto, mainImage: MultipartFile?, attachments: List?): NewsDto { val newNews = NewsEntity.of(request) - for (tagName in request.tags) { - val tag = tagInNewsRepository.findByName(tagName) ?: throw CserealException.Csereal404("해당하는 태그가 없습니다") - NewsTagEntity.createNewsTag(newNews, tag) + for (tag in request.tags) { + val tagEnum = TagInNewsEnum.getTagEnum(tag) + val tagEntity = tagInNewsRepository.findByName(tagEnum) + NewsTagEntity.createNewsTag(newNews, tagEntity) } if (mainImage != null) { @@ -113,18 +114,18 @@ class NewsServiceImpl( val oldTags = news.newsTags.map { it.tag.name } - val tagsToRemove = oldTags - request.tags - val tagsToAdd = request.tags - oldTags + val tagsToRemove = oldTags - request.tags.map { TagInNewsEnum.getTagEnum(it) } + val tagsToAdd = request.tags.map { TagInNewsEnum.getTagEnum(it) } - oldTags - for (tagName in tagsToRemove) { - val tagId = tagInNewsRepository.findByName(tagName)!!.id - news.newsTags.removeIf { it.tag.name == tagName } + for (tagEnum in tagsToRemove) { + val tagId = tagInNewsRepository.findByName(tagEnum).id + news.newsTags.removeIf { it.tag.name == tagEnum } newsTagRepository.deleteByNewsIdAndTagId(newsId, tagId) } - for (tagName in tagsToAdd) { - val tag = tagInNewsRepository.findByName(tagName) ?: throw CserealException.Csereal404("해당하는 태그가 없습니다") - NewsTagEntity.createNewsTag(news, tag) + for (tagEnum in tagsToAdd) { + val tagId = tagInNewsRepository.findByName(tagEnum) + NewsTagEntity.createNewsTag(news, tagId) } val imageURL = mainImageService.createImageURL(news.mainImage) @@ -143,7 +144,7 @@ class NewsServiceImpl( override fun enrollTag(tagName: String) { val newTag = TagInNewsEntity( - name = tagName + name = TagInNewsEnum.getTagEnum(tagName) ) tagInNewsRepository.save(newTag) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt index c95daf5e..cf24f438 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt @@ -8,10 +8,17 @@ import com.wafflestudio.csereal.core.notice.database.QNoticeEntity.noticeEntity import com.wafflestudio.csereal.core.notice.database.QNoticeTagEntity.noticeTagEntity import com.wafflestudio.csereal.core.notice.dto.NoticeSearchDto import com.wafflestudio.csereal.core.notice.dto.NoticeSearchResponse +import com.wafflestudio.csereal.core.user.database.Role +import com.wafflestudio.csereal.core.user.database.UserEntity +import com.wafflestudio.csereal.core.user.database.UserRepository import org.springframework.data.domain.PageRequest import org.springframework.data.domain.Pageable import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.security.oauth2.core.oidc.user.OidcUser import org.springframework.stereotype.Component +import org.springframework.web.context.request.RequestAttributes +import org.springframework.web.context.request.RequestContextHolder import java.time.LocalDateTime import kotlin.math.ceil @@ -33,6 +40,7 @@ interface CustomNoticeRepository { @Component class NoticeRepositoryImpl( private val queryFactory: JPAQueryFactory, + private val userRepository: UserRepository, ) : CustomNoticeRepository { override fun searchNotice( tag: List?, @@ -40,8 +48,25 @@ class NoticeRepositoryImpl( pageable: Pageable, usePageBtn: Boolean ): NoticeSearchResponse { + var user = RequestContextHolder.getRequestAttributes()?.getAttribute( + "loggedInUser", + RequestAttributes.SCOPE_REQUEST + ) as UserEntity? + + if (user == null) { + val oidcUser = SecurityContextHolder.getContext().authentication.principal as OidcUser + val username = oidcUser.idToken.getClaim("username") + + if(userRepository.findByUsername(username) == null) { + user = null + } else { + user = userRepository.findByUsername(username) + } + } + val keywordBooleanBuilder = BooleanBuilder() val tagsBooleanBuilder = BooleanBuilder() + val isPrivateBooleanBuilder = BooleanBuilder() if (!keyword.isNullOrEmpty()) { val keywordList = keyword.split("[^a-zA-Z0-9가-힣]".toRegex()) @@ -66,10 +91,16 @@ class NoticeRepositoryImpl( } } + if(user?.role != Role.ROLE_STAFF) { + isPrivateBooleanBuilder.or( + noticeEntity.isPrivate.eq(false) + ) + } + val jpaQuery = queryFactory.selectFrom(noticeEntity) .leftJoin(noticeTagEntity).on(noticeTagEntity.notice.eq(noticeEntity)) .where(noticeEntity.isDeleted.eq(false)) - .where(keywordBooleanBuilder, tagsBooleanBuilder) + .where(keywordBooleanBuilder, tagsBooleanBuilder, isPrivateBooleanBuilder) val total: Long var pageRequest = pageable @@ -101,7 +132,6 @@ class NoticeRepositoryImpl( hasAttachment = hasAttachment ) } - return NoticeSearchResponse(total, noticeSearchDtoList) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt index d8fca1d5..9c9ae04e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt @@ -131,14 +131,14 @@ class NoticeServiceImpl( val tagsToAdd = request.tags.map { TagInNoticeEnum.getTagEnum(it) } - oldTags for (tagEnum in tagsToRemove) { - val tagId = tagInNoticeRepository.findByName(tagEnum)!!.id + val tagId = tagInNoticeRepository.findByName(tagEnum).id notice.noticeTags.removeIf { it.tag.name == tagEnum } noticeTagRepository.deleteByNoticeIdAndTagId(noticeId, tagId) } for (tagEnum in tagsToAdd) { - val tag = tagInNoticeRepository.findByName(tagEnum) ?: throw CserealException.Csereal404("해당하는 태그가 없습니다") - NoticeTagEntity.createNoticeTag(notice, tag) + val tagId = tagInNoticeRepository.findByName(tagEnum) + NoticeTagEntity.createNoticeTag(notice, tagId) } val attachmentResponses = attachmentService.createAttachmentResponses(notice.attachments) From 944250b56a690f581e77614b1a44284968489222 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Mon, 11 Sep 2023 19:33:02 +0900 Subject: [PATCH 070/124] =?UTF-8?q?feat:=20=EC=A1=B8=EC=97=85=EC=83=9D=20?= =?UTF-8?q?=EC=B0=BD=EC=97=85=20=EC=A7=84=EB=A1=9C,=20=EC=A1=B8=EC=97=85?= =?UTF-8?q?=EC=83=9D=20=EC=A7=84=EB=A1=9C=20response=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20(#94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: news tag 추가 * fix: 일반인은 isPrivate = false인 것만, 직원은 모든 게시글 보여주기 * feat: about 졸업생 read 추가 * fix: 오타 수정 --- .../csereal/core/about/api/AboutController.kt | 6 ++ .../core/about/database/CompanyEntity.kt | 12 ++++ .../core/about/database/CompanyRepository.kt | 7 ++ .../csereal/core/about/database/StatEntity.kt | 21 ++++++ .../core/about/database/StatRepository.kt | 7 ++ .../csereal/core/about/dto/CompanyDto.kt | 8 +++ .../core/about/dto/CompanyNameAndCountDto.kt | 8 +++ .../core/about/dto/FutureCareersPage.kt | 8 +++ .../csereal/core/about/dto/StatDto.kt | 9 +++ .../core/about/service/AboutService.kt | 66 +++++++++++++++++-- 10 files changed, 147 insertions(+), 5 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/database/CompanyEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/database/CompanyRepository.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/database/StatEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/database/StatRepository.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/dto/CompanyDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/dto/CompanyNameAndCountDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersPage.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/dto/StatDto.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt index 183e4a5e..22c92fb8 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt @@ -1,6 +1,8 @@ package com.wafflestudio.csereal.core.about.api import com.wafflestudio.csereal.core.about.dto.AboutDto +import com.wafflestudio.csereal.core.about.dto.CompanyDto +import com.wafflestudio.csereal.core.about.dto.FutureCareersPage import com.wafflestudio.csereal.core.about.service.AboutService import jakarta.validation.Valid import org.springframework.http.ResponseEntity @@ -50,5 +52,9 @@ class AboutController( fun readAllDirections() : ResponseEntity> { return ResponseEntity.ok(aboutService.readAllDirections()) } + @GetMapping("/future-careers") + fun readFutureCareers(): ResponseEntity { + return ResponseEntity.ok(aboutService.readFutureCareers()) + } } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/CompanyEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/CompanyEntity.kt new file mode 100644 index 00000000..e47afbaa --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/CompanyEntity.kt @@ -0,0 +1,12 @@ +package com.wafflestudio.csereal.core.about.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import jakarta.persistence.Entity + +@Entity(name = "company") +class CompanyEntity( + var name: String, + var url: String, + var year: Int, +) : BaseTimeEntity() { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/CompanyRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/CompanyRepository.kt new file mode 100644 index 00000000..a408c8a8 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/CompanyRepository.kt @@ -0,0 +1,7 @@ +package com.wafflestudio.csereal.core.about.database + +import org.springframework.data.jpa.repository.JpaRepository + +interface CompanyRepository: JpaRepository { + fun findAllByOrderByYearDesc(): List +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/StatEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/StatEntity.kt new file mode 100644 index 00000000..db433127 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/StatEntity.kt @@ -0,0 +1,21 @@ +package com.wafflestudio.csereal.core.about.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated + +@Entity(name = "stat") +class StatEntity( + var year: Int, + + @Enumerated(EnumType.STRING) + var degree: Degree, + var name: String, + var count: Int, +): BaseTimeEntity() { +} + +enum class Degree { + BACHELOR, MASTER, DOCTOR +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/StatRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/StatRepository.kt new file mode 100644 index 00000000..a34d825d --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/StatRepository.kt @@ -0,0 +1,7 @@ +package com.wafflestudio.csereal.core.about.database + +import org.springframework.data.jpa.repository.JpaRepository + +interface StatRepository: JpaRepository { + fun findAllByYearAndDegree(year: Int, degree: Degree): List +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/CompanyDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/CompanyDto.kt new file mode 100644 index 00000000..1cc30403 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/CompanyDto.kt @@ -0,0 +1,8 @@ +package com.wafflestudio.csereal.core.about.dto + +data class CompanyDto( + val name: String, + val url: String, + val year: Int, +) { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/CompanyNameAndCountDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/CompanyNameAndCountDto.kt new file mode 100644 index 00000000..b449193c --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/CompanyNameAndCountDto.kt @@ -0,0 +1,8 @@ +package com.wafflestudio.csereal.core.about.dto + +data class CompanyNameAndCountDto( + val id: Long, + val name: String, + val count: Int +) { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersPage.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersPage.kt new file mode 100644 index 00000000..656c26c9 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersPage.kt @@ -0,0 +1,8 @@ +package com.wafflestudio.csereal.core.about.dto + +data class FutureCareersPage( + val description: String, + val stat: List, + val companies: List +) { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/StatDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/StatDto.kt new file mode 100644 index 00000000..45e53137 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/StatDto.kt @@ -0,0 +1,9 @@ +package com.wafflestudio.csereal.core.about.dto + +data class StatDto( + val year: Int, + val bachelor: List, + val master: List, + val doctor: List +) { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt index f973a5c5..9424d73b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt @@ -1,11 +1,8 @@ package com.wafflestudio.csereal.core.about.service import com.wafflestudio.csereal.common.CserealException -import com.wafflestudio.csereal.core.about.database.AboutEntity -import com.wafflestudio.csereal.core.about.database.AboutPostType -import com.wafflestudio.csereal.core.about.database.AboutRepository -import com.wafflestudio.csereal.core.about.database.LocationEntity -import com.wafflestudio.csereal.core.about.dto.AboutDto +import com.wafflestudio.csereal.core.about.database.* +import com.wafflestudio.csereal.core.about.dto.* import com.wafflestudio.csereal.core.resource.attachment.service.AttachmentService import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService import org.springframework.stereotype.Service @@ -18,11 +15,15 @@ interface AboutService { fun readAllClubs() : List fun readAllFacilities() : List fun readAllDirections(): List + fun readFutureCareers(): FutureCareersPage + } @Service class AboutServiceImpl( private val aboutRepository: AboutRepository, + private val companyRepository: CompanyRepository, + private val statRepository: StatRepository, private val mainImageService: MainImageService, private val attachmentService: AttachmentService, ) : AboutService { @@ -96,6 +97,61 @@ class AboutServiceImpl( return directions } + @Transactional + override fun readFutureCareers(): FutureCareersPage { + val description = "컴퓨터공학을 전공함으로써 벤처기업을 창업할 수 있을 뿐 " + + "아니라 시스템엔지니어, 보안전문가, 소프트웨어개발자, 데이터베이스관리자 등 " + + "많은 IT 전문 분야로의 진출이 가능하다. 또한 컴퓨터공학은 바이오, 전자전기, " + + "로봇, 기계, 의료 등 이공계 영역뿐만 아니라 정치, 경제, 사회, 문화의 다양한 분야와 " + + "결합되어 미래 지식정보사회에 대한 새로운 가능성을 제시하고 있고 새로운 학문적 과제가 " + + "지속적으로 생산되기 때문에 많은 전문연구인력이 필요하다.\n" + + "\n" + + "서울대학교 컴퓨터공학부의 경우 학부 졸업생 절반 이상이 대학원에 진학하고 있다. " + + "대학원에 진학하면 여러 전공분야 중 하나를 선택하여 보다 깊이 있는 지식의 습득과 연구과정을 거치게 되며 " + + "그 이후로는 국내외 관련 산업계, 학계에 주로 진출하고 있고, 새로운 아이디어로 벤처기업을 창업하기도 한다." + + val statList = mutableListOf() + for(i: Int in 2021 downTo 2011) { + val bachelor = statRepository.findAllByYearAndDegree(i, Degree.BACHELOR).map { + CompanyNameAndCountDto( + id = it.id, + name = it.name, + count = it.count + ) + } + val master = statRepository.findAllByYearAndDegree(i, Degree.MASTER).map { + CompanyNameAndCountDto( + id = it.id, + name = it.name, + count = it.count, + ) + } + val doctor = statRepository.findAllByYearAndDegree(i, Degree.DOCTOR).map { + CompanyNameAndCountDto( + id = it.id, + name = it.name, + count = it.count, + ) + } + statList.add( + StatDto( + year = i, + bachelor = bachelor, + master = master, + doctor = doctor, + ) + ) + } + val companyList = companyRepository.findAllByOrderByYearDesc().map { + CompanyDto( + name = it.name, + url = it.url, + year = it.year + ) + } + return FutureCareersPage(description, statList, companyList) + } + private fun makeStringToEnum(postType: String) : AboutPostType { try { val upperPostType = postType.replace("-","_").uppercase() From a5b69d424bf0dac8079bda903b9c2002c0db0a1d Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Mon, 11 Sep 2023 19:46:02 +0900 Subject: [PATCH 071/124] =?UTF-8?q?fix:=20conference=20=EC=A3=BC=EC=86=8C?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20(#95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../csereal/core/conference/api/ConferenceController.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/conference/api/ConferenceController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/conference/api/ConferenceController.kt index 7c78df5d..75a3cde7 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/conference/api/ConferenceController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/conference/api/ConferenceController.kt @@ -7,7 +7,7 @@ import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -@RequestMapping("/conference") +@RequestMapping("/api/v1/conference") @RestController class ConferenceController( private val conferenceService: ConferenceService From 2bbcb2a06c78f19c7cc06cb094b0b31fda14ed49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=ED=98=81=EC=A4=80=20=28HyukJoon=20Woo=29?= Date: Tue, 12 Sep 2023 13:15:53 +0900 Subject: [PATCH 072/124] =?UTF-8?q?Feat:=20=EC=97=B0=EA=B5=AC=20=ED=83=AD?= =?UTF-8?q?=20=ED=86=B5=ED=95=A9=EA=B2=80=EC=83=89=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?Table=20=EC=83=9D=EC=84=B1.=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat: Add Controller for member search only top, and page. * Feat: Add ResearchSearchEntity, and entry for conference, research, lab entity. * Feat: For research create/update, lab create, add updating researchsearch. * Feat: Add empty repository and service for ResearchSearch * Fix: Fix typo, import library. --- .../conference/database/ConferenceEntity.kt | 24 ++-- .../member/service/MemberSearchService.kt | 1 - .../core/research/database/LabEntity.kt | 17 +-- .../core/research/database/ResearchEntity.kt | 9 ++ .../research/database/ResearchPostType.kt | 8 +- .../research/database/ResearchSearchEntity.kt | 110 ++++++++++++++++++ .../database/ResearchSearchRepository.kt | 17 +++ .../research/service/ResearchSearchService.kt | 23 ++++ .../core/research/service/ResearchService.kt | 22 +++- 9 files changed, 204 insertions(+), 27 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchSearchEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchSearchRepository.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchSearchService.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferenceEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferenceEntity.kt index 9c811d3d..545d363d 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferenceEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferenceEntity.kt @@ -1,19 +1,21 @@ package com.wafflestudio.csereal.core.conference.database import com.wafflestudio.csereal.common.config.BaseTimeEntity -import jakarta.persistence.Entity -import jakarta.persistence.FetchType -import jakarta.persistence.JoinColumn -import jakarta.persistence.ManyToOne +import com.wafflestudio.csereal.core.research.database.ResearchSearchEntity +import jakarta.persistence.* @Entity(name = "conference") class ConferenceEntity( - val code: String, - val abbreviation: String, - val name: String, + var isDeleted: Boolean = false, + var code: String, + var abbreviation: String, + var name: String, - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "conference_page_id") - val conferencePage: ConferencePageEntity + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "conference_page_id") + val conferencePage: ConferencePageEntity, -) : BaseTimeEntity() + @OneToOne(mappedBy = "conferenceElement", cascade = [CascadeType.ALL], orphanRemoval = true) + var researchSearch: ResearchSearchEntity? = null, +) : BaseTimeEntity() { +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/MemberSearchService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/MemberSearchService.kt index 07e8ebf6..4d8cb40e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/MemberSearchService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/MemberSearchService.kt @@ -11,7 +11,6 @@ import org.springframework.transaction.annotation.Transactional interface MemberSearchService { fun searchTopMember(keyword: String, number: Int): MemberSearchTopResponse fun searchMember(keyword: String, pageSize: Int, pageNum: Int): MemberSearchPageResponse - } @Service diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabEntity.kt index 63aaa866..5d619a21 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabEntity.kt @@ -9,26 +9,29 @@ import jakarta.persistence.* @Entity(name = "lab") class LabEntity( - val name: String, + var name: String, @OneToMany(mappedBy = "lab") val professors: MutableSet = mutableSetOf(), - val location: String?, - val tel: String?, - val acronym: String?, + var location: String?, + var tel: String?, + var acronym: String?, @OneToOne var pdf: AttachmentEntity? = null, - val youtube: String?, + var youtube: String?, @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "research_id") var research: ResearchEntity, - val description: String?, - val websiteURL: String?, + var description: String?, + var websiteURL: String?, + + @OneToOne(mappedBy = "lab", cascade = [CascadeType.ALL], orphanRemoval = true) + var researchSearch: ResearchSearchEntity? = null, ) : BaseTimeEntity() { companion object { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchEntity.kt index 70cbca2b..2769ae29 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchEntity.kt @@ -25,6 +25,8 @@ class ResearchEntity( @OneToMany(mappedBy = "research", cascade = [CascadeType.ALL], orphanRemoval = true) var attachments: MutableList = mutableListOf(), + @OneToOne(mappedBy = "research", cascade = [CascadeType.ALL], orphanRemoval = true) + var researchSearch: ResearchSearchEntity? = null, ) : BaseTimeEntity(), MainImageContentEntityType, AttachmentContentEntityType { override fun bringMainImage() = mainImage override fun bringAttachments() = attachments @@ -38,4 +40,11 @@ class ResearchEntity( ) } } + + fun updateWithoutLabImageAttachment(researchDto: ResearchDto) { + this.postType = researchDto.postType + this.name = researchDto.name + this.description = researchDto.description + } + } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchPostType.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchPostType.kt index c86016b0..d2839103 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchPostType.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchPostType.kt @@ -1,5 +1,9 @@ package com.wafflestudio.csereal.core.research.database -enum class ResearchPostType { - GROUPS, CENTERS, LABS +enum class ResearchPostType ( + val krName: String, +) { + GROUPS("연구 그룹"), + CENTERS("연구 센터"), + LABS("연구실 목록"); } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchSearchEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchSearchEntity.kt new file mode 100644 index 00000000..bf35f813 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchSearchEntity.kt @@ -0,0 +1,110 @@ +package com.wafflestudio.csereal.core.research.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.core.conference.database.ConferenceEntity +import jakarta.persistence.* + +@Entity(name = "research_search") +class ResearchSearchEntity ( + @Column(columnDefinition = "TEXT") + var content: String, + + @OneToOne + @JoinColumn(name = "research_id") + val research: ResearchEntity? = null, + + @OneToOne + @JoinColumn(name = "lab_id") + val lab: LabEntity? = null, + + @OneToOne + @JoinColumn(name = "conference_id") + val conferenceElement: ConferenceEntity? = null, +): BaseTimeEntity() { + companion object { + fun create(research: ResearchEntity): ResearchSearchEntity { + return ResearchSearchEntity( + content = createContent(research), + research = research, + ) + } + + fun create(lab: LabEntity): ResearchSearchEntity { + return ResearchSearchEntity( + content = createContent(lab), + lab = lab, + ) + } + + fun create(conference: ConferenceEntity): ResearchSearchEntity { + return ResearchSearchEntity( + content = createContent(conference), + conferenceElement = conference, + ) + } + + fun createContent(research: ResearchEntity) = StringBuilder().apply { + appendLine(research.name) + appendLine(research.postType.krName) + research.description?.let { appendLine(it) } + research.labs.forEach { appendLine(it.name) } + }.toString() + + fun createContent(lab: LabEntity) = StringBuilder().apply { + appendLine(lab.name) + lab.professors.forEach { appendLine(it.name) } + lab.location?.let { appendLine(it) } + lab.tel?.let { appendLine(it) } + lab.acronym?.let { appendLine(it) } + lab.youtube?.let { appendLine(it) } + appendLine(lab.research.name) + lab.description?.let { appendLine(it) } + lab.websiteURL?.let { appendLine(it) } + }.toString() + + fun createContent(conference: ConferenceEntity) = StringBuilder().apply { + appendLine(conference.name) + appendLine(conference.code) + appendLine(conference.abbreviation) + }.toString() + } + + @PrePersist + @PreUpdate + fun checkType() { + if (!( + (research != null && lab == null && conferenceElement == null) || + (research == null && lab != null && conferenceElement == null) || + (research == null && lab == null && conferenceElement != null) + )) { + throw RuntimeException("ResearchSearchEntity must have either research or lab or conference") + } + } + + fun ofType(): ResearchSearchType { + return when { + research != null && lab == null && conferenceElement == null -> ResearchSearchType.RESEARCH + research == null && lab != null && conferenceElement == null -> ResearchSearchType.LAB + research == null && lab == null && conferenceElement != null -> ResearchSearchType.CONFERENCE + else -> throw RuntimeException("ResearchSearchEntity must have either research or lab or conference") + } + } + + fun update(research: ResearchEntity) { + this.content = createContent(research) + } + + fun update(lab: LabEntity) { + this.content = createContent(lab) + } + + fun update(conference: ConferenceEntity) { + this.content = createContent(conference) + } +} + +enum class ResearchSearchType { + RESEARCH, + LAB, + CONFERENCE; +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchSearchRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchSearchRepository.kt new file mode 100644 index 00000000..c005d5e0 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchSearchRepository.kt @@ -0,0 +1,17 @@ +package com.wafflestudio.csereal.core.research.database + +import com.querydsl.jpa.impl.JPAQueryFactory +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +interface ResearchSearchRepository: JpaRepository, ResearchSearchRepositoryCustom { +} + +interface ResearchSearchRepositoryCustom { +} + +@Repository +class ResearchSearchRepositoryCustomImpl ( + private val jpaQueryFactory: JPAQueryFactory +): ResearchSearchRepositoryCustom { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchSearchService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchSearchService.kt new file mode 100644 index 00000000..6c589929 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchSearchService.kt @@ -0,0 +1,23 @@ +package com.wafflestudio.csereal.core.research.service + +import com.wafflestudio.csereal.core.research.database.ResearchSearchEntity +import com.wafflestudio.csereal.core.research.database.ResearchSearchRepository +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +interface ResearchSearchService { + fun deleteResearchSearch(researchSearchEntity: ResearchSearchEntity) +} + +@Service +class ResearchSearchServiceImpl ( + private val researchSearchRepository: ResearchSearchRepository, +) : ResearchSearchService { + + @Transactional + override fun deleteResearchSearch( + researchSearchEntity: ResearchSearchEntity + ) { + researchSearchRepository.delete(researchSearchEntity) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt index 986649e6..6f40ddb2 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt @@ -25,12 +25,12 @@ interface ResearchService { @Service class ResearchServiceImpl( - private val researchRepository: ResearchRepository, - private val labRepository: LabRepository, - private val professorRepository: ProfessorRepository, - private val mainImageService: MainImageService, - private val attachmentService: AttachmentService, - private val endpointProperties: EndpointProperties, + private val researchRepository: ResearchRepository, + private val labRepository: LabRepository, + private val professorRepository: ProfessorRepository, + private val mainImageService: MainImageService, + private val attachmentService: AttachmentService, + private val endpointProperties: EndpointProperties, ) : ResearchService { @Transactional override fun createResearchDetail(request: ResearchDto, mainImage: MultipartFile?, attachments: List?): ResearchDto { @@ -54,6 +54,8 @@ class ResearchServiceImpl( attachmentService.uploadAllAttachments(newResearch, attachments) } + newResearch.researchSearch = ResearchSearchEntity.create(newResearch) + researchRepository.save(newResearch) val imageURL = mainImageService.createImageURL(newResearch.mainImage) @@ -134,6 +136,12 @@ class ResearchServiceImpl( val imageURL = mainImageService.createImageURL(research.mainImage) val attachmentResponses = attachmentService.createAttachmentResponses(research.attachments) + research.updateWithoutLabImageAttachment(request) + + research.researchSearch?.update(research) + ?: let { + research.researchSearch = ResearchSearchEntity.create(research) + } return ResearchDto.of(research, imageURL, attachmentResponses) } @@ -165,6 +173,8 @@ class ResearchServiceImpl( pdfURL = "${endpointProperties.backend}/v1/attachment/${attachmentDto.filename}" } + newLab.researchSearch = ResearchSearchEntity.create(newLab) + labRepository.save(newLab) return LabDto.of(newLab, pdfURL) From 5225232f65b0924c4a515650295a3b23af91191e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=ED=98=81=EC=A4=80=20=28HyukJoon=20Woo=29?= Date: Tue, 12 Sep 2023 13:56:58 +0900 Subject: [PATCH 073/124] =?UTF-8?q?Feat:=20Lab=20Update=20API=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#98)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat: Add Controller for member search only top, and page. * Feat: Add ResearchSearchEntity, and entry for conference, research, lab entity. * Feat: For research create/update, lab create, add updating researchsearch. * Feat: Add empty repository and service for ResearchSearch * Feat: Add update method for labentity. * Feat: Add update lab service. * Feat: Add update lab controller. * Test: Add ResearchServiceTest. * Feat: Add deleteAttachment (marking) * Fix: Add typo, add import library. --- .../conference/database/ConferenceEntity.kt | 2 +- .../core/research/api/ResearchController.kt | 15 + .../core/research/database/LabEntity.kt | 11 + .../core/research/dto/LabUpdateRequest.kt | 13 + .../core/research/service/ResearchService.kt | 55 +++ .../attachment/service/AttachmentService.kt | 14 + .../reseach/service/ResearchServiceTest.kt | 384 ++++++++++++++++++ 7 files changed, 493 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/research/dto/LabUpdateRequest.kt create mode 100644 src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchServiceTest.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferenceEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferenceEntity.kt index 545d363d..54a19253 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferenceEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferenceEntity.kt @@ -18,4 +18,4 @@ class ConferenceEntity( @OneToOne(mappedBy = "conferenceElement", cascade = [CascadeType.ALL], orphanRemoval = true) var researchSearch: ResearchSearchEntity? = null, ) : BaseTimeEntity() { -} +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt index af093051..69772609 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt @@ -1,6 +1,8 @@ package com.wafflestudio.csereal.core.research.api +import com.wafflestudio.csereal.common.aop.AuthenticatedStaff import com.wafflestudio.csereal.core.research.dto.LabDto +import com.wafflestudio.csereal.core.research.dto.LabUpdateRequest import com.wafflestudio.csereal.core.research.dto.ResearchDto import com.wafflestudio.csereal.core.research.dto.ResearchGroupResponse import com.wafflestudio.csereal.core.research.service.ResearchService @@ -62,4 +64,17 @@ class ResearchController( ): ResponseEntity { return ResponseEntity.ok(researchService.readLab(labId)) } + + /** + * Research Group 수정은 일단 제외하였음. + */ + @AuthenticatedStaff + @PatchMapping("/lab/{labId}") + fun updateLab( + @PathVariable labId: Long, + @Valid @RequestPart("request") request: LabUpdateRequest, + @RequestPart("pdf") pdf: MultipartFile? + ): ResponseEntity { + return ResponseEntity.ok(researchService.updateLab(labId, request, pdf)) + } } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabEntity.kt index 5d619a21..ff56635c 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabEntity.kt @@ -4,6 +4,7 @@ import com.wafflestudio.csereal.common.config.BaseTimeEntity import com.wafflestudio.csereal.common.controller.AttachmentContentEntityType import com.wafflestudio.csereal.core.member.database.ProfessorEntity import com.wafflestudio.csereal.core.research.dto.LabDto +import com.wafflestudio.csereal.core.research.dto.LabUpdateRequest import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity import jakarta.persistence.* @@ -48,4 +49,14 @@ class LabEntity( ) } } + + fun updateWithoutProfessor(labUpdateRequest: LabUpdateRequest) { + this.name = labUpdateRequest.name + this.location = labUpdateRequest.location + this.tel = labUpdateRequest.tel + this.acronym = labUpdateRequest.acronym + this.youtube = labUpdateRequest.youtube + this.description = labUpdateRequest.description + this.websiteURL = labUpdateRequest.websiteURL + } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/LabUpdateRequest.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/LabUpdateRequest.kt new file mode 100644 index 00000000..51d59387 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/LabUpdateRequest.kt @@ -0,0 +1,13 @@ +package com.wafflestudio.csereal.core.research.dto + +data class LabUpdateRequest( + val name: String, + val professorIds: List, + val location: String?, + val tel: String?, + val acronym: String?, + val youtube: String?, + val description: String?, + val websiteURL: String?, + val pdfModified: Boolean, +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt index 6f40ddb2..441e3b23 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt @@ -3,6 +3,7 @@ package com.wafflestudio.csereal.core.research.service import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.common.properties.EndpointProperties import com.wafflestudio.csereal.core.member.database.ProfessorRepository +import com.wafflestudio.csereal.core.member.service.ProfessorService import com.wafflestudio.csereal.core.research.database.* import com.wafflestudio.csereal.core.research.dto.* import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity @@ -21,6 +22,7 @@ interface ResearchService { fun createLab(request: LabDto, pdf: MultipartFile?): LabDto fun readAllLabs(): List fun readLab(labId: Long): LabDto + fun updateLab(labId: Long, request: LabUpdateRequest, pdf: MultipartFile?): LabDto } @Service @@ -208,4 +210,57 @@ class ResearchServiceImpl( private fun createPdfURL(pdf: AttachmentEntity) : String{ return "${endpointProperties.backend}/v1/attachment/${pdf.filename}" } + + @Transactional + override fun updateLab(labId: Long, request: LabUpdateRequest, pdf: MultipartFile?): LabDto { + val labEntity = labRepository.findByIdOrNull(labId) + ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다.(labId=$labId)") + + labEntity.updateWithoutProfessor(request) + + // update professor + val removedProfessorIds = labEntity.professors.map { it.id } - request.professorIds + val addedProfessorIds = request.professorIds - labEntity.professors.map { it.id } + + removedProfessorIds.forEach { + val professor = professorRepository.findByIdOrNull(it) + ?: throw CserealException.Csereal404("해당 교수님을 찾을 수 없습니다.(professorId=$it)") + labEntity.professors.remove( + professor + ) + professor.lab = null + } + + addedProfessorIds.forEach { + val professor = professorRepository.findByIdOrNull(it) + ?: throw CserealException.Csereal404("해당 교수님을 찾을 수 없습니다.(professorId=$it)") + labEntity.professors.add( + professor + ) + professor.lab = labEntity + } + + // update pdf + if (request.pdfModified) { + labEntity.pdf?.let { attachmentService.deleteAttachment(it) } + + pdf?.let { + val attachmentDto = attachmentService.uploadAttachmentInLabEntity(labEntity, it) + } + } + + // update researchSearch + labEntity.researchSearch?.update(labEntity) + ?: let { + labEntity.researchSearch = ResearchSearchEntity.create(labEntity) + } + + return LabDto.of( + labEntity, + labEntity.pdf?.let { + createPdfURL(it) + } ?: "" + ) + } + } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt index 49c6aa4f..2a8b7c95 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt @@ -39,6 +39,8 @@ interface AttachmentService { contentEntity: AttachmentContentEntityType, attachmentsList: List ) + + fun deleteAttachment(attachment: AttachmentEntity) } @Service @@ -194,4 +196,16 @@ class AttachmentServiceImpl( } } } + + @Transactional + override fun deleteAttachment(attachment: AttachmentEntity) { + attachment.isDeleted = true + attachment.news = null + attachment.notice = null + attachment.seminar = null + attachment.about = null + attachment.academics = null + attachment.course = null + attachment.research = null + } } diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchServiceTest.kt new file mode 100644 index 00000000..e65275e8 --- /dev/null +++ b/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchServiceTest.kt @@ -0,0 +1,384 @@ +package com.wafflestudio.csereal.core.reseach.service + +import com.wafflestudio.csereal.core.member.database.ProfessorEntity +import com.wafflestudio.csereal.core.member.database.ProfessorRepository +import com.wafflestudio.csereal.core.member.database.ProfessorStatus +import com.wafflestudio.csereal.core.research.database.* +import com.wafflestudio.csereal.core.research.dto.LabDto +import com.wafflestudio.csereal.core.research.dto.LabProfessorResponse +import com.wafflestudio.csereal.core.research.dto.LabUpdateRequest +import com.wafflestudio.csereal.core.research.dto.ResearchDto +import com.wafflestudio.csereal.core.research.service.ResearchService +import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.extensions.spring.SpringTestExtension +import io.kotest.extensions.spring.SpringTestLifecycleMode +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.data.repository.findByIdOrNull +import org.springframework.transaction.annotation.Transactional + +@SpringBootTest +@Transactional +class ResearchServiceTest ( + private val researchService: ResearchService, + private val professorRepository: ProfessorRepository, + private val labRepository: LabRepository, + private val researchRepository: ResearchRepository, + private val researchSearchRepository: ResearchSearchRepository, +): BehaviorSpec({ + extensions(SpringTestExtension(SpringTestLifecycleMode.Root)) + + beforeSpec { + } + + afterSpec { + professorRepository.deleteAll() + researchRepository.deleteAll() + labRepository.deleteAll() + researchSearchRepository.deleteAll() + } + + // Research + Given("간단한 Research를 생성하려고 할 때") { + val researchDto = ResearchDto( + id = -1, + name = "name", + postType = ResearchPostType.CENTERS, + description = "description", + createdAt = null, + modifiedAt = null, + labs = null, + imageURL = null, + attachments = null, + ) + + When("Research를 생성한다면") { + val createdResearchDto = researchService.createResearchDetail( + researchDto, + null, + null, + ) + + Then("Research가 생성되어야 한다") { + val research = researchRepository.findByIdOrNull(createdResearchDto.id) + research shouldNotBe null + researchRepository.count() shouldBe 1 + } + + Then("생성된 Research의 내용이 Dto와 동일해야 한다.") { + val research = researchRepository.findByIdOrNull(createdResearchDto.id)!! + research.name shouldBe researchDto.name + research.postType shouldBe researchDto.postType + research.description shouldBe researchDto.description + } + + Then("검색 엔티티가 생성되어야 한다.") { + val research = researchRepository.findByIdOrNull(createdResearchDto.id)!! + val researchSearch = research.researchSearch + researchSearch shouldNotBe null + + researchSearch!!.content shouldBe + """ + name + 연구 센터 + description + + """.trimIndent() + } + } + } + + Given("간단한 Research를 수정하려고 할 때") { + val researchDto = ResearchDto( + id = -1, + name = "name", + postType = ResearchPostType.CENTERS, + description = "description", + createdAt = null, + modifiedAt = null, + labs = null, + imageURL = null, + attachments = null, + ) + + val createdResearchDto = researchService.createResearchDetail( + researchDto, + null, + null, + ) + + When("Research를 수정한다면") { + val researchUpdateRequest = ResearchDto( + id = createdResearchDto.id, + name = "name2", + postType = ResearchPostType.GROUPS, + description = "description2", + createdAt = null, + modifiedAt = null, + labs = null, + imageURL = null, + attachments = null, + ) + + researchService.updateResearchDetail( + createdResearchDto.id, + researchUpdateRequest, + null, + null, + ) + + Then("Research가 수정되어야 한다") { + val research = researchRepository.findByIdOrNull(createdResearchDto.id)!! + research.name shouldBe researchUpdateRequest.name + research.postType shouldBe researchUpdateRequest.postType + research.description shouldBe researchUpdateRequest.description + } + + Then("검색 엔티티가 수정되어야 한다.") { + val research = researchRepository.findByIdOrNull(createdResearchDto.id)!! + val researchSearch = research.researchSearch + researchSearch shouldNotBe null + + researchSearch!!.content shouldBe + """ + name2 + 연구 그룹 + description2 + + """.trimIndent() + } + } + } + + + // Lab + Given("pdf 없는 Lab을 생성하려고 할 때") { + // Save professors + val professor1 = professorRepository.save( + ProfessorEntity( + name = "professor1", + status = ProfessorStatus.ACTIVE, + academicRank = "professor", + email = null, + fax = null, + office = null, + phone = null, + website = null, + startDate = null, + endDate = null, + ) + ) + val professor2 = professorRepository.save( + ProfessorEntity( + name = "professor2", + status = ProfessorStatus.ACTIVE, + academicRank = "professor", + email = null, + fax = null, + office = null, + phone = null, + website = null, + startDate = null, + endDate = null, + ) + ) + + // Save research + val research = researchRepository.save( + ResearchEntity( + name = "research", + postType = ResearchPostType.GROUPS, + description = null, + ) + ) + + val labDto = LabDto( + id = -1, + name = "name", + professors = listOf( + LabProfessorResponse(professor1.id, professor1.name), + LabProfessorResponse(professor2.id, professor2.name), + ), + acronym = "acronym", + description = "description", + group = "research", + pdf = null, + location = "location", + tel = "tel", + websiteURL = "websiteURL", + youtube = "youtube", + ) + + When("Lab을 생성한다면") { + val createdLabDto = researchService.createLab(labDto, null) + + Then("Lab이 생성되어야 한다") { + val lab = labRepository.findByIdOrNull(createdLabDto.id) + lab shouldNotBe null + labRepository.count() shouldBe 1 + } + + Then("생성된 Lab의 내용이 Dto와 동일해야 한다.") { + val lab = labRepository.findByIdOrNull(createdLabDto.id)!! + lab.name shouldBe labDto.name + lab.acronym shouldBe labDto.acronym + lab.description shouldBe labDto.description + lab.location shouldBe labDto.location + lab.tel shouldBe labDto.tel + lab.websiteURL shouldBe labDto.websiteURL + lab.youtube shouldBe labDto.youtube + lab.research shouldBe research + lab.professors shouldBe mutableSetOf(professor1, professor2) + } + + Then("검색 엔티티가 생성되어야 한다.") { + val lab = labRepository.findByIdOrNull(createdLabDto.id)!! + val researchSearch = lab.researchSearch + researchSearch shouldNotBe null + + researchSearch!!.content shouldBe + """ + name + professor1 + professor2 + location + tel + acronym + youtube + research + description + websiteURL + + """.trimIndent() + } + } + } + + Given("간단한 Lab을 수정할 경우") { + // Save professors + val professor1 = professorRepository.save( + ProfessorEntity( + name = "professor1", + status = ProfessorStatus.ACTIVE, + academicRank = "professor", + email = null, + fax = null, + office = null, + phone = null, + website = null, + startDate = null, + endDate = null, + ) + ) + val professor2 = professorRepository.save( + ProfessorEntity( + name = "professor2", + status = ProfessorStatus.ACTIVE, + academicRank = "professor", + email = null, + fax = null, + office = null, + phone = null, + website = null, + startDate = null, + endDate = null, + ) + ) + + // Save research + val research = researchRepository.save( + ResearchEntity( + name = "research", + postType = ResearchPostType.GROUPS, + description = null, + ) + ) + + // Save lab + val labDto = LabDto( + id = -1, + name = "name", + professors = listOf( + LabProfessorResponse(professor1.id, professor1.name), + LabProfessorResponse(professor2.id, professor2.name), + ), + acronym = "acronym", + description = "description", + group = "research", + pdf = null, + location = "location", + tel = "tel", + websiteURL = "websiteURL", + youtube = "youtube", + ) + + val createdLabDto = researchService.createLab(labDto, null) + val createdLab = labRepository.findByIdOrNull(createdLabDto.id)!! + + When("pdf를 제외하고 Lab을 수정한다면") { + val professor3 = professorRepository.save( + ProfessorEntity( + name = "professor3", + status = ProfessorStatus.ACTIVE, + academicRank = "professor", + email = null, + fax = null, + office = null, + phone = null, + website = null, + startDate = null, + endDate = null, + ) + ) + + val labUpdateRequest = LabUpdateRequest( + name = "name2", + professorIds = listOf(professor1.id, professor3.id), + acronym = "acronym2", + description = "description2", + location = "location2", + tel = "tel2", + websiteURL = "websiteURL2", + youtube = "youtube2", + pdfModified = false, + ) + + researchService.updateLab(createdLab.id, labUpdateRequest, null) + + Then("Lab이 수정되어야 한다.") { + val lab = labRepository.findByIdOrNull(createdLab.id)!! + lab.name shouldBe labUpdateRequest.name + lab.acronym shouldBe labUpdateRequest.acronym + lab.description shouldBe labUpdateRequest.description + lab.location shouldBe labUpdateRequest.location + lab.tel shouldBe labUpdateRequest.tel + lab.websiteURL shouldBe labUpdateRequest.websiteURL + lab.youtube shouldBe labUpdateRequest.youtube + lab.research shouldBe research + lab.professors shouldBe mutableSetOf(professor1, professor3) + } + + Then("검색 엔티티가 수정되어야 한다.") { + val lab = labRepository.findByIdOrNull(createdLab.id)!! + val researchSearch = lab.researchSearch + researchSearch shouldNotBe null + + researchSearch!!.content shouldBe + """ + name2 + professor1 + professor3 + location2 + tel2 + acronym2 + youtube2 + research + description2 + websiteURL2 + + """.trimIndent() + } + } + } +}) \ No newline at end of file From ba8aace4050c763d8dd688d34f7ba553ef3452e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=ED=98=81=EC=A4=80=20=28HyukJoon=20Woo=29?= Date: Tue, 12 Sep 2023 14:19:30 +0900 Subject: [PATCH 074/124] =?UTF-8?q?Feat:=20Conference=20page=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20API=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat: Add Controller for member search only top, and page. * Feat: Add ResearchSearchEntity, and entry for conference, research, lab entity. * Feat: For research create/update, lab create, add updating researchsearch. * Feat: Add empty repository and service for ResearchSearch * Feat: Add update method for labentity. * Feat: Add update lab service. * Feat: Add update lab controller. * Test: Add ResearchServiceTest. * Feat: Add Conference Repository. * Feat: add conferenc eentity method for create, update entity. * Feat: Change variables to mutable. * Feat: Add id in conferencedto. * Feat: Add to sort the conferenceList. * Feat: Add deleteAttachment (marking) * Feat add service to modify conference page (create, modify, delete conferences) * Feat: Add api for modify conference page. * Test: Add test for conference service. --------- Co-authored-by: Junhyeong Kim --- .../conference/api/ConferenceController.kt | 17 +- .../conference/database/ConferenceEntity.kt | 21 ++- .../database/ConferencePageEntity.kt | 6 +- .../database/ConferenceRepository.kt | 5 + .../conference/dto/ConferenceCreateDto.kt | 8 + .../core/conference/dto/ConferenceDto.kt | 6 +- .../conference/dto/ConferenceModifyRequest.kt | 7 + .../core/conference/dto/ConferencePage.kt | 4 +- .../conference/service/ConferenceService.kt | 103 ++++++++++- .../service/ConferenceServiceTest.kt | 160 ++++++++++++++++++ 10 files changed, 325 insertions(+), 12 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferenceRepository.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceCreateDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceModifyRequest.kt create mode 100644 src/test/kotlin/com/wafflestudio/csereal/core/conference/service/ConferenceServiceTest.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/conference/api/ConferenceController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/conference/api/ConferenceController.kt index 75a3cde7..cbbce4e2 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/conference/api/ConferenceController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/conference/api/ConferenceController.kt @@ -1,11 +1,11 @@ package com.wafflestudio.csereal.core.conference.api +import com.wafflestudio.csereal.common.aop.AuthenticatedStaff +import com.wafflestudio.csereal.core.conference.dto.ConferenceModifyRequest import com.wafflestudio.csereal.core.conference.dto.ConferencePage import com.wafflestudio.csereal.core.conference.service.ConferenceService import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RestController +import org.springframework.web.bind.annotation.* @RequestMapping("/api/v1/conference") @RestController @@ -18,4 +18,15 @@ class ConferenceController( return ResponseEntity.ok(conferenceService.getConferencePage()) } + @AuthenticatedStaff + @PatchMapping("/page/conferences") + fun modifyConferencePage( + @RequestBody conferenceModifyRequest: ConferenceModifyRequest + ): ResponseEntity { + return ResponseEntity.ok( + conferenceService.modifyConferences( + conferenceModifyRequest + ) + ) + } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferenceEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferenceEntity.kt index 54a19253..5ff9e60b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferenceEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferenceEntity.kt @@ -1,6 +1,8 @@ package com.wafflestudio.csereal.core.conference.database import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.core.conference.dto.ConferenceCreateDto +import com.wafflestudio.csereal.core.conference.dto.ConferenceDto import com.wafflestudio.csereal.core.research.database.ResearchSearchEntity import jakarta.persistence.* @@ -18,4 +20,21 @@ class ConferenceEntity( @OneToOne(mappedBy = "conferenceElement", cascade = [CascadeType.ALL], orphanRemoval = true) var researchSearch: ResearchSearchEntity? = null, ) : BaseTimeEntity() { -} \ No newline at end of file + companion object { + fun of( + conferenceCreateDto: ConferenceCreateDto, + conferencePage: ConferencePageEntity, + ) = ConferenceEntity( + code = conferenceCreateDto.code, + abbreviation = conferenceCreateDto.abbreviation, + name = conferenceCreateDto.name, + conferencePage = conferencePage, + ) + } + + fun update(conferenceDto: ConferenceDto) { + this.code = conferenceDto.code + this.abbreviation = conferenceDto.abbreviation + this.name = conferenceDto.name + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferencePageEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferencePageEntity.kt index 4fdc2e14..28a57a71 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferencePageEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferencePageEntity.kt @@ -9,10 +9,10 @@ class ConferencePageEntity( @OneToOne @JoinColumn(name = "author_id") - val author: UserEntity, + var author: UserEntity, - @OneToMany(mappedBy = "conferencePage") + @OneToMany(mappedBy = "conferencePage", cascade = [CascadeType.ALL], orphanRemoval = true) @OrderBy("code ASC") - val conferences: List = mutableListOf() + val conferences: MutableSet = mutableSetOf() ) : BaseTimeEntity() diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferenceRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferenceRepository.kt new file mode 100644 index 00000000..25b6865b --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferenceRepository.kt @@ -0,0 +1,5 @@ +package com.wafflestudio.csereal.core.conference.database + +import org.springframework.data.jpa.repository.JpaRepository + +interface ConferenceRepository: JpaRepository \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceCreateDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceCreateDto.kt new file mode 100644 index 00000000..ab2b475e --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceCreateDto.kt @@ -0,0 +1,8 @@ +package com.wafflestudio.csereal.core.conference.dto + +data class ConferenceCreateDto ( + val code: String, + val abbreviation: String, + val name: String, +) { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceDto.kt index 03178adc..cd8a18c7 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceDto.kt @@ -3,16 +3,18 @@ package com.wafflestudio.csereal.core.conference.dto import com.wafflestudio.csereal.core.conference.database.ConferenceEntity data class ConferenceDto( + val id: Long, val code: String, val abbreviation: String, - val name: String + val name: String, ) { companion object { fun of(conferenceEntity: ConferenceEntity): ConferenceDto { return ConferenceDto( + id = conferenceEntity.id, code = conferenceEntity.code, abbreviation = conferenceEntity.abbreviation, - name = conferenceEntity.name + name = conferenceEntity.name, ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceModifyRequest.kt b/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceModifyRequest.kt new file mode 100644 index 00000000..4d5111d0 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceModifyRequest.kt @@ -0,0 +1,7 @@ +package com.wafflestudio.csereal.core.conference.dto + +data class ConferenceModifyRequest( + val newConferenceList: List, + val modifiedConferenceList: List, + val deleteConfereceIdList: List +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferencePage.kt b/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferencePage.kt index dfb38fb4..640c95e0 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferencePage.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferencePage.kt @@ -15,7 +15,9 @@ data class ConferencePage( createdAt = conferencePageEntity.createdAt!!, modifiedAt = conferencePageEntity.modifiedAt!!, author = conferencePageEntity.author.name, - conferenceList = conferencePageEntity.conferences.map { ConferenceDto.of(it) } + conferenceList = conferencePageEntity.conferences.map { + ConferenceDto.of(it) + }.sortedBy { it.code } ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/conference/service/ConferenceService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/conference/service/ConferenceService.kt index 2bd56077..0829a2a9 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/conference/service/ConferenceService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/conference/service/ConferenceService.kt @@ -1,20 +1,39 @@ package com.wafflestudio.csereal.core.conference.service +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.conference.database.ConferenceEntity +import com.wafflestudio.csereal.core.conference.database.ConferencePageEntity import com.wafflestudio.csereal.core.conference.database.ConferencePageRepository +import com.wafflestudio.csereal.core.conference.database.ConferenceRepository +import com.wafflestudio.csereal.core.conference.dto.ConferenceCreateDto import com.wafflestudio.csereal.core.conference.dto.ConferenceDto +import com.wafflestudio.csereal.core.conference.dto.ConferenceModifyRequest import com.wafflestudio.csereal.core.conference.dto.ConferencePage +import com.wafflestudio.csereal.core.research.database.ResearchSearchEntity +import com.wafflestudio.csereal.core.research.service.ResearchSearchService +import com.wafflestudio.csereal.core.user.database.UserEntity +import com.wafflestudio.csereal.core.user.database.UserRepository +import org.springframework.data.repository.findByIdOrNull +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.security.oauth2.core.oidc.user.OidcUser import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional +import org.springframework.web.context.request.RequestAttributes +import org.springframework.web.context.request.RequestContextHolder interface ConferenceService { fun getConferencePage(): ConferencePage + fun modifyConferences(conferenceModifyRequest: ConferenceModifyRequest): ConferencePage } @Service @Transactional class ConferenceServiceImpl( - private val conferencePageRepository: ConferencePageRepository + private val conferencePageRepository: ConferencePageRepository, + private val conferenceRepository: ConferenceRepository, + private val userRepository: UserRepository, + private val researchSearchService: ResearchSearchService, ) : ConferenceService { @Transactional(readOnly = true) @@ -23,4 +42,84 @@ class ConferenceServiceImpl( return ConferencePage.of(conferencePage) } -} + @Transactional + override fun modifyConferences(conferenceModifyRequest: ConferenceModifyRequest): ConferencePage { + var user = RequestContextHolder.getRequestAttributes()?.getAttribute( + "loggedInUser", + RequestAttributes.SCOPE_REQUEST + ) as UserEntity? + + if (user == null) { + val oidcUser = SecurityContextHolder.getContext().authentication.principal as OidcUser + val username = oidcUser.idToken.getClaim("username") + + user = userRepository.findByUsername(username) ?: throw CserealException.Csereal404("재로그인이 필요합니다.") + } + + val conferencePage = conferencePageRepository.findAll()[0] + + val newConferenceList = conferenceModifyRequest.newConferenceList.map { + createConferenceWithoutSave(it, conferencePage) + } + + val modifiedConferenceList = conferenceModifyRequest.modifiedConferenceList.map { + modifyConferenceWithoutSave(it) + } + + val deleteConferenceList = conferenceModifyRequest.deleteConfereceIdList.map { + deleteConference(it, conferencePage) + } + + conferencePage.author = user + + return ConferencePage.of(conferencePage) + } + + @Transactional + fun createConferenceWithoutSave( + conferenceCreateDto: ConferenceCreateDto, + conferencePage: ConferencePageEntity, + ): ConferenceEntity { + val newConference = ConferenceEntity.of( + conferenceCreateDto, + conferencePage + ) + conferencePage.conferences.add(newConference) + + newConference.researchSearch = ResearchSearchEntity.create(newConference) + + return newConference + } + + @Transactional + fun modifyConferenceWithoutSave( + conferenceDto: ConferenceDto, + ): ConferenceEntity { + val conferenceEntity = conferenceRepository.findByIdOrNull(conferenceDto.id) + ?: throw CserealException.Csereal404("Conference id:${conferenceDto.id} 가 존재하지 않습니다.") + + conferenceEntity.update(conferenceDto) + + conferenceEntity.researchSearch?.update(conferenceEntity) + ?: let { + conferenceEntity.researchSearch = ResearchSearchEntity.create(conferenceEntity) + } + + return conferenceEntity + } + + @Transactional + fun deleteConference( + id: Long, + conferencePage: ConferencePageEntity, + ) = conferenceRepository.findByIdOrNull(id) + ?. let { + it.isDeleted = true + conferencePage.conferences.remove(it) + + it.researchSearch?.let { + researchSearchService.deleteResearchSearch(it) + } + it.researchSearch = null + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/conference/service/ConferenceServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/conference/service/ConferenceServiceTest.kt new file mode 100644 index 00000000..7b6ac283 --- /dev/null +++ b/src/test/kotlin/com/wafflestudio/csereal/core/conference/service/ConferenceServiceTest.kt @@ -0,0 +1,160 @@ +package com.wafflestudio.csereal.core.conference.service + +import com.wafflestudio.csereal.core.conference.database.ConferenceEntity +import com.wafflestudio.csereal.core.conference.database.ConferencePageEntity +import com.wafflestudio.csereal.core.conference.database.ConferencePageRepository +import com.wafflestudio.csereal.core.conference.database.ConferenceRepository +import com.wafflestudio.csereal.core.conference.dto.ConferenceCreateDto +import com.wafflestudio.csereal.core.conference.dto.ConferenceDto +import com.wafflestudio.csereal.core.conference.dto.ConferenceModifyRequest +import com.wafflestudio.csereal.core.user.database.Role +import com.wafflestudio.csereal.core.user.database.UserEntity +import com.wafflestudio.csereal.core.user.database.UserRepository +import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.extensions.spring.SpringTestExtension +import io.kotest.extensions.spring.SpringTestLifecycleMode +import io.kotest.matchers.shouldBe +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.transaction.annotation.Transactional +import org.springframework.web.context.request.RequestAttributes +import org.springframework.web.context.request.RequestContextHolder + +@SpringBootTest +@Transactional +class ConferenceServiceTest ( + private val conferenceService: ConferenceService, + private val conferencePageRepository: ConferencePageRepository, + private val conferenceRepository: ConferenceRepository, + private val userRepository: UserRepository, +): BehaviorSpec ({ + extensions(SpringTestExtension(SpringTestLifecycleMode.Root)) + + beforeSpec { + val user = userRepository.save( + UserEntity( + username = "admin", + name = "admin", + email = "email", + studentId = "studentId", + role = Role.ROLE_STAFF, + ) + ) + + conferencePageRepository.save( + ConferencePageEntity( + author = user, + ) + ) + } + + afterSpec { + conferencePageRepository.deleteAll() + conferenceRepository.deleteAll() + userRepository.deleteAll() + } + + // ConferencePage + Given("Conference를 수정하려고 할 때") { + val userEntity = userRepository.findByUsername("admin")!! + + mockkStatic(RequestContextHolder::class) + val mockRequestAttributes = mockk() + every { + RequestContextHolder.getRequestAttributes() + } returns mockRequestAttributes + every { + mockRequestAttributes.getAttribute( + "loggedInUser", + RequestAttributes.SCOPE_REQUEST + ) + } returns userEntity + + + var conferencePage = conferencePageRepository.findAll().first() + + val conferences = conferenceRepository.saveAll(listOf( + ConferenceEntity( + code = "code1", + name = "name1", + abbreviation = "abbreviation1", + conferencePage = conferencePage, + ), + ConferenceEntity( + code = "code2", + name = "name2", + abbreviation = "abbreviation2", + conferencePage = conferencePage, + ), + ConferenceEntity( + code = "code3", + name = "name3", + abbreviation = "abbreviation3", + conferencePage = conferencePage, + ), + )) + conferencePage = conferencePage.apply { + this.conferences.addAll(conferences) + }.let { + conferencePageRepository.save(it) + } + + When("Conference를 수정한다면") { + val deleteConferenceId = conferences[1].id + val modifiedConference = ConferenceDto( + id = conferences.first().id, + code = "code0", + name = "modifiedName", + abbreviation = "modifiedAbbreviation", + ) + val newConference = ConferenceCreateDto( + code = "code9", + name = "newName", + abbreviation = "newAbbreviation", + ) + val conferenceModifyRequest = ConferenceModifyRequest( + deleteConfereceIdList = listOf(deleteConferenceId), + modifiedConferenceList = listOf(modifiedConference), + newConferenceList = listOf(newConference), + ) + + val conferencePage = conferenceService.modifyConferences(conferenceModifyRequest) + + Then("Conference가 수정되어야 한다.") { + val newConferencePage = conferencePageRepository.findAll().first() + val newConferences = newConferencePage.conferences.sortedBy { it.code } + + newConferences.size shouldBe 3 + newConferences.first().apply { + code shouldBe modifiedConference.code + name shouldBe modifiedConference.name + abbreviation shouldBe modifiedConference.abbreviation + researchSearch?.content shouldBe """ + modifiedName + code0 + modifiedAbbreviation + + """.trimIndent() + } + newConferences[1].apply { + code shouldBe conferences.last().code + name shouldBe conferences.last().name + abbreviation shouldBe conferences.last().abbreviation + } + newConferences.last().apply { + code shouldBe newConference.code + name shouldBe newConference.name + abbreviation shouldBe newConference.abbreviation + researchSearch?.content shouldBe """ + newName + code9 + newAbbreviation + + """.trimIndent() + } + } + } + } +}) \ No newline at end of file From 33372fe29650afc5663a7a28de16be00bcd2c458 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Tue, 12 Sep 2023 17:46:08 +0900 Subject: [PATCH 075/124] =?UTF-8?q?fix:=20=ED=8C=8C=EC=9D=BC=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=88=98=EC=A0=95=20(#100)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 업로드 파일 사이즈 제한 * fix: 원본 첨부파일 이름으로 응답 * refactor: 세미나 업데이트 시 삭제할 첨부파일 id만 받도록 변경 * fix: 파일 api 경로 수정 * fix: deleteAttachment --- .../core/research/service/ResearchService.kt | 69 ++++++++++++------- .../attachment/dto/AttachmentResponse.kt | 3 +- .../attachment/service/AttachmentService.kt | 64 +++++++++-------- .../core/seminar/api/SeminarController.kt | 5 +- .../core/seminar/service/SeminarService.kt | 16 ++--- src/main/resources/application.yaml | 11 ++- 6 files changed, 95 insertions(+), 73 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt index 441e3b23..dcd45175 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt @@ -15,10 +15,21 @@ import org.springframework.transaction.annotation.Transactional import org.springframework.web.multipart.MultipartFile interface ResearchService { - fun createResearchDetail(request: ResearchDto, mainImage: MultipartFile?, attachments: List?): ResearchDto + fun createResearchDetail( + request: ResearchDto, + mainImage: MultipartFile?, + attachments: List? + ): ResearchDto + fun readAllResearchGroups(): ResearchGroupResponse fun readAllResearchCenters(): List - fun updateResearchDetail(researchId: Long, request: ResearchDto, mainImage: MultipartFile?, attachments: List?): ResearchDto + fun updateResearchDetail( + researchId: Long, + request: ResearchDto, + mainImage: MultipartFile?, + attachments: List? + ): ResearchDto + fun createLab(request: LabDto, pdf: MultipartFile?): LabDto fun readAllLabs(): List fun readLab(labId: Long): LabDto @@ -35,12 +46,16 @@ class ResearchServiceImpl( private val endpointProperties: EndpointProperties, ) : ResearchService { @Transactional - override fun createResearchDetail(request: ResearchDto, mainImage: MultipartFile?, attachments: List?): ResearchDto { + override fun createResearchDetail( + request: ResearchDto, + mainImage: MultipartFile?, + attachments: List? + ): ResearchDto { val newResearch = ResearchEntity.of(request) - if(request.labs != null) { + if (request.labs != null) { - for(lab in request.labs) { + for (lab in request.labs) { val labEntity = labRepository.findByIdOrNull(lab.id) ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다.(labId=${lab.id})") newResearch.labs.add(labEntity) @@ -48,11 +63,11 @@ class ResearchServiceImpl( } } - if(mainImage != null) { + if (mainImage != null) { mainImageService.uploadMainImage(newResearch, mainImage) } - if(attachments != null) { + if (attachments != null) { attachmentService.uploadAllAttachments(newResearch, attachments) } @@ -95,13 +110,19 @@ class ResearchServiceImpl( return researchCenters } + @Transactional - override fun updateResearchDetail(researchId: Long, request: ResearchDto, mainImage: MultipartFile?, attachments: List?): ResearchDto { + override fun updateResearchDetail( + researchId: Long, + request: ResearchDto, + mainImage: MultipartFile?, + attachments: List? + ): ResearchDto { val research = researchRepository.findByIdOrNull(researchId) ?: throw CserealException.Csereal404("해당 게시글을 찾을 수 없습니다.(researchId=$researchId)") - if(request.labs != null) { - for(lab in request.labs) { + if (request.labs != null) { + for (lab in request.labs) { val labEntity = labRepository.findByIdOrNull(lab.id) ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다.(labId=${lab.id})") @@ -112,9 +133,9 @@ class ResearchServiceImpl( val labsToRemove = oldLabs - request.labs.map { it.id } val labsToAdd = request.labs.map { it.id } - oldLabs - research.labs.removeIf { it.id in labsToRemove} + research.labs.removeIf { it.id in labsToRemove } - for(labsToAddId in labsToAdd) { + for (labsToAddId in labsToAdd) { val lab = labRepository.findByIdOrNull(labsToAddId)!! research.labs.add(lab) lab.research = research @@ -122,13 +143,13 @@ class ResearchServiceImpl( } } - if(mainImage != null) { + if (mainImage != null) { mainImageService.uploadMainImage(research, mainImage) } else { research.mainImage = null } - if(attachments != null) { + if (attachments != null) { research.attachments.clear() attachmentService.uploadAllAttachments(research, attachments) } else { @@ -153,14 +174,14 @@ class ResearchServiceImpl( val researchGroup = researchRepository.findByName(request.group) ?: throw CserealException.Csereal404("해당 연구그룹을 찾을 수 없습니다.(researchGroupId = ${request.group})") - if(researchGroup.postType != ResearchPostType.GROUPS) { + if (researchGroup.postType != ResearchPostType.GROUPS) { throw CserealException.Csereal404("해당 게시글은 연구그룹이어야 합니다.") } val newLab = LabEntity.of(request, researchGroup) - if(request.professors != null) { - for(professor in request.professors) { + if (request.professors != null) { + for (professor in request.professors) { val professorEntity = professorRepository.findByIdOrNull(professor.id) ?: throw CserealException.Csereal404("해당 교수님을 찾을 수 없습니다.(professorId = ${professor.id}") @@ -170,9 +191,9 @@ class ResearchServiceImpl( } var pdfURL = "" - if(pdf != null) { + if (pdf != null) { val attachmentDto = attachmentService.uploadAttachmentInLabEntity(newLab, pdf) - pdfURL = "${endpointProperties.backend}/v1/attachment/${attachmentDto.filename}" + pdfURL = "${endpointProperties.backend}/v1/file/${attachmentDto.filename}" } newLab.researchSearch = ResearchSearchEntity.create(newLab) @@ -186,7 +207,7 @@ class ResearchServiceImpl( override fun readAllLabs(): List { val labs = labRepository.findAllByOrderByName().map { var pdfURL = "" - if(it.pdf != null) { + if (it.pdf != null) { pdfURL = createPdfURL(it.pdf!!) } LabDto.of(it, pdfURL) @@ -200,15 +221,15 @@ class ResearchServiceImpl( val lab = labRepository.findByIdOrNull(labId) ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다.(labId=$labId)") var pdfURL = "" - if(lab.pdf != null) { + if (lab.pdf != null) { pdfURL = createPdfURL(lab.pdf!!) } return LabDto.of(lab, pdfURL) } - private fun createPdfURL(pdf: AttachmentEntity) : String{ - return "${endpointProperties.backend}/v1/attachment/${pdf.filename}" + private fun createPdfURL(pdf: AttachmentEntity): String { + return "${endpointProperties.backend}/v1/file/${pdf.filename}" } @Transactional @@ -263,4 +284,4 @@ class ResearchServiceImpl( ) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/dto/AttachmentResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/dto/AttachmentResponse.kt index dd226bd2..c3e2abc8 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/dto/AttachmentResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/dto/AttachmentResponse.kt @@ -1,8 +1,9 @@ package com.wafflestudio.csereal.core.resource.attachment.dto data class AttachmentResponse( + val id: Long, val name: String, val url: String, val bytes: Long, ) { -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt index 2a8b7c95..a550d521 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt @@ -1,11 +1,11 @@ package com.wafflestudio.csereal.core.resource.attachment.service +import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.common.controller.AttachmentContentEntityType import com.wafflestudio.csereal.common.properties.EndpointProperties import com.wafflestudio.csereal.core.about.database.AboutEntity import com.wafflestudio.csereal.core.academics.database.AcademicsEntity import com.wafflestudio.csereal.core.academics.database.CourseEntity -import com.wafflestudio.csereal.core.academics.database.ScholarshipEntity import com.wafflestudio.csereal.core.news.database.NewsEntity import com.wafflestudio.csereal.core.notice.database.NoticeEntity import com.wafflestudio.csereal.core.research.database.LabEntity @@ -17,6 +17,7 @@ import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse import com.wafflestudio.csereal.core.seminar.database.SeminarEntity import org.apache.commons.io.FilenameUtils import org.springframework.beans.factory.annotation.Value +import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import org.springframework.web.multipart.MultipartFile @@ -35,11 +36,12 @@ interface AttachmentService { ): List fun createAttachmentResponses(attachments: List?): List - fun updateAttachmentResponses( - contentEntity: AttachmentContentEntityType, - attachmentsList: List - ) +// fun updateAttachmentResponses( +// contentEntity: AttachmentContentEntityType, +// attachmentsList: List +// ) + fun deleteAttachments(ids: List) fun deleteAttachment(attachment: AttachmentEntity) } @@ -126,7 +128,8 @@ class AttachmentServiceImpl( for (attachment in attachments) { if (attachment.isDeleted == false) { val attachmentDto = AttachmentResponse( - name = attachment.filename, + id = attachment.id, + name = attachment.filename.substringAfter("_"), url = "${endpointProperties.backend}/v1/file/${attachment.filename}", bytes = attachment.size, ) @@ -138,26 +141,36 @@ class AttachmentServiceImpl( return list } - @Transactional - override fun updateAttachmentResponses( - contentEntity: AttachmentContentEntityType, - attachmentsList: List - ) { - val oldAttachments = contentEntity.bringAttachments().map { it.filename } +// @Transactional +// override fun updateAttachmentResponses( +// contentEntity: AttachmentContentEntityType, +// attachmentsList: List +// ) { +// val oldAttachments = contentEntity.bringAttachments().map { it.filename } +// +// val attachmentsToRemove = oldAttachments - attachmentsList.map { it.name } +// +// when (contentEntity) { +// is SeminarEntity -> { +// for (attachmentFilename in attachmentsToRemove) { +// val attachmentEntity = attachmentRepository.findByFilename(attachmentFilename) +// attachmentEntity.isDeleted = true +// attachmentEntity.seminar = null +// } +// } +// } +// } - val attachmentsToRemove = oldAttachments - attachmentsList.map { it.name } - - when (contentEntity) { - is SeminarEntity -> { - for (attachmentFilename in attachmentsToRemove) { - val attachmentEntity = attachmentRepository.findByFilename(attachmentFilename) - attachmentEntity.isDeleted = true - attachmentEntity.seminar = null - } - } + @Transactional + override fun deleteAttachments(ids: List) { + for (id in ids) { + val attachment = attachmentRepository.findByIdOrNull(id) + ?: throw CserealException.Csereal404("id:${id}인 첨부파일을 찾을 수 없습니다.") + attachment.isDeleted = true } } + private fun connectAttachmentToEntity(contentEntity: AttachmentContentEntityType, attachment: AttachmentEntity) { when (contentEntity) { is NewsEntity -> { @@ -200,12 +213,5 @@ class AttachmentServiceImpl( @Transactional override fun deleteAttachment(attachment: AttachmentEntity) { attachment.isDeleted = true - attachment.news = null - attachment.notice = null - attachment.seminar = null - attachment.about = null - attachment.academics = null - attachment.course = null - attachment.research = null } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt index 2f5aebee..4a494b2e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt @@ -1,6 +1,5 @@ package com.wafflestudio.csereal.core.seminar.api -import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse import com.wafflestudio.csereal.core.seminar.dto.SeminarDto import com.wafflestudio.csereal.core.seminar.dto.SeminarSearchResponse import com.wafflestudio.csereal.core.seminar.service.SeminarService @@ -48,7 +47,7 @@ class SeminarController( @Valid @RequestPart("request") request: SeminarDto, @RequestPart("newMainImage") newMainImage: MultipartFile?, @RequestPart("newAttachments") newAttachments: List?, - @RequestPart("attachmentsList") attachmentsList: List, + @RequestPart("deleteIds") deleteIds: List, ): ResponseEntity { return ResponseEntity.ok( seminarService.updateSeminar( @@ -56,7 +55,7 @@ class SeminarController( request, newMainImage, newAttachments, - attachmentsList + deleteIds ) ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt index c9b23317..2067bac0 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt @@ -1,7 +1,6 @@ package com.wafflestudio.csereal.core.seminar.service import com.wafflestudio.csereal.common.CserealException -import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse import com.wafflestudio.csereal.core.resource.attachment.service.AttachmentService import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService import com.wafflestudio.csereal.core.seminar.database.SeminarEntity @@ -23,7 +22,7 @@ interface SeminarService { request: SeminarDto, newMainImage: MultipartFile?, newAttachments: List?, - attachmentsList: List, + deleteIds: List, ): SeminarDto fun deleteSeminar(seminarId: Long) @@ -85,7 +84,7 @@ class SeminarServiceImpl( request: SeminarDto, newMainImage: MultipartFile?, newAttachments: List?, - attachmentsList: List, + deleteIds: List, ): SeminarDto { val seminar: SeminarEntity = seminarRepository.findByIdOrNull(seminarId) ?: throw CserealException.Csereal404("존재하지 않는 세미나입니다") @@ -94,21 +93,18 @@ class SeminarServiceImpl( seminar.update(request) if (newMainImage != null) { - seminar.mainImage!!.isDeleted = true + seminar.mainImage?.isDeleted = true mainImageService.uploadMainImage(seminar, newMainImage) } - var attachmentResponses: List = listOf() + attachmentService.deleteAttachments(deleteIds) if (newAttachments != null) { - attachmentService.updateAttachmentResponses(seminar, attachmentsList) attachmentService.uploadAllAttachments(seminar, newAttachments) - - attachmentResponses = attachmentService.createAttachmentResponses(seminar.attachments) - } else { - attachmentService.updateAttachmentResponses(seminar, attachmentsList) } + val attachmentResponses = attachmentService.createAttachmentResponses(seminar.attachments) + val imageURL = mainImageService.createImageURL(seminar.mainImage) return SeminarDto.of(seminar, imageURL, attachmentResponses) } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index aa951515..2621263a 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -16,6 +16,11 @@ spring: idsnucse: issuer-uri: https://id-dev.bacchus.io/o jwk-set-uri: https://id-dev.bacchus.io/o/jwks + servlet: + multipart: + enabled: true + max-request-size: 100MB + max-file-size: 10MB server: servlet: @@ -30,12 +35,6 @@ springdoc: api-docs: path: /api-docs/json -servlet: - multipart: - enabled: true - max-request-size: 100MB - max-file-size: 10MB - --- spring: config.activate.on-profile: local From d5d3b7c9ffe32c6a44aff6afacdc9cea3e5e365f Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Tue, 12 Sep 2023 18:08:05 +0900 Subject: [PATCH 076/124] =?UTF-8?q?fix:=20GET=20notice=20=EA=B6=8C?= =?UTF-8?q?=ED=95=9C=20=EA=B4=80=EB=A0=A8=20=EB=A1=9C=EC=A7=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#101)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/notice/api/NoticeController.kt | 18 ++++++++++++-- .../core/notice/database/NoticeRepository.kt | 24 ++++--------------- .../core/notice/service/NoticeService.kt | 8 ++++--- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt index a45c6b33..0d3d8b5b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt @@ -3,10 +3,14 @@ package com.wafflestudio.csereal.core.notice.api import com.wafflestudio.csereal.common.aop.AuthenticatedStaff import com.wafflestudio.csereal.core.notice.dto.* import com.wafflestudio.csereal.core.notice.service.NoticeService +import com.wafflestudio.csereal.core.user.database.Role +import com.wafflestudio.csereal.core.user.database.UserRepository import jakarta.validation.Valid import org.springframework.data.domain.PageRequest import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity +import org.springframework.security.core.annotation.AuthenticationPrincipal +import org.springframework.security.oauth2.core.oidc.user.OidcUser import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile @@ -14,17 +18,27 @@ import org.springframework.web.multipart.MultipartFile @RestController class NoticeController( private val noticeService: NoticeService, + private val userRepository: UserRepository ) { @GetMapping fun searchNotice( @RequestParam(required = false) tag: List?, @RequestParam(required = false) keyword: String?, - @RequestParam(required = false, defaultValue = "1") pageNum: Int + @RequestParam(required = false, defaultValue = "1") pageNum: Int, + @AuthenticationPrincipal oidcUser: OidcUser? ): ResponseEntity { + var isStaff = false + if (oidcUser != null) { + val username = oidcUser.idToken.getClaim("username") + val user = userRepository.findByUsername(username) + if (user?.role == Role.ROLE_STAFF) { + isStaff = true + } + } val pageSize = 20 val pageRequest = PageRequest.of(pageNum - 1, pageSize) val usePageBtn = pageNum != 1 - return ResponseEntity.ok(noticeService.searchNotice(tag, keyword, pageRequest, usePageBtn)) + return ResponseEntity.ok(noticeService.searchNotice(tag, keyword, pageRequest, usePageBtn, isStaff)) } @GetMapping("/{noticeId}") diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt index cf24f438..7f4207c9 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt @@ -33,7 +33,8 @@ interface CustomNoticeRepository { tag: List?, keyword: String?, pageable: Pageable, - usePageBtn: Boolean + usePageBtn: Boolean, + isStaff: Boolean ): NoticeSearchResponse } @@ -46,24 +47,9 @@ class NoticeRepositoryImpl( tag: List?, keyword: String?, pageable: Pageable, - usePageBtn: Boolean + usePageBtn: Boolean, + isStaff: Boolean ): NoticeSearchResponse { - var user = RequestContextHolder.getRequestAttributes()?.getAttribute( - "loggedInUser", - RequestAttributes.SCOPE_REQUEST - ) as UserEntity? - - if (user == null) { - val oidcUser = SecurityContextHolder.getContext().authentication.principal as OidcUser - val username = oidcUser.idToken.getClaim("username") - - if(userRepository.findByUsername(username) == null) { - user = null - } else { - user = userRepository.findByUsername(username) - } - } - val keywordBooleanBuilder = BooleanBuilder() val tagsBooleanBuilder = BooleanBuilder() val isPrivateBooleanBuilder = BooleanBuilder() @@ -91,7 +77,7 @@ class NoticeRepositoryImpl( } } - if(user?.role != Role.ROLE_STAFF) { + if (!isStaff) { isPrivateBooleanBuilder.or( noticeEntity.isPrivate.eq(false) ) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt index 9c9ae04e..0c74d3b6 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt @@ -22,7 +22,8 @@ interface NoticeService { tag: List?, keyword: String?, pageable: Pageable, - usePageBtn: Boolean + usePageBtn: Boolean, + isStaff: Boolean ): NoticeSearchResponse fun readNotice(noticeId: Long): NoticeDto @@ -48,9 +49,10 @@ class NoticeServiceImpl( tag: List?, keyword: String?, pageable: Pageable, - usePageBtn: Boolean + usePageBtn: Boolean, + isStaff: Boolean ): NoticeSearchResponse { - return noticeRepository.searchNotice(tag, keyword, pageable, usePageBtn) + return noticeRepository.searchNotice(tag, keyword, pageable, usePageBtn, isStaff) } @Transactional(readOnly = true) From e4266bf75691d3b072860123b625e3db5e615b33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=ED=98=81=EC=A4=80=20=28HyukJoon=20Woo=29?= Date: Tue, 12 Sep 2023 19:45:02 +0900 Subject: [PATCH 077/124] Refactor: Extract Full Text Query Template to CommonRepository (#102) * Feat: Extract full text search template to common repository. * Feat: Change full text search to use extracted repository. * Feat: Add for 4 elements. --------- Co-authored-by: Junhyeong Kim --- .../common/config/MySQLDialectCustom.kt | 6 ++ .../common/repository/CommonRepository.kt | 67 +++++++++++++++++++ .../member/database/MemberSearchRepository.kt | 23 +++---- 3 files changed, 84 insertions(+), 12 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/common/repository/CommonRepository.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/MySQLDialectCustom.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/MySQLDialectCustom.kt index aa73d8bb..b8db32f4 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/MySQLDialectCustom.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/MySQLDialectCustom.kt @@ -33,6 +33,12 @@ class MySQLDialectCustom: MySQLDialect( "match (?1, ?2, ?3) against (?4 in boolean mode)", basicTypeRegistry.resolve(StandardBasicTypes.DOUBLE) ) + + functionRegistry.registerPattern( + "match4", + "match (?1, ?2, ?3, ?4) against (?5 in boolean mode)", + basicTypeRegistry.resolve(StandardBasicTypes.DOUBLE) + ) } } } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/repository/CommonRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/common/repository/CommonRepository.kt new file mode 100644 index 00000000..be93f694 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/common/repository/CommonRepository.kt @@ -0,0 +1,67 @@ +package com.wafflestudio.csereal.common.repository + +import com.querydsl.core.types.dsl.Expressions +import com.querydsl.core.types.dsl.NumberTemplate +import org.springframework.stereotype.Repository + +interface CommonRepository { + fun searchFullSingleTextTemplate(keyword: String, field: Any): NumberTemplate + fun searchFullDoubleTextTemplate(keyword: String, field1: Any, field2: Any): NumberTemplate + fun searchFullTripleTextTemplate(keyword: String, field1: Any, field2: Any, field3: Any): NumberTemplate + fun searchFullQuadrapleTextTemplate(keyword: String, field1: Any, field2: Any, field3: Any, field4: Any): NumberTemplate +} + +@Repository +class CommonRepositoryImpl: CommonRepository { + override fun searchFullSingleTextTemplate( + keyword: String, + field: Any, + ) = Expressions.numberTemplate( + Double::class.javaObjectType, + "function('match',{0},{1})", + field, + keyword + ) + + override fun searchFullDoubleTextTemplate( + keyword: String, + field1: Any, + field2: Any, + ) = Expressions.numberTemplate( + Double::class.javaObjectType, + "function('match2',{0},{1},{2})", + field1, + field2, + keyword, + ) + + override fun searchFullTripleTextTemplate( + keyword: String, + field1: Any, + field2: Any, + field3: Any, + ) = Expressions.numberTemplate( + Double::class.javaObjectType, + "function('match3',{0},{1},{2},{3})", + field1, + field2, + field3, + keyword + ) + + override fun searchFullQuadrapleTextTemplate( + keyword: String, + field1: Any, + field2: Any, + field3: Any, + field4: Any, + ) = Expressions.numberTemplate( + Double::class.javaObjectType, + "function('match3',{0},{1},{2},{3},{4})", + field1, + field2, + field3, + field4, + keyword + ) +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/MemberSearchRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/MemberSearchRepository.kt index a6403c70..9ebb4348 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/MemberSearchRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/MemberSearchRepository.kt @@ -1,8 +1,8 @@ package com.wafflestudio.csereal.core.member.database -import com.querydsl.core.types.dsl.Expressions import com.querydsl.jpa.impl.JPAQuery import com.querydsl.jpa.impl.JPAQueryFactory +import com.wafflestudio.csereal.common.repository.CommonRepository import com.wafflestudio.csereal.core.member.database.QMemberSearchEntity.memberSearchEntity import com.wafflestudio.csereal.core.member.database.QProfessorEntity.professorEntity import com.wafflestudio.csereal.core.member.database.QStaffEntity.staffEntity @@ -21,6 +21,7 @@ interface MemberSearchRepositoryCustom { @Repository class MemberSearchRepositoryCustomImpl ( private val queryFactory: JPAQueryFactory, + private val commonRepository: CommonRepository, ): MemberSearchRepositoryCustom { override fun searchTopMember(keyword: String, number: Int): List { @@ -42,16 +43,11 @@ class MemberSearchRepositoryCustomImpl ( return queryResult to total } - fun searchFullTextTemplate(keyword: String) = - Expressions.numberTemplate( - Double::class.javaObjectType, - "function('match',{0},{1})", - memberSearchEntity.content, - keyword - ) - fun searchQuery(keyword: String): JPAQuery { - val searchDoublTemplate = searchFullTextTemplate(keyword) + val searchDoubleTemplate = commonRepository.searchFullSingleTextTemplate( + keyword, + memberSearchEntity.content + ) return queryFactory.select( memberSearchEntity @@ -64,12 +60,15 @@ class MemberSearchRepositoryCustomImpl ( memberSearchEntity.staff, staffEntity ).fetchJoin() .where( - searchDoublTemplate.gt(0.0) + searchDoubleTemplate.gt(0.0) ) } fun getSearchCount(keyword: String): Long { - val searchDoubleTemplate = searchFullTextTemplate(keyword) + val searchDoubleTemplate = commonRepository.searchFullSingleTextTemplate( + keyword, + memberSearchEntity.content + ) return queryFactory.select( memberSearchEntity From 028f52d7496f7904a48e7832b324ea1a947aa917 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=ED=98=81=EC=A4=80=20=28HyukJoon=20Woo=29?= Date: Tue, 12 Sep 2023 19:59:36 +0900 Subject: [PATCH 078/124] Feat: Notice Seminar News Search to Full Text Query (#105) * Feat: Extract full text search template to common repository. * Feat: Change full text search to use extracted repository. * Feat: Add for 4 elements. * Feat: Add more args cnt. * Feat: Change keyword boolean builder to full text. * Feat: Change keyword boolean builder to full text. * Feat: Change keyword boolean builder to full text. * Hotfix: Change mount volume. (#103) --- .../common/config/MySQLDialectCustom.kt | 24 +++++ .../common/repository/CommonRepository.kt | 90 ++++++++++++++++++- .../core/news/database/NewsRepository.kt | 19 ++-- .../core/notice/database/NoticeRepository.kt | 44 +++++---- .../seminar/database/SeminarRepository.kt | 26 +++--- 5 files changed, 161 insertions(+), 42 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/MySQLDialectCustom.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/MySQLDialectCustom.kt index b8db32f4..337c1bbb 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/MySQLDialectCustom.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/MySQLDialectCustom.kt @@ -39,6 +39,30 @@ class MySQLDialectCustom: MySQLDialect( "match (?1, ?2, ?3, ?4) against (?5 in boolean mode)", basicTypeRegistry.resolve(StandardBasicTypes.DOUBLE) ) + + functionRegistry.registerPattern( + "match5", + "match (?1, ?2, ?3, ?4, ?5) against (?6 in boolean mode)", + basicTypeRegistry.resolve(StandardBasicTypes.DOUBLE) + ) + + functionRegistry.registerPattern( + "match6", + "match (?1, ?2, ?3, ?4, ?5, ?6) against (?7 in boolean mode)", + basicTypeRegistry.resolve(StandardBasicTypes.DOUBLE) + ) + + functionRegistry.registerPattern( + "match7", + "match (?1, ?2, ?3, ?4, ?5, ?6, ?7) against (?8 in boolean mode)", + basicTypeRegistry.resolve(StandardBasicTypes.DOUBLE) + ) + + functionRegistry.registerPattern( + "match8", + "match (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8) against (?9 in boolean mode)", + basicTypeRegistry.resolve(StandardBasicTypes.DOUBLE) + ) } } } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/repository/CommonRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/common/repository/CommonRepository.kt index be93f694..d8c8e9ac 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/repository/CommonRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/repository/CommonRepository.kt @@ -9,6 +9,10 @@ interface CommonRepository { fun searchFullDoubleTextTemplate(keyword: String, field1: Any, field2: Any): NumberTemplate fun searchFullTripleTextTemplate(keyword: String, field1: Any, field2: Any, field3: Any): NumberTemplate fun searchFullQuadrapleTextTemplate(keyword: String, field1: Any, field2: Any, field3: Any, field4: Any): NumberTemplate + fun searchFullQuintupleTextTemplate(keyword: String, field1: Any, field2: Any, field3: Any, field4: Any, field5: Any): NumberTemplate + fun searchFullSextupleTextTemplate(keyword: String, field1: Any, field2: Any, field3: Any, field4: Any, field5: Any, field6: Any): NumberTemplate + fun searchFullSeptupleTextTemplate(keyword: String, field1: Any, field2: Any, field3: Any, field4: Any, field5: Any, field6: Any, field7: Any): NumberTemplate + fun searchFullOctupleTextTemplate(keyword: String, field1: Any, field2: Any, field3: Any, field4: Any, field5: Any, field6: Any, field7: Any, field8: Any): NumberTemplate } @Repository @@ -57,11 +61,95 @@ class CommonRepositoryImpl: CommonRepository { field4: Any, ) = Expressions.numberTemplate( Double::class.javaObjectType, - "function('match3',{0},{1},{2},{3},{4})", + "function('match4',{0},{1},{2},{3},{4})", field1, field2, field3, field4, keyword ) + + override fun searchFullQuintupleTextTemplate( + keyword: String, + field1: Any, + field2: Any, + field3: Any, + field4: Any, + field5: Any, + ) = Expressions.numberTemplate( + Double::class.javaObjectType, + "function('match5',{0},{1},{2},{3},{4},{5})", + field1, + field2, + field3, + field4, + field5, + keyword + ) + + override fun searchFullSextupleTextTemplate( + keyword: String, + field1: Any, + field2: Any, + field3: Any, + field4: Any, + field5: Any, + field6: Any, + ) = Expressions.numberTemplate( + Double::class.javaObjectType, + "function('match6',{0},{1},{2},{3},{4},{5},{6})", + field1, + field2, + field3, + field4, + field5, + field6, + keyword + ) + + override fun searchFullSeptupleTextTemplate( + keyword: String, + field1: Any, + field2: Any, + field3: Any, + field4: Any, + field5: Any, + field6: Any, + field7: Any, + ) = Expressions.numberTemplate( + Double::class.javaObjectType, + "function('match7',{0},{1},{2},{3},{4},{5},{6},{7})", + field1, + field2, + field3, + field4, + field5, + field6, + field7, + keyword + ) + + override fun searchFullOctupleTextTemplate( + keyword: String, + field1: Any, + field2: Any, + field3: Any, + field4: Any, + field5: Any, + field6: Any, + field7: Any, + field8: Any, + ) = Expressions.numberTemplate( + Double::class.javaObjectType, + "function('match8',{0},{1},{2},{3},{4},{5},{6},{7},{8})", + field1, + field2, + field3, + field4, + field5, + field6, + field7, + field8, + keyword + ) } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt index eff46859..188fb41d 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt @@ -3,6 +3,7 @@ package com.wafflestudio.csereal.core.news.database import com.querydsl.core.BooleanBuilder import com.querydsl.jpa.impl.JPAQueryFactory import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.common.repository.CommonRepository import com.wafflestudio.csereal.common.utils.FixedPageRequest import com.wafflestudio.csereal.common.utils.cleanTextFromHtml import com.wafflestudio.csereal.core.news.database.QNewsEntity.newsEntity @@ -29,6 +30,7 @@ interface CustomNewsRepository { class NewsRepositoryImpl( private val queryFactory: JPAQueryFactory, private val mainImageService: MainImageService, + private val commonRepository: CommonRepository, ) : CustomNewsRepository { override fun searchNews( tag: List?, @@ -40,17 +42,12 @@ class NewsRepositoryImpl( val tagsBooleanBuilder = BooleanBuilder() if (!keyword.isNullOrEmpty()) { - val keywordList = keyword.split("[^a-zA-Z0-9가-힣]".toRegex()) - keywordList.forEach { - if (it.length == 1) { - throw CserealException.Csereal400("각각의 키워드는 한글자 이상이어야 합니다.") - } else { - keywordBooleanBuilder.and( - newsEntity.title.contains(it) - .or(newsEntity.description.contains(it)) - ) - } - } + val booleanTemplate = commonRepository.searchFullDoubleTextTemplate( + keyword, + newsEntity.title, + newsEntity.plainTextDescription, + ) + keywordBooleanBuilder.and(booleanTemplate.gt(0.0)) } if (!tag.isNullOrEmpty()) { tag.forEach { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt index 7f4207c9..d1bf7f6f 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt @@ -2,7 +2,8 @@ package com.wafflestudio.csereal.core.notice.database import com.querydsl.core.BooleanBuilder import com.querydsl.jpa.impl.JPAQueryFactory -import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.common.repository.CommonRepository +import com.wafflestudio.csereal.common.repository.CommonRepositoryImpl import com.wafflestudio.csereal.common.utils.FixedPageRequest import com.wafflestudio.csereal.core.notice.database.QNoticeEntity.noticeEntity import com.wafflestudio.csereal.core.notice.database.QNoticeTagEntity.noticeTagEntity @@ -11,7 +12,6 @@ import com.wafflestudio.csereal.core.notice.dto.NoticeSearchResponse import com.wafflestudio.csereal.core.user.database.Role import com.wafflestudio.csereal.core.user.database.UserEntity import com.wafflestudio.csereal.core.user.database.UserRepository -import org.springframework.data.domain.PageRequest import org.springframework.data.domain.Pageable import org.springframework.data.jpa.repository.JpaRepository import org.springframework.security.core.context.SecurityContextHolder @@ -20,7 +20,6 @@ import org.springframework.stereotype.Component import org.springframework.web.context.request.RequestAttributes import org.springframework.web.context.request.RequestContextHolder import java.time.LocalDateTime -import kotlin.math.ceil interface NoticeRepository : JpaRepository, CustomNoticeRepository { fun findAllByIsImportant(isImportant: Boolean): List @@ -40,8 +39,9 @@ interface CustomNoticeRepository { @Component class NoticeRepositoryImpl( - private val queryFactory: JPAQueryFactory, - private val userRepository: UserRepository, + private val queryFactory: JPAQueryFactory, + private val userRepository: UserRepository, + private val commonRepository: CommonRepository, ) : CustomNoticeRepository { override fun searchNotice( tag: List?, @@ -50,24 +50,34 @@ class NoticeRepositoryImpl( usePageBtn: Boolean, isStaff: Boolean ): NoticeSearchResponse { + var user = RequestContextHolder.getRequestAttributes()?.getAttribute( + "loggedInUser", + RequestAttributes.SCOPE_REQUEST + ) as UserEntity? + + if (user == null) { + val oidcUser = SecurityContextHolder.getContext().authentication.principal as OidcUser + val username = oidcUser.idToken.getClaim("username") + + if (userRepository.findByUsername(username) == null) { + user = null + } else { + user = userRepository.findByUsername(username) + } + } val keywordBooleanBuilder = BooleanBuilder() val tagsBooleanBuilder = BooleanBuilder() val isPrivateBooleanBuilder = BooleanBuilder() if (!keyword.isNullOrEmpty()) { - val keywordList = keyword.split("[^a-zA-Z0-9가-힣]".toRegex()) - keywordList.forEach { - if (it.length == 1) { - throw CserealException.Csereal400("각각의 키워드는 한글자 이상이어야 합니다.") - } else { - keywordBooleanBuilder.and( - noticeEntity.title.contains(it) - .or(noticeEntity.description.contains(it)) - ) - } - - } + val booleanTemplate = commonRepository.searchFullDoubleTextTemplate( + keyword, + noticeEntity.title, + noticeEntity.plainTextDescription, + ) + keywordBooleanBuilder.and(booleanTemplate.gt(0.0)) } + if (!tag.isNullOrEmpty()) { tag.forEach { val tagEnum = TagInNoticeEnum.getTagEnum(it) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt index 4043fd4b..0348b32d 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt @@ -3,6 +3,7 @@ package com.wafflestudio.csereal.core.seminar.database import com.querydsl.core.BooleanBuilder import com.querydsl.jpa.impl.JPAQueryFactory import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.common.repository.CommonRepository import com.wafflestudio.csereal.common.utils.FixedPageRequest import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService import com.wafflestudio.csereal.core.seminar.database.QSeminarEntity.seminarEntity @@ -27,24 +28,23 @@ interface CustomSeminarRepository { class SeminarRepositoryImpl( private val queryFactory: JPAQueryFactory, private val mainImageService: MainImageService, + private val commonRepository: CommonRepository, ) : CustomSeminarRepository { override fun searchSeminar(keyword: String?, pageable: Pageable, usePageBtn: Boolean): SeminarSearchResponse { val keywordBooleanBuilder = BooleanBuilder() if (!keyword.isNullOrEmpty()) { - val keywordList = keyword.split("[^a-zA-Z0-9가-힣]".toRegex()) - keywordList.forEach { - if (it.length == 1) { - throw CserealException.Csereal400("각각의 키워드는 한글자 이상이어야 합니다.") - } else { - keywordBooleanBuilder.and( - seminarEntity.title.contains(it) - .or(seminarEntity.name.contains(it)) - .or(seminarEntity.affiliation.contains(it)) - .or(seminarEntity.location.contains(it)) - ) - } - } + val booleanTemplate = commonRepository.searchFullSeptupleTextTemplate( + keyword, + seminarEntity.title, + seminarEntity.name, + seminarEntity.affiliation, + seminarEntity.location, + seminarEntity.plainTextDescription, + seminarEntity.plainTextIntroduction, + seminarEntity.plainTextAdditionalNote, + ) + keywordBooleanBuilder.and(booleanTemplate.gt(0.0)) } val jpaQuery = queryFactory.selectFrom(seminarEntity) From 8b9d383b415841efc1d0901aeb7998b702101a9f Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Tue, 12 Sep 2023 20:03:14 +0900 Subject: [PATCH 079/124] =?UTF-8?q?feat:=20api=20=EA=B6=8C=ED=95=9C=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20(#84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../csereal/core/about/api/AboutController.kt | 14 ++++++++------ .../core/academics/api/AcademicsController.kt | 12 ++++++++---- .../csereal/core/admin/api/AdminController.kt | 7 ++++++- .../core/admissions/api/AdmissionsController.kt | 15 ++++++++------- .../core/member/api/ProfessorController.kt | 4 ++++ .../csereal/core/member/api/StaffController.kt | 6 +++++- .../csereal/core/news/api/NewsController.kt | 5 +++++ .../csereal/core/notice/api/NoticeController.kt | 5 +++++ .../core/research/api/ResearchController.kt | 5 ++++- .../core/resource/mainImage/api/FileController.kt | 2 ++ .../csereal/core/seminar/api/SeminarController.kt | 5 +++++ 11 files changed, 60 insertions(+), 20 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt index 22c92fb8..0ec2a655 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt @@ -1,5 +1,6 @@ package com.wafflestudio.csereal.core.about.api +import com.wafflestudio.csereal.common.aop.AuthenticatedStaff import com.wafflestudio.csereal.core.about.dto.AboutDto import com.wafflestudio.csereal.core.about.dto.CompanyDto import com.wafflestudio.csereal.core.about.dto.FutureCareersPage @@ -19,13 +20,14 @@ class AboutController( // postType: directions / name -> by-public-transit, by-car, from-far-away // Todo: 전체 image, file, 학부장 인사말(greetings) signature + @AuthenticatedStaff @PostMapping("/{postType}") fun createAbout( @PathVariable postType: String, @Valid @RequestPart("request") request: AboutDto, @RequestPart("mainImage") mainImage: MultipartFile?, @RequestPart("attachments") attachments: List?, - ) : ResponseEntity { + ): ResponseEntity { return ResponseEntity.ok(aboutService.createAbout(postType, request, mainImage, attachments)) } @@ -33,23 +35,23 @@ class AboutController( @GetMapping("/{postType}") fun readAbout( @PathVariable postType: String, - ) : ResponseEntity { + ): ResponseEntity { return ResponseEntity.ok(aboutService.readAbout(postType)) } @GetMapping("/student-clubs") - fun readAllClubs() : ResponseEntity> { + fun readAllClubs(): ResponseEntity> { return ResponseEntity.ok(aboutService.readAllClubs()) } @GetMapping("/facilities") - fun readAllFacilities() : ResponseEntity> { + fun readAllFacilities(): ResponseEntity> { return ResponseEntity.ok(aboutService.readAllFacilities()) } @GetMapping("/directions") - fun readAllDirections() : ResponseEntity> { + fun readAllDirections(): ResponseEntity> { return ResponseEntity.ok(aboutService.readAllDirections()) } @GetMapping("/future-careers") @@ -57,4 +59,4 @@ class AboutController( return ResponseEntity.ok(aboutService.readFutureCareers()) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt index 28f9d1ab..11b52c29 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt @@ -1,5 +1,6 @@ package com.wafflestudio.csereal.core.academics.api +import com.wafflestudio.csereal.common.aop.AuthenticatedStaff import com.wafflestudio.csereal.core.academics.dto.* import com.wafflestudio.csereal.core.academics.service.AcademicsService import com.wafflestudio.csereal.core.academics.dto.ScholarshipDto @@ -13,13 +14,14 @@ import org.springframework.web.multipart.MultipartFile class AcademicsController( private val academicsService: AcademicsService ) { + @AuthenticatedStaff @PostMapping("/{studentType}/{postType}") fun createAcademics( @PathVariable studentType: String, @PathVariable postType: String, @Valid @RequestPart("request") request: AcademicsDto, @RequestPart("attachments") attachments: List? - ) : ResponseEntity { + ): ResponseEntity { return ResponseEntity.ok(academicsService.createAcademics(studentType, postType, request, attachments)) } @@ -39,12 +41,13 @@ class AcademicsController( } //교과목 정보 + @AuthenticatedStaff @PostMapping("/{studentType}/course") fun createCourse( @PathVariable studentType: String, @Valid @RequestPart("request") request: CourseDto, @RequestPart("attachments") attachments: List?, - ) : ResponseEntity { + ): ResponseEntity { return ResponseEntity.ok(academicsService.createCourse(studentType, request, attachments)) } @@ -63,15 +66,16 @@ class AcademicsController( } @GetMapping("/undergraduate/general-studies-requirements") - fun readGeneralStudiesRequirements() : ResponseEntity { + fun readGeneralStudiesRequirements(): ResponseEntity { return ResponseEntity.ok(academicsService.readGeneralStudies()) } + @AuthenticatedStaff @PostMapping("/{studentType}/scholarshipDetail") fun createScholarshipDetail( @PathVariable studentType: String, @Valid @RequestBody request: ScholarshipDto, - ) : ResponseEntity { + ): ResponseEntity { return ResponseEntity.ok(academicsService.createScholarshipDetail(studentType, request)) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/api/AdminController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/api/AdminController.kt index 73229cbb..737e9db8 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admin/api/AdminController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/api/AdminController.kt @@ -1,5 +1,6 @@ package com.wafflestudio.csereal.core.admin.api +import com.wafflestudio.csereal.common.aop.AuthenticatedStaff import com.wafflestudio.csereal.core.admin.dto.* import com.wafflestudio.csereal.core.admin.service.AdminService import org.springframework.http.ResponseEntity @@ -15,6 +16,7 @@ import org.springframework.web.bind.annotation.RestController class AdminController( private val adminService: AdminService ) { + @AuthenticatedStaff @GetMapping("/slide") fun readAllSlides( @RequestParam(required = false, defaultValue = "0") pageNum: Long @@ -22,6 +24,7 @@ class AdminController( return ResponseEntity.ok(adminService.readAllSlides(pageNum)) } + @AuthenticatedStaff @PatchMapping("/slide") fun unSlideManyNews( @RequestBody request: NewsIdListRequest @@ -29,6 +32,7 @@ class AdminController( adminService.unSlideManyNews(request.newsIdList) } + @AuthenticatedStaff @GetMapping("/important") fun readAllImportants( @RequestParam(required = false, defaultValue = "0") pageNum: Long @@ -36,6 +40,7 @@ class AdminController( return ResponseEntity.ok(adminService.readAllImportants(pageNum)) } + @AuthenticatedStaff @PatchMapping("/important") fun makeNotImportants( @RequestBody request: ImportantRequest @@ -44,4 +49,4 @@ class AdminController( } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/AdmissionsController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/AdmissionsController.kt index 25798f87..a5b1c2fc 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/AdmissionsController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/AdmissionsController.kt @@ -1,5 +1,6 @@ package com.wafflestudio.csereal.core.admissions.api +import com.wafflestudio.csereal.common.aop.AuthenticatedStaff import com.wafflestudio.csereal.core.admissions.dto.AdmissionsDto import com.wafflestudio.csereal.core.admissions.service.AdmissionsService import jakarta.validation.Valid @@ -17,34 +18,34 @@ import org.springframework.web.bind.annotation.RestController class AdmissionsController( private val admissionsService: AdmissionsService ) { + @AuthenticatedStaff @PostMapping("/undergraduate/{postType}") fun createUndergraduateAdmissions( @PathVariable postType: String, @Valid @RequestBody request: AdmissionsDto - ) : AdmissionsDto { + ): AdmissionsDto { return admissionsService.createUndergraduateAdmissions(postType, request) } + @AuthenticatedStaff @PostMapping("/graduate") fun createGraduateAdmissions( @Valid @RequestBody request: AdmissionsDto - ) : AdmissionsDto { + ): AdmissionsDto { return admissionsService.createGraduateAdmissions(request) } @GetMapping("/undergraduate/{postType}") fun readUndergraduateAdmissions( @PathVariable postType: String - ) : ResponseEntity { + ): ResponseEntity { return ResponseEntity.ok(admissionsService.readUndergraduateAdmissions(postType)) } @GetMapping("/graduate") - fun readGraduateAdmissions() : ResponseEntity { + fun readGraduateAdmissions(): ResponseEntity { return ResponseEntity.ok(admissionsService.readGraduateAdmissions()) } - - -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt index fa71dd42..d1e4101c 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt @@ -1,5 +1,6 @@ package com.wafflestudio.csereal.core.member.api +import com.wafflestudio.csereal.common.aop.AuthenticatedStaff import com.wafflestudio.csereal.core.member.dto.ProfessorDto import com.wafflestudio.csereal.core.member.dto.ProfessorPageDto import com.wafflestudio.csereal.core.member.dto.SimpleProfessorDto @@ -14,6 +15,7 @@ class ProfessorController( private val professorService: ProfessorService ) { + @AuthenticatedStaff @PostMapping fun createProfessor( @RequestPart("request") createProfessorRequest: ProfessorDto, @@ -37,6 +39,7 @@ class ProfessorController( return ResponseEntity.ok(professorService.getInactiveProfessors()) } + @AuthenticatedStaff @PatchMapping("/{professorId}") fun updateProfessor( @PathVariable professorId: Long, @@ -46,6 +49,7 @@ class ProfessorController( return ResponseEntity.ok(professorService.updateProfessor(professorId, updateProfessorRequest, mainImage)) } + @AuthenticatedStaff @DeleteMapping("/{professorId}") fun deleteProfessor(@PathVariable professorId: Long): ResponseEntity { professorService.deleteProfessor(professorId) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt index b9584fb0..5b192953 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt @@ -1,5 +1,6 @@ package com.wafflestudio.csereal.core.member.api +import com.wafflestudio.csereal.common.aop.AuthenticatedStaff import com.wafflestudio.csereal.core.member.dto.SimpleStaffDto import com.wafflestudio.csereal.core.member.dto.StaffDto import com.wafflestudio.csereal.core.member.service.StaffService @@ -13,12 +14,13 @@ class StaffController( private val staffService: StaffService ) { + @AuthenticatedStaff @PostMapping fun createStaff( @RequestPart("request") createStaffRequest: StaffDto, @RequestPart("mainImage") mainImage: MultipartFile?, ): ResponseEntity { - return ResponseEntity.ok(staffService.createStaff(createStaffRequest,mainImage)) + return ResponseEntity.ok(staffService.createStaff(createStaffRequest, mainImage)) } @GetMapping("/{staffId}") @@ -31,6 +33,7 @@ class StaffController( return ResponseEntity.ok(staffService.getAllStaff()) } + @AuthenticatedStaff fun updateStaff( @PathVariable staffId: Long, @RequestPart("request") updateStaffRequest: StaffDto, @@ -39,6 +42,7 @@ class StaffController( return ResponseEntity.ok(staffService.updateStaff(staffId, updateStaffRequest, mainImage)) } + @AuthenticatedStaff @DeleteMapping("/{staffId}") fun deleteStaff(@PathVariable staffId: Long): ResponseEntity { staffService.deleteStaff(staffId) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt index 802ce6dc..130f1e35 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt @@ -1,5 +1,6 @@ package com.wafflestudio.csereal.core.news.api +import com.wafflestudio.csereal.common.aop.AuthenticatedStaff import com.wafflestudio.csereal.core.news.dto.NewsDto import com.wafflestudio.csereal.core.news.dto.NewsSearchResponse import com.wafflestudio.csereal.core.news.service.NewsService @@ -34,6 +35,7 @@ class NewsController( return ResponseEntity.ok(newsService.readNews(newsId)) } + @AuthenticatedStaff @PostMapping fun createNews( @Valid @RequestPart("request") request: NewsDto, @@ -43,6 +45,7 @@ class NewsController( return ResponseEntity.ok(newsService.createNews(request, mainImage, attachments)) } + @AuthenticatedStaff @PatchMapping("/{newsId}") fun updateNews( @PathVariable newsId: Long, @@ -53,6 +56,7 @@ class NewsController( return ResponseEntity.ok(newsService.updateNews(newsId, request, mainImage, attachments)) } + @AuthenticatedStaff @DeleteMapping("/{newsId}") fun deleteNews( @PathVariable newsId: Long @@ -60,6 +64,7 @@ class NewsController( newsService.deleteNews(newsId) } + @AuthenticatedStaff @PostMapping("/tag") fun enrollTag( @RequestBody tagName: Map diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt index 0d3d8b5b..fe286a52 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt @@ -57,6 +57,7 @@ class NoticeController( return ResponseEntity.ok(noticeService.createNotice(request, attachments)) } + @AuthenticatedStaff @PatchMapping("/{noticeId}") fun updateNotice( @PathVariable noticeId: Long, @@ -66,6 +67,7 @@ class NoticeController( return ResponseEntity.ok(noticeService.updateNotice(noticeId, request, attachments)) } + @AuthenticatedStaff @DeleteMapping("/{noticeId}") fun deleteNotice( @PathVariable noticeId: Long @@ -73,6 +75,7 @@ class NoticeController( noticeService.deleteNotice(noticeId) } + @AuthenticatedStaff @PatchMapping fun unpinManyNotices( @RequestBody request: NoticeIdListRequest @@ -80,6 +83,7 @@ class NoticeController( noticeService.unpinManyNotices(request.idList) } + @AuthenticatedStaff @DeleteMapping fun deleteManyNotices( @RequestBody request: NoticeIdListRequest @@ -87,6 +91,7 @@ class NoticeController( noticeService.deleteManyNotices(request.idList) } + @AuthenticatedStaff @PostMapping("/tag") fun enrollTag( @RequestBody tagName: Map diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt index 69772609..f0b9d680 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt @@ -16,6 +16,7 @@ import org.springframework.web.multipart.MultipartFile class ResearchController( private val researchService: ResearchService ) { + @AuthenticatedStaff @PostMapping fun createResearchDetail( @Valid @RequestPart("request") request: ResearchDto, @@ -35,6 +36,7 @@ class ResearchController( return ResponseEntity.ok(researchService.readAllResearchCenters()) } + @AuthenticatedStaff @PatchMapping("/{researchId}") fun updateResearchDetail( @PathVariable researchId: Long, @@ -45,6 +47,7 @@ class ResearchController( return ResponseEntity.ok(researchService.updateResearchDetail(researchId, request, mainImage, attachments)) } + @AuthenticatedStaff @PostMapping("/lab") fun createLab( @Valid @RequestPart("request") request: LabDto, @@ -77,4 +80,4 @@ class ResearchController( ): ResponseEntity { return ResponseEntity.ok(researchService.updateLab(labId, request, pdf)) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/FileController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/FileController.kt index a8ffc8d2..0cb63ec7 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/FileController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/FileController.kt @@ -1,5 +1,6 @@ package com.wafflestudio.csereal.core.resource.mainImage.api +import com.wafflestudio.csereal.common.aop.AuthenticatedStaff import jakarta.servlet.http.HttpServletRequest import org.springframework.beans.factory.annotation.Value import org.springframework.core.io.Resource @@ -53,6 +54,7 @@ class FileController( } } + @AuthenticatedStaff @DeleteMapping("/{filename:.+}") fun deleteFile(@PathVariable filename: String): ResponseEntity { val file = Paths.get(uploadPath, filename) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt index 4a494b2e..5e61ee32 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt @@ -1,5 +1,7 @@ package com.wafflestudio.csereal.core.seminar.api +import com.wafflestudio.csereal.common.aop.AuthenticatedStaff +import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse import com.wafflestudio.csereal.core.seminar.dto.SeminarDto import com.wafflestudio.csereal.core.seminar.dto.SeminarSearchResponse import com.wafflestudio.csereal.core.seminar.service.SeminarService @@ -25,6 +27,7 @@ class SeminarController( return ResponseEntity.ok(seminarService.searchSeminar(keyword, pageRequest, usePageBtn)) } + @AuthenticatedStaff @PostMapping fun createSeminar( @Valid @RequestPart("request") request: SeminarDto, @@ -41,6 +44,7 @@ class SeminarController( return ResponseEntity.ok(seminarService.readSeminar(seminarId)) } + @AuthenticatedStaff @PatchMapping("/{seminarId}") fun updateSeminar( @PathVariable seminarId: Long, @@ -60,6 +64,7 @@ class SeminarController( ) } + @AuthenticatedStaff @DeleteMapping("/{seminarId}") fun deleteSeminar( @PathVariable seminarId: Long From 236af47866e2535e613b63d4a26ab2964b38bd1b Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Tue, 12 Sep 2023 20:39:35 +0900 Subject: [PATCH 080/124] =?UTF-8?q?fix:=20GET=20notice=20=EA=B6=8C?= =?UTF-8?q?=ED=95=9C=20=EA=B4=80=EB=A0=A8=20=EB=A1=9C=EC=A7=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#109)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/notice/database/NoticeRepository.kt | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt index d1bf7f6f..92cc19f2 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt @@ -50,21 +50,6 @@ class NoticeRepositoryImpl( usePageBtn: Boolean, isStaff: Boolean ): NoticeSearchResponse { - var user = RequestContextHolder.getRequestAttributes()?.getAttribute( - "loggedInUser", - RequestAttributes.SCOPE_REQUEST - ) as UserEntity? - - if (user == null) { - val oidcUser = SecurityContextHolder.getContext().authentication.principal as OidcUser - val username = oidcUser.idToken.getClaim("username") - - if (userRepository.findByUsername(username) == null) { - user = null - } else { - user = userRepository.findByUsername(username) - } - } val keywordBooleanBuilder = BooleanBuilder() val tagsBooleanBuilder = BooleanBuilder() val isPrivateBooleanBuilder = BooleanBuilder() From ec8010297b105253fcf44a865e9ba0ff62edf89b Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Tue, 12 Sep 2023 21:29:58 +0900 Subject: [PATCH 081/124] =?UTF-8?q?fix:=20=ED=95=84=EC=9A=94=ED=95=9C=20co?= =?UTF-8?q?lumn=EB=A7=8C=20select=20(#111)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/notice/database/NoticeRepository.kt | 45 ++++++++----------- .../core/notice/dto/NoticeSearchDto.kt | 11 ++++- 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt index 92cc19f2..ab2dbb81 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt @@ -1,24 +1,17 @@ package com.wafflestudio.csereal.core.notice.database import com.querydsl.core.BooleanBuilder +import com.querydsl.core.types.Projections import com.querydsl.jpa.impl.JPAQueryFactory import com.wafflestudio.csereal.common.repository.CommonRepository -import com.wafflestudio.csereal.common.repository.CommonRepositoryImpl import com.wafflestudio.csereal.common.utils.FixedPageRequest import com.wafflestudio.csereal.core.notice.database.QNoticeEntity.noticeEntity import com.wafflestudio.csereal.core.notice.database.QNoticeTagEntity.noticeTagEntity import com.wafflestudio.csereal.core.notice.dto.NoticeSearchDto import com.wafflestudio.csereal.core.notice.dto.NoticeSearchResponse -import com.wafflestudio.csereal.core.user.database.Role -import com.wafflestudio.csereal.core.user.database.UserEntity -import com.wafflestudio.csereal.core.user.database.UserRepository import org.springframework.data.domain.Pageable import org.springframework.data.jpa.repository.JpaRepository -import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.security.oauth2.core.oidc.user.OidcUser import org.springframework.stereotype.Component -import org.springframework.web.context.request.RequestAttributes -import org.springframework.web.context.request.RequestContextHolder import java.time.LocalDateTime interface NoticeRepository : JpaRepository, CustomNoticeRepository { @@ -39,9 +32,8 @@ interface CustomNoticeRepository { @Component class NoticeRepositoryImpl( - private val queryFactory: JPAQueryFactory, - private val userRepository: UserRepository, - private val commonRepository: CommonRepository, + private val queryFactory: JPAQueryFactory, + private val commonRepository: CommonRepository, ) : CustomNoticeRepository { override fun searchNotice( tag: List?, @@ -56,9 +48,9 @@ class NoticeRepositoryImpl( if (!keyword.isNullOrEmpty()) { val booleanTemplate = commonRepository.searchFullDoubleTextTemplate( - keyword, - noticeEntity.title, - noticeEntity.plainTextDescription, + keyword, + noticeEntity.title, + noticeEntity.plainTextDescription, ) keywordBooleanBuilder.and(booleanTemplate.gt(0.0)) } @@ -78,7 +70,17 @@ class NoticeRepositoryImpl( ) } - val jpaQuery = queryFactory.selectFrom(noticeEntity) + val jpaQuery = queryFactory.select( + Projections.constructor( + NoticeSearchDto::class.java, + noticeEntity.id, + noticeEntity.title, + noticeEntity.createdAt, + noticeEntity.isPinned, + noticeEntity.attachments.isNotEmpty + ) + ) + .from(noticeEntity) .leftJoin(noticeTagEntity).on(noticeTagEntity.notice.eq(noticeEntity)) .where(noticeEntity.isDeleted.eq(false)) .where(keywordBooleanBuilder, tagsBooleanBuilder, isPrivateBooleanBuilder) @@ -94,7 +96,7 @@ class NoticeRepositoryImpl( total = (10 * pageable.pageSize).toLong() // 10개 페이지 고정 } - val noticeEntityList = jpaQuery + val noticeSearchDtoList = jpaQuery .orderBy(noticeEntity.isPinned.desc()) .orderBy(noticeEntity.createdAt.desc()) .offset(pageRequest.offset) @@ -102,17 +104,6 @@ class NoticeRepositoryImpl( .distinct() .fetch() - val noticeSearchDtoList: List = noticeEntityList.map { - val hasAttachment: Boolean = it.attachments.isNotEmpty() - - NoticeSearchDto( - id = it.id, - title = it.title, - createdAt = it.createdAt, - isPinned = it.isPinned, - hasAttachment = hasAttachment - ) - } return NoticeSearchResponse(total, noticeSearchDtoList) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeSearchDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeSearchDto.kt index 7881d082..c093e37c 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeSearchDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeSearchDto.kt @@ -1,6 +1,7 @@ package com.wafflestudio.csereal.core.notice.dto import com.querydsl.core.annotations.QueryProjection +import com.wafflestudio.csereal.core.notice.database.NoticeEntity import java.time.LocalDateTime data class NoticeSearchDto @QueryProjection constructor( @@ -10,5 +11,11 @@ data class NoticeSearchDto @QueryProjection constructor( val isPinned: Boolean, val hasAttachment: Boolean, ) { - -} \ No newline at end of file + constructor(entity: NoticeEntity, hasAttachment: Boolean) : this( + entity.id, + entity.title, + entity.createdAt, + entity.isPinned, + hasAttachment + ) +} From 5a83833a6ac674d5849d0d58e14cefee2bada50d Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Wed, 13 Sep 2023 13:45:14 +0900 Subject: [PATCH 082/124] =?UTF-8?q?refactor:=20=EA=B3=B5=EC=A7=80=20?= =?UTF-8?q?=ED=83=9C=EA=B7=B8=20=EC=9D=91=EB=8B=B5=20=ED=95=9C=EA=B5=AD?= =?UTF-8?q?=EC=96=B4=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81=20(#113)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 공지 태그 응답 한국어로 수정 및 리팩토링 * refactor: 새소식 태그 리팩토링 * fix: NewsRepository --- .../core/news/database/NewsRepository.kt | 4 +- .../core/news/database/TagInNewsEnum.kt | 38 ++++--------------- .../csereal/core/news/dto/NewsDto.kt | 3 +- .../core/notice/database/TagInNoticeEnum.kt | 27 ++++--------- .../csereal/core/notice/dto/NoticeDto.kt | 2 +- 5 files changed, 19 insertions(+), 55 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt index 188fb41d..c65a17c8 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt @@ -88,10 +88,10 @@ class NewsRepositoryImpl( title = it.title, description = it.plainTextDescription, createdAt = it.createdAt, - tags = it.newsTags.map { newsTagEntity -> TagInNewsEnum.getTagString(newsTagEntity.tag.name) }, + tags = it.newsTags.map { newsTagEntity -> newsTagEntity.tag.name.krName }, imageURL = imageURL ) } - return NewsSearchResponse(total!!, newsSearchDtoList) + return NewsSearchResponse(total, newsSearchDtoList) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/TagInNewsEnum.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/TagInNewsEnum.kt index 2e5749a7..9b6f492a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/TagInNewsEnum.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/TagInNewsEnum.kt @@ -2,39 +2,15 @@ package com.wafflestudio.csereal.core.news.database import com.wafflestudio.csereal.common.CserealException -enum class TagInNewsEnum { - EVENT, RESEARCH, AWARDS, RECRUIT, COLUMN, LECTURE, EDUCATION, INTERVIEW, CAREER, UNCLASSIFIED; +enum class TagInNewsEnum(val krName: String) { + EVENT("행사"), RESEARCH("연구"), AWARDS("수상"), RECRUIT("채용"), COLUMN("칼럼"), + LECTURE("강연"), EDUCATION("교육"), INTERVIEW("인터뷰"), CAREER("진로"), UNCLASSIFIED("과거 미분류"); companion object { - fun getTagEnum(t: String) : TagInNewsEnum { - return when (t) { - "행사" -> EVENT - "연구" -> RESEARCH - "수상" -> AWARDS - "채용" -> RECRUIT - "칼럼" -> COLUMN - "강연" -> LECTURE - "교육" -> EDUCATION - "인터뷰" -> INTERVIEW - "진로" -> CAREER - "과거 미분류" -> UNCLASSIFIED - else -> throw CserealException.Csereal404("태그를 찾을 수 없습니다") - } - } + private val lookupMap: Map = TagInNewsEnum.values().associateBy(TagInNewsEnum::krName) - fun getTagString(t: TagInNewsEnum): String { - return when (t) { - EVENT -> "행사" - RESEARCH -> "연구" - AWARDS -> "수상" - RECRUIT -> "채용" - COLUMN -> "칼럼" - LECTURE -> "강연" - EDUCATION -> "교육" - INTERVIEW -> "인터뷰" - CAREER -> "진로" - UNCLASSIFIED -> "과거 미분류" - } + fun getTagEnum(t: String): TagInNewsEnum { + return lookupMap[t] ?: throw CserealException.Csereal404("태그를 찾을 수 없습니다: $t") } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt index 76b0ee6f..71105b6a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt @@ -1,7 +1,6 @@ package com.wafflestudio.csereal.core.news.dto import com.wafflestudio.csereal.core.news.database.NewsEntity -import com.wafflestudio.csereal.core.news.database.TagInNewsEnum import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse import java.time.LocalDateTime @@ -34,7 +33,7 @@ data class NewsDto( id = this.id, title = this.title, description = this.description, - tags = this.newsTags.map { TagInNewsEnum.getTagString(it.tag.name) }, + tags = this.newsTags.map { it.tag.name.krName }, createdAt = this.createdAt, modifiedAt = this.modifiedAt, isPrivate = this.isPrivate, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeEnum.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeEnum.kt index a661a130..d94f0faa 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeEnum.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeEnum.kt @@ -2,28 +2,17 @@ package com.wafflestudio.csereal.core.notice.database import com.wafflestudio.csereal.common.CserealException -enum class TagInNoticeEnum { - CLASS, SCHOLARSHIP, UNDERGRADUATE, GRADUATE, MINOR, REGISTRATIONS, ADMISSIONS, GRADUATIONS, - RECRUIT, STUDENT_EXCHANGE, INNER_EVENTS_PROGRAMS, OUTER_EVENTS_PROGRAMS, FOREIGN; +enum class TagInNoticeEnum(val krName: String) { + CLASS("수업"), SCHOLARSHIP("장학"), UNDERGRADUATE("학사(학부)"), GRADUATE("학사(대학원)"), + MINOR("다전공/전과"), REGISTRATIONS("등록/복학/휴학/재입학"), ADMISSIONS("입학"), GRADUATIONS("졸업"), + RECRUIT("채용정보"), STUDENT_EXCHANGE("교환학생/유학"), INNER_EVENTS_PROGRAMS("내부행사/프로그램"), + OUTER_EVENTS_PROGRAMS("외부행사/프로그램"), FOREIGN("foreign"); companion object { + private val lookupMap: Map = values().associateBy(TagInNoticeEnum::krName) + fun getTagEnum(t: String): TagInNoticeEnum { - return when (t) { - "수업" -> CLASS - "장학" -> SCHOLARSHIP - "학사(학부)" -> UNDERGRADUATE - "학사(대학원)" -> GRADUATE - "다전공/전과" -> MINOR - "등록/복학/휴학/재입학" -> REGISTRATIONS - "입학" -> ADMISSIONS - "졸업" -> GRADUATIONS - "채용정보" -> RECRUIT - "교환학생/유학" -> STUDENT_EXCHANGE - "내부행사/프로그램" -> INNER_EVENTS_PROGRAMS - "외부행사/프로그램" -> OUTER_EVENTS_PROGRAMS - "foreign" -> FOREIGN - else -> throw CserealException.Csereal404("태그를 찾을 수 없습니다") - } + return lookupMap[t] ?: throw CserealException.Csereal404("태그를 찾을 수 없습니다: $t") } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt index a747b934..f8e3c8c3 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt @@ -35,7 +35,7 @@ data class NoticeDto( title = this.title, description = this.description, author = this.author.name, - tags = this.noticeTags.map { it.tag.name.name }, + tags = this.noticeTags.map { it.tag.name.krName }, createdAt = this.createdAt, modifiedAt = this.modifiedAt, isPrivate = this.isPrivate, From e6550d288b977c4ae58d9ef342dae48059c147ac Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Thu, 14 Sep 2023 00:34:38 +0900 Subject: [PATCH 083/124] =?UTF-8?q?fix:=20=EA=B3=B5=EC=A7=80=20=EC=83=88?= =?UTF-8?q?=EC=86=8C=EC=8B=9D=20PATCH=20=EB=A1=9C=EC=A7=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#114)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 공지 새소식 PATCH 로직 수정 * test: 테스트 수정 --- .../csereal/core/news/api/NewsController.kt | 7 +- .../csereal/core/news/service/NewsService.kt | 27 +++--- .../core/notice/api/NoticeController.kt | 5 +- .../core/notice/service/NoticeService.kt | 24 +++-- .../core/notice/news/NewsServiceTest.kt | 66 +++++++------- .../core/notice/service/NoticeServiceTest.kt | 87 ++++++++++--------- 6 files changed, 116 insertions(+), 100 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt index 130f1e35..80ad83be 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt @@ -50,10 +50,11 @@ class NewsController( fun updateNews( @PathVariable newsId: Long, @Valid @RequestPart("request") request: NewsDto, - @RequestPart("mainImage") mainImage: MultipartFile?, - @RequestPart("attachments") attachments: List? + @RequestPart("newMainImage") newMainImage: MultipartFile?, + @RequestPart("newAttachments") newAttachments: List?, + @RequestPart("deleteIds") deleteIds: List, ): ResponseEntity { - return ResponseEntity.ok(newsService.updateNews(newsId, request, mainImage, attachments)) + return ResponseEntity.ok(newsService.updateNews(newsId, request, newMainImage, newAttachments, deleteIds)) } @AuthenticatedStaff diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt index d4d2cbce..875471eb 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt @@ -19,8 +19,9 @@ interface NewsService { fun updateNews( newsId: Long, request: NewsDto, - mainImage: MultipartFile?, - attachments: List? + newMainImage: MultipartFile?, + newAttachments: List?, + deleteIds: List, ): NewsDto fun deleteNews(newsId: Long) @@ -91,25 +92,25 @@ class NewsServiceImpl( override fun updateNews( newsId: Long, request: NewsDto, - mainImage: MultipartFile?, - attachments: List? + newMainImage: MultipartFile?, + newAttachments: List?, + deleteIds: List, ): NewsDto { val news: NewsEntity = newsRepository.findByIdOrNull(newsId) ?: throw CserealException.Csereal404("존재하지 않는 새소식입니다. (newsId: $newsId)") if (news.isDeleted) throw CserealException.Csereal404("삭제된 새소식입니다.") + news.update(request) - if (mainImage != null) { - mainImageService.uploadMainImage(news, mainImage) - } else { - news.mainImage = null + if (newMainImage != null) { + news.mainImage?.isDeleted = true + mainImageService.uploadMainImage(news, newMainImage) } - if (attachments != null) { - news.attachments.clear() - attachmentService.uploadAllAttachments(news, attachments) - } else { - news.attachments.clear() + attachmentService.deleteAttachments(deleteIds) + + if (newAttachments != null) { + attachmentService.uploadAllAttachments(news, newAttachments) } val oldTags = news.newsTags.map { it.tag.name } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt index fe286a52..7bcec89f 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt @@ -62,9 +62,10 @@ class NoticeController( fun updateNotice( @PathVariable noticeId: Long, @Valid @RequestPart("request") request: NoticeDto, - @RequestPart("attachments") attachments: List?, + @RequestPart("newAttachments") newAttachments: List?, + @RequestPart("deleteIds") deleteIds: List, ): ResponseEntity { - return ResponseEntity.ok(noticeService.updateNotice(noticeId, request, attachments)) + return ResponseEntity.ok(noticeService.updateNotice(noticeId, request, newAttachments, deleteIds)) } @AuthenticatedStaff diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt index 0c74d3b6..a2f1cdcc 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt @@ -28,7 +28,13 @@ interface NoticeService { fun readNotice(noticeId: Long): NoticeDto fun createNotice(request: NoticeDto, attachments: List?): NoticeDto - fun updateNotice(noticeId: Long, request: NoticeDto, attachments: List?): NoticeDto + fun updateNotice( + noticeId: Long, + request: NoticeDto, + newAttachments: List?, + deleteIds: List + ): NoticeDto + fun deleteNotice(noticeId: Long) fun unpinManyNotices(idList: List) fun deleteManyNotices(idList: List) @@ -113,18 +119,22 @@ class NoticeServiceImpl( } @Transactional - override fun updateNotice(noticeId: Long, request: NoticeDto, attachments: List?): NoticeDto { + override fun updateNotice( + noticeId: Long, + request: NoticeDto, + newAttachments: List?, + deleteIds: List + ): NoticeDto { val notice: NoticeEntity = noticeRepository.findByIdOrNull(noticeId) ?: throw CserealException.Csereal404("존재하지 않는 공지사항입니다.(noticeId: $noticeId)") if (notice.isDeleted) throw CserealException.Csereal404("삭제된 공지사항입니다.(noticeId: $noticeId)") notice.update(request) - if (attachments != null) { - notice.attachments.clear() - attachmentService.uploadAllAttachments(notice, attachments) - } else { - notice.attachments.clear() + attachmentService.deleteAttachments(deleteIds) + + if (newAttachments != null) { + attachmentService.uploadAllAttachments(notice, newAttachments) } val oldTags = notice.noticeTags.map { it.tag.name } diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/notice/news/NewsServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/notice/news/NewsServiceTest.kt index 0971633f..18ad507a 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/notice/news/NewsServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/notice/news/NewsServiceTest.kt @@ -12,8 +12,8 @@ import org.springframework.data.repository.findByIdOrNull @SpringBootTest class NewsServiceTest( - private val newsService: NewsService, - private val newsRepository: NewsRepository, + private val newsService: NewsService, + private val newsRepository: NewsRepository, ) : BehaviorSpec() { init { @@ -23,25 +23,25 @@ class NewsServiceTest( Given("뉴스를 생성하려고 할 때 간단한 뉴스가 주어지면") { val newsDTO = NewsDto( - id = -1, - title = "title", - description = """ + id = -1, + title = "title", + description = """

Hello, World!

This is news description.

Goodbye, World!

""".trimIndent(), - tags = emptyList(), - createdAt = null, - modifiedAt = null, - isPrivate = false, - isSlide = false, - isImportant = false, - prevId = null, - prevTitle = null, - nextId = null, - nextTitle = null, - imageURL = null, - attachments = null, + tags = emptyList(), + createdAt = null, + modifiedAt = null, + isPrivate = false, + isSlide = false, + isImportant = false, + prevId = null, + prevTitle = null, + nextId = null, + nextTitle = null, + imageURL = null, + attachments = null, ) When("DTO를 이용하여 뉴스를 생성하면") { @@ -61,33 +61,35 @@ class NewsServiceTest( Given("간단한 뉴스가 저장되어 있을 때") { val newsEntity = newsRepository.save( - NewsEntity( - title = "title", - description = """ + NewsEntity( + title = "title", + description = """

Hello, World!

This is news description.

Goodbye, World!

""".trimIndent(), - plainTextDescription = "Hello, World! This is news description. Goodbye, World!", - isPrivate = false, - isSlide = false, - isImportant = false, - ) + plainTextDescription = "Hello, World! This is news description. Goodbye, World!", + isPrivate = false, + isSlide = false, + isImportant = false, + ) ) When("저장된 뉴스의 Description을 수정하면") { newsService.updateNews( - newsEntity.id, - NewsDto.of(newsEntity, null, emptyList(), null) - .copy(description = """ + newsEntity.id, + NewsDto.of(newsEntity, null, emptyList(), null) + .copy( + description = """

Hello, World!

This is modified news description.

Goodbye, World!

This is additional description.

""".trimIndent() - ), - null, - null + ), + null, + null, + emptyList() ) Then("description, plainTextDescription이 수정되어야 한다.") { @@ -103,4 +105,4 @@ class NewsServiceTest( } } } -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeServiceTest.kt index 173be98a..fdacee10 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeServiceTest.kt @@ -19,20 +19,20 @@ import org.springframework.web.context.request.RequestContextHolder @SpringBootTest class NoticeServiceTest( - private val noticeService: NoticeService, - private val userRepository: UserRepository, - private val noticeRepository: NoticeRepository, + private val noticeService: NoticeService, + private val userRepository: UserRepository, + private val noticeRepository: NoticeRepository, ) : BehaviorSpec() { init { beforeContainer { userRepository.save( - UserEntity( - "username", - "name", - "email", - "studentId", - Role.ROLE_STAFF - ) + UserEntity( + "username", + "name", + "email", + "studentId", + Role.ROLE_STAFF + ) ) } @@ -51,31 +51,31 @@ class NoticeServiceTest( } returns mockRequestAttributes every { mockRequestAttributes.getAttribute( - "loggedInUser", - RequestAttributes.SCOPE_REQUEST + "loggedInUser", + RequestAttributes.SCOPE_REQUEST ) } returns userEntity val noticeDto = NoticeDto( - id = -1, - title = "title", - description = """ + id = -1, + title = "title", + description = """

Hello, World!

This is a test notice.

Goodbye, World!

""".trimIndent(), - author = "username", - tags = emptyList(), - createdAt = null, - modifiedAt = null, - isPrivate = false, - isPinned = false, - isImportant = false, - prevId = null, - prevTitle = null, - nextId = null, - nextTitle = null, - attachments = null, + author = "username", + tags = emptyList(), + createdAt = null, + modifiedAt = null, + isPrivate = false, + isPinned = false, + isImportant = false, + prevId = null, + prevTitle = null, + nextId = null, + nextTitle = null, + attachments = null, ) When("공지사항을 생성하면") { @@ -93,25 +93,25 @@ class NoticeServiceTest( } Given("기존 간단한 공지사항의 Description을 수정하려고 할 때") { - val noticeEntity = noticeRepository.save ( - NoticeEntity( - title = "title", - description = """ + val noticeEntity = noticeRepository.save( + NoticeEntity( + title = "title", + description = """

Hello, World!

This is a test notice.

Goodbye, World!

""".trimIndent(), - plainTextDescription = "Hello, World! This is a test notice. Goodbye, World!", - isPrivate = false, - isPinned = false, - isImportant = false, - author = userRepository.findByUsername("username")!!, - ) + plainTextDescription = "Hello, World! This is a test notice. Goodbye, World!", + isPrivate = false, + isPinned = false, + isImportant = false, + author = userRepository.findByUsername("username")!!, + ) ) val modifiedRequest = NoticeDto.of( - noticeEntity, emptyList(), null + noticeEntity, emptyList(), null ).copy( - description = """ + description = """

Hello, World!

This is a modified test notice.

Goodbye, World!

@@ -121,9 +121,10 @@ class NoticeServiceTest( When("수정된 DTO를 이용하여 수정하면") { val modifiedNoticeDto = noticeService.updateNotice( - modifiedRequest.id, - modifiedRequest, - null + modifiedRequest.id, + modifiedRequest, + null, + emptyList() ) Then("plainTextDescription이 잘 수정되어야 한다.") { @@ -133,4 +134,4 @@ class NoticeServiceTest( } } } -} \ No newline at end of file +} From 019db1b431501d2c9706d0370e303a6e69b1c888 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Thu, 14 Sep 2023 10:16:23 +0900 Subject: [PATCH 084/124] =?UTF-8?q?fix:=20news=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=EC=97=90=20=EB=82=A0=EC=A7=9C=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?(#115)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../csereal/core/news/database/NewsEntity.kt | 8 ++--- .../core/news/database/NewsRepository.kt | 1 + .../csereal/core/news/dto/NewsDto.kt | 2 ++ .../csereal/core/news/dto/NewsSearchDto.kt | 1 + .../core/notice/news/NewsServiceTest.kt | 36 ++++++++++--------- 5 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt index 9304f224..aab41c93 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt @@ -8,12 +8,12 @@ import com.wafflestudio.csereal.core.news.dto.NewsDto import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity import jakarta.persistence.* +import java.time.LocalDateTime @Entity(name = "news") class NewsEntity( var isDeleted: Boolean = false, - var title: String, @Column(columnDefinition = "mediumtext") @@ -22,10 +22,9 @@ class NewsEntity( @Column(columnDefinition = "mediumtext") var plainTextDescription: String, + var date: LocalDateTime?, var isPrivate: Boolean, - var isSlide: Boolean, - var isImportant: Boolean, @OneToOne @@ -47,6 +46,7 @@ class NewsEntity( title = newsDto.title, description = newsDto.description, plainTextDescription = cleanTextFromHtml(newsDto.description), + date = newsDto.date, isPrivate = newsDto.isPrivate, isSlide = newsDto.isSlide, isImportant = newsDto.isImportant, @@ -59,8 +59,8 @@ class NewsEntity( this.description = updateNewsRequest.description this.plainTextDescription = cleanTextFromHtml(updateNewsRequest.description) } - this.title = updateNewsRequest.title + this.date = updateNewsRequest.date this.isPrivate = updateNewsRequest.isPrivate this.isSlide = updateNewsRequest.isSlide this.isImportant = updateNewsRequest.isImportant diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt index c65a17c8..a413215f 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt @@ -88,6 +88,7 @@ class NewsRepositoryImpl( title = it.title, description = it.plainTextDescription, createdAt = it.createdAt, + date = it.date, tags = it.newsTags.map { newsTagEntity -> newsTagEntity.tag.name.krName }, imageURL = imageURL ) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt index 71105b6a..da0614de 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt @@ -11,6 +11,7 @@ data class NewsDto( val tags: List, val createdAt: LocalDateTime?, val modifiedAt: LocalDateTime?, + val date: LocalDateTime?, val isPrivate: Boolean, val isSlide: Boolean, val isImportant: Boolean, @@ -36,6 +37,7 @@ data class NewsDto( tags = this.newsTags.map { it.tag.name.krName }, createdAt = this.createdAt, modifiedAt = this.modifiedAt, + date = this.date, isPrivate = this.isPrivate, isSlide = this.isSlide, isImportant = this.isImportant, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchDto.kt index 2825753b..81569246 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchDto.kt @@ -8,6 +8,7 @@ data class NewsSearchDto @QueryProjection constructor( val title: String, var description: String, val createdAt: LocalDateTime?, + val date: LocalDateTime?, var tags: List?, var imageURL: String?, ) \ No newline at end of file diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/notice/news/NewsServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/notice/news/NewsServiceTest.kt index 18ad507a..0aec38ee 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/notice/news/NewsServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/notice/news/NewsServiceTest.kt @@ -30,18 +30,19 @@ class NewsServiceTest(

This is news description.

Goodbye, World!

""".trimIndent(), - tags = emptyList(), - createdAt = null, - modifiedAt = null, - isPrivate = false, - isSlide = false, - isImportant = false, - prevId = null, - prevTitle = null, - nextId = null, - nextTitle = null, - imageURL = null, - attachments = null, + tags = emptyList(), + createdAt = null, + modifiedAt = null, + date = null, + isPrivate = false, + isSlide = false, + isImportant = false, + prevId = null, + prevTitle = null, + nextId = null, + nextTitle = null, + imageURL = null, + attachments = null, ) When("DTO를 이용하여 뉴스를 생성하면") { @@ -68,11 +69,12 @@ class NewsServiceTest(

This is news description.

Goodbye, World!

""".trimIndent(), - plainTextDescription = "Hello, World! This is news description. Goodbye, World!", - isPrivate = false, - isSlide = false, - isImportant = false, - ) + plainTextDescription = "Hello, World! This is news description. Goodbye, World!", + date = null, + isPrivate = false, + isSlide = false, + isImportant = false, + ) ) When("저장된 뉴스의 Description을 수정하면") { From 32abd66c107e604786f4d11defc42b6bfb32565f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=ED=98=81=EC=A4=80=20=28HyukJoon=20Woo=29?= Date: Fri, 15 Sep 2023 22:04:07 +0900 Subject: [PATCH 085/124] =?UTF-8?q?Feat:=20=EA=B2=80=EC=83=89=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=EC=97=90=EC=84=9C=20keyword=20=EC=9C=84=EC=B9=98,=20?= =?UTF-8?q?=EC=A0=81=EC=A0=88=ED=95=9C=20substring=20=EC=B6=94=EC=B6=9C=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20method=20=EC=B6=94=EA=B0=80=20(#116)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat: Add utils substringAroundKeyword * Test: Add test for Utils.kt --- .../csereal/common/utils/Utils.kt | 20 ++++ .../csereal/common/util/UtilsTest.kt | 104 ++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 src/test/kotlin/com/wafflestudio/csereal/common/util/UtilsTest.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/utils/Utils.kt b/src/main/kotlin/com/wafflestudio/csereal/common/utils/Utils.kt index 757d94a7..70a3812f 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/utils/Utils.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/utils/Utils.kt @@ -3,8 +3,28 @@ package com.wafflestudio.csereal.common.utils import org.jsoup.Jsoup import org.jsoup.parser.Parser import org.jsoup.safety.Safelist +import kotlin.math.max +import kotlin.math.min fun cleanTextFromHtml(description: String): String { val cleanDescription = Jsoup.clean(description, Safelist.none()) return Parser.unescapeEntities(cleanDescription, false) } + +fun substringAroundKeyword(keyword: String, content: String, amount: Int): Pair { + val index = content.indexOf(keyword) + return if (index == -1) { + null to content.substring(0, amount) + } else { + var frontIndex = (index - amount / 2).coerceAtLeast(0) + var backIndex = (index + amount / 2).coerceAtMost(content.length) + + if (frontIndex == 0) { + backIndex = (amount).coerceAtMost(content.length) + } else if (backIndex == content.length) { + frontIndex = (content.length - amount).coerceAtLeast(0) + } + + index to content.substring(frontIndex, backIndex) + } +} diff --git a/src/test/kotlin/com/wafflestudio/csereal/common/util/UtilsTest.kt b/src/test/kotlin/com/wafflestudio/csereal/common/util/UtilsTest.kt new file mode 100644 index 00000000..e7688aa6 --- /dev/null +++ b/src/test/kotlin/com/wafflestudio/csereal/common/util/UtilsTest.kt @@ -0,0 +1,104 @@ +package com.wafflestudio.csereal.common.util + +import com.wafflestudio.csereal.common.utils.cleanTextFromHtml +import com.wafflestudio.csereal.common.utils.substringAroundKeyword +import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.matchers.shouldBe + +class UtilsTest: BehaviorSpec({ + Given("cleanTextFromHtml") { + + When("description is html") { + val description = """ + +

This is a heading

+

This is a paragraph.

+
+

This is a paragraph in div.

+
+ This is a link + + """.trimIndent() + + Then("return text") { + cleanTextFromHtml(description) shouldBe + "This is a heading " + + "This is a paragraph. " + + "This is a paragraph in div. " + + "This is a link" + } + } + } + + Given("substringAroundKeyword") { + val content = "Hello, World! This is the awesome test code using kotest!" + + When("The keyword is given") { + val keyword = "awesome" + val amount = 30 + + val (startIdx, result) = substringAroundKeyword(keyword, content, amount) + + Then("should return proper index") { + startIdx shouldBe 26 + } + + Then("should return proper substring") { + result.length shouldBe amount + result shouldBe "d! This is the awesome test co" + } + } + + When("Not existing keyword is given") { + val keyword = "Super Mario" + val amount = 30 + + val (startIdx, result) = substringAroundKeyword(keyword, content, amount) + + Then("should return null to index") { + startIdx shouldBe null + } + + Then("should return front substring") { + result.length shouldBe amount + result shouldBe "Hello, World! This is the awes" + } + } + + When("The amount is too long in left side") { + val keyword = "World" + val amount = 30 + + val (_, result) = substringAroundKeyword(keyword, content, amount) + + Then("should return front substring") { + result.length shouldBe amount + result shouldBe "Hello, World! This is the awes" + } + } + + When("The amount is too long in right side") { + val keyword = "using" + val amount = 30 + // 12 + + val (_, result) = substringAroundKeyword(keyword, content, amount) + + Then("should return back substring") { + result.length shouldBe amount + result shouldBe "wesome test code using kotest!" + } + } + + When("The amount is longer then full content") { + val keyword = "is the" + val amount = 1000 + + val (_, result) = substringAroundKeyword(keyword, content, amount) + + Then("should return full content") { + result shouldBe content + } + } + } +}) \ No newline at end of file From 155234494543eaea2d0d1d733b57f954816216b2 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Sat, 16 Sep 2023 00:35:08 +0900 Subject: [PATCH 086/124] =?UTF-8?q?fix:=20total=20=EB=8D=94=EB=AF=B8=20?= =?UTF-8?q?=EA=B0=92=20+=20pageNum=20null=EC=9D=BC=EB=95=8C=EB=A7=8C=20?= =?UTF-8?q?=EB=8D=94=EB=AF=B8=20=EA=B0=92=20=EC=A3=BC=EA=B8=B0=20(#117)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../csereal/core/news/api/NewsController.kt | 7 ++++--- .../core/news/database/NewsRepository.kt | 2 +- .../core/notice/api/NoticeController.kt | 19 +++++++++---------- .../core/notice/database/NoticeRepository.kt | 2 +- .../core/seminar/api/SeminarController.kt | 7 ++++--- .../seminar/database/SeminarRepository.kt | 18 +++++++++--------- 6 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt index 80ad83be..85bcd7c2 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt @@ -20,11 +20,12 @@ class NewsController( fun searchNews( @RequestParam(required = false) tag: List?, @RequestParam(required = false) keyword: String?, - @RequestParam(required = false, defaultValue = "1") pageNum: Int + @RequestParam(required = false) pageNum: Int? ): ResponseEntity { val pageSize = 10 - val pageRequest = PageRequest.of(pageNum - 1, pageSize) - val usePageBtn = pageNum != 1 + val usePageBtn = pageNum != null + val page = pageNum ?: 1 + val pageRequest = PageRequest.of(page - 1, pageSize) return ResponseEntity.ok(newsService.searchNews(tag, keyword, pageRequest, usePageBtn)) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt index a413215f..cbe88468 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt @@ -71,7 +71,7 @@ class NewsRepositoryImpl( total = countQuery.select(newsEntity.countDistinct()).fetchOne()!! pageRequest = FixedPageRequest(pageable, total) } else { - total = (10 * pageable.pageSize).toLong() // 10개 페이지 고정 + total = (10 * pageable.pageSize).toLong() + 1 // 10개 페이지 고정 } val newsEntityList = jpaQuery diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt index 7bcec89f..f4ca28be 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt @@ -24,20 +24,19 @@ class NoticeController( fun searchNotice( @RequestParam(required = false) tag: List?, @RequestParam(required = false) keyword: String?, - @RequestParam(required = false, defaultValue = "1") pageNum: Int, + @RequestParam(required = false) pageNum: Int?, @AuthenticationPrincipal oidcUser: OidcUser? ): ResponseEntity { - var isStaff = false - if (oidcUser != null) { - val username = oidcUser.idToken.getClaim("username") + val isStaff = oidcUser?.let { + val username = it.idToken.getClaim("username") val user = userRepository.findByUsername(username) - if (user?.role == Role.ROLE_STAFF) { - isStaff = true - } - } + user?.role == Role.ROLE_STAFF + } ?: false + + val usePageBtn = pageNum != null + val page = pageNum ?: 1 val pageSize = 20 - val pageRequest = PageRequest.of(pageNum - 1, pageSize) - val usePageBtn = pageNum != 1 + val pageRequest = PageRequest.of(page - 1, pageSize) return ResponseEntity.ok(noticeService.searchNotice(tag, keyword, pageRequest, usePageBtn, isStaff)) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt index ab2dbb81..eb94fd7e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt @@ -93,7 +93,7 @@ class NoticeRepositoryImpl( total = countQuery.select(noticeEntity.countDistinct()).fetchOne()!! pageRequest = FixedPageRequest(pageable, total) } else { - total = (10 * pageable.pageSize).toLong() // 10개 페이지 고정 + total = (10 * pageable.pageSize).toLong() + 1 // 10개 페이지 고정 } val noticeSearchDtoList = jpaQuery diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt index 5e61ee32..ccd454be 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt @@ -19,11 +19,12 @@ class SeminarController( @GetMapping fun searchSeminar( @RequestParam(required = false) keyword: String?, - @RequestParam(required = false, defaultValue = "1") pageNum: Int + @RequestParam(required = false) pageNum: Int? ): ResponseEntity { val pageSize = 10 - val pageRequest = PageRequest.of(pageNum - 1, pageSize) - val usePageBtn = pageNum != 1 + val usePageBtn = pageNum != null + val page = pageNum ?: 1 + val pageRequest = PageRequest.of(page - 1, pageSize) return ResponseEntity.ok(seminarService.searchSeminar(keyword, pageRequest, usePageBtn)) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt index 0348b32d..24a479a7 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt @@ -35,14 +35,14 @@ class SeminarRepositoryImpl( if (!keyword.isNullOrEmpty()) { val booleanTemplate = commonRepository.searchFullSeptupleTextTemplate( - keyword, - seminarEntity.title, - seminarEntity.name, - seminarEntity.affiliation, - seminarEntity.location, - seminarEntity.plainTextDescription, - seminarEntity.plainTextIntroduction, - seminarEntity.plainTextAdditionalNote, + keyword, + seminarEntity.title, + seminarEntity.name, + seminarEntity.affiliation, + seminarEntity.location, + seminarEntity.plainTextDescription, + seminarEntity.plainTextIntroduction, + seminarEntity.plainTextAdditionalNote, ) keywordBooleanBuilder.and(booleanTemplate.gt(0.0)) } @@ -59,7 +59,7 @@ class SeminarRepositoryImpl( total = countQuery.select(seminarEntity.count()).fetchOne()!! pageRequest = FixedPageRequest(pageable, total) } else { - total = (10 * pageable.pageSize).toLong() // 10개 페이지 고정 + total = (10 * pageable.pageSize).toLong() + 1 // 10개 페이지 고정 } val seminarEntityList = jpaQuery From 1d23dda19d0e19d1c304a969eb4f830dbdd44f87 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Sat, 16 Sep 2023 00:56:31 +0900 Subject: [PATCH 087/124] =?UTF-8?q?fix:=20=EC=98=88=EC=95=BD=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EA=B6=8C=ED=95=9C=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F?= =?UTF-8?q?=20=EC=9D=91=EB=8B=B5=20=EB=8B=A8=EC=88=9C=ED=99=94=20(#119)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 예약 단건 조회 권한 분리 * feat: 월별,주별 조회용 SimpleReservationDto --- .../conference/service/ConferenceService.kt | 63 +++++++++---------- .../core/notice/service/NoticeService.kt | 11 +--- .../reservation/api/ReservceController.kt | 10 ++- .../core/reservation/dto/ReservationDto.kt | 23 +++++-- .../reservation/dto/SimpleReservationDto.kt | 22 +++++++ .../reservation/service/ReservationService.kt | 35 ++++++----- 6 files changed, 93 insertions(+), 71 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/SimpleReservationDto.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/conference/service/ConferenceService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/conference/service/ConferenceService.kt index 0829a2a9..343eb4a6 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/conference/service/ConferenceService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/conference/service/ConferenceService.kt @@ -30,10 +30,10 @@ interface ConferenceService { @Service @Transactional class ConferenceServiceImpl( - private val conferencePageRepository: ConferencePageRepository, - private val conferenceRepository: ConferenceRepository, - private val userRepository: UserRepository, - private val researchSearchService: ResearchSearchService, + private val conferencePageRepository: ConferencePageRepository, + private val conferenceRepository: ConferenceRepository, + private val userRepository: UserRepository, + private val researchSearchService: ResearchSearchService, ) : ConferenceService { @Transactional(readOnly = true) @@ -44,17 +44,10 @@ class ConferenceServiceImpl( @Transactional override fun modifyConferences(conferenceModifyRequest: ConferenceModifyRequest): ConferencePage { - var user = RequestContextHolder.getRequestAttributes()?.getAttribute( - "loggedInUser", - RequestAttributes.SCOPE_REQUEST - ) as UserEntity? - - if (user == null) { - val oidcUser = SecurityContextHolder.getContext().authentication.principal as OidcUser - val username = oidcUser.idToken.getClaim("username") - - user = userRepository.findByUsername(username) ?: throw CserealException.Csereal404("재로그인이 필요합니다.") - } + val user = RequestContextHolder.getRequestAttributes()?.getAttribute( + "loggedInUser", + RequestAttributes.SCOPE_REQUEST + ) as UserEntity val conferencePage = conferencePageRepository.findAll()[0] @@ -77,12 +70,12 @@ class ConferenceServiceImpl( @Transactional fun createConferenceWithoutSave( - conferenceCreateDto: ConferenceCreateDto, - conferencePage: ConferencePageEntity, + conferenceCreateDto: ConferenceCreateDto, + conferencePage: ConferencePageEntity, ): ConferenceEntity { val newConference = ConferenceEntity.of( - conferenceCreateDto, - conferencePage + conferenceCreateDto, + conferencePage ) conferencePage.conferences.add(newConference) @@ -93,33 +86,33 @@ class ConferenceServiceImpl( @Transactional fun modifyConferenceWithoutSave( - conferenceDto: ConferenceDto, + conferenceDto: ConferenceDto, ): ConferenceEntity { val conferenceEntity = conferenceRepository.findByIdOrNull(conferenceDto.id) - ?: throw CserealException.Csereal404("Conference id:${conferenceDto.id} 가 존재하지 않습니다.") + ?: throw CserealException.Csereal404("Conference id:${conferenceDto.id} 가 존재하지 않습니다.") conferenceEntity.update(conferenceDto) conferenceEntity.researchSearch?.update(conferenceEntity) - ?: let { - conferenceEntity.researchSearch = ResearchSearchEntity.create(conferenceEntity) - } + ?: let { + conferenceEntity.researchSearch = ResearchSearchEntity.create(conferenceEntity) + } return conferenceEntity } @Transactional fun deleteConference( - id: Long, - conferencePage: ConferencePageEntity, + id: Long, + conferencePage: ConferencePageEntity, ) = conferenceRepository.findByIdOrNull(id) - ?. let { - it.isDeleted = true - conferencePage.conferences.remove(it) - - it.researchSearch?.let { - researchSearchService.deleteResearchSearch(it) - } - it.researchSearch = null + ?.let { + it.isDeleted = true + conferencePage.conferences.remove(it) + + it.researchSearch?.let { + researchSearchService.deleteResearchSearch(it) } -} \ No newline at end of file + it.researchSearch = null + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt index a2f1cdcc..ec04cfcf 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt @@ -78,17 +78,10 @@ class NoticeServiceImpl( @Transactional override fun createNotice(request: NoticeDto, attachments: List?): NoticeDto { - var user = RequestContextHolder.getRequestAttributes()?.getAttribute( + val user = RequestContextHolder.getRequestAttributes()?.getAttribute( "loggedInUser", RequestAttributes.SCOPE_REQUEST - ) as UserEntity? - - if (user == null) { - val oidcUser = SecurityContextHolder.getContext().authentication.principal as OidcUser - val username = oidcUser.idToken.getClaim("username") - - user = userRepository.findByUsername(username) ?: throw CserealException.Csereal404("재로그인이 필요합니다.") - } + ) as UserEntity val newNotice = NoticeEntity( title = request.title, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservceController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservceController.kt index 440049e3..d7b0f094 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservceController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservceController.kt @@ -1,11 +1,10 @@ package com.wafflestudio.csereal.core.reservation.api import com.wafflestudio.csereal.common.aop.AuthenticatedForReservation -import com.wafflestudio.csereal.common.aop.AuthenticatedStaff import com.wafflestudio.csereal.core.reservation.dto.ReservationDto import com.wafflestudio.csereal.core.reservation.dto.ReserveRequest +import com.wafflestudio.csereal.core.reservation.dto.SimpleReservationDto import com.wafflestudio.csereal.core.reservation.service.ReservationService -import org.springframework.format.annotation.DateTimeFormat import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.DeleteMapping import org.springframework.web.bind.annotation.GetMapping @@ -15,7 +14,6 @@ import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController -import java.time.LocalDate import java.time.LocalDateTime import java.util.UUID @@ -31,7 +29,7 @@ class ReservationController( @RequestParam roomId: Long, @RequestParam year: Int, @RequestParam month: Int - ): ResponseEntity> { + ): ResponseEntity> { val start = LocalDateTime.of(year, month, 1, 0, 0) val end = start.plusMonths(1) return ResponseEntity.ok(reservationService.getRoomReservationsBetween(roomId, start, end)) @@ -44,14 +42,14 @@ class ReservationController( @RequestParam year: Int, @RequestParam month: Int, @RequestParam day: Int, - ): ResponseEntity> { + ): ResponseEntity> { val start = LocalDateTime.of(year, month, day, 0, 0) val end = start.plusDays(7) return ResponseEntity.ok(reservationService.getRoomReservationsBetween(roomId, start, end)) } @GetMapping("/{reservationId}") - @AuthenticatedStaff + @AuthenticatedForReservation fun getReservation(@PathVariable reservationId: Long): ResponseEntity { return ResponseEntity.ok(reservationService.getReservation(reservationId)) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReservationDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReservationDto.kt index 9ca83d30..cec83d85 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReservationDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReservationDto.kt @@ -6,7 +6,7 @@ import java.util.UUID data class ReservationDto( val id: Long, - val recurrenceId: UUID?, + val recurrenceId: UUID? = null, val title: String, val purpose: String, val startTime: LocalDateTime, @@ -14,9 +14,9 @@ data class ReservationDto( val recurringWeeks: Int = 1, val roomName: String?, val roomLocation: String, - val userName: String, - val contactEmail: String, - val contactPhone: String, + val userName: String? = null, + val contactEmail: String? = null, + val contactPhone: String? = null, val professor: String ) { companion object { @@ -37,5 +37,20 @@ data class ReservationDto( professor = reservationEntity.professor ) } + + fun forNormalUser(reservationEntity: ReservationEntity): ReservationDto { + return ReservationDto( + id = reservationEntity.id, + title = reservationEntity.title, + purpose = reservationEntity.purpose, + startTime = reservationEntity.startTime, + endTime = reservationEntity.endTime, + recurringWeeks = reservationEntity.recurringWeeks, + roomName = reservationEntity.room.name, + roomLocation = reservationEntity.room.location, + professor = reservationEntity.professor + ) + } + } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/SimpleReservationDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/SimpleReservationDto.kt new file mode 100644 index 00000000..a9091263 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/SimpleReservationDto.kt @@ -0,0 +1,22 @@ +package com.wafflestudio.csereal.core.reservation.dto + +import com.wafflestudio.csereal.core.reservation.database.ReservationEntity +import java.time.LocalDateTime + +data class SimpleReservationDto( + val id: Long, + val title: String, + val startTime: LocalDateTime, + val endTime: LocalDateTime +) { + companion object { + fun of(reservationEntity: ReservationEntity): SimpleReservationDto { + return SimpleReservationDto( + id = reservationEntity.id, + title = reservationEntity.title, + startTime = reservationEntity.startTime, + endTime = reservationEntity.endTime + ) + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/service/ReservationService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/service/ReservationService.kt index f0b93136..e3ad74b3 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/service/ReservationService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/service/ReservationService.kt @@ -4,11 +4,10 @@ import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.core.reservation.database.* import com.wafflestudio.csereal.core.reservation.dto.ReservationDto import com.wafflestudio.csereal.core.reservation.dto.ReserveRequest +import com.wafflestudio.csereal.core.reservation.dto.SimpleReservationDto +import com.wafflestudio.csereal.core.user.database.Role import com.wafflestudio.csereal.core.user.database.UserEntity -import com.wafflestudio.csereal.core.user.database.UserRepository import org.springframework.data.repository.findByIdOrNull -import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.security.oauth2.core.oidc.user.OidcUser import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import org.springframework.web.context.request.RequestAttributes @@ -18,7 +17,7 @@ import java.util.* interface ReservationService { fun reserveRoom(reserveRequest: ReserveRequest): List - fun getRoomReservationsBetween(roomId: Long, start: LocalDateTime, end: LocalDateTime): List + fun getRoomReservationsBetween(roomId: Long, start: LocalDateTime, end: LocalDateTime): List fun getReservation(reservationId: Long): ReservationDto fun cancelSpecific(reservationId: Long) fun cancelRecurring(recurrenceId: UUID) @@ -28,22 +27,14 @@ interface ReservationService { @Transactional class ReservationServiceImpl( private val reservationRepository: ReservationRepository, - private val userRepository: UserRepository, private val roomRepository: RoomRepository ) : ReservationService { override fun reserveRoom(reserveRequest: ReserveRequest): List { - var user = RequestContextHolder.getRequestAttributes()?.getAttribute( + val user = RequestContextHolder.getRequestAttributes()?.getAttribute( "loggedInUser", RequestAttributes.SCOPE_REQUEST - ) as UserEntity? - - if (user == null) { - val oidcUser = SecurityContextHolder.getContext().authentication.principal as OidcUser - val username = oidcUser.idToken.getClaim("username") - - user = userRepository.findByUsername(username) ?: throw CserealException.Csereal404("재로그인이 필요합니다.") - } + ) as UserEntity val room = roomRepository.findByIdOrNull(reserveRequest.roomId) ?: throw CserealException.Csereal404("Room Not Found") @@ -82,16 +73,26 @@ class ReservationServiceImpl( roomId: Long, start: LocalDateTime, end: LocalDateTime - ): List { + ): List { return reservationRepository.findByRoomIdAndStartTimeBetweenOrderByStartTimeAsc(roomId, start, end) - .map { ReservationDto.of(it) } + .map { SimpleReservationDto.of(it) } } @Transactional(readOnly = true) override fun getReservation(reservationId: Long): ReservationDto { val reservationEntity = reservationRepository.findByIdOrNull(reservationId) ?: throw CserealException.Csereal404("예약을 찾을 수 없습니다.") - return ReservationDto.of(reservationEntity) + + val user = RequestContextHolder.getRequestAttributes()?.getAttribute( + "loggedInUser", + RequestAttributes.SCOPE_REQUEST + ) as UserEntity + + if (user.role == Role.ROLE_STAFF) { + return ReservationDto.of(reservationEntity) + } else { + return ReservationDto.forNormalUser(reservationEntity) + } } override fun cancelSpecific(reservationId: Long) { From 11631f73c5b4e46d53abebc30c0b92c33a4c26ff Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Sat, 16 Sep 2023 18:49:17 +0900 Subject: [PATCH 088/124] =?UTF-8?q?fix:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EB=A6=AC=EB=8B=A4=EC=9D=B4=EB=A0=89=ED=8A=B8=20=EC=97=94?= =?UTF-8?q?=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EB=B3=B5=EA=B5=AC=20(#1?= =?UTF-8?q?21)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../csereal/common/config/CustomAuthenticationSuccessHandler.kt | 2 +- .../com/wafflestudio/csereal/common/config/SecurityConfig.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/CustomAuthenticationSuccessHandler.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/CustomAuthenticationSuccessHandler.kt index 94a5495d..52d1d027 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/CustomAuthenticationSuccessHandler.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/CustomAuthenticationSuccessHandler.kt @@ -13,7 +13,7 @@ class CustomAuthenticationSuccessHandler( response: HttpServletResponse, authentication: Authentication ) { - val redirectUrl = "http://localhost:3000/login/success" + val redirectUrl = "${frontendEndpoint}/login/success" response.sendRedirect(redirectUrl) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt index 88c34783..bee84ad8 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt @@ -57,7 +57,7 @@ class SecurityConfig( response: HttpServletResponse?, authentication: Authentication? ) { - val redirectUrl = "http://localhost:3000/logout/success" + val redirectUrl = "${endpointProperties.frontend}/logout/success" super.setDefaultTargetUrl(redirectUrl) super.onLogoutSuccess(request, response, authentication) } From e00fb7ac5fe7f9e4022b04b82120e6bf04d3dca2 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Sat, 16 Sep 2023 20:10:45 +0900 Subject: [PATCH 089/124] =?UTF-8?q?feat:=20migrateAbout,=20migrateFutureCa?= =?UTF-8?q?reers=20=EC=B6=94=EA=B0=80=20(#118)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: AboutRequest, migrateAbout 추가 * feat: future-careers 마이그레이션 추가 * feat: request 패키지 추가 * fix: AboutRequest 간소화 * fix: future-careers description aboutEntity에 저장 --- .../csereal/core/about/api/AboutController.kt | 19 ++- .../core/about/database/CompanyEntity.kt | 14 +- .../csereal/core/about/database/StatEntity.kt | 12 ++ .../csereal/core/about/dto/AboutDto.kt | 4 +- .../csereal/core/about/dto/CompanyDto.kt | 4 +- .../core/about/dto/FutureCareersCompanyDto.kt | 8 ++ .../core/about/dto/FutureCareersResponse.kt | 8 ++ .../about/dto/FutureCareersStatDegreeDto.kt | 7 + .../core/about/dto/FutureCareersStatDto.kt | 9 ++ .../core/about/dto/request/AboutRequest.kt | 9 ++ .../about/dto/request/FutureCareersRequest.kt | 11 ++ .../core/about/service/AboutService.kt | 122 ++++++++++++++++-- 12 files changed, 209 insertions(+), 18 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersCompanyDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersResponse.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersStatDegreeDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersStatDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/dto/request/AboutRequest.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/dto/request/FutureCareersRequest.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt index 0ec2a655..67ffaf7d 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt @@ -1,9 +1,9 @@ package com.wafflestudio.csereal.core.about.api import com.wafflestudio.csereal.common.aop.AuthenticatedStaff -import com.wafflestudio.csereal.core.about.dto.AboutDto -import com.wafflestudio.csereal.core.about.dto.CompanyDto -import com.wafflestudio.csereal.core.about.dto.FutureCareersPage +import com.wafflestudio.csereal.core.about.dto.* +import com.wafflestudio.csereal.core.about.dto.request.AboutRequest +import com.wafflestudio.csereal.core.about.dto.request.FutureCareersRequest import com.wafflestudio.csereal.core.about.service.AboutService import jakarta.validation.Valid import org.springframework.http.ResponseEntity @@ -59,4 +59,17 @@ class AboutController( return ResponseEntity.ok(aboutService.readFutureCareers()) } + @PostMapping("/migrate") + fun migrateAbout( + @RequestBody requestList: List + ): ResponseEntity> { + return ResponseEntity.ok(aboutService.migrateAbout(requestList)) + } + + @PostMapping("/future-careers/migrate") + fun migrateFutureCareers( + @RequestBody request: FutureCareersRequest + ): ResponseEntity { + return ResponseEntity.ok(aboutService.migrateFutureCareers(request)) + } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/CompanyEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/CompanyEntity.kt index e47afbaa..bae4b107 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/CompanyEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/CompanyEntity.kt @@ -1,12 +1,22 @@ package com.wafflestudio.csereal.core.about.database import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.core.about.dto.FutureCareersCompanyDto import jakarta.persistence.Entity @Entity(name = "company") class CompanyEntity( var name: String, - var url: String, - var year: Int, + var url: String?, + var year: Int?, ) : BaseTimeEntity() { + companion object { + fun of(companyDto: FutureCareersCompanyDto): CompanyEntity { + return CompanyEntity( + name = companyDto.name, + url = companyDto.url, + year = companyDto.year, + ) + } + } } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/StatEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/StatEntity.kt index db433127..326d8a1a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/StatEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/StatEntity.kt @@ -1,6 +1,8 @@ package com.wafflestudio.csereal.core.about.database import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.core.about.dto.FutureCareersStatDegreeDto +import com.wafflestudio.csereal.core.about.dto.FutureCareersStatDto import jakarta.persistence.Entity import jakarta.persistence.EnumType import jakarta.persistence.Enumerated @@ -14,6 +16,16 @@ class StatEntity( var name: String, var count: Int, ): BaseTimeEntity() { + companion object { + fun of(year: Int, degree: Degree, statDto: FutureCareersStatDegreeDto): StatEntity { + return StatEntity( + year = year, + degree = degree, + name = statDto.name, + count = statDto.count, + ) + } + } } enum class Degree { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutDto.kt index 60233a13..af37473f 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutDto.kt @@ -1,12 +1,14 @@ package com.wafflestudio.csereal.core.about.dto +import com.fasterxml.jackson.annotation.JsonInclude import com.wafflestudio.csereal.core.about.database.AboutEntity import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse import java.time.LocalDateTime data class AboutDto( - val id: Long, + @JsonInclude(JsonInclude.Include.NON_NULL) + val id: Long? = null, val name: String?, val engName: String?, val description: String, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/CompanyDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/CompanyDto.kt index 1cc30403..4fbccfbd 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/CompanyDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/CompanyDto.kt @@ -2,7 +2,7 @@ package com.wafflestudio.csereal.core.about.dto data class CompanyDto( val name: String, - val url: String, - val year: Int, + val url: String?, + val year: Int?, ) { } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersCompanyDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersCompanyDto.kt new file mode 100644 index 00000000..c62dcb20 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersCompanyDto.kt @@ -0,0 +1,8 @@ +package com.wafflestudio.csereal.core.about.dto + +data class FutureCareersCompanyDto( + val name: String, + val url: String?, + val year: Int? +) { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersResponse.kt new file mode 100644 index 00000000..d41b769d --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersResponse.kt @@ -0,0 +1,8 @@ +package com.wafflestudio.csereal.core.about.dto + +data class FutureCareersResponse( + val description: String, + val stat: List, + val companies: List +) { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersStatDegreeDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersStatDegreeDto.kt new file mode 100644 index 00000000..e51ed75f --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersStatDegreeDto.kt @@ -0,0 +1,7 @@ +package com.wafflestudio.csereal.core.about.dto + +data class FutureCareersStatDegreeDto( + val name: String, + val count: Int, +) { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersStatDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersStatDto.kt new file mode 100644 index 00000000..ed46ea41 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersStatDto.kt @@ -0,0 +1,9 @@ +package com.wafflestudio.csereal.core.about.dto + +data class FutureCareersStatDto( + val year: Int, + val bachelor: List, + val master: List, + val doctor: List +) { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/request/AboutRequest.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/request/AboutRequest.kt new file mode 100644 index 00000000..3039f764 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/request/AboutRequest.kt @@ -0,0 +1,9 @@ +package com.wafflestudio.csereal.core.about.dto.request + +import java.time.LocalDateTime + +data class AboutRequest( + val postType: String, + val description: String, +) { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/request/FutureCareersRequest.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/request/FutureCareersRequest.kt new file mode 100644 index 00000000..29845bab --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/request/FutureCareersRequest.kt @@ -0,0 +1,11 @@ +package com.wafflestudio.csereal.core.about.dto.request + +import com.wafflestudio.csereal.core.about.dto.FutureCareersCompanyDto +import com.wafflestudio.csereal.core.about.dto.FutureCareersStatDto + +data class FutureCareersRequest( + val description: String, + val stat: List, + val companies: List +) { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt index 9424d73b..ebfda7e3 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt @@ -3,6 +3,8 @@ package com.wafflestudio.csereal.core.about.service import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.core.about.database.* import com.wafflestudio.csereal.core.about.dto.* +import com.wafflestudio.csereal.core.about.dto.request.AboutRequest +import com.wafflestudio.csereal.core.about.dto.request.FutureCareersRequest import com.wafflestudio.csereal.core.resource.attachment.service.AttachmentService import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService import org.springframework.stereotype.Service @@ -10,12 +12,20 @@ import org.springframework.transaction.annotation.Transactional import org.springframework.web.multipart.MultipartFile interface AboutService { - fun createAbout(postType: String, request: AboutDto, mainImage: MultipartFile?, attachments: List?): AboutDto + fun createAbout( + postType: String, + request: AboutDto, + mainImage: MultipartFile?, + attachments: List? + ): AboutDto + fun readAbout(postType: String): AboutDto - fun readAllClubs() : List - fun readAllFacilities() : List + fun readAllClubs(): List + fun readAllFacilities(): List fun readAllDirections(): List fun readFutureCareers(): FutureCareersPage + fun migrateAbout(requestList: List): List + fun migrateFutureCareers(request: FutureCareersRequest): FutureCareersResponse } @@ -28,21 +38,26 @@ class AboutServiceImpl( private val attachmentService: AttachmentService, ) : AboutService { @Transactional - override fun createAbout(postType: String, request: AboutDto, mainImage: MultipartFile?, attachments: List?): AboutDto { + override fun createAbout( + postType: String, + request: AboutDto, + mainImage: MultipartFile?, + attachments: List? + ): AboutDto { val enumPostType = makeStringToEnum(postType) val newAbout = AboutEntity.of(enumPostType, request) - if(request.locations != null) { + if (request.locations != null) { for (location in request.locations) { LocationEntity.create(location, newAbout) } } - if(mainImage != null) { + if (mainImage != null) { mainImageService.uploadMainImage(newAbout, mainImage) } - if(attachments != null) { + if (attachments != null) { attachmentService.uploadAllAttachments(newAbout, attachments) } aboutRepository.save(newAbout) @@ -111,7 +126,7 @@ class AboutServiceImpl( "그 이후로는 국내외 관련 산업계, 학계에 주로 진출하고 있고, 새로운 아이디어로 벤처기업을 창업하기도 한다." val statList = mutableListOf() - for(i: Int in 2021 downTo 2011) { + for (i: Int in 2021 downTo 2011) { val bachelor = statRepository.findAllByYearAndDegree(i, Degree.BACHELOR).map { CompanyNameAndCountDto( id = it.id, @@ -152,9 +167,96 @@ class AboutServiceImpl( return FutureCareersPage(description, statList, companyList) } - private fun makeStringToEnum(postType: String) : AboutPostType { + @Transactional + override fun migrateAbout(requestList: List): List { + val list = mutableListOf() + + for (request in requestList) { + val enumPostType = makeStringToEnum(request.postType) + + val aboutDto = AboutDto( + id = null, + name = null, + engName = null, + description = request.description, + year = null, + createdAt = null, + modifiedAt = null, + locations = null, + imageURL = null, + attachments = listOf() + ) + val newAbout = AboutEntity.of(enumPostType, aboutDto) + + aboutRepository.save(newAbout) + + list.add(AboutDto.of(newAbout, null, listOf())) + + } + return list + } + + @Transactional + override fun migrateFutureCareers(request: FutureCareersRequest): FutureCareersResponse { + val description = request.description + val statList = mutableListOf() + val companyList = mutableListOf() + + val aboutDto = AboutDto( + id = null, + name = null, + engName = null, + description = description, + year = null, + createdAt = null, + modifiedAt = null, + locations = null, + imageURL = null, + attachments = listOf() + ) + val newAbout = AboutEntity.of(AboutPostType.FUTURE_CAREERS, aboutDto) + aboutRepository.save(newAbout) + + for (stat in request.stat) { + val year = stat.year + val bachelorList = mutableListOf() + val masterList = mutableListOf() + val doctorList = mutableListOf() + + for (bachelor in stat.bachelor) { + val newBachelor = StatEntity.of(year, Degree.BACHELOR, bachelor) + statRepository.save(newBachelor) + + bachelorList.add(bachelor) + } + for (master in stat.master) { + val newMaster = StatEntity.of(year, Degree.MASTER, master) + statRepository.save(newMaster) + + masterList.add(master) + } + for (doctor in stat.doctor) { + val newDoctor = StatEntity.of(year, Degree.DOCTOR, doctor) + statRepository.save(newDoctor) + + doctorList.add(doctor) + } + } + + for (company in request.companies) { + val newCompany = CompanyEntity.of(company) + companyRepository.save(newCompany) + + companyList.add(company) + } + + + return FutureCareersResponse(description, statList.toList(), companyList.toList()) + } + + private fun makeStringToEnum(postType: String): AboutPostType { try { - val upperPostType = postType.replace("-","_").uppercase() + val upperPostType = postType.replace("-", "_").uppercase() return AboutPostType.valueOf(upperPostType) } catch (e: IllegalArgumentException) { From c13d027882cea8f9368f8c0fd19caec17ec752e5 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Sat, 16 Sep 2023 20:28:24 +0900 Subject: [PATCH 090/124] =?UTF-8?q?feat:=20about=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=20=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=85=98=20=EC=B6=94=EA=B0=80=20(#120)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: AboutRequest, migrateAbout 추가 * feat: future-careers 마이그레이션 추가 * feat: request 패키지 추가 * fix: 불필요한 dto 삭제 * feat: migrateStudentClubs 추가 * feat: migrateFacilities 추가 * feat: migrateDirections 추가 * fix: 불필요한 dto 삭제 * fix: request 패키지 삭제 * fix: 오타 수정 --- .../csereal/core/about/api/AboutController.kt | 27 +++- .../csereal/core/about/dto/AboutRequest.kt | 7 + .../csereal/core/about/dto/CompanyDto.kt | 8 -- .../core/about/dto/CompanyNameAndCountDto.kt | 8 -- .../csereal/core/about/dto/DirectionDto.kt | 23 +++ .../csereal/core/about/dto/FacilityDto.kt | 23 +++ .../core/about/dto/FutureCareersCompanyDto.kt | 13 ++ .../core/about/dto/FutureCareersPage.kt | 4 +- ...ersResponse.kt => FutureCareersRequest.kt} | 5 +- .../about/dto/FutureCareersStatDegreeDto.kt | 12 ++ .../csereal/core/about/dto/StatDto.kt | 9 -- .../csereal/core/about/dto/StudentClubDto.kt | 23 +++ .../core/about/service/AboutService.kt | 135 ++++++++++++++---- 13 files changed, 235 insertions(+), 62 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutRequest.kt delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/dto/CompanyDto.kt delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/dto/CompanyNameAndCountDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/dto/DirectionDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FacilityDto.kt rename src/main/kotlin/com/wafflestudio/csereal/core/about/dto/{FutureCareersResponse.kt => FutureCareersRequest.kt} (50%) delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/dto/StatDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/dto/StudentClubDto.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt index 67ffaf7d..a698a80b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt @@ -19,7 +19,7 @@ class AboutController( // postType: facilities / name -> 학부-행정실, S-Lab, 소프트웨어-실습실, 하드웨어-실습실, 해동학술정보실, 학생-공간-및-동아리-방, 세미나실, 서버실 // postType: directions / name -> by-public-transit, by-car, from-far-away - // Todo: 전체 image, file, 학부장 인사말(greetings) signature + // Todo: 학부장 인사말(greetings) signature @AuthenticatedStaff @PostMapping("/{postType}") fun createAbout( @@ -49,11 +49,11 @@ class AboutController( return ResponseEntity.ok(aboutService.readAllFacilities()) } - @GetMapping("/directions") fun readAllDirections(): ResponseEntity> { return ResponseEntity.ok(aboutService.readAllDirections()) } + @GetMapping("/future-careers") fun readFutureCareers(): ResponseEntity { return ResponseEntity.ok(aboutService.readFutureCareers()) @@ -69,7 +69,28 @@ class AboutController( @PostMapping("/future-careers/migrate") fun migrateFutureCareers( @RequestBody request: FutureCareersRequest - ): ResponseEntity { + ): ResponseEntity { return ResponseEntity.ok(aboutService.migrateFutureCareers(request)) } + + @PostMapping("/student-clubs/migrate") + fun migrateStudentClubs( + @RequestBody requestList: List + ): ResponseEntity> { + return ResponseEntity.ok(aboutService.migrateStudentClubs(requestList)) + } + + @PostMapping("/facilities/migrate") + fun migrateFacilities( + @RequestBody requestList: List + ): ResponseEntity> { + return ResponseEntity.ok(aboutService.migrateFacilities(requestList)) + } + + @PostMapping("/directions/migrate") + fun migrateDirections( + @RequestBody requestList: List + ): ResponseEntity> { + return ResponseEntity.ok(aboutService.migrateDirections(requestList)) + } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutRequest.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutRequest.kt new file mode 100644 index 00000000..c720c90c --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutRequest.kt @@ -0,0 +1,7 @@ +package com.wafflestudio.csereal.core.about.dto + +data class AboutRequest( + val postType: String, + val description: String, +) { +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/CompanyDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/CompanyDto.kt deleted file mode 100644 index 4fbccfbd..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/CompanyDto.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.wafflestudio.csereal.core.about.dto - -data class CompanyDto( - val name: String, - val url: String?, - val year: Int?, -) { -} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/CompanyNameAndCountDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/CompanyNameAndCountDto.kt deleted file mode 100644 index b449193c..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/CompanyNameAndCountDto.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.wafflestudio.csereal.core.about.dto - -data class CompanyNameAndCountDto( - val id: Long, - val name: String, - val count: Int -) { -} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/DirectionDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/DirectionDto.kt new file mode 100644 index 00000000..df866def --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/DirectionDto.kt @@ -0,0 +1,23 @@ +package com.wafflestudio.csereal.core.about.dto + +import com.fasterxml.jackson.annotation.JsonInclude +import com.wafflestudio.csereal.core.about.database.AboutEntity + +data class DirectionDto( + @JsonInclude(JsonInclude.Include.NON_NULL) + val id: Long? = null, + val name: String, + val engName: String, + val description: String, +) { + companion object { + fun of(entity: AboutEntity): DirectionDto = entity.run { + DirectionDto( + id = this.id, + name = this.name!!, + engName = this.engName!!, + description = this.description + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FacilityDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FacilityDto.kt new file mode 100644 index 00000000..6a746c25 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FacilityDto.kt @@ -0,0 +1,23 @@ +package com.wafflestudio.csereal.core.about.dto + +import com.fasterxml.jackson.annotation.JsonInclude +import com.wafflestudio.csereal.core.about.database.AboutEntity + +data class FacilityDto( + @JsonInclude(JsonInclude.Include.NON_NULL) + val id: Long? = null, + val name: String, + val description: String, + val locations: List, +) { + companion object { + fun of(entity: AboutEntity): FacilityDto = entity.run { + FacilityDto( + id = this.id, + name = this.name!!, + description = this.description, + locations = this.locations.map { it.name } + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersCompanyDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersCompanyDto.kt index c62dcb20..eb9710f3 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersCompanyDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersCompanyDto.kt @@ -1,8 +1,21 @@ package com.wafflestudio.csereal.core.about.dto +import com.wafflestudio.csereal.core.about.database.CompanyEntity + data class FutureCareersCompanyDto( + val id: Long, val name: String, val url: String?, val year: Int? ) { + companion object { + fun of(entity: CompanyEntity): FutureCareersCompanyDto = entity.run { + FutureCareersCompanyDto( + id = this.id, + name = this.name, + url = this.url, + year = this.year + ) + } + } } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersPage.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersPage.kt index 656c26c9..e2a77fae 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersPage.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersPage.kt @@ -2,7 +2,7 @@ package com.wafflestudio.csereal.core.about.dto data class FutureCareersPage( val description: String, - val stat: List, - val companies: List + val stat: List, + val companies: List ) { } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersRequest.kt similarity index 50% rename from src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersResponse.kt rename to src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersRequest.kt index d41b769d..405e9043 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersRequest.kt @@ -1,6 +1,9 @@ package com.wafflestudio.csereal.core.about.dto -data class FutureCareersResponse( +import com.wafflestudio.csereal.core.about.dto.FutureCareersCompanyDto +import com.wafflestudio.csereal.core.about.dto.FutureCareersStatDto + +data class FutureCareersRequest( val description: String, val stat: List, val companies: List diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersStatDegreeDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersStatDegreeDto.kt index e51ed75f..d930fe44 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersStatDegreeDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersStatDegreeDto.kt @@ -1,7 +1,19 @@ package com.wafflestudio.csereal.core.about.dto +import com.wafflestudio.csereal.core.about.database.StatEntity + data class FutureCareersStatDegreeDto( + val id: Long, val name: String, val count: Int, ) { + companion object { + fun of(entity: StatEntity): FutureCareersStatDegreeDto = entity.run { + FutureCareersStatDegreeDto( + id = this.id, + name = this.name, + count = this.count, + ) + } + } } \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/StatDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/StatDto.kt deleted file mode 100644 index 45e53137..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/StatDto.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.wafflestudio.csereal.core.about.dto - -data class StatDto( - val year: Int, - val bachelor: List, - val master: List, - val doctor: List -) { -} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/StudentClubDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/StudentClubDto.kt new file mode 100644 index 00000000..2196dce8 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/StudentClubDto.kt @@ -0,0 +1,23 @@ +package com.wafflestudio.csereal.core.about.dto + +import com.fasterxml.jackson.annotation.JsonInclude +import com.wafflestudio.csereal.core.about.database.AboutEntity + +data class StudentClubDto( + @JsonInclude(JsonInclude.Include.NON_NULL) + val id: Long? = null, + val name: String, + val engName: String, + val description: String, +) { + companion object { + fun of(entity: AboutEntity): StudentClubDto = entity.run { + StudentClubDto( + id = this.id, + name = this.name!!, + engName = this.engName!!, + description = this.description + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt index ebfda7e3..a9360902 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt @@ -3,6 +3,7 @@ package com.wafflestudio.csereal.core.about.service import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.core.about.database.* import com.wafflestudio.csereal.core.about.dto.* +import com.wafflestudio.csereal.core.about.dto.FutureCareersPage import com.wafflestudio.csereal.core.about.dto.request.AboutRequest import com.wafflestudio.csereal.core.about.dto.request.FutureCareersRequest import com.wafflestudio.csereal.core.resource.attachment.service.AttachmentService @@ -25,7 +26,10 @@ interface AboutService { fun readAllDirections(): List fun readFutureCareers(): FutureCareersPage fun migrateAbout(requestList: List): List - fun migrateFutureCareers(request: FutureCareersRequest): FutureCareersResponse + fun migrateFutureCareers(request: FutureCareersRequest): FutureCareersPage + fun migrateStudentClubs(requestList: List): List + fun migrateFacilities(requestList: List): List + fun migrateDirections(requestList: List): List } @@ -125,44 +129,23 @@ class AboutServiceImpl( "대학원에 진학하면 여러 전공분야 중 하나를 선택하여 보다 깊이 있는 지식의 습득과 연구과정을 거치게 되며 " + "그 이후로는 국내외 관련 산업계, 학계에 주로 진출하고 있고, 새로운 아이디어로 벤처기업을 창업하기도 한다." - val statList = mutableListOf() + val statList = mutableListOf() for (i: Int in 2021 downTo 2011) { val bachelor = statRepository.findAllByYearAndDegree(i, Degree.BACHELOR).map { - CompanyNameAndCountDto( - id = it.id, - name = it.name, - count = it.count - ) + FutureCareersStatDegreeDto.of(it) } val master = statRepository.findAllByYearAndDegree(i, Degree.MASTER).map { - CompanyNameAndCountDto( - id = it.id, - name = it.name, - count = it.count, - ) + FutureCareersStatDegreeDto.of(it) } val doctor = statRepository.findAllByYearAndDegree(i, Degree.DOCTOR).map { - CompanyNameAndCountDto( - id = it.id, - name = it.name, - count = it.count, - ) + FutureCareersStatDegreeDto.of(it) } statList.add( - StatDto( - year = i, - bachelor = bachelor, - master = master, - doctor = doctor, - ) + FutureCareersStatDto(i, bachelor, master, doctor) ) } val companyList = companyRepository.findAllByOrderByYearDesc().map { - CompanyDto( - name = it.name, - url = it.url, - year = it.year - ) + FutureCareersCompanyDto.of(it) } return FutureCareersPage(description, statList, companyList) } @@ -197,7 +180,7 @@ class AboutServiceImpl( } @Transactional - override fun migrateFutureCareers(request: FutureCareersRequest): FutureCareersResponse { + override fun migrateFutureCareers(request: FutureCareersRequest): FutureCareersPage { val description = request.description val statList = mutableListOf() val companyList = mutableListOf() @@ -216,7 +199,7 @@ class AboutServiceImpl( ) val newAbout = AboutEntity.of(AboutPostType.FUTURE_CAREERS, aboutDto) aboutRepository.save(newAbout) - + for (stat in request.stat) { val year = stat.year val bachelorList = mutableListOf() @@ -250,8 +233,97 @@ class AboutServiceImpl( companyList.add(company) } + return FutureCareersPage(description, statList.toList(), companyList.toList()) + } + + @Transactional + override fun migrateStudentClubs(requestList: List): List { + val list = mutableListOf() + + for (request in requestList) { + + val aboutDto = AboutDto( + id = null, + name = request.name, + engName = request.engName, + description = request.description, + year = null, + createdAt = null, + modifiedAt = null, + locations = null, + imageURL = null, + attachments = listOf() + ) + val newAbout = AboutEntity.of(AboutPostType.STUDENT_CLUBS, aboutDto) + + aboutRepository.save(newAbout) + + list.add(StudentClubDto.of(newAbout)) + + } + return list + } + + @Transactional + override fun migrateFacilities(requestList: List): List { + val list = mutableListOf() + + for (request in requestList) { + + val aboutDto = AboutDto( + id = null, + name = request.name, + engName = null, + description = request.description, + year = null, + createdAt = null, + modifiedAt = null, + locations = null, + imageURL = null, + attachments = listOf() + ) + + val newAbout = AboutEntity.of(AboutPostType.FACILITIES, aboutDto) + + for (location in request.locations) { + LocationEntity.create(location, newAbout) + } + + aboutRepository.save(newAbout) + + list.add(FacilityDto.of(newAbout)) + + } + return list + } + + @Transactional + override fun migrateDirections(requestList: List): List { + val list = mutableListOf() + + for (request in requestList) { - return FutureCareersResponse(description, statList.toList(), companyList.toList()) + val aboutDto = AboutDto( + id = null, + name = request.name, + engName = request.engName, + description = request.description, + year = null, + createdAt = null, + modifiedAt = null, + locations = null, + imageURL = null, + attachments = listOf() + ) + + val newAbout = AboutEntity.of(AboutPostType.DIRECTIONS, aboutDto) + + aboutRepository.save(newAbout) + + list.add(DirectionDto.of(newAbout)) + + } + return list } private fun makeStringToEnum(postType: String): AboutPostType { @@ -263,4 +335,5 @@ class AboutServiceImpl( throw CserealException.Csereal400("해당하는 enum을 찾을 수 없습니다") } } + } \ No newline at end of file From e744b210f3b254155fca93ce899a1d9b4c3bf1cd Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Sun, 17 Sep 2023 09:15:04 +0900 Subject: [PATCH 091/124] =?UTF-8?q?fix:=20news,=20notice=EC=97=90=20titleF?= =?UTF-8?q?orMain=20=EC=B6=94=EA=B0=80=20(#125)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 소식3총사 titleForMain 데이터 생성 * fix: 테스트에 titleForMain 추가 * Revert "fix: 테스트에 titleForMain 추가" This reverts commit e824ad34e60e0785f1d4c4e187776d4b8bf53dfa. * fix: 테스트에 titleForMain 추가 * fix: seminar titleForMain 추가 * fix: seminar test titleForMain 추가 --- .../core/main/database/MainRepository.kt | 9 +++-- .../core/main/dto/MainImportantResponse.kt | 1 + .../csereal/core/news/database/NewsEntity.kt | 4 ++ .../csereal/core/news/dto/NewsDto.kt | 2 + .../csereal/core/news/service/NewsService.kt | 4 ++ .../core/notice/database/NoticeEntity.kt | 3 ++ .../csereal/core/notice/dto/NoticeDto.kt | 2 + .../core/notice/service/NoticeService.kt | 5 +++ .../core/seminar/database/SeminarEntity.kt | 4 ++ .../csereal/core/seminar/dto/SeminarDto.kt | 2 + .../core/seminar/service/SeminarService.kt | 4 ++ .../core/notice/news/NewsServiceTest.kt | 40 ++++++++++--------- .../core/notice/service/NoticeServiceTest.kt | 2 + .../seminar/service/SeminarServiceTest.kt | 2 + 14 files changed, 62 insertions(+), 22 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt index a7fd29d7..52e4fb08 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt @@ -86,7 +86,8 @@ class MainRepositoryImpl( mainImportantResponses.add( MainImportantResponse( id = it.id, - title = it.title, + title = it.titleForMain!!, + description = it.description, createdAt = it.createdAt, category = "notice" ) @@ -97,7 +98,8 @@ class MainRepositoryImpl( mainImportantResponses.add( MainImportantResponse( id = it.id, - title = it.title, + title = it.titleForMain!!, + description = it.description, createdAt = it.createdAt, category = "news" ) @@ -108,7 +110,8 @@ class MainRepositoryImpl( mainImportantResponses.add( MainImportantResponse( id = it.id, - title = it.title, + title = it.titleForMain!!, + description = it.description, createdAt = it.createdAt, category = "seminar" ) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainImportantResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainImportantResponse.kt index dc11f667..23b63b56 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainImportantResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainImportantResponse.kt @@ -5,6 +5,7 @@ import java.time.LocalDateTime data class MainImportantResponse( val id: Long, val title: String, + val description: String, val createdAt: LocalDateTime?, val category: String, ) { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt index aab41c93..40e5df0e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt @@ -16,6 +16,8 @@ class NewsEntity( var isDeleted: Boolean = false, var title: String, + var titleForMain: String?, + @Column(columnDefinition = "mediumtext") var description: String, @@ -44,6 +46,7 @@ class NewsEntity( fun of(newsDto: NewsDto): NewsEntity { return NewsEntity( title = newsDto.title, + titleForMain = newsDto.titleForMain, description = newsDto.description, plainTextDescription = cleanTextFromHtml(newsDto.description), date = newsDto.date, @@ -60,6 +63,7 @@ class NewsEntity( this.plainTextDescription = cleanTextFromHtml(updateNewsRequest.description) } this.title = updateNewsRequest.title + this.titleForMain = updateNewsRequest.titleForMain this.date = updateNewsRequest.date this.isPrivate = updateNewsRequest.isPrivate this.isSlide = updateNewsRequest.isSlide diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt index da0614de..411aca20 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt @@ -7,6 +7,7 @@ import java.time.LocalDateTime data class NewsDto( val id: Long, val title: String, + val titleForMain: String?, val description: String, val tags: List, val createdAt: LocalDateTime?, @@ -33,6 +34,7 @@ data class NewsDto( NewsDto( id = this.id, title = this.title, + titleForMain = this.titleForMain, description = this.description, tags = this.newsTags.map { it.tag.name.krName }, createdAt = this.createdAt, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt index 875471eb..00f015b9 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt @@ -80,6 +80,10 @@ class NewsServiceImpl( attachmentService.uploadAllAttachments(newNews, attachments) } + if (request.isImportant && request.titleForMain.isNullOrEmpty()) { + throw CserealException.Csereal400("중요 제목이 입력되어야 합니다") + } + newsRepository.save(newNews) val imageURL = mainImageService.createImageURL(newNews.mainImage) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt index fb46b9ae..6a0d0915 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt @@ -13,6 +13,8 @@ import jakarta.persistence.* class NoticeEntity( var isDeleted: Boolean = false, var title: String, + var titleForMain: String?, + @Column(columnDefinition = "mediumtext") var description: String, @@ -43,6 +45,7 @@ class NoticeEntity( } this.title = updateNoticeRequest.title + this.titleForMain = updateNoticeRequest.titleForMain this.description = updateNoticeRequest.description this.isPrivate = updateNoticeRequest.isPrivate this.isPinned = updateNoticeRequest.isPinned diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt index f8e3c8c3..3f5f0f00 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt @@ -8,6 +8,7 @@ import java.time.LocalDateTime data class NoticeDto( val id: Long, val title: String, + val titleForMain: String?, val description: String, val author: String?, val tags: List, @@ -33,6 +34,7 @@ data class NoticeDto( NoticeDto( id = this.id, title = this.title, + titleForMain = this.titleForMain, description = this.description, author = this.author.name, tags = this.noticeTags.map { it.tag.name.krName }, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt index ec04cfcf..f0ef56d6 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt @@ -85,6 +85,7 @@ class NoticeServiceImpl( val newNotice = NoticeEntity( title = request.title, + titleForMain = request.titleForMain, description = request.description, plainTextDescription = cleanTextFromHtml(request.description), isPrivate = request.isPrivate, @@ -103,6 +104,10 @@ class NoticeServiceImpl( attachmentService.uploadAllAttachments(newNotice, attachments) } + if (request.isImportant && request.titleForMain.isNullOrEmpty()) { + throw CserealException.Csereal400("중요 제목이 입력되어야 합니다") + } + noticeRepository.save(newNotice) val attachmentResponses = attachmentService.createAttachmentResponses(newNotice.attachments) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt index 264c13d6..46e46ca0 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt @@ -17,6 +17,8 @@ class SeminarEntity( var title: String, + var titleForMain: String?, + @Column(columnDefinition = "mediumtext") var description: String, @@ -70,6 +72,7 @@ class SeminarEntity( return SeminarEntity( title = seminarDto.title, + titleForMain = seminarDto.titleForMain, description = seminarDto.description, plainTextDescription = plainTextDescription, introduction = seminarDto.introduction, @@ -108,6 +111,7 @@ class SeminarEntity( } title = updateSeminarRequest.title + titleForMain = updateSeminarRequest.titleForMain introduction = updateSeminarRequest.introduction name = updateSeminarRequest.name speakerURL = updateSeminarRequest.speakerURL diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt index 2855ec2a..3e329115 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt @@ -8,6 +8,7 @@ import java.time.LocalDateTime data class SeminarDto( val id: Long, val title: String, + val titleForMain: String?, val description: String, val introduction: String, val name: String, @@ -43,6 +44,7 @@ data class SeminarDto( SeminarDto( id = this.id, title = this.title, + titleForMain = this.titleForMain, description = this.description, introduction = this.introduction, name = this.name, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt index 2067bac0..f07ca37f 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt @@ -103,6 +103,10 @@ class SeminarServiceImpl( attachmentService.uploadAllAttachments(seminar, newAttachments) } + if (request.isImportant && request.titleForMain.isNullOrEmpty()) { + throw CserealException.Csereal400("중요 제목이 입력되어야 합니다") + } + val attachmentResponses = attachmentService.createAttachmentResponses(seminar.attachments) val imageURL = mainImageService.createImageURL(seminar.mainImage) diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/notice/news/NewsServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/notice/news/NewsServiceTest.kt index 0aec38ee..7678eb7a 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/notice/news/NewsServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/notice/news/NewsServiceTest.kt @@ -25,24 +25,25 @@ class NewsServiceTest( val newsDTO = NewsDto( id = -1, title = "title", + titleForMain = null, description = """

Hello, World!

This is news description.

Goodbye, World!

""".trimIndent(), - tags = emptyList(), - createdAt = null, - modifiedAt = null, - date = null, - isPrivate = false, - isSlide = false, - isImportant = false, - prevId = null, - prevTitle = null, - nextId = null, - nextTitle = null, - imageURL = null, - attachments = null, + tags = emptyList(), + createdAt = null, + modifiedAt = null, + date = null, + isPrivate = false, + isSlide = false, + isImportant = false, + prevId = null, + prevTitle = null, + nextId = null, + nextTitle = null, + imageURL = null, + attachments = null, ) When("DTO를 이용하여 뉴스를 생성하면") { @@ -64,17 +65,18 @@ class NewsServiceTest( val newsEntity = newsRepository.save( NewsEntity( title = "title", + titleForMain = null, description = """

Hello, World!

This is news description.

Goodbye, World!

""".trimIndent(), - plainTextDescription = "Hello, World! This is news description. Goodbye, World!", - date = null, - isPrivate = false, - isSlide = false, - isImportant = false, - ) + plainTextDescription = "Hello, World! This is news description. Goodbye, World!", + date = null, + isPrivate = false, + isSlide = false, + isImportant = false, + ) ) When("저장된 뉴스의 Description을 수정하면") { diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeServiceTest.kt index fdacee10..c2f8264d 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeServiceTest.kt @@ -59,6 +59,7 @@ class NoticeServiceTest( val noticeDto = NoticeDto( id = -1, title = "title", + titleForMain = null, description = """

Hello, World!

This is a test notice.

@@ -96,6 +97,7 @@ class NoticeServiceTest( val noticeEntity = noticeRepository.save( NoticeEntity( title = "title", + titleForMain = null, description = """

Hello, World!

This is a test notice.

diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarServiceTest.kt index 64359c52..3f0a431c 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarServiceTest.kt @@ -30,6 +30,7 @@ class SeminarServiceTest( val seminarDTO = SeminarDto( id = -1, title = "title", + titleForMain = null, description = """

Hello, World!

This is seminar description.

@@ -86,6 +87,7 @@ class SeminarServiceTest( val originalSeminar = seminarRepository.save( SeminarEntity( title = "title", + titleForMain = null, description = """

Hello, World!

This is seminar description.

From d1425e8f4cc490ffe2d1e0bf40b36b7136a26e08 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Sun, 17 Sep 2023 11:24:19 +0900 Subject: [PATCH 092/124] =?UTF-8?q?feat:=20LocalDateTime=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20=EB=A7=88=EC=A7=80=EB=A7=89=EC=97=90=20Z=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#126)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../csereal/common/config/JacksonConfig.kt | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/common/config/JacksonConfig.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/JacksonConfig.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/JacksonConfig.kt new file mode 100644 index 00000000..a1558b82 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/JacksonConfig.kt @@ -0,0 +1,33 @@ +package com.wafflestudio.csereal.common.config + +import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.databind.JsonSerializer +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.SerializerProvider +import com.fasterxml.jackson.databind.module.SimpleModule +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import java.time.LocalDateTime +import java.time.ZoneOffset +import java.time.format.DateTimeFormatter + +@Configuration +class JacksonConfig { + + @Bean + fun objectMapper(): ObjectMapper { + val objectMapper = ObjectMapper() + val module = SimpleModule() + module.addSerializer(LocalDateTime::class.java, LocalDateTimeSerializer()) + objectMapper.registerModule(module) + return objectMapper + } +} + +class LocalDateTimeSerializer : JsonSerializer() { + override fun serialize(value: LocalDateTime, gen: JsonGenerator, serializers: SerializerProvider) { + val zonedDateTime = value.atZone(ZoneOffset.UTC) + val formatted = zonedDateTime.format(DateTimeFormatter.ISO_INSTANT) + gen.writeString(formatted) + } +} From 76adbc6bb972efe2948e79f4cbb6e1fa64431eae Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Sun, 17 Sep 2023 11:31:17 +0900 Subject: [PATCH 093/124] =?UTF-8?q?refactor:=20deleteIds=20request=20?= =?UTF-8?q?=EB=82=B4=EB=B6=80=EB=A1=9C=20=EC=9D=B4=EB=8F=99=20(#127)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: deleteIds request 내부로 이동 * test: 테스트 수정 --- .../csereal/core/news/api/NewsController.kt | 3 +- .../csereal/core/news/dto/NewsDto.kt | 1 + .../csereal/core/news/service/NewsService.kt | 4 +-- .../core/notice/api/NoticeController.kt | 5 ++- .../csereal/core/notice/dto/NoticeDto.kt | 1 + .../core/notice/service/NoticeService.kt | 7 ++-- .../attachment/service/AttachmentService.kt | 33 +++++-------------- .../core/seminar/api/SeminarController.kt | 2 -- .../csereal/core/seminar/dto/SeminarDto.kt | 1 + .../core/seminar/service/SeminarService.kt | 4 +-- .../core/notice/news/NewsServiceTest.kt | 1 - .../core/notice/service/NoticeServiceTest.kt | 1 - .../seminar/service/SeminarServiceTest.kt | 1 - 13 files changed, 18 insertions(+), 46 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt index 85bcd7c2..fb9d04aa 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt @@ -53,9 +53,8 @@ class NewsController( @Valid @RequestPart("request") request: NewsDto, @RequestPart("newMainImage") newMainImage: MultipartFile?, @RequestPart("newAttachments") newAttachments: List?, - @RequestPart("deleteIds") deleteIds: List, ): ResponseEntity { - return ResponseEntity.ok(newsService.updateNews(newsId, request, newMainImage, newAttachments, deleteIds)) + return ResponseEntity.ok(newsService.updateNews(newsId, request, newMainImage, newAttachments)) } @AuthenticatedStaff diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt index 411aca20..e78020fd 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt @@ -22,6 +22,7 @@ data class NewsDto( val nextTitle: String?, val imageURL: String?, val attachments: List?, + val deleteIds: List? = null ) { companion object { fun of( diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt index 00f015b9..6fb0096d 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt @@ -21,7 +21,6 @@ interface NewsService { request: NewsDto, newMainImage: MultipartFile?, newAttachments: List?, - deleteIds: List, ): NewsDto fun deleteNews(newsId: Long) @@ -98,7 +97,6 @@ class NewsServiceImpl( request: NewsDto, newMainImage: MultipartFile?, newAttachments: List?, - deleteIds: List, ): NewsDto { val news: NewsEntity = newsRepository.findByIdOrNull(newsId) ?: throw CserealException.Csereal404("존재하지 않는 새소식입니다. (newsId: $newsId)") @@ -111,7 +109,7 @@ class NewsServiceImpl( mainImageService.uploadMainImage(news, newMainImage) } - attachmentService.deleteAttachments(deleteIds) + attachmentService.deleteAttachments(request.deleteIds) if (newAttachments != null) { attachmentService.uploadAllAttachments(news, newAttachments) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt index f4ca28be..7460df3d 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt @@ -61,10 +61,9 @@ class NoticeController( fun updateNotice( @PathVariable noticeId: Long, @Valid @RequestPart("request") request: NoticeDto, - @RequestPart("newAttachments") newAttachments: List?, - @RequestPart("deleteIds") deleteIds: List, + @RequestPart("newAttachments") newAttachments: List? ): ResponseEntity { - return ResponseEntity.ok(noticeService.updateNotice(noticeId, request, newAttachments, deleteIds)) + return ResponseEntity.ok(noticeService.updateNotice(noticeId, request, newAttachments)) } @AuthenticatedStaff diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt index 3f5f0f00..e255660d 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt @@ -22,6 +22,7 @@ data class NoticeDto( val nextId: Long?, val nextTitle: String?, val attachments: List?, + val deleteIds: List? = null ) { companion object { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt index f0ef56d6..8e193030 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt @@ -32,7 +32,6 @@ interface NoticeService { noticeId: Long, request: NoticeDto, newAttachments: List?, - deleteIds: List ): NoticeDto fun deleteNotice(noticeId: Long) @@ -46,7 +45,6 @@ class NoticeServiceImpl( private val noticeRepository: NoticeRepository, private val tagInNoticeRepository: TagInNoticeRepository, private val noticeTagRepository: NoticeTagRepository, - private val userRepository: UserRepository, private val attachmentService: AttachmentService, ) : NoticeService { @@ -120,8 +118,7 @@ class NoticeServiceImpl( override fun updateNotice( noticeId: Long, request: NoticeDto, - newAttachments: List?, - deleteIds: List + newAttachments: List? ): NoticeDto { val notice: NoticeEntity = noticeRepository.findByIdOrNull(noticeId) ?: throw CserealException.Csereal404("존재하지 않는 공지사항입니다.(noticeId: $noticeId)") @@ -129,7 +126,7 @@ class NoticeServiceImpl( notice.update(request) - attachmentService.deleteAttachments(deleteIds) + attachmentService.deleteAttachments(request.deleteIds) if (newAttachments != null) { attachmentService.uploadAllAttachments(notice, newAttachments) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt index a550d521..032b9a8b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt @@ -41,7 +41,7 @@ interface AttachmentService { // attachmentsList: List // ) - fun deleteAttachments(ids: List) + fun deleteAttachments(ids: List?) fun deleteAttachment(attachment: AttachmentEntity) } @@ -141,32 +141,15 @@ class AttachmentServiceImpl( return list } -// @Transactional -// override fun updateAttachmentResponses( -// contentEntity: AttachmentContentEntityType, -// attachmentsList: List -// ) { -// val oldAttachments = contentEntity.bringAttachments().map { it.filename } -// -// val attachmentsToRemove = oldAttachments - attachmentsList.map { it.name } -// -// when (contentEntity) { -// is SeminarEntity -> { -// for (attachmentFilename in attachmentsToRemove) { -// val attachmentEntity = attachmentRepository.findByFilename(attachmentFilename) -// attachmentEntity.isDeleted = true -// attachmentEntity.seminar = null -// } -// } -// } -// } @Transactional - override fun deleteAttachments(ids: List) { - for (id in ids) { - val attachment = attachmentRepository.findByIdOrNull(id) - ?: throw CserealException.Csereal404("id:${id}인 첨부파일을 찾을 수 없습니다.") - attachment.isDeleted = true + override fun deleteAttachments(ids: List?) { + if (ids != null) { + for (id in ids) { + val attachment = attachmentRepository.findByIdOrNull(id) + ?: throw CserealException.Csereal404("id:${id}인 첨부파일을 찾을 수 없습니다.") + attachment.isDeleted = true + } } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt index ccd454be..8f4d435f 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt @@ -52,7 +52,6 @@ class SeminarController( @Valid @RequestPart("request") request: SeminarDto, @RequestPart("newMainImage") newMainImage: MultipartFile?, @RequestPart("newAttachments") newAttachments: List?, - @RequestPart("deleteIds") deleteIds: List, ): ResponseEntity { return ResponseEntity.ok( seminarService.updateSeminar( @@ -60,7 +59,6 @@ class SeminarController( request, newMainImage, newAttachments, - deleteIds ) ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt index 3e329115..d45ed776 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt @@ -31,6 +31,7 @@ data class SeminarDto( val nextTitle: String?, val imageURL: String?, val attachments: List?, + val deleteIds: List? = null ) { companion object { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt index f07ca37f..ffa7ad2e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt @@ -22,7 +22,6 @@ interface SeminarService { request: SeminarDto, newMainImage: MultipartFile?, newAttachments: List?, - deleteIds: List, ): SeminarDto fun deleteSeminar(seminarId: Long) @@ -84,7 +83,6 @@ class SeminarServiceImpl( request: SeminarDto, newMainImage: MultipartFile?, newAttachments: List?, - deleteIds: List, ): SeminarDto { val seminar: SeminarEntity = seminarRepository.findByIdOrNull(seminarId) ?: throw CserealException.Csereal404("존재하지 않는 세미나입니다") @@ -97,7 +95,7 @@ class SeminarServiceImpl( mainImageService.uploadMainImage(seminar, newMainImage) } - attachmentService.deleteAttachments(deleteIds) + attachmentService.deleteAttachments(request.deleteIds) if (newAttachments != null) { attachmentService.uploadAllAttachments(seminar, newAttachments) diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/notice/news/NewsServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/notice/news/NewsServiceTest.kt index 7678eb7a..13c9f8c7 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/notice/news/NewsServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/notice/news/NewsServiceTest.kt @@ -93,7 +93,6 @@ class NewsServiceTest( ), null, null, - emptyList() ) Then("description, plainTextDescription이 수정되어야 한다.") { diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeServiceTest.kt index c2f8264d..c7363eaa 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeServiceTest.kt @@ -126,7 +126,6 @@ class NoticeServiceTest( modifiedRequest.id, modifiedRequest, null, - emptyList() ) Then("plainTextDescription이 잘 수정되어야 한다.") { diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarServiceTest.kt index 3f0a431c..bdd1f904 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarServiceTest.kt @@ -150,7 +150,6 @@ class SeminarServiceTest( modifiedSeminarDTO, null, null, - emptyList() ) Then("같은 Entity가 수정되어야 한다.") { From ca4a861c211ff58df5bd1da052e99556c7d20aec Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Sun, 17 Sep 2023 11:35:07 +0900 Subject: [PATCH 094/124] =?UTF-8?q?fix:=20=ED=99=95=EC=9E=A5=EC=9E=90=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=EB=B2=84=EA=B7=B8=20(#128)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/resource/attachment/service/AttachmentService.kt | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt index 032b9a8b..1aa7be72 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt @@ -36,10 +36,6 @@ interface AttachmentService { ): List fun createAttachmentResponses(attachments: List?): List -// fun updateAttachmentResponses( -// contentEntity: AttachmentContentEntityType, -// attachmentsList: List -// ) fun deleteAttachments(ids: List?) fun deleteAttachment(attachment: AttachmentEntity) @@ -61,7 +57,7 @@ class AttachmentServiceImpl( val filename = "${timeMillis}_${requestAttachment.originalFilename}" val totalFilename = path + filename - val saveFile = Paths.get("$totalFilename.$extension") + val saveFile = Paths.get(totalFilename) requestAttachment.transferTo(saveFile) val attachment = AttachmentEntity( @@ -97,7 +93,7 @@ class AttachmentServiceImpl( val filename = "${timeMillis}_${requestAttachment.originalFilename}" val totalFilename = path + filename - val saveFile = Paths.get("$totalFilename.$extension") + val saveFile = Paths.get(totalFilename) requestAttachment.transferTo(saveFile) val attachment = AttachmentEntity( From eba0ab5ce3c11eb7b86f438eaf7d9dc10803dcac Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Sun, 17 Sep 2023 11:41:16 +0900 Subject: [PATCH 095/124] =?UTF-8?q?feat:=20=EA=B4=80=EB=A6=AC=EC=9E=90?= =?UTF-8?q?=EB=8A=94=20=EC=83=88=EC=86=8C=EC=8B=9D,=20=EC=84=B8=EB=AF=B8?= =?UTF-8?q?=EB=82=98=20=EB=AA=A9=EB=A1=9D=EC=97=90=EC=84=9C=20=EB=B9=84?= =?UTF-8?q?=EA=B3=B5=EA=B0=9C=20=EA=B8=80=20=ED=99=95=EC=9D=B8=20=EA=B0=80?= =?UTF-8?q?=EB=8A=A5=20(#129)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../csereal/core/news/api/NewsController.kt | 16 ++++++++++-- .../core/news/database/NewsRepository.kt | 24 +++++++++++++++--- .../csereal/core/news/dto/NewsSearchDto.kt | 3 ++- .../csereal/core/news/service/NewsService.kt | 14 ++++++++--- .../core/seminar/api/SeminarController.kt | 16 ++++++++++-- .../seminar/database/SeminarRepository.kt | 25 ++++++++++++++++--- .../core/seminar/dto/SeminarSearchDto.kt | 1 + .../core/seminar/service/SeminarService.kt | 17 ++++++++++--- 8 files changed, 98 insertions(+), 18 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt index fb9d04aa..f9b1a73a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt @@ -4,10 +4,14 @@ import com.wafflestudio.csereal.common.aop.AuthenticatedStaff import com.wafflestudio.csereal.core.news.dto.NewsDto import com.wafflestudio.csereal.core.news.dto.NewsSearchResponse import com.wafflestudio.csereal.core.news.service.NewsService +import com.wafflestudio.csereal.core.user.database.Role +import com.wafflestudio.csereal.core.user.database.UserRepository import jakarta.validation.Valid import org.springframework.data.domain.PageRequest import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity +import org.springframework.security.core.annotation.AuthenticationPrincipal +import org.springframework.security.oauth2.core.oidc.user.OidcUser import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile @@ -15,18 +19,26 @@ import org.springframework.web.multipart.MultipartFile @RestController class NewsController( private val newsService: NewsService, + private val userRepository: UserRepository ) { @GetMapping fun searchNews( @RequestParam(required = false) tag: List?, @RequestParam(required = false) keyword: String?, - @RequestParam(required = false) pageNum: Int? + @RequestParam(required = false) pageNum: Int?, + @AuthenticationPrincipal oidcUser: OidcUser? ): ResponseEntity { + val isStaff = oidcUser?.let { + val username = it.idToken.getClaim("username") + val user = userRepository.findByUsername(username) + user?.role == Role.ROLE_STAFF + } ?: false + val pageSize = 10 val usePageBtn = pageNum != null val page = pageNum ?: 1 val pageRequest = PageRequest.of(page - 1, pageSize) - return ResponseEntity.ok(newsService.searchNews(tag, keyword, pageRequest, usePageBtn)) + return ResponseEntity.ok(newsService.searchNews(tag, keyword, pageRequest, usePageBtn, isStaff)) } @GetMapping("/{newsId}") diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt index cbe88468..01683e4e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt @@ -10,6 +10,7 @@ import com.wafflestudio.csereal.core.news.database.QNewsEntity.newsEntity import com.wafflestudio.csereal.core.news.database.QNewsTagEntity.newsTagEntity import com.wafflestudio.csereal.core.news.dto.NewsSearchDto import com.wafflestudio.csereal.core.news.dto.NewsSearchResponse +import com.wafflestudio.csereal.core.notice.database.QNoticeEntity import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService import org.springframework.data.domain.Pageable import org.springframework.data.jpa.repository.JpaRepository @@ -23,7 +24,13 @@ interface NewsRepository : JpaRepository, CustomNewsRepository } interface CustomNewsRepository { - fun searchNews(tag: List?, keyword: String?, pageable: Pageable, usePageBtn: Boolean): NewsSearchResponse + fun searchNews( + tag: List?, + keyword: String?, + pageable: Pageable, + usePageBtn: Boolean, + isStaff: Boolean + ): NewsSearchResponse } @Component @@ -36,10 +43,12 @@ class NewsRepositoryImpl( tag: List?, keyword: String?, pageable: Pageable, - usePageBtn: Boolean + usePageBtn: Boolean, + isStaff: Boolean ): NewsSearchResponse { val keywordBooleanBuilder = BooleanBuilder() val tagsBooleanBuilder = BooleanBuilder() + val isPrivateBooleanBuilder = BooleanBuilder() if (!keyword.isNullOrEmpty()) { val booleanTemplate = commonRepository.searchFullDoubleTextTemplate( @@ -58,10 +67,16 @@ class NewsRepositoryImpl( } } + if (!isStaff) { + isPrivateBooleanBuilder.or( + QNoticeEntity.noticeEntity.isPrivate.eq(false) + ) + } + val jpaQuery = queryFactory.selectFrom(newsEntity) .leftJoin(newsTagEntity).on(newsTagEntity.news.eq(newsEntity)) .where(newsEntity.isDeleted.eq(false)) - .where(keywordBooleanBuilder).where(tagsBooleanBuilder) + .where(keywordBooleanBuilder, tagsBooleanBuilder, isPrivateBooleanBuilder) val total: Long var pageRequest = pageable @@ -90,7 +105,8 @@ class NewsRepositoryImpl( createdAt = it.createdAt, date = it.date, tags = it.newsTags.map { newsTagEntity -> newsTagEntity.tag.name.krName }, - imageURL = imageURL + imageURL = imageURL, + isPrivate = it.isPrivate ) } return NewsSearchResponse(total, newsSearchDtoList) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchDto.kt index 81569246..2b95ca7c 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchDto.kt @@ -11,4 +11,5 @@ data class NewsSearchDto @QueryProjection constructor( val date: LocalDateTime?, var tags: List?, var imageURL: String?, -) \ No newline at end of file + val isPrivate: Boolean +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt index 6fb0096d..f8b81d82 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt @@ -13,7 +13,14 @@ import org.springframework.transaction.annotation.Transactional import org.springframework.web.multipart.MultipartFile interface NewsService { - fun searchNews(tag: List?, keyword: String?, pageable: Pageable, usePageBtn: Boolean): NewsSearchResponse + fun searchNews( + tag: List?, + keyword: String?, + pageable: Pageable, + usePageBtn: Boolean, + isStaff: Boolean + ): NewsSearchResponse + fun readNews(newsId: Long): NewsDto fun createNews(request: NewsDto, mainImage: MultipartFile?, attachments: List?): NewsDto fun updateNews( @@ -40,9 +47,10 @@ class NewsServiceImpl( tag: List?, keyword: String?, pageable: Pageable, - usePageBtn: Boolean + usePageBtn: Boolean, + isStaff: Boolean ): NewsSearchResponse { - return newsRepository.searchNews(tag, keyword, pageable, usePageBtn) + return newsRepository.searchNews(tag, keyword, pageable, usePageBtn, isStaff) } @Transactional(readOnly = true) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt index 8f4d435f..18e045e7 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt @@ -5,9 +5,13 @@ import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse import com.wafflestudio.csereal.core.seminar.dto.SeminarDto import com.wafflestudio.csereal.core.seminar.dto.SeminarSearchResponse import com.wafflestudio.csereal.core.seminar.service.SeminarService +import com.wafflestudio.csereal.core.user.database.Role +import com.wafflestudio.csereal.core.user.database.UserRepository import jakarta.validation.Valid import org.springframework.data.domain.PageRequest import org.springframework.http.ResponseEntity +import org.springframework.security.core.annotation.AuthenticationPrincipal +import org.springframework.security.oauth2.core.oidc.user.OidcUser import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile @@ -15,17 +19,25 @@ import org.springframework.web.multipart.MultipartFile @RestController class SeminarController( private val seminarService: SeminarService, + private val userRepository: UserRepository ) { @GetMapping fun searchSeminar( @RequestParam(required = false) keyword: String?, - @RequestParam(required = false) pageNum: Int? + @RequestParam(required = false) pageNum: Int?, + @AuthenticationPrincipal oidcUser: OidcUser? ): ResponseEntity { + val isStaff = oidcUser?.let { + val username = it.idToken.getClaim("username") + val user = userRepository.findByUsername(username) + user?.role == Role.ROLE_STAFF + } ?: false + val pageSize = 10 val usePageBtn = pageNum != null val page = pageNum ?: 1 val pageRequest = PageRequest.of(page - 1, pageSize) - return ResponseEntity.ok(seminarService.searchSeminar(keyword, pageRequest, usePageBtn)) + return ResponseEntity.ok(seminarService.searchSeminar(keyword, pageRequest, usePageBtn, isStaff)) } @AuthenticatedStaff diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt index 24a479a7..537ff4c3 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt @@ -5,6 +5,7 @@ import com.querydsl.jpa.impl.JPAQueryFactory import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.common.repository.CommonRepository import com.wafflestudio.csereal.common.utils.FixedPageRequest +import com.wafflestudio.csereal.core.notice.database.QNoticeEntity import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService import com.wafflestudio.csereal.core.seminar.database.QSeminarEntity.seminarEntity import com.wafflestudio.csereal.core.seminar.dto.SeminarSearchDto @@ -21,7 +22,12 @@ interface SeminarRepository : JpaRepository, CustomSeminarR } interface CustomSeminarRepository { - fun searchSeminar(keyword: String?, pageable: Pageable, usePageBtn: Boolean): SeminarSearchResponse + fun searchSeminar( + keyword: String?, + pageable: Pageable, + usePageBtn: Boolean, + isStaff: Boolean + ): SeminarSearchResponse } @Component @@ -30,8 +36,14 @@ class SeminarRepositoryImpl( private val mainImageService: MainImageService, private val commonRepository: CommonRepository, ) : CustomSeminarRepository { - override fun searchSeminar(keyword: String?, pageable: Pageable, usePageBtn: Boolean): SeminarSearchResponse { + override fun searchSeminar( + keyword: String?, + pageable: Pageable, + usePageBtn: Boolean, + isStaff: Boolean + ): SeminarSearchResponse { val keywordBooleanBuilder = BooleanBuilder() + val isPrivateBooleanBuilder = BooleanBuilder() if (!keyword.isNullOrEmpty()) { val booleanTemplate = commonRepository.searchFullSeptupleTextTemplate( @@ -47,9 +59,15 @@ class SeminarRepositoryImpl( keywordBooleanBuilder.and(booleanTemplate.gt(0.0)) } + if (!isStaff) { + isPrivateBooleanBuilder.or( + QNoticeEntity.noticeEntity.isPrivate.eq(false) + ) + } + val jpaQuery = queryFactory.selectFrom(seminarEntity) .where(seminarEntity.isDeleted.eq(false)) - .where(keywordBooleanBuilder) + .where(keywordBooleanBuilder, isPrivateBooleanBuilder) val total: Long var pageRequest = pageable @@ -91,6 +109,7 @@ class SeminarRepositoryImpl( location = seminarEntityList[i].location, imageURL = imageURL, isYearLast = isYearLast, + isPrivate = seminarEntityList[i].isPrivate ) ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarSearchDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarSearchDto.kt index 6bd7c055..79222ce1 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarSearchDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarSearchDto.kt @@ -13,5 +13,6 @@ data class SeminarSearchDto @QueryProjection constructor( val location: String, val imageURL: String?, val isYearLast: Boolean, + val isPrivate: Boolean ) { } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt index ffa7ad2e..25710ca6 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt @@ -14,7 +14,13 @@ import org.springframework.transaction.annotation.Transactional import org.springframework.web.multipart.MultipartFile interface SeminarService { - fun searchSeminar(keyword: String?, pageable: Pageable, usePageBtn: Boolean): SeminarSearchResponse + fun searchSeminar( + keyword: String?, + pageable: Pageable, + usePageBtn: Boolean, + isStaff: Boolean + ): SeminarSearchResponse + fun createSeminar(request: SeminarDto, mainImage: MultipartFile?, attachments: List?): SeminarDto fun readSeminar(seminarId: Long): SeminarDto fun updateSeminar( @@ -34,8 +40,13 @@ class SeminarServiceImpl( private val attachmentService: AttachmentService, ) : SeminarService { @Transactional(readOnly = true) - override fun searchSeminar(keyword: String?, pageable: Pageable, usePageBtn: Boolean): SeminarSearchResponse { - return seminarRepository.searchSeminar(keyword, pageable, usePageBtn) + override fun searchSeminar( + keyword: String?, + pageable: Pageable, + usePageBtn: Boolean, + isStaff: Boolean + ): SeminarSearchResponse { + return seminarRepository.searchSeminar(keyword, pageable, usePageBtn, isStaff) } @Transactional From 8ad3fc82f1bf2e141741fb0c5823dba036fc66cc Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Mon, 18 Sep 2023 01:51:03 +0900 Subject: [PATCH 096/124] =?UTF-8?q?feat:=20research,=20member=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20migrate=20=EC=B6=94=EA=B0=80=20(#130)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: migrateProfessors 추가 * feat: migrateResearchDetail 추가 * feat: migrateLabs 추가 * fix: migrate 엔티티 description에 mediumText 추가 * feat: migrateStaff 추가 * fix: 빠진거 수정 * fix: 중복 패키지 삭제 * fix: 너무 안긴거 text로 수정 --- .../csereal/core/about/api/AboutController.kt | 4 +- .../core/about/database/AboutEntity.kt | 3 + .../core/about/dto/request/AboutRequest.kt | 9 -- .../about/dto/request/FutureCareersRequest.kt | 11 --- .../core/about/service/AboutService.kt | 4 +- .../core/member/api/ProfessorController.kt | 6 ++ .../core/member/api/StaffController.kt | 7 ++ .../core/member/database/CareerEntity.kt | 6 +- .../core/member/database/EducationEntity.kt | 1 + .../member/database/ResearchAreaEntity.kt | 2 + .../core/member/database/TaskEntity.kt | 2 + .../core/member/service/ProfessorService.kt | 34 ++++++++ .../core/member/service/StaffService.kt | 20 +++++ .../core/research/api/ResearchController.kt | 13 +++ .../core/research/database/LabEntity.kt | 1 + .../core/research/database/LabRepository.kt | 1 + .../core/research/database/ResearchEntity.kt | 2 + .../core/research/service/ResearchService.kt | 84 ++++++++++++++----- 18 files changed, 161 insertions(+), 49 deletions(-) delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/dto/request/AboutRequest.kt delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/dto/request/FutureCareersRequest.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt index a698a80b..ba54b62e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt @@ -2,8 +2,8 @@ package com.wafflestudio.csereal.core.about.api import com.wafflestudio.csereal.common.aop.AuthenticatedStaff import com.wafflestudio.csereal.core.about.dto.* -import com.wafflestudio.csereal.core.about.dto.request.AboutRequest -import com.wafflestudio.csereal.core.about.dto.request.FutureCareersRequest +import com.wafflestudio.csereal.core.about.dto.AboutRequest +import com.wafflestudio.csereal.core.about.dto.FutureCareersRequest import com.wafflestudio.csereal.core.about.service.AboutService import jakarta.validation.Valid import org.springframework.http.ResponseEntity diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt index a77058a7..c9854e36 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt @@ -14,7 +14,10 @@ class AboutEntity( var postType: AboutPostType, var name: String?, var engName: String?, + + @Column(columnDefinition = "mediumText") var description: String, + var year: Int?, @OneToMany(mappedBy = "about", cascade = [CascadeType.ALL], orphanRemoval = true) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/request/AboutRequest.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/request/AboutRequest.kt deleted file mode 100644 index 3039f764..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/request/AboutRequest.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.wafflestudio.csereal.core.about.dto.request - -import java.time.LocalDateTime - -data class AboutRequest( - val postType: String, - val description: String, -) { -} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/request/FutureCareersRequest.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/request/FutureCareersRequest.kt deleted file mode 100644 index 29845bab..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/request/FutureCareersRequest.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.wafflestudio.csereal.core.about.dto.request - -import com.wafflestudio.csereal.core.about.dto.FutureCareersCompanyDto -import com.wafflestudio.csereal.core.about.dto.FutureCareersStatDto - -data class FutureCareersRequest( - val description: String, - val stat: List, - val companies: List -) { -} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt index a9360902..5e1f9dcc 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt @@ -4,8 +4,8 @@ import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.core.about.database.* import com.wafflestudio.csereal.core.about.dto.* import com.wafflestudio.csereal.core.about.dto.FutureCareersPage -import com.wafflestudio.csereal.core.about.dto.request.AboutRequest -import com.wafflestudio.csereal.core.about.dto.request.FutureCareersRequest +import com.wafflestudio.csereal.core.about.dto.AboutRequest +import com.wafflestudio.csereal.core.about.dto.FutureCareersRequest import com.wafflestudio.csereal.core.resource.attachment.service.AttachmentService import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService import org.springframework.stereotype.Service diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt index d1e4101c..6c64cc46 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt @@ -56,4 +56,10 @@ class ProfessorController( return ResponseEntity.ok().build() } + @PostMapping("/migrate") + fun migrateProfessors( + @RequestBody requestList: List + ): ResponseEntity> { + return ResponseEntity.ok(professorService.migrateProfessors(requestList)) + } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt index 5b192953..de4c9d07 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt @@ -48,4 +48,11 @@ class StaffController( staffService.deleteStaff(staffId) return ResponseEntity.ok().build() } + + @PostMapping("/migrate") + fun migrateStaff( + @RequestBody requestList: List + ): ResponseEntity> { + return ResponseEntity.ok(staffService.migrateStaff(requestList)) + } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/CareerEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/CareerEntity.kt index a6a40da0..845650fb 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/CareerEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/CareerEntity.kt @@ -1,13 +1,11 @@ package com.wafflestudio.csereal.core.member.database import com.wafflestudio.csereal.common.config.BaseTimeEntity -import jakarta.persistence.Entity -import jakarta.persistence.FetchType -import jakarta.persistence.JoinColumn -import jakarta.persistence.ManyToOne +import jakarta.persistence.* @Entity(name = "career") class CareerEntity( + @Column(columnDefinition = "text") val name: String, @ManyToOne(fetch = FetchType.LAZY) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/EducationEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/EducationEntity.kt index ff4559ce..052cbc9e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/EducationEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/EducationEntity.kt @@ -5,6 +5,7 @@ import jakarta.persistence.* @Entity(name = "education") class EducationEntity( + @Column(columnDefinition = "text") val name: String, @ManyToOne(fetch = FetchType.LAZY) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ResearchAreaEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ResearchAreaEntity.kt index 6f6c8ab4..2cbe6505 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ResearchAreaEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ResearchAreaEntity.kt @@ -1,6 +1,7 @@ package com.wafflestudio.csereal.core.member.database import com.wafflestudio.csereal.common.config.BaseTimeEntity +import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.FetchType import jakarta.persistence.JoinColumn @@ -8,6 +9,7 @@ import jakarta.persistence.ManyToOne @Entity(name = "research_area") class ResearchAreaEntity( + @Column(columnDefinition = "text") val name: String, @ManyToOne(fetch = FetchType.LAZY) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/TaskEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/TaskEntity.kt index d30a96b3..d4a97939 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/TaskEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/TaskEntity.kt @@ -1,6 +1,7 @@ package com.wafflestudio.csereal.core.member.database import com.wafflestudio.csereal.common.config.BaseTimeEntity +import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.FetchType import jakarta.persistence.JoinColumn @@ -8,6 +9,7 @@ import jakarta.persistence.ManyToOne @Entity(name = "task") class TaskEntity( + @Column(columnDefinition = "text") val name: String, @ManyToOne(fetch = FetchType.LAZY) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt index d0f19e2c..bc75f01f 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt @@ -19,6 +19,7 @@ interface ProfessorService { fun getInactiveProfessors(): List fun updateProfessor(professorId: Long, updateProfessorRequest: ProfessorDto, mainImage: MultipartFile?): ProfessorDto fun deleteProfessor(professorId: Long) + fun migrateProfessors(requestList: List): List } @Service @@ -162,4 +163,37 @@ class ProfessorServiceImpl( professorRepository.deleteById(professorId) } + @Transactional + override fun migrateProfessors(requestList: List): List { + val list = mutableListOf() + + for (request in requestList) { + val professor = ProfessorEntity.of(request) + if (request.labName != null) { + val lab = labRepository.findByName(request.labName) + ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다. LabName: ${request.labName}") + professor.addLab(lab) + } + + for (education in request.educations) { + EducationEntity.create(education, professor) + } + + for (researchArea in request.researchAreas) { + ResearchAreaEntity.create(researchArea, professor) + } + + for (career in request.careers) { + CareerEntity.create(career, professor) + } + + professor.memberSearch = MemberSearchEntity.create(professor) + + professorRepository.save(professor) + + list.add(ProfessorDto.of(professor, null)) + } + return list + } + } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt index 7514c5e3..c1d62265 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt @@ -19,6 +19,7 @@ interface StaffService { fun getAllStaff(): List fun updateStaff(staffId: Long, updateStaffRequest: StaffDto, mainImage: MultipartFile?): StaffDto fun deleteStaff(staffId: Long) + fun migrateStaff(requestList: List): List } @Service @@ -101,5 +102,24 @@ class StaffServiceImpl( staffRepository.deleteById(staffId) } + @Transactional + override fun migrateStaff(requestList: List): List { + val list = mutableListOf() + + for (request in requestList) { + val staff = StaffEntity.of(request) + + for (task in request.tasks) { + TaskEntity.create(task, staff) + } + + staffRepository.save(staff) + + list.add(StaffDto.of(staff, null)) + } + + return list + } + } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt index f0b9d680..bcd16eb8 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt @@ -80,4 +80,17 @@ class ResearchController( ): ResponseEntity { return ResponseEntity.ok(researchService.updateLab(labId, request, pdf)) } + + @PostMapping("/migrate") + fun migrateResearchDetail( + @RequestBody requestList: List + ): ResponseEntity> { + return ResponseEntity.ok(researchService.migrateResearchDetail(requestList)) + } + @PostMapping("/lab/migrate") + fun migrateLabs( + @RequestBody requestList: List + ): ResponseEntity> { + return ResponseEntity.ok(researchService.migrateLabs(requestList)) + } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabEntity.kt index ff56635c..c897e9ca 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabEntity.kt @@ -28,6 +28,7 @@ class LabEntity( @JoinColumn(name = "research_id") var research: ResearchEntity, + @Column(columnDefinition = "mediumText") var description: String?, var websiteURL: String?, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabRepository.kt index c7e15692..abbe4372 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabRepository.kt @@ -4,4 +4,5 @@ import org.springframework.data.jpa.repository.JpaRepository interface LabRepository : JpaRepository { fun findAllByOrderByName(): List + fun findByName(name: String): LabEntity? } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchEntity.kt index 2769ae29..facf4a9b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchEntity.kt @@ -14,6 +14,8 @@ class ResearchEntity( var postType: ResearchPostType, var name: String, + + @Column(columnDefinition = "mediumText") var description: String?, @OneToMany(mappedBy = "research", cascade = [CascadeType.ALL], orphanRemoval = true) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt index dcd45175..4dd75aa2 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt @@ -34,16 +34,18 @@ interface ResearchService { fun readAllLabs(): List fun readLab(labId: Long): LabDto fun updateLab(labId: Long, request: LabUpdateRequest, pdf: MultipartFile?): LabDto + fun migrateResearchDetail(requestList: List): List + fun migrateLabs(requestList: List): List } @Service class ResearchServiceImpl( - private val researchRepository: ResearchRepository, - private val labRepository: LabRepository, - private val professorRepository: ProfessorRepository, - private val mainImageService: MainImageService, - private val attachmentService: AttachmentService, - private val endpointProperties: EndpointProperties, + private val researchRepository: ResearchRepository, + private val labRepository: LabRepository, + private val professorRepository: ProfessorRepository, + private val mainImageService: MainImageService, + private val attachmentService: AttachmentService, + private val endpointProperties: EndpointProperties, ) : ResearchService { @Transactional override fun createResearchDetail( @@ -162,16 +164,16 @@ class ResearchServiceImpl( research.updateWithoutLabImageAttachment(request) research.researchSearch?.update(research) - ?: let { - research.researchSearch = ResearchSearchEntity.create(research) - } + ?: let { + research.researchSearch = ResearchSearchEntity.create(research) + } return ResearchDto.of(research, imageURL, attachmentResponses) } @Transactional override fun createLab(request: LabDto, pdf: MultipartFile?): LabDto { - val researchGroup = researchRepository.findByName(request.group) + val researchGroup = researchRepository.findByName(request.group!!) ?: throw CserealException.Csereal404("해당 연구그룹을 찾을 수 없습니다.(researchGroupId = ${request.group})") if (researchGroup.postType != ResearchPostType.GROUPS) { @@ -245,18 +247,18 @@ class ResearchServiceImpl( removedProfessorIds.forEach { val professor = professorRepository.findByIdOrNull(it) - ?: throw CserealException.Csereal404("해당 교수님을 찾을 수 없습니다.(professorId=$it)") + ?: throw CserealException.Csereal404("해당 교수님을 찾을 수 없습니다.(professorId=$it)") labEntity.professors.remove( - professor + professor ) professor.lab = null } addedProfessorIds.forEach { val professor = professorRepository.findByIdOrNull(it) - ?: throw CserealException.Csereal404("해당 교수님을 찾을 수 없습니다.(professorId=$it)") + ?: throw CserealException.Csereal404("해당 교수님을 찾을 수 없습니다.(professorId=$it)") labEntity.professors.add( - professor + professor ) professor.lab = labEntity } @@ -272,16 +274,56 @@ class ResearchServiceImpl( // update researchSearch labEntity.researchSearch?.update(labEntity) - ?: let { - labEntity.researchSearch = ResearchSearchEntity.create(labEntity) - } + ?: let { + labEntity.researchSearch = ResearchSearchEntity.create(labEntity) + } return LabDto.of( - labEntity, - labEntity.pdf?.let { - createPdfURL(it) - } ?: "" + labEntity, + labEntity.pdf?.let { + createPdfURL(it) + } ?: "" ) } + @Transactional + override fun migrateResearchDetail(requestList: List): List { + val list = mutableListOf() + for (request in requestList) { + val newResearch = ResearchEntity.of(request) + + newResearch.researchSearch = ResearchSearchEntity.create(newResearch) + + researchRepository.save(newResearch) + + list.add(ResearchDto.of(newResearch, null, listOf())) + } + + return list + } + + @Transactional + override fun migrateLabs(requestList: List): List { + val list = mutableListOf() + for (request in requestList) { + + val researchGroup = researchRepository.findByName(request.group) + ?: throw CserealException.Csereal404("해당 연구그룹을 찾을 수 없습니다.(researchGroupName = ${request.group})") + + if (researchGroup.postType != ResearchPostType.GROUPS) { + throw CserealException.Csereal404("해당 게시글은 연구그룹이어야 합니다.") + } + + + val newLab = LabEntity.of(request, researchGroup) + + newLab.researchSearch = ResearchSearchEntity.create(newLab) + + labRepository.save(newLab) + + list.add(LabDto.of(newLab, "")) + } + return list + } + } From 5be02b730389e1b17b088e258788b1a1563b4ad5 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Mon, 18 Sep 2023 16:17:39 +0900 Subject: [PATCH 097/124] =?UTF-8?q?fix:=20noticeSearchDto=EC=97=90?= =?UTF-8?q?=EB=8F=84=20private=20=EC=B6=94=EA=B0=80=20(#131)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: noticeSearchDto에도 private 추가 * fix: json 추가 * fix: 수정 및 추가 * fix: 수정 --- .../csereal/core/news/database/NewsRepository.kt | 2 +- .../com/wafflestudio/csereal/core/news/dto/NewsSearchDto.kt | 2 +- .../csereal/core/notice/database/NoticeRepository.kt | 3 ++- .../wafflestudio/csereal/core/notice/dto/NoticeSearchDto.kt | 5 ++++- .../csereal/core/seminar/database/SeminarRepository.kt | 2 +- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt index 01683e4e..76b356e2 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt @@ -69,7 +69,7 @@ class NewsRepositoryImpl( if (!isStaff) { isPrivateBooleanBuilder.or( - QNoticeEntity.noticeEntity.isPrivate.eq(false) + newsEntity.isPrivate.eq(false) ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchDto.kt index 2b95ca7c..5deda4e5 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchDto.kt @@ -11,5 +11,5 @@ data class NewsSearchDto @QueryProjection constructor( val date: LocalDateTime?, var tags: List?, var imageURL: String?, - val isPrivate: Boolean + val isPrivate: Boolean, ) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt index eb94fd7e..0a0776e4 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt @@ -77,7 +77,8 @@ class NoticeRepositoryImpl( noticeEntity.title, noticeEntity.createdAt, noticeEntity.isPinned, - noticeEntity.attachments.isNotEmpty + noticeEntity.attachments.isNotEmpty, + noticeEntity.isPrivate ) ) .from(noticeEntity) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeSearchDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeSearchDto.kt index c093e37c..fb819cba 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeSearchDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeSearchDto.kt @@ -1,5 +1,6 @@ package com.wafflestudio.csereal.core.notice.dto +import com.fasterxml.jackson.annotation.JsonProperty import com.querydsl.core.annotations.QueryProjection import com.wafflestudio.csereal.core.notice.database.NoticeEntity import java.time.LocalDateTime @@ -10,12 +11,14 @@ data class NoticeSearchDto @QueryProjection constructor( val createdAt: LocalDateTime?, val isPinned: Boolean, val hasAttachment: Boolean, + val isPrivate: Boolean, ) { constructor(entity: NoticeEntity, hasAttachment: Boolean) : this( entity.id, entity.title, entity.createdAt, entity.isPinned, - hasAttachment + hasAttachment, + entity.isPrivate ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt index 537ff4c3..26d3633a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt @@ -61,7 +61,7 @@ class SeminarRepositoryImpl( if (!isStaff) { isPrivateBooleanBuilder.or( - QNoticeEntity.noticeEntity.isPrivate.eq(false) + seminarEntity.isPrivate.eq(false) ) } From e1b8dbcbe69d4135b7bbfb6c63325ec9443e1085 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=ED=98=81=EC=A4=80=20=28HyukJoon=20Woo=29?= Date: Mon, 18 Sep 2023 20:30:44 +0900 Subject: [PATCH 098/124] =?UTF-8?q?Fix:=20=ED=82=A4=EC=9B=8C=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EC=B6=9C=20=ED=95=A8=EC=88=98=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?(#122)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix: Fix IndexOutOfBoundException, change to return index based on substring. * Fix: Change to check index exclude case, and set front, back index to better contains keyword. * Test: Update Test. --- .../com/wafflestudio/csereal/common/utils/Utils.kt | 10 +++++----- .../com/wafflestudio/csereal/common/util/UtilsTest.kt | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/utils/Utils.kt b/src/main/kotlin/com/wafflestudio/csereal/common/utils/Utils.kt index 70a3812f..bccc8655 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/utils/Utils.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/utils/Utils.kt @@ -12,12 +12,12 @@ fun cleanTextFromHtml(description: String): String { } fun substringAroundKeyword(keyword: String, content: String, amount: Int): Pair { - val index = content.indexOf(keyword) + val index = content.lowercase().indexOf(keyword.lowercase()) return if (index == -1) { - null to content.substring(0, amount) + null to content.substring(0, amount.coerceAtMost(content.length)) } else { - var frontIndex = (index - amount / 2).coerceAtLeast(0) - var backIndex = (index + amount / 2).coerceAtMost(content.length) + var frontIndex = (index - amount / 2 + keyword.length).coerceAtLeast(0) + var backIndex = (index + amount / 2 + keyword.length).coerceAtMost(content.length) if (frontIndex == 0) { backIndex = (amount).coerceAtMost(content.length) @@ -25,6 +25,6 @@ fun substringAroundKeyword(keyword: String, content: String, amount: Int): Pair< frontIndex = (content.length - amount).coerceAtLeast(0) } - index to content.substring(frontIndex, backIndex) + (index - frontIndex) to content.substring(frontIndex, backIndex) } } diff --git a/src/test/kotlin/com/wafflestudio/csereal/common/util/UtilsTest.kt b/src/test/kotlin/com/wafflestudio/csereal/common/util/UtilsTest.kt index e7688aa6..2f5dacf5 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/common/util/UtilsTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/common/util/UtilsTest.kt @@ -40,12 +40,12 @@ class UtilsTest: BehaviorSpec({ val (startIdx, result) = substringAroundKeyword(keyword, content, amount) Then("should return proper index") { - startIdx shouldBe 26 + startIdx shouldBe 8 } Then("should return proper substring") { result.length shouldBe amount - result shouldBe "d! This is the awesome test co" + result shouldBe " is the awesome test code usin" } } From cc141a883ce5a678836a34dfc9599f793cfe919a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=ED=98=81=EC=A4=80=20=28HyukJoon=20Woo=29?= Date: Mon, 18 Sep 2023 20:46:25 +0900 Subject: [PATCH 099/124] =?UTF-8?q?Feat:=20=ED=86=B5=ED=95=A9=EA=B2=80?= =?UTF-8?q?=EC=83=89=20Notice=20API=20=EA=B5=AC=ED=98=84=20(#123)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat: Add dtos for notice total search. * Feat: Add repository for notice total search. * Feat: Add service, controller for notice total search --- .../core/notice/api/NoticeController.kt | 12 ++++++ .../core/notice/database/NoticeRepository.kt | 42 +++++++++++++++++++ .../notice/dto/NoticeTotalSearchElement.kt | 28 +++++++++++++ .../notice/dto/NoticeTotalSearchResponse.kt | 6 +++ .../core/notice/service/NoticeService.kt | 9 ++++ 5 files changed, 97 insertions(+) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeTotalSearchElement.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeTotalSearchResponse.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt index 7460df3d..973d2f97 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt @@ -6,6 +6,9 @@ import com.wafflestudio.csereal.core.notice.service.NoticeService import com.wafflestudio.csereal.core.user.database.Role import com.wafflestudio.csereal.core.user.database.UserRepository import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Positive +import org.hibernate.validator.constraints.Length import org.springframework.data.domain.PageRequest import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity @@ -40,6 +43,15 @@ class NoticeController( return ResponseEntity.ok(noticeService.searchNotice(tag, keyword, pageRequest, usePageBtn, isStaff)) } + @GetMapping("/totalSearch") + fun totalSearchNotice( + @RequestParam(required = true) @Length(min = 2) @NotBlank keyword: String, + @RequestParam(required = true) @Positive number: Int, + @RequestParam(required = false) @Positive stringLength: Int = 200, + ) = ResponseEntity.ok( + noticeService.searchTotalNotice(keyword, number, stringLength) + ) + @GetMapping("/{noticeId}") fun readNotice( @PathVariable noticeId: Long diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt index 0a0776e4..6a8c8c61 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt @@ -9,6 +9,8 @@ import com.wafflestudio.csereal.core.notice.database.QNoticeEntity.noticeEntity import com.wafflestudio.csereal.core.notice.database.QNoticeTagEntity.noticeTagEntity import com.wafflestudio.csereal.core.notice.dto.NoticeSearchDto import com.wafflestudio.csereal.core.notice.dto.NoticeSearchResponse +import com.wafflestudio.csereal.core.notice.dto.NoticeTotalSearchElement +import com.wafflestudio.csereal.core.notice.dto.NoticeTotalSearchResponse import org.springframework.data.domain.Pageable import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Component @@ -28,6 +30,8 @@ interface CustomNoticeRepository { usePageBtn: Boolean, isStaff: Boolean ): NoticeSearchResponse + + fun totalSearchNotice(keyword: String, number: Int, stringLength: Int): NoticeTotalSearchResponse } @Component @@ -35,6 +39,44 @@ class NoticeRepositoryImpl( private val queryFactory: JPAQueryFactory, private val commonRepository: CommonRepository, ) : CustomNoticeRepository { + override fun totalSearchNotice( + keyword: String, + number: Int, + stringLength: Int, + ): NoticeTotalSearchResponse { + val doubleTemplate = commonRepository.searchFullDoubleTextTemplate( + keyword, + noticeEntity.title, + noticeEntity.plainTextDescription + ) + + val query = queryFactory.select( + noticeEntity.id, + noticeEntity.title, + noticeEntity.createdAt, + noticeEntity.plainTextDescription + ).from(noticeEntity) + .where(doubleTemplate.gt(0.0)) + + val total = query.clone().select(noticeEntity.countDistinct()).fetchOne()!! + + val searchResult = query.limit(number.toLong()).fetch() + + return NoticeTotalSearchResponse( + total.toInt(), + searchResult.map { + NoticeTotalSearchElement( + it[noticeEntity.id]!!, + it[noticeEntity.title]!!, + it[noticeEntity.createdAt]!!, + it[noticeEntity.plainTextDescription]!!, + keyword, + stringLength, + ) + } + ) + } + override fun searchNotice( tag: List?, keyword: String?, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeTotalSearchElement.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeTotalSearchElement.kt new file mode 100644 index 00000000..bb9869bf --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeTotalSearchElement.kt @@ -0,0 +1,28 @@ +package com.wafflestudio.csereal.core.notice.dto + +import com.wafflestudio.csereal.common.utils.substringAroundKeyword +import java.time.LocalDateTime + +data class NoticeTotalSearchElement private constructor( + val id: Long, + val title: String, + val createdAt: LocalDateTime, +) { + lateinit var partialDescription: String + var boldStartIndex: Int = 0 + var boldEndIndex: Int = 0 + + constructor( + id: Long, + title: String, + createdAt: LocalDateTime, + description: String, + keyword: String, + amount: Int, + ): this(id, title, createdAt) { + val (startIdx, partialDescription) = substringAroundKeyword(keyword, description, amount) + this.boldStartIndex = startIdx ?: 0 + this.boldEndIndex = startIdx ?. let { it + keyword.length } ?: 0 + this.partialDescription = partialDescription + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeTotalSearchResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeTotalSearchResponse.kt new file mode 100644 index 00000000..7afcfa82 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeTotalSearchResponse.kt @@ -0,0 +1,6 @@ +package com.wafflestudio.csereal.core.notice.dto + +data class NoticeTotalSearchResponse ( + val total: Int, + val results: List +) \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt index 8e193030..0edf0ed7 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt @@ -26,6 +26,8 @@ interface NoticeService { isStaff: Boolean ): NoticeSearchResponse + fun searchTotalNotice(keyword: String, number: Int, stringLength: Int): NoticeTotalSearchResponse + fun readNotice(noticeId: Long): NoticeDto fun createNotice(request: NoticeDto, attachments: List?): NoticeDto fun updateNotice( @@ -59,6 +61,13 @@ class NoticeServiceImpl( return noticeRepository.searchNotice(tag, keyword, pageable, usePageBtn, isStaff) } + @Transactional(readOnly = true) + override fun searchTotalNotice( + keyword: String, + number: Int, + stringLength: Int, + ) = noticeRepository.totalSearchNotice(keyword, number, stringLength) + @Transactional(readOnly = true) override fun readNotice(noticeId: Long): NoticeDto { val notice = noticeRepository.findByIdOrNull(noticeId) From bb5e7ac9ddb627c356ac7f65d6f29301a85a9f16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=ED=98=81=EC=A4=80=20=28HyukJoon=20Woo=29?= Date: Mon, 18 Sep 2023 21:33:18 +0900 Subject: [PATCH 100/124] =?UTF-8?q?Feat:=20News=20=ED=86=B5=ED=95=A9=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=20API=20=EA=B5=AC=ED=98=84=20(#124)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat: Add dto for total search of news. * Feat: Add repository for total search. * Feat: Add service, controller for news total search. * Fix: Fix conflict issues. --- .../csereal/core/news/api/NewsController.kt | 12 +++ .../core/news/database/NewsRepository.kt | 79 +++++++++++++++++-- .../core/news/dto/NewsTotalSearchDto.kt | 6 ++ .../core/news/dto/NewsTotalSearchElement.kt | 32 ++++++++ .../csereal/core/news/service/NewsService.kt | 14 ++++ 5 files changed, 136 insertions(+), 7 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsTotalSearchDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsTotalSearchElement.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt index f9b1a73a..79c58a43 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt @@ -7,6 +7,9 @@ import com.wafflestudio.csereal.core.news.service.NewsService import com.wafflestudio.csereal.core.user.database.Role import com.wafflestudio.csereal.core.user.database.UserRepository import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Positive +import org.hibernate.validator.constraints.Length import org.springframework.data.domain.PageRequest import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity @@ -41,6 +44,15 @@ class NewsController( return ResponseEntity.ok(newsService.searchNews(tag, keyword, pageRequest, usePageBtn, isStaff)) } + @GetMapping("/totalSearch") + fun searchTotalNews( + @RequestParam(required = true) @Length(min = 1) @NotBlank keyword: String, + @RequestParam(required = true) @Positive number: Int, + @RequestParam(required = false, defaultValue = "200") @Positive stringLength: Int, + ) = ResponseEntity.ok( + newsService.searchTotalNews(keyword, number, stringLength) + ) + @GetMapping("/{newsId}") fun readNews( @PathVariable newsId: Long diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt index 76b356e2..eec400f0 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt @@ -8,8 +8,13 @@ import com.wafflestudio.csereal.common.utils.FixedPageRequest import com.wafflestudio.csereal.common.utils.cleanTextFromHtml import com.wafflestudio.csereal.core.news.database.QNewsEntity.newsEntity import com.wafflestudio.csereal.core.news.database.QNewsTagEntity.newsTagEntity +import com.wafflestudio.csereal.core.news.database.QTagInNewsEntity.tagInNewsEntity import com.wafflestudio.csereal.core.news.dto.NewsSearchDto import com.wafflestudio.csereal.core.news.dto.NewsSearchResponse +import com.wafflestudio.csereal.core.news.dto.NewsTotalSearchDto +import com.wafflestudio.csereal.core.news.dto.NewsTotalSearchElement +import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity +import com.wafflestudio.csereal.core.resource.mainImage.database.QMainImageEntity.mainImageEntity import com.wafflestudio.csereal.core.notice.database.QNoticeEntity import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService import org.springframework.data.domain.Pageable @@ -24,13 +29,13 @@ interface NewsRepository : JpaRepository, CustomNewsRepository } interface CustomNewsRepository { - fun searchNews( - tag: List?, - keyword: String?, - pageable: Pageable, - usePageBtn: Boolean, - isStaff: Boolean - ): NewsSearchResponse + fun searchNews(tag: List?, keyword: String?, pageable: Pageable, usePageBtn: Boolean, isStaff: Boolean): NewsSearchResponse + fun searchTotalNews( + keyword: String, + number: Int, + amount: Int, + imageUrlCreator: (MainImageEntity?) -> String?, + ): NewsTotalSearchDto } @Component @@ -111,4 +116,64 @@ class NewsRepositoryImpl( } return NewsSearchResponse(total, newsSearchDtoList) } + + override fun searchTotalNews( + keyword: String, + number: Int, + amount: Int, + imageUrlCreator: (MainImageEntity?) -> String?, + ): NewsTotalSearchDto { + val doubleTemplate = commonRepository.searchFullDoubleTextTemplate( + keyword, + newsEntity.title, + newsEntity.plainTextDescription, + ) + + val searchResult = queryFactory.select( + newsEntity.id, + newsEntity.title, + newsEntity.date, + newsEntity.plainTextDescription, + mainImageEntity, + ).from(newsEntity) + .leftJoin(mainImageEntity) + .where(doubleTemplate.gt(0.0)) + .limit(number.toLong()) + .fetch() + + val searchResultTags = queryFactory.select( + newsTagEntity.news.id, + newsTagEntity.tag.name, + ).from(newsTagEntity) + .rightJoin(newsEntity) + .leftJoin(tagInNewsEntity) + .where(newsTagEntity.news.id.`in`(searchResult.map { it[newsEntity.id] })) + .distinct() + .fetch() + + val total = queryFactory.select(newsEntity.countDistinct()) + .from(newsEntity) + .where(doubleTemplate.gt(0.0)) + .fetchOne()!! + + return NewsTotalSearchDto( + total.toInt(), + searchResult.map { + NewsTotalSearchElement( + id = it[newsEntity.id]!!, + title = it[newsEntity.title]!!, + date = it[newsEntity.date], + tags = searchResultTags.filter { + tag -> tag[newsTagEntity.news.id] == it[newsEntity.id] + }.map { + tag -> tag[newsTagEntity.tag.name]!!.krName + }, + imageUrl = imageUrlCreator(it[mainImageEntity]), + description = it[newsEntity.plainTextDescription]!!, + keyword = keyword, + amount = amount, + ) + } + ) + } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsTotalSearchDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsTotalSearchDto.kt new file mode 100644 index 00000000..3b8e9fcf --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsTotalSearchDto.kt @@ -0,0 +1,6 @@ +package com.wafflestudio.csereal.core.news.dto + +data class NewsTotalSearchDto ( + val total: Int, + val results: List +) \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsTotalSearchElement.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsTotalSearchElement.kt new file mode 100644 index 00000000..69c048ce --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsTotalSearchElement.kt @@ -0,0 +1,32 @@ +package com.wafflestudio.csereal.core.news.dto + +import com.wafflestudio.csereal.common.utils.substringAroundKeyword +import java.time.LocalDateTime + +data class NewsTotalSearchElement private constructor( + val id: Long, + val title: String, + val date: LocalDateTime?, + val tags: List, + val imageUrl: String?, +) { + lateinit var partialDescription: String + var boldStartIndex: Int = 0 + var boldEndIndex: Int = 0 + + constructor( + id: Long, + title: String, + date: LocalDateTime?, + tags: List, + imageUrl: String?, + description: String, + keyword: String, + amount: Int, + ) : this(id, title, date, tags, imageUrl) { + val (startIdx, substring) = substringAroundKeyword(keyword, description, amount) + partialDescription = substring + boldStartIndex = startIdx ?: 0 + boldEndIndex = startIdx?.plus(keyword.length) ?: 0 + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt index f8b81d82..472248e1 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt @@ -4,6 +4,7 @@ import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.core.news.database.* import com.wafflestudio.csereal.core.news.dto.NewsDto import com.wafflestudio.csereal.core.news.dto.NewsSearchResponse +import com.wafflestudio.csereal.core.news.dto.NewsTotalSearchDto import com.wafflestudio.csereal.core.resource.attachment.service.AttachmentService import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService import org.springframework.data.domain.Pageable @@ -32,6 +33,7 @@ interface NewsService { fun deleteNews(newsId: Long) fun enrollTag(tagName: String) + fun searchTotalNews(keyword: String, number: Int, amount: Int): NewsTotalSearchDto } @Service @@ -53,6 +55,18 @@ class NewsServiceImpl( return newsRepository.searchNews(tag, keyword, pageable, usePageBtn, isStaff) } + @Transactional(readOnly = true) + override fun searchTotalNews( + keyword: String, + number: Int, + amount: Int, + ) = newsRepository.searchTotalNews( + keyword, + number, + amount, + mainImageService::createImageURL, + ) + @Transactional(readOnly = true) override fun readNews(newsId: Long): NewsDto { val news: NewsEntity = newsRepository.findByIdOrNull(newsId) From 467e25fda29c6314825a30e2b0c7bc037e88e4cc Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Mon, 18 Sep 2023 23:31:41 +0900 Subject: [PATCH 101/124] =?UTF-8?q?fix:=20=EA=B8=B0=EC=A1=B4=20objectMappe?= =?UTF-8?q?r=20=EC=84=A4=EC=A0=95=20=EC=98=A4=EB=B2=84=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EB=93=9C=20(#132)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../csereal/common/config/JacksonConfig.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/JacksonConfig.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/JacksonConfig.kt index a1558b82..5c32ca85 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/JacksonConfig.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/JacksonConfig.kt @@ -5,22 +5,22 @@ import com.fasterxml.jackson.databind.JsonSerializer import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.SerializerProvider import com.fasterxml.jackson.databind.module.SimpleModule +import org.springframework.boot.context.event.ApplicationReadyEvent import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration +import org.springframework.context.event.EventListener import java.time.LocalDateTime import java.time.ZoneOffset import java.time.format.DateTimeFormatter @Configuration -class JacksonConfig { +class JacksonConfig(private val objectMapper: ObjectMapper) { - @Bean - fun objectMapper(): ObjectMapper { - val objectMapper = ObjectMapper() + @EventListener(ApplicationReadyEvent::class) + fun setUp() { val module = SimpleModule() module.addSerializer(LocalDateTime::class.java, LocalDateTimeSerializer()) objectMapper.registerModule(module) - return objectMapper } } From 4ead93d36b43341e9302fe24467f30c287e95a4b Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Tue, 19 Sep 2023 00:09:34 +0900 Subject: [PATCH 102/124] =?UTF-8?q?feat:=20=ED=8C=8C=EC=9D=BC=20=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#133)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../attachment/api/AttachmentController.kt | 13 ----- .../attachment/service/AttachmentService.kt | 4 -- .../api/DeprecatedFileController.kt | 35 +++++------ .../api/FileController.kt | 58 ++++++++++++++----- .../resource/common/dto/FileUploadResponse.kt | 12 ++++ .../mainImage/api/MainImageController.kt | 12 ---- src/main/resources/application.yaml | 10 ++-- 7 files changed, 75 insertions(+), 69 deletions(-) delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/api/AttachmentController.kt rename src/main/kotlin/com/wafflestudio/csereal/core/resource/{mainImage => common}/api/DeprecatedFileController.kt (58%) rename src/main/kotlin/com/wafflestudio/csereal/core/resource/{mainImage => common}/api/FileController.kt (52%) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/resource/common/dto/FileUploadResponse.kt delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/MainImageController.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/api/AttachmentController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/api/AttachmentController.kt deleted file mode 100644 index f2b39677..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/api/AttachmentController.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.wafflestudio.csereal.core.resource.attachment.api - -import com.wafflestudio.csereal.core.resource.attachment.service.AttachmentService -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RestController - -@RequestMapping("/api/v1/attachment") -@RestController -class AttachmentController( - private val attachmentService: AttachmentService -) { - -} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt index 1aa7be72..a49438e0 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt @@ -51,8 +51,6 @@ class AttachmentServiceImpl( override fun uploadAttachmentInLabEntity(labEntity: LabEntity, requestAttachment: MultipartFile): AttachmentDto { Files.createDirectories(Paths.get(path)) - val extension = FilenameUtils.getExtension(requestAttachment.originalFilename) - val timeMillis = System.currentTimeMillis() val filename = "${timeMillis}_${requestAttachment.originalFilename}" @@ -87,8 +85,6 @@ class AttachmentServiceImpl( val attachmentsList = mutableListOf() for ((index, requestAttachment) in requestAttachments.withIndex()) { - val extension = FilenameUtils.getExtension(requestAttachment.originalFilename) - val timeMillis = System.currentTimeMillis() val filename = "${timeMillis}_${requestAttachment.originalFilename}" diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/DeprecatedFileController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/DeprecatedFileController.kt similarity index 58% rename from src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/DeprecatedFileController.kt rename to src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/DeprecatedFileController.kt index 24834a17..e80901ee 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/DeprecatedFileController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/DeprecatedFileController.kt @@ -1,4 +1,4 @@ -package com.wafflestudio.csereal.core.resource.mainImage.api +package com.wafflestudio.csereal.core.resource.common.api import jakarta.servlet.http.HttpServletRequest import org.springframework.beans.factory.annotation.Value @@ -18,21 +18,20 @@ import java.nio.file.Paths @RestController @RequestMapping("/sites/default/files") -class DeprecatedFileController ( - @Value("\${oldFiles.path}") - private val oldFilesPath: String, +class DeprecatedFileController( + @Value("\${oldFiles.path}") + private val oldFilesPath: String, ) { @GetMapping("/{map}/**") fun serveOldFile( - @PathVariable map: String, // Just for ensure at least one path variable - @RequestParam(defaultValue = "false") download: Boolean, - request: HttpServletRequest + @PathVariable map: String, // Just for ensure at least one path variable + request: HttpServletRequest ): ResponseEntity { // Extract path from pattern val fileSubDir = AntPathMatcher().extractPathWithinPattern( - "/sites/default/files/**", - request.servletPath - ).substringAfter("/sites/default/files/") + "/sites/default/files/**", + request.servletPath + ).substringAfter("/sites/default/files/") val file = Paths.get(oldFilesPath, fileSubDir) val resource = UrlResource(file.toUri()) @@ -41,22 +40,14 @@ class DeprecatedFileController ( val headers = HttpHeaders() headers.contentType = - org.springframework.http.MediaType.parseMediaType(contentType ?: "application/octet-stream") - - if (download) { - val originalFilename = fileSubDir.substringAfterLast("/") - - val encodedFilename = URLEncoder.encode(originalFilename, Charsets.UTF_8.toString()).replace("+", "%20") - - headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''$encodedFilename") - } + org.springframework.http.MediaType.parseMediaType(contentType ?: "application/octet-stream") ResponseEntity.ok() - .headers(headers) - .body(resource) + .headers(headers) + .body(resource) } else { ResponseEntity.status(HttpStatus.NOT_FOUND).build() } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/FileController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/FileController.kt similarity index 52% rename from src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/FileController.kt rename to src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/FileController.kt index 0cb63ec7..efb3be6a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/FileController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/FileController.kt @@ -1,6 +1,9 @@ -package com.wafflestudio.csereal.core.resource.mainImage.api +package com.wafflestudio.csereal.core.resource.common.api import com.wafflestudio.csereal.common.aop.AuthenticatedStaff +import com.wafflestudio.csereal.common.properties.EndpointProperties +import com.wafflestudio.csereal.core.resource.common.dto.FileUploadResponse +import com.wafflestudio.csereal.core.resource.common.dto.UploadFileInfo import jakarta.servlet.http.HttpServletRequest import org.springframework.beans.factory.annotation.Value import org.springframework.core.io.Resource @@ -9,23 +12,22 @@ import org.springframework.http.HttpHeaders import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* -import java.net.URLEncoder +import org.springframework.web.multipart.MultipartFile import java.nio.file.Files import java.nio.file.Paths -import kotlin.text.Charsets.UTF_8 @RequestMapping("/api/v1/file") @RestController class FileController( @Value("\${csereal.upload.path}") - private val uploadPath: String + private val uploadPath: String, + private val endpointProperties: EndpointProperties ) { @GetMapping("/{filename:.+}") fun serveFile( @PathVariable filename: String, - @RequestParam(defaultValue = "false") download: Boolean, request: HttpServletRequest ): ResponseEntity { val file = Paths.get(uploadPath, filename) @@ -38,14 +40,6 @@ class FileController( headers.contentType = org.springframework.http.MediaType.parseMediaType(contentType ?: "application/octet-stream") - if (download) { - val originalFilename = filename.substringAfter("_") - - val encodedFilename = URLEncoder.encode(originalFilename, UTF_8.toString()).replace("+", "%20") - - headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''$encodedFilename") - } - return ResponseEntity.ok() .headers(headers) .body(resource) @@ -54,6 +48,44 @@ class FileController( } } + @PostMapping("/upload") + fun uploadFiles(@RequestParam files: Map): ResponseEntity { + return try { + Files.createDirectories(Paths.get(uploadPath)) + + val results = mutableListOf() + + for ((_, file) in files) { + val timeMillis = System.currentTimeMillis() + + val filename = "${timeMillis}_${file.originalFilename}" + val totalFilename = uploadPath + filename + val saveFile = Paths.get(totalFilename) + file.transferTo(saveFile) + + val imageUrl = "${endpointProperties.backend}/v1/file/${filename}" + + results.add( + UploadFileInfo( + url = imageUrl, + name = file.originalFilename ?: "unknown", + size = file.size + ) + ) + } + + ResponseEntity( + FileUploadResponse(result = results), + HttpStatus.OK + ) + } catch (e: Exception) { + ResponseEntity( + FileUploadResponse(errorMessage = "An error occurred while uploading the images"), + HttpStatus.BAD_REQUEST + ) + } + } + @AuthenticatedStaff @DeleteMapping("/{filename:.+}") fun deleteFile(@PathVariable filename: String): ResponseEntity { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/dto/FileUploadResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/dto/FileUploadResponse.kt new file mode 100644 index 00000000..220bcce4 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/dto/FileUploadResponse.kt @@ -0,0 +1,12 @@ +package com.wafflestudio.csereal.core.resource.common.dto + +data class FileUploadResponse( + val errorMessage: String? = null, + val result: List? = null +) + +data class UploadFileInfo( + val url: String, + val name: String, + val size: Long +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/MainImageController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/MainImageController.kt deleted file mode 100644 index 7695fff7..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/api/MainImageController.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.wafflestudio.csereal.core.resource.mainImage.api - -import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService -import org.springframework.web.bind.annotation.* - -@RequestMapping("/image") -@RestController -class MainImageController( - private val mainImageService: MainImageService -) { - -} \ No newline at end of file diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 2621263a..b9e35aff 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -68,10 +68,10 @@ csereal: path: ./files/ oldFiles: - path: ./cse-files + path: ./cse-files/ endpoint: - backend: http://localhost:8080 + backend: http://localhost:8080/api frontend: http://localhost:3000 --- @@ -96,7 +96,7 @@ csereal: path: /app/files/ oldFiles: - path: /app/cse-files + path: /app/cse-files/ endpoint: backend: https://${URL}/api @@ -141,8 +141,8 @@ csereal: path: ./files/ oldFiles: - path: ./cse-files + path: ./cse-files/ endpoint: - backend: http://localhost:8080 + backend: http://localhost:8080/api frontend: http://localhost:3000 From 30c538d8343829768f347d45daf8928dcbed3398 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=ED=98=81=EC=A4=80=20=28HyukJoon=20Woo=29?= Date: Tue, 19 Sep 2023 00:14:15 +0900 Subject: [PATCH 103/124] Feat: Change titleForMain column to TEXT type. (#134) Co-authored-by: Junhyeong Kim --- .../com/wafflestudio/csereal/core/news/database/NewsEntity.kt | 1 + .../wafflestudio/csereal/core/notice/database/NoticeEntity.kt | 2 ++ .../wafflestudio/csereal/core/seminar/database/SeminarEntity.kt | 1 + 3 files changed, 4 insertions(+) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt index 40e5df0e..216fc5e2 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt @@ -16,6 +16,7 @@ class NewsEntity( var isDeleted: Boolean = false, var title: String, + @Column(columnDefinition = "text") var titleForMain: String?, @Column(columnDefinition = "mediumtext") diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt index 6a0d0915..0670b222 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt @@ -13,6 +13,8 @@ import jakarta.persistence.* class NoticeEntity( var isDeleted: Boolean = false, var title: String, + + @Column(columnDefinition = "text") var titleForMain: String?, @Column(columnDefinition = "mediumtext") diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt index 46e46ca0..4c177561 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt @@ -17,6 +17,7 @@ class SeminarEntity( var title: String, + @Column(columnDefinition = "text") var titleForMain: String?, @Column(columnDefinition = "mediumtext") From 2baab34a0c478351a8e0302b59c8b3c874742e64 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Tue, 19 Sep 2023 13:58:41 +0900 Subject: [PATCH 104/124] hotfix: mixed content issue (#136) --- src/main/resources/application.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index b9e35aff..0b6eb2e4 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -26,6 +26,7 @@ server: servlet: session: timeout: 7200 # 2시간 + forward-headers-strategy: native springdoc: paths-to-match: From 4ee0dc378f9023eb95876299abbd866175260d6b Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Tue, 19 Sep 2023 19:24:39 +0900 Subject: [PATCH 105/124] hotfix: loginPage to https (#139) * hotfix: mixed content issue * hotfix: loginPage --- .../com/wafflestudio/csereal/common/config/SecurityConfig.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt index bee84ad8..31c7b601 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt @@ -32,7 +32,7 @@ class SecurityConfig( .cors().and() .csrf().disable() .oauth2Login() - .loginPage("/oauth2/authorization/idsnucse") + .loginPage("${endpointProperties.frontend}/oauth2/authorization/idsnucse") .redirectionEndpoint() .baseUri("/api/v1/login/oauth2/code/idsnucse").and() .userInfoEndpoint().oidcUserService(customOidcUserService).and() From f33794396dfd23378fcdc274ee77dd038e1a903f Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Tue, 19 Sep 2023 19:48:12 +0900 Subject: [PATCH 106/124] =?UTF-8?q?fix:=20false=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#141)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wafflestudio/csereal/core/main/database/MainRepository.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt index 52e4fb08..91947243 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt @@ -75,7 +75,7 @@ class MainRepositoryImpl( .rightJoin(noticeEntity).on(noticeTagEntity.notice.eq(noticeEntity)) .rightJoin(tagInNoticeEntity).on(noticeTagEntity.tag.eq(tagInNoticeEntity)) .where(noticeTagEntity.tag.name.eq(tagEnum)) - .where(noticeEntity.isDeleted.eq(false), noticeEntity.isPrivate.eq(true)) + .where(noticeEntity.isDeleted.eq(false), noticeEntity.isPrivate.eq(false)) .orderBy(noticeEntity.isPinned.desc()).orderBy(noticeEntity.createdAt.desc()) .limit(6).distinct().fetch() } From 96934739a2fa2d4c9697b4a40a50e73e1ed18506 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Tue, 19 Sep 2023 22:52:28 +0900 Subject: [PATCH 107/124] =?UTF-8?q?fix:=20=EC=9D=B4=EC=A0=84=20=EB=8B=A4?= =?UTF-8?q?=EC=9D=8C=20=EA=B8=80=EC=97=90=EC=84=9C=20=EB=B9=84=EA=B3=B5?= =?UTF-8?q?=EA=B0=9C=20=EA=B8=80=20=EC=A0=9C=EC=99=B8=20(#143)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 이전 다음글 비공개 제외 * fix: prev-next * CBT 위해서 예약 GET 권한체크 주석처리 --- .../core/news/database/NewsRepository.kt | 87 ++++++++++--------- .../csereal/core/news/service/NewsService.kt | 20 +++-- .../core/notice/database/NoticeRepository.kt | 48 +++++----- .../core/notice/service/NoticeService.kt | 6 +- .../reservation/api/ReservceController.kt | 6 +- .../reservation/service/ReservationService.kt | 20 ++--- .../seminar/database/SeminarRepository.kt | 4 +- .../core/seminar/service/SeminarService.kt | 6 +- src/main/resources/application.yaml | 1 - 9 files changed, 105 insertions(+), 93 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt index eec400f0..80aad842 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt @@ -24,17 +24,24 @@ import java.time.LocalDateTime interface NewsRepository : JpaRepository, CustomNewsRepository { fun findAllByIsImportant(isImportant: Boolean): List - fun findFirstByCreatedAtLessThanOrderByCreatedAtDesc(timestamp: LocalDateTime): NewsEntity? - fun findFirstByCreatedAtGreaterThanOrderByCreatedAtAsc(timestamp: LocalDateTime): NewsEntity? + fun findFirstByCreatedAtLessThanAndIsPrivateFalseOrderByCreatedAtDesc(timestamp: LocalDateTime): NewsEntity? + fun findFirstByCreatedAtGreaterThanAndIsPrivateFalseOrderByCreatedAtAsc(timestamp: LocalDateTime): NewsEntity? } interface CustomNewsRepository { - fun searchNews(tag: List?, keyword: String?, pageable: Pageable, usePageBtn: Boolean, isStaff: Boolean): NewsSearchResponse + fun searchNews( + tag: List?, + keyword: String?, + pageable: Pageable, + usePageBtn: Boolean, + isStaff: Boolean + ): NewsSearchResponse + fun searchTotalNews( - keyword: String, - number: Int, - amount: Int, - imageUrlCreator: (MainImageEntity?) -> String?, + keyword: String, + number: Int, + amount: Int, + imageUrlCreator: (MainImageEntity?) -> String?, ): NewsTotalSearchDto } @@ -118,10 +125,10 @@ class NewsRepositoryImpl( } override fun searchTotalNews( - keyword: String, - number: Int, - amount: Int, - imageUrlCreator: (MainImageEntity?) -> String?, + keyword: String, + number: Int, + amount: Int, + imageUrlCreator: (MainImageEntity?) -> String?, ): NewsTotalSearchDto { val doubleTemplate = commonRepository.searchFullDoubleTextTemplate( keyword, @@ -130,21 +137,21 @@ class NewsRepositoryImpl( ) val searchResult = queryFactory.select( - newsEntity.id, - newsEntity.title, - newsEntity.date, - newsEntity.plainTextDescription, - mainImageEntity, - ).from(newsEntity) + newsEntity.id, + newsEntity.title, + newsEntity.date, + newsEntity.plainTextDescription, + mainImageEntity, + ).from(newsEntity) .leftJoin(mainImageEntity) .where(doubleTemplate.gt(0.0)) .limit(number.toLong()) .fetch() val searchResultTags = queryFactory.select( - newsTagEntity.news.id, - newsTagEntity.tag.name, - ).from(newsTagEntity) + newsTagEntity.news.id, + newsTagEntity.tag.name, + ).from(newsTagEntity) .rightJoin(newsEntity) .leftJoin(tagInNewsEntity) .where(newsTagEntity.news.id.`in`(searchResult.map { it[newsEntity.id] })) @@ -152,28 +159,28 @@ class NewsRepositoryImpl( .fetch() val total = queryFactory.select(newsEntity.countDistinct()) - .from(newsEntity) - .where(doubleTemplate.gt(0.0)) - .fetchOne()!! + .from(newsEntity) + .where(doubleTemplate.gt(0.0)) + .fetchOne()!! return NewsTotalSearchDto( - total.toInt(), - searchResult.map { - NewsTotalSearchElement( - id = it[newsEntity.id]!!, - title = it[newsEntity.title]!!, - date = it[newsEntity.date], - tags = searchResultTags.filter { - tag -> tag[newsTagEntity.news.id] == it[newsEntity.id] - }.map { - tag -> tag[newsTagEntity.tag.name]!!.krName - }, - imageUrl = imageUrlCreator(it[mainImageEntity]), - description = it[newsEntity.plainTextDescription]!!, - keyword = keyword, - amount = amount, - ) - } + total.toInt(), + searchResult.map { + NewsTotalSearchElement( + id = it[newsEntity.id]!!, + title = it[newsEntity.title]!!, + date = it[newsEntity.date], + tags = searchResultTags.filter { tag -> + tag[newsTagEntity.news.id] == it[newsEntity.id] + }.map { tag -> + tag[newsTagEntity.tag.name]!!.krName + }, + imageUrl = imageUrlCreator(it[mainImageEntity]), + description = it[newsEntity.plainTextDescription]!!, + keyword = keyword, + amount = amount, + ) + } ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt index 472248e1..1772c43b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt @@ -57,14 +57,14 @@ class NewsServiceImpl( @Transactional(readOnly = true) override fun searchTotalNews( - keyword: String, - number: Int, - amount: Int, + keyword: String, + number: Int, + amount: Int, ) = newsRepository.searchTotalNews( - keyword, - number, - amount, - mainImageService::createImageURL, + keyword, + number, + amount, + mainImageService::createImageURL, ) @Transactional(readOnly = true) @@ -77,8 +77,10 @@ class NewsServiceImpl( val imageURL = mainImageService.createImageURL(news.mainImage) val attachmentResponses = attachmentService.createAttachmentResponses(news.attachments) - val prevNews = newsRepository.findFirstByCreatedAtLessThanOrderByCreatedAtDesc(news.createdAt!!) - val nextNews = newsRepository.findFirstByCreatedAtGreaterThanOrderByCreatedAtAsc(news.createdAt!!) + val prevNews = + newsRepository.findFirstByCreatedAtLessThanAndIsPrivateFalseOrderByCreatedAtDesc(news.createdAt!!) + val nextNews = + newsRepository.findFirstByCreatedAtGreaterThanAndIsPrivateFalseOrderByCreatedAtAsc(news.createdAt!!) return NewsDto.of(news, imageURL, attachmentResponses, prevNews, nextNews) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt index 6a8c8c61..a0b9769b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt @@ -18,8 +18,8 @@ import java.time.LocalDateTime interface NoticeRepository : JpaRepository, CustomNoticeRepository { fun findAllByIsImportant(isImportant: Boolean): List - fun findFirstByCreatedAtLessThanOrderByCreatedAtDesc(timestamp: LocalDateTime): NoticeEntity? - fun findFirstByCreatedAtGreaterThanOrderByCreatedAtAsc(timestamp: LocalDateTime): NoticeEntity? + fun findFirstByCreatedAtLessThanAndIsPrivateFalseOrderByCreatedAtDesc(timestamp: LocalDateTime): NoticeEntity? + fun findFirstByCreatedAtGreaterThanAndIsPrivateFalseOrderByCreatedAtAsc(timestamp: LocalDateTime): NoticeEntity? } interface CustomNoticeRepository { @@ -40,22 +40,22 @@ class NoticeRepositoryImpl( private val commonRepository: CommonRepository, ) : CustomNoticeRepository { override fun totalSearchNotice( - keyword: String, - number: Int, - stringLength: Int, + keyword: String, + number: Int, + stringLength: Int, ): NoticeTotalSearchResponse { val doubleTemplate = commonRepository.searchFullDoubleTextTemplate( - keyword, - noticeEntity.title, - noticeEntity.plainTextDescription + keyword, + noticeEntity.title, + noticeEntity.plainTextDescription ) val query = queryFactory.select( - noticeEntity.id, - noticeEntity.title, - noticeEntity.createdAt, - noticeEntity.plainTextDescription - ).from(noticeEntity) + noticeEntity.id, + noticeEntity.title, + noticeEntity.createdAt, + noticeEntity.plainTextDescription + ).from(noticeEntity) .where(doubleTemplate.gt(0.0)) val total = query.clone().select(noticeEntity.countDistinct()).fetchOne()!! @@ -63,17 +63,17 @@ class NoticeRepositoryImpl( val searchResult = query.limit(number.toLong()).fetch() return NoticeTotalSearchResponse( - total.toInt(), - searchResult.map { - NoticeTotalSearchElement( - it[noticeEntity.id]!!, - it[noticeEntity.title]!!, - it[noticeEntity.createdAt]!!, - it[noticeEntity.plainTextDescription]!!, - keyword, - stringLength, - ) - } + total.toInt(), + searchResult.map { + NoticeTotalSearchElement( + it[noticeEntity.id]!!, + it[noticeEntity.title]!!, + it[noticeEntity.createdAt]!!, + it[noticeEntity.plainTextDescription]!!, + keyword, + stringLength, + ) + } ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt index 0edf0ed7..26f711ae 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt @@ -77,8 +77,10 @@ class NoticeServiceImpl( val attachmentResponses = attachmentService.createAttachmentResponses(notice.attachments) - val prevNotice = noticeRepository.findFirstByCreatedAtLessThanOrderByCreatedAtDesc(notice.createdAt!!) - val nextNotice = noticeRepository.findFirstByCreatedAtGreaterThanOrderByCreatedAtAsc(notice.createdAt!!) + val prevNotice = + noticeRepository.findFirstByCreatedAtLessThanAndIsPrivateFalseOrderByCreatedAtDesc(notice.createdAt!!) + val nextNotice = + noticeRepository.findFirstByCreatedAtGreaterThanAndIsPrivateFalseOrderByCreatedAtAsc(notice.createdAt!!) return NoticeDto.of(notice, attachmentResponses, prevNotice, nextNotice) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservceController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservceController.kt index d7b0f094..40e929e9 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservceController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservceController.kt @@ -24,7 +24,7 @@ class ReservationController( ) { @GetMapping("/month") - @AuthenticatedForReservation +// @AuthenticatedForReservation TODO: CBT 끝나면 주석 제거 fun getMonthlyReservations( @RequestParam roomId: Long, @RequestParam year: Int, @@ -36,7 +36,7 @@ class ReservationController( } @GetMapping("/week") - @AuthenticatedForReservation +// @AuthenticatedForReservation fun getWeeklyReservations( @RequestParam roomId: Long, @RequestParam year: Int, @@ -49,7 +49,7 @@ class ReservationController( } @GetMapping("/{reservationId}") - @AuthenticatedForReservation +// @AuthenticatedForReservation fun getReservation(@PathVariable reservationId: Long): ResponseEntity { return ResponseEntity.ok(reservationService.getReservation(reservationId)) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/service/ReservationService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/service/ReservationService.kt index e3ad74b3..4dcd0da8 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/service/ReservationService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/service/ReservationService.kt @@ -83,16 +83,16 @@ class ReservationServiceImpl( val reservationEntity = reservationRepository.findByIdOrNull(reservationId) ?: throw CserealException.Csereal404("예약을 찾을 수 없습니다.") - val user = RequestContextHolder.getRequestAttributes()?.getAttribute( - "loggedInUser", - RequestAttributes.SCOPE_REQUEST - ) as UserEntity - - if (user.role == Role.ROLE_STAFF) { - return ReservationDto.of(reservationEntity) - } else { - return ReservationDto.forNormalUser(reservationEntity) - } +// val user = RequestContextHolder.getRequestAttributes()?.getAttribute( +// "loggedInUser", +// RequestAttributes.SCOPE_REQUEST +// ) as UserEntity +// +// if (user.role == Role.ROLE_STAFF) { +// return ReservationDto.of(reservationEntity) +// } else { + return ReservationDto.forNormalUser(reservationEntity) +// } } override fun cancelSpecific(reservationId: Long) { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt index 26d3633a..c086a9fa 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt @@ -17,8 +17,8 @@ import java.time.LocalDateTime interface SeminarRepository : JpaRepository, CustomSeminarRepository { fun findAllByIsImportant(isImportant: Boolean): List - fun findFirstByCreatedAtLessThanOrderByCreatedAtDesc(timestamp: LocalDateTime): SeminarEntity? - fun findFirstByCreatedAtGreaterThanOrderByCreatedAtAsc(timestamp: LocalDateTime): SeminarEntity? + fun findFirstByCreatedAtLessThanAndIsPrivateFalseOrderByCreatedAtDesc(timestamp: LocalDateTime): SeminarEntity? + fun findFirstByCreatedAtGreaterThanAndIsPrivateFalseOrderByCreatedAtAsc(timestamp: LocalDateTime): SeminarEntity? } interface CustomSeminarRepository { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt index 25710ca6..1c874f0a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt @@ -82,8 +82,10 @@ class SeminarServiceImpl( val imageURL = mainImageService.createImageURL(seminar.mainImage) val attachmentResponses = attachmentService.createAttachmentResponses(seminar.attachments) - val prevSeminar = seminarRepository.findFirstByCreatedAtLessThanOrderByCreatedAtDesc(seminar.createdAt!!) - val nextSeminar = seminarRepository.findFirstByCreatedAtGreaterThanOrderByCreatedAtAsc(seminar.createdAt!!) + val prevSeminar = + seminarRepository.findFirstByCreatedAtLessThanAndIsPrivateFalseOrderByCreatedAtDesc(seminar.createdAt!!) + val nextSeminar = + seminarRepository.findFirstByCreatedAtGreaterThanAndIsPrivateFalseOrderByCreatedAtAsc(seminar.createdAt!!) return SeminarDto.of(seminar, imageURL, attachmentResponses, prevSeminar, nextSeminar) } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 0b6eb2e4..b9e35aff 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -26,7 +26,6 @@ server: servlet: session: timeout: 7200 # 2시간 - forward-headers-strategy: native springdoc: paths-to-match: From 70eef9cb9199bcc236ed45da9ba2b897708f6d0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=ED=98=81=EC=A4=80=20=28HyukJoon=20Woo=29?= Date: Tue, 19 Sep 2023 23:10:23 +0900 Subject: [PATCH 108/124] =?UTF-8?q?Feat:=20=EB=89=B4=EC=8A=A4=20=EB=82=A0?= =?UTF-8?q?=EC=A7=9C=EB=A1=9C=20=EC=A0=95=EB=A0=AC=20(#144)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat: Change date field to NOT NULLABLE. * Feat: Change to sort by date, not createdat. * Test: Fix test. * Test: Move news test dir. --------- Co-authored-by: Junhyeong Kim --- .../csereal/core/news/database/NewsEntity.kt | 2 +- .../csereal/core/news/database/NewsRepository.kt | 2 +- .../com/wafflestudio/csereal/core/news/dto/NewsDto.kt | 2 +- .../csereal/core/{notice => }/news/NewsServiceTest.kt | 10 ++++++---- 4 files changed, 9 insertions(+), 7 deletions(-) rename src/test/kotlin/com/wafflestudio/csereal/core/{notice => }/news/NewsServiceTest.kt (93%) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt index 216fc5e2..73ab66b2 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt @@ -25,7 +25,7 @@ class NewsEntity( @Column(columnDefinition = "mediumtext") var plainTextDescription: String, - var date: LocalDateTime?, + var date: LocalDateTime, var isPrivate: Boolean, var isSlide: Boolean, var isImportant: Boolean, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt index 80aad842..f545201c 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt @@ -102,7 +102,7 @@ class NewsRepositoryImpl( } val newsEntityList = jpaQuery - .orderBy(newsEntity.createdAt.desc()) + .orderBy(newsEntity.date.desc()) .offset(pageRequest.offset) .limit(pageRequest.pageSize.toLong()) .distinct() diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt index e78020fd..c913d702 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt @@ -12,7 +12,7 @@ data class NewsDto( val tags: List, val createdAt: LocalDateTime?, val modifiedAt: LocalDateTime?, - val date: LocalDateTime?, + val date: LocalDateTime, val isPrivate: Boolean, val isSlide: Boolean, val isImportant: Boolean, diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/notice/news/NewsServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/news/NewsServiceTest.kt similarity index 93% rename from src/test/kotlin/com/wafflestudio/csereal/core/notice/news/NewsServiceTest.kt rename to src/test/kotlin/com/wafflestudio/csereal/core/news/NewsServiceTest.kt index 13c9f8c7..a6ea95d7 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/notice/news/NewsServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/news/NewsServiceTest.kt @@ -1,4 +1,4 @@ -package com.wafflestudio.csereal.core.notice.news +package com.wafflestudio.csereal.core.news import com.wafflestudio.csereal.core.news.database.NewsEntity import com.wafflestudio.csereal.core.news.database.NewsRepository @@ -9,6 +9,7 @@ import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import org.springframework.boot.test.context.SpringBootTest import org.springframework.data.repository.findByIdOrNull +import java.time.LocalDateTime @SpringBootTest class NewsServiceTest( @@ -34,7 +35,7 @@ class NewsServiceTest( tags = emptyList(), createdAt = null, modifiedAt = null, - date = null, + date = LocalDateTime.now(), isPrivate = false, isSlide = false, isImportant = false, @@ -72,7 +73,7 @@ class NewsServiceTest(

Goodbye, World!

""".trimIndent(), plainTextDescription = "Hello, World! This is news description. Goodbye, World!", - date = null, + date = LocalDateTime.now(), isPrivate = false, isSlide = false, isImportant = false, @@ -103,7 +104,8 @@ class NewsServiceTest(

Goodbye, World!

This is additional description.

""".trimIndent() - updatedNewsEntity.plainTextDescription shouldBe "Hello, World! This is modified news description. Goodbye, World! This is additional description." + updatedNewsEntity.plainTextDescription shouldBe "Hello, World! This is modified news description." + + " Goodbye, World! This is additional description." } } } From 2eafe305a2d23beeda922408974496a0951c7a91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=ED=98=81=EC=A4=80=20=28HyukJoon=20Woo=29?= Date: Tue, 19 Sep 2023 23:14:06 +0900 Subject: [PATCH 109/124] Feat: Add description for slide. (#146) --- .../csereal/core/main/database/MainRepository.kt | 5 ++++- .../wafflestudio/csereal/core/main/dto/MainSlideResponse.kt | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt index 3351df55..698da1ce 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt @@ -44,7 +44,10 @@ class MainRepositoryImpl( id = it.id, title = it.title, imageURL = imageURL, - createdAt = it.createdAt + createdAt = it.createdAt, + description = it.plainTextDescription.substring( + 0, 100.coerceAtMost(it.plainTextDescription.length) + ) ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainSlideResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainSlideResponse.kt index 483723d9..6696f0ed 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainSlideResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainSlideResponse.kt @@ -7,7 +7,8 @@ data class MainSlideResponse @QueryProjection constructor( val id: Long, val title: String, val imageURL: String?, - val createdAt: LocalDateTime? + val createdAt: LocalDateTime?, + val description: String, ) { } \ No newline at end of file From bb4225acb60108ff999563961f2be379d8472341 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Tue, 19 Sep 2023 23:40:18 +0900 Subject: [PATCH 110/124] style: ktlint (#145) * style: ktlint * Feat: change to ktlint_disabled_rules * Refactor: fix to lint using ktlint. * Refactor: After merge. * Fix: After merge. * Refactor: ReReLint --------- Co-authored-by: huGgW --- .editorconfig | 11 + .github/workflows/ktlint-check.yml | 21 ++ build.gradle.kts | 6 +- .../csereal/CserealApplication.kt | 2 +- .../{Exceptions.kt => CserealException.kt} | 0 .../csereal/common/aop/SecurityAspect.kt | 1 - .../csereal/common/config/BaseTimeEntity.kt | 2 +- .../common/config/CserealExceptionHandler.kt | 1 - .../CustomAuthenticationSuccessHandler.kt | 3 +- .../csereal/common/config/JacksonConfig.kt | 1 - .../csereal/common/config/JwtConfig.kt | 1 - .../common/config/MySQLDialectCustom.kt | 55 ++- .../csereal/common/config/OpenApiConfig.kt | 10 +- .../csereal/common/config/QueryDslConfig.kt | 4 +- .../common/config/RestTemplateConfig.kt | 1 - .../csereal/common/config/SecurityConfig.kt | 2 - .../csereal/common/config/WebConfig.kt | 1 - .../controller/AttachmentContentEntityType.kt | 2 +- .../common/controller/CommonController.kt | 2 +- .../common/controller/ContentEntityType.kt | 2 +- .../controller/MainImageContentEntityType.kt | 2 +- .../common/properties/EndpointProperties.kt | 1 - .../common/repository/CommonRepository.kt | 265 +++++++++------ .../csereal/common/utils/FixedPageRequest.kt | 1 - .../csereal/common/utils/Utils.kt | 4 +- .../csereal/core/about/api/AboutController.kt | 8 +- .../core/about/database/AboutEntity.kt | 8 +- .../core/about/database/AboutPostType.kt | 2 +- .../core/about/database/AboutRepository.kt | 2 +- .../core/about/database/CompanyEntity.kt | 6 +- .../core/about/database/CompanyRepository.kt | 4 +- .../core/about/database/LocationEntity.kt | 2 +- .../csereal/core/about/database/StatEntity.kt | 9 +- .../core/about/database/StatRepository.kt | 4 +- .../csereal/core/about/dto/AboutDto.kt | 13 +- .../csereal/core/about/dto/AboutRequest.kt | 5 +- .../csereal/core/about/dto/DirectionDto.kt | 4 +- .../csereal/core/about/dto/FacilityDto.kt | 4 +- .../core/about/dto/FutureCareersCompanyDto.kt | 2 +- .../core/about/dto/FutureCareersPage.kt | 3 +- .../core/about/dto/FutureCareersRequest.kt | 6 +- .../about/dto/FutureCareersStatDegreeDto.kt | 6 +- .../core/about/dto/FutureCareersStatDto.kt | 3 +- .../csereal/core/about/dto/StudentClubDto.kt | 4 +- .../core/about/service/AboutService.kt | 33 +- .../core/academics/api/AcademicsController.kt | 18 +- .../academics/database/AcademicsEntity.kt | 15 +- .../academics/database/AcademicsPostType.kt | 5 +- .../academics/database/AcademicsRepository.kt | 15 +- .../database/AcademicsStudentType.kt | 2 +- .../core/academics/database/CourseEntity.kt | 11 +- .../academics/database/CourseRepository.kt | 6 +- .../academics/database/ScholarshipEntity.kt | 8 +- .../database/ScholarshipRepository.kt | 1 - .../core/academics/dto/AcademicsDto.kt | 8 +- .../academics/dto/AcademicsYearResponse.kt | 2 +- .../csereal/core/academics/dto/CourseDto.kt | 6 +- .../dto/GeneralStudiesPageResponse.kt | 4 +- .../core/academics/dto/GuidePageResponse.kt | 2 +- .../core/academics/dto/SubjectChangesDto.kt | 4 +- .../academics/service/AcademicsService.kt | 38 ++- .../csereal/core/admin/api/AdminController.kt | 2 - .../core/admin/database/AdminRepository.kt | 9 +- .../csereal/core/admin/dto/ImportantDto.kt | 5 +- .../core/admin/dto/ImportantRequest.kt | 3 +- .../core/admin/dto/ImportantResponse.kt | 5 +- .../core/admin/dto/NewsIdListRequest.kt | 4 +- .../csereal/core/admin/dto/SlideResponse.kt | 5 +- .../core/admin/service/AdminService.kt | 9 +- .../admissions/api/AdmissionsController.kt | 9 +- .../admissions/database/AdmissionsEntity.kt | 10 +- .../admissions/database/AdmissionsPostType.kt | 2 +- .../database/AdmissionsRepository.kt | 4 +- .../core/admissions/dto/AdmissionsDto.kt | 8 +- .../admissions/service/AdmissionsService.kt | 18 +- .../conference/api/ConferenceController.kt | 8 +- .../conference/database/ConferenceEntity.kt | 30 +- .../database/ConferencePageRepository.kt | 3 +- .../database/ConferenceRepository.kt | 2 +- .../conference/dto/ConferenceCreateDto.kt | 7 +- .../core/conference/dto/ConferenceDto.kt | 4 +- .../conference/dto/ConferenceModifyRequest.kt | 6 +- .../core/conference/dto/ConferencePage.kt | 4 +- .../conference/service/ConferenceService.kt | 11 +- .../csereal/core/main/api/MainController.kt | 6 +- .../core/main/database/MainRepository.kt | 11 +- .../core/main/dto/MainImportantResponse.kt | 5 +- .../core/main/dto/MainNoticeResponse.kt | 9 +- .../csereal/core/main/dto/MainResponse.kt | 5 +- .../core/main/dto/MainSlideResponse.kt | 6 +- .../csereal/core/main/dto/NoticesResponse.kt | 3 +- .../csereal/core/main/service/MainService.kt | 5 +- .../core/member/api/MemberSearchController.kt | 17 +- .../core/member/api/ProfessorController.kt | 4 +- .../core/member/api/StaffController.kt | 4 +- .../member/database/MemberSearchEntity.kt | 37 +- .../member/database/MemberSearchRepository.kt | 63 ++-- .../core/member/database/ProfessorEntity.kt | 10 +- .../core/member/database/StaffEntity.kt | 4 +- .../core/member/database/StaffRepository.kt | 3 +- .../member/dto/MemberSearchPageResponse.kt | 16 +- .../member/dto/MemberSearchResponseElement.kt | 56 +-- .../member/dto/MemberSearchTopResponse.kt | 14 +- .../csereal/core/member/dto/ProfessorDto.kt | 3 +- .../core/member/dto/SimpleProfessorDto.kt | 1 - .../csereal/core/member/dto/StaffDto.kt | 1 - .../member/service/MemberSearchService.kt | 11 +- .../core/member/service/ProfessorService.kt | 33 +- .../core/member/service/StaffService.kt | 12 +- .../csereal/core/news/api/NewsController.kt | 23 +- .../csereal/core/news/database/NewsEntity.kt | 2 +- .../core/news/database/NewsRepository.kt | 26 +- .../core/news/database/NewsTagEntity.kt | 6 +- .../core/news/database/NewsTagRepository.kt | 3 +- .../core/news/database/TagInNewsEntity.kt | 3 +- .../core/news/database/TagInNewsRepository.kt | 2 +- .../csereal/core/news/dto/NewsDto.kt | 2 +- .../csereal/core/news/dto/NewsSearchDto.kt | 2 +- .../core/news/dto/NewsSearchResponse.kt | 4 +- .../core/news/dto/NewsTotalSearchDto.kt | 8 +- .../core/news/dto/NewsTotalSearchElement.kt | 26 +- .../csereal/core/news/service/NewsService.kt | 16 +- .../core/notice/api/NoticeController.kt | 19 +- .../core/notice/database/NoticeEntity.kt | 6 +- .../core/notice/database/NoticeRepository.kt | 12 +- .../core/notice/database/NoticeTagEntity.kt | 8 +- .../notice/database/NoticeTagRepository.kt | 2 +- .../core/notice/database/TagInNoticeEntity.kt | 3 +- .../core/notice/database/TagInNoticeEnum.kt | 5 - .../notice/database/TagInNoticeRepository.kt | 2 +- .../csereal/core/notice/dto/NoticeDto.kt | 5 +- .../core/notice/dto/NoticeIdListRequest.kt | 4 +- .../core/notice/dto/NoticeSearchDto.kt | 3 +- .../core/notice/dto/NoticeSearchResponse.kt | 4 +- .../notice/dto/NoticeTotalSearchElement.kt | 20 +- .../notice/dto/NoticeTotalSearchResponse.kt | 8 +- .../core/notice/service/NoticeService.kt | 17 +- .../core/recruit/api/RecruitController.kt | 1 - .../recruit/database/RecruitRepository.kt | 3 +- .../csereal/core/recruit/dto/RecruitPage.kt | 1 - .../core/research/api/ResearchController.kt | 23 +- .../core/research/database/LabEntity.kt | 7 +- .../core/research/database/ResearchEntity.kt | 9 +- .../research/database/ResearchPostType.kt | 6 +- .../research/database/ResearchRepository.kt | 2 +- .../research/database/ResearchSearchEntity.kt | 185 +++++----- .../database/ResearchSearchRepository.kt | 13 +- .../csereal/core/research/dto/LabDto.kt | 6 +- .../core/research/dto/LabProfessorResponse.kt | 5 +- .../core/research/dto/LabUpdateRequest.kt | 18 +- .../csereal/core/research/dto/ResearchDto.kt | 4 +- .../research/dto/ResearchGroupResponse.kt | 3 +- .../core/research/dto/ResearchLabResponse.kt | 5 +- .../research/service/ResearchSearchService.kt | 8 +- .../core/research/service/ResearchService.kt | 15 +- ...Controller.kt => ReservationController.kt} | 9 +- .../reservation/database/ReservationEntity.kt | 1 - .../database/ReservationRepository.kt | 11 +- .../reservation/database/RoomRepository.kt | 3 +- .../core/reservation/dto/ReservationDto.kt | 1 - .../reservation/service/ReservationService.kt | 2 - .../attachment/database/AttachmentEntity.kt | 8 +- .../database/AttachmentRepository.kt | 4 +- .../resource/attachment/dto/AttachmentDto.kt | 5 +- .../attachment/dto/AttachmentResponse.kt | 5 +- .../attachment/service/AttachmentService.kt | 17 +- .../common/api/DeprecatedFileController.kt | 5 +- .../resource/common/api/FileController.kt | 4 +- .../mainImage/database/MainImageEntity.kt | 9 +- .../mainImage/database/MainImageRepository.kt | 3 +- .../resource/mainImage/dto/MainImageDto.kt | 5 +- .../mainImage/service/MainImageService.kt | 14 +- .../core/seminar/api/SeminarController.kt | 13 +- .../core/seminar/database/SeminarEntity.kt | 6 +- .../seminar/database/SeminarRepository.kt | 6 +- .../csereal/core/seminar/dto/SeminarDto.kt | 5 +- .../core/seminar/dto/SeminarSearchDto.kt | 3 +- .../core/seminar/dto/SeminarSearchResponse.kt | 3 +- .../core/seminar/service/SeminarService.kt | 6 +- .../csereal/core/user/database/UserEntity.kt | 4 +- .../user/service/CustomOidcUserService.kt | 5 +- .../csereal/CserealApplicationTests.kt | 7 +- .../csereal/common/util/UtilsTest.kt | 4 +- .../service/ConferenceServiceTest.kt | 95 +++--- .../member/service/ProfessorServiceTest.kt | 186 +++++----- .../core/member/service/StaffServiceTest.kt | 46 +-- .../csereal/core/news/NewsServiceTest.kt | 24 +- .../core/notice/service/NoticeServiceTest.kt | 22 +- .../reseach/service/ResearchServiceTest.kt | 319 +++++++++--------- .../seminar/service/SeminarServiceTest.kt | 53 +-- .../csereal/global/config/TestConfig.kt | 8 +- 191 files changed, 1339 insertions(+), 1318 deletions(-) create mode 100644 .editorconfig create mode 100644 .github/workflows/ktlint-check.yml rename src/main/kotlin/com/wafflestudio/csereal/common/{Exceptions.kt => CserealException.kt} (100%) rename src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/{ReservceController.kt => ReservationController.kt} (94%) diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..93ca37ce --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 4 +insert_final_newline = true +max_line_length = 120 +tab_width = 4 +ktlint_disabled_rules = no-wildcard-imports, import-ordering, comment-spacing diff --git a/.github/workflows/ktlint-check.yml b/.github/workflows/ktlint-check.yml new file mode 100644 index 00000000..e9c22ae6 --- /dev/null +++ b/.github/workflows/ktlint-check.yml @@ -0,0 +1,21 @@ +name: Ktlint + +on: + pull_request: + branches: [ main ] + +jobs: + ktlint: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: ktlintCheck with Gradle + run: ./gradlew ktlintCheck diff --git a/build.gradle.kts b/build.gradle.kts index 3948e7d1..ba4bcd26 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,6 +7,7 @@ plugins { kotlin("plugin.spring") version "1.7.22" kotlin("plugin.jpa") version "1.7.22" kotlin("kapt") version "1.7.10" + id("org.jlleitschuh.gradle.ktlint") version "11.6.0" } group = "com.wafflestudio" @@ -64,7 +65,6 @@ dependencies { // Custom Metadata annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") - } noArg { annotation("jakarta.persistence.Entity") @@ -78,6 +78,10 @@ allOpen { annotation("jakarta.persistence.Embeddable") } +apply { + plugin("org.jlleitschuh.gradle.ktlint") +} + tasks.withType { kotlinOptions { freeCompilerArgs += "-Xjsr305=strict" diff --git a/src/main/kotlin/com/wafflestudio/csereal/CserealApplication.kt b/src/main/kotlin/com/wafflestudio/csereal/CserealApplication.kt index 648cca3c..9d3fbbb7 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/CserealApplication.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/CserealApplication.kt @@ -9,5 +9,5 @@ import org.springframework.data.jpa.repository.config.EnableJpaAuditing class CserealApplication fun main(args: Array) { - runApplication(*args) + runApplication(*args) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/Exceptions.kt b/src/main/kotlin/com/wafflestudio/csereal/common/CserealException.kt similarity index 100% rename from src/main/kotlin/com/wafflestudio/csereal/common/Exceptions.kt rename to src/main/kotlin/com/wafflestudio/csereal/common/CserealException.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/aop/SecurityAspect.kt b/src/main/kotlin/com/wafflestudio/csereal/common/aop/SecurityAspect.kt index 99dfe6e2..9eb21d03 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/aop/SecurityAspect.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/aop/SecurityAspect.kt @@ -49,5 +49,4 @@ class SecurityAspect(private val userRepository: UserRepository) { return user } - } diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/BaseTimeEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/BaseTimeEntity.kt index ecd53933..30bf048e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/BaseTimeEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/BaseTimeEntity.kt @@ -20,4 +20,4 @@ abstract class BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long = 0L -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/CserealExceptionHandler.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/CserealExceptionHandler.kt index c93f7b80..d6375c10 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/CserealExceptionHandler.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/CserealExceptionHandler.kt @@ -7,7 +7,6 @@ import org.springframework.validation.BindingResult import org.springframework.web.bind.MethodArgumentNotValidException import org.springframework.web.bind.annotation.ExceptionHandler import org.springframework.web.bind.annotation.RestControllerAdvice -import org.springframework.web.client.ResourceAccessException import org.springframework.web.client.RestClientException import java.sql.SQLIntegrityConstraintViolationException diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/CustomAuthenticationSuccessHandler.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/CustomAuthenticationSuccessHandler.kt index 52d1d027..cc77997b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/CustomAuthenticationSuccessHandler.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/CustomAuthenticationSuccessHandler.kt @@ -13,8 +13,7 @@ class CustomAuthenticationSuccessHandler( response: HttpServletResponse, authentication: Authentication ) { - val redirectUrl = "${frontendEndpoint}/login/success" + val redirectUrl = "$frontendEndpoint/login/success" response.sendRedirect(redirectUrl) } - } diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/JacksonConfig.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/JacksonConfig.kt index 5c32ca85..2c828065 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/JacksonConfig.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/JacksonConfig.kt @@ -6,7 +6,6 @@ import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.SerializerProvider import com.fasterxml.jackson.databind.module.SimpleModule import org.springframework.boot.context.event.ApplicationReadyEvent -import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.context.event.EventListener import java.time.LocalDateTime diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/JwtConfig.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/JwtConfig.kt index 76af30f3..e32e4919 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/JwtConfig.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/JwtConfig.kt @@ -16,5 +16,4 @@ class JwtConfig { idTokenDecoderFactory.setJwsAlgorithmResolver { SignatureAlgorithm.ES256 } return idTokenDecoderFactory } - } diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/MySQLDialectCustom.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/MySQLDialectCustom.kt index 337c1bbb..2214526a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/MySQLDialectCustom.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/MySQLDialectCustom.kt @@ -5,9 +5,8 @@ import org.hibernate.dialect.MySQLDialect import org.hibernate.query.spi.QueryEngine import org.hibernate.type.StandardBasicTypes - -class MySQLDialectCustom: MySQLDialect( - DatabaseVersion.make(8) +class MySQLDialectCustom : MySQLDialect( + DatabaseVersion.make(8) ) { override fun initializeFunctionRegistry(queryEngine: QueryEngine?) { super.initializeFunctionRegistry(queryEngine) @@ -17,52 +16,52 @@ class MySQLDialectCustom: MySQLDialect( if (basicTypeRegistry != null && functionRegistry != null) { functionRegistry.registerPattern( - "match", - "match (?1) against (?2 in boolean mode)", - basicTypeRegistry.resolve(StandardBasicTypes.DOUBLE) + "match", + "match (?1) against (?2 in boolean mode)", + basicTypeRegistry.resolve(StandardBasicTypes.DOUBLE) ) functionRegistry.registerPattern( - "match2", - "match (?1, ?2) against (?3 in boolean mode)", - basicTypeRegistry.resolve(StandardBasicTypes.DOUBLE) + "match2", + "match (?1, ?2) against (?3 in boolean mode)", + basicTypeRegistry.resolve(StandardBasicTypes.DOUBLE) ) functionRegistry.registerPattern( - "match3", - "match (?1, ?2, ?3) against (?4 in boolean mode)", - basicTypeRegistry.resolve(StandardBasicTypes.DOUBLE) + "match3", + "match (?1, ?2, ?3) against (?4 in boolean mode)", + basicTypeRegistry.resolve(StandardBasicTypes.DOUBLE) ) functionRegistry.registerPattern( - "match4", - "match (?1, ?2, ?3, ?4) against (?5 in boolean mode)", - basicTypeRegistry.resolve(StandardBasicTypes.DOUBLE) + "match4", + "match (?1, ?2, ?3, ?4) against (?5 in boolean mode)", + basicTypeRegistry.resolve(StandardBasicTypes.DOUBLE) ) functionRegistry.registerPattern( - "match5", - "match (?1, ?2, ?3, ?4, ?5) against (?6 in boolean mode)", - basicTypeRegistry.resolve(StandardBasicTypes.DOUBLE) + "match5", + "match (?1, ?2, ?3, ?4, ?5) against (?6 in boolean mode)", + basicTypeRegistry.resolve(StandardBasicTypes.DOUBLE) ) functionRegistry.registerPattern( - "match6", - "match (?1, ?2, ?3, ?4, ?5, ?6) against (?7 in boolean mode)", - basicTypeRegistry.resolve(StandardBasicTypes.DOUBLE) + "match6", + "match (?1, ?2, ?3, ?4, ?5, ?6) against (?7 in boolean mode)", + basicTypeRegistry.resolve(StandardBasicTypes.DOUBLE) ) functionRegistry.registerPattern( - "match7", - "match (?1, ?2, ?3, ?4, ?5, ?6, ?7) against (?8 in boolean mode)", - basicTypeRegistry.resolve(StandardBasicTypes.DOUBLE) + "match7", + "match (?1, ?2, ?3, ?4, ?5, ?6, ?7) against (?8 in boolean mode)", + basicTypeRegistry.resolve(StandardBasicTypes.DOUBLE) ) functionRegistry.registerPattern( - "match8", - "match (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8) against (?9 in boolean mode)", - basicTypeRegistry.resolve(StandardBasicTypes.DOUBLE) + "match8", + "match (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8) against (?9 in boolean mode)", + basicTypeRegistry.resolve(StandardBasicTypes.DOUBLE) ) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/OpenApiConfig.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/OpenApiConfig.kt index d78341ae..68a93b0b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/OpenApiConfig.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/OpenApiConfig.kt @@ -11,11 +11,11 @@ class OpenApiConfig { @Bean fun openAPI(): OpenAPI { val info = Info() - .title("컴퓨터공학부 홈페이지 백엔드 API") - .description("컴퓨터공학부 홈페이지 백엔드 API 명세서입니다.") + .title("컴퓨터공학부 홈페이지 백엔드 API") + .description("컴퓨터공학부 홈페이지 백엔드 API 명세서입니다.") return OpenAPI() - .components(Components()) - .info(info) + .components(Components()) + .info(info) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/QueryDslConfig.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/QueryDslConfig.kt index ff32458d..84a57e6d 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/QueryDslConfig.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/QueryDslConfig.kt @@ -9,9 +9,9 @@ import org.springframework.context.annotation.Configuration @Configuration class QueryDslConfig( @PersistenceContext - val entityManager: EntityManager, + val entityManager: EntityManager ) { @Bean fun jpaQueryFactory(): JPAQueryFactory = JPAQueryFactory(entityManager) -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/RestTemplateConfig.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/RestTemplateConfig.kt index e7497867..94b5517c 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/RestTemplateConfig.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/RestTemplateConfig.kt @@ -11,5 +11,4 @@ class RestTemplateConfig { fun restTemplate(): RestTemplate { return RestTemplate() } - } diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt index 31c7b601..6f5c7ff1 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt @@ -17,7 +17,6 @@ import org.springframework.web.cors.CorsConfiguration import org.springframework.web.cors.CorsConfigurationSource import org.springframework.web.cors.UrlBasedCorsConfigurationSource - @Configuration @EnableWebSecurity @EnableConfigurationProperties(EndpointProperties::class) @@ -75,5 +74,4 @@ class SecurityConfig( source.registerCorsConfiguration("/**", configuration) return source } - } diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/config/WebConfig.kt b/src/main/kotlin/com/wafflestudio/csereal/common/config/WebConfig.kt index 02795670..315b1d6a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/WebConfig.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/WebConfig.kt @@ -14,5 +14,4 @@ class WebConfig : WebMvcConfigurer { .allowedHeaders("*") .maxAge(3000) } - } diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/controller/AttachmentContentEntityType.kt b/src/main/kotlin/com/wafflestudio/csereal/common/controller/AttachmentContentEntityType.kt index a2eae43a..6972e8e7 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/controller/AttachmentContentEntityType.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/controller/AttachmentContentEntityType.kt @@ -4,4 +4,4 @@ import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEnti interface AttachmentContentEntityType { fun bringAttachments(): List -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/controller/CommonController.kt b/src/main/kotlin/com/wafflestudio/csereal/common/controller/CommonController.kt index 62eb3a9f..7d8652fa 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/controller/CommonController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/controller/CommonController.kt @@ -9,4 +9,4 @@ class CommonController { fun helloWorld(): String { return "Hello, world!" } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/controller/ContentEntityType.kt b/src/main/kotlin/com/wafflestudio/csereal/common/controller/ContentEntityType.kt index 0631bd6e..cd448be5 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/controller/ContentEntityType.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/controller/ContentEntityType.kt @@ -4,4 +4,4 @@ import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity interface ContentEntityType { fun bringMainImage(): MainImageEntity? -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/controller/MainImageContentEntityType.kt b/src/main/kotlin/com/wafflestudio/csereal/common/controller/MainImageContentEntityType.kt index eae7403c..0923842a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/controller/MainImageContentEntityType.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/controller/MainImageContentEntityType.kt @@ -4,4 +4,4 @@ import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity interface MainImageContentEntityType { fun bringMainImage(): MainImageEntity? -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/properties/EndpointProperties.kt b/src/main/kotlin/com/wafflestudio/csereal/common/properties/EndpointProperties.kt index 471c0270..2567f046 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/properties/EndpointProperties.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/properties/EndpointProperties.kt @@ -2,7 +2,6 @@ package com.wafflestudio.csereal.common.properties import org.springframework.boot.context.properties.ConfigurationProperties - @ConfigurationProperties("endpoint") data class EndpointProperties( val frontend: String, diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/repository/CommonRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/common/repository/CommonRepository.kt index d8c8e9ac..41522e8f 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/repository/CommonRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/repository/CommonRepository.kt @@ -8,148 +8,189 @@ interface CommonRepository { fun searchFullSingleTextTemplate(keyword: String, field: Any): NumberTemplate fun searchFullDoubleTextTemplate(keyword: String, field1: Any, field2: Any): NumberTemplate fun searchFullTripleTextTemplate(keyword: String, field1: Any, field2: Any, field3: Any): NumberTemplate - fun searchFullQuadrapleTextTemplate(keyword: String, field1: Any, field2: Any, field3: Any, field4: Any): NumberTemplate - fun searchFullQuintupleTextTemplate(keyword: String, field1: Any, field2: Any, field3: Any, field4: Any, field5: Any): NumberTemplate - fun searchFullSextupleTextTemplate(keyword: String, field1: Any, field2: Any, field3: Any, field4: Any, field5: Any, field6: Any): NumberTemplate - fun searchFullSeptupleTextTemplate(keyword: String, field1: Any, field2: Any, field3: Any, field4: Any, field5: Any, field6: Any, field7: Any): NumberTemplate - fun searchFullOctupleTextTemplate(keyword: String, field1: Any, field2: Any, field3: Any, field4: Any, field5: Any, field6: Any, field7: Any, field8: Any): NumberTemplate + fun searchFullQuadrapleTextTemplate( + keyword: String, + field1: Any, + field2: Any, + field3: Any, + field4: Any + ): NumberTemplate + + fun searchFullQuintupleTextTemplate( + keyword: String, + field1: Any, + field2: Any, + field3: Any, + field4: Any, + field5: Any + ): NumberTemplate + fun searchFullSextupleTextTemplate( + keyword: String, + field1: Any, + field2: Any, + field3: Any, + field4: Any, + field5: Any, + field6: Any + ): NumberTemplate + fun searchFullSeptupleTextTemplate( + keyword: String, + field1: Any, + field2: Any, + field3: Any, + field4: Any, + field5: Any, + field6: Any, + field7: Any + ): NumberTemplate + fun searchFullOctupleTextTemplate( + keyword: String, + field1: Any, + field2: Any, + field3: Any, + field4: Any, + field5: Any, + field6: Any, + field7: Any, + field8: Any + ): NumberTemplate } @Repository -class CommonRepositoryImpl: CommonRepository { +class CommonRepositoryImpl : CommonRepository { override fun searchFullSingleTextTemplate( - keyword: String, - field: Any, + keyword: String, + field: Any ) = Expressions.numberTemplate( - Double::class.javaObjectType, - "function('match',{0},{1})", - field, - keyword - ) + Double::class.javaObjectType, + "function('match',{0},{1})", + field, + keyword + ) override fun searchFullDoubleTextTemplate( - keyword: String, - field1: Any, - field2: Any, + keyword: String, + field1: Any, + field2: Any ) = Expressions.numberTemplate( - Double::class.javaObjectType, - "function('match2',{0},{1},{2})", - field1, - field2, - keyword, + Double::class.javaObjectType, + "function('match2',{0},{1},{2})", + field1, + field2, + keyword ) override fun searchFullTripleTextTemplate( - keyword: String, - field1: Any, - field2: Any, - field3: Any, + keyword: String, + field1: Any, + field2: Any, + field3: Any ) = Expressions.numberTemplate( - Double::class.javaObjectType, - "function('match3',{0},{1},{2},{3})", - field1, - field2, - field3, - keyword + Double::class.javaObjectType, + "function('match3',{0},{1},{2},{3})", + field1, + field2, + field3, + keyword ) override fun searchFullQuadrapleTextTemplate( - keyword: String, - field1: Any, - field2: Any, - field3: Any, - field4: Any, + keyword: String, + field1: Any, + field2: Any, + field3: Any, + field4: Any ) = Expressions.numberTemplate( - Double::class.javaObjectType, - "function('match4',{0},{1},{2},{3},{4})", - field1, - field2, - field3, - field4, - keyword + Double::class.javaObjectType, + "function('match4',{0},{1},{2},{3},{4})", + field1, + field2, + field3, + field4, + keyword ) override fun searchFullQuintupleTextTemplate( - keyword: String, - field1: Any, - field2: Any, - field3: Any, - field4: Any, - field5: Any, + keyword: String, + field1: Any, + field2: Any, + field3: Any, + field4: Any, + field5: Any ) = Expressions.numberTemplate( - Double::class.javaObjectType, - "function('match5',{0},{1},{2},{3},{4},{5})", - field1, - field2, - field3, - field4, - field5, - keyword + Double::class.javaObjectType, + "function('match5',{0},{1},{2},{3},{4},{5})", + field1, + field2, + field3, + field4, + field5, + keyword ) override fun searchFullSextupleTextTemplate( - keyword: String, - field1: Any, - field2: Any, - field3: Any, - field4: Any, - field5: Any, - field6: Any, + keyword: String, + field1: Any, + field2: Any, + field3: Any, + field4: Any, + field5: Any, + field6: Any ) = Expressions.numberTemplate( - Double::class.javaObjectType, - "function('match6',{0},{1},{2},{3},{4},{5},{6})", - field1, - field2, - field3, - field4, - field5, - field6, - keyword + Double::class.javaObjectType, + "function('match6',{0},{1},{2},{3},{4},{5},{6})", + field1, + field2, + field3, + field4, + field5, + field6, + keyword ) override fun searchFullSeptupleTextTemplate( - keyword: String, - field1: Any, - field2: Any, - field3: Any, - field4: Any, - field5: Any, - field6: Any, - field7: Any, + keyword: String, + field1: Any, + field2: Any, + field3: Any, + field4: Any, + field5: Any, + field6: Any, + field7: Any ) = Expressions.numberTemplate( - Double::class.javaObjectType, - "function('match7',{0},{1},{2},{3},{4},{5},{6},{7})", - field1, - field2, - field3, - field4, - field5, - field6, - field7, - keyword + Double::class.javaObjectType, + "function('match7',{0},{1},{2},{3},{4},{5},{6},{7})", + field1, + field2, + field3, + field4, + field5, + field6, + field7, + keyword ) override fun searchFullOctupleTextTemplate( - keyword: String, - field1: Any, - field2: Any, - field3: Any, - field4: Any, - field5: Any, - field6: Any, - field7: Any, - field8: Any, + keyword: String, + field1: Any, + field2: Any, + field3: Any, + field4: Any, + field5: Any, + field6: Any, + field7: Any, + field8: Any ) = Expressions.numberTemplate( - Double::class.javaObjectType, - "function('match8',{0},{1},{2},{3},{4},{5},{6},{7},{8})", - field1, - field2, - field3, - field4, - field5, - field6, - field7, - field8, - keyword + Double::class.javaObjectType, + "function('match8',{0},{1},{2},{3},{4},{5},{6},{7},{8})", + field1, + field2, + field3, + field4, + field5, + field6, + field7, + field8, + keyword ) -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/utils/FixedPageRequest.kt b/src/main/kotlin/com/wafflestudio/csereal/common/utils/FixedPageRequest.kt index 1005949e..50f38d33 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/utils/FixedPageRequest.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/utils/FixedPageRequest.kt @@ -20,5 +20,4 @@ class FixedPageRequest(pageable: Pageable, total: Long) : return floor(total.toDouble() / pageSize).toInt() } } - } diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/utils/Utils.kt b/src/main/kotlin/com/wafflestudio/csereal/common/utils/Utils.kt index bccc8655..06f32bea 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/utils/Utils.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/utils/Utils.kt @@ -3,8 +3,6 @@ package com.wafflestudio.csereal.common.utils import org.jsoup.Jsoup import org.jsoup.parser.Parser import org.jsoup.safety.Safelist -import kotlin.math.max -import kotlin.math.min fun cleanTextFromHtml(description: String): String { val cleanDescription = Jsoup.clean(description, Safelist.none()) @@ -14,7 +12,7 @@ fun cleanTextFromHtml(description: String): String { fun substringAroundKeyword(keyword: String, content: String, amount: Int): Pair { val index = content.lowercase().indexOf(keyword.lowercase()) return if (index == -1) { - null to content.substring(0, amount.coerceAtMost(content.length)) + null to content.substring(0, amount.coerceAtMost(content.length)) } else { var frontIndex = (index - amount / 2 + keyword.length).coerceAtLeast(0) var backIndex = (index + amount / 2 + keyword.length).coerceAtMost(content.length) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt index ba54b62e..c028b641 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt @@ -24,9 +24,11 @@ class AboutController( @PostMapping("/{postType}") fun createAbout( @PathVariable postType: String, - @Valid @RequestPart("request") request: AboutDto, + @Valid + @RequestPart("request") + request: AboutDto, @RequestPart("mainImage") mainImage: MultipartFile?, - @RequestPart("attachments") attachments: List?, + @RequestPart("attachments") attachments: List? ): ResponseEntity { return ResponseEntity.ok(aboutService.createAbout(postType, request, mainImage, attachments)) } @@ -34,7 +36,7 @@ class AboutController( // read 목록이 하나 @GetMapping("/{postType}") fun readAbout( - @PathVariable postType: String, + @PathVariable postType: String ): ResponseEntity { return ResponseEntity.ok(aboutService.readAbout(postType)) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt index c9854e36..dbe06392 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt @@ -27,9 +27,9 @@ class AboutEntity( var attachments: MutableList = mutableListOf(), @OneToOne - var mainImage: MainImageEntity? = null, + var mainImage: MainImageEntity? = null - ) : BaseTimeEntity(), MainImageContentEntityType, AttachmentContentEntityType { +) : BaseTimeEntity(), MainImageContentEntityType, AttachmentContentEntityType { override fun bringMainImage(): MainImageEntity? = mainImage override fun bringAttachments(): List = attachments @@ -40,8 +40,8 @@ class AboutEntity( name = aboutDto.name, engName = aboutDto.engName, description = aboutDto.description, - year = aboutDto.year, + year = aboutDto.year ) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutPostType.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutPostType.kt index 9dec0a30..61dc72b7 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutPostType.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutPostType.kt @@ -2,4 +2,4 @@ package com.wafflestudio.csereal.core.about.database enum class AboutPostType { OVERVIEW, GREETINGS, HISTORY, FUTURE_CAREERS, CONTACT, STUDENT_CLUBS, FACILITIES, DIRECTIONS, -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutRepository.kt index a66fe46b..85d5331a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutRepository.kt @@ -5,4 +5,4 @@ import org.springframework.data.jpa.repository.JpaRepository interface AboutRepository : JpaRepository { fun findAllByPostTypeOrderByName(postType: AboutPostType): List fun findByPostType(postType: AboutPostType): AboutEntity -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/CompanyEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/CompanyEntity.kt index bae4b107..4d9691f6 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/CompanyEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/CompanyEntity.kt @@ -8,15 +8,15 @@ import jakarta.persistence.Entity class CompanyEntity( var name: String, var url: String?, - var year: Int?, + var year: Int? ) : BaseTimeEntity() { companion object { fun of(companyDto: FutureCareersCompanyDto): CompanyEntity { return CompanyEntity( name = companyDto.name, url = companyDto.url, - year = companyDto.year, + year = companyDto.year ) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/CompanyRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/CompanyRepository.kt index a408c8a8..321605c6 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/CompanyRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/CompanyRepository.kt @@ -2,6 +2,6 @@ package com.wafflestudio.csereal.core.about.database import org.springframework.data.jpa.repository.JpaRepository -interface CompanyRepository: JpaRepository { +interface CompanyRepository : JpaRepository { fun findAllByOrderByYearDesc(): List -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/LocationEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/LocationEntity.kt index 3e3ad0de..38929681 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/LocationEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/LocationEntity.kt @@ -24,4 +24,4 @@ class LocationEntity( return locationEntity } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/StatEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/StatEntity.kt index 326d8a1a..53a711f2 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/StatEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/StatEntity.kt @@ -2,7 +2,6 @@ package com.wafflestudio.csereal.core.about.database import com.wafflestudio.csereal.common.config.BaseTimeEntity import com.wafflestudio.csereal.core.about.dto.FutureCareersStatDegreeDto -import com.wafflestudio.csereal.core.about.dto.FutureCareersStatDto import jakarta.persistence.Entity import jakarta.persistence.EnumType import jakarta.persistence.Enumerated @@ -14,15 +13,15 @@ class StatEntity( @Enumerated(EnumType.STRING) var degree: Degree, var name: String, - var count: Int, -): BaseTimeEntity() { + var count: Int +) : BaseTimeEntity() { companion object { fun of(year: Int, degree: Degree, statDto: FutureCareersStatDegreeDto): StatEntity { return StatEntity( year = year, degree = degree, name = statDto.name, - count = statDto.count, + count = statDto.count ) } } @@ -30,4 +29,4 @@ class StatEntity( enum class Degree { BACHELOR, MASTER, DOCTOR -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/StatRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/StatRepository.kt index a34d825d..080d0c1e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/StatRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/StatRepository.kt @@ -2,6 +2,6 @@ package com.wafflestudio.csereal.core.about.database import org.springframework.data.jpa.repository.JpaRepository -interface StatRepository: JpaRepository { +interface StatRepository : JpaRepository { fun findAllByYearAndDegree(year: Int, degree: Degree): List -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutDto.kt index af37473f..72aa0bde 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutDto.kt @@ -1,6 +1,5 @@ package com.wafflestudio.csereal.core.about.dto - import com.fasterxml.jackson.annotation.JsonInclude import com.wafflestudio.csereal.core.about.database.AboutEntity import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse @@ -17,10 +16,14 @@ data class AboutDto( val modifiedAt: LocalDateTime?, val locations: List?, val imageURL: String?, - val attachments: List?, + val attachments: List? ) { companion object { - fun of(entity: AboutEntity, imageURL: String?, attachmentResponses: List) : AboutDto = entity.run { + fun of( + entity: AboutEntity, + imageURL: String?, + attachmentResponses: List + ): AboutDto = entity.run { AboutDto( id = this.id, name = this.name, @@ -31,8 +34,8 @@ data class AboutDto( modifiedAt = this.modifiedAt, locations = this.locations.map { it.name }, imageURL = imageURL, - attachments = attachmentResponses, + attachments = attachmentResponses ) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutRequest.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutRequest.kt index c720c90c..80aa8c28 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutRequest.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutRequest.kt @@ -2,6 +2,5 @@ package com.wafflestudio.csereal.core.about.dto data class AboutRequest( val postType: String, - val description: String, -) { -} \ No newline at end of file + val description: String +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/DirectionDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/DirectionDto.kt index df866def..3507c5df 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/DirectionDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/DirectionDto.kt @@ -8,7 +8,7 @@ data class DirectionDto( val id: Long? = null, val name: String, val engName: String, - val description: String, + val description: String ) { companion object { fun of(entity: AboutEntity): DirectionDto = entity.run { @@ -20,4 +20,4 @@ data class DirectionDto( ) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FacilityDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FacilityDto.kt index 6a746c25..ac18d04b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FacilityDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FacilityDto.kt @@ -8,7 +8,7 @@ data class FacilityDto( val id: Long? = null, val name: String, val description: String, - val locations: List, + val locations: List ) { companion object { fun of(entity: AboutEntity): FacilityDto = entity.run { @@ -20,4 +20,4 @@ data class FacilityDto( ) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersCompanyDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersCompanyDto.kt index eb9710f3..dc67aaf3 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersCompanyDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersCompanyDto.kt @@ -18,4 +18,4 @@ data class FutureCareersCompanyDto( ) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersPage.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersPage.kt index e2a77fae..e54e9fd0 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersPage.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersPage.kt @@ -4,5 +4,4 @@ data class FutureCareersPage( val description: String, val stat: List, val companies: List -) { -} \ No newline at end of file +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersRequest.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersRequest.kt index 405e9043..425e6a13 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersRequest.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersRequest.kt @@ -1,11 +1,7 @@ package com.wafflestudio.csereal.core.about.dto -import com.wafflestudio.csereal.core.about.dto.FutureCareersCompanyDto -import com.wafflestudio.csereal.core.about.dto.FutureCareersStatDto - data class FutureCareersRequest( val description: String, val stat: List, val companies: List -) { -} \ No newline at end of file +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersStatDegreeDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersStatDegreeDto.kt index d930fe44..bd23c3e2 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersStatDegreeDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersStatDegreeDto.kt @@ -5,15 +5,15 @@ import com.wafflestudio.csereal.core.about.database.StatEntity data class FutureCareersStatDegreeDto( val id: Long, val name: String, - val count: Int, + val count: Int ) { companion object { fun of(entity: StatEntity): FutureCareersStatDegreeDto = entity.run { FutureCareersStatDegreeDto( id = this.id, name = this.name, - count = this.count, + count = this.count ) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersStatDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersStatDto.kt index ed46ea41..8b686811 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersStatDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/FutureCareersStatDto.kt @@ -5,5 +5,4 @@ data class FutureCareersStatDto( val bachelor: List, val master: List, val doctor: List -) { -} \ No newline at end of file +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/StudentClubDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/StudentClubDto.kt index 2196dce8..ed536178 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/StudentClubDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/StudentClubDto.kt @@ -8,7 +8,7 @@ data class StudentClubDto( val id: Long? = null, val name: String, val engName: String, - val description: String, + val description: String ) { companion object { fun of(entity: AboutEntity): StudentClubDto = entity.run { @@ -20,4 +20,4 @@ data class StudentClubDto( ) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt index 5e1f9dcc..ba1f8612 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt @@ -30,7 +30,6 @@ interface AboutService { fun migrateStudentClubs(requestList: List): List fun migrateFacilities(requestList: List): List fun migrateDirections(requestList: List): List - } @Service @@ -39,7 +38,7 @@ class AboutServiceImpl( private val companyRepository: CompanyRepository, private val statRepository: StatRepository, private val mainImageService: MainImageService, - private val attachmentService: AttachmentService, + private val attachmentService: AttachmentService ) : AboutService { @Transactional override fun createAbout( @@ -79,7 +78,6 @@ class AboutServiceImpl( val imageURL = mainImageService.createImageURL(about.mainImage) val attachmentResponses = attachmentService.createAttachmentResponses(about.attachments) - return AboutDto.of(about, imageURL, attachmentResponses) } @@ -119,15 +117,15 @@ class AboutServiceImpl( @Transactional override fun readFutureCareers(): FutureCareersPage { val description = "컴퓨터공학을 전공함으로써 벤처기업을 창업할 수 있을 뿐 " + - "아니라 시스템엔지니어, 보안전문가, 소프트웨어개발자, 데이터베이스관리자 등 " + - "많은 IT 전문 분야로의 진출이 가능하다. 또한 컴퓨터공학은 바이오, 전자전기, " + - "로봇, 기계, 의료 등 이공계 영역뿐만 아니라 정치, 경제, 사회, 문화의 다양한 분야와 " + - "결합되어 미래 지식정보사회에 대한 새로운 가능성을 제시하고 있고 새로운 학문적 과제가 " + - "지속적으로 생산되기 때문에 많은 전문연구인력이 필요하다.\n" + - "\n" + - "서울대학교 컴퓨터공학부의 경우 학부 졸업생 절반 이상이 대학원에 진학하고 있다. " + - "대학원에 진학하면 여러 전공분야 중 하나를 선택하여 보다 깊이 있는 지식의 습득과 연구과정을 거치게 되며 " + - "그 이후로는 국내외 관련 산업계, 학계에 주로 진출하고 있고, 새로운 아이디어로 벤처기업을 창업하기도 한다." + "아니라 시스템엔지니어, 보안전문가, 소프트웨어개발자, 데이터베이스관리자 등 " + + "많은 IT 전문 분야로의 진출이 가능하다. 또한 컴퓨터공학은 바이오, 전자전기, " + + "로봇, 기계, 의료 등 이공계 영역뿐만 아니라 정치, 경제, 사회, 문화의 다양한 분야와 " + + "결합되어 미래 지식정보사회에 대한 새로운 가능성을 제시하고 있고 새로운 학문적 과제가 " + + "지속적으로 생산되기 때문에 많은 전문연구인력이 필요하다.\n" + + "\n" + + "서울대학교 컴퓨터공학부의 경우 학부 졸업생 절반 이상이 대학원에 진학하고 있다. " + + "대학원에 진학하면 여러 전공분야 중 하나를 선택하여 보다 깊이 있는 지식의 습득과 연구과정을 거치게 되며 " + + "그 이후로는 국내외 관련 산업계, 학계에 주로 진출하고 있고, 새로운 아이디어로 벤처기업을 창업하기도 한다." val statList = mutableListOf() for (i: Int in 2021 downTo 2011) { @@ -174,7 +172,6 @@ class AboutServiceImpl( aboutRepository.save(newAbout) list.add(AboutDto.of(newAbout, null, listOf())) - } return list } @@ -241,7 +238,6 @@ class AboutServiceImpl( val list = mutableListOf() for (request in requestList) { - val aboutDto = AboutDto( id = null, name = request.name, @@ -259,7 +255,6 @@ class AboutServiceImpl( aboutRepository.save(newAbout) list.add(StudentClubDto.of(newAbout)) - } return list } @@ -269,7 +264,6 @@ class AboutServiceImpl( val list = mutableListOf() for (request in requestList) { - val aboutDto = AboutDto( id = null, name = request.name, @@ -292,7 +286,6 @@ class AboutServiceImpl( aboutRepository.save(newAbout) list.add(FacilityDto.of(newAbout)) - } return list } @@ -302,7 +295,6 @@ class AboutServiceImpl( val list = mutableListOf() for (request in requestList) { - val aboutDto = AboutDto( id = null, name = request.name, @@ -321,7 +313,6 @@ class AboutServiceImpl( aboutRepository.save(newAbout) list.add(DirectionDto.of(newAbout)) - } return list } @@ -330,10 +321,8 @@ class AboutServiceImpl( try { val upperPostType = postType.replace("-", "_").uppercase() return AboutPostType.valueOf(upperPostType) - } catch (e: IllegalArgumentException) { throw CserealException.Csereal400("해당하는 enum을 찾을 수 없습니다") } } - -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt index 11b52c29..279a3a82 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt @@ -19,7 +19,9 @@ class AcademicsController( fun createAcademics( @PathVariable studentType: String, @PathVariable postType: String, - @Valid @RequestPart("request") request: AcademicsDto, + @Valid + @RequestPart("request") + request: AcademicsDto, @RequestPart("attachments") attachments: List? ): ResponseEntity { return ResponseEntity.ok(academicsService.createAcademics(studentType, postType, request, attachments)) @@ -35,7 +37,7 @@ class AcademicsController( @GetMapping("/{studentType}/{postType}") fun readAcademicsYearResponses( @PathVariable studentType: String, - @PathVariable postType: String, + @PathVariable postType: String ): ResponseEntity> { return ResponseEntity.ok(academicsService.readAcademicsYearResponses(studentType, postType)) } @@ -45,15 +47,17 @@ class AcademicsController( @PostMapping("/{studentType}/course") fun createCourse( @PathVariable studentType: String, - @Valid @RequestPart("request") request: CourseDto, - @RequestPart("attachments") attachments: List?, + @Valid + @RequestPart("request") + request: CourseDto, + @RequestPart("attachments") attachments: List? ): ResponseEntity { return ResponseEntity.ok(academicsService.createCourse(studentType, request, attachments)) } @GetMapping("/{studentType}/courses") fun readAllCourses( - @PathVariable studentType: String, + @PathVariable studentType: String ): ResponseEntity> { return ResponseEntity.ok(academicsService.readAllCourses(studentType)) } @@ -74,7 +78,8 @@ class AcademicsController( @PostMapping("/{studentType}/scholarshipDetail") fun createScholarshipDetail( @PathVariable studentType: String, - @Valid @RequestBody request: ScholarshipDto, + @Valid @RequestBody + request: ScholarshipDto ): ResponseEntity { return ResponseEntity.ok(academicsService.createScholarshipDetail(studentType, request)) } @@ -90,5 +95,4 @@ class AcademicsController( fun getScholarship(@PathVariable scholarshipId: Long): ResponseEntity { return ResponseEntity.ok(academicsService.readScholarship(scholarshipId)) } - } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsEntity.kt index 437c3134..2b90f96d 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsEntity.kt @@ -20,22 +20,25 @@ class AcademicsEntity( var time: String?, @OneToMany(mappedBy = "academics", cascade = [CascadeType.ALL], orphanRemoval = true) - var attachments: MutableList = mutableListOf(), + var attachments: MutableList = mutableListOf() - - ): BaseTimeEntity(), AttachmentContentEntityType { +) : BaseTimeEntity(), AttachmentContentEntityType { override fun bringAttachments() = attachments companion object { - fun of(studentType: AcademicsStudentType, postType: AcademicsPostType, academicsDto: AcademicsDto): AcademicsEntity { + fun of( + studentType: AcademicsStudentType, + postType: AcademicsPostType, + academicsDto: AcademicsDto + ): AcademicsEntity { return AcademicsEntity( studentType = studentType, postType = postType, name = academicsDto.name, description = academicsDto.description, year = academicsDto.year, - time = academicsDto.time, + time = academicsDto.time ) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsPostType.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsPostType.kt index 85684f7a..432ea6c6 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsPostType.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsPostType.kt @@ -1,5 +1,6 @@ package com.wafflestudio.csereal.core.academics.database enum class AcademicsPostType { - GUIDE, GENERAL_STUDIES_REQUIREMENTS, GENERAL_STUDIES_REQUIREMENTS_SUBJECT_CHANGES, CURRICULUM, DEGREE_REQUIREMENTS, COURSE_CHANGES, SCHOLARSHIP -} \ No newline at end of file + GUIDE, GENERAL_STUDIES_REQUIREMENTS, GENERAL_STUDIES_REQUIREMENTS_SUBJECT_CHANGES, + CURRICULUM, DEGREE_REQUIREMENTS, COURSE_CHANGES, SCHOLARSHIP; +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsRepository.kt index 3026acc9..e5eacb97 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsRepository.kt @@ -3,7 +3,16 @@ package com.wafflestudio.csereal.core.academics.database import org.springframework.data.jpa.repository.JpaRepository interface AcademicsRepository : JpaRepository { - fun findByStudentTypeAndPostType(studentType: AcademicsStudentType, postType: AcademicsPostType) : AcademicsEntity - fun findAllByStudentTypeAndPostTypeOrderByYearDesc(studentType: AcademicsStudentType, postType: AcademicsPostType): List - fun findAllByStudentTypeAndPostTypeOrderByTimeDesc(studentType: AcademicsStudentType, postType: AcademicsPostType): List + fun findByStudentTypeAndPostType( + studentType: AcademicsStudentType, + postType: AcademicsPostType + ): AcademicsEntity + fun findAllByStudentTypeAndPostTypeOrderByYearDesc( + studentType: AcademicsStudentType, + postType: AcademicsPostType + ): List + fun findAllByStudentTypeAndPostTypeOrderByTimeDesc( + studentType: AcademicsStudentType, + postType: AcademicsPostType + ): List } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsStudentType.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsStudentType.kt index c153b2a6..2da6e67a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsStudentType.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsStudentType.kt @@ -2,4 +2,4 @@ package com.wafflestudio.csereal.core.academics.database enum class AcademicsStudentType { UNDERGRADUATE, GRADUATE -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseEntity.kt index dee58f66..5f6727ef 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseEntity.kt @@ -2,14 +2,11 @@ package com.wafflestudio.csereal.core.academics.database import com.wafflestudio.csereal.common.config.BaseTimeEntity import com.wafflestudio.csereal.common.controller.AttachmentContentEntityType -import com.wafflestudio.csereal.common.controller.MainImageContentEntityType import com.wafflestudio.csereal.core.academics.dto.CourseDto import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity -import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity import jakarta.persistence.CascadeType import jakarta.persistence.Entity import jakarta.persistence.OneToMany -import jakarta.persistence.OneToOne @Entity(name = "course") class CourseEntity( @@ -30,9 +27,9 @@ class CourseEntity( var description: String?, @OneToMany(mappedBy = "course", cascade = [CascadeType.ALL], orphanRemoval = true) - var attachments: MutableList = mutableListOf(), + var attachments: MutableList = mutableListOf() -): BaseTimeEntity(), AttachmentContentEntityType { +) : BaseTimeEntity(), AttachmentContentEntityType { override fun bringAttachments() = attachments companion object { fun of(studentType: AcademicsStudentType, courseDto: CourseDto): CourseEntity { @@ -40,11 +37,11 @@ class CourseEntity( studentType = studentType, classification = courseDto.classification, code = courseDto.code, - name = courseDto.name.replace(" ","-"), + name = courseDto.name.replace(" ", "-"), credit = courseDto.credit, grade = courseDto.grade, description = courseDto.description ) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseRepository.kt index 61e3d550..3ed6f69e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseRepository.kt @@ -3,6 +3,6 @@ package com.wafflestudio.csereal.core.academics.database import org.springframework.data.jpa.repository.JpaRepository interface CourseRepository : JpaRepository { - fun findAllByStudentTypeOrderByNameAsc(studentType: AcademicsStudentType) : List - fun findByName(name: String) : CourseEntity -} \ No newline at end of file + fun findAllByStudentTypeOrderByNameAsc(studentType: AcademicsStudentType): List + fun findByName(name: String): CourseEntity +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/ScholarshipEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/ScholarshipEntity.kt index 8eb6ed37..3fc88cea 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/ScholarshipEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/ScholarshipEntity.kt @@ -1,9 +1,7 @@ package com.wafflestudio.csereal.core.academics.database import com.wafflestudio.csereal.common.config.BaseTimeEntity -import com.wafflestudio.csereal.common.controller.AttachmentContentEntityType import com.wafflestudio.csereal.core.academics.dto.ScholarshipDto -import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity import jakarta.persistence.* @Entity(name = "scholarship") @@ -14,7 +12,7 @@ class ScholarshipEntity( val name: String, @Column(columnDefinition = "text") - val description: String, + val description: String ) : BaseTimeEntity() { @@ -23,8 +21,8 @@ class ScholarshipEntity( return ScholarshipEntity( studentType = studentType, name = scholarshipDto.name, - description = scholarshipDto.description, + description = scholarshipDto.description ) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/ScholarshipRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/ScholarshipRepository.kt index f54998a1..930d170b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/ScholarshipRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/ScholarshipRepository.kt @@ -1,6 +1,5 @@ package com.wafflestudio.csereal.core.academics.database -import com.wafflestudio.csereal.core.academics.database.ScholarshipEntity import org.springframework.data.jpa.repository.JpaRepository interface ScholarshipRepository : JpaRepository { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsDto.kt index ed0e433a..e3775e33 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsDto.kt @@ -12,10 +12,10 @@ data class AcademicsDto( val time: String?, val createdAt: LocalDateTime?, val modifiedAt: LocalDateTime?, - val attachments: List?, + val attachments: List? ) { companion object { - fun of(entity: AcademicsEntity, attachmentResponses: List) : AcademicsDto = entity.run { + fun of(entity: AcademicsEntity, attachmentResponses: List): AcademicsDto = entity.run { AcademicsDto( id = this.id, name = this.name, @@ -24,8 +24,8 @@ data class AcademicsDto( time = this.time, createdAt = this.createdAt, modifiedAt = this.modifiedAt, - attachments = attachmentResponses, + attachments = attachmentResponses ) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsYearResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsYearResponse.kt index b71d5eed..554e71bf 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsYearResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsYearResponse.kt @@ -17,4 +17,4 @@ class AcademicsYearResponse( ) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/CourseDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/CourseDto.kt index 057e7db0..dedc13f6 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/CourseDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/CourseDto.kt @@ -11,7 +11,7 @@ data class CourseDto( val credit: Int, val grade: String, val description: String?, - val attachments: List?, + val attachments: List? ) { companion object { fun of(entity: CourseEntity, attachmentResponses: List): CourseDto = entity.run { @@ -23,8 +23,8 @@ data class CourseDto( credit = this.credit, grade = this.grade, description = this.description, - attachments = attachmentResponses, + attachments = attachmentResponses ) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GeneralStudiesPageResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GeneralStudiesPageResponse.kt index 0a13e3f5..950434f2 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GeneralStudiesPageResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GeneralStudiesPageResponse.kt @@ -4,7 +4,7 @@ import com.wafflestudio.csereal.core.academics.database.AcademicsEntity class GeneralStudiesPageResponse( val subjectChanges: List, - val description: String, + val description: String ) { companion object { fun of(entity: AcademicsEntity, subjectChangesEntity: List) = entity.run { @@ -14,4 +14,4 @@ class GeneralStudiesPageResponse( ) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GuidePageResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GuidePageResponse.kt index dce8fd54..4c35f4c1 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GuidePageResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GuidePageResponse.kt @@ -15,4 +15,4 @@ class GuidePageResponse( ) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/SubjectChangesDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/SubjectChangesDto.kt index 33cf95d3..6d362ac8 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/SubjectChangesDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/SubjectChangesDto.kt @@ -4,7 +4,7 @@ import com.wafflestudio.csereal.core.academics.database.AcademicsEntity class SubjectChangesDto( val time: String, - val description: String, + val description: String ) { companion object { fun of(entity: AcademicsEntity) = entity.run { @@ -14,4 +14,4 @@ class SubjectChangesDto( ) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt index eb3e3f9c..2773b6d5 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt @@ -12,7 +12,13 @@ import org.springframework.transaction.annotation.Transactional import org.springframework.web.multipart.MultipartFile interface AcademicsService { - fun createAcademics(studentType: String, postType: String, request: AcademicsDto, attachments: List?): AcademicsDto + fun createAcademics( + studentType: String, + postType: String, + request: AcademicsDto, + attachments: List? + ): AcademicsDto + fun readGuide(studentType: String): GuidePageResponse fun readAcademicsYearResponses(studentType: String, postType: String): List fun readGeneralStudies(): GeneralStudiesPageResponse @@ -32,13 +38,18 @@ class AcademicsServiceImpl( private val scholarshipRepository: ScholarshipRepository ) : AcademicsService { @Transactional - override fun createAcademics(studentType: String, postType: String, request: AcademicsDto, attachments: List?): AcademicsDto { + override fun createAcademics( + studentType: String, + postType: String, + request: AcademicsDto, + attachments: List? + ): AcademicsDto { val enumStudentType = makeStringToAcademicsStudentType(studentType) val enumPostType = makeStringToAcademicsPostType(postType) val newAcademics = AcademicsEntity.of(enumStudentType, enumPostType, request) - if(attachments != null) { + if (attachments != null) { attachmentService.uploadAllAttachments(newAcademics, attachments) } @@ -46,13 +57,11 @@ class AcademicsServiceImpl( val attachmentResponses = attachmentService.createAttachmentResponses(newAcademics.attachments) - return AcademicsDto.of(newAcademics, attachmentResponses) } @Transactional(readOnly = true) override fun readGuide(studentType: String): GuidePageResponse { - val enumStudentType = makeStringToAcademicsStudentType(studentType) val academicsEntity = academicsRepository.findByStudentTypeAndPostType(enumStudentType, AcademicsPostType.GUIDE) @@ -65,7 +74,10 @@ class AcademicsServiceImpl( val enumStudentType = makeStringToAcademicsStudentType(studentType) val enumPostType = makeStringToAcademicsPostType(postType) - val academicsEntityList = academicsRepository.findAllByStudentTypeAndPostTypeOrderByYearDesc(enumStudentType, enumPostType) + val academicsEntityList = academicsRepository.findAllByStudentTypeAndPostTypeOrderByYearDesc( + enumStudentType, + enumPostType + ) val academicsYearResponses = academicsEntityList.map { val attachments = attachmentService.createAttachmentResponses(it.attachments) @@ -76,7 +88,10 @@ class AcademicsServiceImpl( } override fun readGeneralStudies(): GeneralStudiesPageResponse { - val academicsEntity = academicsRepository.findByStudentTypeAndPostType(AcademicsStudentType.UNDERGRADUATE, AcademicsPostType.GENERAL_STUDIES_REQUIREMENTS) + val academicsEntity = academicsRepository.findByStudentTypeAndPostType( + AcademicsStudentType.UNDERGRADUATE, + AcademicsPostType.GENERAL_STUDIES_REQUIREMENTS + ) val subjectChangesList = academicsRepository.findAllByStudentTypeAndPostTypeOrderByTimeDesc( AcademicsStudentType.UNDERGRADUATE, AcademicsPostType.GENERAL_STUDIES_REQUIREMENTS_SUBJECT_CHANGES @@ -90,7 +105,7 @@ class AcademicsServiceImpl( val enumStudentType = makeStringToAcademicsStudentType(studentType) val newCourse = CourseEntity.of(enumStudentType, request) - if(attachments != null) { + if (attachments != null) { attachmentService.uploadAllAttachments(newCourse, attachments) } @@ -134,7 +149,10 @@ class AcademicsServiceImpl( override fun readAllScholarship(studentType: String): ScholarshipPageResponse { val enumStudentType = makeStringToAcademicsStudentType(studentType) - val academicsEntity = academicsRepository.findByStudentTypeAndPostType(enumStudentType, AcademicsPostType.SCHOLARSHIP) + val academicsEntity = academicsRepository.findByStudentTypeAndPostType( + enumStudentType, + AcademicsPostType.SCHOLARSHIP + ) val scholarshipEntityList = scholarshipRepository.findAllByStudentType(enumStudentType) return ScholarshipPageResponse.of(academicsEntity, scholarshipEntityList) @@ -151,7 +169,6 @@ class AcademicsServiceImpl( try { val upperPostType = postType.replace("-", "_").uppercase() return AcademicsStudentType.valueOf(upperPostType) - } catch (e: IllegalArgumentException) { throw CserealException.Csereal400("해당하는 enum을 찾을 수 없습니다") } @@ -161,7 +178,6 @@ class AcademicsServiceImpl( try { val upperPostType = postType.replace("-", "_").uppercase() return AcademicsPostType.valueOf(upperPostType) - } catch (e: IllegalArgumentException) { throw CserealException.Csereal400("해당하는 enum을 찾을 수 없습니다") } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/api/AdminController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/api/AdminController.kt index 737e9db8..5f2cce16 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admin/api/AdminController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/api/AdminController.kt @@ -47,6 +47,4 @@ class AdminController( ) { adminService.makeNotImportants(request.targetInfos) } - - } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/database/AdminRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/database/AdminRepository.kt index 236a80c9..25d8d024 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admin/database/AdminRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/database/AdminRepository.kt @@ -2,7 +2,6 @@ package com.wafflestudio.csereal.core.admin.database import com.querydsl.core.types.Projections import com.querydsl.jpa.impl.JPAQueryFactory -import com.wafflestudio.csereal.core.admin.dto.ImportantResponse import com.wafflestudio.csereal.core.admin.dto.SlideResponse import com.wafflestudio.csereal.core.news.database.QNewsEntity.newsEntity import org.springframework.stereotype.Component @@ -13,8 +12,8 @@ interface AdminRepository { @Component class AdminRepositoryImpl( - private val queryFactory: JPAQueryFactory, -): AdminRepository { + private val queryFactory: JPAQueryFactory +) : AdminRepository { override fun readAllSlides(pageNum: Long): List { return queryFactory.select( Projections.constructor( @@ -26,8 +25,8 @@ class AdminRepositoryImpl( ).from(newsEntity) .where(newsEntity.isDeleted.eq(false), newsEntity.isPrivate.eq(false), newsEntity.isSlide.eq(true)) .orderBy(newsEntity.createdAt.desc()) - .offset(40*pageNum) + .offset(40 * pageNum) .limit(40) .fetch() } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/ImportantDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/ImportantDto.kt index b056f38d..4c53eabe 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/ImportantDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/ImportantDto.kt @@ -2,6 +2,5 @@ package com.wafflestudio.csereal.core.admin.dto class ImportantDto( val id: Long, - val category: String, -) { -} \ No newline at end of file + val category: String +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/ImportantRequest.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/ImportantRequest.kt index ff93f215..36115c5d 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/ImportantRequest.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/ImportantRequest.kt @@ -2,5 +2,4 @@ package com.wafflestudio.csereal.core.admin.dto class ImportantRequest( val targetInfos: List -) { -} \ No newline at end of file +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/ImportantResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/ImportantResponse.kt index 639a768e..f2fe3586 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/ImportantResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/ImportantResponse.kt @@ -6,6 +6,5 @@ class ImportantResponse( val id: Long, val title: String, val createdAt: LocalDateTime?, - val category: String, -) { -} \ No newline at end of file + val category: String +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/NewsIdListRequest.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/NewsIdListRequest.kt index f59210cb..dcc04a18 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/NewsIdListRequest.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/NewsIdListRequest.kt @@ -2,6 +2,4 @@ package com.wafflestudio.csereal.core.admin.dto data class NewsIdListRequest( val newsIdList: List -) { - -} \ No newline at end of file +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/SlideResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/SlideResponse.kt index e4de274f..cea89ab9 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/SlideResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/SlideResponse.kt @@ -5,6 +5,5 @@ import java.time.LocalDateTime class SlideResponse( val id: Long, val title: String, - val createdAt: LocalDateTime?, -) { -} \ No newline at end of file + val createdAt: LocalDateTime? +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/service/AdminService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/service/AdminService.kt index 4bc2d899..e90a2357 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admin/service/AdminService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/service/AdminService.kt @@ -3,7 +3,6 @@ package com.wafflestudio.csereal.core.admin.service import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.core.admin.database.AdminRepository import com.wafflestudio.csereal.core.admin.dto.ImportantDto -import com.wafflestudio.csereal.core.admin.dto.ImportantRequest import com.wafflestudio.csereal.core.admin.dto.ImportantResponse import com.wafflestudio.csereal.core.admin.dto.SlideResponse import com.wafflestudio.csereal.core.news.database.NewsEntity @@ -13,7 +12,6 @@ import com.wafflestudio.csereal.core.seminar.database.SeminarRepository import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional -import java.time.LocalDateTime interface AdminService { fun readAllSlides(pageNum: Long): List @@ -27,7 +25,7 @@ class AdminServiceImpl( private val adminRepository: AdminRepository, private val noticeRepository: NoticeRepository, private val newsRepository: NewsRepository, - private val seminarRepository: SeminarRepository, + private val seminarRepository: SeminarRepository ) : AdminService { @Transactional override fun readAllSlides(pageNum: Long): List { @@ -85,8 +83,8 @@ class AdminServiceImpl( @Transactional override fun makeNotImportants(request: List) { - for(important in request) { - when(important.category) { + for (important in request) { + when (important.category) { "notice" -> { val notice = noticeRepository.findByIdOrNull(important.id) ?: throw CserealException.Csereal404("해당하는 공지사항을 찾을 수 없습니다.(noticeId=${important.id})") @@ -106,4 +104,3 @@ class AdminServiceImpl( } } } - diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/AdmissionsController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/AdmissionsController.kt index a5b1c2fc..d9f6a733 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/AdmissionsController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/AdmissionsController.kt @@ -10,7 +10,6 @@ import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController @RequestMapping("/api/v1/admissions") @@ -22,7 +21,8 @@ class AdmissionsController( @PostMapping("/undergraduate/{postType}") fun createUndergraduateAdmissions( @PathVariable postType: String, - @Valid @RequestBody request: AdmissionsDto + @Valid @RequestBody + request: AdmissionsDto ): AdmissionsDto { return admissionsService.createUndergraduateAdmissions(postType, request) } @@ -30,7 +30,8 @@ class AdmissionsController( @AuthenticatedStaff @PostMapping("/graduate") fun createGraduateAdmissions( - @Valid @RequestBody request: AdmissionsDto + @Valid @RequestBody + request: AdmissionsDto ): AdmissionsDto { return admissionsService.createGraduateAdmissions(request) } @@ -46,6 +47,4 @@ class AdmissionsController( fun readGraduateAdmissions(): ResponseEntity { return ResponseEntity.ok(admissionsService.readGraduateAdmissions()) } - - } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsEntity.kt index 7d7640e8..8510708a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsEntity.kt @@ -11,15 +11,15 @@ class AdmissionsEntity( @Enumerated(EnumType.STRING) val postType: AdmissionsPostType, val pageName: String, - val description: String, -): BaseTimeEntity() { + val description: String +) : BaseTimeEntity() { companion object { - fun of(postType: AdmissionsPostType, pageName: String, admissionsDto: AdmissionsDto) : AdmissionsEntity { + fun of(postType: AdmissionsPostType, pageName: String, admissionsDto: AdmissionsDto): AdmissionsEntity { return AdmissionsEntity( postType = postType, pageName = pageName, - description = admissionsDto.description, + description = admissionsDto.description ) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsPostType.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsPostType.kt index 104cf01b..be0adf3f 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsPostType.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsPostType.kt @@ -2,4 +2,4 @@ package com.wafflestudio.csereal.core.admissions.database enum class AdmissionsPostType { GRADUATE, UNDERGRADUATE_EARLY_ADMISSION, UNDERGRADUATE_REGULAR_ADMISSION, -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsRepository.kt index f4e2c1b0..02c2c5c1 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsRepository.kt @@ -3,5 +3,5 @@ package com.wafflestudio.csereal.core.admissions.database import org.springframework.data.jpa.repository.JpaRepository interface AdmissionsRepository : JpaRepository { - fun findByPostType(postType: AdmissionsPostType) : AdmissionsEntity -} \ No newline at end of file + fun findByPostType(postType: AdmissionsPostType): AdmissionsEntity +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/dto/AdmissionsDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/dto/AdmissionsDto.kt index c5bb7dbe..5b8ae40a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/dto/AdmissionsDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/dto/AdmissionsDto.kt @@ -7,16 +7,16 @@ data class AdmissionsDto( val id: Long, val description: String, val createdAt: LocalDateTime?, - val modifiedAt: LocalDateTime?, + val modifiedAt: LocalDateTime? ) { companion object { - fun of(entity: AdmissionsEntity) : AdmissionsDto = entity.run { + fun of(entity: AdmissionsEntity): AdmissionsDto = entity.run { AdmissionsDto( id = this.id, description = this.description, createdAt = this.createdAt, - modifiedAt = this.modifiedAt, + modifiedAt = this.modifiedAt ) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/service/AdmissionsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/service/AdmissionsService.kt index 67a59ae8..e89b57ee 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/service/AdmissionsService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/service/AdmissionsService.kt @@ -13,7 +13,6 @@ interface AdmissionsService { fun createGraduateAdmissions(request: AdmissionsDto): AdmissionsDto fun readUndergraduateAdmissions(postType: String): AdmissionsDto fun readGraduateAdmissions(): AdmissionsDto - } @Service @@ -24,7 +23,7 @@ class AdmissionsServiceImpl( override fun createUndergraduateAdmissions(postType: String, request: AdmissionsDto): AdmissionsDto { val enumPostType = makeStringToAdmissionsPostType(postType) - val pageName = when(enumPostType) { + val pageName = when (enumPostType) { AdmissionsPostType.UNDERGRADUATE_EARLY_ADMISSION -> "수시 모집" AdmissionsPostType.UNDERGRADUATE_REGULAR_ADMISSION -> "정시 모집" else -> throw CserealException.Csereal404("해당하는 페이지를 찾을 수 없습니다.") @@ -49,8 +48,12 @@ class AdmissionsServiceImpl( @Transactional(readOnly = true) override fun readUndergraduateAdmissions(postType: String): AdmissionsDto { return when (postType) { - "early" -> AdmissionsDto.of(admissionsRepository.findByPostType(AdmissionsPostType.UNDERGRADUATE_EARLY_ADMISSION)) - "regular" -> AdmissionsDto.of(admissionsRepository.findByPostType(AdmissionsPostType.UNDERGRADUATE_REGULAR_ADMISSION)) + "early" -> AdmissionsDto.of( + admissionsRepository.findByPostType(AdmissionsPostType.UNDERGRADUATE_EARLY_ADMISSION) + ) + "regular" -> AdmissionsDto.of( + admissionsRepository.findByPostType(AdmissionsPostType.UNDERGRADUATE_REGULAR_ADMISSION) + ) else -> throw CserealException.Csereal404("해당하는 페이지를 찾을 수 없습니다.") } } @@ -60,13 +63,12 @@ class AdmissionsServiceImpl( return AdmissionsDto.of(admissionsRepository.findByPostType(AdmissionsPostType.GRADUATE)) } - private fun makeStringToAdmissionsPostType(postType: String) : AdmissionsPostType { + private fun makeStringToAdmissionsPostType(postType: String): AdmissionsPostType { try { - val upperPostType = postType.replace("-","_").uppercase() + val upperPostType = postType.replace("-", "_").uppercase() return AdmissionsPostType.valueOf(upperPostType) - } catch (e: IllegalArgumentException) { throw CserealException.Csereal400("해당하는 enum을 찾을 수 없습니다") } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/conference/api/ConferenceController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/conference/api/ConferenceController.kt index cbbce4e2..e63cff16 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/conference/api/ConferenceController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/conference/api/ConferenceController.kt @@ -21,12 +21,12 @@ class ConferenceController( @AuthenticatedStaff @PatchMapping("/page/conferences") fun modifyConferencePage( - @RequestBody conferenceModifyRequest: ConferenceModifyRequest + @RequestBody conferenceModifyRequest: ConferenceModifyRequest ): ResponseEntity { return ResponseEntity.ok( - conferenceService.modifyConferences( - conferenceModifyRequest - ) + conferenceService.modifyConferences( + conferenceModifyRequest + ) ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferenceEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferenceEntity.kt index 5ff9e60b..6a3d37b0 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferenceEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferenceEntity.kt @@ -8,27 +8,27 @@ import jakarta.persistence.* @Entity(name = "conference") class ConferenceEntity( - var isDeleted: Boolean = false, - var code: String, - var abbreviation: String, - var name: String, + var isDeleted: Boolean = false, + var code: String, + var abbreviation: String, + var name: String, - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "conference_page_id") - val conferencePage: ConferencePageEntity, + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "conference_page_id") + val conferencePage: ConferencePageEntity, - @OneToOne(mappedBy = "conferenceElement", cascade = [CascadeType.ALL], orphanRemoval = true) - var researchSearch: ResearchSearchEntity? = null, + @OneToOne(mappedBy = "conferenceElement", cascade = [CascadeType.ALL], orphanRemoval = true) + var researchSearch: ResearchSearchEntity? = null ) : BaseTimeEntity() { companion object { fun of( - conferenceCreateDto: ConferenceCreateDto, - conferencePage: ConferencePageEntity, + conferenceCreateDto: ConferenceCreateDto, + conferencePage: ConferencePageEntity ) = ConferenceEntity( - code = conferenceCreateDto.code, - abbreviation = conferenceCreateDto.abbreviation, - name = conferenceCreateDto.name, - conferencePage = conferencePage, + code = conferenceCreateDto.code, + abbreviation = conferenceCreateDto.abbreviation, + name = conferenceCreateDto.name, + conferencePage = conferencePage ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferencePageRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferencePageRepository.kt index 43416e17..26b63997 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferencePageRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferencePageRepository.kt @@ -2,5 +2,4 @@ package com.wafflestudio.csereal.core.conference.database import org.springframework.data.jpa.repository.JpaRepository -interface ConferencePageRepository : JpaRepository { -} +interface ConferencePageRepository : JpaRepository diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferenceRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferenceRepository.kt index 25b6865b..adc5fcd0 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferenceRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferenceRepository.kt @@ -2,4 +2,4 @@ package com.wafflestudio.csereal.core.conference.database import org.springframework.data.jpa.repository.JpaRepository -interface ConferenceRepository: JpaRepository \ No newline at end of file +interface ConferenceRepository : JpaRepository diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceCreateDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceCreateDto.kt index ab2b475e..381ab9e1 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceCreateDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceCreateDto.kt @@ -1,8 +1,7 @@ package com.wafflestudio.csereal.core.conference.dto -data class ConferenceCreateDto ( +data class ConferenceCreateDto( val code: String, val abbreviation: String, - val name: String, -) { -} \ No newline at end of file + val name: String +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceDto.kt index cd8a18c7..b9be02bd 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceDto.kt @@ -6,7 +6,7 @@ data class ConferenceDto( val id: Long, val code: String, val abbreviation: String, - val name: String, + val name: String ) { companion object { fun of(conferenceEntity: ConferenceEntity): ConferenceDto { @@ -14,7 +14,7 @@ data class ConferenceDto( id = conferenceEntity.id, code = conferenceEntity.code, abbreviation = conferenceEntity.abbreviation, - name = conferenceEntity.name, + name = conferenceEntity.name ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceModifyRequest.kt b/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceModifyRequest.kt index 4d5111d0..6d4d10b6 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceModifyRequest.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceModifyRequest.kt @@ -1,7 +1,7 @@ package com.wafflestudio.csereal.core.conference.dto data class ConferenceModifyRequest( - val newConferenceList: List, - val modifiedConferenceList: List, - val deleteConfereceIdList: List + val newConferenceList: List, + val modifiedConferenceList: List, + val deleteConfereceIdList: List ) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferencePage.kt b/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferencePage.kt index 640c95e0..93cb5aca 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferencePage.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferencePage.kt @@ -16,8 +16,8 @@ data class ConferencePage( modifiedAt = conferencePageEntity.modifiedAt!!, author = conferencePageEntity.author.name, conferenceList = conferencePageEntity.conferences.map { - ConferenceDto.of(it) - }.sortedBy { it.code } + ConferenceDto.of(it) + }.sortedBy { it.code } ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/conference/service/ConferenceService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/conference/service/ConferenceService.kt index 343eb4a6..1ca66708 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/conference/service/ConferenceService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/conference/service/ConferenceService.kt @@ -14,14 +14,11 @@ import com.wafflestudio.csereal.core.research.service.ResearchSearchService import com.wafflestudio.csereal.core.user.database.UserEntity import com.wafflestudio.csereal.core.user.database.UserRepository import org.springframework.data.repository.findByIdOrNull -import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.security.oauth2.core.oidc.user.OidcUser import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import org.springframework.web.context.request.RequestAttributes import org.springframework.web.context.request.RequestContextHolder - interface ConferenceService { fun getConferencePage(): ConferencePage fun modifyConferences(conferenceModifyRequest: ConferenceModifyRequest): ConferencePage @@ -33,7 +30,7 @@ class ConferenceServiceImpl( private val conferencePageRepository: ConferencePageRepository, private val conferenceRepository: ConferenceRepository, private val userRepository: UserRepository, - private val researchSearchService: ResearchSearchService, + private val researchSearchService: ResearchSearchService ) : ConferenceService { @Transactional(readOnly = true) @@ -71,7 +68,7 @@ class ConferenceServiceImpl( @Transactional fun createConferenceWithoutSave( conferenceCreateDto: ConferenceCreateDto, - conferencePage: ConferencePageEntity, + conferencePage: ConferencePageEntity ): ConferenceEntity { val newConference = ConferenceEntity.of( conferenceCreateDto, @@ -86,7 +83,7 @@ class ConferenceServiceImpl( @Transactional fun modifyConferenceWithoutSave( - conferenceDto: ConferenceDto, + conferenceDto: ConferenceDto ): ConferenceEntity { val conferenceEntity = conferenceRepository.findByIdOrNull(conferenceDto.id) ?: throw CserealException.Csereal404("Conference id:${conferenceDto.id} 가 존재하지 않습니다.") @@ -104,7 +101,7 @@ class ConferenceServiceImpl( @Transactional fun deleteConference( id: Long, - conferencePage: ConferencePageEntity, + conferencePage: ConferencePageEntity ) = conferenceRepository.findByIdOrNull(id) ?.let { it.isDeleted = true diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/api/MainController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/api/MainController.kt index 49c80bee..fd85e79b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/main/api/MainController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/api/MainController.kt @@ -9,10 +9,10 @@ import org.springframework.web.bind.annotation.RestController @RequestMapping("/api/v1") @RestController class MainController( - private val mainService: MainService, + private val mainService: MainService ) { @GetMapping - fun readMain() : MainResponse { + fun readMain(): MainResponse { return mainService.readMain() } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt index 698da1ce..79bc1e51 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt @@ -30,7 +30,7 @@ class MainRepositoryImpl( private val mainImageService: MainImageService, private val noticeRepository: NoticeRepository, private val newsRepository: NewsRepository, - private val seminarRepository: SeminarRepository, + private val seminarRepository: SeminarRepository ) : MainRepository { override fun readMainSlide(): List { val newsEntityList = queryFactory.selectFrom(newsEntity) @@ -46,7 +46,8 @@ class MainRepositoryImpl( imageURL = imageURL, createdAt = it.createdAt, description = it.plainTextDescription.substring( - 0, 100.coerceAtMost(it.plainTextDescription.length) + 0, + 100.coerceAtMost(it.plainTextDescription.length) ) ) } @@ -59,8 +60,8 @@ class MainRepositoryImpl( noticeEntity.id, noticeEntity.title, noticeEntity.createdAt, - noticeEntity.isPinned, - ), + noticeEntity.isPinned + ) ).from(noticeEntity) .where(noticeEntity.isDeleted.eq(false), noticeEntity.isPrivate.eq(false)) .orderBy(noticeEntity.isPinned.desc()).orderBy(noticeEntity.createdAt.desc()) @@ -74,7 +75,7 @@ class MainRepositoryImpl( noticeTagEntity.notice.id, noticeTagEntity.notice.title, noticeTagEntity.notice.createdAt, - noticeEntity.isPinned, + noticeEntity.isPinned ) ).from(noticeTagEntity) .rightJoin(noticeEntity).on(noticeTagEntity.notice.eq(noticeEntity)) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainImportantResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainImportantResponse.kt index 23b63b56..6546f0d4 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainImportantResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainImportantResponse.kt @@ -7,6 +7,5 @@ data class MainImportantResponse( val title: String, val description: String, val createdAt: LocalDateTime?, - val category: String, -) { -} \ No newline at end of file + val category: String +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainNoticeResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainNoticeResponse.kt index fb395045..e517e4eb 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainNoticeResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainNoticeResponse.kt @@ -10,7 +10,10 @@ data class MainNoticeResponse( ) { @QueryProjection constructor ( - id: Long, title: String, createdAt: LocalDateTime?, isPinned: Boolean - ): this(id, title, createdAt) { + id: Long, + title: String, + createdAt: LocalDateTime?, + isPinned: Boolean + ) : this(id, title, createdAt) { } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainResponse.kt index 06c6dc8b..19a1d7a1 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainResponse.kt @@ -1,10 +1,7 @@ package com.wafflestudio.csereal.core.main.dto - data class MainResponse( val slides: List, val notices: NoticesResponse, val importants: List -) { - -} \ No newline at end of file +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainSlideResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainSlideResponse.kt index 6696f0ed..c7142262 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainSlideResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/MainSlideResponse.kt @@ -8,7 +8,5 @@ data class MainSlideResponse @QueryProjection constructor( val title: String, val imageURL: String?, val createdAt: LocalDateTime?, - val description: String, -) { - -} \ No newline at end of file + val description: String +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/NoticesResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/NoticesResponse.kt index 9b9a2aaf..510322ff 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/NoticesResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/dto/NoticesResponse.kt @@ -7,5 +7,4 @@ data class NoticesResponse @QueryProjection constructor( val scholarship: List, val undergraduate: List, val graduate: List -){ -} \ No newline at end of file +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/service/MainService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/service/MainService.kt index 0c5c8951..1bef3b7e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/main/service/MainService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/service/MainService.kt @@ -6,10 +6,9 @@ import com.wafflestudio.csereal.core.main.dto.NoticesResponse import com.wafflestudio.csereal.core.notice.database.TagInNoticeEnum import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional -import javax.swing.text.html.HTML.Tag interface MainService { - fun readMain() : MainResponse + fun readMain(): MainResponse } @Service @@ -30,4 +29,4 @@ class MainServiceImpl( return MainResponse(slides, notices, importants) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/MemberSearchController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/MemberSearchController.kt index bd13a673..bcd393c3 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/MemberSearchController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/MemberSearchController.kt @@ -1,7 +1,6 @@ package com.wafflestudio.csereal.core.member.api import com.wafflestudio.csereal.core.member.service.MemberSearchService -import org.springframework.data.jpa.repository.Query import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam @@ -10,18 +9,18 @@ import org.springframework.web.bind.annotation.RestController @RestController @RequestMapping("/api/v1/member/search") class MemberSearchController( - private val memberSearchService: MemberSearchService, + private val memberSearchService: MemberSearchService ) { @GetMapping("/top") fun searchTop( - @RequestParam(required = true) keyword: String, - @RequestParam(required = true) number: Int, + @RequestParam(required = true) keyword: String, + @RequestParam(required = true) number: Int ) = memberSearchService.searchTopMember(keyword, number) @GetMapping fun searchPage( - @RequestParam(required = true) keyword: String, - @RequestParam(required = true) pageSize: Int, - @RequestParam(required = true) pageNum: Int, - )= memberSearchService.searchMember(keyword, pageSize, pageNum) -} \ No newline at end of file + @RequestParam(required = true) keyword: String, + @RequestParam(required = true) pageSize: Int, + @RequestParam(required = true) pageNum: Int + ) = memberSearchService.searchMember(keyword, pageSize, pageNum) +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt index 6c64cc46..548f39f9 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt @@ -19,7 +19,7 @@ class ProfessorController( @PostMapping fun createProfessor( @RequestPart("request") createProfessorRequest: ProfessorDto, - @RequestPart("mainImage") mainImage: MultipartFile?, + @RequestPart("mainImage") mainImage: MultipartFile? ): ResponseEntity { return ResponseEntity.ok(professorService.createProfessor(createProfessorRequest, mainImage)) } @@ -44,7 +44,7 @@ class ProfessorController( fun updateProfessor( @PathVariable professorId: Long, @RequestPart("request") updateProfessorRequest: ProfessorDto, - @RequestPart("mainImage") mainImage: MultipartFile?, + @RequestPart("mainImage") mainImage: MultipartFile? ): ResponseEntity { return ResponseEntity.ok(professorService.updateProfessor(professorId, updateProfessorRequest, mainImage)) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt index de4c9d07..eed5164c 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/StaffController.kt @@ -18,7 +18,7 @@ class StaffController( @PostMapping fun createStaff( @RequestPart("request") createStaffRequest: StaffDto, - @RequestPart("mainImage") mainImage: MultipartFile?, + @RequestPart("mainImage") mainImage: MultipartFile? ): ResponseEntity { return ResponseEntity.ok(staffService.createStaff(createStaffRequest, mainImage)) } @@ -37,7 +37,7 @@ class StaffController( fun updateStaff( @PathVariable staffId: Long, @RequestPart("request") updateStaffRequest: StaffDto, - @RequestPart("mainImage") mainImage: MultipartFile?, + @RequestPart("mainImage") mainImage: MultipartFile? ): ResponseEntity { return ResponseEntity.ok(staffService.updateStaff(staffId, updateStaffRequest, mainImage)) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/MemberSearchEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/MemberSearchEntity.kt index 4b669528..3944bf6e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/MemberSearchEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/MemberSearchEntity.kt @@ -1,34 +1,33 @@ package com.wafflestudio.csereal.core.member.database -import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.common.config.BaseTimeEntity import jakarta.persistence.* @Entity(name = "member_search") -class MemberSearchEntity ( - @Column(columnDefinition = "TEXT") - var content: String, +class MemberSearchEntity( + @Column(columnDefinition = "TEXT") + var content: String, - @OneToOne - @JoinColumn(name = "professor_id") - val professor: ProfessorEntity? = null, + @OneToOne + @JoinColumn(name = "professor_id") + val professor: ProfessorEntity? = null, - @OneToOne - @JoinColumn(name = "staff_id") - val staff: StaffEntity? = null, -): BaseTimeEntity() { + @OneToOne + @JoinColumn(name = "staff_id") + val staff: StaffEntity? = null +) : BaseTimeEntity() { companion object { fun create(professor: ProfessorEntity): MemberSearchEntity { return MemberSearchEntity( - content = createContent(professor), - professor = professor + content = createContent(professor), + professor = professor ) } fun create(staff: StaffEntity): MemberSearchEntity { return MemberSearchEntity( - content = createContent(staff), - staff = staff + content = createContent(staff), + staff = staff ) } @@ -49,7 +48,7 @@ class MemberSearchEntity ( professor.researchAreas.forEach { stringBuilder.appendLine(it.name) } professor.careers.forEach { stringBuilder.appendLine(it.name) } - return stringBuilder.toString() + return stringBuilder.toString() } fun createContent(staff: StaffEntity): String { @@ -69,8 +68,8 @@ class MemberSearchEntity ( @PreUpdate fun checkType() { if ( - (professor != null && staff != null) - || (professor == null && staff == null) + (professor != null && staff != null) || + (professor == null && staff == null) ) { throw RuntimeException("MemberSearchEntity must have either professor or staff") } @@ -98,4 +97,4 @@ class MemberSearchEntity ( enum class MemberSearchType { PROFESSOR, STAFF -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/MemberSearchRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/MemberSearchRepository.kt index 9ebb4348..662a2d41 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/MemberSearchRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/MemberSearchRepository.kt @@ -9,9 +9,8 @@ import com.wafflestudio.csereal.core.member.database.QStaffEntity.staffEntity import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository -interface MemberSearchRepository - : JpaRepository, MemberSearchRepositoryCustom { -} +interface MemberSearchRepository : + JpaRepository, MemberSearchRepositoryCustom interface MemberSearchRepositoryCustom { fun searchTopMember(keyword: String, number: Int): List @@ -19,15 +18,15 @@ interface MemberSearchRepositoryCustom { } @Repository -class MemberSearchRepositoryCustomImpl ( - private val queryFactory: JPAQueryFactory, - private val commonRepository: CommonRepository, -): MemberSearchRepositoryCustom { +class MemberSearchRepositoryCustomImpl( + private val queryFactory: JPAQueryFactory, + private val commonRepository: CommonRepository +) : MemberSearchRepositoryCustom { override fun searchTopMember(keyword: String, number: Int): List { return searchQuery(keyword) - .limit(number.toLong()) - .fetch() + .limit(number.toLong()) + .fetch() } override fun searchMember(keyword: String, pageSize: Int, pageNum: Int): Pair, Long> { @@ -36,47 +35,49 @@ class MemberSearchRepositoryCustomImpl ( val validPageNum = exchangePageNum(pageSize, pageNum, total) val queryResult = query - .offset((validPageNum-1) * pageSize.toLong()) - .limit(pageSize.toLong()) - .fetch() + .offset((validPageNum - 1) * pageSize.toLong()) + .limit(pageSize.toLong()) + .fetch() return queryResult to total } fun searchQuery(keyword: String): JPAQuery { val searchDoubleTemplate = commonRepository.searchFullSingleTextTemplate( - keyword, - memberSearchEntity.content - ) + keyword, + memberSearchEntity.content + ) return queryFactory.select( - memberSearchEntity + memberSearchEntity ).from( - memberSearchEntity + memberSearchEntity ).leftJoin( - memberSearchEntity.professor, professorEntity + memberSearchEntity.professor, + professorEntity ).fetchJoin() - .leftJoin( - memberSearchEntity.staff, staffEntity - ).fetchJoin() - .where( - searchDoubleTemplate.gt(0.0) - ) + .leftJoin( + memberSearchEntity.staff, + staffEntity + ).fetchJoin() + .where( + searchDoubleTemplate.gt(0.0) + ) } fun getSearchCount(keyword: String): Long { val searchDoubleTemplate = commonRepository.searchFullSingleTextTemplate( - keyword, - memberSearchEntity.content + keyword, + memberSearchEntity.content ) return queryFactory.select( - memberSearchEntity - .countDistinct() + memberSearchEntity + .countDistinct() ).from( - memberSearchEntity + memberSearchEntity ).where( - searchDoubleTemplate.gt(0.0) + searchDoubleTemplate.gt(0.0) ).fetchOne()!! } @@ -87,4 +88,4 @@ class MemberSearchRepositoryCustomImpl ( Math.ceil(total.toDouble() / pageSize).toInt() } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt index 377d7b3a..b1c80c43 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt @@ -35,7 +35,6 @@ class ProfessorEntity( val educations: MutableList = mutableListOf(), @OneToMany(mappedBy = "professor", cascade = [CascadeType.ALL], orphanRemoval = true) - val researchAreas: MutableList = mutableListOf(), @OneToMany(mappedBy = "professor", cascade = [CascadeType.ALL], orphanRemoval = true) @@ -45,7 +44,7 @@ class ProfessorEntity( var mainImage: MainImageEntity? = null, @OneToOne(mappedBy = "professor", cascade = [CascadeType.ALL], orphanRemoval = true) - var memberSearch: MemberSearchEntity? = null, + var memberSearch: MemberSearchEntity? = null ) : BaseTimeEntity(), MainImageContentEntityType { override fun bringMainImage(): MainImageEntity? = mainImage @@ -61,7 +60,7 @@ class ProfessorEntity( phone = professorDto.phone, fax = professorDto.fax, email = professorDto.email, - website = professorDto.website, + website = professorDto.website ) } } @@ -84,12 +83,11 @@ class ProfessorEntity( this.email = updateProfessorRequest.email this.website = updateProfessorRequest.website } - } -enum class ProfessorStatus ( +enum class ProfessorStatus( val krValue: String -){ +) { ACTIVE("교수"), INACTIVE("역대 교수"), VISITING("객원교수"); diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffEntity.kt index a41d1951..c9b704f6 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffEntity.kt @@ -25,7 +25,7 @@ class StaffEntity( var mainImage: MainImageEntity? = null, @OneToOne(mappedBy = "staff", cascade = [CascadeType.ALL], orphanRemoval = true) - var memberSearch: MemberSearchEntity? = null, + var memberSearch: MemberSearchEntity? = null ) : BaseTimeEntity(), MainImageContentEntityType { override fun bringMainImage(): MainImageEntity? = mainImage @@ -36,7 +36,7 @@ class StaffEntity( role = staffDto.role, office = staffDto.office, phone = staffDto.phone, - email = staffDto.email, + email = staffDto.email ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffRepository.kt index d85ab2d9..a293b642 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/StaffRepository.kt @@ -2,5 +2,4 @@ package com.wafflestudio.csereal.core.member.database import org.springframework.data.jpa.repository.JpaRepository -interface StaffRepository : JpaRepository { -} +interface StaffRepository : JpaRepository diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchPageResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchPageResponse.kt index 430d9024..41df25f1 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchPageResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchPageResponse.kt @@ -4,17 +4,17 @@ import com.wafflestudio.csereal.core.member.database.MemberSearchEntity import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity data class MemberSearchPageResponse( - val members: List, - val total: Long, + val members: List, + val total: Long ) { companion object { fun of( - members: List, - total: Long, - imageURLMaker: (MainImageEntity?) -> String? + members: List, + total: Long, + imageURLMaker: (MainImageEntity?) -> String? ) = MemberSearchPageResponse( - members = members.map { MemberSearchResponseElement.of(it, imageURLMaker) }, - total = total, - ) + members = members.map { MemberSearchResponseElement.of(it, imageURLMaker) }, + total = total + ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchResponseElement.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchResponseElement.kt index a79fe255..5a474194 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchResponseElement.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchResponseElement.kt @@ -6,37 +6,37 @@ import com.wafflestudio.csereal.core.member.database.MemberSearchType import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity data class MemberSearchResponseElement( - val id: Long, - val name: String, - val academicRankOrRole: String, - val imageURL: String?, - val memberType: MemberSearchType, + val id: Long, + val name: String, + val academicRankOrRole: String, + val imageURL: String?, + val memberType: MemberSearchType ) { companion object { fun of( - memberSearch: MemberSearchEntity, - imageURLMaker: (MainImageEntity?) -> String? + memberSearch: MemberSearchEntity, + imageURLMaker: (MainImageEntity?) -> String? ): MemberSearchResponseElement = - when { - memberSearch.professor != null && memberSearch.staff == null -> - MemberSearchResponseElement( - id = memberSearch.professor!!.id, - name = memberSearch.professor!!.name, - academicRankOrRole = memberSearch.professor!!.academicRank, - imageURL = imageURLMaker(memberSearch.professor!!.mainImage), - memberType = MemberSearchType.PROFESSOR - ) - memberSearch.professor == null && memberSearch.staff != null -> - MemberSearchResponseElement( - id = memberSearch.staff!!.id, - name = memberSearch.staff!!.name, - academicRankOrRole = memberSearch.staff!!.role, - imageURL = imageURLMaker(memberSearch.staff!!.mainImage), - memberType = MemberSearchType.STAFF - ) - else -> throw CserealException.Csereal401( - "MemberSearchEntity는 professor 혹은 staff 중 하나와만 연결되어있어야 합니다." - ) - } + when { + memberSearch.professor != null && memberSearch.staff == null -> + MemberSearchResponseElement( + id = memberSearch.professor!!.id, + name = memberSearch.professor!!.name, + academicRankOrRole = memberSearch.professor!!.academicRank, + imageURL = imageURLMaker(memberSearch.professor!!.mainImage), + memberType = MemberSearchType.PROFESSOR + ) + memberSearch.professor == null && memberSearch.staff != null -> + MemberSearchResponseElement( + id = memberSearch.staff!!.id, + name = memberSearch.staff!!.name, + academicRankOrRole = memberSearch.staff!!.role, + imageURL = imageURLMaker(memberSearch.staff!!.mainImage), + memberType = MemberSearchType.STAFF + ) + else -> throw CserealException.Csereal401( + "MemberSearchEntity는 professor 혹은 staff 중 하나와만 연결되어있어야 합니다." + ) + } } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchTopResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchTopResponse.kt index d60b0d55..6e577a56 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchTopResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchTopResponse.kt @@ -4,16 +4,16 @@ import com.wafflestudio.csereal.core.member.database.MemberSearchEntity import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity data class MemberSearchTopResponse( - val topMembers: List + val topMembers: List ) { companion object { fun of( - topMembers: List, - imageURLMaker: (MainImageEntity?) -> String? + topMembers: List, + imageURLMaker: (MainImageEntity?) -> String? ) = MemberSearchTopResponse( - topMembers = topMembers.map { - MemberSearchResponseElement.of(it, imageURLMaker) - } + topMembers = topMembers.map { + MemberSearchResponseElement.of(it, imageURLMaker) + } ) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/ProfessorDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/ProfessorDto.kt index c48f0a5b..90bf7f47 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/ProfessorDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/ProfessorDto.kt @@ -46,9 +46,8 @@ data class ProfessorDto( educations = professorEntity.educations.map { it.name }, researchAreas = professorEntity.researchAreas.map { it.name }, careers = professorEntity.careers.map { it.name }, - imageURL = imageURL, + imageURL = imageURL ) } - } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/SimpleProfessorDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/SimpleProfessorDto.kt index bfe05446..265deaf1 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/SimpleProfessorDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/SimpleProfessorDto.kt @@ -25,6 +25,5 @@ data class SimpleProfessorDto( imageURL = imageURL ) } - } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/StaffDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/StaffDto.kt index 69a1d9fd..bf75f902 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/StaffDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/StaffDto.kt @@ -28,6 +28,5 @@ data class StaffDto( imageURL = imageURL ) } - } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/MemberSearchService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/MemberSearchService.kt index 4d8cb40e..35651b02 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/MemberSearchService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/MemberSearchService.kt @@ -2,7 +2,6 @@ package com.wafflestudio.csereal.core.member.service import com.wafflestudio.csereal.core.member.database.MemberSearchRepository import com.wafflestudio.csereal.core.member.dto.MemberSearchPageResponse -import com.wafflestudio.csereal.core.member.dto.MemberSearchResponseElement import com.wafflestudio.csereal.core.member.dto.MemberSearchTopResponse import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService import org.springframework.stereotype.Service @@ -14,10 +13,10 @@ interface MemberSearchService { } @Service -class MemberSearchServiceImpl ( - private val memberSearchRepository: MemberSearchRepository, - private val mainImageService: MainImageService, -): MemberSearchService { +class MemberSearchServiceImpl( + private val memberSearchRepository: MemberSearchRepository, + private val mainImageService: MainImageService +) : MemberSearchService { @Transactional(readOnly = true) override fun searchTopMember(keyword: String, number: Int): MemberSearchTopResponse { val entityResults = memberSearchRepository.searchTopMember(keyword, number) @@ -29,4 +28,4 @@ class MemberSearchServiceImpl ( val (entityResults, total) = memberSearchRepository.searchMember(keyword, pageSize, pageNum) return MemberSearchPageResponse.of(entityResults, total, mainImageService::createImageURL) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt index bc75f01f..97e69ee2 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt @@ -17,7 +17,11 @@ interface ProfessorService { fun getProfessor(professorId: Long): ProfessorDto fun getActiveProfessors(): ProfessorPageDto fun getInactiveProfessors(): List - fun updateProfessor(professorId: Long, updateProfessorRequest: ProfessorDto, mainImage: MultipartFile?): ProfessorDto + fun updateProfessor( + professorId: Long, + updateProfessorRequest: ProfessorDto, + mainImage: MultipartFile? + ): ProfessorDto fun deleteProfessor(professorId: Long) fun migrateProfessors(requestList: List): List } @@ -27,7 +31,7 @@ interface ProfessorService { class ProfessorServiceImpl( private val labRepository: LabRepository, private val professorRepository: ProfessorRepository, - private val mainImageService: MainImageService, + private val mainImageService: MainImageService ) : ProfessorService { override fun createProfessor(createProfessorRequest: ProfessorDto, mainImage: MultipartFile?): ProfessorDto { val professor = ProfessorEntity.of(createProfessorRequest) @@ -49,7 +53,7 @@ class ProfessorServiceImpl( CareerEntity.create(career, professor) } - if(mainImage != null) { + if (mainImage != null) { mainImageService.uploadMainImage(professor, mainImage) } @@ -65,7 +69,7 @@ class ProfessorServiceImpl( @Transactional(readOnly = true) override fun getProfessor(professorId: Long): ProfessorDto { val professor = professorRepository.findByIdOrNull(professorId) - ?: throw CserealException.Csereal404("해당 교수님을 찾을 수 없습니다. professorId: ${professorId}") + ?: throw CserealException.Csereal404("해당 교수님을 찾을 수 없습니다. professorId: $professorId") val imageURL = mainImageService.createImageURL(professor.mainImage) @@ -75,11 +79,11 @@ class ProfessorServiceImpl( @Transactional(readOnly = true) override fun getActiveProfessors(): ProfessorPageDto { val description = "컴퓨터공학부는 35명의 훌륭한 교수진과 최신 시설을 갖추고 400여 명의 학부생과 350여 명의 대학원생에게 세계 최고 " + - "수준의 교육 연구 환경을 제공하고 있다. 2005년에는 서울대학교 최초로 외국인 정교수인 Robert Ian McKay 교수를 임용한 것을 " + - "시작으로 교내에서 가장 국제화가 활발하게 이루어지고 있는 학부로 평가받고 있다. 현재 훌륭한 외국인 교수님 두 분이 학부 학생들의 " + - "교육 및 연구 지도에 총력을 기울이고 있다.\n\n다수의 외국인 학부생, 대학원생이 재학 중에 있으며 매 학기 전공 필수 과목을 비롯한 " + - "30% 이상의 과목이 영어로 개설되고 있어 외국인 학생의 학업을 돕는 동시에 한국인 학생이 세계로 진출하는 초석이 되고 있다. 또한 " + - "CSE int’l Luncheon을 개최하여 학부 내 외국인 구성원의 화합과 생활의 불편함을 최소화하는 등 학부 차원에서 최선을 다하고 있다." + "수준의 교육 연구 환경을 제공하고 있다. 2005년에는 서울대학교 최초로 외국인 정교수인 Robert Ian McKay 교수를 임용한 것을 " + + "시작으로 교내에서 가장 국제화가 활발하게 이루어지고 있는 학부로 평가받고 있다. 현재 훌륭한 외국인 교수님 두 분이 학부 학생들의 " + + "교육 및 연구 지도에 총력을 기울이고 있다.\n\n다수의 외국인 학부생, 대학원생이 재학 중에 있으며 매 학기 전공 필수 과목을 비롯한 " + + "30% 이상의 과목이 영어로 개설되고 있어 외국인 학생의 학업을 돕는 동시에 한국인 학생이 세계로 진출하는 초석이 되고 있다. 또한 " + + "CSE int’l Luncheon을 개최하여 학부 내 외국인 구성원의 화합과 생활의 불편함을 최소화하는 등 학부 차원에서 최선을 다하고 있다." val professors = professorRepository.findByStatusNot(ProfessorStatus.INACTIVE).map { val imageURL = mainImageService.createImageURL(it.mainImage) SimpleProfessorDto.of(it, imageURL) @@ -97,9 +101,13 @@ class ProfessorServiceImpl( .sortedBy { it.name } } - override fun updateProfessor(professorId: Long, updateProfessorRequest: ProfessorDto, mainImage: MultipartFile?): ProfessorDto { + override fun updateProfessor( + professorId: Long, + updateProfessorRequest: ProfessorDto, + mainImage: MultipartFile? + ): ProfessorDto { val professor = professorRepository.findByIdOrNull(professorId) - ?: throw CserealException.Csereal404("해당 교수님을 찾을 수 없습니다. professorId: ${professorId}") + ?: throw CserealException.Csereal404("해당 교수님을 찾을 수 없습니다. professorId: $professorId") if (updateProfessorRequest.labId != null && updateProfessorRequest.labId != professor.lab?.id) { val lab = labRepository.findByIdOrNull(updateProfessorRequest.labId) @@ -109,7 +117,7 @@ class ProfessorServiceImpl( professor.update(updateProfessorRequest) - if(mainImage != null) { + if (mainImage != null) { mainImageService.uploadMainImage(professor, mainImage) } else { professor.mainImage = null @@ -195,5 +203,4 @@ class ProfessorServiceImpl( } return list } - } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt index c1d62265..7ee17d23 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/StaffService.kt @@ -26,7 +26,7 @@ interface StaffService { @Transactional class StaffServiceImpl( private val staffRepository: StaffRepository, - private val mainImageService: MainImageService, + private val mainImageService: MainImageService ) : StaffService { override fun createStaff(createStaffRequest: StaffDto, mainImage: MultipartFile?): StaffDto { val staff = StaffEntity.of(createStaffRequest) @@ -35,7 +35,7 @@ class StaffServiceImpl( TaskEntity.create(task, staff) } - if(mainImage != null) { + if (mainImage != null) { mainImageService.uploadMainImage(staff, mainImage) } @@ -51,7 +51,7 @@ class StaffServiceImpl( @Transactional(readOnly = true) override fun getStaff(staffId: Long): StaffDto { val staff = staffRepository.findByIdOrNull(staffId) - ?: throw CserealException.Csereal404("해당 행정직원을 찾을 수 없습니다. staffId: ${staffId}") + ?: throw CserealException.Csereal404("해당 행정직원을 찾을 수 없습니다. staffId: $staffId") val imageURL = mainImageService.createImageURL(staff.mainImage) @@ -68,11 +68,11 @@ class StaffServiceImpl( override fun updateStaff(staffId: Long, updateStaffRequest: StaffDto, mainImage: MultipartFile?): StaffDto { val staff = staffRepository.findByIdOrNull(staffId) - ?: throw CserealException.Csereal404("해당 행정직원을 찾을 수 없습니다. staffId: ${staffId}") + ?: throw CserealException.Csereal404("해당 행정직원을 찾을 수 없습니다. staffId: $staffId") staff.update(updateStaffRequest) - if(mainImage != null) { + if (mainImage != null) { mainImageService.uploadMainImage(staff, mainImage) } else { staff.mainImage = null @@ -120,6 +120,4 @@ class StaffServiceImpl( return list } - - } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt index 79c58a43..977f3216 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt @@ -46,11 +46,14 @@ class NewsController( @GetMapping("/totalSearch") fun searchTotalNews( - @RequestParam(required = true) @Length(min = 1) @NotBlank keyword: String, - @RequestParam(required = true) @Positive number: Int, - @RequestParam(required = false, defaultValue = "200") @Positive stringLength: Int, + @RequestParam(required = true) + @Length(min = 1) + @NotBlank + keyword: String, + @RequestParam(required = true) @Positive number: Int, + @RequestParam(required = false, defaultValue = "200") @Positive stringLength: Int ) = ResponseEntity.ok( - newsService.searchTotalNews(keyword, number, stringLength) + newsService.searchTotalNews(keyword, number, stringLength) ) @GetMapping("/{newsId}") @@ -63,7 +66,9 @@ class NewsController( @AuthenticatedStaff @PostMapping fun createNews( - @Valid @RequestPart("request") request: NewsDto, + @Valid + @RequestPart("request") + request: NewsDto, @RequestPart("mainImage") mainImage: MultipartFile?, @RequestPart("attachments") attachments: List? ): ResponseEntity { @@ -74,9 +79,11 @@ class NewsController( @PatchMapping("/{newsId}") fun updateNews( @PathVariable newsId: Long, - @Valid @RequestPart("request") request: NewsDto, + @Valid + @RequestPart("request") + request: NewsDto, @RequestPart("newMainImage") newMainImage: MultipartFile?, - @RequestPart("newAttachments") newAttachments: List?, + @RequestPart("newAttachments") newAttachments: List? ): ResponseEntity { return ResponseEntity.ok(newsService.updateNews(newsId, request, newMainImage, newAttachments)) } @@ -97,6 +104,4 @@ class NewsController( newsService.enrollTag(tagName["name"]!!) return ResponseEntity("등록되었습니다. (tagName: ${tagName["name"]})", HttpStatus.OK) } - - } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt index 73ab66b2..92bc2d94 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsEntity.kt @@ -53,7 +53,7 @@ class NewsEntity( date = newsDto.date, isPrivate = newsDto.isPrivate, isSlide = newsDto.isSlide, - isImportant = newsDto.isImportant, + isImportant = newsDto.isImportant ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt index f545201c..2648c203 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt @@ -2,10 +2,8 @@ package com.wafflestudio.csereal.core.news.database import com.querydsl.core.BooleanBuilder import com.querydsl.jpa.impl.JPAQueryFactory -import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.common.repository.CommonRepository import com.wafflestudio.csereal.common.utils.FixedPageRequest -import com.wafflestudio.csereal.common.utils.cleanTextFromHtml import com.wafflestudio.csereal.core.news.database.QNewsEntity.newsEntity import com.wafflestudio.csereal.core.news.database.QNewsTagEntity.newsTagEntity import com.wafflestudio.csereal.core.news.database.QTagInNewsEntity.tagInNewsEntity @@ -15,7 +13,6 @@ import com.wafflestudio.csereal.core.news.dto.NewsTotalSearchDto import com.wafflestudio.csereal.core.news.dto.NewsTotalSearchElement import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity import com.wafflestudio.csereal.core.resource.mainImage.database.QMainImageEntity.mainImageEntity -import com.wafflestudio.csereal.core.notice.database.QNoticeEntity import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService import org.springframework.data.domain.Pageable import org.springframework.data.jpa.repository.JpaRepository @@ -24,8 +21,8 @@ import java.time.LocalDateTime interface NewsRepository : JpaRepository, CustomNewsRepository { fun findAllByIsImportant(isImportant: Boolean): List - fun findFirstByCreatedAtLessThanAndIsPrivateFalseOrderByCreatedAtDesc(timestamp: LocalDateTime): NewsEntity? - fun findFirstByCreatedAtGreaterThanAndIsPrivateFalseOrderByCreatedAtAsc(timestamp: LocalDateTime): NewsEntity? + fun findFirstByCreatedAtLessThanOrderByCreatedAtDesc(timestamp: LocalDateTime): NewsEntity? + fun findFirstByCreatedAtGreaterThanOrderByCreatedAtAsc(timestamp: LocalDateTime): NewsEntity? } interface CustomNewsRepository { @@ -36,12 +33,11 @@ interface CustomNewsRepository { usePageBtn: Boolean, isStaff: Boolean ): NewsSearchResponse - fun searchTotalNews( keyword: String, number: Int, amount: Int, - imageUrlCreator: (MainImageEntity?) -> String?, + imageUrlCreator: (MainImageEntity?) -> String? ): NewsTotalSearchDto } @@ -49,7 +45,7 @@ interface CustomNewsRepository { class NewsRepositoryImpl( private val queryFactory: JPAQueryFactory, private val mainImageService: MainImageService, - private val commonRepository: CommonRepository, + private val commonRepository: CommonRepository ) : CustomNewsRepository { override fun searchNews( tag: List?, @@ -66,7 +62,7 @@ class NewsRepositoryImpl( val booleanTemplate = commonRepository.searchFullDoubleTextTemplate( keyword, newsEntity.title, - newsEntity.plainTextDescription, + newsEntity.plainTextDescription ) keywordBooleanBuilder.and(booleanTemplate.gt(0.0)) } @@ -102,7 +98,7 @@ class NewsRepositoryImpl( } val newsEntityList = jpaQuery - .orderBy(newsEntity.date.desc()) + .orderBy(newsEntity.createdAt.desc()) .offset(pageRequest.offset) .limit(pageRequest.pageSize.toLong()) .distinct() @@ -128,12 +124,12 @@ class NewsRepositoryImpl( keyword: String, number: Int, amount: Int, - imageUrlCreator: (MainImageEntity?) -> String?, + imageUrlCreator: (MainImageEntity?) -> String? ): NewsTotalSearchDto { val doubleTemplate = commonRepository.searchFullDoubleTextTemplate( keyword, newsEntity.title, - newsEntity.plainTextDescription, + newsEntity.plainTextDescription ) val searchResult = queryFactory.select( @@ -141,7 +137,7 @@ class NewsRepositoryImpl( newsEntity.title, newsEntity.date, newsEntity.plainTextDescription, - mainImageEntity, + mainImageEntity ).from(newsEntity) .leftJoin(mainImageEntity) .where(doubleTemplate.gt(0.0)) @@ -150,7 +146,7 @@ class NewsRepositoryImpl( val searchResultTags = queryFactory.select( newsTagEntity.news.id, - newsTagEntity.tag.name, + newsTagEntity.tag.name ).from(newsTagEntity) .rightJoin(newsEntity) .leftJoin(tagInNewsEntity) @@ -178,7 +174,7 @@ class NewsRepositoryImpl( imageUrl = imageUrlCreator(it[mainImageEntity]), description = it[newsEntity.plainTextDescription]!!, keyword = keyword, - amount = amount, + amount = amount ) } ) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsTagEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsTagEntity.kt index d328f109..ae91847c 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsTagEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsTagEntity.kt @@ -14,9 +14,9 @@ class NewsTagEntity( @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "tag_id") - var tag: TagInNewsEntity, + var tag: TagInNewsEntity - ) : BaseTimeEntity() { +) : BaseTimeEntity() { companion object { fun createNewsTag(news: NewsEntity, tag: TagInNewsEntity) { @@ -25,4 +25,4 @@ class NewsTagEntity( tag.newsTags.add(newsTag) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsTagRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsTagRepository.kt index 3a947ac1..f563c12d 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsTagRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsTagRepository.kt @@ -5,5 +5,4 @@ import org.springframework.data.jpa.repository.JpaRepository interface NewsTagRepository : JpaRepository { fun deleteAllByNewsId(newsId: Long) fun deleteByNewsIdAndTagId(newsId: Long, tagId: Long) - -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/TagInNewsEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/TagInNewsEntity.kt index 46331249..b44566a8 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/TagInNewsEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/TagInNewsEntity.kt @@ -13,5 +13,4 @@ class TagInNewsEntity( @OneToMany(mappedBy = "tag") val newsTags: MutableSet = mutableSetOf() -) : BaseTimeEntity() { -} \ No newline at end of file +) : BaseTimeEntity() diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/TagInNewsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/TagInNewsRepository.kt index fea69c4d..20fdb6e5 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/TagInNewsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/TagInNewsRepository.kt @@ -4,4 +4,4 @@ import org.springframework.data.jpa.repository.JpaRepository interface TagInNewsRepository : JpaRepository { fun findByName(tagName: TagInNewsEnum): TagInNewsEntity -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt index c913d702..84f1f3e9 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsDto.kt @@ -49,7 +49,7 @@ data class NewsDto( nextId = nextNews?.id, nextTitle = nextNews?.title, imageURL = imageURL, - attachments = attachmentResponses, + attachments = attachmentResponses ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchDto.kt index 5deda4e5..2b95ca7c 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchDto.kt @@ -11,5 +11,5 @@ data class NewsSearchDto @QueryProjection constructor( val date: LocalDateTime?, var tags: List?, var imageURL: String?, - val isPrivate: Boolean, + val isPrivate: Boolean ) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchResponse.kt index 81c530cc..c86ee1f9 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsSearchResponse.kt @@ -1,10 +1,8 @@ package com.wafflestudio.csereal.core.news.dto import com.querydsl.core.annotations.QueryProjection -import java.time.LocalDateTime class NewsSearchResponse @QueryProjection constructor( val total: Long, val searchList: List -) { -} +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsTotalSearchDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsTotalSearchDto.kt index 3b8e9fcf..f43ad3d9 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsTotalSearchDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsTotalSearchDto.kt @@ -1,6 +1,6 @@ package com.wafflestudio.csereal.core.news.dto -data class NewsTotalSearchDto ( - val total: Int, - val results: List -) \ No newline at end of file +data class NewsTotalSearchDto( + val total: Int, + val results: List +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsTotalSearchElement.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsTotalSearchElement.kt index 69c048ce..f6b17471 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsTotalSearchElement.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/dto/NewsTotalSearchElement.kt @@ -4,25 +4,25 @@ import com.wafflestudio.csereal.common.utils.substringAroundKeyword import java.time.LocalDateTime data class NewsTotalSearchElement private constructor( - val id: Long, - val title: String, - val date: LocalDateTime?, - val tags: List, - val imageUrl: String?, + val id: Long, + val title: String, + val date: LocalDateTime?, + val tags: List, + val imageUrl: String? ) { lateinit var partialDescription: String var boldStartIndex: Int = 0 var boldEndIndex: Int = 0 constructor( - id: Long, - title: String, - date: LocalDateTime?, - tags: List, - imageUrl: String?, - description: String, - keyword: String, - amount: Int, + id: Long, + title: String, + date: LocalDateTime?, + tags: List, + imageUrl: String?, + description: String, + keyword: String, + amount: Int ) : this(id, title, date, tags, imageUrl) { val (startIdx, substring) = substringAroundKeyword(keyword, description, amount) partialDescription = substring diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt index 1772c43b..b09cb72d 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt @@ -28,7 +28,7 @@ interface NewsService { newsId: Long, request: NewsDto, newMainImage: MultipartFile?, - newAttachments: List?, + newAttachments: List? ): NewsDto fun deleteNews(newsId: Long) @@ -42,7 +42,7 @@ class NewsServiceImpl( private val tagInNewsRepository: TagInNewsRepository, private val newsTagRepository: NewsTagRepository, private val mainImageService: MainImageService, - private val attachmentService: AttachmentService, + private val attachmentService: AttachmentService ) : NewsService { @Transactional(readOnly = true) override fun searchNews( @@ -59,12 +59,12 @@ class NewsServiceImpl( override fun searchTotalNews( keyword: String, number: Int, - amount: Int, + amount: Int ) = newsRepository.searchTotalNews( keyword, number, amount, - mainImageService::createImageURL, + mainImageService::createImageURL ) @Transactional(readOnly = true) @@ -77,10 +77,8 @@ class NewsServiceImpl( val imageURL = mainImageService.createImageURL(news.mainImage) val attachmentResponses = attachmentService.createAttachmentResponses(news.attachments) - val prevNews = - newsRepository.findFirstByCreatedAtLessThanAndIsPrivateFalseOrderByCreatedAtDesc(news.createdAt!!) - val nextNews = - newsRepository.findFirstByCreatedAtGreaterThanAndIsPrivateFalseOrderByCreatedAtAsc(news.createdAt!!) + val prevNews = newsRepository.findFirstByCreatedAtLessThanOrderByCreatedAtDesc(news.createdAt!!) + val nextNews = newsRepository.findFirstByCreatedAtGreaterThanOrderByCreatedAtAsc(news.createdAt!!) return NewsDto.of(news, imageURL, attachmentResponses, prevNews, nextNews) } @@ -120,7 +118,7 @@ class NewsServiceImpl( newsId: Long, request: NewsDto, newMainImage: MultipartFile?, - newAttachments: List?, + newAttachments: List? ): NewsDto { val news: NewsEntity = newsRepository.findByIdOrNull(newsId) ?: throw CserealException.Csereal404("존재하지 않는 새소식입니다. (newsId: $newsId)") diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt index c461693c..4a99ea45 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt @@ -45,11 +45,14 @@ class NoticeController( @GetMapping("/totalSearch") fun totalSearchNotice( - @RequestParam(required = true) @Length(min = 2) @NotBlank keyword: String, - @RequestParam(required = true) @Positive number: Int, - @RequestParam(required = false, defaultValue = "200") @Positive stringLength: Int, + @RequestParam(required = true) + @Length(min = 2) + @NotBlank + keyword: String, + @RequestParam(required = true) @Positive number: Int, + @RequestParam(required = false, defaultValue = "200") @Positive stringLength: Int ) = ResponseEntity.ok( - noticeService.searchTotalNotice(keyword, number, stringLength) + noticeService.searchTotalNotice(keyword, number, stringLength) ) @GetMapping("/{noticeId}") @@ -62,7 +65,9 @@ class NoticeController( @AuthenticatedStaff @PostMapping fun createNotice( - @Valid @RequestPart("request") request: NoticeDto, + @Valid + @RequestPart("request") + request: NoticeDto, @RequestPart("attachments") attachments: List? ): ResponseEntity { return ResponseEntity.ok(noticeService.createNotice(request, attachments)) @@ -72,7 +77,9 @@ class NoticeController( @PatchMapping("/{noticeId}") fun updateNotice( @PathVariable noticeId: Long, - @Valid @RequestPart("request") request: NoticeDto, + @Valid + @RequestPart("request") + request: NoticeDto, @RequestPart("newAttachments") newAttachments: List? ): ResponseEntity { return ResponseEntity.ok(noticeService.updateNotice(noticeId, request, newAttachments)) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt index 0670b222..cc0b8937 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeEntity.kt @@ -8,7 +8,6 @@ import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEnti import com.wafflestudio.csereal.core.user.database.UserEntity import jakarta.persistence.* - @Entity(name = "notice") class NoticeEntity( var isDeleted: Boolean = false, @@ -35,9 +34,9 @@ class NoticeEntity( val author: UserEntity, @OneToMany(mappedBy = "notice", cascade = [CascadeType.ALL], orphanRemoval = true) - var attachments: MutableList = mutableListOf(), + var attachments: MutableList = mutableListOf() - ) : BaseTimeEntity(), AttachmentContentEntityType { +) : BaseTimeEntity(), AttachmentContentEntityType { override fun bringAttachments() = attachments fun update(updateNoticeRequest: NoticeDto) { @@ -53,5 +52,4 @@ class NoticeEntity( this.isPinned = updateNoticeRequest.isPinned this.isImportant = updateNoticeRequest.isImportant } - } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt index a0b9769b..2a27f3bb 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt @@ -18,8 +18,8 @@ import java.time.LocalDateTime interface NoticeRepository : JpaRepository, CustomNoticeRepository { fun findAllByIsImportant(isImportant: Boolean): List - fun findFirstByCreatedAtLessThanAndIsPrivateFalseOrderByCreatedAtDesc(timestamp: LocalDateTime): NoticeEntity? - fun findFirstByCreatedAtGreaterThanAndIsPrivateFalseOrderByCreatedAtAsc(timestamp: LocalDateTime): NoticeEntity? + fun findFirstByCreatedAtLessThanOrderByCreatedAtDesc(timestamp: LocalDateTime): NoticeEntity? + fun findFirstByCreatedAtGreaterThanOrderByCreatedAtAsc(timestamp: LocalDateTime): NoticeEntity? } interface CustomNoticeRepository { @@ -37,12 +37,12 @@ interface CustomNoticeRepository { @Component class NoticeRepositoryImpl( private val queryFactory: JPAQueryFactory, - private val commonRepository: CommonRepository, + private val commonRepository: CommonRepository ) : CustomNoticeRepository { override fun totalSearchNotice( keyword: String, number: Int, - stringLength: Int, + stringLength: Int ): NoticeTotalSearchResponse { val doubleTemplate = commonRepository.searchFullDoubleTextTemplate( keyword, @@ -71,7 +71,7 @@ class NoticeRepositoryImpl( it[noticeEntity.createdAt]!!, it[noticeEntity.plainTextDescription]!!, keyword, - stringLength, + stringLength ) } ) @@ -92,7 +92,7 @@ class NoticeRepositoryImpl( val booleanTemplate = commonRepository.searchFullDoubleTextTemplate( keyword, noticeEntity.title, - noticeEntity.plainTextDescription, + noticeEntity.plainTextDescription ) keywordBooleanBuilder.and(booleanTemplate.gt(0.0)) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeTagEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeTagEntity.kt index 34ba5df3..dd8b972b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeTagEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeTagEntity.kt @@ -11,9 +11,9 @@ class NoticeTagEntity( @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "tag_id") - var tag: TagInNoticeEntity, + var tag: TagInNoticeEntity - ) : BaseTimeEntity() { +) : BaseTimeEntity() { companion object { fun createNoticeTag(notice: NoticeEntity, tag: TagInNoticeEntity) { @@ -22,8 +22,4 @@ class NoticeTagEntity( tag.noticeTags.add(noticeTag) } } - - - } - diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeTagRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeTagRepository.kt index fc5e3f6b..971970c4 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeTagRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeTagRepository.kt @@ -5,4 +5,4 @@ import org.springframework.data.jpa.repository.JpaRepository interface NoticeTagRepository : JpaRepository { fun deleteAllByNoticeId(noticeId: Long) fun deleteByNoticeIdAndTagId(noticeId: Long, tagId: Long) -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeEntity.kt index 811f38e7..35f4b09c 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeEntity.kt @@ -13,5 +13,4 @@ class TagInNoticeEntity( @OneToMany(mappedBy = "tag") val noticeTags: MutableSet = mutableSetOf() -) : BaseTimeEntity() { -} \ No newline at end of file +) : BaseTimeEntity() diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeEnum.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeEnum.kt index d94f0faa..35576755 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeEnum.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeEnum.kt @@ -15,9 +15,4 @@ enum class TagInNoticeEnum(val krName: String) { return lookupMap[t] ?: throw CserealException.Csereal404("태그를 찾을 수 없습니다: $t") } } - - } - - - diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeRepository.kt index baefb68e..aff7b600 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/TagInNoticeRepository.kt @@ -4,4 +4,4 @@ import org.springframework.data.jpa.repository.JpaRepository interface TagInNoticeRepository : JpaRepository { fun findByName(tagName: TagInNoticeEnum): TagInNoticeEntity -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt index e255660d..e786a065 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeDto.kt @@ -1,7 +1,6 @@ package com.wafflestudio.csereal.core.notice.dto import com.wafflestudio.csereal.core.notice.database.NoticeEntity -import com.wafflestudio.csereal.core.notice.database.TagInNoticeEnum import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse import java.time.LocalDateTime @@ -48,10 +47,8 @@ data class NoticeDto( prevTitle = prevNotice?.title, nextId = nextNotice?.id, nextTitle = nextNotice?.title, - attachments = attachmentResponses, + attachments = attachmentResponses ) } - } - } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeIdListRequest.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeIdListRequest.kt index b84f7947..491c7f79 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeIdListRequest.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeIdListRequest.kt @@ -2,6 +2,4 @@ package com.wafflestudio.csereal.core.notice.dto data class NoticeIdListRequest( val idList: List -) { - -} \ No newline at end of file +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeSearchDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeSearchDto.kt index fb819cba..94226bfc 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeSearchDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeSearchDto.kt @@ -1,6 +1,5 @@ package com.wafflestudio.csereal.core.notice.dto -import com.fasterxml.jackson.annotation.JsonProperty import com.querydsl.core.annotations.QueryProjection import com.wafflestudio.csereal.core.notice.database.NoticeEntity import java.time.LocalDateTime @@ -11,7 +10,7 @@ data class NoticeSearchDto @QueryProjection constructor( val createdAt: LocalDateTime?, val isPinned: Boolean, val hasAttachment: Boolean, - val isPrivate: Boolean, + val isPrivate: Boolean ) { constructor(entity: NoticeEntity, hasAttachment: Boolean) : this( entity.id, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeSearchResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeSearchResponse.kt index c4fdec97..efecd2be 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeSearchResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeSearchResponse.kt @@ -3,6 +3,4 @@ package com.wafflestudio.csereal.core.notice.dto data class NoticeSearchResponse( val total: Long, val searchList: List -) { - -} +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeTotalSearchElement.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeTotalSearchElement.kt index bb9869bf..07a44715 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeTotalSearchElement.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeTotalSearchElement.kt @@ -4,22 +4,22 @@ import com.wafflestudio.csereal.common.utils.substringAroundKeyword import java.time.LocalDateTime data class NoticeTotalSearchElement private constructor( - val id: Long, - val title: String, - val createdAt: LocalDateTime, + val id: Long, + val title: String, + val createdAt: LocalDateTime ) { lateinit var partialDescription: String var boldStartIndex: Int = 0 var boldEndIndex: Int = 0 constructor( - id: Long, - title: String, - createdAt: LocalDateTime, - description: String, - keyword: String, - amount: Int, - ): this(id, title, createdAt) { + id: Long, + title: String, + createdAt: LocalDateTime, + description: String, + keyword: String, + amount: Int + ) : this(id, title, createdAt) { val (startIdx, partialDescription) = substringAroundKeyword(keyword, description, amount) this.boldStartIndex = startIdx ?: 0 this.boldEndIndex = startIdx ?. let { it + keyword.length } ?: 0 diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeTotalSearchResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeTotalSearchResponse.kt index 7afcfa82..80437f8f 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeTotalSearchResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/dto/NoticeTotalSearchResponse.kt @@ -1,6 +1,6 @@ package com.wafflestudio.csereal.core.notice.dto -data class NoticeTotalSearchResponse ( - val total: Int, - val results: List -) \ No newline at end of file +data class NoticeTotalSearchResponse( + val total: Int, + val results: List +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt index 26f711ae..59b62670 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt @@ -6,11 +6,8 @@ import com.wafflestudio.csereal.core.notice.database.* import com.wafflestudio.csereal.core.notice.dto.* import com.wafflestudio.csereal.core.resource.attachment.service.AttachmentService import com.wafflestudio.csereal.core.user.database.UserEntity -import com.wafflestudio.csereal.core.user.database.UserRepository import org.springframework.data.domain.Pageable import org.springframework.data.repository.findByIdOrNull -import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.security.oauth2.core.oidc.user.OidcUser import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import org.springframework.web.context.request.RequestAttributes @@ -33,7 +30,7 @@ interface NoticeService { fun updateNotice( noticeId: Long, request: NoticeDto, - newAttachments: List?, + newAttachments: List? ): NoticeDto fun deleteNotice(noticeId: Long) @@ -47,7 +44,7 @@ class NoticeServiceImpl( private val noticeRepository: NoticeRepository, private val tagInNoticeRepository: TagInNoticeRepository, private val noticeTagRepository: NoticeTagRepository, - private val attachmentService: AttachmentService, + private val attachmentService: AttachmentService ) : NoticeService { @Transactional(readOnly = true) @@ -65,7 +62,7 @@ class NoticeServiceImpl( override fun searchTotalNotice( keyword: String, number: Int, - stringLength: Int, + stringLength: Int ) = noticeRepository.totalSearchNotice(keyword, number, stringLength) @Transactional(readOnly = true) @@ -78,9 +75,9 @@ class NoticeServiceImpl( val attachmentResponses = attachmentService.createAttachmentResponses(notice.attachments) val prevNotice = - noticeRepository.findFirstByCreatedAtLessThanAndIsPrivateFalseOrderByCreatedAtDesc(notice.createdAt!!) + noticeRepository.findFirstByCreatedAtLessThanOrderByCreatedAtDesc(notice.createdAt!!) val nextNotice = - noticeRepository.findFirstByCreatedAtGreaterThanAndIsPrivateFalseOrderByCreatedAtAsc(notice.createdAt!!) + noticeRepository.findFirstByCreatedAtGreaterThanOrderByCreatedAtAsc(notice.createdAt!!) return NoticeDto.of(notice, attachmentResponses, prevNotice, nextNotice) } @@ -122,7 +119,6 @@ class NoticeServiceImpl( val attachmentResponses = attachmentService.createAttachmentResponses(newNotice.attachments) return NoticeDto.of(newNotice, attachmentResponses) - } @Transactional @@ -170,7 +166,6 @@ class NoticeServiceImpl( ?: throw CserealException.Csereal404("존재하지 않는 공지사항입니다.(noticeId: $noticeId)") notice.isDeleted = true - } @Transactional @@ -197,6 +192,4 @@ class NoticeServiceImpl( ) tagInNoticeRepository.save(newTag) } - - } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/recruit/api/RecruitController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/recruit/api/RecruitController.kt index 68b2a35b..f4704f64 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/recruit/api/RecruitController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/recruit/api/RecruitController.kt @@ -17,5 +17,4 @@ class RecruitController( fun getRecruitPage(): ResponseEntity { return ResponseEntity.ok(recruitService.getRecruitPage()) } - } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/recruit/database/RecruitRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/recruit/database/RecruitRepository.kt index 795ef256..907e1c9c 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/recruit/database/RecruitRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/recruit/database/RecruitRepository.kt @@ -2,5 +2,4 @@ package com.wafflestudio.csereal.core.recruit.database import org.springframework.data.jpa.repository.JpaRepository -interface RecruitRepository : JpaRepository { -} +interface RecruitRepository : JpaRepository diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/recruit/dto/RecruitPage.kt b/src/main/kotlin/com/wafflestudio/csereal/core/recruit/dto/RecruitPage.kt index ca631293..fe3c42df 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/recruit/dto/RecruitPage.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/recruit/dto/RecruitPage.kt @@ -2,7 +2,6 @@ package com.wafflestudio.csereal.core.recruit.dto import com.wafflestudio.csereal.core.recruit.database.RecruitEntity - data class RecruitPage( val latestRecruitTitle: String, val latestRecruitUrl: String, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt index bcd16eb8..55832b73 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt @@ -19,7 +19,9 @@ class ResearchController( @AuthenticatedStaff @PostMapping fun createResearchDetail( - @Valid @RequestPart("request") request: ResearchDto, + @Valid + @RequestPart("request") + request: ResearchDto, @RequestPart("mainImage") mainImage: MultipartFile?, @RequestPart("attachments") attachments: List? ): ResponseEntity { @@ -40,7 +42,9 @@ class ResearchController( @PatchMapping("/{researchId}") fun updateResearchDetail( @PathVariable researchId: Long, - @Valid @RequestPart("request") request: ResearchDto, + @Valid + @RequestPart("request") + request: ResearchDto, @RequestPart("mainImage") mainImage: MultipartFile?, @RequestPart("attachments") attachments: List? ): ResponseEntity { @@ -50,7 +54,9 @@ class ResearchController( @AuthenticatedStaff @PostMapping("/lab") fun createLab( - @Valid @RequestPart("request") request: LabDto, + @Valid + @RequestPart("request") + request: LabDto, @RequestPart("pdf") pdf: MultipartFile? ): ResponseEntity { return ResponseEntity.ok(researchService.createLab(request, pdf)) @@ -63,7 +69,7 @@ class ResearchController( @GetMapping("/lab/{labId}") fun readLab( - @PathVariable labId: Long, + @PathVariable labId: Long ): ResponseEntity { return ResponseEntity.ok(researchService.readLab(labId)) } @@ -74,9 +80,11 @@ class ResearchController( @AuthenticatedStaff @PatchMapping("/lab/{labId}") fun updateLab( - @PathVariable labId: Long, - @Valid @RequestPart("request") request: LabUpdateRequest, - @RequestPart("pdf") pdf: MultipartFile? + @PathVariable labId: Long, + @Valid + @RequestPart("request") + request: LabUpdateRequest, + @RequestPart("pdf") pdf: MultipartFile? ): ResponseEntity { return ResponseEntity.ok(researchService.updateLab(labId, request, pdf)) } @@ -87,6 +95,7 @@ class ResearchController( ): ResponseEntity> { return ResponseEntity.ok(researchService.migrateResearchDetail(requestList)) } + @PostMapping("/lab/migrate") fun migrateLabs( @RequestBody requestList: List diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabEntity.kt index c897e9ca..78d59a55 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabEntity.kt @@ -1,7 +1,6 @@ package com.wafflestudio.csereal.core.research.database import com.wafflestudio.csereal.common.config.BaseTimeEntity -import com.wafflestudio.csereal.common.controller.AttachmentContentEntityType import com.wafflestudio.csereal.core.member.database.ProfessorEntity import com.wafflestudio.csereal.core.research.dto.LabDto import com.wafflestudio.csereal.core.research.dto.LabUpdateRequest @@ -33,11 +32,11 @@ class LabEntity( var websiteURL: String?, @OneToOne(mappedBy = "lab", cascade = [CascadeType.ALL], orphanRemoval = true) - var researchSearch: ResearchSearchEntity? = null, + var researchSearch: ResearchSearchEntity? = null ) : BaseTimeEntity() { companion object { - fun of(labDto: LabDto, researchGroup: ResearchEntity) : LabEntity { + fun of(labDto: LabDto, researchGroup: ResearchEntity): LabEntity { return LabEntity( name = labDto.name, location = labDto.location, @@ -46,7 +45,7 @@ class LabEntity( youtube = labDto.youtube, research = researchGroup, description = labDto.description, - websiteURL = labDto.websiteURL, + websiteURL = labDto.websiteURL ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchEntity.kt index facf4a9b..20997609 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchEntity.kt @@ -28,8 +28,8 @@ class ResearchEntity( var attachments: MutableList = mutableListOf(), @OneToOne(mappedBy = "research", cascade = [CascadeType.ALL], orphanRemoval = true) - var researchSearch: ResearchSearchEntity? = null, - ) : BaseTimeEntity(), MainImageContentEntityType, AttachmentContentEntityType { + var researchSearch: ResearchSearchEntity? = null +) : BaseTimeEntity(), MainImageContentEntityType, AttachmentContentEntityType { override fun bringMainImage() = mainImage override fun bringAttachments() = attachments @@ -38,7 +38,7 @@ class ResearchEntity( return ResearchEntity( postType = researchDto.postType, name = researchDto.name, - description = researchDto.description, + description = researchDto.description ) } } @@ -48,5 +48,4 @@ class ResearchEntity( this.name = researchDto.name this.description = researchDto.description } - -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchPostType.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchPostType.kt index d2839103..7d651dd8 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchPostType.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchPostType.kt @@ -1,9 +1,9 @@ package com.wafflestudio.csereal.core.research.database -enum class ResearchPostType ( - val krName: String, +enum class ResearchPostType( + val krName: String ) { GROUPS("연구 그룹"), CENTERS("연구 센터"), LABS("연구실 목록"); -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchRepository.kt index e9a9fd8a..a91ac5af 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchRepository.kt @@ -5,4 +5,4 @@ import org.springframework.data.jpa.repository.JpaRepository interface ResearchRepository : JpaRepository { fun findByName(name: String): ResearchEntity? fun findAllByPostTypeOrderByName(postType: ResearchPostType): List -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchSearchEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchSearchEntity.kt index bf35f813..bb3f75ad 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchSearchEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchSearchEntity.kt @@ -5,106 +5,109 @@ import com.wafflestudio.csereal.core.conference.database.ConferenceEntity import jakarta.persistence.* @Entity(name = "research_search") -class ResearchSearchEntity ( - @Column(columnDefinition = "TEXT") - var content: String, - - @OneToOne - @JoinColumn(name = "research_id") - val research: ResearchEntity? = null, - - @OneToOne - @JoinColumn(name = "lab_id") - val lab: LabEntity? = null, - - @OneToOne - @JoinColumn(name = "conference_id") - val conferenceElement: ConferenceEntity? = null, -): BaseTimeEntity() { - companion object { - fun create(research: ResearchEntity): ResearchSearchEntity { - return ResearchSearchEntity( - content = createContent(research), - research = research, - ) - } - - fun create(lab: LabEntity): ResearchSearchEntity { - return ResearchSearchEntity( - content = createContent(lab), - lab = lab, - ) - } - - fun create(conference: ConferenceEntity): ResearchSearchEntity { - return ResearchSearchEntity( - content = createContent(conference), - conferenceElement = conference, - ) - } - - fun createContent(research: ResearchEntity) = StringBuilder().apply { - appendLine(research.name) - appendLine(research.postType.krName) - research.description?.let { appendLine(it) } - research.labs.forEach { appendLine(it.name) } - }.toString() - - fun createContent(lab: LabEntity) = StringBuilder().apply { - appendLine(lab.name) - lab.professors.forEach { appendLine(it.name) } - lab.location?.let { appendLine(it) } - lab.tel?.let { appendLine(it) } - lab.acronym?.let { appendLine(it) } - lab.youtube?.let { appendLine(it) } - appendLine(lab.research.name) - lab.description?.let { appendLine(it) } - lab.websiteURL?.let { appendLine(it) } - }.toString() - - fun createContent(conference: ConferenceEntity) = StringBuilder().apply { - appendLine(conference.name) - appendLine(conference.code) - appendLine(conference.abbreviation) - }.toString() +class ResearchSearchEntity( + @Column(columnDefinition = "TEXT") + var content: String, + + @OneToOne + @JoinColumn(name = "research_id") + val research: ResearchEntity? = null, + + @OneToOne + @JoinColumn(name = "lab_id") + val lab: LabEntity? = null, + + @OneToOne + @JoinColumn(name = "conference_id") + val conferenceElement: ConferenceEntity? = null +) : BaseTimeEntity() { + companion object { + fun create(research: ResearchEntity): ResearchSearchEntity { + return ResearchSearchEntity( + content = createContent(research), + research = research + ) } - @PrePersist - @PreUpdate - fun checkType() { - if (!( - (research != null && lab == null && conferenceElement == null) || - (research == null && lab != null && conferenceElement == null) || - (research == null && lab == null && conferenceElement != null) - )) { - throw RuntimeException("ResearchSearchEntity must have either research or lab or conference") - } + fun create(lab: LabEntity): ResearchSearchEntity { + return ResearchSearchEntity( + content = createContent(lab), + lab = lab + ) } - fun ofType(): ResearchSearchType { - return when { - research != null && lab == null && conferenceElement == null -> ResearchSearchType.RESEARCH - research == null && lab != null && conferenceElement == null -> ResearchSearchType.LAB - research == null && lab == null && conferenceElement != null -> ResearchSearchType.CONFERENCE - else -> throw RuntimeException("ResearchSearchEntity must have either research or lab or conference") - } + fun create(conference: ConferenceEntity): ResearchSearchEntity { + return ResearchSearchEntity( + content = createContent(conference), + conferenceElement = conference + ) } - fun update(research: ResearchEntity) { - this.content = createContent(research) + fun createContent(research: ResearchEntity) = StringBuilder().apply { + appendLine(research.name) + appendLine(research.postType.krName) + research.description?.let { appendLine(it) } + research.labs.forEach { appendLine(it.name) } + }.toString() + + fun createContent(lab: LabEntity) = StringBuilder().apply { + appendLine(lab.name) + lab.professors.forEach { appendLine(it.name) } + lab.location?.let { appendLine(it) } + lab.tel?.let { appendLine(it) } + lab.acronym?.let { appendLine(it) } + lab.youtube?.let { appendLine(it) } + appendLine(lab.research.name) + lab.description?.let { appendLine(it) } + lab.websiteURL?.let { appendLine(it) } + }.toString() + + fun createContent(conference: ConferenceEntity) = StringBuilder().apply { + appendLine(conference.name) + appendLine(conference.code) + appendLine(conference.abbreviation) + }.toString() + } + + @PrePersist + @PreUpdate + fun checkType() { + if (!( + (research != null && lab == null && conferenceElement == null) || + (research == null && lab != null && conferenceElement == null) || + (research == null && lab == null && conferenceElement != null) + ) + ) { + throw RuntimeException("ResearchSearchEntity must have either research or lab or conference") } - - fun update(lab: LabEntity) { - this.content = createContent(lab) + } + + fun ofType(): ResearchSearchType { + return when { + research != null && lab == null && conferenceElement == null -> ResearchSearchType.RESEARCH + research == null && lab != null && conferenceElement == null -> ResearchSearchType.LAB + research == null && lab == null && conferenceElement != null -> ResearchSearchType.CONFERENCE + else -> throw RuntimeException( + "ResearchSearchEntity must have either research or lab or conference" + ) } + } - fun update(conference: ConferenceEntity) { - this.content = createContent(conference) - } + fun update(research: ResearchEntity) { + this.content = createContent(research) + } + + fun update(lab: LabEntity) { + this.content = createContent(lab) + } + + fun update(conference: ConferenceEntity) { + this.content = createContent(conference) + } } enum class ResearchSearchType { - RESEARCH, - LAB, - CONFERENCE; -} \ No newline at end of file + RESEARCH, + LAB, + CONFERENCE; +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchSearchRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchSearchRepository.kt index c005d5e0..e9bf51d0 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchSearchRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchSearchRepository.kt @@ -4,14 +4,11 @@ import com.querydsl.jpa.impl.JPAQueryFactory import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository -interface ResearchSearchRepository: JpaRepository, ResearchSearchRepositoryCustom { -} +interface ResearchSearchRepository : JpaRepository, ResearchSearchRepositoryCustom -interface ResearchSearchRepositoryCustom { -} +interface ResearchSearchRepositoryCustom @Repository -class ResearchSearchRepositoryCustomImpl ( - private val jpaQueryFactory: JPAQueryFactory -): ResearchSearchRepositoryCustom { -} \ No newline at end of file +class ResearchSearchRepositoryCustomImpl( + private val jpaQueryFactory: JPAQueryFactory +) : ResearchSearchRepositoryCustom diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/LabDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/LabDto.kt index 55e19ea0..9060bd3f 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/LabDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/LabDto.kt @@ -13,7 +13,7 @@ data class LabDto( val youtube: String?, val group: String, val description: String?, - val websiteURL: String?, + val websiteURL: String? ) { companion object { fun of(entity: LabEntity, pdfURL: String): LabDto = entity.run { @@ -28,8 +28,8 @@ data class LabDto( youtube = this.youtube, group = this.research.name, description = this.description, - websiteURL = this.websiteURL, + websiteURL = this.websiteURL ) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/LabProfessorResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/LabProfessorResponse.kt index 9ebbdd0a..3e6e76bd 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/LabProfessorResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/LabProfessorResponse.kt @@ -2,6 +2,5 @@ package com.wafflestudio.csereal.core.research.dto data class LabProfessorResponse( val id: Long, - val name: String, -) { -} \ No newline at end of file + val name: String +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/LabUpdateRequest.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/LabUpdateRequest.kt index 51d59387..3994367b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/LabUpdateRequest.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/LabUpdateRequest.kt @@ -1,13 +1,13 @@ package com.wafflestudio.csereal.core.research.dto data class LabUpdateRequest( - val name: String, - val professorIds: List, - val location: String?, - val tel: String?, - val acronym: String?, - val youtube: String?, - val description: String?, - val websiteURL: String?, - val pdfModified: Boolean, + val name: String, + val professorIds: List, + val location: String?, + val tel: String?, + val acronym: String?, + val youtube: String?, + val description: String?, + val websiteURL: String?, + val pdfModified: Boolean ) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchDto.kt index 0f694e81..c1190f39 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchDto.kt @@ -14,7 +14,7 @@ data class ResearchDto( val modifiedAt: LocalDateTime?, val labs: List?, val imageURL: String?, - val attachments: List?, + val attachments: List? ) { companion object { fun of(entity: ResearchEntity, imageURL: String?, attachmentResponse: List) = entity.run { @@ -31,4 +31,4 @@ data class ResearchDto( ) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchGroupResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchGroupResponse.kt index 6bd08a2c..f4450d48 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchGroupResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchGroupResponse.kt @@ -3,5 +3,4 @@ package com.wafflestudio.csereal.core.research.dto data class ResearchGroupResponse( val description: String, val groups: List -) { -} \ No newline at end of file +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchLabResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchLabResponse.kt index 2761c2cb..c6e9c904 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchLabResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchLabResponse.kt @@ -2,6 +2,5 @@ package com.wafflestudio.csereal.core.research.dto data class ResearchLabResponse( val id: Long, - val name: String, -) { -} \ No newline at end of file + val name: String +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchSearchService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchSearchService.kt index 6c589929..7749331f 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchSearchService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchSearchService.kt @@ -10,14 +10,14 @@ interface ResearchSearchService { } @Service -class ResearchSearchServiceImpl ( - private val researchSearchRepository: ResearchSearchRepository, +class ResearchSearchServiceImpl( + private val researchSearchRepository: ResearchSearchRepository ) : ResearchSearchService { @Transactional override fun deleteResearchSearch( - researchSearchEntity: ResearchSearchEntity + researchSearchEntity: ResearchSearchEntity ) { researchSearchRepository.delete(researchSearchEntity) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt index 4dd75aa2..48b92497 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt @@ -3,7 +3,6 @@ package com.wafflestudio.csereal.core.research.service import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.common.properties.EndpointProperties import com.wafflestudio.csereal.core.member.database.ProfessorRepository -import com.wafflestudio.csereal.core.member.service.ProfessorService import com.wafflestudio.csereal.core.research.database.* import com.wafflestudio.csereal.core.research.dto.* import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity @@ -45,7 +44,7 @@ class ResearchServiceImpl( private val professorRepository: ProfessorRepository, private val mainImageService: MainImageService, private val attachmentService: AttachmentService, - private val endpointProperties: EndpointProperties, + private val endpointProperties: EndpointProperties ) : ResearchService { @Transactional override fun createResearchDetail( @@ -56,7 +55,6 @@ class ResearchServiceImpl( val newResearch = ResearchEntity.of(request) if (request.labs != null) { - for (lab in request.labs) { val labEntity = labRepository.findByIdOrNull(lab.id) ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다.(labId=${lab.id})") @@ -87,9 +85,9 @@ class ResearchServiceImpl( override fun readAllResearchGroups(): ResearchGroupResponse { // Todo: description 수정 필요 val description = "세계가 주목하는 컴퓨터공학부의 많은 교수들은 ACM, IEEE 등 " + - "세계적인 컴퓨터관련 주요 학회에서 국제학술지 편집위원, 국제학술회의 위원장, 기조연설자 등으로 활발하게 활동하고 있습니다. " + - "정부 지원과제, 민간 산업체 지원 연구과제 등도 성공적으로 수행, 우수한 성과들을 내놓고 있으며, " + - "오늘도 인류가 꿈꾸는 행복하고 편리한 세상을 위해 변화와 혁신, 연구와 도전을 계속하고 있습니다." + "세계적인 컴퓨터관련 주요 학회에서 국제학술지 편집위원, 국제학술회의 위원장, 기조연설자 등으로 활발하게 활동하고 있습니다. " + + "정부 지원과제, 민간 산업체 지원 연구과제 등도 성공적으로 수행, 우수한 성과들을 내놓고 있으며, " + + "오늘도 인류가 꿈꾸는 행복하고 편리한 세상을 위해 변화와 혁신, 연구와 도전을 계속하고 있습니다." val researchGroups = researchRepository.findAllByPostTypeOrderByName(ResearchPostType.GROUPS).map { val imageURL = mainImageService.createImageURL(it.mainImage) @@ -127,7 +125,6 @@ class ResearchServiceImpl( for (lab in request.labs) { val labEntity = labRepository.findByIdOrNull(lab.id) ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다.(labId=${lab.id})") - } val oldLabs = research.labs.map { it.id } @@ -141,7 +138,6 @@ class ResearchServiceImpl( val lab = labRepository.findByIdOrNull(labsToAddId)!! research.labs.add(lab) lab.research = research - } } @@ -306,7 +302,6 @@ class ResearchServiceImpl( override fun migrateLabs(requestList: List): List { val list = mutableListOf() for (request in requestList) { - val researchGroup = researchRepository.findByName(request.group) ?: throw CserealException.Csereal404("해당 연구그룹을 찾을 수 없습니다.(researchGroupName = ${request.group})") @@ -314,7 +309,6 @@ class ResearchServiceImpl( throw CserealException.Csereal404("해당 게시글은 연구그룹이어야 합니다.") } - val newLab = LabEntity.of(request, researchGroup) newLab.researchSearch = ResearchSearchEntity.create(newLab) @@ -325,5 +319,4 @@ class ResearchServiceImpl( } return list } - } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservceController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservationController.kt similarity index 94% rename from src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservceController.kt rename to src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservationController.kt index 40e929e9..7bc843c1 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservceController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservationController.kt @@ -23,8 +23,8 @@ class ReservationController( private val reservationService: ReservationService ) { + // @AuthenticatedForReservation TODO: CBT 끝나면 주석 제거 @GetMapping("/month") -// @AuthenticatedForReservation TODO: CBT 끝나면 주석 제거 fun getMonthlyReservations( @RequestParam roomId: Long, @RequestParam year: Int, @@ -35,21 +35,21 @@ class ReservationController( return ResponseEntity.ok(reservationService.getRoomReservationsBetween(roomId, start, end)) } + // @AuthenticatedForReservation @GetMapping("/week") -// @AuthenticatedForReservation fun getWeeklyReservations( @RequestParam roomId: Long, @RequestParam year: Int, @RequestParam month: Int, - @RequestParam day: Int, + @RequestParam day: Int ): ResponseEntity> { val start = LocalDateTime.of(year, month, day, 0, 0) val end = start.plusDays(7) return ResponseEntity.ok(reservationService.getRoomReservationsBetween(roomId, start, end)) } + // @AuthenticatedForReservation @GetMapping("/{reservationId}") -// @AuthenticatedForReservation fun getReservation(@PathVariable reservationId: Long): ResponseEntity { return ResponseEntity.ok(reservationService.getReservation(reservationId)) } @@ -73,5 +73,4 @@ class ReservationController( fun cancelRecurring(@PathVariable recurrenceId: UUID): ResponseEntity { return ResponseEntity.ok(reservationService.cancelRecurring(recurrenceId)) } - } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/ReservationEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/ReservationEntity.kt index 32bdf87d..2b08ae95 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/ReservationEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/ReservationEntity.kt @@ -69,5 +69,4 @@ class ReservationEntity( ) } } - } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/ReservationRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/ReservationRepository.kt index dd750501..2abad21c 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/ReservationRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/ReservationRepository.kt @@ -10,7 +10,15 @@ import java.util.UUID interface ReservationRepository : JpaRepository { @Lock(LockModeType.PESSIMISTIC_WRITE) - @Query("SELECT r FROM reservation r WHERE r.room.id = :roomId AND ((:start <= r.startTime AND r.startTime < :end) OR (:start < r.endTime AND r.endTime <= :end) OR (r.startTime <= :start AND r.endTime >= :end))") + @Query( + "SELECT r FROM reservation r " + + "WHERE r.room.id = :roomId " + + "AND ((:start <= r.startTime " + + "AND r.startTime < :end) " + + "OR (:start < r.endTime " + + "AND r.endTime <= :end) " + + "OR (r.startTime <= :start AND r.endTime >= :end))" + ) fun findByRoomIdAndTimeOverlap(roomId: Long, start: LocalDateTime, end: LocalDateTime): List fun findByRoomIdAndStartTimeBetweenOrderByStartTimeAsc( @@ -20,5 +28,4 @@ interface ReservationRepository : JpaRepository { ): List fun deleteAllByRecurrenceId(recurrenceId: UUID) - } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/RoomRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/RoomRepository.kt index c7c9b562..f375afaf 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/RoomRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/RoomRepository.kt @@ -2,5 +2,4 @@ package com.wafflestudio.csereal.core.reservation.database import org.springframework.data.jpa.repository.JpaRepository -interface RoomRepository : JpaRepository { -} +interface RoomRepository : JpaRepository diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReservationDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReservationDto.kt index cec83d85..39d22019 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReservationDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReservationDto.kt @@ -51,6 +51,5 @@ data class ReservationDto( professor = reservationEntity.professor ) } - } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/service/ReservationService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/service/ReservationService.kt index 4dcd0da8..59b4ce35 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/service/ReservationService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/service/ReservationService.kt @@ -5,7 +5,6 @@ import com.wafflestudio.csereal.core.reservation.database.* import com.wafflestudio.csereal.core.reservation.dto.ReservationDto import com.wafflestudio.csereal.core.reservation.dto.ReserveRequest import com.wafflestudio.csereal.core.reservation.dto.SimpleReservationDto -import com.wafflestudio.csereal.core.user.database.Role import com.wafflestudio.csereal.core.user.database.UserEntity import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service @@ -102,5 +101,4 @@ class ReservationServiceImpl( override fun cancelRecurring(recurrenceId: UUID) { reservationRepository.deleteAllByRecurrenceId(recurrenceId) } - } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentEntity.kt index befcb2d6..567e0462 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentEntity.kt @@ -14,7 +14,7 @@ import jakarta.persistence.* @Entity(name = "attachment") class AttachmentEntity( - var isDeleted : Boolean? = false, + var isDeleted: Boolean? = false, @Column(unique = true) val filename: String, @@ -56,7 +56,5 @@ class AttachmentEntity( @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "scholarship_id") - var scholarship: ScholarshipEntity? = null, -) : BaseTimeEntity() { - -} \ No newline at end of file + var scholarship: ScholarshipEntity? = null +) : BaseTimeEntity() diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentRepository.kt index a90f9a30..e91ad398 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentRepository.kt @@ -4,6 +4,6 @@ import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository @Repository -interface AttachmentRepository: JpaRepository { +interface AttachmentRepository : JpaRepository { fun findByFilename(filename: String): AttachmentEntity -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/dto/AttachmentDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/dto/AttachmentDto.kt index 1d07c47f..ff745d08 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/dto/AttachmentDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/dto/AttachmentDto.kt @@ -3,6 +3,5 @@ package com.wafflestudio.csereal.core.resource.attachment.dto data class AttachmentDto( val filename: String, val attachmentsOrder: Int, - val size: Long, -) { -} \ No newline at end of file + val size: Long +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/dto/AttachmentResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/dto/AttachmentResponse.kt index c3e2abc8..01275915 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/dto/AttachmentResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/dto/AttachmentResponse.kt @@ -4,6 +4,5 @@ data class AttachmentResponse( val id: Long, val name: String, val url: String, - val bytes: Long, -) { -} + val bytes: Long +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt index a49438e0..4a1f64cf 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt @@ -15,7 +15,6 @@ import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentRepo import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentDto import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse import com.wafflestudio.csereal.core.seminar.database.SeminarEntity -import org.apache.commons.io.FilenameUtils import org.springframework.beans.factory.annotation.Value import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service @@ -32,7 +31,7 @@ interface AttachmentService { fun uploadAllAttachments( contentEntityType: AttachmentContentEntityType, - requestAttachments: List, + requestAttachments: List ): List fun createAttachmentResponses(attachments: List?): List @@ -46,7 +45,7 @@ class AttachmentServiceImpl( private val attachmentRepository: AttachmentRepository, @Value("\${csereal.upload.path}") private val path: String, - private val endpointProperties: EndpointProperties, + private val endpointProperties: EndpointProperties ) : AttachmentService { override fun uploadAttachmentInLabEntity(labEntity: LabEntity, requestAttachment: MultipartFile): AttachmentDto { Files.createDirectories(Paths.get(path)) @@ -61,7 +60,7 @@ class AttachmentServiceImpl( val attachment = AttachmentEntity( filename = filename, attachmentsOrder = 1, - size = requestAttachment.size, + size = requestAttachment.size ) labEntity.pdf = attachment @@ -72,13 +71,12 @@ class AttachmentServiceImpl( attachmentsOrder = 1, size = requestAttachment.size ) - } @Transactional override fun uploadAllAttachments( contentEntity: AttachmentContentEntityType, - requestAttachments: List, + requestAttachments: List ): List { Files.createDirectories(Paths.get(path)) @@ -95,7 +93,7 @@ class AttachmentServiceImpl( val attachment = AttachmentEntity( filename = filename, attachmentsOrder = index + 1, - size = requestAttachment.size, + size = requestAttachment.size ) connectAttachmentToEntity(contentEntity, attachment) @@ -123,17 +121,15 @@ class AttachmentServiceImpl( id = attachment.id, name = attachment.filename.substringAfter("_"), url = "${endpointProperties.backend}/v1/file/${attachment.filename}", - bytes = attachment.size, + bytes = attachment.size ) list.add(attachmentDto) } - } } return list } - @Transactional override fun deleteAttachments(ids: List?) { if (ids != null) { @@ -145,7 +141,6 @@ class AttachmentServiceImpl( } } - private fun connectAttachmentToEntity(contentEntity: AttachmentContentEntityType, attachment: AttachmentEntity) { when (contentEntity) { is NewsEntity -> { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/DeprecatedFileController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/DeprecatedFileController.kt index e80901ee..f30f0595 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/DeprecatedFileController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/DeprecatedFileController.kt @@ -11,16 +11,14 @@ import org.springframework.util.AntPathMatcher import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController -import java.net.URLEncoder import java.nio.file.Paths @RestController @RequestMapping("/sites/default/files") class DeprecatedFileController( @Value("\${oldFiles.path}") - private val oldFilesPath: String, + private val oldFilesPath: String ) { @GetMapping("/{map}/**") fun serveOldFile( @@ -49,5 +47,4 @@ class DeprecatedFileController( ResponseEntity.status(HttpStatus.NOT_FOUND).build() } } - } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/FileController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/FileController.kt index efb3be6a..6609284e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/FileController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/FileController.kt @@ -16,7 +16,6 @@ import org.springframework.web.multipart.MultipartFile import java.nio.file.Files import java.nio.file.Paths - @RequestMapping("/api/v1/file") @RestController class FileController( @@ -63,7 +62,7 @@ class FileController( val saveFile = Paths.get(totalFilename) file.transferTo(saveFile) - val imageUrl = "${endpointProperties.backend}/v1/file/${filename}" + val imageUrl = "${endpointProperties.backend}/v1/file/$filename" results.add( UploadFileInfo( @@ -98,5 +97,4 @@ class FileController( return ResponseEntity.status(HttpStatus.NOT_FOUND).body("파일을 찾을 수 없습니다.") } } - } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/database/MainImageEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/database/MainImageEntity.kt index 9f86cc41..5740455e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/database/MainImageEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/database/MainImageEntity.kt @@ -3,17 +3,14 @@ package com.wafflestudio.csereal.core.resource.mainImage.database import com.wafflestudio.csereal.common.config.BaseTimeEntity import jakarta.persistence.* - @Entity(name = "mainImage") class MainImageEntity( - var isDeleted : Boolean? = false, + var isDeleted: Boolean? = false, @Column(unique = true) val filename: String, val imagesOrder: Int, - val size: Long, - - ) : BaseTimeEntity() { + val size: Long -} \ No newline at end of file +) : BaseTimeEntity() diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/database/MainImageRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/database/MainImageRepository.kt index f67b9dbc..07de2921 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/database/MainImageRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/database/MainImageRepository.kt @@ -4,5 +4,4 @@ import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository @Repository -interface MainImageRepository : JpaRepository { -} \ No newline at end of file +interface MainImageRepository : JpaRepository diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/dto/MainImageDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/dto/MainImageDto.kt index 6ca97407..0a4eddd5 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/dto/MainImageDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/dto/MainImageDto.kt @@ -3,6 +3,5 @@ package com.wafflestudio.csereal.core.resource.mainImage.dto data class MainImageDto( val filename: String, val imagesOrder: Int, - val size: Long, -) { -} \ No newline at end of file + val size: Long +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt index b0e128c0..c62058e4 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/mainImage/service/MainImageService.kt @@ -4,7 +4,6 @@ import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.common.controller.MainImageContentEntityType import com.wafflestudio.csereal.common.properties.EndpointProperties import com.wafflestudio.csereal.core.about.database.AboutEntity -import com.wafflestudio.csereal.core.academics.database.CourseEntity import com.wafflestudio.csereal.core.member.database.ProfessorEntity import com.wafflestudio.csereal.core.member.database.StaffEntity import com.wafflestudio.csereal.core.news.database.NewsEntity @@ -28,7 +27,7 @@ import kotlin.io.path.name interface MainImageService { fun uploadMainImage( contentEntityType: MainImageContentEntityType, - requestImage: MultipartFile, + requestImage: MultipartFile ): MainImageDto fun createImageURL(image: MainImageEntity?): String? @@ -45,7 +44,7 @@ class MainImageServiceImpl( @Transactional override fun uploadMainImage( contentEntityType: MainImageContentEntityType, - requestImage: MultipartFile, + requestImage: MultipartFile ): MainImageDto { Files.createDirectories(Paths.get(path)) @@ -64,12 +63,12 @@ class MainImageServiceImpl( val totalThumbnailFilename = "${path}thumbnail_$filename" val thumbnailFile = Paths.get(totalThumbnailFilename) - Thumbnailator.createThumbnail(saveFile.toFile(), thumbnailFile.toFile(), 100, 100); + Thumbnailator.createThumbnail(saveFile.toFile(), thumbnailFile.toFile(), 100, 100) val mainImage = MainImageEntity( filename = filename, imagesOrder = 1, - size = requestImage.size, + size = requestImage.size ) val thumbnail = MainImageEntity( @@ -93,7 +92,9 @@ class MainImageServiceImpl( override fun createImageURL(mainImage: MainImageEntity?): String? { return if (mainImage != null) { "${endpointProperties.backend}/v1/file/${mainImage.filename}" - } else null + } else { + null + } } private fun connectMainImageToEntity(contentEntity: MainImageContentEntityType, mainImage: MainImageEntity) { @@ -127,5 +128,4 @@ class MainImageServiceImpl( } } } - } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt index 18e045e7..ea8ec26c 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt @@ -1,7 +1,6 @@ package com.wafflestudio.csereal.core.seminar.api import com.wafflestudio.csereal.common.aop.AuthenticatedStaff -import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse import com.wafflestudio.csereal.core.seminar.dto.SeminarDto import com.wafflestudio.csereal.core.seminar.dto.SeminarSearchResponse import com.wafflestudio.csereal.core.seminar.service.SeminarService @@ -43,7 +42,9 @@ class SeminarController( @AuthenticatedStaff @PostMapping fun createSeminar( - @Valid @RequestPart("request") request: SeminarDto, + @Valid + @RequestPart("request") + request: SeminarDto, @RequestPart("mainImage") mainImage: MultipartFile?, @RequestPart("attachments") attachments: List? ): ResponseEntity { @@ -61,16 +62,18 @@ class SeminarController( @PatchMapping("/{seminarId}") fun updateSeminar( @PathVariable seminarId: Long, - @Valid @RequestPart("request") request: SeminarDto, + @Valid + @RequestPart("request") + request: SeminarDto, @RequestPart("newMainImage") newMainImage: MultipartFile?, - @RequestPart("newAttachments") newAttachments: List?, + @RequestPart("newAttachments") newAttachments: List? ): ResponseEntity { return ResponseEntity.ok( seminarService.updateSeminar( seminarId, request, newMainImage, - newAttachments, + newAttachments ) ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt index 4c177561..cb5914ec 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt @@ -59,9 +59,9 @@ class SeminarEntity( var mainImage: MainImageEntity? = null, @OneToMany(mappedBy = "seminar", cascade = [CascadeType.ALL], orphanRemoval = true) - var attachments: MutableList = mutableListOf(), + var attachments: MutableList = mutableListOf() - ) : BaseTimeEntity(), MainImageContentEntityType, AttachmentContentEntityType { +) : BaseTimeEntity(), MainImageContentEntityType, AttachmentContentEntityType { override fun bringMainImage(): MainImageEntity? = mainImage override fun bringAttachments() = attachments @@ -90,7 +90,7 @@ class SeminarEntity( isPrivate = seminarDto.isPrivate, isImportant = seminarDto.isImportant, additionalNote = seminarDto.additionalNote, - plainTextAdditionalNote = plainTextAdditionalNote, + plainTextAdditionalNote = plainTextAdditionalNote ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt index c086a9fa..09d38889 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt @@ -2,10 +2,8 @@ package com.wafflestudio.csereal.core.seminar.database import com.querydsl.core.BooleanBuilder import com.querydsl.jpa.impl.JPAQueryFactory -import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.common.repository.CommonRepository import com.wafflestudio.csereal.common.utils.FixedPageRequest -import com.wafflestudio.csereal.core.notice.database.QNoticeEntity import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService import com.wafflestudio.csereal.core.seminar.database.QSeminarEntity.seminarEntity import com.wafflestudio.csereal.core.seminar.dto.SeminarSearchDto @@ -34,7 +32,7 @@ interface CustomSeminarRepository { class SeminarRepositoryImpl( private val queryFactory: JPAQueryFactory, private val mainImageService: MainImageService, - private val commonRepository: CommonRepository, + private val commonRepository: CommonRepository ) : CustomSeminarRepository { override fun searchSeminar( keyword: String?, @@ -54,7 +52,7 @@ class SeminarRepositoryImpl( seminarEntity.location, seminarEntity.plainTextDescription, seminarEntity.plainTextIntroduction, - seminarEntity.plainTextAdditionalNote, + seminarEntity.plainTextAdditionalNote ) keywordBooleanBuilder.and(booleanTemplate.gt(0.0)) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt index d45ed776..4f0f3c64 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt @@ -2,7 +2,6 @@ package com.wafflestudio.csereal.core.seminar.dto import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse import com.wafflestudio.csereal.core.seminar.database.SeminarEntity -import java.time.LocalDate import java.time.LocalDateTime data class SeminarDto( @@ -67,10 +66,8 @@ data class SeminarDto( nextId = nextSeminar?.id, nextTitle = nextSeminar?.title, imageURL = imageURL, - attachments = attachmentResponses, + attachments = attachmentResponses ) } - } - } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarSearchDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarSearchDto.kt index 79222ce1..c7fcc1f5 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarSearchDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarSearchDto.kt @@ -14,5 +14,4 @@ data class SeminarSearchDto @QueryProjection constructor( val imageURL: String?, val isYearLast: Boolean, val isPrivate: Boolean -) { -} +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarSearchResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarSearchResponse.kt index 2f2039da..ce74c985 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarSearchResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarSearchResponse.kt @@ -3,5 +3,4 @@ package com.wafflestudio.csereal.core.seminar.dto data class SeminarSearchResponse( val total: Long, val searchList: List -) { -} +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt index 1c874f0a..d8348849 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt @@ -27,7 +27,7 @@ interface SeminarService { seminarId: Long, request: SeminarDto, newMainImage: MultipartFile?, - newAttachments: List?, + newAttachments: List? ): SeminarDto fun deleteSeminar(seminarId: Long) @@ -37,7 +37,7 @@ interface SeminarService { class SeminarServiceImpl( private val seminarRepository: SeminarRepository, private val mainImageService: MainImageService, - private val attachmentService: AttachmentService, + private val attachmentService: AttachmentService ) : SeminarService { @Transactional(readOnly = true) override fun searchSeminar( @@ -95,7 +95,7 @@ class SeminarServiceImpl( seminarId: Long, request: SeminarDto, newMainImage: MultipartFile?, - newAttachments: List?, + newAttachments: List? ): SeminarDto { val seminar: SeminarEntity = seminarRepository.findByIdOrNull(seminarId) ?: throw CserealException.Csereal404("존재하지 않는 세미나입니다") diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/user/database/UserEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/user/database/UserEntity.kt index 2dca2073..a7e1e1d9 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/user/database/UserEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/user/database/UserEntity.kt @@ -14,9 +14,9 @@ class UserEntity( val studentId: String, @Enumerated(EnumType.STRING) - val role: Role?, + val role: Role? - ) : BaseTimeEntity() +) : BaseTimeEntity() enum class Role { ROLE_STAFF, ROLE_GRADUATE, ROLE_PROFESSOR diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/user/service/CustomOidcUserService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/user/service/CustomOidcUserService.kt index 551ce684..3a0609a4 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/user/service/CustomOidcUserService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/user/service/CustomOidcUserService.kt @@ -55,7 +55,9 @@ class CustomOidcUserService( val userInfoResponse = restTemplate.exchange>( userRequest.clientRegistration.providerDetails.userInfoEndpoint.uri, - HttpMethod.POST, requestEntity, Map::class.java + HttpMethod.POST, + requestEntity, + Map::class.java ) if (userInfoResponse.body?.get("sub") != userRequest.idToken.getClaim("sub")) { @@ -67,7 +69,6 @@ class CustomOidcUserService( @Transactional fun createUser(username: String, userInfo: Map) { - val name = userInfo["name"] as String val email = userInfo["email"] as String val studentId = userInfo["student_id"] as String diff --git a/src/test/kotlin/com/wafflestudio/csereal/CserealApplicationTests.kt b/src/test/kotlin/com/wafflestudio/csereal/CserealApplicationTests.kt index 7e705035..db711c7f 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/CserealApplicationTests.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/CserealApplicationTests.kt @@ -6,8 +6,7 @@ import org.springframework.boot.test.context.SpringBootTest @SpringBootTest class CserealApplicationTests { - @Test - fun contextLoads() { - } - + @Test + fun contextLoads() { + } } diff --git a/src/test/kotlin/com/wafflestudio/csereal/common/util/UtilsTest.kt b/src/test/kotlin/com/wafflestudio/csereal/common/util/UtilsTest.kt index 2f5dacf5..54965232 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/common/util/UtilsTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/common/util/UtilsTest.kt @@ -5,7 +5,7 @@ import com.wafflestudio.csereal.common.utils.substringAroundKeyword import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.shouldBe -class UtilsTest: BehaviorSpec({ +class UtilsTest : BehaviorSpec({ Given("cleanTextFromHtml") { When("description is html") { @@ -101,4 +101,4 @@ class UtilsTest: BehaviorSpec({ } } } -}) \ No newline at end of file +}) diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/conference/service/ConferenceServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/conference/service/ConferenceServiceTest.kt index 7b6ac283..7c0ad7a7 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/conference/service/ConferenceServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/conference/service/ConferenceServiceTest.kt @@ -24,29 +24,29 @@ import org.springframework.web.context.request.RequestContextHolder @SpringBootTest @Transactional -class ConferenceServiceTest ( - private val conferenceService: ConferenceService, - private val conferencePageRepository: ConferencePageRepository, - private val conferenceRepository: ConferenceRepository, - private val userRepository: UserRepository, -): BehaviorSpec ({ +class ConferenceServiceTest( + private val conferenceService: ConferenceService, + private val conferencePageRepository: ConferencePageRepository, + private val conferenceRepository: ConferenceRepository, + private val userRepository: UserRepository +) : BehaviorSpec({ extensions(SpringTestExtension(SpringTestLifecycleMode.Root)) beforeSpec { val user = userRepository.save( - UserEntity( - username = "admin", - name = "admin", - email = "email", - studentId = "studentId", - role = Role.ROLE_STAFF, - ) + UserEntity( + username = "admin", + name = "admin", + email = "email", + studentId = "studentId", + role = Role.ROLE_STAFF + ) ) conferencePageRepository.save( - ConferencePageEntity( - author = user, - ) + ConferencePageEntity( + author = user + ) ) } @@ -67,34 +67,35 @@ class ConferenceServiceTest ( } returns mockRequestAttributes every { mockRequestAttributes.getAttribute( - "loggedInUser", - RequestAttributes.SCOPE_REQUEST + "loggedInUser", + RequestAttributes.SCOPE_REQUEST ) } returns userEntity - var conferencePage = conferencePageRepository.findAll().first() - val conferences = conferenceRepository.saveAll(listOf( + val conferences = conferenceRepository.saveAll( + listOf( ConferenceEntity( - code = "code1", - name = "name1", - abbreviation = "abbreviation1", - conferencePage = conferencePage, + code = "code1", + name = "name1", + abbreviation = "abbreviation1", + conferencePage = conferencePage ), ConferenceEntity( - code = "code2", - name = "name2", - abbreviation = "abbreviation2", - conferencePage = conferencePage, + code = "code2", + name = "name2", + abbreviation = "abbreviation2", + conferencePage = conferencePage ), ConferenceEntity( - code = "code3", - name = "name3", - abbreviation = "abbreviation3", - conferencePage = conferencePage, - ), - )) + code = "code3", + name = "name3", + abbreviation = "abbreviation3", + conferencePage = conferencePage + ) + ) + ) conferencePage = conferencePage.apply { this.conferences.addAll(conferences) }.let { @@ -104,20 +105,20 @@ class ConferenceServiceTest ( When("Conference를 수정한다면") { val deleteConferenceId = conferences[1].id val modifiedConference = ConferenceDto( - id = conferences.first().id, - code = "code0", - name = "modifiedName", - abbreviation = "modifiedAbbreviation", - ) + id = conferences.first().id, + code = "code0", + name = "modifiedName", + abbreviation = "modifiedAbbreviation" + ) val newConference = ConferenceCreateDto( - code = "code9", - name = "newName", - abbreviation = "newAbbreviation", - ) + code = "code9", + name = "newName", + abbreviation = "newAbbreviation" + ) val conferenceModifyRequest = ConferenceModifyRequest( - deleteConfereceIdList = listOf(deleteConferenceId), - modifiedConferenceList = listOf(modifiedConference), - newConferenceList = listOf(newConference), + deleteConfereceIdList = listOf(deleteConferenceId), + modifiedConferenceList = listOf(modifiedConference), + newConferenceList = listOf(newConference) ) val conferencePage = conferenceService.modifyConferences(conferenceModifyRequest) @@ -157,4 +158,4 @@ class ConferenceServiceTest ( } } } -}) \ No newline at end of file +}) diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorServiceTest.kt index 8d71962a..b3a8b144 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorServiceTest.kt @@ -17,13 +17,13 @@ import java.time.LocalDate @SpringBootTest @Transactional -class ProfessorServiceTest ( - private val professorService: ProfessorService, - private val professorRepository: ProfessorRepository, - private val labRepository: LabRepository, - private val memberSearchRepository: MemberSearchRepository, - private val researchRepository: ResearchRepository, -): BehaviorSpec({ +class ProfessorServiceTest( + private val professorService: ProfessorService, + private val professorRepository: ProfessorRepository, + private val labRepository: LabRepository, + private val memberSearchRepository: MemberSearchRepository, + private val researchRepository: ResearchRepository +) : BehaviorSpec({ extensions(SpringTestExtension(SpringTestLifecycleMode.Root)) afterContainer { @@ -35,40 +35,40 @@ class ProfessorServiceTest ( val date = LocalDate.now() val researchEntity = ResearchEntity( - name = "researchName", - description = null, - postType = ResearchPostType.LABS, + name = "researchName", + description = null, + postType = ResearchPostType.LABS ) var labEntity = LabEntity( - name = "labName", - location = null, - tel = null, - acronym = null, - youtube = null, - description = null, - websiteURL = null, - research = researchEntity, + name = "labName", + location = null, + tel = null, + acronym = null, + youtube = null, + description = null, + websiteURL = null, + research = researchEntity ) researchEntity.labs.add(labEntity) researchRepository.save(researchEntity) labEntity = labRepository.save(labEntity) val professorDto = ProfessorDto( - name = "name", - email = "email", - status = ProfessorStatus.ACTIVE, - academicRank = "academicRank", - labId = labEntity.id, - labName = null, - startDate = date, - endDate = date, - office = "office", - phone = "phone", - fax = "fax", - website = "website", - educations = listOf("education1", "education2"), - researchAreas = listOf("researchArea1", "researchArea2"), - careers = listOf("career1", "career2") + name = "name", + email = "email", + status = ProfessorStatus.ACTIVE, + academicRank = "academicRank", + labId = labEntity.id, + labName = null, + startDate = date, + endDate = date, + office = "office", + phone = "phone", + fax = "fax", + website = "website", + educations = listOf("education1", "education2"), + researchAreas = listOf("researchArea1", "researchArea2"), + careers = listOf("career1", "career2") ) When("교수를 생성한다면") { @@ -79,7 +79,6 @@ class ProfessorServiceTest ( professorRepository.findByIdOrNull(createdProfessorDto.id) shouldNotBe null } - Then("교수의 정보가 일치해야 한다") { val professorEntity = professorRepository.findByIdOrNull(createdProfessorDto.id)!! @@ -99,7 +98,6 @@ class ProfessorServiceTest ( professorEntity.careers.map { it.name } shouldBe professorDto.careers } - Then("교수의 검색 정보가 생성되어야 한다") { memberSearchRepository.count() shouldBe 1 val memberSearchEntity = memberSearchRepository.findAll()[0] @@ -111,8 +109,8 @@ class ProfessorServiceTest ( 교수 academicRank labName - ${date} - ${date} + $date + $date office phone fax @@ -125,7 +123,7 @@ class ProfessorServiceTest ( career1 career2 - """.trimIndent() + """.trimIndent() memberSearchEntity.content shouldBe contentExpected } @@ -135,76 +133,76 @@ class ProfessorServiceTest ( Given("생성되어 있는 간단한 교수에 대하여") { val date = LocalDate.now() val researchEntity = ResearchEntity( - name = "researchName", - description = null, - postType = ResearchPostType.LABS, + name = "researchName", + description = null, + postType = ResearchPostType.LABS ) val labEntity1 = LabEntity( - name = "labName1", - location = null, - tel = null, - acronym = null, - youtube = null, - description = null, - websiteURL = null, - research = researchEntity, + name = "labName1", + location = null, + tel = null, + acronym = null, + youtube = null, + description = null, + websiteURL = null, + research = researchEntity ) val labEntity2 = LabEntity( - name = "labName2", - location = null, - tel = null, - acronym = null, - youtube = null, - description = null, - websiteURL = null, - research = researchEntity, + name = "labName2", + location = null, + tel = null, + acronym = null, + youtube = null, + description = null, + websiteURL = null, + research = researchEntity ) researchEntity.labs.addAll(listOf(labEntity1, labEntity2)) researchRepository.save(researchEntity) val createdProfessorDto = professorService.createProfessor( - ProfessorDto( - name = "name", - email = "email", - status = ProfessorStatus.ACTIVE, - academicRank = "academicRank", - labId = labEntity1.id, - labName = null, - startDate = date, - endDate = date, - office = "office", - phone = "phone", - fax = "fax", - website = "website", - educations = listOf("education1", "education2"), - researchAreas = listOf("researchArea1", "researchArea2"), - careers = listOf("career1", "career2") - ), - null + ProfessorDto( + name = "name", + email = "email", + status = ProfessorStatus.ACTIVE, + academicRank = "academicRank", + labId = labEntity1.id, + labName = null, + startDate = date, + endDate = date, + office = "office", + phone = "phone", + fax = "fax", + website = "website", + educations = listOf("education1", "education2"), + researchAreas = listOf("researchArea1", "researchArea2"), + careers = listOf("career1", "career2") + ), + null ) When("교수 정보를 수정하면") { val toModifyProfessorDto = createdProfessorDto.copy( - name = "modifiedName", - email = "modifiedEmail", - status = ProfessorStatus.INACTIVE, - academicRank = "modifiedAcademicRank", - labId = labEntity2.id, - startDate = date.plusDays(1), - endDate = date.plusDays(1), - office = "modifiedOffice", - phone = "modifiedPhone", - fax = "modifiedFax", - website = "modifiedWebsite", - educations = listOf("education1", "modifiedEducation2", "modifiedEducation3"), - researchAreas = listOf("researchArea1", "modifiedResearchArea2", "modifiedResearchArea3"), - careers = listOf("career1", "modifiedCareer2", "modifiedCareer3") + name = "modifiedName", + email = "modifiedEmail", + status = ProfessorStatus.INACTIVE, + academicRank = "modifiedAcademicRank", + labId = labEntity2.id, + startDate = date.plusDays(1), + endDate = date.plusDays(1), + office = "modifiedOffice", + phone = "modifiedPhone", + fax = "modifiedFax", + website = "modifiedWebsite", + educations = listOf("education1", "modifiedEducation2", "modifiedEducation3"), + researchAreas = listOf("researchArea1", "modifiedResearchArea2", "modifiedResearchArea3"), + careers = listOf("career1", "modifiedCareer2", "modifiedCareer3") ) val modifiedProfessorDto = professorService.updateProfessor( - toModifyProfessorDto.id!!, - toModifyProfessorDto, - null + toModifyProfessorDto.id!!, + toModifyProfessorDto, + null ) Then("교수 정보가 수정되어야 한다.") { @@ -257,8 +255,8 @@ class ProfessorServiceTest ( modifiedCareer2 modifiedCareer3 - """.trimIndent() + """.trimIndent() } } } -}) \ No newline at end of file +}) diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/member/service/StaffServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/member/service/StaffServiceTest.kt index cd56a5af..c0d61496 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/member/service/StaffServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/member/service/StaffServiceTest.kt @@ -15,10 +15,10 @@ import org.springframework.data.repository.findByIdOrNull @SpringBootTest @Transactional class StaffServiceTest( - private val staffService: StaffService, - private val staffRepository: StaffRepository, - private val memberSearchRepository: MemberSearchRepository, -): BehaviorSpec({ + private val staffService: StaffService, + private val staffRepository: StaffRepository, + private val memberSearchRepository: MemberSearchRepository +) : BehaviorSpec({ extensions(SpringTestExtension(SpringTestLifecycleMode.Root)) afterSpec { @@ -27,12 +27,12 @@ class StaffServiceTest( Given("이미지 없는 행정직원을 생성하려고 할 떄") { val staffDto = StaffDto( - name = "name", - role = "role", - office = "office", - phone = "phone", - email = "email", - tasks = listOf("task1", "task2"), + name = "name", + role = "role", + office = "office", + phone = "phone", + email = "email", + tasks = listOf("task1", "task2") ) When("행정직원을 생성하면") { @@ -75,24 +75,24 @@ class StaffServiceTest( Given("이미지 없는 행정직원을 수정할 때") { val staffDto = StaffDto( - name = "name", - role = "role", - office = "office", - phone = "phone", - email = "email", - tasks = listOf("task1", "task2"), + name = "name", + role = "role", + office = "office", + phone = "phone", + email = "email", + tasks = listOf("task1", "task2") ) val createdStaffDto = staffService.createStaff(staffDto, null) When("행정직원을 수정하면") { val updateStaffDto = StaffDto( - name = "name2", - role = "role2", - office = "office2", - phone = "phone2", - email = "email2", - tasks = listOf("task1", "task3", "task4"), + name = "name2", + role = "role2", + office = "office2", + phone = "phone2", + email = "email2", + tasks = listOf("task1", "task3", "task4") ) val updatedStaffDto = staffService.updateStaff(createdStaffDto.id!!, updateStaffDto, null) @@ -128,4 +128,4 @@ class StaffServiceTest( } } } -}) \ No newline at end of file +}) diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/news/NewsServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/news/NewsServiceTest.kt index a6ea95d7..0b280e91 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/news/NewsServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/news/NewsServiceTest.kt @@ -1,4 +1,4 @@ -package com.wafflestudio.csereal.core.news +package com.wafflestudio.csereal.core.notice.news import com.wafflestudio.csereal.core.news.database.NewsEntity import com.wafflestudio.csereal.core.news.database.NewsRepository @@ -14,7 +14,7 @@ import java.time.LocalDateTime @SpringBootTest class NewsServiceTest( private val newsService: NewsService, - private val newsRepository: NewsRepository, + private val newsRepository: NewsRepository ) : BehaviorSpec() { init { @@ -31,7 +31,7 @@ class NewsServiceTest(

Hello, World!

This is news description.

Goodbye, World!

- """.trimIndent(), + """.trimIndent(), tags = emptyList(), createdAt = null, modifiedAt = null, @@ -44,7 +44,7 @@ class NewsServiceTest( nextId = null, nextTitle = null, imageURL = null, - attachments = null, + attachments = null ) When("DTO를 이용하여 뉴스를 생성하면") { @@ -57,7 +57,9 @@ class NewsServiceTest( Then("plainTextDescription이 생성되었어야 한다.") { val createdNewsEntity = newsRepository.findByIdOrNull(createdNewsDTO.id)!! - createdNewsEntity.plainTextDescription shouldBe "Hello, World! This is news description. Goodbye, World!" + createdNewsEntity.plainTextDescription shouldBe ( + "Hello, World! This is news description. Goodbye, World!" + ) } } } @@ -71,12 +73,12 @@ class NewsServiceTest(

Hello, World!

This is news description.

Goodbye, World!

- """.trimIndent(), + """.trimIndent(), plainTextDescription = "Hello, World! This is news description. Goodbye, World!", date = LocalDateTime.now(), isPrivate = false, isSlide = false, - isImportant = false, + isImportant = false ) ) @@ -93,7 +95,7 @@ class NewsServiceTest( """.trimIndent() ), null, - null, + null ) Then("description, plainTextDescription이 수정되어야 한다.") { @@ -103,9 +105,11 @@ class NewsServiceTest(

This is modified news description.

Goodbye, World!

This is additional description.

- """.trimIndent() - updatedNewsEntity.plainTextDescription shouldBe "Hello, World! This is modified news description." + + """.trimIndent() + updatedNewsEntity.plainTextDescription shouldBe ( + "Hello, World! This is modified news description." + " Goodbye, World! This is additional description." + ) } } } diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeServiceTest.kt index c7363eaa..3242e140 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeServiceTest.kt @@ -21,7 +21,7 @@ import org.springframework.web.context.request.RequestContextHolder class NoticeServiceTest( private val noticeService: NoticeService, private val userRepository: UserRepository, - private val noticeRepository: NoticeRepository, + private val noticeRepository: NoticeRepository ) : BehaviorSpec() { init { beforeContainer { @@ -64,7 +64,7 @@ class NoticeServiceTest(

Hello, World!

This is a test notice.

Goodbye, World!

- """.trimIndent(), + """.trimIndent(), author = "username", tags = emptyList(), createdAt = null, @@ -76,7 +76,7 @@ class NoticeServiceTest( prevTitle = null, nextId = null, nextTitle = null, - attachments = null, + attachments = null ) When("공지사항을 생성하면") { @@ -102,35 +102,39 @@ class NoticeServiceTest(

Hello, World!

This is a test notice.

Goodbye, World!

- """.trimIndent(), + """.trimIndent(), plainTextDescription = "Hello, World! This is a test notice. Goodbye, World!", isPrivate = false, isPinned = false, isImportant = false, - author = userRepository.findByUsername("username")!!, + author = userRepository.findByUsername("username")!! ) ) val modifiedRequest = NoticeDto.of( - noticeEntity, emptyList(), null + noticeEntity, + emptyList(), + null ).copy( description = """

Hello, World!

This is a modified test notice.

Goodbye, World!

And this is a new line.

- """.trimIndent() + """.trimIndent() ) When("수정된 DTO를 이용하여 수정하면") { val modifiedNoticeDto = noticeService.updateNotice( modifiedRequest.id, modifiedRequest, - null, + null ) Then("plainTextDescription이 잘 수정되어야 한다.") { val noticeEntity = noticeRepository.findByIdOrNull(modifiedNoticeDto.id) - noticeEntity?.plainTextDescription shouldBe "Hello, World! This is a modified test notice. Goodbye, World! And this is a new line." + noticeEntity?.plainTextDescription shouldBe ( + "Hello, World! This is a modified test notice. Goodbye, World! And this is a new line." + ) } } } diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchServiceTest.kt index e65275e8..468372f1 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchServiceTest.kt @@ -20,13 +20,13 @@ import org.springframework.transaction.annotation.Transactional @SpringBootTest @Transactional -class ResearchServiceTest ( - private val researchService: ResearchService, - private val professorRepository: ProfessorRepository, - private val labRepository: LabRepository, - private val researchRepository: ResearchRepository, - private val researchSearchRepository: ResearchSearchRepository, -): BehaviorSpec({ +class ResearchServiceTest( + private val researchService: ResearchService, + private val professorRepository: ProfessorRepository, + private val labRepository: LabRepository, + private val researchRepository: ResearchRepository, + private val researchSearchRepository: ResearchSearchRepository +) : BehaviorSpec({ extensions(SpringTestExtension(SpringTestLifecycleMode.Root)) beforeSpec { @@ -42,23 +42,23 @@ class ResearchServiceTest ( // Research Given("간단한 Research를 생성하려고 할 때") { val researchDto = ResearchDto( - id = -1, - name = "name", - postType = ResearchPostType.CENTERS, - description = "description", - createdAt = null, - modifiedAt = null, - labs = null, - imageURL = null, - attachments = null, + id = -1, + name = "name", + postType = ResearchPostType.CENTERS, + description = "description", + createdAt = null, + modifiedAt = null, + labs = null, + imageURL = null, + attachments = null ) When("Research를 생성한다면") { val createdResearchDto = researchService.createResearchDetail( - researchDto, - null, - null, - ) + researchDto, + null, + null + ) Then("Research가 생성되어야 한다") { val research = researchRepository.findByIdOrNull(createdResearchDto.id) @@ -79,53 +79,53 @@ class ResearchServiceTest ( researchSearch shouldNotBe null researchSearch!!.content shouldBe - """ + """ name 연구 센터 description - """.trimIndent() + """.trimIndent() } } } Given("간단한 Research를 수정하려고 할 때") { val researchDto = ResearchDto( - id = -1, - name = "name", - postType = ResearchPostType.CENTERS, - description = "description", - createdAt = null, - modifiedAt = null, - labs = null, - imageURL = null, - attachments = null, + id = -1, + name = "name", + postType = ResearchPostType.CENTERS, + description = "description", + createdAt = null, + modifiedAt = null, + labs = null, + imageURL = null, + attachments = null ) val createdResearchDto = researchService.createResearchDetail( - researchDto, - null, - null, + researchDto, + null, + null ) When("Research를 수정한다면") { val researchUpdateRequest = ResearchDto( - id = createdResearchDto.id, - name = "name2", - postType = ResearchPostType.GROUPS, - description = "description2", - createdAt = null, - modifiedAt = null, - labs = null, - imageURL = null, - attachments = null, + id = createdResearchDto.id, + name = "name2", + postType = ResearchPostType.GROUPS, + description = "description2", + createdAt = null, + modifiedAt = null, + labs = null, + imageURL = null, + attachments = null ) researchService.updateResearchDetail( - createdResearchDto.id, - researchUpdateRequest, - null, - null, + createdResearchDto.id, + researchUpdateRequest, + null, + null ) Then("Research가 수정되어야 한다") { @@ -141,73 +141,72 @@ class ResearchServiceTest ( researchSearch shouldNotBe null researchSearch!!.content shouldBe - """ + """ name2 연구 그룹 description2 - """.trimIndent() + """.trimIndent() } } } - // Lab Given("pdf 없는 Lab을 생성하려고 할 때") { // Save professors val professor1 = professorRepository.save( - ProfessorEntity( - name = "professor1", - status = ProfessorStatus.ACTIVE, - academicRank = "professor", - email = null, - fax = null, - office = null, - phone = null, - website = null, - startDate = null, - endDate = null, + ProfessorEntity( + name = "professor1", + status = ProfessorStatus.ACTIVE, + academicRank = "professor", + email = null, + fax = null, + office = null, + phone = null, + website = null, + startDate = null, + endDate = null ) ) val professor2 = professorRepository.save( - ProfessorEntity( - name = "professor2", - status = ProfessorStatus.ACTIVE, - academicRank = "professor", - email = null, - fax = null, - office = null, - phone = null, - website = null, - startDate = null, - endDate = null, + ProfessorEntity( + name = "professor2", + status = ProfessorStatus.ACTIVE, + academicRank = "professor", + email = null, + fax = null, + office = null, + phone = null, + website = null, + startDate = null, + endDate = null ) ) // Save research val research = researchRepository.save( - ResearchEntity( - name = "research", - postType = ResearchPostType.GROUPS, - description = null, - ) + ResearchEntity( + name = "research", + postType = ResearchPostType.GROUPS, + description = null + ) ) val labDto = LabDto( - id = -1, - name = "name", - professors = listOf( - LabProfessorResponse(professor1.id, professor1.name), - LabProfessorResponse(professor2.id, professor2.name), - ), - acronym = "acronym", - description = "description", - group = "research", - pdf = null, - location = "location", - tel = "tel", - websiteURL = "websiteURL", - youtube = "youtube", + id = -1, + name = "name", + professors = listOf( + LabProfessorResponse(professor1.id, professor1.name), + LabProfessorResponse(professor2.id, professor2.name) + ), + acronym = "acronym", + description = "description", + group = "research", + pdf = null, + location = "location", + tel = "tel", + websiteURL = "websiteURL", + youtube = "youtube" ) When("Lab을 생성한다면") { @@ -238,7 +237,7 @@ class ResearchServiceTest ( researchSearch shouldNotBe null researchSearch!!.content shouldBe - """ + """ name professor1 professor2 @@ -250,7 +249,7 @@ class ResearchServiceTest ( description websiteURL - """.trimIndent() + """.trimIndent() } } } @@ -258,59 +257,59 @@ class ResearchServiceTest ( Given("간단한 Lab을 수정할 경우") { // Save professors val professor1 = professorRepository.save( - ProfessorEntity( - name = "professor1", - status = ProfessorStatus.ACTIVE, - academicRank = "professor", - email = null, - fax = null, - office = null, - phone = null, - website = null, - startDate = null, - endDate = null, - ) + ProfessorEntity( + name = "professor1", + status = ProfessorStatus.ACTIVE, + academicRank = "professor", + email = null, + fax = null, + office = null, + phone = null, + website = null, + startDate = null, + endDate = null + ) ) val professor2 = professorRepository.save( - ProfessorEntity( - name = "professor2", - status = ProfessorStatus.ACTIVE, - academicRank = "professor", - email = null, - fax = null, - office = null, - phone = null, - website = null, - startDate = null, - endDate = null, - ) + ProfessorEntity( + name = "professor2", + status = ProfessorStatus.ACTIVE, + academicRank = "professor", + email = null, + fax = null, + office = null, + phone = null, + website = null, + startDate = null, + endDate = null + ) ) // Save research val research = researchRepository.save( - ResearchEntity( - name = "research", - postType = ResearchPostType.GROUPS, - description = null, - ) + ResearchEntity( + name = "research", + postType = ResearchPostType.GROUPS, + description = null + ) ) // Save lab val labDto = LabDto( - id = -1, - name = "name", - professors = listOf( - LabProfessorResponse(professor1.id, professor1.name), - LabProfessorResponse(professor2.id, professor2.name), - ), - acronym = "acronym", - description = "description", - group = "research", - pdf = null, - location = "location", - tel = "tel", - websiteURL = "websiteURL", - youtube = "youtube", + id = -1, + name = "name", + professors = listOf( + LabProfessorResponse(professor1.id, professor1.name), + LabProfessorResponse(professor2.id, professor2.name) + ), + acronym = "acronym", + description = "description", + group = "research", + pdf = null, + location = "location", + tel = "tel", + websiteURL = "websiteURL", + youtube = "youtube" ) val createdLabDto = researchService.createLab(labDto, null) @@ -318,30 +317,30 @@ class ResearchServiceTest ( When("pdf를 제외하고 Lab을 수정한다면") { val professor3 = professorRepository.save( - ProfessorEntity( - name = "professor3", - status = ProfessorStatus.ACTIVE, - academicRank = "professor", - email = null, - fax = null, - office = null, - phone = null, - website = null, - startDate = null, - endDate = null, - ) + ProfessorEntity( + name = "professor3", + status = ProfessorStatus.ACTIVE, + academicRank = "professor", + email = null, + fax = null, + office = null, + phone = null, + website = null, + startDate = null, + endDate = null + ) ) val labUpdateRequest = LabUpdateRequest( - name = "name2", - professorIds = listOf(professor1.id, professor3.id), - acronym = "acronym2", - description = "description2", - location = "location2", - tel = "tel2", - websiteURL = "websiteURL2", - youtube = "youtube2", - pdfModified = false, + name = "name2", + professorIds = listOf(professor1.id, professor3.id), + acronym = "acronym2", + description = "description2", + location = "location2", + tel = "tel2", + websiteURL = "websiteURL2", + youtube = "youtube2", + pdfModified = false ) researchService.updateLab(createdLab.id, labUpdateRequest, null) @@ -365,7 +364,7 @@ class ResearchServiceTest ( researchSearch shouldNotBe null researchSearch!!.content shouldBe - """ + """ name2 professor1 professor3 @@ -377,8 +376,8 @@ class ResearchServiceTest ( description2 websiteURL2 - """.trimIndent() + """.trimIndent() } } } -}) \ No newline at end of file +}) diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarServiceTest.kt index bdd1f904..3bfcb4e0 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarServiceTest.kt @@ -15,7 +15,7 @@ import java.time.LocalDateTime @Transactional class SeminarServiceTest( private val seminarService: SeminarService, - private val seminarRepository: SeminarRepository, + private val seminarRepository: SeminarRepository ) : BehaviorSpec() { init { @@ -35,12 +35,12 @@ class SeminarServiceTest(

Hello, World!

This is seminar description.

Goodbye, World!

- """.trimIndent(), + """.trimIndent(), introduction = """

Hello, World!

This is seminar introduction.

Goodbye, World!

- """.trimIndent(), + """.trimIndent(), name = "name", speakerURL = "speakerURL", speakerTitle = "speakerTitle", @@ -54,7 +54,7 @@ class SeminarServiceTest(

Hello, World!

This is seminar additionalNote.

Goodbye, World!

- """.trimIndent(), + """.trimIndent(), createdAt = null, modifiedAt = null, isPrivate = false, @@ -76,9 +76,15 @@ class SeminarServiceTest( Then("plain text 값들이 잘 생성되어야 한다.") { val seminarEntity = seminarRepository.findByIdOrNull(resultSeminarDTO.id)!! - seminarEntity.plainTextDescription shouldBe "Hello, World! This is seminar description. Goodbye, World!" - seminarEntity.plainTextIntroduction shouldBe "Hello, World! This is seminar introduction. Goodbye, World!" - seminarEntity.plainTextAdditionalNote shouldBe "Hello, World! This is seminar additionalNote. Goodbye, World!" + seminarEntity.plainTextDescription shouldBe ( + "Hello, World! This is seminar description. Goodbye, World!" + ) + seminarEntity.plainTextIntroduction shouldBe ( + "Hello, World! This is seminar introduction. Goodbye, World!" + ) + seminarEntity.plainTextAdditionalNote shouldBe ( + "Hello, World! This is seminar additionalNote. Goodbye, World!" + ) } } } @@ -92,13 +98,13 @@ class SeminarServiceTest(

Hello, World!

This is seminar description.

Goodbye, World!

- """.trimIndent(), + """.trimIndent(), plainTextDescription = "Hello, World! This is seminar description. Goodbye, World!", introduction = """

Hello, World!

This is seminar introduction.

Goodbye, World!

- """.trimIndent(), + """.trimIndent(), plainTextIntroduction = "Hello, World! This is seminar introduction. Goodbye, World!", name = "name", speakerURL = "speakerURL", @@ -113,43 +119,46 @@ class SeminarServiceTest(

Hello, World!

This is seminar additionalNote.

Goodbye, World!

- """.trimIndent(), + """.trimIndent(), plainTextAdditionalNote = "Hello, World! This is seminar additionalNote. Goodbye, World!", isPrivate = false, - isImportant = false, + isImportant = false ) ) val originalId = originalSeminar.id When("수정된 DTO를 이용하여 수정하면") { val modifiedSeminarDTO = SeminarDto.of( - originalSeminar, null, emptyList(), null + originalSeminar, + null, + emptyList(), + null ).copy( description = """

Hello, World!

This is modified seminar description.

Goodbye, World!

And this is a new line.

- """.trimIndent(), + """.trimIndent(), introduction = """

Hello, World!

This is modified seminar introduction.

Goodbye, World!

And this is a new line.

- """.trimIndent(), + """.trimIndent(), additionalNote = """

Hello, World!

This is modified seminar additionalNote.

Goodbye, World!

And this is a new line.

- """.trimIndent(), + """.trimIndent() ) val modifiedSeminarDto = seminarService.updateSeminar( originalSeminar.id, modifiedSeminarDTO, null, - null, + null ) Then("같은 Entity가 수정되어야 한다.") { @@ -160,9 +169,15 @@ class SeminarServiceTest( Then("plain text 값들이 잘 수정되어야 한다.") { val modifiedSeminarEntity = seminarRepository.findByIdOrNull(modifiedSeminarDto.id)!! - modifiedSeminarEntity.plainTextDescription shouldBe "Hello, World! This is modified seminar description. Goodbye, World! And this is a new line." - modifiedSeminarEntity.plainTextIntroduction shouldBe "Hello, World! This is modified seminar introduction. Goodbye, World! And this is a new line." - modifiedSeminarEntity.plainTextAdditionalNote shouldBe "Hello, World! This is modified seminar additionalNote. Goodbye, World! And this is a new line." + modifiedSeminarEntity.plainTextDescription shouldBe ( + "Hello, World! This is modified seminar description. Goodbye, World! And this is a new line." + ) + modifiedSeminarEntity.plainTextIntroduction shouldBe ( + "Hello, World! This is modified seminar introduction. Goodbye, World! And this is a new line." + ) + modifiedSeminarEntity.plainTextAdditionalNote shouldBe ( + "Hello, World! This is modified seminar additionalNote. Goodbye, World! And this is a new line." + ) } } } diff --git a/src/test/kotlin/com/wafflestudio/csereal/global/config/TestConfig.kt b/src/test/kotlin/com/wafflestudio/csereal/global/config/TestConfig.kt index 38606705..849b6c38 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/global/config/TestConfig.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/global/config/TestConfig.kt @@ -7,10 +7,10 @@ import org.springframework.context.annotation.Bean import org.springframework.boot.test.context.TestConfiguration @TestConfiguration -class TestConfig ( - @PersistenceContext - private val entityManager: EntityManager, +class TestConfig( + @PersistenceContext + private val entityManager: EntityManager ) { @Bean fun jpaQueryFactory() = JPAQueryFactory(entityManager) -} \ No newline at end of file +} From 1ba0234b8b2f35584fc8af3b9bb2b99bcbcef826 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Tue, 19 Sep 2023 23:51:01 +0900 Subject: [PATCH 111/124] =?UTF-8?q?fix:=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=9D=B8=EC=BD=94=EB=94=A9=20(#147)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: content-dispostion * fix: 텍스트 인코딩 * fix: 강제 다운로드 * style: ktlint --------- Co-authored-by: 우혁준 (HyukJoon Woo) --- .../resource/common/api/DeprecatedFileController.kt | 10 ++++++++-- .../csereal/core/resource/common/api/FileController.kt | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/DeprecatedFileController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/DeprecatedFileController.kt index f30f0595..7040757f 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/DeprecatedFileController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/DeprecatedFileController.kt @@ -34,11 +34,17 @@ class DeprecatedFileController( val resource = UrlResource(file.toUri()) return if (resource.exists() || resource.isReadable) { - val contentType: String? = request.servletContext.getMimeType(resource.file.absolutePath) + var contentType: String? = request.servletContext.getMimeType(resource.file.absolutePath) val headers = HttpHeaders() + contentType = contentType ?: "application/octet-stream" + + if (contentType.startsWith("text")) { + contentType += ";charset=UTF-8" + } + headers.contentType = - org.springframework.http.MediaType.parseMediaType(contentType ?: "application/octet-stream") + org.springframework.http.MediaType.parseMediaType(contentType) ResponseEntity.ok() .headers(headers) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/FileController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/FileController.kt index 6609284e..f738d736 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/FileController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/FileController.kt @@ -33,11 +33,17 @@ class FileController( val resource = UrlResource(file.toUri()) if (resource.exists() || resource.isReadable) { - val contentType: String? = request.servletContext.getMimeType(resource.file.absolutePath) + var contentType: String? = request.servletContext.getMimeType(resource.file.absolutePath) val headers = HttpHeaders() + contentType = contentType ?: "application/octet-stream" + + if (contentType.startsWith("text")) { + contentType += ";charset=UTF-8" + } + headers.contentType = - org.springframework.http.MediaType.parseMediaType(contentType ?: "application/octet-stream") + org.springframework.http.MediaType.parseMediaType(contentType) return ResponseEntity.ok() .headers(headers) From 5308b4b2f853d17bbe1119497ce51132ec6d737f Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Wed, 20 Sep 2023 00:31:36 +0900 Subject: [PATCH 112/124] Update ktlint-check.yml --- .github/workflows/ktlint-check.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ktlint-check.yml b/.github/workflows/ktlint-check.yml index e9c22ae6..cff9a7fb 100644 --- a/.github/workflows/ktlint-check.yml +++ b/.github/workflows/ktlint-check.yml @@ -2,7 +2,7 @@ name: Ktlint on: pull_request: - branches: [ main ] + branches: [ develop ] jobs: ktlint: @@ -14,7 +14,7 @@ jobs: - name: Set up JDK uses: actions/setup-java@v1 with: - java-version: 11 + java-version: 17 - name: Grant execute permission for gradlew run: chmod +x gradlew - name: ktlintCheck with Gradle From 5621bd701f497049ff3ed6da8036d21b79581dc1 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Wed, 20 Sep 2023 02:32:18 +0900 Subject: [PATCH 113/124] =?UTF-8?q?fix:=20=EC=A4=91=EC=9A=94=20=EC=95=88?= =?UTF-8?q?=EB=82=B4=20(#149)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 삭제된 글 중요 안내에서 보이지 않도록 수정 * fix: 중요 안내 최대 2개만 보내도록 + plainTextDescription --- .../csereal/core/admin/service/AdminService.kt | 8 +++++--- .../csereal/core/main/database/MainRepository.kt | 14 +++++++------- .../csereal/core/news/database/NewsRepository.kt | 4 +++- .../core/notice/database/NoticeRepository.kt | 3 ++- .../core/seminar/database/SeminarRepository.kt | 3 ++- 5 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/service/AdminService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/service/AdminService.kt index e90a2357..b831309d 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admin/service/AdminService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/service/AdminService.kt @@ -44,7 +44,7 @@ class AdminServiceImpl( @Transactional override fun readAllImportants(pageNum: Long): List { val importantResponses: MutableList = mutableListOf() - noticeRepository.findAllByIsImportant(true).forEach { + noticeRepository.findAllByIsImportantTrueAndIsDeletedFalse().forEach { importantResponses.add( ImportantResponse( id = it.id, @@ -55,7 +55,7 @@ class AdminServiceImpl( ) } - newsRepository.findAllByIsImportant(true).forEach { + newsRepository.findAllByIsImportantTrueAndIsDeletedFalse().forEach { importantResponses.add( ImportantResponse( id = it.id, @@ -66,7 +66,7 @@ class AdminServiceImpl( ) } - seminarRepository.findAllByIsImportant(true).forEach { + seminarRepository.findAllByIsImportantTrueAndIsDeletedFalse().forEach { importantResponses.add( ImportantResponse( id = it.id, @@ -90,11 +90,13 @@ class AdminServiceImpl( ?: throw CserealException.Csereal404("해당하는 공지사항을 찾을 수 없습니다.(noticeId=${important.id})") notice.isImportant = false } + "news" -> { val news = newsRepository.findByIdOrNull(important.id) ?: throw CserealException.Csereal404("해당하는 새소식을 찾을 수 없습니다.(noticeId=${important.id})") news.isImportant = false } + "seminar" -> { val seminar = seminarRepository.findByIdOrNull(important.id) ?: throw CserealException.Csereal404("해당하는 세미나를 찾을 수 없습니다.(noticeId=${important.id})") diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt index 79bc1e51..e7bda26d 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt @@ -88,36 +88,36 @@ class MainRepositoryImpl( override fun readMainImportant(): List { val mainImportantResponses: MutableList = mutableListOf() - noticeRepository.findAllByIsImportant(true).forEach { + noticeRepository.findAllByIsPrivateFalseAndIsImportantTrueAndIsDeletedFalse().forEach { mainImportantResponses.add( MainImportantResponse( id = it.id, title = it.titleForMain ?: it.title, - description = it.description, + description = it.plainTextDescription, createdAt = it.createdAt, category = "notice" ) ) } - newsRepository.findAllByIsImportant(true).forEach { + newsRepository.findAllByIsPrivateFalseAndIsImportantTrueAndIsDeletedFalse().forEach { mainImportantResponses.add( MainImportantResponse( id = it.id, title = it.titleForMain ?: it.title, - description = it.description, + description = it.plainTextDescription, createdAt = it.createdAt, category = "news" ) ) } - seminarRepository.findAllByIsImportant(true).forEach { + seminarRepository.findAllByIsPrivateFalseAndIsImportantTrueAndIsDeletedFalse().forEach { mainImportantResponses.add( MainImportantResponse( id = it.id, title = it.titleForMain ?: it.title, - description = it.description, + description = it.plainTextDescription, createdAt = it.createdAt, category = "seminar" ) @@ -125,6 +125,6 @@ class MainRepositoryImpl( } mainImportantResponses.sortByDescending { it.createdAt } - return mainImportantResponses + return mainImportantResponses.take(2) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt index 2648c203..d9ba9b8d 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt @@ -20,7 +20,8 @@ import org.springframework.stereotype.Component import java.time.LocalDateTime interface NewsRepository : JpaRepository, CustomNewsRepository { - fun findAllByIsImportant(isImportant: Boolean): List + fun findAllByIsPrivateFalseAndIsImportantTrueAndIsDeletedFalse(): List + fun findAllByIsImportantTrueAndIsDeletedFalse(): List fun findFirstByCreatedAtLessThanOrderByCreatedAtDesc(timestamp: LocalDateTime): NewsEntity? fun findFirstByCreatedAtGreaterThanOrderByCreatedAtAsc(timestamp: LocalDateTime): NewsEntity? } @@ -33,6 +34,7 @@ interface CustomNewsRepository { usePageBtn: Boolean, isStaff: Boolean ): NewsSearchResponse + fun searchTotalNews( keyword: String, number: Int, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt index 2a27f3bb..a4a531dd 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt @@ -17,7 +17,8 @@ import org.springframework.stereotype.Component import java.time.LocalDateTime interface NoticeRepository : JpaRepository, CustomNoticeRepository { - fun findAllByIsImportant(isImportant: Boolean): List + fun findAllByIsPrivateFalseAndIsImportantTrueAndIsDeletedFalse(): List + fun findAllByIsImportantTrueAndIsDeletedFalse(): List fun findFirstByCreatedAtLessThanOrderByCreatedAtDesc(timestamp: LocalDateTime): NoticeEntity? fun findFirstByCreatedAtGreaterThanOrderByCreatedAtAsc(timestamp: LocalDateTime): NoticeEntity? } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt index 09d38889..98545085 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt @@ -14,7 +14,8 @@ import org.springframework.stereotype.Component import java.time.LocalDateTime interface SeminarRepository : JpaRepository, CustomSeminarRepository { - fun findAllByIsImportant(isImportant: Boolean): List + fun findAllByIsPrivateFalseAndIsImportantTrueAndIsDeletedFalse(): List + fun findAllByIsImportantTrueAndIsDeletedFalse(): List fun findFirstByCreatedAtLessThanAndIsPrivateFalseOrderByCreatedAtDesc(timestamp: LocalDateTime): SeminarEntity? fun findFirstByCreatedAtGreaterThanAndIsPrivateFalseOrderByCreatedAtAsc(timestamp: LocalDateTime): SeminarEntity? } From bfcf358f5817f5fb91b0d1465fc5b8a64b619a3a Mon Sep 17 00:00:00 2001 From: leeeryboy Date: Wed, 20 Sep 2023 02:42:41 +0900 Subject: [PATCH 114/124] style: ktlint --- .../wafflestudio/csereal/core/news/database/NewsRepository.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt index 587d2689..d9ba9b8d 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt @@ -34,7 +34,7 @@ interface CustomNewsRepository { usePageBtn: Boolean, isStaff: Boolean ): NewsSearchResponse - + fun searchTotalNews( keyword: String, number: Int, From edbc67f3ab2204ea86e52f40f7fa63454c565118 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Thu, 21 Sep 2023 11:51:09 +0900 Subject: [PATCH 115/124] =?UTF-8?q?fix:=20=EC=B2=A8=EB=B6=80=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EB=8B=A4=EC=9A=B4=EB=A1=9C=EB=93=9C=20(#154)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/api/DeprecatedFileController.kt | 15 ++++++++------- .../core/resource/common/api/FileController.kt | 15 ++++++++------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/DeprecatedFileController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/DeprecatedFileController.kt index 7040757f..2103ca8c 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/DeprecatedFileController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/DeprecatedFileController.kt @@ -6,13 +6,16 @@ import org.springframework.core.io.Resource import org.springframework.core.io.UrlResource import org.springframework.http.HttpHeaders import org.springframework.http.HttpStatus +import org.springframework.http.MediaType import org.springframework.http.ResponseEntity import org.springframework.util.AntPathMatcher import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController +import java.net.URLEncoder import java.nio.file.Paths +import kotlin.text.Charsets.UTF_8 @RestController @RequestMapping("/sites/default/files") @@ -34,17 +37,15 @@ class DeprecatedFileController( val resource = UrlResource(file.toUri()) return if (resource.exists() || resource.isReadable) { - var contentType: String? = request.servletContext.getMimeType(resource.file.absolutePath) + val contentType: String? = request.servletContext.getMimeType(resource.file.absolutePath) val headers = HttpHeaders() - contentType = contentType ?: "application/octet-stream" + headers.contentType = MediaType.parseMediaType(contentType ?: "application/octet-stream") - if (contentType.startsWith("text")) { - contentType += ";charset=UTF-8" - } + val originalFilename = fileSubDir.substringAfterLast("/") + val encodedFilename = URLEncoder.encode(originalFilename, UTF_8.toString()).replace("+", "%20") - headers.contentType = - org.springframework.http.MediaType.parseMediaType(contentType) + headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''$encodedFilename") ResponseEntity.ok() .headers(headers) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/FileController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/FileController.kt index f738d736..79dacaa1 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/FileController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/FileController.kt @@ -10,11 +10,14 @@ import org.springframework.core.io.Resource import org.springframework.core.io.UrlResource import org.springframework.http.HttpHeaders import org.springframework.http.HttpStatus +import org.springframework.http.MediaType import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile +import java.net.URLEncoder import java.nio.file.Files import java.nio.file.Paths +import kotlin.text.Charsets.UTF_8 @RequestMapping("/api/v1/file") @RestController @@ -33,17 +36,15 @@ class FileController( val resource = UrlResource(file.toUri()) if (resource.exists() || resource.isReadable) { - var contentType: String? = request.servletContext.getMimeType(resource.file.absolutePath) + val contentType: String? = request.servletContext.getMimeType(resource.file.absolutePath) val headers = HttpHeaders() - contentType = contentType ?: "application/octet-stream" + headers.contentType = MediaType.parseMediaType(contentType ?: "application/octet-stream") - if (contentType.startsWith("text")) { - contentType += ";charset=UTF-8" - } + val originalFilename = filename.substringAfter("_") + val encodedFilename = URLEncoder.encode(originalFilename, UTF_8.toString()).replace("+", "%20") - headers.contentType = - org.springframework.http.MediaType.parseMediaType(contentType) + headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''$encodedFilename") return ResponseEntity.ok() .headers(headers) From 72ff727e988c80e64129cd4bed3d79d612457b9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=ED=98=81=EC=A4=80=20=28HyukJoon=20Woo=29?= Date: Fri, 22 Sep 2023 00:11:04 +0900 Subject: [PATCH 116/124] =?UTF-8?q?Feat:=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=EC=8A=AC=EB=9D=BC=EC=9D=B4=EB=93=9C=EC=97=90=20total=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80,=20refactoring=20(#153)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat: Change dto name, and add total to AdminSlidesResponse. * Feat: Add pageSize as parameter. * Feat: move readAllSlides to news repository, add total query. * Feat: Extract getNewsEntityByIdOrThrow * Feat: Move readAllSlides and unSlideManyNews logic handling to news service. * Comment: Add comment for refactoring. * Test: Add test for read all slides. * Refactor: Linting. * Feat: Change pageNum to start at 1. --------- Co-authored-by: Junhyeong Kim --- .../csereal/core/admin/api/AdminController.kt | 7 +- .../core/admin/database/AdminRepository.kt | 32 ------- ...{SlideResponse.kt => AdminSlideElement.kt} | 2 +- .../core/admin/dto/AdminSlidesResponse.kt | 6 ++ .../core/admin/service/AdminService.kt | 27 +++--- .../core/news/database/NewsRepository.kt | 37 +++++++- .../csereal/core/news/service/NewsService.kt | 27 +++++- .../csereal/core/news/NewsServiceTest.kt | 86 +++++++++++++++++++ 8 files changed, 166 insertions(+), 58 deletions(-) delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/admin/database/AdminRepository.kt rename src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/{SlideResponse.kt => AdminSlideElement.kt} (84%) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/AdminSlidesResponse.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/api/AdminController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/api/AdminController.kt index 5f2cce16..979b371d 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admin/api/AdminController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/api/AdminController.kt @@ -19,9 +19,10 @@ class AdminController( @AuthenticatedStaff @GetMapping("/slide") fun readAllSlides( - @RequestParam(required = false, defaultValue = "0") pageNum: Long - ): ResponseEntity> { - return ResponseEntity.ok(adminService.readAllSlides(pageNum)) + @RequestParam(required = false, defaultValue = "1") pageNum: Long, + @RequestParam(required = false, defaultValue = "40") pageSize: Int + ): ResponseEntity { + return ResponseEntity.ok(adminService.readAllSlides(pageNum - 1, pageSize)) } @AuthenticatedStaff diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/database/AdminRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/database/AdminRepository.kt deleted file mode 100644 index 25d8d024..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admin/database/AdminRepository.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.wafflestudio.csereal.core.admin.database - -import com.querydsl.core.types.Projections -import com.querydsl.jpa.impl.JPAQueryFactory -import com.wafflestudio.csereal.core.admin.dto.SlideResponse -import com.wafflestudio.csereal.core.news.database.QNewsEntity.newsEntity -import org.springframework.stereotype.Component - -interface AdminRepository { - fun readAllSlides(pageNum: Long): List -} - -@Component -class AdminRepositoryImpl( - private val queryFactory: JPAQueryFactory -) : AdminRepository { - override fun readAllSlides(pageNum: Long): List { - return queryFactory.select( - Projections.constructor( - SlideResponse::class.java, - newsEntity.id, - newsEntity.title, - newsEntity.createdAt - ) - ).from(newsEntity) - .where(newsEntity.isDeleted.eq(false), newsEntity.isPrivate.eq(false), newsEntity.isSlide.eq(true)) - .orderBy(newsEntity.createdAt.desc()) - .offset(40 * pageNum) - .limit(40) - .fetch() - } -} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/SlideResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/AdminSlideElement.kt similarity index 84% rename from src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/SlideResponse.kt rename to src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/AdminSlideElement.kt index cea89ab9..77e90b06 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/SlideResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/AdminSlideElement.kt @@ -2,7 +2,7 @@ package com.wafflestudio.csereal.core.admin.dto import java.time.LocalDateTime -class SlideResponse( +data class AdminSlideElement( val id: Long, val title: String, val createdAt: LocalDateTime? diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/AdminSlidesResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/AdminSlidesResponse.kt new file mode 100644 index 00000000..61d7c00c --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/AdminSlidesResponse.kt @@ -0,0 +1,6 @@ +package com.wafflestudio.csereal.core.admin.dto + +data class AdminSlidesResponse( + val total: Long, + val slides: List = listOf() +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/service/AdminService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/service/AdminService.kt index b831309d..721fed89 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admin/service/AdminService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/service/AdminService.kt @@ -1,12 +1,11 @@ package com.wafflestudio.csereal.core.admin.service import com.wafflestudio.csereal.common.CserealException -import com.wafflestudio.csereal.core.admin.database.AdminRepository import com.wafflestudio.csereal.core.admin.dto.ImportantDto import com.wafflestudio.csereal.core.admin.dto.ImportantResponse -import com.wafflestudio.csereal.core.admin.dto.SlideResponse -import com.wafflestudio.csereal.core.news.database.NewsEntity +import com.wafflestudio.csereal.core.admin.dto.AdminSlidesResponse import com.wafflestudio.csereal.core.news.database.NewsRepository +import com.wafflestudio.csereal.core.news.service.NewsService import com.wafflestudio.csereal.core.notice.database.NoticeRepository import com.wafflestudio.csereal.core.seminar.database.SeminarRepository import org.springframework.data.repository.findByIdOrNull @@ -14,7 +13,7 @@ import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional interface AdminService { - fun readAllSlides(pageNum: Long): List + fun readAllSlides(pageNum: Long, pageSize: Int): AdminSlidesResponse fun unSlideManyNews(request: List) fun readAllImportants(pageNum: Long): List fun makeNotImportants(request: List) @@ -22,25 +21,20 @@ interface AdminService { @Service class AdminServiceImpl( - private val adminRepository: AdminRepository, + private val newsService: NewsService, private val noticeRepository: NoticeRepository, private val newsRepository: NewsRepository, private val seminarRepository: SeminarRepository ) : AdminService { - @Transactional - override fun readAllSlides(pageNum: Long): List { - return adminRepository.readAllSlides(pageNum) - } + @Transactional(readOnly = true) + override fun readAllSlides(pageNum: Long, pageSize: Int): AdminSlidesResponse = + newsService.readAllSlides(pageNum, pageSize) @Transactional - override fun unSlideManyNews(request: List) { - for (newsId in request) { - val news: NewsEntity = newsRepository.findByIdOrNull(newsId) - ?: throw CserealException.Csereal404("존재하지 않는 새소식입니다.(newsId=$newsId)") - news.isSlide = false - } - } + override fun unSlideManyNews(request: List) = + newsService.unSlideManyNews(request) + // TODO: 각 도메인의 Service로 구현, Service method 이용하기 @Transactional override fun readAllImportants(pageNum: Long): List { val importantResponses: MutableList = mutableListOf() @@ -81,6 +75,7 @@ class AdminServiceImpl( return importantResponses } + // TODO: 각 도메인의 Service로 구현, Service method 이용하기 @Transactional override fun makeNotImportants(request: List) { for (important in request) { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt index d9ba9b8d..43da67ea 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt @@ -4,6 +4,8 @@ import com.querydsl.core.BooleanBuilder import com.querydsl.jpa.impl.JPAQueryFactory import com.wafflestudio.csereal.common.repository.CommonRepository import com.wafflestudio.csereal.common.utils.FixedPageRequest +import com.wafflestudio.csereal.core.admin.dto.AdminSlideElement +import com.wafflestudio.csereal.core.admin.dto.AdminSlidesResponse import com.wafflestudio.csereal.core.news.database.QNewsEntity.newsEntity import com.wafflestudio.csereal.core.news.database.QNewsTagEntity.newsTagEntity import com.wafflestudio.csereal.core.news.database.QTagInNewsEntity.tagInNewsEntity @@ -16,7 +18,7 @@ import com.wafflestudio.csereal.core.resource.mainImage.database.QMainImageEntit import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService import org.springframework.data.domain.Pageable import org.springframework.data.jpa.repository.JpaRepository -import org.springframework.stereotype.Component +import org.springframework.stereotype.Repository import java.time.LocalDateTime interface NewsRepository : JpaRepository, CustomNewsRepository { @@ -41,9 +43,11 @@ interface CustomNewsRepository { amount: Int, imageUrlCreator: (MainImageEntity?) -> String? ): NewsTotalSearchDto + + fun readAllSlides(pageNum: Long, pageSize: Int): AdminSlidesResponse } -@Component +@Repository class NewsRepositoryImpl( private val queryFactory: JPAQueryFactory, private val mainImageService: MainImageService, @@ -181,4 +185,33 @@ class NewsRepositoryImpl( } ) } + + override fun readAllSlides(pageNum: Long, pageSize: Int): AdminSlidesResponse { + val tuple = queryFactory.select( + newsEntity.id, + newsEntity.title, + newsEntity.createdAt + ).from(newsEntity) + .where(newsEntity.isDeleted.eq(false), newsEntity.isPrivate.eq(false), newsEntity.isSlide.eq(true)) + .orderBy(newsEntity.createdAt.desc()) + .offset(pageSize * pageNum) + .limit(pageSize.toLong()) + .fetch() + + val total = queryFactory.select(newsEntity.count()) + .from(newsEntity) + .where(newsEntity.isDeleted.eq(false), newsEntity.isPrivate.eq(false), newsEntity.isSlide.eq(true)) + .fetchOne()!! + + return AdminSlidesResponse( + total, + tuple.map { + AdminSlideElement( + id = it[newsEntity.id]!!, + title = it[newsEntity.title]!!, + createdAt = it[newsEntity.createdAt]!! + ) + } + ) + } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt index b09cb72d..8bd1448c 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt @@ -1,6 +1,7 @@ package com.wafflestudio.csereal.core.news.service import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.admin.dto.AdminSlidesResponse import com.wafflestudio.csereal.core.news.database.* import com.wafflestudio.csereal.core.news.dto.NewsDto import com.wafflestudio.csereal.core.news.dto.NewsSearchResponse @@ -34,6 +35,8 @@ interface NewsService { fun deleteNews(newsId: Long) fun enrollTag(tagName: String) fun searchTotalNews(keyword: String, number: Int, amount: Int): NewsTotalSearchDto + fun readAllSlides(pageNum: Long, pageSize: Int): AdminSlidesResponse + fun unSlideManyNews(request: List) } @Service @@ -120,8 +123,7 @@ class NewsServiceImpl( newMainImage: MultipartFile?, newAttachments: List? ): NewsDto { - val news: NewsEntity = newsRepository.findByIdOrNull(newsId) - ?: throw CserealException.Csereal404("존재하지 않는 새소식입니다. (newsId: $newsId)") + val news: NewsEntity = getNewsEntityByIdOrThrow(newsId) if (news.isDeleted) throw CserealException.Csereal404("삭제된 새소식입니다.") news.update(request) @@ -161,8 +163,7 @@ class NewsServiceImpl( @Transactional override fun deleteNews(newsId: Long) { - val news: NewsEntity = newsRepository.findByIdOrNull(newsId) - ?: throw CserealException.Csereal404("존재하지 않는 새소식입니다.(newsId: $newsId") + val news: NewsEntity = getNewsEntityByIdOrThrow(newsId) news.isDeleted = true } @@ -173,4 +174,22 @@ class NewsServiceImpl( ) tagInNewsRepository.save(newTag) } + + @Transactional(readOnly = true) + override fun readAllSlides(pageNum: Long, pageSize: Int): AdminSlidesResponse { + return newsRepository.readAllSlides(pageNum, pageSize) + } + + @Transactional + override fun unSlideManyNews(request: List) { + for (newsId in request) { + val news = getNewsEntityByIdOrThrow(newsId) + news.isSlide = false + } + } + + fun getNewsEntityByIdOrThrow(newsId: Long): NewsEntity { + return newsRepository.findByIdOrNull(newsId) + ?: throw CserealException.Csereal404("존재하지 않는 새소식입니다.(newsId: $newsId)") + } } diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/news/NewsServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/news/NewsServiceTest.kt index 0b280e91..348a64d8 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/news/NewsServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/news/NewsServiceTest.kt @@ -5,6 +5,8 @@ import com.wafflestudio.csereal.core.news.database.NewsRepository import com.wafflestudio.csereal.core.news.dto.NewsDto import com.wafflestudio.csereal.core.news.service.NewsService import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.extensions.spring.SpringTestExtension +import io.kotest.extensions.spring.SpringTestLifecycleMode import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import org.springframework.boot.test.context.SpringBootTest @@ -17,6 +19,7 @@ class NewsServiceTest( private val newsRepository: NewsRepository ) : BehaviorSpec() { init { + extensions(SpringTestExtension(SpringTestLifecycleMode.Root)) afterSpec { newsRepository.deleteAll() @@ -113,5 +116,88 @@ class NewsServiceTest( } } } + + Given("Slide된 뉴스 2개, Slide되지 않은 뉴스가 2개 있을 때") { + val newsEntitySlide1 = newsRepository.save( + NewsEntity( + title = "title", + titleForMain = null, + description = """ +

Hello, World!

+

This is news description.

+

Goodbye, World!

+ """.trimIndent(), + plainTextDescription = "Hello, World! This is news description. Goodbye, World!", + date = LocalDateTime.now(), + isPrivate = false, + isSlide = true, + isImportant = false + ) + ) + + val newsEntitySlide2 = newsRepository.save( + NewsEntity( + title = "title", + titleForMain = null, + description = """ +

Hello, World!

+

This is news description.

+

Goodbye, World!

+ """.trimIndent(), + plainTextDescription = "Hello, World! This is news description. Goodbye, World!", + date = LocalDateTime.now(), + isPrivate = false, + isSlide = true, + isImportant = false + ) + ) + + val newsEntityNoSlide1 = newsRepository.save( + NewsEntity( + title = "title", + titleForMain = null, + description = """ +

Hello, World!

+

This is news description.

+

Goodbye, World!

+ """.trimIndent(), + plainTextDescription = "Hello, World! This is news description. Goodbye, World!", + date = LocalDateTime.now(), + isPrivate = false, + isSlide = false, + isImportant = false + ) + ) + + val newsEntityNoSlide2 = newsRepository.save( + NewsEntity( + title = "title", + titleForMain = null, + description = """ +

Hello, World!

+

This is news description.

+

Goodbye, World!

+ """.trimIndent(), + plainTextDescription = "Hello, World! This is news description. Goodbye, World!", + date = LocalDateTime.now(), + isPrivate = false, + isSlide = false, + isImportant = false + ) + ) + + When("Slide한 뉴스 2페이지를 가져오면") { + val response = newsService.readAllSlides(1, 1) + + Then("Slide된 뉴스 2개 중 먼저 생성된 1개가 나와야 한다.") { + response.slides.size shouldBe 1 + response.slides.first().id shouldBe newsEntitySlide1.id + } + + Then("총 개수가 2개가 나와야 한다.") { + response.total shouldBe 2 + } + } + } } } From 3ad4b9130a50a9782992961dd72b4bf342b451c3 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Fri, 22 Sep 2023 13:43:16 +0900 Subject: [PATCH 117/124] =?UTF-8?q?fix:=20=EC=84=B8=EB=AF=B8=EB=82=98=20is?= =?UTF-8?q?YearLast=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95=20(#155)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 세미나 isYearFirst 로직 수정 * fix: isYearLast로 재변경 및 로직 수정 --- .../csereal/core/seminar/database/SeminarRepository.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt index 98545085..54c8145d 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt @@ -89,9 +89,9 @@ class SeminarRepositoryImpl( for (i: Int in 0 until seminarEntityList.size) { var isYearLast = false - if (i == seminarEntityList.size - 1) { + if (i == 0) { isYearLast = true - } else if (seminarEntityList[i].startDate?.year != seminarEntityList[i + 1].startDate?.year) { + } else if (seminarEntityList[i].startDate?.year != seminarEntityList[i - 1].startDate?.year) { isYearLast = true } From 5f6b6f342742e27d39442477162bc069d66c3243 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Fri, 22 Sep 2023 14:37:56 +0900 Subject: [PATCH 118/124] =?UTF-8?q?fix:=20=EC=9D=B4=EC=A0=84=EA=B8=80=20?= =?UTF-8?q?=EB=8B=A4=EC=9D=8C=20=EA=B8=80=20isDelete,isPrivate=20=EC=A1=B0?= =?UTF-8?q?=EA=B1=B4=20=EC=B6=94=EA=B0=80=20(#151)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 이전글 다음 글 isDelete 추가 * fix: ktlint 수정 --------- Co-authored-by: 우혁준 (HyukJoon Woo) Co-authored-by: Junhyeong Kim --- .../csereal/core/news/database/NewsRepository.kt | 9 +++++++-- .../csereal/core/news/service/NewsService.kt | 10 ++++++++-- .../csereal/core/notice/database/NoticeRepository.kt | 12 ++++++++---- .../csereal/core/notice/service/NoticeService.kt | 8 ++++++-- .../core/seminar/database/SeminarRepository.kt | 8 ++++++-- .../csereal/core/seminar/service/SeminarService.kt | 8 ++++++-- 6 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt index 43da67ea..964e8273 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt @@ -24,8 +24,13 @@ import java.time.LocalDateTime interface NewsRepository : JpaRepository, CustomNewsRepository { fun findAllByIsPrivateFalseAndIsImportantTrueAndIsDeletedFalse(): List fun findAllByIsImportantTrueAndIsDeletedFalse(): List - fun findFirstByCreatedAtLessThanOrderByCreatedAtDesc(timestamp: LocalDateTime): NewsEntity? - fun findFirstByCreatedAtGreaterThanOrderByCreatedAtAsc(timestamp: LocalDateTime): NewsEntity? + fun findFirstByIsDeletedFalseAndIsPrivateFalseAndCreatedAtLessThanOrderByCreatedAtDesc( + timestamp: LocalDateTime + ): NewsEntity? + + fun findFirstByIsDeletedFalseAndIsPrivateFalseAndCreatedAtGreaterThanOrderByCreatedAtAsc( + timestamp: LocalDateTime + ): NewsEntity? } interface CustomNewsRepository { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt index 8bd1448c..9bddd8e3 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt @@ -80,8 +80,14 @@ class NewsServiceImpl( val imageURL = mainImageService.createImageURL(news.mainImage) val attachmentResponses = attachmentService.createAttachmentResponses(news.attachments) - val prevNews = newsRepository.findFirstByCreatedAtLessThanOrderByCreatedAtDesc(news.createdAt!!) - val nextNews = newsRepository.findFirstByCreatedAtGreaterThanOrderByCreatedAtAsc(news.createdAt!!) + val prevNews = + newsRepository.findFirstByIsDeletedFalseAndIsPrivateFalseAndCreatedAtLessThanOrderByCreatedAtDesc( + news.createdAt!! + ) + val nextNews = + newsRepository.findFirstByIsDeletedFalseAndIsPrivateFalseAndCreatedAtGreaterThanOrderByCreatedAtAsc( + news.createdAt!! + ) return NewsDto.of(news, imageURL, attachmentResponses, prevNews, nextNews) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt index a4a531dd..ec4acfa5 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt @@ -19,8 +19,13 @@ import java.time.LocalDateTime interface NoticeRepository : JpaRepository, CustomNoticeRepository { fun findAllByIsPrivateFalseAndIsImportantTrueAndIsDeletedFalse(): List fun findAllByIsImportantTrueAndIsDeletedFalse(): List - fun findFirstByCreatedAtLessThanOrderByCreatedAtDesc(timestamp: LocalDateTime): NoticeEntity? - fun findFirstByCreatedAtGreaterThanOrderByCreatedAtAsc(timestamp: LocalDateTime): NoticeEntity? + fun findFirstByIsDeletedFalseAndIsPrivateFalseAndCreatedAtLessThanOrderByCreatedAtDesc( + timestamp: LocalDateTime + ): NoticeEntity? + + fun findFirstByIsDeletedFalseAndIsPrivateFalseAndCreatedAtGreaterThanOrderByCreatedAtAsc( + timestamp: LocalDateTime + ): NoticeEntity? } interface CustomNoticeRepository { @@ -123,8 +128,7 @@ class NoticeRepositoryImpl( noticeEntity.attachments.isNotEmpty, noticeEntity.isPrivate ) - ) - .from(noticeEntity) + ).from(noticeEntity) .leftJoin(noticeTagEntity).on(noticeTagEntity.notice.eq(noticeEntity)) .where(noticeEntity.isDeleted.eq(false)) .where(keywordBooleanBuilder, tagsBooleanBuilder, isPrivateBooleanBuilder) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt index 59b62670..85ab88af 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt @@ -75,9 +75,13 @@ class NoticeServiceImpl( val attachmentResponses = attachmentService.createAttachmentResponses(notice.attachments) val prevNotice = - noticeRepository.findFirstByCreatedAtLessThanOrderByCreatedAtDesc(notice.createdAt!!) + noticeRepository.findFirstByIsDeletedFalseAndIsPrivateFalseAndCreatedAtLessThanOrderByCreatedAtDesc( + notice.createdAt!! + ) val nextNotice = - noticeRepository.findFirstByCreatedAtGreaterThanOrderByCreatedAtAsc(notice.createdAt!!) + noticeRepository.findFirstByIsDeletedFalseAndIsPrivateFalseAndCreatedAtGreaterThanOrderByCreatedAtAsc( + notice.createdAt!! + ) return NoticeDto.of(notice, attachmentResponses, prevNotice, nextNotice) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt index 54c8145d..f7431267 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt @@ -16,8 +16,12 @@ import java.time.LocalDateTime interface SeminarRepository : JpaRepository, CustomSeminarRepository { fun findAllByIsPrivateFalseAndIsImportantTrueAndIsDeletedFalse(): List fun findAllByIsImportantTrueAndIsDeletedFalse(): List - fun findFirstByCreatedAtLessThanAndIsPrivateFalseOrderByCreatedAtDesc(timestamp: LocalDateTime): SeminarEntity? - fun findFirstByCreatedAtGreaterThanAndIsPrivateFalseOrderByCreatedAtAsc(timestamp: LocalDateTime): SeminarEntity? + fun findFirstByIsDeletedFalseAndIsPrivateFalseAndCreatedAtLessThanOrderByCreatedAtDesc( + timestamp: LocalDateTime + ): SeminarEntity? + fun findFirstByIsDeletedFalseAndIsPrivateFalseAndCreatedAtGreaterThanOrderByCreatedAtAsc( + timestamp: LocalDateTime + ): SeminarEntity? } interface CustomSeminarRepository { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt index d8348849..1d78991e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt @@ -83,9 +83,13 @@ class SeminarServiceImpl( val attachmentResponses = attachmentService.createAttachmentResponses(seminar.attachments) val prevSeminar = - seminarRepository.findFirstByCreatedAtLessThanAndIsPrivateFalseOrderByCreatedAtDesc(seminar.createdAt!!) + seminarRepository.findFirstByIsDeletedFalseAndIsPrivateFalseAndCreatedAtLessThanOrderByCreatedAtDesc( + seminar.createdAt!! + ) val nextSeminar = - seminarRepository.findFirstByCreatedAtGreaterThanAndIsPrivateFalseOrderByCreatedAtAsc(seminar.createdAt!!) + seminarRepository.findFirstByIsDeletedFalseAndIsPrivateFalseAndCreatedAtGreaterThanOrderByCreatedAtAsc( + seminar.createdAt!! + ) return SeminarDto.of(seminar, imageURL, attachmentResponses, prevSeminar, nextSeminar) } From c82ea8621e0e5ead737f72c4aacdd4c09fdc9df8 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Fri, 22 Sep 2023 18:11:16 +0900 Subject: [PATCH 119/124] =?UTF-8?q?fix:=20=EB=A9=94=EC=9D=B8=20=EA=B3=B5?= =?UTF-8?q?=EC=A7=80=20=EC=A0=95=EB=A0=AC=20(#158)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wafflestudio/csereal/core/main/database/MainRepository.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt index e7bda26d..776f13f5 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt @@ -64,7 +64,7 @@ class MainRepositoryImpl( ) ).from(noticeEntity) .where(noticeEntity.isDeleted.eq(false), noticeEntity.isPrivate.eq(false)) - .orderBy(noticeEntity.isPinned.desc()).orderBy(noticeEntity.createdAt.desc()) + .orderBy(noticeEntity.createdAt.desc()) .limit(6).fetch() } @@ -82,7 +82,7 @@ class MainRepositoryImpl( .rightJoin(tagInNoticeEntity).on(noticeTagEntity.tag.eq(tagInNoticeEntity)) .where(noticeTagEntity.tag.name.eq(tagEnum)) .where(noticeEntity.isDeleted.eq(false), noticeEntity.isPrivate.eq(false)) - .orderBy(noticeEntity.isPinned.desc()).orderBy(noticeTagEntity.notice.createdAt.desc()) + .orderBy(noticeTagEntity.notice.createdAt.desc()) .limit(6).distinct().fetch() } From d04db993a5648616ed39c4e939972a0f89eb33b4 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Fri, 22 Sep 2023 18:16:04 +0900 Subject: [PATCH 120/124] =?UTF-8?q?fix:=20=EC=98=88=EC=95=BD=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EA=B6=8C=ED=95=9C=20=EC=88=98=EC=A0=95=20(#157)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/api/ReservationController.kt | 22 ++++++++++++++----- .../reservation/database/ReservationEntity.kt | 2 +- .../core/reservation/dto/ReservationDto.kt | 3 ++- .../reservation/service/ReservationService.kt | 19 ++++++---------- 4 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservationController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservationController.kt index 7bc843c1..c48efc8e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservationController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservationController.kt @@ -5,7 +5,11 @@ import com.wafflestudio.csereal.core.reservation.dto.ReservationDto import com.wafflestudio.csereal.core.reservation.dto.ReserveRequest import com.wafflestudio.csereal.core.reservation.dto.SimpleReservationDto import com.wafflestudio.csereal.core.reservation.service.ReservationService +import com.wafflestudio.csereal.core.user.database.Role +import com.wafflestudio.csereal.core.user.database.UserRepository import org.springframework.http.ResponseEntity +import org.springframework.security.core.annotation.AuthenticationPrincipal +import org.springframework.security.oauth2.core.oidc.user.OidcUser import org.springframework.web.bind.annotation.DeleteMapping import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable @@ -20,10 +24,10 @@ import java.util.UUID @RequestMapping("/api/v1/reservation") @RestController class ReservationController( - private val reservationService: ReservationService + private val reservationService: ReservationService, + private val userRepository: UserRepository ) { - // @AuthenticatedForReservation TODO: CBT 끝나면 주석 제거 @GetMapping("/month") fun getMonthlyReservations( @RequestParam roomId: Long, @@ -35,7 +39,6 @@ class ReservationController( return ResponseEntity.ok(reservationService.getRoomReservationsBetween(roomId, start, end)) } - // @AuthenticatedForReservation @GetMapping("/week") fun getWeeklyReservations( @RequestParam roomId: Long, @@ -48,10 +51,17 @@ class ReservationController( return ResponseEntity.ok(reservationService.getRoomReservationsBetween(roomId, start, end)) } - // @AuthenticatedForReservation @GetMapping("/{reservationId}") - fun getReservation(@PathVariable reservationId: Long): ResponseEntity { - return ResponseEntity.ok(reservationService.getReservation(reservationId)) + fun getReservation( + @PathVariable reservationId: Long, + @AuthenticationPrincipal oidcUser: OidcUser? + ): ResponseEntity { + val isStaff = oidcUser?.let { + val username = it.idToken.getClaim("username") + val user = userRepository.findByUsername(username) + user?.role == Role.ROLE_STAFF + } ?: false + return ResponseEntity.ok(reservationService.getReservation(reservationId, isStaff)) } @PostMapping diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/ReservationEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/ReservationEntity.kt index 2b08ae95..95a7e22f 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/ReservationEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/ReservationEntity.kt @@ -33,7 +33,7 @@ class ReservationEntity( val professor: String, val recurringWeeks: Int = 1, - val recurrenceId: UUID? = null + val recurrenceId: UUID ) : BaseTimeEntity() { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReservationDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReservationDto.kt index 39d22019..f6820e63 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReservationDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReservationDto.kt @@ -6,7 +6,7 @@ import java.util.UUID data class ReservationDto( val id: Long, - val recurrenceId: UUID? = null, + val recurrenceId: UUID, val title: String, val purpose: String, val startTime: LocalDateTime, @@ -41,6 +41,7 @@ data class ReservationDto( fun forNormalUser(reservationEntity: ReservationEntity): ReservationDto { return ReservationDto( id = reservationEntity.id, + recurrenceId = reservationEntity.recurrenceId, title = reservationEntity.title, purpose = reservationEntity.purpose, startTime = reservationEntity.startTime, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/service/ReservationService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/service/ReservationService.kt index 59b4ce35..30fda4fe 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/service/ReservationService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/service/ReservationService.kt @@ -17,7 +17,7 @@ import java.util.* interface ReservationService { fun reserveRoom(reserveRequest: ReserveRequest): List fun getRoomReservationsBetween(roomId: Long, start: LocalDateTime, end: LocalDateTime): List - fun getReservation(reservationId: Long): ReservationDto + fun getReservation(reservationId: Long, isStaff: Boolean): ReservationDto fun cancelSpecific(reservationId: Long) fun cancelRecurring(recurrenceId: UUID) } @@ -78,20 +78,15 @@ class ReservationServiceImpl( } @Transactional(readOnly = true) - override fun getReservation(reservationId: Long): ReservationDto { + override fun getReservation(reservationId: Long, isStaff: Boolean): ReservationDto { val reservationEntity = reservationRepository.findByIdOrNull(reservationId) ?: throw CserealException.Csereal404("예약을 찾을 수 없습니다.") -// val user = RequestContextHolder.getRequestAttributes()?.getAttribute( -// "loggedInUser", -// RequestAttributes.SCOPE_REQUEST -// ) as UserEntity -// -// if (user.role == Role.ROLE_STAFF) { -// return ReservationDto.of(reservationEntity) -// } else { - return ReservationDto.forNormalUser(reservationEntity) -// } + return if (isStaff) { + ReservationDto.of(reservationEntity) + } else { + ReservationDto.forNormalUser(reservationEntity) + } } override fun cancelSpecific(reservationId: Long) { From b1a35682a74f5e85780e0c39ce2b7fa76a6be00f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=ED=98=81=EC=A4=80=20=28HyukJoon=20Woo=29?= Date: Fri, 22 Sep 2023 19:19:17 +0900 Subject: [PATCH 121/124] =?UTF-8?q?Feat:=20=ED=86=B5=ED=95=A9=EA=B2=80?= =?UTF-8?q?=EC=83=89=20=EC=A0=95=EB=A0=AC=20=EC=B5=9C=EC=8B=A0=EC=88=9C=20?= =?UTF-8?q?(#156)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../csereal/core/news/database/NewsRepository.kt | 1 + .../csereal/core/notice/database/NoticeRepository.kt | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt index 964e8273..a27805d1 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt @@ -152,6 +152,7 @@ class NewsRepositoryImpl( ).from(newsEntity) .leftJoin(mainImageEntity) .where(doubleTemplate.gt(0.0)) + .orderBy(newsEntity.date.desc()) .limit(number.toLong()) .fetch() diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt index ec4acfa5..17269405 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt @@ -66,7 +66,10 @@ class NoticeRepositoryImpl( val total = query.clone().select(noticeEntity.countDistinct()).fetchOne()!! - val searchResult = query.limit(number.toLong()).fetch() + val searchResult = query + .orderBy(noticeEntity.createdAt.desc()) + .limit(number.toLong()) + .fetch() return NoticeTotalSearchResponse( total.toInt(), From 0129351d7796fb7c21c3682a56fed3aaba64a788 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=ED=98=81=EC=A4=80=20=28HyukJoon=20Woo=29?= Date: Fri, 22 Sep 2023 19:40:20 +0900 Subject: [PATCH 122/124] =?UTF-8?q?Feat:=20Admin=20Important=EC=97=90=20pa?= =?UTF-8?q?gination=20=EC=B6=94=EA=B0=80,=20total=20=EC=B6=94=EA=B0=80.=20?= =?UTF-8?q?(#159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../csereal/core/admin/api/AdminController.kt | 9 ++- .../core/admin/database/AdminRepository.kt | 68 +++++++++++++++++++ ...ntResponse.kt => AdminImportantElement.kt} | 2 +- .../core/admin/dto/AdminImportantResponse.kt | 6 ++ .../core/admin/service/AdminService.kt | 53 ++++----------- 5 files changed, 94 insertions(+), 44 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/admin/database/AdminRepository.kt rename src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/{ImportantResponse.kt => AdminImportantElement.kt} (84%) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/AdminImportantResponse.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/api/AdminController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/api/AdminController.kt index 979b371d..276ece56 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admin/api/AdminController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/api/AdminController.kt @@ -36,9 +36,12 @@ class AdminController( @AuthenticatedStaff @GetMapping("/important") fun readAllImportants( - @RequestParam(required = false, defaultValue = "0") pageNum: Long - ): ResponseEntity> { - return ResponseEntity.ok(adminService.readAllImportants(pageNum)) + @RequestParam(required = false, defaultValue = "1") pageNum: Int, + @RequestParam(required = false, defaultValue = "40") pageSize: Int + ): ResponseEntity { + return ResponseEntity.ok( + adminService.readAllImportants(pageNum - 1, pageSize) + ) } @AuthenticatedStaff diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/database/AdminRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/database/AdminRepository.kt new file mode 100644 index 00000000..caf68c3e --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/database/AdminRepository.kt @@ -0,0 +1,68 @@ +package com.wafflestudio.csereal.core.admin.database + +import com.wafflestudio.csereal.core.admin.dto.AdminImportantElement +import jakarta.persistence.EntityManagerFactory +import org.springframework.stereotype.Repository +import java.sql.Timestamp + +@Repository +class AdminRepository( + private val emf: EntityManagerFactory +) { + fun readImportantsPagination(pageSize: Int, offset: Int): List { + val em = emf.createEntityManager() + val query = em.createNativeQuery( + """ + ( + SELECT id, title, created_at, 'notice' AS type + FROM notice + WHERE (is_deleted = false AND is_important = TRUE) + UNION ALL + SELECT news.id, title, date, 'news' AS type + FROM news + WHERE (is_deleted = false AND is_important = TRUE) + UNION ALL + SELECT seminar.id, title, created_at, 'seminar' AS type + FROM seminar + WHERE (is_deleted = false AND is_important = TRUE) + ) ORDER BY created_at DESC + LIMIT :pageSize OFFSET :offset + """.trimIndent() + ) + query.setParameter("pageSize", pageSize) + query.setParameter("offset", offset) + + val result = query.resultList as List> + val formattedResult = result.map { + AdminImportantElement( + id = it[0] as Long, + title = it[1] as String, + createdAt = (it[2] as Timestamp).toLocalDateTime(), + category = it[3] as String + ) + } + return formattedResult + } + + fun getTotalImportantsCnt(): Long { + val em = emf.createEntityManager() + val query = em.createNativeQuery( + """ + SELECT COUNT(*) FROM ( + SELECT id, title, created_at, 'notice' AS type + FROM notice + WHERE (is_deleted = false AND is_important = TRUE) + UNION ALL + SELECT news.id, title, created_at, 'news' AS type + FROM news + WHERE (is_deleted = false AND is_important = TRUE) + UNION ALL + SELECT seminar.id, title, created_at, 'seminar' AS type + FROM seminar + WHERE (is_deleted = false AND is_important = TRUE) + ) as nn + """.trimIndent() + ) + return query.resultList.first() as Long + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/ImportantResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/AdminImportantElement.kt similarity index 84% rename from src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/ImportantResponse.kt rename to src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/AdminImportantElement.kt index f2fe3586..f706b852 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/ImportantResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/AdminImportantElement.kt @@ -2,7 +2,7 @@ package com.wafflestudio.csereal.core.admin.dto import java.time.LocalDateTime -class ImportantResponse( +data class AdminImportantElement( val id: Long, val title: String, val createdAt: LocalDateTime?, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/AdminImportantResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/AdminImportantResponse.kt new file mode 100644 index 00000000..794adfd0 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/AdminImportantResponse.kt @@ -0,0 +1,6 @@ +package com.wafflestudio.csereal.core.admin.dto + +data class AdminImportantResponse( + val total: Long, + val importants: List = listOf() +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/service/AdminService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/service/AdminService.kt index 721fed89..f5715465 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admin/service/AdminService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/service/AdminService.kt @@ -1,8 +1,9 @@ package com.wafflestudio.csereal.core.admin.service import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.admin.database.AdminRepository import com.wafflestudio.csereal.core.admin.dto.ImportantDto -import com.wafflestudio.csereal.core.admin.dto.ImportantResponse +import com.wafflestudio.csereal.core.admin.dto.AdminImportantResponse import com.wafflestudio.csereal.core.admin.dto.AdminSlidesResponse import com.wafflestudio.csereal.core.news.database.NewsRepository import com.wafflestudio.csereal.core.news.service.NewsService @@ -15,13 +16,14 @@ import org.springframework.transaction.annotation.Transactional interface AdminService { fun readAllSlides(pageNum: Long, pageSize: Int): AdminSlidesResponse fun unSlideManyNews(request: List) - fun readAllImportants(pageNum: Long): List + fun readAllImportants(pageNum: Int, pageSize: Int): AdminImportantResponse fun makeNotImportants(request: List) } @Service class AdminServiceImpl( private val newsService: NewsService, + private val adminRepository: AdminRepository, private val noticeRepository: NoticeRepository, private val newsRepository: NewsRepository, private val seminarRepository: SeminarRepository @@ -34,45 +36,16 @@ class AdminServiceImpl( override fun unSlideManyNews(request: List) = newsService.unSlideManyNews(request) - // TODO: 각 도메인의 Service로 구현, Service method 이용하기 - @Transactional - override fun readAllImportants(pageNum: Long): List { - val importantResponses: MutableList = mutableListOf() - noticeRepository.findAllByIsImportantTrueAndIsDeletedFalse().forEach { - importantResponses.add( - ImportantResponse( - id = it.id, - title = it.title, - createdAt = it.createdAt, - category = "notice" - ) - ) - } - - newsRepository.findAllByIsImportantTrueAndIsDeletedFalse().forEach { - importantResponses.add( - ImportantResponse( - id = it.id, - title = it.title, - createdAt = it.createdAt, - category = "news" - ) - ) - } - - seminarRepository.findAllByIsImportantTrueAndIsDeletedFalse().forEach { - importantResponses.add( - ImportantResponse( - id = it.id, - title = it.title, - createdAt = it.createdAt, - category = "seminar" - ) - ) - } - importantResponses.sortByDescending { it.createdAt } + @Transactional(readOnly = true) + override fun readAllImportants(pageNum: Int, pageSize: Int): AdminImportantResponse { + val offset = pageNum * pageSize + val importantList = adminRepository.readImportantsPagination(pageSize, offset) + val importantTotal = adminRepository.getTotalImportantsCnt() - return importantResponses + return AdminImportantResponse( + total = importantTotal, + importants = importantList + ) } // TODO: 각 도메인의 Service로 구현, Service method 이용하기 From b5db132bc228eb67e784dcf326bd76dc3e3bd869 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Fri, 22 Sep 2023 21:37:01 +0900 Subject: [PATCH 123/124] =?UTF-8?q?fix:=20titleForMain=EC=9D=B4=20?= =?UTF-8?q?=EC=97=86=EC=96=B4=EB=8F=84=20isImportant=20=EB=93=B1=EB=A1=9D?= =?UTF-8?q?=20=EA=B0=80=EB=8A=A5=20(#160)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 우혁준 (HyukJoon Woo) --- .../com/wafflestudio/csereal/core/news/service/NewsService.kt | 4 ---- .../wafflestudio/csereal/core/notice/service/NoticeService.kt | 4 ---- .../csereal/core/seminar/service/SeminarService.kt | 4 ---- 3 files changed, 12 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt index 9bddd8e3..e45a4169 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt @@ -110,10 +110,6 @@ class NewsServiceImpl( attachmentService.uploadAllAttachments(newNews, attachments) } - if (request.isImportant && request.titleForMain.isNullOrEmpty()) { - throw CserealException.Csereal400("중요 제목이 입력되어야 합니다") - } - newsRepository.save(newNews) val imageURL = mainImageService.createImageURL(newNews.mainImage) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt index 85ab88af..b12ea9c2 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt @@ -114,10 +114,6 @@ class NoticeServiceImpl( attachmentService.uploadAllAttachments(newNotice, attachments) } - if (request.isImportant && request.titleForMain.isNullOrEmpty()) { - throw CserealException.Csereal400("중요 제목이 입력되어야 합니다") - } - noticeRepository.save(newNotice) val attachmentResponses = attachmentService.createAttachmentResponses(newNotice.attachments) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt index 1d78991e..a3cccb30 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt @@ -118,10 +118,6 @@ class SeminarServiceImpl( attachmentService.uploadAllAttachments(seminar, newAttachments) } - if (request.isImportant && request.titleForMain.isNullOrEmpty()) { - throw CserealException.Csereal400("중요 제목이 입력되어야 합니다") - } - val attachmentResponses = attachmentService.createAttachmentResponses(seminar.attachments) val imageURL = mainImageService.createImageURL(seminar.mainImage) From 902c45b8b27d2898224773a237c8be824de86b1e Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Sat, 23 Sep 2023 16:53:05 +0900 Subject: [PATCH 124/124] =?UTF-8?q?fix:=20em.close()=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20(#162)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: em.close() 추가 * fix: total에도 추가 --- .../csereal/core/admin/database/AdminRepository.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/database/AdminRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/database/AdminRepository.kt index caf68c3e..08b4b13b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admin/database/AdminRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/database/AdminRepository.kt @@ -41,6 +41,7 @@ class AdminRepository( category = it[3] as String ) } + em.close() return formattedResult } @@ -63,6 +64,8 @@ class AdminRepository( ) as nn """.trimIndent() ) - return query.resultList.first() as Long + val total = query.resultList.first() as Long + em.close() + return total } }