Skip to content

Commit

Permalink
Merge pull request #41 from juwon-code/main
Browse files Browse the repository at this point in the history
[Feat] 긴급 픽스 병합 요청
  • Loading branch information
deveunhwa authored Nov 6, 2024
2 parents c6ee8cd + 8e07579 commit de24158
Show file tree
Hide file tree
Showing 41 changed files with 572 additions and 488 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/CICD.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,5 @@ jobs:
cd ~/bitta-project
docker-compose down
docker-compose up -d --no-deps
sudo nginx -s reload
sudo nginx -s reload
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ COPY build/libs/*.jar /app.jar

# Set the entry point to run the application
EXPOSE 8080
CMD ["java", "-jar", "-Dspring.profiles.active=prod", "/app.jar"]
CMD ["java", "-jar", "-Dspring.profiles.active=prod", "/app.jar"]
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ services:
ports:
- "8082:8000"
environment:
- SPRING_PROFILES_ACTIVE=prod
- SPRING_PROFILES_ACTIVE=prod
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class FeedController (
}

@PostMapping
fun create(requestDto: FeedRequestDto.Create): ResponseEntity<Map<String, Any>> {
fun create(@RequestBody requestDto: FeedRequestDto.Create): ResponseEntity<Map<String, Any>> {
return ResponseEntity.ok(mapOf(
"message" to "피드를 성공적으로 등록했습니다.",
"result" to feedService.save(requestDto)
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/org/tenten/bittakotlin/feed/entity/Feed.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ data class Feed(

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

@LastModifiedDate
@Column(updatable = true, nullable = false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.tenten.bittakotlin.feed.entity.Feed
import org.tenten.bittakotlin.feed.entity.FeedMedia
import org.tenten.bittakotlin.feed.entity.key.FeedMediaId
import org.tenten.bittakotlin.feed.repository.FeedMediaRepository
import org.tenten.bittakotlin.media.dto.MediaRequestDto
import org.tenten.bittakotlin.media.dto.MediaResponseDto
Expand All @@ -22,10 +23,12 @@ class FeedMediaServiceImpl(

uploadRequestDtos.forEach { uploadRequestDto ->
val mediaResponseDto: MediaResponseDto.Upload = mediaService.upload(uploadRequestDto, profile)
val media = mediaResponseDto.media

feedMediaRepository.save(FeedMedia(
id = FeedMediaId(feedId = feed.id!!, mediaId = media.id!!),
feed = feed,
media = mediaResponseDto.media
media = media
))

responseDto.add(MediaResponseDto.Read(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import org.tenten.bittakotlin.feed.dto.FeedRequestDto
import org.tenten.bittakotlin.feed.dto.FeedResponseDto
import org.tenten.bittakotlin.profile.entity.Profile
import org.tenten.bittakotlin.profile.service.ProfileService
import java.time.LocalDateTime

@Service
@RequiredArgsConstructor
Expand Down Expand Up @@ -80,7 +81,9 @@ class FeedServiceImpl(
val feed: Feed = feedRepository.save(Feed(
title = requestDto.title,
content = requestDto.content,
profile = profile
profile = profile,
createdAt = LocalDateTime.now(),
updatedAt = LocalDateTime.now()
))

return if (requestDto.medias != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,10 @@ class MediaResponseDto {

val media: Media
)

data class PublicUpload (
val uploadUrl: String,

val readUrl: String
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ data class Media (

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

@ManyToOne
@JoinColumn
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ class MediaServiceImpl(
}

override fun upload(requestDto: MediaRequestDto.Upload, profile: Profile): MediaResponseDto.Upload {
val filename: String = UUID.randomUUID().toString()
val filetype: MediaType = checkMimetype(requestDto.mimetype)
val filename: String = UUID.randomUUID().toString() + getExtension(requestDto.mimetype)
val filesize: Int = checkFileSize(requestDto.filesize, filetype)

val media: Media = mediaRepository.save(Media(
Expand Down Expand Up @@ -94,6 +94,23 @@ class MediaServiceImpl(
throw MediaException(MediaError.WRONG_MIME_TYPE)
}

private fun getExtension(mimetype: String): String? {
val mimetypeMap = mapOf(
"image/jpeg" to "jpg",
"image/png" to "png",
"image/gif" to "gif",
"image/bmp" to "bmp",
"image/webp" to "webp",
"image/svg+xml" to "svg",
"video/mp4" to "mp4",
"video/webm" to "webm",
"video/ogg" to "ogg",
"video/x-msvideo" to "avi",
"video/x-matroska" to "mkv"
)
return mimetypeMap[mimetype]
}

private fun checkFileSize(filesize: Int, type: MediaType): Int {
val maxSize: Int = if (type == MediaType.IMAGE) {
imageMaxSize
Expand Down
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
import org.tenten.bittakotlin.media.dto.MediaResponseDto
import org.tenten.bittakotlin.profile.entity.Profile

interface ProfileImageService {
fun upload(requestDto: MediaRequestDto.Upload, profile: Profile): MediaResponseDto.PublicUpload

fun delete(requestDto: MediaRequestDto.Delete)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package org.tenten.bittakotlin.media.service

import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.tenten.bittakotlin.media.constant.MediaError
import org.tenten.bittakotlin.media.constant.MediaType
import org.tenten.bittakotlin.media.dto.MediaRequestDto
import org.tenten.bittakotlin.media.dto.MediaResponseDto
import org.tenten.bittakotlin.media.entity.Media
import org.tenten.bittakotlin.media.exception.MediaException
import org.tenten.bittakotlin.media.repository.MediaRepository
import org.tenten.bittakotlin.profile.entity.Profile
import java.util.*

@Service
class ProfileImageServiceImpl (
private val mediaRepository: MediaRepository,

private val s3Service: S3Service,

@Value("\${file.max.size.image}")
private val maxsize: Int
) : ProfileImageService {
companion object {
private val logger: Logger = LoggerFactory.getLogger(ProfileImageServiceImpl::class.java)
}

@Transactional
override fun upload(requestDto: MediaRequestDto.Upload, profile: Profile): MediaResponseDto.PublicUpload {
val filetype: MediaType = checkMimetype(requestDto.mimetype)
val filename: String = UUID.randomUUID().toString() + getExtension(requestDto.mimetype)
val filesize: Int = checkFileSize(requestDto.filesize)

mediaRepository.save(Media(
filename = filename,
filetype = filetype,
filesize = filesize,
profile = profile
))

return s3Service.getPublicUploadUrl(filename, requestDto.mimetype)
}

@Transactional
override fun delete(requestDto: MediaRequestDto.Delete) {
val filename: String = requestDto.filename

mediaRepository.findByFilename(filename).orElseThrow { MediaException(MediaError.CANNOT_FOUND) }
}

private fun getExtension(mimetype: String): String? {
val mimetypeMap = mapOf(
"image/jpeg" to "jpg",
"image/png" to "png",
"image/gif" to "gif",
"image/bmp" to "bmp",
"image/webp" to "webp",
"image/svg+xml" to "svg",
)
return mimetypeMap[mimetype]
}

private fun checkMimetype(mimetype: String): MediaType {
if (mimetype.matches(Regex("image/(jpeg|png|gif|bmp|webp|svg\\+xml)"))) {
return MediaType.IMAGE
}

throw MediaException(MediaError.WRONG_MIME_TYPE)
}

private fun checkFileSize(filesize: Int): Int {
if (filesize > maxsize) {
throw MediaException(MediaError.WRONG_FILE_SIZE)
}

return filesize
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package org.tenten.bittakotlin.media.service

import org.tenten.bittakotlin.media.dto.MediaResponseDto

interface S3Service {
fun getReadUrl(name: String): String

fun getUploadUrl(name: String, contentType: String): String

fun getPublicUploadUrl(name: String, contentType: String): MediaResponseDto.PublicUpload

fun delete(filename: String): Unit
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Service
import org.tenten.bittakotlin.media.constant.MediaError
import org.tenten.bittakotlin.media.dto.MediaResponseDto
import org.tenten.bittakotlin.media.exception.MediaException
import software.amazon.awssdk.regions.Region
import software.amazon.awssdk.services.s3.S3Client
import software.amazon.awssdk.services.s3.model.HeadObjectRequest
import software.amazon.awssdk.services.s3.model.NoSuchKeyException
Expand Down Expand Up @@ -56,6 +58,13 @@ class S3ServiceImpl(
return presignedPutRequest.url().toString()
}

override fun getPublicUploadUrl(name: String, contentType: String): MediaResponseDto.PublicUpload {
return MediaResponseDto.PublicUpload(
uploadUrl = getUploadUrl(name, contentType),
readUrl = "https://${s3Bucket}.s3.${Region.AP_NORTHEAST_2}.amazonaws.com/$name"
)
}

override fun delete(name: String): Unit {
existsInBucket(name)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import io.swagger.v3.oas.annotations.media.Content
import io.swagger.v3.oas.annotations.media.Schema
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.tags.Tag
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.security.access.AccessDeniedException
Expand All @@ -14,17 +17,55 @@ import org.tenten.bittakotlin.member.dto.MemberResponseDTO
import org.tenten.bittakotlin.member.exception.MemberException
import org.tenten.bittakotlin.member.repository.MemberRepository
import org.tenten.bittakotlin.member.service.MemberService
import org.tenten.bittakotlin.security.jwt.JWTUtil
import org.tenten.bittakotlin.security.util.JwtTokenUtil

@Tag(name = "회원관리 API 컨트롤러", description = "회원과 관련된 RestAPI 제공 컨트롤러")
@RestController
@RequestMapping("/api/v1/member")
class MemberController(
private val memberService: MemberService,
private val jwtUtil: JWTUtil,
private val memberRepository: MemberRepository
private val memberRepository: MemberRepository,
private val jwtTokenUtil: JwtTokenUtil
) {

@PostMapping("/login")
fun login(@RequestBody requestDto: MemberRequestDTO.Login): ResponseEntity<Map<String, Any>> {
val responseDto: MemberResponseDTO.Login = memberService.login(requestDto)

val headers = HttpHeaders().apply {
add(HttpHeaders.SET_COOKIE, getCookieString("accessToken", responseDto.accessToken, 3600, false))
add(HttpHeaders.SET_COOKIE, getCookieString("refreshToken", responseDto.refreshToken, 604800, false))
add(HttpHeaders.SET_COOKIE, getCookieString("profileId", responseDto.profileId.toString(), 604800, true))
add(HttpHeaders.SET_COOKIE, getCookieString("profileUrl", responseDto.profileUrl, 604800, true))
}

val body = mapOf("message" to "로그인이 성공했습니다.")

return ResponseEntity(body, headers, HttpStatus.OK)
}

@PostMapping("/logout")
fun logout(request: HttpServletRequest, response: HttpServletResponse): ResponseEntity<Map<String, Any>> {
val headers = HttpHeaders().apply {
add(HttpHeaders.SET_COOKIE, getCookieString("accessToken", null, 0, false))
add(HttpHeaders.SET_COOKIE, getCookieString("refreshToken", null, 0, false))
add(HttpHeaders.SET_COOKIE, getCookieString("profileId", null, 0, true))
add(HttpHeaders.SET_COOKIE, getCookieString("profileUrl", null, 0, true))
}

val body = mapOf("message" to "로그아웃이 성공했습니다.")

return ResponseEntity(body, headers, HttpStatus.OK)
}


private fun getCookieString(name: String, token: String?, ageMax: Int, isPublic: Boolean): String {
return buildString {
append("$name=$token; Path=/; Max-Age=$ageMax;")
if (!isPublic) append(" HttpOnly;")
}
}

// 회원가입

@Operation(
Expand Down Expand Up @@ -77,7 +118,7 @@ class MemberController(
@RequestHeader("access") token: String // JWT 토큰을 헤더에서 추출
): ResponseEntity<Void> {
// 현재 로그인한 사용자 username 추출
val usernameFromToken = jwtUtil.getUsername(token)
val usernameFromToken = jwtTokenUtil.getUsername(token)

// id로 회원 정보 조회
val member = memberRepository.findById(id)
Expand All @@ -96,7 +137,7 @@ class MemberController(

@DeleteMapping("/{id}")
fun remove(@PathVariable id: Long, @RequestHeader("access") token: String): ResponseEntity<String> {
val username = jwtUtil.getUsername(token)
val username = jwtTokenUtil.getUsername(token)
val member = memberRepository.findById(id)
.orElseThrow { IllegalArgumentException("Member not Found.") }
if(member.username != username) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,13 @@ class MemberResponseDTO {
val address: String
)

data class Login(
val accessToken: String,

val refreshToken: String,

val profileId: Long,

val profileUrl: String
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,4 @@ data class Member(

@Column(nullable = false)
var role: String? = null


)
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import org.tenten.bittakotlin.member.dto.MemberRequestDTO
import org.tenten.bittakotlin.member.dto.MemberResponseDTO

interface MemberService {
fun login(requestDto: MemberRequestDTO.Login): MemberResponseDTO.Login

fun join(joinDTO: MemberRequestDTO.Join) // Join 기능 병합

Expand Down
Loading

0 comments on commit de24158

Please sign in to comment.