Repository: sopt-makers/sopt-crew-backend
Pull request #643: feat: 번쩍 개설 및 수정 시 모임 키워드 등록 기능 구현 완료
Author: login: move-hoon
Labels: name: 🎁 feature
description: 새로운 기능
name: size/L
Created: 2025-04-19T14:42:08Z | Merged: 2025-04-20T07:49:11Z

## 👩‍💻 Contents

<!-- 작업 내용을 적어주세요 -->
- 번쩍 개설 및 수정 시 모임 키워드 등록 기능을 구현하였습니다.
- 현재 dev 전용 스키마에 `welcomeMessageTypes`를 jsonb 타입으로 추가한 상태입니다. (local 스키마가 없는 관계로...)
- 따라서, dev서버에서 번쩍 개설 및 수정 시 requestDTO의 meetingKeywordTypes이 null로 넘어와 작동하지 않는게 정책에는 맞지만 dev서버에서 작동하도록 하기위해 현재 null을 허용해둔 상황입니다!!

## 📝 Review Note

<!-- PR과정에서 든 생각이나 개선할 내용이 있다면 적어주세요. -->

### 모임 키워드 포함 번쩍 개설 정상 작동 확인
![image](https://github.com/user-attachments/assets/29bbb1ca-d6bb-4f71-9b07-7c7c3c86812d)

### 모임 키워드 포함 번쩍 수정 정상 작동 확인
![image](https://github.com/user-attachments/assets/db03ce8f-473b-47e8-b2e0-3dc599406ef3)

### 모임 키워드 개수가 1~2개가 아니면 예외처리 확인
#### 빈 리스트
<img width="1267" alt="image" src="https://github.com/user-attachments/assets/828a7eba-2e39-467c-975c-2e10fe7eece6" />

#### 키워드 3개
<img width="1273" alt="image" src="https://github.com/user-attachments/assets/cdd7396f-8ac0-46d7-a80b-ac4508b66b65" />

#### 존재하지 않는 키워드
<img width="1266" alt="image" src="https://github.com/user-attachments/assets/02817629-303b-4075-a8d0-0e62693a71d8" />


## 📣 Related Issue

<!-- 관련 이슈를 적어주세요. -->

- closed #642 

## ✅ 점검사항

- [ ] docker-compose.yml 파일에 마이그레이션 한 API의 포워딩을 변경해줬나요?
- [ ] Spring Secret 값을 수정하거나 추가했다면 Github Secret에서 수정을 해줬나요?
- [ ] Nestjs Secret 값을 수정하거나 추가했다면 Docker-Compose.yml 파일 및 인스턴스 내부의 .env 파일을 수정했나요?

Linked issue #642: feat: 번쩍 개설 및 수정 시 모임 키워드 등록 기능 추가
## Feature Issue 📌
<!-- 해야하는 일과 이 일을 해야하는 이유를 적어주세요 -->
- 번쩍 개설 및 수정 시 모임 키워드 등록 기능을 추가합니다.

<br>

## Todo ✔️

- [x]  번쩍 개설 시 모임 키워드 등록 기능 추가
- [x]  번쩍 수정 시 모임 키워드 등록 기능 추가


Diff:
diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/tag/Tag.java b/main/src/main/java/org/sopt/makers/crew/main/entity/tag/Tag.java
index 1f008909..15d91cc1 100644
--- a/main/src/main/java/org/sopt/makers/crew/main/entity/tag/Tag.java
+++ b/main/src/main/java/org/sopt/makers/crew/main/entity/tag/Tag.java
@@ -4,6 +4,7 @@
 
 import org.hibernate.annotations.Type;
 import org.sopt.makers.crew.main.entity.common.BaseTimeEntity;
+import org.sopt.makers.crew.main.entity.tag.enums.MeetingKeywordType;
 import org.sopt.makers.crew.main.entity.tag.enums.TagType;
 import org.sopt.makers.crew.main.entity.tag.enums.WelcomeMessageType;
 
@@ -17,6 +18,7 @@
 import jakarta.persistence.Id;
 import jakarta.persistence.Table;
 import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
 import lombok.AccessLevel;
 import lombok.Builder;
 import lombok.Getter;
@@ -52,35 +54,51 @@ public class Tag extends BaseTimeEntity {
 	@Type(JsonBinaryType.class)
 	private List<WelcomeMessageType> welcomeMessageTypes;
 
+	@Column(name = "meetingKeywordTypes", columnDefinition = "jsonb")
+	@Type(JsonBinaryType.class)
+	@Size(min = 1, max = 2)
+	private List<MeetingKeywordType> meetingKeywordTypes;
+
 	@Builder
-	private Tag(TagType tagType, Integer meetingId, Integer flashId, List<WelcomeMessageType> welcomeMessageTypes) {
+	private Tag(TagType tagType, Integer meetingId, Integer flashId,
+		List<WelcomeMessageType> welcomeMessageTypes,
+		List<MeetingKeywordType> meetingKeywordTypes) {
 		this.tagType = tagType;
 		this.meetingId = meetingId;
 		this.flashId = flashId;
 		this.welcomeMessageTypes = welcomeMessageTypes;
+		this.meetingKeywordTypes = meetingKeywordTypes;
 	}
 
 	public static Tag createGeneralMeetingTag(TagType tagType, Integer meetingId,
-		List<WelcomeMessageType> welcomeMessageTypes) {
+		List<WelcomeMessageType> welcomeMessageTypes,
+		List<MeetingKeywordType> meetingKeywordTypes) {
 		return Tag.builder()
 			.tagType(tagType)
 			.meetingId(meetingId)
 			.flashId(null)
 			.welcomeMessageTypes(welcomeMessageTypes)
+			.meetingKeywordTypes(meetingKeywordTypes)
 			.build();
 	}
 
 	public static Tag createFlashMeetingTag(TagType tagType, Integer flashId,
-		List<WelcomeMessageType> welcomeMessageTypes) {
+		List<WelcomeMessageType> welcomeMessageTypes,
+		List<MeetingKeywordType> meetingKeywordTypes) {
 		return Tag.builder()
 			.tagType(tagType)
 			.meetingId(null)
 			.flashId(flashId)
 			.welcomeMessageTypes(welcomeMessageTypes)
+			.meetingKeywordTypes(meetingKeywordTypes)
 			.build();
 	}
 
 	public void updateWelcomeMessageTypes(List<WelcomeMessageType> newWelcomeMessageTypes) {
 		this.welcomeMessageTypes = newWelcomeMessageTypes;
 	}
+
+	public void updateMeetingKeywordTypeEnums(List<MeetingKeywordType> newMeetingKeywordTypes) {
+		this.meetingKeywordTypes = newMeetingKeywordTypes;
+	}
 }
diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/tag/enums/MeetingKeywordType.java b/main/src/main/java/org/sopt/makers/crew/main/entity/tag/enums/MeetingKeywordType.java
new file mode 100644
index 00000000..f3892120
--- /dev/null
+++ b/main/src/main/java/org/sopt/makers/crew/main/entity/tag/enums/MeetingKeywordType.java
@@ -0,0 +1,30 @@
+package org.sopt.makers.crew.main.entity.tag.enums;
+
+import org.sopt.makers.crew.main.global.exception.BadRequestException;
+import org.sopt.makers.crew.main.global.exception.ErrorStatus;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@RequiredArgsConstructor
+public enum MeetingKeywordType {
+	EXERCISE("운동"),
+	FOOD("먹방"),
+	HOBBY("취미"),
+	STUDY("학습"),
+	SELF_DEVELOPMENT("자기개발"),
+	NETWORKING("네트워킹"),
+	ETC("기타");
+
+	private final String value;
+
+	public static MeetingKeywordType ofValue(String dbData) {
+		for (MeetingKeywordType type : MeetingKeywordType.values()) {
+			if (type.getValue().equals(dbData)) {
+				return type;
+			}
+		}
+		throw new BadRequestException(ErrorStatus.INVALID_MEETING_KEYWORD_TYPE.getErrorCode());
+	}
+}
diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/tag/enums/WelcomeMessageType.java b/main/src/main/java/org/sopt/makers/crew/main/entity/tag/enums/WelcomeMessageType.java
index 209a194e..82f980b1 100644
--- a/main/src/main/java/org/sopt/makers/crew/main/entity/tag/enums/WelcomeMessageType.java
+++ b/main/src/main/java/org/sopt/makers/crew/main/entity/tag/enums/WelcomeMessageType.java
@@ -1,8 +1,13 @@
 package org.sopt.makers.crew.main.entity.tag.enums;
 
+import org.sopt.makers.crew.main.global.exception.BadRequestException;
+import org.sopt.makers.crew.main.global.exception.ErrorStatus;
+
 import lombok.Getter;
+import lombok.RequiredArgsConstructor;
 
 @Getter
+@RequiredArgsConstructor
 public enum WelcomeMessageType {
 	YB_WELCOME("YB 환영"),
 	OB_WELCOME("OB 환영"),
@@ -12,16 +17,12 @@ public enum WelcomeMessageType {
 
 	private final String value;
 
-	WelcomeMessageType(String value) {
-		this.value = value;
-	}
-
 	public static WelcomeMessageType ofValue(String dbData) {
 		for (WelcomeMessageType type : WelcomeMessageType.values()) {
 			if (type.getValue().equals(dbData)) {
 				return type;
 			}
 		}
-		throw new IllegalArgumentException("Invalid WelcomeMessageType value: " + dbData);
+		throw new BadRequestException(ErrorStatus.INVALID_WELCOME_MESSAGE_TYPE.getErrorCode());
 	}
 }
diff --git a/main/src/main/java/org/sopt/makers/crew/main/flash/v2/FlashV2Api.java b/main/src/main/java/org/sopt/makers/crew/main/flash/v2/FlashV2Api.java
index 77c1b7ce..1a28b0da 100644
--- a/main/src/main/java/org/sopt/makers/crew/main/flash/v2/FlashV2Api.java
+++ b/main/src/main/java/org/sopt/makers/crew/main/flash/v2/FlashV2Api.java
@@ -21,7 +21,7 @@ public interface FlashV2Api {
 
 	@Operation(summary = "번쩍 모임 생성")
 	@ApiResponses(value = {
-		@ApiResponse(responseCode = "201", description = "meetingId: 10"),
+		@ApiResponse(responseCode = "201", description = "meetingId: 10, tagId: 4"),
 		@ApiResponse(responseCode = "400", description = "VALIDATION_EXCEPTION", content = @Content),
 	})
 	ResponseEntity<FlashV2CreateAndUpdateResponseDto> createFlash(
@@ -39,7 +39,7 @@ ResponseEntity<FlashV2GetFlashByMeetingIdResponseDto> getFlashByMeetingId(
 
 	@Operation(summary = "번쩍 모임 수정")
 	@ApiResponses(value = {
-		@ApiResponse(responseCode = "200", description = "meetingId: 10"),
+		@ApiResponse(responseCode = "200", description = "meetingId: 10, tagId: 5"),
 		@ApiResponse(responseCode = "400", description = "VALIDATION_EXCEPTION", content = @Content),
 	})
 	ResponseEntity<FlashV2CreateAndUpdateResponseDto> updateFlash(
diff --git a/main/src/main/java/org/sopt/makers/crew/main/flash/v2/dto/request/FlashV2CreateAndUpdateFlashBodyDto.java b/main/src/main/java/org/sopt/makers/crew/main/flash/v2/dto/request/FlashV2CreateAndUpdateFlashBodyDto.java
index 88b0f756..fe11d819 100644
--- a/main/src/main/java/org/sopt/makers/crew/main/flash/v2/dto/request/FlashV2CreateAndUpdateFlashBodyDto.java
+++ b/main/src/main/java/org/sopt/makers/crew/main/flash/v2/dto/request/FlashV2CreateAndUpdateFlashBodyDto.java
@@ -5,6 +5,7 @@
 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.Valid;
 import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
 
 @Schema(name = "FlashV2CreateAndUpdateFlashBodyDto", description = "번쩍 모임 생성 및 수정 request body dto")
 public record FlashV2CreateAndUpdateFlashBodyDto(
@@ -14,6 +15,10 @@ public record FlashV2CreateAndUpdateFlashBodyDto(
 	FlashV2CreateAndUpdateFlashBodyWithoutWelcomeMessageDto flashBody,
 
 	@Schema(example = "[\"YB 환영\", \"OB 환영\"]", description = "환영 메시지 타입 리스트")
-	List<String> welcomeMessageTypes
+	List<String> welcomeMessageTypes,
+
+	@Schema(example = "[\"운동\", \"자기개발\"]", description = "모임 키워드 타입 리스트")
+	@Size(min = 1, max = 2)
+	List<String> meetingKeywordTypes
 ) {
 }
diff --git a/main/src/main/java/org/sopt/makers/crew/main/flash/v2/dto/response/FlashV2CreateAndUpdateResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/flash/v2/dto/response/FlashV2CreateAndUpdateResponseDto.java
index dd1e65d1..86da51f4 100644
--- a/main/src/main/java/org/sopt/makers/crew/main/flash/v2/dto/response/FlashV2CreateAndUpdateResponseDto.java
+++ b/main/src/main/java/org/sopt/makers/crew/main/flash/v2/dto/response/FlashV2CreateAndUpdateResponseDto.java
@@ -7,9 +7,13 @@
 public record FlashV2CreateAndUpdateResponseDto(
 	@Schema(description = "모임 id - 번쩍 카테고리", example = "1")
 	@NotNull
-	Integer meetingId
+	Integer meetingId,
+
+	@Schema(description = "태그 id - 번쩍 카테고리", example = "1")
+	@NotNull
+	Integer tagId
 ) {
-	public static FlashV2CreateAndUpdateResponseDto from(Integer meetingId) {
-		return new FlashV2CreateAndUpdateResponseDto(meetingId);
+	public static FlashV2CreateAndUpdateResponseDto of(Integer meetingId, Integer tagId) {
+		return new FlashV2CreateAndUpdateResponseDto(meetingId, tagId);
 	}
 }
diff --git a/main/src/main/java/org/sopt/makers/crew/main/flash/v2/service/FlashV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/flash/v2/service/FlashV2ServiceImpl.java
index ad2c23d5..9fdbf4b2 100644
--- a/main/src/main/java/org/sopt/makers/crew/main/flash/v2/service/FlashV2ServiceImpl.java
+++ b/main/src/main/java/org/sopt/makers/crew/main/flash/v2/service/FlashV2ServiceImpl.java
@@ -33,6 +33,7 @@
 import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingLeaderUserIdDto;
 import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2CreateAndUpdateMeetingForFlashResponseDto;
 import org.sopt.makers.crew.main.meeting.v2.service.MeetingV2Service;
+import org.sopt.makers.crew.main.tag.v2.dto.response.TagV2CreateAndUpdateFlashTagResponseDto;
 import org.sopt.makers.crew.main.tag.v2.service.TagV2Service;
 import org.sopt.makers.crew.main.user.v2.service.UserV2Service;
 import org.springframework.cache.annotation.CacheEvict;
@@ -85,14 +86,16 @@ public FlashV2CreateAndUpdateResponseDto createFlash(
 			activeGenerationProvider.getActiveGeneration(), user.getId(), realTime);
 
 		flashRepository.save(flash);
-		tagV2Service.createFlashTag(requestBody.welcomeMessageTypes(), flash.getId());
+		TagV2CreateAndUpdateFlashTagResponseDto tagResponseDto = tagV2Service.createFlashTag(
+			requestBody.welcomeMessageTypes(), requestBody.meetingKeywordTypes(),
+			flash.getId());
 
 		OrgIdListDto orgIdListDto = userReader.findAllOrgIds();
 
 		eventPublisher.publishEvent(
 			new FlashCreatedEventDto(orgIdListDto.getOrgIds(), flash.getMeetingId(), flash.getTitle()));
 
-		return FlashV2CreateAndUpdateResponseDto.from(flash.getMeetingId());
+		return FlashV2CreateAndUpdateResponseDto.of(flash.getMeetingId(), tagResponseDto.tagId());
 	}
 
 	public FlashV2GetFlashByMeetingIdResponseDto getFlashDetail(Integer meetingId, Integer userId) {
@@ -149,9 +152,11 @@ public FlashV2CreateAndUpdateResponseDto updateFlash(Integer meetingId,
 
 		flash.updateFlash(updatedFlash);
 
-		tagV2Service.updateFlashTag(requestBody.welcomeMessageTypes(), flash.getId());
+		TagV2CreateAndUpdateFlashTagResponseDto tagResponseDto = tagV2Service.updateFlashTag(
+			requestBody.welcomeMessageTypes(), requestBody.meetingKeywordTypes(),
+			flash.getId());
 
-		return FlashV2CreateAndUpdateResponseDto.from(flash.getMeetingId());
+		return FlashV2CreateAndUpdateResponseDto.of(flash.getMeetingId(), tagResponseDto.tagId());
 	}
 
 	private List<ApplyWholeInfoDto> getApplyWholeInfoDtos(Applies applies, Integer meetingId, Integer userId) {
diff --git a/main/src/main/java/org/sopt/makers/crew/main/global/exception/ErrorStatus.java b/main/src/main/java/org/sopt/makers/crew/main/global/exception/ErrorStatus.java
index 3fc526c6..a10aedd0 100644
--- a/main/src/main/java/org/sopt/makers/crew/main/global/exception/ErrorStatus.java
+++ b/main/src/main/java/org/sopt/makers/crew/main/global/exception/ErrorStatus.java
@@ -34,6 +34,9 @@ public enum ErrorStatus {
 	LEADER_CANNOT_BE_CO_LEADER_APPLY("모임장은 공동 모임장이 될 수 없습니다."),
 	NOT_ALLOW_MEETING_APPLY("허용되지 않는 모임 신청입니다."),
 	IO_EXCEPTION("파일 입출력 오류가 발생했습니다."),
+	INVALID_WELCOME_MESSAGE_TYPE("유효하지 않은 환영 메시지 타입입니다."),
+	INVALID_MEETING_KEYWORD_TYPE("유효하지 않은 모임 키워드 타입입니다."),
+	INVALID_MEETING_KEYWORD_SIZE("모임 키워드는 최소 1개 이상, 최대 2개까지 선택해야 합니다."),
 
 	/**
 	 * 400 BAD_REQUEST - 유효성 검사 관련 에러
diff --git a/main/src/main/java/org/sopt/makers/crew/main/tag/v2/dto/response/TagV2CreateAndUpdateFlashTagResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/tag/v2/dto/response/TagV2CreateAndUpdateFlashTagResponseDto.java
new file mode 100644
index 00000000..4476eda0
--- /dev/null
+++ b/main/src/main/java/org/sopt/makers/crew/main/tag/v2/dto/response/TagV2CreateAndUpdateFlashTagResponseDto.java
@@ -0,0 +1,15 @@
+package org.sopt.makers.crew.main.tag.v2.dto.response;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+
+@Schema(name = "TagV2CreateAndUpdateFlashTagResponseDto", description = "번쩍 모임 태그 생성 및 수정 응답 Dto")
+public record TagV2CreateAndUpdateFlashTagResponseDto(
+	@Schema(description = "모임 태그 id", example = "1")
+	@NotNull
+	Integer tagId
+) {
+	public static TagV2CreateAndUpdateFlashTagResponseDto from(Integer tagId) {
+		return new TagV2CreateAndUpdateFlashTagResponseDto(tagId);
+	}
+}
diff --git a/main/src/main/java/org/sopt/makers/crew/main/tag/v2/dto/response/TagV2CreateFlashTagResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/tag/v2/dto/response/TagV2CreateFlashTagResponseDto.java
deleted file mode 100644
index 2d731a78..00000000
--- a/main/src/main/java/org/sopt/makers/crew/main/tag/v2/dto/response/TagV2CreateFlashTagResponseDto.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.sopt.makers.crew.main.tag.v2.dto.response;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import jakarta.validation.constraints.NotNull;
-
-@Schema(name = "TagV2CreateFlashTagResponseDto", description = "번쩍 모임 태그 생성 응답 Dto")
-public record TagV2CreateFlashTagResponseDto(
-	@Schema(description = "모임 태그 id", example = "1")
-	@NotNull
-	Integer tagId
-) {
-	public static TagV2CreateFlashTagResponseDto from(Integer tagId) {
-		return new TagV2CreateFlashTagResponseDto(tagId);
-	}
-}
diff --git a/main/src/main/java/org/sopt/makers/crew/main/tag/v2/service/TagV2Service.java b/main/src/main/java/org/sopt/makers/crew/main/tag/v2/service/TagV2Service.java
index 26eef4d9..d83bc9dc 100644
--- a/main/src/main/java/org/sopt/makers/crew/main/tag/v2/service/TagV2Service.java
+++ b/main/src/main/java/org/sopt/makers/crew/main/tag/v2/service/TagV2Service.java
@@ -3,12 +3,16 @@
 import java.util.List;
 
 import org.sopt.makers.crew.main.entity.tag.enums.WelcomeMessageType;
-import org.sopt.makers.crew.main.tag.v2.dto.response.TagV2CreateFlashTagResponseDto;
+import org.sopt.makers.crew.main.tag.v2.dto.response.TagV2CreateAndUpdateFlashTagResponseDto;
 
 public interface TagV2Service {
-	TagV2CreateFlashTagResponseDto createFlashTag(List<String> welcomeMessageTypes, Integer flashId);
+	TagV2CreateAndUpdateFlashTagResponseDto createFlashTag(List<String> welcomeMessageTypes,
+		List<String> meetingKeywordTypes,
+		Integer flashId);
 
 	List<WelcomeMessageType> getWelcomeMessageTypesByFlashId(Integer flashId);
 
-	TagV2CreateFlashTagResponseDto updateFlashTag(List<String> welcomeMessageTypes, Integer flashId);
+	TagV2CreateAndUpdateFlashTagResponseDto updateFlashTag(List<String> welcomeMessageTypes,
+		List<String> meetingKeywordTypes,
+		Integer flashId);
 }
diff --git a/main/src/main/java/org/sopt/makers/crew/main/tag/v2/service/TagV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/tag/v2/service/TagV2ServiceImpl.java
index 4168b5c6..0e390c90 100644
--- a/main/src/main/java/org/sopt/makers/crew/main/tag/v2/service/TagV2ServiceImpl.java
+++ b/main/src/main/java/org/sopt/makers/crew/main/tag/v2/service/TagV2ServiceImpl.java
@@ -4,16 +4,16 @@
 
 import java.util.Collections;
 import java.util.List;
-import java.util.Optional;
 
 import org.sopt.makers.crew.main.entity.tag.Tag;
 import org.sopt.makers.crew.main.entity.tag.TagRepository;
 import org.sopt.makers.crew.main.entity.tag.WelcomeMessageTypeProjection;
+import org.sopt.makers.crew.main.entity.tag.enums.MeetingKeywordType;
 import org.sopt.makers.crew.main.entity.tag.enums.TagType;
 import org.sopt.makers.crew.main.entity.tag.enums.WelcomeMessageType;
 import org.sopt.makers.crew.main.global.exception.BadRequestException;
 import org.sopt.makers.crew.main.global.exception.NotFoundException;
-import org.sopt.makers.crew.main.tag.v2.dto.response.TagV2CreateFlashTagResponseDto;
+import org.sopt.makers.crew.main.tag.v2.dto.response.TagV2CreateAndUpdateFlashTagResponseDto;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -24,28 +24,24 @@
 @Transactional(readOnly = true)
 public class TagV2ServiceImpl implements TagV2Service {
 
+	private static final int MAX_MEETING_KEYWORD_SIZE = 2;
 	private final TagRepository tagRepository;
 
 	// 여기에 createGeneralMeetingTag 메서드도 추가하면 될 것 같습니다 나중에! (추후 일반 모임에 태그 추가 시 작성)
 
 	@Override
 	@Transactional
-	public TagV2CreateFlashTagResponseDto createFlashTag(List<String> welcomeMessageTypes,
+	public TagV2CreateAndUpdateFlashTagResponseDto createFlashTag(List<String> welcomeMessageTypes,
+		List<String> meetingKeywordTypes,
 		Integer flashId) {
-
 		if (flashId == null) {
 			throw new BadRequestException(VALIDATION_EXCEPTION.getErrorCode());
 		}
 
-		if (welcomeMessageTypes == null || welcomeMessageTypes.isEmpty()) {
-			return saveTag(flashId, List.of());
-		}
+		List<WelcomeMessageType> welcomeMessageTypeEnums = toWelcomeMessageTypes(welcomeMessageTypes);
+		List<MeetingKeywordType> meetingKeywordTypeEnums = toMeetingKeywordTypes(meetingKeywordTypes);
 
-		List<WelcomeMessageType> welcomeMessageTypeEnums = welcomeMessageTypes.stream()
-			.map(WelcomeMessageType::ofValue)
-			.toList();
-
-		return saveTag(flashId, welcomeMessageTypeEnums);
+		return saveTag(flashId, welcomeMessageTypeEnums, meetingKeywordTypeEnums);
 	}
 
 	@Override
@@ -61,27 +57,44 @@ public List<WelcomeMessageType> getWelcomeMessageTypesByFlashId(Integer flashId)
 
 	@Override
 	@Transactional
-	public TagV2CreateFlashTagResponseDto updateFlashTag(List<String> welcomeMessageTypes, Integer flashId) {
+	public TagV2CreateAndUpdateFlashTagResponseDto updateFlashTag(List<String> welcomeMessageTypes,
+		List<String> meetingKeywordTypes,
+		Integer flashId) {
 		Tag tag = tagRepository.findTagByFlashId(flashId)
 			.orElseThrow(() -> new NotFoundException(NOT_FOUND_TAG.getErrorCode()));
 
-		List<WelcomeMessageType> welcomeMessageTypeEnums = convertToWelcomeMessageTypeList(welcomeMessageTypes);
+		List<WelcomeMessageType> welcomeMessageTypeEnums = toWelcomeMessageTypes(welcomeMessageTypes);
+		List<MeetingKeywordType> meetingKeywordTypeEnums = toMeetingKeywordTypes(meetingKeywordTypes);
 
 		tag.updateWelcomeMessageTypes(welcomeMessageTypeEnums);
+		tag.updateMeetingKeywordTypeEnums(meetingKeywordTypeEnums);
 
-		return TagV2CreateFlashTagResponseDto.from(tag.getId());
+		return TagV2CreateAndUpdateFlashTagResponseDto.from(tag.getId());
 	}
 
-	private TagV2CreateFlashTagResponseDto saveTag(Integer flashId, List<WelcomeMessageType> welcomeMessageTypeEnums) {
-		Tag tag = Tag.createFlashMeetingTag(TagType.FLASH, flashId, welcomeMessageTypeEnums);
+	private TagV2CreateAndUpdateFlashTagResponseDto saveTag(Integer flashId,
+		List<WelcomeMessageType> welcomeMessageTypes,
+		List<MeetingKeywordType> meetingKeywordTypes) {
+		Tag tag = Tag.createFlashMeetingTag(TagType.FLASH, flashId, welcomeMessageTypes, meetingKeywordTypes);
 		tagRepository.save(tag);
-		return TagV2CreateFlashTagResponseDto.from(tag.getId());
+		return TagV2CreateAndUpdateFlashTagResponseDto.from(tag.getId());
 	}
 
-	private List<WelcomeMessageType> convertToWelcomeMessageTypeList(List<String> welcomeMessageTypes) {
-		return Optional.ofNullable(welcomeMessageTypes)
-			.filter(list -> !list.isEmpty())
-			.map(list -> list.stream().map(WelcomeMessageType::ofValue).toList())
-			.orElse(Collections.emptyList());
+	private List<WelcomeMessageType> toWelcomeMessageTypes(List<String> values) {
+		if (values == null || values.isEmpty()) {
+			return List.of();
+		}
+		return values.stream()
+			.map(WelcomeMessageType::ofValue)
+			.toList();
+	}
+
+	private List<MeetingKeywordType> toMeetingKeywordTypes(List<String> values) {
+		if (values == null || values.isEmpty() || values.size() > MAX_MEETING_KEYWORD_SIZE) {
+			throw new BadRequestException(INVALID_MEETING_KEYWORD_SIZE.getErrorCode());
+		}
+		return values.stream()
+			.map(MeetingKeywordType::ofValue)
+			.toList();
 	}
 }


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (4)</summary><blockquote>

<details>
<summary>main/src/main/java/org/sopt/makers/crew/main/tag/v2/dto/response/TagV2CreateAndUpdateFlashTagResponseDto.java (1)</summary><blockquote>

`6-7`: **Swagger 스키마 이름과 클래스 이름 불일치 수정 필요**

클래스 이름은 `TagV2CreateAndUpdateFlashTagResponseDto`로 변경되었지만, Swagger 스키마 이름은 여전히 `TagV2CreateFlashTagResponseDto`로 남아있습니다. 클래스 이름 변경에 맞게 Schema 애노테이션도 업데이트해야 합니다.

```diff
-@Schema(name = "TagV2CreateFlashTagResponseDto", description = "번쩍 모임 태그 생성 응답 Dto")
+@Schema(name = "TagV2CreateAndUpdateFlashTagResponseDto", description = "번쩍 모임 태그 생성 및 수정 응답 Dto")
```

</blockquote></details>
<details>
<summary>main/src/main/java/org/sopt/makers/crew/main/entity/tag/enums/MeetingKeywordType.java (1)</summary><blockquote>

`22-29`: **ofValue 메서드의 에러 메시지 개선 필요**

유효하지 않은 값에 대한 예외 처리 시, 어떤 값이 유효하지 않았는지 포함하면 디버깅에 더 도움이 될 것입니다.

```diff
-		throw new BadRequestException(ErrorStatus.INVALID_MEETING_KEYWORD_TYPE.getErrorCode());
+		throw new BadRequestException(ErrorStatus.INVALID_MEETING_KEYWORD_TYPE.getErrorCode() + ": '" + dbData + "'");
```

또한, 모든 가능한 값을 메시지에 포함하는 것도 고려해 볼 수 있습니다. 이렇게 하면 API 사용자가 유효한 값을 쉽게 파악할 수 있습니다.

</blockquote></details>
<details>
<summary>main/src/main/java/org/sopt/makers/crew/main/tag/v2/service/TagV2ServiceImpl.java (2)</summary><blockquote>

`41-42`: **메서드 실행 순서 일관성 개선이 필요합니다.**

createFlashTag 메서드에서는 meetingKeywordTypes를 먼저 변환하고, updateFlashTag 메서드(66-67라인)에서는 welcomeMessageTypes를 먼저 변환하고 있습니다. 코드 일관성을 위해 두 메서드에서 동일한 순서로 변환하는 것이 좋습니다.

```diff
- List<MeetingKeywordType> meetingKeywordTypeEnums = toMeetingKeywordTypes(meetingKeywordTypes);
- List<WelcomeMessageType> welcomeMessageTypeEnums = toWelcomeMessageTypes(welcomeMessageTypes);
+ List<WelcomeMessageType> welcomeMessageTypeEnums = toWelcomeMessageTypes(welcomeMessageTypes);
+ List<MeetingKeywordType> meetingKeywordTypeEnums = toMeetingKeywordTypes(meetingKeywordTypes);
```

---

`66-67`: **변환 메서드 호출 순서 확인.**

createFlashTag 메서드와는 다른 순서로 변환 메서드를 호출하고 있습니다. 앞서 언급한 대로 일관성을 위해 동일한 순서로 호출하는 것이 좋습니다.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 8f23f38f75ede089796bbb49eb420c60d5472237 and d7e4dd505f3111785ca05fa938173e7f7faff80b.

</details>

<details>
<summary>📒 Files selected for processing (11)</summary>

* `main/src/main/java/org/sopt/makers/crew/main/entity/tag/Tag.java` (3 hunks)
* `main/src/main/java/org/sopt/makers/crew/main/entity/tag/enums/MeetingKeywordType.java` (1 hunks)
* `main/src/main/java/org/sopt/makers/crew/main/entity/tag/enums/WelcomeMessageType.java` (2 hunks)
* `main/src/main/java/org/sopt/makers/crew/main/flash/v2/FlashV2Api.java` (2 hunks)
* `main/src/main/java/org/sopt/makers/crew/main/flash/v2/dto/request/FlashV2CreateAndUpdateFlashBodyDto.java` (2 hunks)
* `main/src/main/java/org/sopt/makers/crew/main/flash/v2/dto/response/FlashV2CreateAndUpdateResponseDto.java` (1 hunks)
* `main/src/main/java/org/sopt/makers/crew/main/flash/v2/service/FlashV2ServiceImpl.java` (3 hunks)
* `main/src/main/java/org/sopt/makers/crew/main/global/exception/ErrorStatus.java` (1 hunks)
* `main/src/main/java/org/sopt/makers/crew/main/tag/v2/dto/response/TagV2CreateAndUpdateFlashTagResponseDto.java` (1 hunks)
* `main/src/main/java/org/sopt/makers/crew/main/tag/v2/service/TagV2Service.java` (1 hunks)
* `main/src/main/java/org/sopt/makers/crew/main/tag/v2/service/TagV2ServiceImpl.java` (3 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (4)</summary>

<details>
<summary>main/src/main/java/org/sopt/makers/crew/main/entity/tag/enums/WelcomeMessageType.java (2)</summary><blockquote>

<details>
<summary>main/src/main/java/org/sopt/makers/crew/main/global/exception/BadRequestException.java (1)</summary>

* `BadRequestException` (5-14)

</details>
<details>
<summary>main/src/main/java/org/sopt/makers/crew/main/global/exception/BaseException.java (1)</summary>

* `Getter` (9-30)

</details>

</blockquote></details>
<details>
<summary>main/src/main/java/org/sopt/makers/crew/main/entity/tag/enums/MeetingKeywordType.java (1)</summary><blockquote>

<details>
<summary>main/src/main/java/org/sopt/makers/crew/main/global/exception/BadRequestException.java (1)</summary>

* `BadRequestException` (5-14)

</details>

</blockquote></details>
<details>
<summary>main/src/main/java/org/sopt/makers/crew/main/flash/v2/dto/request/FlashV2CreateAndUpdateFlashBodyDto.java (1)</summary><blockquote>

<details>
<summary>main/src/main/java/org/sopt/makers/crew/main/global/dto/MeetingResponseDto.java (1)</summary>

* `Schema` (17-97)

</details>

</blockquote></details>
<details>
<summary>main/src/main/java/org/sopt/makers/crew/main/tag/v2/service/TagV2ServiceImpl.java (2)</summary><blockquote>

<details>
<summary>main/src/main/java/org/sopt/makers/crew/main/global/exception/BadRequestException.java (1)</summary>

* `BadRequestException` (5-14)

</details>
<details>
<summary>main/src/main/java/org/sopt/makers/crew/main/global/exception/NotFoundException.java (1)</summary>

* `NotFoundException` (5-13)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (39)</summary><blockquote>

<details>
<summary>main/src/main/java/org/sopt/makers/crew/main/global/exception/ErrorStatus.java (1)</summary>

`37-39`: **에러 상태 코드 추가가 적절합니다.**

새로 추가된 에러 코드들(환영 메시지 타입, 모임 키워드 타입, 모임 키워드 크기)은 기능 요구사항에 맞게 잘 정의되었습니다. 특히 `INVALID_MEETING_KEYWORD_SIZE` 에러는 모임 키워드가 최소 1개 이상, 최대 2개까지 선택해야 한다는 비즈니스 규칙을 명확하게 표현하고 있습니다.

</details>
<details>
<summary>main/src/main/java/org/sopt/makers/crew/main/flash/v2/FlashV2Api.java (2)</summary>

`24-24`: **API 응답 문서화가 정확합니다.**

API 문서에 `tagId`를 추가하여 번쩍 모임 생성 응답에 태그 ID가 포함되는 것을 명확하게 표시했습니다. 이는 클라이언트 측에서 응답 구조를 이해하는 데 도움이 됩니다.

---

`42-42`: **API 응답 문서화가 일관성 있게 수정되었습니다.**

번쩍 모임 수정 API 응답에도 동일하게 `tagId`를 포함하도록 문서화하여 일관성을 유지했습니다. 이렇게 함으로써 개발자들이 API 응답 형식을 명확하게 이해할 수 있습니다.

</details>
<details>
<summary>main/src/main/java/org/sopt/makers/crew/main/entity/tag/enums/WelcomeMessageType.java (3)</summary>

`3-5`: **필요한 import가 올바르게 추가되었습니다.**

`BadRequestException`과 `ErrorStatus`를 import하여 예외 처리를 개선했습니다. 이는 더 구체적인 예외 처리를 위한 좋은 접근 방식입니다.

---

`7-7`: **Lombok을 활용한 코드 개선이 적절합니다.**

`@RequiredArgsConstructor` 어노테이션을 사용하여 명시적인 생성자 구현을 제거하고 코드를 간결하게 만들었습니다. Lombok의 활용은 보일러플레이트 코드를 줄이는 데 효과적입니다.



Also applies to: 10-10

---

`26-26`: **예외 처리가 향상되었습니다.**

일반적인 `IllegalArgumentException` 대신 `BadRequestException`을 사용하고 구체적인 에러 코드 `INVALID_WELCOME_MESSAGE_TYPE`를 전달함으로써 에러 처리가 더 명확해졌습니다. 이는 클라이언트에게 더 구체적인 피드백을 제공하는 데 도움이 됩니다.

</details>
<details>
<summary>main/src/main/java/org/sopt/makers/crew/main/flash/v2/dto/request/FlashV2CreateAndUpdateFlashBodyDto.java (3)</summary>

`8-8`: **필요한 import가 올바르게 추가되었습니다.**

`Size` 검증 제약조건을 위한 import가 추가되었습니다. 이는 `meetingKeywordTypes` 필드의 유효성 검사에 필요합니다.

---

`18-19`: **welcomeMessageTypes 필드 정의가 적절합니다.**

`welcomeMessageTypes` 필드가 적절하게 정의되었습니다. 필드 끝에 쉼표를 추가하여 새로운 필드를 추가할 준비가 되었습니다.

---

`20-22`: **모임 키워드 타입 필드 추가가 적절합니다.**

새로운 `meetingKeywordTypes` 필드가 추가되었고, `@Size(min = 1, max = 2)` 제약조건을 통해 최소 1개, 최대 2개의 모임 키워드를 선택해야 한다는 비즈니스 규칙이 적절하게 구현되었습니다. Swagger 문서도 예시와 함께 명확하게 작성되었습니다.

</details>
<details>
<summary>main/src/main/java/org/sopt/makers/crew/main/tag/v2/dto/response/TagV2CreateAndUpdateFlashTagResponseDto.java (1)</summary>

`12-13`: **정적 팩토리 메서드가 적절히 업데이트되었습니다**

클래스 이름 변경에 맞게 정적 팩토리 메서드의 반환 타입과 생성자 호출이 올바르게 업데이트되었습니다.

</details>
<details>
<summary>main/src/main/java/org/sopt/makers/crew/main/flash/v2/dto/response/FlashV2CreateAndUpdateResponseDto.java (2)</summary>

`10-15`: **태그 ID 필드가 적절히 추가되었습니다**

번쩍 모임 태그 ID를 응답에 포함시키기 위한 필드 추가가 적절히 이루어졌습니다. NotNull 애노테이션과 Schema 설명이 명확하게 제공되었습니다.

---

`16-18`: **정적 팩토리 메서드가 명확하게 개선되었습니다**

기존의 'from' 메서드에서 더 명확한 'of' 메서드로 변경되었고, 새로 추가된 tagId 파라미터를 포함하도록 적절히 수정되었습니다.

</details>
<details>
<summary>main/src/main/java/org/sopt/makers/crew/main/tag/v2/service/TagV2Service.java (3)</summary>

`5-6`: **DTO 임포트가 적절히 업데이트되었습니다**

응답 DTO 클래스 이름 변경에 맞게 임포트 문이 올바르게 업데이트되었습니다.

---

`9-11`: **createFlashTag 메서드가 모임 키워드 타입을 지원하도록 확장되었습니다**

새로운 기능 요구사항에 맞게 meetingKeywordTypes 파라미터가 추가되었고, 반환 타입이 적절히 업데이트되었습니다.

---

`15-17`: **updateFlashTag 메서드가 모임 키워드 타입을 지원하도록 확장되었습니다**

createFlashTag와 마찬가지로 updateFlashTag 메서드도 meetingKeywordTypes 파라미터를 추가하여 일관성 있게 구현되었습니다.

</details>
<details>
<summary>main/src/main/java/org/sopt/makers/crew/main/entity/tag/enums/MeetingKeywordType.java (1)</summary>

`11-19`: **모임 키워드 타입 열거형이 잘 정의되었습니다**

모임 키워드 타입을 위한 열거형이 필요한 카테고리들을 포함하여 적절히 정의되었습니다. 한글 값을 통해 사용자 친화적인 인터페이스를 지원할 수 있습니다.

</details>
<details>
<summary>main/src/main/java/org/sopt/makers/crew/main/flash/v2/service/FlashV2ServiceImpl.java (5)</summary>

`36-36`: **태그 응답 DTO 임포트 추가 확인.**

새로운 기능 구현에 필요한 DTO 클래스가 올바르게 임포트되었습니다.

---

`89-91`: **모임 키워드 타입 파라미터 전달 확인.**

번쩍 태그 생성 시 welcomeMessageTypes와 함께 meetingKeywordTypes도 전달하는 로직이 올바르게 구현되었습니다.

---

`98-98`: **응답 DTO 생성 방식 변경 확인.**

기존 from 메서드에서 tagId를 포함하는 of 메서드로 변경되어 태그 ID가 응답에 포함되도록 올바르게 구현되었습니다.

---

`155-157`: **모임 키워드 타입 업데이트 파라미터 전달 확인.**

번쩍 태그 업데이트 시 welcomeMessageTypes와 함께 meetingKeywordTypes도 전달하는 로직이 올바르게 구현되었습니다.

---

`159-159`: **업데이트 응답 DTO 생성 방식 변경 확인.**

createFlash와 마찬가지로 tagId를 포함하는 of 메서드로 변경되어 태그 ID가 응답에 포함되도록 올바르게 구현되었습니다.

</details>
<details>
<summary>main/src/main/java/org/sopt/makers/crew/main/entity/tag/Tag.java (7)</summary>

`7-7`: **MeetingKeywordType 임포트 추가 확인.**

모임 키워드 타입을 위한 열거형 클래스가 올바르게 임포트되었습니다.

---

`21-21`: **Size 밸리데이션 애노테이션 임포트 확인.**

모임 키워드 개수 제한을 위한 Size 애노테이션이 올바르게 임포트되었습니다.

---

`57-60`: **meetingKeywordTypes 필드 추가 확인.**

모임 키워드 타입을 JSONB 형식으로 저장하는 필드가 올바르게 추가되었습니다. 최소 1개, 최대 2개의 제약 조건도 적절히 설정되었습니다.

---

`63-71`: **생성자 및 빌더 패턴 업데이트 확인.**

meetingKeywordTypes 필드를 포함하도록 생성자와 빌더 패턴이 올바르게 업데이트되었습니다.

---

`73-82`: **createGeneralMeetingTag 메서드 업데이트 확인.**

일반 모임 태그 생성 메서드가 meetingKeywordTypes를 포함하도록 올바르게 업데이트되었습니다.

---

`85-94`: **createFlashMeetingTag 메서드 업데이트 확인.**

번쩍 모임 태그 생성 메서드가 meetingKeywordTypes를 포함하도록 올바르게 업데이트되었습니다.

---

`101-103`: **updateMeetingKeywordTypeEnums 메서드 추가 확인.**

모임 키워드 타입을 업데이트하는 메서드가 올바르게 추가되었습니다.

</details>
<details>
<summary>main/src/main/java/org/sopt/makers/crew/main/tag/v2/service/TagV2ServiceImpl.java (11)</summary>

`11-11`: **MeetingKeywordType 임포트 추가 확인.**

모임 키워드 타입을 위한 열거형 클래스가 올바르게 임포트되었습니다.

---

`16-16`: **TagV2CreateAndUpdateFlashTagResponseDto 임포트 확인.**

태그 ID를 포함한 응답 DTO 클래스가 올바르게 임포트되었습니다.

---

`27-27`: **MAX_MEETING_KEYWORD_SIZE 상수 추가 확인.**

모임 키워드 최대 개수 상수가 2로 설정되어 있어 Entity의 @Size 제약 조건과 일치합니다.

---

`34-36`: **createFlashTag 메서드 시그니처 업데이트 확인.**

번쩍 태그 생성 메서드가 meetingKeywordTypes를 포함하도록 올바르게 업데이트되었습니다.

---

`44-44`: **saveTag 메서드 호출 업데이트 확인.**

저장 메서드에 meetingKeywordTypeEnums 파라미터를 추가하여 올바르게 호출하고 있습니다.

---

`60-62`: **updateFlashTag 메서드 시그니처 업데이트 확인.**

번쩍 태그 업데이트 메서드가 meetingKeywordTypes를 포함하도록 올바르게 업데이트되었습니다.

---

`70-70`: **updateMeetingKeywordTypeEnums 메서드 호출 확인.**

모임 키워드 타입을 업데이트하는 메서드가 올바르게 호출되었습니다.

---

`72-72`: **응답 DTO 생성 확인.**

TagV2CreateAndUpdateFlashTagResponseDto를 태그 ID로 생성하도록 올바르게 구현되었습니다.

---

`75-81`: **saveTag 메서드 업데이트 확인.**

saveTag 메서드가 meetingKeywordTypes 파라미터를 포함하도록 올바르게 업데이트되었습니다.

---

`83-90`: **toWelcomeMessageTypes 메서드 구현 확인.**

welcomeMessageTypes를 변환하는 메서드가 올바르게 구현되었습니다. 다만, null이나 빈 리스트일 경우 예외를 발생시키지 않고 빈 리스트를 반환하고 있어, toMeetingKeywordTypes와 처리 방식이 다릅니다. 이는 아마도 welcomeMessageTypes는 선택적이고 meetingKeywordTypes는 필수적이기 때문인 것으로 보입니다.

---

`92-99`: **toMeetingKeywordTypes 메서드 구현 확인.**

meetingKeywordTypes를 변환하는 메서드가 올바르게 구현되었습니다. null, 빈 리스트, 최대 개수 초과 시 예외를 발생시키는 검증 로직이 포함되어 있어 적절합니다.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-19T14:44:11Z
user: login: khyojun
body: 👍  좋네요! 고생하셨습니다!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-19T16:08:27Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>main/src/main/java/org/sopt/makers/crew/main/tag/v2/service/TagV2ServiceImpl.java (1)</summary><blockquote>

`27-27`: **상수 네이밍을 더 명확하게 표현해 주세요**  
`MAX_MEETING_KEYWORD_SIZE` 대신 `MAX_MEETING_KEYWORD_COUNT`와 같이 “개수”를 명시적으로 드러내면 의미 파악이 조금 더 수월합니다.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between d7e4dd505f3111785ca05fa938173e7f7faff80b and 2a6742160b348176c4f7763f318f92bb46b6874c.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `main/src/main/java/org/sopt/makers/crew/main/tag/v2/dto/response/TagV2CreateAndUpdateFlashTagResponseDto.java` (1 hunks)
* `main/src/main/java/org/sopt/makers/crew/main/tag/v2/service/TagV2ServiceImpl.java` (3 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* main/src/main/java/org/sopt/makers/crew/main/tag/v2/dto/response/TagV2CreateAndUpdateFlashTagResponseDto.java

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>main/src/main/java/org/sopt/makers/crew/main/tag/v2/service/TagV2ServiceImpl.java (2)</summary><blockquote>

<details>
<summary>main/src/main/java/org/sopt/makers/crew/main/global/exception/BadRequestException.java (1)</summary>

* `BadRequestException` (5-14)

</details>
<details>
<summary>main/src/main/java/org/sopt/makers/crew/main/global/exception/NotFoundException.java (1)</summary>

* `NotFoundException` (5-13)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (3)</summary><blockquote>

<details>
<summary>main/src/main/java/org/sopt/makers/crew/main/tag/v2/service/TagV2ServiceImpl.java (3)</summary>

`60-72`: **Update 로직에도 동일한 Null/중복 키워드 이슈 존재**  
`updateFlashTag` 역시 `meetingKeywordTypes` 가 `null`이거나 비어 있을 때 예외를 던지므로, dev 환경과 정책이 맞지 않습니다. 위 `createFlashTag`와 동일한 방식으로 수정이 필요합니다.  
또한 `tag.updateMeetingKeywordTypeEnums()` 메서드명이 도메인 객체에 실제로 존재하는지 확인해주세요. 기존 코드에서는 `updateMeetingKeywordTypes`와 같이 복수형‑s 없이 사용하는 경우가 많습니다. 컴파일 오류 가능성이 있습니다.

---

`70-70`: **메서드 이름 오타 가능성 확인 요청**  
`tag.updateMeetingKeywordTypeEnums(...)` 라고 호출하고 있는데, 엔티티의 관례상 `updateMeetingKeywordTypes` 혹은 `updateMeetingKeywordType`일 가능성이 높습니다. 실제 Tag 엔티티 메서드 시그니처와 불일치하면 컴파일이 실패합니다.

---

`83-90`: **`toWelcomeMessageTypes`와 `toMeetingKeywordTypes` 간 정책 불일치**  
`welcomeMessageTypes`는 *빈 리스트*를 허용하고, `meetingKeywordTypes`는 허용하지 않습니다.  
비즈니스 요구사항이 의도한 차이라면 괜찮지만, 두 필드가 모두 “0개 허용 안 함 / 1~2개 허용” 정책이라면 `toWelcomeMessageTypes`도 동일한 검증을 적용해야 합니다. 요구사항을 다시 한 번 확인해 주세요.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-20T05:05:29Z

Review comments:
diff_hunk: @@ -24,28 +24,24 @@
 @Transactional(readOnly = true)
 public class TagV2ServiceImpl implements TagV2Service {
 
+	private static final int MAX_MEETING_KEYWORD_SIZE = 2;
 	private final TagRepository tagRepository;
 
 	// 여기에 createGeneralMeetingTag 메서드도 추가하면 될 것 같습니다 나중에! (추후 일반 모임에 태그 추가 시 작성)
 
 	@Override
 	@Transactional
-	public TagV2CreateFlashTagResponseDto createFlashTag(List<String> welcomeMessageTypes,
+	public TagV2CreateAndUpdateFlashTagResponseDto createFlashTag(List<String> welcomeMessageTypes,
+		List<String> meetingKeywordTypes,
 		Integer flashId) {
-
 		if (flashId == null) {
 			throw new BadRequestException(VALIDATION_EXCEPTION.getErrorCode());
 		}
 
-		if (welcomeMessageTypes == null || welcomeMessageTypes.isEmpty()) {
-			return saveTag(flashId, List.of());
-		}
+		List<WelcomeMessageType> welcomeMessageTypeEnums = toWelcomeMessageTypes(welcomeMessageTypes);
+		List<MeetingKeywordType> meetingKeywordTypeEnums = toMeetingKeywordTypes(meetingKeywordTypes);
 
-		List<WelcomeMessageType> welcomeMessageTypeEnums = welcomeMessageTypes.stream()
-			.map(WelcomeMessageType::ofValue)
-			.toList();
-
-		return saveTag(flashId, welcomeMessageTypeEnums);
+		return saveTag(flashId, welcomeMessageTypeEnums, meetingKeywordTypeEnums);
 	}
path: main/src/main/java/org/sopt/makers/crew/main/tag/v2/service/TagV2ServiceImpl.java
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**dev 환경 요구사항과 상충되는 Null 처리 로직**  
PR 설명에 따르면 dev 서버에서는 `meetingKeywordTypes` 값이 `null`일 수 있도록 “임시 허용”했다고 명시되어 있습니다.  
그러나 `createFlashTag`는 `toMeetingKeywordTypes()`를 그대로 호출하며, 해당 메서드는 `null` 입력 시 `BadRequestException`을 던집니다. 결과적으로 dev 서버에서 의도한 “null 허용”이 동작하지 않습니다.

```diff
-List<MeetingKeywordType> meetingKeywordTypeEnums = toMeetingKeywordTypes(meetingKeywordTypes);
+List<MeetingKeywordType> meetingKeywordTypeEnums =
+    meetingKeywordTypes == null ? List.of() : toMeetingKeywordTypes(meetingKeywordTypes);
```

또는 `toMeetingKeywordTypes` 자체에서 dev 프로파일일 때만 예외를 완화하도록 처리하는 방법도 있습니다.  



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
	public TagV2CreateAndUpdateFlashTagResponseDto createFlashTag(List<String> welcomeMessageTypes,
		List<String> meetingKeywordTypes,
		Integer flashId) {
		if (flashId == null) {
			throw new BadRequestException(VALIDATION_EXCEPTION.getErrorCode());
		}

		List<WelcomeMessageType> welcomeMessageTypeEnums = toWelcomeMessageTypes(welcomeMessageTypes);
		List<MeetingKeywordType> meetingKeywordTypeEnums =
			meetingKeywordTypes == null ? List.of() : toMeetingKeywordTypes(meetingKeywordTypes);

		return saveTag(flashId, welcomeMessageTypeEnums, meetingKeywordTypeEnums);
	}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-20T05:05:29Z
author_association: NONE
diff_hunk: @@ -61,27 +57,44 @@ public List<WelcomeMessageType> getWelcomeMessageTypesByFlashId(Integer flashId)
 
 	@Override
 	@Transactional
-	public TagV2CreateFlashTagResponseDto updateFlashTag(List<String> welcomeMessageTypes, Integer flashId) {
+	public TagV2CreateAndUpdateFlashTagResponseDto updateFlashTag(List<String> welcomeMessageTypes,
+		List<String> meetingKeywordTypes,
+		Integer flashId) {
 		Tag tag = tagRepository.findTagByFlashId(flashId)
 			.orElseThrow(() -> new NotFoundException(NOT_FOUND_TAG.getErrorCode()));
 
-		List<WelcomeMessageType> welcomeMessageTypeEnums = convertToWelcomeMessageTypeList(welcomeMessageTypes);
+		List<WelcomeMessageType> welcomeMessageTypeEnums = toWelcomeMessageTypes(welcomeMessageTypes);
+		List<MeetingKeywordType> meetingKeywordTypeEnums = toMeetingKeywordTypes(meetingKeywordTypes);
 
 		tag.updateWelcomeMessageTypes(welcomeMessageTypeEnums);
+		tag.updateMeetingKeywordTypeEnums(meetingKeywordTypeEnums);
 
-		return TagV2CreateFlashTagResponseDto.from(tag.getId());
+		return TagV2CreateAndUpdateFlashTagResponseDto.from(tag.getId());
 	}
 
-	private TagV2CreateFlashTagResponseDto saveTag(Integer flashId, List<WelcomeMessageType> welcomeMessageTypeEnums) {
-		Tag tag = Tag.createFlashMeetingTag(TagType.FLASH, flashId, welcomeMessageTypeEnums);
+	private TagV2CreateAndUpdateFlashTagResponseDto saveTag(Integer flashId,
+		List<WelcomeMessageType> welcomeMessageTypes,
+		List<MeetingKeywordType> meetingKeywordTypes) {
+		Tag tag = Tag.createFlashMeetingTag(TagType.FLASH, flashId, welcomeMessageTypes, meetingKeywordTypes);
 		tagRepository.save(tag);
-		return TagV2CreateFlashTagResponseDto.from(tag.getId());
+		return TagV2CreateAndUpdateFlashTagResponseDto.from(tag.getId());
 	}
 
-	private List<WelcomeMessageType> convertToWelcomeMessageTypeList(List<String> welcomeMessageTypes) {
-		return Optional.ofNullable(welcomeMessageTypes)
-			.filter(list -> !list.isEmpty())
-			.map(list -> list.stream().map(WelcomeMessageType::ofValue).toList())
-			.orElse(Collections.emptyList());
+	private List<WelcomeMessageType> toWelcomeMessageTypes(List<String> values) {
+		if (values == null || values.isEmpty()) {
+			return List.of();
+		}
+		return values.stream()
+			.map(WelcomeMessageType::ofValue)
+			.toList();
+	}
+
+	private List<MeetingKeywordType> toMeetingKeywordTypes(List<String> values) {
+		if (values == null || values.isEmpty() || values.size() > MAX_MEETING_KEYWORD_SIZE) {
+			throw new BadRequestException(INVALID_MEETING_KEYWORD_SIZE.getErrorCode());
+		}
+		return values.stream()
+			.map(MeetingKeywordType::ofValue)
+			.toList();
 	}
path: main/src/main/java/org/sopt/makers/crew/main/tag/v2/service/TagV2ServiceImpl.java
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**중복 키워드 허용 및 예외 메시지 범위 개선 제안**  

1. 현재 로직은 동일한 키워드가 두 번 전달되는 경우를 허용합니다. 중복을 방지하려면 아래와 같이 distinct 체크를 권장합니다.
2. 예외 조건을 하나로 묶어 `INVALID_MEETING_KEYWORD_SIZE`를 사용하고 있지만, *빈 리스트*·*null*과 *허용 개수 초과*는 서로 다른 원인입니다. 메시지를 구분하면 클라이언트 디버깅이 더 쉽습니다.

```diff
-if (values == null || values.isEmpty() || values.size() > MAX_MEETING_KEYWORD_SIZE) {
-    throw new BadRequestException(INVALID_MEETING_KEYWORD_SIZE.getErrorCode());
-}
-return values.stream()
-    .map(MeetingKeywordType::ofValue)
-    .toList();
+if (values == null || values.isEmpty()) {
+    throw new BadRequestException(EMPTY_MEETING_KEYWORDS.getErrorCode());
+}
+if (values.size() > MAX_MEETING_KEYWORD_SIZE) {
+    throw new BadRequestException(INVALID_MEETING_KEYWORD_SIZE.getErrorCode());
+}
+if (values.stream().distinct().count() != values.size()) {
+    throw new BadRequestException(DUPLICATED_MEETING_KEYWORD.getErrorCode());
+}
+return values.stream()
+    .map(MeetingKeywordType::ofValue)
+    .toList();
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
	private List<MeetingKeywordType> toMeetingKeywordTypes(List<String> values) {
		if (values == null || values.isEmpty()) {
			throw new BadRequestException(EMPTY_MEETING_KEYWORDS.getErrorCode());
		}
		if (values.size() > MAX_MEETING_KEYWORD_SIZE) {
			throw new BadRequestException(INVALID_MEETING_KEYWORD_SIZE.getErrorCode());
		}
		if (values.stream().distinct().count() != values.size()) {
			throw new BadRequestException(DUPLICATED_MEETING_KEYWORD.getErrorCode());
		}
		return values.stream()
			.map(MeetingKeywordType::ofValue)
			.toList();
	}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-20T05:05:29Z
author_association: NONE

PR comments:
user: login: height[bot]
created_at: 2025-04-19T14:42:12Z
body: > Link [Height](https://height.app/QvNPTzDUr8) tasks by mentioning a task ID in the pull request title or commit messages, or description and comments with the keyword `link` (e.g. "Link T-123").
>
> 💡Tip: You can also use "Close T-X" to automatically close a task when the pull request is merged.
<!-- Sent by Height -->
author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-04-19T14:42:14Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Summary by CodeRabbit

- **신규 기능**
    - 미팅 태그에 최대 2개의 미팅 키워드 타입을 추가할 수 있도록 확장되었습니다.
    - 플래시 생성 및 수정 시 미팅 키워드 타입을 필수로 입력할 수 있습니다.

- **API 변경**
    - 플래시 생성 및 수정 응답에 meetingId와 함께 tagId가 추가되어 반환됩니다.
    - 관련 API 문서에 tagId 반환이 명시되었습니다.

- **검증 및 예외 처리**
    - 미팅 키워드 타입 및 환영 메시지 타입의 유효성 검증이 강화되었습니다.
    - 잘못된 입력 시 상세한 오류 메시지가 제공됩니다.
"""
## Summary by CodeRabbit

- **신규 기능**
    - 플래시 생성 및 수정 시 최대 2개의 미팅 키워드 유형을 추가로 입력할 수 있습니다.
    - 응답 값에 meetingId와 함께 tagId가 추가로 제공됩니다.

- **버그 수정**
    - 잘못된 환영 메시지 또는 미팅 키워드 유형 입력 시 명확한 오류 메시지가 반환됩니다.

- **문서화**
    - API 문서에 meetingKeywordTypes와 tagId 관련 설명이 추가되었습니다.
## Walkthrough

이번 변경에서는 번쩍(Flash) 모임의 생성 및 수정 시 모임 키워드(최소 1개, 최대 2개)를 등록할 수 있도록 전체적인 데이터 구조와 서비스 로직이 확장되었습니다. 이를 위해 Tag 엔티티와 관련 DTO, 서비스, 예외, Enum 등이 추가 및 수정되었으며, API 응답에도 tagId가 포함되어 반환됩니다. 또한, 입력값 검증 및 예외 처리, OpenAPI 문서 설명도 이에 맞게 보완되었습니다.

## Changes

| 파일/경로 요약 | 변경 내용 요약 |
|---|---|
| .../entity/tag/Tag.java, .../entity/tag/enums/MeetingKeywordType.java, .../entity/tag/enums/WelcomeMessageType.java | Tag 엔티티에 meetingKeywordTypes 필드 추가 및 관련 생성자/메서드 확장, MeetingKeywordType 신규 Enum 도입, WelcomeMessageType 예외처리 개선 |
| .../flash/v2/FlashV2Api.java, .../flash/v2/dto/request/FlashV2CreateAndUpdateFlashBodyDto.java, .../flash/v2/dto/response/FlashV2CreateAndUpdateResponseDto.java | 번쩍 생성/수정 요청 및 응답 DTO에 meetingKeywordTypes, tagId 필드 추가, OpenAPI 문서 설명 보완 |
| .../flash/v2/service/FlashV2ServiceImpl.java | 번쩍 생성/수정 시 meetingKeywordTypes를 Tag 서비스에 전달 및 tagId 반환으로 응답 구조 변경 |
| .../global/exception/ErrorStatus.java | 모임 키워드 및 환영 메시지 타입 관련 에러 코드 3종 추가 |
| .../tag/v2/dto/response/TagV2CreateAndUpdateFlashTagResponseDto.java | Tag 관련 응답 DTO 클래스명 및 팩토리 메서드명 변경, tagId 필드 유지 |
| .../tag/v2/service/TagV2Service.java, .../tag/v2/service/TagV2ServiceImpl.java | Tag 서비스 인터페이스 및 구현에서 meetingKeywordTypes 파라미터 추가, Enum 변환 및 유효성 검증, 응답 DTO 타입 변경 및 저장 로직 확장 |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant Client
    participant FlashV2Api
    participant FlashV2Service
    participant TagV2Service
    participant TagEntity

    Client->>FlashV2Api: 번쩍 생성/수정 요청 (meetingKeywordTypes 포함)
    FlashV2Api->>FlashV2Service: createFlash/updateFlash 호출
    FlashV2Service->>TagV2Service: createFlashTag/updateFlashTag(welcomeMessageTypes, meetingKeywordTypes, flashId)
    TagV2Service->>TagEntity: Tag 생성/수정 (meetingKeywordTypes 저장)
    TagV2Service-->>FlashV2Service: tagId 반환
    FlashV2Service-->>FlashV2Api: meetingId, tagId 포함 응답
    FlashV2Api-->>Client: meetingId, tagId 포함 응답
```

## Assessment against linked issues

| Objective | Addressed | Explanation |
|---|---|---|
| 번쩍 개설 시 모임 키워드 등록 기능 추가 (#642) | ✅ |  |
| 번쩍 수정 시 모임 키워드 등록 기능 추가 (#642) | ✅ |  |

## Poem

> 🐇  
> 번쩍 모임 키워드 두 개까지,  
> 토끼가 깡총깡총 신나게 달리지!  
> 태그에도 담고, 응답에도 싹—  
> 오류도 꼼꼼, 예외도 딱!  
> 오늘도 코드밭에 새싹이 피었네,  
> 모임 키워드로 봄이 왔네!  
> 🌱✨
"""

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=sopt-makers/sopt-crew-backend&utm_content=643):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE