Inventur/inventur_db/src/lib.rs
2024-08-28 19:49:12 +02:00

418 lines
12 KiB
Rust

#![recursion_limit = "512"]
//! Database API using the table style system provided by this crate
mod models;
mod schema;
mod jrtables;
mod jrcolumns;
mod jrentries;
mod jrcells;
mod users;
use dotenvy::dotenv;
use std::env;
use diesel::mysql::MysqlConnection;
use diesel::prelude::*;
use std::hash::{Hash, DefaultHasher};
use std::collections::HashSet;
use diesel::deserialize::FromSql;
use diesel::deserialize;
use diesel::backend::Backend;
use diesel::sql_types::Integer;
use diesel::expression::Expression;
#[derive(PartialEq, Clone, Copy, diesel::FromSqlRow)]
#[repr(i32)]
pub enum FIELDTYPE {
TEXT = 0,
NUMBER = 1,
}
impl Expression for FIELDTYPE {
type SqlType = Integer;
}
impl<DB> FromSql<Integer, DB> for FIELDTYPE
where DB: Backend, i32: FromSql<Integer,DB> {
fn from_sql(bytes: DB::RawValue<'_>) -> deserialize::Result<Self> {
match i32::from_sql(bytes)? {
0 => Ok(FIELDTYPE::TEXT),
1 => Ok(FIELDTYPE::NUMBER),
x => Err(format!("Unknown field type {}.", x).into()),
}
}
}
impl From<isize> for FIELDTYPE {
fn from(value: isize) -> Self {
match value {
1 => FIELDTYPE::NUMBER,
x => FIELDTYPE::TEXT,
}
}
}
/// represents and summarised all relevant data of a table.
/// Standard return type if whole tables should be returned
pub struct Tbl {
/// table id of the represented object
pub tblid: i32,
pub name: String,
pub column_names: Vec<String>,
pub column_types: Vec<FIELDTYPE>,
pub rows: Vec<TRow>,
}
/// Represents a table's row.
/// Internal data structure is abstracted until strings.
#[derive(Hash, Clone, Debug)]
pub struct TRow {
pub row_pos: i32,
pub cells: Vec<String>,
}
impl PartialEq for TRow {
fn eq(&self, other: &Self) -> bool {
let mut hasher = DefaultHasher::new();
self.hash(&mut hasher) == other.hash(&mut hasher)
}
}
impl Eq for TRow {}
/// Identifies a user
/// All fields are unique within the db at any given point.
pub struct User {
/// User's unique id.
pub uid: i32,
pub uname: String,
pub email: String,
}
/// Connect to database.
/// Return value must be passed to most of the crate's functions.
pub fn establish_connection() -> MysqlConnection {
dotenv().ok();
let db_url = env::var("DATABASE_URL").expect("No database url set.");
MysqlConnection::establish(&db_url)
.unwrap_or_else(|_| panic!("Couldn't open database."))
}
/// Fetch one whole table from the database. Identified by it's ID.
/// Needs the id of the requesting user.
/// Returns:
/// A Tbl struct
/// Or:
/// None
/// if the table was not found or
/// if it could not be read or
/// if the requesting user is not the table's owner or
/// if there has been another error
///
pub fn get_table(conn: &mut MysqlConnection, tblid: i32, uid: i32) -> Option<Tbl> {
let tbl = jrtables::get_tbl(conn, tblid);
if tbl.is_err() {
return None;
}
let tbl = tbl.unwrap();
if tbl.owner_id != uid {
return None;
}
let clmns = jrcolumns::get_clmns_of(conn, tblid);
if clmns.is_err() {
return None;
}
let clmns = clmns.unwrap();
let mut clmn_nms = Vec::new();
for clmn in &clmns {
clmn_nms.push(clmn.name.clone());
}
let clmn_nms = clmn_nms;
let mut clmn_ids = Vec::new();
for clmn in &clmns {
clmn_ids.push(clmn.id);
}
let clmn_ids = clmn_ids;
let mut clmn_tps = Vec::new();
for clmn in &clmns {
clmn_tps.push(clmn.column_type);
}
let clmn_tps = clmn_tps;
let rowids = jrentries::get_entries_of(conn, tblid);
if rowids.is_err() {
return None;
}
let rowids = rowids.unwrap();
let mut rows = Vec::new();
for rowid in rowids {
let row = jrcells::get_entry_cells(conn, rowid.id);
if row.is_err() {
return None;
}
let row = row.unwrap();
let mut data = Vec::new();
for cell in row {
data.push(cell.cell_value);
}
let data = data;
rows.push(TRow { row_pos: rowid.row_pos, cells: data });
}
Some(Tbl { tblid: tbl.id, name: tbl.name, column_names: clmn_nms, column_types: clmn_tps,rows: rows })
}
pub fn sort_table(tbl: Tbl, sort_field: usize, sort_dir: u8) -> Tbl {
let mut rows = tbl.rows;
if sort_field == 0 {
rows.sort_unstable_by_key(|a: &TRow| a.row_pos);
if sort_dir == 1 {
rows.reverse();
}
}else {
if tbl.column_types[sort_field-1] == FIELDTYPE::NUMBER {
rows.sort_by_key(|a: &TRow| a.cells[sort_field - 1].trim().parse::<i32>().expect("Number not a number."));
}else {
rows.sort_by_key(|a: &TRow| a.cells[sort_field - 1].clone());
}
if sort_dir == 1 {
rows.reverse();
}
}
let rows = rows;
Tbl { tblid: tbl.tblid, name: tbl.name, column_names: tbl.column_names, column_types: tbl.column_types, rows: rows.clone() }
}
pub fn search_table(tbl: Tbl, search_fields: Vec<i32>, search_value: String) -> Tbl {
let mut rows = tbl.rows;
let mut field_sets = HashSet::new();
for field in search_fields {
for row in &rows {
if row.cells[field as usize].to_lowercase().contains(&search_value) {
field_sets.insert(row.clone());
}
}
}
Tbl { tblid: tbl.tblid, name: tbl.name, column_names: tbl.column_names, column_types: tbl.column_types, rows: Vec::from_iter(field_sets) }
}
pub fn get_tblnames(conn: &mut MysqlConnection, tblids: Vec<i32>) -> Option<Vec<String>> {
let mut tblnames = Vec::new();
for tblid in tblids {
let tblname = jrtables::get_tbl(conn, tblid);
if tblname.is_err() {
return None;
}
tblnames.push(tblname.unwrap().name);
}
Some(tblnames)
}
/// Returns a Vec of the ids of a user's table if the user exists, else None.
/// User is identified by its user ID.
pub fn get_user_tblids(conn: &mut MysqlConnection, uid: i32) -> Option<Vec<i32>> {
let tblids = jrtables::get_tblids_uid(conn, uid);
if tblids.is_err() {
return None;
}
Some(tblids.unwrap())
}
/// Change the name of a table.
/// Needs the user ID of the requesting user.
/// Returns Some(true) if the action seems to have been successful and the requesting user is the
/// table's owner.
/// None otherwise.
pub fn rename_table(conn: &mut MysqlConnection, tblid: i32, new_name: String, uid: i32) -> Option<bool> {
let owner = jrtables::get_owner_id(conn, tblid);
if owner.is_err() || owner.unwrap() != uid {
return None;
}
let rnm = jrtables::rename_jrtable(conn, tblid, &new_name);
if rnm.is_err() {
return None;
}
Some(true)
}
/// Delete a table.
/// Needs the user ID of the requesting user.
/// Returns Some(true) if the action seems to have been successful and the requesting user is the
/// table's owner.
/// None otherwise.
pub fn delete_table(conn: &mut MysqlConnection, tblid: i32, uid: i32) -> Option<bool> {
let owner = jrtables::get_owner_id(conn, tblid);
if owner.is_err() || owner.unwrap() != uid {
return None;
}
let dlt = jrtables::delete_jrtable(conn, tblid);
if dlt.is_err() {
return None;
}
Some(true)
}
/// Create a new table.
/// Needs the user ID of the requesting user.
/// Returns the id of the newly created table if the action seems to have been successful and the requesting user is the
/// table's owner.
/// None otherwise.
pub fn create_table(conn: &mut MysqlConnection, name: String, field_names: Vec<String>, field_types: Vec<crate::FIELDTYPE>, uid: i32) -> Option<i32> {
if jrtables::get_tbl_by_name_uid(conn, &name, uid).is_ok() {
return None;
}
if jrtables::create_jrtable(conn, &name, field_names, field_types, uid).is_err() {
return None;
}
let tblid = jrtables::get_tbl_by_name_uid(conn, &name, uid);
if tblid.is_err() {
return None;
}
Some(tblid.unwrap())
}
/// Returns a User struct of a requested user if the combination of username and email are
/// consistent or both do not exist yet, in which case the user is newly created.
pub fn register_or_login(conn: &mut MysqlConnection, uname: String, mail: String) -> Option<User> {
let mut uid = users::get_uid_email(conn, &mail);
if uid.is_err() {
if users::get_uid_uname(conn, &uname).is_ok() || users::create_user(conn, &uname, &mail).is_err() {
return None;
}
uid = users::get_uid_email(conn, &mail);
if uid.is_err() {
return None;
}
}
let uid = uid.unwrap();
let nm = users::get_uname(conn, uid);
if nm.is_err() || nm.unwrap() != uname {
return None;
}
Some(User { uid: uid, uname: uname, email: mail })
}
pub fn delete_user(conn: &mut MysqlConnection, uid: i32) -> Option<bool> {
if users::delete_user(conn, uid).is_err() {
return None;
}
Some(true)
}
pub fn add_row(conn: &mut MysqlConnection, tblid: i32, values: Vec<String>, uid: i32) -> Option<i32> {
let owner = jrtables::get_owner_id(conn, tblid);
let nrows = jrtables::get_nrows(conn, tblid);
if nrows.is_err() || owner.is_err() || owner.unwrap() != uid {
return None;
}
let nrows = nrows.unwrap() as i32;
if jrentries::create_jrentry(conn, tblid).is_err() {
return None;
}
let entryid = jrentries::get_jrentry_id(conn, tblid, nrows+1);
if entryid.is_err() {
return None;
}
let entryid = entryid.unwrap();
let cols = jrcolumns::get_clmns_of(conn, tblid);
if cols.is_err() {
return None;
}
let cols = cols.unwrap();
for (i,col) in cols.iter().enumerate() {
if jrcells::create_jrcell(conn, entryid, col.id, &values[i]).is_err() {
return None;
}
}
Some(entryid)
}
pub fn delete_row(conn: &mut MysqlConnection, tblid: i32, row_pos: i32, uid: i32) -> Option<bool> {
let owner = jrtables::get_owner_id(conn, tblid);
if owner.is_err() || owner.unwrap() != uid || jrentries::delete_jrentry_relative(conn, tblid, row_pos).is_err() {
return None;
}
Some(true)
}
pub fn move_row(conn: &mut MysqlConnection, tblid: i32, rowpos: i32, newpos: i32, uid: i32) -> Option<bool> {
let owner = jrtables::get_owner_id(conn, tblid);
if owner.is_err() || owner.unwrap() != uid || jrentries::move_jrentry(conn, tblid, rowpos, newpos).is_err() {
return None;
}
Some(true)
}
pub fn edit_row(conn: &mut MysqlConnection, tblid: i32, cells: Vec<String>, row_pos: i32, uid: i32) -> Option<bool> {
let owner = jrtables::get_owner_id(conn, tblid);
if owner.is_err() || owner.unwrap() != uid {
return None;
}
for (i, cell) in cells.iter().enumerate() {
if jrcells::change_jrcell_value_relative(conn, tblid, row_pos, (i+1) as i32, cell).is_err() {
return None;
}
}
Some(true)
}
pub fn edit_cell(conn: &mut MysqlConnection, tblid: i32, row_pos: i32, column_pos: i32, new_value: &String, uid: i32) -> Option<bool> {
let owner = jrtables::get_owner_id(conn, tblid);
if owner.is_err() ||
owner.unwrap() != uid ||
jrcells::change_jrcell_value_relative(conn, tblid, row_pos, column_pos, new_value).is_err()
{
return None;
}
Some(true)
}
pub fn move_column(conn: &mut MysqlConnection, tblid:i32, column_pos: i32, new_column_pos: i32, uid: i32) -> Option<bool> {
let owner = jrtables::get_owner_id(conn, tblid);
if owner.is_err() ||
owner.unwrap() != uid ||
jrcolumns::move_column_relative(conn, tblid, column_pos, new_column_pos).is_err()
{
return None;
}
Some(true)
}
pub fn add_column(conn: &mut MysqlConnection, tblid: i32, name: String, clmtype: FIELDTYPE, uid: i32) -> Option<i32> {
let owner = jrtables::get_owner_id(conn, tblid);
if owner.is_err() ||
owner.unwrap() != uid ||
jrcolumns::create_jrcolumn(conn, tblid, name, clmtype).is_err()
{
return None;
}
let clmid = jrcolumns::get_last_column_id_of(conn, tblid);
if clmid.is_err() {
return None;
}
Some(clmid.unwrap())
}
pub fn delete_column(conn: &mut MysqlConnection, tblid: i32, column_pos: i32, uid: i32) -> Option<bool> {
let owner = jrtables::get_owner_id(conn, tblid);
if owner.is_err() ||
owner.unwrap() != uid ||
jrcolumns::delete_column_relative(conn, tblid, column_pos).is_err()
{
return None;
}
Some(true)
}
pub fn rename_column(conn: &mut MysqlConnection, tblid: i32, column_pos: i32, new_name: String, uid: i32) -> Option<bool> {
let owner = jrtables::get_owner_id(conn, tblid);
if owner.is_err() ||
owner.unwrap() != uid ||
jrcolumns::rename_column_relative(conn, tblid, column_pos, new_name).is_err()
{
return None;
}
Some(true)
}