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:
@@ -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);
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user