From 16550dbff3fdd43f2ac3606a453fb8df013a8dbf Mon Sep 17 00:00:00 2001 From: SEAN Date: Tue, 10 Feb 2026 10:04:58 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20?= =?UTF-8?q?API=20=EA=B5=AC=ED=98=84=20(#56)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - POST /v1/in/auth/signup 엔드포인트 추가 (AllowAnonymous) - SignupRequestDto/SignupResponseDto 생성 - AuthService.SignupAsync 구현 (이메일 중복검사, AdminCode 생성, BCrypt 해싱) - ApiResponse.Success(data, msg) 오버로드 추가 --- SPMS.API/Controllers/AuthController.cs | 14 +++++++ .../DTOs/Auth/SignupRequestDto.cs | 22 ++++++++++ .../DTOs/Auth/SignupResponseDto.cs | 12 ++++++ SPMS.Application/Interfaces/IAuthService.cs | 1 + SPMS.Application/Services/AuthService.cs | 42 +++++++++++++++++++ SPMS.Domain/Common/ApiResponse.cs | 3 ++ 6 files changed, 94 insertions(+) create mode 100644 SPMS.Application/DTOs/Auth/SignupRequestDto.cs create mode 100644 SPMS.Application/DTOs/Auth/SignupResponseDto.cs diff --git a/SPMS.API/Controllers/AuthController.cs b/SPMS.API/Controllers/AuthController.cs index d148255..314c0ea 100644 --- a/SPMS.API/Controllers/AuthController.cs +++ b/SPMS.API/Controllers/AuthController.cs @@ -19,6 +19,20 @@ public class AuthController : ControllerBase _authService = authService; } + [HttpPost("signup")] + [AllowAnonymous] + [SwaggerOperation( + Summary = "회원가입", + Description = "새로운 관리자 계정을 생성합니다. 이메일 인증이 필요합니다.")] + [SwaggerResponse(200, "회원가입 성공", typeof(ApiResponse))] + [SwaggerResponse(400, "잘못된 요청")] + [SwaggerResponse(409, "이미 사용 중인 이메일")] + public async Task SignupAsync([FromBody] SignupRequestDto request) + { + var result = await _authService.SignupAsync(request); + return Ok(ApiResponse.Success(result, "회원가입 완료. 이메일 인증이 필요합니다.")); + } + [HttpPost("login")] [AllowAnonymous] [SwaggerOperation( diff --git a/SPMS.Application/DTOs/Auth/SignupRequestDto.cs b/SPMS.Application/DTOs/Auth/SignupRequestDto.cs new file mode 100644 index 0000000..0e9494b --- /dev/null +++ b/SPMS.Application/DTOs/Auth/SignupRequestDto.cs @@ -0,0 +1,22 @@ +using System.ComponentModel.DataAnnotations; + +namespace SPMS.Application.DTOs.Auth; + +public class SignupRequestDto +{ + [Required(ErrorMessage = "이메일은 필수입니다.")] + [EmailAddress(ErrorMessage = "올바른 이메일 형식이 아닙니다.")] + public string Email { get; set; } = string.Empty; + + [Required(ErrorMessage = "비밀번호는 필수입니다.")] + [MinLength(8, ErrorMessage = "비밀번호는 8자 이상이어야 합니다.")] + public string Password { get; set; } = string.Empty; + + [Required(ErrorMessage = "이름은 필수입니다.")] + [StringLength(50, ErrorMessage = "이름은 50자 이내여야 합니다.")] + public string Name { get; set; } = string.Empty; + + [Required(ErrorMessage = "전화번호는 필수입니다.")] + [StringLength(20, ErrorMessage = "전화번호는 20자 이내여야 합니다.")] + public string Phone { get; set; } = string.Empty; +} diff --git a/SPMS.Application/DTOs/Auth/SignupResponseDto.cs b/SPMS.Application/DTOs/Auth/SignupResponseDto.cs new file mode 100644 index 0000000..dade51a --- /dev/null +++ b/SPMS.Application/DTOs/Auth/SignupResponseDto.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace SPMS.Application.DTOs.Auth; + +public class SignupResponseDto +{ + [JsonPropertyName("admin_code")] + public string AdminCode { get; set; } = string.Empty; + + [JsonPropertyName("email")] + public string Email { get; set; } = string.Empty; +} diff --git a/SPMS.Application/Interfaces/IAuthService.cs b/SPMS.Application/Interfaces/IAuthService.cs index 111592f..84f7713 100644 --- a/SPMS.Application/Interfaces/IAuthService.cs +++ b/SPMS.Application/Interfaces/IAuthService.cs @@ -4,6 +4,7 @@ namespace SPMS.Application.Interfaces; public interface IAuthService { + Task SignupAsync(SignupRequestDto request); Task LoginAsync(LoginRequestDto request); Task RefreshTokenAsync(TokenRefreshRequestDto request); Task LogoutAsync(long adminId); diff --git a/SPMS.Application/Services/AuthService.cs b/SPMS.Application/Services/AuthService.cs index b8761be..e2e3ea1 100644 --- a/SPMS.Application/Services/AuthService.cs +++ b/SPMS.Application/Services/AuthService.cs @@ -3,6 +3,8 @@ using SPMS.Application.DTOs.Auth; using SPMS.Application.Interfaces; using SPMS.Application.Settings; using SPMS.Domain.Common; +using SPMS.Domain.Entities; +using SPMS.Domain.Enums; using SPMS.Domain.Exceptions; using SPMS.Domain.Interfaces; @@ -27,6 +29,46 @@ public class AuthService : IAuthService _jwtSettings = jwtSettings.Value; } + public async Task SignupAsync(SignupRequestDto request) + { + // 1. 이메일 중복 검사 + if (await _adminRepository.EmailExistsAsync(request.Email)) + { + throw new SpmsException( + ErrorCodes.Conflict, + "이미 사용 중인 이메일입니다.", + 409); + } + + // 2. AdminCode 생성 (UUID 12자) + var adminCode = Guid.NewGuid().ToString("N")[..12].ToUpper(); + + // 3. Admin 엔티티 생성 + var admin = new Admin + { + AdminCode = adminCode, + Email = request.Email, + Password = BCrypt.Net.BCrypt.HashPassword(request.Password), + Name = request.Name, + Phone = request.Phone, + Role = AdminRole.User, + EmailVerified = false, + CreatedAt = DateTime.UtcNow, + IsDeleted = false + }; + + // 4. 저장 + await _adminRepository.AddAsync(admin); + await _unitOfWork.SaveChangesAsync(); + + // 5. 응답 반환 + return new SignupResponseDto + { + AdminCode = admin.AdminCode, + Email = admin.Email + }; + } + public async Task LoginAsync(LoginRequestDto request) { // 1. 이메일로 관리자 조회 diff --git a/SPMS.Domain/Common/ApiResponse.cs b/SPMS.Domain/Common/ApiResponse.cs index 35d38c4..a6282d2 100644 --- a/SPMS.Domain/Common/ApiResponse.cs +++ b/SPMS.Domain/Common/ApiResponse.cs @@ -28,6 +28,9 @@ public class ApiResponse : ApiResponse public static ApiResponse Success(T data) => new() { Result = true, Code = ErrorCodes.Success, Data = data }; + public static ApiResponse Success(T data, string msg) + => new() { Result = true, Code = ErrorCodes.Success, Data = data, Msg = msg }; + public new static ApiResponse Fail(string code, string msg) => new() { Result = false, Code = code, Msg = msg }; }