Skip to content
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

[Feat] 용어 사전 api 구현 (#33) #34

Merged
merged 19 commits into from
Jan 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
5e61953
Feat : 용어 사전 예외 추가 (#33)
chanmin-00 Jan 24, 2025
b7ccfa8
Feat : 용어 사전 예외 추가 (#33)
chanmin-00 Jan 24, 2025
7f74ede
Chore : Term 엔터티 필드 수정 (#33)
chanmin-00 Jan 24, 2025
e8df31b
Chore : Term 카테고리 삭제 (#33)
chanmin-00 Jan 24, 2025
79918da
Feat : Term DTO 추가 (#33)
chanmin-00 Jan 24, 2025
7c8c04b
Feat : Term 클라이언트 응답 DTO 추가 (#33)
chanmin-00 Jan 24, 2025
e04c9cc
Feat : TermRepository 추가 (#33)
chanmin-00 Jan 24, 2025
52edb3d
Feat : TermJdbcRepository 추가 (#33)
chanmin-00 Jan 24, 2025
90022b9
Feat : 엑셀 파일 로드 및 용어 데이터 저장 기능 추가(#33)
chanmin-00 Jan 24, 2025
4311bfc
Feat : 용어 사전 기능 구현 (#33)
chanmin-00 Jan 24, 2025
dd988f1
Feat : 용어 사전 api 구현 (#33)
chanmin-00 Jan 24, 2025
6f910c9
Docs : 용어 액셀 문서 추가 (#33)
chanmin-00 Jan 24, 2025
b2efd6e
Feat : 용어 스크랩 및 취소 기능 추가 (#33)
chanmin-00 Jan 24, 2025
e376775
Chore : 용어 사전 목록 조회 페이징 기능으로 수정 (#33)
chanmin-00 Jan 24, 2025
5b141d5
Feat : 용어 사전 api 추가 및 수정 (#33)
chanmin-00 Jan 24, 2025
2f117ff
Feat : 스크랩 관련 에러 코드 추가 (#33)
chanmin-00 Jan 24, 2025
e37ca75
Feat : 키워드 검색 성능 향상을 위한 제목 인덱스 추가 (#33)
chanmin-00 Jan 24, 2025
6d6de3f
Merge remote-tracking branch 'origin/develop' into feat/term-#33
chanmin-00 Jan 24, 2025
67ed4fc
Bug : 서버에서 Jar 파일 경로 찾지 못하는 오류 수정 (#33)
chanmin-00 Jan 24, 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
19 changes: 18 additions & 1 deletion src/main/java/com/ripple/BE/global/excel/ExcelUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,17 @@
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.core.io.ClassPathResource;

public class ExcelUtils {

// 엑셀 파일을 파싱하여 데이터 리스트로 반환하는 메서드
public static List<Map<String, String>> parseExcelFile(String filePath, int sheetNumber)
throws Exception {
List<Map<String, String>> rows = new ArrayList<>();
try (InputStream is = new FileInputStream(new File(filePath));

// InputStream으로 파일 읽기
try (InputStream is = getInputStream(filePath);
Workbook workbook = new XSSFWorkbook(is)) {

Sheet sheet = workbook.getSheetAt(sheetNumber);
Expand All @@ -35,6 +38,8 @@ public static List<Map<String, String>> parseExcelFile(String filePath, int shee
// 각 행의 데이터를 읽어 Map으로 저장
for (int i = 1; i <= sheet.getLastRowNum(); i++) {
Row row = sheet.getRow(i);
if (row == null) continue;

Map<String, String> rowData = new HashMap<>();
for (int j = 0; j < headers.size(); j++) {
Cell cell = row.getCell(j);
Expand All @@ -46,6 +51,18 @@ public static List<Map<String, String>> parseExcelFile(String filePath, int shee
return rows;
}

// filePath로부터 InputStream 생성
private static InputStream getInputStream(String filePath) throws Exception {
try {
// ClassPathResource로 JAR 내부 리소스 파일 처리
ClassPathResource resource = new ClassPathResource(filePath);
return resource.getInputStream();
} catch (Exception e) {
// 파일이 JAR 내부가 아니라면 FileInputStream으로 처리
return new FileInputStream(new File(filePath));
}
}

// 셀의 값을 문자열로 변환하는 유틸리티 메서드
private static String getCellValueAsString(Cell cell) {
if (cell == null) return "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.ripple.BE.learning.exception.QuizException;
import com.ripple.BE.news.exception.NewsException;
import com.ripple.BE.post.exception.PostException;
import com.ripple.BE.term.exception.TermException;
import com.ripple.BE.user.exception.UserException;
import io.micrometer.common.lang.NonNull;
import java.util.List;
Expand Down Expand Up @@ -68,6 +69,11 @@ public ResponseEntity<Object> handleAllException(Exception e) {
return handleExceptionInternal(GlobalErrorCode.INTERNAL_SERVER_ERROR);
}

@ExceptionHandler(TermException.class)
public ResponseEntity<Object> handleTermException(final TermException e) {
return handleExceptionInternal(e.getErrorCode());
}

@ExceptionHandler(UserException.class)
public ResponseEntity<Object> handleUserException(final UserException e) {
return handleExceptionInternal(e.getErrorCode());
Expand All @@ -92,7 +98,7 @@ public ResponseEntity<Object> handlePostException(final PostException e) {
public ResponseEntity<Object> handleNewsException(final NewsException e) {
return handleExceptionInternal(e.getErrorCode());
}

@ExceptionHandler(ImageException.class)
public ResponseEntity<Object> handleImageException(final ImageException e) {
return handleExceptionInternal(e.getErrorCode());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,11 @@
import com.ripple.BE.learning.exception.errorcode.LearningErrorCode;
import com.ripple.BE.learning.repository.LearningSetRepository;
import com.ripple.BE.learning.repository.QuizRepository;
import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -39,9 +37,8 @@ public class LearningAdminService {
@Transactional
public void createLearningSetByExcel() {
try {
File file = new ClassPathResource(FILE_PATH).getFile();

List<LearningSet> learningSetList = parseLearningSetsFromExcel(file.getPath());
List<LearningSet> learningSetList = parseLearningSetsFromExcel();
List<LearningSet> existingLearningSets = learningSetRepository.findAll();

Map<String, LearningSet> learningSetMap =
Expand All @@ -67,8 +64,8 @@ public void createLearningSetByExcel() {
existingSet -> existingSet.getName().equals(learningSet.getName())))
.collect(Collectors.toList());

addConceptsToLearningSets(file.getPath(), newLearningSetMap);
addQuizzesToLearningSets(file.getPath(), newLearningSetMap);
addConceptsToLearningSets(FILE_PATH, newLearningSetMap);
addQuizzesToLearningSets(FILE_PATH, newLearningSetMap);

learningSetRepository.saveAll(newLearningSets);

Expand All @@ -78,8 +75,9 @@ public void createLearningSetByExcel() {
}
}

private List<LearningSet> parseLearningSetsFromExcel(String filePath) throws Exception {
return ExcelUtils.parseExcelFile(filePath, LEARNING_SET_SHEET_INDEX).stream()
private List<LearningSet> parseLearningSetsFromExcel() throws Exception {
return ExcelUtils.parseExcelFile(LearningAdminService.FILE_PATH, LEARNING_SET_SHEET_INDEX)
.stream()
.map(LearningSetDTO::toLearningSetDTO)
.map(LearningSet::toLearningSet)
.collect(Collectors.toList());
Expand Down
103 changes: 103 additions & 0 deletions src/main/java/com/ripple/BE/term/controller/TermController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package com.ripple.BE.term.controller;

import static com.ripple.BE.global.exception.errorcode.GlobalErrorCode.*;

import com.ripple.BE.global.dto.response.ApiResponse;
import com.ripple.BE.term.dto.TermDTO;
import com.ripple.BE.term.dto.TermListDTO;
import com.ripple.BE.term.dto.response.TermListResponse;
import com.ripple.BE.term.dto.response.TermResponse;
import com.ripple.BE.term.exception.TermException;
import com.ripple.BE.term.service.TermAdminService;
import com.ripple.BE.term.service.TermService;
import com.ripple.BE.user.domain.CustomUserDetails;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.PositiveOrZero;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
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.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
@RestController
@RequestMapping("/api/v1/terms")
@Tag(name = "Term", description = "용어집 API")
public class TermController {

private final TermService termService;
private final TermAdminService termAdminService;

@Operation(summary = "자음 별 용어 조회", description = "자음 별 용어를 조회합니다.")
@GetMapping("/search/consonant")
public ResponseEntity<ApiResponse<Object>> getTermsByInitial(
final @RequestParam(required = false, defaultValue = "0") @PositiveOrZero int page,
@RequestParam(value = "consonant", required = false, defaultValue = "ㄱ")
final String consonant) {

if (consonant.length() != 1) {
throw new TermException(INVALID_PARAMETER);
}

TermListDTO termListDTO = termService.getTermsByInitial(page, consonant);

return ResponseEntity.status(HttpStatus.OK)
.body(ApiResponse.from(TermListResponse.toTermListResponse(termListDTO)));
}

@Operation(summary = "키워드 별 용어 조회", description = "키워드 별 용어를 조회합니다.")
@GetMapping("/search/keyword")
public ResponseEntity<ApiResponse<Object>> getTermsByKeyword(
final @RequestParam(required = false, defaultValue = "0") @PositiveOrZero int page,
@RequestParam(value = "keyword", required = false) final String keyword) {

TermListDTO termListDTO = termService.getTermsByKeyword(page, keyword);

return ResponseEntity.status(HttpStatus.OK)
.body(ApiResponse.from(TermListResponse.toTermListResponse(termListDTO)));
}

@Operation(summary = "용어 상세 조회", description = "용어 상세를 조회합니다.")
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<Object>> getTerm(@PathVariable final Long id) {

TermDTO termDTO = termService.getTerm(id);

return ResponseEntity.status(HttpStatus.OK)
.body(ApiResponse.from(TermResponse.toTermResponse(termDTO)));
}

@Operation(summary = "용어 스크랩", description = "용어를 스크랩합니다.")
@PostMapping("/{id}/scrap")
public ResponseEntity<ApiResponse<Object>> scrapTerm(
final @AuthenticationPrincipal CustomUserDetails currentUser,
final @PathVariable("id") long id) {

termService.addScrapToTerm(id, currentUser.getId());
return ResponseEntity.status(HttpStatus.OK).body(ApiResponse.from(ApiResponse.EMPTY_RESPONSE));
}

@Operation(summary = "용어 스크랩 취소", description = "용어 스크랩을 취소합니다.")
@DeleteMapping("/{id}/scrap")
public ResponseEntity<ApiResponse<Object>> unscrapTerm(
final @AuthenticationPrincipal CustomUserDetails currentUser,
final @PathVariable("id") long id) {

termService.removeScrapFromTerm(id, currentUser.getId());
return ResponseEntity.status(HttpStatus.OK).body(ApiResponse.from(ApiResponse.EMPTY_RESPONSE));
}

@Operation(summary = "용어 생성 (관리자)", description = "용어를 생성합니다.")
@PostMapping("/excel")
public ResponseEntity<ApiResponse<?>> saveTermsByExcel() {
termAdminService.createTermByExcel();
return ResponseEntity.status(HttpStatus.OK).body(ApiResponse.EMPTY_RESPONSE);
}
}
29 changes: 21 additions & 8 deletions src/main/java/com/ripple/BE/term/domain/Term.java
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
package com.ripple.BE.term.domain;

import com.ripple.BE.global.entity.BaseEntity;
import jakarta.persistence.CascadeType;
import com.ripple.BE.term.dto.TermDTO;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Index;
import jakarta.persistence.Table;
import java.util.ArrayList;
import java.util.List;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Table(name = "terms")
@Table(
name = "terms",
indexes = {
@Index(name = "idx_title", columnList = "title"), // title 컬럼에 인덱스 추가
@Index(name = "idx_initial", columnList = "initial") // initial 컬럼에 인덱스 추가
})
@Getter
@Builder
@Entity
Expand All @@ -33,9 +37,18 @@ public class Term extends BaseEntity {
@Column(name = "title", nullable = false)
private String title;

@Column(name = "description", nullable = false)
@Column(name = "description", nullable = false, columnDefinition = "TEXT")
private String description;

@OneToMany(mappedBy = "term", cascade = CascadeType.ALL, orphanRemoval = true)
private List<TermCategory> termCategoryList = new ArrayList<>();
@Setter
@Column(name = "initial", nullable = false)
private String initial;

public static Term toTermEntity(final TermDTO termDTO) {
return Term.builder()
.title(termDTO.title())
.description(termDTO.description())
.initial(termDTO.initial())
.build();
}
}
40 changes: 0 additions & 40 deletions src/main/java/com/ripple/BE/term/domain/TermCategory.java

This file was deleted.

11 changes: 11 additions & 0 deletions src/main/java/com/ripple/BE/term/domain/TermScrap.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Table(name = "term_scraps")
@Getter
Expand All @@ -34,7 +35,17 @@ public class TermScrap extends BaseEntity {
@JoinColumn(name = "user_id", nullable = false)
private User user;

@Setter
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "term_id")
private Term term;

public static TermScrap toTermScrapEntity() {
return TermScrap.builder().build();
}

public void setUser(User user) {
this.user = user;
user.getTermScrapList().add(this);
}
}
18 changes: 18 additions & 0 deletions src/main/java/com/ripple/BE/term/dto/TermDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.ripple.BE.term.dto;

import com.ripple.BE.term.domain.Term;
import java.util.Map;

public record TermDTO(Long id, String title, String description, String initial) {

private static final String TITLE = "용어";
private static final String DESCRIPTION = "설명";

public static TermDTO toTermDTO(final Map<String, String> excelData) {
return new TermDTO(null, excelData.get(TITLE), excelData.get(DESCRIPTION), null);
}

public static TermDTO toTermDTO(final Term term) {
return new TermDTO(term.getId(), term.getTitle(), term.getDescription(), term.getInitial());
}
}
16 changes: 16 additions & 0 deletions src/main/java/com/ripple/BE/term/dto/TermListDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.ripple.BE.term.dto;

import com.ripple.BE.term.domain.Term;
import java.util.List;
import org.springframework.data.domain.Page;

public record TermListDTO(List<TermDTO> termList, int totalPage, int currentPage // 용어 리스트
) {

public static TermListDTO toTermListDTO(Page<Term> termPage) {
return new TermListDTO(
termPage.getContent().stream().map(TermDTO::toTermDTO).toList(),
termPage.getTotalPages(),
termPage.getNumber());
}
}
Loading