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

u-wave / core / 11980840475

22 Nov 2024 10:04PM UTC coverage: 78.492% (-1.7%) from 80.158%
11980840475

Pull #637

github

goto-bus-stop
ci: add node 22
Pull Request #637: Switch to a relational database

757 of 912 branches covered (83.0%)

Branch coverage included in aggregate %.

2001 of 2791 new or added lines in 52 files covered. (71.69%)

9 existing lines in 7 files now uncovered.

8666 of 11093 relevant lines covered (78.12%)

70.72 hits per line

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

88.76
/src/controllers/now.js
1
import { getBoothData } from './booth.js';
1✔
2
import { serializePlaylist, serializeUser } from '../utils/serialize.js';
1✔
3
import { legacyPlaylistItem } from './playlists.js';
1✔
4

1✔
5
/**
1✔
6
 * @typedef {import('../schema.js').UserID} UserID
1✔
7
 */
1✔
8

1✔
9
/**
1✔
10
 * @param {import('../Uwave.js').default} uw
1✔
11
 * @param {import('../schema.js').Playlist & { size: number }} playlist
1✔
12
 */
1✔
13
async function getFirstItem(uw, playlist) {
1✔
14
  try {
1✔
15
    if (playlist.size > 0) {
1!
NEW
16
      const { playlistItem, media } = await uw.playlists.getPlaylistItemAt(playlist, 0);
×
NEW
17
      return legacyPlaylistItem(playlistItem, media);
×
UNCOV
18
    }
×
19
  } catch {
1!
20
    // Nothing
×
21
  }
×
22
  return null;
1✔
23
}
1✔
24

1✔
25
/**
1✔
26
 * @param {unknown} str
1✔
27
 */
1✔
28
function toInt(str) {
4✔
29
  if (typeof str !== 'string') return 0;
4✔
30
  if (!/^\d+$/.test(str)) return 0;
×
31
  return parseInt(str, 10);
×
32
}
4✔
33

1✔
34
/**
1✔
35
 * @param {import('../Uwave.js').default} uw
1✔
36
 */
1✔
37
async function getOnlineUsers(uw) {
4✔
38
  const userIDs = /** @type {UserID[]} */ (await uw.redis.lrange('users', 0, -1));
4✔
39
  if (userIDs.length === 0) {
4✔
40
    return [];
3✔
41
  }
3✔
42

1✔
43
  const users = await uw.users.getUsersByIds(userIDs);
1✔
44
  return users.map(serializeUser);
1✔
45
}
4✔
46

1✔
47
/**
1✔
48
 * @param {import('../Uwave.js').default} uw
1✔
49
 */
1✔
50
async function getGuestsCount(uw) {
4✔
51
  const guests = await uw.redis.get('http-api:guests');
4✔
52
  return toInt(guests);
4✔
53
}
4✔
54

1✔
55
/**
1✔
56
 * @type {import('../types.js').Controller}
1✔
57
 */
1✔
58
async function getState(req) {
4✔
59
  const uw = req.uwave;
4✔
60
  const { authRegistry } = req.uwaveHttp;
4✔
61
  const { passport } = uw;
4✔
62
  const { user } = req;
4✔
63

4✔
64
  const motd = uw.motd.get();
4✔
65
  const users = getOnlineUsers(uw);
4✔
66
  const guests = getGuestsCount(uw);
4✔
67
  const roles = uw.acl.getAllRoles();
4✔
68
  const booth = getBoothData(uw);
4✔
69
  const waitlist = uw.waitlist.getUserIDs();
4✔
70
  const waitlistLocked = uw.waitlist.isLocked();
4✔
71
  const autoLeave = user != null ? uw.booth.getRemoveAfterCurrentPlay(user) : false;
4✔
72
  let activePlaylist = user?.activePlaylistID
4✔
73
    ? uw.playlists.getUserPlaylist(user, user.activePlaylistID).catch((error) => {
4✔
NEW
74
      // If the playlist was not found, our database is inconsistent. A deleted or nonexistent
×
NEW
75
      // playlist should never be listed as the active playlist. Most likely this is not the
×
NEW
76
      // user's fault, so we should not error out on `/api/now`. Instead, pretend they don't have
×
NEW
77
      // an active playlist at all. Clients can then let them select a new playlist to activate.
×
NEW
78
      if (error.code === 'NOT_FOUND' || error.code === 'playlist-not-found') {
×
NEW
79
        req.log.warn('The active playlist does not exist', { error });
×
NEW
80
        return null;
×
NEW
81
      }
×
NEW
82
      throw error;
×
83
    })
1✔
84
    : Promise.resolve(null);
4✔
85
  const playlists = user ? uw.playlists.getUserPlaylists(user) : null;
4✔
86
  const firstActivePlaylistItem = activePlaylist.then((playlist) => (
4✔
87
    playlist != null ? getFirstItem(uw, playlist) : null
4✔
88
  ));
4✔
89
  const socketToken = user ? authRegistry.createAuthToken(user) : null;
4✔
90
  const authStrategies = passport.strategies();
4✔
91
  const time = Date.now();
4✔
92

4✔
93
  const stateShape = {
4✔
94
    motd,
4✔
95
    user: user ? serializeUser(user) : null,
4✔
96
    users,
4✔
97
    guests,
4✔
98
    roles,
4✔
99
    booth,
4✔
100
    waitlist,
4✔
101
    waitlistLocked,
4✔
102
    autoLeave,
4✔
103
    activePlaylist: activePlaylist.then((playlist) => playlist?.id ?? null),
4✔
104
    firstActivePlaylistItem,
4✔
105
    playlists,
4✔
106
    socketToken,
4✔
107
    authStrategies,
4✔
108
    time,
4✔
109
  };
4✔
110

4✔
111
  const stateKeys = Object.keys(stateShape);
4✔
112
  // This is a little dirty but maintaining the exact type shape is very hard here.
4✔
113
  // We could solve that in the future by using a `p-props` style function. The npm
4✔
114
  // module `p-props` is a bit wasteful though.
4✔
115
  /** @type {any} */
4✔
116
  const values = Object.values(stateShape);
4✔
117
  const stateValues = await Promise.all(values);
4✔
118

4✔
119
  const state = Object.create(null);
4✔
120
  for (let i = 0; i < stateKeys.length; i += 1) {
4✔
121
    state[stateKeys[i]] = stateValues[i];
60✔
122
  }
60✔
123

4✔
124
  if (state.playlists) {
4✔
125
    state.playlists = state.playlists.map(serializePlaylist);
2✔
126
  }
2✔
127

4✔
128
  for (const permission of Object.values(state.roles).flat()) {
4✔
129
    // Web client expects all permissions to be roles too.
224✔
130
    // This isn't how it works since #637.
224✔
131
    // Clients can still distinguish between roles and permissions using `.includes('.')`
224✔
132
    state.roles[permission] ??= [];
224✔
133
  }
224✔
134

4✔
135
  return state;
4✔
136
}
4✔
137

1✔
138
export { getState };
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