Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BE] S3 를 통한 Image Upload 기능 구현 #428

Merged
merged 52 commits into from
Sep 21, 2023
Merged

Conversation

kpeel5839
Copy link
Collaborator

@kpeel5839 kpeel5839 commented Sep 17, 2023

작업 대상

Image 를 Upload 하는 API 수정

📄 작업 내용

  • POST /pins (핀 생성과 함께 Image 리스트를 받을 수 있도록 수정)
  • POST /pins/images (Pin 에 단일 이미지를 추가하는 기능)
  • POST /topics/new
  • POST /topics/merge
  1. 기존에 위 API 들 모두 String 으로 Image URL 을 받고 있었는데, 해당 부분을 MultipartFile 를 받을 수 있도록 수정
  2. 받은 MultipartFile 을 AWS S3 2023/team-projects/2023-map-be-fine/(dev, prod) 에 upload
  3. cloud front url + "/" + 생성한 image name 으로 DB 에 Image URL 저장
  4. DB 에 저장된 Image URL 로 S3 에 올라가 있는 Image 로 접근가능

이 이외에 이해가 안되는 부분이 있다면 질문 남겨주시면 감사할 것 같아요!

🙋🏻 주의 사항

  • 의존성
image
  • Flow Chart
flowchart LR
	PinController --> PinCommandService --> a[S3ImageService] == Upload File 로 감싸서 넘겨줌 ==> b[S3Client] == 업로드 ==> c[S3]
	TopicController --> TopicCommandService --> a
Loading

여러분들! 제가 revert 와 cherry-pick 을 이용해서 S3 관련해서 변경사항을 이번 PR 에 담아보려 했는데, 잘 안되네요 ㅠㅠ

죄송합니다 ㅠㅠ

대신 중요 코드들에 대해서는 본문에다가 남기도록 하겠습니다.

불편을 끼쳐드려 죄송합니다.

중요 코드

의존성 추가

implementation platform('com.amazonaws:aws-java-sdk-bom:1.11.1000')
implementation 'com.amazonaws:aws-java-sdk-s3'

이번에 s3 를 위해서 추가한 의존성이에용

S3 에 접근하기 위해 필요한 Bean 등록

@Bean  
public InstanceProfileCredentialsProvider instanceProfileCredentialsProvider() {  
    return InstanceProfileCredentialsProvider.getInstance();  
}  
  
@Bean  
public AmazonS3 amazonS3() {  
    return AmazonS3ClientBuilder.standard()  
            .withRegion(Regions.AP_NORTHEAST_2)  
            .withCredentials(instanceProfileCredentialsProvider())  
            .build();  
}

S3 에 접근하기 위해 필요한 빈을 등록 및 설정하는 코드입니다.

기존 코드의 변경을 최소화하고 Image 를 받기 위해 변경한 DTO

제가 S3 업로드 기능 구현을 진행하면서 처음으로 문제를 맞닥뜨렸던 부분은, MultipartFile 을 한 객체에 포함시켜서 받을 수 없다는 점이었는데요.

아래와 같은 상황을 예로 들 수 있을 것 같아요.

public record TopicCreateRequest(  
        String name,  
		MultipartFile image,
        String description,  
        Publicity publicity,  
        PermissionType permissionType,  
        List<Long> pins  
) {  
}

그렇기 때문에 Dto 에서 image 를 빼고 Controller 에서 직접 MultipartFile 을 받는 방법밖에 없었어요.

그래서 아래와 같이 새로운 TopicCreateRequestWithoutImage 와 같은 DTO 를 만들어 TopicCommandService, PinCommandService 의 saveTopic, addImage 파라미터를 변경하지 않는 방법으로 진행했어요

  • Topic 저장
public record TopicCreateRequestWithoutImage(  
        String name,  
        String description,  
        Publicity publicity,  
        PermissionType permissionType,  
        List<Long> pins  
) {  
}
@LoginRequired  
@PostMapping( // MultipartFile 과 Json 을 한번에 받기 위해서 Consume 을 해당 방법으로 지정해야 했습니다.
        value = "/new",  
        consumes = {MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_VALUE}  
)  
public ResponseEntity<Void> create(  
        AuthMember member,  
        @RequestPart TopicCreateRequestWithoutImage request,  
        @RequestPart(required = false) MultipartFile image  
) {  
    TopicCreateRequest topicCreateRequest = TopicCreateRequest.of(request, image);  
    Long topicId = topicCommandService.saveTopic(member, topicCreateRequest);  
  
    return ResponseEntity.created(URI.create("/topics/" + topicId))  
            .build();  
}
  • Pin 에 이미지 추가
@LoginRequired  
@PostMapping(  
        value = "/images",  
        consumes = {MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_VALUE}  
)  
public ResponseEntity<Void> addImage(  
        AuthMember member,  
        @RequestPart Long pinId,  
        @RequestPart(required = false) MultipartFile image  
) {  
    pinCommandService.addImage(member, new PinImageCreateRequest(pinId, image));  
  
    return ResponseEntity.status(HttpStatus.CREATED).build();  
}

S3 에 이미지를 upload 를 하기 위해 필요한 클래스들

  • S3 Service
@Service
public interface S3Service { 
// Test 환경에서는 다른 Component 를 띄워 실제 S3 에 접근하지 않기 위해 interface 를 만들었습니다.

    String upload(MultipartFile multipartFile);

}
  • 테스트에서 사용되는 S3 Service 구현체
@Service
@Profile("test")
public class TestS3ServiceImpl implements S3Service {

    @Override
    public String upload(MultipartFile multipartFile) { // 그냥 String 만 반환하도록 했습니다.
        return "https://mapbefine.github.io/favicon.png";
    }

}
  • 실제 Service 에 사용되는 S3Service 구현체
@Service
@Profile("!test")
public class S3ServiceImpl implements S3Service {

    @Value("${prefix.upload.path}") // prefix.upload.path 는 private repo 에서 확인 부탁드려요
    private String prefixUploadPath;
    private final S3Client s3Client;

    public S3ServiceImpl(S3Client s3Client) {
        this.s3Client = s3Client;
    }

    @Override
    public String upload(MultipartFile multipartFile) {
        try {
            UploadFile uploadFile = UploadFile.of(multipartFile);
            s3Client.upload(uploadFile);
            return getUploadPath(uploadFile);
        } catch (IOException exception) {
            throw new RuntimeException(exception);
        }
    }

    private String getUploadPath(final UploadFile uploadFile) {
        return String.join(
                "/",
                prefixUploadPath,
                uploadFile.getOriginalFilename()
        );
    }

}
  • S3 Client (실제로 S3 에 업로드를 진행하는 부분입니다.)
@Component  
public class S3Client {  
  
    @Value("${s3.bucket}")  
    private String bucket;  
    private final AmazonS3 amazonS3;  
  
    public S3Client(AmazonS3 amazonS3) {  
        this.amazonS3 = amazonS3;  
    }  
  
    public void upload(MultipartFile multipartFile) throws IOException {  
        File tempFile = null;  
  
        try {  
            tempFile = File.createTempFile("upload_", ".tmp");  
            multipartFile.transferTo(tempFile);  
            amazonS3.putObject(new PutObjectRequest(  
                    bucket,  
                    multipartFile.getOriginalFilename(),  
                    tempFile  
            ));  
        } catch (IOException exception) {  
            throw new IOException(exception);  
        } finally {  
            removeTempFileIfExists(tempFile);  
        }  
    }  
  
    private void removeTempFileIfExists(File tempFile) {  
        if (!Objects.isNull(tempFile) && tempFile.exists()) {  
            tempFile.delete();  
        }  
    }  
  
    public void delete(String key) {  
        // 현재는 일단 기능만 만들어놓고, API 는 만들어놓지 않았습니다 회의를 통해서 결정해야 할 사항이 있는 것 같아서요!  
        amazonS3.deleteObject(new DeleteObjectRequest(bucket, key));  
    }  
  
}
  • MultipartFile 을 구현하여, 이름을 커스터마이징합니다. (사용할 필요가 없는 Override 된 메서드들은 고정된 값을 반환하도록 하였습니다.)
public class UploadFile implements MultipartFile {

    private final String fileName;
    private final byte[] bytes;

    private UploadFile(
            String fileName,
            byte[] bytes
    ) {
        this.fileName = fileName;
        this.bytes = bytes;
    }

    public static UploadFile of(
            MultipartFile multipartFile
    ) throws IOException {
        ImageName imageName = ImageName.from(multipartFile.getOriginalFilename());
        byte[] multipartFileBytes = multipartFile.getBytes();

        return new UploadFile(imageName.getFileName(), multipartFileBytes);
    }

    @Override
    public String getName() {
        return fileName;
    }

    @Override
    public String getOriginalFilename() {
        return fileName;
    }

    @Override
    public String getContentType() {
        return null;
    }

    @Override
    public boolean isEmpty() {
        return false;
    }

    @Override
    public long getSize() {
        return 0;
    }

    @Override
    public byte[] getBytes() throws IOException {
        return bytes;
    }

    @Override
    public InputStream getInputStream() throws IOException {
        return new ByteArrayInputStream(bytes);
    }

    @Override
    public Resource getResource() {
        return MultipartFile.super
                .getResource();
    }

    @Override
    public void transferTo(File dest) throws IOException, IllegalStateException {
        try (FileOutputStream fileOutputStream = new FileOutputStream(dest)) {
            fileOutputStream.write(bytes);
        }
    }

}
  • Image 이름은 DateTimeFormatter 를 이용합니다! (현재시각을 정해진 양식으로 치환 후, 확장자를 붙여 반환)
public class ImageName {  
  
    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSSSSS");  
  
    private final String fileName;  
  
    private ImageName(String fileName) {  
        this.fileName = fileName;  
    }  
  
    public static ImageName from(String originalFileName) {  
        String fileName = FORMATTER.format(LocalDateTime.now());  
        String extension = getExtension(originalFileName).getExtension();  
  
        return new ImageName(fileName + extension);  
    }  
  
    private static ImageExtension getExtension(String originalFileName) {  
        return ImageExtension.fromByImageFileName(originalFileName);  
    }  
  
    public String getFileName() {  
        return fileName;  
    }  
  
}

마지막으로 Test 코드에서 바뀐 부분입니다.

  • 테스트에서는 profile 를 분리하여 다른 component 를 띄우기 위해 test 패키지의 application.yml 에 아래 설정을 추가하였습니다.
profiles:  
  active: test
  • Topic 을 생성하는 부분
