backend integration started
This commit is contained in:
@@ -6,7 +6,6 @@
|
||||
* changes. Nothing else in the app calls `fetch`.
|
||||
*/
|
||||
|
||||
import { demoResolve, isDemoActive, MISS } from '@/demo';
|
||||
import type { FiestaEnvelope } from './types';
|
||||
|
||||
/**
|
||||
@@ -16,9 +15,34 @@ import type { FiestaEnvelope } from './types';
|
||||
*/
|
||||
const API_BASE = import.meta.env['VITE_API_BASE'] ?? '/fiesta';
|
||||
|
||||
/** Every console route lives under this prefix. `/mob/*` and `/pos/*` differ. */
|
||||
/** Every console route lives under this prefix. */
|
||||
export const WEB = '/live/api/v1/web';
|
||||
export const POS = '/live/api/v1/pos';
|
||||
|
||||
/**
|
||||
* The console's POS reads — counter sales and till presence.
|
||||
*
|
||||
* `/web/pos`, NOT `/pos`. Those are two different doors and the difference is
|
||||
* deliberate on the backend's side (`posroutes.go`): everything under `/v1/pos`
|
||||
* sits behind `middleware.PosAuth`, which verifies a TERMINAL's session token.
|
||||
* The console has no such token and cannot obtain one — `/pos/login` refuses an
|
||||
* account that is not a till account, which is the separation working as
|
||||
* intended.
|
||||
*
|
||||
* That guard currently waves unauthenticated requests through, so calling the
|
||||
* terminal group appeared to work. The routes file says what happens next in as
|
||||
* many words: "the moment `POS_AUTH_REQUIRED=true` is set, every POS screen in
|
||||
* the back office goes dark." The same five reads are registered again under
|
||||
* `/v1/web/pos` for exactly this caller, and that is where they belong.
|
||||
*/
|
||||
export const POS = '/live/api/v1/web/pos';
|
||||
|
||||
/**
|
||||
* The mobile surface, for the two endpoints the web group does not carry.
|
||||
*
|
||||
* Not a preference — `tenants/getstaffs` is registered on `/v1/mob/tenants`
|
||||
* only (`tenantroutes.go:35`), so the web path 404s.
|
||||
*/
|
||||
export const MOB = '/live/api/v1/mob';
|
||||
|
||||
/**
|
||||
* A failed call, carrying the backend's own message.
|
||||
@@ -73,17 +97,12 @@ interface RequestOptions {
|
||||
async function request<T>(path: string, options: RequestOptions = {}): Promise<T> {
|
||||
const { method = 'GET', params, body, signal } = options;
|
||||
|
||||
// Demo mode short-circuits before any network call. In a production build
|
||||
// `isDemoActive` is a constant `false`, so the bundler removes this branch
|
||||
// and the fixtures with it.
|
||||
if (import.meta.env.DEV && isDemoActive()) {
|
||||
const fixture = await demoResolve(path, params as Record<string, unknown> | undefined);
|
||||
if (fixture !== MISS) {
|
||||
// A beat of latency, so loading states are visible while working on them.
|
||||
await new Promise((resolve) => setTimeout(resolve, 180));
|
||||
return fixture as T;
|
||||
}
|
||||
}
|
||||
// There is exactly one path out of this function and it goes to `fetch`.
|
||||
//
|
||||
// A fixture short-circuit used to sit here, gated on a sessionStorage flag.
|
||||
// It is gone: every screen in every workspace now shows what the API
|
||||
// returned or an error, and there is no longer a mode in which the console
|
||||
// shows something else convincingly.
|
||||
|
||||
const url = `${API_BASE}${path}${toQueryString(params)}`;
|
||||
|
||||
@@ -128,7 +147,11 @@ async function request<T>(path: string, options: RequestOptions = {}): Promise<T
|
||||
);
|
||||
}
|
||||
|
||||
return envelope.details as T;
|
||||
// Most handlers put the payload in `details`, but a handful answer with
|
||||
// `data` instead — `products/getallproducts` and `products/create` among the
|
||||
// ones the console calls (`productController.go:400,206`). Reading only
|
||||
// `details` handed those two callers `undefined` with no error anywhere.
|
||||
return (envelope.details ?? envelope.data) as T;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -166,6 +189,26 @@ export const api = {
|
||||
get: <T>(path: string, params?: Record<string, QueryValue>, signal?: AbortSignal) =>
|
||||
request<T>(path, { method: 'GET', params, signal }),
|
||||
|
||||
/**
|
||||
* A read that returns rows.
|
||||
*
|
||||
* Fiesta answers an empty result with `details: null` about as often as with
|
||||
* `[]` — `Scan` into a nil slice marshals as null, and which one you get
|
||||
* depends on the handler rather than on anything meaningful. A page that maps
|
||||
* over the answer then dies on a white screen, and it dies for the most
|
||||
* ordinary case there is: a tenant with no branches yet, a shop with no
|
||||
* customers.
|
||||
*
|
||||
* So the coercion happens once, here, rather than as `?? []` on forty call
|
||||
* sites where the one that gets forgotten is the one that breaks. A non-array
|
||||
* answer is treated as empty rather than thrown, because the alternative is
|
||||
* an error screen for what is usually "nothing yet".
|
||||
*/
|
||||
list: <T>(path: string, params?: Record<string, QueryValue>, signal?: AbortSignal) =>
|
||||
request<T[] | null>(path, { method: 'GET', params, signal }).then((rows) =>
|
||||
Array.isArray(rows) ? rows : [],
|
||||
),
|
||||
|
||||
post: <T>(path: string, body?: unknown, params?: Record<string, QueryValue>) =>
|
||||
request<T>(path, { method: 'POST', body, params }),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user