Skip to content

Release v0.4.25 - #119

Open
roncodes wants to merge 41 commits into
mainfrom
release/v0.4.25
Open

roncodes wants to merge 41 commits into
mainfrom
release/v0.4.25

Conversation

@roncodes

@roncodes roncodes commented Oct 6, 2026

Copy link
Copy Markdown
Member

Release branch for v0.4.25, cut from main. Versions are bumped in composer.json, extension.json and package.json, and RELEASE.md has the notes.

This branch collects:

Before merging

Merging this tags v0.4.25. The version comes from the branch name and is checked against the version files and the first line of RELEASE.md.

The S3 media bucket enforces bucket-owner object ownership and rejects PUTs that
carry an ACL, so put(..., 'public') returned false and no photo was stored.
…annel resolvers

- POST storefront/v1/customers/socket-token mints a customer principal (store key +
  Customer-Token); 404 while socket auth is disabled, 401 without a customer of the
  storefront's company.
- Checkout initialization responses carry socket_token (checkout kind, scp limited to
  checkout.{public_id}) when socket auth is enabled; absent otherwise.
- Register storefront and checkout channel resolvers with core-api's
  SocketChannelRegistry.
- QPay capture publishes {checkout, status, order, error} on checkout.{public_id}
  instead of the raw payment row; a publish failure no longer fails the callback.
- Require fleetbase/core-api ^1.6.69.
Listing a network's stores ran several queries per store and loaded the whole
network for distance sorting, so a page of stores could take minutes.

- Store resource: build category, networks, locations and media only when the
  field is requested (when() evaluated its value argument for every store)
- Preload the review average with withAvg() on the public and console store
  queries; the rating accessor uses it instead of one AVG query per store
- Console store list eager-loads logo and backdrop, and networks when the
  network category is requested
- Network category lookup reuses eager-loaded networks and resolves each
  network id once instead of once per store
- Ignore missing or unparseable locations instead of measuring from (0, 0):
  no in-memory sort of every store, and maximum_distance no longer filters
  out every store when no location is sent
- Index reviews.subject_uuid
…n-review delete

- Customers can review a store or product only for a completed order placed with
  that store or containing that product, once per order per subject. Reviews
  record the order (new reviews.order_uuid).
- GET reviews/eligibility tells the app whether the signed-in customer can review
  a subject, and why not.
- DELETE reviews/{id} now routes to delete() instead of find(), so customers can
  remove their own reviews.
- Ratings must be whole stars from 1 to 5; content is limited to 2000 characters
  and uploads to four image or video files.
- The review resource names the subject by public id instead of the internal row
  id, and adds subject_type, verified and is_mine.
…on public promotions

- Public promotions name the store or network running them (owner: type, id,
  name, logo_url) and list applies_to targets by public id instead of uuid.
- Code promotions show their code publicly when they have one reusable,
  unassigned, unexpired code; single-use batch codes are never exposed.
- Every promotion reports availability (live, scheduled, ended) and, when
  scheduled, next_starts_at from its date range and weekly hours.
- GET promotions?include=scheduled also lists promotions outside their weekly
  hours or before their start; GET promotions/{id} opens scheduled and ended
  promotions so links from notifications keep working.
- storefront/v1/orders/{id}/chat: show (starts the chat), messages (cursor
  pagination), send (text and up to four photos) and read (receipts), for the
  signed-in customer's own orders in this storefront.
- The chat is a core chat channel tagged with the order in its meta, so the
  driver sees it in Navigator; participants are kept to the customer and the
  currently assigned driver, and it is started when a driver is assigned.
- Customers can read but not send once the order is completed, canceled or
  expired.
- Driver messages are pushed to the customer through storefront push, the inbox
  and the customer's broadcast channel.
- Customer socket tokens include the customer's user uuid so they can subscribe
  to chat_channel.{uuid}, which core authorizes by participant.
@codecov

codecov Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (62af547) to head (ca4a2f6).
⚠️ Report is 3 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff              @@
##                main      #119    +/-   ##
============================================
  Coverage     100.00%   100.00%            
- Complexity      2282      2363    +81     
============================================
  Files            182       183     +1     
  Lines           9082      9269   +187     
============================================
+ Hits            9082      9269   +187     
Flag Coverage Δ
backend 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

fix(reviews): upload review photos without a public ACL
perf: speed up network and console store listings
feat(reviews): verified-purchase reviews, eligibility endpoint and own-review delete
feat(promotions): store attribution, shareable code and availability on public promotions
…-chat

# Conflicts:
#	server/tests/Unit/Routes/StorefrontRoutesTest.php
feat(chat): customer chat with the driver delivering their order
feat(products): expose add-on category limits on public product payloads
…the testing seeders

- Network seeder: a "Home & Wellness" category and two service stores tagged
  `services` (Lion City Cleaning, Tiong Bahru Wellness) with bookable products
  that carry a duration (`meta.duration`, 45 to 360 minutes), options, add-ons
  and a sale price.
