├── .env ├── .gitignore ├── Cargo.toml ├── config └── prometheus.yml ├── entity ├── Cargo.toml └── src │ ├── comment.rs │ ├── mod.rs │ ├── post.rs │ ├── prelude.rs │ └── user.rs ├── migration ├── Cargo.toml ├── README.md └── src │ ├── lib.rs │ ├── m20220101_000001_create_table.rs │ ├── m20231009_234608_create_posts_table.rs │ ├── m20231009_234627_create_comments_table.rs │ └── main.rs ├── public ├── index.html └── uploads │ └── 1699317162.png └── src ├── handlers ├── auth_handlers.rs ├── mod.rs ├── post_handler.rs └── user_handler.rs ├── main.rs ├── models ├── mod.rs ├── post_models.rs └── user_models.rs ├── routes ├── auth_routes.rs ├── home_routes.rs ├── mod.rs └── user_routes.rs └── utils ├── api_error.rs ├── constants.rs ├── guards.rs ├── jwt.rs └── mod.rs /.env: -------------------------------------------------------------------------------- 1 | DATABASE_URL=postgres://trasherr:trasherr@localhost:5432/BlogDB 2 | DATABASE_SCHEMA=public 3 | TOKEN=mzw4em3pic-43x27wwpdt-628s3v2xid -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | target -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "Rust_Axum" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [workspace] 9 | members = [".", "entity", "migration"] 10 | 11 | [dependencies] 12 | entity = { path = "entity" } 13 | migration = { path = "migration" } 14 | axum = { version = "0.6.20", features = ["headers", "multipart"] } 15 | tokio = { version = "1.31.0", features = ["full"] } 16 | sea-orm = { version = "0.12", features = [ "sqlx-postgres", "runtime-tokio-rustls", "macros" ] } 17 | uuid = { version = "1.4.1", features = ["v4"] } 18 | chrono = "0.4.26" 19 | jsonwebtoken = "8" 20 | serde = {version = "1.0.188", features = ["derive"] } 21 | tower-http = { version = "0.4.4", features = ["cors", "fs"] } 22 | dotenv = "0.15.0" 23 | lazy_static = "1.4.0" 24 | serde_json = "1.0.107" 25 | image = "0.24.7" 26 | fast_image_resize = "2.7.3" 27 | axum-prometheus = "0.4.0" -------------------------------------------------------------------------------- /config/prometheus.yml: -------------------------------------------------------------------------------- 1 | # my global config 2 | global: 3 | scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute. 4 | evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute. 5 | # scrape_timeout is set to the global default (10s). 6 | 7 | # Alertmanager configuration 8 | alerting: 9 | alertmanagers: 10 | - static_configs: 11 | - targets: 12 | # - alertmanager:9093 13 | 14 | # Load rules once and periodically evaluate them according to the global 'evaluation_interval'. 15 | rule_files: 16 | # - "first_rules.yml" 17 | # - "second_rules.yml" 18 | 19 | 20 | scrape_configs: 21 | - job_name: blogging_api 22 | static_configs: 23 | - targets: ["host.docker.internal:3000"] 24 | -------------------------------------------------------------------------------- /entity/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "entity" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [lib] 8 | name = "entity" 9 | path = "src/mod.rs" 10 | 11 | [dependencies] 12 | serde = { version = "1", features = ["derive"] } 13 | 14 | [dependencies.sea-orm] 15 | #path = "../../../" # remove this line in your own project 16 | version = "0.12.2" # sea-orm version -------------------------------------------------------------------------------- /entity/src/comment.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.2 2 | 3 | use sea_orm::entity::prelude::*; 4 | 5 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] 6 | #[sea_orm(table_name = "comment")] 7 | pub struct Model { 8 | #[sea_orm(primary_key)] 9 | pub id: i32, 10 | pub user_id: i32, 11 | pub text: String, 12 | pub post_id: i32, 13 | pub created_at: DateTime, 14 | } 15 | 16 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 17 | pub enum Relation { 18 | #[sea_orm( 19 | belongs_to = "super::post::Entity", 20 | from = "Column::PostId", 21 | to = "super::post::Column::Id", 22 | on_update = "NoAction", 23 | on_delete = "NoAction" 24 | )] 25 | Post, 26 | #[sea_orm( 27 | belongs_to = "super::user::Entity", 28 | from = "Column::UserId", 29 | to = "super::user::Column::Id", 30 | on_update = "NoAction", 31 | on_delete = "NoAction" 32 | )] 33 | User, 34 | } 35 | 36 | impl Related for Entity { 37 | fn to() -> RelationDef { 38 | Relation::Post.def() 39 | } 40 | } 41 | 42 | impl Related for Entity { 43 | fn to() -> RelationDef { 44 | Relation::User.def() 45 | } 46 | } 47 | 48 | impl ActiveModelBehavior for ActiveModel {} 49 | -------------------------------------------------------------------------------- /entity/src/mod.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.2 2 | 3 | pub mod prelude; 4 | 5 | pub mod comment; 6 | pub mod post; 7 | pub mod user; 8 | -------------------------------------------------------------------------------- /entity/src/post.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.2 2 | 3 | use sea_orm::entity::prelude::*; 4 | 5 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] 6 | #[sea_orm(table_name = "post")] 7 | pub struct Model { 8 | #[sea_orm(primary_key)] 9 | pub id: i32, 10 | #[sea_orm(unique)] 11 | pub uuid: Uuid, 12 | pub title: String, 13 | pub text: String, 14 | pub image: String, 15 | pub created_at: DateTime, 16 | pub user_id: i32, 17 | } 18 | 19 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 20 | pub enum Relation { 21 | #[sea_orm(has_many = "super::comment::Entity")] 22 | Comment, 23 | #[sea_orm( 24 | belongs_to = "super::user::Entity", 25 | from = "Column::UserId", 26 | to = "super::user::Column::Id", 27 | on_update = "NoAction", 28 | on_delete = "NoAction" 29 | )] 30 | User, 31 | } 32 | 33 | impl Related for Entity { 34 | fn to() -> RelationDef { 35 | Relation::Comment.def() 36 | } 37 | } 38 | 39 | impl Related for Entity { 40 | fn to() -> RelationDef { 41 | Relation::User.def() 42 | } 43 | } 44 | 45 | impl ActiveModelBehavior for ActiveModel {} 46 | -------------------------------------------------------------------------------- /entity/src/prelude.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.2 2 | 3 | pub use super::comment::Entity as Comment; 4 | pub use super::post::Entity as Post; 5 | pub use super::user::Entity as User; 6 | -------------------------------------------------------------------------------- /entity/src/user.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.2 2 | 3 | use sea_orm::entity::prelude::*; 4 | 5 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] 6 | #[sea_orm(table_name = "user")] 7 | pub struct Model { 8 | #[sea_orm(primary_key)] 9 | pub id: i32, 10 | pub name: String, 11 | #[sea_orm(unique)] 12 | pub email: String, 13 | pub password: String, 14 | #[sea_orm(unique)] 15 | pub uuid: Uuid, 16 | pub created_at: DateTime, 17 | } 18 | 19 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 20 | pub enum Relation { 21 | #[sea_orm(has_many = "super::comment::Entity")] 22 | Comment, 23 | #[sea_orm(has_many = "super::post::Entity")] 24 | Post, 25 | } 26 | 27 | impl Related for Entity { 28 | fn to() -> RelationDef { 29 | Relation::Comment.def() 30 | } 31 | } 32 | 33 | impl Related for Entity { 34 | fn to() -> RelationDef { 35 | Relation::Post.def() 36 | } 37 | } 38 | 39 | impl ActiveModelBehavior for ActiveModel {} 40 | -------------------------------------------------------------------------------- /migration/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "migration" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [lib] 8 | name = "migration" 9 | path = "src/lib.rs" 10 | 11 | [dependencies] 12 | async-std = { version = "1", features = ["attributes", "tokio1"] } 13 | 14 | [dependencies.sea-orm-migration] 15 | version = "0.12.0" 16 | features = [ 17 | # Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run migration via CLI. 18 | # View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime. 19 | # e.g. 20 | "runtime-tokio-rustls", # `ASYNC_RUNTIME` feature 21 | "sqlx-postgres", # `DATABASE_DRIVER` feature 22 | ] 23 | -------------------------------------------------------------------------------- /migration/README.md: -------------------------------------------------------------------------------- 1 | # Running Migrator CLI 2 | 3 | - Generate a new migration file 4 | ```sh 5 | cargo run -- generate MIGRATION_NAME 6 | ``` 7 | - Apply all pending migrations 8 | ```sh 9 | cargo run 10 | ``` 11 | ```sh 12 | cargo run -- up 13 | ``` 14 | - Apply first 10 pending migrations 15 | ```sh 16 | cargo run -- up -n 10 17 | ``` 18 | - Rollback last applied migrations 19 | ```sh 20 | cargo run -- down 21 | ``` 22 | - Rollback last 10 applied migrations 23 | ```sh 24 | cargo run -- down -n 10 25 | ``` 26 | - Drop all tables from the database, then reapply all migrations 27 | ```sh 28 | cargo run -- fresh 29 | ``` 30 | - Rollback all applied migrations, then reapply all migrations 31 | ```sh 32 | cargo run -- refresh 33 | ``` 34 | - Rollback all applied migrations 35 | ```sh 36 | cargo run -- reset 37 | ``` 38 | - Check the status of all migrations 39 | ```sh 40 | cargo run -- status 41 | ``` 42 | -------------------------------------------------------------------------------- /migration/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use sea_orm_migration::prelude::*; 2 | 3 | mod m20220101_000001_create_table; 4 | mod m20231009_234608_create_posts_table; 5 | mod m20231009_234627_create_comments_table; 6 | 7 | 8 | pub struct Migrator; 9 | 10 | #[async_trait::async_trait] 11 | impl MigratorTrait for Migrator { 12 | fn migrations() -> Vec> { 13 | vec![ 14 | Box::new(m20220101_000001_create_table::Migration), 15 | Box::new(m20231009_234608_create_posts_table::Migration), 16 | Box::new(m20231009_234627_create_comments_table::Migration), 17 | 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /migration/src/m20220101_000001_create_table.rs: -------------------------------------------------------------------------------- 1 | use sea_orm_migration::prelude::*; 2 | 3 | #[derive(DeriveMigrationName)] 4 | pub struct Migration; 5 | 6 | #[async_trait::async_trait] 7 | impl MigrationTrait for Migration { 8 | async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { 9 | manager 10 | .create_table( 11 | Table::create() 12 | .table(User::Table) 13 | .if_not_exists() 14 | .col( 15 | ColumnDef::new(User::Id) 16 | .integer() 17 | .not_null() 18 | .auto_increment() 19 | .primary_key(), 20 | ) 21 | .col(ColumnDef::new(User::Name).string().not_null()) 22 | .col(ColumnDef::new(User::Email).string().unique_key().not_null()) 23 | .col(ColumnDef::new(User::Password).string().not_null()) 24 | .col(ColumnDef::new(User::Uuid).uuid().unique_key().not_null()) 25 | .col(ColumnDef::new(User::CreatedAt).date_time().not_null()) 26 | .to_owned(), 27 | ) 28 | .await 29 | } 30 | 31 | async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { 32 | 33 | manager 34 | .drop_table(Table::drop().table(User::Table).to_owned()) 35 | .await 36 | } 37 | } 38 | 39 | #[derive(DeriveIden)] 40 | pub enum User { 41 | Table, 42 | Id, 43 | Name, 44 | Email, 45 | Password, 46 | Uuid, 47 | CreatedAt 48 | } 49 | -------------------------------------------------------------------------------- /migration/src/m20231009_234608_create_posts_table.rs: -------------------------------------------------------------------------------- 1 | use sea_orm_migration::prelude::*; 2 | 3 | use crate::m20220101_000001_create_table::User; 4 | 5 | #[derive(DeriveMigrationName)] 6 | pub struct Migration; 7 | 8 | #[async_trait::async_trait] 9 | impl MigrationTrait for Migration { 10 | async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { 11 | 12 | manager 13 | .create_table( 14 | Table::create() 15 | .table(Post::Table) 16 | .if_not_exists() 17 | .col( 18 | ColumnDef::new(Post::Id) 19 | .integer() 20 | .not_null() 21 | .auto_increment() 22 | .primary_key(), 23 | ) 24 | .col(ColumnDef::new(Post::Uuid).uuid().unique_key().not_null()) 25 | .col(ColumnDef::new(Post::Title).string().not_null()) 26 | .col(ColumnDef::new(Post::Text).string().not_null()) 27 | .col(ColumnDef::new(Post::Image).string().not_null()) 28 | .col(ColumnDef::new(Post::CreatedAt).date_time().not_null()) 29 | .col(ColumnDef::new(Post::UserId).integer().not_null()) 30 | .foreign_key(ForeignKey::create().name("fk-posts-users-id").from(Post::Table,Post::UserId).to(User::Table, User::Id)) 31 | .to_owned(), 32 | ) 33 | .await 34 | } 35 | 36 | async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { 37 | 38 | manager 39 | .drop_table(Table::drop().table(Post::Table).to_owned()) 40 | .await 41 | } 42 | } 43 | 44 | #[derive(DeriveIden)] 45 | pub enum Post { 46 | Table, 47 | Id, 48 | Title, 49 | Uuid, 50 | Text, 51 | Image, 52 | UserId, 53 | CreatedAt 54 | } 55 | -------------------------------------------------------------------------------- /migration/src/m20231009_234627_create_comments_table.rs: -------------------------------------------------------------------------------- 1 | use sea_orm_migration::prelude::*; 2 | 3 | use crate::{m20220101_000001_create_table::User, m20231009_234608_create_posts_table::Post}; 4 | 5 | #[derive(DeriveMigrationName)] 6 | pub struct Migration; 7 | 8 | #[async_trait::async_trait] 9 | impl MigrationTrait for Migration { 10 | async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { 11 | 12 | manager 13 | .create_table( 14 | Table::create() 15 | .table(Comment::Table) 16 | .if_not_exists() 17 | .col( 18 | ColumnDef::new(Comment::Id) 19 | .integer() 20 | .not_null() 21 | .auto_increment() 22 | .primary_key(), 23 | ) 24 | .col(ColumnDef::new(Comment::UserId).integer().not_null()) 25 | .col(ColumnDef::new(Comment::Text).string().not_null()) 26 | .col(ColumnDef::new(Comment::PostId).integer().not_null()) 27 | .col(ColumnDef::new(Comment::CreatedAt).date_time().not_null()) 28 | 29 | 30 | .foreign_key(ForeignKey::create().name("fk-comments-user-id") 31 | .from(Comment::Table,Comment::UserId).to(User::Table, User::Id)) 32 | 33 | 34 | .foreign_key(ForeignKey::create().name("fk-comments-post-id") 35 | .from(Comment::Table,Comment::PostId).to(Post::Table, Post::Id)) 36 | .to_owned(), 37 | ) 38 | .await 39 | } 40 | 41 | async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { 42 | 43 | manager 44 | .drop_table(Table::drop().table(Comment::Table).to_owned()) 45 | .await 46 | } 47 | } 48 | 49 | #[derive(DeriveIden)] 50 | pub enum Comment { 51 | Table, 52 | Id, 53 | UserId, 54 | PostId, 55 | CreatedAt, 56 | Text 57 | } 58 | -------------------------------------------------------------------------------- /migration/src/main.rs: -------------------------------------------------------------------------------- 1 | use sea_orm_migration::prelude::*; 2 | 3 | #[async_std::main] 4 | async fn main() { 5 | cli::run_cli(migration::Migrator).await; 6 | } 7 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | @TheOptiCode 7 | 8 | 9 | Hello WOrld 10 | 11 | -------------------------------------------------------------------------------- /public/uploads/1699317162.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trasherr/Blogging-API/79a5ad7ddbb78dc64e00bab5b68bf7d55c6bdd13/public/uploads/1699317162.png -------------------------------------------------------------------------------- /src/handlers/auth_handlers.rs: -------------------------------------------------------------------------------- 1 | use axum::{Json, response::IntoResponse, http::StatusCode, Extension}; 2 | use chrono::Utc; 3 | use sea_orm::{DatabaseConnection, Set, ActiveModelTrait, EntityTrait, Condition, ColumnTrait, QueryFilter}; 4 | use uuid::Uuid; 5 | 6 | use crate::{models::user_models::{CreateUserModel, LoginUserModel, UserModel, LoginUserResponseModel}, utils::{api_error::APIError, jwt::encode_jwt}}; 7 | 8 | 9 | pub async fn create_user_post( 10 | Extension(db): Extension, 11 | Json(user_data): Json 12 | ) -> Result<(),APIError> { 13 | 14 | let user = entity::user::Entity::find() 15 | .filter(entity::user::Column::Email.eq(user_data.email.clone())) 16 | .one(&db).await 17 | .map_err(|err| APIError { message: err.to_string(), status_code:StatusCode::INTERNAL_SERVER_ERROR, error_code: Some(50)})?; 18 | 19 | if user != None { 20 | return Err(APIError { message: "User exists".to_owned(), status_code:StatusCode::CONFLICT, error_code: Some(40) }); 21 | } 22 | 23 | let user_model = entity::user::ActiveModel{ 24 | name: Set(user_data.name.to_owned()), 25 | email: Set(user_data.email.to_owned()), 26 | password: Set(user_data.password.to_owned()), 27 | uuid: Set(Uuid::new_v4()), 28 | created_at: Set(Utc::now().naive_utc()) , 29 | ..Default::default() 30 | }; 31 | user_model.insert(&db).await 32 | .map_err(|err| APIError { message: err.to_string(), status_code:StatusCode::INTERNAL_SERVER_ERROR, error_code: Some(50)})?; 33 | 34 | Ok(()) 35 | 36 | } 37 | 38 | pub async fn login_user_post( 39 | Extension(db): Extension, 40 | Json(user_data): Json 41 | ) -> Result,APIError> { 42 | 43 | let user = entity::user::Entity::find() 44 | .filter( 45 | Condition::all() 46 | .add(entity::user::Column::Email.eq(user_data.email)) 47 | .add(entity::user::Column::Password.eq(user_data.password)) 48 | ).one(&db) 49 | .await 50 | .map_err(|err| APIError { message: err.to_string(), status_code:StatusCode::INTERNAL_SERVER_ERROR, error_code: Some(50)})? 51 | .ok_or(APIError { message: "Not Found".to_owned(), status_code: StatusCode::NOT_FOUND, error_code: Some(44) })?; 52 | 53 | let token = encode_jwt(user.email) 54 | .map_err(|_| APIError { message: "Failed to login".to_owned(), status_code: StatusCode::UNAUTHORIZED, error_code: Some(41) })?; 55 | 56 | Ok(Json(LoginUserResponseModel { token })) 57 | 58 | } -------------------------------------------------------------------------------- /src/handlers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod auth_handlers; 2 | pub mod user_handler; 3 | pub mod post_handler; -------------------------------------------------------------------------------- /src/handlers/post_handler.rs: -------------------------------------------------------------------------------- 1 | use axum::{Extension, Json, http::StatusCode, extract::{Path, Multipart}}; 2 | use chrono::Utc; 3 | use sea_orm::{DatabaseConnection, Set, ActiveModelTrait, EntityTrait, QueryFilter, ColumnTrait, Condition, IntoActiveModel}; 4 | use tokio::{fs::File, io::AsyncWriteExt}; 5 | use uuid::Uuid; 6 | 7 | use crate::{utils::api_error::APIError, models::post_models::{CreatePostModel, PostModel}}; 8 | 9 | use std::io::BufWriter; 10 | use std::num::NonZeroU32; 11 | 12 | use image::codecs::png::PngEncoder; 13 | use image::io::Reader as ImageReader; 14 | use image::{ColorType, ImageEncoder}; 15 | 16 | use fast_image_resize as fr; 17 | 18 | pub async fn upload_image_post( 19 | Extension(db): Extension, 20 | Extension(identity): Extension, 21 | Path(uuid): Path, 22 | mut multipart: Multipart 23 | ) -> Result<(),APIError>{ 24 | 25 | while let Some(field) = multipart.next_field().await.unwrap(){ 26 | 27 | let field_name = field.name().unwrap().to_string(); 28 | 29 | if field_name == "image" { 30 | 31 | let mut post = entity::post::Entity::find().filter( 32 | Condition::all() 33 | .add(entity::post::Column::Uuid.eq(uuid)) 34 | .add(entity::post::Column::UserId.eq(identity.id)) 35 | ).one(&db) 36 | .await.unwrap().unwrap().into_active_model(); 37 | 38 | let img_name: i64 = Utc::now().timestamp(); 39 | let data = field.bytes().await.unwrap(); 40 | 41 | 42 | 43 | // Read source image from file 44 | let img = ImageReader::new(std::io::Cursor::new(data)) 45 | .with_guessed_format() 46 | .unwrap() 47 | .decode() 48 | .unwrap(); 49 | let width = NonZeroU32::new(img.width()).unwrap(); 50 | let height = NonZeroU32::new(img.height()).unwrap(); 51 | let mut src_image = fr::Image::from_vec_u8( 52 | width, 53 | height, 54 | img.to_rgba8().into_raw(), 55 | fr::PixelType::U8x4, 56 | ).unwrap(); 57 | 58 | // Multiple RGB channels of source image by alpha channel 59 | // (not required for the Nearest algorithm) 60 | let alpha_mul_div = fr::MulDiv::default(); 61 | alpha_mul_div 62 | .multiply_alpha_inplace(&mut src_image.view_mut()) 63 | .unwrap(); 64 | 65 | // Create container for data of destination image 66 | let dst_width = NonZeroU32::new(480).unwrap(); 67 | let dst_height = NonZeroU32::new(360).unwrap(); 68 | let mut dst_image = fr::Image::new( 69 | dst_width, 70 | dst_height, 71 | src_image.pixel_type(), 72 | ); 73 | 74 | // Get mutable view of destination image data 75 | let mut dst_view = dst_image.view_mut(); 76 | 77 | // Create Resizer instance and resize source image 78 | // into buffer of destination image 79 | let mut resizer = fr::Resizer::new( 80 | fr::ResizeAlg::Convolution(fr::FilterType::Lanczos3), 81 | ); 82 | resizer.resize(&src_image.view(), &mut dst_view).unwrap(); 83 | 84 | // Divide RGB channels of destination image by alpha 85 | alpha_mul_div.divide_alpha_inplace(&mut dst_view).unwrap(); 86 | 87 | // Write destination image as PNG-file 88 | let mut result_buf = BufWriter::new(Vec::new()); 89 | PngEncoder::new(&mut result_buf) 90 | .write_image( 91 | dst_image.buffer(), 92 | dst_width.get(), 93 | dst_height.get(), 94 | ColorType::Rgba8, 95 | ) 96 | .unwrap(); 97 | 98 | let image_bytes = result_buf.into_inner().unwrap(); 99 | 100 | 101 | let mut file = File::create(format!("./public/uploads/{}.png",img_name)).await.unwrap(); 102 | file.write(&image_bytes).await.unwrap(); 103 | 104 | post.image = Set(format!("/uploads/{}.png",img_name)); 105 | post.update(&db).await.unwrap(); 106 | println!("/uploads/{}.png",img_name); 107 | 108 | } 109 | 110 | else{ 111 | let data = field.text().await.unwrap(); 112 | println!("field: {} value: {}",field_name,data); 113 | } 114 | 115 | } 116 | 117 | 118 | Ok(()) 119 | } 120 | 121 | pub async fn create_post_post( 122 | Extension(db): Extension, 123 | Extension(identity): Extension, 124 | Json(post_data): Json, 125 | 126 | ) -> Result<(),APIError> { 127 | 128 | let post_entity = entity::post::ActiveModel { 129 | title: Set(post_data.title), 130 | text: Set(post_data.text), 131 | image: Set(post_data.image), 132 | created_at: Set(Utc::now().naive_local()), 133 | user_id: Set(identity.id), 134 | uuid: Set(Uuid::new_v4()), 135 | ..Default::default() 136 | }; 137 | 138 | post_entity.insert(&db) 139 | .await 140 | .map_err(|_| APIError { message: "Failed to insert".to_owned(), status_code: StatusCode::INTERNAL_SERVER_ERROR, error_code: Some(50) })?; 141 | 142 | Ok(()) 143 | } 144 | 145 | pub async fn get_post_get( 146 | Extension(db): Extension, 147 | Path(uuid): Path 148 | ) -> Result,APIError> { 149 | 150 | let post: PostModel = entity::post::Entity::find() 151 | .filter(entity::post::Column::Uuid.eq(uuid)) 152 | .find_also_related(entity::user::Entity) 153 | .one(&db) 154 | .await 155 | .map_err(|err| APIError { message: err.to_string(), status_code:StatusCode::INTERNAL_SERVER_ERROR, error_code: Some(50)})? 156 | .ok_or(APIError { message: "Not Found".to_owned(), status_code: StatusCode::NOT_FOUND, error_code: Some(44) })? 157 | .into(); 158 | 159 | Ok(Json(post)) 160 | } -------------------------------------------------------------------------------- /src/handlers/user_handler.rs: -------------------------------------------------------------------------------- 1 | use axum::{Json, response::IntoResponse, http::StatusCode, extract::Path, Extension}; 2 | use sea_orm::{DatabaseConnection, Set, ActiveModelTrait, EntityTrait, ColumnTrait, QueryFilter}; 3 | use uuid::Uuid; 4 | 5 | use crate::{models::user_models::{UpdateUserModel, UserModel}, utils::api_error::APIError}; 6 | 7 | 8 | pub async fn update_user_put( 9 | Extension(db): Extension, 10 | Path(uuid): Path, 11 | Json(user_data): Json 12 | ) -> Result<(),APIError> { 13 | 14 | 15 | let mut user: entity::user::ActiveModel = entity::user::Entity::find() 16 | .filter(entity::user::Column::Uuid.eq(uuid)) 17 | .one(&db) 18 | .await 19 | .map_err(|err| APIError { message: err.to_string(), status_code:StatusCode::INTERNAL_SERVER_ERROR, error_code: Some(50)})? 20 | .ok_or(APIError { message: "Not Found".to_owned(), status_code: StatusCode::NOT_FOUND, error_code: Some(44) })? 21 | .into(); 22 | 23 | user.name = Set(user_data.name); 24 | 25 | user.update(&db).await 26 | .map_err(|err| APIError { message: err.to_string(), status_code:StatusCode::INTERNAL_SERVER_ERROR, error_code: Some(50)})?; 27 | 28 | Ok(()) 29 | 30 | } 31 | 32 | pub async fn delete_user_delete( 33 | Extension(db): Extension, 34 | Path(uuid): Path 35 | )-> Result<(),APIError> { 36 | 37 | let user = entity::user::Entity::find() 38 | .filter(entity::user::Column::Uuid.eq(uuid)) 39 | .one(&db).await 40 | .map_err(|err| APIError { message: err.to_string(), status_code:StatusCode::INTERNAL_SERVER_ERROR, error_code: Some(50)})? 41 | .ok_or(APIError { message: "Not Found".to_owned(), status_code: StatusCode::NOT_FOUND, error_code: Some(44) })?; 42 | 43 | entity::user::Entity::delete_by_id(user.id) 44 | .exec(&db) 45 | .await 46 | .map_err(|err| APIError { message: err.to_string(), status_code:StatusCode::INTERNAL_SERVER_ERROR, error_code: Some(50)})?; 47 | 48 | Ok(()) 49 | } 50 | 51 | pub async fn all_user_get( 52 | Extension(db): Extension 53 | )-> Result>,APIError>{ 54 | 55 | let users: Vec = entity::user::Entity::find().all(&db).await 56 | .map_err(|err| APIError { message: err.to_string(), status_code:StatusCode::INTERNAL_SERVER_ERROR, error_code: Some(50)})? 57 | 58 | .into_iter().map(|item| UserModel{ 59 | name: item.name, 60 | email: item.email, 61 | uuid: item.uuid, 62 | created_at: item.created_at, 63 | }).collect(); 64 | 65 | Ok(Json(users)) 66 | 67 | } -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use axum::{middleware, routing::get, Extension, Router} ; 2 | use axum_prometheus::PrometheusMetricLayer; 3 | use sea_orm::Database; 4 | use tower_http::services::ServeDir; 5 | 6 | 7 | mod models; 8 | mod routes; 9 | mod handlers; 10 | mod utils; 11 | 12 | #[tokio::main] 13 | async fn main() { 14 | 15 | server().await; 16 | } 17 | 18 | async fn server(){ 19 | 20 | let conn_str = (*utils::constants::DATABASE_URL).clone(); 21 | let db = Database::connect(conn_str).await.expect("Failed to connect to db"); 22 | let (prometheus_layer, metric_handle) = PrometheusMetricLayer::pair(); 23 | 24 | let app: Router = Router::new() 25 | .merge(routes::user_routes::user_routes()) 26 | .route_layer(middleware::from_fn(utils::guards::guard)) 27 | .merge(routes::auth_routes::auth_routes()) 28 | .merge(routes::home_routes::home_routes()) 29 | .layer(Extension(db)) 30 | .nest_service("/", ServeDir::new("public")) 31 | .route("/metrics", get(|| async move { metric_handle.render() })) 32 | .layer(prometheus_layer); 33 | 34 | axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()) 35 | .serve(app.into_make_service()) 36 | .await 37 | .unwrap() 38 | } 39 | -------------------------------------------------------------------------------- /src/models/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod post_models; 2 | pub mod user_models; -------------------------------------------------------------------------------- /src/models/post_models.rs: -------------------------------------------------------------------------------- 1 | use serde::{Serialize, Deserialize}; 2 | use uuid::Uuid; 3 | 4 | use super::user_models::UserMicroModel; 5 | 6 | 7 | #[derive(Serialize,Deserialize,Default)] 8 | 9 | pub struct PostModel { 10 | pub uuid: Uuid, 11 | pub text: String, 12 | pub image: String, 13 | pub title: String, 14 | pub user: Option 15 | } 16 | 17 | #[derive(Serialize,Deserialize)] 18 | pub struct CreatePostModel { 19 | pub text: String, 20 | pub image: String, 21 | pub title: String 22 | } 23 | 24 | impl From<(entity::post::Model,Option)> for PostModel { 25 | fn from(value: (entity::post::Model,Option)) -> Self { 26 | let u = value.1.unwrap(); 27 | Self { 28 | uuid: value.0.uuid, 29 | text: value.0.text, 30 | image: value.0.image, 31 | title: value.0.title, 32 | user: Some(UserMicroModel { name: u.name, uuid: u.uuid }), 33 | ..Default::default() 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/models/user_models.rs: -------------------------------------------------------------------------------- 1 | use chrono::NaiveDateTime; 2 | use uuid::Uuid; 3 | use serde::{Serialize, Deserialize}; 4 | 5 | #[derive(Serialize,Deserialize,Clone)] 6 | pub struct UserModel{ 7 | pub name: String, 8 | pub email: String, 9 | pub uuid: Uuid, 10 | pub created_at: NaiveDateTime 11 | } 12 | 13 | #[derive(Serialize,Deserialize,Clone)] 14 | pub struct UserMicroModel{ 15 | pub name: String, 16 | pub uuid: Uuid, 17 | } 18 | 19 | #[derive(Serialize,Deserialize)] 20 | pub struct CreateUserModel{ 21 | pub name: String, 22 | pub email: String, 23 | pub password: String 24 | } 25 | 26 | #[derive(Serialize,Deserialize)] 27 | pub struct LoginUserModel{ 28 | pub email: String, 29 | pub password: String 30 | } 31 | 32 | #[derive(Serialize,Deserialize)] 33 | pub struct LoginUserResponseModel{ 34 | pub token: String 35 | } 36 | 37 | #[derive(Serialize,Deserialize)] 38 | pub struct UpdateUserModel{ 39 | pub name: String 40 | } 41 | -------------------------------------------------------------------------------- /src/routes/auth_routes.rs: -------------------------------------------------------------------------------- 1 | use axum::{Router, http::Method, routing::post}; 2 | use tower_http::cors::{CorsLayer, Any}; 3 | use crate::handlers::auth_handlers; 4 | 5 | 6 | pub fn auth_routes() -> Router { 7 | 8 | let cors = CorsLayer::new() 9 | .allow_methods([Method::POST]) 10 | .allow_origin(Any); 11 | 12 | let router = Router::new() 13 | .route("/api/user/register",post(auth_handlers::create_user_post)) 14 | .route("/api/user/login",post(auth_handlers::login_user_post)) 15 | .layer(cors); 16 | router 17 | } -------------------------------------------------------------------------------- /src/routes/home_routes.rs: -------------------------------------------------------------------------------- 1 | use axum::routing::get; 2 | use axum::{Router, http::Method}; 3 | use tower_http::cors::{CorsLayer, Any}; 4 | use crate::handlers::post_handler; 5 | 6 | pub fn home_routes() -> Router { 7 | 8 | let cors = CorsLayer::new() 9 | .allow_methods([Method::GET]) 10 | .allow_origin(Any); 11 | 12 | let router = Router::new() 13 | .route("/api/post/:uuid",get(post_handler::get_post_get)) 14 | .layer(cors); 15 | router 16 | } 17 | -------------------------------------------------------------------------------- /src/routes/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod auth_routes; 2 | pub mod user_routes; 3 | pub mod home_routes; -------------------------------------------------------------------------------- /src/routes/user_routes.rs: -------------------------------------------------------------------------------- 1 | use axum::routing::{delete, put, get, post}; 2 | use axum::{Router, http::Method}; 3 | use tower_http::cors::{CorsLayer, Any}; 4 | use crate::handlers::{user_handler, post_handler}; 5 | 6 | pub fn user_routes() -> Router { 7 | 8 | let cors = CorsLayer::new() 9 | .allow_methods([Method::GET, Method::POST, Method::PUT, Method::DELETE]) 10 | .allow_origin(Any); 11 | 12 | let router = Router::new() 13 | .route("/api/user/:uuid/update",put(user_handler::update_user_put)) 14 | .route("/api/user/:uuid/delete",delete(user_handler::delete_user_delete)) 15 | .route("/api/user/all",get(user_handler::all_user_get)) 16 | .route("/api/user/post",post(post_handler::create_post_post)) 17 | .route("/api/user/post/:uuid/image",post(post_handler::upload_image_post)) 18 | .layer(cors); 19 | router 20 | } -------------------------------------------------------------------------------- /src/utils/api_error.rs: -------------------------------------------------------------------------------- 1 | use axum::{http::{StatusCode, header}, response::IntoResponse, Json}; 2 | use serde_json::json; 3 | 4 | 5 | #[derive(Debug)] 6 | pub struct APIError{ 7 | pub message: String, 8 | pub status_code: StatusCode, 9 | pub error_code: Option 10 | } 11 | 12 | impl IntoResponse for APIError{ 13 | 14 | fn into_response(self) -> axum::response::Response { 15 | let status_code = self.status_code; 16 | (status_code,[(header::CONTENT_TYPE,"application/json")], Json(json!({ "StatusCode": self.status_code.as_u16(),"ErrorCode": self.error_code,"Message": self.message })) ).into_response() 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/utils/constants.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use dotenv::dotenv; 4 | use lazy_static::lazy_static; 5 | 6 | lazy_static!{ 7 | pub static ref DATABASE_URL: String = set_database(); 8 | pub static ref TOKEN: String = set_token(); 9 | } 10 | 11 | fn set_database() -> String{ 12 | dotenv().ok(); 13 | env::var("DATABASE_URL").unwrap() 14 | } 15 | 16 | 17 | fn set_token() -> String{ 18 | dotenv().ok(); 19 | env::var("TOKEN").unwrap() 20 | } 21 | -------------------------------------------------------------------------------- /src/utils/guards.rs: -------------------------------------------------------------------------------- 1 | use axum::{http::{Request, StatusCode}, middleware::Next, response::Response, headers::{HeaderMapExt, Authorization, authorization::Bearer}}; 2 | use sea_orm::{DatabaseConnection, EntityTrait, QueryFilter, ColumnTrait}; 3 | 4 | use super::{api_error::APIError, jwt::decode_jwt}; 5 | 6 | 7 | 8 | 9 | 10 | pub async fn guard(mut req: Request, next: Next) -> Result { 11 | 12 | let token = req.headers().typed_get::>() 13 | .ok_or(APIError { message: "No Auth token found".to_owned(), status_code: StatusCode::BAD_REQUEST, error_code: Some(40) })?.token().to_owned(); 14 | 15 | let claim = decode_jwt(token) 16 | .map_err(|err| APIError { message: "Unauthorized".to_owned(), status_code: StatusCode::UNAUTHORIZED, error_code: Some(41) })?.claims; 17 | 18 | let db = req.extensions().get::() 19 | .ok_or(APIError { message: "Could not connect to database".to_owned(), status_code: StatusCode::INTERNAL_SERVER_ERROR, error_code: Some(50) })?; 20 | 21 | let identity = entity::user::Entity::find() 22 | .filter(entity::user::Column::Email.eq(claim.email.to_lowercase())) 23 | .one(db) 24 | .await.map_err(|err| APIError { message: err.to_string(), status_code: StatusCode::INTERNAL_SERVER_ERROR, error_code:Some(50)})? 25 | .ok_or(APIError { message: "Unauthorized".to_owned(), status_code: StatusCode::UNAUTHORIZED, error_code: Some(41) })?; 26 | 27 | req.extensions_mut().insert(identity); 28 | 29 | Ok(next.run(req).await) 30 | } -------------------------------------------------------------------------------- /src/utils/jwt.rs: -------------------------------------------------------------------------------- 1 | use axum::http::StatusCode; 2 | use chrono::{Utc, Duration}; 3 | use jsonwebtoken::{encode, Header, EncodingKey, TokenData, decode, DecodingKey, Validation}; 4 | use serde::{Deserialize, Serialize}; 5 | use crate::utils; 6 | 7 | 8 | 9 | 10 | #[derive(Serialize,Deserialize)] 11 | pub struct Cliams{ 12 | pub exp: usize, 13 | pub iat: usize, 14 | pub email: String 15 | } 16 | 17 | 18 | pub fn encode_jwt(email: String) -> Result{ 19 | 20 | let now = Utc::now(); 21 | let expire = Duration::hours(24); 22 | 23 | let claim = Cliams{ iat: now.timestamp() as usize, exp: (now+expire).timestamp() as usize, email: email }; 24 | let secret = (*utils::constants::TOKEN).clone(); 25 | 26 | return encode(&Header::default(), &claim, &EncodingKey::from_secret(secret.as_ref())) 27 | .map_err(|_| { StatusCode::INTERNAL_SERVER_ERROR }); 28 | 29 | } 30 | 31 | 32 | pub fn decode_jwt(jwt: String) -> Result,StatusCode> { 33 | let secret = (*utils::constants::TOKEN).clone(); 34 | let res: Result, StatusCode> = decode(&jwt,&DecodingKey::from_secret(secret.as_ref()),&Validation::default()) 35 | .map_err(|_| { StatusCode::INTERNAL_SERVER_ERROR }); 36 | return res; 37 | } -------------------------------------------------------------------------------- /src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod constants; 2 | pub mod api_error; 3 | pub mod jwt; 4 | pub mod guards; --------------------------------------------------------------------------------