Repository: mathesar-foundation/mathesar

Issue:
Switch frontend code to the database API
**Problem**
<!-- Please provide a clear and concise description of the problem that this feature request is designed to solve.-->
We'd like to remove the database keys API in favor of the new `v0/databases/` API, which can include more information.

**Proposed solution**
<!-- A clear and concise description of your proposed solution or feature. -->
Switch frontend code from using the database keys API to the database API.

**Additional context**
<!-- Add any other context or screenshots about the feature request here.-->
- Blocked by #311


Discussion:

Resolving patch:
diff --git a/mathesar/urls.py b/mathesar/urls.py
--- a/mathesar/urls.py
+++ b/mathesar/urls.py
@@ -1,4 +1,4 @@
-from django.urls import include, path, re_path
+from django.urls import include, path
 from rest_framework_nested import routers
 
 from mathesar.views import api, frontend
@@ -17,10 +17,12 @@
 table_router.register(r'constraints', api.ConstraintViewSet, basename='table-constraint')
 
 urlpatterns = [
-    path('', frontend.index, name="index"),
     path('api/v0/', include(router.urls)),
     path('api/v0/', include(table_router.urls)),
-    # TODO: Handle known urls like /favicon.ico etc.,
-    # Currently, this catches all
-    re_path(r'(?P<dbname>\w+)/.*$', frontend.index, name="index"),
+
+    # Specifying each route individually to facilitate redirection and data pre-rendering based on route
+    path('', frontend.home, name="home"),
+    path('<db_name>/', frontend.db_home, name="db_home"),
+    path('<db_name>/schemas/', frontend.schemas, name="schemas"),
+    path('<db_name>/<int:schema_id>/', frontend.schema_home, name="schema_home"),
 ]
diff --git a/mathesar/views/frontend.py b/mathesar/views/frontend.py
--- a/mathesar/views/frontend.py
+++ b/mathesar/views/frontend.py
@@ -1,31 +1,92 @@
-from django.shortcuts import render, redirect
+from django.shortcuts import render, redirect, get_object_or_404
 
-from mathesar.models import Schema
-from mathesar.serializers import SchemaSerializer
-from mathesar.database.utils import get_non_default_database_keys
+from mathesar.models import Database, Schema
+from mathesar.serializers import DatabaseSerializer, SchemaSerializer
 
 
-def get_schemas(request):
+def get_schema_list(request, database):
     schema_serializer = SchemaSerializer(
-        Schema.objects.all(),
+        Schema.objects.filter(database=database),
         many=True,
         context={'request': request}
     )
     return schema_serializer.data
 
 
-def index(request, **kwargs):
-    requested_db = kwargs.get('dbname')
-    database_list = get_non_default_database_keys()
-    if (requested_db is None or requested_db not in database_list) and len(database_list) > 0:
-        return redirect('index', dbname=database_list[0])
-    return render(
-        request,
-        "mathesar/index.html",
-        {
-            "common_data": {
-                "schemas": get_schemas(request),
-                "databases": database_list
-            }
-        }
+def get_database_list(request):
+    database_serializer = DatabaseSerializer(
+        Database.objects.all(),
+        many=True,
+        context={'request': request}
     )
+    return database_serializer.data
+
+
+def get_common_data(request, database, schema=None):
+    return {
+        'current_db': database.name if database else None,
+        'current_schema': schema.id if schema else None,
+        'schemas': get_schema_list(request, database),
+        'databases': get_database_list(request)
+    }
+
+
+def get_current_database(request, db_name):
+    # if there's a DB name passed in, try to retrieve the database, or return a 404 error.
+    if db_name is not None:
+        return get_object_or_404(Database, name=db_name)
+    else:
+        try:
+            # Try to get the first database available
+            return Database.objects.order_by('id').first()
+        except Database.DoesNotExist:
+            return None
+
+
+def get_current_schema(request, schema_id, database):
+    # if there's a schema ID passed in, try to retrieve the schema, or return a 404 error.
+    if schema_id is not None:
+        return get_object_or_404(Schema, id=schema_id)
+    else:
+        try:
+            # Try to get the first schema in the DB
+            return Schema.objects.filter(database=database).order_by('id').first()
+        except Schema.DoesNotExist:
+            return None
+
+
+def render_schema(request, database, schema):
+    # if there's no schema available, redirect to the schemas page.
+    if not schema:
+        return redirect('schemas', db_name=database.name)
+    else:
+        # We are redirecting so that the correct URL is passed to the frontend.
+        return redirect('schema_home', db_name=database.name, schema_id=schema.id)
+
+
+def home(request):
+    database = get_current_database(request, None)
+    schema = get_current_schema(request, None, database)
+    return render_schema(request, database, schema)
+
+
+def db_home(request, db_name):
+    database = get_current_database(request, db_name)
+    schema = get_current_schema(request, None, database)
+    return render_schema(request, database, schema)
+
+
+def schema_home(request, db_name, schema_id):
+    database = get_current_database(request, db_name)
+    schema = get_current_schema(request, schema_id, database)
+    return render(request, 'mathesar/index.html', {
+        'common_data': get_common_data(request, database, schema)
+    })
+
+
+def schemas(request, db_name):
+    database = get_current_database(request, db_name)
+    schema = get_current_schema(request, None, database)
+    return render(request, 'mathesar/index.html', {
+        'common_data': get_common_data(request, database, schema)
+    })


Test patch:
diff --git a/mathesar_ui/src/__tests__/App.test.ts b/mathesar_ui/src/__tests__/App.test.ts
--- a/mathesar_ui/src/__tests__/App.test.ts
+++ b/mathesar_ui/src/__tests__/App.test.ts
@@ -1,14 +1,15 @@
 import '@testing-library/jest-dom/extend-expect';
 import { render } from '@testing-library/svelte';
-import App from '../App.svelte';
 
 function addCommonDataScript() {
   const commonDataScript = document.createElement('script');
   commonDataScript.id = 'common-data';
   commonDataScript.type = 'application/json';
+  const currentDB = { id: 1, name: 'mathesar_tables' };
   commonDataScript.textContent = JSON.stringify({
     schemas: [],
-    databases: ['mathesar_tables'],
+    databases: [currentDB],
+    current_db: currentDB.name,
   });
   document.body.append(commonDataScript);
 }
@@ -20,9 +21,10 @@ function removeCommonDataScript() {
   }
 }
 
-test('shows mathesar default text when rendered', () => {
+test('shows mathesar default text when rendered', async () => {
   addCommonDataScript();
-  const { getByText } = render(App, {});
+  const App = await import('../App.svelte');
+  const { getByText } = render(App.default, {});
   expect(getByText('mathesar_tables')).toBeInTheDocument();
   removeCommonDataScript();
 });