-
Notifications
You must be signed in to change notification settings - Fork 0
Implement create match between two members for a match cycle #78
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: api-match-repos
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| package org.patinanetwork.patchats.api.match; | ||
|
|
||
| import io.swagger.v3.oas.annotations.Operation; | ||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||
| import jakarta.validation.Valid; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.patinanetwork.patchats.api.match.dto.match.AdminMatchResponse; | ||
| import org.patinanetwork.patchats.api.match.dto.match.CreateMatchRequest; | ||
| import org.patinanetwork.patchats.common.dto.ApiResponder; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.annotation.PostMapping; | ||
| import org.springframework.web.bind.annotation.RequestBody; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/api/admin/matches") | ||
| @Tag(name = "Admin Matches") | ||
| @RequiredArgsConstructor | ||
| public class AdminMatchController { | ||
|
|
||
| private final MatchService matchService; | ||
|
|
||
| @Operation(summary = "Create a match between two members for a match cycle") | ||
| @PostMapping | ||
| public ResponseEntity<ApiResponder<AdminMatchResponse>> createMatch( | ||
| @Valid @RequestBody final CreateMatchRequest request) { | ||
| final AdminMatchResponse response = matchService.createMatch(request); | ||
| return ResponseEntity.ok(ApiResponder.success("Match created successfully", response)); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| package org.patinanetwork.patchats.api.match; | ||
|
|
||
| import java.time.ZoneOffset; | ||
| import java.time.format.DateTimeFormatter; | ||
| import java.util.UUID; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.patinanetwork.patchats.api.match.db.models.Match; | ||
| import org.patinanetwork.patchats.api.match.db.models.MatchCycle; | ||
| import org.patinanetwork.patchats.api.match.db.repos.MatchCycleRepo; | ||
| import org.patinanetwork.patchats.api.match.db.repos.MatchRepo; | ||
| import org.patinanetwork.patchats.api.match.dto.match.AdminMatchResponse; | ||
| import org.patinanetwork.patchats.api.match.dto.match.CreateMatchRequest; | ||
| import org.patinanetwork.patchats.common.web.exception.MatchCycleNotFoundException; | ||
| import org.springframework.stereotype.Service; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class MatchService { | ||
|
|
||
| private static final String DEFAULT_MATCH_STATUS = "PENDING"; | ||
| private static final DateTimeFormatter MONTH_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM"); | ||
|
|
||
| private final MatchRepo matchRepo; | ||
| private final MatchCycleRepo matchCycleRepo; | ||
|
|
||
| public AdminMatchResponse createMatch(CreateMatchRequest request) { | ||
| MatchCycle cycle = matchCycleRepo | ||
| .getMatchCycleById(request.matchCycleId()) | ||
| .orElseThrow(() -> new MatchCycleNotFoundException(request.matchCycleId())); | ||
|
|
||
| Match match = Match.builder() | ||
| .id(UUID.randomUUID()) | ||
| .memberAId(request.memberAId()) | ||
| .memberBId(request.memberBId()) | ||
| .matchCycleId(request.matchCycleId()) | ||
| .matchScore(request.matchScore()) | ||
| .status(request.status() == null ? DEFAULT_MATCH_STATUS : request.status()) | ||
| .build(); | ||
|
|
||
| Match createdMatch = matchRepo.createMatch(match); | ||
| return AdminMatchResponse.from(createdMatch, deriveMonth(cycle)); | ||
| } | ||
|
|
||
| /** Derives the "YYYY-MM" month label for a match from its cycle's run time (UTC). */ | ||
| private String deriveMonth(final MatchCycle cycle) { | ||
| return MONTH_FORMATTER.format(cycle.getRunAt().atZone(ZoneOffset.UTC)); | ||
| } | ||
| } |
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please keep requests and response names synced, even if you have to repeat them. I know it's kinda annoying and verbose, but it should be -> I personally like having protobuf files to condense all of these DTOs into one file, but that's more infra to set up. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package org.patinanetwork.patchats.api.match.dto.cycle; | ||
|
|
||
| import jakarta.validation.constraints.NotNull; | ||
| import java.time.Instant; | ||
|
|
||
| public record CreateMatchCycleRequest( | ||
| @NotNull Instant runAt, String period, Integer totalMembers, Integer totalMatched) {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| package org.patinanetwork.patchats.api.match.dto.cycle; | ||
|
|
||
| public record MatchCycleDetailQuery(String memberIndustry, String status) {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package org.patinanetwork.patchats.api.match.dto.cycle; | ||
|
|
||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import java.util.List; | ||
| import lombok.Builder; | ||
| import lombok.EqualsAndHashCode; | ||
| import lombok.Getter; | ||
| import lombok.ToString; | ||
| import org.patinanetwork.patchats.api.match.dto.match.AdminMatchResponse; | ||
|
|
||
| @Getter | ||
| @Builder | ||
| @ToString | ||
| @EqualsAndHashCode | ||
| public class MatchCycleDetailResponse { | ||
|
|
||
| @Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
| private final MatchCycleResponse cycle; | ||
|
|
||
| @Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
| private final List<AdminMatchResponse> matches; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| package org.patinanetwork.patchats.api.match.dto.cycle; | ||
|
|
||
| import java.time.Instant; | ||
|
|
||
| public record MatchCycleListQuery(String period, Instant startTime, Instant endTime) {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| package org.patinanetwork.patchats.api.match.dto.cycle; | ||
|
|
||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import java.time.Instant; | ||
| import lombok.Builder; | ||
| import lombok.EqualsAndHashCode; | ||
| import lombok.Getter; | ||
| import lombok.ToString; | ||
| import org.patinanetwork.patchats.api.match.db.models.MatchCycle; | ||
|
|
||
| @Getter | ||
| @Builder | ||
| @ToString | ||
| @EqualsAndHashCode | ||
| public class MatchCycleResponse { | ||
|
|
||
| @Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
| private final Integer matchCycleId; | ||
|
|
||
| @Schema(requiredMode = Schema.RequiredMode.REQUIRED, nullable = true) | ||
| private final String period; | ||
|
|
||
| @Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
| private final Instant runAt; | ||
|
|
||
| @Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
| private final Integer totalMembers; | ||
|
|
||
| @Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
| private final Integer totalMatched; | ||
|
|
||
| public static MatchCycleResponse from(final MatchCycle cycle) { | ||
| return MatchCycleResponse.builder() | ||
| .matchCycleId(cycle.getId()) | ||
| .period(cycle.getPeriod()) | ||
| .runAt(cycle.getRunAt()) | ||
| .totalMembers(cycle.getTotalMembers()) | ||
| .totalMatched(cycle.getTotalMatched()) | ||
| .build(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| package org.patinanetwork.patchats.api.match.dto.cycle; | ||
|
|
||
| import java.time.Instant; | ||
|
|
||
| public record UpdateMatchCycleRequest(Instant runAt, String period, Integer totalMembers, Integer totalMatched) {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| package org.patinanetwork.patchats.api.match.dto.match; | ||
|
|
||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import java.time.Instant; | ||
| import java.util.UUID; | ||
| import lombok.Builder; | ||
| import lombok.EqualsAndHashCode; | ||
| import lombok.Getter; | ||
| import lombok.ToString; | ||
| import org.patinanetwork.patchats.api.match.db.models.Match; | ||
|
|
||
| @Getter | ||
| @Builder | ||
| @ToString | ||
| @EqualsAndHashCode | ||
| public class AdminMatchResponse { | ||
|
|
||
| @Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
| private final UUID matchId; | ||
|
|
||
| @Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
| private final UUID memberAId; | ||
|
|
||
| @Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
| private final UUID memberBId; | ||
|
|
||
| @Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
| private final Integer matchCycleId; | ||
|
|
||
| @Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
| private final String month; | ||
|
|
||
| @Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
| private final String status; | ||
|
|
||
| @Schema(requiredMode = Schema.RequiredMode.REQUIRED, nullable = true) | ||
| private final Double matchScore; | ||
|
|
||
| @Schema(requiredMode = Schema.RequiredMode.REQUIRED, nullable = true) | ||
| private final String feedbackA; | ||
|
|
||
| @Schema(requiredMode = Schema.RequiredMode.REQUIRED, nullable = true) | ||
| private final String feedbackB; | ||
|
|
||
| @Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
| private final Instant createdAt; | ||
|
|
||
| public static AdminMatchResponse from(final Match match, final String month) { | ||
| return AdminMatchResponse.builder() | ||
| .matchId(match.getId()) | ||
| .memberAId(match.getMemberAId()) | ||
| .memberBId(match.getMemberBId()) | ||
| .matchCycleId(match.getMatchCycleId()) | ||
| .month(month) | ||
| .status(match.getStatus()) | ||
| .matchScore(match.getMatchScore()) | ||
| .feedbackA(match.getFeedbackA()) | ||
| .feedbackB(match.getFeedbackB()) | ||
| .createdAt(match.getCreatedAt()) | ||
| .build(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package org.patinanetwork.patchats.api.match.dto.match; | ||
|
|
||
| import jakarta.validation.Valid; | ||
| import jakarta.validation.constraints.NotEmpty; | ||
| import java.util.List; | ||
|
|
||
| public record BulkCreateMatchesRequest(@Valid @NotEmpty List<CreateMatchRequest> matches) {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package org.patinanetwork.patchats.api.match.dto.match; | ||
|
|
||
| import jakarta.validation.constraints.NotNull; | ||
| import java.util.UUID; | ||
|
|
||
| public record CreateMatchRequest( | ||
| @NotNull UUID memberAId, | ||
| @NotNull UUID memberBId, | ||
| @NotNull Integer matchCycleId, | ||
| Double matchScore, | ||
| String status) {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package org.patinanetwork.patchats.api.match.dto.match; | ||
|
|
||
| import java.time.Instant; | ||
| import java.util.UUID; | ||
|
|
||
| public record MatchListQuery(UUID memberId, String period, Instant startTime, Instant endTime, String status) {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| package org.patinanetwork.patchats.api.match.dto.match; | ||
|
|
||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import java.util.UUID; | ||
| import lombok.Builder; | ||
| import lombok.EqualsAndHashCode; | ||
| import lombok.Getter; | ||
| import lombok.ToString; | ||
| import org.patinanetwork.patchats.api.match.db.models.Match; | ||
|
|
||
| @Getter | ||
| @Builder | ||
| @ToString | ||
| @EqualsAndHashCode | ||
| public class MatchResponse { | ||
|
|
||
| @Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
| private final UUID matchId; | ||
|
|
||
| @Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
| private final UUID memberAId; | ||
|
|
||
| @Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
| private final UUID memberBId; | ||
|
|
||
| @Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
| private final String month; | ||
|
|
||
| @Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
| private final String status; | ||
|
|
||
| @Schema(requiredMode = Schema.RequiredMode.REQUIRED, nullable = true) | ||
| private final String feedbackA; | ||
|
|
||
| @Schema(requiredMode = Schema.RequiredMode.REQUIRED, nullable = true) | ||
| private final String feedbackB; | ||
|
|
||
| public static MatchResponse from(final Match match, final String month) { | ||
| return MatchResponse.builder() | ||
| .matchId(match.getId()) | ||
| .memberAId(match.getMemberAId()) | ||
| .memberBId(match.getMemberBId()) | ||
| .month(month) | ||
| .status(match.getStatus()) | ||
| .feedbackA(match.getFeedbackA()) | ||
| .feedbackB(match.getFeedbackB()) | ||
| .build(); | ||
| } | ||
| } |
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lets drop everything related to feedback. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| package org.patinanetwork.patchats.api.match.dto.match; | ||
|
|
||
| import jakarta.validation.constraints.NotBlank; | ||
|
|
||
| public record UpdateMatchFeedbackRequest(@NotBlank String feedback) {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| package org.patinanetwork.patchats.api.match.dto.match; | ||
|
|
||
| import jakarta.validation.constraints.NotBlank; | ||
|
|
||
| public record UpdateMatchStatusRequest(@NotBlank String status) {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package org.patinanetwork.patchats.common.web.exception; | ||
|
|
||
| public class MatchCycleNotFoundException extends RuntimeException { | ||
| public MatchCycleNotFoundException(Integer id) { | ||
| super("Match Cycle with ID " + id + " not found"); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package org.patinanetwork.patchats.common.web.exception; | ||
|
|
||
| import java.util.UUID; | ||
|
|
||
| public class MatchNotFoundException extends RuntimeException { | ||
| public MatchNotFoundException(UUID id) { | ||
| super("Match with ID " + id + " not found"); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets just stick with MatchController for everything, and then we can handle the permissioning after the fact, once Randy's done.