Skip to content

Commit

Permalink
chore: project setup
Browse files Browse the repository at this point in the history
  • Loading branch information
EETagent committed Oct 23, 2022
0 parents commit 63c7307
Show file tree
Hide file tree
Showing 20 changed files with 560 additions and 0 deletions.
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Generated by Cargo
# will have compiled files and executables
debug/
target/

# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock

# These are backup files generated by rustfmt
**/*.rs.bk

# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
12 changes: 12 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "sea-orm-rocket-example"
version = "0.1.0"
authors = ["Vojtěch Jungmann", "Sebastian Pravda"]
edition = "2021"
publish = false

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

[dependencies]
portfolio-api = { path = "api" }
5 changes: 5 additions & 0 deletions Rocket.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#[default]
#template_dir = "api/templates/"

[default.databases.sea_orm]
#url = "postgres://root:root@localhost/rocket_example"
25 changes: 25 additions & 0 deletions api/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "portfolio-api"
version = "0.1.0"
edition = "2021"
publish = false

[dependencies]
async-stream = { version = "^0.3" }
async-trait = { version = "0.1" }
futures = { version = "^0.3" }
futures-util = { version = "^0.3" }
rocket = { version = "0.5.0-rc.2", features = [
"json",
] }

serde_json = { version = "^1" }

portfolio-entity = { path = "../entity" }
portfolio-migration = { path = "../migration" }
portfolio-core = { path = "../core" }

tokio = "1.21.2"

[dependencies.sea-orm-rocket]
version = "0.5.1"
100 changes: 100 additions & 0 deletions api/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#[macro_use]
extern crate rocket;

use rocket::fairing::{self, AdHoc};
use rocket::form::{ Form};
use rocket::fs::{relative, FileServer};
use rocket::response::{Flash, Redirect};
use rocket::{Build, Rocket};
use portfolio_core::{Mutation, Query};

use migration::MigratorTrait;
use sea_orm_rocket::{Connection, Database};

mod pool;
use pool::Db;

pub use entity::post;
pub use entity::post::Entity as Post;


#[post("/", data = "<post_form>")]
async fn create(conn: Connection<'_, Db>, post_form: Form<post::Model>) -> Flash<Redirect> {
let db = conn.into_inner();

let form = post_form.into_inner();

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

Flash::success(Redirect::to("/"), "Post successfully added.")
}

#[post("/<id>", data = "<post_form>")]
async fn update(
conn: Connection<'_, Db>,
id: i32,
post_form: Form<post::Model>,
) -> Flash<Redirect> {
let db = conn.into_inner();

let form = post_form.into_inner();

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

Flash::success(Redirect::to("/"), "Post successfully edited.")
}

#[delete("/<id>")]
async fn delete(conn: Connection<'_, Db>, id: i32) -> Flash<Redirect> {
let db = conn.into_inner();

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

Flash::success(Redirect::to("/"), "Post successfully deleted.")
}

#[delete("/")]
async fn destroy(conn: Connection<'_, Db>) -> Result<(), rocket::response::Debug<String>> {
let db = conn.into_inner();

Mutation::delete_all_posts(db)
.await
.map_err(|e| e.to_string())?;

Ok(())
}

async fn run_migrations(rocket: Rocket<Build>) -> fairing::Result {
let conn = &Db::fetch(&rocket).unwrap().conn;
let _ = migration::Migrator::up(conn, None).await;
Ok(rocket)
}

#[tokio::main]
async fn start() -> Result<(), rocket::Error> {
rocket::build()
.attach(Db::init())
.attach(AdHoc::try_on_ignite("Migrations", run_migrations))
//.mount("/", FileServer::from(relative!("/static")))
.mount("/", routes![create, delete, destroy, update])
.register("/", catchers![])
.launch()
.await
.map(|_| ())
}

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

println!("Rocket: deorbit.");

if let Some(err) = result.err() {
println!("Error: {}", err);
}
}
41 changes: 41 additions & 0 deletions api/src/pool.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use portfolio_core::sea_orm;

use async_trait::async_trait;
use sea_orm::ConnectOptions;
use sea_orm_rocket::{rocket::figment::Figment, Config, Database};
use std::time::Duration;

#[derive(Database, Debug)]
#[database("sea_orm")]
pub struct Db(SeaOrmPool);

#[derive(Debug, Clone)]
pub struct SeaOrmPool {
pub conn: sea_orm::DatabaseConnection,
}

#[async_trait]
impl sea_orm_rocket::Pool for SeaOrmPool {
type Error = sea_orm::DbErr;

type Connection = sea_orm::DatabaseConnection;

async fn init(figment: &Figment) -> Result<Self, Self::Error> {
let config = figment.extract::<Config>().unwrap();
let mut options: ConnectOptions = config.url.into();
options
.max_connections(config.max_connections as u32)
.min_connections(config.min_connections.unwrap_or_default())
.connect_timeout(Duration::from_secs(config.connect_timeout));
if let Some(idle_timeout) = config.idle_timeout {
options.idle_timeout(Duration::from_secs(idle_timeout));
}
let conn = sea_orm::Database::connect(options).await?;

Ok(SeaOrmPool { conn })
}

fn borrow(&self) -> &Self::Connection {
&self.conn
}
}
25 changes: 25 additions & 0 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "portfolio-core"
version = "0.1.0"
edition = "2021"

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

[dependencies.sea-orm]
version = "^0.10.0"
features = [
"runtime-tokio-native-tls",
"sqlx-postgres",
"sqlx-sqlite",
]

[dev-dependencies]
tokio = "1.21.2"

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

[[test]]
name = "mock"
required-features = ["mock"]
7 changes: 7 additions & 0 deletions 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 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 = Post::find_by_id(id)
.one(db)
.await?
.ok_or(DbErr::Custom("Cannot find post.".to_owned()))
.map(Into::into)?;

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 = Post::find_by_id(id)
.one(db)
.await?
.ok_or(DbErr::Custom("Cannot find post.".to_owned()))
.map(Into::into)?;

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 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: u64,
posts_per_page: u64,
) -> Result<(Vec<post::Model>, u64), 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))
}
}
79 changes: 79 additions & 0 deletions core/tests/mock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
mod prepare;

use entity::post;
use prepare::prepare_mock_db;
use rocket_example_core::{Mutation, Query};

#[tokio::test]
async fn main() {
let db = &prepare_mock_db();

{
let post = Query::find_post_by_id(db, 1).await.unwrap().unwrap();

assert_eq!(post.id, 1);
}

{
let post = Query::find_post_by_id(db, 5).await.unwrap().unwrap();

assert_eq!(post.id, 5);
}

{
let post = Mutation::create_post(
db,
post::Model {
id: 0,
title: "Title D".to_owned(),
text: "Text D".to_owned(),
},
)
.await
.unwrap();

assert_eq!(
post,
post::ActiveModel {
id: sea_orm::ActiveValue::Unchanged(6),
title: sea_orm::ActiveValue::Unchanged("Title D".to_owned()),
text: sea_orm::ActiveValue::Unchanged("Text D".to_owned())
}
);
}

{
let post = Mutation::update_post_by_id(
db,
1,
post::Model {
id: 1,
title: "New Title A".to_owned(),
text: "New Text A".to_owned(),
},
)
.await
.unwrap();

assert_eq!(
post,
post::Model {
id: 1,
title: "New Title A".to_owned(),
text: "New Text A".to_owned(),
}
);
}

{
let result = Mutation::delete_post(db, 5).await.unwrap();

assert_eq!(result.rows_affected, 1);
}

{
let result = Mutation::delete_all_posts(db).await.unwrap();

assert_eq!(result.rows_affected, 5);
}
}
Loading

0 comments on commit 63c7307

Please sign in to comment.