private ExtractableResponse<Response> createNewTopic(TopicCreateRequestWithoutImage request, String authHeader) {  
	mockFile = new File(  
	        getClass().getClassLoader()  
	                .getResource("test.png")  
	                .getPath()  
	);
    return RestAssured.given()  
            .log().all()  
            .header(AUTHORIZATION, authHeader)  
            .multiPart("image", mockFile, MediaType.MULTIPART_FORM_DATA_VALUE)  
            .multiPart("request", request, MediaType.APPLICATION_JSON_VALUE)  
            .when().post("/topics/new")  
            .then().log().all()  
            .extract();  
}

Topic 을 생성하는 테스트 코드와 Pin 을 생성하는 테스트 코드는 메커니즘이 완전히 동일하기 떄문에 해당 Topic 을 생성하는 테스트 코드를 통해서만 설명을 진행하겠습니다.

일단, 달라진 점은 ContentType 을 오롯이 Application-Json 으로 진행하지 않는다는 점이고, RestAssuredmultiPart 메서드를 통해 ("파라미터 이름", 파라미터로 넘길 값, 파라미터의 Content Type) 을 설정해주고, 이전과 동일하게 요청을 보내면 됩니다.

이 이외에 S3 에 대해 전혀 모르는 분들에게 설명을 덧붙이자면 S3 는 그냥 외부에 있는 DB 라고 생각하시면 되고, 해당 S3 에 직접적으로 접근하는 것은 EC2 에서만 가능합니다. (보안 정책)

그렇기 때문에 S3 에 올라가 있는 파일에 여러분들이 브라우저를 통해 URL 로 직접 접근하려고하면 접근하지 못하죠. (현재 우리 s3 의 위치가 2023-team-projects/2023-map-be-fine/{dev/prod} 이니까, 2023-team-projects/2023-map-be-fine/{dev/prod}/{fileName} 로 접근하더라도 접근하지 못합니다.)

그렇기 때문에 해당 S3 에 접근하려면 Cloud Front 를 통해 접근해야 합니다. 일종의 우회라고 생각하시면 됩니다.

그래서 S3 에 올라가 있는 이미지에 접근할 때에는 Cloud Front Url + "/" + 저장한 이미지 파일 이름 으로 접근한다면? 브라우저에서도 접근이 가능한 것입니다.

S3 에다가 이미지를 업로드 하는 부분은 S3Service, S3Client 에 있기 때문에 설명은 생략하도록 하겠습니다!

긴 설명글 읽어주셔서 정말 감사합니다!

이야기 해보면 좋을 것 같은 것들

이야기 해봐야 할 사항들이 조금 있는 것 같아요! 이야기 해본 다음 제가 계속해서 보완해나가도록 하겠습니다.

  • DB 에 저장해야 할 내용은 Image URL 이라고 생각하시나요? 아니면 fileName 이름이라고 생각하시나요? ex) imageURL -> https://.../imageName.png, image 이름 -> imageName.png

    • Image URL 을 저장한다면?
      • S3 에 이미지를 올린 데이터의 경우에는 그냥 끝에 fileName 만 따서 삭제하면 됩니다. 이건 쉬워요 ex ) url 은 cloud front url + "/" + fileName 이니까 file name 만 따서 S3 에 삭제요청 날리면됩니다.
    • fileName 을 선택한다면?
      • fileName 을 저장하는 것을 선택한다면, 기존의 데이터를 활용하지 못할 것 같아요.
      • Image 이름을 DB 에 저장한다면, 실제로 이미지에 접근할 때 cloud front urlprefix 에 붙여야 하는데, 기존의 데이터들은 https://... 이렇게 저장되어 있어, cloud front url + https://... 로 이미지를 요청하게 되요.
  • 기존 데이터를 어떻게 활용할 것일까?

    • 기존 데이터는 그냥 쌩 인터넷에 올라와 있는 url 이에요
      • 이 때 S3 에 올라가 있는 사진을 지우려고 한다고 가정해볼게요.
        • 그러면 어떤 것은 실제로 S3 에서 지울 수 있는 사진이고
        • 어떤 것은 인터넷에 올라와 있는 사진이니 지울 수 없을거에요.
        • 이 부분은 딱히 문제가 되지 않을까요??
        • 그냥 DB 에서만 지우면 되니까 전혀 문제가 되지 않을까요?
    • ec2 에 작은 Spring Boot Project 를 하나 띄워서 현재 DB 상에 있는 image url 을 모두 파일로 저장하고, S3 에 올리는 방법도 있을 것 같아요 (기존 데이터를 S3 에 이관하기 위해서)
  • 이미지 삭제.. 전에 많이 이야기 나눴지만 막상 S3 와 함께 구현해보려 하니까 쉽지 않아요

    • 현재는 핀을 복사하는 기능이 존재해요
    • 그렇기 때문에 Image URL 과 같은 정보는 그대로 복사가 되죠.
    • 여기서 발생하는 문제는 실제로 S3 의 이미지를 지울 때 발생해요.
    • 어떤 한 유저가 Image 지울 때 같이 S3 의 이미지도 지우면 동일한 Image URL 을 가지고 있던 핀들은 모두 이미지를 조회할 수 없게 되요.
    • 간단하게 이 문제를 해결할 방법을 생각해봤는데, PinImage Entity 에서 imageURL 을 unique 하게 만들어, 동일한 URL 이라면 하나로 묶어주는거에요.
    • 위 방법을 채택하게 되면 이미지를 추가할 때의 로직이 현재와 조금 달라지겠지만, 어떤 User 가 해당 image 를 삭제했을 때, 이제 이 image url 이 DB 상에 하나도 존재하지 않는지 확인하고 S3 에서 제거할 수 있어요.
    • 근데 여기서 문제가 발생하는데, 근데 또 기본 이미지는 어떻게 하죠? 허허허허... 미치겠네요
  • 아직 Rest Docs 를 해결하지 못했더연 도이 말씀대로 notion 에다가 일단 명세 남겨놓고 해결해나가겠습니다. 똥송해연..

스크린샷

📎 관련 이슈

closed #386

레퍼런스

https://blog.pium.life/aws-s3-apply/

@kpeel5839 kpeel5839 added BE 백엔드 관련 이슈 우선순위 : 상 feat 새로운 기능 개발 labels Sep 17, 2023
@kpeel5839 kpeel5839 added this to the 5차 데모데이 milestone Sep 17, 2023
@kpeel5839 kpeel5839 self-assigned this Sep 17, 2023
@github-actions
Copy link

github-actions bot commented Sep 17, 2023

Unit Test Results

  67 files    67 suites   22s ⏱️
295 tests 295 ✔️ 0 💤 0
306 runs  306 ✔️ 0 💤 0

Results for commit 634afdc.

♻️ This comment has been updated with latest results.

@yoondgu
Copy link
Collaborator

yoondgu commented Sep 17, 2023

전체 코드 흐름 파악, 아래에 주신 질문 읽고 전체적인 리뷰 먼저 코멘트로 남겨둡니다!
코드에 대한 리뷰는 내일 아침에 진행할게용

구현 내용 관련 질문

  1. 서비스 메서드의 시그니처를 유지해야 하는 이유가 있을까용? TopicCreateDtoWithOutImage -> TopicCreateDto 이렇게 Dto에 depth가 있는 게 조금 어색하게 느껴져서요! Dto는 하나만 사용하고, Service에서 MultipartFile을 직접 받으면 안되나요?!

  2. S3는 이미지를 저장하기 위한 기술의 이름인데, 상위 패키지명, 인터페이스명에 쓰이는 게 조금 어색한 것 같아요.
    아래와 같은 네이밍은 어떨까요?

패키지명: image
인터페이스: ImageService
구현체: S3ImageService
테스트 구현체: TestImageService

의논 사항 답변

  1. DB에 URL 저장 vs fileName 저장

    • 아래 2번 항목의 기존 데이터 이관을 하고 나면 fineName에 제시해준 단점이 모두 사라질 것 같아요.
    • 그렇다면 기존 데이터 이관을 전제로, fileName 을 저장하는 게 어떨까요?
    • URL을 저장하면, (그럴 일은 잘 없겠지만) 저희의 이미지 저장소가 바뀐다면 그 때 또 DB 데이터에 쓰인 URL도 다 수정해줘야 하는 단점도 있을 것 같아요.
    • 대신 그러면 MemberInfo에서 쓰는 Image 클래스만 따로 분리해야 할 것 같아요.
  2. DB에 저장된 기존 이미지 URL은 S3 URL이 아닌 문제
    기존 데이터 이관 쪽에 동의합니다! 아래처럼 하면 어떨까용

    • 개발 서버 이미지는 유의미한 데이터가 아니니 모두 S3에 저장된 기본 이미지를 바라보도록 PinImage UPDATE 쿼리 날리기
    • 운영 서버 이미지는 기존 데이터를 S3에 이관, DB에 저장된 컬럼 값 수정
  • 이미지 삭제
    • PinImage는 Pin이 여러 이미지를 가지기에 존재하는 매핑 테이블로, pin_id 컬럼도 있어서 URL에 따라 하나로 묶는 건 어렵지 않을까요? 삭제 시에는
1. PinImage 테이블에, 동일한 URL(또는 파일명)를 가진 레코드가 있는지 확인한다.
2. 있으면, 테이블 레코드만 지운다.
3. 없으면, 테이블 레코드도 지우고 S3에도 이미지 삭제 요청을 한다.

이렇게 하는 건 어떨까요?

@kpeel5839
Copy link
Collaborator Author

전체 코드 흐름 파악, 아래에 주신 질문 읽고 전체적인 리뷰 먼저 코멘트로 남겨둡니다! 코드에 대한 리뷰는 내일 아침에 진행할게용

구현 내용 관련 질문

  1. 서비스 메서드의 시그니처를 유지해야 하는 이유가 있을까용? TopicCreateDtoWithOutImage -> TopicCreateDto 이렇게 Dto에 depth가 있는 게 조금 어색하게 느껴져서요! Dto는 하나만 사용하고, Service에서 MultipartFile을 직접 받으면 안되나요?!
  2. S3는 이미지를 저장하기 위한 기술의 이름인데, 상위 패키지명, 인터페이스명에 쓰이는 게 조금 어색한 것 같아요.
    아래와 같은 네이밍은 어떨까요?
패키지명: image
인터페이스: ImageService
구현체: S3ImageService
테스트 구현체: TestImageService

