openxiangda 2.0.0-alpha.48 → 2.0.0-alpha.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/application.d.ts +3 -2
- package/dist/browser/application.d.ts.map +1 -1
- package/dist/browser/application.js +96 -7
- package/dist/browser/application.js.map +1 -1
- package/dist/browser/platform-client.d.ts +47 -0
- package/dist/browser/platform-client.d.ts.map +1 -1
- package/dist/browser/platform-client.js +125 -0
- package/dist/browser/platform-client.js.map +1 -1
- package/dist/browser/route-manifest.d.ts +33 -0
- package/dist/browser/route-manifest.d.ts.map +1 -1
- package/dist/browser/route-manifest.js +128 -0
- package/dist/browser/route-manifest.js.map +1 -1
- package/dist/browser/runtime.d.ts +3 -2
- package/dist/browser/runtime.d.ts.map +1 -1
- package/dist/browser/runtime.js +61 -3
- package/dist/browser/runtime.js.map +1 -1
- package/dist/react.d.ts +1 -0
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +1 -0
- package/dist/react.js.map +1 -1
- package/package.json +7 -7
- package/skills/manifest.json +2 -2
- package/skills/openxiangda-v2/SKILL.md +11 -9
- package/skills/openxiangda-v2/agents/openai.yaml +1 -1
- package/skills/openxiangda-v2/references/data-authz.md +6 -0
- package/skills/openxiangda-v2/references/frontend.md +16 -0
- package/skills/openxiangda-v2/references/public-access.md +159 -0
- package/skills/openxiangda-v2/references/testing.md +8 -0
- package/skills/openxiangda-v2/references/workspace.md +1 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# OpenXiangda 2.0 Anonymous Public Access
|
|
2
|
+
|
|
3
|
+
Read this reference whenever the requirement includes an external person without
|
|
4
|
+
a platform account, anonymous or guest access, a public form/page, resumable
|
|
5
|
+
submission, duplicate checks, public attachment upload, or reading the visitor's
|
|
6
|
+
own submitted records.
|
|
7
|
+
|
|
8
|
+
## Decide the boundary first
|
|
9
|
+
|
|
10
|
+
Record these choices before editing:
|
|
11
|
+
|
|
12
|
+
- the one static public route and one Native resource it serves;
|
|
13
|
+
- the exact fields the visitor may write and receive;
|
|
14
|
+
- whether drafts are needed and their bounded inactivity lifetime;
|
|
15
|
+
- whether the page needs `own.list`, `own.read`, or both;
|
|
16
|
+
- every named duplicate validation and its exact field tuple;
|
|
17
|
+
- file fields and their normal resource-level type, count, size and MIME limits;
|
|
18
|
+
- same-browser-only acceptance and the negative browser/device cases.
|
|
19
|
+
|
|
20
|
+
This contract identifies possession of one browser profile, not a natural person.
|
|
21
|
+
Clearing the platform HttpOnly cookie, private browsing, another browser or another
|
|
22
|
+
device creates a new anonymous visitor. Do not promise recovery, merge or
|
|
23
|
+
cross-device continuity. WeChat, DingTalk, SMS/email verification and automatic
|
|
24
|
+
platform-account creation require later, separate identity contracts.
|
|
25
|
+
|
|
26
|
+
## Declare the one public policy
|
|
27
|
+
|
|
28
|
+
Declare the resource normally, then add one static `surface: 'user'` route and one
|
|
29
|
+
`frontend.publicAccess` policy in `openxiangda.config.ts`:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
export default defineOpenXiangdaApp({
|
|
33
|
+
// ...app, authz and data declarations...
|
|
34
|
+
frontend: {
|
|
35
|
+
root: 'apps/web',
|
|
36
|
+
routes: [
|
|
37
|
+
{
|
|
38
|
+
code: 'visitor-apply',
|
|
39
|
+
path: '/visitor/apply',
|
|
40
|
+
label: '访客预约',
|
|
41
|
+
surface: 'user',
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
publicAccess: {
|
|
45
|
+
policies: [
|
|
46
|
+
{
|
|
47
|
+
code: 'visitor-apply-public',
|
|
48
|
+
routeCode: 'visitor-apply',
|
|
49
|
+
mode: 'anonymous',
|
|
50
|
+
resourceCode: 'visitor-requests',
|
|
51
|
+
operations: [
|
|
52
|
+
'draft.read',
|
|
53
|
+
'draft.update',
|
|
54
|
+
'validate',
|
|
55
|
+
'create',
|
|
56
|
+
'own.list',
|
|
57
|
+
'own.read',
|
|
58
|
+
],
|
|
59
|
+
fields: ['visitorName', 'phone', 'visitDate', 'photo'],
|
|
60
|
+
requiredFields: ['visitorName', 'phone', 'visitDate'],
|
|
61
|
+
ownRecordFields: ['visitorName', 'phone', 'visitDate', 'photo'],
|
|
62
|
+
draft: {
|
|
63
|
+
enabled: true,
|
|
64
|
+
inactivityTtlSeconds: 2_592_000,
|
|
65
|
+
maxBytes: 262_144,
|
|
66
|
+
},
|
|
67
|
+
validations: [
|
|
68
|
+
{
|
|
69
|
+
code: 'phone-unused',
|
|
70
|
+
kind: 'duplicate',
|
|
71
|
+
fields: ['phone'],
|
|
72
|
+
result: 'availability',
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The route must be static and must not also declare `capability` or `access`.
|
|
83
|
+
`fields`, `requiredFields`, `ownRecordFields` and validation fields must reference
|
|
84
|
+
declared fields on the same resource. Public create must cover every writable
|
|
85
|
+
required business field on that Native resource; `check` rejects an incomplete or
|
|
86
|
+
widened declaration.
|
|
87
|
+
|
|
88
|
+
Request only the operations the page uses:
|
|
89
|
+
|
|
90
|
+
- `draft.read` and `draft.update` resume the current browser's active draft;
|
|
91
|
+
- `validate` runs only named availability checks and never returns matching rows;
|
|
92
|
+
- `create` performs the final idempotent submission;
|
|
93
|
+
- `own.list` returns a bounded, server-ordered page of this browser's submitted
|
|
94
|
+
records;
|
|
95
|
+
- `own.read` returns one submitted record only after the server repeats the owner
|
|
96
|
+
and submitted-draft receipt checks.
|
|
97
|
+
|
|
98
|
+
## Use the generated browser client
|
|
99
|
+
|
|
100
|
+
The template already passes generated `anonymousPublicAccess` to
|
|
101
|
+
`OpenXiangdaApplication`. Bind the route through the normal generated `appRoutes`
|
|
102
|
+
contribution, then use only the dedicated client in that page:
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
import { createAnonymousPublicClient } from 'openxiangda/react';
|
|
106
|
+
|
|
107
|
+
const publicClient = createAnonymousPublicClient({ routeCode: 'visitor-apply' });
|
|
108
|
+
|
|
109
|
+
const session = await publicClient.bootstrap();
|
|
110
|
+
const draft = session.draft ?? await publicClient.currentDraft();
|
|
111
|
+
const saved = await publicClient.saveDraft(draft.revision, {
|
|
112
|
+
visitorName,
|
|
113
|
+
phone,
|
|
114
|
+
visitDate,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const availability = await publicClient.validate('phone-unused', { phone });
|
|
118
|
+
const photo = await publicClient.upload('photo', file);
|
|
119
|
+
const withPhoto = await publicClient.saveDraft(saved.revision, { photo });
|
|
120
|
+
const receipt = await publicClient.submit(
|
|
121
|
+
withPhoto.revision,
|
|
122
|
+
crypto.randomUUID(),
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const page = await publicClient.listOwn({ pageSize: 20 });
|
|
126
|
+
const detail = await publicClient.getOwn(receipt.recordId);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Call `bootstrap()` before other methods and reuse the same idempotency key when
|
|
130
|
+
retrying one uncertain submission. Draft writes use the returned revision as CAS;
|
|
131
|
+
on conflict, reload the current draft instead of overwriting it. Treat duplicate
|
|
132
|
+
preflight as UI feedback only: the platform repeats validation atomically during
|
|
133
|
+
submission.
|
|
134
|
+
|
|
135
|
+
Never call the general Native Data API from this page, construct `created_by`,
|
|
136
|
+
accept a caller-selected draft id, query arbitrary filters/order/count, expose an
|
|
137
|
+
object-store credential, or use a NestJS action as anonymous CRUD. Never store an
|
|
138
|
+
identity in local storage or derive ownership from IP, user-agent or a browser
|
|
139
|
+
fingerprint. IP may participate only in platform rate limiting.
|
|
140
|
+
|
|
141
|
+
## Acceptance
|
|
142
|
+
|
|
143
|
+
After `pnpm openxiangda check --json`, verify the exact preproduction route in real
|
|
144
|
+
browsers:
|
|
145
|
+
|
|
146
|
+
1. a fresh browser can bootstrap, save, reload and resume one draft;
|
|
147
|
+
2. declared file upload/preview and final submission succeed;
|
|
148
|
+
3. named validation returns only `available` or `duplicate`, and concurrent final
|
|
149
|
+
duplicate submissions cannot both succeed;
|
|
150
|
+
4. the same browser can list and open its submitted records;
|
|
151
|
+
5. another browser gets an empty list and the same record id returns 404;
|
|
152
|
+
6. clearing the browser credential loses draft/history access as documented;
|
|
153
|
+
7. undeclared routes, fields, operations, validations and ordinary Data API calls
|
|
154
|
+
remain denied;
|
|
155
|
+
8. production uses HTTPS and no public object-storage bucket or anonymous upload
|
|
156
|
+
whitelist was introduced.
|
|
157
|
+
|
|
158
|
+
Read [Data and authorization](data-authz.md), [Frontend](frontend.md) and
|
|
159
|
+
[Testing](testing.md) for the surrounding resource, page and delivery checks.
|
|
@@ -22,6 +22,14 @@ For each changed resource, verify the real chain:
|
|
|
22
22
|
5. stored values and audit records match the declared shape;
|
|
23
23
|
6. PostgreSQL/RLS remains authoritative, including current-user and multi-role-union cases.
|
|
24
24
|
|
|
25
|
+
For `frontend.publicAccess`, add a separate anonymous-browser matrix. Verify draft
|
|
26
|
+
resume, declared file upload, named availability validation, idempotent submission,
|
|
27
|
+
and same-browser `own.list`/`own.read`. In a second browser verify an empty list and
|
|
28
|
+
404 for the first browser's record id. Clear the first browser credential and
|
|
29
|
+
confirm access is lost as documented. Also prove undeclared routes, fields,
|
|
30
|
+
operations, validations and ordinary Data API calls remain denied. A logged-in
|
|
31
|
+
admin path or a mocked client does not close anonymous public acceptance.
|
|
32
|
+
|
|
25
33
|
For platform generator changes, add a representative multi-resource fixture
|
|
26
34
|
(the compatibility corpus fixes 43 resources, complete list/form/detail/mobile
|
|
27
35
|
surfaces, Perspective/AuthZ/Workflow/Event and exactly 161 producers), assert
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
- Use Vite, React Router, Refine Core and Ant Design. Do not add Umi, ProComponents or another admin shell.
|
|
6
6
|
- Bind every generated `appRoutes` entry to its local page with `defineApplicationContributions`; desktop `admin` routes stay inside the platform Shell, while `user` routes render without an admin Shell for independent mobile/user experiences. Generated resource CRUD routes are compiler-owned under `/admin/resources/<resourceCode>...` and `/m/admin/resources/<resourceCode>...`; never recreate root resource paths, aliases or redirects. Explicit routes that have the same canonical shape as another explicit or generated route fail compilation, even when dynamic parameter names differ. Use only the typed `toolbar`, `row` and `detail` resource slots for generated resource actions. Declare the complete editable admin menu with `defineAdminNavigation` and its page/group helpers; the Shell renders only generated `adminNavigation` references and permissions only filter them. Do not create another router, menu store, layout, identity provider, permission store or copied CRUD page; route/action access uses capability or `allOf`/`anyOf`, while Data/App API and Workflow authorization remain server-owned.
|
|
7
7
|
- Application login is optional `frontend.authentication`: existing platform users only, registration rejected, exact desktop `/login` and mobile `/m/login`. Bind generated `authenticationSurfaces` to separate PC/mobile renderers through `defineApplicationContributions`; renderers own only brand visuals and call `ApplicationLoginSurfaceProps`. The platform alone owns passwords, providers, OAuth state/callbacks, secure cookies, current identity and authorization. Never put login in protected `appRoutes`, call a v1 auth route, store tokens, create users/roles, or add another Router/identity provider. Keep QA in generated `platformAuthManifest`, outside the protected route denominator.
|
|
8
|
+
- External users without platform accounts use only an exact static `surface: 'user'` route declared through `frontend.publicAccess`. Declare the one resource, bounded field sets, required operations, draft limits and named duplicate validations; consume only generated `anonymousPublicAccess` and `createAnonymousPublicClient`. `own.list`/`own.read` mean records submitted by the same platform-issued HttpOnly browser credential, not a verified natural person, and another browser or cleared cookie intentionally loses access. Never create a guest role/user, call the general Native Data API, expose an anonymous upload path, store identity locally or derive ownership from IP, user-agent or fingerprint.
|
|
8
9
|
- Declare each resource once in `openxiangda.config.ts` with only `code`, `name`, `fields` and optional `mutationOwner`, generated/list/layout/data-policy settings. Each field owns type, label, required state, Surface flags, reference/file metadata and access. Resource codes are lower kebab-case. Use `native` for direct Data API mutations, `action`, `readonly` or `workflow` for non-Native ownership; never grant or generate Native mutation for a non-Native owner.
|
|
9
10
|
- Never write resource-level `schemaVersion`, `appCode`, `schema`, `surface`, `capabilities`, `fieldPolicies` or `platform/data` modules. The compiler derives the strict DataResource, CRUD capabilities, Surface and AI Schema. The application manifest still starts with its one top-level `schemaVersion: 3`.
|
|
10
11
|
- Ordinary list/get/create/update/delete, filters, export and batch operations use the platform Native Data API. Do not create Function CRUD or NestJS wrappers.
|