Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,6 @@ public ApiResponse<Long> createComment(
summary = "댓글 목록 조회 API",
description = "특정 리소스(이슈, 목표, 외부 이슈)의 댓글 목록을 조회합니다. 최신순으로 정렬되어 반환됩니다."
)
@ApiResponses(value = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "200",
description = "댓글 목록 조회 성공",
content = @Content(schema = @Schema(implementation = CommentListResponseDTO.class))
),
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "404",
description = "대상 리소스를 찾을 수 없음",
content = @Content(schema = @Schema(implementation = ApiResponse.class))
)
})
@GetMapping
public ApiResponse<CommentResponseDTO.CommentListDTO> getAllComments(
@Parameter(description = "댓글을 조회할 대상 리소스의 ID", required = true, example = "1")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ public ApiResponse<List<ExternalResponseDTO.SimpleExternalDTO>> restoreGoals(
@PatchMapping("/{externalId}")
public ApiResponse<ExternalResponseDTO.UpdateResponseDTO> modifyExternal(
@PathVariable Long externalId,
@PathVariable Long teamId,
@Valid @RequestBody ExternalRequestDTO.ExternalUpdateRequestDTO requestDTO) {

return ApiResponse.onSuccess(externalService.updateExternal(externalId, requestDTO));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ ApiResponse<ExternalResponseDTO.CreateResponseDTO> createExternal(
})
ApiResponse<ExternalResponseDTO.UpdateResponseDTO> modifyExternal(
@Parameter(description = "외부이슈 ID", required = true) @PathVariable Long externalId,
@Parameter(description = "팀 ID")@PathVariable Long teamId,
@Valid @RequestBody ExternalRequestDTO.ExternalUpdateRequestDTO requestDTO);

@Operation(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
import com.example.Veco.domain.github.dto.webhook.GitHubWebhookPayload;
import com.example.Veco.domain.external.entity.External;
import com.example.Veco.domain.goal.entity.Goal;
import com.example.Veco.domain.goal.exception.GoalException;
import com.example.Veco.domain.goal.exception.code.GoalErrorCode;
import com.example.Veco.domain.mapping.Assignment;
import com.example.Veco.domain.member.entity.Member;
import com.example.Veco.domain.team.entity.Team;
Expand Down Expand Up @@ -141,14 +139,12 @@ public static ExternalResponseDTO.ExternalInfoDTO toExternalInfoDTO(External ext
return ExternalResponseDTO.ExternalInfoDTO.builder()
.id(external.getId())
.content(external.getDescription())
.startDate(external.getStartDate())
.endDate(external.getEndDate())
.content(external.getDescription())
.name(external.getName())
.priority(external.getPriority())
.goalId(external.getGoal()!= null ? external.getGoal().getId() : null)
.goalTitle(external.getGoal()!= null ? external.getGoal().getTitle() : null)
.deadlines(deadline)
.deadline(deadline)
.title(external.getTitle())
.state(external.getState())
.comments(commentResponseDTO)
Expand Down Expand Up @@ -188,7 +184,7 @@ public static ExternalResponseDTO.ExternalDTO toExternalDTO(External external, L
.priority(external.getPriority())
.goalId(external.getGoal() != null ? external.getGoal().getId() : null)
.goalTitle(external.getGoal() != null ? external.getGoal().getTitle() : null)
.deadlines(deadline)
.deadline(deadline)
.title(external.getTitle())
.state(external.getState())
.managers(assigneeResponseDTO)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,6 @@ public static class ExternalInfoDTO {
@Schema(description = "이슈 상태", example = "TODO")
private State state;

@Schema(description = "시작일", example = "2024-01-01")
private LocalDate startDate;

@Schema(description = "마감일", example = "2024-01-31")
private LocalDate endDate;

@Schema(description = "목표 ID", example = "1")
private Long goalId;

Expand All @@ -74,7 +68,7 @@ public static class ExternalInfoDTO {

private AssigneeResponseDTO managers;

private DeadlineResponseDTO deadlines;
private DeadlineResponseDTO deadline;

private ExternalCommentResponseDTO comments;
}
Expand Down Expand Up @@ -108,12 +102,6 @@ public static class ExternalDTO {
@Schema(description = "이슈 상태", example = "TODO")
private State state;

@Schema(description = "시작일", example = "2024-01-01")
private LocalDate startDate;

@Schema(description = "마감일", example = "2024-01-31")
private LocalDate endDate;

@Schema(description = "목표 ID", example = "1")
private Long goalId;

Expand All @@ -128,7 +116,7 @@ public static class ExternalDTO {

private AssigneeResponseDTO managers;

private DeadlineResponseDTO deadlines;
private DeadlineResponseDTO deadline;
}

@Getter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public class External extends BaseEntity {
@Column(name = "title", nullable = false)
private String title;

@Column(name = "description")
@Column(name = "description", columnDefinition = "TEXT")
@Builder.Default
private String description = "";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -572,10 +572,10 @@ private List<ExternalGroupedResponseDTO.ExternalItemDTO> convertToExternalItemDT
.title(dto.getTitle())
.state(dto.getState())
.priority(dto.getPriority() != null ? dto.getPriority().name() : "없음")
.deadline(dto.getDeadlines() != null ?
.deadline(dto.getDeadline() != null ?
ExternalGroupedResponseDTO.DeadlineDTO.builder()
.start(dto.getDeadlines().getStart() != null ? dto.getDeadlines().getStart().toString() : null)
.end(dto.getDeadlines().getEnd() != null ? dto.getDeadlines().getEnd().toString() : null)
.start(dto.getDeadline().getStart() != null ? dto.getDeadline().getStart().toString() : null)
.end(dto.getDeadline().getEnd() != null ? dto.getDeadline().getEnd().toString() : null)
.build() : null)
.managers(dto.getManagers() != null ?
ExternalGroupedResponseDTO.ManagersDTO.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,15 @@ public ExternalResponseDTO.CreateResponseDTO createExternal(Long teamId,

List<Member> members = memberRepository.findAllByIdIn(request.getManagersId());

if(request.getManagersId().size() != members.size()){
throw new MemberHandler(MemberErrorStatus._MEMBER_NOT_FOUND);
}
if (request.getManagersId() != null) {
if(request.getManagersId().size() != members.size()){
throw new MemberHandler(MemberErrorStatus._MEMBER_NOT_FOUND);
}

List<MemberTeam> memberTeamList = memberTeamRepository.findAllByMemberIdInAndTeamId(request.getManagersId(), teamId);
if (memberTeamList.size() != request.getManagersId().size()) {
throw new MemberHandler(MemberErrorStatus._FORBIDDEN);
List<MemberTeam> memberTeamList = memberTeamRepository.findAllByMemberIdInAndTeamId(request.getManagersId(), teamId);
if (memberTeamList.size() != request.getManagersId().size()) {
throw new MemberHandler(MemberErrorStatus._FORBIDDEN);
}
}

Goal goal = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.Parameter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
Expand All @@ -18,11 +19,12 @@

@RestController
@RequiredArgsConstructor
@Slf4j
public class GitHubRestController implements GitHubSwaggerDocs {

private final GitHubService gitHubService;
private final GitHubRepositoryService gitHubRepositoryService;
private static final String FRONTEND_URL = "http://localhost:5173";
private static final String FRONTEND_URL = "https://web.vecoservice.shop";

@Hidden
@GetMapping("/github/installation/callback")
Expand All @@ -44,13 +46,27 @@ public ResponseEntity<Void> callbackInstallation(
}

@GetMapping("/api/teams/{teamId}/github/repositories")
public Mono<ApiResponse<List<GitHubApiResponseDTO.GitHubRepositoryDTO>>> getRepositories(@PathVariable("teamId") Long teamId) {
public ApiResponse<List<GitHubApiResponseDTO.GitHubRepositoryDTO>> getRepositories(@PathVariable("teamId") Long teamId) {

// owner, repo 정보 추출해서 클라이언트에 제공
return gitHubService.getInstallationIdAsync(teamId) // 비동기 DB 조회
.flatMap(gitHubRepositoryService::getInstallationRepositories)
.map(ApiResponse::onSuccess)
.onErrorReturn(ApiResponse.onFailure("REPO_FETCH_FAILED", "레포지토리 조회 실패", null));
try {
log.info("레포지토리 조회 시작: teamId={}", teamId);

// 1. Installation ID 조회 (동기)
Long installationId = gitHubService.getInstallationIdSync(teamId);
log.info("Installation ID 조회 완료: {}", installationId);

// 2. 레포지토리 목록 조회 (동기)
List<GitHubApiResponseDTO.GitHubRepositoryDTO> repositories =
gitHubRepositoryService.getInstallationRepositoriesSync(installationId);

log.info("레포지토리 조회 완료: {} 개", repositories.size());

return ApiResponse.onSuccess(repositories);

} catch (Exception e) {
log.error("레포지토리 조회 실패: teamId={}", teamId, e);
return ApiResponse.onFailure("REPO_FETCH_FAILED", "레포지토리 조회 실패: " + e.getMessage(), null);
}
}

@GetMapping("/api/github/connect")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public interface GitHubSwaggerDocs {
summary = "GitHub App이 접근 가능한 레포지토리 목록 조회",
description = "해당 팀이 설치한 GitHub App 이 접근 가능한 레포지토리 목록을 조회합니다."
)
Mono<ApiResponse<List<GitHubApiResponseDTO.GitHubRepositoryDTO>>> getRepositories(@PathVariable("teamId") Long teamId);
ApiResponse<List<GitHubApiResponseDTO.GitHubRepositoryDTO>> getRepositories(@PathVariable("teamId") Long teamId);

@Operation(
summary = "GitHub 연동을 위한 App 설치 페이지 URL 조회",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import com.example.Veco.domain.github.dto.response.GitHubApiResponseDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatusCode;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.util.List;

@Service
Expand All @@ -24,6 +26,62 @@ public class GitHubRepositoryService {
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(1024 * 1024)) // 1MB
.build();

public List<GitHubApiResponseDTO.GitHubRepositoryDTO> getInstallationRepositoriesSync(Long installationId) {
try {
log.info("레포지토리 조회 시작: installationId={}", installationId);

// 1. Installation Token 조회 (동기)
String token = tokenService.getInstallationTokenSync(installationId);
log.info("Installation Token 조회 완료: installationId={}", installationId);

// 2. 레포지토리 목록 조회 (동기)
return fetchRepositoriesSync(token, installationId);

} catch (Exception e) {
log.error("레포지토리 조회 실패: installationId={}", installationId, e);
throw new RuntimeException("레포지토리 조회 실패", e);
}
}

private List<GitHubApiResponseDTO.GitHubRepositoryDTO> fetchRepositoriesSync(String token, Long installationId) {
try {
log.info("GitHub API 호출 시작: installationId={}", installationId);

GitHubApiResponseDTO.GitHubRepositoryResponseDTO response = webClient.get()
.uri("/installation/repositories")
.header("Authorization", "Bearer " + token)
.retrieve()
.onStatus(HttpStatusCode::isError, clientResponse -> {
log.error("GitHub API 에러 응답: status={}", clientResponse.statusCode());
return clientResponse.bodyToMono(String.class)
.flatMap(errorBody -> {
log.error("GitHub API 에러 바디: {}", errorBody);
return Mono.error(new RuntimeException(
"GitHub API 에러: " + clientResponse.statusCode() + ", body: " + errorBody));
});
})
.bodyToMono(GitHubApiResponseDTO.GitHubRepositoryResponseDTO.class)
.timeout(Duration.ofSeconds(10))
.doOnSuccess(result -> log.info("GitHub API 응답 성공: installationId={}, totalCount={}",
installationId, result != null ? result.getTotalCount() : 0))
.doOnError(error -> log.error("GitHub API 호출 실패: installationId={}", installationId, error))
.block(); // 동기 처리

if (response != null) {
log.info("설치된 레포지토리 조회 완료: installationId={}, count={}",
installationId, response.getTotalCount());
log.info("repository : {}", response.getRepositories());
return response.getRepositories();
} else {
throw new RuntimeException("GitHub API 응답이 null입니다.");
}

} catch (Exception e) {
log.error("fetchRepositoriesSync 실패: installationId={}", installationId, e);
throw new RuntimeException("GitHub 레포지토리 조회 실패", e);
}
}

public Mono<List<GitHubApiResponseDTO.GitHubRepositoryDTO>> getInstallationRepositories(Long installationId) {
return tokenService.getInstallationToken(installationId)
.flatMap(token -> fetchRepositories(token, installationId));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,8 @@ public Mono<Long> getInstallationIdAsync(Long teamId) {
.subscribeOn(Schedulers.boundedElastic()); // DB 조회를 별도 스레드에서 실행
}

public Long getInstallationIdSync(Long teamId) {
return getInstallationId(teamId);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public class Goal extends BaseEntity {
@Column(name = "title", nullable = false)
private String title;

@Column(name = "content")
@Column(name = "content", columnDefinition = "TEXT")
@Builder.Default
private String content = "";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public class Issue extends BaseEntity {
@Column(name = "title", nullable = false)
private String title;

@Column(name = "content")
@Column(name = "content", columnDefinition = "TEXT")
@Builder.Default
private String content = "";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,9 @@ public List<IssueResponseDTO.SimpleIssue> findIssuesByTeamId(Predicate query, in
List<IssueResponseDTO.SimpleIssue> result = queryFactory
.from(issue)
.where(query)
.leftJoin(assignee).on(assignee.type.eq(Category.ISSUE).and(assignee.targetId.eq(issue.id)))
.leftJoin(member).on(member.id.eq(assignee.memberTeam.member.id))
.leftJoin(goal).on(goal.id.eq(issue.goal.id))
.orderBy(issue.id.desc())
.groupBy(issue.id, assignee.id)
.groupBy(issue.id)
.limit(size)
.transform(GroupBy.groupBy(issue.id).list(
Projections.constructor(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,11 @@
import com.example.Veco.global.aws.util.S3Util;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;
import java.util.Objects;

@Service
@RequiredArgsConstructor
@Slf4j
Expand Down Expand Up @@ -54,7 +48,7 @@ public Member updateProfileImage(MultipartFile file, Member member) {
}

// S3 업로드 및 URL 생성
String uploadedPath = s3Util.uploadFile(file, null);
String uploadedPath = s3Util.uploadFile(file, "profile/");
String imageUrl = s3Util.getImageUrl(uploadedPath);

// DB 업데이트
Expand Down