Skip to content

Commit e67b595

Browse files
authored
[Site Admin API] App Feature Config API- #5852
ref DEV-3687
2 parents 9619632 + 48a9519 commit e67b595

20 files changed

Lines changed: 2724 additions & 2 deletions

.make-lint-expect

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ pkg/api/apierrors/tags.go cannot import github.com/authgear/authgear-server/pkg/
55
pkg/api/event/blocking/util.go cannot import github.com/authgear/authgear-server/pkg/util/accesscontrol
66
pkg/api/event/event.go cannot import github.com/authgear/authgear-server/pkg/util/accesscontrol
77
pkg/api/event/hook_response.go cannot import github.com/authgear/authgear-server/pkg/util/validation
8+
pkg/api/siteadmin/gen.go cannot import github.com/authgear/authgear-server/pkg/lib/config
89

910
exit status 1
1011
pkg/lib/deps/deps_common.go cannot import github.com/authgear/authgear-server/pkg/latte/proofofphonenumberverification

.vettedpositions

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,11 @@
323323
/pkg/portal/transport/stripe_webhook_handler.go:49:9: requestcontext
324324
/pkg/portal/transport/system_config_handler.go:28:10: requestcontext
325325
/pkg/resolver/handler/resolve.go:40:9: requestcontext
326+
/pkg/siteadmin/transport/handler_app_feature_config_get.go:28:47: requestcontext
327+
/pkg/siteadmin/transport/handler_app_feature_config_preview.go:45:75: requestcontext
328+
/pkg/siteadmin/transport/handler_app_feature_config_preview.go:60:51: requestcontext
329+
/pkg/siteadmin/transport/handler_app_feature_config_update.go:47:74: requestcontext
330+
/pkg/siteadmin/transport/handler_app_feature_config_update.go:62:50: requestcontext
326331
/pkg/siteadmin/transport/handler_app_get.go:40:33: requestcontext
327332
/pkg/siteadmin/transport/handler_app_plan_change.go:47:65: requestcontext
328333
/pkg/siteadmin/transport/handler_app_plan_change.go:63:38: requestcontext

docs/api/siteadmin-api.yaml

Lines changed: 288 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,153 @@ paths:
202202
"404":
203203
$ref: "#/components/responses/NotFound"
204204

