ecommerce

package
v0.96.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 7, 2026 License: MIT Imports: 0 Imported by: 0

README

ShopFront: the declaration-driven flagship

This is the largest example in the repo and the proof of the framework's thesis: a complete storefront described once in gofastr.yml: 5 related entities (categories, products, orders, order_items, reviews), a themed 8-screen UI, custom endpoints, seed data, middleware, and a plugin, all emitted as runnable Go by a single command. The owned app/entities/order_totals_hooks.go adds the transactional catalog-pricing and order-total derivations to that scaffold.

What it proves

One app spec fans out into every surface: auto-migrated SQL schema, REST CRUD with validation and cursor pagination, OpenAPI, 25 MCP tools for agents, and a server-rendered themed storefront. All of it is asserted live by flagship_test.go, which regenerates app/ from the app spec, builds the binary, boots it, and hits each surface, so the proof never depends on a stale checkout.

Secure by default

The app generator ships with auth enabled (app.auth.enabled: true) and the customer-PII entities, orders and order_items, owner-scoped via owner_field: user_id. The framework stamps the owner on create and scopes every list/get/update/delete (REST and MCP) to the requesting user, failing closed with 401 when no owner can be derived. TestOrdersOwnerScoped pins this. (The first cut shipped insecure; git history has the full story of the fix.)

Line prices are server-owned: order_items.unit_price and total_price are read-only in the app spec and generated request types. After each item create/update, the owned lifecycle hook reads the stored product and quantity, copies the product's current price, multiplies it using exact decimal arithmetic at four decimal places, and then recomputes the parent order. Pricing and totals share the CRUD transaction, so failures roll back the write. Order subtotals and totals round to the columns' four-decimal scale, including tax and shipping, so SQLite's floating-point SUM artifacts do not reach the API. On SQLite, a derived line price beyond the decimal exactness limit is refused before persistence, and the item write and its parent-total changes roll back. Clients can omit both price fields; supplied values cannot set a price. Quantity-only edits and product changes reprice against the current catalog. Keep the owned hook file when scaffolding; generated hook stubs are not its implementation. TestOrderPricesComeFromProducts exercises this through REST.

Maintainer note: read-only fields are omitted from ordinary CRUD SQL even if a before-hook fills them. Their zero defaults only bridge the insert; the after-hook writes authoritative prices inside the same transaction, before totals, events, and responses. Do not move pricing into a before-hook alone. This demo has no upgrade/backfill migration: existing line items are repriced when written, not merely when a newer executable starts.

Regenerate and run

cd examples/ecommerce
gofastr generate app gofastr.yml --force   # re-emits app/ from the app spec (output_dir: app)
gofastr dev --dir app                         # hot-reload dev server on localhost:8080

app/ is committed, owned Go. output_dir: app in the app spec puts the app in a subpackage so the example also hosts its own test files.

Documentation

Overview

Package ecommerce is GoFastr's declaration-driven flagship example: a complete storefront: five related entities, screens, navigation, custom endpoints, seed data, and a theme, declared once in gofastr.yml and emitted as runnable Go by the CLI:

gofastr generate app gofastr.yml

The generated app is committed under app/ (output_dir: app in the app spec) and is the proof of the framework's thesis: one app spec produces a SQL schema, REST CRUD, an OpenAPI spec, a typed MCP tool surface, and a server-rendered UI, none of it hand-written. flagship_test.go regenerates app/ with --force on every run and appspec_gate_test.go fails when the committed files differ from what the in-tree generator emits, so app/ can never drift from the CLI.

Run it directly:

go run ./examples/ecommerce/app

Directories

Path Synopsis
app

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL