mirror of https://github.com/buster-so/buster.git
feat: Add DatasetToDatasetGroup model and update schema
- Introduced a new `DatasetToDatasetGroup` struct to represent the relationship between datasets and dataset groups, including fields for timestamps and optional deletion. - Updated the database schema to include `updated_at` and `deleted_at` fields for the `datasets_to_dataset_groups` table, enhancing data tracking capabilities. - Refactored the routing in `mod.rs` to include a nested router for assets, improving the organization of dataset group routes.
This commit is contained in:
parent
395b1773e0
commit
71c234aa4b
|
@ -0,0 +1,4 @@
|
||||||
|
-- This file should undo anything in `up.sql`
|
||||||
|
ALTER TABLE datasets_to_dataset_groups
|
||||||
|
DROP COLUMN deleted_at,
|
||||||
|
DROP COLUMN updated_at;
|
|
@ -0,0 +1,4 @@
|
||||||
|
-- Your SQL goes here
|
||||||
|
ALTER TABLE datasets_to_dataset_groups
|
||||||
|
ADD COLUMN updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
ADD COLUMN deleted_at TIMESTAMPTZ;
|
|
@ -558,3 +558,15 @@ pub struct DatasetGroupPermission {
|
||||||
pub updated_at: DateTime<Utc>,
|
pub updated_at: DateTime<Utc>,
|
||||||
pub deleted_at: Option<DateTime<Utc>>,
|
pub deleted_at: Option<DateTime<Utc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Queryable, Insertable, Associations, Debug)]
|
||||||
|
#[diesel(belongs_to(Dataset, foreign_key = dataset_id))]
|
||||||
|
#[diesel(belongs_to(DatasetGroup, foreign_key = dataset_group_id))]
|
||||||
|
#[diesel(table_name = datasets_to_dataset_groups)]
|
||||||
|
pub struct DatasetToDatasetGroup {
|
||||||
|
pub dataset_id: Uuid,
|
||||||
|
pub dataset_group_id: Uuid,
|
||||||
|
pub created_at: DateTime<Utc>,
|
||||||
|
pub updated_at: DateTime<Utc>,
|
||||||
|
pub deleted_at: Option<DateTime<Utc>>,
|
||||||
|
}
|
||||||
|
|
|
@ -260,6 +260,8 @@ diesel::table! {
|
||||||
dataset_id -> Uuid,
|
dataset_id -> Uuid,
|
||||||
dataset_group_id -> Uuid,
|
dataset_group_id -> Uuid,
|
||||||
created_at -> Timestamptz,
|
created_at -> Timestamptz,
|
||||||
|
updated_at -> Timestamptz,
|
||||||
|
deleted_at -> Nullable<Timestamptz>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use axum::extract::Path;
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
use axum::Extension;
|
||||||
|
use diesel::prelude::*;
|
||||||
|
use diesel_async::RunQueryDsl;
|
||||||
|
use serde::Serialize;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::database::lib::get_pg_pool;
|
||||||
|
use crate::database::models::User;
|
||||||
|
use crate::database::schema::{datasets, datasets_to_dataset_groups};
|
||||||
|
use crate::routes::rest::ApiResponse;
|
||||||
|
use crate::utils::security::checks::is_user_workspace_admin_or_data_admin;
|
||||||
|
use crate::utils::user::user_info::get_user_organization_id;
|
||||||
|
|
||||||
|
/// Represents dataset information with its assignment status to a dataset group
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct DatasetInfo {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub name: String,
|
||||||
|
pub assigned: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List datasets that can be associated with a dataset group
|
||||||
|
/// Returns datasets with their current assignment status to the specified dataset group
|
||||||
|
pub async fn list_datasets(
|
||||||
|
Extension(user): Extension<User>,
|
||||||
|
Path(dataset_group_id): Path<Uuid>,
|
||||||
|
) -> Result<ApiResponse<Vec<DatasetInfo>>, (StatusCode, &'static str)> {
|
||||||
|
let datasets = match list_datasets_handler(user, dataset_group_id).await {
|
||||||
|
Ok(datasets) => datasets,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Error listing datasets for dataset group: {:?}", e);
|
||||||
|
return Err((
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
"Error listing datasets for dataset group",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(ApiResponse::JsonData(datasets))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn list_datasets_handler(user: User, dataset_group_id: Uuid) -> Result<Vec<DatasetInfo>> {
|
||||||
|
let mut conn = get_pg_pool().get().await?;
|
||||||
|
let organization_id = get_user_organization_id(&user.id).await?;
|
||||||
|
|
||||||
|
if !is_user_workspace_admin_or_data_admin(&user, &organization_id).await? {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"User is not authorized to list datasets for dataset group"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query datasets and their assignment status to the dataset group
|
||||||
|
let datasets = datasets::table
|
||||||
|
.left_join(
|
||||||
|
datasets_to_dataset_groups::table.on(
|
||||||
|
datasets_to_dataset_groups::dataset_id
|
||||||
|
.eq(datasets::id)
|
||||||
|
.and(datasets_to_dataset_groups::dataset_group_id.eq(dataset_group_id))
|
||||||
|
.and(datasets_to_dataset_groups::deleted_at.is_null()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.select((
|
||||||
|
datasets::id,
|
||||||
|
datasets::name,
|
||||||
|
diesel::dsl::sql::<diesel::sql_types::Bool>(
|
||||||
|
"datasets_to_dataset_groups.dataset_id IS NOT NULL",
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.filter(datasets::organization_id.eq(organization_id))
|
||||||
|
.filter(datasets::deleted_at.is_null())
|
||||||
|
.order_by(datasets::created_at.desc())
|
||||||
|
.load::<(Uuid, String, bool)>(&mut *conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(datasets
|
||||||
|
.into_iter()
|
||||||
|
.map(|(id, name, assigned)| DatasetInfo {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
assigned,
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
|
@ -0,0 +1,112 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use axum::extract::Path;
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
use axum::Extension;
|
||||||
|
use diesel::prelude::*;
|
||||||
|
use diesel_async::RunQueryDsl;
|
||||||
|
use serde::Serialize;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::database::enums::IdentityType;
|
||||||
|
use crate::database::lib::get_pg_pool;
|
||||||
|
use crate::database::models::User;
|
||||||
|
use crate::database::schema::{
|
||||||
|
dataset_groups_permissions, permission_groups, permission_groups_to_identities,
|
||||||
|
};
|
||||||
|
use crate::routes::rest::ApiResponse;
|
||||||
|
use crate::utils::security::checks::is_user_workspace_admin_or_data_admin;
|
||||||
|
use crate::utils::user::user_info::get_user_organization_id;
|
||||||
|
|
||||||
|
/// Represents permission group information with its assignment status to a dataset group
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct PermissionGroupInfo {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub name: String,
|
||||||
|
pub user_count: i64,
|
||||||
|
pub assigned: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List permission groups that can be associated with a dataset group
|
||||||
|
/// Returns permission groups with their current assignment status to the specified dataset group
|
||||||
|
/// and the count of users in each permission group
|
||||||
|
pub async fn list_permission_groups(
|
||||||
|
Extension(user): Extension<User>,
|
||||||
|
Path(dataset_group_id): Path<Uuid>,
|
||||||
|
) -> Result<ApiResponse<Vec<PermissionGroupInfo>>, (StatusCode, &'static str)> {
|
||||||
|
let permission_groups = match list_permission_groups_handler(user, dataset_group_id).await {
|
||||||
|
Ok(groups) => groups,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Error listing permission groups for dataset group: {:?}", e);
|
||||||
|
return Err((
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
"Error listing permission groups for dataset group",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(ApiResponse::JsonData(permission_groups))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn list_permission_groups_handler(
|
||||||
|
user: User,
|
||||||
|
dataset_group_id: Uuid,
|
||||||
|
) -> Result<Vec<PermissionGroupInfo>> {
|
||||||
|
let mut conn = get_pg_pool().get().await?;
|
||||||
|
let organization_id = get_user_organization_id(&user.id).await?;
|
||||||
|
|
||||||
|
if !is_user_workspace_admin_or_data_admin(&user, &organization_id).await? {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"User is not authorized to list permission groups for dataset group"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query permission groups with their user count and assignment status
|
||||||
|
let groups = permission_groups::table
|
||||||
|
.left_join(
|
||||||
|
dataset_groups_permissions::table.on(
|
||||||
|
dataset_groups_permissions::permission_id
|
||||||
|
.eq(permission_groups::id)
|
||||||
|
.and(dataset_groups_permissions::dataset_group_id.eq(dataset_group_id))
|
||||||
|
.and(dataset_groups_permissions::permission_type.eq("permission_group"))
|
||||||
|
.and(dataset_groups_permissions::deleted_at.is_null()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.left_join(
|
||||||
|
permission_groups_to_identities::table.on(
|
||||||
|
permission_groups_to_identities::permission_group_id
|
||||||
|
.eq(permission_groups::id)
|
||||||
|
.and(permission_groups_to_identities::identity_type.eq(IdentityType::User))
|
||||||
|
.and(permission_groups_to_identities::deleted_at.is_null()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.select((
|
||||||
|
permission_groups::id,
|
||||||
|
permission_groups::name,
|
||||||
|
diesel::dsl::sql::<diesel::sql_types::BigInt>(
|
||||||
|
"COALESCE(COUNT(DISTINCT permission_groups_to_identities.identity_id), 0)",
|
||||||
|
),
|
||||||
|
diesel::dsl::sql::<diesel::sql_types::Bool>(
|
||||||
|
"dataset_groups_permissions.id IS NOT NULL",
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.group_by((
|
||||||
|
permission_groups::id,
|
||||||
|
permission_groups::name,
|
||||||
|
dataset_groups_permissions::id,
|
||||||
|
))
|
||||||
|
.filter(permission_groups::organization_id.eq(organization_id))
|
||||||
|
.filter(permission_groups::deleted_at.is_null())
|
||||||
|
.order_by(permission_groups::created_at.desc())
|
||||||
|
.load::<(Uuid, String, i64, bool)>(&mut *conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(groups
|
||||||
|
.into_iter()
|
||||||
|
.map(|(id, name, user_count, assigned)| PermissionGroupInfo {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
user_count,
|
||||||
|
assigned,
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use axum::extract::Path;
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
use axum::Extension;
|
||||||
|
use diesel::prelude::*;
|
||||||
|
use diesel_async::RunQueryDsl;
|
||||||
|
use serde::Serialize;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::database::lib::get_pg_pool;
|
||||||
|
use crate::database::models::User;
|
||||||
|
use crate::database::schema::{dataset_groups_permissions, users, users_to_organizations};
|
||||||
|
use crate::routes::rest::ApiResponse;
|
||||||
|
use crate::utils::security::checks::is_user_workspace_admin_or_data_admin;
|
||||||
|
use crate::utils::user::user_info::get_user_organization_id;
|
||||||
|
|
||||||
|
/// Represents user information with their assignment status to a dataset group
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct UserInfo {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub name: String,
|
||||||
|
pub email: String,
|
||||||
|
pub assigned: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List users that can be associated with a dataset group
|
||||||
|
/// Returns users with their current assignment status to the specified dataset group
|
||||||
|
pub async fn list_users(
|
||||||
|
Extension(user): Extension<User>,
|
||||||
|
Path(dataset_group_id): Path<Uuid>,
|
||||||
|
) -> Result<ApiResponse<Vec<UserInfo>>, (StatusCode, &'static str)> {
|
||||||
|
let users = match list_users_handler(user, dataset_group_id).await {
|
||||||
|
Ok(users) => users,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Error listing users for dataset group: {:?}", e);
|
||||||
|
return Err((
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
"Error listing users for dataset group",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(ApiResponse::JsonData(users))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn list_users_handler(user: User, dataset_group_id: Uuid) -> Result<Vec<UserInfo>> {
|
||||||
|
let mut conn = get_pg_pool().get().await?;
|
||||||
|
let organization_id = get_user_organization_id(&user.id).await?;
|
||||||
|
|
||||||
|
if !is_user_workspace_admin_or_data_admin(&user, &organization_id).await? {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"User is not authorized to list users for dataset group"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query users and their assignment status to the dataset group
|
||||||
|
let users = users::table
|
||||||
|
.left_join(
|
||||||
|
dataset_groups_permissions::table.on(
|
||||||
|
dataset_groups_permissions::permission_id
|
||||||
|
.eq(users::id)
|
||||||
|
.and(dataset_groups_permissions::dataset_group_id.eq(dataset_group_id))
|
||||||
|
.and(dataset_groups_permissions::permission_type.eq("user"))
|
||||||
|
.and(dataset_groups_permissions::deleted_at.is_null()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.inner_join(
|
||||||
|
users_to_organizations::table.on(users_to_organizations::user_id
|
||||||
|
.eq(users::id)
|
||||||
|
.and(users_to_organizations::organization_id.eq(organization_id))
|
||||||
|
.and(users_to_organizations::deleted_at.is_null())),
|
||||||
|
)
|
||||||
|
.select((
|
||||||
|
users::id,
|
||||||
|
users::name.nullable(),
|
||||||
|
users::email,
|
||||||
|
diesel::dsl::sql::<diesel::sql_types::Bool>(
|
||||||
|
"dataset_groups_permissions.id IS NOT NULL",
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.order_by(users::created_at.desc())
|
||||||
|
.load::<(Uuid, Option<String>, String, bool)>(&mut *conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(users
|
||||||
|
.into_iter()
|
||||||
|
.map(|(id, name, email, assigned)| UserInfo {
|
||||||
|
id,
|
||||||
|
name: name.unwrap_or("".to_string()),
|
||||||
|
email,
|
||||||
|
assigned,
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
use axum::{routing::get, Router};
|
||||||
|
|
||||||
|
mod list_datasets;
|
||||||
|
mod list_permission_groups;
|
||||||
|
mod list_users;
|
||||||
|
mod put_datasets;
|
||||||
|
mod put_permission_groups;
|
||||||
|
mod put_users;
|
||||||
|
|
||||||
|
pub use list_datasets::list_datasets;
|
||||||
|
pub use list_permission_groups::list_permission_groups;
|
||||||
|
pub use list_users::list_users;
|
||||||
|
pub use put_datasets::put_datasets;
|
||||||
|
pub use put_permission_groups::put_permission_groups;
|
||||||
|
pub use put_users::put_users;
|
||||||
|
|
||||||
|
pub fn router() -> Router {
|
||||||
|
Router::new()
|
||||||
|
.route("/datasets", get(list_datasets).put(put_datasets))
|
||||||
|
.route(
|
||||||
|
"/permission_groups",
|
||||||
|
get(list_permission_groups).put(put_permission_groups),
|
||||||
|
)
|
||||||
|
.route("/users", get(list_users).put(put_users))
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use axum::extract::Path;
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
use axum::{Extension, Json};
|
||||||
|
use diesel::prelude::*;
|
||||||
|
use diesel_async::RunQueryDsl;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::spawn;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::database::lib::get_pg_pool;
|
||||||
|
use crate::database::models::{DatasetToDatasetGroup, User};
|
||||||
|
use crate::database::schema::datasets_to_dataset_groups;
|
||||||
|
use crate::routes::rest::ApiResponse;
|
||||||
|
use crate::utils::security::checks::is_user_workspace_admin_or_data_admin;
|
||||||
|
use crate::utils::user::user_info::get_user_organization_id;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct DatasetAssignment {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub assigned: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update dataset assignments for a dataset group
|
||||||
|
/// Accepts a list of dataset assignments to add or remove from the dataset group
|
||||||
|
pub async fn put_datasets(
|
||||||
|
Extension(user): Extension<User>,
|
||||||
|
Path(dataset_group_id): Path<Uuid>,
|
||||||
|
Json(assignments): Json<Vec<DatasetAssignment>>,
|
||||||
|
) -> Result<ApiResponse<()>, (StatusCode, &'static str)> {
|
||||||
|
match put_datasets_handler(user, dataset_group_id, assignments).await {
|
||||||
|
Ok(_) => Ok(ApiResponse::NoContent),
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Error assigning datasets to dataset group: {:?}", e);
|
||||||
|
return Err((
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
"Error assigning datasets to dataset group",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn put_datasets_handler(
|
||||||
|
user: User,
|
||||||
|
dataset_group_id: Uuid,
|
||||||
|
assignments: Vec<DatasetAssignment>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let organization_id = get_user_organization_id(&user.id).await?;
|
||||||
|
|
||||||
|
if !is_user_workspace_admin_or_data_admin(&user, &organization_id).await? {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"User is not authorized to assign datasets to dataset group"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (to_assign, to_unassign): (Vec<_>, Vec<_>) = assignments.into_iter().partition(|a| a.assigned);
|
||||||
|
|
||||||
|
let assign_handle = {
|
||||||
|
let dataset_group_id = dataset_group_id;
|
||||||
|
spawn(async move {
|
||||||
|
if !to_assign.is_empty() {
|
||||||
|
let mut conn = get_pg_pool().get().await?;
|
||||||
|
let values: Vec<_> = to_assign
|
||||||
|
.into_iter()
|
||||||
|
.map(|dataset| DatasetToDatasetGroup {
|
||||||
|
dataset_id: dataset.id,
|
||||||
|
dataset_group_id,
|
||||||
|
deleted_at: None,
|
||||||
|
created_at: chrono::Utc::now(),
|
||||||
|
updated_at: chrono::Utc::now(),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
diesel::insert_into(datasets_to_dataset_groups::table)
|
||||||
|
.values(&values)
|
||||||
|
.on_conflict((
|
||||||
|
datasets_to_dataset_groups::dataset_id,
|
||||||
|
datasets_to_dataset_groups::dataset_group_id,
|
||||||
|
))
|
||||||
|
.do_update()
|
||||||
|
.set(datasets_to_dataset_groups::deleted_at.eq(None::<chrono::DateTime<chrono::Utc>>))
|
||||||
|
.execute(&mut *conn)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
Ok::<_, anyhow::Error>(())
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let unassign_handle = {
|
||||||
|
let dataset_group_id = dataset_group_id;
|
||||||
|
spawn(async move {
|
||||||
|
if !to_unassign.is_empty() {
|
||||||
|
let mut conn = get_pg_pool().get().await?;
|
||||||
|
diesel::update(datasets_to_dataset_groups::table)
|
||||||
|
.filter(
|
||||||
|
datasets_to_dataset_groups::dataset_id
|
||||||
|
.eq_any(to_unassign.iter().map(|a| a.id))
|
||||||
|
.and(datasets_to_dataset_groups::dataset_group_id.eq(dataset_group_id)),
|
||||||
|
)
|
||||||
|
.set(datasets_to_dataset_groups::deleted_at.eq(chrono::Utc::now()))
|
||||||
|
.execute(&mut *conn)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
Ok::<_, anyhow::Error>(())
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let (assign_result, unassign_result) = tokio::try_join!(assign_handle, unassign_handle)?;
|
||||||
|
assign_result?;
|
||||||
|
unassign_result?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use axum::extract::Path;
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
use axum::{Extension, Json};
|
||||||
|
use diesel::prelude::*;
|
||||||
|
use diesel_async::RunQueryDsl;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::spawn;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::database::lib::get_pg_pool;
|
||||||
|
use crate::database::models::{DatasetGroupPermission, User};
|
||||||
|
use crate::database::schema::dataset_groups_permissions;
|
||||||
|
use crate::routes::rest::ApiResponse;
|
||||||
|
use crate::utils::security::checks::is_user_workspace_admin_or_data_admin;
|
||||||
|
use crate::utils::user::user_info::get_user_organization_id;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct PermissionGroupAssignment {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub assigned: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update permission group assignments for a dataset group
|
||||||
|
/// Accepts a list of permission group assignments to add or remove from the dataset group
|
||||||
|
pub async fn put_permission_groups(
|
||||||
|
Extension(user): Extension<User>,
|
||||||
|
Path(dataset_group_id): Path<Uuid>,
|
||||||
|
Json(assignments): Json<Vec<PermissionGroupAssignment>>,
|
||||||
|
) -> Result<ApiResponse<()>, (StatusCode, &'static str)> {
|
||||||
|
match put_permission_groups_handler(user, dataset_group_id, assignments).await {
|
||||||
|
Ok(_) => Ok(ApiResponse::NoContent),
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Error assigning permission groups to dataset group: {:?}", e);
|
||||||
|
return Err((
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
"Error assigning permission groups to dataset group",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn put_permission_groups_handler(
|
||||||
|
user: User,
|
||||||
|
dataset_group_id: Uuid,
|
||||||
|
assignments: Vec<PermissionGroupAssignment>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let organization_id = get_user_organization_id(&user.id).await?;
|
||||||
|
|
||||||
|
if !is_user_workspace_admin_or_data_admin(&user, &organization_id).await? {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"User is not authorized to assign permission groups to dataset group"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (to_assign, to_unassign): (Vec<_>, Vec<_>) = assignments.into_iter().partition(|a| a.assigned);
|
||||||
|
|
||||||
|
let assign_handle = {
|
||||||
|
let dataset_group_id = dataset_group_id;
|
||||||
|
spawn(async move {
|
||||||
|
if !to_assign.is_empty() {
|
||||||
|
let mut conn = get_pg_pool().get().await?;
|
||||||
|
let values: Vec<_> = to_assign
|
||||||
|
.into_iter()
|
||||||
|
.map(|group| DatasetGroupPermission {
|
||||||
|
id: Uuid::new_v4(),
|
||||||
|
dataset_group_id,
|
||||||
|
permission_id: group.id,
|
||||||
|
permission_type: "permission_group".to_string(),
|
||||||
|
organization_id,
|
||||||
|
deleted_at: None,
|
||||||
|
created_at: chrono::Utc::now(),
|
||||||
|
updated_at: chrono::Utc::now(),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
diesel::insert_into(dataset_groups_permissions::table)
|
||||||
|
.values(&values)
|
||||||
|
.on_conflict((
|
||||||
|
dataset_groups_permissions::dataset_group_id,
|
||||||
|
dataset_groups_permissions::permission_id,
|
||||||
|
dataset_groups_permissions::permission_type,
|
||||||
|
))
|
||||||
|
.do_update()
|
||||||
|
.set(dataset_groups_permissions::deleted_at.eq(None::<chrono::DateTime<chrono::Utc>>))
|
||||||
|
.execute(&mut *conn)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
Ok::<_, anyhow::Error>(())
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let unassign_handle = {
|
||||||
|
let dataset_group_id = dataset_group_id;
|
||||||
|
spawn(async move {
|
||||||
|
if !to_unassign.is_empty() {
|
||||||
|
let mut conn = get_pg_pool().get().await?;
|
||||||
|
diesel::update(dataset_groups_permissions::table)
|
||||||
|
.filter(
|
||||||
|
dataset_groups_permissions::dataset_group_id
|
||||||
|
.eq(dataset_group_id)
|
||||||
|
.and(dataset_groups_permissions::permission_id.eq_any(
|
||||||
|
to_unassign.iter().map(|a| a.id),
|
||||||
|
))
|
||||||
|
.and(dataset_groups_permissions::permission_type.eq("permission_group")),
|
||||||
|
)
|
||||||
|
.set(dataset_groups_permissions::deleted_at.eq(chrono::Utc::now()))
|
||||||
|
.execute(&mut *conn)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
Ok::<_, anyhow::Error>(())
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let (assign_result, unassign_result) = tokio::try_join!(assign_handle, unassign_handle)?;
|
||||||
|
assign_result?;
|
||||||
|
unassign_result?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use axum::extract::Path;
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
use axum::{Extension, Json};
|
||||||
|
use diesel::prelude::*;
|
||||||
|
use diesel_async::RunQueryDsl;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::spawn;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::database::lib::get_pg_pool;
|
||||||
|
use crate::database::models::{DatasetGroupPermission, User};
|
||||||
|
use crate::database::schema::dataset_groups_permissions;
|
||||||
|
use crate::routes::rest::ApiResponse;
|
||||||
|
use crate::utils::security::checks::is_user_workspace_admin_or_data_admin;
|
||||||
|
use crate::utils::user::user_info::get_user_organization_id;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct UserAssignment {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub assigned: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update user assignments for a dataset group
|
||||||
|
/// Accepts a list of user assignments to add or remove from the dataset group
|
||||||
|
pub async fn put_users(
|
||||||
|
Extension(user): Extension<User>,
|
||||||
|
Path(dataset_group_id): Path<Uuid>,
|
||||||
|
Json(assignments): Json<Vec<UserAssignment>>,
|
||||||
|
) -> Result<ApiResponse<()>, (StatusCode, &'static str)> {
|
||||||
|
match put_users_handler(user, dataset_group_id, assignments).await {
|
||||||
|
Ok(_) => Ok(ApiResponse::NoContent),
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Error assigning users to dataset group: {:?}", e);
|
||||||
|
return Err((
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
"Error assigning users to dataset group",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn put_users_handler(
|
||||||
|
user: User,
|
||||||
|
dataset_group_id: Uuid,
|
||||||
|
assignments: Vec<UserAssignment>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let organization_id = get_user_organization_id(&user.id).await?;
|
||||||
|
|
||||||
|
if !is_user_workspace_admin_or_data_admin(&user, &organization_id).await? {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"User is not authorized to assign users to dataset group"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (to_assign, to_unassign): (Vec<_>, Vec<_>) = assignments.into_iter().partition(|a| a.assigned);
|
||||||
|
|
||||||
|
let assign_handle = {
|
||||||
|
let dataset_group_id = dataset_group_id;
|
||||||
|
spawn(async move {
|
||||||
|
if !to_assign.is_empty() {
|
||||||
|
let mut conn = get_pg_pool().get().await?;
|
||||||
|
let values: Vec<_> = to_assign
|
||||||
|
.into_iter()
|
||||||
|
.map(|user_assignment| DatasetGroupPermission {
|
||||||
|
id: Uuid::new_v4(),
|
||||||
|
dataset_group_id,
|
||||||
|
permission_id: user_assignment.id,
|
||||||
|
permission_type: "user".to_string(),
|
||||||
|
organization_id,
|
||||||
|
deleted_at: None,
|
||||||
|
created_at: chrono::Utc::now(),
|
||||||
|
updated_at: chrono::Utc::now(),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
diesel::insert_into(dataset_groups_permissions::table)
|
||||||
|
.values(&values)
|
||||||
|
.on_conflict((
|
||||||
|
dataset_groups_permissions::dataset_group_id,
|
||||||
|
dataset_groups_permissions::permission_id,
|
||||||
|
dataset_groups_permissions::permission_type,
|
||||||
|
))
|
||||||
|
.do_update()
|
||||||
|
.set(dataset_groups_permissions::deleted_at.eq(None::<chrono::DateTime<chrono::Utc>>))
|
||||||
|
.execute(&mut *conn)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
Ok::<_, anyhow::Error>(())
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let unassign_handle = {
|
||||||
|
let dataset_group_id = dataset_group_id;
|
||||||
|
spawn(async move {
|
||||||
|
if !to_unassign.is_empty() {
|
||||||
|
let mut conn = get_pg_pool().get().await?;
|
||||||
|
diesel::update(dataset_groups_permissions::table)
|
||||||
|
.filter(
|
||||||
|
dataset_groups_permissions::dataset_group_id
|
||||||
|
.eq(dataset_group_id)
|
||||||
|
.and(dataset_groups_permissions::permission_id.eq_any(
|
||||||
|
to_unassign.iter().map(|a| a.id),
|
||||||
|
))
|
||||||
|
.and(dataset_groups_permissions::permission_type.eq("user")),
|
||||||
|
)
|
||||||
|
.set(dataset_groups_permissions::deleted_at.eq(chrono::Utc::now()))
|
||||||
|
.execute(&mut *conn)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
Ok::<_, anyhow::Error>(())
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let (assign_result, unassign_result) = tokio::try_join!(assign_handle, unassign_handle)?;
|
||||||
|
assign_result?;
|
||||||
|
unassign_result?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -1,8 +1,9 @@
|
||||||
pub mod delete_dataset_group;
|
mod assets;
|
||||||
pub mod get_dataset_group;
|
mod delete_dataset_group;
|
||||||
pub mod list_dataset_groups;
|
mod get_dataset_group;
|
||||||
pub mod post_dataset_group;
|
mod list_dataset_groups;
|
||||||
pub mod put_dataset_group;
|
mod post_dataset_group;
|
||||||
|
mod put_dataset_group;
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
middleware,
|
middleware,
|
||||||
|
@ -13,10 +14,8 @@ use axum::{
|
||||||
use crate::buster_middleware::auth::auth;
|
use crate::buster_middleware::auth::auth;
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
delete_dataset_group::delete_dataset_group,
|
delete_dataset_group::delete_dataset_group, get_dataset_group::get_dataset_group,
|
||||||
get_dataset_group::get_dataset_group,
|
list_dataset_groups::list_dataset_groups, post_dataset_group::post_dataset_group,
|
||||||
list_dataset_groups::list_dataset_groups,
|
|
||||||
post_dataset_group::post_dataset_group,
|
|
||||||
put_dataset_group::put_dataset_group,
|
put_dataset_group::put_dataset_group,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -27,5 +26,6 @@ pub fn router() -> Router {
|
||||||
.route("/:dataset_group_id", get(get_dataset_group))
|
.route("/:dataset_group_id", get(get_dataset_group))
|
||||||
.route("/:dataset_group_id", delete(delete_dataset_group))
|
.route("/:dataset_group_id", delete(delete_dataset_group))
|
||||||
.route("/", put(put_dataset_group))
|
.route("/", put(put_dataset_group))
|
||||||
|
.nest("/:dataset_group_id", assets::router())
|
||||||
.route_layer(middleware::from_fn(auth))
|
.route_layer(middleware::from_fn(auth))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue