🏡 index : ~doyle/chartered.git

author Jordan Doyle <jordan@doyle.la> 2022-09-07 22:50:51.0 +01:00:00
committer Jordan Doyle <jordan@doyle.la> 2022-09-07 22:50:51.0 +01:00:00
commit
79104c5592656af92be9cd3794e6d47aff1f4fe8 [patch]
tree
a15bdb037d38387761f75d09c1ddad80c4839417
parent
0b218ab5e60fba0970c07a874364b60fb4706102
download
79104c5592656af92be9cd3794e6d47aff1f4fe8.tar.gz

Run svelte-check in CI



Diff

 chartered-frontend/package.json                                                        |  2 +-
 chartered-frontend/src/global.d.ts                                                     |  5 -----
 chartered-frontend/src/util.ts                                                         |  5 +++++
 chartered-frontend/src/components/Icon.svelte                                          |  6 +++---
 chartered-frontend/src/components/Nav.svelte                                           | 22 +++++++++++++++++++---
 chartered-frontend/src/stores/auth.ts                                                  | 14 +++++++++++---
 chartered-frontend/src/types/crate.ts                                                  | 25 +++++++++++++++++++++++++
 chartered-frontend/src/types/organisations.ts                                          |  7 -------
 chartered-frontend/src/types/ssh_keys.ts                                               |  2 +-
 chartered-frontend/src/types/user.ts                                                   | 10 ++++++++++
 chartered-frontend/src/routes/(unauthed)/+layout.svelte                                |  2 +-
 chartered-frontend/src/routes/(authed)/search/+page.svelte                             |  3 ++-
 chartered-frontend/src/routes/(authed)/ssh-keys/CreateKeyForm.svelte                   |  7 ++++---
 chartered-frontend/src/routes/(authed)/ssh-keys/DeleteSshKeyModal.svelte               |  9 +++++----
 chartered-frontend/src/routes/(authed)/crates/[organisation]/+page.svelte              | 13 +++++++------
 chartered-frontend/src/routes/(authed)/crates/[organisation]/AddMember.svelte          | 20 ++++++++------------
 chartered-frontend/src/routes/(authed)/crates/[organisation]/Member.svelte             | 10 ++++++----
 chartered-frontend/src/routes/(authed)/organisations/create/+page.svelte               |  5 +++--
 chartered-frontend/src/routes/(authed)/organisations/list/+page.svelte                 |  2 +-
 chartered-frontend/src/routes/(authed)/sessions/list/+page.svelte                      |  2 +-
 chartered-frontend/src/routes/(authed)/sessions/list/DeleteSessionModal.svelte         |  9 +++++----
 chartered-frontend/src/routes/(authed)/users/[uuid]/+page.svelte                       |  4 ++--
 chartered-frontend/src/routes/(unauthed)/auth/login/+page.svelte                       |  7 ++++---
 chartered-frontend/src/routes/(unauthed)/auth/register/+page.svelte                    |  5 +++--
 chartered-frontend/src/routes/(unauthed)/login/oauth/+page.svelte                      |  2 +-
 chartered-frontend/src/routes/(authed)/crates/[organisation]/[crate]/+page.svelte      |  8 ++++----
 chartered-frontend/src/routes/(authed)/crates/[organisation]/[crate]/Dependency.svelte |  8 ++++----
 chartered-frontend/src/routes/(authed)/crates/[organisation]/[crate]/MemberTab.svelte  |  7 ++++---
 28 files changed, 139 insertions(+), 82 deletions(-)

