Skip to main content

Documentation 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.

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 in 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

The following limitations are structural properties of the current Local Storage design and cannot be patched within the client-side architecture. They require a backend to resolve.
  • Local Storage is not a database. There are no transactions, no referential integrity, no concurrent-write protection, and no automatic backup. The storage quota is browser-enforced at roughly 5 MB per origin. A single large bulk import can exhaust the quota, at which point saveStoredShipments() silently falls back to an in-memory store (inMemoryShipmentStore) that is lost on page reload.
  • Plaintext passwords are stored in browser storage. warehouseAuthAccounts persists raw password strings to localStorage. Any script with access to the page origin — including browser extensions — can read them. There is no hashing or salting.
  • Authentication is client-side only. AUTH_STORAGE_KEY and SESSION_STORAGE_KEY in auth.js are localStorage entries. A user can manually write a session object and gain access to any role. There is no cryptographic session token and no server that validates identity.
  • No server-side validation or duplicate detection. The inbound and outbound forms perform client-side field checks only. Two browser tabs can save conflicting records simultaneously with no conflict resolution. Duplicate HAWB entries are silently aggregated rather than rejected.
  • QR codes may carry full shipment payloads. The current QR scanner accepts a full JSON shipment object encoded in the QR image. This means sensitive cargo details (client name, quantities, routing) are embedded in a scannable code that anyone with a camera can read. Identifiers only — HAWB or MAWB — should be encoded instead.

The table below maps each localStorage key to its natural relational equivalent. Column names follow snake_case convention appropriate for PostgreSQL or MySQL; adjust as needed for your chosen database.
TableMaps FromKey Fields
userswarehouseAuthAccountsid, username, password_hash, role, status, employee_id, name, created_at, last_login_at
sessionswarehouseAuthSessiontoken, user_id, created_at, expires_at
shipmentswarehouseShipmentsid, hawb, mawb, client, destination, transaction_type, location, entry_type, qty_in, qty_out, saved_at, created_by_user_id
profileswarehouseProfileuser_id, first_name, last_name, email, phone, position
notificationswarehouseNotificationStateid, user_id, type, title, description, meta, time, read
The 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

1

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 a HttpOnly session cookie
  • POST /auth/logout — invalidate the session token
  • GET /auth/me — return the authenticated user’s profile from the users table
The existing 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.
2

Replace auth.js Local Storage reads and writes with API calls

In 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 operationReplacement
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
The login page (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.
3

Replace getStoredShipments() and saveStoredShipments() with API calls

These two functions in 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 callReplacement
getStoredShipments()await fetch('/api/shipments').then(r => r.json())
saveStoredShipments(shipments)await fetch('/api/shipments', { method: 'POST', body: JSON.stringify(newRecord) })
The 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.
4

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:
SELECT
  COALESCE(NULLIF(hawb, ''), mawb) AS reference,
  hawb, mawb, client, destination, location, transaction_type,
  SUM(CASE WHEN entry_type = 'inbound'  THEN qty_in  ELSE 0 END) AS qty_in_value,
  SUM(CASE WHEN entry_type = 'outbound' THEN qty_out ELSE 0 END) AS qty_out_value,
  SUM(CASE WHEN entry_type = 'inbound'  THEN qty_in  ELSE 0 END)
    - SUM(CASE WHEN entry_type = 'outbound' THEN qty_out ELSE 0 END) AS remaining_value
FROM shipments
GROUP BY COALESCE(NULLIF(hawb, ''), mawb), hawb, mawb, client, destination, location, transaction_type;
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()).
5

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 the users table. Never store the raw value.
  • On login (POST /auth/login), use bcrypt.compare() to verify the submitted password against the stored hash.
  • Issue a signed JWT (or a server-managed session token stored in the sessions table) and return it to the client. Store only the token in localStorage or, preferably, in a HttpOnly cookie so it is inaccessible to JavaScript.
  • Remove warehouseAuthAccounts from localStorage entirely — the browser no longer needs the account list for any auth operation.
6

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 from role: "employee" with a 403 Forbidden response.
  • Shipment write endpoints should require at minimum a valid authenticated session.
  • The client-side role checks in app.js can remain as a UX convenience (hiding buttons the user cannot use), but they must no longer be the only authorization boundary.
7

Store only HAWB or MAWB identifiers in QR codes

Replace the current QR payload (a full JSON shipment object) with a plain identifier string:
HAWB-20240601-001
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.

You do not need to rewrite the UI to complete this migration. All rendering, filtering, sorting, modal, drawer, and form interaction logic in assets/app.js operates on plain JavaScript objects. As long as getStoredShipments() returns an array of objects in the same shape as the Shipment Record schema, every table, chart, and filter will continue to work without changes. The migration is entirely a data-access layer swap: replace localStorage.getItem / localStorage.setItem calls with fetch() calls, keep the object shapes consistent, and the 5000+ lines of UI code in app.js carry over unchanged.

Build docs developers (and LLMs) love