Skip to content
This repository has been archived by the owner on Sep 16, 2024. It is now read-only.

Commit

Permalink
Merge pull request #46 from gdsc-konkuk/develop
Browse files Browse the repository at this point in the history
1차 MVP
  • Loading branch information
goldentrash authored Jan 27, 2024
2 parents 9afc611 + a044ad2 commit fa0f9a5
Show file tree
Hide file tree
Showing 41 changed files with 671 additions and 203 deletions.
47 changes: 47 additions & 0 deletions .github/workflows/Continuous Delivery.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Continuous Delivery

on:
pull_request:
branches:
- 'main'

jobs:
Delivery:
runs-on: ubuntu-22.04

env:
DB_URL: ${{ secrets.DB_URL }}
DB_USER: ${{ secrets.DB_USR }}
DB_PASSWORD: ${{ secrets.DB_PWD }}

steps:
- name: Set up sources
uses: actions/checkout@v4
with:
token: ${{ secrets.GH_TOKEN }}
submodules: true

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: 17
distribution: corretto
cache: gradle

- name: Setup Gradle
uses: gradle/gradle-build-action@v2

- name: Execute Gradle build
run: ./gradlew build

- name: Login to docker-hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Build Docker Image
run: docker build --tag goldentrash/plantory:latest .

- name: Push docker image
run: docker push goldentrash/plantory:latest
33 changes: 33 additions & 0 deletions .github/workflows/Deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Deploy

on:
pull_request:
types:
- closed
branches:
- 'main'

jobs:
Deploy:
if: github.event.pull_request.merged == true
runs-on: ubuntu-22.04

steps:
# Needed for `google-github-actions`
- uses: 'actions/checkout@v4'

- name: Login to Google
uses: google-github-actions/auth@v2
with:
credentials_json: ${{ secrets.GCP_CREDENTIALS }}

- name: Update docker container
uses: google-github-actions/ssh-compute@v1
with:
instance_name: plantory
zone: asia-northeast3-a
ssh_private_key: ${{ secrets.GCP_SSH_KEY }}
command: |
docker pull goldentrash/plantory:latest
docker stop plantory
docker run -d --rm -p 8080:8080 --mount type=volume,src=resources,dst=/app/resources --name plantory goldentrash/plantory
9 changes: 9 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# syntax=docker/dockerfile:1

FROM amazoncorretto:17-alpine

WORKDIR /app
COPY /build/libs/plantory-*-SNAPSHOT.jar app.jar

EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
3 changes: 3 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ dependencies {

// firebase
implementation("com.google.firebase:firebase-admin:9.2.0")

// swagger
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0")
}

