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)
|
||||
|
||||
Reference in New Issue
Block a user