Skip to content

Commit 9efa940

Browse files
authored
Merge pull request #8 from BlockCloud-dev/feature/block-api
2 parents 431f525 + ddaad23 commit 9efa940

File tree

13 files changed

+346
-142
lines changed

13 files changed

+346
-142
lines changed

.github/workflows/test.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ jobs:
2727
run: |
2828
mkdir -p src/main/resources
2929
mkdir -p src/test/resources
30-
echo "${{ secrets.APPLICATION_YML }}" > ./src/main/resources/application.yml
31-
echo "${{ secrets.APPLICATION_YML_DEV }}" > ./src/main/resources/application-dev.yml
32-
echo "${{ secrets.APPLICATION_YML_PROD }}" > ./src/main/resources/application-prod.yml
33-
echo "${{ secrets.APPLICATION_YML_SECRET }}" > ./src/main/resources/application-secret.yml
34-
echo "${{ secrets.APPLICATION_YML_TEST }}" > ./src/test/resources/application.yml
30+
echo '${{ secrets.APPLICATION_YML }}' > ./src/main/resources/application.yml
31+
echo '${{ secrets.APPLICATION_YML_DEV }}' > ./src/main/resources/application-dev.yml
32+
echo '${{ secrets.APPLICATION_YML_PROD }}' > ./src/main/resources/application-prod.yml
33+
echo '${{ secrets.APPLICATION_YML_SECRET }}' > ./src/main/resources/application-secret.yml
34+
echo '${{ secrets.APPLICATION_YML_TEST }}' > ./src/test/resources/application.yml
3535
3636
- name: Debug created files
3737
run: |

src/main/java/com/blockcloud/config/SecurityConfig.java

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
package com.blockcloud.config;
22

3+
import com.blockcloud.dto.common.ExceptionDto;
4+
import com.blockcloud.dto.common.ResponseDto;
5+
import com.blockcloud.exception.error.ErrorCode;
36
import com.blockcloud.exception.handler.CustomLogoutSuccessHandler;
47
import com.blockcloud.exception.handler.OAuth2SuccessHandler;
58
import com.blockcloud.jwt.JWTFilter;
69
import com.blockcloud.jwt.JWTUtil;
710
import com.blockcloud.service.CookieService;
811
import com.blockcloud.service.CustomOAuth2UserService;
12+
import com.fasterxml.jackson.databind.ObjectMapper;
13+
import jakarta.servlet.http.HttpServletResponse;
914
import lombok.AllArgsConstructor;
15+
import lombok.RequiredArgsConstructor;
1016
import org.springframework.context.annotation.Bean;
1117
import org.springframework.context.annotation.Configuration;
18+
import org.springframework.http.HttpStatus;
1219
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
1320
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
1421
import org.springframework.security.config.http.SessionCreationPolicy;
@@ -22,12 +29,14 @@
2229

