Skip to content

Commit

Permalink
Merge pull request prgrms-be-devcourse#8 from juwon-code/main
Browse files Browse the repository at this point in the history
[Pullrequest] 미디어 컴포넌트 마이그레이션 및 테스트 환경설정 완료
  • Loading branch information
deveunhwa authored Oct 31, 2024
2 parents ace8e08 + 1b1acf7 commit 2332ea5
Show file tree
Hide file tree
Showing 22 changed files with 524 additions and 3 deletions.
8 changes: 6 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,22 @@ dependencies {
testImplementation("org.springframework.security:spring-security-test")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")

// AWS Java S3 SDK - AWS S3 스토리지와 통신을 수행할 때 사용하는 디펜던시
implementation("software.amazon.awssdk:s3:2.28.16")

// H2 Database Engine - 테스트에서 사용하는 인메모리 데이터베이스 H2
testImplementation("com.h2database:h2:2.3.232")

//JWT 의존성
implementation("io.jsonwebtoken:jjwt-api:0.12.3")
implementation("io.jsonwebtoken:jjwt-impl:0.12.3")
implementation("io.jsonwebtoken:jjwt-jackson:0.12.3")

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

implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")

}

kotlin {
Expand Down
25 changes: 25 additions & 0 deletions src/main/kotlin/org/tenten/bittakotlin/demo/User.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.tenten.bittakotlin.demo;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@Entity
public class User {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;
private String email;
private String phone;

public User() {}
}
24 changes: 24 additions & 0 deletions src/main/kotlin/org/tenten/bittakotlin/demo/UserController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.tenten.bittakotlin.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/users")
public class UserController {

@Autowired
private UserRepository userRepository;

@PostMapping
public User addUser(@RequestBody User user) {
return userRepository.save(user);
}

@GetMapping
public List<User> getAllUsers() {
return userRepository.findAll();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.tenten.bittakotlin.demo;

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {}

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.tenten.bittakotlin.global.config

import org.springframework.context.annotation.Configuration
import org.springframework.data.jpa.repository.config.EnableJpaAuditing

@Configuration
@EnableJpaAuditing
class JpaAuditConfig {
}
41 changes: 41 additions & 0 deletions src/main/kotlin/org/tenten/bittakotlin/global/config/S3Config.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.tenten.bittakotlin.global.config

import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider
import software.amazon.awssdk.regions.Region
import software.amazon.awssdk.services.s3.S3Client
import software.amazon.awssdk.services.s3.presigner.S3Presigner

@Configuration
class S3Config(
@Value("\${s3.access.id}")
private val accessId: String,

@Value("\${s3.secret.key}")
private val secretKey: String
) {
@Bean
fun s3Client() : S3Client {
val basicCredentials: AwsBasicCredentials
= AwsBasicCredentials.create(accessId, secretKey)

return S3Client.builder()
.region(Region.AP_NORTHEAST_2)
.credentialsProvider(StaticCredentialsProvider.create(basicCredentials))
.build()
}

@Bean
fun s3Presigner() : S3Presigner {
val basicCredentials: AwsBasicCredentials
= AwsBasicCredentials.create(accessId, secretKey)

return S3Presigner.builder()
.region(Region.AP_NORTHEAST_2)
.credentialsProvider(StaticCredentialsProvider.create(basicCredentials))
.build()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.tenten.bittakotlin.media.constant

import org.springframework.http.HttpStatus

enum class MediaError(val code: Int, val message: String) {
WRONG_FILE_SIZE(HttpStatus.BAD_REQUEST.value(), "이미지 파일은 10MB, 비디오 파일은 30MB를 초과할 수 없습니다."),
WRONG_MIME_TYPE(HttpStatus.BAD_REQUEST.value(), "올바르지 않은 파일 유형입니다."),
CANNOT_FOUND(HttpStatus.NOT_FOUND.value(), "DB에 파일이 존재하지 않습니다."),
S3_CANNOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR.value(), "S3 서버에 파일이 존재하지 않습니다.")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.tenten.bittakotlin.media.constant

enum class MediaType {
IMAGE, VIDEO
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.tenten.bittakotlin.media.controller

import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import org.tenten.bittakotlin.media.dto.MediaRequestDto
import org.tenten.bittakotlin.media.service.MediaService

@RestController
@RequestMapping("/api/v1/media")
class MediaController(
private val mediaService: MediaService
) {
@GetMapping("/upload")
fun upload(@RequestBody requestDto: MediaRequestDto.Upload): ResponseEntity<Map<String, Any>> {
return ResponseEntity.ok(mapOf(
"message" to "파일 업로드 링크를 성공적으로 생성했습니다.",
"url" to mediaService.upload(requestDto)
))
}

@GetMapping("/read")
fun read(@RequestBody requestDto: MediaRequestDto.Read): ResponseEntity<Map<String, Any>> {
return ResponseEntity.ok(mapOf(
"message" to "파일 조회 링크를 성공적으로 생성했습니다.",
"url" to mediaService.read(requestDto)
))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.tenten.bittakotlin.media.controller

import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.RestControllerAdvice
import org.tenten.bittakotlin.media.exception.MediaException

@RestControllerAdvice
class MediaControllerAdvice {
@ExceptionHandler(MediaException::class)
fun handleMediaException(e: MediaException): ResponseEntity<Map<String, Any>> {
return ResponseEntity.status(e.code).body(mapOf("error" to e.message))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.tenten.bittakotlin.media.dto

import jakarta.validation.constraints.Min
import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.Pattern

class MediaRequestDto {
data class Read (
@field:NotBlank(message = "파일명이 비어있습니다.")
val filename: String
)

data class Upload (
@field:NotBlank(message = "파일명이 비어있습니다.")
val filename: String,

@field:Min(value = 0, message = "비어있는 파일은 업로드할 수 없습니다.")
@field:NotBlank(message = "파일 크기가 비어있습니다.")
val filesize: Int,

@field:Pattern(regexp = "(image/(jpeg|png|gif|bmp|webp|svg\\+xml))|(video/(mp4|webm|ogg|x-msvideo|x-matroska))"
, message = "지원하지 않는 파일 형식입니다.")
@field:NotBlank(message = "파일 형식이 비어있습니다.")
val mimetype: String
)

data class Delete (
@field:NotBlank(message = "회원명이 비어있습니다.")
val username: String,

@field:NotBlank(message = "파일명이 비어있습니다.")
val filename: String
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.tenten.bittakotlin.media.dto

import jakarta.validation.constraints.NotBlank

class MediaResponseDto {
data class Read (
@field:NotBlank(message = "조회 링크가 비어있습니다.")
val link: String
)

data class Upload (
@field:NotBlank(message = "업로드 링크가 비어있습니다.")
val link: String
)
}
37 changes: 37 additions & 0 deletions src/main/kotlin/org/tenten/bittakotlin/media/entity/Media.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.tenten.bittakotlin.media.entity

import jakarta.persistence.*
import org.hibernate.annotations.ColumnDefault
import org.springframework.data.annotation.CreatedDate
import org.springframework.data.jpa.domain.support.AuditingEntityListener
import org.tenten.bittakotlin.media.constant.MediaType
import java.time.LocalDateTime

@Entity
@EntityListeners(AuditingEntityListener::class)
data class Media (
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,

@Column(nullable = false)
val filename: String,

@Column(nullable = false)
@ColumnDefault("0")
val filesize: Int,

@Column(nullable = false)
@Enumerated(EnumType.STRING)
val filetype: MediaType,

@CreatedDate
@Column(nullable = false, updatable = false)
val savedAt: LocalDateTime? = null,

/*
회원과 1:N으로 연결할 예정입니다.
임시로 문자열로 구성해놓았습니다.
*/
val member: String? = null
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.tenten.bittakotlin.media.exception

import org.tenten.bittakotlin.media.constant.MediaError

class MediaException(
val code: Int,

override val message: String
) : RuntimeException(message) {
constructor(mediaError: MediaError) : this(
code = mediaError.code,
message = mediaError.message
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.tenten.bittakotlin.media.repository

import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.query.Param
import org.springframework.stereotype.Repository
import org.tenten.bittakotlin.media.entity.Media
import java.util.Optional

@Repository
interface MediaRepository : JpaRepository<Media, Long> {
@Query("SELECT m FROM Media m WHERE m.filename = :filename")
fun findByFilename(@Param("filename") filename: String): Optional<Media>

@Query("SELECT m FROM Media m WHERE m.filename = :filename AND m.member = :member")
fun findByFilenameAndUsername(@Param("filename") filename: String, @Param("member") member: String): Optional<Media>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.tenten.bittakotlin.media.service

import org.tenten.bittakotlin.media.dto.MediaRequestDto

interface MediaService {
fun read(requestDto: MediaRequestDto.Read): String

fun upload(requestDto: MediaRequestDto.Upload): String

fun delete(requestDto: MediaRequestDto.Delete): Unit
}
Loading

0 comments on commit 2332ea5

Please sign in to comment.