ApiLogicServer 15.0.25__py3-none-any.whl → 15.0.27__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 +2 -2
- api_logic_server_cli/api_logic_server_info.yaml +3 -3
- api_logic_server_cli/genai/genai_admin_app.py +29 -19
- api_logic_server_cli/prototypes/.DS_Store +0 -0
- api_logic_server_cli/prototypes/basic_demo/.DS_Store +0 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/.DS_Store +0 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/.DS_Store +0 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/admin/.DS_Store +0 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/.DS_Store +0 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/README.md +17 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/README_create_react_app.md +70 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/package-lock.json +18469 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/package.json +47 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/public/favicon.ico +0 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/public/index.html +43 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/public/logo192.png +0 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/public/logo512.png +0 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/public/manifest.json +25 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/public/robots.txt +3 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/src/App.css +38 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/src/App.js +61 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/src/App.test.js +8 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/src/Config-reference.js +527 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/src/Config.js +527 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/src/Customer-reference.js +216 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/src/Customer.js +230 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/src/Item.js +170 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/src/Order.js +207 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/src/Product.js +140 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/src/SysEmail.js +157 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/src/SysMcp.js +110 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/src/app_loader.js +24 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/src/index.css +13 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/src/index.js +17 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/src/logo.svg +1 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/src/rav4-jsonapi-client/.eslintrc +5 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/src/rav4-jsonapi-client/.yarnrc.yml +4 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/src/rav4-jsonapi-client/default-settings.js +25 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/src/rav4-jsonapi-client/default-settings.ts +25 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/src/rav4-jsonapi-client/errors.js +116 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/src/rav4-jsonapi-client/errors.ts +116 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/src/rav4-jsonapi-client/index.test.tsx +7 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/src/rav4-jsonapi-client/index.tsx +11 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/src/rav4-jsonapi-client/ra-jsonapi-client.js +577 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/src/rav4-jsonapi-client/ra-jsonapi-client.ts +577 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/src/rav4-jsonapi-client/resourceLookup.js +124 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/src/rav4-jsonapi-client/resourceLookup.ts +124 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/src/rav4-jsonapi-client/styles.module.css +9 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/src/reportWebVitals.js +13 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/basic_demo_app/src/setupTests.js +5 -0
- api_logic_server_cli/prototypes/manager/system/genai/app_templates/app_learning/Admin-App-Resource-Learning-Prompt.md +37 -3
- {apilogicserver-15.0.25.dist-info → apilogicserver-15.0.27.dist-info}/METADATA +1 -1
- {apilogicserver-15.0.25.dist-info → apilogicserver-15.0.27.dist-info}/RECORD +57 -11
- {apilogicserver-15.0.25.dist-info → apilogicserver-15.0.27.dist-info}/WHEEL +0 -0
- {apilogicserver-15.0.25.dist-info → apilogicserver-15.0.27.dist-info}/entry_points.txt +0 -0
- {apilogicserver-15.0.25.dist-info → apilogicserver-15.0.27.dist-info}/licenses/LICENSE +0 -0
- {apilogicserver-15.0.25.dist-info → apilogicserver-15.0.27.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
|
+
})) ;
|
|
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
|
+
};
|