ApiLogicServer 15.0.19__py3-none-any.whl → 15.0.22__py3-none-any.whl
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.
- api_logic_server_cli/api_logic_server.py +4 -4
- api_logic_server_cli/api_logic_server_info.yaml +2 -2
- api_logic_server_cli/genai/genai_admin_app.py +33 -9
- api_logic_server_cli/prototypes/.DS_Store +0 -0
- api_logic_server_cli/prototypes/manager/system/genai/app_templates/app_learning/Admin-App-Resource-Learning-Prompt.md +119 -0
- api_logic_server_cli/prototypes/manager/system/genai/app_templates/app_learning/Admin-App-js-Learning-Prompt.md +71 -0
- api_logic_server_cli/prototypes/manager/system/genai/app_templates/app_learning/{Admin-App-Learning-Prompt.md → z-unused-Admin-App-Learning-Prompt.md} +60 -35
- api_logic_server_cli/prototypes/manager/system/genai/app_templates/react-admin-template/package.json +3 -0
- api_logic_server_cli/prototypes/manager/system/genai/app_templates/react-admin-template/src/Config.js +527 -0
- api_logic_server_cli/prototypes/manager/system/genai/app_templates/react-admin-template/src/app_loader.js +24 -0
- api_logic_server_cli/prototypes/manager/system/genai/app_templates/react-admin-template/src/rav4-jsonapi-client/.eslintrc +5 -0
- api_logic_server_cli/prototypes/manager/system/genai/app_templates/react-admin-template/src/rav4-jsonapi-client/.yarnrc.yml +4 -0
- api_logic_server_cli/prototypes/manager/system/genai/app_templates/react-admin-template/src/rav4-jsonapi-client/default-settings.js +25 -0
- api_logic_server_cli/prototypes/manager/system/genai/app_templates/react-admin-template/src/rav4-jsonapi-client/default-settings.ts +25 -0
- api_logic_server_cli/prototypes/manager/system/genai/app_templates/react-admin-template/src/rav4-jsonapi-client/errors.js +116 -0
- api_logic_server_cli/prototypes/manager/system/genai/app_templates/react-admin-template/src/rav4-jsonapi-client/errors.ts +116 -0
- api_logic_server_cli/prototypes/manager/system/genai/app_templates/react-admin-template/src/rav4-jsonapi-client/index.test.tsx +7 -0
- api_logic_server_cli/prototypes/manager/system/genai/app_templates/react-admin-template/src/rav4-jsonapi-client/index.tsx +11 -0
- api_logic_server_cli/prototypes/manager/system/genai/app_templates/react-admin-template/src/rav4-jsonapi-client/ra-jsonapi-client.js +577 -0
- api_logic_server_cli/prototypes/manager/system/genai/app_templates/react-admin-template/src/rav4-jsonapi-client/ra-jsonapi-client.ts +577 -0
- api_logic_server_cli/prototypes/manager/system/genai/app_templates/react-admin-template/src/rav4-jsonapi-client/resourceLookup.js +124 -0
- api_logic_server_cli/prototypes/manager/system/genai/app_templates/react-admin-template/src/rav4-jsonapi-client/resourceLookup.ts +124 -0
- api_logic_server_cli/prototypes/manager/system/genai/app_templates/react-admin-template/src/rav4-jsonapi-client/styles.module.css +9 -0
- api_logic_server_cli/prototypes/nw/.DS_Store +0 -0
- api_logic_server_cli/prototypes/nw/ui/.DS_Store +0 -0
- api_logic_server_cli/prototypes/nw/ui/react_admin/.DS_Store +0 -0
- api_logic_server_cli/prototypes/nw/ui/react_admin/src/App.js +30 -31
- api_logic_server_cli/prototypes/nw/ui/react_admin/src/Category.js +62 -62
- api_logic_server_cli/prototypes/nw/ui/react_admin/src/Customer.js +143 -48
- api_logic_server_cli/prototypes/nw/ui/react_admin/src/CustomerDemographic.js +20 -37
- api_logic_server_cli/prototypes/nw/ui/react_admin/src/Department.js +38 -39
- api_logic_server_cli/prototypes/nw/ui/react_admin/src/Employee.js +85 -134
- api_logic_server_cli/prototypes/nw/ui/react_admin/src/EmployeeAudit.js +89 -77
- api_logic_server_cli/prototypes/nw/ui/react_admin/src/EmployeeTerritory.js +59 -59
- api_logic_server_cli/prototypes/nw/ui/react_admin/src/Location.js +45 -49
- api_logic_server_cli/prototypes/nw/ui/react_admin/src/Order.js +42 -60
- api_logic_server_cli/prototypes/nw/ui/react_admin/src/OrderDetail.js +97 -106
- api_logic_server_cli/prototypes/nw/ui/react_admin/src/Product.js +60 -62
- api_logic_server_cli/prototypes/nw/ui/react_admin/src/Region.js +36 -41
- api_logic_server_cli/prototypes/nw/ui/react_admin/src/SampleDBVersion.js +73 -0
- api_logic_server_cli/prototypes/nw/ui/react_admin/src/Shipper.js +57 -54
- api_logic_server_cli/prototypes/nw/ui/react_admin/src/Supplier.js +71 -87
- api_logic_server_cli/prototypes/nw/ui/react_admin/src/Territory.js +47 -41
- api_logic_server_cli/prototypes/nw/ui/react_admin/src/Union.js +18 -34
- {apilogicserver-15.0.19.dist-info → apilogicserver-15.0.22.dist-info}/METADATA +1 -1
- {apilogicserver-15.0.19.dist-info → apilogicserver-15.0.22.dist-info}/RECORD +50 -31
- api_logic_server_cli/prototypes/manager/system/genai/app_templates/react-admin-template/src/dataProvider.js +0 -110
- {apilogicserver-15.0.19.dist-info → apilogicserver-15.0.22.dist-info}/WHEEL +0 -0
- {apilogicserver-15.0.19.dist-info → apilogicserver-15.0.22.dist-info}/entry_points.txt +0 -0
- {apilogicserver-15.0.19.dist-info → apilogicserver-15.0.22.dist-info}/licenses/LICENSE +0 -0
- {apilogicserver-15.0.19.dist-info → apilogicserver-15.0.22.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
import { stringify } from 'query-string';
|
|
2
|
+
import { fetchUtils, DataProvider, HttpError } from 'react-admin';
|
|
3
|
+
import merge from 'deepmerge';
|
|
4
|
+
import { defaultSettings } from './default-settings';
|
|
5
|
+
import ResourceLookup from './resourceLookup';
|
|
6
|
+
import Keycloak from 'keycloak-js';
|
|
7
|
+
import {getConf} from '../Config'
|
|
8
|
+
import urlJoin from 'url-join';
|
|
9
|
+
|
|
10
|
+
const conf : { [ key: string] : any } = getConf();
|
|
11
|
+
const duration = 2000;
|
|
12
|
+
|
|
13
|
+
const prepareAttributes = (attributes : any, resource_en : any) => {
|
|
14
|
+
const resource = decodeURI(resource_en)
|
|
15
|
+
// temp: convert all numbers to string to allow FK lookups (jsonapi ids are strings, while FKs may be numbers :////)
|
|
16
|
+
const resource_attr_rels = conf.resources[resource].attributes?.map( (attr : any) => attr.relationship ? attr.name : null)
|
|
17
|
+
const m_attrs = Object.assign({}, attributes)
|
|
18
|
+
for(let [k, v] of Object.entries(attributes)){
|
|
19
|
+
m_attrs[k] = v
|
|
20
|
+
if(typeof v === 'number' && resource_attr_rels.find((n: string) => n === k)){
|
|
21
|
+
m_attrs[k] = v.toString();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return m_attrs
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const prepareQueryFilter = (query: any, ids : any, fks : any) => {
|
|
28
|
+
if(ids.length === fks.length){
|
|
29
|
+
for(let i = 0; i<fks.length; i++){
|
|
30
|
+
let fk = fks[i]
|
|
31
|
+
let id = ids[i]
|
|
32
|
+
query[`filter[${fk}]`] = id
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else{
|
|
36
|
+
// fk probably contains an underscore
|
|
37
|
+
// todo: how to fix???
|
|
38
|
+
console.warn("Wrong FK length.. ", ids, fks)
|
|
39
|
+
query[`filter[${fks[0]}]`] = ids && ids.length ? ids[0] : ""
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const getKeycloakHeaders = (
|
|
44
|
+
keycloak: Keycloak,
|
|
45
|
+
options: fetchUtils.Options | undefined
|
|
46
|
+
): Headers => {
|
|
47
|
+
const headers = ((options && options.headers) ||
|
|
48
|
+
new Headers({
|
|
49
|
+
Accept: 'application/json',
|
|
50
|
+
})) as Headers;
|
|
51
|
+
if(keycloak?.isTokenExpired()){
|
|
52
|
+
keycloak.updateToken(30)
|
|
53
|
+
}
|
|
54
|
+
if (keycloak.token) {
|
|
55
|
+
headers.set('Authorization', `Bearer ${keycloak.token}`);
|
|
56
|
+
}
|
|
57
|
+
return headers;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const httpAuthClient = (url: string, options : any) => {
|
|
61
|
+
if (!options.headers) {
|
|
62
|
+
options.headers = new Headers({ Accept: 'application/json' });
|
|
63
|
+
}
|
|
64
|
+
const token : string = localStorage.getItem('auth_token') || "";
|
|
65
|
+
if(token){
|
|
66
|
+
options.headers.set('Authorization', `Bearer ${token}`);
|
|
67
|
+
}
|
|
68
|
+
return fetchUtils.fetchJson(url, options)
|
|
69
|
+
.catch((e:HttpError ) => {
|
|
70
|
+
const msg = e.body?.msg || "Unknown Error."
|
|
71
|
+
console.warn('httpAuthClient httperror', e, e.status, e.body, msg)
|
|
72
|
+
if(e.body?.message?.startsWith('Booting project')){
|
|
73
|
+
// Custom error handling for booting, todo!!
|
|
74
|
+
console.log('WGserver_response: booting', e, e.status, e.body)
|
|
75
|
+
setTimeout(() => document.location.reload(), 3000);
|
|
76
|
+
throw new HttpError(e, e.status,
|
|
77
|
+
{title: "Booting",
|
|
78
|
+
detail:"Booting project, please wait",
|
|
79
|
+
message:"Booting project, please wait",
|
|
80
|
+
code:e.status})
|
|
81
|
+
}
|
|
82
|
+
throw new HttpError(e, e.status, e.body)
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const createKCHttpAuthClient = (keycloak: Keycloak) => (
|
|
87
|
+
url: any,
|
|
88
|
+
options: fetchUtils.Options | undefined
|
|
89
|
+
) => {
|
|
90
|
+
if(!keycloak){
|
|
91
|
+
console.error("No keycloak")
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
if(keycloak.token){
|
|
95
|
+
localStorage.setItem('auth_token', keycloak.token)
|
|
96
|
+
}
|
|
97
|
+
const requestHeaders = getKeycloakHeaders(keycloak, options);
|
|
98
|
+
return fetchUtils.fetchJson(url, {
|
|
99
|
+
...options,
|
|
100
|
+
headers: requestHeaders,
|
|
101
|
+
})
|
|
102
|
+
.catch(e => {
|
|
103
|
+
console.warn('KC httpAuthClient httperror', e, e.body)
|
|
104
|
+
if(e.body?.message?.startsWith('Booting project')){
|
|
105
|
+
// Custom error handling for booting, todo!!
|
|
106
|
+
console.log('WGserver_response: booting', e, e.status, e.body)
|
|
107
|
+
setTimeout(() => document.location.reload(), 3000);
|
|
108
|
+
throw new HttpError(e, e.status,
|
|
109
|
+
{title: "Booting",
|
|
110
|
+
detail:"Booting project, please wait",
|
|
111
|
+
message:"Booting project, please wait",
|
|
112
|
+
code:e.status})
|
|
113
|
+
}
|
|
114
|
+
throw new HttpError(e, e.status, e.statusText)
|
|
115
|
+
})
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Based on
|
|
120
|
+
*
|
|
121
|
+
* https://github.com/marmelab/react-admin/blob/master/packages/ra-data-simple-rest/src/index.ts
|
|
122
|
+
* @see https://github.com/marmelab/FakeRest
|
|
123
|
+
*
|
|
124
|
+
*/
|
|
125
|
+
export const jsonapiClient = (
|
|
126
|
+
apiUrl: string,
|
|
127
|
+
userSettings = {conf : {}},
|
|
128
|
+
keycloak: Keycloak| null = null,
|
|
129
|
+
httpClient = httpAuthClient,//fetchUtils.fetchJson,
|
|
130
|
+
countHeader: string = 'Content-Range',
|
|
131
|
+
): DataProvider => {
|
|
132
|
+
|
|
133
|
+
if(keycloak?.isTokenExpired()){
|
|
134
|
+
keycloak.login();
|
|
135
|
+
}
|
|
136
|
+
if(keycloak){
|
|
137
|
+
httpClient = createKCHttpAuthClient(keycloak)
|
|
138
|
+
}
|
|
139
|
+
const settings = merge(defaultSettings, userSettings);
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
/*******************************************************************************************
|
|
143
|
+
* getList: fetch a collection
|
|
144
|
+
*******************************************************************************************/
|
|
145
|
+
getList: (resource_name_en, params = {}) => {
|
|
146
|
+
const conf = getConf();
|
|
147
|
+
const resource_name = decodeURI(resource_name_en);
|
|
148
|
+
const { page, perPage } = params?.pagination || { page: 1, perPage: 10 };
|
|
149
|
+
if(! conf.resources[resource_name]){
|
|
150
|
+
console.warn(`Invalid resource ${resource_name}`)
|
|
151
|
+
return Promise.reject(new Error(`Invalid resource ${resource_name}`))
|
|
152
|
+
}
|
|
153
|
+
const resource_conf : any = conf.resources[resource_name];
|
|
154
|
+
const sort : string = resource_conf.sort
|
|
155
|
+
// Create query with pagination params.
|
|
156
|
+
const query : {[k: string]: any} = {
|
|
157
|
+
'page[number]': page,
|
|
158
|
+
'page[size]': perPage,
|
|
159
|
+
'page[offset]': (page - 1) * perPage,
|
|
160
|
+
'page[limit]': perPage
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
if(sort){
|
|
164
|
+
query.sort = sort
|
|
165
|
+
}
|
|
166
|
+
// Add all filter params to query.
|
|
167
|
+
if(params.filter?.q && "resources" in conf){
|
|
168
|
+
// search is requested by react-admin
|
|
169
|
+
const search_cols = resource_conf.search_cols
|
|
170
|
+
const filter = search_cols?.map((col: any) => {
|
|
171
|
+
return {
|
|
172
|
+
"name":col.name,
|
|
173
|
+
"op": col.op? col.op : "ilike",
|
|
174
|
+
"val": col.val ? col.val.format(params.filter.q) : `%${params.filter.q}%`
|
|
175
|
+
};}) || ""
|
|
176
|
+
if(filter){
|
|
177
|
+
query['filter'] = JSON.stringify(filter)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
else{
|
|
181
|
+
Object.keys(params.filter || {}).forEach((key) => {
|
|
182
|
+
query[`filter[${key}]`] = params.filter[key];
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Add sort parameter, first check the default configured sorting, then the customized sort
|
|
187
|
+
if (params.sort && params.sort.field) {
|
|
188
|
+
const prefix = params.sort.order?.toLowerCase() === 'desc' ? '-' : ''; // <> ASC
|
|
189
|
+
query.sort = `${prefix}${params.sort.field}`;
|
|
190
|
+
}
|
|
191
|
+
if(!query.sort){
|
|
192
|
+
query.sort = resource_conf.sort || "id"
|
|
193
|
+
}
|
|
194
|
+
const rel_conf = conf.resources[resource_name].relationships || []
|
|
195
|
+
// we only need "toone" rels in getList so we can show the join/user key
|
|
196
|
+
const includes: string[] = rel_conf.filter((rel : any) => rel.direction !== 'tomany').map((rel : any) => rel.name);
|
|
197
|
+
if(params.meta?.include?.length){
|
|
198
|
+
includes.push(...params.meta.include)
|
|
199
|
+
}
|
|
200
|
+
query['include'] = includes.join(',');
|
|
201
|
+
|
|
202
|
+
const url = `${apiUrl}/${resource_name_en}?${stringify(query)}`;
|
|
203
|
+
return httpClient(url, {})
|
|
204
|
+
.then(({ json }) => {
|
|
205
|
+
// const lookup = new ResourceLookup(json.data);
|
|
206
|
+
// When meta data and the 'total' setting is provided try
|
|
207
|
+
// to get the total count.
|
|
208
|
+
let total = 0;
|
|
209
|
+
if (json.meta && settings.total) {
|
|
210
|
+
total = json.meta[settings.total];
|
|
211
|
+
}
|
|
212
|
+
// Use the length of the data array as a fallback.
|
|
213
|
+
total = total || json.data.length;
|
|
214
|
+
const lookup = new ResourceLookup(json);
|
|
215
|
+
const jsonData = json.data.map((resource: any) =>{
|
|
216
|
+
return lookup.unwrapData(resource)
|
|
217
|
+
}
|
|
218
|
+
);
|
|
219
|
+
const validUntil = new Date();
|
|
220
|
+
validUntil.setTime(validUntil.getTime() + duration);
|
|
221
|
+
return {
|
|
222
|
+
data: jsonData,
|
|
223
|
+
included: json.included,
|
|
224
|
+
validUntil : validUntil,
|
|
225
|
+
total: total
|
|
226
|
+
};
|
|
227
|
+
})
|
|
228
|
+
.catch((err: HttpError) => {
|
|
229
|
+
console.warn('getList Error', err, err.body, err.status);
|
|
230
|
+
const errorHandler = settings.errorHandler;
|
|
231
|
+
return Promise.reject(errorHandler(err));
|
|
232
|
+
});
|
|
233
|
+
},
|
|
234
|
+
|
|
235
|
+
/*******************************************************************************************
|
|
236
|
+
getOne
|
|
237
|
+
********************************************************************************************/
|
|
238
|
+
getOne: (resource_en: any, params: { id: any }) => {
|
|
239
|
+
const conf = getConf();
|
|
240
|
+
const resource = decodeURI(resource_en)
|
|
241
|
+
if(params.id === null || params.id === undefined){
|
|
242
|
+
return new Promise(()=>{return {data: {}}})
|
|
243
|
+
}
|
|
244
|
+
const resource_conf = conf["resources"][resource];
|
|
245
|
+
if(!resource_conf){
|
|
246
|
+
console.warn(`Invalid resource ${resource}`)
|
|
247
|
+
return new Promise(()=>{});
|
|
248
|
+
}
|
|
249
|
+
const rel_conf = resource_conf?.relationships || [];
|
|
250
|
+
const includes: string[] = rel_conf.map((rel : any) => rel.name).join(",")
|
|
251
|
+
const url = `${apiUrl}/${resource}/${params.id}?include=${includes}&page[limit]=1`; // we only need 1 include at most
|
|
252
|
+
|
|
253
|
+
return httpClient(url, {}).then(({ json }) => {
|
|
254
|
+
|
|
255
|
+
let { id, attributes, relationships, type } = json.data;
|
|
256
|
+
if(id === undefined){
|
|
257
|
+
return {data:{}}
|
|
258
|
+
}
|
|
259
|
+
Object.assign(attributes, relationships, {type: type}, {relationships: relationships}, {attributes: {...attributes} });
|
|
260
|
+
attributes = prepareAttributes(attributes, resource)
|
|
261
|
+
const validUntil = new Date();
|
|
262
|
+
validUntil.setTime(validUntil.getTime() + duration);
|
|
263
|
+
return {
|
|
264
|
+
data: {
|
|
265
|
+
id,
|
|
266
|
+
validUntil : validUntil,
|
|
267
|
+
...attributes
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
});
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
/*******************************************************************************************
|
|
274
|
+
getMany
|
|
275
|
+
********************************************************************************************/
|
|
276
|
+
getMany: (resource_en, params: any) => {
|
|
277
|
+
const conf = getConf();
|
|
278
|
+
if(resource_en === null || resource_en === undefined || resource_en === "" || resource_en === "Location"){
|
|
279
|
+
return new Promise(()=>{return {data: {}}})
|
|
280
|
+
}
|
|
281
|
+
const resource_de = decodeURI(resource_en)
|
|
282
|
+
const resource = capitalize(resource_de);
|
|
283
|
+
let query = `filter[id]=${params.ids instanceof Array ? params.ids.join(',') : JSON.stringify(params.ids)}`
|
|
284
|
+
if(params.meta?.include?.length){
|
|
285
|
+
query += `&include=${(params.meta.include).join(',')}`
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const url = `${apiUrl}/${resource}?${query}`;
|
|
289
|
+
return httpClient(url, {}).then(({ json }) => {
|
|
290
|
+
|
|
291
|
+
// When meta data and the 'total' setting is provided try
|
|
292
|
+
// to get the total count.
|
|
293
|
+
let total = 0;
|
|
294
|
+
if (json.meta && settings.total) {
|
|
295
|
+
total = json.meta[settings.total];
|
|
296
|
+
}
|
|
297
|
+
// Use the length of the data array as a fallback.
|
|
298
|
+
total = total || json.data.length; // { id: any; attributes: any; }
|
|
299
|
+
|
|
300
|
+
const jsonData = json.data.map((value: any) => {
|
|
301
|
+
const result = Object.assign({ id: value.id, type: value.type, relationships: value.relationships }, prepareAttributes(value.attributes, resource))
|
|
302
|
+
//const related = json.included || [];
|
|
303
|
+
// TODO!!: this is not working, we need to find the included resources
|
|
304
|
+
// for(const [k,v] of Object.entries(value.relationships || {})){
|
|
305
|
+
// console.log('getMany', k, v)
|
|
306
|
+
// if(v instanceof Array){
|
|
307
|
+
// v.forEach((inc: any) => {
|
|
308
|
+
// const type = inc.data?.type
|
|
309
|
+
// if(!type){
|
|
310
|
+
// return
|
|
311
|
+
// }
|
|
312
|
+
// const related = json.included?.find((rel: any) => rel.type === type && rel.id === inc.id)
|
|
313
|
+
// Object.assign(result, { [k]: related})
|
|
314
|
+
// });
|
|
315
|
+
// }
|
|
316
|
+
// }
|
|
317
|
+
return result;
|
|
318
|
+
}
|
|
319
|
+
);
|
|
320
|
+
const validUntil = new Date();
|
|
321
|
+
validUntil.setTime(validUntil.getTime() + duration);
|
|
322
|
+
return {
|
|
323
|
+
data: jsonData,
|
|
324
|
+
validUntil : validUntil,
|
|
325
|
+
total: total
|
|
326
|
+
};
|
|
327
|
+
});
|
|
328
|
+
},
|
|
329
|
+
|
|
330
|
+
/*******************************************************************************************
|
|
331
|
+
getManyReference
|
|
332
|
+
********************************************************************************************/
|
|
333
|
+
getManyReference: (resource_name_en, params : any) => {
|
|
334
|
+
const conf = getConf();
|
|
335
|
+
const resource_name = decodeURI(resource_name_en)
|
|
336
|
+
const { page, perPage } = params.pagination;
|
|
337
|
+
const { field, order } = params.sort;
|
|
338
|
+
|
|
339
|
+
const query: {[k: string]: any} = { };
|
|
340
|
+
|
|
341
|
+
if (field) {
|
|
342
|
+
const prefix = order === 'DESC' ? '-' : ''; // <> ASC
|
|
343
|
+
query.sort = `${prefix}${field}`;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
let fks = params.target.split('_')
|
|
348
|
+
//const ids = fks.length > 1 ? params.id.split('_') : params.id
|
|
349
|
+
let ids = params.id.split('_')
|
|
350
|
+
|
|
351
|
+
if(ids.length !== fks.length){
|
|
352
|
+
console.warn("Wrong FK length ", ids, fks)
|
|
353
|
+
fks = [params.target]
|
|
354
|
+
ids = [params.id]
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
prepareQueryFilter(query, ids, fks);
|
|
358
|
+
|
|
359
|
+
query[`page[limit]`] = perPage
|
|
360
|
+
query[`page[offset]`] = (page - 1) * perPage
|
|
361
|
+
|
|
362
|
+
const options = {};
|
|
363
|
+
const resource_conf = conf["resources"][resource_name];
|
|
364
|
+
const rel_conf = resource_conf?.relationships || [];
|
|
365
|
+
const includes: string[] = rel_conf.map((rel : any) => rel.name).join(",")
|
|
366
|
+
const url = `${apiUrl}/${resource_name}?${stringify(query)}&include=${includes}`
|
|
367
|
+
return httpClient(url, options).then(({ headers, json }) => {
|
|
368
|
+
if (!headers.has(countHeader)) {
|
|
369
|
+
console.debug(
|
|
370
|
+
`The ${countHeader} header is missing in the HTTP Response. The simple REST data provider expects responses for lists of resources to contain this header with the total number of results to build the pagination. If you are using CORS, did you declare ${countHeader} in the Access-Control-Expose-Headers header?`
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
let total = json.meta?.total;
|
|
374
|
+
if (json.meta && settings.total) {
|
|
375
|
+
total = json.meta[settings.total];
|
|
376
|
+
}
|
|
377
|
+
// Use the length of the data array as a fallback.
|
|
378
|
+
total = total || json.data.length;
|
|
379
|
+
const lookup = new ResourceLookup(json);
|
|
380
|
+
const jsonData = json.data.map((resource: any) =>{
|
|
381
|
+
return lookup.unwrapData(resource)
|
|
382
|
+
}
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
return {
|
|
386
|
+
data: jsonData,
|
|
387
|
+
total: total
|
|
388
|
+
};
|
|
389
|
+
});
|
|
390
|
+
},
|
|
391
|
+
|
|
392
|
+
update: async(resource_name_en : string, params: any) => {
|
|
393
|
+
const conf = getConf();
|
|
394
|
+
const resource_name = decodeURI(resource_name_en)
|
|
395
|
+
let type = conf.resources[resource_name].type || resource_name;
|
|
396
|
+
|
|
397
|
+
const previousDataFiltered = Object.keys(params.previousData)
|
|
398
|
+
.filter(key => !(key in params.data))
|
|
399
|
+
.reduce((obj, key) => {
|
|
400
|
+
obj[key] = params.previousData[key];
|
|
401
|
+
return obj;
|
|
402
|
+
}, {});
|
|
403
|
+
|
|
404
|
+
let Sendingdata = { ...previousDataFiltered, ...params.data };
|
|
405
|
+
const data = {
|
|
406
|
+
data: {
|
|
407
|
+
id: params.id,
|
|
408
|
+
type: type,
|
|
409
|
+
attributes: Sendingdata
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
console.log('Update ', data)
|
|
414
|
+
|
|
415
|
+
return httpClient(`${apiUrl}/${resource_name}/${params.id}`, {
|
|
416
|
+
method: settings.updateMethod,
|
|
417
|
+
body: JSON.stringify(data)
|
|
418
|
+
})
|
|
419
|
+
.then(({ json }) => {
|
|
420
|
+
const { id, attributes } = json.data;
|
|
421
|
+
return {
|
|
422
|
+
data: {
|
|
423
|
+
id,
|
|
424
|
+
...attributes
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
})
|
|
428
|
+
.catch((err: HttpError) => {
|
|
429
|
+
console.log('catch Error', err.body);
|
|
430
|
+
const errorHandler = settings.errorHandler;
|
|
431
|
+
return Promise.reject(errorHandler(err));
|
|
432
|
+
});
|
|
433
|
+
},
|
|
434
|
+
|
|
435
|
+
// },
|
|
436
|
+
|
|
437
|
+
// simple-rest doesn't handle provide an updateMany route, so we fallback to calling update n times instead
|
|
438
|
+
updateMany: (resource_name, params) => {
|
|
439
|
+
// todo : bulk update
|
|
440
|
+
const conf = getConf();
|
|
441
|
+
return Promise.all(
|
|
442
|
+
params.ids.map((id) => {
|
|
443
|
+
const data = {
|
|
444
|
+
data: {
|
|
445
|
+
attributes: params.data
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
return httpClient(`${apiUrl}/${decodeURI(resource_name)}/${id}`, {
|
|
449
|
+
method: 'PATCH',
|
|
450
|
+
body: JSON.stringify(params.data)
|
|
451
|
+
})
|
|
452
|
+
})
|
|
453
|
+
)
|
|
454
|
+
.then((responses) => ({ data: responses.map(({ json }) => json.id) }))
|
|
455
|
+
},
|
|
456
|
+
|
|
457
|
+
create: (resource_name_en : string, params: any) => {
|
|
458
|
+
const conf = getConf();
|
|
459
|
+
const resource_name = decodeURI(resource_name_en)
|
|
460
|
+
let type = conf.resources[resource_name].type || resource_name;
|
|
461
|
+
// use both current (in case of a form reference) and data in case of raw dataProvider.create() call
|
|
462
|
+
let item_data = params.data?.current?.data ?? params.data;
|
|
463
|
+
console.log('creating resource with params', params)
|
|
464
|
+
const data = {
|
|
465
|
+
data: {
|
|
466
|
+
type: type,
|
|
467
|
+
attributes: item_data
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
return httpClient(`${apiUrl}/${resource_name}`, {
|
|
471
|
+
method: 'POST',
|
|
472
|
+
body: JSON.stringify(data)
|
|
473
|
+
})
|
|
474
|
+
.then(({ json }) => {
|
|
475
|
+
const { id, attributes } = json.data;
|
|
476
|
+
return {
|
|
477
|
+
data: {
|
|
478
|
+
id,
|
|
479
|
+
...attributes
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
})
|
|
483
|
+
.catch((err: HttpError) => {
|
|
484
|
+
console.log('catch Error', err.body);
|
|
485
|
+
const errorHandler = settings.errorHandler;
|
|
486
|
+
return Promise.reject(errorHandler(err));
|
|
487
|
+
})
|
|
488
|
+
},
|
|
489
|
+
|
|
490
|
+
delete: (resource, params) => {
|
|
491
|
+
const conf = getConf();
|
|
492
|
+
return httpClient(`${apiUrl}/${decodeURI(resource)}/${params.id}`, {
|
|
493
|
+
method: 'DELETE',
|
|
494
|
+
headers: new Headers({
|
|
495
|
+
'Content-Type': 'text/plain'
|
|
496
|
+
})
|
|
497
|
+
}).then(({ json }) => ({ data: json }))},
|
|
498
|
+
|
|
499
|
+
// simple-rest doesn't handle filters on DELETE route, so we fallback to calling DELETE n times instead
|
|
500
|
+
deleteMany: (resource, params) => {
|
|
501
|
+
const conf = getConf();
|
|
502
|
+
return Promise.all(
|
|
503
|
+
params.ids.map((id) =>
|
|
504
|
+
httpClient(`${apiUrl}/${decodeURI(resource)}/${id}`, {
|
|
505
|
+
method: 'DELETE',
|
|
506
|
+
headers: new Headers({
|
|
507
|
+
'Content-Type': 'text/plain'
|
|
508
|
+
})
|
|
509
|
+
})
|
|
510
|
+
)
|
|
511
|
+
).then((responses) => ({
|
|
512
|
+
data: responses.map(({ json }) => json)
|
|
513
|
+
}))
|
|
514
|
+
},
|
|
515
|
+
|
|
516
|
+
getResources: () => {
|
|
517
|
+
const conf = getConf();
|
|
518
|
+
if(conf){
|
|
519
|
+
return Promise.resolve({data: conf});
|
|
520
|
+
};
|
|
521
|
+
return httpClient(`${apiUrl}/schema`, {
|
|
522
|
+
method: 'GET'
|
|
523
|
+
}).then(({json}) => {
|
|
524
|
+
localStorage.setItem('raconf', JSON.stringify(json));
|
|
525
|
+
return { data: json };
|
|
526
|
+
})
|
|
527
|
+
.catch(()=> {return {data : {}} })
|
|
528
|
+
},
|
|
529
|
+
|
|
530
|
+
execute: (resource_name_en: string, command: string, data: { id: string|undefined, data: any, args: any }) => {
|
|
531
|
+
const conf = getConf();
|
|
532
|
+
const resource_name = decodeURI(resource_name_en)
|
|
533
|
+
console.log(`execute rpc on resource ${resource_name} with params`, data)
|
|
534
|
+
const id = data?.id || ""
|
|
535
|
+
if(!command){
|
|
536
|
+
console.warn('No command provided')
|
|
537
|
+
return new Promise(()=>{})
|
|
538
|
+
}
|
|
539
|
+
const endpoint = id ? `${apiUrl}/${resource_name}/${id}/${command}` : `${apiUrl}/${resource_name}/${command}`
|
|
540
|
+
return httpClient(endpoint, {
|
|
541
|
+
method: 'POST',
|
|
542
|
+
body: JSON.stringify(data?.args || {})
|
|
543
|
+
})
|
|
544
|
+
},
|
|
545
|
+
};
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
function capitalize(s: string): string {
|
|
549
|
+
// todo
|
|
550
|
+
return s;
|
|
551
|
+
return s[0].toUpperCase() + s.slice(1);
|
|
552
|
+
}
|
|
553
|
+
export interface includeRelations {
|
|
554
|
+
resource: string;
|
|
555
|
+
includes: string[];
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/*
|
|
559
|
+
Call safrs jsonapi rpc endpoints
|
|
560
|
+
*/
|
|
561
|
+
export const jaRpc = async (endpoint: string, data?: any, options?: RequestInit) => {
|
|
562
|
+
const url = urlJoin(conf.api_root, endpoint);
|
|
563
|
+
console.log('jaRpc', url, data, options);
|
|
564
|
+
console.log('jaRpc', conf);
|
|
565
|
+
|
|
566
|
+
const defaultOptions: RequestInit = {
|
|
567
|
+
method: "POST",
|
|
568
|
+
headers: {
|
|
569
|
+
"Content-Type": "application/json",
|
|
570
|
+
Authorization: `Bearer ${localStorage.getItem("auth_token")}`,
|
|
571
|
+
},
|
|
572
|
+
body: JSON.stringify(data || {}),
|
|
573
|
+
};
|
|
574
|
+
const requestOptions = { ...defaultOptions, ...options };
|
|
575
|
+
const response = await fetch(url, requestOptions);
|
|
576
|
+
return response.json();
|
|
577
|
+
};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
interface Relationship {
|
|
2
|
+
data: null | { id: string; type: string } | Array<{ id: string; type: string }>;
|
|
3
|
+
relationships?: { [key: string]: Relationship };
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A map-like class that maps resource linkage objects {id: 1, type: "user"} to concrete resources with attributes and
|
|
8
|
+
* relationships
|
|
9
|
+
*/
|
|
10
|
+
export default class ResourceLookup {
|
|
11
|
+
lookup: Map<any, any>;
|
|
12
|
+
includes: string[];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Constructs a new lookup map
|
|
16
|
+
* @param {Object} response A JSON API response, in JSON format
|
|
17
|
+
*/
|
|
18
|
+
constructor(response: any) {
|
|
19
|
+
this.lookup = new Map();
|
|
20
|
+
this.includes = [];
|
|
21
|
+
|
|
22
|
+
// If the response wasn't a JSON dictionary, we can't and don't need to build a lookup
|
|
23
|
+
if (typeof response !== 'object') return;
|
|
24
|
+
|
|
25
|
+
let resources;
|
|
26
|
+
// if (response.hasOwnProperty('included')) {
|
|
27
|
+
if (Object.prototype.hasOwnProperty.call(response, 'included')) {
|
|
28
|
+
resources = [...response.data, ...response.included];
|
|
29
|
+
} else {
|
|
30
|
+
resources = response.data;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Iterate over each resource returned and put each in the lookup
|
|
34
|
+
for (const entry of resources) {
|
|
35
|
+
const key = this.getKey(entry);
|
|
36
|
+
this.lookup.set(key, entry);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Calculates a hashable key for JSON API resources
|
|
42
|
+
* @param resource A resource linkage object
|
|
43
|
+
* @returns {string}
|
|
44
|
+
*/
|
|
45
|
+
getKey(resource: any) {
|
|
46
|
+
return `${resource.type}:${resource.id}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Looks up a resource
|
|
51
|
+
* @param resource A resource linkage object
|
|
52
|
+
* @returns {Object}
|
|
53
|
+
*/
|
|
54
|
+
get(resource: any) {
|
|
55
|
+
// If we don't have included data for this resource, just return the Resource Linkage object, since that's still
|
|
56
|
+
// useful
|
|
57
|
+
if (this.has(resource)) {
|
|
58
|
+
return this.lookup.get(this.getKey(resource));
|
|
59
|
+
} else {
|
|
60
|
+
return structuredClone(resource);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Returns true if the resource is in the lookup
|
|
66
|
+
* @param resource
|
|
67
|
+
* @returns {boolean}
|
|
68
|
+
*/
|
|
69
|
+
has(resource: any) {
|
|
70
|
+
return this.lookup.has(this.getKey(resource));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Converts a JSON API data object (with id, type, and attributes fields) into a flattened object
|
|
75
|
+
* @param {Object} response A JSON API data object
|
|
76
|
+
*/
|
|
77
|
+
unwrapData(response: any) {
|
|
78
|
+
// The base resource object, merge the attributes and the id/type fields
|
|
79
|
+
const result = Object.assign(
|
|
80
|
+
{
|
|
81
|
+
id: response?.id,
|
|
82
|
+
ja_type: response?.type,
|
|
83
|
+
type: response?.Type || response?.type,
|
|
84
|
+
attributes: response.attributes || {},
|
|
85
|
+
},
|
|
86
|
+
response.attributes
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
// Deal with relationships
|
|
90
|
+
if (Object.prototype.hasOwnProperty.call(response, 'relationships')) {
|
|
91
|
+
result.relationships = response.relationships;
|
|
92
|
+
for (const [relationshipName, relationship] of Object.entries(response.relationships)) {
|
|
93
|
+
if ((relationship ).data === null) {
|
|
94
|
+
result[relationshipName] = null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!(relationship ).data) {
|
|
98
|
+
continue;
|
|
99
|
+
} else if (Array.isArray((relationship ).data && (relationship ).data)) {
|
|
100
|
+
result[relationshipName] = (relationship ).data?.map((linkage: any) => {
|
|
101
|
+
const resource = this.get(linkage);
|
|
102
|
+
const lresult = structuredClone(this.unwrapData(resource));
|
|
103
|
+
Object.entries(lresult.attributes || {}).map(([key, value]) => {
|
|
104
|
+
lresult[key] = value;
|
|
105
|
+
});
|
|
106
|
+
result.relationships[relationshipName].data = result[relationshipName];
|
|
107
|
+
return lresult;
|
|
108
|
+
});
|
|
109
|
+
} else if ((relationship ).data?.id) {
|
|
110
|
+
const resource = this.get((relationship ).data);
|
|
111
|
+
result[relationshipName] = this.unwrapData(resource);
|
|
112
|
+
result.relationships[relationshipName].data = result[relationshipName];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
JSON.stringify(structuredClone(result));
|
|
119
|
+
} catch (e) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
return structuredClone(result);
|
|
123
|
+
}
|
|
124
|
+
}
|