const mysql = require("mysql2/promise") const dayjs = require("dayjs") const _ = require("lodash") const log = require("../logger") const searchLib = require("./search") const {host, port, user, database, password} = require("./../settings").db ///////////////////////////////////////////////////////////////////////// let poolP = initPool() async function initPool() { return mysql.createPool({ host, port, database, user, password, }) } ///////////////////////////////////////////////////////////////////////// module.exports = { getUsers, getUserCount, getUserByLogin, getUserByUserId, getUserDefinedFields, getUserDefinedField, getUserTeilnahmen, getObjIdFromRefId, getRefIdFromObjId, getKurse, getKurs, getKursItems, getKursItems2, // getKursByObjId, // getKursByRefId, getKursTeilnehmer, getSingleKursTeilnehmer, getKursTeilnehmerCount, getKursLp, getKursUnterobjektLp, getKursOffline, setKursOffline, getUdf, getKursTeilnehmerRolle, getKursTeilnehmerByRole, getKursRoles, setStatus, getCourseAdminRoles, getCourseWithoutAdminRoles, } ///////////////////////////////////////////////////////////////////////// async function getUserSearchQuery(offset, limit, search) { const sel = `usr_id, login, firstname, lastname, gender, email, institution, street, city, zipcode, country, department, active` if (!search || search === '') { return `SELECT ${sel} FROM ${database}.usr_data AS ud WHERE login REGEXP '^[0-9]+$' LIMIT ${limit} OFFSET ${offset} ` } else { const ids = await searchLib.search(search) if (!ids.length) { throw "nothing found" return await getUserSearchQuery(offset, limit) } // nothing found return `SELECT ${sel} FROM ${database}.usr_data AS ud WHERE login REGEXP '^[0-9]+$' AND usr_id IN (${ids.join(",")}) LIMIT ${limit} OFFSET ${offset} ` } } async function getUsers(offset = 0, limit = 10, search = null) { // log.info("++++++++++ get users", offset, limit, search) limit = Number(limit) || 10 offset = Number(offset) || 0 // TODO check args for SQL Injection const pool = await poolP try { let userSearchQuery = await getUserSearchQuery(offset, limit, search) // log.info(userSearchQuery) const [results, fields] = await pool.query(userSearchQuery) // console.log(results, fields) const count = await getUserCount(offset, limit, search) return { total: count, offset, limit, data: results, } } catch (ex) { return { total: 0, offset: 0, limit: 0, data: [], } } } async function getUserCount(offset, limit, search) { const pool = await poolP // const q = getUserSearchQuery(offset, limit, search) const q = await getUserSearchQuery(0, 1000000, search) // hier darf kein Limit sein, offset=0 const q2 = `SELECT COUNT(*) AS count FROM (${q}) AS X` const [results, fields] = await pool.query(q2) return results[0].count // const [results, fields] = await pool.query( // `SELECT COUNT(*) // FROM ${database}.usr_data AS ud // WHERE login REGEXP '^[0-9]+$'` // ) // return results[0]["COUNT(*)"] } async function getUserByLogin(login) { const sel = `usr_id, login, firstname, lastname, gender, email, institution, street, city, zipcode, country, department, active` // TODO check args for SQL Injection const pool = await poolP const [results, fields] = await pool.query( `SELECT ${sel} FROM ${database}.usr_data AS ud WHERE login = '${login}'` ) return joinUDF(results[0]) } async function getUserByUserId(userId) { const sel = `usr_id, login, firstname, lastname, gender, email, institution, street, city, zipcode, country, department, active` // TODO check args for SQL Injection const pool = await poolP const [results, fields] = await pool.query( `SELECT ${sel} FROM ${database}.usr_data AS ud WHERE usr_id = '${userId}'` ) return joinUDF(results[0]) } async function getUserDefinedFields() { const pool = await poolP const [results] = await pool.query( `SELECT ut.usr_id, ud.field_name, ut.value FROM ${database}.udf_definition ud JOIN ${database}.udf_text ut ON ut.field_id = ud.field_id` ) return results } async function getUserDefinedField(usr_id) { const pool = await poolP const [results] = await pool.query( `SELECT ut.usr_id, ud.field_name, ut.value FROM ${database}.udf_definition ud JOIN ${database}.udf_text ut ON ut.field_id = ud.field_id WHERE ut.usr_id = '${usr_id}' ` ) return results } /////// obj_id / ref_id //////////////////////////////////////////////////////////////// /** * @param refId * @returns {Promise<{ref_id,obj_id}|undefined>} */ async function getObjIdFromRefId(refId) { const pool = await poolP const [results] = await pool.query( `SELECT ref_id, obj_id FROM ${database}.object_reference as obr WHERE obr.ref_id = ${refId} ` ) return results.length ? results[0] : undefined } async function getRefIdFromObjId(objId) { const pool = await poolP const [results] = await pool.query( `SELECT ref_id, obj_id FROM ${database}.object_reference as obr WHERE obr.obj_id = ${objId} ` ) return results.length ? results[0] : undefined } ///////////////////////////////////////////////////////////////////////// async function promiseDelay(ms) { return new Promise(resolve => setTimeout(resolve, ms)) } async function joinUDF(user) { if (!user) return user const fields = await getUserDefinedField(user.usr_id) for (const field of fields) { user[field.field_name] = field.value } return user } /////// Kurs //////////////////////////////////////////////////////////////// async function getKurse() { const pool = await poolP const q = `SELECT or2.ref_id, or2.obj_id, od.title, od.description, od.type, od.offline FROM ${database}.object_reference or2 INNER JOIN ${database}.object_data od ON od.obj_id = or2.obj_id WHERE od.type = 'crs' AND or2.deleted IS NULL ` const [results] = await pool.query(q) return results } // async function getKursByRefId (refId) { // const {ref_id, obj_id} = await getObjIdFromRefId(refId) // return getKursByObjId(obj_id) // } async function getKurs(ref_id) { const pool = await poolP const q = `SELECT or2.ref_id, or2.obj_id, od.title, od.description, od.type, od.create_date, od.offline FROM ${database}.object_reference or2 INNER JOIN ${database}.object_data od ON od.obj_id = or2.obj_id WHERE or2.ref_id = '${ref_id}' AND or2.deleted IS NULL ` let [results] = await pool.query(q) results = results.length ? results[0] : undefined return results } async function getKursItems(ref_id) { const pool = await poolP // const q = `SELECT or2.ref_id, or2.obj_id, od.title, od.description, od.type // FROM ${database}.object_reference or2 // INNER JOIN ${database}.object_data od ON od.obj_id = or2.obj_id // WHERE or2.obj_id = '${obj_id}' // ` const q = `SELECT ci.parent_id, or2.ref_id, or2.obj_id, od.title, od.type FROM ${database}.crs_items ci INNER JOIN ${database}.object_reference or2 ON or2.ref_id = ci.obj_id INNER JOIN ${database}.object_data od ON od.obj_id = or2.obj_id WHERE or2.ref_id = ${ref_id} OR parent_id = ${ref_id} AND or2.deleted is NULL ` const [results] = await pool.query(q) return results } async function getKursItems2(ref_id) { const pool = await poolP const q = ` WITH RECURSIVE tree (parent_id, obj_id, ref_id, title, type) AS (SELECT ci.parent_id, or2.obj_id, ci.obj_id as ref_id, od.title, od.type FROM ${database}.crs_items ci INNER JOIN ${database}.object_reference or2 ON or2.ref_id = ci.obj_id INNER JOIN ${database}.object_data od ON od.obj_id = or2.obj_id WHERE ci.obj_id = ${ref_id} -- Kurs ref_id AND or2.deleted is NULL UNION ALL SELECT child.parent_id, or2.obj_id, child.obj_id as ref_id, od.title, od.type FROM ${database}.crs_items child INNER JOIN ${database}.object_reference or2 ON or2.ref_id = child.obj_id INNER JOIN ${database}.object_data od ON od.obj_id = or2.obj_id JOIN tree ON child.parent_id = tree.ref_id WHERE or2.deleted is NULL) SELECT * FROM tree ORDER BY tree.ref_id ` const [results] = await pool.query(q) return results } async function getKursTeilnehmer(ref_id) { const pool = await poolP const q = `SELECT ci.parent_id, or2.ref_id, or2.obj_id, od.title, od.type, om.usr_id, ud.login, ud.firstname, ud.lastname, ud.active, om.passed, ulm.status, ulm.status_changed FROM ${database}.crs_items ci INNER JOIN ${database}.object_reference or2 ON or2.ref_id = ci.obj_id INNER JOIN ${database}.object_data od ON od.obj_id = or2.obj_id INNER JOIN ${database}.obj_members om ON om.obj_id = or2.obj_id AND om.member = 1 INNER JOIN ${database}.usr_data ud ON ud.usr_id = om.usr_id LEFT JOIN ${database}.ut_lp_marks ulm ON ulm.obj_id = or2.obj_id AND ud.usr_id = ulm.usr_id WHERE (or2.ref_id = ${ref_id} OR parent_id = ${ref_id}) AND or2.deleted IS NULL ORDER BY usr_id ` // log.info(q) const [results] = await pool.query(q) return results } async function getSingleKursTeilnehmer(ref_id, usr_id) { const pool = await poolP const q = `SELECT ci.parent_id, or2.ref_id, or2.obj_id, od.title, od.type, om.usr_id, ud.login, ud.firstname, ud.lastname, ud.active, om.passed, ulm.status, ulm.status_changed FROM ${database}.crs_items ci INNER JOIN ${database}.object_reference or2 ON or2.ref_id = ci.obj_id INNER JOIN ${database}.object_data od ON od.obj_id = or2.obj_id INNER JOIN ${database}.obj_members om ON om.obj_id = or2.obj_id AND om.member = 1 INNER JOIN ${database}.usr_data ud ON ud.usr_id = om.usr_id LEFT JOIN ${database}.ut_lp_marks ulm ON ulm.obj_id = or2.obj_id AND ud.usr_id = ulm.usr_id WHERE (or2.ref_id = ${ref_id} OR parent_id = ${ref_id}) AND om.usr_id = ${usr_id} ORDER BY usr_id ` // log.info(q) const [results] = await pool.query(q) return results[0] } async function getKursTeilnehmerCount(ref_id) { const pool = await poolP const q = `SELECT COUNT(*) as count FROM ${database}.crs_items ci INNER JOIN ${database}.object_reference or2 ON or2.ref_id = ci.obj_id INNER JOIN ${database}.object_data od ON od.obj_id = or2.obj_id INNER JOIN ${database}.obj_members om ON om.obj_id = or2.obj_id AND om.member = 1 INNER JOIN ${database}.usr_data ud ON ud.usr_id = om.usr_id LEFT JOIN ${database}.ut_lp_marks ulm ON ulm.obj_id = or2.obj_id AND ud.usr_id = ulm.usr_id WHERE (or2.ref_id = ${ref_id} OR parent_id = ${ref_id}) ORDER BY usr_id ` let [results] = await pool.query(q) results = results.length ? results[0] : undefined return {ref_id, count: results.count} } async function getUserTeilnahmen(usr_id) { const pool = await poolP const q = `SELECT om.obj_id, or2.ref_id, om.usr_id, od.title, ulm.status, om.passed, ulm.status_changed FROM ${database}.obj_members om INNER JOIN ${database}.object_reference or2 ON or2.obj_id = om.obj_id INNER JOIN ${database}.usr_data ud ON ud.usr_id = om.usr_id INNER JOIN ${database}.object_data od ON od.obj_id = om.obj_id LEFT JOIN ${database}.ut_lp_marks ulm ON ulm.usr_id = om.usr_id AND ulm.obj_id = om.obj_id WHERE om.usr_id = ${usr_id} AND om.member = 1 ` // log.info(q).catch(console.error) const [results] = await pool.query(q) return results } async function getKursLp(obj_id, raw = false) { const {ref_id} = await getRefIdFromObjId(obj_id) const tnUnter = await getKursUnterobjektLp(obj_id) if (raw) { return tnUnter } else { // LP aller Unterobjekte zusammenfassen const libLp = require("../lib/libLp") const data = libLp.alleAuswerten(tnUnter) return data } // const teilnehmer = await getKursTeilnehmer(ref_id) // const tnUnter = await getKursUnterobjektLp(obj_id) // console.table(teilnehmer) // console.table(tnUnter) // // /** // * Die beiden Datensätze mergen // * NEIN - es reicht der // * // */ // // const idx = _.groupBy(tnUnter, "usr_id") // for (const tn of teilnehmer) { // delete tn.parent_id // delete tn.type // delete tn.active // // /** @type Array */ // const unter = idx[tn.usr_id] // if (!unter) continue // // /** // * wenn unter.status_changed neuer, wird unter tn.status_changed vorgezogen // * wenn unter.status neuer, überscheibt es tn.status // * tn.passed muss zurückgesetzt werden wenn tn.status überschrieben wird // */ // tn.status_overwrite = false // // // max unter status_changed finden // const unterStatusChanged = _.max(unter.map(u => u.status_changed)) // // // es muss nur überschrieben werden wenn das Unterdatum größer ist // if (unterStatusChanged > tn.status_changed) { // // unter Status auswerten // // 0 = noch nicht bearbeitet // // 1 = in Bearbeitung // // 2 = bestanden // // 3 = nicht bestanden // // /** @type Array */ // const unterStatusse = unter.map(u => u.status) // let newStatus = tn.status // const allSame = function () { // if (!unterStatusse.length) return false // const first = unterStatusse[0] // return unterStatusse.every(it => it === first) // }() // console.log({unterStatusse}) // // Fall 1: keine Unterstatussse vorhanden -> status vom Kurs // if (!unterStatusse.length) { // newStatus = tn.status // } // // Fall 2: eines nicht bestanden -> nicht bestanden // 0,1,2,3 -> 3 // if (unterStatusse.some(u => u === 3)) { // newStatus = 3 // } // // Fall 3: alle statusse gleich -> status // 0,0,0 1,1,1 2,2,2 3,3,3 // else if (allSame) { // newStatus = unterStatusse[0] // } // // Fall 4: wenn eines in Bearbeitung -> in Bearbeitung // 0,1,0 2,1,2 // else if (unterStatusse.some(u => u === 1)) { // newStatus = 1 // } // // Fall 5: sonst in Bearbeitung // else { // // newStatus = Math.max.apply(this, unterStatusse) // newStatus = 1 // } // tn.status_changed = unterStatusChanged // tn.status = newStatus // tn.status_overwrite = true // } // } // return teilnehmer } async function getKursUnterobjektLp(obj_id) { const pool = await poolP const q = `SELECT ud.usr_id, ud.login, ud.firstname, ud.lastname, ulc.obj_id, ulc.item_id, ulc.lpmode, t.obj_id as item_obj_id, od.type, ulm.status, ulm.status_changed, ulm.percentage, ulm.completed FROM ${database}.ut_lp_collections ulc INNER JOIN ${database}.object_reference t ON t.ref_id = ulc.item_id INNER JOIN ${database}.ut_lp_marks ulm ON ulm.obj_id = t.obj_id INNER JOIN ${database}.usr_data ud ON ud.usr_id = ulm.usr_id INNER JOIN ${database}.object_data od ON od.obj_id = t.obj_id WHERE ulc.obj_id = ${obj_id} # obj_id Kurs AND ulc.active = 1 AND ulc.lpmode = 5 # nur mode 5 ORDER BY ud.usr_id, ulc.item_id ` const [results] = await pool.query(q) return results } async function getKursOffline(obj_id) { const pool = await poolP const q = `SELECT offline FROM ${database}.object_data WHERE obj_id = ${obj_id} ` const [results] = await pool.query(q) return results[0] } async function setKursOffline(isOffline, obj_id) { const pool = await poolP const q = `UPDATE ${database}.object_data SET offline = ${isOffline} WHERE obj_id = ${obj_id} ` const [results] = await pool.query(q) // return results return {offline: isOffline} } /////// UDF //////////////////////////////////////////////////////////////// async function getUdf() { const pool = await poolP const q = `SELECT field_id, field_name, field_type from ${database}.udf_definition;` const [results] = await pool.query(q) return results } /////// ROLLEN //////////////////////////////////////////////////////////////// async function getKursTeilnehmerRolle(obj_id) { const pool = await poolP const q = `SELECT obj_id, title, description from object_data od WHERE type = "role" #AND title LIKE 'il_crs_member_157' AND od.description LIKE 'Member%${obj_id}' ` const [results] = await pool.query(q) // darf nur einen Treffer ergeben if (results.length) { const {obj_id, title, description} = results[0] return obj_id } else { return null } } async function getKursTeilnehmerByRole(obj_id) { const pool = await poolP const q = ` SELECT obj_id as role_id, ru.usr_id, ud.firstname, ud.lastname FROM ${database}.object_data od INNER JOIN ${database}.rbac_ua ru ON ru.rol_id = od.obj_id INNER JOIN ${database}.usr_data ud ON ud.usr_id = ru.usr_id WHERE type = "role" #AND title LIKE 'il_crs_member_157' AND od.description LIKE 'Member%${obj_id}' ` const [results] = await pool.query(q) return results } async function getKursRoles(ref_id) { const pool = await poolP const q = ` SELECT pa.rol_id, or2.ref_id, or2.obj_id, od2.type, od2.title, od2.description FROM ${database}.rbac_pa pa INNER JOIN ${database}.object_reference or2 ON or2.ref_id = pa.ref_id INNER JOIN ${database}.object_data od ON od.obj_id = or2.obj_id INNER JOIN ${database}.object_data od2 ON od2.obj_id = pa.rol_id WHERE pa.ref_id = ${ref_id} ` const [results] = await pool.query(q) return results } /////// STATUS //////////////////////////////////////////////////////////////// /** * Zwei Möglichkeiten: * 1. es gibt schon einen Eintrag in ut_lp_marks, dann muss man diesen updaten * 2. es gibt noch keinen Eintrag in ut_lp_marks, dann muss erst einer erstellt werden (NEIN! Für neuen TN sind bereits beide Einträge vorhanden, kann also wegfallen) * * Weiterhin haben wir es mit zwei Tabellen zu tun: * - obj_members (passed) * - ut_lp_marks (status) * * UPDATE: Wir nehmen an, dass beide Einträge schon vorhanden sind! * * @param courseId * @param userId * @param {Number} passed * @param {Number} status * @returns {Promise<{status: string}>} */ async function setStatus(courseId, userId, passed = null, status = null) { const pool = await poolP log.info(`------------setStatus----- courseId=${courseId} userId=${userId} passed=${passed} status=${status}`) passed = isNaN(Number(passed)) ? passed : Number(passed) status = isNaN(Number(status)) ? status : Number(status) // ACHTUNG Transactions funktioneren so nicht, erst mal ohne machen... const q = ` START TRANSACTION; UPDATE ${database}.ut_lp_marks ulm SET status = ${status} WHERE ulm.usr_id = 31793 AND ulm.obj_id = 32212; UPDATE ${database}.obj_members om SET passed = ${passed} WHERE om.usr_id = ${userId} AND om.obj_id = ${courseId}; COMMIT; ` let date = dayjs().format('YYYY-MM-DD HH:mm:ss') const q1 = ` UPDATE ${database}.ut_lp_marks ulm SET status = ${status}, status_changed = "${date}" WHERE ulm.usr_id = ${userId} AND ulm.obj_id = ${courseId}; ` const q2 = ` UPDATE ${database}.obj_members om SET passed = ${passed} WHERE om.usr_id = ${userId} AND om.obj_id = ${courseId}; ` const [results1] = await pool.query(q1) const [results2] = await pool.query(q2) const {affectedRows: affectedRows1} = results1 const {affectedRows: affectedRows2} = results2 if (affectedRows1 && affectedRows2) { return {status: "ok"} } else { throw { status: "error", reason: {affectedRows1, affectedRows2} } } } /////// ADMINS //////////////////////////////////////////////////////////////// async function getCourseAdminRoles() { const pool = await poolP const q = ` SELECT od.obj_id as crs_obj_id, t.ref_id as crs_ref_id, od.title as crs_title, rf.rol_id, od2.title as role FROM ${database}.object_data od INNER JOIN ${database}.object_reference t ON t.obj_id = od.obj_id INNER JOIN ${database}.rbac_fa rf ON rf.parent = t.ref_id INNER JOIN ${database}.object_data od2 ON od2.obj_id = rf.rol_id WHERE od.\`type\` = "crs" AND od2.title LIKE "%admin%" ` const [results] = await pool.query(q) return results } async function getCourseWithoutAdminRoles() { const pool = await poolP const q = ` SELECT adminRoles.crs_obj_id, adminRoles.crs_ref_id, adminRoles.crs_title, adminRoles.rol_id, adminRoles.role FROM (SELECT od.obj_id as crs_obj_id, t.ref_id as crs_ref_id, od.title as crs_title, rf.rol_id, od2.title as role FROM ${database}.object_data od INNER JOIN ${database}.object_reference t ON t.obj_id = od.obj_id INNER JOIN ${database}.rbac_fa rf ON rf.parent = t.ref_id INNER JOIN ${database}.object_data od2 ON od2.obj_id = rf.rol_id WHERE od.\`type\` = "crs" AND od2.title LIKE "%admin%") adminRoles WHERE adminRoles.rol_id NOT IN (SELECT ru2.rol_id FROM ${database}.rbac_ua ru2) ` const [results] = await pool.query(q) return results } // !! wird nicht gebraucht - Admin Erkennung in ILIAS läuft anders ab - über die Rolle // async function getCourseAdmins() { // const pool = await poolP // const q = `SELECT om.obj_id as kurs_obj_id, // t.ref_id as kurs_ref_id, // om.usr_id, // ud.login, // om.admin, // ud.firstname, // ud.lastname, // od2.title // FROM ${database}.obj_members om // INNER JOIN ${database}.usr_data ud ON ud.usr_id = om.usr_id // INNER JOIN ${database}.object_data od2 ON od2.obj_id = om.obj_id // INNER JOIN ${database}.object_reference t ON t.obj_id = om.obj_id // WHERE om.admin = 1 // ` // const [results] = await pool.query(q) // return results // } // // /** // * Liefert die Kurse ohne Admins // * Admins hier definiert als Einträge in obj_members wo admin==1 // * Darüber hinaus gibt es offenbar noch einen anderen Mechanismus über die Rolle. // * Denn ein Kurs ohne Admin (z.B. lokal Fliesenratgeber ref_id=88) hat in ILIAS // * trotzdem einen Admin im Screen "Members". // * Dort wird wohl über die Rolle zugeordnet. // * // * Die Frage ist wo der Fehler GS-2333 auftritt. // * Bei obj_members oder bei fehlender Rolle. // * @return {Promise<*>} // */ // async function getCoursesWithNoAdmins() { // const pool = await poolP // const q = ` // SELECT asdf.obj_id, t.ref_id, asdf.numTn, asdf.title // FROM (SELECT om.obj_id, COUNT(*) as numTn, od.title, MAX(om.admin) as maxAdmin // FROM ${database}.obj_members om // INNER JOIN ${database}.object_data od ON od.obj_id = om.obj_id // GROUP by om.obj_id // ORDER BY numTn DESC) asdf // INNER JOIN ${database}.object_reference t ON t.obj_id = asdf.obj_id // WHERE asdf.maxAdmin = 0 // ` // const [results] = await pool.query(q) // return results // }