@abtnode/core 1.16.49-beta-20250822-070545-6d3344cc → 1.16.49-beta-20250826-112154-8ca981fa
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.js +85 -1
- package/lib/blocklet/migration-dist/migration.cjs +1 -1
- package/lib/index.js +9 -0
- package/lib/router/index.js +9 -1
- package/lib/states/user.js +202 -0
- package/package.json +26 -26
package/lib/api/team.js
CHANGED
|
@@ -32,7 +32,12 @@ const {
|
|
|
32
32
|
} = require('@abtnode/auth/lib/passport');
|
|
33
33
|
const { formatError } = require('@blocklet/error');
|
|
34
34
|
const { getPassportStatusEndpoint, getApplicationInfo } = require('@abtnode/auth/lib/auth');
|
|
35
|
-
const {
|
|
35
|
+
const {
|
|
36
|
+
callFederated,
|
|
37
|
+
getFederatedMaster,
|
|
38
|
+
findFederatedSite,
|
|
39
|
+
getUserAvatarUrl: getFederatedUserAvatarUrl,
|
|
40
|
+
} = require('@abtnode/auth/lib/util/federated');
|
|
36
41
|
const { hasActiveOwnerPassport } = require('@abtnode/util/lib/passport');
|
|
37
42
|
const getBlockletInfo = require('@blocklet/meta/lib/info');
|
|
38
43
|
const { getUserAvatarUrl, getAppAvatarUrl, extractUserAvatar, getAvatarByUrl } = require('@abtnode/util/lib/user');
|
|
@@ -43,6 +48,7 @@ const pMap = require('p-map');
|
|
|
43
48
|
const isUrl = require('is-url');
|
|
44
49
|
const { PASSPORT_LOG_ACTION, PASSPORT_SOURCE, PASSPORT_ISSUE_ACTION } = require('@abtnode/constant');
|
|
45
50
|
const ensureServerEndpoint = require('@abtnode/util/lib/ensure-server-endpoint');
|
|
51
|
+
const { CustomError } = require('@blocklet/error');
|
|
46
52
|
|
|
47
53
|
const { validateTrustedPassportIssuers } = require('../validators/trusted-passport');
|
|
48
54
|
const { validateTrustedFactories } = require('../validators/trusted-factory');
|
|
@@ -180,6 +186,19 @@ const getUserSessionWhere = ({ status, blocklet }) => {
|
|
|
180
186
|
return {};
|
|
181
187
|
};
|
|
182
188
|
|
|
189
|
+
// 关注取消关注相关接口
|
|
190
|
+
const USER_RELATION_ACTIONS = {
|
|
191
|
+
follow: (state, followerDid, userDid) => state.followUser(followerDid, userDid),
|
|
192
|
+
unfollow: (state, followerDid, userDid) => state.unfollowUser(followerDid, userDid),
|
|
193
|
+
isFollowing: (state, followerDid, userDid) => state.isFollowing(followerDid, userDid),
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// 获取用户相关接口
|
|
197
|
+
const USER_RELATION_QUERIES = {
|
|
198
|
+
followers: (state, userDid, options, context) => state.getFollowers(userDid, options, context),
|
|
199
|
+
following: (state, userDid, options, context) => state.getFollowing(userDid, options, context),
|
|
200
|
+
};
|
|
201
|
+
|
|
183
202
|
class TeamAPI extends EventEmitter {
|
|
184
203
|
/**
|
|
185
204
|
*
|
|
@@ -571,6 +590,71 @@ class TeamAPI extends EventEmitter {
|
|
|
571
590
|
return full || owner;
|
|
572
591
|
}
|
|
573
592
|
|
|
593
|
+
async userFollowAction({ teamDid, userDid, action = 'follow' }, context = {}) {
|
|
594
|
+
const { userDid: followerDid } = context;
|
|
595
|
+
|
|
596
|
+
if (!USER_RELATION_ACTIONS[action]) {
|
|
597
|
+
throw new CustomError(
|
|
598
|
+
400,
|
|
599
|
+
`Invalid action: ${action}. Supported: ${Object.keys(USER_RELATION_ACTIONS).join(', ')}`
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const state = await this.getUserState(teamDid);
|
|
604
|
+
return USER_RELATION_ACTIONS[action](state, followerDid, userDid);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
async getUserFollows({ teamDid, userDid, type = 'following', paging, sort }, context = {}) {
|
|
608
|
+
const state = await this.getUserState(teamDid);
|
|
609
|
+
|
|
610
|
+
const blocklet = await getBlocklet({ did: teamDid, states: this.states, dataDirs: this.dataDirs });
|
|
611
|
+
|
|
612
|
+
if (paging?.pageSize > MAX_USER_PAGE_SIZE) {
|
|
613
|
+
throw new Error(`Length of users should not exceed ${MAX_USER_PAGE_SIZE} per page`);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
if (!USER_RELATION_QUERIES[type]) {
|
|
617
|
+
throw new CustomError(400, `Invalid type: ${type}. Supported: ${Object.keys(USER_RELATION_QUERIES).join(', ')}`);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
try {
|
|
621
|
+
const { list, paging: resultPaging } = await USER_RELATION_QUERIES[type](
|
|
622
|
+
state,
|
|
623
|
+
userDid,
|
|
624
|
+
{
|
|
625
|
+
paging,
|
|
626
|
+
sort,
|
|
627
|
+
},
|
|
628
|
+
context
|
|
629
|
+
);
|
|
630
|
+
|
|
631
|
+
list.forEach((item) => {
|
|
632
|
+
const { user } = item;
|
|
633
|
+
if (user?.avatar && user?.avatar?.startsWith(USER_AVATAR_URL_PREFIX)) {
|
|
634
|
+
user.avatar = `${getFederatedUserAvatarUrl(user.avatar, blocklet)}?imageFilter=resize&w=48&h=48`;
|
|
635
|
+
}
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
return {
|
|
639
|
+
data: list,
|
|
640
|
+
paging: resultPaging,
|
|
641
|
+
};
|
|
642
|
+
} catch (error) {
|
|
643
|
+
logger.error('Failed to get user follows', { error });
|
|
644
|
+
throw error;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
async getUserFollowStats({ teamDid, userDid }) {
|
|
649
|
+
const state = await this.getUserState(teamDid);
|
|
650
|
+
try {
|
|
651
|
+
return state.getFollowStats(userDid);
|
|
652
|
+
} catch (error) {
|
|
653
|
+
logger.error('Failed to get follow stats', { error });
|
|
654
|
+
throw error;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
574
658
|
async updateUser({ teamDid, user }) {
|
|
575
659
|
const state = await this.getUserState(teamDid);
|
|
576
660
|
const exist = await state.getUserByDid(user.did);
|
|
@@ -38923,7 +38923,7 @@ module.exports = require("zlib");
|
|
|
38923
38923
|
/***/ ((module) => {
|
|
38924
38924
|
|
|
38925
38925
|
"use strict";
|
|
38926
|
-
module.exports = /*#__PURE__*/JSON.parse('{"name":"@abtnode/core","publishConfig":{"access":"public"},"version":"1.16.48","description":"","main":"lib/index.js","files":["lib"],"scripts":{"lint":"eslint tests lib --ignore-pattern \'tests/assets/*\'","lint:fix":"eslint --fix tests lib","test":"node tools/jest.js","coverage":"npm run test -- --coverage"},"keywords":[],"author":"wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)","license":"Apache-2.0","dependencies":{"@abtnode/analytics":"1.16.48","@abtnode/auth":"1.16.48","@abtnode/certificate-manager":"1.16.48","@abtnode/constant":"1.16.48","@abtnode/cron":"1.16.48","@abtnode/db-cache":"1.16.48","@abtnode/docker-utils":"1.16.48","@abtnode/logger":"1.16.48","@abtnode/models":"1.16.48","@abtnode/queue":"1.16.48","@abtnode/rbac":"1.16.48","@abtnode/router-provider":"1.16.48","@abtnode/static-server":"1.16.48","@abtnode/timemachine":"1.16.48","@abtnode/util":"1.16.48","@aigne/aigne-hub":"^0.6.
|
|
38926
|
+
module.exports = /*#__PURE__*/JSON.parse('{"name":"@abtnode/core","publishConfig":{"access":"public"},"version":"1.16.48","description":"","main":"lib/index.js","files":["lib"],"scripts":{"lint":"eslint tests lib --ignore-pattern \'tests/assets/*\'","lint:fix":"eslint --fix tests lib","test":"node tools/jest.js","coverage":"npm run test -- --coverage"},"keywords":[],"author":"wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)","license":"Apache-2.0","dependencies":{"@abtnode/analytics":"1.16.48","@abtnode/auth":"1.16.48","@abtnode/certificate-manager":"1.16.48","@abtnode/constant":"1.16.48","@abtnode/cron":"1.16.48","@abtnode/db-cache":"1.16.48","@abtnode/docker-utils":"1.16.48","@abtnode/logger":"1.16.48","@abtnode/models":"1.16.48","@abtnode/queue":"1.16.48","@abtnode/rbac":"1.16.48","@abtnode/router-provider":"1.16.48","@abtnode/static-server":"1.16.48","@abtnode/timemachine":"1.16.48","@abtnode/util":"1.16.48","@aigne/aigne-hub":"^0.6.8","@arcblock/did":"1.22.2","@arcblock/did-connect-js":"1.22.2","@arcblock/did-ext":"1.22.2","@arcblock/did-motif":"^1.1.14","@arcblock/did-util":"1.22.2","@arcblock/event-hub":"1.22.2","@arcblock/jwt":"1.22.2","@arcblock/pm2-events":"^0.0.5","@arcblock/validator":"1.22.2","@arcblock/vc":"1.22.2","@blocklet/constant":"1.16.48","@blocklet/did-space-js":"^1.1.18","@blocklet/env":"1.16.48","@blocklet/error":"^0.2.5","@blocklet/meta":"1.16.48","@blocklet/resolver":"1.16.48","@blocklet/sdk":"1.16.48","@blocklet/server-js":"1.16.48","@blocklet/store":"1.16.48","@blocklet/theme":"^3.1.27","@fidm/x509":"^1.2.1","@ocap/mcrypto":"1.22.2","@ocap/util":"1.22.2","@ocap/wallet":"1.22.2","@slack/webhook":"^5.0.4","archiver":"^7.0.1","axios":"^1.7.9","axon":"^2.0.3","chalk":"^4.1.2","cross-spawn":"^7.0.3","dayjs":"^1.11.13","deep-diff":"^1.0.2","detect-port":"^1.5.1","envfile":"^7.1.0","escape-string-regexp":"^4.0.0","fast-glob":"^3.3.2","filesize":"^10.1.1","flat":"^5.0.2","fs-extra":"^11.2.0","get-port":"^5.1.1","hasha":"^5.2.2","is-base64":"^1.1.0","is-cidr":"4","is-ip":"3","is-url":"^1.2.4","joi":"17.12.2","joi-extension-semver":"^5.0.0","js-yaml":"^4.1.0","kill-port":"^2.0.1","lodash":"^4.17.21","node-stream-zip":"^1.15.0","p-all":"^3.0.0","p-limit":"^3.1.0","p-map":"^4.0.0","p-retry":"^4.6.2","p-wait-for":"^3.2.0","private-ip":"^2.3.4","rate-limiter-flexible":"^5.0.5","read-last-lines":"^1.8.0","semver":"^7.6.3","sequelize":"^6.35.0","shelljs":"^0.8.5","slugify":"^1.6.6","ssri":"^8.0.1","stream-throttle":"^0.1.3","stream-to-promise":"^3.0.0","systeminformation":"^5.23.3","tail":"^2.2.4","tar":"^6.1.11","transliteration":"^2.3.5","ua-parser-js":"^1.0.2","ufo":"^1.5.3","uuid":"^11.1.0","valid-url":"^1.0.9","which":"^2.0.2","xbytes":"^1.8.0"},"devDependencies":{"expand-tilde":"^2.0.2","express":"^4.18.2","jest":"^29.7.0","unzipper":"^0.10.11"},"gitHead":"e5764f753181ed6a7c615cd4fc6682aacf0cb7cd"}');
|
|
38927
38927
|
|
|
38928
38928
|
/***/ }),
|
|
38929
38929
|
|
package/lib/index.js
CHANGED
|
@@ -533,6 +533,15 @@ function ABTNode(options) {
|
|
|
533
533
|
getPassportById: teamAPI.getPassportById.bind(teamAPI),
|
|
534
534
|
getPassportFromFederated: teamAPI.getPassportFromFederated.bind(teamAPI),
|
|
535
535
|
|
|
536
|
+
// user follow
|
|
537
|
+
followUser: (params, context) => teamAPI.userFollowAction({ ...params, action: 'follow' }, context),
|
|
538
|
+
unfollowUser: (params, context) => teamAPI.userFollowAction({ ...params, action: 'unfollow' }, context),
|
|
539
|
+
isFollowing: (params, context) => teamAPI.userFollowAction({ ...params, action: 'isFollowing' }, context),
|
|
540
|
+
|
|
541
|
+
getUserFollowers: (params, context) => teamAPI.getUserFollows({ ...params, type: 'followers' }, context),
|
|
542
|
+
getUserFollowing: (params, context) => teamAPI.getUserFollows({ ...params, type: 'following' }, context),
|
|
543
|
+
getUserFollowStats: teamAPI.getUserFollowStats.bind(teamAPI),
|
|
544
|
+
|
|
536
545
|
// Tagging
|
|
537
546
|
createTag: teamAPI.createTag.bind(teamAPI),
|
|
538
547
|
updateTag: teamAPI.updateTag.bind(teamAPI),
|
package/lib/router/index.js
CHANGED
|
@@ -311,7 +311,7 @@ Router.formatSites = (sites = []) => {
|
|
|
311
311
|
const rules = Array.isArray(site.rules) ? cloneDeep(site.rules) : [];
|
|
312
312
|
const grouped = {};
|
|
313
313
|
|
|
314
|
-
// 0. serve robots.txt/sitemap.xml from root and proxy to daemon
|
|
314
|
+
// 0. serve robots.txt/sitemap.xml and favicon.ico from root and proxy to daemon
|
|
315
315
|
if (isBlockletSite(site.domain) && !site.rules.find((x) => x.from.pathPrefix === '/robots.txt')) {
|
|
316
316
|
site.rules.push({
|
|
317
317
|
dynamic: true,
|
|
@@ -329,6 +329,14 @@ Router.formatSites = (sites = []) => {
|
|
|
329
329
|
});
|
|
330
330
|
}
|
|
331
331
|
|
|
332
|
+
if (isBlockletSite(site.domain) && !site.rules.find((x) => x.from.pathPrefix === '/favicon.ico')) {
|
|
333
|
+
site.rules.push({
|
|
334
|
+
dynamic: true,
|
|
335
|
+
from: { root: true, pathPrefix: '/', groupPathPrefix: '/', pathSuffix: '/favicon.ico' },
|
|
336
|
+
to: { type: ROUTING_RULE_TYPES.SERVICE, port: process.env.ABT_NODE_SERVICE_PORT, did: site.blockletDid },
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
332
340
|
// 1. serve blocklet.js for each component: both prefix and suffix do not contain trailing slash
|
|
333
341
|
rules.forEach((rule) => {
|
|
334
342
|
if ([ROUTING_RULE_TYPES.BLOCKLET].includes(rule.to.type) === false) {
|
package/lib/states/user.js
CHANGED
|
@@ -10,6 +10,7 @@ const { Sequelize, Op } = require('sequelize');
|
|
|
10
10
|
const { updateConnectedAccount } = require('@abtnode/util/lib/user');
|
|
11
11
|
const { LOGIN_PROVIDER } = require('@blocklet/constant');
|
|
12
12
|
const { CustomError } = require('@blocklet/error');
|
|
13
|
+
const { Joi } = require('@arcblock/validator');
|
|
13
14
|
|
|
14
15
|
const logger = require('@abtnode/logger')('@abtnode/core:states:user');
|
|
15
16
|
|
|
@@ -19,6 +20,12 @@ const ExtendBase = require('./base');
|
|
|
19
20
|
|
|
20
21
|
const isNullOrUndefined = (x) => x === undefined || x === null;
|
|
21
22
|
|
|
23
|
+
const checkUserFollowers = (model) => {
|
|
24
|
+
if (!model) {
|
|
25
|
+
throw new CustomError(400, 'User follow feature is not available in this environment');
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
22
29
|
/**
|
|
23
30
|
* Auth0Account
|
|
24
31
|
* @typedef {Object} OAuthAccount
|
|
@@ -67,6 +74,9 @@ class User extends ExtendBase {
|
|
|
67
74
|
this.connectedAccount = new BaseState(models.ConnectedAccount, config);
|
|
68
75
|
this.userSession = new BaseState(models.UserSession, config);
|
|
69
76
|
this.passportLog = new BaseState(models.PassportLog, config);
|
|
77
|
+
if (models.UserFollowers) {
|
|
78
|
+
this.userFollowers = new BaseState(models.UserFollowers, config);
|
|
79
|
+
}
|
|
70
80
|
}
|
|
71
81
|
|
|
72
82
|
// FIXME: @wangshijun wrap these in a transaction
|
|
@@ -817,6 +827,198 @@ SELECT did,inviter,generation FROM UserTree`.trim();
|
|
|
817
827
|
const result = await this.find({ where: condition, replacements, include }, selection);
|
|
818
828
|
return result;
|
|
819
829
|
}
|
|
830
|
+
|
|
831
|
+
/**
|
|
832
|
+
* 验证用户是否存在
|
|
833
|
+
* @private
|
|
834
|
+
*/
|
|
835
|
+
async _validateUsersExist(dids) {
|
|
836
|
+
// 验证 did 是否合法
|
|
837
|
+
dids.forEach((did) => {
|
|
838
|
+
const { error } = Joi.DID().validate(did);
|
|
839
|
+
if (error) {
|
|
840
|
+
throw new CustomError(400, `Invalid did: ${did}`);
|
|
841
|
+
}
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
const users = await Promise.all(dids.map((did) => this.getUserByDid(did)));
|
|
845
|
+
dids.forEach((did, index) => {
|
|
846
|
+
if (!users[index]) {
|
|
847
|
+
throw new CustomError(404, `User does not exist: ${did}`);
|
|
848
|
+
}
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/**
|
|
853
|
+
* 关注用户
|
|
854
|
+
* @param {string} followerDid - 关注者的 DID
|
|
855
|
+
* @param {string} userDid - 被关注用户的 DID
|
|
856
|
+
*/
|
|
857
|
+
async followUser(followerDid, userDid) {
|
|
858
|
+
checkUserFollowers(this.userFollowers);
|
|
859
|
+
|
|
860
|
+
if (followerDid === userDid) {
|
|
861
|
+
throw new CustomError(400, 'Cannot follow yourself');
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// 检查两个用户是否存在 + 检查是否已经关注(并行执行)
|
|
865
|
+
const [, existingFollow] = await Promise.all([
|
|
866
|
+
this._validateUsersExist([followerDid, userDid]),
|
|
867
|
+
this.userFollowers.findOne({ userDid, followerDid }),
|
|
868
|
+
]);
|
|
869
|
+
|
|
870
|
+
if (existingFollow) {
|
|
871
|
+
throw new CustomError(400, 'Already following this user');
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// 创建关注关系
|
|
875
|
+
return this.userFollowers.insert({
|
|
876
|
+
userDid,
|
|
877
|
+
followerDid,
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* 取消关注用户
|
|
883
|
+
* @param {string} followerDid - 关注者的 DID
|
|
884
|
+
* @param {string} userDid - 被关注用户的 DID
|
|
885
|
+
*/
|
|
886
|
+
unfollowUser(followerDid, userDid) {
|
|
887
|
+
checkUserFollowers(this.userFollowers);
|
|
888
|
+
|
|
889
|
+
// 删除关注关系
|
|
890
|
+
return this.userFollowers.remove({
|
|
891
|
+
userDid,
|
|
892
|
+
followerDid,
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
/**
|
|
897
|
+
* 通用的关注关系查询函数
|
|
898
|
+
* @private
|
|
899
|
+
*/
|
|
900
|
+
async _getFollowRelations(targetDid, type, { paging, sort, includeUserInfo = true } = {}, context = {}) {
|
|
901
|
+
checkUserFollowers(this.userFollowers);
|
|
902
|
+
|
|
903
|
+
const { user: contextUser } = context;
|
|
904
|
+
|
|
905
|
+
await this._validateUsersExist([targetDid]);
|
|
906
|
+
|
|
907
|
+
const isFollowers = type === 'followers';
|
|
908
|
+
const where = isFollowers ? { userDid: targetDid } : { followerDid: targetDid };
|
|
909
|
+
|
|
910
|
+
const order = sort?.createdAt === -1 ? 'DESC' : 'ASC';
|
|
911
|
+
const sorting = [['createdAt', sort?.createdAt ? order : 'DESC']];
|
|
912
|
+
const result = await this.userFollowers.paginate({ where }, sorting, paging);
|
|
913
|
+
|
|
914
|
+
// 如果需要包含用户信息,手动查询用户数据
|
|
915
|
+
if (includeUserInfo && this.models.User) {
|
|
916
|
+
const userDids = [...new Set(result.list.map((follow) => (isFollowers ? follow.followerDid : follow.userDid)))];
|
|
917
|
+
const users = await this.find({
|
|
918
|
+
where: { did: { [Op.in]: userDids } },
|
|
919
|
+
attributes: ['did', 'fullName', 'avatar', 'email'],
|
|
920
|
+
});
|
|
921
|
+
const userMap = new Map(users.map((user) => [user.did, user]));
|
|
922
|
+
|
|
923
|
+
const enrichedList = await Promise.all(
|
|
924
|
+
result.list.map(async (follow) => {
|
|
925
|
+
const userDid = isFollowers ? follow.followerDid : follow.userDid;
|
|
926
|
+
const user = userMap.get(userDid);
|
|
927
|
+
const enrichedFollow = {
|
|
928
|
+
...follow,
|
|
929
|
+
user: user ? { ...user } : null,
|
|
930
|
+
};
|
|
931
|
+
|
|
932
|
+
if (contextUser?.did && contextUser.did !== targetDid) {
|
|
933
|
+
// 如果查看的是别人的关注列表,需要添加是否已关注的信息
|
|
934
|
+
const isFollowing = await this.isFollowing(contextUser.did, user.did);
|
|
935
|
+
enrichedFollow.isFollowing = isFollowing;
|
|
936
|
+
} else if (isFollowers) {
|
|
937
|
+
// 如果是查询粉丝,需要添加是否已关注的信息
|
|
938
|
+
const isFollowing = await this.isFollowing(targetDid, follow.followerDid);
|
|
939
|
+
enrichedFollow.isFollowing = isFollowing;
|
|
940
|
+
} else {
|
|
941
|
+
enrichedFollow.isFollowing = true;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
return enrichedFollow;
|
|
945
|
+
})
|
|
946
|
+
);
|
|
947
|
+
|
|
948
|
+
result.list = enrichedList;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
if (!includeUserInfo) {
|
|
952
|
+
result.list = result.list.map((follow) => (isFollowers ? follow.followerDid : follow.userDid));
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
return result;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
/**
|
|
959
|
+
* 获取用户的粉丝列表(谁关注了这个用户)
|
|
960
|
+
*/
|
|
961
|
+
getFollowers(userDid, options = {}, context = {}) {
|
|
962
|
+
return this._getFollowRelations(userDid, 'followers', options, context);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
/**
|
|
966
|
+
* 获取用户关注的人列表(这个用户关注了谁)
|
|
967
|
+
*/
|
|
968
|
+
getFollowing(followerDid, options = {}, context = {}) {
|
|
969
|
+
return this._getFollowRelations(followerDid, 'following', options, context);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* 检查用户A是否关注了用户B
|
|
974
|
+
* @param {string} followerDid - 关注者的 DID
|
|
975
|
+
* @param {string} userDid - 被关注用户的 DID
|
|
976
|
+
*/
|
|
977
|
+
async isFollowing(followerDid, userDid) {
|
|
978
|
+
if (!this.userFollowers) {
|
|
979
|
+
return false;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
try {
|
|
983
|
+
const follow = await this.userFollowers.findOne({
|
|
984
|
+
userDid,
|
|
985
|
+
followerDid,
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
return !!follow;
|
|
989
|
+
} catch (error) {
|
|
990
|
+
logger.error('Failed to check following status:', error);
|
|
991
|
+
return false;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
/**
|
|
996
|
+
* 获取用户的关注统计信息
|
|
997
|
+
* @param {string} userDid - 用户的 DID
|
|
998
|
+
*/
|
|
999
|
+
async getFollowStats(userDid) {
|
|
1000
|
+
if (!this.userFollowers) {
|
|
1001
|
+
return {
|
|
1002
|
+
followers: 0,
|
|
1003
|
+
following: 0,
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
try {
|
|
1008
|
+
const [followers, following] = await Promise.all([
|
|
1009
|
+
this.userFollowers.count({ userDid }), // 粉丝数
|
|
1010
|
+
this.userFollowers.count({ followerDid: userDid }), // 关注数
|
|
1011
|
+
]);
|
|
1012
|
+
|
|
1013
|
+
return {
|
|
1014
|
+
followers,
|
|
1015
|
+
following,
|
|
1016
|
+
};
|
|
1017
|
+
} catch (error) {
|
|
1018
|
+
logger.error('Failed to get follow stats:', error);
|
|
1019
|
+
return { followers: 0, following: 0 };
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
820
1022
|
}
|
|
821
1023
|
|
|
822
1024
|
module.exports = User;
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.16.49-beta-
|
|
6
|
+
"version": "1.16.49-beta-20250826-112154-8ca981fa",
|
|
7
7
|
"description": "",
|
|
8
8
|
"main": "lib/index.js",
|
|
9
9
|
"files": [
|
|
@@ -19,22 +19,22 @@
|
|
|
19
19
|
"author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
|
|
20
20
|
"license": "Apache-2.0",
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@abtnode/analytics": "1.16.49-beta-
|
|
23
|
-
"@abtnode/auth": "1.16.49-beta-
|
|
24
|
-
"@abtnode/certificate-manager": "1.16.49-beta-
|
|
25
|
-
"@abtnode/constant": "1.16.49-beta-
|
|
26
|
-
"@abtnode/cron": "1.16.49-beta-
|
|
27
|
-
"@abtnode/db-cache": "1.16.49-beta-
|
|
28
|
-
"@abtnode/docker-utils": "1.16.49-beta-
|
|
29
|
-
"@abtnode/logger": "1.16.49-beta-
|
|
30
|
-
"@abtnode/models": "1.16.49-beta-
|
|
31
|
-
"@abtnode/queue": "1.16.49-beta-
|
|
32
|
-
"@abtnode/rbac": "1.16.49-beta-
|
|
33
|
-
"@abtnode/router-provider": "1.16.49-beta-
|
|
34
|
-
"@abtnode/static-server": "1.16.49-beta-
|
|
35
|
-
"@abtnode/timemachine": "1.16.49-beta-
|
|
36
|
-
"@abtnode/util": "1.16.49-beta-
|
|
37
|
-
"@aigne/aigne-hub": "^0.6.
|
|
22
|
+
"@abtnode/analytics": "1.16.49-beta-20250826-112154-8ca981fa",
|
|
23
|
+
"@abtnode/auth": "1.16.49-beta-20250826-112154-8ca981fa",
|
|
24
|
+
"@abtnode/certificate-manager": "1.16.49-beta-20250826-112154-8ca981fa",
|
|
25
|
+
"@abtnode/constant": "1.16.49-beta-20250826-112154-8ca981fa",
|
|
26
|
+
"@abtnode/cron": "1.16.49-beta-20250826-112154-8ca981fa",
|
|
27
|
+
"@abtnode/db-cache": "1.16.49-beta-20250826-112154-8ca981fa",
|
|
28
|
+
"@abtnode/docker-utils": "1.16.49-beta-20250826-112154-8ca981fa",
|
|
29
|
+
"@abtnode/logger": "1.16.49-beta-20250826-112154-8ca981fa",
|
|
30
|
+
"@abtnode/models": "1.16.49-beta-20250826-112154-8ca981fa",
|
|
31
|
+
"@abtnode/queue": "1.16.49-beta-20250826-112154-8ca981fa",
|
|
32
|
+
"@abtnode/rbac": "1.16.49-beta-20250826-112154-8ca981fa",
|
|
33
|
+
"@abtnode/router-provider": "1.16.49-beta-20250826-112154-8ca981fa",
|
|
34
|
+
"@abtnode/static-server": "1.16.49-beta-20250826-112154-8ca981fa",
|
|
35
|
+
"@abtnode/timemachine": "1.16.49-beta-20250826-112154-8ca981fa",
|
|
36
|
+
"@abtnode/util": "1.16.49-beta-20250826-112154-8ca981fa",
|
|
37
|
+
"@aigne/aigne-hub": "^0.6.8",
|
|
38
38
|
"@arcblock/did": "1.22.2",
|
|
39
39
|
"@arcblock/did-connect-js": "1.22.2",
|
|
40
40
|
"@arcblock/did-ext": "1.22.2",
|
|
@@ -45,16 +45,16 @@
|
|
|
45
45
|
"@arcblock/pm2-events": "^0.0.5",
|
|
46
46
|
"@arcblock/validator": "1.22.2",
|
|
47
47
|
"@arcblock/vc": "1.22.2",
|
|
48
|
-
"@blocklet/constant": "1.16.49-beta-
|
|
48
|
+
"@blocklet/constant": "1.16.49-beta-20250826-112154-8ca981fa",
|
|
49
49
|
"@blocklet/did-space-js": "^1.1.18",
|
|
50
|
-
"@blocklet/env": "1.16.49-beta-
|
|
50
|
+
"@blocklet/env": "1.16.49-beta-20250826-112154-8ca981fa",
|
|
51
51
|
"@blocklet/error": "^0.2.5",
|
|
52
|
-
"@blocklet/meta": "1.16.49-beta-
|
|
53
|
-
"@blocklet/resolver": "1.16.49-beta-
|
|
54
|
-
"@blocklet/sdk": "1.16.49-beta-
|
|
55
|
-
"@blocklet/server-js": "1.16.49-beta-
|
|
56
|
-
"@blocklet/store": "1.16.49-beta-
|
|
57
|
-
"@blocklet/theme": "^3.1.
|
|
52
|
+
"@blocklet/meta": "1.16.49-beta-20250826-112154-8ca981fa",
|
|
53
|
+
"@blocklet/resolver": "1.16.49-beta-20250826-112154-8ca981fa",
|
|
54
|
+
"@blocklet/sdk": "1.16.49-beta-20250826-112154-8ca981fa",
|
|
55
|
+
"@blocklet/server-js": "1.16.49-beta-20250826-112154-8ca981fa",
|
|
56
|
+
"@blocklet/store": "1.16.49-beta-20250826-112154-8ca981fa",
|
|
57
|
+
"@blocklet/theme": "^3.1.27",
|
|
58
58
|
"@fidm/x509": "^1.2.1",
|
|
59
59
|
"@ocap/mcrypto": "1.22.2",
|
|
60
60
|
"@ocap/util": "1.22.2",
|
|
@@ -118,5 +118,5 @@
|
|
|
118
118
|
"jest": "^29.7.0",
|
|
119
119
|
"unzipper": "^0.10.11"
|
|
120
120
|
},
|
|
121
|
-
"gitHead": "
|
|
121
|
+
"gitHead": "f31434546e024c4ce20a1d3c0d34b64f12980536"
|
|
122
122
|
}
|