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

[PROD 배포] v.? #75

Merged
merged 81 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
6c78fcf
feat: implement photo sort method in findAllByAlbumId
gmkim20713 Sep 3, 2024
c4c354b
feat: add photo sort parameter in get photos api
gmkim20713 Sep 3, 2024
e07d54e
refactor: change handleExceptionInternal to send requestBody info
gmkim20713 Sep 7, 2024
83249d2
refactor: create sendQrRelatedErrorNotification in SlackService
gmkim20713 Sep 8, 2024
40b3509
refactor: combine GlobalExceptionHandler to WebExceptionHandler
gmkim20713 Sep 10, 2024
894bd68
feat: implement presigned url generating logic
gmkim20713 Oct 17, 2024
c441228
feat: implement presigned url generating api
gmkim20713 Oct 17, 2024
b496af7
feat: implement file url upload api
gmkim20713 Oct 17, 2024
e74511c
Merge pull request #66 from YAPP-Github/feature/MAFOO-122
gmkim20713 Oct 17, 2024
a69bbdf
refactor: change BrandType.EXTERNAL importing method
gmkim20713 Oct 18, 2024
477efc7
refactor: create presigned-url-expiration in application.yaml
gmkim20713 Oct 18, 2024
7c0e706
refactor: refactor generatePresignedUrl method in ObjectStorageService
gmkim20713 Oct 18, 2024
dfde7aa
feat: add presigned-url-expiration value for object storage
gmkim20713 Oct 18, 2024
a04ed38
fix: fix presigned url generating api to post mapping
gmkim20713 Oct 19, 2024
aa6de5f
fix: restore original api for uploading qr photo
gmkim20713 Oct 19, 2024
b45359f
feat: add pre signed url generation maximum checking logic
gmkim20713 Oct 19, 2024
ee970bc
Merge pull request #67 from YAPP-Github/feature/MAFOO-151
gmkim20713 Oct 19, 2024
b6e3b84
feat: implement acl setting method for object storage service
gmkim20713 Oct 19, 2024
7360576
feat: attach acl setting logic in createNewPhotoFileUrl
gmkim20713 Oct 19, 2024
3f5332a
fix: fix duplicating lines in createNewPhotoByQrUrl
gmkim20713 Oct 19, 2024
68a8f6e
fix: fix object storage error code typo
gmkim20713 Oct 19, 2024
0756372
refactor: add message for uploadFile runtime exception
gmkim20713 Oct 19, 2024
225de84
Merge pull request #68 from YAPP-Github/feature/MAFOO-168
gmkim20713 Oct 19, 2024
dab1d10
fix: add key extracting logic from filePath
gmkim20713 Oct 19, 2024
e84f88f
fix: fix presigned url format
gmkim20713 Oct 19, 2024
26f8710
fix: fix object storage accessible link format
gmkim20713 Oct 19, 2024
f07d002
refactor: advance extractFullPath as extractURI
gmkim20713 Oct 25, 2024
655b9f9
feat: add albumId saving & photoCount updating logic for uploadFileUr…
gmkim20713 Oct 25, 2024
6f1847a
feat: create findByPhotoId method
gmkim20713 Oct 25, 2024
3c51ab3
refactor: remove album repository from photo service
gmkim20713 Oct 25, 2024
45228f4
refactor: refactor deletePhotoById to use findByPhotoId instead of ph…
gmkim20713 Oct 25, 2024
2236e54
refactor: refactor updatePhotoDisplayIndex to use findByPhotoId inste…
gmkim20713 Oct 25, 2024
9ca74cc
fix: fix updatePhotoBulkAlbumId photoCount & displayIndex updating error
gmkim20713 Oct 25, 2024
7a4a50f
style: remove useless indent
gmkim20713 Oct 25, 2024
05ae694
refactor: refactor methods in album service to use findByAlbumId inst…
gmkim20713 Oct 25, 2024
c1267c6
fix: add sortMethod setting logic for findAllByAlbumId
gmkim20713 Oct 25, 2024
3ddada2
feat: add display index setting logic for createNewPhotoFileUrls
gmkim20713 Oct 25, 2024
fc132fd
Merge pull request #69 from YAPP-Github/feature/MAFOO-121
gmkim20713 Oct 25, 2024
88a73af
feat: implement handlePhotoBrandNotExistsException in WebExceptionHan…
gmkim20713 Oct 25, 2024
20b06b7
feat: create RequestBodyCachingFilter
gmkim20713 Oct 25, 2024
a4d047c
Merge pull request #71 from YAPP-Github/feature/MAFOO-169
gmkim20713 Oct 26, 2024
c80ad4c
Merge pull request #70 from YAPP-Github/feature/MAFOO-173
gmkim20713 Oct 26, 2024
8b4938c
hotfix: remove problematic RequestBodyCachingFilter temporarily
gmkim20713 Oct 26, 2024
88cdcaa
hotfix: fix object storage filePath format error
gmkim20713 Oct 26, 2024
4dd332e
feat: add recap related values in application.yaml
gmkim20713 Oct 27, 2024
dd8641f
chore: add ffmpeg in dependencies list
gmkim20713 Oct 27, 2024
ca46658
feat: implement recap api interface
gmkim20713 Oct 27, 2024
09cad49
feat: implement downloadFilesForRecap in ObjectStorageService
gmkim20713 Oct 27, 2024
12b6168
feat: implement uploadFileFromPath in ObjectStorageService
gmkim20713 Oct 27, 2024
01b84d3
feat: implement generateAlbumChipForRecap in Graphics2dService
gmkim20713 Oct 27, 2024
41b1283
feat: implement deleteSimilarNameFileForPath in LocalFileService
gmkim20713 Oct 27, 2024
011ad9a
feat: implement createRecap in RecapService
gmkim20713 Oct 27, 2024
1243acf
feat: create RecapController
gmkim20713 Oct 27, 2024
0a41edd
hotfix: add file type checking logic for presigned-url generating method
gmkim20713 Oct 27, 2024
8987e43
chore: add ffmpeg path to application.yaml
gmkim20713 Oct 27, 2024
c2a8df8
refactor: change create recap api's parameter to request body
gmkim20713 Oct 27, 2024
3bdab9a
feat: create getMemberInfo in MemberService
gmkim20713 Oct 27, 2024
43b13b8
feat: add userInfo getting logic for create recap api
gmkim20713 Oct 27, 2024
9830b57
feat: set max-size for recap
gmkim20713 Oct 27, 2024
9165635
Merge branch 'dev' into feature/MAFOO-156
gmkim20713 Nov 1, 2024
89aa935
chore: create Dockerfile for custom-base-image
gmkim20713 Nov 1, 2024
d6b900a
chore: add environment value for jib
gmkim20713 Nov 1, 2024
1939758
chore: add RECAP_SRC_URL in jib-build.yaml
gmkim20713 Nov 1, 2024
7650483
feat: create recap properties
gmkim20713 Nov 3, 2024
2597eb9
chore: change recap_src_url as secrets value
gmkim20713 Nov 3, 2024
406cd75
Merge pull request #73 from YAPP-Github/feature/MAFOO-156
gmkim20713 Nov 3, 2024
e0c524c
chore: remove custom-base-image related useless elements
gmkim20713 Nov 3, 2024
26334b8
fix: specify docker image repo name
gmkim20713 Nov 4, 2024
8d17980
fix: change docker base image name
gmkim20713 Nov 4, 2024
a392373
Merge pull request #74 from YAPP-Github/feature/MAFOO-175
gmkim20713 Nov 4, 2024
0c29ec6
refactor: change authorization header getting logic for recap api
gmkim20713 Nov 4, 2024
0e6fc22
feat: add tmp file deleting logic for recap api error case
gmkim20713 Nov 4, 2024
1a4a7c9
docs: add comment for RecapApi
gmkim20713 Nov 4, 2024
e54538c
fix: fix getMemberInfo to use proper endpoint
gmkim20713 Nov 4, 2024
461626b
style: remove underbar from RuntimException message
gmkim20713 Nov 4, 2024
485a219
feat: add debug option for every ffmpeg commend
gmkim20713 Nov 4, 2024
c66aa81
fix: remove wrong semicolon in ffmpeg commend
gmkim20713 Nov 4, 2024
59bd328
fix: fix code to remove wrong semicolon in ffmpeg commend
gmkim20713 Nov 4, 2024
ec5d233
fix: fix to specify input index for ffmpeg commend
gmkim20713 Nov 4, 2024
f9f12fa
refactor: change ffmpeg debug related lines as comment
gmkim20713 Nov 4, 2024
2bba7a5
refactor: fix recap max size as 5
gmkim20713 Nov 7, 2024
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: 2 additions & 1 deletion photo-service/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ dependencies {
implementation("io.opentelemetry:opentelemetry-exporter-zipkin:1.40.0")
implementation("io.micrometer:micrometer-registry-prometheus:1.13.2")
implementation("com.slack.api:slack-api-client:1.40.3")
implementation("net.bramp.ffmpeg:ffmpeg:0.8.0")
}

