Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issues 57/design new admin page questionnaire #156

Merged
merged 2 commits into from
Aug 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified backend/Questionnaire for Upload.xlsx
Binary file not shown.
2 changes: 1 addition & 1 deletion backend/models/questionnaires.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const questionnairesSchema = new Schema(
// _id: mongoose.Schema.Types.ObjectId,
// line above results in the following error "document must have an _id before saving"
title: { type: String, required: false, unique: false },
language: { type: String, required: false, unique: true },
language: { type: String, required: false, unique: false },
questions: { type: Array, required: true },
},
{
Expand Down
97 changes: 68 additions & 29 deletions backend/routes/admins.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');

const Admin = require('../models/admin');
const Questionnaires = require('../models/questionnaires');
const mongoose = require('mongoose');

const { loadQuestionnaireXlsxIntoDB, loadTranslationXlsxIntoDB } = require('./excelToDb');
const {
loadQuestionnaireXlsxIntoDB,
loadTranslationXlsxIntoDB,
} = require('./excelToDb');

const SALT_ROUNDS = 10;
const ERRMSG = { error: { message: 'Not logged in or auth failed' } };
Expand Down Expand Up @@ -149,57 +153,90 @@ router.route('/:id').delete((req, res) => {

/**
* verify that the http request comes from a admin user
* detect the user from the body containing the JWT token
* detect the user from the body containing the JWT token
* respond the request with an error if the token is missing or the user identified
* in token is not an admin user
* call the isAdminCallBack function if the user is an admin
**/
**/
function enforceAdminOnly(req, res, isAdminCallBack) {

//verify the request has a jwtToken beloning to an Admin User
if (!req.body || !req.body.jwToken) {
return res
.status(401)
.json({ error: { message: 'Missing JWT Token' } })
.json({ error: { message: 'Missing JWT Token' } });
}
jwt.verify(req.body.jwToken, process.env.JWT_KEY, function (err, token) {
if (err) {
return res
.status(401)
.json({ error: { message: 'Invalid JWT Token' } })
.json({ error: { message: 'Invalid JWT Token' } });
}
Admin.findOne({ email: token.email })
.exec((error, admin) => {
if (error || !admin) {
return res
.status(401)
.json({ error: { message: 'Invalid Admin User' } })
}
isAdminCallBack();
});
Admin.findOne({ email: token.email }).exec((error, admin) => {
if (error || !admin) {
return res
.status(401)
.json({ error: { message: 'Invalid Admin User' } });
}
isAdminCallBack();
});
});

}
//route for uploading the questionnaires spreadsheet in the database
router.route('/questionnairefile').post((req, res) => {
enforceAdminOnly(req, res, processQuestionnaireAsAdmin);
function processQuestionnaireAsAdmin() {
console.log(req.files, req.body);
if (!req.files || !req.files.questionnaire) {
return res
.status(400)
.json({ error: { message: 'Missing Questionnaire File' } });
}
if (req.files.questionnaire.truncated) {
return res
.status(400)
.json({ error: { message: 'Questionnaire File is too large' } });
return res.status(400).json({
error: { message: 'Questionnaire File is too large' },
});
}
const excelFileContent = req.files.questionnaire.data;
return loadQuestionnaireXlsxIntoDB(excelFileContent).then(() => {
res.status(200).send("Questionnaire Documenent Recieved");
}).catch((err) => {
res.status(500).send("Error, Storing Questionnaire in database");
});
const title = req.body.title;
return loadQuestionnaireXlsxIntoDB(excelFileContent, title)
.then(() => {
res.status(200).json({
message: 'Questionnaire Documenent Recieved',
});
})
.catch((err) => {
console.log(err);
res.status(500).json({
message: 'Error, Storing Questionnaire in database',
});
});
}
});
//route for deleteing a questionnaire by title
router.route('/deletequestionnaire/:title').delete((req, res) => {
enforceAdminOnly(req, res, deleteQuestionnaireByTitle);
function deleteQuestionnaireByTitle() {
return Questionnaires.deleteMany({
title: decodeURIComponent(req.params.title),
})
.then((results) => {
if (!results.ok) {
console.log('Delete Failed');
res.status(500).json({ message: 'Delete Failed' });
return;
}
if (result.deletedCount === 0) {
res.status(404).json({ message: 'Title not found' });
return;
}

res.status(200).json({ message: 'Questionnaire Removed' });
})
.catch((err) => {
res.status(500).json({
message: 'Error, Deleting Questionnaires from database',
});
});
}
});
//route for uploading the translation spreadsheet in the database
Expand All @@ -217,11 +254,13 @@ router.route('/translateContent').post((req, res) => {
.json({ error: { message: 'Translation File is too large' } });
}
const excelFileContent = req.files.translations.data;
return loadTranslationXlsxIntoDB(excelFileContent).then(() => {
res.status(200).send("Translation Document Recieved");
}).catch((err) => {
res.status(500).send("Error, Storing Translation in database");
});
return loadTranslationXlsxIntoDB(excelFileContent)
.then(() => {
res.status(200).send('Translation Document Recieved');
})
.catch((err) => {
res.status(500).send('Error, Storing Translation in database');
});
}
});

Expand Down
136 changes: 80 additions & 56 deletions backend/routes/excelToDb.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ const { LanguageOptions, WorkshopTitle } = require('../LanguageOptions');

/**
* load questionnaire excel file into objects in the Questionnaires collection
* excelFileContent - Node Buffer containing the excel file, this assumes must be formmated
* excelFileContent - Node Buffer containing the excel file, this assumes must be formmated
* with proper sheets for each langauge
* returns; a promise that resolves when operaiton is done
* */
function loadQuestionnaireXlsxIntoDB(excelFileContent) {
function loadQuestionnaireXlsxIntoDB(excelFileContent, title = WorkshopTitle) {
const questionnairePromises = LanguageOptions.map((language, idx) => {
const stream = new Readable();
stream.push(excelFileContent);
Expand All @@ -25,22 +25,31 @@ function loadQuestionnaireXlsxIntoDB(excelFileContent) {
rows.forEach((row, id) => {
if (id === 0) {
let errorMessage = '';
const validHeaders = ["#(id)", "Slug", "Category", "Text", "QuestionType", "AnswerSelections", "AnswerSelectionsValues", "Required?", "FollowUpQuestionSlug", "ParentQuestionSlug"];
const validHeaders = [
'#(id)',
'Slug',
'Category',
'Text',
'QuestionType',
'AnswerSelections',
'AnswerSelectionsValues',
'Required?',
'FollowUpQuestionSlug',
'ParentQuestionSlug',
];
if (row.length !== validHeaders.length) {
errorMessage = "invalid column name row";
}
else {
errorMessage = 'invalid column name row';
} else {
for (let i = 0; i < validHeaders.length; i++) {
if (row[i] !== validHeaders[i]) {
errorMessage = "invalid column name: " + row[i];
errorMessage = 'invalid column name: ' + row[i];
}
}
}
if (errorMessage) {
throw new Error(errorMessage);
}
return;

}

data.push({
Expand All @@ -60,83 +69,98 @@ function loadQuestionnaireXlsxIntoDB(excelFileContent) {
});
});
return Promise.all(questionnairePromises).then((questionnaires) => {
const title = WorkshopTitle;
const insertPromises = LanguageOptions.map((language, idx) => {
const questions = questionnaires[idx];

const insertNewQuestionnaire = () => {
return Questionnaires.insertMany({ title, language: language.code, questions })
return Questionnaires.insertMany({
title,
language: language.code,
questions,
});
};

const removeExistingQuestionnaires = (_id) => {
return Questionnaires.findByIdAndDelete({ _id })
return Questionnaires.findByIdAndDelete({ _id });
};

return Questionnaires.find({ title, language: language.code }).then((result) => {
if (result.length !== 0) {
return removeExistingQuestionnaires(result[0]._id).then(() => {
return Questionnaires.find({ title, language: language.code }).then(
(result) => {
if (result.length !== 0) {
return removeExistingQuestionnaires(result[0]._id).then(
() => {
return insertNewQuestionnaire();
}
);
} else {
return insertNewQuestionnaire();
});
} else {
return insertNewQuestionnaire();
}
}
});
);
});
return Promise.all(insertPromises);
});
}

/**
* load translation excel file into objects in the TranslatedContent collection
* excelFileContent - Node Buffer containing the excel file, this assumes must be formmated
* excelFileContent - Node Buffer containing the excel file, this assumes must be formmated
* with proper translation sheet format
* returns; a promise that resolves when operaiton is done
* */
function loadTranslationXlsxIntoDB(excelFileContent) {
const stream = new Readable();
stream.push(excelFileContent);
stream.push(null);
return xlsxFile(stream).then((rows) => {
return xlsxFile(stream)
.then((rows) => {
const data = rows.reduce((obj, row) => {
for (let i = 1; i < row.length; i++) {
const languageObject = obj[LanguageOptions[i - 1].code];

const data = rows.reduce((obj, row) => {
for (let i = 1; i < row.length; i++) {
const languageObject = obj[LanguageOptions[i - 1].code];

if (languageObject) {
languageObject[row[0]] = row[i];
} else {
obj[LanguageOptions[i - 1].code] = {
[row[0]]: row[i],
};
if (languageObject) {
languageObject[row[0]] = row[i];
} else {
obj[LanguageOptions[i - 1].code] = {
[row[0]]: row[i],
};
}
}
}
return obj;
}, {});
return data;
}).then((translations) => {
const title = WorkshopTitle;
const insertPromises = LanguageOptions.map((language) => {
const content = translations[language.code];
const insertNewTranslatedContent = () => {
return TranslatedContent.insertMany({ title, language: language.code, content })
};
return obj;
}, {});
return data;
})
.then((translations) => {
const title = WorkshopTitle;
const insertPromises = LanguageOptions.map((language) => {
const content = translations[language.code];
const insertNewTranslatedContent = () => {
return TranslatedContent.insertMany({
title,
language: language.code,
content,
});
};

const removeExistingTranslatedContent = (_id) => {
return TranslatedContent.findByIdAndDelete({ _id })
};
const removeExistingTranslatedContent = (_id) => {
return TranslatedContent.findByIdAndDelete({ _id });
};

return TranslatedContent.find({ title, language: language.code }).then((result) => {
if (result.length !== 0) {
return removeExistingTranslatedContent(result[0]._id).then(() => {
return TranslatedContent.find({
title,
language: language.code,
}).then((result) => {
if (result.length !== 0) {
return removeExistingTranslatedContent(
result[0]._id
).then(() => {
return insertNewTranslatedContent();
});
} else {
return insertNewTranslatedContent();
})
}
else {
return insertNewTranslatedContent();
}
}
});
});
return Promise.all(insertPromises);
});
return Promise.all(insertPromises);
});
}
module.exports = { loadQuestionnaireXlsxIntoDB, loadTranslationXlsxIntoDB };
module.exports = { loadQuestionnaireXlsxIntoDB, loadTranslationXlsxIntoDB };
2 changes: 1 addition & 1 deletion backend/routes/questionnaires/questionnaires.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ router.route('/').get((req, res) => {

router.route('/:title.:language').get((req, res) => {
Questionnaires.findOne({
title: req.params.title,
title: decodeURIComponent(req.params.title),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this needed here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in case the title has any special characters or spaces

language: req.params.language,
})
.then((questionnaires) => {
Expand Down
5 changes: 5 additions & 0 deletions src/containers/App/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import MainContainer from '../MainContainer/MainContainer';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Admin from '../../compositions/Admin/Admin.js';
import AdminDashboard from '../../containers/AdminDashboard/AdminDashboard';
import EditQuestionnaires from '../../containers/EditQuestionnaires/EditQuestionnaires';
import './App.css';
import { uploadQuestinnaires } from '../../sendRequest/apis';

function App() {
return (
Expand All @@ -16,6 +18,9 @@ function App() {
<Route path="/login">
<Admin />
</Route>
<Route path="/questionnaires">
<EditQuestionnaires />
</Route>
<Route path="/">
<MainContainer />
</Route>
Expand Down
Loading