feat: implement QuestionBank CRUD with pagination and template query

- Add pagination support to findAll (page, limit query params)
- Add findByTemplateId method to service
- Add GET /by-template/:templateId endpoint to controller
- Service already includes CRUD for QuestionBank and QuestionBankItem
This commit is contained in:
Developer
2026-04-23 17:19:11 +08:00
commit 0a9588abb7
492 changed files with 112453 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { TikaService } from './tika.service';
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [ConfigModule],
providers: [TikaService],
exports: [TikaService],
})
export class TikaModule {}
+57
View File
@@ -0,0 +1,57 @@
import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as fs from 'fs';
import { I18nService } from '../i18n/i18n.service';
@Injectable()
export class TikaService {
private readonly logger = new Logger(TikaService.name);
private readonly tikaHost: string;
constructor(
private configService: ConfigService,
private i18nService: I18nService,
) {
const tikaHost = this.configService.get<string>('TIKA_HOST');
if (!tikaHost) {
throw new Error(this.i18nService.getMessage('tikaHostRequired'));
}
this.tikaHost = tikaHost;
}
async extractText(filePath: string): Promise<string> {
try {
// Use stream instead of reading entire file into memory
const fileStream = fs.createReadStream(filePath);
// Node.js native fetch supports passing a stream as body, but often requires 'duplex: "half"'
// and checking if we need to convert to Web Stream (Readable.toWeb) depends on Node version,
// but usually standard stream works or fs.openAsBlob (Node 20).
// Let's try passing the stream directly with duplex option.
const response = await fetch(`${this.tikaHost}/tika`, {
method: 'PUT',
headers: {
Accept: 'text/plain',
},
// @ts-expect-error - duplex is present in Node's RequestInit but strictly typed definitions might miss it
duplex: 'half',
body: fileStream as any,
});
if (!response.ok) {
const statusText = response.statusText;
// Ensure stream is closed if request failed
fileStream.destroy();
throw new Error(
`Tika extraction failed: ${response.status} ${statusText}`,
);
}
return await response.text();
} catch (error) {
this.logger.error(`Failed to extract text from ${filePath}`, error);
throw error;
}
}
}