Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
@@ -0,0 +1,101 @@
package com.soongsil.CoffeeChat.domain.assignedcoupon.controller;

import java.util.List;

import com.soongsil.CoffeeChat.domain.assignedcoupon.dto.*;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.web.ErrorResponse;
import org.springframework.web.bind.annotation.*;

import com.soongsil.CoffeeChat.domain.assignedcoupon.service.AssignedCouponService;
import com.soongsil.CoffeeChat.global.api.ApiResponse;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v2/assigned-coupons")
@Tag(name = "ASSIGNED COUPON", description = "์ถ•์ œ ์ „์šฉ ์ง€์ • ์ปคํ”ผ ์ฟ ํฐ ๊ด€๋ จ API")
public class AssignedCouponController {

private final AssignedCouponService assignedCouponService;

// ์œ ์ € - ๋ณด๊ด€ํ•จ ์ง„์ž… ์‹œ ๋ฐœ๊ธ‰ ์ž๊ฒฉ ํ™•์ธ
@GetMapping("/eligibility")
@Operation(
summary = "์ง€์ • ์ฟ ํฐ ๋ฐœ๊ธ‰ ์ž๊ฒฉ ํ™•์ธ",
description =
"๋ณด๊ด€ํ•จ ์ง„์ž… ์‹œ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ๋ณธ์ธ์˜ name + phoneNum ์ด ์‚ฌ์ „ ๋“ฑ๋ก๋œ ๋Œ€์ƒ์ž ๋ช…๋‹จ๊ณผ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค."
+ " ๋ฐœ๊ธ‰ ์ด๋ ฅ์ด ์žˆ๋Š” ๊ฒฝ์šฐ ๋ฐœ๊ธ‰ ์ •๋ณด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.")
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "200",
description = "์กฐํšŒ ์„ฑ๊ณต (result.eligible=true && !alreadyIssued ์ผ ๋•Œ๋งŒ ๋ฐœ๊ธ‰ ๋ฒ„ํŠผ ํ™œ์„ฑํ™”)")
public ResponseEntity<ApiResponse<AssignedCouponCheckResponse>> checkEligibility(
Authentication authentication) {
return ResponseEntity.ok()
.body(
ApiResponse.onSuccessOK(
assignedCouponService.checkEligibility(
authentication.getName())));
}

// ์œ ์ € - ๋งค์žฅ PIN ์ธ์ฆ์œผ๋กœ ์ง€์ • ์ฟ ํฐ ๋ฐœ๊ธ‰ (=์‚ฌ์šฉ ์ฒ˜๋ฆฌ)
@PostMapping("/coupons")
@Operation(
summary = "์ง€์ • ์ฟ ํฐ ๋ฐœ๊ธ‰ (๋งค์žฅ PIN ์ธ์ฆ)",
description = "๋งค์žฅ ์ง์›์˜ ํ•€ ๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅ๋ฐ›์•„ ์ตœ์ข… ๊ฒ€์ฆ ํ›„ ์ฟ ํฐ์„ ๋ฐœ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค. ๋ฐœ๊ธ‰๊ณผ ๋™์‹œ์— ์‚ฌ์šฉ ์ฒ˜๋ฆฌ๋˜์–ด ์žฌ์‚ฌ์šฉ ๋ถˆ๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.")
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "200",
description = "๋ฐœ๊ธ‰๋œ ์ฟ ํฐ ์ •๋ณด ๋ฐ˜ํ™˜")
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "400",
description = "EVENT_400_1: ํ•€ ๋ฒˆํ˜ธ ๋ถˆ์ผ์น˜ | ASSIGNED_COUPON_400: ์ „ํ™”๋ฒˆํ˜ธ ๋ฏธ๋“ฑ๋ก",
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "404",
description = "ASSIGNED_COUPON_404: ๋ฐœ๊ธ‰ ๋Œ€์ƒ์ž ์•„๋‹˜",
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "409",
description = "ASSIGNED_COUPON_409: ์ด๋ฏธ ๋ฐœ๊ธ‰๋œ ์ฟ ํฐ",
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "503",
description = "EVENT_503: ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ ์˜ค๋ฅ˜",
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
public ResponseEntity<ApiResponse<AssignedCouponResponse>> issueCoupon(
@RequestParam String storePin, Authentication authentication) {
return ResponseEntity.ok()
.body(
ApiResponse.onSuccessOK(
assignedCouponService.issueCoupon(
authentication.getName(), storePin)));
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
}

// ************ ๊ด€๋ฆฌ์ž์šฉ api ************

@PostMapping("/admin/register")
@Operation(
summary = "์ง€์ • ์ฟ ํฐ ๋Œ€์ƒ์ž ์ผ๊ด„ ๋“ฑ๋ก (๊ด€๋ฆฌ์ž)",
description = "๋Œ€์ƒ์ž(์ด๋ฆ„ + ์ „ํ™”๋ฒˆํ˜ธ) ๋ชฉ๋ก์„ ๋ฐ›์•„ Redis์— ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค. ์ „ํ™”๋ฒˆํ˜ธ๋Š” ํ•˜์ดํ”ˆ ์œ ๋ฌด ๋ฌด๊ด€.")
public ResponseEntity<ApiResponse<AssignedCouponRegisterResult>> registerTargets(
@RequestBody List<AssignedCouponTargetRequest> targets) {
return ResponseEntity.ok()
.body(ApiResponse.onSuccessOK(assignedCouponService.registerTargets(targets)));
}

@PostMapping("/admin/register/one")
@Operation(summary = "์ง€์ • ์ฟ ํฐ ๋Œ€์ƒ์ž ๋‹จ๊ฑด ๋“ฑ๋ก (๊ด€๋ฆฌ์ž)")
public ResponseEntity<ApiResponse<AssignedCouponRegisterResult>> registerOneTarget(
@RequestBody AssignedCouponTargetRequest target) {
return ResponseEntity.ok()
.body(
ApiResponse.onSuccessOK(
assignedCouponService.registerTargets(List.of(target))));
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.soongsil.CoffeeChat.domain.assignedcoupon.dto;

import java.time.LocalDateTime;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Builder
@AllArgsConstructor
public class AssignedCouponCheckResponse {

private boolean eligible;
private boolean alreadyIssued;
private String name;
private String couponNumber;
private String status;
private LocalDateTime issuedAt;
private LocalDateTime usedAt;

public static AssignedCouponCheckResponse notEligible() {
return AssignedCouponCheckResponse.builder()
.eligible(false)
.alreadyIssued(false)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.soongsil.CoffeeChat.domain.assignedcoupon.dto;

import java.util.List;

public record AssignedCouponRegisterResult(
int totalRequested,
int newlyRegistered,
int duplicated,
List<String> failedPhoneNums) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.soongsil.CoffeeChat.domain.assignedcoupon.dto;

import java.time.LocalDateTime;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Builder
@AllArgsConstructor
public class AssignedCouponResponse {

private String couponNumber;
private String name;
private String status;
private LocalDateTime issuedAt;
private LocalDateTime usedAt;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.soongsil.CoffeeChat.domain.assignedcoupon.dto;

public record AssignedCouponTargetRequest(String name, String phoneNum) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.soongsil.CoffeeChat.domain.assignedcoupon.message;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.soongsil.CoffeeChat.infra.aws.s3.service.AmazonS3Service;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
@RequiredArgsConstructor
public class AssignedCouponIssueEventListener {

private final AmazonS3Service amazonS3Service;
private final ObjectMapper objectMapper;

@Value("${cloud.aws.s3.bucket}")
private String bucket;

@Async
@EventListener
public void handleAssignedCouponIssuedEvent(AssignedCouponIssuedEvent event) {
try {
// AssignedCouponIssuedEvent JSON ์ง๋ ฌํ™”
String logJson = objectMapper.writeValueAsString(event);

String fileNamePrefix = "assigned-coupon-" + event.couponNumber();

// event-logs/assigned-coupon ๋””๋ ‰ํ† ๋ฆฌ์— ์ €์žฅ
String fileUrl =
amazonS3Service.uploadJsonFile(
logJson, "event-logs/assigned-coupon", fileNamePrefix);

log.info("S3 ์ง€์ • ์ฟ ํฐ ๋ฐœ๊ธ‰ ๋กœ๊ทธ ์—…๋กœ๋“œ ์™„๋ฃŒ: {} (URL: {})", fileNamePrefix, fileUrl);

} catch (Exception e) {
log.error("S3 ์ง€์ • ์ฟ ํฐ ๋ฐœ๊ธ‰ ๋กœ๊ทธ ์—…๋กœ๋“œ ์‹คํŒจ. couponNumber: {}", event.couponNumber(), e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.soongsil.CoffeeChat.domain.assignedcoupon.message;

import java.time.LocalDateTime;

// ์ง€์ • ์ฟ ํฐ ๋น„๋™๊ธฐ ๋กœ๊น…์šฉ dto
public record AssignedCouponIssuedEvent(
String username,
String name,
String phoneNum,
String couponNumber,
LocalDateTime issuedAt) {
}
Loading
Loading