Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
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;
Expand All @@ -25,26 +26,30 @@ public class MatchCycleController {

private final MatchCycleService matchCycleService;

@Operation(summary = "Create a match cycle")
@PostMapping
public ResponseEntity<ApiResponder<MatchCycleResponse>> createMatchCycle(
@Valid @RequestBody final CreateMatchCycleRequest request) {
final MatchCycleResponse response = matchCycleService.createMatchCycle(request);
return ResponseEntity.ok(ApiResponder.success("Match Cycle created successfully", response));
}

@Operation(summary = "Update a match cycle")
@PatchMapping("/{id}")
public ResponseEntity<ApiResponder<MatchCycleResponse>> updateMatchCycle(
@Valid @RequestBody final UpdateMatchCycleRequest request, @PathVariable final Integer id) {
final MatchCycleResponse response = matchCycleService.updateMatchCycle(request, id);
return ResponseEntity.ok(ApiResponder.success("Match Cycle updated successfully", response));
}

@Operation(summary = "Get a match cycle by ID")
@GetMapping("/{id}")
public ResponseEntity<ApiResponder<MatchCycleResponse>> getMatchCycleById(@PathVariable final Integer id) {
final MatchCycleResponse response = matchCycleService.getMatchCycleById(id);
return ResponseEntity.ok(ApiResponder.success("Match Cycle retrieved successfully", response));
}

@Operation(summary = "Delete a match cycle")
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponder<MatchCycleResponse>> deleteMatchCycle(@PathVariable final Integer id) {
final MatchCycleResponse response = matchCycleService.deleteMatchCycleById(id);
Expand Down
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));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
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)
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())
.createdAt(match.getCreatedAt())
.build();
}
}
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,74 @@
package org.patinanetwork.patchats.api.match;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.time.Instant;
import java.util.Optional;
import java.util.UUID;
import org.junit.jupiter.api.Test;
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;

class MatchServiceTest {

private final MatchRepo matchRepo = mock(MatchRepo.class);
private final MatchCycleRepo matchCycleRepo = mock(MatchCycleRepo.class);
private final MatchService matchService = new MatchService(matchRepo, matchCycleRepo);

private MatchCycle stubCycle() {
return MatchCycle.builder()
.id(1)
.period("2026-07")
.runAt(Instant.parse("2026-07-15T10:00:00Z"))
.build();
}

@Test
void createMatch_successWithAllFields() {
final CreateMatchRequest request =
new CreateMatchRequest(UUID.randomUUID(), UUID.randomUUID(), 1, 0.85, "CONFIRMED");
when(matchCycleRepo.getMatchCycleById(1)).thenReturn(Optional.of(stubCycle()));
when(matchRepo.createMatch(any())).thenAnswer(invocation -> invocation.getArgument(0));

final AdminMatchResponse response = matchService.createMatch(request);

assertNotNull(response.getMatchId());
assertEquals(request.memberAId(), response.getMemberAId());
assertEquals(request.memberBId(), response.getMemberBId());
assertEquals(1, response.getMatchCycleId());
assertEquals("2026-07", response.getMonth());
assertEquals("CONFIRMED", response.getStatus());
assertEquals(0.85, response.getMatchScore());
}

@Test
void createMatch_defaultsStatusToPendingWhenStatusMissing() {
final CreateMatchRequest request = new CreateMatchRequest(UUID.randomUUID(), UUID.randomUUID(), 1, null, null);
when(matchCycleRepo.getMatchCycleById(1)).thenReturn(Optional.of(stubCycle()));
when(matchRepo.createMatch(any())).thenAnswer(invocation -> invocation.getArgument(0));

final AdminMatchResponse response = matchService.createMatch(request);

assertEquals("PENDING", response.getStatus());
}

@Test
void createMatch_throwsMatchCycleNotFoundWhenCycleMissing() {
final CreateMatchRequest request = new CreateMatchRequest(UUID.randomUUID(), UUID.randomUUID(), 99, null, null);
when(matchCycleRepo.getMatchCycleById(99)).thenReturn(Optional.empty());

assertThrows(MatchCycleNotFoundException.class, () -> matchService.createMatch(request));
verify(matchRepo, never()).createMatch(any());
}
}