@nlabs/reaktor 0.1.0

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.
Files changed (154) hide show
  1. package/.vscode/extensions.json +15 -0
  2. package/.vscode/settings.json +82 -0
  3. package/README.md +211 -0
  4. package/index.d.ts +1 -0
  5. package/index.js +5 -0
  6. package/lex.config.js +4 -0
  7. package/lib/config.d.ts +21 -0
  8. package/lib/config.js +127 -0
  9. package/lib/data/conversations.d.ts +6 -0
  10. package/lib/data/conversations.js +201 -0
  11. package/lib/data/dynamodb.d.ts +8 -0
  12. package/lib/data/dynamodb.js +139 -0
  13. package/lib/data/email.d.ts +7 -0
  14. package/lib/data/email.js +164 -0
  15. package/lib/data/files.d.ts +16 -0
  16. package/lib/data/files.js +407 -0
  17. package/lib/data/groups.d.ts +13 -0
  18. package/lib/data/groups.js +354 -0
  19. package/lib/data/images.d.ts +12 -0
  20. package/lib/data/images.js +668 -0
  21. package/lib/data/index.d.ts +19 -0
  22. package/lib/data/index.js +24 -0
  23. package/lib/data/ios.d.ts +6 -0
  24. package/lib/data/ios.js +302 -0
  25. package/lib/data/locations.d.ts +3 -0
  26. package/lib/data/locations.js +132 -0
  27. package/lib/data/messages.d.ts +9 -0
  28. package/lib/data/messages.js +248 -0
  29. package/lib/data/notifications.d.ts +5 -0
  30. package/lib/data/notifications.js +42 -0
  31. package/lib/data/payments.d.ts +11 -0
  32. package/lib/data/payments.js +748 -0
  33. package/lib/data/posts.d.ts +14 -0
  34. package/lib/data/posts.js +458 -0
  35. package/lib/data/reactions.d.ts +6 -0
  36. package/lib/data/reactions.js +218 -0
  37. package/lib/data/s3.d.ts +6 -0
  38. package/lib/data/s3.js +103 -0
  39. package/lib/data/search.d.ts +3 -0
  40. package/lib/data/search.js +98 -0
  41. package/lib/data/sms.d.ts +3 -0
  42. package/lib/data/sms.js +59 -0
  43. package/lib/data/subscription.d.ts +7 -0
  44. package/lib/data/subscription.js +284 -0
  45. package/lib/data/tags.d.ts +14 -0
  46. package/lib/data/tags.js +304 -0
  47. package/lib/data/users.d.ts +12 -0
  48. package/lib/data/users.js +312 -0
  49. package/lib/index.d.ts +3 -0
  50. package/lib/index.js +8 -0
  51. package/lib/types/apps.d.ts +44 -0
  52. package/lib/types/apps.js +2 -0
  53. package/lib/types/arangodb.d.ts +17 -0
  54. package/lib/types/arangodb.js +2 -0
  55. package/lib/types/auth.d.ts +9 -0
  56. package/lib/types/auth.js +2 -0
  57. package/lib/types/conversations.d.ts +6 -0
  58. package/lib/types/conversations.js +2 -0
  59. package/lib/types/email.d.ts +12 -0
  60. package/lib/types/email.js +2 -0
  61. package/lib/types/files.d.ts +28 -0
  62. package/lib/types/files.js +2 -0
  63. package/lib/types/google.d.ts +27 -0
  64. package/lib/types/google.js +2 -0
  65. package/lib/types/groups.d.ts +22 -0
  66. package/lib/types/groups.js +2 -0
  67. package/lib/types/images.d.ts +25 -0
  68. package/lib/types/images.js +2 -0
  69. package/lib/types/index.d.ts +17 -0
  70. package/lib/types/index.js +22 -0
  71. package/lib/types/locations.d.ts +21 -0
  72. package/lib/types/locations.js +2 -0
  73. package/lib/types/messages.d.ts +12 -0
  74. package/lib/types/messages.js +2 -0
  75. package/lib/types/notifications.d.ts +19 -0
  76. package/lib/types/notifications.js +2 -0
  77. package/lib/types/payments.d.ts +119 -0
  78. package/lib/types/payments.js +2 -0
  79. package/lib/types/posts.d.ts +20 -0
  80. package/lib/types/posts.js +2 -0
  81. package/lib/types/reactions.d.ts +4 -0
  82. package/lib/types/reactions.js +2 -0
  83. package/lib/types/tags.d.ts +10 -0
  84. package/lib/types/tags.js +2 -0
  85. package/lib/types/users.d.ts +78 -0
  86. package/lib/types/users.js +2 -0
  87. package/lib/utils/analytics.d.ts +3 -0
  88. package/lib/utils/analytics.js +47 -0
  89. package/lib/utils/arangodb.d.ts +9 -0
  90. package/lib/utils/arangodb.js +98 -0
  91. package/lib/utils/auth.d.ts +2 -0
  92. package/lib/utils/auth.js +43 -0
  93. package/lib/utils/index.d.ts +5 -0
  94. package/lib/utils/index.js +10 -0
  95. package/lib/utils/objects.d.ts +3 -0
  96. package/lib/utils/objects.js +34 -0
  97. package/lib/utils/redis.d.ts +1 -0
  98. package/lib/utils/redis.js +15 -0
  99. package/package.json +75 -0
  100. package/src/config.ts +121 -0
  101. package/src/data/conversations.ts +183 -0
  102. package/src/data/dynamodb.ts +157 -0
  103. package/src/data/email.ts +164 -0
  104. package/src/data/files.ts +352 -0
  105. package/src/data/groups.ts +308 -0
  106. package/src/data/images.ts +606 -0
  107. package/src/data/index.ts +23 -0
  108. package/src/data/ios.ts +249 -0
  109. package/src/data/locations.ts +114 -0
  110. package/src/data/messages.ts +237 -0
  111. package/src/data/notifications.ts +48 -0
  112. package/src/data/payments.ts +675 -0
  113. package/src/data/posts.ts +508 -0
  114. package/src/data/reactions.ts +186 -0
  115. package/src/data/s3.ts +117 -0
  116. package/src/data/search.ts +74 -0
  117. package/src/data/sms.ts +60 -0
  118. package/src/data/subscription.ts +228 -0
  119. package/src/data/tags.ts +230 -0
  120. package/src/data/users.ts +256 -0
  121. package/src/index.ts +7 -0
  122. package/src/types/apps.ts +57 -0
  123. package/src/types/arangodb.ts +23 -0
  124. package/src/types/auth.ts +19 -0
  125. package/src/types/conversations.ts +11 -0
  126. package/src/types/email.ts +17 -0
  127. package/src/types/files.ts +33 -0
  128. package/src/types/google.ts +37 -0
  129. package/src/types/groups.ts +28 -0
  130. package/src/types/images.ts +33 -0
  131. package/src/types/index.ts +21 -0
  132. package/src/types/locations.ts +25 -0
  133. package/src/types/messages.ts +16 -0
  134. package/src/types/notifications.ts +26 -0
  135. package/src/types/payments.ts +134 -0
  136. package/src/types/posts.ts +25 -0
  137. package/src/types/reactions.ts +8 -0
  138. package/src/types/tags.ts +14 -0
  139. package/src/types/users.ts +89 -0
  140. package/src/utils/analytics.ts +41 -0
  141. package/src/utils/arangodb.ts +100 -0
  142. package/src/utils/auth.ts +28 -0
  143. package/src/utils/index.ts +9 -0
  144. package/src/utils/objects.ts +34 -0
  145. package/src/utils/redis.ts +17 -0
  146. package/templates/email/layout.html +279 -0
  147. package/templates/email/passwordForgot.html +15 -0
  148. package/templates/email/passwordRecovery.html +12 -0
  149. package/templates/email/verifyEmail.html +15 -0
  150. package/templates/sms/passwordForgot.txt +1 -0
  151. package/templates/sms/passwordRecovery.txt +1 -0
  152. package/templates/sms/verifyEmail.txt +1 -0
  153. package/templates/sms/verifyPhone.txt +1 -0
  154. package/tsconfig.json +45 -0
