• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

cofacts / rumors-api / 13469918730

22 Feb 2025 05:16AM UTC coverage: 81.521%. First build
13469918730

push

github

MrOrz
Merge remote-tracking branch 'origin/badge-api' into merged

779 of 1009 branches covered (77.21%)

Branch coverage included in aggregate %.

17 of 45 new or added lines in 3 files covered. (37.78%)

1515 of 1805 relevant lines covered (83.93%)

18.46 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

61.18
/src/graphql/models/User.js
1
import {
2
  GraphQLObjectType,
3
  GraphQLString,
4
  GraphQLInt,
5
  GraphQLNonNull,
6
  GraphQLList,
7
  GraphQLID,
8
} from 'graphql';
9
import {
10
  AvatarTypes,
11
  getAvailableAvatarTypes,
12
  getUserId,
13
  avatarUrlResolver,
14
} from 'util/user';
15
import AvatarTypeEnum from './AvatarTypeEnum';
16
import Contribution from './Contribution';
17
import Node from '../interfaces/Node';
18
import { timeRangeInput, createConnectionType } from 'graphql/util';
19
import UserAwardedBadge from './UserAwardedBadge';
20

21
/**
22
 * Field config helper for current user only field.
23
 * Resolves to null if root object is not current user. Otherwise, invoke provided resolver.
24
 *
25
 * @param {GraphQLScalarType | GraphQLObjectType} type
26
 * @param {function?} resolver - Use default resolver if not given.
27
 */
28
export const currentUserOnlyField = (type, resolver) => ({
192✔
29
  type,
30
  description: 'Returns only for current user. Returns `null` otherwise.',
31
  resolve(user, arg, context, info) {
32
    if (!context.user || user.id !== context.user.id) return null;
15✔
33

34
    return resolver ? resolver(user, arg, context, info) : user[info.fieldName];
8✔
35
  },
36
});
37

