@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/src/data/s3.ts ADDED
@@ -0,0 +1,117 @@
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, {S3} from 'aws-sdk';
6
+ import {
7
+ DeleteObjectOutput,
8
+ DeleteObjectRequest,
9
+ DeleteObjectsOutput,
10
+ DeleteObjectsRequest,
11
+ GetObjectOutput,
12
+ GetObjectRequest,
13
+ HeadObjectOutput,
14
+ HeadObjectRequest,
15
+ PutObjectOutput,
16
+ PutObjectRequest
17
+ } from 'aws-sdk/clients/s3';
18
+
19
+ import {Config} from '../config';
20
+
21
+ // AWS S3
22
+ // const eventCategory: string = 's3';
23
+
24
+ export const s3Get = (params: GetObjectRequest): Promise<GetObjectOutput> => {
25
+ return new Promise((resolve, reject) => {
26
+ aws.config.update(Config.get('aws'));
27
+ const s3: S3 = new S3();
28
+
29
+ if(!params.Bucket) {
30
+ params.Bucket = Config.get('aws.Bucket');
31
+ }
32
+
33
+ s3.getObject(params, (error: Error, output: GetObjectOutput) => {
34
+ if(error) {
35
+ return reject(error);
36
+ }
37
+
38
+ return resolve(output);
39
+ });
40
+ });
41
+ };
42
+
43
+ export const s3Head = (params: HeadObjectRequest): Promise<HeadObjectOutput> => {
44
+ return new Promise((resolve, reject) => {
45
+ aws.config.update(Config.get('aws'));
46
+ const s3: S3 = new S3();
47
+
48
+ if(!params.Bucket) {
49
+ params.Bucket = Config.get('aws.Bucket');
50
+ }
51
+
52
+ s3.headObject(params, (error: Error, output: HeadObjectOutput) => {
53
+ if(error) {
54
+ return reject(error);
55
+ }
56
+ return resolve(output);
57
+ });
58
+ });
59
+ };
60
+
61
+ export const s3Put = (params: PutObjectRequest): Promise<PutObjectOutput> => {
62
+ return new Promise((resolve, reject) => {
63
+ aws.config.update(Config.get('aws'));
64
+ const s3 = new aws.S3();
65
+
66
+ if(!params.Bucket) {
67
+ params.Bucket = Config.get('aws.Bucket');
68
+ }
69
+
70
+ if(!params.StorageClass) {
71
+ params.StorageClass = 'REDUCED_REDUNDANCY';
72
+ }
73
+
74
+ s3.putObject(params, (error: Error, output: PutObjectOutput) => {
75
+ if(error) {
76
+ return reject(error);
77
+ }
78
+ return resolve(output);
79
+ });
80
+ });
81
+ };
82
+
83
+ export const s3Delete = (params: DeleteObjectRequest): Promise<DeleteObjectOutput> => {
84
+ return new Promise((resolve, reject) => {
85
+ aws.config.update(Config.get('aws'));
86
+ const s3: S3 = new S3();
87
+
88
+ if(!params.Bucket) {
89
+ params.Bucket = Config.get('aws.Bucket');
90
+ }
91
+
92
+ s3.deleteObject(params, (error, output: DeleteObjectOutput) => {
93
+ if(error) {
94
+ return reject(error);
95
+ }
96
+ return resolve(output);
97
+ });
98
+ });
99
+ };
100
+
101
+ export const s3DeleteList = (params: DeleteObjectsRequest): Promise<DeleteObjectsOutput> => {
102
+ return new Promise((resolve, reject) => {
103
+ aws.config.update(Config.get('aws'));
104
+ const s3: S3 = new S3();
105
+
106
+ if(!params.Bucket) {
107
+ params.Bucket = Config.get('aws.Bucket');
108
+ }
109
+
110
+ s3.deleteObjects(params, (error: Error, output: DeleteObjectsOutput) => {
111
+ if(error) {
112
+ return reject(error);
113
+ }
114
+ return resolve(output);
115
+ });
116
+ });
117
+ };
@@ -0,0 +1,74 @@
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 {aql} from 'arangojs';
6
+ import {AqlQuery} from 'arangojs/lib/cjs/aql-query';
7
+ import {ArrayCursor} from 'arangojs/lib/cjs/cursor';
8
+ import flatten from 'lodash/flatten';
9
+ import uniqBy from 'lodash/uniqBy';
10
+
11
+ import {ArangoDBLimit} from '../types/arangodb';
12
+ import {ApiContext} from '../types/auth';
13
+ import {UserType} from '../types/users';
14
+ import {getLimit, logException, useDb} from '../utils';
15
+ import {getDisplayName} from './users';
16
+
17
+ const eventCategory: string = 'search';
18
+
19
+ export const getSearchList = (context: ApiContext, query: string, from: number, to: number): Promise<UserType[]> => {
20
+ const action: string = 'getList';
21
+ const {database} = context;
22
+ const isString: boolean = true;
23
+
24
+ if(isString) {
25
+ const fields: any[] = [
26
+ {collection: 'users', field: 'first'},
27
+ {collection: 'users', field: 'last'},
28
+ {collection: 'users', field: 'email'},
29
+ {collection: 'users', field: 'username'},
30
+ {collection: 'users', field: 'name'}
31
+ ];
32
+
33
+ return Promise.all(fields.map((obj) => {
34
+ const qry: string = `prefix:${query}`;
35
+ const userQry: AqlQuery = aql`FOR u IN FULLTEXT(${obj.collection}, ${obj.field}, ${qry}, 50) RETURN u`;
36
+ return useDb(database).query(userQry).then((cursor: ArrayCursor) => cursor.all());
37
+ }))
38
+ .then((results) => {
39
+ const list = uniqBy(flatten(results), '_key');
40
+
41
+ return list.map((item: UserType) => {
42
+ const {_id: itemId, name: itemName} = item;
43
+ const collectionIdList: string[] = itemId.split('/');
44
+ const collection: string = collectionIdList[0];
45
+ const name: string = collection === 'users' ? getDisplayName(item) : itemName;
46
+
47
+ return {...item, name};
48
+ });
49
+ });
50
+ }
51
+
52
+ const limit: ArangoDBLimit = getLimit(from, to);
53
+ const aqlQry: string = `FOR u IN users
54
+ FILTER u.phone == "${query}"
55
+ ${limit.aql}
56
+ RETURN u`;
57
+
58
+ return useDb(database).query(aqlQry)
59
+ .then((cursor: ArrayCursor) => cursor.all())
60
+ .then((list: UserType[] = []) => list.map((item = {}) => {
61
+ const {_id: itemId, name: itemName} = item;
62
+ const collectionIdList: string[] = itemId.split('/');
63
+ const collection: string = collectionIdList[0];
64
+ const name: string = collection === 'users' ? getDisplayName(item) : itemName;
65
+
66
+ return {...item, name};
67
+ }))
68
+ .catch((error: Error) => logException({
69
+ action,
70
+ category: eventCategory,
71
+ label: 'db_error',
72
+ value: error.message
73
+ }, context).then(() => null));
74
+ };
@@ -0,0 +1,60 @@
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 {parsePhone} from '@nlabs/utils';
6
+ import fs from 'fs';
7
+ import twilioSdk from 'twilio';
8
+
9
+ import {Config} from '../config';
10
+ import {SMSParamsType, TwilioOptionsType} from '../types/notifications';
11
+ import {appTemplate} from './email';
12
+
13
+ // const eventCategory: string = 'sms';
14
+
15
+ // Twilio
16
+ export const sendSms = (params: SMSParamsType): any => {
17
+ const {
18
+ app = {},
19
+ content,
20
+ user = {}
21
+ } = params;
22
+
23
+ const country = user.country || 'US';
24
+ const phone = parsePhone(user.phone, country);
25
+ const templateContent: string = appTemplate(app, content);
26
+
27
+ const twilio: any = twilioSdk(Config.get('twilio.sid'), Config.get('twilio.token'));
28
+ const cfg: TwilioOptionsType = {
29
+ body: templateContent,
30
+ from: Config.get('twilio.number'),
31
+ to: phone
32
+ };
33
+
34
+ return new Promise((resolve, reject) => {
35
+ twilio.sendMessage(cfg, (error, data) => {
36
+ if(error) {
37
+ reject(new Error(error.error_message));
38
+ } else {
39
+ resolve(data);
40
+ }
41
+ });
42
+ });
43
+ }
44
+
45
+ export const sendSmsTemplate = (templateName: string, smsParams: SMSParamsType): Promise<boolean> => {
46
+ const {app, user, vars} = smsParams;
47
+ try {
48
+ const data = fs.readFileSync(`./templates/sms/${templateName}.txt`, 'utf8');
49
+ const text = appTemplate(app, data, vars);
50
+ const params = {app, text, user};
51
+
52
+ return sendSms(params)
53
+ .then((res) => res.status === 'queued')
54
+ .catch((error: Error) => {
55
+ throw error;
56
+ });
57
+ } catch(error) {
58
+ return Promise.reject(error);
59
+ }
60
+ };
@@ -0,0 +1,228 @@
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, parseChar, parseId, parseNum, parseVarChar} 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 {DateTime} from 'luxon';
10
+ import * as stripe from 'stripe';
11
+
12
+ import {Config} from '../config';
13
+ import {ArangoDBLimit} from '../types/arangodb';
14
+ import {ApiContext} from '../types/auth';
15
+ import {PaymentInterval, PaymentPlan, PaymentSubscription} from '../types/payments';
16
+ import {getLimit, logError, logException, useDb} from '../utils';
17
+ import {getUser} from './users';
18
+
19
+ const eventCategory: string = 'subscription';
20
+
21
+ export const getPlanList = (context: ApiContext, from: number = 0, to: number = 30): Promise<PaymentPlan[]> => {
22
+ const action: string = 'getList';
23
+ const {database} = context;
24
+ const limit: ArangoDBLimit = getLimit(from, to);
25
+ const aqlQry: string = `FOR p IN plans
26
+ ${limit.aql}
27
+ SORT p.amount
28
+ RETURN p`;
29
+
30
+ return useDb(database).query(aqlQry)
31
+ .then((cursor: ArrayCursor) => cursor.all())
32
+ .then((list = []) => list)
33
+ .catch((error: Error) => logError({
34
+ action,
35
+ category: eventCategory,
36
+ label: 'db_error'
37
+ }, error, {}).then(() => null).catch((error) => Promise.reject(error)));
38
+ };
39
+
40
+ export const getSubscription = (context: ApiContext): Promise<PaymentSubscription> => {
41
+ const action: string = 'getItem';
42
+ const {database, userId: sessionId} = context;
43
+ const aqlQry: string = `FOR s IN subscriptions
44
+ FILTER s.userId == ${sessionId} && s.cancelled > 0
45
+ LET plan = FIRST(
46
+ FOR p IN plans
47
+ FILTER p._key == s.planId
48
+ )
49
+ LIMIT 1
50
+ RETURN MERGE(s, {plan: plan})`;
51
+
52
+ return useDb(database).query(aqlQry)
53
+ .then((cursor: ArrayCursor) => cursor.next())
54
+ .then((subscription = {}) => subscription)
55
+ .catch((error: Error) => logError({
56
+ action,
57
+ category: eventCategory,
58
+ label: 'db_error'
59
+ }, error, {}).then(() => null).catch((error) => Promise.reject(error)));
60
+ }
61
+
62
+ export const addPlan = (context: ApiContext, item: PaymentPlan): Promise<PaymentPlan> => {
63
+ const action: string = 'addPlan';
64
+ const {database, userId: sessionId, userType} = context;
65
+ const isAdmin: boolean = userType > 2;
66
+
67
+ if(!isAdmin) {
68
+ return logException({
69
+ action,
70
+ category: eventCategory,
71
+ label: 'unauthorized',
72
+ value: 'invalid_session'
73
+ }, context).then(() => null);
74
+ }
75
+
76
+ const now: number = Date.now();
77
+ const formatId: string = parseId(item.id);
78
+ const planId: string = formatId === '' ? createHash(`tag-${sessionId}`) : formatId;
79
+ const {amount, currency = 'USD', description, interval, intervalCount, name} = item;
80
+ const formatAmount: number = parseNum(amount);
81
+ const formatInterval: PaymentInterval = parseChar(interval, 5).toLowerCase() as PaymentInterval;
82
+ const formatIntervalCnt: number = parseNum(intervalCount, 5);
83
+ const formatName: string = parseVarChar(name, 32);
84
+ const formatDesc: string = parseVarChar(description, 32);
85
+
86
+ const insert: PaymentPlan = {
87
+ _key: planId,
88
+ added: now,
89
+ amount: formatAmount,
90
+ currency,
91
+ description: formatDesc,
92
+ interval: formatInterval,
93
+ intervalCount: formatIntervalCnt,
94
+ modified: now,
95
+ name: formatName
96
+ };
97
+ const aqlQry: AqlQuery = aql`INSERT ${insert} IN plans RETURN NEW`;
98
+
99
+ return useDb(database).query(aqlQry)
100
+ .then((cursor: ArrayCursor) => cursor.next())
101
+ .then((plan: PaymentPlan = {}) => {
102
+ // Stripe
103
+ const stripeClient = stripe(Config.get('stripe.token'));
104
+ return stripeClient.plans
105
+ .create({
106
+ amount: formatAmount * 100,
107
+ currency,
108
+ id: planId,
109
+ interval: formatInterval,
110
+ interval_count: formatIntervalCnt,
111
+ metadata: {
112
+ },
113
+ name: formatName,
114
+ statement_descriptor: formatDesc
115
+ })
116
+ .then(() => plan);
117
+ })
118
+ .catch((error: Error) => logError({
119
+ action,
120
+ category: eventCategory,
121
+ label: 'db_error'
122
+ }, error, {}).then(() => null).catch((error) => Promise.reject(error)));
123
+ };
124
+
125
+ export const addSubscription = (context: ApiContext, item): Promise<PaymentSubscription> => {
126
+ const action: string = 'addSubscription';
127
+ const {database, userId: sessionId, userType} = context;
128
+ const isAdmin: boolean = userType > 2;
129
+
130
+ if(!isAdmin) {
131
+ return logException({
132
+ action,
133
+ category: eventCategory,
134
+ label: 'unauthorized',
135
+ value: 'invalid_session'
136
+ }, context).then(() => null);
137
+ }
138
+
139
+ const now: number = Date.now();
140
+ const formatId: string = parseId(item.id);
141
+ const subscriptionId: string = formatId === '' ? createHash(`tag-${sessionId}`) : formatId;
142
+ const {planId, tax, trialEnd, trialPeriod} = item;
143
+ const formatPlanId: string = parseId(planId);
144
+ const formatTaxPercent: number = parseNum(tax) || 0;
145
+ const formatTrialPeriod: number = parseNum(tax) || 0;
146
+ let formatTrialEnd: number = trialEnd || Date.now();
147
+
148
+ if(formatTrialPeriod) {
149
+ formatTrialEnd = DateTime.local().plus({days: trialPeriod}).toMillis();
150
+ }
151
+
152
+ return getUser(context, sessionId)
153
+ .then((user) => {
154
+ const insert: PaymentSubscription = {
155
+ _key: subscriptionId,
156
+ added: now,
157
+ cancelDate: 0,
158
+ modified: now,
159
+ planId: formatPlanId,
160
+ tax: formatTaxPercent,
161
+ trialEnd: formatTrialEnd,
162
+ userId: sessionId
163
+ };
164
+ const aqlQry: AqlQuery = aql`INSERT ${insert} IN plans RETURN NEW`;
165
+
166
+ return useDb(database).query(aqlQry)
167
+ .then((cursor: ArrayCursor) => cursor.next())
168
+ .then((subscription: PaymentSubscription = {}) => {
169
+ // Stripe
170
+ const stripeClient = stripe(Config.get('stripe.token'));
171
+ return stripeClient.subscriptions
172
+ .create({
173
+ customer: user.stripeCustomerId,
174
+ items: [
175
+ {plan: formatPlanId}
176
+ ],
177
+ metadata: {
178
+ userId: sessionId
179
+ },
180
+ tax_percent: formatTaxPercent * 100,
181
+ trial_end: formatTrialEnd
182
+ })
183
+ .then(() => subscription);
184
+ })
185
+ .catch((error: Error) => logError({
186
+ action,
187
+ category: eventCategory,
188
+ label: 'db_error'
189
+ }, error, {}).then(() => null).catch((error) => Promise.reject(error)));
190
+ });
191
+ };
192
+
193
+ export const deleteSubscription = (context: ApiContext, endDate: number): Promise<boolean> => {
194
+ const action: string = 'deleteSubscription';
195
+ const {database} = context;
196
+ const now: number = Date.now();
197
+ const formatEndDate: number = parseNum(endDate) || now;
198
+
199
+ return getSubscription(context)
200
+ .then((subscription) => {
201
+ // Stripe
202
+ const stripeClient = stripe(Config.get('stripe.token'));
203
+ return stripeClient.subscriptions
204
+ .del(subscription.transactionId, {
205
+ subscription: formatEndDate
206
+ })
207
+ .then((stripeResponse) => {
208
+ // Make sure we cancelled on Stripe before updating the db
209
+ if(stripeResponse.cancelled || stripeResponse.cancel_at_period_end) {
210
+ const update: PaymentSubscription = {
211
+ cancelDate: formatEndDate,
212
+ modified: now,
213
+ planId: ''
214
+ };
215
+ const aqlQry: AqlQuery = aql`UPDATE s WITH ${update} IN subscriptions`;
216
+
217
+ return useDb(database).query(aqlQry)
218
+ .then(() => true)
219
+ .catch((error: Error) => logError({
220
+ action,
221
+ category: eventCategory,
222
+ label: 'db_error'
223
+ }, error, {}).then(() => null).catch((error) => Promise.reject(error)));
224
+ }
225
+ return false;
226
+ });
227
+ });
228
+ };