Fix most glaring bugs and vulnerabilities

This commit is contained in:
Luka Romih
2023-05-08 23:25:25 +02:00
parent 9867875ef2
commit d0e53fee3b
144 changed files with 2231 additions and 2981 deletions
+65 -11
View File
@@ -11,6 +11,25 @@ exports.listComments = async (req, res) => {
filters.ctxId = null
}
if (filters.ctxType === 'entry_dict_int') {
const { dictionary_id: dictionaryId } = await Entry.fetch(filters.ctxId)
const isEditor = req.user.hasAnyDictionaryRole(dictionaryId)
const isPortalAdmin = req.user.hasRole('portal admin')
const isDictionariesAdmin = req.user.hasRole('dictionaries admin')
if (!(isEditor || isPortalAdmin || isDictionariesAdmin)) {
return res.status(400).end()
}
} else if (filters.ctxType === 'entry_consult_int') {
const isConsultantForEntry = req.user.isEditorOfConsultancyEntry(
filters.ctxId
)
const isPortalAdmin = req.user.hasRole('portal admin')
const isConsultancyAdmin = req.user.hasRole('consultancy admin')
if (!(isConsultantForEntry || isPortalAdmin || isConsultancyAdmin)) {
return res.status(400).end()
}
}
const {
pages_total: numberOfAllPages,
comments,
@@ -38,20 +57,55 @@ exports.createComment = async (req, res) => {
res.send({ comments, pagesTotal })
}
exports.seedComments = async (req, res) => {
const { commentCount } = req.params
await Comment.seed(commentCount)
res.send(`${commentCount} new comments generated`)
}
exports.clearComments = async (req, res) => {
await Comment.clear()
res.send('All comments cleared')
}
exports.updateStatus = async (req, res) => {
const commentId = req.body.params.id
const commentStatus = req.body.params.status
const { ctxType, ctxId } = await Comment.fetchContextById(commentId)
let canUpdateStatus = false
switch (ctxType) {
case 'portal':
if (req.user.hasRole('portal admin')) canUpdateStatus = true
break
case 'dictionary':
if (
req.user.hasRole('portal admin') ||
req.user.hasRole('dictionaries admin')
) {
canUpdateStatus = true
}
break
case 'consultancy':
if (
req.user.hasRole('portal admin') ||
req.user.hasRole('consultancy admin')
) {
canUpdateStatus = true
}
break
case 'entry_dict_ext': {
const { dictionary_id: dictionaryId } = await Entry.fetch(ctxId)
if (
req.user.hasRole('portal admin') ||
req.user.hasRole('dictionaries admin') ||
req.user.hasDictionaryRole(dictionaryId, 'administration')
) {
canUpdateStatus = true
}
break
}
default:
throw Error('Invalid context type')
}
if (!canUpdateStatus) return res.status(400).end()
await Comment.updateStatus(commentId, commentStatus)
res.send('Visibility changed')
}
+36 -37
View File
@@ -1,3 +1,4 @@
const user = require('../../../middleware/user')
const ConsultancyEntry = require('../../../models/consultancy-entry')
const Domain = require('../../../models/domain')
const User = require('../../../models/user')
@@ -16,20 +17,6 @@ const generateQuery = require('../../../models/helpers/search/generate-query')
const consultancy = {}
consultancy.listEntries = async (req, res) => {
const consultancyEntryList = await ConsultancyEntry.fetchAll()
const data = {}
data.consEntryList = consultancyEntryList
res.send(data)
}
consultancy.listNewEntries = async (req, res) => {
const consultancyNewEntryList = await ConsultancyEntry.fetchAllNew()
const data = {}
data.consultancyNewEntryList = consultancyNewEntryList
res.send(data)
}
consultancy.sendPaginationData = async (req, res) => {
let requestType = req.query.type
const isAdminPage = req.query.isAdmin === 'true'
@@ -325,9 +312,12 @@ consultancy.publish = async (req, res) => {
const author = await User.fetchUser(entry.authorId)
const portalName = await getInstanceSetting(`portal_name_${author.language}`)
const renderAsync = promisify(req.app.render.bind(req.app))
const emailHtml = await renderAsync('email/consultancy-publish-notify', {
portalName
})
const emailHtml = await renderAsync(
`email/consultancy-publish-notify_${author.language}`,
{
portalName
}
)
await email.send({
to: author.email,
subject: i18next.t('Objava terminološkega vprašanja', {
@@ -342,29 +332,38 @@ consultancy.publish = async (req, res) => {
res.send()
}
consultancy.updateQuestion = async (req, res) => {
const { id, questionTitle, domain: domainId, question, answer } = req.body
consultancy.updateQuestion = [
(req, res, next) => {
const { id } = req.body
if (!id) return res.status(400).send({})
if (!id) return res.status(400).send({})
if (questionTitle === '' || question === '' || answer === '') {
return res
.status(422)
.send(req.t('Polja naslov, vprašanje in mnenje so obvezna!'))
req.entryId = id
next()
},
user.canConsultEntry,
async (req, res) => {
const { id, questionTitle, domain: domainId, question, answer } = req.body
if (questionTitle === '' || question === '' || answer === '') {
return res
.status(422)
.send(req.t('Polja naslov, vprašanje in mnenje so obvezna!'))
}
const entry = await ConsultancyEntry.fetchById(id)
entry.domainPrimaryId = domainId > 0 ? domainId : null
entry.question = question
entry.answer = answer // helper.removeHtmlTags(answer).trim()
entry.title = questionTitle
// TODO Luka: Miha, update only fields that were updated.
await ConsultancyEntry.updateQuestion(entry)
await ConsultancyEntry.indexIntoSearchEngine(id, true)
res.send({})
}
const entry = await ConsultancyEntry.fetchById(id)
entry.domainPrimaryId = domainId > 0 ? domainId : null
entry.question = question
entry.answer = answer // helper.removeHtmlTags(answer).trim()
entry.title = questionTitle
// TODO Luka: Miha, update only fields that were updated.
await ConsultancyEntry.updateQuestion(entry)
await ConsultancyEntry.indexIntoSearchEngine(id, true)
res.send({})
}
]
consultancy.insertNonModerator = async (req, res) => {
const questionId = req.body.question_id
@@ -1,15 +0,0 @@
const DemoPaginacija = require('../../../models/demo-paginacija')
const { DEFAULT_HITS_PER_PAGE } = require('../../../config/settings')
exports.list = async (req, res) => {
const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
const page = +req.query.p > 0 ? +req.query.p : 1
const { pages_total: numberOfAllPages, results } = await DemoPaginacija.fetch(
resultsPerPage,
page
)
res.send({ page, numberOfAllPages, results })
}
+30 -19
View File
@@ -1,4 +1,5 @@
const { rm } = require('fs/promises')
const user = require('../../../middleware/user')
const Dictionary = require('../../../models/dictionary')
const Entry = require('../../../models/entry')
const Extraction = require('../../../models/extraction')
@@ -69,7 +70,11 @@ dictionary.deleteEntry = async (req, res) => {
await Promise.all([
Dictionary.updateMetadataAfterModifyingEntries(dictionaryId),
deleteEntryFromIndex(entryId, true),
minEntriesRequirementCheckAndAct.onDelete(dictionaryId, req.app)
minEntriesRequirementCheckAndAct.onDelete(
dictionaryId,
req.app,
req.determinedLanguage
)
])
res.end()
@@ -84,7 +89,11 @@ dictionary.deleteAllEntries = async (req, res) => {
await Promise.all([
Dictionary.updateMetadataAfterModifyingEntries(dictionaryId),
deleteDictionaryEntriesFromIndex(dictionaryId),
minEntriesRequirementCheckAndAct.onDelete(dictionaryId, req.app)
minEntriesRequirementCheckAndAct.onDelete(
dictionaryId,
req.app,
req.determinedLanguage
)
])
res.end()
@@ -124,12 +133,19 @@ dictionary.delete = async (req, res) => {
res.end()
}
dictionary.updateDomainLabels = async (req, res) => {
const { dictionaryId, payload } = req.body.params
dictionary.updateDomainLabels = [
(req, res, next) => {
req.dictionaryId = req.body.params.dictionaryId
next()
},
user.canAdministrateDictionary,
async (req, res) => {
const { dictionaryId, payload } = req.body.params
await Dictionary.updateDomainLabel(dictionaryId, payload)
res.end()
}
await Dictionary.updateDomainLabel(dictionaryId, payload)
res.end()
}
]
dictionary.renovateSecondaryDomains = async (req, res) => {
const data = req.body.params.payload
@@ -152,7 +168,11 @@ dictionary.listDictionaries = async (req, res) => {
const page = +req.query.p > 0 ? +req.query.p : 1
const { pages_total: numberOfAllPages, results } =
await Dictionary.fetchAllAdminDictionaries(resultsPerPage, page)
await Dictionary.fetchAllAdminDictionaries(
req.determinedLanguage,
resultsPerPage,
page
)
res.send({ page, numberOfAllPages, results })
}
@@ -222,15 +242,6 @@ dictionary.listSecondaryDomainData = async (req, res) => {
})
}
dictionary.listSecondaryDomains = async (req, res) => {
const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
const page = +req.query.p > 0 ? +req.query.p : 1
const { pages_total: numberOfAllPages, results } =
await Dictionary.fetchAllSecondaryDomains(resultsPerPage, page)
res.send({ page, numberOfAllPages, results })
}
dictionary.showImportFromFileForm = async (req, res) => {
const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
const { dictionaryId } = req.params
@@ -252,7 +263,7 @@ dictionary.showExportToFileForm = async (req, res) => {
}
dictionary.importFromExtraction = async (req, res) => {
const { id: dictionaryId, extractionId } = req.params
const { dictionaryId, extractionId } = req.params
const { from, to } = req.body
const fromIndex = +from > 1 ? Math.floor(from) - 1 : 0
const toIndex = Number.isInteger(+(to === '' ? undefined : to))
@@ -279,7 +290,7 @@ dictionary.importFromExtraction = async (req, res) => {
}
dictionary.exportBegin = async (req, res) => {
const dictionaryId = req.params.id
const { dictionaryId } = req.params
const exportParams = {
isValidFilter:
req.body.isValidFilter === 'on' ? undefined : req.body.isValidFilter,
+2 -7
View File
@@ -166,7 +166,7 @@ extraction.ossSearch = [
extraction.ossConfirmParams = async (req, res) => {
const { id: extractionId } = req.params
const { ossParams } = await Extraction.fetch(extractionId)
const { ossParams } = req.extractionData
if (ossParams.status !== 'valid') throw Error('OSS params not valid')
await Extraction.updateOssParams(extractionId, {
params: ossParams.params,
@@ -177,7 +177,7 @@ extraction.ossConfirmParams = async (req, res) => {
extraction.begin = async (req, res) => {
const extractionId = req.params.id
const extraction = await Extraction.fetch(extractionId)
const extraction = req.extractionData
const canBegin = await checkIfcanBegin(extraction)
if (!canBegin) throw Error('Extraction does not qualify to be ran')
@@ -268,11 +268,6 @@ extraction.termCandidatesExport = async (req, res) => {
}
}
extraction.listFinishedForUser = async (req, res) => {
const extractions = await Extraction.fetchFinishedForUser(req.user.id)
res.send(extractions)
}
extraction.listTermCandidates = async (req, res) => {
const extractionId = req.params.id
const termCandidatesJson = await Extraction.fetchTermCandidatesJson(
+6 -2
View File
@@ -178,7 +178,8 @@ exports.listFilteredDictionaries = async (req, res) => {
hitsPerPage,
page,
orderAttribute,
orderIndex
orderIndex,
req.determinedLanguage
)
dictionaries = dictionaries.map(e => {
@@ -246,6 +247,9 @@ exports.showModalFilterResults = async (req, res) => {
const aggregationRaw = await searchEntryIndex(aggregateQuery)
const aggregation = await prepareAggregation(aggregationRaw)
const aggregation = await prepareAggregation(
aggregationRaw,
req.determinedLanguage
)
res.send(prepareSeachFilterData(aggregation, filters))
}
+4 -3
View File
@@ -1,9 +1,10 @@
const debug = require('debug')('termPortal:controllers/api/v1/system')
const Eurotermbank = require('../../../models/system/eurotermbank')
// const debug = require('debug')('termPortal:controllers/api/v1/system')
exports.handleCspReports = (req, res) => {
debug(req.body)
res.sendStatus(200)
// eslint-disable-next-line no-console
console.error(req.body)
res.end()
}
exports.syncWithEurotermbank = async (req, res) => {
+105 -6
View File
@@ -1,5 +1,14 @@
const { randomBytes } = require('crypto')
const { promisify } = require('util')
const RandomBytesAsync = promisify(randomBytes)
const User = require('../../../models/user')
const { DEFAULT_HITS_PER_PAGE } = require('../../../config/settings')
const email = require('../../../models/email')
const { logout: logoutUser } = require('../../../middleware/user')
const { origin } = require('../../../config/keys')
const {
DEFAULT_HITS_PER_PAGE,
CHANGE_EMAIL_TOKEN_VALID_DAYS
} = require('../../../config/settings')
const users = {}
users.listUsers = async (req, res) => {
@@ -23,13 +32,103 @@ users.updateHitsPerPage = async (req, res) => {
res.status(200).send()
}
users.updateFristNameAndSurname = async (req, res) => {
const firstname = req.body.name
const surname = req.body.surname
users.updateBasicData = async (req, res) => {
const { firstName, lastName, email: newEmail } = req.body
await User.updateFirstNameAndLastName(req.user.userName, firstname, surname)
// TODO Validation (valid email format, ...).
res.status(200).send()
const oldEmail = await User.updateFirstNameAndLastName(
req.user.id,
firstName,
lastName
)
if (newEmail === oldEmail) return res.send()
if (await User.isEmailAlreadyTaken(newEmail)) {
req.flash('info', req.t('Elektronski naslov uporablja že drug uporabnik.'))
return res.send()
}
const changeEmailToken = (await RandomBytesAsync(32)).toString('hex')
await User.saveChangeEmailToken(req.user.id, changeEmailToken, newEmail)
let changeEmailLink = new URL('/sprememba-elektronskega-naslova', origin)
changeEmailLink.searchParams.set('token', changeEmailToken)
changeEmailLink = changeEmailLink.href
const renderAsync = promisify(req.app.render.bind(req.app))
const emailHtml = await renderAsync(
`email/user-change-email-token_${req.language}`,
{
username: req.user.userName,
changeEmailLink
}
)
await email.send({
to: newEmail,
subject: req.t('Sprememba elektronskega naslova'),
html: emailHtml
})
const message =
req.t(
'Na vaš elektronski naslov smo vam poslali sporočilo s povezavo, s katero boste potrdili menjavo elektronskega naslova. Povezava za potrditev je veljavna '
) +
`${CHANGE_EMAIL_TOKEN_VALID_DAYS} ` +
req.t('dni.')
req.flash('info', message)
res.send()
}
users.updatePassword = async (req, res) => {
const { passwordOld, passwordNew, passwordNewRepeat } = req.body
// TODO Validation (mirror front end validation, ...).
if (passwordNew !== passwordNewRepeat) {
const err = Error(req.t('Gesli se ne ujemata'))
err.status = 403
err.displayInProd = true
throw err
}
await User.changePassword(req.user.id, passwordOld, passwordNew, req.t)
// TODO Invalidate or log out all session for this user. More details in deleteCurrent method TODO.
const renderAsync = promisify(req.app.render.bind(req.app))
const emailHtml = await renderAsync(
`email/user-change-password_${req.language}`,
{
username: req.user.userName
}
)
await email.send({
to: req.user.email,
subject: req.t('Sprememba gesla'),
html: emailHtml
})
req.flash('info', 'Geslo je bilo spremenjeno.')
res.send()
}
users.deleteCurrent = [
async (req, res, next) => {
await User.closeAccount(req.user.id)
next()
},
logoutUser,
(req, res) => {
// TODO Invalidate or log out all session for this user. Current workaround is in passport.deserializeUser.
// You can probably do it in 1 of 3 ways:
// 1. Brute force; loop through all sessions (using session store's all or ids methods),
// look up their values and remote the ones with user's id
// 2. Include user Id as part of session key; something similar to https://github.com/tj/connect-redis/issues/210#issuecomment-1336545115
// 3. Create some kind of inverse index, mapping user id to his/hers sessions (also mentioned in issue linked above)
req.flash('info', req.t('Vaš uporabniški račun je bil uspešno izbrisan.'))
res.send()
}
]
module.exports = users
+15 -21
View File
@@ -46,11 +46,15 @@ consultancy.search = async (req, res) => {
consultancy.specificQuestion = async (req, res) => {
const { id } = req.params
// TODO validation: is id of proper format and does a question with it actually exist.
// TODO i18n TIME FORMAT
const entry = await ConsultancyEntry.fetchByIdWithFormattedTime(id)
// const author = await User.fetchUser(entry.authorId)
const allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains()
const allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains(
req.determinedLanguage
)
entry.answerAuthors = entry.answerAuthors.filter(author => author !== '')
@@ -86,7 +90,9 @@ consultancy.specificQuestion = async (req, res) => {
}
consultancy.new = async (req, res) => {
const allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains()
const allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains(
req.determinedLanguage
)
res.locals.isOwnConsultancyEnabled =
(await getInstanceSetting('consultancy_type')) === 'own'
@@ -110,12 +116,9 @@ consultancyAdmin.new = async (req, res) => {
}
consultancyAdmin.users = async (req, res) => {
const allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains()
const users = await User.fetchConsultants()
res.render('pages/consultancy/admin/users', {
allPrimaryDomains,
users,
title: req.t('Svetovalci')
})
@@ -175,11 +178,7 @@ consultancyAdmin.inProgress = async (req, res) => {
}
consultancyAdmin.statistics = async (req, res) => {
const allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains()
res.render('pages/consultancy/admin/statistics', {
allPrimaryDomains
})
res.render('pages/consultancy/admin/statistics')
}
consultancyAdmin.edit = async (req, res) => {
@@ -189,18 +188,11 @@ consultancyAdmin.edit = async (req, res) => {
sentFrom[key] = true
const moderator = await ConsultancyEntry.getModerator(id)
const editors = await ConsultancyEntry.getEditors(id)
if (
req.user.hasRole('consultancy admin') ||
req.user.hasRole('portal admin')
) {
console.log('Editor guard omitted due to being administrator')
} else if (editors.filter(editors => editors.id === req.user.id) < 1) {
return res.send('You do not have permsisions to edit this answer')
}
// TODO i18n TIME FORMAT
const entry = await ConsultancyEntry.fetchByIdWithFormattedTime(id)
const allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains()
const allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains(
req.determinedLanguage
)
const author = await User.fetchUser(entry.authorId)
const isPublished = entry.status === 'published'
@@ -318,7 +310,9 @@ async function consultancyRequest(
) {
const searchString = req.query.q?.trim() ?? ''
let allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains()
let allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains(
req.determinedLanguage
)
/// filter selected domains for the prompt ///
// Not duplicates of this code arise...
-17
View File
@@ -1,17 +0,0 @@
const DemoPaginacija = require('../models/demo-paginacija')
const { DEFAULT_HITS_PER_PAGE } = require('../config/settings')
const demoPaginacija = {}
demoPaginacija.izrišiStran = async (req, res) => {
const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
const { pages_total: numberOfAllPages, results } = await DemoPaginacija.fetch(
resultsPerPage,
1
)
res.render('pages/demo-paginacija', { numberOfAllPages, results })
}
module.exports = demoPaginacija
+78 -50
View File
@@ -2,6 +2,7 @@ const { unlink } = require('fs/promises')
const { promisify } = require('util')
const multer = require('multer')
const debug = require('debug')('termPortal:controllers/dictionary')
const user = require('../middleware/user')
const Dictionary = require('../models/dictionary')
const Entry = require('../models/entry')
const User = require('../models/user')
@@ -19,6 +20,7 @@ const {
const SFDSuggestionImporter = require('./helpers/search-filter-data-suggestion-importer')
const Extraction = require('../models/extraction')
const { isGeneratorFunction } = require('util/types')
const { capitalize } = require('../utils')
const importFileBodyParser = multer({
dest: `${DATA_FILES_PATH}/dict_import_temp`,
@@ -38,7 +40,10 @@ const dictionary = {}
dictionary.list = async (req, res) => {
let dictionaries
if (req.isAuthenticated()) {
dictionaries = await Dictionary.fetchAllByUser(req.user.id)
dictionaries = await Dictionary.fetchAllByUser(
req.user.id,
req.determinedLanguage
)
}
res.render('pages/dictionaries/list', {
title: req.t('Seznam slovarjev'),
@@ -47,13 +52,11 @@ dictionary.list = async (req, res) => {
}
dictionary.new = async (req, res) => {
// TODO Once english language is implemented, gather selected language (sl/en) from request ~ (cookies?)
const language = 'name_sl'
const [allPrimaryDomains, allSecondaryDomains, allLanguages] =
await Promise.all([
Dictionary.fetchAllPrimaryDomains(),
Dictionary.fetchAllPrimaryDomains(req.determinedLanguage),
Dictionary.fetchAllApprovedSecondaryDomains(),
Dictionary.fetchAllLanguages(language, true)
Dictionary.fetchAllLanguages(req.determinedLanguage, true)
])
res.render('pages/dictionaries/new', {
@@ -66,7 +69,7 @@ dictionary.new = async (req, res) => {
dictionary.create = async (req, res) => {
await Dictionary.create(req.body, req.user.id)
res.redirect('/slovarji/moji')
res.redirect(303, '/slovarji/moji')
}
dictionary.editDescription = async (req, res) => {
@@ -77,7 +80,7 @@ dictionary.editDescription = async (req, res) => {
dictionary,
associatedSecondaryDomains
] = await Promise.all([
Dictionary.fetchAllPrimaryDomains(),
Dictionary.fetchAllPrimaryDomains(req.determinedLanguage),
Dictionary.fetchAllApprovedSecondaryDomains(),
Dictionary.fetchEditDescription(dictionaryId),
Dictionary.fetchSecondaryDomains(dictionaryId)
@@ -103,14 +106,14 @@ dictionary.updateDescription = async (req, res) => {
Dictionary.updateSecondaryDomains(dictionaryId, body)
])
res.redirect('back')
res.redirect(303, 'back')
}
dictionary.editUsers = async (req, res) => {
const dictionaryId = req.params.dictionaryId
const [dictionary, userRights, entriesCount, minEntries, publishApproval] =
await Promise.all([
Dictionary.fetchEditUsers(dictionaryId),
Dictionary.fetchEditUsers(dictionaryId, req.determinedLanguage),
User.fetchAllWithDictionaryRights(dictionaryId),
Dictionary.countPublishedEntries(dictionaryId),
getInstanceSetting('min_entries_per_dictionary'),
@@ -144,9 +147,9 @@ dictionary.updateUsers = async (req, res) => {
const newDictStatus = await determineNewStatus(isPublished)
// TODO I18n - nameSl
const { nameSl, status: oldDictStatus } = await Dictionary.fetchEditUsers(
dictionaryId
const { name, status: oldDictStatus } = await Dictionary.fetchEditUsers(
dictionaryId,
req.determinedLanguage
)
await Promise.all([
@@ -165,23 +168,20 @@ dictionary.updateUsers = async (req, res) => {
dictionaryId,
isPublished,
oldDictStatus,
nameSl,
name,
req.app,
req.user
)
res.redirect('back')
res.redirect(303, 'back')
}
dictionary.editStructure = async (req, res) => {
// TODO Once english language is implemented, gather selected language (sl/en) from request ~ (cookies?)
// TODO I18n - nameSl
const language = 'name_sl'
const { dictionaryId } = req.params
const [dictionary, associatedLanguages, allLanguages] = await Promise.all([
Dictionary.fetchEditStructure(dictionaryId),
Dictionary.fetchLanguages(dictionaryId),
Dictionary.fetchAllLanguages(language, true)
Dictionary.fetchLanguages(dictionaryId, req.determinedLanguage),
Dictionary.fetchAllLanguages(req.determinedLanguage, true)
])
let viewPath
@@ -211,12 +211,15 @@ dictionary.updateStructure = async (req, res) => {
Dictionary.deleteLanguages(dictionaryId),
Dictionary.updateLanguages(dictionaryId, body)
])
res.redirect('back')
res.redirect(303, 'back')
}
dictionary.editAdvanced = async (req, res) => {
const { dictionaryId } = req.params
const dictionaryName = await Dictionary.fetchName(dictionaryId)
const dictionaryName = await Dictionary.fetchName(
dictionaryId,
req.determinedLanguage
)
let viewPath
switch (req.baseUrl) {
case '/slovarji':
@@ -243,7 +246,7 @@ dictionary.comments = async (req, res) => {
const [{ comments, pages_total: numberOfAllPages }, dictionaryName] =
await Promise.all([
Comment.list(filters, req.user, resultsPerPage, 1),
Dictionary.fetchName(dictionaryId)
Dictionary.fetchName(dictionaryId, req.determinedLanguage)
])
switch (req.baseUrl) {
@@ -269,7 +272,7 @@ dictionary.showImportFromFileForm = async (req, res) => {
const [{ pages_total: numberOfAllPages, results }, dictionaryName] =
await Promise.all([
Dictionary.fetchAllImports(dictionaryId, resultsPerPage, 1),
Dictionary.fetchName(dictionaryId)
Dictionary.fetchName(dictionaryId, req.determinedLanguage)
])
let viewPath
switch (req.baseUrl) {
@@ -293,7 +296,11 @@ dictionary.listAdminDictionaries = async (req, res) => {
const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
const { pages_total: numberOfAllPages, results } =
await Dictionary.fetchAllAdminDictionaries(resultsPerPage, 1)
await Dictionary.fetchAllAdminDictionaries(
req.determinedLanguage,
resultsPerPage,
1
)
res.render('pages/admin/dictionaries-list', {
title: req.t('Seznam slovarjev'),
@@ -311,7 +318,7 @@ dictionary.adminEditDescription = async (req, res) => {
associatedSecondaryDomains,
status
] = await Promise.all([
Dictionary.fetchAllPrimaryDomains(),
Dictionary.fetchAllPrimaryDomains(req.determinedLanguage),
Dictionary.fetchAllApprovedSecondaryDomains(),
Dictionary.fetchEditDescription(dictionaryId),
Dictionary.fetchSecondaryDomains(dictionaryId),
@@ -357,15 +364,19 @@ dictionary.updateAdminDescription = async (req, res) => {
newDictStatus,
oldDictStatus,
req.app,
req.determinedLanguage,
req.user
)
res.redirect('back')
res.redirect(303, 'back')
}
dictionary.showImportFromExtractionForm = async (req, res) => {
const { dictionaryId } = req.params
const dictionaryName = await Dictionary.fetchName(dictionaryId)
const dictionaryName = await Dictionary.fetchName(
dictionaryId,
req.determinedLanguage
)
const extractions = await Extraction.fetchFinishedForUser(req.user.id)
let viewPath, title
switch (req.baseUrl) {
@@ -391,7 +402,7 @@ dictionary.showExportToFileForm = async (req, res) => {
const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
const [dictionaryName, { pages_total: numberOfAllPages, results }] =
await Promise.all([
Dictionary.fetchName(dictionaryId),
Dictionary.fetchName(dictionaryId, req.determinedLanguage),
Dictionary.fetchExports(dictionaryId, resultsPerPage, 1)
])
let viewPath
@@ -418,7 +429,7 @@ dictionary.editDomainLabels = async (req, res) => {
const [{ pages_total: numberOfAllPages, results }, dictionaryName] =
await Promise.all([
Dictionary.fetchPaginationDomainLabels(dictionaryId, resultsPerPage, 1),
Dictionary.fetchName(dictionaryId)
Dictionary.fetchName(dictionaryId, req.determinedLanguage)
])
let viewPath
@@ -445,16 +456,14 @@ dictionary.showContent = async (req, res) => {
const [
hits,
canPublishEntriesInEdit,
dictionaryName,
structure,
languages,
entryDomainLabels
] = await Promise.all([
searchEntryIndex(hitsQuery),
getInstanceSetting('can_publish_entries_in_edit'),
Dictionary.fetchName(dictionaryId),
Dictionary.fetchEditStructure(dictionaryId),
Dictionary.fetchLanguages(dictionaryId),
Dictionary.fetchLanguages(dictionaryId, req.determinedLanguage),
Dictionary.fetchDomainLabels(dictionaryId)
])
@@ -464,7 +473,7 @@ dictionary.showContent = async (req, res) => {
title: req.t('Vsebina slovarja'),
terms,
canPublishEntriesInEdit,
dictionaryName,
dictionaryName: structure[`name${capitalize(req.determinedLanguage)}`],
structure,
languages,
dictionaryId,
@@ -510,7 +519,8 @@ dictionary.dictionaryList = async (req, res) => {
hitsPerPage,
page,
orderAttribute,
orderIndex
orderIndex,
req.determinedLanguage
)
const numberOfAllHits = parseInt(
@@ -518,7 +528,9 @@ dictionary.dictionaryList = async (req, res) => {
)
const numberOfAllPages = Math.ceil(numberOfAllHits / hitsPerPage)
const allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains()
const allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains(
req.determinedLanguage
)
/*
/// filter selected domains for the prompt ///
@@ -567,9 +579,12 @@ dictionary.dictionaryDetails = async (req, res) => {
targetLanguages,
allDictionaryNames,
portals
} = await SFDSuggestionImporter.initialize()
} = await SFDSuggestionImporter.initialize(req.determinedLanguage)
const dictionaryData = await Dictionary.fetchDictionaryBasicInfo(dictId)
const dictionaryData = await Dictionary.fetchDictionaryBasicInfo(
dictId,
req.determinedLanguage
)
const filters = { ctxType: 'dictionary', ctxId: dictId }
// TODO: integrate numberOfAllPages, commentCount with pug
@@ -711,21 +726,34 @@ dictionary.importFromFile = async (req, res) => {
}
}
dictionary.exportDownload = async (req, res) => {
// TODO Add authentication and authorization.
dictionary.exportDownload = [
async (req, res, next) => {
const { exportId } = req.params
const exportDownloadMetadata = await Dictionary.fetchExportDownloadMetadata(
exportId
)
const { exportId } = req.params
const { exportStatus, dictionaryId, nameString, timeString, fileFormat } =
await Dictionary.fetchExportDownloadMetadata(exportId)
if (exportStatus !== 'finished') {
throw Error("Can't request file for unfinished export")
req.dictionaryId = exportDownloadMetadata.dictionaryId
req.exportDownloadMetadata = exportDownloadMetadata
next()
},
user.isDictionaryEditor,
(req, res) => {
const { exportId } = req.params
const { exportStatus, dictionaryId, nameString, timeString, fileFormat } =
req.exportDownloadMetadata
if (exportStatus !== 'finished') {
throw Error("Can't request file for unfinished export")
}
const exportFilesPath = getExportFilesPath(dictionaryId)
const exportFilePath = `${exportFilesPath}/${exportId}`
const exportFileName = `${nameString}_${timeString}.${fileFormat}`
res.download(exportFilePath, exportFileName)
}
const exportFilesPath = getExportFilesPath(dictionaryId)
const exportFilePath = `${exportFilesPath}/${exportId}`
const exportFileName = `${nameString}_${timeString}.${fileFormat}`
res.download(exportFilePath, exportFileName)
}
]
function importFileFilter(req, file, cb) {
if (file.mimetype !== 'text/xml') {
-150
View File
@@ -1,150 +0,0 @@
const { mkdir } = require('fs/promises')
const {
getDocumentsPath,
getStopTermsPath
} = require('../models/helpers/extraction')
const { checkIfcanBegin } = require('./helpers/extraction')
const { MAX_EXTRACTIONS_PER_USER } = require('../config/settings')
const Extraction = require('../models/extraction')
const Dictionary = require('../models/dictionary')
const Domain = require('../models/domain')
const { intoDbArray } = require('../models/helpers')
const { DEFAULT_HITS_PER_PAGE } = require('../config/settings')
const extraction = {}
extraction.list = async (req, res) => {
let extractions = await Extraction.fetchAllForUser(req.user.id)
extractions = await Promise.all(
extractions.map(async extraction => {
extraction.canBegin = await checkIfcanBegin(extraction)
if (extraction.status === 'finished') {
extraction.termCandidatesCount =
await Extraction.fetchTermCandidatesCount(extraction.id)
}
return extraction
})
)
res.render('extraction-poc/list', { extractions })
}
extraction.create = async (req, res) => {
const extractionCount = await Extraction.countAllForUser(req.user.id)
if (extractionCount >= MAX_EXTRACTIONS_PER_USER) {
// TODO Tukaj bo treba prikazati tudi obvestilo uporabniku skladno s trenutno metodologijo prikaza obvestil.
return res.redirect(303, 'back')
}
const extractionName = `Luščenje ${extractionCount + 1}`
const { extractionType } = req.body
let extractionId
if (extractionType === 'own') {
extractionId = await Extraction.createOwn(req.user.id, extractionName)
const documentsPath = getDocumentsPath(extractionId)
const stopTermsPath = getStopTermsPath(extractionId)
await Promise.all([
mkdir(documentsPath, { recursive: true }),
mkdir(stopTermsPath, { recursive: true })
])
} else {
extractionId = await Extraction.createOss(req.user.id, extractionName)
const stopTermsPath = getStopTermsPath(extractionId)
await mkdir(stopTermsPath, { recursive: true })
}
// Redirect to extraction edit page.
res.redirect(`poc/${extractionId}`)
}
extraction.edit = async (req, res) => {
const extractionId = req.params.id
const extraction = await Extraction.fetch(extractionId)
if (extraction.ossParams) {
const [allPrimaryDomains, stopTermsFiles] = await Promise.all([
Dictionary.fetchAllPrimaryDomains(),
Extraction.fetchAllStopTermsFilesStats(extractionId)
])
const { params } = extraction.ossParams
const domainUdk = params?.domainUdk?.[0]
if (domainUdk)
extraction.domainId = await Domain.fetchIdByUdkCode(domainUdk)
extraction.documentType = intoDbArray(params.documentType, 'always')
extraction.year = intoDbArray(params.year, 'always')
extraction.keywords = intoDbArray(params.keywords, 'always')
res.render('extraction-poc/edit-oss', {
id: extractionId,
extraction,
allPrimaryDomains,
stopTermsFiles
})
} else {
const [extractionDocuments, stopTermsFiles] = await Promise.all([
Extraction.fetchAllDocumentsStats(extractionId),
Extraction.fetchAllStopTermsFilesStats(extractionId)
])
res.render('extraction-poc/edit-own', {
id: extractionId,
extraction,
extractionDocuments,
stopTermsFiles
})
}
}
extraction.updateOwn = async (req, res) => {
const extractionId = req.params.id
await Extraction.update(extractionId, req.body.name)
// Reload page.
res.redirect(`./${extractionId}`)
}
extraction.docsEdit = async (req, res) => {
const extractionId = req.params.id
const extractionDocuments = await Extraction.fetchAllDocumentsStats(
extractionId
)
res.render('extraction-poc/docs-edit', {
id: extractionId,
extractionDocuments
})
}
extraction.stopTermsEdit = async (req, res) => {
const extractionId = req.params.id
const stopTermsFiles = await Extraction.fetchAllStopTermsFilesStats(
extractionId
)
res.render('extraction-poc/stop-terms-edit', {
id: extractionId,
stopTermsFiles
})
}
extraction.listTermCandidates = async (req, res) => {
const extractionId = req.params.id
const termCandidatesJson = await Extraction.fetchTermCandidatesJson(
extractionId
)
const termCandidates = JSON.parse(termCandidatesJson).terminoloski_kandidati
const hitsPerPage = +req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
const numberOfAllPages = Math.ceil(termCandidates.length / hitsPerPage)
const firstPageOfTermCandidates = termCandidates.slice(0, hitsPerPage)
res.render('extraction-poc/term-candidates', {
extractionId,
termCandidatesJson,
firstPageOfTermCandidates,
hitsPerPage,
numberOfAllPages
})
}
module.exports = extraction
+23 -8
View File
@@ -63,22 +63,36 @@ extraction.create = async (req, res) => {
}
// Redirect to extraction edit page.
res.redirect(`luscenje/${extractionId}`)
res.redirect(303, `luscenje/${extractionId}`)
}
extraction.validateOwnership = async (req, res, next) => {
const { id: extractionId } = req.params
if (!extractionId) return res.redirect(303, '/')
const extraction = await Extraction.fetch(extractionId)
if (extraction.userId !== req.user.id) return res.redirect(303, '/')
req.extractionData = extraction
next()
}
extraction.edit = async (req, res) => {
const extractionId = req.params.id
const extraction = await Extraction.fetch(extractionId)
const extraction = req.extractionData
if (extraction.ossParams) {
const [allPrimaryDomains, stopTermsFiles] = await Promise.all([
Dictionary.fetchAllPrimaryDomains(),
Extraction.fetchAllStopTermsFilesStats(extractionId)
])
const [allPrimaryDomains, ossDocumentTypes, stopTermsFiles] =
await Promise.all([
Dictionary.fetchAllPrimaryDomains(req.determinedLanguage),
Extraction.fetchOssDocumentTypes(req.determinedLanguage),
Extraction.fetchAllStopTermsFilesStats(extractionId)
])
const { params } = extraction.ossParams
const domainUdk = params?.domainUdk?.[0]
if (domainUdk)
if (domainUdk) {
extraction.domainId = await Domain.fetchIdByUdkCode(domainUdk)
}
extraction.documentType = intoDbArray(params.documentType, 'always')
extraction.year = intoDbArray(params.year, 'always')
extraction.keywords = intoDbArray(params.keywords, 'always')
@@ -88,6 +102,7 @@ extraction.edit = async (req, res) => {
id: extractionId,
extraction,
allPrimaryDomains,
ossDocumentTypes,
stopTermsFiles
})
} else {
@@ -110,7 +125,7 @@ extraction.updateOwn = async (req, res) => {
await Extraction.update(extractionId, req.body.name)
// Reload page.
res.redirect(`./${extractionId}`)
res.redirect(303, `./${extractionId}`)
}
extraction.docsEdit = async (req, res) => {
+13 -19
View File
@@ -42,7 +42,7 @@ const minEntriesEmailBookmark = {
// Exports actions related to checking and acting on minimum entries per dictionary setting.
exports.minEntriesRequirementCheckAndAct = {
// Checks required criteria and sends notifications emails if required.
async onDelete(dictionaryId, appRef) {
async onDelete(dictionaryId, appRef, determinedLanguage) {
const minEntries = await getInstanceSetting('min_entries_per_dictionary')
// Only proceed if a valid and positive minimum entries per dictionary setting is set.
@@ -58,9 +58,8 @@ exports.minEntriesRequirementCheckAndAct = {
if (!isBelowMinEntriesThreshold || wasEmailAlreadySent) return
// Prepare and send notification emails.
// TODO I18n - nameSl
const [nameSl, adminEmails, dictionariesAdminEmails] = await Promise.all([
Dictionary.fetchName(dictionaryId),
const [name, adminEmails, dictionariesAdminEmails] = await Promise.all([
Dictionary.fetchName(dictionaryId, determinedLanguage),
Dictionary.fetchAdminEmails(dictionaryId),
Dictionary.fetchDictionariesAdminEmails()
])
@@ -71,10 +70,9 @@ exports.minEntriesRequirementCheckAndAct = {
const type = 'delete'
const renderAsync = promisify(appRef.render.bind(appRef))
// TODO I18n - nameSl
const emailHtml = await renderAsync('email/dictionary-status-change', {
type,
nameSl
name
})
// TODO i18n - What language are the email title and content (we already have email translated)
@@ -123,12 +121,11 @@ exports.determineNewStatus = async isPublished => {
// Exports actions related to checking and acting on dictionary status changes.
exports.statusChangeCheckAndAct = {
// Notify dictionaries admins by email on dictionary status changes.
// TODO I18n - nameSl
async updateUsers(
dictionaryId,
isPublishedNew,
oldDictStatus,
nameSl,
name,
appRef,
user
) {
@@ -139,12 +136,11 @@ exports.statusChangeCheckAndAct = {
const dictionariesAdminEmails =
await Dictionary.fetchDictionariesAdminEmails()
const type = 'unpublish'
// TODO I18n - nameSl
await renderAndSendStatusChangeEmails(
appRef,
type,
user.email,
nameSl,
name,
dictionariesAdminEmails
)
@@ -157,12 +153,11 @@ exports.statusChangeCheckAndAct = {
const type =
isApprovalRequired === 'T' ? 'publish-approval' : 'publish-no-approval'
// TODO I18n - nameSl
await renderAndSendStatusChangeEmails(
appRef,
type,
user.email,
nameSl,
name,
dictionariesAdminEmails
)
}
@@ -175,12 +170,12 @@ exports.statusChangeCheckAndAct = {
statusNew,
statusOld,
appRef,
determinedLanguage,
user
) {
if (statusOld === 'reviewed' && statusNew !== 'reviewed') {
// TODO I18n - nameSl
const [nameSl, adminEmails] = await Promise.all([
Dictionary.fetchName(dictionaryId),
const [name, adminEmails] = await Promise.all([
Dictionary.fetchName(dictionaryId, determinedLanguage),
Dictionary.fetchAdminEmails(dictionaryId)
])
const type = 'status'
@@ -189,7 +184,7 @@ exports.statusChangeCheckAndAct = {
appRef,
type,
user.email,
nameSl,
name,
adminEmails
)
}
@@ -197,19 +192,18 @@ exports.statusChangeCheckAndAct = {
}
// Helper function used by statusChangeCheckAndAct methods.
// TODO I18n - nameSl
async function renderAndSendStatusChangeEmails(
appRef,
type,
changerEmail,
nameSl,
name,
targetEmails
) {
const renderAsync = promisify(appRef.render.bind(appRef))
const emailHtml = await renderAsync('email/dictionary-status-change', {
type,
changerEmail,
nameSl
name
})
// TODO i18n - What language are the email title and content (we already have email translated)
@@ -4,24 +4,23 @@ const { getInstanceSetting } = require('../../models/helpers')
const helper = {}
helper.initialize = async () => {
helper.initialize = async determinedLanguage => {
const initializers = {}
// TODO Once english language is implemented, gather selected language (sl/en) from request ~ (cookies?)
// TODO i18n name_sl
const language = 'name_sl'
// TODO Consider parallelizing following queries. Single vs pooled clients?
initializers.allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains()
initializers.sourceLanguages = await Dictionary.fetchAllLanguages(language)
initializers.targetLanguages = (
await Dictionary.fetchAllLanguages(language)
).filter(
initializers.allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains(
determinedLanguage
)
const allLanguages = await Dictionary.fetchAllLanguages(determinedLanguage)
initializers.sourceLanguages = allLanguages
initializers.targetLanguages = allLanguages.filter(
// drop slovene language
l => l.id !== 32
)
initializers.allDictionaryNames = await Dictionary.fetchAll()
initializers.allDictionaryNames = await Dictionary.fetchAll(
determinedLanguage
)
initializers.portals = []
initializers.portals.push({
+55 -18
View File
@@ -1,3 +1,4 @@
const { promisify } = require('util')
const Entry = require('../models/entry')
const { getInstanceSetting } = require('../models/helpers')
const Dictionary = require('../models/dictionary')
@@ -13,10 +14,12 @@ const {
prepareSeachFilterData,
prepareConsultancyEntries
} = require('../models/helpers/search')
const email = require('../models/email')
const generateQuery = require('../models/helpers/search/generate-query')
const { DEFAULT_HITS_PER_PAGE } = require('../config/settings')
const SFDSuggestionImporter = require('./helpers/search-filter-data-suggestion-importer')
const User = require('../models/user')
const { capitalize } = require('../utils')
// TODO Luka (note to self): Measure performance, consider caching.
exports.index = async (req, res) => {
@@ -27,12 +30,10 @@ exports.index = async (req, res) => {
targetLanguages,
allDictionaryNames,
portals
} = await SFDSuggestionImporter.initialize()
const englishLanguageEnabled = false // dummy variable for future edit
} = await SFDSuggestionImporter.initialize(req.determinedLanguage)
const latestDicts = await Dictionary.fetchLatest3DictsByPublishDate(
englishLanguageEnabled
req.determinedLanguage
)
const portalName = await getInstanceSetting(`portal_name_${language}`)
@@ -58,7 +59,7 @@ exports.index = async (req, res) => {
exports.search = async (req, res) => {
const searchString = req.query.q?.trim()
if (!searchString) return res.redirect('/')
if (!searchString) return res.redirect(303, '/')
const title = req.t('Iskanje')
@@ -68,7 +69,7 @@ exports.search = async (req, res) => {
targetLanguages,
allDictionaryNames,
portals
} = await SFDSuggestionImporter.initialize()
} = await SFDSuggestionImporter.initialize(req.determinedLanguage)
const filters = {
sourceLanguages: intoDbArray(req.query.sl, 'always'),
@@ -131,7 +132,10 @@ exports.search = async (req, res) => {
// Similar to search query without any extra filters, this is required for modal to diplay ALL res.
// const allAggregation = await indexAllResultsNoFiltering(req, res)
const aggregation = await prepareAggregation(aggregationRaw)
const aggregation = await prepareAggregation(
aggregationRaw,
req.determinedLanguage
)
const searchFilterData = prepareSeachFilterData(aggregation, filters)
// TODO Luka (note to self): Consider reworking Miha's logic below. It's hacky and possibly error prone.
@@ -328,7 +332,7 @@ exports.entryDetails = async (req, res) => {
// If external url, just redirect
if (entry.external_url) {
return res.redirect(entry.external_url)
return res.redirect(303, entry.external_url)
}
// unnecessary legacy assigment, refactor when time is available
@@ -340,12 +344,15 @@ exports.entryDetails = async (req, res) => {
targetLanguages,
allDictionaryNames,
portals
} = await SFDSuggestionImporter.initialize()
} = await SFDSuggestionImporter.initialize(req.determinedLanguage)
const [dictStruct, dictionaryData, selectedDomainLabelsForEntry] =
await Promise.all([
Dictionary.fetchDictionaryWithEditStructure(entry.dictionary_id),
Dictionary.fetchDictionaryBasicInfo(entry.dictionary_id),
Dictionary.fetchDictionaryBasicInfo(
entry.dictionary_id,
req.determinedLanguage
),
Entry.fetchDomainLabels(termId)
])
@@ -399,7 +406,7 @@ exports.entryDetails = async (req, res) => {
prevHref: '/iskanje',
portalCode: dictionaryData[0].portalcode,
portalName: dictionaryData[0].portalname,
dictName: structure.nameSl,
dictName: structure[`name${capitalize(req.determinedLanguage)}`],
dictHref: `/slovarji/${structure.id}/o-slovarju?sentFromEntryId=${termId}`,
fullAuthorName: reducedData.author ? reducedData.author.join(', ') : '',
areas: dictionaryData[0].domain_primary,
@@ -438,11 +445,13 @@ exports.entryDetails = async (req, res) => {
entry.foreignEntries[idx] = await
}) */
const langs = await Dictionary.fetchLanguages(entryData.dictionary_id)
const langs = await Dictionary.fetchLanguages(
entryData.dictionary_id,
req.determinedLanguage
)
entryData.foreign_entries.forEach((val, idx) => {
try {
entry.foreign_entries[idx].name_sl = langs[idx].nameSl
entry.foreign_entries[idx].name_en = langs[idx].nameEn
entry.foreign_entries[idx].name = langs[idx].name
} catch (e) {}
})
@@ -479,16 +488,44 @@ exports.changePassword = async (req, res) => {
}
exports.resetPassword = async (req, res) => {
if (req.user) return res.redirect(303, '/spremeni-geslo')
const { token } = req.query
if (!token) return res.redirect(303, '/')
const isTokenValid = await User.isResetPasswordTokenValid(token)
// console.log(token)
res.render('pages/reset-password/reset-password', {
// title: 'Pozabljeno geslo'
isValidToken: token === '123',
title: 'Ponastavitev gesla',
isTokenValid,
token
})
}
exports.changeEmail = async (req, res) => {
const { token } = req.query
if (!token) return res.redirect(303, '/')
const user = await User.changeEmailWithToken(token, req.t)
const renderAsync = promisify(req.app.render.bind(req.app))
const emailHtml = await renderAsync(
`email/user-change-email-success_${req.language}`,
{
username: user.username
}
)
await email.send({
to: user.email,
subject: req.t('Sprememba elektronskega naslova - uspeh'),
html: emailHtml
})
req.flash('info', req.t('Uspešno ste spremenili svoj elektronski naslov.'))
if (req.user) return res.redirect(303, '/moj-racun')
res.redirect(303, '/')
}
exports.userSettings = async (req, res) => {
const hitsPerPageArr = await User.fetchAllowedHitsPerPage()
res.render('pages/profile/change-profile-settings', {
@@ -513,7 +550,7 @@ exports.changeUserLanguage = async (req, res) => {
req.session.language = languageCode
}
res.redirect('/')
res.redirect(303, '/')
}
function mergeDomains(
+6 -6
View File
@@ -17,7 +17,7 @@ portal.updateInstaceSettings = async (req, res) => {
const payload = req.body
await Portal.updateInstaceSettings(payload)
await clearCachedInstanceSettings()
res.redirect('/admin/nastavitve/portal')
res.redirect(303, '/admin/nastavitve/portal')
}
portal.instanceDictSettings = async (req, res) => {
@@ -33,7 +33,7 @@ portal.updateInstanceDictSettings = async (req, res) => {
const payload = req.body
await Portal.updateInstaceDictSettings(payload)
await clearCachedInstanceSettings()
res.redirect('/admin/nastavitve/slovarji')
res.redirect(303, '/admin/nastavitve/slovarji')
}
portal.instanceConsultancySettings = async (req, res) => {
@@ -48,7 +48,7 @@ portal.updateInstanceConusltacySettings = async (req, res) => {
const payload = req.body
await Portal.updateInstaceConsultancySettings(payload)
await clearCachedInstanceSettings()
res.redirect('/admin/nastavitve/svetovalnica')
res.redirect(303, '/admin/nastavitve/svetovalnica')
}
portal.new = async (req, res) => {
@@ -80,7 +80,7 @@ portal.updatePortal = async (req, res) => {
const portalId = req.params.portalId
const payload = req.body
await Portal.update(portalId, payload)
res.redirect('/admin/povezave/seznam')
res.redirect(303, '/admin/povezave/seznam')
}
portal.fetchSelectedLinkedDictionaries = async (req, res) => {
@@ -101,7 +101,7 @@ portal.fetchSelectedLinkedDictionaries = async (req, res) => {
portal.updateSelectedDictionaries = async (req, res) => {
const linkedId = req.params.portalId
await Portal.updateSelectedDictionaries(linkedId, req.body)
res.redirect('back')
res.redirect(303, 'back')
}
portal.fetchAllLinkedDictionaries = async (req, res) => {
@@ -119,7 +119,7 @@ portal.fetchAllLinkedDictionaries = async (req, res) => {
portal.updateAllDictionaries = async (req, res) => {
await Portal.updateAllDictionaries(req.body)
res.redirect('back')
res.redirect(303, 'back')
}
portal.comments = async (req, res) => {
+98 -19
View File
@@ -3,6 +3,7 @@ const { promisify } = require('util')
const passport = require('passport')
const User = require('../models/user')
const email = require('../models/email')
const { logout: logoutUser } = require('../middleware/user')
const { origin } = require('../config/keys')
const { rememberMeCookieSettings } = require('../config/settings')
const RandomBytesAsync = promisify(randomBytes)
@@ -12,10 +13,14 @@ const user = {}
user.register = async (req, res) => {
// TODO Add validation.
const userId = await User.create({
...req.body,
language: req.session.language
})
const userId = await User.create(
{
...req.body,
language: req.session.language
},
req.t
)
const activationToken = (await RandomBytesAsync(32)).toString('hex')
await User.saveActivationToken(userId, activationToken)
const { email: userEmail, username } = req.body
@@ -38,8 +43,9 @@ user.register = async (req, res) => {
user.activateAccount = async (req, res) => {
// TODO Add validation. What if user is already logged in? What if account is already active? ...
const { token } = req.query
const user = await User.fetchByActivationToken(token)
await User.activateAccount(user)
if (!token) return res.redirect(303, '/')
const user = await User.activateAccountWithToken(token, req.t)
const loginAsync = promisify(req.login.bind(req))
await loginAsync(user)
@@ -48,7 +54,11 @@ user.activateAccount = async (req, res) => {
delete req.session.language
}
res.redirect('/')
req.flash(
'info',
req.t('Uspešno ste aktivirali svoj uporabniški račun in se prijavili.')
)
res.redirect(303, '/')
}
user.login = async (req, res, next) => {
@@ -89,20 +99,86 @@ user.login = async (req, res, next) => {
)(req, res, next)
}
user.logout = async (req, res) => {
const rememberMeToken = req.signedCookies.remember_me
user.logout = [logoutUser, (req, res) => res.redirect(303, '/')]
if (rememberMeToken) {
res.clearCookie('remember_me')
await User.clearRememberMeToken(rememberMeToken)
user.generateResetPasswordToken = async (req, res) => {
const { usernameOrEmail } = req.body
const user = await User.fetchByUsernameOrEmail(usernameOrEmail)
if (!user) {
return res
.status(400)
.send(req.t('Nepravilno uporabniško ime ali elektronski naslov.'))
}
req.session.language = req.user.language
if (user.status !== 'active') {
return res
.status(400)
.send(
req.t(
'Uporabniški račun še ni aktiviran. Kliknite aktivacijsko povezavo, katero smo vam poslali po elektronski pošti.'
)
)
}
req.logout()
// Manually clear session.passport due to bug in current passport version.
delete req.session.passport.user
res.redirect('/')
const resetPasswordToken = (await RandomBytesAsync(32)).toString('hex')
await User.saveResetPasswordToken(user.id, resetPasswordToken)
const { email: userEmail, username } = user
let resetPasswordLink = new URL('/ponastavitev-gesla', origin)
resetPasswordLink.searchParams.set('token', resetPasswordToken)
resetPasswordLink = resetPasswordLink.href
const renderAsync = promisify(req.app.render.bind(req.app))
// TODO i18n - prepare proper slovenian an english email templates
const emailHtml = await renderAsync(
`email/user-reset-password-token_${req.language}`,
{
username,
resetPasswordLink
}
)
await email.send({
to: userEmail,
subject: req.t('Ponastavitev gesla'),
html: emailHtml
})
res.send()
}
user.changePassword = async (req, res) => {
const { token, password, passwordRepeat } = req.body
if (password !== passwordRepeat) {
return res.status(400).send(req.t('Gesli se ne ujemata'))
}
// TODO Add additional password validation (min length, ...)
const user = await User.resetPasswordWithToken(token, password, req.t)
const renderAsync = promisify(req.app.render.bind(req.app))
// TODO i18n - prepare proper slovenian an english email templates
const emailHtml = await renderAsync(
`email/user-reset-password-success_${req.language}`,
{
username: user.username
}
)
await email.send({
to: user.email,
subject: req.t('Uspešna ponastavitev gesla'),
html: emailHtml
})
req.flash('info', req.t('Vaše geslo je bilo uspešno ponastavljeno.'))
const loginAsync = promisify(req.login.bind(req))
await loginAsync(user)
if (req.session.language) {
await User.updateLanguage(user.id, req.session.language)
delete req.session.language
}
res.send()
}
user.list = async (req, res) => {
@@ -135,7 +211,7 @@ user.findByUsernameOrEmail = async (req, res) => {
user.updateRoles = async (req, res) => {
await User.updatePortalRoles(req.body.rolesPerUser)
res.redirect('back')
res.redirect(303, 'back')
}
user.adminEdit = async (req, res) => {
@@ -144,6 +220,9 @@ user.adminEdit = async (req, res) => {
User.fetchUser(userId),
User.fetchUserRoles(userId)
])
if (userData.status === 'closed') return res.redirect(303, '/')
res.render('pages/admin/user-edit', {
title: req.t('Uporabnik'),
userData,
@@ -156,7 +235,7 @@ user.adminEdit = async (req, res) => {
user.adminUpdate = async (req, res) => {
const { userId } = req.params
await User.updateUser(userId, req.body)
res.redirect('back')
res.redirect(303, 'back')
}
module.exports = user