@panneau/protocol 5.0.0-alpha.2 → 5.0.0-alpha.4
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/conformance/index.d.ts.map +1 -1
- package/dist/conformance/index.js +64 -1
- package/dist/conformance/index.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/lint.d.ts +2 -1
- package/dist/lint.d.ts.map +1 -1
- package/dist/lint.js +27 -1
- package/dist/lint.js.map +1 -1
- package/dist/mock/example.d.ts +2 -1
- package/dist/mock/example.d.ts.map +1 -1
- package/dist/mock/example.js +218 -141
- package/dist/mock/example.js.map +1 -1
- package/dist/mock/index.d.ts.map +1 -1
- package/dist/mock/index.js +165 -6
- package/dist/mock/index.js.map +1 -1
- package/dist/openapi.d.ts.map +1 -1
- package/dist/openapi.js +20 -95
- package/dist/openapi.js.map +1 -1
- package/dist/schemas/action.d.ts +1 -0
- package/dist/schemas/action.d.ts.map +1 -1
- package/dist/schemas/action.js +1 -0
- package/dist/schemas/action.js.map +1 -1
- package/dist/schemas/api.d.ts +114 -82
- package/dist/schemas/api.d.ts.map +1 -1
- package/dist/schemas/api.js +70 -27
- package/dist/schemas/api.js.map +1 -1
- package/dist/schemas/definition.d.ts +2 -0
- package/dist/schemas/definition.d.ts.map +1 -1
- package/dist/schemas/definition.js +2 -2
- package/dist/schemas/definition.js.map +1 -1
- package/dist/schemas/field.js +3 -3
- package/dist/schemas/field.js.map +1 -1
- package/dist/schemas/index.d.ts +110 -79
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +11 -5
- package/dist/schemas/index.js.map +1 -1
- package/dist/schemas/resource.d.ts +2 -0
- package/dist/schemas/resource.d.ts.map +1 -1
- package/dist/schemas/resource.js +1 -0
- package/dist/schemas/resource.js.map +1 -1
- package/package.json +1 -1
- package/schema/boot.schema.json +7 -0
- package/schema/definition.schema.json +7 -0
- package/schema/openapi.json +317 -462
package/dist/mock/example.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
//#region src/mock/example.ts
|
|
2
2
|
/**
|
|
3
3
|
* A small but complete definition: pages with localized titles, a status, a publication date shown
|
|
4
|
-
* for published pages, an author and typed blocks, in tabs and a sidebar, plus
|
|
4
|
+
* for published pages, an author, an attachment and typed blocks, in tabs and a sidebar, plus
|
|
5
|
+
* authors with a photo, and a media library. For tests
|
|
5
6
|
* and stories, with `createMockBackend()`.
|
|
6
7
|
*/
|
|
7
8
|
function createExampleDefinition() {
|
|
@@ -11,164 +12,239 @@ function createExampleDefinition() {
|
|
|
11
12
|
locale: "fr",
|
|
12
13
|
locales: ["fr", "en"],
|
|
13
14
|
api: { baseUrl: "/panneau/api" },
|
|
14
|
-
resources: [
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
itemTitle: "title",
|
|
21
|
-
fields: [
|
|
22
|
-
{
|
|
23
|
-
name: "title",
|
|
24
|
-
type: "text",
|
|
25
|
-
label: "Titre",
|
|
26
|
-
localized: true,
|
|
27
|
-
rules: [{ rule: "required" }, {
|
|
28
|
-
rule: "max",
|
|
29
|
-
value: 120
|
|
30
|
-
}]
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
name: "status",
|
|
34
|
-
type: "select",
|
|
35
|
-
options: [{
|
|
36
|
-
value: "draft",
|
|
37
|
-
label: "Brouillon"
|
|
38
|
-
}, {
|
|
39
|
-
value: "published",
|
|
40
|
-
label: "Publiée"
|
|
41
|
-
}],
|
|
42
|
-
rules: [{ rule: "required" }, {
|
|
43
|
-
rule: "in",
|
|
44
|
-
value: ["draft", "published"]
|
|
45
|
-
}]
|
|
15
|
+
resources: [
|
|
16
|
+
{
|
|
17
|
+
id: "pages",
|
|
18
|
+
label: {
|
|
19
|
+
singular: "page",
|
|
20
|
+
plural: "pages"
|
|
46
21
|
},
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
22
|
+
itemTitle: "title",
|
|
23
|
+
fields: [
|
|
24
|
+
{
|
|
25
|
+
name: "title",
|
|
26
|
+
type: "text",
|
|
27
|
+
label: "Titre",
|
|
28
|
+
localized: true,
|
|
29
|
+
rules: [{ rule: "required" }, {
|
|
30
|
+
rule: "max",
|
|
31
|
+
value: 120
|
|
32
|
+
}]
|
|
53
33
|
},
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
fields: [{
|
|
68
|
-
name: "body",
|
|
69
|
-
type: "richtext",
|
|
70
|
-
rules: [{ rule: "required" }]
|
|
34
|
+
{
|
|
35
|
+
name: "status",
|
|
36
|
+
type: "select",
|
|
37
|
+
options: [{
|
|
38
|
+
value: "draft",
|
|
39
|
+
label: "Brouillon"
|
|
40
|
+
}, {
|
|
41
|
+
value: "published",
|
|
42
|
+
label: "Publiée"
|
|
43
|
+
}],
|
|
44
|
+
rules: [{ rule: "required" }, {
|
|
45
|
+
rule: "in",
|
|
46
|
+
value: ["draft", "published"]
|
|
71
47
|
}]
|
|
72
|
-
},
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "published_at",
|
|
51
|
+
type: "datetime",
|
|
52
|
+
visibleWhen: {
|
|
53
|
+
field: "status",
|
|
54
|
+
equals: "published"
|
|
55
|
+
},
|
|
56
|
+
rules: [{ rule: "required" }]
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: "author",
|
|
60
|
+
type: "relation",
|
|
61
|
+
resource: "authors"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: "blocks",
|
|
65
|
+
type: "repeater",
|
|
66
|
+
types: [{
|
|
67
|
+
id: "text",
|
|
68
|
+
label: "Texte",
|
|
69
|
+
fields: [{
|
|
70
|
+
name: "body",
|
|
71
|
+
type: "richtext",
|
|
72
|
+
rules: [{ rule: "required" }]
|
|
73
|
+
}]
|
|
74
|
+
}, {
|
|
75
|
+
id: "image",
|
|
76
|
+
label: "Image",
|
|
77
|
+
fields: [{
|
|
78
|
+
name: "image",
|
|
79
|
+
type: "media",
|
|
80
|
+
rules: [{
|
|
81
|
+
rule: "accept",
|
|
82
|
+
value: ["image/*"]
|
|
83
|
+
}, {
|
|
84
|
+
rule: "maxSize",
|
|
85
|
+
value: 1e3
|
|
86
|
+
}]
|
|
84
87
|
}]
|
|
85
88
|
}]
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: "attachment",
|
|
92
|
+
type: "file",
|
|
93
|
+
label: "Pièce jointe",
|
|
94
|
+
rules: [{
|
|
95
|
+
rule: "accept",
|
|
96
|
+
value: ["application/pdf"]
|
|
97
|
+
}]
|
|
98
|
+
}
|
|
99
|
+
],
|
|
100
|
+
form: { layout: [{
|
|
101
|
+
type: "tabs",
|
|
94
102
|
children: [{
|
|
95
|
-
type: "
|
|
103
|
+
type: "tab",
|
|
104
|
+
label: "Contenu",
|
|
96
105
|
children: [{
|
|
97
|
-
type: "
|
|
98
|
-
|
|
99
|
-
|
|
106
|
+
type: "row",
|
|
107
|
+
children: [{
|
|
108
|
+
type: "field",
|
|
109
|
+
name: "title",
|
|
110
|
+
width: 8
|
|
111
|
+
}, {
|
|
112
|
+
type: "field",
|
|
113
|
+
name: "status",
|
|
114
|
+
width: 4
|
|
115
|
+
}]
|
|
100
116
|
}, {
|
|
101
117
|
type: "field",
|
|
102
|
-
name: "
|
|
103
|
-
width: 4
|
|
118
|
+
name: "blocks"
|
|
104
119
|
}]
|
|
120
|
+
}]
|
|
121
|
+
}, {
|
|
122
|
+
type: "sidebar",
|
|
123
|
+
children: [{
|
|
124
|
+
type: "field",
|
|
125
|
+
name: "published_at"
|
|
105
126
|
}, {
|
|
106
127
|
type: "field",
|
|
107
|
-
name: "
|
|
128
|
+
name: "attachment"
|
|
108
129
|
}]
|
|
109
|
-
}]
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
{
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
},
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
130
|
+
}] },
|
|
131
|
+
list: {
|
|
132
|
+
columns: [
|
|
133
|
+
{
|
|
134
|
+
field: "title",
|
|
135
|
+
sortable: true
|
|
136
|
+
},
|
|
137
|
+
{ field: "status" },
|
|
138
|
+
{ id: "updated" }
|
|
139
|
+
],
|
|
140
|
+
filters: [{
|
|
141
|
+
id: "status",
|
|
142
|
+
type: "select"
|
|
143
|
+
}, {
|
|
144
|
+
id: "published_at",
|
|
145
|
+
type: "date-range",
|
|
146
|
+
label: "Publication"
|
|
147
|
+
}],
|
|
148
|
+
sorts: ["title", "published_at"],
|
|
149
|
+
defaultSort: "title",
|
|
150
|
+
perPage: 20,
|
|
151
|
+
search: true
|
|
152
|
+
},
|
|
153
|
+
actions: [{
|
|
154
|
+
id: "publish",
|
|
155
|
+
label: "Publier",
|
|
156
|
+
scope: ["item", "selection"],
|
|
157
|
+
placement: ["row", "toolbar"],
|
|
158
|
+
confirm: { message: "Publier {count} pages ?" },
|
|
159
|
+
fields: [{
|
|
160
|
+
name: "notify",
|
|
161
|
+
type: "boolean"
|
|
162
|
+
}],
|
|
163
|
+
dryRun: true
|
|
129
164
|
}, {
|
|
130
|
-
id: "
|
|
131
|
-
|
|
132
|
-
|
|
165
|
+
id: "edit",
|
|
166
|
+
label: "Modifier",
|
|
167
|
+
kind: "link",
|
|
168
|
+
url: "/pages/{id}/edit",
|
|
169
|
+
scope: ["item"]
|
|
133
170
|
}],
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
171
|
+
abilities: {
|
|
172
|
+
viewAny: true,
|
|
173
|
+
create: true
|
|
174
|
+
}
|
|
138
175
|
},
|
|
139
|
-
|
|
140
|
-
id: "
|
|
141
|
-
label:
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
176
|
+
{
|
|
177
|
+
id: "authors",
|
|
178
|
+
label: {
|
|
179
|
+
singular: "auteur",
|
|
180
|
+
plural: "auteurs"
|
|
181
|
+
},
|
|
145
182
|
fields: [{
|
|
146
|
-
name: "
|
|
147
|
-
type: "
|
|
183
|
+
name: "name",
|
|
184
|
+
type: "text",
|
|
185
|
+
rules: [{ rule: "required" }]
|
|
186
|
+
}, {
|
|
187
|
+
name: "photo",
|
|
188
|
+
type: "media",
|
|
189
|
+
accept: ["image/*"]
|
|
148
190
|
}]
|
|
149
|
-
}, {
|
|
150
|
-
id: "edit",
|
|
151
|
-
label: "Modifier",
|
|
152
|
-
kind: "link",
|
|
153
|
-
url: "/pages/{id}/edit",
|
|
154
|
-
scope: ["item"]
|
|
155
|
-
}],
|
|
156
|
-
abilities: {
|
|
157
|
-
viewAny: true,
|
|
158
|
-
create: true
|
|
159
|
-
}
|
|
160
|
-
}, {
|
|
161
|
-
id: "authors",
|
|
162
|
-
label: {
|
|
163
|
-
singular: "auteur",
|
|
164
|
-
plural: "auteurs"
|
|
165
191
|
},
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
192
|
+
{
|
|
193
|
+
id: "media",
|
|
194
|
+
kind: "media",
|
|
195
|
+
label: {
|
|
196
|
+
singular: "média",
|
|
197
|
+
plural: "médias"
|
|
198
|
+
},
|
|
199
|
+
itemTitle: "name",
|
|
200
|
+
fields: [
|
|
201
|
+
{
|
|
202
|
+
name: "file",
|
|
203
|
+
type: "file",
|
|
204
|
+
accept: [
|
|
205
|
+
"image/*",
|
|
206
|
+
"video/*",
|
|
207
|
+
"application/pdf"
|
|
208
|
+
],
|
|
209
|
+
rules: [{ rule: "required" }]
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
name: "name",
|
|
213
|
+
type: "text"
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: "alt",
|
|
217
|
+
type: "text",
|
|
218
|
+
localized: true
|
|
219
|
+
}
|
|
220
|
+
],
|
|
221
|
+
list: {
|
|
222
|
+
filters: [{
|
|
223
|
+
id: "type",
|
|
224
|
+
type: "select",
|
|
225
|
+
options: [
|
|
226
|
+
{
|
|
227
|
+
value: "image",
|
|
228
|
+
label: "Images"
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
value: "video",
|
|
232
|
+
label: "Vidéos"
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
value: "document",
|
|
236
|
+
label: "Documents"
|
|
237
|
+
}
|
|
238
|
+
]
|
|
239
|
+
}],
|
|
240
|
+
search: true
|
|
241
|
+
},
|
|
242
|
+
abilities: {
|
|
243
|
+
viewAny: true,
|
|
244
|
+
create: true
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
],
|
|
172
248
|
menus: { main: [{ type: "resources" }, {
|
|
173
249
|
type: "link",
|
|
174
250
|
label: "Site",
|
|
@@ -181,7 +257,8 @@ function createExampleDefinition() {
|
|
|
181
257
|
id: "google",
|
|
182
258
|
label: "Google"
|
|
183
259
|
}]
|
|
184
|
-
}
|
|
260
|
+
},
|
|
261
|
+
uploads: { mode: "presigned" }
|
|
185
262
|
};
|
|
186
263
|
}
|
|
187
264
|
//#endregion
|
package/dist/mock/example.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"example.js","names":[],"sources":["../../src/mock/example.ts"],"sourcesContent":["import type { DefinitionInput } from '../schemas';\n\n/**\n * A small but complete definition: pages with localized titles, a status, a publication date shown\n * for published pages, an author and typed blocks, in tabs and a sidebar, plus authors. For tests\n * and stories, with `createMockBackend()`.\n */\nexport function createExampleDefinition(): DefinitionInput {\n return {\n protocol: 1,\n name: 'Test site',\n locale: 'fr',\n locales: ['fr', 'en'],\n api: { baseUrl: '/panneau/api' },\n resources: [\n {\n id: 'pages',\n label: { singular: 'page', plural: 'pages' },\n itemTitle: 'title',\n fields: [\n {\n name: 'title',\n type: 'text',\n label: 'Titre',\n localized: true,\n rules: [{ rule: 'required' }, { rule: 'max', value: 120 }],\n },\n {\n name: 'status',\n type: 'select',\n options: [\n { value: 'draft', label: 'Brouillon' },\n { value: 'published', label: 'Publiée' },\n ],\n rules: [\n { rule: 'required' },\n { rule: 'in', value: ['draft', 'published'] },\n ],\n },\n {\n name: 'published_at',\n type: 'datetime',\n visibleWhen: { field: 'status', equals: 'published' },\n rules: [{ rule: 'required' }],\n },\n { name: 'author', type: 'relation', resource: 'authors' },\n {\n name: 'blocks',\n type: 'repeater',\n types: [\n {\n id: 'text',\n label: 'Texte',\n fields: [\n {\n name: 'body',\n type: 'richtext',\n rules: [{ rule: 'required' }],\n },\n ],\n },\n {\n id: 'image',\n label: 'Image',\n fields: [\n {\n name: 'image',\n type: 'media',\n rules: [\n { rule: 'accept', value: ['image/*'] },\n { rule: 'maxSize', value: 1000 },\n ],\n },\n ],\n },\n ],\n },\n ],\n form: {\n layout: [\n {\n type: 'tabs',\n children: [\n {\n type: 'tab',\n label: 'Contenu',\n children: [\n {\n type: 'row',\n children: [\n { type: 'field', name: 'title', width: 8 },\n { type: 'field', name: 'status', width: 4 },\n ],\n },\n { type: 'field', name: 'blocks' },\n ],\n },\n ],\n },\n { type: 'sidebar', children: [{ type: 'field', name: 'published_at' }] },\n ],\n },\n list: {\n columns: [\n { field: 'title', sortable: true },\n { field: 'status' },\n { id: 'updated' },\n ],\n filters: [\n { id: 'status', type: 'select' },\n { id: 'published_at', type: 'date-range', label: 'Publication' },\n ],\n sorts: ['title', 'published_at'],\n defaultSort: 'title',\n perPage: 20,\n search: true,\n },\n actions: [\n {\n id: 'publish',\n label: 'Publier',\n scope: ['item', 'selection'],\n placement: ['row', 'toolbar'],\n confirm: { message: 'Publier {count} pages ?' },\n fields: [{ name: 'notify', type: 'boolean' }],\n },\n {\n id: 'edit',\n label: 'Modifier',\n kind: 'link',\n url: '/pages/{id}/edit',\n scope: ['item'],\n },\n ],\n abilities: { viewAny: true, create: true },\n },\n {\n id: 'authors',\n label: { singular: 'auteur', plural: 'auteurs' },\n fields: [{ name: 'name', type: 'text', rules: [{ rule: 'required' }] }],\n },\n ],\n menus: { main: [{ type: 'resources' }, { type: 'link', label: 'Site', url: '/' }] },\n auth: { mode: 'panneau', password: true, providers: [{ id: 'google', label: 'Google' }] },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"example.js","names":[],"sources":["../../src/mock/example.ts"],"sourcesContent":["import type { DefinitionInput } from '../schemas';\n\n/**\n * A small but complete definition: pages with localized titles, a status, a publication date shown\n * for published pages, an author, an attachment and typed blocks, in tabs and a sidebar, plus\n * authors with a photo, and a media library. For tests\n * and stories, with `createMockBackend()`.\n */\nexport function createExampleDefinition(): DefinitionInput {\n return {\n protocol: 1,\n name: 'Test site',\n locale: 'fr',\n locales: ['fr', 'en'],\n api: { baseUrl: '/panneau/api' },\n resources: [\n {\n id: 'pages',\n label: { singular: 'page', plural: 'pages' },\n itemTitle: 'title',\n fields: [\n {\n name: 'title',\n type: 'text',\n label: 'Titre',\n localized: true,\n rules: [{ rule: 'required' }, { rule: 'max', value: 120 }],\n },\n {\n name: 'status',\n type: 'select',\n options: [\n { value: 'draft', label: 'Brouillon' },\n { value: 'published', label: 'Publiée' },\n ],\n rules: [\n { rule: 'required' },\n { rule: 'in', value: ['draft', 'published'] },\n ],\n },\n {\n name: 'published_at',\n type: 'datetime',\n visibleWhen: { field: 'status', equals: 'published' },\n rules: [{ rule: 'required' }],\n },\n { name: 'author', type: 'relation', resource: 'authors' },\n {\n name: 'blocks',\n type: 'repeater',\n types: [\n {\n id: 'text',\n label: 'Texte',\n fields: [\n {\n name: 'body',\n type: 'richtext',\n rules: [{ rule: 'required' }],\n },\n ],\n },\n {\n id: 'image',\n label: 'Image',\n fields: [\n {\n name: 'image',\n type: 'media',\n rules: [\n { rule: 'accept', value: ['image/*'] },\n { rule: 'maxSize', value: 1000 },\n ],\n },\n ],\n },\n ],\n },\n {\n name: 'attachment',\n type: 'file',\n label: 'Pièce jointe',\n rules: [{ rule: 'accept', value: ['application/pdf'] }],\n },\n ],\n form: {\n layout: [\n {\n type: 'tabs',\n children: [\n {\n type: 'tab',\n label: 'Contenu',\n children: [\n {\n type: 'row',\n children: [\n { type: 'field', name: 'title', width: 8 },\n { type: 'field', name: 'status', width: 4 },\n ],\n },\n { type: 'field', name: 'blocks' },\n ],\n },\n ],\n },\n {\n type: 'sidebar',\n children: [\n { type: 'field', name: 'published_at' },\n { type: 'field', name: 'attachment' },\n ],\n },\n ],\n },\n list: {\n columns: [\n { field: 'title', sortable: true },\n { field: 'status' },\n { id: 'updated' },\n ],\n filters: [\n { id: 'status', type: 'select' },\n { id: 'published_at', type: 'date-range', label: 'Publication' },\n ],\n sorts: ['title', 'published_at'],\n defaultSort: 'title',\n perPage: 20,\n search: true,\n },\n actions: [\n {\n id: 'publish',\n label: 'Publier',\n scope: ['item', 'selection'],\n placement: ['row', 'toolbar'],\n confirm: { message: 'Publier {count} pages ?' },\n fields: [{ name: 'notify', type: 'boolean' }],\n dryRun: true,\n },\n {\n id: 'edit',\n label: 'Modifier',\n kind: 'link',\n url: '/pages/{id}/edit',\n scope: ['item'],\n },\n ],\n abilities: { viewAny: true, create: true },\n },\n {\n id: 'authors',\n label: { singular: 'auteur', plural: 'auteurs' },\n fields: [\n { name: 'name', type: 'text', rules: [{ rule: 'required' }] },\n { name: 'photo', type: 'media', accept: ['image/*'] },\n ],\n },\n {\n id: 'media',\n kind: 'media',\n label: { singular: 'média', plural: 'médias' },\n itemTitle: 'name',\n fields: [\n {\n name: 'file',\n type: 'file',\n accept: ['image/*', 'video/*', 'application/pdf'],\n rules: [{ rule: 'required' }],\n },\n { name: 'name', type: 'text' },\n { name: 'alt', type: 'text', localized: true },\n ],\n list: {\n filters: [\n {\n id: 'type',\n type: 'select',\n options: [\n { value: 'image', label: 'Images' },\n { value: 'video', label: 'Vidéos' },\n { value: 'document', label: 'Documents' },\n ],\n },\n ],\n search: true,\n },\n abilities: { viewAny: true, create: true },\n },\n ],\n menus: { main: [{ type: 'resources' }, { type: 'link', label: 'Site', url: '/' }] },\n auth: { mode: 'panneau', password: true, providers: [{ id: 'google', label: 'Google' }] },\n uploads: { mode: 'presigned' },\n };\n}\n"],"mappings":"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"}
|
package/dist/mock/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mock/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAiB,MAAM,YAAY,CAAC;AACjE,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mock/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAiB,MAAM,YAAY,CAAC;AACjE,OAAO,KAAK,EAER,IAAI,EAQP,MAAM,gBAAgB,CAAC;AAIxB,KAAK,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEtC,MAAM,WAAW,kBAAkB;IAC/B,kGAAkG;IAClG,UAAU,EAAE,eAAe,CAAC;IAC5B,mFAAmF;IACnF,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,WAAW;IACxB,oEAAoE;IACpE,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAChF,uCAAuC;IACvC,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EAAE,CAAC;CACvC;AAqID;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,UAAU,EAAE,KAAU,EAAE,EAAE,kBAAkB,GAAG,WAAW,CAwa7F;AAED,OAAO,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/mock/index.js
CHANGED
|
@@ -74,7 +74,7 @@ function applyQuery(items, query) {
|
|
|
74
74
|
}
|
|
75
75
|
const accepted = (Array.isArray(expected) ? expected : [expected]).map(String);
|
|
76
76
|
result = result.filter((item) => {
|
|
77
|
-
const value = item[key];
|
|
77
|
+
const value = key in item ? item[key] : isRecord(item.media) ? item.media[key] : void 0;
|
|
78
78
|
return (Array.isArray(value) ? value : [value]).some((candidate) => accepted.includes(String(candidate)));
|
|
79
79
|
});
|
|
80
80
|
});
|
|
@@ -94,6 +94,24 @@ function applyQuery(items, query) {
|
|
|
94
94
|
});
|
|
95
95
|
return result;
|
|
96
96
|
}
|
|
97
|
+
/** The type of a media, from its MIME type. */
|
|
98
|
+
function mediaType(mime) {
|
|
99
|
+
const family = (mime ?? "").split("/")[0] ?? "";
|
|
100
|
+
if (family === "image" || family === "video" || family === "audio") return family;
|
|
101
|
+
return mime === "application/pdf" || family === "text" ? "document" : "other";
|
|
102
|
+
}
|
|
103
|
+
/** What the mock computes about a stored file: no renditions, ready at once. */
|
|
104
|
+
function mediaOf(file) {
|
|
105
|
+
const type = mediaType(file.mime);
|
|
106
|
+
return {
|
|
107
|
+
type,
|
|
108
|
+
mime: file.mime ?? null,
|
|
109
|
+
size: file.size ?? null,
|
|
110
|
+
url: file.url ?? null,
|
|
111
|
+
thumbnailUrl: type === "image" ? file.url ?? null : null,
|
|
112
|
+
status: "ready"
|
|
113
|
+
};
|
|
114
|
+
}
|
|
97
115
|
/**
|
|
98
116
|
* An in-memory backend that implements the protocol's resource routes: definition, list, show,
|
|
99
117
|
* create, update (with `If-Match`), delete and the built-in `delete` action. Values are checked
|
|
@@ -147,6 +165,123 @@ function createMockBackend({ definition, items = {} }) {
|
|
|
147
165
|
if (q !== void 0) query.q = q;
|
|
148
166
|
return applyQuery(list, query).filter((item) => !except.includes(item.id));
|
|
149
167
|
};
|
|
168
|
+
const uploads = /* @__PURE__ */ new Map();
|
|
169
|
+
let nextUpload = 1;
|
|
170
|
+
const handleUploads = async (segments, method, readBody) => {
|
|
171
|
+
if (definition.uploads?.mode !== "presigned") return problem(404, "Not found.");
|
|
172
|
+
if (method !== "POST") return problem(405, "Method not allowed.");
|
|
173
|
+
if (segments.length === 2 && segments[1] === "sign") {
|
|
174
|
+
const body = await readBody();
|
|
175
|
+
if (body.uploadId === void 0 && (body.method === "POST" || body.method === "PUT")) {
|
|
176
|
+
const id = `upl_${nextUpload++}`;
|
|
177
|
+
const upload = {
|
|
178
|
+
id,
|
|
179
|
+
key: `uploads/${id}`
|
|
180
|
+
};
|
|
181
|
+
uploads.set(id, upload);
|
|
182
|
+
return json(200, {
|
|
183
|
+
url: `https://storage.test/${upload.key}?signature=mock`,
|
|
184
|
+
key: upload.key,
|
|
185
|
+
upload: id
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
const upload = [...uploads.values()].find((candidate) => candidate.key === body.key);
|
|
189
|
+
if (upload === void 0) return problem(404, "Upload not found.");
|
|
190
|
+
return json(200, { url: `https://storage.test/${upload.key}?signature=mock` });
|
|
191
|
+
}
|
|
192
|
+
if (segments.length === 3 && segments[2] === "complete" && segments[1] !== void 0) {
|
|
193
|
+
const upload = uploads.get(segments[1]);
|
|
194
|
+
if (upload === void 0) return problem(404, "Upload not found.");
|
|
195
|
+
const { name } = await readBody();
|
|
196
|
+
if (typeof name === "string") upload.name = name;
|
|
197
|
+
return json(200, { data: upload });
|
|
198
|
+
}
|
|
199
|
+
return problem(404, "Not found.");
|
|
200
|
+
};
|
|
201
|
+
const resolveFiles = (resource, values) => {
|
|
202
|
+
const missing = [];
|
|
203
|
+
const resolved = { ...values };
|
|
204
|
+
const toFile = (value, path) => {
|
|
205
|
+
if (!isRecord(value) || typeof value.upload !== "string") return value;
|
|
206
|
+
const upload = uploads.get(value.upload);
|
|
207
|
+
if (upload === void 0) {
|
|
208
|
+
missing.push(path);
|
|
209
|
+
return value;
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
name: upload.name ?? (typeof value.name === "string" ? value.name : upload.id),
|
|
213
|
+
url: `https://storage.test/${upload.key ?? upload.id}`,
|
|
214
|
+
mime: upload.mime ?? (typeof value.mime === "string" ? value.mime : null),
|
|
215
|
+
size: upload.size ?? (typeof value.size === "number" ? value.size : null)
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
resource.fields.filter((field) => field.type === "file" && field.name in values).forEach((field) => {
|
|
219
|
+
const value = values[field.name];
|
|
220
|
+
resolved[field.name] = Array.isArray(value) ? value.map((entry, index) => toFile(entry, `${field.name}.${String(index)}`)) : toFile(value, field.name);
|
|
221
|
+
});
|
|
222
|
+
return {
|
|
223
|
+
values: resolved,
|
|
224
|
+
missing
|
|
225
|
+
};
|
|
226
|
+
};
|
|
227
|
+
const missingMedia = (paths) => problem(422, "The given data was invalid.", {
|
|
228
|
+
type: "urn:panneau:problem:validation",
|
|
229
|
+
errors: Object.fromEntries(paths.map((path) => [path, ["This media does not exist."]]))
|
|
230
|
+
});
|
|
231
|
+
const missingUploads = (paths) => problem(422, "The given data was invalid.", {
|
|
232
|
+
type: "urn:panneau:problem:validation",
|
|
233
|
+
errors: Object.fromEntries(paths.map((path) => [path, ["This upload does not exist."]]))
|
|
234
|
+
});
|
|
235
|
+
const resolveMedia = (resource, values) => {
|
|
236
|
+
const missing = [];
|
|
237
|
+
const resolved = { ...values };
|
|
238
|
+
const libraries = definition.resources.filter((candidate) => candidate.kind === "media");
|
|
239
|
+
resource.fields.filter((field) => field.type === "media" && field.name in values).forEach((field) => {
|
|
240
|
+
const library = libraries.find((candidate) => field.resource !== void 0 ? candidate.id === field.resource : true);
|
|
241
|
+
const toReference = (value, path) => {
|
|
242
|
+
const id = isRecord(value) ? value.id : value;
|
|
243
|
+
if (typeof id !== "string" && typeof id !== "number") return value;
|
|
244
|
+
const media = (store.get(library?.id ?? "") ?? []).find((candidate) => candidate.id === String(id));
|
|
245
|
+
if (media === void 0 || library === void 0) {
|
|
246
|
+
missing.push(path);
|
|
247
|
+
return value;
|
|
248
|
+
}
|
|
249
|
+
const title = media[library.itemTitle ?? "name"];
|
|
250
|
+
return {
|
|
251
|
+
id: media.id,
|
|
252
|
+
...typeof title === "string" ? { label: title } : {},
|
|
253
|
+
...isRecord(media.media) ? { media: media.media } : {},
|
|
254
|
+
...isRecord(value) && isRecord(value.transform) ? { transform: value.transform } : {}
|
|
255
|
+
};
|
|
256
|
+
};
|
|
257
|
+
const value = values[field.name];
|
|
258
|
+
resolved[field.name] = Array.isArray(value) ? value.map((entry, index) => toReference(entry, `${field.name}.${String(index)}`)) : toReference(value, field.name);
|
|
259
|
+
});
|
|
260
|
+
return {
|
|
261
|
+
values: resolved,
|
|
262
|
+
missing
|
|
263
|
+
};
|
|
264
|
+
};
|
|
265
|
+
const resolveValues = (resource, body) => {
|
|
266
|
+
const sent = { ...body };
|
|
267
|
+
if (resource.kind === "media") delete sent.media;
|
|
268
|
+
const files = resolveFiles(resource, sent);
|
|
269
|
+
if (files.missing.length > 0) return {
|
|
270
|
+
values: body,
|
|
271
|
+
problem: missingUploads(files.missing)
|
|
272
|
+
};
|
|
273
|
+
const media = resolveMedia(resource, files.values);
|
|
274
|
+
if (media.missing.length > 0) return {
|
|
275
|
+
values: body,
|
|
276
|
+
problem: missingMedia(media.missing)
|
|
277
|
+
};
|
|
278
|
+
const values = media.values;
|
|
279
|
+
if (resource.kind === "media" && isRecord(values.file)) values.media = mediaOf(values.file);
|
|
280
|
+
return {
|
|
281
|
+
values,
|
|
282
|
+
problem: null
|
|
283
|
+
};
|
|
284
|
+
};
|
|
150
285
|
const handle = async (request) => {
|
|
151
286
|
const url = new URL(request.url, "http://panneau.test");
|
|
152
287
|
if (!url.pathname.startsWith(basePath)) return problem(404, "Not found.");
|
|
@@ -157,6 +292,7 @@ function createMockBackend({ definition, items = {} }) {
|
|
|
157
292
|
return isRecord(body) ? body : {};
|
|
158
293
|
};
|
|
159
294
|
if (segments.length === 1 && segments[0] === "definition" && method === "GET") return json(200, definition);
|
|
295
|
+
if (segments[0] === "uploads") return handleUploads(segments, method, readBody);
|
|
160
296
|
if (segments[0] !== "resources" || segments[1] === void 0) return problem(404, "Not found.");
|
|
161
297
|
const resource = definition.resources.find((candidate) => candidate.id === segments[1]);
|
|
162
298
|
const list = resource !== void 0 ? store.get(resource.id) : void 0;
|
|
@@ -182,9 +318,11 @@ function createMockBackend({ definition, items = {} }) {
|
|
|
182
318
|
});
|
|
183
319
|
}
|
|
184
320
|
if (method === "POST") {
|
|
185
|
-
const
|
|
186
|
-
const invalid = validationProblem(resource,
|
|
321
|
+
const body = await readBody();
|
|
322
|
+
const invalid = validationProblem(resource, body, "create");
|
|
187
323
|
if (invalid !== null) return invalid;
|
|
324
|
+
const { values, problem: unresolved } = resolveValues(resource, body);
|
|
325
|
+
if (unresolved !== null) return unresolved;
|
|
188
326
|
const item = makeItem(resource, {
|
|
189
327
|
...values,
|
|
190
328
|
id: void 0
|
|
@@ -196,8 +334,27 @@ function createMockBackend({ definition, items = {} }) {
|
|
|
196
334
|
}
|
|
197
335
|
if (segments[2] === "actions" && segments[3] !== void 0 && segments.length === 4) {
|
|
198
336
|
if (method !== "POST") return problem(405, "Method not allowed.");
|
|
199
|
-
|
|
337
|
+
const actionId = segments[3];
|
|
338
|
+
const action = (resource.actions ?? []).find((candidate) => candidate.id === actionId);
|
|
200
339
|
const body = await readBody();
|
|
340
|
+
if (body.dryRun === true) {
|
|
341
|
+
if (action?.dryRun !== true) return problem(400, "This action does not support dry runs.");
|
|
342
|
+
const selected = selectItems(list, body.selection);
|
|
343
|
+
const allowed = selected.filter((item) => item.abilities?.[actionId] !== false);
|
|
344
|
+
return json(200, {
|
|
345
|
+
summary: {
|
|
346
|
+
succeeded: allowed.length,
|
|
347
|
+
failed: 0,
|
|
348
|
+
skipped: selected.length - allowed.length
|
|
349
|
+
},
|
|
350
|
+
results: selected.filter((item) => !allowed.includes(item)).slice(0, 20).map((item) => ({
|
|
351
|
+
id: item.id,
|
|
352
|
+
status: "skipped",
|
|
353
|
+
reason: "forbidden"
|
|
354
|
+
}))
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
if (actionId !== "delete") return problem(404, "Action not found.");
|
|
201
358
|
const selected = selectItems(list, body.selection);
|
|
202
359
|
store.set(resource.id, list.filter((item) => !selected.includes(item)));
|
|
203
360
|
return json(200, {
|
|
@@ -222,9 +379,11 @@ function createMockBackend({ definition, items = {} }) {
|
|
|
222
379
|
type: "urn:panneau:problem:version-conflict",
|
|
223
380
|
current
|
|
224
381
|
});
|
|
225
|
-
const
|
|
226
|
-
const invalid = validationProblem(resource,
|
|
382
|
+
const body = await readBody();
|
|
383
|
+
const invalid = validationProblem(resource, body, "update");
|
|
227
384
|
if (invalid !== null) return invalid;
|
|
385
|
+
const { values, problem: unresolved } = resolveValues(resource, body);
|
|
386
|
+
if (unresolved !== null) return unresolved;
|
|
228
387
|
const updates = Object.fromEntries(Object.entries(values).filter(([key]) => !RESERVED_KEYS.has(key)));
|
|
229
388
|
const updated = {
|
|
230
389
|
...current,
|