Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
fc7c5da
feat: add `S3PresignedUrlGenerator.java`
KOKEONHO Dec 2, 2024
e1546b8
feat: add `CreateTeamRequest.java`
KOKEONHO Dec 2, 2024
c4bb773
feat: add necessary fields in `CreateTeamRequest.java`
KOKEONHO Dec 2, 2024
8d870fb
feat: add necessary constants in `AppConstants.java`
KOKEONHO Dec 2, 2024
05754c9
feat: add `CreateTeamResponse.java`
KOKEONHO Dec 2, 2024
02b08d7
feat: add necessary fields in `CreateTeamResponse.java`
KOKEONHO Dec 2, 2024
bf0c4fb
feat: add `AssignImageRequest.java`
KOKEONHO Dec 2, 2024
90058d9
feat: add `AssignImageResponse.java`
KOKEONHO Dec 2, 2024
33dac99
feat: add necessary field in `AssignImageRequest.java`
KOKEONHO Dec 2, 2024
46432a5
feat: add necessary field in `AssignImageResponse.java`
KOKEONHO Dec 2, 2024
8ac1145
feat: add `TEAM_IMAGE_ASSIGN_SUCCESS` in `ResponseResult.java`
KOKEONHO Dec 2, 2024
dda236b
feat: add necessary annotations in `S3PresignedUrlGenerator.java`
KOKEONHO Dec 2, 2024
430de83
feat: add necessary fields and annotations in `S3PresignedUrlGenerato…
KOKEONHO Dec 2, 2024
8865dd2
feat: add `generatePresignedUrl()` in `S3PresignedUrlGenerator.java`
KOKEONHO Dec 2, 2024
7e5ccb4
feat: add `assignImage()` in `Team.java`
KOKEONHO Dec 2, 2024
a631c96
feat: add `assignImage()` in `TeamController.java`
KOKEONHO Dec 2, 2024
da257b0
feat: add `assignImage()` in `TeamService.java`
KOKEONHO Dec 2, 2024
55b66d3
feat: add `isTeamManager()` in `TeamService.java`
KOKEONHO Dec 2, 2024
dffc687
refactor: edit `createSuccess()` in `TeamControllerTest.java`
KOKEONHO Dec 2, 2024
de00ec5
feat: add `public-read` permission in `S3PresignedUrlGenerator.java`
KOKEONHO Dec 2, 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
8 changes: 5 additions & 3 deletions src/main/java/com/dowe/team/Team.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,18 @@ public class Team extends BaseEntity {
@Builder
public Team(
String title,
String description,
String image
String description
) {
this.title = title;
this.description = description;
this.image = image;
}

public void assignManagerProfile(Profile profile) {
this.managerProfile = profile;
}

public void assignImage(String image) {
this.image = image;
}

}
75 changes: 56 additions & 19 deletions src/main/java/com/dowe/team/application/TeamService.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@

import com.dowe.exception.team.TeamCreationLimitException;
import com.dowe.profile.application.ProfileService;
import com.dowe.profile.infrastructure.ProfileRepository;
import com.dowe.team.dto.request.AssignImageRequest;
import com.dowe.team.dto.request.CreateTeamRequest;
import com.dowe.team.dto.response.AssignImageResponse;
import com.dowe.team.dto.response.CreateTeamResponse;
import com.dowe.util.s3.S3PresignedUrlGenerator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -13,10 +19,7 @@
import com.dowe.member.infrastructure.MemberRepository;
import com.dowe.profile.Profile;
import com.dowe.team.Team;
import com.dowe.team.dto.NewTeam;
import com.dowe.team.dto.TeamSettings;
import com.dowe.team.infrastructure.TeamRepository;
import com.dowe.util.S3Uploader;
import com.dowe.util.StringUtil;

import lombok.RequiredArgsConstructor;
Expand All @@ -26,40 +29,34 @@
@RequiredArgsConstructor
public class TeamService {

private static final String TEAM_IMAGE_DIRECTORY = "team";

private final TeamRepository teamRepository;
private final MemberRepository memberRepository;
private final S3Uploader s3Uploader;

private final ProfileService profileService;

private final S3PresignedUrlGenerator s3PresignedUrlGenerator;
private final ProfileRepository profileRepository;

@Transactional
public NewTeam create(Long memberId, TeamSettings teamSettings) {
public CreateTeamResponse create(
Long memberId,
CreateTeamRequest request
) {

log.info(">>> TeamService create()");

Member member = memberRepository.findById(memberId)
.orElseThrow(MemberNotFoundException::new);

long profileCount = profileService.countProfiles(member.getId());
log.info(">>> ProfileService create() profileCount for member {} : {}", memberId, profileCount);

if (profileCount == MAXIMUM_TEAM_COUNT) {
throw new TeamCreationLimitException();
}

String image = null;
if (teamSettings.getImage() != null && !teamSettings.getImage().isEmpty()) {
image = s3Uploader.upload(TEAM_IMAGE_DIRECTORY, teamSettings.getImage());
}

log.info(">>> image: {}", image);

Team team = Team.builder()
.title(StringUtil.removeExtraSpaces(teamSettings.getTitle()))
.description(StringUtil.removeExtraSpaces(teamSettings.getDescription()))
.image(image)
.title(StringUtil.removeExtraSpaces(request.title()))
.description(StringUtil.removeExtraSpaces(request.description()))
.build();

Profile defaultManagerProfile = profileService.createDefaultProfile(
Expand All @@ -69,8 +66,48 @@ public NewTeam create(Long memberId, TeamSettings teamSettings) {

team.assignManagerProfile(defaultManagerProfile);

Team savedTeam = teamRepository.save(team);
Long teamId = savedTeam.getId();

String presignedUrl = s3PresignedUrlGenerator.generatePresignedUrl(
TEAM_IMAGE_DIRECTORY,
TEAM_IMAGE_PREFIX,
teamId
);

return new CreateTeamResponse(
teamId,
presignedUrl
);
}

@Transactional
public AssignImageResponse assignImage(
Long memberId,
Long teamId,
AssignImageRequest assignImageRequest
) {

Team team = teamRepository.findById(teamId)
.orElseThrow(() -> new IllegalArgumentException("Team not found"));

if (!isTeamManager(team, memberId)) {
throw new IllegalArgumentException("Member is not a manager");
}

team.assignImage(assignImageRequest.image());

teamRepository.save(team);

return new NewTeam(team.getId());
return new AssignImageResponse(team.getId());

}

private boolean isTeamManager(
Team team,
Long memberId
) {
Member manager = team.getManagerProfile().getMember();
return manager.getId().equals(memberId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.dowe.team.dto.request;

public record AssignImageRequest(
String image
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.dowe.team.dto.request;

public record CreateTeamRequest(
String title,
String description
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.dowe.team.dto.response;

public record AssignImageResponse(
Long teamId
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.dowe.team.dto.response;

public record CreateTeamResponse(
Long teamId,
String presignedUrl
) {

}
33 changes: 25 additions & 8 deletions src/main/java/com/dowe/team/presentation/TeamController.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
package com.dowe.team.presentation;

import com.dowe.elasticsearch.application.SearchService;
import com.dowe.team.dto.request.AssignImageRequest;
import com.dowe.team.dto.request.CreateTeamRequest;
import com.dowe.team.dto.response.AssignImageResponse;
import com.dowe.team.dto.response.CreateTeamResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
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.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.dowe.team.application.TeamService;
import com.dowe.team.dto.NewTeam;
import com.dowe.team.dto.TeamSettings;
import com.dowe.util.api.ApiResponse;
import com.dowe.util.api.ResponseResult;
import com.dowe.util.resolver.Login;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

@Slf4j
Expand All @@ -26,16 +28,31 @@ public class TeamController {
private final TeamService teamService;

@PostMapping("/teams")
public ResponseEntity<ApiResponse<NewTeam>> create(
public ResponseEntity<ApiResponse<CreateTeamResponse>> create(
@Login Long memberId,
@ModelAttribute @Valid TeamSettings teamSettings
@RequestBody CreateTeamRequest createTeamRequest
) {
log.info(">>> TeamController create()");
return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiResponse.created(
ResponseResult.TEAM_CREATE_SUCCESS,
teamService.create(memberId, teamSettings)
teamService.create(memberId, createTeamRequest)
)
);
}

@PutMapping("/teams/{teamId}/image")
public ResponseEntity<ApiResponse<AssignImageResponse>> assignImage(
@Login Long memberId,
@PathVariable Long teamId,
@RequestBody AssignImageRequest assignImageRequest
) {
log.info(">>> TeamController assignImage()");
return ResponseEntity.status(HttpStatus.OK)
.body(ApiResponse.ok(
ResponseResult.TEAM_IMAGE_ASSIGN_SUCCESS,
teamService.assignImage(memberId, teamId, assignImageRequest)
));
}

}
3 changes: 3 additions & 0 deletions src/main/java/com/dowe/util/AppConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,7 @@ public final class AppConstants {
public static final int NO_MORE_LAST_HIT_OFFSET = 1;
public static final int HAS_MORE_LAST_HIT_OFFSET = 2;

public static final String TEAM_IMAGE_DIRECTORY = "team/";
public static final String TEAM_IMAGE_PREFIX = "team_";

}
1 change: 1 addition & 0 deletions src/main/java/com/dowe/util/api/ResponseResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public enum ResponseResult {

// Team
TEAM_CREATE_SUCCESS("팀 생성에 성공했습니다"),
TEAM_IMAGE_ASSIGN_SUCCESS("팀 이미지 저장에 성공했습니다."),

// Random
RANDOM_TEAMS_SUCCESS("랜덤 팀 조회에 성공했습니다."),
Expand Down
46 changes: 46 additions & 0 deletions src/main/java/com/dowe/util/s3/S3PresignedUrlGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.dowe.util.s3;

import java.net.URL;
import java.time.Duration;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.PutObjectPresignRequest;

@Component
@RequiredArgsConstructor
public class S3PresignedUrlGenerator {

private final S3Presigner s3Presigner;

@Value("${cloud.aws.s3.bucket}")
private String bucketName;

public String generatePresignedUrl(
String directory,
String imagePrefix,
Long teamId
) {

String key = directory + imagePrefix + teamId;

PutObjectRequest putObjectRequest = PutObjectRequest.builder()
.bucket(bucketName)
.key(key)
.acl("public-read")
.build();

PutObjectPresignRequest presignRequest = PutObjectPresignRequest.builder()
.putObjectRequest(putObjectRequest)
.signatureDuration(Duration.ofMinutes(15))
.build();

URL presignedUrl = s3Presigner.presignPutObject(presignRequest).url();

return presignedUrl.toString();

}

}
31 changes: 20 additions & 11 deletions src/test/java/com/dowe/team/presentation/TeamControllerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import com.dowe.team.dto.request.CreateTeamRequest;
import com.dowe.team.dto.response.CreateTeamResponse;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.http.MediaType;
import org.springframework.restdocs.payload.JsonFieldType;

import com.dowe.RestDocsSupport;
import com.dowe.team.dto.NewTeam;
import com.dowe.util.api.ResponseResult;

class TeamControllerTest extends RestDocsSupport {
Expand All @@ -30,19 +31,26 @@ class TeamControllerTest extends RestDocsSupport {
void createSuccess() throws Exception {
// given
String authorizationHeader = BEARER + "accessToken";
NewTeam newTeam = new NewTeam(20L);
MockMultipartFile image = new MockMultipartFile("image", "test-image.png", "image/png", "some-image".getBytes());

CreateTeamRequest createTeamRequest = new CreateTeamRequest(
"Sample Team",
"Sample Description"
);

CreateTeamResponse createTeamResponse = new CreateTeamResponse(
20L,
"pre-signed url"
);

given(tokenManager.parse(anyString(), any()))
.willReturn(1L);
given(teamService.create(anyLong(), any()))
.willReturn(newTeam);
.willReturn(createTeamResponse);

// when / then
mockMvc.perform(multipart("/teams")
.file(image)
.param("title", "Sample Team")
.param("description", "This is a sample description.")
mockMvc.perform(post("/teams")
.content(objectMapper.writeValueAsString(createTeamRequest))
.contentType(MediaType.APPLICATION_JSON)
.header(AUTHORIZATION, authorizationHeader)
.header(ORIGIN, FRONTEND_DOMAIN)
.header(HOST, BACKEND_DOMAIN))
Expand All @@ -51,14 +59,15 @@ void createSuccess() throws Exception {
.andExpect(jsonPath("$.code").value(HttpStatus.CREATED.value()))
.andExpect(jsonPath("$.status").value(HttpStatus.CREATED.getReasonPhrase()))
.andExpect(jsonPath("$.result").value(ResponseResult.TEAM_CREATE_SUCCESS.getDescription()))
.andExpect(jsonPath("$.data.teamId").value(newTeam.getTeamId()))
.andExpect(jsonPath("$.data.teamId").value(createTeamResponse.teamId()))
.andDo(restDocs.document(
responseFields(
fieldWithPath("code").type(JsonFieldType.NUMBER).description("코드"),
fieldWithPath("status").type(JsonFieldType.STRING).description("상태"),
fieldWithPath("result").type(JsonFieldType.STRING).description("결과"),
fieldWithPath("data").type(JsonFieldType.OBJECT).description("응답 데이터"),
fieldWithPath("data.teamId").type(JsonFieldType.NUMBER).description("팀 ID")
fieldWithPath("data.teamId").type(JsonFieldType.NUMBER).description("팀 ID"),
fieldWithPath("data.presignedUrl").type(JsonFieldType.STRING).description("Pre-signed URL")
)
));
}
Expand Down