Skip to content

Commit

Permalink
Merge pull request #135 from Team-Umbba/feat/#133-album_api
Browse files Browse the repository at this point in the history
[FEAT] 기록하기(Album) API 구현
  • Loading branch information
jun02160 authored Mar 1, 2024
2 parents bf61c29 + 67a875d commit 6887693
Show file tree
Hide file tree
Showing 22 changed files with 578 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ public class SecurityConfig {
// "/log-out",
"/test", "/profile", "/health", "/actuator/health",
"/alarm/qna", "/alarm/drink",
"/demo/**"
"/demo/**",
"/album/image"
};

@Bean
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package sopt.org.umbba.api.controller.album;

import static sopt.org.umbba.api.config.jwt.JwtProvider.*;
import static sopt.org.umbba.common.exception.SuccessType.*;
import static sopt.org.umbba.external.s3.S3BucketPrefix.*;

import java.security.Principal;
import java.util.List;

import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
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.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import lombok.RequiredArgsConstructor;
import sopt.org.umbba.api.controller.album.dto.request.AlbumImgUrlRequestDto;
import sopt.org.umbba.api.controller.album.dto.request.CreateAlbumRequestDto;
import sopt.org.umbba.api.controller.album.dto.response.AlbumResponseDto;
import sopt.org.umbba.api.service.album.AlbumService;
import sopt.org.umbba.common.exception.dto.ApiResponse;
import sopt.org.umbba.external.s3.PreSignedUrlDto;
import sopt.org.umbba.external.s3.S3BucketPrefix;
import sopt.org.umbba.external.s3.S3Service;

