Files
doormilxpress_astryx/src/api/base44Client.js
2026-08-20 18:18:10 +05:30

166 lines
5.4 KiB
JavaScript

/**
* Application data client.
*
* The reference app talks to a Base44 backend through this module. The demo
* keeps the module path, the export name, and the full method contract, and
* swaps the transport for the local store in `store.js` and the local AI engine
* in `aiEngine.js`. Nothing downstream — hooks, pages, components — knows or
* cares, which is exactly the point: the seam stays where it was.
*/
import { createEntity, initStore, resetStore } from './store';
import { invokeLLM, uploadFile } from './aiEngine';
import { DEMO_USER, seedData } from './seed';
initStore(seedData);
const ENTITY_NAMES = [
'JobPosting', 'JobApplication', 'AIInterview', 'Staff', 'WorkerProfile',
'Course', 'Badge', 'LearningPath', 'Certification', 'RoleCategory',
'UserActivity', 'Evidence', 'User',
/* Who is on which position, and for how long. The record that turns "hired"
into workforce allocation: without it a position knows its demand and its
applicants but not who is actually covering it. */
'Assignment',
/* Shifts worked, missed and overrun. The operational record behind
attendance and overtime analysis — see `api/attendanceSeed.js`. */
'ShiftRecord',
];
const entities = Object.fromEntries(
ENTITY_NAMES.map((name) => [name, createEntity(name)])
);
/* ── Auth ──────────────────────────────────────────────────────────────── */
const SESSION_KEY = 'krow_demo_user';
function loadUser() {
try {
const raw = localStorage.getItem(SESSION_KEY);
return raw ? { ...DEMO_USER, ...JSON.parse(raw) } : { ...DEMO_USER };
} catch {
return { ...DEMO_USER };
}
}
let currentUser = loadUser();
/**
* Writes the session user, and says whether it actually landed.
*
* The old version was `try { setItem } catch {}` — a swallowed
* `QuotaExceededError` or a private-browsing refusal, and the caller was handed
* a user object indistinguishable from a successful write. For preferences that
* is invisible; for `customSkills`, which is where every account-authored skill
* definition lives, it is the whole "I saved it and it was gone" report: the
* toast said added, the list showed it, the reload did not.
*
* The read-back matters as much as the catch. A write can be accepted and then
* evicted, and a serialisation can land truncated; comparing what came back
* with what went in is the only way to know the record is really there.
*/
function persistUser() {
const payload = JSON.stringify(currentUser);
try {
localStorage.setItem(SESSION_KEY, payload);
} catch (error) {
return { persisted: false, error };
}
try {
if (localStorage.getItem(SESSION_KEY) !== payload) {
return { persisted: false, error: new Error('The session record did not survive the write.') };
}
} catch (error) {
return { persisted: false, error };
}
return { persisted: true, error: null };
}
const auth = {
/** The demo is always signed in as the seeded employer/admin user. */
async me() {
return { ...currentUser };
},
async updateMe(patch) {
currentUser = { ...currentUser, ...patch };
persistUser();
return { ...currentUser };
},
/**
* Preferences, read synchronously.
*
* `me()` is async because the real client fetches, but the panel provider has
* to decide whether Owliver starts open during its first render — one tick
* later is a visible flash of the wrong workspace. The session user is already
* hydrated from localStorage at module load, so this is a plain read of the
* same record `me()` returns, not a second copy of the state.
*/
preferences() {
return { ...DEMO_USER.preferences, ...(currentUser.preferences || {}) };
},
/**
* Merges into the stored preferences and persists with the rest of the user.
*
* Returns the write's outcome alongside the record, rather than the record
* alone. Preferences are where account-authored skills live, so "did this
* survive the reload" is a question the caller has to be able to answer —
* see `persistUser`.
*/
async updatePreferences(patch) {
currentUser = { ...currentUser, preferences: { ...auth.preferences(), ...patch } };
const write = persistUser();
return { user: { ...currentUser }, ...write };
},
isAuthenticated() {
return true;
},
/**
* There is no identity provider to sign out of, so this clears the local
* session and returns to the requested page.
*/
logout(redirectTo = '/') {
try {
localStorage.removeItem(SESSION_KEY);
} catch {
// Ignore.
}
currentUser = { ...DEMO_USER };
window.location.href = typeof redirectTo === 'string' ? redirectTo : '/';
},
redirectToLogin() {
window.location.href = '/';
},
};
/* ── Integrations & analytics ───────────────────────────────────────────── */
const integrations = {
Core: {
InvokeLLM: invokeLLM,
UploadFile: uploadFile,
},
};
const analytics = {
track({ eventName, properties } = {}) {
if (import.meta.env.DEV) {
console.debug('[krow-demo] analytics', eventName, properties || {});
}
},
};
export const base44 = { entities, auth, integrations, analytics };
/** Restores the shipped demo data, discarding local edits. */
export function resetDemoData() {
resetStore(seedData);
window.location.reload();
}