Initial commit

This commit is contained in:
Luka Romih
2022-12-07 04:43:36 +01:00
commit 257f3c354f
496 changed files with 55373 additions and 0 deletions
+754
View File
@@ -0,0 +1,754 @@
const db = require('./db')
const {
searchEngineClient,
deleteConsultancyEntriesFromIndex,
CONSULTANCY_ENTRY_INDEX
} = require('./search-engine')
const { removeHtmlTags } = require('./helpers')
class ConsultancyEntry {
constructor({
id,
id_external: idExternal,
time_created: timeCreated,
status,
author_id: authorId,
institution,
description,
domain_primary_id_initial: domainPrimaryIdInitial,
existing_solutions: existingSolutions,
examples_of_use: examplesOfUse,
time_published: timePublished,
title,
question,
answer,
path,
answer_authors: answerAuthors,
domain_primary_id: domainPrimaryId,
first_name: firstName,
last_name: lastName,
is_moderator: isModerator,
formatted_time_created: formattedTimeCreated,
formatted_time_published: formattedTimePublished
}) {
this.id = id
this.idExternal = idExternal
this.timeCreated = timeCreated
this.status = status
this.authorId = authorId
this.institution = institution
this.description = description
this.domainPrimaryIdInitial = domainPrimaryIdInitial
this.existingSolutions = existingSolutions
this.examplesOfUse = examplesOfUse
this.timePublished = timePublished
this.title = title
this.question = question
this.answer = answer
this.path = path
this.answerAuthors = answerAuthors
this.domainPrimaryId = domainPrimaryId
this.firstName = firstName
this.lastName = lastName
this.isModerator = isModerator
this.formattedTimeCreated = formattedTimeCreated
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')
static async fetchByIdWithFormattedTime(id) {
const { rows: fetchedConsEntry } = await db.query(
`
SELECT
id,
id_external,
to_char(time_created,'DD. MM. YYYY') time_created,
status,
author_id,
institution,
description,
domain_primary_id_initial,
existing_solutions,
examples_of_use,
to_char(time_published,'DD. MM. YYYY') time_published,
title,
question,
answer,
path,
answer_authors,
domain_primary_id
FROM consultancy_entry
WHERE id=$1`,
[id]
)
return new this(fetchedConsEntry[0])
}
static async fetchById(id) {
const { rows: fetchedConsEntry } = await db.query(
`
SELECT
id,
id_external,
to_char(time_created, 'DD. MM. YYYY') time_created,
status,
author_id,
institution,
description,
domain_primary_id_initial,
existing_solutions,
examples_of_use,
to_char(time_published,'DD. MM. YYYY') time_published,
title,
question,
answer,
path,
answer_authors,
domain_primary_id
FROM consultancy_entry
WHERE id=$1`,
[id]
)
return new this(fetchedConsEntry[0])
}
// Fetch dictionaries admin emails.
static async fetchConsultancyAdminEmails() {
const query = {
text: `
SELECT email FROM "user" WHERE id
IN(SELECT user_id FROM user_role
WHERE role_name = 'consultancy admin')`,
rowMode: 'array'
}
const { rows: users } = await db.query(query)
const emails = users.flat()
return emails
}
static async fetchModeratorEmail(entryId) {
const query = {
text: `
SELECT email FROM "user" WHERE id
IN (SELECT user_id FROM consultancy_entry_consultant cec
WHERE cec.entry_id=$1 AND cec.is_moderator=true)`,
rowMode: 'array'
}
const { rows: users } = await db.query(query, [entryId])
const emails = users.flat()
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 *.
const sqlQuery = `
SELECT COUNT(id)
FROM consultancy_entry
WHERE status=$1`
const values = [status]
const { rows } = await db.query(sqlQuery, values)
return rows[0].count
}
static async fetchWithStatusByIdCount(status, id) {
const sqlQuery = `
SELECT COUNT(DISTINCT(ce.id))
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.status=$1 and cec.user_id=$2`
const values = [status, id]
const { rows } = await db.query(sqlQuery, values)
return rows[0].count
}
static async fetchWithStatusById(status, id) {
const sqlQuery = `
SELECT ce.id,
to_char(time_created, 'FMDD. FMMM. YYYY') formatted_time_created,
author_id,
institution,
description,
domain_primary_id_initial,
to_char(time_published, 'FMDD. FMMM. YYYY') time_published,
title,
question,
answer,
path,
answer_authors,
domain_primary_id,
first_name,
last_name,
cec.is_moderator
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.status=$1 and cec.user_id=$2
ORDER BY time_created DESC`
/* filter them in pug by displaying moderator, and showing authors
of non moderator */
/* AND cec.is_moderator='true';` */
const values = [status, id]
const { rows: fetchedConsEntries } = await db.query(sqlQuery, values)
const deserializedConsEntries = fetchedConsEntries.map(
consEntry => new this(consEntry)
)
return deserializedConsEntries
}
static async fetchInProgressById(id) {
return await this.fetchWithStatusById('in progress', id)
}
static async fetchPublishedById(id) {
return await this.fetchWithStatusById('published', id)
}
// Fetch entry count.
static async fetchAllByStatusWithAuthorDataCount(status) {
const sqlQuery = `
SELECT COUNT(DISTINCT(ce.id))
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.status=$1`
/* filter them in pug by displaying moderator, and showing authors
of non moderator */
/* AND cec.is_moderator='true';` */
const values = [status]
const { rows } = await db.query(sqlQuery, values)
await db.query(sqlQuery, values)
return rows[0].count
}
// Fetch all consultancy entries filtered by status from DB.
static async fetchAllByStatusWithAuthorData(status) {
const sqlQuery = `
SELECT ce.id,
to_char(time_created, 'FMDD. FMMM. YYYY') formatted_time_created,
author_id,
institution,
description,
domain_primary_id_initial,
to_char(time_published, 'FMDD. FMMM. YYYY') time_published,
title,
question,
answer,
path,
answer_authors,
domain_primary_id,
first_name,
last_name,
cec.is_moderator
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.status=$1
ORDER BY time_created DESC`
/* filter them in pug by displaying moderator, and showing authors
of non moderator */
/* AND cec.is_moderator='true';` */
const values = [status]
const { rows: fetchedConsEntries } = await db.query(sqlQuery, values)
const deserializedConsEntries = fetchedConsEntries.map(
consEntry => new this(consEntry)
)
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
}
static async fetchAllInProgress() {
const newEntries = await this.fetchAllByStatusWithAuthorData('in progress')
return newEntries
}
static async fetchAllPrepared() {
const newEntries = await this.fetchAllByStatusWithAuthorData('review')
return newEntries
}
static async fetchAllPublished() {
const newEntries = await this.fetchAllByStatusWithAuthorData('published')
return newEntries
}
static async fetch5MostRecentPublished() {
const sqlQuery = `
SELECT ce.id,
to_char(time_created, 'FMDD. FMMM. YYYY') formatted_time_created,
author_id,
institution,
description,
domain_primary_id_initial,
to_char(time_published, 'FMDD. FMMM. YYYY') time_published,
title,
question,
answer,
path,
answer_authors,
domain_primary_id,
first_name,
last_name,
cec.is_moderator
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.status=$1
ORDER BY time_published DESC
LIMIT 5`
/* filter them in pug by displaying moderator, and showing authors
of non moderator */
/* AND cec.is_moderator='true';` */
const values = ['published']
const { rows: fetchedConsEntries } = await db.query(sqlQuery, values)
const deserializedConsEntries = fetchedConsEntries.map(
consEntry => new this(consEntry)
)
return deserializedConsEntries
}
static async fetchPublishedCount() {
const sqlQuery = `
SELECT COUNT(DISTINCT(ce.id))
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.status=$1`
/* filter them in pug by displaying moderator, and showing authors
of non moderator */
/* AND cec.is_moderator='true';` */
const values = ['published']
const { rows } = await db.query(sqlQuery, values)
return rows[0].count
}
/*
static async fetchAuthorsFirstAndLastName(entryId, userId) {
const sqlQuery = `
SELECT u.first_name u.last_name
FROM "user" u INNER JOIN "consultancy_entry_consultant" cec ON u.id = cec.user_id
WHERE cec.entry_id=$1 and cec.user_id=$2`
const values = [entryId, userId]
const { rows: fetchedConsEntries } = await db.query(sqlQuery, values)
return fetchedConsEntries
}
*/
/* Get shared authors in progress,
answer_authors only get published when the question is published */
static async getSharedAuthorsArrayBeforePublish(entryId) {
const sqlQuery = `SELECT cec.entry_id, cec.user_id,
u.first_name, u.last_name, u.username
FROM "consultancy_entry_consultant" cec INNER JOIN
"user" u ON u.id = cec.user_id
WHERE cec.entry_id=$1 AND cec.is_moderator='false';`
const values = [entryId]
const { rows } = await db.query(sqlQuery, values)
return rows
}
static async getModerator(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 AND cec.is_moderator=true;`
const values = [entryId]
const { rows } = await db.query(sqlQuery, values)
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;`
const values = [entryId]
const { rows } = await db.query(sqlQuery, values)
return rows[0]
}
static async updateSharedAuthorsArray(entryId, authors) {
const sqlQuery = `UPDATE "consultancy_entry" SET answer_authors=$2
WHERE id=$1;`
const values = [entryId, authors]
await db.query(sqlQuery, values)
}
static async createQuestion(consultancyEntry) {
const sqlQuery = `INSERT INTO consultancy_entry (
status,
author_id,
institution,
description,
domain_primary_id_initial,
existing_solutions,
examples_of_use
)
VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7
)
RETURNING id;`
const values = [
consultancyEntry.status,
consultancyEntry.authorId,
consultancyEntry.institution,
consultancyEntry.description,
consultancyEntry.domainPrimaryIdInitial || null,
consultancyEntry.existingSolutions,
consultancyEntry.examplesOfUse
]
const {
rows: [{ id }]
} = await db.query(sqlQuery, values)
return id
}
static async updateQuestion(consultancyEntry) {
await db.transaction(async dbClient => {
const sqlQuery = `UPDATE consultancy_entry SET
id_external=$2,
status=$3,
author_id=$4,
institution=$5,
description=$6,
domain_primary_id_initial=$7,
existing_solutions=$8,
examples_of_use=$9,
title=$10,
question=$11,
answer=$12,
path=$13,
answer_authors=$14,
domain_primary_id=$15
WHERE id=$1;`
const values = [
consultancyEntry.id,
consultancyEntry.idExternal,
consultancyEntry.status,
consultancyEntry.authorId,
consultancyEntry.institution,
consultancyEntry.description,
consultancyEntry.domainPrimaryIdInitial,
consultancyEntry.existingSolutions,
consultancyEntry.examplesOfUse,
consultancyEntry.title,
consultancyEntry.question,
consultancyEntry.answer,
consultancyEntry.path,
consultancyEntry.answerAuthors,
consultancyEntry.domainPrimaryId
]
await dbClient.query(sqlQuery, values)
await dbClient.query(
`UPDATE "consultancy_entry" SET status=$2
WHERE id=$1;`,
[consultancyEntry.id, 'in progress']
)
})
}
static async deleteQuestion(id) {
const sqlQuery = `DELETE FROM consultancy_entry
WHERE id=$1;`
const values = [id]
await db.query(sqlQuery, values)
}
/** refactor in case you need both moderator and non moderator
* insertions as a common function
*/
static async insertNonModerator(entryId, userId) {
const sqlQuery = `INSERT INTO "consultancy_entry_consultant" (entry_id, user_id, is_moderator) VALUES
($1, $2, 'false');
`
const values = [entryId, userId]
const { rows } = await db.query(sqlQuery, values)
return rows
}
static async assignWorkInProgress(entryId, userId) {
await db.transaction(async dbClient => {
let sqlQuery = `SELECT entry_id FROM consultancy_entry_consultant
WHERE entry_id=$1 AND is_moderator='true'`
let values = [entryId]
const { rows } = await dbClient.query(sqlQuery, values)
if (rows.length) {
await dbClient.query(
`DELETE FROM consultancy_entry_consultant
WHERE entry_id=$1 AND is_moderator='true'`,
values
)
}
sqlQuery = [
`UPDATE "consultancy_entry" SET status='in progress'
WHERE id=$1;`,
`INSERT INTO "consultancy_entry_consultant" (entry_id, user_id, is_moderator) VALUES
($1, $2, 'true');
`
]
values = [[entryId], [entryId, userId]]
for (let i = 0; i < sqlQuery.length; i++) {
await dbClient.query(sqlQuery[i], values[i])
}
})
}
static async updateConsultancyEntryStatus(entryId, status) {
const sqlQuery = `UPDATE "consultancy_entry" SET status=$2
WHERE id=$1;`
const values = [entryId, status]
await db.query(sqlQuery, values)
}
static async rejectEntry(entryId) {
await this.updateConsultancyEntryStatus(entryId, 'rejected')
}
static async sendToReview(entryId) {
await this.updateConsultancyEntryStatus(entryId, 'review')
}
static async publish(entryId, answerAuthors) {
await db.transaction(async dbClient => {
await dbClient.query(
`UPDATE "consultancy_entry" SET status=$2
WHERE id=$1;`,
[entryId, 'published']
)
if (answerAuthors) {
answerAuthors = answerAuthors.split(',').filter(author => author !== '')
await dbClient.query(
`UPDATE "consultancy_entry"
SET answer_authors=$2
WHERE id=$1;`,
[entryId, answerAuthors]
)
}
await dbClient.query(
`UPDATE "consultancy_entry"
SET time_published=CURRENT_TIMESTAMP
WHERE id=$1;`,
[entryId]
)
})
}
static async removeConsultantForEntry(entryId, userId) {
// TODO check for any other tables where entry id is foreign key
await db.query(
`DELETE FROM consultancy_entry_consultant
WHERE entry_id=$1 AND user_id=$2`,
[entryId, userId]
)
}
static async removeEntry(id) {
// TODO check for any other tables where entry id is foreign key
await db.transaction(async dbClient => {
await dbClient.query(
`DELETE FROM consultancy_entry_consultant
WHERE entry_id=$1`,
[id]
)
await dbClient.query(
`DELETE FROM consultancy_entry
WHERE id=$1`,
[id]
)
})
}
// (Re)index specific consultancy entry into consultancy search index.
static async indexIntoSearchEngine(entryId, shouldWait) {
const values = [entryId]
const text = `
SELECT
jsonb_strip_nulls(
jsonb_build_object(
'id', ce.id,
'timeCreated', ce.time_created,
'status', ce.status,
'description', ce.description,
'title', ce.title,
'question', ce.question,
'answer', ce.answer,
'answerAuthors', ce.answer_authors,
'primaryDomain', jsonb_strip_nulls(
jsonb_build_object(
'id', dp.id,
'nameSl', dp.name_sl,
'nameEn', dp.name_en
)
),
'assignedConsultants', ARRAY(
SELECT jsonb_strip_nulls(
jsonb_build_object(
'id', u.id,
'firstName', u.first_name,
'lastName', u.last_name,
'isModerator', cec.is_moderator
)
)
FROM consultancy_entry_consultant cec
LEFT JOIN "user" u on u.id = cec.user_id
WHERE cec.entry_id = ce.id
ORDER BY is_moderator DESC
)
)
) entry
FROM consultancy_entry ce
LEFT JOIN domain_primary dp on dp.id = ce.domain_primary_id
WHERE ce.id = $1`
let {
rows: [{ entry }]
} = await db.query(text, values)
if (!entry.answerAuthors?.length) delete entry.answerAuthors
if (!Object.keys(entry.primaryDomain).length) delete entry.primaryDomain
if (!entry.assignedConsultants.length) delete entry.assignedConsultants
entry = removeHtmlTags(JSON.stringify(entry))
await searchEngineClient.index({
id: entryId,
index: CONSULTANCY_ENTRY_INDEX,
body: entry,
refresh: shouldWait ? 'wait_for' : false
})
}
// Remove all consultancy entries from index and index them all again.
static async reindexAll() {
await deleteConsultancyEntriesFromIndex()
const { rows } = await db.query('SELECT id FROM consultancy_entry')
for (const row of rows) {
const { id: entryId } = row
await this.indexIntoSearchEngine(entryId)
}
}
}
module.exports = ConsultancyEntry