Skip to content

v0.2.6

Choose a tag to compare

@ptonlix ptonlix released this 16 Oct 02:54
· 5 commits to main since this release
d85b545

v0.2.6

日期:2026-10-16

这是 v0.2.5 之后的可选商品描述。设备码、授权范围、买家注册、买入开关、支付成功条件、店面缓存、文本压缩范围、Caddy 匹配和安装回滚都没有改。没有新路由,也没有新错误码。compose.yaml 与 v0.2.5 相同。install.sh 与 v0.2.5 相同。

v0.2.5 的商品只有标题、状态、封面和目录字段。公开详情上的说明是用标题、价格和库存拼出来的,商家不能写一段给买家看的正文。这一版给商品增加可选纯文本 description。创建和 PATCH 可以写它。公开详情、商品 Markdown、meta description、JSON-LD 和关键词 q 使用同一份值。新订单行另存下单时的副本。已有商品和已有订单不回填。

同一时间仓库文档也重写了:README.md、README_EN.md、架构图,以及 txpearl.cn、eileenpeng.com 的案例。这些文件不进运行行为。只换文档不会带上商品描述。

生产应用镜像是 docker.io/<用户名>/merclink:0.2.6。正式版同时推送 0.2.6 和 latest。要换应用镜像。进程启动时会执行新迁移 017_product_description.sql。不需要换 compose.yaml。

中文

这一版能做什么

  • products 增加可空列 description。纯文本,先去掉首尾空白,内部换行保留。长度用字符串 length,1 到 2000 个字符。空字符串、非字符串和超长都是 validation_error。省略后为空,空着仍可上架。JSON、页面和订单快照里的空描述都是 null,不写进 fields。
  • description 成为保留系统字段名,和 title、price、stock 一样。不能放进商品 fields,也不能新建、停用或复用这个目录字段 key。拒绝仍是 validation_error,不是新错误码。PATCH 省略 description 不改已保存的值;传 null 清空。省略 fields 仍不改自定义属性。不能为了改描述去重传 fields,也不能用描述改价格。
  • 公开商品 JSON 和商家商品 JSON 都在 title 旁返回 description。没有描述时是 null。不要从 fields 读这个键。GET /api/v1/products 的 q 同时匹配标题、这段描述和目录文本字段,不要求 catalog_id。描述不参与数字、单选或是否过滤。买家 Skill、未开启买入时的 Skill 和商家 Skill 已改成这套读写说明。
  • 内置商品详情在价格和可售状态下面展示这段文字。换行保留,内容转义,不渲染 Markdown 或 HTML。没有描述就不留空块。首页卡片和商品列表仍只显示封面、标题和价格。商品 Markdown 把描述单独成段,不压成一行。
  • 有描述时,商品页 meta description 和 JSON-LD description 先把连续空白收成一个空格,再截到 150 个字符。没有描述时仍用标题、价格和库存状态拼成的句子。不另存 SEO 文案。
  • 新订单行写入 order_items.description_snapshot,从下单时锁定的商品行复制。当时为空就写 null,不放进 fields_snapshot。之后修改商品描述不改这一行。旧订单行保持 null,不回填。订单 JSON 仍只返回行的 id、variant_id、qty 和 amount,不返回这段快照。
  • 内置商品页和起始店面的商品页声明槽位 product.description。槽位有值时转义后输出,换行变成 <br>;没有描述时槽位为空。首页和商品列表模板不声明这个槽位。已经接受的发布不会被改写。已发布且没有这个槽位的自定义店面保持原样,页面不会自动补一块描述。商家要在模板里声明 product.description 后再发布,自定义店面才会出现这段文字。
  • 仓库首页改成带首图的说明,并增加英文 README、架构图和两个在线店铺案例。Mermaid 总览改放在 docs/ARCHITECTURE.md。这只影响仓库阅读,不影响已部署进程。

这一版不做

  • 不渲染描述里的 Markdown 或 HTML,不加后台表单,也不按规格写描述。
  • 不把描述放进首页卡片、商品列表或店铺简介。
  • 不自动把已有 fields.description 搬进新列,也不停用那个 key。
  • 不改设备码流程、scope、买家注册或支付成功条件。只有订单状态 paid 表示支付成功。调用方仍不能传价格。
  • 不改店面 HTML 的 no-store,也不改 CSS、JS 的 10 分钟新鲜期和之后最多 24 小时的后台校验。
  • 不改公开图片按 id 缓存一年的规则。缺失图片仍是 404。
  • 不改 Caddy 的五行 Content-Type 匹配,也不改镜像标签。仍是 caddy:2.11.7-alpine。
  • 不改一键安装询问的字段,不安装系统包,也不默认打开买入。安装和升级未完成时的回滚与 v0.2.5 相同。
  • 不新增路由、错误码或依赖。不激活店面,不补写历史发布,也不把 Docker Hub 用户名、令牌、域名或证书写进仓库。

