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

u-wave / core / 20918361171

12 Jan 2026 11:52AM UTC coverage: 86.641% (+0.04%) from 86.599%
20918361171

Pull #734

github

web-flow
Merge ce65fd2b6 into 2dd6aa2f8
Pull Request #734: Move away from Redis for key-value usages

1027 of 1222 branches covered (84.04%)

Branch coverage included in aggregate %.

35 of 50 new or added lines in 5 files covered. (70.0%)

5 existing lines in 2 files now uncovered.

10628 of 12230 relevant lines covered (86.9%)

106.2 hits per line

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

73.55
/src/controllers/users.js
1
import {
1✔
2
  HTTPError,
1✔
3
  PermissionError,
1✔
4
  UserNotFoundError,
1✔
5
  UserNotInWaitlistError,
1✔
6
} from '../errors/index.js';
1✔
7
import skipIfCurrentDJ from '../utils/skipIfCurrentDJ.js';
1✔
8
import getOffsetPagination from '../utils/getOffsetPagination.js';
1✔
9
import toItemResponse from '../utils/toItemResponse.js';
1✔
10
import toListResponse from '../utils/toListResponse.js';
1✔
11
import toPaginatedResponse from '../utils/toPaginatedResponse.js';
1✔
12
import { muteUser, unmuteUser } from './chat.js';
1✔
13
import { KEY_ACTIVE_SESSIONS } from '../SocketServer.js';
1✔
14

1✔
15
/**
1✔
16
 * @typedef {import('../schema').UserID} UserID
1✔
17
 */
1✔
18

1✔
19
/**
1✔
20
 * @typedef {object} GetUsersQuery
1✔
21
 * @prop {string} filter
1✔
22
 */
1✔
23

1✔
24
/**
1✔
25
 * @type {import('../types.js').AuthenticatedController<{}, GetUsersQuery>}
1✔
26
 */
1✔
27
async function getUsers(req) {
1✔
28
  const { filter } = req.query;
1✔
29
  const pagination = getOffsetPagination(req.query, {
1✔
30
    defaultSize: 50,
1✔
31
  });
1✔
32
  const { users } = req.uwave;
1✔
33

1✔
34
  const userList = await users.getUsers(filter, pagination);
1✔
35

1✔
36
  return toPaginatedResponse(userList, {
1✔
37
    baseUrl: req.fullUrl,
1✔
38
  });
1✔
39
}
1✔
40

1✔
41
/**
1✔
42
 * @typedef {object} GetUserParams
1✔
43
 * @prop {UserID} id
1✔
44
 */
1✔
45

1✔
46
/**
1✔
47
 * @type {import('../types.js').Controller<GetUserParams>}
1✔
48
 */
1✔
49
async function getUser(req) {
4✔
50
  const { users } = req.uwave;
4✔
51
  const { id: userID } = req.params;
4✔
52

4✔
53
  const user = await users.getUser(userID);
4✔
54
  if (!user) {
4!
55
    throw new UserNotFoundError({ id: userID });
×
56
  }
×
57

4✔
58
  return toItemResponse(user, {
4✔
59
    url: req.fullUrl,
4✔
60
  });
4✔
61
}
4✔
62

1✔
63
/**
1✔
64
 * @typedef {object} GetUserRolesParams
1✔
65
 * @prop {UserID} id
1✔
66
 */
1✔
67

1✔
68
/**
1✔
69
 * @type {import('../types.js').Controller<GetUserRolesParams>}
1✔
70
 */
1✔
71
async function getUserRoles(req) {
×
72
  const { acl, users } = req.uwave;
×
73
  const { id } = req.params;
×
74

×
75
  const user = await users.getUser(id);
×
76
  if (!user) {
×
77
    throw new UserNotFoundError({ id });
×
78
  }
×
79

×
80
  const roles = await acl.getAllPermissions(user);
×
81

×
82
  return toListResponse(roles, {
×
83
    url: req.fullUrl,
×
84
  });
×
85
}
×
86

1✔
87
/**
1✔
88
 * @typedef {object} AddUserRoleParams
1✔
89
 * @prop {UserID} id
1✔
90
 * @prop {string} role
1✔
91
 */
1✔
92

1✔
93
/**
1✔
94
 * @type {import('../types.js').AuthenticatedController<AddUserRoleParams>}
1✔
95
 */
1✔
96
async function addUserRole(req) {
3✔
97
  const { user: moderator } = req;
3✔
98
  const { id, role } = req.params;
3✔
99
  const { acl, users } = req.uwave;
3✔
100

3✔
101
  const canModifyRoles = moderator.roles.includes('admin');
3✔
102
  if (!canModifyRoles) {
3!
103
    throw new PermissionError({ requiredRole: 'admin' });
×
104
  }
×
105

3✔
106
  const user = await users.getUser(id);
3✔
107
  if (!user) {
3✔
108
    throw new UserNotFoundError({ id });
1✔
109
  }
1✔
110

2✔
111
  await acl.allow(user, [role]);
2✔
112

1✔
113
  return toItemResponse({}, {
1✔
114
    url: req.fullUrl,
1✔
115
  });
1✔
116
}
3✔
117