38
const User = new GraphQLObjectType({
38✔
39
  name: 'User',
40
  interfaces: [Node],
41
  fields: () => ({
31✔
42
    id: { type: new GraphQLNonNull(GraphQLID) },
43
    slug: { type: GraphQLString },
44
    email: currentUserOnlyField(GraphQLString),
45
    name: { type: GraphQLString },
46
    bio: { type: GraphQLString },
47

48
    avatarUrl: {
49
      type: GraphQLString,
50
      description: 'returns avatar url from facebook, github or gravatar',
51
      resolve: avatarUrlResolver(),
52
    },
53
    avatarData: {
54
      type: GraphQLString,
55
      description:
56
        'return avatar data as JSON string, currently only used when avatarType is OpenPeeps',
57
    },
58
    avatarType: {
59
      type: AvatarTypeEnum,
60
      resolver(user) {
61
        return user?.avatarType ?? AvatarTypes.Gravatar;
×
62
      },
63
    },
64

65
    availableAvatarTypes: currentUserOnlyField(
66
      new GraphQLList(GraphQLString),
67
      (user) => getAvailableAvatarTypes(user)
2✔
68
    ),
69

70
    appId: { type: GraphQLString },
71
    appUserId: currentUserOnlyField(GraphQLString),
72

73
    facebookId: currentUserOnlyField(GraphQLString),
74
    githubId: currentUserOnlyField(GraphQLString),
75
    twitterId: currentUserOnlyField(GraphQLString),
76
    repliedArticleCount: {
77
      type: GraphQLNonNull(GraphQLInt),
78
      description: 'Number of articles this user has replied to',
79
      resolve: (user, args, context) =>
80
        context.loaders.repliedArticleCountLoader
2✔
81
          .load(user.id)
82
          .then((num) => num || 0),
2✔
83
    },
84
    votedArticleReplyCount: {
85
      type: GraphQLNonNull(GraphQLInt),
86
      description: 'Number of article replies this user has given feedbacks',
87
      resolve: (user, args, context) =>
88
        context.loaders.votedArticleReplyCountLoader
2✔
89
          .load(user.id)
90
          .then((num) => num || 0),
2✔
91
    },
92

93
    level: {
94
      type: new GraphQLNonNull(GraphQLInt),
95
      async resolve(user, arg, context) {
96
        const { level } =
97
          (await context.loaders.userLevelLoader.load(user.id)) || {};
2!
98
        return level || 0;
2✔
99
      },
100
    },
101
    points: {
102
      type: new GraphQLNonNull(
103
        new GraphQLObjectType({
104
          name: 'PointInfo',
105
          description:
106
            "Information of a user's point. Only available for current user.",
107
          fields: {
108
            total: {
109
              type: new GraphQLNonNull(GraphQLInt),
110
              description: 'Points earned by the current user',
111
            },
112
            currentLevel: {
113
              type: new GraphQLNonNull(GraphQLInt),
114
              description: 'Points required for current level',
115
            },
116
            nextLevel: {
117
              type: new GraphQLNonNull(GraphQLInt),
118
              description:
119
                'Points required for next level. null when there is no next level.',
120
            },
121
          },
122
        })
123
      ),
124
      resolve: async (user, arg, context) => {
125
        const { totalPoints, currentLevelPoints, nextLevelPoints } =
126
          (await context.loaders.userLevelLoader.load(user.id)) || {};
2!
127

128
        return {
2✔
129
          total: totalPoints || 0,
3✔
130
          currentLevel: currentLevelPoints || 0,
3✔
131
          nextLevel: nextLevelPoints || 1,
2!
132
        };
133
      },
134
    },
135
    createdAt: { type: GraphQLString },
136
    updatedAt: { type: GraphQLString },
137
    lastActiveAt: { type: GraphQLString },
138

139
    contributions: {
140
      type: new GraphQLList(Contribution),
141
      description: 'List of contributions made by the user',
142
      args: {
143
        dateRange: {
144
          type: timeRangeInput,
145
          description:
146
            'List only the contributions between the specific time range.',
147
        },
148
      },
149
      resolve: async ({ id }, { dateRange }, { loaders }) => {
150
        return await loaders.contributionsLoader.load({
1✔
151
          userId: id,
152
          dateRange,
153
        });
154
      },
155
    },
156

157
    blockedReason: {
158
      description:
159
        'If not null, the user is blocked with the announcement in this string.',
160
      type: GraphQLString,
161
    },
162

163
    badges: {
164
      type: new GraphQLNonNull(
165
        new GraphQLList(new GraphQLNonNull(UserAwardedBadge))
166
      ),
167
      description: 'Badges awarded to the user.',
NEW
168
      resolve: (user) => user.badges || [],
×
169
    },
170

171
    majorBadgeBorderUrl: {
172
      type: GraphQLString,
173
      description: 'returns badge background image url',
174
      resolve: async (user, args, { loaders }) => {
NEW
175
        const displayItem = user.badges.find({ isDisplay: true });
×
NEW
176
        if (displayItem == null) {
×
NEW
177
          return null;
×
178
        }
NEW
179
        const badgeId = displayItem.id;
×
NEW
180
        const badgeInfo = loaders.docLoader.load({
×
181
          index: 'badges',
182
          id: badgeId,
183
        });
NEW
184
        return badgeInfo.borderImage;
×
185
      },
186
    },
187

188
    majorBadgeImageUrl: {
189
      type: GraphQLString,
190
      description: 'returns badge background image url',
191
      resolve: async (user, args, { loaders }) => {
NEW
192
        const displayItem = user.badges.find({ isDisplay: true });
×
NEW
193
        if (displayItem == null) {
×
NEW
194
          return null;
×
195
        }
NEW
196
        const badgeId = displayItem.id;
×
NEW
197
        const badgeInfo = loaders.docLoader.load({
×
198
          index: 'badges',
199
          id: badgeId,
200
        });
NEW
201
        return badgeInfo.icon;
×
202
      },
203
    },
204

205
    majorBadgeName: {
206
      type: GraphQLString,
207
      description: 'returns badge background image url',
208
      resolve: async (user, args, { loaders }) => {
NEW
209
        const displayItem = user.badges.find({ isDisplay: true });
×
NEW
210
        if (displayItem == null) {
×
NEW
211
          return null;
×
212
        }
NEW
213
        const badgeId = displayItem.id;
×
NEW
214
        const badgeInfo = loaders.docLoader.load({
×
215
          index: 'badges',
216
          id: badgeId,
217
        });
NEW
218
        return badgeInfo.name;
×
219
      },
220
    },
221
  }),
222
});
223

224
export default User;
225

226
export const userFieldResolver = async (
38✔
227
  { userId, appId },
228
  args,
229
  { loaders, ...context }
230
) => {
231
  // If the root document is created by website users or if the userId is already converted to db userId,
232
  // we can resolve user from userId.
233
  //
234
  if (userId && appId) {
28✔
235
    const id = getUserId({ appId, userId });
19✔
236
    const user = await loaders.docLoader.load({ index: 'users', id });
19✔
237
    if (user) return user;
19✔
238
  }
239

240
  /* TODO: some unit tests are depending on this code block, need to clean up those tests and then
241
     remove the following lines, and the corresponding unit test. */
242

243
  // If the user comes from the same client as the root document, return the user id.
244
  //
245
  if (context.appId && context.appId === appId) return { id: userId };
17✔
246

247
  // If not, this client is not allowed to see user.
248
  //
249
  return null;
10✔
250
};
251

252
export const UserConnection = createConnectionType('UserConnection', User);
38✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc