Skip to content

Instantly share code, notes, and snippets.

@arashatt
Last active January 23, 2025 14:25
Show Gist options
  • Select an option

  • Save arashatt/36a91e3f50791b0cc0a7bbd8c5226498 to your computer and use it in GitHub Desktop.

Select an option

Save arashatt/36a91e3f50791b0cc0a7bbd8c5226498 to your computer and use it in GitHub Desktop.
generated my gemini
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();
}
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