messages
This commit is contained in:
parent
288f193754
commit
fc3a6fe2a0
8 changed files with 372 additions and 50 deletions
101
src/db.rs
101
src/db.rs
|
@ -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
78
src/forms.rs
Normal 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()),
|
||||
}
|
||||
}
|
||||
}
|
52
src/main.rs
52
src/main.rs
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue