feat: 매칭 신청/응답 API 및 티어별 쿼터 가드
- POST /api/matches (신청), /{id}/accept·/reject (응답), GET /received/{dogId}
- 티어 가드: PREMIUM 무제한, FREE/AD_FREE 하루 3회 소진 시 429
- 자기신청/중복신청 차단, 전역 예외 핸들러(404/409/400/429)
- Dog/MatchingRequest 엔티티 및 리포지토리, 통합 테스트 4건 추가(총 7건 통과)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
package com.dog.dognation.api;
|
||||
|
||||
import com.dog.dognation.domain.matching.QuotaExceededException;
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@RestControllerAdvice
|
||||
public class ApiExceptionHandler {
|
||||
|
||||
/** 매칭 쿼터 초과 → 429 Too Many Requests */
|
||||
@ExceptionHandler(QuotaExceededException.class)
|
||||
public ResponseEntity<Map<String, String>> handleQuota(QuotaExceededException e) {
|
||||
return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS)
|
||||
.body(Map.of("code", "MATCH_QUOTA_EXCEEDED", "message", e.getMessage()));
|
||||
}
|
||||
|
||||
/** 존재하지 않는 리소스 → 404 */
|
||||
@ExceptionHandler(EntityNotFoundException.class)
|
||||
public ResponseEntity<Map<String, String>> handleNotFound(EntityNotFoundException e) {
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND)
|
||||
.body(Map.of("code", "NOT_FOUND", "message", e.getMessage()));
|
||||
}
|
||||
|
||||
/** 잘못된 상태(중복 신청/이미 처리됨) → 409 Conflict */
|
||||
@ExceptionHandler(IllegalStateException.class)
|
||||
public ResponseEntity<Map<String, String>> handleConflict(IllegalStateException e) {
|
||||
return ResponseEntity.status(HttpStatus.CONFLICT)
|
||||
.body(Map.of("code", "CONFLICT", "message", e.getMessage()));
|
||||
}
|
||||
|
||||
/** 잘못된 입력 → 400 */
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
public ResponseEntity<Map<String, String>> handleBadRequest(IllegalArgumentException e) {
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||
.body(Map.of("code", "BAD_REQUEST", "message", e.getMessage()));
|
||||
}
|
||||
|
||||
/** @Valid 검증 실패 → 400 */
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ResponseEntity<Map<String, String>> handleValidation(MethodArgumentNotValidException e) {
|
||||
String msg = e.getBindingResult().getFieldErrors().stream()
|
||||
.findFirst()
|
||||
.map(err -> err.getDefaultMessage())
|
||||
.orElse("잘못된 요청입니다.");
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||
.body(Map.of("code", "VALIDATION_ERROR", "message", msg));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.dog.dognation.api;
|
||||
|
||||
import com.dog.dognation.api.dto.CreateMatchRequest;
|
||||
import com.dog.dognation.api.dto.MatchResponse;
|
||||
import com.dog.dognation.domain.matching.MatchingRequest;
|
||||
import com.dog.dognation.domain.matching.MatchingService;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
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;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/matches")
|
||||
public class MatchingController {
|
||||
|
||||
private final MatchingService matchingService;
|
||||
|
||||
public MatchingController(MatchingService matchingService) {
|
||||
this.matchingService = matchingService;
|
||||
}
|
||||
|
||||
/** 매칭 신청. FREE/AD_FREE 는 하루 3회 제한, 초과 시 429. */
|
||||
@PostMapping
|
||||
public ResponseEntity<MatchResponse> create(@Valid @RequestBody CreateMatchRequest request) {
|
||||
MatchingRequest created = matchingService.createRequest(
|
||||
request.requesterDogId(), request.targetDogId());
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(MatchResponse.from(created));
|
||||
}
|
||||
|
||||
/** 대상 견주가 신청 수락. */
|
||||
@PostMapping("/{id}/accept")
|
||||
public MatchResponse accept(@PathVariable Long id) {
|
||||
return MatchResponse.from(matchingService.respond(id, true));
|
||||
}
|
||||
|
||||
/** 대상 견주가 신청 거절. */
|
||||
@PostMapping("/{id}/reject")
|
||||
public MatchResponse reject(@PathVariable Long id) {
|
||||
return MatchResponse.from(matchingService.respond(id, false));
|
||||
}
|
||||
|
||||
/** 특정 견이 받은 대기중 신청 목록. */
|
||||
@GetMapping("/received/{dogId}")
|
||||
public List<MatchResponse> received(@PathVariable Long dogId) {
|
||||
return matchingService.findPendingReceived(dogId).stream()
|
||||
.map(MatchResponse::from)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.dog.dognation.api.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
public record CreateMatchRequest(
|
||||
@NotNull(message = "requesterDogId 는 필수입니다.") Long requesterDogId,
|
||||
@NotNull(message = "targetDogId 는 필수입니다.") Long targetDogId
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.dog.dognation.api.dto;
|
||||
|
||||
import com.dog.dognation.domain.matching.MatchingRequest;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
public record MatchResponse(
|
||||
Long id,
|
||||
Long requesterDogId,
|
||||
Long targetDogId,
|
||||
String status,
|
||||
OffsetDateTime createdAt,
|
||||
OffsetDateTime respondedAt
|
||||
) {
|
||||
public static MatchResponse from(MatchingRequest r) {
|
||||
return new MatchResponse(
|
||||
r.getId(),
|
||||
r.getRequesterDogId(),
|
||||
r.getTargetDogId(),
|
||||
r.getStatus().name(),
|
||||
r.getCreatedAt(),
|
||||
r.getRespondedAt()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.dog.dognation.domain.dog;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "dogs")
|
||||
public class Dog {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "user_id", nullable = false)
|
||||
private Long userId;
|
||||
|
||||
@Column(nullable = false, length = 50)
|
||||
private String name;
|
||||
|
||||
@Column(length = 100)
|
||||
private String breed;
|
||||
|
||||
@Column(name = "birth_date")
|
||||
private LocalDate birthDate;
|
||||
|
||||
@Column(length = 20)
|
||||
private String size;
|
||||
|
||||
@Column(length = 10)
|
||||
private String gender;
|
||||
|
||||
@Column(nullable = false)
|
||||
private boolean neutered = false;
|
||||
|
||||
@Column(name = "image_url", length = 500)
|
||||
private String imageUrl;
|
||||
|
||||
@Column(name = "created_at", nullable = false)
|
||||
private OffsetDateTime createdAt = OffsetDateTime.now();
|
||||
|
||||
protected Dog() {
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/** 소유자(견주) 사용자 ID. 매칭 쿼터/티어 판정 기준. */
|
||||
public Long getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.dog.dognation.domain.dog;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface DogRepository extends JpaRepository<Dog, Long> {
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.dog.dognation.domain.matching;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "matching_requests")
|
||||
public class MatchingRequest {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "requester_dog_id", nullable = false)
|
||||
private Long requesterDogId;
|
||||
|
||||
@Column(name = "target_dog_id", nullable = false)
|
||||
private Long targetDogId;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false, length = 20)
|
||||
private MatchingStatus status = MatchingStatus.PENDING;
|
||||
|
||||
@Column(name = "created_at", nullable = false)
|
||||
private OffsetDateTime createdAt = OffsetDateTime.now();
|
||||
|
||||
@Column(name = "responded_at")
|
||||
private OffsetDateTime respondedAt;
|
||||
|
||||
protected MatchingRequest() {
|
||||
}
|
||||
|
||||
public MatchingRequest(Long requesterDogId, Long targetDogId) {
|
||||
this.requesterDogId = requesterDogId;
|
||||
this.targetDogId = targetDogId;
|
||||
this.status = MatchingStatus.PENDING;
|
||||
this.createdAt = OffsetDateTime.now();
|
||||
}
|
||||
|
||||
/** 대상 견주가 신청을 수락/거절 처리한다. */
|
||||
public void respond(MatchingStatus result) {
|
||||
if (result != MatchingStatus.ACCEPTED && result != MatchingStatus.REJECTED) {
|
||||
throw new IllegalArgumentException("응답은 ACCEPTED 또는 REJECTED 여야 합니다.");
|
||||
}
|
||||
this.status = result;
|
||||
this.respondedAt = OffsetDateTime.now();
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Long getRequesterDogId() {
|
||||
return requesterDogId;
|
||||
}
|
||||
|
||||
public Long getTargetDogId() {
|
||||
return targetDogId;
|
||||
}
|
||||
|
||||
public MatchingStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public OffsetDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public OffsetDateTime getRespondedAt() {
|
||||
return respondedAt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.dog.dognation.domain.matching;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface MatchingRequestRepository extends JpaRepository<MatchingRequest, Long> {
|
||||
|
||||
boolean existsByRequesterDogIdAndTargetDogIdAndStatus(
|
||||
Long requesterDogId, Long targetDogId, MatchingStatus status);
|
||||
|
||||
/** 특정 견이 받은 신청 목록(대상 기준). */
|
||||
List<MatchingRequest> findByTargetDogIdAndStatusOrderByCreatedAtDesc(
|
||||
Long targetDogId, MatchingStatus status);
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.dog.dognation.domain.matching;
|
||||
|
||||
import com.dog.dognation.domain.dog.Dog;
|
||||
import com.dog.dognation.domain.dog.DogRepository;
|
||||
import com.dog.dognation.domain.user.User;
|
||||
import com.dog.dognation.domain.user.UserRepository;
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 매칭 신청/응답 도메인 서비스.
|
||||
*
|
||||
* 티어별 쿼터 가드:
|
||||
* - PREMIUM : 무제한 (쿼터 차감 없음)
|
||||
* - FREE/AD_FREE : 하루 3회. consumeOne() 으로 1회 차감, 소진 시 QuotaExceededException.
|
||||
*/
|
||||
@Service
|
||||
public class MatchingService {
|
||||
|
||||
private final MatchingRequestRepository matchingRequestRepository;
|
||||
private final DogRepository dogRepository;
|
||||
private final UserRepository userRepository;
|
||||
private final MatchQuotaService quotaService;
|
||||
|
||||
public MatchingService(MatchingRequestRepository matchingRequestRepository,
|
||||
DogRepository dogRepository,
|
||||
UserRepository userRepository,
|
||||
MatchQuotaService quotaService) {
|
||||
this.matchingRequestRepository = matchingRequestRepository;
|
||||
this.dogRepository = dogRepository;
|
||||
this.userRepository = userRepository;
|
||||
this.quotaService = quotaService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public MatchingRequest createRequest(Long requesterDogId, Long targetDogId) {
|
||||
if (requesterDogId.equals(targetDogId)) {
|
||||
throw new IllegalArgumentException("자기 자신에게는 매칭을 신청할 수 없습니다.");
|
||||
}
|
||||
|
||||
Dog requesterDog = dogRepository.findById(requesterDogId)
|
||||
.orElseThrow(() -> new EntityNotFoundException("신청 견을 찾을 수 없습니다: " + requesterDogId));
|
||||
if (!dogRepository.existsById(targetDogId)) {
|
||||
throw new EntityNotFoundException("대상 견을 찾을 수 없습니다: " + targetDogId);
|
||||
}
|
||||
|
||||
// 같은 대상에게 이미 대기중인 신청이 있으면 중복 방지 (쿼터 차감 전에 검사)
|
||||
if (matchingRequestRepository.existsByRequesterDogIdAndTargetDogIdAndStatus(
|
||||
requesterDogId, targetDogId, MatchingStatus.PENDING)) {
|
||||
throw new IllegalStateException("이미 대기중인 매칭 신청이 있습니다.");
|
||||
}
|
||||
|
||||
// 티어별 쿼터 가드
|
||||
User owner = userRepository.findById(requesterDog.getUserId())
|
||||
.orElseThrow(() -> new EntityNotFoundException("견주를 찾을 수 없습니다: " + requesterDog.getUserId()));
|
||||
if (owner.getSubscriptionTier().isQuotaLimited()) {
|
||||
if (!quotaService.consumeOne(owner.getId())) {
|
||||
throw new QuotaExceededException(
|
||||
"오늘의 무료 매칭 신청 횟수(3회)를 모두 사용했습니다. PREMIUM 에서 무제한으로 이용하세요.");
|
||||
}
|
||||
}
|
||||
|
||||
return matchingRequestRepository.save(new MatchingRequest(requesterDogId, targetDogId));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public MatchingRequest respond(Long requestId, boolean accept) {
|
||||
MatchingRequest request = matchingRequestRepository.findById(requestId)
|
||||
.orElseThrow(() -> new EntityNotFoundException("매칭 신청을 찾을 수 없습니다: " + requestId));
|
||||
if (request.getStatus() != MatchingStatus.PENDING) {
|
||||
throw new IllegalStateException("이미 처리된 매칭 신청입니다. (현재 상태: " + request.getStatus() + ")");
|
||||
}
|
||||
request.respond(accept ? MatchingStatus.ACCEPTED : MatchingStatus.REJECTED);
|
||||
return request; // 더티 체킹으로 flush
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<MatchingRequest> findPendingReceived(Long targetDogId) {
|
||||
return matchingRequestRepository.findByTargetDogIdAndStatusOrderByCreatedAtDesc(
|
||||
targetDogId, MatchingStatus.PENDING);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.dog.dognation.domain.matching;
|
||||
|
||||
public enum MatchingStatus {
|
||||
PENDING, // 신청 대기
|
||||
ACCEPTED, // 수락됨
|
||||
REJECTED, // 거절됨
|
||||
CANCELED // 신청 취소
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.dog.dognation.domain.matching;
|
||||
|
||||
/** 하루 무료 매칭 신청 한도를 초과했을 때 발생. (HTTP 429 로 매핑) */
|
||||
public class QuotaExceededException extends RuntimeException {
|
||||
public QuotaExceededException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.dog.dognation.domain.user;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package com.dog.dognation.matching;
|
||||
|
||||
import com.dog.dognation.domain.matching.MatchingRequest;
|
||||
import com.dog.dognation.domain.matching.MatchingService;
|
||||
import com.dog.dognation.domain.matching.MatchingStatus;
|
||||
import com.dog.dognation.domain.matching.QuotaExceededException;
|
||||
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.jdbc.core.JdbcTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* 매칭 신청 티어 가드 검증.
|
||||
* - FREE/AD_FREE : 하루 3회, 4번째부터 QuotaExceededException
|
||||
* - PREMIUM : 무제한
|
||||
*/
|
||||
@SpringBootTest
|
||||
class MatchingServiceTest {
|
||||
|
||||
@Autowired MatchingService matchingService;
|
||||
@Autowired JdbcTemplate jdbc;
|
||||
|
||||
private long freeDogId;
|
||||
private long premiumDogId;
|
||||
private final long[] targetDogIds = new long[6];
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
jdbc.update("DELETE FROM matching_requests");
|
||||
jdbc.update("DELETE FROM daily_match_quota");
|
||||
jdbc.update("DELETE FROM dogs");
|
||||
jdbc.update("DELETE FROM users");
|
||||
|
||||
long freeUser = seedUser("free@dog.com", "FREE");
|
||||
long premiumUser = seedUser("premium@dog.com", "PREMIUM");
|
||||
long targetUser = seedUser("target@dog.com", "FREE");
|
||||
|
||||
freeDogId = seedDog(freeUser, "몽이");
|
||||
premiumDogId = seedDog(premiumUser, "레오");
|
||||
for (int i = 0; i < targetDogIds.length; i++) {
|
||||
targetDogIds[i] = seedDog(targetUser, "대상견" + i);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("FREE: 하루 3회까지 신청 성공, 4번째는 429(QuotaExceededException)")
|
||||
void free_blockedAfterThree() {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
MatchingRequest req = matchingService.createRequest(freeDogId, targetDogIds[i]);
|
||||
assertThat(req.getStatus()).isEqualTo(MatchingStatus.PENDING);
|
||||
}
|
||||
|
||||
assertThatThrownBy(() -> matchingService.createRequest(freeDogId, targetDogIds[3]))
|
||||
.isInstanceOf(QuotaExceededException.class);
|
||||
|
||||
// 차단된 신청은 저장되지 않아야 함 → 총 3건
|
||||
Integer count = jdbc.queryForObject(
|
||||
"SELECT count(*) FROM matching_requests WHERE requester_dog_id = ?", Integer.class, freeDogId);
|
||||
assertThat(count).isEqualTo(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("PREMIUM: 3회 초과해도 무제한 신청 가능")
|
||||
void premium_unlimited() {
|
||||
for (int i = 0; i < targetDogIds.length; i++) {
|
||||
matchingService.createRequest(premiumDogId, targetDogIds[i]);
|
||||
}
|
||||
Integer count = jdbc.queryForObject(
|
||||
"SELECT count(*) FROM matching_requests WHERE requester_dog_id = ?", Integer.class, premiumDogId);
|
||||
assertThat(count).isEqualTo(targetDogIds.length); // 6건 전부 성공
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("수락 처리 시 상태가 ACCEPTED 로 바뀌고 respondedAt 이 기록된다")
|
||||
void respond_accept() {
|
||||
MatchingRequest req = matchingService.createRequest(freeDogId, targetDogIds[0]);
|
||||
MatchingRequest accepted = matchingService.respond(req.getId(), true);
|
||||
|
||||
assertThat(accepted.getStatus()).isEqualTo(MatchingStatus.ACCEPTED);
|
||||
assertThat(accepted.getRespondedAt()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("자기 자신에게는 신청할 수 없다")
|
||||
void cannotRequestSelf() {
|
||||
assertThatThrownBy(() -> matchingService.createRequest(freeDogId, freeDogId))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
// --- helpers ---
|
||||
|
||||
private long seedUser(String email, String tier) {
|
||||
jdbc.update("""
|
||||
INSERT INTO users(email, password_hash, nickname, subscription_tier, status, created_at, updated_at)
|
||||
VALUES (?, 'x', ?, ?, 'ACTIVE', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||
""", email, email, tier);
|
||||
return jdbc.queryForObject("SELECT id FROM users WHERE email = ?", Long.class, email);
|
||||
}
|
||||
|
||||
private long seedDog(long userId, String name) {
|
||||
jdbc.update("""
|
||||
INSERT INTO dogs(user_id, name, neutered, created_at)
|
||||
VALUES (?, ?, false, CURRENT_TIMESTAMP)
|
||||
""", userId, name);
|
||||
return jdbc.queryForObject(
|
||||
"SELECT id FROM dogs WHERE user_id = ? AND name = ?", Long.class, userId, name);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user