tasks.withType<Test> {
Expand All @@ -52,7 +53,7 @@ jib {
val imageTag: String? = System.getenv("IMAGE_TAG")
val serverPort: String = System.getenv("SERVER_PORT") ?: "8080"
from {
image = "amazoncorretto:17-alpine3.17-jdk"
image = "spinachpasta/photo-service-base:latest"
}
to {
image = imageName
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package kr.mafoo.photo.api;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import kr.mafoo.photo.annotation.RequestMemberId;
import kr.mafoo.photo.controller.dto.request.ObjectStoragePreSignedUrlRequest;
import kr.mafoo.photo.controller.dto.response.PreSignedUrlResponse;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;

@Validated
@Tag(name = "Object Storage 관련 API", description = "Object Storage 관련 API")
@RequestMapping("/v1/object-storage")
public interface ObjectStorageApi {

@Operation(summary = "Pre-signed Url 요청", description = "Pre-signed Url 목록을 발급합니다.")
@PostMapping
Mono<PreSignedUrlResponse> createPreSignedUrls(
@RequestMemberId
String memberId,

@RequestBody
ObjectStoragePreSignedUrlRequest request
);
}
41 changes: 32 additions & 9 deletions photo-service/src/main/java/kr/mafoo/photo/api/PhotoApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@
import jakarta.validation.Valid;
import kr.mafoo.photo.annotation.RequestMemberId;
import kr.mafoo.photo.annotation.ULID;
import kr.mafoo.photo.controller.dto.request.PhotoCreateRequest;
import kr.mafoo.photo.controller.dto.request.PhotoBulkUpdateAlbumIdRequest;
import kr.mafoo.photo.controller.dto.request.PhotoUpdateAlbumIdRequest;
import kr.mafoo.photo.controller.dto.request.PhotoUpdateDisplayIndexRequest;
import kr.mafoo.photo.controller.dto.request.*;
import kr.mafoo.photo.controller.dto.response.PhotoResponse;
import org.springframework.http.MediaType;
import org.springframework.http.codec.multipart.FilePart;
Expand All @@ -31,18 +28,44 @@ Flux<PhotoResponse> getPhotos(
@ULID
@Parameter(description = "앨범 ID", example = "test_album_id")
@RequestParam
String albumId
String albumId,

@Parameter(description = "정렬 종류", example = "ASC | DESC")
@RequestParam(required = false)
String sort
);

@Operation(summary = "(수정 이전) QR 사진 업로드", description = "QR을 사용해 사진을 업로드합니다.")
@PostMapping(value = "")
Mono<PhotoResponse> uploadQrPhotoOriginal(
@RequestMemberId
String memberId,

@Valid
@RequestBody
PhotoQrUploadRequest request
);

@Operation(summary = "QR 사진 업로드", description = "QR을 사용해 사진을 업로드합니다.")
@PostMapping(value = "/qr")
Mono<PhotoResponse> uploadQrPhoto(
@RequestMemberId
String memberId,

@Valid
@RequestBody
PhotoQrUploadRequest request
);

@Operation(summary = "사진 생성", description = "사진을 생성합니다.")
@PostMapping
Mono<PhotoResponse> createPhoto(
@Operation(summary = "파일(url) 사진 n건 업로드", description = "파일(url)을 사용해 사진을 업로드합니다.")
@PostMapping(value = "/file-urls")
Flux<PhotoResponse> uploadFileUrlPhoto(
@RequestMemberId
String memberId,

@Valid
@RequestBody
PhotoCreateRequest request
PhotoFileUrlUploadRequest request
);

@Operation(summary = "사진 파일로 업로드", description = "사진을 직접 업로드합니다.")
Expand Down
37 changes: 37 additions & 0 deletions photo-service/src/main/java/kr/mafoo/photo/api/RecapApi.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package kr.mafoo.photo.api;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import kr.mafoo.photo.annotation.RequestMemberId;
import kr.mafoo.photo.controller.dto.request.RecapCreateRequest;
import kr.mafoo.photo.controller.dto.response.RecapResponse;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;

@Validated
@Tag(name = "리캡 관련 API", description = "리캡 생성 API")
@RequestMapping("/v1/recaps")
public interface RecapApi {
@Operation(summary = "리캡 생성", description = "앨범의 리캡을 생성합니다.")
@PostMapping
Mono<RecapResponse> createRecap(
@RequestMemberId
String memberId,

@Valid
@RequestBody
RecapCreateRequest request,

@Parameter(description = "정렬 종류", example = "ASC | DESC")
@RequestParam(required = false)
String sort,

// Authorization Header를 받아올 목적
ServerHttpRequest serverHttpRequest
);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package kr.mafoo.photo.config;

import lombok.extern.slf4j.Slf4j;
import net.bramp.ffmpeg.FFmpeg;
import net.bramp.ffmpeg.FFmpegExecutor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;

@Slf4j
@Configuration
public class FFmpegConfig {

@Value("${ffmpeg.path}")
private String ffmpegPath;

@Bean
public FFmpegExecutor ffMpegExecutor() throws IOException {
FFmpeg ffmpeg = new FFmpeg(ffmpegPath);
return new FFmpegExecutor(ffmpeg);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//package kr.mafoo.photo.config;
//
//import org.springframework.core.io.buffer.DataBuffer;
//import org.springframework.core.io.buffer.DataBufferUtils;
//import org.springframework.http.server.reactive.ServerHttpRequest;
//import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
//import org.springframework.stereotype.Component;
//import org.springframework.web.server.ServerWebExchange;
//import org.springframework.web.server.ServerWebExchangeDecorator;
//import org.springframework.web.server.WebFilter;
//import org.springframework.web.server.WebFilterChain;
//import reactor.core.publisher.Flux;
//import reactor.core.publisher.Mono;
//
//@Component
//public class RequestBodyCachingFilter implements WebFilter {
//
// // TODO: 추후 정리 필요
//
// @Override
// public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
// return DataBufferUtils.join(exchange.getRequest().getBody())
// .flatMap(dataBuffer -> {
// byte[] bytes = new byte[dataBuffer.readableByteCount()];
// dataBuffer.read(bytes);
// DataBufferUtils.release(dataBuffer);
//
// ServerHttpRequestDecorator decoratedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
// @Override
// public Flux<DataBuffer> getBody() {
// return Flux.just(exchange.getResponse().bufferFactory().wrap(bytes));
// }
// };
//
// ServerWebExchangeDecorator decoratedExchange = new ServerWebExchangeDecorator(exchange) {
// @Override
// public ServerHttpRequest getRequest() {
// return decoratedRequest;
// }
// };
//
// return chain.filter(decoratedExchange);
// });
// }
//}
//
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,33 @@
import kr.mafoo.photo.controller.dto.response.ErrorResponse;
import kr.mafoo.photo.exception.DomainException;
import kr.mafoo.photo.exception.ErrorCode;
import kr.mafoo.photo.exception.PhotoBrandNotExistsException;
import kr.mafoo.photo.service.SlackService;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.bind.support.WebExchangeBindException;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.Optional;

@RestControllerAdvice
@RequiredArgsConstructor
public class WebExceptionHandler {

private final Logger logger = LoggerFactory.getLogger(WebExceptionHandler.class);
private final SlackService slackService;

@ExceptionHandler(DomainException.class)
public ResponseEntity<ErrorResponse> handleDomainException(DomainException exception) {
return ResponseEntity
Expand Down Expand Up @@ -42,4 +61,99 @@ public ResponseEntity<ErrorResponse> validException(Exception ex) {
.badRequest()
.body(response);
}

@ExceptionHandler(PhotoBrandNotExistsException.class)
public Mono<ResponseEntity<ErrorResponse>> handlePhotoBrandNotExistsException(ServerWebExchange exchange, PhotoBrandNotExistsException exception) {
String method = extractMethod(exchange);
String userAgent = extractUserAgent(exchange);
String fullPath = extractURI(exchange);
String originIp = extractOriginIp(exchange);

return extractRequestBody(exchange).flatMap(requestBody -> {
logException(method, fullPath, originIp, userAgent, exception);

return slackService.sendQrRelatedErrorNotification(
method, fullPath, requestBody, originIp, userAgent, exception.getMessage()
).then(Mono.just(
ResponseEntity
.badRequest()
.body(ErrorResponse.fromErrorCode(exception.getErrorCode()))
));
});
}

@ExceptionHandler(ResponseStatusException.class)
public Mono<ResponseEntity<String>> handleResponseStatusException(ServerWebExchange exchange, ResponseStatusException exception) {
return handleExceptionInternal(exchange, exception, (HttpStatus) exception.getStatusCode());
}

@ExceptionHandler(Exception.class)
public Mono<ResponseEntity<String>> handleGenericException(ServerWebExchange exchange, Exception exception) {
return handleExceptionInternal(exchange, exception, HttpStatus.INTERNAL_SERVER_ERROR);
}

private Mono<ResponseEntity<String>> handleExceptionInternal(ServerWebExchange exchange, Exception exception, HttpStatus status) {
String method = extractMethod(exchange);
String userAgent = extractUserAgent(exchange);
String fullPath = extractURI(exchange);
String originIp = extractOriginIp(exchange);

return extractRequestBody(exchange).flatMap(requestBody -> {

logException(method, fullPath, originIp, userAgent, exception);

if (status == HttpStatus.INTERNAL_SERVER_ERROR) {
return slackService.sendErrorNotification(
method, fullPath, requestBody, originIp, userAgent, exception.getMessage()
).then(Mono.just(new ResponseEntity<>("Internal Server Error", status)));
}

return Mono.just(new ResponseEntity<>(status.getReasonPhrase(), status));
});
}

private String extractMethod(ServerWebExchange exchange) {
return exchange.getRequest().getMethod().toString();
}

private String extractUserAgent(ServerWebExchange exchange) {
return exchange.getRequest().getHeaders().getFirst("User-Agent");
}

private String extractURI(ServerWebExchange exchange) {
var request = exchange.getRequest();

String scheme = request.getURI().getScheme();
String host = request.getURI().getHost();
int port = request.getURI().getPort();
String fullPath = request.getURI().getRawPath();
String query = request.getURI().getQuery();

String baseUrl = (port != -1) ? host + ":" + port : host;
String uriWithHost = scheme + "://" + baseUrl + fullPath;

return (query != null && !query.isEmpty()) ? uriWithHost + "?" + query : uriWithHost;
}

private String extractOriginIp(ServerWebExchange exchange) {
String proxyIp = exchange.getRequest().getHeaders().getFirst("X-Forwarded-For");
InetSocketAddress remoteAddress = exchange.getRequest().getRemoteAddress();
return Optional.ofNullable(proxyIp)
.orElseGet(() -> remoteAddress != null ? remoteAddress.getAddress().getHostAddress() : "UNKNOWN SOURCE");
}

private Mono<String> extractRequestBody(ServerWebExchange exchange) {
return exchange.getRequest().getBody().map(dataBuffer -> {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);

return new String(bytes, StandardCharsets.UTF_8);
}).reduce(new StringBuilder(), StringBuilder::append)
.map(StringBuilder::toString);
}

private void logException(String method, String fullPath, String originIp, String userAgent, Exception exception) {
logger.error("Exception occurred: {} {} {} ERROR {} {}", method, fullPath, originIp, exception.getMessage(), userAgent);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package kr.mafoo.photo.controller;

import kr.mafoo.photo.api.ObjectStorageApi;
import kr.mafoo.photo.controller.dto.request.*;
import kr.mafoo.photo.controller.dto.response.PreSignedUrlResponse;
import kr.mafoo.photo.service.ObjectStorageService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

@RequiredArgsConstructor
@RestController
public class ObjectStorageController implements ObjectStorageApi {

private final ObjectStorageService objectStorageService;

@Override
public Mono<PreSignedUrlResponse> createPreSignedUrls(
String memberId,
ObjectStoragePreSignedUrlRequest request
) {
return objectStorageService
.createPreSignedUrls(request.fileNames(), memberId)
.map(PreSignedUrlResponse::fromStringArray);
}
}
Loading
Loading