Skip to content

Commit

Permalink
Update Actix3 example
Browse files Browse the repository at this point in the history
  • Loading branch information
shpun817 committed Sep 7, 2022
1 parent cfb83be commit ef1132c
Show file tree
Hide file tree
Showing 19 changed files with 490 additions and 251 deletions.
27 changes: 2 additions & 25 deletions examples/actix3_example/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,7 @@ edition = "2021"
publish = false

[workspace]
members = [".", "entity", "migration"]
members = [".", "api", "core", "entity", "migration"]

[dependencies]
actix-http = "2"
actix-web = "3"
actix-flash = "0.2"
actix-files = "0.5"
futures = { version = "^0.3" }
futures-util = { version = "^0.3" }
tera = "1.8.0"
dotenv = "0.15"
listenfd = "0.3.3"
serde = "1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
entity = { path = "entity" }
migration = { path = "migration" }

[dependencies.sea-orm]
path = "../../" # remove this line in your own project
version = "^0.9.0" # sea-orm version
features = [
"debug-print",
"runtime-async-std-native-tls",
"sqlx-mysql",
# "sqlx-postgres",
# "sqlx-sqlite",
]
actix3-example-api = { path = "api" }
22 changes: 22 additions & 0 deletions examples/actix3_example/api/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "actix3-example-api"
version = "0.1.0"
authors = ["Sam Samai <[email protected]>"]
edition = "2021"
publish = false

[dependencies]
actix3-example-core = { path = "../core" }
actix-http = "2"
actix-web = "3"
actix-flash = "0.2"
actix-files = "0.5"
futures = { version = "^0.3" }
futures-util = { version = "^0.3" }
tera = "1.8.0"
dotenv = "0.15"
listenfd = "0.3.3"
serde = "1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
entity = { path = "../entity" }
migration = { path = "../migration" }
219 changes: 219 additions & 0 deletions examples/actix3_example/api/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
use actix3_example_core::{
sea_orm::{Database, DatabaseConnection},
Mutation, Query,
};
use actix_files as fs;
use actix_web::{
error, get, middleware, post, web, App, Error, HttpRequest, HttpResponse, HttpServer, Result,
};

use entity::post;
use listenfd::ListenFd;
use migration::{Migrator, MigratorTrait};
use serde::{Deserialize, Serialize};
use std::env;
use tera::Tera;

const DEFAULT_POSTS_PER_PAGE: usize = 5;

#[derive(Debug, Clone)]
struct AppState {
templates: tera::Tera,
conn: DatabaseConnection,
}
#[derive(Debug, Deserialize)]
pub struct Params {
page: Option<usize>,
posts_per_page: Option<usize>,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
struct FlashData {
kind: String,
message: String,
}

#[get("/")]
async fn list(
req: HttpRequest,
data: web::Data<AppState>,
opt_flash: Option<actix_flash::Message<FlashData>>,
) -> Result<HttpResponse, Error> {
let template = &data.templates;
let conn = &data.conn;

// get params
let params = web::Query::<Params>::from_query(req.query_string()).unwrap();

let page = params.page.unwrap_or(1);
let posts_per_page = params.posts_per_page.unwrap_or(DEFAULT_POSTS_PER_PAGE);

let (posts, num_pages) = Query::find_posts_in_page(conn, page, posts_per_page)
.await
.expect("Cannot find posts in page");

let mut ctx = tera::Context::new();
ctx.insert("posts", &posts);
ctx.insert("page", &page);
ctx.insert("posts_per_page", &posts_per_page);
ctx.insert("num_pages", &num_pages);

if let Some(flash) = opt_flash {
let flash_inner = flash.into_inner();
ctx.insert("flash", &flash_inner);
}

let body = template
.render("index.html.tera", &ctx)
.map_err(|_| error::ErrorInternalServerError("Template error"))?;
Ok(HttpResponse::Ok().content_type("text/html").body(body))
}

#[get("/new")]
async fn new(data: web::Data<AppState>) -> Result<HttpResponse, Error> {
let template = &data.templates;
let ctx = tera::Context::new();
let body = template
.render("new.html.tera", &ctx)
.map_err(|_| error::ErrorInternalServerError("Template error"))?;
Ok(HttpResponse::Ok().content_type("text/html").body(body))
}

#[post("/")]
async fn create(
data: web::Data<AppState>,
post_form: web::Form<post::Model>,
) -> actix_flash::Response<HttpResponse, FlashData> {
let conn = &data.conn;

let form = post_form.into_inner();

Mutation::create_post(conn, form)
.await
.expect("could not insert post");

let flash = FlashData {
kind: "success".to_owned(),
message: "Post successfully added.".to_owned(),
};

actix_flash::Response::with_redirect(flash, "/")
}

#[get("/{id}")]
async fn edit(data: web::Data<AppState>, id: web::Path<i32>) -> Result<HttpResponse, Error> {
let conn = &data.conn;
let template = &data.templates;
let id = id.into_inner();

let post: post::Model = Query::find_post_by_id(conn, id)
.await
.expect("could not find post")
.unwrap_or_else(|| panic!("could not find post with id {}", id));

let mut ctx = tera::Context::new();
ctx.insert("post", &post);

let body = template
.render("edit.html.tera", &ctx)
.map_err(|_| error::ErrorInternalServerError("Template error"))?;
Ok(HttpResponse::Ok().content_type("text/html").body(body))
}

#[post("/{id}")]
async fn update(
data: web::Data<AppState>,
id: web::Path<i32>,
post_form: web::Form<post::Model>,
) -> actix_flash::Response<HttpResponse, FlashData> {
let conn = &data.conn;
let form = post_form.into_inner();
let id = id.into_inner();

Mutation::update_post_by_id(conn, id, form)
.await
.expect("could not edit post");

let flash = FlashData {
kind: "success".to_owned(),
message: "Post successfully updated.".to_owned(),
};

actix_flash::Response::with_redirect(flash, "/")
}

#[post("/delete/{id}")]
async fn delete(
data: web::Data<AppState>,
id: web::Path<i32>,
) -> actix_flash::Response<HttpResponse, FlashData> {
let conn = &data.conn;
let id = id.into_inner();

Mutation::delete_post(conn, id)
.await
.expect("could not delete post");

let flash = FlashData {
kind: "success".to_owned(),
message: "Post successfully deleted.".to_owned(),
};

actix_flash::Response::with_redirect(flash, "/")
}

#[actix_web::main]
async fn start() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "debug");
tracing_subscriber::fmt::init();

