- 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>
90 lines
2.4 KiB
Rust
90 lines
2.4 KiB
Rust
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 {} |