205+
/api/v1/apps/{app_id}/feature-config:
206+
get:
207+
operationId: getAppFeatureConfig
208+
summary: Get the feature config layers of an app
209+
description: >
210+
Returns three layers of the app's feature config, all fully populated
211+
with server defaults so the client never has to reason about what is
212+
"unset": the effective **plan** config (defaults ← plan, i.e. what the
213+
app would get with no app-specific override — display this as the
214+
read-only "Plan" column/panel), the app-specific override YAML
215+
document as stored (verbatim, sparse — only the fields this app
216+
overrides), and the effective config for the app as it actually runs
217+
today (defaults ← plan ← app override). The server merges every
218+
section field-by-field (see `pkg/lib/config/feature_*.go` — every
219+
section's `Merge` is field-level, so setting one field never resets an
220+
unrelated sibling field back to a default); clients MUST NOT
221+
reimplement this merge — always use `effective_plan_feature_config`
222+
and `effective_app_feature_config` from this response for display, and the
223+
preview endpoint for live feedback while editing.
224+
parameters:
225+
- name: app_id
226+
in: path
227+
required: true
228+
schema:
229+
type: string
230+
responses:
231+
"200":
232+
description: The feature config layers of the app
233+
content:
234+
application/json:
235+
schema:
236+
$ref: "#/components/schemas/AppFeatureConfigResponse"
237+
"400":
238+
$ref: "#/components/responses/BadRequest"
239+
"403":
240+
$ref: "#/components/responses/Forbidden"
241+
"404":
242+
$ref: "#/components/responses/NotFound"
243+
244+
put:
245+
operationId: updateAppFeatureConfig
246+
summary: Replace the app-specific feature config override
247+
description: >
248+
Replaces the entire app-specific feature config override with the
249+
given YAML document. This is a full replacement, not a patch: any
250+
field previously overridden but omitted from the new document reverts
251+
to inheriting from the plan. The document is stored verbatim,
252+
preserving comments and key order. Pass an empty string (or a
253+
whitespace-only string) to clear all app-specific overrides. The
254+
document is validated against the feature config JSON schema before
255+
being stored; on failure, responds 400 with reason `ValidationFailed`
256+
and `info.causes` populated per
257+
[ValidationErrorCause](#/components/schemas/ValidationErrorCause) —
258+
each cause's `location` is an RFC 6901 JSON pointer into the submitted
259+
document (e.g. `/identity/oauth/providers/apple/disabled`), letting the
260+
client map every failure directly to the corresponding row in a table
261+
view without any custom error-parsing logic. The plan config itself is
262+
never modified by this endpoint. A site admin audit log entry
263+
(`site_admin.app.feature_config.updated`) is recorded.
264+
parameters:
265+
- name: app_id
266+
in: path
267+
required: true
268+
schema:
269+
type: string
270+
requestBody:
271+
required: true
272+
content:
273+
application/json:
274+
schema:
275+
$ref: "#/components/schemas/UpdateAppFeatureConfigRequest"
276+
responses:
277+
"200":
278+
description: >
279+
The feature config layers after the update, including the newly
280+
computed effective config.
281+
content:
282+
application/json:
283+
schema:
284+
$ref: "#/components/schemas/AppFeatureConfigResponse"
285+
"400":
286+
description: >
287+
Invalid request. When `reason` is `ValidationFailed`,
288+
`info.causes` is an array of ValidationErrorCause objects, one per
289+
failing field — see the `ValidationErrorCause` schema and the
290+
description above for the exact shape and an example.
291+
content:
292+
application/json:
293+
schema:
294+
$ref: "#/components/schemas/ErrorEnvelope"
295+
"403":
296+
$ref: "#/components/responses/Forbidden"
297+
"404":
298+
$ref: "#/components/responses/NotFound"
299+
300+
/api/v1/apps/{app_id}/feature-config/preview:
301+
post:
302+
operationId: previewAppFeatureConfig
303+
summary: Preview the effective feature config for a candidate override
304+
description: >
305+
Computes the effective feature config that would result from the given
306+
app-specific override, without persisting anything. Merging uses the
307+
app's current plan and the exact same server-side merge as the GET and
308+
PUT endpoints. This is the mechanism that keeps merge logic out of the
309+
client entirely: call this endpoint (debounced) on every edit to
310+
refresh a live "Effective" view — never compute it from
311+
`effective_plan_feature_config` and the edited YAML locally. The
312+
candidate override is validated against the feature config JSON
313+
schema exactly as PUT does; validation failures use the same 400
314+
`ValidationFailed` / `info.causes` shape described on PUT, so the same
315+
client-side error-highlighting code handles both endpoints.
316+
parameters:
317+
- name: app_id
318+
in: path
319+
required: true
320+
schema:
321+
type: string
322+
requestBody:
323+
required: true
324+
content:
325+
application/json:
326+
schema:
327+
$ref: "#/components/schemas/PreviewAppFeatureConfigRequest"
328+
responses:
329+
"200":
330+
description: >
331+
The feature config layers as they would be if the candidate
332+
override were saved. Nothing is persisted.
333+
content:
334+
application/json:
335+
schema:
336+
$ref: "#/components/schemas/AppFeatureConfigResponse"
337+
"400":
338+
description: >
339+
Invalid request. When `reason` is `ValidationFailed`,
340+
`info.causes` is an array of ValidationErrorCause objects, one per
341+
failing field — see the `ValidationErrorCause` schema and the
342+
description on PUT for the exact shape and an example.
343+
content:
344+
application/json:
345+
schema:
346+
$ref: "#/components/schemas/ErrorEnvelope"
347+
"403":
348+
$ref: "#/components/responses/Forbidden"
349+
"404":
350+
$ref: "#/components/responses/NotFound"
351+
205352
/api/v1/apps/{app_id}/usage/messaging:
206353
get:
207354
operationId: getAppMessagingUsage
@@ -507,7 +654,13 @@ components:
507654
description: Opaque ID for correlating errors in logs
508655
info:
509656
type: object
510-
description: Additional structured details about the error
657+
description: >
658+
Additional structured details about the error. Shape depends on
659+
`reason`. Notably, for `reason: ValidationFailed` (JSON-schema
660+
validation failures — this is the server-wide convention, not
661+
specific to any one endpoint), `info.causes` is an array of
662+
[ValidationErrorCause](#/components/schemas/ValidationErrorCause)
663+
objects, one per failing field.
511664
additionalProperties: true
512665

513666
MessagingUsage:
@@ -804,3 +957,137 @@ components:
804957
Raw audit log data — the full event JSON stored in the
805958
_audit_log.data column, including context and payload.
806959
additionalProperties: true
960+
961+
ValidationErrorCause:
962+
description: >
963+
One JSON-schema validation failure. Matches the server's
964+
`validation.Error` struct (`pkg/util/validation/error.go`) — this is
965+
the server-wide shape for every `ValidationFailed` error in this API,
966+
not something specific to feature config. Emitted as one entry per
967+
failing leaf value; a client can group/sort by `location` to render
968+
multiple errors against the same document.
969+
type: object
970+
required:
971+
- location
972+
- kind
973+
properties:
974+
location:
975+
type: string
976+
description: >
977+
RFC 6901 JSON pointer to the failing value in the submitted
978+
document, e.g. `/identity/oauth/providers/apple/disabled` or
979+
`/oauth/client/maximum`. Empty string means the failure applies to
980+
the document root. Clients use this to look up the corresponding
981+
field in a declarative field registry and highlight that row.
982+
kind:
983+
type: string
984+
description: >
985+
The JSON schema keyword that failed, e.g. `type`, `required`,
986+
`enum`, `minimum`, `additionalProperties`. Use `details` and/or a
987+
generic per-kind message for display; do not assume every kind
988+
listed here is exhaustive — new keywords can appear as the schema
989+
evolves.
990+
details:
991+
type: object
992+
description: >
993+
Keyword-specific detail payload, e.g. `{"actual": "string",
994+
"expected": "boolean"}` for a `type` failure, or `{"missing":
995+
["client_id"]}` for a `required` failure. Shape varies by `kind`;
996+
treat as opaque beyond displaying it or using known keys
997+
defensively.
998+
additionalProperties: true
999+
1000+
FeatureConfig:
1001+
x-go-type: config.FeatureConfig
1002+
x-go-type-import:
1003+
name: config
1004+
path: github.com/authgear/authgear-server/pkg/lib/config
1005+
description: >
1006+
An Authgear feature config object (the JSON form of
1007+
authgear.features.yaml). The authoritative schema is the server's
1008+
feature config JSON schema (FeatureConfigSchema in
1009+
pkg/lib/config); it is not duplicated here. Top-level sections
1010+
include identity, authentication, authenticator, custom_domain, ui,
1011+
oauth, hook, audit_log, google_tag_manager, rate_limits, messaging,
1012+
usage, collaborator, admin_api, test_mode, and fraud_protection. Every
1013+
section merges field-by-field across layers (code defaults ← cluster
1014+
← plan ← app override) — an app-specific override only ever affects
1015+
the exact fields it sets; every other field simply inherits from the
1016+
plan.
1017+
type: object
1018+
additionalProperties: true
1019+
1020+
AppFeatureConfigResponse:
1021+
type: object
1022+
required:
1023+
- plan_name
1024+
- effective_plan_feature_config
1025+
- app_feature_config_yaml
1026+
- effective_app_feature_config
1027+
properties:
1028+
plan_name:
1029+
type: string
1030+
description: The plan the app is currently on.
1031+
effective_plan_feature_config:
1032+
allOf:
1033+
- $ref: "#/components/schemas/FeatureConfig"
1034+
description: >
1035+
The **effective** feature config of the plan alone — server
1036+
defaults merged with the plan's config, fully populated. This is
1037+
what the app would get if it had no app-specific override at all;
1038+
display this as the read-only "Plan" column/panel. It is
1039+
deliberately not the raw/sparse plan document: since merge is
1040+
field-level everywhere, the effective value is always well-defined
1041+
and there is no ambiguity between "the plan set this" and "this is
1042+
a code default" for the client to worry about — every value shown
1043+
here is simply what the app would actually run with under this
1044+
plan.
1045+
app_feature_config_yaml:
1046+
type: string
1047+
description: >
1048+
The stored app-specific override YAML document, verbatim —
1049+
including comments and key order. Empty string when the app has
1050+
no override. Sparse: only fields explicitly overridden for this
1051+
app are present. Clients parse this to render structured views
1052+
(e.g. a table); any field absent from this document simply
1053+
inherits from `effective_plan_feature_config`.
1054+
effective_app_feature_config:
1055+
allOf:
1056+
- $ref: "#/components/schemas/FeatureConfig"
1057+
description: >
1058+
The effective feature config computed by the server by merging
1059+
defaults ← plan ← app override, fully populated. This is what the
1060+
app actually runs with; use it for the "Effective" display. Always
1061+
re-fetch this (via GET after save, or via the preview endpoint
1062+
while editing) rather than deriving it from
1063+
`effective_plan_feature_config` and `app_feature_config_yaml`
1064+
locally.
1065+
1066+
UpdateAppFeatureConfigRequest:
1067+
type: object
1068+
required:
1069+
- app_feature_config_yaml
1070+
properties:
1071+
app_feature_config_yaml:
1072+
type: string
1073+
description: >
1074+
The new app-specific feature config override as a YAML document.
1075+
Replaces the existing override entirely and is stored verbatim,
1076+
preserving comments and key order. Pass an empty string (or a
1077+
whitespace-only string) to clear all overrides. Validated against
1078+
the feature config JSON schema; see the `ValidationErrorCause`
1079+
schema for the shape of validation failures. Since YAML is a
1080+
superset of JSON, automation may pass a JSON document here.
1081+
1082+
PreviewAppFeatureConfigRequest:
1083+
type: object
1084+
required:
1085+
- app_feature_config_yaml
1086+
properties:
1087+
app_feature_config_yaml:
1088+
type: string
1089+
description: >
1090+
The candidate app-specific feature config override as a YAML
1091+
document. Not persisted. Validated against the feature config
1092+
JSON schema exactly as PUT does; see the `ValidationErrorCause`
1093+
schema for the shape of validation failures.

0 commit comments

Comments
 (0)