This commit is contained in:
Stefan Schwarz 2020-08-20 20:39:53 +02:00
parent 288f193754
commit fc3a6fe2a0
8 changed files with 372 additions and 50 deletions

101
src/db.rs
View file

@ -1,5 +1,6 @@
use chrono::{DateTime, Utc};
use anyhow::{anyhow, Result};
use sqlx::database::HasArguments;
use std::convert::TryFrom;
use std::net::IpAddr;
type QueryAs<'q, T> =
@ -8,28 +9,32 @@ type Query<'q> = sqlx::query::Query<'q, sqlx::MySql, <sqlx::MySql as HasArgument
#[derive(sqlx::FromRow, Debug)]
pub struct Device {
pub id: i32,
pub id: Option<i32>,
pub macaddr: String,
pub nickname: String,
pub descr: String,
pub privacy: PrivacyLevel,
pub created: DateTime<Utc>,
pub present: bool,
}
#[derive(sqlx::Type, Debug, Clone, Copy)]
#[repr(i8)]
pub enum PrivacyLevel {
ShowUserAndDevice = 0,
ShowUser = 1,
ShowAnonymous = 2,
HideUser = 3,
DontLog = 4,
}
impl<'q> Device {
pub fn all() -> QueryAs<'q, Self> {
sqlx::query_as("SELECT * FROM mac_to_nick")
pub fn create(&'q self) -> Result<Query<'q>> {
if let Some(_) = self.id {
return Err(anyhow!("device has already been created"));
}
Ok(sqlx::query(
"
INSERT
INTO mac_to_nick
(macaddr, nickname, descr, privacy, created)
VALUES
(?, ?, ?, ?, NOW())
",
)
.bind(&self.macaddr)
.bind(&self.nickname)
.bind(&self.descr)
.bind(self.privacy))
}
pub fn for_user(user: &'q str) -> QueryAs<'q, Self> {
@ -54,18 +59,52 @@ ORDER BY
.bind(user)
}
pub fn register(mac: &'q str, user: &'q str) -> Query<'q> {
sqlx::query(
pub fn for_mac(macaddr: &'q str) -> QueryAs<'q, Self> {
dbg!(&macaddr);
sqlx::query_as(
"
INSERT
INTO mac_to_nick
(macaddr, nickname, descr, privacy, created)
VALUES
(?, ?, ?, ?, NOW())
SELECT DISTINCT
*,
FALSE present
FROM
mac_to_nick
WHERE
macaddr = ?
",
)
.bind(mac)
.bind(macaddr)
}
pub fn update(&'q self) -> Result<Query<'q>> {
let id = match self.id {
Some(id) => id,
None => return Err(anyhow!("selected device has no id")),
};
Ok(sqlx::query(
"
UPDATE
mac_to_nick
SET
privacy = ?,
descr = ?
WHERE
id = ?
",
)
.bind(self.privacy as u8)
.bind(&self.descr)
.bind(id))
}
}
#[derive(sqlx::Type, Debug, Clone, Copy)]
#[repr(i8)]
pub enum PrivacyLevel {
ShowUserAndDevice = 0,
ShowUser = 1,
ShowAnonymous = 2,
HideUser = 3,
DontLog = 4,
}
impl PrivacyLevel {
@ -82,6 +121,22 @@ impl PrivacyLevel {
}
}
impl TryFrom<i8> for PrivacyLevel {
type Error = &'static str;
fn try_from(i: i8) -> Result<Self, Self::Error> {
let level = match i {
0 => PrivacyLevel::ShowUserAndDevice,
1 => PrivacyLevel::ShowUser,
2 => PrivacyLevel::ShowAnonymous,
3 => PrivacyLevel::HideUser,
4 => PrivacyLevel::DontLog,
_ => return Err("invalid privacy level"),
};
Ok(level)
}
}
#[derive(sqlx::FromRow, Debug)]
pub struct AliveDevice {
pub macaddr: String,

78
src/forms.rs Normal file
View file

@ -0,0 +1,78 @@
use crate::db;
use crate::Level;
use crate::Message;
use crate::USER;
use serde::Deserialize;
use std::convert::TryFrom;
#[derive(Deserialize)]
pub struct RegisterForm {
macaddr: String,
descr: String,
privacy: i8,
}
impl RegisterForm {
pub async fn handle(self, request: &crate::Request) -> Message {
let privacy = match db::PrivacyLevel::try_from(self.privacy) {
Ok(privacy) => privacy,
Err(_) => return (Level::Error, "unable to parse privacy level".to_string()),
};
let dbresult = db::Device {
id: None,
macaddr: self.macaddr,
nickname: USER.to_string(),
descr: self.descr,
privacy,
present: false,
};
let dbresult = dbresult
.create()
.unwrap()
.execute(&request.state().pool)
.await;
return match dbresult {
Ok(_) => (Level::Info, "assinged device".to_string()),
Err(_) => (Level::Error, "unable to create device".to_string()),
};
}
}
#[derive(Deserialize)]
pub struct UpdateForm {
macaddr: String,
descr: String,
privacy: i8,
}
impl UpdateForm {
pub async fn handle(self, request: &crate::Request) -> Message {
let mut device = match db::Device::for_mac(&self.macaddr)
.fetch_one(&request.state().pool)
.await
{
Ok(device) => device,
Err(_) => {
return (
Level::Error,
"unable to load device from database".to_string(),
)
}
};
device.privacy = match db::PrivacyLevel::try_from(self.privacy) {
Ok(privacy) => privacy,
Err(_) => return (Level::Error, "unable to parse privacy level".to_string()),
};
device.descr = self.descr;
match device
.update()
.unwrap()
.execute(&request.state().pool)
.await
{
Ok(_) => (Level::Info, "updated device".to_string()),
Err(_) => (Level::Error, "unable to update device".to_string()),
}
}
}

View file

@ -1,10 +1,15 @@
use argh::FromArgs;
use serde::{Deserialize, Serialize};
use sqlx::MySqlPool;
use std::io;
use tide::sessions::{MemoryStore, SessionMiddleware};
mod db;
mod forms;
mod routes;
mod templates;
pub const USER: &str = "foosinn";
/// Configuration
#[derive(FromArgs, Debug)]
struct Config {
@ -18,6 +23,10 @@ struct Config {
default = "\"mysql://administration:foosinn123@127.0.0.1/administration\".to_string()"
)]
dsn: String,
/// session secret
#[argh(option, default = "\"thisisnotasecretthisisnotasecret\".into()")]
session_secret: String,
}
#[derive(Clone)]
@ -25,6 +34,43 @@ pub struct State {
pool: MySqlPool,
}
#[derive(Debug, Deserialize, Serialize)]
pub enum Level {
Info,
Warn,
Error,
}
pub type Message = (Level, String);
#[derive(Default, Deserialize, Serialize)]
pub struct AppSession {
messages: Vec<Message>,
}
impl AppSession {
pub fn add_message(mut self, message: Message) -> Self {
self.messages.push(message);
self
}
pub fn pop_messages(&mut self) -> Vec<Message> {
let mut messages: Vec<Message> = Vec::new();
std::mem::swap(&mut messages, &mut self.messages);
messages
}
pub fn commit(self, request: &mut Request) {
request.session_mut().insert("app", self).unwrap()
}
}
impl From<&mut Request> for AppSession {
fn from(request: &mut Request) -> Self {
request.session().get("app").unwrap_or_default()
}
}
pub type Request = tide::Request<State>;
#[async_std::main]
@ -35,11 +81,15 @@ async fn main() -> Result<(), io::Error> {
.await
.map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{:?}", err)))?;
let session_store =
SessionMiddleware::new(MemoryStore::new(), config.session_secret.as_bytes());
let mut app = tide::with_state(State { pool });
app.with(session_store);
app.at("/").get(routes::index);
app.at("/register").post(routes::register);
app.at("/update").post(routes::update);
app.at("/healthz").get(routes::healthz);
app.at("/static").serve_dir("static/")?;
app.listen(config.listen).await
}

View file

@ -1,14 +1,15 @@
use crate::db;
use crate::forms;
use crate::templates;
use tide::prelude::*;
const USER: &str = "foosinn";
use crate::AppSession;
use crate::USER;
use tide::Redirect;
pub async fn healthz(_request: crate::Request) -> tide::Result {
Ok("ok".into())
}
pub async fn index(request: crate::Request) -> tide::Result {
pub async fn index(mut request: crate::Request) -> tide::Result {
let my = db::Device::for_user(USER)
.fetch_all(&request.state().pool)
.await
@ -17,15 +18,28 @@ pub async fn index(request: crate::Request) -> tide::Result {
.fetch_all(&request.state().pool)
.await
.map_err(|err| dbg!(err))?;
Ok(templates::IndexTemplate::new(my, unassinged).into())
}
#[derive(Deserialize)]
struct RegisterForm {
macaddr: String,
let mut session = AppSession::from(&mut request);
let messages = session.pop_messages();
session.commit(&mut request);
Ok(templates::IndexTemplate::new(my, unassinged, messages).into())
}
pub async fn register(mut request: crate::Request) -> tide::Result {
let form: RegisterForm = request.body_form().await?;
unimplemented!();
let form: forms::RegisterForm = request.body_form().await?;
let message = form.handle(&request).await;
AppSession::from(&mut request)
.add_message(message)
.commit(&mut request);
Ok(Redirect::see_other("/").into())
}
pub async fn update(mut request: crate::Request) -> tide::Result {
let form: forms::UpdateForm = request.body_form().await?;
let message = form.handle(&request).await;
AppSession::from(&mut request)
.add_message(message)
.commit(&mut request);
Ok(Redirect::see_other("/").into())
}

View file

@ -1,4 +1,5 @@
use crate::db;
use crate::Message;
use askama::Template;
#[derive(Template, Default)]
@ -6,10 +7,19 @@ use askama::Template;
pub struct IndexTemplate {
my: Vec<db::Device>,
unassinged: Vec<db::AliveDevice>,
messages: Vec<Message>,
}
impl IndexTemplate {
pub fn new(my: Vec<db::Device>, unassinged: Vec<db::AliveDevice>) -> Self {
Self { my, unassinged }
pub fn new(
my: Vec<db::Device>,
unassinged: Vec<db::AliveDevice>,
messages: Vec<Message>,
) -> Self {
Self {
my,
unassinged,
messages,
}
}
}