Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -14,6 +14,7 @@
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.tuna.zoopzoop.backend.domain.datasource.dto.*;
import org.tuna.zoopzoop.backend.domain.datasource.entity.Category;
import org.tuna.zoopzoop.backend.domain.datasource.service.DataSourceService;
import org.tuna.zoopzoop.backend.domain.datasource.service.PersonalDataSourceService;
import org.tuna.zoopzoop.backend.global.rsData.RsData;
Expand Down Expand Up @@ -212,8 +213,9 @@ public ResponseEntity<RsData<SearchResponse<DataSourceSearchItem>>> search(
@PageableDefault(size = 8, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable,
@AuthenticationPrincipal CustomUserDetails user
) {
Category categoryEnum = category != null ? Category.from(category) : null;
var cond = DataSourceSearchCondition.builder()
.title(title).summary(summary).category(category).folderId(folderId)
.title(title).summary(summary).category(categoryEnum).folderId(folderId)
.folderName(folderName).isActive(isActive).keyword(keyword).build();

Page<DataSourceSearchItem> page = personalApp.search(user.getMember().getId(), cond, pageable);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

import lombok.Builder;
import lombok.Getter;
import org.tuna.zoopzoop.backend.domain.datasource.entity.Category;

@Getter
@Builder
public class DataSourceSearchCondition {
private final String title;
private final String summary;
private final String category;
private final String Source;
private final Category category;
// private final String Source;
private final Integer folderId;
private final String folderName;
private final Boolean isActive;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.tuna.zoopzoop.backend.domain.datasource.entity;

import com.fasterxml.jackson.annotation.JsonCreator;

public enum Category {
POLITICS("정치"),
ECONOMY("경제"),
Expand Down Expand Up @@ -29,4 +31,22 @@ public boolean isBlank() {
public String toUpperCase() {
return this.name.toUpperCase();
}

// JSON 문자열을 Category enum으로 변환
@JsonCreator
public static Category from(String input) {
if (input == null || input.isBlank()) return null;
if (input.equalsIgnoreCase("IT")) return IT; // IT 예외

// 1) 한글 이름 매칭
for (Category c : values()) {
if (c.getName().equalsIgnoreCase(input)) return c;
}
// 2) 영문 코드 fallback (e.g., "SPORTS")
try {
return Category.valueOf(input.toUpperCase());
} catch (IllegalArgumentException ex) {
throw new IllegalArgumentException("유효하지 않은 카테고리 값입니다: " + input);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.tuna.zoopzoop.backend.domain.archive.folder.entity.QFolder;
import org.tuna.zoopzoop.backend.domain.datasource.dto.DataSourceSearchCondition;
import org.tuna.zoopzoop.backend.domain.datasource.dto.DataSourceSearchItem;
import org.tuna.zoopzoop.backend.domain.datasource.entity.Category;
import org.tuna.zoopzoop.backend.domain.datasource.entity.QDataSource;
import org.tuna.zoopzoop.backend.domain.datasource.entity.QTag;

Expand Down Expand Up @@ -51,19 +52,28 @@ public Page<DataSourceSearchItem> search(Integer memberId, DataSourceSearchCondi

if (hasText(cond.getTitle())) where.and(ds.title.containsIgnoreCase(cond.getTitle()));
if (hasText(cond.getSummary())) where.and(ds.summary.containsIgnoreCase(cond.getSummary()));
if (hasText(cond.getCategory())) where.and(ds.category.stringValue().containsIgnoreCase(cond.getCategory()));
if (hasText(cond.getSource())) where.and(ds.source.containsIgnoreCase(cond.getSource()));
if (cond.getCategory() != null) where.and(ds.category.eq(cond.getCategory()));

// 키워드 검색 (카테고리는 "한글 라벨 정확 일치" + IT 예외만 허용)
if (hasText(cond.getKeyword())) {
String kw = cond.getKeyword();

BooleanBuilder categoryMatch = new BooleanBuilder();
Category cat = resolveCategoryFromKoreanOrIT(kw); // 한글 또는 IT만 매칭
if (cat != null) {
categoryMatch.or(ds.category.eq(cat));
}
// 영어 ENUM 문자열로의 비교는 제거 (ds.category.stringValue().containsIgnoreCase(kw) 금지)

where.and(
ds.title.containsIgnoreCase(kw)
.or(ds.summary.containsIgnoreCase(kw))
.or(ds.source.containsIgnoreCase(kw))
.or(ds.category.stringValue().containsIgnoreCase(kw))
.or(categoryMatch)
);
}

// 폴더 조건
if (hasText(cond.getFolderName())) where.and(ds.folder.name.eq(cond.getFolderName()));
if (cond.getFolderId() != null) where.and(ds.folder.id.eq(cond.getFolderId()));

Expand Down Expand Up @@ -156,33 +166,46 @@ public Page<DataSourceSearchItem> searchInArchive(Integer archiveId, DataSourceS
QTag tag = QTag.tag;

BooleanBuilder where = new BooleanBuilder();

// 활성/비활성
if (cond.getIsActive() == null || Boolean.TRUE.equals(cond.getIsActive())) where.and(ds.isActive.isTrue());
else where.and(ds.isActive.isFalse());

// 개별 필드 필터
if (hasText(cond.getTitle())) where.and(ds.title.containsIgnoreCase(cond.getTitle()));
if (hasText(cond.getSummary())) where.and(ds.summary.containsIgnoreCase(cond.getSummary()));
if (hasText(cond.getCategory())) where.and(ds.category.stringValue().containsIgnoreCase(cond.getCategory()));
if (hasText(cond.getSource())) where.and(ds.source.containsIgnoreCase(cond.getSource()));
if (cond.getCategory() != null) where.and(ds.category.eq(cond.getCategory()));

// 키워드 검색 (카테고리는 "한글 라벨 정확 일치" + IT 예외만 허용)
if (hasText(cond.getKeyword())) {
String kw = cond.getKeyword();

BooleanBuilder categoryMatch = new BooleanBuilder();
Category cat = resolveCategoryFromKoreanOrIT(kw);
if (cat != null) {
categoryMatch.or(ds.category.eq(cat));
}
// 영어 ENUM 문자열 비교 제거

where.and(
ds.title.containsIgnoreCase(kw)
.or(ds.summary.containsIgnoreCase(kw))
.or(ds.source.containsIgnoreCase(kw))
.or(ds.category.stringValue().containsIgnoreCase(kw))
.or(categoryMatch)
);
}
if (hasText(cond.getFolderName())) where.and(ds.folder.name.eq(cond.getFolderName()));
if (cond.getFolderId() != null) where.and(ds.folder.id.eq(cond.getFolderId()));

// 아카이브 스코프
BooleanBuilder scope = new BooleanBuilder().and(folder.archive.id.eq(archiveId));

// count
JPAQuery<Long> countQuery = queryFactory
.select(ds.id.countDistinct())
.from(ds)
.join(ds.folder, folder)
.where(where.and(scope));

// content
JPAQuery<Tuple> contentQuery = queryFactory
.select(ds.id, ds.title, ds.dataCreatedDate, ds.summary, ds.source, ds.sourceUrl, ds.imageUrl, ds.category)
.from(ds)
Expand Down Expand Up @@ -220,10 +243,26 @@ public Page<DataSourceSearchItem> searchInArchive(Integer archiveId, DataSourceS
row.get(ds.sourceUrl),
row.get(ds.imageUrl),
tagsById.getOrDefault(row.get(ds.id), List.of()),
row.get(ds.category).name()
row.get(ds.category).name() // 응답은 영문 코드 유지
))
.toList();

return new PageImpl<>(content, pageable, total);
}

/**
* 키워드가 "IT"(대소문자 무시)이면 IT를, 그 외에는
* 카테고리의 한글 라벨과 "정확 일치"할 때만 해당 Enum을 반환.
* (예: "스포츠" -> SPORTS, "SPORTS" -> null)
*/
private Category resolveCategoryFromKoreanOrIT(String kw) {
if (kw == null || kw.isBlank()) return null;
if ("IT".equalsIgnoreCase(kw)) return Category.IT; // 유일한 영문 예외
for (Category c : Category.values()) {
if (c != Category.IT && c.getName().equalsIgnoreCase(kw)) {
return c; // 한글 라벨 정확 일치
}
}
return null; // 영문 코드 등은 매칭하지 않음
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.tuna.zoopzoop.backend.domain.datasource.dto.*;
import org.tuna.zoopzoop.backend.domain.datasource.entity.Category;
import org.tuna.zoopzoop.backend.domain.datasource.service.DataSourceService;
import org.tuna.zoopzoop.backend.domain.space.archive.service.SpaceDataSourceService;
import org.tuna.zoopzoop.backend.global.rsData.RsData;
Expand Down Expand Up @@ -239,8 +240,9 @@ public ResponseEntity<RsData<SearchResponse<DataSourceSearchItem>>> search(
@PageableDefault(size = 8, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable,
@AuthenticationPrincipal CustomUserDetails user
) {
Category categoryEnum = category != null ? Category.from(category) : null;
var cond = DataSourceSearchCondition.builder()
.title(title).summary(summary).category(category).folderId(folderId)
.title(title).summary(summary).category(categoryEnum).folderId(folderId)
.folderName(folderName).isActive(isActive).keyword(keyword).build();

Page<DataSourceSearchItem> page = spaceApp.search(user.getMember().getId(), spaceId, cond, pageable);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ void filter_summary_contains() {
void filter_category_contains() {
Pageable pageable = PageRequest.of(0, 10);
DataSourceSearchCondition cond = DataSourceSearchCondition.builder()
.category("it")
.category(Category.IT)
.build();

Page<DataSourceSearchItem> page = dataSourceQRepository.search(memberId, cond, pageable);
Expand Down