@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
|
@@ -0,0 +1,508 @@
|
|
|
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, parseString, parseVarChar} from '@nlabs/utils';
|
|
6
|
+
import {aql, Database} from 'arangojs';
|
|
7
|
+
import {AqlQuery} from 'arangojs/lib/cjs/aql-query';
|
|
8
|
+
import {ArrayCursor} from 'arangojs/lib/cjs/cursor';
|
|
9
|
+
import flatten from 'lodash/flatten';
|
|
10
|
+
import uniqBy from 'lodash/uniqBy';
|
|
11
|
+
|
|
12
|
+
import {ApiContext, ArangoDBLimit, FileType, GroupType, PostType, TagType} from '../types';
|
|
13
|
+
import {getLimit, useDb} from '../utils';
|
|
14
|
+
import {updateFiles} from './files';
|
|
15
|
+
import {extractTags} from './tags';
|
|
16
|
+
|
|
17
|
+
// const eventCategory: string = 'posts';
|
|
18
|
+
|
|
19
|
+
export const getPostList = (context: ApiContext, from: number, to: number): Promise<PostType[]> => {
|
|
20
|
+
// const action: string = 'getListByApp';
|
|
21
|
+
const {database} = context;
|
|
22
|
+
const limit: ArangoDBLimit = getLimit(from, to);
|
|
23
|
+
const aqlQry: string = `FOR p IN posts
|
|
24
|
+
FILTER !!p.parent == false
|
|
25
|
+
LET reactions = (
|
|
26
|
+
FOR post, r IN INBOUND p._id reactions
|
|
27
|
+
COLLECT reactionName = r.value INTO reactionItems
|
|
28
|
+
RETURN {name: reactionName, count: LENGTH(reactionItems[*].r.value)}
|
|
29
|
+
)
|
|
30
|
+
FOR u IN users
|
|
31
|
+
FILTER p.userId == u._key
|
|
32
|
+
${limit.aql}
|
|
33
|
+
SORT p.added
|
|
34
|
+
RETURN DISTINCT MERGE(p, {user:u, reactions:reactions})`;
|
|
35
|
+
|
|
36
|
+
return useDb(database).query(aqlQry)
|
|
37
|
+
.then((cursor: ArrayCursor) => cursor.all())
|
|
38
|
+
.catch((error: Error) => {
|
|
39
|
+
throw error;
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const getPostListByGroup = (context: ApiContext, groupId: string, from: number, to: number): Promise<PostType[]> => {
|
|
44
|
+
// const action: string = 'getListByGroup';
|
|
45
|
+
const {database, userId: sessionId} = context;
|
|
46
|
+
|
|
47
|
+
// Group id
|
|
48
|
+
const formatGroupId: string = parseId(groupId);
|
|
49
|
+
const db = useDb(database);
|
|
50
|
+
const aqlQry: string = `FOR u, g IN INBOUND ${formatGroupId} hasGroup
|
|
51
|
+
FILTER u._key == ${sessionId}
|
|
52
|
+
RETURN g`;
|
|
53
|
+
|
|
54
|
+
return db.query(aqlQry)
|
|
55
|
+
.then((cursor: ArrayCursor) => cursor.all())
|
|
56
|
+
.then((groups: GroupType[] = []) => {
|
|
57
|
+
if(groups.length) {
|
|
58
|
+
const limit: ArangoDBLimit = getLimit(from, to);
|
|
59
|
+
const postAqlQry: string = `FOR p IN posts
|
|
60
|
+
FILTER p.groupId == "${formatGroupId}" && !!p.parent == false
|
|
61
|
+
LET reactions = (
|
|
62
|
+
FOR post, r IN INBOUND p._id reactions
|
|
63
|
+
COLLECT reactionName = r.value INTO reactionItems
|
|
64
|
+
RETURN {name: reactionName, count: LENGTH(reactionItems[*].r.value)}
|
|
65
|
+
)
|
|
66
|
+
FOR u IN users
|
|
67
|
+
FILTER p.userId == u._key
|
|
68
|
+
${limit.aql}
|
|
69
|
+
SORT p.added
|
|
70
|
+
RETURN DISTINCT MERGE(p, {user:u, reactions:reactions})`;
|
|
71
|
+
|
|
72
|
+
return db.query(postAqlQry)
|
|
73
|
+
.then((cursor: ArrayCursor) => cursor.all())
|
|
74
|
+
.catch((error: Error) => {
|
|
75
|
+
throw error;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return [];
|
|
80
|
+
})
|
|
81
|
+
.catch((error: Error) => {
|
|
82
|
+
throw error;
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const getPostListByLatest = (context: ApiContext, from: number, to: number): Promise<PostType[]> => {
|
|
87
|
+
// const action: string = 'getListByLatest';
|
|
88
|
+
const {database} = context;
|
|
89
|
+
const limit: ArangoDBLimit = getLimit(from, to);
|
|
90
|
+
const aqlQry: string = `FOR p IN posts
|
|
91
|
+
FILTER p.privacy == "public" && !!p.parent == false
|
|
92
|
+
LET reactions = (
|
|
93
|
+
FOR post, r IN INBOUND p._id reactions
|
|
94
|
+
COLLECT reactionName = r.value INTO reactionItems
|
|
95
|
+
RETURN {name: reactionName, count: LENGTH(reactionItems[*].r.value)}
|
|
96
|
+
)
|
|
97
|
+
FOR u IN users
|
|
98
|
+
FILTER p.userId == u._key
|
|
99
|
+
${limit.aql}
|
|
100
|
+
SORT p.added
|
|
101
|
+
RETURN DISTINCT MERGE(p, {user:u, reactions:reactions})`;
|
|
102
|
+
|
|
103
|
+
return useDb(database).query(aqlQry)
|
|
104
|
+
.then((cursor: ArrayCursor) => cursor.all())
|
|
105
|
+
.catch((error: Error) => {
|
|
106
|
+
throw error;
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export const getPostListByTags = (context: ApiContext, tagNames: string[], from?: number, to?: number): Promise<PostType[]> => {
|
|
111
|
+
// const action: string = 'getListByTags';
|
|
112
|
+
const {database} = context;
|
|
113
|
+
|
|
114
|
+
return Promise.all(
|
|
115
|
+
tagNames.map((tagName: string) => {
|
|
116
|
+
const formatTagId: string = createHash(`tag-${tagName}`, null);
|
|
117
|
+
const limit: ArangoDBLimit = getLimit(from, to);
|
|
118
|
+
const aqlQry: string = `FOR p, e IN OUTBOUND "${`tags/${formatTagId}`}" isTagged
|
|
119
|
+
FOR u IN users
|
|
120
|
+
LET reactions = (
|
|
121
|
+
FOR post, r IN INBOUND p._id reactions
|
|
122
|
+
COLLECT reactionName = r.value INTO reactionItems
|
|
123
|
+
RETURN {name: reactionName, count: LENGTH(reactionItems[*].r.value)}
|
|
124
|
+
)
|
|
125
|
+
FILTER e.type == 'posts' && p.userId == u._key
|
|
126
|
+
${limit.aql}
|
|
127
|
+
SORT p.added
|
|
128
|
+
RETURN DISTINCT MERGE(p, {user:u, reactions:reactions})`;
|
|
129
|
+
|
|
130
|
+
return useDb(database).query(aqlQry)
|
|
131
|
+
.then((cursor: ArrayCursor) => cursor.all())
|
|
132
|
+
.catch((error: Error) => {
|
|
133
|
+
throw error;
|
|
134
|
+
});
|
|
135
|
+
}))
|
|
136
|
+
.then((results) => uniqBy(flatten(results), '_key'))
|
|
137
|
+
.catch((error: Error) => {
|
|
138
|
+
throw error;
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export const getPostListByUser = (context: ApiContext, userId: string, from: number, to: number): Promise<PostType[]> => {
|
|
143
|
+
// const action: string = 'getListByUser';
|
|
144
|
+
const {database} = context;
|
|
145
|
+
const formatUserId: string = parseId(userId);
|
|
146
|
+
const limit: ArangoDBLimit = getLimit(from, to);
|
|
147
|
+
const aqlQry: string = `FOR p IN posts
|
|
148
|
+
FILTER p.userId == "${formatUserId}" && !!p.parent == false
|
|
149
|
+
LET reactions = (
|
|
150
|
+
FOR post, r IN INBOUND p._id reactions
|
|
151
|
+
COLLECT reactionName = r.value INTO reactionItems
|
|
152
|
+
RETURN {name: reactionName, count: LENGTH(reactionItems[*].r.value)}
|
|
153
|
+
)
|
|
154
|
+
FOR u IN users
|
|
155
|
+
FILTER p.userId == u._key
|
|
156
|
+
${limit.aql}
|
|
157
|
+
SORT p.added
|
|
158
|
+
RETURN DISTINCT MERGE(p, {user:u, reactions:reactions})`;
|
|
159
|
+
|
|
160
|
+
return useDb(database).query(aqlQry)
|
|
161
|
+
.then((cursor: ArrayCursor) => cursor.all())
|
|
162
|
+
.catch((error: Error) => {
|
|
163
|
+
throw error;
|
|
164
|
+
});
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export const getPost = (context: ApiContext, itemId: string): Promise<PostType> => {
|
|
168
|
+
// const action: string = 'getItem';
|
|
169
|
+
const {database, userId: sessionId} = context;
|
|
170
|
+
const formatItemId: string = parseId(itemId);
|
|
171
|
+
const db = useDb(database);
|
|
172
|
+
const aqlQry: AqlQuery = aql`FOR p IN posts
|
|
173
|
+
FILTER p._key == ${formatItemId}
|
|
174
|
+
LIMIT 1
|
|
175
|
+
RETURN p`;
|
|
176
|
+
|
|
177
|
+
return db.query(aqlQry)
|
|
178
|
+
.then((cursor: ArrayCursor) => cursor.next())
|
|
179
|
+
.then((post: PostType = {}) => {
|
|
180
|
+
const {
|
|
181
|
+
_key,
|
|
182
|
+
groupId,
|
|
183
|
+
privacy = 'default'
|
|
184
|
+
}: PostType = post;
|
|
185
|
+
|
|
186
|
+
// Query based on privacy level
|
|
187
|
+
let privacyAqlQry: AqlQuery;
|
|
188
|
+
|
|
189
|
+
if(groupId && privacy === 'group') {
|
|
190
|
+
privacyAqlQry = aql`FOR p IN posts
|
|
191
|
+
FOR user IN users
|
|
192
|
+
FILTER p._key == ${_key} && user._key == p.userId
|
|
193
|
+
LET reactions = (
|
|
194
|
+
FOR post, r IN INBOUND p._id reactions
|
|
195
|
+
COLLECT reactionName = r.value INTO reactionItems
|
|
196
|
+
RETURN {name: reactionName, count: LENGTH(reactionItems[*].r.value)}
|
|
197
|
+
)
|
|
198
|
+
FOR group IN groups
|
|
199
|
+
FILTER group._key == p.groupId
|
|
200
|
+
FOR u, e IN OUTBOUND group._id isGrouped
|
|
201
|
+
FILTER u._key == ${sessionId}
|
|
202
|
+
LIMIT 1
|
|
203
|
+
RETURN MERGE(p, {user: user, reactions: reactions})`;
|
|
204
|
+
} else if(privacy === 'public') {
|
|
205
|
+
privacyAqlQry = aql`FOR p IN posts
|
|
206
|
+
FOR user IN users
|
|
207
|
+
FILTER p._key == ${_key} && user._key == p.userId
|
|
208
|
+
LET reactions = (
|
|
209
|
+
FOR post, r IN INBOUND p._id reactions
|
|
210
|
+
COLLECT reactionName = r.value INTO reactionItems
|
|
211
|
+
RETURN {name: reactionName, count: LENGTH(reactionItems[*].r.value)}
|
|
212
|
+
)
|
|
213
|
+
LIMIT 1
|
|
214
|
+
RETURN MERGE(p, {user: user, reactions: reactions})`;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if(privacyAqlQry) {
|
|
218
|
+
return db.query(privacyAqlQry)
|
|
219
|
+
.then((cursor: ArrayCursor) => cursor.next())
|
|
220
|
+
.then((filteredPost: PostType = {}) => filteredPost)
|
|
221
|
+
.catch((error: Error) => {
|
|
222
|
+
throw error;
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return {};
|
|
227
|
+
})
|
|
228
|
+
.catch((error: Error) => {
|
|
229
|
+
throw error;
|
|
230
|
+
});
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
export const getPostComments = (context: ApiContext, itemId: string, from: number, to: number): Promise<PostType[]> => {
|
|
234
|
+
// const action: string = 'getComments';
|
|
235
|
+
const {database, userId: sessionId} = context;
|
|
236
|
+
const formatItemId: string = parseId(itemId);
|
|
237
|
+
|
|
238
|
+
// Get the parent post to get restrictions
|
|
239
|
+
const db = useDb(database);
|
|
240
|
+
const aqlQry: AqlQuery = aql`FOR p IN posts
|
|
241
|
+
FILTER p._key == ${formatItemId}
|
|
242
|
+
LIMIT 1
|
|
243
|
+
RETURN p`;
|
|
244
|
+
|
|
245
|
+
return db.query(aqlQry)
|
|
246
|
+
.then((cursor: ArrayCursor) => cursor.next())
|
|
247
|
+
.then((post: PostType = {}) => {
|
|
248
|
+
const {
|
|
249
|
+
_key,
|
|
250
|
+
groupId,
|
|
251
|
+
privacy = 'public'
|
|
252
|
+
}: PostType = post;
|
|
253
|
+
|
|
254
|
+
// Query based on privacy level
|
|
255
|
+
let privacyAqlQry: string;
|
|
256
|
+
const limit = getLimit(from, to);
|
|
257
|
+
|
|
258
|
+
if(groupId && privacy === 'group') {
|
|
259
|
+
privacyAqlQry = `FOR p IN posts
|
|
260
|
+
FOR user IN users
|
|
261
|
+
FILTER p.parent == "${_key}" && user._key == p.userId
|
|
262
|
+
LET reactions = (
|
|
263
|
+
FOR post, r IN INBOUND p._id reactions
|
|
264
|
+
COLLECT reactionName = r.value INTO reactionItems
|
|
265
|
+
RETURN {name: reactionName, count: LENGTH(reactionItems[*].r.value)}
|
|
266
|
+
)
|
|
267
|
+
FOR group IN groups
|
|
268
|
+
FILTER group._key == p.groupId
|
|
269
|
+
FOR u, e IN OUTBOUND group._id isGrouped
|
|
270
|
+
FILTER u._key == "${sessionId}"
|
|
271
|
+
SORT p.added
|
|
272
|
+
${limit.aql}
|
|
273
|
+
RETURN MERGE(p, {user: user, reactions: reactions})`;
|
|
274
|
+
} else if(privacy === 'public') {
|
|
275
|
+
privacyAqlQry = `FOR p IN posts
|
|
276
|
+
FOR user IN users
|
|
277
|
+
FILTER p.parent == "${_key}" && user._key == p.userId
|
|
278
|
+
LET reactions = (
|
|
279
|
+
FOR post, r IN INBOUND p._id reactions
|
|
280
|
+
COLLECT reactionName = r.value INTO reactionItems
|
|
281
|
+
RETURN {name: reactionName, count: LENGTH(reactionItems[*].r.value)}
|
|
282
|
+
)
|
|
283
|
+
SORT p.added
|
|
284
|
+
${limit.aql}
|
|
285
|
+
RETURN MERGE(p, {user: user, reactions: reactions})`;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if(privacyAqlQry) {
|
|
289
|
+
return db.query(privacyAqlQry)
|
|
290
|
+
.then((cursor: ArrayCursor) => cursor.all())
|
|
291
|
+
.catch((error: Error) => {
|
|
292
|
+
throw error;
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return [];
|
|
297
|
+
})
|
|
298
|
+
.catch((error: Error) => {
|
|
299
|
+
throw error;
|
|
300
|
+
});
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
export const addPost = (context: ApiContext, item: PostType): Promise<PostType> => {
|
|
304
|
+
// const action: string = 'add';
|
|
305
|
+
const {database, userId: sessionId} = context;
|
|
306
|
+
|
|
307
|
+
const {
|
|
308
|
+
groupId = '',
|
|
309
|
+
name = '',
|
|
310
|
+
parentId = '',
|
|
311
|
+
privacy = 'public',
|
|
312
|
+
text = '',
|
|
313
|
+
title = ''
|
|
314
|
+
}: PostType = item;
|
|
315
|
+
|
|
316
|
+
const now: number = Date.now();
|
|
317
|
+
|
|
318
|
+
const insert: PostType = {
|
|
319
|
+
_key: createHash(`post-${sessionId}`),
|
|
320
|
+
added: now,
|
|
321
|
+
groupId: parseId(groupId),
|
|
322
|
+
modified: now,
|
|
323
|
+
name: parseString(name, 160),
|
|
324
|
+
parentId: parseId(parentId),
|
|
325
|
+
privacy: parseVarChar(privacy, 16),
|
|
326
|
+
text: parseString(text, 20000),
|
|
327
|
+
title: parseString(title, 160),
|
|
328
|
+
userId: sessionId
|
|
329
|
+
};
|
|
330
|
+
const db: Database = useDb(database);
|
|
331
|
+
const aqlQry: AqlQuery = aql`INSERT ${insert} IN posts RETURN NEW`;
|
|
332
|
+
|
|
333
|
+
return db.query(aqlQry)
|
|
334
|
+
.then((cursor: ArrayCursor) => cursor.next())
|
|
335
|
+
.then((post: PostType = {}) => {
|
|
336
|
+
const {_key: postKey} = post;
|
|
337
|
+
|
|
338
|
+
// Update linked tags within posts
|
|
339
|
+
return extractTags(db, 'posts', postKey, insert.text)
|
|
340
|
+
.then((tagList: TagType[]) => {
|
|
341
|
+
post.tags = tagList;
|
|
342
|
+
return post;
|
|
343
|
+
});
|
|
344
|
+
})
|
|
345
|
+
.catch((error: Error) => {
|
|
346
|
+
throw error;
|
|
347
|
+
});
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
export const updatePost = (context: ApiContext, item: PostType): Promise<PostType> => {
|
|
351
|
+
// const action: string = 'update';
|
|
352
|
+
const {database, userId: sessionId} = context;
|
|
353
|
+
const now: number = Date.now();
|
|
354
|
+
const {
|
|
355
|
+
groupId,
|
|
356
|
+
id,
|
|
357
|
+
name,
|
|
358
|
+
parentId,
|
|
359
|
+
privacy,
|
|
360
|
+
text
|
|
361
|
+
}: PostType = item;
|
|
362
|
+
|
|
363
|
+
const updatedPost: PostType = {
|
|
364
|
+
modified: now
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
if(name) {
|
|
368
|
+
updatedPost.name = parseString(name, 160);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if(text) {
|
|
372
|
+
updatedPost.text = parseString(text, 640);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if(privacy) {
|
|
376
|
+
updatedPost.privacy = parseVarChar(privacy, 16);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if(parent) {
|
|
380
|
+
updatedPost.parentId = parseId(parentId);
|
|
381
|
+
}
|
|
382
|
+
const update: any = updatedPost;
|
|
383
|
+
|
|
384
|
+
let formatId: string = parseId(id);
|
|
385
|
+
formatId = formatId === '' ? createHash(`post-${sessionId}`) : formatId;
|
|
386
|
+
const formatGroupId: string = parseId(groupId);
|
|
387
|
+
const insert: any = {
|
|
388
|
+
...update,
|
|
389
|
+
_key: formatId,
|
|
390
|
+
added: now,
|
|
391
|
+
groupId: formatGroupId,
|
|
392
|
+
privacy,
|
|
393
|
+
userId: sessionId
|
|
394
|
+
};
|
|
395
|
+
const db: Database = useDb(database);
|
|
396
|
+
const aqlQry: AqlQuery = aql`UPSERT {_key: ${id}, userId: ${sessionId}}
|
|
397
|
+
INSERT ${insert}
|
|
398
|
+
UPDATE ${update}
|
|
399
|
+
IN posts RETURN NEW`;
|
|
400
|
+
|
|
401
|
+
return db.query(aqlQry)
|
|
402
|
+
.then((cursor: ArrayCursor) => cursor.next())
|
|
403
|
+
.then((updatedPost: PostType = {}) => {
|
|
404
|
+
const {_key: updatedPostKey} = updatedPost;
|
|
405
|
+
|
|
406
|
+
// Update linked tags
|
|
407
|
+
return extractTags(db, 'posts', updatedPostKey, update.text || '')
|
|
408
|
+
.then((tagList = []) => {
|
|
409
|
+
updatedPost.tags = tagList;
|
|
410
|
+
|
|
411
|
+
// Update linked files
|
|
412
|
+
const files: FileType[] = updatedPost.files || [];
|
|
413
|
+
|
|
414
|
+
if(files.length) {
|
|
415
|
+
return updateFiles(db, id, files)
|
|
416
|
+
.then((fileList = []) => {
|
|
417
|
+
updatedPost.files = fileList;
|
|
418
|
+
return updatedPost;
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
updatedPost.files = [];
|
|
423
|
+
return updatedPost;
|
|
424
|
+
});
|
|
425
|
+
})
|
|
426
|
+
.catch((error: Error) => {
|
|
427
|
+
throw error;
|
|
428
|
+
});
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
export const deletePost = (context: ApiContext, itemId: string): Promise<PostType> => {
|
|
432
|
+
// const action: string = 'delete';
|
|
433
|
+
const {database, userId: sessionId} = context;
|
|
434
|
+
const formatItemId: string = parseId(itemId);
|
|
435
|
+
const db: Database = useDb(database);
|
|
436
|
+
const aqlQry = aql`FOR p IN posts
|
|
437
|
+
FILTER p._key == ${formatItemId} && p.userId == ${sessionId}
|
|
438
|
+
LIMIT 1
|
|
439
|
+
REMOVE p IN posts
|
|
440
|
+
RETURN OLD`;
|
|
441
|
+
|
|
442
|
+
return db.query(aqlQry)
|
|
443
|
+
.then((cursor: ArrayCursor) => cursor.next())
|
|
444
|
+
.then((post: PostType = {}) => {
|
|
445
|
+
if(post) {
|
|
446
|
+
// Remove tag links
|
|
447
|
+
const edgeAqlQry: AqlQuery = aql`FOR t IN isTagged
|
|
448
|
+
FILTER t._to == ${formatItemId}
|
|
449
|
+
REMOVE t IN isTagged`;
|
|
450
|
+
|
|
451
|
+
return db.query(edgeAqlQry)
|
|
452
|
+
.then(() => {
|
|
453
|
+
// Remove attached files
|
|
454
|
+
const fileAqlQry: AqlQuery = aql`FOR f IN hasFile
|
|
455
|
+
FILTER f._to == ${formatItemId}
|
|
456
|
+
REMOVE f IN hasFile`;
|
|
457
|
+
|
|
458
|
+
return db.query(fileAqlQry)
|
|
459
|
+
.then(() => post)
|
|
460
|
+
.catch((error: Error) => {
|
|
461
|
+
throw error;
|
|
462
|
+
});
|
|
463
|
+
})
|
|
464
|
+
.catch((error: Error) => {
|
|
465
|
+
throw error;
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
return {};
|
|
469
|
+
})
|
|
470
|
+
.catch((error: Error) => {
|
|
471
|
+
throw error;
|
|
472
|
+
});
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
export const cleanPosts = (database: string): Promise<number> => {
|
|
476
|
+
// Remove all messages that are over 60 days and not saved
|
|
477
|
+
const aqlQry: AqlQuery = aql`FOR p IN posts
|
|
478
|
+
FILTER p.added < DATE_TIMESTAMP(DATE_SUBTRACT(DATE_NOW(), 60, 'day')) && p.type == 1
|
|
479
|
+
REMOVE p IN posts
|
|
480
|
+
RETURN OLD`;
|
|
481
|
+
|
|
482
|
+
return useDb(database).query(aqlQry)
|
|
483
|
+
.then((cursor: ArrayCursor) => cursor.all())
|
|
484
|
+
.then((results: PostType[] = []) => results.length)
|
|
485
|
+
.catch((error: Error) => {
|
|
486
|
+
throw error;
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
export const createPostEdge = (db: Database, file: FileType, postId: string): Promise<FileType> => {
|
|
491
|
+
const edgeCollection = db.edgeCollection('isPosted');
|
|
492
|
+
const fileId: string = parseId(file.id);
|
|
493
|
+
const edgeId: string = createHash(`file-${postId}-${fileId}`);
|
|
494
|
+
const formatPostId: string = parseId(postId);
|
|
495
|
+
const fileType: string = parseChar(file.fileType, 16);
|
|
496
|
+
|
|
497
|
+
const edge: any = {
|
|
498
|
+
_key: edgeId,
|
|
499
|
+
added: Date.now(),
|
|
500
|
+
type: fileType
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
return edgeCollection.save(edge, `posts/${formatPostId}`, `files/${fileId}`)
|
|
504
|
+
.then(() => file)
|
|
505
|
+
.catch((error: Error) => {
|
|
506
|
+
throw error;
|
|
507
|
+
});
|
|
508
|
+
};
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import {createHash, parseChar, parseId, parseNum} from '@nlabs/utils';
|
|
2
|
+
import {aql, Database, EdgeCollection} from 'arangojs';
|
|
3
|
+
import {AqlQuery} from 'arangojs/lib/cjs/aql-query';
|
|
4
|
+
import {ArrayCursor} from 'arangojs/lib/cjs/cursor';
|
|
5
|
+
|
|
6
|
+
import {ApiContext, QueryFilter, ReactionType, UserReactionQuery, UserReactionType, UserType} from '../types';
|
|
7
|
+
import {logError, logException, useDb} from '../utils';
|
|
8
|
+
|
|
9
|
+
const eventCategory: string = 'reactions';
|
|
10
|
+
|
|
11
|
+
export const addGroupReaction = (context: ApiContext, params: UserReactionType = {}): Promise<boolean> => {
|
|
12
|
+
const action: string = 'reaction';
|
|
13
|
+
const {database, userId: sessionId} = context;
|
|
14
|
+
|
|
15
|
+
if(!sessionId) {
|
|
16
|
+
return logException({
|
|
17
|
+
action,
|
|
18
|
+
category: eventCategory,
|
|
19
|
+
label: 'unauthorized',
|
|
20
|
+
value: 'invalid_session'
|
|
21
|
+
}, context).then(() => null);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const {id: itemId, value: itemValue = 'like'} = params;
|
|
25
|
+
const formatItemId: string = parseId(itemId);
|
|
26
|
+
const formatValue: string = parseChar(itemValue, 32);
|
|
27
|
+
const edgeCollection = useDb(database).edgeCollection('hasReaction');
|
|
28
|
+
|
|
29
|
+
// Remove existing likes
|
|
30
|
+
const groupDocId: string = `groups/${formatItemId}`;
|
|
31
|
+
const userDocId: string = `users/${sessionId}`;
|
|
32
|
+
const edgeId: string = createHash(`reaction-${formatItemId}-${sessionId}`);
|
|
33
|
+
const edge: any = {
|
|
34
|
+
_key: edgeId,
|
|
35
|
+
added: Date.now(),
|
|
36
|
+
type: 'group',
|
|
37
|
+
value: formatValue
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
return edgeCollection.save(edge, userDocId, groupDocId).then(() => true);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const getGroupReactions = (context: ApiContext, params: UserReactionType = {}): Promise<UserReactionType> => {
|
|
44
|
+
const action: string = 'getReactions';
|
|
45
|
+
const {database} = context;
|
|
46
|
+
const {id: groupId}: UserReactionType = params;
|
|
47
|
+
const groupDocId: string = `groups/${parseId(groupId)}`;
|
|
48
|
+
|
|
49
|
+
// Query
|
|
50
|
+
const aqlQry: AqlQuery = aql`FOR g, r IN INBOUND ${groupDocId} hasReaction
|
|
51
|
+
COLLECT reactionName = r.value INTO reactionItems
|
|
52
|
+
RETURN {value: reactionName, count: LENGTH(reactionItems[*].r.value)}`;
|
|
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 getReactionsByUser = (context: ApiContext, groupId: string): Promise<UserReactionType[]> => {
|
|
64
|
+
const action: string = 'getReactionsByUser';
|
|
65
|
+
const {database, userId: sessionId} = context;
|
|
66
|
+
const groupDocId: string = `groups/${parseId(groupId)}`;
|
|
67
|
+
const userDocId: string = `users/${sessionId}`;
|
|
68
|
+
|
|
69
|
+
// Query
|
|
70
|
+
const aqlQry: AqlQuery = aql`FOR g, r IN INBOUND ${groupDocId} hasReaction
|
|
71
|
+
FILTER r._from == ${userDocId}
|
|
72
|
+
COLLECT reactionName = r.value INTO reactionItems
|
|
73
|
+
RETURN {value: reactionName, count: LENGTH(reactionItems[*].r.value)}`;
|
|
74
|
+
|
|
75
|
+
return useDb(database).query(aqlQry)
|
|
76
|
+
.then((cursor: ArrayCursor) => cursor.all())
|
|
77
|
+
.catch((error: Error) => logError({
|
|
78
|
+
action,
|
|
79
|
+
category: eventCategory,
|
|
80
|
+
label: 'db_error'
|
|
81
|
+
}, error, context).then(() => null));
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const getUsersByReaction = (context: ApiContext, params: UserReactionQuery = {}): Promise<UserType[]> => {
|
|
85
|
+
const action: string = 'getUsersByReaction';
|
|
86
|
+
const {database, userId: sessionId} = context;
|
|
87
|
+
|
|
88
|
+
if(!sessionId) {
|
|
89
|
+
return logException({
|
|
90
|
+
action,
|
|
91
|
+
category: eventCategory,
|
|
92
|
+
label: 'unauthorized',
|
|
93
|
+
value: 'invalid_session'
|
|
94
|
+
}, context).then(() => null);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const {filters = [], id: groupId, value}: UserReactionQuery = params;
|
|
98
|
+
const reaction = parseChar(value, 32);
|
|
99
|
+
const groupDocId = `groups/${parseId(groupId)}`;
|
|
100
|
+
const filterStr: string = filters
|
|
101
|
+
.map((filter: QueryFilter) => {
|
|
102
|
+
const {conditional, name, value: queryValue}: QueryFilter = filter;
|
|
103
|
+
let filterCond: string = conditional;
|
|
104
|
+
|
|
105
|
+
if(conditional !== '>=' && conditional !== '<=' && conditional !== '>' && conditional !== '<') {
|
|
106
|
+
filterCond = '==';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
switch(name) {
|
|
110
|
+
case 'added':
|
|
111
|
+
return `r.added ${filterCond} ${parseNum(queryValue)}`;
|
|
112
|
+
default:
|
|
113
|
+
return '';
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
.concat([
|
|
117
|
+
`r.value == "${reaction}"`,
|
|
118
|
+
'u._id == r._from'
|
|
119
|
+
])
|
|
120
|
+
.join(' && ');
|
|
121
|
+
|
|
122
|
+
// Query
|
|
123
|
+
const aqlQry: string = `FOR g, r IN INBOUND "${groupDocId}" hasReaction
|
|
124
|
+
FOR u IN users
|
|
125
|
+
FILTER ${filterStr}
|
|
126
|
+
RETURN u`;
|
|
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
|
+
|
|
137
|
+
|
|
138
|
+
export const postReaction = (context: ApiContext, postId: string, type: string = 'like'): Promise<ReactionType> => {
|
|
139
|
+
const {database, userId: sessionId} = context;
|
|
140
|
+
const formatItemId: string = parseId(postId);
|
|
141
|
+
const db: Database = useDb(database);
|
|
142
|
+
const edgeCollection: EdgeCollection = db.edgeCollection('reactions');
|
|
143
|
+
const now: number = Date.now();
|
|
144
|
+
|
|
145
|
+
// Remove existing reaction to post
|
|
146
|
+
const postDocId: string = `posts/${formatItemId}`;
|
|
147
|
+
const userDocId: string = `users/${sessionId}`;
|
|
148
|
+
const aqlQry: AqlQuery = aql`FOR p, r IN INBOUND ${postDocId} reaction
|
|
149
|
+
FILTER r.type == "posts" && r._from == ${userDocId}
|
|
150
|
+
REMOVE r IN reactions
|
|
151
|
+
RETURN r`;
|
|
152
|
+
|
|
153
|
+
return db.query(aqlQry)
|
|
154
|
+
.then(() => {
|
|
155
|
+
const edgeId = createHash(`reaction-${postId}-${sessionId}`);
|
|
156
|
+
const edge: any = {
|
|
157
|
+
_key: edgeId,
|
|
158
|
+
added: now,
|
|
159
|
+
type: 'posts',
|
|
160
|
+
value: type
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
return edgeCollection.save(edge, userDocId, postDocId)
|
|
164
|
+
.then(() => {
|
|
165
|
+
const reactionAqlQry: AqlQuery = aql`LET reactions = (
|
|
166
|
+
FOR post, r IN INBOUND p._id reactions
|
|
167
|
+
COLLECT reactionName = r.value INTO reactionItems
|
|
168
|
+
RETURN {id: p._id, type: reactionName, count: LENGTH(reactionItems[*].r.value)}
|
|
169
|
+
)
|
|
170
|
+
RETURN reactions`;
|
|
171
|
+
|
|
172
|
+
return db.query(reactionAqlQry)
|
|
173
|
+
.then((cursor: ArrayCursor) => cursor.next())
|
|
174
|
+
.then((result = {reactions: []}) => result)
|
|
175
|
+
.catch((error: Error) => {
|
|
176
|
+
throw error;
|
|
177
|
+
});
|
|
178
|
+
})
|
|
179
|
+
.catch((error: Error) => {
|
|
180
|
+
throw error;
|
|
181
|
+
});
|
|
182
|
+
})
|
|
183
|
+
.catch((error: Error) => {
|
|
184
|
+
throw error;
|
|
185
|
+
});
|
|
186
|
+
};
|