use crate::{ errors::AppError, extractors::{JwtClaims, ValidatedJson}, state::AppState, }; use api_types::{ requests::{LoginRequest, RefreshTokenRequest, RegisterRequest}, responses::{AuthResponse, UserResponse}, }; use application::identity::{ GetProfileQuery, LoginUserCommand, RefreshTokenCommand, RegisterUserCommand, generate_refresh_token, }; use axum::{Json, extract::State, http::StatusCode}; #[utoipa::path( post, path = "/api/v1/auth/register", request_body = RegisterRequest, responses( (status = 201, description = "User registered", body = AuthResponse), (status = 409, description = "Email already taken"), (status = 422, description = "Validation error") ) )] pub async fn register( State(state): State, ValidatedJson(req): ValidatedJson, ) -> Result<(StatusCode, Json), AppError> { let cmd = RegisterUserCommand { username: req.username, email: req.email, password: req.password, }; let user = state.identity.register.execute(cmd).await?; let token = state .token_issuer .issue(&user.id, "user") .await .map_err(AppError::from)?; let (refresh_token, _) = generate_refresh_token(&state.identity.refresh_token_repo, &user.id).await?; Ok(( StatusCode::CREATED, Json(AuthResponse { token, refresh_token, user: UserResponse::from_domain(&user), }), )) } #[utoipa::path( post, path = "/api/v1/auth/login", request_body = LoginRequest, responses( (status = 200, description = "Login successful", body = AuthResponse), (status = 401, description = "Invalid credentials") ) )] pub async fn login( State(state): State, ValidatedJson(req): ValidatedJson, ) -> Result, AppError> { let cmd = LoginUserCommand { email: req.email, password: req.password, }; let (user, token, refresh_token) = state.identity.login.execute(cmd).await?; Ok(Json(AuthResponse { token, refresh_token, user: UserResponse::from_domain(&user), })) } #[utoipa::path( get, path = "/api/v1/auth/me", security(("bearer_token" = [])), responses( (status = 200, description = "Current user profile", body = UserResponse), (status = 401, description = "Unauthorized") ) )] pub async fn me( State(state): State, claims: JwtClaims, ) -> Result, AppError> { let query = GetProfileQuery { user_id: claims.user_id, }; let user = state.identity.get_profile.execute(query).await?; Ok(Json(UserResponse::from_domain(&user))) } pub async fn refresh( State(state): State, ValidatedJson(req): ValidatedJson, ) -> Result, AppError> { let cmd = RefreshTokenCommand { refresh_token: req.refresh_token, }; let (access_token, refresh_token) = state.identity.refresh.execute(cmd).await?; let (user_id, _) = state.token_issuer.verify(&access_token).await?; let user = state .identity .get_profile .execute(GetProfileQuery { user_id }) .await?; Ok(Json(AuthResponse { token: access_token, refresh_token, user: UserResponse::from_domain(&user), })) } pub async fn logout( State(state): State, claims: JwtClaims, ) -> Result { state.identity.logout.execute(&claims.user_id).await?; Ok(StatusCode::NO_CONTENT) }