Files
term_portal/express/controllers/index.js
T
2022-12-07 04:43:36 +01:00

387 lines
10 KiB
JavaScript

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 { intoDbArray } = require('../models/helpers')
const {
prepareEntries,
prepareAggregation,
prepareSeachFilterData
} = require('../models/helpers/search')
const generateQuery = require('../models/helpers/search/generate-query')
const { DEFAULT_HITS_PER_PAGE } = require('../config/settings')
const SFDSuggestionImporter = require('./helpers/search-filter-data-suggestion-importer')
const User = require('../models/user')
// TODO Luka (note to self): Measure performance, consider caching.
exports.index = async (req, res) => {
const {
allPrimaryDomains,
sourceLanguages,
targetLanguages,
allDictionaryNames,
portals
} = await SFDSuggestionImporter.initialize()
const englishLanguageEnabled = false // dummy variable for future edit
const latestDicts = await Dictionary.fetchLatest3DictsByPublishDate(
englishLanguageEnabled
)
const portalName = await getInstanceSetting('portal_name')
const portalDescription = await getInstanceSetting('portal_description')
const isRoot = true
res.render('pages/index', {
allPrimaryDomains,
sourceLanguages,
targetLanguages,
allDictionaryNames,
portals,
latestDicts,
portalName,
portalDescription,
isRoot
})
}
exports.search = async (req, res) => {
const searchString = req.query.q?.trim()
if (!searchString) return res.redirect('/')
const {
allPrimaryDomains,
sourceLanguages,
targetLanguages,
allDictionaryNames,
portals
} = await SFDSuggestionImporter.initialize()
const filters = {
sourceLanguages: intoDbArray(req.query.sl, 'always'),
targetLanguages: intoDbArray(req.query.tl, 'always'),
primaryDomains: intoDbArray(req.query.pd, 'always'),
dictionaries: intoDbArray(req.query.d, 'always'),
sources: intoDbArray(req.query.s, 'always')
}
const hitsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
const page = +req.query.p > 0 ? +req.query.p : 1
const [hitsQuery, aggregateQuery] = await generateQuery.main(
searchString,
filters,
hitsPerPage,
page,
true
)
const [hits, aggregationRaw] = await Promise.all([
searchEntryIndex(hitsQuery),
searchEntryIndex(aggregateQuery)
])
const numberOfAllHits = hits.body.hits.total.value
const numberOfAllPages = Math.ceil(numberOfAllHits / hitsPerPage)
// TODO If there's no hits, this is probably the place where suggestions/related queries and processing would happen.
let entriesByCategory = prepareEntries(hits)
// Similar to search query without any extra filters, this is required for modal to diplay ALL res.
// const allAggregation = await indexAllResultsNoFiltering(req, res)
const aggregation = await prepareAggregation(aggregationRaw)
const searchFilterData = prepareSeachFilterData(aggregation, filters)
// TODO Luka (note to self): Consider reworking Miha's logic below. It's hacky and possibly error prone.
// TODO copied code below, maybe refactor to a helper function?
// check express/controllers/api/v1/search.js listMainEntries
entriesByCategory = Object.entries(entriesByCategory).reduce(
(acc, category, index) => {
acc[category[0]] = category[1].map(entry => {
if (entry.foreignEntries) {
entry.foreignEntries = entry.foreignEntries.map(fe => {
fe.terms = fe.terms ? fe.terms.filter(term => !!term) : []
fe.synonyms = fe.synonyms
? fe.synonyms.filter(synonym => !!synonym)
: []
fe.nbspCount = fe.terms.length + fe.synonyms.length - 1
return fe
})
}
return entry
})
return acc
},
{}
)
const count = Object.values(entriesByCategory).reduce((acc, cat, idx) => {
return acc + cat.length
}, 0)
// filter to 5 resuts per search filter
Object.entries(searchFilterData).forEach(([key, value]) => {
searchFilterData[key] = value.filter((p, i) => {
return i < 5
})
})
// todo implement ALL disabled in pug view if required
const disabledSideMenuFilters = {
sourceLanguages: searchString === '*',
targetLanguages: false,
primaryDomains: false,
dictionaries: false,
sources: false
}
if (count < 1) {
return res.render('pages/search/no-results', {
allPrimaryDomains,
sourceLanguages,
targetLanguages,
allDictionaryNames,
portals,
searchString,
entriesByCategory,
searchFilterData,
disabledSideMenuFilters,
// allAggregation,
numberOfAllHits,
numberOfAllPages,
page
})
}
// TODO Add a page title?
res.render('pages/search/results', {
allPrimaryDomains,
sourceLanguages,
targetLanguages,
allDictionaryNames,
portals,
searchString,
entriesByCategory,
searchFilterData,
disabledSideMenuFilters,
// allAggregation,
numberOfAllHits,
numberOfAllPages,
page
})
}
exports.entryDetails = async (req, res) => {
const termId = req.params.entryId
/* const [entry, domainLabels] = await Promise.all([
Entry.fetchFullWithOrderedForeignLanguages(termId),
Dictionary.fetchDomainLabelsFromEntryId(termId)
]) */
const entry = await Entry.fetchFullWithOrderedForeignLanguages(termId)
/* const entryData = {
entry
// allDomainLabelsJoined: domainLabels.map(e => e.name).join(', ')
} */
// unnecessary legacy assigment, refactor when time is available
const entryData = entry
const {
allPrimaryDomains,
sourceLanguages,
targetLanguages,
allDictionaryNames,
portals
} = await SFDSuggestionImporter.initialize()
const [dictStruct, dictionaryData, selectedDomainLabelsForEntry] =
await Promise.all([
Dictionary.fetchDictionaryWithEditStructure(entry.dictionary_id),
Dictionary.fetchDictionaryBasicInfo(entry.dictionary_id),
Entry.fetchDomainLabels(termId)
])
///
const filters = { ctxType: 'entry_dict_ext', ctxId: termId }
// TODO: integrate numberOfAllPages, commentCount with pug
const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
const page = 1 // +req.query.p > 0 ? +req.query.p : 1
const {
pages_total: numberOfAllPages,
comments,
comment_count: commentCount
} = await Comment.list(filters, req.user, resultsPerPage, page)
///
// check if it is a local dictionary
if (!dictionaryData.portalname && !dictionaryData.portalcode) {
dictionaryData[0].portalname = await getInstanceSetting('portal_name')
dictionaryData[0].portalcode = await getInstanceSetting('portal_code')
}
const reducedData = dictionaryData.reduce(
(acc, x) => {
if (acc.isbegin) {
x.languages = [x.languagesl]
x.subDomains = [x.domainsecondarysl]
return x
}
if (!acc.languages.includes(x.languagesl)) {
acc.languages.push(x.languagesl)
}
if (!acc.subDomains.includes(x.domainsecondarysl)) {
acc.subDomains.push(x.domainsecondarysl)
}
return acc
},
{ isbegin: true }
)
const { structure } = dictStruct
// Improved version of entropy, but some data duplications still exist
// struct data contains important data and re-maps for unification (maybe refactor later)
const structData = {
termId: termId,
prevWindowTitle: 'Iskanje',
prevHref: '/iskanje',
portalCode: dictionaryData[0].portalcode,
portalName: dictionaryData[0].portalname,
dictName: structure.nameSl,
dictHref: `/slovarji/${structure.id}/o-slovarju?sentFromEntryId=${termId}`,
fullAuthorName: reducedData.author ? reducedData.author.join(', ') : '',
areas: dictionaryData[0].domain_primary,
subareas: reducedData.subDomains ? reducedData.subDomains.join(', ') : '',
languages: reducedData.languages ? reducedData.languages.join(', ') : ''
}
if (reducedData.author) {
if (reducedData.author.length > 2) {
structData.authorLabel = 'Avtorji'
} else if (reducedData.author.length === 2) {
structData.authorLabel = 'Avtorja'
} else if (reducedData.author.length === 1) {
structData.authorLabel = 'Avtor'
}
}
const finalData = {
...reducedData,
...structData
}
let selectedDomainLabelsForEntryString = ''
if (selectedDomainLabelsForEntry.length) {
selectedDomainLabelsForEntryString = mergeDomains(
selectedDomainLabelsForEntry,
(acc, n) => {
if (acc === '') return n.name
else return acc + ', ' + n.name
}
)
}
/* entryData.entry.foreign_entries.forEach((val, idx) => {
entry.foreignEntries[idx] = await
}) */
const langs = await Dictionary.fetchLanguages(entryData.dictionary_id)
entryData.foreign_entries.forEach((val, idx) => {
try {
entry.foreign_entries[idx].name_sl = langs[idx].nameSl
entry.foreign_entries[idx].name_en = langs[idx].nameEn
} catch (e) {}
})
res.render('pages/search/result-detail', {
allPrimaryDomains,
entryData,
sourceLanguages,
targetLanguages,
allDictionaryNames,
portals,
termId,
structure,
finalData,
selectedDomainLabelsForEntryString,
numberOfAllPages,
comments,
commentCount
})
}
exports.myProfile = async (req, res) => {
res.render('pages/profile/my-profile', { title: 'Moj račun' })
}
exports.changePassword = async (req, res) => {
res.render('pages/profile/change-password', { title: 'Spremeni geslo' })
}
exports.userSettings = async (req, res) => {
const hitsPerPageArr = await User.fetchAllowedHitsPerPage()
res.render('pages/profile/change-profile-settings', {
title: 'Nastavitve računa',
hitsPerPageArr,
hitsForUser: req.user?.hitsPerPage
})
}
function mergeDomains(
domainList,
aggregationFn = (acc, n) => {
if (acc === '') return n
else return acc + ', ' + n
}
) {
return Array.from(domainList).reduce(aggregationFn, '')
}
function filterResults(entries) {
let mode = 0
const termLst = []
const ftermLst = []
const otherLst = []
entries.forEach(entry => {
switch (entry._title) {
case 'term':
mode = 0
break
case 'foreignTerm':
mode = 1
break
case 'other':
mode = 2
}
switch (mode) {
case 0:
termLst.push(entry)
break
case 1:
ftermLst.push(entry)
break
case 2:
otherLst.push(entry)
break
}
})
return [termLst, ftermLst, otherLst]
}