Compare commits
	
		
			12 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | fbf2dd89dc | ||
|  | 598125abae | ||
| 0a2ece6f6e | |||
| c29aae3207 | |||
| 466f8dd992 | |||
|  | 2da103d5b5 | ||
| 4451a3cf3d | |||
| 6d08c39a6d | |||
| 19eff26934 | |||
| 85718f3aea | |||
|  | e55f4937e4 | ||
|  | cea215f0f8 | 
							
								
								
									
										21
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										21
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1 +1,22 @@ | |||||||
| /target | /target | ||||||
|  | /migration/target | ||||||
|  |  | ||||||
|  | # Added by cargo | ||||||
|  | # | ||||||
|  | # already existing elements were commented out | ||||||
|  |  | ||||||
|  | #/target | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # Added by cargo | ||||||
|  | # | ||||||
|  | # already existing elements were commented out | ||||||
|  |  | ||||||
|  | #/target | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # Added by cargo | ||||||
|  | # | ||||||
|  | # already existing elements were commented out | ||||||
|  |  | ||||||
|  | #/target | ||||||
|   | |||||||
							
								
								
									
										1664
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										1664
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -4,3 +4,8 @@ version = "0.1.0" | |||||||
| edition = "2021" | edition = "2021" | ||||||
|  |  | ||||||
| [dependencies] | [dependencies] | ||||||
|  | actix-web = "4.9.0" | ||||||
|  | diesel = { version = "2.2.7", features = ["postgres", "r2d2"] } | ||||||
|  | dotenv = "0.15.0" | ||||||
|  | serde = { version = "1.0.218", features = ["derive"] } | ||||||
|  | serde_json = "1.0.139" | ||||||
|   | |||||||
							
								
								
									
										9
									
								
								diesel.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								diesel.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | # For documentation on how to configure this file, | ||||||
|  | # see https://diesel.rs/guides/configuring-diesel-cli | ||||||
|  |  | ||||||
|  | [print_schema] | ||||||
|  | file = "src/schema.rs" | ||||||
|  | custom_type_derives = ["diesel::query_builder::QueryId", "Clone"] | ||||||
|  |  | ||||||
|  | [migrations_directory] | ||||||
|  | dir = "migrations" | ||||||
							
								
								
									
										0
									
								
								migrations/.keep
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								migrations/.keep
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										6
									
								
								migrations/00000000000000_diesel_initial_setup/down.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								migrations/00000000000000_diesel_initial_setup/down.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | -- This file was automatically created by Diesel to setup helper functions | ||||||
|  | -- and other internal bookkeeping. This file is safe to edit, any future | ||||||
|  | -- changes will be added to existing projects as new migrations. | ||||||
|  |  | ||||||
|  | DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass); | ||||||
|  | DROP FUNCTION IF EXISTS diesel_set_updated_at(); | ||||||
							
								
								
									
										36
									
								
								migrations/00000000000000_diesel_initial_setup/up.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								migrations/00000000000000_diesel_initial_setup/up.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | -- This file was automatically created by Diesel to setup helper functions | ||||||
|  | -- and other internal bookkeeping. This file is safe to edit, any future | ||||||
|  | -- changes will be added to existing projects as new migrations. | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | -- Sets up a trigger for the given table to automatically set a column called | ||||||
|  | -- `updated_at` whenever the row is modified (unless `updated_at` was included | ||||||
|  | -- in the modified columns) | ||||||
|  | -- | ||||||
|  | -- # Example | ||||||
|  | -- | ||||||
|  | -- ```sql | ||||||
|  | -- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW()); | ||||||
|  | -- | ||||||
|  | -- SELECT diesel_manage_updated_at('users'); | ||||||
|  | -- ``` | ||||||
|  | CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$ | ||||||
|  | BEGIN | ||||||
|  |     EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s | ||||||
|  |                     FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl); | ||||||
|  | END; | ||||||
|  | $$ LANGUAGE plpgsql; | ||||||
|  |  | ||||||
|  | CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$ | ||||||
|  | BEGIN | ||||||
|  |     IF ( | ||||||
|  |         NEW IS DISTINCT FROM OLD AND | ||||||
|  |         NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at | ||||||
|  |     ) THEN | ||||||
|  |         NEW.updated_at := current_timestamp; | ||||||
|  |     END IF; | ||||||
|  |     RETURN NEW; | ||||||
|  | END; | ||||||
|  | $$ LANGUAGE plpgsql; | ||||||
							
								
								
									
										1
									
								
								migrations/2025-02-10-212801_create_users/down.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								migrations/2025-02-10-212801_create_users/down.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | DROP TABLE users; | ||||||
							
								
								
									
										5
									
								
								migrations/2025-02-10-212801_create_users/up.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								migrations/2025-02-10-212801_create_users/up.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | CREATE TABLE users ( | ||||||
|  |     id SERIAL PRIMARY KEY, | ||||||
|  |     name VARCHAR NOT NULL, | ||||||
|  |     email VARCHAR NOT NULL UNIQUE | ||||||
|  | ); | ||||||
							
								
								
									
										88
									
								
								src/crud/handlers.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								src/crud/handlers.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | |||||||
|  | use actix_web::{web, HttpResponse, Responder}; | ||||||
|  | use diesel::pg::PgConnection; | ||||||
|  | use diesel::r2d2::{self, ConnectionManager}; | ||||||
|  | use serde::Deserialize; | ||||||
|  |  | ||||||
|  | use super::r#trait::CrudEntity; | ||||||
|  |  | ||||||
|  | type DbPool = r2d2::Pool<ConnectionManager<PgConnection>>; | ||||||
|  |  | ||||||
|  | #[derive(Deserialize)] | ||||||
|  | struct Pagination { | ||||||
|  |     limit: Option<i64>, | ||||||
|  |     offset: Option<i64>, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub async fn get_all<T: CrudEntity>( | ||||||
|  |     pool: web::Data<DbPool>, | ||||||
|  |     query: web::Query<Pagination>, | ||||||
|  | ) -> impl Responder { | ||||||
|  |     let mut conn = match pool.get() { | ||||||
|  |         Ok(conn) => conn, | ||||||
|  |         Err(_) => return HttpResponse::InternalServerError().finish(), | ||||||
|  |     }; | ||||||
|  |     match T::find_all(&mut conn, query.limit, query.offset) { | ||||||
|  |         Ok(items) => HttpResponse::Ok().json(items), | ||||||
|  |         Err(_) => HttpResponse::InternalServerError().finish(), | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub async fn get_one<T: CrudEntity>( | ||||||
|  |     pool: web::Data<DbPool>, | ||||||
|  |     id: web::Path<T::Id>, | ||||||
|  | ) -> impl Responder { | ||||||
|  |     let mut conn = match pool.get() { | ||||||
|  |         Ok(conn) => conn, | ||||||
|  |         Err(_) => return HttpResponse::InternalServerError().finish(), | ||||||
|  |     }; | ||||||
|  |     match T::find_by_id(&mut conn, id.into_inner()) { | ||||||
|  |         Ok(item) => HttpResponse::Ok().json(item), | ||||||
|  |         Err(diesel::result::Error::NotFound) => HttpResponse::NotFound().finish(), | ||||||
|  |         Err(_) => HttpResponse::InternalServerError().finish(), | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub async fn create<T: CrudEntity>( | ||||||
|  |     pool: web::Data<DbPool>, | ||||||
|  |     item: web::Json<T::CreateInput>, | ||||||
|  | ) -> impl Responder { | ||||||
|  |     let mut conn = match pool.get() { | ||||||
|  |         Ok(conn) => conn, | ||||||
|  |         Err(_) => return HttpResponse::InternalServerError().finish(), | ||||||
|  |     }; | ||||||
|  |     match T::insert(&mut conn, item.into_inner()) { | ||||||
|  |         Ok(created) => HttpResponse::Created().json(created), | ||||||
|  |         Err(_) => HttpResponse::InternalServerError().finish(), | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub async fn update<T: CrudEntity>( | ||||||
|  |     pool: web::Data<DbPool>, | ||||||
|  |     id: web::Path<T::Id>, | ||||||
|  |     item: web::Json<T::UpdateInput>, | ||||||
|  | ) -> impl Responder { | ||||||
|  |     let mut conn = match pool.get() { | ||||||
|  |         Ok(conn) => conn, | ||||||
|  |         Err(_) => return HttpResponse::InternalServerError().finish(), | ||||||
|  |     }; | ||||||
|  |     match T::update(&mut conn, id.into_inner(), item.into_inner()) { | ||||||
|  |         Ok(updated) => HttpResponse::Ok().json(updated), | ||||||
|  |         Err(diesel::result::Error::NotFound) => HttpResponse::NotFound().finish(), | ||||||
|  |         Err(_) => HttpResponse::InternalServerError().finish(), | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub async fn delete<T: CrudEntity>( | ||||||
|  |     pool: web::Data<DbPool>, | ||||||
|  |     id: web::Path<T::Id>, | ||||||
|  | ) -> impl Responder { | ||||||
|  |     let mut conn = match pool.get() { | ||||||
|  |         Ok(conn) => conn, | ||||||
|  |         Err(_) => return HttpResponse::InternalServerError().finish(), | ||||||
|  |     }; | ||||||
|  |     match T::delete(&mut conn, id.into_inner()) { | ||||||
|  |         Ok(_) => HttpResponse::NoContent().finish(), | ||||||
|  |         Err(diesel::result::Error::NotFound) => HttpResponse::NotFound().finish(), | ||||||
|  |         Err(_) => HttpResponse::InternalServerError().finish(), | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										2
									
								
								src/crud/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/crud/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | |||||||
|  | pub mod r#trait; | ||||||
|  | pub mod handlers; | ||||||
							
								
								
									
										14
									
								
								src/crud/trait.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/crud/trait.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | use diesel::prelude::*; | ||||||
|  | use serde::{de::DeserializeOwned, Serialize}; | ||||||
|  |  | ||||||
|  | pub trait CrudEntity: Sized + Serialize { | ||||||
|  |     type Id: Serialize + DeserializeOwned + 'static; | ||||||
|  |     type CreateInput: DeserializeOwned + 'static; | ||||||
|  |     type UpdateInput: DeserializeOwned + 'static; | ||||||
|  |  | ||||||
|  |     fn find_all(conn: &mut PgConnection, limit: Option<i64>, offset: Option<i64>) -> Result<Vec<Self>, diesel::result::Error>; | ||||||
|  |     fn find_by_id(conn: &mut PgConnection, id: Self::Id) -> Result<Self, diesel::result::Error>; | ||||||
|  |     fn insert(conn: &mut PgConnection, item: Self::CreateInput) -> Result<Self, diesel::result::Error>; | ||||||
|  |     fn update(conn: &mut PgConnection, id: Self::Id, item: Self::UpdateInput) -> Result<Self, diesel::result::Error>; | ||||||
|  |     fn delete(conn: &mut PgConnection, id: Self::Id) -> Result<usize, diesel::result::Error>; | ||||||
|  | } | ||||||
							
								
								
									
										14
									
								
								src/db/connection.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/db/connection.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | use diesel::pg::PgConnection; | ||||||
|  | use diesel::r2d2::{self, ConnectionManager}; | ||||||
|  | use std::env; | ||||||
|  |  | ||||||
|  | pub type Pool = r2d2::Pool<ConnectionManager<PgConnection>>; | ||||||
|  |  | ||||||
|  | pub fn establish_connection_pool() -> Pool { | ||||||
|  |     dotenv::dotenv().ok(); | ||||||
|  |     let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); | ||||||
|  |     let manager = ConnectionManager::<PgConnection>::new(database_url); | ||||||
|  |     r2d2::Pool::builder() | ||||||
|  |         .build(manager) | ||||||
|  |         .expect("Failed to create pool") | ||||||
|  | } | ||||||
							
								
								
									
										1
									
								
								src/db/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/db/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | pub mod connection; | ||||||
							
								
								
									
										25
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								src/main.rs
									
									
									
									
									
								
							| @@ -1,3 +1,24 @@ | |||||||
| fn main() { | use actix_web::{web, App, HttpServer}; | ||||||
|     println!("Hello, world!"); | use db::connection::establish_connection_pool; | ||||||
|  | use models::user::User; | ||||||
|  | use routes::crud::add_crud_routes; | ||||||
|  |  | ||||||
|  | mod crud; | ||||||
|  | mod models; | ||||||
|  | mod db; | ||||||
|  | mod routes; | ||||||
|  | mod schema; | ||||||
|  |  | ||||||
|  | #[actix_web::main] | ||||||
|  | async fn main() -> std::io::Result<()> { | ||||||
|  |     let pool = establish_connection_pool(); | ||||||
|  |  | ||||||
|  |     HttpServer::new(move || { | ||||||
|  |         let mut app = App::new().app_data(web::Data::new(pool.clone())); | ||||||
|  |         app = add_crud_routes::<User>(app, "/users"); | ||||||
|  |         app | ||||||
|  |     }) | ||||||
|  |     .bind("0.0.0.0:3000")? | ||||||
|  |     .run() | ||||||
|  |     .await | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								src/models/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/models/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | pub mod user; | ||||||
							
								
								
									
										63
									
								
								src/models/user.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/models/user.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | |||||||
|  | use diesel::prelude::*; | ||||||
|  | use serde::{Serialize, Deserialize}; | ||||||
|  | use crate::{crud::r#trait::CrudEntity, schema::users}; | ||||||
|  |  | ||||||
|  | #[derive(Serialize, Deserialize, Queryable, Insertable)] | ||||||
|  | #[diesel(table_name = users)] | ||||||
|  | pub struct User { | ||||||
|  |     id: i32, | ||||||
|  |     name: String, | ||||||
|  |     email: String, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Deserialize, Insertable)] | ||||||
|  | #[diesel(table_name = users)] | ||||||
|  | pub struct NewUser { | ||||||
|  |     name: String, | ||||||
|  |     email: String, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Deserialize, AsChangeset)] | ||||||
|  | #[diesel(table_name = users)] | ||||||
|  | pub struct UpdateUser { | ||||||
|  |     name: String, | ||||||
|  |     email: String, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl CrudEntity for User { | ||||||
|  |     type Id = i32; | ||||||
|  |     type CreateInput = NewUser; | ||||||
|  |     type UpdateInput = UpdateUser; // Changed from User to UpdateUser | ||||||
|  |  | ||||||
|  |     fn find_all(conn: &mut PgConnection, limit: Option<i64>, offset: Option<i64>) -> Result<Vec<Self>, diesel::result::Error> { | ||||||
|  |         use crate::schema::users::dsl::*; | ||||||
|  |         let mut query = users.into_boxed(); | ||||||
|  |         if let Some(l) = limit { | ||||||
|  |             query = query.limit(l); | ||||||
|  |         } | ||||||
|  |         if let Some(o) = offset { | ||||||
|  |             query = query.offset(o); | ||||||
|  |         } | ||||||
|  |         query.load::<Self>(conn) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn find_by_id(conn: &mut PgConnection, id: Self::Id) -> Result<Self, diesel::result::Error> { | ||||||
|  |         use crate::schema::users::dsl::*; | ||||||
|  |         users.filter(id.eq(id)).first::<Self>(conn) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn insert(conn: &mut PgConnection, item: Self::CreateInput) -> Result<Self, diesel::result::Error> { | ||||||
|  |         use crate::schema::users; | ||||||
|  |         diesel::insert_into(users::table).values(&item).get_result(conn) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn update(conn: &mut PgConnection, id: Self::Id, item: Self::UpdateInput) -> Result<Self, diesel::result::Error> { | ||||||
|  |         use crate::schema::users::dsl::*; | ||||||
|  |         diesel::update(users.filter(id.eq(id))).set(&item).get_result(conn) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn delete(conn: &mut PgConnection, id: Self::Id) -> Result<usize, diesel::result::Error> { | ||||||
|  |         use crate::schema::users::dsl::*; | ||||||
|  |         diesel::delete(users.filter(id.eq(id))).execute(conn) | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										24
									
								
								src/routes/crud.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/routes/crud.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | use actix_web::{dev::{ServiceFactory, ServiceRequest}, web, App}; | ||||||
|  | use crate::crud::{handlers, r#trait::CrudEntity}; | ||||||
|  |  | ||||||
|  | pub fn add_crud_routes<T: CrudEntity + 'static>(mut app: App<T>, path: &str) -> App<T> | ||||||
|  | where | ||||||
|  |     T::Id: serde::de::DeserializeOwned + serde::Serialize + 'static, | ||||||
|  |     T::CreateInput: serde::de::DeserializeOwned + 'static, | ||||||
|  |     T::UpdateInput: serde::de::DeserializeOwned + 'static, | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     T: ServiceFactory<ServiceRequest, Config = (), Error = actix_web::Error, InitError = ()> | ||||||
|  | { | ||||||
|  |     app = app.service( | ||||||
|  |         web::resource(path) | ||||||
|  |             .route(web::get().to(handlers::get_all::<T>)) | ||||||
|  |             .route(web::post().to(handlers::create::<T>)), | ||||||
|  |     ); | ||||||
|  |     app.service( | ||||||
|  |         web::resource(&format!("{}/{{id}}", path)) | ||||||
|  |             .route(web::get().to(handlers::get_one::<T>)) | ||||||
|  |             .route(web::put().to(handlers::update::<T>)) | ||||||
|  |             .route(web::delete().to(handlers::delete::<T>)), | ||||||
|  |     ) | ||||||
|  | } | ||||||
							
								
								
									
										1
									
								
								src/routes/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/routes/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | pub mod crud; | ||||||
							
								
								
									
										9
									
								
								src/schema.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/schema.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | // @generated automatically by Diesel CLI. | ||||||
|  |  | ||||||
|  | diesel::table! { | ||||||
|  |     users (id) { | ||||||
|  |         id -> Int4, | ||||||
|  |         name -> Varchar, | ||||||
|  |         email -> Varchar, | ||||||
|  |     } | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user