From c9e039a2967741e2a7cb7c0707620d1bad57978f Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Tue, 25 Jul 2023 21:17:12 +0900 Subject: [PATCH 001/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] 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/214] =?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/214] =?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/214] =?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/214] =?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/214] 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/214] [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/214] =?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/214] =?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/214] =?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/214] 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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] 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/214] =?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/214] =?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/214] 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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] 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/214] 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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] 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/214] 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/214] 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/214] =?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/214] =?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/214] =?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/214] 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/214] 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/214] =?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/214] 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/214] =?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/214] 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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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/214] =?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 } } From 85d5222f6754cbd1e2174afc14f0f46293e78362 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Wed, 27 Sep 2023 15:43:45 +0900 Subject: [PATCH 125/214] =?UTF-8?q?feat:=20migrateConference=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#164)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: migrateConferences 추가 * fix: 수정 * fix: 수정 * fix: 검색 추가 * fix: createConferenceDto 삭제 --- .../conference/api/ConferenceController.kt | 15 ++++++--- .../conference/database/ConferenceEntity.kt | 9 +++--- .../database/ConferencePageEntity.kt | 11 ++++++- .../conference/dto/ConferenceCreateDto.kt | 7 ---- .../core/conference/dto/ConferenceDto.kt | 4 ++- .../conference/dto/ConferenceModifyRequest.kt | 4 +-- .../conference/service/ConferenceService.kt | 32 ++++++++++++++++--- .../service/ConferenceServiceTest.kt | 5 ++- 8 files changed, 59 insertions(+), 28 deletions(-) delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceCreateDto.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 e63cff16..8edfed53 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,6 +1,7 @@ package com.wafflestudio.csereal.core.conference.api import com.wafflestudio.csereal.common.aop.AuthenticatedStaff +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.conference.service.ConferenceService @@ -23,10 +24,14 @@ class ConferenceController( fun modifyConferencePage( @RequestBody conferenceModifyRequest: ConferenceModifyRequest ): ResponseEntity { - return ResponseEntity.ok( - conferenceService.modifyConferences( - conferenceModifyRequest - ) - ) + return ResponseEntity.ok(conferenceService.modifyConferences(conferenceModifyRequest)) + } + + @AuthenticatedStaff + @PostMapping("/migrate") + fun migrateConferences( + @RequestBody requestList: List + ): ResponseEntity> { + return ResponseEntity.ok(conferenceService.migrateConferences(requestList)) } } 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 6a3d37b0..a90e6313 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,7 +1,6 @@ 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.* @@ -22,12 +21,12 @@ class ConferenceEntity( ) : BaseTimeEntity() { companion object { fun of( - conferenceCreateDto: ConferenceCreateDto, + conferenceDto: ConferenceDto, conferencePage: ConferencePageEntity ) = ConferenceEntity( - code = conferenceCreateDto.code, - abbreviation = conferenceCreateDto.abbreviation, - name = conferenceCreateDto.name, + code = conferenceDto.code, + abbreviation = conferenceDto.abbreviation, + name = conferenceDto.name, conferencePage = conferencePage ) } 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 28a57a71..1fb9915b 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 @@ -15,4 +15,13 @@ class ConferencePageEntity( @OrderBy("code ASC") val conferences: MutableSet = mutableSetOf() -) : BaseTimeEntity() +) : BaseTimeEntity() { + companion object { + fun of(author: UserEntity): ConferencePageEntity { + return ConferencePageEntity( + author = author, + conferences = mutableSetOf() + ) + } + } +} 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 deleted file mode 100644 index 381ab9e1..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceCreateDto.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.wafflestudio.csereal.core.conference.dto - -data class ConferenceCreateDto( - val code: String, - val abbreviation: String, - 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 b9be02bd..ca427e78 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 @@ -1,9 +1,11 @@ package com.wafflestudio.csereal.core.conference.dto +import com.fasterxml.jackson.annotation.JsonInclude import com.wafflestudio.csereal.core.conference.database.ConferenceEntity data class ConferenceDto( - val id: Long, + @JsonInclude(JsonInclude.Include.NON_NULL) + val id: Long? = null, val code: String, val abbreviation: String, val name: String 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 6d4d10b6..b14c7f3d 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 newConferenceList: List, val modifiedConferenceList: List, - val deleteConfereceIdList: List + val deleteConferenceIdList: List ) 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 1ca66708..6cbe1e4d 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 @@ -5,7 +5,6 @@ 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 @@ -22,6 +21,7 @@ import org.springframework.web.context.request.RequestContextHolder interface ConferenceService { fun getConferencePage(): ConferencePage fun modifyConferences(conferenceModifyRequest: ConferenceModifyRequest): ConferencePage + fun migrateConferences(requestList: List): List } @Service @@ -56,7 +56,7 @@ class ConferenceServiceImpl( modifyConferenceWithoutSave(it) } - val deleteConferenceList = conferenceModifyRequest.deleteConfereceIdList.map { + val deleteConferenceList = conferenceModifyRequest.deleteConferenceIdList.map { deleteConference(it, conferencePage) } @@ -65,13 +65,37 @@ class ConferenceServiceImpl( return ConferencePage.of(conferencePage) } + @Transactional + override fun migrateConferences(requestList: List): List { + val user = RequestContextHolder.getRequestAttributes()?.getAttribute( + "loggedInUser", + RequestAttributes.SCOPE_REQUEST + ) as UserEntity + + val list = mutableListOf() + val conferencePage = ConferencePageEntity.of(user) + conferencePageRepository.save(conferencePage) + for (request in requestList) { + val conference = ConferenceEntity.of(request, conferencePage) + + conferenceRepository.save(conference) + + conferencePage.conferences.add(conference) + + conference.researchSearch = ResearchSearchEntity.create(conference) + list.add(ConferenceDto.of(conference)) + } + + return list + } + @Transactional fun createConferenceWithoutSave( - conferenceCreateDto: ConferenceCreateDto, + conferenceDto: ConferenceDto, conferencePage: ConferencePageEntity ): ConferenceEntity { val newConference = ConferenceEntity.of( - conferenceCreateDto, + conferenceDto, conferencePage ) conferencePage.conferences.add(newConference) 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 7c0ad7a7..206cf76d 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 @@ -4,7 +4,6 @@ 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 @@ -110,13 +109,13 @@ class ConferenceServiceTest( name = "modifiedName", abbreviation = "modifiedAbbreviation" ) - val newConference = ConferenceCreateDto( + val newConference = ConferenceDto( code = "code9", name = "newName", abbreviation = "newAbbreviation" ) val conferenceModifyRequest = ConferenceModifyRequest( - deleteConfereceIdList = listOf(deleteConferenceId), + deleteConferenceIdList = listOf(deleteConferenceId), modifiedConferenceList = listOf(modifiedConference), newConferenceList = listOf(newConference) ) From 193f410eaf7ff60c2159dc4a2adafadd66a8005a 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, 28 Sep 2023 02:07:04 +0900 Subject: [PATCH 126/214] CICD: Add blocking backend api except frontend. (#166) * CICD: Add blocking backend api except frontend. * CICD: Remove login, logout, file api from blacklist. * CICD: Change to just block swagger apis. --- .github/workflows/proxy.yaml | 1 + caddy/Caddyfile | 10 ++++++++-- docker-compose-caddy.yml | 1 + 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/proxy.yaml b/.github/workflows/proxy.yaml index 0e26609d..23742871 100644 --- a/.github/workflows/proxy.yaml +++ b/.github/workflows/proxy.yaml @@ -20,6 +20,7 @@ jobs: name: Create .env file run: | echo "URL=${{secrets.URL}}" > .env + echo "LOCAL_IP=${{secrets.LOCAL_IP}}" > .env - name: SCP Command to Transfer Files diff --git a/caddy/Caddyfile b/caddy/Caddyfile index bb907834..0eeefa95 100644 --- a/caddy/Caddyfile +++ b/caddy/Caddyfile @@ -1,7 +1,13 @@ {$URL} { # Frontend reverse_proxy host.docker.internal:3000 - + + @backend_denied { + path /swagger-ui/* /api-docs/* + not remote_ip {$LOCAL_IP} + } + abort @backend_denied + # Backend reverse_proxy /api/* host.docker.internal:8080 #host.docker.internal:8081 # For blue/green @@ -14,4 +20,4 @@ # 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-caddy.yml b/docker-compose-caddy.yml index 19614d2b..22cce5c9 100644 --- a/docker-compose-caddy.yml +++ b/docker-compose-caddy.yml @@ -11,6 +11,7 @@ services: - ./caddy/config:/config environment: URL: ${URL} + LOCAL_IP: ${LOCAL_IP} extra_hosts: - host.docker.internal:host-gateway restart: always \ No newline at end of file From 7dc9cbb88127733ef6fcca0cd5ba03114d7e369f Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Thu, 28 Sep 2023 10:51:36 +0900 Subject: [PATCH 127/214] =?UTF-8?q?fix:=20group=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EB=B0=98=EC=98=81=20(#167)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../csereal/core/user/database/UserEntity.kt | 2 +- .../user/service/CustomOidcUserService.kt | 36 ++++++++++--------- 2 files changed, 21 insertions(+), 17 deletions(-) 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 a7e1e1d9..731375ca 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,7 +14,7 @@ class UserEntity( val studentId: String, @Enumerated(EnumType.STRING) - val role: Role? + var role: Role? ) : BaseTimeEntity() 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 3a0609a4..f0954e18 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 @@ -25,6 +25,7 @@ class CustomOidcUserService( private val restTemplate: RestTemplate ) : OAuth2UserService { + @Transactional override fun loadUser(userRequest: OidcUserRequest): OidcUser { val oidcUser = DefaultOidcUser( userRequest.clientRegistration.scopes.map { SimpleGrantedAuthority("SCOPE_$it") }, @@ -32,13 +33,26 @@ class CustomOidcUserService( ) val username = oidcUser.idToken.getClaim("username") - val user = userRepository.findByUsername(username) + var user = userRepository.findByUsername(username) if (user == null) { val userInfoAttributes = fetchUserInfo(userRequest) - createUser(username, userInfoAttributes) + user = createUser(username, userInfoAttributes) + } + + val groups = oidcUser.idToken.getClaim>("groups") + 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 } + user.role = role + return oidcUser } @@ -67,31 +81,21 @@ class CustomOidcUserService( return userInfoResponse.body ?: emptyMap() } - @Transactional - fun createUser(username: String, userInfo: Map) { + private fun createUser(username: String, userInfo: Map): UserEntity { 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 + role = null ) userRepository.save(newUser) + + return newUser } } From d62d7f8b5e67fa8605da5fb77a43ec031d838873 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Thu, 28 Sep 2023 15:07:47 +0900 Subject: [PATCH 128/214] =?UTF-8?q?refactor:=20=EC=8B=A4=EC=A0=9C=20?= =?UTF-8?q?=EC=8A=A4=EB=88=84=EC=94=A8=20=EC=84=9C=EB=B2=84=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99=20(#168)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: local-test 중복 항목 공통으로 처리하고 prod에서 오버라이드 * feat: 스누씨 실서버용 환경 변수 설정 * refactor: 로그인 페이지 통합 및 CORS 설정 변경 * fix: CORS --- .github/workflows/deploy.yaml | 26 +- docker-compose-backend.yml | 46 ++-- .../csereal/common/config/SecurityConfig.kt | 10 +- .../csereal/common/config/WebConfig.kt | 10 +- src/main/resources/application.yaml | 236 +++++++++--------- 5 files changed, 160 insertions(+), 168 deletions(-) diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 46513dff..bba8f58f 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -22,30 +22,25 @@ jobs: contents: read steps: - - - name: Checkout + - name: Checkout uses: actions/checkout@v3 - - - name: Setup Java JDK + - name: Setup Java JDK uses: actions/setup-java@v3.12.0 with: java-version: '17' distribution: 'adopt' - - - run: ./gradlew clean bootJar -x test + - run: ./gradlew clean bootJar -x test - - - name: Log in to the Container Registry + - 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 + - name: Build and push Docker image uses: docker/build-push-action@v4.1.1 with: context: . @@ -56,19 +51,17 @@ jobs: ghcr.io/wafflestudio/csereal-server/server_image:latest ghcr.io/wafflestudio/csereal-server/server_image:${{github.sha}} - - - name: Create .env file + - 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 - echo "OIDC_CLIENT_SECRET_DEV=${{secrets.OIDC_CLIENT_SECRET_DEV}}" >> .env + echo "OIDC_CLIENT_SECRET=${{secrets.OIDC_CLIENT_SECRET}}" >> .env echo "URL=${{secrets.URL}}" >> .env - - - name: SCP Command to Transfer Files + - name: SCP Command to Transfer Files uses: appleboy/scp-action@v0.1.4 with: host: ${{secrets.SSH_HOST}} @@ -77,8 +70,7 @@ jobs: source: "docker-compose-backend.yml, .env" target: "~/app" overwrite: true - - - name: SSH Remote Commands + - name: SSH Remote Commands uses: appleboy/ssh-action@v1.0.0 with: host: ${{secrets.SSH_HOST}} diff --git a/docker-compose-backend.yml b/docker-compose-backend.yml index 87d6521a..f1f22d65 100644 --- a/docker-compose-backend.yml +++ b/docker-compose-backend.yml @@ -1,27 +1,27 @@ services: - green: - container_name: csereal_server_green - build: - context: ./ - args: - PROFILE: ${PROFILE} - ports: - - 8080:8080 - volumes: - - ./cse-files:/app/cse-files - - ./files:/app/files + green: + container_name: csereal_server_green + build: + context: ./ + args: + PROFILE: ${PROFILE} + ports: + - 8080:8080 + volumes: + - ./cse-files:/app/cse-files + - ./files:/app/files - 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} - URL: ${URL} - extra_hosts: - - host.docker.internal:host-gateway - restart: always - image: ghcr.io/wafflestudio/csereal-server/server_image:latest - # TODO: Activate after implementing health check + 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: ${OIDC_CLIENT_SECRET} + URL: ${URL} + 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: @@ -42,4 +42,4 @@ services: # extra_hosts: # - host.docker.internal:host-gateway # restart: always -# image: ghcr.io/wafflestudio/csereal-server/server_image:latest \ No newline at end of file +# image: ghcr.io/wafflestudio/csereal-server/server_image:latest 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 6f5c7ff1..08367526 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt @@ -4,6 +4,7 @@ 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.beans.factory.annotation.Value import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @@ -22,7 +23,9 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource @EnableConfigurationProperties(EndpointProperties::class) class SecurityConfig( private val customOidcUserService: CustomOidcUserService, - private val endpointProperties: EndpointProperties + private val endpointProperties: EndpointProperties, + @Value("\${login-page}") + private val loginPage: String ) { @Bean @@ -31,7 +34,7 @@ class SecurityConfig( .cors().and() .csrf().disable() .oauth2Login() - .loginPage("${endpointProperties.frontend}/oauth2/authorization/idsnucse") + .loginPage("$loginPage/oauth2/authorization/idsnucse") .redirectionEndpoint() .baseUri("/api/v1/login/oauth2/code/idsnucse").and() .userInfoEndpoint().oidcUserService(customOidcUserService).and() @@ -66,9 +69,10 @@ class SecurityConfig( @Bean fun corsConfigurationSource(): CorsConfigurationSource { val configuration = CorsConfiguration() - configuration.allowedOrigins = listOf("*") + configuration.allowedOrigins = listOf(endpointProperties.frontend) configuration.allowedMethods = listOf("*") configuration.allowedHeaders = listOf("*") + configuration.allowCredentials = true configuration.maxAge = 3000 val source = UrlBasedCorsConfigurationSource() source.registerCorsConfiguration("/**", configuration) 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 315b1d6a..735cbe0f 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/WebConfig.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/WebConfig.kt @@ -1,17 +1,23 @@ package com.wafflestudio.csereal.common.config +import com.wafflestudio.csereal.common.properties.EndpointProperties +import org.springframework.boot.context.properties.EnableConfigurationProperties 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 { +@EnableConfigurationProperties(EndpointProperties::class) +class WebConfig( + private val endpointProperties: EndpointProperties +) : WebMvcConfigurer { override fun addCorsMappings(registry: CorsRegistry) { registry.addMapping("/**") - .allowedOrigins("*") + .allowedOrigins(endpointProperties.frontend) .allowedMethods("*") .allowedHeaders("*") + .allowCredentials(true) .maxAge(3000) } } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index b9e35aff..36fe2706 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1,148 +1,138 @@ 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 - scope: openid, profile, email - provider: - 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 + 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 + scope: openid, profile, email + redirect-uri: http://localhost:8080/api/v1/login/oauth2/code/idsnucse + provider: + 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: - session: - timeout: 7200 # 2시간 + servlet: + session: + timeout: 7200 # 2시간 springdoc: - paths-to-match: - - /api/** - swagger-ui: - path: index.html - api-docs: - path: /api-docs/json - ---- -spring: - config.activate.on-profile: local - datasource: - 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 - show-sql: true - open-in-view: false - properties: - hibernate: - dialect: com.wafflestudio.csereal.common.config.MySQLDialectCustom - security: - oauth2: - client: - registration: - idsnucse: - redirect-uri: http://localhost:8080/api/v1/login/oauth2/code/idsnucse - -logging.level: - default: INFO - org: - springframework: - security: DEBUG + paths-to-match: + - /api/** + swagger-ui: + path: index.html + api-docs: + path: /api-docs/json csereal: - upload: - path: ./files/ + upload: + path: ./files/ oldFiles: - path: ./cse-files/ + path: ./cse-files/ endpoint: - backend: http://localhost:8080/api - frontend: http://localhost:3000 + backend: http://localhost:8080/api + frontend: http://localhost:3000 + +login-page: http://localhost:8080 + +--- +spring: + config.activate.on-profile: local + datasource: + 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 + show-sql: true + open-in-view: false + properties: + hibernate: + dialect: com.wafflestudio.csereal.common.config.MySQLDialectCustom + +logging.level: + default: INFO + org: + springframework: + security: DEBUG --- spring: - config.activate.on-profile: prod - jpa: - 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: - registration: - idsnucse: - redirect-uri: https://${URL}/api/v1/login/oauth2/code/idsnucse + config.activate.on-profile: prod + jpa: + 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: + registration: + idsnucse: + client-id: cse-waffle-dev + client-secret: ${OIDC_CLIENT_SECRET} + authorization-grant-type: authorization_code + scope: openid, profile, email + redirect-uri: https://${URL}/api/v1/login/oauth2/code/idsnucse + provider: + idsnucse: + issuer-uri: https://id.snucse.org/o + jwk-set-uri: https://id.snucse.org/o/jwks csereal: - upload: - path: /app/files/ + upload: + path: /app/files/ oldFiles: - path: /app/cse-files/ + path: /app/cse-files/ endpoint: - backend: https://${URL}/api - frontend: https://${URL} + backend: https://${URL}/api + frontend: https://${URL} + +login-page: https://${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 - properties: - hibernate: - dialect: org.hibernate.dialect.H2Dialect - h2: - console: - enabled: true - security: - oauth2: - client: - registration: - idsnucse: - redirect-uri: http://localhost:8080/api/v1/login/oauth2/code/idsnucse + 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 + properties: + hibernate: + dialect: org.hibernate.dialect.H2Dialect + h2: + console: + enabled: true logging.level: - default: INFO - org: - springframework: - security: DEBUG - -csereal: - upload: - path: ./files/ - -oldFiles: - path: ./cse-files/ - -endpoint: - backend: http://localhost:8080/api - frontend: http://localhost:3000 + default: INFO + org: + springframework: + security: DEBUG From 29f33975384880f23416f225b8e732c17e167cca Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Fri, 29 Sep 2023 00:25:33 +0900 Subject: [PATCH 129/214] =?UTF-8?q?feat:=20migrateAdmissions=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#165)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: migrateAdmissions 추가 * fix: 잘못 쓴거 수정 --------- Co-authored-by: Junhyeong Kim --- .../admissions/api/AdmissionsController.kt | 8 +++++ .../admissions/database/AdmissionsEntity.kt | 3 ++ .../core/admissions/dto/AdmissionsDto.kt | 4 ++- .../core/admissions/dto/AdmissionsRequest.kt | 6 ++++ .../admissions/service/AdmissionsService.kt | 36 ++++++++++++++++++- 5 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/admissions/dto/AdmissionsRequest.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 index d9f6a733..5b716ef9 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 @@ -2,6 +2,7 @@ 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.dto.AdmissionsRequest import com.wafflestudio.csereal.core.admissions.service.AdmissionsService import jakarta.validation.Valid import org.springframework.http.ResponseEntity @@ -47,4 +48,11 @@ class AdmissionsController( fun readGraduateAdmissions(): ResponseEntity { return ResponseEntity.ok(admissionsService.readGraduateAdmissions()) } + + @PostMapping("/migrate") + fun migrateAdmissions( + @RequestBody requestList: List + ): ResponseEntity> { + return ResponseEntity.ok(admissionsService.migrateAdmissions(requestList)) + } } 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 8510708a..a2f50be2 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 @@ -2,6 +2,7 @@ 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.Column import jakarta.persistence.Entity import jakarta.persistence.EnumType import jakarta.persistence.Enumerated @@ -11,6 +12,8 @@ class AdmissionsEntity( @Enumerated(EnumType.STRING) val postType: AdmissionsPostType, val pageName: String, + + @Column(columnDefinition = "mediumText") val description: String ) : BaseTimeEntity() { companion object { 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 5b8ae40a..890f4825 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,10 +1,12 @@ package com.wafflestudio.csereal.core.admissions.dto +import com.fasterxml.jackson.annotation.JsonInclude import com.wafflestudio.csereal.core.admissions.database.AdmissionsEntity import java.time.LocalDateTime data class AdmissionsDto( - val id: Long, + @JsonInclude(JsonInclude.Include.NON_NULL) + val id: Long? = null, val description: String, val createdAt: LocalDateTime?, val modifiedAt: LocalDateTime? diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/dto/AdmissionsRequest.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/dto/AdmissionsRequest.kt new file mode 100644 index 00000000..4fd1e458 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/dto/AdmissionsRequest.kt @@ -0,0 +1,6 @@ +package com.wafflestudio.csereal.core.admissions.dto + +data class AdmissionsRequest( + val postType: String, + val description: String +) 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 e89b57ee..4f943785 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,10 +1,11 @@ package com.wafflestudio.csereal.core.admissions.service 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.AdmissionsPostType import com.wafflestudio.csereal.core.admissions.database.AdmissionsRepository import com.wafflestudio.csereal.core.admissions.dto.AdmissionsDto +import com.wafflestudio.csereal.core.admissions.dto.AdmissionsRequest import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -13,6 +14,7 @@ interface AdmissionsService { fun createGraduateAdmissions(request: AdmissionsDto): AdmissionsDto fun readUndergraduateAdmissions(postType: String): AdmissionsDto fun readGraduateAdmissions(): AdmissionsDto + fun migrateAdmissions(requestList: List): List } @Service @@ -51,9 +53,11 @@ class AdmissionsServiceImpl( "early" -> AdmissionsDto.of( admissionsRepository.findByPostType(AdmissionsPostType.UNDERGRADUATE_EARLY_ADMISSION) ) + "regular" -> AdmissionsDto.of( admissionsRepository.findByPostType(AdmissionsPostType.UNDERGRADUATE_REGULAR_ADMISSION) ) + else -> throw CserealException.Csereal404("해당하는 페이지를 찾을 수 없습니다.") } } @@ -63,6 +67,36 @@ class AdmissionsServiceImpl( return AdmissionsDto.of(admissionsRepository.findByPostType(AdmissionsPostType.GRADUATE)) } + @Transactional + override fun migrateAdmissions(requestList: List): List { + val list = mutableListOf() + + for (request in requestList) { + val enumPostType = makeStringToAdmissionsPostType(request.postType) + + val pageName = when (enumPostType) { + AdmissionsPostType.UNDERGRADUATE_EARLY_ADMISSION -> "수시 모집" + AdmissionsPostType.UNDERGRADUATE_REGULAR_ADMISSION -> "정시 모집" + AdmissionsPostType.GRADUATE -> "대학원" + else -> throw CserealException.Csereal404("해당하는 페이지를 찾을 수 없습니다.") + } + + val admissionsDto = AdmissionsDto( + id = null, + description = request.description, + createdAt = null, + modifiedAt = null + ) + + val newAdmissions = AdmissionsEntity.of(enumPostType, pageName, admissionsDto) + + admissionsRepository.save(newAdmissions) + + list.add(AdmissionsDto.of(newAdmissions)) + } + return list + } + private fun makeStringToAdmissionsPostType(postType: String): AdmissionsPostType { try { val upperPostType = postType.replace("-", "_").uppercase() From b5864da4350ca07d3cde80ab76f4d303b6610a8f 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, 15 Oct 2023 03:50:48 +0900 Subject: [PATCH 130/214] Feat: Increase speakerurl size (#170) --- .../csereal/core/seminar/database/SeminarEntity.kt | 3 +++ 1 file changed, 3 insertions(+) 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 cb5914ec..870a93bd 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 @@ -34,7 +34,10 @@ class SeminarEntity( // 연사 정보 var name: String, + + @Column(columnDefinition = "varchar(2047)") var speakerURL: String?, + var speakerTitle: String?, var affiliation: String, var affiliationURL: String?, From 20be046a07ee55fac8b842246e50ae758e390337 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Sat, 27 Jan 2024 14:12:41 +0900 Subject: [PATCH 131/214] =?UTF-8?q?fix:=20=EB=A1=9C=EC=BB=AC=EC=97=90?= =?UTF-8?q?=EC=84=9C=EB=8F=84=20idsnucse=EB=A1=9C=20=EC=97=B0=EA=B2=B0=20(?= =?UTF-8?q?#174)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 로컬에서도 idsnucse 바라보게끔 변경 * fix: test에서 idsnucse 접근 x * Revert "fix: test에서 idsnucse 접근 x" This reverts commit cf526452abdc629b433efe701c9fd6ff60893f1e. * Fix: move local security settings to local, not global. * Fix: Disable SecurityConfig on "test" profile --------- Co-authored-by: huGgW --- .../csereal/common/config/SecurityConfig.kt | 2 ++ src/main/resources/application.yaml | 28 +++++++++---------- 2 files changed, 16 insertions(+), 14 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 08367526..f1ca9ef4 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/SecurityConfig.kt @@ -8,6 +8,7 @@ import org.springframework.beans.factory.annotation.Value import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration +import org.springframework.context.annotation.Profile import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity import org.springframework.security.core.Authentication @@ -18,6 +19,7 @@ import org.springframework.web.cors.CorsConfiguration import org.springframework.web.cors.CorsConfigurationSource import org.springframework.web.cors.UrlBasedCorsConfigurationSource +@Profile("!test") @Configuration @EnableWebSecurity @EnableConfigurationProperties(EndpointProperties::class) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 36fe2706..1817e8e4 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -3,20 +3,6 @@ spring: 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 - scope: openid, profile, email - redirect-uri: http://localhost:8080/api/v1/login/oauth2/code/idsnucse - provider: - idsnucse: - issuer-uri: https://id-dev.bacchus.io/o - jwk-set-uri: https://id-dev.bacchus.io/o/jwks servlet: multipart: enabled: true @@ -64,6 +50,20 @@ spring: properties: hibernate: dialect: com.wafflestudio.csereal.common.config.MySQLDialectCustom + security: + oauth2: + client: + registration: + idsnucse: + client-id: cse-waffle-dev + client-secret: ${OIDC_CLIENT_SECRET} + authorization-grant-type: authorization_code + scope: openid, profile, email + redirect-uri: http://localhost:8080/api/v1/login/oauth2/code/idsnucse + provider: + idsnucse: + issuer-uri: https://id.snucse.org/o + jwk-set-uri: https://id.snucse.org/o/jwks logging.level: default: INFO From 01be994bc091fe651cc0673314f5834bee9b410e 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, 3 Feb 2024 22:58:46 +0900 Subject: [PATCH 132/214] =?UTF-8?q?feat:=20Research=20=EA=B2=80=EC=83=89?= =?UTF-8?q?=20(#175)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 로컬에서도 idsnucse 바라보게끔 변경 * feat: enable async. * Feat: create professor event. * Feat: Publish event on professor modification. * Feat: Update research search when listen professor event. * Test: ResearchSearchService listen professor event * fix: test에서 idsnucse 접근 x * Revert "fix: test에서 idsnucse 접근 x" This reverts commit cf526452abdc629b433efe701c9fd6ff60893f1e. * Fix: fixed event listener logic * Feat: Add search api for research. * Test: Fix test. * Fix: move local security settings to local, not global. * Fix: Disable SecurityConfig on "test" profile --------- Co-authored-by: leeeryboy --- .../csereal/CserealApplication.kt | 2 + .../member/event/ProfessorCreatedEvent.kt | 15 + .../member/event/ProfessorDeletedEvent.kt | 15 + .../member/event/ProfessorModifiedEvent.kt | 17 ++ .../core/member/service/ProfessorService.kt | 24 +- .../core/research/api/ResearchController.kt | 22 +- .../database/ResearchSearchRepository.kt | 86 +++++- .../dto/ResearchSearchPageResponse.kt | 18 ++ .../dto/ResearchSearchResponseElement.kt | 55 ++++ .../research/dto/ResearchSearchTopResponse.kt | 15 + .../research/service/ResearchSearchService.kt | 77 +++++ .../service/ResearchSearchServiceTest.kt | 282 ++++++++++++++++++ 12 files changed, 619 insertions(+), 9 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/event/ProfessorCreatedEvent.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/event/ProfessorDeletedEvent.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/event/ProfessorModifiedEvent.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchSearchPageResponse.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchSearchResponseElement.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchSearchTopResponse.kt create mode 100644 src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchSearchServiceTest.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/CserealApplication.kt b/src/main/kotlin/com/wafflestudio/csereal/CserealApplication.kt index 9d3fbbb7..0f3b1340 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/CserealApplication.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/CserealApplication.kt @@ -3,9 +3,11 @@ package com.wafflestudio.csereal import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication import org.springframework.data.jpa.repository.config.EnableJpaAuditing +import org.springframework.scheduling.annotation.EnableAsync @EnableJpaAuditing @SpringBootApplication +@EnableAsync class CserealApplication fun main(args: Array) { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/event/ProfessorCreatedEvent.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/event/ProfessorCreatedEvent.kt new file mode 100644 index 00000000..6d45931c --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/event/ProfessorCreatedEvent.kt @@ -0,0 +1,15 @@ +package com.wafflestudio.csereal.core.member.event + +import com.wafflestudio.csereal.core.member.database.ProfessorEntity + +data class ProfessorCreatedEvent( + val id: Long, + val labId: Long? +) { + companion object { + fun of(professor: ProfessorEntity) = ProfessorCreatedEvent( + id = professor.id, + labId = professor.lab?.id + ) + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/event/ProfessorDeletedEvent.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/event/ProfessorDeletedEvent.kt new file mode 100644 index 00000000..3f249ba4 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/event/ProfessorDeletedEvent.kt @@ -0,0 +1,15 @@ +package com.wafflestudio.csereal.core.member.event + +import com.wafflestudio.csereal.core.member.database.ProfessorEntity + +data class ProfessorDeletedEvent( + val id: Long, + val labId: Long? +) { + companion object { + fun of(professor: ProfessorEntity) = ProfessorDeletedEvent( + id = professor.id, + labId = professor.lab?.id + ) + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/event/ProfessorModifiedEvent.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/event/ProfessorModifiedEvent.kt new file mode 100644 index 00000000..78b6e310 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/event/ProfessorModifiedEvent.kt @@ -0,0 +1,17 @@ +package com.wafflestudio.csereal.core.member.event + +import com.wafflestudio.csereal.core.member.database.ProfessorEntity + +data class ProfessorModifiedEvent( + val id: Long, + val beforeLabId: Long?, + val afterLabId: Long? +) { + companion object { + fun of(updatedProfessor: ProfessorEntity, beforeLabId: Long?) = ProfessorModifiedEvent( + id = updatedProfessor.id, + beforeLabId, + afterLabId = updatedProfessor.lab?.id + ) + } +} 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 97e69ee2..210c8f58 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 @@ -5,8 +5,12 @@ 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.member.event.ProfessorCreatedEvent +import com.wafflestudio.csereal.core.member.event.ProfessorDeletedEvent +import com.wafflestudio.csereal.core.member.event.ProfessorModifiedEvent import com.wafflestudio.csereal.core.research.database.LabRepository import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService +import org.springframework.context.ApplicationEventPublisher import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -31,7 +35,8 @@ interface ProfessorService { class ProfessorServiceImpl( private val labRepository: LabRepository, private val professorRepository: ProfessorRepository, - private val mainImageService: MainImageService + private val mainImageService: MainImageService, + private val applicationEventPublisher: ApplicationEventPublisher ) : ProfessorService { override fun createProfessor(createProfessorRequest: ProfessorDto, mainImage: MultipartFile?): ProfessorDto { val professor = ProfessorEntity.of(createProfessorRequest) @@ -63,6 +68,10 @@ class ProfessorServiceImpl( val imageURL = mainImageService.createImageURL(professor.mainImage) + applicationEventPublisher.publishEvent( + ProfessorCreatedEvent.of(professor) + ) + return ProfessorDto.of(professor, imageURL) } @@ -109,6 +118,8 @@ class ProfessorServiceImpl( val professor = professorRepository.findByIdOrNull(professorId) ?: throw CserealException.Csereal404("해당 교수님을 찾을 수 없습니다. professorId: $professorId") + val outdatedLabId = professor.lab?.id + if (updateProfessorRequest.labId != null && updateProfessorRequest.labId != professor.lab?.id) { val lab = labRepository.findByIdOrNull(updateProfessorRequest.labId) ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다. LabId: ${updateProfessorRequest.labId}") @@ -162,13 +173,24 @@ class ProfessorServiceImpl( // 검색 엔티티 업데이트 professor.memberSearch!!.update(professor) + // update event 생성 + applicationEventPublisher.publishEvent( + ProfessorModifiedEvent.of(professor, outdatedLabId) + ) + val imageURL = mainImageService.createImageURL(professor.mainImage) return ProfessorDto.of(professor, imageURL) } override fun deleteProfessor(professorId: Long) { + val professorEntity = professorRepository.findByIdOrNull(professorId) ?: return + professorRepository.deleteById(professorId) + + applicationEventPublisher.publishEvent( + ProfessorDeletedEvent.of(professorEntity) + ) } @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 55832b73..d6c55a3e 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,10 +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.dto.* +import com.wafflestudio.csereal.core.research.service.ResearchSearchService import com.wafflestudio.csereal.core.research.service.ResearchService import jakarta.validation.Valid import org.springframework.http.ResponseEntity @@ -14,7 +12,8 @@ import org.springframework.web.multipart.MultipartFile @RequestMapping("/api/v1/research") @RestController class ResearchController( - private val researchService: ResearchService + private val researchService: ResearchService, + private val researchSearchService: ResearchSearchService ) { @AuthenticatedStaff @PostMapping @@ -102,4 +101,17 @@ class ResearchController( ): ResponseEntity> { return ResponseEntity.ok(researchService.migrateLabs(requestList)) } + + @GetMapping("/search/top") + fun searchTop( + @RequestParam(required = true) keyword: String, + @RequestParam(required = true) number: Int + ) = researchSearchService.searchTopResearch(keyword, number) + + @GetMapping("/search") + fun searchPage( + @RequestParam(required = true) keyword: String, + @RequestParam(required = true) pageSize: Int, + @RequestParam(required = true) pageNum: Int + ) = researchSearchService.searchResearch(keyword, pageSize, pageNum) } 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 e9bf51d0..b9bf2b6d 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 @@ -1,14 +1,94 @@ package com.wafflestudio.csereal.core.research.database +import com.querydsl.jpa.impl.JPAQuery import com.querydsl.jpa.impl.JPAQueryFactory +import com.wafflestudio.csereal.common.repository.CommonRepository +import com.wafflestudio.csereal.core.conference.database.QConferenceEntity.conferenceEntity +import com.wafflestudio.csereal.core.research.database.QLabEntity.labEntity +import com.wafflestudio.csereal.core.research.database.QResearchEntity.researchEntity +import com.wafflestudio.csereal.core.research.database.QResearchSearchEntity.researchSearchEntity import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository interface ResearchSearchRepository : JpaRepository, ResearchSearchRepositoryCustom -interface ResearchSearchRepositoryCustom +interface ResearchSearchRepositoryCustom { + fun searchTopResearch(keyword: String, number: Int): List + + fun searchResearch(keyword: String, pageSize: Int, pageNum: Int): Pair, Long> +} @Repository class ResearchSearchRepositoryCustomImpl( - private val jpaQueryFactory: JPAQueryFactory -) : ResearchSearchRepositoryCustom + private val queryFactory: JPAQueryFactory, + private val commonRepository: CommonRepository +) : ResearchSearchRepositoryCustom { + override fun searchTopResearch(keyword: String, number: Int): List { + return searchQuery(keyword) + .limit(number.toLong()) + .fetch() + } + + override fun searchResearch(keyword: String, pageSize: Int, pageNum: Int): Pair, Long> { + val query = searchQuery(keyword) + val total = getSearchCount(keyword) + + val validPageNum = exchangePageNum(pageSize, pageNum, total) + val validOffset = (if (validPageNum >= 1) validPageNum - 1 else 0) * pageSize.toLong() + val queryResult = query + .offset(validOffset) + .limit(pageSize.toLong()) + .fetch() + + return queryResult to total + } + + fun searchQuery(keyword: String): JPAQuery { + val searchDoubleTemplate = commonRepository.searchFullSingleTextTemplate( + keyword, + researchSearchEntity.content + ) + + return queryFactory.selectFrom( + researchSearchEntity + ).leftJoin( + researchSearchEntity.lab, + labEntity + ).fetchJoin() + .leftJoin( + researchSearchEntity.research, + researchEntity + ).fetchJoin() + .leftJoin( + researchSearchEntity.conferenceElement, + conferenceEntity + ).fetchJoin() + .where( + searchDoubleTemplate.gt(0.0) + ) + } + + fun getSearchCount(keyword: String): Long { + val searchDoubleTemplate = commonRepository.searchFullSingleTextTemplate( + keyword, + researchSearchEntity.content + ) + + return queryFactory.select( + researchSearchEntity + .countDistinct() + ).from( + researchSearchEntity + ).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() + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchSearchPageResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchSearchPageResponse.kt new file mode 100644 index 00000000..59cf3b61 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchSearchPageResponse.kt @@ -0,0 +1,18 @@ +package com.wafflestudio.csereal.core.research.dto + +import com.wafflestudio.csereal.core.research.database.ResearchSearchEntity + +data class ResearchSearchPageResponse( + val researches: List, + val total: Long +) { + companion object { + fun of( + researches: List, + total: Long + ) = ResearchSearchPageResponse( + researches = researches.map(ResearchSearchResponseElement::of), + total = total + ) + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchSearchResponseElement.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchSearchResponseElement.kt new file mode 100644 index 00000000..ea5e5b37 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchSearchResponseElement.kt @@ -0,0 +1,55 @@ +package com.wafflestudio.csereal.core.research.dto + +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.research.database.ResearchSearchEntity +import com.wafflestudio.csereal.core.research.database.ResearchSearchType + +data class ResearchSearchResponseElement( + val id: Long, + val name: String, + val researchType: ResearchSearchType +) { + companion object { + fun of( + researchSearchEntity: ResearchSearchEntity + ): ResearchSearchResponseElement = + when { + researchSearchEntity.research != null && + researchSearchEntity.lab == null && + researchSearchEntity.conferenceElement == null + -> researchSearchEntity.research!!.let { + ResearchSearchResponseElement( + id = it.id, + name = it.name, + researchType = ResearchSearchType.RESEARCH + ) + } + + researchSearchEntity.lab != null && + researchSearchEntity.research == null && + researchSearchEntity.conferenceElement == null + -> researchSearchEntity.lab!!.let { + ResearchSearchResponseElement( + id = it.id, + name = it.name, + researchType = ResearchSearchType.LAB + ) + } + + researchSearchEntity.conferenceElement != null && + researchSearchEntity.research == null && + researchSearchEntity.lab == null + -> researchSearchEntity.conferenceElement!!.let { + ResearchSearchResponseElement( + id = it.id, + name = it.name, + researchType = ResearchSearchType.CONFERENCE + ) + } + + else -> throw CserealException.Csereal401( + "ResearchSearchEntity의 연결이 올바르지 않습니다." + ) + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchSearchTopResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchSearchTopResponse.kt new file mode 100644 index 00000000..784e2c47 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchSearchTopResponse.kt @@ -0,0 +1,15 @@ +package com.wafflestudio.csereal.core.research.dto + +import com.wafflestudio.csereal.core.research.database.ResearchSearchEntity + +data class ResearchSearchTopResponse( + val topResearches: List +) { + companion object { + fun of( + topResearches: List + ) = ResearchSearchTopResponse( + topResearches = topResearches.map(ResearchSearchResponseElement::of) + ) + } +} 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 7749331f..23af349a 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 @@ -1,18 +1,95 @@ package com.wafflestudio.csereal.core.research.service +import com.wafflestudio.csereal.core.member.event.ProfessorCreatedEvent +import com.wafflestudio.csereal.core.member.event.ProfessorDeletedEvent +import com.wafflestudio.csereal.core.member.event.ProfessorModifiedEvent +import com.wafflestudio.csereal.core.research.database.LabRepository import com.wafflestudio.csereal.core.research.database.ResearchSearchEntity import com.wafflestudio.csereal.core.research.database.ResearchSearchRepository +import com.wafflestudio.csereal.core.research.dto.ResearchSearchPageResponse +import com.wafflestudio.csereal.core.research.dto.ResearchSearchTopResponse +import org.springframework.context.event.EventListener +import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional interface ResearchSearchService { + fun professorCreatedEventListener(professorCreatedEvent: ProfessorCreatedEvent) + fun professorDeletedEventListener(professorDeletedEvent: ProfessorDeletedEvent) + fun professorModifiedEventListener(professorModifiedEvent: ProfessorModifiedEvent) fun deleteResearchSearch(researchSearchEntity: ResearchSearchEntity) + fun searchTopResearch(keyword: String, number: Int): ResearchSearchTopResponse + fun searchResearch(keyword: String, pageSize: Int, pageNum: Int): ResearchSearchPageResponse } @Service class ResearchSearchServiceImpl( + private val labRepository: LabRepository, private val researchSearchRepository: ResearchSearchRepository ) : ResearchSearchService { + @Transactional(readOnly = true) + override fun searchTopResearch(keyword: String, number: Int): ResearchSearchTopResponse = + ResearchSearchTopResponse.of( + researchSearchRepository.searchTopResearch(keyword, number) + ) + + @Transactional(readOnly = true) + override fun searchResearch(keyword: String, pageSize: Int, pageNum: Int): ResearchSearchPageResponse = + researchSearchRepository.searchResearch(keyword, pageSize, pageNum).let { + ResearchSearchPageResponse.of(it.first, it.second) + } + + @EventListener + @Transactional + override fun professorCreatedEventListener(professorCreatedEvent: ProfessorCreatedEvent) { + val lab = professorCreatedEvent.labId?.let { + labRepository.findByIdOrNull(it) + } ?: return + + lab.researchSearch?.update(lab) ?: let { + lab.researchSearch = ResearchSearchEntity.create(lab) + } + } + + @EventListener + @Transactional + override fun professorDeletedEventListener(professorDeletedEvent: ProfessorDeletedEvent) { + val lab = professorDeletedEvent.labId?.let { + labRepository.findByIdOrNull(it) + } ?: return + + // if lab still has professor, remove it + lab.professors.removeIf { it.id == professorDeletedEvent.id } + + // update search data + lab.researchSearch?.update(lab) + } + + @EventListener + @Transactional + override fun professorModifiedEventListener(professorModifiedEvent: ProfessorModifiedEvent) { + val beforeLab = professorModifiedEvent.beforeLabId?.let { + labRepository.findByIdOrNull(it) + } + + val afterLab = professorModifiedEvent.afterLabId?.let { + labRepository.findByIdOrNull(it) + } + + if (beforeLab != null && beforeLab == afterLab) { + beforeLab.researchSearch?.update(beforeLab) + } + + beforeLab?.run { + // if lab still has professor, remove it + professors.removeIf { it.id == professorModifiedEvent.id } + researchSearch?.update(this) + } + + afterLab?.run { + researchSearch?.update(this) + } + } @Transactional override fun deleteResearchSearch( diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchSearchServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchSearchServiceTest.kt new file mode 100644 index 00000000..5e6cb075 --- /dev/null +++ b/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchSearchServiceTest.kt @@ -0,0 +1,282 @@ +package com.wafflestudio.csereal.core.reseach.service + +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.member.service.ProfessorService +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.service.ResearchSearchService +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 +import java.time.LocalDate + +@SpringBootTest +@Transactional +class ResearchSearchServiceTest( + private val researchSearchService: ResearchSearchService, + private val professorRepository: ProfessorRepository, + private val professorService: ProfessorService, + private val labRepository: LabRepository, + private val researchRepository: ResearchRepository, + private val researchSearchRepository: ResearchSearchRepository, + private val researchService: ResearchService +) : BehaviorSpec() { + init { + extensions(SpringTestExtension(SpringTestLifecycleMode.Root)) + + beforeSpec { + } + + afterSpec { + professorRepository.deleteAll() + labRepository.deleteAll() + researchSearchRepository.deleteAll() + } + + // Event Listener Test + 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 professor1Dto = professorService.createProfessor( + createProfessorRequest = ProfessorDto( + name = "professor1", + email = null, + status = ProfessorStatus.ACTIVE, + academicRank = "professor", + labId = null, + labName = null, + startDate = null, + endDate = null, + office = null, + phone = null, + fax = null, + website = null, + educations = emptyList(), + researchAreas = emptyList(), + careers = emptyList() + ), + mainImage = 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 +// ) +// ) + val professor2Dto = professorService.createProfessor( + createProfessorRequest = ProfessorDto( + name = "professor2", + email = null, + status = ProfessorStatus.ACTIVE, + academicRank = "professor", + labId = null, + labName = null, + startDate = null, + endDate = null, + office = null, + phone = null, + fax = null, + website = null, + educations = emptyList(), + researchAreas = emptyList(), + careers = emptyList() + ), + mainImage = null + ) + + val professor1 = professorRepository.findByIdOrNull(professor1Dto.id)!! + val professor2 = professorRepository.findByIdOrNull(professor2Dto.id)!! + + // 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 emptyLabDto = LabDto( + id = -1, + name = "nameE", + professors = listOf(), + acronym = "acronymE", + description = "descriptionE", + group = "research", + pdf = null, + location = "locationE", + tel = "telE", + websiteURL = "websiteURLE", + youtube = "youtubeE" + ) + + val createdLabDto = researchService.createLab(labDto, null) + val createdEmptyLabDto = researchService.createLab(emptyLabDto, null) + + When("professor가 제거된다면") { + professorService.deleteProfessor(professor1.id) + + Then("검색 엔티티의 내용이 변경된다") { + val lab = labRepository.findByIdOrNull(createdLabDto.id)!! + val search = lab.researchSearch + + search shouldNotBe null + search!!.content shouldBe + """ + name + professor2 + location + tel + acronym + youtube + research + description + websiteURL + + """.trimIndent() + } + } + + When("professor가 추가된다면") { + val process3CreatedDto = professorService.createProfessor( + createProfessorRequest = ProfessorDto( + name = "newProfessor", + email = "email", + status = ProfessorStatus.ACTIVE, + academicRank = "academicRank", + labId = createdLabDto.id, + labName = null, + startDate = LocalDate.now(), + endDate = LocalDate.now(), + office = "office", + phone = "phone", + fax = "fax", + website = "website", + educations = listOf("education1", "education2"), + researchAreas = listOf("researchArea1", "researchArea2"), + careers = listOf("career1", "career2") + ), + mainImage = null + ) + + Then("검색 엔티티의 내용이 변경된다") { + val lab = labRepository.findByIdOrNull(createdLabDto.id)!! + val search = lab.researchSearch + + search shouldNotBe null + search!!.content shouldBe + """ + name + professor2 + newProfessor + location + tel + acronym + youtube + research + description + websiteURL + + """.trimIndent() + } + } + + When("professor가 수정된다면") { + professorService.updateProfessor( + professor2.id, + ProfessorDto.of(professor2, null) + .copy(name = "updateProfessor", labId = createdEmptyLabDto.id), + mainImage = null + ) + + Then("예전 검색 데이터에서 빠져야 한다.") { + val lab = labRepository.findByIdOrNull(createdLabDto.id)!! + val search = lab.researchSearch + + search shouldNotBe null + search!!.content shouldBe + """ + name + newProfessor + location + tel + acronym + youtube + research + description + websiteURL + + """.trimIndent() + } + + Then("새로운 검색 데이터에 포함되어야 한다.") { + val lab = labRepository.findByIdOrNull(createdEmptyLabDto.id)!! + val search = lab.researchSearch + + search shouldNotBe null + search!!.content shouldBe + """ + nameE + updateProfessor + locationE + telE + acronymE + youtubeE + research + descriptionE + websiteURLE + + """.trimIndent() + } + } + } + } +} From f62f9d2f4c34db803ae0a64d79ef725919099d5e Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Tue, 6 Feb 2024 11:09:39 +0900 Subject: [PATCH 133/214] =?UTF-8?q?feat:=20staff=20=EA=B2=80=EC=83=89=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20(#176)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wafflestudio/csereal/core/member/service/StaffService.kt | 2 ++ 1 file changed, 2 insertions(+) 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 7ee17d23..90a527c9 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 @@ -113,6 +113,8 @@ class StaffServiceImpl( TaskEntity.create(task, staff) } + staff.memberSearch = MemberSearchEntity.create(staff) + staffRepository.save(staff) list.add(StaffDto.of(staff, null)) From f9d2319c8a732926198b527b1f77f199638aac9d 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, 6 Feb 2024 21:58:44 +0900 Subject: [PATCH 134/214] =?UTF-8?q?Feat:=20=ED=95=99=EC=82=AC=20=EB=B0=8F?= =?UTF-8?q?=20=EA=B5=90=EA=B3=BC=20=EA=B2=80=EC=83=89=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20(#177)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Revert "fix: test에서 idsnucse 접근 x" This reverts commit cf526452abdc629b433efe701c9fd6ff60893f1e. * Fix: move local security settings to local, not global. * Feat: AcademicsStudentType에 값 지정. * Feat: AcademicsSearchEntity 추가 * Feat: 각 entity에 search 관계 추가 * Feat: 각 entity create하는 서비스 method에 search entity도 생성하도록 추가. * Feat: Add academicsSearchRepository * Feat: Add dto for search * Refactor: Make name field of academics non null * Feat: Add exchangePageNum to utils. * Feat: Add academics search service * Feat: Add search api for academics. * Feat: add validation code for utils.exchangePageNum * Test: Add testcode for exchangePageNum * REfactor: Add transactional readonly for readGeneralStudies * fix: apply newacademics to use after saved cached entity. * fix: fix to add target enitity when create of search entity is called. * refactor: add default values for nullable values. * Temp: test code, should be fixed h2 issue. --------- Co-authored-by: leeeryboy --- .../csereal/common/utils/Utils.kt | 13 ++ .../core/academics/api/AcademicsController.kt | 24 +++- .../academics/database/AcademicsEntity.kt | 7 +- .../database/AcademicsSearchEntity.kt | 128 ++++++++++++++++++ .../database/AcademicsSearchRepository.kt | 87 ++++++++++++ .../database/AcademicsStudentType.kt | 5 +- .../core/academics/database/CourseEntity.kt | 6 +- .../academics/database/ScholarshipEntity.kt | 5 +- .../core/academics/dto/AcademicsDto.kt | 14 +- .../dto/AcademicsSearchPageResponse.kt | 18 +++ .../dto/AcademicsSearchResponseElement.kt | 43 ++++++ .../dto/AcademicsSearchTopResponse.kt | 15 ++ .../service/AcademicsSearchService.kt | 39 ++++++ .../academics/service/AcademicsService.kt | 32 ++++- .../csereal/common/util/UtilsTest.kt | 44 ++++++ .../core/academics/AcademicsServiceTest.kt | 78 +++++++++++ 16 files changed, 538 insertions(+), 20 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchRepository.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchPageResponse.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchResponseElement.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchTopResponse.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsSearchService.kt create mode 100644 src/test/kotlin/com/wafflestudio/csereal/core/academics/AcademicsServiceTest.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 06f32bea..f0f59159 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/utils/Utils.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/utils/Utils.kt @@ -26,3 +26,16 @@ fun substringAroundKeyword(keyword: String, content: String, amount: Int): Pair< (index - frontIndex) to content.substring(frontIndex, backIndex) } } + +fun exchangePageNum(pageSize: Int, pageNum: Int, total: Long): Int { + // Validate + if (!(pageSize > 0 && pageNum > 0 && total >= 0)) { + throw RuntimeException() + } + + return if ((pageNum - 1) * pageSize < total) { + pageNum + } else { + Math.ceil(total.toDouble() / pageSize).toInt() + } +} 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 279a3a82..6fe51849 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 @@ -4,6 +4,7 @@ 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 +import com.wafflestudio.csereal.core.academics.service.AcademicsSearchService import jakarta.validation.Valid import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* @@ -12,7 +13,8 @@ import org.springframework.web.multipart.MultipartFile @RequestMapping("/api/v1/academics") @RestController class AcademicsController( - private val academicsService: AcademicsService + private val academicsService: AcademicsService, + private val academicsSearchService: AcademicsSearchService ) { @AuthenticatedStaff @PostMapping("/{studentType}/{postType}") @@ -95,4 +97,24 @@ class AcademicsController( fun getScholarship(@PathVariable scholarshipId: Long): ResponseEntity { return ResponseEntity.ok(academicsService.readScholarship(scholarshipId)) } + + @GetMapping("/search/top") + fun searchTop( + @RequestParam(required = true) keyword: String, + @RequestParam(required = true) number: Int + ) = academicsSearchService.searchTopAcademics( + keyword = keyword, + number = number + ) + + @GetMapping("/search") + fun searchAcademics( + @RequestParam(required = true) keyword: String, + @RequestParam(required = true) pageSize: Int, + @RequestParam(required = true) pageNum: Int + ) = academicsSearchService.searchAcademics( + keyword = keyword, + pageSize = pageSize, + pageNum = pageNum + ) } 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 2b90f96d..f3861445 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,13 +14,16 @@ 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() + var attachments: MutableList = mutableListOf(), + + @OneToOne(mappedBy = "academics", cascade = [CascadeType.ALL], orphanRemoval = true) + var academicsSearch: AcademicsSearchEntity? = null ) : BaseTimeEntity(), AttachmentContentEntityType { override fun bringAttachments() = attachments diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchEntity.kt new file mode 100644 index 00000000..ce50ce3b --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchEntity.kt @@ -0,0 +1,128 @@ +package com.wafflestudio.csereal.core.academics.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.common.utils.cleanTextFromHtml +import jakarta.persistence.* + +@Entity(name = "academics_search") +class AcademicsSearchEntity( + @Column(columnDefinition = "TEXT", nullable = false) + var content: String, + + @OneToOne + @JoinColumn(name = "academics_id") + val academics: AcademicsEntity? = null, + + @OneToOne + @JoinColumn(name = "course_id") + val course: CourseEntity? = null, + + @OneToOne + @JoinColumn(name = "scholarship_id") + val scholarship: ScholarshipEntity? = null + +) : BaseTimeEntity() { + companion object { + fun create(academics: AcademicsEntity): AcademicsSearchEntity { + return AcademicsSearchEntity( + academics = academics, + content = createContent(academics) + ) + } + + fun create(course: CourseEntity): AcademicsSearchEntity { + return AcademicsSearchEntity( + course = course, + content = createContent(course) + ) + } + + fun create(scholarship: ScholarshipEntity): AcademicsSearchEntity { + return AcademicsSearchEntity( + scholarship = scholarship, + content = createContent(scholarship) + ) + } + + fun createContent(academics: AcademicsEntity): String { + val sb = StringBuilder() + academics.name.let { sb.appendLine(it) } + academics.time?.let { sb.appendLine(it) } + academics.year?.let { sb.appendLine(it) } + sb.appendLine(academics.studentType.value) + sb.appendLine( + cleanTextFromHtml( + academics.description + ) + ) + + return sb.toString() + } + + fun createContent(course: CourseEntity) = + course.let { + val sb = StringBuilder() + sb.appendLine(it.studentType.value) + sb.appendLine(it.classification) + sb.appendLine(it.code) + sb.appendLine(it.name) + sb.appendLine(it.credit) + sb.appendLine(it.grade) + it.description?.let { desc -> + sb.appendLine(cleanTextFromHtml(desc)) + } + + sb.toString() + } + + fun createContent(scholarship: ScholarshipEntity) = + scholarship.let { + val sb = StringBuilder() + sb.appendLine(it.studentType.value) + sb.appendLine(it.name) + sb.appendLine( + cleanTextFromHtml(it.description) + ) + sb.toString() + } + } + + fun update(academics: AcademicsEntity) { + this.content = createContent(academics) + } + + fun update(course: CourseEntity) { + this.content = createContent(course) + } + + fun update(scholarship: ScholarshipEntity) { + this.content = createContent(scholarship) + } + + @PrePersist + @PreUpdate + fun checkType() { + if (!( + (academics != null && course == null && scholarship == null) || + (academics == null && course != null && scholarship == null) || + (academics == null && course == null && scholarship != null) + ) + ) { + throw IllegalStateException("AcademicsSearchEntity must have only one type of entity") + } + } + + fun ofType() = + when { + academics != null && course == null && scholarship == null -> AcademicsSearchType.ACADEMICS + academics == null && course != null && scholarship == null -> AcademicsSearchType.COURSE + academics == null && course == null && scholarship != null -> AcademicsSearchType.SCHOLARSHIP + else -> throw IllegalStateException("AcademicsSearchEntity must have only one type of entity") + } +} + +enum class AcademicsSearchType { + ACADEMICS, + COURSE, + SCHOLARSHIP +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchRepository.kt new file mode 100644 index 00000000..cd16fb10 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchRepository.kt @@ -0,0 +1,87 @@ +package com.wafflestudio.csereal.core.academics.database + +import com.querydsl.jpa.impl.JPAQuery +import com.querydsl.jpa.impl.JPAQueryFactory +import com.wafflestudio.csereal.common.repository.CommonRepository +import com.wafflestudio.csereal.core.academics.database.QAcademicsEntity.academicsEntity +import com.wafflestudio.csereal.core.academics.database.QAcademicsSearchEntity.academicsSearchEntity +import com.wafflestudio.csereal.core.academics.database.QCourseEntity.courseEntity +import com.wafflestudio.csereal.core.academics.database.QScholarshipEntity.scholarshipEntity +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository +import com.wafflestudio.csereal.common.utils.exchangePageNum + +interface AcademicsSearchRepository : JpaRepository, AcademicsSearchCustomRepository + +interface AcademicsSearchCustomRepository { + fun searchAcademics(keyword: String, pageSize: Int, pageNum: Int): Pair, Long> + fun searchTopAcademics(keyword: String, number: Int): List +} + +@Repository +class AcademicsSearchCustomRepositoryImpl( + private val queryFactory: JPAQueryFactory, + private val commonRepository: CommonRepository +) : AcademicsSearchCustomRepository { + override fun searchTopAcademics(keyword: String, number: Int): List { + return searchQuery(keyword) + .limit(number.toLong()) + .fetch() + } + + override fun searchAcademics( + keyword: String, + pageSize: Int, + pageNum: Int + ): Pair, Long> { + val query = searchQuery(keyword) + val total = getSearchCount(keyword) + + val validPageNum = exchangePageNum(pageSize, pageNum, total) + val validOffset = (if (validPageNum >= 1) validPageNum - 1 else 0) * pageSize.toLong() + val queryResult = query.offset(validOffset) + .limit(pageSize.toLong()) + .fetch() + + return queryResult to total + } + + fun searchQuery(keyword: String): JPAQuery { + val searchDoubleTemplate = commonRepository.searchFullSingleTextTemplate( + keyword, + academicsSearchEntity.content + ) + + return queryFactory.selectFrom( + academicsSearchEntity + ).leftJoin( + academicsSearchEntity.academics, + academicsEntity + ).fetchJoin() + .leftJoin( + academicsSearchEntity.course, + courseEntity + ).fetchJoin() + .leftJoin( + academicsSearchEntity.scholarship, + scholarshipEntity + ).fetchJoin() + .where( + searchDoubleTemplate.gt(0.0) + ) + } + + fun getSearchCount(keyword: String): Long { + val searchDoubleTemplate = commonRepository.searchFullSingleTextTemplate( + keyword, + academicsSearchEntity.content + ) + + return queryFactory.select( + academicsSearchEntity.countDistinct() + ).from(academicsSearchEntity) + .where( + searchDoubleTemplate.gt(0.0) + ).fetchOne()!! + } +} 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 2da6e67a..13e53321 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 @@ -1,5 +1,6 @@ package com.wafflestudio.csereal.core.academics.database -enum class AcademicsStudentType { - UNDERGRADUATE, GRADUATE +enum class AcademicsStudentType(val value: String) { + UNDERGRADUATE("학부"), + GRADUATE("대학원"); } 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 5f6727ef..baa4769c 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 @@ -7,6 +7,7 @@ import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEnti import jakarta.persistence.CascadeType import jakarta.persistence.Entity import jakarta.persistence.OneToMany +import jakarta.persistence.OneToOne @Entity(name = "course") class CourseEntity( @@ -27,7 +28,10 @@ class CourseEntity( var description: String?, @OneToMany(mappedBy = "course", cascade = [CascadeType.ALL], orphanRemoval = true) - var attachments: MutableList = mutableListOf() + var attachments: MutableList = mutableListOf(), + + @OneToOne(mappedBy = "course", cascade = [CascadeType.ALL], orphanRemoval = true) + var academicsSearch: AcademicsSearchEntity? = null ) : BaseTimeEntity(), AttachmentContentEntityType { override fun bringAttachments() = attachments 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 3fc88cea..6aee54fd 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 @@ -12,7 +12,10 @@ class ScholarshipEntity( val name: String, @Column(columnDefinition = "text") - val description: String + val description: String, + + @OneToOne(mappedBy = "scholarship", cascade = [CascadeType.ALL], orphanRemoval = true) + var academicsSearch: AcademicsSearchEntity? = null ) : BaseTimeEntity() { 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 e3775e33..448a0632 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,14 +5,14 @@ import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse import java.time.LocalDateTime data class AcademicsDto( - val id: Long, - val name: String?, + val id: Long = -1, // TODO: Seperate to multiple DTOs or set this as nullable + val name: String, val description: String, - val year: Int?, - val time: String?, - val createdAt: LocalDateTime?, - val modifiedAt: LocalDateTime?, - val attachments: List? + val year: Int? = null, + val time: String? = null, + val createdAt: LocalDateTime? = null, + val modifiedAt: LocalDateTime? = null, + val attachments: List? = null ) { companion object { fun of(entity: AcademicsEntity, attachmentResponses: List): AcademicsDto = entity.run { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchPageResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchPageResponse.kt new file mode 100644 index 00000000..36b6b79f --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchPageResponse.kt @@ -0,0 +1,18 @@ +package com.wafflestudio.csereal.core.academics.dto + +import com.wafflestudio.csereal.core.academics.database.AcademicsSearchEntity + +data class AcademicsSearchPageResponse( + val academics: List, + val total: Long +) { + companion object { + fun of( + academics: List, + total: Long + ) = AcademicsSearchPageResponse( + academics = academics.map(AcademicsSearchResponseElement::of), + total = total + ) + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchResponseElement.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchResponseElement.kt new file mode 100644 index 00000000..7854a72c --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchResponseElement.kt @@ -0,0 +1,43 @@ +package com.wafflestudio.csereal.core.academics.dto + +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.academics.database.AcademicsSearchEntity +import com.wafflestudio.csereal.core.academics.database.AcademicsSearchType + +data class AcademicsSearchResponseElement( + val id: Long, + val name: String, + val academicsType: AcademicsSearchType +) { + companion object { + fun of(academicsSearch: AcademicsSearchEntity): AcademicsSearchResponseElement { + return when { + academicsSearch.academics != null && + academicsSearch.course == null && + academicsSearch.scholarship == null -> + AcademicsSearchResponseElement( + id = academicsSearch.academics!!.id, + name = academicsSearch.academics!!.name, + academicsType = AcademicsSearchType.ACADEMICS + ) + academicsSearch.academics == null && + academicsSearch.course != null && + academicsSearch.scholarship == null -> + AcademicsSearchResponseElement( + id = academicsSearch.course!!.id, + name = academicsSearch.course!!.name, + academicsType = AcademicsSearchType.COURSE + ) + academicsSearch.academics == null && + academicsSearch.course == null && + academicsSearch.scholarship != null -> + AcademicsSearchResponseElement( + id = academicsSearch.scholarship!!.id, + name = academicsSearch.scholarship!!.name, + academicsType = AcademicsSearchType.SCHOLARSHIP + ) + else -> throw CserealException.Csereal401("AcademicsSearchEntity의 연결이 올바르지 않습니다.") + } + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchTopResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchTopResponse.kt new file mode 100644 index 00000000..232b2fe2 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchTopResponse.kt @@ -0,0 +1,15 @@ +package com.wafflestudio.csereal.core.academics.dto + +import com.wafflestudio.csereal.core.academics.database.AcademicsSearchEntity + +data class AcademicsSearchTopResponse( + val topAcademics: List +) { + companion object { + fun of( + topAcademics: List + ) = AcademicsSearchTopResponse( + topAcademics = topAcademics.map(AcademicsSearchResponseElement::of) + ) + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsSearchService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsSearchService.kt new file mode 100644 index 00000000..eb2738b2 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsSearchService.kt @@ -0,0 +1,39 @@ +package com.wafflestudio.csereal.core.academics.service + +import com.wafflestudio.csereal.core.academics.database.AcademicsSearchRepository +import com.wafflestudio.csereal.core.academics.dto.AcademicsSearchPageResponse +import com.wafflestudio.csereal.core.academics.dto.AcademicsSearchTopResponse +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +interface AcademicsSearchService { + fun searchAcademics(keyword: String, pageSize: Int, pageNum: Int): AcademicsSearchPageResponse + fun searchTopAcademics(keyword: String, number: Int): AcademicsSearchTopResponse +} + +@Service +class AcademicsSearchServiceImpl( + private val academicsSearchRepository: AcademicsSearchRepository +) : AcademicsSearchService { + @Transactional(readOnly = true) + override fun searchTopAcademics(keyword: String, number: Int) = + AcademicsSearchTopResponse.of( + academicsSearchRepository.searchTopAcademics( + keyword = keyword, + number = number + ) + ) + + @Transactional(readOnly = true) + override fun searchAcademics(keyword: String, pageSize: Int, pageNum: Int) = + academicsSearchRepository.searchAcademics( + keyword = keyword, + pageSize = pageSize, + pageNum = pageNum + ).let { + AcademicsSearchPageResponse.of( + academics = it.first, + total = it.second + ) + } +} 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 2773b6d5..7ee322cf 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 @@ -30,6 +30,10 @@ interface AcademicsService { fun readScholarship(scholarshipId: Long): ScholarshipDto } +// TODO: add Update, Delete method +// remember to update academicsSearch Field on Update method +// remember to mark delete of academicsSearch Field on Delete mark method + @Service class AcademicsServiceImpl( private val academicsRepository: AcademicsRepository, @@ -47,13 +51,18 @@ class AcademicsServiceImpl( val enumStudentType = makeStringToAcademicsStudentType(studentType) val enumPostType = makeStringToAcademicsPostType(postType) - val newAcademics = AcademicsEntity.of(enumStudentType, enumPostType, request) + var newAcademics = AcademicsEntity.of(enumStudentType, enumPostType, request) if (attachments != null) { attachmentService.uploadAllAttachments(newAcademics, attachments) } - academicsRepository.save(newAcademics) + // create search data + newAcademics.apply { + academicsSearch = AcademicsSearchEntity.create(this) + } + + newAcademics = academicsRepository.save(newAcademics) val attachmentResponses = attachmentService.createAttachmentResponses(newAcademics.attachments) @@ -87,6 +96,7 @@ class AcademicsServiceImpl( return academicsYearResponses } + @Transactional(readOnly = true) override fun readGeneralStudies(): GeneralStudiesPageResponse { val academicsEntity = academicsRepository.findByStudentTypeAndPostType( AcademicsStudentType.UNDERGRADUATE, @@ -103,13 +113,18 @@ class AcademicsServiceImpl( @Transactional override fun createCourse(studentType: String, request: CourseDto, attachments: List?): CourseDto { val enumStudentType = makeStringToAcademicsStudentType(studentType) - val newCourse = CourseEntity.of(enumStudentType, request) + var newCourse = CourseEntity.of(enumStudentType, request) if (attachments != null) { attachmentService.uploadAllAttachments(newCourse, attachments) } - courseRepository.save(newCourse) + // create search data + newCourse.apply { + academicsSearch = AcademicsSearchEntity.create(this) + } + + newCourse = courseRepository.save(newCourse) val attachmentResponses = attachmentService.createAttachmentResponses(newCourse.attachments) @@ -138,9 +153,14 @@ class AcademicsServiceImpl( @Transactional override fun createScholarshipDetail(studentType: String, request: ScholarshipDto): ScholarshipDto { val enumStudentType = makeStringToAcademicsStudentType(studentType) - val newScholarship = ScholarshipEntity.of(enumStudentType, request) + var newScholarship = ScholarshipEntity.of(enumStudentType, request) + + // create search data + newScholarship.apply { + academicsSearch = AcademicsSearchEntity.create(this) + } - scholarshipRepository.save(newScholarship) + newScholarship = scholarshipRepository.save(newScholarship) return ScholarshipDto.of(newScholarship) } 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 54965232..a54c0d31 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/common/util/UtilsTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/common/util/UtilsTest.kt @@ -1,7 +1,9 @@ package com.wafflestudio.csereal.common.util import com.wafflestudio.csereal.common.utils.cleanTextFromHtml +import com.wafflestudio.csereal.common.utils.exchangePageNum import com.wafflestudio.csereal.common.utils.substringAroundKeyword +import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.shouldBe @@ -101,4 +103,46 @@ class UtilsTest : BehaviorSpec({ } } } + + Given("Using exchangePageNum to get valid page number") { + When("Given variables are not positive") { + val totalMinus = Triple(1, 1, -1) + val pageSizeZero = Triple(0, 1, 1) + val pageNumZero = Triple(1, 0, 1) + + Then("should throw AssertionError") { + shouldThrow { + exchangePageNum(totalMinus.first, totalMinus.second, totalMinus.third) + } + shouldThrow { + exchangePageNum(pageSizeZero.first, pageSizeZero.second, pageSizeZero.third) + } + shouldThrow { + exchangePageNum(pageNumZero.first, pageNumZero.second, pageNumZero.third) + } + } + } + + When("Given page is in the range") { + val pageSize = 10 + val total = 100L + val pageNum = 3 + + Then("Should return pageNum itself") { + val resultPageNum = exchangePageNum(pageSize, pageNum, total) + resultPageNum shouldBe pageNum + } + } + + When("Given page is out of range (bigger)") { + val pageSize = 10 + val total = 104L + val pageNum = 15 + + Then("Should return last page number") { + val resultPageNum = exchangePageNum(pageSize, pageNum, total) + resultPageNum shouldBe 11 + } + } + } }) diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/academics/AcademicsServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/academics/AcademicsServiceTest.kt new file mode 100644 index 00000000..d5968a7d --- /dev/null +++ b/src/test/kotlin/com/wafflestudio/csereal/core/academics/AcademicsServiceTest.kt @@ -0,0 +1,78 @@ +//package com.wafflestudio.csereal.core.academics +// +//import com.wafflestudio.csereal.core.academics.database.AcademicsRepository +//import com.wafflestudio.csereal.core.academics.database.AcademicsSearchRepository +//import com.wafflestudio.csereal.core.academics.dto.AcademicsDto +//import com.wafflestudio.csereal.core.academics.service.AcademicsService +//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 org.springframework.test.context.ActiveProfiles +// +// +// TODO: Fix test issue +//@SpringBootTest +//@ActiveProfiles("test") +//@Transactional +//class AcademicsServiceTest ( +// private val academicsService: AcademicsService, +// private val academicsRepository: AcademicsRepository, +// private val academicsSearchRepository: AcademicsSearchRepository, +//): BehaviorSpec({ +// extensions(SpringTestExtension(SpringTestLifecycleMode.Root)) +// +// Given("첨부파일 없는 Academics를 생성하려고 할 때") { +// val studentType = "undergraduate" +// val enumPostType = "guide" +// val request = AcademicsDto( +// name = "name", +// description = "

description

", +// year = 2023, +// time = "12:43", +// ) +// +// When("Academics를 생성한다면") { +// val returnDto = academicsService.createAcademics(studentType, enumPostType, request, null) +// +// Then("Academics가 생성되어야 한다.") { +// val id = returnDto.id +// val savedAcademics = academicsRepository.findByIdOrNull(id) +// +// savedAcademics shouldNotBe null +// savedAcademics!!.let { +// it.name shouldBe request.name +// it.description shouldBe request.description +// it.year shouldBe request.year +// it.time shouldBe request.time +// } +// } +// +// Then("검색 데이터가 생성되어야 한다.") { +// val savedAcademics = academicsRepository.findByIdOrNull(returnDto.id)!! +// val createdSearch = savedAcademics.academicsSearch?.id.let { +// academicsSearchRepository.findByIdOrNull(it) +// } +// +// createdSearch shouldNotBe null +// createdSearch!!.let { +// it.academics shouldBe savedAcademics +// it.course shouldBe null +// it.scholarship shouldBe null +// it.content shouldBe """ +// name +// 12:43 +// 2023 +// 학부생 +// description +// +// """.trimIndent() +// } +// } +// } +// } +//}) From e593ee1b6303847061a55a0276753e531d42dd4c 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, 6 Feb 2024 22:09:27 +0900 Subject: [PATCH 135/214] =?UTF-8?q?Fix:=20Research=20=EA=B2=80=EC=83=89?= =?UTF-8?q?=EC=97=90=EC=84=9C=20html=20=ED=83=9C=EA=B7=B8=20=EC=97=86?= =?UTF-8?q?=EC=9D=B4=20=EA=B2=80=EC=83=89=EB=90=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20(#178)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix: Change createContent to remove html tags on description. * Test: Update to check removing html tags. --- .../research/database/ResearchSearchEntity.kt | 9 ++++-- .../service/ResearchSearchServiceTest.kt | 32 ++----------------- 2 files changed, 9 insertions(+), 32 deletions(-) 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 bb3f75ad..96dea54f 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 @@ -1,6 +1,7 @@ package com.wafflestudio.csereal.core.research.database import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.common.utils.cleanTextFromHtml import com.wafflestudio.csereal.core.conference.database.ConferenceEntity import jakarta.persistence.* @@ -46,7 +47,9 @@ class ResearchSearchEntity( fun createContent(research: ResearchEntity) = StringBuilder().apply { appendLine(research.name) appendLine(research.postType.krName) - research.description?.let { appendLine(it) } + research.description?.let { + appendLine(cleanTextFromHtml(it)) + } research.labs.forEach { appendLine(it.name) } }.toString() @@ -58,7 +61,9 @@ class ResearchSearchEntity( lab.acronym?.let { appendLine(it) } lab.youtube?.let { appendLine(it) } appendLine(lab.research.name) - lab.description?.let { appendLine(it) } + lab.description?.let { + appendLine(cleanTextFromHtml(it)) + } lab.websiteURL?.let { appendLine(it) } }.toString() diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchSearchServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchSearchServiceTest.kt index 5e6cb075..032743ba 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchSearchServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchSearchServiceTest.kt @@ -45,20 +45,6 @@ class ResearchSearchServiceTest( // Event Listener Test 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 professor1Dto = professorService.createProfessor( createProfessorRequest = ProfessorDto( name = "professor1", @@ -79,20 +65,6 @@ class ResearchSearchServiceTest( ), mainImage = 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 -// ) -// ) val professor2Dto = professorService.createProfessor( createProfessorRequest = ProfessorDto( name = "professor2", @@ -135,7 +107,7 @@ class ResearchSearchServiceTest( LabProfessorResponse(professor2.id, professor2.name) ), acronym = "acronym", - description = "description", + description = "

description

", group = "research", pdf = null, location = "location", @@ -149,7 +121,7 @@ class ResearchSearchServiceTest( name = "nameE", professors = listOf(), acronym = "acronymE", - description = "descriptionE", + description = "

descriptionE

", group = "research", pdf = null, location = "locationE", From 48edba4d0ea62aa2825ff42a28d5ad42ebaaba18 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Sat, 10 Feb 2024 00:19:44 +0900 Subject: [PATCH 136/214] Feat/add language field (#180) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 엔티티, dto에 language 추가 * feat: read에 language 추가 * fix: ktlint 수정 * fix: languageRepository 삭제, 함수 enum 클래스로 이동 * fix: language pathVariable -> requestParam 변경 && engName 추가 삭제 * fix: dto 소문자로 return하기 enum 함수로 넣어주기 * fix: requestParam defaultValue 추가 --- .../csereal/common/properties/LanguageType.kt | 21 ++++ .../csereal/core/about/api/AboutController.kt | 21 ++-- .../core/about/database/AboutEntity.kt | 8 +- .../core/about/database/AboutRepository.kt | 5 +- .../csereal/core/about/dto/AboutDto.kt | 5 +- .../csereal/core/about/dto/AboutRequest.kt | 1 + .../csereal/core/about/dto/DirectionDto.kt | 5 +- .../csereal/core/about/dto/FacilityDto.kt | 3 + .../core/about/dto/FutureCareersRequest.kt | 1 + .../csereal/core/about/dto/StudentClubDto.kt | 5 +- .../core/about/service/AboutService.kt | 114 +++++++++++------- 11 files changed, 127 insertions(+), 62 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/common/properties/LanguageType.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/properties/LanguageType.kt b/src/main/kotlin/com/wafflestudio/csereal/common/properties/LanguageType.kt new file mode 100644 index 00000000..92b6f511 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/common/properties/LanguageType.kt @@ -0,0 +1,21 @@ +package com.wafflestudio.csereal.common.properties + +import com.wafflestudio.csereal.common.CserealException + +enum class LanguageType { + KO, EN; + + companion object { + fun makeStringToLanguageType(language: String): LanguageType { + try { + val upperLanguageType = language.uppercase() + return LanguageType.valueOf(upperLanguageType) + } catch (e: IllegalArgumentException) { + throw CserealException.Csereal400("해당하는 enum을 찾을 수 없습니다") + } + } + + // dto로 통신할 때 소문자로 return + fun makeLowercase(languageType: LanguageType): String = languageType.toString().lowercase() + } +} 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 c028b641..87468574 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 @@ -36,24 +36,31 @@ class AboutController( // read 목록이 하나 @GetMapping("/{postType}") fun readAbout( + @RequestParam(required = false, defaultValue = "ko") language: String, @PathVariable postType: String ): ResponseEntity { - return ResponseEntity.ok(aboutService.readAbout(postType)) + return ResponseEntity.ok(aboutService.readAbout(language, postType)) } @GetMapping("/student-clubs") - fun readAllClubs(): ResponseEntity> { - return ResponseEntity.ok(aboutService.readAllClubs()) + fun readAllClubs( + @RequestParam(required = false, defaultValue = "ko") language: String + ): ResponseEntity> { + return ResponseEntity.ok(aboutService.readAllClubs(language)) } @GetMapping("/facilities") - fun readAllFacilities(): ResponseEntity> { - return ResponseEntity.ok(aboutService.readAllFacilities()) + fun readAllFacilities( + @RequestParam(required = false, defaultValue = "ko") language: String + ): ResponseEntity> { + return ResponseEntity.ok(aboutService.readAllFacilities(language)) } @GetMapping("/directions") - fun readAllDirections(): ResponseEntity> { - return ResponseEntity.ok(aboutService.readAllDirections()) + fun readAllDirections( + @RequestParam(required = false, defaultValue = "ko") language: String + ): ResponseEntity> { + return ResponseEntity.ok(aboutService.readAllDirections(language)) } @GetMapping("/future-careers") 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 dbe06392..24f0ebd8 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 @@ -3,6 +3,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.MainImageContentEntityType +import com.wafflestudio.csereal.common.properties.LanguageType 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 @@ -12,8 +13,9 @@ import jakarta.persistence.* class AboutEntity( @Enumerated(EnumType.STRING) var postType: AboutPostType, + @Enumerated(EnumType.STRING) + var language: LanguageType = LanguageType.KO, var name: String?, - var engName: String?, @Column(columnDefinition = "mediumText") var description: String, @@ -34,11 +36,11 @@ class AboutEntity( override fun bringAttachments(): List = attachments companion object { - fun of(postType: AboutPostType, aboutDto: AboutDto): AboutEntity { + fun of(postType: AboutPostType, languageType: LanguageType, aboutDto: AboutDto): AboutEntity { return AboutEntity( postType = postType, + language = languageType, name = aboutDto.name, - engName = aboutDto.engName, description = aboutDto.description, year = aboutDto.year ) 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 85d5331a..3790bce0 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 @@ -1,8 +1,9 @@ package com.wafflestudio.csereal.core.about.database +import com.wafflestudio.csereal.common.properties.LanguageType import org.springframework.data.jpa.repository.JpaRepository interface AboutRepository : JpaRepository { - fun findAllByPostTypeOrderByName(postType: AboutPostType): List - fun findByPostType(postType: AboutPostType): AboutEntity + fun findAllByLanguageAndPostTypeOrderByName(languageType: LanguageType, postType: AboutPostType): List + fun findByLanguageAndPostType(languageType: LanguageType, postType: AboutPostType): 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 72aa0bde..6160b839 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,7 @@ package com.wafflestudio.csereal.core.about.dto import com.fasterxml.jackson.annotation.JsonInclude +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.about.database.AboutEntity import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse import java.time.LocalDateTime @@ -8,8 +9,8 @@ import java.time.LocalDateTime data class AboutDto( @JsonInclude(JsonInclude.Include.NON_NULL) val id: Long? = null, + val language: String, val name: String?, - val engName: String?, val description: String, val year: Int?, val createdAt: LocalDateTime?, @@ -26,8 +27,8 @@ data class AboutDto( ): AboutDto = entity.run { AboutDto( id = this.id, + language = LanguageType.makeLowercase(this.language), name = this.name, - engName = this.engName, description = this.description, year = this.year, createdAt = this.createdAt, 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 80aa8c28..2c81496c 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,5 +2,6 @@ package com.wafflestudio.csereal.core.about.dto data class AboutRequest( val postType: String, + val language: String, 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 3507c5df..bd2ae452 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 @@ -1,21 +1,22 @@ package com.wafflestudio.csereal.core.about.dto import com.fasterxml.jackson.annotation.JsonInclude +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.about.database.AboutEntity data class DirectionDto( @JsonInclude(JsonInclude.Include.NON_NULL) val id: Long? = null, + val language: String, val name: String, - val engName: String, val description: String ) { companion object { fun of(entity: AboutEntity): DirectionDto = entity.run { DirectionDto( id = this.id, + language = LanguageType.makeLowercase(this.language), name = this.name!!, - engName = this.engName!!, description = this.description ) } 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 ac18d04b..1feceaa5 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 @@ -1,11 +1,13 @@ package com.wafflestudio.csereal.core.about.dto import com.fasterxml.jackson.annotation.JsonInclude +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.about.database.AboutEntity data class FacilityDto( @JsonInclude(JsonInclude.Include.NON_NULL) val id: Long? = null, + val language: String, val name: String, val description: String, val locations: List @@ -14,6 +16,7 @@ data class FacilityDto( fun of(entity: AboutEntity): FacilityDto = entity.run { FacilityDto( id = this.id, + language = LanguageType.makeLowercase(this.language), name = this.name!!, description = this.description, locations = this.locations.map { it.name } 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 425e6a13..c7341cef 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,6 +1,7 @@ package com.wafflestudio.csereal.core.about.dto data class FutureCareersRequest( + val language: String, val description: String, val stat: List, val companies: List 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 ed536178..89b70984 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 @@ -1,21 +1,22 @@ package com.wafflestudio.csereal.core.about.dto import com.fasterxml.jackson.annotation.JsonInclude +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.about.database.AboutEntity data class StudentClubDto( @JsonInclude(JsonInclude.Include.NON_NULL) val id: Long? = null, + val language: String, val name: String, - val engName: String, val description: String ) { companion object { fun of(entity: AboutEntity): StudentClubDto = entity.run { StudentClubDto( id = this.id, + language = LanguageType.makeLowercase(this.language), name = this.name!!, - engName = this.engName!!, description = this.description ) } 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 ba1f8612..ccac3db5 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,9 @@ package com.wafflestudio.csereal.core.about.service import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.common.properties.LanguageType 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.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 @@ -20,10 +18,10 @@ interface AboutService { attachments: List? ): AboutDto - fun readAbout(postType: String): AboutDto - fun readAllClubs(): List - fun readAllFacilities(): List - fun readAllDirections(): List + fun readAbout(language: String, postType: String): AboutDto + fun readAllClubs(language: String): List + fun readAllFacilities(language: String): List + fun readAllDirections(language: String): List fun readFutureCareers(): FutureCareersPage fun migrateAbout(requestList: List): List fun migrateFutureCareers(request: FutureCareersRequest): FutureCareersPage @@ -48,7 +46,8 @@ class AboutServiceImpl( attachments: List? ): AboutDto { val enumPostType = makeStringToEnum(postType) - val newAbout = AboutEntity.of(enumPostType, request) + val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) + val newAbout = AboutEntity.of(enumPostType, enumLanguageType, request) if (request.locations != null) { for (location in request.locations) { @@ -72,9 +71,10 @@ class AboutServiceImpl( } @Transactional(readOnly = true) - override fun readAbout(postType: String): AboutDto { + override fun readAbout(language: String, postType: String): AboutDto { + val languageType = LanguageType.makeStringToLanguageType(language) val enumPostType = makeStringToEnum(postType) - val about = aboutRepository.findByPostType(enumPostType) + val about = aboutRepository.findByLanguageAndPostType(languageType, enumPostType) val imageURL = mainImageService.createImageURL(about.mainImage) val attachmentResponses = attachmentService.createAttachmentResponses(about.attachments) @@ -82,34 +82,40 @@ class AboutServiceImpl( } @Transactional(readOnly = true) - override fun readAllClubs(): List { - val clubs = aboutRepository.findAllByPostTypeOrderByName(AboutPostType.STUDENT_CLUBS).map { - val imageURL = mainImageService.createImageURL(it.mainImage) - val attachmentResponses = attachmentService.createAttachmentResponses(it.attachments) - AboutDto.of(it, imageURL, attachmentResponses) - } + override fun readAllClubs(language: String): List { + val languageType = LanguageType.makeStringToLanguageType(language) + val clubs = + aboutRepository.findAllByLanguageAndPostTypeOrderByName(languageType, AboutPostType.STUDENT_CLUBS).map { + val imageURL = mainImageService.createImageURL(it.mainImage) + val attachmentResponses = attachmentService.createAttachmentResponses(it.attachments) + AboutDto.of(it, imageURL, attachmentResponses) + } return clubs } @Transactional(readOnly = true) - override fun readAllFacilities(): List { - val facilities = aboutRepository.findAllByPostTypeOrderByName(AboutPostType.FACILITIES).map { - val imageURL = mainImageService.createImageURL(it.mainImage) - val attachmentResponses = attachmentService.createAttachmentResponses(it.attachments) - AboutDto.of(it, imageURL, attachmentResponses) - } + override fun readAllFacilities(language: String): List { + val languageType = LanguageType.makeStringToLanguageType(language) + val facilities = + aboutRepository.findAllByLanguageAndPostTypeOrderByName(languageType, AboutPostType.FACILITIES).map { + val imageURL = mainImageService.createImageURL(it.mainImage) + val attachmentResponses = attachmentService.createAttachmentResponses(it.attachments) + AboutDto.of(it, imageURL, attachmentResponses) + } return facilities } @Transactional(readOnly = true) - override fun readAllDirections(): List { - val directions = aboutRepository.findAllByPostTypeOrderByName(AboutPostType.DIRECTIONS).map { - val imageURL = mainImageService.createImageURL(it.mainImage) - val attachments = attachmentService.createAttachmentResponses(it.attachments) - AboutDto.of(it, imageURL, attachments) - } + override fun readAllDirections(language: String): List { + val languageType = LanguageType.makeStringToLanguageType(language) + val directions = + aboutRepository.findAllByLanguageAndPostTypeOrderByName(languageType, AboutPostType.DIRECTIONS).map { + val imageURL = mainImageService.createImageURL(it.mainImage) + val attachments = attachmentService.createAttachmentResponses(it.attachments) + AboutDto.of(it, imageURL, attachments) + } return directions } @@ -153,13 +159,15 @@ class AboutServiceImpl( val list = mutableListOf() for (request in requestList) { + val language = request.language + val description = request.description val enumPostType = makeStringToEnum(request.postType) val aboutDto = AboutDto( id = null, + language = language, name = null, - engName = null, - description = request.description, + description = description, year = null, createdAt = null, modifiedAt = null, @@ -167,7 +175,9 @@ class AboutServiceImpl( imageURL = null, attachments = listOf() ) - val newAbout = AboutEntity.of(enumPostType, aboutDto) + + val languageType = LanguageType.makeStringToLanguageType(language) + val newAbout = AboutEntity.of(enumPostType, languageType, aboutDto) aboutRepository.save(newAbout) @@ -179,13 +189,14 @@ class AboutServiceImpl( @Transactional override fun migrateFutureCareers(request: FutureCareersRequest): FutureCareersPage { val description = request.description + val language = request.language val statList = mutableListOf() val companyList = mutableListOf() val aboutDto = AboutDto( id = null, + language = language, name = null, - engName = null, description = description, year = null, createdAt = null, @@ -194,7 +205,9 @@ class AboutServiceImpl( imageURL = null, attachments = listOf() ) - val newAbout = AboutEntity.of(AboutPostType.FUTURE_CAREERS, aboutDto) + + val languageType = LanguageType.makeStringToLanguageType(language) + val newAbout = AboutEntity.of(AboutPostType.FUTURE_CAREERS, languageType, aboutDto) aboutRepository.save(newAbout) for (stat in request.stat) { @@ -238,10 +251,13 @@ class AboutServiceImpl( val list = mutableListOf() for (request in requestList) { + val language = request.language + val name = request.name + val aboutDto = AboutDto( id = null, - name = request.name, - engName = request.engName, + language = language, + name = name, description = request.description, year = null, createdAt = null, @@ -250,7 +266,8 @@ class AboutServiceImpl( imageURL = null, attachments = listOf() ) - val newAbout = AboutEntity.of(AboutPostType.STUDENT_CLUBS, aboutDto) + val languageType = LanguageType.makeStringToLanguageType(language) + val newAbout = AboutEntity.of(AboutPostType.STUDENT_CLUBS, languageType, aboutDto) aboutRepository.save(newAbout) @@ -264,11 +281,14 @@ class AboutServiceImpl( val list = mutableListOf() for (request in requestList) { + val language = request.language + val name = request.name + val description = request.description val aboutDto = AboutDto( id = null, - name = request.name, - engName = null, - description = request.description, + language = language, + name = name, + description = description, year = null, createdAt = null, modifiedAt = null, @@ -277,7 +297,8 @@ class AboutServiceImpl( attachments = listOf() ) - val newAbout = AboutEntity.of(AboutPostType.FACILITIES, aboutDto) + val languageType = LanguageType.makeStringToLanguageType(language) + val newAbout = AboutEntity.of(AboutPostType.FACILITIES, languageType, aboutDto) for (location in request.locations) { LocationEntity.create(location, newAbout) @@ -295,11 +316,15 @@ class AboutServiceImpl( val list = mutableListOf() for (request in requestList) { + val language = request.language + val name = request.name + val description = request.description + val aboutDto = AboutDto( id = null, - name = request.name, - engName = request.engName, - description = request.description, + language = language, + name = name, + description = description, year = null, createdAt = null, modifiedAt = null, @@ -308,7 +333,8 @@ class AboutServiceImpl( attachments = listOf() ) - val newAbout = AboutEntity.of(AboutPostType.DIRECTIONS, aboutDto) + val languageType = LanguageType.makeStringToLanguageType(language) + val newAbout = AboutEntity.of(AboutPostType.DIRECTIONS, languageType, aboutDto) aboutRepository.save(newAbout) From dafdb2f8682e8c3ff231e69aa745c9bcbfbd1647 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, 11 Feb 2024 12:37:03 +0900 Subject: [PATCH 137/214] =?UTF-8?q?Refactor:=20Admission=20=EC=96=B8?= =?UTF-8?q?=EC=96=B4=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=84=B0=EB=A7=81=20=EC=A7=84=ED=96=89=20(#182)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat: Move type to type dir, and add main type enum. * Refactor: Add request body for create, and changed migrate elem body. * Feat: Change pagename -> name, add language and mainType column, add unique constraints. * Refactor: Remove unused request dto. * Refactor: Change AdmissionsDto by adding columns, to be able to used as pure dto. * Feat: Change to more specific search query. * Feat: Change multiple methods to few methods, by using mainType. * Feat: Change to few controller by using mainType, and use request bodies instead of dto. * Test: AdmissionsServiceTest * Fix: Add mapping path variable. * Fix: Fix replacing logic. --- .../admissions/api/AdmissionsController.kt | 60 ++++---- .../api/req/AdmissionMigrateElem.kt | 11 ++ .../admissions/api/req/AdmissionReqBody.kt | 9 ++ .../admissions/database/AdmissionsEntity.kt | 51 +++++-- .../admissions/database/AdmissionsPostType.kt | 5 - .../database/AdmissionsRepository.kt | 9 +- .../core/admissions/dto/AdmissionsDto.kt | 21 ++- .../core/admissions/dto/AdmissionsRequest.kt | 6 - .../admissions/service/AdmissionsService.kt | 133 +++++++----------- .../admissions/type/AdmissionsMainType.kt | 21 +++ .../admissions/type/AdmissionsPostType.kt | 28 ++++ .../service/AdmissionsServiceTest.kt | 122 ++++++++++++++++ 12 files changed, 330 insertions(+), 146 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/req/AdmissionMigrateElem.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/req/AdmissionReqBody.kt delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsPostType.kt delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/admissions/dto/AdmissionsRequest.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/admissions/type/AdmissionsMainType.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/admissions/type/AdmissionsPostType.kt create mode 100644 src/test/kotlin/com/wafflestudio/csereal/core/admissions/service/AdmissionsServiceTest.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 index 5b716ef9..9db77ef2 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,17 +1,15 @@ package com.wafflestudio.csereal.core.admissions.api import com.wafflestudio.csereal.common.aop.AuthenticatedStaff +import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.core.admissions.api.req.AdmissionReqBody import com.wafflestudio.csereal.core.admissions.dto.AdmissionsDto -import com.wafflestudio.csereal.core.admissions.dto.AdmissionsRequest +import com.wafflestudio.csereal.core.admissions.api.req.AdmissionMigrateElem import com.wafflestudio.csereal.core.admissions.service.AdmissionsService +import com.wafflestudio.csereal.core.admissions.type.AdmissionsMainType +import com.wafflestudio.csereal.core.admissions.type.AdmissionsPostType 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 +import org.springframework.web.bind.annotation.* @RequestMapping("/api/v1/admissions") @RestController @@ -19,40 +17,32 @@ class AdmissionsController( private val admissionsService: AdmissionsService ) { @AuthenticatedStaff - @PostMapping("/undergraduate/{postType}") - fun createUndergraduateAdmissions( - @PathVariable postType: String, + @PostMapping("/{mainTypeStr}/{postTypeStr}") + fun createAdmission( + @PathVariable(required = true) mainTypeStr: String, + @PathVariable(required = true) postTypeStr: String, @Valid @RequestBody - request: AdmissionsDto + req: AdmissionReqBody ): AdmissionsDto { - return admissionsService.createUndergraduateAdmissions(postType, request) + val mainType = AdmissionsMainType.fromJsonValue(mainTypeStr) + val postType = AdmissionsPostType.fromJsonValue(postTypeStr) + return admissionsService.createAdmission(req, mainType, postType) } - @AuthenticatedStaff - @PostMapping("/graduate") - fun createGraduateAdmissions( - @Valid @RequestBody - request: AdmissionsDto + @GetMapping("/{mainTypeStr}/{postTypeStr}") + fun readAdmission( + @PathVariable(required = true) mainTypeStr: String, + @PathVariable(required = true) postTypeStr: String, + @RequestParam(required = true, defaultValue = "ko") language: String ): AdmissionsDto { - return admissionsService.createGraduateAdmissions(request) - } - - @GetMapping("/undergraduate/{postType}") - fun readUndergraduateAdmissions( - @PathVariable postType: String - ): ResponseEntity { - return ResponseEntity.ok(admissionsService.readUndergraduateAdmissions(postType)) - } - - @GetMapping("/graduate") - fun readGraduateAdmissions(): ResponseEntity { - return ResponseEntity.ok(admissionsService.readGraduateAdmissions()) + val mainType = AdmissionsMainType.fromJsonValue(mainTypeStr) + val postType = AdmissionsPostType.fromJsonValue(postTypeStr) + val languageType = LanguageType.makeStringToLanguageType(language) + return admissionsService.readAdmission(mainType, postType, languageType) } @PostMapping("/migrate") fun migrateAdmissions( - @RequestBody requestList: List - ): ResponseEntity> { - return ResponseEntity.ok(admissionsService.migrateAdmissions(requestList)) - } + @RequestBody reqList: List<@Valid AdmissionMigrateElem> + ): List = admissionsService.migrateAdmissions(reqList) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/req/AdmissionMigrateElem.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/req/AdmissionMigrateElem.kt new file mode 100644 index 00000000..6589041c --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/req/AdmissionMigrateElem.kt @@ -0,0 +1,11 @@ +package com.wafflestudio.csereal.core.admissions.api.req + +import org.jetbrains.annotations.NotNull + +data class AdmissionMigrateElem( + @field:NotNull val name: String?, + val mainType: String, + val postType: String, + val language: String, + @field:NotNull val description: String? +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/req/AdmissionReqBody.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/req/AdmissionReqBody.kt new file mode 100644 index 00000000..5489f175 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/req/AdmissionReqBody.kt @@ -0,0 +1,9 @@ +package com.wafflestudio.csereal.core.admissions.api.req + +import org.jetbrains.annotations.NotNull + +data class AdmissionReqBody( + @field:NotNull val name: String?, + val language: String = "ko", + @field:NotNull val description: String? +) 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 a2f50be2..6b2e1466 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 @@ -1,28 +1,63 @@ package com.wafflestudio.csereal.core.admissions.database import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.core.admissions.api.req.AdmissionReqBody import com.wafflestudio.csereal.core.admissions.dto.AdmissionsDto +import com.wafflestudio.csereal.core.admissions.type.AdmissionsMainType +import com.wafflestudio.csereal.core.admissions.type.AdmissionsPostType import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.EnumType import jakarta.persistence.Enumerated +import jakarta.persistence.Table +import jakarta.persistence.UniqueConstraint @Entity(name = "admissions") +@Table( + uniqueConstraints = [ + UniqueConstraint(columnNames = ["language", "mainType", "postType"]) + ] +) class AdmissionsEntity( + val name: String, + + @Enumerated(EnumType.STRING) + val language: LanguageType, + + @Enumerated(EnumType.STRING) + val mainType: AdmissionsMainType, + @Enumerated(EnumType.STRING) val postType: AdmissionsPostType, - val pageName: String, @Column(columnDefinition = "mediumText") val description: String ) : BaseTimeEntity() { companion object { - fun of(postType: AdmissionsPostType, pageName: String, admissionsDto: AdmissionsDto): AdmissionsEntity { - return AdmissionsEntity( - postType = postType, - pageName = pageName, - description = admissionsDto.description - ) - } + fun of( + mainType: AdmissionsMainType, + postType: AdmissionsPostType, + name: String, + admissionsDto: AdmissionsDto + ) = AdmissionsEntity( + mainType = mainType, + postType = postType, + name = name, + description = admissionsDto.description, + language = LanguageType.makeStringToLanguageType(admissionsDto.language) + ) + + fun of( + mainType: AdmissionsMainType, + postType: AdmissionsPostType, + req: AdmissionReqBody + ) = AdmissionsEntity( + mainType = mainType, + postType = postType, + name = req.name!!, + description = req.description!!, + language = LanguageType.makeStringToLanguageType(req.language) + ) } } 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 deleted file mode 100644 index be0adf3f..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsPostType.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.wafflestudio.csereal.core.admissions.database - -enum class AdmissionsPostType { - GRADUATE, UNDERGRADUATE_EARLY_ADMISSION, UNDERGRADUATE_REGULAR_ADMISSION, -} 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 02c2c5c1..f6086ef2 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 @@ -1,7 +1,14 @@ package com.wafflestudio.csereal.core.admissions.database +import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.core.admissions.type.AdmissionsMainType +import com.wafflestudio.csereal.core.admissions.type.AdmissionsPostType import org.springframework.data.jpa.repository.JpaRepository interface AdmissionsRepository : JpaRepository { - fun findByPostType(postType: AdmissionsPostType): AdmissionsEntity + fun findByMainTypeAndPostTypeAndLanguage( + mainType: AdmissionsMainType, + postType: AdmissionsPostType, + language: LanguageType + ): 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 890f4825..80c28352 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,23 +1,30 @@ package com.wafflestudio.csereal.core.admissions.dto -import com.fasterxml.jackson.annotation.JsonInclude +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.admissions.database.AdmissionsEntity import java.time.LocalDateTime data class AdmissionsDto( - @JsonInclude(JsonInclude.Include.NON_NULL) - val id: Long? = null, + val id: Long, + val name: String, + val mainType: String, + val postType: String, + val language: String, val description: String, - val createdAt: LocalDateTime?, - val modifiedAt: LocalDateTime? + val createdAt: LocalDateTime, + val modifiedAt: LocalDateTime ) { companion object { fun of(entity: AdmissionsEntity): AdmissionsDto = entity.run { AdmissionsDto( id = this.id, + name = this.name, + mainType = this.mainType.toJsonValue(), + postType = this.postType.toJsonValue(), + language = LanguageType.makeLowercase(this.language), description = this.description, - createdAt = this.createdAt, - modifiedAt = this.modifiedAt + createdAt = this.createdAt!!, + modifiedAt = this.modifiedAt!! ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/dto/AdmissionsRequest.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/dto/AdmissionsRequest.kt deleted file mode 100644 index 4fd1e458..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/dto/AdmissionsRequest.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.wafflestudio.csereal.core.admissions.dto - -data class AdmissionsRequest( - val postType: String, - val description: String -) 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 4f943785..f56e026e 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,20 +1,29 @@ package com.wafflestudio.csereal.core.admissions.service import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.core.admissions.api.req.AdmissionReqBody import com.wafflestudio.csereal.core.admissions.database.AdmissionsEntity -import com.wafflestudio.csereal.core.admissions.database.AdmissionsPostType +import com.wafflestudio.csereal.core.admissions.type.AdmissionsPostType import com.wafflestudio.csereal.core.admissions.database.AdmissionsRepository import com.wafflestudio.csereal.core.admissions.dto.AdmissionsDto -import com.wafflestudio.csereal.core.admissions.dto.AdmissionsRequest +import com.wafflestudio.csereal.core.admissions.api.req.AdmissionMigrateElem +import com.wafflestudio.csereal.core.admissions.type.AdmissionsMainType import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional interface AdmissionsService { - fun createUndergraduateAdmissions(postType: String, request: AdmissionsDto): AdmissionsDto - fun createGraduateAdmissions(request: AdmissionsDto): AdmissionsDto - fun readUndergraduateAdmissions(postType: String): AdmissionsDto - fun readGraduateAdmissions(): AdmissionsDto - fun migrateAdmissions(requestList: List): List + fun createAdmission( + req: AdmissionReqBody, + mainType: AdmissionsMainType, + postType: AdmissionsPostType + ): AdmissionsDto + fun readAdmission( + mainType: AdmissionsMainType, + postType: AdmissionsPostType, + language: LanguageType + ): AdmissionsDto + fun migrateAdmissions(requestList: List): List } @Service @@ -22,87 +31,43 @@ class AdmissionsServiceImpl( private val admissionsRepository: AdmissionsRepository ) : AdmissionsService { @Transactional - override fun createUndergraduateAdmissions(postType: String, request: AdmissionsDto): AdmissionsDto { - val enumPostType = makeStringToAdmissionsPostType(postType) - - 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) - - return AdmissionsDto.of(newAdmissions) - } - - @Transactional - override fun createGraduateAdmissions(request: AdmissionsDto): AdmissionsDto { - val newAdmissions: AdmissionsEntity = AdmissionsEntity.of(AdmissionsPostType.GRADUATE, "전기/후기 모집", request) - - admissionsRepository.save(newAdmissions) - - return AdmissionsDto.of(newAdmissions) + override fun createAdmission( + req: AdmissionReqBody, + mainType: AdmissionsMainType, + postType: AdmissionsPostType + ) = admissionsRepository.save( + AdmissionsEntity.of(mainType, postType, req) + ).let { + AdmissionsDto.of(it) } @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) - ) - - else -> throw CserealException.Csereal404("해당하는 페이지를 찾을 수 없습니다.") - } - } - - @Transactional(readOnly = true) - override fun readGraduateAdmissions(): AdmissionsDto { - return AdmissionsDto.of(admissionsRepository.findByPostType(AdmissionsPostType.GRADUATE)) - } + override fun readAdmission( + mainType: AdmissionsMainType, + postType: AdmissionsPostType, + language: LanguageType + ) = admissionsRepository.findByMainTypeAndPostTypeAndLanguage( + mainType, + postType, + language + )?.let { AdmissionsDto.of(it) } + ?: throw CserealException.Csereal404("해당하는 페이지를 찾을 수 없습니다.") @Transactional - override fun migrateAdmissions(requestList: List): List { - val list = mutableListOf() - - for (request in requestList) { - val enumPostType = makeStringToAdmissionsPostType(request.postType) - - val pageName = when (enumPostType) { - AdmissionsPostType.UNDERGRADUATE_EARLY_ADMISSION -> "수시 모집" - AdmissionsPostType.UNDERGRADUATE_REGULAR_ADMISSION -> "정시 모집" - AdmissionsPostType.GRADUATE -> "대학원" - else -> throw CserealException.Csereal404("해당하는 페이지를 찾을 수 없습니다.") - } - - val admissionsDto = AdmissionsDto( - id = null, - description = request.description, - createdAt = null, - modifiedAt = null - ) - - val newAdmissions = AdmissionsEntity.of(enumPostType, pageName, admissionsDto) - - admissionsRepository.save(newAdmissions) - - list.add(AdmissionsDto.of(newAdmissions)) - } - return list - } - - private fun makeStringToAdmissionsPostType(postType: String): AdmissionsPostType { - try { - val upperPostType = postType.replace("-", "_").uppercase() - return AdmissionsPostType.valueOf(upperPostType) - } catch (e: IllegalArgumentException) { - throw CserealException.Csereal400("해당하는 enum을 찾을 수 없습니다") - } + override fun migrateAdmissions(requestList: List) = requestList.map { + val mainType = AdmissionsMainType.fromJsonValue(it.mainType) + val postType = AdmissionsPostType.fromJsonValue(it.postType) + val language = LanguageType.makeStringToLanguageType(it.language) + AdmissionsEntity( + name = it.name!!, + mainType = mainType, + postType = postType, + language = language, + description = it.description!! + ) + }.let { + admissionsRepository.saveAll(it) + }.map { + AdmissionsDto.of(it) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/type/AdmissionsMainType.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/type/AdmissionsMainType.kt new file mode 100644 index 00000000..7329ee64 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/type/AdmissionsMainType.kt @@ -0,0 +1,21 @@ +package com.wafflestudio.csereal.core.admissions.type + +import com.wafflestudio.csereal.common.CserealException + +enum class AdmissionsMainType { + UNDERGRADUATE, + GRADUATE, + INTERNATIONAL; + + fun toJsonValue() = this.name.lowercase() + + companion object { + fun fromJsonValue(field: String) = try { + field + .uppercase() + .let { AdmissionsMainType.valueOf(it) } + } catch (e: IllegalArgumentException) { + throw CserealException.Csereal400("존재하지 않는 Admission Main Type입니다.") + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/type/AdmissionsPostType.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/type/AdmissionsPostType.kt new file mode 100644 index 00000000..2c0a87b2 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/type/AdmissionsPostType.kt @@ -0,0 +1,28 @@ +package com.wafflestudio.csereal.core.admissions.type + +import com.wafflestudio.csereal.common.CserealException + +enum class AdmissionsPostType { + // For graduate, undergraduate + EARLY_ADMISSION, + REGULAR_ADMISSION, + + // For international + UNDERGRADUATE, + GRADUATE, + EXCHANGE_VISITING, + SCHOLARSHIPS; + + fun toJsonValue() = this.name.lowercase() + + companion object { + fun fromJsonValue(field: String) = + try { + field.replace('-', '_') + .uppercase() + .let { AdmissionsPostType.valueOf(it) } + } catch (e: IllegalArgumentException) { + throw CserealException.Csereal400("잘못된 Admission Post Type이 주어졌습니다.") + } + } +} diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/admissions/service/AdmissionsServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/admissions/service/AdmissionsServiceTest.kt new file mode 100644 index 00000000..24c0a505 --- /dev/null +++ b/src/test/kotlin/com/wafflestudio/csereal/core/admissions/service/AdmissionsServiceTest.kt @@ -0,0 +1,122 @@ +package com.wafflestudio.csereal.core.admissions.service + +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.core.admissions.api.req.AdmissionReqBody +import com.wafflestudio.csereal.core.admissions.database.AdmissionsEntity +import com.wafflestudio.csereal.core.admissions.database.AdmissionsRepository +import com.wafflestudio.csereal.core.admissions.type.AdmissionsMainType +import com.wafflestudio.csereal.core.admissions.type.AdmissionsPostType +import io.kotest.assertions.throwables.shouldThrow +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.context.annotation.Profile +import org.springframework.data.repository.findByIdOrNull +import org.springframework.transaction.annotation.Transactional + +@SpringBootTest +@Profile("test") +@Transactional +class AdmissionsServiceTest( + private val admissionsService: AdmissionsService, + private val admissionsRepository: AdmissionsRepository +) : BehaviorSpec({ + extensions(SpringTestExtension(SpringTestLifecycleMode.Root)) + + afterContainer { + admissionsRepository.deleteAll() + } + + Given("AdmissionReqBody, AdmissionMainType, AdmissionPostType이 주어졌을 때") { + val req = AdmissionReqBody( + name = "name", + language = "ko", + description = "description" + ) + val mainType = AdmissionsMainType.UNDERGRADUATE + val postType = AdmissionsPostType.REGULAR_ADMISSION + + When("createAdmission이 호출되면") { + val result = admissionsService.createAdmission(req, mainType, postType) + + Then("주어진 정보와 일치하는 AdmissionDto가 반환된다.") { + result.name shouldBe req.name + result.mainType shouldBe mainType.toJsonValue() + result.postType shouldBe postType.toJsonValue() + result.language shouldBe req.language + result.description shouldBe req.description + } + + Then("주어진 정보와 일치하는 AdmissionEnitity가 생성된다.") { + val entity = admissionsRepository.findByIdOrNull(result.id) + entity shouldNotBe null + entity!!.name shouldBe req.name + entity.mainType shouldBe mainType + entity.postType shouldBe postType + entity.language shouldBe LanguageType.makeStringToLanguageType(req.language) + entity.description shouldBe req.description + } + } + } + Given("AdmissionReqBody에 잘못된 Language가 주어졌을 때") { + val req = AdmissionReqBody( + name = "name", + language = "wrong", + description = "description" + ) + val mainType = AdmissionsMainType.UNDERGRADUATE + val postType = AdmissionsPostType.REGULAR_ADMISSION + When("createAdmission이 호출되면") { + Then("Csereal400 에러가 발생한다.") { + shouldThrow { + admissionsService.createAdmission(req, mainType, postType) + } + } + } + } + + Given("Admission이 존재할 때") { + val admission = AdmissionsEntity( + name = "name", + mainType = AdmissionsMainType.INTERNATIONAL, + postType = AdmissionsPostType.EXCHANGE_VISITING, + language = LanguageType.EN, + description = "description" + ).let { + admissionsRepository.save(it) + } + + When("존재하는 readAdmission이 호출되면") { + val mainType = AdmissionsMainType.INTERNATIONAL + val postType = AdmissionsPostType.EXCHANGE_VISITING + val language = LanguageType.EN + val result = admissionsService.readAdmission(mainType, postType, language) + + Then("주어진 정보와 일치하는 AdmissionDto가 반환된다.") { + result.let { + it.name shouldBe admission.name + it.mainType shouldBe admission.mainType.toJsonValue() + it.postType shouldBe admission.postType.toJsonValue() + it.language shouldBe LanguageType.makeLowercase(admission.language) + it.description shouldBe admission.description + } + } + } + + When("존재하지 않는 readAdmission이 호출되면") { + Then("Csereal404 에러가 발생한다.") { + shouldThrow { + admissionsService.readAdmission( + AdmissionsMainType.UNDERGRADUATE, + AdmissionsPostType.REGULAR_ADMISSION, + LanguageType.KO + ) + } + } + } + } +}) From d664dc46131687e27dec9d0943656af575c710a9 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Sun, 11 Feb 2024 15:42:10 +0900 Subject: [PATCH 138/214] =?UTF-8?q?feat:=20academics=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=EC=97=90=20language=20field=20=EC=B6=94=EA=B0=80=20(#?= =?UTF-8?q?181)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 엔티티, dto에 language 추가 * feat: read에 language 추가 * feat: academics에 language field 추가 * fix: ktlint 수정 * feat: course 엔티티에 language field 추가 * feat: readCourse에 language parameter 추가 * fix: languageRepository 삭제, 함수 enum 클래스 이동, academicsEntity name 속성 nullable 추가 * fix: requestParam+defaultValue 추가, * fix: academics name nullable 없앰 --- .../core/about/database/AboutRepository.kt | 10 ++++-- .../core/academics/api/AcademicsController.kt | 6 ++-- .../academics/database/AcademicsEntity.kt | 5 +++ .../core/academics/database/CourseEntity.kt | 17 ++++----- .../academics/database/CourseRepository.kt | 11 ++++-- .../core/academics/dto/AcademicsDto.kt | 3 ++ .../csereal/core/academics/dto/CourseDto.kt | 3 ++ .../academics/service/AcademicsService.kt | 36 ++++++++++--------- 8 files changed, 59 insertions(+), 32 deletions(-) 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 3790bce0..b98a26fd 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 @@ -4,6 +4,12 @@ import com.wafflestudio.csereal.common.properties.LanguageType import org.springframework.data.jpa.repository.JpaRepository interface AboutRepository : JpaRepository { - fun findAllByLanguageAndPostTypeOrderByName(languageType: LanguageType, postType: AboutPostType): List - fun findByLanguageAndPostType(languageType: LanguageType, postType: AboutPostType): AboutEntity + fun findAllByLanguageAndPostTypeOrderByName( + languageType: LanguageType, + postType: AboutPostType + ): List + fun findByLanguageAndPostType( + languageType: LanguageType, + postType: AboutPostType + ): AboutEntity } 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 6fe51849..55df6ce2 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 @@ -59,16 +59,18 @@ class AcademicsController( @GetMapping("/{studentType}/courses") fun readAllCourses( + @RequestParam(required = false, defaultValue = "ko") language: String, @PathVariable studentType: String ): ResponseEntity> { - return ResponseEntity.ok(academicsService.readAllCourses(studentType)) + return ResponseEntity.ok(academicsService.readAllCourses(language, studentType)) } @GetMapping("/course") fun readCourse( + @RequestParam(required = false, defaultValue = "ko") language: String, @RequestParam name: String ): ResponseEntity { - return ResponseEntity.ok(academicsService.readCourse(name)) + return ResponseEntity.ok(academicsService.readCourse(language, name)) } @GetMapping("/undergraduate/general-studies-requirements") 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 f3861445..e5e55b91 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 @@ -2,6 +2,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.common.properties.LanguageType import com.wafflestudio.csereal.core.academics.dto.AcademicsDto import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity import jakarta.persistence.* @@ -13,6 +14,8 @@ class AcademicsEntity( @Enumerated(EnumType.STRING) var postType: AcademicsPostType, + @Enumerated(EnumType.STRING) + var language: LanguageType, var name: String, var description: String, @@ -32,11 +35,13 @@ class AcademicsEntity( fun of( studentType: AcademicsStudentType, postType: AcademicsPostType, + languageType: LanguageType, academicsDto: AcademicsDto ): AcademicsEntity { return AcademicsEntity( studentType = studentType, postType = postType, + language = languageType, name = academicsDto.name, description = academicsDto.description, year = academicsDto.year, 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 baa4769c..792c0507 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,12 +2,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.common.properties.LanguageType 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 -import jakarta.persistence.OneToOne +import jakarta.persistence.* @Entity(name = "course") class CourseEntity( @@ -15,16 +13,14 @@ class CourseEntity( var studentType: AcademicsStudentType, - var classification: String, + @Enumerated(EnumType.STRING) + var language: LanguageType, + var classification: String, var code: String, - var name: String, - var credit: Int, - var grade: String, - var description: String?, @OneToMany(mappedBy = "course", cascade = [CascadeType.ALL], orphanRemoval = true) @@ -36,9 +32,10 @@ class CourseEntity( ) : BaseTimeEntity(), AttachmentContentEntityType { override fun bringAttachments() = attachments companion object { - fun of(studentType: AcademicsStudentType, courseDto: CourseDto): CourseEntity { + fun of(studentType: AcademicsStudentType, languageType: LanguageType, courseDto: CourseDto): CourseEntity { return CourseEntity( studentType = studentType, + language = languageType, classification = courseDto.classification, code = courseDto.code, name = courseDto.name.replace(" ", "-"), 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 3ed6f69e..c48ec048 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 @@ -1,8 +1,15 @@ package com.wafflestudio.csereal.core.academics.database +import com.wafflestudio.csereal.common.properties.LanguageType import org.springframework.data.jpa.repository.JpaRepository interface CourseRepository : JpaRepository { - fun findAllByStudentTypeOrderByNameAsc(studentType: AcademicsStudentType): List - fun findByName(name: String): CourseEntity + fun findAllByLanguageAndStudentTypeOrderByNameAsc( + languageType: LanguageType, + studentType: AcademicsStudentType + ): List + fun findByLanguageAndName( + languageType: LanguageType, + name: String + ): CourseEntity } 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 448a0632..1530c621 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,11 +1,13 @@ package com.wafflestudio.csereal.core.academics.dto +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.academics.database.AcademicsEntity import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse import java.time.LocalDateTime data class AcademicsDto( val id: Long = -1, // TODO: Seperate to multiple DTOs or set this as nullable + val language: String, val name: String, val description: String, val year: Int? = null, @@ -18,6 +20,7 @@ data class AcademicsDto( fun of(entity: AcademicsEntity, attachmentResponses: List): AcademicsDto = entity.run { AcademicsDto( id = this.id, + language = LanguageType.makeLowercase(this.language), name = this.name, description = this.description, year = this.year, 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 dedc13f6..e1f680fc 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,10 +1,12 @@ package com.wafflestudio.csereal.core.academics.dto +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.academics.database.CourseEntity import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse data class CourseDto( val id: Long, + val language: String, val classification: String, val code: String, val name: String, @@ -17,6 +19,7 @@ data class CourseDto( fun of(entity: CourseEntity, attachmentResponses: List): CourseDto = entity.run { CourseDto( id = this.id, + language = LanguageType.makeLowercase(this.language), classification = this.classification, code = this.code, name = this.name, 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 7ee322cf..176d5b8b 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,6 +1,7 @@ package com.wafflestudio.csereal.core.academics.service import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.academics.database.* import com.wafflestudio.csereal.core.academics.dto.* import com.wafflestudio.csereal.core.resource.attachment.service.AttachmentService @@ -23,8 +24,8 @@ interface AcademicsService { 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 readAllCourses(language: String, studentType: String): List + fun readCourse(language: String, name: String): CourseDto fun createScholarshipDetail(studentType: String, request: ScholarshipDto): ScholarshipDto fun readAllScholarship(studentType: String): ScholarshipPageResponse fun readScholarship(scholarshipId: Long): ScholarshipDto @@ -50,8 +51,8 @@ class AcademicsServiceImpl( ): AcademicsDto { val enumStudentType = makeStringToAcademicsStudentType(studentType) val enumPostType = makeStringToAcademicsPostType(postType) - - var newAcademics = AcademicsEntity.of(enumStudentType, enumPostType, request) + val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) + val newAcademics = AcademicsEntity.of(enumStudentType, enumPostType, enumLanguageType, request) if (attachments != null) { attachmentService.uploadAllAttachments(newAcademics, attachments) @@ -62,7 +63,7 @@ class AcademicsServiceImpl( academicsSearch = AcademicsSearchEntity.create(this) } - newAcademics = academicsRepository.save(newAcademics) + academicsRepository.save(newAcademics) val attachmentResponses = attachmentService.createAttachmentResponses(newAcademics.attachments) @@ -113,7 +114,9 @@ class AcademicsServiceImpl( @Transactional override fun createCourse(studentType: String, request: CourseDto, attachments: List?): CourseDto { val enumStudentType = makeStringToAcademicsStudentType(studentType) - var newCourse = CourseEntity.of(enumStudentType, request) + val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) + + val newCourse = CourseEntity.of(enumStudentType, enumLanguageType, request) if (attachments != null) { attachmentService.uploadAllAttachments(newCourse, attachments) @@ -123,8 +126,7 @@ class AcademicsServiceImpl( newCourse.apply { academicsSearch = AcademicsSearchEntity.create(this) } - - newCourse = courseRepository.save(newCourse) + courseRepository.save(newCourse) val attachmentResponses = attachmentService.createAttachmentResponses(newCourse.attachments) @@ -132,19 +134,21 @@ class AcademicsServiceImpl( } @Transactional(readOnly = true) - override fun readAllCourses(studentType: String): List { + override fun readAllCourses(language: String, studentType: String): List { val enumStudentType = makeStringToAcademicsStudentType(studentType) - - val courseDtoList = courseRepository.findAllByStudentTypeOrderByNameAsc(enumStudentType).map { - val attachmentResponses = attachmentService.createAttachmentResponses(it.attachments) - CourseDto.of(it, attachmentResponses) - } + val enumLanguageType = LanguageType.makeStringToLanguageType(language) + val courseDtoList = + courseRepository.findAllByLanguageAndStudentTypeOrderByNameAsc(enumLanguageType, enumStudentType).map { + val attachmentResponses = attachmentService.createAttachmentResponses(it.attachments) + CourseDto.of(it, attachmentResponses) + } return courseDtoList } @Transactional(readOnly = true) - override fun readCourse(name: String): CourseDto { - val course = courseRepository.findByName(name) + override fun readCourse(language: String, name: String): CourseDto { + val enumLanguageType = LanguageType.makeStringToLanguageType(language) + val course = courseRepository.findByLanguageAndName(enumLanguageType, name) val attachmentResponses = attachmentService.createAttachmentResponses(course.attachments) return CourseDto.of(course, attachmentResponses) From 1870638cfdb98635ad0e1445c19a0639b17dcf05 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Thu, 15 Feb 2024 23:04:13 +0900 Subject: [PATCH 139/214] =?UTF-8?q?feat:=20staff=EC=97=90=20language,=20mi?= =?UTF-8?q?gration=20=EB=B0=A9=EB=B2=95=20=EC=B6=94=EA=B0=80=20(#183)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: staffEntity에 language 추가 * feat: branch 이름 변경 * fix: branch origin 이름 변경 * feat: staff 읽기, 수정에서 language 속성 추가 * feat: migrateStaffImage 추가 * fix: StaffServiceTest 수정 --- .../core/member/api/StaffController.kt | 14 ++++++++-- .../core/member/database/StaffEntity.kt | 12 ++++---- .../core/member/database/StaffRepository.kt | 5 +++- .../csereal/core/member/dto/StaffDto.kt | 3 ++ .../core/member/service/StaffService.kt | 28 +++++++++++++++---- .../core/member/service/StaffServiceTest.kt | 3 ++ 6 files changed, 52 insertions(+), 13 deletions(-) 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 eed5164c..6f298f94 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 @@ -29,8 +29,10 @@ class StaffController( } @GetMapping - fun getAllStaff(): ResponseEntity> { - return ResponseEntity.ok(staffService.getAllStaff()) + fun getAllStaff( + @RequestParam(required = false, defaultValue = "ko") language: String + ): ResponseEntity> { + return ResponseEntity.ok(staffService.getAllStaff(language)) } @AuthenticatedStaff @@ -55,4 +57,12 @@ class StaffController( ): ResponseEntity> { return ResponseEntity.ok(staffService.migrateStaff(requestList)) } + + @PatchMapping("/migrateImage/{staffId}") + fun migrateStaffImage( + @PathVariable staffId: Long, + @RequestPart("mainImage") mainImage: MultipartFile + ): ResponseEntity { + return ResponseEntity.ok(staffService.migrateStaffImage(staffId, mainImage)) + } } 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 c9b704f6..60fc24b6 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 @@ -2,15 +2,16 @@ package com.wafflestudio.csereal.core.member.database import com.wafflestudio.csereal.common.config.BaseTimeEntity import com.wafflestudio.csereal.common.controller.MainImageContentEntityType +import com.wafflestudio.csereal.common.properties.LanguageType 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 +import jakarta.persistence.* @Entity(name = "staff") class StaffEntity( + @Enumerated(EnumType.STRING) + var language: LanguageType, + var name: String, var role: String, @@ -30,8 +31,9 @@ class StaffEntity( override fun bringMainImage(): MainImageEntity? = mainImage companion object { - fun of(staffDto: StaffDto): StaffEntity { + fun of(languageType: LanguageType, staffDto: StaffDto): StaffEntity { return StaffEntity( + language = languageType, name = staffDto.name, role = staffDto.role, office = staffDto.office, 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 a293b642..6ce765aa 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 @@ -1,5 +1,8 @@ package com.wafflestudio.csereal.core.member.database +import com.wafflestudio.csereal.common.properties.LanguageType import org.springframework.data.jpa.repository.JpaRepository -interface StaffRepository : JpaRepository +interface StaffRepository : JpaRepository { + fun findAllByLanguage(languageType: LanguageType): List +} 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 bf75f902..1c16482a 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 @@ -1,11 +1,13 @@ package com.wafflestudio.csereal.core.member.dto import com.fasterxml.jackson.annotation.JsonInclude +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.member.database.StaffEntity data class StaffDto( @JsonInclude(JsonInclude.Include.NON_NULL) var id: Long? = null, + val language: String, val name: String, val role: String, val office: String, @@ -19,6 +21,7 @@ data class StaffDto( fun of(staffEntity: StaffEntity, imageURL: String?): StaffDto { return StaffDto( id = staffEntity.id, + language = LanguageType.makeLowercase(staffEntity.language), name = staffEntity.name, role = staffEntity.role, office = staffEntity.office, 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 90a527c9..32d5f9a4 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.common.properties.LanguageType import com.wafflestudio.csereal.core.member.database.MemberSearchEntity import com.wafflestudio.csereal.core.member.database.StaffEntity import com.wafflestudio.csereal.core.member.database.StaffRepository @@ -16,10 +17,11 @@ import org.springframework.web.multipart.MultipartFile interface StaffService { fun createStaff(createStaffRequest: StaffDto, mainImage: MultipartFile?): StaffDto fun getStaff(staffId: Long): StaffDto - fun getAllStaff(): List + fun getAllStaff(language: String): List fun updateStaff(staffId: Long, updateStaffRequest: StaffDto, mainImage: MultipartFile?): StaffDto fun deleteStaff(staffId: Long) fun migrateStaff(requestList: List): List + fun migrateStaffImage(staffId: Long, mainImage: MultipartFile): StaffDto } @Service @@ -29,7 +31,8 @@ class StaffServiceImpl( private val mainImageService: MainImageService ) : StaffService { override fun createStaff(createStaffRequest: StaffDto, mainImage: MultipartFile?): StaffDto { - val staff = StaffEntity.of(createStaffRequest) + val enumLanguageType = LanguageType.makeStringToLanguageType(createStaffRequest.language) + val staff = StaffEntity.of(enumLanguageType, createStaffRequest) for (task in createStaffRequest.tasks) { TaskEntity.create(task, staff) @@ -59,8 +62,10 @@ class StaffServiceImpl( } @Transactional(readOnly = true) - override fun getAllStaff(): List { - return staffRepository.findAll().map { + override fun getAllStaff(language: String): List { + val enumLanguageType = LanguageType.makeStringToLanguageType(language) + + return staffRepository.findAllByLanguage(enumLanguageType).map { val imageURL = mainImageService.createImageURL(it.mainImage) SimpleStaffDto.of(it, imageURL) }.sortedBy { it.name } @@ -107,7 +112,8 @@ class StaffServiceImpl( val list = mutableListOf() for (request in requestList) { - val staff = StaffEntity.of(request) + val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) + val staff = StaffEntity.of(enumLanguageType, request) for (task in request.tasks) { TaskEntity.create(task, staff) @@ -122,4 +128,16 @@ class StaffServiceImpl( return list } + + @Transactional + override fun migrateStaffImage(staffId: Long, mainImage: MultipartFile): StaffDto { + val staff = staffRepository.findByIdOrNull(staffId) + ?: throw CserealException.Csereal404("해당 행정직원을 찾을 수 없습니다. staffId: $staffId") + + mainImageService.uploadMainImage(staff, mainImage) + + val imageURL = mainImageService.createImageURL(staff.mainImage) + + return StaffDto.of(staff, imageURL) + } } 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 c0d61496..37043fad 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 @@ -27,6 +27,7 @@ class StaffServiceTest( Given("이미지 없는 행정직원을 생성하려고 할 떄") { val staffDto = StaffDto( + language = "ko", name = "name", role = "role", office = "office", @@ -75,6 +76,7 @@ class StaffServiceTest( Given("이미지 없는 행정직원을 수정할 때") { val staffDto = StaffDto( + language = "ko", name = "name", role = "role", office = "office", @@ -87,6 +89,7 @@ class StaffServiceTest( When("행정직원을 수정하면") { val updateStaffDto = StaffDto( + language = "ko", name = "name2", role = "role2", office = "office2", From 424570e97a795a9662b2a80a79dd086e34952143 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, 18 Feb 2024 17:35:43 +0900 Subject: [PATCH 140/214] =?UTF-8?q?Feat:=20Admission=20=EA=B2=80=EC=83=89?= =?UTF-8?q?=20(#184)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat: Add korean, english section name. * Feat: add searchContent column for saving index. * Feat: Add search query repository method. * Feat: Define dto of search result element. * Feat: Add search Top Admission method, and add migrate method creating search content. * Feat: Add search top admissions method to controller. * Test: Add test for Admission Service. --- .../admissions/api/AdmissionsController.kt | 11 ++++++ .../api/res/AdmissionSearchResElem.kt | 28 +++++++++++++ .../admissions/database/AdmissionsEntity.kt | 39 +++++++++++++++++-- .../database/AdmissionsRepository.kt | 36 ++++++++++++++++- .../admissions/service/AdmissionsService.kt | 25 ++++++++++-- .../admissions/type/AdmissionsMainType.kt | 17 ++++++-- .../admissions/type/AdmissionsPostType.kt | 23 +++++++---- .../service/AdmissionsServiceTest.kt | 16 +++++++- 8 files changed, 174 insertions(+), 21 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/res/AdmissionSearchResElem.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 index 9db77ef2..b25edb3c 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 @@ -41,6 +41,17 @@ class AdmissionsController( return admissionsService.readAdmission(mainType, postType, languageType) } + @GetMapping("/search") + fun searchTopAdmissions( + @RequestParam(required = true) keyword: String, + @RequestParam(required = true, defaultValue = "ko") language: String, + @RequestParam(required = true) number: Int + ) = admissionsService.searchTopAdmission( + keyword, + LanguageType.makeStringToLanguageType(language), + number + ) + @PostMapping("/migrate") fun migrateAdmissions( @RequestBody reqList: List<@Valid AdmissionMigrateElem> diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/res/AdmissionSearchResElem.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/res/AdmissionSearchResElem.kt new file mode 100644 index 00000000..a1e7d015 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/res/AdmissionSearchResElem.kt @@ -0,0 +1,28 @@ +package com.wafflestudio.csereal.core.admissions.api.res + +import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.core.admissions.database.AdmissionsEntity + +data class AdmissionSearchResBody( + val admissions: List +) + +data class AdmissionSearchResElem( + val id: Long, + val name: String, + val mainType: String, + val postType: String, + val language: String +) { + companion object { + fun of( + admissions: AdmissionsEntity + ) = AdmissionSearchResElem( + id = admissions.id, + name = admissions.name, + mainType = admissions.mainType.toJsonValue(), + postType = admissions.postType.toJsonValue(), + language = LanguageType.makeLowercase(admissions.language) + ) + } +} 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 6b2e1466..5f37583e 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 @@ -2,6 +2,7 @@ package com.wafflestudio.csereal.core.admissions.database import com.wafflestudio.csereal.common.config.BaseTimeEntity import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.utils.cleanTextFromHtml import com.wafflestudio.csereal.core.admissions.api.req.AdmissionReqBody import com.wafflestudio.csereal.core.admissions.dto.AdmissionsDto import com.wafflestudio.csereal.core.admissions.type.AdmissionsMainType @@ -20,7 +21,7 @@ import jakarta.persistence.UniqueConstraint ] ) class AdmissionsEntity( - val name: String, + var name: String, @Enumerated(EnumType.STRING) val language: LanguageType, @@ -32,7 +33,10 @@ class AdmissionsEntity( val postType: AdmissionsPostType, @Column(columnDefinition = "mediumText") - val description: String + val description: String, + + @Column(nullable = false, columnDefinition = "mediumText") + var searchContent: String ) : BaseTimeEntity() { companion object { fun of( @@ -45,7 +49,14 @@ class AdmissionsEntity( postType = postType, name = name, description = admissionsDto.description, - language = LanguageType.makeStringToLanguageType(admissionsDto.language) + language = LanguageType.makeStringToLanguageType(admissionsDto.language), + searchContent = createSearchContent( + name = name, + mainType = mainType, + postType = postType, + language = LanguageType.makeStringToLanguageType(admissionsDto.language), + description = admissionsDto.description + ) ) fun of( @@ -57,7 +68,27 @@ class AdmissionsEntity( postType = postType, name = req.name!!, description = req.description!!, - language = LanguageType.makeStringToLanguageType(req.language) + language = LanguageType.makeStringToLanguageType(req.language), + searchContent = createSearchContent( + name = req.name, + mainType = mainType, + postType = postType, + language = LanguageType.makeStringToLanguageType(req.language), + description = req.description + ) ) + + fun createSearchContent( + name: String, + mainType: AdmissionsMainType, + postType: AdmissionsPostType, + language: LanguageType, + description: String + ) = StringBuilder().apply { + appendLine(name) + appendLine(mainType.getLanguageValue(language)) + appendLine(postType.getLanguageValue(language)) + appendLine(cleanTextFromHtml(description)) + }.toString() } } 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 f6086ef2..34979cb0 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 @@ -1,14 +1,48 @@ package com.wafflestudio.csereal.core.admissions.database +import com.querydsl.jpa.impl.JPAQueryFactory import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.repository.CommonRepository +import com.wafflestudio.csereal.core.admissions.database.QAdmissionsEntity.admissionsEntity import com.wafflestudio.csereal.core.admissions.type.AdmissionsMainType import com.wafflestudio.csereal.core.admissions.type.AdmissionsPostType import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository -interface AdmissionsRepository : JpaRepository { +interface AdmissionsRepository : JpaRepository, AdmissionsCustomRepository { fun findByMainTypeAndPostTypeAndLanguage( mainType: AdmissionsMainType, postType: AdmissionsPostType, language: LanguageType ): AdmissionsEntity? } + +interface AdmissionsCustomRepository { + fun searchTopAdmissions(keyword: String, language: LanguageType, number: Int): List +} + +@Repository +class AdmissionsCustomRepositoryImpl( + private val commonRepository: CommonRepository, + private val queryFactory: JPAQueryFactory +) : AdmissionsCustomRepository { + override fun searchTopAdmissions( + keyword: String, + language: LanguageType, + number: Int + ): List = + searchQueryOfLanguage(keyword, language) + .limit(number.toLong()) + .fetch() + + fun searchQueryOfLanguage(keyword: String, language: LanguageType) = + queryFactory.selectFrom( + admissionsEntity + ).where( + commonRepository.searchFullSingleTextTemplate( + keyword, + admissionsEntity.searchContent + ).gt(0.0), + admissionsEntity.language.eq(language) + ) +} 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 f56e026e..a31e65d3 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 @@ -2,13 +2,14 @@ package com.wafflestudio.csereal.core.admissions.service import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.core.admissions.api.req.AdmissionMigrateElem import com.wafflestudio.csereal.core.admissions.api.req.AdmissionReqBody +import com.wafflestudio.csereal.core.admissions.api.res.AdmissionSearchResElem import com.wafflestudio.csereal.core.admissions.database.AdmissionsEntity -import com.wafflestudio.csereal.core.admissions.type.AdmissionsPostType import com.wafflestudio.csereal.core.admissions.database.AdmissionsRepository import com.wafflestudio.csereal.core.admissions.dto.AdmissionsDto -import com.wafflestudio.csereal.core.admissions.api.req.AdmissionMigrateElem import com.wafflestudio.csereal.core.admissions.type.AdmissionsMainType +import com.wafflestudio.csereal.core.admissions.type.AdmissionsPostType import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -18,12 +19,17 @@ interface AdmissionsService { mainType: AdmissionsMainType, postType: AdmissionsPostType ): AdmissionsDto + fun readAdmission( mainType: AdmissionsMainType, postType: AdmissionsPostType, language: LanguageType ): AdmissionsDto + fun migrateAdmissions(requestList: List): List + + @Transactional(readOnly = true) + fun searchTopAdmission(keyword: String, language: LanguageType, number: Int): List } @Service @@ -53,6 +59,12 @@ class AdmissionsServiceImpl( )?.let { AdmissionsDto.of(it) } ?: throw CserealException.Csereal404("해당하는 페이지를 찾을 수 없습니다.") + @Transactional(readOnly = true) + override fun searchTopAdmission(keyword: String, language: LanguageType, number: Int) = + admissionsRepository.searchTopAdmissions(keyword, language, number).map { + AdmissionSearchResElem.of(it) + } + @Transactional override fun migrateAdmissions(requestList: List) = requestList.map { val mainType = AdmissionsMainType.fromJsonValue(it.mainType) @@ -63,7 +75,14 @@ class AdmissionsServiceImpl( mainType = mainType, postType = postType, language = language, - description = it.description!! + description = it.description!!, + searchContent = AdmissionsEntity.createSearchContent( + name = it.name, + mainType = mainType, + postType = postType, + language = language, + description = it.description + ) ) }.let { admissionsRepository.saveAll(it) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/type/AdmissionsMainType.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/type/AdmissionsMainType.kt index 7329ee64..6e3776c9 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/type/AdmissionsMainType.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/type/AdmissionsMainType.kt @@ -1,11 +1,20 @@ package com.wafflestudio.csereal.core.admissions.type import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.common.properties.LanguageType -enum class AdmissionsMainType { - UNDERGRADUATE, - GRADUATE, - INTERNATIONAL; +enum class AdmissionsMainType( + val ko: String, + val en: String +) { + UNDERGRADUATE("학부", "Undergraduate"), + GRADUATE("대학원", "Graduate"), + INTERNATIONAL("International", "International"); + + fun getLanguageValue(language: LanguageType) = when (language) { + LanguageType.KO -> this.ko + LanguageType.EN -> this.en + } fun toJsonValue() = this.name.lowercase() diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/type/AdmissionsPostType.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/type/AdmissionsPostType.kt index 2c0a87b2..571ff67b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/type/AdmissionsPostType.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/type/AdmissionsPostType.kt @@ -1,17 +1,26 @@ package com.wafflestudio.csereal.core.admissions.type import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.common.properties.LanguageType -enum class AdmissionsPostType { +enum class AdmissionsPostType( + val ko: String, + val en: String +) { // For graduate, undergraduate - EARLY_ADMISSION, - REGULAR_ADMISSION, + EARLY_ADMISSION("수시 모집", "Early Admission"), + REGULAR_ADMISSION("정시 모집", "Regular Admission"), // For international - UNDERGRADUATE, - GRADUATE, - EXCHANGE_VISITING, - SCHOLARSHIPS; + UNDERGRADUATE("Undergraduate", "Undergraduate"), + GRADUATE("Graduate", "Graduate"), + EXCHANGE_VISITING("Exchange/Visiting Program", "Exchange/Visiting Program"), + SCHOLARSHIPS("Scholarships", "Scholarships") ; + + fun getLanguageValue(language: LanguageType) = when (language) { + LanguageType.KO -> this.ko + LanguageType.EN -> this.en + } fun toJsonValue() = this.name.lowercase() diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/admissions/service/AdmissionsServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/admissions/service/AdmissionsServiceTest.kt index 24c0a505..3d8d1888 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/admissions/service/AdmissionsServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/admissions/service/AdmissionsServiceTest.kt @@ -35,7 +35,7 @@ class AdmissionsServiceTest( val req = AdmissionReqBody( name = "name", language = "ko", - description = "description" + description = "

description

" ) val mainType = AdmissionsMainType.UNDERGRADUATE val postType = AdmissionsPostType.REGULAR_ADMISSION @@ -60,6 +60,17 @@ class AdmissionsServiceTest( entity.language shouldBe LanguageType.makeStringToLanguageType(req.language) entity.description shouldBe req.description } + + Then("검색 정보가 잘 생성되어야 한다.") { + val entity = admissionsRepository.findByIdOrNull(result.id)!! + entity.searchContent shouldBe """ + ${req.name} + ${mainType.getLanguageValue(LanguageType.KO)} + ${postType.getLanguageValue(LanguageType.KO)} + description + + """.trimIndent() + } } } Given("AdmissionReqBody에 잘못된 Language가 주어졌을 때") { @@ -85,7 +96,8 @@ class AdmissionsServiceTest( mainType = AdmissionsMainType.INTERNATIONAL, postType = AdmissionsPostType.EXCHANGE_VISITING, language = LanguageType.EN, - description = "description" + description = "description", + searchContent = "ss" ).let { admissionsRepository.save(it) } From 75091b02b73acd8f4f978379d68faea3e36e2cb9 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, 18 Feb 2024 18:19:32 +0900 Subject: [PATCH 141/214] Refactor: Remove Location Entity (#186) --- .../common/utils/StringListConverter.kt | 15 ++++++ .../core/about/database/AboutEntity.kt | 9 ++-- .../core/about/database/LocationEntity.kt | 27 ----------- .../csereal/core/about/dto/AboutDto.kt | 2 +- .../csereal/core/about/dto/FacilityDto.kt | 2 +- .../core/about/service/AboutService.kt | 48 +++++++------------ 6 files changed, 40 insertions(+), 63 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/common/utils/StringListConverter.kt delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/database/LocationEntity.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/utils/StringListConverter.kt b/src/main/kotlin/com/wafflestudio/csereal/common/utils/StringListConverter.kt new file mode 100644 index 00000000..a6227833 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/common/utils/StringListConverter.kt @@ -0,0 +1,15 @@ +package com.wafflestudio.csereal.common.utils + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import jakarta.persistence.AttributeConverter +import jakarta.persistence.Converter + +@Converter +class StringListConverter : AttributeConverter, String> { + override fun convertToDatabaseColumn(p0: MutableList?): String = + ObjectMapper().writeValueAsString(p0 ?: mutableListOf()) + + override fun convertToEntityAttribute(p0: String?): MutableList = + ObjectMapper().readValue(p0 ?: "[]") +} 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 24f0ebd8..2b97fd06 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 @@ -4,6 +4,7 @@ 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.properties.LanguageType +import com.wafflestudio.csereal.common.utils.StringListConverter 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 @@ -22,8 +23,9 @@ class AboutEntity( var year: Int?, - @OneToMany(mappedBy = "about", cascade = [CascadeType.ALL], orphanRemoval = true) - val locations: MutableList = mutableListOf(), + @Column(columnDefinition = "TEXT") + @Convert(converter = StringListConverter::class) + var locations: MutableList = mutableListOf(), @OneToMany(mappedBy = "") var attachments: MutableList = mutableListOf(), @@ -42,7 +44,8 @@ class AboutEntity( language = languageType, name = aboutDto.name, description = aboutDto.description, - year = aboutDto.year + year = aboutDto.year, + locations = aboutDto.locations?.toMutableList() ?: mutableListOf() ) } } 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 deleted file mode 100644 index 38929681..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/LocationEntity.kt +++ /dev/null @@ -1,27 +0,0 @@ -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 - } - } -} 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 6160b839..3dd181b6 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 @@ -33,7 +33,7 @@ data class AboutDto( year = this.year, createdAt = this.createdAt, modifiedAt = this.modifiedAt, - locations = this.locations.map { it.name }, + locations = this.locations, imageURL = imageURL, attachments = attachmentResponses ) 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 1feceaa5..71ce1d5d 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 @@ -19,7 +19,7 @@ data class FacilityDto( language = LanguageType.makeLowercase(this.language), name = this.name!!, description = this.description, - locations = this.locations.map { it.name } + locations = this.locations ) } } 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 ccac3db5..cfc3acb6 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 @@ -49,12 +49,6 @@ class AboutServiceImpl( val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) val newAbout = AboutEntity.of(enumPostType, enumLanguageType, request) - if (request.locations != null) { - for (location in request.locations) { - LocationEntity.create(location, newAbout) - } - } - if (mainImage != null) { mainImageService.uploadMainImage(newAbout, mainImage) } @@ -277,39 +271,31 @@ class AboutServiceImpl( } @Transactional - override fun migrateFacilities(requestList: List): List { - val list = mutableListOf() - - for (request in requestList) { - val language = request.language - val name = request.name - val description = request.description - val aboutDto = AboutDto( + override fun migrateFacilities(requestList: List): List = + requestList.map { + AboutDto( id = null, - language = language, - name = name, - description = description, + language = it.language, + name = it.name, + description = it.description, year = null, createdAt = null, modifiedAt = null, - locations = null, + locations = it.locations, imageURL = null, attachments = listOf() - ) - - val languageType = LanguageType.makeStringToLanguageType(language) - val newAbout = AboutEntity.of(AboutPostType.FACILITIES, languageType, aboutDto) - - for (location in request.locations) { - LocationEntity.create(location, newAbout) + ).let { dto -> + AboutEntity.of( + AboutPostType.FACILITIES, + LanguageType.makeStringToLanguageType(it.language), + dto + ) } - - aboutRepository.save(newAbout) - - list.add(FacilityDto.of(newAbout)) + }.let { + aboutRepository.saveAll(it) + }.map { + FacilityDto.of(it) } - return list - } @Transactional override fun migrateDirections(requestList: List): List { From ccefb4edf837f14cf91d29678ffbb1d83689ff3d Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Thu, 22 Feb 2024 09:49:51 +0900 Subject: [PATCH 142/214] =?UTF-8?q?feat:=20member/research=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20=EC=9D=B4=EB=AF=B8=EC=A7=80=EC=99=80=20?= =?UTF-8?q?=EC=B2=A8=EB=B6=80=ED=8C=8C=EC=9D=BC=20=EB=A7=88=EC=9D=B4?= =?UTF-8?q?=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20(#185)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: migrateProfessorImage 추가 * feat: migrateResearchImageAndAttachments 추가 * feat: migrateStaffImage 추가 * feat: migrateLabPdf 추가 --- .../core/member/api/ProfessorController.kt | 8 ++++ .../core/member/service/ProfessorService.kt | 13 ++++++ .../core/research/api/ResearchController.kt | 25 +++++++++++ .../core/research/service/ResearchService.kt | 43 +++++++++++++++++++ 4 files changed, 89 insertions(+) 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 548f39f9..d4e0f771 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 @@ -62,4 +62,12 @@ class ProfessorController( ): ResponseEntity> { return ResponseEntity.ok(professorService.migrateProfessors(requestList)) } + + @PatchMapping("/migragteImage/{professorId}") + fun migrateProfessorImage( + @PathVariable professorId: Long, + @RequestPart("mainImage") mainImage: MultipartFile + ): ResponseEntity { + return ResponseEntity.ok(professorService.migrateProfessorImage(professorId, mainImage)) + } } 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 210c8f58..ebc22b35 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 @@ -28,6 +28,7 @@ interface ProfessorService { ): ProfessorDto fun deleteProfessor(professorId: Long) fun migrateProfessors(requestList: List): List + fun migrateProfessorImage(professorId: Long, mainImage: MultipartFile): ProfessorDto } @Service @@ -225,4 +226,16 @@ class ProfessorServiceImpl( } return list } + + @Transactional + override fun migrateProfessorImage(professorId: Long, mainImage: MultipartFile): ProfessorDto { + val professor = professorRepository.findByIdOrNull(professorId) + ?: throw CserealException.Csereal404("해당 교수님을 찾을 수 없습니다. professorId: $professorId") + + mainImageService.uploadMainImage(professor, mainImage) + + val imageURL = mainImageService.createImageURL(professor.mainImage) + + return ProfessorDto.of(professor, imageURL) + } } 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 d6c55a3e..7ee21f75 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 @@ -102,6 +102,31 @@ class ResearchController( return ResponseEntity.ok(researchService.migrateLabs(requestList)) } + @PatchMapping("/migrateImageAndAttachments/{researchId}") + fun migrateResearchDetailImageAndAttachments( + @PathVariable researchId: Long, + @RequestPart("mainImage") mainImage: MultipartFile?, + @RequestPart("attachments") attachments: List? + ): ResponseEntity { + return ResponseEntity.ok( + researchService.migrateResearchDetailImageAndAttachments( + researchId, + mainImage, + attachments + ) + ) + } + + @PatchMapping("/lab/migratePdf/{labId}") + fun migrateLabPdf( + @PathVariable labId: Long, + @RequestPart("pdf") pdf: MultipartFile? + ): ResponseEntity { + return ResponseEntity.ok( + researchService.migrateLabPdf(labId, pdf) + ) + } + @GetMapping("/search/top") fun searchTop( @RequestParam(required = true) keyword: String, 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 48b92497..2dc97539 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 @@ -35,6 +35,12 @@ interface ResearchService { fun updateLab(labId: Long, request: LabUpdateRequest, pdf: MultipartFile?): LabDto fun migrateResearchDetail(requestList: List): List fun migrateLabs(requestList: List): List + fun migrateResearchDetailImageAndAttachments( + researchId: Long, + mainImage: MultipartFile?, + attachments: List? + ): ResearchDto + fun migrateLabPdf(labId: Long, pdf: MultipartFile?): LabDto } @Service @@ -319,4 +325,41 @@ class ResearchServiceImpl( } return list } + + @Transactional + override fun migrateResearchDetailImageAndAttachments( + researchId: Long, + mainImage: MultipartFile?, + attachments: List? + ): ResearchDto { + val researchDetail = researchRepository.findByIdOrNull(researchId) + ?: throw CserealException.Csereal404("해당 연구내용을 찾을 수 없습니다.") + + if (mainImage != null) { + mainImageService.uploadMainImage(researchDetail, mainImage) + } + + if (attachments != null) { + attachmentService.uploadAllAttachments(researchDetail, attachments) + } + + val imageURL = mainImageService.createImageURL(researchDetail.mainImage) + val attachmentResponses = attachmentService.createAttachmentResponses(researchDetail.attachments) + + return ResearchDto.of(researchDetail, imageURL, attachmentResponses) + } + + @Transactional + override fun migrateLabPdf(labId: Long, pdf: MultipartFile?): LabDto { + val lab = labRepository.findByIdOrNull(labId) + ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다.") + + var pdfURL = "" + if (pdf != null) { + val attachmentDto = attachmentService.uploadAttachmentInLabEntity(lab, pdf) + pdfURL = "${endpointProperties.backend}/v1/file/${attachmentDto.filename}" + } + + return LabDto.of(lab, pdfURL) + } } From cb93db716928bcb2328dda24021872ad21e66de5 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Thu, 22 Feb 2024 12:06:19 +0900 Subject: [PATCH 143/214] =?UTF-8?q?feat:=20about=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=EB=9E=91=20=EC=B2=A8=EB=B6=80?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=B6=94=EA=B0=80=20(#187)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: migrateProfessorImage 추가 * feat: migrateResearchImageAndAttachments 추가 * feat: migrateStaffImage 추가 * feat: migrateLabPdf 추가 * feat: about에서 이미지랑 첨부파일 추가 --- .../csereal/core/about/api/AboutController.kt | 11 ++++++++ .../core/about/service/AboutService.kt | 25 +++++++++++++++++++ 2 files changed, 36 insertions(+) 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 87468574..61043e39 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 @@ -102,4 +102,15 @@ class AboutController( ): ResponseEntity> { return ResponseEntity.ok(aboutService.migrateDirections(requestList)) } + + @PatchMapping("/migrateImage/{aboutId}") + fun migrateAboutImageAndAttachment( + @PathVariable aboutId: Long, + @RequestPart("mainImage") mainImage: MultipartFile?, + @RequestPart("attachments") attachments: List? + ): ResponseEntity { + return ResponseEntity.ok( + aboutService.migrateAboutImageAndAttachments(aboutId, mainImage, 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 cfc3acb6..c63b6c3a 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,6 +6,7 @@ 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.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import org.springframework.web.multipart.MultipartFile @@ -28,6 +29,11 @@ interface AboutService { fun migrateStudentClubs(requestList: List): List fun migrateFacilities(requestList: List): List fun migrateDirections(requestList: List): List + fun migrateAboutImageAndAttachments( + aboutId: Long, + mainImage: MultipartFile?, + attachments: List? + ): AboutDto } @Service @@ -329,6 +335,25 @@ class AboutServiceImpl( return list } + @Transactional + override fun migrateAboutImageAndAttachments( + aboutId: Long, + mainImage: MultipartFile?, + attachments: List? + ): AboutDto { + val about = aboutRepository.findByIdOrNull(aboutId) + ?: throw CserealException.Csereal404("해당 소개는 존재하지 않습니다.") + + if (mainImage != null) { + mainImageService.uploadMainImage(about, mainImage) + } + + val imageURL = mainImageService.createImageURL(about.mainImage) + val attachmentResponses = attachmentService.createAttachmentResponses(about.attachments) + + return AboutDto.of(about, imageURL, attachmentResponses) + } + private fun makeStringToEnum(postType: String): AboutPostType { try { val upperPostType = postType.replace("-", "_").uppercase() From 997d40858a7f884ed46123d100cf49322bb94af0 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Sat, 24 Feb 2024 13:14:23 +0900 Subject: [PATCH 144/214] =?UTF-8?q?feat:=20dev=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20(#188)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * dev서버 로그인 바이패스 * style: ktlint * fix: mock auth config 분리 * fix: uri 수정 --------- Co-authored-by: 우혁준 (HyukJoon Woo) --- .../csereal/common/aop/SecurityAspect.kt | 6 +++ .../common/mockauth/CustomPrincipal.kt | 10 ++++ .../common/mockauth/DevAuthController.kt | 49 +++++++++++++++++++ .../mockauth/DevAuthenticationProvider.kt | 26 ++++++++++ .../csereal/common/mockauth/MockAuthConfig.kt | 25 ++++++++++ 5 files changed, 116 insertions(+) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/common/mockauth/CustomPrincipal.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/common/mockauth/DevAuthController.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/common/mockauth/DevAuthenticationProvider.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/common/mockauth/MockAuthConfig.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 9eb21d03..1b56827a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/aop/SecurityAspect.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/aop/SecurityAspect.kt @@ -1,6 +1,7 @@ package com.wafflestudio.csereal.common.aop import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.common.mockauth.CustomPrincipal import com.wafflestudio.csereal.core.user.database.Role import com.wafflestudio.csereal.core.user.database.UserEntity import com.wafflestudio.csereal.core.user.database.UserRepository @@ -38,6 +39,11 @@ class SecurityAspect(private val userRepository: UserRepository) { val authentication = SecurityContextHolder.getContext().authentication val principal = authentication.principal + // for dev Mock User + if (principal is CustomPrincipal) { + return principal.userEntity + } + if (principal !is OidcUser) { throw CserealException.Csereal401("로그인이 필요합니다.") } diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/mockauth/CustomPrincipal.kt b/src/main/kotlin/com/wafflestudio/csereal/common/mockauth/CustomPrincipal.kt new file mode 100644 index 00000000..16eec3c1 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/common/mockauth/CustomPrincipal.kt @@ -0,0 +1,10 @@ +package com.wafflestudio.csereal.common.mockauth + +import com.wafflestudio.csereal.core.user.database.UserEntity +import java.security.Principal + +data class CustomPrincipal(val userEntity: UserEntity) : Principal { + override fun getName(): String { + return userEntity.username + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/mockauth/DevAuthController.kt b/src/main/kotlin/com/wafflestudio/csereal/common/mockauth/DevAuthController.kt new file mode 100644 index 00000000..19110220 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/common/mockauth/DevAuthController.kt @@ -0,0 +1,49 @@ +package com.wafflestudio.csereal.common.mockauth + +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 jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import org.springframework.http.ResponseEntity +import org.springframework.security.authentication.AuthenticationManager +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.core.authority.SimpleGrantedAuthority +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.security.web.context.SecurityContextRepository +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +//TODO: 정식 릴리즈 후에는 dev 서버에서만 가능하게 +@RestController +@RequestMapping("/api") +class DevAuthController( + private val authenticationManager: AuthenticationManager, + private val userRepository: UserRepository, + private val securityContextRepository: SecurityContextRepository +) { + + @GetMapping("/mock-login") + fun mockLogin(request: HttpServletRequest, response: HttpServletResponse): ResponseEntity { + val mockUser = userRepository.findByUsername("devUser") + ?: userRepository.save(UserEntity("devUser", "Mock", "mock@abc.com", "0000-00000", Role.ROLE_STAFF)) + val customPrincipal = CustomPrincipal(mockUser) + val authenticationToken = UsernamePasswordAuthenticationToken( + customPrincipal, + null, + listOf( + SimpleGrantedAuthority("ROLE_STAFF") + ) + ) + + val authentication = authenticationManager.authenticate(authenticationToken) + SecurityContextHolder.getContext().authentication = authentication + + securityContextRepository.saveContext(SecurityContextHolder.getContext(), request, response) + + request.getSession(true) + + return ResponseEntity.ok().body("Mock user authenticated") + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/mockauth/DevAuthenticationProvider.kt b/src/main/kotlin/com/wafflestudio/csereal/common/mockauth/DevAuthenticationProvider.kt new file mode 100644 index 00000000..cfdf0761 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/common/mockauth/DevAuthenticationProvider.kt @@ -0,0 +1,26 @@ +package com.wafflestudio.csereal.common.mockauth + +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.user.database.UserRepository +import org.springframework.security.authentication.AuthenticationProvider +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.core.Authentication +import org.springframework.security.core.authority.SimpleGrantedAuthority +import org.springframework.stereotype.Component + +@Component +class DevAuthenticationProvider(private val userRepository: UserRepository) : AuthenticationProvider { + + override fun authenticate(authentication: Authentication): Authentication? { + val username = authentication.name + val userEntity = + userRepository.findByUsername(username) ?: throw CserealException.Csereal404("Mock User not found") + + val customPrincipal = CustomPrincipal(userEntity) + return UsernamePasswordAuthenticationToken(customPrincipal, null, listOf(SimpleGrantedAuthority("ROLE_STAFF"))) + } + + override fun supports(authentication: Class<*>): Boolean { + return UsernamePasswordAuthenticationToken::class.java.isAssignableFrom(authentication) + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/mockauth/MockAuthConfig.kt b/src/main/kotlin/com/wafflestudio/csereal/common/mockauth/MockAuthConfig.kt new file mode 100644 index 00000000..5f535263 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/common/mockauth/MockAuthConfig.kt @@ -0,0 +1,25 @@ +package com.wafflestudio.csereal.common.mockauth + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.security.authentication.AuthenticationManager +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.web.context.HttpSessionSecurityContextRepository +import org.springframework.security.web.context.SecurityContextRepository + +@Configuration +class MockAuthConfig( + private val devAuthenticationProvider: DevAuthenticationProvider +) { + @Bean + fun authenticationManager(http: HttpSecurity): AuthenticationManager { + http.authenticationProvider(devAuthenticationProvider) + return http.getSharedObject(AuthenticationManagerBuilder::class.java).build() + } + + @Bean + fun securityContextRepository(): SecurityContextRepository { + return HttpSessionSecurityContextRepository() + } +} From 8831bb015fd30873949c406e9cbf71d5295de219 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Tue, 27 Feb 2024 10:10:20 +0900 Subject: [PATCH 145/214] =?UTF-8?q?fix:=20professor=20language=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#189)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: professor language 추가 * feat: professor 테스트 추가 * feat: professor language 다른 테스트에도 추가 * feat: 테스트 알맞게 수정 --- .../csereal/core/member/database/ProfessorEntity.kt | 6 +++++- .../wafflestudio/csereal/core/member/dto/ProfessorDto.kt | 3 +++ .../csereal/core/member/service/ProfessorService.kt | 7 +++++-- .../csereal/core/member/service/ProfessorServiceTest.kt | 2 ++ .../core/reseach/service/ResearchSearchServiceTest.kt | 3 +++ .../csereal/core/reseach/service/ResearchServiceTest.kt | 6 ++++++ 6 files changed, 24 insertions(+), 3 deletions(-) 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 b1c80c43..00b46019 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 @@ -2,6 +2,7 @@ package com.wafflestudio.csereal.core.member.database import com.wafflestudio.csereal.common.config.BaseTimeEntity import com.wafflestudio.csereal.common.controller.MainImageContentEntityType +import com.wafflestudio.csereal.common.properties.LanguageType 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 @@ -10,6 +11,8 @@ import java.time.LocalDate @Entity(name = "professor") class ProfessorEntity( + @Enumerated(EnumType.STRING) + var language: LanguageType, var name: String, @@ -49,8 +52,9 @@ class ProfessorEntity( override fun bringMainImage(): MainImageEntity? = mainImage companion object { - fun of(professorDto: ProfessorDto): ProfessorEntity { + fun of(languageType: LanguageType, professorDto: ProfessorDto): ProfessorEntity { return ProfessorEntity( + language = languageType, name = professorDto.name, status = professorDto.status, academicRank = professorDto.academicRank, 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 90bf7f47..072db7e1 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 @@ -1,6 +1,7 @@ package com.wafflestudio.csereal.core.member.dto import com.fasterxml.jackson.annotation.JsonInclude +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.member.database.ProfessorEntity import com.wafflestudio.csereal.core.member.database.ProfessorStatus import java.time.LocalDate @@ -8,6 +9,7 @@ import java.time.LocalDate data class ProfessorDto( @JsonInclude(JsonInclude.Include.NON_NULL) var id: Long? = null, + val language: String, val name: String, val status: ProfessorStatus, val academicRank: String, @@ -31,6 +33,7 @@ data class ProfessorDto( fun of(professorEntity: ProfessorEntity, imageURL: String?): ProfessorDto { return ProfessorDto( id = professorEntity.id, + language = LanguageType.makeLowercase(professorEntity.language), name = professorEntity.name, status = professorEntity.status, academicRank = professorEntity.academicRank, 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 ebc22b35..a0eb66fa 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 @@ -1,6 +1,7 @@ package com.wafflestudio.csereal.core.member.service import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.member.database.* import com.wafflestudio.csereal.core.member.dto.ProfessorDto import com.wafflestudio.csereal.core.member.dto.ProfessorPageDto @@ -40,7 +41,8 @@ class ProfessorServiceImpl( private val applicationEventPublisher: ApplicationEventPublisher ) : ProfessorService { override fun createProfessor(createProfessorRequest: ProfessorDto, mainImage: MultipartFile?): ProfessorDto { - val professor = ProfessorEntity.of(createProfessorRequest) + val enumLanguageType = LanguageType.makeStringToLanguageType(createProfessorRequest.language) + val professor = ProfessorEntity.of(enumLanguageType, createProfessorRequest) if (createProfessorRequest.labId != null) { val lab = labRepository.findByIdOrNull(createProfessorRequest.labId) ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다. LabId: ${createProfessorRequest.labId}") @@ -199,7 +201,8 @@ class ProfessorServiceImpl( val list = mutableListOf() for (request in requestList) { - val professor = ProfessorEntity.of(request) + val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) + val professor = ProfessorEntity.of(enumLanguageType, request) if (request.labName != null) { val lab = labRepository.findByName(request.labName) ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다. LabName: ${request.labName}") 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 b3a8b144..bbb1e43c 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 @@ -54,6 +54,7 @@ class ProfessorServiceTest( labEntity = labRepository.save(labEntity) val professorDto = ProfessorDto( + language = "ko", name = "name", email = "email", status = ProfessorStatus.ACTIVE, @@ -162,6 +163,7 @@ class ProfessorServiceTest( val createdProfessorDto = professorService.createProfessor( ProfessorDto( + language = "ko", name = "name", email = "email", status = ProfessorStatus.ACTIVE, diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchSearchServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchSearchServiceTest.kt index 032743ba..031ec07a 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchSearchServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchSearchServiceTest.kt @@ -47,6 +47,7 @@ class ResearchSearchServiceTest( // Save professors val professor1Dto = professorService.createProfessor( createProfessorRequest = ProfessorDto( + language = "ko", name = "professor1", email = null, status = ProfessorStatus.ACTIVE, @@ -67,6 +68,7 @@ class ResearchSearchServiceTest( ) val professor2Dto = professorService.createProfessor( createProfessorRequest = ProfessorDto( + language = "ko", name = "professor2", email = null, status = ProfessorStatus.ACTIVE, @@ -160,6 +162,7 @@ class ResearchSearchServiceTest( When("professor가 추가된다면") { val process3CreatedDto = professorService.createProfessor( createProfessorRequest = ProfessorDto( + language = "ko", name = "newProfessor", email = "email", status = ProfessorStatus.ACTIVE, 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 468372f1..df6eb464 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 @@ -1,5 +1,6 @@ package com.wafflestudio.csereal.core.reseach.service +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.member.database.ProfessorEntity import com.wafflestudio.csereal.core.member.database.ProfessorRepository import com.wafflestudio.csereal.core.member.database.ProfessorStatus @@ -156,6 +157,7 @@ class ResearchServiceTest( // Save professors val professor1 = professorRepository.save( ProfessorEntity( + language = LanguageType.KO, name = "professor1", status = ProfessorStatus.ACTIVE, academicRank = "professor", @@ -170,6 +172,7 @@ class ResearchServiceTest( ) val professor2 = professorRepository.save( ProfessorEntity( + language = LanguageType.KO, name = "professor2", status = ProfessorStatus.ACTIVE, academicRank = "professor", @@ -258,6 +261,7 @@ class ResearchServiceTest( // Save professors val professor1 = professorRepository.save( ProfessorEntity( + language = LanguageType.KO, name = "professor1", status = ProfessorStatus.ACTIVE, academicRank = "professor", @@ -272,6 +276,7 @@ class ResearchServiceTest( ) val professor2 = professorRepository.save( ProfessorEntity( + language = LanguageType.KO, name = "professor2", status = ProfessorStatus.ACTIVE, academicRank = "professor", @@ -318,6 +323,7 @@ class ResearchServiceTest( When("pdf를 제외하고 Lab을 수정한다면") { val professor3 = professorRepository.save( ProfessorEntity( + language = LanguageType.KO, name = "professor3", status = ProfessorStatus.ACTIVE, academicRank = "professor", From ac20a5632c7c9ebc0de3f5996c8df051389f8991 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, 28 Feb 2024 17:17:37 +0900 Subject: [PATCH 146/214] =?UTF-8?q?Feature:=20About=20=EA=B2=80=EC=83=89?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20(#192)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat: Change to create search content. * Feat: Add search query method. * Feat: Add create, migrate to create searchContent, and add search method. * Feat: add search api * COMMENT: add fix comment for aboutservicetest. * Refactor: Move dtos to ee directory. * Feat: Add validation for number, pagesize, pagenum --- .../csereal/core/about/api/AboutController.kt | 34 +++++- .../about/api/res/AboutSearchElementDto.kt | 37 ++++++ .../core/about/api/res/AboutSearchResBody.kt | 6 + .../core/about/database/AboutEntity.kt | 51 ++++++++- .../core/about/database/AboutRepository.kt | 63 ++++++++++- .../core/about/service/AboutService.kt | 88 +++++++++++++-- .../csereal/core/about/AboutServiceTest.kt | 106 ++++++++++++++++++ 7 files changed, 369 insertions(+), 16 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/api/res/AboutSearchElementDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/api/res/AboutSearchResBody.kt create mode 100644 src/test/kotlin/com/wafflestudio/csereal/core/about/AboutServiceTest.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 61043e39..f5164b1d 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,11 +1,13 @@ package com.wafflestudio.csereal.core.about.api -import com.wafflestudio.csereal.common.aop.AuthenticatedStaff +import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.core.about.api.res.AboutSearchResBody import com.wafflestudio.csereal.core.about.dto.* 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 jakarta.validation.constraints.Positive import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile @@ -20,7 +22,7 @@ class AboutController( // postType: directions / name -> by-public-transit, by-car, from-far-away // Todo: 학부장 인사말(greetings) signature - @AuthenticatedStaff +// @AuthenticatedStaff @PostMapping("/{postType}") fun createAbout( @PathVariable postType: String, @@ -68,6 +70,34 @@ class AboutController( return ResponseEntity.ok(aboutService.readFutureCareers()) } + @GetMapping("/search/top") + fun searchTopAbout( + @RequestParam(required = true) keyword: String, + @RequestParam(required = true) @Valid @Positive number: Int, + @RequestParam(required = true, defaultValue = "ko") language: String, + @RequestParam(required = false, defaultValue = "30") @Valid @Positive amount: Int + ): AboutSearchResBody = aboutService.searchTopAbout( + keyword, + LanguageType.makeStringToLanguageType(language), + number, + amount + ) + + @GetMapping("/search") + fun searchPageAbout( + @RequestParam(required = true) keyword: String, + @RequestParam(required = true) @Valid @Positive pageNum: Int, + @RequestParam(required = true) @Valid @Positive pageSize: Int, + @RequestParam(required = true, defaultValue = "ko") language: String, + @RequestParam(required = false, defaultValue = "30") @Valid @Positive amount: Int + ): AboutSearchResBody = aboutService.searchPageAbout( + keyword, + LanguageType.makeStringToLanguageType(language), + pageSize, + pageNum, + amount + ) + @PostMapping("/migrate") fun migrateAbout( @RequestBody requestList: List diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/res/AboutSearchElementDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/res/AboutSearchElementDto.kt new file mode 100644 index 00000000..0e6a09ff --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/res/AboutSearchElementDto.kt @@ -0,0 +1,37 @@ +package com.wafflestudio.csereal.core.about.api.res + +import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.utils.cleanTextFromHtml +import com.wafflestudio.csereal.common.utils.substringAroundKeyword +import com.wafflestudio.csereal.core.about.database.AboutEntity +import com.wafflestudio.csereal.core.about.database.AboutPostType + +data class AboutSearchElementDto private constructor( + val id: Long, + val language: String, + val aboutPostType: AboutPostType, + val name: String?, + val partialDescription: String, + val boldStartIndex: Int, + val boldEndIndex: Int +) { + companion object { + fun of(about: AboutEntity, keyword: String, amount: Int) = about.run { + val (boldStartIdx, partialDescription) = substringAroundKeyword( + keyword, + cleanTextFromHtml(description), + amount + ) + + AboutSearchElementDto( + id = id, + language = LanguageType.makeLowercase(language), + aboutPostType = postType, + name = name, + partialDescription = partialDescription.replace('\n', ' '), + boldStartIndex = boldStartIdx ?: 0, + boldEndIndex = boldStartIdx?.plus(keyword.length) ?: 0 + ) + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/res/AboutSearchResBody.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/res/AboutSearchResBody.kt new file mode 100644 index 00000000..5c48e99b --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/res/AboutSearchResBody.kt @@ -0,0 +1,6 @@ +package com.wafflestudio.csereal.core.about.api.res + +data class AboutSearchResBody( + val total: Long, + val results: List +) 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 2b97fd06..4a334658 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 @@ -5,6 +5,7 @@ import com.wafflestudio.csereal.common.controller.AttachmentContentEntityType import com.wafflestudio.csereal.common.controller.MainImageContentEntityType import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.common.utils.StringListConverter +import com.wafflestudio.csereal.common.utils.cleanTextFromHtml 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 @@ -31,22 +32,66 @@ class AboutEntity( var attachments: MutableList = mutableListOf(), @OneToOne - var mainImage: MainImageEntity? = null + var mainImage: MainImageEntity? = null, + + @Column(columnDefinition = "TEXT") + var searchContent: String ) : BaseTimeEntity(), MainImageContentEntityType, AttachmentContentEntityType { override fun bringMainImage(): MainImageEntity? = mainImage override fun bringAttachments(): List = attachments companion object { - fun of(postType: AboutPostType, languageType: LanguageType, aboutDto: AboutDto): AboutEntity { + fun of( + postType: AboutPostType, + languageType: LanguageType, + aboutDto: AboutDto + ): AboutEntity { return AboutEntity( postType = postType, language = languageType, name = aboutDto.name, description = aboutDto.description, year = aboutDto.year, - locations = aboutDto.locations?.toMutableList() ?: mutableListOf() + locations = aboutDto.locations?.toMutableList() ?: mutableListOf(), + searchContent = "" ) } + + fun createContent(name: String?, description: String, locations: List) = StringBuilder().apply { + name?.let { appendLine(it) } + appendLine(cleanTextFromHtml(description)) + locations.forEach { + appendLine(it) + } + }.toString() + + fun createContent( + name: String?, + description: String, + statNames: List, + companyNames: List + ): String { + return StringBuilder().apply { + name?.let { appendLine(it) } + appendLine(cleanTextFromHtml(description)) + statNames.forEach { + appendLine(it) + } + companyNames.forEach { + appendLine(it) + } + }.toString() + } + } + + fun syncSearchContent() { + assert(postType != AboutPostType.FUTURE_CAREERS) + searchContent = createContent(name, description, locations) + } + + fun syncSearchContent(statNames: List, companyNames: List) { + assert(postType == AboutPostType.FUTURE_CAREERS) + searchContent = createContent(name, description, statNames, companyNames) } } 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 b98a26fd..727cbb09 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 @@ -1,9 +1,15 @@ package com.wafflestudio.csereal.core.about.database +import com.querydsl.jpa.impl.JPAQuery +import com.querydsl.jpa.impl.JPAQueryFactory import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.repository.CommonRepository +import com.wafflestudio.csereal.common.utils.exchangePageNum +import com.wafflestudio.csereal.core.about.database.QAboutEntity.aboutEntity import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository -interface AboutRepository : JpaRepository { +interface AboutRepository : JpaRepository, AboutCustomRepository { fun findAllByLanguageAndPostTypeOrderByName( languageType: LanguageType, postType: AboutPostType @@ -13,3 +19,58 @@ interface AboutRepository : JpaRepository { postType: AboutPostType ): AboutEntity } + +interface AboutCustomRepository { + fun searchAbouts( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int + ): Pair, Long> +} + +@Repository +class AboutCustomRepositoryImpl( + private val queryFactory: JPAQueryFactory, + private val commonRepository: CommonRepository +) : AboutCustomRepository { + override fun searchAbouts( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int + ): Pair, Long> { + val total = searchCount(keyword, language) + val validPageNum = exchangePageNum(pageSize, pageNum, total) + val validOffset = ( + if (validPageNum >= 1) validPageNum - 1 else 0 + ) * pageSize.toLong() + + val queryResult = searchQueryExpr(keyword, language) + .offset(validOffset) + .limit(pageSize.toLong()) + .fetch() + + return queryResult to total + } + + fun searchCount(keyword: String, language: LanguageType): Long { + return searchQueryExpr(keyword, language) + .select(aboutEntity.countDistinct()) + .fetchOne()!! + } + + fun searchQueryExpr(keyword: String, language: LanguageType): JPAQuery { + val matchExpression = commonRepository.searchFullSingleTextTemplate( + keyword, + aboutEntity.searchContent + ) + + return queryFactory.select(aboutEntity) + .from(aboutEntity) + .where( + matchExpression.gt(0.0), + aboutEntity.language.eq(language) + ) + } +} 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 c63b6c3a..3fe5367f 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 @@ -2,6 +2,8 @@ package com.wafflestudio.csereal.core.about.service import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.core.about.api.res.AboutSearchElementDto +import com.wafflestudio.csereal.core.about.api.res.AboutSearchResBody import com.wafflestudio.csereal.core.about.database.* import com.wafflestudio.csereal.core.about.dto.* import com.wafflestudio.csereal.core.resource.attachment.service.AttachmentService @@ -24,6 +26,17 @@ interface AboutService { fun readAllFacilities(language: String): List fun readAllDirections(language: String): List fun readFutureCareers(): FutureCareersPage + + fun searchTopAbout(keyword: String, language: LanguageType, number: Int, amount: Int): AboutSearchResBody + + fun searchPageAbout( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int, + amount: Int + ): AboutSearchResBody + fun migrateAbout(requestList: List): List fun migrateFutureCareers(request: FutureCareersRequest): FutureCareersPage fun migrateStudentClubs(requestList: List): List @@ -53,7 +66,7 @@ class AboutServiceImpl( ): AboutDto { val enumPostType = makeStringToEnum(postType) val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) - val newAbout = AboutEntity.of(enumPostType, enumLanguageType, request) + var newAbout = AboutEntity.of(enumPostType, enumLanguageType, request) if (mainImage != null) { mainImageService.uploadMainImage(newAbout, mainImage) @@ -62,7 +75,10 @@ class AboutServiceImpl( if (attachments != null) { attachmentService.uploadAllAttachments(newAbout, attachments) } - aboutRepository.save(newAbout) + + syncSearchOfAbout(newAbout) + + newAbout = aboutRepository.save(newAbout) val imageURL = mainImageService.createImageURL(newAbout.mainImage) val attachmentResponses = attachmentService.createAttachmentResponses(newAbout.attachments) @@ -154,6 +170,51 @@ class AboutServiceImpl( return FutureCareersPage(description, statList, companyList) } + @Transactional + fun syncSearchOfAbout(about: AboutEntity) { + if (about.postType == AboutPostType.FUTURE_CAREERS) { + about.syncSearchContent( + statRepository.findAll().map { it.name }, + companyRepository.findAll().map { it.name } + ) + } else { + about.syncSearchContent() + } + } + + @Transactional(readOnly = true) + override fun searchTopAbout( + keyword: String, + language: LanguageType, + number: Int, + amount: Int + ): AboutSearchResBody { + val (searchEntities, searchCnt) = aboutRepository.searchAbouts(keyword, language, number, 1) + return AboutSearchResBody( + searchCnt, + searchEntities.map { + AboutSearchElementDto.of(it, keyword, amount) + } + ) + } + + @Transactional(readOnly = true) + override fun searchPageAbout( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int, + amount: Int + ): AboutSearchResBody { + val (searchEntities, searchCnt) = aboutRepository.searchAbouts(keyword, language, pageSize, pageNum) + return AboutSearchResBody( + searchCnt, + searchEntities.map { + AboutSearchElementDto.of(it, keyword, amount) + } + ) + } + @Transactional override fun migrateAbout(requestList: List): List { val list = mutableListOf() @@ -177,9 +238,10 @@ class AboutServiceImpl( ) val languageType = LanguageType.makeStringToLanguageType(language) - val newAbout = AboutEntity.of(enumPostType, languageType, aboutDto) + var newAbout = AboutEntity.of(enumPostType, languageType, aboutDto) + syncSearchOfAbout(newAbout) - aboutRepository.save(newAbout) + newAbout = aboutRepository.save(newAbout) list.add(AboutDto.of(newAbout, null, listOf())) } @@ -207,8 +269,7 @@ class AboutServiceImpl( ) val languageType = LanguageType.makeStringToLanguageType(language) - val newAbout = AboutEntity.of(AboutPostType.FUTURE_CAREERS, languageType, aboutDto) - aboutRepository.save(newAbout) + var newAbout = AboutEntity.of(AboutPostType.FUTURE_CAREERS, languageType, aboutDto) for (stat in request.stat) { val year = stat.year @@ -243,6 +304,9 @@ class AboutServiceImpl( companyList.add(company) } + syncSearchOfAbout(newAbout) + newAbout = aboutRepository.save(newAbout) + return FutureCareersPage(description, statList.toList(), companyList.toList()) } @@ -267,9 +331,10 @@ class AboutServiceImpl( attachments = listOf() ) val languageType = LanguageType.makeStringToLanguageType(language) - val newAbout = AboutEntity.of(AboutPostType.STUDENT_CLUBS, languageType, aboutDto) + var newAbout = AboutEntity.of(AboutPostType.STUDENT_CLUBS, languageType, aboutDto) - aboutRepository.save(newAbout) + syncSearchOfAbout(newAbout) + newAbout = aboutRepository.save(newAbout) list.add(StudentClubDto.of(newAbout)) } @@ -296,6 +361,8 @@ class AboutServiceImpl( LanguageType.makeStringToLanguageType(it.language), dto ) + }.also { + syncSearchOfAbout(it) } }.let { aboutRepository.saveAll(it) @@ -326,9 +393,10 @@ class AboutServiceImpl( ) val languageType = LanguageType.makeStringToLanguageType(language) - val newAbout = AboutEntity.of(AboutPostType.DIRECTIONS, languageType, aboutDto) + var newAbout = AboutEntity.of(AboutPostType.DIRECTIONS, languageType, aboutDto) + syncSearchOfAbout(newAbout) - aboutRepository.save(newAbout) + newAbout = aboutRepository.save(newAbout) list.add(DirectionDto.of(newAbout)) } diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/about/AboutServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/about/AboutServiceTest.kt new file mode 100644 index 00000000..cb6863f4 --- /dev/null +++ b/src/test/kotlin/com/wafflestudio/csereal/core/about/AboutServiceTest.kt @@ -0,0 +1,106 @@ +package com.wafflestudio.csereal.core.about + +// FIXME: org.springframework.dao.InvalidDataAccessResourceUsageException +//import com.querydsl.jpa.impl.JPAQueryFactory +//import com.wafflestudio.csereal.core.about.database.* +//import com.wafflestudio.csereal.core.about.dto.AboutDto +//import com.wafflestudio.csereal.core.about.service.AboutService +//import com.wafflestudio.csereal.core.about.service.AboutServiceImpl +//import com.wafflestudio.csereal.global.config.TestConfig +//import io.kotest.core.spec.style.BehaviorSpec +//import io.kotest.extensions.spring.SpringExtension +//import io.kotest.extensions.spring.SpringTestExtension +//import io.kotest.extensions.spring.SpringTestLifecycleMode +//import io.kotest.matchers.shouldBe +//import io.kotest.matchers.shouldNotBe +//import io.mockk.impl.annotations.MockK +//import io.swagger.v3.oas.annotations.extensions.Extension +//import jakarta.persistence.EntityManager +//import org.junit.jupiter.api.extension.ExtendWith +//import org.springframework.boot.test.context.SpringBootTest +//import org.springframework.context.annotation.Import +//import org.springframework.context.annotation.Profile +//import org.springframework.data.repository.findByIdOrNull +//import org.springframework.test.context.ActiveProfiles +//import org.springframework.transaction.annotation.Transactional +// +////@SpringBootTest(classes = [ +//// AboutCustomRepositoryImpl::class, +//// AboutServiceImpl::class, +////]) +////@Import(TestConfig::class) +//@SpringBootTest +//@Transactional +//class AboutServiceTest( +// private val aboutService: AboutService, +// private val aboutRepository: AboutRepository, +//): BehaviorSpec ({ +// extensions(SpringTestExtension(SpringTestLifecycleMode.Root)) +// +// Given("이미지, 첨부 파일 없는 About 정보가 주어지는 경우") { +// val postTypeStr = "facilities" +// val aboutDto = AboutDto( +// name = "test name", +// description = "

test description

", +// language = "ko", +// locations = listOf("testLoc1", "testLoc2"), +// attachments = null, +// imageURL = null, +// createdAt = null, +// modifiedAt = null, +// year = null, +// ) +// +// When("About을 생성한다면") { +// val createdAboutDto = aboutService.createAbout( +// postTypeStr, +// aboutDto, +// null, +// null +// ) +// +// Then("반환된 Dto가 주어진 정보와 일치하여야 한다.") { +// createdAboutDto.id shouldNotBe null +// createdAboutDto.name shouldBe aboutDto.name +// createdAboutDto.description shouldBe aboutDto.description +// createdAboutDto.language shouldBe aboutDto.language +// createdAboutDto.locations shouldBe aboutDto.locations +// } +// +// Then("About이 DB에 저장되어야 한다.") { +// val about = createdAboutDto.id?.let { +// aboutRepository.findByIdOrNull(it) +// } +// +// about shouldNotBe null +// about!!.name shouldBe aboutDto.name +// about.description shouldBe aboutDto.description +// about.language shouldBe aboutDto.language +// about.locations shouldBe aboutDto.locations +// } +// +// Then("About의 postType이 facilities여야 한다.") { +// val about = createdAboutDto.id?.let { +// aboutRepository.findByIdOrNull(it) +// } +// +// about shouldNotBe null +// about!!.postType shouldBe AboutPostType.FACILITIES +// } +// +// Then("About의 searchContent가 생성되어야 한다.") { +// val about = createdAboutDto.id?.let { +// aboutRepository.findByIdOrNull(it) +// } +// +// about shouldNotBe null +// about!!.searchContent shouldBe """ +// test name +// test description +// testLoc1 +// testLoc2 +// """.trimIndent() +// } +// } +// } +//}) From e4ca67118b7835b37ea86e6b6ee5e6555add7abd Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Thu, 29 Feb 2024 15:57:12 +0900 Subject: [PATCH 147/214] =?UTF-8?q?feat:=20research=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=EC=97=90=20language=20=EC=B6=94=EA=B0=80=20(#194)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: professor language 추가 * feat: professor 테스트 추가 * feat: professor language 다른 테스트에도 추가 * feat: 테스트 알맞게 수정 * feat: researchDetail에 language 추가 * feat: lab에 language 추가 * fix: about, admission migrate search todo 추가 --- .../csereal/core/about/service/AboutService.kt | 5 +++++ .../core/admissions/service/AdmissionsService.kt | 1 + .../csereal/core/research/database/LabEntity.kt | 5 ++++- .../core/research/database/ResearchEntity.kt | 7 ++++++- .../csereal/core/research/dto/LabDto.kt | 3 +++ .../csereal/core/research/dto/ResearchDto.kt | 3 +++ .../core/research/service/ResearchService.kt | 13 +++++++++---- .../core/member/service/ProfessorServiceTest.kt | 6 ++++++ .../reseach/service/ResearchSearchServiceTest.kt | 4 ++++ .../core/reseach/service/ResearchServiceTest.kt | 7 +++++++ 10 files changed, 48 insertions(+), 6 deletions(-) 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 3fe5367f..6bbcf2fd 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 @@ -217,6 +217,7 @@ class AboutServiceImpl( @Transactional override fun migrateAbout(requestList: List): List { + // Todo: add about migrate search val list = mutableListOf() for (request in requestList) { @@ -250,6 +251,7 @@ class AboutServiceImpl( @Transactional override fun migrateFutureCareers(request: FutureCareersRequest): FutureCareersPage { + // Todo: add about migrate search val description = request.description val language = request.language val statList = mutableListOf() @@ -312,6 +314,7 @@ class AboutServiceImpl( @Transactional override fun migrateStudentClubs(requestList: List): List { + // Todo: add about migrate search val list = mutableListOf() for (request in requestList) { @@ -343,6 +346,7 @@ class AboutServiceImpl( @Transactional override fun migrateFacilities(requestList: List): List = + // Todo: add about migrate search requestList.map { AboutDto( id = null, @@ -372,6 +376,7 @@ class AboutServiceImpl( @Transactional override fun migrateDirections(requestList: List): List { + // Todo: add about migrate search val list = mutableListOf() for (request in requestList) { 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 a31e65d3..63d7a98d 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 @@ -67,6 +67,7 @@ class AdmissionsServiceImpl( @Transactional override fun migrateAdmissions(requestList: List) = requestList.map { + // Todo: add admission migrate search val mainType = AdmissionsMainType.fromJsonValue(it.mainType) val postType = AdmissionsPostType.fromJsonValue(it.postType) val language = LanguageType.makeStringToLanguageType(it.language) 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 78d59a55..66daa817 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,6 +1,7 @@ package com.wafflestudio.csereal.core.research.database import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.member.database.ProfessorEntity import com.wafflestudio.csereal.core.research.dto.LabDto import com.wafflestudio.csereal.core.research.dto.LabUpdateRequest @@ -9,6 +10,7 @@ import jakarta.persistence.* @Entity(name = "lab") class LabEntity( + var language: LanguageType, var name: String, @OneToMany(mappedBy = "lab") @@ -36,8 +38,9 @@ class LabEntity( ) : BaseTimeEntity() { companion object { - fun of(labDto: LabDto, researchGroup: ResearchEntity): LabEntity { + fun of(languageType: LanguageType, labDto: LabDto, researchGroup: ResearchEntity): LabEntity { return LabEntity( + language = languageType, name = labDto.name, location = labDto.location, tel = labDto.tel, 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 20997609..33b1a55c 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 @@ -3,6 +3,7 @@ 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.common.properties.LanguageType 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 @@ -13,6 +14,9 @@ class ResearchEntity( @Enumerated(EnumType.STRING) var postType: ResearchPostType, + @Enumerated(EnumType.STRING) + var language: LanguageType, + var name: String, @Column(columnDefinition = "mediumText") @@ -34,9 +38,10 @@ class ResearchEntity( override fun bringAttachments() = attachments companion object { - fun of(researchDto: ResearchDto): ResearchEntity { + fun of(languageType: LanguageType, researchDto: ResearchDto): ResearchEntity { return ResearchEntity( postType = researchDto.postType, + language = languageType, name = researchDto.name, description = researchDto.description ) 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 9060bd3f..19126f2b 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,9 +1,11 @@ package com.wafflestudio.csereal.core.research.dto +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.research.database.LabEntity data class LabDto( val id: Long, + val language: String, val name: String, val professors: List?, val location: String?, @@ -19,6 +21,7 @@ data class LabDto( fun of(entity: LabEntity, pdfURL: String): LabDto = entity.run { LabDto( id = this.id, + language = LanguageType.makeLowercase(entity.language), name = this.name, professors = this.professors.map { LabProfessorResponse(id = it.id, name = it.name) }, location = this.location, 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 c1190f39..11325c04 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 @@ -1,5 +1,6 @@ package com.wafflestudio.csereal.core.research.dto +import com.wafflestudio.csereal.common.properties.LanguageType 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 @@ -8,6 +9,7 @@ import java.time.LocalDateTime data class ResearchDto( val id: Long, val postType: ResearchPostType, + val language: String, val name: String, val description: String?, val createdAt: LocalDateTime?, @@ -21,6 +23,7 @@ data class ResearchDto( ResearchDto( id = this.id, postType = this.postType, + language = LanguageType.makeLowercase(entity.language), name = this.name, description = this.description, createdAt = this.createdAt, 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 2dc97539..59c473e3 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 @@ -2,6 +2,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.common.properties.LanguageType import com.wafflestudio.csereal.core.member.database.ProfessorRepository import com.wafflestudio.csereal.core.research.database.* import com.wafflestudio.csereal.core.research.dto.* @@ -58,7 +59,8 @@ class ResearchServiceImpl( mainImage: MultipartFile?, attachments: List? ): ResearchDto { - val newResearch = ResearchEntity.of(request) + val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) + val newResearch = ResearchEntity.of(enumLanguageType, request) if (request.labs != null) { for (lab in request.labs) { @@ -182,7 +184,8 @@ class ResearchServiceImpl( throw CserealException.Csereal404("해당 게시글은 연구그룹이어야 합니다.") } - val newLab = LabEntity.of(request, researchGroup) + val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) + val newLab = LabEntity.of(enumLanguageType, request, researchGroup) if (request.professors != null) { for (professor in request.professors) { @@ -292,7 +295,8 @@ class ResearchServiceImpl( override fun migrateResearchDetail(requestList: List): List { val list = mutableListOf() for (request in requestList) { - val newResearch = ResearchEntity.of(request) + val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) + val newResearch = ResearchEntity.of(enumLanguageType, request) newResearch.researchSearch = ResearchSearchEntity.create(newResearch) @@ -315,7 +319,8 @@ class ResearchServiceImpl( throw CserealException.Csereal404("해당 게시글은 연구그룹이어야 합니다.") } - val newLab = LabEntity.of(request, researchGroup) + val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) + val newLab = LabEntity.of(enumLanguageType, request, researchGroup) newLab.researchSearch = ResearchSearchEntity.create(newLab) 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 bbb1e43c..09329eb3 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 @@ -1,5 +1,6 @@ package com.wafflestudio.csereal.core.member.service +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.member.database.MemberSearchRepository import com.wafflestudio.csereal.core.member.database.ProfessorRepository import com.wafflestudio.csereal.core.member.database.ProfessorStatus @@ -35,11 +36,13 @@ class ProfessorServiceTest( val date = LocalDate.now() val researchEntity = ResearchEntity( + language = LanguageType.KO, name = "researchName", description = null, postType = ResearchPostType.LABS ) var labEntity = LabEntity( + language = LanguageType.KO, name = "labName", location = null, tel = null, @@ -134,11 +137,13 @@ class ProfessorServiceTest( Given("생성되어 있는 간단한 교수에 대하여") { val date = LocalDate.now() val researchEntity = ResearchEntity( + language = LanguageType.KO, name = "researchName", description = null, postType = ResearchPostType.LABS ) val labEntity1 = LabEntity( + language = LanguageType.KO, name = "labName1", location = null, tel = null, @@ -149,6 +154,7 @@ class ProfessorServiceTest( research = researchEntity ) val labEntity2 = LabEntity( + language = LanguageType.KO, name = "labName2", location = null, tel = null, diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchSearchServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchSearchServiceTest.kt index 031ec07a..76603b52 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchSearchServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchSearchServiceTest.kt @@ -1,5 +1,6 @@ package com.wafflestudio.csereal.core.reseach.service +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.member.database.ProfessorRepository import com.wafflestudio.csereal.core.member.database.ProfessorStatus import com.wafflestudio.csereal.core.member.dto.ProfessorDto @@ -94,6 +95,7 @@ class ResearchSearchServiceTest( // Save research val research = researchRepository.save( ResearchEntity( + language = LanguageType.KO, name = "research", postType = ResearchPostType.GROUPS, description = null @@ -103,6 +105,7 @@ class ResearchSearchServiceTest( // Save lab val labDto = LabDto( id = -1, + language = "ko", name = "name", professors = listOf( LabProfessorResponse(professor1.id, professor1.name), @@ -120,6 +123,7 @@ class ResearchSearchServiceTest( val emptyLabDto = LabDto( id = -1, + language = "ko", name = "nameE", professors = listOf(), acronym = "acronymE", 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 df6eb464..fe7a8538 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 @@ -44,6 +44,7 @@ class ResearchServiceTest( Given("간단한 Research를 생성하려고 할 때") { val researchDto = ResearchDto( id = -1, + language = "ko", name = "name", postType = ResearchPostType.CENTERS, description = "description", @@ -93,6 +94,7 @@ class ResearchServiceTest( Given("간단한 Research를 수정하려고 할 때") { val researchDto = ResearchDto( id = -1, + language = "ko", name = "name", postType = ResearchPostType.CENTERS, description = "description", @@ -112,6 +114,7 @@ class ResearchServiceTest( When("Research를 수정한다면") { val researchUpdateRequest = ResearchDto( id = createdResearchDto.id, + language = "ko", name = "name2", postType = ResearchPostType.GROUPS, description = "description2", @@ -189,6 +192,7 @@ class ResearchServiceTest( // Save research val research = researchRepository.save( ResearchEntity( + language = LanguageType.KO, name = "research", postType = ResearchPostType.GROUPS, description = null @@ -197,6 +201,7 @@ class ResearchServiceTest( val labDto = LabDto( id = -1, + language = "ko", name = "name", professors = listOf( LabProfessorResponse(professor1.id, professor1.name), @@ -293,6 +298,7 @@ class ResearchServiceTest( // Save research val research = researchRepository.save( ResearchEntity( + language = LanguageType.KO, name = "research", postType = ResearchPostType.GROUPS, description = null @@ -302,6 +308,7 @@ class ResearchServiceTest( // Save lab val labDto = LabDto( id = -1, + language = "ko", name = "name", professors = listOf( LabProfessorResponse(professor1.id, professor1.name), From a4bfc471071e25fcf56c0a7181709aaf0973f83c 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, 29 Feb 2024 22:51:46 +0900 Subject: [PATCH 148/214] =?UTF-8?q?Refactor:=20Admission=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=20=EB=A6=AC=ED=8E=99=ED=84=B0=EB=A7=81=20(#193)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Refactor: Merge queries into one. * Refactor: Add page search method. * Refactor: Add total count. * Refactor: change to search page or search top. --- .../admissions/api/AdmissionsController.kt | 24 ++++++++-- .../api/res/AdmissionSearchResBody.kt | 6 +++ .../api/res/AdmissionSearchResElem.kt | 41 ++++++++++------ .../database/AdmissionsRepository.kt | 33 ++++++++++--- .../admissions/service/AdmissionsService.kt | 47 ++++++++++++++++--- 5 files changed, 122 insertions(+), 29 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/res/AdmissionSearchResBody.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 index b25edb3c..f5654461 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 @@ -9,6 +9,7 @@ import com.wafflestudio.csereal.core.admissions.service.AdmissionsService import com.wafflestudio.csereal.core.admissions.type.AdmissionsMainType import com.wafflestudio.csereal.core.admissions.type.AdmissionsPostType import jakarta.validation.Valid +import jakarta.validation.constraints.Positive import org.springframework.web.bind.annotation.* @RequestMapping("/api/v1/admissions") @@ -41,15 +42,32 @@ class AdmissionsController( return admissionsService.readAdmission(mainType, postType, languageType) } - @GetMapping("/search") + @GetMapping("/search/top") fun searchTopAdmissions( @RequestParam(required = true) keyword: String, @RequestParam(required = true, defaultValue = "ko") language: String, - @RequestParam(required = true) number: Int + @RequestParam(required = true) @Valid @Positive number: Int, + @RequestParam(required = false, defaultValue = "30") @Valid @Positive amount: Int ) = admissionsService.searchTopAdmission( keyword, LanguageType.makeStringToLanguageType(language), - number + number, + amount + ) + + @GetMapping("/search") + fun searchPageAdmissions( + @RequestParam(required = true) keyword: String, + @RequestParam(required = true, defaultValue = "ko") language: String, + @RequestParam(required = true) @Valid @Positive pageSize: Int, + @RequestParam(required = true) @Valid @Positive pageNum: Int, + @RequestParam(required = false, defaultValue = "30") @Valid @Positive amount: Int + ) = admissionsService.searchPageAdmission( + keyword, + LanguageType.makeStringToLanguageType(language), + pageSize, + pageNum, + amount ) @PostMapping("/migrate") diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/res/AdmissionSearchResBody.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/res/AdmissionSearchResBody.kt new file mode 100644 index 00000000..49b868fa --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/res/AdmissionSearchResBody.kt @@ -0,0 +1,6 @@ +package com.wafflestudio.csereal.core.admissions.api.res + +data class AdmissionSearchResBody( + val total: Long, + val admissions: List +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/res/AdmissionSearchResElem.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/res/AdmissionSearchResElem.kt index a1e7d015..522aa41c 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/res/AdmissionSearchResElem.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/res/AdmissionSearchResElem.kt @@ -1,28 +1,41 @@ package com.wafflestudio.csereal.core.admissions.api.res import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.utils.substringAroundKeyword import com.wafflestudio.csereal.core.admissions.database.AdmissionsEntity -data class AdmissionSearchResBody( - val admissions: List -) - -data class AdmissionSearchResElem( +data class AdmissionSearchResElem private constructor( val id: Long, val name: String, val mainType: String, val postType: String, - val language: String + val language: String, + val partialDescription: String, + val boldStartIndex: Int, + val boldEndIndex: Int ) { companion object { fun of( - admissions: AdmissionsEntity - ) = AdmissionSearchResElem( - id = admissions.id, - name = admissions.name, - mainType = admissions.mainType.toJsonValue(), - postType = admissions.postType.toJsonValue(), - language = LanguageType.makeLowercase(admissions.language) - ) + admissions: AdmissionsEntity, + keyword: String, + amount: Int + ) = admissions.let { + val (boldStartIdx, partialDescription) = substringAroundKeyword( + keyword = keyword, + content = it.description, + amount = amount + ) + + AdmissionSearchResElem( + id = it.id, + name = it.name, + mainType = it.mainType.toJsonValue(), + postType = it.postType.toJsonValue(), + language = LanguageType.makeLowercase(it.language), + partialDescription = partialDescription.replace('\n', ' '), + boldStartIndex = boldStartIdx ?: 0, + boldEndIndex = boldStartIdx?.plus(keyword.length) ?: 0 + ) + } } } 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 34979cb0..71b4d450 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,7 @@ package com.wafflestudio.csereal.core.admissions.database import com.querydsl.jpa.impl.JPAQueryFactory import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.common.repository.CommonRepository +import com.wafflestudio.csereal.common.utils.exchangePageNum import com.wafflestudio.csereal.core.admissions.database.QAdmissionsEntity.admissionsEntity import com.wafflestudio.csereal.core.admissions.type.AdmissionsMainType import com.wafflestudio.csereal.core.admissions.type.AdmissionsPostType @@ -18,7 +19,12 @@ interface AdmissionsRepository : JpaRepository, Admissio } interface AdmissionsCustomRepository { - fun searchTopAdmissions(keyword: String, language: LanguageType, number: Int): List + fun searchAdmissions( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int + ): Pair, Long> } @Repository @@ -26,15 +32,30 @@ class AdmissionsCustomRepositoryImpl( private val commonRepository: CommonRepository, private val queryFactory: JPAQueryFactory ) : AdmissionsCustomRepository { - override fun searchTopAdmissions( + override fun searchAdmissions( keyword: String, language: LanguageType, - number: Int - ): List = - searchQueryOfLanguage(keyword, language) - .limit(number.toLong()) + pageSize: Int, + pageNum: Int + ): Pair, Long> { + val total = searchCount(keyword, language) + val validPageNum = exchangePageNum(pageSize, pageNum, total) + val validOffset = ( + if (validPageNum >= 1) validPageNum - 1 else 0 + ) * pageSize.toLong() + + val result = searchQueryOfLanguage(keyword, language) + .offset(validOffset) + .limit(pageSize.toLong()) .fetch() + return result to total + } + fun searchCount(keyword: String, language: LanguageType) = + searchQueryOfLanguage(keyword, language) + .select(admissionsEntity.countDistinct()) + .fetchOne()!! + fun searchQueryOfLanguage(keyword: String, language: LanguageType) = queryFactory.selectFrom( admissionsEntity 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 63d7a98d..2959dab1 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 @@ -4,6 +4,7 @@ import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.admissions.api.req.AdmissionMigrateElem import com.wafflestudio.csereal.core.admissions.api.req.AdmissionReqBody +import com.wafflestudio.csereal.core.admissions.api.res.AdmissionSearchResBody import com.wafflestudio.csereal.core.admissions.api.res.AdmissionSearchResElem import com.wafflestudio.csereal.core.admissions.database.AdmissionsEntity import com.wafflestudio.csereal.core.admissions.database.AdmissionsRepository @@ -28,8 +29,15 @@ interface AdmissionsService { fun migrateAdmissions(requestList: List): List - @Transactional(readOnly = true) - fun searchTopAdmission(keyword: String, language: LanguageType, number: Int): List + fun searchPageAdmission( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int, + amount: Int + ): AdmissionSearchResBody + + fun searchTopAdmission(keyword: String, language: LanguageType, number: Int, amount: Int): AdmissionSearchResBody } @Service @@ -60,10 +68,37 @@ class AdmissionsServiceImpl( ?: throw CserealException.Csereal404("해당하는 페이지를 찾을 수 없습니다.") @Transactional(readOnly = true) - override fun searchTopAdmission(keyword: String, language: LanguageType, number: Int) = - admissionsRepository.searchTopAdmissions(keyword, language, number).map { - AdmissionSearchResElem.of(it) - } + override fun searchTopAdmission( + keyword: String, + language: LanguageType, + number: Int, + amount: Int + ): AdmissionSearchResBody { + val (admissions, total) = admissionsRepository.searchAdmissions(keyword, language, number, 1) + return AdmissionSearchResBody( + total = total, + admissions = admissions.map { + AdmissionSearchResElem.of(it, keyword, amount) + } + ) + } + + @Transactional(readOnly = true) + override fun searchPageAdmission( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int, + amount: Int + ): AdmissionSearchResBody { + val (admissions, total) = admissionsRepository.searchAdmissions(keyword, language, pageSize, pageNum) + return AdmissionSearchResBody( + total = total, + admissions = admissions.map { + AdmissionSearchResElem.of(it, keyword, amount) + } + ) + } @Transactional override fun migrateAdmissions(requestList: List) = requestList.map { From 710fbccf408d6e01a38ef40f44d145cc6b31d3c3 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Fri, 1 Mar 2024 13:13:31 +0900 Subject: [PATCH 149/214] =?UTF-8?q?feat:=20academics=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(#195)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: professor language 추가 * feat: professor 테스트 추가 * feat: professor language 다른 테스트에도 추가 * feat: 테스트 알맞게 수정 * feat: researchDetail에 language 추가 * feat: lab에 language 추가 * fix: about, admission migrate search todo 추가 * fix: course migrate 추가 * fix: conference, scholarship language 추가 * fix: migrate academics, scholarship 추가 * fix: migrate academics attachments 추가 --------- Co-authored-by: 우혁준 (HyukJoon Woo) --- .../core/academics/api/AcademicsController.kt | 42 +++++++ .../academics/database/AcademicsEntity.kt | 2 + .../core/academics/database/CourseEntity.kt | 2 + .../academics/database/ScholarshipEntity.kt | 11 +- .../core/academics/dto/ScholarshipDto.kt | 3 + .../academics/service/AcademicsService.kt | 112 +++++++++++++++++- .../conference/database/ConferenceEntity.kt | 6 + .../core/conference/dto/ConferenceDto.kt | 3 + .../conference/service/ConferenceService.kt | 10 +- .../service/ConferenceServiceTest.kt | 6 + 10 files changed, 193 insertions(+), 4 deletions(-) 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 55df6ce2..9d8e4d3d 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 @@ -100,6 +100,48 @@ class AcademicsController( return ResponseEntity.ok(academicsService.readScholarship(scholarshipId)) } + @PostMapping("/{studentType}/{postType}/migrate") + fun migrateAcademicsDetail( + @PathVariable studentType: String, + @PathVariable postType: String, + @RequestBody requestList: List + ): ResponseEntity> { + return ResponseEntity.ok( + academicsService.migrateAcademicsDetail(studentType, postType, requestList) + ) + } + + @PostMapping("/course/migrate/{studentType}") + fun migrateCourses( + @PathVariable studentType: String, + @RequestBody requestList: List + ): ResponseEntity> { + return ResponseEntity.ok(academicsService.migrateCourses(studentType, requestList)) + } + + @PostMapping("/{studentType}/scholarshipDetail/migrate") + fun migrateScholarshipDetail( + @PathVariable studentType: String, + @RequestBody requestList: List + ): ResponseEntity> { + return ResponseEntity.ok( + academicsService.migrateScholarshipDetail(studentType, requestList) + ) + } + + @PatchMapping("/migrateAttachment/{academicsId}") + fun migrateAcademicsDetailAttachments( + @PathVariable academicsId: Long, + @RequestPart("attachments") attachments: List? + ): ResponseEntity { + return ResponseEntity.ok( + academicsService.migrateAcademicsDetailAttachments( + academicsId, + attachments + ) + ) + } + @GetMapping("/search/top") fun searchTop( @RequestParam(required = true) keyword: String, 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 e5e55b91..23339598 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 @@ -18,6 +18,8 @@ class AcademicsEntity( var language: LanguageType, var name: String, + + @Column(columnDefinition = "mediumText") var description: String, var year: Int?, var time: String?, 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 792c0507..1200c790 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 @@ -21,6 +21,8 @@ class CourseEntity( var name: String, var credit: Int, var grade: String, + + @Column(columnDefinition = "mediumText") var description: String?, @OneToMany(mappedBy = "course", cascade = [CascadeType.ALL], orphanRemoval = true) 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 6aee54fd..64ab6098 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,6 +1,7 @@ package com.wafflestudio.csereal.core.academics.database import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.academics.dto.ScholarshipDto import jakarta.persistence.* @@ -9,6 +10,9 @@ class ScholarshipEntity( @Enumerated(EnumType.STRING) var studentType: AcademicsStudentType, + @Enumerated(EnumType.STRING) + var language: LanguageType, + val name: String, @Column(columnDefinition = "text") @@ -20,8 +24,13 @@ class ScholarshipEntity( ) : BaseTimeEntity() { companion object { - fun of(studentType: AcademicsStudentType, scholarshipDto: ScholarshipDto): ScholarshipEntity { + fun of( + languageType: LanguageType, + studentType: AcademicsStudentType, + scholarshipDto: ScholarshipDto + ): ScholarshipEntity { return ScholarshipEntity( + language = languageType, studentType = studentType, name = scholarshipDto.name, description = scholarshipDto.description diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/ScholarshipDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/ScholarshipDto.kt index 533f9395..0312e296 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/ScholarshipDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/ScholarshipDto.kt @@ -1,9 +1,11 @@ package com.wafflestudio.csereal.core.academics.dto +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.academics.database.ScholarshipEntity data class ScholarshipDto( val id: Long, + val language: String, val name: String, val description: String ) { @@ -11,6 +13,7 @@ data class ScholarshipDto( fun of(scholarshipEntity: ScholarshipEntity): ScholarshipDto { return ScholarshipDto( id = scholarshipEntity.id, + language = LanguageType.makeLowercase(scholarshipEntity.language), name = scholarshipEntity.name, description = scholarshipEntity.description ) 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 176d5b8b..3441f954 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 @@ -29,6 +29,20 @@ interface AcademicsService { fun createScholarshipDetail(studentType: String, request: ScholarshipDto): ScholarshipDto fun readAllScholarship(studentType: String): ScholarshipPageResponse fun readScholarship(scholarshipId: Long): ScholarshipDto + fun migrateAcademicsDetail( + studentType: String, + postType: String, + requestList: List + ): List + fun migrateCourses(studentType: String, requestList: List): List + fun migrateScholarshipDetail( + studentType: String, + requestList: List + ): List + fun migrateAcademicsDetailAttachments( + academicsId: Long, + attachments: List? + ): AcademicsDto } // TODO: add Update, Delete method @@ -156,8 +170,9 @@ class AcademicsServiceImpl( @Transactional override fun createScholarshipDetail(studentType: String, request: ScholarshipDto): ScholarshipDto { + val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) val enumStudentType = makeStringToAcademicsStudentType(studentType) - var newScholarship = ScholarshipEntity.of(enumStudentType, request) + var newScholarship = ScholarshipEntity.of(enumLanguageType, enumStudentType, request) // create search data newScholarship.apply { @@ -189,6 +204,101 @@ class AcademicsServiceImpl( return ScholarshipDto.of(scholarship) } + @Transactional + override fun migrateAcademicsDetail( + studentType: String, + postType: String, + requestList: List + ): List { + val enumStudentType = makeStringToAcademicsStudentType(studentType) + val enumPostType = makeStringToAcademicsPostType(postType) + val list = mutableListOf() + for (request in requestList) { + val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) + val newAcademics = AcademicsEntity.of( + enumStudentType, + enumPostType, + enumLanguageType, + request + ) + + newAcademics.apply { + academicsSearch = AcademicsSearchEntity.create(this) + } + + academicsRepository.save(newAcademics) + + list.add(AcademicsDto.of(newAcademics, listOf())) + } + + return list + } + + @Transactional + override fun migrateCourses(studentType: String, requestList: List): List { + val enumStudentType = makeStringToAcademicsStudentType(studentType) + val list = mutableListOf() + for (request in requestList) { + val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) + val newCourse = CourseEntity.of(enumStudentType, enumLanguageType, request) + + newCourse.apply { + academicsSearch = AcademicsSearchEntity.create(this) + } + courseRepository.save(newCourse) + + list.add(CourseDto.of(newCourse, listOf())) + } + + return list + } + + @Transactional + override fun migrateScholarshipDetail( + studentType: String, + requestList: List + ): List { + val enumStudentType = makeStringToAcademicsStudentType(studentType) + val list = mutableListOf() + for (request in requestList) { + val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) + val newScholarship = ScholarshipEntity.of( + enumLanguageType, + enumStudentType, + request + ) + + newScholarship.apply { + academicsSearch = AcademicsSearchEntity.create(this) + } + + scholarshipRepository.save(newScholarship) + + list.add(ScholarshipDto.of(newScholarship)) + } + + return list + } + + @Transactional + override fun migrateAcademicsDetailAttachments( + academicsId: Long, + attachments: List? + ): AcademicsDto { + val academics = academicsRepository.findByIdOrNull(academicsId) + ?: throw CserealException.Csereal404("해당 내용을 찾을 수 없습니다.") + + if (attachments != null) { + attachmentService.uploadAllAttachments(academics, attachments) + } + + val attachmentResponses = attachmentService.createAttachmentResponses( + academics.attachments + ) + + return AcademicsDto.of(academics, attachmentResponses) + } + private fun makeStringToAcademicsStudentType(postType: String): AcademicsStudentType { try { val upperPostType = postType.replace("-", "_").uppercase() 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 a90e6313..5a5f03dc 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,12 +1,16 @@ package com.wafflestudio.csereal.core.conference.database import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.conference.dto.ConferenceDto import com.wafflestudio.csereal.core.research.database.ResearchSearchEntity import jakarta.persistence.* @Entity(name = "conference") class ConferenceEntity( + @Enumerated(EnumType.STRING) + var language: LanguageType, + var isDeleted: Boolean = false, var code: String, var abbreviation: String, @@ -21,9 +25,11 @@ class ConferenceEntity( ) : BaseTimeEntity() { companion object { fun of( + languageType: LanguageType, conferenceDto: ConferenceDto, conferencePage: ConferencePageEntity ) = ConferenceEntity( + language = languageType, code = conferenceDto.code, abbreviation = conferenceDto.abbreviation, name = conferenceDto.name, 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 ca427e78..d6e95c59 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 @@ -1,11 +1,13 @@ package com.wafflestudio.csereal.core.conference.dto import com.fasterxml.jackson.annotation.JsonInclude +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.conference.database.ConferenceEntity data class ConferenceDto( @JsonInclude(JsonInclude.Include.NON_NULL) val id: Long? = null, + val language: String, val code: String, val abbreviation: String, val name: String @@ -14,6 +16,7 @@ data class ConferenceDto( fun of(conferenceEntity: ConferenceEntity): ConferenceDto { return ConferenceDto( id = conferenceEntity.id, + language = LanguageType.makeLowercase(conferenceEntity.language), code = conferenceEntity.code, abbreviation = conferenceEntity.abbreviation, name = conferenceEntity.name 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 6cbe1e4d..9fe38d23 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,6 +1,7 @@ package com.wafflestudio.csereal.core.conference.service import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.conference.database.ConferenceEntity import com.wafflestudio.csereal.core.conference.database.ConferencePageEntity import com.wafflestudio.csereal.core.conference.database.ConferencePageRepository @@ -40,7 +41,9 @@ class ConferenceServiceImpl( } @Transactional - override fun modifyConferences(conferenceModifyRequest: ConferenceModifyRequest): ConferencePage { + override fun modifyConferences( + conferenceModifyRequest: ConferenceModifyRequest + ): ConferencePage { val user = RequestContextHolder.getRequestAttributes()?.getAttribute( "loggedInUser", RequestAttributes.SCOPE_REQUEST @@ -76,7 +79,8 @@ class ConferenceServiceImpl( val conferencePage = ConferencePageEntity.of(user) conferencePageRepository.save(conferencePage) for (request in requestList) { - val conference = ConferenceEntity.of(request, conferencePage) + val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) + val conference = ConferenceEntity.of(enumLanguageType, request, conferencePage) conferenceRepository.save(conference) @@ -94,7 +98,9 @@ class ConferenceServiceImpl( conferenceDto: ConferenceDto, conferencePage: ConferencePageEntity ): ConferenceEntity { + val enumLanguageType = LanguageType.makeStringToLanguageType(conferenceDto.language) val newConference = ConferenceEntity.of( + enumLanguageType, conferenceDto, conferencePage ) 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 206cf76d..2b6a87f0 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 @@ -1,5 +1,6 @@ package com.wafflestudio.csereal.core.conference.service +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.conference.database.ConferenceEntity import com.wafflestudio.csereal.core.conference.database.ConferencePageEntity import com.wafflestudio.csereal.core.conference.database.ConferencePageRepository @@ -76,18 +77,21 @@ class ConferenceServiceTest( val conferences = conferenceRepository.saveAll( listOf( ConferenceEntity( + language = LanguageType.KO, code = "code1", name = "name1", abbreviation = "abbreviation1", conferencePage = conferencePage ), ConferenceEntity( + language = LanguageType.KO, code = "code2", name = "name2", abbreviation = "abbreviation2", conferencePage = conferencePage ), ConferenceEntity( + language = LanguageType.KO, code = "code3", name = "name3", abbreviation = "abbreviation3", @@ -105,11 +109,13 @@ class ConferenceServiceTest( val deleteConferenceId = conferences[1].id val modifiedConference = ConferenceDto( id = conferences.first().id, + language = "ko", code = "code0", name = "modifiedName", abbreviation = "modifiedAbbreviation" ) val newConference = ConferenceDto( + language = "ko", code = "code9", name = "newName", abbreviation = "newAbbreviation" From eb2fbd61e1cbdc1b77efada654cebb3b15304e33 Mon Sep 17 00:00:00 2001 From: "DESKTOP-USQPRVG\\gram" Date: Fri, 1 Mar 2024 14:03:07 +0900 Subject: [PATCH 150/214] =?UTF-8?q?fix:=20ktlint=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/about/service/AboutService.kt | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) 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 3fe5367f..e7a7ffbe 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 @@ -27,7 +27,12 @@ interface AboutService { fun readAllDirections(language: String): List fun readFutureCareers(): FutureCareersPage - fun searchTopAbout(keyword: String, language: LanguageType, number: Int, amount: Int): AboutSearchResBody + fun searchTopAbout( + keyword: String, + language: LanguageType, + number: Int, + amount: Int + ): AboutSearchResBody fun searchPageAbout( keyword: String, @@ -114,7 +119,10 @@ class AboutServiceImpl( override fun readAllFacilities(language: String): List { val languageType = LanguageType.makeStringToLanguageType(language) val facilities = - aboutRepository.findAllByLanguageAndPostTypeOrderByName(languageType, AboutPostType.FACILITIES).map { + aboutRepository.findAllByLanguageAndPostTypeOrderByName( + languageType, + AboutPostType.FACILITIES + ).map { val imageURL = mainImageService.createImageURL(it.mainImage) val attachmentResponses = attachmentService.createAttachmentResponses(it.attachments) AboutDto.of(it, imageURL, attachmentResponses) @@ -127,7 +135,10 @@ class AboutServiceImpl( override fun readAllDirections(language: String): List { val languageType = LanguageType.makeStringToLanguageType(language) val directions = - aboutRepository.findAllByLanguageAndPostTypeOrderByName(languageType, AboutPostType.DIRECTIONS).map { + aboutRepository.findAllByLanguageAndPostTypeOrderByName( + languageType, + AboutPostType.DIRECTIONS + ).map { val imageURL = mainImageService.createImageURL(it.mainImage) val attachments = attachmentService.createAttachmentResponses(it.attachments) AboutDto.of(it, imageURL, attachments) @@ -206,7 +217,12 @@ class AboutServiceImpl( pageNum: Int, amount: Int ): AboutSearchResBody { - val (searchEntities, searchCnt) = aboutRepository.searchAbouts(keyword, language, pageSize, pageNum) + val (searchEntities, searchCnt) = aboutRepository.searchAbouts( + keyword, + language, + pageSize, + pageNum + ) return AboutSearchResBody( searchCnt, searchEntities.map { From 5c3679cf767523e8c3d92fd37688fdf16e18888f Mon Sep 17 00:00:00 2001 From: "DESKTOP-USQPRVG\\gram" Date: Fri, 1 Mar 2024 14:10:49 +0900 Subject: [PATCH 151/214] =?UTF-8?q?fix:=20ktlint=20=EC=A0=95=EB=A6=AC=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/about/service/AboutService.kt | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) 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 da735db0..f376b0b5 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 @@ -86,7 +86,8 @@ class AboutServiceImpl( newAbout = aboutRepository.save(newAbout) val imageURL = mainImageService.createImageURL(newAbout.mainImage) - val attachmentResponses = attachmentService.createAttachmentResponses(newAbout.attachments) + val attachmentResponses = + attachmentService.createAttachmentResponses(newAbout.attachments) return AboutDto.of(newAbout, imageURL, attachmentResponses) } @@ -106,9 +107,13 @@ class AboutServiceImpl( override fun readAllClubs(language: String): List { val languageType = LanguageType.makeStringToLanguageType(language) val clubs = - aboutRepository.findAllByLanguageAndPostTypeOrderByName(languageType, AboutPostType.STUDENT_CLUBS).map { + aboutRepository.findAllByLanguageAndPostTypeOrderByName( + languageType, + AboutPostType.STUDENT_CLUBS + ).map { val imageURL = mainImageService.createImageURL(it.mainImage) - val attachmentResponses = attachmentService.createAttachmentResponses(it.attachments) + val attachmentResponses = + attachmentService.createAttachmentResponses(it.attachments) AboutDto.of(it, imageURL, attachmentResponses) } @@ -124,7 +129,8 @@ class AboutServiceImpl( AboutPostType.FACILITIES ).map { val imageURL = mainImageService.createImageURL(it.mainImage) - val attachmentResponses = attachmentService.createAttachmentResponses(it.attachments) + val attachmentResponses = + attachmentService.createAttachmentResponses(it.attachments) AboutDto.of(it, imageURL, attachmentResponses) } @@ -200,7 +206,8 @@ class AboutServiceImpl( number: Int, amount: Int ): AboutSearchResBody { - val (searchEntities, searchCnt) = aboutRepository.searchAbouts(keyword, language, number, 1) + val (searchEntities, searchCnt) = + aboutRepository.searchAbouts(keyword, language, number, 1) return AboutSearchResBody( searchCnt, searchEntities.map { @@ -287,7 +294,7 @@ class AboutServiceImpl( ) val languageType = LanguageType.makeStringToLanguageType(language) - + var newAbout = AboutEntity.of(AboutPostType.FUTURE_CAREERS, languageType, aboutDto) for (stat in request.stat) { @@ -331,7 +338,6 @@ class AboutServiceImpl( @Transactional override fun migrateStudentClubs(requestList: List): List { - // Todo: add about migrate search val list = mutableListOf() for (request in requestList) { @@ -439,7 +445,8 @@ class AboutServiceImpl( } val imageURL = mainImageService.createImageURL(about.mainImage) - val attachmentResponses = attachmentService.createAttachmentResponses(about.attachments) + val attachmentResponses = + attachmentService.createAttachmentResponses(about.attachments) return AboutDto.of(about, imageURL, attachmentResponses) } From bebe886549aa3d923f9465dabf419134ec670cce Mon Sep 17 00:00:00 2001 From: "DESKTOP-USQPRVG\\gram" Date: Fri, 1 Mar 2024 14:36:53 +0900 Subject: [PATCH 152/214] =?UTF-8?q?fix:=20=EC=8B=A4=EC=88=98=EB=A1=9C=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EB=B3=B5?= =?UTF-8?q?=EA=B5=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../csereal/core/admissions/api/res/AdmissionSearchResElem.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/res/AdmissionSearchResElem.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/res/AdmissionSearchResElem.kt index 1a295618..522aa41c 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/res/AdmissionSearchResElem.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/res/AdmissionSearchResElem.kt @@ -1,6 +1,8 @@ package com.wafflestudio.csereal.core.admissions.api.res import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.utils.substringAroundKeyword +import com.wafflestudio.csereal.core.admissions.database.AdmissionsEntity data class AdmissionSearchResElem private constructor( val id: Long, From 8079be150b054dfbd738d47da5daea66caa71114 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 Mar 2024 13:06:55 +0900 Subject: [PATCH 153/214] Refactor: Member Search (#197) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: professor 테스트 추가 * feat: 테스트 알맞게 수정 * fix: about, admission migrate search todo 추가 * Fix: Add commented authentication. * Feat: Add language field to search entity * Feat: Add language in search condition, remove duplicated exchangePageNum, Merge search queries * Feat: Change res body dir, merge two seperate body into one, add language field * Feat: Change return body, repository method useing. * Feat: Add language query parameter, change response body. * Test: Check language of search. * Refactor: Change res body results key as "results" * Refactor: Remove unneeded transactional on interface. --------- Co-authored-by: DESKTOP-USQPRVG\gram --- .../csereal/core/about/api/AboutController.kt | 3 +- .../core/about/service/AboutService.kt | 1 + .../admissions/service/AdmissionsService.kt | 1 + .../core/member/api/MemberSearchController.kt | 19 +++++--- .../res/MemberSearchResBody.kt} | 10 ++--- .../res}/MemberSearchResponseElement.kt | 6 ++- .../member/database/MemberSearchEntity.kt | 5 +++ .../member/database/MemberSearchRepository.kt | 45 +++++++++---------- .../member/dto/MemberSearchTopResponse.kt | 19 -------- .../member/service/MemberSearchService.kt | 26 ++++++----- .../member/service/ProfessorServiceTest.kt | 3 ++ .../core/member/service/StaffServiceTest.kt | 4 ++ 12 files changed, 78 insertions(+), 64 deletions(-) rename src/main/kotlin/com/wafflestudio/csereal/core/member/{dto/MemberSearchPageResponse.kt => api/res/MemberSearchResBody.kt} (63%) rename src/main/kotlin/com/wafflestudio/csereal/core/member/{dto => api/res}/MemberSearchResponseElement.kt (84%) delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchTopResponse.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 f5164b1d..ac46e33e 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.common.properties.LanguageType import com.wafflestudio.csereal.core.about.api.res.AboutSearchResBody import com.wafflestudio.csereal.core.about.dto.* @@ -22,7 +23,7 @@ class AboutController( // postType: directions / name -> by-public-transit, by-car, from-far-away // Todo: 학부장 인사말(greetings) signature -// @AuthenticatedStaff + @AuthenticatedStaff @PostMapping("/{postType}") fun createAbout( @PathVariable postType: String, 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 f376b0b5..e85732e7 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 @@ -370,6 +370,7 @@ class AboutServiceImpl( @Transactional override fun migrateFacilities(requestList: List): List = + // Todo: add about migrate search requestList.map { AboutDto( id = null, 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 6782fc9d..2959dab1 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 @@ -102,6 +102,7 @@ class AdmissionsServiceImpl( @Transactional override fun migrateAdmissions(requestList: List) = requestList.map { + // Todo: add admission migrate search val mainType = AdmissionsMainType.fromJsonValue(it.mainType) val postType = AdmissionsPostType.fromJsonValue(it.postType) val language = LanguageType.makeStringToLanguageType(it.language) 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 bcd393c3..8053d71b 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,6 +1,9 @@ package com.wafflestudio.csereal.core.member.api +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.member.service.MemberSearchService +import jakarta.validation.Valid +import jakarta.validation.constraints.Positive import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam @@ -14,13 +17,19 @@ class MemberSearchController( @GetMapping("/top") fun searchTop( @RequestParam(required = true) keyword: String, - @RequestParam(required = true) number: Int - ) = memberSearchService.searchTopMember(keyword, number) + @RequestParam(required = true) @Valid @Positive number: Int, + @RequestParam(required = true, defaultValue = "ko") language: String + ) = LanguageType.makeStringToLanguageType(language).let { + memberSearchService.searchTopMember(keyword, it, number) + } @GetMapping fun searchPage( @RequestParam(required = true) keyword: String, - @RequestParam(required = true) pageSize: Int, - @RequestParam(required = true) pageNum: Int - ) = memberSearchService.searchMember(keyword, pageSize, pageNum) + @RequestParam(required = true, defaultValue = "ko") language: String, + @RequestParam(required = true) @Valid @Positive pageSize: Int, + @RequestParam(required = true) @Valid @Positive pageNum: Int + ) = LanguageType.makeStringToLanguageType(language).let { + memberSearchService.searchMember(keyword, it, pageSize, pageNum) + } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchPageResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/res/MemberSearchResBody.kt similarity index 63% rename from src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchPageResponse.kt rename to src/main/kotlin/com/wafflestudio/csereal/core/member/api/res/MemberSearchResBody.kt index 41df25f1..b805c85b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchPageResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/res/MemberSearchResBody.kt @@ -1,10 +1,10 @@ -package com.wafflestudio.csereal.core.member.dto +package com.wafflestudio.csereal.core.member.api.res import com.wafflestudio.csereal.core.member.database.MemberSearchEntity import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity -data class MemberSearchPageResponse( - val members: List, +data class MemberSearchResBody( + val results: List, val total: Long ) { companion object { @@ -12,8 +12,8 @@ data class MemberSearchPageResponse( members: List, total: Long, imageURLMaker: (MainImageEntity?) -> String? - ) = MemberSearchPageResponse( - members = members.map { MemberSearchResponseElement.of(it, imageURLMaker) }, + ) = MemberSearchResBody( + results = 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/api/res/MemberSearchResponseElement.kt similarity index 84% rename from src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchResponseElement.kt rename to src/main/kotlin/com/wafflestudio/csereal/core/member/api/res/MemberSearchResponseElement.kt index 5a474194..a7365722 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchResponseElement.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/res/MemberSearchResponseElement.kt @@ -1,12 +1,14 @@ -package com.wafflestudio.csereal.core.member.dto +package com.wafflestudio.csereal.core.member.api.res import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.common.properties.LanguageType 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 language: String, val name: String, val academicRankOrRole: String, val imageURL: String?, @@ -21,6 +23,7 @@ data class MemberSearchResponseElement( memberSearch.professor != null && memberSearch.staff == null -> MemberSearchResponseElement( id = memberSearch.professor!!.id, + language = memberSearch.language.let { LanguageType.makeLowercase(it) }, name = memberSearch.professor!!.name, academicRankOrRole = memberSearch.professor!!.academicRank, imageURL = imageURLMaker(memberSearch.professor!!.mainImage), @@ -29,6 +32,7 @@ data class MemberSearchResponseElement( memberSearch.professor == null && memberSearch.staff != null -> MemberSearchResponseElement( id = memberSearch.staff!!.id, + language = memberSearch.language.let { LanguageType.makeLowercase(it) }, name = memberSearch.staff!!.name, academicRankOrRole = memberSearch.staff!!.role, imageURL = imageURLMaker(memberSearch.staff!!.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 3944bf6e..6262fef2 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,6 +1,7 @@ package com.wafflestudio.csereal.core.member.database import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.common.properties.LanguageType import jakarta.persistence.* @Entity(name = "member_search") @@ -8,6 +9,8 @@ class MemberSearchEntity( @Column(columnDefinition = "TEXT") var content: String, + val language: LanguageType, + @OneToOne @JoinColumn(name = "professor_id") val professor: ProfessorEntity? = null, @@ -20,6 +23,7 @@ class MemberSearchEntity( fun create(professor: ProfessorEntity): MemberSearchEntity { return MemberSearchEntity( content = createContent(professor), + language = professor.language, professor = professor ) } @@ -27,6 +31,7 @@ class MemberSearchEntity( fun create(staff: StaffEntity): MemberSearchEntity { return MemberSearchEntity( content = createContent(staff), + language = staff.language, staff = staff ) } 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 662a2d41..2ab47803 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 @@ -2,7 +2,9 @@ package com.wafflestudio.csereal.core.member.database import com.querydsl.jpa.impl.JPAQuery import com.querydsl.jpa.impl.JPAQueryFactory +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.common.repository.CommonRepository +import com.wafflestudio.csereal.common.utils.exchangePageNum 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 @@ -13,8 +15,12 @@ interface MemberSearchRepository : JpaRepository, MemberSearchRepositoryCustom interface MemberSearchRepositoryCustom { - fun searchTopMember(keyword: String, number: Int): List - fun searchMember(keyword: String, pageSize: Int, pageNum: Int): Pair, Long> + fun searchMember( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int + ): Pair, Long> } @Repository @@ -23,15 +29,14 @@ class MemberSearchRepositoryCustomImpl( private val commonRepository: CommonRepository ) : 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) + override fun searchMember( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int + ): Pair, Long> { + val query = searchQuery(keyword, language) + val total = getSearchCount(keyword, language) val validPageNum = exchangePageNum(pageSize, pageNum, total) val queryResult = query @@ -42,7 +47,7 @@ class MemberSearchRepositoryCustomImpl( return queryResult to total } - fun searchQuery(keyword: String): JPAQuery { + fun searchQuery(keyword: String, language: LanguageType): JPAQuery { val searchDoubleTemplate = commonRepository.searchFullSingleTextTemplate( keyword, memberSearchEntity.content @@ -61,11 +66,12 @@ class MemberSearchRepositoryCustomImpl( staffEntity ).fetchJoin() .where( - searchDoubleTemplate.gt(0.0) + searchDoubleTemplate.gt(0.0), + memberSearchEntity.language.eq(language) ) } - fun getSearchCount(keyword: String): Long { + fun getSearchCount(keyword: String, language: LanguageType): Long { val searchDoubleTemplate = commonRepository.searchFullSingleTextTemplate( keyword, memberSearchEntity.content @@ -77,15 +83,8 @@ class MemberSearchRepositoryCustomImpl( ).from( memberSearchEntity ).where( - searchDoubleTemplate.gt(0.0) + searchDoubleTemplate.gt(0.0), + memberSearchEntity.language.eq(language) ).fetchOne()!! } - - fun exchangePageNum(pageSize: Int, pageNum: Int, total: Long): Int { - return if ((pageNum - 1) * pageSize < total) { - pageNum - } else { - Math.ceil(total.toDouble() / pageSize).toInt() - } - } } 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 deleted file mode 100644 index 6e577a56..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchTopResponse.kt +++ /dev/null @@ -1,19 +0,0 @@ -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) - } - ) - } -} 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 35651b02..ca338b53 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,15 +1,16 @@ package com.wafflestudio.csereal.core.member.service +import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.core.member.api.res.MemberSearchResBody import com.wafflestudio.csereal.core.member.database.MemberSearchRepository -import com.wafflestudio.csereal.core.member.dto.MemberSearchPageResponse -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 + fun searchTopMember(keyword: String, language: LanguageType, number: Int): MemberSearchResBody + + fun searchMember(keyword: String, language: LanguageType, pageSize: Int, pageNum: Int): MemberSearchResBody } @Service @@ -18,14 +19,19 @@ class MemberSearchServiceImpl( 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) + override fun searchTopMember(keyword: String, language: LanguageType, number: Int): MemberSearchResBody { + val (entityResults, total) = memberSearchRepository.searchMember(keyword, language, number, 1) + return MemberSearchResBody.of(entityResults, total, 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) + override fun searchMember( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int + ): MemberSearchResBody { + val (entityResults, total) = memberSearchRepository.searchMember(keyword, language, pageSize, pageNum) + return MemberSearchResBody.of(entityResults, total, mainImageService::createImageURL) } } 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 09329eb3..6e38fa5e 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 @@ -14,9 +14,11 @@ import io.kotest.matchers.shouldNotBe import jakarta.transaction.Transactional import org.springframework.boot.test.context.SpringBootTest import org.springframework.data.repository.findByIdOrNull +import org.springframework.test.context.ActiveProfiles import java.time.LocalDate @SpringBootTest +@ActiveProfiles("test") @Transactional class ProfessorServiceTest( private val professorService: ProfessorService, @@ -107,6 +109,7 @@ class ProfessorServiceTest( val memberSearchEntity = memberSearchRepository.findAll()[0] memberSearchEntity.professor?.id shouldBe createdProfessorDto.id + memberSearchEntity.language shouldBe LanguageType.KO val contentExpected = """ name 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 37043fad..40644618 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 @@ -1,5 +1,6 @@ package com.wafflestudio.csereal.core.member.service +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.member.database.MemberSearchRepository import com.wafflestudio.csereal.core.member.database.StaffRepository import com.wafflestudio.csereal.core.member.dto.StaffDto @@ -11,8 +12,10 @@ import io.kotest.matchers.shouldNotBe import jakarta.transaction.Transactional import org.springframework.boot.test.context.SpringBootTest import org.springframework.data.repository.findByIdOrNull +import org.springframework.test.context.ActiveProfiles @SpringBootTest +@ActiveProfiles("test") @Transactional class StaffServiceTest( private val staffService: StaffService, @@ -60,6 +63,7 @@ class StaffServiceTest( val staffEntity = staffRepository.findByIdOrNull(createdStaffDto.id!!)!! val memberSearch = staffEntity.memberSearch!! + memberSearch.language shouldBe LanguageType.KO memberSearch.content shouldBe """ name role From 4b4479e25277a4eb903a9845139f4eda8dbb2562 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Sat, 2 Mar 2024 13:10:32 +0900 Subject: [PATCH 154/214] =?UTF-8?q?feat:=20getMapping=EC=97=90=20language?= =?UTF-8?q?=20requestParam=20=EC=B6=94=EA=B0=80=20(#198)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: academics 패키지 get에 language 추가 * fix: professor 패키지 get에 language 추가 * fix: research 패키지 get에 language 추가 * feat: academics 패키지 이름에 맞게 변경 (#199) * feat: add readDegreeRequirements * feat: readGeneralRequirements 삭제, readDegreeRequirements 추가 * feat: AcademicsEntity time 칼럼 삭제 --------- Co-authored-by: 우혁준 (HyukJoon Woo) --- .../core/academics/api/AcademicsController.kt | 27 ++-- .../academics/database/AcademicsEntity.kt | 4 +- .../academics/database/AcademicsPostType.kt | 2 +- .../academics/database/AcademicsRepository.kt | 11 +- .../database/AcademicsSearchEntity.kt | 1 - .../core/academics/dto/AcademicsDto.kt | 2 - ...ChangesDto.kt => DegreeRequirementsDto.kt} | 8 +- .../dto/DegreeRequirementsPageResponse.kt | 17 +++ .../dto/GeneralStudiesPageResponse.kt | 17 --- .../academics/service/AcademicsService.kt | 123 ++++++++++++------ .../core/member/api/ProfessorController.kt | 18 ++- .../member/database/ProfessorRepository.kt | 11 +- .../core/member/service/ProfessorService.kt | 56 +++++--- .../core/research/api/ResearchController.kt | 18 ++- .../core/research/database/LabRepository.kt | 3 +- .../research/database/ResearchRepository.kt | 6 +- .../core/research/service/ResearchService.kt | 61 +++++---- 17 files changed, 247 insertions(+), 138 deletions(-) rename src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/{SubjectChangesDto.kt => DegreeRequirementsDto.kt} (72%) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsPageResponse.kt delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GeneralStudiesPageResponse.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 9d8e4d3d..89f907c5 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 @@ -26,22 +26,28 @@ class AcademicsController( request: AcademicsDto, @RequestPart("attachments") attachments: List? ): ResponseEntity { - return ResponseEntity.ok(academicsService.createAcademics(studentType, postType, request, attachments)) + return ResponseEntity.ok( + academicsService.createAcademics(studentType, postType, request, attachments) + ) } @GetMapping("/{studentType}/guide") fun readGuide( + @RequestParam(required = false, defaultValue = "ko") language: String, @PathVariable studentType: String ): ResponseEntity { - return ResponseEntity.ok(academicsService.readGuide(studentType)) + return ResponseEntity.ok(academicsService.readGuide(language, studentType)) } @GetMapping("/{studentType}/{postType}") fun readAcademicsYearResponses( + @RequestParam(required = false, defaultValue = "ko") language: String, @PathVariable studentType: String, @PathVariable postType: String ): ResponseEntity> { - return ResponseEntity.ok(academicsService.readAcademicsYearResponses(studentType, postType)) + return ResponseEntity.ok( + academicsService.readAcademicsYearResponses(language, studentType, postType) + ) } //교과목 정보 @@ -73,9 +79,11 @@ class AcademicsController( return ResponseEntity.ok(academicsService.readCourse(language, name)) } - @GetMapping("/undergraduate/general-studies-requirements") - fun readGeneralStudiesRequirements(): ResponseEntity { - return ResponseEntity.ok(academicsService.readGeneralStudies()) + @GetMapping("/undergraduate/degree-requirements") + fun readDegreeRequirements( + @RequestParam(required = false, defaultValue = "ko") language: String + ): ResponseEntity { + return ResponseEntity.ok(academicsService.readDegreeRequirements(language)) } @AuthenticatedStaff @@ -90,13 +98,16 @@ class AcademicsController( @GetMapping("/{studentType}/scholarship") fun readAllScholarship( + @RequestParam(required = false, defaultValue = "ko") language: String, @PathVariable studentType: String ): ResponseEntity { - return ResponseEntity.ok(academicsService.readAllScholarship(studentType)) + return ResponseEntity.ok(academicsService.readAllScholarship(language, studentType)) } @GetMapping("/scholarship/{scholarshipId}") - fun getScholarship(@PathVariable scholarshipId: Long): ResponseEntity { + 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 23339598..a3b87160 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 @@ -22,7 +22,6 @@ class AcademicsEntity( @Column(columnDefinition = "mediumText") var description: String, var year: Int?, - var time: String?, @OneToMany(mappedBy = "academics", cascade = [CascadeType.ALL], orphanRemoval = true) var attachments: MutableList = mutableListOf(), @@ -46,8 +45,7 @@ class AcademicsEntity( language = languageType, name = academicsDto.name, description = academicsDto.description, - year = academicsDto.year, - time = academicsDto.time + 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 index 432ea6c6..fb77471f 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 @@ -2,5 +2,5 @@ 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; + CURRICULUM, DEGREE_REQUIREMENTS, DEGREE_REQUIREMENTS_YEAR_LIST, 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 e5eacb97..2aeeb80e 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 @@ -1,17 +1,16 @@ package com.wafflestudio.csereal.core.academics.database +import com.wafflestudio.csereal.common.properties.LanguageType import org.springframework.data.jpa.repository.JpaRepository interface AcademicsRepository : JpaRepository { - fun findByStudentTypeAndPostType( + fun findByLanguageAndStudentTypeAndPostType( + languageType: LanguageType, studentType: AcademicsStudentType, postType: AcademicsPostType ): AcademicsEntity - fun findAllByStudentTypeAndPostTypeOrderByYearDesc( - studentType: AcademicsStudentType, - postType: AcademicsPostType - ): List - fun findAllByStudentTypeAndPostTypeOrderByTimeDesc( + fun findAllByLanguageAndStudentTypeAndPostTypeOrderByYearDesc( + languageType: LanguageType, studentType: AcademicsStudentType, postType: AcademicsPostType ): List diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchEntity.kt index ce50ce3b..6b4d59ce 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchEntity.kt @@ -47,7 +47,6 @@ class AcademicsSearchEntity( fun createContent(academics: AcademicsEntity): String { val sb = StringBuilder() academics.name.let { sb.appendLine(it) } - academics.time?.let { sb.appendLine(it) } academics.year?.let { sb.appendLine(it) } sb.appendLine(academics.studentType.value) sb.appendLine( 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 1530c621..38091e05 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 @@ -11,7 +11,6 @@ data class AcademicsDto( val name: String, val description: String, val year: Int? = null, - val time: String? = null, val createdAt: LocalDateTime? = null, val modifiedAt: LocalDateTime? = null, val attachments: List? = null @@ -24,7 +23,6 @@ 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/SubjectChangesDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsDto.kt similarity index 72% rename from src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/SubjectChangesDto.kt rename to src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsDto.kt index 6d362ac8..e5c531a5 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/SubjectChangesDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsDto.kt @@ -2,14 +2,14 @@ package com.wafflestudio.csereal.core.academics.dto import com.wafflestudio.csereal.core.academics.database.AcademicsEntity -class SubjectChangesDto( - val time: String, +class DegreeRequirementsDto( + val year: Int, val description: String ) { companion object { fun of(entity: AcademicsEntity) = entity.run { - SubjectChangesDto( - time = this.time!!, + DegreeRequirementsDto( + year = this.year!!, description = this.description ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsPageResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsPageResponse.kt new file mode 100644 index 00000000..8d4d2ad4 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsPageResponse.kt @@ -0,0 +1,17 @@ +package com.wafflestudio.csereal.core.academics.dto + +import com.wafflestudio.csereal.core.academics.database.AcademicsEntity + +class DegreeRequirementsPageResponse( + val description: String, + val yearList: List +) { + companion object { + fun of(entity: AcademicsEntity, yearList: List) = entity.run { + DegreeRequirementsPageResponse( + description = this.description, + yearList = yearList.map { DegreeRequirementsDto.of(it) } + ) + } + } +} 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 deleted file mode 100644 index 950434f2..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GeneralStudiesPageResponse.kt +++ /dev/null @@ -1,17 +0,0 @@ -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 - ) - } - } -} 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 3441f954..d2be6702 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 @@ -20,14 +20,25 @@ interface AcademicsService { attachments: List? ): 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 readGuide(language: String, studentType: String): GuidePageResponse + fun readAcademicsYearResponses( + language: String, + studentType: String, + postType: String + ): List + fun readDegreeRequirements(language: String): DegreeRequirementsPageResponse + fun createCourse( + studentType: String, + request: CourseDto, + attachments: List? + ): CourseDto fun readAllCourses(language: String, studentType: String): List fun readCourse(language: String, name: String): CourseDto - fun createScholarshipDetail(studentType: String, request: ScholarshipDto): ScholarshipDto - fun readAllScholarship(studentType: String): ScholarshipPageResponse + fun createScholarshipDetail( + studentType: String, + request: ScholarshipDto + ): ScholarshipDto + fun readAllScholarship(language: String, studentType: String): ScholarshipPageResponse fun readScholarship(scholarshipId: Long): ScholarshipDto fun migrateAcademicsDetail( studentType: String, @@ -66,7 +77,8 @@ class AcademicsServiceImpl( val enumStudentType = makeStringToAcademicsStudentType(studentType) val enumPostType = makeStringToAcademicsPostType(postType) val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) - val newAcademics = AcademicsEntity.of(enumStudentType, enumPostType, enumLanguageType, request) + val newAcademics = + AcademicsEntity.of(enumStudentType, enumPostType, enumLanguageType, request) if (attachments != null) { attachmentService.uploadAllAttachments(newAcademics, attachments) @@ -79,29 +91,44 @@ class AcademicsServiceImpl( academicsRepository.save(newAcademics) - val attachmentResponses = attachmentService.createAttachmentResponses(newAcademics.attachments) + val attachmentResponses = + attachmentService.createAttachmentResponses(newAcademics.attachments) return AcademicsDto.of(newAcademics, attachmentResponses) } @Transactional(readOnly = true) - override fun readGuide(studentType: String): GuidePageResponse { + override fun readGuide(language: String, studentType: String): GuidePageResponse { + val languageType = LanguageType.makeStringToLanguageType(language) val enumStudentType = makeStringToAcademicsStudentType(studentType) - val academicsEntity = academicsRepository.findByStudentTypeAndPostType(enumStudentType, AcademicsPostType.GUIDE) - val attachmentResponses = attachmentService.createAttachmentResponses(academicsEntity.attachments) + val academicsEntity = + academicsRepository.findByLanguageAndStudentTypeAndPostType( + languageType, + 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 { + override fun readAcademicsYearResponses( + language: String, + studentType: String, + postType: String + ): List { + val languageType = LanguageType.makeStringToLanguageType(language) val enumStudentType = makeStringToAcademicsStudentType(studentType) val enumPostType = makeStringToAcademicsPostType(postType) - val academicsEntityList = academicsRepository.findAllByStudentTypeAndPostTypeOrderByYearDesc( - enumStudentType, - enumPostType - ) + val academicsEntityList = + academicsRepository.findAllByLanguageAndStudentTypeAndPostTypeOrderByYearDesc( + languageType, + enumStudentType, + enumPostType + ) val academicsYearResponses = academicsEntityList.map { val attachments = attachmentService.createAttachmentResponses(it.attachments) @@ -112,21 +139,31 @@ class AcademicsServiceImpl( } @Transactional(readOnly = true) - 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 - ) + override fun readDegreeRequirements(language: String): DegreeRequirementsPageResponse { + val enumLanguageType = LanguageType.makeStringToLanguageType(language) + val academicsEntity = + academicsRepository.findByLanguageAndStudentTypeAndPostType( + enumLanguageType, + AcademicsStudentType.UNDERGRADUATE, + AcademicsPostType.DEGREE_REQUIREMENTS + ) + + val yearList = + academicsRepository.findAllByLanguageAndStudentTypeAndPostTypeOrderByYearDesc( + enumLanguageType, + AcademicsStudentType.UNDERGRADUATE, + AcademicsPostType.DEGREE_REQUIREMENTS_YEAR_LIST + ) - return GeneralStudiesPageResponse.of(academicsEntity, subjectChangesList) + return DegreeRequirementsPageResponse.of(academicsEntity, yearList) } @Transactional - override fun createCourse(studentType: String, request: CourseDto, attachments: List?): CourseDto { + override fun createCourse( + studentType: String, + request: CourseDto, + attachments: List? + ): CourseDto { val enumStudentType = makeStringToAcademicsStudentType(studentType) val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) @@ -142,7 +179,8 @@ class AcademicsServiceImpl( } courseRepository.save(newCourse) - val attachmentResponses = attachmentService.createAttachmentResponses(newCourse.attachments) + val attachmentResponses = + attachmentService.createAttachmentResponses(newCourse.attachments) return CourseDto.of(newCourse, attachmentResponses) } @@ -152,8 +190,13 @@ class AcademicsServiceImpl( val enumStudentType = makeStringToAcademicsStudentType(studentType) val enumLanguageType = LanguageType.makeStringToLanguageType(language) val courseDtoList = - courseRepository.findAllByLanguageAndStudentTypeOrderByNameAsc(enumLanguageType, enumStudentType).map { - val attachmentResponses = attachmentService.createAttachmentResponses(it.attachments) + courseRepository.findAllByLanguageAndStudentTypeOrderByNameAsc( + enumLanguageType, + enumStudentType + ).map { + val attachmentResponses = + attachmentService.createAttachmentResponses(it.attachments) + CourseDto.of(it, attachmentResponses) } return courseDtoList @@ -185,13 +228,16 @@ class AcademicsServiceImpl( } @Transactional(readOnly = true) - override fun readAllScholarship(studentType: String): ScholarshipPageResponse { + override fun readAllScholarship(language: String, studentType: String): ScholarshipPageResponse { + val enumLanguageType = LanguageType.makeStringToLanguageType(language) val enumStudentType = makeStringToAcademicsStudentType(studentType) - val academicsEntity = academicsRepository.findByStudentTypeAndPostType( - enumStudentType, - AcademicsPostType.SCHOLARSHIP - ) + val academicsEntity = + academicsRepository.findByLanguageAndStudentTypeAndPostType( + enumLanguageType, + enumStudentType, + AcademicsPostType.SCHOLARSHIP + ) val scholarshipEntityList = scholarshipRepository.findAllByStudentType(enumStudentType) return ScholarshipPageResponse.of(academicsEntity, scholarshipEntityList) @@ -200,7 +246,7 @@ class AcademicsServiceImpl( @Transactional(readOnly = true) override fun readScholarship(scholarshipId: Long): ScholarshipDto { val scholarship = scholarshipRepository.findByIdOrNull(scholarshipId) - ?: throw CserealException.Csereal404("id: $scholarshipId 에 해당하는 장학제도를 찾을 수 없습니다") + ?: throw CserealException.Csereal404("해당하는 장학제도를 찾을 수 없습니다") return ScholarshipDto.of(scholarship) } @@ -235,7 +281,10 @@ class AcademicsServiceImpl( } @Transactional - override fun migrateCourses(studentType: String, requestList: List): List { + override fun migrateCourses( + studentType: String, + requestList: List + ): List { val enumStudentType = makeStringToAcademicsStudentType(studentType) val list = mutableListOf() for (request in requestList) { 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 d4e0f771..7ac5a09d 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 @@ -30,13 +30,17 @@ class ProfessorController( } @GetMapping("/active") - fun getActiveProfessors(): ResponseEntity { - return ResponseEntity.ok(professorService.getActiveProfessors()) + fun getActiveProfessors( + @RequestParam(required = false, defaultValue = "ko") language: String + ): ResponseEntity { + return ResponseEntity.ok(professorService.getActiveProfessors(language)) } @GetMapping("/inactive") - fun getInactiveProfessors(): ResponseEntity> { - return ResponseEntity.ok(professorService.getInactiveProfessors()) + fun getInactiveProfessors( + @RequestParam(required = false, defaultValue = "ko") language: String + ): ResponseEntity> { + return ResponseEntity.ok(professorService.getInactiveProfessors(language)) } @AuthenticatedStaff @@ -46,7 +50,9 @@ class ProfessorController( @RequestPart("request") updateProfessorRequest: ProfessorDto, @RequestPart("mainImage") mainImage: MultipartFile? ): ResponseEntity { - return ResponseEntity.ok(professorService.updateProfessor(professorId, updateProfessorRequest, mainImage)) + return ResponseEntity.ok( + professorService.updateProfessor(professorId, updateProfessorRequest, mainImage) + ) } @AuthenticatedStaff @@ -63,7 +69,7 @@ class ProfessorController( return ResponseEntity.ok(professorService.migrateProfessors(requestList)) } - @PatchMapping("/migragteImage/{professorId}") + @PatchMapping("/migrateImage/{professorId}") fun migrateProfessorImage( @PathVariable professorId: Long, @RequestPart("mainImage") mainImage: MultipartFile 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 f190d236..d11b54c3 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 @@ -1,8 +1,15 @@ package com.wafflestudio.csereal.core.member.database +import com.wafflestudio.csereal.common.properties.LanguageType import org.springframework.data.jpa.repository.JpaRepository interface ProfessorRepository : JpaRepository { - fun findByStatus(status: ProfessorStatus): List - fun findByStatusNot(status: ProfessorStatus): List + fun findByLanguageAndStatus( + languageType: LanguageType, + status: ProfessorStatus + ): List + fun findByLanguageAndStatusNot( + languageType: LanguageType, + status: ProfessorStatus + ): List } 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 a0eb66fa..d064b7f8 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 @@ -20,8 +20,8 @@ import org.springframework.web.multipart.MultipartFile interface ProfessorService { fun createProfessor(createProfessorRequest: ProfessorDto, mainImage: MultipartFile?): ProfessorDto fun getProfessor(professorId: Long): ProfessorDto - fun getActiveProfessors(): ProfessorPageDto - fun getInactiveProfessors(): List + fun getActiveProfessors(language: String): ProfessorPageDto + fun getInactiveProfessors(language: String): List fun updateProfessor( professorId: Long, updateProfessorRequest: ProfessorDto, @@ -40,7 +40,10 @@ class ProfessorServiceImpl( private val mainImageService: MainImageService, private val applicationEventPublisher: ApplicationEventPublisher ) : ProfessorService { - override fun createProfessor(createProfessorRequest: ProfessorDto, mainImage: MultipartFile?): ProfessorDto { + override fun createProfessor( + createProfessorRequest: ProfessorDto, + mainImage: MultipartFile? + ): ProfessorDto { val enumLanguageType = LanguageType.makeStringToLanguageType(createProfessorRequest.language) val professor = ProfessorEntity.of(enumLanguageType, createProfessorRequest) if (createProfessorRequest.labId != null) { @@ -89,24 +92,35 @@ 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을 개최하여 학부 내 외국인 구성원의 화합과 생활의 불편함을 최소화하는 등 학부 차원에서 최선을 다하고 있다." - val professors = professorRepository.findByStatusNot(ProfessorStatus.INACTIVE).map { - val imageURL = mainImageService.createImageURL(it.mainImage) - SimpleProfessorDto.of(it, imageURL) - } - .sortedBy { it.name } + override fun getActiveProfessors(language: String): ProfessorPageDto { + val enumLanguageType = LanguageType.makeStringToLanguageType(language) + val description = "컴퓨터공학부는 35명의 훌륭한 교수진과 최신 시설을 갖추고 400여 명의 학부생과 " + + "350여 명의 대학원생에게 세계 최고 수준의 교육 연구 환경을 제공하고 있다. 2005년에는 서울대학교 " + + "최초로 외국인 정교수인 Robert Ian McKay 교수를 임용한 것을 시작으로 교내에서 가장 국제화가 " + + "활발하게 이루어지고 있는 학부로 평가받고 있다. 현재 훌륭한 외국인 교수님 두 분이 학부 학생들의 " + + "교육 및 연구 지도에 총력을 기울이고 있다.\n\n다수의 외국인 학부생, 대학원생이 재학 중에 있으며 매" + + " 학기 전공 필수 과목을 비롯한 30% 이상의 과목이 영어로 개설되고 있어 외국인 학생의 학업을 돕는 " + + "동시에 한국인 학생이 세계로 진출하는 초석이 되고 있다. 또한 CSE int’l Luncheon을 개최하여 " + + "학부 내 외국인 구성원의 화합과 생활의 불편함을 최소화하는 등 학부 차원에서 최선을 다하고 있다." + val professors = + professorRepository.findByLanguageAndStatusNot( + enumLanguageType, + ProfessorStatus.INACTIVE + ).map { + val imageURL = mainImageService.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 { + override fun getInactiveProfessors(language: String): List { + val enumLanguageType = LanguageType.makeStringToLanguageType(language) + return professorRepository.findByLanguageAndStatus( + enumLanguageType, + ProfessorStatus.INACTIVE + ).map { val imageURL = mainImageService.createImageURL(it.mainImage) SimpleProfessorDto.of(it, imageURL) } @@ -125,7 +139,9 @@ class ProfessorServiceImpl( if (updateProfessorRequest.labId != null && updateProfessorRequest.labId != professor.lab?.id) { val lab = labRepository.findByIdOrNull(updateProfessorRequest.labId) - ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다. LabId: ${updateProfessorRequest.labId}") + ?: throw CserealException.Csereal404( + "해당 연구실을 찾을 수 없습니다. LabId: ${updateProfessorRequest.labId}" + ) professor.addLab(lab) } @@ -205,7 +221,9 @@ class ProfessorServiceImpl( val professor = ProfessorEntity.of(enumLanguageType, request) if (request.labName != null) { val lab = labRepository.findByName(request.labName) - ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다. LabName: ${request.labName}") + ?: throw CserealException.Csereal404( + "해당 연구실을 찾을 수 없습니다. LabName: ${request.labName}" + ) professor.addLab(lab) } 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 7ee21f75..4c01c3d3 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 @@ -28,13 +28,17 @@ class ResearchController( } @GetMapping("/groups") - fun readAllResearchGroups(): ResponseEntity { - return ResponseEntity.ok(researchService.readAllResearchGroups()) + fun readAllResearchGroups( + @RequestParam(required = false, defaultValue = "ko") language: String + ): ResponseEntity { + return ResponseEntity.ok(researchService.readAllResearchGroups(language)) } @GetMapping("/centers") - fun readAllResearchCenters(): ResponseEntity> { - return ResponseEntity.ok(researchService.readAllResearchCenters()) + fun readAllResearchCenters( + @RequestParam(required = false, defaultValue = "ko") language: String + ): ResponseEntity> { + return ResponseEntity.ok(researchService.readAllResearchCenters(language)) } @AuthenticatedStaff @@ -62,8 +66,10 @@ class ResearchController( } @GetMapping("/labs") - fun readAllLabs(): ResponseEntity> { - return ResponseEntity.ok(researchService.readAllLabs()) + fun readAllLabs( + @RequestParam(required = false, defaultValue = "ko") language: String + ): ResponseEntity> { + return ResponseEntity.ok(researchService.readAllLabs(language)) } @GetMapping("/lab/{labId}") 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 abbe4372..71a26786 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 @@ -1,8 +1,9 @@ package com.wafflestudio.csereal.core.research.database +import com.wafflestudio.csereal.common.properties.LanguageType import org.springframework.data.jpa.repository.JpaRepository interface LabRepository : JpaRepository { - fun findAllByOrderByName(): List + fun findAllByLanguageOrderByName(languageType: LanguageType): List fun findByName(name: String): LabEntity? } 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 a91ac5af..b4dd7323 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 @@ -1,8 +1,12 @@ package com.wafflestudio.csereal.core.research.database +import com.wafflestudio.csereal.common.properties.LanguageType import org.springframework.data.jpa.repository.JpaRepository interface ResearchRepository : JpaRepository { fun findByName(name: String): ResearchEntity? - fun findAllByPostTypeOrderByName(postType: ResearchPostType): List + fun findAllByPostTypeAndLanguageOrderByName( + postType: ResearchPostType, + languageType: LanguageType + ): List } 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 59c473e3..4e700af0 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 @@ -21,8 +21,8 @@ interface ResearchService { attachments: List? ): ResearchDto - fun readAllResearchGroups(): ResearchGroupResponse - fun readAllResearchCenters(): List + fun readAllResearchGroups(language: String): ResearchGroupResponse + fun readAllResearchCenters(language: String): List fun updateResearchDetail( researchId: Long, request: ResearchDto, @@ -31,7 +31,7 @@ interface ResearchService { ): ResearchDto fun createLab(request: LabDto, pdf: MultipartFile?): LabDto - fun readAllLabs(): List + fun readAllLabs(language: String): List fun readLab(labId: Long): LabDto fun updateLab(labId: Long, request: LabUpdateRequest, pdf: MultipartFile?): LabDto fun migrateResearchDetail(requestList: List): List @@ -84,37 +84,49 @@ class ResearchServiceImpl( researchRepository.save(newResearch) val imageURL = mainImageService.createImageURL(newResearch.mainImage) - val attachmentResponses = attachmentService.createAttachmentResponses(newResearch.attachments) + val attachmentResponses = + attachmentService.createAttachmentResponses(newResearch.attachments) return ResearchDto.of(newResearch, imageURL, attachmentResponses) } @Transactional(readOnly = true) - override fun readAllResearchGroups(): ResearchGroupResponse { + override fun readAllResearchGroups(language: String): ResearchGroupResponse { // Todo: description 수정 필요 val description = "세계가 주목하는 컴퓨터공학부의 많은 교수들은 ACM, IEEE 등 " + - "세계적인 컴퓨터관련 주요 학회에서 국제학술지 편집위원, 국제학술회의 위원장, 기조연설자 등으로 활발하게 활동하고 있습니다. " + - "정부 지원과제, 민간 산업체 지원 연구과제 등도 성공적으로 수행, 우수한 성과들을 내놓고 있으며, " + - "오늘도 인류가 꿈꾸는 행복하고 편리한 세상을 위해 변화와 혁신, 연구와 도전을 계속하고 있습니다." - - val researchGroups = researchRepository.findAllByPostTypeOrderByName(ResearchPostType.GROUPS).map { - val imageURL = mainImageService.createImageURL(it.mainImage) - val attachmentResponses = attachmentService.createAttachmentResponses(it.attachments) - - ResearchDto.of(it, imageURL, attachmentResponses) - } + "세계적인 컴퓨터관련 주요 학회에서 국제학술지 편집위원, 국제학술회의 위원장, " + + "기조연설자 등으로 활발하게 활동하고 있습니다. 정부 지원과제, 민간 산업체 지원 " + + "연구과제 등도 성공적으로 수행, 우수한 성과들을 내놓고 있으며, 오늘도 인류가 " + + "꿈꾸는 행복하고 편리한 세상을 위해 변화와 혁신, 연구와 도전을 계속하고 있습니다." + + val enumLanguageType = LanguageType.makeStringToLanguageType(language) + val researchGroups = + researchRepository.findAllByPostTypeAndLanguageOrderByName( + ResearchPostType.GROUPS, + enumLanguageType + ).map { + val imageURL = mainImageService.createImageURL(it.mainImage) + val attachmentResponses = attachmentService.createAttachmentResponses(it.attachments) + + ResearchDto.of(it, imageURL, attachmentResponses) + } return ResearchGroupResponse(description, researchGroups) } @Transactional(readOnly = true) - override fun readAllResearchCenters(): List { - val researchCenters = researchRepository.findAllByPostTypeOrderByName(ResearchPostType.CENTERS).map { - val imageURL = mainImageService.createImageURL(it.mainImage) - val attachmentResponses = attachmentService.createAttachmentResponses(it.attachments) - - ResearchDto.of(it, imageURL, attachmentResponses) - } + override fun readAllResearchCenters(language: String): List { + val enumLanguageType = LanguageType.makeStringToLanguageType(language) + val researchCenters = + researchRepository.findAllByPostTypeAndLanguageOrderByName( + ResearchPostType.CENTERS, + enumLanguageType + ).map { + val imageURL = mainImageService.createImageURL(it.mainImage) + val attachmentResponses = attachmentService.createAttachmentResponses(it.attachments) + + ResearchDto.of(it, imageURL, attachmentResponses) + } return researchCenters } @@ -211,8 +223,9 @@ class ResearchServiceImpl( } @Transactional(readOnly = true) - override fun readAllLabs(): List { - val labs = labRepository.findAllByOrderByName().map { + override fun readAllLabs(language: String): List { + val enumLanguageType = LanguageType.makeStringToLanguageType(language) + val labs = labRepository.findAllByLanguageOrderByName(enumLanguageType).map { var pdfURL = "" if (it.pdf != null) { pdfURL = createPdfURL(it.pdf!!) From 0ee8536c4644bdf74d0c5cc64a443fcebebc8472 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 Mar 2024 13:20:53 +0900 Subject: [PATCH 155/214] Refactor: academics search (#200) --- .../core/academics/api/AcademicsController.kt | 20 +++- .../api/res/AcademicsSearchResBody.kt | 26 ++++++ .../api/res/AcademicsSearchResElement.kt | 92 +++++++++++++++++++ .../database/AcademicsSearchEntity.kt | 7 ++ .../database/AcademicsSearchRepository.kt | 33 ++++--- .../dto/AcademicsSearchPageResponse.kt | 18 ---- .../dto/AcademicsSearchResponseElement.kt | 43 --------- .../dto/AcademicsSearchTopResponse.kt | 15 --- .../service/AcademicsSearchService.kt | 56 ++++++++--- 9 files changed, 202 insertions(+), 108 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/api/res/AcademicsSearchResBody.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/api/res/AcademicsSearchResElement.kt delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchPageResponse.kt delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchResponseElement.kt delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchTopResponse.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 89f907c5..06d8a5cc 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,11 +1,13 @@ package com.wafflestudio.csereal.core.academics.api import com.wafflestudio.csereal.common.aop.AuthenticatedStaff +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.academics.dto.* import com.wafflestudio.csereal.core.academics.service.AcademicsService import com.wafflestudio.csereal.core.academics.dto.ScholarshipDto import com.wafflestudio.csereal.core.academics.service.AcademicsSearchService import jakarta.validation.Valid +import jakarta.validation.constraints.Positive import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile @@ -156,20 +158,28 @@ class AcademicsController( @GetMapping("/search/top") fun searchTop( @RequestParam(required = true) keyword: String, - @RequestParam(required = true) number: Int + @RequestParam(required = true) @Valid @Positive number: Int, + @RequestParam(required = true, defaultValue = "ko") language: String, + @RequestParam(required = false, defaultValue = "30") amount: Int ) = academicsSearchService.searchTopAcademics( keyword = keyword, - number = number + language = LanguageType.makeStringToLanguageType(language), + number = number, + amount = amount ) @GetMapping("/search") fun searchAcademics( @RequestParam(required = true) keyword: String, - @RequestParam(required = true) pageSize: Int, - @RequestParam(required = true) pageNum: Int + @RequestParam(required = true) @Valid @Positive pageSize: Int, + @RequestParam(required = true) @Valid @Positive pageNum: Int, + @RequestParam(required = true, defaultValue = "ko") language: String, + @RequestParam(required = false, defaultValue = "30") amount: Int ) = academicsSearchService.searchAcademics( keyword = keyword, + language = LanguageType.makeStringToLanguageType(language), pageSize = pageSize, - pageNum = pageNum + pageNum = pageNum, + amount = amount ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/res/AcademicsSearchResBody.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/res/AcademicsSearchResBody.kt new file mode 100644 index 00000000..6cf2e59d --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/res/AcademicsSearchResBody.kt @@ -0,0 +1,26 @@ +package com.wafflestudio.csereal.core.academics.api.res + +import com.wafflestudio.csereal.core.academics.database.AcademicsSearchEntity + +data class AcademicsSearchResBody( + val results: List, + val total: Long +) { + companion object { + fun of( + total: Long, + academics: List, + keyword: String, + amount: Int + ) = AcademicsSearchResBody( + results = academics.map { + AcademicsSearchResElement.of( + it, + keyword, + amount + ) + }, + total = total + ) + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/res/AcademicsSearchResElement.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/res/AcademicsSearchResElement.kt new file mode 100644 index 00000000..8958fdcf --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/res/AcademicsSearchResElement.kt @@ -0,0 +1,92 @@ +package com.wafflestudio.csereal.core.academics.api.res + +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.utils.substringAroundKeyword +import com.wafflestudio.csereal.core.academics.database.AcademicsSearchEntity +import com.wafflestudio.csereal.core.academics.database.AcademicsSearchType + +data class AcademicsSearchResElement( + val id: Long, + val language: String, + val name: String, + val academicsType: AcademicsSearchType, + val partialDescription: String, + val boldStartIndex: Int, + val boldEndIndex: Int +) { + companion object { + fun of( + academicsSearch: AcademicsSearchEntity, + keyword: String, + amount: Int + ): AcademicsSearchResElement { + return when { + academicsSearch.academics != null && + academicsSearch.course == null && + academicsSearch.scholarship == null -> { + val (startIdx, partialDescription) = substringAroundKeyword( + keyword, + academicsSearch.content, + amount + ) + AcademicsSearchResElement( + id = academicsSearch.academics!!.id, + name = academicsSearch.academics!!.name, + language = academicsSearch.academics!!.language.let { + LanguageType.makeLowercase(it) + }, + academicsType = AcademicsSearchType.ACADEMICS, + partialDescription = partialDescription.replace("\n", " "), + boldStartIndex = startIdx ?: 0, + boldEndIndex = startIdx?.plus(keyword.length) ?: 0 + ) + } + + academicsSearch.academics == null && + academicsSearch.course != null && + academicsSearch.scholarship == null -> { + val (startIdx, partialDescription) = substringAroundKeyword( + keyword, + academicsSearch.content, + amount + ) + AcademicsSearchResElement( + id = academicsSearch.course!!.id, + name = academicsSearch.course!!.name, + language = academicsSearch.course!!.language.let { + LanguageType.makeLowercase(it) + }, + academicsType = AcademicsSearchType.COURSE, + partialDescription = partialDescription.replace("\n", " "), + boldStartIndex = startIdx ?: 0, + boldEndIndex = startIdx?.plus(keyword.length) ?: 0 + ) + } + + academicsSearch.academics == null && + academicsSearch.course == null && + academicsSearch.scholarship != null -> { + val (startIdx, partialDescription) = substringAroundKeyword( + keyword, + academicsSearch.content, + amount + ) + AcademicsSearchResElement( + id = academicsSearch.scholarship!!.id, + name = academicsSearch.scholarship!!.name, + language = academicsSearch.scholarship!!.language.let { + LanguageType.makeLowercase(it) + }, + academicsType = AcademicsSearchType.SCHOLARSHIP, + partialDescription = partialDescription.replace("\n", " "), + boldStartIndex = startIdx ?: 0, + boldEndIndex = startIdx?.plus(keyword.length) ?: 0 + ) + } + + else -> throw CserealException.Csereal401("AcademicsSearchEntity의 연결이 올바르지 않습니다.") + } + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchEntity.kt index 6b4d59ce..1cd8ea7b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchEntity.kt @@ -1,6 +1,7 @@ package com.wafflestudio.csereal.core.academics.database import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.common.utils.cleanTextFromHtml import jakarta.persistence.* @@ -9,6 +10,9 @@ class AcademicsSearchEntity( @Column(columnDefinition = "TEXT", nullable = false) var content: String, + @Enumerated(value = EnumType.STRING) + val language: LanguageType, + @OneToOne @JoinColumn(name = "academics_id") val academics: AcademicsEntity? = null, @@ -26,6 +30,7 @@ class AcademicsSearchEntity( fun create(academics: AcademicsEntity): AcademicsSearchEntity { return AcademicsSearchEntity( academics = academics, + language = academics.language, content = createContent(academics) ) } @@ -33,6 +38,7 @@ class AcademicsSearchEntity( fun create(course: CourseEntity): AcademicsSearchEntity { return AcademicsSearchEntity( course = course, + language = course.language, content = createContent(course) ) } @@ -40,6 +46,7 @@ class AcademicsSearchEntity( fun create(scholarship: ScholarshipEntity): AcademicsSearchEntity { return AcademicsSearchEntity( scholarship = scholarship, + language = scholarship.language, content = createContent(scholarship) ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchRepository.kt index cd16fb10..df24e29a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchRepository.kt @@ -2,20 +2,25 @@ package com.wafflestudio.csereal.core.academics.database import com.querydsl.jpa.impl.JPAQuery import com.querydsl.jpa.impl.JPAQueryFactory +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.common.repository.CommonRepository +import com.wafflestudio.csereal.common.utils.exchangePageNum import com.wafflestudio.csereal.core.academics.database.QAcademicsEntity.academicsEntity import com.wafflestudio.csereal.core.academics.database.QAcademicsSearchEntity.academicsSearchEntity import com.wafflestudio.csereal.core.academics.database.QCourseEntity.courseEntity import com.wafflestudio.csereal.core.academics.database.QScholarshipEntity.scholarshipEntity import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository -import com.wafflestudio.csereal.common.utils.exchangePageNum interface AcademicsSearchRepository : JpaRepository, AcademicsSearchCustomRepository interface AcademicsSearchCustomRepository { - fun searchAcademics(keyword: String, pageSize: Int, pageNum: Int): Pair, Long> - fun searchTopAcademics(keyword: String, number: Int): List + fun searchAcademics( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int + ): Pair, Long> } @Repository @@ -23,22 +28,18 @@ class AcademicsSearchCustomRepositoryImpl( private val queryFactory: JPAQueryFactory, private val commonRepository: CommonRepository ) : AcademicsSearchCustomRepository { - override fun searchTopAcademics(keyword: String, number: Int): List { - return searchQuery(keyword) - .limit(number.toLong()) - .fetch() - } - override fun searchAcademics( keyword: String, + language: LanguageType, pageSize: Int, pageNum: Int ): Pair, Long> { - val query = searchQuery(keyword) - val total = getSearchCount(keyword) + val query = searchQuery(keyword, language) + val total = getSearchCount(keyword, language) val validPageNum = exchangePageNum(pageSize, pageNum, total) val validOffset = (if (validPageNum >= 1) validPageNum - 1 else 0) * pageSize.toLong() + val queryResult = query.offset(validOffset) .limit(pageSize.toLong()) .fetch() @@ -46,7 +47,7 @@ class AcademicsSearchCustomRepositoryImpl( return queryResult to total } - fun searchQuery(keyword: String): JPAQuery { + fun searchQuery(keyword: String, language: LanguageType): JPAQuery { val searchDoubleTemplate = commonRepository.searchFullSingleTextTemplate( keyword, academicsSearchEntity.content @@ -67,11 +68,12 @@ class AcademicsSearchCustomRepositoryImpl( scholarshipEntity ).fetchJoin() .where( - searchDoubleTemplate.gt(0.0) + searchDoubleTemplate.gt(0.0), + academicsSearchEntity.language.eq(language) ) } - fun getSearchCount(keyword: String): Long { + fun getSearchCount(keyword: String, language: LanguageType): Long { val searchDoubleTemplate = commonRepository.searchFullSingleTextTemplate( keyword, academicsSearchEntity.content @@ -81,7 +83,8 @@ class AcademicsSearchCustomRepositoryImpl( academicsSearchEntity.countDistinct() ).from(academicsSearchEntity) .where( - searchDoubleTemplate.gt(0.0) + searchDoubleTemplate.gt(0.0), + academicsSearchEntity.language.eq(language) ).fetchOne()!! } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchPageResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchPageResponse.kt deleted file mode 100644 index 36b6b79f..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchPageResponse.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.wafflestudio.csereal.core.academics.dto - -import com.wafflestudio.csereal.core.academics.database.AcademicsSearchEntity - -data class AcademicsSearchPageResponse( - val academics: List, - val total: Long -) { - companion object { - fun of( - academics: List, - total: Long - ) = AcademicsSearchPageResponse( - academics = academics.map(AcademicsSearchResponseElement::of), - total = total - ) - } -} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchResponseElement.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchResponseElement.kt deleted file mode 100644 index 7854a72c..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchResponseElement.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.wafflestudio.csereal.core.academics.dto - -import com.wafflestudio.csereal.common.CserealException -import com.wafflestudio.csereal.core.academics.database.AcademicsSearchEntity -import com.wafflestudio.csereal.core.academics.database.AcademicsSearchType - -data class AcademicsSearchResponseElement( - val id: Long, - val name: String, - val academicsType: AcademicsSearchType -) { - companion object { - fun of(academicsSearch: AcademicsSearchEntity): AcademicsSearchResponseElement { - return when { - academicsSearch.academics != null && - academicsSearch.course == null && - academicsSearch.scholarship == null -> - AcademicsSearchResponseElement( - id = academicsSearch.academics!!.id, - name = academicsSearch.academics!!.name, - academicsType = AcademicsSearchType.ACADEMICS - ) - academicsSearch.academics == null && - academicsSearch.course != null && - academicsSearch.scholarship == null -> - AcademicsSearchResponseElement( - id = academicsSearch.course!!.id, - name = academicsSearch.course!!.name, - academicsType = AcademicsSearchType.COURSE - ) - academicsSearch.academics == null && - academicsSearch.course == null && - academicsSearch.scholarship != null -> - AcademicsSearchResponseElement( - id = academicsSearch.scholarship!!.id, - name = academicsSearch.scholarship!!.name, - academicsType = AcademicsSearchType.SCHOLARSHIP - ) - else -> throw CserealException.Csereal401("AcademicsSearchEntity의 연결이 올바르지 않습니다.") - } - } - } -} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchTopResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchTopResponse.kt deleted file mode 100644 index 232b2fe2..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchTopResponse.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.wafflestudio.csereal.core.academics.dto - -import com.wafflestudio.csereal.core.academics.database.AcademicsSearchEntity - -data class AcademicsSearchTopResponse( - val topAcademics: List -) { - companion object { - fun of( - topAcademics: List - ) = AcademicsSearchTopResponse( - topAcademics = topAcademics.map(AcademicsSearchResponseElement::of) - ) - } -} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsSearchService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsSearchService.kt index eb2738b2..63a0b3dc 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsSearchService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsSearchService.kt @@ -1,14 +1,25 @@ package com.wafflestudio.csereal.core.academics.service +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.academics.database.AcademicsSearchRepository -import com.wafflestudio.csereal.core.academics.dto.AcademicsSearchPageResponse -import com.wafflestudio.csereal.core.academics.dto.AcademicsSearchTopResponse +import com.wafflestudio.csereal.core.academics.api.res.AcademicsSearchResBody import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional interface AcademicsSearchService { - fun searchAcademics(keyword: String, pageSize: Int, pageNum: Int): AcademicsSearchPageResponse - fun searchTopAcademics(keyword: String, number: Int): AcademicsSearchTopResponse + fun searchTopAcademics( + keyword: String, + language: LanguageType, + number: Int, + amount: Int + ): AcademicsSearchResBody + fun searchAcademics( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int, + amount: Int + ): AcademicsSearchResBody } @Service @@ -16,24 +27,45 @@ class AcademicsSearchServiceImpl( private val academicsSearchRepository: AcademicsSearchRepository ) : AcademicsSearchService { @Transactional(readOnly = true) - override fun searchTopAcademics(keyword: String, number: Int) = - AcademicsSearchTopResponse.of( - academicsSearchRepository.searchTopAcademics( + override fun searchTopAcademics( + keyword: String, + language: LanguageType, + number: Int, + amount: Int + ) = + academicsSearchRepository.searchAcademics( + keyword = keyword, + language = language, + pageSize = number, + pageNum = 1 + ).let { (acds, total) -> + AcademicsSearchResBody.of( + total = total, + academics = acds, keyword = keyword, - number = number + amount = amount ) - ) + } @Transactional(readOnly = true) - override fun searchAcademics(keyword: String, pageSize: Int, pageNum: Int) = + override fun searchAcademics( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int, + amount: Int + ) = academicsSearchRepository.searchAcademics( keyword = keyword, + language = language, pageSize = pageSize, pageNum = pageNum ).let { - AcademicsSearchPageResponse.of( + AcademicsSearchResBody.of( academics = it.first, - total = it.second + total = it.second, + keyword = keyword, + amount = amount ) } } From 385476eb54b66b18a88316cd3e3baeb7d88441e6 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 Mar 2024 13:25:40 +0900 Subject: [PATCH 156/214] Refactor: research search (#201) --- .../core/research/api/ResearchController.kt | 30 +++++-- .../research/api/res/ResearchSearchResBody.kt | 22 +++++ .../api/res/ResearchSearchResElement.kt | 90 +++++++++++++++++++ .../research/database/ResearchSearchEntity.kt | 7 ++ .../database/ResearchSearchRepository.kt | 46 +++++----- .../dto/ResearchSearchPageResponse.kt | 18 ---- .../dto/ResearchSearchResponseElement.kt | 55 ------------ .../research/dto/ResearchSearchTopResponse.kt | 15 ---- .../research/service/ResearchSearchService.kt | 49 +++++++--- .../reseach/service/ResearchServiceTest.kt | 4 + 10 files changed, 208 insertions(+), 128 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/research/api/res/ResearchSearchResBody.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/research/api/res/ResearchSearchResElement.kt delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchSearchPageResponse.kt delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchSearchResponseElement.kt delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchSearchTopResponse.kt 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 4c01c3d3..87407f66 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,10 +1,15 @@ package com.wafflestudio.csereal.core.research.api import com.wafflestudio.csereal.common.aop.AuthenticatedStaff -import com.wafflestudio.csereal.core.research.dto.* +import com.wafflestudio.csereal.common.properties.LanguageType +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.ResearchSearchService import com.wafflestudio.csereal.core.research.service.ResearchService import jakarta.validation.Valid +import jakarta.validation.constraints.Positive import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile @@ -136,13 +141,28 @@ class ResearchController( @GetMapping("/search/top") fun searchTop( @RequestParam(required = true) keyword: String, - @RequestParam(required = true) number: Int - ) = researchSearchService.searchTopResearch(keyword, number) + @RequestParam(required = true) @Valid @Positive number: Int, + @RequestParam(required = true, defaultValue = "ko") language: String, + @RequestParam(required = false, defaultValue = "30") @Valid @Positive amount: Int + ) = researchSearchService.searchTopResearch( + keyword, + LanguageType.makeStringToLanguageType(language), + number, + amount + ) @GetMapping("/search") fun searchPage( @RequestParam(required = true) keyword: String, @RequestParam(required = true) pageSize: Int, - @RequestParam(required = true) pageNum: Int - ) = researchSearchService.searchResearch(keyword, pageSize, pageNum) + @RequestParam(required = true) pageNum: Int, + @RequestParam(required = true, defaultValue = "ko") language: String, + @RequestParam(required = false, defaultValue = "30") @Valid @Positive amount: Int + ) = researchSearchService.searchResearch( + keyword, + LanguageType.makeStringToLanguageType(language), + pageSize, + pageNum, + amount + ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/api/res/ResearchSearchResBody.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/res/ResearchSearchResBody.kt new file mode 100644 index 00000000..04616d92 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/res/ResearchSearchResBody.kt @@ -0,0 +1,22 @@ +package com.wafflestudio.csereal.core.research.api.res + +import com.wafflestudio.csereal.core.research.database.ResearchSearchEntity + +data class ResearchSearchResBody( + val results: List, + val total: Long +) { + companion object { + fun of( + researches: List, + keyword: String, + amount: Int, + total: Long + ) = ResearchSearchResBody( + results = researches.map { + ResearchSearchResElement.of(it, keyword, amount) + }, + total = total + ) + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/api/res/ResearchSearchResElement.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/res/ResearchSearchResElement.kt new file mode 100644 index 00000000..6a3f825e --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/res/ResearchSearchResElement.kt @@ -0,0 +1,90 @@ +package com.wafflestudio.csereal.core.research.api.res + +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.utils.substringAroundKeyword +import com.wafflestudio.csereal.core.research.database.ResearchSearchEntity +import com.wafflestudio.csereal.core.research.database.ResearchSearchType + +data class ResearchSearchResElement( + val id: Long, + val language: String, + val name: String, + val researchType: ResearchSearchType, + val partialDescription: String, + val boldStartIdx: Int, + val boldEndIdx: Int +) { + companion object { + fun of( + researchSearchEntity: ResearchSearchEntity, + keyword: String, + amount: Int + ): ResearchSearchResElement = + when { + researchSearchEntity.research != null && + researchSearchEntity.lab == null && + researchSearchEntity.conferenceElement == null + -> researchSearchEntity.research!!.let { + val (startIdx, partialDesc) = substringAroundKeyword( + keyword, + researchSearchEntity.content, + amount + ) + ResearchSearchResElement( + id = it.id, + name = it.name, + language = it.language.let { ln -> LanguageType.makeLowercase(ln) }, + researchType = ResearchSearchType.RESEARCH, + partialDescription = partialDesc, + boldStartIdx = startIdx ?: 0, + boldEndIdx = startIdx?.plus(keyword.length) ?: 0 + ) + } + + researchSearchEntity.lab != null && + researchSearchEntity.research == null && + researchSearchEntity.conferenceElement == null + -> researchSearchEntity.lab!!.let { + val (startIdx, partialDesc) = substringAroundKeyword( + keyword, + researchSearchEntity.content, + amount + ) + ResearchSearchResElement( + id = it.id, + name = it.name, + language = it.language.let { ln -> LanguageType.makeLowercase(ln) }, + researchType = ResearchSearchType.LAB, + partialDescription = partialDesc, + boldStartIdx = startIdx ?: 0, + boldEndIdx = startIdx?.plus(keyword.length) ?: 0 + ) + } + + researchSearchEntity.conferenceElement != null && + researchSearchEntity.research == null && + researchSearchEntity.lab == null + -> researchSearchEntity.conferenceElement!!.let { + val (startIdx, partialDesc) = substringAroundKeyword( + keyword, + researchSearchEntity.content, + amount + ) + ResearchSearchResElement( + id = it.id, + name = it.name, + language = it.language.let { ln -> LanguageType.makeLowercase(ln) }, + researchType = ResearchSearchType.CONFERENCE, + partialDescription = partialDesc, + boldStartIdx = startIdx ?: 0, + boldEndIdx = startIdx?.plus(keyword.length) ?: 0 + ) + } + + else -> throw CserealException.Csereal401( + "ResearchSearchEntity의 연결이 올바르지 않습니다." + ) + } + } +} 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 96dea54f..d07c5ae7 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 @@ -1,6 +1,7 @@ package com.wafflestudio.csereal.core.research.database import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.common.utils.cleanTextFromHtml import com.wafflestudio.csereal.core.conference.database.ConferenceEntity import jakarta.persistence.* @@ -10,6 +11,9 @@ class ResearchSearchEntity( @Column(columnDefinition = "TEXT") var content: String, + @Enumerated(value = EnumType.STRING) + val language: LanguageType, + @OneToOne @JoinColumn(name = "research_id") val research: ResearchEntity? = null, @@ -26,6 +30,7 @@ class ResearchSearchEntity( fun create(research: ResearchEntity): ResearchSearchEntity { return ResearchSearchEntity( content = createContent(research), + language = research.language, research = research ) } @@ -33,6 +38,7 @@ class ResearchSearchEntity( fun create(lab: LabEntity): ResearchSearchEntity { return ResearchSearchEntity( content = createContent(lab), + language = lab.language, lab = lab ) } @@ -40,6 +46,7 @@ class ResearchSearchEntity( fun create(conference: ConferenceEntity): ResearchSearchEntity { return ResearchSearchEntity( content = createContent(conference), + language = conference.language, conferenceElement = 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 b9bf2b6d..3c82b593 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 @@ -2,7 +2,9 @@ package com.wafflestudio.csereal.core.research.database import com.querydsl.jpa.impl.JPAQuery import com.querydsl.jpa.impl.JPAQueryFactory +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.common.repository.CommonRepository +import com.wafflestudio.csereal.common.utils.exchangePageNum import com.wafflestudio.csereal.core.conference.database.QConferenceEntity.conferenceEntity import com.wafflestudio.csereal.core.research.database.QLabEntity.labEntity import com.wafflestudio.csereal.core.research.database.QResearchEntity.researchEntity @@ -13,9 +15,12 @@ import org.springframework.stereotype.Repository interface ResearchSearchRepository : JpaRepository, ResearchSearchRepositoryCustom interface ResearchSearchRepositoryCustom { - fun searchTopResearch(keyword: String, number: Int): List - - fun searchResearch(keyword: String, pageSize: Int, pageNum: Int): Pair, Long> + fun searchResearch( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int + ): Pair, Long> } @Repository @@ -23,15 +28,14 @@ class ResearchSearchRepositoryCustomImpl( private val queryFactory: JPAQueryFactory, private val commonRepository: CommonRepository ) : ResearchSearchRepositoryCustom { - override fun searchTopResearch(keyword: String, number: Int): List { - return searchQuery(keyword) - .limit(number.toLong()) - .fetch() - } - - override fun searchResearch(keyword: String, pageSize: Int, pageNum: Int): Pair, Long> { - val query = searchQuery(keyword) - val total = getSearchCount(keyword) + override fun searchResearch( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int + ): Pair, Long> { + val query = searchQuery(keyword, language) + val total = getSearchCount(keyword, language) val validPageNum = exchangePageNum(pageSize, pageNum, total) val validOffset = (if (validPageNum >= 1) validPageNum - 1 else 0) * pageSize.toLong() @@ -43,7 +47,7 @@ class ResearchSearchRepositoryCustomImpl( return queryResult to total } - fun searchQuery(keyword: String): JPAQuery { + fun searchQuery(keyword: String, language: LanguageType): JPAQuery { val searchDoubleTemplate = commonRepository.searchFullSingleTextTemplate( keyword, researchSearchEntity.content @@ -64,11 +68,12 @@ class ResearchSearchRepositoryCustomImpl( conferenceEntity ).fetchJoin() .where( - searchDoubleTemplate.gt(0.0) + searchDoubleTemplate.gt(0.0), + researchSearchEntity.language.eq(language) ) } - fun getSearchCount(keyword: String): Long { + fun getSearchCount(keyword: String, language: LanguageType): Long { val searchDoubleTemplate = commonRepository.searchFullSingleTextTemplate( keyword, researchSearchEntity.content @@ -80,15 +85,8 @@ class ResearchSearchRepositoryCustomImpl( ).from( researchSearchEntity ).where( - searchDoubleTemplate.gt(0.0) + searchDoubleTemplate.gt(0.0), + researchSearchEntity.language.eq(language) ).fetchOne()!! } - - fun exchangePageNum(pageSize: Int, pageNum: Int, total: Long): Int { - return if ((pageNum - 1) * pageSize < total) { - pageNum - } else { - Math.ceil(total.toDouble() / pageSize).toInt() - } - } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchSearchPageResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchSearchPageResponse.kt deleted file mode 100644 index 59cf3b61..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchSearchPageResponse.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.wafflestudio.csereal.core.research.dto - -import com.wafflestudio.csereal.core.research.database.ResearchSearchEntity - -data class ResearchSearchPageResponse( - val researches: List, - val total: Long -) { - companion object { - fun of( - researches: List, - total: Long - ) = ResearchSearchPageResponse( - researches = researches.map(ResearchSearchResponseElement::of), - total = total - ) - } -} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchSearchResponseElement.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchSearchResponseElement.kt deleted file mode 100644 index ea5e5b37..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchSearchResponseElement.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.wafflestudio.csereal.core.research.dto - -import com.wafflestudio.csereal.common.CserealException -import com.wafflestudio.csereal.core.research.database.ResearchSearchEntity -import com.wafflestudio.csereal.core.research.database.ResearchSearchType - -data class ResearchSearchResponseElement( - val id: Long, - val name: String, - val researchType: ResearchSearchType -) { - companion object { - fun of( - researchSearchEntity: ResearchSearchEntity - ): ResearchSearchResponseElement = - when { - researchSearchEntity.research != null && - researchSearchEntity.lab == null && - researchSearchEntity.conferenceElement == null - -> researchSearchEntity.research!!.let { - ResearchSearchResponseElement( - id = it.id, - name = it.name, - researchType = ResearchSearchType.RESEARCH - ) - } - - researchSearchEntity.lab != null && - researchSearchEntity.research == null && - researchSearchEntity.conferenceElement == null - -> researchSearchEntity.lab!!.let { - ResearchSearchResponseElement( - id = it.id, - name = it.name, - researchType = ResearchSearchType.LAB - ) - } - - researchSearchEntity.conferenceElement != null && - researchSearchEntity.research == null && - researchSearchEntity.lab == null - -> researchSearchEntity.conferenceElement!!.let { - ResearchSearchResponseElement( - id = it.id, - name = it.name, - researchType = ResearchSearchType.CONFERENCE - ) - } - - else -> throw CserealException.Csereal401( - "ResearchSearchEntity의 연결이 올바르지 않습니다." - ) - } - } -} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchSearchTopResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchSearchTopResponse.kt deleted file mode 100644 index 784e2c47..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchSearchTopResponse.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.wafflestudio.csereal.core.research.dto - -import com.wafflestudio.csereal.core.research.database.ResearchSearchEntity - -data class ResearchSearchTopResponse( - val topResearches: List -) { - companion object { - fun of( - topResearches: List - ) = ResearchSearchTopResponse( - topResearches = topResearches.map(ResearchSearchResponseElement::of) - ) - } -} 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 23af349a..8b9b0bac 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 @@ -1,13 +1,13 @@ package com.wafflestudio.csereal.core.research.service +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.member.event.ProfessorCreatedEvent import com.wafflestudio.csereal.core.member.event.ProfessorDeletedEvent import com.wafflestudio.csereal.core.member.event.ProfessorModifiedEvent import com.wafflestudio.csereal.core.research.database.LabRepository import com.wafflestudio.csereal.core.research.database.ResearchSearchEntity import com.wafflestudio.csereal.core.research.database.ResearchSearchRepository -import com.wafflestudio.csereal.core.research.dto.ResearchSearchPageResponse -import com.wafflestudio.csereal.core.research.dto.ResearchSearchTopResponse +import com.wafflestudio.csereal.core.research.api.res.ResearchSearchResBody import org.springframework.context.event.EventListener import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service @@ -18,8 +18,14 @@ interface ResearchSearchService { fun professorDeletedEventListener(professorDeletedEvent: ProfessorDeletedEvent) fun professorModifiedEventListener(professorModifiedEvent: ProfessorModifiedEvent) fun deleteResearchSearch(researchSearchEntity: ResearchSearchEntity) - fun searchTopResearch(keyword: String, number: Int): ResearchSearchTopResponse - fun searchResearch(keyword: String, pageSize: Int, pageNum: Int): ResearchSearchPageResponse + fun searchTopResearch(keyword: String, language: LanguageType, number: Int, amount: Int): ResearchSearchResBody + fun searchResearch( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int, + amount: Int + ): ResearchSearchResBody } @Service @@ -28,15 +34,36 @@ class ResearchSearchServiceImpl( private val researchSearchRepository: ResearchSearchRepository ) : ResearchSearchService { @Transactional(readOnly = true) - override fun searchTopResearch(keyword: String, number: Int): ResearchSearchTopResponse = - ResearchSearchTopResponse.of( - researchSearchRepository.searchTopResearch(keyword, number) - ) + override fun searchTopResearch( + keyword: String, + language: LanguageType, + number: Int, + amount: Int + ): ResearchSearchResBody = + researchSearchRepository.searchResearch(keyword, language, number, 1).let { + ResearchSearchResBody.of( + researches = it.first, + keyword = keyword, + amount = amount, + total = it.second + ) + } @Transactional(readOnly = true) - override fun searchResearch(keyword: String, pageSize: Int, pageNum: Int): ResearchSearchPageResponse = - researchSearchRepository.searchResearch(keyword, pageSize, pageNum).let { - ResearchSearchPageResponse.of(it.first, it.second) + override fun searchResearch( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int, + amount: Int + ): ResearchSearchResBody = + researchSearchRepository.searchResearch(keyword, language, pageSize, pageNum).let { + ResearchSearchResBody.of( + researches = it.first, + keyword = keyword, + amount = amount, + total = it.second + ) } @EventListener 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 fe7a8538..c7e8e91d 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 @@ -17,9 +17,11 @@ 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.test.context.ActiveProfiles import org.springframework.transaction.annotation.Transactional @SpringBootTest +@ActiveProfiles("test") @Transactional class ResearchServiceTest( private val researchService: ResearchService, @@ -79,6 +81,7 @@ class ResearchServiceTest( val research = researchRepository.findByIdOrNull(createdResearchDto.id)!! val researchSearch = research.researchSearch researchSearch shouldNotBe null + researchSearch!!.language shouldBe LanguageType.KO researchSearch!!.content shouldBe """ @@ -243,6 +246,7 @@ class ResearchServiceTest( val lab = labRepository.findByIdOrNull(createdLabDto.id)!! val researchSearch = lab.researchSearch researchSearch shouldNotBe null + researchSearch!!.language shouldBe LanguageType.KO researchSearch!!.content shouldBe """ From ce02d1b4ecaf0faebbde838e1e98ec23d5b9afdc Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Tue, 5 Mar 2024 21:38:34 +0900 Subject: [PATCH 157/214] =?UTF-8?q?fix:=20pageSize=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?(#203)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: pageSize 쿼리 파라미터 추가 * 함수 인자에서 defaultValue 지정 --- .../com/wafflestudio/csereal/core/news/api/NewsController.kt | 2 +- .../wafflestudio/csereal/core/notice/api/NoticeController.kt | 2 +- .../wafflestudio/csereal/core/seminar/api/SeminarController.kt | 2 +- 3 files changed, 3 insertions(+), 3 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 977f3216..b0c3f872 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 @@ -29,6 +29,7 @@ class NewsController( @RequestParam(required = false) tag: List?, @RequestParam(required = false) keyword: String?, @RequestParam(required = false) pageNum: Int?, + @RequestParam(required = false, defaultValue = "10") pageSize: Int, @AuthenticationPrincipal oidcUser: OidcUser? ): ResponseEntity { val isStaff = oidcUser?.let { @@ -37,7 +38,6 @@ class NewsController( user?.role == Role.ROLE_STAFF } ?: false - val pageSize = 10 val usePageBtn = pageNum != null val page = pageNum ?: 1 val pageRequest = PageRequest.of(page - 1, pageSize) 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 4a99ea45..80a0a00b 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 @@ -28,6 +28,7 @@ class NoticeController( @RequestParam(required = false) tag: List?, @RequestParam(required = false) keyword: String?, @RequestParam(required = false) pageNum: Int?, + @RequestParam(required = false, defaultValue = "20") pageSize: Int, @AuthenticationPrincipal oidcUser: OidcUser? ): ResponseEntity { val isStaff = oidcUser?.let { @@ -38,7 +39,6 @@ class NoticeController( val usePageBtn = pageNum != null val page = pageNum ?: 1 - val pageSize = 20 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/seminar/api/SeminarController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt index ea8ec26c..d7705a6d 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 @@ -24,6 +24,7 @@ class SeminarController( fun searchSeminar( @RequestParam(required = false) keyword: String?, @RequestParam(required = false) pageNum: Int?, + @RequestParam(required = false, defaultValue = "10") pageSize: Int, @AuthenticationPrincipal oidcUser: OidcUser? ): ResponseEntity { val isStaff = oidcUser?.let { @@ -32,7 +33,6 @@ class SeminarController( user?.role == Role.ROLE_STAFF } ?: false - val pageSize = 10 val usePageBtn = pageNum != null val page = pageNum ?: 1 val pageRequest = PageRequest.of(page - 1, pageSize) From ca9b4f7496e81518f7bd7f4771513929f091de77 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Sat, 9 Mar 2024 20:08:49 +0900 Subject: [PATCH 158/214] =?UTF-8?q?fix:=20=EC=9D=B8=EC=A6=9D=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=88=98=EC=A0=95=20(#206)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: mock-login 위해서 인증 로직 수정 * add v1 to uri --- .../common/mockauth/DevAuthController.kt | 2 +- .../csereal/core/news/api/NewsController.kt | 16 ++++++++++---- .../core/notice/api/NoticeController.kt | 16 ++++++++++---- .../reservation/api/ReservationController.kt | 16 ++++++++++---- .../core/seminar/api/SeminarController.kt | 16 ++++++++++---- .../csereal/core/user/api/UserController.kt | 21 ++++++++++++------- 6 files changed, 62 insertions(+), 25 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/mockauth/DevAuthController.kt b/src/main/kotlin/com/wafflestudio/csereal/common/mockauth/DevAuthController.kt index 19110220..27319abc 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/mockauth/DevAuthController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/mockauth/DevAuthController.kt @@ -17,7 +17,7 @@ import org.springframework.web.bind.annotation.RestController //TODO: 정식 릴리즈 후에는 dev 서버에서만 가능하게 @RestController -@RequestMapping("/api") +@RequestMapping("/api/v1") class DevAuthController( private val authenticationManager: AuthenticationManager, private val userRepository: UserRepository, 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 b0c3f872..193a097a 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,6 +1,8 @@ package com.wafflestudio.csereal.core.news.api +import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.common.aop.AuthenticatedStaff +import com.wafflestudio.csereal.common.mockauth.CustomPrincipal import com.wafflestudio.csereal.core.news.dto.NewsDto import com.wafflestudio.csereal.core.news.dto.NewsSearchResponse import com.wafflestudio.csereal.core.news.service.NewsService @@ -13,7 +15,7 @@ import org.hibernate.validator.constraints.Length 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.core.Authentication import org.springframework.security.oauth2.core.oidc.user.OidcUser import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile @@ -30,10 +32,16 @@ class NewsController( @RequestParam(required = false) keyword: String?, @RequestParam(required = false) pageNum: Int?, @RequestParam(required = false, defaultValue = "10") pageSize: Int, - @AuthenticationPrincipal oidcUser: OidcUser? + authentication: Authentication? ): ResponseEntity { - val isStaff = oidcUser?.let { - val username = it.idToken.getClaim("username") + val principal = authentication?.principal + + val isStaff = principal?.let { + val username = when (principal) { + is OidcUser -> principal.idToken.getClaim("username") + is CustomPrincipal -> principal.userEntity.username + else -> throw CserealException.Csereal401("Unsupported principal type") + } val user = userRepository.findByUsername(username) user?.role == Role.ROLE_STAFF } ?: false 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 80a0a00b..611d7ba8 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,6 +1,8 @@ package com.wafflestudio.csereal.core.notice.api +import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.common.aop.AuthenticatedStaff +import com.wafflestudio.csereal.common.mockauth.CustomPrincipal import com.wafflestudio.csereal.core.notice.dto.* import com.wafflestudio.csereal.core.notice.service.NoticeService import com.wafflestudio.csereal.core.user.database.Role @@ -12,7 +14,7 @@ import org.hibernate.validator.constraints.Length 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.core.Authentication import org.springframework.security.oauth2.core.oidc.user.OidcUser import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile @@ -29,10 +31,16 @@ class NoticeController( @RequestParam(required = false) keyword: String?, @RequestParam(required = false) pageNum: Int?, @RequestParam(required = false, defaultValue = "20") pageSize: Int, - @AuthenticationPrincipal oidcUser: OidcUser? + authentication: Authentication? ): ResponseEntity { - val isStaff = oidcUser?.let { - val username = it.idToken.getClaim("username") + val principal = authentication?.principal + + val isStaff = principal?.let { + val username = when (principal) { + is OidcUser -> principal.idToken.getClaim("username") + is CustomPrincipal -> principal.userEntity.username + else -> throw CserealException.Csereal401("Unsupported principal type") + } val user = userRepository.findByUsername(username) user?.role == Role.ROLE_STAFF } ?: false 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 c48efc8e..e053955c 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 @@ -1,6 +1,8 @@ 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.mockauth.CustomPrincipal import com.wafflestudio.csereal.core.reservation.dto.ReservationDto import com.wafflestudio.csereal.core.reservation.dto.ReserveRequest import com.wafflestudio.csereal.core.reservation.dto.SimpleReservationDto @@ -8,7 +10,7 @@ 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.core.Authentication import org.springframework.security.oauth2.core.oidc.user.OidcUser import org.springframework.web.bind.annotation.DeleteMapping import org.springframework.web.bind.annotation.GetMapping @@ -54,10 +56,16 @@ class ReservationController( @GetMapping("/{reservationId}") fun getReservation( @PathVariable reservationId: Long, - @AuthenticationPrincipal oidcUser: OidcUser? + authentication: Authentication? ): ResponseEntity { - val isStaff = oidcUser?.let { - val username = it.idToken.getClaim("username") + val principal = authentication?.principal + + val isStaff = principal?.let { + val username = when (principal) { + is OidcUser -> principal.idToken.getClaim("username") + is CustomPrincipal -> principal.userEntity.username + else -> throw CserealException.Csereal401("Unsupported principal type") + } val user = userRepository.findByUsername(username) user?.role == Role.ROLE_STAFF } ?: false 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 d7705a6d..bbf80373 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,8 @@ package com.wafflestudio.csereal.core.seminar.api +import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.common.aop.AuthenticatedStaff +import com.wafflestudio.csereal.common.mockauth.CustomPrincipal import com.wafflestudio.csereal.core.seminar.dto.SeminarDto import com.wafflestudio.csereal.core.seminar.dto.SeminarSearchResponse import com.wafflestudio.csereal.core.seminar.service.SeminarService @@ -9,7 +11,7 @@ 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.core.Authentication import org.springframework.security.oauth2.core.oidc.user.OidcUser import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile @@ -25,10 +27,16 @@ class SeminarController( @RequestParam(required = false) keyword: String?, @RequestParam(required = false) pageNum: Int?, @RequestParam(required = false, defaultValue = "10") pageSize: Int, - @AuthenticationPrincipal oidcUser: OidcUser? + authentication: Authentication? ): ResponseEntity { - val isStaff = oidcUser?.let { - val username = it.idToken.getClaim("username") + val principal = authentication?.principal + + val isStaff = principal?.let { + val username = when (principal) { + is OidcUser -> principal.idToken.getClaim("username") + is CustomPrincipal -> principal.userEntity.username + else -> throw CserealException.Csereal401("Unsupported principal type") + } val user = userRepository.findByUsername(username) user?.role == Role.ROLE_STAFF } ?: false 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 index 828bbade..c538d33f 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/user/api/UserController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/user/api/UserController.kt @@ -1,10 +1,11 @@ package com.wafflestudio.csereal.core.user.api import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.common.mockauth.CustomPrincipal 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.core.Authentication import org.springframework.security.oauth2.core.oidc.user.OidcUser import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping @@ -17,15 +18,19 @@ class UserController( ) { @GetMapping("/is-staff") - fun isStaff(@AuthenticationPrincipal oidcUser: OidcUser?): ResponseEntity { - if (oidcUser == null) { - throw CserealException.Csereal401("로그인이 필요합니다.") + fun isStaff(authentication: Authentication?): ResponseEntity { + val principal = authentication?.principal ?: throw CserealException.Csereal401("로그인이 필요합니다.") + + val username = when (principal) { + is OidcUser -> principal.idToken.getClaim("username") + is CustomPrincipal -> principal.userEntity.username + else -> throw CserealException.Csereal401("Unsupported principal type") } - val username = oidcUser.idToken.getClaim("username") - if (userService.checkStaffAuth(username)) { - return ResponseEntity.ok(StaffAuthResponse(true)) + + return if (userService.checkStaffAuth(username)) { + ResponseEntity.ok(StaffAuthResponse(true)) } else { - return ResponseEntity.ok(StaffAuthResponse(false)) + ResponseEntity.ok(StaffAuthResponse(false)) } } } From e483c66856dfc249ef6f9f7440e7c13c221e5d62 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Sun, 10 Mar 2024 19:24:57 +0900 Subject: [PATCH 159/214] =?UTF-8?q?fix:=20migrateConferences=EC=97=90?= =?UTF-8?q?=EC=84=9C=20authenticatedStaff=20=EC=82=AD=EC=A0=9C=20(#204)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../csereal/core/conference/api/ConferenceController.kt | 1 - 1 file changed, 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 8edfed53..c6f5915e 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 @@ -27,7 +27,6 @@ class ConferenceController( return ResponseEntity.ok(conferenceService.modifyConferences(conferenceModifyRequest)) } - @AuthenticatedStaff @PostMapping("/migrate") fun migrateConferences( @RequestBody requestList: List From 34d6ade1ce6ee8a509e714862eaf7a4c46e04cb2 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Sun, 10 Mar 2024 20:17:34 +0900 Subject: [PATCH 160/214] =?UTF-8?q?fix:=20simpleProfessorDto=EC=97=90?= =?UTF-8?q?=EC=84=9C=20status=20=EC=B6=94=EA=B0=80=20(#205)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wafflestudio/csereal/core/member/dto/SimpleProfessorDto.kt | 2 ++ 1 file changed, 2 insertions(+) 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 265deaf1..c8756c7e 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 @@ -6,6 +6,7 @@ data class SimpleProfessorDto( val id: Long, val name: String, val academicRank: String, + val status: String, val labId: Long?, val labName: String?, val phone: String?, @@ -18,6 +19,7 @@ data class SimpleProfessorDto( id = professorEntity.id, name = professorEntity.name, academicRank = professorEntity.academicRank, + status = professorEntity.status.toString(), labId = professorEntity.lab?.id, labName = professorEntity.lab?.name, phone = professorEntity.phone, From 39ce4b783e646597d314f64f10890d410748689a Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Tue, 12 Mar 2024 23:19:41 +0900 Subject: [PATCH 161/214] =?UTF-8?q?fix:=20readDegreeRequirements=EC=97=90?= =?UTF-8?q?=20=ED=8C=8C=EC=9D=BC=20=EB=B3=B4=EC=9D=B4=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#209)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/academics/dto/DegreeRequirementsDto.kt | 10 +++++++--- .../academics/dto/DegreeRequirementsPageResponse.kt | 4 ++-- .../csereal/core/academics/service/AcademicsService.kt | 5 ++++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsDto.kt index e5c531a5..82cbaa9f 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsDto.kt @@ -1,16 +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 DegreeRequirementsDto( val year: Int, - val description: String + val description: String, + val attachments: List + ) { companion object { - fun of(entity: AcademicsEntity) = entity.run { + fun of(entity: AcademicsEntity, attachments: List) = entity.run { DegreeRequirementsDto( year = this.year!!, - description = this.description + description = this.description, + attachments = attachments ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsPageResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsPageResponse.kt index 8d4d2ad4..3fdfecb1 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsPageResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsPageResponse.kt @@ -7,10 +7,10 @@ class DegreeRequirementsPageResponse( val yearList: List ) { companion object { - fun of(entity: AcademicsEntity, yearList: List) = entity.run { + fun of(entity: AcademicsEntity, yearList: List) = entity.run { DegreeRequirementsPageResponse( description = this.description, - yearList = yearList.map { DegreeRequirementsDto.of(it) } + yearList = yearList ) } } 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 d2be6702..eb0e5c70 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 @@ -153,7 +153,10 @@ class AcademicsServiceImpl( enumLanguageType, AcademicsStudentType.UNDERGRADUATE, AcademicsPostType.DEGREE_REQUIREMENTS_YEAR_LIST - ) + ).map { + val attachments = attachmentService.createAttachmentResponses(it.attachments) + DegreeRequirementsDto.of(it, attachments) + } return DegreeRequirementsPageResponse.of(academicsEntity, yearList) } From ad8942f8bf9be40acba1fb1102e60aff379a30dd Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Wed, 13 Mar 2024 01:25:58 +0900 Subject: [PATCH 162/214] =?UTF-8?q?fix:=20StudentClubDto=EC=97=90=20engNam?= =?UTF-8?q?e=20=EC=B6=94=EA=B0=80=20(#210)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../csereal/core/about/api/AboutController.kt | 2 +- .../csereal/core/about/dto/StudentClubDto.kt | 30 ++++++++++++++++--- .../core/about/service/AboutService.kt | 13 ++++---- 3 files changed, 35 insertions(+), 10 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 ac46e33e..8d862ac0 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 @@ -48,7 +48,7 @@ class AboutController( @GetMapping("/student-clubs") fun readAllClubs( @RequestParam(required = false, defaultValue = "ko") language: String - ): ResponseEntity> { + ): ResponseEntity> { return ResponseEntity.ok(aboutService.readAllClubs(language)) } 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 89b70984..45968622 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 @@ -3,21 +3,43 @@ package com.wafflestudio.csereal.core.about.dto import com.fasterxml.jackson.annotation.JsonInclude import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.about.database.AboutEntity +import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse +import java.time.LocalDateTime data class StudentClubDto( @JsonInclude(JsonInclude.Include.NON_NULL) val id: Long? = null, val language: String, val name: String, - val description: String + val engName: String, + val description: String, + val year: Int?, + val createdAt: LocalDateTime?, + val modifiedAt: LocalDateTime?, + val locations: List?, + val imageURL: String?, + val attachments: List? ) { companion object { - fun of(entity: AboutEntity): StudentClubDto = entity.run { + fun of( + entity: AboutEntity, + name: String, + engName: String, + imageURL: String?, + attachmentResponses: List + ): StudentClubDto = entity.run { StudentClubDto( id = this.id, language = LanguageType.makeLowercase(this.language), - name = this.name!!, - description = this.description + name = name, + engName = engName, + description = this.description, + year = this.year, + createdAt = this.createdAt, + modifiedAt = this.modifiedAt, + locations = this.locations, + imageURL = imageURL, + 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 e85732e7..cdb4f121 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 @@ -22,7 +22,7 @@ interface AboutService { ): AboutDto fun readAbout(language: String, postType: String): AboutDto - fun readAllClubs(language: String): List + fun readAllClubs(language: String): List fun readAllFacilities(language: String): List fun readAllDirections(language: String): List fun readFutureCareers(): FutureCareersPage @@ -104,17 +104,19 @@ class AboutServiceImpl( } @Transactional(readOnly = true) - override fun readAllClubs(language: String): List { + override fun readAllClubs(language: String): List { val languageType = LanguageType.makeStringToLanguageType(language) val clubs = aboutRepository.findAllByLanguageAndPostTypeOrderByName( languageType, AboutPostType.STUDENT_CLUBS ).map { + val name = it.name!!.split("(")[0] + val engName = it.name!!.split("(")[1].replaceFirst(")", "") val imageURL = mainImageService.createImageURL(it.mainImage) val attachmentResponses = attachmentService.createAttachmentResponses(it.attachments) - AboutDto.of(it, imageURL, attachmentResponses) + StudentClubDto.of(it, name, engName, imageURL, attachmentResponses) } return clubs @@ -342,7 +344,8 @@ class AboutServiceImpl( for (request in requestList) { val language = request.language - val name = request.name + val name = request.name.split("(")[0] + val engName = request.name.split("(")[1].replaceFirst(")", "") val aboutDto = AboutDto( id = null, @@ -363,7 +366,7 @@ class AboutServiceImpl( syncSearchOfAbout(newAbout) newAbout = aboutRepository.save(newAbout) - list.add(StudentClubDto.of(newAbout)) + list.add(StudentClubDto.of(newAbout, name, engName, null, listOf())) } return list } From 60c8941e61f023b34478f5e4fc31e85877fdfe34 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Wed, 13 Mar 2024 02:17:08 +0900 Subject: [PATCH 163/214] =?UTF-8?q?fix:=20researchEntity=EC=97=90=20websit?= =?UTF-8?q?eURL=20=EC=B6=94=EA=B0=80=20(#211)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: researchEntity에 websiteURL 추가 * fix: ProfessorServiceTest 수정 --- .../csereal/core/research/database/ResearchEntity.kt | 5 ++++- .../core/research/database/ResearchSearchEntity.kt | 1 + .../csereal/core/research/dto/ResearchDto.kt | 2 ++ .../csereal/core/member/service/ProfessorServiceTest.kt | 2 ++ .../core/reseach/service/ResearchSearchServiceTest.kt | 3 ++- .../csereal/core/reseach/service/ResearchServiceTest.kt | 9 +++++++-- 6 files changed, 18 insertions(+), 4 deletions(-) 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 33b1a55c..e0c3db51 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 @@ -22,6 +22,8 @@ class ResearchEntity( @Column(columnDefinition = "mediumText") var description: String?, + var websiteURL: String?, + @OneToMany(mappedBy = "research", cascade = [CascadeType.ALL], orphanRemoval = true) var labs: MutableList = mutableListOf(), @@ -43,7 +45,8 @@ class ResearchEntity( postType = researchDto.postType, language = languageType, name = researchDto.name, - description = researchDto.description + description = researchDto.description, + websiteURL = researchDto.websiteURL ) } } 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 d07c5ae7..fce8d297 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 @@ -58,6 +58,7 @@ class ResearchSearchEntity( appendLine(cleanTextFromHtml(it)) } research.labs.forEach { appendLine(it.name) } + research.websiteURL?.let { appendLine(it) } }.toString() fun createContent(lab: LabEntity) = StringBuilder().apply { 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 11325c04..e87c6668 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 @@ -12,6 +12,7 @@ data class ResearchDto( val language: String, val name: String, val description: String?, + val websiteURL: String?, val createdAt: LocalDateTime?, val modifiedAt: LocalDateTime?, val labs: List?, @@ -26,6 +27,7 @@ data class ResearchDto( language = LanguageType.makeLowercase(entity.language), name = this.name, description = this.description, + websiteURL = this.websiteURL, createdAt = this.createdAt, modifiedAt = this.modifiedAt, labs = this.labs.map { ResearchLabResponse(id = it.id, name = it.name) }, 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 6e38fa5e..9c6b282b 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 @@ -41,6 +41,7 @@ class ProfessorServiceTest( language = LanguageType.KO, name = "researchName", description = null, + websiteURL = null, postType = ResearchPostType.LABS ) var labEntity = LabEntity( @@ -143,6 +144,7 @@ class ProfessorServiceTest( language = LanguageType.KO, name = "researchName", description = null, + websiteURL = null, postType = ResearchPostType.LABS ) val labEntity1 = LabEntity( diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchSearchServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchSearchServiceTest.kt index 76603b52..85d9be47 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchSearchServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchSearchServiceTest.kt @@ -98,7 +98,8 @@ class ResearchSearchServiceTest( language = LanguageType.KO, name = "research", postType = ResearchPostType.GROUPS, - description = null + description = null, + websiteURL = 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 index c7e8e91d..cf631d44 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 @@ -50,6 +50,7 @@ class ResearchServiceTest( name = "name", postType = ResearchPostType.CENTERS, description = "description", + websiteURL = null, createdAt = null, modifiedAt = null, labs = null, @@ -101,6 +102,7 @@ class ResearchServiceTest( name = "name", postType = ResearchPostType.CENTERS, description = "description", + websiteURL = null, createdAt = null, modifiedAt = null, labs = null, @@ -121,6 +123,7 @@ class ResearchServiceTest( name = "name2", postType = ResearchPostType.GROUPS, description = "description2", + websiteURL = null, createdAt = null, modifiedAt = null, labs = null, @@ -198,7 +201,8 @@ class ResearchServiceTest( language = LanguageType.KO, name = "research", postType = ResearchPostType.GROUPS, - description = null + description = null, + websiteURL = null ) ) @@ -305,7 +309,8 @@ class ResearchServiceTest( language = LanguageType.KO, name = "research", postType = ResearchPostType.GROUPS, - description = null + description = null, + websiteURL = null ) ) From 974711cb84551663329565e89b0d09888edfda39 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, 13 Mar 2024 17:32:31 +0900 Subject: [PATCH 164/214] =?UTF-8?q?Fix:=20`exchangepageNum`=EC=97=90?= =?UTF-8?q?=EC=84=9C=20total=EC=9D=B4=200=EC=9D=BC=20=EA=B2=BD=EC=9A=B0=20?= =?UTF-8?q?=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=B6=94=EA=B0=80.=20(#213)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix: Add condition that when total is 0 return pageNum as 1 * Refactor: Rename method name to be more clear. * Refactor: Remove not needed duplicated conditional check. * Test: Add test for exchangeValidPageNum for checking when total = 0 --- .../csereal/common/utils/Utils.kt | 11 +++++---- .../core/about/database/AboutRepository.kt | 8 +++---- .../database/AcademicsSearchRepository.kt | 6 ++--- .../database/AdmissionsRepository.kt | 8 +++---- .../member/database/MemberSearchRepository.kt | 4 ++-- .../database/ResearchSearchRepository.kt | 6 ++--- .../csereal/common/util/UtilsTest.kt | 23 ++++++++++++++----- 7 files changed, 37 insertions(+), 29 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 f0f59159..1b9cef0c 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/utils/Utils.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/utils/Utils.kt @@ -3,6 +3,7 @@ package com.wafflestudio.csereal.common.utils import org.jsoup.Jsoup import org.jsoup.parser.Parser import org.jsoup.safety.Safelist +import kotlin.math.ceil fun cleanTextFromHtml(description: String): String { val cleanDescription = Jsoup.clean(description, Safelist.none()) @@ -27,15 +28,15 @@ fun substringAroundKeyword(keyword: String, content: String, amount: Int): Pair< } } -fun exchangePageNum(pageSize: Int, pageNum: Int, total: Long): Int { +fun exchangeValidPageNum(pageSize: Int, pageNum: Int, total: Long): Int { // Validate if (!(pageSize > 0 && pageNum > 0 && total >= 0)) { throw RuntimeException() } - return if ((pageNum - 1) * pageSize < total) { - pageNum - } else { - Math.ceil(total.toDouble() / pageSize).toInt() + return when { + total == 0L -> 1 + (pageNum - 1) * pageSize < total -> pageNum + else -> ceil(total.toDouble() / pageSize).toInt() } } 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 727cbb09..1c00139b 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 @@ -4,7 +4,7 @@ import com.querydsl.jpa.impl.JPAQuery import com.querydsl.jpa.impl.JPAQueryFactory import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.common.repository.CommonRepository -import com.wafflestudio.csereal.common.utils.exchangePageNum +import com.wafflestudio.csereal.common.utils.exchangeValidPageNum import com.wafflestudio.csereal.core.about.database.QAboutEntity.aboutEntity import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository @@ -41,10 +41,8 @@ class AboutCustomRepositoryImpl( pageNum: Int ): Pair, Long> { val total = searchCount(keyword, language) - val validPageNum = exchangePageNum(pageSize, pageNum, total) - val validOffset = ( - if (validPageNum >= 1) validPageNum - 1 else 0 - ) * pageSize.toLong() + val validPageNum = exchangeValidPageNum(pageSize, pageNum, total) + val validOffset = (validPageNum - 1) * pageSize.toLong() val queryResult = searchQueryExpr(keyword, language) .offset(validOffset) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchRepository.kt index df24e29a..70e35c03 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchRepository.kt @@ -4,7 +4,7 @@ import com.querydsl.jpa.impl.JPAQuery import com.querydsl.jpa.impl.JPAQueryFactory import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.common.repository.CommonRepository -import com.wafflestudio.csereal.common.utils.exchangePageNum +import com.wafflestudio.csereal.common.utils.exchangeValidPageNum import com.wafflestudio.csereal.core.academics.database.QAcademicsEntity.academicsEntity import com.wafflestudio.csereal.core.academics.database.QAcademicsSearchEntity.academicsSearchEntity import com.wafflestudio.csereal.core.academics.database.QCourseEntity.courseEntity @@ -37,8 +37,8 @@ class AcademicsSearchCustomRepositoryImpl( val query = searchQuery(keyword, language) val total = getSearchCount(keyword, language) - val validPageNum = exchangePageNum(pageSize, pageNum, total) - val validOffset = (if (validPageNum >= 1) validPageNum - 1 else 0) * pageSize.toLong() + val validPageNum = exchangeValidPageNum(pageSize, pageNum, total) + val validOffset = (validPageNum - 1) * pageSize.toLong() val queryResult = query.offset(validOffset) .limit(pageSize.toLong()) 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 71b4d450..67ae8be4 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,7 +3,7 @@ package com.wafflestudio.csereal.core.admissions.database import com.querydsl.jpa.impl.JPAQueryFactory import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.common.repository.CommonRepository -import com.wafflestudio.csereal.common.utils.exchangePageNum +import com.wafflestudio.csereal.common.utils.exchangeValidPageNum import com.wafflestudio.csereal.core.admissions.database.QAdmissionsEntity.admissionsEntity import com.wafflestudio.csereal.core.admissions.type.AdmissionsMainType import com.wafflestudio.csereal.core.admissions.type.AdmissionsPostType @@ -39,10 +39,8 @@ class AdmissionsCustomRepositoryImpl( pageNum: Int ): Pair, Long> { val total = searchCount(keyword, language) - val validPageNum = exchangePageNum(pageSize, pageNum, total) - val validOffset = ( - if (validPageNum >= 1) validPageNum - 1 else 0 - ) * pageSize.toLong() + val validPageNum = exchangeValidPageNum(pageSize, pageNum, total) + val validOffset = (validPageNum - 1) * pageSize.toLong() val result = searchQueryOfLanguage(keyword, language) .offset(validOffset) 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 2ab47803..bb358ee2 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 @@ -4,7 +4,7 @@ import com.querydsl.jpa.impl.JPAQuery import com.querydsl.jpa.impl.JPAQueryFactory import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.common.repository.CommonRepository -import com.wafflestudio.csereal.common.utils.exchangePageNum +import com.wafflestudio.csereal.common.utils.exchangeValidPageNum 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 @@ -38,7 +38,7 @@ class MemberSearchRepositoryCustomImpl( val query = searchQuery(keyword, language) val total = getSearchCount(keyword, language) - val validPageNum = exchangePageNum(pageSize, pageNum, total) + val validPageNum = exchangeValidPageNum(pageSize, pageNum, total) val queryResult = query .offset((validPageNum - 1) * pageSize.toLong()) .limit(pageSize.toLong()) 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 3c82b593..d415297d 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,7 +4,7 @@ import com.querydsl.jpa.impl.JPAQuery import com.querydsl.jpa.impl.JPAQueryFactory import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.common.repository.CommonRepository -import com.wafflestudio.csereal.common.utils.exchangePageNum +import com.wafflestudio.csereal.common.utils.exchangeValidPageNum import com.wafflestudio.csereal.core.conference.database.QConferenceEntity.conferenceEntity import com.wafflestudio.csereal.core.research.database.QLabEntity.labEntity import com.wafflestudio.csereal.core.research.database.QResearchEntity.researchEntity @@ -37,8 +37,8 @@ class ResearchSearchRepositoryCustomImpl( val query = searchQuery(keyword, language) val total = getSearchCount(keyword, language) - val validPageNum = exchangePageNum(pageSize, pageNum, total) - val validOffset = (if (validPageNum >= 1) validPageNum - 1 else 0) * pageSize.toLong() + val validPageNum = exchangeValidPageNum(pageSize, pageNum, total) + val validOffset = (validPageNum - 1) * pageSize.toLong() val queryResult = query .offset(validOffset) .limit(pageSize.toLong()) 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 a54c0d31..f65f03bf 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/common/util/UtilsTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/common/util/UtilsTest.kt @@ -1,7 +1,7 @@ package com.wafflestudio.csereal.common.util import com.wafflestudio.csereal.common.utils.cleanTextFromHtml -import com.wafflestudio.csereal.common.utils.exchangePageNum +import com.wafflestudio.csereal.common.utils.exchangeValidPageNum import com.wafflestudio.csereal.common.utils.substringAroundKeyword import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.BehaviorSpec @@ -112,13 +112,13 @@ class UtilsTest : BehaviorSpec({ Then("should throw AssertionError") { shouldThrow { - exchangePageNum(totalMinus.first, totalMinus.second, totalMinus.third) + exchangeValidPageNum(totalMinus.first, totalMinus.second, totalMinus.third) } shouldThrow { - exchangePageNum(pageSizeZero.first, pageSizeZero.second, pageSizeZero.third) + exchangeValidPageNum(pageSizeZero.first, pageSizeZero.second, pageSizeZero.third) } shouldThrow { - exchangePageNum(pageNumZero.first, pageNumZero.second, pageNumZero.third) + exchangeValidPageNum(pageNumZero.first, pageNumZero.second, pageNumZero.third) } } } @@ -129,7 +129,7 @@ class UtilsTest : BehaviorSpec({ val pageNum = 3 Then("Should return pageNum itself") { - val resultPageNum = exchangePageNum(pageSize, pageNum, total) + val resultPageNum = exchangeValidPageNum(pageSize, pageNum, total) resultPageNum shouldBe pageNum } } @@ -140,9 +140,20 @@ class UtilsTest : BehaviorSpec({ val pageNum = 15 Then("Should return last page number") { - val resultPageNum = exchangePageNum(pageSize, pageNum, total) + val resultPageNum = exchangeValidPageNum(pageSize, pageNum, total) resultPageNum shouldBe 11 } } + + When("Given total count is zero") { + val pageSize = 10 + val total = 0L + val pageNum = 1 + + Then("Should return first page number") { + val resultPageNum = exchangeValidPageNum(pageSize, pageNum, total) + resultPageNum shouldBe 1 + } + } } }) From 3d545b6e1713b5cd53d877c196e0c8f647e9d42c Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Thu, 14 Mar 2024 00:24:13 +0900 Subject: [PATCH 165/214] =?UTF-8?q?fix:=20readGeneralStudiesRequirements?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20(#212)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: readGeneralStudiesRequirements 추가 * fix: readGeneralStudiesRequirements 프론트 협의사항에 따라 수정 * fix: DegreeRequirementsDto description 삭제 * AcademicsPostType 타입 이름 주석 추가 --- .../core/academics/api/AcademicsController.kt | 7 +++++ .../academics/database/AcademicsPostType.kt | 7 +++++ .../academics/database/AcademicsRepository.kt | 6 +++++ .../academics/dto/DegreeRequirementsDto.kt | 2 -- .../core/academics/dto/GeneralStudiesDto.kt | 19 ++++++++++++++ .../GeneralStudiesRequirementsPageResponse.kt | 23 ++++++++++++++++ .../academics/service/AcademicsService.kt | 26 +++++++++++++++++++ 7 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GeneralStudiesDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GeneralStudiesRequirementsPageResponse.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 06d8a5cc..88f69bd0 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 @@ -41,6 +41,13 @@ class AcademicsController( return ResponseEntity.ok(academicsService.readGuide(language, studentType)) } + @GetMapping("/undergraduate/general-studies-requirements") + fun readGeneralStudiesRequirements( + @RequestParam(required = false, defaultValue = "ko") language: String + ): ResponseEntity { + return ResponseEntity.ok(academicsService.readGeneralStudiesRequirements(language)) + } + @GetMapping("/{studentType}/{postType}") fun readAcademicsYearResponses( @RequestParam(required = false, defaultValue = "ko") language: String, 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 fb77471f..ac8b4d07 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 @@ -3,4 +3,11 @@ package com.wafflestudio.csereal.core.academics.database enum class AcademicsPostType { GUIDE, GENERAL_STUDIES_REQUIREMENTS, GENERAL_STUDIES_REQUIREMENTS_SUBJECT_CHANGES, CURRICULUM, DEGREE_REQUIREMENTS, DEGREE_REQUIREMENTS_YEAR_LIST, COURSE_CHANGES, SCHOLARSHIP; + + // GUIDE: 학부 안내 + // GENERAL_STUDIES_REQUIREMENTS: 필수 교양 과목 + // 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 2aeeb80e..a3b0beff 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 @@ -9,6 +9,12 @@ interface AcademicsRepository : JpaRepository { studentType: AcademicsStudentType, postType: AcademicsPostType ): AcademicsEntity + fun findByLanguageAndStudentTypeAndPostTypeAndYear( + languageType: LanguageType, + studentType: AcademicsStudentType, + postType: AcademicsPostType, + year: Int? + ): AcademicsEntity fun findAllByLanguageAndStudentTypeAndPostTypeOrderByYearDesc( languageType: LanguageType, studentType: AcademicsStudentType, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsDto.kt index 82cbaa9f..604f2c69 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsDto.kt @@ -5,7 +5,6 @@ import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse class DegreeRequirementsDto( val year: Int, - val description: String, val attachments: List ) { @@ -13,7 +12,6 @@ class DegreeRequirementsDto( fun of(entity: AcademicsEntity, attachments: List) = entity.run { DegreeRequirementsDto( year = this.year!!, - description = this.description, attachments = attachments ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GeneralStudiesDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GeneralStudiesDto.kt new file mode 100644 index 00000000..efb541fa --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GeneralStudiesDto.kt @@ -0,0 +1,19 @@ +package com.wafflestudio.csereal.core.academics.dto + +import com.wafflestudio.csereal.core.academics.database.AcademicsEntity + +class GeneralStudiesDto( + val id: Long, + val year: Int, + val description: String +) { + companion object { + fun of(academicsEntity: AcademicsEntity): GeneralStudiesDto { + return GeneralStudiesDto( + id = academicsEntity.id, + year = academicsEntity.year!!, + description = academicsEntity.description + ) + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GeneralStudiesRequirementsPageResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GeneralStudiesRequirementsPageResponse.kt new file mode 100644 index 00000000..0851527d --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GeneralStudiesRequirementsPageResponse.kt @@ -0,0 +1,23 @@ +package com.wafflestudio.csereal.core.academics.dto + +import com.wafflestudio.csereal.core.academics.database.AcademicsEntity + +class GeneralStudiesRequirementsPageResponse( + val overview: String, + val subjectChanges: String, + val generalStudies: List +) { + companion object { + fun of( + overview: AcademicsEntity, + subjectChanges: AcademicsEntity, + generalStudies: List + ): GeneralStudiesRequirementsPageResponse { + return GeneralStudiesRequirementsPageResponse( + overview = overview.description, + subjectChanges = subjectChanges.description, + generalStudies = generalStudies.map { GeneralStudiesDto.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 eb0e5c70..1e033e68 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 @@ -26,6 +26,7 @@ interface AcademicsService { studentType: String, postType: String ): List + fun readGeneralStudiesRequirements(language: String): GeneralStudiesRequirementsPageResponse fun readDegreeRequirements(language: String): DegreeRequirementsPageResponse fun createCourse( studentType: String, @@ -138,6 +139,31 @@ class AcademicsServiceImpl( return academicsYearResponses } + @Transactional(readOnly = true) + override fun readGeneralStudiesRequirements(language: String): GeneralStudiesRequirementsPageResponse { + val enumLanguageType = LanguageType.makeStringToLanguageType(language) + val overview = + academicsRepository.findByLanguageAndStudentTypeAndPostTypeAndYear( + enumLanguageType, + AcademicsStudentType.UNDERGRADUATE, + AcademicsPostType.GENERAL_STUDIES_REQUIREMENTS, + null + ) + val subjectChanges = + academicsRepository.findByLanguageAndStudentTypeAndPostType( + enumLanguageType, + AcademicsStudentType.UNDERGRADUATE, + AcademicsPostType.GENERAL_STUDIES_REQUIREMENTS_SUBJECT_CHANGES + ) + val generalStudiesEntity = + academicsRepository.findAllByLanguageAndStudentTypeAndPostTypeOrderByYearDesc( + enumLanguageType, + AcademicsStudentType.UNDERGRADUATE, + AcademicsPostType.GENERAL_STUDIES_REQUIREMENTS + ).filter { academicsEntity -> academicsEntity.year != null } + return GeneralStudiesRequirementsPageResponse.of(overview, subjectChanges, generalStudiesEntity) + } + @Transactional(readOnly = true) override fun readDegreeRequirements(language: String): DegreeRequirementsPageResponse { val enumLanguageType = LanguageType.makeStringToLanguageType(language) From 03ad82287e5cf1c7c45015f28bef78b296b40f6a 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, 14 Mar 2024 15:05:24 +0900 Subject: [PATCH 166/214] =?UTF-8?q?Fix:=20Notice=20=EB=B9=84=EB=B0=80?= =?UTF-8?q?=EA=B8=80=20=ED=91=9C=EC=8B=9C=20=EC=95=88=EB=90=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95=20(#216)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat: Add FindByIdAndIsPrivateFalse * Fix: Change totalSearchNotice to get isStaff and hide private when not a staff. * Fix: Change searchTotalNotice and readNotice to consider isStaff. * Fix: Change totalSearchNotice and readNotice to check authentication and get staff info. --- .../core/notice/api/NoticeController.kt | 40 +++++++++++++++---- .../core/notice/database/NoticeRepository.kt | 10 +++-- .../core/notice/service/NoticeService.kt | 18 +++++---- 3 files changed, 51 insertions(+), 17 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 611d7ba8..f9384227 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 @@ -58,16 +58,42 @@ class NoticeController( @NotBlank keyword: String, @RequestParam(required = true) @Positive number: Int, - @RequestParam(required = false, defaultValue = "200") @Positive stringLength: Int - ) = ResponseEntity.ok( - noticeService.searchTotalNotice(keyword, number, stringLength) - ) + @RequestParam(required = false, defaultValue = "200") @Positive stringLength: Int, + authentication: Authentication? + ): NoticeTotalSearchResponse { + val principal = authentication?.principal + + val isStaff = principal?.let { + val username = when (principal) { + is OidcUser -> principal.idToken.getClaim("username") + is CustomPrincipal -> principal.userEntity.username + else -> throw CserealException.Csereal401("Unsupported principal type") + } + val user = userRepository.findByUsername(username) + user?.role == Role.ROLE_STAFF + } ?: false + + return noticeService.searchTotalNotice(keyword, number, stringLength, isStaff) + } @GetMapping("/{noticeId}") fun readNotice( - @PathVariable noticeId: Long - ): ResponseEntity { - return ResponseEntity.ok(noticeService.readNotice(noticeId)) + @PathVariable noticeId: Long, + authentication: Authentication? + ): NoticeDto { + val principal = authentication?.principal + + val isStaff = principal?.let { + val username = when (principal) { + is OidcUser -> principal.idToken.getClaim("username") + is CustomPrincipal -> principal.userEntity.username + else -> throw CserealException.Csereal401("Unsupported principal type") + } + val user = userRepository.findByUsername(username) + user?.role == Role.ROLE_STAFF + } ?: false + + return noticeService.readNotice(noticeId, isStaff) } @AuthenticatedStaff 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 17269405..00b0ee23 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,6 +17,7 @@ import org.springframework.stereotype.Component import java.time.LocalDateTime interface NoticeRepository : JpaRepository, CustomNoticeRepository { + fun findByIdAndIsPrivateFalse(id: Long): NoticeEntity? fun findAllByIsPrivateFalseAndIsImportantTrueAndIsDeletedFalse(): List fun findAllByIsImportantTrueAndIsDeletedFalse(): List fun findFirstByIsDeletedFalseAndIsPrivateFalseAndCreatedAtLessThanOrderByCreatedAtDesc( @@ -37,7 +38,7 @@ interface CustomNoticeRepository { isStaff: Boolean ): NoticeSearchResponse - fun totalSearchNotice(keyword: String, number: Int, stringLength: Int): NoticeTotalSearchResponse + fun totalSearchNotice(keyword: String, number: Int, stringLength: Int, isStaff: Boolean): NoticeTotalSearchResponse } @Component @@ -48,7 +49,8 @@ class NoticeRepositoryImpl( override fun totalSearchNotice( keyword: String, number: Int, - stringLength: Int + stringLength: Int, + isStaff: Boolean ): NoticeTotalSearchResponse { val doubleTemplate = commonRepository.searchFullDoubleTextTemplate( keyword, @@ -56,13 +58,15 @@ class NoticeRepositoryImpl( noticeEntity.plainTextDescription ) + val privateBoolean = noticeEntity.isPrivate.eq(false).takeUnless { isStaff } + val query = queryFactory.select( noticeEntity.id, noticeEntity.title, noticeEntity.createdAt, noticeEntity.plainTextDescription ).from(noticeEntity) - .where(doubleTemplate.gt(0.0)) + .where(doubleTemplate.gt(0.0), privateBoolean) val total = query.clone().select(noticeEntity.countDistinct()).fetchOne()!! 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 b12ea9c2..8904eb77 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 @@ -23,9 +23,9 @@ interface NoticeService { isStaff: Boolean ): NoticeSearchResponse - fun searchTotalNotice(keyword: String, number: Int, stringLength: Int): NoticeTotalSearchResponse + fun searchTotalNotice(keyword: String, number: Int, stringLength: Int, isStaff: Boolean): NoticeTotalSearchResponse - fun readNotice(noticeId: Long): NoticeDto + fun readNotice(noticeId: Long, isStaff: Boolean): NoticeDto fun createNotice(request: NoticeDto, attachments: List?): NoticeDto fun updateNotice( noticeId: Long, @@ -62,13 +62,17 @@ class NoticeServiceImpl( override fun searchTotalNotice( keyword: String, number: Int, - stringLength: Int - ) = noticeRepository.totalSearchNotice(keyword, number, stringLength) + stringLength: Int, + isStaff: Boolean + ) = noticeRepository.totalSearchNotice(keyword, number, stringLength, isStaff) @Transactional(readOnly = true) - override fun readNotice(noticeId: Long): NoticeDto { - val notice = noticeRepository.findByIdOrNull(noticeId) - ?: throw CserealException.Csereal404("존재하지 않는 공지사항입니다.(noticeId: $noticeId)") + override fun readNotice(noticeId: Long, isStaff: Boolean): NoticeDto { + val notice = if (isStaff) { + noticeRepository.findByIdOrNull(noticeId) + } else { + noticeRepository.findByIdAndIsPrivateFalse(noticeId) + } ?: throw CserealException.Csereal404("존재하지 않는 공지사항입니다.(noticeId: $noticeId)") if (notice.isDeleted) throw CserealException.Csereal404("삭제된 공지사항입니다.(noticeId: $noticeId)") From aee005748af0ab23f90372539ed9f2de8fbf806a Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Thu, 14 Mar 2024 23:38:58 +0900 Subject: [PATCH 167/214] =?UTF-8?q?fix:=20readFutureCareers=EC=97=90=20des?= =?UTF-8?q?cription=20=EC=88=98=EC=A0=95=20(#214)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 우혁준 (HyukJoon Woo) --- .../csereal/core/about/api/AboutController.kt | 6 ++++-- .../core/about/service/AboutService.kt | 20 ++++++++----------- 2 files changed, 12 insertions(+), 14 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 8d862ac0..8af09233 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 @@ -67,8 +67,10 @@ class AboutController( } @GetMapping("/future-careers") - fun readFutureCareers(): ResponseEntity { - return ResponseEntity.ok(aboutService.readFutureCareers()) + fun readFutureCareers( + @RequestParam(required = false, defaultValue = "ko") language: String + ): ResponseEntity { + return ResponseEntity.ok(aboutService.readFutureCareers(language)) } @GetMapping("/search/top") 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 cdb4f121..884e1554 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 @@ -25,7 +25,7 @@ interface AboutService { fun readAllClubs(language: String): List fun readAllFacilities(language: String): List fun readAllDirections(language: String): List - fun readFutureCareers(): FutureCareersPage + fun readFutureCareers(language: String): FutureCareersPage fun searchTopAbout( keyword: String, @@ -156,17 +156,13 @@ class AboutServiceImpl( } @Transactional - override fun readFutureCareers(): FutureCareersPage { - val description = "컴퓨터공학을 전공함으로써 벤처기업을 창업할 수 있을 뿐 " + - "아니라 시스템엔지니어, 보안전문가, 소프트웨어개발자, 데이터베이스관리자 등 " + - "많은 IT 전문 분야로의 진출이 가능하다. 또한 컴퓨터공학은 바이오, 전자전기, " + - "로봇, 기계, 의료 등 이공계 영역뿐만 아니라 정치, 경제, 사회, 문화의 다양한 분야와 " + - "결합되어 미래 지식정보사회에 대한 새로운 가능성을 제시하고 있고 새로운 학문적 과제가 " + - "지속적으로 생산되기 때문에 많은 전문연구인력이 필요하다.\n" + - "\n" + - "서울대학교 컴퓨터공학부의 경우 학부 졸업생 절반 이상이 대학원에 진학하고 있다. " + - "대학원에 진학하면 여러 전공분야 중 하나를 선택하여 보다 깊이 있는 지식의 습득과 연구과정을 거치게 되며 " + - "그 이후로는 국내외 관련 산업계, 학계에 주로 진출하고 있고, 새로운 아이디어로 벤처기업을 창업하기도 한다." + override fun readFutureCareers(language: String): FutureCareersPage { + val languageType = LanguageType.makeStringToLanguageType(language) + val description = + aboutRepository.findByLanguageAndPostType( + languageType, + AboutPostType.FUTURE_CAREERS + ).description val statList = mutableListOf() for (i: Int in 2021 downTo 2011) { From 8f11cec6fe7cada0037ce49508f352a361a48dbe Mon Sep 17 00:00:00 2001 From: "DESKTOP-USQPRVG\\gram" Date: Thu, 14 Mar 2024 23:51:24 +0900 Subject: [PATCH 168/214] =?UTF-8?q?fix:=20=ED=8C=8C=EC=9D=BC=20=ED=81=AC?= =?UTF-8?q?=EA=B8=B0=20100MB=EB=A1=9C=20=EB=8A=98=EB=A6=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 1817e8e4..654feeca 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -7,7 +7,7 @@ spring: multipart: enabled: true max-request-size: 100MB - max-file-size: 10MB + max-file-size: 100MB server: servlet: From 688c18eef20676a12b63328f74026f71e702bb17 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 Mar 2024 00:20:54 +0900 Subject: [PATCH 169/214] =?UTF-8?q?Feat:=20Research=20=EA=B2=80=EC=83=89?= =?UTF-8?q?=20=EA=B2=B0=EA=B3=BC=20research=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=ED=99=94=20(#218)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Refactor: Remove unused method * Comment: Add comment * Feat: Add specific research types to enum * Feat: Change to give more specified enum when it is research related column. * Refactor: Remove unused LAB entry from Research. --- .../research/api/res/ResearchSearchResElement.kt | 6 +++++- .../core/research/database/ResearchPostType.kt | 3 +-- .../core/research/database/ResearchSearchEntity.kt | 14 ++------------ .../core/member/service/ProfessorServiceTest.kt | 4 ++-- 4 files changed, 10 insertions(+), 17 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/api/res/ResearchSearchResElement.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/res/ResearchSearchResElement.kt index 6a3f825e..593e5138 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/api/res/ResearchSearchResElement.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/res/ResearchSearchResElement.kt @@ -3,6 +3,7 @@ package com.wafflestudio.csereal.core.research.api.res import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.common.utils.substringAroundKeyword +import com.wafflestudio.csereal.core.research.database.ResearchPostType import com.wafflestudio.csereal.core.research.database.ResearchSearchEntity import com.wafflestudio.csereal.core.research.database.ResearchSearchType @@ -35,7 +36,10 @@ data class ResearchSearchResElement( id = it.id, name = it.name, language = it.language.let { ln -> LanguageType.makeLowercase(ln) }, - researchType = ResearchSearchType.RESEARCH, + researchType = when (it.postType) { + ResearchPostType.GROUPS -> ResearchSearchType.RESEARCH_GROUP + ResearchPostType.CENTERS -> ResearchSearchType.RESEARCH_CENTER + }, partialDescription = partialDesc, boldStartIdx = startIdx ?: 0, boldEndIdx = startIdx?.plus(keyword.length) ?: 0 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 7d651dd8..4e5d6447 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 @@ -4,6 +4,5 @@ enum class ResearchPostType( val krName: String ) { GROUPS("연구 그룹"), - CENTERS("연구 센터"), - LABS("연구실 목록"); + CENTERS("연구 센터"); } 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 fce8d297..cec416bb 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 @@ -95,17 +95,6 @@ class ResearchSearchEntity( } } - 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) } @@ -120,7 +109,8 @@ class ResearchSearchEntity( } enum class ResearchSearchType { - RESEARCH, + RESEARCH_GROUP, + RESEARCH_CENTER, LAB, CONFERENCE; } 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 9c6b282b..13256934 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 @@ -42,7 +42,7 @@ class ProfessorServiceTest( name = "researchName", description = null, websiteURL = null, - postType = ResearchPostType.LABS + postType = ResearchPostType.GROUPS ) var labEntity = LabEntity( language = LanguageType.KO, @@ -145,7 +145,7 @@ class ProfessorServiceTest( name = "researchName", description = null, websiteURL = null, - postType = ResearchPostType.LABS + postType = ResearchPostType.GROUPS ) val labEntity1 = LabEntity( language = LanguageType.KO, From 18f5f5a5d1c00e5ba80e73b4a8451a88606dd512 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Sat, 16 Mar 2024 00:10:02 +0900 Subject: [PATCH 170/214] =?UTF-8?q?fix:=20pdf=20=ED=83=80=EC=9E=85=20Attac?= =?UTF-8?q?hmentResponse=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20(#220)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../csereal/core/research/dto/LabDto.kt | 7 +-- .../core/research/service/ResearchService.kt | 45 +++++++++---------- .../attachment/service/AttachmentService.kt | 19 +++++++- 3 files changed, 43 insertions(+), 28 deletions(-) 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 19126f2b..3250be51 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 @@ -2,6 +2,7 @@ package com.wafflestudio.csereal.core.research.dto import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.research.database.LabEntity +import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse data class LabDto( val id: Long, @@ -11,14 +12,14 @@ data class LabDto( val location: String?, val tel: String?, val acronym: String?, - val pdf: String?, + val pdf: AttachmentResponse?, val youtube: String?, val group: String, val description: String?, val websiteURL: String? ) { companion object { - fun of(entity: LabEntity, pdfURL: String): LabDto = entity.run { + fun of(entity: LabEntity, pdf: AttachmentResponse?): LabDto = entity.run { LabDto( id = this.id, language = LanguageType.makeLowercase(entity.language), @@ -27,7 +28,7 @@ data class LabDto( location = this.location, tel = this.tel, acronym = this.acronym, - pdf = pdfURL, + pdf = pdf, youtube = this.youtube, group = this.research.name, description = this.description, 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 4e700af0..023eac57 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 @@ -209,28 +209,27 @@ class ResearchServiceImpl( } } - var pdfURL = "" if (pdf != null) { - val attachmentDto = attachmentService.uploadAttachmentInLabEntity(newLab, pdf) - pdfURL = "${endpointProperties.backend}/v1/file/${attachmentDto.filename}" + attachmentService.uploadAttachmentInLabEntity(newLab, pdf) } newLab.researchSearch = ResearchSearchEntity.create(newLab) labRepository.save(newLab) - return LabDto.of(newLab, pdfURL) + val attachmentResponse = + attachmentService.createOneAttachmentResponse(newLab.pdf) + + return LabDto.of(newLab, attachmentResponse) } @Transactional(readOnly = true) override fun readAllLabs(language: String): List { val enumLanguageType = LanguageType.makeStringToLanguageType(language) val labs = labRepository.findAllByLanguageOrderByName(enumLanguageType).map { - var pdfURL = "" - if (it.pdf != null) { - pdfURL = createPdfURL(it.pdf!!) - } - LabDto.of(it, pdfURL) + val attachmentResponse = + attachmentService.createOneAttachmentResponse(it.pdf) + LabDto.of(it, attachmentResponse) } return labs @@ -240,12 +239,11 @@ class ResearchServiceImpl( 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) + val attachmentResponse = + attachmentService.createOneAttachmentResponse(lab.pdf) + + return LabDto.of(lab, attachmentResponse) } private fun createPdfURL(pdf: AttachmentEntity): String { @@ -296,12 +294,10 @@ class ResearchServiceImpl( labEntity.researchSearch = ResearchSearchEntity.create(labEntity) } - return LabDto.of( - labEntity, - labEntity.pdf?.let { - createPdfURL(it) - } ?: "" - ) + val attachmentResponse = + attachmentService.createOneAttachmentResponse(labEntity.pdf) + + return LabDto.of(labEntity, attachmentResponse) } @Transactional @@ -339,7 +335,7 @@ class ResearchServiceImpl( labRepository.save(newLab) - list.add(LabDto.of(newLab, "")) + list.add(LabDto.of(newLab, null)) } return list } @@ -372,12 +368,13 @@ class ResearchServiceImpl( val lab = labRepository.findByIdOrNull(labId) ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다.") - var pdfURL = "" if (pdf != null) { val attachmentDto = attachmentService.uploadAttachmentInLabEntity(lab, pdf) - pdfURL = "${endpointProperties.backend}/v1/file/${attachmentDto.filename}" } - return LabDto.of(lab, pdfURL) + val attachmentResponse = + attachmentService.createOneAttachmentResponse(lab.pdf) + + return LabDto.of(lab, attachmentResponse) } } 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 4a1f64cf..15d7410e 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 @@ -33,7 +33,7 @@ interface AttachmentService { contentEntityType: AttachmentContentEntityType, requestAttachments: List ): List - + fun createOneAttachmentResponse(attachment: AttachmentEntity?): AttachmentResponse? fun createAttachmentResponses(attachments: List?): List fun deleteAttachments(ids: List?) @@ -111,6 +111,23 @@ class AttachmentServiceImpl( return attachmentsList } + @Transactional + override fun createOneAttachmentResponse(attachment: AttachmentEntity?): AttachmentResponse? { + var attachmentDto: AttachmentResponse? = null + if (attachment != null) { + if (attachment.isDeleted == false) { + attachmentDto = AttachmentResponse( + id = attachment.id, + name = attachment.filename.substringAfter("_"), + url = "${endpointProperties.backend}/v1/file/${attachment.filename}", + bytes = attachment.size + ) + } + } + + return attachmentDto + } + @Transactional override fun createAttachmentResponses(attachments: List?): List { val list = mutableListOf() From c8f01b531e2f53e40c95854704ba96b07cad5c7e 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, 16 Mar 2024 01:18:39 +0900 Subject: [PATCH 171/214] Feat: Add more type to academic. (#221) --- .../api/res/AcademicsSearchResElement.kt | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/res/AcademicsSearchResElement.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/res/AcademicsSearchResElement.kt index 8958fdcf..452666c5 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/res/AcademicsSearchResElement.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/res/AcademicsSearchResElement.kt @@ -3,14 +3,18 @@ package com.wafflestudio.csereal.core.academics.api.res import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.common.utils.substringAroundKeyword +import com.wafflestudio.csereal.core.academics.database.AcademicsPostType import com.wafflestudio.csereal.core.academics.database.AcademicsSearchEntity import com.wafflestudio.csereal.core.academics.database.AcademicsSearchType +import com.wafflestudio.csereal.core.academics.database.AcademicsStudentType data class AcademicsSearchResElement( val id: Long, val language: String, val name: String, - val academicsType: AcademicsSearchType, + val postType: AcademicsSearchType, + val studentType: AcademicsStudentType? = null, + val academicType: AcademicsPostType? = null, val partialDescription: String, val boldStartIndex: Int, val boldEndIndex: Int @@ -30,17 +34,21 @@ data class AcademicsSearchResElement( academicsSearch.content, amount ) - AcademicsSearchResElement( - id = academicsSearch.academics!!.id, - name = academicsSearch.academics!!.name, - language = academicsSearch.academics!!.language.let { - LanguageType.makeLowercase(it) - }, - academicsType = AcademicsSearchType.ACADEMICS, - partialDescription = partialDescription.replace("\n", " "), - boldStartIndex = startIdx ?: 0, - boldEndIndex = startIdx?.plus(keyword.length) ?: 0 - ) + academicsSearch.academics!!.let { + AcademicsSearchResElement( + id = it.id, + name = it.name, + language = it.language.let { lan -> + LanguageType.makeLowercase(lan) + }, + postType = AcademicsSearchType.ACADEMICS, + academicType = it.postType, + studentType = it.studentType, + partialDescription = partialDescription.replace("\n", " "), + boldStartIndex = startIdx ?: 0, + boldEndIndex = startIdx?.plus(keyword.length) ?: 0 + ) + } } academicsSearch.academics == null && @@ -57,7 +65,7 @@ data class AcademicsSearchResElement( language = academicsSearch.course!!.language.let { LanguageType.makeLowercase(it) }, - academicsType = AcademicsSearchType.COURSE, + postType = AcademicsSearchType.COURSE, partialDescription = partialDescription.replace("\n", " "), boldStartIndex = startIdx ?: 0, boldEndIndex = startIdx?.plus(keyword.length) ?: 0 @@ -78,7 +86,7 @@ data class AcademicsSearchResElement( language = academicsSearch.scholarship!!.language.let { LanguageType.makeLowercase(it) }, - academicsType = AcademicsSearchType.SCHOLARSHIP, + postType = AcademicsSearchType.SCHOLARSHIP, partialDescription = partialDescription.replace("\n", " "), boldStartIndex = startIdx ?: 0, boldEndIndex = startIdx?.plus(keyword.length) ?: 0 From 43b96c9baf1573b5f37f65e074000ed84ddb6d0e 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, 16 Mar 2024 01:28:39 +0900 Subject: [PATCH 172/214] Fix: Change partialDescription to give with searchContent. (without html, all fields) (#222) --- .../csereal/core/admissions/api/res/AdmissionSearchResElem.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/res/AdmissionSearchResElem.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/res/AdmissionSearchResElem.kt index 522aa41c..4de31ae2 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/res/AdmissionSearchResElem.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/res/AdmissionSearchResElem.kt @@ -22,7 +22,7 @@ data class AdmissionSearchResElem private constructor( ) = admissions.let { val (boldStartIdx, partialDescription) = substringAroundKeyword( keyword = keyword, - content = it.description, + content = it.searchContent, amount = amount ) From 345b3ecd527b0b95314fa585caf5534e64cd9b18 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Sat, 16 Mar 2024 11:38:29 +0900 Subject: [PATCH 173/214] =?UTF-8?q?fix:=20=EB=8B=A8=EA=B1=B4=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20&=20=ED=86=B5=ED=95=A9=20=EA=B2=80=EC=83=89=20?= =?UTF-8?q?=EA=B6=8C=ED=95=9C=20=EC=B2=B4=ED=81=AC=20(#217)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: private 단건 조회 권한 체크 * Fix: Notice 비밀글 표시 안되도록 수정 (#216) * Feat: Add FindByIdAndIsPrivateFalse * Fix: Change totalSearchNotice to get isStaff and hide private when not a staff. * Fix: Change searchTotalNotice and readNotice to consider isStaff. * Fix: Change totalSearchNotice and readNotice to check authentication and get staff info. * fix: newsTotalSearch 권한 체크 * style: ktlint --------- Co-authored-by: 우혁준 (HyukJoon Woo) --- .../csereal/common/utils/Utils.kt | 16 +++++++ .../csereal/core/news/api/NewsController.kt | 42 +++++++++-------- .../core/news/database/NewsRepository.kt | 10 +++-- .../csereal/core/news/service/NewsService.kt | 14 +++--- .../core/notice/api/NoticeController.kt | 45 +++++-------------- .../core/notice/service/NoticeService.kt | 9 ++-- .../core/seminar/api/SeminarController.kt | 26 +++++------ .../core/seminar/service/SeminarService.kt | 6 ++- 8 files changed, 88 insertions(+), 80 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 1b9cef0c..da3564b8 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/utils/Utils.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/utils/Utils.kt @@ -1,8 +1,12 @@ package com.wafflestudio.csereal.common.utils +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.common.mockauth.CustomPrincipal import org.jsoup.Jsoup import org.jsoup.parser.Parser import org.jsoup.safety.Safelist +import org.springframework.security.core.Authentication +import org.springframework.security.oauth2.core.oidc.user.OidcUser import kotlin.math.ceil fun cleanTextFromHtml(description: String): String { @@ -40,3 +44,15 @@ fun exchangeValidPageNum(pageSize: Int, pageNum: Int, total: Long): Int { else -> ceil(total.toDouble() / pageSize).toInt() } } + +fun getUsername(authentication: Authentication?): String? { + val principal = authentication?.principal + + return principal?.let { + when (principal) { + is OidcUser -> principal.idToken.getClaim("username") + is CustomPrincipal -> principal.userEntity.username + else -> throw CserealException.Csereal401("Unsupported principal type") + } + } +} 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 193a097a..0f253dc7 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,10 +1,10 @@ package com.wafflestudio.csereal.core.news.api -import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.common.aop.AuthenticatedStaff -import com.wafflestudio.csereal.common.mockauth.CustomPrincipal +import com.wafflestudio.csereal.common.utils.getUsername 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.news.service.NewsService import com.wafflestudio.csereal.core.user.database.Role import com.wafflestudio.csereal.core.user.database.UserRepository @@ -16,7 +16,6 @@ import org.springframework.data.domain.PageRequest import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.security.core.Authentication -import org.springframework.security.oauth2.core.oidc.user.OidcUser import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile @@ -34,15 +33,9 @@ class NewsController( @RequestParam(required = false, defaultValue = "10") pageSize: Int, authentication: Authentication? ): ResponseEntity { - val principal = authentication?.principal - - val isStaff = principal?.let { - val username = when (principal) { - is OidcUser -> principal.idToken.getClaim("username") - is CustomPrincipal -> principal.userEntity.username - else -> throw CserealException.Csereal401("Unsupported principal type") - } - val user = userRepository.findByUsername(username) + val username = getUsername(authentication) + val isStaff = username?.let { + val user = userRepository.findByUsername(it) user?.role == Role.ROLE_STAFF } ?: false @@ -59,16 +52,29 @@ class NewsController( @NotBlank keyword: String, @RequestParam(required = true) @Positive number: Int, - @RequestParam(required = false, defaultValue = "200") @Positive stringLength: Int - ) = ResponseEntity.ok( - newsService.searchTotalNews(keyword, number, stringLength) - ) + @RequestParam(required = false, defaultValue = "200") @Positive stringLength: Int, + authentication: Authentication? + ): NewsTotalSearchDto { + val username = getUsername(authentication) + val isStaff = username?.let { + val user = userRepository.findByUsername(it) + user?.role == Role.ROLE_STAFF + } ?: false + + return newsService.searchTotalNews(keyword, number, stringLength, isStaff) + } @GetMapping("/{newsId}") fun readNews( - @PathVariable newsId: Long + @PathVariable newsId: Long, + authentication: Authentication? ): ResponseEntity { - return ResponseEntity.ok(newsService.readNews(newsId)) + val username = getUsername(authentication) + val isStaff = username?.let { + val user = userRepository.findByUsername(it) + user?.role == Role.ROLE_STAFF + } ?: false + return ResponseEntity.ok(newsService.readNews(newsId, isStaff)) } @AuthenticatedStaff 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 a27805d1..cf6711d9 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 @@ -46,7 +46,8 @@ interface CustomNewsRepository { keyword: String, number: Int, amount: Int, - imageUrlCreator: (MainImageEntity?) -> String? + imageUrlCreator: (MainImageEntity?) -> String?, + isStaff: Boolean ): NewsTotalSearchDto fun readAllSlides(pageNum: Long, pageSize: Int): AdminSlidesResponse @@ -135,7 +136,8 @@ class NewsRepositoryImpl( keyword: String, number: Int, amount: Int, - imageUrlCreator: (MainImageEntity?) -> String? + imageUrlCreator: (MainImageEntity?) -> String?, + isStaff: Boolean ): NewsTotalSearchDto { val doubleTemplate = commonRepository.searchFullDoubleTextTemplate( keyword, @@ -143,6 +145,8 @@ class NewsRepositoryImpl( newsEntity.plainTextDescription ) + val privateBoolean = newsEntity.isPrivate.eq(false).takeUnless { isStaff } + val searchResult = queryFactory.select( newsEntity.id, newsEntity.title, @@ -151,7 +155,7 @@ class NewsRepositoryImpl( mainImageEntity ).from(newsEntity) .leftJoin(mainImageEntity) - .where(doubleTemplate.gt(0.0)) + .where(doubleTemplate.gt(0.0), privateBoolean) .orderBy(newsEntity.date.desc()) .limit(number.toLong()) .fetch() 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 e45a4169..0104a651 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 @@ -23,7 +23,7 @@ interface NewsService { isStaff: Boolean ): NewsSearchResponse - fun readNews(newsId: Long): NewsDto + fun readNews(newsId: Long, isStaff: Boolean): NewsDto fun createNews(request: NewsDto, mainImage: MultipartFile?, attachments: List?): NewsDto fun updateNews( newsId: Long, @@ -34,7 +34,7 @@ interface NewsService { fun deleteNews(newsId: Long) fun enrollTag(tagName: String) - fun searchTotalNews(keyword: String, number: Int, amount: Int): NewsTotalSearchDto + fun searchTotalNews(keyword: String, number: Int, amount: Int, isStaff: Boolean): NewsTotalSearchDto fun readAllSlides(pageNum: Long, pageSize: Int): AdminSlidesResponse fun unSlideManyNews(request: List) } @@ -62,21 +62,25 @@ class NewsServiceImpl( override fun searchTotalNews( keyword: String, number: Int, - amount: Int + amount: Int, + isStaff: Boolean ) = newsRepository.searchTotalNews( keyword, number, amount, - mainImageService::createImageURL + mainImageService::createImageURL, + isStaff ) @Transactional(readOnly = true) - override fun readNews(newsId: Long): NewsDto { + override fun readNews(newsId: Long, isStaff: Boolean): NewsDto { val news: NewsEntity = newsRepository.findByIdOrNull(newsId) ?: throw CserealException.Csereal404("존재하지 않는 새소식입니다.(newsId: $newsId)") if (news.isDeleted) throw CserealException.Csereal404("삭제된 새소식입니다.(newsId: $newsId)") + if (news.isPrivate && !isStaff) throw CserealException.Csereal401("접근 권한이 없습니다.") + val imageURL = mainImageService.createImageURL(news.mainImage) val attachmentResponses = attachmentService.createAttachmentResponses(news.attachments) 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 f9384227..9d5dd471 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,7 @@ package com.wafflestudio.csereal.core.notice.api -import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.common.aop.AuthenticatedStaff -import com.wafflestudio.csereal.common.mockauth.CustomPrincipal +import com.wafflestudio.csereal.common.utils.getUsername import com.wafflestudio.csereal.core.notice.dto.* import com.wafflestudio.csereal.core.notice.service.NoticeService import com.wafflestudio.csereal.core.user.database.Role @@ -15,7 +14,6 @@ import org.springframework.data.domain.PageRequest import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.security.core.Authentication -import org.springframework.security.oauth2.core.oidc.user.OidcUser import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile @@ -33,15 +31,9 @@ class NoticeController( @RequestParam(required = false, defaultValue = "20") pageSize: Int, authentication: Authentication? ): ResponseEntity { - val principal = authentication?.principal - - val isStaff = principal?.let { - val username = when (principal) { - is OidcUser -> principal.idToken.getClaim("username") - is CustomPrincipal -> principal.userEntity.username - else -> throw CserealException.Csereal401("Unsupported principal type") - } - val user = userRepository.findByUsername(username) + val username = getUsername(authentication) + val isStaff = username?.let { + val user = userRepository.findByUsername(it) user?.role == Role.ROLE_STAFF } ?: false @@ -61,15 +53,9 @@ class NoticeController( @RequestParam(required = false, defaultValue = "200") @Positive stringLength: Int, authentication: Authentication? ): NoticeTotalSearchResponse { - val principal = authentication?.principal - - val isStaff = principal?.let { - val username = when (principal) { - is OidcUser -> principal.idToken.getClaim("username") - is CustomPrincipal -> principal.userEntity.username - else -> throw CserealException.Csereal401("Unsupported principal type") - } - val user = userRepository.findByUsername(username) + val username = getUsername(authentication) + val isStaff = username?.let { + val user = userRepository.findByUsername(it) user?.role == Role.ROLE_STAFF } ?: false @@ -80,20 +66,13 @@ class NoticeController( fun readNotice( @PathVariable noticeId: Long, authentication: Authentication? - ): NoticeDto { - val principal = authentication?.principal - - val isStaff = principal?.let { - val username = when (principal) { - is OidcUser -> principal.idToken.getClaim("username") - is CustomPrincipal -> principal.userEntity.username - else -> throw CserealException.Csereal401("Unsupported principal type") - } - val user = userRepository.findByUsername(username) + ): ResponseEntity { + val username = getUsername(authentication) + val isStaff = username?.let { + val user = userRepository.findByUsername(it) user?.role == Role.ROLE_STAFF } ?: false - - return noticeService.readNotice(noticeId, isStaff) + return ResponseEntity.ok(noticeService.readNotice(noticeId, isStaff)) } @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 8904eb77..860941fd 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 @@ -68,14 +68,13 @@ class NoticeServiceImpl( @Transactional(readOnly = true) override fun readNotice(noticeId: Long, isStaff: Boolean): NoticeDto { - val notice = if (isStaff) { - noticeRepository.findByIdOrNull(noticeId) - } else { - noticeRepository.findByIdAndIsPrivateFalse(noticeId) - } ?: throw CserealException.Csereal404("존재하지 않는 공지사항입니다.(noticeId: $noticeId)") + val notice = noticeRepository.findByIdOrNull(noticeId) + ?: throw CserealException.Csereal404("존재하지 않는 공지사항입니다.(noticeId: $noticeId)") if (notice.isDeleted) throw CserealException.Csereal404("삭제된 공지사항입니다.(noticeId: $noticeId)") + if (notice.isPrivate && !isStaff) throw CserealException.Csereal401("접근 권한이 없습니다.") + val attachmentResponses = attachmentService.createAttachmentResponses(notice.attachments) val prevNotice = 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 bbf80373..fe744ccc 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,8 +1,7 @@ package com.wafflestudio.csereal.core.seminar.api -import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.common.aop.AuthenticatedStaff -import com.wafflestudio.csereal.common.mockauth.CustomPrincipal +import com.wafflestudio.csereal.common.utils.getUsername import com.wafflestudio.csereal.core.seminar.dto.SeminarDto import com.wafflestudio.csereal.core.seminar.dto.SeminarSearchResponse import com.wafflestudio.csereal.core.seminar.service.SeminarService @@ -12,7 +11,6 @@ import jakarta.validation.Valid import org.springframework.data.domain.PageRequest import org.springframework.http.ResponseEntity import org.springframework.security.core.Authentication -import org.springframework.security.oauth2.core.oidc.user.OidcUser import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile @@ -29,15 +27,9 @@ class SeminarController( @RequestParam(required = false, defaultValue = "10") pageSize: Int, authentication: Authentication? ): ResponseEntity { - val principal = authentication?.principal - - val isStaff = principal?.let { - val username = when (principal) { - is OidcUser -> principal.idToken.getClaim("username") - is CustomPrincipal -> principal.userEntity.username - else -> throw CserealException.Csereal401("Unsupported principal type") - } - val user = userRepository.findByUsername(username) + val username = getUsername(authentication) + val isStaff = username?.let { + val user = userRepository.findByUsername(it) user?.role == Role.ROLE_STAFF } ?: false @@ -61,9 +53,15 @@ class SeminarController( @GetMapping("/{seminarId}") fun readSeminar( - @PathVariable seminarId: Long + @PathVariable seminarId: Long, + authentication: Authentication? ): ResponseEntity { - return ResponseEntity.ok(seminarService.readSeminar(seminarId)) + val username = getUsername(authentication) + val isStaff = username?.let { + val user = userRepository.findByUsername(it) + user?.role == Role.ROLE_STAFF + } ?: false + return ResponseEntity.ok(seminarService.readSeminar(seminarId, isStaff)) } @AuthenticatedStaff 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 a3cccb30..d6237be8 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,7 @@ interface SeminarService { ): SeminarSearchResponse fun createSeminar(request: SeminarDto, mainImage: MultipartFile?, attachments: List?): SeminarDto - fun readSeminar(seminarId: Long): SeminarDto + fun readSeminar(seminarId: Long, isStaff: Boolean): SeminarDto fun updateSeminar( seminarId: Long, request: SeminarDto, @@ -73,12 +73,14 @@ class SeminarServiceImpl( } @Transactional(readOnly = true) - override fun readSeminar(seminarId: Long): SeminarDto { + override fun readSeminar(seminarId: Long, isStaff: Boolean): SeminarDto { val seminar: SeminarEntity = seminarRepository.findByIdOrNull(seminarId) ?: throw CserealException.Csereal404("존재하지 않는 세미나입니다.(seminarId: $seminarId)") if (seminar.isDeleted) throw CserealException.Csereal400("삭제된 세미나입니다. (seminarId: $seminarId)") + if (seminar.isPrivate && !isStaff) throw CserealException.Csereal401("접근 권한이 없습니다.") + val imageURL = mainImageService.createImageURL(seminar.mainImage) val attachmentResponses = attachmentService.createAttachmentResponses(seminar.attachments) From e039808f1eaea7e04485d33f3722f8807b428513 Mon Sep 17 00:00:00 2001 From: huGgW Date: Sat, 16 Mar 2024 11:56:48 +0900 Subject: [PATCH 174/214] Fix: Formatting issue. --- .../com/wafflestudio/csereal/core/news/api/NewsController.kt | 1 - .../com/wafflestudio/csereal/core/notice/api/NoticeController.kt | 1 - .../wafflestudio/csereal/core/seminar/api/SeminarController.kt | 1 - 3 files changed, 3 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 58bbf76b..0f253dc7 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,6 +1,5 @@ package com.wafflestudio.csereal.core.news.api -import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.common.aop.AuthenticatedStaff import com.wafflestudio.csereal.common.utils.getUsername import com.wafflestudio.csereal.core.news.dto.NewsDto 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 2b5f37f6..9d5dd471 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,6 +1,5 @@ package com.wafflestudio.csereal.core.notice.api -import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.common.aop.AuthenticatedStaff import com.wafflestudio.csereal.common.utils.getUsername import com.wafflestudio.csereal.core.notice.dto.* 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 7449f7b7..fe744ccc 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.common.CserealException import com.wafflestudio.csereal.common.aop.AuthenticatedStaff import com.wafflestudio.csereal.common.utils.getUsername import com.wafflestudio.csereal.core.seminar.dto.SeminarDto From b35265455d9bde4789cca39811fbda354449a0e7 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 Mar 2024 15:14:13 +0900 Subject: [PATCH 175/214] Fix: Add studentType for all academic search result. (#224) --- .../core/academics/api/res/AcademicsSearchResElement.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/res/AcademicsSearchResElement.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/res/AcademicsSearchResElement.kt index 452666c5..e88522b6 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/res/AcademicsSearchResElement.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/res/AcademicsSearchResElement.kt @@ -13,7 +13,7 @@ data class AcademicsSearchResElement( val language: String, val name: String, val postType: AcademicsSearchType, - val studentType: AcademicsStudentType? = null, + val studentType: AcademicsStudentType, val academicType: AcademicsPostType? = null, val partialDescription: String, val boldStartIndex: Int, @@ -66,6 +66,7 @@ data class AcademicsSearchResElement( LanguageType.makeLowercase(it) }, postType = AcademicsSearchType.COURSE, + studentType = academicsSearch.course!!.studentType, partialDescription = partialDescription.replace("\n", " "), boldStartIndex = startIdx ?: 0, boldEndIndex = startIdx?.plus(keyword.length) ?: 0 @@ -87,6 +88,7 @@ data class AcademicsSearchResElement( LanguageType.makeLowercase(it) }, postType = AcademicsSearchType.SCHOLARSHIP, + studentType = academicsSearch.scholarship!!.studentType, partialDescription = partialDescription.replace("\n", " "), boldStartIndex = startIdx ?: 0, boldEndIndex = startIdx?.plus(keyword.length) ?: 0 From 1d95ad3920370b3979bb0e9d6fe8010bed4d5e3b 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 Mar 2024 23:12:39 +0900 Subject: [PATCH 176/214] =?UTF-8?q?Config:=20Spring=20Boot=203.2,=20Java?= =?UTF-8?q?=2021=EB=A1=9C=20=EC=97=85=EA=B7=B8=EB=A0=88=EC=9D=B4=EB=93=9C,?= =?UTF-8?q?=20Virtual=20Thread=20=EC=A0=81=EC=9A=A9=20(#223)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build_and_test.yaml | 4 +-- .github/workflows/deploy.yaml | 4 +-- .github/workflows/ktlint-check.yml | 3 +- Dockerfile | 4 +-- build.gradle.kts | 34 +++++++++---------- .../common/config/MySQLDialectCustom.kt | 10 +++--- src/main/resources/application.yaml | 3 ++ 7 files changed, 33 insertions(+), 29 deletions(-) diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml index daef1551..97430304 100644 --- a/.github/workflows/build_and_test.yaml +++ b/.github/workflows/build_and_test.yaml @@ -20,8 +20,8 @@ jobs: - name: Setup Java JDK uses: actions/setup-java@v3.12.0 with: - java-version: '17' - distribution: 'adopt' + java-version: '21' + distribution: 'temurin' - name: Build with Gradle run: ./gradlew clean build -x test diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index bba8f58f..32ddf4e8 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -28,8 +28,8 @@ jobs: - name: Setup Java JDK uses: actions/setup-java@v3.12.0 with: - java-version: '17' - distribution: 'adopt' + java-version: '21' + distribution: 'temurin' - run: ./gradlew clean bootJar -x test diff --git a/.github/workflows/ktlint-check.yml b/.github/workflows/ktlint-check.yml index cff9a7fb..7ebeb4c0 100644 --- a/.github/workflows/ktlint-check.yml +++ b/.github/workflows/ktlint-check.yml @@ -14,7 +14,8 @@ jobs: - name: Set up JDK uses: actions/setup-java@v1 with: - java-version: 17 + java-version: 21 + distribution: 'temurin' - name: Grant execute permission for gradlew run: chmod +x gradlew - name: ktlintCheck with Gradle diff --git a/Dockerfile b/Dockerfile index 63e3df63..dd8b3e1c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM openjdk:17-slim +FROM openjdk:21-slim # Set profile ARG PROFILE @@ -19,4 +19,4 @@ RUN mkdir /app/cse-files # Expose port 8080 EXPOSE 8080 -ENTRYPOINT java -Dspring.profiles.active=$profile -jar app.jar \ No newline at end of file +ENTRYPOINT java -Dspring.profiles.active=$profile -jar app.jar diff --git a/build.gradle.kts b/build.gradle.kts index ba4bcd26..23d0efab 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,12 +1,12 @@ 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.2.3" + id("io.spring.dependency-management") version "1.1.4" + kotlin("jvm") version "1.9.22" + kotlin("plugin.spring") version "1.9.22" + kotlin("plugin.jpa") version "1.9.22" + kotlin("kapt") version "1.9.22" id("org.jlleitschuh.gradle.ktlint") version "11.6.0" } @@ -14,7 +14,7 @@ group = "com.wafflestudio" version = "0.0.1-SNAPSHOT" java { - sourceCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_21 } repositories { @@ -29,33 +29,33 @@ 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") + implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.4.0") runtimeOnly("com.mysql:mysql-connector-j") 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") + testImplementation("io.kotest:kotest-runner-junit5-jvm:5.8.1") + testImplementation("io.kotest:kotest-assertions-core-jvm:5.8.1") + testImplementation("io.kotest:kotest-property-jvm:5.8.1") implementation("io.kotest.extensions:kotest-extensions-spring:1.1.3") // mockk - testImplementation("io.mockk:mockk:1.13.7") + testImplementation("io.mockk:mockk:1.13.10") 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") + // queryDsl + implementation("com.querydsl:querydsl-jpa:5.1.0:jakarta") + kapt("com.querydsl:querydsl-apt:5.1.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.17.2") // 이미지 업로드 implementation("commons-io:commons-io:2.11.0") @@ -85,7 +85,7 @@ apply { tasks.withType { kotlinOptions { freeCompilerArgs += "-Xjsr305=strict" - jvmTarget = "17" + jvmTarget = "21" } } 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 2214526a..8a9be910 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/MySQLDialectCustom.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/MySQLDialectCustom.kt @@ -1,18 +1,18 @@ package com.wafflestudio.csereal.common.config +import org.hibernate.boot.model.FunctionContributions 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) + override fun initializeFunctionRegistry(functionContributions: FunctionContributions?) { + super.initializeFunctionRegistry(functionContributions) - val basicTypeRegistry = queryEngine?.typeConfiguration?.basicTypeRegistry - val functionRegistry = queryEngine?.sqmFunctionRegistry + val basicTypeRegistry = functionContributions?.typeConfiguration?.basicTypeRegistry + val functionRegistry = functionContributions?.functionRegistry if (basicTypeRegistry != null && functionRegistry != null) { functionRegistry.registerPattern( diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 654feeca..323f7d67 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1,4 +1,7 @@ spring: + threads: + virtual: + enabled: false profiles: active: local datasource: From 51bd3fae3c5ea267e7eeda73bb2ccfe5587456f8 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, 20 Mar 2024 19:53:56 +0900 Subject: [PATCH 177/214] Fix: add join condition for news total search (#226) --- .../csereal/core/news/database/NewsRepository.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 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 cf6711d9..34957c45 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 @@ -154,7 +154,7 @@ class NewsRepositoryImpl( newsEntity.plainTextDescription, mainImageEntity ).from(newsEntity) - .leftJoin(mainImageEntity) + .leftJoin(mainImageEntity).on(newsEntity.mainImage.eq(mainImageEntity)) .where(doubleTemplate.gt(0.0), privateBoolean) .orderBy(newsEntity.date.desc()) .limit(number.toLong()) @@ -164,8 +164,8 @@ class NewsRepositoryImpl( newsTagEntity.news.id, newsTagEntity.tag.name ).from(newsTagEntity) - .rightJoin(newsEntity) - .leftJoin(tagInNewsEntity) + .rightJoin(newsEntity).on(newsTagEntity.news.eq(newsEntity)) + .leftJoin(tagInNewsEntity).on(newsTagEntity.tag.eq(tagInNewsEntity)) .where(newsTagEntity.news.id.`in`(searchResult.map { it[newsEntity.id] })) .distinct() .fetch() From 5476d99597ad10fb10f29219259a7432fc499807 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, 28 Mar 2024 16:26:44 +0900 Subject: [PATCH 178/214] =?UTF-8?q?Feat:=20Spring=20Boot=20=EC=84=9C?= =?UTF-8?q?=EB=B2=84=EC=97=90=EC=84=9C=20Error=20=EB=B0=9C=EC=83=9D=20?= =?UTF-8?q?=EC=8B=9C=20Slack=EC=9C=BC=EB=A1=9C=20=EB=A1=9C=EA=B7=B8=20?= =?UTF-8?q?=EC=A0=84=EC=86=A1=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?(#228)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Dependency: Add slack appender * Config: Add slack appender for sending error log to slack. * Config: Add slack api info when making env file. * Feat: Add to logging error for SQLIntegrityConstraintViolationException and RESTClientException * Config: Update gradle wrapper --- .github/workflows/deploy.yaml | 2 + build.gradle.kts | 3 ++ gradle/wrapper/gradle-wrapper.jar | Bin 62076 -> 43462 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 22 ++++---- .../common/config/CserealExceptionHandler.kt | 4 ++ src/main/resources/application.yaml | 27 ++++++---- src/main/resources/logback-spring.xml | 51 ++++++++++++++++++ 8 files changed, 92 insertions(+), 20 deletions(-) create mode 100644 src/main/resources/logback-spring.xml diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 32ddf4e8..0c120312 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -60,6 +60,8 @@ jobs: echo "PROFILE=prod" >> .env echo "OIDC_CLIENT_SECRET=${{secrets.OIDC_CLIENT_SECRET}}" >> .env echo "URL=${{secrets.URL}}" >> .env + echo "SLACK_TOKEN=${{secrets.SLACK_TOKEN}}" >> .env + echo "SLACK_CHANNEL=${{secrets.SLACK_CHANNEL}}" >> .env - name: SCP Command to Transfer Files uses: appleboy/scp-action@v0.1.4 diff --git a/build.gradle.kts b/build.gradle.kts index 23d0efab..758f74cf 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -54,6 +54,9 @@ dependencies { kapt("jakarta.annotation:jakarta.annotation-api") kapt("jakarta.persistence:jakarta.persistence-api") + // slack log appender + implementation("com.cyfrania:logback-slack-appender:1.2") + // 태그 제거 implementation("org.jsoup:jsoup:1.17.2") diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index c1962a79e29d3e0ab67b14947c167a862655af9b..d64cd4917707c1f8861d8cb53dd15194d4248596 100644 GIT binary patch literal 43462 zcma&NWl&^owk(X(xVyW%ySuwf;qI=D6|RlDJ2cR^yEKh!@I- zp9QeisK*rlxC>+~7Dk4IxIRsKBHqdR9b3+fyL=ynHmIDe&|>O*VlvO+%z5;9Z$|DJ zb4dO}-R=MKr^6EKJiOrJdLnCJn>np?~vU-1sSFgPu;pthGwf}bG z(1db%xwr#x)r+`4AGu$j7~u2MpVs3VpLp|mx&;>`0p0vH6kF+D2CY0fVdQOZ@h;A` z{infNyvmFUiu*XG}RNMNwXrbec_*a3N=2zJ|Wh5z* z5rAX$JJR{#zP>KY**>xHTuw?|-Rg|o24V)74HcfVT;WtQHXlE+_4iPE8QE#DUm%x0 zEKr75ur~W%w#-My3Tj`hH6EuEW+8K-^5P62$7Sc5OK+22qj&Pd1;)1#4tKihi=~8C zHiQSst0cpri6%OeaR`PY>HH_;CPaRNty%WTm4{wDK8V6gCZlG@U3$~JQZ;HPvDJcT1V{ z?>H@13MJcCNe#5z+MecYNi@VT5|&UiN1D4ATT+%M+h4c$t;C#UAs3O_q=GxK0}8%8 z8J(_M9bayxN}69ex4dzM_P3oh@ZGREjVvn%%r7=xjkqxJP4kj}5tlf;QosR=%4L5y zWhgejO=vao5oX%mOHbhJ8V+SG&K5dABn6!WiKl{|oPkq(9z8l&Mm%(=qGcFzI=eLu zWc_oCLyf;hVlB@dnwY98?75B20=n$>u3b|NB28H0u-6Rpl((%KWEBOfElVWJx+5yg z#SGqwza7f}$z;n~g%4HDU{;V{gXIhft*q2=4zSezGK~nBgu9-Q*rZ#2f=Q}i2|qOp z!!y4p)4o=LVUNhlkp#JL{tfkhXNbB=Ox>M=n6soptJw-IDI|_$is2w}(XY>a=H52d z3zE$tjPUhWWS+5h=KVH&uqQS=$v3nRs&p$%11b%5qtF}S2#Pc`IiyBIF4%A!;AVoI zXU8-Rpv!DQNcF~(qQnyyMy=-AN~U>#&X1j5BLDP{?K!%h!;hfJI>$mdLSvktEr*89 zdJHvby^$xEX0^l9g$xW-d?J;L0#(`UT~zpL&*cEh$L|HPAu=P8`OQZV!-}l`noSp_ zQ-1$q$R-gDL)?6YaM!=8H=QGW$NT2SeZlb8PKJdc=F-cT@j7Xags+Pr*jPtlHFnf- zh?q<6;)27IdPc^Wdy-mX%2s84C1xZq9Xms+==F4);O`VUASmu3(RlgE#0+#giLh-& zcxm3_e}n4{%|X zJp{G_j+%`j_q5}k{eW&TlP}J2wtZ2^<^E(O)4OQX8FDp6RJq!F{(6eHWSD3=f~(h} zJXCf7=r<16X{pHkm%yzYI_=VDP&9bmI1*)YXZeB}F? z(%QsB5fo*FUZxK$oX~X^69;x~j7ms8xlzpt-T15e9}$4T-pC z6PFg@;B-j|Ywajpe4~bk#S6(fO^|mm1hKOPfA%8-_iGCfICE|=P_~e;Wz6my&)h_~ zkv&_xSAw7AZ%ThYF(4jADW4vg=oEdJGVOs>FqamoL3Np8>?!W#!R-0%2Bg4h?kz5I zKV-rKN2n(vUL%D<4oj@|`eJ>0i#TmYBtYmfla;c!ATW%;xGQ0*TW@PTlGG><@dxUI zg>+3SiGdZ%?5N=8uoLA|$4isK$aJ%i{hECP$bK{J#0W2gQ3YEa zZQ50Stn6hqdfxJ*9#NuSLwKFCUGk@c=(igyVL;;2^wi4o30YXSIb2g_ud$ zgpCr@H0qWtk2hK8Q|&wx)}4+hTYlf;$a4#oUM=V@Cw#!$(nOFFpZ;0lc!qd=c$S}Z zGGI-0jg~S~cgVT=4Vo)b)|4phjStD49*EqC)IPwyeKBLcN;Wu@Aeph;emROAwJ-0< z_#>wVm$)ygH|qyxZaet&(Vf%pVdnvKWJn9`%DAxj3ot;v>S$I}jJ$FLBF*~iZ!ZXE zkvui&p}fI0Y=IDX)mm0@tAd|fEHl~J&K}ZX(Mm3cm1UAuwJ42+AO5@HwYfDH7ipIc zmI;1J;J@+aCNG1M`Btf>YT>~c&3j~Qi@Py5JT6;zjx$cvOQW@3oQ>|}GH?TW-E z1R;q^QFjm5W~7f}c3Ww|awg1BAJ^slEV~Pk`Kd`PS$7;SqJZNj->it4DW2l15}xP6 zoCl$kyEF%yJni0(L!Z&14m!1urXh6Btj_5JYt1{#+H8w?5QI%% zo-$KYWNMJVH?Hh@1n7OSu~QhSswL8x0=$<8QG_zepi_`y_79=nK=_ZP_`Em2UI*tyQoB+r{1QYZCpb?2OrgUw#oRH$?^Tj!Req>XiE#~B|~ z+%HB;=ic+R@px4Ld8mwpY;W^A%8%l8$@B@1m5n`TlKI6bz2mp*^^^1mK$COW$HOfp zUGTz-cN9?BGEp}5A!mDFjaiWa2_J2Iq8qj0mXzk; z66JBKRP{p%wN7XobR0YjhAuW9T1Gw3FDvR5dWJ8ElNYF94eF3ebu+QwKjtvVu4L zI9ip#mQ@4uqVdkl-TUQMb^XBJVLW(-$s;Nq;@5gr4`UfLgF$adIhd?rHOa%D);whv z=;krPp~@I+-Z|r#s3yCH+c1US?dnm+C*)r{m+86sTJusLdNu^sqLrfWed^ndHXH`m zd3#cOe3>w-ga(Dus_^ppG9AC>Iq{y%%CK+Cro_sqLCs{VLuK=dev>OL1dis4(PQ5R zcz)>DjEkfV+MO;~>VUlYF00SgfUo~@(&9$Iy2|G0T9BSP?&T22>K46D zL*~j#yJ?)^*%J3!16f)@Y2Z^kS*BzwfAQ7K96rFRIh>#$*$_Io;z>ux@}G98!fWR@ zGTFxv4r~v)Gsd|pF91*-eaZ3Qw1MH$K^7JhWIdX%o$2kCbvGDXy)a?@8T&1dY4`;L z4Kn+f%SSFWE_rpEpL9bnlmYq`D!6F%di<&Hh=+!VI~j)2mfil03T#jJ_s?}VV0_hp z7T9bWxc>Jm2Z0WMU?`Z$xE74Gu~%s{mW!d4uvKCx@WD+gPUQ zV0vQS(Ig++z=EHN)BR44*EDSWIyT~R4$FcF*VEY*8@l=218Q05D2$|fXKFhRgBIEE zdDFB}1dKkoO^7}{5crKX!p?dZWNz$m>1icsXG2N+((x0OIST9Zo^DW_tytvlwXGpn zs8?pJXjEG;T@qrZi%#h93?FP$!&P4JA(&H61tqQi=opRzNpm zkrG}$^t9&XduK*Qa1?355wd8G2CI6QEh@Ua>AsD;7oRUNLPb76m4HG3K?)wF~IyS3`fXuNM>${?wmB zpVz;?6_(Fiadfd{vUCBM*_kt$+F3J+IojI;9L(gc9n3{sEZyzR9o!_mOwFC#tQ{Q~ zP3-`#uK#tP3Q7~Q;4H|wjZHO8h7e4IuBxl&vz2w~D8)w=Wtg31zpZhz%+kzSzL*dV zwp@{WU4i;hJ7c2f1O;7Mz6qRKeASoIv0_bV=i@NMG*l<#+;INk-^`5w@}Dj~;k=|}qM1vq_P z|GpBGe_IKq|LNy9SJhKOQ$c=5L{Dv|Q_lZl=-ky*BFBJLW9&y_C|!vyM~rQx=!vun z?rZJQB5t}Dctmui5i31C_;_}CEn}_W%>oSXtt>@kE1=JW*4*v4tPp;O6 zmAk{)m!)}34pTWg8{i>($%NQ(Tl;QC@J@FfBoc%Gr&m560^kgSfodAFrIjF}aIw)X zoXZ`@IsMkc8_=w%-7`D6Y4e*CG8k%Ud=GXhsTR50jUnm+R*0A(O3UKFg0`K;qp1bl z7``HN=?39ic_kR|^R^~w-*pa?Vj#7|e9F1iRx{GN2?wK!xR1GW!qa=~pjJb-#u1K8 zeR?Y2i-pt}yJq;SCiVHODIvQJX|ZJaT8nO+(?HXbLefulKKgM^B(UIO1r+S=7;kLJ zcH}1J=Px2jsh3Tec&v8Jcbng8;V-`#*UHt?hB(pmOipKwf3Lz8rG$heEB30Sg*2rx zV<|KN86$soN(I!BwO`1n^^uF2*x&vJ$2d$>+`(romzHP|)K_KkO6Hc>_dwMW-M(#S zK(~SiXT1@fvc#U+?|?PniDRm01)f^#55;nhM|wi?oG>yBsa?~?^xTU|fX-R(sTA+5 zaq}-8Tx7zrOy#3*JLIIVsBmHYLdD}!0NP!+ITW+Thn0)8SS!$@)HXwB3tY!fMxc#1 zMp3H?q3eD?u&Njx4;KQ5G>32+GRp1Ee5qMO0lZjaRRu&{W<&~DoJNGkcYF<5(Ab+J zgO>VhBl{okDPn78<%&e2mR{jwVCz5Og;*Z;;3%VvoGo_;HaGLWYF7q#jDX=Z#Ml`H z858YVV$%J|e<1n`%6Vsvq7GmnAV0wW4$5qQ3uR@1i>tW{xrl|ExywIc?fNgYlA?C5 zh$ezAFb5{rQu6i7BSS5*J-|9DQ{6^BVQ{b*lq`xS@RyrsJN?-t=MTMPY;WYeKBCNg z^2|pN!Q^WPJuuO4!|P@jzt&tY1Y8d%FNK5xK(!@`jO2aEA*4 zkO6b|UVBipci?){-Ke=+1;mGlND8)6+P;8sq}UXw2hn;fc7nM>g}GSMWu&v&fqh

iViYT=fZ(|3Ox^$aWPp4a8h24tD<|8-!aK0lHgL$N7Efw}J zVIB!7=T$U`ao1?upi5V4Et*-lTG0XvExbf!ya{cua==$WJyVG(CmA6Of*8E@DSE%L z`V^$qz&RU$7G5mg;8;=#`@rRG`-uS18$0WPN@!v2d{H2sOqP|!(cQ@ zUHo!d>>yFArLPf1q`uBvY32miqShLT1B@gDL4XoVTK&@owOoD)OIHXrYK-a1d$B{v zF^}8D3Y^g%^cnvScOSJR5QNH+BI%d|;J;wWM3~l>${fb8DNPg)wrf|GBP8p%LNGN# z3EaIiItgwtGgT&iYCFy9-LG}bMI|4LdmmJt@V@% zb6B)1kc=T)(|L@0;wr<>=?r04N;E&ef+7C^`wPWtyQe(*pD1pI_&XHy|0gIGHMekd zF_*M4yi6J&Z4LQj65)S zXwdM{SwUo%3SbPwFsHgqF@V|6afT|R6?&S;lw=8% z3}@9B=#JI3@B*#4s!O))~z zc>2_4Q_#&+5V`GFd?88^;c1i7;Vv_I*qt!_Yx*n=;rj!82rrR2rQ8u5(Ejlo{15P% zs~!{%XJ>FmJ})H^I9bn^Re&38H{xA!0l3^89k(oU;bZWXM@kn$#aoS&Y4l^-WEn-fH39Jb9lA%s*WsKJQl?n9B7_~P z-XM&WL7Z!PcoF6_D>V@$CvUIEy=+Z&0kt{szMk=f1|M+r*a43^$$B^MidrT0J;RI` z(?f!O<8UZkm$_Ny$Hth1J#^4ni+im8M9mr&k|3cIgwvjAgjH z8`N&h25xV#v*d$qBX5jkI|xOhQn!>IYZK7l5#^P4M&twe9&Ey@@GxYMxBZq2e7?`q z$~Szs0!g{2fGcp9PZEt|rdQ6bhAgpcLHPz?f-vB?$dc*!9OL?Q8mn7->bFD2Si60* z!O%y)fCdMSV|lkF9w%x~J*A&srMyYY3{=&$}H zGQ4VG_?$2X(0|vT0{=;W$~icCI{b6W{B!Q8xdGhF|D{25G_5_+%s(46lhvNLkik~R z>nr(&C#5wwOzJZQo9m|U<;&Wk!_#q|V>fsmj1g<6%hB{jGoNUPjgJslld>xmODzGjYc?7JSuA?A_QzjDw5AsRgi@Y|Z0{F{!1=!NES-#*f^s4l0Hu zz468))2IY5dmD9pa*(yT5{EyP^G>@ZWumealS-*WeRcZ}B%gxq{MiJ|RyX-^C1V=0 z@iKdrGi1jTe8Ya^x7yyH$kBNvM4R~`fbPq$BzHum-3Zo8C6=KW@||>zsA8-Y9uV5V z#oq-f5L5}V<&wF4@X@<3^C%ptp6+Ce)~hGl`kwj)bsAjmo_GU^r940Z-|`<)oGnh7 zFF0Tde3>ui?8Yj{sF-Z@)yQd~CGZ*w-6p2U<8}JO-sRsVI5dBji`01W8A&3$?}lxBaC&vn0E$c5tW* zX>5(zzZ=qn&!J~KdsPl;P@bmA-Pr8T*)eh_+Dv5=Ma|XSle6t(k8qcgNyar{*ReQ8 zTXwi=8vr>!3Ywr+BhggHDw8ke==NTQVMCK`$69fhzEFB*4+H9LIvdt-#IbhZvpS}} zO3lz;P?zr0*0$%-Rq_y^k(?I{Mk}h@w}cZpMUp|ucs55bcloL2)($u%mXQw({Wzc~ z;6nu5MkjP)0C(@%6Q_I_vsWrfhl7Zpoxw#WoE~r&GOSCz;_ro6i(^hM>I$8y>`!wW z*U^@?B!MMmb89I}2(hcE4zN2G^kwyWCZp5JG>$Ez7zP~D=J^LMjSM)27_0B_X^C(M z`fFT+%DcKlu?^)FCK>QzSnV%IsXVcUFhFdBP!6~se&xxrIxsvySAWu++IrH;FbcY$ z2DWTvSBRfLwdhr0nMx+URA$j3i7_*6BWv#DXfym?ZRDcX9C?cY9sD3q)uBDR3uWg= z(lUIzB)G$Hr!){>E{s4Dew+tb9kvToZp-1&c?y2wn@Z~(VBhqz`cB;{E4(P3N2*nJ z_>~g@;UF2iG{Kt(<1PyePTKahF8<)pozZ*xH~U-kfoAayCwJViIrnqwqO}7{0pHw$ zs2Kx?s#vQr7XZ264>5RNKSL8|Ty^=PsIx^}QqOOcfpGUU4tRkUc|kc7-!Ae6!+B{o~7nFpm3|G5^=0#Bnm6`V}oSQlrX(u%OWnC zoLPy&Q;1Jui&7ST0~#+}I^&?vcE*t47~Xq#YwvA^6^} z`WkC)$AkNub|t@S!$8CBlwbV~?yp&@9h{D|3z-vJXgzRC5^nYm+PyPcgRzAnEi6Q^gslXYRv4nycsy-SJu?lMps-? zV`U*#WnFsdPLL)Q$AmD|0`UaC4ND07+&UmOu!eHruzV|OUox<+Jl|Mr@6~C`T@P%s zW7sgXLF2SSe9Fl^O(I*{9wsFSYb2l%-;&Pi^dpv!{)C3d0AlNY6!4fgmSgj_wQ*7Am7&$z;Jg&wgR-Ih;lUvWS|KTSg!&s_E9_bXBkZvGiC6bFKDWZxsD$*NZ#_8bl zG1P-#@?OQzED7@jlMJTH@V!6k;W>auvft)}g zhoV{7$q=*;=l{O>Q4a@ ziMjf_u*o^PsO)#BjC%0^h>Xp@;5$p{JSYDt)zbb}s{Kbt!T*I@Pk@X0zds6wsefuU zW$XY%yyRGC94=6mf?x+bbA5CDQ2AgW1T-jVAJbm7K(gp+;v6E0WI#kuACgV$r}6L? zd|Tj?^%^*N&b>Dd{Wr$FS2qI#Ucs1yd4N+RBUQiSZGujH`#I)mG&VKoDh=KKFl4=G z&MagXl6*<)$6P}*Tiebpz5L=oMaPrN+caUXRJ`D?=K9!e0f{@D&cZLKN?iNP@X0aF zE(^pl+;*T5qt?1jRC=5PMgV!XNITRLS_=9{CJExaQj;lt!&pdzpK?8p>%Mb+D z?yO*uSung=-`QQ@yX@Hyd4@CI^r{2oiu`%^bNkz+Nkk!IunjwNC|WcqvX~k=><-I3 zDQdbdb|!v+Iz01$w@aMl!R)koD77Xp;eZwzSl-AT zr@Vu{=xvgfq9akRrrM)}=!=xcs+U1JO}{t(avgz`6RqiiX<|hGG1pmop8k6Q+G_mv zJv|RfDheUp2L3=^C=4aCBMBn0aRCU(DQwX-W(RkRwmLeuJYF<0urcaf(=7)JPg<3P zQs!~G)9CT18o!J4{zX{_e}4eS)U-E)0FAt}wEI(c0%HkxgggW;(1E=>J17_hsH^sP z%lT0LGgbUXHx-K*CI-MCrP66UP0PvGqM$MkeLyqHdbgP|_Cm!7te~b8p+e6sQ_3k| zVcwTh6d83ltdnR>D^)BYQpDKlLk3g0Hdcgz2}%qUs9~~Rie)A-BV1mS&naYai#xcZ z(d{8=-LVpTp}2*y)|gR~;qc7fp26}lPcLZ#=JpYcn3AT9(UIdOyg+d(P5T7D&*P}# zQCYplZO5|7+r19%9e`v^vfSS1sbX1c%=w1;oyruXB%Kl$ACgKQ6=qNWLsc=28xJjg zwvsI5-%SGU|3p>&zXVl^vVtQT3o-#$UT9LI@Npz~6=4!>mc431VRNN8od&Ul^+G_kHC`G=6WVWM z%9eWNyy(FTO|A+@x}Ou3CH)oi;t#7rAxdIXfNFwOj_@Y&TGz6P_sqiB`Q6Lxy|Q{`|fgmRG(k+!#b*M+Z9zFce)f-7;?Km5O=LHV9f9_87; zF7%R2B+$?@sH&&-$@tzaPYkw0;=i|;vWdI|Wl3q_Zu>l;XdIw2FjV=;Mq5t1Q0|f< zs08j54Bp`3RzqE=2enlkZxmX6OF+@|2<)A^RNQpBd6o@OXl+i)zO%D4iGiQNuXd+zIR{_lb96{lc~bxsBveIw6umhShTX+3@ZJ=YHh@ zWY3(d0azg;7oHn>H<>?4@*RQbi>SmM=JrHvIG(~BrvI)#W(EAeO6fS+}mxxcc+X~W6&YVl86W9WFSS}Vz-f9vS?XUDBk)3TcF z8V?$4Q)`uKFq>xT=)Y9mMFVTUk*NIA!0$?RP6Ig0TBmUFrq*Q-Agq~DzxjStQyJ({ zBeZ;o5qUUKg=4Hypm|}>>L=XKsZ!F$yNTDO)jt4H0gdQ5$f|d&bnVCMMXhNh)~mN z@_UV6D7MVlsWz+zM+inZZp&P4fj=tm6fX)SG5H>OsQf_I8c~uGCig$GzuwViK54bcgL;VN|FnyQl>Ed7(@>=8$a_UKIz|V6CeVSd2(P z0Uu>A8A+muM%HLFJQ9UZ5c)BSAv_zH#1f02x?h9C}@pN@6{>UiAp>({Fn(T9Q8B z^`zB;kJ5b`>%dLm+Ol}ty!3;8f1XDSVX0AUe5P#@I+FQ-`$(a;zNgz)4x5hz$Hfbg z!Q(z26wHLXko(1`;(BAOg_wShpX0ixfWq3ponndY+u%1gyX)_h=v1zR#V}#q{au6; z!3K=7fQwnRfg6FXtNQmP>`<;!N137paFS%y?;lb1@BEdbvQHYC{976l`cLqn;b8lp zIDY>~m{gDj(wfnK!lpW6pli)HyLEiUrNc%eXTil|F2s(AY+LW5hkKb>TQ3|Q4S9rr zpDs4uK_co6XPsn_z$LeS{K4jFF`2>U`tbgKdyDne`xmR<@6AA+_hPNKCOR-Zqv;xk zu5!HsBUb^!4uJ7v0RuH-7?l?}b=w5lzzXJ~gZcxRKOovSk@|#V+MuX%Y+=;14i*%{)_gSW9(#4%)AV#3__kac1|qUy!uyP{>?U#5wYNq}y$S9pCc zFc~4mgSC*G~j0u#qqp9 z${>3HV~@->GqEhr_Xwoxq?Hjn#=s2;i~g^&Hn|aDKpA>Oc%HlW(KA1?BXqpxB;Ydx)w;2z^MpjJ(Qi(X!$5RC z*P{~%JGDQqojV>2JbEeCE*OEu!$XJ>bWA9Oa_Hd;y)F%MhBRi*LPcdqR8X`NQ&1L# z5#9L*@qxrx8n}LfeB^J{%-?SU{FCwiWyHp682F+|pa+CQa3ZLzBqN1{)h4d6+vBbV zC#NEbQLC;}me3eeYnOG*nXOJZEU$xLZ1<1Y=7r0(-U0P6-AqwMAM`a(Ed#7vJkn6plb4eI4?2y3yOTGmmDQ!z9`wzbf z_OY#0@5=bnep;MV0X_;;SJJWEf^E6Bd^tVJ9znWx&Ks8t*B>AM@?;D4oWUGc z!H*`6d7Cxo6VuyS4Eye&L1ZRhrRmN6Lr`{NL(wDbif|y&z)JN>Fl5#Wi&mMIr5i;x zBx}3YfF>>8EC(fYnmpu~)CYHuHCyr5*`ECap%t@y=jD>!_%3iiE|LN$mK9>- zHdtpy8fGZtkZF?%TW~29JIAfi2jZT8>OA7=h;8T{{k?c2`nCEx9$r zS+*&vt~2o^^J+}RDG@+9&M^K*z4p{5#IEVbz`1%`m5c2};aGt=V?~vIM}ZdPECDI)47|CWBCfDWUbxBCnmYivQ*0Nu_xb*C>~C9(VjHM zxe<*D<#dQ8TlpMX2c@M<9$w!RP$hpG4cs%AI){jp*Sj|*`m)5(Bw*A0$*i-(CA5#%>a)$+jI2C9r6|(>J8InryENI z$NohnxDUB;wAYDwrb*!N3noBTKPpPN}~09SEL18tkG zxgz(RYU_;DPT{l?Q$+eaZaxnsWCA^ds^0PVRkIM%bOd|G2IEBBiz{&^JtNsODs;5z zICt_Zj8wo^KT$7Bg4H+y!Df#3mbl%%?|EXe!&(Vmac1DJ*y~3+kRKAD=Ovde4^^%~ zw<9av18HLyrf*_>Slp;^i`Uy~`mvBjZ|?Ad63yQa#YK`4+c6;pW4?XIY9G1(Xh9WO8{F-Aju+nS9Vmv=$Ac0ienZ+p9*O%NG zMZKy5?%Z6TAJTE?o5vEr0r>f>hb#2w2U3DL64*au_@P!J!TL`oH2r*{>ffu6|A7tv zL4juf$DZ1MW5ZPsG!5)`k8d8c$J$o;%EIL0va9&GzWvkS%ZsGb#S(?{!UFOZ9<$a| zY|a+5kmD5N&{vRqkgY>aHsBT&`rg|&kezoD)gP0fsNYHsO#TRc_$n6Lf1Z{?+DLziXlHrq4sf(!>O{?Tj;Eh@%)+nRE_2VxbN&&%%caU#JDU%vL3}Cb zsb4AazPI{>8H&d=jUaZDS$-0^AxE@utGs;-Ez_F(qC9T=UZX=>ok2k2 ziTn{K?y~a5reD2A)P${NoI^>JXn>`IeArow(41c-Wm~)wiryEP(OS{YXWi7;%dG9v zI?mwu1MxD{yp_rrk!j^cKM)dc4@p4Ezyo%lRN|XyD}}>v=Xoib0gOcdXrQ^*61HNj z=NP|pd>@yfvr-=m{8$3A8TQGMTE7g=z!%yt`8`Bk-0MMwW~h^++;qyUP!J~ykh1GO z(FZ59xuFR$(WE;F@UUyE@Sp>`aVNjyj=Ty>_Vo}xf`e7`F;j-IgL5`1~-#70$9_=uBMq!2&1l zomRgpD58@)YYfvLtPW}{C5B35R;ZVvB<<#)x%srmc_S=A7F@DW8>QOEGwD6suhwCg z>Pa+YyULhmw%BA*4yjDp|2{!T98~<6Yfd(wo1mQ!KWwq0eg+6)o1>W~f~kL<-S+P@$wx*zeI|1t7z#Sxr5 zt6w+;YblPQNplq4Z#T$GLX#j6yldXAqj>4gAnnWtBICUnA&-dtnlh=t0Ho_vEKwV` z)DlJi#!@nkYV#$!)@>udAU*hF?V`2$Hf=V&6PP_|r#Iv*J$9)pF@X3`k;5})9^o4y z&)~?EjX5yX12O(BsFy-l6}nYeuKkiq`u9145&3Ssg^y{5G3Pse z9w(YVa0)N-fLaBq1`P!_#>SS(8fh_5!f{UrgZ~uEdeMJIz7DzI5!NHHqQtm~#CPij z?=N|J>nPR6_sL7!f4hD_|KH`vf8(Wpnj-(gPWH+ZvID}%?~68SwhPTC3u1_cB`otq z)U?6qo!ZLi5b>*KnYHWW=3F!p%h1;h{L&(Q&{qY6)_qxNfbP6E3yYpW!EO+IW3?@J z);4>g4gnl^8klu7uA>eGF6rIGSynacogr)KUwE_R4E5Xzi*Qir@b-jy55-JPC8c~( zo!W8y9OGZ&`xmc8;=4-U9=h{vCqfCNzYirONmGbRQlR`WWlgnY+1wCXbMz&NT~9*| z6@FrzP!LX&{no2!Ln_3|I==_4`@}V?4a;YZKTdw;vT<+K+z=uWbW(&bXEaWJ^W8Td z-3&1bY^Z*oM<=M}LVt>_j+p=2Iu7pZmbXrhQ_k)ysE9yXKygFNw$5hwDn(M>H+e1&9BM5!|81vd%r%vEm zqxY3?F@fb6O#5UunwgAHR9jp_W2zZ}NGp2%mTW@(hz7$^+a`A?mb8|_G*GNMJ) zjqegXQio=i@AINre&%ofexAr95aop5C+0MZ0m-l=MeO8m3epm7U%vZB8+I+C*iNFM z#T3l`gknX;D$-`2XT^Cg*vrv=RH+P;_dfF++cP?B_msQI4j+lt&rX2)3GaJx%W*Nn zkML%D{z5tpHH=dksQ*gzc|}gzW;lwAbxoR07VNgS*-c3d&8J|;@3t^ zVUz*J*&r7DFRuFVDCJDK8V9NN5hvpgGjwx+5n)qa;YCKe8TKtdnh{I7NU9BCN!0dq zczrBk8pE{{@vJa9ywR@mq*J=v+PG;?fwqlJVhijG!3VmIKs>9T6r7MJpC)m!Tc#>g zMtVsU>wbwFJEfwZ{vB|ZlttNe83)$iz`~#8UJ^r)lJ@HA&G#}W&ZH*;k{=TavpjWE z7hdyLZPf*X%Gm}i`Y{OGeeu^~nB8=`{r#TUrM-`;1cBvEd#d!kPqIgYySYhN-*1;L z^byj%Yi}Gx)Wnkosi337BKs}+5H5dth1JA{Ir-JKN$7zC)*}hqeoD(WfaUDPT>0`- z(6sa0AoIqASwF`>hP}^|)a_j2s^PQn*qVC{Q}htR z5-)duBFXT_V56-+UohKXlq~^6uf!6sA#ttk1o~*QEy_Y-S$gAvq47J9Vtk$5oA$Ct zYhYJ@8{hsC^98${!#Ho?4y5MCa7iGnfz}b9jE~h%EAAv~Qxu)_rAV;^cygV~5r_~?l=B`zObj7S=H=~$W zPtI_m%g$`kL_fVUk9J@>EiBH zOO&jtn~&`hIFMS5S`g8w94R4H40mdNUH4W@@XQk1sr17b{@y|JB*G9z1|CrQjd+GX z6+KyURG3;!*BQrentw{B2R&@2&`2}n(z-2&X7#r!{yg@Soy}cRD~j zj9@UBW+N|4HW4AWapy4wfUI- zZ`gSL6DUlgj*f1hSOGXG0IVH8HxK?o2|3HZ;KW{K+yPAlxtb)NV_2AwJm|E)FRs&& z=c^e7bvUsztY|+f^k7NXs$o1EUq>cR7C0$UKi6IooHWlK_#?IWDkvywnzg&ThWo^? z2O_N{5X39#?eV9l)xI(>@!vSB{DLt*oY!K1R8}_?%+0^C{d9a%N4 zoxHVT1&Lm|uDX%$QrBun5e-F`HJ^T$ zmzv)p@4ZHd_w9!%Hf9UYNvGCw2TTTbrj9pl+T9%-_-}L(tES>Or-}Z4F*{##n3~L~TuxjirGuIY#H7{%$E${?p{Q01 zi6T`n;rbK1yIB9jmQNycD~yZq&mbIsFWHo|ZAChSFPQa<(%d8mGw*V3fh|yFoxOOiWJd(qvVb!Z$b88cg->N=qO*4k~6;R==|9ihg&riu#P~s4Oap9O7f%crSr^rljeIfXDEg>wi)&v*a%7zpz<9w z*r!3q9J|390x`Zk;g$&OeN&ctp)VKRpDSV@kU2Q>jtok($Y-*x8_$2piTxun81@vt z!Vj?COa0fg2RPXMSIo26T=~0d`{oGP*eV+$!0I<(4azk&Vj3SiG=Q!6mX0p$z7I}; z9BJUFgT-K9MQQ-0@Z=^7R<{bn2Fm48endsSs`V7_@%8?Bxkqv>BDoVcj?K#dV#uUP zL1ND~?D-|VGKe3Rw_7-Idpht>H6XRLh*U7epS6byiGvJpr%d}XwfusjH9g;Z98H`x zyde%%5mhGOiL4wljCaWCk-&uE4_OOccb9c!ZaWt4B(wYl!?vyzl%7n~QepN&eFUrw zFIOl9c({``6~QD+43*_tzP{f2x41h(?b43^y6=iwyB)2os5hBE!@YUS5?N_tXd=h( z)WE286Fbd>R4M^P{!G)f;h<3Q>Fipuy+d2q-)!RyTgt;wr$(?9ox3;q+{E*ZQHhOn;lM`cjnu9 zXa48ks-v(~b*;MAI<>YZH(^NV8vjb34beE<_cwKlJoR;k6lJNSP6v}uiyRD?|0w+X@o1ONrH8a$fCxXpf? z?$DL0)7|X}Oc%h^zrMKWc-NS9I0Utu@>*j}b@tJ=ixQSJ={4@854wzW@E>VSL+Y{i z#0b=WpbCZS>kUCO_iQz)LoE>P5LIG-hv9E+oG}DtlIDF>$tJ1aw9^LuhLEHt?BCj& z(O4I8v1s#HUi5A>nIS-JK{v!7dJx)^Yg%XjNmlkWAq2*cv#tHgz`Y(bETc6CuO1VkN^L-L3j_x<4NqYb5rzrLC-7uOv z!5e`GZt%B782C5-fGnn*GhDF$%(qP<74Z}3xx+{$4cYKy2ikxI7B2N+2r07DN;|-T->nU&!=Cm#rZt%O_5c&1Z%nlWq3TKAW0w zQqemZw_ue--2uKQsx+niCUou?HjD`xhEjjQd3%rrBi82crq*~#uA4+>vR<_S{~5ce z-2EIl?~s z1=GVL{NxP1N3%=AOaC}j_Fv=ur&THz zyO!d9kHq|c73kpq`$+t+8Bw7MgeR5~`d7ChYyGCBWSteTB>8WAU(NPYt2Dk`@#+}= zI4SvLlyk#pBgVigEe`?NG*vl7V6m+<}%FwPV=~PvvA)=#ths==DRTDEYh4V5}Cf$z@#;< zyWfLY_5sP$gc3LLl2x+Ii)#b2nhNXJ{R~vk`s5U7Nyu^3yFg&D%Txwj6QezMX`V(x z=C`{76*mNb!qHHs)#GgGZ_7|vkt9izl_&PBrsu@}L`X{95-2jf99K)0=*N)VxBX2q z((vkpP2RneSIiIUEnGb?VqbMb=Zia+rF~+iqslydE34cSLJ&BJW^3knX@M;t*b=EA zNvGzv41Ld_T+WT#XjDB840vovUU^FtN_)G}7v)1lPetgpEK9YS^OWFkPoE{ovj^=@ zO9N$S=G$1ecndT_=5ehth2Lmd1II-PuT~C9`XVePw$y8J#dpZ?Tss<6wtVglm(Ok7 z3?^oi@pPio6l&!z8JY(pJvG=*pI?GIOu}e^EB6QYk$#FJQ%^AIK$I4epJ+9t?KjqA+bkj&PQ*|vLttme+`9G=L% ziadyMw_7-M)hS(3E$QGNCu|o23|%O+VN7;Qggp?PB3K-iSeBa2b}V4_wY`G1Jsfz4 z9|SdB^;|I8E8gWqHKx!vj_@SMY^hLEIbSMCuE?WKq=c2mJK z8LoG-pnY!uhqFv&L?yEuxo{dpMTsmCn)95xanqBrNPTgXP((H$9N${Ow~Is-FBg%h z53;|Y5$MUN)9W2HBe2TD`ct^LHI<(xWrw}$qSoei?}s)&w$;&!14w6B6>Yr6Y8b)S z0r71`WmAvJJ`1h&poLftLUS6Ir zC$bG9!Im_4Zjse)#K=oJM9mHW1{%l8sz$1o?ltdKlLTxWWPB>Vk22czVt|1%^wnN@*!l)}?EgtvhC>vlHm^t+ogpgHI1_$1ox9e;>0!+b(tBrmXRB`PY1vp-R**8N7 zGP|QqI$m(Rdu#=(?!(N}G9QhQ%o!aXE=aN{&wtGP8|_qh+7a_j_sU5|J^)vxq;# zjvzLn%_QPHZZIWu1&mRAj;Sa_97p_lLq_{~j!M9N^1yp3U_SxRqK&JnR%6VI#^E12 z>CdOVI^_9aPK2eZ4h&^{pQs}xsijXgFYRIxJ~N7&BB9jUR1fm!(xl)mvy|3e6-B3j zJn#ajL;bFTYJ2+Q)tDjx=3IklO@Q+FFM}6UJr6km7hj7th9n_&JR7fnqC!hTZoM~T zBeaVFp%)0cbPhejX<8pf5HyRUj2>aXnXBqDJe73~J%P(2C?-RT{c3NjE`)om! zl$uewSgWkE66$Kb34+QZZvRn`fob~Cl9=cRk@Es}KQm=?E~CE%spXaMO6YmrMl%9Q zlA3Q$3|L1QJ4?->UjT&CBd!~ru{Ih^in&JXO=|<6J!&qp zRe*OZ*cj5bHYlz!!~iEKcuE|;U4vN1rk$xq6>bUWD*u(V@8sG^7>kVuo(QL@Ki;yL zWC!FT(q{E8#on>%1iAS0HMZDJg{Z{^!De(vSIq&;1$+b)oRMwA3nc3mdTSG#3uYO_ z>+x;7p4I;uHz?ZB>dA-BKl+t-3IB!jBRgdvAbW!aJ(Q{aT>+iz?91`C-xbe)IBoND z9_Xth{6?(y3rddwY$GD65IT#f3<(0o#`di{sh2gm{dw*#-Vnc3r=4==&PU^hCv$qd zjw;>i&?L*Wq#TxG$mFIUf>eK+170KG;~+o&1;Tom9}}mKo23KwdEM6UonXgc z!6N(@k8q@HPw{O8O!lAyi{rZv|DpgfU{py+j(X_cwpKqcalcqKIr0kM^%Br3SdeD> zHSKV94Yxw;pjzDHo!Q?8^0bb%L|wC;4U^9I#pd5O&eexX+Im{ z?jKnCcsE|H?{uGMqVie_C~w7GX)kYGWAg%-?8|N_1#W-|4F)3YTDC+QSq1s!DnOML3@d`mG%o2YbYd#jww|jD$gotpa)kntakp#K;+yo-_ZF9qrNZw<%#C zuPE@#3RocLgPyiBZ+R_-FJ_$xP!RzWm|aN)S+{$LY9vvN+IW~Kf3TsEIvP+B9Mtm! zpfNNxObWQpLoaO&cJh5>%slZnHl_Q~(-Tfh!DMz(dTWld@LG1VRF`9`DYKhyNv z2pU|UZ$#_yUx_B_|MxUq^glT}O5Xt(Vm4Mr02><%C)@v;vPb@pT$*yzJ4aPc_FZ3z z3}PLoMBIM>q_9U2rl^sGhk1VUJ89=*?7|v`{!Z{6bqFMq(mYiA?%KbsI~JwuqVA9$H5vDE+VocjX+G^%bieqx->s;XWlKcuv(s%y%D5Xbc9+ zc(_2nYS1&^yL*ey664&4`IoOeDIig}y-E~_GS?m;D!xv5-xwz+G`5l6V+}CpeJDi^ z%4ed$qowm88=iYG+(`ld5Uh&>Dgs4uPHSJ^TngXP_V6fPyl~>2bhi20QB%lSd#yYn zO05?KT1z@?^-bqO8Cg`;ft>ilejsw@2%RR7;`$Vs;FmO(Yr3Fp`pHGr@P2hC%QcA|X&N2Dn zYf`MqXdHi%cGR@%y7Rg7?d3?an){s$zA{!H;Ie5exE#c~@NhQUFG8V=SQh%UxUeiV zd7#UcYqD=lk-}sEwlpu&H^T_V0{#G?lZMxL7ih_&{(g)MWBnCZxtXg znr#}>U^6!jA%e}@Gj49LWG@*&t0V>Cxc3?oO7LSG%~)Y5}f7vqUUnQ;STjdDU}P9IF9d9<$;=QaXc zL1^X7>fa^jHBu_}9}J~#-oz3Oq^JmGR#?GO7b9a(=R@fw@}Q{{@`Wy1vIQ#Bw?>@X z-_RGG@wt|%u`XUc%W{J z>iSeiz8C3H7@St3mOr_mU+&bL#Uif;+Xw-aZdNYUpdf>Rvu0i0t6k*}vwU`XNO2he z%miH|1tQ8~ZK!zmL&wa3E;l?!!XzgV#%PMVU!0xrDsNNZUWKlbiOjzH-1Uoxm8E#r`#2Sz;-o&qcqB zC-O_R{QGuynW14@)7&@yw1U}uP(1cov)twxeLus0s|7ayrtT8c#`&2~Fiu2=R;1_4bCaD=*E@cYI>7YSnt)nQc zohw5CsK%m?8Ack)qNx`W0_v$5S}nO|(V|RZKBD+btO?JXe|~^Qqur%@eO~<8-L^9d z=GA3-V14ng9L29~XJ>a5k~xT2152zLhM*@zlp2P5Eu}bywkcqR;ISbas&#T#;HZSf z2m69qTV(V@EkY(1Dk3`}j)JMo%ZVJ*5eB zYOjIisi+igK0#yW*gBGj?@I{~mUOvRFQR^pJbEbzFxTubnrw(Muk%}jI+vXmJ;{Q6 zrSobKD>T%}jV4Ub?L1+MGOD~0Ir%-`iTnWZN^~YPrcP5y3VMAzQ+&en^VzKEb$K!Q z<7Dbg&DNXuow*eD5yMr+#08nF!;%4vGrJI++5HdCFcGLfMW!KS*Oi@=7hFwDG!h2< zPunUEAF+HncQkbfFj&pbzp|MU*~60Z(|Ik%Tn{BXMN!hZOosNIseT?R;A`W?=d?5X zK(FB=9mZusYahp|K-wyb={rOpdn=@;4YI2W0EcbMKyo~-#^?h`BA9~o285%oY zfifCh5Lk$SY@|2A@a!T2V+{^!psQkx4?x0HSV`(w9{l75QxMk!)U52Lbhn{8ol?S) zCKo*7R(z!uk<6*qO=wh!Pul{(qq6g6xW;X68GI_CXp`XwO zxuSgPRAtM8K7}5E#-GM!*ydOOG_{A{)hkCII<|2=ma*71ci_-}VPARm3crFQjLYV! z9zbz82$|l01mv`$WahE2$=fAGWkd^X2kY(J7iz}WGS z@%MyBEO=A?HB9=^?nX`@nh;7;laAjs+fbo!|K^mE!tOB>$2a_O0y-*uaIn8k^6Y zSbuv;5~##*4Y~+y7Z5O*3w4qgI5V^17u*ZeupVGH^nM&$qmAk|anf*>r zWc5CV;-JY-Z@Uq1Irpb^O`L_7AGiqd*YpGUShb==os$uN3yYvb`wm6d=?T*it&pDk zo`vhw)RZX|91^^Wa_ti2zBFyWy4cJu#g)_S6~jT}CC{DJ_kKpT`$oAL%b^!2M;JgT zM3ZNbUB?}kP(*YYvXDIH8^7LUxz5oE%kMhF!rnPqv!GiY0o}NR$OD=ITDo9r%4E>E0Y^R(rS^~XjWyVI6 zMOR5rPXhTp*G*M&X#NTL`Hu*R+u*QNoiOKg4CtNPrjgH>c?Hi4MUG#I917fx**+pJfOo!zFM&*da&G_x)L(`k&TPI*t3e^{crd zX<4I$5nBQ8Ax_lmNRa~E*zS-R0sxkz`|>7q_?*e%7bxqNm3_eRG#1ae3gtV9!fQpY z+!^a38o4ZGy9!J5sylDxZTx$JmG!wg7;>&5H1)>f4dXj;B+@6tMlL=)cLl={jLMxY zbbf1ax3S4>bwB9-$;SN2?+GULu;UA-35;VY*^9Blx)Jwyb$=U!D>HhB&=jSsd^6yw zL)?a|>GxU!W}ocTC(?-%z3!IUhw^uzc`Vz_g>-tv)(XA#JK^)ZnC|l1`@CdX1@|!| z_9gQ)7uOf?cR@KDp97*>6X|;t@Y`k_N@)aH7gY27)COv^P3ya9I{4z~vUjLR9~z1Z z5=G{mVtKH*&$*t0@}-i_v|3B$AHHYale7>E+jP`ClqG%L{u;*ff_h@)al?RuL7tOO z->;I}>%WI{;vbLP3VIQ^iA$4wl6@0sDj|~112Y4OFjMs`13!$JGkp%b&E8QzJw_L5 zOnw9joc0^;O%OpF$Qp)W1HI!$4BaXX84`%@#^dk^hFp^pQ@rx4g(8Xjy#!X%+X5Jd@fs3amGT`}mhq#L97R>OwT5-m|h#yT_-v@(k$q7P*9X~T*3)LTdzP!*B} z+SldbVWrrwQo9wX*%FyK+sRXTa@O?WM^FGWOE?S`R(0P{<6p#f?0NJvnBia?k^fX2 zNQs7K-?EijgHJY}&zsr;qJ<*PCZUd*x|dD=IQPUK_nn)@X4KWtqoJNHkT?ZWL_hF? zS8lp2(q>;RXR|F;1O}EE#}gCrY~#n^O`_I&?&z5~7N;zL0)3Tup`%)oHMK-^r$NT% zbFg|o?b9w(q@)6w5V%si<$!U<#}s#x@0aX-hP>zwS#9*75VXA4K*%gUc>+yzupTDBOKH8WR4V0pM(HrfbQ&eJ79>HdCvE=F z|J>s;;iDLB^3(9}?biKbxf1$lI!*Z%*0&8UUq}wMyPs_hclyQQi4;NUY+x2qy|0J; zhn8;5)4ED1oHwg+VZF|80<4MrL97tGGXc5Sw$wAI#|2*cvQ=jB5+{AjMiDHmhUC*a zlmiZ`LAuAn_}hftXh;`Kq0zblDk8?O-`tnilIh|;3lZp@F_osJUV9`*R29M?7H{Fy z`nfVEIDIWXmU&YW;NjU8)EJpXhxe5t+scf|VXM!^bBlwNh)~7|3?fWwo_~ZFk(22% zTMesYw+LNx3J-_|DM~`v93yXe=jPD{q;li;5PD?Dyk+b? zo21|XpT@)$BM$%F=P9J19Vi&1#{jM3!^Y&fr&_`toi`XB1!n>sbL%U9I5<7!@?t)~ z;&H%z>bAaQ4f$wIzkjH70;<8tpUoxzKrPhn#IQfS%9l5=Iu))^XC<58D!-O z{B+o5R^Z21H0T9JQ5gNJnqh#qH^na|z92=hONIM~@_iuOi|F>jBh-?aA20}Qx~EpDGElELNn~|7WRXRFnw+Wdo`|# zBpU=Cz3z%cUJ0mx_1($X<40XEIYz(`noWeO+x#yb_pwj6)R(__%@_Cf>txOQ74wSJ z0#F3(zWWaR-jMEY$7C*3HJrohc79>MCUu26mfYN)f4M~4gD`}EX4e}A!U}QV8!S47 z6y-U-%+h`1n`*pQuKE%Av0@)+wBZr9mH}@vH@i{v(m-6QK7Ncf17x_D=)32`FOjjo zg|^VPf5c6-!FxN{25dvVh#fog=NNpXz zfB$o+0jbRkHH{!TKhE709f+jI^$3#v1Nmf80w`@7-5$1Iv_`)W^px8P-({xwb;D0y z7LKDAHgX<84?l!I*Dvi2#D@oAE^J|g$3!)x1Ua;_;<@#l1fD}lqU2_tS^6Ht$1Wl} zBESo7o^)9-Tjuz$8YQSGhfs{BQV6zW7dA?0b(Dbt=UnQs&4zHfe_sj{RJ4uS-vQpC zX;Bbsuju4%!o8?&m4UZU@~ZZjeFF6ex2ss5_60_JS_|iNc+R0GIjH1@Z z=rLT9%B|WWgOrR7IiIwr2=T;Ne?30M!@{%Qf8o`!>=s<2CBpCK_TWc(DX51>e^xh8 z&@$^b6CgOd7KXQV&Y4%}_#uN*mbanXq(2=Nj`L7H7*k(6F8s6{FOw@(DzU`4-*77{ zF+dxpv}%mFpYK?>N_2*#Y?oB*qEKB}VoQ@bzm>ptmVS_EC(#}Lxxx730trt0G)#$b zE=wVvtqOct1%*9}U{q<)2?{+0TzZzP0jgf9*)arV)*e!f`|jgT{7_9iS@e)recI#z zbzolURQ+TOzE!ymqvBY7+5NnAbWxvMLsLTwEbFqW=CPyCsmJ}P1^V30|D5E|p3BC5 z)3|qgw@ra7aXb-wsa|l^in~1_fm{7bS9jhVRkYVO#U{qMp z)Wce+|DJ}4<2gp8r0_xfZpMo#{Hl2MfjLcZdRB9(B(A(f;+4s*FxV{1F|4d`*sRNd zp4#@sEY|?^FIJ;tmH{@keZ$P(sLh5IdOk@k^0uB^BWr@pk6mHy$qf&~rI>P*a;h0C{%oA*i!VjWn&D~O#MxN&f@1Po# zKN+ zrGrkSjcr?^R#nGl<#Q722^wbYcgW@{+6CBS<1@%dPA8HC!~a`jTz<`g_l5N1M@9wn9GOAZ>nqNgq!yOCbZ@1z`U_N`Z>}+1HIZxk*5RDc&rd5{3qjRh8QmT$VyS;jK z;AF+r6XnnCp=wQYoG|rT2@8&IvKq*IB_WvS%nt%e{MCFm`&W*#LXc|HrD?nVBo=(8*=Aq?u$sDA_sC_RPDUiQ+wnIJET8vx$&fxkW~kP9qXKt zozR)@xGC!P)CTkjeWvXW5&@2?)qt)jiYWWBU?AUtzAN}{JE1I)dfz~7$;}~BmQF`k zpn11qmObXwRB8&rnEG*#4Xax3XBkKlw(;tb?Np^i+H8m(Wyz9k{~ogba@laiEk;2! zV*QV^6g6(QG%vX5Um#^sT&_e`B1pBW5yVth~xUs#0}nv?~C#l?W+9Lsb_5)!71rirGvY zTIJ$OPOY516Y|_014sNv+Z8cc5t_V=i>lWV=vNu#!58y9Zl&GsMEW#pPYPYGHQ|;vFvd*9eM==$_=vc7xnyz0~ zY}r??$<`wAO?JQk@?RGvkWVJlq2dk9vB(yV^vm{=NVI8dhsX<)O(#nr9YD?I?(VmQ z^r7VfUBn<~p3()8yOBjm$#KWx!5hRW)5Jl7wY@ky9lNM^jaT##8QGVsYeaVywmpv>X|Xj7gWE1Ezai&wVLt3p)k4w~yrskT-!PR!kiyQlaxl(( zXhF%Q9x}1TMt3~u@|#wWm-Vq?ZerK={8@~&@9r5JW}r#45#rWii};t`{5#&3$W)|@ zbAf2yDNe0q}NEUvq_Quq3cTjcw z@H_;$hu&xllCI9CFDLuScEMg|x{S7GdV8<&Mq=ezDnRZAyX-8gv97YTm0bg=d)(>N z+B2FcqvI9>jGtnK%eO%y zoBPkJTk%y`8TLf4)IXPBn`U|9>O~WL2C~C$z~9|0m*YH<-vg2CD^SX#&)B4ngOSG$ zV^wmy_iQk>dfN@Pv(ckfy&#ak@MLC7&Q6Ro#!ezM*VEh`+b3Jt%m(^T&p&WJ2Oqvj zs-4nq0TW6cv~(YI$n0UkfwN}kg3_fp?(ijSV#tR9L0}l2qjc7W?i*q01=St0eZ=4h zyGQbEw`9OEH>NMuIe)hVwYHsGERWOD;JxEiO7cQv%pFCeR+IyhwQ|y@&^24k+|8fD zLiOWFNJ2&vu2&`Jv96_z-Cd5RLgmeY3*4rDOQo?Jm`;I_(+ejsPM03!ly!*Cu}Cco zrQSrEDHNyzT(D5s1rZq!8#?f6@v6dB7a-aWs(Qk>N?UGAo{gytlh$%_IhyL7h?DLXDGx zgxGEBQoCAWo-$LRvM=F5MTle`M})t3vVv;2j0HZY&G z22^iGhV@uaJh(XyyY%} zd4iH_UfdV#T=3n}(Lj^|n;O4|$;xhu*8T3hR1mc_A}fK}jfZ7LX~*n5+`8N2q#rI$ z@<_2VANlYF$vIH$ zl<)+*tIWW78IIINA7Rr7i{<;#^yzxoLNkXL)eSs=%|P>$YQIh+ea_3k z_s7r4%j7%&*NHSl?R4k%1>Z=M9o#zxY!n8sL5>BO-ZP;T3Gut>iLS@U%IBrX6BA3k z)&@q}V8a{X<5B}K5s(c(LQ=%v1ocr`t$EqqY0EqVjr65usa=0bkf|O#ky{j3)WBR(((L^wmyHRzoWuL2~WTC=`yZ zn%VX`L=|Ok0v7?s>IHg?yArBcync5rG#^+u)>a%qjES%dRZoIyA8gQ;StH z1Ao7{<&}6U=5}4v<)1T7t!J_CL%U}CKNs-0xWoTTeqj{5{?Be$L0_tk>M9o8 zo371}S#30rKZFM{`H_(L`EM9DGp+Mifk&IP|C2Zu_)Ghr4Qtpmkm1osCf@%Z$%t+7 zYH$Cr)Ro@3-QDeQJ8m+x6%;?YYT;k6Z0E-?kr>x33`H%*ueBD7Zx~3&HtWn0?2Wt} zTG}*|v?{$ajzt}xPzV%lL1t-URi8*Zn)YljXNGDb>;!905Td|mpa@mHjIH%VIiGx- zd@MqhpYFu4_?y5N4xiHn3vX&|e6r~Xt> zZG`aGq|yTNjv;9E+Txuoa@A(9V7g?1_T5FzRI;!=NP1Kqou1z5?%X~Wwb{trRfd>i z8&y^H)8YnKyA_Fyx>}RNmQIczT?w2J4SNvI{5J&}Wto|8FR(W;Qw#b1G<1%#tmYzQ zQ2mZA-PAdi%RQOhkHy9Ea#TPSw?WxwL@H@cbkZwIq0B!@ns}niALidmn&W?!Vd4Gj zO7FiuV4*6Mr^2xlFSvM;Cp_#r8UaqIzHJQg_z^rEJw&OMm_8NGAY2)rKvki|o1bH~ z$2IbfVeY2L(^*rMRU1lM5Y_sgrDS`Z??nR2lX;zyR=c%UyGb*%TC-Dil?SihkjrQy~TMv6;BMs7P8il`H7DmpVm@rJ;b)hW)BL)GjS154b*xq-NXq2cwE z^;VP7ua2pxvCmxrnqUYQMH%a%nHmwmI33nJM(>4LznvY*k&C0{8f*%?zggpDgkuz&JBx{9mfb@wegEl2v!=}Sq2Gaty0<)UrOT0{MZtZ~j5y&w zXlYa_jY)I_+VA-^#mEox#+G>UgvM!Ac8zI<%JRXM_73Q!#i3O|)lOP*qBeJG#BST0 zqohi)O!|$|2SeJQo(w6w7%*92S})XfnhrH_Z8qe!G5>CglP=nI7JAOW?(Z29;pXJ9 zR9`KzQ=WEhy*)WH>$;7Cdz|>*i>=##0bB)oU0OR>>N<21e4rMCHDemNi2LD>Nc$;& zQRFthpWniC1J6@Zh~iJCoLOxN`oCKD5Q4r%ynwgUKPlIEd#?QViIqovY|czyK8>6B zSP%{2-<;%;1`#0mG^B(8KbtXF;Nf>K#Di72UWE4gQ%(_26Koiad)q$xRL~?pN71ZZ zujaaCx~jXjygw;rI!WB=xrOJO6HJ!!w}7eiivtCg5K|F6$EXa)=xUC za^JXSX98W`7g-tm@uo|BKj39Dl;sg5ta;4qjo^pCh~{-HdLl6qI9Ix6f$+qiZ$}s= zNguKrU;u+T@ko(Vr1>)Q%h$?UKXCY>3se%&;h2osl2D zE4A9bd7_|^njDd)6cI*FupHpE3){4NQ*$k*cOWZ_?CZ>Z4_fl@n(mMnYK62Q1d@+I zr&O))G4hMihgBqRIAJkLdk(p(D~X{-oBUA+If@B}j& zsHbeJ3RzTq96lB7d($h$xTeZ^gP0c{t!Y0c)aQE;$FY2!mACg!GDEMKXFOPI^)nHZ z`aSPJpvV0|bbrzhWWkuPURlDeN%VT8tndV8?d)eN*i4I@u zVKl^6{?}A?P)Fsy?3oi#clf}L18t;TjNI2>eI&(ezDK7RyqFxcv%>?oxUlonv(px) z$vnPzRH`y5A(x!yOIfL0bmgeMQB$H5wenx~!ujQK*nUBW;@Em&6Xv2%s(~H5WcU2R z;%Nw<$tI)a`Ve!>x+qegJnQsN2N7HaKzrFqM>`6R*gvh%O*-%THt zrB$Nk;lE;z{s{r^PPm5qz(&lM{sO*g+W{sK+m3M_z=4=&CC>T`{X}1Vg2PEfSj2x_ zmT*(x;ov%3F?qoEeeM>dUn$a*?SIGyO8m806J1W1o+4HRhc2`9$s6hM#qAm zChQ87b~GEw{ADfs+5}FJ8+|bIlIv(jT$Ap#hSHoXdd9#w<#cA<1Rkq^*EEkknUd4& zoIWIY)sAswy6fSERVm&!SO~#iN$OgOX*{9@_BWFyJTvC%S++ilSfCrO(?u=Dc?CXZ zzCG&0yVR{Z`|ZF0eEApWEo#s9osV>F{uK{QA@BES#&;#KsScf>y zvs?vIbI>VrT<*!;XmQS=bhq%46-aambZ(8KU-wOO2=en~D}MCToB_u;Yz{)1ySrPZ z@=$}EvjTdzTWU7c0ZI6L8=yP+YRD_eMMos}b5vY^S*~VZysrkq<`cK3>>v%uy7jgq z0ilW9KjVDHLv0b<1K_`1IkbTOINs0=m-22c%M~l=^S}%hbli-3?BnNq?b`hx^HX2J zIe6ECljRL0uBWb`%{EA=%!i^4sMcj+U_TaTZRb+~GOk z^ZW!nky0n*Wb*r+Q|9H@ml@Z5gU&W`(z4-j!OzC1wOke`TRAYGZVl$PmQ16{3196( zO*?`--I}Qf(2HIwb2&1FB^!faPA2=sLg(@6P4mN)>Dc3i(B0;@O-y2;lM4akD>@^v z=u>*|!s&9zem70g7zfw9FXl1bpJW(C#5w#uy5!V?Q(U35A~$dR%LDVnq@}kQm13{} zd53q3N(s$Eu{R}k2esbftfjfOITCL;jWa$}(mmm}d(&7JZ6d3%IABCapFFYjdEjdK z&4Edqf$G^MNAtL=uCDRs&Fu@FXRgX{*0<(@c3|PNHa>L%zvxWS={L8%qw`STm+=Rd zA}FLspESSIpE_^41~#5yI2bJ=9`oc;GIL!JuW&7YetZ?0H}$$%8rW@*J37L-~Rsx!)8($nI4 zZhcZ2^=Y+p4YPl%j!nFJA|*M^gc(0o$i3nlphe+~-_m}jVkRN{spFs(o0ajW@f3K{ zDV!#BwL322CET$}Y}^0ixYj2w>&Xh12|R8&yEw|wLDvF!lZ#dOTHM9pK6@Nm-@9Lnng4ZHBgBSrr7KI8YCC9DX5Kg|`HsiwJHg2(7#nS;A{b3tVO?Z% za{m5b3rFV6EpX;=;n#wltDv1LE*|g5pQ+OY&*6qCJZc5oDS6Z6JD#6F)bWxZSF@q% z+1WV;m!lRB!n^PC>RgQCI#D1br_o^#iPk>;K2hB~0^<~)?p}LG%kigm@moD#q3PE+ zA^Qca)(xnqw6x>XFhV6ku9r$E>bWNrVH9fum0?4s?Rn2LG{Vm_+QJHse6xa%nzQ?k zKug4PW~#Gtb;#5+9!QBgyB@q=sk9=$S{4T>wjFICStOM?__fr+Kei1 z3j~xPqW;W@YkiUM;HngG!;>@AITg}vAE`M2Pj9Irl4w1fo4w<|Bu!%rh%a(Ai^Zhi zs92>v5;@Y(Zi#RI*ua*h`d_7;byQSa*v9E{2x$<-_=5Z<7{%)}4XExANcz@rK69T0x3%H<@frW>RA8^swA+^a(FxK| zFl3LD*ImHN=XDUkrRhp6RY5$rQ{bRgSO*(vEHYV)3Mo6Jy3puiLmU&g82p{qr0F?ohmbz)f2r{X2|T2 z$4fdQ=>0BeKbiVM!e-lIIs8wVTuC_m7}y4A_%ikI;Wm5$9j(^Y z(cD%U%k)X>_>9~t8;pGzL6L-fmQO@K; zo&vQzMlgY95;1BSkngY)e{`n0!NfVgf}2mB3t}D9@*N;FQ{HZ3Pb%BK6;5#-O|WI( zb6h@qTLU~AbVW#_6?c!?Dj65Now7*pU{h!1+eCV^KCuPAGs28~3k@ueL5+u|Z-7}t z9|lskE`4B7W8wMs@xJa{#bsCGDFoRSNSnmNYB&U7 zVGKWe%+kFB6kb)e;TyHfqtU6~fRg)f|>=5(N36)0+C z`hv65J<$B}WUc!wFAb^QtY31yNleq4dzmG`1wHTj=c*=hay9iD071Hc?oYoUk|M*_ zU1GihAMBsM@5rUJ(qS?9ZYJ6@{bNqJ`2Mr+5#hKf?doa?F|+^IR!8lq9)wS3tF_9n zW_?hm)G(M+MYb?V9YoX^_mu5h-LP^TL^!Q9Z7|@sO(rg_4+@=PdI)WL(B7`!K^ND- z-uIuVDCVEdH_C@c71YGYT^_Scf_dhB8Z2Xy6vGtBSlYud9vggOqv^L~F{BraSE_t} zIkP+Hp2&nH^-MNEs}^`oMLy11`PQW$T|K(`Bu*(f@)mv1-qY(_YG&J2M2<7k;;RK~ zL{Fqj9yCz8(S{}@c)S!65aF<=&eLI{hAMErCx&>i7OeDN>okvegO87OaG{Jmi<|}D zaT@b|0X{d@OIJ7zvT>r+eTzgLq~|Dpu)Z&db-P4z*`M$UL51lf>FLlq6rfG)%doyp z)3kk_YIM!03eQ8Vu_2fg{+osaEJPtJ-s36R+5_AEG12`NG)IQ#TF9c@$99%0iye+ zUzZ57=m2)$D(5Nx!n)=5Au&O0BBgwxIBaeI(mro$#&UGCr<;C{UjJVAbVi%|+WP(a zL$U@TYCxJ=1{Z~}rnW;7UVb7+ZnzgmrogDxhjLGo>c~MiJAWs&&;AGg@%U?Y^0JhL ze(x6Z74JG6FlOFK(T}SXQfhr}RIFl@QXKnIcXYF)5|V~e-}suHILKT-k|<*~Ij|VF zC;t@=uj=hot~*!C68G8hTA%8SzOfETOXQ|3FSaIEjvBJp(A)7SWUi5!Eu#yWgY+;n zlm<$+UDou*V+246_o#V4kMdto8hF%%Lki#zPh}KYXmMf?hrN0;>Mv%`@{0Qn`Ujp) z=lZe+13>^Q!9zT);H<(#bIeRWz%#*}sgUX9P|9($kexOyKIOc`dLux}c$7It4u|Rl z6SSkY*V~g_B-hMPo_ak>>z@AVQ(_N)VY2kB3IZ0G(iDUYw+2d7W^~(Jq}KY=JnWS( z#rzEa&0uNhJ>QE8iiyz;n2H|SV#Og+wEZv=f2%1ELX!SX-(d3tEj$5$1}70Mp<&eI zCkfbByL7af=qQE@5vDVxx1}FSGt_a1DoE3SDI+G)mBAna)KBG4p8Epxl9QZ4BfdAN zFnF|Y(umr;gRgG6NLQ$?ZWgllEeeq~z^ZS7L?<(~O&$5|y)Al^iMKy}&W+eMm1W z7EMU)u^ke(A1#XCV>CZ71}P}0x)4wtHO8#JRG3MA-6g=`ZM!FcICCZ{IEw8Dm2&LQ z1|r)BUG^0GzI6f946RrBlfB1Vs)~8toZf~7)+G;pv&XiUO(%5bm)pl=p>nV^o*;&T z;}@oZSibzto$arQgfkp|z4Z($P>dTXE{4O=vY0!)kDO* zGF8a4wq#VaFpLfK!iELy@?-SeRrdz%F*}hjKcA*y@mj~VD3!it9lhRhX}5YOaR9$} z3mS%$2Be7{l(+MVx3 z(4?h;P!jnRmX9J9sYN#7i=iyj_5q7n#X(!cdqI2lnr8T$IfOW<_v`eB!d9xY1P=2q&WtOXY=D9QYteP)De?S4}FK6#6Ma z=E*V+#s8>L;8aVroK^6iKo=MH{4yEZ_>N-N z`(|;aOATba1^asjxlILk<4}f~`39dBFlxj>Dw(hMYKPO3EEt1@S`1lxFNM+J@uB7T zZ8WKjz7HF1-5&2=l=fqF-*@>n5J}jIxdDwpT?oKM3s8Nr`x8JnN-kCE?~aM1H!hAE z%%w(3kHfGwMnMmNj(SU(w42OrC-euI>Dsjk&jz3ts}WHqmMpzQ3vZrsXrZ|}+MHA7 z068obeXZTsO*6RS@o3x80E4ok``rV^Y3hr&C1;|ZZ0|*EKO`$lECUYG2gVFtUTw)R z4Um<0ZzlON`zTdvVdL#KFoMFQX*a5wM0Czp%wTtfK4Sjs)P**RW&?lP$(<}q%r68Z zS53Y!d@&~ne9O)A^tNrXHhXBkj~$8j%pT1%%mypa9AW5E&s9)rjF4@O3ytH{0z6riz|@< zB~UPh*wRFg2^7EbQrHf0y?E~dHlkOxof_a?M{LqQ^C!i2dawHTPYUE=X@2(3<=OOxs8qn_(y>pU>u^}3y&df{JarR0@VJn0f+U%UiF=$Wyq zQvnVHESil@d|8&R<%}uidGh7@u^(%?$#|&J$pvFC-n8&A>utA=n3#)yMkz+qnG3wd zP7xCnF|$9Dif@N~L)Vde3hW8W!UY0BgT2v(wzp;tlLmyk2%N|0jfG$%<;A&IVrOI< z!L)o>j>;dFaqA3pL}b-Je(bB@VJ4%!JeX@3x!i{yIeIso^=n?fDX`3bU=eG7sTc%g%ye8$v8P@yKE^XD=NYxTb zbf!Mk=h|otpqjFaA-vs5YOF-*GwWPc7VbaOW&stlANnCN8iftFMMrUdYNJ_Bnn5Vt zxfz@Ah|+4&P;reZxp;MmEI7C|FOv8NKUm8njF7Wb6Gi7DeODLl&G~}G4be&*Hi0Qw z5}77vL0P+7-B%UL@3n1&JPxW^d@vVwp?u#gVcJqY9#@-3X{ok#UfW3<1fb%FT`|)V~ggq z(3AUoUS-;7)^hCjdT0Kf{i}h)mBg4qhtHHBti=~h^n^OTH5U*XMgDLIR@sre`AaB$ zg)IGBET_4??m@cx&c~bA80O7B8CHR7(LX7%HThkeC*@vi{-pL%e)yXp!B2InafbDF zjPXf1mko3h59{lT6EEbxKO1Z5GF71)WwowO6kY|6tjSVSWdQ}NsK2x{>i|MKZK8%Q zfu&_0D;CO-Jg0#YmyfctyJ!mRJp)e#@O0mYdp|8x;G1%OZQ3Q847YWTyy|%^cpA;m zze0(5p{tMu^lDkpe?HynyO?a1$_LJl2L&mpeKu%8YvgRNr=%2z${%WThHG=vrWY@4 zsA`OP#O&)TetZ>s%h!=+CE15lOOls&nvC~$Qz0Ph7tHiP;O$i|eDwpT{cp>+)0-|; zY$|bB+Gbel>5aRN3>c0x)4U=|X+z+{ zn*_p*EQoquRL+=+p;=lm`d71&1NqBz&_ph)MXu(Nv6&XE7(RsS)^MGj5Q?Fwude-(sq zjJ>aOq!7!EN>@(fK7EE#;i_BGvli`5U;r!YA{JRodLBc6-`n8K+Fjgwb%sX;j=qHQ z7&Tr!)!{HXoO<2BQrV9Sw?JRaLXV8HrsNevvnf>Y-6|{T!pYLl7jp$-nEE z#X!4G4L#K0qG_4Z;Cj6=;b|Be$hi4JvMH!-voxqx^@8cXp`B??eFBz2lLD8RRaRGh zn7kUfy!YV~p(R|p7iC1Rdgt$_24i0cd-S8HpG|`@my70g^y`gu%#Tf_L21-k?sRRZHK&at(*ED0P8iw{7?R$9~OF$Ko;Iu5)ur5<->x!m93Eb zFYpIx60s=Wxxw=`$aS-O&dCO_9?b1yKiPCQmSQb>T)963`*U+Ydj5kI(B(B?HNP8r z*bfSBpSu)w(Z3j7HQoRjUG(+d=IaE~tv}y14zHHs|0UcN52fT8V_<@2ep_ee{QgZG zmgp8iv4V{k;~8@I%M3<#B;2R>Ef(Gg_cQM7%}0s*^)SK6!Ym+~P^58*wnwV1BW@eG z4sZLqsUvBbFsr#8u7S1r4teQ;t)Y@jnn_m5jS$CsW1um!p&PqAcc8!zyiXHVta9QC zY~wCwCF0U%xiQPD_INKtTb;A|Zf29(mu9NI;E zc-e>*1%(LSXB`g}kd`#}O;veb<(sk~RWL|f3ljxCnEZDdNSTDV6#Td({6l&y4IjKF z^}lIUq*ZUqgTPumD)RrCN{M^jhY>E~1pn|KOZ5((%F)G|*ZQ|r4zIbrEiV%42hJV8 z3xS)=!X1+=olbdGJ=yZil?oXLct8FM{(6ikLL3E%=q#O6(H$p~gQu6T8N!plf!96| z&Q3=`L~>U0zZh;z(pGR2^S^{#PrPxTRHD1RQOON&f)Siaf`GLj#UOk&(|@0?zm;Sx ztsGt8=29-MZs5CSf1l1jNFtNt5rFNZxJPvkNu~2}7*9468TWm>nN9TP&^!;J{-h)_ z7WsHH9|F%I`Pb!>KAS3jQWKfGivTVkMJLO-HUGM_a4UQ_%RgL6WZvrW+Z4ujZn;y@ zz9$=oO!7qVTaQAA^BhX&ZxS*|5dj803M=k&2%QrXda`-Q#IoZL6E(g+tN!6CA!CP* zCpWtCujIea)ENl0liwVfj)Nc<9mV%+e@=d`haoZ*`B7+PNjEbXBkv=B+Pi^~L#EO$D$ZqTiD8f<5$eyb54-(=3 zh)6i8i|jp(@OnRrY5B8t|LFXFQVQ895n*P16cEKTrT*~yLH6Z4e*bZ5otpRDri&+A zfNbK1D5@O=sm`fN=WzWyse!za5n%^+6dHPGX#8DyIK>?9qyX}2XvBWVqbP%%D)7$= z=#$WulZlZR<{m#gU7lwqK4WS1Ne$#_P{b17qe$~UOXCl>5b|6WVh;5vVnR<%d+Lnp z$uEmML38}U4vaW8>shm6CzB(Wei3s#NAWE3)a2)z@i{4jTn;;aQS)O@l{rUM`J@K& l00vQ5JBs~;vo!vr%%-k{2_Fq1Mn4QF81S)AQ99zk{{c4yR+0b! literal 62076 zcmb5VV{~QRw)Y#`wrv{~+qP{x72B%VwzFc}c2cp;N~)5ZbDrJayPv(!dGEd-##*zr z)#n-$y^sH|_dchh3@8{H5D*j;5D<{i*8l5IFJ|DjL!e)upfGNX(kojugZ3I`oH1PvW`wFW_ske0j@lB9bX zO;2)`y+|!@X(fZ1<2n!Qx*)_^Ai@Cv-dF&(vnudG?0CsddG_&Wtae(n|K59ew)6St z#dj7_(Cfwzh$H$5M!$UDd8=4>IQsD3xV=lXUq($;(h*$0^yd+b{qq63f0r_de#!o_ zXDngc>zy`uor)4A^2M#U*DC~i+dc<)Tb1Tv&~Ev@oM)5iJ4Sn#8iRw16XXuV50BS7 zdBL5Mefch(&^{luE{*5qtCZk$oFr3RH=H!c3wGR=HJ(yKc_re_X9pD` zJ;uxPzUfVpgU>DSq?J;I@a+10l0ONXPcDkiYcihREt5~T5Gb}sT0+6Q;AWHl`S5dV>lv%-p9l#xNNy7ZCr%cyqHY%TZ8Q4 zbp&#ov1*$#grNG#1vgfFOLJCaNG@K|2!W&HSh@3@Y%T?3YI75bJp!VP*$*!< z;(ffNS_;@RJ`=c7yX04!u3JP*<8jeqLHVJu#WV&v6wA!OYJS4h<_}^QI&97-;=ojW zQ-1t)7wnxG*5I%U4)9$wlv5Fr;cIizft@&N+32O%B{R1POm$oap@&f| zh+5J{>U6ftv|vAeKGc|zC=kO(+l7_cLpV}-D#oUltScw})N>~JOZLU_0{Ka2e1evz z{^a*ZrLr+JUj;)K&u2CoCAXLC2=fVScI(m_p~0FmF>>&3DHziouln?;sxW`NB}cSX z8?IsJB)Z=aYRz!X=yJn$kyOWK%rCYf-YarNqKzmWu$ZvkP12b4qH zhS9Q>j<}(*frr?z<%9hl*i^#@*O2q(Z^CN)c2c z>1B~D;@YpG?G!Yk+*yn4vM4sO-_!&m6+`k|3zd;8DJnxsBYtI;W3We+FN@|tQ5EW= z!VU>jtim0Mw#iaT8t_<+qKIEB-WwE04lBd%Letbml9N!?SLrEG$nmn7&W(W`VB@5S zaY=sEw2}i@F_1P4OtEw?xj4@D6>_e=m=797#hg}f*l^`AB|Y0# z9=)o|%TZFCY$SzgSjS|8AI-%J4x}J)!IMxY3_KYze`_I=c1nmrk@E8c9?MVRu)7+Ue79|)rBX7tVB7U|w4*h(;Gi3D9le49B38`wuv zp7{4X^p+K4*$@gU(Tq3K1a#3SmYhvI42)GzG4f|u zwQFT1n_=n|jpi=70-yE9LA+d*T8u z`=VmmXJ_f6WmZveZPct$Cgu^~gFiyL>Lnpj*6ee>*0pz=t$IJ}+rE zsf@>jlcG%Wx;Cp5x)YSVvB1$yyY1l&o zvwX=D7k)Dn;ciX?Z)Pn8$flC8#m`nB&(8?RSdBvr?>T9?E$U3uIX7T?$v4dWCa46 z+&`ot8ZTEgp7G+c52oHJ8nw5}a^dwb_l%MOh(ebVj9>_koQP^$2B~eUfSbw9RY$_< z&DDWf2LW;b0ZDOaZ&2^i^g+5uTd;GwO(-bbo|P^;CNL-%?9mRmxEw~5&z=X^Rvbo^WJW=n_%*7974RY}JhFv46> zd}`2|qkd;89l}R;i~9T)V-Q%K)O=yfVKNM4Gbacc7AOd>#^&W&)Xx!Uy5!BHnp9kh z`a(7MO6+Ren#>R^D0K)1sE{Bv>}s6Rb9MT14u!(NpZOe-?4V=>qZ>}uS)!y~;jEUK z&!U7Fj&{WdgU#L0%bM}SYXRtM5z!6M+kgaMKt%3FkjWYh=#QUpt$XX1!*XkpSq-pl zhMe{muh#knk{9_V3%qdDcWDv}v)m4t9 zQhv{;} zc{}#V^N3H>9mFM8`i`0p+fN@GqX+kl|M94$BK3J-X`Hyj8r!#x6Vt(PXjn?N)qedP z=o1T^#?1^a{;bZ&x`U{f?}TMo8ToN zkHj5v|}r}wDEi7I@)Gj+S1aE-GdnLN+$hw!=DzglMaj#{qjXi_dwpr|HL(gcCXwGLEmi|{4&4#OZ4ChceA zKVd4K!D>_N=_X;{poT~4Q+!Le+ZV>=H7v1*l%w`|`Dx8{)McN@NDlQyln&N3@bFpV z_1w~O4EH3fF@IzJ9kDk@7@QctFq8FbkbaH7K$iX=bV~o#gfh?2JD6lZf(XP>~DACF)fGFt)X%-h1yY~MJU{nA5 ze2zxWMs{YdX3q5XU*9hOH0!_S24DOBA5usB+Ws$6{|AMe*joJ?RxfV}*7AKN9V*~J zK+OMcE@bTD>TG1*yc?*qGqjBN8mgg@h1cJLDv)0!WRPIkC` zZrWXrceVw;fB%3`6kq=a!pq|hFIsQ%ZSlo~)D z|64!aCnw-?>}AG|*iOl44KVf8@|joXi&|)1rB;EQWgm+iHfVbgllP$f!$Wf42%NO5b(j9Bw6L z;0dpUUK$5GX4QbMlTmLM_jJt!ur`_0~$b#BB7FL*%XFf<b__1o)Ao3rlobbN8-(T!1d-bR8D3S0@d zLI!*GMb5s~Q<&sjd}lBb8Nr0>PqE6_!3!2d(KAWFxa{hm`@u|a(%#i(#f8{BP2wbs zt+N_slWF4IF_O|{w`c~)Xvh&R{Au~CFmW#0+}MBd2~X}t9lz6*E7uAD`@EBDe$>7W zzPUkJx<`f$0VA$=>R57^(K^h86>09?>_@M(R4q($!Ck6GG@pnu-x*exAx1jOv|>KH zjNfG5pwm`E-=ydcb+3BJwuU;V&OS=6yM^4Jq{%AVqnTTLwV`AorIDD}T&jWr8pB&j28fVtk_y*JRP^t@l*($UZ z6(B^-PBNZ+z!p?+e8@$&jCv^EWLb$WO=}Scr$6SM*&~B95El~;W_0(Bvoha|uQ1T< zO$%_oLAwf1bW*rKWmlD+@CP&$ObiDy=nh1b2ejz%LO9937N{LDe7gle4i!{}I$;&Y zkexJ9Ybr+lrCmKWg&}p=`2&Gf10orS?4$VrzWidT=*6{KzOGMo?KI0>GL0{iFWc;C z+LPq%VH5g}6V@-tg2m{C!-$fapJ9y}c$U}aUmS{9#0CM*8pC|sfer!)nG7Ji>mfRh z+~6CxNb>6eWKMHBz-w2{mLLwdA7dA-qfTu^A2yG1+9s5k zcF=le_UPYG&q!t5Zd_*E_P3Cf5T6821bO`daa`;DODm8Ih8k89=RN;-asHIigj`n=ux>*f!OC5#;X5i;Q z+V!GUy0|&Y_*8k_QRUA8$lHP;GJ3UUD08P|ALknng|YY13)}!!HW@0z$q+kCH%xet zlWf@BXQ=b=4}QO5eNnN~CzWBbHGUivG=`&eWK}beuV*;?zt=P#pM*eTuy3 zP}c#}AXJ0OIaqXji78l;YrP4sQe#^pOqwZUiiN6^0RCd#D271XCbEKpk`HI0IsN^s zES7YtU#7=8gTn#lkrc~6)R9u&SX6*Jk4GFX7){E)WE?pT8a-%6P+zS6o&A#ml{$WX zABFz#i7`DDlo{34)oo?bOa4Z_lNH>n;f0nbt$JfAl~;4QY@}NH!X|A$KgMmEsd^&Y zt;pi=>AID7ROQfr;MsMtClr5b0)xo|fwhc=qk33wQ|}$@?{}qXcmECh>#kUQ-If0$ zseb{Wf4VFGLNc*Rax#P8ko*=`MwaR-DQ8L8V8r=2N{Gaips2_^cS|oC$+yScRo*uF zUO|5=?Q?{p$inDpx*t#Xyo6=s?bbN}y>NNVxj9NZCdtwRI70jxvm3!5R7yiWjREEd zDUjrsZhS|P&|Ng5r+f^kA6BNN#|Se}_GF>P6sy^e8kBrgMv3#vk%m}9PCwUWJg-AD zFnZ=}lbi*mN-AOm zCs)r=*YQAA!`e#1N>aHF=bb*z*hXH#Wl$z^o}x##ZrUc=kh%OHWhp=7;?8%Xj||@V?1c ziWoaC$^&04;A|T)!Zd9sUzE&$ODyJaBpvqsw19Uiuq{i#VK1!htkdRWBnb z`{rat=nHArT%^R>u#CjjCkw-7%g53|&7z-;X+ewb?OLWiV|#nuc8mp*LuGSi3IP<<*Wyo9GKV7l0Noa4Jr0g3p_$ z*R9{qn=?IXC#WU>48-k5V2Oc_>P;4_)J@bo1|pf=%Rcbgk=5m)CJZ`caHBTm3%!Z9 z_?7LHr_BXbKKr=JD!%?KhwdYSdu8XxPoA{n8^%_lh5cjRHuCY9Zlpz8g+$f@bw@0V z+6DRMT9c|>1^3D|$Vzc(C?M~iZurGH2pXPT%F!JSaAMdO%!5o0uc&iqHx?ImcX6fI zCApkzc~OOnfzAd_+-DcMp&AOQxE_EsMqKM{%dRMI5`5CT&%mQO?-@F6tE*xL?aEGZ z8^wH@wRl`Izx4sDmU>}Ym{ybUm@F83qqZPD6nFm?t?(7>h*?`fw)L3t*l%*iw0Qu#?$5eq!Qc zpQvqgSxrd83NsdO@lL6#{%lsYXWen~d3p4fGBb7&5xqNYJ)yn84!e1PmPo7ChVd%4 zHUsV0Mh?VpzZD=A6%)Qrd~i7 z96*RPbid;BN{Wh?adeD_p8YU``kOrGkNox3D9~!K?w>#kFz!4lzOWR}puS(DmfjJD z`x0z|qB33*^0mZdM&6$|+T>fq>M%yoy(BEjuh9L0>{P&XJ3enGpoQRx`v6$txXt#c z0#N?b5%srj(4xmPvJxrlF3H%OMB!jvfy z;wx8RzU~lb?h_}@V=bh6p8PSb-dG|-T#A?`c&H2`_!u+uenIZe`6f~A7r)`9m8atC zt(b|6Eg#!Q*DfRU=Ix`#B_dK)nnJ_+>Q<1d7W)eynaVn`FNuN~%B;uO2}vXr5^zi2 z!ifIF5@Zlo0^h~8+ixFBGqtweFc`C~JkSq}&*a3C}L?b5Mh-bW=e)({F_g4O3 zb@SFTK3VD9QuFgFnK4Ve_pXc3{S$=+Z;;4+;*{H}Rc;845rP?DLK6G5Y-xdUKkA6E3Dz&5f{F^FjJQ(NSpZ8q-_!L3LL@H* zxbDF{gd^U3uD;)a)sJwAVi}7@%pRM&?5IaUH%+m{E)DlA_$IA1=&jr{KrhD5q&lTC zAa3c)A(K!{#nOvenH6XrR-y>*4M#DpTTOGQEO5Jr6kni9pDW`rvY*fs|ItV;CVITh z=`rxcH2nEJpkQ^(;1c^hfb8vGN;{{oR=qNyKtR1;J>CByul*+=`NydWnSWJR#I2lN zTvgnR|MBx*XFsfdA&;tr^dYaqRZp*2NwkAZE6kV@1f{76e56eUmGrZ>MDId)oqSWw z7d&r3qfazg+W2?bT}F)4jD6sWaw`_fXZGY&wnGm$FRPFL$HzVTH^MYBHWGCOk-89y zA+n+Q6EVSSCpgC~%uHfvyg@ufE^#u?JH?<73A}jj5iILz4Qqk5$+^U(SX(-qv5agK znUkfpke(KDn~dU0>gdKqjTkVk`0`9^0n_wzXO7R!0Thd@S;U`y)VVP&mOd-2 z(hT(|$=>4FY;CBY9#_lB$;|Wd$aOMT5O_3}DYXEHn&Jrc3`2JiB`b6X@EUOD zVl0S{ijm65@n^19T3l%>*;F(?3r3s?zY{thc4%AD30CeL_4{8x6&cN}zN3fE+x<9; zt2j1RRVy5j22-8U8a6$pyT+<`f+x2l$fd_{qEp_bfxfzu>ORJsXaJn4>U6oNJ#|~p z`*ZC&NPXl&=vq2{Ne79AkQncuxvbOG+28*2wU$R=GOmns3W@HE%^r)Fu%Utj=r9t` zd;SVOnA(=MXgnOzI2@3SGKHz8HN~Vpx&!Ea+Df~`*n@8O=0!b4m?7cE^K*~@fqv9q zF*uk#1@6Re_<^9eElgJD!nTA@K9C732tV~;B`hzZ321Ph=^BH?zXddiu{Du5*IPg} zqDM=QxjT!Rp|#Bkp$(mL)aar)f(dOAXUiw81pX0DC|Y4;>Vz>>DMshoips^8Frdv} zlTD=cKa48M>dR<>(YlLPOW%rokJZNF2gp8fwc8b2sN+i6&-pHr?$rj|uFgktK@jg~ zIFS(%=r|QJ=$kvm_~@n=ai1lA{7Z}i+zj&yzY+!t$iGUy|9jH#&oTNJ;JW-3n>DF+ z3aCOzqn|$X-Olu_p7brzn`uk1F*N4@=b=m;S_C?#hy{&NE#3HkATrg?enaVGT^$qIjvgc61y!T$9<1B@?_ibtDZ{G zeXInVr5?OD_nS_O|CK3|RzzMmu+8!#Zb8Ik;rkIAR%6?$pN@d<0dKD2c@k2quB%s( zQL^<_EM6ow8F6^wJN1QcPOm|ehA+dP(!>IX=Euz5qqIq}Y3;ibQtJnkDmZ8c8=Cf3 zu`mJ!Q6wI7EblC5RvP*@)j?}W=WxwCvF3*5Up_`3*a~z$`wHwCy)2risye=1mSp%p zu+tD6NAK3o@)4VBsM!@);qgsjgB$kkCZhaimHg&+k69~drbvRTacWKH;YCK(!rC?8 zP#cK5JPHSw;V;{Yji=55X~S+)%(8fuz}O>*F3)hR;STU`z6T1aM#Wd+FP(M5*@T1P z^06O;I20Sk!bxW<-O;E081KRdHZrtsGJflFRRFS zdi5w9OVDGSL3 zNrC7GVsGN=b;YH9jp8Z2$^!K@h=r-xV(aEH@#JicPy;A0k1>g1g^XeR`YV2HfmqXY zYbRwaxHvf}OlCAwHoVI&QBLr5R|THf?nAevV-=~V8;gCsX>jndvNOcFA+DI+zbh~# zZ7`qNk&w+_+Yp!}j;OYxIfx_{f0-ONc?mHCiCUak=>j>~>YR4#w# zuKz~UhT!L~GfW^CPqG8Lg)&Rc6y^{%3H7iLa%^l}cw_8UuG;8nn9)kbPGXS}p3!L_ zd#9~5CrH8xtUd?{d2y^PJg+z(xIfRU;`}^=OlehGN2=?}9yH$4Rag}*+AWotyxfCJ zHx=r7ZH>j2kV?%7WTtp+-HMa0)_*DBBmC{sd$)np&GEJ__kEd`xB5a2A z*J+yx>4o#ZxwA{;NjhU*1KT~=ZK~GAA;KZHDyBNTaWQ1+;tOFFthnD)DrCn`DjBZ% zk$N5B4^$`n^jNSOr=t(zi8TN4fpaccsb`zOPD~iY=UEK$0Y70bG{idLx@IL)7^(pL z{??Bnu=lDeguDrd%qW1)H)H`9otsOL-f4bSu};o9OXybo6J!Lek`a4ff>*O)BDT_g z<6@SrI|C9klY(>_PfA^qai7A_)VNE4c^ZjFcE$Isp>`e5fLc)rg@8Q_d^Uk24$2bn z9#}6kZ2ZxS9sI(RqT7?El2@B+($>eBQrNi_k#CDJ8D9}8$mmm z4oSKO^F$i+NG)-HE$O6s1--6EzJa?C{x=QgK&c=)b(Q9OVoAXYEEH20G|q$}Hue%~ zO3B^bF=t7t48sN zWh_zA`w~|){-!^g?6Mqf6ieV zFx~aPUOJGR=4{KsW7I?<=J2|lY`NTU=lt=%JE9H1vBpkcn=uq(q~=?iBt_-r(PLBM zP-0dxljJO>4Wq-;stY)CLB4q`-r*T$!K2o}?E-w_i>3_aEbA^MB7P5piwt1dI-6o!qWCy0 ztYy!x9arGTS?kabkkyv*yxvsPQ7Vx)twkS6z2T@kZ|kb8yjm+^$|sEBmvACeqbz)RmxkkDQX-A*K!YFziuhwb|ym>C$}U|J)4y z$(z#)GH%uV6{ec%Zy~AhK|+GtG8u@c884Nq%w`O^wv2#A(&xH@c5M`Vjk*SR_tJnq z0trB#aY)!EKW_}{#L3lph5ow=@|D5LzJYUFD6 z7XnUeo_V0DVSIKMFD_T0AqAO|#VFDc7c?c-Q%#u00F%!_TW1@JVnsfvm@_9HKWflBOUD~)RL``-!P;(bCON_4eVdduMO>?IrQ__*zE@7(OX zUtfH@AX*53&xJW*Pu9zcqxGiM>xol0I~QL5B%Toog3Jlenc^WbVgeBvV8C8AX^Vj& z^I}H})B=VboO%q1;aU5ACMh{yK4J;xlMc`jCnZR^!~LDs_MP&8;dd@4LDWw~*>#OT zeZHwdQWS!tt5MJQI~cw|Ka^b4c|qyd_ly(+Ql2m&AAw^ zQeSXDOOH!!mAgzAp0z)DD>6Xo``b6QwzUV@w%h}Yo>)a|xRi$jGuHQhJVA%>)PUvK zBQ!l0hq<3VZ*RnrDODP)>&iS^wf64C;MGqDvx>|p;35%6(u+IHoNbK z;Gb;TneFo*`zUKS6kwF*&b!U8e5m4YAo03a_e^!5BP42+r)LFhEy?_7U1IR<; z^0v|DhCYMSj<-;MtY%R@Fg;9Kky^pz_t2nJfKWfh5Eu@_l{^ph%1z{jkg5jQrkvD< z#vdK!nku*RrH~TdN~`wDs;d>XY1PH?O<4^U4lmA|wUW{Crrv#r%N>7k#{Gc44Fr|t z@UZP}Y-TrAmnEZ39A*@6;ccsR>)$A)S>$-Cj!=x$rz7IvjHIPM(TB+JFf{ehuIvY$ zsDAwREg*%|=>Hw$`us~RP&3{QJg%}RjJKS^mC_!U;E5u>`X`jW$}P`Mf}?7G7FX#{ zE(9u1SO;3q@ZhDL9O({-RD+SqqPX)`0l5IQu4q)49TUTkxR(czeT}4`WV~pV*KY&i zAl3~X%D2cPVD^B43*~&f%+Op)wl<&|D{;=SZwImydWL6@_RJjxP2g)s=dH)u9Npki zs~z9A+3fj0l?yu4N0^4aC5x)Osnm0qrhz@?nwG_`h(71P znbIewljU%T*cC=~NJy|)#hT+lx#^5MuDDnkaMb*Efw9eThXo|*WOQzJ*#3dmRWm@! zfuSc@#kY{Um^gBc^_Xdxnl!n&y&}R4yAbK&RMc+P^Ti;YIUh|C+K1|=Z^{nZ}}rxH*v{xR!i%qO~o zTr`WDE@k$M9o0r4YUFFeQO7xCu_Zgy)==;fCJ94M_rLAv&~NhfvcLWCoaGg2ao~3e zBG?Ms9B+efMkp}7BhmISGWmJsKI@a8b}4lLI48oWKY|8?zuuNc$lt5Npr+p7a#sWu zh!@2nnLBVJK!$S~>r2-pN||^w|fY`CT{TFnJy`B|e5;=+_v4l8O-fkN&UQbA4NKTyntd zqK{xEKh}U{NHoQUf!M=2(&w+eef77VtYr;xs%^cPfKLObyOV_9q<(%76-J%vR>w9!us-0c-~Y?_EVS%v!* z15s2s3eTs$Osz$JayyH|5nPAIPEX=U;r&p;K14G<1)bvn@?bM5kC{am|C5%hyxv}a z(DeSKI5ZfZ1*%dl8frIX2?);R^^~LuDOpNpk-2R8U1w92HmG1m&|j&J{EK=|p$;f9 z7Rs5|jr4r8k5El&qcuM+YRlKny%t+1CgqEWO>3;BSRZi(LA3U%Jm{@{y+A+w(gzA< z7dBq6a1sEWa4cD0W7=Ld9z0H7RI^Z7vl(bfA;72j?SWCo`#5mVC$l1Q2--%V)-uN* z9ha*s-AdfbDZ8R8*fpwjzx=WvOtmSzGFjC#X)hD%Caeo^OWjS(3h|d9_*U)l%{Ab8 zfv$yoP{OuUl@$(-sEVNt{*=qi5P=lpxWVuz2?I7Dc%BRc+NGNw+323^ z5BXGfS71oP^%apUo(Y#xkxE)y?>BFzEBZ}UBbr~R4$%b7h3iZu3S(|A;&HqBR{nK& z$;GApNnz=kNO^FL&nYcfpB7Qg;hGJPsCW44CbkG1@l9pn0`~oKy5S777uH)l{irK!ru|X+;4&0D;VE*Ii|<3P zUx#xUqvZT5kVQxsF#~MwKnv7;1pR^0;PW@$@T7I?s`_rD1EGUdSA5Q(C<>5SzE!vw z;{L&kKFM-MO>hy#-8z`sdVx})^(Dc-dw;k-h*9O2_YZw}|9^y-|8RQ`BWJUJL(Cer zP5Z@fNc>pTXABbTRY-B5*MphpZv6#i802giwV&SkFCR zGMETyUm(KJbh+&$8X*RB#+{surjr;8^REEt`2&Dubw3$mx>|~B5IKZJ`s_6fw zKAZx9&PwBqW1Oz0r0A4GtnZd7XTKViX2%kPfv+^X3|_}RrQ2e3l=KG_VyY`H?I5&CS+lAX5HbA%TD9u6&s#v!G> zzW9n4J%d5ye7x0y`*{KZvqyXUfMEE^ZIffzI=Hh|3J}^yx7eL=s+TPH(Q2GT-sJ~3 zI463C{(ag7-hS1ETtU;_&+49ABt5!A7CwLwe z=SoA8mYZIQeU;9txI=zcQVbuO%q@E)JI+6Q!3lMc=Gbj(ASg-{V27u>z2e8n;Nc*pf}AqKz1D>p9G#QA+7mqqrEjGfw+85Uyh!=tTFTv3|O z+)-kFe_8FF_EkTw!YzwK^Hi^_dV5x-Ob*UWmD-})qKj9@aE8g240nUh=g|j28^?v7 zHRTBo{0KGaWBbyX2+lx$wgXW{3aUab6Bhm1G1{jTC7ota*JM6t+qy)c5<@ zpc&(jVdTJf(q3xB=JotgF$X>cxh7k*(T`-V~AR+`%e?YOeALQ2Qud( zz35YizXt(aW3qndR}fTw1p()Ol4t!D1pitGNL95{SX4ywzh0SF;=!wf=?Q?_h6!f* zh7<+GFi)q|XBsvXZ^qVCY$LUa{5?!CgwY?EG;*)0ceFe&=A;!~o`ae}Z+6me#^sv- z1F6=WNd6>M(~ z+092z>?Clrcp)lYNQl9jN-JF6n&Y0mp7|I0dpPx+4*RRK+VQI~>en0Dc;Zfl+x z_e_b7s`t1_A`RP3$H}y7F9_na%D7EM+**G_Z0l_nwE+&d_kc35n$Fxkd4r=ltRZhh zr9zER8>j(EdV&Jgh(+i}ltESBK62m0nGH6tCBr90!4)-`HeBmz54p~QP#dsu%nb~W z7sS|(Iydi>C@6ZM(Us!jyIiszMkd)^u<1D+R@~O>HqZIW&kearPWmT>63%_t2B{_G zX{&a(gOYJx!Hq=!T$RZ&<8LDnxsmx9+TBL0gTk$|vz9O5GkK_Yx+55^R=2g!K}NJ3 zW?C;XQCHZl7H`K5^BF!Q5X2^Mj93&0l_O3Ea3!Ave|ixx+~bS@Iv18v2ctpSt4zO{ zp#7pj!AtDmti$T`e9{s^jf(ku&E|83JIJO5Qo9weT6g?@vX!{7)cNwymo1+u(YQ94 zopuz-L@|5=h8A!(g-MXgLJC0MA|CgQF8qlonnu#j z;uCeq9ny9QSD|p)9sp3ebgY3rk#y0DA(SHdh$DUm^?GI<>%e1?&}w(b zdip1;P2Z=1wM+$q=TgLP$}svd!vk+BZ@h<^4R=GS2+sri7Z*2f`9 z5_?i)xj?m#pSVchk-SR!2&uNhzEi+#5t1Z$o0PoLGz*pT64%+|Wa+rd5Z}60(j?X= z{NLjtgRb|W?CUADqOS@(*MA-l|E342NxRaxLTDqsOyfWWe%N(jjBh}G zm7WPel6jXijaTiNita+z(5GCO0NM=Melxud57PP^d_U## zbA;9iVi<@wr0DGB8=T9Ab#2K_#zi=$igyK48@;V|W`fg~7;+!q8)aCOo{HA@vpSy-4`^!ze6-~8|QE||hC{ICKllG9fbg_Y7v z$jn{00!ob3!@~-Z%!rSZ0JO#@>|3k10mLK0JRKP-Cc8UYFu>z93=Ab-r^oL2 zl`-&VBh#=-?{l1TatC;VweM^=M7-DUE>m+xO7Xi6vTEsReyLs8KJ+2GZ&rxw$d4IT zPXy6pu^4#e;;ZTsgmG+ZPx>piodegkx2n0}SM77+Y*j^~ICvp#2wj^BuqRY*&cjmL zcKp78aZt>e{3YBb4!J_2|K~A`lN=u&5j!byw`1itV(+Q_?RvV7&Z5XS1HF)L2v6ji z&kOEPmv+k_lSXb{$)of~(BkO^py&7oOzpjdG>vI1kcm_oPFHy38%D4&A4h_CSo#lX z2#oqMCTEP7UvUR3mwkPxbl8AMW(e{ARi@HCYLPSHE^L<1I}OgZD{I#YH#GKnpRmW3 z2jkz~Sa(D)f?V?$gNi?6)Y;Sm{&?~2p=0&BUl_(@hYeX8YjaRO=IqO7neK0RsSNdYjD zaw$g2sG(>JR=8Iz1SK4`*kqd_3-?;_BIcaaMd^}<@MYbYisWZm2C2|Np_l|8r9yM|JkUngSo@?wci(7&O9a z%|V(4C1c9pps0xxzPbXH=}QTxc2rr7fXk$9`a6TbWKPCz&p=VsB8^W96W=BsB|7bc zf(QR8&Ktj*iz)wK&mW`#V%4XTM&jWNnDF56O+2bo<3|NyUhQ%#OZE8$Uv2a@J>D%t zMVMiHh?es!Ex19q&6eC&L=XDU_BA&uR^^w>fpz2_`U87q_?N2y;!Z!bjoeKrzfC)} z?m^PM=(z{%n9K`p|7Bz$LuC7!>tFOuN74MFELm}OD9?%jpT>38J;=1Y-VWtZAscaI z_8jUZ#GwWz{JqvGEUmL?G#l5E=*m>`cY?m*XOc*yOCNtpuIGD+Z|kn4Xww=BLrNYS zGO=wQh}Gtr|7DGXLF%|`G>J~l{k^*{;S-Zhq|&HO7rC_r;o`gTB7)uMZ|WWIn@e0( zX$MccUMv3ABg^$%_lNrgU{EVi8O^UyGHPNRt%R!1#MQJn41aD|_93NsBQhP80yP<9 zG4(&0u7AtJJXLPcqzjv`S~5;Q|5TVGccN=Uzm}K{v)?f7W!230C<``9(64}D2raRU zAW5bp%}VEo{4Rko`bD%Ehf=0voW?-4Mk#d3_pXTF!-TyIt6U+({6OXWVAa;s-`Ta5 zTqx&8msH3+DLrVmQOTBOAj=uoxKYT3DS1^zBXM?1W+7gI!aQNPYfUl{3;PzS9*F7g zWJN8x?KjBDx^V&6iCY8o_gslO16=kh(|Gp)kz8qlQ`dzxQv;)V&t+B}wwdi~uBs4? zu~G|}y!`3;8#vIMUdyC7YEx6bb^1o}G!Jky4cN?BV9ejBfN<&!4M)L&lRKiuMS#3} z_B}Nkv+zzxhy{dYCW$oGC&J(Ty&7%=5B$sD0bkuPmj7g>|962`(Q{ZZMDv%YMuT^KweiRDvYTEop3IgFv#)(w>1 zSzH>J`q!LK)c(AK>&Ib)A{g`Fdykxqd`Yq@yB}E{gnQV$K!}RsgMGWqC3DKE(=!{}ekB3+(1?g}xF>^icEJbc z5bdxAPkW90atZT+&*7qoLqL#p=>t-(-lsnl2XMpZcYeW|o|a322&)yO_8p(&Sw{|b zn(tY$xn5yS$DD)UYS%sP?c|z>1dp!QUD)l;aW#`%qMtQJjE!s2z`+bTSZmLK7SvCR z=@I4|U^sCwZLQSfd*ACw9B@`1c1|&i^W_OD(570SDLK`MD0wTiR8|$7+%{cF&){$G zU~|$^Ed?TIxyw{1$e|D$050n8AjJvvOWhLtLHbSB|HIfjMp+gu>DraHZJRrdO53(= z+o-f{+qNog+qSLB%KY;5>Av6X(>-qYk3IIEwZ5~6a+P9lMpC^ z8CJ0q>rEpjlsxCvJm=kms@tlN4+sv}He`xkr`S}bGih4t`+#VEIt{1veE z{ZLtb_pSbcfcYPf4=T1+|BtR!x5|X#x2TZEEkUB6kslKAE;x)*0x~ES0kl4Dex4e- zT2P~|lT^vUnMp{7e4OExfxak0EE$Hcw;D$ehTV4a6hqxru0$|Mo``>*a5=1Ym0u>BDJKO|=TEWJ5jZu!W}t$Kv{1!q`4Sn7 zrxRQOt>^6}Iz@%gA3&=5r;Lp=N@WKW;>O!eGIj#J;&>+3va^~GXRHCY2}*g#9ULab zitCJt-OV0*D_Q3Q`p1_+GbPxRtV_T`jyATjax<;zZ?;S+VD}a(aN7j?4<~>BkHK7bO8_Vqfdq1#W&p~2H z&w-gJB4?;Q&pG9%8P(oOGZ#`!m>qAeE)SeL*t8KL|1oe;#+uOK6w&PqSDhw^9-&Fa zuEzbi!!7|YhlWhqmiUm!muO(F8-F7|r#5lU8d0+=;<`{$mS=AnAo4Zb^{%p}*gZL! zeE!#-zg0FWsSnablw!9$<&K(#z!XOW z;*BVx2_+H#`1b@>RtY@=KqD)63brP+`Cm$L1@ArAddNS1oP8UE$p05R=bvZoYz+^6 z<)!v7pRvi!u_-V?!d}XWQR1~0q(H3{d^4JGa=W#^Z<@TvI6J*lk!A zZ*UIKj*hyO#5akL*Bx6iPKvR3_2-^2mw|Rh-3O_SGN3V9GRo52Q;JnW{iTGqb9W99 z7_+F(Op6>~3P-?Q8LTZ-lwB}xh*@J2Ni5HhUI3`ct|*W#pqb>8i*TXOLn~GlYECIj zhLaa_rBH|1jgi(S%~31Xm{NB!30*mcsF_wgOY2N0XjG_`kFB+uQuJbBm3bIM$qhUyE&$_u$gb zpK_r{99svp3N3p4yHHS=#csK@j9ql*>j0X=+cD2dj<^Wiu@i>c_v zK|ovi7}@4sVB#bzq$n3`EgI?~xDmkCW=2&^tD5RuaSNHf@Y!5C(Is$hd6cuyoK|;d zO}w2AqJPS`Zq+(mc*^%6qe>1d&(n&~()6-ZATASNPsJ|XnxelLkz8r1x@c2XS)R*H(_B=IN>JeQUR;T=i3<^~;$<+8W*eRKWGt7c#>N`@;#!`kZ!P!&{9J1>_g8Zj zXEXxmA=^{8A|3=Au+LfxIWra)4p<}1LYd_$1KI0r3o~s1N(x#QYgvL4#2{z8`=mXy zQD#iJ0itk1d@Iy*DtXw)Wz!H@G2St?QZFz zVPkM%H8Cd2EZS?teQN*Ecnu|PrC!a7F_XX}AzfZl3fXfhBtc2-)zaC2eKx*{XdM~QUo4IwcGgVdW69 z1UrSAqqMALf^2|(I}hgo38l|Ur=-SC*^Bo5ej`hb;C$@3%NFxx5{cxXUMnTyaX{>~ zjL~xm;*`d08bG_K3-E+TI>#oqIN2=An(C6aJ*MrKlxj?-;G zICL$hi>`F%{xd%V{$NhisHSL~R>f!F7AWR&7b~TgLu6!3s#~8|VKIX)KtqTH5aZ8j zY?wY)XH~1_a3&>#j7N}0az+HZ;is;Zw(Am{MX}YhDTe(t{ZZ;TG}2qWYO+hdX}vp9 z@uIRR8g#y~-^E`Qyem(31{H0&V?GLdq9LEOb2(ea#e-$_`5Q{T%E?W(6 z(XbX*Ck%TQM;9V2LL}*Tf`yzai{0@pYMwBu%(I@wTY!;kMrzcfq0w?X`+y@0ah510 zQX5SU(I!*Fag4U6a7Lw%LL;L*PQ}2v2WwYF(lHx_Uz2ceI$mnZ7*eZ?RFO8UvKI0H z9Pq-mB`mEqn6n_W9(s~Jt_D~j!Ln9HA)P;owD-l~9FYszs)oEKShF9Zzcmnb8kZ7% zQ`>}ki1kwUO3j~ zEmh140sOkA9v>j@#56ymn_RnSF`p@9cO1XkQy6_Kog?0ivZDb`QWOX@tjMd@^Qr(p z!sFN=A)QZm!sTh(#q%O{Ovl{IxkF!&+A)w2@50=?a-+VuZt6On1;d4YtUDW{YNDN_ zG@_jZi1IlW8cck{uHg^g=H58lPQ^HwnybWy@@8iw%G! zwB9qVGt_?~M*nFAKd|{cGg+8`+w{j_^;nD>IrPf-S%YjBslSEDxgKH{5p)3LNr!lD z4ii)^%d&cCXIU7UK?^ZQwmD(RCd=?OxmY(Ko#+#CsTLT;p#A%{;t5YpHFWgl+@)N1 zZ5VDyB;+TN+g@u~{UrWrv)&#u~k$S&GeW)G{M#&Di)LdYk?{($Cq zZGMKeYW)aMtjmKgvF0Tg>Mmkf9IB#2tYmH-s%D_9y3{tfFmX1BSMtbe<(yqAyWX60 zzkgSgKb3c{QPG2MalYp`7mIrYg|Y<4Jk?XvJK)?|Ecr+)oNf}XLPuTZK%W>;<|r+% zTNViRI|{sf1v7CsWHvFrkQ$F7+FbqPQ#Bj7XX=#M(a~9^80}~l-DueX#;b}Ajn3VE z{BWI}$q{XcQ3g{(p>IOzFcAMDG0xL)H%wA)<(gl3I-oVhK~u_m=hAr&oeo|4lZbf} z+pe)c34Am<=z@5!2;_lwya;l?xV5&kWe}*5uBvckm(d|7R>&(iJNa6Y05SvlZcWBlE{{%2- z`86)Y5?H!**?{QbzGG~|k2O%eA8q=gxx-3}&Csf6<9BsiXC)T;x4YmbBIkNf;0Nd5 z%whM^!K+9zH>on_<&>Ws?^v-EyNE)}4g$Fk?Z#748e+GFp)QrQQETx@u6(1fk2!(W zWiCF~MomG*y4@Zk;h#2H8S@&@xwBIs|82R*^K(i*0MTE%Rz4rgO&$R zo9Neb;}_ulaCcdn3i17MO3NxzyJ=l;LU*N9ztBJ30j=+?6>N4{9YXg$m=^9@Cl9VY zbo^{yS@gU=)EpQ#;UIQBpf&zfCA;00H-ee=1+TRw@(h%W=)7WYSb5a%$UqNS@oI@= zDrq|+Y9e&SmZrH^iA>Of8(9~Cf-G(P^5Xb%dDgMMIl8gk6zdyh`D3OGNVV4P9X|EvIhplXDld8d z^YWtYUz@tpg*38Xys2?zj$F8%ivA47cGSl;hjD23#*62w3+fwxNE7M7zVK?x_`dBSgPK zWY_~wF~OEZi9|~CSH8}Xi>#8G73!QLCAh58W+KMJJC81{60?&~BM_0t-u|VsPBxn* zW7viEKwBBTsn_A{g@1!wnJ8@&h&d>!qAe+j_$$Vk;OJq`hrjzEE8Wjtm)Z>h=*M25 zOgETOM9-8xuuZ&^@rLObtcz>%iWe%!uGV09nUZ*nxJAY%&KAYGY}U1WChFik7HIw% zZP$3Bx|TG_`~19XV7kfi2GaBEhKap&)Q<9`aPs#^!kMjtPb|+-fX66z3^E)iwyXK7 z8)_p<)O{|i&!qxtgBvWXx8*69WO$5zACl++1qa;)0zlXf`eKWl!0zV&I`8?sG)OD2Vy?reNN<{eK+_ za4M;Hh%&IszR%)&gpgRCP}yheQ+l#AS-GnY81M!kzhWxIR?PW`G3G?} z$d%J28uQIuK@QxzGMKU_;r8P0+oIjM+k)&lZ39i#(ntY)*B$fdJnQ3Hw3Lsi8z&V+ zZly2}(Uzpt2aOubRjttzqrvinBFH4jrN)f0hy)tj4__UTwN)#1fj3-&dC_Vh7}ri* zfJ=oqLMJ-_<#rwVyN}_a-rFBe2>U;;1(7UKH!$L??zTbbzP#bvyg7OQBGQklJ~DgP zd<1?RJ<}8lWwSL)`jM53iG+}y2`_yUvC!JkMpbZyb&50V3sR~u+lok zT0uFRS-yx@8q4fPRZ%KIpLp8R#;2%c&Ra4p(GWRT4)qLaPNxa&?8!LRVdOUZ)2vrh zBSx&kB%#Y4!+>~)<&c>D$O}!$o{<1AB$M7-^`h!eW;c(3J~ztoOgy6Ek8Pwu5Y`Xion zFl9fb!k2`3uHPAbd(D^IZmwR5d8D$495nN2`Ue&`W;M-nlb8T-OVKt|fHk zBpjX$a(IR6*-swdNk@#}G?k6F-~c{AE0EWoZ?H|ZpkBxqU<0NUtvubJtwJ1mHV%9v?GdDw; zAyXZiD}f0Zdt-cl9(P1la+vQ$Er0~v}gYJVwQazv zH#+Z%2CIfOf90fNMGos|{zf&N`c0@x0N`tkFv|_9af3~<0z@mnf*e;%r*Fbuwl-IW z{}B3=(mJ#iwLIPiUP`J3SoP~#)6v;aRXJ)A-pD2?_2_CZ#}SAZ<#v7&Vk6{*i(~|5 z9v^nC`T6o`CN*n%&9+bopj^r|E(|pul;|q6m7Tx+U|UMjWK8o-lBSgc3ZF=rP{|l9 zc&R$4+-UG6i}c==!;I#8aDIbAvgLuB66CQLRoTMu~jdw`fPlKy@AKYWS-xyZzPg&JRAa@m-H43*+ne!8B7)HkQY4 zIh}NL4Q79a-`x;I_^>s$Z4J4-Ngq=XNWQ>yAUCoe&SMAYowP>r_O}S=V+3=3&(O=h zNJDYNs*R3Y{WLmBHc?mFEeA4`0Y`_CN%?8qbDvG2m}kMAiqCv`_BK z_6a@n`$#w6Csr@e2YsMx8udNWtNt=kcqDZdWZ-lGA$?1PA*f4?X*)hjn{sSo8!bHz zb&lGdAgBx@iTNPK#T_wy`KvOIZvTWqSHb=gWUCKXAiB5ckQI`1KkPx{{%1R*F2)Oc z(9p@yG{fRSWE*M9cdbrO^)8vQ2U`H6M>V$gK*rz!&f%@3t*d-r3mSW>D;wYxOhUul zk~~&ip5B$mZ~-F1orsq<|1bc3Zpw6)Ws5;4)HilsN;1tx;N6)tuePw& z==OlmaN*ybM&-V`yt|;vDz(_+UZ0m&&9#{9O|?0I|4j1YCMW;fXm}YT$0%EZ5^YEI z4i9WV*JBmEU{qz5O{#bs`R1wU%W$qKx?bC|e-iS&d*Qm7S=l~bMT{~m3iZl+PIXq{ zn-c~|l)*|NWLM%ysfTV-oR0AJ3O>=uB-vpld{V|cWFhI~sx>ciV9sPkC*3i0Gg_9G!=4ar*-W?D9)?EFL1=;O+W8}WGdp8TT!Fgv z{HKD`W>t(`Cds_qliEzuE!r{ihwEv1l5o~iqlgjAyGBi)$%zNvl~fSlg@M=C{TE;V zQkH`zS8b&!ut(m)%4n2E6MB>p*4(oV>+PT51#I{OXs9j1vo>9I<4CL1kv1aurV*AFZ^w_qfVL*G2rG@D2 zrs87oV3#mf8^E5hd_b$IXfH6vHe&lm@7On~Nkcq~YtE!}ad~?5*?X*>y`o;6Q9lkk zmf%TYonZM`{vJg$`lt@MXsg%*&zZZ0uUSse8o=!=bfr&DV)9Y6$c!2$NHyYAQf*Rs zk{^?gl9E z5Im8wlAsvQ6C2?DyG@95gUXZ3?pPijug25g;#(esF_~3uCj3~94}b*L>N2GSk%Qst z=w|Z>UX$m!ZOd(xV*2xvWjN&c5BVEdVZ0wvmk)I+YxnyK%l~caR=7uNQ=+cnNTLZ@&M!I$Mj-r{!P=; z`C2)D=VmvK8@T5S9JZoRtN!S*D_oqOxyy!q6Zk|~4aT|*iRN)fL)c>-yycR>-is0X zKrko-iZw(f(!}dEa?hef5yl%p0-v-8#8CX8!W#n2KNyT--^3hq6r&`)5Y@>}e^4h- zlPiDT^zt}Ynk&x@F8R&=)k8j$=N{w9qUcIc&)Qo9u4Y(Ae@9tA`3oglxjj6c{^pN( zQH+Uds2=9WKjH#KBIwrQI%bbs`mP=7V>rs$KG4|}>dxl_k!}3ZSKeEen4Iswt96GGw`E6^5Ov)VyyY}@itlj&sao|>Sb5 zeY+#1EK(}iaYI~EaHQkh7Uh>DnzcfIKv8ygx1Dv`8N8a6m+AcTa-f;17RiEed>?RT zk=dAksmFYPMV1vIS(Qc6tUO+`1jRZ}tcDP? zt)=7B?yK2RcAd1+Y!$K5*ds=SD;EEqCMG6+OqPoj{&8Y5IqP(&@zq@=A7+X|JBRi4 zMv!czlMPz)gt-St2VZwDD=w_S>gRpc-g zUd*J3>bXeZ?Psjohe;z7k|d<*T21PA1i)AOi8iMRwTBSCd0ses{)Q`9o&p9rsKeLaiY zluBw{1r_IFKR76YCAfl&_S1*(yFW8HM^T()&p#6y%{(j7Qu56^ZJx1LnN`-RTwimdnuo*M8N1ISl+$C-%=HLG-s} zc99>IXRG#FEWqSV9@GFW$V8!{>=lSO%v@X*pz*7()xb>=yz{E$3VE;e)_Ok@A*~El zV$sYm=}uNlUxV~6e<6LtYli1!^X!Ii$L~j4e{sI$tq_A(OkGquC$+>Rw3NFObV2Z)3Rt~Jr{oYGnZaFZ^g5TDZlg;gaeIP} z!7;T{(9h7mv{s@piF{-35L=Ea%kOp;^j|b5ZC#xvD^^n#vPH=)lopYz1n?Kt;vZmJ z!FP>Gs7=W{sva+aO9S}jh0vBs+|(B6Jf7t4F^jO3su;M13I{2rd8PJjQe1JyBUJ5v zcT%>D?8^Kp-70bP8*rulxlm)SySQhG$Pz*bo@mb5bvpLAEp${?r^2!Wl*6d7+0Hs_ zGPaC~w0E!bf1qFLDM@}zso7i~(``)H)zRgcExT_2#!YOPtBVN5Hf5~Ll3f~rWZ(UsJtM?O*cA1_W0)&qz%{bDoA}{$S&-r;0iIkIjbY~ zaAqH45I&ALpP=9Vof4OapFB`+_PLDd-0hMqCQq08>6G+C;9R~}Ug_nm?hhdkK$xpI zgXl24{4jq(!gPr2bGtq+hyd3%Fg%nofK`psHMs}EFh@}sdWCd!5NMs)eZg`ZlS#O0 zru6b8#NClS(25tXqnl{|Ax@RvzEG!+esNW-VRxba(f`}hGoqci$U(g30i}2w9`&z= zb8XjQLGN!REzGx)mg~RSBaU{KCPvQx8)|TNf|Oi8KWgv{7^tu}pZq|BS&S<53fC2K4Fw6>M^s$R$}LD*sUxdy6Pf5YKDbVet;P!bw5Al-8I1Nr(`SAubX5^D9hk6$agWpF}T#Bdf{b9-F#2WVO*5N zp+5uGgADy7m!hAcFz{-sS0kM7O)qq*rC!>W@St~^OW@R1wr{ajyYZq5H!T?P0e+)a zaQ%IL@X_`hzp~vRH0yUblo`#g`LMC%9}P;TGt+I7qNcBSe&tLGL4zqZqB!Bfl%SUa z6-J_XLrnm*WA`34&mF+&e1sPCP9=deazrM=Pc4Bn(nV;X%HG^4%Afv4CI~&l!Sjzb z{rHZ3od0!Al{}oBO>F*mOFAJrz>gX-vs!7>+_G%BB(ljWh$252j1h;9p~xVA=9_`P z5KoFiz96_QsTK%B&>MSXEYh`|U5PjX1(+4b#1PufXRJ*uZ*KWdth1<0 zsAmgjT%bowLyNDv7bTUGy|g~N34I-?lqxOUtFpTLSV6?o?<7-UFy*`-BEUsrdANh} zBWkDt2SAcGHRiqz)x!iVoB~&t?$yn6b#T=SP6Ou8lW=B>=>@ik93LaBL56ub`>Uo!>0@O8?e)$t(sgy$I z6tk3nS@yFFBC#aFf?!d_3;%>wHR;A3f2SP?Na8~$r5C1N(>-ME@HOpv4B|Ty7%jAv zR}GJwsiJZ5@H+D$^Cwj#0XA_(m^COZl8y7Vv(k=iav1=%QgBOVzeAiw zaDzzdrxzj%sE^c9_uM5D;$A_7)Ln}BvBx^=)fO+${ou%B*u$(IzVr-gH3=zL6La;G zu0Kzy5CLyNGoKRtK=G0-w|tnwI)puPDOakRzG(}R9fl7#<|oQEX;E#yCWVg95 z;NzWbyF&wGg_k+_4x4=z1GUcn6JrdX4nOVGaAQ8#^Ga>aFvajQN{!+9rgO-dHP zIp@%&ebVg}IqnRWwZRTNxLds+gz2@~VU(HI=?Epw>?yiEdZ>MjajqlO>2KDxA>)cj z2|k%dhh%d8SijIo1~20*5YT1eZTDkN2rc^zWr!2`5}f<2f%M_$to*3?Ok>e9$X>AV z2jYmfAd)s|(h?|B(XYrIfl=Wa_lBvk9R1KaP{90-z{xKi+&8=dI$W0+qzX|ZovWGOotP+vvYR(o=jo?k1=oG?%;pSqxcU* zWVGVMw?z__XQ9mnP!hziHC`ChGD{k#SqEn*ph6l46PZVkm>JF^Q{p&0=MKy_6apts z`}%_y+Tl_dSP(;Ja&sih$>qBH;bG;4;75)jUoVqw^}ee=ciV;0#t09AOhB^Py7`NC z-m+ybq1>_OO+V*Z>dhk}QFKA8V?9Mc4WSpzj{6IWfFpF7l^au#r7&^BK2Ac7vCkCn{m0uuN93Ee&rXfl1NBY4NnO9lFUp zY++C1I;_{#OH#TeP2Dp?l4KOF8ub?m6zE@XOB5Aiu$E~QNBM@;r+A5mF2W1-c7>ex zHiB=WJ&|`6wDq*+xv8UNLVUy4uW1OT>ey~Xgj@MMpS@wQbHAh>ysYvdl-1YH@&+Q! z075(Qd4C!V`9Q9jI4 zSt{HJRvZec>vaL_brKhQQwbpQd4_Lmmr0@1GdUeU-QcC{{8o=@nwwf>+dIKFVzPriGNX4VjHCa zTbL9w{Y2V87c2ofX%`(48A+4~mYTiFFl!e{3K^C_k%{&QTsgOd0*95KmWN)P}m zTRr{`f7@=v#+z_&fKYkQT!mJn{*crj%ZJz#(+c?>cD&2Lo~FFAWy&UG*Op^pV`BR^I|g?T>4l5;b|5OQ@t*?_Slp`*~Y3`&RfKD^1uLezIW(cE-Dq2z%I zBi8bWsz0857`6e!ahet}1>`9cYyIa{pe53Kl?8|Qg2RGrx@AlvG3HAL-^9c^1GW;)vQt8IK+ zM>!IW*~682A~MDlyCukldMd;8P|JCZ&oNL(;HZgJ>ie1PlaInK7C@Jg{3kMKYui?e!b`(&?t6PTb5UPrW-6DVU%^@^E`*y-Fd(p|`+JH&MzfEq;kikdse ziFOiDWH(D< zyV7Rxt^D0_N{v?O53N$a2gu%1pxbeK;&ua`ZkgSic~$+zvt~|1Yb=UfKJW2F7wC^evlPf(*El+#}ZBy0d4kbVJsK- z05>;>?HZO(YBF&v5tNv_WcI@O@LKFl*VO?L(!BAd!KbkVzo;v@~3v`-816GG?P zY+H3ujC>5=Am3RIZDdT#0G5A6xe`vGCNq88ZC1aVXafJkUlcYmHE^+Z{*S->ol%-O znm9R0TYTr2w*N8Vs#s-5=^w*{Y}qp5GG)Yt1oLNsH7y~N@>Eghms|K*Sdt_u!&I}$ z+GSdFTpbz%KH+?B%Ncy;C`uW6oWI46(tk>r|5|-K6)?O0d_neghUUOa9BXHP*>vi; z={&jIGMn-92HvInCMJcyXwHTJ42FZp&Wxu+9Rx;1x(EcIQwPUQ@YEQQ`bbMy4q3hP zNFoq~Qd0=|xS-R}k1Im3;8s{BnS!iaHIMLx)aITl)+)?Yt#fov|Eh>}dv@o6R{tG>uHsy&jGmWN5+*wAik|78(b?jtysPHC#e+Bzz~V zS3eEXv7!Qn4uWi!FS3B?afdD*{fr9>B~&tc671fi--V}~E4un;Q|PzZRwk-azprM$4AesvUb5`S`(5x#5VJ~4%ET6&%GR$}muHV-5lTsCi_R|6KM(g2PCD@|yOpKluT zakH!1V7nKN)?6JmC-zJoA#ciFux8!)ajiY%K#RtEg$gm1#oKUKX_Ms^%hvKWi|B=~ zLbl-L)-=`bfhl`>m!^sRR{}cP`Oim-{7}oz4p@>Y(FF5FUEOfMwO!ft6YytF`iZRq zfFr{!&0Efqa{1k|bZ4KLox;&V@ZW$997;+Ld8Yle91he{BfjRhjFTFv&^YuBr^&Pe zswA|Bn$vtifycN8Lxr`D7!Kygd7CuQyWqf}Q_PM}cX~S1$-6xUD%-jrSi24sBTFNz(Fy{QL2AmNbaVggWOhP;UY4D>S zqKr!UggZ9Pl9Nh_H;qI`-WoH{ceXj?m8y==MGY`AOJ7l0Uu z)>M%?dtaz2rjn1SW3k+p`1vs&lwb%msw8R!5nLS;upDSxViY98IIbxnh{}mRfEp=9 zbrPl>HEJeN7J=KnB6?dwEA6YMs~chHNG?pJsEj#&iUubdf3JJwu=C(t?JpE6xMyhA3e}SRhunDC zn-~83*9=mADUsk^sCc%&&G1q5T^HR9$P#2DejaG`Ui*z1hI#h7dwpIXg)C{8s< z%^#@uQRAg-$z&fmnYc$Duw63_Zopx|n{Bv*9Xau{a)2%?H<6D>kYY7_)e>OFT<6TT z0A}MQLgXbC2uf`;67`mhlcUhtXd)Kbc$PMm=|V}h;*_%vCw4L6r>3Vi)lE5`8hkSg zNGmW-BAOO)(W((6*e_tW&I>Nt9B$xynx|sj^ux~?q?J@F$L4;rnm_xy8E*JYwO-02u9_@@W0_2@?B@1J{y~Q39N3NX^t7#`=34Wh)X~sU&uZWgS1Z09%_k|EjA4w_QqPdY`oIdv$dJZ;(!k)#U8L+|y~gCzn+6WmFt#d{OUuKHqh1-uX_p*Af8pFYkYvKPKBxyid4KHc}H` z*KcyY;=@wzXYR{`d{6RYPhapShXIV?0cg_?ahZ7do)Ot#mxgXYJYx}<%E1pX;zqHd zf!c(onm{~#!O$2`VIXezECAHVd|`vyP)Uyt^-075X@NZDBaQt<>trA3nY-Dayki4S zZ^j6CCmx1r46`4G9794j-WC0&R9(G7kskS>=y${j-2;(BuIZTLDmAyWTG~`0)Bxqk zd{NkDe9ug|ms@0A>JVmB-IDuse9h?z9nw!U6tr7t-Lri5H`?TjpV~8(gZWFq4Vru4 z!86bDB;3lpV%{rZ`3gtmcRH1hjj!loI9jN>6stN6A*ujt!~s!2Q+U1(EFQEQb(h4E z6VKuRouEH`G6+8Qv2C)K@^;ldIuMVXdDDu}-!7FS8~k^&+}e9EXgx~)4V4~o6P^52 z)a|`J-fOirL^oK}tqD@pqBZi_;7N43%{IQ{v&G9^Y^1?SesL`;Z(dt!nn9Oj5Odde%opv&t zxJ><~b#m+^KV&b?R#)fRi;eyqAJ_0(nL*61yPkJGt;gZxSHY#t>ATnEl-E%q$E16% zZdQfvhm5B((y4E3Hk6cBdwGdDy?i5CqBlCVHZr-rI$B#>Tbi4}Gcvyg_~2=6O9D-8 zY2|tKrNzbVR$h57R?Pe+gUU_il}ZaWu|Az#QO@};=|(L-RVf0AIW zq#pO+RfM7tdV`9lI6g;{qABNId`fG%U9Va^ravVT^)CklDcx)YJKeJdGpM{W1v8jg z@&N+mR?BPB=K1}kNwXk_pj44sd>&^;d!Z~P>O78emE@Qp@&8PyB^^4^2f7e)gekMv z2aZNvP@;%i{+_~>jK7*2wQc6nseT^n6St9KG#1~Y@$~zR_=AcO2hF5lCoH|M&c{vR zSp(GRVVl=T*m~dIA;HvYm8HOdCkW&&4M~UDd^H)`p__!4k+6b)yG0Zcek8OLw$C^K z3-BbLiG_%qX|ZYpXJ$(c@aa7b4-*IQkDF}=gZSV`*ljP|5mWuHSCcf$5qqhZTv&P?I$z^>}qP(q!Aku2yA5vu38d8x*q{6-1`%PrE_r0-9Qo?a#7Zbz#iGI7K<(@k^|i4QJ1H z4jx?{rZbgV!me2VT72@nBjucoT zUM9;Y%TCoDop?Q5fEQ35bCYk7!;gH*;t9t-QHLXGmUF;|vm365#X)6b2Njsyf1h9JW#x$;@x5Nx2$K$Z-O3txa%;OEbOn6xBzd4n4v)Va=sj5 z%rb#j7{_??Tjb8(Hac<^&s^V{yO-BL*uSUk2;X4xt%NC8SjO-3?;Lzld{gM5A=9AV z)DBu-Z8rRvXXwSVDH|dL-3FODWhfe1C_iF``F05e{dl(MmS|W%k-j)!7(ARkV?6r~ zF=o42y+VapxdZn;GnzZfGu<6oG-gQ7j7Zvgo7Am@jYxC2FpS@I;Jb%EyaJDBQC(q% zKlZ}TVu!>;i3t~OAgl@QYy1X|T~D{HOyaS*Bh}A}S#a9MYS{XV{R-|niEB*W%GPW! zP^NU(L<}>Uab<;)#H)rYbnqt|dOK(-DCnY==%d~y(1*{D{Eo1cqIV8*iMfx&J*%yh zx=+WHjt0q2m*pLx8=--UqfM6ZWjkev>W-*}_*$Y(bikH`#-Gn#!6_ zIA&kxn;XYI;eN9yvqztK-a113A%97in5CL5Z&#VsQ4=fyf&3MeKu70)(x^z_uw*RG zo2Pv&+81u*DjMO6>Mrr7vKE2CONqR6C0(*;@4FBM;jPIiuTuhQ-0&C)JIzo_k>TaS zN_hB;_G=JJJvGGpB?uGgSeKaix~AkNtYky4P7GDTW6{rW{}V9K)Cn^vBYKe*OmP!; zohJs=l-0sv5&phSCi&8JSrokrKP$LVa!LbtlN#T^cedgH@ijt5T-Acxd9{fQY z4qsg1O{|U5Rzh_j;9QD(g*j+*=xULyi-FY|-mUXl7-2O`TYQny<@jSQ%^ye*VW_N< z4mmvhrDYBJ;QSoPvwgi<`7g*Pwg5ANA8i%Kum;<=i|4lwEdN+`)U3f2%bcRZRK!P z70kd~`b0vX=j20UM5rBO#$V~+grM)WRhmzb15ya^Vba{SlSB4Kn}zf#EmEEhGruj| zBn0T2n9G2_GZXnyHcFkUlzdRZEZ0m&bP-MxNr zd;kl7=@l^9TVrg;Y6J(%!p#NV*Lo}xV^Nz0#B*~XRk0K2hgu5;7R9}O=t+R(r_U%j z$`CgPL|7CPH&1cK5vnBo<1$P{WFp8#YUP%W)rS*a_s8kKE@5zdiAh*cjmLiiKVoWD z!y$@Cc5=Wj^VDr$!04FI#%pu6(a9 zM_FAE+?2tp2<$Sqp5VtADB>yY*cRR+{OeZ5g2zW=`>(tA~*-T)X|ahF{xQmypWp%2X{385+=0S|Jyf`XA-c7wAx`#5n2b-s*R>m zP30qtS8aUXa1%8KT8p{=(yEvm2Gvux5z22;isLuY5kN{IIGwYE1Pj);?AS@ex~FEt zQ`Gc|)o-eOyCams!|F0_;YF$nxcMl^+z0sSs@ry01hpsy3p<|xOliR zr-dxK0`DlAydK!br?|Xi(>buASy4@C8)ccRCJ3w;v&tA1WOCaieifLl#(J% zODPi5fr~ASdz$Hln~PVE6xekE{Xb286t(UtYhDWo8JWN6sNyRVkIvC$unIl8QMe@^ z;1c<0RO5~Jv@@gtDGPDOdqnECOurq@l02NC#N98-suyq_)k(`G=O`dJU8I8LcP!4z z8fkgqViqFbR+3IkwLa)^>Z@O{qxTLU63~^lod{@${q;-l?S|4Tq0)As-Gz!D(*P)Vf6wm6B8GGWi7B)Q^~T?sseZeI+}LyBAG!LRZn_ktDlht1j2ok@ljteyuNUkG67 zipkCx-7k(FZQhYjZ%T9X7`tO99$Wj~K`9r0IkWhPul`Q_t1YnVK=YI1dMc_b!FEU4 zkv=PGf{5$P#w{|m92tfVnsnfd%%KW;1a*cLmga4bSYl^*49M4cs+Fe>P!n=$G6hL6 z>IM&0+c(Nvr0I!5CGx7WK*Z3V^w0+QcF=hU0B4=+;=tn*+XDxKa;NB-z4O~I zf}TSb^Z;L_Og>!D1`;w@zf@GCqCUNY%N?IPmEkTco^}bX~BWM_Hamu05>#B zBh%QfUeHPu`MsYVQQ3hOT;HmP_C|nOl zjluk7vaSICyQ01h`^c)DWp>cxPjGEc6D^~2L79hyK_J#<9H#8o`&XM4=aB`@< z<|1oR6Djf))P1l2C{qSwa4u-&LDG{FLz#ym_@I+vo}D}#%;vNN%& zW&9||THv_^B!1Fo+$3A6hEAed$I-{a^6FVvwMtT~e%*&RvY5mj<@(-{y^xn6ZCYqNK|#v^xbWpy15YL18z#Y&5YwOnd!A*@>k^7CaX0~4*6QB{Bgh$KJqesFc(lSQ{iQAKY%Ge}2CeuFJ{4YmgrP(gpcH zXJQjSH^cw`Z0tV^axT&RkOBP2A~#fvmMFrL&mwdDn<*l3;3A425_lzHL`+6sT9LeY zu@TH0u4tj199jQBzz*~Up5)7=4OP%Ok{rxQYNb!hphAoW-BFJn>O=%ov*$ir?dIx% z56Y`>?(1YQ8Fc(D7pq2`9swz@*RIoTAvMT%CPbt;$P%eG(P%*ZMjklLoXqTE*Jg^T zlEQbMi@_E|ll_>pTJ!(-x41R}4sY<5A2VVQ^#4eE{imHt#NEi+#p#EBC2C=9B4A|n zqe03T*czDqQ-VxZ+jPQG!}!M0SlFm^@wTW?otBZ+q~xkk29u1i7Q|kaJ(9{AiP1`p zbEe5&!>V;1wnQ1-Qpyn2B5!S(lh=38hl6IilCC6n4|yz~q94S9_5+Od*$c)%r|)f~ z;^-lf=6POs>Ur4i-F>-wm;3(v7Y_itzt)*M!b~&oK%;re(p^>zS#QZ+Rt$T#Y%q1{ zx+?@~+FjR1MkGr~N`OYBSsVr}lcBZ+ij!0SY{^w((2&U*M`AcfSV9apro+J{>F&tX zT~e zMvsv$Q)AQl_~);g8OOt4plYESr8}9?T!yO(Wb?b~1n0^xVG;gAP}d}#%^9wqN7~F5 z!jWIpqxZ28LyT|UFH!u?V>F6&Hd~H|<(3w*o{Ps>G|4=z`Ws9oX5~)V=uc?Wmg6y< zJKnB4Opz^9v>vAI)ZLf2$pJdm>ZwOzCX@Yw0;-fqB}Ow+u`wglzwznQAP(xbs`fA7 zylmol=ea)g}&;8;)q0h7>xCJA+01w+RY`x`RO% z9g1`ypy?w-lF8e5xJXS4(I^=k1zA46V)=lkCv?k-3hR9q?oZPzwJl$yOHWeMc9wFuE6;SObNsmC4L6;eWPuAcfHoxd59gD7^Xsb$lS_@xI|S-gb? z*;u@#_|4vo*IUEL2Fxci+@yQY6<&t=oNcWTVtfi1Ltveqijf``a!Do0s5e#BEhn5C zBXCHZJY-?lZAEx>nv3k1lE=AN10vz!hpeUY9gy4Xuy940j#Rq^yH`H0W2SgXtn=X1 zV6cY>fVbQhGwQIaEG!O#p)aE8&{gAS z^oVa-0M`bG`0DE;mV)ATVNrt;?j-o*?Tdl=M&+WrW12B{+5Um)qKHd_HIv@xPE+;& zPI|zXfrErYzDD2mOhtrZLAQ zP#f9e!vqBSyoKZ#{n6R1MAW$n8wH~)P3L~CSeBrk4T0dzIp&g9^(_5zY*7$@l%%nL zG$Z}u8pu^Mw}%{_KDBaDjp$NWes|DGAn~WKg{Msbp*uPiH9V|tJ_pLQROQY?T0Pmt zs4^NBZbn7B^L%o#q!-`*+cicZS9Ycu+m)rDb98CJ+m1u}e5ccKwbc0|q)ICBEnLN# zV)8P1s;r@hE3sG2wID0@`M9XIn~hm+W1(scCZr^Vs)w4PKIW_qasyjbOBC`ixG8K$ z9xu^v(xNy4HV{wu2z-B87XG#yWu~B6@|*X#BhR!_jeF*DG@n_RupAvc{DsC3VCHT# za6Z&9k#<*y?O0UoK3MLlSX6wRh`q&E>DOZTG=zRxj0pR0c3vskjPOqkh9;o>a1>!P zxD|LU0qw6S4~iN8EIM2^$k72(=a6-Tk?%1uSj@0;u$0f*LhC%|mC`m`w#%W)IK zN_UvJkmzdP84ZV7CP|@k>j^ zPa%;PDu1TLyNvLQdo!i1XA|49nN}DuTho6=z>Vfduv@}mpM({Jh289V%W@9opFELb z?R}D#CqVew1@W=XY-SoMNul(J)zX(BFP?#@9x<&R!D1X&d|-P;VS5Gmd?Nvu$eRNM zG;u~o*~9&A2k&w}IX}@x>LMHv`ith+t6`uQGZP8JyVimg>d}n$0dDw$Av{?qU=vRq zU@e2worL8vTFtK@%pdbaGdUK*BEe$XE=pYxE_q{(hUR_Gzkn=c#==}ZS^C6fKBIfG z@hc);p+atn`3yrTY^x+<y`F0>p02jUL8cgLa|&yknDj;g73m&Sm&@ju91?uG*w?^d%Yap&d2Bp3v7KlQmh z(N<38o-iRk9*UV?wFirV>|46JqxOZ_o8xv_eJ1dv} zw&zDHZOU%`U{9ckU8DS$lB6J!B`JuThCnwKphODv`3bd?_=~tjNHstM>xoA53-p#F zLCVB^E`@r_D>yHLr10Sm4NRX8FQ+&zw)wt)VsPmLK|vLwB-}}jwEIE!5fLE;(~|DA ztMr8D0w^FPKp{trPYHXI7-;UJf;2+DOpHt%*qRgdWawy1qdsj%#7|aRSfRmaT=a1> zJ8U>fcn-W$l-~R3oikH+W$kRR&a$L!*HdKD_g}2eu*3p)twz`D+NbtVCD|-IQdJlFnZ0%@=!g`nRA(f!)EnC0 zm+420FOSRm?OJ;~8D2w5HD2m8iH|diz%%gCWR|EjYI^n7vRN@vcBrsyQ;zha15{uh zJ^HJ`lo+k&C~bcjhccoiB77-5=SS%s7UC*H!clrU$4QY@aPf<9 z0JGDeI(6S%|K-f@U#%SP`{>6NKP~I#&rSHBTUUvHn#ul4*A@BcRR`#yL%yfZj*$_% zAa$P%`!8xJp+N-Zy|yRT$gj#4->h+eV)-R6l}+)9_3lq*A6)zZ)bnogF9`5o!)ub3 zxCx|7GPCqJlnRVPb&!227Ok@-5N2Y6^j#uF6ihXjTRfbf&ZOP zVc$!`$ns;pPW_=n|8Kw4*2&qx+WMb9!DQ7lC1f@DZyr|zeQcC|B6ma*0}X%BSmFJ6 zeDNWGf=Pmmw5b{1)OZ6^CMK$kw2z*fqN+oup2J8E^)mHj?>nWhBIN|hm#Km4eMyL= zXRqzro9k7(ulJi5J^<`KHJAh-(@W=5x>9+YMFcx$6A5dP-5i6u!k*o-zD z37IkyZqjlNh*%-)rAQrCjJo)u9Hf9Yb1f3-#a=nY&M%a{t0g7w6>{AybZ9IY46i4+%^u zwq}TCN@~S>i7_2T>GdvrCkf&=-OvQV9V3$RR_Gk7$t}63L}Y6d_4l{3b#f9vup-7s z3yKz5)54OVLzH~Ty=HwVC=c$Tl=cvi1L?R>*#ki4t6pgqdB$sx6O(IIvYO8Q>&kq;c3Y-T?b z*6XAc?orv>?V7#vxmD7geKjf%v~%yjbp%^`%e>dw96!JAm4ybAJLo0+4=TB% zShgMl)@@lgdotD?C1Ok^o&hFRYfMbmlbfk677k%%Qy-BG3V9txEjZmK+QY5nlL2D$Wq~04&rwN`-ujpp)wUm5YQc}&tK#zUR zW?HbbHFfSDsT{Xh&RoKiGp)7WPX4 zD^3(}^!TS|hm?YC16YV59v9ir>ypihBLmr?LAY87PIHgRv*SS>FqZwNJKgf6hy8?9 zaGTxa*_r`ZhE|U9S*pn5Mngb7&%!as3%^ifE@zDvX`GP+=oz@p)rAl2KL}ZO1!-us zY`+7ln`|c!2=?tVsO{C}=``aibcdc1N#;c^$BfJr84=5DCy+OT4AB1BUWkDw1R$=FneVh*ajD&(j2IcWH8stMShVcMe zAi6d7p)>hgPJbcb(=NMw$Bo;gQ}3=hCQsi{6{2s~=ZEOizY(j{zYY-W8RiNjycv00 z8(JpE{}=CHx0ib3(nZgo776X=wBUbfk$y2r*}aNG@A0_zOa4k3?1EeH7Z43{@IP>{^M+M`M)0w*@Go z>kg~UfgP1{vH+IU(0p(VRVlLNMHN1C&3cFnp*}4d1a*kwHJL)rjf`Fi5z)#RGTr7E zOhWfTtQyCo&8_N(zIYEugQI}_k|2X(=dMA43Nt*e93&otv`ha-i;ACB$tIK% zRDOtU^1CD5>7?&Vbh<+cz)(CBM}@a)qZ^ld?uYfp3OjiZOCP7u6~H# zMU;=U=1&DQ9Qp|7j4qpN5Dr7sH(p^&Sqy|{uH)lIv3wk?xoVuN`ILg}HUCLs1Bp2^ za8&M?ZQVWFX>Rg4_i$C$U`89i6O(RmWQ4&O=?B6@6`a8fI)Q6q0t{&o%)|n7jN)7V z{S;u+{UzXnUJN}bCE&4u5wBxaFv7De0huAjhy#o~6NH&1X{OA4Y>v0$F-G*gZqFym zhTZ7~nfaMdN8I&2ri;fk*`LhES$vkyq-dBuRF!BC)q%;lt0`Z(*=Sl>uvU`LAvbyt zL1|M@Jas<@1hK!prK}$@&fbf70o7>3&CovCKi815v$6T7R&1GOG~R4pEu2B z%bxG{n`u$7ps(}Tt(P608J@{+>X(?=-j8CkF!T79c`1@E%?vOL%TYrMe1ozi<##IsIC1YRojP!gD%|+7|z^-Vj$a85gbmtB#unyoy%gw9m1yB z|L^-wylT%}=pNpq!QYz9zoV7>zM2g2d9lm{Q zP|dx3=De3NSNGuMWRdO_ctQJUud?_96HbrHiSKmp;{MHZhX#*L+^I11#r;grJ8_21 zt6b*wmCaAw(>A`ftjlL@vi06Z7xF<&xNOrTHrDeMHk*$$+pGK0p+|}H=Kgl{=naBy zclyQsRTraO4!uo})OTSp_x`^0jj7>|H=FOGnAbKT_LuSUiSd3QuCMq>sEhB=V63Nm zZxrtB0)U@x2A#VHqo2ab=pn~tu>kJ;TVASb_&ePAgVcic@>^YM?^LYRLr^O12>~45 z-EE?-Z$xjxsN92EaBi)~D~1OzRVH`o!)kYv7IIx??(B)>R|xa&(wmlU2gdV0+N+3% z7r$w5(L<|?@46ITJZS5koAELgVV_&KHj(9KG??A);@gL`s1th*c#t5>U(*+nb0+H% zOhJG5tth59%*>S~JIi%<0VAi;k>}&(Ojg!fyH0(fza!1kA~a}Vt{|3z{`Pt@VuYyB zFUt(kR$<`X_J&UQ%;ui2zob1!H{PL8X>>wbpGn~@&h__AfBit)4`D^#->1+Qn^MH9 zYD?%)Pa)D-xQzVGm!g)N$^_z`9)(>)gyQ+(7N@k4GO?~43wcE-|77;CPwPXHQcfcJ^I&IOOah zzL|dhoR*#m5sw{b&L=@<-30s9F|{@V05;4Wf6Z_1gpZnJ*SVN}3O7)-=yYuj2)O0d zX=I9TzzTK%QG&ujvS!F*aJ8eqt4|#VE;``yKqCx7#8QC7AmVn+zW9km3L5TN=R>{5 zLcW`6NKkTz`c{`-w!X9zMG;JZP|skLGs7qBHaWj7Ew!VR=`>n30NX)7j~-RbDmQ6b zHr)zVcn^~e2xqFCBG4P$ZCcRDml-&1^5fqN=CHgBVu1yTg32_N>tZ;N%h*TwOf^1lE#w1$yF$kXaP|V$2XuZ+3wH4Ws6%U;^iP|c6`#etHogQ+E@+~PZ1zdGAty6qTmBM z>!)Wfgq~%lD)m>avXMm)ReN}s9!T_>ic6xA|m7$(&n(Z&j} zHC=}~I(^-*PS2pc7%>)6w}F1il&p*0jX1z)jSvG%S{I3d9w$A|5;TS)4w81yzq5f8 zZVfF~`74m1KXQg|`OS>;FCgZw!AL;2PV{&8%~rG!;`eD=g!luE0k40GjIgjD!JSDNf$eW zZtPMF)&EH_#?IwVLEx&Tosh9K8Ln4Pb$`j2=><6MAezsQvhP#YNnw&cL>12xf)dPz z1tk;{SH6HDcbV0x(+5=2n;A->&iYDa5Zr9$&j?2iAz-(l1;#Vc3-ULyqRV9d0*psG7QHE! z*J=*^sKK?iTO$g*+j~C?QzzIu`6Z{2N-ANrd5*?o%x& z&WMin)$Wq%G!?{EH(2}A?Wx@ zn8|q7xPad4Gu>l^&SBl|mhUxp;S+Cb125`h5aBz9pM34$7n-GHGx*=yqAphZKkds7 z$=5Jnt*6&8@y80jNXm|>2IR<$D5frk;c2f5zLS5xe*^W>kkZa5R1+Am34;mo{Gr=Z zD=z8fgTHwx%)7hzjOo9*Cogbru8GgDzrE;3y%TR+u`|zz%c0Tyd8;#EQXdr4Rgx(2LPRzVI2FwsbXwnF;DP^fg zdYOd|zU&AqgCJ;R+?oSgEgZM`ZX>7&$A-j2m|Tcz4ictXoQkz6Tr<2zhOudU16k<7 zLdk&FCL>=a^>0gV@m#9SnMd)R$5&1mh8p2McnUbk;1|C;`7pPkYjf|o>|a6`x`z1O zt>8~Q%zHX%C=D2!;_1eo3qfbB4QQK^{ON_f*7XhLk{6sr2(KIVmax}fUtF-zHZiUd zHPb9jidV`dE;lsw?1uQH!b%MvPE|lh9-8R_z4^PC8{XAf?S73(n*FvYPoMES+LfOx zcjm4ZZOmKY>M2e${QBVT+XnBQ(oC0fAYcXi7+=}_!hS9m>Y%G@zxn3z#Pb;bJ~-kI zAHNmWgQJp$e8L-uKQ|c4B;#0BTsfRB+}pl7xe=2_1U7pahx5S$TVbRnU0oi1?Wh|A zR7ebg9TK1GgKa4@ic#q_*<;c8?CkjX zMMyq`J()_&(j-FZY7q%z6CN^a0%V{UL)jmrvEg{doZd?qIjgJ^UPr(QUs`68;qkdI zzj_XBQ|#K2U!5?fmIEtXX6^rFY;h4=Vx<-C(d;W6Bi_Xsg{ZJPL*K;I?5U$=V-BNP zn9pKiMc=hZNe**GZBw1kVs#-8c2ZRjol}}^V@^}BqY7c0=!mA;v0`d|(d;R-iT|GK z>zt>Tt3oV09%Y;^RM6=p9C-ys_a``HB_D-pnyX(CeA(GiJqx7xxFE52Y`j~iMv;sP z%jPmx#8p%5`flAU(b!c9XBvV+fygn`BP-C#lyRa;9%>YyW6~A_g?@2J+oY0HAg{qO znT4%ViCgw&eE=W8yt-0{cw`tMieWOG3wyNX#3a^qPhE8TH1?QhwhR~}Ic zZ^q$TF8$p0b0=L8aw&qaTjuAYPmr-6x;U*k*vRnOaBwb_( z5+ls5b(E!(71*l)M&(7ZEgBCtB{6Kh#ArV4u0iNnK!ml!nK5=3;9e76yD9oU4xTAK zPGsGkjtFMMY3pRP5u07;#af?b0C7u) zD^=9X@DRasHaf#c>4rF5GAT!Ggj0!7!z?Q-1_X6ZP2g|+?nVutp|rp}eFlKc8}Q&_ z17$NpDQvQolMWZfj0W0|WKm`nd_KXYH_#wRRzs1aRBYqo#feM}a?joONn30Z4Z9PG zg1c!_<52-9D53Wq4z8pUzGkEFm1@Ws(kp4}CO7csZ-7+b)^)M)(xo}_IpTLl7}5BmbBCI{4>rw>4c_gBQHtRd5Z=SW&6Qp2qMOjr3W+ZRmP;S(U+h=^BHKohhRp6Zgf zwt&$zQXhMm@kh1@SB%dIE*kFDZym3Mky$NRljX?}&JGK`PIV1C;Pf!JV{hb4y;Ju- zlpfEPUd+mV5XQH<#BRFhZ}>b#IdF?a?x;rBg-v)@fZpA?+J{3WZjbl3E zv(a&1=pGYPxP@K!6Qg5Vx=-jwc=BA{xL3+QWb&9~DGS1EFkIC+>55{dvY4LV@s5$C zKJmCjigp7?m27*GN_GROz}y+y5%iIj=*JTYccaFjvD&VN%ewfSp=0P zspdFfDqj?gs!N64cEy5uR~wD>af!1PE*xo{^a^8BPIL2=U>B!m2AM0Jf<8qWLoHxi zxQfkbbwkRXgJgLW_j{ZkCxHLBU{@D6T5u90UNs5P769Zei|C$@nA5$L$4ZvxQl1i? z8vLHg17}e{zM$=&h%8Swbfz7yw~X^N|7Chp1bC(oV72l#R8&%Ne5>F=7wR(dB; zkDX!%&fxS19JBjP<6H7+!dO`nPLvB~xn{aDh#^iHKP|A5UQlCG%v%x9@q1w2fa#&% za^UwHu!~(qrv99G%9_e4OBbJ-CkB*1M_?t6UXZ#}4JFDzB|x(1Z}ckuiY}${zj`eVo})!rN8Je z%h2CVJG1$K$2deXx^h8trLs~Han^e>_-M6@0o4C7d548|#mKtm@DvdVAX5ZzA8=*! zKq5C+cM9u)qJ%YBJ1UAcG}6Ji4=$piaZ(K@>1BiD;$R9bR*QP`dH2T=)dgW#f7U)S zZ~i#VYLOnUZt^~Iu3x8QPJaHVUxtRyipQ+tbmWKl14iW1!f6JSDvT$xt8>~7-1ZlJ zU|)Ab*lhvz-JO!$a}RBH9u8$=R)*qeD@iS@(px~OVvML-qqO5&Ujnhw1>G~**Ld{W zE+7h|!{rDZ#;ipZx4^Tcr9vnO)0>WFPzpFu*MYST(`GFzCq*@Gqse6VwDH#x?-{rs z+=dqd$W0*AuAEhzM@GC&!oZa1*lRsx>>mP>DNYigdm^A~xzo}=uV$w#iadO+!&q_~ zT>AsHXOEGsNyfcJt2V$rhGxaIcTEvZr7CMVEu=>l30N~52^71U^<_uw6h@v@`BA2! z)ViU+wF#^$=5o44TpOj?#eyq*+A&c0ghrt8%}SiK)FgLk-;-^+ zXt|1}1vcKAAuR|?L*a8;04p%!M~U2~UC-OJK)DMtBQ#+ZttJgDFNA4zchA*T)cN(E zmpIMLU*c*NrCSV^qdLXD751DsO`#V#K1BVX4qI-B3Rg(zcvlg^mgY^V3Q*5RRQ4-8 z_kAlUisma2SNEx47euK5Y#eu_-gwRW0}M90hEI}eIJ9aU?t11^jSCn4>e~XLSF7Y3 z7JF)1ZbS_P<$<#y(*u@w!jF4FW_f~bxzi%cgP~B1K5N6GFYSAf=D_s5XomU0G9I%Y zPWc{&MItPR#^Le)?zsRkQMmHx^Cnn&;TrPzRVG`wyNH*U;|r3^2NY(z0lwikP}cWF z`p%R@?dy*7H~0&3ST>L9)b7#kwg+|n0#E&-FNf+Z_t7tpa711FogBPV`S3MW_FMGQ zJ@8Z}qXR4-l%p76mvcH`{Fu(^O;8H2@#LZUH#9p6!EX$AEYV$c`s zkPimL3kv>y=WQ+?KIAuim``%cAeBhA6g8}p_*FBH(#{vKi)CIz_D)DFXPql*ccC}O zRW;+Y6V@=&*d6QJUbRxPX+-_24tc-hYHEFaP-IAj*|-P5%xbWujQvu#TF>xigr_r! znuu7b(!PyYX=O#>;+0cGRx>Sy39(3y=TCf_BZ$<%m#inup$>o(3dA1Byfsip8S975-iVe7UklFm|$4&kaJ!n66_k-7-k}Z_?){LQe&wTeJ^CR{u6p+U#4_iSZZ1wjB-1gVGNQqnkk*-wFLj(eK8Ut{waU zb1jwb2I?Wg&98jSQWom8c?2>BWt*!3WQ?>fB$KguB9_sStno%x=JXPEFrT|hh~Po2 zSPzu3IL10O?9U(3{X8OLN-!l6DJVtgr$yYXeAPh~%(FECDe;$mIY7R4Miv1GEFk9x zpw`}E5M)qTr60D^;a#OCd0xP*w8y+my1^l8Qd*V`wLoj)GFFj;;esW2PMO=sbas{yX6asXIJ$|LW< zts$A+JaxoM({kv+2d@#bhl?#V#FZn_=8tTTvup?Vq!p!46W{be)EP=VlYE|UzAU}) zz})UzJVWi;9br0k&5>}sqwa_`TP*c}^$9+q)Dks#qEVg>p)71sqKF-YLP@UF{(>lp7;CHAWK;K0TZ_+?>EtZKprfU@;52a1IU8HNx-mnoZrb8| zP8FPb#T$0VE+G-l508;d{DSfC6#dbp(j|^i^I3z9?Qmkr+(dw^w??h}WTN{_ls-GuE~lF;1Urgbtq|Ud_r>wecb@?{{z? zX>X$&Ud+(I(5}5d^>&Z2m+qy=h#vR*lS084ATwUWZLg6PX1Ft+YI`0iI)ynij}{4X zrQE!Mr1m^-?kw<|VT0mG+5J{!;j;zJT`?_=P*09n+=e``CN|7rC$u~Ksg7LSMS(Q~ z51!n1htcK0q7*K-*u0?c8ZlvPXcNwXmFe0Or2}}R@?j@{ECCNZ6va1tZ>|ZOgGZ1j z9?mRkeSK%{X4O>J$@hyFsD)7s67Uldb>O93wQQiV%-FfbEY_@q>1VUstIJs|QgB`o1z**F#s z^joAYN~5{EQ_wZ~R6-nEV#HsQbNU59dT;G zovb$}pb=LdR^{W2Nh~8yWfq*vC_DvJxM=)2N`5x+N6Sl`3{Wl@$*BYol#0^idTuM` zJ=prt$REkxn6%dimg%99{(Dt6D67sTUR6l1F@9&Z9<)XgWK#x zVohUH6>_xRuw1^V**+BCZ@dZj97T*67OBO>6UUivH`<@ray~ym^E?bO=vKqFfK3Kv z`RKxs4raHacB<(XAeH`@0G*K2@ill_U@m=icT@F{k1PU3j4VBde`ThtW8%Z~A>)45ARjQCDXbH}_rS^IxHGp#utBEj3W3KSAU+$6I4s~9OWueETo!J-f~+DV8< z+VMtdcQ?M+?S}kl&uImYiIUJ-K0-te7W4sdWpS6Fqs-I!Tj{8Qp6lMn$Zm8uU)s{X z8|O}HN%8sEl4em&qv{VBq{}$@cCG{B z5~3DY$WRYSkO~z=sxRct5^G5bPZW;LF)(zY)HREgpRrkYV@H3^BTD6u+bJE~$cqr< zw@Gb3^|n*kHZ%Vnu6~B7pB4iM0C4kDuk8Q1R^<(x%>|sCOl%CTe^N)K?Tiepg?|#m z94!og0*38u|67h%*!)SJhUdvFimsktaqp#im9IpH-$fQc79gi259qPkEZ)XU?2uWW zRg?$8`vl;V%-Tk+rwpTGaxy)h%3AmF^78<#i+Q6~M4#>J4`NNEEzy~xZ&O*9q%}@7 zs9XBO#vSKSM<-OjPIDzO9JiAYFWrK14Am{uZT=S3zaCu~K%kZo&u*=k9L#xi6vyaG zQFD76MOE&=c1G;7Zivp<%%fRq+@3wgZg>k@AYQf|*Qyzy$tqc20m?F5nGbG@V#gW` z8RMb2oBxgiqa?)_G6&-;L#(HCoaJrs_ED{IUZ^$~)+e#0iZT!AJDb2V{Sen*70TO& zyI`*~#ZdLFhYP_#DTuoqQ0OS6j0o15r{}O&YoT5wCp|x_dD{#Y;Y}0P1ta?2VEh4* ztrRN5tL6UvoH@M9L z=%FKpf@iSp2P>C(*o<-Ng4qF#A?i!AxjXLG8%Gm`$rZxw;ZqSvv5@@sZ|N*~do5fb zKWR)T_>`kxaS|MHFh`-`fc`C%=i@EFk$O&)*_OVrgP4MWsZkE2RJB(WC>w}him zb3KV>1I&nHP9};o8Kw-K$wF8`(R?UMzNB22kSIn#dEe|V-CuMw8I7|#`qSB6dpYg$ zoaDHj%zV6*;`u`VVdsTBKv&g75Q`68rdQU6O>_wkMT9d!z@)q2E)R3(j$*C4jp$Fo z2pE>*ih{4Xzh}W+5!Qw)#M*^E(0X-6-!%wj@4*^)8F=N*0Y5Or+>d= zhMNs@R~>R9;KmyP@I@bpU3&w?)jj0rGrb@q)P>wLVbz1!TZY$#+H-mK6B^0{vdvt0 zaJ0~7p%I#1PpPm1DvBzh7*UsCl^I5^`@XzPzbg+v3T_WyKN?TJ9J=57v^IUO`aQN} z@>Y>WIj+gT@-sobU-tW%L5GP(qY?Eep&I;@osY}O*3i1Ar?Sv|EI6S-pK_!~*A$K| zs-hHESqd`vv;zIzgv2ho5-hsIL5Ke~siJ(v0`Qm7W_Rms2rB67=p&HGRhA-)$p-BS zvXSmgGIGgeJMBcsgp=L8U3Ep$VPBFhvJ!3M5{pocGBS~iZj0({9Jt9nbC{Z$LVb%= zGqzRBjlqkAU{#sOX56})^QjX;jQ26M`poAFIZ#H31td9sQlgBBrfIYgDC9+kO~}s{ zb1i*{#{5tPWhv4pecAZygXG>?5xKx7iPXd?nR;QaIfhlhqNBaLDy>9Yd1Sf3P!s4~ zhfHaFGsIFy&ZM=6^qc>>V>o!zk%5Lk5BtS7oU=YfjWUN;c zrh$6Cyr%KC@QNTzTZvb)QXQkV)01MEY+EzC%CJx)Q&6MM={paB}Dp=qCn^eJ}5LeXG9Gqynt0ir>DvSIZ=i?*_xR3=% zppf1w51ypF2KL6ug zCm}eCi>&>xT;Idzh^PmtDWrU(&eC2hAt(nmd#?;W)*&4lb2Z2Ykv*XLNDEm`_1n3C z`l!wZwiF9b?mN@z?s~>v%hT01C{E3md6M5_Xi3fKD6s26Tt~Z>8|~Ao9ds!cF_Y1| zRG>!=TD0k0`|T*)oX!SlSt8g4Uh@nc(QosCoen@i*ZCSyh|IliliuhEw$8?4ZL9N2 zMQ%%S=3Tj_QilhHW@cSr1UYTtDem{A-ZxyCa$K9A%(!`X_?ieJzXbfERST|JxqmbL zHe!hSqYk|!=!$8CJ5>q}Pj63@Q#PO{gpVb+0-qHFM`j5x_s#~dxvy5u62vywq8upP z_)N)3n9cn7YEf2D8L}x0#_B_~>HT8;;8JC5q+}1gEyd%XqYvY?deQzwD1Lx{ghI3; zv?f;&6CY$H&dDL$k#)hb)5lIqUZ~oU!z)hMI!B9THhw?9!}ykqpFJ|hB?JjV9uwqb z3_70pMV^C7I<3Cg&yMi8JJ3V2gYTOMV=IopfZ#1o>&+j-mB-V${Ok(f?I3{+vR~zE_RR$?9xI~^% z53~ z&bCl+6UeKkUWJ-%mnK{9K>?(3BM3C`@xi}v8)q#;YJhMr5dWvMtAL7X``!bHv~(%m zH8d#Q4N6G~lEW}aGn9ZZNT?v9bV$emf)dg#ASDV?(nu+wpu!_X;(vL<<1zBo-~X&N z>keyizVGaP&c65DbIyEwFn2%(L`P424ZI3nFBA%w{yJ?E} zlwSKF;jIhs(!TFOdMUW|(=qHjr#U-k>`>1u1_yL5Gyy;7@WTOt_)nfIp{D9kwR8f0 z;^Fq=iF(&yd|z30&+I`FBM-P6ouHQ@96TkIe@9=pDDL#_zgXos)-ri5lX-&2D~DsI z4R>xVM$c&aFLgFjwq{1I;jpODOx|n*#@e2+Wgdkm(E(Fad_)peD`1^CJ2TpglmgoC)F(Z)F7y2rzzDU^4wvO{bzw{mzSs4tF;*qabKkC?D!j!tbF z4D_6zbqFVI>n@2-Qmg1BiDdD}>E(72)aMv1Y9duOxwlG|E!L(QmQ#j5vmN@a7v{zIt3qQSP?96^$ITE=h~sLn|N|v8YqmA~-0HWgcPHZ@!3Dzm2X{Bozc{qm>J`Ehp}`FQ%Ecbw%+|H8f`pykvo-%&0a z?&ZtJF*{#AYs8Z|z(IFI8sBiZs)L!C9#1W@;hEInZZZdPz2ZnmhoSP9VHQt7mzZUZ zhM!!5IJbe4Z@zEoMjKaxH&Px8p}1<0YmtWwcG@ZPY@*oQSteU zRy+W=Rs>sJ##v^8EJJt0=5---o<@^?fOEp=N<~xXvcf?$gXD0zVHziRMMmC#Mp3o ze(eT!dvjmXp9_C%pV_>{H=nsqYO)n1J?Ihi zjy7f00`|S<;)I!ZyUO{~#+wXX)z(BWsN|$7n9s}H%ZzE8YQv#vRTHjq@D%tYyfe=3)|7jYxRT#E16nFk&1jFC6CH5d4kiJCVq+%r_$Rec7=G!GuZ-0*$5N2GqXB(dqWPS1Um4{xgi2k=;eO_LDy&GR=Q!)bjKY{f!0yoc0Rol&!E`2BkI$5y4U^*k0=GyL-m8XJL%8prM%;fwyX9M^ zs48n3Oh#a>FVWI7dsm~*l0$^J)lxnfTTw~1ceZ73yNvNurwd`;+^1XuucaFN85M8? z$fNl!D9g*O>6IE^POaoDq`86Sw0t4%jIi`&*EEZI?wwOiEvH8(qpfyDvAe`4pWf7k z3-pFgeT{qtj)B!1ZamZ5g3z6Nd40P(%^Kf@#!uzbIk~8w`9wbhWc~1E|sw6-FsOqrhb2DLDwlaq@)Y zAi$KoA=Vyn=Yxqxtf7wu*$47Ht>WZi{AdeN79#9ws~CtE;~gC$q7T>*5yKK3VT)Q=sllRR}lBIGd17+bOu| zeUeUrMgF=Gjk-{epAyUd_KNgwZK_Pz=H$+{4~E_ZRa3IJpU~IZ5U4Z3l%u3{Ls~`H z(iysmm+!HBJTC-$EpHM9yrXUM^_FZ(3sdmsyZ6=lU8bb3V(WK>P0$l~#QA&NMj@OA z*OQ>^-s_D-bda022~!G!bTh7@FR>t!1r`Js1;4$(^_*hH-_pUPf5C}K-v$%i#KBB! zU{~a7)R>ix z#LA|<6v#rwKkB1JBLWkWu#M0#8i1J0e4dFDP3jrlFfxhkDs%Q~)e6e7fR$U?e$<{x zfZb0?UMsB|E}Fk)@|^{)_^L7O%rp1GRNig@bUX(^6}6HoGi8IXoSKpI1A(GV)uA=7 zOXG&KjZYVjYn6}2YV0yfnKsnpDlF)h$Gv--|6$BsWFg|IWnp|#sk}zOAb6Bb?vb@t zs^7=4IdiKE_rUT@rG!D4Zy zcnas#XT77V&%igMXY(lQS|)lgO{pN9!P-94KeZH_+PK5jESYCSPMN)=D(JIAVeB%D zI_>_lvD;pylkZ#Ral0IzC6ei$J$4NnGw(pnVd`&aaNT5mfq-4)aPjj(v;`VvJ6Xxjm@3DX+Kju z@9-h++s7x>idTEL zd)ptYy?P2$S*_DI;eMR0ZdAuS)~fGEZEguO&+3AwW@Sw$&KvgJr6aGK*Ar;0wx`lr z7V&!+9C7`VcV^t+Wj~AweOGQL!)0)serr$8Fez7kC(VSVRdjqpQuq964RW^2euIre zh10&Tv)|dj*CoRozrW<4y_+5}3EGRok+G7ODl3-CF1r?JYDdw&NbcVT=7ljq_K+8bMeG3uRw@3=cof?j+v+WaKI`WqwByf#7aFK3 z0+R34xQ-6nxQ&9xJKl}`C9FlUe1-h^i?5fr5kjot#MA-$%k106t>*gM+yF3m2X#=1tt07`cK)37dA^A4d8%6R>@0U-UZ~wSvzMlK$tlm~aK`%e8|quXyH`aLM0#Dcu%sqEsKV%i zVn_*W-Qbnl)h?RP>)$rZ5JL!*H;Z{ zk7(FB`lo~h&zB|S6j-Na;y$QM*rn^tkO{>#DWZN@IwJps3*Nm&ox0{{;=J~hvPb-* zvAOEPImrdq()yl~`j`Q;R1Y%CdLKKw*;gtNaM~WDO95YXsTjKCOdRD2Is@aVRTYFD zpS=_EB!@Ub&c*JmNMF=F+)Bq)52|=83IEG;M5(Ol*97!W(S-5X-5w&7->`1Pw-0Ml zpA>jaofnyPQTCzoIG}OK9j^nn>F>jC#$iSnJY8y6ue4nxs@3HtfNx01XVK7NcX#Cu z34g-z=0!7ip&@wI>>6ynJYyFTEgH6DA?b>~V%2s_@NPDza5&6cno!S(|85*74}6_M z%s1c4`B{lqMu``(4~Jk#_`^=tu36TgXPv_}{lhhyi(rrSM_uoVVNuZOuxCXom9|wg zNf&BtzX=hVi*4dG&1J!^QW;O%fQ$jVH=W74B8WR)*tM1{(@cHRqiS_W6R^h8uxd@zV>KNI zR(-LNNkLqh>e=CmL|q9sRHm#15%q$o7_GQMp8FLX-HGnJ<+(;k{Q%+Sk+!^mM+2#1y9+gG2IDZGt%;Cfk{+ zT5}^x=!i2$tnH_se6eC zkn;kK>%ICpo=X&=cSsbxQ|AjJ;5Ff;AyIj>$YA8cw*?W^Nn}S|1jrbf@Bd zr82I8KlOh4#5C0sw3oVvuC0NFPKH4S0$~F$U4JM1Im$B%%oGm_5$Lnr{#Pv}eL1k& zMP(pG$MI^8&!nYffq#$zJ^3GF|cC%2d4V@qKV#fu6u2O

k)oKu82Fu=RODzQrHPEC+Mz{hW(G7VuCl8g1ou-Ot!41bp_>OC1&@A_6e*hc)1X zMuDvzEZyB*fW1^+7dL0%ofr;-xT6B@0~|VazatI{60!X=po^uOr6UB$1POKmuI_&b zOL&O+w*!>`k+y%?Z|wm4$@_1|WC|pKM(F{k8TR$-4hs?i|GBc9)qa{vYq)~5qa(2N zsR?s}0Pp^ufVGEB8oE9VCFa0K$x0HSpem!tIyR69y0rnjg8cqjmWyz7*Kx3~X> z|BZX}Y;oVB1HX@l9_-y7dI*WgruY@?rC&64`}3W`ECA>O@Y#Q@JS<4WBF(QbwJqHM zt)fE#6jTSyZ^E8y0INaIf!omWjvS=@15`O%V2CKg+}z=M9##kLKRN0uJuK250bXVU zwzT&n@30^dzKnlL^us;wClg?CKWEtiEb#zhPVx{PxFQiwEPp^C53zN21EdZAz?3D& zC6fK|_!S5Mq&0z;xWGLEv}!zjfpRg_orp7|fXMx=uP!@X`yT@5(N_Hza}p5fBk&|)J7fZ`NQ9Nz@5xT? zi?iV$q+bG!2LZUpF)>Yl!u;DEHV3!i{ipcJm_8Gj@Dac%N3|SQVGqRhrJ;WOR|CtrwzPTW^&$A6!A$E)h7xohm>hA8p{PUZ~ z_&zeg@OL3PxPtzkfsNZAqXCZ8Is7yQ+plm~8;}|~DEkv&f@?q5hB*OGQYXuwVQOp0 z?QQ`6qyp|-$47wjuV74IE_x2I17$+grwMBE^25d<5!lYhnszuh|5Yk;RB+Uk*hk=m zu73=E^7ul{40{A^?Rg^fq0ZfZO@C1HupR*_d;J>lkFv6&x&}4N;t}1T@2}~AC^<3b zA}RxFPPZe5R{_6dIN9N-GT29Oa}RzA2ekKuEVZbuMOB?Xf**`N5&m}?)TjigdY(rF z?~+a=`0);TlDa1j)1G`AfW? zRl883QPq=w zbB|bHEx%_u*$t@Yl#Vc;y*?2W^|^NJ)DmioQFr~1&>MSBL_b(YIpGWdDm3bT=Mgm1 e+h0K+-~H6qzyuy}`;+tYAZFmzUSVSYum1yJqxCBQ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37aef8d3..1af9e093 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index aeb74cbb..1aa94a42 100755 --- a/gradlew +++ b/gradlew @@ -83,7 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -130,10 +131,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -141,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -149,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -198,11 +202,11 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ 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 d6375c10..c532443a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/config/CserealExceptionHandler.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/config/CserealExceptionHandler.kt @@ -1,6 +1,7 @@ package com.wafflestudio.csereal.common.config import com.wafflestudio.csereal.common.CserealException +import org.slf4j.LoggerFactory import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.validation.BindingResult @@ -12,6 +13,7 @@ import java.sql.SQLIntegrityConstraintViolationException @RestControllerAdvice class CserealExceptionHandler { + private val log = LoggerFactory.getLogger(this::class.java) // @Valid로 인해 오류 떴을 때 메시지 전송 @ExceptionHandler(value = [MethodArgumentNotValidException::class]) @@ -29,12 +31,14 @@ class CserealExceptionHandler { // db에서 중복된 값 있을 때 @ExceptionHandler(value = [SQLIntegrityConstraintViolationException::class]) fun handle(e: SQLIntegrityConstraintViolationException): ResponseEntity { + log.error(e.stackTraceToString()) return ResponseEntity("중복된 값이 있습니다.", HttpStatus.CONFLICT) } // oidc provider 서버에 문제가 있을때 @ExceptionHandler(value = [RestClientException::class]) fun handle(e: RestClientException): ResponseEntity { + log.error(e.stackTraceToString()) return ResponseEntity("idsnucse error: ${e.message}", HttpStatus.BAD_GATEWAY) } } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 323f7d67..df47478b 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -68,11 +68,12 @@ spring: issuer-uri: https://id.snucse.org/o jwk-set-uri: https://id.snucse.org/o/jwks -logging.level: - default: INFO - org: - springframework: - security: DEBUG +logging: + level: + # default: INFO + org: + springframework: + security: DEBUG --- spring: @@ -112,6 +113,10 @@ endpoint: login-page: https://${URL} +slack: + token: ${SLACK_TOKEN} + channel: ${SLACK_CHANNEL} + --- spring: config.activate.on-profile: test @@ -134,8 +139,10 @@ spring: console: enabled: true -logging.level: - default: INFO - org: - springframework: - security: DEBUG +logging: + level: + # default: INFO + org: + springframework: + security: DEBUG + diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 00000000..867810e6 --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + ${SLACK_TOKEN} + ${SLACK_CHANNEL} + + + ${SLACK_LOG_PATTERN} + + + + + 5000 + + + ERROR + + + + + + + + + + + + + + + + + From 9437614907fc719bea9ce146f7e468ef79b5b9ed Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Tue, 2 Apr 2024 10:09:27 +0900 Subject: [PATCH 179/214] =?UTF-8?q?feat:=20=ED=95=99=EB=B6=80=20=EC=B4=9D?= =?UTF-8?q?=EA=B4=84=20=ED=96=89=EC=A0=95=EC=A7=81=EC=9B=90=20=EB=A7=A8=20?= =?UTF-8?q?=EC=95=9E=EC=9C=BC=EB=A1=9C=20=EC=9D=B4=EB=8F=99=20(#232)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../csereal/core/member/service/StaffService.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) 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 32d5f9a4..09fabba5 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 @@ -65,10 +65,17 @@ class StaffServiceImpl( override fun getAllStaff(language: String): List { val enumLanguageType = LanguageType.makeStringToLanguageType(language) - return staffRepository.findAllByLanguage(enumLanguageType).map { + val sortedStaff = staffRepository.findAllByLanguage(enumLanguageType).map { val imageURL = mainImageService.createImageURL(it.mainImage) SimpleStaffDto.of(it, imageURL) - }.sortedBy { it.name } + }.sortedBy { it.name }.toMutableList() + + sortedStaff.indexOfFirst { it.email == "misuk@snu.ac.kr" }.takeIf { it != -1 }?.let { index -> + val headStaff = sortedStaff.removeAt(index) + sortedStaff.add(0, headStaff) + } + + return sortedStaff } override fun updateStaff(staffId: Long, updateStaffRequest: StaffDto, mainImage: MultipartFile?): StaffDto { From ff37dcee5e85a7608a2a1907bff8f79ec2637a6d Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Wed, 3 Apr 2024 00:25:14 +0900 Subject: [PATCH 180/214] =?UTF-8?q?feat:=20=ED=95=9C=EA=B8=80=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EC=9A=B0=EC=84=A0=EC=88=9C=EC=9C=84=20=EC=A0=95?= =?UTF-8?q?=EB=A0=AC=20(#235)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 한글 이름 우선순위 정렬 * refactor: 유틸 함수 분리 --- .../wafflestudio/csereal/common/utils/Utils.kt | 4 ++++ .../core/member/service/ProfessorService.kt | 18 ++++++++++++++++-- 2 files changed, 20 insertions(+), 2 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 da3564b8..d6daee44 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/utils/Utils.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/utils/Utils.kt @@ -56,3 +56,7 @@ fun getUsername(authentication: Authentication?): String? { } } } + +fun startsWithEnglish(name: String): Boolean { + return name.isNotEmpty() && name.first().let { it in 'A'..'Z' || it in 'a'..'z' } +} 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 d064b7f8..d42cd569 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 @@ -2,6 +2,7 @@ package com.wafflestudio.csereal.core.member.service import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.utils.startsWithEnglish import com.wafflestudio.csereal.core.member.database.* import com.wafflestudio.csereal.core.member.dto.ProfessorDto import com.wafflestudio.csereal.core.member.dto.ProfessorPageDto @@ -27,6 +28,7 @@ interface ProfessorService { updateProfessorRequest: ProfessorDto, mainImage: MultipartFile? ): ProfessorDto + fun deleteProfessor(professorId: Long) fun migrateProfessors(requestList: List): List fun migrateProfessorImage(professorId: Long, mainImage: MultipartFile): ProfessorDto @@ -110,7 +112,13 @@ class ProfessorServiceImpl( val imageURL = mainImageService.createImageURL(it.mainImage) SimpleProfessorDto.of(it, imageURL) } - .sortedBy { it.name } + .sortedWith { a, b -> + when { + startsWithEnglish(a.name) && !startsWithEnglish(b.name) -> 1 + !startsWithEnglish(a.name) && !startsWithEnglish(b.name) -> -1 + else -> a.name.compareTo(b.name) + } + } return ProfessorPageDto(description, professors) } @@ -124,7 +132,13 @@ class ProfessorServiceImpl( val imageURL = mainImageService.createImageURL(it.mainImage) SimpleProfessorDto.of(it, imageURL) } - .sortedBy { it.name } + .sortedWith { a, b -> + when { + startsWithEnglish(a.name) && !startsWithEnglish(b.name) -> 1 + !startsWithEnglish(a.name) && !startsWithEnglish(b.name) -> -1 + else -> a.name.compareTo(b.name) + } + } } override fun updateProfessor( From 7838f14cecf98d20e0e2341d9fbad4327796e0e2 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, 3 Apr 2024 12:44:41 +0900 Subject: [PATCH 181/214] =?UTF-8?q?Feat:=20=EA=B2=8C=EC=8B=9C=EB=AC=BC=20?= =?UTF-8?q?=EC=A0=9C=EC=99=B8=20=ED=86=B5=ED=95=A9=EA=B2=80=EC=83=89?= =?UTF-8?q?=EC=97=90=20=EC=82=AC=EC=9A=A9=EB=90=98=EB=8A=94=20field?= =?UTF-8?q?=EB=93=A4=20refersh=ED=95=98=EB=8A=94=20API=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#236)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat: Add refresh search event * Feat: Add api for trigger refresh search indexes. * Feat: Add refresh search event listener for about entity. * Feat: Add refresh search event listener for academics entity. * Feat: Add refresh search event listener for admission entity. * Feat: Add refresh search event listener for member entity. * Feat: Add refresh search event listener for research entity. * Fix: Change transactional propagation as REQUIRES_NEW for about's refresh event listener. * style: ktlint --------- Co-authored-by: Junhyeong Kim Co-authored-by: leeeryboy --- .../core/about/service/AboutService.kt | 11 ++++ .../service/AcademicsSearchService.kt | 54 ++++++++++++++++++- .../admissions/service/AdmissionsService.kt | 24 +++++++++ .../csereal/core/main/api/MainController.kt | 5 ++ .../core/main/event/RefreshSearchEvent.kt | 3 ++ .../csereal/core/main/service/MainService.kt | 10 +++- .../member/service/MemberSearchService.kt | 26 ++++++++- .../core/member/service/ProfessorService.kt | 4 ++ .../research/service/ResearchSearchService.kt | 30 ++++++++++- 9 files changed, 162 insertions(+), 5 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/main/event/RefreshSearchEvent.kt 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 884e1554..faf6fdec 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,10 +6,13 @@ import com.wafflestudio.csereal.core.about.api.res.AboutSearchElementDto import com.wafflestudio.csereal.core.about.api.res.AboutSearchResBody import com.wafflestudio.csereal.core.about.database.* import com.wafflestudio.csereal.core.about.dto.* +import com.wafflestudio.csereal.core.main.event.RefreshSearchEvent import com.wafflestudio.csereal.core.resource.attachment.service.AttachmentService import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService +import org.springframework.context.event.EventListener import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Propagation import org.springframework.transaction.annotation.Transactional import org.springframework.web.multipart.MultipartFile @@ -185,6 +188,14 @@ class AboutServiceImpl( return FutureCareersPage(description, statList, companyList) } + @Transactional(propagation = Propagation.REQUIRES_NEW) + @EventListener + fun refreshSearchListener(event: RefreshSearchEvent) { + aboutRepository.findAll().forEach { + syncSearchOfAbout(it) + } + } + @Transactional fun syncSearchOfAbout(about: AboutEntity) { if (about.postType == AboutPostType.FUTURE_CAREERS) { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsSearchService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsSearchService.kt index 63a0b3dc..3a126a08 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsSearchService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsSearchService.kt @@ -1,9 +1,12 @@ package com.wafflestudio.csereal.core.academics.service import com.wafflestudio.csereal.common.properties.LanguageType -import com.wafflestudio.csereal.core.academics.database.AcademicsSearchRepository import com.wafflestudio.csereal.core.academics.api.res.AcademicsSearchResBody +import com.wafflestudio.csereal.core.academics.database.* +import com.wafflestudio.csereal.core.main.event.RefreshSearchEvent +import org.springframework.context.event.EventListener import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Propagation import org.springframework.transaction.annotation.Transactional interface AcademicsSearchService { @@ -20,11 +23,18 @@ interface AcademicsSearchService { pageNum: Int, amount: Int ): AcademicsSearchResBody + + fun syncCourseSearch(course: CourseEntity) + fun syncScholarshipSearch(scholarship: ScholarshipEntity) + fun syncAcademicsSearch(academics: AcademicsEntity) } @Service class AcademicsSearchServiceImpl( - private val academicsSearchRepository: AcademicsSearchRepository + private val academicsSearchRepository: AcademicsSearchRepository, + private val academicsRepository: AcademicsRepository, + private val courseRepository: CourseRepository, + private val scholarshipRepository: ScholarshipRepository ) : AcademicsSearchService { @Transactional(readOnly = true) override fun searchTopAcademics( @@ -68,4 +78,44 @@ class AcademicsSearchServiceImpl( amount = amount ) } + + @Transactional(propagation = Propagation.REQUIRES_NEW) + @EventListener + fun refreshSearchListener(event: RefreshSearchEvent) { + academicsRepository.findAll().forEach { + syncAcademicsSearch(it) + } + + courseRepository.findAll().forEach { + syncCourseSearch(it) + } + + scholarshipRepository.findAll().forEach { + syncScholarshipSearch(it) + } + } + + @Transactional + override fun syncAcademicsSearch(academics: AcademicsEntity) { + academics.academicsSearch?.update(academics) + ?: let { + academics.academicsSearch = AcademicsSearchEntity.create(academics) + } + } + + @Transactional + override fun syncScholarshipSearch(scholarship: ScholarshipEntity) { + scholarship.academicsSearch?.update(scholarship) + ?: let { + scholarship.academicsSearch = AcademicsSearchEntity.create(scholarship) + } + } + + @Transactional + override fun syncCourseSearch(course: CourseEntity) { + course.academicsSearch?.update(course) + ?: let { + course.academicsSearch = AcademicsSearchEntity.create(course) + } + } } 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 2959dab1..2b9df4ce 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 @@ -11,7 +11,10 @@ import com.wafflestudio.csereal.core.admissions.database.AdmissionsRepository import com.wafflestudio.csereal.core.admissions.dto.AdmissionsDto import com.wafflestudio.csereal.core.admissions.type.AdmissionsMainType import com.wafflestudio.csereal.core.admissions.type.AdmissionsPostType +import com.wafflestudio.csereal.core.main.event.RefreshSearchEvent +import org.springframework.context.event.EventListener import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Propagation import org.springframework.transaction.annotation.Transactional interface AdmissionsService { @@ -83,6 +86,27 @@ class AdmissionsServiceImpl( ) } + @Transactional(propagation = Propagation.REQUIRES_NEW) + @EventListener + fun refreshSearch(event: RefreshSearchEvent) { + admissionsRepository.findAll().forEach { + syncSearchAdmission(it) + } + } + + @Transactional + fun syncSearchAdmission(admissions: AdmissionsEntity) { + admissions.apply { + searchContent = AdmissionsEntity.createSearchContent( + name, + mainType, + postType, + language, + description + ) + } + } + @Transactional(readOnly = true) override fun searchPageAdmission( keyword: String, 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 fd85e79b..33a533d2 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 @@ -15,4 +15,9 @@ class MainController( fun readMain(): MainResponse { return mainService.readMain() } + + @GetMapping("/search/refresh") + fun refreshSearches() { + mainService.refreshSearch() + } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/event/RefreshSearchEvent.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/event/RefreshSearchEvent.kt new file mode 100644 index 00000000..5bfadd5a --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/event/RefreshSearchEvent.kt @@ -0,0 +1,3 @@ +package com.wafflestudio.csereal.core.main.event + +class RefreshSearchEvent 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 1bef3b7e..fecebe6e 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 @@ -3,17 +3,21 @@ 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.main.event.RefreshSearchEvent import com.wafflestudio.csereal.core.notice.database.TagInNoticeEnum +import org.springframework.context.ApplicationEventPublisher import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional interface MainService { fun readMain(): MainResponse + fun refreshSearch() } @Service class MainServiceImpl( - private val mainRepository: MainRepository + private val mainRepository: MainRepository, + private val eventPublisher: ApplicationEventPublisher ) : MainService { @Transactional(readOnly = true) override fun readMain(): MainResponse { @@ -29,4 +33,8 @@ class MainServiceImpl( return MainResponse(slides, notices, importants) } + + override fun refreshSearch() { + eventPublisher.publishEvent(RefreshSearchEvent()) + } } 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 ca338b53..d6ba232e 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,10 +1,16 @@ package com.wafflestudio.csereal.core.member.service import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.core.main.event.RefreshSearchEvent import com.wafflestudio.csereal.core.member.api.res.MemberSearchResBody +import com.wafflestudio.csereal.core.member.database.MemberSearchEntity import com.wafflestudio.csereal.core.member.database.MemberSearchRepository +import com.wafflestudio.csereal.core.member.database.ProfessorRepository +import com.wafflestudio.csereal.core.member.database.StaffRepository import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService +import org.springframework.context.event.EventListener import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Propagation import org.springframework.transaction.annotation.Transactional interface MemberSearchService { @@ -16,7 +22,9 @@ interface MemberSearchService { @Service class MemberSearchServiceImpl( private val memberSearchRepository: MemberSearchRepository, - private val mainImageService: MainImageService + private val mainImageService: MainImageService, + private val professorRepository: ProfessorRepository, + private val staffRepository: StaffRepository ) : MemberSearchService { @Transactional(readOnly = true) override fun searchTopMember(keyword: String, language: LanguageType, number: Int): MemberSearchResBody { @@ -34,4 +42,20 @@ class MemberSearchServiceImpl( val (entityResults, total) = memberSearchRepository.searchMember(keyword, language, pageSize, pageNum) return MemberSearchResBody.of(entityResults, total, mainImageService::createImageURL) } + + @Transactional(propagation = Propagation.REQUIRES_NEW) + @EventListener + fun refreshSearchListener(event: RefreshSearchEvent) { + professorRepository.findAll().forEach { pf -> + pf.memberSearch?.update(pf) ?: let { + pf.memberSearch = MemberSearchEntity.create(pf) + } + } + + staffRepository.findAll().forEach { st -> + st.memberSearch?.update(st) ?: let { + st.memberSearch = MemberSearchEntity.create(st) + } + } + } } 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 d42cd569..d62cf464 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 @@ -96,6 +96,8 @@ class ProfessorServiceImpl( @Transactional(readOnly = true) override fun getActiveProfessors(language: String): ProfessorPageDto { val enumLanguageType = LanguageType.makeStringToLanguageType(language) + + // TODO: Refactor to save in database val description = "컴퓨터공학부는 35명의 훌륭한 교수진과 최신 시설을 갖추고 400여 명의 학부생과 " + "350여 명의 대학원생에게 세계 최고 수준의 교육 연구 환경을 제공하고 있다. 2005년에는 서울대학교 " + "최초로 외국인 정교수인 Robert Ian McKay 교수를 임용한 것을 시작으로 교내에서 가장 국제화가 " + @@ -104,6 +106,7 @@ class ProfessorServiceImpl( " 학기 전공 필수 과목을 비롯한 30% 이상의 과목이 영어로 개설되고 있어 외국인 학생의 학업을 돕는 " + "동시에 한국인 학생이 세계로 진출하는 초석이 되고 있다. 또한 CSE int’l Luncheon을 개최하여 " + "학부 내 외국인 구성원의 화합과 생활의 불편함을 최소화하는 등 학부 차원에서 최선을 다하고 있다." + val professors = professorRepository.findByLanguageAndStatusNot( enumLanguageType, @@ -119,6 +122,7 @@ class ProfessorServiceImpl( else -> a.name.compareTo(b.name) } } + return ProfessorPageDto(description, professors) } 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 8b9b0bac..90f0f2b6 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 @@ -1,6 +1,8 @@ package com.wafflestudio.csereal.core.research.service import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.core.conference.database.ConferenceRepository +import com.wafflestudio.csereal.core.main.event.RefreshSearchEvent import com.wafflestudio.csereal.core.member.event.ProfessorCreatedEvent import com.wafflestudio.csereal.core.member.event.ProfessorDeletedEvent import com.wafflestudio.csereal.core.member.event.ProfessorModifiedEvent @@ -8,9 +10,11 @@ import com.wafflestudio.csereal.core.research.database.LabRepository import com.wafflestudio.csereal.core.research.database.ResearchSearchEntity import com.wafflestudio.csereal.core.research.database.ResearchSearchRepository import com.wafflestudio.csereal.core.research.api.res.ResearchSearchResBody +import com.wafflestudio.csereal.core.research.database.ResearchRepository import org.springframework.context.event.EventListener import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Propagation import org.springframework.transaction.annotation.Transactional interface ResearchSearchService { @@ -31,7 +35,9 @@ interface ResearchSearchService { @Service class ResearchSearchServiceImpl( private val labRepository: LabRepository, - private val researchSearchRepository: ResearchSearchRepository + private val researchSearchRepository: ResearchSearchRepository, + private val researchRepository: ResearchRepository, + private val conferenceRepository: ConferenceRepository ) : ResearchSearchService { @Transactional(readOnly = true) override fun searchTopResearch( @@ -118,6 +124,28 @@ class ResearchSearchServiceImpl( } } + @EventListener + @Transactional(propagation = Propagation.REQUIRES_NEW) + fun refreshSearchListener(event: RefreshSearchEvent) { + labRepository.findAll().forEach { + it.researchSearch?.update(it) ?: let { _ -> + it.researchSearch = ResearchSearchEntity.create(it) + } + } + + researchRepository.findAll().forEach { + it.researchSearch?.update(it) ?: let { _ -> + it.researchSearch = ResearchSearchEntity.create(it) + } + } + + conferenceRepository.findAll().forEach { + it.researchSearch?.update(it) ?: let { _ -> + it.researchSearch = ResearchSearchEntity.create(it) + } + } + } + @Transactional override fun deleteResearchSearch( researchSearchEntity: ResearchSearchEntity From c80b2dfc789ae9df832e5e2b236b9f3031fc49b9 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Wed, 3 Apr 2024 13:33:54 +0900 Subject: [PATCH 182/214] delete image thumbnail logic (#237) --- build.gradle.kts | 3 --- .../resource/mainImage/service/MainImageService.kt | 14 -------------- 2 files changed, 17 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 758f74cf..e62c1599 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -63,9 +63,6 @@ dependencies { // 이미지 업로드 implementation("commons-io:commons-io:2.11.0") - // 썸네일 보여주기 - implementation("net.coobird:thumbnailator:0.4.19") - // Custom Metadata annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") } 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 c62058e4..614ced84 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 @@ -12,7 +12,6 @@ import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageReposi 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 @@ -21,8 +20,6 @@ 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 MainImageService { fun uploadMainImage( @@ -61,25 +58,14 @@ class MainImageServiceImpl( val saveFile = Paths.get(totalFilename) requestImage.transferTo(saveFile) - val totalThumbnailFilename = "${path}thumbnail_$filename" - val thumbnailFile = Paths.get(totalThumbnailFilename) - Thumbnailator.createThumbnail(saveFile.toFile(), thumbnailFile.toFile(), 100, 100) - val mainImage = MainImageEntity( filename = filename, imagesOrder = 1, size = requestImage.size ) - val thumbnail = MainImageEntity( - filename = thumbnailFile.name, - imagesOrder = 1, - size = thumbnailFile.fileSize() - ) - connectMainImageToEntity(contentEntityType, mainImage) mainImageRepository.save(mainImage) - mainImageRepository.save(thumbnail) return MainImageDto( filename = filename, From 6efa275a4c114ffd3dd2c3a14f48976b24b8f278 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, 3 Apr 2024 21:23:23 +0900 Subject: [PATCH 183/214] Fix: Fill "mappedBy" information for remove about_attachments table. (#239) --- .../com/wafflestudio/csereal/core/about/database/AboutEntity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 4a334658..3f72d722 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 @@ -28,7 +28,7 @@ class AboutEntity( @Convert(converter = StringListConverter::class) var locations: MutableList = mutableListOf(), - @OneToMany(mappedBy = "") + @OneToMany(mappedBy = "about", cascade = [CascadeType.ALL], orphanRemoval = true) var attachments: MutableList = mutableListOf(), @OneToOne From b4f9024e692ef359344801c86d2ed0242a9634f7 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, 4 Apr 2024 00:00:31 +0900 Subject: [PATCH 184/214] =?UTF-8?q?CICD:=20=EB=B0=B0=ED=8F=AC,=20=EA=B0=9C?= =?UTF-8?q?=EB=B0=9C=EA=B3=84=20=EB=B6=84=EB=A6=AC=20=EC=A0=81=EC=9A=A9=20?= =?UTF-8?q?(#238)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * CICD: Add dev deployment workflow * Refactor: Remove unused comments * Config: Add dev profile * CICD: Add ssh port for production deploy environment * Config: Change slack message format to more readable --- .github/workflows/database.yaml | 2 + .github/workflows/database_dev.yaml | 52 ++++++++++++++++ .github/workflows/deploy.yaml | 4 +- .github/workflows/deploy_dev.yaml | 86 +++++++++++++++++++++++++++ .github/workflows/proxy.yaml | 2 + .github/workflows/proxy_dev.yaml | 47 +++++++++++++++ caddy/Caddyfile | 12 ++-- docker-compose-backend.yml | 22 ------- src/main/resources/application.yaml | 48 ++++++++++++--- src/main/resources/logback-spring.xml | 9 ++- 10 files changed, 247 insertions(+), 37 deletions(-) create mode 100644 .github/workflows/database_dev.yaml create mode 100644 .github/workflows/deploy_dev.yaml create mode 100644 .github/workflows/proxy_dev.yaml diff --git a/.github/workflows/database.yaml b/.github/workflows/database.yaml index 86818a91..9078ded5 100644 --- a/.github/workflows/database.yaml +++ b/.github/workflows/database.yaml @@ -33,6 +33,7 @@ jobs: with: host: ${{secrets.SSH_HOST}} username: ${{secrets.SSH_USER}} + port: ${{secrets.SSH_PORT}} key: ${{secrets.SSH_KEY}} source: "docker-compose-db.yml, .env" target: "~/app" @@ -44,6 +45,7 @@ jobs: with: host: ${{secrets.SSH_HOST}} username: ${{secrets.SSH_USER}} + port: ${{secrets.SSH_PORT}} key: ${{secrets.SSH_KEY}} script: | cd ~/app diff --git a/.github/workflows/database_dev.yaml b/.github/workflows/database_dev.yaml new file mode 100644 index 00000000..37873eeb --- /dev/null +++ b/.github/workflows/database_dev.yaml @@ -0,0 +1,52 @@ +on: + push: + branches: + - develop + paths: + - docker-compose-db.yml + - db_config/* + - .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_DEV}} + 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_DEV}} + 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 0c120312..8cc27deb 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -68,6 +68,7 @@ jobs: with: host: ${{secrets.SSH_HOST}} username: ${{secrets.SSH_USER}} + port: ${{secrets.SSH_PORT}} key: ${{secrets.SSH_KEY}} source: "docker-compose-backend.yml, .env" target: "~/app" @@ -77,8 +78,9 @@ jobs: with: host: ${{secrets.SSH_HOST}} username: ${{secrets.SSH_USER}} + port: ${{secrets.SSH_PORT}} key: ${{secrets.SSH_KEY}} - script: | # TODO: Change to blue-green deployment + script: | cd ~/app source .env docker-compose -f docker-compose-backend.yml down diff --git a/.github/workflows/deploy_dev.yaml b/.github/workflows/deploy_dev.yaml new file mode 100644 index 00000000..49bfb53b --- /dev/null +++ b/.github/workflows/deploy_dev.yaml @@ -0,0 +1,86 @@ +on: + push: + branches: + - develop + 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: + 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: '21' + distribution: 'temurin' + + - 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 + build-args: | + PROFILE=prod + 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=dev" >> .env + echo "OIDC_CLIENT_SECRET=${{secrets.OIDC_CLIENT_SECRET}}" >> .env + echo "URL=${{secrets.URL_DEV}}" >> .env + # echo "SLACK_TOKEN=${{secrets.SLACK_TOKEN}}" >> .env + # echo "SLACK_CHANNEL=${{secrets.SLACK_CHANNEL}}" >> .env + + - name: SCP Command to Transfer Files + uses: appleboy/scp-action@v0.1.4 + with: + host: ${{secrets.SSH_HOST_DEV}} + username: ${{secrets.SSH_USER}} + key: ${{secrets.SSH_KEY}} + source: "docker-compose-backend.yml, .env" + target: "~/app" + overwrite: true + - name: SSH Remote Commands + uses: appleboy/ssh-action@v1.0.0 + with: + host: ${{secrets.SSH_HOST_DEV}} + username: ${{secrets.SSH_USER}} + key: ${{secrets.SSH_KEY}} + script: | + cd ~/app + source .env + docker-compose -f docker-compose-backend.yml down + docker-compose -f docker-compose-backend.yml pull + docker-compose -f docker-compose-backend.yml up -d diff --git a/.github/workflows/proxy.yaml b/.github/workflows/proxy.yaml index d158fa86..62fa771c 100644 --- a/.github/workflows/proxy.yaml +++ b/.github/workflows/proxy.yaml @@ -28,6 +28,7 @@ jobs: with: host: ${{secrets.SSH_HOST}} username: ${{secrets.SSH_USER}} + port: ${{secrets.SSH_PORT}} key: ${{secrets.SSH_KEY}} source: "docker-compose-caddy.yml, .env, caddy/Caddyfile" target: "~/proxy" @@ -39,6 +40,7 @@ jobs: with: host: ${{secrets.SSH_HOST}} username: ${{secrets.SSH_USER}} + port: ${{secrets.SSH_PORT}} key: ${{secrets.SSH_KEY}} script: | cd ~/proxy diff --git a/.github/workflows/proxy_dev.yaml b/.github/workflows/proxy_dev.yaml new file mode 100644 index 00000000..f1e8d553 --- /dev/null +++ b/.github/workflows/proxy_dev.yaml @@ -0,0 +1,47 @@ +on: + push: + branches: + - develop + 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_DEV}}" > .env + echo "LOCAL_IP=${{secrets.LOCAL_IP_DEV}}" >> .env + + - + name: SCP Command to Transfer Files + uses: appleboy/scp-action@v0.1.4 + with: + host: ${{secrets.SSH_HOST_DEV}} + 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_DEV}} + 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 diff --git a/caddy/Caddyfile b/caddy/Caddyfile index 0eeefa95..11f81c5a 100644 --- a/caddy/Caddyfile +++ b/caddy/Caddyfile @@ -9,15 +9,15 @@ abort @backend_denied # Backend - reverse_proxy /api/* host.docker.internal:8080 #host.docker.internal:8081 # For blue/green + reverse_proxy /api/* host.docker.internal:8080 # Old file serving - reverse_proxy /sites/default/files/* host.docker.internal:8080 #host.docker.internal:8081 # For blue/green + reverse_proxy /sites/default/files/* host.docker.internal:8080 # Login - reverse_proxy /oauth2/authorization/idsnucse host.docker.internal:8080 #host.docker.internal:8081 # For blue/green + reverse_proxy /oauth2/authorization/idsnucse host.docker.internal:8080 # 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 + reverse_proxy /swagger-ui/* host.docker.internal:8080 + reverse_proxy /api-docs/* host.docker.internal:8080 +} diff --git a/docker-compose-backend.yml b/docker-compose-backend.yml index 82e3578a..ffba533b 100644 --- a/docker-compose-backend.yml +++ b/docker-compose-backend.yml @@ -23,25 +23,3 @@ services: - 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 -# volumes: -# - ./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} -# SPRING_DATASOURCE_PASSWORD: ${MYSQL_PASSWORD} -# OIDC_CLIENT_SECRET_DEV: ${OIDC_CLIENT_SECRET_DEV} -# URL: ${URL} -# extra_hosts: -# - host.docker.internal:host-gateway -# restart: always -# image: ghcr.io/wafflestudio/csereal-server/server_image:latest diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index df47478b..04f23b8d 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -6,6 +6,10 @@ spring: active: local datasource: driver-class-name: com.mysql.cj.jdbc.Driver + jpa: + properties: + hibernate: + dialect: com.wafflestudio.csereal.common.config.MySQLDialectCustom servlet: multipart: enabled: true @@ -50,9 +54,6 @@ spring: ddl-auto: update show-sql: true open-in-view: false - properties: - hibernate: - dialect: com.wafflestudio.csereal.common.config.MySQLDialectCustom security: oauth2: client: @@ -80,11 +81,8 @@ spring: config.activate.on-profile: prod jpa: hibernate: - ddl-auto: update # TODO: change to validate (or none) when save actual data to server + ddl-auto: validate open-in-view: false - properties: - hibernate: - dialect: com.wafflestudio.csereal.common.config.MySQLDialectCustom security: oauth2: client: @@ -117,6 +115,42 @@ slack: token: ${SLACK_TOKEN} channel: ${SLACK_CHANNEL} +--- +spring: + config.activate.on-profile: dev + jpa: + hibernate: + ddl-auto: update + show-sql: true + open-in-view: false + security: + oauth2: + client: + registration: + idsnucse: + client-id: cse-waffle-dev + client-secret: ${OIDC_CLIENT_SECRET} + authorization-grant-type: authorization_code + scope: openid, profile, email + redirect-uri: https://${URL}/api/v1/login/oauth2/code/idsnucse + provider: + idsnucse: + issuer-uri: https://id.snucse.org/o + jwk-set-uri: https://id.snucse.org/o/jwks + +csereal: + upload: + path: /app/files/ + +oldFiles: + path: /app/cse-files/ + +endpoint: + backend: https://${URL}/api + frontend: https://${URL} + +login-page: https://${URL} + --- spring: config.activate.on-profile: test diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml index 867810e6..d7d3352b 100644 --- a/src/main/resources/logback-spring.xml +++ b/src/main/resources/logback-spring.xml @@ -13,7 +13,7 @@ - + @@ -42,10 +42,17 @@ + + + + + + + From 3f8b2820526f15e2bad5a7e3a883849cbb62868e Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Fri, 5 Apr 2024 16:43:47 +0900 Subject: [PATCH 185/214] =?UTF-8?q?fix:=20course=20=EB=9D=84=EC=96=B4?= =?UTF-8?q?=EC=93=B0=EA=B8=B0=20=EA=B7=B8=EB=8C=80=EB=A1=9C=20=EC=9C=A0?= =?UTF-8?q?=EC=A7=80=20(#241)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../csereal/core/academics/database/CourseEntity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1200c790..da7134a5 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 @@ -40,7 +40,7 @@ class CourseEntity( language = languageType, classification = courseDto.classification, code = courseDto.code, - name = courseDto.name.replace(" ", "-"), + name = courseDto.name, credit = courseDto.credit, grade = courseDto.grade, description = courseDto.description From d5253c0e228b78ddf29f266fed8da9d56d242439 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Sat, 6 Apr 2024 13:51:38 +0900 Subject: [PATCH 186/214] =?UTF-8?q?prod=EC=97=90=EC=84=9C=20mock-login=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20(#242)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: mock login api only for profile dev * feat: delete mock login api in prod --- .../wafflestudio/csereal/common/mockauth/DevAuthController.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/mockauth/DevAuthController.kt b/src/main/kotlin/com/wafflestudio/csereal/common/mockauth/DevAuthController.kt index 27319abc..a278580d 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/mockauth/DevAuthController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/mockauth/DevAuthController.kt @@ -5,6 +5,7 @@ import com.wafflestudio.csereal.core.user.database.UserEntity import com.wafflestudio.csereal.core.user.database.UserRepository import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse +import org.springframework.context.annotation.Profile import org.springframework.http.ResponseEntity import org.springframework.security.authentication.AuthenticationManager import org.springframework.security.authentication.UsernamePasswordAuthenticationToken @@ -15,7 +16,7 @@ import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -//TODO: 정식 릴리즈 후에는 dev 서버에서만 가능하게 +@Profile("!prod") @RestController @RequestMapping("/api/v1") class DevAuthController( From 662135a5667dae798f56d6f74584d2278c655ad9 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Sat, 6 Apr 2024 14:06:15 +0900 Subject: [PATCH 187/214] =?UTF-8?q?fix:=20=EA=B5=90=EC=88=98=20=EC=A0=95?= =?UTF-8?q?=EB=A0=AC=20(#243)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../csereal/core/member/service/ProfessorService.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 d62cf464..bb504865 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 @@ -118,7 +118,7 @@ class ProfessorServiceImpl( .sortedWith { a, b -> when { startsWithEnglish(a.name) && !startsWithEnglish(b.name) -> 1 - !startsWithEnglish(a.name) && !startsWithEnglish(b.name) -> -1 + !startsWithEnglish(a.name) && startsWithEnglish(b.name) -> -1 else -> a.name.compareTo(b.name) } } @@ -139,7 +139,7 @@ class ProfessorServiceImpl( .sortedWith { a, b -> when { startsWithEnglish(a.name) && !startsWithEnglish(b.name) -> 1 - !startsWithEnglish(a.name) && !startsWithEnglish(b.name) -> -1 + !startsWithEnglish(a.name) && startsWithEnglish(b.name) -> -1 else -> a.name.compareTo(b.name) } } From a83a6334b83717796153616dd9325480e83a8e83 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, 6 Apr 2024 14:13:21 +0900 Subject: [PATCH 188/214] Refactor: Move languagetype from properties from enums (#244) Co-authored-by: Jo Seonggyu --- .../csereal/common/{properties => enums}/LanguageType.kt | 2 +- .../com/wafflestudio/csereal/core/about/api/AboutController.kt | 2 +- .../csereal/core/about/api/res/AboutSearchElementDto.kt | 2 +- .../com/wafflestudio/csereal/core/about/database/AboutEntity.kt | 2 +- .../wafflestudio/csereal/core/about/database/AboutRepository.kt | 2 +- .../kotlin/com/wafflestudio/csereal/core/about/dto/AboutDto.kt | 2 +- .../com/wafflestudio/csereal/core/about/dto/DirectionDto.kt | 2 +- .../com/wafflestudio/csereal/core/about/dto/FacilityDto.kt | 2 +- .../com/wafflestudio/csereal/core/about/dto/StudentClubDto.kt | 2 +- .../com/wafflestudio/csereal/core/about/service/AboutService.kt | 2 +- .../csereal/core/academics/api/AcademicsController.kt | 2 +- .../csereal/core/academics/api/res/AcademicsSearchResElement.kt | 2 +- .../csereal/core/academics/database/AcademicsEntity.kt | 2 +- .../csereal/core/academics/database/AcademicsRepository.kt | 2 +- .../csereal/core/academics/database/AcademicsSearchEntity.kt | 2 +- .../core/academics/database/AcademicsSearchRepository.kt | 2 +- .../csereal/core/academics/database/CourseEntity.kt | 2 +- .../csereal/core/academics/database/CourseRepository.kt | 2 +- .../csereal/core/academics/database/ScholarshipEntity.kt | 2 +- .../com/wafflestudio/csereal/core/academics/dto/AcademicsDto.kt | 2 +- .../com/wafflestudio/csereal/core/academics/dto/CourseDto.kt | 2 +- .../wafflestudio/csereal/core/academics/dto/ScholarshipDto.kt | 2 +- .../csereal/core/academics/service/AcademicsSearchService.kt | 2 +- .../csereal/core/academics/service/AcademicsService.kt | 2 +- .../csereal/core/admissions/api/AdmissionsController.kt | 2 +- .../csereal/core/admissions/api/res/AdmissionSearchResElem.kt | 2 +- .../csereal/core/admissions/database/AdmissionsEntity.kt | 2 +- .../csereal/core/admissions/database/AdmissionsRepository.kt | 2 +- .../wafflestudio/csereal/core/admissions/dto/AdmissionsDto.kt | 2 +- .../csereal/core/admissions/service/AdmissionsService.kt | 2 +- .../csereal/core/admissions/type/AdmissionsMainType.kt | 2 +- .../csereal/core/admissions/type/AdmissionsPostType.kt | 2 +- .../csereal/core/conference/database/ConferenceEntity.kt | 2 +- .../wafflestudio/csereal/core/conference/dto/ConferenceDto.kt | 2 +- .../csereal/core/conference/service/ConferenceService.kt | 2 +- .../csereal/core/member/api/MemberSearchController.kt | 2 +- .../csereal/core/member/api/res/MemberSearchResponseElement.kt | 2 +- .../csereal/core/member/database/MemberSearchEntity.kt | 2 +- .../csereal/core/member/database/MemberSearchRepository.kt | 2 +- .../csereal/core/member/database/ProfessorEntity.kt | 2 +- .../csereal/core/member/database/ProfessorRepository.kt | 2 +- .../wafflestudio/csereal/core/member/database/StaffEntity.kt | 2 +- .../csereal/core/member/database/StaffRepository.kt | 2 +- .../com/wafflestudio/csereal/core/member/dto/ProfessorDto.kt | 2 +- .../kotlin/com/wafflestudio/csereal/core/member/dto/StaffDto.kt | 2 +- .../csereal/core/member/service/MemberSearchService.kt | 2 +- .../csereal/core/member/service/ProfessorService.kt | 2 +- .../wafflestudio/csereal/core/member/service/StaffService.kt | 2 +- .../csereal/core/research/api/ResearchController.kt | 2 +- .../csereal/core/research/api/res/ResearchSearchResElement.kt | 2 +- .../wafflestudio/csereal/core/research/database/LabEntity.kt | 2 +- .../csereal/core/research/database/LabRepository.kt | 2 +- .../csereal/core/research/database/ResearchEntity.kt | 2 +- .../csereal/core/research/database/ResearchRepository.kt | 2 +- .../csereal/core/research/database/ResearchSearchEntity.kt | 2 +- .../csereal/core/research/database/ResearchSearchRepository.kt | 2 +- .../kotlin/com/wafflestudio/csereal/core/research/dto/LabDto.kt | 2 +- .../com/wafflestudio/csereal/core/research/dto/ResearchDto.kt | 2 +- .../csereal/core/research/service/ResearchSearchService.kt | 2 +- .../csereal/core/research/service/ResearchService.kt | 2 +- .../csereal/core/admissions/service/AdmissionsServiceTest.kt | 2 +- .../csereal/core/conference/service/ConferenceServiceTest.kt | 2 +- .../csereal/core/member/service/ProfessorServiceTest.kt | 2 +- .../csereal/core/member/service/StaffServiceTest.kt | 2 +- .../csereal/core/reseach/service/ResearchSearchServiceTest.kt | 2 +- .../csereal/core/reseach/service/ResearchServiceTest.kt | 2 +- 66 files changed, 66 insertions(+), 66 deletions(-) rename src/main/kotlin/com/wafflestudio/csereal/common/{properties => enums}/LanguageType.kt (92%) diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/properties/LanguageType.kt b/src/main/kotlin/com/wafflestudio/csereal/common/enums/LanguageType.kt similarity index 92% rename from src/main/kotlin/com/wafflestudio/csereal/common/properties/LanguageType.kt rename to src/main/kotlin/com/wafflestudio/csereal/common/enums/LanguageType.kt index 92b6f511..9f3265ba 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/properties/LanguageType.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/enums/LanguageType.kt @@ -1,4 +1,4 @@ -package com.wafflestudio.csereal.common.properties +package com.wafflestudio.csereal.common.enums import com.wafflestudio.csereal.common.CserealException 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 8af09233..85d77a47 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,7 +1,7 @@ package com.wafflestudio.csereal.core.about.api import com.wafflestudio.csereal.common.aop.AuthenticatedStaff -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.about.api.res.AboutSearchResBody import com.wafflestudio.csereal.core.about.dto.* import com.wafflestudio.csereal.core.about.dto.AboutRequest diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/res/AboutSearchElementDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/res/AboutSearchElementDto.kt index 0e6a09ff..a7435523 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/res/AboutSearchElementDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/res/AboutSearchElementDto.kt @@ -1,6 +1,6 @@ package com.wafflestudio.csereal.core.about.api.res -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.common.utils.cleanTextFromHtml import com.wafflestudio.csereal.common.utils.substringAroundKeyword import com.wafflestudio.csereal.core.about.database.AboutEntity 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 3f72d722..6aeeacb2 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 @@ -3,7 +3,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.MainImageContentEntityType -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.common.utils.StringListConverter import com.wafflestudio.csereal.common.utils.cleanTextFromHtml import com.wafflestudio.csereal.core.about.dto.AboutDto 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 1c00139b..9470cd50 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 @@ -2,7 +2,7 @@ package com.wafflestudio.csereal.core.about.database import com.querydsl.jpa.impl.JPAQuery import com.querydsl.jpa.impl.JPAQueryFactory -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.common.repository.CommonRepository import com.wafflestudio.csereal.common.utils.exchangeValidPageNum import com.wafflestudio.csereal.core.about.database.QAboutEntity.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 3dd181b6..fc35fbbc 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,7 +1,7 @@ package com.wafflestudio.csereal.core.about.dto import com.fasterxml.jackson.annotation.JsonInclude -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.about.database.AboutEntity import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse import java.time.LocalDateTime 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 bd2ae452..5e41978c 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 @@ -1,7 +1,7 @@ package com.wafflestudio.csereal.core.about.dto import com.fasterxml.jackson.annotation.JsonInclude -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.about.database.AboutEntity data class DirectionDto( 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 71ce1d5d..edf03869 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 @@ -1,7 +1,7 @@ package com.wafflestudio.csereal.core.about.dto import com.fasterxml.jackson.annotation.JsonInclude -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.about.database.AboutEntity data class FacilityDto( 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 45968622..bf889423 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 @@ -1,7 +1,7 @@ package com.wafflestudio.csereal.core.about.dto import com.fasterxml.jackson.annotation.JsonInclude -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.about.database.AboutEntity import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse import java.time.LocalDateTime 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 faf6fdec..ea7cb50a 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,7 +1,7 @@ package com.wafflestudio.csereal.core.about.service import com.wafflestudio.csereal.common.CserealException -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.about.api.res.AboutSearchElementDto import com.wafflestudio.csereal.core.about.api.res.AboutSearchResBody import com.wafflestudio.csereal.core.about.database.* 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 88f69bd0..45b2090c 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,7 +1,7 @@ package com.wafflestudio.csereal.core.academics.api import com.wafflestudio.csereal.common.aop.AuthenticatedStaff -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.academics.dto.* import com.wafflestudio.csereal.core.academics.service.AcademicsService import com.wafflestudio.csereal.core.academics.dto.ScholarshipDto diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/res/AcademicsSearchResElement.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/res/AcademicsSearchResElement.kt index e88522b6..90adb2ba 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/res/AcademicsSearchResElement.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/res/AcademicsSearchResElement.kt @@ -1,7 +1,7 @@ package com.wafflestudio.csereal.core.academics.api.res import com.wafflestudio.csereal.common.CserealException -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.common.utils.substringAroundKeyword import com.wafflestudio.csereal.core.academics.database.AcademicsPostType import com.wafflestudio.csereal.core.academics.database.AcademicsSearchEntity 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 a3b87160..d8cf4af2 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 @@ -2,7 +2,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.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.academics.dto.AcademicsDto import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity import jakarta.persistence.* 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 a3b0beff..b0a4a747 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 @@ -1,6 +1,6 @@ package com.wafflestudio.csereal.core.academics.database -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import org.springframework.data.jpa.repository.JpaRepository interface AcademicsRepository : JpaRepository { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchEntity.kt index 1cd8ea7b..c9e3fa3d 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchEntity.kt @@ -1,7 +1,7 @@ package com.wafflestudio.csereal.core.academics.database import com.wafflestudio.csereal.common.config.BaseTimeEntity -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.common.utils.cleanTextFromHtml import jakarta.persistence.* diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchRepository.kt index 70e35c03..c2efbab6 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchRepository.kt @@ -2,7 +2,7 @@ package com.wafflestudio.csereal.core.academics.database import com.querydsl.jpa.impl.JPAQuery import com.querydsl.jpa.impl.JPAQueryFactory -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.common.repository.CommonRepository import com.wafflestudio.csereal.common.utils.exchangeValidPageNum import com.wafflestudio.csereal.core.academics.database.QAcademicsEntity.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 index da7134a5..aad8b620 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,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.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.academics.dto.CourseDto import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity import jakarta.persistence.* 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 c48ec048..b4fdb2dc 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 @@ -1,6 +1,6 @@ package com.wafflestudio.csereal.core.academics.database -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import org.springframework.data.jpa.repository.JpaRepository interface CourseRepository : JpaRepository { 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 64ab6098..dc7cc265 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,7 +1,7 @@ package com.wafflestudio.csereal.core.academics.database import com.wafflestudio.csereal.common.config.BaseTimeEntity -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.academics.dto.ScholarshipDto import jakarta.persistence.* 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 38091e05..7281d2fd 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,6 @@ package com.wafflestudio.csereal.core.academics.dto -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.academics.database.AcademicsEntity import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse import java.time.LocalDateTime 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 e1f680fc..cbd0df79 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,6 +1,6 @@ package com.wafflestudio.csereal.core.academics.dto -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.academics.database.CourseEntity import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/ScholarshipDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/ScholarshipDto.kt index 0312e296..755be2ca 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/ScholarshipDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/ScholarshipDto.kt @@ -1,6 +1,6 @@ package com.wafflestudio.csereal.core.academics.dto -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.academics.database.ScholarshipEntity data class ScholarshipDto( diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsSearchService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsSearchService.kt index 3a126a08..dc7d32b5 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsSearchService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsSearchService.kt @@ -1,6 +1,6 @@ package com.wafflestudio.csereal.core.academics.service -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.academics.api.res.AcademicsSearchResBody import com.wafflestudio.csereal.core.academics.database.* import com.wafflestudio.csereal.core.main.event.RefreshSearchEvent 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 1e033e68..6874f169 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,7 +1,7 @@ package com.wafflestudio.csereal.core.academics.service import com.wafflestudio.csereal.common.CserealException -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.academics.database.* import com.wafflestudio.csereal.core.academics.dto.* import com.wafflestudio.csereal.core.resource.attachment.service.AttachmentService 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 f5654461..d4cc6d36 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,7 @@ package com.wafflestudio.csereal.core.admissions.api import com.wafflestudio.csereal.common.aop.AuthenticatedStaff -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.admissions.api.req.AdmissionReqBody import com.wafflestudio.csereal.core.admissions.dto.AdmissionsDto import com.wafflestudio.csereal.core.admissions.api.req.AdmissionMigrateElem diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/res/AdmissionSearchResElem.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/res/AdmissionSearchResElem.kt index 4de31ae2..653ea2b7 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/res/AdmissionSearchResElem.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/res/AdmissionSearchResElem.kt @@ -1,6 +1,6 @@ package com.wafflestudio.csereal.core.admissions.api.res -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.common.utils.substringAroundKeyword import com.wafflestudio.csereal.core.admissions.database.AdmissionsEntity 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 5f37583e..e06ec1d1 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 @@ -1,7 +1,7 @@ package com.wafflestudio.csereal.core.admissions.database import com.wafflestudio.csereal.common.config.BaseTimeEntity -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.common.utils.cleanTextFromHtml import com.wafflestudio.csereal.core.admissions.api.req.AdmissionReqBody import com.wafflestudio.csereal.core.admissions.dto.AdmissionsDto 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 67ae8be4..18528dbd 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 @@ -1,7 +1,7 @@ package com.wafflestudio.csereal.core.admissions.database import com.querydsl.jpa.impl.JPAQueryFactory -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.common.repository.CommonRepository import com.wafflestudio.csereal.common.utils.exchangeValidPageNum import com.wafflestudio.csereal.core.admissions.database.QAdmissionsEntity.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 80c28352..39cb577d 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,6 +1,6 @@ package com.wafflestudio.csereal.core.admissions.dto -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.admissions.database.AdmissionsEntity import java.time.LocalDateTime 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 2b9df4ce..1c5ae87d 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,7 +1,7 @@ package com.wafflestudio.csereal.core.admissions.service import com.wafflestudio.csereal.common.CserealException -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.admissions.api.req.AdmissionMigrateElem import com.wafflestudio.csereal.core.admissions.api.req.AdmissionReqBody import com.wafflestudio.csereal.core.admissions.api.res.AdmissionSearchResBody diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/type/AdmissionsMainType.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/type/AdmissionsMainType.kt index 6e3776c9..f57bb73b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/type/AdmissionsMainType.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/type/AdmissionsMainType.kt @@ -1,7 +1,7 @@ package com.wafflestudio.csereal.core.admissions.type import com.wafflestudio.csereal.common.CserealException -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType enum class AdmissionsMainType( val ko: String, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/type/AdmissionsPostType.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/type/AdmissionsPostType.kt index 571ff67b..084485b6 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/type/AdmissionsPostType.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/type/AdmissionsPostType.kt @@ -1,7 +1,7 @@ package com.wafflestudio.csereal.core.admissions.type import com.wafflestudio.csereal.common.CserealException -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType enum class AdmissionsPostType( val ko: String, 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 5a5f03dc..6e911164 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,7 +1,7 @@ package com.wafflestudio.csereal.core.conference.database import com.wafflestudio.csereal.common.config.BaseTimeEntity -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.conference.dto.ConferenceDto import com.wafflestudio.csereal.core.research.database.ResearchSearchEntity import jakarta.persistence.* 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 d6e95c59..a335e86c 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 @@ -1,7 +1,7 @@ package com.wafflestudio.csereal.core.conference.dto import com.fasterxml.jackson.annotation.JsonInclude -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.conference.database.ConferenceEntity data class ConferenceDto( 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 c24fe88e..09b2484a 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,7 +1,7 @@ package com.wafflestudio.csereal.core.conference.service import com.wafflestudio.csereal.common.CserealException -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.conference.database.ConferenceEntity import com.wafflestudio.csereal.core.conference.database.ConferencePageEntity import com.wafflestudio.csereal.core.conference.database.ConferencePageRepository 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 8053d71b..2c016e07 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,6 +1,6 @@ package com.wafflestudio.csereal.core.member.api -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.member.service.MemberSearchService import jakarta.validation.Valid import jakarta.validation.constraints.Positive diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/res/MemberSearchResponseElement.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/res/MemberSearchResponseElement.kt index a7365722..e56bbef8 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/res/MemberSearchResponseElement.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/res/MemberSearchResponseElement.kt @@ -1,7 +1,7 @@ package com.wafflestudio.csereal.core.member.api.res import com.wafflestudio.csereal.common.CserealException -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType 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 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 6262fef2..6c4d0d13 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,7 +1,7 @@ package com.wafflestudio.csereal.core.member.database import com.wafflestudio.csereal.common.config.BaseTimeEntity -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import jakarta.persistence.* @Entity(name = "member_search") 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 bb358ee2..773ec5b0 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 @@ -2,7 +2,7 @@ package com.wafflestudio.csereal.core.member.database import com.querydsl.jpa.impl.JPAQuery import com.querydsl.jpa.impl.JPAQueryFactory -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.common.repository.CommonRepository import com.wafflestudio.csereal.common.utils.exchangeValidPageNum import com.wafflestudio.csereal.core.member.database.QMemberSearchEntity.memberSearchEntity 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 00b46019..b0f50258 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 @@ -2,7 +2,7 @@ package com.wafflestudio.csereal.core.member.database import com.wafflestudio.csereal.common.config.BaseTimeEntity import com.wafflestudio.csereal.common.controller.MainImageContentEntityType -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType 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 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 d11b54c3..b3387159 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 @@ -1,6 +1,6 @@ package com.wafflestudio.csereal.core.member.database -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import org.springframework.data.jpa.repository.JpaRepository interface ProfessorRepository : JpaRepository { 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 60fc24b6..fd17c244 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 @@ -2,7 +2,7 @@ package com.wafflestudio.csereal.core.member.database import com.wafflestudio.csereal.common.config.BaseTimeEntity import com.wafflestudio.csereal.common.controller.MainImageContentEntityType -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.member.dto.StaffDto import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity import jakarta.persistence.* 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 6ce765aa..bb302940 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 @@ -1,6 +1,6 @@ package com.wafflestudio.csereal.core.member.database -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import org.springframework.data.jpa.repository.JpaRepository interface StaffRepository : JpaRepository { 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 072db7e1..36423d7b 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 @@ -1,7 +1,7 @@ package com.wafflestudio.csereal.core.member.dto import com.fasterxml.jackson.annotation.JsonInclude -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.member.database.ProfessorEntity import com.wafflestudio.csereal.core.member.database.ProfessorStatus import java.time.LocalDate 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 1c16482a..59eebfaf 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 @@ -1,7 +1,7 @@ package com.wafflestudio.csereal.core.member.dto import com.fasterxml.jackson.annotation.JsonInclude -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.member.database.StaffEntity data class StaffDto( 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 d6ba232e..69b4fc6c 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,6 +1,6 @@ package com.wafflestudio.csereal.core.member.service -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.main.event.RefreshSearchEvent import com.wafflestudio.csereal.core.member.api.res.MemberSearchResBody import com.wafflestudio.csereal.core.member.database.MemberSearchEntity 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 bb504865..76d27d8e 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 @@ -1,7 +1,7 @@ package com.wafflestudio.csereal.core.member.service import com.wafflestudio.csereal.common.CserealException -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.common.utils.startsWithEnglish import com.wafflestudio.csereal.core.member.database.* import com.wafflestudio.csereal.core.member.dto.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 09fabba5..bf4cff86 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,7 +1,7 @@ package com.wafflestudio.csereal.core.member.service import com.wafflestudio.csereal.common.CserealException -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.member.database.MemberSearchEntity import com.wafflestudio.csereal.core.member.database.StaffEntity import com.wafflestudio.csereal.core.member.database.StaffRepository 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 87407f66..dd00c319 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,7 +1,7 @@ package com.wafflestudio.csereal.core.research.api import com.wafflestudio.csereal.common.aop.AuthenticatedStaff -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.research.dto.LabDto import com.wafflestudio.csereal.core.research.dto.LabUpdateRequest import com.wafflestudio.csereal.core.research.dto.ResearchDto diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/api/res/ResearchSearchResElement.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/res/ResearchSearchResElement.kt index 593e5138..a5874bfa 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/api/res/ResearchSearchResElement.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/res/ResearchSearchResElement.kt @@ -1,7 +1,7 @@ package com.wafflestudio.csereal.core.research.api.res import com.wafflestudio.csereal.common.CserealException -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.common.utils.substringAroundKeyword import com.wafflestudio.csereal.core.research.database.ResearchPostType import com.wafflestudio.csereal.core.research.database.ResearchSearchEntity 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 66daa817..6a77b365 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,7 @@ package com.wafflestudio.csereal.core.research.database import com.wafflestudio.csereal.common.config.BaseTimeEntity -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.member.database.ProfessorEntity import com.wafflestudio.csereal.core.research.dto.LabDto import com.wafflestudio.csereal.core.research.dto.LabUpdateRequest 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 71a26786..17294647 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 @@ -1,6 +1,6 @@ package com.wafflestudio.csereal.core.research.database -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import org.springframework.data.jpa.repository.JpaRepository interface LabRepository : JpaRepository { 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 e0c3db51..e7911d6f 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 @@ -3,7 +3,7 @@ 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.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType 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 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 b4dd7323..6a22d921 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 @@ -1,6 +1,6 @@ package com.wafflestudio.csereal.core.research.database -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import org.springframework.data.jpa.repository.JpaRepository interface ResearchRepository : JpaRepository { 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 cec416bb..46c287ea 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 @@ -1,7 +1,7 @@ package com.wafflestudio.csereal.core.research.database import com.wafflestudio.csereal.common.config.BaseTimeEntity -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.common.utils.cleanTextFromHtml import com.wafflestudio.csereal.core.conference.database.ConferenceEntity import jakarta.persistence.* 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 d415297d..cb29f401 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 @@ -2,7 +2,7 @@ package com.wafflestudio.csereal.core.research.database import com.querydsl.jpa.impl.JPAQuery import com.querydsl.jpa.impl.JPAQueryFactory -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.common.repository.CommonRepository import com.wafflestudio.csereal.common.utils.exchangeValidPageNum import com.wafflestudio.csereal.core.conference.database.QConferenceEntity.conferenceEntity 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 3250be51..74ed33d7 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,6 +1,6 @@ package com.wafflestudio.csereal.core.research.dto -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.research.database.LabEntity import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse 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 e87c6668..77e6ffb4 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 @@ -1,6 +1,6 @@ package com.wafflestudio.csereal.core.research.dto -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType 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 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 90f0f2b6..84877958 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 @@ -1,6 +1,6 @@ package com.wafflestudio.csereal.core.research.service -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.conference.database.ConferenceRepository import com.wafflestudio.csereal.core.main.event.RefreshSearchEvent import com.wafflestudio.csereal.core.member.event.ProfessorCreatedEvent 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 023eac57..4d2cfa65 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 @@ -2,7 +2,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.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.member.database.ProfessorRepository import com.wafflestudio.csereal.core.research.database.* import com.wafflestudio.csereal.core.research.dto.* diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/admissions/service/AdmissionsServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/admissions/service/AdmissionsServiceTest.kt index 3d8d1888..d86cd85b 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/admissions/service/AdmissionsServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/admissions/service/AdmissionsServiceTest.kt @@ -1,7 +1,7 @@ package com.wafflestudio.csereal.core.admissions.service import com.wafflestudio.csereal.common.CserealException -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.admissions.api.req.AdmissionReqBody import com.wafflestudio.csereal.core.admissions.database.AdmissionsEntity import com.wafflestudio.csereal.core.admissions.database.AdmissionsRepository 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 2b6a87f0..0c6501e2 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 @@ -1,6 +1,6 @@ package com.wafflestudio.csereal.core.conference.service -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.conference.database.ConferenceEntity import com.wafflestudio.csereal.core.conference.database.ConferencePageEntity import com.wafflestudio.csereal.core.conference.database.ConferencePageRepository 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 13256934..1f880acd 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 @@ -1,6 +1,6 @@ package com.wafflestudio.csereal.core.member.service -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.member.database.MemberSearchRepository import com.wafflestudio.csereal.core.member.database.ProfessorRepository import com.wafflestudio.csereal.core.member.database.ProfessorStatus 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 40644618..9ec09da7 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 @@ -1,6 +1,6 @@ package com.wafflestudio.csereal.core.member.service -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.member.database.MemberSearchRepository import com.wafflestudio.csereal.core.member.database.StaffRepository import com.wafflestudio.csereal.core.member.dto.StaffDto diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchSearchServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchSearchServiceTest.kt index 85d9be47..83460145 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchSearchServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchSearchServiceTest.kt @@ -1,6 +1,6 @@ package com.wafflestudio.csereal.core.reseach.service -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.member.database.ProfessorRepository import com.wafflestudio.csereal.core.member.database.ProfessorStatus import com.wafflestudio.csereal.core.member.dto.ProfessorDto 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 cf631d44..171650a1 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 @@ -1,6 +1,6 @@ package com.wafflestudio.csereal.core.reseach.service -import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.member.database.ProfessorEntity import com.wafflestudio.csereal.core.member.database.ProfessorRepository import com.wafflestudio.csereal.core.member.database.ProfessorStatus From 7f9cfab973fc18ee1c8d11e217dde1fa81889970 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Sat, 6 Apr 2024 16:04:19 +0900 Subject: [PATCH 189/214] =?UTF-8?q?fix:=20stat=20year=202050=EB=85=84?= =?UTF-8?q?=EA=B9=8C=EC=A7=80=20=ED=99=95=EC=9E=A5=20(#245)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: stat year 2050년까지 확장 * fix: 쿼리 수정 --- .../csereal/core/about/database/StatRepository.kt | 5 +++++ .../wafflestudio/csereal/core/about/service/AboutService.kt | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) 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 080d0c1e..665cc143 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 @@ -1,7 +1,12 @@ package com.wafflestudio.csereal.core.about.database import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query interface StatRepository : JpaRepository { + fun findAllByYear(year: Int): List fun findAllByYearAndDegree(year: Int, degree: Degree): List + + @Query("SELECT MAX(s.year) FROM stat s") + fun findMaxYear(): Int } 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 ea7cb50a..7c676e7d 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 @@ -168,7 +168,8 @@ class AboutServiceImpl( ).description val statList = mutableListOf() - for (i: Int in 2021 downTo 2011) { + val maxYear = statRepository.findMaxYear() + for (i: Int in maxYear downTo 2011) { val bachelor = statRepository.findAllByYearAndDegree(i, Degree.BACHELOR).map { FutureCareersStatDegreeDto.of(it) } From dafec0b651ee7e48bd5a1ff2ffbc78f333f8dcfd Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Sat, 6 Apr 2024 16:09:45 +0900 Subject: [PATCH 190/214] fix: adjust max thread and pool size (#246) --- src/main/resources/application.yaml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 04f23b8d..999e0bd2 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -6,6 +6,8 @@ spring: active: local datasource: driver-class-name: com.mysql.cj.jdbc.Driver + hikari: + maximum-pool-size: 24 jpa: properties: hibernate: @@ -17,6 +19,9 @@ spring: max-file-size: 100MB server: + tomcat: + threads: + max: 16 servlet: session: timeout: 7200 # 2시간 @@ -71,7 +76,7 @@ spring: logging: level: - # default: INFO + # default: INFO org: springframework: security: DEBUG @@ -120,7 +125,7 @@ spring: config.activate.on-profile: dev jpa: hibernate: - ddl-auto: update + ddl-auto: update show-sql: true open-in-view: false security: @@ -175,7 +180,7 @@ spring: logging: level: - # default: INFO + # default: INFO org: springframework: security: DEBUG From 58455b71e0ddebbbd4542fdc3e54b7e1f0001e41 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, 6 Apr 2024 16:15:40 +0900 Subject: [PATCH 191/214] =?UTF-8?q?Feat:=20=EA=B3=B5=EC=A7=80,=20=EC=83=88?= =?UTF-8?q?=EC=86=8C=EC=8B=9D,=20=EC=84=B8=EB=AF=B8=EB=82=98=20=EC=A0=95?= =?UTF-8?q?=EB=A0=AC=20=EC=98=B5=EC=85=98=20=EC=B6=94=EA=B0=80=20(#247)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Refactor: Move languagetype from properties from enums * Feat: Add sort order type for content search (notice, news, seminar) * Feat: Add sort option for news search. * Feat: Add sort option for notice search. * Feat: Add sort option for seminar search. * Fix: Handle for empty keyword. --------- Co-authored-by: Junhyeong Kim --- .../common/enums/ContentSearchSortType.kt | 19 ++++++++ .../csereal/core/news/api/NewsController.kt | 7 ++- .../core/news/database/NewsRepository.kt | 14 ++++-- .../csereal/core/news/service/NewsService.kt | 5 +- .../core/notice/api/NoticeController.kt | 7 ++- .../core/notice/database/NoticeRepository.kt | 48 +++++++++++++------ .../core/notice/dto/NoticeSearchDto.kt | 12 ++++- .../core/notice/service/NoticeService.kt | 5 +- .../core/seminar/api/SeminarController.kt | 7 ++- .../seminar/database/SeminarRepository.kt | 14 ++++-- .../core/seminar/service/SeminarService.kt | 5 +- 11 files changed, 116 insertions(+), 27 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/common/enums/ContentSearchSortType.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/enums/ContentSearchSortType.kt b/src/main/kotlin/com/wafflestudio/csereal/common/enums/ContentSearchSortType.kt new file mode 100644 index 00000000..94218ad4 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/common/enums/ContentSearchSortType.kt @@ -0,0 +1,19 @@ +package com.wafflestudio.csereal.common.enums + +import com.wafflestudio.csereal.common.CserealException + +enum class ContentSearchSortType { + DATE, + RELEVANCE; + + companion object { + fun fromJsonValue(field: String) = + try { + field.replace('-', '_') + .uppercase() + .let { ContentSearchSortType.valueOf(it) } + } catch (e: IllegalArgumentException) { + throw CserealException.Csereal400("잘못된 Sort Type이 주어졌습니다.") + } + } +} 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 0f253dc7..d998834b 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,6 +1,7 @@ package com.wafflestudio.csereal.core.news.api import com.wafflestudio.csereal.common.aop.AuthenticatedStaff +import com.wafflestudio.csereal.common.enums.ContentSearchSortType import com.wafflestudio.csereal.common.utils.getUsername import com.wafflestudio.csereal.core.news.dto.NewsDto import com.wafflestudio.csereal.core.news.dto.NewsSearchResponse @@ -31,6 +32,7 @@ class NewsController( @RequestParam(required = false) keyword: String?, @RequestParam(required = false) pageNum: Int?, @RequestParam(required = false, defaultValue = "10") pageSize: Int, + @RequestParam(required = false, defaultValue = "DATE") sortBy: String, authentication: Authentication? ): ResponseEntity { val username = getUsername(authentication) @@ -42,7 +44,10 @@ class NewsController( val usePageBtn = pageNum != null val page = pageNum ?: 1 val pageRequest = PageRequest.of(page - 1, pageSize) - return ResponseEntity.ok(newsService.searchNews(tag, keyword, pageRequest, usePageBtn, isStaff)) + + val sortType = ContentSearchSortType.fromJsonValue(sortBy) + + return ResponseEntity.ok(newsService.searchNews(tag, keyword, pageRequest, usePageBtn, sortType, isStaff)) } @GetMapping("/totalSearch") 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 34957c45..c0eef4e2 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,6 +2,7 @@ package com.wafflestudio.csereal.core.news.database import com.querydsl.core.BooleanBuilder import com.querydsl.jpa.impl.JPAQueryFactory +import com.wafflestudio.csereal.common.enums.ContentSearchSortType import com.wafflestudio.csereal.common.repository.CommonRepository import com.wafflestudio.csereal.common.utils.FixedPageRequest import com.wafflestudio.csereal.core.admin.dto.AdminSlideElement @@ -39,6 +40,7 @@ interface CustomNewsRepository { keyword: String?, pageable: Pageable, usePageBtn: Boolean, + sortBy: ContentSearchSortType, isStaff: Boolean ): NewsSearchResponse @@ -64,6 +66,7 @@ class NewsRepositoryImpl( keyword: String?, pageable: Pageable, usePageBtn: Boolean, + sortBy: ContentSearchSortType, isStaff: Boolean ): NewsSearchResponse { val keywordBooleanBuilder = BooleanBuilder() @@ -109,12 +112,17 @@ class NewsRepositoryImpl( total = (10 * pageable.pageSize).toLong() + 1 // 10개 페이지 고정 } - val newsEntityList = jpaQuery - .orderBy(newsEntity.createdAt.desc()) + val newsEntityQuery = jpaQuery .offset(pageRequest.offset) .limit(pageRequest.pageSize.toLong()) .distinct() - .fetch() + + val newsEntityList = when { + sortBy == ContentSearchSortType.DATE || keyword.isNullOrEmpty() -> newsEntityQuery.orderBy( + newsEntity.createdAt.desc() + ) + else /* sortBy == RELEVANCE */ -> newsEntityQuery + }.fetch() val newsSearchDtoList: List = newsEntityList.map { val imageURL = mainImageService.createImageURL(it.mainImage) 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 0104a651..b0f42a11 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.common.enums.ContentSearchSortType import com.wafflestudio.csereal.core.admin.dto.AdminSlidesResponse import com.wafflestudio.csereal.core.news.database.* import com.wafflestudio.csereal.core.news.dto.NewsDto @@ -20,6 +21,7 @@ interface NewsService { keyword: String?, pageable: Pageable, usePageBtn: Boolean, + sortBy: ContentSearchSortType, isStaff: Boolean ): NewsSearchResponse @@ -53,9 +55,10 @@ class NewsServiceImpl( keyword: String?, pageable: Pageable, usePageBtn: Boolean, + sortBy: ContentSearchSortType, isStaff: Boolean ): NewsSearchResponse { - return newsRepository.searchNews(tag, keyword, pageable, usePageBtn, isStaff) + return newsRepository.searchNews(tag, keyword, pageable, usePageBtn, sortBy, isStaff) } @Transactional(readOnly = true) 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 9d5dd471..110fdcd0 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,6 +1,7 @@ package com.wafflestudio.csereal.core.notice.api import com.wafflestudio.csereal.common.aop.AuthenticatedStaff +import com.wafflestudio.csereal.common.enums.ContentSearchSortType import com.wafflestudio.csereal.common.utils.getUsername import com.wafflestudio.csereal.core.notice.dto.* import com.wafflestudio.csereal.core.notice.service.NoticeService @@ -29,6 +30,7 @@ class NoticeController( @RequestParam(required = false) keyword: String?, @RequestParam(required = false) pageNum: Int?, @RequestParam(required = false, defaultValue = "20") pageSize: Int, + @RequestParam(required = false, defaultValue = "DATE") sortBy: String, authentication: Authentication? ): ResponseEntity { val username = getUsername(authentication) @@ -40,7 +42,10 @@ class NoticeController( val usePageBtn = pageNum != null val page = pageNum ?: 1 val pageRequest = PageRequest.of(page - 1, pageSize) - return ResponseEntity.ok(noticeService.searchNotice(tag, keyword, pageRequest, usePageBtn, isStaff)) + + val sortType = ContentSearchSortType.fromJsonValue(sortBy) + + return ResponseEntity.ok(noticeService.searchNotice(tag, keyword, pageRequest, usePageBtn, sortType, isStaff)) } @GetMapping("/totalSearch") 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 00b0ee23..f2615c8f 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.core.types.Projections import com.querydsl.jpa.impl.JPAQueryFactory +import com.wafflestudio.csereal.common.enums.ContentSearchSortType import com.wafflestudio.csereal.common.repository.CommonRepository import com.wafflestudio.csereal.common.utils.FixedPageRequest import com.wafflestudio.csereal.core.notice.database.QNoticeEntity.noticeEntity @@ -35,6 +36,7 @@ interface CustomNoticeRepository { keyword: String?, pageable: Pageable, usePageBtn: Boolean, + sortBy: ContentSearchSortType, isStaff: Boolean ): NoticeSearchResponse @@ -95,21 +97,13 @@ class NoticeRepositoryImpl( keyword: String?, pageable: Pageable, usePageBtn: Boolean, + sortBy: ContentSearchSortType, isStaff: Boolean ): NoticeSearchResponse { val keywordBooleanBuilder = BooleanBuilder() val tagsBooleanBuilder = BooleanBuilder() val isPrivateBooleanBuilder = BooleanBuilder() - if (!keyword.isNullOrEmpty()) { - 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) @@ -125,6 +119,20 @@ class NoticeRepositoryImpl( ) } + val scoreOrNull = if (!keyword.isNullOrEmpty()) { + commonRepository.searchFullDoubleTextTemplate( + keyword, + noticeEntity.title, + noticeEntity.plainTextDescription + ) + } else { + null + } + + if (scoreOrNull != null) { + keywordBooleanBuilder.and(scoreOrNull.gt(0.0)) + } + val jpaQuery = queryFactory.select( Projections.constructor( NoticeSearchDto::class.java, @@ -133,7 +141,8 @@ class NoticeRepositoryImpl( noticeEntity.createdAt, noticeEntity.isPinned, noticeEntity.attachments.isNotEmpty, - noticeEntity.isPrivate + noticeEntity.isPrivate, + scoreOrNull ) ).from(noticeEntity) .leftJoin(noticeTagEntity).on(noticeTagEntity.notice.eq(noticeEntity)) @@ -151,13 +160,24 @@ class NoticeRepositoryImpl( total = (10 * pageable.pageSize).toLong() + 1 // 10개 페이지 고정 } - val noticeSearchDtoList = jpaQuery - .orderBy(noticeEntity.isPinned.desc()) - .orderBy(noticeEntity.createdAt.desc()) + val noticeSearchQuery = jpaQuery .offset(pageRequest.offset) .limit(pageRequest.pageSize.toLong()) .distinct() - .fetch() + + val noticeSearchDtoList = noticeSearchQuery + .orderBy(noticeEntity.isPinned.desc()) + .let { + when { + sortBy == ContentSearchSortType.DATE || scoreOrNull == null -> { + it.orderBy(noticeEntity.createdAt.desc()) + } + + else -> { + it.orderBy(scoreOrNull.desc()) + } + } + }.fetch() 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 94226bfc..3e8d0409 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 @@ -4,7 +4,7 @@ import com.querydsl.core.annotations.QueryProjection import com.wafflestudio.csereal.core.notice.database.NoticeEntity import java.time.LocalDateTime -data class NoticeSearchDto @QueryProjection constructor( +data class NoticeSearchDto( val id: Long, val title: String, val createdAt: LocalDateTime?, @@ -12,6 +12,16 @@ data class NoticeSearchDto @QueryProjection constructor( val hasAttachment: Boolean, val isPrivate: Boolean ) { + @QueryProjection constructor( + id: Long, + title: String, + createdAt: LocalDateTime?, + isPinned: Boolean, + hasAttachment: Boolean, + isPrivate: Boolean, + score: Double? + ) : this(id, title, createdAt, isPinned, hasAttachment, isPrivate) + constructor(entity: NoticeEntity, hasAttachment: Boolean) : this( entity.id, entity.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 86a3cc74..47f43121 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.enums.ContentSearchSortType import com.wafflestudio.csereal.common.utils.cleanTextFromHtml import com.wafflestudio.csereal.core.notice.database.* import com.wafflestudio.csereal.core.notice.dto.* @@ -21,6 +22,7 @@ interface NoticeService { keyword: String?, pageable: Pageable, usePageBtn: Boolean, + sortBy: ContentSearchSortType, isStaff: Boolean ): NoticeSearchResponse @@ -55,9 +57,10 @@ class NoticeServiceImpl( keyword: String?, pageable: Pageable, usePageBtn: Boolean, + sortBy: ContentSearchSortType, isStaff: Boolean ): NoticeSearchResponse { - return noticeRepository.searchNotice(tag, keyword, pageable, usePageBtn, isStaff) + return noticeRepository.searchNotice(tag, keyword, pageable, usePageBtn, sortBy, 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 fe744ccc..15f43926 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,7 @@ package com.wafflestudio.csereal.core.seminar.api import com.wafflestudio.csereal.common.aop.AuthenticatedStaff +import com.wafflestudio.csereal.common.enums.ContentSearchSortType import com.wafflestudio.csereal.common.utils.getUsername import com.wafflestudio.csereal.core.seminar.dto.SeminarDto import com.wafflestudio.csereal.core.seminar.dto.SeminarSearchResponse @@ -25,6 +26,7 @@ class SeminarController( @RequestParam(required = false) keyword: String?, @RequestParam(required = false) pageNum: Int?, @RequestParam(required = false, defaultValue = "10") pageSize: Int, + @RequestParam(required = false, defaultValue = "DATE") sortBy: String, authentication: Authentication? ): ResponseEntity { val username = getUsername(authentication) @@ -36,7 +38,10 @@ class SeminarController( val usePageBtn = pageNum != null val page = pageNum ?: 1 val pageRequest = PageRequest.of(page - 1, pageSize) - return ResponseEntity.ok(seminarService.searchSeminar(keyword, pageRequest, usePageBtn, isStaff)) + + val sortType = ContentSearchSortType.fromJsonValue(sortBy) + + return ResponseEntity.ok(seminarService.searchSeminar(keyword, pageRequest, usePageBtn, sortType, 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 f7431267..978ebb19 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,6 +2,7 @@ package com.wafflestudio.csereal.core.seminar.database import com.querydsl.core.BooleanBuilder import com.querydsl.jpa.impl.JPAQueryFactory +import com.wafflestudio.csereal.common.enums.ContentSearchSortType import com.wafflestudio.csereal.common.repository.CommonRepository import com.wafflestudio.csereal.common.utils.FixedPageRequest import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService @@ -29,6 +30,7 @@ interface CustomSeminarRepository { keyword: String?, pageable: Pageable, usePageBtn: Boolean, + sortBy: ContentSearchSortType, isStaff: Boolean ): SeminarSearchResponse } @@ -43,6 +45,7 @@ class SeminarRepositoryImpl( keyword: String?, pageable: Pageable, usePageBtn: Boolean, + sortBy: ContentSearchSortType, isStaff: Boolean ): SeminarSearchResponse { val keywordBooleanBuilder = BooleanBuilder() @@ -83,11 +86,16 @@ class SeminarRepositoryImpl( total = (10 * pageable.pageSize).toLong() + 1 // 10개 페이지 고정 } - val seminarEntityList = jpaQuery - .orderBy(seminarEntity.createdAt.desc()) + val seminarEntityQuery = jpaQuery .offset(pageRequest.offset) .limit(pageRequest.pageSize.toLong()) - .fetch() + + val seminarEntityList = when { + sortBy == ContentSearchSortType.DATE || keyword.isNullOrEmpty() -> seminarEntityQuery.orderBy( + seminarEntity.createdAt.desc() + ) + else /* sortBy == RELEVANCE */ -> seminarEntityQuery + }.fetch() val seminarSearchDtoList: MutableList = mutableListOf() 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 d6237be8..625bfe5c 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.common.enums.ContentSearchSortType 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 @@ -18,6 +19,7 @@ interface SeminarService { keyword: String?, pageable: Pageable, usePageBtn: Boolean, + sortBy: ContentSearchSortType, isStaff: Boolean ): SeminarSearchResponse @@ -44,9 +46,10 @@ class SeminarServiceImpl( keyword: String?, pageable: Pageable, usePageBtn: Boolean, + sortBy: ContentSearchSortType, isStaff: Boolean ): SeminarSearchResponse { - return seminarRepository.searchSeminar(keyword, pageable, usePageBtn, isStaff) + return seminarRepository.searchSeminar(keyword, pageable, usePageBtn, sortBy, isStaff) } @Transactional From ffde429c2432f3025cf97e0391a4b809b2b5dc7f 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, 6 Apr 2024 16:19:16 +0900 Subject: [PATCH 192/214] =?UTF-8?q?CICD:=20env=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC,=20=EC=84=9C=EB=B2=84=20docker=20image=20tag?= =?UTF-8?q?=20=EB=B6=84=EB=A6=AC=20(#248)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * CICD: Seperate docker image tag of prod, dev, rename env * CICD: Change env name of db * CICD: Change env name of proxy * CICD: Remove unused latest tag --------- Co-authored-by: Junhyeong Kim --- .github/workflows/database.yaml | 12 ++++++------ .github/workflows/database_dev.yaml | 12 ++++++------ .github/workflows/deploy.yaml | 24 ++++++++++++------------ .github/workflows/deploy_dev.yaml | 26 +++++++++++++------------- .github/workflows/proxy.yaml | 8 ++++---- .github/workflows/proxy_dev.yaml | 8 ++++---- docker-compose-backend.yml | 6 +----- 7 files changed, 46 insertions(+), 50 deletions(-) diff --git a/.github/workflows/database.yaml b/.github/workflows/database.yaml index faddfc34..150baa71 100644 --- a/.github/workflows/database.yaml +++ b/.github/workflows/database.yaml @@ -22,10 +22,10 @@ jobs: - 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 "MYSQL_ROOT_PASSWORD=${{secrets.MYSQL_ROOT_PASSWORD}}" > .env_db + echo "MYSQL_USER=${{secrets.MYSQL_USER}}" >> .env_db + echo "MYSQL_PASSWORD=${{secrets.MYSQL_PASSWORD}}" >> .env_db + echo "MYSQL_DATABASE=${{secrets.MYSQL_DATABASE}}" >> .env_db - name: SCP Command to Transfer Files @@ -35,7 +35,7 @@ jobs: username: ${{secrets.SSH_USER}} port: ${{secrets.SSH_PORT}} key: ${{secrets.SSH_KEY}} - source: "docker-compose-db.yml, .env" + source: "docker-compose-db.yml, .env_db" target: "~/app" overwrite: true @@ -49,6 +49,6 @@ jobs: key: ${{secrets.SSH_KEY}} script: | cd ~/app - source .env + source .env_db docker compose -f docker-compose-db.yml down docker compose -f docker-compose-db.yml up -d diff --git a/.github/workflows/database_dev.yaml b/.github/workflows/database_dev.yaml index 37873eeb..60e1f4e3 100644 --- a/.github/workflows/database_dev.yaml +++ b/.github/workflows/database_dev.yaml @@ -22,10 +22,10 @@ jobs: - 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 "MYSQL_ROOT_PASSWORD=${{secrets.MYSQL_ROOT_PASSWORD}}" > .env_db + echo "MYSQL_USER=${{secrets.MYSQL_USER}}" >> .env_db + echo "MYSQL_PASSWORD=${{secrets.MYSQL_PASSWORD}}" >> .env_db + echo "MYSQL_DATABASE=${{secrets.MYSQL_DATABASE}}" >> .env_db - name: SCP Command to Transfer Files @@ -34,7 +34,7 @@ jobs: host: ${{secrets.SSH_HOST_DEV}} username: ${{secrets.SSH_USER}} key: ${{secrets.SSH_KEY}} - source: "docker-compose-db.yml, .env" + source: "docker-compose-db.yml, .env_db" target: "~/app" overwrite: true @@ -47,6 +47,6 @@ jobs: key: ${{secrets.SSH_KEY}} script: | cd ~/app - source .env + source .env_db 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 77796e3f..765da919 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -48,20 +48,20 @@ jobs: build-args: | PROFILE=prod tags: | - ghcr.io/wafflestudio/csereal-server/server_image:latest + ghcr.io/wafflestudio/csereal-server/server_image:prod 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 - echo "OIDC_CLIENT_SECRET=${{secrets.OIDC_CLIENT_SECRET}}" >> .env - echo "URL=${{secrets.URL}}" >> .env - echo "SLACK_TOKEN=${{secrets.SLACK_TOKEN}}" >> .env - echo "SLACK_CHANNEL=${{secrets.SLACK_CHANNEL}}" >> .env + echo "PROFILE=prod" > .env_server + echo "MYSQL_ROOT_PASSWORD=${{secrets.MYSQL_ROOT_PASSWORD}}" >> .env_server + echo "MYSQL_USER=${{secrets.MYSQL_USER}}" >> .env_server + echo "MYSQL_PASSWORD=${{secrets.MYSQL_PASSWORD}}" >> .env_server + echo "MYSQL_DATABASE=${{secrets.MYSQL_DATABASE}}" >> .env_server + echo "OIDC_CLIENT_SECRET=${{secrets.OIDC_CLIENT_SECRET}}" >> .env_server + echo "URL=${{secrets.URL}}" >> .env_server + echo "SLACK_TOKEN=${{secrets.SLACK_TOKEN}}" >> .env_server + echo "SLACK_CHANNEL=${{secrets.SLACK_CHANNEL}}" >> .env_server - name: SCP Command to Transfer Files uses: appleboy/scp-action@v0.1.4 @@ -70,7 +70,7 @@ jobs: username: ${{secrets.SSH_USER}} port: ${{secrets.SSH_PORT}} key: ${{secrets.SSH_KEY}} - source: "docker-compose-backend.yml, .env" + source: "docker-compose-backend.yml, .env_server" target: "~/app" overwrite: true - name: SSH Remote Commands @@ -82,7 +82,7 @@ jobs: key: ${{secrets.SSH_KEY}} script: | cd ~/app - source .env + source .env_server docker compose -f docker-compose-backend.yml down docker compose -f docker-compose-backend.yml pull docker compose -f docker-compose-backend.yml up -d diff --git a/.github/workflows/deploy_dev.yaml b/.github/workflows/deploy_dev.yaml index 49bfb53b..7c8f03b9 100644 --- a/.github/workflows/deploy_dev.yaml +++ b/.github/workflows/deploy_dev.yaml @@ -46,22 +46,22 @@ jobs: context: . push: true build-args: | - PROFILE=prod + PROFILE=dev tags: | - ghcr.io/wafflestudio/csereal-server/server_image:latest + ghcr.io/wafflestudio/csereal-server/server_image:dev 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=dev" >> .env - echo "OIDC_CLIENT_SECRET=${{secrets.OIDC_CLIENT_SECRET}}" >> .env - echo "URL=${{secrets.URL_DEV}}" >> .env - # echo "SLACK_TOKEN=${{secrets.SLACK_TOKEN}}" >> .env - # echo "SLACK_CHANNEL=${{secrets.SLACK_CHANNEL}}" >> .env + echo "PROFILE=dev" > .env_server + echo "MYSQL_ROOT_PASSWORD=${{secrets.MYSQL_ROOT_PASSWORD}}" >> .env_server + echo "MYSQL_USER=${{secrets.MYSQL_USER}}" >> .env_server + echo "MYSQL_PASSWORD=${{secrets.MYSQL_PASSWORD}}" >> .env_server + echo "MYSQL_DATABASE=${{secrets.MYSQL_DATABASE}}" >> .env_server + echo "OIDC_CLIENT_SECRET=${{secrets.OIDC_CLIENT_SECRET}}" >> .env_server + echo "URL=${{secrets.URL_DEV}}" >> .env_server + # echo "SLACK_TOKEN=${{secrets.SLACK_TOKEN}}" >> .env_server + # echo "SLACK_CHANNEL=${{secrets.SLACK_CHANNEL}}" >> .env_server - name: SCP Command to Transfer Files uses: appleboy/scp-action@v0.1.4 @@ -69,7 +69,7 @@ jobs: host: ${{secrets.SSH_HOST_DEV}} username: ${{secrets.SSH_USER}} key: ${{secrets.SSH_KEY}} - source: "docker-compose-backend.yml, .env" + source: "docker-compose-backend.yml, .env_server" target: "~/app" overwrite: true - name: SSH Remote Commands @@ -80,7 +80,7 @@ jobs: key: ${{secrets.SSH_KEY}} script: | cd ~/app - source .env + source .env_server docker-compose -f docker-compose-backend.yml down docker-compose -f docker-compose-backend.yml pull docker-compose -f docker-compose-backend.yml up -d diff --git a/.github/workflows/proxy.yaml b/.github/workflows/proxy.yaml index cf1f509b..f86188b1 100644 --- a/.github/workflows/proxy.yaml +++ b/.github/workflows/proxy.yaml @@ -19,8 +19,8 @@ jobs: - name: Create .env file run: | - echo "URL=${{secrets.URL}}" > .env - echo "LOCAL_IP=${{secrets.LOCAL_IP}}" >> .env + echo "URL=${{secrets.URL}}" > .env_proxy + echo "LOCAL_IP=${{secrets.LOCAL_IP}}" >> .env_proxy - name: SCP Command to Transfer Files @@ -30,7 +30,7 @@ jobs: username: ${{secrets.SSH_USER}} port: ${{secrets.SSH_PORT}} key: ${{secrets.SSH_KEY}} - source: "docker-compose-caddy.yml, .env, caddy/Caddyfile" + source: "docker-compose-caddy.yml, .env_proxy, caddy/Caddyfile" target: "~/proxy" overwrite: true @@ -44,6 +44,6 @@ jobs: key: ${{secrets.SSH_KEY}} script: | cd ~/proxy - source .env + source .env_proxy docker compose -f docker-compose-caddy.yml down docker compose -f docker-compose-caddy.yml up -d diff --git a/.github/workflows/proxy_dev.yaml b/.github/workflows/proxy_dev.yaml index f1e8d553..f22e1717 100644 --- a/.github/workflows/proxy_dev.yaml +++ b/.github/workflows/proxy_dev.yaml @@ -19,8 +19,8 @@ jobs: - name: Create .env file run: | - echo "URL=${{secrets.URL_DEV}}" > .env - echo "LOCAL_IP=${{secrets.LOCAL_IP_DEV}}" >> .env + echo "URL=${{secrets.URL_DEV}}" > .env_proxy + echo "LOCAL_IP=${{secrets.LOCAL_IP_DEV}}" >> .env_proxy - name: SCP Command to Transfer Files @@ -29,7 +29,7 @@ jobs: host: ${{secrets.SSH_HOST_DEV}} username: ${{secrets.SSH_USER}} key: ${{secrets.SSH_KEY}} - source: "docker-compose-caddy.yml, .env, caddy/Caddyfile" + source: "docker-compose-caddy.yml, .env_proxy, caddy/Caddyfile" target: "~/proxy" overwrite: true @@ -42,6 +42,6 @@ jobs: key: ${{secrets.SSH_KEY}} script: | cd ~/proxy - source .env + source .env_proxy docker-compose -f docker-compose-caddy.yml down docker-compose -f docker-compose-caddy.yml up -d diff --git a/docker-compose-backend.yml b/docker-compose-backend.yml index ffba533b..f8469c0d 100644 --- a/docker-compose-backend.yml +++ b/docker-compose-backend.yml @@ -1,10 +1,6 @@ services: green: container_name: csereal_server_green - build: - context: ./ - args: - PROFILE: ${PROFILE} ports: - 8080:8080 volumes: @@ -22,4 +18,4 @@ services: extra_hosts: - host.docker.internal:host-gateway restart: always - image: ghcr.io/wafflestudio/csereal-server/server_image:latest + image: "ghcr.io/wafflestudio/csereal-server/server_image:${PROFILE}" From f80056dfbb7ad58223a90f487db88f8c04fbc3d0 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Sat, 6 Apr 2024 16:22:28 +0900 Subject: [PATCH 193/214] =?UTF-8?q?feat:=20=EA=B0=9C=EC=9D=B8=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EB=8F=99=EC=9D=98=20=EC=97=AC=EB=B6=80=20=EA=B8=B0?= =?UTF-8?q?=EB=A1=9D=20(#250)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 우혁준 (HyukJoon Woo) --- .../csereal/core/reservation/database/ReservationEntity.kt | 7 +++++-- .../csereal/core/reservation/dto/ReserveRequest.kt | 1 + .../csereal/core/reservation/service/ReservationService.kt | 4 ++++ 3 files changed, 10 insertions(+), 2 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 95a7e22f..84f3db28 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,9 @@ class ReservationEntity( val professor: String, val recurringWeeks: Int = 1, - val recurrenceId: UUID + val recurrenceId: UUID, + + val agreed: Boolean ) : BaseTimeEntity() { @@ -65,7 +67,8 @@ class ReservationEntity( endTime = end, professor = reserveRequest.professor, recurringWeeks = reserveRequest.recurringWeeks, - recurrenceId = recurrenceId + recurrenceId = recurrenceId, + agreed = reserveRequest.agreed ) } } 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 b3e0655e..83e94c62 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 @@ -11,5 +11,6 @@ data class ReserveRequest( val purpose: String, val startTime: LocalDateTime, val endTime: LocalDateTime, + val agreed: Boolean, 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 b504d659..780f574a 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 @@ -32,6 +32,10 @@ class ReservationServiceImpl( ) : ReservationService { override fun reserveRoom(reserveRequest: ReserveRequest): List { + if (!reserveRequest.agreed) { + throw CserealException.Csereal400("Policy Not Agreed") + } + val user = RequestContextHolder.getRequestAttributes()?.getAttribute( "loggedInUser", RequestAttributes.SCOPE_REQUEST From 5a9a8b4bf0ac5b853df1f7b4ee4e4e0ee3366fe9 Mon Sep 17 00:00:00 2001 From: huGgW Date: Sat, 6 Apr 2024 19:01:00 +0900 Subject: [PATCH 194/214] Fix: fix merge conflict --- .github/workflows/proxy.yaml | 8 ++++---- .github/workflows/proxy_dev.yaml | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/proxy.yaml b/.github/workflows/proxy.yaml index f86188b1..cf1f509b 100644 --- a/.github/workflows/proxy.yaml +++ b/.github/workflows/proxy.yaml @@ -19,8 +19,8 @@ jobs: - name: Create .env file run: | - echo "URL=${{secrets.URL}}" > .env_proxy - echo "LOCAL_IP=${{secrets.LOCAL_IP}}" >> .env_proxy + echo "URL=${{secrets.URL}}" > .env + echo "LOCAL_IP=${{secrets.LOCAL_IP}}" >> .env - name: SCP Command to Transfer Files @@ -30,7 +30,7 @@ jobs: username: ${{secrets.SSH_USER}} port: ${{secrets.SSH_PORT}} key: ${{secrets.SSH_KEY}} - source: "docker-compose-caddy.yml, .env_proxy, caddy/Caddyfile" + source: "docker-compose-caddy.yml, .env, caddy/Caddyfile" target: "~/proxy" overwrite: true @@ -44,6 +44,6 @@ jobs: key: ${{secrets.SSH_KEY}} script: | cd ~/proxy - source .env_proxy + source .env docker compose -f docker-compose-caddy.yml down docker compose -f docker-compose-caddy.yml up -d diff --git a/.github/workflows/proxy_dev.yaml b/.github/workflows/proxy_dev.yaml index f22e1717..f1e8d553 100644 --- a/.github/workflows/proxy_dev.yaml +++ b/.github/workflows/proxy_dev.yaml @@ -19,8 +19,8 @@ jobs: - name: Create .env file run: | - echo "URL=${{secrets.URL_DEV}}" > .env_proxy - echo "LOCAL_IP=${{secrets.LOCAL_IP_DEV}}" >> .env_proxy + echo "URL=${{secrets.URL_DEV}}" > .env + echo "LOCAL_IP=${{secrets.LOCAL_IP_DEV}}" >> .env - name: SCP Command to Transfer Files @@ -29,7 +29,7 @@ jobs: host: ${{secrets.SSH_HOST_DEV}} username: ${{secrets.SSH_USER}} key: ${{secrets.SSH_KEY}} - source: "docker-compose-caddy.yml, .env_proxy, caddy/Caddyfile" + source: "docker-compose-caddy.yml, .env, caddy/Caddyfile" target: "~/proxy" overwrite: true @@ -42,6 +42,6 @@ jobs: key: ${{secrets.SSH_KEY}} script: | cd ~/proxy - source .env_proxy + source .env docker-compose -f docker-compose-caddy.yml down docker-compose -f docker-compose-caddy.yml up -d From 641b2e0822c4242746d8cb631da1496bad36cc47 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, 7 Apr 2024 12:12:07 +0900 Subject: [PATCH 195/214] Fix: give 0.0 to score when keyword is null or empty on notice. (#254) --- .../csereal/core/notice/database/NoticeRepository.kt | 4 +++- .../wafflestudio/csereal/core/notice/dto/NoticeSearchDto.kt | 2 +- 2 files changed, 4 insertions(+), 2 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 f2615c8f..0277a82f 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,6 +2,7 @@ package com.wafflestudio.csereal.core.notice.database import com.querydsl.core.BooleanBuilder import com.querydsl.core.types.Projections +import com.querydsl.core.types.dsl.Expressions import com.querydsl.jpa.impl.JPAQueryFactory import com.wafflestudio.csereal.common.enums.ContentSearchSortType import com.wafflestudio.csereal.common.repository.CommonRepository @@ -142,7 +143,8 @@ class NoticeRepositoryImpl( noticeEntity.isPinned, noticeEntity.attachments.isNotEmpty, noticeEntity.isPrivate, - scoreOrNull + // if scoreOrNull is null, put 0.0 + scoreOrNull ?: Expressions.numberTemplate(Double::class.javaObjectType, "0.0") ) ).from(noticeEntity) .leftJoin(noticeTagEntity).on(noticeTagEntity.notice.eq(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 3e8d0409..8dffde59 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 @@ -19,7 +19,7 @@ data class NoticeSearchDto( isPinned: Boolean, hasAttachment: Boolean, isPrivate: Boolean, - score: Double? + score: Double ) : this(id, title, createdAt, isPinned, hasAttachment, isPrivate) constructor(entity: NoticeEntity, hasAttachment: Boolean) : this( From d88d45ffbf18cf2d34c6a3c2c1a23a5e147cc8e1 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, 7 Apr 2024 13:50:14 +0900 Subject: [PATCH 196/214] CICD: Disable auto https redirect. (#256) --- caddy/Caddyfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/caddy/Caddyfile b/caddy/Caddyfile index 11f81c5a..c676e508 100644 --- a/caddy/Caddyfile +++ b/caddy/Caddyfile @@ -1,3 +1,6 @@ +{ + auto_https disable_redirects +} {$URL} { # Frontend reverse_proxy host.docker.internal:3000 From 53ca80877b0f4357e45b7c12ae7796256b0a96c0 Mon Sep 17 00:00:00 2001 From: huGgW Date: Sun, 7 Apr 2024 14:06:45 +0900 Subject: [PATCH 197/214] CICD: Revert to enable redirect --- caddy/Caddyfile | 3 --- 1 file changed, 3 deletions(-) diff --git a/caddy/Caddyfile b/caddy/Caddyfile index c676e508..11f81c5a 100644 --- a/caddy/Caddyfile +++ b/caddy/Caddyfile @@ -1,6 +1,3 @@ -{ - auto_https disable_redirects -} {$URL} { # Frontend reverse_proxy host.docker.internal:3000 From aa2eba9d71824c65dbcfe5d373a9e86d4c0dba2e Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Sun, 7 Apr 2024 14:15:26 +0900 Subject: [PATCH 198/214] fix: sort labs (#258) --- .../csereal/core/research/service/ResearchService.kt | 8 ++++++++ 1 file changed, 8 insertions(+) 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 4d2cfa65..5070294b 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.common.enums.LanguageType +import com.wafflestudio.csereal.common.utils.startsWithEnglish import com.wafflestudio.csereal.core.member.database.ProfessorRepository import com.wafflestudio.csereal.core.research.database.* import com.wafflestudio.csereal.core.research.dto.* @@ -41,6 +42,7 @@ interface ResearchService { mainImage: MultipartFile?, attachments: List? ): ResearchDto + fun migrateLabPdf(labId: Long, pdf: MultipartFile?): LabDto } @@ -230,6 +232,12 @@ class ResearchServiceImpl( val attachmentResponse = attachmentService.createOneAttachmentResponse(it.pdf) LabDto.of(it, attachmentResponse) + }.sortedWith { a, b -> + when { + startsWithEnglish(a.name) && !startsWithEnglish(b.name) -> 1 + !startsWithEnglish(a.name) && startsWithEnglish(b.name) -> -1 + else -> a.name.compareTo(b.name) + } } return labs From 7bcb3d68dd1f1fdfe7d8c1b358ca9b738443a141 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Sun, 7 Apr 2024 14:45:40 +0900 Subject: [PATCH 199/214] =?UTF-8?q?fix:=20idsnucse=20prod=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EB=B6=84=EB=A6=AC=20(#260)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: idsnucse prod * fix: 환경변수 이름 --- .github/workflows/deploy_dev.yaml | 4 ++-- src/main/resources/application.yaml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy_dev.yaml b/.github/workflows/deploy_dev.yaml index bbb8d3bb..2f59d548 100644 --- a/.github/workflows/deploy_dev.yaml +++ b/.github/workflows/deploy_dev.yaml @@ -58,7 +58,7 @@ jobs: echo "MYSQL_USER=${{secrets.MYSQL_USER}}" >> .env echo "MYSQL_PASSWORD=${{secrets.MYSQL_PASSWORD}}" >> .env echo "MYSQL_DATABASE=${{secrets.MYSQL_DATABASE}}" >> .env - echo "OIDC_CLIENT_SECRET=${{secrets.OIDC_CLIENT_SECRET}}" >> .env + echo "OIDC_CLIENT_SECRET_DEV=${{secrets.OIDC_CLIENT_SECRET_DEV}}" >> .env echo "URL=${{secrets.URL_DEV}}" >> .env # echo "SLACK_TOKEN=${{secrets.SLACK_TOKEN}}" >> .env # echo "SLACK_CHANNEL=${{secrets.SLACK_CHANNEL}}" >> .env @@ -78,7 +78,7 @@ jobs: host: ${{secrets.SSH_HOST_DEV}} username: ${{secrets.SSH_USER}} key: ${{secrets.SSH_KEY}} - script: | + script: | cd ~/app source .env docker-compose -f docker-compose-backend.yml down diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 999e0bd2..4a40f3e2 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -65,7 +65,7 @@ spring: registration: idsnucse: client-id: cse-waffle-dev - client-secret: ${OIDC_CLIENT_SECRET} + client-secret: ${OIDC_CLIENT_SECRET_DEV} authorization-grant-type: authorization_code scope: openid, profile, email redirect-uri: http://localhost:8080/api/v1/login/oauth2/code/idsnucse @@ -93,7 +93,7 @@ spring: client: registration: idsnucse: - client-id: cse-waffle-dev + client-id: cse-waffle-prod client-secret: ${OIDC_CLIENT_SECRET} authorization-grant-type: authorization_code scope: openid, profile, email @@ -134,7 +134,7 @@ spring: registration: idsnucse: client-id: cse-waffle-dev - client-secret: ${OIDC_CLIENT_SECRET} + client-secret: ${OIDC_CLIENT_SECRET_DEV} authorization-grant-type: authorization_code scope: openid, profile, email redirect-uri: https://${URL}/api/v1/login/oauth2/code/idsnucse From db83a77552cd5455755ef3a9e86c0346866c683e 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, 7 Apr 2024 14:57:14 +0900 Subject: [PATCH 200/214] Config: Disable migrate on profile prod. (#262) * Config: Disable migrate on profile prod. * Config: Disable refresh search on profile prod. --- .../wafflestudio/csereal/core/about/api/AboutController.kt | 7 +++++++ .../csereal/core/academics/api/AcademicsController.kt | 5 +++++ .../csereal/core/admissions/api/AdmissionsController.kt | 2 ++ .../csereal/core/conference/api/ConferenceController.kt | 2 ++ .../wafflestudio/csereal/core/main/api/MainController.kt | 2 ++ .../csereal/core/member/api/ProfessorController.kt | 3 +++ .../csereal/core/member/api/StaffController.kt | 3 +++ .../csereal/core/research/api/ResearchController.kt | 5 +++++ 8 files changed, 29 insertions(+) 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 85d77a47..c4f00fd8 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 com.wafflestudio.csereal.core.about.dto.FutureCareersRequest import com.wafflestudio.csereal.core.about.service.AboutService import jakarta.validation.Valid import jakarta.validation.constraints.Positive +import org.springframework.context.annotation.Profile import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile @@ -101,6 +102,7 @@ class AboutController( amount ) + @Profile("!prod") @PostMapping("/migrate") fun migrateAbout( @RequestBody requestList: List @@ -108,6 +110,7 @@ class AboutController( return ResponseEntity.ok(aboutService.migrateAbout(requestList)) } + @Profile("!prod") @PostMapping("/future-careers/migrate") fun migrateFutureCareers( @RequestBody request: FutureCareersRequest @@ -115,6 +118,7 @@ class AboutController( return ResponseEntity.ok(aboutService.migrateFutureCareers(request)) } + @Profile("!prod") @PostMapping("/student-clubs/migrate") fun migrateStudentClubs( @RequestBody requestList: List @@ -122,6 +126,7 @@ class AboutController( return ResponseEntity.ok(aboutService.migrateStudentClubs(requestList)) } + @Profile("!prod") @PostMapping("/facilities/migrate") fun migrateFacilities( @RequestBody requestList: List @@ -129,6 +134,7 @@ class AboutController( return ResponseEntity.ok(aboutService.migrateFacilities(requestList)) } + @Profile("!prod") @PostMapping("/directions/migrate") fun migrateDirections( @RequestBody requestList: List @@ -136,6 +142,7 @@ class AboutController( return ResponseEntity.ok(aboutService.migrateDirections(requestList)) } + @Profile("!prod") @PatchMapping("/migrateImage/{aboutId}") fun migrateAboutImageAndAttachment( @PathVariable aboutId: Long, 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 45b2090c..9b1ea36d 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 @@ -8,6 +8,7 @@ import com.wafflestudio.csereal.core.academics.dto.ScholarshipDto import com.wafflestudio.csereal.core.academics.service.AcademicsSearchService import jakarta.validation.Valid import jakarta.validation.constraints.Positive +import org.springframework.context.annotation.Profile import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile @@ -120,6 +121,7 @@ class AcademicsController( return ResponseEntity.ok(academicsService.readScholarship(scholarshipId)) } + @Profile("!prod") @PostMapping("/{studentType}/{postType}/migrate") fun migrateAcademicsDetail( @PathVariable studentType: String, @@ -131,6 +133,7 @@ class AcademicsController( ) } + @Profile("!prod") @PostMapping("/course/migrate/{studentType}") fun migrateCourses( @PathVariable studentType: String, @@ -139,6 +142,7 @@ class AcademicsController( return ResponseEntity.ok(academicsService.migrateCourses(studentType, requestList)) } + @Profile("!prod") @PostMapping("/{studentType}/scholarshipDetail/migrate") fun migrateScholarshipDetail( @PathVariable studentType: String, @@ -149,6 +153,7 @@ class AcademicsController( ) } + @Profile("!prod") @PatchMapping("/migrateAttachment/{academicsId}") fun migrateAcademicsDetailAttachments( @PathVariable academicsId: Long, 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 d4cc6d36..546c17a8 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,6 +10,7 @@ import com.wafflestudio.csereal.core.admissions.type.AdmissionsMainType import com.wafflestudio.csereal.core.admissions.type.AdmissionsPostType import jakarta.validation.Valid import jakarta.validation.constraints.Positive +import org.springframework.context.annotation.Profile import org.springframework.web.bind.annotation.* @RequestMapping("/api/v1/admissions") @@ -70,6 +71,7 @@ class AdmissionsController( amount ) + @Profile("!prod") @PostMapping("/migrate") fun migrateAdmissions( @RequestBody reqList: List<@Valid AdmissionMigrateElem> 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 c6f5915e..022a817f 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 @@ -5,6 +5,7 @@ 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.conference.service.ConferenceService +import org.springframework.context.annotation.Profile import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* @@ -27,6 +28,7 @@ class ConferenceController( return ResponseEntity.ok(conferenceService.modifyConferences(conferenceModifyRequest)) } + @Profile("!prod") @PostMapping("/migrate") fun migrateConferences( @RequestBody requestList: List 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 33a533d2..81ac0a52 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 @@ -2,6 +2,7 @@ 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.context.annotation.Profile import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController @@ -16,6 +17,7 @@ class MainController( return mainService.readMain() } + @Profile("!prod") @GetMapping("/search/refresh") fun refreshSearches() { mainService.refreshSearch() 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 7ac5a09d..1cc860e0 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 @@ -5,6 +5,7 @@ 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.context.annotation.Profile import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile @@ -62,6 +63,7 @@ class ProfessorController( return ResponseEntity.ok().build() } + @Profile("!prod") @PostMapping("/migrate") fun migrateProfessors( @RequestBody requestList: List @@ -69,6 +71,7 @@ class ProfessorController( return ResponseEntity.ok(professorService.migrateProfessors(requestList)) } + @Profile("!prod") @PatchMapping("/migrateImage/{professorId}") fun migrateProfessorImage( @PathVariable professorId: Long, 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 6f298f94..d31a1ca2 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,6 +4,7 @@ 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 +import org.springframework.context.annotation.Profile import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile @@ -51,6 +52,7 @@ class StaffController( return ResponseEntity.ok().build() } + @Profile("!prod") @PostMapping("/migrate") fun migrateStaff( @RequestBody requestList: List @@ -58,6 +60,7 @@ class StaffController( return ResponseEntity.ok(staffService.migrateStaff(requestList)) } + @Profile("!prod") @PatchMapping("/migrateImage/{staffId}") fun migrateStaffImage( @PathVariable staffId: Long, 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 dd00c319..cc3b82fd 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 @@ -10,6 +10,7 @@ import com.wafflestudio.csereal.core.research.service.ResearchSearchService import com.wafflestudio.csereal.core.research.service.ResearchService import jakarta.validation.Valid import jakarta.validation.constraints.Positive +import org.springframework.context.annotation.Profile import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile @@ -99,6 +100,7 @@ class ResearchController( return ResponseEntity.ok(researchService.updateLab(labId, request, pdf)) } + @Profile("!prod") @PostMapping("/migrate") fun migrateResearchDetail( @RequestBody requestList: List @@ -106,6 +108,7 @@ class ResearchController( return ResponseEntity.ok(researchService.migrateResearchDetail(requestList)) } + @Profile("!prod") @PostMapping("/lab/migrate") fun migrateLabs( @RequestBody requestList: List @@ -113,6 +116,7 @@ class ResearchController( return ResponseEntity.ok(researchService.migrateLabs(requestList)) } + @Profile("!prod") @PatchMapping("/migrateImageAndAttachments/{researchId}") fun migrateResearchDetailImageAndAttachments( @PathVariable researchId: Long, @@ -128,6 +132,7 @@ class ResearchController( ) } + @Profile("!prod") @PatchMapping("/lab/migratePdf/{labId}") fun migrateLabPdf( @PathVariable labId: Long, From e15512d9e2f5fb52034bb596d54d19e33116a3c6 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Thu, 11 Apr 2024 11:50:32 +0900 Subject: [PATCH 201/214] fix: adjust max thread and pool size (#264) --- src/main/resources/application.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 4a40f3e2..46f814e8 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -7,7 +7,7 @@ spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver hikari: - maximum-pool-size: 24 + maximum-pool-size: 65 jpa: properties: hibernate: @@ -21,7 +21,7 @@ spring: server: tomcat: threads: - max: 16 + max: 32 servlet: session: timeout: 7200 # 2시간 From 3213a13d4a40642e2900079c729323b1cca54de5 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, 11 Apr 2024 17:17:39 +0900 Subject: [PATCH 202/214] =?UTF-8?q?Feat:=20=EA=B2=80=EC=83=89=20refresh=20?= =?UTF-8?q?api=20=EB=A1=9C=EC=BB=AC=EC=97=90=EC=84=9C=20=EC=A0=91=EA=B7=BC?= =?UTF-8?q?=20=EA=B0=80=EB=8A=A5=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20(#265)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat: Change refresh search api available at prod profile * Config: Block refresh api from proxy. * Config: Remove unused reverse proxy of swagger, api docs. --- caddy/Caddyfile | 6 +----- .../wafflestudio/csereal/core/main/api/MainController.kt | 2 -- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/caddy/Caddyfile b/caddy/Caddyfile index 11f81c5a..e9d2a891 100644 --- a/caddy/Caddyfile +++ b/caddy/Caddyfile @@ -3,7 +3,7 @@ reverse_proxy host.docker.internal:3000 @backend_denied { - path /swagger-ui/* /api-docs/* + path /swagger-ui/* /api-docs/* /api/v1/search/refresh not remote_ip {$LOCAL_IP} } abort @backend_denied @@ -16,8 +16,4 @@ # Login reverse_proxy /oauth2/authorization/idsnucse host.docker.internal:8080 - - # Swagger - reverse_proxy /swagger-ui/* host.docker.internal:8080 - reverse_proxy /api-docs/* host.docker.internal:8080 } 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 81ac0a52..33a533d2 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 @@ -2,7 +2,6 @@ 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.context.annotation.Profile import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController @@ -17,7 +16,6 @@ class MainController( return mainService.readMain() } - @Profile("!prod") @GetMapping("/search/refresh") fun refreshSearches() { mainService.refreshSearch() From f34f257d106904b0dd25b962b2432c5b41965f36 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Thu, 11 Apr 2024 22:11:26 +0900 Subject: [PATCH 203/214] fix: sort seminar (#267) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 우혁준 (HyukJoon Woo) --- .../csereal/core/seminar/database/SeminarRepository.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 978ebb19..12211f0d 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 @@ -20,6 +20,7 @@ interface SeminarRepository : JpaRepository, CustomSeminarR fun findFirstByIsDeletedFalseAndIsPrivateFalseAndCreatedAtLessThanOrderByCreatedAtDesc( timestamp: LocalDateTime ): SeminarEntity? + fun findFirstByIsDeletedFalseAndIsPrivateFalseAndCreatedAtGreaterThanOrderByCreatedAtAsc( timestamp: LocalDateTime ): SeminarEntity? @@ -92,8 +93,9 @@ class SeminarRepositoryImpl( val seminarEntityList = when { sortBy == ContentSearchSortType.DATE || keyword.isNullOrEmpty() -> seminarEntityQuery.orderBy( - seminarEntity.createdAt.desc() + seminarEntity.startDate.desc() ) + else /* sortBy == RELEVANCE */ -> seminarEntityQuery }.fetch() From dff39af8c54c9370299bbcb61d1eef994cb13024 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Thu, 11 Apr 2024 22:11:44 +0900 Subject: [PATCH 204/214] =?UTF-8?q?fix:=20=EA=B5=90=EC=96=91=20=EA=B5=90?= =?UTF-8?q?=EA=B3=BC=EA=B3=BC=EC=A0=95=20=EB=B3=80=EA=B2=BD=20=EB=82=B4?= =?UTF-8?q?=EC=97=AD=20=EC=82=AD=EC=A0=9C=20(#268)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/GeneralStudiesRequirementsPageResponse.kt | 3 --- .../core/academics/service/AcademicsService.kt | 13 ++++++------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GeneralStudiesRequirementsPageResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GeneralStudiesRequirementsPageResponse.kt index 0851527d..f76e2e42 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GeneralStudiesRequirementsPageResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GeneralStudiesRequirementsPageResponse.kt @@ -4,18 +4,15 @@ import com.wafflestudio.csereal.core.academics.database.AcademicsEntity class GeneralStudiesRequirementsPageResponse( val overview: String, - val subjectChanges: String, val generalStudies: List ) { companion object { fun of( overview: AcademicsEntity, - subjectChanges: AcademicsEntity, generalStudies: List ): GeneralStudiesRequirementsPageResponse { return GeneralStudiesRequirementsPageResponse( overview = overview.description, - subjectChanges = subjectChanges.description, generalStudies = generalStudies.map { GeneralStudiesDto.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 6874f169..c734d5c3 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 @@ -26,6 +26,7 @@ interface AcademicsService { studentType: String, postType: String ): List + fun readGeneralStudiesRequirements(language: String): GeneralStudiesRequirementsPageResponse fun readDegreeRequirements(language: String): DegreeRequirementsPageResponse fun createCourse( @@ -33,12 +34,14 @@ interface AcademicsService { request: CourseDto, attachments: List? ): CourseDto + fun readAllCourses(language: String, studentType: String): List fun readCourse(language: String, name: String): CourseDto fun createScholarshipDetail( studentType: String, request: ScholarshipDto ): ScholarshipDto + fun readAllScholarship(language: String, studentType: String): ScholarshipPageResponse fun readScholarship(scholarshipId: Long): ScholarshipDto fun migrateAcademicsDetail( @@ -46,11 +49,13 @@ interface AcademicsService { postType: String, requestList: List ): List + fun migrateCourses(studentType: String, requestList: List): List fun migrateScholarshipDetail( studentType: String, requestList: List ): List + fun migrateAcademicsDetailAttachments( academicsId: Long, attachments: List? @@ -149,19 +154,13 @@ class AcademicsServiceImpl( AcademicsPostType.GENERAL_STUDIES_REQUIREMENTS, null ) - val subjectChanges = - academicsRepository.findByLanguageAndStudentTypeAndPostType( - enumLanguageType, - AcademicsStudentType.UNDERGRADUATE, - AcademicsPostType.GENERAL_STUDIES_REQUIREMENTS_SUBJECT_CHANGES - ) val generalStudiesEntity = academicsRepository.findAllByLanguageAndStudentTypeAndPostTypeOrderByYearDesc( enumLanguageType, AcademicsStudentType.UNDERGRADUATE, AcademicsPostType.GENERAL_STUDIES_REQUIREMENTS ).filter { academicsEntity -> academicsEntity.year != null } - return GeneralStudiesRequirementsPageResponse.of(overview, subjectChanges, generalStudiesEntity) + return GeneralStudiesRequirementsPageResponse.of(overview, generalStudiesEntity) } @Transactional(readOnly = true) From dc3c400cce55408ebda3fc73e736820ee2effaf3 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, 11 Apr 2024 22:46:09 +0900 Subject: [PATCH 205/214] =?UTF-8?q?Config:=20=EB=A1=9C=EA=B7=B8=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=A0=80=EC=9E=A5=20=EC=B6=94=EA=B0=80,?= =?UTF-8?q?=20=ED=95=AD=EB=AA=A9=20=EC=B6=94=EA=B0=80=20(#266)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Config: Add async file log appender for dev, prod env * Config: Set log to show servlet, security, sql info for all profile. * CICD: Mount log directory. * Fix: Fix property * Fix: Extract and set log file name and base property for all profiles. --- docker-compose-backend.yml | 1 + src/main/resources/application.yaml | 44 ++++++++++++++++----------- src/main/resources/logback-spring.xml | 29 ++++++++++++++++-- 3 files changed, 55 insertions(+), 19 deletions(-) diff --git a/docker-compose-backend.yml b/docker-compose-backend.yml index f8469c0d..287f0108 100644 --- a/docker-compose-backend.yml +++ b/docker-compose-backend.yml @@ -6,6 +6,7 @@ services: volumes: - ./cse-files:/app/cse-files - ./files:/app/files + - ./logs:/app/logs environment: SPRING_DATASOURCE_URL: "jdbc:mysql://host.docker.internal:3306/${MYSQL_DATABASE}?serverTimezone=Asia/Seoul&useSSL=false&allowPublicKeyRetrieval=true" diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 46f814e8..835e2c45 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -9,6 +9,7 @@ spring: hikari: maximum-pool-size: 65 jpa: + show-sql: true properties: hibernate: dialect: com.wafflestudio.csereal.common.config.MySQLDialectCustom @@ -47,6 +48,19 @@ endpoint: login-page: http://localhost:8080 + +logging: + level: + org: + springframework: + security: DEBUG + web: + servlet: DEBUG + +customlog: + file: + name: "csereal-backend" + --- spring: config.activate.on-profile: local @@ -57,7 +71,6 @@ spring: jpa: hibernate: ddl-auto: update - show-sql: true open-in-view: false security: oauth2: @@ -74,12 +87,9 @@ spring: issuer-uri: https://id.snucse.org/o jwk-set-uri: https://id.snucse.org/o/jwks -logging: - level: - # default: INFO - org: - springframework: - security: DEBUG +customlog: + file: + base: "./logs" --- spring: @@ -120,13 +130,16 @@ slack: token: ${SLACK_TOKEN} channel: ${SLACK_CHANNEL} +customlog: + file: + base: "/app/logs" + --- spring: config.activate.on-profile: dev jpa: hibernate: ddl-auto: update - show-sql: true open-in-view: false security: oauth2: @@ -156,6 +169,9 @@ endpoint: login-page: https://${URL} +customlog: + file: + base: "./logs" --- spring: config.activate.on-profile: test @@ -167,7 +183,6 @@ spring: jpa: database: h2 database-platform: org.hibernate.dialect.H2Dialect - show-sql: true open-in-view: false hibernate: ddl-auto: create-drop @@ -177,11 +192,6 @@ spring: h2: console: enabled: true - -logging: - level: - # default: INFO - org: - springframework: - security: DEBUG - +customlog: + file: + base: "./logs" diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml index d7d3352b..cb4d3e48 100644 --- a/src/main/resources/logback-spring.xml +++ b/src/main/resources/logback-spring.xml @@ -11,11 +11,10 @@ + - - @@ -36,6 +35,30 @@ + + + + + + ${LOG_FILE_BASE}/${LOG_FILE_NAME}.log + + ${FILE_LOG_PATTERN} + ${FILE_LOG_CHARSET} + + + ${LOG_FILE_BASE}/${LOG_FILE_NAME}_%d{yyyy-MM-dd}.log + false + 30 + + + + + + 5000 + + + + @@ -45,6 +68,7 @@ + @@ -52,6 +76,7 @@ + From c216c8554141536e587f84a547fe9cd308ee9733 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Thu, 11 Apr 2024 23:02:09 +0900 Subject: [PATCH 206/214] Update docker-compose-backend.yml (#269) --- docker-compose-backend.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose-backend.yml b/docker-compose-backend.yml index 287f0108..f0ed034e 100644 --- a/docker-compose-backend.yml +++ b/docker-compose-backend.yml @@ -13,6 +13,7 @@ services: SPRING_DATASOURCE_USERNAME: ${MYSQL_USER} SPRING_DATASOURCE_PASSWORD: ${MYSQL_PASSWORD} OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET} + OIDC_CLIENT_SECRET_DEV: ${OIDC_CLIENT_SECRET_DEV} URL: ${URL} SLACK_TOKEN: ${SLACK_TOKEN} SLACK_CHANNEL: ${SLACK_CHANNEL} From 549178cd3c1e67d6c3f3b0b4461fba2f24cec537 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Fri, 12 Apr 2024 15:45:49 +0900 Subject: [PATCH 207/214] fix: subtract 9 hours to start to convert it to UTC (#270) --- .../csereal/core/reservation/api/ReservationController.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e053955c..12264c60 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 @@ -48,7 +48,7 @@ class ReservationController( @RequestParam month: Int, @RequestParam day: Int ): ResponseEntity> { - val start = LocalDateTime.of(year, month, day, 0, 0) + val start = LocalDateTime.of(year, month, day, 0, 0).minusHours(9) val end = start.plusDays(7) return ResponseEntity.ok(reservationService.getRoomReservationsBetween(roomId, start, end)) } From ec16850e5db2a25edb2a20e95b456734e4ae68c5 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Tue, 16 Apr 2024 21:20:08 +0900 Subject: [PATCH 208/214] =?UTF-8?q?fix:=20readDegreeRequirements=20respons?= =?UTF-8?q?e=20=ED=96=89=EC=A0=95=EC=8B=A4=20=EC=9A=94=EA=B5=AC=EC=82=AC?= =?UTF-8?q?=ED=95=AD=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?(#272)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: readDegreeRequirements response 행정실 요구사항에 맞게 수정 * fix: 주석 삭제 --- .../dto/DegreeRequirementsPageResponse.kt | 7 ++++--- .../core/academics/service/AcademicsService.kt | 14 +++----------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsPageResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsPageResponse.kt index 3fdfecb1..c97e3186 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsPageResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsPageResponse.kt @@ -1,16 +1,17 @@ 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 DegreeRequirementsPageResponse( val description: String, - val yearList: List + val attachments: List ) { companion object { - fun of(entity: AcademicsEntity, yearList: List) = entity.run { + fun of(entity: AcademicsEntity, attachments: List) = entity.run { DegreeRequirementsPageResponse( description = this.description, - yearList = yearList + 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 c734d5c3..8cda491f 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 @@ -166,6 +166,7 @@ class AcademicsServiceImpl( @Transactional(readOnly = true) override fun readDegreeRequirements(language: String): DegreeRequirementsPageResponse { val enumLanguageType = LanguageType.makeStringToLanguageType(language) + val academicsEntity = academicsRepository.findByLanguageAndStudentTypeAndPostType( enumLanguageType, @@ -173,17 +174,8 @@ class AcademicsServiceImpl( AcademicsPostType.DEGREE_REQUIREMENTS ) - val yearList = - academicsRepository.findAllByLanguageAndStudentTypeAndPostTypeOrderByYearDesc( - enumLanguageType, - AcademicsStudentType.UNDERGRADUATE, - AcademicsPostType.DEGREE_REQUIREMENTS_YEAR_LIST - ).map { - val attachments = attachmentService.createAttachmentResponses(it.attachments) - DegreeRequirementsDto.of(it, attachments) - } - - return DegreeRequirementsPageResponse.of(academicsEntity, yearList) + val attachments = attachmentService.createAttachmentResponses(academicsEntity.attachments) + return DegreeRequirementsPageResponse.of(academicsEntity, attachments) } @Transactional From 5cace4712a34324a263735caf8c04fa21441d153 Mon Sep 17 00:00:00 2001 From: huGgW Date: Wed, 17 Apr 2024 02:04:03 +0900 Subject: [PATCH 209/214] =?UTF-8?q?Feat:=20=EA=B2=80=EC=83=89=20refresh=20?= =?UTF-8?q?api=20=EB=A1=9C=EC=BB=AC=EC=97=90=EC=84=9C=20=EC=A0=91=EA=B7=BC?= =?UTF-8?q?=20=EA=B0=80=EB=8A=A5=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/wafflestudio/csereal/core/main/api/MainController.kt | 2 -- 1 file changed, 2 deletions(-) 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 81ac0a52..33a533d2 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 @@ -2,7 +2,6 @@ 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.context.annotation.Profile import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController @@ -17,7 +16,6 @@ class MainController( return mainService.readMain() } - @Profile("!prod") @GetMapping("/search/refresh") fun refreshSearches() { mainService.refreshSearch() From 15c08ae7bbacb9b1d5350a33669dc3b248de0c77 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Sun, 28 Apr 2024 15:31:45 +0900 Subject: [PATCH 210/214] =?UTF-8?q?tcl=20=EA=B4=80=EB=A0=A8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#274)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 인정 IF 필드 추가 * fix: code 없는 컨퍼런스 후순위로 조정 * test 수정 --- .../core/conference/database/ConferenceEntity.kt | 4 +++- .../csereal/core/conference/dto/ConferenceDto.kt | 6 ++++-- .../csereal/core/conference/dto/ConferencePage.kt | 2 +- .../conference/service/ConferenceServiceTest.kt | 15 ++++++++++----- 4 files changed, 18 insertions(+), 9 deletions(-) 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 6e911164..e25c230a 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 @@ -15,6 +15,7 @@ class ConferenceEntity( var code: String, var abbreviation: String, var name: String, + var ackIf: Int?, @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "conference_page_id") @@ -33,7 +34,8 @@ class ConferenceEntity( code = conferenceDto.code, abbreviation = conferenceDto.abbreviation, name = conferenceDto.name, - conferencePage = conferencePage + conferencePage = conferencePage, + ackIf = conferenceDto.ackIf ) } 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 a335e86c..555a8acf 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 @@ -10,7 +10,8 @@ data class ConferenceDto( val language: String, val code: String, val abbreviation: String, - val name: String + val name: String, + val ackIf: Int? ) { companion object { fun of(conferenceEntity: ConferenceEntity): ConferenceDto { @@ -19,7 +20,8 @@ data class ConferenceDto( language = LanguageType.makeLowercase(conferenceEntity.language), code = conferenceEntity.code, abbreviation = conferenceEntity.abbreviation, - name = conferenceEntity.name + name = conferenceEntity.name, + ackIf = conferenceEntity.ackIf ) } } 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 93cb5aca..be5e5465 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 @@ -17,7 +17,7 @@ data class ConferencePage( author = conferencePageEntity.author.name, conferenceList = conferencePageEntity.conferences.map { ConferenceDto.of(it) - }.sortedBy { it.code } + }.sortedWith(compareBy { it.code.isBlank() }.thenBy { it.code }) ) } } 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 0c6501e2..5c46056e 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 @@ -81,21 +81,24 @@ class ConferenceServiceTest( code = "code1", name = "name1", abbreviation = "abbreviation1", - conferencePage = conferencePage + conferencePage = conferencePage, + ackIf = 1 ), ConferenceEntity( language = LanguageType.KO, code = "code2", name = "name2", abbreviation = "abbreviation2", - conferencePage = conferencePage + conferencePage = conferencePage, + ackIf = 1 ), ConferenceEntity( language = LanguageType.KO, code = "code3", name = "name3", abbreviation = "abbreviation3", - conferencePage = conferencePage + conferencePage = conferencePage, + ackIf = 1 ) ) ) @@ -112,13 +115,15 @@ class ConferenceServiceTest( language = "ko", code = "code0", name = "modifiedName", - abbreviation = "modifiedAbbreviation" + abbreviation = "modifiedAbbreviation", + ackIf = 1 ) val newConference = ConferenceDto( language = "ko", code = "code9", name = "newName", - abbreviation = "newAbbreviation" + abbreviation = "newAbbreviation", + ackIf = 1 ) val conferenceModifyRequest = ConferenceModifyRequest( deleteConferenceIdList = listOf(deleteConferenceId), From 7bb30f8f439b04691ceea83db66aee238f8b0568 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Mon, 29 Apr 2024 21:48:43 +0900 Subject: [PATCH 211/214] =?UTF-8?q?=EC=98=81=EB=AC=B8=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20LastName=20=EA=B8=B0=EC=A4=80=20=EC=A0=95=EB=A0=AC?= =?UTF-8?q?=20(#276)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../csereal/core/member/service/ProfessorService.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) 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 76d27d8e..5555329d 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 @@ -116,7 +116,14 @@ class ProfessorServiceImpl( SimpleProfessorDto.of(it, imageURL) } .sortedWith { a, b -> + when { + enumLanguageType == LanguageType.EN -> { + val lastNameA = a.name.split(" ").last() + val lastNameB = b.name.split(" ").last() + lastNameA.compareTo(lastNameB) + } + startsWithEnglish(a.name) && !startsWithEnglish(b.name) -> 1 !startsWithEnglish(a.name) && startsWithEnglish(b.name) -> -1 else -> a.name.compareTo(b.name) @@ -138,6 +145,12 @@ class ProfessorServiceImpl( } .sortedWith { a, b -> when { + enumLanguageType == LanguageType.EN -> { + val lastNameA = a.name.split(" ").last() + val lastNameB = b.name.split(" ").last() + lastNameA.compareTo(lastNameB) + } + startsWithEnglish(a.name) && !startsWithEnglish(b.name) -> 1 !startsWithEnglish(a.name) && startsWithEnglish(b.name) -> -1 else -> a.name.compareTo(b.name) From 4689a95c105f188f97c7db7a961dfdac685cc848 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Thu, 9 May 2024 13:57:31 +0900 Subject: [PATCH 212/214] =?UTF-8?q?TCL=20=EC=BD=94=EB=93=9C,=20=EC=9D=B8?= =?UTF-8?q?=EC=A0=95IF=20=EC=82=AD=EC=A0=9C=20=EB=B0=8F=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EC=88=9C=20=EC=A0=95=EB=A0=AC=20(#278)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * TCL 코드, 인정IF 삭제 및 이름 순 정렬 * style: ktlint * test 수정 * style: ktlint --- .../conference/database/ConferenceEntity.kt | 7 +---- .../database/ConferencePageEntity.kt | 2 +- .../core/conference/dto/ConferenceDto.kt | 8 ++---- .../core/conference/dto/ConferencePage.kt | 2 +- .../research/database/ResearchSearchEntity.kt | 1 - .../service/ConferenceServiceTest.kt | 27 +++++-------------- 6 files changed, 11 insertions(+), 36 deletions(-) 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 e25c230a..db934b05 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 @@ -12,10 +12,8 @@ class ConferenceEntity( var language: LanguageType, var isDeleted: Boolean = false, - var code: String, var abbreviation: String, var name: String, - var ackIf: Int?, @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "conference_page_id") @@ -31,16 +29,13 @@ class ConferenceEntity( conferencePage: ConferencePageEntity ) = ConferenceEntity( language = languageType, - code = conferenceDto.code, abbreviation = conferenceDto.abbreviation, name = conferenceDto.name, - conferencePage = conferencePage, - ackIf = conferenceDto.ackIf + 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 1fb9915b..f6896a49 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 @@ -12,7 +12,7 @@ class ConferencePageEntity( var author: UserEntity, @OneToMany(mappedBy = "conferencePage", cascade = [CascadeType.ALL], orphanRemoval = true) - @OrderBy("code ASC") + @OrderBy("name ASC") val conferences: MutableSet = mutableSetOf() ) : BaseTimeEntity() { 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 555a8acf..ade80227 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 @@ -8,20 +8,16 @@ data class ConferenceDto( @JsonInclude(JsonInclude.Include.NON_NULL) val id: Long? = null, val language: String, - val code: String, val abbreviation: String, - val name: String, - val ackIf: Int? + val name: String ) { companion object { fun of(conferenceEntity: ConferenceEntity): ConferenceDto { return ConferenceDto( id = conferenceEntity.id, language = LanguageType.makeLowercase(conferenceEntity.language), - code = conferenceEntity.code, abbreviation = conferenceEntity.abbreviation, - name = conferenceEntity.name, - ackIf = conferenceEntity.ackIf + 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 index be5e5465..c58a313d 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 @@ -17,7 +17,7 @@ data class ConferencePage( author = conferencePageEntity.author.name, conferenceList = conferencePageEntity.conferences.map { ConferenceDto.of(it) - }.sortedWith(compareBy { it.code.isBlank() }.thenBy { it.code }) + }.sortedBy { it.name } ) } } 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 46c287ea..397c73c5 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 @@ -77,7 +77,6 @@ class ResearchSearchEntity( fun createContent(conference: ConferenceEntity) = StringBuilder().apply { appendLine(conference.name) - appendLine(conference.code) appendLine(conference.abbreviation) }.toString() } 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 5c46056e..5f5e9303 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 @@ -78,27 +78,21 @@ class ConferenceServiceTest( listOf( ConferenceEntity( language = LanguageType.KO, - code = "code1", name = "name1", abbreviation = "abbreviation1", - conferencePage = conferencePage, - ackIf = 1 + conferencePage = conferencePage ), ConferenceEntity( language = LanguageType.KO, - code = "code2", name = "name2", abbreviation = "abbreviation2", - conferencePage = conferencePage, - ackIf = 1 + conferencePage = conferencePage ), ConferenceEntity( language = LanguageType.KO, - code = "code3", name = "name3", abbreviation = "abbreviation3", - conferencePage = conferencePage, - ackIf = 1 + conferencePage = conferencePage ) ) ) @@ -113,17 +107,13 @@ class ConferenceServiceTest( val modifiedConference = ConferenceDto( id = conferences.first().id, language = "ko", - code = "code0", name = "modifiedName", - abbreviation = "modifiedAbbreviation", - ackIf = 1 + abbreviation = "modifiedAbbreviation" ) val newConference = ConferenceDto( language = "ko", - code = "code9", name = "newName", - abbreviation = "newAbbreviation", - ackIf = 1 + abbreviation = "newAbbreviation" ) val conferenceModifyRequest = ConferenceModifyRequest( deleteConferenceIdList = listOf(deleteConferenceId), @@ -135,32 +125,27 @@ class ConferenceServiceTest( Then("Conference가 수정되어야 한다.") { val newConferencePage = conferencePageRepository.findAll().first() - val newConferences = newConferencePage.conferences.sortedBy { it.code } + val newConferences = newConferencePage.conferences.sortedBy { it.name } 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() From 4abab4f9cce3c392c11777b1a06a15db043c6eb6 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Sun, 19 May 2024 16:43:19 +0900 Subject: [PATCH 213/214] feat: id list api for seo (#279) --- .../wafflestudio/csereal/core/news/api/NewsController.kt | 5 +++++ .../csereal/core/news/database/NewsRepository.kt | 5 +++++ .../wafflestudio/csereal/core/news/service/NewsService.kt | 6 ++++++ .../csereal/core/notice/api/NoticeController.kt | 5 +++++ .../csereal/core/notice/database/NoticeRepository.kt | 4 ++++ .../csereal/core/notice/service/NoticeService.kt | 6 ++++++ .../csereal/core/seminar/api/SeminarController.kt | 5 +++++ .../csereal/core/seminar/database/SeminarRepository.kt | 4 ++++ .../csereal/core/seminar/service/SeminarService.kt | 6 ++++++ 9 files changed, 46 insertions(+) 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 d998834b..5cf0dc23 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 @@ -123,4 +123,9 @@ class NewsController( newsService.enrollTag(tagName["name"]!!) return ResponseEntity("등록되었습니다. (tagName: ${tagName["name"]})", HttpStatus.OK) } + + @GetMapping("/ids") + fun getAllIds(): ResponseEntity> { + return ResponseEntity.ok(newsService.getAllIds()) + } } 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 c0eef4e2..b1fc6aa5 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 @@ -19,6 +19,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.data.jpa.repository.Query import org.springframework.stereotype.Repository import java.time.LocalDateTime @@ -32,6 +33,9 @@ interface NewsRepository : JpaRepository, CustomNewsRepository fun findFirstByIsDeletedFalseAndIsPrivateFalseAndCreatedAtGreaterThanOrderByCreatedAtAsc( timestamp: LocalDateTime ): NewsEntity? + + @Query("SELECT n.id FROM news n") + fun findAllIds(): List } interface CustomNewsRepository { @@ -121,6 +125,7 @@ class NewsRepositoryImpl( sortBy == ContentSearchSortType.DATE || keyword.isNullOrEmpty() -> newsEntityQuery.orderBy( newsEntity.createdAt.desc() ) + else /* sortBy == RELEVANCE */ -> newsEntityQuery }.fetch() 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 b0f42a11..dd08cce7 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 @@ -39,6 +39,7 @@ interface NewsService { fun searchTotalNews(keyword: String, number: Int, amount: Int, isStaff: Boolean): NewsTotalSearchDto fun readAllSlides(pageNum: Long, pageSize: Int): AdminSlidesResponse fun unSlideManyNews(request: List) + fun getAllIds(): List } @Service @@ -197,6 +198,11 @@ class NewsServiceImpl( } } + @Transactional(readOnly = true) + override fun getAllIds(): List { + return newsRepository.findAllIds() + } + fun getNewsEntityByIdOrThrow(newsId: Long): NewsEntity { return 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 110fdcd0..94b95a0f 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 @@ -135,4 +135,9 @@ class NoticeController( noticeService.enrollTag(tagName["name"]!!) return ResponseEntity("등록되었습니다. (tagName: ${tagName["name"]})", HttpStatus.OK) } + + @GetMapping("/ids") + fun getAllIds(): ResponseEntity> { + return ResponseEntity.ok(noticeService.getAllIds()) + } } 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 0277a82f..dbf1a2f2 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 @@ -15,6 +15,7 @@ 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.data.jpa.repository.Query import org.springframework.stereotype.Component import java.time.LocalDateTime @@ -29,6 +30,9 @@ interface NoticeRepository : JpaRepository, CustomNoticeRepo fun findFirstByIsDeletedFalseAndIsPrivateFalseAndCreatedAtGreaterThanOrderByCreatedAtAsc( timestamp: LocalDateTime ): NoticeEntity? + + @Query("SELECT n.id FROM notice n") + fun findAllIds(): List } interface CustomNoticeRepository { 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 47f43121..a929d997 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 @@ -40,6 +40,7 @@ interface NoticeService { fun unpinManyNotices(idList: List) fun deleteManyNotices(idList: List) fun enrollTag(tagName: String) + fun getAllIds(): List } @Service @@ -200,4 +201,9 @@ class NoticeServiceImpl( ) tagInNoticeRepository.save(newTag) } + + @Transactional(readOnly = true) + override fun getAllIds(): List { + return noticeRepository.findAllIds() + } } 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 15f43926..da8bba14 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 @@ -96,4 +96,9 @@ class SeminarController( ) { seminarService.deleteSeminar(seminarId) } + + @GetMapping("/ids") + fun getAllIds(): ResponseEntity> { + return ResponseEntity.ok(seminarService.getAllIds()) + } } 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 12211f0d..75b71699 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 @@ -11,6 +11,7 @@ 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.data.jpa.repository.Query import org.springframework.stereotype.Component import java.time.LocalDateTime @@ -24,6 +25,9 @@ interface SeminarRepository : JpaRepository, CustomSeminarR fun findFirstByIsDeletedFalseAndIsPrivateFalseAndCreatedAtGreaterThanOrderByCreatedAtAsc( timestamp: LocalDateTime ): SeminarEntity? + + @Query("SELECT s.id FROM seminar s") + fun findAllIds(): List } 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 625bfe5c..796c1c6b 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 @@ -33,6 +33,7 @@ interface SeminarService { ): SeminarDto fun deleteSeminar(seminarId: Long) + fun getAllIds(): List } @Service @@ -136,4 +137,9 @@ class SeminarServiceImpl( seminar.isDeleted = true } + + @Transactional(readOnly = true) + override fun getAllIds(): List { + return seminarRepository.findAllIds() + } } From c5258af31de1520f4ce2d485c239618075d985a2 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, 20 May 2024 13:51:10 +0900 Subject: [PATCH 214/214] =?UTF-8?q?Feat:=20=EC=83=88=EC=86=8C=EC=8B=9D=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EB=82=A0=EC=A7=9C=EA=B0=80=20=EC=95=84?= =?UTF-8?q?=EB=8B=8C=20=EC=8B=A4=EC=A0=9C=20=EB=82=A0=EC=A7=9C=20=EC=88=9C?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=A0=95=EB=A0=AC=EB=90=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=B3=80=EA=B2=BD=20(#281)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat: Change news search to sort by date, not created date. * Refactor: Remove unused method. --- .../wafflestudio/csereal/core/news/database/NewsRepository.kt | 3 +-- 1 file changed, 1 insertion(+), 2 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 b1fc6aa5..3f1bec45 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 @@ -25,7 +25,6 @@ import java.time.LocalDateTime interface NewsRepository : JpaRepository, CustomNewsRepository { fun findAllByIsPrivateFalseAndIsImportantTrueAndIsDeletedFalse(): List - fun findAllByIsImportantTrueAndIsDeletedFalse(): List fun findFirstByIsDeletedFalseAndIsPrivateFalseAndCreatedAtLessThanOrderByCreatedAtDesc( timestamp: LocalDateTime ): NewsEntity? @@ -123,7 +122,7 @@ class NewsRepositoryImpl( val newsEntityList = when { sortBy == ContentSearchSortType.DATE || keyword.isNullOrEmpty() -> newsEntityQuery.orderBy( - newsEntity.createdAt.desc() + newsEntity.date.desc() ) else /* sortBy == RELEVANCE */ -> newsEntityQuery