tasks.withType<KotlinCompile> {
Expand Down
20 changes: 20 additions & 0 deletions src/main/kotlin/gdsc/plantory/common/config/SwaggerConfig.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package gdsc.plantory.common.config

import gdsc.plantory.common.support.AccessDeviceToken
import gdsc.plantory.common.support.DEVICE_ID_HEADER
import io.swagger.v3.oas.models.Operation
import io.swagger.v3.oas.models.security.SecurityRequirement
import org.springdoc.core.customizers.OperationCustomizer
import org.springframework.stereotype.Component
import org.springframework.web.method.HandlerMethod

@Component
class Operation : OperationCustomizer {
override fun customize(operation: Operation?, handlerMethod: HandlerMethod?): Operation? {
if (handlerMethod?.methodParameters?.find { it.hasParameterAnnotation(AccessDeviceToken::class.java) } != null) {
operation?.addSecurityItem(SecurityRequirement().addList(DEVICE_ID_HEADER))
}

return operation
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import org.slf4j.LoggerFactory
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.RestControllerAdvice
import org.springframework.web.multipart.MaxUploadSizeExceededException

@RestControllerAdvice
class CommonExceptionHandler {
Expand All @@ -27,4 +28,9 @@ class CommonExceptionHandler {

return ResponseEntity.internalServerError().body(ErrorResponse("서버 에러가 발생했습니다. 관리자에게 문의해주세요."))
}

@ExceptionHandler
fun handleMaxSizeException(ex: MaxUploadSizeExceededException): ResponseEntity<ErrorResponse> {
return ResponseEntity.badRequest().body(ErrorResponse("파일의 최대 사이즈를 확인해주세요."))
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package gdsc.plantory.common.support

import io.swagger.v3.oas.annotations.Parameter
import io.swagger.v3.oas.annotations.enums.ParameterIn

@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Parameter(`in` = ParameterIn.HEADER, name = DEVICE_ID_HEADER, description = "사용자 디바이스/인증 토큰")
annotation class AccessDeviceToken
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package gdsc.plantory.common.support

import org.springframework.web.context.request.NativeWebRequest

private const val DEVICE_ID_HEADER = "Device-Token"
const val DEVICE_ID_HEADER = "Device-Token"

class DeviceHeaderExtractor {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class PhotoLocalManager(

private fun uploadFileInLocal(multipartFile: MultipartFile, uploadPath: File) {
try {
multipartFile.transferTo(uploadPath)
multipartFile.transferTo(uploadPath.toPath())
} catch (e: IOException) {
throw IllegalStateException("파일 변환이 실패했습니다.")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,28 @@ package gdsc.plantory.member.presentation

import gdsc.plantory.member.presentation.dto.MemberCreateRequest
import gdsc.plantory.member.service.MemberService
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.Parameter
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.tags.Tag
import org.springframework.http.ResponseEntity
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

@Tag(name = "Member Command", description = "사용자 정보 수정")
@RestController
@RequestMapping("/api/v1/members")
class MemberCommandApi(
private val memberService: MemberService,
) {

@Operation(summary = "사용자 등록", description = "사용자를 등록/추가합니다.")
@ApiResponse(responseCode = "200", description = "등록 성공")
@PostMapping
fun signUp(
@RequestBody request: MemberCreateRequest,
@Parameter(description = "사용자 정보") @RequestBody request: MemberCreateRequest,
): ResponseEntity<Unit> {
memberService.signUp(request.deviceToken)
return ResponseEntity.ok().build()
Expand Down
24 changes: 21 additions & 3 deletions src/main/kotlin/gdsc/plantory/plant/domain/CompanionPlant.kt
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ class CompanionPlant(
val getNickName: String
get() = this.nickname.value

val getName: String
get() = this.nickname.value

val getSortDescription: String
get() = this.shortDescription.value

Expand All @@ -97,12 +100,11 @@ class CompanionPlant(
}

this.records.add(PlantRecord(imageUrl, comment, this))
this.saveRecordHistory(date)
}

fun saveHistory(historyType: HistoryType, date: LocalDate = LocalDate.now()) {
if (isNotCurrentDay(date)) {
throw IllegalArgumentException("물을 줄 날짜는 오늘 날짜여야 합니다.")
}
validateInput(historyType, date)

if (historyType == HistoryType.WATER_CHANGE) {
this.lastWaterDate = date
Expand All @@ -122,6 +124,22 @@ class CompanionPlant(
return ChronoUnit.DAYS.between(birthDate, currentDate).toInt() + 1
}

private fun validateInput(historyType: HistoryType, date: LocalDate) {
if (isRecordType(historyType)) {
throw IllegalArgumentException("데일리 기록은 히스토리 타입을 직접 추가할 수 없습니다.")
}

if (isNotCurrentDay(date)) {
throw IllegalArgumentException("물을 줄 날짜는 오늘 날짜여야 합니다.")
}
}

private fun isRecordType(historyType: HistoryType) = historyType == HistoryType.RECORDING

private fun saveRecordHistory(date: LocalDate) {
this.histories.add(PlantHistory(HistoryType.RECORDING, date, this))
}

private fun isNotCurrentDay(date: LocalDate) = !date.isEqual(LocalDate.now())

override fun equals(other: Any?): Boolean {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package gdsc.plantory.plant.domain

import NotFoundException
import gdsc.plantory.plant.presentation.dto.CompanionPlantLookupDto
import gdsc.plantory.plant.presentation.dto.CompanionPlantWaterCycleDto
import gdsc.plantory.plant.presentation.dto.PlantRecordDto
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query
import java.time.LocalDate
Expand All @@ -10,16 +12,56 @@ fun CompanionPlantRepository.findByIdAndMemberIdOrThrow(id: Long, memberId: Long
return findByIdAndMemberId(id, memberId) ?: throw NotFoundException("식물 정보가 없어요")
}

fun CompanionPlantRepository.findRecordByDateOrThrow(id: Long, memberId: Long, date: LocalDate): PlantRecord {
return findRecordByDate(id, memberId, date) ?: throw NotFoundException("데일리 기록이 없어요")
}

interface CompanionPlantRepository : JpaRepository<CompanionPlant, Long> {

fun findAllByMemberId(memberId: Long): List<CompanionPlant>
fun findByIdAndMemberId(id: Long, memberId: Long): CompanionPlant?
fun removeByIdAndMemberId(id: Long, memberId: Long)

@Query(
"""
SELECT new gdsc.plantory.plant.presentation.dto.PlantRecordDto(
r.id,
r.imageUrl._value,
r.comment.content,
cp.nickname._value
)
FROM CompanionPlant cp LEFT JOIN PlantRecord r
ON cp.id = r.companionPlant.id
WHERE cp.id = :companionPlantId
AND cp.memberId = :memberId
AND DATE(r.createAt) = :recordDate
"""
)
fun findRecordByDate(companionPlantId: Long, memberId: Long, recordDate: LocalDate): PlantRecordDto

@Query(
"""
SELECT history.type
FROM PlantHistory history
WHERE
history.companionPlant.id = :companionPlantId
AND DATE(history.createAt) = :recordDate
"""
)
fun findAllHistoryTypeByDate(companionPlantId: Long, recordDate: LocalDate): List<HistoryType>

@Query(
"""
SELECT new gdsc.plantory.plant.presentation.dto.CompanionPlantLookupDto(
plant.id,
plant.imageUrl._value,
plant.nickname._value,
plant.shortDescription._value,
plant.birthDate,
information.species.name
)
FROM PlantInformation information JOIN CompanionPlant plant
ON information.id = plant.plantInformationId
WHERE plant.memberId = :memberId
"""
)
fun findAllByMemberId(memberId: Long): List<CompanionPlantLookupDto>

@Query(
"""
SELECT new gdsc.plantory.plant.presentation.dto.CompanionPlantWaterCycleDto(
Expand All @@ -44,15 +86,4 @@ interface CompanionPlantRepository : JpaRepository<CompanionPlant, Long> {
"""
)
fun findAllHistoriesByMonth(id: Long, memberId: Long, year: Int, month: Int): List<PlantHistory>

@Query(
"""
SELECT record FROM PlantRecord record
WHERE
record.companionPlant.id = :id
AND record.companionPlant.memberId = :memberId
AND DATE(record.createAt) = :date
"""
)
fun findRecordByDate(id: Long, memberId: Long, date: LocalDate): PlantRecord?
}
Loading

0 comments on commit fa0f9a5

Please sign in to comment.