feat: 회원가입 API 구현 (#56)
All checks were successful
SPMS_API/pipeline/head This commit looks good

Reviewed-on: https://git.ipstein.myds.me/SPMS/SPMS_API/pulls/57
This commit is contained in:
김선규 2026-02-10 01:06:59 +00:00
commit 94b0787bf8
6 changed files with 94 additions and 0 deletions

View File

@ -19,6 +19,20 @@ public class AuthController : ControllerBase
_authService = authService;
}
[HttpPost("signup")]
[AllowAnonymous]
[SwaggerOperation(
Summary = "회원가입",
Description = "새로운 관리자 계정을 생성합니다. 이메일 인증이 필요합니다.")]
[SwaggerResponse(200, "회원가입 성공", typeof(ApiResponse<SignupResponseDto>))]
[SwaggerResponse(400, "잘못된 요청")]
[SwaggerResponse(409, "이미 사용 중인 이메일")]
public async Task<IActionResult> SignupAsync([FromBody] SignupRequestDto request)
{
var result = await _authService.SignupAsync(request);
return Ok(ApiResponse<SignupResponseDto>.Success(result, "회원가입 완료. 이메일 인증이 필요합니다."));
}
[HttpPost("login")]
[AllowAnonymous]
[SwaggerOperation(

View File

@ -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;
}

View File

@ -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;
}

View File

@ -4,6 +4,7 @@ namespace SPMS.Application.Interfaces;
public interface IAuthService
{
Task<SignupResponseDto> SignupAsync(SignupRequestDto request);
Task<LoginResponseDto> LoginAsync(LoginRequestDto request);
Task<TokenRefreshResponseDto> RefreshTokenAsync(TokenRefreshRequestDto request);
Task LogoutAsync(long adminId);

View File

@ -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<SignupResponseDto> 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<LoginResponseDto> LoginAsync(LoginRequestDto request)
{
// 1. 이메일로 관리자 조회

View File

@ -28,6 +28,9 @@ public class ApiResponse<T> : ApiResponse
public static ApiResponse<T> Success(T data)
=> new() { Result = true, Code = ErrorCodes.Success, Data = data };
public static ApiResponse<T> Success(T data, string msg)
=> new() { Result = true, Code = ErrorCodes.Success, Data = data, Msg = msg };
public new static ApiResponse<T> Fail(string code, string msg)
=> new() { Result = false, Code = code, Msg = msg };
}