Replace the old touchmark/PNG identity with the supplied lockup and monogram, segmented role-aware navigation, Live/Draft listing filters with generation-based cache invalidation, and admin Projects covering immutable file revisions, listing live pointers, and exact-decimal filament/packaging/shipping costing. Additive schema only; no Etsy publication. Isolated clone tests: 301 tests, 1162 assertions.
9.6 KiB
HickLab3D dashboard
Laravel 13, Tailwind CSS 4, and MySQL dashboard for the HickLab3D workshop. It includes user accounts, Etsy shop data, BamBuddy printer status, Parcel2Go orders, and filament dryer control.
Setup
Requirements: PHP 8.3+, Composer, Node.js, and MySQL. Point the web server document root at public/.
composer install
npm install
cp .env.example .env
php artisan key:generate
On Windows PowerShell, use Copy-Item .env.example .env in place of cp .env.example .env.
Set DB_* in .env to your MySQL database, then run:
php artisan migrate
php artisan app:create-admin your-username you@example.com
npm run build
php artisan serve
The admin command prompts for a password. Never pass it on the command line. For local front-end development, use npm run dev alongside php artisan serve.
The migrations use the legacy table and column names. If you point the app at the existing HickLab3D database, back it up first. The migrations skip tables already present and create Laravel's session, cache, and job tables. Existing schema variations have not been verified against a database dump. Do not run migrate:fresh on a database with production data.
Integrations
- Enter Etsy, Parcel2Go, BamBuddy, and dryer connection details on the admin Settings page. Saved values are encrypted in the database and take precedence over matching
.envvalues. - Existing
P2G_*,BAMBUDDY_*,DRYER_*, andETSY_*environment values remain available as fallbacks. Save each integration on the Settings page to import those values into the database. - Connect the Etsy shop with OAuth after saving its app credentials. The browser talks to authenticated Laravel routes for dryer status and controls.
- Runtime Etsy credentials remain Settings-page rows /
ETSY_*env (optional aliases:HL3D_ETSY_KEYSTRING,HL3D_ETSY_SECRET). Never put tokens in MCP config.
Etsy MCP
First-party stdio MCP for HL3D agents. It uses the stored HickLab3D shop connection — agents must not start a new OAuth dance.
php artisan etsy:mcp
Register the local .mcp.json only where PHP is installed and the working directory is this app. On the Hermes host, use the existing web container instead (after deployment):
hermes -p hl3d_teamlead mcp add etsy --command ssh --args -T H-NODE-05 'docker exec -i -u www-data -w /var/www/HickLab3D nginx-ui-php php artisan etsy:mcp --no-ansi'
This uses the managed live environment without placing credentials in MCP config and does not require host PHP. Configure positive tool allow-lists through supported hermes config set commands before exposing tools to agents: teamlead/designer should get read/status tools only; marketing may additionally create drafts. Do not expose publishing, shipment or inventory-write tools without separately authorising those operations. Disable MCP sampling for this server. Start a fresh profile session or reload MCP after configuration.
An admin must reconnect Etsy in the dashboard if status reports unknown or missing scopes (listings_r listings_w transactions_r transactions_w shops_r). Never request operator passwords or MFA codes. New listings are drafts; confirm=true is a technical publish guard, not operator authorisation. Publishing, activating, renewing or repricing live listings, inventory changes and shipping still require explicit approval for the specific action in the current conversation.
Artisan scheduler
The Settings page lets admins tune registered jobs or run them immediately. Defaults: integrations:refresh and alerts:scan every 30 seconds, etsy:sync every 15 minutes, and etsy:refresh-token checks every five minutes. Etsy is no longer force-refreshed by the bundled integration job. The refresh check rotates only within ten minutes of expiry; database row locking and a fresh read serialise rotating-token use across MCP, HTTP and scheduler processes. Lowering the token-check cadence or disabling it can leave an idle access token expired.
Settings are atomically replaced under a shared file lock at storage/app/dashboard-schedules.json. Preserve storage and the Infisical-managed .env during deployments. Only H-NODE-05 owns the systemd scheduler timer; its container working directory must be /var/www/HickLab3D, matching the actual vhost and CI target. Subminute tasks keep schedule:run alive until the end of the minute, so do not install a duplicate cron or scheduler elsewhere.
For local tests in the existing serviceengineering toolchain image, explicitly set APP_ENV=testing: the image exports APP_ENV=local, which otherwise wins over PHPUnit XML and correctly activates real CSRF enforcement. Never disable production CSRF to repair that test-environment mismatch.
Etsy Open API v3 has no conversation inbox (conversations_* scopes do not exist). Receipt notes are exposed; sending a buyer-inbox reply is not.
Projects (admin)
Projects are the product aggregate: a reversible internal lifecycle (Design, Ready, Live, Retired — distinct from Etsy's own listing state), approved immutable revisions with private STL/3MF/G-code file bundles, Etsy listing mappings verified by reading the listing from Etsy (never an arbitrary shop/listing ID), and append-only cost snapshots and releases.
- Uploads are limited to
.stl,.3mfand.gcode, 100 MiB per file, 250 MiB combined per request, and 10 files per request, and are structurally validated (STL triangle/size cross-check, bounded 3MF zip/XML with no path traversal, symlink entries, external references or DOCTYPE/ENTITY declarations, plain-text G-code). Nothing uploaded here is ever parsed as a slicer job, executed, or served from a public URL — files live on theproject_filesdisk (storage/app/project-files, not web-served) under a random generated key, streamed rather than buffered in memory, and only stream back through the authenticated, policy-checkedprojects.files.downloadroute. Any failure partway through a multi-file upload (validation or otherwise) deletes every byte already written for that request. - Releasing a revision to a listing mapping requires a cost snapshot for that same revision with every category costed (filament, packaging and shipping — even as an explicit
0, but not missing) and pins an exact revision + cost snapshot; uploading a newer revision never moves that pointer on its own, and both release and revert create a new append-onlyproject_releasesrow under an optimistic lock (project_listing_links.lock_version) rather than overwriting history. A detached listing mapping can no longer be released or reverted to. - Cost snapshots use fixed-point integer arithmetic (
App\Support\FixedPoint/App\Services\ProjectCosting), not floats: the bcmath/gmp PHP extensions are not installed in this project's runtime, so exact-decimal math is done by scaling decimal strings into bounded native integers with explicit overflow and storable-precision checks. Estimate and actual snapshots are both append-only and never overwrite each other; a missing cost category is stored asnull("not costed"), distinct from an intentional0.0000. Each category accepts exactly one quantity unit (filamentg, packagingunit, shippingparcel) so a mistyped unit is rejected rather than silently mis-costed. Snapshot currency must match the project (no implied FX). Shipping quantities are parcels;units_per_parcelallocates that parcel cost across the saleable batch yield (batch_units / units_per_parcel * parcel cost) independently ofperUnit(). Actual shipping needs a confirmed shipment or invoice reference. The form starts with separate filament, packaging and shipping lines and can add more materials/components; Fusion project/document/version fields on a revision are optional manual provenance only. - Stage can only move to Live once every non-detached listing mapping has an active release (not merely one of several); moving a project's internal stage never calls the Etsy API or publishes anything. Attaching a new, not-yet-released listing mapping to a project already at Live drops its stage back to Design rather than leaving Live claiming coverage it no longer has.
Branding
The sidebar/login logo, favicon and in-app colour tokens use the supplied HL3D brand assets under public/branding/ (carbon #111316, alloy #A7ADB5, fluorescent yellow #E7FF00, ice #F4F6F8, slate #515963). The CSS font stack (resources/css/app.css) names "Avenir Next" first and falls back to the local system sans/monospace stack; no font files are bundled or fetched from Google/Bunny Fonts, and the wordmark is supplied as vector outlines so no font is needed to render the logo itself. Follow-up: no redistributable Avenir Next web-font licence has been secured — if a licensed web font becomes available, add it as a self-hosted asset (no external font CDN calls) and keep the system-stack fallback.
Backups
A restorable backup needs both of the following, taken together (the private files reference projects/revisions by ID, so restoring one without the other breaks file bundles):
- The database (
mysqldumpin production;database/database.sqliteonly exists for local/dev use). - The private file store at
storage/app/project-files/(dedicated, non-public disk for uploaded STL/3MF/G-code bytes — distinct fromstorage/app/public).
Verify a restore against an isolated copy before trusting it, and spot-check a project_files.sha256 value against the restored bytes on disk.
Verification
php artisan test --compact
npm run build
The test suite uses an in-memory SQLite database. MySQL and the external services need environment-specific smoke checks with your credentials and devices.