Dynamically load organisations from API
Diff
chartered-db/src/organisations.rs | 31 +++++++++++++++++++++++++++++++
chartered-web/src/main.rs | 4 ++++
chartered-frontend/src/pages/crate/OrganisationView.tsx | 50 +++++++++++++++++++++++++++++++-------------------
chartered-frontend/src/pages/organisations/ListOrganisations.tsx | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
chartered-web/src/endpoints/web_api/organisations/list.rs | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++
chartered-web/src/endpoints/web_api/organisations/mod.rs | 2 ++
6 files changed, 167 insertions(+), 64 deletions(-)
@@ -1,4 +1,6 @@
use crate::{crates::Crate, permissions::UserPermission, users::User, Error};
use crate::{
crates::Crate, permissions::UserPermission, users::User, BitwiseExpressionMethods, Error,
};
use super::{
schema::{organisations, user_organisation_permissions, users},
@@ -19,6 +21,33 @@
}
impl Organisation {
pub async fn list(conn: ConnectionPool, requesting_user_id: i32) -> Result<Vec<Organisation>> {
use user_organisation_permissions::dsl::permissions;
tokio::task::spawn_blocking(move || {
let conn = conn.get()?;
organisations::table
.inner_join(
user_organisation_permissions::table.on(user_organisation_permissions::user_id
.eq(requesting_user_id)
.and(
user_organisation_permissions::organisation_id
.eq(organisations::dsl::id),
)),
)
.filter(
permissions
.bitwise_and(UserPermission::VISIBLE.bits())
.eq(UserPermission::VISIBLE.bits()),
)
.select(organisations::all_columns)
.load(&conn)
.map_err(Into::into)
})
.await?
}
pub async fn find_by_name(
conn: ConnectionPool,
requesting_user_id: i32,
@@ -72,6 +72,10 @@
let web_authenticated = axum_box_after_every_route!(Router::new()
.route(
"/organisations",
get(endpoints::web_api::organisations::list)
)
.route(
"/organisations/:org",
get(endpoints::web_api::organisations::info)
)
@@ -113,30 +113,24 @@
</ul>
</div>
<div className="card-body">
<div className="d-flex flex-row align-items-center">
{activeTab == "crates" ? (
<ListCrates
organisation={organisation}
crates={organisationDetails.crates}
/>
) : (
<></>
)}
{activeTab == "members" ? (
<ListMembers
organisation={organisation}
members={organisationDetails.members}
possiblePermissions={
organisationDetails.possible_permissions
}
reload={() => setReload(reload + 1)}
/>
) : (
<></>
)}
</div>
</div>
{activeTab == "crates" ? (
<ListCrates
organisation={organisation}
crates={organisationDetails.crates}
/>
) : (
<></>
)}
{activeTab == "members" ? (
<ListMembers
organisation={organisation}
members={organisationDetails.members}
possiblePermissions={organisationDetails.possible_permissions}
reload={() => setReload(reload + 1)}
/>
) : (
<></>
)}
</div>
</div>
</div>
@@ -152,6 +146,14 @@
organisation: string;
crates: Crate[];
}) {
if (crates.length === 0) {
return (
<div className="card-body">
This organisation doesn't have any crates yet.
</div>
);
}
return (
<div className="table-responsive w-100">
<table className="table table-striped">
@@ -1,24 +1,35 @@
import React = require("react");
import { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import Nav from "../../sections/Nav";
import { useAuth } from "../../useAuth";
import { useAuthenticatedRequest, authenticatedEndpoint } from "../../util";
import { Plus, Trash } from "react-bootstrap-icons";
import {
Button,
Dropdown,
Modal,
OverlayTrigger,
Tooltip,
} from "react-bootstrap";
import HumanTime from "react-human-time";
import { useAuthenticatedRequest } from "../../util";
import ErrorPage from "../ErrorPage";
import Loading from "../Loading";
interface Response {
organisations: ResponseOrganisations[];
}
interface ResponseOrganisations {
name: string;
description: string;
}
export default function ListOrganisations() {
const auth = useAuth();
const { response: list, error } = useAuthenticatedRequest<Response>({
auth,
endpoint: "organisations",
});
if (error) {
return <ErrorPage message={error} />;
} else if (!list) {
return <Loading />;
}
return (
<div className="text-white">
<Nav />
@@ -27,33 +38,37 @@
<h1>Your Organisations</h1>
<div className="card border-0 shadow-sm text-black">
<table className="table table-striped">
<tbody>
<tr>
<td className="align-middle fit">
<img
src="http://placekitten.com/48/48"
className="rounded-circle"
/>
</td>
<td className="align-middle">
<Link to="/crates/core">core</Link>
</td>
<td className="fit align-middle">
<Dropdown>
<Dropdown.Toggle variant=""></Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Item href="#/action-1">Members</Dropdown.Item>
<Dropdown.Item href="#/action-2">Crates</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
</td>
</tr>
</tbody>
</table>
{list.organisations.length === 0 ? (
<div className="card-body">
You don't belong to any organisations yet.
</div>
) : (
<table className="table table-striped">
<tbody>
{list.organisations.map((v, i) => (
<tr key={i}>
<td className="align-middle fit">
<img
src="http://placekitten.com/48/48"
className="rounded-circle"
/>
</td>
<td className="align-middle" style={{ lineHeight: "1.1" }}>
<div>
<Link to={`/crates/${v.name}`}>{v.name}</Link>
</div>
<div>
<small style={{ fontSize: "0.75rem" }}>
{v.description}
</small>
</div>
</td>
</tr>
))}
</tbody>
</table>
)}
</div>
</div>
</div>
@@ -1,0 +1,51 @@
use axum::{extract, Json};
use chartered_db::{
organisations::Organisation, users::User, ConnectionPool,
};
use serde::Serialize;
use std::sync::Arc;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("{0}")]
Database(#[from] chartered_db::Error),
}
impl Error {
pub fn status_code(&self) -> axum::http::StatusCode {
match self {
Self::Database(e) => e.status_code(),
}
}
}
define_error_response!(Error);
pub async fn handle_get(
extract::Extension(db): extract::Extension<ConnectionPool>,
extract::Extension(user): extract::Extension<Arc<User>>,
) -> Result<Json<Response>, Error> {
let organisations = Organisation::list(db.clone(), user.id).await?;
Ok(Json(Response {
organisations: organisations
.into_iter()
.map(|v| ResponseOrganisation {
name: v.name,
description: v.description,
})
.collect(),
}))
}
#[derive(Serialize)]
pub struct Response {
organisations: Vec<ResponseOrganisation>,
}
#[derive(Serialize)]
pub struct ResponseOrganisation {
name: String,
description: String,
}
@@ -1,7 +1,9 @@
mod info;
mod list;
mod members;
pub use info::handle_get as info;
pub use list::handle_get as list;
pub use members::{
handle_delete as delete_member, handle_patch as update_member, handle_put as insert_member,
};