diff --git a/chartered-frontend/package.json b/chartered-frontend/package.json
index 002092f..f0de21a 100644
--- a/chartered-frontend/package.json
+++ a/chartered-frontend/package.json
@@ -9,7 +9,7 @@
        "test": "playwright test",
        "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
        "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
        "lint": "prettier --check . && eslint .",
        "lint": "prettier --check . && eslint . && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
        "format": "prettier --write ."
    },

    "devDependencies": {
diff --git a/chartered-frontend/src/global.d.ts b/chartered-frontend/src/global.d.ts
deleted file mode 100644
index d79cb03..0000000 100644
--- a/chartered-frontend/src/global.d.ts
+++ /dev/null
@@ -1,5 +1,0 @@
declare global {
    interface Window {
        extendSessionInterval: nodejs.Timer;
    }
}
diff --git a/chartered-frontend/src/util.ts b/chartered-frontend/src/util.ts
new file mode 100644
index 0000000..0ab5645 100644
--- /dev/null
+++ a/chartered-frontend/src/util.ts
@@ -1,0 +1,5 @@
export function getErrorMessage(error: unknown) {
    if (error instanceof Error) return error.message;

    return String(error);
}
diff --git a/chartered-frontend/src/components/Icon.svelte b/chartered-frontend/src/components/Icon.svelte
index 7d687fa..96e3425 100644
--- a/chartered-frontend/src/components/Icon.svelte
+++ a/chartered-frontend/src/components/Icon.svelte
@@ -1,11 +1,11 @@
<script type="typescript">
    import feather from 'feather-icons';
    export const directions = ['n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'];

    export let name;
    export let name: string;
    export let direction = 'n';
    export let strokeWidth = null;
    export let stroke = null;
    export let strokeWidth: string | null = null;
    export let stroke: string | null = null;
    export let width = '1em';
    export let height = '1em';

diff --git a/chartered-frontend/src/components/Nav.svelte b/chartered-frontend/src/components/Nav.svelte
index e50de2d..7a450d2 100644
--- a/chartered-frontend/src/components/Nav.svelte
+++ a/chartered-frontend/src/components/Nav.svelte
@@ -25,6 +25,20 @@
    async function performSearch() {
        await goto(`/search?q=${search}`);
    }

    /**
     * Prevents the dropdown from automatically closing if it was a non-link element
     * that was clicked.
     *
     * @param e mouse click event
     */
    function handleDropdownClick(e: MouseEvent) {
        let tagName = (e.target as HTMLElement)?.tagName.toLowerCase();

        if (tagName !== 'a' && tagName != 'button') {
            e.stopPropagation();
        }
    }
</script>

<svelte:window on:click={() => (userDropdownShown = false)} />
@@ -68,8 +82,8 @@
                    class="ml-2 relative flex items-center text-gray-300 dark:text-inherit"
                >
                    <span class="sr-only">Open user menu</span>
                    {#if $auth.picture_url}
                        <img alt="You" src={$auth.picture_url} class="rounded-[50%] h-[2.4rem] inline mr-0.5" />
                    {#if $auth?.picture_url}
                        <img alt="You" src={$auth?.picture_url} class="rounded-[50%] h-[2.4rem] inline mr-0.5" />
                    {:else}
                        <div
                            class="h-[2.4rem] w-[2.4rem] rounded-[50%] inline mr-0.5 bg-gray-100 dark:bg-gray-800 overflow-hidden"
@@ -83,12 +97,12 @@
                <div
                    class:hidden={!userDropdownShown}
                    class="absolute top-7 right-0 z-50 w-[10rem] my-4 text-base list-none bg-white rounded divide-y divide-gray-100 shadow dark:bg-gray-700 dark:divide-gray-600"
                    on:click={(e) => (e.target.tagName.toLowerCase() === 'a' ? null : e.stopPropagation())}
                    on:click={handleDropdownClick}
                >
                    <ul class="py-1">
                        <li>
                            <a
                                href={`/users/${$auth.uuid}`}
                                href={`/users/${$auth?.uuid}`}
                                class="block py-2 px-4 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white"
                            >
                                Profile
diff --git a/chartered-frontend/src/stores/auth.ts b/chartered-frontend/src/stores/auth.ts
index 2735d15..6b80621 100644
--- a/chartered-frontend/src/stores/auth.ts
+++ a/chartered-frontend/src/stores/auth.ts
@@ -1,5 +1,6 @@
import { get, writable } from 'svelte/store';
import { goto } from '$app/navigation';
import { getErrorMessage } from '../util';

/**
 * The base URL of the chartered-web instance
@@ -82,7 +83,7 @@
        // if the user's token is invalid for whatever reason after trying to refresh it,
        // we should log them out. otherwise, we don't really know how to handle the error,
        // and we should just print the error out to the console
        if (e === 'Expired auth token') {
        if (getErrorMessage(e) === 'Expired auth token') {
            auth.set(null);
        } else {
            console.error('Failed to extend user session', e);
@@ -90,6 +91,12 @@
    }
}

declare global {
    interface Window {
        extendSessionInterval: NodeJS.Timer;
    }
}

// start the cron loop to extend the session every minute
if (!window.extendSessionInterval) {
    extendSession();
@@ -245,8 +252,9 @@
/**
 * Grab all the possible authentication methods from the backend.
 */
export function fetchOAuthProviders(): Promise<OAuthProviders> {
    return fetch(`${BASE_URL}/a/-/web/v1/auth/login/oauth/providers`).then((v) => v.json());
export async function fetchOAuthProviders(): Promise<OAuthProviders> {
    const result = await fetch(`${BASE_URL}/a/-/web/v1/auth/login/oauth/providers`);
    return await result.json();
}

/**
diff --git a/chartered-frontend/src/types/crate.ts b/chartered-frontend/src/types/crate.ts
index 34be5a8..661975b 100644
--- a/chartered-frontend/src/types/crate.ts
+++ a/chartered-frontend/src/types/crate.ts
@@ -29,3 +29,28 @@
    display_name: string;
    picture_url?: string;
}

export interface Search {
    crates: SearchCrate[];
}

export interface SearchCrate {
    organisation: string;
    name: string;
    description?: string;
    version: string;
    homepage?: string;
    repository?: string;
}

export interface CrateMembers {
    possible_permissions: string[];
    members: CrateMember[];
}

export interface CrateMember {
    uuid: string;
    display_name: string;
    picture_url?: string;
    permissions: string[];
}
diff --git a/chartered-frontend/src/types/organisations.ts b/chartered-frontend/src/types/organisations.ts
index 2e13252..2c9ff78 100644
--- a/chartered-frontend/src/types/organisations.ts
+++ a/chartered-frontend/src/types/organisations.ts
@@ -16,10 +16,3 @@
    name: string;
    description: string;
}

export interface OrganisationMember {
    uuid: string;
    display_name: string;
    picture_url?: string;
    permissions: string[];
}
diff --git a/chartered-frontend/src/types/ssh_keys.ts b/chartered-frontend/src/types/ssh_keys.ts
index 5b54637..fca98a0 100644
--- a/chartered-frontend/src/types/ssh_keys.ts
+++ a/chartered-frontend/src/types/ssh_keys.ts
@@ -1,8 +1,8 @@
/**
 * The result of a `GET /web/v1/ssh-key`
 */
export interface SshKeys {
    keys: { [k: number]: SshKey };
    keys: SshKey[];
}

/**
diff --git a/chartered-frontend/src/types/user.ts b/chartered-frontend/src/types/user.ts
index 66937f4..44cbfc3 100644
--- a/chartered-frontend/src/types/user.ts
+++ a/chartered-frontend/src/types/user.ts
@@ -7,3 +7,13 @@
    external_profile_url?: string;
    picture_url?: string;
}

export interface UserSearch {
    users: UserSearchUser[];
}

export interface UserSearchUser {
    user_uuid: string;
    display_name: string;
    picture_url: string;
}
diff --git a/chartered-frontend/src/routes/(unauthed)/+layout.svelte b/chartered-frontend/src/routes/(unauthed)/+layout.svelte
index f5d4441..009c4cf 100644
--- a/chartered-frontend/src/routes/(unauthed)/+layout.svelte
+++ a/chartered-frontend/src/routes/(unauthed)/+layout.svelte
@@ -1,5 +1,5 @@
<script type="typescript">
    import { auth } from '../../stores/auth.ts';
    import { auth } from '../../stores/auth';
    import { goto } from '$app/navigation';

    $: if ($auth) {
diff --git a/chartered-frontend/src/routes/(authed)/search/+page.svelte b/chartered-frontend/src/routes/(authed)/search/+page.svelte
index 62488f5..7133603 100644
--- a/chartered-frontend/src/routes/(authed)/search/+page.svelte
+++ a/chartered-frontend/src/routes/(authed)/search/+page.svelte
@@ -1,9 +1,10 @@
<script type="typescript">
    import { page } from '$app/stores';
    import { request } from '../../../stores/auth';
    import Spinner from '../../../components/Spinner.svelte';
    import type { Search } from '../../../types/crate';

    let searchPromise;
    let searchPromise: Promise<Search>;
    $: searchPromise = request(`/web/v1/crates/search?q=${$page.url.searchParams.get('q')}`);
</script>

diff --git a/chartered-frontend/src/routes/(authed)/ssh-keys/CreateKeyForm.svelte b/chartered-frontend/src/routes/(authed)/ssh-keys/CreateKeyForm.svelte
index 43b73df..07e3e6a 100644
--- a/chartered-frontend/src/routes/(authed)/ssh-keys/CreateKeyForm.svelte
+++ a/chartered-frontend/src/routes/(authed)/ssh-keys/CreateKeyForm.svelte
@@ -5,6 +5,7 @@
    import { createEventDispatcher } from 'svelte';
    import Spinner from '../../../components/Spinner.svelte';
    import ErrorAlert from '../../../components/ErrorAlert.svelte';
    import { getErrorMessage } from '../../../util';

    // set up the event dispatcher for alerting the main page when the user has successfully
    // added a key, so it can reload the full key list from the backend again
@@ -25,7 +26,7 @@
     * Any errors that came of the last submission. If this is not null the user will be shown
     * an alert.
     */
    let error = null;
    let error: string | null = null;

    /**
     * Submits an SSH key to the server to attempt to add it to the user, all our validation
@@ -40,7 +41,7 @@

        try {
            // submit the key to the backend
            let result = await fetch(`${BASE_URL}/a/${$auth.auth_key}/web/v1/ssh-key`, {
            let result = await fetch(`${BASE_URL}/a/${$auth?.auth_key}/web/v1/ssh-key`, {
                method: 'PUT',
                headers: {
                    'Content-Type': 'application/json',
@@ -58,7 +59,7 @@
            sshKey = '';
            dispatch('complete');
        } catch (e) {
            error = e.toString();
            error = getErrorMessage(e);
        } finally {
            // stop showing the spinner since we've finished working
            submitting = false;
diff --git a/chartered-frontend/src/routes/(authed)/ssh-keys/DeleteSshKeyModal.svelte b/chartered-frontend/src/routes/(authed)/ssh-keys/DeleteSshKeyModal.svelte
index 873946b..9e34b34 100644
--- a/chartered-frontend/src/routes/(authed)/ssh-keys/DeleteSshKeyModal.svelte
+++ a/chartered-frontend/src/routes/(authed)/ssh-keys/DeleteSshKeyModal.svelte
@@ -6,6 +6,7 @@
    import RelativeTime from '../../../components/RelativeTime.svelte';
    import Spinner from '../../../components/Spinner.svelte';
    import { auth, BASE_URL } from '../../../stores/auth';
    import { getErrorMessage } from '../../../util';

    const dispatch = createEventDispatcher();

@@ -18,7 +19,7 @@
     * Any errors that came of the last submission. If this is not null the user will be shown
     * an alert.
     */
    let deleteError = null;
    let deleteError: string | null = null;

    /**
     * Simple boolean flag to determine if a key is currently being submitted, so we can
@@ -29,7 +30,7 @@
    /**
     * Binds helper keys for the modal, so we can provide some expected functions of a modal.
     */
    function handleKeydown(event) {
    function handleKeydown(event: KeyboardEvent) {
        if (event.key == 'Escape') {
            closeModal();
        }
@@ -48,7 +49,7 @@

        try {
            // submit deletion request to backend
            let res = await fetch(`${BASE_URL}/a/${$auth.auth_key}/web/v1/ssh-key/${deleting.uuid}`, {
            let res = await fetch(`${BASE_URL}/a/${$auth?.auth_key}/web/v1/ssh-key/${deleting.uuid}`, {
                method: 'DELETE',
            });
            let json: DeleteSshKeyResult = await res.json();
@@ -64,7 +65,7 @@
            closeModal();
        } catch (e) {
            isDeleting = false;
            deleteError = e.toString();
            deleteError = getErrorMessage(e);
        }
    }

diff --git a/chartered-frontend/src/routes/(authed)/crates/[organisation]/+page.svelte b/chartered-frontend/src/routes/(authed)/crates/[organisation]/+page.svelte
index 7ded578..7928ca9 100644
--- a/chartered-frontend/src/routes/(authed)/crates/[organisation]/+page.svelte
+++ a/chartered-frontend/src/routes/(authed)/crates/[organisation]/+page.svelte
@@ -7,12 +7,13 @@
    import type { OrganisationDetail } from '../../../../types/organisations';
    import Member from './Member.svelte';
    import AddMember from './AddMember.svelte';
    import type { CrateMembers, CrateMember } from '../../../../types/crate';

    let organisationPromise;
    $: organisationPromise = request<OrganisationDetail>(`/web/v1/organisations/${$page.params.organisation}`);
    let organisationPromise: Promise<OrganisationDetail & CrateMembers>;
    $: organisationPromise = request(`/web/v1/organisations/${$page.params.organisation}`);

    function reload(event) {
        organisationPromise = request<OrganisationDetail>(`/web/v1/organisations/${$page.params.organisation}`);
    function reload(event: { detail: string }) {
        organisationPromise = request(`/web/v1/organisations/${$page.params.organisation}`);

        if (newMember && event.detail === newMember.uuid) {
            newMember = null;
@@ -38,7 +39,7 @@
    ];

    let currentTab = Tab.CRATES;
    let newMember = null;
    let newMember: CrateMember | null = null;
</script>

<header>
@@ -138,7 +139,7 @@
                {/if}
            {:else if currentTab === Tab.MEMBERS}
                <div class="card p-0 divide-y card-divide">
                    {#each organisation.members as member, i}
                    {#each organisation.members as member}
                        <Member
                            {member}
                            organisation={$page.params.organisation}
diff --git a/chartered-frontend/src/routes/(authed)/crates/[organisation]/AddMember.svelte b/chartered-frontend/src/routes/(authed)/crates/[organisation]/AddMember.svelte
index 5d515ad..ad6e964 100644
--- a/chartered-frontend/src/routes/(authed)/crates/[organisation]/AddMember.svelte
+++ a/chartered-frontend/src/routes/(authed)/crates/[organisation]/AddMember.svelte
@@ -1,9 +1,10 @@
<script type="typescript">
    import { debounce } from 'lodash';
    import { auth, BASE_URL } from '../../../../stores/auth';
    import { request } from '../../../../stores/auth';
    import Spinner from '../../../../components/Spinner.svelte';
    import Icon from '../../../../components/Icon.svelte';
    import { createEventDispatcher } from 'svelte';
    import type { UserSearch, UserSearchUser } from '../../../../types/user';

    const dispatch = createEventDispatcher();

@@ -12,7 +13,7 @@
    let loading = false;

    let search = '';
    let searchResults = [];
    let searchResults: UserSearchUser[] = [];
    $: performSearch(search);

    const onInput = debounce((event) => {
@@ -27,22 +28,17 @@
        loading = true;

        try {
            let result = await fetch(`${BASE_URL}/a/${$auth.auth_key}/web/v1/users/search?q=${search}`);
            let json = await result.json();

            if (json.error) {
                throw new Error(json.error);
            }

            searchResults = json.users || [];
        } catch (e) {
            let result = await request<UserSearch>(`/web/v1/users/search?q=${search}`);

            searchResults = result.users || [];
        } catch (e: unknown) {
            console.log(e);
        } finally {
            loading = false;
        }
    }

    function dispatchNewMember(member) {
    function dispatchNewMember(member: UserSearchUser) {
        dispatch('new', member);
        searchResults = [];
        search = '';
diff --git a/chartered-frontend/src/routes/(authed)/crates/[organisation]/Member.svelte b/chartered-frontend/src/routes/(authed)/crates/[organisation]/Member.svelte
index 716adc5..c2af427 100644
--- a/chartered-frontend/src/routes/(authed)/crates/[organisation]/Member.svelte
+++ a/chartered-frontend/src/routes/(authed)/crates/[organisation]/Member.svelte
@@ -5,6 +5,8 @@
    import { auth, BASE_URL } from '../../../../stores/auth';
    import ErrorAlert from '../../../../components/ErrorAlert.svelte';
    import { createEventDispatcher } from 'svelte';
    import type { CrateMember } from '../../../../types/crate';
    import { getErrorMessage } from '../../../../util';

    const dispatch = createEventDispatcher();

@@ -12,13 +14,13 @@

    export let organisation: string;
    export let crate: string | null = null;
    export let member;
    export let member: CrateMember;
    export let newPermissions = member.permissions;
    export let possiblePermissions: string[];
    export { clazz as class };

    let saving = false;
    let error = null;
    let error: string | null = null;

    async function save() {
        saving = true;
@@ -41,7 +43,7 @@
                url = `web/v1/organisations/${organisation}`;
            }

            let result = await fetch(`${BASE_URL}/a/${$auth.auth_key}/${url}/members`, {
            let result = await fetch(`${BASE_URL}/a/${$auth?.auth_key}/${url}/members`, {
                method,
                headers: {
                    Accept: 'application/json',
@@ -62,7 +64,7 @@
            member.permissions = newPermissions;
            dispatch('updated', member.uuid);
        } catch (e) {
            error = e.toString();
            error = getErrorMessage(e);
        } finally {
            saving = false;
        }
diff --git a/chartered-frontend/src/routes/(authed)/organisations/create/+page.svelte b/chartered-frontend/src/routes/(authed)/organisations/create/+page.svelte
index 884331f..67d0020 100644
--- a/chartered-frontend/src/routes/(authed)/organisations/create/+page.svelte
+++ a/chartered-frontend/src/routes/(authed)/organisations/create/+page.svelte
@@ -1,9 +1,10 @@
<script type="typescript">
    import Icon from '../../../../components/Icon.svelte';
    import ErrorAlert from '../../../../components/ErrorAlert.svelte';
    import { BASE_URL, auth } from '../../../../stores/auth';
    import { goto } from '$app/navigation';
    import Spinner from '../../../../components/Spinner.svelte';
    import { getErrorMessage } from '../../../../util';

    /**
     * Binding to the name field in the form.
@@ -43,7 +44,7 @@

        try {
            // attempt the actual creation of the organisation
            let result = await fetch(`${BASE_URL}/a/${$auth.auth_key}/web/v1/organisations`, {
            let result = await fetch(`${BASE_URL}/a/${$auth?.auth_key}/web/v1/organisations`, {
                method: 'PUT',
                headers: {
                    'Content-Type': 'application/json',
@@ -59,7 +60,7 @@
            // successful creation, return the user back to the list
            await goto('/organisations/list');
        } catch (e) {
            error = e.toString();
            error = getErrorMessage(e);
        } finally {
            // stop showing the spinner once creation is complete
            submitting = false;
diff --git a/chartered-frontend/src/routes/(authed)/organisations/list/+page.svelte b/chartered-frontend/src/routes/(authed)/organisations/list/+page.svelte
index 899b19d..5a6e8d7 100644
--- a/chartered-frontend/src/routes/(authed)/organisations/list/+page.svelte
+++ a/chartered-frontend/src/routes/(authed)/organisations/list/+page.svelte
@@ -33,7 +33,7 @@
            class:hidden={organisations.organisations.length === 0}
            class="mb-4 grid md:grid-cols-2 lg:grid-cols-4 gap-2"
        >
            {#each organisations.organisations as organisation, i}
            {#each organisations.organisations as organisation}
                <a class="card flex space-x-2 items-center" href={`/crates/${organisation.name}`}>
                    <div class="flex-grow h-full">
                        <h5 class="text-highlight card-header">{organisation.name}</h5>
diff --git a/chartered-frontend/src/routes/(authed)/sessions/list/+page.svelte b/chartered-frontend/src/routes/(authed)/sessions/list/+page.svelte
index 2512b2f..961347a 100644
--- a/chartered-frontend/src/routes/(authed)/sessions/list/+page.svelte
+++ a/chartered-frontend/src/routes/(authed)/sessions/list/+page.svelte
@@ -77,7 +77,7 @@
    <DeleteSessionModal {deleting} on:complete={reloadSessions} on:close={() => (deleting = null)} />
{/if}

<style>
<style lang="postcss">
    th,
    td {
        @apply text-left px-5;
diff --git a/chartered-frontend/src/routes/(authed)/sessions/list/DeleteSessionModal.svelte b/chartered-frontend/src/routes/(authed)/sessions/list/DeleteSessionModal.svelte
index 2a686fd..a39ca69 100644
--- a/chartered-frontend/src/routes/(authed)/sessions/list/DeleteSessionModal.svelte
+++ a/chartered-frontend/src/routes/(authed)/sessions/list/DeleteSessionModal.svelte
@@ -5,6 +5,7 @@
    import Spinner from '../../../../components/Spinner.svelte';
    import { auth, BASE_URL } from '../../../../stores/auth';
    import type { Session } from '../../../../types/sessions';
    import { getErrorMessage } from '../../../../util';

    const dispatch = createEventDispatcher();

@@ -17,7 +18,7 @@
     * Any errors that came of the last submission. If this is not null the user will be shown
     * an alert.
     */
    let deleteError = null;
    let deleteError: string | null = null;

    /**
     * Simple boolean flag to determine if a session is currently being submitted, so we can
@@ -28,7 +29,7 @@
    /**
     * Binds helper keys for the modal, so we can provide some expected functions of a modal.
     */
    function handleKeydown(event) {
    function handleKeydown(event: KeyboardEvent) {
        if (event.key == 'Escape') {
            closeModal();
        }
@@ -47,7 +48,7 @@

        try {
            // submit deletion request to backend
            let res = await fetch(`${BASE_URL}/a/${$auth.auth_key}/web/v1/sessions`, {
            let res = await fetch(`${BASE_URL}/a/${$auth?.auth_key}/web/v1/sessions`, {
                method: 'DELETE',
                headers: {
                    'Content-Type': 'application/json',
@@ -67,7 +68,7 @@
            closeModal();
        } catch (e) {
            isDeleting = false;
            deleteError = e.toString();
            deleteError = getErrorMessage(e);
        }
    }

diff --git a/chartered-frontend/src/routes/(authed)/users/[uuid]/+page.svelte b/chartered-frontend/src/routes/(authed)/users/[uuid]/+page.svelte
index 0da406b..889c1ee 100644
--- a/chartered-frontend/src/routes/(authed)/users/[uuid]/+page.svelte
+++ a/chartered-frontend/src/routes/(authed)/users/[uuid]/+page.svelte
@@ -5,9 +5,9 @@
    import Icon from '../../../../components/Icon.svelte';
    import type { User } from '../../../../types/user';

    let userPromise;
    let userPromise: Promise<User & { displayName?: string }>;
    $: userPromise = request<User>(`/web/v1/users/info/${$page.params.uuid}`).then(
        (user: User & { displayName: string }) => {
        (user: User & { displayName?: string }) => {
            user.displayName = user.nick || user.name || user.username;
            return user;
        },
diff --git a/chartered-frontend/src/routes/(unauthed)/auth/login/+page.svelte b/chartered-frontend/src/routes/(unauthed)/auth/login/+page.svelte
index 42b3c8e..0e59b22 100644
--- a/chartered-frontend/src/routes/(unauthed)/auth/login/+page.svelte
+++ a/chartered-frontend/src/routes/(unauthed)/auth/login/+page.svelte
@@ -1,8 +1,9 @@
<script type="typescript">
    import { loginOAuth, login, fetchOAuthProviders } from '../../../../stores/auth';
    import Spinner from '../../../../components/Spinner.svelte';
    import ErrorAlert from '../../../../components/ErrorAlert.svelte';
    import { goto } from '$app/navigation';
    import { getErrorMessage } from '../../../../util';

    /**
     * Once OAuth providers are loaded this will contain whether password auth
@@ -20,7 +21,7 @@
    /**
     * Displays an error to the user, if empty will hide the error popup.
     */
    let error = null;
    let error: string | null = null;

    /**
     * A binding to the username field in the form.
@@ -42,7 +43,7 @@
        try {
            await login(username, password);
        } catch (e) {
            error = e.toString();
            error = getErrorMessage(e);
        } finally {
            // behave like a real application and set the password to empty so
            // if the user fails authentication they can type it in again
@@ -72,7 +73,7 @@
            // attempt to redirect the user to the OAuth login page
            await loginOAuth(provider);
        } catch (e) {
            error = e.toString();
            error = getErrorMessage(e);
        } finally {
            loginInProgress = false;
        }
diff --git a/chartered-frontend/src/routes/(unauthed)/auth/register/+page.svelte b/chartered-frontend/src/routes/(unauthed)/auth/register/+page.svelte
index 447b523..e0f9e9f 100644
--- a/chartered-frontend/src/routes/(unauthed)/auth/register/+page.svelte
+++ a/chartered-frontend/src/routes/(unauthed)/auth/register/+page.svelte
@@ -1,8 +1,9 @@
<script type="typescript">
    import Spinner from '../../../../components/Spinner.svelte';
    import ErrorAlert from '../../../../components/ErrorAlert.svelte';
    import { register } from '../../../../stores/auth';
    import { goto } from '$app/navigation';
    import { getErrorMessage } from '../../../../util';

    /**
     * Displays a spinner while registration is being performed.
@@ -12,7 +13,7 @@
    /**
     * Displays an error message to the user above the form if set to a string
     */
    let error = null;
    let error: string | null = null;

    /**
     * The username bound to the corresponding form field
@@ -44,7 +45,7 @@
            // redirect the user back to the login
            await goto('/auth/login');
        } catch (e) {
            error = e.toString();
            error = getErrorMessage(e);
        } finally {
            // stop displaying the spinner since we've finished attempting to register the
            // user
diff --git a/chartered-frontend/src/routes/(unauthed)/login/oauth/+page.svelte b/chartered-frontend/src/routes/(unauthed)/login/oauth/+page.svelte
index 924cf95..9758c10 100644
--- a/chartered-frontend/src/routes/(unauthed)/login/oauth/+page.svelte
+++ a/chartered-frontend/src/routes/(unauthed)/login/oauth/+page.svelte
@@ -10,7 +10,7 @@
<div class="h-[18rem]">
    {#await callback}
        <Spinner />
    {:then x}
    {:then}
        <Spinner />
    {:catch error}
        <!-- todo: redirect back to login -->
diff --git a/chartered-frontend/src/routes/(authed)/crates/[organisation]/[crate]/+page.svelte b/chartered-frontend/src/routes/(authed)/crates/[organisation]/[crate]/+page.svelte
index 1fdd841..5341842 100644
--- a/chartered-frontend/src/routes/(authed)/crates/[organisation]/[crate]/+page.svelte
+++ a/chartered-frontend/src/routes/(authed)/crates/[organisation]/[crate]/+page.svelte
@@ -33,8 +33,8 @@
        },
    ];

    let cratePromise;
    $: cratePromise = request<Crate>(`/web/v1/crates/${$page.params.organisation}/${$page.params.crate}`);
    let cratePromise: Promise<Crate>;
    $: cratePromise = request(`/web/v1/crates/${$page.params.organisation}/${$page.params.crate}`);

    let currentTab = Tab.README;
</script>
@@ -129,7 +129,7 @@
                <h1 class="text-xl p-3 border-b border-gray-200 dark:border-gray-700 font-medium">Dependencies</h1>

                <div class="divide-y divide-gray-200 dark:divide-gray-700">
                    {#each crate.versions[0].deps as dependency, i}
                    {#each crate.versions[0].deps as dependency}
                        <Dependency {dependency} class="p-3" />
                    {/each}
                </div>
@@ -138,7 +138,7 @@
    {/await}
</main>

<style>
<style lang="postcss">
    .card-header-button {
        @apply inline-flex items-center gap-x-1;
    }
diff --git a/chartered-frontend/src/routes/(authed)/crates/[organisation]/[crate]/Dependency.svelte b/chartered-frontend/src/routes/(authed)/crates/[organisation]/[crate]/Dependency.svelte
index 9ac1d34..0a84c5e 100644
--- a/chartered-frontend/src/routes/(authed)/crates/[organisation]/[crate]/Dependency.svelte
+++ a/chartered-frontend/src/routes/(authed)/crates/[organisation]/[crate]/Dependency.svelte
@@ -6,10 +6,10 @@
    export let dependency: VersionDependency;
    export { clazz as class };

    function getLocalDependencyOrganisation(): string {
        const s = dependency.registry.split('/');
    function getLocalDependencyOrganisation(): string | undefined {
        const s = dependency.registry?.split('/');

        return s[s.length - 1];
        return s ? s[s.length - 1] : dependency.registry;
    }
</script>

@@ -18,7 +18,7 @@
        <a href={`https://crates.io/crates/${dependency.name}`} target="_blank">
            {dependency.name}
        </a>
    {:else if dependency.registry.indexOf('ssh://') === 0}
    {:else if dependency.registry?.indexOf('ssh://') === 0}
        <a href={`/crates/${getLocalDependencyOrganisation()}/${dependency.name}`} target="_blank">
            {dependency.name}
        </a>
diff --git a/chartered-frontend/src/routes/(authed)/crates/[organisation]/[crate]/MemberTab.svelte b/chartered-frontend/src/routes/(authed)/crates/[organisation]/[crate]/MemberTab.svelte
index 37782bd..d1af600 100644
--- a/chartered-frontend/src/routes/(authed)/crates/[organisation]/[crate]/MemberTab.svelte
+++ a/chartered-frontend/src/routes/(authed)/crates/[organisation]/[crate]/MemberTab.svelte
@@ -1,12 +1,13 @@
<script type="typescript">
    import { request } from '../../../../../stores/auth';
    import { page } from '$app/stores';
    import AddMember from '../AddMember.svelte';
    import Member from '../Member.svelte';
    import type { CrateMembers, CrateMember } from '../../../../../types/crate';

    let newMember = null;
    let newMember: CrateMember | null = null;

    let membersPromise;
    let membersPromise: Promise<CrateMembers>;
    $: membersPromise = request(`/web/v1/crates/${$page.params.organisation}/${$page.params.crate}/members`);

    function reloadMembers() {
@@ -17,7 +18,7 @@

{#await membersPromise then members}
    <div class="card-divide divide-y">
        {#each members.members as member, i}
        {#each members.members as member}
            <Member
                {member}
                organisation={$page.params.organisation}