Compare commits
No commits in common. "0f432d3e7e7e42ea6ce1a99f69441ee2552c848c" and "527ae6433c2bcdd7be48027169d6784f1aff57c0" have entirely different histories.
0f432d3e7e
...
527ae6433c
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
|
||||
|
||||
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.
|
||||
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
|
||||
|
|
@ -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