Skip to content

Commit

Permalink
Merge pull request #34 from IT-Cotato/feat/term-#33
Browse files Browse the repository at this point in the history
[Feat] 용어 사전 api 구현
  • Loading branch information
chanmin-00 authored Jan 25, 2025
2 parents 97c0681 + 67ed4fc commit 6761b52
Show file tree
Hide file tree
Showing 21 changed files with 553 additions and 58 deletions.
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

0 comments on commit 6761b52

Please sign in to comment.