add in sharing lib

This commit is contained in:
dal 2025-03-05 17:52:08 -07:00
parent 7b44f395f3
commit 2e61c39d0a
No known key found for this signature in database
GPG Key ID: 16F4B0E1E9F61122
5 changed files with 310 additions and 0 deletions

View File

@ -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 = []

View File

@ -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),
}

View File

@ -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);
}
}

View File

@ -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>,
}
}

View File

@ -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)
}