diff --git a/src/main/java/com/atmd/backend/domain/group/dto/request/GroupCreateRequestDTO.java b/src/main/java/com/atmd/backend/domain/group/dto/request/GroupCreateRequestDTO.java index 37166bb..f1e6a3b 100644 --- a/src/main/java/com/atmd/backend/domain/group/dto/request/GroupCreateRequestDTO.java +++ b/src/main/java/com/atmd/backend/domain/group/dto/request/GroupCreateRequestDTO.java @@ -1,6 +1,9 @@ package com.atmd.backend.domain.group.dto.request; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import lombok.Getter; import lombok.NoArgsConstructor; @@ -18,8 +21,11 @@ public class GroupCreateRequestDTO { private String penalty; /** - * 첫 그룹이 아닌 경우(SUBSCRIBED)에만 사용. 2~5 사이 값. - * 첫 그룹은 서버에서 2로 고정하므로 null 허용. + * 그룹 가용 최대 인원 (방장 포함, 2~5). + * 2인은 무료(GENERAL), 3~5인은 유료(SUBSCRIBED, (maxMemberCount - 2) × 500원). */ + @NotNull + @Min(2) + @Max(5) private Integer maxMemberCount; } diff --git a/src/main/java/com/atmd/backend/domain/group/entity/Group.java b/src/main/java/com/atmd/backend/domain/group/entity/Group.java index 2fea2df..f76387e 100644 --- a/src/main/java/com/atmd/backend/domain/group/entity/Group.java +++ b/src/main/java/com/atmd/backend/domain/group/entity/Group.java @@ -24,9 +24,9 @@ public class Group extends BaseEntity { public static final int MAX_GROUPS_PER_USER = 5; - public static final int GENERAL_MAX_MEMBER_COUNT = 2; - public static final int SUBSCRIBED_MIN_MEMBER_COUNT = 2; - public static final int SUBSCRIBED_MAX_MEMBER_COUNT = 5; + public static final int MIN_MEMBER_COUNT = 2; + public static final int MAX_MEMBER_COUNT = 5; + public static final int FREE_MEMBER_THRESHOLD = 2; public static final int PRICE_PER_MEMBER = 500; @Id @@ -75,44 +75,34 @@ private Group( this.price = price; } - public static Group createFirst( - User owner, - String name, - String inviteCode, - String penalty - ) { - return Group.builder() - .owner(owner) - .name(name) - .membership(GroupMembership.GENERAL) - .inviteCode(inviteCode) - .penalty(penalty) - .maxMemberCount(GENERAL_MAX_MEMBER_COUNT) - .price(0) - .build(); - } - - public static Group createSubscribed( + public static Group create( User owner, String name, String inviteCode, String penalty, int maxMemberCount ) { + GroupMembership membership = maxMemberCount <= FREE_MEMBER_THRESHOLD + ? GroupMembership.GENERAL + : GroupMembership.SUBSCRIBED; + int price = calculatePrice(maxMemberCount); return Group.builder() .owner(owner) .name(name) - .membership(GroupMembership.SUBSCRIBED) + .membership(membership) .inviteCode(inviteCode) .penalty(penalty) .maxMemberCount(maxMemberCount) - .price(maxMemberCount * PRICE_PER_MEMBER) + .price(price) .build(); } - public static boolean isValidSubscribedCapacity(int memberCount) { - return memberCount >= SUBSCRIBED_MIN_MEMBER_COUNT - && memberCount <= SUBSCRIBED_MAX_MEMBER_COUNT; + public static int calculatePrice(int maxMemberCount) { + return Math.max(0, maxMemberCount - FREE_MEMBER_THRESHOLD) * PRICE_PER_MEMBER; + } + + public static boolean isValidMemberCount(int memberCount) { + return memberCount >= MIN_MEMBER_COUNT && memberCount <= MAX_MEMBER_COUNT; } public boolean isOwner(Long userId) { diff --git a/src/main/java/com/atmd/backend/domain/group/service/GroupService.java b/src/main/java/com/atmd/backend/domain/group/service/GroupService.java index fb0b03c..81ca577 100644 --- a/src/main/java/com/atmd/backend/domain/group/service/GroupService.java +++ b/src/main/java/com/atmd/backend/domain/group/service/GroupService.java @@ -48,20 +48,17 @@ public GroupResponseDTO create(Long userId, GroupCreateRequestDTO request) { throw new GeneralException(GroupErrorCode.MAX_GROUPS_EXCEEDED); } - boolean isFirstGroup = !owner.isFirstGroupCreated(); - String inviteCode = generateUniqueInviteCode(); + Integer maxMemberCount = request.getMaxMemberCount(); + if (maxMemberCount == null || !Group.isValidMemberCount(maxMemberCount)) { + throw new GeneralException(GroupErrorCode.INVALID_MEMBER_COUNT); + } - Group group = isFirstGroup - ? Group.createFirst(owner, request.getName(), inviteCode, request.getPenalty()) - : createSubscribedGroup(owner, request, inviteCode); + String inviteCode = generateUniqueInviteCode(); + Group group = Group.create(owner, request.getName(), inviteCode, request.getPenalty(), maxMemberCount); groupRepository.save(group); groupUserRepository.save(GroupUser.of(group, owner)); - if (isFirstGroup) { - owner.markFirstGroupCreated(); - } - return GroupResponseDTO.of(group, 1L, userId); } @@ -166,14 +163,6 @@ private GroupJoinResponseDTO joinInternal(String inviteCode, User user) { return GroupJoinResponseDTO.of(group.getId(), group.getName(), currentCount + 1); } - private Group createSubscribedGroup(User owner, GroupCreateRequestDTO request, String inviteCode) { - Integer requested = request.getMaxMemberCount(); - if (requested == null || !Group.isValidSubscribedCapacity(requested)) { - throw new GeneralException(GroupErrorCode.INVALID_MEMBER_COUNT); - } - return Group.createSubscribed(owner, request.getName(), inviteCode, request.getPenalty(), requested); - } - private String generateUniqueInviteCode() { for (int i = 0; i < MAX_GENERATION_ATTEMPTS; i++) { String code = randomCode(); diff --git a/src/main/java/com/atmd/backend/domain/user/entity/User.java b/src/main/java/com/atmd/backend/domain/user/entity/User.java index cdf90ad..218043d 100644 --- a/src/main/java/com/atmd/backend/domain/user/entity/User.java +++ b/src/main/java/com/atmd/backend/domain/user/entity/User.java @@ -63,9 +63,6 @@ public class User extends BaseEntity { @JoinColumn(name = "address_id") private Address address; - @Column(name = "first_group_created", nullable = false) - private boolean firstGroupCreated = false; - @Builder private User(String email, String nickname, Provider provider, String providerId, Role role, Integer age, Gender gender, Double height, Double weight, Address address) { @@ -113,8 +110,4 @@ private Double calculateBmi(Double height, Double weight) { public void updateAddress(Address address) { this.address = address; } - - public void markFirstGroupCreated() { - this.firstGroupCreated = true; - } } diff --git a/src/main/resources/db/migration/V7__add_users_first_group_created.sql b/src/main/resources/db/migration/V7__add_users_first_group_created.sql new file mode 100644 index 0000000..d1d405f --- /dev/null +++ b/src/main/resources/db/migration/V7__add_users_first_group_created.sql @@ -0,0 +1,6 @@ +-- users 테이블에 그룹 도메인용 first_group_created 컬럼 추가 +-- 프로덕션 DB에는 이미 수동으로 추가되어 있을 수 있으므로 IF NOT EXISTS 사용 (idempotent) +-- 새 환경 배포 및 DR 상황에서도 안전하게 실행됨 + +ALTER TABLE users + ADD COLUMN IF NOT EXISTS first_group_created BOOLEAN NOT NULL DEFAULT FALSE; diff --git a/src/main/resources/db/migration/V8__drop_users_first_group_created.sql b/src/main/resources/db/migration/V8__drop_users_first_group_created.sql new file mode 100644 index 0000000..29abfef --- /dev/null +++ b/src/main/resources/db/migration/V8__drop_users_first_group_created.sql @@ -0,0 +1,5 @@ +-- 그룹 생성 정책 변경(2인 무료/3~5인 유료)으로 first_group_created 플래그 미사용 처리 +-- 프로덕션 DB에 컬럼이 없는 환경에서도 안전하게 실행되도록 IF EXISTS 사용 (idempotent) + +ALTER TABLE users + DROP COLUMN IF EXISTS first_group_created;