Skip to content

Commit

Permalink
feat(example): sea-orm (#101)
Browse files Browse the repository at this point in the history
  • Loading branch information
Liangdi authored Nov 29, 2023
1 parent 102ae4e commit 3d1e46d
Show file tree
Hide file tree
Showing 12 changed files with 398 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ members = [
"examples/templates/*",
"examples/tracing",
"examples/graceful-shutdown",
"examples/databases/*",
]
resolver = "2"

Expand Down
4 changes: 4 additions & 0 deletions examples/databases/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Databases

## Examples
- [sea-orm](./sea-orm/README.md)
17 changes: 17 additions & 0 deletions examples/databases/sea-orm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "sea-orm-example"
version = "0.1.0"
edition.workspace = true
publish = false

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

[dependencies]
viz = { workspace = true, features = ["serve"] }
serde.workspace = true

tokio = { workspace = true, features = [ "rt-multi-thread", "macros" ] }
sea-orm = { version = "0.12.7", features = ["runtime-tokio-rustls", "sqlx-sqlite"] }

[lints]
workspace = true
25 changes: 25 additions & 0 deletions examples/databases/sea-orm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Viz SeaOrm example

UI inspired by: https://github.com/HapticX/happyx/blob/master/examples/todo/README.md

## USAGE
sqlite use `in-memory`` mode,every time run the app, content reset!
```base
carog run
```

## FUNCTION IMPL

- [x] list
- [x] create
- [x] update
- [ ] delete

## SCREENSHOT

![SeaOrm Demo](./sea-orm-demo.gif)

## FAQ
- libsqlite3 error: you need install libsqlite3 for your system

- sea-orm doc: https://www.sea-ql.org/sea-orm-tutorial/ch01-00-build-backend-getting-started.html
154 changes: 154 additions & 0 deletions examples/databases/sea-orm/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.tailwindcss.com"></script>
<title>Viz SeaOrm Todo Demo</title>
</head>

<body>
<div class="flex justify-center items-center w-screen h-screen bg-gray-100">
<div class="flex flex-col gap-4 px-8 py-4 bg-white rounded-2xl drop-shadow-xl">
<div class="flex justify-between gap-2 items-center">
<input id="input" maxlength="20" placeholder="Enter task..." type="text"
class="rounded-full bg-gray-100 px-4 py-2 outline-0 border-0">
<button
class="flex text-xl font-semibold w-10 h-10 justify-center items-center rounded-full cursor-pointer bg-green-300"
id="submit">+</button>
</div>

<div id="todo-container" class="flex flex-col gap-2">
</div>
</div>
</div>
</body>
<script>

var checked_tpl = `
<div
class="flex gap-2 bg-green-400 rounded-xl px-4 py-2 w-full cursor-pointer select-none transition-all">
<div class="flex justify-center items-center w-6 h-6 rounded-md outline outline-1 outline-black">
</div>
</div>
`;
var unchecked_tpl = `
<div
class="flex gap-2 bg-red-400 rounded-xl px-4 py-2 w-full cursor-pointer select-none transition-all">
<div class="flex justify-center items-center w-6 h-6 rounded-md outline outline-1 outline-black">
</div>
</div>
`

var template = {
update: (todos) => {
var todo_container = document.getElementById("todo-container")
todo_container.innerHTML = "";
todos.forEach(todo => {
if (todo['completed']) {
template.create_div(todo, checked_tpl, todo_container);
} else {
template.create_div(todo, unchecked_tpl, todo_container);

}
});
},
create_div: (todo, html, parent) => {
var div = document.createElement("div");
div.setAttribute("id", todo['id']);
div.innerHTML = html;
parent.appendChild(div);
var child = document.createElement("div")
child.classList = "flex-1"
child.innerHTML = `
${todo['text']}
`

var close_div = document.createElement("div");
close_div.classList = "text-xs rounded-full bg-gray-100 p-1"
close_div.innerHTML = "❌";
close_div.addEventListener("click", (event) => {
event.stopPropagation();
event.preventDefault();
service.delete(todo['id']);

})
var new_el = document.getElementById(todo['id']);
new_el.getElementsByTagName("div")[0]
.appendChild(child);

new_el.getElementsByTagName("div")[0].appendChild(close_div)
new_el.addEventListener("click", () => {
todo['completed'] = !todo['completed'];
service.update(todo);
})
}

}

var service = {
load: () => {
fetch("/todos")
.then(response => response.json())
.then(json => {
template.update(json);
})
.catch(err => console.log('Request Failed', err));
},
create: (task) => {
fetch("/todos", { method: "POST", headers: { "Content-Type": "application/json; charset=utf-8" }, body: JSON.stringify(task) })
.then(response => response.json())
.then(json => {
service.load();
})
.catch(err => console.log('Request Failed', err));
},
update: (task) => {
fetch(`/todos/${task['id']}`, { method: "PUT", headers: { "Content-Type": "application/json; charset=utf-8" }, body: JSON.stringify(task) })
.then(response => response.json())
.then(json => {
service.load();
})
.catch(err => console.log('Request Failed', err));
},
delete: (id) => {
fetch(`/todos/${id}`, { method: "DELETE", headers: { "Content-Type": "application/json; charset=utf-8" } })
.then(response => response.json())
.then(json => {
service.load();
})
.catch(err => console.log('Request Failed', err));
}
}
document.addEventListener("DOMContentLoaded", () => {
service.load();
var input = document.getElementById("input");

var create_task = () => {
var text = input.value;
if (!text) {
alert("task is empty");
return;

}
service.create({ "text": text, "completed": false });
input.value = ""
}
input.addEventListener("keypress", () => {
if (event.key === "Enter") {
event.preventDefault();
create_task();
}
})

document.getElementById("submit").addEventListener("click", () => {
create_task()

})
})
</script>

</html>
Binary file added examples/databases/sea-orm/sea-orm-demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
78 changes: 78 additions & 0 deletions examples/databases/sea-orm/src/api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//! web api mod
use crate::entities::todo::{ActiveModel, Entity as todoEntity, Model as Todo};

use sea_orm::{
ActiveModelTrait, ActiveValue::NotSet, DatabaseConnection, EntityTrait, Set, TryIntoModel,
};
use viz::{
types::{Json, Params, State},
IntoResponse, Request, RequestExt, Response, ResponseExt, Result,
};

/// list todos
/// # Errors
/// - `viz::Error`
pub async fn list(mut req: Request) -> Result<Response> {
let State(db) = req.extract::<State<DatabaseConnection>>().await?;
let todos = todoEntity::find()
.all(&db)
.await
.map_err(|err| err.to_string().into_error())?;
Ok(Response::json(todos)?)
}

/// create todos
/// # Errors
/// - `viz::Error`
pub async fn create(mut req: Request) -> Result<Response> {
let (State(db), Json(todo)) = req
.extract::<(State<DatabaseConnection>, Json<Todo>)>()
.await?;

let mut todo_am: ActiveModel = todo.into();
todo_am.id = NotSet;
let result = todo_am
.insert(&db)
.await
.map_err(|err| err.to_string().into_error())?;
let todo_new: Todo = result
.try_into_model()
.map_err(|err| err.to_string().into_error())?;
Ok(Response::json(todo_new)?)
}

/// update todos
/// PUT /todos/:id
/// # Errors
/// - `viz::Error`
pub async fn update(mut req: Request) -> Result<Response> {
let (State(db), Params(id), Json(todo)) = req
.extract::<(State<DatabaseConnection>, Params<i32>, Json<Todo>)>()
.await?;
let mut todo_am: ActiveModel = todo.clone().into();
todo_am.id = Set(id);
todo_am.completed = Set(todo.completed);
let model = todo_am
.update(&db)
.await
.map_err(|err| err.to_string().into_error())?;

Ok(Response::json(model)?)
}

/// delete todos
/// DELETE /todos/:id
/// # Errors
/// - `viz::Error`
pub async fn delete(mut req: Request) -> Result<Response> {
let (State(db), Params(id)) = req
.extract::<(State<DatabaseConnection>, Params<i32>)>()
.await?;
let delete_result = todoEntity::delete_by_id(id)
.exec(&db)
.await
.map_err(|err| err.to_string().into_error())?;
let rows_affected = delete_result.rows_affected;
Ok(Response::json(rows_affected)?)
}
54 changes: 54 additions & 0 deletions examples/databases/sea-orm/src/db.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//! db module , init sqlite db
use crate::entities::todo::Entity;
use sea_orm::{
sea_query::{ColumnDef, SqliteQueryBuilder, Table, TableCreateStatement},
ConnectionTrait, Database, DatabaseConnection, DbBackend, Schema,
};

///
/// # Errors
/// - `DbErr`
pub async fn init_db() -> Result<DatabaseConnection, Box<dyn std::error::Error>> {
let db = Database::connect("sqlite::memory:").await?;
setup_schema(&db).await;
Ok(db)
}

/// setup sqlite schema
async fn setup_schema(db: &DatabaseConnection) {
// Setup Schema helper
let schema = Schema::new(DbBackend::Sqlite);

// Derive from Entity
let stmt: TableCreateStatement = schema.create_table_from_entity(Entity);

// Or setup manually
assert_eq!(
stmt.build(SqliteQueryBuilder),
Table::create()
.table(Entity)
.col(
ColumnDef::new(<Entity as sea_orm::EntityTrait>::Column::Id)
.primary_key()
.auto_increment()
.integer()
.not_null()
)
.col(
ColumnDef::new(<Entity as sea_orm::EntityTrait>::Column::Text)
.text()
.not_null()
)
.col(
ColumnDef::new(<Entity as sea_orm::EntityTrait>::Column::Completed)
.boolean()
.not_null()
)
//...
.build(SqliteQueryBuilder)
);

// Execute create table statement
let _ = db.execute(db.get_database_backend().build(&stmt)).await;
}
2 changes: 2 additions & 0 deletions examples/databases/sea-orm/src/entities/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
//! sea-orm entities mod
pub mod todo;
23 changes: 23 additions & 0 deletions examples/databases/sea-orm/src/entities/todo.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//! todo model
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

///
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Deserialize, Serialize)]
#[sea_orm(table_name = "todos")]
pub struct Model {
///
#[sea_orm(primary_key)]
#[serde[skip_deserializing]]
pub id: i32,
///
pub text: String,
///
pub completed: bool,
}
///
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}

impl ActiveModelBehavior for ActiveModel {}
6 changes: 6 additions & 0 deletions examples/databases/sea-orm/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#![deny(warnings)]
//! `SeaOrm` example for Viz framework.
pub mod api;
pub mod db;
pub mod entities;
Loading

0 comments on commit 3d1e46d

Please sign in to comment.