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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions src/backend/file_server/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Azul Zulu JDK 17 기반 이미지 사용
FROM azul/zulu-openjdk:17

# 작업 디렉토리 설정
WORKDIR /app

# 환경 변수 파일 복사
COPY .env .env

# 빌드된 JAR 파일 복사
ARG JAR_FILE=build/libs/file_server-0.0.1-SNAPSHOT.jar
COPY ${JAR_FILE} app.jar

# 환경 변수 설정
ENV $(cat .env)

# 포트 노출
EXPOSE 8083

# 애플리케이션 실행
ENTRYPOINT ["java", "-jar", "app.jar"]
Empty file removed src/backend/file_server/local.env
Empty file.
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
package com.jootalkpia.file_server.controller;

import com.jootalkpia.file_server.dto.ChangeProfileResponseDto;
import com.jootalkpia.file_server.dto.UploadFileRequestDto;
import com.jootalkpia.file_server.dto.UploadFileResponseDto;
import com.jootalkpia.file_server.service.FileService;
import com.jootalkpia.file_server.utils.ValidationUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import software.amazon.awssdk.core.ResponseInputStream;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;

@RestController
@RequestMapping("/api/v1")
Expand All @@ -28,24 +36,54 @@ public ResponseEntity<String> testEndpoint() {
return ResponseEntity.ok("Test successful");
}

@PostMapping("/workspace/{workspaceId}/channel/{channelId}")
public ResponseEntity<UploadFileResponseDto> uploadFiles(
@PathVariable Long workspaceId,
@PathVariable Long channelId,
@RequestParam("files") MultipartFile[] files,
@RequestParam("thumbnails") MultipartFile[] thumbnails) {
log.info("ids: {}", workspaceId);
log.info("Files: {}", files != null ? files.length : "null");
log.info("Thumbnails: {}", thumbnails != null ? thumbnails.length : "null");

UploadFileRequestDto uploadFileRequest = new UploadFileRequestDto();
uploadFileRequest.setWorkspaceId(workspaceId);
uploadFileRequest.setChannelId(channelId);
uploadFileRequest.setFiles(files);
uploadFileRequest.setThumbnails(thumbnails);

UploadFileResponseDto response = fileService.uploadFiles(1L, uploadFileRequest);
@PostMapping("/files")
public ResponseEntity<UploadFileResponseDto> uploadFiles(@ModelAttribute UploadFileRequestDto uploadFileRequest) {
log.info("got uploadFileRequest: {}", uploadFileRequest);
ValidationUtils.validateLengthOfFilesAndThumbnails(uploadFileRequest.getFiles().length, uploadFileRequest.getThumbnails().length);
ValidationUtils.validateWorkSpaceId(uploadFileRequest.getWorkspaceId());
ValidationUtils.validateChannelId(uploadFileRequest.getChannelId());
ValidationUtils.validateFiles(uploadFileRequest.getFiles());
ValidationUtils.validateFiles(uploadFileRequest.getThumbnails());

log.info("got uploadFileRequest: {}", uploadFileRequest.getFiles().length);
UploadFileResponseDto response = fileService.uploadFiles(userId, uploadFileRequest);
return ResponseEntity.ok(response);
}

@GetMapping("/files/{fileId}")
public ResponseEntity<InputStreamResource> downloadFile(@PathVariable Long fileId) {
log.info("got downloadFile id: {}", fileId);
ValidationUtils.validateFileId(fileId);

ResponseInputStream<GetObjectResponse> s3InputStream = fileService.downloadFile(fileId);

Copy link
Collaborator

Choose a reason for hiding this comment

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

저는 Controller가 데이터를 전달하는 책임만 가지는 것이 좋다라고 생각해서
다음 비즈니스 로직을 Service단 또는 다른 class로 옮기는 것이 어떠신가요?

Copy link
Member Author

Choose a reason for hiding this comment

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

다음 이슈에서 반영하도록 하겠습니다!

// response 생성
long contentLength = s3InputStream.response().contentLength();

// Content-Type 가져오기 기본값: application/octet-stream
String contentType = s3InputStream.response().contentType() != null
? s3InputStream.response().contentType()
: MediaType.APPLICATION_OCTET_STREAM_VALUE;

// 헤더 설정
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType(contentType));
headers.setContentLength(contentLength);
headers.setContentDispositionFormData("attachment", "file-" + fileId);

return ResponseEntity.ok()
.headers(headers)
.body(new InputStreamResource(s3InputStream));
}

