Skip to content

Commit

Permalink
Feat: Lab CRUD (#319)
Browse files Browse the repository at this point in the history
* feat: add request body

* feat: modify dto nullability

* feat: extract lab service && implement CRUD

* feat: add lab language dto

* feat: add lab events

* fix: fix unique constraints for research language entity

* feat: add methods to repositories

* feat: add LAB CRUD APIs

* refactor: seperate event services

* refactor: professors to mutable var

* fix: fix typos

* comment: add comment to professor service method

* test: remove unneed test file

* feat: split search service and event service

* feat: implement professor, research event service

* fix: apply ktlint
  • Loading branch information
huGgW authored Sep 20, 2024
1 parent da7d055 commit 2ded285
Show file tree
Hide file tree
Showing 22 changed files with 775 additions and 582 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package com.wafflestudio.csereal.core.member.service

import com.wafflestudio.csereal.core.member.database.MemberSearchEntity
import com.wafflestudio.csereal.core.member.database.ProfessorEntity
import com.wafflestudio.csereal.core.member.database.ProfessorRepository
import com.wafflestudio.csereal.core.research.database.LabRepository
import com.wafflestudio.csereal.core.research.event.LabCreatedEvent
import com.wafflestudio.csereal.core.research.event.LabDeletedEvent
import com.wafflestudio.csereal.core.research.event.LabModifiedEvent
import org.springframework.context.event.EventListener
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

interface ProfessorEventService {
fun labDeletedEventListener(event: LabDeletedEvent)
fun labModifiedEventListener(event: LabModifiedEvent)
fun labCreatedEventListener(event: LabCreatedEvent)
}

@Service
class ProfessorEventServiceImpl(
private val professorRepository: ProfessorRepository,
private val labRepository: LabRepository
) : ProfessorEventService {
@EventListener
@Transactional
override fun labCreatedEventListener(event: LabCreatedEvent) {
if (event.professorIds.isEmpty()) {
return
}

val lab = labRepository.findByIdOrNull(event.id)!!
val professors = professorRepository.findAllById(event.professorIds)
.takeIf { it.size == event.professorIds.size }!!

// TODO: Consider professor's before lab value
professors.forEach {
it.lab = lab
upsertProfessorSearchIndex(it)
}
}

@EventListener
@Transactional
override fun labModifiedEventListener(event: LabModifiedEvent) {
val lab = labRepository.findByIdOrNull(event.id)!!

val oldProfessorIds = event.professorIdsModified.first
val oldProfessors = oldProfessorIds.let { p ->
professorRepository.findAllById(p)
.takeIf { it.size == p.size }!!
}

val newProfessorIds = event.professorIdsModified.second
val newProfessors = newProfessorIds.let { p ->
professorRepository.findAllById(p)
.takeIf { it.size == p.size }!!
}

when {
oldProfessors.isEmpty() && newProfessors.isEmpty() -> {}

oldProfessors.isEmpty() && newProfessors.isNotEmpty() -> {
newProfessors.forEach {
it.lab = lab
upsertProfessorSearchIndex(it)
}
}

oldProfessors.isNotEmpty() && newProfessors.isEmpty() -> {
oldProfessors.forEach {
it.lab = null
upsertProfessorSearchIndex(it)
}
}

oldProfessorIds == newProfessorIds -> {
oldProfessors.forEach { upsertProfessorSearchIndex(it) }
}

else -> {
val removeProfessorIds = oldProfessorIds - newProfessorIds
oldProfessors.forEach {
if (it.id in removeProfessorIds) {
it.lab = null
}
upsertProfessorSearchIndex(it)
}

val addProfessorIds = newProfessorIds - oldProfessorIds
newProfessors.forEach {
if (it.id in addProfessorIds) {
it.lab = lab
}
upsertProfessorSearchIndex(it)
}
}
}
}

@EventListener
@Transactional
override fun labDeletedEventListener(event: LabDeletedEvent) {
val lab = labRepository.findByIdOrNull(event.id)!!
val professors = professorRepository.findAllById(event.professorIds)
.takeIf { it.size == event.professorIds.size }!!

professors.forEach {
it.lab = null
upsertProfessorSearchIndex(it)
}
}

@Transactional
fun upsertProfessorSearchIndex(professor: ProfessorEntity) {
professor.memberSearch?.update(professor)
?: let { professor.memberSearch = MemberSearchEntity.create(professor) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ class ProfessorServiceImpl(
?: throw CserealException.Csereal404("해당 교수님을 찾을 수 없습니다. professorId: $professorId")

// Lab 업데이트
// 기존 연구실이 제거되지 않는 이상 수동으로 교수의 Lab을 제거할 수 없음
val outdatedLabId = professor.lab?.id
if (updateReq.labId != null && updateReq.labId != professor.lab?.id) {
val lab = labRepository.findByIdOrNull(updateReq.labId) ?: throw CserealException.Csereal404(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.wafflestudio.csereal.core.research.api.req

data class CreateLabLanguageReqBody(
val ko: CreateLabReqBody,
val en: CreateLabReqBody
)

data class CreateLabReqBody(
val name: String,
val description: String?,
val groupId: Long?,
val professorIds: Set<Long>,
val location: String?,
val tel: String?,
val acronym: String?,
val youtube: String?,
val websiteURL: String?
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.wafflestudio.csereal.core.research.api.req

data class ModifyLabLanguageReqBody(
val ko: ModifyLabReqBody,
val en: ModifyLabReqBody
)

data class ModifyLabReqBody(
val name: String,
val description: String?,
val location: String?,
val tel: String?,
val acronym: String?,
val youtube: String?,
val websiteURL: String?,
val groupId: Long?,
val professorIds: Set<Long>,
val removePdf: Boolean
)
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
package com.wafflestudio.csereal.core.research.api.v1

import com.wafflestudio.csereal.common.aop.AuthenticatedStaff
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
import com.wafflestudio.csereal.core.research.dto.ResearchGroupResponse
import com.wafflestudio.csereal.core.research.service.LabService
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

@RequestMapping("/api/v1/research")
@RestController("ResearchControllerV1")
@Deprecated(message = "Use V2 API")
class ResearchController(
private val researchService: ResearchService,
private val researchSearchService: ResearchSearchService
private val researchSearchService: ResearchSearchService,
private val labService: LabService
) {
@GetMapping("/groups")
fun readAllResearchGroups(
Expand All @@ -35,44 +34,18 @@ class ResearchController(
return ResponseEntity.ok(researchService.readAllResearchCentersDeprecated(language))
}

@AuthenticatedStaff
@PostMapping("/lab")
fun createLab(
@Valid
@RequestPart("request")
request: LabDto,
@RequestPart("pdf") pdf: MultipartFile?
): ResponseEntity<LabDto> {
return ResponseEntity.ok(researchService.createLab(request, pdf))
}

@GetMapping("/labs")
fun readAllLabs(
@RequestParam(required = false, defaultValue = "ko") language: String
): ResponseEntity<List<LabDto>> {
return ResponseEntity.ok(researchService.readAllLabs(language))
return ResponseEntity.ok(labService.readAllLabs(language))
}

@GetMapping("/lab/{labId}")
fun readLab(
@PathVariable labId: Long
): ResponseEntity<LabDto> {
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<LabDto> {
return ResponseEntity.ok(researchService.updateLab(labId, request, pdf))
return ResponseEntity.ok(labService.readLab(labId))
}

@GetMapping("/search/top")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,30 @@ package com.wafflestudio.csereal.core.research.api.v2

import com.wafflestudio.csereal.common.aop.AuthenticatedStaff
import com.wafflestudio.csereal.common.enums.LanguageType
import com.wafflestudio.csereal.core.research.api.req.CreateLabLanguageReqBody
import com.wafflestudio.csereal.core.research.api.req.CreateResearchLanguageReqBody
import com.wafflestudio.csereal.core.research.api.req.ModifyLabLanguageReqBody
import com.wafflestudio.csereal.core.research.api.req.ModifyResearchLanguageReqBody
import com.wafflestudio.csereal.core.research.dto.LabDto
import com.wafflestudio.csereal.core.research.dto.ResearchLanguageDto
import com.wafflestudio.csereal.core.research.dto.ResearchSealedDto
import com.wafflestudio.csereal.core.research.dto.*
import com.wafflestudio.csereal.core.research.service.LabService
import com.wafflestudio.csereal.core.research.service.ResearchSearchService
import com.wafflestudio.csereal.core.research.service.ResearchService
import com.wafflestudio.csereal.core.research.type.ResearchType
import io.swagger.v3.oas.annotations.Parameter
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

@RequestMapping("/api/v2/research")
@RestController
class ResearchController(
private val researchService: ResearchService,
private val labService: LabService,
private val researchSearchService: ResearchSearchService
) {
// Research APIs

@GetMapping("/{researchId:[0-9]+}")
fun readResearch(
@Positive
Expand Down Expand Up @@ -76,46 +79,54 @@ class ResearchController(
researchService.deleteResearchLanguage(koreanId, englishId)
}

// TODO: Change to Language Unified API
@GetMapping("/labs")
// Lab APIs

@GetMapping("/lab")
fun readAllLabs(
@RequestParam(required = false, defaultValue = "ko") language: String
): ResponseEntity<List<LabDto>> {
return ResponseEntity.ok(researchService.readAllLabs(language))
}
): List<LabDto> = labService.readAllLabs(language)

// TODO: Change to Language Unified API
@GetMapping("/lab/{labId}")
fun readLab(
@PathVariable labId: Long
): ResponseEntity<LabDto> {
return ResponseEntity.ok(researchService.readLab(labId))
): LabLanguageDto = labService.readLabLanguage(labId)

@AuthenticatedStaff
@PostMapping("/lab", consumes = ["multipart/form-data"])
fun createLab(
@Valid
@RequestPart("request")
request: CreateLabLanguageReqBody,

@RequestPart("pdf") pdf: MultipartFile?
): LabLanguageDto = labService.createLabLanguage(request, pdf)

@AuthenticatedStaff
@PutMapping("/lab/{koreanLabId}/{englishLabId}", consumes = ["multipart/form-data"])
fun updateLab(
@PathVariable @Positive
koreanLabId: Long,
@PathVariable @Positive
englishLabId: Long,
@Valid
@RequestPart("request")
request: ModifyLabLanguageReqBody,
@RequestPart("pdf") pdf: MultipartFile?
): LabLanguageDto = labService.updateLabLanguage(koreanLabId, englishLabId, request, pdf)

@AuthenticatedStaff
@DeleteMapping("/lab/{koreanLabId}/{englishLabId}")
fun deleteLab(
@PathVariable @Positive
koreanLabId: Long,
@PathVariable @Positive
englishLabId: Long
) {
labService.deleteLabLanguage(koreanLabId, englishLabId)
}

// @AuthenticatedStaff
// @PostMapping("/lab")
// fun createLab(
// @Valid
// @RequestPart("request")
// request: LabDto,
// @RequestPart("pdf") pdf: MultipartFile?
// ): ResponseEntity<LabDto> {
// return ResponseEntity.ok(researchService.createLab(request, pdf))
// }
//
//
// // TODO: Change to Language Unified API
// @AuthenticatedStaff
// @PatchMapping("/lab/{labId}")
// fun updateLab(
// @PathVariable labId: Long,
// @Valid
// @RequestPart("request")
// request: LabUpdateRequest,
// @RequestPart("pdf") pdf: MultipartFile?
// ): ResponseEntity<LabDto> {
// return ResponseEntity.ok(researchService.updateLab(labId, request, pdf))
// }
// Search APIs

@GetMapping("/search/top")
fun searchTop(
Expand Down
Loading

0 comments on commit 2ded285

Please sign in to comment.