@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.
- package/.vscode/extensions.json +15 -0
- package/.vscode/settings.json +82 -0
- package/README.md +211 -0
- package/index.d.ts +1 -0
- package/index.js +5 -0
- package/lex.config.js +4 -0
- package/lib/config.d.ts +21 -0
- package/lib/config.js +127 -0
- package/lib/data/conversations.d.ts +6 -0
- package/lib/data/conversations.js +201 -0
- package/lib/data/dynamodb.d.ts +8 -0
- package/lib/data/dynamodb.js +139 -0
- package/lib/data/email.d.ts +7 -0
- package/lib/data/email.js +164 -0
- package/lib/data/files.d.ts +16 -0
- package/lib/data/files.js +407 -0
- package/lib/data/groups.d.ts +13 -0
- package/lib/data/groups.js +354 -0
- package/lib/data/images.d.ts +12 -0
- package/lib/data/images.js +668 -0
- package/lib/data/index.d.ts +19 -0
- package/lib/data/index.js +24 -0
- package/lib/data/ios.d.ts +6 -0
- package/lib/data/ios.js +302 -0
- package/lib/data/locations.d.ts +3 -0
- package/lib/data/locations.js +132 -0
- package/lib/data/messages.d.ts +9 -0
- package/lib/data/messages.js +248 -0
- package/lib/data/notifications.d.ts +5 -0
- package/lib/data/notifications.js +42 -0
- package/lib/data/payments.d.ts +11 -0
- package/lib/data/payments.js +748 -0
- package/lib/data/posts.d.ts +14 -0
- package/lib/data/posts.js +458 -0
- package/lib/data/reactions.d.ts +6 -0
- package/lib/data/reactions.js +218 -0
- package/lib/data/s3.d.ts +6 -0
- package/lib/data/s3.js +103 -0
- package/lib/data/search.d.ts +3 -0
- package/lib/data/search.js +98 -0
- package/lib/data/sms.d.ts +3 -0
- package/lib/data/sms.js +59 -0
- package/lib/data/subscription.d.ts +7 -0
- package/lib/data/subscription.js +284 -0
- package/lib/data/tags.d.ts +14 -0
- package/lib/data/tags.js +304 -0
- package/lib/data/users.d.ts +12 -0
- package/lib/data/users.js +312 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +8 -0
- package/lib/types/apps.d.ts +44 -0
- package/lib/types/apps.js +2 -0
- package/lib/types/arangodb.d.ts +17 -0
- package/lib/types/arangodb.js +2 -0
- package/lib/types/auth.d.ts +9 -0
- package/lib/types/auth.js +2 -0
- package/lib/types/conversations.d.ts +6 -0
- package/lib/types/conversations.js +2 -0
- package/lib/types/email.d.ts +12 -0
- package/lib/types/email.js +2 -0
- package/lib/types/files.d.ts +28 -0
- package/lib/types/files.js +2 -0
- package/lib/types/google.d.ts +27 -0
- package/lib/types/google.js +2 -0
- package/lib/types/groups.d.ts +22 -0
- package/lib/types/groups.js +2 -0
- package/lib/types/images.d.ts +25 -0
- package/lib/types/images.js +2 -0
- package/lib/types/index.d.ts +17 -0
- package/lib/types/index.js +22 -0
- package/lib/types/locations.d.ts +21 -0
- package/lib/types/locations.js +2 -0
- package/lib/types/messages.d.ts +12 -0
- package/lib/types/messages.js +2 -0
- package/lib/types/notifications.d.ts +19 -0
- package/lib/types/notifications.js +2 -0
- package/lib/types/payments.d.ts +119 -0
- package/lib/types/payments.js +2 -0
- package/lib/types/posts.d.ts +20 -0
- package/lib/types/posts.js +2 -0
- package/lib/types/reactions.d.ts +4 -0
- package/lib/types/reactions.js +2 -0
- package/lib/types/tags.d.ts +10 -0
- package/lib/types/tags.js +2 -0
- package/lib/types/users.d.ts +78 -0
- package/lib/types/users.js +2 -0
- package/lib/utils/analytics.d.ts +3 -0
- package/lib/utils/analytics.js +47 -0
- package/lib/utils/arangodb.d.ts +9 -0
- package/lib/utils/arangodb.js +98 -0
- package/lib/utils/auth.d.ts +2 -0
- package/lib/utils/auth.js +43 -0
- package/lib/utils/index.d.ts +5 -0
- package/lib/utils/index.js +10 -0
- package/lib/utils/objects.d.ts +3 -0
- package/lib/utils/objects.js +34 -0
- package/lib/utils/redis.d.ts +1 -0
- package/lib/utils/redis.js +15 -0
- package/package.json +75 -0
- package/src/config.ts +121 -0
- package/src/data/conversations.ts +183 -0
- package/src/data/dynamodb.ts +157 -0
- package/src/data/email.ts +164 -0
- package/src/data/files.ts +352 -0
- package/src/data/groups.ts +308 -0
- package/src/data/images.ts +606 -0
- package/src/data/index.ts +23 -0
- package/src/data/ios.ts +249 -0
- package/src/data/locations.ts +114 -0
- package/src/data/messages.ts +237 -0
- package/src/data/notifications.ts +48 -0
- package/src/data/payments.ts +675 -0
- package/src/data/posts.ts +508 -0
- package/src/data/reactions.ts +186 -0
- package/src/data/s3.ts +117 -0
- package/src/data/search.ts +74 -0
- package/src/data/sms.ts +60 -0
- package/src/data/subscription.ts +228 -0
- package/src/data/tags.ts +230 -0
- package/src/data/users.ts +256 -0
- package/src/index.ts +7 -0
- package/src/types/apps.ts +57 -0
- package/src/types/arangodb.ts +23 -0
- package/src/types/auth.ts +19 -0
- package/src/types/conversations.ts +11 -0
- package/src/types/email.ts +17 -0
- package/src/types/files.ts +33 -0
- package/src/types/google.ts +37 -0
- package/src/types/groups.ts +28 -0
- package/src/types/images.ts +33 -0
- package/src/types/index.ts +21 -0
- package/src/types/locations.ts +25 -0
- package/src/types/messages.ts +16 -0
- package/src/types/notifications.ts +26 -0
- package/src/types/payments.ts +134 -0
- package/src/types/posts.ts +25 -0
- package/src/types/reactions.ts +8 -0
- package/src/types/tags.ts +14 -0
- package/src/types/users.ts +89 -0
- package/src/utils/analytics.ts +41 -0
- package/src/utils/arangodb.ts +100 -0
- package/src/utils/auth.ts +28 -0
- package/src/utils/index.ts +9 -0
- package/src/utils/objects.ts +34 -0
- package/src/utils/redis.ts +17 -0
- package/templates/email/layout.html +279 -0
- package/templates/email/passwordForgot.html +15 -0
- package/templates/email/passwordRecovery.html +12 -0
- package/templates/email/verifyEmail.html +15 -0
- package/templates/sms/passwordForgot.txt +1 -0
- package/templates/sms/passwordRecovery.txt +1 -0
- package/templates/sms/verifyEmail.txt +1 -0
- package/templates/sms/verifyPhone.txt +1 -0
- 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
|
+
};
|
package/src/data/sms.ts
ADDED
|
@@ -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
|
+
};
|