Last active
January 23, 2025 14:25
-
-
Save arashatt/36a91e3f50791b0cc0a7bbd8c5226498 to your computer and use it in GitHub Desktop.
generated my gemini
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use axum::{ | |
| extract::{Extension, Json, State}, | |
| http::StatusCode, | |
| middleware::{self, Next}, | |
| response::{IntoResponse, Response}, | |
| routing::{get, post}, | |
| Router, | |
| }; | |
| use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation}; | |
| use serde::{Deserialize, Serialize}; | |
| use std::{net::SocketAddr, sync::Arc}; | |
| use tokio::sync::Mutex; | |
| #[derive(Debug, Serialize, Deserialize)] | |
| struct Claims { | |
| sub: String, // Subject (user ID) | |
| exp: usize, // Expiration time (unix timestamp) | |
| } | |
| #[derive(Debug, Serialize, Deserialize)] | |
| struct User { | |
| id: String, | |
| username: String, | |
| } | |
| #[derive(Debug, Serialize, Deserialize)] | |
| struct Credentials { | |
| username: String, | |
| password: String, | |
| } | |
| struct AppState { | |
| jwt_secret: String, | |
| users: Arc<Mutex<Vec<User>>>, | |
| } | |
| async fn login( | |
| State(state): State<Arc<AppState>>, | |
| Json(credentials): Json<Credentials>, | |
| ) -> Result<Json<String>, (StatusCode, String)> { | |
| let users = state.users.lock().await; | |
| let user = users.iter().find(|u| u.username == credentials.username && u.password == "password"); // In real app, hash and salt passwords! | |
| match user { | |
| Some(user) => { | |
| let expiration = chrono::Utc::now() | |
| .checked_add_signed(chrono::Duration::hours(1)) | |
| .expect("valid timestamp") | |
| .timestamp() as usize; | |
| let claims = Claims { | |
| sub: user.id.clone(), | |
| exp: expiration, | |
| }; | |
| let token = encode( | |
| &Header::default(), | |
| &claims, | |
| &EncodingKey::from_secret(state.jwt_secret.as_bytes()), | |
| ) | |
| .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Failed to generate token".to_string()))?; | |
| Ok(Json(token)) | |
| } | |
| None => Err((StatusCode::UNAUTHORIZED, "Invalid credentials".to_string())), | |
| } | |
| } | |
| async fn protected(Extension(user_id): Extension<String>) -> impl IntoResponse { | |
| (StatusCode::OK, format!("Welcome, user ID: {}", user_id)) | |
| } | |
| async fn auth_middleware<B>( | |
| State(state): State<Arc<AppState>>, | |
| mut req: axum::http::Request<B>, | |
| next: Next<B>, | |
| ) -> Result<Response, (StatusCode, String)> { | |
| let auth_header = req.headers().get("Authorization"); | |
| if let Some(auth_header) = auth_header { | |
| if let Ok(auth_str) = auth_header.to_str() { | |
| if auth_str.starts_with("Bearer ") { | |
| let token = auth_str[7..].trim(); | |
| let validation = Validation::new(Algorithm::HS256); | |
| let decoded = decode::<Claims>( | |
| token, | |
| &DecodingKey::from_secret(state.jwt_secret.as_bytes()), | |
| &validation, | |
| ) | |
| .map_err(|_| (StatusCode::UNAUTHORIZED, "Invalid token".to_string()))?; | |
| req.extensions_mut().insert(decoded.claims.sub); | |
| return Ok(next.run(req).await); | |
| } | |
| } | |
| } | |
| Err((StatusCode::UNAUTHORIZED, "Missing or invalid token".to_string())) | |
| } | |
| #[tokio::main] | |
| async fn main() { | |
| let jwt_secret = "your_secret_key".to_string(); // In real app, load this from an environment variable! | |
| let users = Arc::new(Mutex::new(vec![ | |
| User { | |
| id: "1".to_string(), | |
| username: "test".to_string(), | |
| }, | |
| ])); | |
| let shared_state = Arc::new(AppState { jwt_secret, users }); | |
| let app = Router::new() | |
| .route("/login", post(login)) | |
| .route("/protected", get(protected).route_layer(middleware::from_fn_with_state(shared_state.clone(), auth_middleware))) | |
| .with_state(shared_state); | |
| let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); | |
| println!("listening on {}", addr); | |
| axum::Server::bind(&addr) | |
| .serve(app.into_make_service()) | |
| .await | |
| .unwrap(); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use axum::{ | |
| extract::{Path, State}, | |
| http::StatusCode, | |
| response::IntoResponse, | |
| routing::{get, post}, | |
| Json, Router, | |
| }; | |
| use serde::{Deserialize, Serialize}; | |
| use sqlx::{mysql::MySqlPoolOptions, MySqlPool, Row}; | |
| use std::net::SocketAddr; | |
| #[derive(Debug, Serialize, Deserialize, Clone)] | |
| struct Todo { | |
| id: u32, // MySQL uses unsigned integers more often | |
| title: String, | |
| completed: bool, | |
| } | |
| struct AppState { | |
| pool: MySqlPool, | |
| } | |
| async fn create_todo( | |
| State(state): State<AppState>, | |
| Json(payload): Json<Todo>, | |
| ) -> Result<(StatusCode, Json<Todo>), (StatusCode, String)> { | |
| let result = sqlx::query!( | |
| "INSERT INTO todos (title, completed) VALUES (?, ?) RETURNING id", | |
| payload.title, | |
| payload.completed | |
| ) | |
| .fetch_one(&state.pool) | |
| .await; | |
| match result { | |
| Ok(row) => { | |
| let id: u32 = row.id; // Correctly extract as u32 | |
| let new_todo = Todo { | |
| id, | |
| title: payload.title, | |
| completed: payload.completed, | |
| }; | |
| Ok((StatusCode::CREATED, Json(new_todo))) | |
| } | |
| Err(e) => Err((StatusCode::INTERNAL_SERVER_ERROR, e.to_string())), | |
| } | |
| } | |
| async fn get_todos(State(state): State<AppState>) -> Result<Json<Vec<Todo>>, (StatusCode, String)> { | |
| let result = sqlx::query("SELECT id, title, completed FROM todos") | |
| .fetch_all(&state.pool) | |
| .await; | |
| match result { | |
| Ok(rows) => { | |
| let todos: Vec<Todo> = rows | |
| .iter() | |
| .map(|row| Todo { | |
| id: row.get("id"), | |
| title: row.get("title"), | |
| completed: row.get("completed"), | |
| }) | |
| .collect(); | |
| Ok(Json(todos)) | |
| } | |
| Err(e) => Err((StatusCode::INTERNAL_SERVER_ERROR, e.to_string())), | |
| } | |
| } | |
| async fn get_todo( | |
| Path(id): Path<u32>, // Path parameter is now u32 | |
| State(state): State<AppState>, | |
| ) -> Result<Json<Todo>, (StatusCode, String)> { | |
| let result = sqlx::query("SELECT id, title, completed FROM todos WHERE id = ?") | |
| .bind(id) | |
| .fetch_one(&state.pool) | |
| .await; | |
| match result { | |
| Ok(row) => Ok(Json(Todo { | |
| id: row.get("id"), | |
| title: row.get("title"), | |
| completed: row.get("completed"), | |
| })), | |
| Err(sqlx::Error::RowNotFound) => Err((StatusCode::NOT_FOUND, "Todo not found".to_string())), | |
| Err(e) => Err((StatusCode::INTERNAL_SERVER_ERROR, e.to_string())), | |
| } | |
| } | |
| #[tokio::main] | |
| async fn main() -> Result<(), sqlx::Error> { | |
| // Database URL (replace with your credentials) | |
| let database_url = "mysql://user:password@localhost:3306/your_database_name"; | |
| // initialize database pool | |
| let pool = MySqlPoolOptions::new() | |
| .max_connections(5) | |
| .connect(database_url) | |
| .await?; | |
| // Create table if it doesn't exist (important!) | |
| sqlx::query( | |
| "CREATE TABLE IF NOT EXISTS todos ( | |
| id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, | |
| title VARCHAR(255) NOT NULL, | |
| completed BOOLEAN NOT NULL DEFAULT FALSE | |
| )", | |
| ) | |
| .execute(&pool) | |
| .await?; | |
| let shared_state = AppState { pool }; | |
| // build our application with a route | |
| let app = Router::new() | |
| .route("/todos", get(get_todos).post(create_todo)) | |
| .route("/todos/:id", get(get_todo)) | |
| .with_state(shared_state); | |
| // run it | |
| let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); | |
| println!("listening on {}", addr); | |
| axum::Server::bind(&addr) | |
| .serve(app.into_make_service()) | |
| .await | |
| .unwrap(); | |
| Ok(()) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment