🏡 index : ~doyle/chartered.git

author Jordan Doyle <jordan@doyle.la> 2021-09-26 16:36:34.0 +01:00:00
committer Jordan Doyle <jordan@doyle.la> 2021-09-26 16:37:45.0 +01:00:00
commit
d084d8bbc5c1dc5da2998d9a979ef68008b37507 [patch]
tree
b140ffd0d0acfd3706e0818f37f5b45bcca1b78f
parent
b369917fae3a7ff257749a5322096516c2a76fca
download
d084d8bbc5c1dc5da2998d9a979ef68008b37507.tar.gz

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(-)

diff --git a/chartered-db/src/organisations.rs b/chartered-db/src/organisations.rs
index b8b774c..aadf4b9 100644
--- a/chartered-db/src/organisations.rs
+++ a/chartered-db/src/organisations.rs
@@ -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,
diff --git a/chartered-web/src/main.rs b/chartered-web/src/main.rs
index 1b46c75..21ea132 100644
--- a/chartered-web/src/main.rs
+++ a/chartered-web/src/main.rs
@@ -72,6 +72,10 @@
    let web_authenticated = axum_box_after_every_route!(Router::new()
        // organisations endpoints
        .route(
            "/organisations",
            get(endpoints::web_api::organisations::list)
        )
        .route(
            "/organisations/:org",
            get(endpoints::web_api::organisations::info)
        )
diff --git a/chartered-frontend/src/pages/crate/OrganisationView.tsx b/chartered-frontend/src/pages/crate/OrganisationView.tsx
index 935c1e6..1e69361 100644
--- a/chartered-frontend/src/pages/crate/OrganisationView.tsx
+++ a/chartered-frontend/src/pages/crate/OrganisationView.tsx
@@ -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">
diff --git a/chartered-frontend/src/pages/organisations/ListOrganisations.tsx b/chartered-frontend/src/pages/organisations/ListOrganisations.tsx
index e3be293..d878cc7 100644
--- a/chartered-frontend/src/pages/organisations/ListOrganisations.tsx
+++ a/chartered-frontend/src/pages/organisations/ListOrganisations.tsx
@@ -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>
diff --git a/chartered-web/src/endpoints/web_api/organisations/list.rs b/chartered-web/src/endpoints/web_api/organisations/list.rs
new file mode 100644
index 0000000..cf973cf 100644
--- /dev/null
+++ a/chartered-web/src/endpoints/web_api/organisations/list.rs
@@ -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,
}
diff --git a/chartered-web/src/endpoints/web_api/organisations/mod.rs b/chartered-web/src/endpoints/web_api/organisations/mod.rs
index b5ec7cc..5a50049 100644
--- a/chartered-web/src/endpoints/web_api/organisations/mod.rs
+++ a/chartered-web/src/endpoints/web_api/organisations/mod.rs
@@ -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,
};