2330
@Configuration
2431
@EnableWebSecurity
25-
@AllArgsConstructor
32+
@RequiredArgsConstructor
33+
2634
public class SecurityConfig {
2735

2836
private final JWTUtil jwtUtil;
2937
private final CookieService cookieService;
3038
private final CustomOAuth2UserService customOAuth2UserService;
39+
private final ObjectMapper objectMapper;
3140

3241
@Bean
3342
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
@@ -54,6 +63,26 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
5463
)
5564
.successHandler(new OAuth2SuccessHandler(jwtUtil, cookieService))
5665
)
66+
// 401 Unauthorized 에러를 공통 응답 포맷으로 변경
67+
.exceptionHandling(ex -> ex
68+
.authenticationEntryPoint((request, response, authException) -> {
69+
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
70+
response.setContentType("application/json;charset=UTF-8");
71+
72+
ExceptionDto errorDto = ExceptionDto.of(ErrorCode.AUTHENTICATION_REQUIRED);
73+
74+
// 공통 응답 DTO로 감싸기
75+
ResponseDto<Object> errorResponse = ResponseDto.builder()
76+
.httpStatus(HttpStatus.UNAUTHORIZED)
77+
.success(false)
78+
.data(null)
79+
.error(errorDto)
80+
.build();
81+
82+
// ObjectMapper를 사용하여 JSON으로 변환 후 응답
83+
response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
84+
})
85+
)
5786
.sessionManagement(session -> session
5887
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
5988
)
@@ -70,7 +99,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
7099
public CorsConfigurationSource corsConfigurationSource() {
71100
CorsConfiguration configuration = new CorsConfiguration();
72101
configuration.setAllowedOriginPatterns(List.of("*"));
73-
configuration.setAllowedMethods(List.of("GET", "POST", "OPTIONS"));
102+
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
74103
configuration.setAllowedHeaders(List.of("Authorization", "Content-Type", "cookie"));
75104
configuration.setExposedHeaders(List.of("Authorization", "verify"));
76105
configuration.setAllowCredentials(true);
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package com.blockcloud.controller;
2+
3+
import com.blockcloud.dto.RequestDto.BlockSaveRequestDto;
4+
import com.blockcloud.dto.ResponseDto.BlockGetResponseDto;
5+
import com.blockcloud.dto.ResponseDto.BlockSaveResponseDto;
6+
import com.blockcloud.dto.common.ResponseDto;
7+
import com.blockcloud.dto.oauth.CustomUserDetails;
8+
import com.blockcloud.service.BlockService;
9+
import io.swagger.v3.oas.annotations.Operation;
10+
import io.swagger.v3.oas.annotations.Parameter;
11+
import io.swagger.v3.oas.annotations.tags.Tag;
12+
import jakarta.validation.Valid;
13+
import lombok.RequiredArgsConstructor;
14+
import org.springframework.security.core.Authentication;
15+
import org.springframework.web.bind.annotation.*;
16+
17+
@Tag(name = "Block API", description = "블록 아키텍처 저장 및 조회 API")
18+
@RestController
19+
@RequiredArgsConstructor
20+
@RequestMapping("/api/block")
21+
public class BlockController {
22+
23+
private final BlockService blockService;
24+
25+
@Operation(
26+
summary = "블록 아키텍처 저장",
27+
description = "특정 프로젝트(`projectId`)에 대한 블록 인프라 데이터를 저장합니다. "
28+
)
29+
@PostMapping("/{projectId}")
30+
public ResponseDto<BlockSaveResponseDto> saveBlocks(
31+
@Parameter(description = "블록을 저장할 프로젝트 ID", required = true)
32+
@PathVariable Long projectId,
33+
@io.swagger.v3.oas.annotations.parameters.RequestBody(
34+
description = "저장할 블록 아키텍처 데이터",
35+
required = true
36+
)
37+
@Valid @RequestBody BlockSaveRequestDto requestDto,
38+
Authentication authentication) {
39+
40+
CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
41+
return ResponseDto.ok(
42+
blockService.saveBlocks(projectId, requestDto, userDetails.getUsername()));
43+
}
44+
45+
@Operation(
46+
summary = "블록 아키텍처 불러오기",
47+
description = "특정 프로젝트(`projectId`)의 블록 및 연결 정보를 불러옵니다. "
48+
)
49+
@GetMapping("/{projectId}")
50+
public ResponseDto<BlockGetResponseDto> getBlocks(
51+
@Parameter(description = "블록을 조회할 프로젝트 ID", required = true) @PathVariable Long projectId,
52+
Authentication authentication) {
53+
54+
CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
55+
return ResponseDto.ok(blockService.getBlocks(projectId, userDetails.getUsername()));
56+
}
57+
}
Lines changed: 29 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package com.blockcloud.controller;
22

3-
import com.blockcloud.dto.common.CommonResponse;
43
import com.blockcloud.dto.RequestDto.ProjectRequestDto;
54
import com.blockcloud.dto.ResponseDto.ProjectListResponseDto;
65
import com.blockcloud.dto.ResponseDto.ProjectResponseDto;
6+
import com.blockcloud.dto.common.ResponseDto;
77
import com.blockcloud.dto.oauth.CustomUserDetails;
88
import com.blockcloud.service.ProjectService;
99
import io.swagger.v3.oas.annotations.Operation;
@@ -14,8 +14,7 @@
1414
import io.swagger.v3.oas.annotations.responses.ApiResponses;
1515
import io.swagger.v3.oas.annotations.tags.Tag;
1616
import jakarta.validation.Valid;
17-
import org.springframework.http.HttpStatus;
18-
import org.springframework.http.ResponseEntity;
17+
import lombok.RequiredArgsConstructor;
1918
import org.springframework.security.core.Authentication;
2019
import org.springframework.web.bind.annotation.*;
2120

@@ -25,14 +24,11 @@
2524
@Tag(name = "Project API", description = "프로젝트 생성, 조회, 수정, 삭제 관련 API")
2625
@RestController
2726
@RequestMapping("/api/projects")
27+
@RequiredArgsConstructor
2828
public class ProjectController {
2929

3030
private final ProjectService projectService;
3131

32-
public ProjectController(ProjectService projectService) {
33-
this.projectService = projectService;
34-
}
35-
3632
/**
3733
* 새로운 프로젝트를 생성합니다.
3834
*
@@ -45,25 +41,17 @@ public ProjectController(ProjectService projectService) {
4541
description = "새로운 프로젝트를 생성합니다. 요청 바디에 `name`, `description`을 포함해야 하며, JWT 토큰이 필요합니다."
4642
)
4743
@ApiResponses(value = {
48-
@ApiResponse(responseCode = "200", description = "프로젝트 생성 성공",
49-
content = @Content(schema = @Schema(implementation = ProjectResponseDto.class))),
50-
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터"),
51-
@ApiResponse(responseCode = "401", description = "인증 실패 (JWT 필요)")
44+
@ApiResponse(responseCode = "201", description = "프로젝트 생성 성공",
45+
content = @Content(schema = @Schema(implementation = ResponseDto.class))),
46+
@ApiResponse(responseCode = "400", description = "INVALID_ARGUMENT (요청 데이터 유효성 검증 실패)"),
47+
@ApiResponse(responseCode = "401", description = "UNAUTHORIZED (인증 실패)")
5248
})
5349
@PostMapping
54-
public ResponseEntity<ProjectResponseDto> create(
55-
@io.swagger.v3.oas.annotations.parameters.RequestBody(
56-
description = "생성할 프로젝트 정보 (name: 프로젝트 이름, description: 설명)",
57-
required = true
58-
)
59-
@RequestBody @Valid ProjectRequestDto dto,
50+
public ResponseDto<ProjectResponseDto> create(
51+
@Valid @RequestBody ProjectRequestDto dto,
6052
Authentication authentication) {
61-
6253
CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
63-
String email = userDetails.getUsername();
64-
65-
ProjectResponseDto response = projectService.create(dto, email);
66-
return ResponseEntity.status(HttpStatus.CREATED).body(response);
54+
return ResponseDto.created(projectService.create(dto, userDetails.getUsername()));
6755
}
6856

6957
/**
@@ -79,17 +67,15 @@ public ResponseEntity<ProjectResponseDto> create(
7967
)
8068
@ApiResponses(value = {
8169
@ApiResponse(responseCode = "200", description = "조회 성공",
82-
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ProjectListResponseDto.class))),
83-
@ApiResponse(responseCode = "401", description = "인증 실패 (JWT 필요)")
70+
content = @Content(schema = @Schema(implementation = ResponseDto.class)))
8471
})
8572
@GetMapping
86-
public ResponseEntity<ProjectListResponseDto> getProjects(
73+
public ResponseDto<ProjectListResponseDto> getProjects(
8774
@Parameter(description = "마지막으로 조회한 프로젝트 ID (첫 호출 시 생략 가능)")
8875
@RequestParam(required = false) Long lastId,
8976
@Parameter(description = "가져올 데이터 개수 (기본값 8)")
9077
@RequestParam(defaultValue = "8") int size) {
91-
92-
return ResponseEntity.ok(projectService.findNext(lastId, size));
78+
return ResponseDto.ok(projectService.findNext(lastId, size));
9379
}
9480

9581
/**
@@ -105,26 +91,19 @@ public ResponseEntity<ProjectListResponseDto> getProjects(
10591
)
10692
@ApiResponses(value = {
10793
@ApiResponse(responseCode = "200", description = "수정 성공",
108-
content = @Content(schema = @Schema(implementation = ProjectResponseDto.class))),
109-
@ApiResponse(responseCode = "401", description = "인증 실패 (JWT 필요)"),
110-
@ApiResponse(responseCode = "403", description = "접근 권한 없음"),
111-
@ApiResponse(responseCode = "404", description = "프로젝트를 찾을 수 없음")
94+
content = @Content(schema = @Schema(implementation = ResponseDto.class))),
95+
@ApiResponse(responseCode = "400", description = "INVALID_ARGUMENT (요청 데이터 유효성 검증 실패)"),
96+
@ApiResponse(responseCode = "401", description = "UNAUTHORIZED (인증 실패)"),
97+
@ApiResponse(responseCode = "403", description = "FORBIDDEN (접근 권한 없음)"),
98+
@ApiResponse(responseCode = "404", description = "NOT_FOUND (프로젝트를 찾을 수 없음)")
11299
})
113100
@PutMapping("/{projectId}")
114-
public ResponseEntity<ProjectResponseDto> update(
115-
@Parameter(description = "수정할 프로젝트 ID", required = true)
116-
@PathVariable Long projectId,
117-
@io.swagger.v3.oas.annotations.parameters.RequestBody(
118-
description = "수정할 프로젝트 정보 (name, description 포함)",
119-
required = true
120-
)
121-
@RequestBody @Valid ProjectRequestDto dto,
101+
public ResponseDto<ProjectResponseDto> update(
102+
@Parameter(description = "수정할 프로젝트 ID", required = true) @PathVariable Long projectId,
103+
@Valid @RequestBody ProjectRequestDto dto,
122104
Authentication authentication) {
123-
124105
CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
125-
String email = userDetails.getUsername();
126-
127-
return ResponseEntity.ok(projectService.update(projectId, dto, email));
106+
return ResponseDto.ok(projectService.update(projectId, dto, userDetails.getUsername()));
128107
}
129108

130109
/**
@@ -138,22 +117,18 @@ public ResponseEntity<ProjectResponseDto> update(
138117
description = "프로젝트를 삭제합니다. JWT 인증 필요."
139118
)
140119
@ApiResponses(value = {
141-
@ApiResponse(responseCode = "200", description = "삭제 성공",
142-
content = @Content(schema = @Schema(implementation = CommonResponse.class))),
143-
@ApiResponse(responseCode = "401", description = "인증 실패 (JWT 필요)"),
144-
@ApiResponse(responseCode = "403", description = "접근 권한 없음"),
145-
@ApiResponse(responseCode = "404", description = "프로젝트를 찾을 수 없음")
120+
@ApiResponse(responseCode = "204", description = "삭제 성공"),
121+
@ApiResponse(responseCode = "401", description = "UNAUTHORIZED (인증 실패)"),
122+
@ApiResponse(responseCode = "403", description = "FORBIDDEN (접근 권한 없음)"),
123+
@ApiResponse(responseCode = "404", description = "NOT_FOUND (프로젝트를 찾을 수 없음)")
146124
})
147125
@DeleteMapping("/{projectId}")
148-
public ResponseEntity<CommonResponse> delete(
126+
public ResponseDto<Object> delete(
149127
@Parameter(description = "삭제할 프로젝트 ID", required = true)
150128
@PathVariable Long projectId,
151129
Authentication authentication) {
152-
153130
CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
154-
String email = userDetails.getUsername();
155-
156-
projectService.delete(projectId, email);
157-
return ResponseEntity.ok(new CommonResponse(true, "프로젝트를 성공적으로 삭제했습니다."));
131+
projectService.delete(projectId, userDetails.getUsername());
132+
return ResponseDto.noContent();
158133
}
159134
}

src/main/java/com/blockcloud/domain/project/Project.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ public class Project extends BaseTimeEntity {
2525
@Column(columnDefinition = "TEXT")
2626
private String description;
2727

28+
@Column(name = "block_info", columnDefinition = "LONGTEXT")
29+
private String blockInfo;
30+
2831
@OneToMany(mappedBy = "project", cascade = CascadeType.ALL, orphanRemoval = true)
2932
@JsonIgnore
3033
private List<ProjectUser> members = new ArrayList<>();
@@ -33,4 +36,8 @@ public void updateInfo(String name, String description) {
3336
this.name = name;
3437
this.description = description;
3538
}
39+
40+
public void updateArchitecture(String blockInfo) {
41+
this.blockInfo = blockInfo;
42+
}
3643
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.blockcloud.dto.RequestDto;
2+
3+
import jakarta.validation.constraints.NotNull;
4+
import lombok.AllArgsConstructor;
5+
import lombok.Builder;
6+
import lombok.Getter;
7+
import lombok.NoArgsConstructor;
8+
9+
/**
10+
* 블록 저장 요청 DTO
11+
* 블록 정보를 포함하여 저장 요청을 처리하기 위한 DTO 클래스입니다.
12+
*/
13+
@Getter
14+
@NoArgsConstructor
15+
@AllArgsConstructor
16+
@Builder
17+
public class BlockSaveRequestDto {
18+
19+
@NotNull(message = "블록 정보는 필수입니다.")
20+
private Object blocks;
21+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.blockcloud.dto.ResponseDto;
2+
3+
import java.time.LocalDateTime;
4+
import lombok.Builder;
5+
import lombok.Getter;
6+
7+
/**
8+
* 블록 조회 성공 시 'data' 필드에 담길 응답 DTO
9+
*/
10+
@Getter
11+
@Builder
12+
public class BlockGetResponseDto {
13+
14+
private LocalDateTime createdAt;
15+
private LocalDateTime updatedAt;
16+
private Object blocks;
17+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.blockcloud.dto.ResponseDto;
2+
3+
import java.time.LocalDateTime;
4+
import lombok.Builder;
5+
import lombok.Getter;
6+
7+
/**
8+
* 블록 저장 응답 DTO
9+
*/
10+
@Getter
11+
@Builder
12+
public class BlockSaveResponseDto {
13+
14+
private Long projectId;
15+
private String architectureName;
16+
private LocalDateTime updatedAt;
17+
}

0 commit comments

Comments
 (0)