// get env vars
dotenv::dotenv().ok();
let db_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set in .env file");
let host = env::var("HOST").expect("HOST is not set in .env file");
let port = env::var("PORT").expect("PORT is not set in .env file");
let server_url = format!("{}:{}", host, port);

// create post table if not exists
let conn = Database::connect(&db_url).await.unwrap();
Migrator::up(&conn, None).await.unwrap();
let templates = Tera::new(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*")).unwrap();
let state = AppState { templates, conn };

let mut listenfd = ListenFd::from_env();
let mut server = HttpServer::new(move || {
App::new()
.data(state.clone())
.wrap(middleware::Logger::default()) // enable logger
.wrap(actix_flash::Flash::default())
.configure(init)
.service(fs::Files::new("/static", "./api/static").show_files_listing())
});

server = match listenfd.take_tcp_listener(0)? {
Some(listener) => server.listen(listener)?,
None => server.bind(&server_url)?,
};

println!("Starting server at {}", server_url);
server.run().await?;

Ok(())
}

fn init(cfg: &mut web::ServiceConfig) {
cfg.service(list);
cfg.service(new);
cfg.service(create);
cfg.service(edit);
cfg.service(update);
cfg.service(delete);
}

pub fn main() {
let result = start();

if let Some(err) = result.err() {
println!("Error: {}", err)
}
}
30 changes: 30 additions & 0 deletions examples/actix3_example/core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[package]
name = "actix3-example-core"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
entity = { path = "../entity" }

[dependencies.sea-orm]
path = "../../../" # remove this line in your own project
version = "^0.9.0" # sea-orm version
features = [
"debug-print",
"runtime-async-std-native-tls",
"sqlx-mysql",
# "sqlx-postgres",
# "sqlx-sqlite",
]

[dev-dependencies]
tokio = { version = "1.20.0", features = ["macros", "rt"] }

[features]
mock = ["sea-orm/mock"]

[[test]]
name = "mock"
required-features = ["mock"]
7 changes: 7 additions & 0 deletions examples/actix3_example/core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
mod mutation;
mod query;

pub use mutation::*;
pub use query::*;

pub use sea_orm;
53 changes: 53 additions & 0 deletions examples/actix3_example/core/src/mutation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use ::entity::{post, post::Entity as Post};
use sea_orm::*;

pub struct Mutation;

impl Mutation {
pub async fn create_post(
db: &DbConn,
form_data: post::Model,
) -> Result<post::ActiveModel, DbErr> {
post::ActiveModel {
title: Set(form_data.title.to_owned()),
text: Set(form_data.text.to_owned()),
..Default::default()
}
.save(db)
.await
}

pub async fn update_post_by_id(
db: &DbConn,
id: i32,
form_data: post::Model,
) -> Result<post::Model, DbErr> {
let post: post::ActiveModel = if let Ok(Some(post)) = Post::find_by_id(id).one(db).await {
post.into()
} else {
return Err(DbErr::Custom("Cannot find post.".to_owned()));
};

post::ActiveModel {
id: post.id,
title: Set(form_data.title.to_owned()),
text: Set(form_data.text.to_owned()),
}
.update(db)
.await
}

pub async fn delete_post(db: &DbConn, id: i32) -> Result<DeleteResult, DbErr> {
let post: post::ActiveModel = if let Ok(Some(post)) = Post::find_by_id(id).one(db).await {
post.into()
} else {
return Err(DbErr::Custom("Cannot find post.".to_owned()));
};

post.delete(db).await
}

pub async fn delete_all_posts(db: &DbConn) -> Result<DeleteResult, DbErr> {
Post::delete_many().exec(db).await
}
}
26 changes: 26 additions & 0 deletions examples/actix3_example/core/src/query.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use ::entity::{post, post::Entity as Post};
use sea_orm::*;

pub struct Query;

impl Query {
pub async fn find_post_by_id(db: &DbConn, id: i32) -> Result<Option<post::Model>, DbErr> {
Post::find_by_id(id).one(db).await
}

/// If ok, returns (post models, num pages).
pub async fn find_posts_in_page(
db: &DbConn,
page: usize,
posts_per_page: usize,
) -> Result<(Vec<post::Model>, usize), DbErr> {
// Setup paginator
let paginator = Post::find()
.order_by_asc(post::Column::Id)
.paginate(db, posts_per_page);
let num_pages = paginator.num_pages().await?;

// Fetch paginated posts
paginator.fetch_page(page - 1).await.map(|p| (p, num_pages))
}
}
Loading

0 comments on commit ef1132c

Please sign in to comment.