升级注意

商品描述在应用镜像和数据库迁移里。compose.yaml 和 install.sh 与 v0.2.5 相同。不要删 .env,也不要删 caddy_data。不要改已经应用过的迁移;新文件只是 017_product_description.sql。

升级前先查目录字段。只要有一行 product_fields.key = 'description',迁移会抛出 product_fields.key description already exists 并整段回滚,新进程起不来。这一版不复制、也不停用那个 key。先导出值,再用现有字段停用流程去掉该 key,然后再升级。停用可能让缺值商品下架。没有这个 key 时,迁移只给 products 和 order_items 各加一列可空文本,不回填。

一键安装过的机器,等这个正式版发布后执行:

curl -fsSL -o /tmp/merclink-install.sh https://github.com/ptonlix/MercLink/releases/latest/download/install.sh && bash /tmp/merclink-install.sh upgrade

upgrade 会下载这个 tag 的 compose.yaml,保留已有密钥和后来手工打开的买入配置,然后 pull。它不打印已保存的密码。这次 compose.yaml 没有变化。升级如果没有完成,部署文件和版本号会回到升级前;已经换上的容器要再执行 docker compose up -d 才和文件一致。新容器要能跑完 017 才会正常服务。

手工部署的机器不要再跑一键安装。把 MERCLINK_IMAGE 改为 docker.io/<用户名>/merclink:0.2.6,然后执行:

docker compose pull && docker compose up -d

不要加 --build。拉取完成且进程起来之后,旧商品的 description 仍是 null,公开页和上架行为与 v0.2.5 一致,直到创建或 PATCH 写入描述。用商家接口写入后,内置商品详情会显示这段文字。还没有接受发布时,重新下载的起始店面商品页带有 product.description。已经激活的自定义店面不会因此改版。

回滚时把应用镜像改回 docker.io/<用户名>/merclink:0.2.5,再 pull。不要把 latest 当作回滚点。latest 会在这个正式版发布时移到 0.2.6。0.2.5 不读这两列。迁移已经成功时,不要为了回滚删掉 products.description 或 order_items.description_snapshot;删掉会丢掉新写入的描述。订单金额、库存和支付状态不依赖这两列。

English

Date: 2026-10-16

This is the optional product description after v0.2.5. The device-code flow, authorization scopes, buyer registration, purchase switch, the rule that only paid means payment success, storefront caching, the set of compressed types, the Caddy matchers, and install rollback are unchanged. There is no new route and no new error code. compose.yaml is the same file as v0.2.5. install.sh is the same file as v0.2.5.

In v0.2.5 a product has a title, status, cover, and catalog fields. The public detail blurb is assembled from the title, price, and stock. A merchant cannot write body text for buyers. This version adds an optional plain-text description. Create and PATCH can set it. The public detail page, product Markdown, meta description, JSON-LD, and keyword q use that same value. A new order line stores a copy from order time. Existing products and existing orders are not backfilled.

The repository docs were rewritten in the same range: README.md, README_EN.md, the architecture figure, and case notes for txpearl.cn and eileenpeng.com. Those files do not change runtime behavior. Replacing only the docs does not ship product descriptions.

The production image is docker.io/<namespace>/merclink:0.2.6. The stable release also publishes latest. The application image has to be replaced. Process startup applies the new migration 017_product_description.sql. compose.yaml does not need to change.

