YUSEN LIMO Warehouse is a static-deployment prototype — every page is a plain HTML file, all logic runs in the browser, and every byte of data lives inDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/raceliciouss/YUSEN-LIMO-WAREHOUSE/llms.txt
Use this file to discover all available pages before exploring further.
localStorage. This architecture makes the system trivially deployable (open index.html, and it works), but it is not suitable for a production environment with multiple simultaneous users, auditable records, or real authorization boundaries. This guide describes the current constraints and maps out a concrete migration path to a backend-backed system that retains the existing UI.
Current Limitations
Recommended Database Tables
The table below maps eachlocalStorage key to its natural relational equivalent. Column names follow snake_case convention appropriate for PostgreSQL or MySQL; adjust as needed for your chosen database.
| Table | Maps From | Key Fields |
|---|---|---|
users | warehouseAuthAccounts | id, username, password_hash, role, status, employee_id, name, created_at, last_login_at |
sessions | warehouseAuthSession | token, user_id, created_at, expires_at |
shipments | warehouseShipments | id, hawb, mawb, client, destination, transaction_type, location, entry_type, qty_in, qty_out, saved_at, created_by_user_id |
profiles | warehouseProfile | user_id, first_name, last_name, email, phone, position |
notifications | warehouseNotificationState | id, user_id, type, title, description, meta, time, read |
shipments table intentionally mirrors the flat structure of warehouseShipments — one row per transaction event — so that aggregateShipmentsByReference() logic can be implemented as a SQL GROUP BY on hawb or mawb with SUM(qty_in) and SUM(qty_out).
Migration Steps
Set up a backend API with authentication endpoints
Choose a backend framework (Node.js/Express, Python/FastAPI, Go/Gin, etc.) and create a server that the static HTML pages can reach. At minimum, expose:
POST /auth/login— validate credentials, return a signed JWT or set aHttpOnlysession cookiePOST /auth/logout— invalidate the session tokenGET /auth/me— return the authenticated user’s profile from theuserstable
auth.js wraps all localStorage access in an IIFE with a getStorage() helper. Replace getStorage() to return a shim that delegates to fetch() rather than localStorage, or rewrite the three auth functions (login, logout, getSession) as async functions backed by API calls.Replace auth.js Local Storage reads and writes with API calls
In
The login page (
assets/auth.js, the constants AUTH_STORAGE_KEY = 'warehouseAuthAccounts' and SESSION_STORAGE_KEY = 'warehouseAuthSession' drive all account and session persistence. Replace the relevant read/write calls:| Current operation | Replacement |
|---|---|
storage.getItem(SESSION_STORAGE_KEY) | GET /auth/me |
storage.setItem(SESSION_STORAGE_KEY, ...) | Response body of POST /auth/login |
storage.removeItem(SESSION_STORAGE_KEY) | POST /auth/logout |
storage.getItem(AUTH_STORAGE_KEY) | GET /users (admin only) |
storage.setItem(AUTH_STORAGE_KEY, ...) | POST /users, PATCH /users/:id, DELETE /users/:id |
assets/login.js) reads the result of auth.login() synchronously today. You will need to make it await the network call and show a loading state during the round trip.Replace getStoredShipments() and saveStoredShipments() with API calls
These two functions in
The
assets/app.js are the only points of contact between the UI and the shipment data store. Swapping them is sufficient to migrate all shipment persistence:| Current call | Replacement |
|---|---|
getStoredShipments() | await fetch('/api/shipments').then(r => r.json()) |
saveStoredShipments(shipments) | await fetch('/api/shipments', { method: 'POST', body: JSON.stringify(newRecord) }) |
saveStoredShipments() function currently accepts the entire array and overwrites the key. On the backend, the equivalent is a POST /shipments endpoint that appends a single record. Callers in app.js that mutate the array (e.g. delete a record) will need a corresponding DELETE /shipments/:id call.saveStoredShipments() also dispatches a warehouse:data-updated custom event on window after every write. Keep this dispatch in the API-backed version so downstream listeners (notification panel, dashboard refresh) continue to work without changes.Move aggregateShipmentsByReference() to the backend
Currently, the browser downloads every shipment record and aggregates in JavaScript. With hundreds or thousands of records this becomes slow. On the backend, implement the same grouping logic as a SQL query:Return the pre-aggregated rows from
GET /api/inventory. getInventoryData() and getActivityData() in app.js can then be simplified to a single fetch instead of calling aggregateShipmentsByReference(getStoredShipments()).Hash passwords and use signed session tokens
Replace plaintext password storage with
bcrypt (or argon2):- On account creation (
POST /users), hash the submitted password before inserting to theuserstable. Never store the raw value. - On login (
POST /auth/login), usebcrypt.compare()to verify the submitted password against the stored hash. - Issue a signed JWT (or a server-managed session token stored in the
sessionstable) and return it to the client. Store only the token inlocalStorageor, preferably, in aHttpOnlycookie so it is inaccessible to JavaScript. - Remove
warehouseAuthAccountsfromlocalStorageentirely — the browser no longer needs the account list for any auth operation.
Enforce role-based authorization on every API endpoint
The current system checks
currentUser.role in app.js to show or hide UI elements. This is a presentation-layer guard only — anyone who can run JavaScript in the browser can bypass it. On the backend, every endpoint must independently verify the caller’s role:- Admin-only endpoints (
POST /users,PATCH /users/:id,DELETE /users/:id,GET /users) must reject requests fromrole: "employee"with a403 Forbiddenresponse. - Shipment write endpoints should require at minimum a valid authenticated session.
- The client-side role checks in
app.jscan remain as a UX convenience (hiding buttons the user cannot use), but they must no longer be the only authorization boundary.
Store only HAWB or MAWB identifiers in QR codes
Replace the current QR payload (a full JSON shipment object) with a plain identifier string:When the QR scanner reads this string, the app calls
GET /api/shipments?hawb=HAWB-20240601-001 to retrieve the current record from the backend and pre-fill the form. This eliminates stale data in printed QR codes and prevents sensitive cargo details from being embedded in publicly scannable images. The scanner already handles plain string values alongside JSON objects — the parsing path for non-JSON input in app.js can be kept and wired to the lookup call.