Files
OSIT-AE-App-Svelte/src/lib/stores/ae_stores.ts
Scott Idem 65e0477761 refactor(build): replace staging/cp env hack with vite --mode per-environment
- Rename .env.staging → .env.dev (and .default template)
- Add .env.test.default for the test tier (test-api.oneskyit.com)
- build:staging → build:dev/test/prod using vite --mode <name>
- deploy:staging → deploy:dev; add deploy:test
- Dockerfile: ARG BUILD_MODE=dev; explicit .env.runtime copy per mode
- .dockerignore: rewritten (deduped); allow .env.dev/.env.test/.env.prod
- .gitignore: track .env.dev.default and .env.test.default
- Remove dead PUBLIC_AE_* imports from ae_stores.ts (ACCOUNT_ID, EVENT_ID,
  NO_ACCOUNT_ID_TOKEN, SPONSORSHIP_CFG_ID); sponsorship_cfg_id defaults to null
- Strip dead vars from .env.prod.default template (AE_CFG_ID, AE_APP_NODE_PORT,
  ACCOUNT_ID, EVENT_ID, SPONSORSHIP_CFG_ID, NO_ACCOUNT_ID_TOKEN)
- GUIDE__Development.md: build:staging → build:dev

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 16:07:31 -04:00

387 lines
12 KiB
TypeScript

