Skip to content

Commit 701abe0

Browse files
authored
Merge pull request #157 from YAPP-Github/feat/PRODUCT-237
[Feat] 응원 태그 도메인 추가
2 parents c304461 + 2d8e987 commit 701abe0

File tree

9 files changed

+224
-1
lines changed

9 files changed

+224
-1
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package eatda.domain.cheer;
2+
3+
import jakarta.persistence.Column;
4+
import jakarta.persistence.Entity;
5+
import jakarta.persistence.EnumType;
6+
import jakarta.persistence.Enumerated;
7+
import jakarta.persistence.FetchType;
8+
import jakarta.persistence.GeneratedValue;
9+
import jakarta.persistence.GenerationType;
10+
import jakarta.persistence.Id;
11+
import jakarta.persistence.JoinColumn;
12+
import jakarta.persistence.ManyToOne;
13+
import jakarta.persistence.Table;
14+
import jakarta.persistence.UniqueConstraint;
15+
import lombok.AccessLevel;
16+
import lombok.Getter;
17+
import lombok.NoArgsConstructor;
18+
19+
@Table(
20+
name = "cheer_tag",
21+
uniqueConstraints = @UniqueConstraint(name = "uk_cheer_tag_cheer_id_name", columnNames = {"cheer_id", "name"})
22+
)
23+
@Entity
24+
@Getter
25+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
26+
public class CheerTag {
27+
28+
@Id
29+
@GeneratedValue(strategy = GenerationType.IDENTITY)
30+
private Long id;
31+
32+
@ManyToOne(fetch = FetchType.LAZY)
33+
@JoinColumn(name = "cheer_id", nullable = false)
34+
private Cheer cheer;
35+
36+
@Enumerated(EnumType.STRING)
37+
@Column(nullable = false, length = 63)
38+
private CheerTagName name;
39+
40+
public CheerTag(Cheer cheer, CheerTagName name) {
41+
this.cheer = cheer;
42+
this.name = name;
43+
}
44+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package eatda.domain.cheer;
2+
3+
import lombok.Getter;
4+
5+
@Getter
6+
public enum CheerTagName {
7+
8+
OLD_STORE_MOOD(CheerTagCategory.MOOD, "노포 감성"),
9+
ENERGETIC(CheerTagCategory.MOOD, "활기찬"),
10+
GOOD_FOR_DATING(CheerTagCategory.MOOD, "데이트하기좋은"),
11+
QUIET(CheerTagCategory.MOOD, "조용한"),
12+
GOOD_FOR_DRINKING(CheerTagCategory.MOOD, "술 땡기는"),
13+
INSTAGRAMMABLE(CheerTagCategory.MOOD, "인스타 감성"),
14+
GOOD_FOR_FAMILY(CheerTagCategory.MOOD, "부모님과 가기 좋은"),
15+
YOUTUBE_FAMOUS(CheerTagCategory.MOOD, "유튜버 맛집"),
16+
17+
GROUP_RESERVATION(CheerTagCategory.PRACTICAL, "단체 예약"),
18+
LARGE_PARKING(CheerTagCategory.PRACTICAL, "넓은 주차장"),
19+
CLEAN_RESTROOM(CheerTagCategory.PRACTICAL, "깔끔한 화장실"),
20+
PET_FRIENDLY(CheerTagCategory.PRACTICAL, "반려동물 동반 가능"),
21+
LATE_NIGHT(CheerTagCategory.PRACTICAL, "늦게까지 영업"),
22+
NEAR_SUBWAY(CheerTagCategory.PRACTICAL, "지하철과 가까운"),
23+
MANY_NEARBY_ATTRACTIONS(CheerTagCategory.PRACTICAL, "주변에 놀거리가 많은"),
24+
;
25+
26+
private final CheerTagCategory type;
27+
private final String displayName;
28+
29+
CheerTagName(CheerTagCategory type, String displayName) {
30+
this.type = type;
31+
this.displayName = displayName;
32+
}
33+
34+
public enum CheerTagCategory {
35+
MOOD, PRACTICAL
36+
}
37+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package eatda.domain.cheer;
2+
3+
import eatda.exception.BusinessErrorCode;
4+
import eatda.exception.BusinessException;
5+
import java.util.List;
6+
import java.util.stream.Collectors;
7+
8+
public class CheerTagNames {
9+
10+
private static final int MAX_CHEER_TAGS_PER_TYPE = 2;
11+
12+
private final List<CheerTagName> cheerTagNames;
13+
14+
public CheerTagNames(List<CheerTagName> cheerTagNames) {
15+
validate(cheerTagNames);
16+
this.cheerTagNames = List.copyOf(cheerTagNames);
17+
}
18+
19+
private void validate(List<CheerTagName> cheerTagNames) {
20+
if (isDuplicated(cheerTagNames)) {
21+
throw new BusinessException(BusinessErrorCode.CHEER_TAGS_DUPLICATED);
22+
}
23+
if (maxCountByType(cheerTagNames) > MAX_CHEER_TAGS_PER_TYPE) {
24+
throw new BusinessException(BusinessErrorCode.EXCEED_CHEER_TAGS_PER_TYPE);
25+
}
26+
}
27+
28+
private boolean isDuplicated(List<CheerTagName> cheerTagNames) {
29+
long distinctCount = cheerTagNames.stream()
30+
.distinct()
31+
.count();
32+
return distinctCount != cheerTagNames.size();
33+
}
34+
35+
private long maxCountByType(List<CheerTagName> cheerTagNames) {
36+
return cheerTagNames.stream()
37+
.collect(Collectors.groupingBy(CheerTagName::getType, Collectors.counting()))
38+
.values()
39+
.stream()
40+
.max(Long::compareTo)
41+
.orElse(0L);
42+
}
43+
44+
public List<CheerTag> toCheerTags(Cheer cheer) {
45+
return cheerTagNames.stream()
46+
.map(name -> new CheerTag(cheer, name))
47+
.toList();
48+
}
49+
}

src/main/java/eatda/exception/BusinessErrorCode.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ public enum BusinessErrorCode {
2929
FULL_CHEER_SIZE_PER_MEMBER("CHE003", "회원당 응원 한도가 넘었습니다."),
3030
ALREADY_CHEERED("CHE004", "이미 응원한 가게입니다."),
3131

32+
// CheerTag
33+
CHEER_TAGS_DUPLICATED("CHE_TAG001", "응원 태그는 중복될 수 없습니다."),
34+
EXCEED_CHEER_TAGS_PER_TYPE("CHE_TAG002", "각 응원 태그 타입당 최대 태그 개수를 초과했습니다."),
35+
3236
// Map
3337
MAP_SERVER_ERROR("MAP001", "지도 서버와의 통신 오류입니다.", HttpStatus.INTERNAL_SERVER_ERROR),
3438

@@ -53,7 +57,8 @@ public enum BusinessErrorCode {
5357
INVALID_STORE_ID("STY006", "유효하지 않은 가게 ID입니다."),
5458
INVALID_STORE_KAKAO_ID("STY007", "스토어 Kakao ID는 필수입니다."),
5559
INVALID_STORE_NAME("STY008", "스토어 이름은 필수입니다."),
56-
INVALID_STORE_ADDRESS("STY009", "스토어 주소는 필수입니다.");
60+
INVALID_STORE_ADDRESS("STY009", "스토어 주소는 필수입니다."),
61+
;
5762

5863
private final String code;
5964
private final String message;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
CREATE TABLE `cheer_tag`
2+
(
3+
`id` BIGINT NOT NULL AUTO_INCREMENT,
4+
`cheer_id` BIGINT NOT NULL,
5+
`name` VARCHAR(63) NOT NULL,
6+
PRIMARY KEY (`id`),
7+
FOREIGN KEY (`cheer_id`) REFERENCES `cheer` (`id`) ON DELETE CASCADE,
8+
UNIQUE KEY `uk_cheer_tag_cheer_id_name` (`cheer_id`, `name`)
9+
);
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
INSERT INTO cheer_tag (cheer_id, name)
2+
VALUES (1, 'GOOD_FOR_FAMILY'),
3+
(2, 'GOOD_FOR_DATING'),
4+
(2, 'INSTAGRAMMABLE'),
5+
(4, 'QUIET'),
6+
(5, 'INSTAGRAMMABLE'),
7+
(5, 'NEAR_SUBWAY'),
8+
(6, 'GOOD_FOR_DATING'),
9+
(6, 'INSTAGRAMMABLE'),
10+
(6, 'CLEAN_RESTROOM'),
11+
(6, 'MANY_NEARBY_ATTRACTIONS'),
12+
(7, 'LATE_NIGHT');
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
INSERT INTO cheer_tag (cheer_id, name)
2+
VALUES (1, 'GOOD_FOR_FAMILY'),
3+
(2, 'GOOD_FOR_DATING'),
4+
(2, 'INSTAGRAMMABLE'),
5+
(4, 'QUIET'),
6+
(5, 'INSTAGRAMMABLE'),
7+
(5, 'NEAR_SUBWAY'),
8+
(6, 'GOOD_FOR_DATING'),
9+
(6, 'INSTAGRAMMABLE'),
10+
(6, 'CLEAN_RESTROOM'),
11+
(6, 'MANY_NEARBY_ATTRACTIONS'),
12+
(7, 'LATE_NIGHT');
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-- Prod 환경 생략
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package eatda.domain.cheer;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
import static org.assertj.core.api.Assertions.assertThatCode;
5+
import static org.junit.jupiter.api.Assertions.assertThrows;
6+
7+
import eatda.exception.BusinessErrorCode;
8+
import eatda.exception.BusinessException;
9+
import java.util.Collections;
10+
import java.util.List;
11+
import org.junit.jupiter.api.Nested;
12+
import org.junit.jupiter.api.Test;
13+
14+
class CheerTagNamesTest {
15+
16+
@Nested
17+
class Validate {
18+
19+
@Test
20+
void 각_카테고리별_태그는_최대_개수가_정해져있다() {
21+
List<CheerTagName> tagNames = List.of(
22+
CheerTagName.OLD_STORE_MOOD, CheerTagName.ENERGETIC,
23+
CheerTagName.GROUP_RESERVATION, CheerTagName.LARGE_PARKING);
24+
25+
assertThatCode(() -> new CheerTagNames(tagNames)).doesNotThrowAnyException();
26+
}
27+
28+
@Test
29+
void 태그_이름은_비어있을_수_있다() {
30+
List<CheerTagName> tagNames = Collections.emptyList();
31+
32+
assertThatCode(() -> new CheerTagNames(tagNames)).doesNotThrowAnyException();
33+
}
34+
35+
@Test
36+
void 카테고리별_태그는_최대_개수를_초과할_수_없다() {
37+
List<CheerTagName> tagNames = List.of(
38+
CheerTagName.OLD_STORE_MOOD, CheerTagName.ENERGETIC, CheerTagName.GOOD_FOR_DATING);
39+
40+
BusinessException exception = assertThrows(BusinessException.class, () -> new CheerTagNames(tagNames));
41+
42+
assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.EXCEED_CHEER_TAGS_PER_TYPE);
43+
}
44+
45+
@Test
46+
void 태그_이름은_중복될_수_없다() {
47+
List<CheerTagName> tagNames = List.of(CheerTagName.OLD_STORE_MOOD, CheerTagName.OLD_STORE_MOOD);
48+
49+
BusinessException exception = assertThrows(BusinessException.class, () -> new CheerTagNames(tagNames));
50+
51+
assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.CHEER_TAGS_DUPLICATED);
52+
}
53+
}
54+
}

0 commit comments

Comments
 (0)