Skip to content

Commit

Permalink
add seaorm example
Browse files Browse the repository at this point in the history
  • Loading branch information
X committed Sep 20, 2022
1 parent bbeb861 commit 56e450c
Show file tree
Hide file tree
Showing 23 changed files with 1,475 additions and 0 deletions.
4 changes: 4 additions & 0 deletions examples/seaorm/.env
@@ -0,0 +1,4 @@
HOST=127.0.0.1
PORT=8000
#DATABASE_URL="mysql://root:@localhost/poem_example"
DATABASE_URL="sqlite::memory:"
3 changes: 3 additions & 0 deletions examples/seaorm/.gitignore
@@ -0,0 +1,3 @@
.idea
*.exe
Cargo.lock
26 changes: 26 additions & 0 deletions examples/seaorm/Cargo.toml
@@ -0,0 +1,26 @@
[package]
name = "sea-orm-salvo-example"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[workspace]
members = [".", "entity", "migration"]

[dependencies]
tokio = { version = "1.21.1", features = ["macros", "rt-multi-thread"] }
salvo = { version = "0.35.1", features = ["affix", "serve-static"] }
tracing = "0.1.36"
tracing-subscriber = { version = "0.3.15", features = ["env-filter"] }
serde = { version = "1.0.144", features = ["derive"] }
tera = "1.17.1"
dotenv = "0.15"
entity = { path = "entity" }
migration = { path = "migration" }
sea-orm = { version = "0.9.2", features = [
"debug-print",
"runtime-tokio-native-tls",
"sqlx-sqlite",
# "sqlx-postgres",
# "sqlx-mysql",
] }
13 changes: 13 additions & 0 deletions examples/seaorm/README.md
@@ -0,0 +1,13 @@
![screenshot](Screenshot.png)

# Salvo with SeaORM example app

[Modify from (github.com/SeaQL/sea-orm/examples/salvo_example](https://github.com/SeaQL/sea-orm/tree/master/examples/salvo_example))

1. Modify the `DATABASE_URL` var in `.env` to point to your chosen database

2. Turn on the appropriate database feature for your chosen db in `Cargo.toml` (the `"sqlx-sqlite",` line)

3. Execute `cargo run` to start the server

4. Visit [localhost:8000](http://localhost:8000) in browser after seeing the `server started` line
Binary file added examples/seaorm/Screenshot.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions examples/seaorm/entity/Cargo.toml
@@ -0,0 +1,14 @@
[package]
name = "entity"
version = "0.1.0"
edition = "2021"
publish = false

[lib]
name = "entity"
path = "src/lib.rs"

[dependencies]
serde = { version = "1", features = ["derive"] }
sea-orm = "0.9.2"
sea-orm-migration = "0.9.2"
1 change: 1 addition & 0 deletions examples/seaorm/entity/src/lib.rs
@@ -0,0 +1 @@
pub mod post;
18 changes: 18 additions & 0 deletions examples/seaorm/entity/src/post.rs
@@ -0,0 +1,18 @@
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, PartialEq, Eq, 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 {}
13 changes: 13 additions & 0 deletions examples/seaorm/migration/Cargo.toml
@@ -0,0 +1,13 @@
[package]
name = "migration"
version = "0.1.0"
edition = "2021"
publish = false

[lib]
name = "migration"
path = "src/lib.rs"

[dependencies]
async-std = { version = "^1", features = ["attributes", "tokio1"] }
sea-orm-migration = "0.9.2"
37 changes: 37 additions & 0 deletions examples/seaorm/migration/README.md
@@ -0,0 +1,37 @@
# Running Migrator CLI

- Apply all pending migrations
```sh
cargo run
```
```sh
cargo run -- up
```
- Apply first 10 pending migrations
```sh
cargo run -- up -n 10
```
- Rollback last applied migrations
```sh
cargo run -- down
```
- Rollback last 10 applied migrations
```sh
cargo run -- down -n 10
```
- Drop all tables from the database, then reapply all migrations
```sh
cargo run -- fresh
```
- Rollback all applied migrations, then reapply all migrations
```sh
cargo run -- refresh
```
- Rollback all applied migrations
```sh
cargo run -- reset
```
- Check the status of all migrations
```sh
cargo run -- status
```
12 changes: 12 additions & 0 deletions examples/seaorm/migration/src/lib.rs
@@ -0,0 +1,12 @@
pub use sea_orm_migration::prelude::*;

mod m20220120_000001_create_post_table;

pub struct Migrator;

#[async_trait::async_trait]
impl MigratorTrait for Migrator {
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
vec![Box::new(m20220120_000001_create_post_table::Migration)]
}
}
@@ -0,0 +1,42 @@
use sea_orm_migration::prelude::*;

#[derive(DeriveMigrationName)]
pub struct Migration;

#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(Posts::Table)
.if_not_exists()
.col(
ColumnDef::new(Posts::Id)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(ColumnDef::new(Posts::Title).string().not_null())
.col(ColumnDef::new(Posts::Text).string().not_null())
.to_owned(),
)
.await
}

async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(Posts::Table).to_owned())
.await
}
}

/// Learn more at https://docs.rs/sea-query#iden
#[derive(Iden)]
enum Posts {
Table,
Id,
Title,
Text,
}
6 changes: 6 additions & 0 deletions examples/seaorm/migration/src/main.rs
@@ -0,0 +1,6 @@
use sea_orm_migration::prelude::*;

#[async_std::main]
async fn main() {
cli::run_cli(migration::Migrator).await;
}
189 changes: 189 additions & 0 deletions examples/seaorm/src/main.rs
@@ -0,0 +1,189 @@
use std::env;

use entity::post;
use migration::{Migrator, MigratorTrait};
use salvo::extra::affix;
use salvo::extra::serve_static::StaticDir;
use salvo::prelude::*;
use salvo::writer::Text;
use sea_orm::{entity::*, query::*, DatabaseConnection};
use tera::Tera;

const DEFAULT_POSTS_PER_PAGE: u64 = 5;
type Result<T> = std::result::Result<T, StatusError>;

#[derive(Debug, Clone)]
struct AppState {
templates: tera::Tera,
conn: DatabaseConnection,
}

#[handler]
async fn create(req: &mut Request, depot: &mut Depot, res: &mut Response) -> Result<()> {
let state = depot
.obtain::<AppState>()
.ok_or_else(StatusError::internal_server_error)?;
let form = req
.parse_form::<post::Model>()
.await
.map_err(|_| StatusError::bad_request())?;
post::ActiveModel {
title: Set(form.title.to_owned()),
text: Set(form.text.to_owned()),
..Default::default()
}
.save(&state.conn)
.await
.map_err(|_| StatusError::internal_server_error())?;

res.render(Redirect::found("/").unwrap());
Ok(())
}

#[handler]
async fn list(req: &mut Request, depot: &mut Depot) -> Result<Text<String>> {
let state = depot
.obtain::<AppState>()
.ok_or_else(StatusError::internal_server_error)?;
let page = req.query("page").unwrap_or(1);
let posts_per_page = req
.query("posts_per_page")
.unwrap_or(DEFAULT_POSTS_PER_PAGE) as usize;
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(|_| StatusError::bad_request())?;
let posts = paginator
.fetch_page(page - 1)
.await
.map_err(|_| StatusError::internal_server_error())?;

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(|_| StatusError::internal_server_error())?;
Ok(Text::Html(body))
}

#[handler]
async fn new(depot: &mut Depot) -> Result<Text<String>> {
let state = depot
.obtain::<AppState>()
.ok_or_else(StatusError::internal_server_error)?;
let ctx = tera::Context::new();
let body = state
.templates
.render("new.html.tera", &ctx)
.map_err(|_| StatusError::internal_server_error())?;
Ok(Text::Html(body))
}

#[handler]
async fn edit(req: &mut Request, depot: &mut Depot) -> Result<Text<String>> {
let state = depot
.obtain::<AppState>()
.ok_or_else(StatusError::internal_server_error)?;
let id = req.param::<i32>("id").unwrap_or_default();
let post: post::Model = post::Entity::find_by_id(id)
.one(&state.conn)
.await
.map_err(|_| StatusError::internal_server_error())?
.ok_or_else(StatusError::not_found)?;

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

let body = state
.templates
.render("edit.html.tera", &ctx)
.map_err(|_| StatusError::internal_server_error())?;
Ok(Text::Html(body))
}

#[handler]
async fn update(req: &mut Request, depot: &mut Depot, res: &mut Response) -> Result<()> {
let state = depot
.obtain::<AppState>()
.ok_or_else(StatusError::internal_server_error)?;
let id = req.param::<i32>("id").unwrap_or_default();
let form = req
.parse_form::<post::Model>()
.await
.map_err(|_| StatusError::bad_request())?;
post::ActiveModel {
id: Set(id),
title: Set(form.title.to_owned()),
text: Set(form.text.to_owned()),
}
.save(&state.conn)
.await
.map_err(|_| StatusError::internal_server_error())?;
res.render(Redirect::found("/").unwrap());
Ok(())
}

#[handler]
async fn delete(req: &mut Request, depot: &mut Depot, res: &mut Response) -> Result<()> {
let state = depot
.obtain::<AppState>()
.ok_or_else(StatusError::internal_server_error)?;
let id = req.param::<i32>("id").unwrap_or_default();
let post: post::ActiveModel = post::Entity::find_by_id(id)
.one(&state.conn)
.await
.map_err(|_| StatusError::internal_server_error())?
.ok_or_else(StatusError::not_found)?
.into();
post.delete(&state.conn)
.await
.map_err(|_| StatusError::internal_server_error())?;

res.render(Redirect::found("/").unwrap());
Ok(())
}

#[tokio::main]
async fn main() {
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").unwrap_or("0.0.0.0".to_owned());
let port = env::var("PORT").unwrap_or("8080".to_owned());
let server_url = format!("{}:{}", host, port);

// create post table if not exists
let conn = sea_orm::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 };

println!("Starting server at {}", server_url);

let router = Router::new()
.hoop(affix::inject(state))
.post(create)
.get(list)
.push(Router::with_path("new").get(new))
.push(Router::with_path("<id>").get(edit).post(update))
.push(Router::with_path("delete/<id>").post(delete))
.push(Router::with_path("static/<**>").get(StaticDir::new(concat!(
env!("CARGO_MANIFEST_DIR"),
"/static"
))));

Server::new(TcpListener::bind(&server_url))
.serve(router)
.await;
}

0 comments on commit 56e450c

Please sign in to comment.