@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
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "@nlabs/reaktor",
3
+ "version": "0.1.0",
4
+ "description": "Reaktor",
5
+ "main": "./index.js",
6
+ "types": "./lib/index.d.js",
7
+ "keywords": [
8
+ "reaktor",
9
+ "nitrogenlabs",
10
+ "react",
11
+ "arkhamjs",
12
+ "graphql"
13
+ ],
14
+ "author": {
15
+ "name": "Giraldo Rosales",
16
+ "email": "giraldo@nitrogenlabs.com",
17
+ "url": "http://nitrogenlabs.com"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/nitrogenlabs/reaktor-api"
22
+ },
23
+ "homepage": "https://github.com/nitrogenlabs/reaktor-api",
24
+ "bugs": {
25
+ "url": "https://github.com/nitrogenlabs/reaktor-api/issues"
26
+ },
27
+ "scripts": {
28
+ "build": "lex compile -r",
29
+ "clean": "lex clean",
30
+ "lint": "eslint ./src --ext .ts,.tsx",
31
+ "pretest": "stage=test npm run lint",
32
+ "test": "stage=test jest",
33
+ "update": "lex update -i"
34
+ },
35
+ "dependencies": {
36
+ "@nlabs/arkhamjs": "^3.11.11",
37
+ "@nlabs/rip-hunter": "^2.0.0",
38
+ "@nlabs/utils": "^1.2.0",
39
+ "apn": "^2.2.0",
40
+ "arangojs": "^6.0.0",
41
+ "aws-sdk": "^2.411.0",
42
+ "gm": "^1.23.1",
43
+ "google-libphonenumber": "^3.0.10",
44
+ "googleapis": "^37.2.0",
45
+ "graphql": "^14.0.2",
46
+ "graphql-errors": "^2.1.0",
47
+ "graphql-fields": "^2.0.1",
48
+ "hiredis": "^0.5.0",
49
+ "lodash": "^4.17.4",
50
+ "luxon": "^1.3.3",
51
+ "node-yelp": "0.0.3",
52
+ "numeral": "^2.0.6",
53
+ "redis": "^2.8.0",
54
+ "request-promise": "^4.2.2",
55
+ "stripe": "^6.12.1",
56
+ "to": "^0.2.9",
57
+ "twilio": "^3.11.0",
58
+ "typed-promisify": "^0.4.0",
59
+ "universal-analytics": "^0.4.16",
60
+ "update": "^0.7.4"
61
+ },
62
+ "devDependencies": {
63
+ "@types/graphql": "^14.0.1",
64
+ "@types/history": "^4.6.2",
65
+ "@types/jest": "^24.0.9",
66
+ "@types/luxon": "^1.11.0",
67
+ "@types/node": "^11.9.4",
68
+ "@types/twilio": "^0.0.10",
69
+ "eslint": "^5.13.0",
70
+ "eslint-config-styleguidejs": "^1.0.3",
71
+ "graphql-tools": "^4.0.0",
72
+ "source-map-loader": "^0.2.4",
73
+ "typescript": "^3.0.3"
74
+ }
75
+ }
package/src/config.ts ADDED
@@ -0,0 +1,121 @@
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
+ import get from 'lodash/get';
6
+ import merge from 'lodash/merge';
7
+
8
+ export interface AppConfig {
9
+ readonly app?: object;
10
+ readonly arangodb?: object;
11
+ readonly aws?: object;
12
+ readonly giphy?: object;
13
+ readonly google?: object;
14
+ readonly image?: object;
15
+ readonly redis?: object;
16
+ readonly stripe?: object;
17
+ readonly twilio?: object;
18
+ }
19
+
20
+ export interface EnvConfig {
21
+ readonly default: AppConfig;
22
+ readonly development: AppConfig;
23
+ readonly production: AppConfig;
24
+ readonly test: AppConfig;
25
+ }
26
+
27
+ const appPackage: any = require('../package.json');
28
+
29
+ const {arangodbDatabase, arangodbPassword, arangodbPort = '8529', arangodbUrl, arangodbUsername} = process.env;
30
+
31
+ export class Config {
32
+ static values: EnvConfig = {
33
+ default: {
34
+ app: {
35
+ name: 'reaktor',
36
+ url: 'reaktor.io',
37
+ version: appPackage.version
38
+ },
39
+ arangodb: {
40
+ apiUrl: `http://${arangodbUsername}:${arangodbPassword}@${arangodbUrl}:${arangodbPort}`,
41
+ database: arangodbDatabase,
42
+ dump: '/Applications/ArangoDB-CLI.app/Contents/MacOS/arangodump',
43
+ password: arangodbPassword,
44
+ port: arangodbPort,
45
+ restore: '/Applications/ArangoDB-CLI.app/Contents/MacOS/arangorestore',
46
+ url: arangodbUrl,
47
+ username: arangodbUsername
48
+ },
49
+ aws: {
50
+ Bucket: 'dev.reaktor.io',
51
+ accessKeyId: 'AKIAJIYDT3EA2ZFMTJNA',
52
+ maxRetries: 3,
53
+ region: 'us-east-1',
54
+ secretAccessKey: 'bUssQRtJTZC7geF9RDH5KCn8CalVnf2VT34yqS+9',
55
+ signatureVersion: 'v4'
56
+ },
57
+ giphy: {
58
+ key: 'dc6zaTOxFJmzC'
59
+ },
60
+ google: {
61
+ analytics: {
62
+ accountId: '78158717',
63
+ trackingId: 'UA-78158717-1'
64
+ },
65
+ geocode: {
66
+ key: 'AIzaSyBwjmpBjWhiJoBCZiYzRurM9m4WnCxslv0',
67
+ url: 'https://maps.googleapis.com/maps/api/geocode/json'
68
+ },
69
+ key: 'AIzaSyC3_f2Us3y0qjbyMJ-4t5ObtCzfq_FHIgQ'
70
+ },
71
+ image: {
72
+ imgQuality: 90,
73
+ imgSize: 1500,
74
+ thmQuality: 80,
75
+ thmSize: 150
76
+ },
77
+ redis: {
78
+ host: '127.0.0.1',
79
+ port: 6379
80
+ },
81
+ stripe: {
82
+ token: 'sk_test_LS956be57YUXTB5a4sLGb5BQ'
83
+ },
84
+ twilio: {
85
+ number: '+15005550006',
86
+ sid: 'AC6bd7f513cbed2e5134c650be06cc732e',
87
+ token: '520ae739e761bab759b147f1ad28278f'
88
+ }
89
+ },
90
+ development: {
91
+ },
92
+ production: {
93
+ arangodb: {
94
+ dump: '/usr/bin/arangodump',
95
+ restore: '/usr/bin/arangorestore',
96
+ },
97
+ aws: {
98
+ Bucket: 'box.reaktor.io'
99
+ },
100
+ redis: {
101
+ host: 'reaktor-redis.ehcvnt.0001.use1.cache.amazonaws.com'
102
+ },
103
+ stripe: {
104
+ token: 'sk_live_bElZl8RLhMCJy33KG4lyLD4o'
105
+ },
106
+ twilio: {
107
+ number: '+18554165227',
108
+ sid: 'AC90b0528a911e652d643329a4d7b4d2c7',
109
+ token: 'a7941d173bd4846d0966a186e9865b72'
110
+ }
111
+ },
112
+ test: {
113
+ }
114
+ };
115
+
116
+ static get(path: string | string[]): any {
117
+ const environment: string = process.env.stage || 'development';
118
+ const configValues: object = merge(this.values.default, this.values[environment], {environment});
119
+ return get(configValues, path);
120
+ }
121
+ }
@@ -0,0 +1,183 @@
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
+ import {createHash, parseId} from '@nlabs/utils';
6
+ import {aql} from 'arangojs';
7
+ import {AqlQuery} from 'arangojs/lib/cjs/aql-query';
8
+ import {ArrayCursor} from 'arangojs/lib/cjs/cursor';
9
+ import cloneDeep from 'lodash/cloneDeep';
10
+ import identity from 'lodash/identity';
11
+ import isEmpty from 'lodash/isEmpty';
12
+ import pick from 'lodash/pick';
13
+ import {ApiContext} from 'types/auth';
14
+
15
+ import {ArangoDBLimit, ConversationType, ImageUrlData, UserType} from '../types';
16
+ import {getLimit, useDb} from '../utils';
17
+ import {logError} from '../utils/analytics';
18
+ import {getUrlImages} from './images';
19
+ import {getDisplayName} from './users';
20
+
21
+ const eventCategory: string = 'conversations';
22
+
23
+ export const getConversationList = (context: ApiContext, from: number, to: number): Promise<ConversationType[]> => {
24
+ const action: string = 'getList';
25
+ const {database, userId: sessionId} = context;
26
+ const limit: ArangoDBLimit = getLimit(from, to);
27
+ const aqlQry: string = `FOR c IN conversations
28
+ FILTER POSITION(c.users, "${sessionId}", false)
29
+ LET u = (
30
+ FOR g IN c.users
31
+ FOR u IN users
32
+ FILTER g == u._key
33
+ RETURN {_key:u._key, first:u.first, last:u.last, name:u.name, email:u.email, username:u.username, photo:u.photo }
34
+ )
35
+ FILTER LENGTH(u) > 1
36
+ ${limit.aql}
37
+ RETURN {_key:c._key, name: c.name, direct: c.direct, users: u}`;
38
+
39
+ return useDb(database).query(aqlQry)
40
+ .then((cursor: ArrayCursor) => cursor.all())
41
+ .catch((error: Error) => logError({
42
+ action,
43
+ category: eventCategory,
44
+ label: 'db_error'
45
+ }, error, context).then(() => null));
46
+ };
47
+
48
+ export const getDirectConversation = (context: ApiContext, userId: string): Promise<ConversationType> => {
49
+ const action: string = 'getDirect';
50
+ const {appId: sessionApp, database, userId: sessionId} = context;
51
+ const formatUserId: string = parseId(userId);
52
+
53
+ const aqlQry: AqlQuery = aql`FOR c IN conversations
54
+ FILTER LENGTH(c.users) == 2 &&
55
+ POSITION(c.users, ${sessionId}, false) &&
56
+ POSITION(c.users, ${formatUserId}, false)
57
+ LET u = (
58
+ FOR g IN c.users
59
+ FOR user IN users
60
+ FILTER user._key == g
61
+ RETURN user
62
+ )
63
+ LIMIT 1
64
+ RETURN MERGE(c, {users: u})`;
65
+
66
+ return useDb(database).query(aqlQry)
67
+ .then((cursor: ArrayCursor) => cursor.next())
68
+ .then((conversation: ConversationType = {}) => {
69
+ const {users = []} = conversation;
70
+
71
+ if(users.length) {
72
+ conversation.users = users.map((user: UserType) => {
73
+ const {_key: userKey, imgId} = user;
74
+ const thumbUrlData: ImageUrlData = {
75
+ appId: sessionApp,
76
+ directory: 'images',
77
+ imgId,
78
+ isThumb: true,
79
+ type: 'users',
80
+ typeId: userKey
81
+ };
82
+ return {
83
+ ...user,
84
+ name: getDisplayName(user),
85
+ thumb: getUrlImages(thumbUrlData)
86
+ };
87
+ });
88
+ }
89
+
90
+ return conversation;
91
+ })
92
+ .catch((error: Error) => logError({
93
+ action,
94
+ category: eventCategory,
95
+ label: 'db_error'
96
+ }, error, context).then(() => null));
97
+ };
98
+
99
+ export const getConversation = (context: ApiContext, convoId: string): Promise<ConversationType> => {
100
+ const action: string = 'getItem';
101
+ const {appId: sessionApp, database, userId: sessionId} = context;
102
+ const formatConvoId: string = parseId(convoId);
103
+
104
+ const aqlQry: AqlQuery = aql`FOR c IN conversations
105
+ FILTER ${formatConvoId} == c._key && IS_LIST(c.users) && POSITION(c.users, ${sessionId}, false)
106
+ LET u = (
107
+ FOR g IN c.users
108
+ FOR u IN users
109
+ FILTER g == u._key
110
+ RETURN {_key:u._key, first:u.first, last:u.last, name:u.name, email:u.email, username:u.username, photo:u.photo }
111
+ )
112
+ FILTER LENGTH(u) > 1
113
+ LIMIT 1
114
+ RETURN MERGE(c, {users: u})`;
115
+
116
+ return useDb(database).query(aqlQry)
117
+ .then((cursor: ArrayCursor) => cursor.next())
118
+ .then((conversation: ConversationType = {}) => {
119
+ if(!isEmpty(conversation)) {
120
+ conversation.users = conversation.users.map((user: UserType) => {
121
+ const {_key: userKey, imgId} = user;
122
+ const thumbUrlData: ImageUrlData = {
123
+ appId: sessionApp,
124
+ directory: 'images',
125
+ imgId,
126
+ isThumb: true,
127
+ type: 'users',
128
+ typeId: userKey
129
+ };
130
+ return {
131
+ ...user,
132
+ name: getDisplayName(user),
133
+ thumb: getUrlImages(thumbUrlData)
134
+ };
135
+ });
136
+
137
+ return conversation;
138
+ }
139
+ return {};
140
+ })
141
+ .catch((error: Error) => logError({
142
+ action,
143
+ category: eventCategory,
144
+ label: 'db_error'
145
+ }, error, context).then(() => null));
146
+ };
147
+
148
+ export const updateConversation = (context: ApiContext, item): Promise<ConversationType> => {
149
+ const action: string = 'update';
150
+ const {database, userId: sessionId} = context;
151
+ const {direct, id, name, users} = pick(item, identity);
152
+ const rcpt: UserType[] = users || [];
153
+ const formatId: string = item.id || createHash(`convo-${sessionId}-${rcpt.join('')}`);
154
+ const now: number = Date.now();
155
+ const update: any = {
156
+ // Direct message
157
+ direct: !!direct ? direct : undefined,
158
+ modified: now,
159
+ name: !!name ? name : undefined,
160
+ // Users
161
+ users: !!users ? users : undefined
162
+ };
163
+
164
+ const insert: any = {
165
+ ...cloneDeep(update),
166
+ _key: formatId,
167
+ added: now
168
+ };
169
+
170
+ const aqlQry: AqlQuery = aql`UPSERT {_key: ${id}}
171
+ INSERT ${insert}
172
+ UPDATE ${update}
173
+ IN conversations RETURN NEW`;
174
+
175
+ return useDb(database).query(aqlQry)
176
+ .then((cursor: ArrayCursor) => cursor.next())
177
+ .then((conversation: ConversationType = {}) => conversation)
178
+ .catch((error: Error) => logError({
179
+ action,
180
+ category: eventCategory,
181
+ label: 'db_error'
182
+ }, error, context).then(() => null));
183
+ };
@@ -0,0 +1,157 @@
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
+ import aws, {DynamoDB} from 'aws-sdk';
6
+ import {
7
+ BatchWriteItemInput,
8
+ BatchWriteItemOutput,
9
+ DeleteItemInput,
10
+ DeleteItemOutput,
11
+ GetItemInput,
12
+ GetItemOutput,
13
+ PutItemInput,
14
+ PutItemOutput,
15
+ QueryInput,
16
+ QueryOutput,
17
+ UpdateItemInput,
18
+ UpdateItemOutput
19
+ } from 'aws-sdk/clients/dynamodb';
20
+
21
+ import {Config} from '../config';
22
+
23
+ // const eventCategory: string = 'dynamodb';
24
+
25
+ export const dynamoGet = (params: GetItemInput): Promise<GetItemOutput> => {
26
+ return new Promise((resolve, reject) => {
27
+ aws.config.update(Config.get('aws'));
28
+ const dynamodb: DynamoDB = new DynamoDB();
29
+ params.ConsistentRead = false;
30
+
31
+ dynamodb.getItem(params, (error: Error, results: GetItemOutput) => {
32
+ if(error) {
33
+ return reject(error);
34
+ }
35
+
36
+ return resolve(results);
37
+ });
38
+ });
39
+ };
40
+
41
+ export const dynamoGetList = (params: QueryInput, index): Promise<GetItemOutput[]> => {
42
+ return new Promise((resolve, reject) => {
43
+ aws.config.update(Config.get('aws'));
44
+ const dynamodb: DynamoDB = new DynamoDB();
45
+ const {ConsistentRead: consistentRead} = params;
46
+ const updatedParams = {...params};
47
+
48
+ if(index) {
49
+ updatedParams.ExclusiveStartKey = index;
50
+ }
51
+
52
+ if(!consistentRead) {
53
+ updatedParams.ConsistentRead = false;
54
+ }
55
+
56
+ dynamodb.query(updatedParams, (error: Error, output: QueryOutput) => {
57
+ if(error) {
58
+ return reject(error);
59
+ }
60
+ // Save list of items
61
+ const list: any[] = parseDynamo(output);
62
+ const {LastEvaluatedKey: lastKey} = output;
63
+
64
+ if(lastKey !== undefined) {
65
+ // Save last index before looping
66
+ return dynamoGetList(params, lastKey);
67
+ }
68
+
69
+ // Query complete, return final list
70
+ return resolve(list);
71
+ });
72
+ });
73
+ };
74
+
75
+ export const dynamoPut = (params: PutItemInput): Promise<PutItemOutput> => {
76
+ return new Promise((resolve, reject) => {
77
+ aws.config.update(Config.get('aws'));
78
+ const dynamodb: DynamoDB = new DynamoDB();
79
+
80
+ dynamodb.putItem(params, (error, output: PutItemOutput) => {
81
+ if(error) {
82
+ return reject(error);
83
+ }
84
+ return resolve(output);
85
+ });
86
+ });
87
+ };
88
+
89
+ export const dynamoUpdate = (params: UpdateItemInput): Promise<UpdateItemOutput> => {
90
+ return new Promise((resolve, reject) => {
91
+ aws.config.update(Config.get('aws'));
92
+ const dynamodb: DynamoDB = new DynamoDB();
93
+
94
+ dynamodb.updateItem(params, (error: Error, output: UpdateItemOutput) => {
95
+ if(error) {
96
+ return reject(error);
97
+ }
98
+ return resolve(output);
99
+ });
100
+ });
101
+ };
102
+
103
+ export const dynamoPutList = (params: BatchWriteItemInput): Promise<BatchWriteItemOutput> => {
104
+ return new Promise((resolve, reject) => {
105
+ aws.config.update(Config.get('aws'));
106
+ const dynamodb: DynamoDB = new DynamoDB();
107
+
108
+ dynamodb.batchWriteItem(params, (error: Error, output: BatchWriteItemOutput) => {
109
+ if(error) {
110
+ return reject(error);
111
+ }
112
+ return resolve(output);
113
+ });
114
+ });
115
+ };
116
+
117
+ export const dynamoDel = (params: DeleteItemInput): Promise<DeleteItemOutput> => {
118
+ return new Promise((resolve, reject) => {
119
+ aws.config.update(Config.get('aws'));
120
+ const dynamodb: DynamoDB = new DynamoDB();
121
+
122
+ dynamodb.deleteItem(params, (error, output: DeleteItemOutput) => {
123
+ if(error) {
124
+ return reject(error);
125
+ }
126
+ return resolve(output);
127
+ });
128
+ });
129
+ };
130
+
131
+ export const parseDynamo = (results: QueryOutput): any[] => {
132
+ const {Items: items} = results;
133
+ const data: any[] = [];
134
+
135
+ for(let idx: number = 0, len = items.length; idx < len; idx++) {
136
+ const tmp = items[idx];
137
+ const obj = {};
138
+ let key;
139
+
140
+ for(key in tmp) {
141
+ if(tmp.hasOwnProperty(key)) {
142
+ if(tmp[key].S) {
143
+ obj[key] = tmp[key].S.toString();
144
+ } else if(tmp[key].N) {
145
+ obj[key] = parseFloat(tmp[key].N);
146
+ } else {
147
+ obj[key] = tmp[key].B;
148
+ }
149
+ }
150
+ }
151
+
152
+ data.push(obj);
153
+ }
154
+
155
+ return data;
156
+ };
157
+
@@ -0,0 +1,164 @@
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
+ import aws, {SES} from 'aws-sdk';
6
+ import {GetObjectRequest} from 'aws-sdk/clients/s3';
7
+ import {SendEmailRequest, SendEmailResponse} from 'aws-sdk/clients/ses';
8
+ import fs from 'fs';
9
+ import isEmpty from 'lodash/isEmpty';
10
+
11
+ import {Config} from '../config';
12
+ import {AppType} from '../types/apps';
13
+ import {EmailType} from '../types/email';
14
+ import {s3Get} from './s3';
15
+
16
+ // const eventCategory: string = 'email';
17
+
18
+ export const appTemplate = (app: AppType, message: string = '', vars = {}): string => {
19
+ let updatedMessage: string = '';
20
+
21
+ if(!isEmpty(app)) {
22
+ updatedMessage = message.replace(/\[appName\]/g, app.name)
23
+ .replace(/\[appUrl\]/g, app.url)
24
+ .replace(/\[appEmail\]/g, app.email)
25
+ .replace(/\[appId\]/g, app.id);
26
+ }
27
+
28
+ Object.keys(vars).forEach((key: string) => {
29
+ if(vars[key]) {
30
+ updatedMessage = message.replace(new RegExp(`\\[${key}\\]`, 'g'), vars[key]);
31
+ }
32
+ });
33
+
34
+ return updatedMessage;
35
+ };
36
+
37
+ export const sendEmail = (params: EmailType): Promise<boolean> => {
38
+ const {
39
+ app = {},
40
+ html,
41
+ subject,
42
+ subTitle = '',
43
+ text,
44
+ title = '',
45
+ user = {}
46
+ } = params;
47
+
48
+ const to = [user.email];
49
+ let promise;
50
+
51
+ const formatSubject: string = appTemplate(app, subject);
52
+ const formatSubTitle: string = appTemplate(app, subTitle);
53
+ const formatTitle: string = appTemplate(app, title);
54
+ const formatText: string = appTemplate(app, text);
55
+ const formatHtml: string = appTemplate(app, html);
56
+ const {_key: appId, hasCustomEmail, urlFacebook, urlTwitter} = app;
57
+
58
+ const parseTemplate = (body: string) => {
59
+ const templateBody: string = appTemplate(app, body || '');
60
+
61
+ // Links
62
+ let links: string = '';
63
+ const spacer = '<td width="5"><img width="1" height="1" border="0" title="" alt="" ' +
64
+ 'src="https://box.reaktor.io/images/email/spacer.gif"/></td>';
65
+
66
+ if(urlFacebook) {
67
+ links = `<td width="30"><a href="${urlFacebook}"><img width="30" height="30" border="0" ` +
68
+ 'title="Facebook" alt="Facebook" src="https://box.reaktor.io/images/email/icon-facebook.png"/></a></td>';
69
+ }
70
+
71
+ if(urlTwitter) {
72
+ if(urlFacebook) {
73
+ links += spacer;
74
+ }
75
+
76
+ links += `<td width="30"><a href="${urlTwitter}"><img width="30" height="30" border="0" ` +
77
+ 'title="Twitter" alt="Twitter" src="https://box.reaktor.io/images/email/icon-twitter.png"/></a></td>';
78
+ }
79
+
80
+ if(!links.length) {
81
+ links += spacer;
82
+ }
83
+
84
+ return templateBody.replace(/\[title\]/g, formatTitle)
85
+ .replace(/\[subTitle\]/g, formatSubTitle)
86
+ .replace(/\[subject\]/g, formatSubject)
87
+ .replace(/\[message\]/g, formatHtml)
88
+ .replace(/\[links\]/g, links);
89
+ };
90
+
91
+ if(hasCustomEmail) {
92
+ const s3Params: GetObjectRequest = {Bucket: null, Key: `apps/${appId}/templates/email.html`};
93
+ promise = s3Get(s3Params).then((results) => parseTemplate(results.Body.toString()));
94
+ } else {
95
+ promise = new Promise((resolve, reject) => {
96
+ fs.readFile('./templates/email/layout.html', 'utf8', (err, data) => {
97
+ if(err) {
98
+ return reject(err);
99
+ }
100
+
101
+ return resolve(parseTemplate(data));
102
+ });
103
+ });
104
+ }
105
+
106
+ return promise.then((parsedHtml: string) => {
107
+ const emailParams: SendEmailRequest = {
108
+ Destination: {ToAddresses: to},
109
+ Message: {
110
+ Body: {
111
+ Html: {Data: parsedHtml},
112
+ Text: {Data: formatText}
113
+ },
114
+ Subject: {Data: formatSubject}
115
+ },
116
+ Source: `"${app.supportName}" <noreply@${Config.get('app.url')}>`
117
+ };
118
+
119
+ return sesSend(emailParams);
120
+ });
121
+ };
122
+
123
+ export const sendTemplate = (templateName: string, emailParams: EmailType): Promise<boolean> => {
124
+ const {app, user, subject, subTitle, title, vars}: EmailType = emailParams;
125
+
126
+ try {
127
+ const html: string = fs.readFileSync(`./templates/email/${templateName}.html`, 'utf8');
128
+ const templateHtml: string = appTemplate(app, html, vars);
129
+ const text: string = fs.readFileSync(`./templates/sms/${templateName}.txt`, 'utf8');
130
+ const templateText: string = appTemplate(app, text, vars);
131
+
132
+ const params = {
133
+ app,
134
+ html: templateHtml,
135
+ subTitle,
136
+ subject,
137
+ text: templateText,
138
+ title,
139
+ user
140
+ };
141
+
142
+ return sendEmail(params)
143
+ .then(() => true)
144
+ .catch(() => false);
145
+ } catch(error) {
146
+ return Promise.reject(error);
147
+ }
148
+ };
149
+
150
+ // AWS::SES
151
+ export const sesSend = (params: SendEmailRequest): Promise<SendEmailResponse> => {
152
+ return new Promise((resolve, reject) => {
153
+ aws.config.update(Config.get('aws'));
154
+ const ses: SES = new SES();
155
+
156
+ ses.sendEmail(params, (error: Error, response: SendEmailResponse) => {
157
+ if(error) {
158
+ return reject(error);
159
+ }
160
+
161
+ return resolve(response);
162
+ });
163
+ });
164
+ };