mirror of https://github.com/buster-so/buster.git
add in sharing lib
This commit is contained in:
parent
7b44f395f3
commit
2e61c39d0a
|
@ -0,0 +1,21 @@
|
||||||
|
[package]
|
||||||
|
name = "sharing"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = { workspace = true }
|
||||||
|
chrono = { workspace = true }
|
||||||
|
serde = { workspace = true }
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
tokio = { workspace = true }
|
||||||
|
tracing = { workspace = true }
|
||||||
|
uuid = { workspace = true }
|
||||||
|
diesel = { workspace = true }
|
||||||
|
diesel-async = { workspace = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tokio-test = { workspace = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
|
@ -0,0 +1,22 @@
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("Resource not found: {0}")]
|
||||||
|
ResourceNotFound(String),
|
||||||
|
|
||||||
|
#[error("Permission denied: {0}")]
|
||||||
|
PermissionDenied(String),
|
||||||
|
|
||||||
|
#[error("Invalid sharing configuration: {0}")]
|
||||||
|
InvalidConfiguration(String),
|
||||||
|
|
||||||
|
#[error("Resource already shared with user: {0}")]
|
||||||
|
AlreadyShared(String),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
Database(#[from] diesel::result::Error),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
Other(#[from] anyhow::Error),
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
//! Sharing Library
|
||||||
|
//!
|
||||||
|
//! This library provides functionality for managing sharing of various resources
|
||||||
|
//! such as metrics, dashboards, collections, and chats. It handles the creation,
|
||||||
|
//! verification, and management of sharing records.
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
pub mod models;
|
||||||
|
pub mod utils;
|
||||||
|
mod errors;
|
||||||
|
|
||||||
|
// Re-exports
|
||||||
|
pub use errors::Error;
|
||||||
|
pub use models::Share;
|
||||||
|
pub use utils::{create_share, check_access, remove_share, list_shares};
|
||||||
|
|
||||||
|
/// Represents the different types of resources that can be shared
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub enum ShareableResource {
|
||||||
|
/// A metric resource
|
||||||
|
Metric(uuid::Uuid),
|
||||||
|
/// A dashboard resource
|
||||||
|
Dashboard(uuid::Uuid),
|
||||||
|
/// A collection resource
|
||||||
|
Collection(uuid::Uuid),
|
||||||
|
/// A chat resource
|
||||||
|
Chat(uuid::Uuid),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the different levels of sharing permissions
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub enum SharePermission {
|
||||||
|
/// Read-only access
|
||||||
|
View,
|
||||||
|
/// Can modify the resource
|
||||||
|
Edit,
|
||||||
|
/// Full control over the resource
|
||||||
|
Admin,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A trait for types that can be shared
|
||||||
|
pub trait Shareable {
|
||||||
|
/// Convert the type into a ShareableResource
|
||||||
|
fn into_shareable(&self) -> ShareableResource;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_shareable_resource_serialization() {
|
||||||
|
let id = Uuid::new_v4();
|
||||||
|
let resource = ShareableResource::Metric(id);
|
||||||
|
let serialized = serde_json::to_string(&resource).unwrap();
|
||||||
|
let deserialized: ShareableResource = serde_json::from_str(&serialized).unwrap();
|
||||||
|
assert_eq!(resource, deserialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_share_permission_serialization() {
|
||||||
|
let permission = SharePermission::Edit;
|
||||||
|
let serialized = serde_json::to_string(&permission).unwrap();
|
||||||
|
let deserialized: SharePermission = serde_json::from_str(&serialized).unwrap();
|
||||||
|
assert_eq!(permission, deserialized);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use diesel::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::{ShareableResource, SharePermission};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Queryable, Insertable, Serialize, Deserialize)]
|
||||||
|
#[diesel(table_name = sharing)]
|
||||||
|
pub struct Share {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub resource_type: String,
|
||||||
|
pub resource_id: Uuid,
|
||||||
|
pub shared_by: Uuid,
|
||||||
|
pub shared_with: Uuid,
|
||||||
|
pub permission: String,
|
||||||
|
pub created_at: DateTime<Utc>,
|
||||||
|
pub updated_at: DateTime<Utc>,
|
||||||
|
pub expires_at: Option<DateTime<Utc>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Share {
|
||||||
|
pub fn new(
|
||||||
|
resource: ShareableResource,
|
||||||
|
shared_by: Uuid,
|
||||||
|
shared_with: Uuid,
|
||||||
|
permission: SharePermission,
|
||||||
|
expires_at: Option<DateTime<Utc>>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
id: Uuid::new_v4(),
|
||||||
|
resource_type: match &resource {
|
||||||
|
ShareableResource::Metric(_) => "metric",
|
||||||
|
ShareableResource::Dashboard(_) => "dashboard",
|
||||||
|
ShareableResource::Collection(_) => "collection",
|
||||||
|
ShareableResource::Chat(_) => "chat",
|
||||||
|
}.to_string(),
|
||||||
|
resource_id: match resource {
|
||||||
|
ShareableResource::Metric(id) |
|
||||||
|
ShareableResource::Dashboard(id) |
|
||||||
|
ShareableResource::Collection(id) |
|
||||||
|
ShareableResource::Chat(id) => id,
|
||||||
|
},
|
||||||
|
shared_by,
|
||||||
|
shared_with,
|
||||||
|
permission: match permission {
|
||||||
|
SharePermission::View => "view",
|
||||||
|
SharePermission::Edit => "edit",
|
||||||
|
SharePermission::Admin => "admin",
|
||||||
|
}.to_string(),
|
||||||
|
created_at: Utc::now(),
|
||||||
|
updated_at: Utc::now(),
|
||||||
|
expires_at,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Diesel schema definition
|
||||||
|
table! {
|
||||||
|
sharing (id) {
|
||||||
|
id -> Uuid,
|
||||||
|
resource_type -> Text,
|
||||||
|
resource_id -> Uuid,
|
||||||
|
shared_by -> Uuid,
|
||||||
|
shared_with -> Uuid,
|
||||||
|
permission -> Text,
|
||||||
|
created_at -> Timestamptz,
|
||||||
|
updated_at -> Timestamptz,
|
||||||
|
expires_at -> Nullable<Timestamptz>,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,127 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use diesel::prelude::*;
|
||||||
|
use diesel_async::RunQueryDsl;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
models::Share,
|
||||||
|
ShareableResource,
|
||||||
|
SharePermission,
|
||||||
|
Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Creates a new sharing record
|
||||||
|
pub async fn create_share(
|
||||||
|
conn: &mut diesel_async::AsyncPgConnection,
|
||||||
|
resource: ShareableResource,
|
||||||
|
shared_by: Uuid,
|
||||||
|
shared_with: Uuid,
|
||||||
|
permission: SharePermission,
|
||||||
|
) -> Result<Share> {
|
||||||
|
use crate::models::sharing::dsl::*;
|
||||||
|
|
||||||
|
// Check if sharing already exists
|
||||||
|
let existing = sharing
|
||||||
|
.filter(resource_id.eq(match &resource {
|
||||||
|
ShareableResource::Metric(id) |
|
||||||
|
ShareableResource::Dashboard(id) |
|
||||||
|
ShareableResource::Collection(id) |
|
||||||
|
ShareableResource::Chat(id) => id,
|
||||||
|
}))
|
||||||
|
.filter(shared_with.eq(shared_with))
|
||||||
|
.first::<Share>(conn)
|
||||||
|
.await
|
||||||
|
.optional()?;
|
||||||
|
|
||||||
|
if existing.is_some() {
|
||||||
|
return Err(Error::AlreadyShared("Resource already shared with this user".to_string()).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let share = Share::new(resource, shared_by, shared_with, permission, None);
|
||||||
|
diesel::insert_into(sharing)
|
||||||
|
.values(&share)
|
||||||
|
.get_result(conn)
|
||||||
|
.await
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if a user has access to a resource
|
||||||
|
pub async fn check_access(
|
||||||
|
conn: &mut diesel_async::AsyncPgConnection,
|
||||||
|
resource: &ShareableResource,
|
||||||
|
user_id: Uuid,
|
||||||
|
required_permission: SharePermission,
|
||||||
|
) -> Result<bool> {
|
||||||
|
use crate::models::sharing::dsl::*;
|
||||||
|
|
||||||
|
let resource_id_val = match resource {
|
||||||
|
ShareableResource::Metric(id) |
|
||||||
|
ShareableResource::Dashboard(id) |
|
||||||
|
ShareableResource::Collection(id) |
|
||||||
|
ShareableResource::Chat(id) => *id,
|
||||||
|
};
|
||||||
|
|
||||||
|
let share = sharing
|
||||||
|
.filter(resource_id.eq(resource_id_val))
|
||||||
|
.filter(shared_with.eq(user_id))
|
||||||
|
.first::<Share>(conn)
|
||||||
|
.await
|
||||||
|
.optional()?;
|
||||||
|
|
||||||
|
Ok(match share {
|
||||||
|
Some(share) => {
|
||||||
|
match (share.permission.as_str(), required_permission) {
|
||||||
|
(_, SharePermission::View) => true,
|
||||||
|
("admin", _) => true,
|
||||||
|
("edit", SharePermission::Edit) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes a sharing record
|
||||||
|
pub async fn remove_share(
|
||||||
|
conn: &mut diesel_async::AsyncPgConnection,
|
||||||
|
resource: &ShareableResource,
|
||||||
|
shared_with: Uuid,
|
||||||
|
) -> Result<bool> {
|
||||||
|
use crate::models::sharing::dsl::*;
|
||||||
|
|
||||||
|
let resource_id_val = match resource {
|
||||||
|
ShareableResource::Metric(id) |
|
||||||
|
ShareableResource::Dashboard(id) |
|
||||||
|
ShareableResource::Collection(id) |
|
||||||
|
ShareableResource::Chat(id) => *id,
|
||||||
|
};
|
||||||
|
|
||||||
|
let deleted = diesel::delete(sharing)
|
||||||
|
.filter(resource_id.eq(resource_id_val))
|
||||||
|
.filter(shared_with.eq(shared_with))
|
||||||
|
.execute(conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(deleted > 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lists all shares for a resource
|
||||||
|
pub async fn list_shares(
|
||||||
|
conn: &mut diesel_async::AsyncPgConnection,
|
||||||
|
resource: &ShareableResource,
|
||||||
|
) -> Result<Vec<Share>> {
|
||||||
|
use crate::models::sharing::dsl::*;
|
||||||
|
|
||||||
|
let resource_id_val = match resource {
|
||||||
|
ShareableResource::Metric(id) |
|
||||||
|
ShareableResource::Dashboard(id) |
|
||||||
|
ShareableResource::Collection(id) |
|
||||||
|
ShareableResource::Chat(id) => *id,
|
||||||
|
};
|
||||||
|
|
||||||
|
sharing
|
||||||
|
.filter(resource_id.eq(resource_id_val))
|
||||||
|
.load::<Share>(conn)
|
||||||
|
.await
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
Loading…
Reference in New Issue