1pub const CARGO_TOML: &str = r#"[package]
2name = "{{project_name}}"
3version = "0.1.0"
4edition = "2021"
5
6[dependencies]
7argon2 = "0.5.3"
8bcrypt = "0.16.0"
9bson = { version = "2.13.0", features = ["chrono-0_4"] }
10chrono = { version = "0.4.39", features = ["serde"] }
11dotenvy = "0.15.7"
12futures = "0.3.31"
13jsonwebtoken = "9.3.0"
14mongodb = "3.1.1"
15rand = "0.8.5"
16regex = "1.11.1"
17rocket = { version = "0.5.1", features = ["json"] }
18schemars = "0.8.21"
19serde = { version = "1.0.216", features = ["derive"] }
20tokio = { version = "1.42.0", features = ["full"] }
21sha2 = "0.10.8"
22"#;
23
24pub const MAIN_RS: &str = r#"#[macro_use]
25extern crate rocket;
26
27mod auth;
28mod catchers;
29mod db;
30mod fairings;
31mod guards;
32mod middleware;
33mod models;
34mod options;
35mod repositories;
36mod routes;
37
38#[launch]
39fn rocket() -> _ {
40 rocket::build()
41 .attach(db::init())
42 .attach(fairings::Cors)
43 .register(
44 "/",
45 catchers![
46 catchers::bad_request,
47 catchers::unauthorized,
48 catchers::forbidden,
49 catchers::not_found,
50 catchers::method_not_allowed,
51 catchers::request_timeout,
52 catchers::conflict,
53 catchers::payload_too_large,
54 catchers::unsupported_media_type,
55 catchers::teapot,
56 catchers::too_many_requests,
57 catchers::internal_error,
58 catchers::bad_gateway,
59 catchers::service_unavailable,
60 catchers::gateway_timeout
61 ],
62 )
63 .mount("/", routes![options::options])
64 .mount("/", routes::user_routes())
65}
66"#;
67
68pub const CATACHERS: &str = r#"use rocket::catch;
69
70#[catch(400)]
71pub async fn bad_request() -> &'static str {
72 "Bad Request."
73}
74
75#[catch(401)]
76pub async fn unauthorized() -> &'static str {
77 "Unauthorized access."
78}
79
80#[catch(403)]
81pub async fn forbidden() -> &'static str {
82 "You don't have permission to access this resource."
83}
84
85#[catch(404)]
86pub async fn not_found() -> &'static str {
87 "Resource not found."
88}
89
90#[catch(405)]
91pub async fn method_not_allowed() -> &'static str {
92 "Method Not Allowed."
93}
94
95#[catch(408)]
96pub async fn request_timeout() -> &'static str {
97 "Request Timeout."
98}
99
100#[catch(409)]
101pub async fn conflict() -> &'static str {
102 "The request could not be completed due to a conflict."
103}
104
105#[catch(413)]
106pub async fn payload_too_large() -> &'static str {
107 "Payload Too Large."
108}
109
110#[catch(415)]
111pub async fn unsupported_media_type() -> &'static str {
112 "Unsupported Media Type."
113}
114
115#[catch(418)]
116pub async fn teapot() -> &'static str {
117 "I'm a teapot."
118}
119
120#[catch(429)]
121pub async fn too_many_requests() -> &'static str {
122 "Too Many Requests."
123}
124
125#[catch(500)]
126pub async fn internal_error() -> &'static str {
127 "Internal Server Error."
128}
129
130#[catch(502)]
131pub async fn bad_gateway() -> &'static str {
132 "Bad Gateway."
133}
134
135#[catch(503)]
136pub async fn service_unavailable() -> &'static str {
137 "Service Unavailable."
138}
139
140#[catch(504)]
141pub async fn gateway_timeout() -> &'static str {
142 "Gateway Timeout."
143}
144"#;
145
146pub const MODELS: &str = r#"use chrono::{DateTime, Utc};
147use mongodb::bson::oid::ObjectId;
148use schemars::JsonSchema;
149use serde::{Deserialize, Serialize};
150
151#[derive(Debug, Serialize, Deserialize, Clone)]
152pub struct UserDocument {
153 #[serde(rename = "_id")]
154 pub id: ObjectId,
155 pub username: String,
156 pub email: String,
157 pub password: String,
158 #[serde(
159 with = "bson::serde_helpers::chrono_datetime_as_bson_datetime",
160 rename = "createdAt"
161 )]
162 pub created_at: DateTime<Utc>,
163}
164
165#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone)]
166pub struct User {
167 #[serde(rename = "_id")]
168 pub id: String,
169 pub username: String,
170 pub email: String,
171 pub password: String,
172 #[serde(rename = "createdAt")]
173 pub created_at: String,
174}
175
176#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone)]
177pub struct UserInfo {
178 #[serde(rename = "_id")]
179 pub id: String,
180 pub username: String,
181 pub email: String,
182 #[serde(rename = "createdAt")]
183 pub created_at: String,
184}
185
186#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone)]
187pub struct LoginCredentials {
188 pub email: String,
189 pub password: String,
190}
191
192#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone)]
193pub struct RegistrationCredentials {
194 pub username: String,
195 pub email: String,
196 pub password: String,
197}
198
199#[derive(Debug, Deserialize, Serialize)]
200pub struct SuccessResponse {
201 pub status: u16,
202 pub message: String,
203}
204
205#[derive(Debug, Deserialize, Serialize)]
206pub struct ErrorResponse {
207 pub status: u16,
208 pub message: String,
209}
210"#;
211
212pub const OPTIONS: &str = r#"
213#[rocket::options("/<_route_args..>")]
214pub async fn options(_route_args: Option<std::path::PathBuf>) -> rocket::http::Status {
215 rocket::http::Status::Ok
216}
217"#;
218
219pub const ROUTES_MOD: &str = r#"use crate::auth::{authorize_user, hash_password};
220use crate::guards::AuthClaims;
221use crate::models::{ErrorResponse, SuccessResponse, UserInfo};
222use crate::models::{LoginCredentials, RegistrationCredentials, User, UserDocument};
223use crate::repositories::UserRepository;
224
225use rocket::http::Status;
226use rocket::http::{Cookie, CookieJar, SameSite};
227use rocket::serde::json::Json;
228use rocket::{State, delete, get, post, put, routes};
229
230use std::sync::Arc;
231
232/// Registers a new user.
233#[post("/register", data = "<credentials>")]
234pub async fn register(
235 repo: &State<Arc<UserRepository>>,
236 credentials: Json<RegistrationCredentials>,
237) -> Result<Json<SuccessResponse>, Json<ErrorResponse>> {
238 if let Ok(Some(_)) = repo.get_user_by_email(&credentials.email).await {
239 return Err(Json(ErrorResponse {
240 status: Status::Conflict.code,
241 message: "A user with this email already exists".to_string(),
242 }));
243 }
244
245 let hashed_password = match hash_password(credentials.password.clone()) {
246 Ok(hash) => hash,
247 Err(_) => {
248 return Err(Json(ErrorResponse {
249 status: Status::InternalServerError.code,
250 message: "Something went wrong, please try again later".to_string(),
251 }));
252 }
253 };
254
255 let _ = match repo
256 .create_user(&credentials.username, &credentials.email, &hashed_password)
257 .await
258 {
259 Ok(user) => user,
260 Err(_) => {
261 return Err(Json(ErrorResponse {
262 status: Status::InternalServerError.code,
263 message: "Failed to register account".to_string(),
264 }));
265 }
266 };
267
268 Ok(Json(SuccessResponse {
269 status: Status::Ok.code,
270 message: "User registered successfully".to_string(),
271 }))
272}
273
274/// Authenticates a user and sets an authentication cookie.
275#[post("/login", data = "<credentials>")]
276pub async fn login(
277 repo: &State<Arc<UserRepository>>,
278 credentials: Json<LoginCredentials>,
279 cookies: &CookieJar<'_>,
280) -> Result<Json<SuccessResponse>, Json<ErrorResponse>> {
281 let user_document = match repo.get_user_by_email(&credentials.email).await {
282 Ok(Some(user_document)) => user_document,
283 Ok(None) => {
284 return Err(Json(ErrorResponse {
285 status: Status::Unauthorized.code,
286 message: "Invalid email or password".to_string(),
287 }));
288 }
289 Err(_) => {
290 return Err(Json(ErrorResponse {
291 status: Status::InternalServerError.code,
292 message: "Something went wrong, please try again later".to_string(),
293 }));
294 }
295 };
296
297 let user = User {
298 id: user_document.id.to_string(),
299 username: user_document.username.to_string(),
300 email: user_document.email.clone(),
301 password: user_document.password.clone(),
302 created_at: user_document.created_at.to_rfc3339(),
303 };
304
305 let token = match authorize_user(&user, &credentials).await {
306 Ok(token) => token,
307 Err(_) => {
308 return Err(Json(ErrorResponse {
309 status: Status::Unauthorized.code,
310 message: "Invalid email or password".to_string(),
311 }));
312 }
313 };
314
315 // Set the token cookie (HTTP-only, secure)
316 #[allow(deprecated)]
317 let cookie = Cookie::build(("auth_token", token.clone()))
318 .http_only(true)
319 .secure(false) // Ensure your app uses HTTPS
320 .same_site(SameSite::Lax)
321 .path("/")
322 .finish();
323
324 cookies.add(cookie);
325
326 Ok(Json(SuccessResponse {
327 status: Status::Ok.code,
328 message: "Login successful".to_string(),
329 }))
330}
331
332/// Logs out the current user by removing the authentication cookie.
333#[post("/logout")]
334pub fn logout(cookies: &CookieJar<'_>) -> Json<SuccessResponse> {
335 cookies.remove(Cookie::build(("auth_token", "")).path("/").build());
336 Json(SuccessResponse {
337 status: 200,
338 message: "Logged out successfully".to_string(),
339 })
340}
341
342/// Retrieves a single user by ID (requires authentication).
343#[get("/users/<id>")]
344pub async fn get_user(
345 _auth: AuthClaims,
346 repo: &State<Arc<UserRepository>>,
347 id: &str,
348) -> Result<Json<UserDocument>, Json<ErrorResponse>> {
349 let user = match repo.get_user_by_id(&id).await {
350 Ok(Some(user)) => user,
351 Ok(None) => {
352 return Err(Json(ErrorResponse {
353 status: Status::NotFound.code,
354 message: "User not found".to_string(),
355 }));
356 }
357 Err(_) => {
358 return Err(Json(ErrorResponse {
359 status: Status::InternalServerError.code,
360 message: "Something went wrong, please try again later".to_string(),
361 }));
362 }
363 };
364
365 Ok(Json(user))
366}
367
368/// Retrieves a single user by email (requires authentication).
369#[get("/user/<email>")]
370pub async fn get_user_by_email(
371 _auth: AuthClaims,
372 repo: &State<Arc<UserRepository>>,
373 email: &str,
374) -> Result<Json<UserInfo>, Json<ErrorResponse>> {
375 let user = match repo.get_user_by_email(&email).await {
376 Ok(Some(user)) => user,
377 Ok(None) => {
378 return Err(Json(ErrorResponse {
379 status: Status::NotFound.code,
380 message: "User not found".to_string(),
381 }));
382 }
383 Err(_) => {
384 return Err(Json(ErrorResponse {
385 status: Status::InternalServerError.code,
386 message: "Something went wrong, please try again later".to_string(),
387 }));
388 }
389 };
390
391 Ok(Json(UserInfo {
392 id: user.id.to_string(),
393 username: user.username,
394 email: user.email,
395 created_at: user.created_at.to_string(),
396 }))
397}
398
399/// Updates an existing user's information by ID (requires authentication).
400#[put("/update/<id>", data = "<credentials>")]
401pub async fn update_user(
402 _auth: AuthClaims,
403 repo: &State<Arc<UserRepository>>,
404 id: &str,
405 credentials: Json<RegistrationCredentials>,
406) -> Result<Json<UserDocument>, Json<ErrorResponse>> {
407 // Check if the email is already in use by another user
408 if let Ok(Some(existing_user)) = repo.get_user_by_email(&credentials.email).await {
409 // If the email exists and it's not the user being updated
410 if existing_user.id.to_string() != id {
411 return Err(Json(ErrorResponse {
412 status: Status::Conflict.code,
413 message: "A user with this email already exists".to_string(),
414 }));
415 }
416 }
417
418 let hashed_password = match hash_password(credentials.password.clone()) {
419 Ok(hash) => hash,
420 Err(_) => {
421 return Err(Json(ErrorResponse {
422 status: Status::InternalServerError.code,
423 message: "Something went wrong, please try again later".to_string(),
424 }));
425 }
426 };
427
428 let user = match repo
429 .update_user(
430 &id,
431 Some(&credentials.username),
432 Some(&credentials.email),
433 Some(&hashed_password),
434 )
435 .await
436 {
437 Ok(Some(user)) => user,
438 Ok(None) => {
439 return Err(Json(ErrorResponse {
440 status: Status::NotFound.code,
441 message: "User not found".to_string(),
442 }));
443 }
444 Err(_) => {
445 return Err(Json(ErrorResponse {
446 status: Status::InternalServerError.code,
447 message: "Something went wrong, please try again later".to_string(),
448 }));
449 }
450 };
451
452 Ok(Json(user))
453}
454
455/// Deletes a user by ID (requires authentication).
456#[delete("/delete/<id>")]
457pub async fn delete_user(
458 _auth: AuthClaims,
459 repo: &State<Arc<UserRepository>>,
460 id: &str,
461) -> Result<Json<SuccessResponse>, Json<ErrorResponse>> {
462 match repo.delete_user(&id).await {
463 Ok(Some(_)) => Ok(Json(SuccessResponse {
464 status: Status::Ok.code,
465 message: "User deleted successfully".to_string(),
466 })),
467 Ok(None) => {
468 return Err(Json(ErrorResponse {
469 status: Status::NotFound.code,
470 message: "User not found".to_string(),
471 }));
472 }
473 Err(_) => {
474 return Err(Json(ErrorResponse {
475 status: Status::InternalServerError.code,
476 message: "Something went wrong, please try again later".to_string(),
477 }));
478 }
479 }
480}
481
482/// Collects all user-related routes for mounting.
483pub fn user_routes() -> Vec<rocket::Route> {
484 routes![
485 register,
486 login,
487 logout,
488 get_user,
489 get_user_by_email,
490 update_user,
491 delete_user
492 ]
493}
494"#;
495
496pub const DB: &str = r#"use dotenvy::dotenv;
497use mongodb::{Client, options::ClientOptions};
498use rocket::fairing::AdHoc;
499use std::sync::Arc;
500
501use crate::repositories::UserRepository;
502
503pub fn init() -> AdHoc {
504 AdHoc::on_ignite(
505 "Establish connection with Database cluster",
506 |rocket| async {
507 match connect().await {
508 Ok(user_repository) => rocket.manage(user_repository),
509 Err(error) => {
510 panic!("Cannot connect to instance -> {:?}", error)
511 }
512 }
513 },
514 )
515}
516
517async fn connect() -> mongodb::error::Result<Arc<UserRepository>> {
518 dotenv().ok();
519 let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set...");
520 let database_name = std::env::var("DATABASE").expect("DATABASE must be set...");
521 let client_options = ClientOptions::parse(database_url).await?;
522 let client = Client::with_options(client_options)?;
523 let _database = client.database(&database_name);
524
525 Ok(Arc::new(UserRepository::new(
526 &client,
527 &database_name,
528 "users",
529 )))
530}
531"#;
532
533pub const REPOSITORIES: &str = r#"#![allow(unused)]
534use chrono::{DateTime, Utc};
535use futures::stream::TryStreamExt;
536use mongodb::{
537 bson::{doc, oid::ObjectId},
538 error::{Error, Result},
539 options::ClientOptions,
540 Client, Collection,
541};
542use serde::{Deserialize, Serialize};
543
544use crate::models::UserDocument;
545
546#[derive(Debug)]
547pub struct UserRepository {
548 collection: Collection<UserDocument>,
549}
550
551impl UserRepository {
552 pub fn new(client: &Client, db_name: &str, collection_name: &str) -> Self {
553 let collection = client
554 .database(db_name)
555 .collection::<UserDocument>(collection_name);
556 Self { collection }
557 }
558
559 /// CREATE a new user
560 pub async fn create_user(
561 &self,
562 username: &str,
563 email: &str,
564 password: &str,
565 ) -> Result<UserDocument> {
566 if let Some(_) = self.collection.find_one(doc! { "email": email }).await? {
567 return Err(Error::from(std::io::Error::new(
568 std::io::ErrorKind::AlreadyExists,
569 "A user with this email already exists.",
570 )));
571 }
572
573 let user = UserDocument {
574 id: ObjectId::new(),
575 username: username.to_string(),
576 email: email.to_string(),
577 password: password.to_string(),
578 created_at: Utc::now(),
579 };
580
581 self.collection.insert_one(&user).await?;
582
583 Ok(user)
584 }
585
586 /// GET user by id
587 pub async fn get_user_by_id(&self, id: &str) -> Result<Option<UserDocument>> {
588 match ObjectId::parse_str(id) {
589 Ok(object_id) => {
590 let filter = doc! { "_id": object_id };
591 let user = self.collection.find_one(filter).await?;
592 Ok(user)
593 }
594 Err(_) => Ok(None), // Invalid ID treated as "not found"
595 }
596 }
597
598 /// GET user by email
599 pub async fn get_user_by_email(&self, email: &str) -> Result<Option<UserDocument>> {
600 let filter = doc! { "email": email };
601 let user = self.collection.find_one(filter).await?;
602 Ok(user)
603 }
604
605 /// UPDATE a user
606 pub async fn update_user(
607 &self,
608 id: &str,
609 username: Option<&str>,
610 email: Option<&str>,
611 password: Option<&str>,
612 ) -> Result<Option<UserDocument>> {
613 let object_id = match ObjectId::parse_str(id) {
614 Ok(oid) => oid,
615 Err(_) => return Ok(None),
616 };
617
618 let mut update_doc = doc! {};
619
620 if let Some(username) = username {
621 update_doc.insert("username", username);
622 }
623
624 if let Some(email) = email {
625 update_doc.insert("email", email);
626 }
627
628 if let Some(password) = password {
629 update_doc.insert("password", password);
630 }
631
632 if update_doc.is_empty() {
633 return Ok(None);
634 }
635
636 let filter = doc! { "_id": object_id };
637 let update = doc! { "$set": update_doc };
638
639 let user = self.collection.find_one_and_update(filter, update).await?;
640 Ok(user)
641 }
642
643 /// DELETE a user
644 pub async fn delete_user(&self, id: &str) -> Result<Option<UserDocument>> {
645 let object_id = match ObjectId::parse_str(id) {
646 Ok(oid) => oid,
647 Err(_) => return Ok(None),
648 };
649
650 let filter = doc! { "_id": object_id };
651 let user = self.collection.find_one_and_delete(filter).await?;
652 Ok(user)
653 }
654
655 /// GET all users
656 pub async fn list_users(&self) -> Result<Vec<UserDocument>> {
657 let filter = doc! {};
658 let mut cursor = self.collection.find(filter).await?;
659 let mut users = Vec::new();
660
661 while let Some(user) = cursor.try_next().await? {
662 users.push(user);
663 }
664
665 Ok(users)
666 }
667}
668"#;