Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
f1fe2d4
feat: 토스트 컴포넌트 생성 (Refs: #4)
hanguswls Jan 13, 2025
753fe98
feat: 필수 입력 항목을 입력하지 않은 상태에서 글 저장시, alert 대신 토스트로 표시 (Refs: #4)
hanguswls Jan 13, 2025
2ba9663
chore: store 폴더의 이름을 stores로 변경
hanguswls Jan 13, 2025
73983e5
feat: Post 페이지 레이아웃 구현(#6)
Jan 13, 2025
0defedd
feat: Post 페이지를 route에 추가(#6)
Jan 13, 2025
08b589a
refactor: 토스트의 duration을 적절한 값(2500)으로 단축 (Refs: #4)
hanguswls Jan 13, 2025
6a74ba1
feat: 카테고리 생성 기능 구현 (#8)
peanut990 Jan 18, 2025
fd0a135
feat: 카테고리 생성 기능 테스트 코드 추가 (#8)
peanut990 Jan 18, 2025
a9547d5
feat: 예외처리 핸들러 추가, 카테고리 기본 생성자 추가 (#8)
peanut990 Jan 18, 2025
c632327
feat: 카테고리 수정 관련 Dto 정의 (#8)
peanut990 Jan 18, 2025
55e88e9
feat: 카테고리 수정 기능 구현 (#8)
peanut990 Jan 18, 2025
4f0a7ac
feat: 카테고리 수정 기능 테스트 코드 추가 (#8)
peanut990 Jan 18, 2025
cfbf90c
Merge pull request #65 from hanguswls/feature/4-toast
hanguswls Jan 20, 2025
7a6dde3
Merge branch 'develop' into feature/post-6
thanhhien234 Jan 20, 2025
b2ea01a
refactor: 글 목록 페이지 레이아웃 수정
hanguswls Jan 20, 2025
a6cb606
fix: API 응답 데이터 처리 로직 수정
hanguswls Jan 20, 2025
0118992
fix: fetchPostList 데이터 처리 로직 개선
hanguswls Jan 20, 2025
c45a8cc
refactor: Home.tsx에서 글 목록과 태그 컨테이너 반응형 디자인 적용
hanguswls Jan 21, 2025
f8913ff
fix: categoryId를 쿼리 문자열로 사용하는 네비게이션 경로 수정
hanguswls Jan 21, 2025
ea449e3
Merge branch 'DevBadgers:main' into feature/post-6
thanhhien234 Jan 21, 2025
cd1784d
fix: 스타일 오타 수정(#6)
Jan 21, 2025
1fc0e27
Merge pull request #70 from hanguswls/feature/69-fix-view-postlist
hanguswls Jan 21, 2025
3113074
Merge pull request #66 from thanhhien234/feature/post-6
thanhhien234 Jan 21, 2025
e085d36
feat: 카테고리 삭제 기능 구현 (#8)
peanut990 Jan 21, 2025
6aaef34
feat: 카테고리 삭제 기능 테스트 코드 추가 (#8)
peanut990 Jan 21, 2025
027e00d
fix: 경로 수정(#6)
Jan 21, 2025
ca31094
fix: 카테고리 스타일 수정(#6)
Jan 21, 2025
1bc5658
feat: Link 삭제, cursor 추가(#6)
Jan 21, 2025
7912af7
feat: @_types 선언(#6)
Jan 21, 2025
d09d43b
feat: 이름 변경 Post -> PostParams(#6)
Jan 21, 2025
7449dee
feat: navigate to Post page(#6)
Jan 21, 2025
8eb7390
feat: Post 조회 api 연동, 스타일 수정(#6)
Jan 21, 2025
ed91bb7
fix: 페이징 버그 수정(#69)
Jan 21, 2025
95f2d02
feat: SVG를 React 컴포넌트로 가져오기 위해 'vite-plugin-svgr' 추가
hanguswls Jan 22, 2025
830530c
feat: IconButton 컴포넌트 구현 및 스타일링
hanguswls Jan 22, 2025
a78e911
feat: CategoryNode 컴포넌트 구현 및 드래그 앤 드롭 기능 추가
hanguswls Jan 22, 2025
e717552
feat: CategoryManagement 컴포넌트 추가 및 스타일링
hanguswls Jan 22, 2025
6eeabdc
Merge pull request #72 from thanhhien234/feature/post-6
thanhhien234 Jan 22, 2025
8ad50bb
Merge pull request #71 from DevBadgers/feature/6-get-post
peanut990 Jan 23, 2025
add882c
Merge pull request #73 from thanhhien234/bug/paging
thanhhien234 Jan 23, 2025
7d3552a
feat: 기타를 선택 시 카테고리 없는 게시글 이동(#8)
Jan 23, 2025
bd804b4
feat: 게시글을 생성할 때 ‘카테고리 없음’ 추가, categoryId = 0로 보내기(#8)
Jan 23, 2025
39c02af
Merge pull request #74 from hanguswls/feature/8-category-management
hanguswls Jan 24, 2025
0a6fe18
fix: remove 기타 버튼(#8)
Jan 24, 2025
7b84b02
feat: 카테고리 없는 걸 기타로 바꿈(#8)
Jan 24, 2025
1ff4ca2
Merge pull request #75 from thanhhien234/feature/non-category-6
thanhhien234 Jan 24, 2025
79f7a7b
fix: 데이터베이스 초기화시 사용되는 카테고리 테이블명 수정 (#8)
peanut990 Jan 24, 2025
0ed7fca
Merge pull request #76 from DevBadgers/fix/init-database
peanut990 Jan 24, 2025
e7bab4d
feat: 브랜치 구별해서 배포 되도록 수정
peanut990 Jan 27, 2025
c1a7da1
Merge pull request #78 from DevBadgers/feature/spring-CD
peanut990 Jan 27, 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
4 changes: 3 additions & 1 deletion .github/workflows/spring-delploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ jobs:
ssh ec2-user@${{ vars.EC2_HOST }} << 'EOF'
cd /home/ec2-user/starchive/scripts

git checkout develop
# Dynamically checkout the branch that triggered the merge
git checkout ${{ github.event.pull_request.base.ref }}

# 스프링 배포 스크립트 실행
./spring-deploy.sh

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
package com.starchive.springapp.category.controller;

import com.starchive.springapp.category.dto.CategoryCreateRequest;
import com.starchive.springapp.category.dto.CategoryDto;
import com.starchive.springapp.category.dto.CategoryUpdateRequest;
import com.starchive.springapp.category.dto.CategoryUpdateResponse;
import com.starchive.springapp.category.service.CategoryService;
import com.starchive.springapp.global.dto.ResponseDto;
import com.starchive.springapp.hashtag.dto.HashTagDto;
import com.starchive.springapp.hashtag.service.HashTagService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Null;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
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.RestController;

@RestController
Expand All @@ -36,4 +44,28 @@ public ResponseEntity<ResponseDto<List<HashTagDto>>> showHashTags(@PathVariable(
ResponseDto<List<HashTagDto>> listResponseDto = new ResponseDto<>(categories);
return ResponseEntity.ok(listResponseDto);
}

@PostMapping("/categories")
@Operation(summary = "카테고리 생성")
public ResponseEntity<Null> createCategory(@Valid CategoryCreateRequest categoryCreateRequest) {
categoryService.create(categoryCreateRequest);

return ResponseEntity.noContent().build();
}

@PutMapping("/categories")
@Operation(summary = "카테고리 수정")
public ResponseEntity<ResponseDto<CategoryUpdateResponse>> updateCategory(
@Valid CategoryUpdateRequest categoryUpdateRequest) {
CategoryUpdateResponse updateResponse = categoryService.update(categoryUpdateRequest);

return ResponseEntity.ok(new ResponseDto<>(updateResponse));
}

@DeleteMapping("/categories/{categoryId}")
@Operation(summary = "카테고리 삭제")
public ResponseEntity<Null> deleteCategory(@PathVariable("categoryId") Long categoryId) {
categoryService.delete(categoryId);
return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static jakarta.persistence.FetchType.LAZY;

import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
Expand Down Expand Up @@ -33,7 +34,7 @@ public class Category {
@Column(length = 100)
String name;

@OneToMany(mappedBy = "parent", fetch = LAZY)
@OneToMany(mappedBy = "parent", fetch = LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
private List<Category> children = new ArrayList<>();

public Category(String name, Category parent) {
Expand All @@ -44,9 +45,15 @@ public Category(String name, Category parent) {
}
}

private void changeParent(Category parent) {
public void changeName(String name) {
this.name = name;
}

public void changeParent(Category parent) {
this.parent = parent;
parent.getChildren().add(this);
if (parent != null) {
parent.getChildren().add(this);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.starchive.springapp.category.dto;

import jakarta.annotation.Nullable;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class CategoryCreateRequest {
@Size(max = 100)
@NotNull
private String name;

@Nullable
private Long parentId;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.starchive.springapp.category.dto;

import jakarta.annotation.Nullable;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class CategoryUpdateRequest {
@NotNull
private Long categoryId;

@Size(max = 100)
@NotNull
private String name;

@Nullable
private Long parentId;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.starchive.springapp.category.dto;

import com.starchive.springapp.category.domain.Category;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class CategoryUpdateResponse {
private Long categoryId;

private String name;

private Long parentId;

public static CategoryUpdateResponse from(Category category) {
CategoryUpdateResponse categoryUpdateResponse = new CategoryUpdateResponse();
categoryUpdateResponse.categoryId = category.getId();
categoryUpdateResponse.name = category.getName();
if (category.getParent() == null) {
categoryUpdateResponse.parentId = null;
} else {
categoryUpdateResponse.parentId = category.getParent().getId();
}
return categoryUpdateResponse;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.starchive.springapp.category.exception;

import static com.starchive.springapp.global.ErrorMessage.ALREADY_EXISTS_CATEGORY;

public class CategoryAlreadyExistsException extends RuntimeException {
public CategoryAlreadyExistsException() {
super(ALREADY_EXISTS_CATEGORY);
}

public CategoryAlreadyExistsException(String message) {
super(message);
}

public CategoryAlreadyExistsException(String message, Throwable cause) {
super(message, cause);
}

public CategoryAlreadyExistsException(Throwable cause) {
super(cause);
}

protected CategoryAlreadyExistsException(String message, Throwable cause, boolean enableSuppression,
boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@

public interface CategoryRepository extends JpaRepository<Category, Long> {
@Query("SELECT c FROM Category c LEFT JOIN FETCH c.children left join fetch c.parent WHERE c.id = :id")
Optional<Category> findByIdWithChildren(@Param("id") Long id);
Optional<Category> findByIdWithParentAndChildren(@Param("id") Long id);

@Query("SELECT DISTINCT c FROM Category c LEFT JOIN FETCH c.children WHERE c.parent IS NULL")
List<Category> findRootCategoriesWithChildren();

Optional<Category> findByName(@Param("name") String name);

@Query("select c from Category c left join fetch c.parent where c.id = :id")
Optional<Category> findByIdWithParent(@Param("id") Long id);

}
Original file line number Diff line number Diff line change
@@ -1,26 +1,126 @@
package com.starchive.springapp.category.service;

import com.starchive.springapp.category.domain.Category;
import com.starchive.springapp.category.dto.CategoryCreateRequest;
import com.starchive.springapp.category.dto.CategoryDto;
import com.starchive.springapp.category.dto.CategoryUpdateRequest;
import com.starchive.springapp.category.dto.CategoryUpdateResponse;
import com.starchive.springapp.category.exception.CategoryAlreadyExistsException;
import com.starchive.springapp.category.exception.CategoryNotFoundException;
import com.starchive.springapp.category.repository.CategoryRepository;
import com.starchive.springapp.post.repository.PostRepository;
import java.util.ArrayList;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
@RequiredArgsConstructor
public class CategoryService {
private final CategoryRepository categoryRepository;
private final PostRepository postRepository;

public List<CategoryDto> findAll() {
List<Category> rootCateGories = categoryRepository.findRootCategoriesWithChildren();
return rootCateGories.stream().map(CategoryDto::from).toList();
}

public Category findOne(Long id) {
return categoryRepository.findByIdWithChildren(id).orElseThrow(CategoryNotFoundException::new);
return categoryRepository.findByIdWithParentAndChildren(id).orElseThrow(CategoryNotFoundException::new);
}


public void create(CategoryCreateRequest categoryCreateRequest) {
categoryRepository.findByName(categoryCreateRequest.getName()).ifPresent(category -> {
throw new CategoryAlreadyExistsException();
});

if (categoryCreateRequest.getParentId() == null) {
Category category = new Category(categoryCreateRequest.getName(), null);
categoryRepository.save(category);
return;
}

Category parentCategory = categoryRepository.findById(categoryCreateRequest.getParentId())
.orElseThrow(() -> new CategoryNotFoundException("존재하지 않는 부모 카테고리입니다."));

Category category = new Category(categoryCreateRequest.getName(), parentCategory);
categoryRepository.save(category);

}

public CategoryUpdateResponse update(CategoryUpdateRequest categoryUpdateRequest) {
Category category = categoryRepository.findByIdWithParent(categoryUpdateRequest.getCategoryId())
.orElseThrow(CategoryNotFoundException::new);

if (!category.getName().equals(categoryUpdateRequest.getName())) {
categoryRepository.findByName(categoryUpdateRequest.getName()).ifPresent(findOne -> {
throw new CategoryAlreadyExistsException(categoryUpdateRequest.getName() + " 은 이미 존재하는 카테고리 이름입니다.");
});

category.changeName(categoryUpdateRequest.getName());
}

updateParentCategory(categoryUpdateRequest, category);

return CategoryUpdateResponse.from(category);
}

public void delete(Long id) {
if (id == 0) {
throw new RuntimeException("삭제할 수 없는 카테고리입니다.");
}
Category category = categoryRepository.findByIdWithParentAndChildren(id)
.orElseThrow(CategoryNotFoundException::new);

List<Long> categoryIds = new ArrayList<>();
categoryIds.add(category.getId());

if (!category.getChildren().isEmpty()) {
for (Category child : category.getChildren()) {
categoryIds.add(child.getId());
}
}

postRepository.bulkUpdateToNoneCategory(categoryIds);

categoryRepository.deleteById(category.getId());
}

private Category updateParentCategory(CategoryUpdateRequest categoryUpdateRequest, Category category) {
Long newParentId = categoryUpdateRequest.getParentId();
if (category.getParent() != null && newParentId == null) {
category.changeParent(null);
return category;
}

if (category.getParent() != null && newParentId != null) {

Category parentCategory = findParentCategoryById(newParentId);

if (category.getParent().getId() != newParentId) {
category.changeParent(parentCategory);
}

return category;
}

if (category.getParent() == null && newParentId != null) {

Category parentCategory = findParentCategoryById(newParentId);

category.changeParent(parentCategory);

return category;
}

return category;
}

private Category findParentCategoryById(Long parentId) {
return categoryRepository.findById(parentId)
.orElseThrow(() -> new CategoryNotFoundException("존재하지 않는 부모 카테고리입니다."));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ public class ErrorMessage {
final public static String CATEGORY_NOT_FOUND = "카테고리가 존재하지 않습니다.";
final public static String HASHTAG_NOT_FOUND = "해쉬태그가 존재하지 않습니다.";
final public static String POST_NOT_FOUND = "게시글이 존재하지 않습니다.";
final public static String ALREADY_EXISTS_CATEGORY = "이미 존재하는 카테고리 이름입니다.";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.starchive.springapp.global.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.http.HttpStatus;

@Data
@AllArgsConstructor
public class ErrorResult {
private HttpStatus code;
private String message;
}
Loading
Loading