@meith/import 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +165 -0
- package/package.json +24 -0
- package/src/index.ts +130 -0
- package/src/legacy-urls.ts +131 -0
- package/src/map.ts +297 -0
- package/src/memory-sink.fixture.ts +64 -0
- package/src/mybb-fixture.ts +185 -0
- package/src/mybb-rows.ts +172 -0
- package/src/mysql-source.ts +391 -0
- package/src/phpbb-fixture.ts +191 -0
- package/src/phpbb-map.ts +297 -0
- package/src/phpbb-mysql-source.ts +355 -0
- package/src/phpbb-rows.ts +160 -0
- package/src/phpbb-text.ts +44 -0
- package/src/run.ts +261 -0
- package/src/source.ts +98 -0
- package/src/types.ts +176 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import {
|
|
2
|
+
mapAttachment,
|
|
3
|
+
mapAvatar,
|
|
4
|
+
mapBan,
|
|
5
|
+
mapForum,
|
|
6
|
+
mapForumSubscription,
|
|
7
|
+
mapPoll,
|
|
8
|
+
mapPollVote,
|
|
9
|
+
mapPost,
|
|
10
|
+
mapPrivateMessage,
|
|
11
|
+
mapRelationList,
|
|
12
|
+
mapReputation,
|
|
13
|
+
mapThread,
|
|
14
|
+
mapThreadSubscription,
|
|
15
|
+
mapUser,
|
|
16
|
+
mapWarning,
|
|
17
|
+
} from './map'
|
|
18
|
+
import type {
|
|
19
|
+
MybbAttachment,
|
|
20
|
+
MybbAvatarRow,
|
|
21
|
+
MybbBan,
|
|
22
|
+
MybbForum,
|
|
23
|
+
MybbForumSubscription,
|
|
24
|
+
MybbPoll,
|
|
25
|
+
MybbPollVote,
|
|
26
|
+
MybbPost,
|
|
27
|
+
MybbPrivateMessage,
|
|
28
|
+
MybbRelationList,
|
|
29
|
+
MybbReputation,
|
|
30
|
+
MybbThread,
|
|
31
|
+
MybbThreadSubscription,
|
|
32
|
+
MybbUser,
|
|
33
|
+
MybbWarning,
|
|
34
|
+
} from './mybb-rows'
|
|
35
|
+
import { type ImportSource, type Page, pageByGroup } from './source'
|
|
36
|
+
import type {
|
|
37
|
+
ImportedAttachment,
|
|
38
|
+
ImportedAvatar,
|
|
39
|
+
ImportedBan,
|
|
40
|
+
ImportedForum,
|
|
41
|
+
ImportedPoll,
|
|
42
|
+
ImportedPollVote,
|
|
43
|
+
ImportedPost,
|
|
44
|
+
ImportedPrivateMessage,
|
|
45
|
+
ImportedReputation,
|
|
46
|
+
ImportedSubscription,
|
|
47
|
+
ImportedThread,
|
|
48
|
+
ImportedUser,
|
|
49
|
+
ImportedUserRelation,
|
|
50
|
+
ImportedWarning,
|
|
51
|
+
} from './types'
|
|
52
|
+
|
|
53
|
+
export interface MysqlSourceOptions {
|
|
54
|
+
readonly host: string
|
|
55
|
+
readonly port?: number | undefined
|
|
56
|
+
readonly user: string
|
|
57
|
+
readonly password: string
|
|
58
|
+
readonly database: string
|
|
59
|
+
readonly tablePrefix?: string | undefined
|
|
60
|
+
readonly charset?: string | undefined
|
|
61
|
+
readonly ssl?: boolean | undefined
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const PREFIX_PATTERN = /^[A-Za-z0-9_]{0,32}$/
|
|
65
|
+
|
|
66
|
+
export function assertSafePrefix(prefix: string): void {
|
|
67
|
+
if (!PREFIX_PATTERN.test(prefix)) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
`Unsafe legacy table prefix ${JSON.stringify(prefix)}. ` +
|
|
70
|
+
'Letters, digits and underscores only — a prefix becomes part of a table name, ' +
|
|
71
|
+
'and a table name cannot be a bound parameter.',
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface Queryable {
|
|
77
|
+
query(sql: string, values: readonly unknown[]): Promise<[unknown, unknown]>
|
|
78
|
+
end(): Promise<void>
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function connectMysql(options: MysqlSourceOptions): Promise<Queryable> {
|
|
82
|
+
const mysql = await import('mysql2/promise')
|
|
83
|
+
|
|
84
|
+
const connection = await mysql.createConnection({
|
|
85
|
+
host: options.host,
|
|
86
|
+
port: options.port ?? 3306,
|
|
87
|
+
user: options.user,
|
|
88
|
+
password: options.password,
|
|
89
|
+
database: options.database,
|
|
90
|
+
charset: options.charset ?? 'utf8mb4',
|
|
91
|
+
...(options.ssl === true ? { ssl: {} } : {}),
|
|
92
|
+
supportBigNumbers: true,
|
|
93
|
+
bigNumberStrings: false,
|
|
94
|
+
dateStrings: false,
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
return connection as unknown as Queryable
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function selectRows<T>(
|
|
101
|
+
connection: Queryable,
|
|
102
|
+
sql: string,
|
|
103
|
+
values: readonly unknown[],
|
|
104
|
+
): Promise<readonly T[]> {
|
|
105
|
+
const [result] = await connection.query(sql, values)
|
|
106
|
+
return (Array.isArray(result) ? result : []) as T[]
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function keysetPage<T>(
|
|
110
|
+
connection: Queryable,
|
|
111
|
+
select: string,
|
|
112
|
+
keyColumn: string,
|
|
113
|
+
idOf: (row: T) => number,
|
|
114
|
+
afterId: number,
|
|
115
|
+
limit: number,
|
|
116
|
+
): Promise<Page<T>> {
|
|
117
|
+
const rows = await selectRows<T>(
|
|
118
|
+
connection,
|
|
119
|
+
`${select} where ${keyColumn} > ? order by ${keyColumn} asc limit ?`,
|
|
120
|
+
[afterId, limit],
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
const last = rows.at(-1)
|
|
124
|
+
return { rows, nextCursor: rows.length < limit || last === undefined ? null : idOf(last) }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function mapPage<Raw, Mapped>(
|
|
128
|
+
page: Page<Raw>,
|
|
129
|
+
map: (row: Raw) => Mapped | null,
|
|
130
|
+
): Page<Mapped> {
|
|
131
|
+
return {
|
|
132
|
+
rows: page.rows.map(map).filter((row): row is NonNullable<Mapped> => row !== null),
|
|
133
|
+
nextCursor: page.nextCursor,
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export class MysqlMybbSource implements ImportSource {
|
|
138
|
+
readonly name = 'mybb'
|
|
139
|
+
|
|
140
|
+
private constructor(
|
|
141
|
+
private readonly connection: Queryable,
|
|
142
|
+
private readonly prefix: string,
|
|
143
|
+
) {}
|
|
144
|
+
|
|
145
|
+
static async connect(options: MysqlSourceOptions): Promise<MysqlMybbSource> {
|
|
146
|
+
const prefix = options.tablePrefix ?? 'mybb_'
|
|
147
|
+
assertSafePrefix(prefix)
|
|
148
|
+
return new MysqlMybbSource(await connectMysql(options), prefix)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async close(): Promise<void> {
|
|
152
|
+
await this.connection.end()
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async users(afterId: number, limit: number): Promise<Page<ImportedUser>> {
|
|
156
|
+
return mapPage(
|
|
157
|
+
await this.#page<MybbUser>(
|
|
158
|
+
`select uid, username, email, password, salt, usergroup,
|
|
159
|
+
regdate, lastvisit, postnum
|
|
160
|
+
from \`${this.prefix}users\``,
|
|
161
|
+
'uid',
|
|
162
|
+
(row) => row.uid,
|
|
163
|
+
afterId,
|
|
164
|
+
limit,
|
|
165
|
+
),
|
|
166
|
+
mapUser,
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async forums(afterId: number, limit: number): Promise<Page<ImportedForum>> {
|
|
171
|
+
return mapPage(
|
|
172
|
+
await this.#page<MybbForum>(
|
|
173
|
+
`select fid, name, description, type, pid, disporder, linkto, threads, posts
|
|
174
|
+
from \`${this.prefix}forums\``,
|
|
175
|
+
'fid',
|
|
176
|
+
(row) => row.fid,
|
|
177
|
+
afterId,
|
|
178
|
+
limit,
|
|
179
|
+
),
|
|
180
|
+
mapForum,
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async threads(afterId: number, limit: number): Promise<Page<ImportedThread>> {
|
|
185
|
+
return mapPage(
|
|
186
|
+
await this.#page<MybbThread>(
|
|
187
|
+
`select tid, fid, subject, uid, username, dateline, lastpost,
|
|
188
|
+
replies, views, sticky, closed, visible
|
|
189
|
+
from \`${this.prefix}threads\``,
|
|
190
|
+
'tid',
|
|
191
|
+
(row) => row.tid,
|
|
192
|
+
afterId,
|
|
193
|
+
limit,
|
|
194
|
+
),
|
|
195
|
+
mapThread,
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async posts(afterId: number, limit: number): Promise<Page<ImportedPost>> {
|
|
200
|
+
return mapPage(
|
|
201
|
+
await this.#page<MybbPost>(
|
|
202
|
+
`select pid, tid, fid, uid, username, subject, message,
|
|
203
|
+
dateline, edituid, edittime, visible
|
|
204
|
+
from \`${this.prefix}posts\``,
|
|
205
|
+
'pid',
|
|
206
|
+
(row) => row.pid,
|
|
207
|
+
afterId,
|
|
208
|
+
limit,
|
|
209
|
+
),
|
|
210
|
+
mapPost,
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async avatars(afterId: number, limit: number): Promise<Page<ImportedAvatar>> {
|
|
215
|
+
const rows = await selectRows<MybbAvatarRow>(
|
|
216
|
+
this.connection,
|
|
217
|
+
`select uid, avatar, avatartype, avatardimensions
|
|
218
|
+
from \`${this.prefix}users\`
|
|
219
|
+
where uid > ? and avatar <> ''
|
|
220
|
+
order by uid asc limit ?`,
|
|
221
|
+
[afterId, limit],
|
|
222
|
+
)
|
|
223
|
+
const last = rows.at(-1)
|
|
224
|
+
return mapPage(
|
|
225
|
+
{ rows, nextCursor: rows.length < limit || last === undefined ? null : last.uid },
|
|
226
|
+
mapAvatar,
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async attachments(afterId: number, limit: number): Promise<Page<ImportedAttachment>> {
|
|
231
|
+
return mapPage(
|
|
232
|
+
await this.#page<MybbAttachment>(
|
|
233
|
+
`select aid, pid, uid, filename, filetype, filesize, attachname,
|
|
234
|
+
thumbnail, downloads, dateuploaded
|
|
235
|
+
from \`${this.prefix}attachments\``,
|
|
236
|
+
'aid',
|
|
237
|
+
(row) => row.aid,
|
|
238
|
+
afterId,
|
|
239
|
+
limit,
|
|
240
|
+
),
|
|
241
|
+
mapAttachment,
|
|
242
|
+
)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async polls(afterId: number, limit: number): Promise<Page<ImportedPoll>> {
|
|
246
|
+
return mapPage(
|
|
247
|
+
await this.#page<MybbPoll>(
|
|
248
|
+
`select pid, tid, question, dateline, options, votes, timeout, multiple, public
|
|
249
|
+
from \`${this.prefix}polls\``,
|
|
250
|
+
'pid',
|
|
251
|
+
(row) => row.pid,
|
|
252
|
+
afterId,
|
|
253
|
+
limit,
|
|
254
|
+
),
|
|
255
|
+
mapPoll,
|
|
256
|
+
)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async pollVotes(afterId: number, limit: number): Promise<Page<ImportedPollVote>> {
|
|
260
|
+
return mapPage(
|
|
261
|
+
await this.#page<MybbPollVote>(
|
|
262
|
+
`select vid, pid, uid, voteoption, dateline
|
|
263
|
+
from \`${this.prefix}pollvotes\``,
|
|
264
|
+
'vid',
|
|
265
|
+
(row) => row.vid,
|
|
266
|
+
afterId,
|
|
267
|
+
limit,
|
|
268
|
+
),
|
|
269
|
+
mapPollVote,
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async privateMessages(afterId: number, limit: number): Promise<Page<ImportedPrivateMessage>> {
|
|
274
|
+
return mapPage(
|
|
275
|
+
await this.#page<MybbPrivateMessage>(
|
|
276
|
+
`select pmid, uid, fromid, subject, message, dateline, folder, status, readtime
|
|
277
|
+
from \`${this.prefix}privatemessages\``,
|
|
278
|
+
'pmid',
|
|
279
|
+
(row) => row.pmid,
|
|
280
|
+
afterId,
|
|
281
|
+
limit,
|
|
282
|
+
),
|
|
283
|
+
mapPrivateMessage,
|
|
284
|
+
)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async threadSubscriptions(afterId: number, limit: number): Promise<Page<ImportedSubscription>> {
|
|
288
|
+
return mapPage(
|
|
289
|
+
await this.#page<MybbThreadSubscription>(
|
|
290
|
+
`select sid, uid, tid, notification
|
|
291
|
+
from \`${this.prefix}threadsubscriptions\``,
|
|
292
|
+
'sid',
|
|
293
|
+
(row) => row.sid,
|
|
294
|
+
afterId,
|
|
295
|
+
limit,
|
|
296
|
+
),
|
|
297
|
+
mapThreadSubscription,
|
|
298
|
+
)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async forumSubscriptions(afterId: number, limit: number): Promise<Page<ImportedSubscription>> {
|
|
302
|
+
const table = `\`${this.prefix}forumsubscriptions\``
|
|
303
|
+
const page = await pageByGroup<MybbForumSubscription>({
|
|
304
|
+
afterGroup: afterId,
|
|
305
|
+
limit,
|
|
306
|
+
groupOf: (row) => row.uid,
|
|
307
|
+
fetch: (afterGroup, take) =>
|
|
308
|
+
selectRows(
|
|
309
|
+
this.connection,
|
|
310
|
+
`select fid, uid from ${table} where uid > ? order by uid asc, fid asc limit ?`,
|
|
311
|
+
[afterGroup, take],
|
|
312
|
+
),
|
|
313
|
+
fetchGroup: (group) =>
|
|
314
|
+
selectRows(
|
|
315
|
+
this.connection,
|
|
316
|
+
`select fid, uid from ${table} where uid = ? order by fid asc`,
|
|
317
|
+
[group],
|
|
318
|
+
),
|
|
319
|
+
})
|
|
320
|
+
return mapPage(page, mapForumSubscription)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async reputation(afterId: number, limit: number): Promise<Page<ImportedReputation>> {
|
|
324
|
+
return mapPage(
|
|
325
|
+
await this.#page<MybbReputation>(
|
|
326
|
+
`select rid, uid, adduid, pid, reputation, dateline, comments
|
|
327
|
+
from \`${this.prefix}reputation\``,
|
|
328
|
+
'rid',
|
|
329
|
+
(row) => row.rid,
|
|
330
|
+
afterId,
|
|
331
|
+
limit,
|
|
332
|
+
),
|
|
333
|
+
mapReputation,
|
|
334
|
+
)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async warnings(afterId: number, limit: number): Promise<Page<ImportedWarning>> {
|
|
338
|
+
return mapPage(
|
|
339
|
+
await this.#page<MybbWarning>(
|
|
340
|
+
`select wid, uid, pid, title, points, dateline, issuedby,
|
|
341
|
+
expires, daterevoked, revokereason, notes
|
|
342
|
+
from \`${this.prefix}warnings\``,
|
|
343
|
+
'wid',
|
|
344
|
+
(row) => row.wid,
|
|
345
|
+
afterId,
|
|
346
|
+
limit,
|
|
347
|
+
),
|
|
348
|
+
mapWarning,
|
|
349
|
+
)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async bans(afterId: number, limit: number): Promise<Page<ImportedBan>> {
|
|
353
|
+
return mapPage(
|
|
354
|
+
await this.#page<MybbBan>(
|
|
355
|
+
`select uid, admin, dateline, lifted, reason
|
|
356
|
+
from \`${this.prefix}banned\``,
|
|
357
|
+
'uid',
|
|
358
|
+
(row) => row.uid,
|
|
359
|
+
afterId,
|
|
360
|
+
limit,
|
|
361
|
+
),
|
|
362
|
+
mapBan,
|
|
363
|
+
)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async userRelations(afterId: number, limit: number): Promise<Page<ImportedUserRelation>> {
|
|
367
|
+
const rows = await selectRows<MybbRelationList>(
|
|
368
|
+
this.connection,
|
|
369
|
+
`select uid, buddylist, ignorelist
|
|
370
|
+
from \`${this.prefix}users\`
|
|
371
|
+
where uid > ? and (buddylist <> '' or ignorelist <> '')
|
|
372
|
+
order by uid asc limit ?`,
|
|
373
|
+
[afterId, limit],
|
|
374
|
+
)
|
|
375
|
+
const last = rows.at(-1)
|
|
376
|
+
return {
|
|
377
|
+
rows: rows.flatMap(mapRelationList),
|
|
378
|
+
nextCursor: rows.length < limit || last === undefined ? null : last.uid,
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
#page<T>(
|
|
383
|
+
select: string,
|
|
384
|
+
keyColumn: string,
|
|
385
|
+
idOf: (row: T) => number,
|
|
386
|
+
afterId: number,
|
|
387
|
+
limit: number,
|
|
388
|
+
): Promise<Page<T>> {
|
|
389
|
+
return keysetPage(this.connection, select, keyColumn, idOf, afterId, limit)
|
|
390
|
+
}
|
|
391
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import {
|
|
2
|
+
mapPhpbbAttachment,
|
|
3
|
+
mapPhpbbAvatar,
|
|
4
|
+
mapPhpbbBan,
|
|
5
|
+
mapPhpbbForum,
|
|
6
|
+
mapPhpbbPoll,
|
|
7
|
+
mapPhpbbPollVote,
|
|
8
|
+
mapPhpbbPost,
|
|
9
|
+
mapPhpbbPrivateMessage,
|
|
10
|
+
mapPhpbbTopic,
|
|
11
|
+
mapPhpbbUser,
|
|
12
|
+
mapPhpbbWarning,
|
|
13
|
+
mapPhpbbWatch,
|
|
14
|
+
mapPhpbbZebra,
|
|
15
|
+
noPhpbbReputation,
|
|
16
|
+
} from './phpbb-map'
|
|
17
|
+
import type { PhpbbTables, PhpbbWatch } from './phpbb-rows'
|
|
18
|
+
import { groupRows, type ImportSource, type Page, pageByGroup, pageById } from './source'
|
|
19
|
+
import type {
|
|
20
|
+
ImportedAttachment,
|
|
21
|
+
ImportedAvatar,
|
|
22
|
+
ImportedBan,
|
|
23
|
+
ImportedForum,
|
|
24
|
+
ImportedPoll,
|
|
25
|
+
ImportedPollVote,
|
|
26
|
+
ImportedPost,
|
|
27
|
+
ImportedPrivateMessage,
|
|
28
|
+
ImportedReputation,
|
|
29
|
+
ImportedSubscription,
|
|
30
|
+
ImportedThread,
|
|
31
|
+
ImportedUser,
|
|
32
|
+
ImportedUserRelation,
|
|
33
|
+
ImportedWarning,
|
|
34
|
+
} from './types'
|
|
35
|
+
|
|
36
|
+
function mapPage<Raw, Mapped>(page: Page<Raw>, map: (row: Raw) => Mapped | null): Page<Mapped> {
|
|
37
|
+
return {
|
|
38
|
+
rows: page.rows.map(map).filter((row): row is NonNullable<Mapped> => row !== null),
|
|
39
|
+
nextCursor: page.nextCursor,
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class FixturePhpbbSource implements ImportSource {
|
|
44
|
+
readonly name = 'phpbb'
|
|
45
|
+
|
|
46
|
+
constructor(private readonly data: PhpbbTables) {}
|
|
47
|
+
|
|
48
|
+
users(afterId: number, limit: number): Promise<Page<ImportedUser>> {
|
|
49
|
+
return Promise.resolve(
|
|
50
|
+
mapPage(
|
|
51
|
+
pageById(this.data.users ?? [], (row) => row.user_id, afterId, limit),
|
|
52
|
+
mapPhpbbUser,
|
|
53
|
+
),
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
forums(afterId: number, limit: number): Promise<Page<ImportedForum>> {
|
|
58
|
+
return Promise.resolve(
|
|
59
|
+
mapPage(
|
|
60
|
+
pageById(this.data.forums ?? [], (row) => row.forum_id, afterId, limit),
|
|
61
|
+
mapPhpbbForum,
|
|
62
|
+
),
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
threads(afterId: number, limit: number): Promise<Page<ImportedThread>> {
|
|
67
|
+
return Promise.resolve(
|
|
68
|
+
mapPage(
|
|
69
|
+
pageById(this.data.topics ?? [], (row) => row.topic_id, afterId, limit),
|
|
70
|
+
mapPhpbbTopic,
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
posts(afterId: number, limit: number): Promise<Page<ImportedPost>> {
|
|
76
|
+
return Promise.resolve(
|
|
77
|
+
mapPage(
|
|
78
|
+
pageById(this.data.posts ?? [], (row) => row.post_id, afterId, limit),
|
|
79
|
+
mapPhpbbPost,
|
|
80
|
+
),
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
avatars(afterId: number, limit: number): Promise<Page<ImportedAvatar>> {
|
|
85
|
+
return Promise.resolve(
|
|
86
|
+
mapPage(
|
|
87
|
+
pageById(this.data.avatars ?? [], (row) => row.user_id, afterId, limit),
|
|
88
|
+
mapPhpbbAvatar,
|
|
89
|
+
),
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
attachments(afterId: number, limit: number): Promise<Page<ImportedAttachment>> {
|
|
94
|
+
return Promise.resolve(
|
|
95
|
+
mapPage(
|
|
96
|
+
pageById(this.data.attachments ?? [], (row) => row.attach_id, afterId, limit),
|
|
97
|
+
mapPhpbbAttachment,
|
|
98
|
+
),
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
polls(afterId: number, limit: number): Promise<Page<ImportedPoll>> {
|
|
103
|
+
const page = pageById(this.data.pollTopics ?? [], (row) => row.topic_id, afterId, limit)
|
|
104
|
+
return Promise.resolve({
|
|
105
|
+
rows: page.rows
|
|
106
|
+
.map((topic) => mapPhpbbPoll(topic, this.data.pollOptions ?? []))
|
|
107
|
+
.filter((poll): poll is NonNullable<typeof poll> => poll !== null),
|
|
108
|
+
nextCursor: page.nextCursor,
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async pollVotes(afterId: number, limit: number): Promise<Page<ImportedPollVote>> {
|
|
113
|
+
const { fetch, fetchGroup } = groupRows(this.data.pollVotes ?? [], (row) => row.topic_id)
|
|
114
|
+
const page = await pageByGroup({
|
|
115
|
+
afterGroup: afterId,
|
|
116
|
+
limit,
|
|
117
|
+
groupOf: (row) => row.topic_id,
|
|
118
|
+
fetch,
|
|
119
|
+
fetchGroup,
|
|
120
|
+
})
|
|
121
|
+
return mapPage(page, mapPhpbbPollVote)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
privateMessages(afterId: number, limit: number): Promise<Page<ImportedPrivateMessage>> {
|
|
125
|
+
const page = pageById(this.data.privateMessages ?? [], (row) => row.msg_id, afterId, limit)
|
|
126
|
+
return Promise.resolve({
|
|
127
|
+
rows: page.rows
|
|
128
|
+
.map((message) => mapPhpbbPrivateMessage(message, this.data.privateMessageCopies ?? []))
|
|
129
|
+
.filter((message): message is NonNullable<typeof message> => message !== null),
|
|
130
|
+
nextCursor: page.nextCursor,
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
threadSubscriptions(afterId: number, limit: number): Promise<Page<ImportedSubscription>> {
|
|
135
|
+
return this.#watch(this.data.topicWatch ?? [], afterId, limit)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
forumSubscriptions(afterId: number, limit: number): Promise<Page<ImportedSubscription>> {
|
|
139
|
+
return this.#watch(this.data.forumWatch ?? [], afterId, limit)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
reputation(): Promise<Page<ImportedReputation>> {
|
|
143
|
+
return noPhpbbReputation()
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
warnings(afterId: number, limit: number): Promise<Page<ImportedWarning>> {
|
|
147
|
+
return Promise.resolve(
|
|
148
|
+
mapPage(
|
|
149
|
+
pageById(this.data.warnings ?? [], (row) => row.warning_id, afterId, limit),
|
|
150
|
+
mapPhpbbWarning,
|
|
151
|
+
),
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
bans(afterId: number, limit: number): Promise<Page<ImportedBan>> {
|
|
156
|
+
return Promise.resolve(
|
|
157
|
+
mapPage(
|
|
158
|
+
pageById(this.data.bans ?? [], (row) => row.ban_id, afterId, limit),
|
|
159
|
+
mapPhpbbBan,
|
|
160
|
+
),
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async userRelations(afterId: number, limit: number): Promise<Page<ImportedUserRelation>> {
|
|
165
|
+
const { fetch, fetchGroup } = groupRows(this.data.zebra ?? [], (row) => row.user_id)
|
|
166
|
+
const page = await pageByGroup({
|
|
167
|
+
afterGroup: afterId,
|
|
168
|
+
limit,
|
|
169
|
+
groupOf: (row) => row.user_id,
|
|
170
|
+
fetch,
|
|
171
|
+
fetchGroup,
|
|
172
|
+
})
|
|
173
|
+
return mapPage(page, mapPhpbbZebra)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async #watch(
|
|
177
|
+
rows: readonly PhpbbWatch[],
|
|
178
|
+
afterId: number,
|
|
179
|
+
limit: number,
|
|
180
|
+
): Promise<Page<ImportedSubscription>> {
|
|
181
|
+
const { fetch, fetchGroup } = groupRows(rows, (row) => row.user_id)
|
|
182
|
+
const page = await pageByGroup({
|
|
183
|
+
afterGroup: afterId,
|
|
184
|
+
limit,
|
|
185
|
+
groupOf: (row) => row.user_id,
|
|
186
|
+
fetch,
|
|
187
|
+
fetchGroup,
|
|
188
|
+
})
|
|
189
|
+
return mapPage(page, mapPhpbbWatch)
|
|
190
|
+
}
|
|
191
|
+
}
|