Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
dd5617f
[refactor] 익명 사용자의 인가 처리를 httpSecurity로 위임
JayongLee Sep 11, 2025
378dfc1
[refactor] 익명 사용자의 @UserId의 값을 null로 처리 및 @UserId Optional 사용 가능하도록 수정
JayongLee Sep 11, 2025
c9348c3
[refactor] 블로그 상세조회시 글 수정 가능 여부 반환
JayongLee Sep 11, 2025
88c629b
[chore] 개발용으로 사용한 로깅 삭제
JayongLee Sep 11, 2025
95749bc
[feat] AccessDeniedException 관련 에러 메시지 및 핸들러 추가
kimhm0828 Sep 11, 2025
164c9d6
[feat] 블로그 글 수정 기능 구현
kimhm0828 Sep 11, 2025
cfba2d3
[refactor] 운영용으로 코드 수정
kimhm0828 Sep 11, 2025
6c9fe88
[feat] 블로그 글 삭제 기능 구현
kimhm0828 Sep 11, 2025
ceacb97
[refactor] 사용되지 않는 import문 제거
kimhm0828 Sep 11, 2025
ec186ef
[refactor] 보호된 엔드포인트에 대한 미인증 사용자 예외처리 누락 수정
JayongLee Sep 11, 2025
d9fccc2
[refactor] PR Review 반영
JayongLee Sep 11, 2025
afa9d8f
[refactor] Blog/Project 기본 정보 응답 DTO 필드에 생성일/수정일 추가
JayongLee Sep 11, 2025
1d50295
[refactor] PR Review : 화이트리스트 요청에 대한 유효하지 않은 토큰을 소유한 사용자가 인증 객체를 생성하지…
JayongLee Sep 11, 2025
5327f8e
블로그 상세조회 응답 DTO에 수정 가능 여부 반환 & @UserId 관련 수정 - #67
JayongLee Sep 11, 2025
a702b2f
Blog/Project 기본 정보 응답 DTO 필드에 생성일/수정일 추가 - #70
JayongLee Sep 11, 2025
ad597a4
[refactor] PR 리뷰 반영
kimhm0828 Sep 11, 2025
b50a7ec
[refactor] merge 및 conflict 해결
kimhm0828 Sep 11, 2025
230769b
[refactor] PR 리뷰 반영 - db 로직 개선
kimhm0828 Sep 11, 2025
bfe18de
[refactor] Progress Enum 값 변경
JayongLee Sep 11, 2025
48782a3
[refactor] PR 리뷰 반영 - db 쿼리 개선
kimhm0828 Sep 11, 2025
d60f92c
Progress Enum 값 변경 - #72
JayongLee Sep 11, 2025
de7283b
[refactor] Category Enum 값 변경
JayongLee Sep 11, 2025
84f97bd
[refactor] DTO 통합 및 요청 필드에 progresses 추가
kimhm0828 Sep 11, 2025
ed5b939
Category Enum 값 변경 - #73
JayongLee Sep 11, 2025
eb55203
[refactor] 중복으로 인한 BlogUpdateReq.java 제거
JayongLee Sep 11, 2025
25ee0a4
[refactor] 중복 로직 메서드로 추출 및 스타일 개선
JayongLee Sep 11, 2025
24c70e9
[refactor] PR Review : Blog Entity 태그 필드 업데이트 로직 개선
JayongLee Sep 11, 2025
35c49dd
블로그 글 수정 및 삭제 api 구현 - #69
JayongLee Sep 11, 2025
e210380
[refactor] 허용 / 거부 URI List로 추출 및 List를 통해 검증로직을 수행하도록 수정
JayongLee Sep 19, 2025
412c3b1
허용 / 거부 URI List로 추출 및 List를 통해 검증로직을 수행하도록 수정 - #76
JayongLee Sep 19, 2025
5c5b80e
[chore] 블로그 업데이트 엔드포인트 오류 수정
JayongLee Sep 25, 2025
74b8a0d
블로그 업데이트 엔드포인트 오류 수정 - #77
JayongLee Sep 25, 2025
208797b
[feat] Google API를 이용한 유저 프로필 이미지 조회 및 저장
JayongLee Sep 26, 2025
40570a5
[feat] 유저 프로필 이미지 추가에 따른 DTO 필드 추가
JayongLee Sep 26, 2025
f377e2d
[chroe] API 설명 주석 추가
JayongLee Sep 26, 2025
3cc8800
[refactor] PR Review 반영
JayongLee Sep 26, 2025
7780cb3
구글 API를 이용한 유저 프로필 이미지 조회 및 저장 - #79
JayongLee Sep 26, 2025
ae579e0
[feat] 프로젝트 삭제 & 프로젝트-멤버 추가, 삭제 API 구현
JayongLee Sep 28, 2025
5db1867
[chore] 블로그 업데이트 엔드포인트 오류 수정
JayongLee Sep 25, 2025
fa4db7d
[feat] Google API를 이용한 유저 프로필 이미지 조회 및 저장
JayongLee Sep 26, 2025
a905b50
[feat] 유저 프로필 이미지 추가에 따른 DTO 필드 추가
JayongLee Sep 26, 2025
315d3e7
[chroe] API 설명 주석 추가
JayongLee Sep 26, 2025
c1eb4af
[refactor] PR Review 반영
JayongLee Sep 26, 2025
51fc194
Merge branch 'dev' into feat/#63
JayongLee Sep 28, 2025
a8025ad
[feat] 프로젝트 삭제 & 프로젝트-멤버 추가,삭제 API 오륲 수정
JayongLee Sep 28, 2025
b592f3d
[feat] PR Review - 불필요한 쿼리 제거 및 로직 리팩토링
JayongLee Sep 29, 2025
e093c5f
[feat] PR Review - 팀장이 자신을 프로젝트에서 제거할 수 없도록 수정
JayongLee Sep 29, 2025
48b5681
프로젝트 삭제 & 프로젝트-멤버 추가, 삭제 API 구현 - #81
JayongLee Sep 29, 2025
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
Expand Up @@ -2,4 +2,5 @@

public abstract class DomainConstants {
public static final String LEADER = "팀장";
public static final String MEMBER = "팀원";
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package dev.woori.wooriLog.domain.blog.controller;

import dev.woori.wooriLog.domain.blog.dto.request.BlogCreateReq;
import dev.woori.wooriLog.domain.blog.dto.request.BlogCreateOrUpdateReq;
import dev.woori.wooriLog.domain.blog.dto.response.BlogCreateRes;
import dev.woori.wooriLog.domain.blog.dto.response.BlogDetailInfoRes;
import dev.woori.wooriLog.domain.blog.service.BlogService;
Expand All @@ -10,39 +10,60 @@
import dev.woori.wooriLog.global.response.SuccessCode;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.Optional;


@Slf4j
@RestController
@RequestMapping("/api")
@RequiredArgsConstructor
public class BlogController {

private final BlogService blogService;

// 최신 5개의 블로그 조회 (홈화면)
@GetMapping("/blog/home")
public ResponseEntity<BaseResponse<?>> getHomeBlogInfos() {
return ApiResponseUtil.success(SuccessCode.OK, blogService.getBlogBasicInfos());
}

// 블로그 작성
@PostMapping("/blog/{projectId}")
public ResponseEntity<BaseResponse<?>> createBlog(
@PathVariable("projectId") Long projectId,
@UserId Long userId,
@Valid @RequestBody BlogCreateReq request
@Valid @RequestBody BlogCreateOrUpdateReq request
) {
return ApiResponseUtil.success(
SuccessCode.OK,
BlogCreateRes.from(blogService.createBlog(projectId, userId, request))
);
}

// 블로그 조회
@GetMapping("/blog/{postId}")
public ResponseEntity<BaseResponse<?>> getBlogInfo(
@UserId Optional<Long> userId,
@PathVariable("postId") Long postId
) {
BlogDetailInfoRes res = blogService.getBlogInfo(postId);
BlogDetailInfoRes res = blogService.getBlogInfo(userId, postId);
return ApiResponseUtil.success(SuccessCode.OK, res);
}

// 블로그 수정
@PutMapping("/blog/{postId}")
public ResponseEntity<BaseResponse<?>> updateBlog(@UserId Long userId, @PathVariable("postId") Long postId, @Valid @RequestBody BlogCreateOrUpdateReq request){
return ApiResponseUtil.success(SuccessCode.OK, blogService.updateBlog(userId, postId, request));
}

// 블로그 삭제
@DeleteMapping("/blog/{postId}")
public ResponseEntity<BaseResponse<?>> deleteBlog(@UserId Long userId, @PathVariable("postId") Long postId) {
blogService.deleteBlog(userId, postId);
return ApiResponseUtil.success(SuccessCode.OK);
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
package dev.woori.wooriLog.domain.blog.dto.request;

import dev.woori.wooriLog.domain.blog.entity.Progress;
import dev.woori.wooriLog.domain.blog.enums.Category;
import dev.woori.wooriLog.domain.blog.enums.ProgressValue;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Builder;

import java.util.List;

@Builder
public record BlogCreateReq(
public record BlogCreateOrUpdateReq(
@NotBlank
String title,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
import dev.woori.wooriLog.domain.blog.entity.Blog;
import dev.woori.wooriLog.domain.blog.entity.Progress;
import dev.woori.wooriLog.domain.blog.enums.Category;
import dev.woori.wooriLog.domain.member.entity.Member;
import lombok.Builder;

import java.time.LocalDateTime;
import java.util.List;

@Builder
Expand All @@ -14,19 +16,27 @@ public record BlogBasicInfoRes(
String title,
String projectName,
String authorName,
String authorProfileUrl,
Category category,
List<String> tags,
List<ProgressDto> progresses
List<ProgressDto> progresses,
LocalDateTime createdAt,
LocalDateTime updatedAt
) {
public static BlogBasicInfoRes create(Blog blog) {
Member author = blog.getMember();

return BlogBasicInfoRes.builder()
.blogId(blog.getId())
.title(blog.getTitle())
.projectName(blog.getProject().getProjectName())
.authorName(blog.getMember().getName())
.authorName(author.getName())
.authorProfileUrl(author.getProfileUrl())
.category(blog.getCategory())
.tags(blog.getTags())
.progresses(transProgressDtos(blog.getProgresses()))
.createdAt(blog.getCreatedAt())
.updatedAt(blog.getUpdatedAt())
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,19 @@

@Builder
public record BlogDetailInfoRes(
boolean isAuthor,
BlogDto post,
BlogProjectDto project,
MemberInfoDto author
) {
public static BlogDetailInfoRes create(
boolean isAuthor,
BlogDto post,
BlogProjectDto project,
MemberInfoDto author
) {
return BlogDetailInfoRes.builder()
.isAuthor(isAuthor)
.post(post)
.project(project)
.author(author)
Expand Down
16 changes: 14 additions & 2 deletions src/main/java/dev/woori/wooriLog/domain/blog/entity/Blog.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package dev.woori.wooriLog.domain.blog.entity;

import dev.woori.wooriLog.domain.blog.dto.request.BlogCreateReq;
import dev.woori.wooriLog.domain.blog.dto.request.BlogCreateOrUpdateReq;
import dev.woori.wooriLog.domain.blog.enums.Category;
import dev.woori.wooriLog.domain.member.entity.Member;
import dev.woori.wooriLog.domain.project.entity.Project;
Expand Down Expand Up @@ -55,7 +55,7 @@ public class Blog extends BaseEntity {
@Builder.Default
private List<Progress> progresses = new ArrayList<>();

public static Blog create(Project project, Member member, BlogCreateReq request, List<Progress> progresses) {
public static Blog create(Project project, Member member, BlogCreateOrUpdateReq request, List<Progress> progresses) {
Blog blog = Blog.builder()
.document(request.document())
.title(request.title())
Expand All @@ -68,6 +68,18 @@ public static Blog create(Project project, Member member, BlogCreateReq request,
return blog;
}

public void update(BlogCreateOrUpdateReq request, List<Progress> progresses) {
this.title = request.title();
this.category = request.category();
this.document = request.document();
// 태그 초기화 및 업데이트
this.tags.clear();
this.tags.addAll(request.tags());
// Progress 초기화 및 업데이트
this.progresses.clear();
addProgresses(progresses);
}

private void addProgress(Progress p) {
this.progresses.add(p);
p.setBlog(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
import com.fasterxml.jackson.annotation.JsonValue;

public enum Category {
BLOG("블로그"),
CHECKPOINT("체크포인트"),
REVIEW("리뷰"),
TECH("기술/딥다이브");
REVIEW("회고"),
TROUBLESHOOTING("트러블슈팅"),
DEEPDIVE("딥다이브"),
ETC("기타");

private final String value;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,23 @@
import com.fasterxml.jackson.annotation.JsonValue;

public enum ProgressValue {
// 회고
OUTLINE("개요"),
KEEP("잘했던 점"),
PROBLEM("아쉬웠던 점"),
TRY("배운 점 & 개선 방안"),
// 트러블 슈팅
SITUATION("상황"),
CONCERN("고민"),
ACTION("실행"),
REFLECTION("회고/성장");
REFLECTION("회고/성장"),
// 딥다이브
INTRODUCE("기술소개"),
EXAMPLE("예시"),
CORE("핵심 개념"),
CONCLUSION("결론"),
// 기타
SUMMARY("요약");

private final String value;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
public interface BlogRepository extends JpaRepository<Blog, Long> {
Optional<Blog> findById(Long blogId);

@Query("SELECT b FROM Blog b JOIN FETCH b.member WHERE b.id = :blogId")
Optional<Blog> findByIdWithMember(@Param("blogId") Long blogId);

@Query("SELECT b FROM Blog b LEFT JOIN FETCH b.tags WHERE b.member.id = :memberId")
List<Blog> findByMemberId(Long memberId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@


import dev.woori.wooriLog.domain.blog.dto.*;
import dev.woori.wooriLog.domain.blog.dto.request.BlogCreateReq;
import dev.woori.wooriLog.domain.blog.dto.request.BlogCreateOrUpdateReq;
import dev.woori.wooriLog.domain.blog.dto.response.BlogBasicInfoRes;
import dev.woori.wooriLog.domain.blog.dto.response.BlogDetailInfoRes;
import dev.woori.wooriLog.domain.blog.entity.Blog;
Expand All @@ -17,10 +17,12 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.PageRequest;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;

import static dev.woori.wooriLog.global.response.error.ErrorMessage.*;

Expand All @@ -41,17 +43,14 @@ public class BlogService {
* @param request 새로운 글의 데이터가 담긴 request
*/
@Transactional
public Long createBlog(Long projectId, Long userId, BlogCreateReq request) {
public Long createBlog(Long projectId, Long userId, BlogCreateOrUpdateReq request) {
log.info("[Blog Service] Create Blog : projectId={}, userId={}", projectId, userId);
// 프로젝트-멤버 관계 조회
ProjectMember projectMember = projectMemberRepository.findWithMemberAndProjectByIds(projectId, userId)
.orElseThrow(() -> new EntityNotFoundException(RELATION_NOT_FOUND));

// Progress Entity 생성
List<Progress> progresses = request.progresses().stream()
.filter(progressDto -> progressDto.message() != null && !progressDto.message().isBlank())
.map(Progress::create)
.toList();
List<Progress> progresses = filterAndCreateProgress(request);

// Blog Entity 생성
Blog createdBlog = Blog.create(
Expand All @@ -72,16 +71,17 @@ public Long createBlog(Long projectId, Long userId, BlogCreateReq request) {
* @return BlogDetailInfoRes: 열람할 글, 작성자, 작성 프로젝트의 정보
*/
@Transactional
public BlogDetailInfoRes getBlogInfo(Long postId) {
public BlogDetailInfoRes getBlogInfo(Optional<Long> memberId, Long postId) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

로그인한 사용자가 글 작성자인지 확인하는 로직을 getBlogInfo 메서드에 추가하셨는데, 이 로직을 블로그 수정(updateBlog) 및 삭제(deleteBlog) 시에도 활용할 수 있도록 findBlogAndCheckOwnerShip 메서드 내에서 처리하는 것이 더 일관성 있고 재사용성이 높습니다. 현재는 updateBlogdeleteBlog에서만 소유권 확인을 하고 있어, getBlogInfo에서도 동일한 로직을 사용하면 중복을 줄일 수 있습니다.

log.info("[Blog Service] Get Blog Info : blogId={}", postId);

Blog blog = blogRepository.findBlogByIdWithDetails(postId)
.orElseThrow(() -> new EntityNotFoundException(BLOG_NOT_FOUND));

Project project = blog.getProject();
Member author = blog.getMember();
boolean isAuthor = checkAuthor(memberId, author.getId());

return BlogDetailInfoRes.create(
isAuthor,
BlogDto.create(blog),
createBlogProjectDTO(project, author),
MemberInfoDto.create(author)
Expand Down Expand Up @@ -117,4 +117,65 @@ private BlogProjectDto createBlogProjectDTO(Project project, Member author) {

return BlogProjectDto.create(project, memberList);
}

/**
* 블로그 id와 수정된 블로그 포스팅 정보를 통해 블로그 글을 수정
* 블로그 글 작성자 id와 요청을 보낸 사용자 id가 일치하지 않으면 예외 발생
* @param userId 사용자 id
* @param blogId 블로그 id
* @param request 블로그 수정 폼에 담긴 내용들
* @return blogId 블로그 id
*/
@Transactional
public Long updateBlog(Long userId, Long blogId, BlogCreateOrUpdateReq request) {
log.info("[Blog Service] Update Blog : blogId={}", blogId);
Blog blog = findBlogAndCheckOwnerShip(userId, blogId);
List<Progress> progresses = filterAndCreateProgress(request);
blog.update(request, progresses);
return blogId;
}

/**
* 블로그 id를 받아와 해당 블로그 글을 삭제
* 블로그 글 작성자 id와 요청을 보낸 사용자 id가 일치하지 않으면 예외 발생
* @param userId 사용자 id
* @param blogId 블로그 id
*/
@Transactional
public void deleteBlog(Long userId, Long blogId) {
log.info("[Blog Service] Delete Blog : blogId={}", blogId);
Blog blog = findBlogAndCheckOwnerShip(userId, blogId);
blogRepository.delete(blog);
}

/**
* 블로그 글 id를 통해 블로그 글을 가져오고 글의 작성자인지 확인하는 메서드
* @param userId 사용자 id
* @param blogId 블로그 id
* @return Blog 요청을 보낸 사람이 작성자인 게 확인된 블로그 entity
*/
private Blog findBlogAndCheckOwnerShip(Long userId, Long blogId) {
Blog blog = blogRepository.findByIdWithMember(blogId).orElseThrow(() -> new EntityNotFoundException(BLOG_NOT_FOUND));
if (!blog.getMember().getId().equals(userId)) {
throw new AccessDeniedException(BLOG_ACCESS_DENIED);
}
return blog;
}

/**
* 글의 작성자인지 확인하는 메서드
* @param memberId 조회한 유저의 memberId
* @param authorId 작성자의 memberId
* @return boolean 조회한 클라이언트가 작성자인지 여부
*/
private static boolean checkAuthor(Optional<Long> memberId, Long authorId) {
return memberId.filter(id -> id.equals(authorId)).isPresent();
}

private static List<Progress> filterAndCreateProgress(BlogCreateOrUpdateReq request) {
return request.progresses().stream()
.filter(progressDto -> progressDto.message() != null && !progressDto.message().isBlank())
.map(Progress::create)
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,25 @@ public class MemberController {

private final MemberService memberService;

// 이메일을 이용한 유저 검색
@GetMapping("/members/search")
public ResponseEntity<?> getMembersInfoByEmail(@RequestParam String email, @UserId Long memberId) {
return ApiResponseUtil.success(SuccessCode.OK, memberService.findMembersByEmail(email, memberId));
}

// 유저 조회
@GetMapping("/members")
public ResponseEntity<?> getMemberInfoById(@UserId Long userId) {
return ApiResponseUtil.success(SuccessCode.OK, memberService.getMemberInfo(userId));
}

// 유저 프로필 조회
@GetMapping("/members/profile")
public ResponseEntity<?> getProfileInfoById(@UserId Long userId) {
return ApiResponseUtil.success(SuccessCode.OK, memberService.getProfileInfoById(userId));
}

// 유저 프로필 수정
@PutMapping("/members/profile")
public ResponseEntity<?> updateMemberInfo(@UserId Long userId, @Valid @RequestBody MemberUpdateReq request) {
return ApiResponseUtil.success(SuccessCode.OK, memberService.updateMemberInfo(userId, request));
Expand Down
Loading