Implement complete full-stack authentication system
- Restructure project with separate frontend/backend architecture - Create dedicated backend with Axum, SQLite, JWT authentication - Implement real API endpoints for register/login/verify - Update frontend to use HTTP requests instead of mock auth - Add bcrypt password hashing and secure token generation - Separate Cargo.toml files for frontend and backend builds - Fix Trunk compilation by isolating WASM-incompatible dependencies - Create demo user in database for easy testing - Both servers running: frontend (8081), backend (3000) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
90
backend/src/models.rs
Normal file
90
backend/src/models.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
use axum::{
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
Json,
|
||||
};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// Database models
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct User {
|
||||
pub id: String,
|
||||
pub username: String,
|
||||
pub email: String,
|
||||
pub password_hash: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
// API request/response types
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct UserInfo {
|
||||
pub id: String,
|
||||
pub username: String,
|
||||
pub email: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct RegisterRequest {
|
||||
pub username: String,
|
||||
pub email: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct LoginRequest {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct AuthResponse {
|
||||
pub token: String,
|
||||
pub user: UserInfo,
|
||||
}
|
||||
|
||||
// Error handling
|
||||
#[derive(Debug)]
|
||||
pub enum ApiError {
|
||||
Database(String),
|
||||
NotFound(String),
|
||||
Unauthorized(String),
|
||||
BadRequest(String),
|
||||
Conflict(String),
|
||||
Internal(String),
|
||||
}
|
||||
|
||||
impl IntoResponse for ApiError {
|
||||
fn into_response(self) -> Response {
|
||||
let (status, error_message) = match self {
|
||||
ApiError::Database(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg),
|
||||
ApiError::NotFound(msg) => (StatusCode::NOT_FOUND, msg),
|
||||
ApiError::Unauthorized(msg) => (StatusCode::UNAUTHORIZED, msg),
|
||||
ApiError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg),
|
||||
ApiError::Conflict(msg) => (StatusCode::CONFLICT, msg),
|
||||
ApiError::Internal(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg),
|
||||
};
|
||||
|
||||
let body = Json(serde_json::json!({
|
||||
"error": error_message,
|
||||
"status": status.as_u16()
|
||||
}));
|
||||
|
||||
(status, body).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ApiError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ApiError::Database(msg) => write!(f, "Database error: {}", msg),
|
||||
ApiError::NotFound(msg) => write!(f, "Not found: {}", msg),
|
||||
ApiError::Unauthorized(msg) => write!(f, "Unauthorized: {}", msg),
|
||||
ApiError::BadRequest(msg) => write!(f, "Bad request: {}", msg),
|
||||
ApiError::Conflict(msg) => write!(f, "Conflict: {}", msg),
|
||||
ApiError::Internal(msg) => write!(f, "Internal error: {}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ApiError {}
|
||||
Reference in New Issue
Block a user