- Promotions for the network and individual stores, covering every type
  (percentage, fixed amount, free delivery, buy 2 get 1) and state (live,
  weekly schedule, weekends only, code only, first order only, upcoming, ended,
  draft), with codes WELCOME15, FIRSTCLEAN, MARKET5 and VIPPREVIEW. They target
  categories, products or stores.
- Customer segments (loyal, recent, lapsed, never ordered, push enabled) and
  sent, scheduled and draft campaigns that deep-link to a promotion, store or
  product. Scheduled campaigns are due days ahead, so the scheduler doesn't send
  them straight away.
- Single-store seeder: a smaller set of promotions, segments and campaigns.
- Re-running purges seeded campaigns, promotions with their codes and
  redemptions, and segments.
- Fix: re-running failed with "A customer with this email already exists".
  Both seeders used the same customer emails and phones in one company, and
  Fleet-Ops allows one customer profile per account there. The store seeder's
  customers now use `+market` emails and `+65 9101` phones.
- Reviews sorted ascending whatever was asked: applySort set sort_direction,
  which core's query builder never reads. It now sends the columns core
  understands ("-rating", "-created_at"), with the newest review first on equal
  ratings, so "highest" and "newest" come back in the right order.
- Payment methods: creating the Stripe customer ran outside the try/catch, so a
  gateway whose key Stripe refuses (such as the seeded placeholder key) threw
  and came back as a bare 401 "Unauthenticated.". It now returns the Stripe
  error as a 400, like the other Stripe failures there.
Re-seeding purged and recreated the network and stores, and the models generate
a new key on every create, so each run changed the storefront key and forced an
app rebuild. The seeder now remembers the uuid, public id and key of every store
and network it created before purging, recreates them with the same uuid, and
writes the old public id and key back after the insert. New ones get new keys
as before.
- No service rate had the storefront service type, so every delivery quote
  failed with "No service rates available!" and checkout showed delivery as
  unavailable for any address. Both seeders now create a "Storefront Delivery"
  rate (base fee plus a per-km fee, no service area) for the storefront order
  config, purged and recreated like their other fixtures.
- Re-seeding replaced the seeded Stripe gateways' keys with placeholders. A
  gateway now keeps the real keys it already had (set in the console), unless
  SEED_STRIPE_SECRET_KEY / SEED_STRIPE_PUBLISHABLE_KEY are given.
The test stores, network, products, promotions, seeded orders and the seeded
delivery rate now use Singapore dollars. Service rates only quote carts in
their own currency, so the delivery rate follows the seeded owner's currency.
…hen it is updated

Choosing another payment method calls stripe-update-payment-intent, which
updated the PaymentIntent and then created a new checkout without its
PaymentIntent id. The app captured with that checkout's token and capture
refused it ('Stripe PaymentIntent is not linked to this checkout').

The checkout that started the PaymentIntent is now updated in place (a
PaymentIntent belongs to one checkout), and its promotion reservations are
released before being reserved again at the new price. A PaymentIntent on
another customer's checkout is refused. With no existing checkout, the new
one is linked to the PaymentIntent.
…ampaign text

The seeded stores and network sell in SGD, but promotion names, descriptions
and campaign copy wrote amounts as "$40", while the app formats the amounts
it calculates as "S$40".
GET storefront/v1/orders/{id}/activity-flow returns the signed-in customer's
order as a line of steps from its order config, so apps can show custom flows
(e.g. Created > Dispatched > Started > Enroute > Completed) instead of guessing
from status codes.

Steps are the activities the order has been through (tracking history, first
time each was reached), its current activity, then the path it is expected to
take. At a branch, Fleet-Ops' own activity conditions decide: an activity whose
conditions this order meets (pickup's 'ready for pickup') wins over an
unconditional one, cancellation branches are never projected, and a looping
flow is capped. Unknown statuses still appear, labelled from tracking history.
Labels and details come from the config, with {storefront.name}-style
placeholders filled.
Seeded store hours used "monday", while the console writes "Monday" and
clients such as the storefront SDK match on that, so the app's store info
screen crashed on seeded stores.
…urns them; the console serializers handle place types
…ulti-store order; otherwise the tip goes to the network
…translations build again

c8af413 inserted the split-tips label between the two lines of the
enable-multi-cart-checkout value, which js-yaml reads as a bad mapping entry
and the engine build fails.
…h the shared identity cells, pills and select options

The orders, network orders, customers and network customers tables use
table/cell/identity typed for the shared fleetops-data descriptors, reading
the loaded relation or an identity stub named after the row, so a row shows
the right tile and name before the relation loads. Clicking a customer opens
the storefront customer page; a driver or place opens its FleetOps panel,
loading the engine only then.

The order details, fulfillment and customer panels, the incoming-order
modal, the orders and customers widgets and the food-truck cards use
Customer::Pill, Driver::Pill and Vehicle::Pill in place of hand-rolled
avatar blocks, whose styles are removed. Driver, vehicle, customer, user and
category selects render SelectOption rows with compact selected items.

The fleetops-data range moves to ^0.2.3, the version the workspace links.
feat(identity): render drivers, customers, vehicles and places through the shared identity components
…ore anything is created and a failed attempt is undone
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant