diff --git a/src/main/java/com/back/matchduo/domain/party/dto/PartyDto.java b/src/main/java/com/back/matchduo/domain/party/dto/PartyDto.java deleted file mode 100644 index 39d9bbc..0000000 --- a/src/main/java/com/back/matchduo/domain/party/dto/PartyDto.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.back.matchduo.domain.party.dto; - -public class PartyDto { -} - diff --git a/src/main/java/com/back/matchduo/domain/party/dto/request/PartyCloseRequest.java b/src/main/java/com/back/matchduo/domain/party/dto/request/PartyCloseRequest.java new file mode 100644 index 0000000..38a4b43 --- /dev/null +++ b/src/main/java/com/back/matchduo/domain/party/dto/request/PartyCloseRequest.java @@ -0,0 +1,5 @@ +package com.back.matchduo.domain.party.dto.request; + +public record PartyCloseRequest( + Long partyId +) {} \ No newline at end of file diff --git a/src/main/java/com/back/matchduo/domain/party/dto/request/PartyMemberAddRequest.java b/src/main/java/com/back/matchduo/domain/party/dto/request/PartyMemberAddRequest.java new file mode 100644 index 0000000..8aa71f8 --- /dev/null +++ b/src/main/java/com/back/matchduo/domain/party/dto/request/PartyMemberAddRequest.java @@ -0,0 +1,10 @@ +package com.back.matchduo.domain.party.dto.request; + +import jakarta.validation.constraints.NotEmpty; + +import java.util.List; + +public record PartyMemberAddRequest( + @NotEmpty(message = "추가할 유저를 최소 1명 이상 선택해주세요.") + List targetUserIds // 다인파티 확정성 고려하여 List로 받음 +) {} \ No newline at end of file diff --git a/src/main/java/com/back/matchduo/domain/party/dto/response/MyPartyListResponse.java b/src/main/java/com/back/matchduo/domain/party/dto/response/MyPartyListResponse.java new file mode 100644 index 0000000..10294c1 --- /dev/null +++ b/src/main/java/com/back/matchduo/domain/party/dto/response/MyPartyListResponse.java @@ -0,0 +1,33 @@ +package com.back.matchduo.domain.party.dto.response; + +import com.back.matchduo.domain.party.entity.PartyMemberRole; +import com.back.matchduo.domain.party.entity.PartyStatus; + +import java.time.LocalDateTime; +import java.util.List; + +public record MyPartyListResponse( + List parties +) { + public record MyPartyDto( + Long partyId, + Long postId, + String postTitle, + String gameMode, + PartyStatus status, + PartyMemberRole myRole, + LocalDateTime joinedAt + ) { + public static MyPartyDto of(Long partyId, Long postId, String postTitle, String gameMode, PartyStatus status, PartyMemberRole myRole, LocalDateTime joinedAt) { + return new MyPartyDto( + partyId, + postId, + postTitle, + gameMode, + status, + myRole, + joinedAt + ); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/back/matchduo/domain/party/dto/response/PartyByPostResponse.java b/src/main/java/com/back/matchduo/domain/party/dto/response/PartyByPostResponse.java new file mode 100644 index 0000000..4e8ea45 --- /dev/null +++ b/src/main/java/com/back/matchduo/domain/party/dto/response/PartyByPostResponse.java @@ -0,0 +1,30 @@ +package com.back.matchduo.domain.party.dto.response; + +import com.back.matchduo.domain.party.entity.PartyMemberRole; +import com.back.matchduo.domain.party.entity.PartyStatus; + +import java.time.LocalDateTime; +import java.util.List; + +public record PartyByPostResponse( + Long partyId, + Long postId, + PartyStatus status, + int currentCount, + int maxCount, + LocalDateTime createdAt, + boolean isJoined, + List members +) { + public record PartyMemberDto( + Long partyMemberId, + Long userId, + String nickname, + String profileImage, + PartyMemberRole role + ) { + public static PartyMemberDto of(Long partyMemberId, Long userId, String nickname, String profileImage, PartyMemberRole role) { + return new PartyMemberDto(partyMemberId, userId, nickname, profileImage, role); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/back/matchduo/domain/party/dto/response/PartyCloseResponse.java b/src/main/java/com/back/matchduo/domain/party/dto/response/PartyCloseResponse.java new file mode 100644 index 0000000..2b50f50 --- /dev/null +++ b/src/main/java/com/back/matchduo/domain/party/dto/response/PartyCloseResponse.java @@ -0,0 +1,9 @@ +package com.back.matchduo.domain.party.dto.response; + +import java.time.LocalDateTime; + +public record PartyCloseResponse( + Long partyId, + String status, // "CLOSED" + LocalDateTime closedAt +) {} \ No newline at end of file diff --git a/src/main/java/com/back/matchduo/domain/party/dto/response/PartyMemberAddResponse.java b/src/main/java/com/back/matchduo/domain/party/dto/response/PartyMemberAddResponse.java new file mode 100644 index 0000000..421e8a0 --- /dev/null +++ b/src/main/java/com/back/matchduo/domain/party/dto/response/PartyMemberAddResponse.java @@ -0,0 +1,29 @@ +package com.back.matchduo.domain.party.dto.response; + +import com.back.matchduo.domain.party.entity.PartyMember; +import com.back.matchduo.domain.party.entity.PartyMemberRole; +import com.back.matchduo.domain.party.entity.PartyMemberState; + +import java.time.LocalDateTime; + +public record PartyMemberAddResponse( + Long partyMemberId, + Long userId, + String nickname, + String profileImage, + PartyMemberRole role,// LEADER / MEMBER + PartyMemberState state,// JOINED + LocalDateTime joinedAt +) { + public static PartyMemberAddResponse of(PartyMember member, String nickname, String profileImage) { + return new PartyMemberAddResponse( + member.getId(), + member.getUserId(), + nickname, + profileImage, + member.getRole(), + member.getState(), + member.getJoinedAt() + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/back/matchduo/domain/party/dto/response/PartyMemberListResponse.java b/src/main/java/com/back/matchduo/domain/party/dto/response/PartyMemberListResponse.java new file mode 100644 index 0000000..add05f0 --- /dev/null +++ b/src/main/java/com/back/matchduo/domain/party/dto/response/PartyMemberListResponse.java @@ -0,0 +1,33 @@ +package com.back.matchduo.domain.party.dto.response; + +import com.back.matchduo.domain.party.entity.PartyMemberRole; + +import java.time.LocalDateTime; +import java.util.List; + +public record PartyMemberListResponse( + Long partyId, + int currentCount, + int maxCount, + List members +) { + public record PartyMemberDto( + Long partyMemberId, + Long userId, + String nickname, + String profileImage, + PartyMemberRole role, // LEADER / MEMBER + LocalDateTime joinedAt + ) { + public static PartyMemberDto of(Long partyMemberId, Long userId, PartyMemberRole role, LocalDateTime joinedAt, String nickname, String profileImage) { + return new PartyMemberDto( + partyMemberId, + userId, + nickname, + profileImage, + role, + joinedAt + ); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/back/matchduo/domain/party/dto/response/PartyMemberRemoveResponse.java b/src/main/java/com/back/matchduo/domain/party/dto/response/PartyMemberRemoveResponse.java new file mode 100644 index 0000000..083156c --- /dev/null +++ b/src/main/java/com/back/matchduo/domain/party/dto/response/PartyMemberRemoveResponse.java @@ -0,0 +1,20 @@ +package com.back.matchduo.domain.party.dto.response; + +import com.back.matchduo.domain.party.entity.PartyMember; +import java.time.LocalDateTime; + +public record PartyMemberRemoveResponse( + Long partyMemberId, + Long userId, + String state,// "LEFT" + LocalDateTime leftAt +) { + public static PartyMemberRemoveResponse from(PartyMember member) { + return new PartyMemberRemoveResponse( + member.getId(), + member.getUserId(), + member.getState().name(), + member.getLeftAt() + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/back/matchduo/domain/party/entity/Party.java b/src/main/java/com/back/matchduo/domain/party/entity/Party.java index 7687a58..2a4acc2 100644 --- a/src/main/java/com/back/matchduo/domain/party/entity/Party.java +++ b/src/main/java/com/back/matchduo/domain/party/entity/Party.java @@ -1,10 +1,67 @@ package com.back.matchduo.domain.party.entity; import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; @Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "party") public class Party { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "party_id") private Long id; + + @Column(name = "post_id", nullable = false) + private Long postId; + + @Column(name = "leader_id", nullable = false) + private Long leaderId; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private PartyStatus status; + + @Column(name = "created_at", nullable = false) + private LocalDateTime createdAt; + + // 자동 파티완료 된 경우 + @Column(name = "expires_at", nullable = false) + private LocalDateTime expiresAt; + + // 수동 파티완료, 자동 파티완료 된 경우의 시간 + @Column(name = "closed_at") + private LocalDateTime closedAt; + + + public Party(Long postId, Long leaderId) { + this.postId = postId; + this.leaderId = leaderId; + this.status = PartyStatus.ACTIVE; + this.createdAt = LocalDateTime.now(); + this.expiresAt = this.createdAt.plusHours(6); + } + + + // 파티장이 수동 파티완료 처리 + public void closeParty() { + if (this.status == PartyStatus.ACTIVE) { + this.status = PartyStatus.CLOSED; + this.closedAt = LocalDateTime.now(); // 현재 시간 기록 + } + } + + // 스케줄러가 자동 파티완료 처리 + public void expireParty() { + if (this.status == PartyStatus.ACTIVE) { + this.status = PartyStatus.CLOSED; + this.closedAt = this.expiresAt; // 만료 예정 시간이 곧 종료 시간 + } + } } diff --git a/src/main/java/com/back/matchduo/domain/party/entity/PartyMember.java b/src/main/java/com/back/matchduo/domain/party/entity/PartyMember.java new file mode 100644 index 0000000..7d60513 --- /dev/null +++ b/src/main/java/com/back/matchduo/domain/party/entity/PartyMember.java @@ -0,0 +1,72 @@ +package com.back.matchduo.domain.party.entity; + +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table( + // 파티 중복 참여 방지 unique + name = "party_member", + uniqueConstraints = { + @UniqueConstraint( + name = "uk_party_member_user", + columnNames = {"party_id", "user_id"} + ) + } +) +public class PartyMember { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "party_member_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "party_id", nullable = false) + private Party party; + + @Column(name = "user_id", nullable = false) + private Long userId; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private PartyMemberRole role; // LEADER, MEMBER + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private PartyMemberState state; // JOINED, LEFT + + @Column(name = "joined_at", nullable = false) + private LocalDateTime joinedAt; + + @Column(name = "left_at") + private LocalDateTime leftAt; + + + public PartyMember(Party party, Long userId, PartyMemberRole role) { + this.party = party; + this.userId = userId; + this.role = role; + this.state = PartyMemberState.JOINED; + this.joinedAt = LocalDateTime.now(); + } + + + public void leaveParty() { + this.state = PartyMemberState.LEFT; + this.leftAt = LocalDateTime.now(); + } + + // 나갔던 유저가 다시 들어올 때 사용 + public void rejoinParty() { + this.state = PartyMemberState.JOINED; + this.joinedAt = LocalDateTime.now(); + this.leftAt = null; + } +} \ No newline at end of file diff --git a/src/main/java/com/back/matchduo/domain/party/entity/PartyMemberRole.java b/src/main/java/com/back/matchduo/domain/party/entity/PartyMemberRole.java new file mode 100644 index 0000000..b68da92 --- /dev/null +++ b/src/main/java/com/back/matchduo/domain/party/entity/PartyMemberRole.java @@ -0,0 +1,6 @@ +package com.back.matchduo.domain.party.entity; + +public enum PartyMemberRole { + LEADER, // 파티장 + MEMBER // 파티원 + } \ No newline at end of file diff --git a/src/main/java/com/back/matchduo/domain/party/entity/PartyMemberState.java b/src/main/java/com/back/matchduo/domain/party/entity/PartyMemberState.java new file mode 100644 index 0000000..403fb68 --- /dev/null +++ b/src/main/java/com/back/matchduo/domain/party/entity/PartyMemberState.java @@ -0,0 +1,6 @@ +package com.back.matchduo.domain.party.entity; + +public enum PartyMemberState { + JOINED, // 참여 중 + LEFT, // 나감 (탈퇴/강퇴) +} \ No newline at end of file diff --git a/src/main/java/com/back/matchduo/domain/party/entity/PartyStatus.java b/src/main/java/com/back/matchduo/domain/party/entity/PartyStatus.java new file mode 100644 index 0000000..a1c71d0 --- /dev/null +++ b/src/main/java/com/back/matchduo/domain/party/entity/PartyStatus.java @@ -0,0 +1,6 @@ +package com.back.matchduo.domain.party.entity; + +public enum PartyStatus { + ACTIVE, // 모집완료 시 + CLOSED // 모집중 , 게임완료(수동, 6시간 자동 포함) +} diff --git a/src/main/java/com/back/matchduo/domain/party/repository/PartyMemberRepository.java b/src/main/java/com/back/matchduo/domain/party/repository/PartyMemberRepository.java new file mode 100644 index 0000000..9f493c6 --- /dev/null +++ b/src/main/java/com/back/matchduo/domain/party/repository/PartyMemberRepository.java @@ -0,0 +1,22 @@ +package com.back.matchduo.domain.party.repository; + +import com.back.matchduo.domain.party.entity.PartyMember; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface PartyMemberRepository extends JpaRepository { + + // 1. 특정 파티의 멤버 목록 조회 (가입된 상태만 가져오거나, 전체 다 가져오거나) + List findByPartyId(Long partyId); + + // 2. 내가 참여한 파티 목록 조회 + List findByUserId(Long userId); + + // 3. 이미 참여했는지 확인 (중복 참여 방지) + boolean existsByPartyIdAndUserId(Long partyId, Long userId); + + // 4. 특정 파티에서 내 멤버 정보 찾기 + Optional findByPartyIdAndUserId(Long partyId, Long userId); +} \ No newline at end of file diff --git a/src/main/java/com/back/matchduo/domain/party/repository/PartyRepository.java b/src/main/java/com/back/matchduo/domain/party/repository/PartyRepository.java index d05021d..ca2257e 100644 --- a/src/main/java/com/back/matchduo/domain/party/repository/PartyRepository.java +++ b/src/main/java/com/back/matchduo/domain/party/repository/PartyRepository.java @@ -1,7 +1,20 @@ package com.back.matchduo.domain.party.repository; import com.back.matchduo.domain.party.entity.Party; +import com.back.matchduo.domain.party.entity.PartyStatus; import org.springframework.data.jpa.repository.JpaRepository; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + public interface PartyRepository extends JpaRepository { + + // 1. 모집글 ID로 파티 조회 + // 용도: GET /api/v1/posts/{postId}/party (모집글 눌렀을 때 파티 정보 띄우기) + Optional findByPostId(Long postId); + + // 2. 만료 시간이 지났고, 상태가 ACTIVE인 파티 조회 + // 용도: 스케줄러가 6시간 지난 파티를 찾아서 자동으로 닫을 때 사용 + List findByStatusAndExpiresAtBefore(PartyStatus status, LocalDateTime now); }