chore: aggressive cleanup: remove legacy element_data_store.svelte (v1) after v3 migration
This commit is contained in:
@@ -1,430 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { browser } from '$app/environment';
|
||||
import { onMount, untrack } from 'svelte';
|
||||
import { Modal } from 'flowbite-svelte';
|
||||
import { liveQuery } from 'dexie';
|
||||
|
||||
import { api } from '$lib/api/api';
|
||||
import { ae_loc, ae_sess, ae_api, slct } from '$lib/stores/ae_stores';
|
||||
import { db_core } from '$lib/ae_core/db_core';
|
||||
import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||
import type { key_val } from '$lib/stores/ae_stores';
|
||||
import type { ae_DataStore } from '$lib/types/ae_types';
|
||||
import { LoaderCircle, Pencil, Save, Trash2 } from '@lucide/svelte';
|
||||
interface Props {
|
||||
log_lvl?: number;
|
||||
expire_minutes?: number;
|
||||
mount_reload_sec?: number;
|
||||
ds_code: string;
|
||||
ds_name?: null | string;
|
||||
ds_type?: string;
|
||||
for_type?: null | string;
|
||||
for_id?: null | string;
|
||||
class_li?: string;
|
||||
display?: string;
|
||||
try_cache?: boolean;
|
||||
hide?: boolean;
|
||||
show_edit?: boolean;
|
||||
show_edit_btn?: boolean;
|
||||
show_view?: boolean;
|
||||
ds_loaded?: boolean;
|
||||
debug?: boolean;
|
||||
ds_loading_status?: string;
|
||||
val_sql?: null | any;
|
||||
}
|
||||
|
||||
let {
|
||||
log_lvl = 0,
|
||||
expire_minutes = 15,
|
||||
mount_reload_sec = 0,
|
||||
ds_code,
|
||||
ds_name = null,
|
||||
ds_type = 'text',
|
||||
for_type = null,
|
||||
for_id = null,
|
||||
class_li = '',
|
||||
display = undefined as string | undefined,
|
||||
try_cache = true,
|
||||
hide = false,
|
||||
show_edit = $bindable(false),
|
||||
show_edit_btn = false,
|
||||
show_view = $bindable(true),
|
||||
ds_loaded = $bindable(false),
|
||||
debug = false,
|
||||
ds_loading_status = $bindable('starting'),
|
||||
val_sql = $bindable(null)
|
||||
}: Props = $props();
|
||||
|
||||
// Local reactive state
|
||||
let trigger: null | string = $state(null);
|
||||
let ds_submit_results: Promise<any> | key_val | undefined = $state();
|
||||
|
||||
// Dexie LiveQuery for data store
|
||||
// This derived observable will automatically update when dependencies change
|
||||
let lq__ds_obj = $derived(
|
||||
liveQuery(async () => {
|
||||
const current_code = ds_code;
|
||||
const account_id = $ae_loc.account_id;
|
||||
const current_for_type = for_type;
|
||||
const current_for_id = for_id;
|
||||
|
||||
if (!current_code) return null;
|
||||
|
||||
if (log_lvl) console.log(`ae_e_data_store [${current_code}]: LQ Lookup...`, { account_id, current_for_type, current_for_id });
|
||||
|
||||
// Hierarchical Local Lookup (Specific -> Account -> Global)
|
||||
// Mimics backend SQL priority: WHERE code = :code ORDER BY for_id DESC, account_id DESC
|
||||
if (log_lvl) console.log(`ae_e_data_store [${current_code}]: Fetching all matching codes for priority sorting...`);
|
||||
|
||||
const results = await db_core.data_store
|
||||
.where('code')
|
||||
.equals(current_code)
|
||||
.toArray();
|
||||
|
||||
if (!results || results.length === 0) return null;
|
||||
|
||||
// Sort by specificity
|
||||
results.sort((a, b) => {
|
||||
// 1. Priority: Specific Context match (for_type + for_id)
|
||||
const a_context = (current_for_id && a.for_id === current_for_id && a.for_type === current_for_type) ? 1 : 0;
|
||||
const b_context = (current_for_id && b.for_id === current_for_id && b.for_type === current_for_type) ? 1 : 0;
|
||||
if (a_context !== b_context) return b_context - a_context;
|
||||
|
||||
// 2. Priority: Account-specific match
|
||||
const a_account = (account_id && a.account_id === account_id) ? 1 : 0;
|
||||
const b_account = (account_id && b.account_id === account_id) ? 1 : 0;
|
||||
if (a_account !== b_account) return b_account - a_account;
|
||||
|
||||
// 3. Tie-breaker: Newest updated
|
||||
const a_time = new Date(a.updated_on || a.created_on || 0).getTime();
|
||||
const b_time = new Date(b.updated_on || b.created_on || 0).getTime();
|
||||
return b_time - a_time;
|
||||
});
|
||||
|
||||
if (log_lvl) console.log(`ae_e_data_store [${current_code}]: Best match found (ID: ${results[0].id}, Account: ${results[0].account_id})`);
|
||||
return results[0];
|
||||
})
|
||||
);
|
||||
|
||||
// Sync status and bound props when the live data changes
|
||||
$effect(() => {
|
||||
const entry = $lq__ds_obj as ae_DataStore | null;
|
||||
|
||||
untrack(() => {
|
||||
ds_loaded = !!entry;
|
||||
if (entry) {
|
||||
ds_loading_status = 'loaded';
|
||||
// Handle val_sql binding if type is sql
|
||||
if (ds_type === 'sql') {
|
||||
val_sql = entry.text || entry.html || null;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Initial Trigger & Context Change Guard
|
||||
$effect(() => {
|
||||
const account_id = $slct.account_id;
|
||||
const api_ready = !!$ae_api?.base_url;
|
||||
const entry = $lq__ds_obj;
|
||||
|
||||
if (browser && api_ready && !entry && ds_loading_status === 'starting') {
|
||||
trigger = 'load__ds__code';
|
||||
}
|
||||
});
|
||||
|
||||
// Fetch handler
|
||||
$effect(() => {
|
||||
if (trigger === 'load__ds__code') {
|
||||
untrack(() => {
|
||||
trigger = null;
|
||||
load_data_store();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Mount reload logic
|
||||
onMount(() => {
|
||||
if (mount_reload_sec > 0) {
|
||||
const random_ms = Math.floor(Math.random() * mount_reload_sec * 1000);
|
||||
setTimeout(() => { trigger = 'load__ds__code'; }, random_ms);
|
||||
}
|
||||
});
|
||||
|
||||
async function load_data_store() {
|
||||
if (ds_loading_status === 'loading') return;
|
||||
ds_loading_status = 'loading';
|
||||
const api_cfg = untrack(() => $ae_api);
|
||||
|
||||
if (log_lvl) console.log(`ae_e_data_store [${ds_code}]: Fetching...`);
|
||||
|
||||
try {
|
||||
// Attempt 1: Context-specific fetch
|
||||
let ds_results = await api.get_data_store_v3({
|
||||
api_cfg,
|
||||
code: ds_code,
|
||||
for_type: for_type,
|
||||
for_id: for_id,
|
||||
log_lvl: log_lvl
|
||||
});
|
||||
|
||||
// V3 API structured check
|
||||
const is_error = ds_results?.meta?.success === false;
|
||||
const status_code = ds_results?.meta?.status_code || (ds_results === false ? 500 : 200);
|
||||
|
||||
// Fallback to Global if not found (404), unauthorized (403/401), or explicitly failed
|
||||
if (!ds_results || is_error || status_code === 404 || status_code === 403 || status_code === 401) {
|
||||
if (log_lvl) console.log(`ae_e_data_store [${ds_code}]: Not found in context (Status ${status_code}). Trying global fallback.`);
|
||||
|
||||
ds_results = await api.get_data_store_v3({
|
||||
api_cfg,
|
||||
code: ds_code,
|
||||
no_account_id: true,
|
||||
log_lvl: log_lvl
|
||||
});
|
||||
}
|
||||
|
||||
const ds_id = ds_results?.data_store_id || ds_results?.id;
|
||||
|
||||
if (ds_results && ds_id) {
|
||||
// Map fields correctly for V3 alignment
|
||||
const text_val = ds_results.text || '';
|
||||
const json_val = ds_results.json || (ds_results.json_str ? JSON.parse(ds_results.json_str) : null);
|
||||
|
||||
// Save to Dexie
|
||||
const ds_to_save: ae_DataStore = {
|
||||
...ds_results,
|
||||
id: ds_id,
|
||||
data_store_id: ds_results.data_store_id || ds_id,
|
||||
// data_store_id: ds_id,
|
||||
account_id: ds_results.account_id || ds_results.account_id,
|
||||
// account_id: ds_results.account_id || ds_results.account_id,
|
||||
updated_on: ds_results.updated_on || new Date().toISOString(),
|
||||
text: text_val,
|
||||
html: text_val, // Default map text to html
|
||||
json: json_val
|
||||
};
|
||||
|
||||
await db_core.data_store.put(ds_to_save);
|
||||
if (log_lvl) console.log(`ae_e_data_store [${ds_code}]: Saved to Dexie. ID: ${ds_id}`);
|
||||
} else {
|
||||
ds_loading_status = 'not found';
|
||||
if (log_lvl) console.warn(`ae_e_data_store [${ds_code}]: Result had no valid ID.`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`ae_e_data_store [${ds_code}]: Fetch failed.`, err);
|
||||
ds_loading_status = 'error';
|
||||
}
|
||||
}
|
||||
|
||||
async function handle_submit_form(event: Event) {
|
||||
const target = event.target as HTMLFormElement;
|
||||
$ae_sess.ds.submit_status = 'processing';
|
||||
|
||||
const form_data = new FormData(target);
|
||||
const data_store_di = ae_util.extract_prefixed_form_data({
|
||||
prefix: null,
|
||||
form_data,
|
||||
trim_values: true,
|
||||
bool_tf_str: true
|
||||
});
|
||||
|
||||
const data_store_do: key_val = {
|
||||
code: data_store_di.ds_code ?? ds_code,
|
||||
name: data_store_di.ds_name ?? ds_name,
|
||||
type: data_store_di.ds_type ?? ds_type,
|
||||
for_type: data_store_di.ds_for_type ?? null,
|
||||
for_id: data_store_di.ds_for_id ?? null,
|
||||
access_read: data_store_di.ds_access_read,
|
||||
access_write: data_store_di.ds_access_write,
|
||||
access_delete: data_store_di.ds_access_delete,
|
||||
enable: data_store_di.ds_enable ?? true,
|
||||
account_id: data_store_di.ds_use_account_id ? (data_store_di.ds_account_id ?? $slct.account_id) : null
|
||||
};
|
||||
|
||||
const content_val = data_store_di.ds_value;
|
||||
if (data_store_do.type === 'json') {
|
||||
data_store_do.json = content_val;
|
||||
try {
|
||||
// Ensure it's valid JSON if stringified
|
||||
if (typeof content_val === 'string') JSON.parse(content_val);
|
||||
} catch (e) {
|
||||
console.error("Invalid JSON content");
|
||||
}
|
||||
} else {
|
||||
data_store_do.text = content_val;
|
||||
}
|
||||
|
||||
const api_cfg = untrack(() => $ae_api);
|
||||
|
||||
if ($lq__ds_obj?.id) {
|
||||
ds_submit_results = api.update_ae_obj_v3({
|
||||
api_cfg,
|
||||
obj_type: 'data_store',
|
||||
obj_id: $lq__ds_obj.id,
|
||||
fields: data_store_do
|
||||
}).then((res) => {
|
||||
if (res) {
|
||||
$ae_sess.ds.submit_status = 'updated';
|
||||
trigger = 'load__ds__code';
|
||||
}
|
||||
return res;
|
||||
});
|
||||
} else {
|
||||
ds_submit_results = api.create_ae_obj_v3({
|
||||
api_cfg,
|
||||
obj_type: 'data_store',
|
||||
fields: data_store_do
|
||||
}).then((res) => {
|
||||
if (res) {
|
||||
$ae_sess.ds.submit_status = 'created';
|
||||
trigger = 'load__ds__code';
|
||||
}
|
||||
return res;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function handle_delete() {
|
||||
if (!$lq__ds_obj?.id || !confirm('Are you sure you want to delete this data store?')) return;
|
||||
|
||||
const api_cfg = untrack(() => $ae_api);
|
||||
const res = await api.delete_ae_obj_v3({
|
||||
api_cfg,
|
||||
obj_type: 'data_store',
|
||||
obj_id: $lq__ds_obj.id,
|
||||
method: 'delete'
|
||||
});
|
||||
|
||||
if (res) {
|
||||
await db_core.data_store.delete($lq__ds_obj.id);
|
||||
ds_loading_status = 'not found';
|
||||
show_edit = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="ae__elem__data_store relative {class_li}" class:hidden={hide} style={display ? `display: ${display}` : undefined}>
|
||||
|
||||
{#if $lq__ds_obj}
|
||||
{#if debug || $ae_loc.debug === 'debug'}
|
||||
Debug is ON!
|
||||
<pre class="text-[10px] bg-black/10 p-2 rounded mb-2 overflow-x-auto">
|
||||
ID: {$lq__ds_obj.id}
|
||||
Code: {$lq__ds_obj.code}
|
||||
Name: {$lq__ds_obj.name}
|
||||
Type: {$lq__ds_obj.type}
|
||||
Account: {$lq__ds_obj.account_id || 'Global / NULL'}
|
||||
Created: {$lq__ds_obj.created_on}
|
||||
Updated: {$lq__ds_obj.updated_on}
|
||||
</pre>
|
||||
|
||||
<hr />
|
||||
{/if}
|
||||
|
||||
<Modal
|
||||
title="{$lq__ds_obj.name || 'Unnamed'} - {$lq__ds_obj.code}"
|
||||
bind:open={show_edit}
|
||||
autoclose={false}
|
||||
size="xl"
|
||||
class="w-full max-w-6xl"
|
||||
>
|
||||
<form class="flex flex-col gap-4" onsubmit={(e) => { e.preventDefault(); handle_submit_form(e); }}>
|
||||
<input type="hidden" name="ds_id_random" value={$lq__ds_obj.id} />
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<label class="label">
|
||||
<span class="text-xs font-bold opacity-70">Code</span>
|
||||
<input type="text" name="ds_code" class="input font-mono" value={$lq__ds_obj.code} readonly={!$ae_loc.manager_access} required />
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="text-xs font-bold opacity-70">Name</span>
|
||||
<input type="text" name="ds_name" class="input" value={$lq__ds_obj.name} required />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="label">
|
||||
<span class="text-xs font-bold opacity-70">Type</span>
|
||||
<select name="ds_type" class="select" value={$lq__ds_obj.type}>
|
||||
<option value="text">Text</option>
|
||||
<option value="html">HTML</option>
|
||||
<option value="json">JSON</option>
|
||||
<option value="md">Markdown</option>
|
||||
<option value="sql">SQL</option>
|
||||
</select>
|
||||
</label>
|
||||
<div class="flex items-center gap-2 pt-6">
|
||||
<input type="checkbox" name="ds_use_account_id" class="checkbox" checked={!!$lq__ds_obj.account_id} />
|
||||
<span class="text-xs">Account Specific</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<span class="text-xs font-bold opacity-70">Content</span>
|
||||
<textarea
|
||||
name="ds_value"
|
||||
class="textarea font-mono text-sm"
|
||||
rows="15"
|
||||
placeholder="Enter content here..."
|
||||
>{$lq__ds_obj.type === 'json' ? (typeof $lq__ds_obj.json === 'string' ? $lq__ds_obj.json : JSON.stringify($lq__ds_obj.json, null, 2)) : ($lq__ds_obj.text || $lq__ds_obj.html || '')}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="text-xs text-surface-500">
|
||||
Created on: {$lq__ds_obj.created_on} | Last Updated: {$lq__ds_obj.updated_on}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="flex justify-between items-center pt-4">
|
||||
<button type="button" class="btn variant-filled-error" onclick={handle_delete}>
|
||||
<Trash2 size="1em" class="mr-2" /> Delete
|
||||
</button>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<button type="button" class="btn variant-soft" onclick={() => show_edit = false}>Cancel</button>
|
||||
<button type="submit" class="btn variant-filled-primary">
|
||||
<Save size="1em" class="mr-2" /> Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
|
||||
{#if show_view}
|
||||
{#if $lq__ds_obj.type === 'html' && $lq__ds_obj.html}
|
||||
{@html $lq__ds_obj.html}
|
||||
{:else if $lq__ds_obj.type === 'text' && $lq__ds_obj.text}
|
||||
<div class="whitespace-pre-wrap">{$lq__ds_obj.text}</div>
|
||||
{:else if $lq__ds_obj.type === 'sql' && $lq__ds_obj.text}
|
||||
{#if debug}<div class="font-mono text-xs opacity-50">SQL: {$lq__ds_obj.text}</div>{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if $ae_loc.edit_mode && ($ae_loc.manager_access || (show_edit_btn && $ae_loc.administrator_access))}
|
||||
<button
|
||||
type="button"
|
||||
class="absolute top-0 right-0 btn btn-sm variant-soft-warning opacity-20 hover:opacity-100 z-10"
|
||||
ondblclick={() => { show_edit = true; show_view = false; }}
|
||||
title="Edit Data Store: {ds_code}"
|
||||
>
|
||||
<Pencil size="1em" />
|
||||
</button>
|
||||
{/if}
|
||||
{:else if ds_loading_status === 'not found'}
|
||||
<!-- Only show diagnostic to administrator+ (no edit_mode needed) or trusted staff in edit mode.
|
||||
Anonymous/user/public visitors must never see internal data store codes or gaps. -->
|
||||
{#if $ae_loc.administrator_access || ($ae_loc.trusted_access && $ae_loc.edit_mode)}
|
||||
<div class="p-2 border border-dashed border-surface-500/30 rounded text-xs opacity-50">
|
||||
Data Store not found: {ds_code}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if ds_loading_status === 'loading'}
|
||||
<div class="absolute bottom-0 left-0 p-1 opacity-50">
|
||||
<LoaderCircle size="1em" class="text-xs animate-spin" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -2,7 +2,7 @@
|
||||
import type { key_val } from '$lib/stores/ae_stores';
|
||||
import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||
// import { api } from '$lib/api';
|
||||
// import Element_data_store from '$lib/element_data_store_v3.svelte';
|
||||
// import Element_data_store from '$lib/element_data_store.svelte';
|
||||
|
||||
// import { core_func } from '$lib/ae_core_functions';
|
||||
import { ae_loc, ae_sess, ae_api, ae_trig, slct, slct_trigger } from '$lib/stores/ae_stores';
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import type { key_val } from '$lib/stores/ae_stores';
|
||||
// import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||
// import { api } from '$lib/api';
|
||||
// import Element_data_store from '$lib/element_data_store_v3.svelte';
|
||||
// import Element_data_store from '$lib/element_data_store.svelte';
|
||||
import Element_manage_hosted_file_li from '$lib/elements/element_manage_hosted_file_li.svelte';
|
||||
|
||||
// import { core_func } from '$lib/ae_core_functions';
|
||||
|
||||
Reference in New Issue
Block a user