-
Notifications
You must be signed in to change notification settings - Fork 1
✨ Feat: Block CRUD development #8
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
Merged
Merged
Changes from 17 commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
2d73324
feat: Add block and connection info to Project entity
parksomii 7d6f29f
feat: Add BlockSaveRequestDto for block save requests
parksomii c2f08d3
feat: Add BlockSaveResponseDto for block save responses
parksomii be0d665
feat: Add BlockService for block management
parksomii 9d6991b
feat: Add BlockController for block architecture API
parksomii bdabfbc
feat: Return JSON response for 401 Unauthorized errors
parksomii 608409c
feat: Add global exception handler for API errors
parksomii 264d3a5
Merge branch 'develop' of https://github.com/BlockCloud-dev/blockclou…
parksomii f455a45
Merge branch 'develop' of https://github.com/BlockCloud-dev/blockclou…
parksomii 37a3197
feat: Add NOT_FOUND_PROJECT error code
parksomii fcb7063
refactor: Remove GlobalExceptionHandler
parksomii 7e037ac
feat: Add validation to BlockSaveRequestDto
parksomii dd64be1
feat: Add BlockGetResponseDto for block retrieval responses
parksomii b8f8681
refactor: Flatten BlockSaveResponseDto structure
parksomii 7383c15
refactor: Enhance BlockService with member validation and DTOs
parksomii 466bb62
refactor: Apply ResponseDto and authentication to BlockController
parksomii f0b5286
refactor: Standardize authentication error response
parksomii 4112e10
feat: Add blockInfo field and updateArchitecture method to Project
parksomii 4258910
refactor: Change projects field type in ProjectListResponseDto
parksomii 09f4cd7
refactor: Flatten ProjectResponseDto structure
parksomii f1a762c
refactor: Improve exception handling and DTO mapping in ProjectService
parksomii 6f8cdae
refactor: Standardize API responses and documentation in ProjectContr…
parksomii cf04539
fix: test.yml
parksomii c29680b
Merge branch 'feature/project-api' of https://github.com/BlockCloud-d…
parksomii ddaad23
refactor: Remove detailed response annotations
parksomii File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
141 changes: 141 additions & 0 deletions
141
src/main/java/com/blockcloud/controller/BlockController.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,141 @@ | ||
| package com.blockcloud.controller; | ||
|
|
||
| import com.blockcloud.dto.RequestDto.BlockSaveRequestDto; | ||
| import com.blockcloud.dto.ResponseDto.BlockGetResponseDto; | ||
| import com.blockcloud.dto.ResponseDto.BlockSaveResponseDto; | ||
| import com.blockcloud.dto.common.ResponseDto; | ||
| import com.blockcloud.dto.oauth.CustomUserDetails; | ||
| import com.blockcloud.service.BlockService; | ||
| import io.swagger.v3.oas.annotations.Operation; | ||
| import io.swagger.v3.oas.annotations.Parameter; | ||
| import io.swagger.v3.oas.annotations.media.Content; | ||
| import io.swagger.v3.oas.annotations.media.ExampleObject; | ||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import io.swagger.v3.oas.annotations.responses.ApiResponse; | ||
| import io.swagger.v3.oas.annotations.responses.ApiResponses; | ||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||
| import jakarta.validation.Valid; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.security.core.Authentication; | ||
| import org.springframework.web.bind.annotation.*; | ||
|
|
||
| @Tag(name = "Block API", description = "블록 아키텍처 저장 및 조회 API") | ||
| @RestController | ||
| @RequiredArgsConstructor | ||
| @RequestMapping("/api/block") | ||
| public class BlockController { | ||
|
|
||
| private final BlockService blockService; | ||
|
|
||
| /** | ||
| * 블록 아키텍처 저장 특정 프로젝트(projectId)에 대한 블록 인프라 데이터를 저장합니다. | ||
| */ | ||
| @Operation( | ||
| summary = "블록 아키텍처 저장", | ||
| description = "특정 프로젝트(`projectId`)에 대한 블록 인프라 데이터를 저장합니다. " | ||
| ) | ||
| @ApiResponses(value = { | ||
| @ApiResponse(responseCode = "200", description = "저장 성공", | ||
| content = @Content(schema = @Schema(implementation = ResponseDto.class), | ||
| examples = @ExampleObject(value = """ | ||
| { | ||
| "success": true, | ||
| "message": "아키텍처가 성공적으로 저장되었습니다.", | ||
| "data": { | ||
| "projectId": 1, | ||
| "architectureName": "aws-architecture-2025-07-22", | ||
| "updatedAt": "2025-07-22T14:30:00" | ||
| } | ||
| } | ||
| """))), | ||
| @ApiResponse(responseCode = "400", description = "INVALID_ARGUMENT (요청 데이터 유효성 검증 실패)"), | ||
| @ApiResponse(responseCode = "401", description = "UNAUTHORIZED (인증 실패)"), | ||
| @ApiResponse(responseCode = "403", description = "ACCESS_DENIED (접근 권한 없음)"), | ||
| @ApiResponse(responseCode = "404", description = "NOT_FOUND_PROJECT (프로젝트를 찾을 수 없음)") | ||
| }) | ||
| @PostMapping("/{projectId}") | ||
| public ResponseDto<BlockSaveResponseDto> saveBlocks( | ||
| @Parameter(description = "블록을 저장할 프로젝트 ID", required = true) | ||
| @PathVariable Long projectId, | ||
| @io.swagger.v3.oas.annotations.parameters.RequestBody( | ||
| description = "저장할 블록 아키텍처 데이터", | ||
| required = true, | ||
| content = @Content(examples = @ExampleObject(value = """ | ||
| { | ||
| "createdAt": "2025-07-22T14:20:00", | ||
| "updatedAt": "2025-07-22T14:25:00", | ||
| "blocks": [ | ||
| { | ||
| "id": "web-server", | ||
| "type": "aws_instance", | ||
| "position": { | ||
| "x": 100, | ||
| "y": 200 | ||
| }, | ||
| "properties": { | ||
| "ami": "ami-12345678", | ||
| "instance_type": "t2.micro", | ||
| "tags": { | ||
| "Name": "MyServer" | ||
| } | ||
| }, | ||
| "connections": [ | ||
| "subnet-1" | ||
| ] | ||
| } | ||
| ] | ||
| } | ||
| """)) | ||
| ) | ||
| @Valid @RequestBody BlockSaveRequestDto requestDto, | ||
| Authentication authentication) { | ||
|
|
||
| CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); | ||
| return ResponseDto.ok( | ||
| blockService.saveBlocks(projectId, requestDto, userDetails.getUsername())); | ||
| } | ||
|
|
||
| /** | ||
| * 블록 아키텍처 불러오기 특정 프로젝트(projectId)의 블록 및 연결 정보를 불러옵니다. | ||
| */ | ||
| @Operation( | ||
| summary = "블록 아키텍처 불러오기", | ||
| description = "특정 프로젝트(`projectId`)의 블록 및 연결 정보를 불러옵니다. " | ||
| ) | ||
| @ApiResponses(value = { | ||
| @ApiResponse(responseCode = "200", description = "조회 성공", | ||
| content = @Content(examples = @ExampleObject(value = """ | ||
| { | ||
| "createdAt": "2025-07-22T14:20:00", | ||
| "updatedAt": "2025-07-22T14:25:00", | ||
| "blocks": [ | ||
| { | ||
| "id": "web-server", | ||
| "type": "aws_instance", | ||
| "position": { "x": 100, "y": 200 }, | ||
| "properties": { ... }, | ||
| "connections": ["subnet-1"] | ||
| }, | ||
| { | ||
| "id": "subnet-1", | ||
| "type": "aws_subnet", | ||
| "position": { "x": 200, "y": 300 }, | ||
| "properties": { ... }, | ||
| "connections": [] | ||
| } | ||
| ] | ||
| } | ||
| """))), | ||
| @ApiResponse(responseCode = "401", description = "UNAUTHORIZED (인증 실패)"), | ||
| @ApiResponse(responseCode = "403", description = "ACCESS_DENIED (접근 권한 없음)"), | ||
| @ApiResponse(responseCode = "404", description = "NOT_FOUND_PROJECT (프로젝트를 찾을 수 없음)") | ||
| }) | ||
| @GetMapping("/{projectId}") | ||
| public ResponseDto<BlockGetResponseDto> getBlocks( | ||
| @Parameter(description = "블록을 조회할 프로젝트 ID", required = true) @PathVariable Long projectId, | ||
| Authentication authentication) { | ||
|
|
||
| CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); | ||
| return ResponseDto.ok(blockService.getBlocks(projectId, userDetails.getUsername())); | ||
| } | ||
| } | ||
21 changes: 21 additions & 0 deletions
21
src/main/java/com/blockcloud/dto/RequestDto/BlockSaveRequestDto.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| package com.blockcloud.dto.RequestDto; | ||
|
|
||
| import jakarta.validation.constraints.NotNull; | ||
| import lombok.AllArgsConstructor; | ||
| import lombok.Builder; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| /** | ||
| * 블록 저장 요청 DTO | ||
| * 블록 정보를 포함하여 저장 요청을 처리하기 위한 DTO 클래스입니다. | ||
| */ | ||
| @Getter | ||
| @NoArgsConstructor | ||
| @AllArgsConstructor | ||
| @Builder | ||
| public class BlockSaveRequestDto { | ||
|
|
||
| @NotNull(message = "블록 정보는 필수입니다.") | ||
| private Object blocks; | ||
| } |
17 changes: 17 additions & 0 deletions
17
src/main/java/com/blockcloud/dto/ResponseDto/BlockGetResponseDto.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package com.blockcloud.dto.ResponseDto; | ||
|
|
||
| import java.time.LocalDateTime; | ||
| import lombok.Builder; | ||
| import lombok.Getter; | ||
|
|
||
| /** | ||
| * 블록 조회 성공 시 'data' 필드에 담길 응답 DTO | ||
| */ | ||
| @Getter | ||
| @Builder | ||
| public class BlockGetResponseDto { | ||
|
|
||
| private LocalDateTime createdAt; | ||
| private LocalDateTime updatedAt; | ||
| private Object blocks; | ||
| } |
17 changes: 17 additions & 0 deletions
17
src/main/java/com/blockcloud/dto/ResponseDto/BlockSaveResponseDto.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package com.blockcloud.dto.ResponseDto; | ||
|
|
||
| import java.time.LocalDateTime; | ||
| import lombok.Builder; | ||
| import lombok.Getter; | ||
|
|
||
| /** | ||
| * 블록 저장 응답 DTO | ||
| */ | ||
| @Getter | ||
| @Builder | ||
| public class BlockSaveResponseDto { | ||
|
|
||
| private Long projectId; | ||
| private String architectureName; | ||
| private LocalDateTime updatedAt; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| package com.blockcloud.service; | ||
|
|
||
| import com.blockcloud.domain.project.Project; | ||
| import com.blockcloud.domain.project.ProjectRepository; | ||
| import com.blockcloud.dto.RequestDto.BlockSaveRequestDto; | ||
| import com.blockcloud.dto.ResponseDto.BlockGetResponseDto; | ||
| import com.blockcloud.dto.ResponseDto.BlockSaveResponseDto; | ||
| import com.blockcloud.exception.CommonException; | ||
| import com.blockcloud.exception.error.ErrorCode; | ||
| import com.nimbusds.jose.shaded.gson.Gson; | ||
| import java.time.LocalDateTime; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| /** | ||
| * 블록 아키텍처 관리를 위한 서비스 클래스 | ||
| */ | ||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class BlockService { | ||
|
|
||
| private final ProjectRepository projectRepository; | ||
|
|
||
| /** | ||
| * 프로젝트의 블록 아키텍처 정보를 저장합니다. | ||
| * | ||
| * @param projectId 저장할 프로젝트의 ID | ||
| * @param dto 블록 정보가 담긴 요청 DTO | ||
| * @return 저장된 블록 정보를 담은 DTO (데이터 부분) | ||
| * @throws CommonException 해당 프로젝트를 찾을 수 없는 경우 | ||
| */ | ||
| @Transactional | ||
| public BlockSaveResponseDto saveBlocks(Long projectId, BlockSaveRequestDto dto, String email) { | ||
| Project project = projectRepository.findById(projectId) | ||
| .orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_PROJECT)); | ||
|
|
||
| validateProjectMember(project, email); | ||
|
|
||
| String blockInfoJson = new Gson().toJson(dto.getBlocks()); | ||
| project.updateArchitecture(blockInfoJson); | ||
| projectRepository.save(project); | ||
|
|
||
| return BlockSaveResponseDto.builder() | ||
| .projectId(project.getId()) | ||
| .architectureName(project.getName() + "-" + LocalDateTime.now()) | ||
| .updatedAt(project.getUpdatedAt()) | ||
| .build(); | ||
| } | ||
|
|
||
| /** | ||
| * 프로젝트의 블록 아키텍처 정보를 조회합니다. | ||
| * | ||
| * @param projectId 조회할 프로젝트의 ID | ||
| * @param email 요청한 사용자의 이메일 | ||
| * @return 블록 아키텍처 정보가 담긴 응답 DTO (데이터 부분) | ||
| * @throws CommonException 해당 프로젝트를 찾을 수 없는 경우 또는 접근 권한이 없는 경우 | ||
| */ | ||
| @Transactional(readOnly = true) | ||
| public BlockGetResponseDto getBlocks(Long projectId, String email) { | ||
| Project project = projectRepository.findById(projectId) | ||
| .orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_PROJECT)); | ||
|
|
||
| validateProjectMember(project, email); | ||
|
|
||
| return BlockGetResponseDto.builder() | ||
| .createdAt(project.getCreatedAt()) | ||
| .updatedAt(project.getUpdatedAt()) | ||
| .blocks(new Gson().fromJson(project.getBlockInfo(), Object.class)) | ||
| .build(); | ||
| } | ||
|
|
||
| /** | ||
| * 사용자가 해당 프로젝트의 멤버인지 검증하는 메서드 | ||
| * | ||
| * @param project 검증할 프로젝트 | ||
| * @param email 요청한 사용자의 이메일 | ||
| * @throws CommonException 접근 권한이 없는 경우 | ||
| */ | ||
| private void validateProjectMember(Project project, String email) { | ||
| boolean isMember = project.getMembers().stream() | ||
| .anyMatch(member -> member.getUser().getEmail().equals(email)); | ||
| if (!isMember) { | ||
| throw new CommonException(ErrorCode.ACCESS_DENIED); | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.