const db = require('./db') const bcrypt = require('bcrypt') const uid = require('uid-safe') const { deserialize } = require('./helpers/user') const User = {} // Create new user in DB. User.create = async user => { const SALT_ROUNDS = 12 const bcryptHash = await bcrypt.hash(user.password, SALT_ROUNDS) const values = [ user.username || null, user.firstName || null, user.lastName || null, user.email || null, bcryptHash || null ] const text = `INSERT INTO "user" ( username, first_name, last_name, email, bcrypt_hash ) VALUES (${db.genParamStr(values)}) RETURNING id` const { rows } = await db.query(text, values) const userId = rows[0].id return userId } // Save an activation token for a single user in DB. User.saveActivationToken = async (userId, activationToken) => { await db.query( 'INSERT INTO user_token_activation (token, user_id) VALUES ($1, $2)', [activationToken, userId] ) } // 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] const { rows } = await db.query(text, values) const user = rows[0] // 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') return user } // Activate user account. User.activateAccount = async user => { await db.query(`UPDATE "user" SET status = 'active' WHERE id = $1`, [user.id]) } // Generate a user remember me token. User.generateRememberMeToken = async () => { const token = await uid(32) return token } // Save a remember me token for a single user in DB. User.saveRememberMeToken = async (user, rememberMeToken) => { await db.query( 'INSERT INTO user_token_remember_me (token, user_id) VALUES ($1, $2)', [rememberMeToken, user.id] ) } // Remove a specific remember me token from DB. User.clearRememberMeToken = async rememberMeToken => { await db.query('DELETE FROM user_token_remember_me WHERE token = $1', [ rememberMeToken ]) } // Fetch user data that should be available on every request from DB by id. User.fetchDeserializedDataById = async userId => { const text = ` SELECT u.id, u.username, u.first_name, u.last_name, u.email, u.hits_per_page, ARRAY( SELECT jsonb_build_object( 'roleName', r.role_name, 'dictionaryId', r.dictionary_id, 'administration', r.administration, 'terminologyReview', r.terminology_review, 'languageReview', r.language_review, 'editing', r.editing) FROM user_role r WHERE r.user_id = u.id ) user_roles, ARRAY( SELECT jsonb_build_object( 'id', cr.entry_id, 'isModerator', cr.is_moderator) FROM consultancy_entry_consultant cr WHERE cr.user_id = u.id ) assigned_consultancy_entries FROM "user" u WHERE u.id = $1` const values = [userId] const { rows: [user] } = await db.query(text, values) const deserializedUser = deserialize.userById(user) return deserializedUser } // Fetch all registered users on the portal User.fetchAll = async (resultsPerPage, page) => { const { rows: [{ result }] } = await db.query( ` SELECT jsonb_build_object( 'pages_total', ( SELECT CEIL(COUNT(*) / $1::float) FROM "user" ), 'results', ARRAY( SELECT jsonb_build_object( 'id', id, 'userName', username, 'email', email, 'status', status ) FROM "user" ORDER BY username LIMIT $1 OFFSET $2 ) ) result `, [resultsPerPage, resultsPerPage * (page - 1)] ) return result } // Fetch all users with at least one portal role along with all their portal roles. User.fetchAllWithPortalRoles = async () => { const text = ` SELECT u.id, u.username, u.email, jsonb_build_object( 'isPortalAdmin', 'portal admin' = ANY (r.roles_array), 'isDictionariesAdmin', 'dictionaries admin' = ANY (r.roles_array), 'isConsultancyAdmin', 'consultancy admin' = ANY (r.roles_array) ) roles FROM ( SELECT user_id, array_agg(role_name) roles_array FROM user_role WHERE role_name IN ('portal admin', 'dictionaries admin', 'consultancy admin') GROUP BY user_id ) r LEFT JOIN "user" u ON u.id = r.user_id ORDER BY u.id ` const { rows: users } = await db.query(text) return users } // Find searched user with specific username or email User.findByUsernameOrEmail = async userNameEmail => { const text = ` SELECT id, username, email FROM "user" WHERE username=$1 OR email=$1` const value = [userNameEmail] const { rows: user } = await db.query(text, value) return user } // Delete relevant portal roles and set new ones. User.updatePortalRoles = async rolesPerUser => { const rolesPerUserArr = Object.entries(rolesPerUser) const dbClient = await db.getClient() try { await dbClient.query('BEGIN') const removeAllPortalRolesQuery = ` DELETE FROM user_role WHERE role_name IN ('portal admin', 'dictionaries admin', 'consultancy admin')` await dbClient.query(removeAllPortalRolesQuery) const updateRoles = rolesPerUserArr.map(async ([userId, roles]) => { userId = +userId.replaceAll("'", '') const rolesArr = [] if (roles.isPortalAdmin) rolesArr.push('portal admin') if (roles.isDictionariesAdmin) rolesArr.push('dictionaries admin') if (roles.isConsultancyAdmin) { rolesArr.push('consultancy admin') rolesArr.push('consultant') } const updateRolesPerUser = rolesArr.map(async roleName => { const text = ` INSERT INTO user_role (user_id, role_name) VALUES ($1, $2)` const values = [userId, roleName] await dbClient.query(text, values) }) await Promise.all(updateRolesPerUser) }) await Promise.all(updateRoles) const countAdminsQuery = ` SELECT COUNT(*) portal_admins_count FROM user_role WHERE role_name = 'portal admin'` const { rows } = await dbClient.query(countAdminsQuery) const portalAdminsCount = +rows[0].portal_admins_count if (!portalAdminsCount) throw Error('Can not delete all portal admins') await dbClient.query('COMMIT') } catch (error) { await dbClient.query('ROLLBACK') throw error } finally { dbClient.release() } } User.fetchUser = async userId => { const text = ` SELECT id, username, first_name, last_name, email FROM "user" WHERE id=$1` const value = [userId] const { rows } = await db.query(text, value) const fetchedUser = rows[0] const deserializedUserData = deserialize.user(fetchedUser) return deserializedUserData } User.updateUser = async (userId, payload) => { const text = ` UPDATE "user" SET username = $2, first_name = $3, last_name = $4 WHERE id = $1` const values = [userId, payload.username, payload.firstName, payload.lastName] await db.query(text, values) } User.fetchUserRoles = async userId => { const text = ` SELECT jsonb_build_object( 'isPortalAdmin', 'portal admin' = ANY (r.roles_array), 'isDictionariesAdmin', 'dictionaries admin' = ANY (r.roles_array), 'isConsultancyAdmin', 'consultancy admin' = ANY (r.roles_array), 'isConsultant', 'consultant' = ANY (r.roles_array), 'isEditor', 'editor' = ANY (r.roles_array) ) roles FROM ( SELECT user_id, array_agg(role_name) roles_array FROM user_role WHERE role_name IN ('portal admin', 'dictionaries admin', 'consultancy admin', 'consultant', 'editor') GROUP BY user_id ) r LEFT JOIN "user" u ON u.id = r.user_id WHERE u.id=$1` const value = [userId] const { rows } = await db.query(text, value) const fetchedUser = rows[0] return fetchedUser } User.fetchAllWithDictionaryRights = async dictionaryId => { const text = ` SELECT u.username,u.email, u.id, r.administration, r.editing, r.terminology_review, r.language_review FROM user_role r INNER JOIN "user" u ON u.id = r.user_id WHERE dictionary_id = $1 ORDER BY u.username` const value = [dictionaryId] const { rows: fetchedUserRights } = await db.query(text, value) const deserializedRights = fetchedUserRights.map(user => deserialize.userRights(user) ) return deserializedRights } User.updateUserRights = async (dictionaryId, rightsPerUser) => { const rightsPerUserArr = Object.entries(rightsPerUser) const dbClient = await db.getClient() try { await dbClient.query('BEGIN') const value = [dictionaryId] const text = ` DELETE FROM user_role WHERE dictionary_id = $1` await dbClient.query(text, value) const roleName = 'editor' const updateRights = rightsPerUserArr.map(async ([userId, roles]) => { userId = +userId.replaceAll("'", '') const values = [ userId, roleName, dictionaryId, !!roles.isAdministration, !!roles.isEditing, !!roles.isTerminologyReview, !!roles.isLanguageReview ] const text = ` INSERT INTO user_role (user_id, role_name, dictionary_id, administration, editing, terminology_review, language_review) VALUES ($1, $2, $3, $4, $5, $6, $7)` await dbClient.query(text, values) }) await Promise.all(updateRights) const textCount = ` SELECT COUNT(*) dictionary_admins_count FROM user_role WHERE administration = true AND dictionary_id = $1` const { rows } = await dbClient.query(textCount, value) const portalAdminsCount = +rows[0].dictionary_admins_count if (!portalAdminsCount) throw Error('Can not delete all dictionary admins') await dbClient.query('COMMIT') } catch (error) { await dbClient.query('ROLLBACK') throw error } finally { dbClient.release() } } User.deleteUserDictionary = async dictionaryId => { const text = 'DELETE FROM user_role WHERE dictionary_id = $1' const value = [dictionaryId] await db.query(text, value) } // Fetch user with role consultant or consultancy admin User.fetchConsultants = async () => { // fetch domains string for the user const text = ` SELECT DISTINCT u.id, u.username, u.first_name, u.last_name, ur.domains FROM user_role ur INNER JOIN "user" u ON u.id = ur.user_id ORDER BY u.id ASC ` const { rows } = await db.query(text) const users = rows return users } // Insert consultancy role domains User.updateConsultancyDomains = async (id, value) => { await db.query('UPDATE user_role SET domains=$1 WHERE user_id = $2', [ value, id ]) } // Insert new consultant role with domain of User.insertNewConsultantWithDomain = async (userId, domains) => { const { rows } = await db.query( "SELECT user_id FROM user_role WHERE user_id = $1 and role_name = 'consultant'", [userId] ) if (rows.length) return await db.query( "INSERT INTO user_role (user_id, domains, role_name) VALUES ($1, $2, 'consultant')", [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] ) await User.insertNewConsultantWithDomain(rows[0].id, domains) } // Remove consultant role User.removeConsultant = async userId => { await db.query( `DELETE FROM user_role WHERE user_id=$1 and role_name='consultant'`, [userId] ) } User.fetchAllowedHitsPerPage = async () => { return ( await db.query(`SELECT unnest(enum_range(NULL::user_hits_per_page))`) ).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.updateHitsPerPage = async (username, hitsPerPageAmount) => { return await db.query( `UPDATE "user" SET hits_per_page=$2::user_hits_per_page WHERE username=$1;`, [username, hitsPerPageAmount] ) } module.exports = User