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

u-wave / core / 20207691175

14 Dec 2025 12:06PM UTC coverage: 85.696% (-0.3%) from 85.96%
20207691175

Pull #730

github

web-flow
Merge e21b60062 into 166408e46
Pull Request #730: Add chat backscroll endpoint

999 of 1190 branches covered (83.95%)

Branch coverage included in aggregate %.

41 of 84 new or added lines in 3 files covered. (48.81%)

3 existing lines in 2 files now uncovered.

10456 of 12177 relevant lines covered (85.87%)

97.78 hits per line

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

64.0
/src/controllers/chat.js
1
import { sql } from 'kysely';
1✔
2
import { UserNotFoundError, CannotSelfMuteError } from '../errors/index.js';
1✔
3
import { fromJson, json } from '../utils/sqlite.js';
1✔
4
import toItemResponse from '../utils/toItemResponse.js';
1✔
5
import toListResponse from '../utils/toListResponse.js';
1✔
6

1✔
7
/**
1✔
8
 * @typedef {import('../schema').UserID} UserID
1✔
9
 */
1✔
10

1✔
11
const BACKSCROLL_LENGTH = 20;
1✔
12

1✔
13
/**
1✔
14
 * @typedef {object} MuteUserParams
1✔
15
 * @prop {UserID} id
1✔
16
 * @typedef {object} MuteUserBody
1✔
17
 * @prop {number} time
1✔
18
 */
1✔
19

1✔
20
/**
1✔
21
 * @type {import('../types.js').AuthenticatedController<MuteUserParams, {}, MuteUserBody>}
1✔
22
 */
1✔
23
async function muteUser(req) {
1✔
24
  const { user: moderator } = req;
1✔
25
  const { id } = req.params;
1✔
26
  const duration = req.body.time;
1✔
27
  const { chat, users } = req.uwave;
1✔
28

1✔
29
  if (moderator.id === id) {
1!
UNCOV
30
    throw new CannotSelfMuteError({ action: 'mute' });
×
31
  }
×
32

1✔
33
  const user = await users.getUser(id);
1✔
34
  if (!user) throw new UserNotFoundError({ id });
1!
35

1✔
36
  await chat.mute(user, duration, { moderator });
1✔
37

1✔
38
  return toItemResponse({});
1✔
39
}
1✔
40

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

1✔
46
/**
1✔
47
 * @type {import('../types.js').AuthenticatedController<UnmuteUserParams>}
1✔
48
 */
1✔
UNCOV
49
async function unmuteUser(req) {
×
50
  const { user: moderator } = req;
×
51
  const { id } = req.params;
×
52
  const { chat, users } = req.uwave;
×
53

×
54
  if (moderator.id === id) {
×
55
    throw new CannotSelfMuteError({ action: 'unmute' });
×
56
  }
×
57

×
58
  const user = await users.getUser(id);
×
59
  if (!user) throw new UserNotFoundError({ id });
×
60

×
61
  await chat.unmute(user, { moderator });
×
62

×
63
  return toItemResponse({});
×
64
}
×
65

1✔
66
/**
1✔
67
 * @type {import('../types.js').AuthenticatedController}
1✔
68
 */
1✔
69
async function deleteAll(req) {
2✔
70
  const { user: moderator } = req;
2✔
71
  const { chat } = req.uwave;
2✔
72

2✔
73
  chat.delete({}, { moderator });
2✔
74

2✔
75
  return toItemResponse({});
2✔
76
}
2✔
77

1✔
78
/**
1✔
79
 * @typedef {object} DeleteByUserParams
1✔
80
 * @prop {UserID} id
1✔
81
 */
1✔
82

1✔
83
/**
1✔
84
 * @type {import('../types.js').AuthenticatedController<DeleteByUserParams>}
1✔
85
 */
1✔
86
async function deleteByUser(req) {
2✔
87
  const { user: moderator } = req;
2✔
88
  const { chat } = req.uwave;
2✔
89
  const { id } = req.params;
2✔
90

2✔
91
  chat.delete({ userID: id }, { moderator });
2✔
92

2✔
93
  return toItemResponse({});
2✔
94
}
2✔
95

1✔
96
/**
1✔
97
 * @typedef {object} DeleteMessageParams
1✔
98
 * @prop {string} id
1✔
99
 */
1✔
100

1✔
101
/**
1✔
102
 * @type {import('../types.js').AuthenticatedController<DeleteMessageParams>}
1✔
103
 */
1✔
104
async function deleteMessage(req) {
2✔
105
  const { user: moderator } = req;
2✔
106
  const { chat } = req.uwave;
2✔
107
  const { id } = req.params;
2✔
108

2✔
109
  chat.delete({ id }, { moderator });
2✔
110

2✔
111
  return toItemResponse({});
2✔
112
}
2✔
113

1✔
114
/**
1✔
115
 * @type {import('../types.js').Controller<{}>}
1✔
116
 */
1✔
NEW
117
async function getBackscroll(req) {
×
NEW
118
  const { db, users } = req.uwave;
×
NEW
119

×
NEW
120
  const rows = await db.selectFrom('socketMessageQueue')
×
NEW
121
    .where('command', '=', 'chatMessage')
×
NEW
122
    .innerJoin('users', (join) => join
×
NEW
123
      .on('users.id', '=', (eb) => sql`${eb.ref('data')}->>'userID'`)
×
NEW
124
    )
×
NEW
125
    .select([
×
NEW
126
      (eb) => json(eb.ref('data')).as('data'),
×
NEW
127
      ...users.publicUserColumns,
×
NEW
128
    ])
×
NEW
129
    .orderBy('socketMessageQueue.id', 'desc')
×
NEW
130
    .limit(BACKSCROLL_LENGTH)
×
NEW
131
    .execute()
×
NEW
132

×
NEW
133
  const messages = rows.map((row) => {
×
NEW
134
    const message = /** @type {import('../redisMessages.js').ServerActionParameters['chat:message']} */ (fromJson(row.data));
×
NEW
135
    return {
×
NEW
136
      id: message.id,
×
NEW
137
      /** Deprecated: timestamp as unixy milliseconds */
×
NEW
138
      timestamp: message.timestamp,
×
NEW
139
      createdAt: new Date(message.timestamp),
×
NEW
140
      message: message.message,
×
NEW
141
      user: {
×
NEW
142
        id: row.id,
×
NEW
143
        username: row.username,
×
NEW
144
        slug: row.slug,
×
NEW
145
        createdAt: row.createdAt,
×
NEW
146
        updatedAt: row.updatedAt,
×
NEW
147
        avatar: row.avatar,
×
NEW
148
        roles: row.roles == null ? [] : fromJson(row.roles),
×
NEW
149
      },
×
NEW
150
    };
×
NEW
151
  });
×
NEW
152

×
NEW
153
  return toListResponse(messages, {
×
NEW
154
    included: {
×
NEW
155
      user: ['user'],
×
NEW
156
    },
×
NEW
157
    url: req.fullUrl,
×
NEW
158
  });
×
NEW
159
}
×
160

1✔
161
export {
1✔
162
  muteUser,
1✔
163
  unmuteUser,
1✔
164
  deleteAll,
1✔
165
  deleteByUser,
1✔
166
  deleteMessage,
1✔
167
  getBackscroll,
1✔
168
};
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