First full release
This commit is contained in:
@@ -1,11 +1,18 @@
|
||||
const ConsultancyEntry = require('../../../models/consultancy-entry')
|
||||
const Domain = require('../../../models/domain')
|
||||
const User = require('../../../models/user')
|
||||
const { promisify } = require('util')
|
||||
const i18next = require('i18next')
|
||||
const {
|
||||
deleteConsultancyEntryFromIndex
|
||||
} = require('../../../models/search-engine')
|
||||
const email = require('../../../models/email')
|
||||
const helper = require('../../../models/helpers')
|
||||
const { getInstanceSetting } = require('../../../models/helpers')
|
||||
const { searchConsultancyEntryIndex } = require('../../../models/search-engine')
|
||||
const { prepareConsultancyEntries } = require('../../../models/helpers/search')
|
||||
const { DEFAULT_HITS_PER_PAGE } = require('../../../config/settings')
|
||||
const generateQuery = require('../../../models/helpers/search/generate-query')
|
||||
|
||||
const consultancy = {}
|
||||
|
||||
@@ -23,6 +30,43 @@ consultancy.listNewEntries = async (req, res) => {
|
||||
res.send(data)
|
||||
}
|
||||
|
||||
consultancy.sendPaginationData = async (req, res) => {
|
||||
let requestType = req.query.type
|
||||
const isAdminPage = req.query.isAdmin === 'true'
|
||||
let page = +req.query.p || 1
|
||||
|
||||
if (page < 1) {
|
||||
page = 1
|
||||
}
|
||||
|
||||
if (isAdminPage) {
|
||||
if (
|
||||
req.user &&
|
||||
(req.user.hasRole('portal admin') ||
|
||||
req.user.hasRole('consultancy admin'))
|
||||
) {
|
||||
// Everything is allowed, nothing to do here
|
||||
} else if (req.user && req.user.hasRole('consultant')) {
|
||||
if (!(requestType === 'in progress' || requestType === 'published')) {
|
||||
return res.status(400).send()
|
||||
}
|
||||
} else {
|
||||
// for now, public can only see published entries
|
||||
requestType = 'published'
|
||||
}
|
||||
}
|
||||
|
||||
return await consultancyRequestItems(
|
||||
req,
|
||||
res,
|
||||
requestType || 'published',
|
||||
'components/consultancy/api/consultancy-item-rendered',
|
||||
isAdminPage,
|
||||
!isAdminPage, // -> In current implementation, it is just the inverse of isAdminPage
|
||||
page
|
||||
)
|
||||
}
|
||||
|
||||
consultancy.createQuestion = async (req, res) => {
|
||||
const q = req.body
|
||||
const consultancyEntry = {}
|
||||
@@ -35,7 +79,7 @@ consultancy.createQuestion = async (req, res) => {
|
||||
|
||||
const { description } = q
|
||||
if (!description) {
|
||||
return res.status(400).send('description is a required parameter!')
|
||||
return res.status(400).send('Description is a required parameter!')
|
||||
}
|
||||
consultancyEntry.description = description
|
||||
|
||||
@@ -52,24 +96,46 @@ consultancy.createQuestion = async (req, res) => {
|
||||
consultancyEntry[key] = helper.removeHtmlTags(consultancyEntry[key]).trim()
|
||||
})
|
||||
|
||||
const questionId = await ConsultancyEntry.createQuestion(consultancyEntry)
|
||||
await ConsultancyEntry.indexIntoSearchEngine(questionId, true)
|
||||
const isOwnConsultancyEnabled =
|
||||
(await getInstanceSetting('consultancy_type')) === 'own'
|
||||
|
||||
// TODO SEND EMAIL
|
||||
// TODOOOOOOOOO
|
||||
let domainNameSl
|
||||
if (consultancyEntry.domainPrimaryIdInitial) {
|
||||
domainNameSl = (
|
||||
await Domain.fetchById(consultancyEntry.domainPrimaryIdInitial)
|
||||
).nameSl
|
||||
} else {
|
||||
domainNameSl = ''
|
||||
}
|
||||
|
||||
const emails = await ConsultancyEntry.fetchConsultancyAdminEmails()
|
||||
let emails
|
||||
let subjectText
|
||||
if (isOwnConsultancyEnabled) {
|
||||
const questionId = await ConsultancyEntry.createQuestion(consultancyEntry)
|
||||
await ConsultancyEntry.indexIntoSearchEngine(questionId, true)
|
||||
subjectText = req.t('Ustvarjeno novo vprašanje v svetovalnici')
|
||||
emails = await ConsultancyEntry.fetchConsultancyAdminEmails()
|
||||
} else {
|
||||
subjectText = req.t('Novo vprašanje za Terminološko svetovalnico')
|
||||
emails = await getInstanceSetting('zrc_email')
|
||||
}
|
||||
|
||||
const renderAsync = promisify(req.app.render.bind(req.app))
|
||||
const emailHtml = await renderAsync('email/consultancy-creation-notify', {
|
||||
propertyToPassGoesHere: 'test1234'
|
||||
nameAndSurname: `${res.locals.user.firstName} ${res.locals.user.lastName}`,
|
||||
email: res.locals.user.email,
|
||||
domain: domainNameSl,
|
||||
institution: consultancyEntry.institution,
|
||||
question: consultancyEntry.description,
|
||||
existingSolutions: consultancyEntry.existingSolutions,
|
||||
examplesOfUse: consultancyEntry.examplesOfUse
|
||||
})
|
||||
// TODO i18n - What language are the email title and content (we already have email translated)
|
||||
await email.send({
|
||||
to: emails,
|
||||
subject: 'Ustvarjeno novo vprašanje v svetovalnici',
|
||||
subject: subjectText,
|
||||
html: emailHtml
|
||||
})
|
||||
/// /////////////////
|
||||
|
||||
res.status(201).send()
|
||||
}
|
||||
@@ -179,9 +245,10 @@ consultancy.assign = async (req, res) => {
|
||||
const renderAsync = promisify(req.app.render.bind(req.app))
|
||||
const emailHtml = await renderAsync('email/consultancy-assigned')
|
||||
|
||||
// TODO i18n - What language are the email title and content (we already have email translated)
|
||||
await email.send({
|
||||
to: emails,
|
||||
subject: 'Novo terminološko vprašanje',
|
||||
subject: req.t('Novo terminološko vprašanje'),
|
||||
html: emailHtml
|
||||
})
|
||||
|
||||
@@ -234,9 +301,10 @@ consultancy.sendToReview = async (req, res) => {
|
||||
const renderAsync = promisify(req.app.render.bind(req.app))
|
||||
const emailHtml = await renderAsync('email/consultancy-item-review')
|
||||
|
||||
// TODO i18n - What language are the email title and content (we already have email translated)
|
||||
await email.send({
|
||||
to: emails,
|
||||
subject: 'Potrditev objave',
|
||||
subject: req.t('Potrditev objave'),
|
||||
html: emailHtml
|
||||
})
|
||||
|
||||
@@ -254,6 +322,19 @@ consultancy.publish = async (req, res) => {
|
||||
if (!entry.title) {
|
||||
return res.status(400).send('Answer not completed')
|
||||
}
|
||||
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
|
||||
})
|
||||
await email.send({
|
||||
to: author.email,
|
||||
subject: i18next.t('Objava terminološkega vprašanja', {
|
||||
lng: author.language
|
||||
}),
|
||||
html: emailHtml
|
||||
})
|
||||
|
||||
await ConsultancyEntry.publish(questionId, answerAuthors)
|
||||
await ConsultancyEntry.indexIntoSearchEngine(questionId, true)
|
||||
@@ -267,7 +348,9 @@ consultancy.updateQuestion = async (req, res) => {
|
||||
if (!id) return res.status(400).send({})
|
||||
|
||||
if (questionTitle === '' || question === '' || answer === '') {
|
||||
return res.status(422).send('Polja naslov, vprašanje in mnenje so obvezna!')
|
||||
return res
|
||||
.status(422)
|
||||
.send(req.t('Polja naslov, vprašanje in mnenje so obvezna!'))
|
||||
}
|
||||
|
||||
const entry = await ConsultancyEntry.fetchById(id)
|
||||
@@ -318,4 +401,157 @@ consultancy.deleteQuestion = async (req, res) => {
|
||||
res.send()
|
||||
}
|
||||
|
||||
async function consultancyRequestItems(
|
||||
req,
|
||||
res,
|
||||
type,
|
||||
url,
|
||||
isAdminPage = false,
|
||||
privilegeToSeAll = true, // this method seperates consultancy main from admin, so all results get visible TO ALL REGISTERED USERS, not just admins
|
||||
page
|
||||
) {
|
||||
const searchString = req.query.q?.trim() ?? ''
|
||||
|
||||
/// //////////////////////////////////////////
|
||||
// let allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains()
|
||||
|
||||
/// filter selected domains for the prompt ///
|
||||
// Not duplicates of this code arise...
|
||||
// const pdList = intoDbArray(req.query.pd, 'always')
|
||||
|
||||
// allPrimaryDomains = allPrimaryDomains.map(entry => {
|
||||
// if (pdList.includes(`${entry.id}`)) {
|
||||
// entry.selected = true
|
||||
// }
|
||||
// return entry
|
||||
// })
|
||||
/// //////////////////////////////////////////
|
||||
|
||||
let assignedConsultant
|
||||
|
||||
if (
|
||||
!privilegeToSeAll &&
|
||||
req.user &&
|
||||
!(req.user.hasRole('portal admin') || req.user.hasRole('consultancy admin'))
|
||||
) {
|
||||
assignedConsultant = req.user.id
|
||||
} else {
|
||||
assignedConsultant = undefined
|
||||
}
|
||||
|
||||
const filters = {
|
||||
status: type,
|
||||
assignedConsultant,
|
||||
primaryDomain: req.query.pd
|
||||
}
|
||||
|
||||
if (!isAdminPage) {
|
||||
filters.assignedConsultant = undefined
|
||||
filters.status = 'published' // guard
|
||||
}
|
||||
|
||||
const hitsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
|
||||
|
||||
// const page = +req.query.p > 0 ? +req.query.p : 1
|
||||
|
||||
const hitsQuery = generateQuery.consultancy(
|
||||
searchString,
|
||||
filters,
|
||||
hitsPerPage,
|
||||
page
|
||||
)
|
||||
|
||||
const hits = await searchConsultancyEntryIndex(hitsQuery)
|
||||
|
||||
const numberOfAllHits = hits.body.hits.total.value
|
||||
const numberOfAllPages = Math.ceil(numberOfAllHits / hitsPerPage)
|
||||
|
||||
let entries = prepareConsultancyEntries(hits)
|
||||
// console.log({ entries, numberOfAllHits, numberOfAllPages })
|
||||
|
||||
entries = entries.map(entry => {
|
||||
entry.primaryDomain = entry.primaryDomain
|
||||
? entry.primaryDomain.nameSl
|
||||
: req.t('nedefinirano')
|
||||
|
||||
if (entry.assignedConsultants) {
|
||||
entry.firstName = entry.assignedConsultants[0]?.firstName
|
||||
entry.lastName = entry.assignedConsultants[0]?.lastName
|
||||
}
|
||||
|
||||
if (entry.assignedConsultants && entry.assignedConsultants.length > 1) {
|
||||
entry.sharedAuthors = []
|
||||
for (let i = 1; i < entry.assignedConsultants.length; i++) {
|
||||
// skip first element (moderator)
|
||||
entry.sharedAuthors.push(
|
||||
`${entry.assignedConsultants[i].firstName} ${entry.assignedConsultants[i].lastName}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (entry.timeCreated) {
|
||||
// TODO i18n date format
|
||||
const date = new Date(entry.timeCreated)
|
||||
|
||||
entry.formattedTimeCreated = `${date.getDate()}. ${
|
||||
date.getMonth() + 1
|
||||
}. ${date.getFullYear()}`
|
||||
}
|
||||
|
||||
return entry
|
||||
})
|
||||
|
||||
// const entryList = await mapEntryList(inProgressEntryList)
|
||||
const userList = await User.fetchConsultants()
|
||||
|
||||
if (!isAdminPage) {
|
||||
entries.map(entry => {
|
||||
const MAX_CHARACTER_LENGTH = 200
|
||||
let appendAnswer = ''
|
||||
let appendQuestion = ''
|
||||
if (entry.answer && entry.answer.length > MAX_CHARACTER_LENGTH) {
|
||||
appendAnswer = '...'
|
||||
}
|
||||
if (entry.question && entry.question.length > MAX_CHARACTER_LENGTH) {
|
||||
appendQuestion = '...'
|
||||
}
|
||||
|
||||
entry.answerSummary = `${entry.answer.slice(
|
||||
0,
|
||||
MAX_CHARACTER_LENGTH
|
||||
)}${appendAnswer}`
|
||||
|
||||
entry.question = `${entry.question.slice(
|
||||
0,
|
||||
MAX_CHARACTER_LENGTH
|
||||
)}${appendQuestion}`
|
||||
|
||||
return entry
|
||||
})
|
||||
}
|
||||
|
||||
res.append('page', page)
|
||||
res.append('number-of-all-pages', numberOfAllPages)
|
||||
res.render(url, {
|
||||
entries, // entryList,
|
||||
userList,
|
||||
numberOfAllPages,
|
||||
isAdminPage,
|
||||
section: consultancyAdminPageMapper(type),
|
||||
queryCount: numberOfAllHits
|
||||
})
|
||||
}
|
||||
|
||||
// mapping required due to the inconsistent naming convention
|
||||
function consultancyAdminPageMapper(type) {
|
||||
if (type === 'in progress') {
|
||||
return { inProgress: true }
|
||||
}
|
||||
if (type === 'review') {
|
||||
return { prepared: true }
|
||||
}
|
||||
|
||||
return { type: true }
|
||||
}
|
||||
|
||||
module.exports = consultancy
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
const { rm } = require('fs/promises')
|
||||
const Dictionary = require('../../../models/dictionary')
|
||||
const Entry = require('../../../models/entry')
|
||||
const Extraction = require('../../../models/extraction')
|
||||
const {
|
||||
searchEntryIndex,
|
||||
deleteEntryFromIndex,
|
||||
@@ -9,6 +11,7 @@ const genEditorAllQuery = require('../../../models/helpers/search/generate-query
|
||||
const { prepareEditorEntries } = require('../../../models/helpers/search')
|
||||
const { DEFAULT_HITS_PER_PAGE } = require('../../../config/settings')
|
||||
const { minEntriesRequirementCheckAndAct } = require('../../helpers/dictionary')
|
||||
const { getExportFilesPath } = require('../../../models/helpers/dictionary')
|
||||
|
||||
const dictionary = {}
|
||||
|
||||
@@ -115,6 +118,8 @@ dictionary.delete = async (req, res) => {
|
||||
const dictionaryId = +req.params.dictionaryId
|
||||
await Dictionary.delete(dictionaryId)
|
||||
await deleteDictionaryEntriesFromIndex(dictionaryId)
|
||||
const exportFilesPath = getExportFilesPath(dictionaryId)
|
||||
await rm(exportFilesPath, { recursive: true, force: true })
|
||||
|
||||
res.end()
|
||||
}
|
||||
@@ -167,6 +172,56 @@ dictionary.listDomainLabels = async (req, res) => {
|
||||
res.send({ page, numberOfAllPages, results })
|
||||
}
|
||||
|
||||
dictionary.listFilteredDomainLabels = async (req, res) => {
|
||||
const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
|
||||
const { dictionaryId } = req.params
|
||||
const page = +req.query.p > 0 ? +req.query.p : 1
|
||||
|
||||
let { q } = req.query
|
||||
|
||||
if (!q) {
|
||||
q = ''
|
||||
}
|
||||
|
||||
const { pages_total: numberOfAllPages, results } =
|
||||
await Dictionary.fetchFilteredPaginationDomainLabels(
|
||||
dictionaryId,
|
||||
q,
|
||||
resultsPerPage,
|
||||
page
|
||||
)
|
||||
|
||||
res.append('page', page)
|
||||
res.append('number-of-all-pages', numberOfAllPages)
|
||||
res.render('utilities/response-pug-wrapper/domainLabelLister', {
|
||||
dictionary: { id: dictionaryId },
|
||||
numberOfAllPages,
|
||||
results
|
||||
})
|
||||
}
|
||||
|
||||
dictionary.listSecondaryDomainData = async (req, res) => {
|
||||
const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
|
||||
let { q } = req.query
|
||||
const page = +req.query.p > 0 ? +req.query.p : 1
|
||||
|
||||
if (!q) {
|
||||
q = ''
|
||||
}
|
||||
|
||||
const { pages_total: numberOfAllPages, results } =
|
||||
await Dictionary.fetchFilteredSecondaryDomains(q, resultsPerPage, page)
|
||||
|
||||
res.append('page', page)
|
||||
res.append('number-of-all-pages', numberOfAllPages)
|
||||
|
||||
res.render('utilities/response-pug-wrapper/secondaryDomainLister', {
|
||||
page,
|
||||
numberOfAllPages,
|
||||
results
|
||||
})
|
||||
}
|
||||
|
||||
dictionary.listSecondaryDomains = async (req, res) => {
|
||||
const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
|
||||
const page = +req.query.p > 0 ? +req.query.p : 1
|
||||
@@ -176,14 +231,92 @@ dictionary.listSecondaryDomains = async (req, res) => {
|
||||
res.send({ page, numberOfAllPages, results })
|
||||
}
|
||||
|
||||
dictionary.extractionImport = async (req, res) => {
|
||||
// TODO Import logic (Luka's task)
|
||||
// const { id: dictionaryId, extractionId } = req.params
|
||||
// const { from, to } = req.query
|
||||
// const fromIndex = +from > 1 ? Math.floor(from) - 1 : 0
|
||||
// const toIndex = Number.isInteger(+to) ? Math.abs(to) : undefined
|
||||
// console.log({ dictionaryId, extractionId, fromIndex, toIndex })
|
||||
res.send('IMPORTING')
|
||||
dictionary.showImportFromFileForm = async (req, res) => {
|
||||
const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
|
||||
const { dictionaryId } = req.params
|
||||
const page = +req.query.p > 0 ? +req.query.p : 1
|
||||
const { pages_total: numberOfAllPages, results } =
|
||||
await Dictionary.fetchAllImports(dictionaryId, resultsPerPage, page)
|
||||
|
||||
res.send({ page, numberOfAllPages, results })
|
||||
}
|
||||
|
||||
dictionary.showExportToFileForm = async (req, res) => {
|
||||
const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
|
||||
const { dictionaryId } = req.params
|
||||
const page = +req.query.p > 0 ? +req.query.p : 1
|
||||
const { pages_total: numberOfAllPages, results } =
|
||||
await Dictionary.fetchExports(dictionaryId, resultsPerPage, page)
|
||||
|
||||
res.send({ page, numberOfAllPages, results })
|
||||
}
|
||||
|
||||
dictionary.importFromExtraction = async (req, res) => {
|
||||
const { id: 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))
|
||||
? Math.abs(to)
|
||||
: undefined
|
||||
|
||||
// TODO Authentication, authorization, validation.
|
||||
|
||||
const termCandidatesToImport = await Extraction.fetchTermCandidatesSlice(
|
||||
extractionId,
|
||||
fromIndex,
|
||||
toIndex
|
||||
)
|
||||
|
||||
await Dictionary.importFromExtraction(
|
||||
dictionaryId,
|
||||
req.user.id,
|
||||
termCandidatesToImport
|
||||
)
|
||||
|
||||
await Dictionary.indexIntoSearchEngine(dictionaryId)
|
||||
|
||||
res.end()
|
||||
}
|
||||
|
||||
dictionary.exportBegin = async (req, res) => {
|
||||
const dictionaryId = req.params.id
|
||||
const exportParams = {
|
||||
isValidFilter:
|
||||
req.body.isValidFilter === 'on' ? undefined : req.body.isValidFilter,
|
||||
isPublishedFilter:
|
||||
req.body.isPublishedFilter === 'on'
|
||||
? undefined
|
||||
: req.body.isPublishedFilter,
|
||||
statusFilter:
|
||||
req.body.statusFilter === 'complete'
|
||||
? 'complete'
|
||||
: req.body.statusFilter === 'inEdit'
|
||||
? 'in_edit'
|
||||
: undefined,
|
||||
isTerminologyReviewedFilter:
|
||||
req.body.isTerminologyReviewedFilter === 'on'
|
||||
? undefined
|
||||
: req.body.isTerminologyReviewedFilter,
|
||||
isLanguageReviewedFilter:
|
||||
req.body.isLanguageReviewedFilter === 'on'
|
||||
? undefined
|
||||
: req.body.isLanguageReviewedFilter,
|
||||
exportFileFormat: req.body.exportFileFormat
|
||||
}
|
||||
|
||||
const exportId = await Dictionary.beginExport(dictionaryId, exportParams)
|
||||
|
||||
res.end()
|
||||
|
||||
// Explicitcly catch any errors after the response has been sent
|
||||
// since the the final error handler won't be able to send another.
|
||||
try {
|
||||
// TODO Consider delegating processing of export to a seperate process or at least a seperate thread (much like importing, extraction, ...).
|
||||
await Dictionary.processExport(exportId)
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = dictionary
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
const { unlink, rm, mkdir } = require('fs/promises')
|
||||
const { unlink, rm, mkdir, writeFile } = require('fs/promises')
|
||||
const { promisify } = require('util')
|
||||
const { URLSearchParams } = require('url')
|
||||
const multer = require('multer')
|
||||
const i18next = require('i18next')
|
||||
const validator = require('validator')
|
||||
const axios = require('axios')
|
||||
const {
|
||||
getExtractionFilesPath,
|
||||
getDocumentsPath,
|
||||
getStopTermsPath,
|
||||
getConllusPath
|
||||
getConllusPath,
|
||||
getFileStats
|
||||
} = require('../../../models/helpers/extraction')
|
||||
const { checkIfcanBegin } = require('../../helpers/extraction')
|
||||
const Extraction = require('../../../models/extraction')
|
||||
const Domain = require('../../../models/domain')
|
||||
const email = require('../../../models/email')
|
||||
const { intoDbArray } = require('../../../models/helpers')
|
||||
const { origin } = require('../../../config/keys')
|
||||
const { DEFAULT_HITS_PER_PAGE } = require('../../../config/settings')
|
||||
const { origin, extractionApiOrigin } = require('../../../config/keys')
|
||||
const {
|
||||
DEFAULT_HITS_PER_PAGE,
|
||||
TEMP_EXPORT_PATH
|
||||
} = require('../../../config/settings')
|
||||
|
||||
const MAX_FILE_NAME_LENGTH = 100
|
||||
const MAX_FILE_SIZE = 10 ** 9 // 1 GB
|
||||
@@ -77,16 +82,19 @@ extraction.docsList = async (req, res) => {
|
||||
extraction.docsUpdate = async (req, res) => {
|
||||
try {
|
||||
await parseExtractionFileBody(req, res)
|
||||
const fileStats = await getFileStats(req.file.path)
|
||||
res.send(fileStats)
|
||||
} catch (error) {
|
||||
if (
|
||||
error instanceof multer.MulterError &&
|
||||
error.code === 'LIMIT_FILE_SIZE'
|
||||
) {
|
||||
throw Error('File too large. Must not be over 1 GB.')
|
||||
const customError = Error('File too large. Must not be over 1 GB.')
|
||||
customError.displayInProd = true
|
||||
throw customError
|
||||
}
|
||||
throw error
|
||||
}
|
||||
res.end()
|
||||
}
|
||||
|
||||
extraction.docDelete = async (req, res) => {
|
||||
@@ -108,16 +116,19 @@ extraction.stopTermsList = async (req, res) => {
|
||||
extraction.stopTermsUpdate = async (req, res) => {
|
||||
try {
|
||||
await parseExtractionFileBody(req, res)
|
||||
const fileStats = await getFileStats(req.file.path)
|
||||
res.send(fileStats)
|
||||
} catch (error) {
|
||||
if (
|
||||
error instanceof multer.MulterError &&
|
||||
error.code === 'LIMIT_FILE_SIZE'
|
||||
) {
|
||||
throw Error('File too large. Must not be over 1 GB.')
|
||||
const customError = Error('File too large. Must not be over 1 GB.')
|
||||
customError.displayInProd = true
|
||||
throw customError
|
||||
}
|
||||
throw error
|
||||
}
|
||||
res.end()
|
||||
}
|
||||
|
||||
extraction.stopTermDelete = async (req, res) => {
|
||||
@@ -141,7 +152,7 @@ extraction.ossSearch = [
|
||||
...(ossParams.keywords && { kljucneBesede: ossParams.keywords }),
|
||||
...(ossParams.domainUdk && { udk: ossParams.domainUdk })
|
||||
})
|
||||
const searchApiUrl = `http://rsdo.lhrs.feri.um.si:8080/oss/steviloBesedilPoIskanju?${searchParams}`
|
||||
const searchApiUrl = `${extractionApiOrigin}/oss/steviloBesedilPoIskanju?${searchParams}`
|
||||
|
||||
const { data: documentCount } = await axios.get(searchApiUrl)
|
||||
const canSave = documentCount && documentCount <= MAX_OSS_DOCUMENT_COUNT
|
||||
@@ -183,28 +194,39 @@ extraction.begin = async (req, res) => {
|
||||
|
||||
res.send(timeStarted)
|
||||
|
||||
if (ossParams) {
|
||||
// TODO This next method is only a temporary solution.
|
||||
// TODO It should be called before response and its execution delegated to a seperate process or at least a seperate thread.
|
||||
await Extraction.processOss(extractionId, ossParams.params)
|
||||
} else {
|
||||
// TODO This next method is only a temporary solution.
|
||||
// TODO It should be called before response and its execution delegated to a seperate process or at least a seperate thread.
|
||||
await Extraction.processOwn(extractionId, extractionName)
|
||||
}
|
||||
// Explicitcly catch any errors after the response has been sent
|
||||
// since the the final error handler won't be able to send another.
|
||||
try {
|
||||
if (ossParams) {
|
||||
// TODO This next method is only a temporary solution.
|
||||
// TODO It should be called before response and its execution delegated to a seperate process or at least a seperate thread.
|
||||
await Extraction.processOss(extractionId, ossParams.params)
|
||||
} else {
|
||||
// TODO This next method is only a temporary solution.
|
||||
// TODO It should be called before response and its execution delegated to a seperate process or at least a seperate thread.
|
||||
await Extraction.processOwn(extractionId, extractionName)
|
||||
}
|
||||
|
||||
const extractionLink = new URL('/luscenje', origin)
|
||||
const renderAsync = promisify(req.app.render.bind(req.app))
|
||||
const authorEmail = await Extraction.fetchAuthorEmail(extractionId)
|
||||
const emailHtml = await renderAsync('email/extraction-done', {
|
||||
extractionName,
|
||||
extractionLink
|
||||
})
|
||||
await email.send({
|
||||
to: authorEmail,
|
||||
subject: 'Luščenje končano',
|
||||
html: emailHtml
|
||||
})
|
||||
const extractionLink = new URL('/luscenje', origin)
|
||||
const renderAsync = promisify(req.app.render.bind(req.app))
|
||||
const { email: authorEmail, language: authorLanguage } =
|
||||
await Extraction.fetchAuthorData(extractionId)
|
||||
const emailHtml = await renderAsync(
|
||||
`email/extraction-done_${authorLanguage}`,
|
||||
{
|
||||
extractionName,
|
||||
extractionLink
|
||||
}
|
||||
)
|
||||
await email.send({
|
||||
to: authorEmail,
|
||||
subject: i18next.t('Luščenje končano', { lng: authorLanguage }),
|
||||
html: emailHtml
|
||||
})
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
extraction.duplicate = async (req, res) => {
|
||||
@@ -214,13 +236,36 @@ extraction.duplicate = async (req, res) => {
|
||||
}
|
||||
|
||||
extraction.termCandidatesExport = async (req, res) => {
|
||||
// TODO CSV logic (Luka's task)
|
||||
// const extractionId = req.params.id
|
||||
// const { from, to } = req.query
|
||||
// const fromIndex = +from > 1 ? Math.floor(from) - 1 : 0
|
||||
// const toIndex = Number.isInteger(+to) ? Math.abs(to) : undefined
|
||||
// console.log({ extractionId, fromIndex, toIndex })
|
||||
res.download('public/images/help-amebis-logo-pug-demo.png')
|
||||
// TODO More formats (CSV, TSV, TXT, ...)
|
||||
|
||||
const extractionId = req.params.id
|
||||
const { from, to } = req.query
|
||||
const fromIndex = +from > 1 ? Math.floor(from) - 1 : 0
|
||||
const toIndex = Number.isInteger(+(to === '' ? undefined : to))
|
||||
? Math.abs(to)
|
||||
: undefined
|
||||
|
||||
// TODO Authentication, authorization, validation.
|
||||
|
||||
const termCandidatesToExport = await Extraction.fetchTermCandidatesSlice(
|
||||
extractionId,
|
||||
fromIndex,
|
||||
toIndex
|
||||
)
|
||||
|
||||
const exportFileName = `term_candidates_${extractionId}`
|
||||
const exportFilePath = `${TEMP_EXPORT_PATH}/${exportFileName}`
|
||||
await writeFile(
|
||||
exportFilePath,
|
||||
JSON.stringify({ terminoloski_kandidati: termCandidatesToExport })
|
||||
)
|
||||
|
||||
const downloadAsync = promisify(res.download.bind(res))
|
||||
try {
|
||||
await downloadAsync(exportFilePath, 'term_candidates.json')
|
||||
} finally {
|
||||
await unlink(exportFilePath)
|
||||
}
|
||||
}
|
||||
|
||||
extraction.listFinishedForUser = async (req, res) => {
|
||||
@@ -251,8 +296,11 @@ function extractionFileFilter(req, file, cb) {
|
||||
fileType = 'stopTerms'
|
||||
break
|
||||
|
||||
default:
|
||||
return cb(Error('Invalid API endpoint'))
|
||||
default: {
|
||||
const customError = Error('Invalid API endpoint')
|
||||
customError.displayInProd = true
|
||||
return cb(customError)
|
||||
}
|
||||
}
|
||||
|
||||
const filenamePartsArray = file.originalname.split('.')
|
||||
@@ -264,30 +312,32 @@ function extractionFileFilter(req, file, cb) {
|
||||
(fileType === 'stopTerms' &&
|
||||
fileExtension !== VALID_STOP_TERMS_FILE_EXTENSION)
|
||||
) {
|
||||
return cb(Error('Invalid file type'))
|
||||
const customError = Error('Invalid file type')
|
||||
customError.displayInProd = true
|
||||
return cb(customError)
|
||||
}
|
||||
|
||||
const fileName = filenamePartsArray.join('.')
|
||||
if (!fileName || fileName.length > MAX_FILE_NAME_LENGTH) {
|
||||
return cb(
|
||||
Error(
|
||||
`Filename must be between 1 and ${MAX_FILE_NAME_LENGTH} characters long.`
|
||||
)
|
||||
const customError = Error(
|
||||
`Filename must be between 1 and ${MAX_FILE_NAME_LENGTH} characters long.`
|
||||
)
|
||||
customError.displayInProd = true
|
||||
return cb(customError)
|
||||
}
|
||||
if (!validator.isAlphanumeric(fileName[0], 'sl-SI', { ignore: '_' })) {
|
||||
return cb(
|
||||
Error(
|
||||
'Filename must begin with an alphanumeric character or an underscore.'
|
||||
)
|
||||
const customError = Error(
|
||||
'Filename must begin with an alphanumeric character or an underscore.'
|
||||
)
|
||||
customError.displayInProd = true
|
||||
return cb(customError)
|
||||
}
|
||||
if (!validator.isAlphanumeric(fileName, 'sl-SI', { ignore: ' _-.' })) {
|
||||
return cb(
|
||||
Error(
|
||||
'Filename can only contain alphanumeric characters, spaces, underscores, minuses and periods.'
|
||||
)
|
||||
const customError = Error(
|
||||
'Filename can only contain alphanumeric characters, spaces, underscores, minuses and periods.'
|
||||
)
|
||||
customError.displayInProd = true
|
||||
return cb(customError)
|
||||
}
|
||||
|
||||
req.fileType = fileType
|
||||
|
||||
@@ -66,6 +66,27 @@ exports.listMainEntries = async (req, res) => {
|
||||
{}
|
||||
)
|
||||
|
||||
// duplicated code below for filtering foreignent, optimize later
|
||||
const categoriesLabels = Object.keys(entriesByCategory)
|
||||
for (
|
||||
let categoryIndex = 0;
|
||||
categoryIndex < categoriesLabels.length;
|
||||
categoryIndex++
|
||||
) {
|
||||
entriesByCategory[categoriesLabels[categoryIndex]] = entriesByCategory[
|
||||
categoriesLabels[categoryIndex]
|
||||
].map(entry => {
|
||||
entry.foreignEntries = entry.foreignEntries?.filter(foreignEntry => {
|
||||
if (filters.targetLanguages.length > 0) {
|
||||
return filters.targetLanguages.includes(`${foreignEntry.lang.id}`)
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
})
|
||||
return entry
|
||||
})
|
||||
}
|
||||
|
||||
// res.send({ page, numberOfAllPages, entries })
|
||||
res.append('page', page)
|
||||
res.append('number-of-all-pages', numberOfAllPages)
|
||||
|
||||
@@ -8,6 +8,7 @@ const { DEFAULT_HITS_PER_PAGE } = require('../config/settings')
|
||||
const generateQuery = require('../models/helpers/search/generate-query')
|
||||
const { searchConsultancyEntryIndex } = require('../models/search-engine')
|
||||
const { prepareConsultancyEntries } = require('../models/helpers/search')
|
||||
const { getInstanceSetting, intoDbArray } = require('../models/helpers')
|
||||
// const { minEntriesRequirementCheckAndAct } = require('./helpers/dictionary')
|
||||
|
||||
const consultancy = {}
|
||||
@@ -16,6 +17,9 @@ const consultancyAdmin = {}
|
||||
consultancy.index = async (req, res) => {
|
||||
req.indexHitPageAmount = '5'
|
||||
|
||||
res.locals.isOwnConsultancyEnabled =
|
||||
(await getInstanceSetting('consultancy_type')) === 'own'
|
||||
|
||||
return await consultancyRequest(
|
||||
req,
|
||||
res,
|
||||
@@ -25,17 +29,24 @@ consultancy.index = async (req, res) => {
|
||||
}
|
||||
|
||||
consultancy.search = async (req, res) => {
|
||||
res.locals.isOwnConsultancyEnabled =
|
||||
(await getInstanceSetting('consultancy_type')) === 'own'
|
||||
|
||||
res.locals.queryKey = req.query.q
|
||||
|
||||
return await consultancyRequest(
|
||||
req,
|
||||
res,
|
||||
'published',
|
||||
'pages/consultancy/search'
|
||||
'pages/consultancy/search',
|
||||
req.t('Odgovori')
|
||||
)
|
||||
}
|
||||
|
||||
consultancy.specificQuestion = async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
// TODO i18n TIME FORMAT
|
||||
const entry = await ConsultancyEntry.fetchByIdWithFormattedTime(id)
|
||||
// const author = await User.fetchUser(entry.authorId)
|
||||
|
||||
@@ -44,12 +55,13 @@ consultancy.specificQuestion = async (req, res) => {
|
||||
entry.answerAuthors = entry.answerAuthors.filter(author => author !== '')
|
||||
|
||||
let authorString
|
||||
// TODO I18n
|
||||
if (entry.answerAuthors.length === 1) {
|
||||
authorString = 'Avtor'
|
||||
authorString = req.t('Avtor')
|
||||
} else if (entry.answerAuthors.length === 2) {
|
||||
authorString = 'Avtorja'
|
||||
authorString = req.t('Avtorja')
|
||||
} else {
|
||||
authorString = 'Avtorji'
|
||||
authorString = req.t('Avtorji')
|
||||
}
|
||||
|
||||
entry.domain = allPrimaryDomains.filter(
|
||||
@@ -62,18 +74,26 @@ consultancy.specificQuestion = async (req, res) => {
|
||||
entry.domain = false
|
||||
}
|
||||
|
||||
res.locals.isOwnConsultancyEnabled =
|
||||
(await getInstanceSetting('consultancy_type')) === 'own'
|
||||
|
||||
res.render('pages/consultancy/item-details', {
|
||||
allPrimaryDomains,
|
||||
authorString,
|
||||
entry
|
||||
entry,
|
||||
title: req.t('Odgovor')
|
||||
})
|
||||
}
|
||||
|
||||
consultancy.new = async (req, res) => {
|
||||
const allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains()
|
||||
|
||||
res.locals.isOwnConsultancyEnabled =
|
||||
(await getInstanceSetting('consultancy_type')) === 'own'
|
||||
|
||||
res.render('pages/consultancy/ask', {
|
||||
allPrimaryDomains
|
||||
allPrimaryDomains,
|
||||
title: req.t('Novo vprašanje')
|
||||
})
|
||||
}
|
||||
|
||||
@@ -83,6 +103,7 @@ consultancyAdmin.new = async (req, res) => {
|
||||
res,
|
||||
'new',
|
||||
'pages/consultancy/admin/index',
|
||||
req.t('Novo'),
|
||||
true,
|
||||
false
|
||||
)
|
||||
@@ -95,7 +116,8 @@ consultancyAdmin.users = async (req, res) => {
|
||||
|
||||
res.render('pages/consultancy/admin/users', {
|
||||
allPrimaryDomains,
|
||||
users
|
||||
users,
|
||||
title: req.t('Svetovalci')
|
||||
})
|
||||
}
|
||||
|
||||
@@ -105,6 +127,7 @@ consultancyAdmin.rejected = async (req, res) => {
|
||||
res,
|
||||
'rejected',
|
||||
'pages/consultancy/admin/rejected',
|
||||
req.t('Zavrnjeno'),
|
||||
true,
|
||||
false
|
||||
)
|
||||
@@ -116,8 +139,10 @@ consultancyAdmin.published = async (req, res) => {
|
||||
res,
|
||||
'published',
|
||||
'pages/consultancy/admin/published',
|
||||
req.t('Objavljeno'),
|
||||
true,
|
||||
false
|
||||
false,
|
||||
'published'
|
||||
)
|
||||
}
|
||||
|
||||
@@ -127,6 +152,7 @@ consultancyAdmin.prepared = async (req, res) => {
|
||||
res,
|
||||
'review',
|
||||
'pages/consultancy/admin/prepared',
|
||||
req.t('Pripravljeno'),
|
||||
true,
|
||||
false
|
||||
)
|
||||
@@ -142,6 +168,7 @@ consultancyAdmin.inProgress = async (req, res) => {
|
||||
res,
|
||||
'in progress',
|
||||
'pages/consultancy/admin/in-progress',
|
||||
req.t('V delu'),
|
||||
true,
|
||||
false
|
||||
)
|
||||
@@ -171,6 +198,7 @@ consultancyAdmin.edit = async (req, res) => {
|
||||
} 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 author = await User.fetchUser(entry.authorId)
|
||||
@@ -186,7 +214,8 @@ consultancyAdmin.edit = async (req, res) => {
|
||||
author,
|
||||
isPublished,
|
||||
// TODO Luka: I suspect this will not work as intended on staging or production environments. Test.
|
||||
urlPrefix: req.protocol + '://' + req.get('host')
|
||||
urlPrefix: req.protocol + '://' + req.get('host'),
|
||||
title: req.t('Urejanje')
|
||||
})
|
||||
}
|
||||
|
||||
@@ -198,14 +227,14 @@ function dateMap(obj) {
|
||||
return obj
|
||||
}
|
||||
|
||||
async function mapDomainIdToDomainNameSlovene(obj) {
|
||||
async function mapDomainIdToDomainNameSlovene(obj, t) {
|
||||
try {
|
||||
const area = await Domain.fetchById(
|
||||
obj.domainPrimaryId ? obj.domainPrimaryId : obj.domainPrimaryIdInitial
|
||||
)
|
||||
obj.area = area.nameSl
|
||||
} catch {
|
||||
obj.area = 'Ni področja'
|
||||
obj.area = t('Ni področja')
|
||||
}
|
||||
|
||||
return obj
|
||||
@@ -227,14 +256,14 @@ function mapInitialValuesAsEmpty(obj) {
|
||||
return obj
|
||||
}
|
||||
|
||||
async function mapEntryList(list) {
|
||||
async function mapEntryList(list, t) {
|
||||
return await Promise.all(
|
||||
list.map(entry => {
|
||||
let entity = utils.compose(dateMap, mapInitialValuesAsEmpty)(entry)
|
||||
|
||||
// TODO Each mapDomainIdToDomainNameSlovene call leads to one DB query.
|
||||
// TODO Test if and what scenarios can lead to too many calls and how it can be avoided.
|
||||
entity = utils.composeAsync(mapDomainIdToDomainNameSlovene)(entry)
|
||||
entity = utils.composeAsync(mapDomainIdToDomainNameSlovene)(entry, t)
|
||||
|
||||
return entity
|
||||
})
|
||||
@@ -283,11 +312,26 @@ async function consultancyRequest(
|
||||
res,
|
||||
type,
|
||||
url,
|
||||
title = req.t('Svetovanje'),
|
||||
isAdminPage = false,
|
||||
privilegeToSeAll = true // this method seperates consultancy main from admin, so all results get visible TO ALL REGISTERED USERS, not just admins
|
||||
) {
|
||||
const searchString = req.query.q?.trim() ?? ''
|
||||
|
||||
let allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains()
|
||||
|
||||
/// filter selected domains for the prompt ///
|
||||
// Not duplicates of this code arise...
|
||||
const pdList = intoDbArray(req.query.pd, 'always')
|
||||
|
||||
allPrimaryDomains = allPrimaryDomains.map(entry => {
|
||||
if (pdList.includes(`${entry.id}`)) {
|
||||
entry.selected = true
|
||||
}
|
||||
return entry
|
||||
})
|
||||
/// //////////////////////////////////////////
|
||||
|
||||
let assignedConsultant
|
||||
|
||||
if (
|
||||
@@ -333,11 +377,11 @@ async function consultancyRequest(
|
||||
|
||||
let entries = prepareConsultancyEntries(hits)
|
||||
// console.log({ entries, numberOfAllHits, numberOfAllPages })
|
||||
|
||||
// TODO I18n - nameSl
|
||||
entries = entries.map(entry => {
|
||||
entry.primaryDomain = entry.primaryDomain
|
||||
? entry.primaryDomain.nameSl
|
||||
: 'nedefinirano'
|
||||
: req.t('nedefinirano')
|
||||
|
||||
if (entry.assignedConsultants) {
|
||||
entry.firstName = entry.assignedConsultants[0]?.firstName
|
||||
@@ -365,8 +409,6 @@ async function consultancyRequest(
|
||||
return entry
|
||||
})
|
||||
|
||||
const allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains()
|
||||
|
||||
// const entryList = await mapEntryList(inProgressEntryList)
|
||||
const userList = await User.fetchConsultants()
|
||||
|
||||
@@ -401,7 +443,9 @@ async function consultancyRequest(
|
||||
entries, // entryList,
|
||||
userList,
|
||||
numberOfAllPages,
|
||||
queryCount: numberOfAllHits
|
||||
queryCount: numberOfAllHits,
|
||||
consultancyPageType: type,
|
||||
title
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@ const Comment = require('../models/comment')
|
||||
const genEditorAllQuery = require('../models/helpers/search/generate-query/editor/all')
|
||||
const { searchEntryIndex } = require('../models/search-engine')
|
||||
const { prepareEditorEntries } = require('../models/helpers/search')
|
||||
const { getInstanceSetting } = require('../models/helpers')
|
||||
const { getInstanceSetting, intoDbArray } = require('../models/helpers')
|
||||
const { getExportFilesPath } = require('../models/helpers/dictionary')
|
||||
const { DEFAULT_HITS_PER_PAGE, DATA_FILES_PATH } = require('../config/settings')
|
||||
const {
|
||||
statusChangeCheckAndAct,
|
||||
@@ -17,6 +18,7 @@ const {
|
||||
} = require('./helpers/dictionary')
|
||||
const SFDSuggestionImporter = require('./helpers/search-filter-data-suggestion-importer')
|
||||
const Extraction = require('../models/extraction')
|
||||
const { isGeneratorFunction } = require('util/types')
|
||||
|
||||
const importFileBodyParser = multer({
|
||||
dest: `${DATA_FILES_PATH}/dict_import_temp`,
|
||||
@@ -39,7 +41,7 @@ dictionary.list = async (req, res) => {
|
||||
dictionaries = await Dictionary.fetchAllByUser(req.user.id)
|
||||
}
|
||||
res.render('pages/dictionaries/list', {
|
||||
title: 'Seznam slovarjev',
|
||||
title: req.t('Seznam slovarjev'),
|
||||
dictionaries
|
||||
})
|
||||
}
|
||||
@@ -51,11 +53,11 @@ dictionary.new = async (req, res) => {
|
||||
await Promise.all([
|
||||
Dictionary.fetchAllPrimaryDomains(),
|
||||
Dictionary.fetchAllApprovedSecondaryDomains(),
|
||||
Dictionary.fetchAllLanguages(language)
|
||||
Dictionary.fetchAllLanguages(language, true)
|
||||
])
|
||||
|
||||
res.render('pages/dictionaries/new', {
|
||||
title: 'Nov slovar',
|
||||
title: req.t('Nov slovar'),
|
||||
allPrimaryDomains,
|
||||
allSecondaryDomains,
|
||||
allLanguages
|
||||
@@ -82,7 +84,7 @@ dictionary.editDescription = async (req, res) => {
|
||||
])
|
||||
|
||||
res.render('pages/dictionaries/description', {
|
||||
title: 'Ime in opis',
|
||||
title: req.t('Osnovni podatki'),
|
||||
allPrimaryDomains,
|
||||
allSecondaryDomains,
|
||||
dictionary,
|
||||
@@ -125,7 +127,7 @@ dictionary.editUsers = async (req, res) => {
|
||||
}
|
||||
|
||||
res.render(viewPath, {
|
||||
title: 'Uporabniki',
|
||||
title: req.t('Uporabniki'),
|
||||
dictionary,
|
||||
userRights,
|
||||
entriesCount,
|
||||
@@ -142,6 +144,7 @@ dictionary.updateUsers = async (req, res) => {
|
||||
|
||||
const newDictStatus = await determineNewStatus(isPublished)
|
||||
|
||||
// TODO I18n - nameSl
|
||||
const { nameSl, status: oldDictStatus } = await Dictionary.fetchEditUsers(
|
||||
dictionaryId
|
||||
)
|
||||
@@ -172,12 +175,13 @@ dictionary.updateUsers = async (req, res) => {
|
||||
|
||||
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)
|
||||
Dictionary.fetchAllLanguages(language, true)
|
||||
])
|
||||
|
||||
let viewPath
|
||||
@@ -190,7 +194,7 @@ dictionary.editStructure = async (req, res) => {
|
||||
}
|
||||
|
||||
res.render(viewPath, {
|
||||
title: 'Struktura slovarskega sestavka',
|
||||
title: req.t('Struktura slovarskega sestavka'),
|
||||
dictionary,
|
||||
associatedLanguages,
|
||||
allLanguages
|
||||
@@ -223,7 +227,7 @@ dictionary.editAdvanced = async (req, res) => {
|
||||
}
|
||||
|
||||
res.render(viewPath, {
|
||||
title: 'Napredno',
|
||||
title: req.t('Napredno'),
|
||||
dictionary: { id: req.params.dictionaryId },
|
||||
dictionaryName
|
||||
})
|
||||
@@ -251,7 +255,7 @@ dictionary.comments = async (req, res) => {
|
||||
}
|
||||
|
||||
res.render(viewPath, {
|
||||
title: 'Komentarji',
|
||||
title: req.t('Komentarji'),
|
||||
numberOfAllPages,
|
||||
dictionary: { id: req.params.dictionaryId },
|
||||
comments,
|
||||
@@ -261,10 +265,12 @@ dictionary.comments = async (req, res) => {
|
||||
|
||||
dictionary.showImportFromFileForm = async (req, res) => {
|
||||
const { dictionaryId } = req.params
|
||||
const [imports, dictionaryName] = await Promise.all([
|
||||
Dictionary.fetchAllImports(dictionaryId),
|
||||
Dictionary.fetchName(dictionaryId)
|
||||
])
|
||||
const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
|
||||
const [{ pages_total: numberOfAllPages, results }, dictionaryName] =
|
||||
await Promise.all([
|
||||
Dictionary.fetchAllImports(dictionaryId, resultsPerPage, 1),
|
||||
Dictionary.fetchName(dictionaryId)
|
||||
])
|
||||
let viewPath
|
||||
switch (req.baseUrl) {
|
||||
case '/slovarji':
|
||||
@@ -275,9 +281,10 @@ dictionary.showImportFromFileForm = async (req, res) => {
|
||||
}
|
||||
|
||||
res.render(viewPath, {
|
||||
title: 'Uvoz iz datoteke',
|
||||
title: req.t('Uvoz iz datoteke'),
|
||||
dictionary: { id: dictionaryId },
|
||||
imports,
|
||||
numberOfAllPages,
|
||||
results,
|
||||
dictionaryName
|
||||
})
|
||||
}
|
||||
@@ -289,7 +296,7 @@ dictionary.listAdminDictionaries = async (req, res) => {
|
||||
await Dictionary.fetchAllAdminDictionaries(resultsPerPage, 1)
|
||||
|
||||
res.render('pages/admin/dictionaries-list', {
|
||||
title: 'Struktura slovarjev',
|
||||
title: req.t('Seznam slovarjev'),
|
||||
numberOfAllPages,
|
||||
results
|
||||
})
|
||||
@@ -312,7 +319,7 @@ dictionary.adminEditDescription = async (req, res) => {
|
||||
])
|
||||
|
||||
res.render('pages/admin/dictionary-description', {
|
||||
title: 'Podatki',
|
||||
title: req.t('Osnovni podatki'),
|
||||
allPrimaryDomains,
|
||||
allSecondaryDomains,
|
||||
dictionary,
|
||||
@@ -364,11 +371,11 @@ dictionary.showImportFromExtractionForm = async (req, res) => {
|
||||
switch (req.baseUrl) {
|
||||
case '/slovarji':
|
||||
viewPath = 'pages/dictionaries/extraction-import'
|
||||
title = 'Uvoz'
|
||||
title = req.t('Uvoz iz luščilnika')
|
||||
break
|
||||
case '/admin':
|
||||
viewPath = 'pages/admin/dictionary-extraction-import'
|
||||
title = 'Uvoz luščenje'
|
||||
title = req.t('Uvoz iz luščilnika')
|
||||
}
|
||||
|
||||
res.render(viewPath, {
|
||||
@@ -381,7 +388,12 @@ dictionary.showImportFromExtractionForm = async (req, res) => {
|
||||
|
||||
dictionary.showExportToFileForm = async (req, res) => {
|
||||
const { dictionaryId } = req.params
|
||||
const dictionaryName = await Dictionary.fetchName(dictionaryId)
|
||||
const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
|
||||
const [dictionaryName, { pages_total: numberOfAllPages, results }] =
|
||||
await Promise.all([
|
||||
Dictionary.fetchName(dictionaryId),
|
||||
Dictionary.fetchExports(dictionaryId, resultsPerPage, 1)
|
||||
])
|
||||
let viewPath
|
||||
switch (req.baseUrl) {
|
||||
case '/slovarji':
|
||||
@@ -390,11 +402,12 @@ dictionary.showExportToFileForm = async (req, res) => {
|
||||
case '/admin':
|
||||
viewPath = 'pages/admin/dictionary-export'
|
||||
}
|
||||
|
||||
res.render(viewPath, {
|
||||
title: 'Izvoz',
|
||||
dictionary: { id: req.params.dictionaryId },
|
||||
dictionaryName
|
||||
title: req.t('Izvoz'),
|
||||
dictionary: { id: dictionaryId },
|
||||
dictionaryName,
|
||||
numberOfAllPages,
|
||||
results
|
||||
})
|
||||
}
|
||||
|
||||
@@ -418,7 +431,7 @@ dictionary.editDomainLabels = async (req, res) => {
|
||||
}
|
||||
|
||||
res.render(viewPath, {
|
||||
title: 'Področne oznake',
|
||||
title: req.t('Področne oznake'),
|
||||
dictionary: { id: dictionaryId },
|
||||
numberOfAllPages,
|
||||
results,
|
||||
@@ -448,7 +461,7 @@ dictionary.showContent = async (req, res) => {
|
||||
const terms = prepareEditorEntries(hits)
|
||||
|
||||
res.render('pages/dictionaries/content', {
|
||||
title: 'Vsebina slovarja',
|
||||
title: req.t('Vsebina slovarja'),
|
||||
terms,
|
||||
canPublishEntriesInEdit,
|
||||
dictionaryName,
|
||||
@@ -466,7 +479,7 @@ dictionary.showSecondaryDomains = async (req, res) => {
|
||||
await Dictionary.fetchAllSecondaryDomains(resultsPerPage, 1)
|
||||
|
||||
res.render('pages/admin/areas', {
|
||||
title: 'Podpodročja',
|
||||
title: req.t('Področne oznake'),
|
||||
numberOfAllPages,
|
||||
results
|
||||
})
|
||||
@@ -501,12 +514,27 @@ dictionary.dictionaryList = async (req, res) => {
|
||||
)
|
||||
|
||||
const numberOfAllHits = parseInt(
|
||||
(await Dictionary.fetchAllDictionariesCount()).count
|
||||
(await Dictionary.fetchAllDictionariesPublishedCount()).count
|
||||
)
|
||||
const numberOfAllPages = Math.ceil(numberOfAllHits / hitsPerPage)
|
||||
|
||||
const allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains()
|
||||
|
||||
/*
|
||||
/// filter selected domains for the prompt ///
|
||||
// Not duplicates of this code arise...
|
||||
const pdList = intoDbArray(req.query.pd, 'always')
|
||||
|
||||
allPrimaryDomains = allPrimaryDomains.map(entry => {
|
||||
if (pdList.includes(`${entry.id}`)) {
|
||||
entry.selected = true
|
||||
}
|
||||
return entry
|
||||
})
|
||||
|
||||
/// //////////////////////////////////////////
|
||||
*/
|
||||
|
||||
dictionaries = dictionaries.map(e => {
|
||||
if (!e.portalcode) {
|
||||
e.portalcode = defaultportalcode
|
||||
@@ -518,7 +546,7 @@ dictionary.dictionaryList = async (req, res) => {
|
||||
const isDictionaryListPage = true
|
||||
|
||||
res.render('pages/dictionaries/dictlist', {
|
||||
title: 'Seznam slovarjev',
|
||||
title: req.t('Seznam slovarjev'),
|
||||
dictionaries,
|
||||
allPrimaryDomains,
|
||||
numberOfAllPages,
|
||||
@@ -532,6 +560,7 @@ dictionary.dictionaryList = async (req, res) => {
|
||||
dictionary.dictionaryDetails = async (req, res) => {
|
||||
const { absolutePrevPath, sentFromEntryId } = req.query
|
||||
const dictId = req.params.dictionaryId
|
||||
const title = req.t('O slovarju')
|
||||
const {
|
||||
allPrimaryDomains,
|
||||
sourceLanguages,
|
||||
@@ -555,7 +584,7 @@ dictionary.dictionaryDetails = async (req, res) => {
|
||||
|
||||
// check if it is a local dictionary
|
||||
if (!dictionaryData.portalname && !dictionaryData.portalcode) {
|
||||
dictionaryData[0].portalname = await getInstanceSetting('portal_name')
|
||||
dictionaryData[0].portalname = await getInstanceSetting('portal_name_sl')
|
||||
dictionaryData[0].portalcode = await getInstanceSetting('portal_code')
|
||||
}
|
||||
|
||||
@@ -579,7 +608,7 @@ dictionary.dictionaryDetails = async (req, res) => {
|
||||
)
|
||||
|
||||
const structData = {
|
||||
prevWindowTitle: 'Nazaj',
|
||||
prevWindowTitle: req.t('Nazaj'),
|
||||
dictName: dictionaryData[0].dictionarysl,
|
||||
portalCode: dictionaryData[0].portalcode,
|
||||
portalName: dictionaryData[0].portalname,
|
||||
@@ -589,13 +618,14 @@ dictionary.dictionaryDetails = async (req, res) => {
|
||||
languages: reducedData.languages ? reducedData.languages.join(', ') : ''
|
||||
}
|
||||
|
||||
// TODO I18n
|
||||
if (reducedData.author) {
|
||||
if (reducedData.author.length > 2) {
|
||||
structData.authorLabel = 'Avtorji'
|
||||
structData.authorLabel = req.t('Avtorji')
|
||||
} else if (reducedData.author.length === 2) {
|
||||
structData.authorLabel = 'Avtorja'
|
||||
structData.authorLabel = req.t('Avtorja')
|
||||
} else if (reducedData.author.length === 1) {
|
||||
structData.authorLabel = 'Avtor'
|
||||
structData.authorLabel = req.t('Avtor')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -623,7 +653,8 @@ dictionary.dictionaryDetails = async (req, res) => {
|
||||
finalData,
|
||||
numberOfAllPages,
|
||||
comments,
|
||||
commentCount
|
||||
commentCount,
|
||||
title
|
||||
}) // todo
|
||||
}
|
||||
|
||||
@@ -680,8 +711,28 @@ dictionary.importFromFile = async (req, res) => {
|
||||
}
|
||||
}
|
||||
|
||||
dictionary.exportDownload = async (req, res) => {
|
||||
// TODO Add authentication and authorization.
|
||||
|
||||
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")
|
||||
}
|
||||
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') return cb(Error('Invalid file type'))
|
||||
if (file.mimetype !== 'text/xml') {
|
||||
const customError = Error('Invalid file type')
|
||||
customError.displayInProd = true
|
||||
return cb(customError)
|
||||
}
|
||||
cb(null, true)
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,10 @@ extraction.list = async (req, res) => {
|
||||
)
|
||||
}
|
||||
|
||||
res.render('pages/extraction/list', { title: 'Luščenje seznam', extractions })
|
||||
res.render('pages/extraction/list', {
|
||||
title: req.t('Seznam luščenj'),
|
||||
extractions
|
||||
})
|
||||
}
|
||||
|
||||
extraction.create = async (req, res) => {
|
||||
@@ -39,7 +42,7 @@ extraction.create = async (req, res) => {
|
||||
return res.redirect(303, 'back')
|
||||
}
|
||||
|
||||
const extractionName = `Luščenje ${extractionCount + 1}`
|
||||
const extractionName = req.t('Luščenje') + `${extractionCount + 1}`
|
||||
const { extractionType } = req.body
|
||||
|
||||
let extractionId
|
||||
@@ -81,7 +84,7 @@ extraction.edit = async (req, res) => {
|
||||
extraction.keywords = intoDbArray(params.keywords, 'always')
|
||||
|
||||
res.render('pages/extraction/edit-oss', {
|
||||
title: 'KAS + dokumenti',
|
||||
title: req.t('Besedila'),
|
||||
id: extractionId,
|
||||
extraction,
|
||||
allPrimaryDomains,
|
||||
@@ -93,7 +96,7 @@ extraction.edit = async (req, res) => {
|
||||
Extraction.fetchAllStopTermsFilesStats(extractionId)
|
||||
])
|
||||
res.render('pages/extraction/edit-own', {
|
||||
title: 'Besedila',
|
||||
title: req.t('Besedila'),
|
||||
id: extractionId,
|
||||
extraction,
|
||||
extractionDocuments,
|
||||
@@ -117,6 +120,7 @@ extraction.docsEdit = async (req, res) => {
|
||||
)
|
||||
|
||||
res.render('pages/extraction/docs-edit', {
|
||||
title: req.t('Besedila'),
|
||||
id: extractionId,
|
||||
extractionDocuments
|
||||
})
|
||||
@@ -129,6 +133,7 @@ extraction.stopTermsEdit = async (req, res) => {
|
||||
)
|
||||
|
||||
res.render('pages/extraction/stop-terms-edit', {
|
||||
title: req.t('Stop termini'),
|
||||
id: extractionId,
|
||||
stopTermsFiles
|
||||
})
|
||||
@@ -145,6 +150,7 @@ extraction.listTermCandidates = async (req, res) => {
|
||||
const firstPageOfTermCandidates = termCandidates.slice(0, hitsPerPage)
|
||||
|
||||
res.render('pages/extraction/term-candidates', {
|
||||
title: req.t('Terminološki kandidati'),
|
||||
extractionId,
|
||||
termCandidatesJson,
|
||||
firstPageOfTermCandidates,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* global __ */
|
||||
|
||||
const { getInstanceSetting } = require('../../models/helpers')
|
||||
const Dictionary = require('../../models/dictionary')
|
||||
const cache = require('../../models/cache')
|
||||
@@ -56,6 +58,7 @@ exports.minEntriesRequirementCheckAndAct = {
|
||||
if (!isBelowMinEntriesThreshold || wasEmailAlreadySent) return
|
||||
|
||||
// Prepare and send notification emails.
|
||||
// TODO I18n - nameSl
|
||||
const [nameSl, adminEmails, dictionariesAdminEmails] = await Promise.all([
|
||||
Dictionary.fetchName(dictionaryId),
|
||||
Dictionary.fetchAdminEmails(dictionaryId),
|
||||
@@ -68,14 +71,16 @@ 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
|
||||
})
|
||||
|
||||
// TODO i18n - What language are the email title and content (we already have email translated)
|
||||
await email.send({
|
||||
to: allEmails,
|
||||
subject: 'Obvestilo o številu gesel',
|
||||
subject: __('Obvestilo o številu gesel'),
|
||||
html: emailHtml
|
||||
})
|
||||
|
||||
@@ -118,6 +123,7 @@ 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,
|
||||
@@ -133,7 +139,7 @@ exports.statusChangeCheckAndAct = {
|
||||
const dictionariesAdminEmails =
|
||||
await Dictionary.fetchDictionariesAdminEmails()
|
||||
const type = 'unpublish'
|
||||
|
||||
// TODO I18n - nameSl
|
||||
await renderAndSendStatusChangeEmails(
|
||||
appRef,
|
||||
type,
|
||||
@@ -151,6 +157,7 @@ exports.statusChangeCheckAndAct = {
|
||||
const type =
|
||||
isApprovalRequired === 'T' ? 'publish-approval' : 'publish-no-approval'
|
||||
|
||||
// TODO I18n - nameSl
|
||||
await renderAndSendStatusChangeEmails(
|
||||
appRef,
|
||||
type,
|
||||
@@ -171,6 +178,7 @@ exports.statusChangeCheckAndAct = {
|
||||
user
|
||||
) {
|
||||
if (statusOld === 'reviewed' && statusNew !== 'reviewed') {
|
||||
// TODO I18n - nameSl
|
||||
const [nameSl, adminEmails] = await Promise.all([
|
||||
Dictionary.fetchName(dictionaryId),
|
||||
Dictionary.fetchAdminEmails(dictionaryId)
|
||||
@@ -189,6 +197,7 @@ exports.statusChangeCheckAndAct = {
|
||||
}
|
||||
|
||||
// Helper function used by statusChangeCheckAndAct methods.
|
||||
// TODO I18n - nameSl
|
||||
async function renderAndSendStatusChangeEmails(
|
||||
appRef,
|
||||
type,
|
||||
@@ -203,9 +212,10 @@ async function renderAndSendStatusChangeEmails(
|
||||
nameSl
|
||||
})
|
||||
|
||||
// TODO i18n - What language are the email title and content (we already have email translated)
|
||||
await email.send({
|
||||
to: targetEmails,
|
||||
subject: 'Sprememba stanja slovarja',
|
||||
subject: __('Sprememba stanja slovarja'),
|
||||
html: emailHtml
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,12 +8,15 @@ helper.initialize = async () => {
|
||||
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 = initializers.sourceLanguages.filter(
|
||||
initializers.targetLanguages = (
|
||||
await Dictionary.fetchAllLanguages(language)
|
||||
).filter(
|
||||
// drop slovene language
|
||||
l => l.id !== 32
|
||||
)
|
||||
@@ -22,7 +25,7 @@ helper.initialize = async () => {
|
||||
|
||||
initializers.portals = []
|
||||
initializers.portals.push({
|
||||
name: await getInstanceSetting('portal_name'),
|
||||
name: await getInstanceSetting('portal_name_sl'),
|
||||
code: await getInstanceSetting('portal_code')
|
||||
})
|
||||
|
||||
|
||||
+211
-17
@@ -2,12 +2,16 @@ const Entry = require('../models/entry')
|
||||
const { getInstanceSetting } = require('../models/helpers')
|
||||
const Dictionary = require('../models/dictionary')
|
||||
const Comment = require('../models/comment')
|
||||
const { searchEntryIndex } = require('../models/search-engine')
|
||||
const {
|
||||
searchEntryIndex,
|
||||
searchConsultancyEntryIndex
|
||||
} = require('../models/search-engine')
|
||||
const { intoDbArray } = require('../models/helpers')
|
||||
const {
|
||||
prepareEntries,
|
||||
prepareAggregation,
|
||||
prepareSeachFilterData
|
||||
prepareSeachFilterData,
|
||||
prepareConsultancyEntries
|
||||
} = require('../models/helpers/search')
|
||||
const generateQuery = require('../models/helpers/search/generate-query')
|
||||
const { DEFAULT_HITS_PER_PAGE } = require('../config/settings')
|
||||
@@ -16,6 +20,7 @@ const User = require('../models/user')
|
||||
|
||||
// TODO Luka (note to self): Measure performance, consider caching.
|
||||
exports.index = async (req, res) => {
|
||||
const { language } = req
|
||||
const {
|
||||
allPrimaryDomains,
|
||||
sourceLanguages,
|
||||
@@ -30,8 +35,10 @@ exports.index = async (req, res) => {
|
||||
englishLanguageEnabled
|
||||
)
|
||||
|
||||
const portalName = await getInstanceSetting('portal_name')
|
||||
const portalDescription = await getInstanceSetting('portal_description')
|
||||
const portalName = await getInstanceSetting(`portal_name_${language}`)
|
||||
const portalDescription = await getInstanceSetting(
|
||||
`portal_description_${language}`
|
||||
)
|
||||
|
||||
const isRoot = true
|
||||
|
||||
@@ -53,7 +60,9 @@ exports.search = async (req, res) => {
|
||||
|
||||
if (!searchString) return res.redirect('/')
|
||||
|
||||
const {
|
||||
const title = req.t('Iskanje')
|
||||
|
||||
let {
|
||||
allPrimaryDomains,
|
||||
sourceLanguages,
|
||||
targetLanguages,
|
||||
@@ -81,6 +90,32 @@ exports.search = async (req, res) => {
|
||||
true
|
||||
)
|
||||
|
||||
// Consultancy
|
||||
|
||||
const hitsQueryConsultancy = generateQuery.consultancy(
|
||||
searchString,
|
||||
{
|
||||
status: 'published',
|
||||
primaryDomain: filters.primaryDomains[0]
|
||||
},
|
||||
hitsPerPage,
|
||||
page
|
||||
)
|
||||
|
||||
const hitsConsultancy = await searchConsultancyEntryIndex(
|
||||
hitsQueryConsultancy
|
||||
)
|
||||
|
||||
const consultancyHits = hitsConsultancy.body.hits.total.value
|
||||
|
||||
const consultancyURL = `/svetovanje/iskanje?q=${searchString}${
|
||||
filters.primaryDomains.length > 0 ? '&pd=' + filters.primaryDomains[0] : '' // consultancy filtering only supports one domain
|
||||
}`
|
||||
|
||||
// console.log(entries)
|
||||
|
||||
//
|
||||
|
||||
const [hits, aggregationRaw] = await Promise.all([
|
||||
searchEntryIndex(hitsQuery),
|
||||
searchEntryIndex(aggregateQuery)
|
||||
@@ -143,6 +178,30 @@ exports.search = async (req, res) => {
|
||||
sources: false
|
||||
}
|
||||
|
||||
// Keep selected items on refresh
|
||||
sourceLanguages = addSelectedIdentifierToFilters(
|
||||
sourceLanguages,
|
||||
filters.sourceLanguages
|
||||
)
|
||||
|
||||
targetLanguages = addSelectedIdentifierToFilters(
|
||||
targetLanguages,
|
||||
filters.targetLanguages
|
||||
)
|
||||
allPrimaryDomains = addSelectedIdentifierToFilters(
|
||||
allPrimaryDomains,
|
||||
filters.primaryDomains
|
||||
)
|
||||
|
||||
allDictionaryNames = addSelectedIdentifierToFilters(
|
||||
allDictionaryNames,
|
||||
filters.dictionaries
|
||||
)
|
||||
|
||||
/// //////////////
|
||||
|
||||
portals = addSelectedIdentifierToPortals(portals, filters.sources)
|
||||
|
||||
if (count < 1) {
|
||||
return res.render('pages/search/no-results', {
|
||||
allPrimaryDomains,
|
||||
@@ -154,6 +213,8 @@ exports.search = async (req, res) => {
|
||||
entriesByCategory,
|
||||
searchFilterData,
|
||||
disabledSideMenuFilters,
|
||||
consultancyHits,
|
||||
consultancyURL,
|
||||
// allAggregation,
|
||||
numberOfAllHits,
|
||||
numberOfAllPages,
|
||||
@@ -161,6 +222,74 @@ exports.search = async (req, res) => {
|
||||
})
|
||||
}
|
||||
|
||||
// duplicated code below for filtering foreignentries, optimize later
|
||||
// Below code filters target languages based on source and target filters for each category
|
||||
const categoriesLabels = Object.keys(entriesByCategory)
|
||||
for (
|
||||
let categoryIndex = 0;
|
||||
categoryIndex < categoriesLabels.length;
|
||||
categoryIndex++
|
||||
) {
|
||||
entriesByCategory[categoriesLabels[categoryIndex]] = entriesByCategory[
|
||||
categoriesLabels[categoryIndex]
|
||||
].map(entry => {
|
||||
entry.foreignEntries = entry.foreignEntries?.filter(foreignEntry => {
|
||||
if (filters.targetLanguages.length > 0) {
|
||||
if (aggregation.sourceLanguages.length === 1) {
|
||||
filters.sourceLanguages = [aggregation.sourceLanguages[0].id]
|
||||
}
|
||||
|
||||
return (
|
||||
filters.targetLanguages.includes(`${foreignEntry.lang.id}`) ||
|
||||
filters.sourceLanguages?.includes(`${foreignEntry.lang.id}`)
|
||||
)
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
})
|
||||
return entry
|
||||
})
|
||||
}
|
||||
|
||||
// Disable target languages logic
|
||||
|
||||
if (
|
||||
filters.sourceLanguages.length > 1 || // more or equal than 2 source languages
|
||||
(filters.sourceLanguages.length < 1 && // No filters, but more than 1 source language filters returned
|
||||
aggregation.sourceLanguages.length > 1) ||
|
||||
(aggregation.targetLanguages.length === 1 &&
|
||||
aggregation.sourceLanguages.length === 1 &&
|
||||
aggregation.targetLanguages[0].id === aggregation.sourceLanguages[0].id) //
|
||||
) {
|
||||
// predicate 1 Disable if 2 or more filters in Source languages are selected
|
||||
// predicate 2 If none are selected, then check if displayed filters are more than 2
|
||||
aggregation.targetLanguages = []
|
||||
filters.targetLanguages = []
|
||||
targetLanguages = []
|
||||
searchFilterData.targetLanguages = []
|
||||
disabledSideMenuFilters.targetLanguages = true
|
||||
}
|
||||
|
||||
// remove source language from target language
|
||||
filters.sourceLanguages.forEach(entry => {
|
||||
if (
|
||||
aggregation.targetLanguages.filter(toFilter => toFilter.id === entry)
|
||||
.length > 0
|
||||
) {
|
||||
aggregation.targetLanguages = aggregation.targetLanguages.filter(
|
||||
toFilter => toFilter.id !== entry
|
||||
)
|
||||
searchFilterData.targetLanguages =
|
||||
searchFilterData.targetLanguages.filter(
|
||||
toFilter => toFilter.id !== entry
|
||||
)
|
||||
}
|
||||
})
|
||||
if (aggregation.targetLanguages.length < 0) {
|
||||
searchFilterData.targetLanguages = []
|
||||
disabledSideMenuFilters.targetLanguages = true
|
||||
}
|
||||
|
||||
// TODO Add a page title?
|
||||
res.render('pages/search/results', {
|
||||
allPrimaryDomains,
|
||||
@@ -172,15 +301,19 @@ exports.search = async (req, res) => {
|
||||
entriesByCategory,
|
||||
searchFilterData,
|
||||
disabledSideMenuFilters,
|
||||
consultancyHits,
|
||||
consultancyURL,
|
||||
// allAggregation,
|
||||
numberOfAllHits,
|
||||
numberOfAllPages,
|
||||
page
|
||||
page,
|
||||
title
|
||||
})
|
||||
}
|
||||
|
||||
exports.entryDetails = async (req, res) => {
|
||||
const termId = req.params.entryId
|
||||
const title = req.t('Termin')
|
||||
|
||||
/* const [entry, domainLabels] = await Promise.all([
|
||||
Entry.fetchFullWithOrderedForeignLanguages(termId),
|
||||
@@ -188,12 +321,16 @@ exports.entryDetails = async (req, res) => {
|
||||
]) */
|
||||
|
||||
const entry = await Entry.fetchFullWithOrderedForeignLanguages(termId)
|
||||
|
||||
/* const entryData = {
|
||||
entry
|
||||
// allDomainLabelsJoined: domainLabels.map(e => e.name).join(', ')
|
||||
} */
|
||||
|
||||
// If external url, just redirect
|
||||
if (entry.external_url) {
|
||||
return res.redirect(entry.external_url)
|
||||
}
|
||||
|
||||
// unnecessary legacy assigment, refactor when time is available
|
||||
const entryData = entry
|
||||
|
||||
@@ -228,8 +365,8 @@ exports.entryDetails = async (req, res) => {
|
||||
///
|
||||
|
||||
// check if it is a local dictionary
|
||||
if (!dictionaryData.portalname && !dictionaryData.portalcode) {
|
||||
dictionaryData[0].portalname = await getInstanceSetting('portal_name')
|
||||
if (!dictionaryData.portalnamesl && !dictionaryData.portalcode) {
|
||||
dictionaryData[0].portalname = await getInstanceSetting('portal_name_sl')
|
||||
dictionaryData[0].portalcode = await getInstanceSetting('portal_code')
|
||||
}
|
||||
|
||||
@@ -258,7 +395,7 @@ exports.entryDetails = async (req, res) => {
|
||||
// struct data contains important data and re-maps for unification (maybe refactor later)
|
||||
const structData = {
|
||||
termId: termId,
|
||||
prevWindowTitle: 'Iskanje',
|
||||
prevWindowTitle: req.t('Iskanje'),
|
||||
prevHref: '/iskanje',
|
||||
portalCode: dictionaryData[0].portalcode,
|
||||
portalName: dictionaryData[0].portalname,
|
||||
@@ -270,13 +407,14 @@ exports.entryDetails = async (req, res) => {
|
||||
languages: reducedData.languages ? reducedData.languages.join(', ') : ''
|
||||
}
|
||||
|
||||
// TODO I18n
|
||||
if (reducedData.author) {
|
||||
if (reducedData.author.length > 2) {
|
||||
structData.authorLabel = 'Avtorji'
|
||||
structData.authorLabel = req.t('Avtorji')
|
||||
} else if (reducedData.author.length === 2) {
|
||||
structData.authorLabel = 'Avtorja'
|
||||
structData.authorLabel = req.t('Avtorja')
|
||||
} else if (reducedData.author.length === 1) {
|
||||
structData.authorLabel = 'Avtor'
|
||||
structData.authorLabel = req.t('Avtor')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,27 +459,63 @@ exports.entryDetails = async (req, res) => {
|
||||
selectedDomainLabelsForEntryString,
|
||||
numberOfAllPages,
|
||||
comments,
|
||||
commentCount
|
||||
commentCount,
|
||||
title
|
||||
})
|
||||
}
|
||||
|
||||
exports.myProfile = async (req, res) => {
|
||||
res.render('pages/profile/my-profile', { title: 'Moj račun' })
|
||||
res.render('pages/profile/my-profile', { title: req.t('Osnovni podatki') })
|
||||
}
|
||||
|
||||
exports.deleteProfile = async (req, res) => {
|
||||
res.render('pages/profile/delete-profile', { title: req.t('Izbriši račun') })
|
||||
}
|
||||
|
||||
exports.changePassword = async (req, res) => {
|
||||
res.render('pages/profile/change-password', { title: 'Spremeni geslo' })
|
||||
res.render('pages/profile/change-password', {
|
||||
title: req.t('Spremeni geslo')
|
||||
})
|
||||
}
|
||||
|
||||
exports.resetPassword = async (req, res) => {
|
||||
const { token } = req.query
|
||||
|
||||
// console.log(token)
|
||||
res.render('pages/reset-password/reset-password', {
|
||||
// title: 'Pozabljeno geslo'
|
||||
isValidToken: token === '123',
|
||||
token
|
||||
})
|
||||
}
|
||||
|
||||
exports.userSettings = async (req, res) => {
|
||||
const hitsPerPageArr = await User.fetchAllowedHitsPerPage()
|
||||
res.render('pages/profile/change-profile-settings', {
|
||||
title: 'Nastavitve računa',
|
||||
title: req.t('Nastavitve računa'),
|
||||
hitsPerPageArr,
|
||||
hitsForUser: req.user?.hitsPerPage
|
||||
})
|
||||
}
|
||||
|
||||
exports.changeUserLanguage = async (req, res) => {
|
||||
const { languageCode } = req.params
|
||||
const validCodes = ['sl', 'en']
|
||||
|
||||
if (!validCodes.includes(languageCode)) {
|
||||
throw Error(`Invalid language: ${languageCode}`)
|
||||
}
|
||||
|
||||
const { user } = req
|
||||
if (user) {
|
||||
await User.updateLanguage(user.id, languageCode)
|
||||
} else {
|
||||
req.session.language = languageCode
|
||||
}
|
||||
|
||||
res.redirect('/')
|
||||
}
|
||||
|
||||
function mergeDomains(
|
||||
domainList,
|
||||
aggregationFn = (acc, n) => {
|
||||
@@ -384,3 +558,23 @@ function filterResults(entries) {
|
||||
|
||||
return [termLst, ftermLst, otherLst]
|
||||
}
|
||||
|
||||
function addSelectedIdentifierToFilters(source, selectedIDs) {
|
||||
return source.map(entry => {
|
||||
if (selectedIDs.includes(`${entry.id}`)) {
|
||||
entry.selected = true
|
||||
}
|
||||
|
||||
return entry
|
||||
})
|
||||
}
|
||||
|
||||
function addSelectedIdentifierToPortals(source, selectedIDs) {
|
||||
return source.map(entry => {
|
||||
if (selectedIDs.includes(`${entry.code}`)) {
|
||||
entry.selected = true
|
||||
}
|
||||
|
||||
return entry
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ portal.instanceSettings = async (req, res) => {
|
||||
const portal = await Portal.fetchInstanceSettings()
|
||||
|
||||
res.render('pages/admin/portal', {
|
||||
title: 'Nastavitve portala',
|
||||
title: req.t('Nastavitve portala'),
|
||||
portal
|
||||
})
|
||||
}
|
||||
@@ -24,7 +24,7 @@ portal.instanceDictSettings = async (req, res) => {
|
||||
const dictionary = await Portal.fetchInstanceDictSettings()
|
||||
|
||||
res.render('pages/admin/settings-dictionaries', {
|
||||
title: 'Nastavitve slovarjev',
|
||||
title: req.t('Nastavitve slovarjev'),
|
||||
dictionary
|
||||
})
|
||||
}
|
||||
@@ -39,7 +39,7 @@ portal.updateInstanceDictSettings = async (req, res) => {
|
||||
portal.instanceConsultancySettings = async (req, res) => {
|
||||
const consultancy = await Portal.fetchInstanceConsultancySettings()
|
||||
res.render('pages/admin/portal-consultancy-settings', {
|
||||
title: 'Nastavitve svetovalnice',
|
||||
title: req.t('Nastavitve svetovalnice'),
|
||||
consultancy
|
||||
})
|
||||
}
|
||||
@@ -53,7 +53,7 @@ portal.updateInstanceConusltacySettings = async (req, res) => {
|
||||
|
||||
portal.new = async (req, res) => {
|
||||
res.render('pages/admin/new-connection', {
|
||||
title: 'Nova povezava'
|
||||
title: req.t('Nova povezava')
|
||||
})
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ portal.list = async (req, res) => {
|
||||
const allLinkedPortals = await Portal.fetchAll()
|
||||
|
||||
res.render('pages/admin/connections-list', {
|
||||
title: 'Seznam povezav',
|
||||
title: req.t('Seznam povezav'),
|
||||
allLinkedPortals
|
||||
})
|
||||
}
|
||||
@@ -70,7 +70,10 @@ portal.fetchPortal = async (req, res) => {
|
||||
const portalId = req.params.portalId
|
||||
const portal = await Portal.fetchPortal(portalId)
|
||||
|
||||
res.render('pages/admin/portal-edit', { title: 'Uredi povezavo', portal })
|
||||
res.render('pages/admin/portal-edit', {
|
||||
title: req.t('Uredi povezavo'),
|
||||
portal
|
||||
})
|
||||
}
|
||||
|
||||
portal.updatePortal = async (req, res) => {
|
||||
@@ -88,7 +91,7 @@ portal.fetchSelectedLinkedDictionaries = async (req, res) => {
|
||||
await Portal.fetchSelectedLinkedDictionaries(linkedId, resultsPerPage, 1)
|
||||
|
||||
res.render('pages/admin/portal-list-dict', {
|
||||
title: 'Slovarji portala',
|
||||
title: req.t('Slovarji portala'),
|
||||
linkedId,
|
||||
numberOfAllPages,
|
||||
results
|
||||
@@ -108,7 +111,7 @@ portal.fetchAllLinkedDictionaries = async (req, res) => {
|
||||
await Portal.fetchAllLinkedDictionaries(resultsPerPage, 1)
|
||||
|
||||
res.render('pages/admin/portals-all-linked-dictionaries', {
|
||||
title: 'Povezani',
|
||||
title: req.t('Povezani slovarji'),
|
||||
numberOfAllPages,
|
||||
results
|
||||
})
|
||||
@@ -130,7 +133,7 @@ portal.comments = async (req, res) => {
|
||||
1
|
||||
)
|
||||
res.render('pages/admin/comments', {
|
||||
title: 'Komentarji',
|
||||
title: req.t('Komentarji'),
|
||||
numberOfAllPages,
|
||||
dictionary: { id: req.params.dictionaryId },
|
||||
comments
|
||||
|
||||
@@ -12,7 +12,10 @@ const user = {}
|
||||
|
||||
user.register = async (req, res) => {
|
||||
// TODO Add validation.
|
||||
const userId = await User.create(req.body)
|
||||
const userId = await User.create({
|
||||
...req.body,
|
||||
language: req.session.language
|
||||
})
|
||||
const activationToken = (await RandomBytesAsync(32)).toString('hex')
|
||||
await User.saveActivationToken(userId, activationToken)
|
||||
const { email: userEmail, username } = req.body
|
||||
@@ -20,16 +23,16 @@ user.register = async (req, res) => {
|
||||
activationLink.searchParams.set('token', activationToken)
|
||||
activationLink = activationLink.href
|
||||
const renderAsync = promisify(req.app.render.bind(req.app))
|
||||
const emailHtml = await renderAsync('email/user-activation', {
|
||||
const emailHtml = await renderAsync(`email/user-activation_${req.language}`, {
|
||||
username,
|
||||
activationLink
|
||||
})
|
||||
await email.send({
|
||||
to: userEmail,
|
||||
subject: 'Aktivacija računa',
|
||||
subject: req.t('Aktivacija računa'),
|
||||
html: emailHtml
|
||||
})
|
||||
res.send('Registracija uspešna')
|
||||
res.send(req.t('Registracija uspešna'))
|
||||
}
|
||||
|
||||
user.activateAccount = async (req, res) => {
|
||||
@@ -39,6 +42,12 @@ user.activateAccount = async (req, res) => {
|
||||
await User.activateAccount(user)
|
||||
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.redirect('/')
|
||||
}
|
||||
|
||||
@@ -46,8 +55,9 @@ user.login = async (req, res, next) => {
|
||||
passport.authenticate(
|
||||
'local',
|
||||
{
|
||||
badRequestMessage:
|
||||
badRequestMessage: req.t(
|
||||
'Nepravilno uporabniško ime, elektronski naslov ali geslo.'
|
||||
)
|
||||
},
|
||||
async (err, user, info) => {
|
||||
if (err) return next(err)
|
||||
@@ -69,7 +79,12 @@ user.login = async (req, res, next) => {
|
||||
res.cookie('remember_me', rememberMeToken, rememberMeCookieSettings)
|
||||
}
|
||||
|
||||
res.send('Prijava uspešna')
|
||||
if (req.session.language) {
|
||||
await User.updateLanguage(user.id, req.session.language)
|
||||
delete req.session.language
|
||||
}
|
||||
|
||||
res.send(req.t('Prijava uspešna'))
|
||||
}
|
||||
)(req, res, next)
|
||||
}
|
||||
@@ -82,6 +97,8 @@ user.logout = async (req, res) => {
|
||||
await User.clearRememberMeToken(rememberMeToken)
|
||||
}
|
||||
|
||||
req.session.language = req.user.language
|
||||
|
||||
req.logout()
|
||||
// Manually clear session.passport due to bug in current passport version.
|
||||
delete req.session.passport.user
|
||||
@@ -96,7 +113,7 @@ user.list = async (req, res) => {
|
||||
1
|
||||
)
|
||||
res.render('pages/admin/user-list', {
|
||||
title: 'Seznam slovarjeva',
|
||||
title: req.t('Seznam uporabnikov'),
|
||||
numberOfAllPages,
|
||||
results
|
||||
})
|
||||
@@ -104,7 +121,10 @@ user.list = async (req, res) => {
|
||||
|
||||
user.listAllWithPortalRoles = async (req, res) => {
|
||||
const users = await User.fetchAllWithPortalRoles()
|
||||
res.render('pages/admin/user-portals', { title: 'Seznam slovarjev', users })
|
||||
res.render('pages/admin/user-portals', {
|
||||
title: req.t('Skrbniki portala'),
|
||||
users
|
||||
})
|
||||
}
|
||||
|
||||
user.findByUsernameOrEmail = async (req, res) => {
|
||||
@@ -125,7 +145,7 @@ user.adminEdit = async (req, res) => {
|
||||
User.fetchUserRoles(userId)
|
||||
])
|
||||
res.render('pages/admin/user-edit', {
|
||||
title: 'Urejanje uporabnikov',
|
||||
title: req.t('Uporabnik'),
|
||||
userData,
|
||||
userRoles
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user