First release

This commit is contained in:
Johannes Randerath
2024-08-27 18:25:04 +02:00
parent 218c7e8f40
commit c06ddc9498
125 changed files with 12726 additions and 244 deletions

111
src/auth.rs Normal file
View File

@@ -0,0 +1,111 @@
use crate::Db;
use rocket::response::Redirect;
use rocket::request;
use rocket::http::{Status, Cookie, CookieJar, SameSite};
use inventur_db;
use rocket_oauth2::{OAuth2, TokenResponse};
use reqwest::Client;
use rocket::serde::{Deserialize, json::Json};
pub struct RanderathIdentity;
#[derive(Deserialize)]
#[serde(crate = "rocket::serde")]
struct UnAuthUser {
preferred_username: String,
email: String,
}
pub struct AuthUser {
pub uid: i32,
pub uname: String,
pub email: String,
}
#[rocket::async_trait]
impl<'r> request::FromRequest<'r> for AuthUser {
type Error = ();
async fn from_request(request: &'r request::Request<'_>) -> request::Outcome<AuthUser, ()> {
let cookies = request
.guard::<&CookieJar<'_>>()
.await
.expect("Cookie request failed...");
let uid = cookies.get_private("uid");
let uname = cookies.get_private("username");
let email = cookies.get_private("email");
if uid.is_some() && uname.is_some() && email.is_some() {
let uid = uid.unwrap().value().parse::<i32>().unwrap();
let uname = uname.unwrap().value().to_string();
let email = email.unwrap().value().to_string();
return request::Outcome::Success(AuthUser { uid, uname, email });
}
return request::Outcome::Forward(Status::Unauthorized);
}
}
pub async fn login_or_register(conn: Db, access_token: &str) -> Option<inventur_db::User> {
let ui = Client::new()
.get("https://ldap.randerath.eu/realms/master/protocol/openid-connect/userinfo")
.bearer_auth(access_token)
.send()
.await;
if ui.is_err() {
return None;
}
let ui = ui.unwrap().json::<UnAuthUser>().await;
if ui.is_err() {
return None;
}
let ui = ui.unwrap();
let user = conn.run(move |c| inventur_db::register_or_login(c, ui.preferred_username, ui.email)).await;
if user.is_none() {
return None;
}
Some(user.unwrap())
}
#[catch(401)]
pub async fn redirect_to_login() -> Redirect {
Redirect::to(uri!(oauth_login()))
}
#[get("/login")]
pub fn oauth_login(oauth2: OAuth2<RanderathIdentity>, cookies: &CookieJar<'_>) -> Redirect {
oauth2.get_redirect(cookies, &["openid"]).unwrap()
}
#[get("/auth")]
pub async fn oauth_callback(conn: Db, token: TokenResponse<RanderathIdentity>, cookies: &CookieJar<'_>) -> Result<Redirect, Status> {
let at = token.access_token().to_string();
let tv = token.as_value();
cookies.add_private(
Cookie::build(("token", at.to_string()))
.same_site(SameSite::Lax)
.build()
);
let user = login_or_register(conn, &at).await;
if user.is_none() {
return Err(Status::Forbidden);
}
let user = user.unwrap();
cookies.add_private(
Cookie::build(("username", user.uname))
.same_site(SameSite::Lax)
.build());
cookies.add_private(
Cookie::build(("email", user.email))
.same_site(SameSite::Lax)
.build());
cookies.add_private(
Cookie::build(("uid", user.uid.to_string()))
.same_site(SameSite::Lax)
.build());
Ok(Redirect::to("/"))
}

View File

