Skip to content
Merged
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
16 changes: 13 additions & 3 deletions src/main/java/com/flyway/auth/service/SignUpServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.flyway.auth.domain.KakaoUserInfo;
import com.flyway.auth.dto.EmailSignUpRequest;
import com.flyway.auth.repository.SignUpAttemptRepository;
import com.flyway.sender.service.SmsVerificationService;
import com.flyway.template.exception.BusinessException;
import com.flyway.template.exception.ErrorCode;
import com.flyway.user.domain.User;
Expand All @@ -30,13 +31,19 @@ public class SignUpServiceImpl implements SignUpService {
private final UserProfileRepository userProfileRepository;
private final SignUpAttemptRepository signUpAttemptRepository;
private final PasswordEncoder passwordEncoder;
private final SmsVerificationService smsVerificationService;

@Override
@Transactional
public void signUp(EmailSignUpRequest request) {
validateRequest(request);
LocalDateTime now = LocalDateTime.now();

// SMS 인증 확인
if (!smsVerificationService.isVerified(request.getPhoneNumber())) {
throw new BusinessException(ErrorCode.USER_PHONE_NOT_VERIFIED);
}

int validAttempt = signUpAttemptRepository.consumeIfVerified(request.getAttemptId(), request.getEmail(), now);
if (validAttempt != 1) {
throw new BusinessException(ErrorCode.USER_INVALID_SIGN_UP_ATTEMPT);
Expand Down Expand Up @@ -70,6 +77,7 @@ public void signUp(EmailSignUpRequest request) {

userRepository.save(user);


// UserIdentity 생성
UserIdentity identity = UserIdentity.builder()
.userIdentityId(UUID.randomUUID().toString())
Expand Down Expand Up @@ -124,14 +132,12 @@ public User signUpKakaoUser(KakaoUserInfo userInfo) {
.build();

userIdentityRepository.save(identity);

UserProfile profile = UserProfile.builder()
.userId(userId)
.name(nickname)
.phoneNumber("")
.build();

userProfileRepository.createProfile(profile);

return user;
}

Expand All @@ -155,6 +161,10 @@ public void completeOauthSignUp(String userId, EmailSignUpRequest request) {

userRepository.updateStatus(userId, AuthStatus.ACTIVE);

if (!smsVerificationService.isVerified(request.getPhoneNumber())) {
throw new BusinessException(ErrorCode.USER_PHONE_NOT_VERIFIED);
}

UserProfile profile = UserProfile.builder()
.userId(userId)
.name(request.getName())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ protected void configure(HttpSecurity http) throws Exception {
// 비회원 허용 API
.antMatchers(
"/api/auth/**",
"/api/public/**"
"/api/public/**",
"/api/sms/**"
).permitAll()

.anyRequest().authenticated()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public class SecurityConfigWeb extends WebSecurityConfigurerAdapter {

private static final String[] PUBLIC_ENDPOINTS = {
"/", "/login", "/loginProc", "/signup", "/auth/**", "/search/**",
"/payments/success", "/payments/fail", "/payments/complete" //결제 콜백
"/payments/success", "/payments/fail", "/payments/complete", "/api/sms/**"
};

private final JwtProvider jwtProvider;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ public class OnboardingAccessFilter extends OncePerRequestFilter {
"/favicon.ico",
"/error",
"/api/auth/",
"/api/public/"
"/api/public/",
"/api/sms/"
};

@Override
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/com/flyway/security/jwt/JwtApiAuthFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ protected boolean shouldNotFilter(@NonNull HttpServletRequest request) {
}

return path.startsWith("/api/public/")
|| path.startsWith("/api/auth/");
|| path.startsWith("/api/auth/")
|| path.startsWith("/api/sms/");
}

@Override
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/com/flyway/security/jwt/JwtWebAuthFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ public class JwtWebAuthFilter extends OncePerRequestFilter {

@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
return resolvePath(request).startsWith(ADMIN_PREFIX);
String path = resolvePath(request);
return path.startsWith(ADMIN_PREFIX)
|| path.startsWith("/api/");
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.flyway.sender.controller;

import com.flyway.sender.service.SmsVerificationService;
import com.flyway.template.common.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/sms")
@RequiredArgsConstructor
public class SmsVerificationApiController {

private final SmsVerificationService service;

@PostMapping("/send")
public ResponseEntity<ApiResponse<Void>> send(@RequestParam String phoneNumber) {
try {
service.sendCode(phoneNumber);
return ResponseEntity.ok(ApiResponse.success(null, "인증번호가 발송되었습니다."));
} catch (Exception e) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("FAIL", e.getMessage()));
}
}
Comment on lines +17 to +25

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

입력값 검증 및 예외 처리 개선 필요

  1. phoneNumber에 대한 유효성 검증이 없습니다. null, 빈 문자열, 또는 잘못된 형식의 전화번호가 서비스 레이어까지 전달될 수 있습니다.
  2. Exception을 포괄적으로 catch하면 예상치 못한 오류(예: NullPointerException, DB 연결 오류)가 일반적인 "FAIL" 응답으로 처리되어 디버깅이 어려워집니다.
🛡️ 수정 제안
 `@PostMapping`("/send")
-public ResponseEntity<ApiResponse<Void>> send(`@RequestParam` String phoneNumber) {
+public ResponseEntity<ApiResponse<Void>> send(`@RequestParam` String phoneNumber) {
+    if (phoneNumber == null || phoneNumber.isBlank()) {
+        return ResponseEntity.badRequest()
+                .body(ApiResponse.error("INVALID_INPUT", "전화번호를 입력해주세요."));
+    }
     try {
         service.sendCode(phoneNumber);
         return ResponseEntity.ok(ApiResponse.success(null, "인증번호가 발송되었습니다."));
-    } catch (Exception e) {
+    } catch (IllegalArgumentException e) {
         return ResponseEntity.badRequest()
                 .body(ApiResponse.error("FAIL", e.getMessage()));
     }
 }
🤖 Prompt for AI Agents
In `@src/main/java/com/flyway/sender/controller/SmsVerificationApiController.java`
around lines 17 - 25, SmsVerificationApiController.send currently accepts any
phoneNumber and catches Exception broadly; add input validation for phoneNumber
(null/blank and regex format check) at the start of send and throw or return a
meaningful bad-request ApiResponse when invalid, referencing the method send and
ApiResponse; replace the single catch(Exception e) with specific handling: catch
validation/format errors (e.g., IllegalArgumentException or a custom
ValidationException) to return ResponseEntity.badRequest() with a clear error
code/message, and keep a separate generic catch(Exception e) that logs the full
exception and returns ResponseEntity.status(500) with
ApiResponse.error("SERVER_ERROR", "Internal server error") so unexpected errors
aren't masked; ensure service.sendCode(phoneNumber) is only called after
validation.


@PostMapping("/verify")
public ResponseEntity<ApiResponse<Boolean>> verify(
@RequestParam String phoneNumber,
@RequestParam String code) {
boolean ok = service.verify(phoneNumber, code);
String msg = ok ? "인증되었습니다." : "인증번호가 일치하지 않습니다.";
return ResponseEntity.ok(ApiResponse.success(ok, msg));
}
}
19 changes: 19 additions & 0 deletions src/main/java/com/flyway/sender/domain/SmsVerification.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.flyway.sender.domain;

import lombok.*;
import java.time.LocalDateTime;

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SmsVerification {

private String smsVerificationId;
private String phoneNumber;
private String code;
private LocalDateTime expiresAt;
private LocalDateTime verifiedAt;
private LocalDateTime createdAt;

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ public class PassengerTicketInfo {
private String passengerId;
private String firstName;
private String lastName;
private String phoneNumber;

// 구간 정보
private String reservationSegmentId;
Expand Down
23 changes: 23 additions & 0 deletions src/main/java/com/flyway/sender/mapper/SmsVerificationMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.flyway.sender.mapper;

import com.flyway.sender.domain.SmsVerification;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.time.LocalDateTime;

@Mapper
public interface SmsVerificationMapper {
void insert(SmsVerification verification);

SmsVerification findLatestByPhone(@Param("phoneNumber") String phoneNumber);

int markVerified(@Param("smsVerificationId") String id,
@Param("verifiedAt") LocalDateTime verifiedAt);

int countRecentByPhone(@Param("phoneNumber") String phoneNumber,
@Param("since") LocalDateTime since);

// 인증 완료 여부 확인 (5분 내)
int existsVerifiedPhone(@Param("phoneNumber") String phoneNumber,
@Param("since") LocalDateTime since);
Comment on lines +20 to +22

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

주석과 실제 사용이 불일치

주석에는 "5분 내"라고 되어 있지만, SmsVerificationService.isVerified()에서는 10분을 사용합니다. 주석을 정확하게 수정하거나, 시간 제한이 동적이므로 주석을 제거하세요.

📝 수정 제안
-    // 인증 완료 여부 확인 (5분 내)
+    // 인증 완료 여부 확인 (since 시간 이후)
     int existsVerifiedPhone(`@Param`("phoneNumber") String phoneNumber,
                             `@Param`("since") LocalDateTime since);
🤖 Prompt for AI Agents
In `@src/main/java/com/flyway/sender/mapper/SmsVerificationMapper.java` around
lines 20 - 22, The comment for SmsVerificationMapper.existsVerifiedPhone
incorrectly states "5분 내" while SmsVerificationService.isVerified uses a
10-minute window; update the comment to match the actual behavior or remove the
hardcoded time note since the time limit is handled dynamically in
SmsVerificationService.isVerified, and ensure both
SmsVerificationMapper.existsVerifiedPhone and SmsVerificationService.isVerified
remain consistent about which timeframe is enforced or documented.

}
22 changes: 15 additions & 7 deletions src/main/java/com/flyway/sender/service/SmsService.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,21 @@ public void sendRefundComplete(String to, String reservationId, Long amount) {
sendSms(to, content);
}

// 탑승객에게 티켓 정보 SMS 발송 (reservationId만 받음)
// 탑승객 티켓 정보를 예약자(회원) 번호로 SMS 발송
public void sendTicketInfoToPassengers(String reservationId) {

// 항공편 정보 조회
// 1. 예약자(회원)의 전화번호 조회
String userPhone = smsMapper.selectPhoneByReservationId(reservationId);
if (userPhone == null || userPhone.isEmpty()) {
log.info("[SMS] 티켓 발송 실패 - 회원 전화번호 없음, reservationId: {}", reservationId);
return;
}

// 2. 항공편 정보 조회
List<ReservationSegmentView> segments =
reservationBookingRepository.findSegments(reservationId);

// 탑승객별 티켓 정보 조회 (좌석/기내식/수하물 포함)
// 3. 탑승객별 티켓 정보 조회 (좌석/기내식/수하물 포함)
List<PassengerTicketInfo> ticketInfoList =
smsMapper.selectPassengerTicketInfo(reservationId);

Expand All @@ -95,19 +102,20 @@ public void sendTicketInfoToPassengers(String reservationId) {
return;
}

// passengerId별로 그룹핑
// 4. passengerId별로 그룹핑
Map<String, List<PassengerTicketInfo>> passengerMap = ticketInfoList.stream()
.collect(Collectors.groupingBy(PassengerTicketInfo::getPassengerId));

// 5. 각 탑승객 정보를 회원 번호로 발송
for (Map.Entry<String, List<PassengerTicketInfo>> entry : passengerMap.entrySet()) {
List<PassengerTicketInfo> paxInfoList = entry.getValue();
PassengerTicketInfo firstInfo = paxInfoList.get(0);

try {
String content = buildTicketSmsContent(firstInfo, segments, paxInfoList);
sendSms(firstInfo.getPhoneNumber(), content);
log.info("[SMS] 티켓 발송 - passenger: {} {}, phone: {}",
firstInfo.getFirstName(), firstInfo.getLastName(), firstInfo.getPhoneNumber());
sendSms(userPhone, content); // 회원 번호로 발송
log.info("[SMS] 티켓 발송 - passenger: {} {}, to userPhone: {}",
firstInfo.getFirstName(), firstInfo.getLastName(), userPhone);
Comment on lines 114 to +118

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

전화번호 로그 마스킹 필요(PII 노출 위험)
새 로그가 사용자 전화번호를 그대로 노출합니다. 운영 로그 접근 범위를 고려하면 마스킹이 필요합니다. (가능하면 sendSms 내부 로그도 동일하게 마스킹 권장)

🔒 제안 변경안
-                log.info("[SMS] 티켓 발송 - passenger: {} {}, to userPhone: {}",
-                        firstInfo.getFirstName(), firstInfo.getLastName(), userPhone);
+                log.info("[SMS] 티켓 발송 - passenger: {} {}, to userPhone: {}",
+                        firstInfo.getFirstName(), firstInfo.getLastName(), maskPhone(userPhone));
+    private String maskPhone(String phone) {
+        if (phone == null) return null;
+        int len = phone.length();
+        if (len <= 4) return "****";
+        return phone.substring(0, Math.min(3, len)) + "****" + phone.substring(len - 4);
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
String content = buildTicketSmsContent(firstInfo, segments, paxInfoList);
sendSms(firstInfo.getPhoneNumber(), content);
log.info("[SMS] 티켓 발송 - passenger: {} {}, phone: {}",
firstInfo.getFirstName(), firstInfo.getLastName(), firstInfo.getPhoneNumber());
sendSms(userPhone, content); // 회원 번호로 발송
log.info("[SMS] 티켓 발송 - passenger: {} {}, to userPhone: {}",
firstInfo.getFirstName(), firstInfo.getLastName(), userPhone);
try {
String content = buildTicketSmsContent(firstInfo, segments, paxInfoList);
sendSms(userPhone, content); // 회원 번호로 발송
log.info("[SMS] 티켓 발송 - passenger: {} {}, to userPhone: {}",
firstInfo.getFirstName(), firstInfo.getLastName(), maskPhone(userPhone));
}
private String maskPhone(String phone) {
if (phone == null) return null;
int len = phone.length();
if (len <= 4) return "****";
return phone.substring(0, Math.min(3, len)) + "****" + phone.substring(len - 4);
}
🤖 Prompt for AI Agents
In `@src/main/java/com/flyway/sender/service/SmsService.java` around lines 114 -
118, The log at the end of the ticket-sending block currently prints userPhone
in cleartext; change it to log a masked version of the phone number to avoid PII
exposure by adding/using a phone-masking helper (e.g., maskPhoneNumber) or
inline masking logic and call that when logging; update the log.info line in
SmsService (the block that calls buildTicketSmsContent(...) and sendSms(...)) to
pass the masked phone value instead of userPhone, and also ensure sendSms and
any internal SMS-related logs use the same mask helper for consistency.

} catch (Exception e) {
log.error("[SMS] 티켓 발송 실패 - passengerId: {}, error: {}",
firstInfo.getPassengerId(), e.getMessage());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.flyway.sender.service;

import com.flyway.sender.domain.SmsVerification;
import com.flyway.sender.mapper.SmsVerificationMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.Random;
import java.util.UUID;

@Slf4j
@Service
@RequiredArgsConstructor
public class SmsVerificationService {

private final SmsVerificationMapper mapper;
private final SmsService smsService;

@Transactional
public void sendCode(String phoneNumber) {
// 5분 내 3회 제한
int count = mapper.countRecentByPhone(phoneNumber, LocalDateTime.now().minusMinutes(5));
if (count >= 3) {
throw new RuntimeException("잠시 후 다시 시도해주세요.");
}

String code = String.format("%06d", new Random().nextInt(1000000));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

java.util.Random 대신 SecureRandom 사용 필요

인증 코드 생성에 java.util.Random을 사용하고 있습니다. 이는 예측 가능한 시드를 사용하므로 공격자가 다음 인증 코드를 예측할 수 있습니다. 보안에 민감한 코드 생성에는 SecureRandom을 사용해야 합니다.

🔒 수정 제안
 import java.util.Random;
+import java.security.SecureRandom;
 import java.util.UUID;

 `@Slf4j`
 `@Service`
 `@RequiredArgsConstructor`
 public class SmsVerificationService {

     private final SmsVerificationMapper mapper;
     private final SmsService smsService;
+    private final SecureRandom secureRandom = new SecureRandom();

     `@Transactional`
     public void sendCode(String phoneNumber) {
         // 5분 내 3회 제한
         int count = mapper.countRecentByPhone(phoneNumber, LocalDateTime.now().minusMinutes(5));
         if (count >= 3) {
             throw new RuntimeException("잠시 후 다시 시도해주세요.");
         }

-        String code = String.format("%06d", new Random().nextInt(1000000));
+        String code = String.format("%06d", secureRandom.nextInt(1000000));
🤖 Prompt for AI Agents
In `@src/main/java/com/flyway/sender/service/SmsVerificationService.java` at line
30, The code in SmsVerificationService generates the OTP using java.util.Random
(String code = String.format("%06d", new Random().nextInt(1000000))); which is
predictable; replace it with java.security.SecureRandom (e.g., a static final
SecureRandom instance used to call nextInt(1_000_000)) and update the import;
ensure the replacement occurs where the code variable is constructed so the
formatted 6-digit string is produced from SecureRandom.nextInt(1_000_000).


SmsVerification v = SmsVerification.builder()
.smsVerificationId(UUID.randomUUID().toString())
.phoneNumber(phoneNumber)
.code(code)
.expiresAt(LocalDateTime.now().plusMinutes(3))
.build();
mapper.insert(v);

smsService.sendSms(phoneNumber, "[Flyway] 인증번호 [" + code + "]");
log.info("[SMS] 인증코드 발송 - {}", phoneNumber.substring(0, 3) + "****");
}

@Transactional
public boolean verify(String phoneNumber, String code) {
SmsVerification v = mapper.findLatestByPhone(phoneNumber);
if (v == null) return false;
if (v.getExpiresAt().isBefore(LocalDateTime.now())) return false;
if (!v.getCode().equals(code)) return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

타이밍 공격 방지를 위한 상수 시간 비교 필요

String.equals()는 첫 번째 불일치 문자에서 즉시 반환하므로 응답 시간 차이를 통해 인증 코드를 유추할 수 있는 타이밍 공격에 취약합니다. MessageDigest.isEqual() 또는 상수 시간 비교를 사용하세요.

🔒 수정 제안
+import java.security.MessageDigest;
+import java.nio.charset.StandardCharsets;

     `@Transactional`
     public boolean verify(String phoneNumber, String code) {
         SmsVerification v = mapper.findLatestByPhone(phoneNumber);
         if (v == null) return false;
         if (v.getExpiresAt().isBefore(LocalDateTime.now())) return false;
-        if (!v.getCode().equals(code)) return false;
+        if (!constantTimeEquals(v.getCode(), code)) return false;

         mapper.markVerified(v.getSmsVerificationId(), LocalDateTime.now());
         return true;
     }

+    private boolean constantTimeEquals(String a, String b) {
+        if (a == null || b == null) return false;
+        return MessageDigest.isEqual(
+            a.getBytes(StandardCharsets.UTF_8),
+            b.getBytes(StandardCharsets.UTF_8)
+        );
+    }
🤖 Prompt for AI Agents
In `@src/main/java/com/flyway/sender/service/SmsVerificationService.java` at line
49, The line using v.getCode().equals(code) in SmsVerificationService should be
replaced with a constant-time comparison to prevent timing attacks: convert both
strings to byte[] with a fixed charset (e.g., UTF-8), guard against nulls, and
use MessageDigest.isEqual(byte[], byte[]) (or another constant-time utility) to
compare the stored code (v.getCode()) and the supplied code parameter, returning
the result of that comparison instead of String.equals().


mapper.markVerified(v.getSmsVerificationId(), LocalDateTime.now());
return true;
}

// 회원가입 시 인증 완료 여부 확인 (10분 내 인증 완료된 번호인지)
public boolean isVerified(String phoneNumber) {
return mapper.existsVerifiedPhone(phoneNumber, LocalDateTime.now().minusMinutes(10)) > 0;
}
}
1 change: 1 addition & 0 deletions src/main/java/com/flyway/template/exception/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public enum ErrorCode {
USER_PASSWORD_ENCODE_ERROR(500, "U006", "비밀번호 처리 중 오류가 발생했습니다."),
USER_DB_ERROR(500, "U007", "회원 정보 저장 중 오류가 발생했습니다."),
USER_INVALID_SIGN_UP_ATTEMPT(400, "U008", "유효하지 않은 회원가입 요청입니다."),
USER_PHONE_NOT_VERIFIED(400, "U012", "전화번호 인증이 필요합니다."),
USER_ALREADY_WITHDRAWN(409, "U009", "이미 탈퇴 처리된 사용자입니다."),
USER_NOT_WITHDRAWN(409, "U010", "탈퇴 처리된 사용자만 익명화할 수 있습니다."),
USER_NOT_FOUND(404, "U011", "사용자를 찾을 수 없습니다."),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
CREATE TABLE sms_verification (
sms_verification_id CHAR(36) NOT NULL,
phone_number VARCHAR(20) NOT NULL,
code VARCHAR(6) NOT NULL,
expires_at DATETIME NOT NULL,
verified_at DATETIME NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (sms_verification_id),
INDEX idx_sms_phone (phone_number, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
4 changes: 1 addition & 3 deletions src/main/resources/mapper/sender/SmsMapper.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@
AND phone_number != ''
</select>
<!-- 탑승객별 티켓 정보 (좌석/기내식/수하물 ) -->
<!-- 탑승객별 티켓 정보 (좌석/기내식/수하물 ) -->
<select id="selectPassengerTicketInfo"
resultType="com.flyway.sender.dto.PassengerTicketInfo">
SELECT
p.passenger_id AS passengerId,
p.first_name AS firstName,
p.last_name AS lastName,
p.phone_number AS phoneNumber,
rs.reservation_segment_id AS reservationSegmentId,
rs.segment_order AS segmentOrder,
acs.seat_no AS seatNo,
Expand All @@ -60,8 +60,6 @@
LEFT JOIN meal_option mo
ON mo.meal_id = ps.meal_id
WHERE p.reservation_id = #{reservationId}
AND p.phone_number IS NOT NULL
AND p.phone_number != ''
ORDER BY p.passenger_id, rs.segment_order, ps.added_at
</select>

Expand Down
39 changes: 39 additions & 0 deletions src/main/resources/mapper/sender/SmsVerificationMapper.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.flyway.sender.mapper.SmsVerificationMapper">

<insert id="insert">
INSERT INTO sms_verification
(sms_verification_id, phone_number, code, expires_at, created_at)
VALUES (#{smsVerificationId}, #{phoneNumber}, #{code}, #{expiresAt}, NOW())
</insert>

<select id="findLatestByPhone" resultType="com.flyway.sender.domain.SmsVerification">
SELECT sms_verification_id AS smsVerificationId,
phone_number AS phoneNumber, code,
expires_at AS expiresAt, verified_at AS verifiedAt
FROM sms_verification
WHERE phone_number = #{phoneNumber} AND verified_at IS NULL
ORDER BY created_at DESC LIMIT 1
</select>

<update id="markVerified">
UPDATE sms_verification SET verified_at = #{verifiedAt}
WHERE sms_verification_id = #{smsVerificationId} AND verified_at IS NULL
</update>

<select id="countRecentByPhone" resultType="int">
SELECT COUNT(*) FROM sms_verification
WHERE phone_number = #{phoneNumber} AND created_at >= #{since}
</select>

<select id="existsVerifiedPhone" resultType="int">
SELECT COUNT(*) FROM sms_verification
WHERE phone_number = #{phoneNumber}
AND verified_at IS NOT NULL
AND verified_at >= #{since}
</select>

</mapper>
Loading