Skip to content

Commit

Permalink
Attempt at making on demand instance data queries
Browse files Browse the repository at this point in the history
  • Loading branch information
dokempf committed Jul 16, 2024
1 parent c79b5aa commit b60d68e
Show file tree
Hide file tree
Showing 10 changed files with 181 additions and 101 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# This file is regularyl overwritten for pregeneration
src/lib/easydb.js

# Logs
logs
*.log
Expand Down
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon">
<title>EasyDB Detail View Demo</title>
</head>
<body>
Expand Down
9 changes: 8 additions & 1 deletion src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import EasyDbDetailView from "./components/EasyDBDetailView.svelte";
let uuid = "859e2318-32f6-4013-8468-ef8cec0b581b";
let instance = "https://heidicon.ub.uni-heidelberg.de";
let languages = [
{ value: 'de-DE', name: 'Deutsch'},
Expand All @@ -21,6 +22,12 @@
explain what can be interactively controlled from the JavaScript side.
On the right hand side, we see the detail view component result.
</p>
<div class="space-y-2 p-4">
<Label>
URL of the EasyDB instance
</Label>
<Input type="text" bind:value={instance} size="lg" class="w-full"></Input>
</div>
<div class="space-y-2 p-4">
<Label>
UUID of EasyDB record
Expand Down Expand Up @@ -52,7 +59,7 @@
</div>
</div>
<div class="w-1/2 p-4 border border-gray-300">
<EasyDbDetailView uuid={uuid} l10n={selected_language} />
<EasyDbDetailView uuid={uuid} l10n={selected_language} easydb_instance={instance} />
</div>
</div>
</main>
18 changes: 13 additions & 5 deletions src/components/EasyDBDetailView.svelte
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
<script>
import { easydb_api_object } from "../lib/apiaccess";
import { pregen_instance } from "../lib/easydbPregen";
import { maskObj } from "../lib/easydbHelpers";
import { setContext } from "svelte";
import { l10nStore } from "../lib/stores";
import { l10nStore, easydbDataStore, easydbInstanceStore, easydbDataPromiseStore } from "../lib/stores";
import RecursiveEasyDbDetailView from "./RecursiveEasyDBDetailView.svelte";
export let uuid = "";
export let l10n = "de-DE";
export let easydb_instance = pregen_instance;
$: l10nStore.set(l10n);
$: easydbInstanceStore.set(easydb_instance);
setContext("l10n", l10nStore);
</script>

{#await easydb_api_object(uuid) }
Waiting for API response...
{:then data }
<RecursiveEasyDbDetailView fields={maskObj(data).fields} data={data} table={maskObj(data).table_name_hint}/>
{#await $easydbDataPromiseStore }
Accessing the EasyDB instance...
{:then}
{#await easydb_api_object(uuid) }
Waiting for API response...
{:then data }
<RecursiveEasyDbDetailView fields={maskObj(data).fields} data={data} table={maskObj(data).table_name_hint}/>
{/await}
{/await}
96 changes: 8 additions & 88 deletions src/generate.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { writeFile } from 'fs/promises';

// TODO: In the reorganization functions below we could also cull data that we don't need
// in order to reduce the bundle size. This is a low priority performance optimization.
import { accessInstance } from './lib/easydbData.js';

// This "trick" is super-unfortunate, but Svelte Preprocessing is fully regex-based
// and any attempts to pass stringified data that contains backslashes will result in
Expand All @@ -11,96 +9,18 @@ function escape(str) {
return str.replaceAll("\\\"", "__ESCAPED_QUOTES__").replaceAll("\\", "__BACKSLASH__");
}

// We massage the API response a bit to fit our needs better. This makes it more convenient
// to work with the data in the Svelte component.
function reorganize_masks(maskdata) {
let newdata = {};
for (let mask of maskdata.masks) {
newdata[mask.name] = mask;
}
return newdata;
}

// We do the same for the schema data, where we want to access the tables directly by
// their name in the Svelte component.
function reorganize_schemas(schemadata) {
let newdata = {};
for (let table of schemadata.tables) {
newdata[table.name] = table;
console.log(table.name);
}
return newdata;
}

// Get a session token from the EasyDB instance
const token_response = await fetch('https://heidicon.ub.uni-heidelberg.de/api/session');
if(token_response.status != 200) {
throw new Error("Could not get a session token from the EasyDB instance.");
}
const token_json = await token_response.json();
const token = token_json.token;

// Authenticcate the session token
const auth_response = await fetch(
'https://heidicon.ub.uni-heidelberg.de/api/session/authenticate?' +
new URLSearchParams({
token: token,
method: 'anonymous',
}),
{
method: 'POST',
}
);
if(auth_response.status != 200) {
throw new Error("Could not authenticate the session token.");
}

// Fetch all the masks for this instance
const mask_response = await fetch(
'https://heidicon.ub.uni-heidelberg.de/api/v1/mask/HEAD?' +
new URLSearchParams({
"token": token,
"format": "json",
}),
);
if(mask_response.status != 200) {
throw new Error("Could not fetch the masks for this instance");
}
const masks = await mask_response.json();

// Fetch all the l10n data for this instance
const l10n_response = await fetch(
'https://heidicon.ub.uni-heidelberg.de/api/v1/l10n/user/HEAD?' +
new URLSearchParams({
"token": token,
}),
);
if(l10n_response.status != 200) {
throw new Error("Could not fetch the l10n data for this instance");
}
const l10n = await l10n_response.json();

// Fetch the schema data for this instance
const schema_response = await fetch(
'https://heidicon.ub.uni-heidelberg.de/api/v1/schema/user/HEAD?' +
new URLSearchParams({
"token": token,
"format": "json",
}),
);
if(schema_response.status != 200) {
throw new Error("Could not fetch the schema data for this instance");
}
const schema = await schema_response.json();
const instance = "https://heidicon.ub.uni-heidelberg.de";
const data = await accessInstance(instance);

// Inject the data into the Svelte component as a constant
const content =
`export const masks = JSON.parse('${escape(JSON.stringify(reorganize_masks(masks)))}'.replaceAll("__ESCAPED_QUOTES__", "\\\\\\"").replaceAll("__BACKSLASH__", "\\\\"));\n` +
`export const l10n = JSON.parse('${escape(JSON.stringify(l10n))}'.replaceAll("__ESCAPED_QUOTES__", "\\\\\\"").replaceAll("__BACKSLASH__", "\\\\"));\n` +
`export const schemas = JSON.parse('${escape(JSON.stringify(reorganize_schemas(schema)))}'.replaceAll("__ESCAPED_QUOTES__", "\\\\\\"").replaceAll("__BACKSLASH__", "\\\\"));\n`;
`export const pregen_instance = '${instance}';\n` +
`export const pregen_masks = JSON.parse('${escape(JSON.stringify(data.masks))}'.replaceAll("__ESCAPED_QUOTES__", "\\\\\\"").replaceAll("__BACKSLASH__", "\\\\"));\n` +
`export const pregen_l10n = JSON.parse('${escape(JSON.stringify(data.l10n))}'.replaceAll("__ESCAPED_QUOTES__", "\\\\\\"").replaceAll("__BACKSLASH__", "\\\\"));\n` +
`export const pregen_schemas = JSON.parse('${escape(JSON.stringify(data.schemas))}'.replaceAll("__ESCAPED_QUOTES__", "\\\\\\"").replaceAll("__BACKSLASH__", "\\\\"));\n`;

try {
await writeFile('src/lib/easydb.js', content);
await writeFile('src/lib/easydbPregen.js', content);
console.log('Successfully regenerated the EasyDB data.');
} catch (error) {
console.error('Error writing the EasyDB data:', error);
Expand Down
5 changes: 4 additions & 1 deletion src/lib/apiaccess.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { get } from 'svelte/store';
import { easydbInstanceStore } from './stores';

export async function easydb_api_object(uuid) {
if (!uuid) {
return {}
}

// Fetch the schema data for this instance
const response = await fetch('http://localhost:8080/https://heidicon.ub.uni-heidelberg.de/api/objects/uuid/' + uuid);
const response = await fetch(`${get(easydbInstanceStore)}/api/objects/uuid/${uuid}`);
if(response.status != 200) {
throw new Error(`Could not fetch the data for uuid: ${uuid}`);
}
Expand Down
92 changes: 92 additions & 0 deletions src/lib/easydbData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// TODO: In the reorganization functions below we could also cull data that we don't need
// in order to reduce the bundle size. This is a low priority performance optimization.

// We massage the API response a bit to fit our needs better. This makes it more convenient
// to work with the data in the Svelte component.
function reorganize_masks(maskdata) {
let newdata = {};
for (let mask of maskdata.masks) {
newdata[mask.name] = mask;
}
return newdata;
}

// We do the same for the schema data, where we want to access the tables directly by
// their name in the Svelte component.
function reorganize_schemas(schemadata) {
let newdata = {};
for (let table of schemadata.tables) {
newdata[table.name] = table;
}
return newdata;
}

export async function accessInstance(instance) {
// Get a session token from the EasyDB instance
const token_response = await fetch(`${instance}/api/session`);
if(token_response.status != 200) {
throw new Error("Could not get a session token from the EasyDB instance.");
}
const token_json = await token_response.json();
const token = token_json.token;

// Authenticcate the session token
const auth_response = await fetch(
`${instance}/api/session/authenticate?` +
new URLSearchParams({
token: token,
method: 'anonymous',
}),
{
method: 'POST',
}
);
if(auth_response.status != 200) {
throw new Error("Could not authenticate the session token.");
}

// Fetch all the masks for this instance
const mask_response = await fetch(
`${instance}/api/v1/mask/HEAD?` +
new URLSearchParams({
"token": token,
"format": "json",
}),
);
if(mask_response.status != 200) {
throw new Error("Could not fetch the masks for this instance");
}
const masks = await mask_response.json();

// Fetch all the l10n data for this instance
const l10n_response = await fetch(
`${instance}/api/v1/l10n/user/HEAD?` +
new URLSearchParams({
"token": token,
}),
);
if(l10n_response.status != 200) {
throw new Error("Could not fetch the l10n data for this instance");
}
const l10n = await l10n_response.json();

// Fetch the schema data for this instance
const schema_response = await fetch(
`${instance}/api/v1/schema/user/HEAD?` +
new URLSearchParams({
"token": token,
"format": "json",
}),
);
if(schema_response.status != 200) {
throw new Error("Could not fetch the schema data for this instance");
}
const schema = await schema_response.json();

return {
instance: instance,
masks: reorganize_masks(masks),
l10n: l10n,
schemas: reorganize_schemas(schema),
}
}
11 changes: 6 additions & 5 deletions src/lib/easydbHelpers.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { l10n, masks, schemas } from './easydb';
import { easydbDataStore } from './stores';
import { get } from 'svelte/store';
import { bestLanguage } from './l10n';

export function maskObj(data) {
return masks[data._mask];
return get(easydbDataStore).masks[data._mask];
}

// Given a data JSON object and a field from a mask, return the schema for that column
export function findSchemaColumn(table, field) {
return schemas[table].columns.find((column) => column.name === field.column_name_hint);
return get(easydbDataStore).schemas[table].columns.find((column) => column.name === field.column_name_hint);
}

// Given a data JSON object and the field definition from a mask, return the label of the field with the language code lang
export function fieldLabel(table, field, lang) {
return bestLanguage(l10n[`schema.${table}.column.${field.column_name_hint}`], lang);
return bestLanguage(get(easydbDataStore).l10n[`schema.${table}.column.${field.column_name_hint}`], lang);
}

// Given a data JSON object and the field definition from a mask, return a boolean whether it exists in the data
Expand Down Expand Up @@ -48,7 +49,7 @@ export function reverseLinkedSubData(data, table, field) {
}

export function splitterTitle(data, table, options, lang) {
return bestLanguage(l10n[`mask.${schemas[table].table_id}.${maskObj(data).name}.splitter.${String(options.splitterIdx)}`], lang);
return bestLanguage(get(easydbDataStore).l10n[`mask.${get(easydbDataStore).schemas[table].table_id}.${maskObj(data).name}.splitter.${String(options.splitterIdx)}`], lang);
}

export function hasContent(data, table, fields) {
Expand Down
4 changes: 4 additions & 0 deletions src/lib/easydbPregen.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const pregen_instance = null;
export const pregen_masks = null;
export const pregen_l10n = null;
export const pregen_schemas = null;
43 changes: 42 additions & 1 deletion src/lib/stores.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,44 @@
import { writable } from "svelte/store";
import { derived, writable } from "svelte/store";
import { accessInstance } from "./easydbData";
import { pregen_instance, pregen_l10n, pregen_masks, pregen_schemas } from "./easydbPregen";

// A derived store that resolves a promise
function derivedPromise(store) {
return derived(store, ($store, set) => {
$store.then(value => {
set(value);
});
});
}

// Our pregenerated defaults wrapped in a Promise
async function pregenDefaults() {
return {
instance: pregen_instance,
masks: pregen_masks,
schemas: pregen_schemas,
l10n: pregen_l10n,
};
}

// This manages the global state of the current language
export const l10nStore = writable(null);

// This manages the global state of the EasyDB instance we are talking to
export const easydbInstanceStore = writable(null);

// A derived store that delivers a promise.
export const easydbDataPromiseStore = derived(
easydbInstanceStore,
($instance) => {
if ($instance === pregen_instance) {
return pregenDefaults();
}
return accessInstance($instance);
}
);

// A derived store that awaits above promise. It would be better to not even
// expose the promise store, but I did not get this to work without the explicit
// await in EasyDBDetailView.svelte.
export const easydbDataStore = derivedPromise(easydbDataPromiseStore);

0 comments on commit b60d68e

Please sign in to comment.