A fullβstack inventory and purchase ordering portal for Mardens stores. It enables store users to browse catalog categories and products, build carts, submit orders, and track fulfillment. Admin users manage users, stores, categories, products, and can disable/enable accounts.
Tech Stack: TAVRN (TypeScript + TailwindCSS + Actix + Vite + React + NextUI/HeroUI) with a Rust (Actix-web + SQLx) backend and a React 18 SPA frontend.
- Features
- Architecture Overview
- Directory Structure
- Data Model / Database Schema
- Authentication & Authorization
- Disabled User Workflow
- Password Reset & New User Onboarding Flow
- Email Service
- Environment Variables
- Local Development
- Build & Deployment
- Available NPM Scripts
- API Overview
- Frontend Overview
- Image & File Handling
- Security Notes & Hardening TODOs
- Testing
- Troubleshooting
- Screenshots
- Contributing
- License
- Attribution / Notes
- Store user login with JWT + refresh tokens
- Role-based authorization (admin vs. store)
- Category & product management (with images, stock, price)
- Order creation, itemized order lines, status tracking (Pending β Shipped β Delivered)
- Soft disabling of users with optional expiration and reason
- Password reset and new user password setup via emailed one-time token (1-hour validity)
- Self-service forgot password flow
- Admin user CRUD (create without a password β email setup link)
- Store records management
+-------------------+ +-----------------------+
| React (Vite SPA) | <----> | Actix-Web (Rust API) |
| HeroUI / Tailwind| | Auth, Orders, Products|
+-------------------+ +-----------+-----------+
| |
| (Static assets / Dev Proxy) |
v v
Vite Dev Server (dev) MySQL (SQLx)
Backend responsibilities:
- HTTP API under
/api/* - Authentication (JWT issuance & verification; refresh flow)
- Table bootstrapping (auto-creates tables at startup if absent)
- Business logic for orders, products, categories, stores, users
- Email dispatch for password workflows
- Periodic cleanup of expired disabled-user records
Frontend responsibilities:
- SPA routing (React Router v7) & protected routes
- UI for browsing, ordering, and admin management
- Token storage & refresh handling (via AuthProvider)
root
ββ src/ # Frontend (React + TS + Vite)
β ββ components/ # Reusable UI components & modals
β ββ pages/ # Route-level pages
β ββ providers/ # Context providers (Auth, Cart, Layout)
β ββ utils/ # API helper, types, image resizing
β ββ css/ # Tailwind & 3rd party styles
ββ src-actix/ # Rust backend crate
β ββ auth/ # Auth modules (JWT, endpoints, email, disabled users)
β ββ categories/
β ββ products/
β ββ orders/
β ββ stores/
β ββ upload.rs # File upload / image endpoint(s)
β ββ asset_endpoint.rs # Version / asset helpers
β ββ lib.rs # Server assembly & startup
ββ openapi.json # (If maintained) spec stub / future docs
ββ Cargo.toml / Cargo.lock
ββ package.json / pnpm-lock.yaml
ββ vite.config.ts / tailwind.config.js / tsconfig.json
ββ README.md
Tables are auto-created (idempotently) at startup via SQL executed in each module. (Assumes a MySQL-compatible database.)
| Table | Purpose | Key Columns / Notes |
|---|---|---|
stores |
Physical store locations | id, city, address |
users |
Application users | email (unique), `role (admin |
disabled_users |
Tracks disabled state (soft lockout) | user_id PK, reason, expiration? |
password_reset_tokens |
One-time password/setup tokens | token (UUID), expires_at, used |
categories |
Product taxonomy | Self-referencing parent_id, is_active, sort_order |
products |
Items available for ordering | sku (unique), category_id, price, stock_quantity, in_stock |
orders |
Store purchase orders | order_number (unique), user_id, store_id, status (ENUM) |
order_items |
Line items per order | order_id, product_id, quantity, unit_price, total_price |
Status enums:
- Orders:
PENDING,SHIPPED,DELIVERED
Automatic behaviors:
- Stock decremented on order creation (with
in_stockrecalculated). - Disabled user cleanup task runs periodically (expired rows removed).
- Password reset tokens are invalidated when reused or expired.
Flow:
- User submits email/password to
/api/auth/login. - Backend verifies credentials (bcrypt) & domain restriction (
@mardens.com). - Issues: (a) Access JWT (24h) + (b) Refresh token (30d) β both signed HS256.
- Access token required in
Authorization: Bearer <token>for protected endpoints. /api/auth/refreshexchanges a valid refresh token for fresh access and refresh tokens.- Middleware (
jwt_validator) injects decoded claims into request extensions. - Admin-only endpoints check
claims.role == "admin".
Claims include:
sub: user id (u64)
email: user email
role: "admin" | "store"
store_id: optional store reference
exp / iat: expiry & issued-at
ID Obfuscation: Public responses encode numeric IDs via serde_hash::hashids to avoid exposing raw sequential integers.
- Admin can disable a user (
/api/auth/admin/disable-user) with reason and optional expiration. - Middleware blocks disabled users early, returning 403 JSON with reason and expiration.
- Re-enable:
/api/auth/admin/enable-user/{user_id}deletes the disabled row. - Background task purges expired disabled records every 24h.
- Admin creates a user without a password β temp random password hashed β reset token created.
- Email sent with one-time setup link (
/reset-password?token=...). - Token validity: single-use, 1 hour.
- Forgot password uses the same token creation and email template (existing-user variant).
- After successful password change token marked
used = TRUE.
Located in src-actix/auth/email_service.rs using lettre with STARTTLS (Office 365). Currently, credentials & SMTP host are hard-coded (development placeholder). This must be externalized to environment variables before production.
Recommended variables:
SMTP_HOST=smtp.office365.com
SMTP_USERNAME=...
SMTP_PASSWORD=...
SMTP_FROM=store-portal@mardens.com
PUBLIC_BASE_URL=https://store-orders.mardens.com
DEV_BASE_URL=http://127.0.0.1:1422
Below is a consolidated list (some inferred; see NOTE). Create a .env (and do NOT commit secrets).
# --- JWT Secrets (remove hard-coded fallbacks from jwt.rs for production) ---
JWT_ACCESS_SECRET=replace_with_strong_random
JWT_REFRESH_SECRET=replace_with_strong_random_refresh
# --- Email / SMTP ---
SMTP_HOST=smtp.office365.com
SMTP_USERNAME=YOUR_USER@domain.com
SMTP_PASSWORD=super_secret
SMTP_FROM=Store Orders Portal <no-reply@domain.com>
# --- App / Server ---
APP_PORT=1422
RUST_LOG=info,store_orders=debug
NODE_ENV=development
PUBLIC_BASE_URL=http://127.0.0.1:1422
NOTE: The database_common_lib crate determines exact variable names; adjust if its documentation differs.
See .env.example for a templated version.
Prerequisites:
- Rust (stable >= 1.78) & Cargo
- Node.js (>= 20) + npm / pnpm
- MySQL 8.x (or compatible, e.g., MariaDB 10.6+)
- (Optional) WSL2 for Windows builds (script uses
wsl bash -icinbuild-api)
Steps:
- Clone repo
- Copy
.env.exampleto.env& fill secrets - Create database:
CREATE DATABASE stores CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - Install frontend deps:
npm install - Run combined dev experience:
- Start backend:
cargo run --bin store_orders - In another shell, start Vite (if not auto-started by backend):
npm run dev
- Start backend:
- Visit
http://127.0.0.1:1422(backend proxies/serves SPA)
The backend in debug mode spawns a Vite dev server (see lib.rs), enabling HMR.
Production build (Rust release and frontend bundle):
npm run build
This runs:
build-frontend:tsc && vite build-> outputs todist(then integrated / served)build-api: Cargo release build
Binary output: target/release/store_orders
Deployment considerations:
- Set real JWT secrets and remove development early
return Ok(b"...")overrides injwt.rs. - Externalize SMTP credentials.
- Serve over HTTPS (reverse proxy: Nginx / Traefik) and restrict direct DB access.
- Run with systemd or container orchestrator.
Example systemd unit (snippet):
[Service]
EnvironmentFile=/etc/store-orders.env
ExecStart=/opt/store-orders/store_orders
Restart=on-failure
| Script | Action |
|---|---|
dev |
Run Vite dev server (frontend) |
build-frontend |
Type-check then build SPA bundle |
build-api |
Build Rust backend (release) via WSL helper on Windows |
build |
Frontend build then backend build |
run-api |
Run backend in debug (uses dev features) |
lint |
ESLint on TS/TSX |
publish |
Custom deploy helper (copies binary + assets to remote host) |
All endpoints prefixed with /api. Highlights:
Auth:
- POST
/api/auth/loginβ email/password -> tokens - POST
/api/auth/refreshβ refresh token -> new pair - GET
/api/auth/meβ current user profile - POST
/api/auth/forgot-password,/api/auth/reset-password - Admin: create/update/delete users, disable/enable, list disabled
Users:
- GET
/api/auth/users(admin) - PUT
/api/auth/users/{id}(admin) - DELETE
/api/auth/users/{id}(admin)
Categories:
- CRUD endpoints under
/api/categories/*
Products:
- CRUD endpoints under
/api/products/* - Image upload via
/api/upload(seeupload.rs)
Orders:
- Create/list per user / per store
- Status update workflow
Stores:
- CRUD for store locations (admin)
See openapi.json (stub) β keeping it updated is recommended (consider integrating utoipa or okapi crate for Rust-generated OpenAPI in future).
- React Router v7 for routing (see
src/components/routing/& pages) - Context Providers (
AuthProvider,CartProvider,LayoutProvider) - ProtectedRoute & role-based gating (
RequireRole.tsx) - UI Library: HeroUI/NextUI
- TailwindCSS for utility styling (configured in
tailwind.config.js) - Error boundary for top-level component resilience
Token Handling Strategy:
- Access & refresh tokens stored (implementation detail: ensure no XSS leaks; consider using httpOnly cookies in future).
- Automatic refresh on expiration path.
- Product images stored in
products/directory (created if absent) and exposed viaGET /products/<filename>(static file service) β no directory listing. - Upload endpoint saves file then returns URL path; ensure file extension validation / size limits if expanding (currently not fully describedβreview
upload.rs).
Rust tests:
- JWT round-trip tests in
auth/jwt.rs - Email sending test (integration-style) in
auth/email_service.rs(will attempt real SMTP β disable or mock in CI)
Recommended additions:
- Add unit tests for order creation & stock adjustment
- Add integration tests with an ephemeral MySQL (Docker) &
sqlx::migrate!()if migrations adopted - Frontend: add React Testing Library tests for auth flows & forms
Running (Rust):
cargo test
Running (lint frontend):
npm run lint
| Issue | Possible Cause | Fix |
|---|---|---|
| Cannot log in | DB not reachable | Verify DB env vars & network |
| 403 after login | User disabled | Check /api/auth/admin/disabled-users |
| Password reset email not received | SMTP blocked / creds invalid | Verify SMTP env & logs |
| Vite assets not loading in prod | Build not run / wrong static path | Run npm run build & ensure Actix serves wwwroot/dist |
| Orders show wrong stock | Concurrency race | Add transactional stock validation |
Logs: Controlled by RUST_LOG & pretty_env_logger (timestamps suppressed for readability). Increase to debug for detailed traces.
- Create a feature branch.
- Add/Update tests where applicable.
- Run lint & tests (
npm run lint,cargo test). - Open PR with clear summary & any schema changes documented.
GPL-3.0-or-later
Internal use for Mardens. Remove hard-coded secrets before any external distribution.





