Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ repositories {
extra["springCloudVersion"] = "2024.0.0-RC1"

dependencies {
implementation("org.springframework.boot:spring-boot-starter-aop")
implementation("org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j")
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.springframework.boot:spring-boot-starter-data-redis")
Expand All @@ -49,6 +51,7 @@ dependencies {
testImplementation("org.springframework.security:spring-security-test")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
implementation("com.querydsl:querydsl-jpa:5.0.0:jakarta")
implementation ("org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE")
kapt("com.querydsl:querydsl-apt:5.0.0:jakarta")
kapt("jakarta.annotation:jakarta.annotation-api")
kapt("jakarta.persistence:jakarta.persistence-api")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ class BoardProcessor(
commentCount = 0,
likeCount = 0,
isFiltered = false,
createdAt = LocalDateTime.now()
createdAt = LocalDateTime.now(),
imageUrl = writeCommunityBoardDto.imageUrl,
)

return boardRepository.save(board)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ class Board(
var isFiltered: Boolean,

@Column(name = "created_at", nullable = false)
val createdAt: LocalDateTime
val createdAt: LocalDateTime,

@Column(name = "image_url", length = 800, nullable = true)
val imageUrl: String?,
) {

fun minusLikeCount() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ data class WriteCommunityBoardDto(
@Size(max = 1000)
val content: String,
@NotNull
val gameCategory: GameCategory
val gameCategory: GameCategory,
val imageUrl: String?,
)

data class GetCommunityBoardResDto(
Expand Down Expand Up @@ -48,6 +49,7 @@ data class GetCommunityBoardInfoResDto(
val isLiked: Boolean,
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss")
val createdAt: LocalDateTime,
val imageUrl: String?,
val stage: StageDto,
val commentCount: Int,
val comment: List<CommentDto>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ class CommunityCustomRepositoryImpl(
createdAt = board.createdAt,
stage = stageDto,
commentCount = board.commentCount,
comment = commentDto
comment = commentDto,
imageUrl = board.imageUrl
)

return response
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package gogo.gogostage.domain.image.application

import gogo.gogostage.domain.image.application.dto.ImageUploadResDto
import org.springframework.stereotype.Component

@Component
class ImageMapper {

fun mapUpload(url: String): ImageUploadResDto =
ImageUploadResDto(
imageUrl = url
)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package gogo.gogostage.domain.image.application

import gogo.gogostage.domain.image.application.dto.ImageUploadResDto
import org.springframework.web.multipart.MultipartFile

interface ImageService {
fun upload(image: MultipartFile): ImageUploadResDto
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package gogo.gogostage.domain.image.application

import gogo.gogostage.domain.image.application.dto.ImageUploadResDto
import gogo.gogostage.global.aws.s3.S3Uploader
import org.springframework.stereotype.Service
import org.springframework.web.multipart.MultipartFile

@Service
class ImageServiceImpl(
private val s3Uploader: S3Uploader,
private val imageMapper: ImageMapper,
private val imageValidator: ImageValidator
): ImageService {

override fun upload(image: MultipartFile): ImageUploadResDto {
imageValidator.validImage(image)
val url = s3Uploader.upload(image)
return imageMapper.mapUpload(url)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package gogo.gogostage.domain.image.application

import gogo.gogostage.global.error.StageException
import org.springframework.stereotype.Component
import org.springframework.web.multipart.MultipartFile

@Component
class ImageValidator {

fun validImage(image: MultipartFile) {
val list = listOf("jpg", "png", "gif")
val splitFile = image.originalFilename.toString().split(".")

if(splitFile.size != 2)
throw StageException("Image Extension Invalid", 400)

val extension = splitFile[1].lowercase()

if(list.none { it == extension })
throw StageException("Image Extension Invalid", 400)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package gogo.gogostage.domain.image.application.dto

data class ImageUploadResDto(
val imageUrl: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package gogo.gogostage.domain.image.presentation

import gogo.gogostage.domain.image.application.ImageService
import gogo.gogostage.domain.image.application.dto.ImageUploadResDto
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestPart
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.multipart.MultipartFile

@RestController
@RequestMapping("/stage/image")
class ImageController(
private val imageService: ImageService
) {

@PostMapping
fun uploadImage(
@RequestPart("image") file: MultipartFile
): ResponseEntity<ImageUploadResDto> {
val response = imageService.upload(file)
return ResponseEntity.status(HttpStatus.CREATED).body(response)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ class StageCustomRepositoryImpl(
)
.from(stageParticipant)
.where(predicate)
.orderBy(stageParticipant.point.desc())
.orderBy(
stageParticipant.point.desc(),
stageParticipant.studentId.asc()
)
.offset(pageable.offset)
.limit(pageable.pageSize.toLong())
.fetch()
Expand Down
31 changes: 31 additions & 0 deletions src/main/kotlin/gogo/gogostage/global/aws/s3/S3Config.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package gogo.gogostage.global.aws.s3

import com.amazonaws.auth.AWSCredentials
import com.amazonaws.auth.AWSStaticCredentialsProvider
import com.amazonaws.auth.BasicAWSCredentials
import com.amazonaws.services.s3.AmazonS3
import com.amazonaws.services.s3.AmazonS3ClientBuilder
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class S3Config {
@Value("\${cloud.aws.credentials.access-key}")
private val accessKey: String? = null

@Value("\${cloud.aws.credentials.secret-key}")
private val secretKey: String? = null

@Value("\${cloud.aws.region.static}")
private val region: String? = null

@Bean
fun amazonS3(): AmazonS3 {
val awsCredentials: AWSCredentials = BasicAWSCredentials(accessKey, secretKey)
return AmazonS3ClientBuilder.standard()
.withRegion(region)
.withCredentials(AWSStaticCredentialsProvider(awsCredentials))
.build()
}
}
25 changes: 25 additions & 0 deletions src/main/kotlin/gogo/gogostage/global/aws/s3/S3Uploader.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package gogo.gogostage.global.aws.s3

import com.amazonaws.services.s3.AmazonS3
import com.amazonaws.services.s3.model.ObjectMetadata
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component
import org.springframework.web.multipart.MultipartFile
import java.util.*

@Component
class S3Uploader(
private val amazonS3: AmazonS3
) {

@Value("\${cloud.aws.s3.bucket}")
private val bucket: String? = null

fun upload(file: MultipartFile): String {
val profileName = "${bucket}/${UUID.randomUUID()}${file.originalFilename}"
val metadata = ObjectMetadata()
metadata.contentLength = file.inputStream.available().toLong()
amazonS3.putObject(bucket, profileName, file.inputStream, metadata)
return amazonS3.getUrl(bucket, profileName).toString()
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package gogo.gogostage.global.config


import gogo.gogostage.domain.image.application.ImageMapper
import gogo.gogostage.global.filter.AuthenticationFilter
import gogo.gogostage.global.filter.LoggingFilter
import gogo.gogostage.global.handler.CustomAccessDeniedHandler
Expand Down Expand Up @@ -90,6 +91,9 @@ class SecurityConfig(
httpRequests.requestMatchers(HttpMethod.POST, "/stage/community/comment/{board_id}").hasAnyRole(Authority.USER.name, Authority.STAFF.name)
httpRequests.requestMatchers(HttpMethod.POST, "/stage/community/comment/like/{board_id}").hasAnyRole(Authority.USER.name, Authority.STAFF.name)

// image
httpRequests.requestMatchers(HttpMethod.POST, "/stage/image").hasAnyRole(Authority.USER.name, Authority.STAFF.name)

// server to server
httpRequests.requestMatchers(HttpMethod.GET, "/stage/api/point/{stage_id}").access { _, context -> hasIpAddress(context) }
httpRequests.requestMatchers(HttpMethod.GET, "/stage/api/match/info").access { _, context -> hasIpAddress(context) }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package gogo.gogostage.global.feign.client

import gogo.gogostage.global.feign.fallback.BettingClientFallbackFactory
import gogo.gogostage.global.internal.betting.stub.BettingBundleDto
import gogo.gogostage.global.internal.betting.stub.TotalBettingPointDto
import org.springframework.cloud.openfeign.FeignClient
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestParam

@FeignClient(name = "gogo-betting")
@FeignClient(name = "gogo-betting", fallbackFactory = BettingClientFallbackFactory::class)
interface BettingClient {
@GetMapping("/betting/bundle")
fun bundle(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package gogo.gogostage.global.feign.fallback

import gogo.gogostage.global.feign.client.BettingClient
import gogo.gogostage.global.filter.LoggingFilter
import gogo.gogostage.global.internal.betting.stub.BettingBundleDto
import gogo.gogostage.global.internal.betting.stub.TotalBettingPointDto
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.cloud.openfeign.FallbackFactory
import org.springframework.stereotype.Component

@Component
class BettingClientFallbackFactory : FallbackFactory<BettingClient> {

private val log: Logger = LoggerFactory.getLogger(LoggingFilter::class.java)

override fun create(cause: Throwable): BettingClient {
return object : BettingClient {
override fun bundle(matchIds: List<Long>, studentId: Long): BettingBundleDto {
log.error("BettingClient.bundle FallBack - matchIds: $matchIds, studentId: $studentId")
return BettingBundleDto(
bettings = emptyList(),
)
}

override fun totalBettingPoint(matchIds: List<Long>, studentId: Long): TotalBettingPointDto {
log.error("BettingClient.totalBettingPoint FallBack - matchIds: $matchIds, studentId: $studentId")
throw cause
}
}
}
}
Loading