The Copy Shop · Order System feat/local-cloudflare-core

Current architecture · August 23, 2026

Codebase walkthrough

The real implementation foundation: an Angular customer and staff interface backed by a modular Hono Worker, locally simulated Cloudflare D1, and locally simulated R2.

PersistenceD1 metadata + R2 file bodies
Local identityDepartment email selector + HttpOnly cookie
DeploymentDeferred · no cloud resources created
This document describes the active Angular/Worker application. Target domain: orders.thecopyshoponline.com

01 · At a glance

What runs today

The active application keeps the user interface, API, structured records, and uploaded file bytes in clearly separated layers.

A

Angular UI

Standalone, strict, zoneless Angular application with customer intake, staff production workspace, responsive layout, and printable job sheets.

src/
W

Hono Worker

Cloudflare Worker-compatible API split into focused routes, identity middleware, validation, and order query/command modules.

worker/
D1

Structured data

Departments, workstation identities, orders, file metadata, activity events, and daily counters live behind the D1 binding.

env.DB
R2

File bodies

Uploaded bytes use private generated object keys in the R2 binding. Searchable metadata and original filenames remain in D1.

env.FILES

02 · Runtime shape

One interface, two local services

During normal development, Angular serves the UI on port 4200 and proxies /api to Wrangler on port 8787. Wrangler runs the Worker and simulates both Cloudflare storage bindings through Miniflare.

1Browser

Customer intake at / or staff workspace at /staff.

2Angular

Renders UI and sends typed requests from OrderApiService.

3Hono Worker

Validates requests, applies identity context, and coordinates persistence.

4D1 + R2

D1 stores relational state; R2 stores uploaded file bodies.

Production-like preview