What this version does

  • products gains a nullable description column. The text is plain, end whitespace is removed, and internal line breaks stay. Length is JavaScript string length, from 1 to 2000 characters. An empty string, a non-string, and an over-long value are validation_error. Omitting it leaves the description empty, and publishing does not require it. An empty description is null in JSON, on the page, and in the order snapshot. It is not stored in fields.
  • description is now a reserved system field name, like title, price, and stock. It cannot be placed in product fields, and it cannot be created, retired, or reused as a catalog field key. Rejection stays validation_error; there is no new error code. Omitting description on PATCH leaves the saved value unchanged; null clears it. Omitting fields still leaves custom attributes unchanged. Do not resend fields only to change the description, and do not use the description to change a price.
  • Public product JSON and merchant product JSON both return description beside title. It is null when absent. Do not read this key from fields. q on GET /api/v1/products matches the title, this description, and catalog text fields, and does not require catalog_id. The description does not take part in number, single-select, or boolean filters. The buyer Skill, the purchase-disabled Skill, and the merchant Skill now describe this read and write behavior.
  • The built-in product detail shows the text under the price and availability. Line breaks are kept, the text is escaped, and Markdown or HTML is not rendered. An empty description leaves no empty block. Home cards and the product list still show only the cover, title, and price. Product Markdown keeps the description as its own paragraph and does not collapse it to one line.
  • When a description exists, the product meta description and JSON-LD description collapse consecutive whitespace to one space, then truncate to 150 characters. Without a description they still use the sentence built from title, price, and stock. No separate SEO copy is stored.
  • A new order line writes order_items.description_snapshot, copied from the product row locked at order time. An empty description is stored as null and is not placed in fields_snapshot. A later edit to the product description does not change that line. Old order lines stay null and are not backfilled. Order JSON still returns only id, variant_id, qty, and amount for a line. It does not return this snapshot.
  • The built-in product page and the starter storefront product page declare product.description. A filled slot is escaped and line breaks become <br>; an empty description leaves the slot empty. The home and product-list templates do not declare this slot. An accepted release is not rewritten. A published custom storefront that does not declare the slot stays as it is, and the page does not grow a description block on its own. The merchant declares product.description in the template and publishes again before a custom storefront shows the text.
  • The repository home page is now a hero-led introduction, with an English README, an architecture figure, and two live-store case notes. The Mermaid overview moves to docs/ARCHITECTURE.md. This changes how the repository reads. It does not change a deployed process.

What this version does not do

  • It does not render Markdown or HTML inside a description, add an admin form, or store a description per variant.
  • It does not put the description on home cards, the product list, or the store profile.
  • It does not copy an existing fields.description into the new column, and it does not retire that key.
  • It does not change the device-code flow, scopes, buyer registration, or the payment-success rule. Only order status paid means payment succeeded. The caller still cannot send a price.
  • It does not change no-store on storefront HTML, or the 10-minute freshness window and later 24-hour background revalidation for CSS and JS.
  • It does not change the one-year cache for a public image id. A missing image is still 404.
  • It does not change Caddy's five Content-Type lines or the image tag. It remains caddy:2.11.7-alpine.
  • It does not change what one-click install asks for, it does not install system packages, and it does not turn purchase on by default. Rollback of an unfinished install or upgrade stays as it was in v0.2.5.
  • It does not add a route, an error code, or a dependency. It does not activate a storefront, backfill old releases, or store a Docker Hub username, token, domain, or certificate in the repository.

Upgrade note

The description ships in the application image and in a database migration. compose.yaml and install.sh match v0.2.5. Do not delete .env or caddy_data. Do not edit a migration that has already been applied. The new file is only 017_product_description.sql.

Check catalog fields before upgrading. If any row has product_fields.key = 'description', the migration raises product_fields.key description already exists and rolls the whole step back. The new process does not stay up. This version does not copy or retire that key. Export the values, remove the key with the existing field-retirement flow, then upgrade. Retirement can unpublish products that lose a required value. When the key is absent, the migration only adds one nullable text column to products and one to order_items. It does not backfill.

After this stable release is published, a host installed by the script should run:

curl -fsSL -o /tmp/merclink-install.sh https://github.com/ptonlix/MercLink/releases/latest/download/install.sh && bash /tmp/merclink-install.sh upgrade

upgrade downloads compose.yaml for this tag, keeps generated secrets and any later purchase settings, then pulls. It does not print stored passwords. This compose.yaml is unchanged. If this upgrade does not finish, the deploy files and version pin return to their previous values. Containers already recreated from the new image match those files only after another docker compose up -d. The new container serves traffic only after 017 finishes.

Do not run a fresh one-click install over a deployment that was created by hand. Set MERCLINK_IMAGE to docker.io/<namespace>/merclink:0.2.6, then run:

docker compose pull && docker compose up -d

Do not add --build. After the pull, once the process is up, existing products still have description null. Public pages and publishing stay as they were in v0.2.5 until a create or PATCH writes a description. After that write, the built-in product detail shows the text. When no release has been accepted, a newly downloaded starter product page contains product.description. An already active custom storefront is not republished by this upgrade.

To roll back, set the application image to docker.io/<namespace>/merclink:0.2.5 and pull again. Do not use latest as the rollback point. latest moves to 0.2.6 when this stable release is published. 0.2.5 does not read the two new columns. After the migration has succeeded, do not drop products.description or order_items.description_snapshot just to roll back. Dropping them discards descriptions written by this version. Order amounts, stock, and payment status do not depend on those columns.