Skip to content

Commit 78d6754

Browse files
committed
Add proof-of-concept example for Bookmark Manager
Introduces a comprehensive proof-of-concept example in examples/proof-of-concept, demonstrating RustAPI features including JWT authentication, CRUD operations, category management, search, pagination, SSE, and Swagger UI. Adds all related handlers, models, in-memory stores, and SSE broadcaster. Updates workspace members in Cargo.toml to include the new example.
1 parent ec0bcb2 commit 78d6754

File tree

12 files changed

+1496
-0
lines changed

12 files changed

+1496
-0
lines changed

Cargo.lock

Lines changed: 99 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ members = [
1111
"examples/sqlx-crud",
1212
"examples/crud-api",
1313
"examples/auth-api",
14+
"examples/proof-of-concept",
1415
]
1516

1617
[workspace.package]
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[package]
2+
name = "proof-of-concept"
3+
version = "0.1.0"
4+
edition = "2021"
5+
publish = false
6+
description = "Comprehensive POC demonstrating all RustAPI features - Bookmark Manager"
7+
8+
[dependencies]
9+
rustapi-rs = { path = "../../crates/rustapi-rs", features = ["jwt", "cors", "rate-limit"] }
10+
tokio = { version = "1.35", features = ["full", "sync"] }
11+
serde = { version = "1.0", features = ["derive"] }
12+
serde_json = "1.0"
13+
validator = { workspace = true }
14+
utoipa = { workspace = true }
15+
chrono = { version = "0.4", features = ["serde"] }
16+
uuid = { version = "1.6", features = ["v4", "serde"] }
17+
futures-util = "0.3"
18+
19+
[dev-dependencies]
20+
proptest = "1.4"
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
//! Authentication handlers
2+
3+
use chrono::Utc;
4+
use rustapi_rs::prelude::*;
5+
use std::sync::Arc;
6+
use std::time::{SystemTime, UNIX_EPOCH};
7+
8+
use crate::models::{AuthResponse, Claims, LoginRequest, RegisterRequest, User, UserInfo};
9+
use crate::stores::AppState;
10+
use crate::{JWT_SECRET, TOKEN_EXPIRY_SECS};
11+
12+
/// Register a new user
13+
async fn register(
14+
State(state): State<Arc<AppState>>,
15+
ValidatedJson(body): ValidatedJson<RegisterRequest>,
16+
) -> Result<Created<AuthResponse>, ApiError> {
17+
// Create user (password stored as-is for POC, use bcrypt in production)
18+
let user = User {
19+
id: 0, // Will be set by store
20+
username: body.username,
21+
email: body.email,
22+
password_hash: body.password, // In production: hash with bcrypt/argon2
23+
created_at: Utc::now(),
24+
};
25+
26+
let created_user = state
27+
.users
28+
.create(user)
29+
.await
30+
.ok_or_else(|| ApiError::bad_request("Email already registered"))?;
31+
32+
// Generate JWT token
33+
let exp = SystemTime::now()
34+
.duration_since(UNIX_EPOCH)
35+
.unwrap()
36+
.as_secs()
37+
+ TOKEN_EXPIRY_SECS;
38+
39+
let claims = Claims {
40+
sub: created_user.id.to_string(),
41+
username: created_user.username.clone(),
42+
email: created_user.email.clone(),
43+
exp,
44+
};
45+
46+
let token = create_token(&claims, JWT_SECRET)
47+
.map_err(|e| ApiError::internal(format!("Failed to create token: {}", e)))?;
48+
49+
Ok(Created(AuthResponse {
50+
token,
51+
token_type: "Bearer".to_string(),
52+
expires_in: TOKEN_EXPIRY_SECS,
53+
user: UserInfo {
54+
id: created_user.id,
55+
username: created_user.username,
56+
email: created_user.email,
57+
},
58+
}))
59+
}
60+
61+
/// Login with email and password
62+
async fn login(
63+
State(state): State<Arc<AppState>>,
64+
ValidatedJson(body): ValidatedJson<LoginRequest>,
65+
) -> Result<Json<AuthResponse>, ApiError> {
66+
// Find user by email
67+
let user = state
68+
.users
69+
.find_by_email(&body.email)
70+
.await
71+
.ok_or_else(|| ApiError::unauthorized("Invalid email or password"))?;
72+
73+
// Verify password (plain comparison for POC, use bcrypt in production)
74+
if user.password_hash != body.password {
75+
return Err(ApiError::unauthorized("Invalid email or password"));
76+
}
77+
78+
// Generate JWT token
79+
let exp = SystemTime::now()
80+
.duration_since(UNIX_EPOCH)
81+
.unwrap()
82+
.as_secs()
83+
+ TOKEN_EXPIRY_SECS;
84+
85+
let claims = Claims {
86+
sub: user.id.to_string(),
87+
username: user.username.clone(),
88+
email: user.email.clone(),
89+
exp,
90+
};
91+
92+
let token = create_token(&claims, JWT_SECRET)
93+
.map_err(|e| ApiError::internal(format!("Failed to create token: {}", e)))?;
94+
95+
Ok(Json(AuthResponse {
96+
token,
97+
token_type: "Bearer".to_string(),
98+
expires_in: TOKEN_EXPIRY_SECS,
99+
user: UserInfo {
100+
id: user.id,
101+
username: user.username,
102+
email: user.email,
103+
},
104+
}))
105+
}
106+
107+
108+
// Route functions
109+
pub fn register_route() -> Route {
110+
post_route("/auth/register", register)
111+
}
112+
113+
pub fn login_route() -> Route {
114+
post_route("/auth/login", login)
115+
}

0 commit comments

Comments
 (0)