const user = require('../../../middleware/user') 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 = {} 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 = {} consultancyEntry.status = 'new' consultancyEntry.authorId = req.user.id consultancyEntry.institution = q.institution const { description } = q if (!description) { return res.status(400).send('Description is a required parameter!') } consultancyEntry.description = description consultancyEntry.domainPrimaryIdInitial = q.domainPrimary if (!consultancyEntry.domainPrimaryIdInitial || q.domainPrimary === '-1') { consultancyEntry.domainPrimaryIdInitial = null // undefined } consultancyEntry.existingSolutions = q.existing_solutions consultancyEntry.examplesOfUse = q.examples_of_use Object.keys(consultancyEntry).forEach(key => { consultancyEntry[key] = helper.removeHtmlTags(consultancyEntry[key]).trim() }) const isOwnConsultancyEnabled = (await getInstanceSetting('consultancy_type')) === 'own' let domainNameSl if (consultancyEntry.domainPrimaryIdInitial) { domainNameSl = ( await Domain.fetchById(consultancyEntry.domainPrimaryIdInitial) ).nameSl } else { domainNameSl = '' } 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', { 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: subjectText, html: emailHtml }) res.status(201).send() } consultancy.updateDomain = async (req, res) => { const { id, value } = req.body if (!id) return res.status(400).send() await User.updateConsultancyDomains(id, value) res.status(204).send() } consultancy.insertConsultantAndHisDomainsByUsername = async (req, res) => { const { username, domains } = req.body if (!username) { return res.status(400).send() } await User.insertNewConsultantWithDomainByUsername(username, domains) res.status(204).send() } consultancy.removeConsultant = async (req, res) => { const { id } = req.body if (!id) return res.status(400).send() await User.removeConsultant(id) res.send() } consultancy.getSharedAuthors = async (req, res) => { const { id } = req.query if (!id) return res.status(400).send() const authors = await ConsultancyEntry.getSharedAuthorsArray(id) res.send(authors) } consultancy.insertNonModerator = async (req, res) => { const userId = req.body.user_id const entryId = req.body.entry_id if (!userId || !entryId) return res.status(400).send() await ConsultancyEntry.insertNonModerator(entryId, userId) await ConsultancyEntry.indexIntoSearchEngine(entryId, true) res.status(201).send() } consultancy.getSharedAuthorsBeforePublish = async (req, res) => { const { id } = req.query if (!id) return res.status(400).send() const authors = await ConsultancyEntry.getSharedAuthorsArrayBeforePublish(id) res.send(authors) } consultancy.updateSharedAuthors = async (req, res) => { const { id, authors } = req.body if (!id || !authors.length) return res.status(400).send() await ConsultancyEntry.updateSharedAuthorsArray(id, authors) await ConsultancyEntry.indexIntoSearchEngine(id, true) res.send({}) } /* function assign - assigns from any state of the consultancy question to work in-progress inputs: id -> id of the question */ consultancy.assign = async (req, res) => { const questionId = req.body.question_id let userId = req.body.user_id if (!questionId) return res.status(400).send() if (!userId) { const user = await ConsultancyEntry.getModerator(questionId) userId = user.id if (!userId) return res.status(400).send({}) } await ConsultancyEntry.assignWorkInProgress(questionId, userId) await ConsultancyEntry.indexIntoSearchEngine(questionId, true) // TODO SEND MAIL TO MODERATOR /* Skrbnik svetovalnice vam je v urejanje dodelil novo terminološko vprašanje. */ const emails = await ConsultancyEntry.fetchModeratorEmail(questionId) 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: req.t('Novo terminološko vprašanje'), html: emailHtml }) res.send() } consultancy.reject = async (req, res) => { const questionId = req.body.question_id if (!questionId) return res.status(400).send() await ConsultancyEntry.rejectEntry(questionId) await ConsultancyEntry.indexIntoSearchEngine(questionId, true) res.send() } consultancy.sendToReview = async (req, res) => { const questionId = req.body.question_id if (!questionId) return res.status(400).send() await ConsultancyEntry.sendToReview(questionId) await ConsultancyEntry.indexIntoSearchEngine(questionId, true) /* SEND MAIL TO ALL CONSULTANCY ADMINS Za potrditev objave ste prejeli novo terminološko vprašanje. const allEmailsSet = new Set([...adminEmails, ...dictionariesAdminEmails]) const allEmails = [] for (const email of allEmailsSet) allEmails.push(email) const type = 'delete' const renderAsync = promisify(appRef.render.bind(appRef)) const emailHtml = await renderAsync('email/dictionary-status-change', { type, nameSl }) await email.send({ to: allEmails, subject: 'Obvestilo o številu gesel', html: emailHtml }) */ const emails = await ConsultancyEntry.fetchConsultancyAdminEmails() 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: req.t('Potrditev objave'), html: emailHtml }) res.send() } consultancy.publish = async (req, res) => { const questionId = req.body.question_id const answerAuthors = req.body.answer_authors if (!questionId) return res.status(400).send() const entry = await ConsultancyEntry.fetchById(questionId) 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_${author.language}`, { 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) res.send() } consultancy.updateQuestion = [ (req, res, next) => { const { id } = req.body if (!id) return res.status(400).send({}) req.entryId = id next() }, user.canConsultEntry, async (req, res) => { const { id, questionTitle, domain: domainId, question, answer } = req.body if (questionTitle === '' || question === '' || answer === '') { return res .status(422) .send(req.t('Polja naslov, vprašanje in mnenje so obvezna!')) } const entry = await ConsultancyEntry.fetchById(id) entry.domainPrimaryId = domainId > 0 ? domainId : null entry.question = question entry.answer = answer // helper.removeHtmlTags(answer).trim() entry.title = questionTitle // TODO Luka: Miha, update only fields that were updated. await ConsultancyEntry.updateQuestion(entry) await ConsultancyEntry.indexIntoSearchEngine(id, true) res.send({}) } ] consultancy.insertNonModerator = async (req, res) => { const questionId = req.body.question_id const userId = req.body.user_id if (!questionId || !userId) return res.status(400).send() await ConsultancyEntry.insertNonModerator(questionId, userId) await ConsultancyEntry.indexIntoSearchEngine(questionId, true) res.send({}) } consultancy.deleteConsultantForEntry = async (req, res) => { const questionId = req.body.question_id const userId = req.body.user_id if (!questionId || !userId) return res.status(400).send() await ConsultancyEntry.removeConsultantForEntry(questionId, userId) await ConsultancyEntry.indexIntoSearchEngine(questionId, true) res.send({}) } consultancy.deleteQuestion = async (req, res) => { const { id } = req.body // question/entry id if (!id) return res.status(400).send() await ConsultancyEntry.removeEntry(id) await deleteConsultancyEntryFromIndex(id, true) 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