@RestController
@RequestMapping("/album")
@RequiredArgsConstructor
public class AlbumController {

private final AlbumService albumService;
private final S3Service s3Service;

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public ApiResponse createAlbum(@Valid @RequestBody final CreateAlbumRequestDto request, final Principal principal, HttpServletResponse response) {
String imgUrl = s3Service.getS3ImgUrl(ALBUM_PREFIX.getValue(), request.getImgFileName());
Long albumId = albumService.createAlbum(request, imgUrl, getUserFromPrincial(principal));
response.setHeader("Location", "/album/" + albumId);
return ApiResponse.success(CREATE_ALBUM_SUCCESS);
}

// PreSigned Url 이용 (클라이언트에서 해당 URL로 업로드)
@PatchMapping("/image")
@ResponseStatus(HttpStatus.OK)
public ApiResponse<PreSignedUrlDto> getImgPreSignedUrl(@RequestBody final AlbumImgUrlRequestDto request) {
return ApiResponse.success(GET_PRE_SIGNED_URL_SUCCESS, s3Service.getPreSignedUrl(S3BucketPrefix.of(request.getImgPrefix())));
}

@DeleteMapping("/{albumId}")
@ResponseStatus(HttpStatus.OK)
public ApiResponse deleteAlbum(@PathVariable final Long albumId, final Principal principal) {
String imgUrl = albumService.deleteAlbum(albumId, getUserFromPrincial(principal));
s3Service.deleteS3Image(imgUrl);
return ApiResponse.success(DELETE_ALBUM_SUCCESS);
}

@GetMapping
@ResponseStatus(HttpStatus.OK)
public ApiResponse<List<AlbumResponseDto>> getAlbumList(final Principal principal) {
return ApiResponse.success(GET_ALBUM_LIST_SUCCESS, albumService.getAlbumList(getUserFromPrincial(principal)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package sopt.org.umbba.api.controller.album.dto.request;

import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class AlbumImgUrlRequestDto {

private String imgPrefix;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package sopt.org.umbba.api.controller.album.dto.request;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class CreateAlbumRequestDto {

@NotBlank(message = "제목은 필수 입력 값입니다.")
@Size(max = 15)
private String title;

@NotBlank(message = "소개글은 필수 입력 값입니다.")
@Size(max = 32)
private String content;

@NotBlank(message = "이미지 파일명은 필수 입력 값입니다.")
private String imgFileName;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package sopt.org.umbba.api.controller.album.dto.response;

import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

import lombok.Builder;
import lombok.Getter;
import sopt.org.umbba.domain.domain.album.Album;

@Getter
@Builder
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class AlbumResponseDto {

private Long albumId;
private String title;
private String content;
private String writer;
private String imgUrl;

public static AlbumResponseDto of(Album album) {
return AlbumResponseDto.builder()
.albumId(album.getId())
.title(album.getTitle())
.content(album.getContent())
.writer(album.getWriter())
.imgUrl(album.getImgUrl())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package sopt.org.umbba.api.service.album;

import java.util.List;
import java.util.stream.Collectors;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import lombok.RequiredArgsConstructor;
import sopt.org.umbba.api.controller.album.dto.request.CreateAlbumRequestDto;
import sopt.org.umbba.api.controller.album.dto.response.AlbumResponseDto;
import sopt.org.umbba.common.exception.ErrorType;
import sopt.org.umbba.common.exception.model.CustomException;
import sopt.org.umbba.domain.domain.album.Album;
import sopt.org.umbba.domain.domain.album.repository.AlbumRepository;
import sopt.org.umbba.domain.domain.parentchild.Parentchild;
import sopt.org.umbba.domain.domain.user.User;
import sopt.org.umbba.domain.domain.user.repository.UserRepository;

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class AlbumService {

private final AlbumRepository albumRepository;
private final UserRepository userRepository;

@Transactional
public Long createAlbum(final CreateAlbumRequestDto request, final String imgUrl, final Long userId) {

User user = getUserById(userId);
Parentchild parentchild = getParentchildByUser(user);

Album album = Album.builder()
.title(request.getTitle())
.content(request.getContent())
.imgUrl(imgUrl)
.writer(user.getUsername())
.parentchild(parentchild)
.build();
albumRepository.save(album);
album.setParentchild(parentchild);
parentchild.addAlbum(album);

return album.getId();
}

@Transactional
public String deleteAlbum(final Long albumId, final Long userId) {

User user = getUserById(userId);
Parentchild parentchild = getParentchildByUser(user);
Album album = getAlbumById(albumId);

album.deleteParentchild();
parentchild.deleteAlbum(album);
albumRepository.delete(album);

return album.getImgUrl();
}

public List<AlbumResponseDto> getAlbumList(final Long userId) {
User user = getUserById(userId);
Parentchild parentchild = getParentchildByUser(user);
List<Album> albumList = albumRepository.findAllByParentchildOrderByCreatedAtDesc(
parentchild);

return albumList.stream()
.map(AlbumResponseDto::of)
.collect(Collectors.toList());
}

private User getUserById(Long userId) { // TODO userId -> Parentchild 한번에 가져오기
return userRepository.findById(userId).orElseThrow(
() -> new CustomException(ErrorType.INVALID_USER)
);
}

private Album getAlbumById(Long albumId) {
return albumRepository.findById(albumId).orElseThrow(
() -> new CustomException(ErrorType.NOT_FOUND_ALBUM)
);
}

private Parentchild getParentchildByUser(User user) {
Parentchild parentchild = user.getParentChild();
if (parentchild == null) {
throw new CustomException(ErrorType.USER_HAVE_NO_PARENTCHILD);
}

return parentchild;
}
}

This file was deleted.

1 change: 1 addition & 0 deletions umbba-api/src/main/resources/application-dev1.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ cloud:
static: ${CLOUD_REGION_DEV}
s3:
bucket: ${BUCKET_NAME_DEV}
bucketImg: ${IMG_BUCKET_DEV}
stack:
auto: false
sqs:
Expand Down
1 change: 1 addition & 0 deletions umbba-api/src/main/resources/application-dev2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ cloud:
static: ${CLOUD_REGION_DEV}
s3:
bucket: ${BUCKET_NAME_DEV}
bucketImg: ${IMG_BUCKET_DEV}
stack:
auto: false
sqs:
Expand Down
1 change: 1 addition & 0 deletions umbba-api/src/main/resources/application-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ cloud:
static: ${CLOUD_REGION_LOCAL}
s3:
bucket: ${BUCKET_NAME_LOCAL}
bucketImg: ${IMG_BUCKET_LOCAL}
stack:
auto: false
sqs:
Expand Down
1 change: 1 addition & 0 deletions umbba-api/src/main/resources/application-prod1.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ cloud:
static: ${CLOUD_REGION_PROD}
s3:
bucket: ${BUCKET_NAME_PROD}
bucketImg: ${IMG_BUCKET_PROD}
stack:
auto: false
sqs:
Expand Down
1 change: 1 addition & 0 deletions umbba-api/src/main/resources/application-prod2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ cloud:
static: ${CLOUD_REGION_PROD}
s3:
bucket: ${BUCKET_NAME_PROD}
bucketImg: ${IMG_BUCKET_PROD}
stack:
auto: false
sqs:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public enum ErrorType {
ALREADY_EXISTS_PARENT_CHILD_USER(HttpStatus.BAD_REQUEST, "이미 해당 유저의 부모자식 관계가 존재합니다."),
ALREADY_QNA_LIST_FULL(HttpStatus.BAD_REQUEST, "이미 QNA 리스트가 가득 찼습니다"),

// Album
INVALID_BUCKET_PREFIX(HttpStatus.BAD_REQUEST, "유효하지 않은 S3 버킷 디렉토리명입니다."),

/**
* 401 UNAUTHORIZED
Expand Down Expand Up @@ -61,6 +63,7 @@ public enum ErrorType {
PARENTCHILD_HAVE_NO_QNALIST(HttpStatus.NOT_FOUND, "부모자식 관계가 가지고 있는 QnA 데이터가 없습니다."),
PARENTCHILD_HAVE_NO_OPPONENT(HttpStatus.NOT_FOUND, "부모자식 관계에 1명만 참여하고 있습니다."),
NOT_FOUND_SECTION(HttpStatus.NOT_FOUND, "해당 아이디와 일치하는 섹션이 없습니다."),
NOT_FOUND_ALBUM(HttpStatus.NOT_FOUND, "존재하지 않는 앨범입니다."),

/**
* About Apple (HttpStatus 고민)
Expand All @@ -80,6 +83,9 @@ public enum ErrorType {
DATABASE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "데이터베이스 관련 에러가 발생했습니다."),
FIREBASE_CONNECTION_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "파이어베이스 서버와의 연결에 실패했습니다."),
FAIL_TO_SEND_PUSH_ALARM(HttpStatus.INTERNAL_SERVER_ERROR, "푸시 알림 메세지 전송에 실패했습니다."),
FAIL_TO_GET_IMAGE_PRE_SIGNED_URL(HttpStatus.INTERNAL_SERVER_ERROR, "PreSigned Url을 가져오는 데 실패했습니다."),
FAIL_TO_DELETE_IMAGE(HttpStatus.INTERNAL_SERVER_ERROR, "S3 버킷에서 이미지를 삭제하는 데 실패했습니다."),
S3_BUCKET_GET_IMAGE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "S3 버킷에서 이미지를 불러오는 데 실패했습니다."),

// ETC
INDEX_OUT_OF_BOUNDS(HttpStatus.INTERNAL_SERVER_ERROR, "인덱스 범위를 초과했습니다."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ public enum SuccessType {
TEST_SUCCESS(HttpStatus.OK, "데모데이 테스트용 API 호출에 성공했습니다."),
RESTART_QNA_SUCCESS(HttpStatus.OK, "7일 이후 문답이 정상적으로 시작되었습니다."),
GET_USER_FIRST_ENTRY_SUCCESS(HttpStatus.OK, "유저의 첫 진입여부 조회에 성공했습니다."),
GET_PRE_SIGNED_URL_SUCCESS(HttpStatus.OK, "PreSigned Url 조회에 성공했습니다."),
IMAGE_S3_DELETE_SUCCESS(HttpStatus.OK, "S3 버킷에서 이미지를 삭제하는 데 성공했습니다."),
DELETE_ALBUM_SUCCESS(HttpStatus.OK, "앨범의 기록 삭제에 성공했습니다."),
GET_ALBUM_LIST_SUCCESS(HttpStatus.OK, "앨범의 기록 목록 조회에 성공했습니다."),


/**
Expand All @@ -37,6 +41,7 @@ public enum SuccessType {
CREATE_PARENT_CHILD_SUCCESS(HttpStatus.CREATED, "온보딩 정보를 입력받아 부모자식 관계를 생성하는 데 성공했습니다."),
MATCH_PARENT_CHILD_SUCCESS(HttpStatus.CREATED, "부모자식 관계 매칭에 성공했습니다."),
ANSWER_TODAY_QUESTION_SUCCESS(HttpStatus.CREATED, "오늘의 일일문답에 답변을 완료하였습니다."),
CREATE_ALBUM_SUCCESS(HttpStatus.CREATED, "앨범의 기록 등록에 성공했습니다."),

;

Expand Down
Loading

0 comments on commit 6887693

Please sign in to comment.