about summary refs log tree commit diff
diff options
context:
space:
mode:
authorJohn Nduli2024-10-18 15:06:34 +0300
committerFrederick Muriuki Muriithi2024-10-18 09:07:16 -0500
commit1aeb61f50567e2400c3cc1a18eeef1e59bdc68ac (patch)
tree7a4624659f735980345cf10aae101f9e6ec94deb
parent0820295202c2fe747c05b93ce0f1c5a604442f69 (diff)
downloadgenenetwork3-1aeb61f50567e2400c3cc1a18eeef1e59bdc68ac.tar.gz
refactor: remove unused gn3.auth modules
-rw-r--r--gn3/auth/authorisation/checks.py69
-rw-r--r--gn3/auth/authorisation/data/__init__.py0
-rw-r--r--gn3/auth/authorisation/data/genotypes.py96
-rw-r--r--gn3/auth/authorisation/data/mrna.py100
-rw-r--r--gn3/auth/authorisation/data/phenotypes.py140
-rw-r--r--gn3/auth/authorisation/data/views.py310
-rw-r--r--gn3/auth/authorisation/groups/__init__.py3
-rw-r--r--gn3/auth/authorisation/groups/data.py106
-rw-r--r--gn3/auth/authorisation/groups/models.py400
-rw-r--r--gn3/auth/authorisation/groups/views.py430
-rw-r--r--gn3/auth/authorisation/oauth2/__init__.py1
-rw-r--r--gn3/auth/authorisation/oauth2/oauth2client.py234
-rw-r--r--gn3/auth/authorisation/oauth2/oauth2token.py133
-rw-r--r--gn3/auth/authorisation/oauth2/resource_server.py19
-rw-r--r--gn3/auth/authorisation/privileges.py47
-rw-r--r--gn3/auth/authorisation/resources/__init__.py2
-rw-r--r--gn3/auth/authorisation/resources/checks.py47
-rw-r--r--gn3/auth/authorisation/resources/models.py579
-rw-r--r--gn3/auth/authorisation/resources/views.py272
-rw-r--r--gn3/auth/authorisation/roles/__init__.py3
-rw-r--r--gn3/auth/authorisation/roles/models.py161
-rw-r--r--gn3/auth/authorisation/roles/views.py25
-rw-r--r--gn3/auth/authorisation/users/__init__.py12
-rw-r--r--gn3/auth/authorisation/users/base.py128
-rw-r--r--gn3/auth/authorisation/users/collections/__init__.py1
-rw-r--r--gn3/auth/authorisation/users/collections/models.py269
-rw-r--r--gn3/auth/authorisation/users/collections/views.py239
-rw-r--r--gn3/auth/authorisation/users/models.py66
-rw-r--r--gn3/auth/authorisation/users/views.py173
-rw-r--r--gn3/auth/views.py16
30 files changed, 0 insertions, 4081 deletions
diff --git a/gn3/auth/authorisation/checks.py b/gn3/auth/authorisation/checks.py
deleted file mode 100644
index 17daca4..0000000
--- a/gn3/auth/authorisation/checks.py
+++ /dev/null
@@ -1,69 +0,0 @@
-"""Functions to check for authorisation."""
-from functools import wraps
-from typing import Callable
-
-from flask import request, current_app as app
-
-from gn3.auth import db
-from gn3.auth.authorisation.oauth2.resource_server import require_oauth
-
-from . import privileges as auth_privs
-from .errors import InvalidData, AuthorisationError
-
-def __system_privileges_in_roles__(conn, user):
-    """
-    This really is a hack since groups are not treated as resources at the
-    moment of writing this.
-
-    We need a way of allowing the user to have the system:group:* privileges.
-    """
-    query = (
-        "SELECT DISTINCT p.* FROM users AS u "
-        "INNER JOIN group_user_roles_on_resources AS guror "
-        "ON u.user_id=guror.user_id "
-        "INNER JOIN roles AS r ON guror.role_id=r.role_id "
-        "INNER JOIN role_privileges AS rp ON r.role_id=rp.role_id "
-        "INNER JOIN privileges AS p ON rp.privilege_id=p.privilege_id "
-        "WHERE u.user_id=? AND p.privilege_id LIKE 'system:%'")
-    with db.cursor(conn) as cursor:
-        cursor.execute(query, (str(user.user_id),))
-        return (row["privilege_id"] for row in cursor.fetchall())
-
-def authorised_p(
-        privileges: tuple[str, ...],
-        error_description: str = (
-            "You lack authorisation to perform requested action"),
-        oauth2_scope = "profile"):
-    """Authorisation decorator."""
-    assert len(privileges) > 0, "You must provide at least one privilege"
-    def __build_authoriser__(func: Callable):
-        @wraps(func)
-        def __authoriser__(*args, **kwargs):
-            # the_user = user or (hasattr(g, "user") and g.user)
-            with require_oauth.acquire(oauth2_scope) as the_token:
-                the_user = the_token.user
-                if the_user:
-                    with db.connection(app.config["AUTH_DB"]) as conn:
-                        user_privileges = tuple(
-                            priv.privilege_id for priv in
-                            auth_privs.user_privileges(conn, the_user)) + tuple(
-                                priv_id for priv_id in
-                                __system_privileges_in_roles__(conn, the_user))
-
-                    not_assigned = [
-                        priv for priv in privileges if priv not in user_privileges]
-                    if len(not_assigned) == 0:
-                        return func(*args, **kwargs)
-
-                raise AuthorisationError(error_description)
-        return __authoriser__
-    return __build_authoriser__
-
-def require_json(func):
-    """Ensure the request has JSON data."""
-    @wraps(func)
-    def __req_json__(*args, **kwargs):
-        if bool(request.json):
-            return func(*args, **kwargs)
-        raise InvalidData("Expected JSON data in the request.")
-    return __req_json__
diff --git a/gn3/auth/authorisation/data/__init__.py b/gn3/auth/authorisation/data/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/gn3/auth/authorisation/data/__init__.py
+++ /dev/null
diff --git a/gn3/auth/authorisation/data/genotypes.py b/gn3/auth/authorisation/data/genotypes.py
deleted file mode 100644
index 8f901a5..0000000
--- a/gn3/auth/authorisation/data/genotypes.py
+++ /dev/null
@@ -1,96 +0,0 @@
-"""Handle linking of Genotype data to the Auth(entic|oris)ation system."""
-import uuid
-from typing import Iterable
-
-from MySQLdb.cursors import DictCursor
-
-import gn3.auth.db as authdb
-import gn3.db_utils as gn3db
-from gn3.auth.dictify import dictify
-from gn3.auth.authorisation.checks import authorised_p
-from gn3.auth.authorisation.groups.models import Group
-
-def linked_genotype_data(conn: authdb.DbConnection) -> Iterable[dict]:
-    """Retrive genotype data that is linked to user groups."""
-    with authdb.cursor(conn) as cursor:
-        cursor.execute("SELECT * FROM linked_genotype_data")
-        return (dict(row) for row in cursor.fetchall())
-
-@authorised_p(("system:data:link-to-group",),
-              error_description=(
-                  "You do not have sufficient privileges to link data to (a) "
-                  "group(s)."),
-              oauth2_scope="profile group resource")
-def ungrouped_genotype_data(# pylint: disable=[too-many-arguments]
-        authconn: authdb.DbConnection, gn3conn: gn3db.Connection,
-        search_query: str, selected: tuple[dict, ...] = tuple(),
-        limit: int = 10000, offset: int = 0) -> tuple[
-            dict, ...]:
-    """Retrieve genotype data that is not linked to any user group."""
-    params = tuple(
-        (row["SpeciesId"], row["InbredSetId"], row["GenoFreezeId"])
-            for row in linked_genotype_data(authconn)) + tuple(
-                    (row["SpeciesId"], row["InbredSetId"], row["GenoFreezeId"])
-                    for row in selected)
-    query = (
-        "SELECT s.SpeciesId, iset.InbredSetId, iset.InbredSetName, "
-        "gf.Id AS GenoFreezeId, gf.Name AS dataset_name, "
-        "gf.FullName AS dataset_fullname, "
-        "gf.ShortName AS dataset_shortname "
-        "FROM Species AS s INNER JOIN InbredSet AS iset "
-        "ON s.SpeciesId=iset.SpeciesId INNER JOIN GenoFreeze AS gf "
-        "ON iset.InbredSetId=gf.InbredSetId ")
-
-    if len(params) > 0 or bool(search_query):
-        query = query + "WHERE "
-
-    if len(params) > 0:
-        paramstr = ", ".join(["(%s, %s, %s)"] * len(params))
-        query = query + (
-            "(s.SpeciesId, iset.InbredSetId, gf.Id) "
-            f"NOT IN ({paramstr}) "
-            ) + ("AND " if bool(search_query) else "")
-
-    if bool(search_query):
-        query = query + (
-            "CONCAT(gf.Name, ' ', gf.FullName, ' ', gf.ShortName) LIKE %s ")
-        params = params + ((f"%{search_query}%",),)# type: ignore[operator]
-
-    query = query + f"LIMIT {int(limit)} OFFSET {int(offset)}"
-    with gn3conn.cursor(DictCursor) as cursor:
-        cursor.execute(
-            query, tuple(item for sublist in params for item in sublist))
-        return tuple(row for row in cursor.fetchall())
-
-@authorised_p(
-    ("system:data:link-to-group",),
-    error_description=(
-        "You do not have sufficient privileges to link data to (a) "
-        "group(s)."),
-    oauth2_scope="profile group resource")
-def link_genotype_data(
-        conn: authdb.DbConnection, group: Group, datasets: dict) -> dict:
-    """Link genotye `datasets` to `group`."""
-    with authdb.cursor(conn) as cursor:
-        cursor.executemany(
-            "INSERT INTO linked_genotype_data VALUES "
-            "(:data_link_id, :group_id, :SpeciesId, :InbredSetId, "
-            ":GenoFreezeId, :dataset_name, :dataset_fullname, "
-            ":dataset_shortname) "
-            "ON CONFLICT (SpeciesId, InbredSetId, GenoFreezeId) DO NOTHING",
-            tuple({
-                "data_link_id": str(uuid.uuid4()),
-                "group_id": str(group.group_id),
-                **{
-                    key: value for key,value in dataset.items() if key in (
-                        "GenoFreezeId", "InbredSetId", "SpeciesId",
-                        "dataset_fullname", "dataset_name", "dataset_shortname")
-                }
-            } for dataset in datasets))
-        return {
-            "description": (
-                f"Successfully linked {len(datasets)} to group "
-                f"'{group.group_name}'."),
-            "group": dictify(group),
-            "datasets": datasets
-        }
diff --git a/gn3/auth/authorisation/data/mrna.py b/gn3/auth/authorisation/data/mrna.py
deleted file mode 100644
index bdfc5c1..0000000
--- a/gn3/auth/authorisation/data/mrna.py
+++ /dev/null
@@ -1,100 +0,0 @@
-"""Handle linking of mRNA Assay data to the Auth(entic|oris)ation system."""
-import uuid
-from typing import Iterable
-from MySQLdb.cursors import DictCursor
-
-import gn3.auth.db as authdb
-import gn3.db_utils as gn3db
-from gn3.auth.dictify import dictify
-from gn3.auth.authorisation.checks import authorised_p
-from gn3.auth.authorisation.groups.models import Group
-
-def linked_mrna_data(conn: authdb.DbConnection) -> Iterable[dict]:
-    """Retrieve mRNA Assay data that is linked to user groups."""
-    with authdb.cursor(conn) as cursor:
-        cursor.execute("SELECT * FROM linked_mrna_data")
-        return (dict(row) for row in cursor.fetchall())
-
-@authorised_p(("system:data:link-to-group",),
-              error_description=(
-                  "You do not have sufficient privileges to link data to (a) "
-                  "group(s)."),
-              oauth2_scope="profile group resource")
-def ungrouped_mrna_data(# pylint: disable=[too-many-arguments]
-        authconn: authdb.DbConnection, gn3conn: gn3db.Connection,
-        search_query: str, selected: tuple[dict, ...] = tuple(),
-        limit: int = 10000, offset: int = 0) -> tuple[
-            dict, ...]:
-    """Retrieve mrna data that is not linked to any user group."""
-    params = tuple(
-        (row["SpeciesId"], row["InbredSetId"], row["ProbeFreezeId"],
-         row["ProbeSetFreezeId"])
-        for row in linked_mrna_data(authconn)) + tuple(
-                (row["SpeciesId"], row["InbredSetId"], row["ProbeFreezeId"],
-                 row["ProbeSetFreezeId"])
-                for row in selected)
-    query = (
-        "SELECT s.SpeciesId, iset.InbredSetId, iset.InbredSetName, "
-        "pf.ProbeFreezeId, pf.Name AS StudyName, psf.Id AS ProbeSetFreezeId, "
-        "psf.Name AS dataset_name, psf.FullName AS dataset_fullname, "
-        "psf.ShortName AS dataset_shortname "
-        "FROM Species AS s INNER JOIN InbredSet AS iset "
-        "ON s.SpeciesId=iset.SpeciesId INNER JOIN ProbeFreeze AS pf "
-        "ON iset.InbredSetId=pf.InbredSetId INNER JOIN ProbeSetFreeze AS psf "
-        "ON pf.ProbeFreezeId=psf.ProbeFreezeId ") + (
-            "WHERE " if (len(params) > 0 or bool(search_query)) else "")
-
-    if len(params) > 0:
-        paramstr = ", ".join(["(%s, %s, %s, %s)"] * len(params))
-        query = query + (
-            "(s.SpeciesId, iset.InbredSetId, pf.ProbeFreezeId, psf.Id) "
-            f"NOT IN ({paramstr}) "
-            ) + ("AND " if bool(search_query) else "")
-
-    if bool(search_query):
-        query = query + (
-            "CONCAT(pf.Name, psf.Name, ' ', psf.FullName, ' ', psf.ShortName) "
-            "LIKE %s ")
-        params = params + ((f"%{search_query}%",),)# type: ignore[operator]
-
-    query = query + f"LIMIT {int(limit)} OFFSET {int(offset)}"
-    with gn3conn.cursor(DictCursor) as cursor:
-        cursor.execute(
-            query, tuple(item for sublist in params for item in sublist))
-        return tuple(row for row in cursor.fetchall())
-
-@authorised_p(
-    ("system:data:link-to-group",),
-    error_description=(
-        "You do not have sufficient privileges to link data to (a) "
-        "group(s)."),
-    oauth2_scope="profile group resource")
-def link_mrna_data(
-        conn: authdb.DbConnection, group: Group, datasets: dict) -> dict:
-    """Link genotye `datasets` to `group`."""
-    with authdb.cursor(conn) as cursor:
-        cursor.executemany(
-            "INSERT INTO linked_mrna_data VALUES "
-            "(:data_link_id, :group_id, :SpeciesId, :InbredSetId, "
-            ":ProbeFreezeId, :ProbeSetFreezeId, :dataset_name, "
-            ":dataset_fullname, :dataset_shortname) "
-            "ON CONFLICT "
-            "(SpeciesId, InbredSetId, ProbeFreezeId, ProbeSetFreezeId) "
-            "DO NOTHING",
-            tuple({
-                "data_link_id": str(uuid.uuid4()),
-                "group_id": str(group.group_id),
-                **{
-                    key: value for key,value in dataset.items() if key in (
-                        "SpeciesId", "InbredSetId", "ProbeFreezeId",
-                        "ProbeSetFreezeId", "dataset_fullname", "dataset_name",
-                        "dataset_shortname")
-                }
-            } for dataset in datasets))
-        return {
-            "description": (
-                f"Successfully linked {len(datasets)} to group "
-                f"'{group.group_name}'."),
-            "group": dictify(group),
-            "datasets": datasets
-        }
diff --git a/gn3/auth/authorisation/data/phenotypes.py b/gn3/auth/authorisation/data/phenotypes.py
deleted file mode 100644
index ff98295..0000000
--- a/gn3/auth/authorisation/data/phenotypes.py
+++ /dev/null
@@ -1,140 +0,0 @@
-"""Handle linking of Phenotype data to the Auth(entic|oris)ation system."""
-import uuid
-from typing import Any, Iterable
-
-from MySQLdb.cursors import DictCursor
-
-import gn3.auth.db as authdb
-import gn3.db_utils as gn3db
-from gn3.auth.dictify import dictify
-from gn3.auth.authorisation.checks import authorised_p
-from gn3.auth.authorisation.groups.models import Group
-
-def linked_phenotype_data(
-        authconn: authdb.DbConnection, gn3conn: gn3db.Connection,
-        species: str = "") -> Iterable[dict[str, Any]]:
-    """Retrieve phenotype data linked to user groups."""
-    authkeys = ("SpeciesId", "InbredSetId", "PublishFreezeId", "PublishXRefId")
-    with (authdb.cursor(authconn) as authcursor,
-          gn3conn.cursor(DictCursor) as gn3cursor):
-        authcursor.execute("SELECT * FROM linked_phenotype_data")
-        linked = tuple(tuple(row[key] for key in authkeys)
-                       for row in authcursor.fetchall())
-        if len(linked) <= 0:
-            return iter(())
-        paramstr = ", ".join(["(%s, %s, %s, %s)"] * len(linked))
-        query = (
-            "SELECT spc.SpeciesId, spc.Name AS SpeciesName, iset.InbredSetId, "
-            "iset.InbredSetName, pf.Id AS PublishFreezeId, "
-            "pf.Name AS dataset_name, pf.FullName AS dataset_fullname, "
-            "pf.ShortName AS dataset_shortname, pxr.Id AS PublishXRefId "
-            "FROM "
-            "Species AS spc "
-            "INNER JOIN InbredSet AS iset "
-            "ON spc.SpeciesId=iset.SpeciesId "
-            "INNER JOIN PublishFreeze AS pf "
-            "ON iset.InbredSetId=pf.InbredSetId "
-            "INNER JOIN PublishXRef AS pxr "
-            "ON pf.InbredSetId=pxr.InbredSetId") + (
-                " WHERE" if (len(linked) > 0 or bool(species)) else "") + (
-                    (" (spc.SpeciesId, iset.InbredSetId, pf.Id, pxr.Id) "
-                     f"IN ({paramstr})") if len(linked) > 0 else "") + (
-                        " AND"if len(linked) > 0 else "") + (
-                        " spc.SpeciesName=%s" if bool(species) else "")
-        params = tuple(item for sublist in linked for item in sublist) + (
-            (species,) if bool(species) else tuple())
-        gn3cursor.execute(query, params)
-        return (item for item in gn3cursor.fetchall())
-
-@authorised_p(("system:data:link-to-group",),
-              error_description=(
-                  "You do not have sufficient privileges to link data to (a) "
-                  "group(s)."),
-              oauth2_scope="profile group resource")
-def ungrouped_phenotype_data(
-        authconn: authdb.DbConnection, gn3conn: gn3db.Connection):
-    """Retrieve phenotype data that is not linked to any user group."""
-    with gn3conn.cursor() as cursor:
-        params = tuple(
-            (row["SpeciesId"], row["InbredSetId"], row["PublishFreezeId"],
-             row["PublishXRefId"])
-            for row in linked_phenotype_data(authconn, gn3conn))
-        paramstr = ", ".join(["(?, ?, ?, ?)"] * len(params))
-        query = (
-            "SELECT spc.SpeciesId, spc.SpeciesName, iset.InbredSetId, "
-            "iset.InbredSetName, pf.Id AS PublishFreezeId, "
-            "pf.Name AS dataset_name, pf.FullName AS dataset_fullname, "
-            "pf.ShortName AS dataset_shortname, pxr.Id AS PublishXRefId "
-            "FROM "
-            "Species AS spc "
-            "INNER JOIN InbredSet AS iset "
-            "ON spc.SpeciesId=iset.SpeciesId "
-            "INNER JOIN PublishFreeze AS pf "
-            "ON iset.InbredSetId=pf.InbredSetId "
-            "INNER JOIN PublishXRef AS pxr "
-            "ON pf.InbredSetId=pxr.InbredSetId")
-        if len(params) > 0:
-            query = query + (
-                f" WHERE (iset.InbredSetId, pf.Id, pxr.Id) NOT IN ({paramstr})")
-
-        cursor.execute(query, params)
-        return tuple(dict(row) for row in cursor.fetchall())
-
-    return tuple()
-
-def __traits__(gn3conn: gn3db.Connection, params: tuple[dict, ...]) -> tuple[dict, ...]:
-    """An internal utility function. Don't use outside of this module."""
-    if len(params) < 1:
-        return tuple()
-    paramstr = ", ".join(["(%s, %s, %s, %s)"] * len(params))
-    with gn3conn.cursor(DictCursor) as cursor:
-        cursor.execute(
-            "SELECT spc.SpeciesId, iset.InbredSetId, pf.Id AS PublishFreezeId, "
-            "pf.Name AS dataset_name, pf.FullName AS dataset_fullname, "
-            "pf.ShortName AS dataset_shortname, pxr.Id AS PublishXRefId "
-            "FROM "
-            "Species AS spc "
-            "INNER JOIN InbredSet AS iset "
-            "ON spc.SpeciesId=iset.SpeciesId "
-            "INNER JOIN PublishFreeze AS pf "
-            "ON iset.InbredSetId=pf.InbredSetId "
-            "INNER JOIN PublishXRef AS pxr "
-            "ON pf.InbredSetId=pxr.InbredSetId "
-            "WHERE (spc.SpeciesName, iset.InbredSetName, pf.Name, pxr.Id) "
-            f"IN ({paramstr})",
-            tuple(
-                itm for sublist in (
-                    (item["species"], item["group"], item["dataset"], item["name"])
-                    for item in params)
-                for itm in sublist))
-        return cursor.fetchall()
-
-@authorised_p(("system:data:link-to-group",),
-              error_description=(
-                  "You do not have sufficient privileges to link data to (a) "
-                  "group(s)."),
-              oauth2_scope="profile group resource")
-def link_phenotype_data(
-        authconn:authdb.DbConnection, gn3conn: gn3db.Connection, group: Group,
-        traits: tuple[dict, ...]) -> dict:
-    """Link phenotype traits to a user group."""
-    with authdb.cursor(authconn) as cursor:
-        params = tuple({
-            "data_link_id": str(uuid.uuid4()),
-            "group_id": str(group.group_id),
-            **item
-        } for item in __traits__(gn3conn, traits))
-        cursor.executemany(
-            "INSERT INTO linked_phenotype_data "
-            "VALUES ("
-            ":data_link_id, :group_id, :SpeciesId, :InbredSetId, "
-            ":PublishFreezeId, :dataset_name, :dataset_fullname, "
-            ":dataset_shortname, :PublishXRefId"
-            ")",
-            params)
-        return {
-            "description": (
-                f"Successfully linked {len(traits)} traits to group."),
-            "group": dictify(group),
-            "traits": params
-        }
diff --git a/gn3/auth/authorisation/data/views.py b/gn3/auth/authorisation/data/views.py
deleted file mode 100644
index 81811dd..0000000
--- a/gn3/auth/authorisation/data/views.py
+++ /dev/null
@@ -1,310 +0,0 @@
-"""Handle data endpoints."""
-import sys
-import uuid
-import json
-from typing import Any
-from functools import partial
-
-import redis
-from MySQLdb.cursors import DictCursor
-from authlib.integrations.flask_oauth2.errors import _HTTPException
-from flask import request, jsonify, Response, Blueprint, current_app as app
-
-import gn3.db_utils as gn3db
-from gn3 import jobs
-from gn3.commands import run_async_cmd
-from gn3.db.traits import build_trait_name
-
-from gn3.auth import db
-from gn3.auth.db_utils import with_db_connection
-
-from gn3.auth.authorisation.checks import require_json
-from gn3.auth.authorisation.errors import InvalidData, NotFoundError
-
-from gn3.auth.authorisation.groups.models import group_by_id
-
-from gn3.auth.authorisation.users.models import user_resource_roles
-
-from gn3.auth.authorisation.resources.checks import authorised_for
-from gn3.auth.authorisation.resources.models import (
-    user_resources, public_resources, attach_resources_data)
-
-from gn3.auth.authorisation.oauth2.resource_server import require_oauth
-
-from gn3.auth.authorisation.users import User
-from gn3.auth.authorisation.data.phenotypes import link_phenotype_data
-from gn3.auth.authorisation.data.mrna import link_mrna_data, ungrouped_mrna_data
-from gn3.auth.authorisation.data.genotypes import (
-    link_genotype_data, ungrouped_genotype_data)
-
-data = Blueprint("data", __name__)
-
-@data.route("species")
-def list_species() -> Response:
-    """List all available species information."""
-    with (gn3db.database_connection(app.config["SQL_URI"]) as gn3conn,
-          gn3conn.cursor(DictCursor) as cursor):
-        cursor.execute("SELECT * FROM Species")
-        return jsonify(tuple(dict(row) for row in cursor.fetchall()))
-
-@data.route("/authorisation", methods=["POST"])
-@require_json
-def authorisation() -> Response:
-    """Retrive the authorisation level for datasets/traits for the user."""
-    # Access endpoint with something like:
-    # curl -X POST http://127.0.0.1:8080/api/oauth2/data/authorisation \
-    #    -H "Content-Type: application/json" \
-    #    -d '{"traits": ["HC_M2_0606_P::1442370_at", "BXDGeno::01.001.695",
-    #        "BXDPublish::10001"]}'
-    db_uri = app.config["AUTH_DB"]
-    privileges = {}
-    user = User(uuid.uuid4(), "anon@ymous.user", "Anonymous User")
-    with db.connection(db_uri) as auth_conn:
-        try:
-            with require_oauth.acquire("profile group resource") as the_token:
-                user = the_token.user
-                resources = attach_resources_data(
-                    auth_conn, user_resources(auth_conn, the_token.user))
-                resources_roles = user_resource_roles(auth_conn, the_token.user)
-                privileges = {
-                    resource_id: tuple(
-                        privilege.privilege_id
-                        for roles in resources_roles[resource_id]
-                        for privilege in roles.privileges)#("group:resource:view-resource",)
-                    for resource_id, is_authorised
-                    in authorised_for(
-                        auth_conn, the_token.user,
-                        ("group:resource:view-resource",), tuple(
-                            resource.resource_id for resource in resources)).items()
-                    if is_authorised
-                }
-        except _HTTPException as exc:
-            err_msg = json.loads(exc.body)
-            if err_msg["error"] == "missing_authorization":
-                resources = attach_resources_data(
-                    auth_conn, public_resources(auth_conn))
-            else:
-                raise exc from None
-
-        def __gen_key__(resource, data_item):
-            if resource.resource_category.resource_category_key.lower() == "phenotype":
-                return (
-                    f"{resource.resource_category.resource_category_key.lower()}::"
-                    f"{data_item['dataset_name']}::{data_item['PublishXRefId']}")
-            return (
-                f"{resource.resource_category.resource_category_key.lower()}::"
-                f"{data_item['dataset_name']}")
-
-        data_to_resource_map = {
-            __gen_key__(resource, data_item): resource.resource_id
-            for resource in resources
-            for data_item in resource.resource_data
-        }
-        privileges = {
-            **{
-                resource.resource_id: ("system:resource:public-read",)
-                for resource in resources if resource.public
-            },
-            **privileges}
-
-        args = request.get_json()
-        traits_names = args["traits"] # type: ignore[index]
-        def __translate__(val):
-            return {
-                "Temp": "Temp",
-                "ProbeSet": "mRNA",
-                "Geno": "Genotype",
-                "Publish": "Phenotype"
-            }[val]
-
-        def __trait_key__(trait):
-            dataset_type = __translate__(trait['db']['dataset_type']).lower()
-            dataset_name = trait["db"]["dataset_name"]
-            if dataset_type == "phenotype":
-                return f"{dataset_type}::{dataset_name}::{trait['trait_name']}"
-            return f"{dataset_type}::{dataset_name}"
-
-        return jsonify(tuple(
-            {
-                "user": user._asdict(),
-                **{key:trait[key] for key in ("trait_fullname", "trait_name")},
-                "dataset_name": trait["db"]["dataset_name"],
-                "dataset_type": __translate__(trait["db"]["dataset_type"]),
-                "resource_id": data_to_resource_map.get(__trait_key__(trait)),
-                "privileges": privileges.get(
-                    data_to_resource_map.get(
-                        __trait_key__(trait),
-                        uuid.UUID("4afa415e-94cb-4189-b2c6-f9ce2b6a878d")),
-                    tuple()) + (
-                        # Temporary traits do not exist in db: Set them
-                        # as public-read
-                        ("system:resource:public-read",)
-                        if trait["db"]["dataset_type"] == "Temp"
-                        else tuple())
-            } for trait in
-            (build_trait_name(trait_fullname)
-             for trait_fullname in traits_names)))
-
-def __search_mrna__():
-    query = __request_key__("query", "")
-    limit = int(__request_key__("limit", 10000))
-    offset = int(__request_key__("offset", 0))
-    with gn3db.database_connection(app.config["SQL_URI"]) as gn3conn:
-        __ungrouped__ = partial(
-            ungrouped_mrna_data, gn3conn=gn3conn, search_query=query,
-            selected=__request_key_list__("selected"),
-            limit=limit, offset=offset)
-        return jsonify(with_db_connection(__ungrouped__))
-
-def __request_key__(key: str, default: Any = ""):
-    if bool(request.json):
-        return request.json.get(#type: ignore[union-attr]
-            key, request.args.get(key, request.form.get(key, default)))
-    return request.args.get(key, request.form.get(key, default))
-
-def __request_key_list__(key: str, default: tuple[Any, ...] = tuple()):
-    if bool(request.json):
-        return (request.json.get(key,[])#type: ignore[union-attr]
-                or request.args.getlist(key) or request.form.getlist(key)
-                or list(default))
-    return (request.args.getlist(key)
-            or request.form.getlist(key) or list(default))
-
-def __search_genotypes__():
-    query = __request_key__("query", "")
-    limit = int(__request_key__("limit", 10000))
-    offset = int(__request_key__("offset", 0))
-    with gn3db.database_connection(app.config["SQL_URI"]) as gn3conn:
-        __ungrouped__ = partial(
-            ungrouped_genotype_data, gn3conn=gn3conn, search_query=query,
-            selected=__request_key_list__("selected"),
-            limit=limit, offset=offset)
-        return jsonify(with_db_connection(__ungrouped__))
-
-def __search_phenotypes__():
-    # launch the external process to search for phenotypes
-    redisuri = app.config["REDIS_URI"]
-    with redis.Redis.from_url(redisuri, decode_responses=True) as redisconn:
-        job_id = uuid.uuid4()
-        selected = __request_key__("selected_traits", [])
-        command =[
-            sys.executable, "-m", "scripts.search_phenotypes",
-            __request_key__("species_name"),
-            __request_key__("query"),
-            str(job_id),
-            f"--host={__request_key__('gn3_server_uri')}",
-            f"--auth-db-uri={app.config['AUTH_DB']}",
-            f"--gn3-db-uri={app.config['SQL_URI']}",
-            f"--redis-uri={redisuri}",
-            f"--per-page={__request_key__('per_page')}"] +(
-                [f"--selected={json.dumps(selected)}"]
-                if len(selected) > 0 else [])
-        jobs.create_job(redisconn, {
-            "job_id": job_id, "command": command, "status": "queued",
-            "search_results": tuple()})
-        return jsonify({
-            "job_id": job_id,
-            "command_id": run_async_cmd(
-                redisconn, app.config.get("REDIS_JOB_QUEUE"), command),
-            "command": command
-        })
-
-@data.route("/search", methods=["GET"])
-@require_oauth("profile group resource")
-def search_unlinked_data():
-    """Search for various unlinked data."""
-    dataset_type = request.json["dataset_type"]
-    search_fns = {
-        "mrna": __search_mrna__,
-        "genotype": __search_genotypes__,
-        "phenotype": __search_phenotypes__
-    }
-    return search_fns[dataset_type]()
-
-@data.route("/search/phenotype/<uuid:job_id>", methods=["GET"])
-def pheno_search_results(job_id: uuid.UUID) -> Response:
-    """Get the search results from the external script"""
-    def __search_error__(err):
-        raise NotFoundError(err["error_description"])
-    redisuri = app.config["REDIS_URI"]
-    with redis.Redis.from_url(redisuri, decode_responses=True) as redisconn:
-        return jobs.job(redisconn, job_id).either(
-            __search_error__, jsonify)
-
-@data.route("/link/genotype", methods=["POST"])
-def link_genotypes() -> Response:
-    """Link genotype data to group."""
-    def __values__(form) -> dict[str, Any]:
-        if not bool(form.get("species_name", "").strip()):
-            raise InvalidData("Expected 'species_name' not provided.")
-        if not bool(form.get("group_id")):
-            raise InvalidData("Expected 'group_id' not provided.",)
-        try:
-            _group_id = uuid.UUID(form.get("group_id"))
-        except TypeError as terr:
-            raise InvalidData("Expected a UUID for 'group_id' value.") from terr
-        if not bool(form.get("selected")):
-            raise InvalidData("Expected at least one dataset to be provided.")
-        return {
-            "group_id": uuid.UUID(form.get("group_id")),
-            "datasets": form.get("selected")
-        }
-
-    def __link__(conn: db.DbConnection, group_id: uuid.UUID, datasets: dict):
-        return link_genotype_data(conn, group_by_id(conn, group_id), datasets)
-
-    return jsonify(with_db_connection(
-        partial(__link__, **__values__(request.json))))
-
-@data.route("/link/mrna", methods=["POST"])
-def link_mrna() -> Response:
-    """Link mrna data to group."""
-    def __values__(form) -> dict[str, Any]:
-        if not bool(form.get("species_name", "").strip()):
-            raise InvalidData("Expected 'species_name' not provided.")
-        if not bool(form.get("group_id")):
-            raise InvalidData("Expected 'group_id' not provided.",)
-        try:
-            _group_id = uuid.UUID(form.get("group_id"))
-        except TypeError as terr:
-            raise InvalidData("Expected a UUID for 'group_id' value.") from terr
-        if not bool(form.get("selected")):
-            raise InvalidData("Expected at least one dataset to be provided.")
-        return {
-            "group_id": uuid.UUID(form.get("group_id")),
-            "datasets": form.get("selected")
-        }
-
-    def __link__(conn: db.DbConnection, group_id: uuid.UUID, datasets: dict):
-        return link_mrna_data(conn, group_by_id(conn, group_id), datasets)
-
-    return jsonify(with_db_connection(
-        partial(__link__, **__values__(request.json))))
-
-@data.route("/link/phenotype", methods=["POST"])
-def link_phenotype() -> Response:
-    """Link phenotype data to group."""
-    def __values__(form):
-        if not bool(form.get("species_name", "").strip()):
-            raise InvalidData("Expected 'species_name' not provided.")
-        if not bool(form.get("group_id")):
-            raise InvalidData("Expected 'group_id' not provided.",)
-        try:
-            _group_id = uuid.UUID(form.get("group_id"))
-        except TypeError as terr:
-            raise InvalidData("Expected a UUID for 'group_id' value.") from terr
-        if not bool(form.get("selected")):
-            raise InvalidData("Expected at least one dataset to be provided.")
-        return {
-            "group_id": uuid.UUID(form["group_id"]),
-            "traits": form["selected"]
-        }
-
-    with gn3db.database_connection(app.config["SQL_URI"]) as gn3conn:
-        def __link__(conn: db.DbConnection, group_id: uuid.UUID,
-                     traits: tuple[dict, ...]) -> dict:
-            return link_phenotype_data(
-                conn, gn3conn, group_by_id(conn, group_id), traits)
-
-        return jsonify(with_db_connection(
-            partial(__link__, **__values__(request.json))))
diff --git a/gn3/auth/authorisation/groups/__init__.py b/gn3/auth/authorisation/groups/__init__.py
deleted file mode 100644
index 1cb0bba..0000000
--- a/gn3/auth/authorisation/groups/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-"""Initialise the `gn3.auth.authorisation.groups` package"""
-
-from .models import Group, GroupRole
diff --git a/gn3/auth/authorisation/groups/data.py b/gn3/auth/authorisation/groups/data.py
deleted file mode 100644
index ee6f70e..0000000
--- a/gn3/auth/authorisation/groups/data.py
+++ /dev/null
@@ -1,106 +0,0 @@
-"""Handles the resource objects' data."""
-from MySQLdb.cursors import DictCursor
-
-from gn3 import db_utils as gn3db
-from gn3.auth import db as authdb
-from gn3.auth.authorisation.groups import Group
-from gn3.auth.authorisation.checks import authorised_p
-from gn3.auth.authorisation.errors import NotFoundError
-
-def __fetch_mrna_data_by_ids__(
-        conn: gn3db.Connection, dataset_ids: tuple[str, ...]) -> tuple[
-            dict, ...]:
-    """Fetch mRNA Assay data by ID."""
-    with conn.cursor(DictCursor) as cursor:
-        paramstr = ", ".join(["%s"] * len(dataset_ids))
-        cursor.execute(
-            "SELECT psf.Id, psf.Name AS dataset_name, "
-            "psf.FullName AS dataset_fullname, "
-            "ifiles.GN_AccesionId AS accession_id FROM ProbeSetFreeze AS psf "
-            "INNER JOIN InfoFiles AS ifiles ON psf.Name=ifiles.InfoPageName "
-            f"WHERE psf.Id IN ({paramstr})",
-            dataset_ids)
-        res = cursor.fetchall()
-        if res:
-            return tuple(dict(row) for row in res)
-        raise NotFoundError("Could not find mRNA Assay data with the given ID.")
-
-def __fetch_geno_data_by_ids__(
-        conn: gn3db.Connection, dataset_ids: tuple[str, ...]) -> tuple[
-            dict, ...]:
-    """Fetch genotype data by ID."""
-    with conn.cursor(DictCursor) as cursor:
-        paramstr = ", ".join(["%s"] * len(dataset_ids))
-        cursor.execute(
-            "SELECT gf.Id, gf.Name AS dataset_name, "
-            "gf.FullName AS dataset_fullname, "
-            "ifiles.GN_AccesionId AS accession_id FROM GenoFreeze AS gf "
-            "INNER JOIN InfoFiles AS ifiles ON gf.Name=ifiles.InfoPageName "
-            f"WHERE gf.Id IN ({paramstr})",
-            dataset_ids)
-        res = cursor.fetchall()
-        if res:
-            return tuple(dict(row) for row in res)
-        raise NotFoundError("Could not find Genotype data with the given ID.")
-
-def __fetch_pheno_data_by_ids__(
-        conn: gn3db.Connection, dataset_ids: tuple[str, ...]) -> tuple[
-            dict, ...]:
-    """Fetch phenotype data by ID."""
-    with conn.cursor(DictCursor) as cursor:
-        paramstr = ", ".join(["%s"] * len(dataset_ids))
-        cursor.execute(
-            "SELECT pxf.Id, iset.InbredSetName, pf.Id AS dataset_id, "
-            "pf.Name AS dataset_name, pf.FullName AS dataset_fullname, "
-            "ifiles.GN_AccesionId AS accession_id "
-            "FROM PublishXRef AS pxf "
-            "INNER JOIN InbredSet AS iset ON pxf.InbredSetId=iset.InbredSetId "
-            "INNER JOIN PublishFreeze AS pf ON iset.InbredSetId=pf.InbredSetId "
-            "INNER JOIN InfoFiles AS ifiles ON pf.Name=ifiles.InfoPageName "
-            f"WHERE pxf.Id IN ({paramstr})",
-            dataset_ids)
-        res = cursor.fetchall()
-        if res:
-            return tuple(dict(row) for row in res)
-        raise NotFoundError(
-            "Could not find Phenotype/Publish data with the given IDs.")
-
-def __fetch_data_by_id(
-        conn: gn3db.Connection, dataset_type: str,
-        dataset_ids: tuple[str, ...]) -> tuple[dict, ...]:
-    """Fetch data from MySQL by IDs."""
-    fetch_fns = {
-        "mrna": __fetch_mrna_data_by_ids__,
-        "genotype": __fetch_geno_data_by_ids__,
-        "phenotype": __fetch_pheno_data_by_ids__
-    }
-    return fetch_fns[dataset_type](conn, dataset_ids)
-
-@authorised_p(("system:data:link-to-group",),
-              error_description=(
-                  "You do not have sufficient privileges to link data to (a) "
-                  "group(s)."),
-              oauth2_scope="profile group resource")
-def link_data_to_group(
-        authconn: authdb.DbConnection, gn3conn: gn3db.Connection,
-        dataset_type: str, dataset_ids: tuple[str, ...], group: Group) -> tuple[
-            dict, ...]:
-    """Link the given data to the specified group."""
-    the_data = __fetch_data_by_id(gn3conn, dataset_type, dataset_ids)
-    with authdb.cursor(authconn) as cursor:
-        params = tuple({
-            "group_id": str(group.group_id), "dataset_type": {
-                "mrna": "mRNA", "genotype": "Genotype",
-                "phenotype": "Phenotype"
-            }[dataset_type],
-            "dataset_or_trait_id": item["Id"],
-            "dataset_name": item["dataset_name"],
-            "dataset_fullname": item["dataset_fullname"],
-            "accession_id": item["accession_id"]
-        } for item in the_data)
-        cursor.executemany(
-            "INSERT INTO linked_group_data VALUES"
-            "(:group_id, :dataset_type, :dataset_or_trait_id, :dataset_name, "
-            ":dataset_fullname, :accession_id)",
-            params)
-        return params
diff --git a/gn3/auth/authorisation/groups/models.py b/gn3/auth/authorisation/groups/models.py
deleted file mode 100644
index 7212a78..0000000
--- a/gn3/auth/authorisation/groups/models.py
+++ /dev/null
@@ -1,400 +0,0 @@
-"""Handle the management of resource/user groups."""
-import json
-from uuid import UUID, uuid4
-from functools import reduce
-from typing import Any, Sequence, Iterable, Optional, NamedTuple
-
-from flask import g
-from pymonad.maybe import Just, Maybe, Nothing
-
-from gn3.auth import db
-from gn3.auth.dictify import dictify
-from gn3.auth.authorisation.users import User, user_by_id
-
-from ..checks import authorised_p
-from ..privileges import Privilege
-from ..errors import NotFoundError, AuthorisationError, InconsistencyError
-from ..roles.models import (
-    Role, create_role, check_user_editable, revoke_user_role_by_name,
-    assign_user_role_by_name)
-
-class Group(NamedTuple):
-    """Class representing a group."""
-    group_id: UUID
-    group_name: str
-    group_metadata: dict[str, Any]
-
-    def dictify(self):
-        """Return a dict representation of `Group` objects."""
-        return {
-            "group_id": self.group_id, "group_name": self.group_name,
-            "group_metadata": self.group_metadata
-        }
-
-DUMMY_GROUP = Group(
-    group_id=UUID("77cee65b-fe29-4383-ae41-3cb3b480cc70"),
-    group_name="GN3_DUMMY_GROUP",
-    group_metadata={
-        "group-description": "This is a dummy group to use as a placeholder"
-    })
-
-class GroupRole(NamedTuple):
-    """Class representing a role tied/belonging to a group."""
-    group_role_id: UUID
-    group: Group
-    role: Role
-
-    def dictify(self) -> dict[str, Any]:
-        """Return a dict representation of `GroupRole` objects."""
-        return {
-            "group_role_id": self.group_role_id, "group": dictify(self.group),
-            "role": dictify(self.role)
-        }
-
-class GroupCreationError(AuthorisationError):
-    """Raised whenever a group creation fails"""
-
-class MembershipError(AuthorisationError):
-    """Raised when there is an error with a user's membership to a group."""
-
-    def __init__(self, user: User, groups: Sequence[Group]):
-        """Initialise the `MembershipError` exception object."""
-        groups_str = ", ".join(group.group_name for group in groups)
-        error_description = (
-            f"User '{user.name} ({user.email})' is a member of {len(groups)} "
-            f"groups ({groups_str})")
-        super().__init__(f"{type(self).__name__}: {error_description}.")
-
-def user_membership(conn: db.DbConnection, user: User) -> Sequence[Group]:
-    """Returns all the groups that a member belongs to"""
-    query = (
-        "SELECT groups.group_id, group_name, groups.group_metadata "
-        "FROM group_users INNER JOIN groups "
-        "ON group_users.group_id=groups.group_id "
-        "WHERE group_users.user_id=?")
-    with db.cursor(conn) as cursor:
-        cursor.execute(query, (str(user.user_id),))
-        groups = tuple(Group(row[0], row[1], json.loads(row[2]))
-                       for row in cursor.fetchall())
-
-    return groups
-
-@authorised_p(
-    privileges = ("system:group:create-group",),
-    error_description = (
-        "You do not have the appropriate privileges to enable you to "
-        "create a new group."),
-    oauth2_scope = "profile group")
-def create_group(
-        conn: db.DbConnection, group_name: str, group_leader: User,
-        group_description: Optional[str] = None) -> Group:
-    """Create a new group."""
-    user_groups = user_membership(conn, group_leader)
-    if len(user_groups) > 0:
-        raise MembershipError(group_leader, user_groups)
-
-    with db.cursor(conn) as cursor:
-        new_group = save_group(
-            cursor, group_name,(
-                {"group_description": group_description}
-                if group_description else {}))
-        add_user_to_group(cursor, new_group, group_leader)
-        revoke_user_role_by_name(cursor, group_leader, "group-creator")
-        assign_user_role_by_name(cursor, group_leader, "group-leader")
-        return new_group
-
-@authorised_p(("group:role:create-role",),
-              error_description="Could not create the group role")
-def create_group_role(
-        conn: db.DbConnection, group: Group, role_name: str,
-        privileges: Iterable[Privilege]) -> GroupRole:
-    """Create a role attached to a group."""
-    with db.cursor(conn) as cursor:
-        group_role_id = uuid4()
-        role = create_role(cursor, role_name, privileges)
-        cursor.execute(
-            ("INSERT INTO group_roles(group_role_id, group_id, role_id) "
-             "VALUES(?, ?, ?)"),
-            (str(group_role_id), str(group.group_id), str(role.role_id)))
-
-    return GroupRole(group_role_id, group, role)
-
-def authenticated_user_group(conn) -> Maybe:
-    """
-    Returns the currently authenticated user's group.
-
-    Look into returning a Maybe object.
-    """
-    user = g.user
-    with db.cursor(conn) as cursor:
-        cursor.execute(
-            ("SELECT groups.* FROM group_users "
-             "INNER JOIN groups ON group_users.group_id=groups.group_id "
-             "WHERE group_users.user_id = ?"),
-            (str(user.user_id),))
-        groups = tuple(Group(UUID(row[0]), row[1], json.loads(row[2] or "{}"))
-                       for row in cursor.fetchall())
-
-    if len(groups) > 1:
-        raise MembershipError(user, groups)
-
-    if len(groups) == 1:
-        return Just(groups[0])
-
-    return Nothing
-
-def user_group(conn: db.DbConnection, user: User) -> Maybe[Group]:
-    """Returns the given user's group"""
-    with db.cursor(conn) as cursor:
-        cursor.execute(
-            ("SELECT groups.group_id, groups.group_name, groups.group_metadata "
-             "FROM group_users "
-             "INNER JOIN groups ON group_users.group_id=groups.group_id "
-             "WHERE group_users.user_id = ?"),
-            (str(user.user_id),))
-        groups = tuple(
-            Group(UUID(row[0]), row[1], json.loads(row[2] or "{}"))
-            for row in cursor.fetchall())
-
-        if len(groups) > 1:
-            raise MembershipError(user, groups)
-
-        if len(groups) == 1:
-            return Just(groups[0])
-
-    return Nothing
-
-def is_group_leader(conn: db.DbConnection, user: User, group: Group) -> bool:
-    """Check whether the given `user` is the leader of `group`."""
-
-    ugroup = user_group(conn, user).maybe(
-        False, lambda val: val) # type: ignore[arg-type, misc]
-    if not group:
-        # User cannot be a group leader if not a member of ANY group
-        return False
-
-    if not ugroup == group:
-        # User cannot be a group leader if not a member of THIS group
-        return False
-
-    with db.cursor(conn) as cursor:
-        cursor.execute(
-            ("SELECT roles.role_name FROM user_roles LEFT JOIN roles "
-             "ON user_roles.role_id = roles.role_id WHERE user_id = ?"),
-            (str(user.user_id),))
-        role_names = tuple(row[0] for row in cursor.fetchall())
-
-        return "group-leader" in role_names
-
-def all_groups(conn: db.DbConnection) -> Maybe[Sequence[Group]]:
-    """Retrieve all existing groups"""
-    with db.cursor(conn) as cursor:
-        cursor.execute("SELECT * FROM groups")
-        res = cursor.fetchall()
-        if res:
-            return Just(tuple(
-                Group(row["group_id"], row["group_name"],
-                      json.loads(row["group_metadata"])) for row in res))
-
-    return Nothing
-
-def save_group(
-        cursor: db.DbCursor, group_name: str,
-        group_metadata: dict[str, Any]) -> Group:
-    """Save a group to db"""
-    the_group = Group(uuid4(), group_name, group_metadata)
-    cursor.execute(
-        ("INSERT INTO groups "
-         "VALUES(:group_id, :group_name, :group_metadata) "
-         "ON CONFLICT (group_id) DO UPDATE SET "
-         "group_name=:group_name, group_metadata=:group_metadata"),
-    {"group_id": str(the_group.group_id), "group_name": the_group.group_name,
-     "group_metadata": json.dumps(the_group.group_metadata)})
-    return the_group
-
-def add_user_to_group(cursor: db.DbCursor, the_group: Group, user: User):
-    """Add `user` to `the_group` as a member."""
-    cursor.execute(
-        ("INSERT INTO group_users VALUES (:group_id, :user_id) "
-         "ON CONFLICT (group_id, user_id) DO NOTHING"),
-        {"group_id": str(the_group.group_id), "user_id": str(user.user_id)})
-
-@authorised_p(
-    privileges = ("system:group:view-group",),
-    error_description = (
-        "You do not have the appropriate privileges to access the list of users"
-        " in the group."))
-def group_users(conn: db.DbConnection, group_id: UUID) -> Iterable[User]:
-    """Retrieve all users that are members of group with id `group_id`."""
-    with db.cursor(conn) as cursor:
-        cursor.execute(
-            "SELECT u.* FROM group_users AS gu INNER JOIN users AS u "
-            "ON gu.user_id = u.user_id WHERE gu.group_id=:group_id",
-            {"group_id": str(group_id)})
-        results = cursor.fetchall()
-
-    return (User(UUID(row["user_id"]), row["email"], row["name"])
-            for row in results)
-
-@authorised_p(
-    privileges = ("system:group:view-group",),
-    error_description = (
-        "You do not have the appropriate privileges to access the group."))
-def group_by_id(conn: db.DbConnection, group_id: UUID) -> Group:
-    """Retrieve a group by its ID"""
-    with db.cursor(conn) as cursor:
-        cursor.execute("SELECT * FROM groups WHERE group_id=:group_id",
-                       {"group_id": str(group_id)})
-        row = cursor.fetchone()
-        if row:
-            return Group(
-                UUID(row["group_id"]),
-                row["group_name"],
-                json.loads(row["group_metadata"]))
-
-    raise NotFoundError(f"Could not find group with ID '{group_id}'.")
-
-@authorised_p(("system:group:view-group", "system:group:edit-group"),
-              error_description=("You do not have the appropriate authorisation"
-                                 " to act upon the join requests."),
-              oauth2_scope="profile group")
-def join_requests(conn: db.DbConnection, user: User):
-    """List all the join requests for the user's group."""
-    with db.cursor(conn) as cursor:
-        group = user_group(conn, user).maybe(DUMMY_GROUP, lambda grp: grp)# type: ignore[misc]
-        if group != DUMMY_GROUP and is_group_leader(conn, user, group):
-            cursor.execute(
-                "SELECT gjr.*, u.email, u.name FROM group_join_requests AS gjr "
-                "INNER JOIN users AS u ON gjr.requester_id=u.user_id "
-                "WHERE gjr.group_id=? AND gjr.status='PENDING'",
-                (str(group.group_id),))
-            return tuple(dict(row)for row in cursor.fetchall())
-
-    raise AuthorisationError(
-        "You do not have the appropriate authorisation to access the "
-        "group's join requests.")
-
-@authorised_p(("system:group:view-group", "system:group:edit-group"),
-              error_description=("You do not have the appropriate authorisation"
-                                 " to act upon the join requests."),
-              oauth2_scope="profile group")
-def accept_reject_join_request(
-        conn: db.DbConnection, request_id: UUID, user: User, status: str) -> dict:
-    """Accept/Reject a join request."""
-    assert status in ("ACCEPTED", "REJECTED"), f"Invalid status '{status}'."
-    with db.cursor(conn) as cursor:
-        group = user_group(conn, user).maybe(DUMMY_GROUP, lambda grp: grp) # type: ignore[misc]
-        cursor.execute("SELECT * FROM group_join_requests WHERE request_id=?",
-                       (str(request_id),))
-        row = cursor.fetchone()
-        if row:
-            if group.group_id == UUID(row["group_id"]):
-                try:
-                    the_user = user_by_id(conn, UUID(row["requester_id"]))
-                    if status == "ACCEPTED":
-                        add_user_to_group(cursor, group, the_user)
-                        revoke_user_role_by_name(cursor, the_user, "group-creator")
-                    cursor.execute(
-                        "UPDATE group_join_requests SET status=? "
-                        "WHERE request_id=?",
-                        (status, str(request_id)))
-                    return {"request_id": request_id, "status": status}
-                except NotFoundError as nfe:
-                    raise InconsistencyError(
-                        "Could not find user associated with join request."
-                    ) from nfe
-            raise AuthorisationError(
-                "You cannot act on other groups join requests")
-        raise NotFoundError(f"Could not find request with ID '{request_id}'")
-
-def __organise_privileges__(acc, row):
-    role_id = UUID(row["role_id"])
-    role = acc.get(role_id, False)
-    if role:
-        return {
-            **acc,
-            role_id: Role(
-                role.role_id, role.role_name,
-                bool(int(row["user_editable"])),
-                role.privileges + (
-                    Privilege(row["privilege_id"],
-                              row["privilege_description"]),))
-        }
-    return {
-        **acc,
-        role_id: Role(
-            UUID(row["role_id"]), row["role_name"],
-            bool(int(row["user_editable"])),
-            (Privilege(row["privilege_id"], row["privilege_description"]),))
-    }
-
-# @authorised_p(("group:role:view",),
-#               "Insufficient privileges to view role",
-#               oauth2_scope="profile group role")
-def group_role_by_id(
-        conn: db.DbConnection, group: Group, group_role_id: UUID) -> GroupRole:
-    """Retrieve GroupRole from id by its `group_role_id`."""
-    ## TODO: do privileges check before running actual query
-    ##       the check commented out above doesn't work correctly
-    with db.cursor(conn) as cursor:
-        cursor.execute(
-            "SELECT gr.group_role_id, r.*, p.* "
-            "FROM group_roles AS gr "
-            "INNER JOIN roles AS r ON gr.role_id=r.role_id "
-            "INNER JOIN role_privileges AS rp ON rp.role_id=r.role_id "
-            "INNER JOIN privileges AS p ON p.privilege_id=rp.privilege_id "
-            "WHERE gr.group_role_id=? AND gr.group_id=?",
-            (str(group_role_id), str(group.group_id)))
-        rows = cursor.fetchall()
-        if rows:
-            roles: tuple[Role,...] = tuple(reduce(
-                __organise_privileges__, rows, {}).values())
-            assert len(roles) == 1
-            return GroupRole(group_role_id, group, roles[0])
-        raise NotFoundError(
-            f"Group role with ID '{group_role_id}' does not exist.")
-
-@authorised_p(("group:role:edit-role",),
-              "You do not have the privilege to edit a role.",
-              oauth2_scope="profile group role")
-def add_privilege_to_group_role(conn: db.DbConnection, group_role: GroupRole,
-                                privilege: Privilege) -> GroupRole:
-    """Add `privilege` to `group_role`."""
-    ## TODO: do privileges check.
-    check_user_editable(group_role.role)
-    with db.cursor(conn) as cursor:
-        cursor.execute(
-            "INSERT INTO role_privileges(role_id,privilege_id) "
-            "VALUES (?, ?) ON CONFLICT (role_id, privilege_id) "
-            "DO NOTHING",
-            (str(group_role.role.role_id), str(privilege.privilege_id)))
-        return GroupRole(
-            group_role.group_role_id,
-            group_role.group,
-            Role(group_role.role.role_id,
-                 group_role.role.role_name,
-                 group_role.role.user_editable,
-                 group_role.role.privileges + (privilege,)))
-
-@authorised_p(("group:role:edit-role",),
-              "You do not have the privilege to edit a role.",
-              oauth2_scope="profile group role")
-def delete_privilege_from_group_role(
-        conn: db.DbConnection, group_role: GroupRole,
-        privilege: Privilege) -> GroupRole:
-    """Delete `privilege` to `group_role`."""
-    ## TODO: do privileges check.
-    check_user_editable(group_role.role)
-    with db.cursor(conn) as cursor:
-        cursor.execute(
-            "DELETE FROM role_privileges WHERE "
-            "role_id=? AND privilege_id=?",
-            (str(group_role.role.role_id), str(privilege.privilege_id)))
-        return GroupRole(
-            group_role.group_role_id,
-            group_role.group,
-            Role(group_role.role.role_id,
-                 group_role.role.role_name,
-                 group_role.role.user_editable,
-                 tuple(priv for priv in group_role.role.privileges
-                       if priv != privilege)))
diff --git a/gn3/auth/authorisation/groups/views.py b/gn3/auth/authorisation/groups/views.py
deleted file mode 100644
index a849a73..0000000
--- a/gn3/auth/authorisation/groups/views.py
+++ /dev/null
@@ -1,430 +0,0 @@
-"""The views/routes for the `gn3.auth.authorisation.groups` package."""
-import uuid
-import datetime
-from typing import Iterable
-from functools import partial
-
-from MySQLdb.cursors import DictCursor
-from flask import request, jsonify, Response, Blueprint, current_app
-
-from gn3.auth import db
-from gn3 import db_utils as gn3db
-
-from gn3.auth.dictify import dictify
-from gn3.auth.db_utils import with_db_connection
-from gn3.auth.authorisation.users import User
-from gn3.auth.authorisation.oauth2.resource_server import require_oauth
-
-from .data import link_data_to_group
-from .models import (
-    Group, user_group, all_groups, DUMMY_GROUP, GroupRole, group_by_id,
-    join_requests, group_role_by_id, GroupCreationError,
-    accept_reject_join_request, group_users as _group_users,
-    create_group as _create_group, add_privilege_to_group_role,
-    delete_privilege_from_group_role, create_group_role as _create_group_role)
-
-from ..roles.models import Role
-from ..roles.models import user_roles
-
-from ..checks import authorised_p
-from ..privileges import Privilege, privileges_by_ids
-from ..errors import InvalidData, NotFoundError, AuthorisationError
-
-groups = Blueprint("groups", __name__)
-
-@groups.route("/list", methods=["GET"])
-@require_oauth("profile group")
-def list_groups():
-    """Return the list of groups that exist."""
-    with db.connection(current_app.config["AUTH_DB"]) as conn:
-        the_groups = all_groups(conn)
-
-    return jsonify(the_groups.maybe(
-        [], lambda grps: [dictify(grp) for grp in grps]))
-
-@groups.route("/create", methods=["POST"])
-@require_oauth("profile group")
-def create_group():
-    """Create a new group."""
-    with require_oauth.acquire("profile group") as the_token:
-        group_name=request.form.get("group_name", "").strip()
-        if not bool(group_name):
-            raise GroupCreationError("Could not create the group.")
-
-        db_uri = current_app.config["AUTH_DB"]
-        with db.connection(db_uri) as conn:
-            user = the_token.user
-            new_group = _create_group(
-                conn, group_name, user, request.form.get("group_description"))
-            return jsonify({
-                **dictify(new_group), "group_leader": dictify(user)
-            })
-
-@groups.route("/members/<uuid:group_id>", methods=["GET"])
-@require_oauth("profile group")
-def group_members(group_id: uuid.UUID) -> Response:
-    """Retrieve all the members of a group."""
-    with require_oauth.acquire("profile group") as the_token:# pylint: disable=[unused-variable]
-        db_uri = current_app.config["AUTH_DB"]
-        ## Check that user has appropriate privileges and remove the pylint disable above
-        with db.connection(db_uri) as conn:
-            return jsonify(tuple(
-                dictify(user) for user in _group_users(conn, group_id)))
-
-@groups.route("/requests/join/<uuid:group_id>", methods=["POST"])
-@require_oauth("profile group")
-def request_to_join(group_id: uuid.UUID) -> Response:
-    """Request to join a group."""
-    def __request__(conn: db.DbConnection, user: User, group_id: uuid.UUID,
-                    message: str):
-        with db.cursor(conn) as cursor:
-            group = user_group(conn, user).maybe(# type: ignore[misc]
-                False, lambda grp: grp)# type: ignore[arg-type]
-            if group:
-                error = AuthorisationError(
-                    "You cannot request to join a new group while being a "
-                    "member of an existing group.")
-                error.error_code = 400
-                raise error
-            request_id = uuid.uuid4()
-            cursor.execute(
-                "INSERT INTO group_join_requests VALUES "
-                "(:request_id, :group_id, :user_id, :ts, :status, :msg)",
-                {
-                    "request_id": str(request_id),
-                    "group_id": str(group_id),
-                    "user_id": str(user.user_id),
-                    "ts": datetime.datetime.now().timestamp(),
-                    "status": "PENDING",
-                    "msg": message
-                })
-            return {
-                "request_id":  request_id,
-                "message": "Successfully sent the join request."
-            }
-
-    with require_oauth.acquire("profile group") as the_token:
-        form = request.form
-        results = with_db_connection(partial(
-            __request__, user=the_token.user, group_id=group_id, message=form.get(
-                "message", "I hereby request that you add me to your group.")))
-        return jsonify(results)
-
-@groups.route("/requests/join/list", methods=["GET"])
-@require_oauth("profile group")
-def list_join_requests() -> Response:
-    """List the pending join requests."""
-    with require_oauth.acquire("profile group") as the_token:
-        return jsonify(with_db_connection(partial(
-            join_requests, user=the_token.user)))
-
-@groups.route("/requests/join/accept", methods=["POST"])
-@require_oauth("profile group")
-def accept_join_requests() -> Response:
-    """Accept a join request."""
-    with require_oauth.acquire("profile group") as the_token:
-        form = request.form
-        request_id = uuid.UUID(form.get("request_id"))
-        return jsonify(with_db_connection(partial(
-            accept_reject_join_request, request_id=request_id,
-            user=the_token.user, status="ACCEPTED")))
-
-@groups.route("/requests/join/reject", methods=["POST"])
-@require_oauth("profile group")
-def reject_join_requests() -> Response:
-    """Reject a join request."""
-    with require_oauth.acquire("profile group") as the_token:
-        form = request.form
-        request_id = uuid.UUID(form.get("request_id"))
-        return jsonify(with_db_connection(partial(
-            accept_reject_join_request, request_id=request_id,
-            user=the_token.user, status="REJECTED")))
-
-def unlinked_mrna_data(
-        conn: db.DbConnection, group: Group) -> tuple[dict, ...]:
-    """
-    Retrieve all mRNA Assay data linked to a group but not linked to any
-    resource.
-    """
-    query = (
-        "SELECT lmd.* FROM linked_mrna_data lmd "
-        "LEFT JOIN mrna_resources mr ON lmd.data_link_id=mr.data_link_id "
-        "WHERE lmd.group_id=? AND mr.data_link_id IS NULL")
-    with db.cursor(conn) as cursor:
-        cursor.execute(query, (str(group.group_id),))
-        return tuple(dict(row) for row in cursor.fetchall())
-
-def unlinked_genotype_data(
-        conn: db.DbConnection, group: Group) -> tuple[dict, ...]:
-    """
-    Retrieve all genotype data linked to a group but not linked to any resource.
-    """
-    query = (
-        "SELECT lgd.* FROM linked_genotype_data lgd "
-        "LEFT JOIN genotype_resources gr ON lgd.data_link_id=gr.data_link_id "
-        "WHERE lgd.group_id=? AND gr.data_link_id IS NULL")
-    with db.cursor(conn) as cursor:
-        cursor.execute(query, (str(group.group_id),))
-        return tuple(dict(row) for row in cursor.fetchall())
-
-def unlinked_phenotype_data(
-        authconn: db.DbConnection, gn3conn: gn3db.Connection,
-        group: Group) -> tuple[dict, ...]:
-    """
-    Retrieve all phenotype data linked to a group but not linked to any
-    resource.
-    """
-    with db.cursor(authconn) as authcur, gn3conn.cursor(DictCursor) as gn3cur:
-        authcur.execute(
-            "SELECT lpd.* FROM linked_phenotype_data AS lpd "
-            "LEFT JOIN phenotype_resources AS pr "
-            "ON lpd.data_link_id=pr.data_link_id "
-            "WHERE lpd.group_id=? AND pr.data_link_id IS NULL",
-            (str(group.group_id),))
-        results = authcur.fetchall()
-        ids: dict[tuple[str, ...], str] = {
-            (
-                row["SpeciesId"], row["InbredSetId"], row["PublishFreezeId"],
-                row["PublishXRefId"]): row["data_link_id"]
-            for row in results
-        }
-        if len(ids.keys()) < 1:
-            return tuple()
-        paramstr = ", ".join(["(%s, %s, %s, %s)"] * len(ids.keys()))
-        gn3cur.execute(
-            "SELECT spc.SpeciesId, spc.SpeciesName, iset.InbredSetId, "
-            "iset.InbredSetName, pf.Id AS PublishFreezeId, "
-            "pf.Name AS dataset_name, pf.FullName AS dataset_fullname, "
-            "pf.ShortName AS dataset_shortname, pxr.Id AS PublishXRefId, "
-            "pub.PubMed_ID, pub.Title, pub.Year, "
-            "phen.Pre_publication_description, "
-            "phen.Post_publication_description, phen.Original_description "
-            "FROM "
-            "Species AS spc "
-            "INNER JOIN InbredSet AS iset "
-            "ON spc.SpeciesId=iset.SpeciesId "
-            "INNER JOIN PublishFreeze AS pf "
-            "ON iset.InbredSetId=pf.InbredSetId "
-            "INNER JOIN PublishXRef AS pxr "
-            "ON pf.InbredSetId=pxr.InbredSetId "
-            "INNER JOIN Publication AS pub "
-            "ON pxr.PublicationId=pub.Id "
-            "INNER JOIN Phenotype AS phen "
-            "ON pxr.PhenotypeId=phen.Id "
-            "WHERE (spc.SpeciesId, iset.InbredSetId, pf.Id, pxr.Id) "
-            f"IN ({paramstr})",
-            tuple(item for sublist in ids.keys() for item in sublist))
-        return tuple({
-            **{key: value for key, value in row.items() if key not in
-               ("Post_publication_description", "Pre_publication_description",
-                "Original_description")},
-            "description": (
-                row["Post_publication_description"] or
-                row["Pre_publication_description"] or
-                row["Original_description"]),
-            "data_link_id": ids[tuple(str(row[key]) for key in (
-                "SpeciesId", "InbredSetId", "PublishFreezeId",
-                "PublishXRefId"))]
-        } for row in gn3cur.fetchall())
-
-@groups.route("/<string:resource_type>/unlinked-data")
-@require_oauth("profile group resource")
-def unlinked_data(resource_type: str) -> Response:
-    """View data linked to the group but not linked to any resource."""
-    if resource_type not in ("all", "mrna", "genotype", "phenotype"):
-        raise AuthorisationError(f"Invalid resource type {resource_type}")
-
-    with require_oauth.acquire("profile group resource") as the_token:
-        db_uri = current_app.config["AUTH_DB"]
-        gn3db_uri = current_app.config["SQL_URI"]
-        with (db.connection(db_uri) as authconn,
-              gn3db.database_connection(gn3db_uri) as gn3conn):
-            ugroup = user_group(authconn, the_token.user).maybe(# type: ignore[misc]
-                DUMMY_GROUP, lambda grp: grp)
-            if ugroup == DUMMY_GROUP:
-                return jsonify(tuple())
-
-            unlinked_fns = {
-                "mrna": unlinked_mrna_data,
-                "genotype": unlinked_genotype_data,
-                "phenotype": lambda conn, grp: partial(
-                    unlinked_phenotype_data, gn3conn=gn3conn)(
-                        authconn=conn, group=grp)
-            }
-            return jsonify(tuple(
-                dict(row) for row in unlinked_fns[resource_type](
-                    authconn, ugroup)))
-
-    return jsonify(tuple())
-
-@groups.route("/data/link", methods=["POST"])
-@require_oauth("profile group resource")
-def link_data() -> Response:
-    """Link selected data to specified group."""
-    with require_oauth.acquire("profile group resource") as _the_token:
-        form = request.form
-        group_id = uuid.UUID(form["group_id"])
-        dataset_ids = form.getlist("dataset_ids")
-        dataset_type = form.get("dataset_type")
-        if dataset_type not in ("mrna", "genotype", "phenotype"):
-            raise InvalidData("Unexpected dataset type requested!")
-        def __link__(conn: db.DbConnection):
-            group = group_by_id(conn, group_id)
-            with gn3db.database_connection(current_app.config["SQL_URI"]) as gn3conn:
-                return link_data_to_group(
-                    conn, gn3conn, dataset_type, dataset_ids, group)
-
-        return jsonify(with_db_connection(__link__))
-
-@groups.route("/roles", methods=["GET"])
-@require_oauth("profile group")
-def group_roles():
-    """Return a list of all available group roles."""
-    with require_oauth.acquire("profile group role") as the_token:
-        def __list_roles__(conn: db.DbConnection):
-            ## TODO: Check that user has appropriate privileges
-            with db.cursor(conn) as cursor:
-                group = user_group(conn, the_token.user).maybe(# type: ignore[misc]
-                    DUMMY_GROUP, lambda grp: grp)
-                if group == DUMMY_GROUP:
-                    return tuple()
-                cursor.execute(
-                    "SELECT gr.group_role_id, r.* "
-                    "FROM group_roles AS gr INNER JOIN roles AS r "
-                    "ON gr.role_id=r.role_id "
-                    "WHERE group_id=?",
-                    (str(group.group_id),))
-                return tuple(
-                    GroupRole(uuid.UUID(row["group_role_id"]),
-                              group,
-                              Role(uuid.UUID(row["role_id"]),
-                                   row["role_name"],
-                                   bool(int(row["user_editable"])),
-                                   tuple()))
-                    for row in cursor.fetchall())
-        return jsonify(tuple(
-            dictify(role) for role in with_db_connection(__list_roles__)))
-
-@groups.route("/privileges", methods=["GET"])
-@require_oauth("profile group")
-def group_privileges():
-    """Return a list of all available group roles."""
-    with require_oauth.acquire("profile group role") as the_token:
-        def __list_privileges__(conn: db.DbConnection) -> Iterable[Privilege]:
-            ## TODO: Check that user has appropriate privileges
-            this_user_roles = user_roles(conn, the_token.user)
-            with db.cursor(conn) as cursor:
-                cursor.execute("SELECT * FROM privileges "
-                               "WHERE privilege_id LIKE 'group:%'")
-                group_level_roles = tuple(
-                    Privilege(row["privilege_id"], row["privilege_description"])
-                    for row in cursor.fetchall())
-            return tuple(privilege for arole in this_user_roles
-                         for privilege in arole.privileges) + group_level_roles
-        return jsonify(tuple(
-            dictify(priv) for priv in with_db_connection(__list_privileges__)))
-
-
-
-@groups.route("/role/create", methods=["POST"])
-@require_oauth("profile group")
-def create_group_role():
-    """Create a new group role."""
-    with require_oauth.acquire("profile group role") as the_token:
-        ## TODO: Check that user has appropriate privileges
-        @authorised_p(("group:role:create-role",),
-                      "You do not have the privilege to create new roles",
-                      oauth2_scope="profile group role")
-        def __create__(conn: db.DbConnection) -> GroupRole:
-            ## TODO: Check user cannot assign any privilege they don't have.
-            form = request.form
-            role_name = form.get("role_name", "").strip()
-            privileges_ids = form.getlist("privileges[]")
-            if len(role_name) == 0:
-                raise InvalidData("Role name not provided!")
-            if len(privileges_ids) == 0:
-                raise InvalidData(
-                    "At least one privilege needs to be provided.")
-
-            group = user_group(conn, the_token.user).maybe(# type: ignore[misc]
-                    DUMMY_GROUP, lambda grp: grp)
-
-            if group == DUMMY_GROUP:
-                raise AuthorisationError(
-                    "A user without a group cannot create a new role.")
-            privileges = privileges_by_ids(conn, tuple(privileges_ids))
-            if len(privileges_ids) != len(privileges):
-                raise InvalidData(
-                    f"{len(privileges_ids) - len(privileges)} of the selected "
-                    "privileges were not found in the database.")
-
-            return _create_group_role(conn, group, role_name, privileges)
-
-        return jsonify(with_db_connection(__create__))
-
-@groups.route("/role/<uuid:group_role_id>", methods=["GET"])
-@require_oauth("profile group")
-def view_group_role(group_role_id: uuid.UUID):
-    """Return the details of the given role."""
-    with require_oauth.acquire("profile group role") as the_token:
-        def __group_role__(conn: db.DbConnection) -> GroupRole:
-            group = user_group(conn, the_token.user).maybe(#type: ignore[misc]
-                DUMMY_GROUP, lambda grp: grp)
-
-            if group == DUMMY_GROUP:
-                raise AuthorisationError(
-                    "A user without a group cannot view group roles.")
-            return group_role_by_id(conn, group, group_role_id)
-        return jsonify(dictify(with_db_connection(__group_role__)))
-
-def __add_remove_priv_to_from_role__(conn: db.DbConnection,
-                                     group_role_id: uuid.UUID,
-                                     direction: str,
-                                     user: User) -> GroupRole:
-    assert direction in ("ADD", "DELETE")
-    group = user_group(conn, user).maybe(# type: ignore[misc]
-        DUMMY_GROUP, lambda grp: grp)
-
-    if group == DUMMY_GROUP:
-        raise AuthorisationError(
-            "You need to be a member of a group to edit roles.")
-    try:
-        privilege_id = request.form.get("privilege_id", "")
-        assert bool(privilege_id), "Privilege to add must be provided."
-        privileges = privileges_by_ids(conn, (privilege_id,))
-        if len(privileges) == 0:
-            raise NotFoundError("Privilege not found.")
-        dir_fns = {
-            "ADD": add_privilege_to_group_role,
-            "DELETE": delete_privilege_from_group_role
-        }
-        return dir_fns[direction](
-            conn,
-            group_role_by_id(conn, group, group_role_id),
-            privileges[0])
-    except AssertionError as aerr:
-        raise InvalidData(aerr.args[0]) from aerr
-
-@groups.route("/role/<uuid:group_role_id>/privilege/add", methods=["POST"])
-@require_oauth("profile group")
-def add_priv_to_role(group_role_id: uuid.UUID) -> Response:
-    """Add privilege to group role."""
-    with require_oauth.acquire("profile group role") as the_token:
-        return jsonify({
-            **dictify(with_db_connection(partial(
-                __add_remove_priv_to_from_role__, group_role_id=group_role_id,
-                direction="ADD", user=the_token.user))),
-            "description": "Privilege added successfully"
-        })
-
-@groups.route("/role/<uuid:group_role_id>/privilege/delete", methods=["POST"])
-@require_oauth("profile group")
-def delete_priv_from_role(group_role_id: uuid.UUID) -> Response:
-    """Delete privilege from group role."""
-    with require_oauth.acquire("profile group role") as the_token:
-        return jsonify({
-            **dictify(with_db_connection(partial(
-                __add_remove_priv_to_from_role__, group_role_id=group_role_id,
-                direction="DELETE", user=the_token.user))),
-            "description": "Privilege deleted successfully"
-        })
diff --git a/gn3/auth/authorisation/oauth2/__init__.py b/gn3/auth/authorisation/oauth2/__init__.py
deleted file mode 100644
index d083773..0000000
--- a/gn3/auth/authorisation/oauth2/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-"""OAuth2 modules."""
diff --git a/gn3/auth/authorisation/oauth2/oauth2client.py b/gn3/auth/authorisation/oauth2/oauth2client.py
deleted file mode 100644
index dc54a41..0000000
--- a/gn3/auth/authorisation/oauth2/oauth2client.py
+++ /dev/null
@@ -1,234 +0,0 @@
-"""OAuth2 Client model."""
-import json
-import datetime
-from uuid import UUID
-from typing import Sequence, Optional, NamedTuple
-
-from pymonad.maybe import Just, Maybe, Nothing
-
-from gn3.auth import db
-
-from gn3.auth.authorisation.errors import NotFoundError
-from gn3.auth.authorisation.users import User, users, user_by_id, same_password
-
-class OAuth2Client(NamedTuple):
-    """
-    Client to the OAuth2 Server.
-
-    This is defined according to the mixin at
-    https://docs.authlib.org/en/latest/specs/rfc6749.html#authlib.oauth2.rfc6749.ClientMixin
-    """
-    client_id: UUID
-    client_secret: str
-    client_id_issued_at: datetime.datetime
-    client_secret_expires_at: datetime.datetime
-    client_metadata: dict
-    user: User
-
-    def check_client_secret(self, client_secret: str) -> bool:
-        """Check whether the `client_secret` matches this client."""
-        return same_password(client_secret, self.client_secret)
-
-    @property
-    def token_endpoint_auth_method(self) -> str:
-        """Return the token endpoint authorisation method."""
-        return self.client_metadata.get("token_endpoint_auth_method", ["none"])
-
-    @property
-    def client_type(self) -> str:
-        """
-        Return the token endpoint authorisation method.
-
-        Acceptable client types:
-        * public: Unable to use registered client secrets, e.g. browsers, apps
-          on mobile devices.
-        * confidential: able to securely authenticate with authorisation server
-          e.g. being able to keep their registered client secret safe.
-        """
-        return self.client_metadata.get("client_type", "public")
-
-    def check_endpoint_auth_method(self, method: str, endpoint: str) -> bool:
-        """
-        Check if the client supports the given method for the given endpoint.
-
-        Acceptable methods:
-        * none: Client is a public client and does not have a client secret
-        * client_secret_post: Client uses the HTTP POST parameters
-        * client_secret_basic: Client uses HTTP Basic
-        """
-        if endpoint == "token":
-            return (method in self.token_endpoint_auth_method
-                    and method == "client_secret_post")
-        if endpoint in ("introspection", "revoke"):
-            return (method in self.token_endpoint_auth_method
-                    and method == "client_secret_basic")
-        return False
-
-    @property
-    def id(self):# pylint: disable=[invalid-name]
-        """Return the client_id."""
-        return self.client_id
-
-    @property
-    def grant_types(self) -> Sequence[str]:
-        """
-        Return the grant types that this client supports.
-
-        Valid grant types:
-        * authorisation_code
-        * implicit
-        * client_credentials
-        * password
-        """
-        return self.client_metadata.get("grant_types", [])
-
-    def check_grant_type(self, grant_type: str) -> bool:
-        """
-        Validate that client can handle the given grant types
-        """
-        return grant_type in self.grant_types
-
-    @property
-    def redirect_uris(self) -> Sequence[str]:
-        """Return the redirect_uris that this client supports."""
-        return self.client_metadata.get('redirect_uris', [])
-
-    def check_redirect_uri(self, redirect_uri: str) -> bool:
-        """
-        Check whether the given `redirect_uri` is one of the expected ones.
-        """
-        return redirect_uri in self.redirect_uris
-
-    @property
-    def response_types(self) -> Sequence[str]:
-        """Return the response_types that this client supports."""
-        return self.client_metadata.get("response_type", [])
-
-    def check_response_type(self, response_type: str) -> bool:
-        """Check whether this client supports `response_type`."""
-        return response_type in self.response_types
-
-    @property
-    def scope(self) -> Sequence[str]:
-        """Return valid scopes for this client."""
-        return tuple(set(self.client_metadata.get("scope", [])))
-
-    def get_allowed_scope(self, scope: str) -> str:
-        """Return list of scopes in `scope` that are supported by this client."""
-        if not bool(scope):
-            return ""
-        requested = scope.split()
-        return " ".join(sorted(set(
-            scp for scp in requested if scp in self.scope)))
-
-    def get_client_id(self):
-        """Return this client's identifier."""
-        return self.client_id
-
-    def get_default_redirect_uri(self) -> str:
-        """Return the default redirect uri"""
-        return self.client_metadata.get("default_redirect_uri", "")
-
-def client(conn: db.DbConnection, client_id: UUID,
-           user: Optional[User] = None) -> Maybe:
-    """Retrieve a client by its ID"""
-    with db.cursor(conn) as cursor:
-        cursor.execute(
-            "SELECT * FROM oauth2_clients WHERE client_id=?", (str(client_id),))
-        result = cursor.fetchone()
-        the_user = user
-        if result:
-            if not bool(the_user):
-                try:
-                    the_user = user_by_id(conn, result["user_id"])
-                except NotFoundError as _nfe:
-                    the_user = None
-
-            return Just(
-                OAuth2Client(UUID(result["client_id"]),
-                             result["client_secret"],
-                             datetime.datetime.fromtimestamp(
-                                 result["client_id_issued_at"]),
-                             datetime.datetime.fromtimestamp(
-                                 result["client_secret_expires_at"]),
-                             json.loads(result["client_metadata"]),
-                             the_user))# type: ignore[arg-type]
-
-    return Nothing
-
-def client_by_id_and_secret(conn: db.DbConnection, client_id: UUID,
-                            client_secret: str) -> OAuth2Client:
-    """Retrieve a client by its ID and secret"""
-    with db.cursor(conn) as cursor:
-        cursor.execute(
-            "SELECT * FROM oauth2_clients WHERE client_id=?",
-            (str(client_id),))
-        row = cursor.fetchone()
-        if bool(row) and same_password(client_secret, row["client_secret"]):
-            return OAuth2Client(
-                client_id, client_secret,
-                datetime.datetime.fromtimestamp(row["client_id_issued_at"]),
-                datetime.datetime.fromtimestamp(
-                    row["client_secret_expires_at"]),
-                json.loads(row["client_metadata"]),
-                user_by_id(conn, UUID(row["user_id"])))
-
-        raise NotFoundError("Could not find client with the given credentials.")
-
-def save_client(conn: db.DbConnection, the_client: OAuth2Client) -> OAuth2Client:
-    """Persist the client details into the database."""
-    with db.cursor(conn) as cursor:
-        query = (
-            "INSERT INTO oauth2_clients "
-            "(client_id, client_secret, client_id_issued_at, "
-            "client_secret_expires_at, client_metadata, user_id) "
-            "VALUES "
-            "(:client_id, :client_secret, :client_id_issued_at, "
-            ":client_secret_expires_at, :client_metadata, :user_id) "
-            "ON CONFLICT (client_id) DO UPDATE SET "
-            "client_secret=:client_secret, "
-            "client_id_issued_at=:client_id_issued_at, "
-            "client_secret_expires_at=:client_secret_expires_at, "
-            "client_metadata=:client_metadata, user_id=:user_id")
-        cursor.execute(
-            query,
-            {
-                "client_id": str(the_client.client_id),
-                "client_secret": the_client.client_secret,
-                "client_id_issued_at": (
-                    the_client.client_id_issued_at.timestamp()),
-                "client_secret_expires_at": (
-                    the_client.client_secret_expires_at.timestamp()),
-                "client_metadata": json.dumps(the_client.client_metadata),
-                "user_id": str(the_client.user.user_id)
-            })
-        return the_client
-
-def oauth2_clients(conn: db.DbConnection) -> tuple[OAuth2Client, ...]:
-    """Fetch a list of all OAuth2 clients."""
-    with db.cursor(conn) as cursor:
-        cursor.execute("SELECT * FROM oauth2_clients")
-        clients_rs = cursor.fetchall()
-        the_users = {
-            usr.user_id: usr for usr in users(
-                conn, tuple({UUID(result["user_id"]) for result in clients_rs}))
-        }
-        return tuple(OAuth2Client(UUID(result["client_id"]),
-                                  result["client_secret"],
-                                  datetime.datetime.fromtimestamp(
-                                     result["client_id_issued_at"]),
-                                  datetime.datetime.fromtimestamp(
-                                     result["client_secret_expires_at"]),
-                                  json.loads(result["client_metadata"]),
-                                  the_users[UUID(result["user_id"])])
-                     for result in clients_rs)
-
-def delete_client(conn: db.DbConnection, the_client: OAuth2Client) -> OAuth2Client:
-    """Delete the given client from the database"""
-    with db.cursor(conn) as cursor:
-        params = (str(the_client.client_id),)
-        cursor.execute("DELETE FROM authorisation_code WHERE client_id=?",
-                       params)
-        cursor.execute("DELETE FROM oauth2_tokens WHERE client_id=?", params)
-        cursor.execute("DELETE FROM oauth2_clients WHERE client_id=?", params)
-        return the_client
diff --git a/gn3/auth/authorisation/oauth2/oauth2token.py b/gn3/auth/authorisation/oauth2/oauth2token.py
deleted file mode 100644
index bb19039..0000000
--- a/gn3/auth/authorisation/oauth2/oauth2token.py
+++ /dev/null
@@ -1,133 +0,0 @@
-"""OAuth2 Token"""
-import uuid
-import datetime
-from typing import NamedTuple, Optional
-
-from pymonad.maybe import Just, Maybe, Nothing
-
-from gn3.auth import db
-
-from gn3.auth.authorisation.errors import NotFoundError
-from gn3.auth.authorisation.users import User, user_by_id
-
-from .oauth2client import client, OAuth2Client
-
-class OAuth2Token(NamedTuple):
-    """Implement Tokens for OAuth2."""
-    token_id: uuid.UUID
-    client: OAuth2Client
-    token_type: str
-    access_token: str
-    refresh_token: Optional[str]
-    scope: str
-    revoked: bool
-    issued_at: datetime.datetime
-    expires_in: int
-    user: User
-
-    @property
-    def expires_at(self) -> datetime.datetime:
-        """Return the time when the token expires."""
-        return self.issued_at + datetime.timedelta(seconds=self.expires_in)
-
-    def check_client(self, client: OAuth2Client) -> bool:# pylint: disable=[redefined-outer-name]
-        """Check whether the token is issued to given `client`."""
-        return client.client_id == self.client.client_id
-
-    def get_expires_in(self) -> int:
-        """Return the `expires_in` value for the token."""
-        return self.expires_in
-
-    def get_scope(self) -> str:
-        """Return the valid scope for the token."""
-        return self.scope
-
-    def is_expired(self) -> bool:
-        """Check whether the token is expired."""
-        return self.expires_at < datetime.datetime.now()
-
-    def is_revoked(self):
-        """Check whether the token has been revoked."""
-        return self.revoked
-
-def __token_from_resultset__(conn: db.DbConnection, rset) -> Maybe:
-    def __identity__(val):
-        return val
-    try:
-        the_user = user_by_id(conn, uuid.UUID(rset["user_id"]))
-    except NotFoundError as _nfe:
-        the_user = None
-    the_client = client(conn, uuid.UUID(rset["client_id"]), the_user)
-
-    if the_client.is_just() and bool(the_user):
-        return Just(OAuth2Token(token_id=uuid.UUID(rset["token_id"]),
-                                client=the_client.maybe(None, __identity__),
-                                token_type=rset["token_type"],
-                                access_token=rset["access_token"],
-                                refresh_token=rset["refresh_token"],
-                                scope=rset["scope"],
-                                revoked=(rset["revoked"] == 1),
-                                issued_at=datetime.datetime.fromtimestamp(
-                                    rset["issued_at"]),
-                                expires_in=rset["expires_in"],
-                                user=the_user))# type: ignore[arg-type]
-
-    return Nothing
-
-def token_by_access_token(conn: db.DbConnection, token_str: str) -> Maybe:
-    """Retrieve token by its token string"""
-    with db.cursor(conn) as cursor:
-        cursor.execute("SELECT * FROM oauth2_tokens WHERE access_token=?",
-                       (token_str,))
-        res = cursor.fetchone()
-        if res:
-            return __token_from_resultset__(conn, res)
-
-    return Nothing
-
-def token_by_refresh_token(conn: db.DbConnection, token_str: str) -> Maybe:
-    """Retrieve token by its token string"""
-    with db.cursor(conn) as cursor:
-        cursor.execute(
-            "SELECT * FROM oauth2_tokens WHERE refresh_token=?",
-            (token_str,))
-        res = cursor.fetchone()
-        if res:
-            return __token_from_resultset__(conn, res)
-
-    return Nothing
-
-def revoke_token(token: OAuth2Token) -> OAuth2Token:
-    """
-    Return a new token derived from `token` with the `revoked` field set to
-    `True`.
-    """
-    return OAuth2Token(
-        token_id=token.token_id, client=token.client,
-        token_type=token.token_type, access_token=token.access_token,
-        refresh_token=token.refresh_token, scope=token.scope, revoked=True,
-        issued_at=token.issued_at, expires_in=token.expires_in, user=token.user)
-
-def save_token(conn: db.DbConnection, token: OAuth2Token) -> None:
-    """Save/Update the token."""
-    with db.cursor(conn) as cursor:
-        cursor.execute(
-            ("INSERT INTO oauth2_tokens VALUES (:token_id, :client_id, "
-             ":token_type, :access_token, :refresh_token, :scope, :revoked, "
-             ":issued_at, :expires_in, :user_id) "
-             "ON CONFLICT (token_id) DO UPDATE SET "
-             "refresh_token=:refresh_token, revoked=:revoked, "
-             "expires_in=:expires_in "
-             "WHERE token_id=:token_id"),
-            {
-                "token_id": str(token.token_id),
-                "client_id": str(token.client.client_id),
-                "token_type": token.token_type,
-                "access_token": token.access_token,
-                "refresh_token": token.refresh_token,
-                "scope": token.scope,
-                "revoked": 1 if token.revoked else 0,
-                "issued_at": int(token.issued_at.timestamp()),
-                "expires_in": token.expires_in,
-                "user_id": str(token.user.user_id)
-            })
diff --git a/gn3/auth/authorisation/oauth2/resource_server.py b/gn3/auth/authorisation/oauth2/resource_server.py
deleted file mode 100644
index e806dc5..0000000
--- a/gn3/auth/authorisation/oauth2/resource_server.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""Protect the resources endpoints"""
-
-from flask import current_app as app
-from authlib.oauth2.rfc6750 import BearerTokenValidator as _BearerTokenValidator
-from authlib.integrations.flask_oauth2 import ResourceProtector
-
-from gn3.auth import db
-from gn3.auth.authorisation.oauth2.oauth2token import token_by_access_token
-
-class BearerTokenValidator(_BearerTokenValidator):
-    """Extends `authlib.oauth2.rfc6750.BearerTokenValidator`"""
-    def authenticate_token(self, token_string: str):
-        with db.connection(app.config["AUTH_DB"]) as conn:
-            return token_by_access_token(conn, token_string).maybe(# type: ignore[misc]
-                None, lambda tok: tok)
-
-require_oauth = ResourceProtector()
-
-require_oauth.register_token_validator(BearerTokenValidator())
diff --git a/gn3/auth/authorisation/privileges.py b/gn3/auth/authorisation/privileges.py
deleted file mode 100644
index 7907d76..0000000
--- a/gn3/auth/authorisation/privileges.py
+++ /dev/null
@@ -1,47 +0,0 @@
-"""Handle privileges"""
-from typing import Any, Iterable, NamedTuple
-
-from gn3.auth import db
-from gn3.auth.authorisation.users import User
-
-class Privilege(NamedTuple):
-    """Class representing a privilege: creates immutable objects."""
-    privilege_id: str
-    privilege_description: str
-
-    def dictify(self) -> dict[str, Any]:
-        """Return a dict representation of `Privilege` objects."""
-        return {
-            "privilege_id": self.privilege_id,
-            "privilege_description": self.privilege_description
-        }
-
-def user_privileges(conn: db.DbConnection, user: User) -> Iterable[Privilege]:
-    """Fetch the user's privileges from the database."""
-    with db.cursor(conn) as cursor:
-        cursor.execute(
-            ("SELECT p.privilege_id, p.privilege_description "
-             "FROM user_roles AS ur "
-             "INNER JOIN role_privileges AS rp ON ur.role_id=rp.role_id "
-             "INNER JOIN privileges AS p ON rp.privilege_id=p.privilege_id "
-             "WHERE ur.user_id=?"),
-            (str(user.user_id),))
-        results = cursor.fetchall()
-
-    return (Privilege(row[0], row[1]) for row in results)
-
-def privileges_by_ids(
-        conn: db.DbConnection, privileges_ids: tuple[str, ...]) -> tuple[
-            Privilege, ...]:
-    """Fetch privileges by their ids."""
-    if len(privileges_ids) == 0:
-        return tuple()
-
-    with db.cursor(conn) as cursor:
-        clause = ", ".join(["?"] * len(privileges_ids))
-        cursor.execute(
-            f"SELECT * FROM privileges WHERE privilege_id IN ({clause})",
-            privileges_ids)
-        return tuple(
-            Privilege(row["privilege_id"], row["privilege_description"])
-            for row in cursor.fetchall())
diff --git a/gn3/auth/authorisation/resources/__init__.py b/gn3/auth/authorisation/resources/__init__.py
deleted file mode 100644
index 869ab60..0000000
--- a/gn3/auth/authorisation/resources/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-"""Initialise the `gn3.auth.authorisation.resources` package."""
-from .models import Resource, ResourceCategory
diff --git a/gn3/auth/authorisation/resources/checks.py b/gn3/auth/authorisation/resources/checks.py
deleted file mode 100644
index 1f5a0f9..0000000
--- a/gn3/auth/authorisation/resources/checks.py
+++ /dev/null
@@ -1,47 +0,0 @@
-"""Handle authorisation checks for resources"""
-from uuid import UUID
-from functools import reduce
-from typing import Sequence
-
-from gn3.auth import db
-from gn3.auth.authorisation.users import User
-
-def __organise_privileges_by_resource_id__(rows):
-    def __organise__(privs, row):
-        resource_id = UUID(row["resource_id"])
-        return {
-            **privs,
-            resource_id: (row["privilege_id"],) + privs.get(
-                resource_id, tuple())
-        }
-    return reduce(__organise__, rows, {})
-
-def authorised_for(conn: db.DbConnection, user: User, privileges: tuple[str],
-                   resource_ids: Sequence[UUID]) -> dict[UUID, bool]:
-    """
-    Check whether `user` is authorised to access `resources` according to given
-    `privileges`.
-    """
-    with db.cursor(conn) as cursor:
-        cursor.execute(
-            ("SELECT guror.*, rp.privilege_id FROM "
-             "group_user_roles_on_resources AS guror "
-             "INNER JOIN group_roles AS gr ON  "
-             "(guror.group_id=gr.group_id AND guror.role_id=gr.role_id) "
-             "INNER JOIN roles AS r ON gr.role_id=r.role_id "
-             "INNER JOIN role_privileges AS rp ON r.role_id=rp.role_id "
-             "WHERE guror.user_id=? "
-             f"AND guror.resource_id IN ({', '.join(['?']*len(resource_ids))})"
-             f"AND rp.privilege_id IN ({', '.join(['?']*len(privileges))})"),
-            ((str(user.user_id),) + tuple(
-                str(r_id) for r_id in resource_ids) + tuple(privileges)))
-        resource_privileges = __organise_privileges_by_resource_id__(
-            cursor.fetchall())
-        authorised = tuple(resource_id for resource_id, res_privileges
-                           in resource_privileges.items()
-                           if all(priv in res_privileges
-                                  for priv in privileges))
-        return {
-            resource_id: resource_id in authorised
-            for resource_id in resource_ids
-        }
diff --git a/gn3/auth/authorisation/resources/models.py b/gn3/auth/authorisation/resources/models.py
deleted file mode 100644
index cf7769e..0000000
--- a/gn3/auth/authorisation/resources/models.py
+++ /dev/null
@@ -1,579 +0,0 @@
-"""Handle the management of resources."""
-import json
-import sqlite3
-from uuid import UUID, uuid4
-from functools import reduce, partial
-from typing import Any, Dict, Sequence, Optional, NamedTuple
-
-from gn3.auth import db
-from gn3.auth.dictify import dictify
-from gn3.auth.authorisation.users import User
-from gn3.auth.db_utils import with_db_connection
-
-from .checks import authorised_for
-
-from ..checks import authorised_p
-from ..errors import NotFoundError, AuthorisationError
-from ..groups.models import (
-    Group, GroupRole, user_group, group_by_id, is_group_leader)
-
-class MissingGroupError(AuthorisationError):
-    """Raised for any resource operation without a group."""
-
-class ResourceCategory(NamedTuple):
-    """Class representing a resource category."""
-    resource_category_id: UUID
-    resource_category_key: str
-    resource_category_description: str
-
-    def dictify(self) -> dict[str, Any]:
-        """Return a dict representation of `ResourceCategory` objects."""
-        return {
-            "resource_category_id": self.resource_category_id,
-            "resource_category_key": self.resource_category_key,
-            "resource_category_description": self.resource_category_description
-        }
-
-class Resource(NamedTuple):
-    """Class representing a resource."""
-    group: Group
-    resource_id: UUID
-    resource_name: str
-    resource_category: ResourceCategory
-    public: bool
-    resource_data: Sequence[dict[str, Any]] = tuple()
-
-    def dictify(self) -> dict[str, Any]:
-        """Return a dict representation of `Resource` objects."""
-        return {
-            "group": dictify(self.group), "resource_id": self.resource_id,
-            "resource_name": self.resource_name,
-            "resource_category": dictify(self.resource_category),
-            "public": self.public,
-            "resource_data": self.resource_data
-        }
-
-def __assign_resource_owner_role__(cursor, resource, user):
-    """Assign `user` the 'Resource Owner' role for `resource`."""
-    cursor.execute(
-        "SELECT gr.* FROM group_roles AS gr INNER JOIN roles AS r "
-        "ON gr.role_id=r.role_id WHERE r.role_name='resource-owner' "
-        "AND gr.group_id=?",
-        (str(resource.group.group_id),))
-    role = cursor.fetchone()
-    if not role:
-        cursor.execute("SELECT * FROM roles WHERE role_name='resource-owner'")
-        role = cursor.fetchone()
-        cursor.execute(
-            "INSERT INTO group_roles VALUES "
-            "(:group_role_id, :group_id, :role_id)",
-            {"group_role_id": str(uuid4()),
-             "group_id": str(resource.group.group_id),
-             "role_id": role["role_id"]})
-
-    cursor.execute(
-            "INSERT INTO group_user_roles_on_resources "
-            "VALUES ("
-            ":group_id, :user_id, :role_id, :resource_id"
-            ")",
-            {"group_id": str(resource.group.group_id),
-             "user_id": str(user.user_id),
-             "role_id": role["role_id"],
-             "resource_id": str(resource.resource_id)})
-
-@authorised_p(("group:resource:create-resource",),
-              error_description="Insufficient privileges to create a resource",
-              oauth2_scope="profile resource")
-def create_resource(
-        conn: db.DbConnection, resource_name: str,
-        resource_category: ResourceCategory, user: User,
-        public: bool) -> Resource:
-    """Create a resource item."""
-    with db.cursor(conn) as cursor:
-        group = user_group(conn, user).maybe(
-            False, lambda grp: grp)# type: ignore[misc, arg-type]
-        if not group:
-            raise MissingGroupError(
-                "User with no group cannot create a resource.")
-        resource = Resource(
-            group, uuid4(), resource_name, resource_category, public)
-        cursor.execute(
-            "INSERT INTO resources VALUES (?, ?, ?, ?, ?)",
-            (str(resource.group.group_id), str(resource.resource_id),
-             resource_name,
-             str(resource.resource_category.resource_category_id),
-             1 if resource.public else 0))
-        __assign_resource_owner_role__(cursor, resource, user)
-
-    return resource
-
-def resource_category_by_id(
-        conn: db.DbConnection, category_id: UUID) -> ResourceCategory:
-    """Retrieve a resource category by its ID."""
-    with db.cursor(conn) as cursor:
-        cursor.execute(
-            "SELECT * FROM resource_categories WHERE "
-            "resource_category_id=?",
-            (str(category_id),))
-        results = cursor.fetchone()
-        if results:
-            return ResourceCategory(
-                UUID(results["resource_category_id"]),
-                results["resource_category_key"],
-                results["resource_category_description"])
-
-    raise NotFoundError(
-        f"Could not find a ResourceCategory with ID '{category_id}'")
-
-def resource_categories(conn: db.DbConnection) -> Sequence[ResourceCategory]:
-    """Retrieve all available resource categories"""
-    with db.cursor(conn) as cursor:
-        cursor.execute("SELECT * FROM resource_categories")
-        return tuple(
-            ResourceCategory(UUID(row[0]), row[1], row[2])
-            for row in cursor.fetchall())
-    return tuple()
-
-def public_resources(conn: db.DbConnection) -> Sequence[Resource]:
-    """List all resources marked as public"""
-    categories = {
-        str(cat.resource_category_id): cat for cat in resource_categories(conn)
-    }
-    with db.cursor(conn) as cursor:
-        cursor.execute("SELECT * FROM resources WHERE public=1")
-        results = cursor.fetchall()
-        group_uuids = tuple(row[0] for row in results)
-        query = ("SELECT * FROM groups WHERE group_id IN "
-                 f"({', '.join(['?'] * len(group_uuids))})")
-        cursor.execute(query, group_uuids)
-        groups = {
-            row[0]: Group(
-                UUID(row[0]), row[1], json.loads(row[2] or "{}"))
-            for row in cursor.fetchall()
-        }
-        return tuple(
-            Resource(groups[row[0]], UUID(row[1]), row[2], categories[row[3]],
-                     bool(row[4]))
-            for row in results)
-
-def group_leader_resources(
-        conn: db.DbConnection, user: User, group: Group,
-        res_categories: Dict[UUID, ResourceCategory]) -> Sequence[Resource]:
-    """Return all the resources available to the group leader"""
-    if is_group_leader(conn, user, group):
-        with db.cursor(conn) as cursor:
-            cursor.execute("SELECT * FROM resources WHERE group_id=?",
-                           (str(group.group_id),))
-            return tuple(
-                Resource(group, UUID(row[1]), row[2],
-                         res_categories[UUID(row[3])], bool(row[4]))
-                for row in cursor.fetchall())
-    return tuple()
-
-def user_resources(conn: db.DbConnection, user: User) -> Sequence[Resource]:
-    """List the resources available to the user"""
-    categories = { # Repeated in `public_resources` function
-        cat.resource_category_id: cat for cat in resource_categories(conn)
-    }
-    with db.cursor(conn) as cursor:
-        def __all_resources__(group) -> Sequence[Resource]:
-            gl_resources = group_leader_resources(conn, user, group, categories)
-
-            cursor.execute(
-                ("SELECT resources.* FROM group_user_roles_on_resources "
-                 "LEFT JOIN resources "
-                 "ON group_user_roles_on_resources.resource_id=resources.resource_id "
-                 "WHERE group_user_roles_on_resources.group_id = ? "
-                 "AND group_user_roles_on_resources.user_id = ?"),
-                (str(group.group_id), str(user.user_id)))
-            rows = cursor.fetchall()
-            private_res = tuple(
-                Resource(group, UUID(row[1]), row[2], categories[UUID(row[3])],
-                         bool(row[4]))
-                for row in rows)
-            return tuple({
-                res.resource_id: res
-                for res in
-                (private_res + gl_resources + public_resources(conn))# type: ignore[operator]
-            }.values())
-
-        # Fix the typing here
-        return user_group(conn, user).map(__all_resources__).maybe(# type: ignore[arg-type,misc]
-            public_resources(conn), lambda res: res)# type: ignore[arg-type,return-value]
-
-def resource_data(conn, resource, offset: int = 0, limit: Optional[int] = None) -> tuple[dict, ...]:
-    """
-    Retrieve the data for `resource`, optionally limiting the number of items.
-    """
-    resource_data_function = {
-        "mrna": mrna_resource_data,
-        "genotype": genotype_resource_data,
-        "phenotype": phenotype_resource_data
-    }
-    with db.cursor(conn) as cursor:
-        return tuple(
-            dict(data_row) for data_row in
-            resource_data_function[
-                resource.resource_category.resource_category_key](
-                    cursor, resource.resource_id, offset, limit))
-
-def attach_resource_data(cursor: db.DbCursor, resource: Resource) -> Resource:
-    """Attach the linked data to the resource"""
-    resource_data_function = {
-        "mrna": mrna_resource_data,
-        "genotype": genotype_resource_data,
-        "phenotype": phenotype_resource_data
-    }
-    category = resource.resource_category
-    data_rows = tuple(
-        dict(data_row) for data_row in
-        resource_data_function[category.resource_category_key](
-            cursor, resource.resource_id))
-    return Resource(
-        resource.group, resource.resource_id, resource.resource_name,
-        resource.resource_category, resource.public, data_rows)
-
-def mrna_resource_data(cursor: db.DbCursor,
-                       resource_id: UUID,
-                       offset: int = 0,
-                       limit: Optional[int] = None) -> Sequence[sqlite3.Row]:
-    """Fetch data linked to a mRNA resource"""
-    cursor.execute(
-        (("SELECT * FROM mrna_resources AS mr "
-          "INNER JOIN linked_mrna_data AS lmr "
-          "ON mr.data_link_id=lmr.data_link_id "
-          "WHERE mr.resource_id=?") + (
-              f" LIMIT {limit} OFFSET {offset}" if bool(limit) else "")),
-        (str(resource_id),))
-    return cursor.fetchall()
-
-def genotype_resource_data(
-        cursor: db.DbCursor,
-        resource_id: UUID,
-        offset: int = 0,
-        limit: Optional[int] = None) -> Sequence[sqlite3.Row]:
-    """Fetch data linked to a Genotype resource"""
-    cursor.execute(
-        (("SELECT * FROM genotype_resources AS gr "
-          "INNER JOIN linked_genotype_data AS lgd "
-          "ON gr.data_link_id=lgd.data_link_id "
-          "WHERE gr.resource_id=?") + (
-              f" LIMIT {limit} OFFSET {offset}" if bool(limit) else "")),
-        (str(resource_id),))
-    return cursor.fetchall()
-
-def phenotype_resource_data(
-        cursor: db.DbCursor,
-        resource_id: UUID,
-        offset: int = 0,
-        limit: Optional[int] = None) -> Sequence[sqlite3.Row]:
-    """Fetch data linked to a Phenotype resource"""
-    cursor.execute(
-        ("SELECT * FROM phenotype_resources AS pr "
-         "INNER JOIN linked_phenotype_data AS lpd "
-         "ON pr.data_link_id=lpd.data_link_id "
-         "WHERE pr.resource_id=?") + (
-             f" LIMIT {limit} OFFSET {offset}" if bool(limit) else ""),
-        (str(resource_id),))
-    return cursor.fetchall()
-
-def resource_by_id(
-        conn: db.DbConnection, user: User, resource_id: UUID) -> Resource:
-    """Retrieve a resource by its ID."""
-    if not authorised_for(
-            conn, user, ("group:resource:view-resource",),
-            (resource_id,))[resource_id]:
-        raise AuthorisationError(
-            "You are not authorised to access resource with id "
-            f"'{resource_id}'.")
-
-    with db.cursor(conn) as cursor:
-        cursor.execute("SELECT * FROM resources WHERE resource_id=:id",
-                       {"id": str(resource_id)})
-        row = cursor.fetchone()
-        if row:
-            return Resource(
-                group_by_id(conn, UUID(row["group_id"])),
-                UUID(row["resource_id"]), row["resource_name"],
-                resource_category_by_id(conn, row["resource_category_id"]),
-                bool(int(row["public"])))
-
-    raise NotFoundError(f"Could not find a resource with id '{resource_id}'")
-
-def __link_mrna_data_to_resource__(
-        conn: db.DbConnection, resource: Resource, data_link_id: UUID) -> dict:
-    """Link mRNA Assay data with a resource."""
-    with db.cursor(conn) as cursor:
-        params = {
-            "group_id": str(resource.group.group_id),
-            "resource_id": str(resource.resource_id),
-            "data_link_id": str(data_link_id)
-        }
-        cursor.execute(
-            "INSERT INTO mrna_resources VALUES"
-            "(:group_id, :resource_id, :data_link_id)",
-            params)
-        return params
-
-def __link_geno_data_to_resource__(
-        conn: db.DbConnection, resource: Resource, data_link_id: UUID) -> dict:
-    """Link Genotype data with a resource."""
-    with db.cursor(conn) as cursor:
-        params = {
-            "group_id": str(resource.group.group_id),
-            "resource_id": str(resource.resource_id),
-            "data_link_id": str(data_link_id)
-        }
-        cursor.execute(
-            "INSERT INTO genotype_resources VALUES"
-            "(:group_id, :resource_id, :data_link_id)",
-            params)
-        return params
-
-def __link_pheno_data_to_resource__(
-        conn: db.DbConnection, resource: Resource, data_link_id: UUID) -> dict:
-    """Link Phenotype data with a resource."""
-    with db.cursor(conn) as cursor:
-        params = {
-            "group_id": str(resource.group.group_id),
-            "resource_id": str(resource.resource_id),
-            "data_link_id": str(data_link_id)
-        }
-        cursor.execute(
-            "INSERT INTO phenotype_resources VALUES"
-            "(:group_id, :resource_id, :data_link_id)",
-            params)
-        return params
-
-def link_data_to_resource(
-        conn: db.DbConnection, user: User, resource_id: UUID, dataset_type: str,
-        data_link_id: UUID) -> dict:
-    """Link data to resource."""
-    if not authorised_for(
-            conn, user, ("group:resource:edit-resource",),
-            (resource_id,))[resource_id]:
-        raise AuthorisationError(
-            "You are not authorised to link data to resource with id "
-            f"{resource_id}")
-
-    resource = with_db_connection(partial(
-        resource_by_id, user=user, resource_id=resource_id))
-    return {
-        "mrna": __link_mrna_data_to_resource__,
-        "genotype": __link_geno_data_to_resource__,
-        "phenotype": __link_pheno_data_to_resource__,
-    }[dataset_type.lower()](conn, resource, data_link_id)
-
-def __unlink_mrna_data_to_resource__(
-        conn: db.DbConnection, resource: Resource, data_link_id: UUID) -> dict:
-    """Unlink data from mRNA Assay resources"""
-    with db.cursor(conn) as cursor:
-        cursor.execute("DELETE FROM mrna_resources "
-                       "WHERE resource_id=? AND data_link_id=?",
-                       (str(resource.resource_id), str(data_link_id)))
-        return {
-            "resource_id": str(resource.resource_id),
-            "dataset_type": resource.resource_category.resource_category_key,
-            "data_link_id": data_link_id
-        }
-
-def __unlink_geno_data_to_resource__(
-        conn: db.DbConnection, resource: Resource, data_link_id: UUID) -> dict:
-    """Unlink data from Genotype resources"""
-    with db.cursor(conn) as cursor:
-        cursor.execute("DELETE FROM genotype_resources "
-                       "WHERE resource_id=? AND data_link_id=?",
-                       (str(resource.resource_id), str(data_link_id)))
-        return {
-            "resource_id": str(resource.resource_id),
-            "dataset_type": resource.resource_category.resource_category_key,
-            "data_link_id": data_link_id
-        }
-
-def __unlink_pheno_data_to_resource__(
-        conn: db.DbConnection, resource: Resource, data_link_id: UUID) -> dict:
-    """Unlink data from Phenotype resources"""
-    with db.cursor(conn) as cursor:
-        cursor.execute("DELETE FROM phenotype_resources "
-                       "WHERE resource_id=? AND data_link_id=?",
-                       (str(resource.resource_id), str(data_link_id)))
-        return {
-            "resource_id": str(resource.resource_id),
-            "dataset_type": resource.resource_category.resource_category_key,
-            "data_link_id": str(data_link_id)
-        }
-
-def unlink_data_from_resource(
-        conn: db.DbConnection, user: User, resource_id: UUID, data_link_id: UUID):
-    """Unlink data from resource."""
-    if not authorised_for(
-            conn, user, ("group:resource:edit-resource",),
-            (resource_id,))[resource_id]:
-        raise AuthorisationError(
-            "You are not authorised to link data to resource with id "
-            f"{resource_id}")
-
-    resource = with_db_connection(partial(
-        resource_by_id, user=user, resource_id=resource_id))
-    dataset_type = resource.resource_category.resource_category_key
-    return {
-        "mrna": __unlink_mrna_data_to_resource__,
-        "genotype": __unlink_geno_data_to_resource__,
-        "phenotype": __unlink_pheno_data_to_resource__,
-    }[dataset_type.lower()](conn, resource, data_link_id)
-
-def organise_resources_by_category(resources: Sequence[Resource]) -> dict[
-        ResourceCategory, tuple[Resource]]:
-    """Organise the `resources` by their categories."""
-    def __organise__(accumulator, resource):
-        category = resource.resource_category
-        return {
-            **accumulator,
-            category: accumulator.get(category, tuple()) + (resource,)
-        }
-    return reduce(__organise__, resources, {})
-
-def __attach_data__(
-        data_rows: Sequence[sqlite3.Row],
-        resources: Sequence[Resource]) -> Sequence[Resource]:
-    def __organise__(acc, row):
-        resource_id = UUID(row["resource_id"])
-        return {
-            **acc,
-            resource_id: acc.get(resource_id, tuple()) + (dict(row),)
-        }
-    organised: dict[UUID, tuple[dict, ...]] = reduce(__organise__, data_rows, {})
-    return tuple(
-        Resource(
-            resource.group, resource.resource_id, resource.resource_name,
-            resource.resource_category, resource.public,
-            organised.get(resource.resource_id, tuple()))
-        for resource in resources)
-
-def attach_mrna_resources_data(
-        cursor, resources: Sequence[Resource]) -> Sequence[Resource]:
-    """Attach linked data to mRNA Assay resources"""
-    placeholders = ", ".join(["?"] * len(resources))
-    cursor.execute(
-        "SELECT * FROM mrna_resources AS mr INNER JOIN linked_mrna_data AS lmd"
-        " ON mr.data_link_id=lmd.data_link_id "
-        f"WHERE mr.resource_id IN ({placeholders})",
-        tuple(str(resource.resource_id) for resource in resources))
-    return __attach_data__(cursor.fetchall(), resources)
-
-def attach_genotype_resources_data(
-        cursor, resources: Sequence[Resource]) -> Sequence[Resource]:
-    """Attach linked data to Genotype resources"""
-    placeholders = ", ".join(["?"] * len(resources))
-    cursor.execute(
-        "SELECT * FROM genotype_resources AS gr "
-        "INNER JOIN linked_genotype_data AS lgd "
-        "ON gr.data_link_id=lgd.data_link_id "
-        f"WHERE gr.resource_id IN ({placeholders})",
-        tuple(str(resource.resource_id) for resource in resources))
-    return __attach_data__(cursor.fetchall(), resources)
-
-def attach_phenotype_resources_data(
-        cursor, resources: Sequence[Resource]) -> Sequence[Resource]:
-    """Attach linked data to Phenotype resources"""
-    placeholders = ", ".join(["?"] * len(resources))
-    cursor.execute(
-        "SELECT * FROM phenotype_resources AS pr "
-        "INNER JOIN linked_phenotype_data AS lpd "
-        "ON pr.data_link_id=lpd.data_link_id "
-        f"WHERE pr.resource_id IN ({placeholders})",
-        tuple(str(resource.resource_id) for resource in resources))
-    return __attach_data__(cursor.fetchall(), resources)
-
-def attach_resources_data(
-        conn: db.DbConnection, resources: Sequence[Resource]) -> Sequence[
-            Resource]:
-    """Attach linked data for each resource in `resources`"""
-    resource_data_function = {
-        "mrna": attach_mrna_resources_data,
-        "genotype": attach_genotype_resources_data,
-        "phenotype": attach_phenotype_resources_data
-    }
-    organised = organise_resources_by_category(resources)
-    with db.cursor(conn) as cursor:
-        return tuple(
-            resource for categories in
-            (resource_data_function[category.resource_category_key](
-                cursor, rscs)
-             for category, rscs in organised.items())
-            for resource in categories)
-
-@authorised_p(
-    ("group:user:assign-role",),
-    "You cannot assign roles to users for this group.",
-    oauth2_scope="profile group role resource")
-def assign_resource_user(
-        conn: db.DbConnection, resource: Resource, user: User,
-        role: GroupRole) -> dict:
-    """Assign `role` to `user` for the specific `resource`."""
-    with db.cursor(conn) as cursor:
-        cursor.execute(
-            "INSERT INTO "
-            "group_user_roles_on_resources(group_id, user_id, role_id, "
-            "resource_id) "
-            "VALUES (?, ?, ?, ?) "
-            "ON CONFLICT (group_id, user_id, role_id, resource_id) "
-            "DO NOTHING",
-            (str(resource.group.group_id), str(user.user_id),
-             str(role.role.role_id), str(resource.resource_id)))
-        return {
-            "resource": dictify(resource),
-            "user": dictify(user),
-            "role": dictify(role),
-            "description": (
-                f"The user '{user.name}'({user.email}) was assigned the "
-                f"'{role.role.role_name}' role on resource with ID "
-                f"'{resource.resource_id}'.")}
-
-@authorised_p(
-    ("group:user:assign-role",),
-    "You cannot assign roles to users for this group.",
-    oauth2_scope="profile group role resource")
-def unassign_resource_user(
-        conn: db.DbConnection, resource: Resource, user: User,
-        role: GroupRole) -> dict:
-    """Assign `role` to `user` for the specific `resource`."""
-    with db.cursor(conn) as cursor:
-        cursor.execute(
-            "DELETE FROM group_user_roles_on_resources "
-            "WHERE group_id=? AND user_id=? AND role_id=? AND resource_id=?",
-            (str(resource.group.group_id), str(user.user_id),
-             str(role.role.role_id), str(resource.resource_id)))
-        return {
-            "resource": dictify(resource),
-            "user": dictify(user),
-            "role": dictify(role),
-            "description": (
-                f"The user '{user.name}'({user.email}) had the "
-                f"'{role.role.role_name}' role on resource with ID "
-                f"'{resource.resource_id}' taken away.")}
-
-def save_resource(
-        conn: db.DbConnection, user: User, resource: Resource) -> Resource:
-    """Update an existing resource."""
-    resource_id = resource.resource_id
-    authorised = authorised_for(
-        conn, user, ("group:resource:edit-resource",), (resource_id,))
-    if authorised[resource_id]:
-        with db.cursor(conn) as cursor:
-            cursor.execute(
-                "UPDATE resources SET "
-                "resource_name=:resource_name, "
-                "public=:public "
-                "WHERE group_id=:group_id "
-                "AND resource_id=:resource_id",
-                {
-                    "resource_name": resource.resource_name,
-                    "public": 1 if resource.public else 0,
-                    "group_id": str(resource.group.group_id),
-                    "resource_id": str(resource.resource_id)
-                })
-            return resource
-
-    raise AuthorisationError(
-        "You do not have the appropriate privileges to edit this resource.")
diff --git a/gn3/auth/authorisation/resources/views.py b/gn3/auth/authorisation/resources/views.py
deleted file mode 100644
index bda67cd..0000000
--- a/gn3/auth/authorisation/resources/views.py
+++ /dev/null
@@ -1,272 +0,0 @@
-"""The views/routes for the resources package"""
-import uuid
-import json
-import sqlite3
-from functools import reduce
-
-from flask import request, jsonify, Response, Blueprint, current_app as app
-
-from gn3.auth.db_utils import with_db_connection
-from gn3.auth.authorisation.oauth2.resource_server import require_oauth
-from gn3.auth.authorisation.users import User, user_by_id, user_by_email
-
-from .checks import authorised_for
-from .models import (
-    Resource, save_resource, resource_data, resource_by_id, resource_categories,
-    assign_resource_user, link_data_to_resource, unassign_resource_user,
-    resource_category_by_id, unlink_data_from_resource,
-    create_resource as _create_resource)
-
-from ..roles import Role
-from ..errors import InvalidData, InconsistencyError, AuthorisationError
-from ..groups.models import Group, GroupRole, group_role_by_id
-
-from ... import db
-from ...dictify import dictify
-
-resources = Blueprint("resources", __name__)
-
-@resources.route("/categories", methods=["GET"])
-@require_oauth("profile group resource")
-def list_resource_categories() -> Response:
-    """Retrieve all resource categories"""
-    db_uri = app.config["AUTH_DB"]
-    with db.connection(db_uri) as conn:
-        return jsonify(tuple(
-            dictify(category) for category in resource_categories(conn)))
-
-@resources.route("/create", methods=["POST"])
-@require_oauth("profile group resource")
-def create_resource() -> Response:
-    """Create a new resource"""
-    with require_oauth.acquire("profile group resource") as the_token:
-        form = request.form
-        resource_name = form.get("resource_name")
-        resource_category_id = uuid.UUID(form.get("resource_category"))
-        db_uri = app.config["AUTH_DB"]
-        with db.connection(db_uri) as conn:
-            try:
-                resource = _create_resource(
-                    conn,
-                    resource_name,
-                    resource_category_by_id(conn, resource_category_id),
-                    the_token.user,
-                    (form.get("public") == "on"))
-                return jsonify(dictify(resource))
-            except sqlite3.IntegrityError as sql3ie:
-                if sql3ie.args[0] == ("UNIQUE constraint failed: "
-                                      "resources.resource_name"):
-                    raise InconsistencyError(
-                        "You cannot have duplicate resource names.") from sql3ie
-                app.logger.debug(
-                    f"{type(sql3ie)=}: {sql3ie=}")
-                raise
-
-@resources.route("/view/<uuid:resource_id>")
-@require_oauth("profile group resource")
-def view_resource(resource_id: uuid.UUID) -> Response:
-    """View a particular resource's details."""
-    with require_oauth.acquire("profile group resource") as the_token:
-        db_uri = app.config["AUTH_DB"]
-        with db.connection(db_uri) as conn:
-            return jsonify(dictify(resource_by_id(
-                conn, the_token.user, resource_id)))
-
-def __safe_get_requests_page__(key: str = "page") -> int:
-    """Get the results page if it exists or default to the first page."""
-    try:
-        return abs(int(request.args.get(key, "1"), base=10))
-    except ValueError as _valerr:
-        return 1
-
-def __safe_get_requests_count__(key: str = "count_per_page") -> int:
-    """Get the results page if it exists or default to the first page."""
-    try:
-        count = request.args.get(key, "0")
-        if count != 0:
-            return abs(int(count, base=10))
-        return 0
-    except ValueError as _valerr:
-        return 0
-
-@resources.route("/view/<uuid:resource_id>/data")
-@require_oauth("profile group resource")
-def view_resource_data(resource_id: uuid.UUID) -> Response:
-    """Retrieve a particular resource's data."""
-    with require_oauth.acquire("profile group resource") as the_token:
-        db_uri = app.config["AUTH_DB"]
-        count_per_page = __safe_get_requests_count__("count_per_page")
-        offset = (__safe_get_requests_page__("page") - 1)
-        with db.connection(db_uri) as conn:
-            resource = resource_by_id(conn, the_token.user, resource_id)
-            return jsonify(resource_data(
-                conn,
-                resource,
-                ((offset * count_per_page) if bool(count_per_page) else offset),
-                count_per_page))
-
-@resources.route("/data/link", methods=["POST"])
-@require_oauth("profile group resource")
-def link_data():
-    """Link group data to a specific resource."""
-    try:
-        form = request.form
-        assert "resource_id" in form, "Resource ID not provided."
-        assert "data_link_id" in form, "Data Link ID not provided."
-        assert "dataset_type" in form, "Dataset type not specified"
-        assert form["dataset_type"].lower() in (
-            "mrna", "genotype", "phenotype"), "Invalid dataset type provided."
-
-        with require_oauth.acquire("profile group resource") as the_token:
-            def __link__(conn: db.DbConnection):
-                return link_data_to_resource(
-                    conn, the_token.user, uuid.UUID(form["resource_id"]),
-                    form["dataset_type"], uuid.UUID(form["data_link_id"]))
-
-            return jsonify(with_db_connection(__link__))
-    except AssertionError as aserr:
-        raise InvalidData(aserr.args[0]) from aserr
-
-
-
-@resources.route("/data/unlink", methods=["POST"])
-@require_oauth("profile group resource")
-def unlink_data():
-    """Unlink data bound to a specific resource."""
-    try:
-        form = request.form
-        assert "resource_id" in form, "Resource ID not provided."
-        assert "data_link_id" in form, "Data Link ID not provided."
-
-        with require_oauth.acquire("profile group resource") as the_token:
-            def __unlink__(conn: db.DbConnection):
-                return unlink_data_from_resource(
-                    conn, the_token.user, uuid.UUID(form["resource_id"]),
-                    uuid.UUID(form["data_link_id"]))
-            return jsonify(with_db_connection(__unlink__))
-    except AssertionError as aserr:
-        raise InvalidData(aserr.args[0]) from aserr
-
-@resources.route("<uuid:resource_id>/user/list", methods=["GET"])
-@require_oauth("profile group resource")
-def resource_users(resource_id: uuid.UUID):
-    """Retrieve all users with access to the given resource."""
-    with require_oauth.acquire("profile group resource") as the_token:
-        def __the_users__(conn: db.DbConnection):
-            resource = resource_by_id(conn, the_token.user, resource_id)
-            authorised = authorised_for(
-                conn, the_token.user, ("group:resource:edit-resource",),
-                (resource_id,))
-            if authorised.get(resource_id, False):
-                with db.cursor(conn) as cursor:
-                    def __organise_users_n_roles__(users_n_roles, row):
-                        user_id = uuid.UUID(row["user_id"])
-                        user = users_n_roles.get(user_id, {}).get(
-                            "user", User(user_id, row["email"], row["name"]))
-                        role = GroupRole(
-                            uuid.UUID(row["group_role_id"]),
-                            resource.group,
-                            Role(uuid.UUID(row["role_id"]), row["role_name"],
-                                 bool(int(row["user_editable"])), tuple()))
-                        return {
-                            **users_n_roles,
-                            user_id: {
-                                "user": user,
-                                "user_group": Group(
-                                    uuid.UUID(row["group_id"]), row["group_name"],
-                                    json.loads(row["group_metadata"])),
-                                "roles": users_n_roles.get(
-                                    user_id, {}).get("roles", tuple()) + (role,)
-                            }
-                        }
-                    cursor.execute(
-                        "SELECT g.*, u.*, r.*, gr.group_role_id "
-                        "FROM groups AS g INNER JOIN "
-                        "group_users AS gu ON g.group_id=gu.group_id "
-                        "INNER JOIN users AS u ON gu.user_id=u.user_id "
-                        "INNER JOIN group_user_roles_on_resources AS guror "
-                        "ON u.user_id=guror.user_id INNER JOIN roles AS r "
-                        "ON guror.role_id=r.role_id "
-                        "INNER JOIN group_roles AS gr ON r.role_id=gr.role_id "
-                        "WHERE guror.resource_id=?",
-                        (str(resource_id),))
-                    return reduce(__organise_users_n_roles__, cursor.fetchall(), {})
-            raise AuthorisationError(
-                "You do not have sufficient privileges to view the resource "
-                "users.")
-        results = (
-            {
-                "user": dictify(row["user"]),
-                "user_group": dictify(row["user_group"]),
-                "roles": tuple(dictify(role) for role in row["roles"])
-            } for row in (
-                user_row for user_id, user_row
-                in with_db_connection(__the_users__).items()))
-        return jsonify(tuple(results))
-
-@resources.route("<uuid:resource_id>/user/assign", methods=["POST"])
-@require_oauth("profile group resource role")
-def assign_role_to_user(resource_id: uuid.UUID) -> Response:
-    """Assign a role on the specified resource to a user."""
-    with require_oauth.acquire("profile group resource role") as the_token:
-        try:
-            form = request.form
-            group_role_id = form.get("group_role_id", "")
-            user_email = form.get("user_email", "")
-            assert bool(group_role_id), "The role must be provided."
-            assert bool(user_email), "The user email must be provided."
-
-            def __assign__(conn: db.DbConnection) -> dict:
-                resource = resource_by_id(conn, the_token.user, resource_id)
-                user = user_by_email(conn, user_email)
-                return assign_resource_user(
-                    conn, resource, user,
-                    group_role_by_id(conn, resource.group,
-                                     uuid.UUID(group_role_id)))
-        except AssertionError as aserr:
-            raise AuthorisationError(aserr.args[0]) from aserr
-
-        return jsonify(with_db_connection(__assign__))
-
-@resources.route("<uuid:resource_id>/user/unassign", methods=["POST"])
-@require_oauth("profile group resource role")
-def unassign_role_to_user(resource_id: uuid.UUID) -> Response:
-    """Unassign a role on the specified resource from a user."""
-    with require_oauth.acquire("profile group resource role") as the_token:
-        try:
-            form = request.form
-            group_role_id = form.get("group_role_id", "")
-            user_id = form.get("user_id", "")
-            assert bool(group_role_id), "The role must be provided."
-            assert bool(user_id), "The user id must be provided."
-
-            def __assign__(conn: db.DbConnection) -> dict:
-                resource = resource_by_id(conn, the_token.user, resource_id)
-                return unassign_resource_user(
-                    conn, resource, user_by_id(conn, uuid.UUID(user_id)),
-                    group_role_by_id(conn, resource.group,
-                                     uuid.UUID(group_role_id)))
-        except AssertionError as aserr:
-            raise AuthorisationError(aserr.args[0]) from aserr
-
-        return jsonify(with_db_connection(__assign__))
-
-@resources.route("<uuid:resource_id>/toggle-public", methods=["POST"])
-@require_oauth("profile group resource role")
-def toggle_public(resource_id: uuid.UUID) -> Response:
-    """Make a resource public if it is private, or private if public."""
-    with require_oauth.acquire("profile group resource") as the_token:
-        def __toggle__(conn: db.DbConnection) -> Resource:
-            old_rsc = resource_by_id(conn, the_token.user, resource_id)
-            return save_resource(
-                conn, the_token.user, Resource(
-                    old_rsc.group, old_rsc.resource_id, old_rsc.resource_name,
-                    old_rsc.resource_category, not old_rsc.public,
-                    old_rsc.resource_data))
-
-        resource = with_db_connection(__toggle__)
-        return jsonify({
-            "resource": dictify(resource),
-            "description": (
-                "Made resource public" if resource.public
-                else "Made resource private")})
diff --git a/gn3/auth/authorisation/roles/__init__.py b/gn3/auth/authorisation/roles/__init__.py
deleted file mode 100644
index 293a12f..0000000
--- a/gn3/auth/authorisation/roles/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-"""Initialise the `gn3.auth.authorisation.roles` package"""
-
-from .models import Role
diff --git a/gn3/auth/authorisation/roles/models.py b/gn3/auth/authorisation/roles/models.py
deleted file mode 100644
index 890d33b..0000000
--- a/gn3/auth/authorisation/roles/models.py
+++ /dev/null
@@ -1,161 +0,0 @@
-"""Handle management of roles"""
-from uuid import UUID, uuid4
-from functools import reduce
-from typing import Any, Sequence, Iterable, NamedTuple
-
-from pymonad.either import Left, Right, Either
-
-from gn3.auth import db
-from gn3.auth.dictify import dictify
-from gn3.auth.authorisation.users import User
-from gn3.auth.authorisation.errors import AuthorisationError
-
-from ..checks import authorised_p
-from ..privileges import Privilege
-from ..errors import NotFoundError
-
-class Role(NamedTuple):
-    """Class representing a role: creates immutable objects."""
-    role_id: UUID
-    role_name: str
-    user_editable: bool
-    privileges: tuple[Privilege, ...]
-
-    def dictify(self) -> dict[str, Any]:
-        """Return a dict representation of `Role` objects."""
-        return {
-            "role_id": self.role_id, "role_name": self.role_name,
-            "user_editable": self.user_editable,
-            "privileges": tuple(dictify(priv) for priv in self.privileges)
-        }
-
-def check_user_editable(role: Role):
-    """Raise an exception if `role` is not user editable."""
-    if not role.user_editable:
-        raise AuthorisationError(
-            f"The role `{role.role_name}` is not user editable.")
-
-@authorised_p(
-    privileges = ("group:role:create-role",),
-    error_description="Could not create role")
-def create_role(
-        cursor: db.DbCursor, role_name: str,
-        privileges: Iterable[Privilege]) -> Role:
-    """
-    Create a new generic role.
-
-    PARAMS:
-    * cursor: A database cursor object - This function could be used as part of
-              a transaction, hence the use of a cursor rather than a connection
-              object.
-    * role_name: The name of the role
-    * privileges: A 'list' of privileges to assign the new role
-
-    RETURNS: An immutable `gn3.auth.authorisation.roles.Role` object
-    """
-    role = Role(uuid4(), role_name, True, tuple(privileges))
-
-    cursor.execute(
-        "INSERT INTO roles(role_id, role_name, user_editable) VALUES (?, ?, ?)",
-        (str(role.role_id), role.role_name, (1 if role.user_editable else 0)))
-    cursor.executemany(
-        "INSERT INTO role_privileges(role_id, privilege_id) VALUES (?, ?)",
-        tuple((str(role.role_id), str(priv.privilege_id))
-              for priv in privileges))
-
-    return role
-
-def __organise_privileges__(roles_dict, privilege_row):
-    """Organise the privileges into their roles."""
-    role_id_str = privilege_row["role_id"]
-    if  role_id_str in roles_dict:
-        return {
-            **roles_dict,
-            role_id_str: Role(
-                UUID(role_id_str),
-                privilege_row["role_name"],
-                bool(int(privilege_row["user_editable"])),
-                roles_dict[role_id_str].privileges + (
-                    Privilege(privilege_row["privilege_id"],
-                              privilege_row["privilege_description"]),))
-        }
-
-    return {
-        **roles_dict,
-        role_id_str: Role(
-            UUID(role_id_str),
-            privilege_row["role_name"],
-            bool(int(privilege_row["user_editable"])),
-            (Privilege(privilege_row["privilege_id"],
-                       privilege_row["privilege_description"]),))
-    }
-
-def user_roles(conn: db.DbConnection, user: User) -> Sequence[Role]:
-    """Retrieve non-resource roles assigned to the user."""
-    with db.cursor(conn) as cursor:
-        cursor.execute(
-            "SELECT r.*, p.* FROM user_roles AS ur INNER JOIN roles AS r "
-            "ON ur.role_id=r.role_id INNER JOIN role_privileges AS rp "
-            "ON r.role_id=rp.role_id INNER JOIN privileges AS p "
-            "ON rp.privilege_id=p.privilege_id WHERE ur.user_id=?",
-            (str(user.user_id),))
-
-        return tuple(
-            reduce(__organise_privileges__, cursor.fetchall(), {}).values())
-    return tuple()
-
-def user_role(conn: db.DbConnection, user: User, role_id: UUID) -> Either:
-    """Retrieve a specific non-resource role assigned to the user."""
-    with db.cursor(conn) as cursor:
-        cursor.execute(
-            "SELECT r.*, p.* FROM user_roles AS ur INNER JOIN roles AS r "
-            "ON ur.role_id=r.role_id INNER JOIN role_privileges AS rp "
-            "ON r.role_id=rp.role_id INNER JOIN privileges AS p "
-            "ON rp.privilege_id=p.privilege_id "
-            "WHERE ur.user_id=? AND ur.role_id=?",
-            (str(user.user_id), str(role_id)))
-
-        results = cursor.fetchall()
-        if results:
-            return Right(tuple(
-                reduce(__organise_privileges__, results, {}).values())[0])
-        return Left(NotFoundError(
-            f"Could not find role with id '{role_id}'",))
-
-def assign_default_roles(cursor: db.DbCursor, user: User):
-    """Assign `user` some default roles."""
-    cursor.execute(
-        'SELECT role_id FROM roles WHERE role_name IN '
-        '("group-creator")')
-    role_ids = cursor.fetchall()
-    str_user_id = str(user.user_id)
-    params = tuple(
-        {"user_id": str_user_id, "role_id": row["role_id"]} for row in role_ids)
-    cursor.executemany(
-        ("INSERT INTO user_roles VALUES (:user_id, :role_id)"),
-        params)
-
-def revoke_user_role_by_name(cursor: db.DbCursor, user: User, role_name: str):
-    """Revoke a role from `user` by the role's name"""
-    cursor.execute(
-        "SELECT role_id FROM roles WHERE role_name=:role_name",
-        {"role_name": role_name})
-    role = cursor.fetchone()
-    if role:
-        cursor.execute(
-            ("DELETE FROM user_roles "
-             "WHERE user_id=:user_id AND role_id=:role_id"),
-            {"user_id": str(user.user_id), "role_id": role["role_id"]})
-
-def assign_user_role_by_name(cursor: db.DbCursor, user: User, role_name: str):
-    """Revoke a role from `user` by the role's name"""
-    cursor.execute(
-        "SELECT role_id FROM roles WHERE role_name=:role_name",
-        {"role_name": role_name})
-    role = cursor.fetchone()
-
-    if role:
-        cursor.execute(
-            ("INSERT INTO user_roles VALUES(:user_id, :role_id) "
-             "ON CONFLICT DO NOTHING"),
-            {"user_id": str(user.user_id), "role_id": role["role_id"]})
diff --git a/gn3/auth/authorisation/roles/views.py b/gn3/auth/authorisation/roles/views.py
deleted file mode 100644
index d00e596..0000000
--- a/gn3/auth/authorisation/roles/views.py
+++ /dev/null
@@ -1,25 +0,0 @@
-"""The views/routes for the `gn3.auth.authorisation.roles` package."""
-import uuid
-
-from flask import jsonify, Response, Blueprint, current_app
-
-from gn3.auth import db
-from gn3.auth.dictify import dictify
-from gn3.auth.authorisation.oauth2.resource_server import require_oauth
-
-from .models import user_role
-
-roles = Blueprint("roles", __name__)
-
-@roles.route("/view/<uuid:role_id>", methods=["GET"])
-@require_oauth("profile role")
-def view_role(role_id: uuid.UUID) -> Response:
-    """Retrieve a user role with id `role_id`"""
-    def __error__(exc: Exception):
-        raise exc
-    with require_oauth.acquire("profile role") as the_token:
-        db_uri = current_app.config["AUTH_DB"]
-        with db.connection(db_uri) as conn:
-            the_role = user_role(conn, the_token.user, role_id)
-            return the_role.either(
-                __error__, lambda a_role: jsonify(dictify(a_role)))
diff --git a/gn3/auth/authorisation/users/__init__.py b/gn3/auth/authorisation/users/__init__.py
deleted file mode 100644
index 5f0c89c..0000000
--- a/gn3/auth/authorisation/users/__init__.py
+++ /dev/null
@@ -1,12 +0,0 @@
-"""Initialise the users' package."""
-from .base import (
-    User,
-    users,
-    save_user,
-    user_by_id,
-    # valid_login,
-    user_by_email,
-    hash_password, # only used in tests... maybe make gn-auth a GN3 dependency
-    same_password,
-    set_user_password
-)
diff --git a/gn3/auth/authorisation/users/base.py b/gn3/auth/authorisation/users/base.py
deleted file mode 100644
index 0e72ed2..0000000
--- a/gn3/auth/authorisation/users/base.py
+++ /dev/null
@@ -1,128 +0,0 @@
-"""User-specific code and data structures."""
-from uuid import UUID, uuid4
-from typing import Any, Tuple, NamedTuple
-
-from argon2 import PasswordHasher
-from argon2.exceptions import VerifyMismatchError
-
-from gn3.auth import db
-from gn3.auth.authorisation.errors import NotFoundError
-
-class User(NamedTuple):
-    """Class representing a user."""
-    user_id: UUID
-    email: str
-    name: str
-
-    def get_user_id(self):
-        """Return the user's UUID. Mostly for use with Authlib."""
-        return self.user_id
-
-    def dictify(self) -> dict[str, Any]:
-        """Return a dict representation of `User` objects."""
-        return {"user_id": self.user_id, "email": self.email, "name": self.name}
-
-DUMMY_USER = User(user_id=UUID("a391cf60-e8b7-4294-bd22-ddbbda4b3530"),
-                  email="gn3@dummy.user",
-                  name="Dummy user to use as placeholder")
-
-def user_by_email(conn: db.DbConnection, email: str) -> User:
-    """Retrieve user from database by their email address"""
-    with db.cursor(conn) as cursor:
-        cursor.execute("SELECT * FROM users WHERE email=?", (email,))
-        row = cursor.fetchone()
-
-    if row:
-        return User(UUID(row["user_id"]), row["email"], row["name"])
-
-    raise NotFoundError(f"Could not find user with email {email}")
-
-def user_by_id(conn: db.DbConnection, user_id: UUID) -> User:
-    """Retrieve user from database by their user id"""
-    with db.cursor(conn) as cursor:
-        cursor.execute("SELECT * FROM users WHERE user_id=?", (str(user_id),))
-        row = cursor.fetchone()
-
-    if row:
-        return User(UUID(row["user_id"]), row["email"], row["name"])
-
-    raise NotFoundError(f"Could not find user with ID {user_id}")
-
-def same_password(password: str, hashed: str) -> bool:
-    """Check that `raw_password` is hashed to `hash`"""
-    try:
-        return hasher().verify(hashed, password)
-    except VerifyMismatchError as _vme:
-        return False
-
-def valid_login(conn: db.DbConnection, user: User, password: str) -> bool:
-    """Check the validity of the provided credentials for login."""
-    with db.cursor(conn) as cursor:
-        cursor.execute(
-            ("SELECT * FROM users LEFT JOIN user_credentials "
-             "ON users.user_id=user_credentials.user_id "
-             "WHERE users.user_id=?"),
-            (str(user.user_id),))
-        row = cursor.fetchone()
-
-    if row is None:
-        return False
-
-    return same_password(password, row["password"])
-
-def save_user(cursor: db.DbCursor, email: str, name: str) -> User:
-    """
-    Create and persist a user.
-
-    The user creation could be done during a transaction, therefore the function
-    takes a cursor object rather than a connection.
-
-    The newly created and persisted user is then returned.
-    """
-    user_id = uuid4()
-    cursor.execute("INSERT INTO users VALUES (?, ?, ?)",
-                   (str(user_id), email, name))
-    return User(user_id, email, name)
-
-def hasher():
-    """Retrieve PasswordHasher object"""
-    # TODO: Maybe tune the parameters here...
-    # Tuneable Parameters:
-    # - time_cost (default: 2)
-    # - memory_cost (default: 102400)
-    # - parallelism (default: 8)
-    # - hash_len (default: 16)
-    # - salt_len (default: 16)
-    # - encoding (default: 'utf-8')
-    # - type (default: <Type.ID: 2>)
-    return PasswordHasher()
-
-def hash_password(password):
-    """Hash the password."""
-    return hasher().hash(password)
-
-def set_user_password(
-        cursor: db.DbCursor, user: User, password: str) -> Tuple[User, bytes]:
-    """Set the given user's password in the database."""
-    hashed_password = hash_password(password)
-    cursor.execute(
-        ("INSERT INTO user_credentials VALUES (:user_id, :hash) "
-         "ON CONFLICT (user_id) DO UPDATE SET password=:hash"),
-        {"user_id": str(user.user_id), "hash": hashed_password})
-    return user, hashed_password
-
-def users(conn: db.DbConnection,
-          ids: tuple[UUID, ...] = tuple()) -> tuple[User, ...]:
-    """
-    Fetch all users with the given `ids`. If `ids` is empty, return ALL users.
-    """
-    params = ", ".join(["?"] * len(ids))
-    with db.cursor(conn) as cursor:
-        query = "SELECT * FROM users" + (
-            f" WHERE user_id IN ({params})"
-            if len(ids) > 0 else "")
-        print(query)
-        cursor.execute(query, tuple(str(the_id) for the_id in ids))
-        return tuple(User(UUID(row["user_id"]), row["email"], row["name"])
-                     for row in cursor.fetchall())
-    return tuple()
diff --git a/gn3/auth/authorisation/users/collections/__init__.py b/gn3/auth/authorisation/users/collections/__init__.py
deleted file mode 100644
index 88ab040..0000000
--- a/gn3/auth/authorisation/users/collections/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-"""Package dealing with user collections."""
diff --git a/gn3/auth/authorisation/users/collections/models.py b/gn3/auth/authorisation/users/collections/models.py
deleted file mode 100644
index 7577fa8..0000000
--- a/gn3/auth/authorisation/users/collections/models.py
+++ /dev/null
@@ -1,269 +0,0 @@
-"""Handle user collections."""
-import json
-from uuid import UUID, uuid4
-from datetime import datetime
-
-from redis import Redis
-from email_validator import validate_email, EmailNotValidError
-
-from gn3.auth.authorisation.errors import InvalidData, NotFoundError
-
-from ..models import User
-
-__OLD_REDIS_COLLECTIONS_KEY__ = "collections"
-__REDIS_COLLECTIONS_KEY__ = "collections2"
-
-class CollectionJSONEncoder(json.JSONEncoder):
-    """Serialise collection objects into JSON."""
-    def default(self, obj):# pylint: disable=[arguments-renamed]
-        if isinstance(obj, UUID):
-            return str(obj)
-        if isinstance(obj, datetime):
-            return obj.strftime("%b %d %Y %I:%M%p")
-        return json.JSONEncoder.default(self, obj)
-
-def __valid_email__(email:str) -> bool:
-    """Check for email validity."""
-    try:
-        validate_email(email, check_deliverability=True)
-    except EmailNotValidError as _enve:
-        return False
-    return True
-
-def __toggle_boolean_field__(
-        rconn: Redis, email: str, field: str):
-    """Toggle the valuen of a boolean field"""
-    mig_dict = json.loads(rconn.hget("migratable-accounts", email) or "{}")
-    if bool(mig_dict):
-        rconn.hset("migratable-accounts", email,
-                   json.dumps({**mig_dict, field: not mig_dict.get(field, True)}))
-
-def __build_email_uuid_bridge__(rconn: Redis):
-    """
-    Build a connection between new accounts and old user accounts.
-
-    The only thing that is common between the two is the email address,
-    therefore, we use that to link the two items.
-    """
-    old_accounts = {
-        account["email_address"]: {
-            "user_id": account["user_id"],
-            "collections-migrated": False,
-            "resources_migrated": False
-        } for account in (
-            acct for acct in
-            (json.loads(usr) for usr in rconn.hgetall("users").values())
-            if (bool(acct.get("email_address", False)) and
-                __valid_email__(acct["email_address"])))
-    }
-    if bool(old_accounts):
-        rconn.hset("migratable-accounts", mapping={
-            key: json.dumps(value) for key,value in old_accounts.items()
-        })
-    return old_accounts
-
-def __retrieve_old_accounts__(rconn: Redis) -> dict:
-    accounts = rconn.hgetall("migratable-accounts")
-    if accounts:
-        return {
-            key: json.loads(value) for key, value in accounts.items()
-        }
-    return __build_email_uuid_bridge__(rconn)
-
-def parse_collection(coll: dict) -> dict:
-    """Parse the collection as persisted in redis to a usable python object."""
-    created = coll.get("created", coll.get("created_timestamp"))
-    changed = coll.get("changed", coll.get("changed_timestamp"))
-    return {
-        "id": UUID(coll["id"]),
-        "name": coll["name"],
-        "created": datetime.strptime(created, "%b %d %Y %I:%M%p"),
-        "changed": datetime.strptime(changed, "%b %d %Y %I:%M%p"),
-        "num_members": int(coll["num_members"]),
-        "members": coll["members"]
-    }
-
-def dump_collection(pythoncollection: dict) -> str:
-    """Convert the collection from a python object to a json string."""
-    return json.dumps(pythoncollection, cls=CollectionJSONEncoder)
-
-def __retrieve_old_user_collections__(rconn: Redis, old_user_id: UUID) -> tuple:
-    """Retrieve any old collections relating to the user."""
-    return tuple(parse_collection(coll) for coll in
-                 json.loads(rconn.hget(
-                     __OLD_REDIS_COLLECTIONS_KEY__, str(old_user_id)) or "[]"))
-
-def user_collections(rconn: Redis, user: User) -> tuple[dict, ...]:
-    """Retrieve current user collections."""
-    collections = tuple(parse_collection(coll) for coll in json.loads(
-        rconn.hget(__REDIS_COLLECTIONS_KEY__, str(user.user_id)) or
-        "[]"))
-    old_accounts = __retrieve_old_accounts__(rconn)
-    if (user.email in old_accounts and
-        not old_accounts[user.email]["collections-migrated"]):
-        old_user_id = old_accounts[user.email]["user_id"]
-        collections = tuple({
-            coll["id"]: coll for coll in (
-                collections + __retrieve_old_user_collections__(
-                    rconn, UUID(old_user_id)))
-        }.values())
-        __toggle_boolean_field__(rconn, user.email, "collections-migrated")
-        rconn.hset(
-            __REDIS_COLLECTIONS_KEY__,
-            key=str(user.user_id),
-            value=json.dumps(collections, cls=CollectionJSONEncoder))
-    return collections
-
-def save_collections(rconn: Redis, user: User, collections: tuple[dict, ...]) -> tuple[dict, ...]:
-    """Save the `collections` to redis."""
-    rconn.hset(
-        __REDIS_COLLECTIONS_KEY__,
-        str(user.user_id),
-        json.dumps(collections, cls=CollectionJSONEncoder))
-    return collections
-
-def add_to_user_collections(rconn: Redis, user: User, collection: dict) -> dict:
-    """Add `collection` to list of user collections."""
-    ucolls = user_collections(rconn, user)
-    save_collections(rconn, user, ucolls + (collection,))
-    return collection
-
-def create_collection(rconn: Redis, user: User, name: str, traits: tuple) -> dict:
-    """Create a new collection."""
-    now = datetime.utcnow()
-    return add_to_user_collections(rconn, user, {
-        "id": uuid4(),
-        "name": name,
-        "created": now,
-        "changed": now,
-        "num_members": len(traits),
-        "members": traits
-    })
-
-def get_collection(rconn: Redis, user: User, collection_id: UUID) -> dict:
-    """Retrieve the collection with ID `collection_id`."""
-    colls = tuple(coll for coll in user_collections(rconn, user)
-                  if coll["id"] == collection_id)
-    if len(colls) == 0:
-        raise NotFoundError(
-            f"Could not find a collection with ID `{collection_id}` for user "
-            f"with ID `{user.user_id}`")
-    if len(colls) > 1:
-        err = InvalidData(
-            "More than one collection was found having the ID "
-            f"`{collection_id}` for user with ID `{user.user_id}`.")
-        err.error_code = 513
-        raise err
-    return colls[0]
-
-def __raise_if_collections_empty__(user: User, collections: tuple[dict, ...]):
-    """Raise an exception if no collections are found for `user`."""
-    if len(collections) < 1:
-        raise NotFoundError(f"No collections found for user `{user.user_id}`")
-
-def __raise_if_not_single_collection__(
-        user: User, collection_id: UUID, collections: tuple[dict, ...]):
-    """
-    Raise an exception there is zero, or more than one collection for `user`.
-    """
-    if len(collections) == 0:
-        raise NotFoundError(f"No collections found for user `{user.user_id}` "
-                            f"with ID `{collection_id}`.")
-    if len(collections) > 1:
-        err = InvalidData(
-            "More than one collection was found having the ID "
-            f"`{collection_id}` for user with ID `{user.user_id}`.")
-        err.error_code = 513
-        raise err
-
-def delete_collections(rconn: Redis,
-                       user: User,
-                       collection_ids: tuple[UUID, ...]) -> tuple[dict, ...]:
-    """
-    Delete collections with the given `collection_ids` returning the deleted
-    collections.
-    """
-    ucolls = user_collections(rconn, user)
-    save_collections(
-        rconn,
-        user,
-        tuple(coll for coll in ucolls if coll["id"] not in collection_ids))
-    return tuple(coll for coll in ucolls if coll["id"] in collection_ids)
-
-def add_traits(rconn: Redis,
-               user: User,
-               collection_id: UUID,
-               traits: tuple[str, ...]) -> dict:
-    """
-    Add `traits` to the `user` collection identified by `collection_id`.
-
-    Returns: The collection with the new traits added.
-    """
-    ucolls = user_collections(rconn, user)
-    __raise_if_collections_empty__(user, ucolls)
-
-    mod_col = tuple(coll for coll in ucolls if coll["id"] == collection_id)
-    __raise_if_not_single_collection__(user, collection_id, mod_col)
-    new_members = tuple(set(tuple(mod_col[0]["members"]) + traits))
-    new_coll = {
-        **mod_col[0],
-        "members": new_members,
-        "num_members": len(new_members)
-    }
-    save_collections(
-        rconn,
-        user,
-        (tuple(coll for coll in ucolls if coll["id"] != collection_id) +
-         (new_coll,)))
-    return new_coll
-
-def remove_traits(rconn: Redis,
-                  user: User,
-                  collection_id: UUID,
-                  traits: tuple[str, ...]) -> dict:
-    """
-    Remove `traits` from the `user` collection identified by `collection_id`.
-
-    Returns: The collection with the specified `traits` removed.
-    """
-    ucolls = user_collections(rconn, user)
-    __raise_if_collections_empty__(user, ucolls)
-
-    mod_col = tuple(coll for coll in ucolls if coll["id"] == collection_id)
-    __raise_if_not_single_collection__(user, collection_id, mod_col)
-    new_members = tuple(
-        trait for trait in mod_col[0]["members"] if trait not in traits)
-    new_coll = {
-        **mod_col[0],
-        "members": new_members,
-        "num_members": len(new_members)
-    }
-    save_collections(
-        rconn,
-        user,
-        (tuple(coll for coll in ucolls if coll["id"] != collection_id) +
-         (new_coll,)))
-    return new_coll
-
-def change_name(rconn: Redis,
-                user: User,
-                collection_id: UUID,
-                new_name: str) -> dict:
-    """
-    Change the collection's name.
-
-    Returns: The collection with the new name.
-    """
-    ucolls = user_collections(rconn, user)
-    __raise_if_collections_empty__(user, ucolls)
-
-    mod_col = tuple(coll for coll in ucolls if coll["id"] == collection_id)
-    __raise_if_not_single_collection__(user, collection_id, mod_col)
-
-    new_coll = {**mod_col[0], "name": new_name}
-    save_collections(
-        rconn,
-        user,
-        (tuple(coll for coll in ucolls if coll["id"] != collection_id) +
-         (new_coll,)))
-    return new_coll
diff --git a/gn3/auth/authorisation/users/collections/views.py b/gn3/auth/authorisation/users/collections/views.py
deleted file mode 100644
index 775e8bc..0000000
--- a/gn3/auth/authorisation/users/collections/views.py
+++ /dev/null
@@ -1,239 +0,0 @@
-"""Views regarding user collections."""
-from uuid import UUID
-
-from redis import Redis
-from flask import jsonify, request, Response, Blueprint, current_app
-
-from gn3.auth import db
-from gn3.auth.db_utils import with_db_connection
-from gn3.auth.authorisation.checks import require_json
-from gn3.auth.authorisation.errors import NotFoundError
-
-from gn3.auth.authorisation.users import User, user_by_id
-from gn3.auth.authorisation.oauth2.resource_server import require_oauth
-
-from .models import (
-    add_traits,
-    change_name,
-    remove_traits,
-    get_collection,
-    user_collections,
-    save_collections,
-    create_collection,
-    delete_collections as _delete_collections)
-
-collections = Blueprint("collections", __name__)
-
-@collections.route("/list")
-@require_oauth("profile user")
-def list_user_collections() -> Response:
-    """Retrieve the user ids"""
-    with (require_oauth.acquire("profile user") as the_token,
-          Redis.from_url(current_app.config["REDIS_URI"],
-                         decode_responses=True) as redisconn):
-        return jsonify(user_collections(redisconn, the_token.user))
-
-@collections.route("/<uuid:anon_id>/list")
-def list_anonymous_collections(anon_id: UUID) -> Response:
-    """Fetch anonymous collections"""
-    with Redis.from_url(
-            current_app.config["REDIS_URI"], decode_responses=True) as redisconn:
-        def __list__(conn: db.DbConnection) -> tuple:
-            try:
-                _user = user_by_id(conn, anon_id)
-                current_app.logger.warning(
-                    "Fetch collections for authenticated user using the "
-                    "`list_user_collections()` endpoint.")
-                return tuple()
-            except NotFoundError as _nfe:
-                return user_collections(
-                    redisconn, User(anon_id, "anon@ymous.user", "Anonymous User"))
-
-        return jsonify(with_db_connection(__list__))
-
-@require_oauth("profile user")
-def __new_collection_as_authenticated_user__(redisconn, name, traits):
-    """Create a new collection as an authenticated user."""
-    with require_oauth.acquire("profile user") as token:
-        return create_collection(redisconn, token.user, name, traits)
-
-def __new_collection_as_anonymous_user__(redisconn, name, traits):
-    """Create a new collection as an anonymous user."""
-    return create_collection(redisconn,
-                             User(UUID(request.json.get("anon_id")),
-                                  "anon@ymous.user",
-                                  "Anonymous User"),
-                             name,
-                             traits)
-
-@collections.route("/new", methods=["POST"])
-@require_json
-def new_user_collection() -> Response:
-    """Create a new collection."""
-    with (Redis.from_url(current_app.config["REDIS_URI"],
-                         decode_responses=True) as redisconn):
-        traits = tuple(request.json.get("traits", tuple()))# type: ignore[union-attr]
-        name = request.json.get("name")# type: ignore[union-attr]
-        if bool(request.headers.get("Authorization")):
-            return jsonify(__new_collection_as_authenticated_user__(
-                redisconn, name, traits))
-        return jsonify(__new_collection_as_anonymous_user__(
-            redisconn, name, traits))
-
-@collections.route("/<uuid:collection_id>/view", methods=["POST"])
-@require_json
-def view_collection(collection_id: UUID) -> Response:
-    """View a particular collection"""
-    with (Redis.from_url(current_app.config["REDIS_URI"],
-                         decode_responses=True) as redisconn):
-        if bool(request.headers.get("Authorization")):
-            with require_oauth.acquire("profile user") as token:
-                return jsonify(get_collection(redisconn,
-                                              token.user,
-                                              collection_id))
-        return jsonify(get_collection(
-            redisconn,
-            User(
-                UUID(request.json.get("anon_id")),#type: ignore[union-attr]
-                "anon@ymous.user",
-                "Anonymous User"),
-            collection_id))
-
-@collections.route("/anonymous/import", methods=["POST"])
-@require_json
-@require_oauth("profile user")
-def import_anonymous() -> Response:
-    """Import anonymous collections."""
-    with (require_oauth.acquire("profile user") as token,
-          Redis.from_url(current_app.config["REDIS_URI"],
-                         decode_responses=True) as redisconn):
-        anon_id = UUID(request.json.get("anon_id"))#type: ignore[union-attr]
-        anon_colls = user_collections(redisconn, User(
-            anon_id, "anon@ymous.user", "Anonymous User"))
-        save_collections(
-            redisconn,
-            token.user,
-            (user_collections(redisconn, token.user) +
-             anon_colls))
-        redisconn.hdel("collections", str(anon_id))
-        return jsonify({
-            "message": f"Import of {len(anon_colls)} was successful."
-        })
-
-@collections.route("/anonymous/delete", methods=["POST"])
-@require_json
-@require_oauth("profile user")
-def delete_anonymous() -> Response:
-    """Delete anonymous collections."""
-    with (require_oauth.acquire("profile user") as _token,
-          Redis.from_url(current_app.config["REDIS_URI"],
-                         decode_responses=True) as redisconn):
-        anon_id = UUID(request.json.get("anon_id"))#type: ignore[union-attr]
-        anon_colls = user_collections(redisconn, User(
-            anon_id, "anon@ymous.user", "Anonymous User"))
-        redisconn.hdel("collections", str(anon_id))
-        return jsonify({
-            "message": f"Deletion of {len(anon_colls)} was successful."
-        })
-
-@collections.route("/delete", methods=["POST"])
-@require_json
-def delete_collections():
-    """Delete specified collections."""
-    with (Redis.from_url(current_app.config["REDIS_URI"],
-                         decode_responses=True) as redisconn):
-        coll_ids = tuple(UUID(cid) for cid in request.json["collection_ids"])
-        deleted = _delete_collections(
-            redisconn,
-            User(request.json["anon_id"], "anon@ymous.user", "Anonymous User"),
-            coll_ids)
-        if bool(request.headers.get("Authorization")):
-            with require_oauth.acquire("profile user") as token:
-                deleted = deleted + _delete_collections(
-                    redisconn, token.user, coll_ids)
-
-        return jsonify({
-            "message": f"Deleted {len(deleted)} collections."})
-
-@collections.route("/<uuid:collection_id>/traits/remove", methods=["POST"])
-@require_json
-def remove_traits_from_collection(collection_id: UUID) -> Response:
-    """Remove specified traits from collection with ID `collection_id`."""
-    if len(request.json["traits"]) < 1:#type: ignore[index]
-        return jsonify({"message": "No trait to remove from collection."})
-
-    the_traits = tuple(request.json["traits"])#type: ignore[index]
-    with (Redis.from_url(current_app.config["REDIS_URI"],
-                         decode_responses=True) as redisconn):
-        if not bool(request.headers.get("Authorization")):
-            coll = remove_traits(
-                redisconn,
-                User(request.json["anon_id"],#type: ignore[index]
-                     "anon@ymous.user",
-                     "Anonymous User"),
-                collection_id,
-                the_traits)
-        else:
-            with require_oauth.acquire("profile user") as token:
-                coll = remove_traits(
-                    redisconn, token.user, collection_id, the_traits)
-
-        return jsonify({
-            "message": f"Deleted {len(the_traits)} traits from collection.",
-            "collection": coll
-        })
-
-@collections.route("/<uuid:collection_id>/traits/add", methods=["POST"])
-@require_json
-def add_traits_to_collection(collection_id: UUID) -> Response:
-    """Add specified traits to collection with ID `collection_id`."""
-    if len(request.json["traits"]) < 1:#type: ignore[index]
-        return jsonify({"message": "No trait to add to collection."})
-
-    the_traits = tuple(request.json["traits"])#type: ignore[index]
-    with (Redis.from_url(current_app.config["REDIS_URI"],
-                         decode_responses=True) as redisconn):
-        if not bool(request.headers.get("Authorization")):
-            coll = add_traits(
-                redisconn,
-                User(request.json["anon_id"],#type: ignore[index]
-                     "anon@ymous.user",
-                     "Anonymous User"),
-                collection_id,
-                the_traits)
-        else:
-            with require_oauth.acquire("profile user") as token:
-                coll = add_traits(
-                    redisconn, token.user, collection_id, the_traits)
-
-        return jsonify({
-            "message": f"Added {len(the_traits)} traits to collection.",
-            "collection": coll
-        })
-
-@collections.route("/<uuid:collection_id>/rename", methods=["POST"])
-@require_json
-def rename_collection(collection_id: UUID) -> Response:
-    """Rename the given collection"""
-    if not bool(request.json["new_name"]):#type: ignore[index]
-        return jsonify({"message": "No new name to change to."})
-
-    new_name = request.json["new_name"]#type: ignore[index]
-    with (Redis.from_url(current_app.config["REDIS_URI"],
-                         decode_responses=True) as redisconn):
-        if not bool(request.headers.get("Authorization")):
-            coll = change_name(redisconn,
-                               User(UUID(request.json["anon_id"]),#type: ignore[index]
-                                    "anon@ymous.user",
-                                    "Anonymous User"),
-                               collection_id,
-                               new_name)
-        else:
-            with require_oauth.acquire("profile user") as token:
-                coll = change_name(
-                    redisconn, token.user, collection_id, new_name)
-
-        return jsonify({
-            "message": "Collection rename successful.",
-            "collection": coll
-        })
diff --git a/gn3/auth/authorisation/users/models.py b/gn3/auth/authorisation/users/models.py
deleted file mode 100644
index 0157154..0000000
--- a/gn3/auth/authorisation/users/models.py
+++ /dev/null
@@ -1,66 +0,0 @@
-"""Functions for acting on users."""
-import uuid
-from functools import reduce
-
-from gn3.auth import db
-from gn3.auth.authorisation.roles.models import Role
-from gn3.auth.authorisation.checks import authorised_p
-from gn3.auth.authorisation.privileges import Privilege
-
-from .base import User
-
-@authorised_p(
-    ("system:user:list",),
-    "You do not have the appropriate privileges to list users.",
-    oauth2_scope="profile user")
-def list_users(conn: db.DbConnection) -> tuple[User, ...]:
-    """List out all users."""
-    with db.cursor(conn) as cursor:
-        cursor.execute("SELECT * FROM users")
-        return tuple(
-            User(uuid.UUID(row["user_id"]), row["email"], row["name"])
-            for row in cursor.fetchall())
-
-def __build_resource_roles__(rows):
-    def __build_roles__(roles, row):
-        role_id = uuid.UUID(row["role_id"])
-        priv = Privilege(row["privilege_id"], row["privilege_description"])
-        role = roles.get(role_id, Role(
-            role_id, row["role_name"], bool(row["user_editable"]), tuple()))
-        return {
-            **roles,
-            role_id: Role(role_id, role.role_name, role.user_editable, role.privileges + (priv,))
-        }
-    def __build__(acc, row):
-        resource_id = uuid.UUID(row["resource_id"])
-        return {
-            **acc,
-            resource_id: __build_roles__(acc.get(resource_id, {}), row)
-        }
-    return {
-        resource_id: tuple(roles.values())
-        for resource_id, roles in reduce(__build__, rows, {}).items()
-    }
-
-# @authorised_p(
-#     ("",),
-#     ("You do not have the appropriate privileges to view a user's roles on "
-#      "resources."))
-def user_resource_roles(conn: db.DbConnection, user: User) -> dict[uuid.UUID, tuple[Role, ...]]:
-    """Fetch all the user's roles on resources."""
-    with db.cursor(conn) as cursor:
-        cursor.execute(
-            "SELECT res.*, rls.*, p.*"
-            "FROM resources AS res INNER JOIN "
-            "group_user_roles_on_resources AS guror "
-            "ON res.resource_id=guror.resource_id "
-            "LEFT JOIN roles AS rls "
-            "ON guror.role_id=rls.role_id "
-            "LEFT JOIN role_privileges AS rp "
-            "ON rls.role_id=rp.role_id "
-            "LEFT JOIN privileges AS p "
-            "ON rp.privilege_id=p.privilege_id "
-            "WHERE guror.user_id = ?",
-            (str(user.user_id),))
-        return __build_resource_roles__(
-            (dict(row) for row in cursor.fetchall()))
diff --git a/gn3/auth/authorisation/users/views.py b/gn3/auth/authorisation/users/views.py
deleted file mode 100644
index f75b51e..0000000
--- a/gn3/auth/authorisation/users/views.py
+++ /dev/null
@@ -1,173 +0,0 @@
-"""User authorisation endpoints."""
-import traceback
-from typing import Any
-from functools import partial
-
-import sqlite3
-from email_validator import validate_email, EmailNotValidError
-from flask import request, jsonify, Response, Blueprint, current_app
-
-from gn3.auth import db
-from gn3.auth.dictify import dictify
-from gn3.auth.db_utils import with_db_connection
-from gn3.auth.authorisation.oauth2.resource_server import require_oauth
-from gn3.auth.authorisation.users import User, save_user, set_user_password
-from gn3.auth.authorisation.oauth2.oauth2token import token_by_access_token
-
-from .models import list_users
-from .collections.views import collections
-
-from ..groups.models import user_group as _user_group
-from ..resources.models import user_resources as _user_resources
-from ..roles.models import assign_default_roles, user_roles as _user_roles
-from ..errors import (
-    NotFoundError, UsernameError, PasswordError, UserRegistrationError)
-
-users = Blueprint("users", __name__)
-users.register_blueprint(collections, url_prefix="/collections")
-
-@users.route("/", methods=["GET"])
-@require_oauth("profile")
-def user_details() -> Response:
-    """Return user's details."""
-    with require_oauth.acquire("profile") as the_token:
-        user = the_token.user
-        user_dets = {
-            "user_id": user.user_id, "email": user.email, "name": user.name,
-            "group": False
-        }
-        with db.connection(current_app.config["AUTH_DB"]) as conn:
-            the_group = _user_group(conn, user).maybe(# type: ignore[misc]
-                False, lambda grp: grp)# type: ignore[arg-type]
-            return jsonify({
-                **user_dets,
-                "group": dictify(the_group) if the_group else False
-            })
-
-@users.route("/roles", methods=["GET"])
-@require_oauth("role")
-def user_roles() -> Response:
-    """Return the non-resource roles assigned to the user."""
-    with require_oauth.acquire("role") as token:
-        with db.connection(current_app.config["AUTH_DB"]) as conn:
-            return jsonify(tuple(
-                dictify(role) for role in _user_roles(conn, token.user)))
-
-def validate_password(password, confirm_password) -> str:
-    """Validate the provided password."""
-    if len(password) < 8:
-        raise PasswordError("The password must be at least 8 characters long.")
-
-    if password != confirm_password:
-        raise PasswordError("Mismatched password values")
-
-    return password
-
-def validate_username(name: str) -> str:
-    """Validate the provides name."""
-    if name == "":
-        raise UsernameError("User's name not provided.")
-
-    return name
-
-def __assert_not_logged_in__(conn: db.DbConnection):
-    bearer = request.headers.get('Authorization')
-    if bearer:
-        token = token_by_access_token(conn, bearer.split(None)[1]).maybe(# type: ignore[misc]
-            False, lambda tok: tok)
-        if token:
-            raise UserRegistrationError(
-                "Cannot register user while authenticated")
-
-@users.route("/register", methods=["POST"])
-def register_user() -> Response:
-    """Register a user."""
-    with db.connection(current_app.config["AUTH_DB"]) as conn:
-        __assert_not_logged_in__(conn)
-
-        try:
-            form = request.form
-            email = validate_email(form.get("email", "").strip(),
-                                   check_deliverability=True)
-            password = validate_password(
-                form.get("password", "").strip(),
-                form.get("confirm_password", "").strip())
-            user_name = validate_username(form.get("user_name", "").strip())
-            with db.cursor(conn) as cursor:
-                user, _hashed_password = set_user_password(
-                    cursor, save_user(
-                        cursor, email["email"], user_name), password)
-                assign_default_roles(cursor, user)
-                return jsonify(
-                    {
-                        "user_id": user.user_id,
-                        "email": user.email,
-                        "name": user.name
-                    })
-        except sqlite3.IntegrityError as sq3ie:
-            current_app.logger.debug(traceback.format_exc())
-            raise UserRegistrationError(
-                "A user with that email already exists") from sq3ie
-        except EmailNotValidError as enve:
-            current_app.logger.debug(traceback.format_exc())
-            raise(UserRegistrationError(f"Email Error: {str(enve)}")) from enve
-
-    raise Exception(
-        "unknown_error", "The system experienced an unexpected error.")
-
-@users.route("/group", methods=["GET"])
-@require_oauth("profile group")
-def user_group() -> Response:
-    """Retrieve the group in which the user is a member."""
-    with require_oauth.acquire("profile group") as the_token:
-        db_uri = current_app.config["AUTH_DB"]
-        with db.connection(db_uri) as conn:
-            group = _user_group(conn, the_token.user).maybe(# type: ignore[misc]
-                False, lambda grp: grp)# type: ignore[arg-type]
-
-        if group:
-            return jsonify(dictify(group))
-        raise NotFoundError("User is not a member of any group.")
-
-@users.route("/resources", methods=["GET"])
-@require_oauth("profile resource")
-def user_resources() -> Response:
-    """Retrieve the resources a user has access to."""
-    with require_oauth.acquire("profile resource") as the_token:
-        db_uri = current_app.config["AUTH_DB"]
-        with db.connection(db_uri) as conn:
-            return jsonify([
-                dictify(resource) for resource in
-                _user_resources(conn, the_token.user)])
-
-@users.route("group/join-request", methods=["GET"])
-@require_oauth("profile group")
-def user_join_request_exists():
-    """Check whether a user has an active group join request."""
-    def __request_exists__(conn: db.DbConnection, user: User) -> dict[str, Any]:
-        with db.cursor(conn) as cursor:
-            cursor.execute(
-                "SELECT * FROM group_join_requests WHERE requester_id=? AND "
-                "status = 'PENDING'",
-                (str(user.user_id),))
-            res = cursor.fetchone()
-            if res:
-                return {
-                    "request_id": res["request_id"],
-                    "exists": True
-                }
-        return{
-            "status": "Not found",
-            "exists": False
-        }
-    with require_oauth.acquire("profile group") as the_token:
-        return jsonify(with_db_connection(partial(
-            __request_exists__, user=the_token.user)))
-
-@users.route("/list", methods=["GET"])
-@require_oauth("profile user")
-def list_all_users() -> Response:
-    """List all the users."""
-    with require_oauth.acquire("profile group") as _the_token:
-        return jsonify(tuple(
-            dictify(user) for user in with_db_connection(list_users)))
diff --git a/gn3/auth/views.py b/gn3/auth/views.py
deleted file mode 100644
index da64049..0000000
--- a/gn3/auth/views.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""The Auth(oris|entic)ation routes/views"""
-from flask import Blueprint
-
-from .authorisation.data.views import data
-from .authorisation.users.views import users
-from .authorisation.roles.views import roles
-from .authorisation.groups.views import groups
-from .authorisation.resources.views import resources
-
-oauth2 = Blueprint("oauth2", __name__)
-
-oauth2.register_blueprint(data, url_prefix="/data")
-oauth2.register_blueprint(users, url_prefix="/user")
-oauth2.register_blueprint(roles, url_prefix="/role")
-oauth2.register_blueprint(groups, url_prefix="/group")
-oauth2.register_blueprint(resources, url_prefix="/resource")