@abtnode/core 1.17.8-beta-20260109-075740-5f484e08 → 1.17.8-beta-20260113-015027-32a1cec4
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/lib/api/team/access-key-manager.js +104 -0
- package/lib/api/team/invitation-manager.js +461 -0
- package/lib/api/team/notification-manager.js +189 -0
- package/lib/api/team/oauth-manager.js +60 -0
- package/lib/api/team/org-crud-manager.js +202 -0
- package/lib/api/team/org-manager.js +56 -0
- package/lib/api/team/org-member-manager.js +403 -0
- package/lib/api/team/org-query-manager.js +126 -0
- package/lib/api/team/org-resource-manager.js +186 -0
- package/lib/api/team/passport-manager.js +670 -0
- package/lib/api/team/rbac-manager.js +335 -0
- package/lib/api/team/session-manager.js +540 -0
- package/lib/api/team/store-manager.js +198 -0
- package/lib/api/team/tag-manager.js +230 -0
- package/lib/api/team/user-auth-manager.js +132 -0
- package/lib/api/team/user-manager.js +78 -0
- package/lib/api/team/user-query-manager.js +299 -0
- package/lib/api/team/user-social-manager.js +354 -0
- package/lib/api/team/user-update-manager.js +224 -0
- package/lib/api/team/verify-code-manager.js +161 -0
- package/lib/api/team.js +439 -3287
- package/lib/blocklet/manager/disk/auth-manager.js +68 -0
- package/lib/blocklet/manager/disk/backup-manager.js +288 -0
- package/lib/blocklet/manager/disk/cleanup-manager.js +157 -0
- package/lib/blocklet/manager/disk/component-manager.js +83 -0
- package/lib/blocklet/manager/disk/config-manager.js +191 -0
- package/lib/blocklet/manager/disk/controller-manager.js +64 -0
- package/lib/blocklet/manager/disk/delete-reset-manager.js +328 -0
- package/lib/blocklet/manager/disk/download-manager.js +96 -0
- package/lib/blocklet/manager/disk/env-config-manager.js +311 -0
- package/lib/blocklet/manager/disk/federated-manager.js +651 -0
- package/lib/blocklet/manager/disk/hook-manager.js +124 -0
- package/lib/blocklet/manager/disk/install-component-manager.js +95 -0
- package/lib/blocklet/manager/disk/install-core-manager.js +448 -0
- package/lib/blocklet/manager/disk/install-download-manager.js +313 -0
- package/lib/blocklet/manager/disk/install-manager.js +36 -0
- package/lib/blocklet/manager/disk/install-upgrade-manager.js +340 -0
- package/lib/blocklet/manager/disk/job-manager.js +467 -0
- package/lib/blocklet/manager/disk/lifecycle-manager.js +26 -0
- package/lib/blocklet/manager/disk/notification-manager.js +343 -0
- package/lib/blocklet/manager/disk/query-manager.js +562 -0
- package/lib/blocklet/manager/disk/settings-manager.js +507 -0
- package/lib/blocklet/manager/disk/start-manager.js +611 -0
- package/lib/blocklet/manager/disk/stop-restart-manager.js +292 -0
- package/lib/blocklet/manager/disk/update-manager.js +153 -0
- package/lib/blocklet/manager/disk.js +669 -5796
- package/lib/blocklet/manager/helper/blue-green-start-blocklet.js +5 -0
- package/lib/blocklet/manager/lock.js +18 -0
- package/lib/event/index.js +28 -24
- package/lib/util/blocklet/app-utils.js +192 -0
- package/lib/util/blocklet/blocklet-loader.js +258 -0
- package/lib/util/blocklet/config-manager.js +232 -0
- package/lib/util/blocklet/did-document.js +240 -0
- package/lib/util/blocklet/environment.js +555 -0
- package/lib/util/blocklet/health-check.js +449 -0
- package/lib/util/blocklet/install-utils.js +365 -0
- package/lib/util/blocklet/logo.js +57 -0
- package/lib/util/blocklet/meta-utils.js +269 -0
- package/lib/util/blocklet/port-manager.js +141 -0
- package/lib/util/blocklet/process-manager.js +504 -0
- package/lib/util/blocklet/runtime-info.js +105 -0
- package/lib/util/blocklet/validation.js +418 -0
- package/lib/util/blocklet.js +98 -3066
- package/lib/util/wallet-app-notification.js +40 -0
- package/package.json +22 -22
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
const pick = require('lodash/pick');
|
|
2
|
+
const pMap = require('p-map');
|
|
3
|
+
const { MAX_USER_PAGE_SIZE, PASSPORT_STATUS } = require('@abtnode/constant');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Get users
|
|
7
|
+
* @param {Object} api - TeamAPI instance
|
|
8
|
+
* @param {Object} params
|
|
9
|
+
* @param {string} params.teamDid - Team DID
|
|
10
|
+
* @param {Object} params.query - Query
|
|
11
|
+
* @param {Object} params.paging - Paging
|
|
12
|
+
* @param {Object} params.sort - Sort
|
|
13
|
+
* @param {Array} params.dids - DIDs
|
|
14
|
+
* @param {Object} context
|
|
15
|
+
* @returns {Promise<Object>}
|
|
16
|
+
*/
|
|
17
|
+
async function getUsers(api, { teamDid, query, paging: inputPaging, sort, dids }, context = {}) {
|
|
18
|
+
const state = await api.getUserState(teamDid);
|
|
19
|
+
const nodeInfo = await api.node.read();
|
|
20
|
+
|
|
21
|
+
// HACK: 如果查询的是 server 的数据库,则不应该查询 userSession 表
|
|
22
|
+
if (teamDid === nodeInfo.did) {
|
|
23
|
+
if (query) {
|
|
24
|
+
delete query.includeUserSessions;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (inputPaging?.pageSize > MAX_USER_PAGE_SIZE) {
|
|
29
|
+
throw new Error(`Length of users should not exceed ${MAX_USER_PAGE_SIZE} per page`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (dids && dids.length > MAX_USER_PAGE_SIZE) {
|
|
33
|
+
throw new Error(`Length of users should not exceed ${MAX_USER_PAGE_SIZE}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let list;
|
|
37
|
+
let paging;
|
|
38
|
+
|
|
39
|
+
if (dids) {
|
|
40
|
+
list = await state.getUsersByDids({ query, dids }, context);
|
|
41
|
+
paging = {
|
|
42
|
+
total: list.length,
|
|
43
|
+
pageSize: dids.length,
|
|
44
|
+
pageCount: 1,
|
|
45
|
+
page: 1,
|
|
46
|
+
};
|
|
47
|
+
} else {
|
|
48
|
+
const doc = await state.getUsers({ query, sort, paging: { pageSize: 20, ...inputPaging } }, context);
|
|
49
|
+
list = doc.list;
|
|
50
|
+
paging = doc.paging;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let userSessionsCountList = [];
|
|
54
|
+
if (teamDid !== nodeInfo.did && query?.includeUserSessions) {
|
|
55
|
+
userSessionsCountList = await pMap(
|
|
56
|
+
list,
|
|
57
|
+
async (x) => {
|
|
58
|
+
const { count: userSessionsCount } = await api.getUserSessionsCount({
|
|
59
|
+
teamDid,
|
|
60
|
+
query: {
|
|
61
|
+
userDid: x.did,
|
|
62
|
+
appPid: teamDid,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
return userSessionsCount;
|
|
66
|
+
},
|
|
67
|
+
{ concurrency: 10 }
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
const users = list.map(
|
|
71
|
+
(d, index) => {
|
|
72
|
+
const pickData = pick(d, [
|
|
73
|
+
'did',
|
|
74
|
+
'pk',
|
|
75
|
+
'role',
|
|
76
|
+
'email',
|
|
77
|
+
'phone',
|
|
78
|
+
'fullName',
|
|
79
|
+
'approved',
|
|
80
|
+
'createdAt',
|
|
81
|
+
'updatedAt',
|
|
82
|
+
'passports',
|
|
83
|
+
'firstLoginAt',
|
|
84
|
+
'lastLoginAt',
|
|
85
|
+
'lastLoginIp',
|
|
86
|
+
'remark',
|
|
87
|
+
'avatar',
|
|
88
|
+
'locale',
|
|
89
|
+
'tags',
|
|
90
|
+
'url',
|
|
91
|
+
'inviter',
|
|
92
|
+
'generation',
|
|
93
|
+
'emailVerified',
|
|
94
|
+
'phoneVerified',
|
|
95
|
+
// oauth relate fields
|
|
96
|
+
'sourceProvider',
|
|
97
|
+
'sourceAppPid',
|
|
98
|
+
'connectedAccounts',
|
|
99
|
+
|
|
100
|
+
'metadata',
|
|
101
|
+
'isFollowing',
|
|
102
|
+
]);
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
...pickData,
|
|
106
|
+
userSessions: [],
|
|
107
|
+
userSessionsCount: userSessionsCountList[index] || 0,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// eslint-disable-next-line function-paren-newline
|
|
112
|
+
);
|
|
113
|
+
return {
|
|
114
|
+
// FIXME: @zhanghan 这里做字段过滤的目的是?gql 本身已经对字段做了过滤了
|
|
115
|
+
users,
|
|
116
|
+
paging,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get users count
|
|
122
|
+
* @param {Object} api - TeamAPI instance
|
|
123
|
+
* @param {Object} params
|
|
124
|
+
* @param {string} params.teamDid - Team DID
|
|
125
|
+
* @returns {Promise<number>}
|
|
126
|
+
*/
|
|
127
|
+
async function getUsersCount(api, { teamDid }) {
|
|
128
|
+
const state = await api.getUserState(teamDid);
|
|
129
|
+
return state.count();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get users count per role
|
|
134
|
+
* @param {Object} api - TeamAPI instance
|
|
135
|
+
* @param {Object} params
|
|
136
|
+
* @param {string} params.teamDid - Team DID
|
|
137
|
+
* @returns {Promise<Array>}
|
|
138
|
+
*/
|
|
139
|
+
async function getUsersCountPerRole(api, { teamDid }) {
|
|
140
|
+
const roles = await api.getRoles({ teamDid });
|
|
141
|
+
const state = await api.getUserState(teamDid);
|
|
142
|
+
const names = ['$all', ...roles.filter((x) => !x.orgId).map((x) => x.name), '$none', '$blocked'];
|
|
143
|
+
return Promise.all(
|
|
144
|
+
names.map(async (name) => {
|
|
145
|
+
const count = await state.countByPassport({ name, status: PASSPORT_STATUS.VALID });
|
|
146
|
+
return { key: name, value: count };
|
|
147
|
+
})
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Get user
|
|
153
|
+
* @param {Object} api - TeamAPI instance
|
|
154
|
+
* @param {Object} params
|
|
155
|
+
* @param {string} params.teamDid - Team DID
|
|
156
|
+
* @param {Object} params.user - User
|
|
157
|
+
* @param {Object} params.options - Options
|
|
158
|
+
* @returns {Promise<Object>}
|
|
159
|
+
*/
|
|
160
|
+
async function getUser(api, { teamDid, user, options = {} }) {
|
|
161
|
+
const state = await api.getUserState(teamDid);
|
|
162
|
+
return state.getUser(user.did, options);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Get connected account
|
|
167
|
+
* @param {Object} api - TeamAPI instance
|
|
168
|
+
* @param {Object} params
|
|
169
|
+
* @param {string} params.teamDid - Team DID
|
|
170
|
+
* @param {string} params.id - Account ID
|
|
171
|
+
* @returns {Promise<Object>}
|
|
172
|
+
*/
|
|
173
|
+
async function getConnectedAccount(api, { teamDid, id }) {
|
|
174
|
+
const state = await api.getUserState(teamDid);
|
|
175
|
+
return state.getConnectedAccount(id);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Check if email is used
|
|
180
|
+
* @param {Object} api - TeamAPI instance
|
|
181
|
+
* @param {Object} params
|
|
182
|
+
* @param {string} params.teamDid - Team DID
|
|
183
|
+
* @param {string} params.email - Email
|
|
184
|
+
* @param {boolean} params.verified - Verified
|
|
185
|
+
* @param {string} params.sourceProvider - Source provider
|
|
186
|
+
* @returns {Promise<boolean>}
|
|
187
|
+
*/
|
|
188
|
+
async function isEmailUsed(api, { teamDid, email, verified = false, sourceProvider = '' }) {
|
|
189
|
+
const state = await api.getUserState(teamDid);
|
|
190
|
+
return state.isEmailUsed(email, verified, sourceProvider);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Check if phone is used
|
|
195
|
+
* @param {Object} api - TeamAPI instance
|
|
196
|
+
* @param {Object} params
|
|
197
|
+
* @param {string} params.teamDid - Team DID
|
|
198
|
+
* @param {string} params.phone - Phone
|
|
199
|
+
* @param {boolean} params.verified - Verified
|
|
200
|
+
* @returns {Promise<boolean>}
|
|
201
|
+
*/
|
|
202
|
+
async function isPhoneUsed(api, { teamDid, phone, verified = false }) {
|
|
203
|
+
const state = await api.getUserState(teamDid);
|
|
204
|
+
return state.isPhoneUsed(phone, verified);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Get user by DID
|
|
209
|
+
* @param {Object} api - TeamAPI instance
|
|
210
|
+
* @param {Object} params
|
|
211
|
+
* @param {string} params.teamDid - Team DID
|
|
212
|
+
* @param {string} params.userDid - User DID
|
|
213
|
+
* @param {Array} params.attributes - Attributes
|
|
214
|
+
* @returns {Promise<Object>}
|
|
215
|
+
*/
|
|
216
|
+
async function getUserByDid(api, { teamDid, userDid, attributes = [] }) {
|
|
217
|
+
const state = await api.getUserState(teamDid);
|
|
218
|
+
return state.getUserByDid(userDid, attributes);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Check if user is valid
|
|
223
|
+
* @param {Object} api - TeamAPI instance
|
|
224
|
+
* @param {Object} params
|
|
225
|
+
* @param {string} params.teamDid - Team DID
|
|
226
|
+
* @param {string} params.userDid - User DID
|
|
227
|
+
* @returns {Promise<boolean>}
|
|
228
|
+
*/
|
|
229
|
+
async function isUserValid(api, { teamDid, userDid }) {
|
|
230
|
+
const state = await api.getUserState(teamDid);
|
|
231
|
+
return state.isUserValid(userDid);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Check if passport is valid
|
|
236
|
+
* @param {Object} api - TeamAPI instance
|
|
237
|
+
* @param {Object} params
|
|
238
|
+
* @param {string} params.teamDid - Team DID
|
|
239
|
+
* @param {string} params.passportId - Passport ID
|
|
240
|
+
* @returns {Promise<boolean>}
|
|
241
|
+
*/
|
|
242
|
+
async function isPassportValid(api, { teamDid, passportId }) {
|
|
243
|
+
const state = await api.getUserState(teamDid);
|
|
244
|
+
return state.isPassportValid(passportId);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Check if is connected account
|
|
249
|
+
* @param {Object} api - TeamAPI instance
|
|
250
|
+
* @param {Object} params
|
|
251
|
+
* @param {string} params.teamDid - Team DID
|
|
252
|
+
* @param {string} params.did - DID
|
|
253
|
+
* @returns {Promise<boolean>}
|
|
254
|
+
*/
|
|
255
|
+
async function isConnectedAccount(api, { teamDid, did }) {
|
|
256
|
+
const state = await api.getUserState(teamDid);
|
|
257
|
+
return state.isConnectedAccount(did);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Get owner
|
|
262
|
+
* @param {Object} api - TeamAPI instance
|
|
263
|
+
* @param {Object} params
|
|
264
|
+
* @param {string} params.teamDid - Team DID
|
|
265
|
+
* @returns {Promise<Object>}
|
|
266
|
+
*/
|
|
267
|
+
async function getOwner(api, { teamDid }) {
|
|
268
|
+
let owner = await api.teamManager.getOwner(teamDid);
|
|
269
|
+
const state = await api.getUserState(teamDid);
|
|
270
|
+
if (!owner) {
|
|
271
|
+
const ownerDids = await state.getOwnerDids();
|
|
272
|
+
if (ownerDids.length > 0) {
|
|
273
|
+
owner = { did: ownerDids[0] };
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (!owner) {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// NOTICE: 目前 owner 只能为 did-wallet,无需查询 connectedAccount
|
|
282
|
+
const full = await state.getUser(owner.did);
|
|
283
|
+
return full || owner;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
module.exports = {
|
|
287
|
+
getUsers,
|
|
288
|
+
getUsersCount,
|
|
289
|
+
getUsersCountPerRole,
|
|
290
|
+
getUser,
|
|
291
|
+
getConnectedAccount,
|
|
292
|
+
isEmailUsed,
|
|
293
|
+
isPhoneUsed,
|
|
294
|
+
getUserByDid,
|
|
295
|
+
isUserValid,
|
|
296
|
+
isPassportValid,
|
|
297
|
+
isConnectedAccount,
|
|
298
|
+
getOwner,
|
|
299
|
+
};
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
const omit = require('lodash/omit');
|
|
2
|
+
const { joinURL } = require('ufo');
|
|
3
|
+
const logger = require('@abtnode/logger')('@abtnode/core:api:team:user-social');
|
|
4
|
+
const {
|
|
5
|
+
MAX_USER_PAGE_SIZE,
|
|
6
|
+
USER_AVATAR_URL_PREFIX,
|
|
7
|
+
WELLKNOWN_SERVICE_PATH_PREFIX,
|
|
8
|
+
NOTIFICATION_SEND_CHANNEL,
|
|
9
|
+
WEBHOOK_CONSECUTIVE_FAILURES_THRESHOLD,
|
|
10
|
+
} = require('@abtnode/constant');
|
|
11
|
+
const { CustomError } = require('@blocklet/error');
|
|
12
|
+
const { getUserAvatarUrl: getFederatedUserAvatarUrl } = require('@abtnode/auth/lib/util/federated');
|
|
13
|
+
|
|
14
|
+
const { getBlocklet } = require('../../util/blocklet');
|
|
15
|
+
|
|
16
|
+
// 关注取消关注相关接口
|
|
17
|
+
const USER_RELATION_ACTIONS = {
|
|
18
|
+
follow: (state, followerDid, userDid, options) => state.followUser(followerDid, userDid, options),
|
|
19
|
+
unfollow: (state, followerDid, userDid, options) => state.unfollowUser(followerDid, userDid, options),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// 获取用户相关接口
|
|
23
|
+
const USER_RELATION_QUERIES = {
|
|
24
|
+
followers: (state, userDid, options, context) => state.getFollowers(userDid, options, context),
|
|
25
|
+
following: (state, userDid, options, context) => state.getFollowing(userDid, options, context),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* User follow action
|
|
30
|
+
* @param {Object} api - TeamAPI instance
|
|
31
|
+
* @param {Object} params
|
|
32
|
+
* @param {string} params.teamDid - Team DID
|
|
33
|
+
* @param {string} params.userDid - User DID
|
|
34
|
+
* @param {string} params.followerDid - Follower DID
|
|
35
|
+
* @param {string} params.action - Action
|
|
36
|
+
* @param {Object} params.options - Options
|
|
37
|
+
* @param {Object} context
|
|
38
|
+
* @returns {Promise<Object>}
|
|
39
|
+
*/
|
|
40
|
+
async function userFollowAction(api, { teamDid, userDid, followerDid, action = 'follow', options = {} }, context = {}) {
|
|
41
|
+
const targetDid = followerDid || context.userDid;
|
|
42
|
+
|
|
43
|
+
if (!USER_RELATION_ACTIONS[action]) {
|
|
44
|
+
throw new CustomError(
|
|
45
|
+
400,
|
|
46
|
+
`Invalid action: ${action}. Supported: ${Object.keys(USER_RELATION_ACTIONS).join(', ')}`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
let result;
|
|
50
|
+
try {
|
|
51
|
+
const state = await api.getUserState(teamDid);
|
|
52
|
+
result = await USER_RELATION_ACTIONS[action](state, targetDid, userDid, options);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
logger.error(`Failed to ${action} user`, {
|
|
55
|
+
followerDid: targetDid,
|
|
56
|
+
userDid,
|
|
57
|
+
error,
|
|
58
|
+
});
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
if (action === 'follow' && !options.skipNotification) {
|
|
64
|
+
const state = await api.getUserState(teamDid);
|
|
65
|
+
const currentUser = await state.getUser(targetDid);
|
|
66
|
+
await api.createNotification({
|
|
67
|
+
teamDid,
|
|
68
|
+
receiver: userDid,
|
|
69
|
+
title: 'Followed',
|
|
70
|
+
description: `<${currentUser.fullName}(did:abt:${currentUser.did})> followed you`,
|
|
71
|
+
activity: {
|
|
72
|
+
type: 'follow',
|
|
73
|
+
actor: followerDid,
|
|
74
|
+
target: {
|
|
75
|
+
type: 'user',
|
|
76
|
+
id: followerDid,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
entityId: teamDid,
|
|
80
|
+
source: 'system',
|
|
81
|
+
severity: 'success',
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
} catch (error) {
|
|
85
|
+
// 消息创建失败,不影响 follow 操作
|
|
86
|
+
logger.error('Failed to create notification', { error });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get user follows
|
|
94
|
+
* @param {Object} api - TeamAPI instance
|
|
95
|
+
* @param {Object} params
|
|
96
|
+
* @param {string} params.teamDid - Team DID
|
|
97
|
+
* @param {string} params.userDid - User DID
|
|
98
|
+
* @param {string} params.type - Type
|
|
99
|
+
* @param {Object} params.paging - Paging
|
|
100
|
+
* @param {Object} params.sort - Sort
|
|
101
|
+
* @param {Object} params.options - Options
|
|
102
|
+
* @param {Object} context
|
|
103
|
+
* @returns {Promise<Object>}
|
|
104
|
+
*/
|
|
105
|
+
async function getUserFollows(api, { teamDid, userDid, type = 'following', paging, sort, options = {} }, context = {}) {
|
|
106
|
+
const state = await api.getUserState(teamDid);
|
|
107
|
+
|
|
108
|
+
const blocklet = await getBlocklet({ did: teamDid, states: api.states, dataDirs: api.dataDirs });
|
|
109
|
+
|
|
110
|
+
if (paging?.pageSize > MAX_USER_PAGE_SIZE) {
|
|
111
|
+
throw new Error(`Length of users should not exceed ${MAX_USER_PAGE_SIZE} per page`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!USER_RELATION_QUERIES[type]) {
|
|
115
|
+
throw new CustomError(400, `Invalid type: ${type}. Supported: ${Object.keys(USER_RELATION_QUERIES).join(', ')}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const { list, paging: resultPaging } = await USER_RELATION_QUERIES[type](
|
|
120
|
+
state,
|
|
121
|
+
userDid,
|
|
122
|
+
{
|
|
123
|
+
paging,
|
|
124
|
+
sort,
|
|
125
|
+
...options,
|
|
126
|
+
},
|
|
127
|
+
context
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
list.forEach((item) => {
|
|
131
|
+
const { user } = item;
|
|
132
|
+
if (user && user?.avatar && user?.avatar?.startsWith(USER_AVATAR_URL_PREFIX)) {
|
|
133
|
+
user.avatar = `${getFederatedUserAvatarUrl(user.avatar, blocklet)}?imageFilter=resize&w=48&h=48`;
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
data: list,
|
|
139
|
+
paging: resultPaging,
|
|
140
|
+
};
|
|
141
|
+
} catch (error) {
|
|
142
|
+
logger.error('Failed to get user follows', { error });
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get user follow stats
|
|
149
|
+
* @param {Object} api - TeamAPI instance
|
|
150
|
+
* @param {Object} params
|
|
151
|
+
* @param {string} params.teamDid - Team DID
|
|
152
|
+
* @param {Array} params.userDids - User DIDs
|
|
153
|
+
* @param {Object} params.options - Options
|
|
154
|
+
* @param {Object} context
|
|
155
|
+
* @returns {Promise<Object>}
|
|
156
|
+
*/
|
|
157
|
+
async function getUserFollowStats(api, { teamDid, userDids, options = {} }, context = {}) {
|
|
158
|
+
const state = await api.getUserState(teamDid);
|
|
159
|
+
const info = await api.node.read();
|
|
160
|
+
const prefix = process.env.NODE_ENV === 'production' ? info.routing.adminPath : '';
|
|
161
|
+
return state.getFollowStats({ userDids, teamDid, prefix, options }, context);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Check following
|
|
166
|
+
* @param {Object} api - TeamAPI instance
|
|
167
|
+
* @param {Object} params
|
|
168
|
+
* @param {string} params.teamDid - Team DID
|
|
169
|
+
* @param {Array} params.userDids - User DIDs
|
|
170
|
+
* @param {string} params.followerDid - Follower DID
|
|
171
|
+
* @returns {Promise<Object>}
|
|
172
|
+
*/
|
|
173
|
+
async function checkFollowing(api, { teamDid, userDids = [], followerDid }) {
|
|
174
|
+
if (!followerDid) {
|
|
175
|
+
throw new CustomError(400, 'Follower DID is required');
|
|
176
|
+
}
|
|
177
|
+
const state = await api.getUserState(teamDid);
|
|
178
|
+
return state.isFollowing(followerDid, userDids);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Get user invites
|
|
183
|
+
* @param {Object} api - TeamAPI instance
|
|
184
|
+
* @param {Object} params
|
|
185
|
+
* @param {string} params.teamDid - Team DID
|
|
186
|
+
* @param {string} params.userDid - User DID
|
|
187
|
+
* @param {Object} params.paging - Paging
|
|
188
|
+
* @param {Object} params.sort - Sort
|
|
189
|
+
* @param {Object} params.options - Options
|
|
190
|
+
* @param {Object} context
|
|
191
|
+
* @returns {Promise<Object>}
|
|
192
|
+
*/
|
|
193
|
+
async function getUserInvites(api, { teamDid, userDid, paging, sort, options = {} }, context = {}) {
|
|
194
|
+
try {
|
|
195
|
+
if (paging?.pageSize > MAX_USER_PAGE_SIZE) {
|
|
196
|
+
throw new CustomError(400, `Length of users should not exceed ${MAX_USER_PAGE_SIZE} per page`);
|
|
197
|
+
}
|
|
198
|
+
const state = await api.getUserState(teamDid);
|
|
199
|
+
const { users, paging: resultPaging } = await state.getInvitees(userDid, { paging, sort, ...options }, context);
|
|
200
|
+
// 处理头像
|
|
201
|
+
const blocklet = await getBlocklet({ did: teamDid, states: api.states, dataDirs: api.dataDirs });
|
|
202
|
+
users.forEach((user) => {
|
|
203
|
+
if (user && user?.avatar && user?.avatar?.startsWith(USER_AVATAR_URL_PREFIX)) {
|
|
204
|
+
user.avatar = `${getFederatedUserAvatarUrl(user.avatar, blocklet)}?imageFilter=resize&w=48&h=48`;
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
return {
|
|
208
|
+
users,
|
|
209
|
+
paging: resultPaging,
|
|
210
|
+
};
|
|
211
|
+
} catch (error) {
|
|
212
|
+
logger.error('Failed to get user invites', { teamDid, userDid, error });
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Create webhook disabled notification
|
|
219
|
+
* @param {Object} api - TeamAPI instance
|
|
220
|
+
* @param {Object} params
|
|
221
|
+
* @param {string} params.teamDid - Team DID
|
|
222
|
+
* @param {string} params.userDid - User DID
|
|
223
|
+
* @param {Object} params.webhook - Webhook
|
|
224
|
+
* @param {string} params.action - Action
|
|
225
|
+
* @param {string} params.locale - Locale
|
|
226
|
+
* @returns {Promise<void>}
|
|
227
|
+
*/
|
|
228
|
+
async function createWebhookDisabledNotification(api, { teamDid, userDid, webhook, action = '', locale = 'en' }) {
|
|
229
|
+
const notification = {
|
|
230
|
+
en: {
|
|
231
|
+
title: 'Your webhook has been temporarily disabled',
|
|
232
|
+
description: `Your Webhook has been temporarily disabled after ${WEBHOOK_CONSECUTIVE_FAILURES_THRESHOLD} consecutive failed message delivery attempts. Please check its availability and you can click the button below to reactivate it if needed.`,
|
|
233
|
+
failureCount: 'Number of failures',
|
|
234
|
+
failureUrl: 'Failed Webhook URL',
|
|
235
|
+
},
|
|
236
|
+
zh: {
|
|
237
|
+
title: '您的 webhook 已被暂时禁用',
|
|
238
|
+
description: `您的 Webhook 在连续 ${WEBHOOK_CONSECUTIVE_FAILURES_THRESHOLD} 次消息投递失败后已被暂时禁用。请检查其可用性,如果需要重新激活,请点击下方按钮。`,
|
|
239
|
+
failureCount: '失败次数',
|
|
240
|
+
failureUrl: '失败的 Webhook URL',
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
const nodeInfo = await api.node.read();
|
|
246
|
+
// server 的 webhook 页面地址
|
|
247
|
+
const actionPath = '/settings/integration';
|
|
248
|
+
let _action = process.env.NODE_ENV === 'production' ? joinURL(nodeInfo.routing.adminPath, actionPath) : actionPath;
|
|
249
|
+
const channels = [NOTIFICATION_SEND_CHANNEL.WALLET, NOTIFICATION_SEND_CHANNEL.PUSH];
|
|
250
|
+
if (teamDid && !api.teamManager.isNodeTeam(teamDid)) {
|
|
251
|
+
channels.push(NOTIFICATION_SEND_CHANNEL.EMAIL);
|
|
252
|
+
_action = action || `${WELLKNOWN_SERVICE_PATH_PREFIX}/user/settings`; // 默认返回用户中心页面设置页面
|
|
253
|
+
}
|
|
254
|
+
await api.teamManager.createNotification({
|
|
255
|
+
title: notification[locale].title,
|
|
256
|
+
description: notification[locale].description,
|
|
257
|
+
attachments: [
|
|
258
|
+
{
|
|
259
|
+
type: 'section',
|
|
260
|
+
fields: [
|
|
261
|
+
{
|
|
262
|
+
type: 'text',
|
|
263
|
+
data: {
|
|
264
|
+
type: 'plain',
|
|
265
|
+
color: '#9397A1',
|
|
266
|
+
text: notification[locale].failureUrl,
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
type: 'text',
|
|
271
|
+
data: {
|
|
272
|
+
type: 'plain',
|
|
273
|
+
color: '#9397A1',
|
|
274
|
+
text: webhook.url,
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
type: 'text',
|
|
279
|
+
data: {
|
|
280
|
+
type: 'plain',
|
|
281
|
+
color: '#9397A1',
|
|
282
|
+
text: notification[locale].failureCount,
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
type: 'text',
|
|
287
|
+
data: {
|
|
288
|
+
type: 'plain',
|
|
289
|
+
color: '#9397A1',
|
|
290
|
+
text: `${WEBHOOK_CONSECUTIVE_FAILURES_THRESHOLD}`,
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
],
|
|
294
|
+
},
|
|
295
|
+
],
|
|
296
|
+
source: 'system',
|
|
297
|
+
severity: 'warning',
|
|
298
|
+
action: _action,
|
|
299
|
+
channels,
|
|
300
|
+
...(teamDid ? { teamDid } : {}),
|
|
301
|
+
...(userDid ? { receiver: userDid } : {}),
|
|
302
|
+
});
|
|
303
|
+
} catch (error) {
|
|
304
|
+
logger.error('Failed to create webhook disabled notification', { teamDid, userDid, webhook, error });
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Update webhook state
|
|
310
|
+
* @param {Object} api - TeamAPI instance
|
|
311
|
+
* @param {Object} params
|
|
312
|
+
* @param {string} params.teamDid - Team DID
|
|
313
|
+
* @param {Array} params.userDids - User DIDs
|
|
314
|
+
* @param {Object} params.webhook - Webhook
|
|
315
|
+
* @param {boolean} params.enabled - Enabled
|
|
316
|
+
* @param {number} params.consecutiveFailures - Consecutive failures
|
|
317
|
+
* @returns {Promise<Array>}
|
|
318
|
+
*/
|
|
319
|
+
async function updateWebHookState(api, { teamDid, userDids, webhook, enabled, consecutiveFailures }) {
|
|
320
|
+
const state = await api.getUserState(teamDid);
|
|
321
|
+
return Promise.all(
|
|
322
|
+
userDids.map(async (userDid) => {
|
|
323
|
+
const webhookParams =
|
|
324
|
+
consecutiveFailures === undefined
|
|
325
|
+
? omit(
|
|
326
|
+
{
|
|
327
|
+
...webhook,
|
|
328
|
+
enabled: enabled ?? webhook.enabled,
|
|
329
|
+
},
|
|
330
|
+
['consecutiveFailures']
|
|
331
|
+
)
|
|
332
|
+
: {
|
|
333
|
+
...webhook,
|
|
334
|
+
enabled: enabled ?? webhook.enabled,
|
|
335
|
+
consecutiveFailures,
|
|
336
|
+
};
|
|
337
|
+
await state.updateWebhook(userDid, webhookParams, (updated) => {
|
|
338
|
+
createWebhookDisabledNotification(api, { teamDid, userDid, webhook: updated });
|
|
339
|
+
});
|
|
340
|
+
})
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
module.exports = {
|
|
345
|
+
USER_RELATION_ACTIONS,
|
|
346
|
+
USER_RELATION_QUERIES,
|
|
347
|
+
userFollowAction,
|
|
348
|
+
getUserFollows,
|
|
349
|
+
getUserFollowStats,
|
|
350
|
+
checkFollowing,
|
|
351
|
+
getUserInvites,
|
|
352
|
+
createWebhookDisabledNotification,
|
|
353
|
+
updateWebHookState,
|
|
354
|
+
};
|