Skip to content

Commit

Permalink
feature: remove a user from an org
Browse files Browse the repository at this point in the history
  • Loading branch information
densumesh authored and skeptrunedev committed May 14, 2024
1 parent a3f9b8c commit 30c714a
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 3 deletions.
67 changes: 65 additions & 2 deletions dashboard/src/pages/Dashboard/UserManagment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import { InviteUserModal } from "../../components/InviteUserModal";
import { EditUserModal } from "../../components/EditUserModal";
import { createToast } from "../../components/ShowToasts";
import { FaRegularTrashCan } from "solid-icons/fa";

export const UserManagement = () => {
const apiHost = import.meta.env.VITE_API_HOST as unknown as string;
Expand Down Expand Up @@ -96,6 +97,46 @@ export const UserManagement = () => {
});
};

const removeUser = (id: string) => {
const confirm = window.confirm(
"Are you sure you want to remove this user?",
);
if (!confirm) {
return;
}

fetch(
`${apiHost}/organization/${
userContext.selectedOrganizationId?.() as string
}/user/${id}`,
{
method: "DELETE",
headers: {
"TR-Organization": userContext.selectedOrganizationId?.() as string,
},
credentials: "include",
},
)
.then((res) => {
if (res.ok) {
getUsers();
createToast({
title: "Success",
type: "success",
message: "User removed successfully!",
});
}
})
.catch((err) => {
console.error(err);
createToast({
title: "Error",
type: "error",
message: "Error removing user!",
});
});
};

const deleteInvitation = (id: string) => {
fetch(`${apiHost}/invitation/${id}`, {
method: "DELETE",
Expand Down Expand Up @@ -228,10 +269,16 @@ export const UserManagement = () => {
</th>
<th
scope="col"
class="sticky top-0 border-b border-neutral-300 bg-white bg-opacity-75 py-3.5 pl-3 pr-4 backdrop-blur backdrop-filter sm:pr-6 lg:pr-8"
class="sticky top-0 border-b border-neutral-300 bg-white bg-opacity-75 py-3.5 pl-3 backdrop-blur backdrop-filter"
>
<span class="sr-only">Edit</span>
</th>
<th
scope="col"
class="sticky top-0 border-b border-neutral-300 bg-white bg-opacity-75 pr-4 backdrop-blur backdrop-filter sm:pr-6 lg:pr-8"
>
<span class="sr-only">Delete</span>
</th>
</tr>
</thead>
<tbody>
Expand All @@ -247,7 +294,7 @@ export const UserManagement = () => {
<td class="whitespace-nowrap border-b border-neutral-200 px-3 py-4 text-sm text-neutral-900">
{fromI32ToUserRole(user.user_orgs[0].role) as string}
</td>
<td class="relative whitespace-nowrap border-b border-neutral-200 py-4 pr-4 text-right font-medium sm:pr-8 lg:pr-36 xl:pr-48">
<td class="relative whitespace-nowrap border-b border-neutral-200 py-4 text-right font-medium ">
<button
onClick={() => {
setEditingUser(user);
Expand All @@ -263,6 +310,22 @@ export const UserManagement = () => {
Edit
</button>
</td>
<td class="whitespace-nowrap border-b border-neutral-200 py-4 pr-4 text-right text-sm font-medium">
<button
onClick={() => {
removeUser(user.id);
}}
disabled={user.id === userContext.user?.()?.id}
classList={{
"text-neutral-200 cursor-not-allowed":
user.id === userContext.user?.()?.id,
"text-red-500 hover:text-red-900":
user.id !== userContext.user?.()?.id,
}}
>
<FaRegularTrashCan />
</button>
</td>
</tr>
)}
</For>
Expand Down
41 changes: 40 additions & 1 deletion server/src/handlers/organization_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
create_organization_query, delete_organization_query, get_org_from_id_query,
get_org_usage_by_id_query, get_org_users_by_id_query, update_organization_query,
},
user_operator::add_user_to_organization,
user_operator::{add_user_to_organization, remove_user_from_org_query},
},
};
use actix_web::{web, HttpRequest, HttpResponse};
Expand Down Expand Up @@ -255,3 +255,42 @@ pub async fn get_organization_users(

Ok(HttpResponse::Ok().json(usage))
}

#[derive(Serialize, Deserialize, Debug, Clone, ToSchema)]
pub struct RemoveUserFromOrgData {
/// The id of the organization to remove the user from.
organization_id: uuid::Uuid,
/// The id of the user to remove from the organization.
user_id: uuid::Uuid,
}

/// Remove User From Organization
///
/// Remove a user from an organization. The auth'ed user must be an admin or owner of the organization to remove a user.
#[utoipa::path(
delete,
path = "/organization/{organization_id}/user/{user_id}",
context_path = "/api",
tag = "organization",
responses(
(status = 204, description = "Confirmation that the user was removed from the organization"),
(status = 400, description = "Service error relating to removing the user from the organization", body = ErrorResponseBody),
),
params(
("TR-Organization" = String, Header, description = "The organization id to use for the request"),
("user_id" = Option<uuid::Uuid>, Path, description = "The id of the user you want to remove from the organization."),
),
security(
("ApiKey" = ["readonly"]),
)
)]
pub async fn remove_user_from_org(
data: web::Path<RemoveUserFromOrgData>,
pool: web::Data<Pool>,
redis_pool: web::Data<RedisPool>,
_admin: OwnerOnly,
) -> Result<HttpResponse, actix_web::Error> {
remove_user_from_org_query(data.user_id, data.organization_id, pool, redis_pool).await?;

Ok(HttpResponse::NoContent().finish())
}
4 changes: 4 additions & 0 deletions server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,10 @@ pub fn main() -> std::io::Result<()> {
handlers::organization_handler::get_organization_users,
)),
)
.service(
web::resource("/{organization_id}/user/{user_id}")
.route(web::delete().to(handlers::organization_handler::remove_user_from_org)),
)
.service(
web::resource("/{organization_id}")
.route(
Expand Down
30 changes: 30 additions & 0 deletions server/src/operators/user_operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -559,3 +559,33 @@ pub async fn create_default_user(api_key: &str, pool: web::Data<Pool>) -> Result

Ok(())
}

pub async fn remove_user_from_org_query(
user_id: uuid::Uuid,
organization_id: uuid::Uuid,
pool: web::Data<Pool>,
redis_pool: web::Data<RedisPool>,
) -> Result<(), ServiceError> {
use crate::data::schema::user_organizations::dsl as user_organizations_columns;

let mut conn = pool.get().await.unwrap();

diesel::delete(
user_organizations_columns::user_organizations
.filter(user_organizations_columns::user_id.eq(user_id))
.filter(user_organizations_columns::organization_id.eq(organization_id)),
)
.execute(&mut conn)
.await
.map_err(|_| ServiceError::BadRequest("Error removing user from org".to_string()))?;

let mut redis_conn = redis_pool.get().await.map_err(|_| {
ServiceError::InternalServerError("Failed to get redis connection".to_string())
})?;

redis_conn.del(user_id.to_string()).await.map_err(|_| {
ServiceError::InternalServerError("Failed to delete user from redis".to_string())
})?;

Ok(())
}

0 comments on commit 30c714a

Please sign in to comment.