npm run preview builds Angular and lets the Worker serve the compiled single-page application and API together from port 8787. Wrangler is configured to run the Worker first for /api/* and fall back to the SPA for browser routes.

03 · Worker organization

Composition, routes, data access

worker/app.ts is intentionally an 18-line composition root. It creates Hono, installs shared error behavior, registers route groups, and returns the app. Business rules and SQL are kept out of that file.

routes/system.ts

Health, departments, local identities, local session creation, session lookup, and sign-out.

routes/customer-orders.ts

Public initiation, Worker-mediated R2 upload, stored-size verification, and order completion.

routes/staff-orders.ts

Staff listing/detail, filters, status transitions, cancellation, downloads, audit events, and reset.

orders/queries.ts

D1 reads, list filtering, joined order summaries, files, events, departments, and submitted-order lookup.

orders/commands.ts

Atomic public IDs, idempotent initiation, writes, status/cancel events, and download attribution.

orders/models.ts

Internal D1 row shapes and reusable select projections, separate from public API contracts.

identity.ts

Development-only environment guard, principal lookup, and staff middleware.

order-validation.ts

Required fields, due date/time, delivery, extension, count, and byte-limit rules.

errors.ts · http.ts

Consistent JSON errors, safe JSON parsing, and content-disposition handling.

development-reset.ts

Deletes local R2 objects, clears D1 test data in dependency order, and reapplies the seed.

types.ts · db.ts

Binding types, request principal type, and small typed D1 result helpers.

index.ts

Creates the composed application once and exports it as the Worker entry point.

copy-shop-order-system/ ├── src/ Angular UI │ └── app/ │ ├── customer/ public intake │ ├── staff/ production workspace │ └── core/ API client + formatting ├── shared/order-contracts.ts framework-neutral contracts ├── worker/ │ ├── app.ts composition root │ ├── routes/ HTTP boundary │ └── orders/ D1 commands, queries, row models ├── db/schema.ts Drizzle schema source ├── migrations/ generated D1 SQL ├── seed/local.sql idempotent local fixtures ├── tests/worker/ isolated D1/R2 tests └── e2e/ Playwright workflows

04 · Data layer

D1 records, R2 objects

The Drizzle schema is the readable source of truth; generated SQL migrations are applied by Wrangler. Prepared D1 statements and batches are used at runtime. File bodies never go into D1.

departments D1

  • Stable ID and slug
  • Display name
  • Active/inactive status

users D1

  • Multiple emails per department
  • Normalized unique email
  • Workstation display name
  • Active/disabled status

orders D1

  • Internal and public IDs
  • Unique idempotency key
  • Pending/submitted state
  • Workflow status and cancellation
  • Customer, due, department, delivery

files D1 + R2

  • Order relationship
  • Original name and MIME type
  • Generated R2 object key
  • Expected byte size
  • Upload state, token, expiry

order_events D1

  • Submitted, status, cancel, download
  • Actor user relationship
  • Actor email snapshot
  • Timestamped activity history

order_counters D1

  • Date-keyed sequence
  • Atomic upsert with returning
  • Produces CS-YYMMDD-NNNN

Initiate → upload → complete

  1. The public client sends order metadata, file descriptors, and an idempotency key.
  2. D1 creates a pending order and pending file rows with generated R2 object keys and expiring upload tokens.
  3. The local Worker streams each file body into R2 and verifies the stored object size.
  4. Only after verification does the file row become available. A successful upload can be safely retried.
  5. Completion rejects the order while any file remains incomplete, then marks the order submitted and records an event.

05 · HTTP interface

Routes by trust boundary

Customer intake and department lookup are public. Local identity helpers exist only in local and test. Staff routes require the selected local principal on the server.

MethodRouteScopePurpose
GET/api/healthPublicRuntime health and environment.
GET/api/departmentsPublicActive D1-backed department choices.
POST/api/orders/initiatePublicValidate and create a pending, idempotent order.
PUT/api/uploads/:tokenPublic tokenStream one validated file to local R2.
POST/api/orders/:id/completePublicRequire available files and submit the order.
GET/api/local-identitiesLocal/testList seeded workstation identities.
POST/api/local-sessionLocal/testSelect identity and set an HttpOnly cookie.
GET · DELETE/api/sessionLocal/testRead or clear the current development session.
GET/api/staff/ordersStaffList and filter submitted orders.
GET/api/staff/orders/:idStaffOrder detail, files, and activity.
PATCH/api/staff/orders/:id/statusStaffEnforce forward-only workflow transitions and record actor.
POST/api/staff/orders/:id/cancelStaffCancel without rewriting production status; record actor.
GET/api/staff/orders/:id/files/:fileIdStaffStream an individual R2 object and audit the download.
POST/api/dev/resetLocal staffClear test records and R2 objects, then reseed.

Intentionally absent: there is no bulk ZIP endpoint. Individual file downloads are the supported interface.

06 · Development identity

A workstation represents a department

A “user” is currently modeled as an email identity for a department workstation. The schema permits multiple active emails per department even though the local seed starts with one fake .local identity for each of the five departments.

Selected emailcopies@thecopyshop.local
Session principaluser-copies
Default departmentdept-copies

Current behavior

  • The selector uses no password and is explicitly development-only.
  • The selected user ID is stored in an HttpOnly, same-site cookie.
  • Staff middleware resolves the cookie back to an active D1 user and department.
  • The principal’s department becomes the initial staff filter.
  • Every current local identity may still select “All departments” or another department.
  • Status changes, cancellation, and downloads store the actor email in activity history.

07 · Local development

One command to start

npm run dev applies pending D1 migrations, runs the idempotent local seed, then starts Wrangler and Angular together. Persistent local D1 and R2 emulator state lives under the ignored .wrangler/state/ directory.

Install and run
npm install
npm run dev

# UI
http://127.0.0.1:4200

# Worker health
http://127.0.0.1:8787/api/health
Database lifecycle
npm run db:migrate:local
npm run db:seed:local
npm run db:setup
npm run db:generate
Production-like local preview
npm run preview

# Compiled SPA + Worker API
http://127.0.0.1:8787
Acceptance sequence
npm run lint
npm test
npm run build
npm run test:e2e

# Everything above
npm run check

Angular cache wrapper

Angular commands run through scripts/angular.mjs. It clears only generated Angular cache data and forces the SQLite cache backend, avoiding the local LMDB crash and Angular 22’s stale compiled-byte serialization problem.

Seed and reset

seed/local.sql contains five departments, five fake workstation identities, current sample orders, metadata-only sample attachments, events, and counters. The staff reset control removes new R2 objects and restores these records.

08 · Verification

Behavior at every layer

The complete acceptance command currently passes lint, Angular tests, isolated Worker tests, a production Angular build plus Worker dry run, and Playwright workflows.

7Angular unit tests

Formatting, API request contracts, and customer component behavior.

5Worker tests

Fresh D1/R2 bindings per test, identity, uploads, idempotency, filters, workflow, audit, and reset.

8Playwright tests

End-to-end submission, download, statuses, cancellation, identity switching, filters, responsive/print behavior, and overflow regression.

Storage behavior covered

  • Idempotent initiation and completion
  • File extension, count, and size validation
  • Incomplete upload rejection
  • R2 persistence and stored-size verification
  • Individual download and audit history
  • D1-backed department and staff filters

UI behavior covered

  • Department-default staff view and all-department access
  • Identity switching and actor attribution
  • Responsive customer and production layouts
  • Printable job sheet controls
  • Zero horizontal overflow around “Mark Complete” at seven widths from 390px to 1280px

09 · Production roadmap

Documented, not implemented

This branch deliberately stops at a strong local core. It creates no Cloudflare account resources, DNS records, production credentials, or real authentication.

Identity

Verified-email authentication, Cloudflare Access or an approved provider, explicit allowlists, and D1 user-to-department authorization.

Cloud storage

Separate preview/production D1 databases and R2 buckets, reviewed remote migrations, private objects, lifecycle policies, and backups.

Uploads

Short-lived direct R2 URLs or multipart uploads, checksums, narrow CORS, orphan cleanup, and approved malware controls.

Public defense

Turnstile, rate limiting, quotas, file-signature checks, abuse monitoring, and safe retention.

Operations

Notifications, observability, incident ownership, restore testing, rollback, and a limited parallel-operation period.

Domain

Configure orders.thecopyshoponline.com as a future Worker Custom Domain only after resource and DNS review.

The complete checklist lives in docs/production-deployment.md.