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

cofacts / rumors-api / 14198914215

01 Apr 2025 02:53PM UTC coverage: 82.154% (-0.9%) from 83.071%
14198914215

Pull #357

github

jhk482001
update badge api, add test case, fix issue and prettier issue
Pull Request #357: add Badge api

798 of 1030 branches covered (77.48%)

Branch coverage included in aggregate %.

93 of 121 new or added lines in 9 files covered. (76.86%)

2 existing lines in 1 file now uncovered.

1536 of 1811 relevant lines covered (84.82%)

18.45 hits per line

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

78.33
/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.',
168
      resolve: (user) => user.badges || [],
2!
169
    },
170

171
    majorBadgeBorderUrl: {
172
      type: GraphQLString,
173
      description: 'returns badge background image url',
174
      resolve: async (user, args, { loaders }) => {
175
        if (!user.badges || !Array.isArray(user.badges)) {
2!
NEW
176
          console.log('No badges array found for user:', user.id);
×
NEW
177
          return null;
×
178
        }
179

180
        const displayItem = user.badges.find(
2✔
181
          (badge) => badge.isDisplayed === true
1✔
182
        );
183
        if (!displayItem) {
2✔
184
          console.log('No displayed badge found for user:', user.id);
1✔
185
          return null;
1✔
186
        }
187

188
        console.log('Finding badge with ID:', displayItem.badgeId);
1✔
189

190
        const badgeInfo = await loaders.docLoader.load({
1✔
191
          index: 'badges',
192
          id: displayItem.badgeId,
193
        });
194

195
        console.log('Badge info found:', badgeInfo);
1✔
196
        return badgeInfo?.borderImage || null;
1!
197
      },
198
    },
199

200
    majorBadgeId: {
201
      type: GraphQLString,
202
      description: 'returns badge icon url',
203
      resolve: async (user) => {
NEW
204
        if (!user.badges || !Array.isArray(user.badges)) return null;
×
205

NEW
206
        const displayItem = user.badges.find(
×
NEW
207
          (badge) => badge.isDisplayed === true
×
208
        );
NEW
209
        if (!displayItem) return null;
×
210

NEW
211
        return displayItem.badgeId;
×
212
      },
213
    },
214

215
    majorBadgeImageUrl: {
216
      type: GraphQLString,
217
      description: 'returns badge icon url',
218
      resolve: async (user, args, { loaders }) => {
219
        if (!user.badges || !Array.isArray(user.badges)) return null;
2!
220

221
        const displayItem = user.badges.find(
2✔
222
          (badge) => badge.isDisplayed === true
1✔
223
        );
224
        if (!displayItem) return null;
2✔
225

226
        const badgeInfo = await loaders.docLoader.load({
1✔
227
          index: 'badges',
228
          id: displayItem.badgeId,
229
        });
230

231
        return badgeInfo?.icon || null;
1!
232
      },
233
    },
234

235
    majorBadgeName: {
236
      type: GraphQLString,
237
      description: 'returns badge name',
238
      resolve: async (user, args, { loaders }) => {
239
        if (!user.badges || !Array.isArray(user.badges)) return null;
2!
240

241
        const displayItem = user.badges.find(
2✔
242
          (badge) => badge.isDisplayed === true
1✔
243
        );
244
        if (!displayItem) return null;
2✔
245

246
        const badgeInfo = await loaders.docLoader.load({
1✔
247
          index: 'badges',
248
          id: displayItem.badgeId,
249
        });
250

251
        return badgeInfo?.name || null;
1!
252
      },
253
    },
254
  }),
255
});
256

257
export default User;
258

259
export const userFieldResolver = async (
38✔
260
  { userId, appId },
261
  args,
262
  { loaders, ...context }
263
) => {
264
  // If the root document is created by website users or if the userId is already converted to db userId,
265
  // we can resolve user from userId.
266
  //
267
  if (userId && appId) {
28✔
268
    const id = getUserId({ appId, userId });
19✔
269
    const user = await loaders.docLoader.load({ index: 'users', id });
19✔
270
    if (user) return user;
19✔
271
  }
272

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

276
  // If the user comes from the same client as the root document, return the user id.
277
  //
278
  if (context.appId && context.appId === appId) return { id: userId };
17✔
279

280
  // If not, this client is not allowed to see user.
281
  //
282
  return null;
10✔
283
};
284

285
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