@@ -1,112 +1,74 @@
#[macro_use] extern crate rocket;
#[macro_use] extern crate rocket_db_pools;
mod auth;
mod table;
use auth::AuthUser;
// TODO: filter (ajax, spinning circle), show options as in excel
use rocket::fs::{FileServer, relative};
use rocket_dyn_templates::{Template, context};
use rocket_db_pools::{Database, Connection};
use rocket_db_pools::diesel::{QueryResult, MysqlPool, prelude::*};
use rocket::form::Form;
/*use dotenvy::dotenv;
use std::env;*/
use rocket::request;
use rocket::response::Redirect;
use rocket::http::{Status, Cookie, CookieJar, SameSite};
use inventur_db;
use rocket_sync_db_pools::{database, diesel};
use rocket_oauth2::OAuth2;
use std::env;
use dotenvy::dotenv;
use rocket::Config;
use rocket::figment::providers::{Toml, Env, Format};
#[derive(Database)]
#[database("inventur")]
struct Db(MysqlPool);
pub struct Db(diesel::MysqlConnection);
/*fn connect_db() -> MysqlConnection {
dotenv.ok();
let database_url = env::var("DATABASE_URL").expect("Can't find database url");
MysqlConnection::establish(&database_url).expect_or_else(|_| panic!("Couldn't connect to database {}.", database_url));
}*/
struct Owner {
id: i64,
email: String,
name: Option<String>,
}
#[derive(Queryable, Insertable)]
#[diesel(table_name = users)]
struct User {
id: i64,
email: String,
}
diesel::table! {
users (id) {
id -> BigInt,
email -> Text,
#[get("/")]
async fn home(conn: Db, user: AuthUser) -> Template {
let uid = user.uid;
let (tids, tnames) = table::get_tids(&conn, uid).await;
let mut cols = Vec::new();
let mut rows = Vec::new();
for tblid in tids.clone() {
let tbl = conn.run(move |c| inventur_db::get_table(c, tblid, uid)).await.unwrap();
cols.push(tbl.column_names.clone());
let mut rws : Vec<Vec<String>> = Vec::new();
for row in tbl.rows {
rws.push(row.cells);
}
rows.push(rws.clone());
}
}
#[derive(Queryable, Insertable)]
#[diesel(table_name = jrtables)]
struct JRTable {
id: i64,
name: String,
owner_id: i64,
}
diesel::table! {
jrtables (id) {
id -> BigInt,
name -> Text,
owner_id -> BigInt,
}
}
#[get("/<tname>")]
fn table(tname: &str) -> Template {
let columns = ["name", "djhfae", "fsjhr"];
let rows = [
["1", "first", "hasdjf", "753rgf"],
["2", "second", "7438ued", "🚀"],
["3", "third", "", ""]
];
Template::render("table",
context!{tname: tname,
columns: columns,
rows: rows,
}
Template::render("home",
context!{
tids: tids,
tnames: tnames,
columns: cols,
rows: rows,
}
)
}
#[derive(FromForm)]
struct New_table<'r> {
name: &'r str,
fields: Vec<&'r str>,
#[field(name = "done")]
complete: bool,
}
#[post("/create", data="<data>")]
fn create(data: Form<New_table<'_>>) {
//println!("{:?}", data);
}
#[derive(FromForm)]
struct Args <'r> {
value: &'r str,
}
#[get("/table/<tname>?filter&<column>&<mode>&<args..>")]
async fn filter(db:Connection<Db>, tname: &str, column: &str, mode: &str, args: Args<'_>) -> &'static str {
todo!()
}
#[get("/")]
fn index(db: Connection<Db>) -> Template {
Template::render("test", context!{foo: 123,})
#[get("/", rank=2)]
async fn login_home() -> Redirect {
Redirect::to(uri!(auth::oauth_login()))
}
#[launch]
fn rocket() -> _ {
rocket::build()
async fn rocket() -> _ {
dotenv().ok();
let cfg = Config::figment()
.merge(Env::prefixed("ROCKET_"));
rocket::custom(cfg)
.attach(Template::fairing())
.attach(Db::init())
.mount("/", FileServer::from(relative!("static")))
.mount("/", routes![index])
.mount("/table", routes![create, table])
//connect_db();
.attach(Db::fairing())
.attach(OAuth2::<auth::RanderathIdentity>::fairing("oauth"))
.mount("/", routes![auth::oauth_login, auth::oauth_callback, home, login_home])
.mount("/table", routes![table::table, table::table_sec, table::edit_tname, table::create, table::import_table])
.mount("/entry", routes![table::new_entry])
.register("/", catchers![auth::redirect_to_login])
.mount("/static", FileServer::from(relative!("static")))
}

View File

@@ -1,11 +1,10 @@
// @generated automatically by Diesel CLI.
diesel::table! {
users (id) {
jrtables (id) {
id -> Integer,
#[max_length = 64]
email -> Varchar,
#[max_length = 255]
name -> Nullable<Varchar>,
name -> Varchar,
num_fields -> Integer,
}
}

180
src/table.rs Normal file
View File

@@ -0,0 +1,180 @@
use crate::auth;
use auth::AuthUser;
use crate::Db;
use rocket_dyn_templates::{Template, context};
use rocket::form::Form;
use rocket::response::Redirect;
use inventur_db;
use rocket::serde::{Serialize, Deserialize, json::Json};
use rocket::http::{Status, Cookie, CookieJar, SameSite};
#[derive(FromForm)]
struct NewEntry {
tblid: i32,
cells: Vec<String>,
}
#[derive(FromForm)]
struct EditTname {
tblid: i32,
new_name: String,
}
#[derive(FromForm)]
struct NewTable {
name: String,
fields: Vec<String>,
}
#[derive(Deserialize)]
#[serde(crate = "rocket::serde")]
struct ImportTable {
name: String,
columns: Vec<String>,
rows: Vec<Vec<String>>,
}
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
struct JRows {
rows: Vec<Vec<String>>,
}
pub async fn get_tids(conn: &Db, uid: i32) -> (Vec<i32>, Vec<String>) {
let mut tids = conn.run(move |c| inventur_db::get_user_tblids(c, uid)).await;
let tnames;
if tids.is_none() {
tids = Some(Vec::new());
tnames = Some(Vec::new());
}else {
let tids = tids.clone().unwrap();
tnames = conn.run(move |c| inventur_db::get_tblnames(c, tids)).await;
}
(tids.unwrap(), tnames.unwrap())
}
#[get("/<tid>", rank=2)]
pub async fn table_sec(conn: Db, tid: i32, user: AuthUser) -> Redirect {
let nus : Option<usize> = None;
let nu8 : Option<u8> = None;
let nvi32 : Option<Vec<i32>> = None;
let ns : Option<String> = None;
Redirect::to(uri!("/table", table(tid, nu8, nus, nvi32, ns)))
}
#[get("/<tid>?<sort_dir>&<sort_field>&<search_fields>&<search_value>")]
pub async fn table(conn: Db, tid: i32, sort_dir: Option<u8>, sort_field: Option<usize>, search_fields: Option<Vec<i32>>, search_value: Option<String>, user: AuthUser) -> Option<Template> {
let uid = user.uid;
let table = conn.run(move |c| inventur_db::get_table(c, tid, uid)).await;
if table.is_none() {
return None;
}
let mut table = table.unwrap();
let searchvalue;
let searchfields;
if search_value.is_some() && !search_value.clone().unwrap().eq_ignore_ascii_case("") && search_fields.is_some() {
searchvalue = search_value.unwrap();
searchfields = search_fields.unwrap();
table = inventur_db::search_table(table, searchfields.clone(), searchvalue.clone());
}else {
if search_value.is_none() {
searchvalue = String::new();
} else {
searchvalue = search_value.unwrap();
}
if search_fields.is_none() {
searchfields = (0i32..(table.column_names.len() as i32)).collect();
} else {
searchfields = search_fields.unwrap();
}
}
let sortdir;
if sort_dir.is_none() || sort_dir.unwrap() > 1 {
sortdir = 0;
}else {
sortdir = sort_dir.unwrap();
}
let sortfield;
if sort_field.is_none() {
sortfield = 0;
}else {
sortfield = sort_field.unwrap();
}
let table = inventur_db::sort_table(table, sortfield, sortdir);
let tname = table.name;
let columns = table.column_names;
let rows : Vec<Vec<String>>= table.rows.iter().map(|v| {let mut r = v.cells.clone(); r.insert(0, v.row_pos.to_string()); r}).collect();
let (tids, tnames) = get_tids(&conn, uid).await;
Some(
Template::render("table",
context!{
search_value: searchvalue,
search_fields: searchfields,
sort_field: sortfield,
sort_dir: sortdir,
tblid: tid,
tblname: tname,
tids: tids,
tnames: tnames,
columns: columns,
rows: rows,
}
)
)
}
#[post("/new", data="<data>")]
pub async fn new_entry(conn: Db, data: Form<NewEntry>, user: AuthUser) -> Result<Redirect, Status> {
let uid = user.uid;
let cells = data.cells.clone();
let tblid = data.tblid;
if conn.run(move |c| inventur_db::add_row(c, data.tblid, cells, uid)).await.is_none() {
return Err(Status::Forbidden);
}
Ok(Redirect::to(uri!("/table", table_sec(tblid))))
}
#[post("/name/edit", data="<data>")]
pub async fn edit_tname(conn: Db, data: Form<EditTname>, user: AuthUser) -> Result<Redirect, Status> {
let uid = user.uid;
let tblid = data.tblid;
if conn.run(move |c| inventur_db::rename_table(c, data.tblid, data.new_name.clone(), uid)).await.is_none() {
return Err(Status::Forbidden);
}
Ok(Redirect::to(uri!("/table", table_sec(tblid))))
}
#[post("/create", data="<data>")]
pub async fn create(conn: Db, data: Form<NewTable>, user: AuthUser) -> Result<Redirect, Status> {
let uid = user.uid;
let tblid = conn.run(move |c| inventur_db::create_table(c, data.name.clone(), data.fields.clone(), uid)).await;
if tblid.is_none() {
return Err(Status::Forbidden);
}
Ok(Redirect::to(uri!("/table", table_sec(tblid.unwrap()))))
}
#[post("/import", data="<data>")]
pub async fn import_table(conn: Db, data: Json<ImportTable>, user: AuthUser) -> Result<Redirect, Status> {
let uid = user.uid;
let columns = data.columns.clone();
let rows = data.rows.clone();
let tblid = conn.run(move |c| inventur_db::create_table(c, data.name.clone(), columns, uid)).await;
if tblid.is_none() {
return Err(Status::UnprocessableEntity);
}
let tblid = tblid.unwrap();
for row in rows {
conn.run(move |c| inventur_db::add_row(c, tblid, row, uid)).await;
}
Ok(Redirect::to(uri!("/table", table_sec(tblid))))
}

120
src/table.rs~ Normal file
View File

@@ -0,0 +1,120 @@
#[get("/<tid>")]
async fn table(conn: Db, tid: i32, user: AuthUser) -> Option<Template> {
let uid = user.uid;
let table = conn.run(move |c| inventur_db::get_table(c, tid, uid)).await;
if table.is_none() {
return None;
}
let table = table.unwrap();
let tname = table.name;
let columns = table.column_names;
let trows = table.rows;
let mut rows = Vec::new();
for trow in trows {
rows.push(trow.cells);
let index = rows.len()-1;
rows[index].insert(0, trow.row_pos.to_string());
}
let rows = rows;
let mut tids = conn.run(move |c| inventur_db::get_user_tblids(c, uid)).await;
let tnames;
if tids.is_none() {
tids = Some(Vec::new());
tnames = Some(Vec::new());
}else {
let tids = tids.clone().unwrap();
tnames = conn.run(move |c| inventur_db::get_tblnames(c, tids)).await;
}
let tids = tids.unwrap();
if tnames.is_none() {
return None;
}
Some(
Template::render("table",
context!{
sort_field: 0,
sort_dir: 0,
tblid: tid,
tblname: tname,
tids: tids,
tnames: tnames,
columns: columns,
rows: rows,
}
)
)
}
#[derive(FromForm)]
struct NewEntry {
tblid: i32,
cells: Vec<String>,
}
#[post("/new", data="<data>")]
async fn new_entry(conn: Db, data: Form<NewEntry>, user: AuthUser) -> Result<Redirect, Status> {
let uid = user.uid;
let cells = data.cells.clone();
let tblid = data.tblid;
if conn.run(move |c| inventur_db::add_row(c, data.tblid, cells, uid)).await.is_none() {
return Err(Status::Forbidden);
}
Ok(Redirect::to(uri!("/table", table(tblid))))
}
#[derive(FromForm)]
struct EditTname {
tblid: i32,
new_name: String,
}
#[post("/name/edit", data="<data>")]
async fn edit_tname(conn: Db, data: Form<EditTname>, user: AuthUser) -> Result<Redirect, Status> {
let uid = user.uid;
let tblid = data.tblid;
if conn.run(move |c| inventur_db::rename_table(c, data.tblid, data.new_name.clone(), uid)).await.is_none() {
return Err(Status::Forbidden);
}
Ok(Redirect::to(uri!("/table", table(tblid))))
}
#[derive(FromForm)]
struct NewTable {
name: String,
fields: Vec<String>,
}
#[post("/create", data="<data>")]
async fn create(conn: Db, data: Form<NewTable>, user: AuthUser) -> Result<Redirect, Status> {
let uid = user.uid;
let tblid = conn.run(move |c| inventur_db::create_table(c, data.name.clone(), data.fields.clone(), uid)).await;
if tblid.is_none() {
return Err(Status::Forbidden);
}
Ok(Redirect::to(uri!("/table", table(tblid.unwrap()))))
}
#[derive(Deserialize)]
#[serde(crate = "rocket::serde")]
struct ImportTable {
name: String,
columns: Vec<String>,
rows: Vec<Vec<String>>,
}
#[post("/import", data="<data>")]
pub async fn import_table(conn: Db, data: Json<ImportTable>, user: AuthUser) -> Result<Redirect, Status> {
let uid = user.uid;
let columns = data.columns.clone();
let rows = data.rows.clone();
let tblid = conn.run(move |c| inventur_db::create_table(c, data.name.clone(), columns, uid)).await;
if tblid.is_none() {
return Err(Status::UnprocessableEntity);
}
let tblid = tblid.unwrap();
for row in rows {
conn.run(move |c| inventur_db::add_row(c, tblid, row, uid)).await;
}
Ok(Redirect::to(uri!("/table", table(tblid))))
}