feat: migrate AuthController to FastEndpoints Endpoints (Phase 1)
TaxBaik CI/CD / build-and-deploy (push) Successful in 1m31s

IMPLEMENTATION:
- Create 4 FastEndpoints Endpoint classes:
  - LoginEndpoint: POST /api/auth/login
  - RefreshTokenEndpoint: POST /api/auth/refresh
  - ChangePasswordEndpoint: POST /api/auth/change-password
  - ResetPasswordEndpoint: POST /api/auth/reset-password

- Backup AuthController.cs (no longer active)
- Add FastEndpoints.Endpoint<TRequest, TResponse> pattern
- Implement proper DI with AuthService injection
- Use Policies("Bearer") for authorization
- Proper error handling with ThrowError()

ARCHITECTURE:
- Start of Phase 1: Core Auth APIs
- Endpoints follow FastEndpoints conventions
- DTOs: LoginRequest, RefreshTokenRequest, ChangePasswordRequest, ResetPasswordRequest, TokenPairResponse, MessageResponse
- AllowAnonymous for login/refresh/reset
- Bearer policy for change-password

VERIFICATION:
 dotnet build: 0 errors, 0 warnings
 dotnet test: 26/26 passed
 FastEndpoints auto-discovery working (no endpoint errors)
 JWT validation passes

Next Phase: BlogController and remaining APIs

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-07-03 17:14:35 +09:00
parent 055bc48d1d
commit 675ef64975
102 changed files with 4293 additions and 2 deletions
@@ -0,0 +1,56 @@
using FastEndpoints;
using TaxBaik.Web.Services;
namespace TaxBaik.Web.Endpoints.Auth;
public class LoginRequest
{
public string Username { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
}
public class TokenPairResponse
{
public string Token { get; set; } = string.Empty;
public string AccessToken { get; set; } = string.Empty;
public string RefreshToken { get; set; } = string.Empty;
public int ExpiresIn { get; set; }
}
public class LoginEndpoint : Endpoint<LoginRequest, TokenPairResponse>
{
private readonly AuthService _authService;
public LoginEndpoint(AuthService authService)
{
_authService = authService;
}
public override void Configure()
{
Post("/api/auth/login");
AllowAnonymous();
}
public override async Task HandleAsync(LoginRequest request, CancellationToken ct)
{
if (string.IsNullOrWhiteSpace(request.Username) || string.IsNullOrWhiteSpace(request.Password))
{
ThrowError("로그인 정보가 필요합니다.");
}
var tokenPair = await _authService.AuthenticateAndGenerateTokenPairAsync(request.Username, request.Password);
if (tokenPair == null)
{
ThrowError("아이디 또는 비밀번호가 올바르지 않습니다.");
}
await SendAsync(new TokenPairResponse
{
Token = tokenPair!.AccessToken,
AccessToken = tokenPair.AccessToken,
RefreshToken = tokenPair.RefreshToken,
ExpiresIn = tokenPair.ExpiresIn
}, 200, cancellation: ct);
}
}