Sharing tables readonly with other users

- Added sharing feature for table owners to share their tables with
  other registered users.
- Fixed a bug where the wrong entries would be deleted or modified when
  searching or filtering.
This commit is contained in:
Johannes Randerath
2024-09-01 20:14:31 +02:00
parent c6cb3a48bc
commit 61708e5199
22 changed files with 766 additions and 167 deletions

View File

@@ -24,4 +24,5 @@ file = "src/schema.rs"
custom_type_derives = ["diesel::query_builder::QueryId", "Clone"]
[migrations_directory]
dir = "/opt/inventur/inventur/inventur_db/migrations"
dir = "/home/johannes/code/inventur/inventur_db/migrations"

View File

@@ -0,0 +1 @@
DROP TABLE shares;

View File

@@ -0,0 +1,13 @@
CREATE TABLE shares (
id INTEGER AUTO_INCREMENT UNIQUE NOT NULL,
PRIMARY KEY(id),
sharee_id INTEGER NOT NULL,
FOREIGN KEY (sharee_id) REFERENCES users(id)
ON DELETE CASCADE
ON UPDATE CASCADE,
tblid INTEGER NOT NULL,
FOREIGN KEY (tblid) REFERENCES jrtables(id)
ON DELETE CASCADE
ON UPDATE CASCADE,
readonly BOOLEAN NOT NULL DEFAULT true
);

View File

