UNPKG

14.6 kBPlain TextView Raw
1/**
2 * Created by frank.zickert on 02.05.19.
3 */
4
5import AWS from 'aws-sdk';
6import gql from 'graphql-tag';
7import Cookies from 'universal-cookie';
8
9import { mutation, params, types, query } from 'typed-graphqlify'
10import {IC_USER_ID} from "../authentication/auth-middleware";
11
12/**
13 * transforms a function into a Promise
14 */
15const promisify = foo => new Promise((resolve, reject) => {
16 foo((error, result) => {
17 if(error) {
18 reject(error)
19 } else {
20 resolve(result)
21 }
22 })
23});
24
25const applyOfflineConfig = (offline: boolean) => {
26 return offline ? {
27 region: 'localhost',
28 endpoint: 'http://localhost:8000'
29 } : {}
30};
31
32export const setEntry = (tableName, pkEntity, pkId, skEntity, skId, jsonData, isOffline) => {
33
34 //console.log("setEntry: ", pkEntity, "|", pkId, "|", skEntity, "|", skId );
35
36 return promisify(callback =>
37 new AWS.DynamoDB.DocumentClient(applyOfflineConfig(isOffline)).update({
38 TableName: tableName,
39 /**
40 * ALL KEYS MUST BE SPECIFIED HERE!
41 */
42 Key: {
43 pk: `${pkEntity}|${pkId}`,
44 sk: `${skEntity}|${skId}`
45 },
46 UpdateExpression: `SET jsonData = :jsonData`,
47 ExpressionAttributeValues: {
48 ':jsonData': `${JSON.stringify(jsonData)}`,
49 }
50 }, callback))
51 .then(() => {
52 //console.log("data stored!")
53
54 var result = {};
55 result[pkEntity] = pkId;
56 result[skEntity] = skId;
57 result["data"] = `${JSON.stringify(jsonData).replace(/"/g, "\\\"")}`;
58
59 return result;
60 }).catch(error => { console.log(error) });
61};
62
63
64/**
65 * Get all entries to a entity|value pair in the key-field whose range have the specified rangeEntity
66 *
67 * @param key specify which field is the key: pk or sk
68 * @param entity specifies the entity of the key-field
69 * @param value specify the id of the key-field
70 * @param rangeEntity specify the entity of the range
71 * @returns {Promise<string>|any}
72 */
73export const ddbListEntries = (tableName, key, entity, value, rangeEntity, isOffline) => {
74
75 //console.log("ddbListEntries: ", tableName, key, entity, value, rangeEntity, isOffline);
76
77 const q = {
78 // use the table_name as specified in the serverless.yml
79 TableName: tableName,
80 IndexName: key === "sk" ? "reverse" : undefined,
81 /**
82 * ALL KEYS MUST HAVE KEY-CONDITION-EXPRESSIONS!
83 */
84 KeyConditionExpression: `${
85 key
86 } = :value and begins_with(${
87 key === "pk" ? "sk" : "pk"
88 }, :entity)`,
89 ExpressionAttributeValues: {
90 ":value": `${entity}|${value}`,
91 ":entity": rangeEntity ? rangeEntity : "undefined"
92 }
93 };
94
95 //console.log("query: ", q);
96
97 return promisify(callback =>
98 new AWS.DynamoDB.DocumentClient(applyOfflineConfig(isOffline)).query(q, callback))
99 .then(result => {
100 //console.log("ddb-result: ", result);
101 return result["Items"];
102
103 /*
104 if (result.Items) {
105
106 return result.Items.map(item => JSON.stringify(item));
107 }
108
109 return [];*/
110 //return result.Items.map(item => JSON.stringify(item));
111 }).catch(error => { console.log(error) });
112};
113
114export const ddbGetEntry = (tableName, pkEntity, pkValue, skEntity, skValue, isOffline) => {
115
116 //console.log("ddbGetEntry: ", `${pkEntity}|${pkValue}`, ` -- ${skEntity}|${skValue}`, " -- ", tableName);
117
118 const q = {
119 TableName: tableName,
120 Key: {
121 pk: `${pkEntity}|${pkValue}`,
122 sk: `${skEntity}|${skValue}`
123 }
124 };
125
126 //console.log("ddbGetEntry-query: ", q);
127
128 return promisify(callback =>
129 new AWS.DynamoDB.DocumentClient(applyOfflineConfig(isOffline)).get(q, callback))
130 .then(result => {
131 //console.log("ddbGetEntry result: ", result);
132
133 return result["Item"] ? result["Item"] : result;
134
135 }).catch(error => { console.log(error) });
136};
137
138export const ddbScan = (tableName, key, entity, start_value, end_value, rangeEntity, isOffline) => {
139
140 //console.log("scan: ", tableName, key, entity, start_value, end_value, rangeEntity, isOffline);
141
142 const q = {
143 // use the table_name as specified in the serverless.yml
144 TableName: tableName,
145 FilterExpression: `${
146 key
147 } between :sv and :ev and begins_with(${
148 key === "pk" ? "sk" : "pk"
149 }, :entity)`,
150 ExpressionAttributeValues: {
151 ":sv": `${entity}|${start_value}`,
152 ":ev": `${entity}|${end_value}`,
153 ":entity": rangeEntity ? rangeEntity : "undefined"
154 }
155 };
156
157 const allQ = {
158 // use the table_name as specified in the serverless.yml
159 TableName: tableName,
160 FilterExpression: `begins_with(${key}, :entity) and begins_with(${
161 key === "pk" ? "sk" : "pk"}, :rangeentity)`,
162 ExpressionAttributeValues: {
163 ":entity": entity,
164 ":rangeentity": rangeEntity ? rangeEntity : "undefined"
165 }
166 };
167
168
169
170 //console.log("query: ", q);
171
172 return promisify(callback =>
173 new AWS.DynamoDB.DocumentClient(applyOfflineConfig(isOffline)).scan(start_value && end_value ? q : allQ, callback))
174 .then(result => {
175 //console.log("ddb-result: ", result);
176 return result["Items"];
177
178 /**
179 * TODO
180
181
182 return await client.select(Object.assign({
183 query: query,
184 context: context
185 }, params)).then(result => {
186 //console.log("select result: ", result)
187
188 return result.data.Items.concat(typeof result.data.LastEvaluatedKey != "undefined" ?
189 scan(
190 client, {
191 query: query,
192 context: context,
193 params: {
194 ExclusiveStartKey: result.data.LastEvaluatedKey
195 }
196 }
197 ): []);
198 // continue scanning if we have more movies, because
199 // scan can retrieve a maximum of 1MB of data
200 if (typeof data.LastEvaluatedKey != "undefined") {
201 console.log("Scanning for more...");
202 params.ExclusiveStartKey = data.LastEvaluatedKey;
203 docClient.scan(params, onScan);
204 }
205
206 return [];*/
207 //return result.Items.map(item => JSON.stringify(item));
208 }).catch(error => { console.log(error) });
209};
210
211
212
213export const deleteEntry = (tableName, pkEntity, pkValue, skEntity, skValue, isOffline) => {
214
215 //console.log("delete entry: ", pkEntity, pkValue, skEntity, skValue)
216 //console.log("pk: ", `${pkEntity}|${pkValue}`);
217 //console.log("sk: ", `${skEntity}|${skValue}`);
218
219 return promisify(callback =>
220 new AWS.DynamoDB.DocumentClient(applyOfflineConfig(isOffline)).delete({
221 // use the table_name as specified in the serverless.yml
222 TableName: tableName,
223 Key: {
224 pk: `${pkEntity}|${pkValue}`,
225 sk: `${skEntity}|${skValue}`
226 }
227 }, callback))
228 .then(result => {
229 //console.log("result: ", result);
230
231 return result["Item"] ? result["Item"] : result;
232
233 }).catch(error => { console.log(error) });
234};
235
236
237
238/**
239 * this function provides a executable graphql-query
240 * TODO the fields must be taken from the data-layer, not requiring the user to provide them
241 */
242export const setEntryMutation = ( entryId, data, fields, context={}) => {
243 //console.log("setEntryMutation: ", entryId, data, fields);
244
245
246 const mutationObj = {};
247 mutationObj[`set_${entryId}`] = params(
248 Object.keys(data).reduce((result, key) => {
249 result[key] = `"${data[key]}"`;
250 return result;
251 },{}),
252 Object.keys(fields).reduce((result, key) => {
253 result[key] = types.string;
254 return result;
255 },{})
256 );
257
258 return {
259 mutation: gql`${mutation(mutationObj)}`,
260 context: context
261 }
262
263};
264
265
266export const deleteEntryMutation = ( entryId, data, fields, context={}) => {
267 //console.log("deleteEntryMutation: ", entryId, data);
268
269 const mutationObj = {};
270 mutationObj[`delete_${entryId}`] = params(
271 Object.keys(data).reduce((result, key) => {
272 result[key] = `"${data[key]}"`;
273 return result;
274 },{}),
275 Object.keys(fields).reduce((result, key) => {
276 result[key] = types.string;
277 return result;
278 },{})
279 );
280
281 return {
282 mutation: gql`${mutation(mutationObj)}`,
283 context: context
284 }
285
286};
287
288
289/**
290 * this function provides a executable graphql-query
291 */
292export const getEntryListQuery = ( entryId, data, fields, context={}) => {
293 //console.log("getEntryListQuery: ", entryId, data, fields, context);
294
295 if (data == undefined) {
296 console.error("getEntryListQuery requires a data argument");
297 return undefined;
298 }
299
300
301
302 if (Object.keys(data).length !== 1) {
303 console.error("getEntryListQuery requires exact 1 field provided in the data argument");
304 return undefined;
305 }
306
307 const queryKey = Object.keys(data)[0];
308
309 const queryObj = {};
310 queryObj[`list_${entryId}_${queryKey}`] = params(
311 Object.keys(data).filter(key => key === queryKey).reduce((result, key) => {
312 result[key] = `"${data[key]}"`;
313 return result;
314 },{}),
315 Object.keys(fields).reduce((result, key) => {
316 result[key] = types.string;
317 return result;
318 },{})
319 );
320
321 //console.log("listQuery string: ", query(queryObj));
322
323 return {
324 query:gql`${query(queryObj)}`,
325 context: context
326 }
327
328};
329
330export const getEntryQuery = ( entryId, data, fields, context={}) => {
331 //console.log("getEntryQuery: ", entryId, data, fields, context);
332
333 if (data == undefined) {
334 console.error("getEntryQuery requires a data argument");
335 return undefined;
336 }
337
338 if (Object.keys(data).length !== 2) {
339 console.error("getEntryQuery requires exact 2 fields provided in the data argument");
340 return undefined;
341 }
342
343 const queryObj = {};
344 queryObj[`get_${entryId}`] = params(
345 Object.keys(data).reduce((result, key) => {
346 result[key] = `"${data[key]}"`;
347 return result;
348 },{}),
349 Object.keys(fields).reduce((result, key) => {
350 result[key] = types.string;
351 return result;
352 },{})
353 );
354
355 //console.log("listQuery string: ", query(queryObj));
356
357 return {
358 query:gql`${query(queryObj)}`,
359 context: context
360 }
361
362};
363
364export const updateEntryQuery = ( entryId, callback, context={}) => {
365 return {entryId: entryId, callback: callback, context: context };
366}
367
368
369/**
370 * this function provides a executable graphql-query: "scan_{entryId}"
371 *
372 */
373export const getEntryScanQuery = ( entryId, data, fields, context={}) => {
374 //console.log("getEntryScanQuery: ", entryId, data, fields, context);
375
376 if (data == undefined) {
377 console.error("getEntryScanQuery requires a data argument, this may be empty");
378 return undefined;
379 }
380
381
382 const queryObj = {};
383
384 if (Object.keys(data).length > 0) {
385 const queryKey = Object.keys(data)[0];
386
387 queryObj[`scan_${entryId}_${queryKey}`] = params(
388 Object.keys(data).reduce((result, key) => {
389 // when we have an array at the key-pos in data, then we want to get a range
390 if (Array.isArray(data[key])) {
391 if (data[key].length > 0 && data[key][0] !== undefined) {
392 result[`start_${key}`] = `"${data[key][0]}"`;
393 }
394
395 if (data[key].length > 1 && data[key][1] !== undefined) {
396 result[`end_${key}`] = `"${data[key][1]}"`;
397 }
398
399 } else {
400 result[key] = `"${data[key]}"`;
401 }
402
403 return result;
404 },{}),
405 Object.keys(fields).reduce((result, key) => {
406 result[key] = types.string;
407 return result;
408 },{})
409 );
410
411 //console.log(gql`${query(queryObj)}`);
412
413 return {
414 query:gql`${query(queryObj)}`,
415 context: context
416 }
417
418 } else {
419
420 queryObj[`scan_${entryId}`] = params(
421 {scanall:`"yes"`},
422 Object.keys(fields).reduce((result, key) => {
423 result[key] = types.string;
424 return result;
425 },{})
426 );
427
428 //console.log(gql`${query(queryObj)}`);
429
430 return {
431 query:gql`${query(queryObj)}`,
432 context: context
433 }
434
435 }
436
437 //console.log("scanQuery string: ", query(queryObj));
438
439
440
441};
442
443export async function select (client, {query, context={}}) {
444
445
446 if (!context["userId"]) {
447 context["userId"] = new Cookies().get(IC_USER_ID);
448 }
449
450 //console.log("select: ", query, context);
451
452
453 return await client.query({
454 query: query,
455 context: context
456 }).then(result => {
457 //console.log("select result: ", result)
458
459 return result.data ? result.data : result;
460
461 }).catch(error => {
462 console.log(error);
463 });
464
465};
466
467
468
469/**
470 * uses this: https://github.com/acro5piano/typed-graphqlify
471 *
472 * TODO generalize to other data-types than string
473 *
474 * @param client
475 * @param entryId
476 * @param data
477 * @returns {any|Promise<T>|Promise<U>}
478 */
479export async function mutate (client, { mutation, context={}}) {
480
481 if (!context["userId"]) {
482 context["userId"] = new Cookies().get(IC_USER_ID);
483 }
484
485
486 //console.log("mutate: ", mutation, context);
487
488 //console.log("mutation string: ", mutation(mutationObj));
489
490 return await client.mutate({
491 mutation: mutation,
492 context: context
493 }).then(result => { /*console.log(result)*/}).catch(error => { console.log(error) });
494
495};
496
497/**
498 *
499 * @param client
500 * @param callback (oldData) => newData
501 * @param context
502 * @returns {Promise<any>}
503 */
504export async function update (client, { entryId, getEntryQuery, setEntryMutation}) {
505
506 const oldData = await select(
507 client,
508 getEntryQuery()
509 );
510
511
512 return await mutate(
513 client,
514 setEntryMutation(oldData[`get_${entryId}`])
515 );
516
517
518};
519