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

cofacts / rumors-api / 12866871569

20 Jan 2025 11:18AM UTC coverage: 81.013% (-1.7%) from 82.699%
12866871569

Pull #357

github

jhk482001
remove test env file, update User's model
Pull Request #357: add Badge api

764 of 992 branches covered (77.02%)

Branch coverage included in aggregate %.

17 of 54 new or added lines in 5 files covered. (31.48%)

1 existing line in 1 file now uncovered.

1476 of 1773 relevant lines covered (83.25%)

18.54 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 Badge 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(new GraphQLList(new GraphQLNonNull(Badge))),
165
      description: 'Badges awarded to the user.',
NEW
166
      resolve: (user) => user.badges || [],
×
167
    },
168

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

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

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

222
export default User;
223

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

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

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

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

250
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