의논 사항 답변

  1. DB에 URL 저장 vs fileName 저장

    • 아래 2번 항목의 기존 데이터 이관을 하고 나면 fineName에 제시해준 단점이 모두 사라질 것 같아요.
    • 그렇다면 기존 데이터 이관을 전제로, fileName 을 저장하는 게 어떨까요?
    • URL을 저장하면, (그럴 일은 잘 없겠지만) 저희의 이미지 저장소가 바뀐다면 그 때 또 DB 데이터에 쓰인 URL도 다 수정해줘야 하는 단점도 있을 것 같아요.
    • 대신 그러면 MemberInfo에서 쓰는 Image 클래스만 따로 분리해야 할 것 같아요.
  2. DB에 저장된 기존 이미지 URL은 S3 URL이 아닌 문제
    기존 데이터 이관 쪽에 동의합니다! 아래처럼 하면 어떨까용

    • 개발 서버 이미지는 유의미한 데이터가 아니니 모두 S3에 저장된 기본 이미지를 바라보도록 PinImage UPDATE 쿼리 날리기
    • 운영 서버 이미지는 기존 데이터를 S3에 이관, DB에 저장된 컬럼 값 수정
  • 이미지 삭제

    • PinImage는 Pin이 여러 이미지를 가지기에 존재하는 매핑 테이블로, pin_id 컬럼도 있어서 URL에 따라 하나로 묶는 건 어렵지 않을까요? 삭제 시에는
1. PinImage 테이블에, 동일한 URL(또는 파일명)를 가진 레코드가 있는지 확인한다.
2. 있으면, 테이블 레코드만 지운다.
3. 없으면, 테이블 레코드도 지우고 S3에도 이미지 삭제 요청을 한다.

이렇게 하는 건 어떨까요?

전체 코드 흐름 파악, 아래에 주신 질문 읽고 전체적인 리뷰 먼저 코멘트로 남겨둡니다! 코드에 대한 리뷰는 내일 아침에 진행할게용

구현 내용 관련 질문

  1. 서비스 메서드의 시그니처를 유지해야 하는 이유가 있을까용? TopicCreateDtoWithOutImage -> TopicCreateDto 이렇게 Dto에 depth가 있는 게 조금 어색하게 느껴져서요! Dto는 하나만 사용하고, Service에서 MultipartFile을 직접 받으면 안되나요?!
  2. S3는 이미지를 저장하기 위한 기술의 이름인데, 상위 패키지명, 인터페이스명에 쓰이는 게 조금 어색한 것 같아요.
    아래와 같은 네이밍은 어떨까요?
패키지명: image
인터페이스: ImageService
구현체: S3ImageService
테스트 구현체: TestImageService

의논 사항 답변

  1. DB에 URL 저장 vs fileName 저장

    • 아래 2번 항목의 기존 데이터 이관을 하고 나면 fineName에 제시해준 단점이 모두 사라질 것 같아요.
    • 그렇다면 기존 데이터 이관을 전제로, fileName 을 저장하는 게 어떨까요?
    • URL을 저장하면, (그럴 일은 잘 없겠지만) 저희의 이미지 저장소가 바뀐다면 그 때 또 DB 데이터에 쓰인 URL도 다 수정해줘야 하는 단점도 있을 것 같아요.
    • 대신 그러면 MemberInfo에서 쓰는 Image 클래스만 따로 분리해야 할 것 같아요.
  2. DB에 저장된 기존 이미지 URL은 S3 URL이 아닌 문제
    기존 데이터 이관 쪽에 동의합니다! 아래처럼 하면 어떨까용

    • 개발 서버 이미지는 유의미한 데이터가 아니니 모두 S3에 저장된 기본 이미지를 바라보도록 PinImage UPDATE 쿼리 날리기
    • 운영 서버 이미지는 기존 데이터를 S3에 이관, DB에 저장된 컬럼 값 수정
  • 이미지 삭제

    • PinImage는 Pin이 여러 이미지를 가지기에 존재하는 매핑 테이블로, pin_id 컬럼도 있어서 URL에 따라 하나로 묶는 건 어렵지 않을까요? 삭제 시에는
1. PinImage 테이블에, 동일한 URL(또는 파일명)를 가진 레코드가 있는지 확인한다.
2. 있으면, 테이블 레코드만 지운다.
3. 없으면, 테이블 레코드도 지우고 S3에도 이미지 삭제 요청을 한다.

이렇게 하는 건 어떨까요?

도이! 빠른 답변 감사합니다!

  1. 서비스 메서드의 시그니처를 유지해야 하는 이유가 있을까용? TopicCreateDtoWithOutImage -> TopicCreateDto 이렇게 Dto에 depth가 있는 게 조금 어색하게 느껴져서요! Dto는 하나만 사용하고, Service에서 MultipartFile을 직접 받으면 안되나요?!

이 부분은 레벨 2에서 코일에게 리뷰를 받은적도 있고, 초기에 저와 쥬니가 Pin CRUD 기능을 개발할 때 Controller 에서 받는 Dto 와 Service 에서 받는 Dto 를 분리하자는 의견을 낸적도 있는 걸로 기억해요! (그 때 Dto 가 너무 많아져서 선택을 안하긴 했었죠 허허허)

제가 해당 방법을 선택한 이유는 Action Method 의 파라미터가, 내용은 동일하지만 형태가 달라졌다고 해서 Service 의 파라미터도 바뀐다면 변경사항이 너무 많아질 것 같다고 생각했어요.

실제로도 위와 같이 진행해서 변경사항이 많이 줄어들었던 것 같기도해요!

이 이상으로 고민하지는 않았지만, 위에서 설명드린 근거를 바탕으로 Service 의 파라미터를 그대로 유지하기로 판단했던 것 같아요!

다만, 이 방법이 올바르지 않다고 생각하시면 다시 한번 말씀해주세요!

그러면 이 부분도 수정하도록 하겠습니다!

그리고 나머지 부분들... 완전히 동의합니다.

삭제 부분도 제가 생각했던 플로우랑 동일해요.

다만, 제가 잘못 생각했던 부분은 Pin 복사시에 PinImage 도 똑똑하게 복사한다는 것을 잠시 잊고 있었네요! 짚어주셔서 감사해요.

긴 의견 감사합니다 도이! 이따가 더 이야기 해봐요~!

Copy link
Collaborator

@yoondgu yoondgu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수고 많으셨습니다 매튜 ~!!
이번에도 쉬운 확인을 위해 질문/제안 위주로 리뷰 남겼습니다.
다같이 합의해야할 부분 하나를 P1으로 남겼고, P2/P3 몇개 가볍게 남겼으니 확인 부탁드려요 ㅎㅎ

이전 코멘트에 대해서도 빠르게 답변해주셔서 감사합니다.
서비스 Dto에 대해서는..! 이전에 제안해주셨던 방향과 비슷한 거군요.
저는 딱 이미지에 대해서만 서비스 Dto가 분리된 것 같아서 / 네이밍만 봤을 때는 이를 유추하기 어려워서 고민인 것 같아요 ㅜㅜ
근데 변경이 매우 많아진다는 것엔 동의해서, 다른 분들의 의견이랑 함께 얘기나눠봐요!

@@ -9,7 +9,7 @@
import org.springframework.web.multipart.MultipartFile;

@Service
@Profile("!test")
@Profile("!test") // Test 시에는 TestS3ServiceImpl 를 Component 로 띄우기 위해 Profile 을 분리하였습니다.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍👍
P2. 근데 주석은 TODO로 남겨주시면, 나중에 해결됐는지 안됐는지/까먹고 안지운 걸 쉽게 찾을 수 있을 것 같아요!!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO 를 붙이는 것을 까먹었네요 수정하도록 하겠슴돠!

Comment on lines 29 to 31
} catch (IOException exception) { // 이거 어떻게 처리해야할까요..
throw new RuntimeException(exception);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3. 500 응답을 해야 하는 부분이면, IOException으로 하고 예외 메시지를 로그에서 확인할 수 있을정도로만 적어줘도 되지 않을까용....? (제가 질문에 대한 맞는 대답을 했는지 모르겠네요)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네 질문에 대답 잘해주셨어요!

하지만 IOException 가 checked exception 으로 알고 있어서, 저기서 IOException 을 throws 를 하면 해당 메서드를 사용하는 메서드들에서도 throws 를 추가해줘야 해서 고민하고 있었어요.

그냥 checked exception 을 던져야 할까요?

return Arrays.stream(values())
.filter(imageExtension -> imageFileName.endsWith(imageExtension.getExtension()))
.findFirst()
.orElseThrow(() -> new S3BadRequestException(ILLEGAL_IMAGE_FILE_EXTENSION));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍👍

this.extension = extension;
}

public static ImageExtension fromByImageFileName(String imageFileName) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3. 메서드명 from으로 하는 건 어떨까요?! imageFileName이 파라미터에 이미 명시가 되어있어서요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맞네요! 수정하겠습니다

Comment on lines 18 to 24
String extension = getExtension(originalFileName).getExtension();

return new ImageName(fileName + extension);
}

private static String getExtension(String originalFileName) {
return originalFileName.substring(
originalFileName.lastIndexOf(EXTENSION_DELIMITER)
);
private static ImageExtension getExtension(String originalFileName) {
return ImageExtension.fromByImageFileName(originalFileName);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3. 메서드 분리 한 김에 .getExtension() 도 분리한 메서드 안에서 하면 어떨까요? + 메서드명을 findExtension으로 해서 덜 헷갈리게 하면 어떨까요?!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수정하겠습니당!

Comment on lines +24 to +26
private static final Image DEFAULT_IMAGE = Image.from(
"https://velog.velcdn.com/images/semnil5202/post/37f3bcb9-0b07-4100-85f6-f1d5ad037c14/image.svg"
);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이전과 이 링크의 차이는 어떤거죠...?! 단순 궁금증입니닿 😀

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이미지가 다른 걸로 알아요! 확인하지는 않긴 했지만 허허허..

현재 저희 괜찮을지도 서비스에서 기본적으로 제공하는 이미지가 저 이미지더라고요 빠뚜릭이 보내줘서 바꿨습니다.

@@ -114,6 +114,23 @@ private ExtractableResponse<Response> createPin(PinCreateRequest request) {
.extract();
}

@Test
@DisplayName("Image List 없이 Pin 을 정상적으로 생성한다.")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

굿굿

Comment on lines 7 to 21
ILLEGAL_TOPIC_ID("10000", "유효하지 않은 지도입니다."),
ILLEGAL_NAME_NULL("10001", "지도 이름은 필수로 입력해야합니다."),
ILLEGAL_NAME_LENGTH("10002", "지도 이름 길이는 최소 1자에서 20자여야 합니다."),
ILLEGAL_DESCRIPTION_NULL("10003", "지도 설명은 필수로 입력해야합니다."),
ILLEGAL_DESCRIPTION_LENGTH("10004", "지도 설명의 길이는 최소 1자에서 1000자여야 합니다."),
ILLEGAL_PUBLICITY_NULL("10005", "지도의 공개 범위는 필수로 입력해야합니다."),
ILLEGAL_PERMISSION_NULL("10006", "지도의 권한 설정은 필수로 입력해야합니다."),
ILLEGAL_PERMISSION_FOR_PUBLICITY_PRIVATE("10007", "비공개 지도인 경우, 권한 설정이 소속 회원이어야합니다."),
ILLEGAL_PUBLICITY_FOR_PERMISSION_ALL_MEMBERS("10008", "권한 범위가 모든 회원인 경우, 비공개 지도로 설정할 수 없습니다."),
ILLEGAL_PERMISSION_UPDATE("10009", "권한 범위를 모든 회원에서 소속 회원으로 수정할 수 없습니다."),
FORBIDDEN_TOPIC_CREATE("10300", "로그인하지 않은 사용자는 지도를 생성할 수 없습니다."),
FORBIDDEN_TOPIC_UPDATE("10301", "지도 수정 권한이 없습니다."),
FORBIDDEN_TOPIC_DELETE("10302", "지도 삭제 권한이 없습니다."),
FORBIDDEN_TOPIC_READ("10302", "지도 조회 권한이 없습니다."),
TOPIC_NOT_FOUND("10400", "지도가 존재하지 않습니다."),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1. 앗 S3ErrorCode가 패키지 순서 중간에 생기면서 번호를 같이 바꿔주셨군요!!
이 부분 전체 논의가 필요할 것 같아요 !
저는 에러코드 처음 작성 시 패키지 순서 대로 부여한 번호 / 그 이후에 새로 생기는 패키지는 그 다음 새 번호(10, 11)를 부여하는 걸로 생각하고 있었거든요. 그럼 패키지 당 번호가 조금 헷갈리겠지만, 대신 패키지가 추가될 때마다 문서에 이를 업데이트하는 걸로요.
같이 얘기해봐요 !!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아 맞아요!

사실 저도 그냥 그대로 둘려고 했었는데 아무리 봐도 패키지 순서로 정하신 것 같고, 제가 임의대로 10번부터 시작해버리면 헷갈릴 수도 있을 것 같아서 위와 같이 바꿨었습니다.

하지만 계속 이렇게 바꾸게 되면 추후 변경사항이 너무 많아질 것 같긴합니다.

한번 이야기 해보죠!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저한테는 결과만 알려주세요.
아무래도 상관없으니까요 힛

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

어쩔 tvtv

kpeel5839 and others added 7 commits September 18, 2023 12:44
* fix: RefreshToken Payload 추가 및 CORS 완화

* fix: Refresh Token Header 허용

* fix: CORS 재설정 및 sameSite None
* fix: RefreshToken Payload 추가 및 CORS 완화

* fix: Refresh Token Header 허용

* fix: CORS 재설정 및 sameSite None

* fix: refreshToken 존재 시 삭제 로직 변경
kpeel5839 and others added 24 commits September 20, 2023 16:31
* chore: yml 변수 적용 확인을 위한 debug 로그 추가

* chore: 톰캣 설정 기본값 추가

* chore: 톰캣 설정 기본값 추가

---------

Co-authored-by: yoondgu <[email protected]>
Copy link
Collaborator

@junpakPark junpakPark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수고하셨습니다~!!

Copy link
Collaborator

@yoondgu yoondgu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수고 많으셨습니다 !!

@kpeel5839 kpeel5839 merged commit 327e80a into develop-BE Sep 21, 2023
jiwonh423 pushed a commit that referenced this pull request Sep 21, 2023
* [BE] Fix/#366 테스트 수행 시 로그 패턴 깨지는 오류 해결 (#367)

* fix: 테스트 로그 패턴 설정 오류 수정

- 잘못된 로그 패턴 설정으로 인한 'LOG_PATTERN IS_UNDEFINED' 메시지 출력 오류 수정

* chore: 프론트엔드, 백엔드 develop 분리에 따른 워크플로우 수정

* fix: 테스트 로그 설정 파일명 변경
- springProperty 지원을 위해 파일명 변경

* feat: 로그 내용 및 설정 보완(색상 적용)

- 로그에서 로거, 레벨, PID 확인 가능하도록 내용 보완
- 테스트 로그의 경우 프로젝트 패키지에 해당하는 로그만 DEBUG 레벨로 설정
- 콘솔 로그 색상 적용

* feat: 로그 내용 보완 - 스레드 출력하도록 수정

* refactor: DataBaseCleanup JdbcTemplate 적용 (#371)

* [BE] Refactor/#376 로깅 환경 개선을 위한 설정 파일 리팩터링 (#377)

* refactor: 로깅 전략 보완에 따른 설정 파일 수정

- 운영 환경 별 로그 종류(콘솔, 파일), 레벨 변경 (PR 첨부 설명 참조)
- logback-spring.xml 에서 Appender 분리
- application-*.xml 에서 로그 패턴 값 삭제

* chore: 에러 로그 슬랙 알림을 위한 의존성 추가, 관련 주석 작성

* remove: 테스트 설정 파일에 불필요한 로그 설정 삭제

* style: 불필요한 빈 줄 삭제

* chore: 콘솔 파일 로그 설정 삭제로 인한 불필요한 설정 삭제

* chore: 로그 파일 롤링 용량 설정 모든 레벨 통일

* [BE] Chore/#372 submodule 적용 (#375)

* chore: submodule 적용

Co-authored-by: jaeyeon kim <[email protected]>

* chore: workflow access token 적용

Co-authored-by: jaeyeon kim <[email protected]>

---------

Co-authored-by: jaeyeon kim <[email protected]>
Co-authored-by: yoondgu <[email protected]>

* feat: 운영 서버 500 에러 슬랙 알림 적용, 로컬 환경변수 서브모듈에 저장 (#379)

* hotfix: yml 문법으로 인한 오류 수정 (#381)

* fix: 워크플로우 서브모듈 문제 해결 (#384)

* chore: 워크플로우 서브모듈 문제 해결 확인을 위한 push (#385)

* Feat/#386 image (#391)

* chore : S3 의존성 추가

* feat : Amazon S3 Component 추가

* feat : S3 에 업로드 될 Image 의 이름을 설정해주는 ImageName 과 UploadFile 추가

* feat : S3 에 업로드 될 Image 의 이름을 설정해주는 ImageNae 과 UploadFile 추가

* feat : 파일을 업로드하고 해당하는 URL 을 반환하는 Service 구현

* chore : s3 환경 설정 추가

* feat : String image -> MultipartFile image 로 변경

* feat : @RequestPart 적용 및 S3 upload 로직 추가

* chore : Test 시 Profile 설정

* test : 테스트 시 S3 에 접근하지 않도록 하기 위해 Profile 별로 S3Service Bean 구분

* test : 추가된 Image 저장 기능에 맞춰 일부 Test 수정

* chore : S3, CloudFront 환경설정 적용

* test : RestDocs 수정중

* refactor : S3 Bean 추가 (#396)

* [BE] Refactor/#390 지도 및 핀 상세 조회 API에 수정 권한 여부 필드 추가 (#392)

* feat: 토픽 상세 조회 내용에 수정 권한 추가

* refactor: 토픽 상세 조회 DTO 정적 팩터리 메서드명 수정

* feat: 핀 상세 조회 내용에 수정 권한 추가

* fix: 토픽 RestDocs 깨지는 문제 수정, API 목차 순서 조정

- 목록 조회 API 기준으로 순서 조정, API 네이밍 보완

* refactor: 토픽, 핀 수정 권한 여부 필드명 직관적으로 수정

- hasUpdatePermission -> canUpdate

* refactor: Guest 전용 토픽 상세조회 DTO 정적 팩터리 메서드 정의

* refactor: 메서드 순서 정리, Guest 전용 토픽 조회 DTO 정적 팩터리 메서드 정의

* refactor: Guest 전용 토픽 List 조회 DTO 정적 팩터리 메서드 정의

* refactor: Info, Debug 레벨 커스텀 로그만 출력하도록 변경 (#397)

* feat : pin 생성시 image upload 기능 추가 (#401)

* [BE] Feat/#378 Admin API 구현 (#405)

* feat: 전체 회원 조회 기능 구현

* feat: 회원 삭제(탈퇴) 기능 구현

* feat: 회원 삭제(탈퇴)시 Pin/Topic Soft-deleting 구현

* refactor: Admin DTO 분리

* feat: Member 상세 정보 조회 기능 구현

* feat: Topic 삭제 및 이미지 삭제 기능 구현

* feat: Pin 삭제 및 이미지 삭제 기능 구현

* feat: Admin API 구현

* refactor: Member 상태(차단, 탈퇴 등) 필드에 따른 로그인 로직 수정

* refactor: @SqlDelete 삭제 및 JPQL 대체

* feat: AdminInterceptor 구현

* test: Repository soft-deleting 테스트 구현

* test: AdminQueryService 테스트 구현

* test: AdminCommandService 테스트 구현

* test: AdminController Restdocs 테스트 구현

* test: AdminInterceptor Mocking

* test: 통합 테스트 구현

* refactor: 오탈자 수정

* refactor: Auth 관련 예외 클래스 추가

* refactor: 불필요한 메서드 제거

* refactor: findMemberById 예외 수정

* test: GithubActions 실패 테스트 수정

* refactor: isAdmin() 메서드 추가

* refactor: 회원 삭제(탈퇴)시, 추가 정보(즐겨찾기 등) 삭제

* Revert "[BE] Feat/#378 Admin API 구현 (#405)" (#414)

This reverts commit 4722faa.

* [BE] Feat/#378 Admin API 구현 (#415)

* feat: 전체 회원 조회 기능 구현

* feat: 회원 삭제(탈퇴) 기능 구현

* feat: 회원 삭제(탈퇴)시 Pin/Topic Soft-deleting 구현

* refactor: Admin DTO 분리

* feat: Member 상세 정보 조회 기능 구현

* feat: Topic 삭제 및 이미지 삭제 기능 구현

* feat: Pin 삭제 및 이미지 삭제 기능 구현

* feat: Admin API 구현

* refactor: Member 상태(차단, 탈퇴 등) 필드에 따른 로그인 로직 수정

* refactor: @SqlDelete 삭제 및 JPQL 대체

* feat: AdminInterceptor 구현

* test: Repository soft-deleting 테스트 구현

* test: AdminQueryService 테스트 구현

* test: AdminCommandService 테스트 구현

* test: AdminController Restdocs 테스트 구현

* test: AdminInterceptor Mocking

* test: 통합 테스트 구현

* refactor: 오탈자 수정

* refactor: Auth 관련 예외 클래스 추가

* refactor: 불필요한 메서드 제거

* refactor: findMemberById 예외 수정

* test: GithubActions 실패 테스트 수정

* refactor: isAdmin() 메서드 추가

* refactor: 회원 삭제(탈퇴)시, 추가 정보(즐겨찾기 등) 삭제

* refactor: Member status 기본값 설정

* remove: Member status 기본값 설정 삭제

* [BE] Feature/#399 내 정보(회원 닉네임) 수정 API 구현 (#408)

* refactor: 사용하지 않는 MemberRepository 메서드 삭제

* refactor: 회원 업데이트 부분 변경으로 시그니처 변경

- 현재 회원 update에서 변경되는 부분만 인자로 남겨둠
- update 시, Member 에서 MemberInfo.getXX을 하는 대신 MemberInfo에서 부분 변경된 객체를 새로 반환하도록 수정

* feat: 회원 정보 수정 API 구현 및 테스트 작성

* test: JwtTokenProviderTest 작성

- 로컬에서 Postman 테스트 시 이 테스트를 사용하면 쉽게 토큰 발급 후 활용 가능

* chore: 로컬 환경용 더미데이터 sql 작성

* chore: 로컬 환경 data.sql을 위한 서브모듈 변경

* docs: 기능 명세서 및 테스트 코드 용어 정리 (유저, 멤버 -> 회원)

* chore: 로그 환경설정 파일 디렉터리 분리

* feat: 닉네임 중복 검증 구현

* refactor: 회원의 이메일 Unique 제약조건 삭제

- 닉네임, OauthId로 회원을 식별할 수 있다.
- 같은 이메일로 네이버, 카카오에 가입한 사람이 소셜 로그인으로 두 계정을 만들 경우, 동일한 이메일이 저장될 수도 있다.

* refactor: 모호한 메서드명 수정

* refactor: Email이 Unique하지 않음에 따라 테스트에서 사용하는 조회 쿼리 변경

- findByEmail 대신 findById
- 기본키가 아닌 유일키로 조회하는 건 테이블 구조 변경 여지가 있으므로 findById 사용

* refactor: 내 정보 수정 API URI 변경

* fix: 디렉터리 분리에 따른 로그 설정 파일 appender 경로 수정

* [BE] Refactor/#406 토픽 권한을 가진 회원 목록 조회 시 공개 여부를 함께 반환하도록 변경  (#412)

* refactor: 사용하지 않는 MemberRepository 메서드 삭제

* refactor: 회원 업데이트 부분 변경으로 시그니처 변경

- 현재 회원 update에서 변경되는 부분만 인자로 남겨둠
- update 시, Member 에서 MemberInfo.getXX을 하는 대신 MemberInfo에서 부분 변경된 객체를 새로 반환하도록 수정

* feat: 회원 정보 수정 API 구현 및 테스트 작성

* test: JwtTokenProviderTest 작성

- 로컬에서 Postman 테스트 시 이 테스트를 사용하면 쉽게 토큰 발급 후 활용 가능

* chore: 로컬 환경용 더미데이터 sql 작성

* chore: 로컬 환경 data.sql을 위한 서브모듈 변경

* docs: 기능 명세서 및 테스트 코드 용어 정리 (유저, 멤버 -> 회원)

* chore: 로그 환경설정 파일 디렉터리 분리

* feat: 닉네임 중복 검증 구현

* refactor: 회원의 이메일 Unique 제약조건 삭제

- 닉네임, OauthId로 회원을 식별할 수 있다.
- 같은 이메일로 네이버, 카카오에 가입한 사람이 소셜 로그인으로 두 계정을 만들 경우, 동일한 이메일이 저장될 수도 있다.

* refactor: 모호한 메서드명 수정

* refactor: Email이 Unique하지 않음에 따라 테스트에서 사용하는 조회 쿼리 변경

- findByEmail 대신 findById
- 기본키가 아닌 유일키로 조회하는 건 테이블 구조 변경 여지가 있으므로 findById 사용

* refactor: 내 정보 수정 API URI 변경

* refactor: 토픽 권한 회원 목록 조회 API를 접근 정보(권한 회원 목록 및 공개 여부) 조회로 명세 변경

- 관련 검토가 필요한 API 설계 및 구현 내용에 대한 TODO 주석 작성

* fix: 디렉터리 분리에 따른 로그 설정 파일 appender 경로 수정

* fix: 디렉터리 분리에 따른 로그 설정 파일 appender 경로 수정

* refactor: 실수로 바꾼 기존 메서드명 원복

* refactor: 불필요한 import문 제거

* refactor: 불필요한 접근제어자, 중복 코드 제거

* docs: Restdocs API 네이밍 반영

* fix: 내 정보 수정 RestDocs 스니펫 누락 추가

* [BE] Feature/#388 refresh token 및 로그아웃 기능 구현 (#411)

* chore: redis 의존성 추가

* refactor: OauthService 필드에 final 추가

* feat: refreshToken 엔티티 및 레포지토리 구현

* feat: JwtTokenProvider RefreshToken 발급 구현

* feat: 로그인 시 RefreshToken 발급 기능 구현

* feat: Auth 패키지 커스텀 예외 추가

* refactor: validate 메서드 리팩터링

* chore: refreshToken 만료 시간 추가

* test: Test를 위한 설정 변경

* feat: 액세스 토큰 재발급 및 로그아웃 기능 구현

* chore: Redis 의존성 제거

* test: TestTokenProvider 객체 구현

* refactor: /logout HttpMethod 변경, cookie 관련 cors설정 및 maxAge 설정,

* test: DisplayName 추가

* feat: RTR 적용 및 OauthConntroller 제거, OauthService 및 TokenService 역할과 책임 재분배

* refactor : 피드백 반영

* refactor : 매직넘버 상수화

* refactor : 네이밍 수정

* feat: 쿠키 설정 추가

* [BE] Fix/#424 refresh token duplicated (#425)

* fix: RefreshToken Payload 추가 및 CORS 완화

* fix: Refresh Token Header 허용

* [BE] Fix/#426 Token CORS 재설정 (#427)

* fix: RefreshToken Payload 추가 및 CORS 완화

* fix: Refresh Token Header 허용

* fix: CORS 재설정 및 sameSite None

* [BE] HotFix/#426 Refresh Token 중복 저장 방지 로직 수정 (#431)

* fix: RefreshToken Payload 추가 및 CORS 완화

* fix: Refresh Token Header 허용

* fix: CORS 재설정 및 sameSite None

* fix: refreshToken 존재 시 삭제 로직 변경

* [BE] HotFix/#426 delete 메서드에 clearAutomatically 속성 적용 (#432)

* fix: RefreshToken Payload 추가 및 CORS 완화

* fix: Refresh Token Header 허용

* fix: CORS 재설정 및 sameSite None

* fix: refreshToken 존재 시 삭제 로직 변경

* fix: delete 메서드에 clearAutomatically 속성 적용

* [BE] HotFix/#426 tokenService flush 추가 (#433)

* fix: RefreshToken Payload 추가 및 CORS 완화

* fix: Refresh Token Header 허용

* fix: CORS 재설정 및 sameSite None

* fix: refreshToken 존재 시 삭제 로직 변경

* fix: delete 메서드에 clearAutomatically 속성 적용

* fix: delete 메서드에 clearAutomatically 속성 제거 및 flush 추가

* [BE] Refactor/#400 토픽 조회 시 업데이트 일시를 최근에 핀이 추가/변경된 일시로 변경 (#429)

* refactor: BaseEntity의 createdAt update 방지

* feat: Topic에 lastPinUpdatedAt 컬럼 추가, EntityListner 적용

- 기존 BaseEntity의 값들은 객체가 영속화될 때 저장된다.
- 이에 대해 일관성을 유지해야 한다. (핀 생성 일시, 핀 변경 일시 = 토픽의 최근 핀 변경 일시가 서로 같아야 하므로)
- 따라서 lastPinUpdatedAt 컬럼의 업데이트 또한 EntityListener 로 적용한다.

* feat: 토픽 조회 DTO의 updatedAt 값 lastPinUpdatedAt 으로 변경

* feat: 토픽 최신순 조회 로직 수정

- Topic에 lastPinUpdatedAt 추가로 인해 로직 수정 가능

* test: 토픽 조회 시 updatedAt 검증 테스트 추가

* chore: 로컬 테스트용 SQL에 테이블 컬럼 추가 변경 반영

* refactor: 토픽 Response Dto에 lastPinUpdatedAt 반영

* fix : 토큰 만료시간 및 redirect uri 수정

---------

Co-authored-by: jaeyeon kim <[email protected]>

* [BE] Feature/#422 성능 측정을 위한 로깅 구현 (#434)

* feat: QueryCounter 객체 구현

* feat: QueryInspector 객체 구현

* feat: LatencyRecorder 객체 구현

* feat: LatencyLoggingFilter 객체 구현

* feat: LatencyRecorder Thread-safe 테스트 구현

* feat: HibernateConfig 구현

* test: 테스트 수정

* style: 개행 추가

* refactor: 수식 표현 방식 수정

* [BE] HotFix/#424 refresh token duplicated (#441)

* fix: RefreshToken Payload 추가 및 CORS 완화

* fix: Refresh Token Header 허용

* fix: CORS 재설정 및 sameSite None

* fix: 디버깅을 위한 에러코드 추가

* [BE] HOTFix/#424 validateTokensForReissue 디버깅을 위한 에러코드 추가 (#443)

* fix: RefreshToken Payload 추가 및 CORS 완화

* fix: Refresh Token Header 허용

* fix: CORS 재설정 및 sameSite None

* fix: 디버깅을 위한 에러코드 추가

* fix: validateTokensForReissue 디버깅을 위한 에러코드 추가

* fix: isExpired 임시 log 처리 (#444)

* Revert "fix: isExpired 임시 log 처리 (#444)"

This reverts commit 445f0dd.

* fix: cors Credentials 추가 (#458)

* [BE] Hotfix/cors allowHeaders 와일드카드 적용 (#462)

* fix: cors Credentials 추가

* fix: allowedHeaders 와일드카드 적용

* [BE] 부하테스트를 위한 Tomcat Log 추가 (#464)

* chore: yml 변수 적용 확인을 위한 debug 로그 추가

* chore: 톰캣 설정 기본값 추가

* chore: 톰캣 설정 기본값 추가

---------

Co-authored-by: yoondgu <[email protected]>

* [BE] S3 를 통한 Image Upload 기능 구현 (#428)

* refactor : s3 패키지 추가로 인한 에러 Code 수정

* feat : s3 exception 추가

* refactor : image extension 추출 방식 수정

* refactor : S3Client 가 IOException 을 throw 할 수 있도록 작성

* style : 프린트, 주석 제거

* test : imageExtension Test 작성

* refactor : image 가 요청으로 들어오지 않는 경우를 고려해 로직 수정

* test : 이미지가 null 로 들어오는 경우 test 작성

* feat : 병합시에도 S3 Image Upload 가 가능하도록 구현

* refactor : 기본 이미지 URL 변경

* refactor : 기본 이미지의 처리를 TopicInfo -> Image 에서 할 수 있도록 수정

* refactor : 주석 앞에 TODO 추가

* refactor : fromImageFileName -> from 으로 메서드 명 변경

* refactor : getExtension -> findExtension 으로 변경

* refactor : S3 관련 Service 네이밍 수정

* [BE] Fix/#426 Token CORS 재설정 (#427)

* fix: RefreshToken Payload 추가 및 CORS 완화

* fix: Refresh Token Header 허용

* fix: CORS 재설정 및 sameSite None

* refactor : S3 관련 Service 네이밍 수정

* [BE] HotFix/#426 Refresh Token 중복 저장 방지 로직 수정 (#431)

* fix: RefreshToken Payload 추가 및 CORS 완화

* fix: Refresh Token Header 허용

* fix: CORS 재설정 및 sameSite None

* fix: refreshToken 존재 시 삭제 로직 변경

* [BE] HotFix/#426 delete 메서드에 clearAutomatically 속성 적용 (#432)

* fix: RefreshToken Payload 추가 및 CORS 완화

* fix: Refresh Token Header 허용

* fix: CORS 재설정 및 sameSite None

* fix: refreshToken 존재 시 삭제 로직 변경

* fix: delete 메서드에 clearAutomatically 속성 적용

* [BE] HotFix/#426 tokenService flush 추가 (#433)

* fix: RefreshToken Payload 추가 및 CORS 완화

* fix: Refresh Token Header 허용

* fix: CORS 재설정 및 sameSite None

* fix: refreshToken 존재 시 삭제 로직 변경

* fix: delete 메서드에 clearAutomatically 속성 적용

* fix: delete 메서드에 clearAutomatically 속성 제거 및 flush 추가

* [BE] Refactor/#400 토픽 조회 시 업데이트 일시를 최근에 핀이 추가/변경된 일시로 변경 (#429)

* refactor: BaseEntity의 createdAt update 방지

* feat: Topic에 lastPinUpdatedAt 컬럼 추가, EntityListner 적용

- 기존 BaseEntity의 값들은 객체가 영속화될 때 저장된다.
- 이에 대해 일관성을 유지해야 한다. (핀 생성 일시, 핀 변경 일시 = 토픽의 최근 핀 변경 일시가 서로 같아야 하므로)
- 따라서 lastPinUpdatedAt 컬럼의 업데이트 또한 EntityListener 로 적용한다.

* feat: 토픽 조회 DTO의 updatedAt 값 lastPinUpdatedAt 으로 변경

* feat: 토픽 최신순 조회 로직 수정

- Topic에 lastPinUpdatedAt 추가로 인해 로직 수정 가능

* test: 토픽 조회 시 updatedAt 검증 테스트 추가

* chore: 로컬 테스트용 SQL에 테이블 컬럼 추가 변경 반영

* refactor: 토픽 Response Dto에 lastPinUpdatedAt 반영

* fix : 토큰 만료시간 및 redirect uri 수정

---------

Co-authored-by: jaeyeon kim <[email protected]>

* [BE] Feature/#422 성능 측정을 위한 로깅 구현 (#434)

* feat: QueryCounter 객체 구현

* feat: QueryInspector 객체 구현

* feat: LatencyRecorder 객체 구현

* feat: LatencyLoggingFilter 객체 구현

* feat: LatencyRecorder Thread-safe 테스트 구현

* feat: HibernateConfig 구현

* test: 테스트 수정

* style: 개행 추가

* refactor: 수식 표현 방식 수정

* [BE] HotFix/#424 refresh token duplicated (#441)

* fix: RefreshToken Payload 추가 및 CORS 완화

* fix: Refresh Token Header 허용

* fix: CORS 재설정 및 sameSite None

* fix: 디버깅을 위한 에러코드 추가

* [BE] HOTFix/#424 validateTokensForReissue 디버깅을 위한 에러코드 추가 (#443)

* fix: RefreshToken Payload 추가 및 CORS 완화

* fix: Refresh Token Header 허용

* fix: CORS 재설정 및 sameSite None

* fix: 디버깅을 위한 에러코드 추가

* fix: validateTokensForReissue 디버깅을 위한 에러코드 추가

* fix: isExpired 임시 log 처리 (#444)

* Revert "fix: isExpired 임시 log 처리 (#444)"

This reverts commit 445f0dd.

* fix: cors Credentials 추가 (#458)

* [BE] Hotfix/cors allowHeaders 와일드카드 적용 (#462)

* fix: cors Credentials 추가

* fix: allowedHeaders 와일드카드 적용

* [BE] 부하테스트를 위한 Tomcat Log 추가 (#464)

* chore: yml 변수 적용 확인을 위한 debug 로그 추가

* chore: 톰캣 설정 기본값 추가

* chore: 톰캣 설정 기본값 추가

---------

Co-authored-by: yoondgu <[email protected]>

* refactor : s3 패키지 추가로 인한 에러 Code 수정

* feat : s3 exception 추가

* refactor : image extension 추출 방식 수정

* refactor : S3Client 가 IOException 을 throw 할 수 있도록 작성

* style : 프린트, 주석 제거

* test : imageExtension Test 작성

* refactor : image 가 요청으로 들어오지 않는 경우를 고려해 로직 수정

* test : 이미지가 null 로 들어오는 경우 test 작성

* feat : 병합시에도 S3 Image Upload 가 가능하도록 구현

* refactor : 기본 이미지 URL 변경

* refactor : 기본 이미지의 처리를 TopicInfo -> Image 에서 할 수 있도록 수정

* refactor : 주석 앞에 TODO 추가

* refactor : fromImageFileName -> from 으로 메서드 명 변경

* refactor : getExtension -> findExtension 으로 변경

* refactor : S3 관련 Service 네이밍 수정

* refactor : S3 관련 Service 네이밍 수정

* refactor : topic, image errorCode 수정

* refactor : Exception 부분 네이밍 S3 -> Image 로 변경

* refactor : findExtension -> extractExtensio 으로 메서드 네이밍 변경

* refactor : 부정 조건문 제거

* refactor : Illegal Image File Extension 에러 메세지 수정

* refactor : action method consume type 순서 조정

---------

Co-authored-by: 준팍(junpak) <[email protected]>
Co-authored-by: Doy <[email protected]>
Co-authored-by: zun <[email protected]>

---------

Co-authored-by: Doy <[email protected]>
Co-authored-by: 준팍(junpak) <[email protected]>
Co-authored-by: jaeyeon kim <[email protected]>
Co-authored-by: yoondgu <[email protected]>
Co-authored-by: kpeel5839 <[email protected]>
kpeel5839 added a commit that referenced this pull request Sep 21, 2023
* [BE] Fix/#366 테스트 수행 시 로그 패턴 깨지는 오류 해결 (#367)

* fix: 테스트 로그 패턴 설정 오류 수정

- 잘못된 로그 패턴 설정으로 인한 'LOG_PATTERN IS_UNDEFINED' 메시지 출력 오류 수정

* chore: 프론트엔드, 백엔드 develop 분리에 따른 워크플로우 수정

* fix: 테스트 로그 설정 파일명 변경
- springProperty 지원을 위해 파일명 변경

* feat: 로그 내용 및 설정 보완(색상 적용)

- 로그에서 로거, 레벨, PID 확인 가능하도록 내용 보완
- 테스트 로그의 경우 프로젝트 패키지에 해당하는 로그만 DEBUG 레벨로 설정
- 콘솔 로그 색상 적용

* feat: 로그 내용 보완 - 스레드 출력하도록 수정

* refactor: DataBaseCleanup JdbcTemplate 적용 (#371)

* [BE] Refactor/#376 로깅 환경 개선을 위한 설정 파일 리팩터링 (#377)

* refactor: 로깅 전략 보완에 따른 설정 파일 수정

- 운영 환경 별 로그 종류(콘솔, 파일), 레벨 변경 (PR 첨부 설명 참조)
- logback-spring.xml 에서 Appender 분리
- application-*.xml 에서 로그 패턴 값 삭제

* chore: 에러 로그 슬랙 알림을 위한 의존성 추가, 관련 주석 작성

* remove: 테스트 설정 파일에 불필요한 로그 설정 삭제

* style: 불필요한 빈 줄 삭제

* chore: 콘솔 파일 로그 설정 삭제로 인한 불필요한 설정 삭제

* chore: 로그 파일 롤링 용량 설정 모든 레벨 통일

* [BE] Chore/#372 submodule 적용 (#375)

* chore: submodule 적용

Co-authored-by: jaeyeon kim <[email protected]>

* chore: workflow access token 적용

Co-authored-by: jaeyeon kim <[email protected]>

---------

Co-authored-by: jaeyeon kim <[email protected]>
Co-authored-by: yoondgu <[email protected]>

* feat: 운영 서버 500 에러 슬랙 알림 적용, 로컬 환경변수 서브모듈에 저장 (#379)

* hotfix: yml 문법으로 인한 오류 수정 (#381)

* fix: 워크플로우 서브모듈 문제 해결 (#384)

* chore: 워크플로우 서브모듈 문제 해결 확인을 위한 push (#385)

* Feat/#386 image (#391)

* chore : S3 의존성 추가

* feat : Amazon S3 Component 추가

* feat : S3 에 업로드 될 Image 의 이름을 설정해주는 ImageName 과 UploadFile 추가

* feat : S3 에 업로드 될 Image 의 이름을 설정해주는 ImageNae 과 UploadFile 추가

* feat : 파일을 업로드하고 해당하는 URL 을 반환하는 Service 구현

* chore : s3 환경 설정 추가

* feat : String image -> MultipartFile image 로 변경

* feat : @RequestPart 적용 및 S3 upload 로직 추가

* chore : Test 시 Profile 설정

* test : 테스트 시 S3 에 접근하지 않도록 하기 위해 Profile 별로 S3Service Bean 구분

* test : 추가된 Image 저장 기능에 맞춰 일부 Test 수정

* chore : S3, CloudFront 환경설정 적용

* test : RestDocs 수정중

* refactor : S3 Bean 추가 (#396)

* [BE] Refactor/#390 지도 및 핀 상세 조회 API에 수정 권한 여부 필드 추가 (#392)

* feat: 토픽 상세 조회 내용에 수정 권한 추가

* refactor: 토픽 상세 조회 DTO 정적 팩터리 메서드명 수정

* feat: 핀 상세 조회 내용에 수정 권한 추가

* fix: 토픽 RestDocs 깨지는 문제 수정, API 목차 순서 조정

- 목록 조회 API 기준으로 순서 조정, API 네이밍 보완

* refactor: 토픽, 핀 수정 권한 여부 필드명 직관적으로 수정

- hasUpdatePermission -> canUpdate

* refactor: Guest 전용 토픽 상세조회 DTO 정적 팩터리 메서드 정의

* refactor: 메서드 순서 정리, Guest 전용 토픽 조회 DTO 정적 팩터리 메서드 정의

* refactor: Guest 전용 토픽 List 조회 DTO 정적 팩터리 메서드 정의

* refactor: Info, Debug 레벨 커스텀 로그만 출력하도록 변경 (#397)

* feat : pin 생성시 image upload 기능 추가 (#401)

* [BE] Feat/#378 Admin API 구현 (#405)

* feat: 전체 회원 조회 기능 구현

* feat: 회원 삭제(탈퇴) 기능 구현

* feat: 회원 삭제(탈퇴)시 Pin/Topic Soft-deleting 구현

* refactor: Admin DTO 분리

* feat: Member 상세 정보 조회 기능 구현

* feat: Topic 삭제 및 이미지 삭제 기능 구현

* feat: Pin 삭제 및 이미지 삭제 기능 구현

* feat: Admin API 구현

* refactor: Member 상태(차단, 탈퇴 등) 필드에 따른 로그인 로직 수정

* refactor: @SqlDelete 삭제 및 JPQL 대체

* feat: AdminInterceptor 구현

* test: Repository soft-deleting 테스트 구현

* test: AdminQueryService 테스트 구현

* test: AdminCommandService 테스트 구현

* test: AdminController Restdocs 테스트 구현

* test: AdminInterceptor Mocking

* test: 통합 테스트 구현

* refactor: 오탈자 수정

* refactor: Auth 관련 예외 클래스 추가

* refactor: 불필요한 메서드 제거

* refactor: findMemberById 예외 수정

* test: GithubActions 실패 테스트 수정

* refactor: isAdmin() 메서드 추가

* refactor: 회원 삭제(탈퇴)시, 추가 정보(즐겨찾기 등) 삭제

* Revert "[BE] Feat/#378 Admin API 구현 (#405)" (#414)

This reverts commit 4722faa.

* [BE] Feat/#378 Admin API 구현 (#415)

* feat: 전체 회원 조회 기능 구현

* feat: 회원 삭제(탈퇴) 기능 구현

* feat: 회원 삭제(탈퇴)시 Pin/Topic Soft-deleting 구현

* refactor: Admin DTO 분리

* feat: Member 상세 정보 조회 기능 구현

* feat: Topic 삭제 및 이미지 삭제 기능 구현

* feat: Pin 삭제 및 이미지 삭제 기능 구현

* feat: Admin API 구현

* refactor: Member 상태(차단, 탈퇴 등) 필드에 따른 로그인 로직 수정

* refactor: @SqlDelete 삭제 및 JPQL 대체

* feat: AdminInterceptor 구현

* test: Repository soft-deleting 테스트 구현

* test: AdminQueryService 테스트 구현

* test: AdminCommandService 테스트 구현

* test: AdminController Restdocs 테스트 구현

* test: AdminInterceptor Mocking

* test: 통합 테스트 구현

* refactor: 오탈자 수정

* refactor: Auth 관련 예외 클래스 추가

* refactor: 불필요한 메서드 제거

* refactor: findMemberById 예외 수정

* test: GithubActions 실패 테스트 수정

* refactor: isAdmin() 메서드 추가

* refactor: 회원 삭제(탈퇴)시, 추가 정보(즐겨찾기 등) 삭제

* refactor: Member status 기본값 설정

* remove: Member status 기본값 설정 삭제

* [BE] Feature/#399 내 정보(회원 닉네임) 수정 API 구현 (#408)

* refactor: 사용하지 않는 MemberRepository 메서드 삭제

* refactor: 회원 업데이트 부분 변경으로 시그니처 변경

- 현재 회원 update에서 변경되는 부분만 인자로 남겨둠
- update 시, Member 에서 MemberInfo.getXX을 하는 대신 MemberInfo에서 부분 변경된 객체를 새로 반환하도록 수정

* feat: 회원 정보 수정 API 구현 및 테스트 작성

* test: JwtTokenProviderTest 작성

- 로컬에서 Postman 테스트 시 이 테스트를 사용하면 쉽게 토큰 발급 후 활용 가능

* chore: 로컬 환경용 더미데이터 sql 작성

* chore: 로컬 환경 data.sql을 위한 서브모듈 변경

* docs: 기능 명세서 및 테스트 코드 용어 정리 (유저, 멤버 -> 회원)

* chore: 로그 환경설정 파일 디렉터리 분리

* feat: 닉네임 중복 검증 구현

* refactor: 회원의 이메일 Unique 제약조건 삭제

- 닉네임, OauthId로 회원을 식별할 수 있다.
- 같은 이메일로 네이버, 카카오에 가입한 사람이 소셜 로그인으로 두 계정을 만들 경우, 동일한 이메일이 저장될 수도 있다.

* refactor: 모호한 메서드명 수정

* refactor: Email이 Unique하지 않음에 따라 테스트에서 사용하는 조회 쿼리 변경

- findByEmail 대신 findById
- 기본키가 아닌 유일키로 조회하는 건 테이블 구조 변경 여지가 있으므로 findById 사용

* refactor: 내 정보 수정 API URI 변경

* fix: 디렉터리 분리에 따른 로그 설정 파일 appender 경로 수정

* [BE] Refactor/#406 토픽 권한을 가진 회원 목록 조회 시 공개 여부를 함께 반환하도록 변경  (#412)

* refactor: 사용하지 않는 MemberRepository 메서드 삭제

* refactor: 회원 업데이트 부분 변경으로 시그니처 변경

- 현재 회원 update에서 변경되는 부분만 인자로 남겨둠
- update 시, Member 에서 MemberInfo.getXX을 하는 대신 MemberInfo에서 부분 변경된 객체를 새로 반환하도록 수정

* feat: 회원 정보 수정 API 구현 및 테스트 작성

* test: JwtTokenProviderTest 작성

- 로컬에서 Postman 테스트 시 이 테스트를 사용하면 쉽게 토큰 발급 후 활용 가능

* chore: 로컬 환경용 더미데이터 sql 작성

* chore: 로컬 환경 data.sql을 위한 서브모듈 변경

* docs: 기능 명세서 및 테스트 코드 용어 정리 (유저, 멤버 -> 회원)

* chore: 로그 환경설정 파일 디렉터리 분리

* feat: 닉네임 중복 검증 구현

* refactor: 회원의 이메일 Unique 제약조건 삭제

- 닉네임, OauthId로 회원을 식별할 수 있다.
- 같은 이메일로 네이버, 카카오에 가입한 사람이 소셜 로그인으로 두 계정을 만들 경우, 동일한 이메일이 저장될 수도 있다.

* refactor: 모호한 메서드명 수정

* refactor: Email이 Unique하지 않음에 따라 테스트에서 사용하는 조회 쿼리 변경

- findByEmail 대신 findById
- 기본키가 아닌 유일키로 조회하는 건 테이블 구조 변경 여지가 있으므로 findById 사용

* refactor: 내 정보 수정 API URI 변경

* refactor: 토픽 권한 회원 목록 조회 API를 접근 정보(권한 회원 목록 및 공개 여부) 조회로 명세 변경

- 관련 검토가 필요한 API 설계 및 구현 내용에 대한 TODO 주석 작성

* fix: 디렉터리 분리에 따른 로그 설정 파일 appender 경로 수정

* fix: 디렉터리 분리에 따른 로그 설정 파일 appender 경로 수정

* refactor: 실수로 바꾼 기존 메서드명 원복

* refactor: 불필요한 import문 제거

* refactor: 불필요한 접근제어자, 중복 코드 제거

* docs: Restdocs API 네이밍 반영

* fix: 내 정보 수정 RestDocs 스니펫 누락 추가

* [BE] Feature/#388 refresh token 및 로그아웃 기능 구현 (#411)

* chore: redis 의존성 추가

* refactor: OauthService 필드에 final 추가

* feat: refreshToken 엔티티 및 레포지토리 구현

* feat: JwtTokenProvider RefreshToken 발급 구현

* feat: 로그인 시 RefreshToken 발급 기능 구현

* feat: Auth 패키지 커스텀 예외 추가

* refactor: validate 메서드 리팩터링

* chore: refreshToken 만료 시간 추가

* test: Test를 위한 설정 변경

* feat: 액세스 토큰 재발급 및 로그아웃 기능 구현

* chore: Redis 의존성 제거

* test: TestTokenProvider 객체 구현

* refactor: /logout HttpMethod 변경, cookie 관련 cors설정 및 maxAge 설정,

* test: DisplayName 추가

* feat: RTR 적용 및 OauthConntroller 제거, OauthService 및 TokenService 역할과 책임 재분배

* refactor : 피드백 반영

* refactor : 매직넘버 상수화

* refactor : 네이밍 수정

* feat: 쿠키 설정 추가

* [BE] Fix/#424 refresh token duplicated (#425)

* fix: RefreshToken Payload 추가 및 CORS 완화

* fix: Refresh Token Header 허용

* [BE] Fix/#426 Token CORS 재설정 (#427)

* fix: RefreshToken Payload 추가 및 CORS 완화

* fix: Refresh Token Header 허용

* fix: CORS 재설정 및 sameSite None

* [BE] HotFix/#426 Refresh Token 중복 저장 방지 로직 수정 (#431)

* fix: RefreshToken Payload 추가 및 CORS 완화

* fix: Refresh Token Header 허용

* fix: CORS 재설정 및 sameSite None

* fix: refreshToken 존재 시 삭제 로직 변경

* [BE] HotFix/#426 delete 메서드에 clearAutomatically 속성 적용 (#432)

* fix: RefreshToken Payload 추가 및 CORS 완화

* fix: Refresh Token Header 허용

* fix: CORS 재설정 및 sameSite None

* fix: refreshToken 존재 시 삭제 로직 변경

* fix: delete 메서드에 clearAutomatically 속성 적용

* [BE] HotFix/#426 tokenService flush 추가 (#433)

* fix: RefreshToken Payload 추가 및 CORS 완화

* fix: Refresh Token Header 허용

* fix: CORS 재설정 및 sameSite None

* fix: refreshToken 존재 시 삭제 로직 변경

* fix: delete 메서드에 clearAutomatically 속성 적용

* fix: delete 메서드에 clearAutomatically 속성 제거 및 flush 추가

* [BE] Refactor/#400 토픽 조회 시 업데이트 일시를 최근에 핀이 추가/변경된 일시로 변경 (#429)

* refactor: BaseEntity의 createdAt update 방지

* feat: Topic에 lastPinUpdatedAt 컬럼 추가, EntityListner 적용

- 기존 BaseEntity의 값들은 객체가 영속화될 때 저장된다.
- 이에 대해 일관성을 유지해야 한다. (핀 생성 일시, 핀 변경 일시 = 토픽의 최근 핀 변경 일시가 서로 같아야 하므로)
- 따라서 lastPinUpdatedAt 컬럼의 업데이트 또한 EntityListener 로 적용한다.

* feat: 토픽 조회 DTO의 updatedAt 값 lastPinUpdatedAt 으로 변경

* feat: 토픽 최신순 조회 로직 수정

- Topic에 lastPinUpdatedAt 추가로 인해 로직 수정 가능

* test: 토픽 조회 시 updatedAt 검증 테스트 추가

* chore: 로컬 테스트용 SQL에 테이블 컬럼 추가 변경 반영

* refactor: 토픽 Response Dto에 lastPinUpdatedAt 반영

* fix : 토큰 만료시간 및 redirect uri 수정

---------

Co-authored-by: jaeyeon kim <[email protected]>

* [BE] Feature/#422 성능 측정을 위한 로깅 구현 (#434)

* feat: QueryCounter 객체 구현

* feat: QueryInspector 객체 구현

* feat: LatencyRecorder 객체 구현

* feat: LatencyLoggingFilter 객체 구현

* feat: LatencyRecorder Thread-safe 테스트 구현

* feat: HibernateConfig 구현

* test: 테스트 수정

* style: 개행 추가

* refactor: 수식 표현 방식 수정

* [BE] HotFix/#424 refresh token duplicated (#441)

* fix: RefreshToken Payload 추가 및 CORS 완화

* fix: Refresh Token Header 허용

* fix: CORS 재설정 및 sameSite None

* fix: 디버깅을 위한 에러코드 추가

* [BE] HOTFix/#424 validateTokensForReissue 디버깅을 위한 에러코드 추가 (#443)

* fix: RefreshToken Payload 추가 및 CORS 완화

* fix: Refresh Token Header 허용

* fix: CORS 재설정 및 sameSite None

* fix: 디버깅을 위한 에러코드 추가

* fix: validateTokensForReissue 디버깅을 위한 에러코드 추가

* fix: isExpired 임시 log 처리 (#444)

* Revert "fix: isExpired 임시 log 처리 (#444)"

This reverts commit 445f0dd.

* fix: cors Credentials 추가 (#458)

* [BE] Hotfix/cors allowHeaders 와일드카드 적용 (#462)

* fix: cors Credentials 추가

* fix: allowedHeaders 와일드카드 적용

* [BE] 부하테스트를 위한 Tomcat Log 추가 (#464)

* chore: yml 변수 적용 확인을 위한 debug 로그 추가

* chore: 톰캣 설정 기본값 추가

* chore: 톰캣 설정 기본값 추가

---------

Co-authored-by: yoondgu <[email protected]>

* [BE] S3 를 통한 Image Upload 기능 구현 (#428)

* refactor : s3 패키지 추가로 인한 에러 Code 수정

* feat : s3 exception 추가

* refactor : image extension 추출 방식 수정

* refactor : S3Client 가 IOException 을 throw 할 수 있도록 작성

* style : 프린트, 주석 제거

* test : imageExtension Test 작성

* refactor : image 가 요청으로 들어오지 않는 경우를 고려해 로직 수정

* test : 이미지가 null 로 들어오는 경우 test 작성

* feat : 병합시에도 S3 Image Upload 가 가능하도록 구현

* refactor : 기본 이미지 URL 변경

* refactor : 기본 이미지의 처리를 TopicInfo -> Image 에서 할 수 있도록 수정

* refactor : 주석 앞에 TODO 추가

* refactor : fromImageFileName -> from 으로 메서드 명 변경

* refactor : getExtension -> findExtension 으로 변경

* refactor : S3 관련 Service 네이밍 수정

* [BE] Fix/#426 Token CORS 재설정 (#427)

* fix: RefreshToken Payload 추가 및 CORS 완화

* fix: Refresh Token Header 허용

* fix: CORS 재설정 및 sameSite None

* refactor : S3 관련 Service 네이밍 수정

* [BE] HotFix/#426 Refresh Token 중복 저장 방지 로직 수정 (#431)

* fix: RefreshToken Payload 추가 및 CORS 완화

* fix: Refresh Token Header 허용

* fix: CORS 재설정 및 sameSite None

* fix: refreshToken 존재 시 삭제 로직 변경

* [BE] HotFix/#426 delete 메서드에 clearAutomatically 속성 적용 (#432)

* fix: RefreshToken Payload 추가 및 CORS 완화

* fix: Refresh Token Header 허용

* fix: CORS 재설정 및 sameSite None

* fix: refreshToken 존재 시 삭제 로직 변경

* fix: delete 메서드에 clearAutomatically 속성 적용

* [BE] HotFix/#426 tokenService flush 추가 (#433)

* fix: RefreshToken Payload 추가 및 CORS 완화

* fix: Refresh Token Header 허용

* fix: CORS 재설정 및 sameSite None

* fix: refreshToken 존재 시 삭제 로직 변경

* fix: delete 메서드에 clearAutomatically 속성 적용

* fix: delete 메서드에 clearAutomatically 속성 제거 및 flush 추가

* [BE] Refactor/#400 토픽 조회 시 업데이트 일시를 최근에 핀이 추가/변경된 일시로 변경 (#429)

* refactor: BaseEntity의 createdAt update 방지

* feat: Topic에 lastPinUpdatedAt 컬럼 추가, EntityListner 적용

- 기존 BaseEntity의 값들은 객체가 영속화될 때 저장된다.
- 이에 대해 일관성을 유지해야 한다. (핀 생성 일시, 핀 변경 일시 = 토픽의 최근 핀 변경 일시가 서로 같아야 하므로)
- 따라서 lastPinUpdatedAt 컬럼의 업데이트 또한 EntityListener 로 적용한다.

* feat: 토픽 조회 DTO의 updatedAt 값 lastPinUpdatedAt 으로 변경

* feat: 토픽 최신순 조회 로직 수정

- Topic에 lastPinUpdatedAt 추가로 인해 로직 수정 가능

* test: 토픽 조회 시 updatedAt 검증 테스트 추가

* chore: 로컬 테스트용 SQL에 테이블 컬럼 추가 변경 반영

* refactor: 토픽 Response Dto에 lastPinUpdatedAt 반영

* fix : 토큰 만료시간 및 redirect uri 수정

---------

Co-authored-by: jaeyeon kim <[email protected]>

* [BE] Feature/#422 성능 측정을 위한 로깅 구현 (#434)

* feat: QueryCounter 객체 구현

* feat: QueryInspector 객체 구현

* feat: LatencyRecorder 객체 구현

* feat: LatencyLoggingFilter 객체 구현

* feat: LatencyRecorder Thread-safe 테스트 구현

* feat: HibernateConfig 구현

* test: 테스트 수정

* style: 개행 추가

* refactor: 수식 표현 방식 수정

* [BE] HotFix/#424 refresh token duplicated (#441)

* fix: RefreshToken Payload 추가 및 CORS 완화

* fix: Refresh Token Header 허용

* fix: CORS 재설정 및 sameSite None

* fix: 디버깅을 위한 에러코드 추가

* [BE] HOTFix/#424 validateTokensForReissue 디버깅을 위한 에러코드 추가 (#443)

* fix: RefreshToken Payload 추가 및 CORS 완화

* fix: Refresh Token Header 허용

* fix: CORS 재설정 및 sameSite None

* fix: 디버깅을 위한 에러코드 추가

* fix: validateTokensForReissue 디버깅을 위한 에러코드 추가

* fix: isExpired 임시 log 처리 (#444)

* Revert "fix: isExpired 임시 log 처리 (#444)"

This reverts commit 445f0dd.

* fix: cors Credentials 추가 (#458)

* [BE] Hotfix/cors allowHeaders 와일드카드 적용 (#462)

* fix: cors Credentials 추가

* fix: allowedHeaders 와일드카드 적용

* [BE] 부하테스트를 위한 Tomcat Log 추가 (#464)

* chore: yml 변수 적용 확인을 위한 debug 로그 추가

* chore: 톰캣 설정 기본값 추가

* chore: 톰캣 설정 기본값 추가

---------

Co-authored-by: yoondgu <[email protected]>

* refactor : s3 패키지 추가로 인한 에러 Code 수정

* feat : s3 exception 추가

* refactor : image extension 추출 방식 수정

* refactor : S3Client 가 IOException 을 throw 할 수 있도록 작성

* style : 프린트, 주석 제거

* test : imageExtension Test 작성

* refactor : image 가 요청으로 들어오지 않는 경우를 고려해 로직 수정

* test : 이미지가 null 로 들어오는 경우 test 작성

* feat : 병합시에도 S3 Image Upload 가 가능하도록 구현

* refactor : 기본 이미지 URL 변경

* refactor : 기본 이미지의 처리를 TopicInfo -> Image 에서 할 수 있도록 수정

* refactor : 주석 앞에 TODO 추가

* refactor : fromImageFileName -> from 으로 메서드 명 변경

* refactor : getExtension -> findExtension 으로 변경

* refactor : S3 관련 Service 네이밍 수정

* refactor : S3 관련 Service 네이밍 수정

* refactor : topic, image errorCode 수정

* refactor : Exception 부분 네이밍 S3 -> Image 로 변경

* refactor : findExtension -> extractExtensio 으로 메서드 네이밍 변경

* refactor : 부정 조건문 제거

* refactor : Illegal Image File Extension 에러 메세지 수정

* refactor : action method consume type 순서 조정

---------

Co-authored-by: 준팍(junpak) <[email protected]>
Co-authored-by: Doy <[email protected]>
Co-authored-by: zun <[email protected]>

---------

Co-authored-by: zun <[email protected]>
Co-authored-by: 준팍(junpak) <[email protected]>
Co-authored-by: jaeyeon kim <[email protected]>
Co-authored-by: kpeel5839 <[email protected]>
@yoondgu yoondgu deleted the feat/#386-images branch October 6, 2023 15:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
BE 백엔드 관련 이슈 feat 새로운 기능 개발 우선순위 : 상
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

4 participants