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:
@@ -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"
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE shares;
|
||||
@@ -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
|
||||
);
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
53
inventur_db/src/shares.rs
Normal 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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user