fix(api): preserve omitted client fields in partial updates (#7042)

Keep omitted client fields and per-inbound flows unchanged while honoring explicit clears. Support the query aliases used to recover legacy dot-name clients and regenerate the API reference. Add an HTTP regression covering partial updates through those aliases.
This commit is contained in:
Aleksey Fedorov
2026-11-06 03:06:16 +10:00
committed by GitHub
parent cd176faf4e
commit e4935be861
14 changed files with 525 additions and 71 deletions
+40 -30
View File
@@ -48,15 +48,17 @@ _openapi:
title: Preview client auto-renewal dates without saving or resetting anything.
url: '#preview-client-auto-renewal-dates-without-saving-or-resetting-anything'
- depth: 2
title: Update an existing client by email. Changes propagate to every attached
inbound. Body is the JSON client payload — supply the full set of fields
you want to keep (the server replaces the row, it does not patch).
url: '#update-an-existing-client-by-email-changes-propagate-to-every-attached-inbound-body-is-the-json-client-payload--supply-the-full-set-of-fields-you-want-to-keep-the-server-replaces-the-row-it-does-not-patch'
title: 'Update an existing client by email. Changes propagate to every attached
inbound. Body is the JSON client payload: omitted fields keep their
current values, and an explicit zero, empty or null value clears a
field, except as noted below.'
url: '#update-an-existing-client-by-email-changes-propagate-to-every-attached-inbound-body-is-the-json-client-payload-omitted-fields-keep-their-current-values-and-an-explicit-zero-empty-or-null-value-clears-a-field-except-as-noted-below'
- depth: 2
title: Update an existing client by email. Changes propagate to every attached
inbound. Body is the JSON client payload — supply the full set of fields
you want to keep (the server replaces the row, it does not patch).
url: '#update-an-existing-client-by-email-changes-propagate-to-every-attached-inbound-body-is-the-json-client-payload--supply-the-full-set-of-fields-you-want-to-keep-the-server-replaces-the-row-it-does-not-patch-1'
title: 'Update an existing client by email. Changes propagate to every attached
inbound. Body is the JSON client payload: omitted fields keep their
current values, and an explicit zero, empty or null value clears a
field, except as noted below.'
url: '#update-an-existing-client-by-email-changes-propagate-to-every-attached-inbound-body-is-the-json-client-payload-omitted-fields-keep-their-current-values-and-an-explicit-zero-empty-or-null-value-clears-a-field-except-as-noted-below-1'
- depth: 2
title: Delete a client by email. Removes it from every attached inbound and
drops its traffic record unless keepTraffic=1 is passed.
@@ -351,16 +353,16 @@ _openapi:
id: create-a-new-client-and-attach-it-to-one-or-more-inbounds-in-a-single-call-body-is-json-per-protocol-secrets-are-generated-server-side-when-omitted-so-callers-can-send-only-the-universal-fields
- content: Preview client auto-renewal dates without saving or resetting anything.
id: preview-client-auto-renewal-dates-without-saving-or-resetting-anything
- content: Update an existing client by email. Changes propagate to every attached
inbound. Body is the JSON client payload — supply the full set of
fields you want to keep (the server replaces the row, it does not
patch).
id: update-an-existing-client-by-email-changes-propagate-to-every-attached-inbound-body-is-the-json-client-payload--supply-the-full-set-of-fields-you-want-to-keep-the-server-replaces-the-row-it-does-not-patch
- content: Update an existing client by email. Changes propagate to every attached
inbound. Body is the JSON client payload — supply the full set of
fields you want to keep (the server replaces the row, it does not
patch).
id: update-an-existing-client-by-email-changes-propagate-to-every-attached-inbound-body-is-the-json-client-payload--supply-the-full-set-of-fields-you-want-to-keep-the-server-replaces-the-row-it-does-not-patch-1
- content: 'Update an existing client by email. Changes propagate to every
attached inbound. Body is the JSON client payload: omitted fields keep
their current values, and an explicit zero, empty or null value clears
a field, except as noted below.'
id: update-an-existing-client-by-email-changes-propagate-to-every-attached-inbound-body-is-the-json-client-payload-omitted-fields-keep-their-current-values-and-an-explicit-zero-empty-or-null-value-clears-a-field-except-as-noted-below
- content: 'Update an existing client by email. Changes propagate to every
attached inbound. Body is the JSON client payload: omitted fields keep
their current values, and an explicit zero, empty or null value clears
a field, except as noted below.'
id: update-an-existing-client-by-email-changes-propagate-to-every-attached-inbound-body-is-the-json-client-payload-omitted-fields-keep-their-current-values-and-an-explicit-zero-empty-or-null-value-clears-a-field-except-as-noted-below-1
- content: Delete a client by email. Removes it from every attached inbound and
drops its traffic record unless keepTraffic=1 is passed.
id: delete-a-client-by-email-removes-it-from-every-attached-inbound-and-drops-its-traffic-record-unless-keeptraffic1-is-passed
@@ -671,17 +673,10 @@ _openapi:
preview is informational and does not reserve an allowance or
guarantee node availability.
heading: preview-client-auto-renewal-dates-without-saving-or-resetting-anything
- content: 'The inbounds are applied concurrently and independently: one that
fails no longer stops the others. Every inbound error names the
inbound it came from (`inbound 7: <message>`), and several failures
are reported together, one per line. So a `success:false` response can
still have applied the edit to the remaining inbounds. The client
record is written after the inbounds, so a failure there is reported
without an `inbound <id>:` prefix and leaves the inbound edits in
place.'
heading: update-an-existing-client-by-email-changes-propagate-to-every-attached-inbound-body-is-the-json-client-payload--supply-the-full-set-of-fields-you-want-to-keep-the-server-replaces-the-row-it-does-not-patch
- content: 'Query-parameter alias for the path endpoint. Use email=. or email=..
to manage legacy reserved names without URL dot-segment normalization.
- content: 'An omitted `flow` leaves every attached inbound on its own flow. Send
`"reverse": null` to remove the reverse tag. Credentials (`id`,
`password`, `auth`, `secret`, `subId`) and WireGuard/AmneziaWG
`allowedIPs` are never cleared: an empty value keeps the stored one.
The inbounds are applied concurrently and independently: one that
fails no longer stops the others. Every inbound error names the
inbound it came from (`inbound 7: <message>`), and several failures
@@ -690,7 +685,22 @@ _openapi:
record is written after the inbounds, so a failure there is reported
without an `inbound <id>:` prefix and leaves the inbound edits in
place.'
heading: update-an-existing-client-by-email-changes-propagate-to-every-attached-inbound-body-is-the-json-client-payload--supply-the-full-set-of-fields-you-want-to-keep-the-server-replaces-the-row-it-does-not-patch-1
heading: update-an-existing-client-by-email-changes-propagate-to-every-attached-inbound-body-is-the-json-client-payload-omitted-fields-keep-their-current-values-and-an-explicit-zero-empty-or-null-value-clears-a-field-except-as-noted-below
- content: 'Query-parameter alias for the path endpoint. Use email=. or email=..
to manage legacy reserved names without URL dot-segment normalization.
An omitted `flow` leaves every attached inbound on its own flow. Send
`"reverse": null` to remove the reverse tag. Credentials (`id`,
`password`, `auth`, `secret`, `subId`) and WireGuard/AmneziaWG
`allowedIPs` are never cleared: an empty value keeps the stored one.
The inbounds are applied concurrently and independently: one that
fails no longer stops the others. Every inbound error names the
inbound it came from (`inbound 7: <message>`), and several failures
are reported together, one per line. So a `success:false` response can
still have applied the edit to the remaining inbounds. The client
record is written after the inbounds, so a failure there is reported
without an `inbound <id>:` prefix and leaves the inbound edits in
place.'
heading: update-an-existing-client-by-email-changes-propagate-to-every-attached-inbound-body-is-the-json-client-payload-omitted-fields-keep-their-current-values-and-an-explicit-zero-empty-or-null-value-clears-a-field-except-as-noted-below-1
- content: 'The inbounds are applied concurrently and independently: one that
fails no longer stops the others. Every inbound error names the
inbound it came from (`inbound 7: <message>`), and several failures
@@ -44,10 +44,11 @@ _openapi:
- depth: 2
title: >-
Update an existing client by email. Changes propagate to every attached
inbound. Body is the JSON client payload — supply the full set of fields
you want to keep (the server replaces the row, it does not patch).
inbound. Body is the JSON client payload: omitted fields keep their
current values, and an explicit zero, empty or null value clears a
field, except as noted below.
url: >-
#update-an-existing-client-by-email-changes-propagate-to-every-attached-inbound-body-is-the-json-client-payload--supply-the-full-set-of-fields-you-want-to-keep-the-server-replaces-the-row-it-does-not-patch
#update-an-existing-client-by-email-changes-propagate-to-every-attached-inbound-body-is-the-json-client-payload-omitted-fields-keep-their-current-values-and-an-explicit-zero-empty-or-null-value-clears-a-field-except-as-noted-below
- depth: 2
title: >-
Delete a client by email. Removes it from every attached inbound and
@@ -358,11 +359,11 @@ _openapi:
create-a-new-client-and-attach-it-to-one-or-more-inbounds-in-a-single-call-body-is-json-per-protocol-secrets-are-generated-server-side-when-omitted-so-callers-can-send-only-the-universal-fields
- content: >-
Update an existing client by email. Changes propagate to every
attached inbound. Body is the JSON client payload — supply the full
set of fields you want to keep (the server replaces the row, it does
not patch).
attached inbound. Body is the JSON client payload: omitted fields keep
their current values, and an explicit zero, empty or null value clears
a field, except as noted below.
id: >-
update-an-existing-client-by-email-changes-propagate-to-every-attached-inbound-body-is-the-json-client-payload--supply-the-full-set-of-fields-you-want-to-keep-the-server-replaces-the-row-it-does-not-patch
update-an-existing-client-by-email-changes-propagate-to-every-attached-inbound-body-is-the-json-client-payload-omitted-fields-keep-their-current-values-and-an-explicit-zero-empty-or-null-value-clears-a-field-except-as-noted-below
- content: >-
Delete a client by email. Removes it from every attached inbound and
drops its traffic record unless keepTraffic=1 is passed.
@@ -44,10 +44,11 @@ _openapi:
- depth: 2
title: >-
Update an existing client by email. Changes propagate to every attached
inbound. Body is the JSON client payload — supply the full set of fields
you want to keep (the server replaces the row, it does not patch).
inbound. Body is the JSON client payload: omitted fields keep their
current values, and an explicit zero, empty or null value clears a
field, except as noted below.
url: >-
#update-an-existing-client-by-email-changes-propagate-to-every-attached-inbound-body-is-the-json-client-payload--supply-the-full-set-of-fields-you-want-to-keep-the-server-replaces-the-row-it-does-not-patch
#update-an-existing-client-by-email-changes-propagate-to-every-attached-inbound-body-is-the-json-client-payload-omitted-fields-keep-their-current-values-and-an-explicit-zero-empty-or-null-value-clears-a-field-except-as-noted-below
- depth: 2
title: >-
Delete a client by email. Removes it from every attached inbound and
@@ -358,11 +359,11 @@ _openapi:
create-a-new-client-and-attach-it-to-one-or-more-inbounds-in-a-single-call-body-is-json-per-protocol-secrets-are-generated-server-side-when-omitted-so-callers-can-send-only-the-universal-fields
- content: >-
Update an existing client by email. Changes propagate to every
attached inbound. Body is the JSON client payload — supply the full
set of fields you want to keep (the server replaces the row, it does
not patch).
attached inbound. Body is the JSON client payload: omitted fields keep
their current values, and an explicit zero, empty or null value clears
a field, except as noted below.
id: >-
update-an-existing-client-by-email-changes-propagate-to-every-attached-inbound-body-is-the-json-client-payload--supply-the-full-set-of-fields-you-want-to-keep-the-server-replaces-the-row-it-does-not-patch
update-an-existing-client-by-email-changes-propagate-to-every-attached-inbound-body-is-the-json-client-payload-omitted-fields-keep-their-current-values-and-an-explicit-zero-empty-or-null-value-clears-a-field-except-as-noted-below
- content: >-
Delete a client by email. Removes it from every attached inbound and
drops its traffic record unless keepTraffic=1 is passed.
@@ -35,10 +35,10 @@ _openapi:
#create-a-new-client-and-attach-it-to-one-or-more-inbounds-in-a-single-call-body-is-json-per-protocol-secrets-are-generated-server-side-when-omitted-so-callers-can-send-only-the-universal-fields
- depth: 2
title: >-
按 email 更新现有客户端。变更会传播到每个挂载的入站。请求体为 JSON 客户端载荷——
请提供你希望保留的完整字段集(服务端会替换整条记录,而非局部更新)。
按 email 更新现有客户端。变更会传播到每个挂载的入站。请求体为 JSON 客户端载荷:
省略的字段保留当前值,显式的零值、空值或 null 会清除该字段(例外见下文)。
url: >-
#update-an-existing-client-by-email-changes-propagate-to-every-attached-inbound-body-is-the-json-client-payload--supply-the-full-set-of-fields-you-want-to-keep-the-server-replaces-the-row-it-does-not-patch
#update-an-existing-client-by-email-changes-propagate-to-every-attached-inbound-body-is-the-json-client-payload-omitted-fields-keep-their-current-values-and-an-explicit-zero-empty-or-null-value-clears-a-field-except-as-noted-below
- depth: 2
title: >-
按 email 删除客户端。将其从每个挂载的入站中移除,并删除其流量记录,除非传入
@@ -292,10 +292,10 @@ _openapi:
id: >-
create-a-new-client-and-attach-it-to-one-or-more-inbounds-in-a-single-call-body-is-json-per-protocol-secrets-are-generated-server-side-when-omitted-so-callers-can-send-only-the-universal-fields
- content: >-
按 email 更新现有客户端。变更会传播到每个挂载的入站。请求体为 JSON 客户端载荷——
请提供你希望保留的完整字段集(服务端会替换整条记录,而非局部更新)。
按 email 更新现有客户端。变更会传播到每个挂载的入站。请求体为 JSON 客户端载荷:
省略的字段保留当前值,显式的零值、空值或 null 会清除该字段(例外见下文)。
id: >-
update-an-existing-client-by-email-changes-propagate-to-every-attached-inbound-body-is-the-json-client-payload--supply-the-full-set-of-fields-you-want-to-keep-the-server-replaces-the-row-it-does-not-patch
update-an-existing-client-by-email-changes-propagate-to-every-attached-inbound-body-is-the-json-client-payload-omitted-fields-keep-their-current-values-and-an-explicit-zero-empty-or-null-value-clears-a-field-except-as-noted-below
- content: >-
按 email 删除客户端。将其从每个挂载的入站中移除,并删除其流量记录,除非传入
keepTraffic=1。