-
-
Notifications
You must be signed in to change notification settings - Fork 520
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #446 from sunli829/master
Add Poem example
- Loading branch information
Showing
18 changed files
with
1,355 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
HOST=127.0.0.1 | ||
PORT=8000 | ||
#DATABASE_URL="mysql://root:@localhost/poem_example" | ||
DATABASE_URL="sqlite::memory:" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
[package] | ||
name = "sea-orm-poem-example" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
[workspace] | ||
|
||
[dependencies] | ||
tokio = { version = "1.15.0", features = ["macros", "rt-multi-thread"] } | ||
poem = { version = "1.2.33", features = ["static-files"] } | ||
tracing-subscriber = { version = "0.3", features = ["env-filter"] } | ||
serde = { version = "1", features = ["derive"] } | ||
tera = "1.8.0" | ||
dotenv = "0.15" | ||
|
||
[dependencies.sea-orm] | ||
path = "../../" # remove this line in your own project | ||
version = "^0.5.0" | ||
features = ["macros", "runtime-tokio-native-tls", "debug-print"] | ||
default-features = false | ||
|
||
[features] | ||
default = ["sqlx-sqlite"] | ||
sqlx-mysql = ["sea-orm/sqlx-mysql"] | ||
sqlx-postgres = ["sea-orm/sqlx-postgres"] | ||
sqlx-sqlite = ["sea-orm/sqlx-sqlite"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# Poem with SeaORM example app | ||
|
||
Edit `Cargo.toml` to use `sqlx-mysql` or `sqlx-postgres`. | ||
|
||
```toml | ||
[features] | ||
default = ["sqlx-$DATABASE"] | ||
``` | ||
|
||
Edit `.env` to point to your database. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
use std::env; | ||
|
||
use poem::endpoint::StaticFilesEndpoint; | ||
use poem::error::{BadRequest, InternalServerError}; | ||
use poem::http::StatusCode; | ||
use poem::listener::TcpListener; | ||
use poem::web::{Data, Form, Html, Path, Query}; | ||
use poem::{get, handler, post, EndpointExt, Error, IntoResponse, Result, Route, Server}; | ||
use sea_orm::{entity::*, query::*, DatabaseConnection}; | ||
use serde::Deserialize; | ||
use tera::Tera; | ||
|
||
mod post; | ||
mod setup; | ||
|
||
const DEFAULT_POSTS_PER_PAGE: usize = 5; | ||
|
||
#[derive(Debug, Clone)] | ||
struct AppState { | ||
templates: tera::Tera, | ||
conn: DatabaseConnection, | ||
} | ||
|
||
#[derive(Deserialize)] | ||
struct Params { | ||
page: Option<usize>, | ||
posts_per_page: Option<usize>, | ||
} | ||
|
||
#[handler] | ||
async fn create(state: Data<&AppState>, form: Form<post::Model>) -> Result<impl IntoResponse> { | ||
post::ActiveModel { | ||
title: Set(form.title.to_owned()), | ||
text: Set(form.text.to_owned()), | ||
..Default::default() | ||
} | ||
.save(&state.conn) | ||
.await | ||
.map_err(InternalServerError)?; | ||
|
||
Ok(StatusCode::FOUND.with_header("location", "/")) | ||
} | ||
|
||
#[handler] | ||
async fn list(state: Data<&AppState>, Query(params): Query<Params>) -> Result<impl IntoResponse> { | ||
let page = params.page.unwrap_or(1); | ||
let posts_per_page = params.posts_per_page.unwrap_or(DEFAULT_POSTS_PER_PAGE); | ||
let paginator = post::Entity::find() | ||
.order_by_asc(post::Column::Id) | ||
.paginate(&state.conn, posts_per_page); | ||
let num_pages = paginator.num_pages().await.map_err(BadRequest)?; | ||
let posts = paginator | ||
.fetch_page(page - 1) | ||
.await | ||
.map_err(InternalServerError)?; | ||
|
||
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); | ||
|
||
let body = state | ||
.templates | ||
.render("index.html.tera", &ctx) | ||
.map_err(InternalServerError)?; | ||
Ok(Html(body)) | ||
} | ||
|
||
#[handler] | ||
async fn new(state: Data<&AppState>) -> Result<impl IntoResponse> { | ||
let ctx = tera::Context::new(); | ||
let body = state | ||
.templates | ||
.render("new.html.tera", &ctx) | ||
.map_err(InternalServerError)?; | ||
Ok(Html(body)) | ||
} | ||
|
||
#[handler] | ||
async fn edit(state: Data<&AppState>, Path(id): Path<i32>) -> Result<impl IntoResponse> { | ||
let post: post::Model = post::Entity::find_by_id(id) | ||
.one(&state.conn) | ||
.await | ||
.map_err(InternalServerError)? | ||
.ok_or_else(|| Error::from_status(StatusCode::NOT_FOUND))?; | ||
|
||
let mut ctx = tera::Context::new(); | ||
ctx.insert("post", &post); | ||
|
||
let body = state | ||
.templates | ||
.render("edit.html.tera", &ctx) | ||
.map_err(InternalServerError)?; | ||
Ok(Html(body)) | ||
} | ||
|
||
#[handler] | ||
async fn update( | ||
state: Data<&AppState>, | ||
Path(id): Path<i32>, | ||
form: Form<post::Model>, | ||
) -> Result<impl IntoResponse> { | ||
post::ActiveModel { | ||
id: Set(id), | ||
title: Set(form.title.to_owned()), | ||
text: Set(form.text.to_owned()), | ||
} | ||
.save(&state.conn) | ||
.await | ||
.map_err(InternalServerError)?; | ||
|
||
Ok(StatusCode::FOUND.with_header("location", "/")) | ||
} | ||
|
||
#[handler] | ||
async fn delete(state: Data<&AppState>, Path(id): Path<i32>) -> Result<impl IntoResponse> { | ||
let post: post::ActiveModel = post::Entity::find_by_id(id) | ||
.one(&state.conn) | ||
.await | ||
.map_err(InternalServerError)? | ||
.ok_or_else(|| Error::from_status(StatusCode::NOT_FOUND))? | ||
.into(); | ||
post.delete(&state.conn) | ||
.await | ||
.map_err(InternalServerError)?; | ||
|
||
Ok(StatusCode::FOUND.with_header("location", "/")) | ||
} | ||
|
||
#[tokio::main] | ||
async fn main() -> 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 = sea_orm::Database::connect(&db_url).await.unwrap(); | ||
let _ = setup::create_post_table(&conn).await; | ||
let templates = Tera::new(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*")).unwrap(); | ||
let state = AppState { templates, conn }; | ||
|
||
println!("Starting server at {}", server_url); | ||
|
||
let app = Route::new() | ||
.at("/", post(create).get(list)) | ||
.at("/new", new) | ||
.at("/:id", get(edit).post(update)) | ||
.at("/delete/:id", post(delete)) | ||
.nest( | ||
"/static", | ||
StaticFilesEndpoint::new(concat!(env!("CARGO_MANIFEST_DIR"), "/static")), | ||
) | ||
.data(state); | ||
let server = Server::new(TcpListener::bind(format!("{}:{}", host, port))); | ||
server.run(app).await | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
use sea_orm::entity::prelude::*; | ||
use serde::{Deserialize, Serialize}; | ||
|
||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize)] | ||
#[sea_orm(table_name = "posts")] | ||
pub struct Model { | ||
#[sea_orm(primary_key)] | ||
#[serde(skip_deserializing)] | ||
pub id: i32, | ||
pub title: String, | ||
#[sea_orm(column_type = "Text")] | ||
pub text: String, | ||
} | ||
|
||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] | ||
pub enum Relation {} | ||
|
||
impl ActiveModelBehavior for ActiveModel {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
use sea_orm::sea_query::{ColumnDef, TableCreateStatement}; | ||
use sea_orm::{error::*, sea_query, ConnectionTrait, DbConn, ExecResult}; | ||
|
||
async fn create_table(db: &DbConn, stmt: &TableCreateStatement) -> Result<ExecResult, DbErr> { | ||
let builder = db.get_database_backend(); | ||
db.execute(builder.build(stmt)).await | ||
} | ||
|
||
pub async fn create_post_table(db: &DbConn) -> Result<ExecResult, DbErr> { | ||
let stmt = sea_query::Table::create() | ||
.table(super::post::Entity) | ||
.if_not_exists() | ||
.col( | ||
ColumnDef::new(super::post::Column::Id) | ||
.integer() | ||
.not_null() | ||
.auto_increment() | ||
.primary_key(), | ||
) | ||
.col( | ||
ColumnDef::new(super::post::Column::Title) | ||
.string() | ||
.not_null(), | ||
) | ||
.col( | ||
ColumnDef::new(super::post::Column::Text) | ||
.string() | ||
.not_null(), | ||
) | ||
.to_owned(); | ||
|
||
create_table(db, &stmt).await | ||
} |
Oops, something went wrong.