@PostMapping("/{userId}/profile-image")
public ResponseEntity<ChangeProfileResponseDto> changeProfile(
@PathVariable Long userId,
@RequestParam("newImage") MultipartFile newImage) {
log.info("got new profile Image: {}", newImage);
ValidationUtils.validateFile(newImage);

ChangeProfileResponseDto response = fileService.changeProfile(userId, newImage);
return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.jootalkpia.file_server.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
public class ChangeProfileResponseDto {
private Long userId;
private String nickname;
private String profileImage;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.jootalkpia.file_server.entity;

import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import java.time.LocalDateTime;
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseTimeEntity {

@CreatedDate
private LocalDateTime createdAt;

@LastModifiedDate
protected LocalDateTime updatedAt;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.jootalkpia.file_server.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@Table(name = "users")
@Builder
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class User extends BaseTimeEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId;

private Long socialId;

private String email;

private String platform;

private String nickname;

private String profileImage;

public void updateProfileImage(final String newProfileImage) {
this.profileImage = newProfileImage;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.jootalkpia.file_server.exception.common;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class CustomException extends RuntimeException {

private final String code;

public CustomException(String code, String message) {
super(message);
this.code = code;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.jootalkpia.file_server.exception.common;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum ErrorCode {

// 400 Bad Request
UNKNOWN("F00001", "알 수 없는 에러가 발생했습니다."),
BAD_REQUEST("F40001", "잘못된 요청입니다."),
VALIDATION_FAILED("F40002", "유효성 검증에 실패했습니다."),
MISSING_PARAMETER("F40003", "필수 파라미터가 누락되었습니다."),
INVALID_PARAMETER("F40004", "잘못된 파라미터가 포함되었습니다."),
MISSING_FILES("F40005", "파일이 포함되어야 합니다."),

// 404 Not Found
WORKSPACE_NOT_FOUND("F40401", "등록되지 않은 워크스페이스입니다."),
CHANNEL_NOT_FOUND("F40402", "등록되지 않은 채널입니다."),
USER_NOT_FOUND("F40403", "등록되지 않은 유저입니다."),
FILE_NOT_FOUND("F40404", "등록되지 않은 파일입니다."),

// 415 Unsupported Type
UNSUPPORTED_FILE_TYPE("F41501", "지원되지 않는 파일 타입입니다."),

// 500 Internal Server Error
INTERNAL_SERVER_ERROR("F50001", "서버 내부 오류가 발생했습니다."),
DATABASE_ERROR("F50002", "데이터베이스 처리 중 오류가 발생했습니다."),
IMAGE_UPLOAD_FAILED("F50003", "파일 업로드에 실패했습니다."),
IMAGE_DOWNLOAD_FAILED("F50004", "파일 다운로드 중 오류가 발생했습니다."),
FILE_PROCESSING_FAILED("F50005", "파일 처리 중 오류가 발생했습니다."),
UNEXPECTED_ERROR("F50006", "예상치 못한 오류가 발생했습니다.");

private final String code;
private final String msg;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.jootalkpia.file_server.exception.common;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
Copy link
Collaborator

Choose a reason for hiding this comment

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

미르님이 안좋다면서요 Data 쓰는거요ㅠㅠㅠ

@AllArgsConstructor
public class ErrorResponse {
private String code;
private String message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.jootalkpia.file_server.exception.common;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(CustomException.class)
public ResponseEntity<ErrorResponse> handleCustomException(CustomException ex) {
ErrorResponse errorResponse = new ErrorResponse(ex.getCode(), ex.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
}

@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGenericException(Exception ex) {
ErrorResponse errorResponse = new ErrorResponse("UNKNOWN_ERROR", "An unexpected error occurred.");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@

@Repository
public interface FileRepository extends JpaRepository<Files, Long> {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.jootalkpia.file_server.repository;

import com.jootalkpia.file_server.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
}
Loading