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
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
package com.nect.api.domain.mypage.controller;

import com.nect.api.domain.mypage.dto.*;
import com.nect.api.domain.matching.service.RecruitmentService;
import com.nect.api.domain.mypage.dto.MyProjectStringListRequest;
import com.nect.api.domain.matching.dto.RecruitmentReqDto;
import com.nect.api.domain.matching.dto.RecruitmentResDto;
import com.nect.api.domain.matching.service.RecruitmentService;
import com.nect.api.domain.mypage.dto.MyProjectStringListRequest;
import com.nect.api.domain.mypage.dto.MyProjectsResponseDto;
import com.nect.api.domain.mypage.dto.ProfileSettingsDto;
import com.nect.api.domain.mypage.dto.ProfileSettingsDto.*;
import com.nect.api.domain.mypage.service.*;
import com.nect.api.domain.mypage.dto.ProfileSettingsDto.ProfileSettingsRequestDto;
import com.nect.api.domain.mypage.dto.ProfileSettingsDto.ProfileSettingsResponseDto;
import com.nect.api.domain.team.project.dto.ProjectUserFieldReqDto;
import com.nect.api.domain.team.project.dto.ProjectUserFieldResDto;
import com.nect.api.domain.team.project.dto.ProjectUserResDto;
import com.nect.api.domain.team.project.dto.ProjectUserTypeReqDto;
import com.nect.api.domain.team.project.dto.ProjectMemberStatisticResponse;
import com.nect.api.domain.mypage.dto.TeamRoleAddRequestDto;
import com.nect.api.domain.mypage.service.*;
import com.nect.api.domain.team.project.dto.*;
import com.nect.api.domain.team.project.service.ProjectMemberStatisticService;
import com.nect.api.domain.team.project.service.ProjectService;
import com.nect.api.domain.team.project.service.ProjectUserService;
import com.nect.api.global.response.ApiResponse;
import com.nect.api.global.security.UserDetailsImpl;
Expand Down Expand Up @@ -46,6 +42,7 @@ public class MypageController {
private final UserTeamRoleQueryService userTeamRoleQueryService;
private final ProjectMemberStatisticService projectMemberStatisticService;
private final ProjectDeleteService projectDeleteService;
private final ProjectService projectService;

/**
* 프로필 조회
Expand Down Expand Up @@ -336,5 +333,12 @@ public ApiResponse<Void> deleteProject(
return ApiResponse.ok();
}


@PostMapping("{projectId}/image")
public ApiResponse<String> uploadImage(
@AuthenticationPrincipal UserDetailsImpl user,
@PathVariable Long projectId,
@RequestParam("image") MultipartFile image
) {
return ApiResponse.ok(projectService.uploadImage(user.getUserId(), projectId, image));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,16 @@ public enum ProjectErrorCode implements ResponseCode {
WEEK_MISSION_ALREADY_INITIALIZED("P400_7", "위크미션이 이미 생성되어 있습니다."),
INVALID_WEEK_MISSION_UPDATE("P400_8", "수정할 수 없는 항목이 포함되어 있습니다."),
PROJECT_CREATE_LIMIT_EXCEEDED("P400_9", "생성할 수 있는 프로젝트는 최대 2개입니다."),

INVALID_IMAGE("P400_10", "올바르지 않은 이미지 형식입니다."),
PROJECT_PART_NOT_FOUND("P400_9", "해당 프로젝트 파트(팀 역할)를 찾을 수 없습니다."),
DUPLICATE_PART("P400_10", "이미 존재하는 파트입니다."),
INVALID_CUSTOM_PART_NAME("P400_11", "CUSTOM 파트 이름이 올바르지 않습니다."),


PROJECT_MEMBER_FORBIDDEN("P403_0", "프로젝트 멤버만 접근할 수 있습니다."),
LEADER_ONLY_ACTION("P403_1", "리더만 할 수 있는 요청입니다."),
;

IMAGE_UPLOAD_FAILED("P500_1", "이미지 업로드에 실패했습니다.");

private final String statusCode;
private final String message;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.nect.api.domain.team.project.exception.ProjectException;
import com.nect.api.domain.user.enums.UserErrorCode;
import com.nect.api.domain.user.service.UserService;
import com.nect.api.global.infra.S3Service;
import com.nect.core.entity.analysis.ProjectIdeaAnalysis;
import com.nect.core.entity.analysis.ProjectImprovementPoint;
import com.nect.core.entity.analysis.ProjectWeeklyPlan;
Expand Down Expand Up @@ -38,7 +39,9 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.List;
import java.util.stream.Collectors;
Expand All @@ -61,6 +64,7 @@ public class ProjectService {
private final UserService userService;
private final ProjectInterestFieldRepository projectInterestFieldRepository;
private final UserTeamRoleRepository userTeamRoleRepository;
private final S3Service s3Service;

public Project getProject(Long projectId){
return projectRepository.findById(projectId)
Expand Down Expand Up @@ -350,4 +354,25 @@ private void saveUserTeamRoles(Project project, ProjectIdeaAnalysis analysis) {
}


public String uploadImage(Long userId, Long projectId, MultipartFile image) {
Project project = getProject(projectId);

if(!(userId.equals(projectUserRepository.findLeaderByProject(project)))){
throw new ProjectException(ProjectErrorCode.LEADER_ONLY_ACTION);
}

if (image == null || image.isEmpty()) {
throw new ProjectException(ProjectErrorCode.INVALID_IMAGE);
}

try {
String imageName = s3Service.uploadFile(image);
project.setImageName(imageName);
return s3Service.getPresignedGetUrl(imageName);
} catch (IOException e) {
throw new ProjectException(
ProjectErrorCode.IMAGE_UPLOAD_FAILED, e.getMessage()
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,18 @@
import com.nect.api.domain.mypage.dto.ProfileSettingsDto;
import com.nect.api.domain.mypage.dto.TeamRoleAddRequestDto;
import com.nect.api.domain.mypage.service.*;
import com.nect.api.domain.team.project.dto.ProjectMemberStatisticResponse;
import com.nect.api.domain.team.project.dto.ProjectUserFieldReqDto;
import com.nect.api.domain.team.project.dto.ProjectUserFieldResDto;
import com.nect.api.domain.team.project.dto.ProjectUserResDto;
import com.nect.api.domain.team.project.service.ProjectMemberStatisticService;
import com.nect.api.domain.team.project.service.ProjectService;
import com.nect.api.domain.team.project.service.ProjectUserService;
import com.nect.core.entity.team.enums.PlanFileType;
import com.nect.core.entity.team.enums.ProjectMemberStatus;
import com.nect.core.entity.team.enums.ProjectMemberType;
import com.nect.core.entity.team.enums.RecruitmentStatus;
import com.nect.core.entity.user.enums.InterestField;
import com.nect.api.domain.team.project.dto.ProjectMemberStatisticResponse;
import com.nect.core.entity.user.enums.Role;
import com.nect.core.entity.user.enums.RoleField;
import org.junit.jupiter.api.Test;
Expand All @@ -38,6 +39,7 @@
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.doNothing;
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.*;
Expand Down Expand Up @@ -71,6 +73,9 @@ class MypageControllerTest extends NectDocumentApiTester {
@MockitoBean
private ProjectDeleteService projectDeleteService;

@MockitoBean
private ProjectService projectService;

@Test
void getProfile() throws Exception {
ProfileSettingsDto.ProfileSettingsResponseDto mockResponse = new ProfileSettingsDto.ProfileSettingsResponseDto(
Expand Down Expand Up @@ -439,6 +444,56 @@ void uploadPlanFile_FILE() throws Exception {
));
}

@Test
void uploadImage() throws Exception {
long projectId = 1L;
long userId = 1L;

MockMultipartFile image = new MockMultipartFile(
"image",
"project-image.png",
MediaType.IMAGE_PNG_VALUE,
"dummy image bytes".getBytes()
);

String uploadedUrl = "https://cdn.example.com/projects/1/project-image.png";

given(projectService.uploadImage(anyLong(), eq(projectId), any())).willReturn(uploadedUrl);

mockMvc.perform(multipart("/api/v1/mypage/{projectId}/image", projectId)
.file(image)
.header(AUTH_HEADER, TEST_ACCESS_TOKEN)
.contentType(MediaType.MULTIPART_FORM_DATA)
.accept(MediaType.APPLICATION_JSON)
)
.andExpect(status().isOk())
.andDo(document("mypage-upload-project-image",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
requestHeaders(
headerWithName(AUTH_HEADER).description("Bearer Access Token")
),
requestParts(
partWithName("image").description("업로드할 이미지(MultipartFile)")
),
resource(ResourceSnippetParameters.builder()
.tag("Mypage")
.summary("프로젝트 이미지 업로드")
.description("프로젝트 대표 이미지를 업로드합니다. 업로드 성공 시 이미지 URL을 반환합니다.")
.pathParameters(
parameterWithName("projectId").description("프로젝트 ID")
)
.responseFields(
fieldWithPath("status.statusCode").type(JsonFieldType.STRING).description("상태 코드"),
fieldWithPath("status.message").type(JsonFieldType.STRING).description("상태 메시지"),
fieldWithPath("status.description").optional().type(JsonFieldType.STRING).description("상태 설명"),
fieldWithPath("body").type(JsonFieldType.STRING).description("업로드된 이미지 URL")
)
.build()
)
));
}

@Test
void editPlanFile_LINK() throws Exception {
long projectId = 1L;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,8 @@ public void setProjectPeriod(LocalDate startDate, LocalDate endDate) {
this.plannedStartedOn = startDate;
this.plannedEndedOn = endDate;
}

public void setImageName(String imageName){
this.imageName = imageName;
}
}