@@ -26,6 +26,7 @@ mod jrcolumns;
mod jrentries;
mod jrcells;
mod users;
mod shares;
use dotenvy::dotenv;
use std::env;
@@ -38,11 +39,12 @@ use diesel::deserialize;
use diesel::backend::Backend;
use diesel::sql_types::Integer;
use diesel::expression::Expression;
use models::Share;
/// Every column has a type.
/// The default is text, currently number (isize) exists too.
/// Types are mainly used for sorting and can be helpful in client applications.
#[derive(PartialEq, Clone, Copy, diesel::FromSqlRow)]
#[derive(PartialEq, Clone, Copy, diesel::FromSqlRow, Debug)]
#[repr(i32)]
pub enum FIELDTYPE {
TEXT = 0,
@@ -75,6 +77,7 @@ impl From<isize> for FIELDTYPE {
/// represents and summarised all relevant data of a table.
/// Standard return type if whole tables should be returned
#[derive(Clone, Debug)]
pub struct Tbl {
/// table id of the represented object
pub tblid: i32,
@@ -109,6 +112,14 @@ pub struct User {
pub email: String,
}
#[derive(Clone)]
pub struct ShareCard {
pub tblid: i32,
pub sharee_id: i32,
pub sharee_name: String,
pub readonly: bool,
}
/// Connect to database.
/// Return value must be passed to most of the crate's functions.
pub fn establish_connection() -> MysqlConnection {
@@ -130,14 +141,21 @@ pub fn establish_connection() -> MysqlConnection {
/// if there has been another error
///
pub fn get_table(conn: &mut MysqlConnection, tblid: i32, uid: i32) -> Option<Tbl> {
let tbl = fetch_table(conn, tblid);
if tbl.is_none() ||
tbl.clone().unwrap().1 != uid
{
return None;
}
Some(tbl.unwrap().0)
}
fn fetch_table(conn: &mut MysqlConnection, tblid: i32) -> Option<(Tbl, i32)>{
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;
@@ -172,7 +190,20 @@ pub fn get_table(conn: &mut MysqlConnection, tblid: i32, uid: i32) -> Option<Tbl
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 })
Some((Tbl { tblid: tbl.id, name: tbl.name, column_names: clmn_nms, column_types: clmn_tps,rows: rows }, tbl.owner_id))
}
pub fn get_table_shared(conn: &mut MysqlConnection, tblid: i32, uid: i32) -> Option<Tbl> {
let tbl = fetch_table(conn, tblid);
let share = shares::is_shared(conn, tblid, uid);
if !share {
return None;
}
let tbl = tbl;
if tbl.is_none() {
return None;
}
Some(tbl.unwrap().0)
}
/// Take a Tbl object, sort it according to parameters and return a sorted Tbl object.
@@ -468,3 +499,83 @@ pub fn edit_column(conn: &mut MysqlConnection, tblid: i32, column_pos: i32, new_
}
Some(true)
}
pub fn share(conn: &mut MysqlConnection, tblid: i32, sharee_id: &i32, readonly: &bool, uid: i32) -> Option<bool> {
let owner = jrtables::get_owner_id(conn, tblid);
println!(":)");
if owner.is_err() ||
owner.unwrap() != uid ||
shares::edit_or_create(conn, tblid, sharee_id, readonly).is_err()
{
return None;
}
Some(true)
}
pub fn unshare(conn: &mut MysqlConnection, tblid: i32, sharee_id: &i32, uid: i32) -> Option<bool> {
let owner = jrtables::get_owner_id(conn, tblid);
if owner.is_err() ||
owner.unwrap() != uid ||
shares::delete(conn, tblid, sharee_id).is_err()
{
return None;
}
Some(true)
}
pub fn get_sharees(conn: &mut MysqlConnection, tblid: i32, uid: i32) -> Option<Vec<ShareCard>> {
let owner = jrtables::get_owner_id(conn, tblid);
if owner.is_err() ||
owner.unwrap() != uid
{
return None;
}
let result = shares::get_shares_tbl(conn, tblid);
if result.is_err() {
return None;
}
let result = result.unwrap();
let mut sharecards = Vec::new();
for share in result {
let uname = users::get_uname(conn, uid);
if uname.is_err() {
continue;
}
sharecards.push(ShareCard { tblid: tblid, sharee_id: share.sharee_id, sharee_name: uname.unwrap(), readonly: share.readonly });
}
return Some(sharecards)
}
pub fn get_shared_tbls(conn: &mut MysqlConnection, uid: i32) -> Option<Vec<ShareCard>> {
let result = shares::get_shares_usr(conn, uid);
if result.is_err() {
return None;
}
let result = result.unwrap();
let uname = users::get_uname(conn, uid);
if uname.is_err() {
return None;
}
let uname = uname.unwrap();
let mut sharecards = Vec::new();
for share in result {
sharecards.push(ShareCard { tblid: share.tblid, sharee_id: uid, sharee_name: uname.clone(), readonly: share.readonly});
}
Some(sharecards)
}
pub fn query_users(conn: &mut MysqlConnection, query: String, uid: i32) -> Option<Vec<User>> {
let result = users::query_users(conn, query);
if users::get_uname(conn, uid).is_err() ||
result.is_err()
{
return None;
}
let result = result.unwrap();
let mut pubusers: Vec<User> = Vec::new();
for user in result {
pubusers.push( User { uid: user.id, uname: user.username, email: user.email } );
}
Some(pubusers)
}

View File

@@ -16,7 +16,7 @@
*/
use diesel::prelude::*;
use crate::schema::{jrtables, jrcolumns, jrentries, jrcells, users};
use crate::schema::{jrtables, jrcolumns, jrentries, jrcells, users, shares};
#[derive(Queryable, Selectable, Identifiable, Associations)]
#[diesel(table_name = crate::schema::jrtables)]
@@ -114,3 +114,23 @@ pub struct NewUser {
pub email: String,
}
#[derive(Insertable)]
#[diesel(table_name = shares)]
#[diesel(check_for_backend(diesel::mysql::Mysql))]
pub struct NewShare {
pub sharee_id: i32,
pub readonly: bool,
pub tblid: i32,
}
#[derive(Queryable, Selectable, Identifiable, Associations)]
#[diesel(table_name = shares)]
#[diesel(belongs_to(Jrtable, foreign_key = tblid))]
#[diesel(belongs_to(User, foreign_key = sharee_id))]
#[diesel(check_for_backend(diesel::mysql::Mysql))]
pub struct Share {
pub id: i32,
pub sharee_id: i32,
pub tblid: i32,
pub readonly: bool,
}

View File

@@ -1,20 +1,3 @@
/* Simple web app using rocket to help maintain inventory data.
* Copyright (C) 2024 Johannes Randerath
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// @generated automatically by Diesel CLI.
diesel::table! {
@@ -55,6 +38,15 @@ diesel::table! {
}
}
diesel::table! {
shares (id) {
id -> Integer,
sharee_id -> Integer,
tblid -> Integer,
readonly -> Bool,
}
}
diesel::table! {
users (id) {
id -> Integer,
@@ -70,11 +62,14 @@ diesel::joinable!(jrcells -> jrentries (jrentry_id));
diesel::joinable!(jrcolumns -> jrtables (jrtable_id));
diesel::joinable!(jrentries -> jrtables (jrtable_id));
diesel::joinable!(jrtables -> users (owner_id));
diesel::joinable!(shares -> jrtables (tblid));
diesel::joinable!(shares -> users (sharee_id));
diesel::allow_tables_to_appear_in_same_query!(
jrcells,
jrcolumns,
jrentries,
jrtables,
shares,
users,
);

53
inventur_db/src/shares.rs Normal file
View File

@@ -0,0 +1,53 @@
use crate::models;
use crate::schema;
use models::{NewShare, Share};
use schema::shares::dsl::{id, shares, tblid, sharee_id, readonly};
use diesel::mysql::MysqlConnection;
use diesel::prelude::*;
pub fn edit_or_create(conn: &mut MysqlConnection, tableid: i32, sharee: &i32, ro: &bool) -> Result<usize, diesel::result::Error> {
let share = shares.filter(tblid.eq(tableid))
.filter(sharee_id.eq(sharee))
.select(id)
.first::<i32>(conn);
if share.is_err() {
return diesel::insert_into(schema::shares::table)
.values(&(NewShare { tblid: tableid, sharee_id: *sharee, readonly: *ro }))
.execute(conn);
}
return diesel::update(shares.find(share.unwrap()))
.set(readonly.eq(ro))
.execute(conn);
}
pub fn delete(conn: &mut MysqlConnection, tableid: i32, sharee: &i32) -> Result<usize, diesel::result::Error> {
diesel::delete(shares.filter(tblid.eq(tableid)).filter(sharee_id.eq(*sharee))).execute(conn)
}
pub fn get_shares_tbl(conn: &mut MysqlConnection, tableid: i32) -> Result<Vec<Share>, diesel::result::Error> {
shares
.filter(tblid.eq(tableid))
.select(Share::as_select())
.load::<Share>(conn)
}
pub fn get_shares_usr(conn: &mut MysqlConnection, uid: i32) -> Result<Vec<Share>, diesel::result::Error> {
shares
.filter(sharee_id.eq(uid))
.select(Share::as_select())
.load::<Share>(conn)
}
pub fn is_shared(conn: &mut MysqlConnection, tableid: i32, uid: i32) -> bool {
let share = shares
.filter(sharee_id.eq(uid))
.filter(tblid.eq(tableid))
.execute(conn);
if share.is_err() {
return false;
}
println!("{share:?}");
share.unwrap() >= 1
}

View File

@@ -20,7 +20,7 @@ use crate::models;
use crate::schema;
use schema::users::dsl::{users, id, username, email};
use models::NewUser;
use models::{ User, NewUser };
use diesel::prelude::*;
use diesel::mysql::MysqlConnection;
@@ -58,4 +58,11 @@ pub fn get_uname(conn: &mut MysqlConnection, uid: i32) -> Result<String, diesel:
.first::<String>(conn)
}
pub fn query_users(conn: &mut MysqlConnection, query: String) -> Result<Vec<User>, diesel::result::Error> {
users
.filter(username.like(format!("{query}%")))
.select(User::as_select())
.load::<User>(conn)
}