diff --git a/src/main/java/com/aliens/backend/block/domain/Block.java b/src/main/java/com/aliens/backend/block/domain/Block.java index 1c2a0ad6..e2f0f247 100644 --- a/src/main/java/com/aliens/backend/block/domain/Block.java +++ b/src/main/java/com/aliens/backend/block/domain/Block.java @@ -27,4 +27,12 @@ public static Block of(Member blockedMember, Member blockingMember) { block.blockedMember = blockedMember; return block; } + + public Long getBlockingMemberId() { + return blockingMember.getId(); + } + + public Long getBlockedMemberId() { + return blockedMember.getId(); + } } \ No newline at end of file diff --git a/src/main/java/com/aliens/backend/block/domain/repository/BlockRepository.java b/src/main/java/com/aliens/backend/block/domain/repository/BlockRepository.java index 92b85c4b..9297d22a 100644 --- a/src/main/java/com/aliens/backend/block/domain/repository/BlockRepository.java +++ b/src/main/java/com/aliens/backend/block/domain/repository/BlockRepository.java @@ -1,9 +1,14 @@ package com.aliens.backend.block.domain.repository; +import com.aliens.backend.auth.domain.Member; import com.aliens.backend.block.domain.Block; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.List; +import java.util.Optional; + @Repository public interface BlockRepository extends JpaRepository { + List findAllByBlockingMember(Member blockingMember); } diff --git a/src/main/java/com/aliens/backend/global/property/MatchingRuleProperties.java b/src/main/java/com/aliens/backend/global/property/MatchingRuleProperties.java index 1f678d9b..e6a07970 100644 --- a/src/main/java/com/aliens/backend/global/property/MatchingRuleProperties.java +++ b/src/main/java/com/aliens/backend/global/property/MatchingRuleProperties.java @@ -6,23 +6,23 @@ @Component public class MatchingRuleProperties { @Value("${matching.rule.max-matches.partner}") - private String maxPartners; + private Integer maxPartners; @Value("${matching.rule.max-matches.normal-partner}") - private String maxNormalPartners; + private Integer maxNormalPartners; @Value("${matching.rule.max-tries}") - private String maxTries; + private Integer maxTries; public Integer getMaxNormalPartners() { - return Integer.parseInt(maxNormalPartners); + return maxNormalPartners; } public Integer getMaxTries() { - return Integer.parseInt(maxTries); + return maxTries; } public Integer getMaxPartners() { - return Integer.parseInt(maxPartners); + return maxPartners; } } diff --git a/src/main/java/com/aliens/backend/global/property/MatchingTimeProperties.java b/src/main/java/com/aliens/backend/global/property/MatchingTimeProperties.java index e199d2ee..202da223 100644 --- a/src/main/java/com/aliens/backend/global/property/MatchingTimeProperties.java +++ b/src/main/java/com/aliens/backend/global/property/MatchingTimeProperties.java @@ -8,35 +8,35 @@ @Component public class MatchingTimeProperties { @Value("${matching.request.time.hours}") - private String matchingRequestAvailableTime; + private Long requestAvailableTime; @Value("${matching.valid.time.hours}") - private String matchingValidBeginHours; + private Integer validBeginHours; @Value("${matching.valid.day-of-week.if.monday.hours}") - private String mondayMatchingValidHours; + private Long mondayMatchingValidHours; @Value("${matching.valid.day-of-week.if.thursday.hours}") - private String thursdayMatchingValidHours; + private Long thursdayMatchingValidHours; @Value("${matching.valid.day-of-week.if.default.hours}") - private String defaultMatchingValidHours; + private Long defaultMatchingValidHours; - public Long getMatchingRequestAvailableTime() { - return Long.parseLong(matchingRequestAvailableTime); + public Long getRequestAvailableTime() { + return requestAvailableTime; } - public Integer getMatchingValidBeginHours() { - return Integer.parseInt(matchingValidBeginHours); + public Integer getValidBeginHours() { + return validBeginHours; } public Long getMatchingValidHours(final DayOfWeek dayOfWeek) { if (dayOfWeek.equals(DayOfWeek.MONDAY)) { - return Long.parseLong(mondayMatchingValidHours); + return mondayMatchingValidHours; } if (dayOfWeek.equals(DayOfWeek.THURSDAY)) { - return Long.parseLong(thursdayMatchingValidHours); + return thursdayMatchingValidHours; } - return Long.parseLong(defaultMatchingValidHours); + return defaultMatchingValidHours; } } diff --git a/src/main/java/com/aliens/backend/global/validator/LanguageCheck.java b/src/main/java/com/aliens/backend/global/validator/LanguageCheck.java new file mode 100644 index 00000000..2aaa0276 --- /dev/null +++ b/src/main/java/com/aliens/backend/global/validator/LanguageCheck.java @@ -0,0 +1,24 @@ +package com.aliens.backend.global.validator; + +import com.aliens.backend.mathcing.util.validator.LanguageValidator; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; + +@Target({ PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = {LanguageValidator.class}) +@Documented +public @interface LanguageCheck { + String message() default "Invalid Language Input"; + + Class[] groups() default { }; + + Class[] payload() default { }; +} \ No newline at end of file diff --git a/src/main/java/com/aliens/backend/mathcing/business/MatchingBusiness.java b/src/main/java/com/aliens/backend/mathcing/business/MatchingBusiness.java index e000f0e4..9b266c79 100644 --- a/src/main/java/com/aliens/backend/mathcing/business/MatchingBusiness.java +++ b/src/main/java/com/aliens/backend/mathcing/business/MatchingBusiness.java @@ -1,125 +1,37 @@ package com.aliens.backend.mathcing.business; import com.aliens.backend.global.property.MatchingRuleProperties; -import com.aliens.backend.mathcing.domain.MatchingApplication; -import com.aliens.backend.mathcing.service.model.Language; -import com.aliens.backend.mathcing.service.model.Participant; -import com.aliens.backend.mathcing.service.model.MatchingMode; -import com.aliens.backend.mathcing.service.model.Relationship; +import com.aliens.backend.mathcing.business.model.*; +import com.aliens.backend.mathcing.controller.dto.request.MatchingOperateRequest; import org.springframework.stereotype.Component; import java.util.*; -import java.util.stream.Collectors; @Component public class MatchingBusiness { private final MatchingRuleProperties matchingRuleProperties; - private List participants = new ArrayList<>(); - private Map> languageQueueWithParticipants = new HashMap<>(); - private Relationship relationship; + private MatchingTypeGroup matchingTypeGroup; + private ParticipantGroup participantGroup; + private LanguageQueue languageQueue; public MatchingBusiness(final MatchingRuleProperties matchingRuleProperties) { this.matchingRuleProperties = matchingRuleProperties; } - private void initialize(final List matchingApplications) { - participants = MatchingApplication.toParticipantList(matchingApplications); - languageQueueWithParticipants = Language.createQueueWith(participants); - relationship = Relationship.NORMAL; - } - - public List operateMatching(final List matchingApplications) { - initialize(matchingApplications); - - matchParticipantsWith(MatchingMode.FIRST_PREFER_LANGUAGE); - matchParticipantsWith(MatchingMode.SECOND_PREFER_LANGUAGE); - matchParticipantsWith(MatchingMode.RANDOM); - matchParticipantsWith(MatchingMode.SPECIAL); - - return participants; - } - - private void matchParticipantsWith(final MatchingMode matchingMode) { - List participants = null; - if (matchingMode.equals(MatchingMode.FIRST_PREFER_LANGUAGE)) { - participants = this.participants; - } - if (matchingMode.equals(MatchingMode.SECOND_PREFER_LANGUAGE)) { - participants = getParticipantsLessThan(matchingRuleProperties.getMaxNormalPartners()); - languageQueueWithParticipants = Language.createQueueWith(this.participants); - } - if (matchingMode.equals(MatchingMode.RANDOM)) { - relationship = Relationship.SPECIAL; - participants = getParticipantsLessThan(matchingRuleProperties.getMaxNormalPartners()); - } - if (matchingMode.equals(MatchingMode.SPECIAL)) { - participants = getParticipantsLessThan(matchingRuleProperties.getMaxPartners()); - } - matchWith(matchingMode, participants); - } - - private void matchWith(final MatchingMode matchingMode, final List participants) { - Queue candidates = null; - if (matchingMode.equals(MatchingMode.RANDOM)) { - candidates = new LinkedList<>(getParticipantsLessThan(matchingRuleProperties.getMaxPartners())); - } - if (matchingMode.equals(MatchingMode.SPECIAL)) { - candidates = new LinkedList<>(participants); - } - for (Participant participant : participants) { - if (matchingMode.equals(MatchingMode.FIRST_PREFER_LANGUAGE) || - matchingMode.equals(MatchingMode.SECOND_PREFER_LANGUAGE)) { - candidates = languageQueueWithParticipants.get(participant.getPreferLanguage(matchingMode)); - } - tryMatchBetween(participant, candidates); - } - } - - private void tryMatchBetween(final Participant participant, final Queue candidates) { - int tries = 0; - while (!isExceededMaxPartners(relationship, participant) && !isExceedMaxTries(tries) && !candidates.isEmpty()) { - Participant partner = candidates.poll(); - tries++; - if (isValidMatching(relationship, participant, partner)) { - addMatching(participant, partner); - if (!isExceededMaxPartners(relationship, partner)) { - candidates.add(partner); - } - } else { - candidates.add(partner); - } - } - } - - private void addMatching(final Participant participant, final Participant partner) { - participant.addPartner(relationship, partner.memberId()); - partner.addPartner(relationship, participant.memberId()); - } - - private List getParticipantsLessThan(int numberOfPartner) { - return participants.stream() - .filter(participant -> participant.getNumberOfPartners() < numberOfPartner) - .collect(Collectors.toList()); - } + public void operateMatching(final MatchingOperateRequest matchingOperateRequest) { + initialize(matchingOperateRequest); - private boolean isValidMatching(final Relationship relationship, - final Participant participant, - final Participant partner) { - return participant != partner && - !participant.isPartnerWith(partner) && - !partner.isPartnerWith(participant) && - !isExceededMaxPartners(relationship, partner); + matchingTypeGroup.matchParticipants(participantGroup, languageQueue); } - private boolean isExceededMaxPartners(final Relationship relationship, final Participant participant) { - if (relationship.equals(Relationship.NORMAL)) { - return participant.getNumberOfPartners() >= matchingRuleProperties.getMaxNormalPartners(); // 4 - } - return participant.getNumberOfPartners() >= matchingRuleProperties.getMaxPartners(); // 5 + public List getMatchedParticipants() { + return participantGroup.getParticipants(); } - private boolean isExceedMaxTries(int tries) { - return tries > matchingRuleProperties.getMaxTries(); + private void initialize(final MatchingOperateRequest matchingOperateRequest) { + participantGroup = ParticipantGroup.from(matchingOperateRequest, matchingRuleProperties); // TODO : 이전 매칭 기록, 차단 목록 주고 만들도록 시킴 + languageQueue = LanguageQueue.classifyByLanguage(participantGroup); + matchingTypeGroup = MatchingTypeGroup.init(matchingRuleProperties); } } diff --git a/src/main/java/com/aliens/backend/mathcing/business/model/BlockHistoryGroup.java b/src/main/java/com/aliens/backend/mathcing/business/model/BlockHistoryGroup.java new file mode 100644 index 00000000..36ef999e --- /dev/null +++ b/src/main/java/com/aliens/backend/mathcing/business/model/BlockHistoryGroup.java @@ -0,0 +1,25 @@ +package com.aliens.backend.mathcing.business.model; + +import com.aliens.backend.block.domain.Block; +import com.aliens.backend.mathcing.domain.MatchingApplication; + +import java.util.List; + +public class BlockHistoryGroup { + private final List blockHistories; + + private BlockHistoryGroup(final List blockHistories) { + this.blockHistories = blockHistories; + } + + public static BlockHistoryGroup of(final List blockHistories) { + return new BlockHistoryGroup(blockHistories); + } + + public List getBlockHistoriesWith(MatchingApplication matchingApplication) { + List filteredBlockHistories = blockHistories.stream() + .filter(blockHistory -> blockHistory.getBlockingMemberId().equals(matchingApplication.getMemberId())) + .toList(); + return filteredBlockHistories; + } +} diff --git a/src/main/java/com/aliens/backend/mathcing/business/model/BlockedPartnerGroup.java b/src/main/java/com/aliens/backend/mathcing/business/model/BlockedPartnerGroup.java new file mode 100644 index 00000000..9214371f --- /dev/null +++ b/src/main/java/com/aliens/backend/mathcing/business/model/BlockedPartnerGroup.java @@ -0,0 +1,30 @@ +package com.aliens.backend.mathcing.business.model; + +import com.aliens.backend.block.domain.Block; + +import java.util.List; + +public class BlockedPartnerGroup { + private final List blockedPartners; + + public BlockedPartnerGroup(final List blockedPartners) { + this.blockedPartners = blockedPartners; + } + + public static BlockedPartnerGroup from(final List blockHistories) { + List blockedPartners = blockHistories.stream() + .mapToLong(Block::getBlockedMemberId).boxed().toList(); + return new BlockedPartnerGroup(blockedPartners); + } + + public boolean contains(Participant participant) { + return blockedPartners.contains(participant.memberId()); + } + + @Override + public String toString() { + return "BlockedPartnerGroup{" + + "blockedPartners=" + blockedPartners + + '}'; + } +} diff --git a/src/main/java/com/aliens/backend/mathcing/business/model/CandidateGroup.java b/src/main/java/com/aliens/backend/mathcing/business/model/CandidateGroup.java new file mode 100644 index 00000000..99f20a33 --- /dev/null +++ b/src/main/java/com/aliens/backend/mathcing/business/model/CandidateGroup.java @@ -0,0 +1,37 @@ +package com.aliens.backend.mathcing.business.model; + +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +public class CandidateGroup { + private final Queue candidateQueue; + + private CandidateGroup(final Queue candidateQueue) { + this.candidateQueue = candidateQueue; + } + + public static CandidateGroup initWithEmpty() { + return new CandidateGroup(new LinkedList<>()); + } + + public static CandidateGroup of(ParticipantGroup participantGroup) { + return new CandidateGroup(new LinkedList<>(participantGroup.getParticipants())); + } + + public void add(Participant participant) { + candidateQueue.add(participant); + } + + public void addAll(List participants) { + candidateQueue.addAll(participants); + } + + public Participant poll() { + return candidateQueue.poll(); + } + + public boolean isEmpty() { + return candidateQueue.isEmpty(); + } +} diff --git a/src/main/java/com/aliens/backend/mathcing/business/model/Language.java b/src/main/java/com/aliens/backend/mathcing/business/model/Language.java new file mode 100644 index 00000000..5e35718f --- /dev/null +++ b/src/main/java/com/aliens/backend/mathcing/business/model/Language.java @@ -0,0 +1,9 @@ +package com.aliens.backend.mathcing.business.model; + +public enum Language { + KOREAN, + ENGLISH, + JAPANESE, + CHINESE + ; +} diff --git a/src/main/java/com/aliens/backend/mathcing/business/model/LanguageQueue.java b/src/main/java/com/aliens/backend/mathcing/business/model/LanguageQueue.java new file mode 100644 index 00000000..b02af574 --- /dev/null +++ b/src/main/java/com/aliens/backend/mathcing/business/model/LanguageQueue.java @@ -0,0 +1,40 @@ +package com.aliens.backend.mathcing.business.model; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class LanguageQueue { + private final Map languageQueue; + + private LanguageQueue(final Map languageQueue) { + this.languageQueue = languageQueue; + } + + public static LanguageQueue classifyByLanguage(final ParticipantGroup participantGroup) { + Map languageQueue = createEmptyLanguageQueues(); + classifyParticipantsByLanguage(languageQueue, participantGroup); + + return new LanguageQueue(languageQueue); + } + + public CandidateGroup getCandidateGroupByLanguage(final Language language) { + return languageQueue.get(language); + } + + private static Map createEmptyLanguageQueues() { + Map languageQueue = new HashMap<>(); + List languages = List.of(Language.values()); + languages.forEach(language -> languageQueue.put(language, CandidateGroup.initWithEmpty())); + return languageQueue; + } + + private static void classifyParticipantsByLanguage(final Map languageQueue, + final ParticipantGroup participantGroup) { + for (Language language : Language.values()) { + List participants = participantGroup.getParticipantsByLanguage(language); + CandidateGroup candidateGroup = languageQueue.get(language); + candidateGroup.addAll(participants); + } + } +} diff --git a/src/main/java/com/aliens/backend/mathcing/service/model/MatchingMode.java b/src/main/java/com/aliens/backend/mathcing/business/model/MatchingMode.java similarity index 69% rename from src/main/java/com/aliens/backend/mathcing/service/model/MatchingMode.java rename to src/main/java/com/aliens/backend/mathcing/business/model/MatchingMode.java index 82909c58..6948809b 100644 --- a/src/main/java/com/aliens/backend/mathcing/service/model/MatchingMode.java +++ b/src/main/java/com/aliens/backend/mathcing/business/model/MatchingMode.java @@ -1,4 +1,4 @@ -package com.aliens.backend.mathcing.service.model; +package com.aliens.backend.mathcing.business.model; public enum MatchingMode { FIRST_PREFER_LANGUAGE, diff --git a/src/main/java/com/aliens/backend/mathcing/business/model/MatchingResultGroup.java b/src/main/java/com/aliens/backend/mathcing/business/model/MatchingResultGroup.java new file mode 100644 index 00000000..59a23dad --- /dev/null +++ b/src/main/java/com/aliens/backend/mathcing/business/model/MatchingResultGroup.java @@ -0,0 +1,25 @@ +package com.aliens.backend.mathcing.business.model; + +import com.aliens.backend.mathcing.domain.MatchingApplication; +import com.aliens.backend.mathcing.domain.MatchingResult; + +import java.util.List; + +public class MatchingResultGroup { + private final List matchingResults; + + private MatchingResultGroup(final List matchingResults) { + this.matchingResults = matchingResults; + } + + public static MatchingResultGroup of(List matchingResults) { + return new MatchingResultGroup(matchingResults); + } + + public List getMatchingResultsWith(MatchingApplication matchingApplication) { + List filteredMatchingResult = matchingResults.stream() + .filter(matchingResult -> matchingApplication.getMemberId().equals(matchingResult.getMatchingMemberId())) + .toList(); + return filteredMatchingResult; + } +} diff --git a/src/main/java/com/aliens/backend/mathcing/business/model/MatchingTypeGroup.java b/src/main/java/com/aliens/backend/mathcing/business/model/MatchingTypeGroup.java new file mode 100644 index 00000000..0699bdac --- /dev/null +++ b/src/main/java/com/aliens/backend/mathcing/business/model/MatchingTypeGroup.java @@ -0,0 +1,26 @@ +package com.aliens.backend.mathcing.business.model; + +import com.aliens.backend.global.property.MatchingRuleProperties; +import com.aliens.backend.mathcing.business.type.*; + +import java.util.List; + +public class MatchingTypeGroup { + private final List matchingTypes; + + private MatchingTypeGroup(final List matchingTypes) { + this.matchingTypes = matchingTypes; + } + + public static MatchingTypeGroup init(final MatchingRuleProperties matchingRuleProperties) { + return new MatchingTypeGroup( + List.of(new FirstPreferLanguageType(matchingRuleProperties), + new SecondPreferLanguageType(matchingRuleProperties), + new RandomType(matchingRuleProperties), + new SpecialType(matchingRuleProperties))); + } + + public void matchParticipants(ParticipantGroup participantGroup, LanguageQueue languageQueue) { + matchingTypes.forEach(types -> types.doMatch(participantGroup, languageQueue)); + } +} diff --git a/src/main/java/com/aliens/backend/mathcing/service/model/Participant.java b/src/main/java/com/aliens/backend/mathcing/business/model/Participant.java similarity index 56% rename from src/main/java/com/aliens/backend/mathcing/service/model/Participant.java rename to src/main/java/com/aliens/backend/mathcing/business/model/Participant.java index 2ddd3895..6c24bf5b 100644 --- a/src/main/java/com/aliens/backend/mathcing/service/model/Participant.java +++ b/src/main/java/com/aliens/backend/mathcing/business/model/Participant.java @@ -1,8 +1,10 @@ -package com.aliens.backend.mathcing.service.model; +package com.aliens.backend.mathcing.business.model; +import com.aliens.backend.block.domain.Block; import com.aliens.backend.global.response.error.MatchingError; import com.aliens.backend.global.exception.RestApiException; import com.aliens.backend.mathcing.domain.MatchingApplication; +import com.aliens.backend.mathcing.domain.MatchingResult; import java.util.ArrayList; import java.util.List; @@ -11,7 +13,9 @@ public record Participant( Long memberId, Language firstPreferLanguage, Language secondPreferLanguage, - List partners + List partners, + PreviousPartnerGroup previousPartnerGroup, + BlockedPartnerGroup blockedPartnerGroup ) { public Language getPreferLanguage(MatchingMode matchingMode) { if (matchingMode.equals(MatchingMode.FIRST_PREFER_LANGUAGE)) { @@ -23,14 +27,20 @@ public Language getPreferLanguage(MatchingMode matchingMode) { throw new RestApiException(MatchingError.NOT_FOUND_PREFER_LANGUAGE); } - public static Participant of(final MatchingApplication matchingApplication) { + public static Participant from(final MatchingApplication matchingApplication, + final List previousMatchingResults, + final List blockHistories) { + PreviousPartnerGroup previousPartnerGroup = PreviousPartnerGroup.from(previousMatchingResults); + BlockedPartnerGroup blockedPartnerGroup = BlockedPartnerGroup.from(blockHistories); + return new Participant( - matchingApplication.getId().getMemberId(), + matchingApplication.getMemberId(), matchingApplication.getFirstPreferLanguage(), matchingApplication.getSecondPreferLanguage(), - new ArrayList<>() + new ArrayList<>(), previousPartnerGroup, blockedPartnerGroup ); } + public int getNumberOfPartners() { return partners.size(); } @@ -47,4 +57,12 @@ public boolean isPartnerWith(Participant participant) { } return false; } + + public boolean hasMetPreviousRound(Participant participant) { + return previousPartnerGroup.contains(participant); + } + + public boolean hasBlocked(Participant participant) { + return blockedPartnerGroup.contains(participant); + } } diff --git a/src/main/java/com/aliens/backend/mathcing/business/model/ParticipantGroup.java b/src/main/java/com/aliens/backend/mathcing/business/model/ParticipantGroup.java new file mode 100644 index 00000000..1311827b --- /dev/null +++ b/src/main/java/com/aliens/backend/mathcing/business/model/ParticipantGroup.java @@ -0,0 +1,130 @@ +package com.aliens.backend.mathcing.business.model; + +import com.aliens.backend.block.domain.Block; +import com.aliens.backend.global.property.MatchingRuleProperties; +import com.aliens.backend.mathcing.controller.dto.request.MatchingOperateRequest; +import com.aliens.backend.mathcing.domain.MatchingApplication; +import com.aliens.backend.mathcing.domain.MatchingResult; + +import java.util.List; + +public class ParticipantGroup { + private final List participants; + private final MatchingRuleProperties matchingRuleProperties; + private Relationship relationship; + + private ParticipantGroup(final List participants, + final MatchingRuleProperties matchingRuleProperties) { + this.participants = participants; + this.matchingRuleProperties = matchingRuleProperties; + this.relationship = Relationship.NORMAL; + } + + public static ParticipantGroup from(final MatchingOperateRequest matchingOperateRequest, + final MatchingRuleProperties matchingRuleProperties) { + List matchingApplications = matchingOperateRequest.matchingApplications(); + MatchingResultGroup previousMatchingResultGroup = matchingOperateRequest.previousMatchingResultGroup(); + BlockHistoryGroup blockHistoryGroup = matchingOperateRequest.blockHistoryGroup(); + + List participants = matchingApplications.stream() + .map(matchingApplication -> { + List previousMatchingResults = previousMatchingResultGroup.getMatchingResultsWith(matchingApplication); + List blockHistories = blockHistoryGroup.getBlockHistoriesWith(matchingApplication); + return Participant.from(matchingApplication, previousMatchingResults, blockHistories); + }).toList(); + return new ParticipantGroup(participants, matchingRuleProperties); + } + + public void matchAllWith(CandidateGroup candidateGroup) { + for (Participant participant : participants) { + tryMatchBetween(participant, candidateGroup); + } + } + + public void matchEachWith(LanguageQueue languageQueue, MatchingMode matchingMode) { + for (Participant participant : participants) { + Language preferLanguage = participant.getPreferLanguage(matchingMode); + CandidateGroup candidateGroup = languageQueue.getCandidateGroupByLanguage(preferLanguage); + tryMatchBetween(participant, candidateGroup); + } + } + + public void updateToSpecialRelationshipMode() { + this.relationship = Relationship.SPECIAL; + } + + public ParticipantGroup getParticipantsLessThan(final int numberOfPartner) { + List filteredParticipants = participants.stream() + .filter(participant -> participant.getNumberOfPartners() < numberOfPartner).toList(); + return new ParticipantGroup(filteredParticipants, matchingRuleProperties); + } + + public List getParticipantsByLanguage(Language language) { + return participants.stream().filter(participant -> participant.firstPreferLanguage().equals(language)).toList(); + } + + public List getParticipants() { + return participants; + } + + private void tryMatchBetween(final Participant participant, final CandidateGroup candidates) { + int tries = 0; + while (canContinueMatching(relationship, participant, tries, candidates)) { + Participant partner = candidates.poll(); + tries++; + if (isValidMatching(relationship, participant, partner)) { + addMatching(participant, partner); + if (!isExceededMaxPartners(relationship, partner)) { + candidates.add(partner); + } + } else { + candidates.add(partner); + } + } + } + + private boolean canContinueMatching(final Relationship relationship, + final Participant participant, + int tries, + final CandidateGroup candidates) { + return !isExceededMaxPartners(relationship, participant) && !isExceedMaxTries(tries) && !candidates.isEmpty(); + } + + private void addMatching(final Participant participant, final Participant partner) { + participant.addPartner(relationship, partner.memberId()); + partner.addPartner(relationship, participant.memberId()); + } + + private boolean isValidMatching(final Relationship relationship, + final Participant participant, + final Participant partner) { + return participant != partner && + !hasMetBetween(participant, partner) && + !isPartnerBetween(participant, partner) && + !hasBlockedBetween(participant, partner) && + !isExceededMaxPartners(relationship, partner); + } + + private boolean isExceededMaxPartners(final Relationship relationship, final Participant participant) { + if (relationship.equals(Relationship.NORMAL)) { + return participant.getNumberOfPartners() >= matchingRuleProperties.getMaxNormalPartners(); // 4 + } + return participant.getNumberOfPartners() >= matchingRuleProperties.getMaxPartners(); // 5 + } + + private boolean hasMetBetween(final Participant participant, final Participant partner) { + return participant.hasMetPreviousRound(partner) || partner.hasMetPreviousRound(participant); + } + + private boolean isPartnerBetween(final Participant participant, final Participant partner) { + return participant.isPartnerWith(partner) || partner.isPartnerWith(participant); + } + + private boolean hasBlockedBetween(final Participant participant, final Participant partner) { + return participant.hasBlocked(partner) || partner.hasBlocked(participant); + } + + private boolean isExceedMaxTries(int tries) { + return tries > matchingRuleProperties.getMaxTries(); + } +} diff --git a/src/main/java/com/aliens/backend/mathcing/service/model/Partner.java b/src/main/java/com/aliens/backend/mathcing/business/model/Partner.java similarity index 80% rename from src/main/java/com/aliens/backend/mathcing/service/model/Partner.java rename to src/main/java/com/aliens/backend/mathcing/business/model/Partner.java index 288f1f0c..e1acc020 100644 --- a/src/main/java/com/aliens/backend/mathcing/service/model/Partner.java +++ b/src/main/java/com/aliens/backend/mathcing/business/model/Partner.java @@ -1,4 +1,4 @@ -package com.aliens.backend.mathcing.service.model; +package com.aliens.backend.mathcing.business.model; public record Partner( Relationship relationship, diff --git a/src/main/java/com/aliens/backend/mathcing/business/model/PreviousPartnerGroup.java b/src/main/java/com/aliens/backend/mathcing/business/model/PreviousPartnerGroup.java new file mode 100644 index 00000000..6e0034d9 --- /dev/null +++ b/src/main/java/com/aliens/backend/mathcing/business/model/PreviousPartnerGroup.java @@ -0,0 +1,30 @@ +package com.aliens.backend.mathcing.business.model; + +import com.aliens.backend.mathcing.domain.MatchingResult; + +import java.util.List; + +public class PreviousPartnerGroup { + private final List previousPartners; + + private PreviousPartnerGroup(final List previousPartners) { + this.previousPartners = previousPartners; + } + + public static PreviousPartnerGroup from(final List previousMatchingResults) { + List previousPartners = previousMatchingResults.stream() + .mapToLong(MatchingResult::getMatchedMemberId).boxed().toList(); + return new PreviousPartnerGroup(previousPartners); + } + + public boolean contains(Participant participant) { + return previousPartners.contains(participant.memberId()); + } + + @Override + public String toString() { + return "PreviousPartnerGroup{" + + "previousPartners=" + previousPartners + + '}'; + } +} diff --git a/src/main/java/com/aliens/backend/mathcing/service/model/Relationship.java b/src/main/java/com/aliens/backend/mathcing/business/model/Relationship.java similarity index 53% rename from src/main/java/com/aliens/backend/mathcing/service/model/Relationship.java rename to src/main/java/com/aliens/backend/mathcing/business/model/Relationship.java index 04b97fbd..cc54a174 100644 --- a/src/main/java/com/aliens/backend/mathcing/service/model/Relationship.java +++ b/src/main/java/com/aliens/backend/mathcing/business/model/Relationship.java @@ -1,4 +1,4 @@ -package com.aliens.backend.mathcing.service.model; +package com.aliens.backend.mathcing.business.model; public enum Relationship { NORMAL, diff --git a/src/main/java/com/aliens/backend/mathcing/business/type/FirstPreferLanguageType.java b/src/main/java/com/aliens/backend/mathcing/business/type/FirstPreferLanguageType.java new file mode 100644 index 00000000..faac2513 --- /dev/null +++ b/src/main/java/com/aliens/backend/mathcing/business/type/FirstPreferLanguageType.java @@ -0,0 +1,19 @@ +package com.aliens.backend.mathcing.business.type; + +import com.aliens.backend.global.property.MatchingRuleProperties; +import com.aliens.backend.mathcing.business.model.LanguageQueue; +import com.aliens.backend.mathcing.business.model.MatchingMode; +import com.aliens.backend.mathcing.business.model.ParticipantGroup; + +public class FirstPreferLanguageType implements MatchingType { + private final MatchingRuleProperties matchingRuleProperties; + + public FirstPreferLanguageType(final MatchingRuleProperties matchingRuleProperties) { + this.matchingRuleProperties = matchingRuleProperties; + } + + @Override + public void doMatch(ParticipantGroup participantGroup, LanguageQueue languageQueue) { + participantGroup.matchEachWith(languageQueue, MatchingMode.FIRST_PREFER_LANGUAGE); + } +} diff --git a/src/main/java/com/aliens/backend/mathcing/business/type/MatchingType.java b/src/main/java/com/aliens/backend/mathcing/business/type/MatchingType.java new file mode 100644 index 00000000..2b5681bd --- /dev/null +++ b/src/main/java/com/aliens/backend/mathcing/business/type/MatchingType.java @@ -0,0 +1,8 @@ +package com.aliens.backend.mathcing.business.type; + +import com.aliens.backend.mathcing.business.model.LanguageQueue; +import com.aliens.backend.mathcing.business.model.ParticipantGroup; + +public interface MatchingType { + void doMatch(ParticipantGroup participantGroup, LanguageQueue languageQueue); +} diff --git a/src/main/java/com/aliens/backend/mathcing/business/type/RandomType.java b/src/main/java/com/aliens/backend/mathcing/business/type/RandomType.java new file mode 100644 index 00000000..9ef79667 --- /dev/null +++ b/src/main/java/com/aliens/backend/mathcing/business/type/RandomType.java @@ -0,0 +1,21 @@ +package com.aliens.backend.mathcing.business.type; + +import com.aliens.backend.global.property.MatchingRuleProperties; +import com.aliens.backend.mathcing.business.model.CandidateGroup; +import com.aliens.backend.mathcing.business.model.LanguageQueue; +import com.aliens.backend.mathcing.business.model.ParticipantGroup; + +public class RandomType implements MatchingType { + private final MatchingRuleProperties matchingRuleProperties; + + public RandomType(final MatchingRuleProperties matchingRuleProperties) { + this.matchingRuleProperties = matchingRuleProperties; + } + + @Override + public void doMatch(final ParticipantGroup participantGroup, final LanguageQueue languageQueue) { + ParticipantGroup remainedParticipants = participantGroup.getParticipantsLessThan(matchingRuleProperties.getMaxNormalPartners()); + remainedParticipants.updateToSpecialRelationshipMode(); + remainedParticipants.matchAllWith(CandidateGroup.of(remainedParticipants)); + } +} diff --git a/src/main/java/com/aliens/backend/mathcing/business/type/SecondPreferLanguageType.java b/src/main/java/com/aliens/backend/mathcing/business/type/SecondPreferLanguageType.java new file mode 100644 index 00000000..4cc738ee --- /dev/null +++ b/src/main/java/com/aliens/backend/mathcing/business/type/SecondPreferLanguageType.java @@ -0,0 +1,20 @@ +package com.aliens.backend.mathcing.business.type; + +import com.aliens.backend.global.property.MatchingRuleProperties; +import com.aliens.backend.mathcing.business.model.LanguageQueue; +import com.aliens.backend.mathcing.business.model.MatchingMode; +import com.aliens.backend.mathcing.business.model.ParticipantGroup; + +public class SecondPreferLanguageType implements MatchingType { + private final MatchingRuleProperties matchingRuleProperties; + + public SecondPreferLanguageType(final MatchingRuleProperties matchingRuleProperties) { + this.matchingRuleProperties = matchingRuleProperties; + } + + @Override + public void doMatch(final ParticipantGroup participantGroup, final LanguageQueue languageQueue) { + ParticipantGroup remainedParticipants = participantGroup.getParticipantsLessThan(matchingRuleProperties.getMaxNormalPartners()); + remainedParticipants.matchEachWith(languageQueue, MatchingMode.SECOND_PREFER_LANGUAGE); + } +} diff --git a/src/main/java/com/aliens/backend/mathcing/business/type/SpecialType.java b/src/main/java/com/aliens/backend/mathcing/business/type/SpecialType.java new file mode 100644 index 00000000..81b1be03 --- /dev/null +++ b/src/main/java/com/aliens/backend/mathcing/business/type/SpecialType.java @@ -0,0 +1,21 @@ +package com.aliens.backend.mathcing.business.type; + +import com.aliens.backend.global.property.MatchingRuleProperties; +import com.aliens.backend.mathcing.business.model.CandidateGroup; +import com.aliens.backend.mathcing.business.model.LanguageQueue; +import com.aliens.backend.mathcing.business.model.ParticipantGroup; + +public class SpecialType implements MatchingType { + private final MatchingRuleProperties matchingRuleProperties; + + public SpecialType(final MatchingRuleProperties matchingRuleProperties) { + this.matchingRuleProperties = matchingRuleProperties; + } + + @Override + public void doMatch(final ParticipantGroup participantGroup, final LanguageQueue languageQueue) { + ParticipantGroup remainedParticipants = participantGroup.getParticipantsLessThan(matchingRuleProperties.getMaxPartners()); + remainedParticipants.updateToSpecialRelationshipMode(); + remainedParticipants.matchAllWith(CandidateGroup.of(remainedParticipants)); + } +} diff --git a/src/main/java/com/aliens/backend/mathcing/controller/MatchingApplicationController.java b/src/main/java/com/aliens/backend/mathcing/controller/MatchingApplicationController.java new file mode 100644 index 00000000..eef63ac7 --- /dev/null +++ b/src/main/java/com/aliens/backend/mathcing/controller/MatchingApplicationController.java @@ -0,0 +1,40 @@ +package com.aliens.backend.mathcing.controller; + +import com.aliens.backend.auth.controller.dto.LoginMember; +import com.aliens.backend.global.config.resolver.Login; +import com.aliens.backend.global.response.SuccessResponse; +import com.aliens.backend.global.response.success.MatchingSuccess; +import com.aliens.backend.global.validator.LanguageCheck; +import com.aliens.backend.mathcing.controller.dto.request.MatchingApplicationRequest; +import com.aliens.backend.mathcing.controller.dto.response.MatchingApplicationResponse; +import com.aliens.backend.mathcing.service.MatchingApplicationService; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/matchings") +public class MatchingApplicationController { + private final MatchingApplicationService matchingApplicationService; + + public MatchingApplicationController(final MatchingApplicationService matchingApplicationService) { + this.matchingApplicationService = matchingApplicationService; + } + + @PostMapping("/applications") + public SuccessResponse applyMatch(final @Login LoginMember loginMember, + final @RequestBody @LanguageCheck MatchingApplicationRequest matchingApplicationRequest) { + return SuccessResponse.of(MatchingSuccess.APPLY_MATCHING_SUCCESS, + matchingApplicationService.saveParticipant(loginMember, matchingApplicationRequest)); + } + + @GetMapping("/applications") + public SuccessResponse getMatchingApplication(final @Login LoginMember loginMember) { + return SuccessResponse.of(MatchingSuccess.GET_MATCHING_APPLICATION_STATUS_SUCCESS, + matchingApplicationService.findMatchingApplication(loginMember)); + } + + @DeleteMapping("/applications") + public SuccessResponse cancelMatchingApplication(final @Login LoginMember loginMember) { + return SuccessResponse.of(MatchingSuccess.CANCEL_MATCHING_APPLICATION_SUCCESS, + matchingApplicationService.cancelMatchingApplication(loginMember)); + } +} diff --git a/src/main/java/com/aliens/backend/mathcing/controller/MatchingController.java b/src/main/java/com/aliens/backend/mathcing/controller/MatchingController.java deleted file mode 100644 index ce50db2a..00000000 --- a/src/main/java/com/aliens/backend/mathcing/controller/MatchingController.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.aliens.backend.mathcing.controller; - -import com.aliens.backend.auth.controller.dto.LoginMember; -import com.aliens.backend.global.config.resolver.Login; -import com.aliens.backend.global.response.success.MatchingSuccess; -import com.aliens.backend.global.response.SuccessResponse; -import com.aliens.backend.mathcing.controller.dto.response.MatchingResponse; -import com.aliens.backend.mathcing.service.MatchingApplicationService; -import com.aliens.backend.mathcing.service.MatchingService; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -import static com.aliens.backend.mathcing.controller.dto.input.MatchingInput.*; - -@RestController -@RequestMapping("/matchings") -public class MatchingController { - private final MatchingApplicationService matchingApplicationService; - private final MatchingService matchingService; - - public MatchingController(final MatchingApplicationService matchingApplicationService, - final MatchingService matchingService) { - this.matchingApplicationService = matchingApplicationService; - this.matchingService = matchingService; - } - - @PostMapping("/applications") - public SuccessResponse applyMatch(final @Login LoginMember loginMember, - final @RequestBody MatchingApplicationInput input) { - - return SuccessResponse.of( - MatchingSuccess.APPLY_MATCHING_SUCCESS, - matchingApplicationService.saveParticipant(input.toRequest(loginMember.memberId())) - ); - } - - @GetMapping("/applications") - public SuccessResponse getMatchingApplication(final @Login LoginMember loginMember) { - - return SuccessResponse.of( - MatchingSuccess.GET_MATCHING_APPLICATION_STATUS_SUCCESS, - matchingApplicationService.findMatchingApplication(loginMember.memberId()) - ); - } - - @DeleteMapping("/applications") - public SuccessResponse cancelMatchingApplication(final @Login LoginMember loginMember) { - - return SuccessResponse.of( - MatchingSuccess.CANCEL_MATCHING_APPLICATION_SUCCESS, - matchingApplicationService.deleteMatchingApplication(loginMember.memberId()) - ); - } - - @GetMapping("/partners") - public SuccessResponse> getMatchingPartners(final @Login LoginMember loginMember) { - - return SuccessResponse.of( - MatchingSuccess.GET_MATCHING_PARTNERS_SUCCESS, - matchingService.findMatchingResult(loginMember.memberId()) - ); - } -} diff --git a/src/main/java/com/aliens/backend/mathcing/controller/MatchingProcessController.java b/src/main/java/com/aliens/backend/mathcing/controller/MatchingProcessController.java new file mode 100644 index 00000000..2772a5f5 --- /dev/null +++ b/src/main/java/com/aliens/backend/mathcing/controller/MatchingProcessController.java @@ -0,0 +1,27 @@ +package com.aliens.backend.mathcing.controller; + +import com.aliens.backend.auth.controller.dto.LoginMember; +import com.aliens.backend.global.config.resolver.Login; +import com.aliens.backend.global.response.success.MatchingSuccess; +import com.aliens.backend.global.response.SuccessResponse; +import com.aliens.backend.mathcing.controller.dto.response.MatchingResultResponse; +import com.aliens.backend.mathcing.service.MatchingProcessService; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/matchings") +public class MatchingProcessController { + private final MatchingProcessService matchingProcessService; + + public MatchingProcessController(final MatchingProcessService matchingProcessService) { + this.matchingProcessService = matchingProcessService; + } + + @GetMapping("/partners") + public SuccessResponse> getMatchingPartners(final @Login LoginMember loginMember) { + return SuccessResponse.of(MatchingSuccess.GET_MATCHING_PARTNERS_SUCCESS, + matchingProcessService.findMatchingResult(loginMember)); + } +} diff --git a/src/main/java/com/aliens/backend/mathcing/controller/dto/input/MatchingInput.java b/src/main/java/com/aliens/backend/mathcing/controller/dto/input/MatchingInput.java deleted file mode 100644 index cc1365e7..00000000 --- a/src/main/java/com/aliens/backend/mathcing/controller/dto/input/MatchingInput.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.aliens.backend.mathcing.controller.dto.input; - -import com.aliens.backend.global.response.error.MatchingError; -import com.aliens.backend.global.exception.RestApiException; -import com.aliens.backend.mathcing.service.model.Language; - -import static com.aliens.backend.mathcing.controller.dto.request.MatchingRequest.*; - -public class MatchingInput { - public record MatchingApplicationInput( - Language firstPreferLanguage, - Language secondPreferLanguage) { - public MatchingApplicationRequest toRequest(final Long memberId){ - validateInput(); - return new MatchingApplicationRequest(memberId, firstPreferLanguage, secondPreferLanguage); - } - - public static MatchingApplicationInput of(Language firstPreferLanguage, Language secondPreferLanguage) { - return new MatchingApplicationInput(firstPreferLanguage, secondPreferLanguage); - } - - private void validateInput() { - if (firstPreferLanguage.equals(secondPreferLanguage)) { - throw new RestApiException(MatchingError.INVALID_LANGUAGE_INPUT); - } - } - } -} diff --git a/src/main/java/com/aliens/backend/mathcing/controller/dto/request/MatchingApplicationRequest.java b/src/main/java/com/aliens/backend/mathcing/controller/dto/request/MatchingApplicationRequest.java new file mode 100644 index 00000000..5d2d6907 --- /dev/null +++ b/src/main/java/com/aliens/backend/mathcing/controller/dto/request/MatchingApplicationRequest.java @@ -0,0 +1,9 @@ +package com.aliens.backend.mathcing.controller.dto.request; + +import com.aliens.backend.mathcing.business.model.Language; + +public record MatchingApplicationRequest( + Language firstPreferLanguage, + Language secondPreferLanguage +) { +} diff --git a/src/main/java/com/aliens/backend/mathcing/controller/dto/request/MatchingOperateRequest.java b/src/main/java/com/aliens/backend/mathcing/controller/dto/request/MatchingOperateRequest.java new file mode 100644 index 00000000..205ea931 --- /dev/null +++ b/src/main/java/com/aliens/backend/mathcing/controller/dto/request/MatchingOperateRequest.java @@ -0,0 +1,23 @@ +package com.aliens.backend.mathcing.controller.dto.request; + +import com.aliens.backend.block.domain.Block; +import com.aliens.backend.mathcing.business.model.BlockHistoryGroup; +import com.aliens.backend.mathcing.business.model.MatchingResultGroup; +import com.aliens.backend.mathcing.domain.MatchingApplication; +import com.aliens.backend.mathcing.domain.MatchingResult; + +import java.util.List; + +public record MatchingOperateRequest( + List matchingApplications, + MatchingResultGroup previousMatchingResultGroup, + BlockHistoryGroup blockHistoryGroup +) { + public static MatchingOperateRequest of(List matchingApplications, + List previousMatchingResults, + List blockHistories) { + return new MatchingOperateRequest(matchingApplications, + MatchingResultGroup.of(previousMatchingResults), + BlockHistoryGroup.of(blockHistories)); + } +} \ No newline at end of file diff --git a/src/main/java/com/aliens/backend/mathcing/controller/dto/request/MatchingRequest.java b/src/main/java/com/aliens/backend/mathcing/controller/dto/request/MatchingRequest.java deleted file mode 100644 index dbd8943c..00000000 --- a/src/main/java/com/aliens/backend/mathcing/controller/dto/request/MatchingRequest.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.aliens.backend.mathcing.controller.dto.request; - -import com.aliens.backend.mathcing.domain.MatchingApplication; -import com.aliens.backend.mathcing.domain.MatchingRound; -import com.aliens.backend.mathcing.service.model.Language; - -public class MatchingRequest { - public record MatchingApplicationRequest( - Long memberId, - Language firstPreferLanguage, - Language secondPreferLanguage - ) { - public MatchingApplication toEntity(MatchingRound matchingRound) { - return MatchingApplication.of(matchingRound, memberId, firstPreferLanguage, secondPreferLanguage); - } - } -} diff --git a/src/main/java/com/aliens/backend/mathcing/controller/dto/response/MatchingApplicationResponse.java b/src/main/java/com/aliens/backend/mathcing/controller/dto/response/MatchingApplicationResponse.java new file mode 100644 index 00000000..828d469c --- /dev/null +++ b/src/main/java/com/aliens/backend/mathcing/controller/dto/response/MatchingApplicationResponse.java @@ -0,0 +1,18 @@ +package com.aliens.backend.mathcing.controller.dto.response; + +import com.aliens.backend.mathcing.domain.MatchingApplication; +import com.aliens.backend.mathcing.business.model.Language; + +public record MatchingApplicationResponse( + Long matchingRound, + Long memberId, + Language firstPreferLanguage, + Language secondPreferLanguage) { + public static MatchingApplicationResponse from(MatchingApplication matchingApplication) { + return new MatchingApplicationResponse( + matchingApplication.getRound(), + matchingApplication.getMemberId(), + matchingApplication.getFirstPreferLanguage(), + matchingApplication.getSecondPreferLanguage()); + } +} \ No newline at end of file diff --git a/src/main/java/com/aliens/backend/mathcing/controller/dto/response/MatchingResponse.java b/src/main/java/com/aliens/backend/mathcing/controller/dto/response/MatchingResponse.java deleted file mode 100644 index c2415b8a..00000000 --- a/src/main/java/com/aliens/backend/mathcing/controller/dto/response/MatchingResponse.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.aliens.backend.mathcing.controller.dto.response; - -import com.aliens.backend.mathcing.domain.MatchingApplication; -import com.aliens.backend.mathcing.domain.MatchingResult; -import com.aliens.backend.mathcing.service.model.Language; -import com.aliens.backend.mathcing.service.model.Partner; -import com.aliens.backend.mathcing.service.model.Relationship; - -import java.util.List; - -public class MatchingResponse { - public record MatchingApplicationResponse( - Long matchingRound, - Long memberId, - Language firstPreferLanguage, - Language secondPreferLanguage) { - public static MatchingApplicationResponse of(MatchingApplication matchingApplication) { - return new MatchingApplicationResponse( - matchingApplication.getId().getMatchingRound().getRound(), - matchingApplication.getId().getMemberId(), - matchingApplication.getFirstPreferLanguage(), - matchingApplication.getSecondPreferLanguage()); - } - } - - public record MatchingResultResponse( - Long matchedMemberId, - Relationship relationship - ) { - public static MatchingResultResponse of(MatchingResult matchingResult) { - return new MatchingResultResponse(matchingResult.getId().getMatchedMemberId(), matchingResult.getRelationship()); - } - } -} diff --git a/src/main/java/com/aliens/backend/mathcing/controller/dto/response/MatchingResultResponse.java b/src/main/java/com/aliens/backend/mathcing/controller/dto/response/MatchingResultResponse.java new file mode 100644 index 00000000..625f2ef4 --- /dev/null +++ b/src/main/java/com/aliens/backend/mathcing/controller/dto/response/MatchingResultResponse.java @@ -0,0 +1,13 @@ +package com.aliens.backend.mathcing.controller.dto.response; + +import com.aliens.backend.mathcing.domain.MatchingResult; +import com.aliens.backend.mathcing.business.model.Relationship; + +public record MatchingResultResponse( + Long matchedMemberId, + Relationship relationship +) { + public static MatchingResultResponse from(MatchingResult matchingResult) { + return new MatchingResultResponse(matchingResult.getMatchedMemberId(), matchingResult.getRelationship()); + } +} diff --git a/src/main/java/com/aliens/backend/mathcing/domain/MatchingApplication.java b/src/main/java/com/aliens/backend/mathcing/domain/MatchingApplication.java index 730921da..eec9a9f6 100644 --- a/src/main/java/com/aliens/backend/mathcing/domain/MatchingApplication.java +++ b/src/main/java/com/aliens/backend/mathcing/domain/MatchingApplication.java @@ -1,14 +1,15 @@ package com.aliens.backend.mathcing.domain; +import com.aliens.backend.auth.controller.dto.LoginMember; +import com.aliens.backend.mathcing.controller.dto.request.MatchingApplicationRequest; import com.aliens.backend.mathcing.domain.id.MatchingApplicationId; -import com.aliens.backend.mathcing.service.model.Language; -import com.aliens.backend.mathcing.service.model.Participant; +import com.aliens.backend.mathcing.business.model.Language; +import com.aliens.backend.mathcing.business.model.Participant; import jakarta.persistence.EmbeddedId; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; -import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -34,10 +35,6 @@ private MatchingApplication(final MatchingApplicationId id, this.secondPreferLanguage = secondPreferLanguage; } - public MatchingApplicationId getId() { - return id; - } - public Language getFirstPreferLanguage() { return firstPreferLanguage; } @@ -48,16 +45,29 @@ public Language getSecondPreferLanguage() { public static MatchingApplication of(final MatchingRound matchingRound, final Long memberId, - final Language firstPreferLanguage, final Language secondPreferLanguage) { - return new MatchingApplication( - MatchingApplicationId.of(matchingRound, memberId), - firstPreferLanguage, secondPreferLanguage); + final Language firstPreferLanguage, + final Language secondPreferLanguage) { + return new MatchingApplication(MatchingApplicationId.of(matchingRound, memberId), firstPreferLanguage, secondPreferLanguage); + } + + public static MatchingApplication from(final MatchingRound matchingRound, + final LoginMember loginMember, + final MatchingApplicationRequest matchingApplicationRequest) { + return MatchingApplication.of(matchingRound, loginMember.memberId(), + matchingApplicationRequest.firstPreferLanguage(), + matchingApplicationRequest.secondPreferLanguage()); + } + + public Long getMemberId() { + return id.getMemberId(); + } + + public MatchingRound getMatchingRound() { + return id.getMatchingRound(); } - public static List toParticipantList(final List matchingApplications) { - return matchingApplications.stream() - .map(Participant::of) - .collect(Collectors.toList()); + public Long getRound() { + return getMatchingRound().getRound(); } @Override diff --git a/src/main/java/com/aliens/backend/mathcing/domain/MatchingResult.java b/src/main/java/com/aliens/backend/mathcing/domain/MatchingResult.java index 7c347059..bccbf16d 100644 --- a/src/main/java/com/aliens/backend/mathcing/domain/MatchingResult.java +++ b/src/main/java/com/aliens/backend/mathcing/domain/MatchingResult.java @@ -1,7 +1,7 @@ package com.aliens.backend.mathcing.domain; import com.aliens.backend.mathcing.domain.id.MatchingResultId; -import com.aliens.backend.mathcing.service.model.Relationship; +import com.aliens.backend.mathcing.business.model.Relationship; import jakarta.persistence.*; @Entity @@ -24,12 +24,19 @@ public static MatchingResult of(MatchingRound matchingRound, Long matchingMemberId, Long matchedMemberId, Relationship relationship) { - return new MatchingResult( - MatchingResultId.of(matchingRound, matchingMemberId, matchedMemberId), relationship); + return new MatchingResult(MatchingResultId.of(matchingRound, matchingMemberId, matchedMemberId), relationship); } - public MatchingResultId getId() { - return id; + public MatchingRound getMatchingRound() { + return id.getMatchingRound(); + } + + public Long getMatchingMemberId() { + return id.getMatchingMemberId(); + } + + public Long getMatchedMemberId() { + return id.getMatchedMemberId(); } public Relationship getRelationship() { diff --git a/src/main/java/com/aliens/backend/mathcing/domain/MatchingRound.java b/src/main/java/com/aliens/backend/mathcing/domain/MatchingRound.java index bac8ccb2..ec42c63b 100644 --- a/src/main/java/com/aliens/backend/mathcing/domain/MatchingRound.java +++ b/src/main/java/com/aliens/backend/mathcing/domain/MatchingRound.java @@ -12,17 +12,17 @@ public class MatchingRound { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long round; - @Column(name = "matching_request_start_time") - private LocalDateTime matchingRequestStartTime; + @Column(name = "request_start_time") + private LocalDateTime requestStartTime; - @Column(name = "matching_request_end_time") - private LocalDateTime matchingRequestEndTime; + @Column(name = "request_end_time") + private LocalDateTime requestEndTime; - @Column(name = "matching_valid_start_time") - private LocalDateTime matchingValidStartTime; + @Column(name = "valid_start_time") + private LocalDateTime validStartTime; - @Column(name = "matching_valid_end_time") - private LocalDateTime matchingValidEndTime; + @Column(name = "valid_end_time") + private LocalDateTime validEndTime; protected MatchingRound() { } @@ -31,55 +31,67 @@ public Long getRound() { return round; } - public LocalDateTime getMatchingRequestStartTime() { - return matchingRequestStartTime; + public LocalDateTime getRequestStartTime() { + return requestStartTime; } - public LocalDateTime getMatchingRequestEndTime() { - return matchingRequestEndTime; + public LocalDateTime getRequestEndTime() { + return requestEndTime; } - public LocalDateTime getMatchingValidStartTime() { - return matchingValidStartTime; + public LocalDateTime getValidStartTime() { + return validStartTime; } - public LocalDateTime getMatchingValidEndTime() { - return matchingValidEndTime; + public LocalDateTime getValidEndTime() { + return validEndTime; } - private MatchingRound(final LocalDateTime matchingRequestStartTime, - final LocalDateTime matchingRequestEndTime, - final LocalDateTime matchingValidStartTime, - final LocalDateTime matchingValidEndTime) { - this.matchingRequestStartTime = matchingRequestStartTime; - this.matchingRequestEndTime = matchingRequestEndTime; - this.matchingValidStartTime = matchingValidStartTime; - this.matchingValidEndTime = matchingValidEndTime; + public DayOfWeek getDayOfWeek() { + return requestStartTime.getDayOfWeek(); } - public static MatchingRound of(final LocalDateTime today, final MatchingTimeProperties matchingTimeProperties) { + private MatchingRound(final LocalDateTime requestStartTime, + final LocalDateTime requestEndTime, + final LocalDateTime validStartTime, + final LocalDateTime validEndTime) { + this.requestStartTime = requestStartTime; + this.requestEndTime = requestEndTime; + this.validStartTime = validStartTime; + this.validEndTime = validEndTime; + } + + public static MatchingRound from(final LocalDateTime today, final MatchingTimeProperties matchingTimeProperties) { DayOfWeek dayOfWeek = today.getDayOfWeek(); - LocalDateTime matchingRequestStartTime = today.withHour(0).withMinute(0).withSecond(0).withNano(0); - LocalDateTime matchingRequestEndTime = matchingRequestStartTime.plusHours(matchingTimeProperties.getMatchingRequestAvailableTime()); - LocalDateTime matchingValidStartTime = today.withHour(matchingTimeProperties.getMatchingValidBeginHours()).withMinute(0).withSecond(0).withNano(0); - LocalDateTime matchingValidEndTime = matchingValidStartTime.plusHours(matchingTimeProperties.getMatchingValidHours(dayOfWeek)); + LocalDateTime requestStartTime = today.withHour(0).withMinute(0).withSecond(0).withNano(0); + LocalDateTime requestEndTime = requestStartTime.plusHours(matchingTimeProperties.getRequestAvailableTime()); + LocalDateTime validStartTime = today.withHour(matchingTimeProperties.getValidBeginHours()).withMinute(0).withSecond(0).withNano(0); + LocalDateTime validEndTime = validStartTime.plusHours(matchingTimeProperties.getMatchingValidHours(dayOfWeek)); - return new MatchingRound(matchingRequestStartTime, matchingRequestEndTime, matchingValidStartTime, matchingValidEndTime); + return new MatchingRound(requestStartTime, requestEndTime, validStartTime, validEndTime); } public boolean isReceptionTime(LocalDateTime now) { - return now.isAfter(this.getMatchingRequestStartTime()) && now.isBefore(this.getMatchingRequestEndTime()); + return now.isAfter(this.getRequestStartTime()) && now.isBefore(this.getRequestEndTime()); + } + + public boolean isFirstRound() { + return round == 1; + } + + public Long getPreviousRound() { + return round - 1; } @Override public String toString() { return "MatchingRound{" + "round=" + round + - ", matchingRequestStartTime=" + matchingRequestStartTime + - ", matchingRequestEndTime=" + matchingRequestEndTime + - ", matchingValidStartTime=" + matchingValidStartTime + - ", matchingValidEndTime=" + matchingValidEndTime + + ", requestStartTime=" + requestStartTime + + ", requestEndTime=" + requestEndTime + + ", validStartTime=" + validStartTime + + ", validEndTime=" + validEndTime + '}'; } } diff --git a/src/main/java/com/aliens/backend/mathcing/domain/repository/MatchingRoundRepository.java b/src/main/java/com/aliens/backend/mathcing/domain/repository/MatchingRoundRepository.java index af5f1431..e2f57b47 100644 --- a/src/main/java/com/aliens/backend/mathcing/domain/repository/MatchingRoundRepository.java +++ b/src/main/java/com/aliens/backend/mathcing/domain/repository/MatchingRoundRepository.java @@ -9,6 +9,8 @@ @Repository public interface MatchingRoundRepository extends JpaRepository { - @Query("SELECT mr FROM MatchingRound mr ORDER BY mr.round DESC") + @Query("SELECT mr FROM MatchingRound mr WHERE mr.round = (SELECT MAX(mr.round) FROM MatchingRound mr)") Optional findCurrentRound(); + + Optional findMatchingRoundByRound(Long round); } diff --git a/src/main/java/com/aliens/backend/mathcing/service/MatchingApplicationService.java b/src/main/java/com/aliens/backend/mathcing/service/MatchingApplicationService.java index 8decf16f..77a4514d 100644 --- a/src/main/java/com/aliens/backend/mathcing/service/MatchingApplicationService.java +++ b/src/main/java/com/aliens/backend/mathcing/service/MatchingApplicationService.java @@ -1,8 +1,11 @@ package com.aliens.backend.mathcing.service; +import com.aliens.backend.auth.controller.dto.LoginMember; import com.aliens.backend.global.response.error.MatchingError; import com.aliens.backend.global.exception.RestApiException; import com.aliens.backend.global.response.success.MatchingSuccess; +import com.aliens.backend.mathcing.controller.dto.request.MatchingApplicationRequest; +import com.aliens.backend.mathcing.controller.dto.response.MatchingApplicationResponse; import com.aliens.backend.mathcing.domain.MatchingApplication; import com.aliens.backend.mathcing.domain.MatchingRound; import com.aliens.backend.mathcing.domain.id.MatchingApplicationId; @@ -14,9 +17,6 @@ import java.time.Clock; import java.time.LocalDateTime; -import static com.aliens.backend.mathcing.controller.dto.request.MatchingRequest.*; -import static com.aliens.backend.mathcing.controller.dto.response.MatchingResponse.*; - @Service public class MatchingApplicationService { private final MatchingApplicationRepository matchingApplicationRepository; @@ -32,38 +32,41 @@ public MatchingApplicationService(final MatchingApplicationRepository matchingAp } @Transactional - public String saveParticipant(final MatchingApplicationRequest matchingApplicationRequest) { + public String saveParticipant(final LoginMember loginMember, + final MatchingApplicationRequest matchingApplicationRequest) { MatchingRound currentRound = getCurrentRound(); checkReceptionTime(currentRound); - matchingApplicationRepository.save(matchingApplicationRequest.toEntity(currentRound)); + MatchingApplication matchingApplication = MatchingApplication.from(currentRound, loginMember, matchingApplicationRequest); + matchingApplicationRepository.save(matchingApplication); return MatchingSuccess.APPLY_MATCHING_SUCCESS.getMessage(); } @Transactional(readOnly = true) - public MatchingApplicationResponse findMatchingApplication(final Long memberId) { + public MatchingApplicationResponse findMatchingApplication(final LoginMember loginMember) { MatchingRound currentRound = getCurrentRound(); - MatchingApplication matchingApplication = - matchingApplicationRepository.findById(MatchingApplicationId.of(currentRound, memberId)) - .orElseThrow(()->new RestApiException(MatchingError.NOT_FOUND_MATCHING_APPLICATION_INFO)); - return MatchingApplicationResponse.of(matchingApplication); - } - - private MatchingRound getCurrentRound() { - return matchingRoundRepository.findCurrentRound() - .orElseThrow(()-> new RestApiException(MatchingError.NOT_FOUND_MATCHING_ROUND)); + MatchingApplication matchingApplication = getMatchingApplication(currentRound, loginMember); + return MatchingApplicationResponse.from(matchingApplication); } @Transactional - public String deleteMatchingApplication(final Long memberId) { + public String cancelMatchingApplication(final LoginMember loginMember) { MatchingRound currentRound = getCurrentRound(); checkReceptionTime(currentRound); - MatchingApplication matchingApplication = - matchingApplicationRepository.findById(MatchingApplicationId.of(currentRound, memberId)) - .orElseThrow(()->new RestApiException(MatchingError.NOT_FOUND_MATCHING_APPLICATION_INFO)); + MatchingApplication matchingApplication = getMatchingApplication(currentRound, loginMember); matchingApplicationRepository.delete(matchingApplication); return MatchingSuccess.CANCEL_MATCHING_APPLICATION_SUCCESS.getMessage(); } + private MatchingRound getCurrentRound() { + return matchingRoundRepository.findCurrentRound() + .orElseThrow(()-> new RestApiException(MatchingError.NOT_FOUND_MATCHING_ROUND)); + } + + private MatchingApplication getMatchingApplication(MatchingRound matchingRound, LoginMember loginMember) { + return matchingApplicationRepository.findById(MatchingApplicationId.of(matchingRound, loginMember.memberId())) + .orElseThrow(()->new RestApiException(MatchingError.NOT_FOUND_MATCHING_APPLICATION_INFO)); + } + private void checkReceptionTime(MatchingRound matchingRound) { if (!matchingRound.isReceptionTime(LocalDateTime.now(clock))) { throw new RestApiException(MatchingError.NOT_VALID_MATCHING_RECEPTION_TIME); diff --git a/src/main/java/com/aliens/backend/mathcing/service/MatchingProcessService.java b/src/main/java/com/aliens/backend/mathcing/service/MatchingProcessService.java new file mode 100644 index 00000000..cf659443 --- /dev/null +++ b/src/main/java/com/aliens/backend/mathcing/service/MatchingProcessService.java @@ -0,0 +1,141 @@ +package com.aliens.backend.mathcing.service; + +import com.aliens.backend.auth.controller.dto.LoginMember; +import com.aliens.backend.auth.domain.Member; +import com.aliens.backend.auth.domain.repository.MemberRepository; +import com.aliens.backend.block.domain.Block; +import com.aliens.backend.block.domain.repository.BlockRepository; +import com.aliens.backend.global.response.error.MatchingError; +import com.aliens.backend.global.exception.RestApiException; +import com.aliens.backend.global.response.error.MemberError; +import com.aliens.backend.mathcing.business.MatchingBusiness; +import com.aliens.backend.mathcing.controller.dto.request.MatchingOperateRequest; +import com.aliens.backend.mathcing.controller.dto.response.MatchingResultResponse; +import com.aliens.backend.mathcing.domain.MatchingApplication; +import com.aliens.backend.mathcing.domain.MatchingResult; +import com.aliens.backend.mathcing.domain.MatchingRound; +import com.aliens.backend.mathcing.domain.repository.MatchingApplicationRepository; +import com.aliens.backend.mathcing.domain.repository.MatchingResultRepository; +import com.aliens.backend.mathcing.domain.repository.MatchingRoundRepository; +import com.aliens.backend.mathcing.business.model.Participant; +import com.aliens.backend.mathcing.business.model.Partner; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; + +@Service +public class MatchingProcessService { + private final MatchingBusiness matchingBusiness; + private final MatchingRoundRepository matchingRoundRepository; + private final MatchingApplicationRepository matchingApplicationRepository; + private final MatchingResultRepository matchingResultRepository; + private final MemberRepository memberRepository; + private final BlockRepository blockRepository; + + public MatchingProcessService(final MatchingBusiness matchingBusiness, + final MatchingRoundRepository matchingRoundRepository, + final MatchingApplicationRepository matchingApplicationRepository, + final MatchingResultRepository matchingResultRepository, + final MemberRepository memberRepository, + final BlockRepository blockRepository) { + this.matchingRoundRepository = matchingRoundRepository; + this.matchingApplicationRepository = matchingApplicationRepository; + this.matchingResultRepository = matchingResultRepository; + this.matchingBusiness = matchingBusiness; + this.memberRepository = memberRepository; + this.blockRepository = blockRepository; + } + + @Scheduled(cron = "${matching.round.start}") + @Transactional + public void operateMatching() { + MatchingRound currentRound = getCurrentRound(); + MatchingOperateRequest matchingOperateRequest = createOperateRequest(currentRound); + + matchingBusiness.operateMatching(matchingOperateRequest); + + List matchedParticipants = matchingBusiness.getMatchedParticipants(); + saveMatchingResult(currentRound, matchedParticipants); + } + + @Transactional(readOnly = true) + public List findMatchingResult(final LoginMember loginMember) { + MatchingRound currentRound = getCurrentRound(); + List matchingResults = getMatchingResult(currentRound, loginMember); + checkHasApplied(matchingResults); + return matchingResults.stream().map(MatchingResultResponse::from).toList(); + } + + private void saveMatchingResult(final MatchingRound matchingRound, final List participants) { + for (Participant participant : participants) { + for (Partner partner : participant.partners()) { + MatchingResult matchingResult = + MatchingResult.of(matchingRound, participant.memberId(), partner.memberId(), partner.relationship()); + matchingResultRepository.save(matchingResult); + } + // TODO : 매칭 완료 알림 이벤트 발송 & 채팅방 개설 이벤트 발송 + } + } + + private void checkHasApplied(final List matchingResults) { + if (matchingResults.isEmpty()) { + throw new RestApiException(MatchingError.NOT_FOUND_MATCHING_APPLICATION_INFO); + } + } + + private MatchingRound getCurrentRound() { + return matchingRoundRepository.findCurrentRound() + .orElseThrow(()-> new RestApiException(MatchingError.NOT_FOUND_MATCHING_ROUND)); + } + + private List getMatchingResult(final MatchingRound matchingRound, final LoginMember loginMember) { + return matchingResultRepository.findAllByMatchingRoundAndMemberId(matchingRound, loginMember.memberId()); + } + + private List getMatchingApplications(final MatchingRound matchingRound) { + return matchingApplicationRepository.findAllByMatchingRound(matchingRound); + } + + private MatchingRound getPreviousMatchingRound(MatchingRound matchingRound) { + Long previousRound = matchingRound.getPreviousRound(); + return matchingRoundRepository.findMatchingRoundByRound(previousRound) + .orElseThrow(() -> new RestApiException(MatchingError.NOT_FOUND_MATCHING_ROUND)); + } + + private List getPreviousMatchingResult(MatchingRound matchingRound) { + if (matchingRound.isFirstRound()) { + return new ArrayList<>(); + } + MatchingRound previousMatchingRound = getPreviousMatchingRound(matchingRound); + return matchingResultRepository.findAllByMatchingRound(previousMatchingRound); + } + + private List getBlockListByMatchingApplications(MatchingRound matchingRound) { + List matchingApplications = getMatchingApplications(matchingRound); + List blockHistory = matchingApplications.stream() + .map(MatchingApplication::getMemberId) + .map(this::getMemberById) + .flatMap(member -> getBlockListByBlockingMember(member).stream()) + .toList(); + return blockHistory; + } + + private List getBlockListByBlockingMember(Member blockingMember) { + return blockRepository.findAllByBlockingMember(blockingMember); + } + + private Member getMemberById(Long memberId) { + return memberRepository.findById(memberId) + .orElseThrow(() -> new RestApiException(MemberError.NULL_MEMBER)); + } + + private MatchingOperateRequest createOperateRequest(MatchingRound matchingRound) { + List matchingApplications = getMatchingApplications(matchingRound); + List previousMatchingResult = getPreviousMatchingResult(matchingRound); + List participantBlockHistory = getBlockListByMatchingApplications(matchingRound); + return MatchingOperateRequest.of(matchingApplications, previousMatchingResult, participantBlockHistory); + } +} diff --git a/src/main/java/com/aliens/backend/mathcing/service/MatchingRoundService.java b/src/main/java/com/aliens/backend/mathcing/service/MatchingRoundService.java index c2ba2929..f24ca0ea 100644 --- a/src/main/java/com/aliens/backend/mathcing/service/MatchingRoundService.java +++ b/src/main/java/com/aliens/backend/mathcing/service/MatchingRoundService.java @@ -3,7 +3,6 @@ import com.aliens.backend.global.property.MatchingTimeProperties; import com.aliens.backend.mathcing.domain.MatchingRound; import com.aliens.backend.mathcing.domain.repository.MatchingRoundRepository; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @@ -26,6 +25,7 @@ public MatchingRoundService(final MatchingRoundRepository matchingRoundRepositor @Scheduled(cron = "${matching.round.update-date}") private void saveMatchRound() { - matchingRoundRepository.save(MatchingRound.of(LocalDateTime.now(clock), matchingTimeProperties)); + MatchingRound matchingRound = MatchingRound.from(LocalDateTime.now(clock), matchingTimeProperties); + matchingRoundRepository.save(matchingRound); } } diff --git a/src/main/java/com/aliens/backend/mathcing/service/MatchingService.java b/src/main/java/com/aliens/backend/mathcing/service/MatchingService.java deleted file mode 100644 index e6e4c41c..00000000 --- a/src/main/java/com/aliens/backend/mathcing/service/MatchingService.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.aliens.backend.mathcing.service; - -import com.aliens.backend.global.response.error.MatchingError; -import com.aliens.backend.global.exception.RestApiException; -import com.aliens.backend.mathcing.business.MatchingBusiness; -import com.aliens.backend.mathcing.domain.MatchingResult; -import com.aliens.backend.mathcing.domain.MatchingRound; -import com.aliens.backend.mathcing.domain.repository.MatchingApplicationRepository; -import com.aliens.backend.mathcing.domain.repository.MatchingResultRepository; -import com.aliens.backend.mathcing.domain.repository.MatchingRoundRepository; -import com.aliens.backend.mathcing.service.model.Participant; -import com.aliens.backend.mathcing.service.model.Partner; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; - -import static com.aliens.backend.mathcing.controller.dto.response.MatchingResponse.*; - -@Service -public class MatchingService { - private final MatchingRoundRepository matchingRoundRepository; - private final MatchingApplicationRepository matchingApplicationRepository; - private final MatchingResultRepository matchingResultRepository; - private final MatchingBusiness matchingBusiness; - - private MatchingRound currentRound; - - @Autowired - public MatchingService(final MatchingRoundRepository matchingRoundRepository, - final MatchingApplicationRepository matchingApplicationRepository, - final MatchingResultRepository matchingResultRepository, - final MatchingBusiness matchingBusiness) { - this.matchingRoundRepository = matchingRoundRepository; - this.matchingApplicationRepository = matchingApplicationRepository; - this.matchingResultRepository = matchingResultRepository; - this.matchingBusiness = matchingBusiness; - } - - @Scheduled(cron = "${matching.round.start}") - @Transactional - public void operateMatching() { - currentRound = matchingRoundRepository.findCurrentRound() - .orElseThrow(()-> new RestApiException(MatchingError.NOT_FOUND_MATCHING_ROUND)); - List participants = matchingBusiness.operateMatching( - matchingApplicationRepository.findAllByMatchingRound(currentRound)); - saveMatchingResult(participants); - } - - @Transactional(readOnly = true) - public List findMatchingResult(final Long memberId) { - currentRound = matchingRoundRepository.findCurrentRound() - .orElseThrow(()-> new RestApiException(MatchingError.NOT_FOUND_MATCHING_ROUND)); - List matchingResults = - matchingResultRepository.findAllByMatchingRoundAndMemberId(currentRound, memberId); - checkHasApplied(matchingResults); - return matchingResults.stream().map(MatchingResultResponse::of).toList(); - } - - private void saveMatchingResult(final List participants) { - for (Participant participant : participants) { - for (Partner partner : participant.partners()) { - matchingResultRepository.save( - MatchingResult.of(currentRound, partner.memberId(), partner.memberId(), partner.relationship())); - } - // TODO : 매칭 완료 알림 이벤트 발송 & 채팅방 개설 이벤트 발송 - } - } - - private void checkHasApplied(List matchingResults) { - if (matchingResults.isEmpty()) { - throw new RestApiException(MatchingError.NOT_FOUND_MATCHING_APPLICATION_INFO); - } - } -} diff --git a/src/main/java/com/aliens/backend/mathcing/service/model/Language.java b/src/main/java/com/aliens/backend/mathcing/service/model/Language.java deleted file mode 100644 index 2c217c7a..00000000 --- a/src/main/java/com/aliens/backend/mathcing/service/model/Language.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.aliens.backend.mathcing.service.model; - -import java.util.*; - -public enum Language { - KOREAN, - ENGLISH, - JAPANESE, - CHINESE - ; - - public static Map> createQueueWith(final List participants) { - Map> languageQueue = new HashMap<>(); - for (Language language : values()) { - languageQueue.put(language, new LinkedList<>()); - } - - for (Participant participant : participants) { - languageQueue.get(participant.getPreferLanguage(MatchingMode.FIRST_PREFER_LANGUAGE)).add(participant); - } - - return languageQueue; - } -} diff --git a/src/main/java/com/aliens/backend/mathcing/util/validator/LanguageValidator.java b/src/main/java/com/aliens/backend/mathcing/util/validator/LanguageValidator.java new file mode 100644 index 00000000..993c20cf --- /dev/null +++ b/src/main/java/com/aliens/backend/mathcing/util/validator/LanguageValidator.java @@ -0,0 +1,29 @@ +package com.aliens.backend.mathcing.util.validator; + + +import com.aliens.backend.global.exception.RestApiException; +import com.aliens.backend.global.response.error.MatchingError; +import com.aliens.backend.global.validator.LanguageCheck; +import com.aliens.backend.mathcing.controller.dto.request.MatchingApplicationRequest; +import com.aliens.backend.mathcing.business.model.Language; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + + +public class LanguageValidator implements ConstraintValidator { + @Override + public boolean isValid(final MatchingApplicationRequest value, final ConstraintValidatorContext context) { + Language firstPreferLanguage = value.firstPreferLanguage(); + Language secondPreferLanguage = value.secondPreferLanguage(); + + if (firstPreferLanguage.equals(secondPreferLanguage)) { + throw new RestApiException(MatchingError.INVALID_LANGUAGE_INPUT); + } + return true; + } + + @Override + public void initialize(final LanguageCheck constraintAnnotation) { + ConstraintValidator.super.initialize(constraintAnnotation); + } +} \ No newline at end of file diff --git a/src/test/java/com/aliens/backend/global/DummyGenerator.java b/src/test/java/com/aliens/backend/global/DummyGenerator.java index a4a8c677..69836d12 100644 --- a/src/test/java/com/aliens/backend/global/DummyGenerator.java +++ b/src/test/java/com/aliens/backend/global/DummyGenerator.java @@ -1,11 +1,13 @@ package com.aliens.backend.global; +import com.aliens.backend.auth.controller.dto.LoginMember; import com.aliens.backend.auth.domain.Member; +import com.aliens.backend.auth.domain.MemberRole; import com.aliens.backend.auth.service.PasswordEncoder; import com.aliens.backend.auth.service.TokenProvider; -import com.aliens.backend.mathcing.controller.dto.request.MatchingRequest; +import com.aliens.backend.mathcing.controller.dto.request.MatchingApplicationRequest; import com.aliens.backend.mathcing.service.MatchingApplicationService; -import com.aliens.backend.mathcing.service.model.Language; +import com.aliens.backend.mathcing.business.model.Language; import com.aliens.backend.member.controller.dto.EncodedMember; import com.aliens.backend.member.controller.dto.EncodedSignUp; import com.aliens.backend.member.domain.Image; @@ -121,8 +123,9 @@ public void generateAppliersToMatch(Long numberOfMember) { secondPreferLanguage = getRandomLanguage(random); } while (firstPreferLanguage == secondPreferLanguage); - MatchingRequest.MatchingApplicationRequest request = new MatchingRequest.MatchingApplicationRequest(i, firstPreferLanguage, secondPreferLanguage); - matchingApplicationService.saveParticipant(request); + LoginMember loginMember = new LoginMember(i, MemberRole.MEMBER); + MatchingApplicationRequest request = new MatchingApplicationRequest(firstPreferLanguage, secondPreferLanguage); + matchingApplicationService.saveParticipant(loginMember, request); } } diff --git a/src/test/java/com/aliens/backend/matching/business/MatchingBusinessTest.java b/src/test/java/com/aliens/backend/matching/business/MatchingBusinessTest.java deleted file mode 100644 index 32c76cf2..00000000 --- a/src/test/java/com/aliens/backend/matching/business/MatchingBusinessTest.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.aliens.backend.matching.business; - -import com.aliens.backend.global.DummyGenerator; -import com.aliens.backend.global.response.error.MatchingError; -import com.aliens.backend.global.exception.RestApiException; -import com.aliens.backend.global.property.MatchingTimeProperties; -import com.aliens.backend.matching.time.MockClock; -import com.aliens.backend.matching.time.MockTime; -import com.aliens.backend.mathcing.business.MatchingBusiness; -import com.aliens.backend.mathcing.domain.MatchingResult; -import com.aliens.backend.mathcing.domain.MatchingRound; -import com.aliens.backend.mathcing.domain.repository.MatchingApplicationRepository; -import com.aliens.backend.mathcing.domain.repository.MatchingResultRepository; -import com.aliens.backend.mathcing.domain.repository.MatchingRoundRepository; -import com.aliens.backend.mathcing.service.MatchingService; -import com.aliens.backend.mathcing.service.model.Participant; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.transaction.annotation.Transactional; - -import java.time.LocalDateTime; -import java.util.List; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -@SpringBootTest -class MatchingBusinessTest { - @Autowired MatchingService matchingService; - @Autowired MatchingApplicationRepository matchingApplicationRepository; - @Autowired MatchingRoundRepository matchingRoundRepository; - @Autowired MatchingResultRepository matchingResultRepository; - @Autowired MatchingBusiness matchingBusiness; - @Autowired DummyGenerator dummyGenerator; - @Autowired MatchingTimeProperties matchingTimeProperties; - @Autowired MockClock mockClock; - - MatchingRound currentRound; - - @BeforeEach - void setUp() { - LocalDateTime roundBeginTime = LocalDateTime.of(2024, 1, 29, 0, 0); - matchingRoundRepository.save(MatchingRound.of(roundBeginTime, matchingTimeProperties)); - currentRound = matchingRoundRepository.findCurrentRound() - .orElseThrow(()-> new RestApiException(MatchingError.NOT_FOUND_MATCHING_ROUND)); - } - - @Test - @Transactional - @DisplayName("매칭 로직 실행 테스트") - void matchingLogicTest() { - mockClock.mockTime(MockTime.VALID_TIME); - dummyGenerator.generateAppliersToMatch(15L); - - List result = matchingBusiness.operateMatching(matchingApplicationRepository.findAllByMatchingRound(currentRound)); - - result.forEach(participant -> assertThat(participant.partners()).isNotNull()); - } - - @Test - @DisplayName("매칭 결과 조회") - @Transactional - void operateMatchingTest() { - // given - mockClock.mockTime(MockTime.VALID_TIME); - dummyGenerator.generateAppliersToMatch(20L); - - // when - matchingService.operateMatching(); - - // then - List result = matchingResultRepository.findAllByMatchingRound(currentRound); - Assertions.assertThat(result).isNotNull(); - } -} diff --git a/src/test/java/com/aliens/backend/matching/service/MatchingRoundServiceTest.java b/src/test/java/com/aliens/backend/matching/service/MatchingRoundServiceTest.java deleted file mode 100644 index 60024e53..00000000 --- a/src/test/java/com/aliens/backend/matching/service/MatchingRoundServiceTest.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.aliens.backend.matching.service; - -import com.aliens.backend.global.response.error.MatchingError; -import com.aliens.backend.global.exception.RestApiException; -import com.aliens.backend.global.property.MatchingTimeProperties; -import com.aliens.backend.mathcing.domain.MatchingRound; -import com.aliens.backend.mathcing.domain.repository.MatchingRoundRepository; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.transaction.annotation.Transactional; - -import java.time.LocalDateTime; - -import static org.assertj.core.api.Assertions.*; - -@SpringBootTest -class MatchingRoundServiceTest { - @Autowired MatchingRoundRepository matchingRoundRepository; - @Autowired MatchingTimeProperties matchingTimeProperties; - - @Test - @DisplayName("매주 월, 목 매칭 회차 업데이트") - @Transactional - void saveMatchRoundTest() { - LocalDateTime monday = LocalDateTime.of(2024, 1, 29, 0, 0); - MatchingRound result = matchingRoundRepository.save(MatchingRound.of(monday, matchingTimeProperties)); - - assertThat(result.getRound()).isNotNull(); - } - - @Test - @DisplayName("현재 매칭 회차 조회") - @Transactional - void getCurrentRound() { - LocalDateTime monday = LocalDateTime.of(2024, 1, 29, 0, 0); - matchingRoundRepository.save(MatchingRound.of(monday, matchingTimeProperties)); - - MatchingRound result = matchingRoundRepository.findCurrentRound() - .orElseThrow(() -> new RestApiException(MatchingError.NOT_FOUND_MATCHING_ROUND)); - - assertThat(result.getRound()).isNotNull(); - } -} diff --git a/src/test/java/com/aliens/backend/matching/service/MatchingServiceTest.java b/src/test/java/com/aliens/backend/matching/service/MatchingServiceTest.java deleted file mode 100644 index 40131857..00000000 --- a/src/test/java/com/aliens/backend/matching/service/MatchingServiceTest.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.aliens.backend.matching.service; - -import com.aliens.backend.global.response.error.MatchingError; -import com.aliens.backend.global.exception.RestApiException; -import com.aliens.backend.global.property.MatchingTimeProperties; -import com.aliens.backend.mathcing.domain.MatchingRound; -import com.aliens.backend.mathcing.domain.repository.MatchingRoundRepository; -import com.aliens.backend.mathcing.service.MatchingService; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; - -import java.time.LocalDateTime; - -import static org.assertj.core.api.Assertions.*; - -@SpringBootTest -class MatchingServiceTest { - - @Autowired MatchingService matchingService; - @Autowired MatchingRoundRepository matchingRoundRepository; - @Autowired MatchingTimeProperties matchingTimeProperties; - - MatchingRound currentRound; - - @BeforeEach - void setUp() { - LocalDateTime roundBeginTime = LocalDateTime.of(2024, 1, 29, 0, 0); - matchingRoundRepository.save(MatchingRound.of(roundBeginTime, matchingTimeProperties)); - currentRound = matchingRoundRepository.findCurrentRound() - .orElseThrow(()-> new RestApiException(MatchingError.NOT_FOUND_MATCHING_ROUND)); - } - - @Test - @DisplayName("매칭을 신청한 적이 없는 회원이 매칭 조회") - void getMatchingResultTest() { - assertThatThrownBy(() -> matchingService.findMatchingResult(1L)) - .hasMessage(MatchingError.NOT_FOUND_MATCHING_APPLICATION_INFO.getDevelopCode()); - } -} diff --git a/src/test/java/com/aliens/backend/matching/time/MockTime.java b/src/test/java/com/aliens/backend/matching/time/MockTime.java deleted file mode 100644 index 9a1538c7..00000000 --- a/src/test/java/com/aliens/backend/matching/time/MockTime.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.aliens.backend.matching.time; - -import java.time.LocalDateTime; - -public enum MockTime { - INVALID_TIME(LocalDateTime.of(2024, 1, 29, 19, 0)), - VALID_TIME(LocalDateTime.of(2024, 1, 29, 10, 0)); - - public LocalDateTime time; - - MockTime(LocalDateTime time) { - this.time = time; - } -} diff --git a/src/test/java/com/aliens/backend/matching/unit/business/MatchingBusinessTest.java b/src/test/java/com/aliens/backend/matching/unit/business/MatchingBusinessTest.java new file mode 100644 index 00000000..03ac2a87 --- /dev/null +++ b/src/test/java/com/aliens/backend/matching/unit/business/MatchingBusinessTest.java @@ -0,0 +1,127 @@ +package com.aliens.backend.matching.unit.business; + +import com.aliens.backend.auth.domain.Member; +import com.aliens.backend.auth.domain.repository.MemberRepository; +import com.aliens.backend.block.domain.Block; +import com.aliens.backend.block.domain.repository.BlockRepository; +import com.aliens.backend.global.BaseServiceTest; +import com.aliens.backend.global.DummyGenerator; +import com.aliens.backend.global.response.error.MatchingError; +import com.aliens.backend.global.exception.RestApiException; +import com.aliens.backend.global.property.MatchingTimeProperties; +import com.aliens.backend.global.response.error.MemberError; +import com.aliens.backend.matching.util.time.MockClock; +import com.aliens.backend.matching.util.time.MockTime; +import com.aliens.backend.mathcing.business.MatchingBusiness; +import com.aliens.backend.mathcing.controller.dto.request.MatchingOperateRequest; +import com.aliens.backend.mathcing.domain.MatchingApplication; +import com.aliens.backend.mathcing.domain.MatchingResult; +import com.aliens.backend.mathcing.domain.MatchingRound; +import com.aliens.backend.mathcing.domain.repository.MatchingApplicationRepository; +import com.aliens.backend.mathcing.domain.repository.MatchingResultRepository; +import com.aliens.backend.mathcing.domain.repository.MatchingRoundRepository; +import com.aliens.backend.mathcing.service.MatchingProcessService; +import com.aliens.backend.mathcing.business.model.Participant; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@SpringBootTest +class MatchingBusinessTest extends BaseServiceTest { + @Autowired MatchingProcessService matchingProcessService; + @Autowired MatchingApplicationRepository matchingApplicationRepository; + @Autowired MatchingRoundRepository matchingRoundRepository; + @Autowired MatchingResultRepository matchingResultRepository; + @Autowired MatchingBusiness matchingBusiness; + @Autowired DummyGenerator dummyGenerator; + @Autowired MatchingTimeProperties matchingTimeProperties; + @Autowired BlockRepository blockRepository; + @Autowired MemberRepository memberRepository; + @Autowired MockClock mockClock; + + MatchingOperateRequest matchingOperateRequest; + + @BeforeEach + void setUp() { + saveMatchRound(MockTime.MONDAY); + } + + @Test + @DisplayName("매칭 로직 실행 테스트") + void matchingLogicTest() { + operateMatching(MockTime.VALID_RECEPTION_TIME_ON_MONDAY); + + List result = matchingBusiness.getMatchedParticipants(); + result.forEach(participant -> assertThat(participant.partners()).isNotNull()); + } + + private MatchingRound getCurrentRound() { + return matchingRoundRepository.findCurrentRound() + .orElseThrow(()-> new RestApiException(MatchingError.NOT_FOUND_MATCHING_ROUND)); + } + + private List getMatchingApplications(MatchingRound matchingRound) { + return matchingApplicationRepository.findAllByMatchingRound(matchingRound); + } + + private MatchingRound getPreviousMatchingRound(MatchingRound matchingRound) { + Long previousRound = matchingRound.getPreviousRound(); + return matchingRoundRepository.findMatchingRoundByRound(previousRound) + .orElseThrow(() -> new RestApiException(MatchingError.NOT_FOUND_MATCHING_ROUND)); + } + + private List getPreviousMatchingResult(MatchingRound matchingRound) { + if (matchingRound.isFirstRound()) { + return new ArrayList<>(); + } + MatchingRound previousMatchingRound = getPreviousMatchingRound(matchingRound); + return matchingResultRepository.findAllByMatchingRound(previousMatchingRound); + } + + private List getBlockListByMatchingApplications(MatchingRound matchingRound) { + List matchingApplications = getMatchingApplications(matchingRound); + List blockHistory = matchingApplications.stream() + .map(MatchingApplication::getMemberId) + .map(this::getMemberById) + .flatMap(member -> getBlockListByBlockingMember(member).stream()) + .toList(); + return blockHistory; + } + + private List getBlockListByBlockingMember(Member blockingMember) { + return blockRepository.findAllByBlockingMember(blockingMember); + } + + private Member getMemberById(Long memberId) { + return memberRepository.findById(memberId) + .orElseThrow(() -> new RestApiException(MemberError.NULL_MEMBER)); + } + + private MatchingOperateRequest createOperateRequest(MatchingRound matchingRound) { + List matchingApplications = getMatchingApplications(matchingRound); + List previousMatchingResult = getPreviousMatchingResult(matchingRound); + List participantBlockHistory = getBlockListByMatchingApplications(matchingRound); + return MatchingOperateRequest.of(matchingApplications, previousMatchingResult, participantBlockHistory); + } + + private void saveMatchRound(MockTime mockTime) { + mockClock.mockTime(mockTime); + MatchingRound matchingRound = MatchingRound.from(mockTime.getTime(), matchingTimeProperties); + matchingRoundRepository.save(matchingRound); + } + + private void operateMatching(MockTime mockTime) { + mockClock.mockTime(mockTime); + dummyGenerator.generateMultiMember(21); + dummyGenerator.generateAppliersToMatch(20L); + matchingOperateRequest = createOperateRequest(getCurrentRound()); + matchingBusiness.operateMatching(matchingOperateRequest); + } +} diff --git a/src/test/java/com/aliens/backend/matching/service/MatchingApplicationServiceTest.java b/src/test/java/com/aliens/backend/matching/unit/service/MatchingApplicationServiceTest.java similarity index 58% rename from src/test/java/com/aliens/backend/matching/service/MatchingApplicationServiceTest.java rename to src/test/java/com/aliens/backend/matching/unit/service/MatchingApplicationServiceTest.java index 3dc96c3b..5e2faa7f 100644 --- a/src/test/java/com/aliens/backend/matching/service/MatchingApplicationServiceTest.java +++ b/src/test/java/com/aliens/backend/matching/unit/service/MatchingApplicationServiceTest.java @@ -1,154 +1,158 @@ -package com.aliens.backend.matching.service; +package com.aliens.backend.matching.unit.service; +import com.aliens.backend.auth.controller.dto.LoginMember; +import com.aliens.backend.auth.domain.MemberRole; +import com.aliens.backend.global.BaseServiceTest; import com.aliens.backend.global.response.error.MatchingError; import com.aliens.backend.global.exception.RestApiException; import com.aliens.backend.global.property.MatchingTimeProperties; -import com.aliens.backend.matching.time.MockClock; +import com.aliens.backend.matching.util.time.MockClock; +import com.aliens.backend.matching.util.time.MockTime; +import com.aliens.backend.mathcing.controller.dto.request.MatchingApplicationRequest; +import com.aliens.backend.mathcing.controller.dto.response.MatchingApplicationResponse; import com.aliens.backend.mathcing.domain.MatchingApplication; import com.aliens.backend.mathcing.domain.MatchingRound; import com.aliens.backend.mathcing.domain.id.MatchingApplicationId; import com.aliens.backend.mathcing.domain.repository.MatchingApplicationRepository; import com.aliens.backend.mathcing.domain.repository.MatchingRoundRepository; import com.aliens.backend.mathcing.service.MatchingApplicationService; -import com.aliens.backend.mathcing.service.model.Language; +import com.aliens.backend.mathcing.business.model.Language; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; -import static com.aliens.backend.matching.time.MockTime.INVALID_TIME; -import static com.aliens.backend.matching.time.MockTime.VALID_TIME; -import static com.aliens.backend.mathcing.controller.dto.request.MatchingRequest.*; -import static com.aliens.backend.mathcing.controller.dto.response.MatchingResponse.*; +import static com.aliens.backend.matching.util.time.MockTime.INVALID_RECEPTION_TIME; +import static com.aliens.backend.matching.util.time.MockTime.VALID_RECEPTION_TIME_ON_MONDAY; import static org.assertj.core.api.Assertions.*; @SpringBootTest -class MatchingApplicationServiceTest { +class MatchingApplicationServiceTest extends BaseServiceTest { @Autowired MatchingApplicationService matchingApplicationService; @Autowired MatchingApplicationRepository matchingApplicationRepository; @Autowired MatchingRoundRepository matchingRoundRepository; @Autowired MatchingTimeProperties matchingTimeProperties; @Autowired MockClock mockClock; + LoginMember loginMember; MatchingApplicationRequest matchingApplicationRequest; @BeforeEach void setUp() { - createNewMatchingRound(); - matchingApplicationRequest = new MatchingApplicationRequest(1L, Language.KOREAN, Language.ENGLISH); + saveMatchRound(MockTime.MONDAY); + loginMember = new LoginMember(1L, MemberRole.MEMBER); + matchingApplicationRequest = new MatchingApplicationRequest(Language.KOREAN, Language.ENGLISH); } @Test @DisplayName("매칭 신청 단위 테스트") - @Transactional void applyMatchTest() { // given - mockClock.mockTime(VALID_TIME); + mockClock.mockTime(VALID_RECEPTION_TIME_ON_MONDAY); + Long expectedResult = loginMember.memberId(); // when - matchingApplicationService.saveParticipant(matchingApplicationRequest); + matchingApplicationService.saveParticipant(loginMember, matchingApplicationRequest); // then - MatchingApplication result = matchingApplicationRepository - .findById(MatchingApplicationId.of(getCurrentRound(), matchingApplicationRequest.memberId())) - .orElseThrow(() -> new RestApiException(MatchingError.NOT_FOUND_MATCHING_APPLICATION_INFO)); - assertThat(result.getId().getMemberId()).isEqualTo(matchingApplicationRequest.memberId()); + MatchingApplication result = findMatchingApplication(loginMember); + assertThat(result.getMemberId()).isEqualTo(expectedResult); } @Test @DisplayName("지정 시간 외 매칭 신청시, 에러 발생") - @Transactional void applyMatchIfNotValidTime() { // given - mockClock.mockTime(INVALID_TIME); + mockClock.mockTime(INVALID_RECEPTION_TIME); // when & then - assertThatThrownBy(() -> matchingApplicationService.saveParticipant(matchingApplicationRequest)) + assertThatThrownBy(() -> matchingApplicationService.saveParticipant(loginMember, matchingApplicationRequest)) .hasMessage(MatchingError.NOT_VALID_MATCHING_RECEPTION_TIME.getDevelopCode()); } @Test @DisplayName("매칭 신청 조회 단위 테스트") - @Transactional void getMatchingApplicationTest() { // given + Long expectedResult = loginMember.memberId(); applyToMatch(); //when - MatchingApplicationResponse result = matchingApplicationService - .findMatchingApplication(matchingApplicationRequest.memberId()); + MatchingApplicationResponse result = matchingApplicationService.findMatchingApplication(loginMember); // then - assertThat(result.memberId()).isEqualTo(matchingApplicationRequest.memberId()); + assertThat(result.memberId()).isEqualTo(expectedResult); } @Test @DisplayName("매칭 신청하지 않은 사용자 조회 테스트") - @Transactional void getMatchingApplicationIfNotApplied() { // when & then - assertThatThrownBy(() -> matchingApplicationService.findMatchingApplication(matchingApplicationRequest.memberId())) + assertThatThrownBy(() -> matchingApplicationService.findMatchingApplication(loginMember)) .hasMessage(MatchingError.NOT_FOUND_MATCHING_APPLICATION_INFO.getDevelopCode()); } @Test @DisplayName("매칭 신청 취소 단위 테스트") - @Transactional void deleteMatchingApplicationTest() { // given applyToMatch(); - mockClock.mockTime(VALID_TIME); + mockClock.mockTime(VALID_RECEPTION_TIME_ON_MONDAY); // when - matchingApplicationService.deleteMatchingApplication(matchingApplicationRequest.memberId()); + matchingApplicationService.cancelMatchingApplication(loginMember); // then - assertThatThrownBy(() -> matchingApplicationService.findMatchingApplication(matchingApplicationRequest.memberId())) + assertThatThrownBy(() -> matchingApplicationService.findMatchingApplication(loginMember)) .hasMessage(MatchingError.NOT_FOUND_MATCHING_APPLICATION_INFO.getDevelopCode()); } @Test @DisplayName("지정 시간 외 매칭 취소 신청시, 에러 발생") - @Transactional void deleteMatchIfNotValidTime() { // given applyToMatch(); - mockClock.mockTime(INVALID_TIME); + mockClock.mockTime(INVALID_RECEPTION_TIME); // when & then - assertThatThrownBy(() -> matchingApplicationService.deleteMatchingApplication(matchingApplicationRequest.memberId())) + assertThatThrownBy(() -> matchingApplicationService.cancelMatchingApplication(loginMember)) .hasMessage(MatchingError.NOT_VALID_MATCHING_RECEPTION_TIME.getDevelopCode()); } @Test @DisplayName("매칭을 신청하지 않은 사용자 매칭 삭제 요청 테스트") - @Transactional void deleteMatchIfNotApplied() { // given - mockClock.mockTime(VALID_TIME); + mockClock.mockTime(VALID_RECEPTION_TIME_ON_MONDAY); - assertThatThrownBy(() -> matchingApplicationService.deleteMatchingApplication(matchingApplicationRequest.memberId())) + assertThatThrownBy(() -> matchingApplicationService.cancelMatchingApplication(loginMember)) .hasMessage(MatchingError.NOT_FOUND_MATCHING_APPLICATION_INFO.getDevelopCode()); } private void applyToMatch() { - mockClock.mockTime(VALID_TIME); - matchingApplicationService.saveParticipant(matchingApplicationRequest); + mockClock.mockTime(VALID_RECEPTION_TIME_ON_MONDAY); + matchingApplicationService.saveParticipant(loginMember, matchingApplicationRequest); } - private void createNewMatchingRound() { - LocalDateTime roundBeginTime = LocalDateTime.of(2024, 1, 29, 0, 0); - matchingRoundRepository.save(MatchingRound.of(roundBeginTime, matchingTimeProperties)); + private void saveMatchRound(MockTime mockTime) { + mockClock.mockTime(mockTime); + MatchingRound matchingRound = MatchingRound.from(mockTime.getTime(), matchingTimeProperties); + matchingRoundRepository.save(matchingRound); } private MatchingRound getCurrentRound() { return matchingRoundRepository.findCurrentRound() .orElseThrow(() -> new RestApiException(MatchingError.NOT_FOUND_MATCHING_ROUND)); } + + private MatchingApplication findMatchingApplication(LoginMember loginMember) { + return matchingApplicationRepository + .findById(MatchingApplicationId.of(getCurrentRound(), loginMember.memberId())) + .orElseThrow(() -> new RestApiException(MatchingError.NOT_FOUND_MATCHING_APPLICATION_INFO)); + } } diff --git a/src/test/java/com/aliens/backend/matching/unit/service/MatchingProcessServiceTest.java b/src/test/java/com/aliens/backend/matching/unit/service/MatchingProcessServiceTest.java new file mode 100644 index 00000000..ff4bb127 --- /dev/null +++ b/src/test/java/com/aliens/backend/matching/unit/service/MatchingProcessServiceTest.java @@ -0,0 +1,147 @@ +package com.aliens.backend.matching.unit.service; + +import com.aliens.backend.auth.controller.dto.LoginMember; +import com.aliens.backend.auth.domain.Member; +import com.aliens.backend.block.controller.dto.BlockRequest; +import com.aliens.backend.block.domain.Block; +import com.aliens.backend.block.domain.repository.BlockRepository; +import com.aliens.backend.global.BaseServiceTest; +import com.aliens.backend.global.DummyGenerator; +import com.aliens.backend.global.response.error.MatchingError; +import com.aliens.backend.global.exception.RestApiException; +import com.aliens.backend.global.property.MatchingTimeProperties; +import com.aliens.backend.matching.util.time.MockClock; +import com.aliens.backend.matching.util.time.MockTime; +import com.aliens.backend.mathcing.domain.MatchingResult; +import com.aliens.backend.mathcing.domain.MatchingRound; +import com.aliens.backend.mathcing.domain.repository.MatchingResultRepository; +import com.aliens.backend.mathcing.domain.repository.MatchingRoundRepository; +import com.aliens.backend.mathcing.service.MatchingProcessService; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.*; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.*; + +@SpringBootTest +class MatchingProcessServiceTest extends BaseServiceTest { + @Autowired MatchingProcessService matchingProcessService; + @Autowired MatchingRoundRepository matchingRoundRepository; + @Autowired MatchingTimeProperties matchingTimeProperties; + @Autowired MatchingResultRepository matchingResultRepository; + @Autowired BlockRepository blockRepository; + @Autowired MockClock mockClock; + @Autowired DummyGenerator dummyGenerator; + + List members; + + @BeforeEach + void setUp() { + members = dummyGenerator.generateMultiMember(20); + saveMatchRound(MockTime.MONDAY); + } + + @Test + @DisplayName("매칭 결과 조회") + void operateMatchingTest() { + // given & when + operateMatching(MockTime.VALID_RECEPTION_TIME_ON_MONDAY); + + // then + List result = matchingResultRepository.findAllByMatchingRound(getCurrentRound()); + Assertions.assertThat(result).isNotNull(); + } + + @Test + @DisplayName("연속 매칭 테스트") + void operateMatchingTwice() { + operateMatching(MockTime.VALID_RECEPTION_TIME_ON_MONDAY); + saveMatchRound(MockTime.THURSDAY); + operateMatching(MockTime.VALID_RECEPTION_TIME_ON_THURSDAY); + } + + @Test + @DisplayName("직전 회차에 매칭된 사용자와 매칭되지 않는 기능 테스트") + void isDuplicateMatchingTest() { + // given & when + operateMatching(MockTime.VALID_RECEPTION_TIME_ON_MONDAY); + List firstRoundResult = getMatchingResultByMatchingRound(getCurrentRound()); + saveMatchRound(MockTime.THURSDAY); + operateMatching(MockTime.VALID_RECEPTION_TIME_ON_THURSDAY); + List secondRoundResult = getMatchingResultByMatchingRound(getCurrentRound()); + + Map> matchedMembersInSecondRound = secondRoundResult.stream() + .collect(Collectors.groupingBy( + MatchingResult::getMatchingMemberId, + Collectors.mapping(MatchingResult::getMatchedMemberId, Collectors.toSet()) + )); + + // then + firstRoundResult.forEach(first -> { + Set matchedMemberIds = matchedMembersInSecondRound.get(first.getMatchingMemberId()); + assertThat(matchedMemberIds).doesNotContain(first.getMatchedMemberId()); + }); + } + + @Test + @DisplayName("차단된 유저와 매칭이 되지 않는지 테스트") + void isBlockedMemberTest() { + // given + Member blockingMember = members.get(0); + makeThisMemberBlockAllPartner(blockingMember); + + // when + operateMatching(MockTime.VALID_RECEPTION_TIME_ON_MONDAY); + + // then + List matchingResults = getMatchingResultByMatchingRound(getCurrentRound()); + List matchingResultsOfBlockingMember = matchingResults.stream() + .filter(matchingResult -> matchingResult.getMatchingMemberId().equals(blockingMember.getId())).toList(); + assertThat(matchingResultsOfBlockingMember).isEmpty(); + } + + @Test + @DisplayName("매칭을 신청한 적이 없는 회원이 매칭 조회") + void getMatchingResultTest() { + Member member = members.get(0); + LoginMember loginMember = member.getLoginMember(); + + assertThatThrownBy(() -> matchingProcessService.findMatchingResult(loginMember)) + .hasMessage(MatchingError.NOT_FOUND_MATCHING_APPLICATION_INFO.getDevelopCode()); + } + + private MatchingRound getCurrentRound() { + return matchingRoundRepository.findCurrentRound() + .orElseThrow(()-> new RestApiException(MatchingError.NOT_FOUND_MATCHING_ROUND)); + } + + private void saveMatchRound(MockTime mockTime) { + mockClock.mockTime(mockTime); + MatchingRound matchingRound = MatchingRound.from(mockTime.getTime(), matchingTimeProperties); + matchingRoundRepository.save(matchingRound); + } + + private void operateMatching(MockTime mockTime) { + mockClock.mockTime(mockTime); + dummyGenerator.generateAppliersToMatch(20L); + matchingProcessService.operateMatching(); + } + + private List getMatchingResultByMatchingRound(MatchingRound matchingRound) { + return matchingResultRepository.findAllByMatchingRound(matchingRound); + } + + private void makeThisMemberBlockAllPartner(Member blockingMember) { + for (int i = 1; i < members.size(); i++) { + Member blockedMember = members.get(i); + Block blockRequest = Block.of(blockingMember, blockedMember); + blockRepository.save(blockRequest); + } + } +} diff --git a/src/test/java/com/aliens/backend/matching/unit/service/MatchingRoundServiceTest.java b/src/test/java/com/aliens/backend/matching/unit/service/MatchingRoundServiceTest.java new file mode 100644 index 00000000..196dcdea --- /dev/null +++ b/src/test/java/com/aliens/backend/matching/unit/service/MatchingRoundServiceTest.java @@ -0,0 +1,59 @@ +package com.aliens.backend.matching.unit.service; + +import com.aliens.backend.global.BaseServiceTest; +import com.aliens.backend.global.response.error.MatchingError; +import com.aliens.backend.global.exception.RestApiException; +import com.aliens.backend.global.property.MatchingTimeProperties; +import com.aliens.backend.matching.util.time.MockTime; +import com.aliens.backend.mathcing.domain.MatchingRound; +import com.aliens.backend.mathcing.domain.repository.MatchingRoundRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +import java.time.DayOfWeek; +import java.time.LocalDateTime; + +import static org.assertj.core.api.Assertions.*; + +@SpringBootTest +class MatchingRoundServiceTest extends BaseServiceTest { + @Autowired MatchingRoundRepository matchingRoundRepository; + @Autowired MatchingTimeProperties matchingTimeProperties; + + @Test + @DisplayName("매주 월, 목 매칭 회차 업데이트") + void saveMatchRoundTest() { + // given + MatchingRound mondayRound = MatchingRound.from(MockTime.MONDAY.getTime(), matchingTimeProperties); + + // when + matchingRoundRepository.save(mondayRound); + + // then + MatchingRound currentRound = getCurrentRound(); + DayOfWeek result = currentRound.getDayOfWeek(); + assertThat(result).isEqualTo(DayOfWeek.MONDAY); + } + + @Test + @DisplayName("현재 매칭 회차 조회") + void getCurrentRoundTest() { + // given + MatchingRound mondayRound = MatchingRound.from(MockTime.MONDAY.getTime(), matchingTimeProperties); + matchingRoundRepository.save(mondayRound); + + // then + MatchingRound currentRound = getCurrentRound(); + DayOfWeek result = currentRound.getDayOfWeek(); + + assertThat(result).isEqualTo(DayOfWeek.MONDAY); + } + + private MatchingRound getCurrentRound() { + return matchingRoundRepository.findCurrentRound() + .orElseThrow(() -> new RestApiException(MatchingError.NOT_FOUND_MATCHING_ROUND)); + } +} diff --git a/src/test/java/com/aliens/backend/matching/time/MockClock.java b/src/test/java/com/aliens/backend/matching/util/time/MockClock.java similarity index 72% rename from src/test/java/com/aliens/backend/matching/time/MockClock.java rename to src/test/java/com/aliens/backend/matching/util/time/MockClock.java index cd850598..a5777828 100644 --- a/src/test/java/com/aliens/backend/matching/time/MockClock.java +++ b/src/test/java/com/aliens/backend/matching/util/time/MockClock.java @@ -1,5 +1,7 @@ -package com.aliens.backend.matching.time; +package com.aliens.backend.matching.util.time; +import com.aliens.backend.mathcing.domain.MatchingRound; +import org.mockito.BDDMockito; import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.stereotype.Component; @@ -7,6 +9,7 @@ import java.time.LocalDateTime; import java.time.ZoneId; +import static org.mockito.BDDMockito.*; import static org.mockito.Mockito.when; @Component @@ -15,7 +18,7 @@ public class MockClock { private Clock clock; public void mockTime(MockTime mockTime) { - LocalDateTime time = mockTime.time; + LocalDateTime time = mockTime.getTime(); Clock fixedClock = Clock.fixed(time.atZone(ZoneId.systemDefault()).toInstant(), ZoneId.systemDefault()); when(clock.instant()).thenReturn(fixedClock.instant()); when(clock.getZone()).thenReturn(fixedClock.getZone()); diff --git a/src/test/java/com/aliens/backend/matching/util/time/MockTime.java b/src/test/java/com/aliens/backend/matching/util/time/MockTime.java new file mode 100644 index 00000000..7afe35ed --- /dev/null +++ b/src/test/java/com/aliens/backend/matching/util/time/MockTime.java @@ -0,0 +1,24 @@ +package com.aliens.backend.matching.util.time; + +import java.time.LocalDateTime; + +public enum MockTime { + INVALID_RECEPTION_TIME(LocalDateTime.of(2024, 1, 29, 19, 0)), + VALID_RECEPTION_TIME_ON_MONDAY(LocalDateTime.of(2024, 1, 29, 10, 0)), + VALID_RECEPTION_TIME_ON_THURSDAY(LocalDateTime.of(2024, 2, 1, 10, 0)), + + + MONDAY(LocalDateTime.of(2024, 1, 29, 0, 0)), + THURSDAY(LocalDateTime.of(2024, 2, 1, 0, 0)), + ; + + private final LocalDateTime time; + + MockTime(LocalDateTime time) { + this.time = time; + } + + public LocalDateTime getTime() { + return time; + } +}