Skip to content

Commit

Permalink
Merge pull request #156 from codeforsanjose/issues-57/design-new-admi…
Browse files Browse the repository at this point in the history
…n-page-questionnaire

Issues 57/design new admin page questionnaire
  • Loading branch information
JMStudiosJoe authored Aug 9, 2021
2 parents 6dc43b9 + 71b2791 commit e8ac3f4
Show file tree
Hide file tree
Showing 10 changed files with 423 additions and 90 deletions.
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 @@ -151,57 +155,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 @@ -219,11 +256,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),
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

0 comments on commit e8ac3f4

Please sign in to comment.