Compare commits
No commits in common. "tutorial" and "main" have entirely different histories.
1997
Cargo.lock
generated
1997
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1,9 +0,0 @@
|
||||||
[workspace]
|
|
||||||
|
|
||||||
members = [
|
|
||||||
"backend",
|
|
||||||
"frontend",
|
|
||||||
|
|
||||||
#Internal
|
|
||||||
"common"
|
|
||||||
]
|
|
10
README.md
10
README.md
|
@ -1,11 +1,3 @@
|
||||||
# HambiMap
|
# HambiMap
|
||||||
|
|
||||||
Code for a Website that collects stories and photos from 11 years of Hambach Forest occupation, with the goal of producing info tables to put up in the forest
|
Code for a Website that collects stories and photos from 11 years of Hambach Forest occupation, with the goal of producing info tables to put up in the forest
|
||||||
|
|
||||||
## Tutorial branch
|
|
||||||
I'll use this branch to follow [this tutorial](https://blog.logrocket.com/full-stack-rust-a-complete-tutorial-with-examples/)
|
|
||||||
|
|
||||||
This uses postgresql as database, my original plan was sqlite. Let's see.
|
|
||||||
Also I'm not sure if wasm is the frontend I want.
|
|
||||||
|
|
||||||
There are crates used that require a newer rust than debian stable can provide.
|
|
|
@ -1,17 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "backend"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Gandalf <gandalfderbunte@riseup.net>"]
|
|
||||||
edition = "2018"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
tokio = { version = "=1.6.1", features = ["macros", "rt-multi-thread"] }
|
|
||||||
warp = "=0.3.1"
|
|
||||||
mobc = "=0.7.2"
|
|
||||||
mobc-postgres = { version = "=0.7.0", features = ["with-chrono-0_4", "with-serde_json-1"] }
|
|
||||||
# tokio-postgres = "=0.7.0"
|
|
||||||
hyper = "=0.14.0"
|
|
||||||
serde = {version = "=1.0.126", features = ["derive"] }
|
|
||||||
serde_json = "=1.0.64"
|
|
||||||
thiserror = "=1.0.24"
|
|
||||||
common = { version = "0.1.0", path = "../common" }
|
|
|
@ -1,16 +0,0 @@
|
||||||
CREATE TABLE IF NOT EXISTS owner
|
|
||||||
(
|
|
||||||
id SERIAL PRIMARY KEY NOT NULL,
|
|
||||||
name VARCHAR(255) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS pet
|
|
||||||
(
|
|
||||||
id SERIAL PRIMARY KEY NOT NULL,
|
|
||||||
owner_id INT NOT NULL,
|
|
||||||
name VARCHAR(255) NOT NULL,
|
|
||||||
animal_type VARCHAR(255) NOT NULL,
|
|
||||||
color VARCHAR(255),
|
|
||||||
|
|
||||||
CONSTRAINT fk_pet_owner_id FOREIGN KEY (owner_id) REFERENCES pet(id)
|
|
||||||
);
|
|
|
@ -1,30 +0,0 @@
|
||||||
type Result<T> = std::result::Result<T, error::Error>;
|
|
||||||
|
|
||||||
const DB_POOL_MAX_OPEN: u64 = 32;
|
|
||||||
const DB_POOL_MAX_IDLE: u64 = 8;
|
|
||||||
const DB_POOL_TIMEOUT_SECONDS: u64 = 15;
|
|
||||||
const INIT_SQL: &str = "./db.sql";
|
|
||||||
|
|
||||||
pub async fn init_db(db_pool: &DBPool) -> Result<()> {
|
|
||||||
let init_file = fs::read_to_string(INIT_SQL)?;
|
|
||||||
let con = get_db_con(db_pool).await?;
|
|
||||||
con.batch_execute(init_file.as_str())
|
|
||||||
.await
|
|
||||||
.map_err(DBInitError)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_db_con(db_pool: &DBPool) -> Result<DBCon> {
|
|
||||||
db_pool.get().await.map_err(DBPoolError)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_pool() -> std::result::Result<DBPool, mobc::Error<Error>> {
|
|
||||||
let config = Config::from_str("postgres://postgres@127.0.0.1:7878/postgres")?;
|
|
||||||
|
|
||||||
let manager = PgConnectionManager::new(config, NoTls);
|
|
||||||
Ok(Pool::builder()
|
|
||||||
.max_open(DB_POOL_MAX_OPEN)
|
|
||||||
.max_idle(DB_POOL_MAX_IDLE)
|
|
||||||
.get_timeout(Some(Duration::from_secs(DB_POOL_TIMEOUT_SECONDS)))
|
|
||||||
.build(manager))
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
pub const TABLE: &str = "owner";
|
|
||||||
const SELECT_FIELDS: &str = "id, name";
|
|
||||||
|
|
||||||
pub async fn fetch(db_pool: &DBPool) -> Result<Vec<Owner>> {
|
|
||||||
let con = get_db_con(db_pool).await?;
|
|
||||||
let query = format!("SELECT {} FROM {}", SELECT_FIELDS, TABLE);
|
|
||||||
let rows = con.query(query.as_str(), &[]).await.map_err(DBQueryError)?;
|
|
||||||
|
|
||||||
Ok(rows.iter().map(|r| row_to_owner(&r)).collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn fetch_one(db_pool: &DBPool, id: i32) -> Result<Owner> {
|
|
||||||
let con = get_db_con(db_pool).await?;
|
|
||||||
let query = format!("SELECT {} FROM {} WHERE id = $1", SELECT_FIELDS, TABLE);
|
|
||||||
|
|
||||||
let row = con
|
|
||||||
.query_one(query.as_str(), &[&id])
|
|
||||||
.await
|
|
||||||
.map_err(DBQueryError)?;
|
|
||||||
Ok(row_to_owner(&row))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn create(db_pool: &DBPool, body: OwnerRequest) -> Result<Owner> {
|
|
||||||
let con = get_db_con(db_pool).await?;
|
|
||||||
let query = format!("INSERT INTO {} (name) VALUES ($1) RETURNING *", TABLE);
|
|
||||||
let row = con
|
|
||||||
.query_one(query.as_str(), &[&body.name])
|
|
||||||
.await
|
|
||||||
.map_err(DBQueryError)?;
|
|
||||||
Ok(row_to_owner(&row))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn row_to_owner(row: &Row) -> Owner {
|
|
||||||
let id: i32 = row.get(0);
|
|
||||||
let name: String = row.get(1);
|
|
||||||
Owner { id, name }
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
pub const TABLE: &str = "pet";
|
|
||||||
const SELECT_FIELDS: &str = "id, owner_id, name, animal_type, color";
|
|
||||||
|
|
||||||
pub async fn fetch(db_pool: &DBPool, owner_id: i32) -> Result<Vec<Pet>> {
|
|
||||||
let con = get_db_con(db_pool).await?;
|
|
||||||
let query = format!(
|
|
||||||
"SELECT {} FROM {} WHERE owner_id = $1",
|
|
||||||
SELECT_FIELDS, TABLE
|
|
||||||
);
|
|
||||||
let rows = con
|
|
||||||
.query(query.as_str(), &[&owner_id])
|
|
||||||
.await
|
|
||||||
.map_err(DBQueryError)?;
|
|
||||||
|
|
||||||
Ok(rows.iter().map(|r| row_to_pet(&r)).collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn create(db_pool: &DBPool, owner_id: i32, body: PetRequest) -> Result<Pet> {
|
|
||||||
let con = get_db_con(db_pool).await?;
|
|
||||||
let query = format!(
|
|
||||||
"INSERT INTO {} (name, owner_id, animal_type, color) VALUES ($1, $2, $3, $4) RETURNING *",
|
|
||||||
TABLE
|
|
||||||
);
|
|
||||||
let row = con
|
|
||||||
.query_one(
|
|
||||||
query.as_str(),
|
|
||||||
&[&body.name, &owner_id, &body.animal_type, &body.color],
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map_err(DBQueryError)?;
|
|
||||||
Ok(row_to_pet(&row))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn delete(db_pool: &DBPool, owner_id: i32, id: i32) -> Result<u64> {
|
|
||||||
let con = get_db_con(db_pool).await?;
|
|
||||||
let query = format!("DELETE FROM {} WHERE id = $1 AND owner_id = $2", TABLE);
|
|
||||||
con.execute(query.as_str(), &[&id, &owner_id])
|
|
||||||
.await
|
|
||||||
.map_err(DBQueryError)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn row_to_pet(row: &Row) -> Pet {
|
|
||||||
let id: i32 = row.get(0);
|
|
||||||
let owner_id: i32 = row.get(1);
|
|
||||||
let name: String = row.get(2);
|
|
||||||
let animal_type: String = row.get(3);
|
|
||||||
let color: Option<String> = row.get(4);
|
|
||||||
Pet {
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
owner_id,
|
|
||||||
animal_type,
|
|
||||||
color,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
pub async fn list_pets_handler(owner_id: i32, db_pool: DBPool) -> Result<impl Reply> {
|
|
||||||
let pets = db::pet::fetch(&db_pool, owner_id)
|
|
||||||
.await
|
|
||||||
.map_err(reject::custom)?;
|
|
||||||
Ok(json::<Vec<_>>(
|
|
||||||
&pets.into_iter().map(PetResponse::of).collect(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn create_pet_handler(
|
|
||||||
owner_id: i32,
|
|
||||||
body: PetRequest,
|
|
||||||
db_pool: DBPool,
|
|
||||||
) -> Result<impl Reply> {
|
|
||||||
Ok(json(&PetResponse::of(
|
|
||||||
db::pet::create(&db_pool, owner_id, body)
|
|
||||||
.await
|
|
||||||
.map_err(reject::custom)?,
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn delete_pet_handler(owner_id: i32, id: i32, db_pool: DBPool) -> Result<impl Reply> {
|
|
||||||
db::pet::delete(&db_pool, owner_id, id)
|
|
||||||
.await
|
|
||||||
.map_err(reject::custom)?;
|
|
||||||
Ok(StatusCode::OK)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn list_owners_handler(db_pool: DBPool) -> Result<impl Reply> {
|
|
||||||
let owners = db::owner::fetch(&db_pool).await.map_err(reject::custom)?;
|
|
||||||
Ok(json::<Vec<_>>(
|
|
||||||
&owners.into_iter().map(OwnerResponse::of).collect(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn fetch_owner_handler(id: i32, db_pool: DBPool) -> Result<impl Reply> {
|
|
||||||
let owner = db::owner::fetch_one(&db_pool, id)
|
|
||||||
.await
|
|
||||||
.map_err(reject::custom)?;
|
|
||||||
Ok(json(&OwnerResponse::of(owner)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn create_owner_handler(body: OwnerRequest, db_pool: DBPool) -> Result<impl Reply> {
|
|
||||||
Ok(json(&OwnerResponse::of(
|
|
||||||
db::owner::create(&db_pool, body)
|
|
||||||
.await
|
|
||||||
.map_err(reject::custom)?,
|
|
||||||
)))
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
mod db;
|
|
||||||
mod error;
|
|
||||||
mod handler;
|
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, Rejection>;
|
|
||||||
type DBCon = Connection<PgConnectionManager<NoTls>>;
|
|
||||||
type DBPool = Pool<PgConnectionManager<NoTls>>;
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() {
|
|
||||||
let db_pool = db::create_pool().expect("database pool can be created");
|
|
||||||
|
|
||||||
db::init_db(&db_pool)
|
|
||||||
.await
|
|
||||||
.expect("database can be initialized");
|
|
||||||
|
|
||||||
let pet = warp::path!("owner" / i32 / "pet");
|
|
||||||
let pet_param = warp::path!("owner" / i32 / "pet" / i32);
|
|
||||||
let owner = warp::path("owner");
|
|
||||||
|
|
||||||
let pet_routes = pet
|
|
||||||
.and(warp::get())
|
|
||||||
.and(with_db(db_pool.clone()))
|
|
||||||
.and_then(handler::list_pets_handler)
|
|
||||||
.or(pet
|
|
||||||
.and(warp::post())
|
|
||||||
.and(warp::body::json())
|
|
||||||
.and(with_db(db_pool.clone()))
|
|
||||||
.and_then(handler::create_pet_handler))
|
|
||||||
.or(pet_param
|
|
||||||
.and(warp::delete())
|
|
||||||
.and(with_db(db_pool.clone()))
|
|
||||||
.and_then(handler::delete_pet_handler));
|
|
||||||
|
|
||||||
let owner_routes = owner
|
|
||||||
.and(warp::get())
|
|
||||||
.and(warp::path::param())
|
|
||||||
.and(with_db(db_pool.clone()))
|
|
||||||
.and_then(handler::fetch_owner_handler)
|
|
||||||
.or(owner
|
|
||||||
.and(warp::get())
|
|
||||||
.and(with_db(db_pool.clone()))
|
|
||||||
.and_then(handler::list_owners_handler))
|
|
||||||
.or(owner
|
|
||||||
.and(warp::post())
|
|
||||||
.and(warp::body::json())
|
|
||||||
.and(with_db(db_pool.clone()))
|
|
||||||
.and_then(handler::create_owner_handler));
|
|
||||||
|
|
||||||
let routes = pet_routes
|
|
||||||
.or(owner_routes)
|
|
||||||
.recover(error::handle_rejection)
|
|
||||||
.with(
|
|
||||||
warp::cors()
|
|
||||||
.allow_credentials(true)
|
|
||||||
.allow_methods(&[
|
|
||||||
Method::OPTIONS,
|
|
||||||
Method::GET,
|
|
||||||
Method::POST,
|
|
||||||
Method::DELETE,
|
|
||||||
Method::PUT,
|
|
||||||
])
|
|
||||||
.allow_headers(vec![header::CONTENT_TYPE, header::ACCEPT])
|
|
||||||
.expose_headers(vec![header::LINK])
|
|
||||||
.max_age(300)
|
|
||||||
.allow_any_origin(),
|
|
||||||
);
|
|
||||||
|
|
||||||
warp::serve(routes).run(([127, 0, 0, 1], 8000)).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_db(db_pool: DBPool) -> impl Filter<Extract = (DBPool,), Error = Infallible> + Clone {
|
|
||||||
warp::any().map(move || db_pool.clone())
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "common"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Gandalf <gandalfderbunte@riseup.net>"]
|
|
||||||
edition = "2018"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
serde = {version = "=1.0.126", features = ["derive"] }
|
|
|
@ -1,70 +0,0 @@
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
#[test]
|
|
||||||
fn it_works() {
|
|
||||||
assert_eq!(2 + 2, 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Deserialize, Clone, PartialEq, Debug)]
|
|
||||||
pub struct Owner {
|
|
||||||
pub id: i32,
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
|
||||||
pub struct OwnerRequest {
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
|
||||||
pub struct OwnerResponse {
|
|
||||||
pub id: i32,
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OwnerResponse {
|
|
||||||
pub fn of(owner: Owner) -> OwnerResponse {
|
|
||||||
OwnerResponse {
|
|
||||||
id: owner.id,
|
|
||||||
name: owner.name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Clone, PartialEq, Debug)]
|
|
||||||
pub struct Pet {
|
|
||||||
pub id: i32,
|
|
||||||
pub name: String,
|
|
||||||
pub owner_id: i32,
|
|
||||||
pub animal_type: String,
|
|
||||||
pub color: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
|
||||||
pub struct PetRequest {
|
|
||||||
pub name: String,
|
|
||||||
pub animal_type: String,
|
|
||||||
pub color: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
|
||||||
pub struct PetResponse {
|
|
||||||
pub id: i32,
|
|
||||||
pub name: String,
|
|
||||||
pub animal_type: String,
|
|
||||||
pub color: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PetResponse {
|
|
||||||
pub fn of(pet: Pet) -> PetResponse {
|
|
||||||
PetResponse {
|
|
||||||
id: pet.id,
|
|
||||||
name: pet.name,
|
|
||||||
animal_type: pet.animal_type,
|
|
||||||
color: pet.color,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "frontend"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Gandalf <gandalfderbunte@riseup.net>"]
|
|
||||||
edition = "2018"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
yew = "0.18"
|
|
||||||
wasm-bindgen = "0.2.67"
|
|
||||||
serde_json = "1"
|
|
||||||
serde = {version = "=1.0.126", features = ["derive"] }
|
|
||||||
anyhow = "1"
|
|
||||||
yew-router = "0.15.0"
|
|
||||||
common = { version = "0.1.0", path = "../common" }
|
|
|
@ -1,7 +0,0 @@
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
#[test]
|
|
||||||
fn it_works() {
|
|
||||||
assert_eq!(2 + 2, 4);
|
|
||||||
}
|
|
||||||
}
|
|
3
src/main.rs
Normal file
3
src/main.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
fn main() {
|
||||||
|
println!("Hello, world!");
|
||||||
|
}
|
Loading…
Reference in a new issue