use axum::{ extract::State, http::StatusCode, Json, }; use axum_login::AuthSession; use sea_orm::{DatabaseConnection, EntityTrait, ColumnTrait, QueryFilter, ActiveModelTrait, Set}; use serde::{Deserialize, Serialize}; use utoipa::ToSchema; use crate::auth::{AuthBackend, Credentials}; use crate::models::{user, User, family, Family}; use crate::services::FamilyService; #[derive(Debug, Deserialize, ToSchema)] pub struct LoginRequest { pub username: String, pub password: String, } #[derive(Debug, Serialize, ToSchema)] pub struct LoginResponse { pub success: bool, pub is_admin: bool, } #[derive(Debug, Serialize, ToSchema)] pub struct MeResponse { pub id: i32, pub username: Option, pub email: Option, pub is_admin: bool, pub family_id: Option, } #[utoipa::path( post, path = "/login", tag = "auth", request_body = LoginRequest, responses( (status = 200, description = "Login successful", body = LoginResponse), (status = 401, description = "Invalid credentials") ) )] pub async fn login( mut auth_session: AuthSession, Json(payload): Json, ) -> Result, StatusCode> { let user = auth_session .authenticate(Credentials { username: payload.username, password: payload.password, }) .await .map_err(|_| StatusCode::UNAUTHORIZED)? .ok_or(StatusCode::UNAUTHORIZED)?; auth_session .login(&user) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; Ok(Json(LoginResponse { success: true, is_admin: user.is_admin, })) } #[utoipa::path( post, path = "/logout", tag = "auth", responses( (status = 200, description = "Logout successful") ) )] pub async fn logout( mut auth_session: AuthSession, ) -> Result { auth_session .logout() .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; Ok(StatusCode::OK) } #[utoipa::path( get, path = "/me", tag = "auth", responses( (status = 200, description = "Current user info", body = MeResponse), (status = 401, description = "Not authenticated") ) )] pub async fn me( auth_session: AuthSession, ) -> Result, StatusCode> { let user = auth_session.user.ok_or(StatusCode::UNAUTHORIZED)?; Ok(Json(MeResponse { id: user.id, username: user.username, email: user.email, is_admin: user.is_admin, family_id: user.family_id, })) } #[derive(Debug, Deserialize, ToSchema)] pub struct FamilyLoginRequest { pub family_name: String, pub password: String, } #[derive(Debug, Serialize, ToSchema)] pub struct FamilyLoginResponse { pub success: bool, pub family_id: i32, } #[utoipa::path( post, path = "/auth/family-login", tag = "auth", request_body = FamilyLoginRequest, responses( (status = 200, description = "Login successful", body = FamilyLoginResponse), (status = 401, description = "Invalid credentials"), (status = 404, description = "Family not found") ) )] pub async fn family_login( mut auth_session: AuthSession, State(db): State, Json(payload): Json, ) -> Result, StatusCode> { let family = Family::find() .filter(family::Column::Name.eq(&payload.family_name)) .one(&db) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? .ok_or(StatusCode::NOT_FOUND)?; let valid = FamilyService::verify_password(&db, family.id, payload.password.clone()) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; if !valid { return Err(StatusCode::UNAUTHORIZED); } let existing_member = User::find() .filter(user::Column::FamilyId.eq(family.id)) .filter(user::Column::GoogleId.is_null()) .filter(user::Column::Username.eq(&payload.family_name)) .one(&db) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let member_user = if let Some(user) = existing_member { user } else { let new_member = user::ActiveModel { username: Set(Some(payload.family_name)), email: Set(None), google_id: Set(None), password_hash: Set(None), is_admin: Set(false), family_id: Set(Some(family.id)), ..Default::default() }; new_member.insert(&db) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? }; auth_session .login(&member_user) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; Ok(Json(FamilyLoginResponse { success: true, family_id: family.id, })) }