@@ -3,13 +3,19 @@ import { join } from 'path'
33import { createId } from '@paralleldrive/cuid2'
44import { lock } from 'proper-lockfile'
55import type { H3Event } from 'h3'
6- import type { Question , Results , Answer } from '~/types'
6+ import type { Question , Results , Answer , InputQuestion } from '~/types'
77
88const DATA_DIR = join ( process . cwd ( ) , 'data' )
99const QUESTIONS_FILE = join ( DATA_DIR , 'questions.json' )
1010const ANSWERS_FILE = join ( DATA_DIR , 'answers.json' )
1111const ADMIN_FILE = join ( DATA_DIR , 'admin.json' )
12+ const PREDEFINED_QUESTIONS_FILE = join ( DATA_DIR , 'predefined-questions.json' )
13+ const PROCESSING_FILE = `${ PREDEFINED_QUESTIONS_FILE } .processing`
1214
15+ // Type guard to check for Node.js errors
16+ function isNodeError ( error : unknown ) : error is NodeJS . ErrnoException {
17+ return error instanceof Error && 'code' in error
18+ }
1319// Initialize storage with runtime config
1420async function initStorage ( event ?: H3Event ) {
1521 try {
@@ -23,6 +29,87 @@ async function initStorage(event?: H3Event) {
2329 await fs . writeFile ( QUESTIONS_FILE , JSON . stringify ( [ ] ) )
2430 }
2531
32+ // Process predefined questions atomically
33+ try {
34+ // Step 1: Rename the file to mark it as being processed.
35+ await fs . rename ( PREDEFINED_QUESTIONS_FILE , PROCESSING_FILE )
36+
37+ // Step 2: Read and validate the processing file.
38+ const predefinedData = await fs . readFile ( PROCESSING_FILE , 'utf-8' )
39+ let predefinedQuestions : InputQuestion [ ]
40+
41+ try {
42+ predefinedQuestions = JSON . parse ( predefinedData )
43+ }
44+ catch ( parseError : unknown ) {
45+ logger_error ( 'Malformed JSON in processing file:' , parseError )
46+ // Leave the .processing file for manual inspection.
47+ return
48+ }
49+
50+ if ( ! Array . isArray ( predefinedQuestions ) ) {
51+ logger_error ( 'Processing file must contain a JSON array.' )
52+ return
53+ }
54+
55+ for ( const q of predefinedQuestions ) {
56+ if ( typeof q . question_text !== 'string' || q . question_text . trim ( ) === '' ) {
57+ logger_error ( 'Invalid question_text in processing file:' , q )
58+ return
59+ }
60+ if ( ! Array . isArray ( q . answer_options ) || q . answer_options . length === 0 ) {
61+ logger_error ( 'Invalid answer_options in processing file:' , q )
62+ return
63+ }
64+ }
65+
66+ // Step 3: Atomically update the questions file.
67+ if ( predefinedQuestions . length > 0 ) {
68+ const release = await lock ( QUESTIONS_FILE )
69+ try {
70+ const questionsData = await fs . readFile ( QUESTIONS_FILE , 'utf-8' )
71+ const existingQuestions : Question [ ] = JSON . parse ( questionsData )
72+ const existingQuestionTexts = new Set ( existingQuestions . map ( q => q . question_text ) )
73+
74+ const newQuestions : Question [ ] = [ ]
75+ for ( const q of predefinedQuestions ) {
76+ if ( ! existingQuestionTexts . has ( q . question_text ) ) {
77+ existingQuestionTexts . add ( q . question_text ) // Add to set to prevent duplicates within the batch
78+ newQuestions . push ( {
79+ ...q ,
80+ id : createId ( ) ,
81+ is_locked : false
82+ } )
83+ }
84+ }
85+
86+ if ( newQuestions . length > 0 ) {
87+ const allQuestions = [ ...existingQuestions , ...newQuestions ]
88+ await fs . writeFile ( QUESTIONS_FILE , JSON . stringify ( allQuestions , null , 2 ) )
89+ logger ( `${ newQuestions . length } new predefined questions loaded successfully.` )
90+ }
91+ else {
92+ logger ( 'No new predefined questions to load.' )
93+ }
94+ }
95+ finally {
96+ await release ( )
97+ }
98+ }
99+
100+ // Step 4: Remove the processing file on success.
101+ await fs . unlink ( PROCESSING_FILE )
102+ }
103+ catch ( error : unknown ) {
104+ if ( isNodeError ( error ) && error . code === 'ENOENT' ) {
105+ // This is fine, no predefined questions file to process.
106+ }
107+ else {
108+ logger_error ( 'Error processing predefined questions:' , error )
109+ // If an error occurred, the .processing file is left for manual review.
110+ }
111+ }
112+
26113 // Initialize answers file
27114 try {
28115 await fs . access ( ANSWERS_FILE )
0 commit comments