// store_versions MUST be first import — its side-effect wipes stale localStorage
// before svelte-persisted-store hydrates from it.
import { AE_LOC_VERSION } from '$lib/stores/store_versions';
import { auth_loc_defaults } from '$lib/stores/ae_stores__auth_loc_defaults';
import { persisted } from 'svelte-persisted-store';
import { readable, writable } from 'svelte/store';
import type { Readable, Writable } from 'svelte/store';
import {
PUBLIC_AE_API_PROTOCOL,
PUBLIC_AE_API_SERVER,
PUBLIC_AE_API_BAK_SERVER,
PUBLIC_AE_API_PORT,
PUBLIC_AE_API_PATH,
PUBLIC_AE_API_SECRET_KEY,
PUBLIC_AE_API_CRUD_SUPER_KEY,
PUBLIC_AE_NO_ACCOUNT_ID,
// PUBLIC_AE_NO_ACCOUNT_ID_TOKEN,
// PUBLIC_AE_ACCOUNT_ID,
// PUBLIC_AE_EVENT_ID,
// PUBLIC_AE_SPONSORSHIP_CFG_ID
} from '$env/static/public';
const api_server_fqdn = PUBLIC_AE_API_SERVER; // 'api.oneskyit.com'
const api_base_url = `${PUBLIC_AE_API_PROTOCOL}://${PUBLIC_AE_API_SERVER}:${PUBLIC_AE_API_PORT}${PUBLIC_AE_API_PATH}`;
const api_base_url_bak = `${PUBLIC_AE_API_PROTOCOL}://${PUBLIC_AE_API_BAK_SERVER}:${PUBLIC_AE_API_PORT}${PUBLIC_AE_API_PATH}`;
const api_secret_key = PUBLIC_AE_API_SECRET_KEY;
const api_crud_super_key = PUBLIC_AE_API_CRUD_SUPER_KEY;
// const ae_account_id = PUBLIC_AE_ACCOUNT_ID;
const ae_account_id: null | string = null;
const ae_no_account_id = PUBLIC_AE_NO_ACCOUNT_ID;
// const ae_no_account_id_token = PUBLIC_AE_NO_ACCOUNT_ID_TOKEN;
// const ae_event_id = PUBLIC_AE_EVENT_ID;
// const ae_sponsorship_cfg_id = PUBLIC_AE_SPONSORSHIP_CFG_ID;
// Loose bag-of-anything type used throughout the store. Once stores are broken into
// typed domain files, individual structs will have proper interfaces instead.
export type key_val = {
[key: string]: any;
};
import { string_snippets } from '$lib/utils/ae_string_snippets';
export const ae_snip = string_snippets;
// Deployment version stamp. Compared against ae_sess.ver in +layout.svelte to detect
// stale persisted data after a deploy (triggers a reload). Bump this alongside
// ae_app_session_data_defaults.ver whenever a deploy changes expected stored shape.
// See store_versions.ts for the schema-level localStorage wipe mechanism (separate concern).
const ver = '2025-05-01_1445';
// *** BEGIN *** Longer-term app data. This should be stored to local storage.
const ae_app_local_data_defaults: key_val = {
__version: AE_LOC_VERSION, // Schema version gate — see store_versions.ts
last_page_reload: null,
last_cache_refresh: Date.now(),
cache_expired: false,
// Deployment stamp — compared against ae_sess.ver in +layout.svelte to trigger
// a reload when the persisted local data predates the current deploy.
ver: ver,
name: 'Aether - App Hub',
theme: 'light',
theme_mode: 'light',
theme_name: 'AE_Firefly', // OSIT default theme
font_size_mode: 'default', // 'default' | 'larger' | 'smaller'
iframe: false,
browser_type: null, // Safari needs special handling for scrolling in iframes.
title: `OSIT's Æ`,
debug_mode: false,
edit_mode: false,
adv_mode: false,
sync_local_config: true, // When true, pulls site config from the remote API on load.
// Auth and identity section — see ae_stores__auth_loc_defaults.ts.
...auth_loc_defaults,
qry__enabled: 'enabled', // all, disabled, enabled
qry__hidden: 'not_hidden', // all, hidden, not_hidden
qry__limit: 20,
qry__offset: 0,
qr_scanner_version: 'one',
admin: {
show_element__sql_qry: false,
show_element__sql_qry_results: false
},
sys_menu: {
hide: false,
expand: false,
hide_access_type: false,
expand_access_type: false,
hide_edit_mode: false,
expand_edit_mode: false,
hide_user: false,
expand_user: false,
hide_theme: false,
expand_theme: false,
hide_app_cfg: false,
expand_app_cfg: false
},
debug_menu: {
hide: false,
expand: false
},
app_cfg: {
show_element__header: false,
show_element__footer: false,
show_element__menu: false,
show_element__menu_btn: true,
show_element__access_type: true,
show_element__passcode_input: true,
show_element__cfg: true,
show_element__cfg_detail: false,
show_element__sign_in_out: true, // Show the sign-in/out button in the UI
show_opt__debug: true,
show_opt__permissions: true,
show_opt__reset: true,
show_opt__sync: true,
show_opt__theme: true,
show_opt__utilities: true
},
files: {
processed_file_kv: {},
uploaded_file_kv: {},
video_clip_file_kv: {},
add_to_use_files_method: 'upload' // upload, select
},
ds: {},
hub: {
show_element__cfg: true,
show_element__cfg_detail: false,
show_element__access_type: true,
theme_mode: 'light',
theme_name: 'wintry', // wintry
classes__form:
'border border-surface-200 p-4 space-y-4 rounded-container',
qr: {}
},
mod: {
archives: {},
events: {
event_id: null,
show_edit__event_presenter_obj: false,
show_list__event_presenter_obj_li: true,
show_view__event_presenter_obj: false,
submit_status: null,
// When set, new presenters are automatically assigned to this session.
default_session_id: null
},
journals: {},
posts: {},
sponsorships: {
cfg_id: null,
for_type: null,
for_id: null,
// Max complimentary guests per sponsorship tier (0 = no tier).
// These are fallback defaults; the active event config should override via events_cfg_json.
level_guest_max_li: {
0: 0,
1: 4,
2: 8,
3: 8,
4: 8,
5: 8,
6: 16,
7: 16
},
show_edit__sponsorship_obj: false,
show_list__sponsorship_obj_li: true,
show_view__sponsorship_obj: false,
show_question__accommodations: false,
submit_status: null // 'saving', 'created', 'updated'
}
// testing: {},
}
};
export const ae_loc: Writable<key_val> = persisted(
'ae_loc',
ae_app_local_data_defaults
);
// *** BEGIN *** Temporary app data. This should be stored to session storage.
const ae_app_session_data_defaults: key_val = {
// Deployment stamp — compared against ae_loc.ver in +layout.svelte.
// When these differ, the app reloads to flush stale persisted data.
ver: ver,
log_lvl: 0,
disable_sys_header: false,
disable_sys_nav: false,
disable_sys_footer: false,
sys_menu: {
expand: false,
focus_passcode_input: false
},
debug_menu: {
hide_quick_info: true
},
app_cfg: {
show_element__passcode_input: true
},
ds: {
submit_status: null
},
ds_loaded: {
hub__site__root_page_header: false,
hub__site__root_page_content: false,
hub__site__root_page_footer: false
},
files: {
disable_submit__hosted_file_obj: false,
processed_file_kv: {},
uploaded_file_kv: {},
video_clip_file_kv: {},
processed_file_list: [],
status__file_list: null,
video_clip_file_list: [],
submit_status: null,
clip_complete: null
},
hub: {
show_xyz: null,
account_id_qry_status: null,
event_badge_id_status_qry__search: null,
event_presenter_id_qry_status: null,
site_domain_id_qry_status: null,
sponsorship_id_qry_status: null,
sponsorship_cfg_id_qry_status: null,
qr: {}
},
mod: {
archives: {},
events: {},
journals: {},
posts: {},
sponsorships: {
disable_submit__sponsorship_obj: false,
slct__level_num: 0,
show_question__accommodations: false,
submit_status: null // 'saving', 'created', 'updated', 'saved'
},
testing: {}
},
person: {
show_report__person_li: false,
qry_limit__people: 100
},
show__modal_change_password: false,
download: {},
// Per-file progress keyed by file_id (downloads) or temp_id (uploads).
// Shape: { status, endpoint, filename, size_total, size_loaded, percent_completed }
api_download_kv: {},
api_upload_kv: {},
test: true
};
export const ae_sess = writable(ae_app_session_data_defaults);
export const ae_api_data_struct: key_val = {
ver: '2024-08-11_11',
fqdn: api_server_fqdn,
base_url: api_base_url,
base_url_bak: api_base_url_bak,
api_secret_key: api_secret_key,
api_secret_key_bak: api_secret_key,
api_crud_super_key: api_crud_super_key, // Used for operations that bypass normal account-scoped CRUD limits.
headers: {},
account_id: ae_account_id
};
const ae_api_headers: key_val = {};
ae_api_headers['Content-Type'] = 'application/json';
ae_api_headers['x-aether-api-key'] = ae_api_data_struct.api_secret_key;
if (ae_account_id) {
ae_api_headers['x-account-id'] = ae_account_id;
}
if (ae_no_account_id) {
ae_api_headers['x-no-account-id'] = ae_no_account_id;
}
ae_api_data_struct['headers'] = ae_api_headers;
export const ae_api = writable(ae_api_data_struct);
// General-purpose reactive trigger. Set any truthy value to signal consumers to re-query.
const ae_trig_template: key_val = {};
export const ae_trig = writable(ae_trig_template);
/* *** BEGIN *** Initialize slct and slct_trigger */
/* The slct and slct_trigger variable should not be stored in local storage. Only use session storage because browser tabs can be open to different accounts, events, sponsorships, etc. */
// Intended for temporary session storage.
// Updated 2024-03-15
const slct_obj_template: key_val = {
account_id: ae_account_id,
account_obj: {},
event_id: null,
event_obj: {},
event_obj_li: [],
event_presentation_id: null,
event_presentation_obj: {},
event_presentation_obj_li: [],
event_presenter_id: null,
event_presenter_obj: {},
event_presenter_obj_li: [],
event_session_id: null,
event_session_obj: {},
event_session_obj_li: [],
sponsorship_id: null,
sponsorship_obj: {},
sponsorship_obj_li: [],
sponsorship_cfg_id: null,
sponsorship_cfg_obj: {},
sponsorship_cfg_obj_li: [],
post_id: null,
post_obj: {},
post_obj_li: [],
post_comment_id: null,
post_comment_obj: {},
post_comment_obj_li: []
};
// In-memory only (not persisted) — separate browser tabs can have different selections.
export const slct = writable(slct_obj_template);
// Increment (or set any truthy value) to tell subscribers that the selection changed
// and they should re-query their data.
export const slct_trigger: any = writable(null);
// Auth error signal — set by API helpers on 401/403 to trigger the session-expired banner in the root layout.
// Only set from browser context (never SSR). 'expired' covers both 401 and 403 responses.
export const ae_auth_error = writable<{
type: 'expired' | null;
ts: number | null;
}>({ type: null, ts: null });
// Reactive clock — updates every second. Used for live countdown and elapsed-time displays.
export const time = readable(new Date(), function start(set) {
const interval = setInterval(() => {
set(new Date());
}, 1000);
return function stop() {
clearInterval(interval);
};
});