Fix most glaring bugs and vulnerabilities
This commit is contained in:
+11
-2
@@ -6,6 +6,7 @@ const helmet = require('helmet')
|
||||
const favicon = require('serve-favicon')
|
||||
const cookieParser = require('cookie-parser')
|
||||
const createError = require('http-errors')
|
||||
const flash = require('connect-flash-plus')
|
||||
const i18next = require('i18next')
|
||||
const i18nextMiddleware = require('i18next-http-middleware')
|
||||
// const debug = require('debug')('termPortal:app')
|
||||
@@ -18,7 +19,8 @@ const i18n = require('./middleware/i18n')
|
||||
const passport = require('./middleware/auth')
|
||||
const user = require('./middleware/user')
|
||||
const settings = require('./middleware/settings')
|
||||
const { enhanceLocals } = require('./middleware')
|
||||
const { enhanceLocals, adjustHeaders } = require('./middleware')
|
||||
const { capitalize } = require('./utils')
|
||||
|
||||
// Import Routers.
|
||||
const apiRouter = require('./routes/api')
|
||||
@@ -41,16 +43,22 @@ app.locals.basedir = viewsPath
|
||||
const inDevEnv = app.get('env') === 'development'
|
||||
if (isBehindProxy) app.set('trust proxy', 1) // Trust first proxy.
|
||||
app.locals.inDevEnv = inDevEnv
|
||||
app.locals.capitalize = capitalize
|
||||
|
||||
// Mount middleware.
|
||||
app.use(logger('dev'))
|
||||
app.use(helmet(helmetConfig))
|
||||
app.use(favicon(path.join(__dirname, 'public', 'images', 'favicon.ico')))
|
||||
app.use(express.static(path.join(__dirname, 'public')))
|
||||
app.use(
|
||||
express.static(path.join(__dirname, 'public'), {
|
||||
maxAge: inDevEnv ? 0 : '1h'
|
||||
})
|
||||
)
|
||||
app.use(express.json({ type: ['application/json', 'application/csp-report'] }))
|
||||
app.use(express.urlencoded({ extended: true }))
|
||||
app.use(cookieParser(secret))
|
||||
app.use(session)
|
||||
app.use(flash())
|
||||
app.use(passport.initialize())
|
||||
app.use(passport.session())
|
||||
app.use(passport.authenticate('remember-me'))
|
||||
@@ -59,6 +67,7 @@ app.use(i18nextMiddleware.handle(i18next))
|
||||
app.use(user.enhance)
|
||||
app.use(settings.prepareRequiredSettings)
|
||||
app.use(enhanceLocals)
|
||||
app.use(adjustHeaders)
|
||||
|
||||
if (inDevEnv) {
|
||||
app.post(
|
||||
|
||||
+2
-5
@@ -12,8 +12,6 @@ const cache = require('../models/cache')
|
||||
const searchEngine = require('../models/search-engine')
|
||||
const email = require('../models/email')
|
||||
const init = require('../config/init')
|
||||
const { seedDummyData } = require('../models/comment')
|
||||
// const { initDemoData } = require('../models/demo-paginacija')
|
||||
const app = require('../app')
|
||||
const debug = require('debug')('termPortal:server')
|
||||
const http = require('http')
|
||||
@@ -42,12 +40,11 @@ const server = http.createServer(app)
|
||||
cache.waitForConnection(),
|
||||
searchEngine.waitForConnection(),
|
||||
email.waitForConnection(),
|
||||
init()
|
||||
init.fsStructure()
|
||||
])
|
||||
await searchEngine.initEntryIndex()
|
||||
await searchEngine.initConsultancyEntryIndex()
|
||||
seedDummyData()
|
||||
// initDemoData()
|
||||
await init.adminUser()
|
||||
server.listen(port)
|
||||
})()
|
||||
|
||||
|
||||
+55
-1
@@ -1,6 +1,60 @@
|
||||
const { mkdir } = require('fs/promises')
|
||||
const db = require('../models/db')
|
||||
const User = require('../models/user')
|
||||
const {
|
||||
portalAdminInitialEmail,
|
||||
portalAdminInitialPassword
|
||||
} = require('../config/keys')
|
||||
const { TEMP_EXPORT_PATH } = require('./settings')
|
||||
const debug = require('debug')('termPortal:config/init')
|
||||
|
||||
module.exports = async () => {
|
||||
exports.fsStructure = async () => {
|
||||
await mkdir(TEMP_EXPORT_PATH, { recursive: true })
|
||||
}
|
||||
|
||||
async function createPortalAdmin() {
|
||||
const {
|
||||
rows: [{ exists }]
|
||||
} = await db.query(
|
||||
"SELECT EXISTS (SELECT 1 FROM user_role WHERE role_name = 'portal admin')"
|
||||
)
|
||||
|
||||
if (exists) return 'Skipping creation of portal admin (already exists)'
|
||||
|
||||
const ADMIN_BASE = 'admin'
|
||||
const adminUser = {
|
||||
username: ADMIN_BASE,
|
||||
firstName: ADMIN_BASE,
|
||||
lastName: ADMIN_BASE,
|
||||
password: portalAdminInitialPassword,
|
||||
email: portalAdminInitialEmail
|
||||
}
|
||||
const userId = await User.create(adminUser)
|
||||
const assignAdminRole = db.query(
|
||||
`INSERT INTO user_role (user_id, role_name)
|
||||
VALUES
|
||||
($1, 'portal admin'),
|
||||
($1, 'dictionaries admin'),
|
||||
($1, 'consultancy admin'),
|
||||
($1, 'consultant')`,
|
||||
[userId]
|
||||
)
|
||||
const activateAdminUser = db.query(
|
||||
`UPDATE "user" SET status = 'active', time_activated = time_registered WHERE username = $1`,
|
||||
[adminUser.username]
|
||||
)
|
||||
await Promise.all([assignAdminRole, activateAdminUser])
|
||||
return `Successfully created portal admin (username: ${adminUser.username}, password: ${adminUser.password})`
|
||||
}
|
||||
|
||||
exports.adminUser = async () => {
|
||||
// Generate portal admin user in empty DB.
|
||||
// TODO Replace with a more robust solution for production.
|
||||
try {
|
||||
const message = await createPortalAdmin()
|
||||
debug(message)
|
||||
} catch (error) {
|
||||
debug('Portal admin not seeded.')
|
||||
debug(error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,3 +24,7 @@ exports.DATA_FILES_PATH = DATA_FILES_PATH
|
||||
exports.TEMP_EXPORT_PATH = `${DATA_FILES_PATH}/export_temp`
|
||||
|
||||
exports.MAX_EXTRACTIONS_PER_USER = 5
|
||||
|
||||
exports.ACTIVATION_TOKEN_VALID_DAYS = 7
|
||||
|
||||
exports.CHANGE_EMAIL_TOKEN_VALID_DAYS = 7
|
||||
|
||||
@@ -11,6 +11,25 @@ exports.listComments = async (req, res) => {
|
||||
filters.ctxId = null
|
||||
}
|
||||
|
||||
if (filters.ctxType === 'entry_dict_int') {
|
||||
const { dictionary_id: dictionaryId } = await Entry.fetch(filters.ctxId)
|
||||
const isEditor = req.user.hasAnyDictionaryRole(dictionaryId)
|
||||
const isPortalAdmin = req.user.hasRole('portal admin')
|
||||
const isDictionariesAdmin = req.user.hasRole('dictionaries admin')
|
||||
if (!(isEditor || isPortalAdmin || isDictionariesAdmin)) {
|
||||
return res.status(400).end()
|
||||
}
|
||||
} else if (filters.ctxType === 'entry_consult_int') {
|
||||
const isConsultantForEntry = req.user.isEditorOfConsultancyEntry(
|
||||
filters.ctxId
|
||||
)
|
||||
const isPortalAdmin = req.user.hasRole('portal admin')
|
||||
const isConsultancyAdmin = req.user.hasRole('consultancy admin')
|
||||
if (!(isConsultantForEntry || isPortalAdmin || isConsultancyAdmin)) {
|
||||
return res.status(400).end()
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
pages_total: numberOfAllPages,
|
||||
comments,
|
||||
@@ -38,20 +57,55 @@ exports.createComment = async (req, res) => {
|
||||
res.send({ comments, pagesTotal })
|
||||
}
|
||||
|
||||
exports.seedComments = async (req, res) => {
|
||||
const { commentCount } = req.params
|
||||
await Comment.seed(commentCount)
|
||||
res.send(`${commentCount} new comments generated`)
|
||||
}
|
||||
|
||||
exports.clearComments = async (req, res) => {
|
||||
await Comment.clear()
|
||||
res.send('All comments cleared')
|
||||
}
|
||||
|
||||
exports.updateStatus = async (req, res) => {
|
||||
const commentId = req.body.params.id
|
||||
const commentStatus = req.body.params.status
|
||||
|
||||
const { ctxType, ctxId } = await Comment.fetchContextById(commentId)
|
||||
|
||||
let canUpdateStatus = false
|
||||
switch (ctxType) {
|
||||
case 'portal':
|
||||
if (req.user.hasRole('portal admin')) canUpdateStatus = true
|
||||
break
|
||||
|
||||
case 'dictionary':
|
||||
if (
|
||||
req.user.hasRole('portal admin') ||
|
||||
req.user.hasRole('dictionaries admin')
|
||||
) {
|
||||
canUpdateStatus = true
|
||||
}
|
||||
break
|
||||
|
||||
case 'consultancy':
|
||||
if (
|
||||
req.user.hasRole('portal admin') ||
|
||||
req.user.hasRole('consultancy admin')
|
||||
) {
|
||||
canUpdateStatus = true
|
||||
}
|
||||
break
|
||||
|
||||
case 'entry_dict_ext': {
|
||||
const { dictionary_id: dictionaryId } = await Entry.fetch(ctxId)
|
||||
|
||||
if (
|
||||
req.user.hasRole('portal admin') ||
|
||||
req.user.hasRole('dictionaries admin') ||
|
||||
req.user.hasDictionaryRole(dictionaryId, 'administration')
|
||||
) {
|
||||
canUpdateStatus = true
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
default:
|
||||
throw Error('Invalid context type')
|
||||
}
|
||||
|
||||
if (!canUpdateStatus) return res.status(400).end()
|
||||
|
||||
await Comment.updateStatus(commentId, commentStatus)
|
||||
res.send('Visibility changed')
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
const user = require('../../../middleware/user')
|
||||
const ConsultancyEntry = require('../../../models/consultancy-entry')
|
||||
const Domain = require('../../../models/domain')
|
||||
const User = require('../../../models/user')
|
||||
@@ -16,20 +17,6 @@ const generateQuery = require('../../../models/helpers/search/generate-query')
|
||||
|
||||
const consultancy = {}
|
||||
|
||||
consultancy.listEntries = async (req, res) => {
|
||||
const consultancyEntryList = await ConsultancyEntry.fetchAll()
|
||||
const data = {}
|
||||
data.consEntryList = consultancyEntryList
|
||||
res.send(data)
|
||||
}
|
||||
|
||||
consultancy.listNewEntries = async (req, res) => {
|
||||
const consultancyNewEntryList = await ConsultancyEntry.fetchAllNew()
|
||||
const data = {}
|
||||
data.consultancyNewEntryList = consultancyNewEntryList
|
||||
res.send(data)
|
||||
}
|
||||
|
||||
consultancy.sendPaginationData = async (req, res) => {
|
||||
let requestType = req.query.type
|
||||
const isAdminPage = req.query.isAdmin === 'true'
|
||||
@@ -325,9 +312,12 @@ consultancy.publish = async (req, res) => {
|
||||
const author = await User.fetchUser(entry.authorId)
|
||||
const portalName = await getInstanceSetting(`portal_name_${author.language}`)
|
||||
const renderAsync = promisify(req.app.render.bind(req.app))
|
||||
const emailHtml = await renderAsync('email/consultancy-publish-notify', {
|
||||
portalName
|
||||
})
|
||||
const emailHtml = await renderAsync(
|
||||
`email/consultancy-publish-notify_${author.language}`,
|
||||
{
|
||||
portalName
|
||||
}
|
||||
)
|
||||
await email.send({
|
||||
to: author.email,
|
||||
subject: i18next.t('Objava terminološkega vprašanja', {
|
||||
@@ -342,29 +332,38 @@ consultancy.publish = async (req, res) => {
|
||||
res.send()
|
||||
}
|
||||
|
||||
consultancy.updateQuestion = async (req, res) => {
|
||||
const { id, questionTitle, domain: domainId, question, answer } = req.body
|
||||
consultancy.updateQuestion = [
|
||||
(req, res, next) => {
|
||||
const { id } = req.body
|
||||
|
||||
if (!id) return res.status(400).send({})
|
||||
if (!id) return res.status(400).send({})
|
||||
|
||||
if (questionTitle === '' || question === '' || answer === '') {
|
||||
return res
|
||||
.status(422)
|
||||
.send(req.t('Polja naslov, vprašanje in mnenje so obvezna!'))
|
||||
req.entryId = id
|
||||
next()
|
||||
},
|
||||
user.canConsultEntry,
|
||||
async (req, res) => {
|
||||
const { id, questionTitle, domain: domainId, question, answer } = req.body
|
||||
|
||||
if (questionTitle === '' || question === '' || answer === '') {
|
||||
return res
|
||||
.status(422)
|
||||
.send(req.t('Polja naslov, vprašanje in mnenje so obvezna!'))
|
||||
}
|
||||
|
||||
const entry = await ConsultancyEntry.fetchById(id)
|
||||
entry.domainPrimaryId = domainId > 0 ? domainId : null
|
||||
entry.question = question
|
||||
entry.answer = answer // helper.removeHtmlTags(answer).trim()
|
||||
entry.title = questionTitle
|
||||
|
||||
// TODO Luka: Miha, update only fields that were updated.
|
||||
await ConsultancyEntry.updateQuestion(entry)
|
||||
await ConsultancyEntry.indexIntoSearchEngine(id, true)
|
||||
|
||||
res.send({})
|
||||
}
|
||||
|
||||
const entry = await ConsultancyEntry.fetchById(id)
|
||||
entry.domainPrimaryId = domainId > 0 ? domainId : null
|
||||
entry.question = question
|
||||
entry.answer = answer // helper.removeHtmlTags(answer).trim()
|
||||
entry.title = questionTitle
|
||||
|
||||
// TODO Luka: Miha, update only fields that were updated.
|
||||
await ConsultancyEntry.updateQuestion(entry)
|
||||
await ConsultancyEntry.indexIntoSearchEngine(id, true)
|
||||
|
||||
res.send({})
|
||||
}
|
||||
]
|
||||
|
||||
consultancy.insertNonModerator = async (req, res) => {
|
||||
const questionId = req.body.question_id
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
const DemoPaginacija = require('../../../models/demo-paginacija')
|
||||
const { DEFAULT_HITS_PER_PAGE } = require('../../../config/settings')
|
||||
|
||||
exports.list = async (req, res) => {
|
||||
const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
|
||||
|
||||
const page = +req.query.p > 0 ? +req.query.p : 1
|
||||
|
||||
const { pages_total: numberOfAllPages, results } = await DemoPaginacija.fetch(
|
||||
resultsPerPage,
|
||||
page
|
||||
)
|
||||
|
||||
res.send({ page, numberOfAllPages, results })
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
const { rm } = require('fs/promises')
|
||||
const user = require('../../../middleware/user')
|
||||
const Dictionary = require('../../../models/dictionary')
|
||||
const Entry = require('../../../models/entry')
|
||||
const Extraction = require('../../../models/extraction')
|
||||
@@ -69,7 +70,11 @@ dictionary.deleteEntry = async (req, res) => {
|
||||
await Promise.all([
|
||||
Dictionary.updateMetadataAfterModifyingEntries(dictionaryId),
|
||||
deleteEntryFromIndex(entryId, true),
|
||||
minEntriesRequirementCheckAndAct.onDelete(dictionaryId, req.app)
|
||||
minEntriesRequirementCheckAndAct.onDelete(
|
||||
dictionaryId,
|
||||
req.app,
|
||||
req.determinedLanguage
|
||||
)
|
||||
])
|
||||
|
||||
res.end()
|
||||
@@ -84,7 +89,11 @@ dictionary.deleteAllEntries = async (req, res) => {
|
||||
await Promise.all([
|
||||
Dictionary.updateMetadataAfterModifyingEntries(dictionaryId),
|
||||
deleteDictionaryEntriesFromIndex(dictionaryId),
|
||||
minEntriesRequirementCheckAndAct.onDelete(dictionaryId, req.app)
|
||||
minEntriesRequirementCheckAndAct.onDelete(
|
||||
dictionaryId,
|
||||
req.app,
|
||||
req.determinedLanguage
|
||||
)
|
||||
])
|
||||
|
||||
res.end()
|
||||
@@ -124,12 +133,19 @@ dictionary.delete = async (req, res) => {
|
||||
res.end()
|
||||
}
|
||||
|
||||
dictionary.updateDomainLabels = async (req, res) => {
|
||||
const { dictionaryId, payload } = req.body.params
|
||||
dictionary.updateDomainLabels = [
|
||||
(req, res, next) => {
|
||||
req.dictionaryId = req.body.params.dictionaryId
|
||||
next()
|
||||
},
|
||||
user.canAdministrateDictionary,
|
||||
async (req, res) => {
|
||||
const { dictionaryId, payload } = req.body.params
|
||||
|
||||
await Dictionary.updateDomainLabel(dictionaryId, payload)
|
||||
res.end()
|
||||
}
|
||||
await Dictionary.updateDomainLabel(dictionaryId, payload)
|
||||
res.end()
|
||||
}
|
||||
]
|
||||
|
||||
dictionary.renovateSecondaryDomains = async (req, res) => {
|
||||
const data = req.body.params.payload
|
||||
@@ -152,7 +168,11 @@ dictionary.listDictionaries = async (req, res) => {
|
||||
const page = +req.query.p > 0 ? +req.query.p : 1
|
||||
|
||||
const { pages_total: numberOfAllPages, results } =
|
||||
await Dictionary.fetchAllAdminDictionaries(resultsPerPage, page)
|
||||
await Dictionary.fetchAllAdminDictionaries(
|
||||
req.determinedLanguage,
|
||||
resultsPerPage,
|
||||
page
|
||||
)
|
||||
|
||||
res.send({ page, numberOfAllPages, results })
|
||||
}
|
||||
@@ -222,15 +242,6 @@ dictionary.listSecondaryDomainData = async (req, res) => {
|
||||
})
|
||||
}
|
||||
|
||||
dictionary.listSecondaryDomains = async (req, res) => {
|
||||
const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
|
||||
const page = +req.query.p > 0 ? +req.query.p : 1
|
||||
const { pages_total: numberOfAllPages, results } =
|
||||
await Dictionary.fetchAllSecondaryDomains(resultsPerPage, page)
|
||||
|
||||
res.send({ page, numberOfAllPages, results })
|
||||
}
|
||||
|
||||
dictionary.showImportFromFileForm = async (req, res) => {
|
||||
const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
|
||||
const { dictionaryId } = req.params
|
||||
@@ -252,7 +263,7 @@ dictionary.showExportToFileForm = async (req, res) => {
|
||||
}
|
||||
|
||||
dictionary.importFromExtraction = async (req, res) => {
|
||||
const { id: dictionaryId, extractionId } = req.params
|
||||
const { dictionaryId, extractionId } = req.params
|
||||
const { from, to } = req.body
|
||||
const fromIndex = +from > 1 ? Math.floor(from) - 1 : 0
|
||||
const toIndex = Number.isInteger(+(to === '' ? undefined : to))
|
||||
@@ -279,7 +290,7 @@ dictionary.importFromExtraction = async (req, res) => {
|
||||
}
|
||||
|
||||
dictionary.exportBegin = async (req, res) => {
|
||||
const dictionaryId = req.params.id
|
||||
const { dictionaryId } = req.params
|
||||
const exportParams = {
|
||||
isValidFilter:
|
||||
req.body.isValidFilter === 'on' ? undefined : req.body.isValidFilter,
|
||||
|
||||
@@ -166,7 +166,7 @@ extraction.ossSearch = [
|
||||
|
||||
extraction.ossConfirmParams = async (req, res) => {
|
||||
const { id: extractionId } = req.params
|
||||
const { ossParams } = await Extraction.fetch(extractionId)
|
||||
const { ossParams } = req.extractionData
|
||||
if (ossParams.status !== 'valid') throw Error('OSS params not valid')
|
||||
await Extraction.updateOssParams(extractionId, {
|
||||
params: ossParams.params,
|
||||
@@ -177,7 +177,7 @@ extraction.ossConfirmParams = async (req, res) => {
|
||||
|
||||
extraction.begin = async (req, res) => {
|
||||
const extractionId = req.params.id
|
||||
const extraction = await Extraction.fetch(extractionId)
|
||||
const extraction = req.extractionData
|
||||
const canBegin = await checkIfcanBegin(extraction)
|
||||
if (!canBegin) throw Error('Extraction does not qualify to be ran')
|
||||
|
||||
@@ -268,11 +268,6 @@ extraction.termCandidatesExport = async (req, res) => {
|
||||
}
|
||||
}
|
||||
|
||||
extraction.listFinishedForUser = async (req, res) => {
|
||||
const extractions = await Extraction.fetchFinishedForUser(req.user.id)
|
||||
res.send(extractions)
|
||||
}
|
||||
|
||||
extraction.listTermCandidates = async (req, res) => {
|
||||
const extractionId = req.params.id
|
||||
const termCandidatesJson = await Extraction.fetchTermCandidatesJson(
|
||||
|
||||
@@ -178,7 +178,8 @@ exports.listFilteredDictionaries = async (req, res) => {
|
||||
hitsPerPage,
|
||||
page,
|
||||
orderAttribute,
|
||||
orderIndex
|
||||
orderIndex,
|
||||
req.determinedLanguage
|
||||
)
|
||||
|
||||
dictionaries = dictionaries.map(e => {
|
||||
@@ -246,6 +247,9 @@ exports.showModalFilterResults = async (req, res) => {
|
||||
|
||||
const aggregationRaw = await searchEntryIndex(aggregateQuery)
|
||||
|
||||
const aggregation = await prepareAggregation(aggregationRaw)
|
||||
const aggregation = await prepareAggregation(
|
||||
aggregationRaw,
|
||||
req.determinedLanguage
|
||||
)
|
||||
res.send(prepareSeachFilterData(aggregation, filters))
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
const debug = require('debug')('termPortal:controllers/api/v1/system')
|
||||
const Eurotermbank = require('../../../models/system/eurotermbank')
|
||||
// const debug = require('debug')('termPortal:controllers/api/v1/system')
|
||||
|
||||
exports.handleCspReports = (req, res) => {
|
||||
debug(req.body)
|
||||
res.sendStatus(200)
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(req.body)
|
||||
res.end()
|
||||
}
|
||||
|
||||
exports.syncWithEurotermbank = async (req, res) => {
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
const { randomBytes } = require('crypto')
|
||||
const { promisify } = require('util')
|
||||
const RandomBytesAsync = promisify(randomBytes)
|
||||
const User = require('../../../models/user')
|
||||
const { DEFAULT_HITS_PER_PAGE } = require('../../../config/settings')
|
||||
const email = require('../../../models/email')
|
||||
const { logout: logoutUser } = require('../../../middleware/user')
|
||||
const { origin } = require('../../../config/keys')
|
||||
const {
|
||||
DEFAULT_HITS_PER_PAGE,
|
||||
CHANGE_EMAIL_TOKEN_VALID_DAYS
|
||||
} = require('../../../config/settings')
|
||||
|
||||
const users = {}
|
||||
users.listUsers = async (req, res) => {
|
||||
@@ -23,13 +32,103 @@ users.updateHitsPerPage = async (req, res) => {
|
||||
res.status(200).send()
|
||||
}
|
||||
|
||||
users.updateFristNameAndSurname = async (req, res) => {
|
||||
const firstname = req.body.name
|
||||
const surname = req.body.surname
|
||||
users.updateBasicData = async (req, res) => {
|
||||
const { firstName, lastName, email: newEmail } = req.body
|
||||
|
||||
await User.updateFirstNameAndLastName(req.user.userName, firstname, surname)
|
||||
// TODO Validation (valid email format, ...).
|
||||
|
||||
res.status(200).send()
|
||||
const oldEmail = await User.updateFirstNameAndLastName(
|
||||
req.user.id,
|
||||
firstName,
|
||||
lastName
|
||||
)
|
||||
|
||||
if (newEmail === oldEmail) return res.send()
|
||||
|
||||
if (await User.isEmailAlreadyTaken(newEmail)) {
|
||||
req.flash('info', req.t('Elektronski naslov uporablja že drug uporabnik.'))
|
||||
return res.send()
|
||||
}
|
||||
|
||||
const changeEmailToken = (await RandomBytesAsync(32)).toString('hex')
|
||||
await User.saveChangeEmailToken(req.user.id, changeEmailToken, newEmail)
|
||||
let changeEmailLink = new URL('/sprememba-elektronskega-naslova', origin)
|
||||
changeEmailLink.searchParams.set('token', changeEmailToken)
|
||||
changeEmailLink = changeEmailLink.href
|
||||
const renderAsync = promisify(req.app.render.bind(req.app))
|
||||
const emailHtml = await renderAsync(
|
||||
`email/user-change-email-token_${req.language}`,
|
||||
{
|
||||
username: req.user.userName,
|
||||
changeEmailLink
|
||||
}
|
||||
)
|
||||
await email.send({
|
||||
to: newEmail,
|
||||
subject: req.t('Sprememba elektronskega naslova'),
|
||||
html: emailHtml
|
||||
})
|
||||
|
||||
const message =
|
||||
req.t(
|
||||
'Na vaš elektronski naslov smo vam poslali sporočilo s povezavo, s katero boste potrdili menjavo elektronskega naslova. Povezava za potrditev je veljavna '
|
||||
) +
|
||||
`${CHANGE_EMAIL_TOKEN_VALID_DAYS} ` +
|
||||
req.t('dni.')
|
||||
|
||||
req.flash('info', message)
|
||||
res.send()
|
||||
}
|
||||
|
||||
users.updatePassword = async (req, res) => {
|
||||
const { passwordOld, passwordNew, passwordNewRepeat } = req.body
|
||||
|
||||
// TODO Validation (mirror front end validation, ...).
|
||||
if (passwordNew !== passwordNewRepeat) {
|
||||
const err = Error(req.t('Gesli se ne ujemata'))
|
||||
err.status = 403
|
||||
err.displayInProd = true
|
||||
|
||||
throw err
|
||||
}
|
||||
|
||||
await User.changePassword(req.user.id, passwordOld, passwordNew, req.t)
|
||||
|
||||
// TODO Invalidate or log out all session for this user. More details in deleteCurrent method TODO.
|
||||
|
||||
const renderAsync = promisify(req.app.render.bind(req.app))
|
||||
const emailHtml = await renderAsync(
|
||||
`email/user-change-password_${req.language}`,
|
||||
{
|
||||
username: req.user.userName
|
||||
}
|
||||
)
|
||||
await email.send({
|
||||
to: req.user.email,
|
||||
subject: req.t('Sprememba gesla'),
|
||||
html: emailHtml
|
||||
})
|
||||
|
||||
req.flash('info', 'Geslo je bilo spremenjeno.')
|
||||
res.send()
|
||||
}
|
||||
|
||||
users.deleteCurrent = [
|
||||
async (req, res, next) => {
|
||||
await User.closeAccount(req.user.id)
|
||||
next()
|
||||
},
|
||||
logoutUser,
|
||||
(req, res) => {
|
||||
// TODO Invalidate or log out all session for this user. Current workaround is in passport.deserializeUser.
|
||||
// You can probably do it in 1 of 3 ways:
|
||||
// 1. Brute force; loop through all sessions (using session store's all or ids methods),
|
||||
// look up their values and remote the ones with user's id
|
||||
// 2. Include user Id as part of session key; something similar to https://github.com/tj/connect-redis/issues/210#issuecomment-1336545115
|
||||
// 3. Create some kind of inverse index, mapping user id to his/hers sessions (also mentioned in issue linked above)
|
||||
req.flash('info', req.t('Vaš uporabniški račun je bil uspešno izbrisan.'))
|
||||
res.send()
|
||||
}
|
||||
]
|
||||
|
||||
module.exports = users
|
||||
|
||||
@@ -46,11 +46,15 @@ consultancy.search = async (req, res) => {
|
||||
consultancy.specificQuestion = async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
// TODO validation: is id of proper format and does a question with it actually exist.
|
||||
|
||||
// TODO i18n TIME FORMAT
|
||||
const entry = await ConsultancyEntry.fetchByIdWithFormattedTime(id)
|
||||
// const author = await User.fetchUser(entry.authorId)
|
||||
|
||||
const allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains()
|
||||
const allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains(
|
||||
req.determinedLanguage
|
||||
)
|
||||
|
||||
entry.answerAuthors = entry.answerAuthors.filter(author => author !== '')
|
||||
|
||||
@@ -86,7 +90,9 @@ consultancy.specificQuestion = async (req, res) => {
|
||||
}
|
||||
|
||||
consultancy.new = async (req, res) => {
|
||||
const allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains()
|
||||
const allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains(
|
||||
req.determinedLanguage
|
||||
)
|
||||
|
||||
res.locals.isOwnConsultancyEnabled =
|
||||
(await getInstanceSetting('consultancy_type')) === 'own'
|
||||
@@ -110,12 +116,9 @@ consultancyAdmin.new = async (req, res) => {
|
||||
}
|
||||
|
||||
consultancyAdmin.users = async (req, res) => {
|
||||
const allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains()
|
||||
|
||||
const users = await User.fetchConsultants()
|
||||
|
||||
res.render('pages/consultancy/admin/users', {
|
||||
allPrimaryDomains,
|
||||
users,
|
||||
title: req.t('Svetovalci')
|
||||
})
|
||||
@@ -175,11 +178,7 @@ consultancyAdmin.inProgress = async (req, res) => {
|
||||
}
|
||||
|
||||
consultancyAdmin.statistics = async (req, res) => {
|
||||
const allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains()
|
||||
|
||||
res.render('pages/consultancy/admin/statistics', {
|
||||
allPrimaryDomains
|
||||
})
|
||||
res.render('pages/consultancy/admin/statistics')
|
||||
}
|
||||
|
||||
consultancyAdmin.edit = async (req, res) => {
|
||||
@@ -189,18 +188,11 @@ consultancyAdmin.edit = async (req, res) => {
|
||||
sentFrom[key] = true
|
||||
|
||||
const moderator = await ConsultancyEntry.getModerator(id)
|
||||
const editors = await ConsultancyEntry.getEditors(id)
|
||||
if (
|
||||
req.user.hasRole('consultancy admin') ||
|
||||
req.user.hasRole('portal admin')
|
||||
) {
|
||||
console.log('Editor guard omitted due to being administrator')
|
||||
} else if (editors.filter(editors => editors.id === req.user.id) < 1) {
|
||||
return res.send('You do not have permsisions to edit this answer')
|
||||
}
|
||||
// TODO i18n TIME FORMAT
|
||||
const entry = await ConsultancyEntry.fetchByIdWithFormattedTime(id)
|
||||
const allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains()
|
||||
const allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains(
|
||||
req.determinedLanguage
|
||||
)
|
||||
const author = await User.fetchUser(entry.authorId)
|
||||
|
||||
const isPublished = entry.status === 'published'
|
||||
@@ -318,7 +310,9 @@ async function consultancyRequest(
|
||||
) {
|
||||
const searchString = req.query.q?.trim() ?? ''
|
||||
|
||||
let allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains()
|
||||
let allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains(
|
||||
req.determinedLanguage
|
||||
)
|
||||
|
||||
/// filter selected domains for the prompt ///
|
||||
// Not duplicates of this code arise...
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
const DemoPaginacija = require('../models/demo-paginacija')
|
||||
const { DEFAULT_HITS_PER_PAGE } = require('../config/settings')
|
||||
|
||||
const demoPaginacija = {}
|
||||
|
||||
demoPaginacija.izrišiStran = async (req, res) => {
|
||||
const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
|
||||
|
||||
const { pages_total: numberOfAllPages, results } = await DemoPaginacija.fetch(
|
||||
resultsPerPage,
|
||||
1
|
||||
)
|
||||
|
||||
res.render('pages/demo-paginacija', { numberOfAllPages, results })
|
||||
}
|
||||
|
||||
module.exports = demoPaginacija
|
||||
@@ -2,6 +2,7 @@ const { unlink } = require('fs/promises')
|
||||
const { promisify } = require('util')
|
||||
const multer = require('multer')
|
||||
const debug = require('debug')('termPortal:controllers/dictionary')
|
||||
const user = require('../middleware/user')
|
||||
const Dictionary = require('../models/dictionary')
|
||||
const Entry = require('../models/entry')
|
||||
const User = require('../models/user')
|
||||
@@ -19,6 +20,7 @@ const {
|
||||
const SFDSuggestionImporter = require('./helpers/search-filter-data-suggestion-importer')
|
||||
const Extraction = require('../models/extraction')
|
||||
const { isGeneratorFunction } = require('util/types')
|
||||
const { capitalize } = require('../utils')
|
||||
|
||||
const importFileBodyParser = multer({
|
||||
dest: `${DATA_FILES_PATH}/dict_import_temp`,
|
||||
@@ -38,7 +40,10 @@ const dictionary = {}
|
||||
dictionary.list = async (req, res) => {
|
||||
let dictionaries
|
||||
if (req.isAuthenticated()) {
|
||||
dictionaries = await Dictionary.fetchAllByUser(req.user.id)
|
||||
dictionaries = await Dictionary.fetchAllByUser(
|
||||
req.user.id,
|
||||
req.determinedLanguage
|
||||
)
|
||||
}
|
||||
res.render('pages/dictionaries/list', {
|
||||
title: req.t('Seznam slovarjev'),
|
||||
@@ -47,13 +52,11 @@ dictionary.list = async (req, res) => {
|
||||
}
|
||||
|
||||
dictionary.new = async (req, res) => {
|
||||
// TODO Once english language is implemented, gather selected language (sl/en) from request ~ (cookies?)
|
||||
const language = 'name_sl'
|
||||
const [allPrimaryDomains, allSecondaryDomains, allLanguages] =
|
||||
await Promise.all([
|
||||
Dictionary.fetchAllPrimaryDomains(),
|
||||
Dictionary.fetchAllPrimaryDomains(req.determinedLanguage),
|
||||
Dictionary.fetchAllApprovedSecondaryDomains(),
|
||||
Dictionary.fetchAllLanguages(language, true)
|
||||
Dictionary.fetchAllLanguages(req.determinedLanguage, true)
|
||||
])
|
||||
|
||||
res.render('pages/dictionaries/new', {
|
||||
@@ -66,7 +69,7 @@ dictionary.new = async (req, res) => {
|
||||
|
||||
dictionary.create = async (req, res) => {
|
||||
await Dictionary.create(req.body, req.user.id)
|
||||
res.redirect('/slovarji/moji')
|
||||
res.redirect(303, '/slovarji/moji')
|
||||
}
|
||||
|
||||
dictionary.editDescription = async (req, res) => {
|
||||
@@ -77,7 +80,7 @@ dictionary.editDescription = async (req, res) => {
|
||||
dictionary,
|
||||
associatedSecondaryDomains
|
||||
] = await Promise.all([
|
||||
Dictionary.fetchAllPrimaryDomains(),
|
||||
Dictionary.fetchAllPrimaryDomains(req.determinedLanguage),
|
||||
Dictionary.fetchAllApprovedSecondaryDomains(),
|
||||
Dictionary.fetchEditDescription(dictionaryId),
|
||||
Dictionary.fetchSecondaryDomains(dictionaryId)
|
||||
@@ -103,14 +106,14 @@ dictionary.updateDescription = async (req, res) => {
|
||||
Dictionary.updateSecondaryDomains(dictionaryId, body)
|
||||
])
|
||||
|
||||
res.redirect('back')
|
||||
res.redirect(303, 'back')
|
||||
}
|
||||
|
||||
dictionary.editUsers = async (req, res) => {
|
||||
const dictionaryId = req.params.dictionaryId
|
||||
const [dictionary, userRights, entriesCount, minEntries, publishApproval] =
|
||||
await Promise.all([
|
||||
Dictionary.fetchEditUsers(dictionaryId),
|
||||
Dictionary.fetchEditUsers(dictionaryId, req.determinedLanguage),
|
||||
User.fetchAllWithDictionaryRights(dictionaryId),
|
||||
Dictionary.countPublishedEntries(dictionaryId),
|
||||
getInstanceSetting('min_entries_per_dictionary'),
|
||||
@@ -144,9 +147,9 @@ dictionary.updateUsers = async (req, res) => {
|
||||
|
||||
const newDictStatus = await determineNewStatus(isPublished)
|
||||
|
||||
// TODO I18n - nameSl
|
||||
const { nameSl, status: oldDictStatus } = await Dictionary.fetchEditUsers(
|
||||
dictionaryId
|
||||
const { name, status: oldDictStatus } = await Dictionary.fetchEditUsers(
|
||||
dictionaryId,
|
||||
req.determinedLanguage
|
||||
)
|
||||
|
||||
await Promise.all([
|
||||
@@ -165,23 +168,20 @@ dictionary.updateUsers = async (req, res) => {
|
||||
dictionaryId,
|
||||
isPublished,
|
||||
oldDictStatus,
|
||||
nameSl,
|
||||
name,
|
||||
req.app,
|
||||
req.user
|
||||
)
|
||||
|
||||
res.redirect('back')
|
||||
res.redirect(303, 'back')
|
||||
}
|
||||
|
||||
dictionary.editStructure = async (req, res) => {
|
||||
// TODO Once english language is implemented, gather selected language (sl/en) from request ~ (cookies?)
|
||||
// TODO I18n - nameSl
|
||||
const language = 'name_sl'
|
||||
const { dictionaryId } = req.params
|
||||
const [dictionary, associatedLanguages, allLanguages] = await Promise.all([
|
||||
Dictionary.fetchEditStructure(dictionaryId),
|
||||
Dictionary.fetchLanguages(dictionaryId),
|
||||
Dictionary.fetchAllLanguages(language, true)
|
||||
Dictionary.fetchLanguages(dictionaryId, req.determinedLanguage),
|
||||
Dictionary.fetchAllLanguages(req.determinedLanguage, true)
|
||||
])
|
||||
|
||||
let viewPath
|
||||
@@ -211,12 +211,15 @@ dictionary.updateStructure = async (req, res) => {
|
||||
Dictionary.deleteLanguages(dictionaryId),
|
||||
Dictionary.updateLanguages(dictionaryId, body)
|
||||
])
|
||||
res.redirect('back')
|
||||
res.redirect(303, 'back')
|
||||
}
|
||||
|
||||
dictionary.editAdvanced = async (req, res) => {
|
||||
const { dictionaryId } = req.params
|
||||
const dictionaryName = await Dictionary.fetchName(dictionaryId)
|
||||
const dictionaryName = await Dictionary.fetchName(
|
||||
dictionaryId,
|
||||
req.determinedLanguage
|
||||
)
|
||||
let viewPath
|
||||
switch (req.baseUrl) {
|
||||
case '/slovarji':
|
||||
@@ -243,7 +246,7 @@ dictionary.comments = async (req, res) => {
|
||||
const [{ comments, pages_total: numberOfAllPages }, dictionaryName] =
|
||||
await Promise.all([
|
||||
Comment.list(filters, req.user, resultsPerPage, 1),
|
||||
Dictionary.fetchName(dictionaryId)
|
||||
Dictionary.fetchName(dictionaryId, req.determinedLanguage)
|
||||
])
|
||||
|
||||
switch (req.baseUrl) {
|
||||
@@ -269,7 +272,7 @@ dictionary.showImportFromFileForm = async (req, res) => {
|
||||
const [{ pages_total: numberOfAllPages, results }, dictionaryName] =
|
||||
await Promise.all([
|
||||
Dictionary.fetchAllImports(dictionaryId, resultsPerPage, 1),
|
||||
Dictionary.fetchName(dictionaryId)
|
||||
Dictionary.fetchName(dictionaryId, req.determinedLanguage)
|
||||
])
|
||||
let viewPath
|
||||
switch (req.baseUrl) {
|
||||
@@ -293,7 +296,11 @@ dictionary.listAdminDictionaries = async (req, res) => {
|
||||
const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
|
||||
|
||||
const { pages_total: numberOfAllPages, results } =
|
||||
await Dictionary.fetchAllAdminDictionaries(resultsPerPage, 1)
|
||||
await Dictionary.fetchAllAdminDictionaries(
|
||||
req.determinedLanguage,
|
||||
resultsPerPage,
|
||||
1
|
||||
)
|
||||
|
||||
res.render('pages/admin/dictionaries-list', {
|
||||
title: req.t('Seznam slovarjev'),
|
||||
@@ -311,7 +318,7 @@ dictionary.adminEditDescription = async (req, res) => {
|
||||
associatedSecondaryDomains,
|
||||
status
|
||||
] = await Promise.all([
|
||||
Dictionary.fetchAllPrimaryDomains(),
|
||||
Dictionary.fetchAllPrimaryDomains(req.determinedLanguage),
|
||||
Dictionary.fetchAllApprovedSecondaryDomains(),
|
||||
Dictionary.fetchEditDescription(dictionaryId),
|
||||
Dictionary.fetchSecondaryDomains(dictionaryId),
|
||||
@@ -357,15 +364,19 @@ dictionary.updateAdminDescription = async (req, res) => {
|
||||
newDictStatus,
|
||||
oldDictStatus,
|
||||
req.app,
|
||||
req.determinedLanguage,
|
||||
req.user
|
||||
)
|
||||
|
||||
res.redirect('back')
|
||||
res.redirect(303, 'back')
|
||||
}
|
||||
|
||||
dictionary.showImportFromExtractionForm = async (req, res) => {
|
||||
const { dictionaryId } = req.params
|
||||
const dictionaryName = await Dictionary.fetchName(dictionaryId)
|
||||
const dictionaryName = await Dictionary.fetchName(
|
||||
dictionaryId,
|
||||
req.determinedLanguage
|
||||
)
|
||||
const extractions = await Extraction.fetchFinishedForUser(req.user.id)
|
||||
let viewPath, title
|
||||
switch (req.baseUrl) {
|
||||
@@ -391,7 +402,7 @@ dictionary.showExportToFileForm = async (req, res) => {
|
||||
const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
|
||||
const [dictionaryName, { pages_total: numberOfAllPages, results }] =
|
||||
await Promise.all([
|
||||
Dictionary.fetchName(dictionaryId),
|
||||
Dictionary.fetchName(dictionaryId, req.determinedLanguage),
|
||||
Dictionary.fetchExports(dictionaryId, resultsPerPage, 1)
|
||||
])
|
||||
let viewPath
|
||||
@@ -418,7 +429,7 @@ dictionary.editDomainLabels = async (req, res) => {
|
||||
const [{ pages_total: numberOfAllPages, results }, dictionaryName] =
|
||||
await Promise.all([
|
||||
Dictionary.fetchPaginationDomainLabels(dictionaryId, resultsPerPage, 1),
|
||||
Dictionary.fetchName(dictionaryId)
|
||||
Dictionary.fetchName(dictionaryId, req.determinedLanguage)
|
||||
])
|
||||
|
||||
let viewPath
|
||||
@@ -445,16 +456,14 @@ dictionary.showContent = async (req, res) => {
|
||||
const [
|
||||
hits,
|
||||
canPublishEntriesInEdit,
|
||||
dictionaryName,
|
||||
structure,
|
||||
languages,
|
||||
entryDomainLabels
|
||||
] = await Promise.all([
|
||||
searchEntryIndex(hitsQuery),
|
||||
getInstanceSetting('can_publish_entries_in_edit'),
|
||||
Dictionary.fetchName(dictionaryId),
|
||||
Dictionary.fetchEditStructure(dictionaryId),
|
||||
Dictionary.fetchLanguages(dictionaryId),
|
||||
Dictionary.fetchLanguages(dictionaryId, req.determinedLanguage),
|
||||
Dictionary.fetchDomainLabels(dictionaryId)
|
||||
])
|
||||
|
||||
@@ -464,7 +473,7 @@ dictionary.showContent = async (req, res) => {
|
||||
title: req.t('Vsebina slovarja'),
|
||||
terms,
|
||||
canPublishEntriesInEdit,
|
||||
dictionaryName,
|
||||
dictionaryName: structure[`name${capitalize(req.determinedLanguage)}`],
|
||||
structure,
|
||||
languages,
|
||||
dictionaryId,
|
||||
@@ -510,7 +519,8 @@ dictionary.dictionaryList = async (req, res) => {
|
||||
hitsPerPage,
|
||||
page,
|
||||
orderAttribute,
|
||||
orderIndex
|
||||
orderIndex,
|
||||
req.determinedLanguage
|
||||
)
|
||||
|
||||
const numberOfAllHits = parseInt(
|
||||
@@ -518,7 +528,9 @@ dictionary.dictionaryList = async (req, res) => {
|
||||
)
|
||||
const numberOfAllPages = Math.ceil(numberOfAllHits / hitsPerPage)
|
||||
|
||||
const allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains()
|
||||
const allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains(
|
||||
req.determinedLanguage
|
||||
)
|
||||
|
||||
/*
|
||||
/// filter selected domains for the prompt ///
|
||||
@@ -567,9 +579,12 @@ dictionary.dictionaryDetails = async (req, res) => {
|
||||
targetLanguages,
|
||||
allDictionaryNames,
|
||||
portals
|
||||
} = await SFDSuggestionImporter.initialize()
|
||||
} = await SFDSuggestionImporter.initialize(req.determinedLanguage)
|
||||
|
||||
const dictionaryData = await Dictionary.fetchDictionaryBasicInfo(dictId)
|
||||
const dictionaryData = await Dictionary.fetchDictionaryBasicInfo(
|
||||
dictId,
|
||||
req.determinedLanguage
|
||||
)
|
||||
|
||||
const filters = { ctxType: 'dictionary', ctxId: dictId }
|
||||
// TODO: integrate numberOfAllPages, commentCount with pug
|
||||
@@ -711,21 +726,34 @@ dictionary.importFromFile = async (req, res) => {
|
||||
}
|
||||
}
|
||||
|
||||
dictionary.exportDownload = async (req, res) => {
|
||||
// TODO Add authentication and authorization.
|
||||
dictionary.exportDownload = [
|
||||
async (req, res, next) => {
|
||||
const { exportId } = req.params
|
||||
const exportDownloadMetadata = await Dictionary.fetchExportDownloadMetadata(
|
||||
exportId
|
||||
)
|
||||
|
||||
const { exportId } = req.params
|
||||
const { exportStatus, dictionaryId, nameString, timeString, fileFormat } =
|
||||
await Dictionary.fetchExportDownloadMetadata(exportId)
|
||||
if (exportStatus !== 'finished') {
|
||||
throw Error("Can't request file for unfinished export")
|
||||
req.dictionaryId = exportDownloadMetadata.dictionaryId
|
||||
req.exportDownloadMetadata = exportDownloadMetadata
|
||||
|
||||
next()
|
||||
},
|
||||
user.isDictionaryEditor,
|
||||
(req, res) => {
|
||||
const { exportId } = req.params
|
||||
const { exportStatus, dictionaryId, nameString, timeString, fileFormat } =
|
||||
req.exportDownloadMetadata
|
||||
|
||||
if (exportStatus !== 'finished') {
|
||||
throw Error("Can't request file for unfinished export")
|
||||
}
|
||||
const exportFilesPath = getExportFilesPath(dictionaryId)
|
||||
const exportFilePath = `${exportFilesPath}/${exportId}`
|
||||
const exportFileName = `${nameString}_${timeString}.${fileFormat}`
|
||||
|
||||
res.download(exportFilePath, exportFileName)
|
||||
}
|
||||
const exportFilesPath = getExportFilesPath(dictionaryId)
|
||||
const exportFilePath = `${exportFilesPath}/${exportId}`
|
||||
const exportFileName = `${nameString}_${timeString}.${fileFormat}`
|
||||
|
||||
res.download(exportFilePath, exportFileName)
|
||||
}
|
||||
]
|
||||
|
||||
function importFileFilter(req, file, cb) {
|
||||
if (file.mimetype !== 'text/xml') {
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
const { mkdir } = require('fs/promises')
|
||||
const {
|
||||
getDocumentsPath,
|
||||
getStopTermsPath
|
||||
} = require('../models/helpers/extraction')
|
||||
const { checkIfcanBegin } = require('./helpers/extraction')
|
||||
const { MAX_EXTRACTIONS_PER_USER } = require('../config/settings')
|
||||
const Extraction = require('../models/extraction')
|
||||
const Dictionary = require('../models/dictionary')
|
||||
const Domain = require('../models/domain')
|
||||
const { intoDbArray } = require('../models/helpers')
|
||||
const { DEFAULT_HITS_PER_PAGE } = require('../config/settings')
|
||||
|
||||
const extraction = {}
|
||||
|
||||
extraction.list = async (req, res) => {
|
||||
let extractions = await Extraction.fetchAllForUser(req.user.id)
|
||||
extractions = await Promise.all(
|
||||
extractions.map(async extraction => {
|
||||
extraction.canBegin = await checkIfcanBegin(extraction)
|
||||
if (extraction.status === 'finished') {
|
||||
extraction.termCandidatesCount =
|
||||
await Extraction.fetchTermCandidatesCount(extraction.id)
|
||||
}
|
||||
return extraction
|
||||
})
|
||||
)
|
||||
res.render('extraction-poc/list', { extractions })
|
||||
}
|
||||
|
||||
extraction.create = async (req, res) => {
|
||||
const extractionCount = await Extraction.countAllForUser(req.user.id)
|
||||
if (extractionCount >= MAX_EXTRACTIONS_PER_USER) {
|
||||
// TODO Tukaj bo treba prikazati tudi obvestilo uporabniku skladno s trenutno metodologijo prikaza obvestil.
|
||||
return res.redirect(303, 'back')
|
||||
}
|
||||
|
||||
const extractionName = `Luščenje ${extractionCount + 1}`
|
||||
const { extractionType } = req.body
|
||||
|
||||
let extractionId
|
||||
if (extractionType === 'own') {
|
||||
extractionId = await Extraction.createOwn(req.user.id, extractionName)
|
||||
|
||||
const documentsPath = getDocumentsPath(extractionId)
|
||||
const stopTermsPath = getStopTermsPath(extractionId)
|
||||
await Promise.all([
|
||||
mkdir(documentsPath, { recursive: true }),
|
||||
mkdir(stopTermsPath, { recursive: true })
|
||||
])
|
||||
} else {
|
||||
extractionId = await Extraction.createOss(req.user.id, extractionName)
|
||||
|
||||
const stopTermsPath = getStopTermsPath(extractionId)
|
||||
await mkdir(stopTermsPath, { recursive: true })
|
||||
}
|
||||
|
||||
// Redirect to extraction edit page.
|
||||
res.redirect(`poc/${extractionId}`)
|
||||
}
|
||||
|
||||
extraction.edit = async (req, res) => {
|
||||
const extractionId = req.params.id
|
||||
const extraction = await Extraction.fetch(extractionId)
|
||||
|
||||
if (extraction.ossParams) {
|
||||
const [allPrimaryDomains, stopTermsFiles] = await Promise.all([
|
||||
Dictionary.fetchAllPrimaryDomains(),
|
||||
Extraction.fetchAllStopTermsFilesStats(extractionId)
|
||||
])
|
||||
const { params } = extraction.ossParams
|
||||
const domainUdk = params?.domainUdk?.[0]
|
||||
if (domainUdk)
|
||||
extraction.domainId = await Domain.fetchIdByUdkCode(domainUdk)
|
||||
extraction.documentType = intoDbArray(params.documentType, 'always')
|
||||
extraction.year = intoDbArray(params.year, 'always')
|
||||
extraction.keywords = intoDbArray(params.keywords, 'always')
|
||||
|
||||
res.render('extraction-poc/edit-oss', {
|
||||
id: extractionId,
|
||||
extraction,
|
||||
allPrimaryDomains,
|
||||
stopTermsFiles
|
||||
})
|
||||
} else {
|
||||
const [extractionDocuments, stopTermsFiles] = await Promise.all([
|
||||
Extraction.fetchAllDocumentsStats(extractionId),
|
||||
Extraction.fetchAllStopTermsFilesStats(extractionId)
|
||||
])
|
||||
res.render('extraction-poc/edit-own', {
|
||||
id: extractionId,
|
||||
extraction,
|
||||
extractionDocuments,
|
||||
stopTermsFiles
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
extraction.updateOwn = async (req, res) => {
|
||||
const extractionId = req.params.id
|
||||
await Extraction.update(extractionId, req.body.name)
|
||||
|
||||
// Reload page.
|
||||
res.redirect(`./${extractionId}`)
|
||||
}
|
||||
|
||||
extraction.docsEdit = async (req, res) => {
|
||||
const extractionId = req.params.id
|
||||
const extractionDocuments = await Extraction.fetchAllDocumentsStats(
|
||||
extractionId
|
||||
)
|
||||
|
||||
res.render('extraction-poc/docs-edit', {
|
||||
id: extractionId,
|
||||
extractionDocuments
|
||||
})
|
||||
}
|
||||
|
||||
extraction.stopTermsEdit = async (req, res) => {
|
||||
const extractionId = req.params.id
|
||||
const stopTermsFiles = await Extraction.fetchAllStopTermsFilesStats(
|
||||
extractionId
|
||||
)
|
||||
|
||||
res.render('extraction-poc/stop-terms-edit', {
|
||||
id: extractionId,
|
||||
stopTermsFiles
|
||||
})
|
||||
}
|
||||
|
||||
extraction.listTermCandidates = async (req, res) => {
|
||||
const extractionId = req.params.id
|
||||
const termCandidatesJson = await Extraction.fetchTermCandidatesJson(
|
||||
extractionId
|
||||
)
|
||||
const termCandidates = JSON.parse(termCandidatesJson).terminoloski_kandidati
|
||||
const hitsPerPage = +req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
|
||||
const numberOfAllPages = Math.ceil(termCandidates.length / hitsPerPage)
|
||||
const firstPageOfTermCandidates = termCandidates.slice(0, hitsPerPage)
|
||||
|
||||
res.render('extraction-poc/term-candidates', {
|
||||
extractionId,
|
||||
termCandidatesJson,
|
||||
firstPageOfTermCandidates,
|
||||
hitsPerPage,
|
||||
numberOfAllPages
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = extraction
|
||||
@@ -63,22 +63,36 @@ extraction.create = async (req, res) => {
|
||||
}
|
||||
|
||||
// Redirect to extraction edit page.
|
||||
res.redirect(`luscenje/${extractionId}`)
|
||||
res.redirect(303, `luscenje/${extractionId}`)
|
||||
}
|
||||
|
||||
extraction.validateOwnership = async (req, res, next) => {
|
||||
const { id: extractionId } = req.params
|
||||
if (!extractionId) return res.redirect(303, '/')
|
||||
|
||||
const extraction = await Extraction.fetch(extractionId)
|
||||
if (extraction.userId !== req.user.id) return res.redirect(303, '/')
|
||||
|
||||
req.extractionData = extraction
|
||||
next()
|
||||
}
|
||||
|
||||
extraction.edit = async (req, res) => {
|
||||
const extractionId = req.params.id
|
||||
const extraction = await Extraction.fetch(extractionId)
|
||||
const extraction = req.extractionData
|
||||
|
||||
if (extraction.ossParams) {
|
||||
const [allPrimaryDomains, stopTermsFiles] = await Promise.all([
|
||||
Dictionary.fetchAllPrimaryDomains(),
|
||||
Extraction.fetchAllStopTermsFilesStats(extractionId)
|
||||
])
|
||||
const [allPrimaryDomains, ossDocumentTypes, stopTermsFiles] =
|
||||
await Promise.all([
|
||||
Dictionary.fetchAllPrimaryDomains(req.determinedLanguage),
|
||||
Extraction.fetchOssDocumentTypes(req.determinedLanguage),
|
||||
Extraction.fetchAllStopTermsFilesStats(extractionId)
|
||||
])
|
||||
const { params } = extraction.ossParams
|
||||
const domainUdk = params?.domainUdk?.[0]
|
||||
if (domainUdk)
|
||||
if (domainUdk) {
|
||||
extraction.domainId = await Domain.fetchIdByUdkCode(domainUdk)
|
||||
}
|
||||
extraction.documentType = intoDbArray(params.documentType, 'always')
|
||||
extraction.year = intoDbArray(params.year, 'always')
|
||||
extraction.keywords = intoDbArray(params.keywords, 'always')
|
||||
@@ -88,6 +102,7 @@ extraction.edit = async (req, res) => {
|
||||
id: extractionId,
|
||||
extraction,
|
||||
allPrimaryDomains,
|
||||
ossDocumentTypes,
|
||||
stopTermsFiles
|
||||
})
|
||||
} else {
|
||||
@@ -110,7 +125,7 @@ extraction.updateOwn = async (req, res) => {
|
||||
await Extraction.update(extractionId, req.body.name)
|
||||
|
||||
// Reload page.
|
||||
res.redirect(`./${extractionId}`)
|
||||
res.redirect(303, `./${extractionId}`)
|
||||
}
|
||||
|
||||
extraction.docsEdit = async (req, res) => {
|
||||
|
||||
@@ -42,7 +42,7 @@ const minEntriesEmailBookmark = {
|
||||
// Exports actions related to checking and acting on minimum entries per dictionary setting.
|
||||
exports.minEntriesRequirementCheckAndAct = {
|
||||
// Checks required criteria and sends notifications emails if required.
|
||||
async onDelete(dictionaryId, appRef) {
|
||||
async onDelete(dictionaryId, appRef, determinedLanguage) {
|
||||
const minEntries = await getInstanceSetting('min_entries_per_dictionary')
|
||||
|
||||
// Only proceed if a valid and positive minimum entries per dictionary setting is set.
|
||||
@@ -58,9 +58,8 @@ exports.minEntriesRequirementCheckAndAct = {
|
||||
if (!isBelowMinEntriesThreshold || wasEmailAlreadySent) return
|
||||
|
||||
// Prepare and send notification emails.
|
||||
// TODO I18n - nameSl
|
||||
const [nameSl, adminEmails, dictionariesAdminEmails] = await Promise.all([
|
||||
Dictionary.fetchName(dictionaryId),
|
||||
const [name, adminEmails, dictionariesAdminEmails] = await Promise.all([
|
||||
Dictionary.fetchName(dictionaryId, determinedLanguage),
|
||||
Dictionary.fetchAdminEmails(dictionaryId),
|
||||
Dictionary.fetchDictionariesAdminEmails()
|
||||
])
|
||||
@@ -71,10 +70,9 @@ exports.minEntriesRequirementCheckAndAct = {
|
||||
|
||||
const type = 'delete'
|
||||
const renderAsync = promisify(appRef.render.bind(appRef))
|
||||
// TODO I18n - nameSl
|
||||
const emailHtml = await renderAsync('email/dictionary-status-change', {
|
||||
type,
|
||||
nameSl
|
||||
name
|
||||
})
|
||||
|
||||
// TODO i18n - What language are the email title and content (we already have email translated)
|
||||
@@ -123,12 +121,11 @@ exports.determineNewStatus = async isPublished => {
|
||||
// Exports actions related to checking and acting on dictionary status changes.
|
||||
exports.statusChangeCheckAndAct = {
|
||||
// Notify dictionaries admins by email on dictionary status changes.
|
||||
// TODO I18n - nameSl
|
||||
async updateUsers(
|
||||
dictionaryId,
|
||||
isPublishedNew,
|
||||
oldDictStatus,
|
||||
nameSl,
|
||||
name,
|
||||
appRef,
|
||||
user
|
||||
) {
|
||||
@@ -139,12 +136,11 @@ exports.statusChangeCheckAndAct = {
|
||||
const dictionariesAdminEmails =
|
||||
await Dictionary.fetchDictionariesAdminEmails()
|
||||
const type = 'unpublish'
|
||||
// TODO I18n - nameSl
|
||||
await renderAndSendStatusChangeEmails(
|
||||
appRef,
|
||||
type,
|
||||
user.email,
|
||||
nameSl,
|
||||
name,
|
||||
dictionariesAdminEmails
|
||||
)
|
||||
|
||||
@@ -157,12 +153,11 @@ exports.statusChangeCheckAndAct = {
|
||||
const type =
|
||||
isApprovalRequired === 'T' ? 'publish-approval' : 'publish-no-approval'
|
||||
|
||||
// TODO I18n - nameSl
|
||||
await renderAndSendStatusChangeEmails(
|
||||
appRef,
|
||||
type,
|
||||
user.email,
|
||||
nameSl,
|
||||
name,
|
||||
dictionariesAdminEmails
|
||||
)
|
||||
}
|
||||
@@ -175,12 +170,12 @@ exports.statusChangeCheckAndAct = {
|
||||
statusNew,
|
||||
statusOld,
|
||||
appRef,
|
||||
determinedLanguage,
|
||||
user
|
||||
) {
|
||||
if (statusOld === 'reviewed' && statusNew !== 'reviewed') {
|
||||
// TODO I18n - nameSl
|
||||
const [nameSl, adminEmails] = await Promise.all([
|
||||
Dictionary.fetchName(dictionaryId),
|
||||
const [name, adminEmails] = await Promise.all([
|
||||
Dictionary.fetchName(dictionaryId, determinedLanguage),
|
||||
Dictionary.fetchAdminEmails(dictionaryId)
|
||||
])
|
||||
const type = 'status'
|
||||
@@ -189,7 +184,7 @@ exports.statusChangeCheckAndAct = {
|
||||
appRef,
|
||||
type,
|
||||
user.email,
|
||||
nameSl,
|
||||
name,
|
||||
adminEmails
|
||||
)
|
||||
}
|
||||
@@ -197,19 +192,18 @@ exports.statusChangeCheckAndAct = {
|
||||
}
|
||||
|
||||
// Helper function used by statusChangeCheckAndAct methods.
|
||||
// TODO I18n - nameSl
|
||||
async function renderAndSendStatusChangeEmails(
|
||||
appRef,
|
||||
type,
|
||||
changerEmail,
|
||||
nameSl,
|
||||
name,
|
||||
targetEmails
|
||||
) {
|
||||
const renderAsync = promisify(appRef.render.bind(appRef))
|
||||
const emailHtml = await renderAsync('email/dictionary-status-change', {
|
||||
type,
|
||||
changerEmail,
|
||||
nameSl
|
||||
name
|
||||
})
|
||||
|
||||
// TODO i18n - What language are the email title and content (we already have email translated)
|
||||
|
||||
@@ -4,24 +4,23 @@ const { getInstanceSetting } = require('../../models/helpers')
|
||||
|
||||
const helper = {}
|
||||
|
||||
helper.initialize = async () => {
|
||||
helper.initialize = async determinedLanguage => {
|
||||
const initializers = {}
|
||||
|
||||
// TODO Once english language is implemented, gather selected language (sl/en) from request ~ (cookies?)
|
||||
// TODO i18n name_sl
|
||||
const language = 'name_sl'
|
||||
|
||||
// TODO Consider parallelizing following queries. Single vs pooled clients?
|
||||
initializers.allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains()
|
||||
initializers.sourceLanguages = await Dictionary.fetchAllLanguages(language)
|
||||
initializers.targetLanguages = (
|
||||
await Dictionary.fetchAllLanguages(language)
|
||||
).filter(
|
||||
initializers.allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains(
|
||||
determinedLanguage
|
||||
)
|
||||
const allLanguages = await Dictionary.fetchAllLanguages(determinedLanguage)
|
||||
initializers.sourceLanguages = allLanguages
|
||||
initializers.targetLanguages = allLanguages.filter(
|
||||
// drop slovene language
|
||||
l => l.id !== 32
|
||||
)
|
||||
|
||||
initializers.allDictionaryNames = await Dictionary.fetchAll()
|
||||
initializers.allDictionaryNames = await Dictionary.fetchAll(
|
||||
determinedLanguage
|
||||
)
|
||||
|
||||
initializers.portals = []
|
||||
initializers.portals.push({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
const { promisify } = require('util')
|
||||
const Entry = require('../models/entry')
|
||||
const { getInstanceSetting } = require('../models/helpers')
|
||||
const Dictionary = require('../models/dictionary')
|
||||
@@ -13,10 +14,12 @@ const {
|
||||
prepareSeachFilterData,
|
||||
prepareConsultancyEntries
|
||||
} = require('../models/helpers/search')
|
||||
const email = require('../models/email')
|
||||
const generateQuery = require('../models/helpers/search/generate-query')
|
||||
const { DEFAULT_HITS_PER_PAGE } = require('../config/settings')
|
||||
const SFDSuggestionImporter = require('./helpers/search-filter-data-suggestion-importer')
|
||||
const User = require('../models/user')
|
||||
const { capitalize } = require('../utils')
|
||||
|
||||
// TODO Luka (note to self): Measure performance, consider caching.
|
||||
exports.index = async (req, res) => {
|
||||
@@ -27,12 +30,10 @@ exports.index = async (req, res) => {
|
||||
targetLanguages,
|
||||
allDictionaryNames,
|
||||
portals
|
||||
} = await SFDSuggestionImporter.initialize()
|
||||
|
||||
const englishLanguageEnabled = false // dummy variable for future edit
|
||||
} = await SFDSuggestionImporter.initialize(req.determinedLanguage)
|
||||
|
||||
const latestDicts = await Dictionary.fetchLatest3DictsByPublishDate(
|
||||
englishLanguageEnabled
|
||||
req.determinedLanguage
|
||||
)
|
||||
|
||||
const portalName = await getInstanceSetting(`portal_name_${language}`)
|
||||
@@ -58,7 +59,7 @@ exports.index = async (req, res) => {
|
||||
exports.search = async (req, res) => {
|
||||
const searchString = req.query.q?.trim()
|
||||
|
||||
if (!searchString) return res.redirect('/')
|
||||
if (!searchString) return res.redirect(303, '/')
|
||||
|
||||
const title = req.t('Iskanje')
|
||||
|
||||
@@ -68,7 +69,7 @@ exports.search = async (req, res) => {
|
||||
targetLanguages,
|
||||
allDictionaryNames,
|
||||
portals
|
||||
} = await SFDSuggestionImporter.initialize()
|
||||
} = await SFDSuggestionImporter.initialize(req.determinedLanguage)
|
||||
|
||||
const filters = {
|
||||
sourceLanguages: intoDbArray(req.query.sl, 'always'),
|
||||
@@ -131,7 +132,10 @@ exports.search = async (req, res) => {
|
||||
// Similar to search query without any extra filters, this is required for modal to diplay ALL res.
|
||||
// const allAggregation = await indexAllResultsNoFiltering(req, res)
|
||||
|
||||
const aggregation = await prepareAggregation(aggregationRaw)
|
||||
const aggregation = await prepareAggregation(
|
||||
aggregationRaw,
|
||||
req.determinedLanguage
|
||||
)
|
||||
const searchFilterData = prepareSeachFilterData(aggregation, filters)
|
||||
|
||||
// TODO Luka (note to self): Consider reworking Miha's logic below. It's hacky and possibly error prone.
|
||||
@@ -328,7 +332,7 @@ exports.entryDetails = async (req, res) => {
|
||||
|
||||
// If external url, just redirect
|
||||
if (entry.external_url) {
|
||||
return res.redirect(entry.external_url)
|
||||
return res.redirect(303, entry.external_url)
|
||||
}
|
||||
|
||||
// unnecessary legacy assigment, refactor when time is available
|
||||
@@ -340,12 +344,15 @@ exports.entryDetails = async (req, res) => {
|
||||
targetLanguages,
|
||||
allDictionaryNames,
|
||||
portals
|
||||
} = await SFDSuggestionImporter.initialize()
|
||||
} = await SFDSuggestionImporter.initialize(req.determinedLanguage)
|
||||
|
||||
const [dictStruct, dictionaryData, selectedDomainLabelsForEntry] =
|
||||
await Promise.all([
|
||||
Dictionary.fetchDictionaryWithEditStructure(entry.dictionary_id),
|
||||
Dictionary.fetchDictionaryBasicInfo(entry.dictionary_id),
|
||||
Dictionary.fetchDictionaryBasicInfo(
|
||||
entry.dictionary_id,
|
||||
req.determinedLanguage
|
||||
),
|
||||
Entry.fetchDomainLabels(termId)
|
||||
])
|
||||
|
||||
@@ -399,7 +406,7 @@ exports.entryDetails = async (req, res) => {
|
||||
prevHref: '/iskanje',
|
||||
portalCode: dictionaryData[0].portalcode,
|
||||
portalName: dictionaryData[0].portalname,
|
||||
dictName: structure.nameSl,
|
||||
dictName: structure[`name${capitalize(req.determinedLanguage)}`],
|
||||
dictHref: `/slovarji/${structure.id}/o-slovarju?sentFromEntryId=${termId}`,
|
||||
fullAuthorName: reducedData.author ? reducedData.author.join(', ') : '',
|
||||
areas: dictionaryData[0].domain_primary,
|
||||
@@ -438,11 +445,13 @@ exports.entryDetails = async (req, res) => {
|
||||
entry.foreignEntries[idx] = await
|
||||
}) */
|
||||
|
||||
const langs = await Dictionary.fetchLanguages(entryData.dictionary_id)
|
||||
const langs = await Dictionary.fetchLanguages(
|
||||
entryData.dictionary_id,
|
||||
req.determinedLanguage
|
||||
)
|
||||
entryData.foreign_entries.forEach((val, idx) => {
|
||||
try {
|
||||
entry.foreign_entries[idx].name_sl = langs[idx].nameSl
|
||||
entry.foreign_entries[idx].name_en = langs[idx].nameEn
|
||||
entry.foreign_entries[idx].name = langs[idx].name
|
||||
} catch (e) {}
|
||||
})
|
||||
|
||||
@@ -479,16 +488,44 @@ exports.changePassword = async (req, res) => {
|
||||
}
|
||||
|
||||
exports.resetPassword = async (req, res) => {
|
||||
if (req.user) return res.redirect(303, '/spremeni-geslo')
|
||||
|
||||
const { token } = req.query
|
||||
if (!token) return res.redirect(303, '/')
|
||||
|
||||
const isTokenValid = await User.isResetPasswordTokenValid(token)
|
||||
|
||||
// console.log(token)
|
||||
res.render('pages/reset-password/reset-password', {
|
||||
// title: 'Pozabljeno geslo'
|
||||
isValidToken: token === '123',
|
||||
title: 'Ponastavitev gesla',
|
||||
isTokenValid,
|
||||
token
|
||||
})
|
||||
}
|
||||
|
||||
exports.changeEmail = async (req, res) => {
|
||||
const { token } = req.query
|
||||
if (!token) return res.redirect(303, '/')
|
||||
|
||||
const user = await User.changeEmailWithToken(token, req.t)
|
||||
|
||||
const renderAsync = promisify(req.app.render.bind(req.app))
|
||||
const emailHtml = await renderAsync(
|
||||
`email/user-change-email-success_${req.language}`,
|
||||
{
|
||||
username: user.username
|
||||
}
|
||||
)
|
||||
await email.send({
|
||||
to: user.email,
|
||||
subject: req.t('Sprememba elektronskega naslova - uspeh'),
|
||||
html: emailHtml
|
||||
})
|
||||
|
||||
req.flash('info', req.t('Uspešno ste spremenili svoj elektronski naslov.'))
|
||||
if (req.user) return res.redirect(303, '/moj-racun')
|
||||
res.redirect(303, '/')
|
||||
}
|
||||
|
||||
exports.userSettings = async (req, res) => {
|
||||
const hitsPerPageArr = await User.fetchAllowedHitsPerPage()
|
||||
res.render('pages/profile/change-profile-settings', {
|
||||
@@ -513,7 +550,7 @@ exports.changeUserLanguage = async (req, res) => {
|
||||
req.session.language = languageCode
|
||||
}
|
||||
|
||||
res.redirect('/')
|
||||
res.redirect(303, '/')
|
||||
}
|
||||
|
||||
function mergeDomains(
|
||||
|
||||
@@ -17,7 +17,7 @@ portal.updateInstaceSettings = async (req, res) => {
|
||||
const payload = req.body
|
||||
await Portal.updateInstaceSettings(payload)
|
||||
await clearCachedInstanceSettings()
|
||||
res.redirect('/admin/nastavitve/portal')
|
||||
res.redirect(303, '/admin/nastavitve/portal')
|
||||
}
|
||||
|
||||
portal.instanceDictSettings = async (req, res) => {
|
||||
@@ -33,7 +33,7 @@ portal.updateInstanceDictSettings = async (req, res) => {
|
||||
const payload = req.body
|
||||
await Portal.updateInstaceDictSettings(payload)
|
||||
await clearCachedInstanceSettings()
|
||||
res.redirect('/admin/nastavitve/slovarji')
|
||||
res.redirect(303, '/admin/nastavitve/slovarji')
|
||||
}
|
||||
|
||||
portal.instanceConsultancySettings = async (req, res) => {
|
||||
@@ -48,7 +48,7 @@ portal.updateInstanceConusltacySettings = async (req, res) => {
|
||||
const payload = req.body
|
||||
await Portal.updateInstaceConsultancySettings(payload)
|
||||
await clearCachedInstanceSettings()
|
||||
res.redirect('/admin/nastavitve/svetovalnica')
|
||||
res.redirect(303, '/admin/nastavitve/svetovalnica')
|
||||
}
|
||||
|
||||
portal.new = async (req, res) => {
|
||||
@@ -80,7 +80,7 @@ portal.updatePortal = async (req, res) => {
|
||||
const portalId = req.params.portalId
|
||||
const payload = req.body
|
||||
await Portal.update(portalId, payload)
|
||||
res.redirect('/admin/povezave/seznam')
|
||||
res.redirect(303, '/admin/povezave/seznam')
|
||||
}
|
||||
|
||||
portal.fetchSelectedLinkedDictionaries = async (req, res) => {
|
||||
@@ -101,7 +101,7 @@ portal.fetchSelectedLinkedDictionaries = async (req, res) => {
|
||||
portal.updateSelectedDictionaries = async (req, res) => {
|
||||
const linkedId = req.params.portalId
|
||||
await Portal.updateSelectedDictionaries(linkedId, req.body)
|
||||
res.redirect('back')
|
||||
res.redirect(303, 'back')
|
||||
}
|
||||
|
||||
portal.fetchAllLinkedDictionaries = async (req, res) => {
|
||||
@@ -119,7 +119,7 @@ portal.fetchAllLinkedDictionaries = async (req, res) => {
|
||||
|
||||
portal.updateAllDictionaries = async (req, res) => {
|
||||
await Portal.updateAllDictionaries(req.body)
|
||||
res.redirect('back')
|
||||
res.redirect(303, 'back')
|
||||
}
|
||||
|
||||
portal.comments = async (req, res) => {
|
||||
|
||||
@@ -3,6 +3,7 @@ const { promisify } = require('util')
|
||||
const passport = require('passport')
|
||||
const User = require('../models/user')
|
||||
const email = require('../models/email')
|
||||
const { logout: logoutUser } = require('../middleware/user')
|
||||
const { origin } = require('../config/keys')
|
||||
const { rememberMeCookieSettings } = require('../config/settings')
|
||||
const RandomBytesAsync = promisify(randomBytes)
|
||||
@@ -12,10 +13,14 @@ const user = {}
|
||||
|
||||
user.register = async (req, res) => {
|
||||
// TODO Add validation.
|
||||
const userId = await User.create({
|
||||
...req.body,
|
||||
language: req.session.language
|
||||
})
|
||||
|
||||
const userId = await User.create(
|
||||
{
|
||||
...req.body,
|
||||
language: req.session.language
|
||||
},
|
||||
req.t
|
||||
)
|
||||
const activationToken = (await RandomBytesAsync(32)).toString('hex')
|
||||
await User.saveActivationToken(userId, activationToken)
|
||||
const { email: userEmail, username } = req.body
|
||||
@@ -38,8 +43,9 @@ user.register = async (req, res) => {
|
||||
user.activateAccount = async (req, res) => {
|
||||
// TODO Add validation. What if user is already logged in? What if account is already active? ...
|
||||
const { token } = req.query
|
||||
const user = await User.fetchByActivationToken(token)
|
||||
await User.activateAccount(user)
|
||||
if (!token) return res.redirect(303, '/')
|
||||
|
||||
const user = await User.activateAccountWithToken(token, req.t)
|
||||
const loginAsync = promisify(req.login.bind(req))
|
||||
await loginAsync(user)
|
||||
|
||||
@@ -48,7 +54,11 @@ user.activateAccount = async (req, res) => {
|
||||
delete req.session.language
|
||||
}
|
||||
|
||||
res.redirect('/')
|
||||
req.flash(
|
||||
'info',
|
||||
req.t('Uspešno ste aktivirali svoj uporabniški račun in se prijavili.')
|
||||
)
|
||||
res.redirect(303, '/')
|
||||
}
|
||||
|
||||
user.login = async (req, res, next) => {
|
||||
@@ -89,20 +99,86 @@ user.login = async (req, res, next) => {
|
||||
)(req, res, next)
|
||||
}
|
||||
|
||||
user.logout = async (req, res) => {
|
||||
const rememberMeToken = req.signedCookies.remember_me
|
||||
user.logout = [logoutUser, (req, res) => res.redirect(303, '/')]
|
||||
|
||||
if (rememberMeToken) {
|
||||
res.clearCookie('remember_me')
|
||||
await User.clearRememberMeToken(rememberMeToken)
|
||||
user.generateResetPasswordToken = async (req, res) => {
|
||||
const { usernameOrEmail } = req.body
|
||||
const user = await User.fetchByUsernameOrEmail(usernameOrEmail)
|
||||
|
||||
if (!user) {
|
||||
return res
|
||||
.status(400)
|
||||
.send(req.t('Nepravilno uporabniško ime ali elektronski naslov.'))
|
||||
}
|
||||
|
||||
req.session.language = req.user.language
|
||||
if (user.status !== 'active') {
|
||||
return res
|
||||
.status(400)
|
||||
.send(
|
||||
req.t(
|
||||
'Uporabniški račun še ni aktiviran. Kliknite aktivacijsko povezavo, katero smo vam poslali po elektronski pošti.'
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
req.logout()
|
||||
// Manually clear session.passport due to bug in current passport version.
|
||||
delete req.session.passport.user
|
||||
res.redirect('/')
|
||||
const resetPasswordToken = (await RandomBytesAsync(32)).toString('hex')
|
||||
await User.saveResetPasswordToken(user.id, resetPasswordToken)
|
||||
const { email: userEmail, username } = user
|
||||
let resetPasswordLink = new URL('/ponastavitev-gesla', origin)
|
||||
resetPasswordLink.searchParams.set('token', resetPasswordToken)
|
||||
resetPasswordLink = resetPasswordLink.href
|
||||
const renderAsync = promisify(req.app.render.bind(req.app))
|
||||
// TODO i18n - prepare proper slovenian an english email templates
|
||||
const emailHtml = await renderAsync(
|
||||
`email/user-reset-password-token_${req.language}`,
|
||||
{
|
||||
username,
|
||||
resetPasswordLink
|
||||
}
|
||||
)
|
||||
await email.send({
|
||||
to: userEmail,
|
||||
subject: req.t('Ponastavitev gesla'),
|
||||
html: emailHtml
|
||||
})
|
||||
res.send()
|
||||
}
|
||||
|
||||
user.changePassword = async (req, res) => {
|
||||
const { token, password, passwordRepeat } = req.body
|
||||
|
||||
if (password !== passwordRepeat) {
|
||||
return res.status(400).send(req.t('Gesli se ne ujemata'))
|
||||
}
|
||||
// TODO Add additional password validation (min length, ...)
|
||||
|
||||
const user = await User.resetPasswordWithToken(token, password, req.t)
|
||||
|
||||
const renderAsync = promisify(req.app.render.bind(req.app))
|
||||
// TODO i18n - prepare proper slovenian an english email templates
|
||||
const emailHtml = await renderAsync(
|
||||
`email/user-reset-password-success_${req.language}`,
|
||||
{
|
||||
username: user.username
|
||||
}
|
||||
)
|
||||
await email.send({
|
||||
to: user.email,
|
||||
subject: req.t('Uspešna ponastavitev gesla'),
|
||||
html: emailHtml
|
||||
})
|
||||
|
||||
req.flash('info', req.t('Vaše geslo je bilo uspešno ponastavljeno.'))
|
||||
|
||||
const loginAsync = promisify(req.login.bind(req))
|
||||
await loginAsync(user)
|
||||
|
||||
if (req.session.language) {
|
||||
await User.updateLanguage(user.id, req.session.language)
|
||||
delete req.session.language
|
||||
}
|
||||
|
||||
res.send()
|
||||
}
|
||||
|
||||
user.list = async (req, res) => {
|
||||
@@ -135,7 +211,7 @@ user.findByUsernameOrEmail = async (req, res) => {
|
||||
|
||||
user.updateRoles = async (req, res) => {
|
||||
await User.updatePortalRoles(req.body.rolesPerUser)
|
||||
res.redirect('back')
|
||||
res.redirect(303, 'back')
|
||||
}
|
||||
|
||||
user.adminEdit = async (req, res) => {
|
||||
@@ -144,6 +220,9 @@ user.adminEdit = async (req, res) => {
|
||||
User.fetchUser(userId),
|
||||
User.fetchUserRoles(userId)
|
||||
])
|
||||
|
||||
if (userData.status === 'closed') return res.redirect(303, '/')
|
||||
|
||||
res.render('pages/admin/user-edit', {
|
||||
title: req.t('Uporabnik'),
|
||||
userData,
|
||||
@@ -156,7 +235,7 @@ user.adminEdit = async (req, res) => {
|
||||
user.adminUpdate = async (req, res) => {
|
||||
const { userId } = req.params
|
||||
await User.updateUser(userId, req.body)
|
||||
res.redirect('back')
|
||||
res.redirect(303, 'back')
|
||||
}
|
||||
|
||||
module.exports = user
|
||||
|
||||
@@ -21,6 +21,9 @@ passport.deserializeUser(async (id, done) => {
|
||||
const user = await User.fetchDeserializedDataById(id)
|
||||
|
||||
// TODO Consider what to do if no user was found?
|
||||
|
||||
// TODO This is a current workaround until session invalidation is implemented.
|
||||
if (user.status !== 'active') return done(null, false)
|
||||
done(null, user)
|
||||
} catch (error) {
|
||||
done(error)
|
||||
@@ -32,11 +35,7 @@ passport.use(
|
||||
{ passReqToCallback: true, usernameField: 'usernameOrEmail' },
|
||||
async (req, usernameOrEmail, password, done) => {
|
||||
try {
|
||||
const { rows } = await db.query(
|
||||
'SELECT id, status, bcrypt_hash FROM "user" WHERE username = $1 OR email = $1',
|
||||
[usernameOrEmail]
|
||||
)
|
||||
const user = rows[0]
|
||||
const user = await User.fetchByUsernameOrEmail(usernameOrEmail)
|
||||
|
||||
if (!user) {
|
||||
return done(null, false, {
|
||||
|
||||
@@ -2,6 +2,13 @@ const { getInstanceSetting } = require('../models/helpers')
|
||||
|
||||
exports.enhanceLocals = async (req, res, next) => {
|
||||
res.locals.portalCode = await getInstanceSetting('portal_code')
|
||||
res.locals.flashInfo = req.flash('info')
|
||||
|
||||
next()
|
||||
}
|
||||
|
||||
exports.adjustHeaders = async (req, res, next) => {
|
||||
res.set('Cache-Control', 'no-cache, private')
|
||||
|
||||
next()
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
const User = require('../models/user')
|
||||
|
||||
const user = {}
|
||||
|
||||
user.enhance = (req, res, next) => {
|
||||
@@ -18,7 +20,16 @@ user.isAuthenticated = (req, res, next) => {
|
||||
if (req.isAuthenticated()) return next()
|
||||
|
||||
if (req.isAjax) return res.status(400).end()
|
||||
res.redirect(req.baseUrl || '/')
|
||||
res.redirect(303, req.baseUrl || '/')
|
||||
}
|
||||
|
||||
user.isPortalAdmin = (req, res, next) => {
|
||||
const isPortalAdmin = req.user.hasRole('portal admin')
|
||||
|
||||
if (isPortalAdmin) return next()
|
||||
|
||||
if (req.isAjax) return res.status(400).end()
|
||||
res.redirect(303, req.baseUrl || '/')
|
||||
}
|
||||
|
||||
user.isDictionaryAdmin = (req, res, next) => {
|
||||
@@ -28,17 +39,37 @@ user.isDictionaryAdmin = (req, res, next) => {
|
||||
if (isAdmin) return next()
|
||||
|
||||
if (req.isAjax) return res.status(400).end()
|
||||
res.redirect(req.baseUrl || '/')
|
||||
res.redirect(303, req.baseUrl || '/')
|
||||
}
|
||||
|
||||
user.isDictionaryEditor = (req, res, next) => {
|
||||
const { dictionaryId } = req.params
|
||||
const dictionaryId = req.params.dictionaryId || req.dictionaryId
|
||||
const isEditor = req.user.hasAnyDictionaryRole(dictionaryId)
|
||||
|
||||
if (isEditor) return next()
|
||||
|
||||
if (req.isAjax) return res.status(400).end()
|
||||
res.redirect(req.baseUrl || '/')
|
||||
res.redirect(303, req.baseUrl || '/')
|
||||
}
|
||||
|
||||
user.canAdministrateDictionary = (req, res, next) => {
|
||||
const dictionaryId = req.params.dictionaryId || req.dictionaryId
|
||||
const isAdmin = req.user.hasDictionaryRole(dictionaryId, 'administration')
|
||||
const isPortalAdmin = req.user.hasRole('portal admin')
|
||||
const isDictionariesAdmin = req.user.hasRole('dictionaries admin')
|
||||
if (isAdmin || isPortalAdmin || isDictionariesAdmin) return next()
|
||||
|
||||
if (req.isAjax) return res.status(400).end()
|
||||
res.redirect(303, req.baseUrl || '/')
|
||||
}
|
||||
|
||||
user.canAdministrateDictionaries = (req, res, next) => {
|
||||
const isPortalAdmin = req.user.hasRole('portal admin')
|
||||
const isDictionariesAdmin = req.user.hasRole('dictionaries admin')
|
||||
if (isPortalAdmin || isDictionariesAdmin) return next()
|
||||
|
||||
if (req.isAjax) return res.status(400).end()
|
||||
res.redirect(303, req.baseUrl || '/')
|
||||
}
|
||||
|
||||
user.canContentEdit = (req, res, next) => {
|
||||
@@ -49,7 +80,54 @@ user.canContentEdit = (req, res, next) => {
|
||||
if (isEditor || isPortalAdmin || isDictionariesAdmin) return next()
|
||||
|
||||
if (req.isAjax) return res.status(400).end()
|
||||
res.redirect(req.baseUrl || '/')
|
||||
res.redirect(303, req.baseUrl || '/')
|
||||
}
|
||||
|
||||
user.canAdministrateConsultancy = (req, res, next) => {
|
||||
const isPortalAdmin = req.user.hasRole('portal admin')
|
||||
const isConsultancyAdmin = req.user.hasRole('consultancy admin')
|
||||
if (isPortalAdmin || isConsultancyAdmin) return next()
|
||||
|
||||
if (req.isAjax) return res.status(400).end()
|
||||
res.redirect(303, req.baseUrl || '/')
|
||||
}
|
||||
|
||||
user.canConsult = (req, res, next) => {
|
||||
const isConsultant = req.user.hasRole('consultant')
|
||||
const isPortalAdmin = req.user.hasRole('portal admin')
|
||||
const isConsultancyAdmin = req.user.hasRole('consultancy admin')
|
||||
if (isConsultant || isPortalAdmin || isConsultancyAdmin) return next()
|
||||
|
||||
if (req.isAjax) return res.status(400).end()
|
||||
res.redirect(303, req.baseUrl || '/')
|
||||
}
|
||||
|
||||
user.canConsultEntry = (req, res, next) => {
|
||||
const id = req.params.id || req.entryId
|
||||
const isConsultantForEntry = req.user.isEditorOfConsultancyEntry(id)
|
||||
const isPortalAdmin = req.user.hasRole('portal admin')
|
||||
const isConsultancyAdmin = req.user.hasRole('consultancy admin')
|
||||
if (isConsultantForEntry || isPortalAdmin || isConsultancyAdmin) return next()
|
||||
|
||||
if (req.isAjax) return res.status(400).end()
|
||||
res.redirect(303, req.baseUrl || '/')
|
||||
}
|
||||
|
||||
user.logout = async (req, res, next) => {
|
||||
const rememberMeToken = req.signedCookies.remember_me
|
||||
|
||||
if (rememberMeToken) {
|
||||
res.clearCookie('remember_me')
|
||||
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
|
||||
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+20
-240
@@ -1,10 +1,6 @@
|
||||
const db = require('./db')
|
||||
const debug = require('debug')('termPortal:models/comment')
|
||||
const User = require('../models/user')
|
||||
const {
|
||||
portalAdminInitialEmail,
|
||||
portalAdminInitialPassword
|
||||
} = require('../config/keys')
|
||||
const Entry = require('./entry')
|
||||
// const debug = require('debug')('termPortal:models/comment')
|
||||
|
||||
class Comment {
|
||||
// Deserialize flat data into an organized comment object.
|
||||
@@ -84,15 +80,18 @@ class Comment {
|
||||
}
|
||||
break
|
||||
|
||||
case 'entry_dict_ext':
|
||||
case 'entry_dict_ext': {
|
||||
const { dictionary_id: dictionaryId } = await Entry.fetch(ctxId)
|
||||
|
||||
if (
|
||||
user.hasRole('portal admin') ||
|
||||
user.hasRole('consultancy admin') ||
|
||||
user.hasDictionaryRole(ctxId, 'administration')
|
||||
user.hasRole('dictionaries admin') ||
|
||||
user.hasDictionaryRole(dictionaryId, 'administration')
|
||||
) {
|
||||
isCommentModerator = true
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
default:
|
||||
throw Error('Invalid context type')
|
||||
@@ -189,6 +188,18 @@ class Comment {
|
||||
await db.query(text, values)
|
||||
}
|
||||
|
||||
// Fetch context info for a specific comment.
|
||||
static async fetchContextById(id) {
|
||||
const {
|
||||
rows: [{ context_type: ctxType, context_id: ctxId }]
|
||||
} = await db.query(
|
||||
'SELECT context_type, context_id FROM comment WHERE id = $1',
|
||||
[id]
|
||||
)
|
||||
|
||||
return { ctxType, ctxId }
|
||||
}
|
||||
|
||||
static async updateStatus(status, id) {
|
||||
const values = [id, status]
|
||||
const text = `
|
||||
@@ -197,237 +208,6 @@ class Comment {
|
||||
WHERE id = $2`
|
||||
await db.query(text, values)
|
||||
}
|
||||
|
||||
// Insert a new demo comment into DB.
|
||||
static async createDemo(comment) {
|
||||
const text =
|
||||
"INSERT INTO comment (message, author_id, context_type, quoted_comment_id) VALUES ($1, $2, 'portal', $3) RETURNING id"
|
||||
const values = [comment.message, pickRandomMockUserId(), comment.quoteId]
|
||||
const { rows } = await db.query(text, values)
|
||||
const idOfInsertedComment = rows[0].id
|
||||
const text2 = `${selectAllCommentsQueryString} WHERE c.id = ${idOfInsertedComment}`
|
||||
const { rows: rows2 } = await db.query(text2)
|
||||
|
||||
const insertedComment = rows2[0]
|
||||
|
||||
const deserializedComment = new this(insertedComment)
|
||||
|
||||
return deserializedComment
|
||||
}
|
||||
|
||||
// Seed DB with <commentCount> random comments.
|
||||
static async seed(commentCount) {
|
||||
const seedTasks = []
|
||||
|
||||
for (let i = 0; i < commentCount; i++) {
|
||||
seedTasks.push(this.createDemo({ message: pickRandomMockMessage() }))
|
||||
}
|
||||
|
||||
const seededComments = await Promise.all(seedTasks)
|
||||
debug(`Successfully seeded ${commentCount} comments`)
|
||||
debug('Comments:')
|
||||
seededComments.forEach(comment => debug(comment))
|
||||
}
|
||||
|
||||
// Clear all comments from DB.
|
||||
static async clear() {
|
||||
await db.query('TRUNCATE comment')
|
||||
}
|
||||
}
|
||||
|
||||
// Base SQL query string to fetch all comments.
|
||||
// Can be extended with a WHEN filter clause.
|
||||
const selectAllCommentsQueryString = `SELECT
|
||||
c.id,
|
||||
c.message,
|
||||
cu.first_name author_first_name,
|
||||
cu.last_name author_last_name,
|
||||
c.time_created,
|
||||
c.status,
|
||||
q.message quote_message,
|
||||
qu.first_name quote_author_first_name,
|
||||
qu.last_name quote_author_last_name,
|
||||
q.time_created quote_time_created
|
||||
FROM comment c
|
||||
LEFT JOIN "user" cu
|
||||
ON cu.id = c.author_id
|
||||
LEFT JOIN comment q
|
||||
ON q.id = c.quoted_comment_id
|
||||
LEFT JOIN "user" qu
|
||||
ON qu.id = q.author_id`
|
||||
|
||||
// A list of messages of varying length for DB seeding.
|
||||
const mockMessageVariations = [
|
||||
'Lorem ipsum dolor sit amet.',
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
||||
'Lorem ipsum dolor sit, amet consectetur adipisicing elit. Dolorem obcaecati ut reprehenderit explicabo, adipisci atque! Repudiandae eos facilis veniam modi.',
|
||||
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Ab dicta error architecto id soluta laborum pariatur saepe doloribus voluptatem voluptas totam placeat, inventore rem! Tempore illum deleniti esse nemo. Amet.',
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur tristique at sem eu ultricies. Curabitur cursus efficitur ipsum, et iaculis ipsum egestas vel egestas vestibulum nec odio posuere, mollis diam et, bibendum velit. Proin non velit nec dui luctus dolor.',
|
||||
'Lorem ipsum dolor sit, amet consectetur adipisicing elit. Eveniet deleniti ad quasi, ea, recusandae esse autem expedita tempora molestiae ipsa labore magnam dolorem nostrum, corrupti sint obcaecati. Voluptatum molestiae, qui laudantium voluptatibus eius, ratione voluptate, eaque quae alias dicta pariatur?'
|
||||
]
|
||||
|
||||
// A list of users for DB seeding and random asigning to new
|
||||
// comments until authentication and session mechanism are in place.
|
||||
const mockUsers = [
|
||||
{ firstName: 'Primož', lastName: 'Roglič' },
|
||||
{ firstName: 'Tadej', lastName: 'Pogačar' },
|
||||
{ firstName: 'Krištof', lastName: 'Kolumb' },
|
||||
{ firstName: 'Rudolf', lastName: 'Maister' },
|
||||
{ firstName: 'Ricky', lastName: 'Rickardo' },
|
||||
{ firstName: 'Freddy', lastName: 'Mercury' },
|
||||
{ firstName: 'Roger', lastName: 'Moore' },
|
||||
{ firstName: 'Michael', lastName: 'Jackson' },
|
||||
{ firstName: 'John', lastName: 'Elton' },
|
||||
{ firstName: 'Harry', lastName: 'Potter' }
|
||||
]
|
||||
|
||||
function pickRandomMockMessage() {
|
||||
return mockMessageVariations[
|
||||
Math.floor(Math.random() * mockMessageVariations.length)
|
||||
]
|
||||
}
|
||||
|
||||
function pickRandomMockUserId() {
|
||||
return Math.ceil(Math.random() * mockUsers.length)
|
||||
}
|
||||
|
||||
async function seedMockUsersInDb() {
|
||||
const { rows } = await db.query('SELECT COUNT(*) user_count FROM "user"')
|
||||
const userCount = rows[0].user_count
|
||||
if (+userCount) return 'Users already exist'
|
||||
|
||||
await Promise.all(
|
||||
mockUsers.map(async user => {
|
||||
const text =
|
||||
'INSERT INTO "user"(username, first_name, last_name, email, bcrypt_hash) VALUES ($1, $2, $3, $4, \'dummyBcryptHash\') RETURNING *'
|
||||
const values = [
|
||||
`${user.firstName}_${user.lastName}`,
|
||||
user.firstName,
|
||||
user.lastName,
|
||||
`${user.firstName}.${user.lastName}@rsdo.com`
|
||||
]
|
||||
const { rows } = await db.query(text, values)
|
||||
const createdUser = rows[0]
|
||||
debug(`Created user: ${JSON.stringify(createdUser)}`)
|
||||
})
|
||||
)
|
||||
return 'Successfully seeded all users'
|
||||
}
|
||||
|
||||
async function seedPortalAdmin() {
|
||||
const {
|
||||
rows: [{ exists }]
|
||||
} = await db.query(
|
||||
"SELECT EXISTS (SELECT 1 FROM user_role WHERE role_name = 'portal admin')"
|
||||
)
|
||||
|
||||
if (exists) return 'Skipping creation of portal admin (already exists)'
|
||||
|
||||
const MOCK_ADMIN_BASE = 'admin'
|
||||
const adminUser = {
|
||||
username: MOCK_ADMIN_BASE,
|
||||
firstName: MOCK_ADMIN_BASE,
|
||||
lastName: MOCK_ADMIN_BASE,
|
||||
password: portalAdminInitialPassword,
|
||||
email: portalAdminInitialEmail
|
||||
}
|
||||
const userId = await User.create(adminUser)
|
||||
const assignAdminRole = db.query(
|
||||
`INSERT INTO user_role (user_id, role_name)
|
||||
VALUES
|
||||
($1, 'portal admin'),
|
||||
($1, 'dictionaries admin'),
|
||||
($1, 'consultancy admin'),
|
||||
($1, 'consultant')`,
|
||||
[userId]
|
||||
)
|
||||
const activateAdminUser = db.query(
|
||||
`UPDATE "user" SET status = 'active', time_activated = time_registered WHERE username = $1`,
|
||||
[adminUser.username]
|
||||
)
|
||||
await Promise.all([assignAdminRole, activateAdminUser])
|
||||
return `Successfully created portal admin (username: ${adminUser.username}, password: ${adminUser.password})`
|
||||
}
|
||||
|
||||
async function seedConsultants() {
|
||||
const {
|
||||
rows: [mockConsultancyAdmin]
|
||||
} = await db.query('SELECT id FROM "user" WHERE username = $1', ['cadmin'])
|
||||
|
||||
if (mockConsultancyAdmin) {
|
||||
return "Consultants already exist: 'cadmin', 'consultant1', 'consultant2', 'consultant3'"
|
||||
}
|
||||
|
||||
const cadminUser = {
|
||||
username: 'cadmin',
|
||||
firstName: 'cadmin',
|
||||
lastName: 'cadmin',
|
||||
password: 'cadmin',
|
||||
email: 'cadmin@rsdo.com'
|
||||
}
|
||||
const userId = await User.create(cadminUser)
|
||||
const assignConsultancyAdminRole = db.query(
|
||||
`INSERT INTO user_role (user_id, role_name)
|
||||
VALUES
|
||||
($1, 'consultancy admin')`,
|
||||
[userId]
|
||||
)
|
||||
const activateConsultancyAdminUser = db.query(
|
||||
`UPDATE "user" SET status = 'active', time_activated = time_registered WHERE username = $1`,
|
||||
[cadminUser.username]
|
||||
)
|
||||
await Promise.all([assignConsultancyAdminRole, activateConsultancyAdminUser])
|
||||
|
||||
for (let i = 1; i <= 3; i++) {
|
||||
const consultant = {
|
||||
username: `consultant${i}`,
|
||||
firstName: `consultant${i}`,
|
||||
lastName: `consultant${i}`,
|
||||
password: `consultant${i}`,
|
||||
email: `consultant${i}@rsdo.com`
|
||||
}
|
||||
const userId = await User.create(consultant)
|
||||
const assignConsultantRole = db.query(
|
||||
`INSERT INTO user_role (user_id, role_name)
|
||||
VALUES
|
||||
($1, 'consultant')`,
|
||||
[userId]
|
||||
)
|
||||
const activateConsultant = db.query(
|
||||
`UPDATE "user" SET status = 'active', time_activated = time_registered WHERE username = $1`,
|
||||
[consultant.username]
|
||||
)
|
||||
await Promise.all([assignConsultantRole, activateConsultant])
|
||||
}
|
||||
|
||||
return `Successfully seeded consultants 'cadmin', 'consultant1', 'consultant2', 'consultant3'`
|
||||
}
|
||||
|
||||
Comment.seedDummyData = () => {
|
||||
// Seed DB with mock users on empty DB.
|
||||
// seedMockUsersInDb()
|
||||
// .then(debug)
|
||||
// .catch(err => {
|
||||
// debug('Users not seeded.')
|
||||
// debug(err)
|
||||
// })
|
||||
|
||||
// Seed DB with mock portal admin user on empty DB.
|
||||
// TODO Replace with a more robust solution for production.
|
||||
seedPortalAdmin()
|
||||
.then(debug)
|
||||
.catch(err => {
|
||||
debug('Portal admin not seeded.')
|
||||
debug(err)
|
||||
})
|
||||
|
||||
// Seed DB with mock consultancy admin and consultant users on empty DB.
|
||||
// seedConsultants()
|
||||
// .then(debug)
|
||||
// .catch(err => {
|
||||
// debug('Consultants not seeded.')
|
||||
// debug(err)
|
||||
// })
|
||||
}
|
||||
|
||||
module.exports = Comment
|
||||
|
||||
@@ -55,19 +55,6 @@ class ConsultancyEntry {
|
||||
this.formattedTimePublished = formattedTimePublished
|
||||
}
|
||||
|
||||
// Fetch all consultancy entries from DB.
|
||||
static async fetchAll() {
|
||||
// TODO Luka: Miha, define specific fields instead of using *.
|
||||
const { rows: fetchedConsEntries } = await db.query(`
|
||||
SELECT *
|
||||
FROM consultancy_entry
|
||||
ORDER BY time_created DESC`)
|
||||
const deserializedConsEntries = fetchedConsEntries.map(
|
||||
consEntry => new this(consEntry)
|
||||
)
|
||||
return deserializedConsEntries
|
||||
}
|
||||
|
||||
// Fetch consultancy entry by ID
|
||||
// to_char(time_created,'HH24:MI:SS DD/MM/YYYY')
|
||||
// TODO i18n date format
|
||||
@@ -162,24 +149,6 @@ class ConsultancyEntry {
|
||||
return emails
|
||||
}
|
||||
|
||||
// Fetch all consultancy entries filtered by status from DB.
|
||||
static async fetchAllByStatus(status) {
|
||||
// TODO Luka: Miha, define specific fields instead of using *.
|
||||
const sqlQuery = `
|
||||
SELECT *, to_char(time_created, 'FMDD. FMMM. YYYY') formatted_time_created
|
||||
FROM consultancy_entry
|
||||
WHERE status=$1
|
||||
ORDER BY time_created DESC`
|
||||
|
||||
const values = [status]
|
||||
const { rows: fetchedConsEntries } = await db.query(sqlQuery, values)
|
||||
|
||||
const deserializedConsEntries = fetchedConsEntries.map(
|
||||
consEntry => new this(consEntry)
|
||||
)
|
||||
return deserializedConsEntries
|
||||
}
|
||||
|
||||
// Fetch all consultancy entries filtered by status from DB.
|
||||
static async fetchAllByStatusCount(status) {
|
||||
// TODO Luka: Miha, define specific fields instead of using *.
|
||||
@@ -311,12 +280,6 @@ class ConsultancyEntry {
|
||||
return deserializedConsEntries
|
||||
}
|
||||
|
||||
// Fetch all new consultancy entries from DB.
|
||||
static async fetchAllNew() {
|
||||
const newEntries = await this.fetchAllByStatus('new')
|
||||
return newEntries
|
||||
}
|
||||
|
||||
static async fetchAllRejected() {
|
||||
const newEntries = await this.fetchAllByStatusWithAuthorData('rejected')
|
||||
return newEntries
|
||||
@@ -438,20 +401,6 @@ class ConsultancyEntry {
|
||||
return rows[0]
|
||||
}
|
||||
|
||||
static async getEditors(entryId) {
|
||||
const sqlQuery = `
|
||||
SELECT u.id, u.first_name, u.last_name
|
||||
FROM "consultancy_entry" ce
|
||||
INNER JOIN "consultancy_entry_consultant" cec ON ce.id = cec.entry_id
|
||||
INNER JOIN "user" u ON u.id = cec.user_id
|
||||
WHERE ce.id=$1`
|
||||
|
||||
const values = [entryId]
|
||||
|
||||
const { rows } = await db.query(sqlQuery, values)
|
||||
return rows
|
||||
}
|
||||
|
||||
static async getSharedAuthorsArray(entryId) {
|
||||
const sqlQuery = `SELECT answer_authors FROM "consultancy_entry"
|
||||
WHERE id=$1;`
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
const db = require('./db')
|
||||
|
||||
const DemoPaginacija = {}
|
||||
|
||||
// Metoda za generacijo demo podatkov.
|
||||
DemoPaginacija.initDemoData = async () => {
|
||||
await db.query(`
|
||||
CREATE TABLE IF NOT EXISTS demo_paginacija (zanimivo TEXT, nezanimivo1 TEXT, nezanimivo2 TEXT);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF (SELECT COUNT(*) FROM demo_paginacija) = 0 THEN
|
||||
FOR stevec IN 1..9993 LOOP
|
||||
INSERT INTO demo_paginacija VALUES ('vrednost' || stevec, 'brezveze', 'tega res ne rabimo');
|
||||
END LOOP;
|
||||
END IF;
|
||||
END $$`)
|
||||
}
|
||||
|
||||
// Metoda za poizvedbo demo podatkov za določeno stran.
|
||||
DemoPaginacija.fetch = async (resultsPerPage, page) => {
|
||||
const {
|
||||
rows: [{ result }]
|
||||
} = await db.query(
|
||||
`
|
||||
SELECT jsonb_build_object(
|
||||
'pages_total', (
|
||||
SELECT CEIL(COUNT(*) / $1::float)
|
||||
FROM demo_paginacija
|
||||
),
|
||||
'results', ARRAY(
|
||||
SELECT jsonb_build_object(
|
||||
'zanimivo', zanimivo,
|
||||
'nezanimivo1', nezanimivo1,
|
||||
'nezanimivo2', nezanimivo2
|
||||
)
|
||||
FROM demo_paginacija
|
||||
LIMIT $1
|
||||
OFFSET $2
|
||||
)
|
||||
) result
|
||||
`,
|
||||
[resultsPerPage, resultsPerPage * (page - 1)]
|
||||
)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
module.exports = DemoPaginacija
|
||||
@@ -17,14 +17,18 @@ class Dictionary {
|
||||
// TODO Add (language sensitive) dictionary status text conversion.
|
||||
constructor({
|
||||
id,
|
||||
name,
|
||||
name_sl: nameSl,
|
||||
name_en: nameEn,
|
||||
time_modified: timeModified,
|
||||
status,
|
||||
count_entries: countEntries,
|
||||
count_comments: countComments
|
||||
}) {
|
||||
this.id = id
|
||||
this.name = name
|
||||
this.nameSl = nameSl
|
||||
this.nameEn = nameEn
|
||||
this.timeModified = timeModified
|
||||
this.status = status
|
||||
this.countEntries = countEntries
|
||||
@@ -32,13 +36,12 @@ class Dictionary {
|
||||
}
|
||||
|
||||
// Fetch all dictionaries from DB.
|
||||
// TODO i18n name_sl
|
||||
static async fetchAll() {
|
||||
static async fetchAll(determinedLanguage) {
|
||||
// TODO Implement SQL stored procedures or functions.
|
||||
const { rows: fetchedDictionaries } = await db.query(`
|
||||
SELECT
|
||||
id,
|
||||
name_sl,
|
||||
name_${determinedLanguage} name,
|
||||
time_modified,
|
||||
status,
|
||||
count_entries,
|
||||
@@ -51,36 +54,13 @@ class Dictionary {
|
||||
return deserializedDictionaries
|
||||
}
|
||||
|
||||
/*
|
||||
// Fetch all dictionaries from DB by it's ID.
|
||||
static async fetchAllByUser(id) {
|
||||
// TODO Implement SQL stored procedures or functions.
|
||||
const text = `
|
||||
SELECT
|
||||
id,
|
||||
name_sl,
|
||||
time_modified,
|
||||
status,
|
||||
count_entries,
|
||||
count_comments
|
||||
FROM dictionary
|
||||
WHERE id=$1
|
||||
`
|
||||
const values = [id]
|
||||
const { rows: fetchedDictionaries } = await db.query(text, values)
|
||||
const deserializedDictionary = new this(fetchedDictionaries[0])
|
||||
return deserializedDictionary
|
||||
}
|
||||
*/
|
||||
|
||||
// TODO i18n name_sl
|
||||
// Fetch all dictionaries from DB for which the user has at least one dictionary role.
|
||||
static async fetchAllByUser(userId) {
|
||||
static async fetchAllByUser(userId, determinedLanguage) {
|
||||
// TODO Implement SQL stored procedures or functions.
|
||||
const text = `
|
||||
SELECT
|
||||
id,
|
||||
name_sl,
|
||||
name_${determinedLanguage} name,
|
||||
time_modified,
|
||||
status,
|
||||
count_entries,
|
||||
@@ -107,16 +87,14 @@ class Dictionary {
|
||||
}
|
||||
|
||||
// Fetch all primary domains from DB.
|
||||
static async fetchAllPrimaryDomains() {
|
||||
static async fetchAllPrimaryDomains(determinedLanguage) {
|
||||
// TODO Implement SQL stored procedures or functions.
|
||||
const { rows: fetchedDomains } = await db.query(`
|
||||
SELECT id, name_sl, name_en
|
||||
SELECT id, name_${determinedLanguage} name
|
||||
FROM domain_primary
|
||||
ORDER BY id`)
|
||||
const deserializedDomains = fetchedDomains.map(domain =>
|
||||
deserialize.primaryDomain(domain)
|
||||
)
|
||||
return deserializedDomains
|
||||
ORDER BY name_${determinedLanguage}`)
|
||||
|
||||
return fetchedDomains
|
||||
}
|
||||
|
||||
// Fetch primary domain for a specific dictionary from DB.
|
||||
@@ -152,18 +130,16 @@ class Dictionary {
|
||||
}
|
||||
|
||||
// Fetch all languages from DB.
|
||||
static async fetchAllLanguages(lang, excludeSlovene) {
|
||||
static async fetchAllLanguages(determinedLanguage, excludeSlovene) {
|
||||
// TODO Implement SQL stored procedures or functions.
|
||||
const { rows: fetchedLanguages } = await db.query(`
|
||||
SELECT
|
||||
id,
|
||||
${lang}
|
||||
name_${determinedLanguage} name
|
||||
FROM language${excludeSlovene ? "\nWHERE code <> 'sl'" : ''}
|
||||
ORDER BY ${lang}`)
|
||||
const deserializedLanguages = fetchedLanguages.map(language =>
|
||||
deserialize.language(language)
|
||||
)
|
||||
return deserializedLanguages
|
||||
ORDER BY name_${determinedLanguage}`)
|
||||
|
||||
return fetchedLanguages
|
||||
}
|
||||
|
||||
// Create new dictionary in DB and assign the creator as its admin.
|
||||
@@ -304,11 +280,11 @@ class Dictionary {
|
||||
}
|
||||
|
||||
// Fetch single dictionary data for editing users from DB.
|
||||
static async fetchEditUsers(dictionaryId) {
|
||||
static async fetchEditUsers(dictionaryId, determinedLanguage) {
|
||||
const text = `
|
||||
SELECT
|
||||
id,
|
||||
name_sl,
|
||||
name_${determinedLanguage} name,
|
||||
entries_have_terminology_review_flag,
|
||||
entries_have_language_review_flag,
|
||||
status
|
||||
@@ -385,6 +361,7 @@ class Dictionary {
|
||||
SELECT
|
||||
id,
|
||||
name_sl,
|
||||
name_en,
|
||||
entries_have_domain_labels,
|
||||
entries_have_label,
|
||||
entries_have_definition,
|
||||
@@ -412,21 +389,19 @@ class Dictionary {
|
||||
}
|
||||
|
||||
// Fetch single dictionary data for viewing details about it
|
||||
static async fetchDictionaryBasicInfo(dictionaryId) {
|
||||
const isInEnglish = false
|
||||
const lang = isInEnglish ? 'd.name_en' : 'd.name_sl'
|
||||
static async fetchDictionaryBasicInfo(dictionaryId, determinedLanguage) {
|
||||
const text = `
|
||||
SELECT
|
||||
d.id,
|
||||
${lang} dictionarysl,
|
||||
d.name_${determinedLanguage} dictionarysl,
|
||||
d.count_entries,
|
||||
to_char(d.time_modified,'YYYY-MM-DD') time_modified,
|
||||
d.issn,
|
||||
d.author,
|
||||
dp.name_sl domain_primary,
|
||||
dp.name_${determinedLanguage} domain_primary,
|
||||
description,
|
||||
l.name_sl languageSl,
|
||||
ds.name_sl domainSecondarySl,
|
||||
l.name_${determinedLanguage} languageSl,
|
||||
ds.name_${determinedLanguage} domainSecondarySl,
|
||||
lp.name portalnamesl,
|
||||
lp.code portalcode
|
||||
FROM
|
||||
@@ -551,7 +526,8 @@ class Dictionary {
|
||||
resultsPerPage,
|
||||
page,
|
||||
orderType,
|
||||
orderIndex
|
||||
orderIndex,
|
||||
determinedLanguage
|
||||
) {
|
||||
let queryAppend = ''
|
||||
if (domainQuery.length > 0) {
|
||||
@@ -580,22 +556,21 @@ class Dictionary {
|
||||
}
|
||||
}
|
||||
|
||||
const isInEnglish = false
|
||||
const lang = isInEnglish ? 'name_en' : 'name_sl'
|
||||
|
||||
const orderBy = `${orderType}.${lang} ${orderIndex ? 'DESC' : 'ASC'}`
|
||||
const orderBy = `${orderType}.name_${determinedLanguage} ${
|
||||
orderIndex ? 'DESC' : 'ASC'
|
||||
}`
|
||||
|
||||
// TODO domain primary language toggle!
|
||||
const text = `
|
||||
SELECT
|
||||
distinct (d.id),
|
||||
d.${lang} dictionarysl,
|
||||
d.name_${determinedLanguage} dictionarysl,
|
||||
d.count_entries,
|
||||
d.count_comments,
|
||||
to_char(d.time_modified,'YYYY-MM-DD') time_modified,
|
||||
d.issn,
|
||||
d.author,
|
||||
dp.name_sl domain_primary,
|
||||
dp.name_${determinedLanguage} domain_primary,
|
||||
description,
|
||||
lp.name portalnamesl,
|
||||
lp.code portalcode
|
||||
@@ -605,7 +580,7 @@ class Dictionary {
|
||||
INNER JOIN domain_primary dp ON d.domain_primary_id = dp.id
|
||||
LEFT JOIN linked_dictionary ld ON ld.target_dictionary_id = d.id
|
||||
LEFT JOIN linked_portal lp ON ld.linked_portal_id = lp.id
|
||||
WHERE d.status = 'published' AND LOWER(d.name_sl) LIKE '%' || LOWER($1) || '%' ${queryAppend}
|
||||
WHERE d.status = 'published' AND LOWER(d.name_${determinedLanguage}) LIKE '%' || LOWER($1) || '%' ${queryAppend}
|
||||
ORDER BY ${orderBy}
|
||||
LIMIT $2
|
||||
OFFSET $3`
|
||||
@@ -737,13 +712,12 @@ class Dictionary {
|
||||
}
|
||||
|
||||
// Fetch languages associated with a single dictionary from DB.
|
||||
static async fetchLanguages(dictionaryId) {
|
||||
static async fetchLanguages(dictionaryId, determinedLanguage) {
|
||||
const text = `
|
||||
SELECT
|
||||
l.id,
|
||||
l.code,
|
||||
l.name_sl,
|
||||
l.name_en
|
||||
l.name_${determinedLanguage} name
|
||||
FROM dictionary d
|
||||
INNER JOIN dictionary_language dl ON d.id = dl.dictionary_id
|
||||
INNER JOIN language l ON dl.language_id = l.id
|
||||
@@ -753,20 +727,16 @@ class Dictionary {
|
||||
|
||||
const { rows: fetchedLanguages } = await db.query(text, value)
|
||||
|
||||
const deserializedLanguages = fetchedLanguages.map(language =>
|
||||
deserialize.language(language)
|
||||
)
|
||||
return deserializedLanguages
|
||||
return fetchedLanguages
|
||||
}
|
||||
|
||||
// Fetch latest 3 dictionaries by publish date
|
||||
static async fetchLatest3DictsByPublishDate(isInEnglish) {
|
||||
const lang = isInEnglish ? 'd.name_en' : 'd.name_sl'
|
||||
static async fetchLatest3DictsByPublishDate(determinedLanguage) {
|
||||
const text = `
|
||||
SELECT
|
||||
d.id,
|
||||
${lang} dictionarysl,
|
||||
dp.name_sl domain_primary,
|
||||
d.name_${determinedLanguage} dictionarysl,
|
||||
dp.name_${determinedLanguage} domain_primary,
|
||||
d.count_comments
|
||||
FROM dictionary d
|
||||
INNER JOIN domain_primary dp ON d.domain_primary_id = dp.id
|
||||
@@ -842,7 +812,11 @@ class Dictionary {
|
||||
await db.query(text, values)
|
||||
}
|
||||
|
||||
static async fetchAllAdminDictionaries(resultsPerPage, page) {
|
||||
static async fetchAllAdminDictionaries(
|
||||
determinedLanguage,
|
||||
resultsPerPage,
|
||||
page
|
||||
) {
|
||||
// TODO Implement SQL stored procedures or functions.
|
||||
const {
|
||||
rows: [{ result }]
|
||||
@@ -856,7 +830,7 @@ class Dictionary {
|
||||
'results', ARRAY(
|
||||
SELECT jsonb_build_object(
|
||||
'id', id,
|
||||
'name', name_sl,
|
||||
'name', name_${determinedLanguage},
|
||||
'timeCreated', time_created,
|
||||
'timeModified', time_modified,
|
||||
'status', status
|
||||
@@ -1162,12 +1136,12 @@ class Dictionary {
|
||||
}
|
||||
|
||||
// Fetch single dictionary's name from DB.
|
||||
static async fetchName(dictionaryId) {
|
||||
static async fetchName(dictionaryId, determinedLanguage) {
|
||||
const { rows } = await db.query(
|
||||
'SELECT name_sl FROM dictionary WHERE id = $1',
|
||||
`SELECT name_${determinedLanguage} name FROM dictionary WHERE id = $1`,
|
||||
[dictionaryId]
|
||||
)
|
||||
const dictionaryName = rows[0].name_sl
|
||||
const dictionaryName = rows[0].name
|
||||
return dictionaryName
|
||||
}
|
||||
|
||||
|
||||
+47
-93
@@ -1,7 +1,10 @@
|
||||
const db = require('./db')
|
||||
const { searchEngineClient, ENTRY_INDEX } = require('./search-engine')
|
||||
const { intoDbArray, getInstanceSetting, removeHtmlTags } = require('./helpers')
|
||||
const { prepareEntryForIndexing } = require('./helpers/dictionary')
|
||||
const {
|
||||
prepareEntryForIndexing,
|
||||
sanitizeField
|
||||
} = require('./helpers/dictionary')
|
||||
|
||||
const Entry = {}
|
||||
|
||||
@@ -10,7 +13,7 @@ Entry.create = async (userId, dictionaryId, entry) => {
|
||||
const pickedLinks = intoDbArray(entry.links, 'always')
|
||||
const pickedType = intoDbArray(entry.type, 'always')
|
||||
const links = pickedLinks.map((link, index) => ({
|
||||
link,
|
||||
link: sanitizeField.toMixedBasic(link),
|
||||
type: pickedType[index]
|
||||
}))
|
||||
const foreign = intoDbArray(entry.foreign, 'always')
|
||||
@@ -18,9 +21,13 @@ Entry.create = async (userId, dictionaryId, entry) => {
|
||||
if (row.term || row.definition || row.synonym) {
|
||||
agg.push({
|
||||
language: row.code,
|
||||
terms: intoDbArray(row.term, 'undefined'),
|
||||
definition: row.definition || null,
|
||||
synonyms: intoDbArray(row.synonym, 'undefined')
|
||||
terms: intoDbArray(row.term, 'undefined')?.map(term =>
|
||||
sanitizeField.toMixedBasic(term)
|
||||
),
|
||||
definition: sanitizeField.toMixedExtended(row.definition) || null,
|
||||
synonyms: intoDbArray(row.synonym, 'undefined')?.map(synonym =>
|
||||
sanitizeField.toMixedBasic(synonym)
|
||||
)
|
||||
})
|
||||
}
|
||||
return agg
|
||||
@@ -34,22 +41,26 @@ Entry.create = async (userId, dictionaryId, entry) => {
|
||||
dictionaryId,
|
||||
isValid,
|
||||
entry.status,
|
||||
entry.term || null,
|
||||
sanitizeField.toMixedBasic(entry.term) || null,
|
||||
userId,
|
||||
entry.homonymSort || null,
|
||||
entry.wordforms || null,
|
||||
entry.accent || null,
|
||||
entry.pronunciation,
|
||||
intoDbArray(entry.domainLabels, 'always'),
|
||||
entry.label || null,
|
||||
entry.definition || null,
|
||||
intoDbArray(entry.synonyms),
|
||||
entry.pronunciation || null,
|
||||
intoDbArray(entry.domainLabels, 'always').map(label =>
|
||||
sanitizeField.toText(label)
|
||||
),
|
||||
sanitizeField.toMixedExtended(entry.label) || null,
|
||||
sanitizeField.toMixedExtended(entry.definition) || null,
|
||||
intoDbArray(entry.synonyms)?.map(synonym =>
|
||||
sanitizeField.toMixedBasic(synonym)
|
||||
),
|
||||
links,
|
||||
entry.other || null,
|
||||
sanitizeField.toMixedOther(entry.other) || null,
|
||||
foreignLanguageContent,
|
||||
intoDbArray(entry.image),
|
||||
intoDbArray(entry.audio),
|
||||
intoDbArray(entry.video)
|
||||
intoDbArray(entry.image)?.map(image => sanitizeField.toText(image)),
|
||||
intoDbArray(entry.audio)?.map(audio => sanitizeField.toText(audio)),
|
||||
intoDbArray(entry.video)?.map(video => sanitizeField.toText(video))
|
||||
]
|
||||
const text = `SELECT entry_new (${db.genParamStr(values)})`
|
||||
|
||||
@@ -60,71 +71,6 @@ Entry.create = async (userId, dictionaryId, entry) => {
|
||||
return entryId
|
||||
}
|
||||
|
||||
// // Fetch all entry terms of a single dictionary from DB.
|
||||
// Entry.fetchAll = async dictionaryId => {
|
||||
// const text = `
|
||||
// SELECT
|
||||
// e.id,
|
||||
// e.is_valid as valid,
|
||||
// e.is_published as published,
|
||||
// e.term as term,
|
||||
// MAX(ef.term) as fterm,
|
||||
// CASE
|
||||
// WHEN NOW() - e.time_most_recent_comment < INTERVAL '1 week' THEN 'T'
|
||||
// WHEN NOW() - e.time_most_recent_comment < INTERVAL '1 month' THEN 'M'
|
||||
// WHEN NOW() - e.time_most_recent_comment < INTERVAL '1 year' THEN 'L'
|
||||
// ELSE ''
|
||||
// END comment_age
|
||||
// FROM entry e
|
||||
// LEFT JOIN entry_foreign ef ON e.id = ef.entry_id
|
||||
// WHERE dictionary_id = $1
|
||||
// GROUP BY id, is_valid, is_published, e.term, comment_age
|
||||
// ORDER BY e.term`
|
||||
// const value = [dictionaryId]
|
||||
|
||||
// const { rows: fetchedTerms } = await db.query(text, value)
|
||||
// return fetchedTerms
|
||||
// }
|
||||
|
||||
// Metoda za poizvedbo demo podatkov za določeno stran.
|
||||
// Entry.fetchPaginated = async (resultsPerPage, page) => {
|
||||
// const {
|
||||
// rows: [{ result }]
|
||||
// } = await db.query(
|
||||
// `
|
||||
// SELECT jsonb_build_object(
|
||||
// 'pages_total', (
|
||||
// SELECT CEIL(COUNT(*) / $1::float)
|
||||
// FROM demo_paginacija
|
||||
// ),
|
||||
// 'results', ARRAY(
|
||||
// SELECT jsonb_build_object(
|
||||
// dictionary_id,
|
||||
// term,
|
||||
// is_published,
|
||||
// is_terminology_reviewed,
|
||||
// is_language_reviewed,
|
||||
// status,
|
||||
// label,
|
||||
// definition,
|
||||
// synonym,
|
||||
// other,
|
||||
// image,
|
||||
// audio,
|
||||
// video
|
||||
// )
|
||||
// FROM entry
|
||||
// LIMIT $1
|
||||
// OFFSET $2
|
||||
// )
|
||||
// ) result
|
||||
// `,
|
||||
// [resultsPerPage, resultsPerPage * (page - 1)]
|
||||
// )
|
||||
|
||||
// return result
|
||||
// }
|
||||
|
||||
// Fetch all data, related to single entry from DB.
|
||||
Entry.fetchFull = async entryId => {
|
||||
const text = `
|
||||
@@ -565,7 +511,7 @@ Entry.update = async (userId, entry) => {
|
||||
const pickedLinks = intoDbArray(entry.links, 'always')
|
||||
const pickedType = intoDbArray(entry.type, 'always')
|
||||
const links = pickedLinks.map((link, index) => ({
|
||||
link,
|
||||
link: sanitizeField.toMixedBasic(link),
|
||||
type: pickedType[index]
|
||||
}))
|
||||
const foreign = intoDbArray(entry.foreign, 'always')
|
||||
@@ -573,9 +519,13 @@ Entry.update = async (userId, entry) => {
|
||||
if (row.term || row.definition || row.synonym) {
|
||||
agg.push({
|
||||
language: row.code,
|
||||
terms: intoDbArray(row.term, 'undefined'),
|
||||
definition: row.definition || null,
|
||||
synonyms: intoDbArray(row.synonym, 'undefined')
|
||||
terms: intoDbArray(row.term, 'undefined')?.map(term =>
|
||||
sanitizeField.toMixedBasic(term)
|
||||
),
|
||||
definition: sanitizeField.toMixedExtended(row.definition) || null,
|
||||
synonyms: intoDbArray(row.synonym, 'undefined')?.map(synonym =>
|
||||
sanitizeField.toMixedBasic(synonym)
|
||||
)
|
||||
})
|
||||
}
|
||||
return agg
|
||||
@@ -591,19 +541,23 @@ Entry.update = async (userId, entry) => {
|
||||
!!entry.isTerminologyReviewed,
|
||||
!!entry.isLanguageReviewed,
|
||||
entry.status,
|
||||
entry.term || null,
|
||||
sanitizeField.toMixedBasic(entry.term) || null,
|
||||
userId,
|
||||
entry.homonymSort || null,
|
||||
intoDbArray(entry.domainLabels, 'always'),
|
||||
entry.label || null,
|
||||
entry.definition || null,
|
||||
intoDbArray(entry.synonyms),
|
||||
intoDbArray(entry.domainLabels, 'always').map(label =>
|
||||
sanitizeField.toText(label)
|
||||
),
|
||||
sanitizeField.toMixedExtended(entry.label) || null,
|
||||
sanitizeField.toMixedExtended(entry.definition) || null,
|
||||
intoDbArray(entry.synonyms)?.map(synonym =>
|
||||
sanitizeField.toMixedBasic(synonym)
|
||||
),
|
||||
links,
|
||||
entry.other || null,
|
||||
sanitizeField.toMixedOther(entry.other) || null,
|
||||
foreignLanguageContent,
|
||||
intoDbArray(entry.image),
|
||||
intoDbArray(entry.audio),
|
||||
intoDbArray(entry.video)
|
||||
intoDbArray(entry.image)?.map(image => sanitizeField.toText(image)),
|
||||
intoDbArray(entry.audio)?.map(audio => sanitizeField.toText(audio)),
|
||||
intoDbArray(entry.video)?.map(video => sanitizeField.toText(video))
|
||||
]
|
||||
const text = `SELECT entry_update (${db.genParamStr(values)})`
|
||||
|
||||
|
||||
+124
-29
@@ -68,13 +68,22 @@ Extraction.fetch = async id => {
|
||||
const {
|
||||
rows: [fetchedExtraction]
|
||||
} = await db.query(
|
||||
'SELECT id, name, status, corpus_id, oss_params, time_started, time_finished FROM extraction WHERE id = $1',
|
||||
'SELECT id, user_id, name, status, corpus_id, oss_params, time_started, time_finished FROM extraction WHERE id = $1',
|
||||
[id]
|
||||
)
|
||||
|
||||
return deserialize.extraction(fetchedExtraction)
|
||||
}
|
||||
|
||||
// Fetch oss document types from DB.
|
||||
Extraction.fetchOssDocumentTypes = async determinedLanguage => {
|
||||
const { rows: fetchedDocumentTypes } = await db.query(
|
||||
`SELECT id, name_${determinedLanguage} name FROM extraction_oss_document_types`
|
||||
)
|
||||
|
||||
return fetchedDocumentTypes
|
||||
}
|
||||
|
||||
// Fetch data of the author of a specific extraction entry from DB.
|
||||
Extraction.fetchAuthorData = async id => {
|
||||
const {
|
||||
@@ -249,21 +258,29 @@ Extraction.processOwn = async function (extractionId, extractionName) {
|
||||
const conllusPath = getConllusPath(extractionId)
|
||||
const conllusPaths = []
|
||||
const MAX_BODY_LENGTH = 10 ** 9 // 1 GB
|
||||
const RETRY_SECONDS_INTERVAL = 60 // 1 minute
|
||||
const RETRY_SECONDS_MAX = 60 * 60 * 24 // 1 day
|
||||
// Using remote API, transform each document into conllu format.
|
||||
for (const documentName of documentNames) {
|
||||
const filePath = `${documentsPath}/${documentName}`
|
||||
const form = new FormData()
|
||||
form.append('file', createReadStream(filePath), documentName)
|
||||
try {
|
||||
const { data: data1 } = await axios.post(
|
||||
`${extractionApiOrigin}/datotekaVConlluAsync`,
|
||||
form,
|
||||
{
|
||||
headers: {
|
||||
...form.getHeaders()
|
||||
},
|
||||
maxBodyLength: MAX_BODY_LENGTH
|
||||
}
|
||||
const { data: data1 } = await retry(
|
||||
async () => {
|
||||
const form = new FormData()
|
||||
form.append('file', createReadStream(filePath), documentName)
|
||||
return await axios.post(
|
||||
`${extractionApiOrigin}/datotekaVConlluAsync`,
|
||||
form,
|
||||
{
|
||||
headers: {
|
||||
...form.getHeaders()
|
||||
},
|
||||
maxBodyLength: MAX_BODY_LENGTH
|
||||
}
|
||||
)
|
||||
},
|
||||
RETRY_SECONDS_INTERVAL,
|
||||
RETRY_SECONDS_MAX
|
||||
)
|
||||
const remotejobId = +data1.check_job_url.split('/').at(-1)
|
||||
await db.query(
|
||||
@@ -276,9 +293,14 @@ Extraction.processOwn = async function (extractionId, extractionName) {
|
||||
// Poll job until finished.
|
||||
while (true) {
|
||||
await sleep(5)
|
||||
const { data: data2 } = await axios.get(
|
||||
`${extractionApiOrigin}/job/${remotejobId}`
|
||||
const { data: data2 } = await retry(
|
||||
async () => {
|
||||
return await axios.get(`${extractionApiOrigin}/job/${remotejobId}`)
|
||||
},
|
||||
RETRY_SECONDS_INTERVAL,
|
||||
RETRY_SECONDS_MAX
|
||||
)
|
||||
|
||||
if (data2.finished_on) {
|
||||
if (data2.job_status !== 'finished processing (OK)') {
|
||||
throw Error(
|
||||
@@ -326,18 +348,25 @@ Extraction.processOwn = async function (extractionId, extractionName) {
|
||||
stopTerms.forEach(stopTerm => stopTermsSet.add(stopTerm.trim()))
|
||||
}
|
||||
stopTermsSet.delete('')
|
||||
const stopTermsArr = Array.from(stopTermsSet)
|
||||
const termCandidatesPath = getTermCandidatesPath(extractionId)
|
||||
|
||||
try {
|
||||
const { data: data3 } = await axios.post(
|
||||
`${extractionApiOrigin}/izlusciAsync`,
|
||||
{
|
||||
conllus: conllusArr,
|
||||
prepovedaneBesede: Array.from(stopTermsSet),
|
||||
// TODO Enabled for all cases. Add a switch for users later.
|
||||
definicije: true
|
||||
const { data: data3 } = await retry(
|
||||
async () => {
|
||||
return await axios.post(
|
||||
`${extractionApiOrigin}/izlusciAsync`,
|
||||
{
|
||||
conllus: conllusArr,
|
||||
prepovedaneBesede: stopTermsArr,
|
||||
// TODO Enabled for all cases. Add a switch for users later.
|
||||
definicije: true
|
||||
},
|
||||
{ maxBodyLength: MAX_BODY_LENGTH }
|
||||
)
|
||||
},
|
||||
{ maxBodyLength: MAX_BODY_LENGTH }
|
||||
RETRY_SECONDS_INTERVAL,
|
||||
RETRY_SECONDS_MAX
|
||||
)
|
||||
const remotejobId = +data3.check_job_url.split('/').at(-1)
|
||||
await db.query(
|
||||
@@ -348,8 +377,12 @@ Extraction.processOwn = async function (extractionId, extractionName) {
|
||||
// Poll job until finished.
|
||||
while (true) {
|
||||
await sleep(5)
|
||||
const { data: data4 } = await axios.get(
|
||||
`${extractionApiOrigin}/job/${remotejobId}`
|
||||
const { data: data4 } = await retry(
|
||||
async () => {
|
||||
return await axios.get(`${extractionApiOrigin}/job/${remotejobId}`)
|
||||
},
|
||||
RETRY_SECONDS_INTERVAL,
|
||||
RETRY_SECONDS_MAX
|
||||
)
|
||||
if (data4.finished_on) {
|
||||
const { job_result: jobResult } = data4
|
||||
@@ -395,6 +428,11 @@ Extraction.processOwn = async function (extractionId, extractionName) {
|
||||
title: extractionName
|
||||
})
|
||||
|
||||
await db.query('UPDATE extraction SET corpus_id = $1 WHERE id = $2', [
|
||||
corpusId,
|
||||
extractionId
|
||||
])
|
||||
|
||||
// Wait for creation of corpus.
|
||||
while (true) {
|
||||
console.log('SLEEP FOR 5 SECS')
|
||||
@@ -484,8 +522,8 @@ Extraction.processOwn = async function (extractionId, extractionName) {
|
||||
)
|
||||
|
||||
await db.query(
|
||||
"UPDATE extraction SET status = 'finished', time_finished = NOW(), corpus_id = $1 WHERE id = $2",
|
||||
[corpusId, extractionId]
|
||||
"UPDATE extraction SET status = 'finished', time_finished = NOW() WHERE id = $1",
|
||||
[extractionId]
|
||||
)
|
||||
console.log('EXTRACTION SUCCESSFUL')
|
||||
} catch (error) {
|
||||
@@ -503,6 +541,8 @@ Extraction.processOss = async function (extractionId, ossParams) {
|
||||
// TODO Probably not, at least not while the the OSS enpoint is GET, due to limited length of URLs.
|
||||
// TODO Also consider refactoring certain parts,
|
||||
// TODO as some are identical or similar to Own variants or used earlier in the same pipeline.
|
||||
const RETRY_SECONDS_INTERVAL = 60 // 1 minute
|
||||
const RETRY_SECONDS_MAX = 60 * 60 * 24 // 1 day
|
||||
const stopTermsPath = getStopTermsPath(extractionId)
|
||||
const stopTermsFilesNames = await this.fetchAllStopTermsFilesNames(
|
||||
extractionId
|
||||
@@ -532,7 +572,13 @@ Extraction.processOss = async function (extractionId, ossParams) {
|
||||
|
||||
const extractApiUrl = `${extractionApiOrigin}/oss/izlusciPoIskanjuAsync?${searchParams}`
|
||||
try {
|
||||
const { data: data1 } = await axios.get(extractApiUrl)
|
||||
const { data: data1 } = await retry(
|
||||
async () => {
|
||||
return await axios.get(extractApiUrl)
|
||||
},
|
||||
RETRY_SECONDS_INTERVAL,
|
||||
RETRY_SECONDS_MAX
|
||||
)
|
||||
const remotejobId = +data1.check_job_url.split('/').at(-1)
|
||||
await db.query(
|
||||
"UPDATE extraction_job SET status = 'in progress', remote_job_id = $1, time_started = NOW() WHERE extraction_id = $2 AND job_type = $3 AND filename = $4",
|
||||
@@ -542,8 +588,12 @@ Extraction.processOss = async function (extractionId, ossParams) {
|
||||
// Poll job until finished.
|
||||
while (true) {
|
||||
await sleep(5)
|
||||
const { data: data2 } = await axios.get(
|
||||
`${extractionApiOrigin}/job/${remotejobId}`
|
||||
const { data: data2 } = await retry(
|
||||
async () => {
|
||||
return await axios.get(`${extractionApiOrigin}/job/${remotejobId}`)
|
||||
},
|
||||
RETRY_SECONDS_INTERVAL,
|
||||
RETRY_SECONDS_MAX
|
||||
)
|
||||
if (data2.finished_on) {
|
||||
if (
|
||||
@@ -618,4 +668,49 @@ function logExtractionError(error, extractionId, jobType, filename) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
async function retry(callback, everySeconds, maxSeconds) {
|
||||
const startTime = new Date()
|
||||
let numOfRetries = 0
|
||||
|
||||
/* eslint-disable no-console */
|
||||
while (true) {
|
||||
try {
|
||||
const result = await callback()
|
||||
if (numOfRetries) {
|
||||
console.log(
|
||||
`Recovered after ${numOfRetries} retries and ${Math.floor(
|
||||
(new Date() - startTime) / 1000
|
||||
)} seconds`
|
||||
)
|
||||
}
|
||||
return result
|
||||
} catch (error) {
|
||||
const secondsSinceStart = Math.floor((new Date() - startTime) / 1000)
|
||||
const nextRetrySeconds = secondsSinceStart + everySeconds
|
||||
|
||||
console.log('Failed inside retry')
|
||||
console.log(
|
||||
error.isAxiosError ? `Axios message: ${error.message}` : error
|
||||
)
|
||||
console.log({
|
||||
numOfRetries,
|
||||
secondsSinceStart,
|
||||
everySeconds,
|
||||
nextRetrySeconds,
|
||||
maxSeconds
|
||||
})
|
||||
|
||||
if (nextRetrySeconds > maxSeconds) {
|
||||
console.log('FAILING RETRIES')
|
||||
throw error
|
||||
}
|
||||
|
||||
console.log(`RETRYING IN ${everySeconds} SECONDS`)
|
||||
numOfRetries++
|
||||
await sleep(everySeconds)
|
||||
}
|
||||
}
|
||||
/* eslint-enable no-console */
|
||||
}
|
||||
|
||||
module.exports = Extraction
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
const fs = require('fs')
|
||||
const xmlFlow = require('xml-flow')
|
||||
const xss = require('xss')
|
||||
const debug = require('debug')('termPortal:models/helpers/dictionary')
|
||||
const db = require('../../db')
|
||||
const { intoDbArray } = require('..')
|
||||
const { sanitizeField } = require('./index')
|
||||
|
||||
const JOBS_MAX = 50
|
||||
const JOBS_MIN = 15
|
||||
@@ -199,84 +199,18 @@ function handleEntryXml(
|
||||
}
|
||||
}
|
||||
|
||||
const markupFilter = {
|
||||
noMixed: new xss.FilterXSS({
|
||||
whiteList: {},
|
||||
stripIgnoreTag: true,
|
||||
stripIgnoreTagBody: ['script', 'style']
|
||||
}),
|
||||
|
||||
mixedBasic: new xss.FilterXSS({
|
||||
whiteList: {
|
||||
sup: [],
|
||||
sub: []
|
||||
},
|
||||
stripIgnoreTag: true,
|
||||
stripIgnoreTagBody: ['script', 'style']
|
||||
}),
|
||||
|
||||
mixedExtended: new xss.FilterXSS({
|
||||
whiteList: {
|
||||
sup: [],
|
||||
sub: [],
|
||||
b: [],
|
||||
i: [],
|
||||
a: ['href']
|
||||
},
|
||||
stripIgnoreTag: true,
|
||||
stripIgnoreTagBody: ['script', 'style'],
|
||||
onTag: customTagHandler
|
||||
}),
|
||||
|
||||
mixedOther: new xss.FilterXSS({
|
||||
whiteList: {
|
||||
sup: [],
|
||||
sub: [],
|
||||
b: [],
|
||||
i: [],
|
||||
a: ['href'],
|
||||
br: []
|
||||
},
|
||||
stripIgnoreTag: true,
|
||||
stripIgnoreTagBody: ['script', 'style'],
|
||||
onTag: customTagHandler
|
||||
})
|
||||
}
|
||||
|
||||
function customTagHandler(tag, html, { isWhite, isClosing }) {
|
||||
// Special treatment only for whitelisted opening anchor tags.
|
||||
if (tag !== 'a' || !isWhite || isClosing) return
|
||||
|
||||
const matchUrl = html.match(/href="?(?<url>https?:\/\/.*?)"?[\s>]/)
|
||||
const url = matchUrl ? xss.escapeAttrValue(matchUrl.groups.url) : undefined
|
||||
|
||||
return `<a href="${url || ''}" target="_blank">`
|
||||
}
|
||||
|
||||
function toText(markupObj) {
|
||||
return markupFilter.noMixed
|
||||
.process(xmlFlow.toXml(markupObj))
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim()
|
||||
return sanitizeField.toText(xmlFlow.toXml(markupObj))
|
||||
}
|
||||
|
||||
function toMixedBasic(markupObj) {
|
||||
return markupFilter.mixedBasic
|
||||
.process(xmlFlow.toXml(markupObj))
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim()
|
||||
return sanitizeField.toMixedBasic(xmlFlow.toXml(markupObj))
|
||||
}
|
||||
|
||||
function toMixedExtended(markupObj) {
|
||||
return markupFilter.mixedExtended
|
||||
.process(xmlFlow.toXml(markupObj))
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim()
|
||||
return sanitizeField.toMixedExtended(xmlFlow.toXml(markupObj))
|
||||
}
|
||||
|
||||
function toMixedOther(markupObj) {
|
||||
return markupFilter.mixedOther
|
||||
.process(xmlFlow.toXml(markupObj))
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim()
|
||||
return sanitizeField.toMixedOther(xmlFlow.toXml(markupObj))
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
const xss = require('xss')
|
||||
const { removeHtmlTags } = require('../../helpers')
|
||||
const { searchEngineClient, ENTRY_INDEX } = require('../../search-engine')
|
||||
const { DATA_FILES_PATH } = require('../../../config/settings')
|
||||
@@ -6,7 +7,7 @@ exports.deserialize = {
|
||||
dictionary(dictionary) {
|
||||
const deserializedDictionary = {
|
||||
id: dictionary.id,
|
||||
nameSl: dictionary.name_sl,
|
||||
name: dictionary.name,
|
||||
timeModified: dictionary.time_modified,
|
||||
status: dictionary.status,
|
||||
countEntries: dictionary.count_entries,
|
||||
@@ -48,16 +49,16 @@ exports.deserialize = {
|
||||
return deserializedDomain
|
||||
},
|
||||
|
||||
language(language) {
|
||||
const deserializedLanguage = {
|
||||
id: language.id,
|
||||
code: language.code,
|
||||
nameSl: language.name_sl,
|
||||
nameEn: language.name_en
|
||||
}
|
||||
// language(language) {
|
||||
// const deserializedLanguage = {
|
||||
// id: language.id,
|
||||
// code: language.code,
|
||||
// nameSl: language.name_sl,
|
||||
// nameEn: language.name_en
|
||||
// }
|
||||
|
||||
return deserializedLanguage
|
||||
},
|
||||
// return deserializedLanguage
|
||||
// },
|
||||
|
||||
editDescription(dictionary) {
|
||||
const deserializedDictionary = {
|
||||
@@ -77,7 +78,7 @@ exports.deserialize = {
|
||||
editUsers(dictionary) {
|
||||
const deserializedDictionary = {
|
||||
id: dictionary.id,
|
||||
nameSl: dictionary.name_sl,
|
||||
name: dictionary.name,
|
||||
terminologyReviewFlag: dictionary.entries_have_terminology_review_flag,
|
||||
languageReviewFlag: dictionary.entries_have_language_review_flag,
|
||||
status: dictionary.status
|
||||
@@ -90,6 +91,7 @@ exports.deserialize = {
|
||||
const deserializedDictionary = {
|
||||
id: dictionary.id,
|
||||
nameSl: dictionary.name_sl,
|
||||
nameEn: dictionary.name_en,
|
||||
hasDomainLabels: dictionary.entries_have_domain_labels,
|
||||
hasLabel: dictionary.entries_have_label,
|
||||
hasDefinition: dictionary.entries_have_definition,
|
||||
@@ -275,3 +277,79 @@ exports.prepareEntryForIndexing = prepareEntryForIndexing
|
||||
exports.getExportFilesPath = dictId => {
|
||||
return `${DATA_FILES_PATH}/dict_export/${dictId}`
|
||||
}
|
||||
|
||||
const markupFilter = {
|
||||
noMixed: new xss.FilterXSS({
|
||||
whiteList: {},
|
||||
stripIgnoreTag: true,
|
||||
stripIgnoreTagBody: ['script', 'style']
|
||||
}),
|
||||
|
||||
mixedBasic: new xss.FilterXSS({
|
||||
whiteList: {
|
||||
sup: [],
|
||||
sub: []
|
||||
},
|
||||
stripIgnoreTag: true,
|
||||
stripIgnoreTagBody: ['script', 'style']
|
||||
}),
|
||||
|
||||
mixedExtended: new xss.FilterXSS({
|
||||
whiteList: {
|
||||
sup: [],
|
||||
sub: [],
|
||||
b: [],
|
||||
i: [],
|
||||
a: ['href']
|
||||
},
|
||||
stripIgnoreTag: true,
|
||||
stripIgnoreTagBody: ['script', 'style'],
|
||||
onTag: customTagHandler
|
||||
}),
|
||||
|
||||
mixedOther: new xss.FilterXSS({
|
||||
whiteList: {
|
||||
sup: [],
|
||||
sub: [],
|
||||
b: [],
|
||||
i: [],
|
||||
a: ['href'],
|
||||
br: []
|
||||
},
|
||||
stripIgnoreTag: true,
|
||||
stripIgnoreTagBody: ['script', 'style'],
|
||||
onTag: customTagHandler
|
||||
})
|
||||
}
|
||||
|
||||
function customTagHandler(tag, html, { isWhite, isClosing }) {
|
||||
// Special treatment only for whitelisted opening anchor tags.
|
||||
if (tag !== 'a' || !isWhite || isClosing) return
|
||||
|
||||
const matchUrl = html.match(/href="?(?<url>https?:\/\/.*?)"?[\s>]/)
|
||||
const url = matchUrl ? xss.escapeAttrValue(matchUrl.groups.url) : undefined
|
||||
|
||||
return `<a href="${url || ''}" target="_blank">`
|
||||
}
|
||||
|
||||
function sanitize(string, filter) {
|
||||
return filter.process(string).replace(/\s+/g, ' ').trim()
|
||||
}
|
||||
|
||||
exports.sanitizeField = {
|
||||
toText(string) {
|
||||
return sanitize(string, markupFilter.noMixed)
|
||||
},
|
||||
|
||||
toMixedBasic(string) {
|
||||
return sanitize(string, markupFilter.mixedBasic)
|
||||
},
|
||||
|
||||
toMixedExtended(string) {
|
||||
return sanitize(string, markupFilter.mixedExtended)
|
||||
},
|
||||
|
||||
toMixedOther(string) {
|
||||
return sanitize(string, markupFilter.mixedOther)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ exports.deserialize = {
|
||||
extraction(extraction) {
|
||||
const deserializedExtraction = {
|
||||
id: extraction.id,
|
||||
userId: extraction.user_id,
|
||||
name: extraction.name,
|
||||
status: extraction.status,
|
||||
corpusId: extraction.corpus_id,
|
||||
|
||||
@@ -50,7 +50,7 @@ exports.prepareEntries = hits => {
|
||||
}
|
||||
|
||||
// Transform search engine's aggregation raw output into correct and friendly format.
|
||||
exports.prepareAggregation = async aggregationRaw => {
|
||||
exports.prepareAggregation = async (aggregationRaw, determinedLanguage) => {
|
||||
const { aggregations, hits } = aggregationRaw.body
|
||||
|
||||
const hitsCount = hits.total.value
|
||||
@@ -107,7 +107,8 @@ exports.prepareAggregation = async aggregationRaw => {
|
||||
const names = await Portal.getSearchAggregateNames(
|
||||
primaryDomainIds,
|
||||
dictionaryIds,
|
||||
languageIds
|
||||
languageIds,
|
||||
determinedLanguage
|
||||
)
|
||||
|
||||
const aggregation = {
|
||||
|
||||
@@ -6,6 +6,7 @@ exports.deserialize = {
|
||||
firstName: user.first_name,
|
||||
lastName: user.last_name,
|
||||
email: user.email,
|
||||
status: user.status,
|
||||
hitsPerPage: user.hits_per_page,
|
||||
language: user.language,
|
||||
userRoles: user.user_roles,
|
||||
|
||||
@@ -455,27 +455,28 @@ Portal.getSlovenianLanguageId = async () => {
|
||||
Portal.getSearchAggregateNames = async (
|
||||
primaryDomainIds,
|
||||
dictionaryIds,
|
||||
languageIds
|
||||
languageIds,
|
||||
determinedLanguage
|
||||
) => {
|
||||
const text = `
|
||||
SELECT jsonb_build_object(
|
||||
'primaryDomains', jsonb_object(
|
||||
ARRAY(
|
||||
SELECT ARRAY [id, name_sl]::TEXT[]
|
||||
SELECT ARRAY [id, name_${determinedLanguage}]::TEXT[]
|
||||
FROM domain_primary
|
||||
WHERE id = ANY ($1)
|
||||
)
|
||||
),
|
||||
'dictionaries', jsonb_object(
|
||||
ARRAY(
|
||||
SELECT ARRAY [id, name_sl]::TEXT[]
|
||||
SELECT ARRAY [id, name_${determinedLanguage}]::TEXT[]
|
||||
FROM dictionary
|
||||
WHERE id = ANY ($2)
|
||||
)
|
||||
),
|
||||
'languages', jsonb_object(
|
||||
ARRAY(
|
||||
SELECT ARRAY [id, name_sl]::TEXT[]
|
||||
SELECT ARRAY [id, name_${determinedLanguage}]::TEXT[]
|
||||
FROM language
|
||||
WHERE id = ANY ($3)
|
||||
)
|
||||
|
||||
+314
-38
@@ -1,13 +1,60 @@
|
||||
const { randomBytes } = require('crypto')
|
||||
const { promisify } = require('util')
|
||||
const RandomBytesAsync = promisify(randomBytes)
|
||||
const db = require('./db')
|
||||
const bcrypt = require('bcrypt')
|
||||
const uid = require('uid-safe')
|
||||
const { deserialize } = require('./helpers/user')
|
||||
const {
|
||||
ACTIVATION_TOKEN_VALID_DAYS,
|
||||
CHANGE_EMAIL_TOKEN_VALID_DAYS
|
||||
} = require('../config/settings')
|
||||
|
||||
const SALT_ROUNDS = 12
|
||||
const PASSWORD_RESET_VALID_INTERVAL = '1 day'
|
||||
|
||||
const User = {}
|
||||
|
||||
// Check if a user with provided email already exists.
|
||||
User.isEmailAlreadyTaken = async email => {
|
||||
const { rows } = await db.query('SELECT 1 FROM "user" WHERE email = $1', [
|
||||
email
|
||||
])
|
||||
|
||||
const isTaken = rows.length > 0
|
||||
|
||||
return isTaken
|
||||
}
|
||||
|
||||
// Create new user in DB.
|
||||
User.create = async user => {
|
||||
const SALT_ROUNDS = 12
|
||||
User.create = async (user, t) => {
|
||||
const {
|
||||
rows: [userWithSameEmail]
|
||||
} = await db.query('SELECT status FROM "user" WHERE email = $1', [user.email])
|
||||
|
||||
if (userWithSameEmail && userWithSameEmail.status !== 'registered') {
|
||||
const err = Error(t('Elektronski naslov uporablja že drug uporabnik.'))
|
||||
err.status = 400
|
||||
err.displayInProd = true
|
||||
|
||||
throw err
|
||||
}
|
||||
|
||||
const {
|
||||
rows: [isUsernameAlreadyTakenByOther]
|
||||
} = await db.query(
|
||||
'SELECT 1 FROM "user" WHERE username = $1 AND email <> $2',
|
||||
[user.username, user.email]
|
||||
)
|
||||
|
||||
if (isUsernameAlreadyTakenByOther) {
|
||||
const err = Error(t('Izbrano uporabniško ime uporablja že drug uporabnik.'))
|
||||
err.status = 400
|
||||
err.displayInProd = true
|
||||
|
||||
throw err
|
||||
}
|
||||
|
||||
const bcryptHash = await bcrypt.hash(user.password, SALT_ROUNDS)
|
||||
|
||||
const values = [
|
||||
@@ -19,7 +66,19 @@ User.create = async user => {
|
||||
]
|
||||
if (user.language) values.push(user.language)
|
||||
|
||||
const text = `INSERT INTO "user" (
|
||||
let text
|
||||
|
||||
if (userWithSameEmail) {
|
||||
text = `UPDATE "user" SET
|
||||
username = $1,
|
||||
first_name = $2,
|
||||
last_name = $3,
|
||||
bcrypt_hash = $5
|
||||
${user.language ? ', language = $6' : ''}
|
||||
WHERE email = $4
|
||||
RETURNING id`
|
||||
} else {
|
||||
text = `INSERT INTO "user" (
|
||||
username,
|
||||
first_name,
|
||||
last_name,
|
||||
@@ -29,6 +88,7 @@ User.create = async user => {
|
||||
)
|
||||
VALUES (${db.genParamStr(values)})
|
||||
RETURNING id`
|
||||
}
|
||||
|
||||
const { rows } = await db.query(text, values)
|
||||
|
||||
@@ -45,36 +105,42 @@ User.saveActivationToken = async (userId, activationToken) => {
|
||||
)
|
||||
}
|
||||
|
||||
// Fetch user from DB by (valid) activation token.
|
||||
User.fetchByActivationToken = async activationToken => {
|
||||
const TOKEN_VALID_PERIOD = '1 week'
|
||||
const text = `
|
||||
SELECT u.id
|
||||
FROM user_token_activation t
|
||||
INNER JOIN "user" u ON u.id = t.user_id
|
||||
WHERE
|
||||
t.token = $1
|
||||
AND AGE(NOW(), t.time_created) < INTERVAL '${TOKEN_VALID_PERIOD}'
|
||||
`
|
||||
const values = [activationToken]
|
||||
// Activate user account using the provided activation token.
|
||||
User.activateAccountWithToken = async (token, t) => {
|
||||
let user
|
||||
|
||||
const { rows } = await db.query(text, values)
|
||||
const user = rows[0]
|
||||
await db.transaction(async dbClient => {
|
||||
const { rows } = await dbClient.query(
|
||||
`SELECT user_id FROM user_token_activation WHERE token = $1 AND NOW() - time_created < '${ACTIVATION_TOKEN_VALID_DAYS} days'`,
|
||||
[token]
|
||||
)
|
||||
|
||||
// TODO Perhaps suggest to the user to request another one and make a shortcut.
|
||||
if (!user) throw Error('Povezava je neveljavna ali pa je že potekla')
|
||||
if (rows.length === 0) {
|
||||
const err = Error(
|
||||
t('Povezava ni (več) veljavna. Prosimo, da se ponovno registrirate.')
|
||||
)
|
||||
err.status = 403
|
||||
err.displayInProd = true
|
||||
|
||||
throw err
|
||||
}
|
||||
|
||||
const userId = rows[0].user_id
|
||||
;({
|
||||
rows: [user]
|
||||
} = await dbClient.query(
|
||||
`UPDATE "user" SET status = 'active', time_activated = NOW() WHERE id = $1 RETURNING id`,
|
||||
[userId]
|
||||
))
|
||||
|
||||
await dbClient.query('DELETE FROM user_token_activation WHERE token = $1', [
|
||||
token
|
||||
])
|
||||
})
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
// Activate user account.
|
||||
User.activateAccount = async user => {
|
||||
await db.query(
|
||||
`UPDATE "user" SET status = 'active', time_activated = NOW() WHERE id = $1`,
|
||||
[user.id]
|
||||
)
|
||||
}
|
||||
|
||||
// Generate a user remember me token.
|
||||
User.generateRememberMeToken = async () => {
|
||||
const token = await uid(32)
|
||||
@@ -96,6 +162,130 @@ User.clearRememberMeToken = async rememberMeToken => {
|
||||
])
|
||||
}
|
||||
|
||||
// Save a password reset token for a single user in DB.
|
||||
User.saveResetPasswordToken = async (userId, resetPasswordToken) => {
|
||||
await db.query(
|
||||
'INSERT INTO user_token_reset_password (token, user_id) VALUES ($1, $2)',
|
||||
[resetPasswordToken, userId]
|
||||
)
|
||||
}
|
||||
|
||||
// Check existance and validity of password reset token in DB.
|
||||
User.isResetPasswordTokenValid = async token => {
|
||||
const { rows } = await db.query(
|
||||
`SELECT 1 exists FROM user_token_reset_password WHERE token = $1 AND NOW() - time_created < '${PASSWORD_RESET_VALID_INTERVAL}'`,
|
||||
[token]
|
||||
)
|
||||
const isValid = rows.length > 0
|
||||
|
||||
return isValid
|
||||
}
|
||||
|
||||
// Set new password for user using the provided reset password token.
|
||||
User.resetPasswordWithToken = async (token, password, t) => {
|
||||
let user
|
||||
|
||||
await db.transaction(async dbClient => {
|
||||
const { rows } = await dbClient.query(
|
||||
`SELECT user_id FROM user_token_reset_password WHERE token = $1 AND NOW() - time_created < '${PASSWORD_RESET_VALID_INTERVAL}'`,
|
||||
[token]
|
||||
)
|
||||
|
||||
if (rows.length === 0) {
|
||||
const err = Error(
|
||||
t(
|
||||
'Povezava ni (več) veljavna. Prosimo, da ponovno zahtevate ponastavitev gesla.'
|
||||
)
|
||||
)
|
||||
err.status = 403
|
||||
err.displayInProd = true
|
||||
|
||||
throw err
|
||||
}
|
||||
|
||||
const bcryptHash = await bcrypt.hash(password, SALT_ROUNDS)
|
||||
const userId = rows[0].user_id
|
||||
;({
|
||||
rows: [user]
|
||||
} = await dbClient.query(
|
||||
'UPDATE "user" SET bcrypt_hash = $1 WHERE id = $2 RETURNING id, username, email',
|
||||
[bcryptHash, userId]
|
||||
))
|
||||
|
||||
await dbClient.query(
|
||||
'DELETE FROM user_token_reset_password WHERE token = $1',
|
||||
[token]
|
||||
)
|
||||
})
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
// Save change email token for a single user in DB.
|
||||
User.saveChangeEmailToken = async (userId, changeEmailToken, newEmail) => {
|
||||
await db.query(
|
||||
'INSERT INTO user_token_change_email (token, user_id, new_email) VALUES ($1, $2, $3)',
|
||||
[changeEmailToken, userId, newEmail]
|
||||
)
|
||||
}
|
||||
|
||||
// Set new email for user using the provided change email token.
|
||||
User.changeEmailWithToken = async function (token, t) {
|
||||
let user
|
||||
|
||||
await db.transaction(async dbClient => {
|
||||
const { rows } = await dbClient.query(
|
||||
`SELECT user_id, new_email FROM user_token_change_email WHERE token = $1 AND NOW() - time_created < '${CHANGE_EMAIL_TOKEN_VALID_DAYS} days'`,
|
||||
[token]
|
||||
)
|
||||
|
||||
if (rows.length === 0) {
|
||||
const err = Error(
|
||||
t('Povezava ni (več) veljavna. Elektronski naslov ni bil spremenjen.')
|
||||
)
|
||||
err.status = 403
|
||||
err.displayInProd = true
|
||||
|
||||
throw err
|
||||
}
|
||||
|
||||
const { user_id: userId, new_email: newEmail } = rows[0]
|
||||
if (await this.isEmailAlreadyTaken(newEmail)) {
|
||||
const err = Error(t('Elektronski naslov uporablja že drug uporabnik.'))
|
||||
err.status = 403
|
||||
err.displayInProd = true
|
||||
|
||||
throw err
|
||||
}
|
||||
|
||||
;({
|
||||
rows: [user]
|
||||
} = await dbClient.query(
|
||||
'UPDATE "user" SET email = $1 WHERE id = $2 RETURNING id, username, email',
|
||||
[newEmail, userId]
|
||||
))
|
||||
|
||||
await dbClient.query(
|
||||
'DELETE FROM user_token_change_email WHERE token = $1',
|
||||
[token]
|
||||
)
|
||||
})
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
// Fetch user from DB by username or email.
|
||||
User.fetchByUsernameOrEmail = async usernameOrEmail => {
|
||||
const {
|
||||
rows: [user]
|
||||
} = await db.query(
|
||||
'SELECT id, username, email, status, bcrypt_hash FROM "user" WHERE username = $1 OR email = $1',
|
||||
[usernameOrEmail]
|
||||
)
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
// Fetch user data that should be available on every request from DB by id.
|
||||
User.fetchDeserializedDataById = async userId => {
|
||||
const text = `
|
||||
@@ -105,6 +295,7 @@ User.fetchDeserializedDataById = async userId => {
|
||||
u.first_name,
|
||||
u.last_name,
|
||||
u.email,
|
||||
u.status,
|
||||
u.hits_per_page,
|
||||
u.language,
|
||||
ARRAY(
|
||||
@@ -148,6 +339,7 @@ User.fetchAll = async (resultsPerPage, page) => {
|
||||
'pages_total', (
|
||||
SELECT CEIL(COUNT(*) / $1::float)
|
||||
FROM "user"
|
||||
WHERE status <> 'closed'
|
||||
),
|
||||
'results', ARRAY(
|
||||
SELECT jsonb_build_object(
|
||||
@@ -157,6 +349,7 @@ User.fetchAll = async (resultsPerPage, page) => {
|
||||
'status', status
|
||||
)
|
||||
FROM "user"
|
||||
WHERE status <> 'closed'
|
||||
ORDER BY username
|
||||
LIMIT $1
|
||||
OFFSET $2
|
||||
@@ -281,8 +474,12 @@ User.updateUser = async (userId, payload) => {
|
||||
const { rows } = await db.query(previousStatusText, [userId])
|
||||
const previousStatus = rows[0].status
|
||||
|
||||
if (previousStatus === 'closed') throw Error()
|
||||
|
||||
let statusValue
|
||||
let setTimeActivated = false
|
||||
if (previousStatus === 'registered') {
|
||||
setTimeActivated = !!payload.status
|
||||
statusValue = !payload.status ? 'registered' : 'active'
|
||||
} else statusValue = !payload.status ? 'inactive' : 'active'
|
||||
|
||||
@@ -293,6 +490,7 @@ User.updateUser = async (userId, payload) => {
|
||||
first_name = $3,
|
||||
last_name = $4,
|
||||
status = $5
|
||||
${setTimeActivated ? ', time_activated = NOW()' : ''}
|
||||
WHERE id = $1`
|
||||
|
||||
const values = [
|
||||
@@ -448,12 +646,9 @@ User.insertNewConsultantWithDomain = async (userId, domains) => {
|
||||
|
||||
// Insert new consultant role with domain of
|
||||
User.insertNewConsultantWithDomainByUsername = async (username, domains) => {
|
||||
const { rows } = await db.query(
|
||||
'SELECT id FROM "user" WHERE username = $1 or email = $1',
|
||||
[username]
|
||||
)
|
||||
const user = await User.fetchByUsernameOrEmail(username)
|
||||
|
||||
await User.insertNewConsultantWithDomain(rows[0].id, domains)
|
||||
await User.insertNewConsultantWithDomain(user.id, domains)
|
||||
}
|
||||
|
||||
// Remove consultant role
|
||||
@@ -471,13 +666,15 @@ User.fetchAllowedHitsPerPage = async () => {
|
||||
).rows.map(e => e.unnest)
|
||||
}
|
||||
|
||||
User.updateFirstNameAndLastName = async (username, firstName, lastName) => {
|
||||
return await db.query(
|
||||
`UPDATE "user"
|
||||
SET first_name=$2, last_name=$3
|
||||
WHERE username=$1;`,
|
||||
[username, firstName, lastName]
|
||||
User.updateFirstNameAndLastName = async (userId, firstName, LastName) => {
|
||||
const {
|
||||
rows: [{ email }]
|
||||
} = await db.query(
|
||||
'UPDATE "user" SET first_name = $1, last_name = $2 WHERE id = $3 RETURNING email',
|
||||
[firstName, LastName, userId]
|
||||
)
|
||||
|
||||
return email
|
||||
}
|
||||
|
||||
User.updateHitsPerPage = async (username, hitsPerPageAmount) => {
|
||||
@@ -497,4 +694,83 @@ User.updateLanguage = async (userId, languageCode) => {
|
||||
])
|
||||
}
|
||||
|
||||
// Change user's password.
|
||||
User.changePassword = async (userId, passwordOld, passwordNew, t) => {
|
||||
await db.transaction(async dbClient => {
|
||||
const {
|
||||
rows: [{ bcrypt_hash: bcryptHashOld }]
|
||||
} = await dbClient.query('SELECT bcrypt_hash FROM "user" WHERE id = $1', [
|
||||
userId
|
||||
])
|
||||
|
||||
const isOldPasswordCorrect = await bcrypt.compare(
|
||||
passwordOld,
|
||||
bcryptHashOld
|
||||
)
|
||||
|
||||
if (!isOldPasswordCorrect) {
|
||||
const err = Error(t('Nepravilno staro geslo.'))
|
||||
err.status = 403
|
||||
err.displayInProd = true
|
||||
|
||||
throw err
|
||||
}
|
||||
|
||||
const bcryptHashNew = await bcrypt.hash(passwordNew, SALT_ROUNDS)
|
||||
|
||||
await dbClient.query('UPDATE "user" SET bcrypt_hash = $1 WHERE id = $2', [
|
||||
bcryptHashNew,
|
||||
userId
|
||||
])
|
||||
})
|
||||
}
|
||||
|
||||
// Close user's account and anonymize any personal data.
|
||||
User.closeAccount = async userId => {
|
||||
const maskString = '#####'
|
||||
const randomString = (await RandomBytesAsync(10)).toString('hex')
|
||||
|
||||
const anonymizedUsername = randomString
|
||||
const anonymizedFirstName = maskString
|
||||
const anonymizedLastName = maskString
|
||||
const anonymizedEmail = randomString
|
||||
|
||||
await db.transaction(async dbClient => {
|
||||
await Promise.all([
|
||||
dbClient.query(
|
||||
`
|
||||
UPDATE "user"
|
||||
SET
|
||||
username = $1,
|
||||
first_name = $2,
|
||||
last_name = $3,
|
||||
email = $4,
|
||||
status = 'closed',
|
||||
time_closed = NOW()
|
||||
WHERE id = $5`,
|
||||
[
|
||||
anonymizedUsername,
|
||||
anonymizedFirstName,
|
||||
anonymizedLastName,
|
||||
anonymizedEmail,
|
||||
userId
|
||||
]
|
||||
),
|
||||
dbClient.query('DELETE FROM user_token_activation WHERE user_id = $1', [
|
||||
userId
|
||||
]),
|
||||
dbClient.query('DELETE FROM user_token_remember_me WHERE user_id = $1', [
|
||||
userId
|
||||
]),
|
||||
dbClient.query(
|
||||
'DELETE FROM user_token_reset_password WHERE user_id = $1',
|
||||
[userId]
|
||||
),
|
||||
dbClient.query('DELETE FROM user_token_change_email WHERE user_id = $1', [
|
||||
userId
|
||||
])
|
||||
])
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = User
|
||||
|
||||
Generated
+14
@@ -12,6 +12,7 @@
|
||||
"async": "^3.2.3",
|
||||
"axios": "^0.26.1",
|
||||
"bcrypt": "^5.0.1",
|
||||
"connect-flash-plus": "^0.2.1",
|
||||
"connect-redis": "^6.0.0",
|
||||
"cookie-parser": "^1.4.5",
|
||||
"debug": "^4.3.2",
|
||||
@@ -794,6 +795,14 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/connect-flash-plus": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/connect-flash-plus/-/connect-flash-plus-0.2.1.tgz",
|
||||
"integrity": "sha512-MqnJms7FpZFFlLMaooLviOWwc04chmcPKaqwStjFIYd8MthE99f71yTzUcoyx1XIx87VsJuxzraL/ScbfrUUfQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/connect-redis": {
|
||||
"version": "6.1.3",
|
||||
"resolved": "https://registry.npmjs.org/connect-redis/-/connect-redis-6.1.3.tgz",
|
||||
@@ -4418,6 +4427,11 @@
|
||||
"xdg-basedir": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"connect-flash-plus": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/connect-flash-plus/-/connect-flash-plus-0.2.1.tgz",
|
||||
"integrity": "sha512-MqnJms7FpZFFlLMaooLviOWwc04chmcPKaqwStjFIYd8MthE99f71yTzUcoyx1XIx87VsJuxzraL/ScbfrUUfQ=="
|
||||
},
|
||||
"connect-redis": {
|
||||
"version": "6.1.3",
|
||||
"resolved": "https://registry.npmjs.org/connect-redis/-/connect-redis-6.1.3.tgz",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"async": "^3.2.3",
|
||||
"axios": "^0.26.1",
|
||||
"bcrypt": "^5.0.1",
|
||||
"connect-flash-plus": "^0.2.1",
|
||||
"connect-redis": "^6.0.0",
|
||||
"cookie-parser": "^1.4.5",
|
||||
"debug": "^4.3.2",
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg width="98" height="98" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 1C18.1 1 23 5.9 23 12C23 18.1 18.1 23 12 23C5.9 23 1 18.1 1 12C1 5.9 5.9 1 12 1ZM12 21C17 21 21 17 21 12C21 7 17 3 12 3C7 3 3 7 3 12C3 17 7 21 12 21Z" fill="#057ED1"/>
|
||||
<path d="M12 11C12.6 11 13 11.4 13 12L13 16C13 16.6 12.6 17 12 17C11.4 17 11 16.6 11 16L11 12C11 11.4 11.4 11 12 11Z" fill="#057ED1"/>
|
||||
<path d="M12 7C12.3 7 12.5 7.1 12.7 7.3C12.9 7.5 13 7.7 13 8C13 8.1 13 8.3 12.9 8.4C12.8 8.5 12.8 8.6 12.7 8.7C12.4 9 12 9.1 11.6 8.9C11.5 8.9 11.5 8.9 11.4 8.8C11.4 8.8 11.3 8.7 11.2 8.7C11.1 8.6 11 8.5 11 8.4C11 8.3 11 8.1 11 8C11 7.9 11 7.7 11.1 7.6C11.2 7.5 11.2 7.4 11.3 7.3C11.5 7.1 11.7 7 12 7Z" fill="#057ED1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 739 B |
@@ -1,4 +1,4 @@
|
||||
/* global $, axios, bootstrap, currentPagePath, initPagination, removeAllChildNodes, unsavedData, replaceContainer, i18next */
|
||||
/* global $, axios, bootstrap, currentPagePath, initPagination, removeAllChildNodes, unsavedData, replaceContainer, isI18nReady, i18next, validator */
|
||||
|
||||
// const currentPagePath = location.pathname
|
||||
|
||||
@@ -144,62 +144,64 @@ function initAdmin() {
|
||||
}
|
||||
|
||||
if (currentPagePath === '/admin/uporabniki/seznam') {
|
||||
const resultsListEl = document.getElementById('page-results')
|
||||
isI18nReady.then(t => {
|
||||
const resultsListEl = document.getElementById('page-results')
|
||||
|
||||
const updatePager = initPagination('pagination', onPageChange)
|
||||
const updatePager = initPagination('pagination', onPageChange)
|
||||
|
||||
async function onPageChange(newPage) {
|
||||
try {
|
||||
const { page, numberOfAllPages, results } = await getDataForPage(
|
||||
newPage
|
||||
)
|
||||
removeAllChildNodes(resultsListEl)
|
||||
renderResults(results)
|
||||
updatePager(page, numberOfAllPages)
|
||||
} catch (error) {
|
||||
let message = i18next.t('Prišlo je do napake.')
|
||||
if (error.response?.data) {
|
||||
message = error.response.data
|
||||
} else if (error.request) {
|
||||
message = i18next.t('Strežnik ni dosegljiv. Poskusite kasneje.')
|
||||
async function onPageChange(newPage) {
|
||||
try {
|
||||
const { page, numberOfAllPages, results } = await getDataForPage(
|
||||
newPage
|
||||
)
|
||||
removeAllChildNodes(resultsListEl)
|
||||
renderResults(results)
|
||||
updatePager(page, numberOfAllPages)
|
||||
} catch (error) {
|
||||
let message = i18next.t('Prišlo je do napake.')
|
||||
if (error.response?.data) {
|
||||
message = error.response.data
|
||||
} else if (error.request) {
|
||||
message = i18next.t('Strežnik ni dosegljiv. Poskusite kasneje.')
|
||||
}
|
||||
alert(message)
|
||||
updatePager()
|
||||
}
|
||||
alert(message)
|
||||
updatePager()
|
||||
}
|
||||
}
|
||||
|
||||
async function getDataForPage(page) {
|
||||
const url = `/api/v1/users/listAllUsers?p=${page}`
|
||||
const { data } = await axios.get(url)
|
||||
return data
|
||||
}
|
||||
async function getDataForPage(page) {
|
||||
const url = `/api/v1/users/listAllUsers?p=${page}`
|
||||
const { data } = await axios.get(url)
|
||||
return data
|
||||
}
|
||||
|
||||
function renderResults(results) {
|
||||
results.forEach(result => {
|
||||
const rowEl = document.createElement('tr')
|
||||
const td1 = document.createElement('td')
|
||||
const td2 = document.createElement('td')
|
||||
const td3 = document.createElement('td')
|
||||
const td4 = document.createElement('td')
|
||||
const aEl = document.createElement('a')
|
||||
const imgEl = document.createElement('img')
|
||||
const spanEl = document.createElement('span')
|
||||
td1.textContent = result.userName
|
||||
td2.textContent = result.email
|
||||
td3.textContent = result.status
|
||||
aEl.classList.add('image-link')
|
||||
aEl.type = 'link'
|
||||
aEl.href = `/admin/uporabniki/${result.id}/urejanje`
|
||||
imgEl.src = '/images/u_edit-alt.svg'
|
||||
imgEl.alt = i18next.t('Uredi')
|
||||
spanEl.className = 'normal-gray ms-1'
|
||||
spanEl.textContent = i18next.t('Uredi')
|
||||
td4.append(aEl)
|
||||
aEl.append(imgEl, spanEl)
|
||||
rowEl.append(td1, td2, td3, td4)
|
||||
resultsListEl.appendChild(rowEl)
|
||||
})
|
||||
}
|
||||
function renderResults(results) {
|
||||
results.forEach(result => {
|
||||
const rowEl = document.createElement('tr')
|
||||
const td1 = document.createElement('td')
|
||||
const td2 = document.createElement('td')
|
||||
const td3 = document.createElement('td')
|
||||
const td4 = document.createElement('td')
|
||||
const aEl = document.createElement('a')
|
||||
const imgEl = document.createElement('img')
|
||||
const spanEl = document.createElement('span')
|
||||
td1.textContent = result.userName
|
||||
td2.textContent = result.email
|
||||
td3.textContent = t(`userStatus${result.status}`)
|
||||
aEl.classList.add('image-link')
|
||||
aEl.type = 'link'
|
||||
aEl.href = `/admin/uporabniki/${result.id}/urejanje`
|
||||
imgEl.src = '/images/u_edit-alt.svg'
|
||||
imgEl.alt = i18next.t('Uredi')
|
||||
spanEl.className = 'normal-gray ms-1'
|
||||
spanEl.textContent = i18next.t('Uredi')
|
||||
td4.append(aEl)
|
||||
aEl.append(imgEl, spanEl)
|
||||
rowEl.append(td1, td2, td3, td4)
|
||||
resultsListEl.appendChild(rowEl)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (currentPagePath === '/admin/slovarji') {
|
||||
@@ -280,12 +282,16 @@ function initAdmin() {
|
||||
}
|
||||
}
|
||||
|
||||
if (/\/admin\/slovarji\/\d+\/podatki/.test(currentPagePath)) {
|
||||
if (
|
||||
/\/admin\/slovarji\/\d+\/podatki/.test(currentPagePath) ||
|
||||
/\/slovarji\/\d+\/podatki/.test(currentPagePath)
|
||||
) {
|
||||
const formEl = document.getElementById('admin-description')
|
||||
const imgTrashIcon = document.querySelectorAll('.delete-author-btn')
|
||||
const inputNewAuthorEl = document.getElementById('input-new-author')
|
||||
const inputNewAreaEl = document.getElementById('input-new-area')
|
||||
const dictSideMenu = document.querySelector('.admin-nav-content')
|
||||
$('.without-addition').on('change', enableButton)
|
||||
unsavedData(formEl, dictSideMenu)
|
||||
formEl.addEventListener('input', enableButton)
|
||||
if (imgTrashIcon !== null) {
|
||||
@@ -946,7 +952,7 @@ function mobileMoveContent() {
|
||||
// primaryButton.style.marginRight = '10px'
|
||||
primaryButton.style.whiteSpace = 'nowrap'
|
||||
}
|
||||
navTitle.textContent = siteHeadingTextContent
|
||||
if (navTitle) navTitle.textContent = siteHeadingTextContent
|
||||
siteHeading.style.display = 'none'
|
||||
}
|
||||
if (document.body.clientWidth > 1200) {
|
||||
@@ -966,12 +972,14 @@ function mobileMoveContent() {
|
||||
primaryButton.style.whiteSpace = ''
|
||||
}
|
||||
|
||||
if (
|
||||
currentPagePath.includes('slovarji') &&
|
||||
!currentPagePath.includes('admin')
|
||||
)
|
||||
navTitle.textContent = i18next.t('Urejanje')
|
||||
else navTitle.textContent = i18next.t('Administracija')
|
||||
if (navTitle) {
|
||||
if (
|
||||
currentPagePath.includes('slovarji') &&
|
||||
!currentPagePath.includes('admin')
|
||||
)
|
||||
navTitle.textContent = i18next.t('Urejanje')
|
||||
else navTitle.textContent = i18next.t('Administracija')
|
||||
}
|
||||
siteHeading.style.display = 'block'
|
||||
}
|
||||
}
|
||||
@@ -1180,7 +1188,7 @@ $('.summernote').summernote({
|
||||
const profileForm = document.getElementById('profileForm')
|
||||
|
||||
if (profileForm) {
|
||||
profileForm.addEventListener('change', e => {
|
||||
profileForm.addEventListener('input', e => {
|
||||
enableButton()
|
||||
})
|
||||
|
||||
@@ -1189,12 +1197,30 @@ $('.summernote').summernote({
|
||||
|
||||
const data = Object.fromEntries(new FormData(e.target))
|
||||
|
||||
const notifyAction = () => {
|
||||
document.getElementById('fpi-text').innerHTML = i18next.t(
|
||||
'Izpolnite vsa prazna polja.'
|
||||
)
|
||||
$('#reset-pass-info').modal('show')
|
||||
}
|
||||
|
||||
if (data.numberOfHits) {
|
||||
await handleUpdateHitsPerPage(data.numberOfHits)
|
||||
}
|
||||
|
||||
if (data.name && data.surname) {
|
||||
await handleUpdateUsersName(data.name, data.surname)
|
||||
if (data.firstName && data.lastName && data.email) {
|
||||
await handleUpdateBasicData(data)
|
||||
} else if (location.pathname === '/moj-racun') {
|
||||
// if missing the required data on endpoint /moj-racun, notify!
|
||||
notifyAction()
|
||||
return
|
||||
}
|
||||
|
||||
if (data.passwordOld && data.passwordNew && data.passwordNewRepeat) {
|
||||
await handleUpdatePassword(data)
|
||||
} else if (location.pathname === '/spremeni-geslo') {
|
||||
// if missing the required data on endpoint /spremeni-geslo, notify!
|
||||
notifyAction()
|
||||
}
|
||||
|
||||
// console.log(data)
|
||||
@@ -1204,23 +1230,60 @@ $('.summernote').summernote({
|
||||
async function handleUpdateHitsPerPage(hitAmount) {
|
||||
try {
|
||||
await axios.post('/api/v1/users/hitsPerPage', { hitAmount })
|
||||
} catch (error) {
|
||||
// console.log(error)
|
||||
} finally {
|
||||
location.reload(true)
|
||||
} catch (error) {
|
||||
displayError(error)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleUpdateUsersName(name, surname) {
|
||||
async function handleUpdateBasicData(payload) {
|
||||
try {
|
||||
await axios.post('/api/v1/users/nameAndSurname', {
|
||||
name,
|
||||
surname
|
||||
})
|
||||
} catch (error) {
|
||||
// console.log(error)
|
||||
} finally {
|
||||
if (!validator.isEmail(payload.email)) {
|
||||
document.getElementById('fpi-text').innerHTML =
|
||||
i18next.t('Neveljavna e-pošta')
|
||||
$('#reset-pass-info').modal('show')
|
||||
return
|
||||
}
|
||||
await axios.post('/api/v1/users/basic-data', payload)
|
||||
location.reload(true)
|
||||
} catch (error) {
|
||||
displayError(error)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleUpdatePassword(payload) {
|
||||
try {
|
||||
if (payload.passwordNew !== payload.passwordNewRepeat) {
|
||||
document.getElementById('fpi-text').innerHTML = i18next.t(
|
||||
'Gesli se ne ujemata'
|
||||
)
|
||||
$('#reset-pass-info').modal('show')
|
||||
return
|
||||
}
|
||||
|
||||
if (!validator.isLength(payload.passwordNew, { min: 8 })) {
|
||||
document.getElementById('fpi-text').innerHTML =
|
||||
i18next.t('Geslo je prekratko')
|
||||
$('#reset-pass-info').modal('show')
|
||||
return
|
||||
}
|
||||
|
||||
await axios.post('/api/v1/users/password', payload)
|
||||
location.reload(true)
|
||||
} catch (error) {
|
||||
displayError(error)
|
||||
}
|
||||
}
|
||||
|
||||
function displayError(error) {
|
||||
let message = i18next.t('Prišlo je do napake.')
|
||||
if (error.response) {
|
||||
message = error.response.data
|
||||
} else if (error.request) {
|
||||
message = i18next.t('Strežnik ni dosegljiv. Poskusite kasneje.')
|
||||
}
|
||||
|
||||
document.getElementById('fpi-text').textContent = message
|
||||
$('#reset-pass-info').modal('show')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,188 +0,0 @@
|
||||
/* global axios */
|
||||
|
||||
// Priporočam uporabo block scopa okoli paginacijske logike za čimvečjo izolacijo.
|
||||
{
|
||||
// Referenca na element, kamor se izrisuje seznam rezultatov.
|
||||
const resultsListEl = document.getElementById('page-results')
|
||||
|
||||
// Paginacijo inicializiraš s klicem funkcije initPagination:
|
||||
// 1. parameter: id pager elementa. V demo-paginacija.pug je to #pagination.
|
||||
// UPDATE: Sedaj sprejme tudi array idjev, če je pager kontrolerjev več (npr. en zgoraj (#pagination-top), en spodaj (#pagination-bottom)).
|
||||
// 2. parameter: callback funkcijo, ki jo pager pokliče vsakič, ko uporabnik zahteva novo stran. Poliče jo s številko zahtevane strani.
|
||||
// vrnjena vrednost: funkcija, ki jo (kasneje) kličeš za posodobitev pagerja. Tu jo poimenujem updateDemoPager.
|
||||
const updateDemoPager = initPagination(
|
||||
['pagination-top', 'pagination-bottom'],
|
||||
onPageChange
|
||||
)
|
||||
// Za samo en kontroler je bilo:
|
||||
// const updateDemoPager = initPagination('pagination', onPageChange)
|
||||
|
||||
// Fukncija, prejme številko nove strani in naj:
|
||||
// 1. Pridobi podatke nove strani.
|
||||
// 2. Izriše seznam elementov te strani.
|
||||
// 3. Posodobi pager, tako, da kliče funkcijo, ki jo je vrnil klic initPagination (updateDemoPager) z novo stranjo in številom vseh strani.
|
||||
async function onPageChange(newPage) {
|
||||
try {
|
||||
const { page, numberOfAllPages, results } = await getDataForPage(newPage)
|
||||
removeAllChildNodes(resultsListEl)
|
||||
renderResults(results)
|
||||
updateDemoPager(page, numberOfAllPages)
|
||||
} catch (error) {
|
||||
let message = 'Prišlo je do napake.'
|
||||
if (error.response?.data) {
|
||||
message = error.response.data
|
||||
} else if (error.request) {
|
||||
message = 'Strežnik ni dosegljiv. Poskusite kasneje.'
|
||||
}
|
||||
alert(message)
|
||||
updateDemoPager()
|
||||
}
|
||||
}
|
||||
|
||||
// Primer helper funkcije za pridobitev podatkov želene strani.
|
||||
async function getDataForPage(page) {
|
||||
const url = `/api/v1/demo-paginacija/list?p=${page}`
|
||||
const { data } = await axios.get(url)
|
||||
return data
|
||||
}
|
||||
|
||||
// Primer helper funkcije za izris seznama novih podatkov.
|
||||
function renderResults(results) {
|
||||
results.forEach(result => {
|
||||
const newListEl = document.createElement('li')
|
||||
const textNode1 = document.createTextNode('Zanimiva vrednost: ')
|
||||
const boldedEl = document.createElement('b')
|
||||
boldedEl.textContent = result.zanimivo
|
||||
const textNode2 = document.createTextNode(
|
||||
`. Totalno nezanimivo: ${result.nezanimivo1} in ${result.nezanimivo2}`
|
||||
)
|
||||
|
||||
newListEl.append(textNode1, boldedEl, textNode2)
|
||||
resultsListEl.appendChild(newListEl)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/** ****************************************************************************************************************************************** **\
|
||||
* Koda od tu navzdol za vaju ni relevantna. Tu je, da dela zgornja koda. Za realno uporabo sem je skopiral tudi v public/javascripts/scripts.js *
|
||||
* Na vsaki strani, kjer bo paginacija, jo inicializiraj in uporabljaj po zgledu zgornje kode. *
|
||||
\** ****************************************************************************************************************************************** **/
|
||||
|
||||
function initPagination(paginationRootElIds, onPageChange, currentPage = 1) {
|
||||
let reqLock = false
|
||||
let numOfAllPages
|
||||
const backwardBtnEls = []
|
||||
const forwardBtnEls = []
|
||||
const pageInputEls = []
|
||||
const pagesCountDisplayEls = []
|
||||
|
||||
if (Array.isArray(paginationRootElIds)) {
|
||||
paginationRootElIds.forEach(id => initControls(id))
|
||||
} else {
|
||||
initControls(paginationRootElIds)
|
||||
}
|
||||
const allControlEls = [...backwardBtnEls, ...forwardBtnEls, ...pageInputEls]
|
||||
|
||||
function initControls(paginationRootElId) {
|
||||
const rootEl = document.getElementById(paginationRootElId)
|
||||
const btnFirstPage = rootEl.querySelector('.first-page')
|
||||
const btnPreviousPage = rootEl.querySelector('.previous-page')
|
||||
const btnNextPage = rootEl.querySelector('.next-page')
|
||||
const btnLastPage = rootEl.querySelector('.last-page')
|
||||
const formEl = rootEl.querySelector('form')
|
||||
const pageInputEl = formEl.querySelector('input')
|
||||
const pagesCountDisplayEl = formEl.querySelector('.pages-total')
|
||||
|
||||
numOfAllPages = +pagesCountDisplayEl.textContent
|
||||
|
||||
backwardBtnEls.push(btnFirstPage, btnPreviousPage)
|
||||
forwardBtnEls.push(btnNextPage, btnLastPage)
|
||||
pageInputEls.push(pageInputEl)
|
||||
pagesCountDisplayEls.push(pagesCountDisplayEl)
|
||||
|
||||
rootEl.addEventListener('click', e => {
|
||||
handleButtonClick(e, paginationRootElId)
|
||||
})
|
||||
formEl.addEventListener('submit', e => handleFormSubmit(e, pageInputEl))
|
||||
}
|
||||
|
||||
function handleButtonClick({ target }, paginationRootElId) {
|
||||
if (reqLock) return
|
||||
|
||||
const buttonEl = target.closest(`#${paginationRootElId} button`)
|
||||
if (!buttonEl) return
|
||||
|
||||
if (buttonEl.classList.contains('first-page')) {
|
||||
if (currentPage === 1) return
|
||||
enableLock()
|
||||
onPageChange(1)
|
||||
} else if (buttonEl.classList.contains('previous-page')) {
|
||||
if (currentPage === 1) return
|
||||
enableLock()
|
||||
onPageChange(currentPage - 1)
|
||||
} else if (buttonEl.classList.contains('next-page')) {
|
||||
if (currentPage === numOfAllPages) return
|
||||
enableLock()
|
||||
onPageChange(currentPage + 1)
|
||||
} else if (buttonEl.classList.contains('last-page')) {
|
||||
if (currentPage === numOfAllPages) return
|
||||
enableLock()
|
||||
onPageChange(numOfAllPages)
|
||||
}
|
||||
}
|
||||
|
||||
function handleFormSubmit(e, pageInputEl) {
|
||||
e.preventDefault()
|
||||
if (reqLock) return
|
||||
|
||||
const inputValue = +pageInputEl.value
|
||||
if (!(inputValue > 0 && inputValue <= numOfAllPages)) {
|
||||
alert('Nepravilna vrednost strani')
|
||||
pageInputEl.value = currentPage
|
||||
return
|
||||
}
|
||||
|
||||
enableLock()
|
||||
onPageChange(inputValue)
|
||||
}
|
||||
|
||||
function enableLock() {
|
||||
reqLock = true
|
||||
allControlEls.forEach(el => (el.disabled = true))
|
||||
}
|
||||
|
||||
function disableLock() {
|
||||
reqLock = false
|
||||
allControlEls.forEach(el => (el.disabled = false))
|
||||
}
|
||||
|
||||
function updatePagerUi(newCurrentPage, newNumOfAllPages) {
|
||||
disableLock()
|
||||
if (!newCurrentPage) return
|
||||
|
||||
currentPage = newCurrentPage
|
||||
pageInputEls.forEach(el => (el.value = newCurrentPage))
|
||||
pagesCountDisplayEls.forEach(el => (el.textContent = newNumOfAllPages))
|
||||
|
||||
if (newCurrentPage === 1) {
|
||||
backwardBtnEls.forEach(el => (el.disabled = true))
|
||||
} else {
|
||||
backwardBtnEls.forEach(el => (el.disabled = false))
|
||||
}
|
||||
|
||||
if (newCurrentPage === newNumOfAllPages) {
|
||||
forwardBtnEls.forEach(el => (el.disabled = true))
|
||||
} else {
|
||||
forwardBtnEls.forEach(el => (el.disabled = false))
|
||||
}
|
||||
}
|
||||
|
||||
return updatePagerUi
|
||||
}
|
||||
|
||||
// Helper function to easily remove all child nodes. Useful for pagination.
|
||||
function removeAllChildNodes(parent) {
|
||||
while (parent.firstChild) {
|
||||
parent.removeChild(parent.firstChild)
|
||||
}
|
||||
}
|
||||
@@ -845,7 +845,7 @@ function initDictionaries() {
|
||||
}
|
||||
}
|
||||
if (listForeignSynonyms.length) {
|
||||
if (el.synonym != null)
|
||||
if (el.synonym != null) {
|
||||
el.synonym.forEach(element => {
|
||||
if (element.length) {
|
||||
// eslint-disable-next-line
|
||||
@@ -861,6 +861,7 @@ function initDictionaries() {
|
||||
selectSyn.append(newOption).trigger('change')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -2035,19 +2036,21 @@ function loadPreview(info) {
|
||||
}
|
||||
|
||||
const languageContainers = document.querySelectorAll('.preview-one-language')
|
||||
const langLine = document.querySelector('.language-line')
|
||||
const langLine = document.querySelector('.start-line')
|
||||
if (languageContainers) {
|
||||
const arrLang = Array.from(languageContainers)
|
||||
arrLang.forEach(el => {
|
||||
const arrChildren = Array.from(el.children)
|
||||
if (arrChildren.filter(e => e.classList.contains('d-none')).length > 2) {
|
||||
el.classList.add('d-none')
|
||||
langLine.classList.add('d-none')
|
||||
} else {
|
||||
el.classList.remove('d-none')
|
||||
langLine.classList.remove('d-none')
|
||||
}
|
||||
} else el.classList.remove('d-none')
|
||||
})
|
||||
if (
|
||||
arrLang.filter(el => el.classList.contains('d-none')).length >=
|
||||
arrLang.length
|
||||
) {
|
||||
langLine.classList.add('d-none')
|
||||
} else langLine.classList.remove('d-none')
|
||||
}
|
||||
changeCollapsedContent()
|
||||
}
|
||||
@@ -2110,10 +2113,10 @@ function changeVersionList(versions) {
|
||||
// eslint-disable-next-line
|
||||
new bootstrap.Tooltip(dateLabel, {
|
||||
title:
|
||||
i18next.t('Verzija') +
|
||||
`${el.version} <br>` +
|
||||
i18next.t('Verzija:') +
|
||||
` ${el.version} <br>` +
|
||||
i18next.t('Avtor:') +
|
||||
`${el.version_author}`
|
||||
` ${el.version_author}`
|
||||
})
|
||||
allDatesEl.append(dateRadio)
|
||||
allDatesEl.appendChild(dateLabel)
|
||||
@@ -2133,9 +2136,9 @@ function setLatestVersion(data) {
|
||||
const tooltip = new bootstrap.Tooltip(latestVersionLabel, {
|
||||
title:
|
||||
i18next.t('Verzija:') +
|
||||
`${data.version} <br>` +
|
||||
` ${data.version} <br>` +
|
||||
i18next.t('Avtor:') +
|
||||
`${data.version_author}`,
|
||||
` ${data.version_author}`,
|
||||
customClass: 'dark-gray-tooltip',
|
||||
html: true,
|
||||
placement: 'bottom'
|
||||
@@ -2823,6 +2826,12 @@ function deleteContentData(
|
||||
linkType.value = 'related'
|
||||
linkText.value = ''
|
||||
}
|
||||
if (listForeignDefinitions) {
|
||||
const foreignDefinitionsFields = document.querySelectorAll(
|
||||
'.foreign-definition-el'
|
||||
)
|
||||
foreignDefinitionsFields.forEach(el => (el.value = ''))
|
||||
}
|
||||
}
|
||||
|
||||
function activateMe(term) {
|
||||
|
||||
@@ -62,6 +62,10 @@
|
||||
}
|
||||
|
||||
function renderResults(results) {
|
||||
const tableContainer = document.querySelector(
|
||||
'.list-terminology-candidates'
|
||||
)
|
||||
tableContainer.classList.remove('d-none')
|
||||
results.forEach(([sequentialCount, candidate]) => {
|
||||
const rowEl = document.createElement('tr')
|
||||
const tdId = document.createElement('td')
|
||||
|
||||
@@ -197,7 +197,7 @@ function updateFileListEl(fileListEl, { status, fileStats, index }) {
|
||||
deleteImgEl.alt = ''
|
||||
const deleteSpanEl = document.createElement('span')
|
||||
deleteSpanEl.className = 'ms-2'
|
||||
deleteSpanEl.textContent = 'Briši'
|
||||
deleteSpanEl.textContent = i18next.t('Briši')
|
||||
const deleteButtonEl = document.createElement('button')
|
||||
deleteButtonEl.className = 'p-0 delete-file delete-btn-table'
|
||||
deleteButtonEl.append(deleteImgEl, deleteSpanEl)
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
/* global axios */
|
||||
|
||||
const fileUploadForm = document.forms['upload-files']
|
||||
const fileInputEl = fileUploadForm.querySelector('input[type="file"]')
|
||||
const messageContainerEl = document.getElementById('messages')
|
||||
const filesListEl = document.getElementById('files-list')
|
||||
|
||||
const extractionId = +fileUploadForm.extractionId.value
|
||||
let apiEndpointBase
|
||||
switch (location.pathname.split('/').at(-1)) {
|
||||
case 'besedila':
|
||||
apiEndpointBase = `/api/v1/extraction/${extractionId}/documents`
|
||||
break
|
||||
|
||||
case 'stop-termini':
|
||||
apiEndpointBase = `/api/v1/extraction/${extractionId}/stop-terms`
|
||||
break
|
||||
|
||||
default:
|
||||
throw Error("apiEndpointBase couldn't be determined")
|
||||
}
|
||||
|
||||
fileInputEl.addEventListener('change', submitFiles)
|
||||
filesListEl.addEventListener('click', handleFileClick)
|
||||
|
||||
async function submitFiles() {
|
||||
// TODO Lock additional submits for the duration of this function execution?
|
||||
const MAX_FILE_SIZE = 10 ** 9 // 1 GB
|
||||
const failedUploads = []
|
||||
|
||||
displaySpinner()
|
||||
|
||||
for (const file of fileInputEl.files) {
|
||||
if (file.size > MAX_FILE_SIZE) {
|
||||
const failedUpload = {
|
||||
filename: file.name,
|
||||
message: 'File too large. Must not be over 1 GB.'
|
||||
}
|
||||
failedUploads.push(failedUpload)
|
||||
continue
|
||||
}
|
||||
|
||||
const payload = new FormData()
|
||||
payload.set(fileInputEl.name, file)
|
||||
try {
|
||||
await axios.put(apiEndpointBase, payload)
|
||||
} catch (error) {
|
||||
const failedUpload = {
|
||||
filename: file.name,
|
||||
message: error.response.data
|
||||
}
|
||||
failedUploads.push(failedUpload)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const { data: files } = await axios.get(apiEndpointBase)
|
||||
updateFilesList(files)
|
||||
} catch {
|
||||
alert('Pri posodobljanju seznama naloženih datotek je prišlo do napake.')
|
||||
}
|
||||
|
||||
fileInputEl.value = ''
|
||||
displayFailedUploads(failedUploads)
|
||||
hideSpinner()
|
||||
}
|
||||
|
||||
function displaySpinner() {
|
||||
const messageEl = document.createElement('li')
|
||||
messageEl.textContent = 'Spinner on'
|
||||
messageContainerEl.appendChild(messageEl)
|
||||
}
|
||||
|
||||
function hideSpinner() {
|
||||
const messageEl = document.createElement('li')
|
||||
messageEl.textContent = 'Spinner off'
|
||||
messageContainerEl.appendChild(messageEl)
|
||||
}
|
||||
|
||||
function updateFilesList(files) {
|
||||
removeAllChildNodes(filesListEl)
|
||||
files.forEach(({ filename, size, timeModified }) => {
|
||||
const fileEl = document.createElement('li')
|
||||
const filenameSpanEl = document.createElement('span')
|
||||
filenameSpanEl.className = 'filename'
|
||||
filenameSpanEl.textContent = filename
|
||||
const formattedDate = new Date(timeModified).toLocaleDateString('sl-SL')
|
||||
const deleteButtonEl = document.createElement('a')
|
||||
deleteButtonEl.className = 'delete-file'
|
||||
deleteButtonEl.href = '#'
|
||||
deleteButtonEl.textContent = 'BRIŠI'
|
||||
fileEl.append(
|
||||
'DATOTEKA - Ime: ',
|
||||
filenameSpanEl,
|
||||
`, velikost: ${size}, datum: ${formattedDate} `,
|
||||
deleteButtonEl
|
||||
)
|
||||
filesListEl.appendChild(fileEl)
|
||||
})
|
||||
}
|
||||
|
||||
function displayFailedUploads(failedUploads) {
|
||||
failedUploads.forEach(({ filename, message }) => {
|
||||
const messageEl = document.createElement('li')
|
||||
messageEl.textContent = `NAPAKA - Ime datoteke: ${filename}, razlog: ${message}`
|
||||
messageContainerEl.appendChild(messageEl)
|
||||
})
|
||||
}
|
||||
|
||||
async function handleFileClick(e) {
|
||||
if (e.target.closest('.delete-file')) {
|
||||
const fileEl = e.target.closest('li')
|
||||
const filename = fileEl.querySelector('.filename').textContent
|
||||
try {
|
||||
await axios.delete(`${apiEndpointBase}/${filename}`)
|
||||
fileEl.remove()
|
||||
} catch {
|
||||
alert('Pri brisanju datoteke je prišlo do napake.')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Don't copy this one into final JS. It's already defined in scripts.js
|
||||
function removeAllChildNodes(parent) {
|
||||
while (parent.firstChild) {
|
||||
parent.removeChild(parent.firstChild)
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
/* global axios */
|
||||
|
||||
const extractionListEl = document.getElementById('extraction-list')
|
||||
|
||||
extractionListEl.addEventListener('click', onListClick)
|
||||
|
||||
async function onListClick({ target }) {
|
||||
if (target.classList.contains('btn-delete')) {
|
||||
const extractionEl = target.closest('li')
|
||||
const extractionId = extractionEl.dataset.id
|
||||
await axios.delete(`/api/v1/extraction/${extractionId}`)
|
||||
extractionEl.remove()
|
||||
} else if (target.classList.contains('btn-begin')) {
|
||||
const extractionEl = target.closest('li')
|
||||
const extractionId = extractionEl.dataset.id
|
||||
await axios.put(`/api/v1/extraction/${extractionId}/begin`)
|
||||
} else if (target.classList.contains('btn-duplicate')) {
|
||||
const extractionEl = target.closest('li')
|
||||
const extractionId = extractionEl.dataset.id
|
||||
await axios.post(`/api/v1/extraction/${extractionId}/duplicate`)
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
/* global $, axios */
|
||||
|
||||
$('.pick-multiple').select2()
|
||||
$('.enter-multiple').select2({
|
||||
tags: true
|
||||
})
|
||||
|
||||
const editStopTermsLink = document.getElementById('edit-stop-terms')
|
||||
const searchButton = document.getElementById('search-btn')
|
||||
const searchResultEl = document.getElementById('search-result')
|
||||
const messageContainerEl = document.getElementById('messages')
|
||||
const formEl = document.forms[0]
|
||||
const extractionId = +location.pathname.split('/').at(-1)
|
||||
|
||||
editStopTermsLink.addEventListener('click', saveOssParamsFirst)
|
||||
searchButton.addEventListener('click', handleSearch)
|
||||
searchResultEl.addEventListener('click', handleSearchResultsClick)
|
||||
|
||||
async function saveOssParamsFirst() {
|
||||
const payload = new URLSearchParams(new FormData(formEl))
|
||||
navigator.sendBeacon(
|
||||
`/api/v1/extraction/${extractionId}/oss-save-params`,
|
||||
payload
|
||||
)
|
||||
}
|
||||
|
||||
async function handleSearch() {
|
||||
displaySpinner()
|
||||
try {
|
||||
const { data } = await submitSearch()
|
||||
displaySearchResults(data)
|
||||
} catch {
|
||||
handleSearchError()
|
||||
}
|
||||
hideSpinner()
|
||||
}
|
||||
|
||||
function displaySpinner() {
|
||||
const messageEl = document.createElement('li')
|
||||
messageEl.textContent = 'Spinner on'
|
||||
messageContainerEl.appendChild(messageEl)
|
||||
}
|
||||
|
||||
function hideSpinner() {
|
||||
const messageEl = document.createElement('li')
|
||||
messageEl.textContent = 'Spinner off'
|
||||
messageContainerEl.appendChild(messageEl)
|
||||
}
|
||||
|
||||
function handleSearchError() {
|
||||
const messageEl = document.createElement('li')
|
||||
messageEl.textContent = 'Notify the user of error that occured during search'
|
||||
messageContainerEl.appendChild(messageEl)
|
||||
}
|
||||
|
||||
async function submitSearch() {
|
||||
const payload = new URLSearchParams(new FormData(formEl))
|
||||
return await axios.put(
|
||||
`/api/v1/extraction/${extractionId}/oss-search`,
|
||||
payload
|
||||
)
|
||||
}
|
||||
|
||||
function displaySearchResults({ documentCount, canSave }) {
|
||||
removeAllChildNodes(searchResultEl)
|
||||
searchResultEl.textContent = `Število dokumentov: ${documentCount}`
|
||||
if (canSave) {
|
||||
const saveButton = document.createElement('button')
|
||||
saveButton.id = 'save-params'
|
||||
saveButton.textContent = 'Shrani'
|
||||
searchResultEl.append(saveButton)
|
||||
}
|
||||
}
|
||||
|
||||
function handleSearchResultsClick({ target }) {
|
||||
if (target.closest('#save-params')) confirmParams()
|
||||
}
|
||||
|
||||
async function confirmParams() {
|
||||
displaySpinner()
|
||||
try {
|
||||
await axios.put(`/api/v1/extraction/${extractionId}/oss-confirm-params`)
|
||||
location = '../poc'
|
||||
} catch {
|
||||
alert('Error saving params')
|
||||
hideSpinner()
|
||||
}
|
||||
}
|
||||
|
||||
// Don't copy this one into final JS. It's already defined in scripts.js
|
||||
function removeAllChildNodes(parent) {
|
||||
while (parent.firstChild) {
|
||||
parent.removeChild(parent.firstChild)
|
||||
}
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
/* global termCandidates, hitsPerPage, numberOfAllPages */
|
||||
{
|
||||
const resultsListEl = document.getElementById('page-results')
|
||||
|
||||
const updateDemoPager = initPagination('pagination', onPageChange)
|
||||
|
||||
function onPageChange(newPage) {
|
||||
const results = getDataForPage(newPage)
|
||||
removeAllChildNodes(resultsListEl)
|
||||
renderResults(results)
|
||||
updateDemoPager(newPage, numberOfAllPages)
|
||||
}
|
||||
|
||||
function getDataForPage(page) {
|
||||
const sliceStart = (page - 1) * hitsPerPage
|
||||
const sliceEnd = page * hitsPerPage
|
||||
const onePageOfTermCandidates = termCandidates.slice(sliceStart, sliceEnd)
|
||||
const data = onePageOfTermCandidates.map((candidate, index) => {
|
||||
const sequentialCount = sliceStart + index + 1
|
||||
return [sequentialCount, candidate]
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
function renderResults(results) {
|
||||
results.forEach(([sequentialCount, candidate]) => {
|
||||
const newListEl = document.createElement('li')
|
||||
newListEl.textContent = `[${sequentialCount}] ${JSON.stringify(
|
||||
candidate
|
||||
)}`
|
||||
resultsListEl.appendChild(newListEl)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Below code is copied from scripts.js, so it will already be available on the real page. No need to copy it there also.
|
||||
function initPagination(paginationRootElId, onPageChange, currentPage = 1) {
|
||||
const rootEl = document.getElementById(paginationRootElId)
|
||||
const btnFirstPage = rootEl.querySelector('.first-page')
|
||||
const btnPreviousPage = rootEl.querySelector('.previous-page')
|
||||
const btnNextPage = rootEl.querySelector('.next-page')
|
||||
const btnLastPage = rootEl.querySelector('.last-page')
|
||||
const formEl = rootEl.querySelector('form')
|
||||
const pageInputEl = formEl.querySelector('input')
|
||||
const pagesCountDisplayEl = formEl.querySelector('.pages-total')
|
||||
|
||||
let reqLock = false
|
||||
|
||||
rootEl.addEventListener('click', handleButtonClick)
|
||||
formEl.addEventListener('submit', handleFormSubmit)
|
||||
|
||||
function handleButtonClick({ target }) {
|
||||
if (reqLock) return
|
||||
|
||||
const buttonEl = target.closest(`#${paginationRootElId} button`)
|
||||
if (!buttonEl) return
|
||||
const numOfAllPages = +pagesCountDisplayEl.textContent
|
||||
|
||||
if (buttonEl.classList.contains('first-page')) {
|
||||
if (currentPage === 1) return
|
||||
enableLock()
|
||||
onPageChange(1)
|
||||
} else if (buttonEl.classList.contains('previous-page')) {
|
||||
if (currentPage === 1) return
|
||||
enableLock()
|
||||
onPageChange(currentPage - 1)
|
||||
} else if (buttonEl.classList.contains('next-page')) {
|
||||
if (currentPage === numOfAllPages) return
|
||||
enableLock()
|
||||
onPageChange(currentPage + 1)
|
||||
} else if (buttonEl.classList.contains('last-page')) {
|
||||
if (currentPage === numOfAllPages) return
|
||||
enableLock()
|
||||
onPageChange(numOfAllPages)
|
||||
}
|
||||
}
|
||||
|
||||
function handleFormSubmit(e) {
|
||||
e.preventDefault()
|
||||
if (reqLock) return
|
||||
|
||||
const inputValue = +pageInputEl.value
|
||||
if (!(inputValue > 0 && inputValue <= pagesCountDisplayEl.textContent)) {
|
||||
alert('Nepravilna vrednost strani')
|
||||
pageInputEl.value = currentPage
|
||||
return
|
||||
}
|
||||
|
||||
enableLock()
|
||||
onPageChange(inputValue)
|
||||
}
|
||||
|
||||
function enableLock() {
|
||||
reqLock = true
|
||||
btnFirstPage.disabled = true
|
||||
btnPreviousPage.disabled = true
|
||||
btnNextPage.disabled = true
|
||||
btnLastPage.disabled = true
|
||||
pageInputEl.disabled = true
|
||||
}
|
||||
|
||||
function disableLock() {
|
||||
reqLock = false
|
||||
btnFirstPage.disabled = false
|
||||
btnPreviousPage.disabled = false
|
||||
btnNextPage.disabled = false
|
||||
btnLastPage.disabled = false
|
||||
pageInputEl.disabled = false
|
||||
}
|
||||
|
||||
function updatePagerUi(newCurrentPage, newNumOfAllPages) {
|
||||
disableLock()
|
||||
if (!newCurrentPage) return
|
||||
|
||||
currentPage = newCurrentPage
|
||||
pageInputEl.value = newCurrentPage
|
||||
pagesCountDisplayEl.textContent = newNumOfAllPages
|
||||
|
||||
if (newCurrentPage === 1) {
|
||||
btnFirstPage.disabled = true
|
||||
btnPreviousPage.disabled = true
|
||||
} else {
|
||||
btnFirstPage.disabled = false
|
||||
btnPreviousPage.disabled = false
|
||||
}
|
||||
|
||||
if (newCurrentPage === newNumOfAllPages) {
|
||||
btnNextPage.disabled = true
|
||||
btnLastPage.disabled = true
|
||||
} else {
|
||||
btnNextPage.disabled = false
|
||||
btnLastPage.disabled = false
|
||||
}
|
||||
}
|
||||
|
||||
return updatePagerUi
|
||||
}
|
||||
|
||||
// Helper function to easily remove all child nodes. Useful for pagination.
|
||||
function removeAllChildNodes(parent) {
|
||||
while (parent.firstChild) {
|
||||
parent.removeChild(parent.firstChild)
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,34 @@
|
||||
/* global isI18nReady */
|
||||
/* global isI18nReady, i18next, $, axios */
|
||||
|
||||
isI18nReady.then(t => {
|
||||
const alertText = document.querySelector('#alert-text')
|
||||
alertText.textContent = t(
|
||||
'Ali želite zbrisati ta profil. S tem bodo izbrisani vsi podatki, ki ste jih ustvarili'
|
||||
'Ali res želite izbrisati svoj račun? S tem boste trajno izgubili dostop do podatkov, ki ste jih ustvarili.'
|
||||
)
|
||||
})
|
||||
|
||||
const redConfirm = document.querySelector('#modal-use-btn')
|
||||
redConfirm.style.backgroundColor = '#AC7171'
|
||||
document.querySelector('#modal-alert-label').style.color = '#AC7171'
|
||||
|
||||
redConfirm.addEventListener('click', async () => {
|
||||
// TODO: Optimize, create a common helper function for example
|
||||
function displayError(error) {
|
||||
let message = i18next.t('Prišlo je do napake.')
|
||||
if (error.response) {
|
||||
message = error.response.data
|
||||
} else if (error.request) {
|
||||
message = i18next.t('Strežnik ni dosegljiv. Poskusite kasneje.')
|
||||
}
|
||||
|
||||
document.querySelector('#info-text').textContent = message
|
||||
$('#info-modal').modal('show')
|
||||
}
|
||||
|
||||
try {
|
||||
await axios.delete('/api/v1/users/current')
|
||||
window.location = '/'
|
||||
} catch (error) {
|
||||
displayError(error)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,59 +1,66 @@
|
||||
/* global $, axios, i18next */
|
||||
/* global $, axios, validator, i18next */
|
||||
|
||||
// Function to verify password and repeat password
|
||||
function verifyPassword() {
|
||||
const password = document.getElementById('reset-password').value
|
||||
const repeatPassword = document.getElementById('reset-password-repeat').value
|
||||
|
||||
if (password !== repeatPassword) {
|
||||
alert(i18next.t('Gesli se ne ujemata')) // 'Passwords do not match')
|
||||
// alert(i18next.t('Gesli se ne ujemata')) // 'Passwords do not match')
|
||||
document.querySelector('#reset-password-error').textContent = i18next.t(
|
||||
'Gesli se ne ujemata'
|
||||
)
|
||||
document.querySelector('#reset-password-error').style.visibility = 'visible'
|
||||
return false
|
||||
}
|
||||
|
||||
if (!validator.isLength(password, { min: 8 })) {
|
||||
// alert(i18next.t('Geslo je prekratko')) // 'Passwords do not match')
|
||||
document.querySelector('#reset-password-error').textContent =
|
||||
i18next.t('Geslo je prekratko')
|
||||
document.querySelector('#reset-password-error').style.visibility = 'visible'
|
||||
return false
|
||||
}
|
||||
|
||||
document.querySelector('#reset-password-error').style.visibility = 'invisible'
|
||||
return true
|
||||
}
|
||||
|
||||
// Handle submit event
|
||||
document
|
||||
.querySelector('#reset-and-redirect')
|
||||
.addEventListener('submit', event => {
|
||||
.addEventListener('submit', async event => {
|
||||
event.preventDefault()
|
||||
if (verifyPassword()) {
|
||||
const token = document.getElementById('token').value
|
||||
const password = document.getElementById('reset-password').value
|
||||
const repeatPassword = document.getElementById(
|
||||
const passwordRepeat = document.getElementById(
|
||||
'reset-password-repeat'
|
||||
).value
|
||||
const token = document.getElementById('token').value
|
||||
// const email = document.getElementById('email').value
|
||||
// const data = { password, repeatPassword, token, email }
|
||||
axios
|
||||
.post('/api/v1/users/reset-passwordPLACEHOLDER_URL', {
|
||||
try {
|
||||
await axios.post('/api/v1/users/reset-password-submit', {
|
||||
token,
|
||||
password,
|
||||
repeatPassword,
|
||||
token
|
||||
})
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
$('#reset-pass-info').modal('show')
|
||||
// window.location = '/'
|
||||
} else {
|
||||
alert(response.data.message)
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
// Very unlikely, but can happen
|
||||
alert(i18next.t('Napaka na strežniku'))
|
||||
console.log(error)
|
||||
// DEBUG SUCCESS DUE TO NO ENDPOINT $('#reset-pass-info').modal('show') <<- REMOVE
|
||||
passwordRepeat
|
||||
})
|
||||
|
||||
window.location = '/'
|
||||
} catch (error) {
|
||||
displayError(error)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Handle cancel event
|
||||
document.querySelector('#cancel-btn').addEventListener('click', event => {
|
||||
event.preventDefault()
|
||||
window.location = '/'
|
||||
})
|
||||
function displayError(error) {
|
||||
let message = i18next.t('Prišlo je do napake.')
|
||||
if (error.response) {
|
||||
message = error.response.data
|
||||
} else if (error.request) {
|
||||
message = i18next.t('Strežnik ni dosegljiv. Poskusite kasneje.')
|
||||
}
|
||||
|
||||
// Handle redirect on success
|
||||
document.querySelector('#modal-fp-info-close').addEventListener('click', () => {
|
||||
window.location = '/'
|
||||
})
|
||||
document.querySelector('#fpi-text.normal-gray').textContent = message
|
||||
$('#reset-pass-info').modal('show')
|
||||
}
|
||||
|
||||
@@ -953,85 +953,45 @@ if (registerSwithcButton) {
|
||||
|
||||
$(document).ready(function () {
|
||||
// TODO refactor wth specific select2
|
||||
$('.select-search-field').select2({})
|
||||
|
||||
initSelect2('.select-domain-field', 'Področje')
|
||||
initSelect2('.select-src-lang-field', 'Jezik iskanja')
|
||||
initSelect2('.select-dest-lang-field', 'Ciljni jezik')
|
||||
initSelect2('.select-dict-field', 'Slovar')
|
||||
initSelect2('.select-source-field', 'Vir')
|
||||
isI18nReady.then(t => {
|
||||
$('.select-search-field').select2({})
|
||||
|
||||
$('b[role="presentation"]').hide()
|
||||
$('.select2-selection__arrow').append(
|
||||
'<img src="/images/chevron-down-darker.svg" alt="V"></img>'
|
||||
)
|
||||
initSelect2('.select-domain-field', t('Področje'))
|
||||
initSelect2('.select-src-lang-field', t('Jezik iskanja'))
|
||||
initSelect2('.select-dest-lang-field', t('Ciljni jezik'))
|
||||
initSelect2('.select-dict-field', t('Slovar'))
|
||||
initSelect2('.select-source-field', t('Vir'))
|
||||
|
||||
$('.select-search-field').on('select2:select', function (e) {
|
||||
selectActiveElementFromInput(e)
|
||||
// console.log(activeInput)
|
||||
// console.log(e.target.parentNode.children[2])
|
||||
// const label = e.target.parentNode.children[2]
|
||||
$('b[role="presentation"]').hide()
|
||||
$('.select2-selection__arrow').append(
|
||||
'<img src="/images/chevron-down-darker.svg" alt="V"></img>'
|
||||
)
|
||||
|
||||
// console.log(activeInput)
|
||||
// console.log(activeInput)
|
||||
activeInput.addTag(e.params.data._resultId)
|
||||
activeInput.labelJump()
|
||||
})
|
||||
$('.select-search-field').on('select2:select', function (e) {
|
||||
selectActiveElementFromInput(e)
|
||||
// console.log(activeInput)
|
||||
// console.log(e.target.parentNode.children[2])
|
||||
// const label = e.target.parentNode.children[2]
|
||||
|
||||
$('.select-search-field').on('select2:unselect', function (e) {
|
||||
selectActiveElementFromInput(e)
|
||||
// console.log(activeInput)
|
||||
// console.log(activeInput)
|
||||
activeInput.removeTag(e.params.data._resultId)
|
||||
activeInput.labelJump()
|
||||
// console.log('DELETED ' + e)
|
||||
})
|
||||
// console.log(activeInput)
|
||||
// console.log(activeInput)
|
||||
activeInput.addTag(e.params.data._resultId)
|
||||
activeInput.labelJump()
|
||||
})
|
||||
|
||||
inputs.forEach(e => {
|
||||
e.input = e.domElement.querySelector('.select2-search__field')
|
||||
})
|
||||
})
|
||||
$('.select-search-field').on('select2:unselect', function (e) {
|
||||
selectActiveElementFromInput(e)
|
||||
// console.log(activeInput)
|
||||
// console.log(activeInput)
|
||||
activeInput.removeTag(e.params.data._resultId)
|
||||
activeInput.labelJump()
|
||||
// console.log('DELETED ' + e)
|
||||
})
|
||||
|
||||
/* TODO REMOVE OTHER SCRIPTS WHEN YOU FINISH MODULARIZING THINGS THAT COULD BE MODULARIZED */
|
||||
|
||||
$(document).ready(function () {
|
||||
// TODO refactor wth specific select2
|
||||
$('.select-search-field').select2({})
|
||||
|
||||
initSelect2('.select-domain-field', 'Področje')
|
||||
initSelect2('.select-src-lang-field', 'Jezik iskanja')
|
||||
initSelect2('.select-dest-lang-field', 'Ciljni jezik')
|
||||
initSelect2('.select-dict-field', 'Slovar')
|
||||
initSelect2('.select-source-field', 'Vir')
|
||||
|
||||
$('b[role="presentation"]').hide()
|
||||
$('.select2-selection__arrow').append(
|
||||
'<img src="/images/chevron-down-darker.svg" alt="V"></img>'
|
||||
)
|
||||
|
||||
$('.select-search-field').on('select2:select', function (e) {
|
||||
selectActiveElementFromInput(e)
|
||||
// console.log(activeInput)
|
||||
// console.log(e.target.parentNode.children[2])
|
||||
// const label = e.target.parentNode.children[2]
|
||||
|
||||
// console.log(activeInput)
|
||||
// console.log(activeInput)
|
||||
activeInput.addTag(e.params.data._resultId)
|
||||
activeInput.labelJump()
|
||||
})
|
||||
|
||||
$('.select-search-field').on('select2:unselect', function (e) {
|
||||
selectActiveElementFromInput(e)
|
||||
// console.log(activeInput)
|
||||
// console.log(activeInput)
|
||||
activeInput.removeTag(e.params.data._resultId)
|
||||
activeInput.labelJump()
|
||||
// console.log('DELETED ' + e)
|
||||
})
|
||||
|
||||
inputs.forEach(e => {
|
||||
e.input = e.domElement.querySelector('.select2-search__field')
|
||||
inputs.forEach(e => {
|
||||
e.input = e.domElement.querySelector('.select2-search__field')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1192,7 +1152,7 @@ const forgottenPasswordInvoker = new StateManagerInvoker(stateManager, () => {
|
||||
}
|
||||
|
||||
function apllyDescriptionAccordingToState() {
|
||||
$('#alert-text').text(stateManager.getState().fpassWindowDescription)
|
||||
$('#fpi-text').text(stateManager.getState().fpassWindowDescription)
|
||||
}
|
||||
|
||||
closeAllFPRelatedModals()
|
||||
@@ -1233,8 +1193,21 @@ function onCancelForgotPassword() {
|
||||
}
|
||||
|
||||
function onSendForgottenEmailRequest() {
|
||||
if (document.getElementById('forgot-pass-input').value === '') {
|
||||
stateManager.setState({
|
||||
fpassWindowDescription: i18next.t(
|
||||
'Prosimo vnesite vaš elektronski naslov.'
|
||||
)
|
||||
})
|
||||
|
||||
forgottenPasswordInvoker.setState({
|
||||
fpassWindowState: ForgotPasswordState.FORGOT_PASSWORD_ERROR
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
axios
|
||||
.post('/REPLACETHISDUMMYURL', {
|
||||
.post('/api/v1/users/reset-password-init', {
|
||||
usernameOrEmail: document.getElementById('forgot-pass-input').value
|
||||
})
|
||||
.then(res => {
|
||||
@@ -1253,11 +1226,18 @@ function onSendForgottenEmailRequest() {
|
||||
})
|
||||
})
|
||||
.catch(err => {
|
||||
stateManager.setState({
|
||||
fpassWindowDescription: i18next.t(
|
||||
'Prišlo je do napake pri pošiljanju sporočila na vaš elektronski naslov. Poskusite ponovno.'
|
||||
)
|
||||
})
|
||||
if (err.response && err.response.data) {
|
||||
stateManager.setState({
|
||||
fpassWindowDescription: err.response.data
|
||||
})
|
||||
} else {
|
||||
stateManager.setState({
|
||||
fpassWindowDescription: i18next.t(
|
||||
'Prišlo je do napake pri pošiljanju sporočila na vaš elektronski naslov. Poskusite ponovno.'
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
forgottenPasswordInvoker.setState({
|
||||
fpassWindowState: ForgotPasswordState.FORGOT_PASSWORD_ERROR
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* global $, axios, currentPagePath, initPagination,
|
||||
removeAllChildNodes, transferText, tooltipTriggerList,
|
||||
tooltipList, createTooltip, resetTooltipTriggerList,
|
||||
transferTextExtended, i18next */
|
||||
transferTextExtended, isI18nReady, i18next */
|
||||
|
||||
// position correction functions
|
||||
|
||||
@@ -278,20 +278,22 @@ window.addEventListener('load', () => {
|
||||
$('[data-toggle="tooltip"]').tooltip()
|
||||
})
|
||||
|
||||
function handleProperTextDisplay() {
|
||||
// const BROWSER_UNUSUAL_OFFSET = 17
|
||||
if (/\/iskanje/.test(currentPagePath)) {
|
||||
transferText('Iskanje po slovarjih', true, 'site-heading') //,
|
||||
// BROWSER_UNUSUAL_OFFSET
|
||||
// )
|
||||
} else if (/\/termin/.test(currentPagePath)) {
|
||||
transferText('', true, 'site-heading') // , BROWSER_UNUSUAL_OFFSET)
|
||||
isI18nReady.then(t => {
|
||||
function handleProperTextDisplay() {
|
||||
// const BROWSER_UNUSUAL_OFFSET = 17
|
||||
if (/\/iskanje/.test(currentPagePath)) {
|
||||
transferText(t('Iskanje po slovarjih'), true, 'site-heading') //,
|
||||
// BROWSER_UNUSUAL_OFFSET
|
||||
// )
|
||||
} else if (/\/termin/.test(currentPagePath)) {
|
||||
transferText('', true, 'site-heading') // , BROWSER_UNUSUAL_OFFSET)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
window.addEventListener('resize', () => {
|
||||
handleProperTextDisplay()
|
||||
adjustOffsetBy()
|
||||
})
|
||||
|
||||
handleProperTextDisplay()
|
||||
adjustOffsetBy()
|
||||
})
|
||||
|
||||
handleProperTextDisplay()
|
||||
|
||||
@@ -222,7 +222,7 @@
|
||||
"Modul za svetovanje pri terminoloških zagatah.": "Terminology consulting module.",
|
||||
"Modul za urejanje terminoloških slovarjev.": "Module for editing terminology dictionaries.",
|
||||
"MODULI": "MODULES",
|
||||
"Moj Profil": "My Profile",
|
||||
"Moj Račun": "My Account",
|
||||
"Moji slovarji": "My dictionaries",
|
||||
"Morebitne že obstoječe poimenovalne rešitve, če obstajajo.": "Any already existing naming solutions.",
|
||||
"Morebitni primeri rabe termina v besedilih ali povezave do njih, če obstajajo.": "Any examples of term use in texts or links to text where the term is used.",
|
||||
@@ -234,7 +234,7 @@
|
||||
"Na tem mestu lahko določite področne oznake, če želite posamezne termine v svojem terminološkem slovarju razvrstiti še podrobneje.": "Here you can define domain labels, if you want to classify individual terms in your terminology dictionary in more detail.",
|
||||
"Na tem mestu so zbrani vsi komentarji, povezani s terminološkim portalom.": "All comments related to the terminology portal can be found here.",
|
||||
"Na tem mestu so zbrani vsi komentarji, povezani z izbranim terminološkim slovarjem.": "All comments related to the selected terminology dictionary can be found here.",
|
||||
"na vaš elektronski naslov smo vam poslali sporočilo s povezavo, s katero boste potrdili svoj uporabniški račun na Terminološkem portalu. Povezava za potrditev je veljavna ": "we a message with a link to verify your Termonology Portal user account to your e-mail address. The verification link is valid.",
|
||||
"na vaš elektronski naslov smo vam poslali sporočilo s povezavo, s katero boste potrdili svoj uporabniški račun na Terminološkem portalu. Povezava za potrditev je veljavna ": "we have sent a message with a link to verify your Termonology Portal user account to your e-mail address. The verification link will be valid ",
|
||||
"Na vaš elektronski naslov smo vam posredovali povezavo za ponastavitev gesla. Prosimo preverite svoj elektronski predal.": "We sent a password reset link to your e-mail address. Please, check your inbox.",
|
||||
"Nabor vseh luščenj, s katerimi lahko uporabnik iz izbranih besedil pridobi sezname terminoloških kandidatov. Posamezne terminološke kandidate lahko preveri tudi v konkordančniku.": "All extractions the users can use to obtain a list of term candidates from the selected texts. The user can also check individual term candidates using the concordance tool.",
|
||||
"Nabor vseh luščenj, s pomočjo katerih uporabnik iz vhodnih besedil pridobi sezname terminoloških kandidatov in dostop do konkordančnika po teh besedilih.": "All extractions the users can use to obtain a list of term candidates from the selected texts and access to the concordance tools for these texts.",
|
||||
@@ -407,7 +407,7 @@
|
||||
"POVEZANI TERMIN": "RELATED TERM",
|
||||
"POVEZANI TERMIN:": "RELATED TERM:",
|
||||
"POVEZAVA": "LINK",
|
||||
"Povezava ni (več) veljavna. Prosimo, da ponovno zahtevate ponastavitev gesla.": "The link is (no longer) valid. Please, request new password reset link.",
|
||||
"Povezava ni (več) veljavna. Prosimo, da ponovno zahtevate ponastavitev gesla.": "This link is invalid or expired. Please, request new password reset link.",
|
||||
"Povezave": "Links",
|
||||
"Povezave s portali": "Links to other portals",
|
||||
"Pozabljeno geslo": "Forgoten password",
|
||||
@@ -540,7 +540,7 @@
|
||||
"Strokovni pregled": "Terminology review",
|
||||
"Strokovno pregledano": "Terminologically reviewed",
|
||||
"Struktura": "Structure",
|
||||
"STRUKTURA - ELEMENTI SLOVARSKEGA SESTAVKA:": "STRUCTURE - ELEMENTS OF THA DICTIONARY ENTRY:",
|
||||
"STRUKTURA - ELEMENTI SLOVARSKEGA SESTAVKA:": "STRUCTURE - ELEMENTS OF THE DICTIONARY ENTRY:",
|
||||
"Struktura slovarskega sestavka": "The structure of the dictionary entry",
|
||||
"Svetovalci": "Consultants",
|
||||
"Svetovalec": "Consultant",
|
||||
@@ -706,5 +706,35 @@
|
||||
"ZVOK": "AUDIO",
|
||||
"ZVOK:": "AUDIO:",
|
||||
"titleTermsOfUse": "Terms of Use",
|
||||
"titlePrivacyPolicy": "Privacy Policy"
|
||||
"titlePrivacyPolicy": "Privacy Policy",
|
||||
"userStatusregistered": "registered",
|
||||
"userStatusactive": "active",
|
||||
"userStatusinactive": "inactive",
|
||||
"userStatusclosed": "closed",
|
||||
"Nepravilno uporabniško ime ali elektronski naslov.": "Invalid user name or e-mail address.",
|
||||
"Ponastavitev gesla": "Password reset",
|
||||
"Uspešna ponastavitev gesla": "Password successfully reset",
|
||||
"Vaše geslo je bilo uspešno ponastavljeno.": "Your password has been successfully reset.",
|
||||
"Prosimo vnesite vaš elektronski naslov.": "Please, enter your e-mail address.",
|
||||
"Sprememba elektronskega naslova": "Change e-mail address",
|
||||
"Na vaš elektronski naslov smo vam poslali sporočilo s povezavo, s katero boste potrdili menjavo elektronskega naslova. Povezava za potrditev je veljavna ": "We have sent a message with a link to confirm the change of your e-mail address to your new e-mail adress. The verification link will be valid ",
|
||||
"Sprememba elektronskega naslova - uspeh": "E-mail address change successful",
|
||||
"Uspešno ste spremenili svoj elektronski naslov.": "You have successfully changed your e-mail address.",
|
||||
"Elektronski naslov uporablja že drug uporabnik.": "This e-mail address is already used by another user.",
|
||||
"Povezava ni (več) veljavna. Elektronski naslov ni bil spremenjen.": "This link is invalid or expired. The e-mail address has not been changed.",
|
||||
"Vaš uporabniški račun je bil uspešno izbrisan.": "Your user account has been successfully deleted.",
|
||||
"Ali res želite izbrisati svoj račun? S tem boste trajno izgubili dostop do podatkov, ki ste jih ustvarili.": "Do you really want to delete your account? If you delete your account, you will permanently lose access to all data you created.",
|
||||
"Uspešno ste aktivirali svoj uporabniški račun in se prijavili.": "You have successfully activated your user account and signed in.",
|
||||
"Povezava ni (več) veljavna. Prosimo, da se ponovno registrirate.": "This link is invalid or expired. Please, register again.",
|
||||
"Nepravilno staro geslo.": "Invalid current password.",
|
||||
"Sprememba gesla": "Change password",
|
||||
"Izbrano uporabniško ime uporablja že drug uporabnik.": "This user name is already taken.",
|
||||
"Izpolnite vsa prazna polja.": "Fill-in all empty fields.",
|
||||
"Jezik iskanja": "Search language",
|
||||
"Ciljni jezik": "Target language",
|
||||
"Spremenjen": "Changed",
|
||||
"Število slovarskih sestavkov": "Number of dictionary entries",
|
||||
"VPRAŠANJE": "QUESTION",
|
||||
"MNENJE": "OPINION",
|
||||
"ANGLEŠKI PREVOD": "ENGLISH TRANSLATION"
|
||||
}
|
||||
|
||||
@@ -222,7 +222,7 @@
|
||||
"Modul za svetovanje pri terminoloških zagatah.": "Modul za svetovanje pri terminoloških zagatah.",
|
||||
"Modul za urejanje terminoloških slovarjev.": "Modul za urejanje terminoloških slovarjev.",
|
||||
"MODULI": "MODULI",
|
||||
"Moj Profil": "Moj Profil",
|
||||
"Moj Račun": "Moj Račun",
|
||||
"Moji slovarji": "Moji slovarji",
|
||||
"Morebitne že obstoječe poimenovalne rešitve, če obstajajo.": "Morebitne že obstoječe poimenovalne rešitve, če obstajajo.",
|
||||
"Morebitni primeri rabe termina v besedilih ali povezave do njih, če obstajajo.": "Morebitni primeri rabe termina v besedilih ali povezave do njih, če obstajajo.",
|
||||
@@ -706,5 +706,35 @@
|
||||
"ZVOK": "ZVOK",
|
||||
"ZVOK:": "ZVOK:",
|
||||
"titleTermsOfUse": "Pogoji uporabe",
|
||||
"titlePrivacyPolicy": "Politika zasebnosti"
|
||||
"titlePrivacyPolicy": "Politika zasebnosti",
|
||||
"userStatusregistered": "registriran",
|
||||
"userStatusactive": "aktiven",
|
||||
"userStatusinactive": "neaktiven",
|
||||
"userStatusclosed": "zaprt",
|
||||
"Nepravilno uporabniško ime ali elektronski naslov.": "Nepravilno uporabniško ime ali elektronski naslov.",
|
||||
"Ponastavitev gesla": "Ponastavitev gesla",
|
||||
"Uspešna ponastavitev gesla": "Uspešna ponastavitev gesla",
|
||||
"Vaše geslo je bilo uspešno ponastavljeno.": "Vaše geslo je bilo uspešno ponastavljeno.",
|
||||
"Prosimo vnesite vaš elektronski naslov.": "Prosimo vnesite vaš elektronski naslov.",
|
||||
"Sprememba elektronskega naslova": "Sprememba elektronskega naslova",
|
||||
"Na vaš elektronski naslov smo vam poslali sporočilo s povezavo, s katero boste potrdili menjavo elektronskega naslova. Povezava za potrditev je veljavna ": "Na vaš elektronski naslov smo vam poslali sporočilo s povezavo, s katero boste potrdili menjavo elektronskega naslova. Povezava za potrditev je veljavna ",
|
||||
"Sprememba elektronskega naslova - uspeh": "Sprememba elektronskega naslova - uspeh",
|
||||
"Uspešno ste spremenili svoj elektronski naslov.": "Uspešno ste spremenili svoj elektronski naslov.",
|
||||
"Elektronski naslov uporablja že drug uporabnik.": "Elektronski naslov uporablja že drug uporabnik.",
|
||||
"Povezava ni (več) veljavna. Elektronski naslov ni bil spremenjen.": "Povezava ni (več) veljavna. Elektronski naslov ni bil spremenjen.",
|
||||
"Vaš uporabniški račun je bil uspešno izbrisan.": "Vaš uporabniški račun je bil uspešno izbrisan.",
|
||||
"Ali res želite izbrisati svoj račun? S tem boste trajno izgubili dostop do podatkov, ki ste jih ustvarili.": "Ali res želite izbrisati svoj račun? S tem boste trajno izgubili dostop do podatkov, ki ste jih ustvarili.",
|
||||
"Uspešno ste aktivirali svoj uporabniški račun in se prijavili.": "Uspešno ste aktivirali svoj uporabniški račun in se prijavili.",
|
||||
"Povezava ni (več) veljavna. Prosimo, da se ponovno registrirate.": "Povezava ni (več) veljavna. Prosimo, da se ponovno registrirate.",
|
||||
"Nepravilno staro geslo.": "Nepravilno staro geslo.",
|
||||
"Sprememba gesla": "Sprememba gesla",
|
||||
"Izbrano uporabniško ime uporablja že drug uporabnik.": "Izbrano uporabniško ime uporablja že drug uporabnik.",
|
||||
"Izpolnite vsa prazna polja.": "Izpolnite vsa prazna polja.",
|
||||
"Jezik iskanja": "Jezik iskanja",
|
||||
"Ciljni jezik": "Ciljni jezik",
|
||||
"Spremenjen": "Spremenjen",
|
||||
"Število slovarskih sestavkov": "Število slovarskih sestavkov",
|
||||
"VPRAŠANJE": "VPRAŠANJE",
|
||||
"MNENJE": "MNENJE",
|
||||
"ANGLEŠKI PREVOD": "ANGLEŠKI PREVOD"
|
||||
}
|
||||
|
||||
@@ -6,18 +6,18 @@ const portal = require('../controllers/portals')
|
||||
// All routes require an authenticated user.
|
||||
router.use((req, res, next) => {
|
||||
if (req.isAuthenticated()) return next()
|
||||
res.redirect('/')
|
||||
res.redirect(303, '/')
|
||||
})
|
||||
|
||||
// Menu entry point. Redirect based on role.
|
||||
router.get('/', (req, res, next) => {
|
||||
const { user, baseUrl } = req
|
||||
if (user.hasRole('portal admin')) {
|
||||
return res.redirect(`${baseUrl}/nastavitve/portal`)
|
||||
return res.redirect(303, `${baseUrl}/nastavitve/portal`)
|
||||
} else if (user.hasRole('dictionaries admin')) {
|
||||
return res.redirect(`${baseUrl}/slovarji`)
|
||||
return res.redirect(303, `${baseUrl}/slovarji`)
|
||||
}
|
||||
res.redirect('/')
|
||||
res.redirect(303, '/')
|
||||
})
|
||||
|
||||
// Authorize endpoints for portal admin.
|
||||
@@ -25,14 +25,14 @@ router.use(
|
||||
['/nastavitve', '/povezave', '/uporabniki', '/komentarji'],
|
||||
(req, res, next) => {
|
||||
if (req.user.hasRole('portal admin')) return next()
|
||||
res.redirect('/')
|
||||
res.redirect(303, '/')
|
||||
}
|
||||
)
|
||||
|
||||
// Authorize endpoints for dictionaries admin.
|
||||
router.use(['/slovarji', '/podrocja'], (req, res, next) => {
|
||||
router.use(['/slovarji', '/podpodrocja'], (req, res, next) => {
|
||||
if (req.user.hasRole('dictionaries admin')) return next()
|
||||
res.redirect('/')
|
||||
res.redirect(303, '/')
|
||||
})
|
||||
|
||||
router.get('/nastavitve/portal', portal.instanceSettings)
|
||||
@@ -59,9 +59,9 @@ router.get('/povezave/seznam/:portalId', portal.fetchSelectedLinkedDictionaries)
|
||||
|
||||
router.post('/povezave/seznam/:portalId', portal.updateSelectedDictionaries)
|
||||
|
||||
router.get('/povezava/:portalId/urejanje', portal.fetchPortal)
|
||||
router.get('/povezave/:portalId/urejanje', portal.fetchPortal)
|
||||
|
||||
router.post('/povezava/:portalId/urejanje', portal.updatePortal)
|
||||
router.post('/povezave/:portalId/urejanje', portal.updatePortal)
|
||||
|
||||
// Show a list of user's dictionaries.
|
||||
router.get('/slovarji', dictionary.listAdminDictionaries)
|
||||
|
||||
@@ -3,20 +3,12 @@ const router = require('express-promise-router')()
|
||||
const {
|
||||
listComments,
|
||||
createComment,
|
||||
seedComments,
|
||||
clearComments,
|
||||
updateStatus
|
||||
} = require('../../../controllers/api/v1/comments')
|
||||
|
||||
// List comments (one page).
|
||||
router.get('/', listComments)
|
||||
|
||||
// TEMP - Seed comments.
|
||||
router.get('/seed/:commentCount', seedComments)
|
||||
|
||||
// TEMP - Clear comments.
|
||||
router.get('/clear', clearComments)
|
||||
|
||||
// All further routes are only available to authenticated users.
|
||||
router.use((req, res, next) => {
|
||||
if (req.isAuthenticated()) return next()
|
||||
|
||||
@@ -2,8 +2,6 @@ const router = require('express-promise-router')()
|
||||
|
||||
const {
|
||||
assign,
|
||||
listEntries,
|
||||
listNewEntries,
|
||||
createQuestion,
|
||||
deleteQuestion,
|
||||
updateDomain,
|
||||
@@ -23,10 +21,6 @@ const {
|
||||
|
||||
router.get('/entry-pagination', sendPaginationData)
|
||||
|
||||
router.get('/entry', listEntries)
|
||||
|
||||
router.get('/new-entry', listNewEntries)
|
||||
|
||||
// All routes require an authenticated user.
|
||||
router.use((req, res, next) => {
|
||||
if (req.isAuthenticated()) return next()
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
const router = require('express-promise-router')()
|
||||
const demoPaginacija = require('../../../controllers/api/v1/demo-paginacija')
|
||||
|
||||
// Tale endpoint vrača rezultate.
|
||||
router.get('/list', demoPaginacija.list)
|
||||
|
||||
module.exports = router
|
||||
@@ -1,54 +1,91 @@
|
||||
const router = require('express-promise-router')()
|
||||
|
||||
const dictionaries = require('../../../controllers/api/v1/dictionaries')
|
||||
const user = require('../../../middleware/user')
|
||||
|
||||
// Update domain labels.
|
||||
router.post('/update-domain-labels', dictionaries.updateDomainLabels)
|
||||
|
||||
// Update secondary domains
|
||||
router.post('/renovateSecondaryDomains', dictionaries.renovateSecondaryDomains)
|
||||
router.post(
|
||||
'/renovateSecondaryDomains',
|
||||
user.canAdministrateDictionaries,
|
||||
dictionaries.renovateSecondaryDomains
|
||||
)
|
||||
|
||||
// Delete specific dictionary.
|
||||
router.delete('/:dictionaryId', dictionaries.delete)
|
||||
router.delete(
|
||||
'/:dictionaryId',
|
||||
user.canAdministrateDictionary,
|
||||
dictionaries.delete
|
||||
)
|
||||
|
||||
// Change page in pagination
|
||||
router.get('/listAllDictionaries', dictionaries.listDictionaries)
|
||||
router.get(
|
||||
'/listAllDictionaries',
|
||||
user.canAdministrateDictionaries,
|
||||
dictionaries.listDictionaries
|
||||
)
|
||||
|
||||
// Change page in pagination
|
||||
router.get('/:dictionaryId/listDomainLabels', dictionaries.listDomainLabels)
|
||||
|
||||
// Change page in pagination
|
||||
router.get('/listSecondaryDomains', dictionaries.listSecondaryDomains)
|
||||
router.get(
|
||||
'/:dictionaryId/listDomainLabels',
|
||||
user.canAdministrateDictionary,
|
||||
dictionaries.listDomainLabels
|
||||
)
|
||||
|
||||
// Change page in pagination
|
||||
router.get(
|
||||
'/:dictionaryId/showImportFromFileForm',
|
||||
user.canContentEdit,
|
||||
dictionaries.showImportFromFileForm
|
||||
)
|
||||
|
||||
// Change page in pagination
|
||||
router.get(
|
||||
'/:dictionaryId/showExportToFileForm',
|
||||
user.canContentEdit,
|
||||
dictionaries.showExportToFileForm
|
||||
)
|
||||
|
||||
// Delete all entries of specific dictionary.
|
||||
router.delete('/:dictionaryId/entries/all', dictionaries.deleteAllEntries)
|
||||
router.delete(
|
||||
'/:dictionaryId/entries/all',
|
||||
user.canAdministrateDictionary,
|
||||
dictionaries.deleteAllEntries
|
||||
)
|
||||
|
||||
// Publish all entries of specific dictionary.
|
||||
router.put('/:dictionaryId/entries/all/publish', dictionaries.publishAllEntries)
|
||||
router.put(
|
||||
'/:dictionaryId/entries/all/publish',
|
||||
user.canAdministrateDictionary,
|
||||
dictionaries.publishAllEntries
|
||||
)
|
||||
|
||||
// Import term candidates from extraction.
|
||||
router.post(
|
||||
'/:id/import-extraction/:extractionId',
|
||||
'/:dictionaryId/import-extraction/:extractionId',
|
||||
user.canContentEdit,
|
||||
dictionaries.importFromExtraction
|
||||
)
|
||||
|
||||
// Export dictionary into a file.
|
||||
router.post('/:id/export-begin', dictionaries.exportBegin)
|
||||
router.post(
|
||||
'/:dictionaryId/export-begin',
|
||||
user.canContentEdit,
|
||||
dictionaries.exportBegin
|
||||
)
|
||||
|
||||
router.get('/:dictionaryId/domainLabels', dictionaries.listFilteredDomainLabels)
|
||||
router.get(
|
||||
'/:dictionaryId/domainLabels',
|
||||
user.canContentEdit,
|
||||
dictionaries.listFilteredDomainLabels
|
||||
)
|
||||
|
||||
router.get('/secondaryDomains', dictionaries.listSecondaryDomainData)
|
||||
router.get(
|
||||
'/secondaryDomains',
|
||||
user.canAdministrateDictionaries,
|
||||
dictionaries.listSecondaryDomainData
|
||||
)
|
||||
|
||||
module.exports = router
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// This page refers to dictionary entries, not consultancy or other entries
|
||||
const router = require('express-promise-router')()
|
||||
const user = require('../../../middleware/user')
|
||||
|
||||
const {
|
||||
getEntry,
|
||||
@@ -10,7 +11,8 @@ const {
|
||||
getEntryVersionSnapshot
|
||||
} = require('../../../controllers/api/v1/dictionaries')
|
||||
|
||||
// TODO Add authorization.
|
||||
// All further routes are only available to authenticated users with dictionary content editing rights.
|
||||
router.use(user.isAuthenticated, user.canContentEdit)
|
||||
|
||||
// Render entry data for the selected entry
|
||||
router.get('/', getEntry)
|
||||
|
||||
@@ -1,54 +1,76 @@
|
||||
const router = require('express-promise-router')()
|
||||
|
||||
const extraction = require('../../../controllers/api/v1/extraction')
|
||||
const { validateOwnership } = require('../../../controllers/extraction')
|
||||
|
||||
// TODO In final version test authentication and authorization.
|
||||
// TODO Also try to lock file editing or other operations once extraction was started.
|
||||
// TODO Try to lock file editing or other operations once extraction was started.
|
||||
|
||||
// All further routes are only available to authenticated users.
|
||||
router.use((req, res, next) => {
|
||||
if (req.isAuthenticated()) return next()
|
||||
res.status(400).end()
|
||||
})
|
||||
|
||||
// Delete extraction.
|
||||
router.delete('/:id', extraction.delete)
|
||||
router.delete('/:id', validateOwnership, extraction.delete)
|
||||
|
||||
// List documents.
|
||||
router.get('/:id/documents', extraction.docsList)
|
||||
router.get('/:id/documents', validateOwnership, extraction.docsList)
|
||||
|
||||
// Update documents.
|
||||
router.put('/:id/documents', extraction.docsUpdate)
|
||||
router.put('/:id/documents', validateOwnership, extraction.docsUpdate)
|
||||
|
||||
// Delete a specific document.
|
||||
router.delete('/:id/documents/:filename', extraction.docDelete)
|
||||
router.delete(
|
||||
'/:id/documents/:filename',
|
||||
validateOwnership,
|
||||
extraction.docDelete
|
||||
)
|
||||
|
||||
// List stop term files.
|
||||
router.get('/:id/stop-terms', extraction.stopTermsList)
|
||||
router.get('/:id/stop-terms', validateOwnership, extraction.stopTermsList)
|
||||
|
||||
// Update stop term files.
|
||||
router.put('/:id/stop-terms', extraction.stopTermsUpdate)
|
||||
router.put('/:id/stop-terms', validateOwnership, extraction.stopTermsUpdate)
|
||||
|
||||
// Delete a specific stop term file.
|
||||
router.delete('/:id/stop-terms/:filename', extraction.stopTermDelete)
|
||||
router.delete(
|
||||
'/:id/stop-terms/:filename',
|
||||
validateOwnership,
|
||||
extraction.stopTermDelete
|
||||
)
|
||||
|
||||
// Save oss params.
|
||||
router.post('/:id/oss-save-params', extraction.ossSaveParams)
|
||||
router.post('/:id/oss-save-params', validateOwnership, extraction.ossSaveParams)
|
||||
|
||||
// Execute search by oss params.
|
||||
router.put('/:id/oss-search', extraction.ossSearch)
|
||||
router.put('/:id/oss-search', validateOwnership, extraction.ossSearch)
|
||||
|
||||
// Confirm oss params.
|
||||
router.put('/:id/oss-confirm-params', extraction.ossConfirmParams)
|
||||
router.put(
|
||||
'/:id/oss-confirm-params',
|
||||
validateOwnership,
|
||||
extraction.ossConfirmParams
|
||||
)
|
||||
|
||||
// Begin extraction.
|
||||
router.put('/:id/begin', extraction.begin)
|
||||
router.put('/:id/begin', validateOwnership, extraction.begin)
|
||||
|
||||
// Duplicate extraction.
|
||||
router.post('/:id/duplicate', extraction.duplicate)
|
||||
router.post('/:id/duplicate', validateOwnership, extraction.duplicate)
|
||||
|
||||
// Export term candidates.
|
||||
router.get('/:id/term-candidates-export', extraction.termCandidatesExport)
|
||||
|
||||
// List all finished extractions for current user.
|
||||
// TODO Is this enpoint even necessarry?
|
||||
router.get('/finished-for-user', extraction.listFinishedForUser)
|
||||
router.get(
|
||||
'/:id/term-candidates-export',
|
||||
validateOwnership,
|
||||
extraction.termCandidatesExport
|
||||
)
|
||||
|
||||
// List term candidates for specific extraction.
|
||||
router.get('/:id/term-candidates', extraction.listTermCandidates)
|
||||
router.get(
|
||||
'/:id/term-candidates',
|
||||
validateOwnership,
|
||||
extraction.listTermCandidates
|
||||
)
|
||||
|
||||
module.exports = router
|
||||
|
||||
@@ -7,7 +7,6 @@ const dictionariesRouter = require('./dictionaries')
|
||||
const portalsRouter = require('./portals')
|
||||
const searchRouter = require('./search')
|
||||
const consultancyRouter = require('./consultancy')
|
||||
const demoPaginacijaRouter = require('./demo-paginacija')
|
||||
const extractionRouter = require('./extraction')
|
||||
|
||||
router.use('/comments', commentsRouter)
|
||||
@@ -18,7 +17,6 @@ router.use('/dictionaries', dictionariesRouter)
|
||||
router.use('/portals', portalsRouter)
|
||||
router.use('/search', searchRouter)
|
||||
router.use('/consultancy', consultancyRouter)
|
||||
router.use('/demo-paginacija', demoPaginacijaRouter)
|
||||
router.use('/extraction', extractionRouter)
|
||||
|
||||
module.exports = router
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const router = require('express-promise-router')()
|
||||
const user = require('../../../middleware/user')
|
||||
|
||||
const {
|
||||
create,
|
||||
@@ -10,6 +11,9 @@ const {
|
||||
deleteLinkedDictionary
|
||||
} = require('../../../controllers/api/v1/portals')
|
||||
|
||||
// All further routes are only available to portal admin.
|
||||
router.use(user.isAuthenticated, user.isPortalAdmin)
|
||||
|
||||
// Create new portal connection
|
||||
router.post('/createPortal', create)
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@ const user = require('../../../middleware/user')
|
||||
// Return search results for main search.
|
||||
router.get('/main', search.listMainEntries)
|
||||
|
||||
// TODO This route assumes existance of user. Make sure to pre check it.
|
||||
// Return search results for editor search.
|
||||
router.get(
|
||||
'/editor/:dictionaryId',
|
||||
user.isDictionaryEditor,
|
||||
user.isAuthenticated,
|
||||
user.canContentEdit,
|
||||
search.listEditorEntries
|
||||
)
|
||||
|
||||
|
||||
@@ -4,12 +4,18 @@ const {
|
||||
listDictionaries,
|
||||
syncDictionary
|
||||
} = require('../../../controllers/api/v1/inter_instance_sync')
|
||||
const user = require('../../../middleware/user')
|
||||
|
||||
// Receive Content Security Policy violation reports.
|
||||
router.post('/csp-reports', system.handleCspReports)
|
||||
|
||||
// Trigger synchronization with Eurotermbank.
|
||||
router.get('/eurotermbank-sync-push', system.syncWithEurotermbank)
|
||||
router.get(
|
||||
'/eurotermbank-sync-push',
|
||||
user.isAuthenticated,
|
||||
user.isPortalAdmin,
|
||||
system.syncWithEurotermbank
|
||||
)
|
||||
|
||||
// List dictionaries (one page).
|
||||
router.get('/inter-instance-sync/dictionaries', listDictionaries)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const router = require('express-promise-router')()
|
||||
const user = require('../../../controllers/users')
|
||||
const users = require('../../../controllers/api/v1/users')
|
||||
const userAuth = require('../../../middleware/user')
|
||||
|
||||
// Validate and create a new user.
|
||||
router.post('/register', user.register)
|
||||
@@ -8,12 +9,38 @@ router.post('/register', user.register)
|
||||
// Validate and log the user in.
|
||||
router.post('/login', user.login)
|
||||
|
||||
router.get('/addUser', user.findByUsernameOrEmail)
|
||||
// Supply user with the token to set a new password.
|
||||
router.post('/reset-password-init', user.generateResetPasswordToken)
|
||||
|
||||
router.get('/listAllUsers', users.listUsers)
|
||||
// Validate new passwords, change if successful and sign the user in.
|
||||
router.post('/reset-password-submit', user.changePassword)
|
||||
|
||||
// Find user by username or email in dictionary or admin console.
|
||||
router.get(
|
||||
'/addUser',
|
||||
userAuth.isAuthenticated,
|
||||
userAuth.canAdministrateDictionary,
|
||||
user.findByUsernameOrEmail
|
||||
)
|
||||
|
||||
// List all users in the admin console.
|
||||
router.get(
|
||||
'/listAllUsers',
|
||||
userAuth.isAuthenticated,
|
||||
userAuth.isPortalAdmin,
|
||||
users.listUsers
|
||||
)
|
||||
|
||||
// Update hits per page setting of the requesting user.
|
||||
router.post('/hitsPerPage', users.updateHitsPerPage)
|
||||
|
||||
router.post('/nameAndSurname', users.updateFristNameAndSurname)
|
||||
// Update first name, last name and email of the requesting user.
|
||||
router.post('/basic-data', users.updateBasicData)
|
||||
|
||||
// Update password of the requesting user.
|
||||
router.post('/password', users.updatePassword)
|
||||
|
||||
// Delete user profile of the requesting user.
|
||||
router.delete('/current', users.deleteCurrent)
|
||||
|
||||
module.exports = router
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const router = require('express-promise-router')()
|
||||
|
||||
const user = require('../middleware/user')
|
||||
const { consultancy, consultancyAdmin } = require('../controllers/consultancy')
|
||||
|
||||
router.get('/', consultancy.index)
|
||||
@@ -12,23 +13,51 @@ router.get('/vprasanje/:id', consultancy.specificQuestion)
|
||||
|
||||
router.use((req, res, next) => {
|
||||
if (req.isAuthenticated()) return next()
|
||||
res.redirect('/')
|
||||
res.redirect(303, '/')
|
||||
})
|
||||
|
||||
router.get('/vprasanje/admin/novo', consultancyAdmin.new)
|
||||
router.get(
|
||||
'/vprasanje/admin/novo',
|
||||
user.canAdministrateConsultancy,
|
||||
consultancyAdmin.new
|
||||
)
|
||||
|
||||
router.get('/vprasanje/admin/svetovalci', consultancyAdmin.users)
|
||||
router.get(
|
||||
'/vprasanje/admin/svetovalci',
|
||||
user.canAdministrateConsultancy,
|
||||
consultancyAdmin.users
|
||||
)
|
||||
|
||||
router.get('/vprasanje/admin/zavrnjeno', consultancyAdmin.rejected)
|
||||
router.get(
|
||||
'/vprasanje/admin/zavrnjeno',
|
||||
user.canAdministrateConsultancy,
|
||||
consultancyAdmin.rejected
|
||||
)
|
||||
|
||||
router.get('/vprasanje/admin/objavljeno', consultancyAdmin.published)
|
||||
router.get(
|
||||
'/vprasanje/admin/objavljeno',
|
||||
user.canConsult,
|
||||
consultancyAdmin.published
|
||||
)
|
||||
|
||||
router.get('/vprasanje/admin/pripravljeno', consultancyAdmin.prepared)
|
||||
router.get(
|
||||
'/vprasanje/admin/pripravljeno',
|
||||
user.canAdministrateConsultancy,
|
||||
consultancyAdmin.prepared
|
||||
)
|
||||
|
||||
router.get('/vprasanje/admin/v-delu', consultancyAdmin.inProgress)
|
||||
router.get(
|
||||
'/vprasanje/admin/v-delu',
|
||||
user.canConsult,
|
||||
consultancyAdmin.inProgress
|
||||
)
|
||||
|
||||
router.get('/vprasanje/admin/statistika', consultancyAdmin.statistics)
|
||||
// router.get('/vprasanje/admin/statistika', consultancyAdmin.statistics)
|
||||
|
||||
router.get('/vprasanje/admin/urejanje/:id', consultancyAdmin.edit)
|
||||
router.get(
|
||||
'/vprasanje/admin/urejanje/:id',
|
||||
user.canConsultEntry,
|
||||
consultancyAdmin.edit
|
||||
)
|
||||
|
||||
module.exports = router
|
||||
|
||||
@@ -13,7 +13,7 @@ router.get('/:dictionaryId/o-slovarju', dictionary.dictionaryDetails)
|
||||
// All further routes are only available to authenticated users.
|
||||
router.use((req, res, next) => {
|
||||
if (req.isAuthenticated()) return next()
|
||||
res.redirect(req.baseUrl)
|
||||
res.redirect(303, req.baseUrl)
|
||||
})
|
||||
|
||||
// Show a form to create a new dictionary.
|
||||
@@ -92,13 +92,6 @@ router.get(
|
||||
dictionary.showContent
|
||||
)
|
||||
|
||||
// Validate and update dictionary content entries.
|
||||
// router.post(
|
||||
// '/:dictionaryId/vsebina',
|
||||
// user.isDictionaryEditor,
|
||||
// dictionary.updateContent
|
||||
// )
|
||||
|
||||
// Show a form to export a dictionary.
|
||||
router.get(
|
||||
'/:dictionaryId/izvoz',
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
const router = require('express-promise-router')()
|
||||
const extraction = require('../controllers/extraction-poc')
|
||||
|
||||
// TODO In final version test authentication and authorization.
|
||||
|
||||
// List all extractions.
|
||||
router.get('/', extraction.list)
|
||||
|
||||
// Create new extraction.
|
||||
router.post('/', extraction.create)
|
||||
|
||||
// Edit extraction.
|
||||
router.get('/:id', extraction.edit)
|
||||
|
||||
// Update extraction (own documents).
|
||||
router.post('/:id', extraction.updateOwn)
|
||||
|
||||
// Edit documents.
|
||||
router.get('/:id/besedila', extraction.docsEdit)
|
||||
|
||||
// Edit stop terms.
|
||||
router.get('/:id/stop-termini', extraction.stopTermsEdit)
|
||||
|
||||
// List term candidates for extraction and display export interface.
|
||||
router.get('/:id/kandidati', extraction.listTermCandidates)
|
||||
|
||||
module.exports = router
|
||||
@@ -1,6 +1,5 @@
|
||||
const router = require('express-promise-router')()
|
||||
const extraction = require('../controllers/extraction')
|
||||
const extractionPocRouter = require('./extraction-poc')
|
||||
|
||||
// List all extractions.
|
||||
router.get('/', extraction.list)
|
||||
@@ -8,54 +7,33 @@ router.get('/', extraction.list)
|
||||
// All further routes are only available to authenticated users.
|
||||
router.use((req, res, next) => {
|
||||
if (req.isAuthenticated()) return next()
|
||||
res.redirect(req.baseUrl)
|
||||
res.redirect(303, req.baseUrl)
|
||||
})
|
||||
|
||||
// Create new extraction.
|
||||
router.post('/', extraction.create)
|
||||
|
||||
router.use('/poc', extractionPocRouter)
|
||||
|
||||
// Edit extraction.
|
||||
router.get('/:id', extraction.edit)
|
||||
router.get('/:id', extraction.validateOwnership, extraction.edit)
|
||||
|
||||
// Update extraction (own documents).
|
||||
router.post('/:id', extraction.updateOwn)
|
||||
router.post('/:id', extraction.validateOwnership, extraction.updateOwn)
|
||||
|
||||
// Edit documents.
|
||||
router.get('/:id/besedila', extraction.docsEdit)
|
||||
router.get('/:id/besedila', extraction.validateOwnership, extraction.docsEdit)
|
||||
|
||||
// Edit stop terms.
|
||||
router.get('/:id/stop-termini', extraction.stopTermsEdit)
|
||||
router.get(
|
||||
'/:id/stop-termini',
|
||||
extraction.validateOwnership,
|
||||
extraction.stopTermsEdit
|
||||
)
|
||||
|
||||
// List term candidates for extraction and display export interface.
|
||||
router.get('/:id/kandidati', extraction.listTermCandidates)
|
||||
|
||||
// // TODO: Ask Luka or Miro what to do with this page.
|
||||
// router.get('/id_luscenja/kandidati', (req, res) => {
|
||||
// res.render('pages/extraction/terms', {
|
||||
// title: 'Terminološki kandidati'
|
||||
// })
|
||||
// })
|
||||
|
||||
// // Show a form to export a dictionary
|
||||
// router.get('/id_luscenja/dokumenti/besedila', (req, res) => {
|
||||
// res.render('pages/extraction/docs-edit', {
|
||||
// title: 'Besedila'
|
||||
// })
|
||||
// })
|
||||
|
||||
// // Show a form to export a dictionary
|
||||
// router.get('/id_luscenja/oss', (req, res) => {
|
||||
// res.render('pages/extraction/edit-oss', {
|
||||
// title: 'KAS + dokumenti'
|
||||
// })
|
||||
// })
|
||||
|
||||
// router.get('/id_luscenja/dokumenti/termini', (req, res) => {
|
||||
// res.render('pages/extraction/stop-terms-edit', {
|
||||
// title: 'Termini'
|
||||
// })
|
||||
// })
|
||||
router.get(
|
||||
'/:id/kandidati',
|
||||
extraction.validateOwnership,
|
||||
extraction.listTermCandidates
|
||||
)
|
||||
|
||||
module.exports = router
|
||||
|
||||
+11
-64
@@ -1,11 +1,7 @@
|
||||
const router = require('express-promise-router')()
|
||||
const { isAuthenticated: isUserAuthenticated } = require('../middleware/user')
|
||||
const db = require('../models/db')
|
||||
const cache = require('../models/cache')
|
||||
const { searchEngineClient } = require('../models/search-engine')
|
||||
const index = require('../controllers/index')
|
||||
const user = require('../controllers/users')
|
||||
const demoPaginacija = require('../controllers/demo-paginacija')
|
||||
|
||||
// TODO Some of these endpoints might need authorization protection. Review and clean up at a later point.
|
||||
|
||||
@@ -18,54 +14,6 @@ router.get('/iskanje', index.search)
|
||||
// Show a list of search results.
|
||||
router.get('/termin/:entryId', index.entryDetails)
|
||||
|
||||
/*
|
||||
router.get('/termin/id_termina', (req, res) => {
|
||||
res.render('pages/search/result-detail')
|
||||
})
|
||||
*/
|
||||
|
||||
router.get('/keys', (req, res) => {
|
||||
res.render('pages/search/special-keys-demo-rm-this')
|
||||
})
|
||||
|
||||
router.get('/demo', (req, res) => {
|
||||
res.render('pages/admin')
|
||||
})
|
||||
|
||||
router.get('/select', (req, res) => {
|
||||
res.render('pages/search-root')
|
||||
})
|
||||
|
||||
router.get('/dictionaries', (req, res) => {
|
||||
res.render('pages/my-dictionaries-root')
|
||||
})
|
||||
|
||||
router.get('/db-query-demo', async (req, res) => {
|
||||
const queryResult = await db.query("SELECT 'yes' as does_it_work")
|
||||
res.send(queryResult.rows[0])
|
||||
})
|
||||
|
||||
router.get('/test-seje', (req, res) => {
|
||||
// req.session.before = { a: 1, b: 'yes' }
|
||||
const { sessionID, session } = req
|
||||
// req.session.after = { c: 2, d: 'no' }
|
||||
res.send({ sessionID, session })
|
||||
})
|
||||
|
||||
router.get('/test-cacha', async (req, res, next) => {
|
||||
const sRes = await cache.set('my key', 'also my val')
|
||||
const gRes = await cache.get('my key')
|
||||
res.send({ sRes, gRes })
|
||||
})
|
||||
|
||||
router.get('/test-iskalnika', async (req, res) => {
|
||||
const response = await searchEngineClient.ping()
|
||||
res.send(response)
|
||||
})
|
||||
|
||||
// Stran z demo implementacijo paginirane vsebine.
|
||||
router.get('/demo-paginacija', demoPaginacija.izrišiStran)
|
||||
|
||||
// Render help page
|
||||
router.get('/pomoc', (req, res) => {
|
||||
res.render('pages/help', { title: req.t('Pomoč') })
|
||||
@@ -86,17 +34,9 @@ router.get('/slovarji/xml-schema', (req, res) => {
|
||||
res.download('public/documents/dictionary_schema.xsd')
|
||||
})
|
||||
|
||||
// Render demo help pug page.
|
||||
router.get('/pomoc-pug-demo', (req, res) => {
|
||||
res.render('pages/help-pug-demo-frame', { title: 'Pomoč - pug demo' })
|
||||
})
|
||||
|
||||
// Activate user account.
|
||||
router.get('/users/activate', user.activateAccount)
|
||||
|
||||
// Log the user out.
|
||||
router.post('/users/logout', user.logout)
|
||||
|
||||
// Render privacy policy page
|
||||
router.get('/politika-zasebnosti', (req, res) => {
|
||||
res.render(`pages/privacy-policy_${req.language}`, {
|
||||
@@ -111,6 +51,15 @@ router.get('/pogoji-uporabe', (req, res) => {
|
||||
})
|
||||
})
|
||||
|
||||
// Form for user to reset their password.
|
||||
router.get('/ponastavitev-gesla', index.resetPassword)
|
||||
|
||||
// Confirm user email change.
|
||||
router.get('/sprememba-elektronskega-naslova', index.changeEmail)
|
||||
|
||||
// Change user locale.
|
||||
router.get('/spremeni-jezik/:languageCode', index.changeUserLanguage)
|
||||
|
||||
router.get('/moj-racun', isUserAuthenticated, index.myProfile)
|
||||
|
||||
router.get('/izbrisi-racun', isUserAuthenticated, index.deleteProfile)
|
||||
@@ -119,9 +68,7 @@ router.get('/spremeni-geslo', isUserAuthenticated, index.changePassword)
|
||||
|
||||
router.get('/nastavitve-racuna', isUserAuthenticated, index.userSettings)
|
||||
|
||||
router.get('/ponastavitev-gesla', index.resetPassword)
|
||||
|
||||
// Change user locale.
|
||||
router.get('/spremeni-jezik/:languageCode', index.changeUserLanguage)
|
||||
// Log the user out.
|
||||
router.post('/users/logout', isUserAuthenticated, user.logout)
|
||||
|
||||
module.exports = router
|
||||
|
||||
@@ -7,4 +7,6 @@ utils.pipe = (...fns) => (initialVal) => fns.reduce((val, fn) => fn(val), initia
|
||||
utils.composeAsync = (...fns) => input => fns.reduceRight((chain, func) => chain.then(func), Promise.resolve(input));
|
||||
utils.pipeAsync = (...fns) => input => fns.reduce((chain, func) => chain.then(func), Promise.resolve(input));
|
||||
|
||||
utils.capitalize = string => string[0].toUpperCase() + string.slice(1)
|
||||
|
||||
module.exports = utils
|
||||
|
||||
@@ -7,7 +7,7 @@ mixin sideNavigation(metaData)
|
||||
src="/images/burger-menu-button-icon.svg"
|
||||
alt="Meni"
|
||||
)
|
||||
span#nav-title.nav-title= 'Svetovalnica'
|
||||
span#nav-title.nav-title= t('Svetovalnica')
|
||||
#mobile-right-holder
|
||||
nav
|
||||
ul.admin-nav-content.slidable.z-20.scroller-style
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
type="checkbox"
|
||||
)
|
||||
label.ps-2.align-middle.rememberloginlabel.text-header-description-gray.pe-2(
|
||||
for="login-remember-me"
|
||||
for="login-remember"
|
||||
)= t('Zapomni si prijavo')
|
||||
.col-5.pt-2.d-flex-and-align-end.pe-0
|
||||
a#forgotten-password.ms-auto.forgotten-password.gray1-text.no-text-decoration.align-middle(
|
||||
|
||||
@@ -58,18 +58,18 @@ mixin main-navigation-right(languageToolbarPresent=false, menuTextPresent=false,
|
||||
a.dropdown-item.ps-0.pb-2.navigation-text-color(
|
||||
href="/nastavitve-racuna"
|
||||
)
|
||||
.row
|
||||
.col-3
|
||||
img(src="/images/cog.svg" alt="")
|
||||
.col-9
|
||||
.d-flex.flex-row
|
||||
.d-flex.flex-shrink-1.flex-grow-0
|
||||
img.pe-2(src="/images/cog.svg" alt="")
|
||||
.d-flex.flex-grow-1
|
||||
span.text-header-description-gray.d-block= t('Nastavitve')
|
||||
li.pb-0.mb-0.ps-3
|
||||
form(action="/users/logout" method="post")
|
||||
button.profile-menu-button
|
||||
.row
|
||||
.col-3
|
||||
img(src="/images/log-out.svg" alt="")
|
||||
.col-9
|
||||
button.profile-menu-button.pb-2.pt-1
|
||||
.d-flex.flex-row
|
||||
.d-flex.flex-shrink-1.flex-grow-0
|
||||
img.pe-2(src="/images/log-out.svg" alt="")
|
||||
.d-flex.flex-grow-1
|
||||
span.text-header-description-gray.d-block= t('Odjava')
|
||||
else
|
||||
button#login-static-backdrop-button.col-sm.nav-entry.no-border.no-bg(
|
||||
|
||||
@@ -15,7 +15,7 @@ mixin consultancySM(adminFields=false)
|
||||
)
|
||||
//- option.d-none(value="-1")= ' '
|
||||
each domain in allPrimaryDomains
|
||||
option(value=domain.id selected=domain.selected) #{ domain.nameSl }
|
||||
option(value=domain.id selected=domain.selected)= domain.name
|
||||
span.advanced-input-label
|
||||
//- span.advanced-input-label Področje
|
||||
//- img#area-img(src="/images/chevron-down-darker.svg" alt="")
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
//- input#src-lang-input.box-sizing-content.bg-transparent
|
||||
select#src-lang-select.select-src-lang-field(multiple="multiple")
|
||||
each language in sourceLanguages
|
||||
option(value=language.id selected=language.selected) #{ language.nameSl }
|
||||
option(value=language.id selected=language.selected)= language.name
|
||||
span.advanced-input-label
|
||||
//- span.advanced-input-label Jezik iskanja
|
||||
//- img#src-lang-img(src="/images/chevron-down-darker.svg" alt="")
|
||||
@@ -35,7 +35,7 @@
|
||||
//- input#dest-lang-input.box-sizing-content.bg-transparent
|
||||
select#dest-lang-select.select-dest-lang-field(multiple="multiple")
|
||||
each language in targetLanguages
|
||||
option(value=language.id selected=language.selected) #{ language.nameSl }
|
||||
option(value=language.id selected=language.selected)= language.name
|
||||
span.advanced-input-label
|
||||
//- span.advanced-input-label Ciljni jezik
|
||||
//- img#dest-lang-img(src="/images/chevron-down-darker.svg" alt="")
|
||||
@@ -62,7 +62,7 @@
|
||||
multiple="multiple"
|
||||
)
|
||||
each domain in allPrimaryDomains
|
||||
option(value=domain.id selected=domain.selected) #{ domain.nameSl }
|
||||
option(value=domain.id selected=domain.selected)= domain.name
|
||||
span.advanced-input-label
|
||||
//- span.advanced-input-label Področje
|
||||
//- img#area-img(src="/images/chevron-down-darker.svg" alt="")
|
||||
@@ -78,7 +78,7 @@
|
||||
multiple="multiple"
|
||||
)
|
||||
each dictionary in allDictionaryNames
|
||||
option(value=dictionary.id selected=dictionary.selected) #{ dictionary.nameSl }
|
||||
option(value=dictionary.id selected=dictionary.selected)= dictionary.name
|
||||
span.advanced-input-label
|
||||
//- span.advanced-input-label Slovar
|
||||
//- img#dict-img(src="/images/chevron-down-darker.svg" alt="")
|
||||
@@ -91,7 +91,7 @@
|
||||
//- input#source-input.box-sizing-content.bg-transparent
|
||||
select#src-select.select-source-field(multiple="multiple")
|
||||
each portal in portals
|
||||
option(value=portal.code selected=portal.selected) #{ portal.name }
|
||||
option(value=portal.code selected=portal.selected)= portal.name
|
||||
span.advanced-input-label
|
||||
//- span.advanced-input-label Vir
|
||||
//- img#source-img(src="/images/chevron-down-darker.svg" alt="")
|
||||
|
||||
@@ -2,7 +2,7 @@ mixin result-item(headword, entry, isLast=false)
|
||||
-
|
||||
const foreignEntries = entry.foreignEntries ? entry.foreignEntries : []
|
||||
const synonyms = entry.synonyms?entry.synonyms:[]
|
||||
const domain = entry.primaryDomain&&entry.primaryDomain.nameSl?entry.primaryDomain.nameSl:""
|
||||
const domain = entry.primaryDomain?entry.primaryDomain[`name${capitalize(determinedLanguage)}`]:""
|
||||
const slovenianDefinitionExists = !!entry.definition
|
||||
let foreignDefinitionExists = !!entry.foreignEntries && entry.foreignEntries.length > 0
|
||||
let checkAllForeignDefinitions = false
|
||||
@@ -92,7 +92,7 @@ mixin result-item(headword, entry, isLast=false)
|
||||
.results-sources.d-flex.flex-shrink-1.justify-content-end.align-items-center
|
||||
.results-dict.d-flex.justify-content-end.align-items-center.me-2.px-2(
|
||||
data-bs-toggle="tooltip"
|
||||
title=`${entry.dictionary.nameSl}`
|
||||
title=`${entry.dictionary[`name${capitalize(determinedLanguage)}`]}`
|
||||
) #{ entry.dictionary.nameSlShort }
|
||||
.results-portal.d-flex.justify-content-end.align-items-center.te(
|
||||
data-bs-toggle="tooltip"
|
||||
|
||||
@@ -22,7 +22,7 @@ section.filter-modal-root
|
||||
span.d-flex.flex-grow-1.align-content-center
|
||||
a#ccf.clear-filter-modal-section(href="#")
|
||||
img(src="/images/square-minus.svg" alt="")
|
||||
span.ps-2 POČISTI FILTRE
|
||||
span.ps-2= t('POČISTI FILTRE')
|
||||
.modal-body.pb-2
|
||||
ul#modal-list.nav-modal-section-content.mb-0.pb-0
|
||||
// Added dynamically...
|
||||
@@ -31,8 +31,6 @@ section.filter-modal-root
|
||||
button.btn.btn-light.border.border-dark.close(
|
||||
type="button"
|
||||
data-bs-dismiss="modal"
|
||||
)
|
||||
| Zapri
|
||||
)= t('Zapri')
|
||||
.justify-content-end
|
||||
button#sf-modal-button.btn.btn-primary.use(type="button")
|
||||
| Uporabi
|
||||
button#sf-modal-button.btn.btn-primary.use(type="button")= t('Uporabi')
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
p Pozdravljeni,
|
||||
case type
|
||||
when 'status'
|
||||
p oseba: #[b #{ changerEmail }] je spremenila stanje vašega slovarja: #[b #{ nameSl }].
|
||||
p oseba: #[b #{ changerEmail }] je spremenila stanje vašega slovarja: #[b #{ name }].
|
||||
when 'unpublish'
|
||||
p oseba: #[b #{ changerEmail }] je spremenila status slovarja #[b #{ nameSl }], ki ni več objavljen.
|
||||
p oseba: #[b #{ changerEmail }] je spremenila status slovarja #[b #{ name }], ki ni več objavljen.
|
||||
when 'delete'
|
||||
p slovar: #[b #{ nameSl }] ima manjše število gesel kot je nastavljeno.
|
||||
p slovar: #[b #{ name }] ima manjše število gesel kot je nastavljeno.
|
||||
when 'publish-approval'
|
||||
p slovar #[b #{ nameSl }] je bil dan v stanje preverjanja. Prosimo, da ga odprete/zaprete.
|
||||
p slovar #[b #{ name }] je bil dan v stanje preverjanja. Prosimo, da ga odprete/zaprete.
|
||||
when 'publish-no-approval'
|
||||
p slovar #[b #{ nameSl }] je bil objavljen.
|
||||
p slovar #[b #{ name }] je bil objavljen.
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
p Dear #{ username },
|
||||
p you have successfully changed the e-mail address used for your Slovenian Terminology Portal user account.
|
||||
@@ -0,0 +1,2 @@
|
||||
p Pozdravljeni #{ username },
|
||||
p to sporočilo ste prejeli, ker ste na Slovenskem terminološkem portalu uspešno spremenili elektronski naslov.
|
||||
@@ -0,0 +1,4 @@
|
||||
p Dear #{ username },
|
||||
p you have received this message because you want to change the e-mail address used for your Slovenian Terminology Portal account.
|
||||
p To change the e-mail address, please confirm that this is your new e-mail address. The link will be valid for 7 days.
|
||||
a(href=changeEmailLink) I confirm my e-mail address
|
||||
@@ -0,0 +1,4 @@
|
||||
p Pozdravljeni #{ username },
|
||||
p to sporočilo ste prejeli, ker na Slovenskem terminološkem portalu želite spremeniti elektronski naslov.
|
||||
p Za spremembo naslova potrebujete le še potrditi, da je to res vaš novi elektronski naslov. Povezava je veljavna 7 dni.
|
||||
a(href=changeEmailLink) Potrjujem svoj naslov
|
||||
@@ -0,0 +1,2 @@
|
||||
p Dear #{ username },
|
||||
p you have changed the password for your Slovenian Terminology Portal user account.
|
||||
@@ -0,0 +1,2 @@
|
||||
p Pozdravljeni #{ username },
|
||||
p to sporočilo ste prejeli, ker ste na Slovenskem terminološkem portalu spremenili geslo.
|
||||
@@ -0,0 +1,2 @@
|
||||
p Dear #{ username },
|
||||
p you have successfully reset the password for your Slovenian Terminology Portal user account.
|
||||
@@ -0,0 +1,2 @@
|
||||
p Pozdravljeni #{ username },
|
||||
p to sporočilo ste prejeli, ker ste na Slovenskem terminološkem portalu uspešno ponastavili geslo.
|
||||
@@ -0,0 +1,4 @@
|
||||
p Dear #{ username },
|
||||
p you have received this message because you want to change the password for the Slovenian Terminology Portal user account connected to this e-mail address.
|
||||
p To reset the password, click the link below. The link will be valid for 1 day.
|
||||
a(href=resetPasswordLink) Password reset
|
||||
@@ -0,0 +1,4 @@
|
||||
p Pozdravljeni #{ username },
|
||||
p to sporočilo ste prejeli, ker na Slovenskem terminološkem portalu želite ponastaviti geslo uporabniškega računa s tem elektronskim naslovom.
|
||||
p Za ponastavitev sledite spodnji povezavi. Povezava je veljavna 1 dan.
|
||||
a(href=resetPasswordLink) Ponastavitev gesla
|
||||
@@ -1,29 +0,0 @@
|
||||
//- TODO MARK FOR DELETION
|
||||
|
||||
h1 Dokumenti za luščenje #{ id }
|
||||
|
||||
h2 Seznam datotek
|
||||
ul#files-list
|
||||
each document in extractionDocuments
|
||||
li DATOTEKA - Ime: #[span.filename= document.filename], velikost: #{ document.size }, datum: #{ new Date(document.timeModified).toLocaleDateString('sl-SL') } #[a.delete-file(href="#") BRIŠI]
|
||||
|
||||
h2 Dodaj nov(e) datoteke(e)
|
||||
form#upload-files(method="post" enctype="multipart/form-data")
|
||||
input(type="hidden" name="extractionId" value=id)
|
||||
label Datoteka(e):
|
||||
input(
|
||||
type="file"
|
||||
name="extractionFile"
|
||||
accept=".txt, .doc, .docx, .pdf"
|
||||
multiple
|
||||
)
|
||||
|
||||
a(href=`../${id}`) Nazaj
|
||||
p Obestila:
|
||||
ul#messages
|
||||
|
||||
script(
|
||||
nonce=cspNonce
|
||||
src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"
|
||||
)
|
||||
script(nonce=cspNonce src="/javascripts/extraction-poc-files-edit.js")
|
||||
@@ -1,128 +0,0 @@
|
||||
h1 Luščenje #{ id } - uredi
|
||||
|
||||
form(method="post")
|
||||
div
|
||||
label Ime:
|
||||
input(name="name" value=extraction.name)
|
||||
div
|
||||
label Glavno področje:
|
||||
select(name="domain")
|
||||
option
|
||||
each domain in allPrimaryDomains
|
||||
option(value=domain.id selected=domain.id === extraction.domainId)= domain.nameSl
|
||||
div
|
||||
label Vrsta dokumenta:
|
||||
select.pick-multiple(name="documentType" multiple)
|
||||
option
|
||||
option(value=101 selected=extraction.documentType.includes(101)) Izvirni znanstveni članek
|
||||
option(value=102 selected=extraction.documentType.includes(102)) Pregledni znanstveni članek
|
||||
option(value=103 selected=extraction.documentType.includes(103)) Kratki znanstveni prispevek
|
||||
option(value=104 selected=extraction.documentType.includes(104)) Strokovni članek
|
||||
option(value=105 selected=extraction.documentType.includes(105)) Poljudni članek
|
||||
option(value=106 selected=extraction.documentType.includes(106)) Objavljeni znanstveni prispevek na konferenci (vabljeno predavanje)
|
||||
option(value=107 selected=extraction.documentType.includes(107)) Objavljeni strokovni prispevek na konferenci (vabljeno predavanje)
|
||||
option(value=108 selected=extraction.documentType.includes(108)) Objavljeni znanstveni prispevek na konferenci
|
||||
option(value=109 selected=extraction.documentType.includes(109)) Objavljeni strokovni prispevek na konferenci
|
||||
option(value=110 selected=extraction.documentType.includes(110)) Objavljeni povzetek znanstvenega prispevka na konferenci (vabljeno predavanje)
|
||||
option(value=112 selected=extraction.documentType.includes(112)) Objavljeni povzetek znanstvenega prispevka na konferenci
|
||||
option(value=113 selected=extraction.documentType.includes(113)) Objavljeni povzetek strokovnega prispevka na konferenci
|
||||
option(value=116 selected=extraction.documentType.includes(116)) Samostojni znanstveni sestavek ali poglavje v monografski publikaciji
|
||||
option(value=117 selected=extraction.documentType.includes(117)) Samostojni strokovni sestavek ali poglavje v monografski publikaciji
|
||||
option(value=118 selected=extraction.documentType.includes(118)) Geslo – sestavek v enciklopediji, leksikonu, slovarju...
|
||||
option(value=119 selected=extraction.documentType.includes(119)) Recenzija, prikaz knjige, kritika
|
||||
option(value=120 selected=extraction.documentType.includes(120)) Predgovor, spremna beseda
|
||||
option(value=121 selected=extraction.documentType.includes(121)) Polemika, diskusijski prispevek
|
||||
option(value=122 selected=extraction.documentType.includes(122)) Intervju
|
||||
option(value=123 selected=extraction.documentType.includes(123)) Umetniški sestavek
|
||||
option(value=124 selected=extraction.documentType.includes(124)) Bibliografija, kazalo ipd.
|
||||
option(value=125 selected=extraction.documentType.includes(125)) Drugi članki ali sestavki
|
||||
option(value=201 selected=extraction.documentType.includes(201)) Znanstvena monografija
|
||||
option(value=202 selected=extraction.documentType.includes(202)) Strokovna monografija
|
||||
option(value=203 selected=extraction.documentType.includes(203)) Univerzitetni, visokošolski ali višješolski učbenik z recenzijo
|
||||
option(value=204 selected=extraction.documentType.includes(204)) Srednješolski, osnovnošolski ali drugi učbenik z recenzijo
|
||||
option(value=205 selected=extraction.documentType.includes(205)) Video in druga učna gradiva
|
||||
option(value=206 selected=extraction.documentType.includes(206)) Enciklopedija, slovar, leksikon, priročnik, atlas, zemljevid
|
||||
option(value=207 selected=extraction.documentType.includes(207)) Bibliografija
|
||||
option(value=208 selected=extraction.documentType.includes(208)) Doktorska disertacija
|
||||
option(value=209 selected=extraction.documentType.includes(209)) Magistrsko delo
|
||||
option(value=210 selected=extraction.documentType.includes(210)) Specialistično delo
|
||||
option(value=211 selected=extraction.documentType.includes(211)) Diplomsko delo
|
||||
option(value=212 selected=extraction.documentType.includes(212)) Končno poročilo o rezultatih raziskav
|
||||
option(value=213 selected=extraction.documentType.includes(213)) Elaborat, predštudija, študija
|
||||
option(value=214 selected=extraction.documentType.includes(214)) Projektna dokumentacija (idejni projekt, izvedbeni projekt)
|
||||
option(value=215 selected=extraction.documentType.includes(215)) Izvedensko mnenje, arbitražna odločba
|
||||
option(value=216 selected=extraction.documentType.includes(216)) Umetniško delo
|
||||
option(value=217 selected=extraction.documentType.includes(217)) Katalog razstave
|
||||
option(value=218 selected=extraction.documentType.includes(218)) Raziskovalni ali dokumentarni film, zvočni ali video posnetek
|
||||
option(value=219 selected=extraction.documentType.includes(219)) Radijska ali televizijska oddaja
|
||||
option(value=220 selected=extraction.documentType.includes(220)) Raziskovalni podatki
|
||||
option(value=221 selected=extraction.documentType.includes(221)) Programska oprema
|
||||
option(value=222 selected=extraction.documentType.includes(222)) Nova sorta
|
||||
option(value=223 selected=extraction.documentType.includes(223)) Patentna prijava
|
||||
option(value=224 selected=extraction.documentType.includes(224)) Patent
|
||||
option(value=225 selected=extraction.documentType.includes(225)) Druge monografije in druga zaključena dela
|
||||
option(value=227 selected=extraction.documentType.includes(227)) Znanstveni terminološki slovar, enciklopedija ali tematski leksikon
|
||||
option(value=230 selected=extraction.documentType.includes(230)) Zbornik strokovnih ali nerecenziranih znanstvenih prispevkov na konferenci
|
||||
option(value=231 selected=extraction.documentType.includes(231)) Zbornik recenziranih znanstvenih prispevkov na mednarodni ali tuji konferenci
|
||||
option(value=232 selected=extraction.documentType.includes(232)) Zbornik recenziranih znanstvenih prispevkov na domači konferenci
|
||||
option(value=310 selected=extraction.documentType.includes(310)) Umetniška poustvaritev
|
||||
option(value=311 selected=extraction.documentType.includes(311)) Radijski ali TV dogodek
|
||||
option(value=312 selected=extraction.documentType.includes(312)) Razstava
|
||||
option(value=313 selected=extraction.documentType.includes(313)) Organiziranje znanstvenih in strokovnih sestankov
|
||||
option(value=314 selected=extraction.documentType.includes(314)) Predavanje na tuji univerzi
|
||||
option(value=315 selected=extraction.documentType.includes(315)) Prispevek na konferenci brez natisa
|
||||
option(value=316 selected=extraction.documentType.includes(316)) Vabljeno predavanje na konferenci brez natisa
|
||||
option(value=320 selected=extraction.documentType.includes(320)) Druga dela
|
||||
option(value=325 selected=extraction.documentType.includes(325)) Druga izvedena dela
|
||||
div
|
||||
label Leto:
|
||||
select.enter-multiple(name="year" multiple style="width: 500px")
|
||||
each year in extraction.year
|
||||
option(value=year selected)= year
|
||||
div
|
||||
label Ključne besede:
|
||||
select.enter-multiple(name="keywords" multiple style="width: 500px")
|
||||
each keyword in extraction.keywords
|
||||
option(value=keyword selected)= keyword
|
||||
|
||||
h2 Stop termini:
|
||||
ul
|
||||
each file in stopTermsFiles
|
||||
li DATOTEKA - Ime: #[span.filename= file.filename], velikost: #{ file.size }, datum: #{ new Date(file.timeModified).toLocaleDateString('sl-SL') }
|
||||
a#edit-stop-terms(href=`${id}/stop-termini`) Uredi
|
||||
|
||||
br
|
||||
br
|
||||
button#search-btn Najdi
|
||||
#search-result
|
||||
|
||||
br
|
||||
br
|
||||
a(href="../poc") Nazaj
|
||||
|
||||
p Obestila:
|
||||
ul#messages
|
||||
|
||||
link(
|
||||
href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css"
|
||||
rel="stylesheet"
|
||||
integrity="sha256-zaSoHBhwFdle0scfGEFUCwggPN7F+ip9XRglo8IWb4w="
|
||||
crossorigin="anonymous"
|
||||
)
|
||||
script(
|
||||
nonce=cspNonce
|
||||
src="https://code.jquery.com/jquery-3.6.0.slim.min.js"
|
||||
integrity="sha256-u7e5khyithlIdTpu22PHhENmPcRdFiHRjhAuHcs05RI="
|
||||
crossorigin="anonymous"
|
||||
)
|
||||
script(
|
||||
nonce=cspNonce
|
||||
src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"
|
||||
integrity="sha256-9yRP/2EFlblE92vzCA10469Ctd0jT48HnmmMw5rJZrA="
|
||||
crossorigin="anonymous"
|
||||
)
|
||||
script(
|
||||
nonce=cspNonce
|
||||
src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"
|
||||
)
|
||||
script(nonce=cspNonce src="/javascripts/extraction-poc-oss-edit.js")
|
||||
@@ -1,20 +0,0 @@
|
||||
h1 Luščenje #{ id } - uredi
|
||||
|
||||
form(method="post")
|
||||
label Ime:
|
||||
input(name="name" value=extraction.name)
|
||||
button Potrdi
|
||||
|
||||
h2 Lastni dokumenti:
|
||||
ul
|
||||
each document in extractionDocuments
|
||||
li DATOTEKA - Ime: #[span.filename= document.filename], velikost: #{ document.size }, datum: #{ new Date(document.timeModified).toLocaleDateString('sl-SL') }
|
||||
a(href=`${id}/besedila`) Uredi
|
||||
h2 Stop termini:
|
||||
ul
|
||||
each file in stopTermsFiles
|
||||
li DATOTEKA - Ime: #[span.filename= file.filename], velikost: #{ file.size }, datum: #{ new Date(file.timeModified).toLocaleDateString('sl-SL') }
|
||||
a(href=`${id}/stop-termini`) Uredi
|
||||
|
||||
br
|
||||
a(href="../poc") Nazaj
|
||||
@@ -1,44 +0,0 @@
|
||||
h1 Luščenje
|
||||
|
||||
form(method="post")
|
||||
input(type="hidden" name="extractionType" value="own")
|
||||
p Luščenje iz lastnih besedil
|
||||
= ' '
|
||||
button Dodaj
|
||||
|
||||
form(method="post")
|
||||
input(type="hidden" name="extractionType" value="oss")
|
||||
p Luščenje iz OSS besedil
|
||||
= ' '
|
||||
button Dodaj
|
||||
|
||||
h2 Seznam obstoječih luščenj
|
||||
ul#extraction-list
|
||||
each e in extractions
|
||||
li(data-id=e.id)
|
||||
| #{ e.name } (STATUS: #[span.extraction-status= e.status]) (
|
||||
if e.status === 'new'
|
||||
a(href=`poc/${e.id}`) Uredi
|
||||
if e.status !== 'in progress'
|
||||
button.btn-delete Izbriši
|
||||
if e.canBegin
|
||||
button.btn-begin Začni
|
||||
if e.status === 'finished' || e.status === 'failed'
|
||||
button.btn-duplicate Podvoji
|
||||
if e.status === 'finished'
|
||||
a(href=`poc/${e.id}/kandidati`) Terminološki kandidati [#{ e.termCandidatesCount }]
|
||||
if e.status === 'finished' && e.corpusId
|
||||
a(href=`poc/korpus/${e.corpusId}` target="_blank") Uporabniški korpus
|
||||
if e.status === 'finished' && e.ossParams
|
||||
a(
|
||||
href="https://www.clarin.si/ske/#dashboard?corpname=oss"
|
||||
target="_blank"
|
||||
) Korpus KAS+
|
||||
| ) (ZAČETEK: #[span.extraction-time-started= e.timeStarted ? e.timeStarted.toLocaleString('sl-SI') : 'ni še bilo začeto'],
|
||||
| KONEC: #[span.extraction-time-finished= e.timeFinished ? e.timeFinished.toLocaleString('sl-SI') : 'ni še bilo končano'])
|
||||
|
||||
script(
|
||||
nonce=cspNonce
|
||||
src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"
|
||||
)
|
||||
script(nonce=cspNonce src="/javascripts/extraction-poc-list.js")
|
||||
@@ -1,22 +0,0 @@
|
||||
h1 Stop termini za luščenje #{ id }
|
||||
|
||||
h2 Seznam datotek
|
||||
ul#files-list
|
||||
each file in stopTermsFiles
|
||||
li DATOTEKA - Ime: #[span.filename= file.filename], velikost: #{ file.size }, datum: #{ new Date(file.timeModified).toLocaleDateString('sl-SL') } #[a.delete-file(href="#") BRIŠI]
|
||||
|
||||
h2 Dodaj nov(e) datoteke(e)
|
||||
form#upload-files(method="post" enctype="multipart/form-data")
|
||||
input(type="hidden" name="extractionId" value=id)
|
||||
label Datoteka(e):
|
||||
input(type="file" name="extractionFile" accept=".txt" multiple)
|
||||
|
||||
a(href=`../${id}`) Nazaj
|
||||
p Obestila:
|
||||
ul#messages
|
||||
|
||||
script(
|
||||
nonce=cspNonce
|
||||
src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"
|
||||
)
|
||||
script(nonce=cspNonce src="/javascripts/extraction-poc-files-edit.js")
|
||||
@@ -1,23 +0,0 @@
|
||||
h1 Terminološki kandidati
|
||||
|
||||
include /utilities/pager
|
||||
+pager
|
||||
|
||||
ul#page-results
|
||||
each termCandidate, index in firstPageOfTermCandidates
|
||||
li [#{ index + 1 }] #{ JSON.stringify(termCandidate) }
|
||||
|
||||
form(action=`/api/v1/extraction/${extractionId}/term-candidates-export`)
|
||||
p Izvozi termine od številke
|
||||
input(type="number" name="from")
|
||||
p do številke
|
||||
input(type="number" name="to")
|
||||
button Izvozi
|
||||
|
||||
script#variables-transport-script(nonce=cspNonce).
|
||||
const termCandidates = !{ termCandidatesJson }.terminoloski_kandidati;
|
||||
const hitsPerPage = #{ hitsPerPage };
|
||||
const numberOfAllPages = #{ numberOfAllPages };
|
||||
document.getElementById('variables-transport-script').remove();
|
||||
|
||||
script(nonce=cspNonce src="/javascripts/extraction-poc-term-candidates.js")
|
||||
@@ -100,6 +100,13 @@ html(lang=language)
|
||||
crossorigin="anonymous"
|
||||
)
|
||||
|
||||
if flashInfo.length
|
||||
include /utilities/modal-response
|
||||
+responseModal("flash-modal", t("Razumem"), "understand-btn", flashInfo[0])
|
||||
script(nonce=cspNonce).
|
||||
const flashModal = new bootstrap.Modal(document.getElementById('flash-modal'));
|
||||
flashModal.toggle();
|
||||
|
||||
if inDevEnv
|
||||
script(nonce=cspNonce) window.inDevEnv = true
|
||||
script(nonce=cspNonce src="/javascripts/globals.js")
|
||||
|
||||
@@ -70,14 +70,14 @@ block body
|
||||
#input-row.row
|
||||
.col-sm-3
|
||||
.subject-name
|
||||
label.input-name-txt= ('PODPODROČJE')
|
||||
label.input-name-txt= t('PODPODROČJE')
|
||||
input#area-input.form-control(type="text")
|
||||
.col-sm-3
|
||||
.subject-name
|
||||
label.input-name-txt= ('ANGLEŠKI PREVOD')
|
||||
label.input-name-txt= t('ANGLEŠKI PREVOD')
|
||||
input#translation-input.form-control(type="text")
|
||||
.col.d-flex.align-items-end.mt-2
|
||||
button#add-area.btn.btn-primary(type="submit" disabled)= ('Dodaj')
|
||||
button#add-area.btn.btn-primary(type="submit" disabled)= t('Dodaj')
|
||||
include /common/footer
|
||||
|
||||
include /utilities/modal-alert
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user