Skip to content

Commit eeeb431

Browse files
committed
feat: Introduce more temporary bulk importer (skin URLs and hostnames)
1 parent 7b870c2 commit eeeb431

6 files changed

Lines changed: 85 additions & 6 deletions

File tree

src/cli/commands/ImportCommand.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ import BulkQueueImporter, { type BulkQueueImportResult } from '../../import_queu
55
import CliCommand from './CliCommand.js';
66

77
type ImportCommandArgs = {
8-
type: 'uuid' | 'username' | 'profile-texture-value' | 'dir-with-skin-files',
8+
type: 'uuid' | 'username' | 'profile-texture-value' | 'dir-with-skin-files' | 'skin-urls' | 'domains',
99
filePath: string,
1010
apiKeyId: bigint
1111
};
1212

1313
@injectable({ token: ContainerTokens.CLI_COMMAND })
1414
export default class ImportCommand implements CliCommand {
15-
private readonly VALID_IMPORT_TYPES: string[] = ['uuid', 'username', 'profile-texture-value', 'dir-with-skin-files'] satisfies ImportCommandArgs['type'][];
15+
private readonly VALID_IMPORT_TYPES: string[] = ['uuid', 'username', 'profile-texture-value', 'dir-with-skin-files', 'skin-urls', 'domains'] satisfies ImportCommandArgs['type'][];
1616

1717
constructor(
1818
private readonly bulkQueueImporter: BulkQueueImporter,

src/import_queue/bulk/BulkQueueImporter.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@ import Fs from 'node:fs';
33
import Path from 'node:path';
44
import { singleton } from 'tsyringe';
55
import DatabaseClient from '../../database/DatabaseClient.js';
6+
import AutoProxiedHttpClient from '../../http/clients/AutoProxiedHttpClient.js';
7+
import ServerBlocklistService from '../../minecraft/server/blocklist/ServerBlocklistService.js';
68
import BulkImporter from './importer/BulkImporter.js';
9+
import DomainBulkImporter from './importer/DomainBulkImporter.js';
710
import ProfileTextureValueBulkImporter from './importer/ProfileTextureValueBulkImporter.js';
811
import SkinFileBulkImporter from './importer/SkinFileBulkImporter.js';
12+
import SkinUrlBulkImporter from './importer/SkinUrlBulkImporter.js';
913
import UsernameBulkImporter from './importer/UsernameBulkImporter.js';
1014
import UuidBulkImporter from './importer/UuidBulkImporter.js';
1115

@@ -22,10 +26,12 @@ export type BulkQueueImportResult = {
2226
export default class BulkQueueImporter {
2327
constructor(
2428
private readonly databaseClient: DatabaseClient,
29+
private readonly httpClient: AutoProxiedHttpClient,
30+
private readonly serverBlocklistService: ServerBlocklistService,
2531
) {
2632
}
2733

28-
async importEachLine(filePath: string, type: 'uuid' | 'username' | 'profile-texture-value', importingApiKeyId: bigint): Promise<BulkQueueImportResult> {
34+
async importEachLine(filePath: string, type: 'uuid' | 'username' | 'profile-texture-value' | 'skin-urls' | 'domains', importingApiKeyId: bigint): Promise<BulkQueueImportResult> {
2935
const fileHandle = await Fs.promises.open(filePath, 'r');
3036
const totalFileBytes = (await fileHandle.stat()).size;
3137

@@ -40,6 +46,12 @@ export default class BulkQueueImporter {
4046
case 'profile-texture-value':
4147
payloadImporter = new ProfileTextureValueBulkImporter(new UuidBulkImporter());
4248
break;
49+
case 'skin-urls':
50+
payloadImporter = new SkinUrlBulkImporter(this.httpClient);
51+
break;
52+
case 'domains':
53+
payloadImporter = new DomainBulkImporter(this.serverBlocklistService);
54+
break;
4355

4456
default:
4557
throw new Error(`Unsupported bulk queue import type: ${type}`);
@@ -94,7 +106,7 @@ export default class BulkQueueImporter {
94106
continue;
95107
}
96108

97-
insertBatch.push(...payloadImporter.createTasks(payload, importGroup.id));
109+
insertBatch.push(...(await payloadImporter.createTasks(payload, importGroup.id)));
98110
if (insertBatch.length >= 250) {
99111
const newlyQueued = await this.writeBatch(transaction, insertBatch);
100112
result.queued += newlyQueued;

src/import_queue/bulk/importer/BulkImporter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ import type * as PrismaClient from '@prisma/client';
33
export default interface BulkImporter {
44
isValidPayload(payload: string): true | string | Promise<true | string>;
55

6-
createTasks(payload: string, importGroupId: bigint): PrismaClient.Prisma.ImportTaskCreateManyInput[];
6+
createTasks(payload: string, importGroupId: bigint): PrismaClient.Prisma.ImportTaskCreateManyInput[]|Promise<PrismaClient.Prisma.ImportTaskCreateManyInput[]>;
77
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import * as PrismaClient from '@prisma/client';
2+
import type ServerBlocklistService from '../../../minecraft/server/blocklist/ServerBlocklistService.js';
3+
import type BulkImporter from './BulkImporter.js';
4+
5+
export default class DomainBulkImporter implements BulkImporter {
6+
constructor(
7+
private readonly serverBlocklistService: ServerBlocklistService,
8+
) {
9+
}
10+
11+
isValidPayload(payload: string): true | string {
12+
return true;
13+
}
14+
15+
async createTasks(payload: string, importGroupId: bigint): Promise<PrismaClient.Prisma.ImportTaskCreateManyInput[]> {
16+
try {
17+
await this.serverBlocklistService.checkBlocklist(payload);
18+
} catch (err) {
19+
console.error(`Error checking blocklist for Hostname ${payload}: ` + (err as Error).message);
20+
}
21+
22+
return [];
23+
}
24+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import * as PrismaClient from '@prisma/client';
2+
import type AutoProxiedHttpClient from '../../../http/clients/AutoProxiedHttpClient.js';
3+
import type BulkImporter from './BulkImporter.js';
4+
5+
export default class SkinUrlBulkImporter implements BulkImporter {
6+
constructor(
7+
private readonly httpClient: AutoProxiedHttpClient,
8+
) {
9+
}
10+
11+
isValidPayload(payload: string): true | string {
12+
try {
13+
new URL(payload);
14+
return true;
15+
} catch (err: any) {
16+
return `Invalid URL (${err.message}): ${JSON.stringify(payload)}`;
17+
}
18+
}
19+
20+
async createTasks(payload: string, importGroupId: bigint): Promise<PrismaClient.Prisma.ImportTaskCreateManyInput[]> {
21+
const skinImage = await this.httpClient.get(payload);
22+
if (skinImage.statusCode === 404) {
23+
console.warn('Skin URL returned 404 Not Found: ' + payload);
24+
return [];
25+
}
26+
if (skinImage.statusCode !== 200) {
27+
console.error(`Skin URL returned unexpected status code ${skinImage.statusCode}: ` + payload);
28+
return [];
29+
}
30+
31+
const contentType = skinImage.headers.get('content-type');
32+
if (typeof contentType === 'string' && !contentType.includes('image/') && contentType !== 'binary/octet-stream') {
33+
console.error(`Skin URL returned unexpected content type ${contentType}: ` + payload);
34+
return [];
35+
}
36+
37+
return [{
38+
payload: skinImage.body,
39+
payloadType: 'SKIN_IMAGE',
40+
importGroupId,
41+
}];
42+
}
43+
}

src/import_queue/worker/ContinuousQueueWorker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export default class ContinuousQueueWorker {
7878
private async tick(): Promise<void> {
7979
const task = await this.fetchNextTask();
8080
if (task == null) {
81-
console.debug('No tasks in the queue, waiting for new tasks...');
81+
// console.debug('No tasks in the queue, waiting for new tasks...');
8282
await this.tickForEmptyQueue();
8383
return;
8484
}

0 commit comments

Comments
 (0)