1✔
118
/**
1✔
119
 * @typedef {object} RemoveUserRoleParams
1✔
120
 * @prop {UserID} id
1✔
121
 * @prop {string} role
1✔
122
 */
1✔
123

1✔
124
/**
1✔
125
 * @type {import('../types.js').AuthenticatedController<RemoveUserRoleParams>}
1✔
126
 */
1✔
127
async function removeUserRole(req) {
2✔
128
  const { user: moderator } = req;
2✔
129
  const { id, role } = req.params;
2✔
130
  const { acl, users } = req.uwave;
2✔
131

2✔
132
  const canModifyRoles = moderator.roles.includes('admin');
2✔
133
  if (!canModifyRoles) {
2!
134
    throw new PermissionError({ requiredRole: 'admin' });
×
135
  }
×
136

2✔
137
  const user = await users.getUser(id);
2✔
138
  if (!user) {
2✔
139
    throw new UserNotFoundError({ id });
1✔
140
  }
1✔
141

1✔
142
  await acl.disallow(user, [role]);
1✔
143

1✔
144
  return toItemResponse({}, {
1✔
145
    url: req.fullUrl,
1✔
146
  });
1✔
147
}
2✔
148

1✔
149
/**
1✔
150
 * @typedef {object} ChangeUsernameParams
1✔
151
 * @prop {UserID} id
1✔
152
 * @typedef {object} ChangeUsernameBody
1✔
153
 * @prop {string} username
1✔
154
 */
1✔
155

1✔
156
/**
1✔
157
 * @type {import('../types.js').AuthenticatedController<
1✔
158
 *     ChangeUsernameParams, {}, ChangeUsernameBody>}
1✔
159
 */
1✔
160
async function changeUsername(req) {
5✔
161
  const { user: moderator } = req;
5✔
162
  const { id } = req.params;
5✔
163
  const { username } = req.body;
5✔
164
  const { users } = req.uwave;
5✔
165

5✔
166
  if (id !== moderator.id) {
5✔
167
    throw new PermissionError();
1✔
168
  }
1✔
169

4✔
170
  const user = await users.updateUser(
4✔
171
    id,
4✔
172
    { username },
4✔
173
    { moderator },
4✔
174
  );
4✔
175

3✔
176
  return toItemResponse(user);
3✔
177
}
5✔
178

1✔
179
/**
1✔
180
 * @returns {Promise<import('type-fest').JsonObject>}
1✔
181
 */
1✔
182
async function changeAvatar() {
×
183
  throw new HTTPError(500, 'Not implemented');
×
184
}
×
185

1✔
186
/**
1✔
187
 * Remove the user ID from the online users list.
1✔
188
 *
1✔
189
 * @param {import('../Uwave.js').default} uw
1✔
190
 * @param {UserID} userID
1✔
191
 */
1✔
192
async function disconnectUser(uw, userID) {
×
193
  await skipIfCurrentDJ(uw, userID);
×
194

×
195
  try {
×
196
    await uw.waitlist.removeUser(userID);
×
197
  } catch (err) {
×
198
    // It's expected that the user would not be in the waitlist
×
199
    if (!(err instanceof UserNotInWaitlistError)) {
×
200
      throw err;
×
201
    }
×
202
  }
×
203

×
NEW
204
  const userIDs = new Set(
×
NEW
205
    /** @type {import('../schema.js').UserID[] | null} */ (
×
NEW
206
      await uw.keyv.get(KEY_ACTIVE_SESSIONS)
×
NEW
207
    ) ?? [],
×
NEW
208
  );
×
NEW
209
  userIDs.delete(userID);
×
NEW
210

×
NEW
211
  await uw.keyv.set(KEY_ACTIVE_SESSIONS, Array.from(userIDs));
×
212

×
213
  uw.publish('user:leave', { userID });
×
214
}
×
215

1✔
216
/**
1✔
217
 * @typedef {object} GetHistoryParams
1✔
218
 * @prop {UserID} id
1✔
219
 */
1✔
220

1✔
221
/**
1✔
222
 * @type {import('../types.js').Controller<GetHistoryParams>}
1✔
223
 */
1✔
224
async function getHistory(req) {
×
225
  const { id } = req.params;
×
226
  const pagination = getOffsetPagination(req.query, {
×
227
    defaultSize: 25,
×
228
    maxSize: 100,
×
229
  });
×
230
  const uw = req.uwave;
×
231

×
232
  const user = await uw.users.getUser(id);
×
233
  if (!user) {
×
234
    throw new UserNotFoundError({ id });
×
235
  }
×
236

×
237
  const history = await uw.history.getUserHistory(user, pagination);
×
238

×
239
  return toPaginatedResponse(history, {
×
240
    baseUrl: req.fullUrl,
×
241
    included: {
×
242
      media: ['media.media'],
×
243
      user: ['user'],
×
244
    },
×
245
  });
×
246
}
×
247

1✔
248
export {
1✔
249
  getUsers,
1✔
250
  getUser,
1✔
251
  getUserRoles,
1✔
252
  addUserRole,
1✔
253
  removeUserRole,
1✔
254
  changeUsername,
1✔
255
  changeAvatar,
1✔
256
  disconnectUser,
1✔
257
  getHistory,
1✔
258
  muteUser,
1✔
259
  unmuteUser,
1✔
260
};
1✔
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