diff --git a/src/modules/api/api.ts b/src/modules/api/api.ts index 743ca5d..72567bf 100644 --- a/src/modules/api/api.ts +++ b/src/modules/api/api.ts @@ -37,6 +37,7 @@ import { shouldSearchDataFile, loadJSON, Result, + isQuestionValid, } from '../../utils/actions' import dbtools from '../../utils/dbtools' import auth from '../../middlewares/auth.middleware' @@ -58,7 +59,6 @@ import { // files const msgFile = 'stats/msgs' -const passwordFile = 'data/dataEditorPasswords.json' const dataEditsLog = 'stats/dataEdits' const dailyDataCountFile = 'stats/dailyDataCount' const usersDbBackupPath = 'data/dbs/backup' @@ -262,8 +262,7 @@ function GetApp(): ModuleType { // ------------------------------------------------------------- app.get('/getDbs', (req: Request, res: any) => { - logger.LogReq(req) - + // logger.LogReq(req) res.json( questionDbs.map((qdb) => { return { @@ -824,114 +823,6 @@ function GetApp(): ModuleType { }) // ------------------------------------------------------------------------------------------- - // API - - app.post('/uploaddata', (req: Request, res: any) => { - // body: JSON.stringify({ - // newData: data, - // count: getCount(data), - // initialCount: initialCount, - // password: password, - // editedQuestions: editedQuestions - // }) - - const { - count, - initialCount, - editedQuestions, - password /*, newData*/, - } = req.body - const respStatuses = { - invalidPass: 'invalidPass', - ok: 'ok', - error: 'error', - } - - logger.LogReq(req) - - try { - // finding user - const pwds = JSON.parse(utils.ReadFile(passwordFile)) - const userKey = Object.keys(pwds).find((key) => { - const userKey = pwds[key] - return userKey.password === password - }) - // FIXME: check user type in dataeditorPW-s json - const user: any = pwds[userKey] - - // logging and stuff - logger.Log(`Data upload`, logger.GetColor('bluebg')) - logger.Log(`PWD: ${password}`, logger.GetColor('bluebg')) - // returning if user password is not ok - if (!user) { - logger.Log( - `Data upload: invalid password ${password}`, - logger.GetColor('red') - ) - utils.AppendToFile( - utils.GetDateString() + - '\n' + - password + - '(FAILED PASSWORD)\n' + - JSON.stringify(editedQuestions) + - '\n\n', - dataEditsLog - ) - res.json({ status: respStatuses.invalidPass }) - return - } - - logger.Log( - `Password accepted for ${user.name}`, - logger.GetColor('bluebg') - ) - logger.Log( - `Old Subjects/Questions: ${initialCount.subjectCount} / ${ - initialCount.questionCount - } | New: ${count.subjectCount} / ${ - count.questionCount - } | Edited question count: ${Object.keys(editedQuestions).length}`, - logger.GetColor('bluebg') - ) - // saving detailed editedCount - utils.AppendToFile( - utils.GetDateString() + - '\n' + - JSON.stringify(user) + - '\n' + - JSON.stringify(editedQuestions) + - '\n\n', - dataEditsLog - ) - - // making backup - // TODO - // utils.CopyFile( - // './' + dataFile, - // `./publicDirs/qminingPublic/backs/data_before_${ - // user.name - // }_${utils.GetDateString().replace(/ /g, '_')}` - // ) // TODO: rewrite to dinamyc public!!! - // logger.Log('Backup made') - // // writing data - // utils.WriteFile(JSON.stringify(newData), dataFile) - // logger.Log('New data file written') - // // reloading data file - // data = [...newData] - // data = newData - logger.Log('Data set to newData') - - res.json({ - status: respStatuses.ok, - user: user.name, - }) - logger.Log('Data updating done!', logger.GetColor('bluebg')) - } catch (error) { - logger.Log(`Data upload error! `, logger.GetColor('redbg')) - console.error(error) - res.json({ status: respStatuses.error, msg: error.message }) - } - }) function getNewQdb(location, maxIndex) { logger.Log( @@ -1069,6 +960,10 @@ function GetApp(): ModuleType { function saveQuestion(questions, subj, testUrl, userid) { // TODO: clear folder every now and then, check if saved questions exist + if (!subj) { + logger.Log('No subj name to save test question') + return + } const questionsToSave = { questions: questions, subj: subj, @@ -1077,7 +972,7 @@ function GetApp(): ModuleType { date: new Date(), } const fname = `${utils.GetDateString()}_${userid}_${testUrl}.json` - const subject = getSubjNameWithoutYear(subj) + const subject = getSubjNameWithoutYear(subj).replace(/\//g, '-') const subjPath = `${savedQuestionsDir}/${subject}` const savedSubjQuestionsFilePath = `${subjPath}/${savedQuestionsFileName}` @@ -1451,6 +1346,354 @@ function GetApp(): ModuleType { res.json({ msg: 'done' }) }) + app.get('/possibleAnswers', (req: Request, res: any) => { + logger.LogReq(req) + const files = utils.ReadDir(savedQuestionsDir) + + files.sort(function(a, b) { + return ( + fs.statSync(savedQuestionsDir + '/' + b).mtime.getTime() - + fs.statSync(savedQuestionsDir + '/' + a).mtime.getTime() + ) + }) + + res.json({ + savedQuestionsFileName: savedQuestionsFileName, + subjects: files.map((subj) => { + return { + name: subj, + path: `savedQuestions/${subj}/`, + } + }), + }) + }) + + app.post('/rmPossibleAnswer', (req: Request, res: any) => { + logger.LogReq(req) + const user: User = req.session.user + + const subj = req.body.subj + const file = req.body.file + const savedQuestionsPath = `${savedQuestionsDir}/${subj}/${savedQuestionsFileName}` + const savedQuestions = utils.ReadJSON(savedQuestionsPath) + let path = `${savedQuestionsDir}/${subj}/${file}` + // to prevent deleting ../../../ ... /etc/shadow + while (path.includes('..')) { + path = path.replace(/\.\./g, '.') + } + + if (utils.FileExists(path)) { + utils.deleteFile(path) + + utils.WriteFile( + JSON.stringify( + savedQuestions.filter((sq) => { + return sq.fname !== file + }) + ), + savedQuestionsPath + ) + + logger.Log( + `User #${user.id} deleted '${file}' from subject '${subj}'`, + logger.GetColor('cyanbg') + ) + res.json({ + res: 'ok', + }) + } else { + logger.Log( + `User #${user.id} tried to delete '${file}' from subject '${subj}', but failed`, + logger.GetColor('red') + ) + res.json({ + res: 'fail', + }) + } + }) + + app.post('/updateQuestion', (req: Request, res) => { + logger.LogReq(req) + const user: User = req.session.user + const date = utils.GetDateString() + let saveDb = false + + const editType = req.body.type + const selectedDb = req.body.selectedDb + if (!editType || !selectedDb) { + res.json({ + status: 'fail', + msg: 'No .editType or .selectedDb !', + }) + return + } + + const dbIndex = questionDbs.findIndex((qdb) => { + return qdb.name === selectedDb.name + }) + const currDb = questionDbs[dbIndex] + + if (dbIndex === -1) { + res.json({ + status: 'fail', + msg: `No question db named like ${selectedDb.name}!`, + }) + return + } + + // { + // "index": 0, + // "subjName": "VHDL programozás", + // "type": "delete", + // "selectedDb": { + // "path": "questionDbs/elearning.uni-obuda.hu.json", + // "name": "elearning.uni-obuda.hu" + // } + // } + if (editType === 'delete') { + const { index, subjName } = req.body + let deletedQuestion = {} + if (isNaN(index) || !subjName) { + res.json({ + status: 'fail', + msg: 'No .index or .subjName !', + }) + return + } + + questionDbs[dbIndex].data = currDb.data.map((subj) => { + if (subj.Name !== subjName) { + return subj + } else { + return { + ...subj, + Questions: subj.Questions.filter((question, i) => { + if (index === i) { + deletedQuestion = question + return false + } else { + return true + } + }), + } + } + }) + + logger.Log( + `User #${user.id} deleted a question from '${subjName}'`, + logger.GetColor('cyanbg') + ) + utils.AppendToFile( + `${date}: User ${user.id} deleted a question from '${subjName}' (index: ${index})`, + dataEditsLog + ) + utils.AppendToFile(JSON.stringify(deletedQuestion, null, 2), dataEditsLog) + saveDb = true + } + + // { + // "index": 0, + // "subjName": "Elektronika", + // "type": "edit", + // "newVal": { + // "Q": "Analóg műszer esetén az érzékenység az a legkisebb mennyiség, amely a műszer kijelzőjén meghatározott mértékű változást okoz.", + // "A": "Igaz", + // "data": { + // "type": "simple", + // "possibleAnswers": [ + // "Igaz" + // ] + // }, + // "possibleAnswers": [ + // "Igaz" + // ] + // }, + // "selectedDb": { + // "path": "questionDbs/elearning.uni-obuda.hu.json", + // "name": "elearning.uni-obuda.hu" + // } + // } + if (editType === 'edit') { + const { index, subjName, newVal } = req.body + let oldVal = {} + if (isNaN(index) || !subjName) { + res.json({ + status: 'fail', + msg: 'No .index or .subjName !', + }) + return + } + if (!isQuestionValid(newVal)) { + res.json({ + status: 'fail', + msg: 'edited question is not valid', + question: newVal, + }) + return + } + + questionDbs[dbIndex].data = currDb.data.map((subj) => { + if (subj.Name !== subjName) { + return subj + } else { + return { + ...subj, + Questions: subj.Questions.map((question, i) => { + if (index === i) { + oldVal = question + return newVal + } else { + return question + } + }), + } + } + }) + + logger.Log( + `User #${user.id} edited a question in '${subjName}'`, + logger.GetColor('cyanbg') + ) + utils.AppendToFile( + `${date}: User ${user.id} edited a question in '${subjName}' (index: ${index})`, + dataEditsLog + ) + utils.AppendToFile( + JSON.stringify( + { + newVal: newVal, + oldVal: oldVal, + }, + null, + 2 + ), + dataEditsLog + ) + saveDb = true + } + + // { + // "subjName": "Elektronika", + // "changedQuestions": [ + // { + // "index": 1, + // "value": { + // "Q": "A műszer pontosságát a hibájával fejezzük ki, melyet az osztályjel (osztálypontosság ) mutat meg.", + // "A": "Hamis", + // "data": { + // "type": "simple", + // "possibleAnswers": [ + // "Igaz", + // "Hamis" + // ] + // } + // } + // } + // ], + // "deletedQuestions": [ + // 0 + // ], + // "type": "subjEdit", + // "selectedDb": { + // "path": "questionDbs/elearning.uni-obuda.hu.json", + // "name": "elearning.uni-obuda.hu" + // } + // } + if (editType === 'subjEdit') { + const { subjName, changedQuestions, deletedQuestions } = req.body + const deletedQuestionsToWrite = [] + const changedQuestionsToWrite = [] + if ( + !Array.isArray(changedQuestions) || + !Array.isArray(deletedQuestions) + ) { + res.json({ + status: 'fail', + msg: 'no changedQuestions or deletedQuestions!', + }) + return + } + + // processing changed questions + questionDbs[dbIndex].data = currDb.data.map((subj) => { + if (subj.Name !== subjName) { + return subj + } else { + return { + ...subj, + Questions: subj.Questions.map((question, i) => { + const changedTo = changedQuestions.find((cq) => { + return cq.index === i + }) + if (changedTo) { + changedQuestionsToWrite.push({ + oldVal: question, + newVal: changedTo.value, + }) + return changedTo.value + } else { + return question + } + }), + } + } + }) + + // processing deletedQuestions + questionDbs[dbIndex].data = currDb.data.map((subj) => { + if (subj.Name !== subjName) { + return subj + } else { + return { + ...subj, + Questions: subj.Questions.filter((question, i) => { + const isDeleted = deletedQuestions.includes(i) + if (isDeleted) { + deletedQuestionsToWrite.push(question) + return false + } else { + return true + } + }), + } + } + }) + + logger.Log( + `User #${user.id} modified '${subjName}'. Edited: ${deletedQuestionsToWrite.length}, deleted: ${deletedQuestionsToWrite.length}`, + logger.GetColor('cyanbg') + ) + utils.AppendToFile( + `${date} User #${user.id} modified '${subjName}'. Edited: ${deletedQuestionsToWrite.length}, deleted: ${deletedQuestionsToWrite.length}`, + dataEditsLog + ) + utils.AppendToFile( + JSON.stringify( + { + deletedQuestions: deletedQuestionsToWrite, + changedQuestions: changedQuestionsToWrite, + }, + null, + 2 + ), + dataEditsLog + ) + saveDb = true + } + + if (saveDb) { + utils.WriteFile(JSON.stringify(currDb.data), currDb.path) + msgAllWorker({ + qdbs: questionDbs, + type: 'update', + }) + } + + res.json({ + status: 'OK', + }) + }) + // ------------------------------------------------------------------------------------------- app.get('*', function(req: Request, res: any) { diff --git a/src/server.ts b/src/server.ts index 0364e31..6663bf3 100755 --- a/src/server.ts +++ b/src/server.ts @@ -161,7 +161,7 @@ const excludeFromStats = utils.ReadJSON(statExcludeFile) app.use( reqlogger({ loggableKeywords: ['news.json'], - loggableModules: ['dataeditor'], + loggableModules: [], exceptions: ['_next/static'], excludeFromStats: excludeFromStats, }) diff --git a/src/utils/actions.ts b/src/utils/actions.ts index b7c23b1..73d9593 100755 --- a/src/utils/actions.ts +++ b/src/utils/actions.ts @@ -268,7 +268,7 @@ function processIncomingRequestUsingDb( }) } -function isQuestionValid(question: Question) { +export function isQuestionValid(question: Question): Boolean { if (!question.Q) { return false } diff --git a/src/utils/classes.ts b/src/utils/classes.ts index 6ec914f..ccc9b15 100755 --- a/src/utils/classes.ts +++ b/src/utils/classes.ts @@ -619,6 +619,8 @@ if (!isMainThread) { // ) } else if (msg.type === 'update') { qdbs = msg.qdbs + logger.DebugLog(`Worker update ${workerIndex}`, 'worker update', 1) + // console.log(`[THREAD #${workerIndex}]: update`) } else if (msg.type === 'newdb') { qdbs.push(msg.newdb) diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 70d3579..c5d6bc6 100755 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -375,6 +375,9 @@ function C(color?: string): string { if (color === 'bluebg') { return '\x1b[44m' } + if (color === 'cyanbg') { + return '\x1b[46m' + } if (color === 'green') { return '\x1b[32m' } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index b192b63..bb0852f 100755 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -11,6 +11,7 @@ export default { CopyFile, GetDateString, formatUrl, + deleteFile: deleteFile, } import fs from 'fs' @@ -162,3 +163,11 @@ function AppendToFile(data: string, file: string): void { console.error(err) } } + +function deleteFile(fname: string): Boolean { + if (FileExists(fname)) { + fs.unlinkSync(fname) + return true + } + return false +} diff --git a/submodules/qmining-data-editor b/submodules/qmining-data-editor index 2d6211a..2ae8d7f 160000 --- a/submodules/qmining-data-editor +++ b/submodules/qmining-data-editor @@ -1 +1 @@ -Subproject commit 2d6211a80af3ccd6fe78d6e30c98f340f0579ac8 +Subproject commit 2ae8d7ffb21e8bcc42731e8a4a61fe819f850478 diff --git a/submodules/qmining-page b/submodules/qmining-page index e9e9f94..aae9433 160000 --- a/submodules/qmining-page +++ b/submodules/qmining-page @@ -1 +1 @@ -Subproject commit e9e9f94130e452e9d88eb0c1949b8f80e98c2f13 +Subproject commit aae943336ab052e61f61e20d2c3dd0b45a263183