@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 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 { callFederated, getFederatedMaster, findFederatedSite } = require('@abtnode/auth/lib/util/federated');
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.7","@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/server-js":"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/store":"1.16.48","@blocklet/theme":"^3.1.25","@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"}');
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),
@@ -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) {
@@ -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-20250822-070545-6d3344cc",
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-20250822-070545-6d3344cc",
23
- "@abtnode/auth": "1.16.49-beta-20250822-070545-6d3344cc",
24
- "@abtnode/certificate-manager": "1.16.49-beta-20250822-070545-6d3344cc",
25
- "@abtnode/constant": "1.16.49-beta-20250822-070545-6d3344cc",
26
- "@abtnode/cron": "1.16.49-beta-20250822-070545-6d3344cc",
27
- "@abtnode/db-cache": "1.16.49-beta-20250822-070545-6d3344cc",
28
- "@abtnode/docker-utils": "1.16.49-beta-20250822-070545-6d3344cc",
29
- "@abtnode/logger": "1.16.49-beta-20250822-070545-6d3344cc",
30
- "@abtnode/models": "1.16.49-beta-20250822-070545-6d3344cc",
31
- "@abtnode/queue": "1.16.49-beta-20250822-070545-6d3344cc",
32
- "@abtnode/rbac": "1.16.49-beta-20250822-070545-6d3344cc",
33
- "@abtnode/router-provider": "1.16.49-beta-20250822-070545-6d3344cc",
34
- "@abtnode/static-server": "1.16.49-beta-20250822-070545-6d3344cc",
35
- "@abtnode/timemachine": "1.16.49-beta-20250822-070545-6d3344cc",
36
- "@abtnode/util": "1.16.49-beta-20250822-070545-6d3344cc",
37
- "@aigne/aigne-hub": "^0.6.7",
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-20250822-070545-6d3344cc",
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-20250822-070545-6d3344cc",
50
+ "@blocklet/env": "1.16.49-beta-20250826-112154-8ca981fa",
51
51
  "@blocklet/error": "^0.2.5",
52
- "@blocklet/meta": "1.16.49-beta-20250822-070545-6d3344cc",
53
- "@blocklet/resolver": "1.16.49-beta-20250822-070545-6d3344cc",
54
- "@blocklet/sdk": "1.16.49-beta-20250822-070545-6d3344cc",
55
- "@blocklet/server-js": "1.16.49-beta-20250822-070545-6d3344cc",
56
- "@blocklet/store": "1.16.49-beta-20250822-070545-6d3344cc",
57
- "@blocklet/theme": "^3.1.25",
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": "c5560fe749fe495b03003ac7325da29c71f5e62b"
121
+ "gitHead": "f31434546e024c4ce20a1d3c0d34b64f12980536"
122
122
  }