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
+44
View File
@@ -0,0 +1,44 @@
export const DEFAULT_CHUNK_SIZE = 200;
export const MIN_CHUNK_SIZE = 50;
export const MAX_CHUNK_SIZE = 8191;
export const DEFAULT_CHUNK_OVERLAP = 40;
export const MIN_CHUNK_OVERLAP = 25;
export const DEFAULT_MAX_OVERLAP_RATIO = 0.5;
export const DEFAULT_VECTOR_DIMENSIONS = 1536;
// File size limit (バイト)
export const MAX_FILE_SIZE = 100 * 1024 * 1024; // 100MB
export const DEFAULT_MAX_BATCH_SIZE = 2048;
// Supported languages
const SUPPORTED_LANGUAGES = ['zh', 'en', 'ja'] as const;
/**
* Get the default language from the environment variable.
* Fallback to 'en' (English) if not set or invalid.
*/
function getDefaultLanguage(): (typeof SUPPORTED_LANGUAGES)[number] {
const envValue = process.env.DEFAULT_LANGUAGE?.toLowerCase();
// Validate: must be one of supported languages
if (
envValue &&
SUPPORTED_LANGUAGES.includes(
envValue as (typeof SUPPORTED_LANGUAGES)[number],
)
) {
return envValue as (typeof SUPPORTED_LANGUAGES)[number];
}
// Fallback to English if not set or invalid
return 'en';
}
// Default language - read from env, fallback to English
export const DEFAULT_LANGUAGE = getDefaultLanguage();
export const DEFAULT_LANGUAGE_FALLBACK = 'en';
// システム全体の共通テナントID(シードデータetc.で使用)
export const GLOBAL_TENANT_ID = '00000000-0000-0000-0000-000000000000';
@@ -0,0 +1,64 @@
export const DOC_EXTENSIONS = [
'pdf',
'doc',
'docx',
'xls',
'xlsx',
'ppt',
'pptx',
'rtf',
'csv',
'txt',
'md',
'html',
'json',
'xml',
'odt',
'ods',
'odp',
];
export const CODE_EXTENSIONS = [
'js',
'jsx',
'ts',
'tsx',
'css',
'py',
'java',
'sql',
'cpp',
'h',
'go',
'rs',
'php',
'rb',
];
export const IMAGE_EXTENSIONS = [
'jpg',
'jpeg',
'png',
'gif',
'bmp',
'webp',
'tiff',
];
export const IMAGE_MIME_TYPES = [
'image/jpeg',
'image/png',
'image/gif',
'image/bmp',
'image/webp',
'image/tiff',
];
export const ALL_ALLOWED_EXTENSIONS = [
...DOC_EXTENSIONS,
...CODE_EXTENSIONS,
...IMAGE_EXTENSIONS,
];
export const isAllowedByExtension = (filename: string): boolean => {
const ext = filename.toLowerCase().split('.').pop();
if (!ext) return false;
return ALL_ALLOWED_EXTENSIONS.includes(ext);
};
+48
View File
@@ -0,0 +1,48 @@
/**
* Safely parses JSON from a string, handling markdown code blocks and leading/trailing text.
*/
export function safeParseJson<T = any>(text: string): T | null {
if (!text) return null;
let jsonStr = text.trim();
// 1. Try to extract JSON from markdown code blocks if they exist
// Matches ```json ... ``` or just ``` ... ```
const codeBlockRegex = /```(?:json)?\s*([\s\S]*?)\s*```/i;
const match = jsonStr.match(codeBlockRegex);
if (match && match[1]) {
jsonStr = match[1].trim();
} else {
// 2. If no markdown block, try to find the start and end of JSON characters
// This handles cases where the AI adds introductory or concluding text outside the block
const firstOpenBrace = jsonStr.indexOf('{');
const firstOpenBracket = jsonStr.indexOf('[');
let startIndex = -1;
if (firstOpenBrace !== -1 && (firstOpenBracket === -1 || firstOpenBrace < firstOpenBracket)) {
startIndex = firstOpenBrace;
} else if (firstOpenBracket !== -1) {
startIndex = firstOpenBracket;
}
if (startIndex !== -1) {
const lastCloseBrace = jsonStr.lastIndexOf('}');
const lastCloseBracket = jsonStr.lastIndexOf(']');
const endIndex = Math.max(lastCloseBrace, lastCloseBracket);
if (endIndex !== -1 && endIndex > startIndex) {
jsonStr = jsonStr.substring(startIndex, endIndex + 1);
}
}
}
try {
return JSON.parse(jsonStr) as T;
} catch (error) {
console.error('[safeParseJson] Failed to parse JSON:', error);
console.error('[safeParseJson] Original text:', text);
console.error('[safeParseJson] Extracted string:', jsonStr);
return null;
}
}