@@ -0,0 +1,606 @@
1
+ import {get as httpGet} from '@nlabs/rip-hunter';
2
+ import {createHash, parseChar, parseId, parseNum} from '@nlabs/utils';
3
+ import {aql} from 'arangojs';
4
+ import {AqlQuery} from 'arangojs/lib/cjs/aql-query';
5
+ import {ArrayCursor} from 'arangojs/lib/cjs/cursor';
6
+ import {DeleteObjectsRequest, PutObjectRequest} from 'aws-sdk/clients/s3';
7
+ import * as gm from 'gm';
8
+ import cloneDeep from 'lodash/cloneDeep';
9
+ import isEmpty from 'lodash/isEmpty';
10
+ import {DateTime} from 'luxon';
11
+
12
+ import {Config} from '../config';
13
+ import {
14
+ ApiContext,
15
+ ArangoDBLimit,
16
+ FileDecodedType,
17
+ FileEdgeType,
18
+ FileType,
19
+ GroupType,
20
+ GroupUserType,
21
+ ImageIdentifyType,
22
+ ImageType,
23
+ ImageUrlData,
24
+ QueryFilter
25
+ } from '../types';
26
+ import {defaultObject, getLimit, logError, logException, lowerCaseKeys, useDb} from '../utils';
27
+ import {decodeBase64} from './files';
28
+ import {getGroupDetails, isGrouped} from './groups';
29
+ import {s3DeleteList, s3Put} from './s3';
30
+
31
+ /**
32
+ * Copyright (c) 2019-Present, Nitrogen Labs, Inc.
33
+ * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.
34
+ */
35
+ const eventCategory: string = 'images';
36
+
37
+ export const getImageListByUser = (context: ApiContext, userId: string, from: number, to: number): Promise<ImageType[]> => {
38
+ const action: string = 'getListByUser';
39
+ const {database} = context;
40
+ const formatUserId: string = parseId(userId);
41
+ const limit: ArangoDBLimit = getLimit(from, to);
42
+ const aqlQry: string = `FOR i IN images
43
+ FILTER i.userId == "${formatUserId}"
44
+ LET user = (
45
+ FOR u IN users
46
+ FILTER u._key == i.userId
47
+ LIMIT 1
48
+ RETURN u
49
+ )
50
+ ${limit.aql}
51
+ SORT i.added
52
+ RETURN MERGE(i, {user: FIRST(user)})`;
53
+
54
+ return useDb(database).query(aqlQry)
55
+ .then((cursor: ArrayCursor) => cursor.all())
56
+ .catch((error: Error) => logError({
57
+ action,
58
+ category: eventCategory,
59
+ label: 'db_error'
60
+ }, error, context).then(() => null));
61
+ };
62
+
63
+ export const getImageListByGroup = (context: ApiContext, params): Promise<ImageType[]> => {
64
+ const action: string = 'getListByGroup';
65
+ const {database, userId: sessionId} = context;
66
+ const {filters = [], groupId, from, to} = params;
67
+ const formatGroupId: string = parseId(groupId);
68
+ const limit = getLimit(from, to);
69
+
70
+ filters
71
+ .map((filter: QueryFilter) => {
72
+ const {conditional, name, value} = filter;
73
+ let formatCond: string = conditional;
74
+
75
+ if(conditional !== '>=' && conditional !== '<=' && conditional !== '>' && conditional !== '<') {
76
+ formatCond = '==';
77
+ }
78
+
79
+ switch(name) {
80
+ case 'added':
81
+ return `p.added ${formatCond} ${parseNum(value)}`;
82
+ default:
83
+ return '';
84
+ }
85
+ });
86
+
87
+ return getGroupDetails(context, formatGroupId)
88
+ .then((group: GroupType) => {
89
+ if(group.privacy === 'public') {
90
+ filters.push(`p.groupId == "${groupId}"`);
91
+ const filterStr = filters.join(' && ');
92
+ const aqlQry: string = `FOR i IN
93
+ FLATTEN(
94
+ FOR p IN posts
95
+ FILTER ${filterStr}
96
+ LET images = (
97
+ FOR i, e IN INBOUND p._id hasImage
98
+ RETURN i
99
+ )
100
+ SORT p.added DESC
101
+ RETURN images
102
+ )
103
+ SORT i.added DESC
104
+ ${limit.aql}
105
+ RETURN i`;
106
+
107
+ return useDb(database).query(aqlQry)
108
+ .then((cursor: ArrayCursor) => cursor.all())
109
+ .catch((error: Error) => logError({
110
+ action,
111
+ category: eventCategory,
112
+ label: 'db_error'
113
+ }, error, context).then(() => null));
114
+ }
115
+ return isGrouped(database, sessionId, groupId)
116
+ .then((grouped: GroupUserType) => {
117
+ if(grouped.isValid) {
118
+ filters.push(`p.groupId == "${grouped.groupId}"`);
119
+ const filterList: string = filters.join(' && ');
120
+ const aqlQry: string = `FOR p IN post
121
+ FILTER ${filterList}
122
+ FOR f IN p.files
123
+ FILTER f.type == "image/jpeg" || f.type == "image/png"
124
+ ${limit.aql}
125
+ SORT p.added DESC
126
+ RETURN f`;
127
+
128
+ return useDb(database).query(aqlQry)
129
+ .then((cursor: ArrayCursor) => cursor.all())
130
+ .catch((error: Error) => logError({
131
+ action,
132
+ category: eventCategory,
133
+ label: 'db_error'
134
+ }, error, context).then(() => null));
135
+ }
136
+ return [];
137
+ });
138
+ });
139
+ };
140
+
141
+ export const getImage = (context: ApiContext, id: string): Promise<ImageType> => {
142
+ const action: string = 'getItem';
143
+ const {database} = context;
144
+ const formatId: string = parseId(id);
145
+ const aqlQry: AqlQuery = aql`FOR i IN images
146
+ FILTER i._key==${formatId}
147
+ LIMIT 1
148
+ RETURN i`;
149
+
150
+ return useDb(database).query(aqlQry)
151
+ .then((cursor: ArrayCursor) => cursor.next())
152
+ .then((image: ImageType = {}) => image)
153
+ .catch((error: Error) => logError({
154
+ action,
155
+ category: eventCategory,
156
+ label: 'db_error'
157
+ }, error, context).then(() => null));
158
+ };
159
+
160
+ export const updateImage = (context: ApiContext, item: FileType): Promise<ImageType> => {
161
+ const action: string = 'update';
162
+ const {database, userId: sessionId} = context;
163
+
164
+ // Item props
165
+ const {
166
+ base64 = '',
167
+ description = '',
168
+ fileType = '',
169
+ id,
170
+ itemId,
171
+ itemType,
172
+ url = ''
173
+ } = item;
174
+
175
+ // Save Base64 data
176
+ const photoId: string = id || createHash(`image-${sessionId}`);
177
+ const now: number = Date.now();
178
+ let contentType: string = fileType;
179
+ const formatItemId: string = parseId(itemId);
180
+ const formatItemType: string = parseChar(itemType).toLowerCase();
181
+
182
+ if(base64 !== '') {
183
+ let decodedBase64: FileDecodedType;
184
+
185
+ try {
186
+ decodedBase64 = decodeBase64(base64);
187
+ } catch(error) {
188
+ return logError({
189
+ action,
190
+ category: eventCategory,
191
+ label: 'decode_base64'
192
+ }, error, context).then(() => null);
193
+ }
194
+
195
+ const {data, type} = decodedBase64;
196
+ const imgParams: FileType = {
197
+ buffer: data,
198
+ description,
199
+ fileType: type,
200
+ id: photoId,
201
+ userId: sessionId
202
+ };
203
+
204
+ return addImage(context, imgParams)
205
+ .then((image: ImageType) => {
206
+ if(formatItemId && formatItemType) {
207
+ const imageEdge: FileEdgeType = {imgId: photoId, itemId: formatItemId, itemType: formatItemType};
208
+ return addImageEdge(context, imageEdge).then(() => image);
209
+ }
210
+ return image;
211
+ })
212
+ .catch((error) => logError({
213
+ action,
214
+ category: eventCategory,
215
+ label: 'image_save_error'
216
+ }, error, context).then(() => null));
217
+ } else if(url !== '') {
218
+ // Download image from the web
219
+ return httpGet(url)
220
+ .then((res: Response) => {
221
+ if(res.status !== 200) {
222
+ return logException({
223
+ action,
224
+ category: eventCategory,
225
+ label: 'fetch_image_url',
226
+ value: res.statusText
227
+ }, context).then(() => null);
228
+ }
229
+
230
+ contentType = res.headers.get('content-type');
231
+
232
+ return res;
233
+ })
234
+ .then((res) => res.buffer())
235
+ .then((buffer: Buffer) => {
236
+ const imgParams = {
237
+ buffer,
238
+ description,
239
+ id: photoId,
240
+ type: contentType,
241
+ userId: sessionId
242
+ };
243
+ return addImage(context, imgParams).then((image: ImageType) => {
244
+ if(formatItemId && formatItemType) {
245
+ const imageEdge: FileEdgeType = {imgId: id, itemId: formatItemId, itemType: formatItemType};
246
+ return addImageEdge(context, imageEdge).then(() => image);
247
+ }
248
+
249
+ return image;
250
+ });
251
+ })
252
+ .catch((error: Error) => logError({
253
+ action,
254
+ category: eventCategory,
255
+ label: 'fetch_error'
256
+ }, error, context).then(() => null));
257
+ } else if(id !== '') {
258
+ // Update metadata
259
+ const update: any = {
260
+ description,
261
+ modified: now
262
+ };
263
+ const aqlQry: AqlQuery = aql`UPDATE ${id} WITH ${update} IN images RETURN NEW`;
264
+
265
+ return useDb(database).query(aqlQry)
266
+ .then((cursor: ArrayCursor) => cursor.next())
267
+ .catch((error: Error) => logError({
268
+ action,
269
+ category: eventCategory,
270
+ label: 'db_error'
271
+ }, error, context).then(() => null));
272
+ }
273
+
274
+ return null;
275
+ };
276
+
277
+ export const addImage = (context: ApiContext, image: ImageType): Promise<ImageType> => {
278
+ const action: string = 'addImage';
279
+ const {database} = context;
280
+ const {userId, id, description, buffer, fileType} = image;
281
+ const now: number = Date.now();
282
+
283
+ return resizeSaveImage(userId, id, buffer, fileType)
284
+ .then((resizedImage: any) => {
285
+ const insert: ImageType = {
286
+ ...resizedImage,
287
+ _key: id,
288
+ added: now,
289
+ description,
290
+ fileType,
291
+ modified: now,
292
+ userId
293
+ };
294
+
295
+ const aqlQry: AqlQuery = aql`INSERT ${insert} IN images RETURN NEW`;
296
+
297
+ return useDb(database).query(aqlQry)
298
+ .then((cursor: ArrayCursor) => cursor.next())
299
+ .then(defaultObject)
300
+ .catch((error: Error) => logError({
301
+ action,
302
+ category: eventCategory,
303
+ isInternal: true,
304
+ label: 'db_error'
305
+ }, error, context).then(() => null));
306
+ })
307
+ .catch((error: Error) => logError({
308
+ action,
309
+ category: eventCategory,
310
+ isInternal: true,
311
+ label: 'image_resize'
312
+ }, error, context).then(() => null));
313
+ };
314
+
315
+ export const addImageEdge = (context: ApiContext, imageEdge: FileEdgeType): Promise<object> => {
316
+ const action: string = 'addImageEdge';
317
+ const {database, userId: sessionId} = context;
318
+ const {imgId, itemId, itemType} = imageEdge;
319
+ const now: number = Date.now();
320
+ const edgeCollection = useDb(database).edgeCollection('hasImage');
321
+ const edgeId: string = createHash(`hasImage-${imgId}-${itemId}-${sessionId}`);
322
+ const edge: any = {
323
+ _key: edgeId,
324
+ added: now
325
+ };
326
+
327
+ const formatItemType: string = parseChar(itemType).toLowerCase();
328
+ const formatItemId: string = parseId(itemId);
329
+ let itemDocId: string;
330
+ const formatImgId: string = parseId(imgId);
331
+ const imageDocId = `images/${formatImgId}`;
332
+
333
+ switch(formatItemType) {
334
+ case 'posts':
335
+ itemDocId = `posts/${formatItemId}`;
336
+ break;
337
+ default:
338
+ itemDocId = '';
339
+ break;
340
+ }
341
+
342
+ if(itemDocId) {
343
+ return edgeCollection.save(edge, imageDocId, itemDocId)
344
+ .then((fileEdge) => edgeCollection.edge(fileEdge))
345
+ .catch((error: Error) => logError({
346
+ action,
347
+ category: eventCategory,
348
+ isInternal: true,
349
+ label: 'db_error'
350
+ }, error, context).then(() => null));
351
+ }
352
+
353
+ return Promise.resolve({});
354
+ };
355
+
356
+ export const deleteImage = (context: ApiContext, imgId): Promise<ImageType> => {
357
+ const action: string = 'delete';
358
+ const {database, userId: sessionId} = context;
359
+ const formatImgId = parseId(imgId);
360
+
361
+ const aqlQry = aql`FOR i IN images
362
+ FILTER i._key == ${formatImgId} && i.userId == ${sessionId}
363
+ REMOVE i IN images
364
+ RETURN OLD`;
365
+
366
+ return useDb(database).query(aqlQry)
367
+ .then((cursor: ArrayCursor) => cursor.next())
368
+ .then((image: ImageType = {}) => {
369
+ if(!isEmpty(image)) {
370
+ const {_key: imageKey} = image;
371
+ const params: DeleteObjectsRequest = {
372
+ Bucket: null,
373
+ Delete: {
374
+ Objects: [
375
+ {Key: `users/${sessionId}/images/${imageKey}.jpg`},
376
+ {Key: `users/${sessionId}/thumbs/${imageKey}.jpg`}
377
+ ],
378
+ Quiet: true
379
+ }
380
+ };
381
+
382
+ return s3DeleteList(params).then(() => image);
383
+ }
384
+ return {};
385
+ })
386
+ .catch((error: Error) => logError({
387
+ action,
388
+ category: eventCategory,
389
+ label: 'db_error'
390
+ }, error, context).then(() => null));
391
+ };
392
+
393
+ // Images
394
+ export const getPathUserImages = (userID: string, photoId: string, type: string, dir: string = 'images'): string => {
395
+ let filename: string = photoId;
396
+
397
+ switch(type) {
398
+ case 'image/png':
399
+ filename = `${photoId}.png`;
400
+ break;
401
+ default:
402
+ filename = `${photoId}.jpg`;
403
+ break;
404
+ }
405
+
406
+ return `users/${userID}/${dir}/${filename}`;
407
+ };
408
+
409
+ export const getUrlImages = (data: ImageUrlData): string => {
410
+ const {appId, imgId, directory = 'images', imgType = 'profile', isThumb, type, typeId} = data;
411
+ const host: string = Config.get('environment') === 'production'
412
+ ? `https://box.${Config.get('app.url')}`
413
+ : `https://s3.amazonaws.com/dev.${Config.get('app.url')}`;
414
+ const suffix: string = isThumb ? '-th' : '';
415
+
416
+ if(imgId) {
417
+ switch(type) {
418
+ case 'app':
419
+ // https://box.reaktor.io/myApp/app/images/123.jpg
420
+ return `${host}/${appId}/${type}/${directory}/${imgId}${suffix}.jpg`;
421
+ case 'users':
422
+ // https://box.reaktor.io/myApp/users/demoUser/images/123.jpg
423
+ return `${host}/${appId}/${type}/${typeId}/${directory}/${imgId}${suffix}.jpg`;
424
+ }
425
+
426
+ if(imgType === 'profile') {
427
+ return `${host}/${appId}/defaults/${type}_bk${suffix}.jpg`;
428
+ }
429
+
430
+ return `${host}/${appId}/defaults/${type}_wh${suffix}.jpg`;
431
+ }
432
+
433
+ return '';
434
+ };
435
+
436
+ export const resizeSaveImage = (userId: string,
437
+ photoId: string,
438
+ buffer: Buffer,
439
+ type: string = 'image/jpeg'): Promise<ImageType> => {
440
+ const action: string = 'resizeSaveImage';
441
+ const imgW: number = Config.get('image.imgSize');
442
+ const imgQ: number = Config.get('image.imgQuality');
443
+ let photo: ImageType = {};
444
+ const format: string = (type.split('/'))[1];
445
+
446
+ return new Promise((resolve) => {
447
+ gm(buffer, 'img')
448
+ .setFormat(format)
449
+ .quality(imgQ)
450
+ .autoOrient()
451
+ .resize(imgW, imgW, '>')
452
+ .identify({bufferStream: true}, (error: Error, val: ImageIdentifyType = {}): any => {
453
+ if(error) {
454
+ logError({
455
+ action,
456
+ category: eventCategory,
457
+ label: 'image_save',
458
+ value: 'gm_image_identify'
459
+ }, error, {id: userId}).catch((error) => {
460
+ throw error;
461
+ });
462
+ } else {
463
+ const formatVals = lowerCaseKeys(val);
464
+ const {make: cameraMake, model: cameraModel, datetimeoriginal: taken}: ImageIdentifyType = formatVals;
465
+ photo = {
466
+ ...cloneDeep(photo),
467
+ make: cameraMake,
468
+ model: cameraModel,
469
+ taken: DateTime.fromMillis(taken).millisecond
470
+ };
471
+
472
+ // If no background color, get the mean color value
473
+ const stats = formatVals['channel statistics'];
474
+
475
+ if(stats) {
476
+ let {red, green, blue, mean} = stats;
477
+
478
+ if(red) {
479
+ mean = red['standard deviation'] || red.mean;
480
+ red = mean ? +((mean.split(' ')[0]).substring(0, 3)) : 0;
481
+ } else {
482
+ red = 0;
483
+ }
484
+
485
+ if(green) {
486
+ mean = green['standard deviation'] || green.mean;
487
+ green = mean ? +((mean.split(' ')[0]).substring(0, 3)) : 0;
488
+ } else {
489
+ green = 0;
490
+ }
491
+
492
+ if(blue) {
493
+ mean = blue['standard deviation'] || blue.mean;
494
+ blue = mean ? +((mean.split(' ')[0]).substring(0, 3)) : 0;
495
+ } else {
496
+ blue = 0;
497
+ }
498
+
499
+ const rgb = blue | (green << 8) | (red << 16);
500
+ const color = rgb.toString(16);
501
+ photo.color = color === '0' ? '000000' : color;
502
+ }
503
+ }
504
+ })
505
+ .stream((error: Error, stdout): any => {
506
+ if(error) {
507
+ logError({
508
+ action,
509
+ category: eventCategory,
510
+ isInternal: true,
511
+ label: 'image_save',
512
+ value: 'gm_image_steam'
513
+ }, error, {id: userId}).then(() => null);
514
+ } else {
515
+ let imageBuffer: Buffer = new Buffer('');
516
+
517
+ stdout.on('data', (data) => {
518
+ imageBuffer = Buffer.concat([imageBuffer, data]);
519
+ });
520
+
521
+ stdout.on('end', () => {
522
+ // Get file size
523
+ photo.fileSize = imageBuffer.length;
524
+
525
+ // Upload data
526
+ const imageObj: PutObjectRequest = {
527
+ ACL: 'public-read',
528
+ Body: imageBuffer,
529
+ Bucket: null,
530
+ ContentType: type,
531
+ Key: getPathUserImages(userId, photoId, type, 'images')
532
+ };
533
+
534
+ s3Put(imageObj)
535
+ .then(() => {
536
+ const thmW = Config.get('image.thmSize');
537
+ const thmQ = Config.get('image.thmQuality');
538
+
539
+ // Upload thumbnail
540
+ gm(imageBuffer, 'img')
541
+ .setFormat('jpeg')
542
+ .size((thumbError: Error, resizeVal) => {
543
+ if(!thumbError) {
544
+ // Get updated resize values
545
+ const {height, width} = resizeVal;
546
+ photo = {...cloneDeep(photo), height, width};
547
+ }
548
+ })
549
+ .gravity('Center')
550
+ .resize(thmW, thmW, '^')
551
+ .extent(thmW, thmW)
552
+ .quality(thmQ)
553
+ .stream((streamError: Error, thumbStdout): any => {
554
+ if(streamError) {
555
+ logError({
556
+ action,
557
+ category: eventCategory,
558
+ label: 'image_save',
559
+ value: 'gm_thumbnail_steam'
560
+ }, streamError, {id: userId})
561
+ .catch((error) => {
562
+ throw error;
563
+ });
564
+ } else {
565
+ let thumbBuffer: Buffer = new Buffer('');
566
+
567
+ thumbStdout.on('data', (data) => {
568
+ thumbBuffer = Buffer.concat([thumbBuffer, data]);
569
+ });
570
+
571
+ thumbStdout.on('end', () => {
572
+ // Upload data
573
+ const thumbObj: PutObjectRequest = {
574
+ ACL: 'public-read',
575
+ Body: thumbBuffer,
576
+ Bucket: null,
577
+ ContentType: type,
578
+ Key: getPathUserImages(userId, photoId, type, 'thumbs')
579
+ };
580
+
581
+ s3Put(thumbObj)
582
+ .then(() => {
583
+ resolve(photo);
584
+ })
585
+ .catch(() => logError({
586
+ action,
587
+ category: eventCategory,
588
+ label: 'image_save',
589
+ value: 's3_put_image'
590
+ }, streamError, {id: userId}).then(() => null));
591
+ });
592
+ }
593
+ });
594
+ })
595
+ .catch(() => logError({
596
+ action,
597
+ category: eventCategory,
598
+ isInternal: true,
599
+ label: 'image_save',
600
+ value: 's3_image_save'
601
+ }, error, {id: userId}).then(() => null));
602
+ });
603
+ }
604
+ });
605
+ });
606
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Copyright (c) 2019-Present, Nitrogen Labs, Inc.
3
+ * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.
4
+ */
5
+ export * from './conversations';
6
+ export * from './dynamodb';
7
+ export * from './email';
8
+ export * from './files';
9
+ export * from './groups';
10
+ export * from './images';
11
+ export * from './ios';
12
+ export * from './locations';
13
+ export * from './messages';
14
+ export * from './notifications';
15
+ export * from './payments';
16
+ export * from './posts';
17
+ export * from './reactions';
18
+ export * from './s3';
19
+ export * from './search';
20
+ export * from './sms';
21
+ export * from './subscription';
22
+ export * from './tags';
23
+ export * from './users';