REST Service for POPCORN - ILIAS
alex
2025-11-28 a380a465ce02059a630ef678fecd6666cbdf8f30
lib/db.js
@@ -1,5 +1,8 @@
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
@@ -7,7 +10,7 @@
let poolP = initPool()
async function initPool () {
async function initPool() {
   return mysql.createPool({
      host,
      port,
@@ -38,13 +41,30 @@
   // getKursByObjId,
   // getKursByRefId,
   getKursTeilnehmer,
   getSingleKursTeilnehmer,
   getKursTeilnehmerCount,
   getKursLp,
   getKursUnterobjektLp,
   getKursOffline,
   setKursOffline,
   getUdf,
   getKursTeilnehmerRolle,
   getKursTeilnehmerByRole,
   getKursRoles,
   setStatus,
   getCourseAdminRoles,
   getCourseWithoutAdminRoles,
}
/////////////////////////////////////////////////////////////////////////
function getUserSearchQuery (offset, limit, search) {
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}
@@ -53,12 +73,11 @@
              LIMIT ${limit}
              OFFSET ${offset}
      `
   }
   else {
      const ids = searchLib.search(search)
   } else {
      const ids = await searchLib.search(search)
      if (!ids.length) {
         throw "nothing found"
         return getUserSearchQuery(offset, limit)
         return await getUserSearchQuery(offset, limit)
      } // nothing found
      return `SELECT ${sel}
              FROM ${database}.usr_data AS ud
@@ -70,8 +89,8 @@
   }
}
async function getUsers (offset = 0, limit = 10, search = null) {
   console.log("++++++++++ get users", offset, limit, search)
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
@@ -79,9 +98,10 @@
   const pool = await poolP
   try {
      let userSearchQuery = getUserSearchQuery(offset, limit, search)
      // console.log(userSearchQuery)
      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,
@@ -98,10 +118,10 @@
   }
}
async function getUserCount (offset, limit, search) {
async function getUserCount(offset, limit, search) {
   const pool = await poolP
   // const q = getUserSearchQuery(offset, limit, search)
   const q = getUserSearchQuery(0, 1000000, search) // hier darf kein Limit sein, offset=0
   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)
@@ -115,7 +135,7 @@
   // return results[0]["COUNT(*)"]
}
async function getUserByLogin (login) {
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
@@ -128,7 +148,7 @@
   return joinUDF(results[0])
}
async function getUserByUserId (userId) {
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
@@ -141,7 +161,7 @@
   return joinUDF(results[0])
}
async function getUserDefinedFields () {
async function getUserDefinedFields() {
   const pool = await poolP
   const [results] = await pool.query(
      `SELECT ut.usr_id, ud.field_name, ut.value
@@ -151,7 +171,7 @@
   return results
}
async function getUserDefinedField (usr_id) {
async function getUserDefinedField(usr_id) {
   const pool = await poolP
   const [results] = await pool.query(
      `SELECT ut.usr_id, ud.field_name, ut.value
@@ -165,7 +185,11 @@
/////// obj_id / ref_id ////////////////////////////////////////////////////////////////
async function getObjIdFromRefId (refId) {
/**
 * @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
@@ -176,7 +200,7 @@
   return results.length ? results[0] : undefined
}
async function getRefIdFromObjId (objId) {
async function getRefIdFromObjId(objId) {
   const pool = await poolP
   const [results] = await pool.query(
      `SELECT ref_id, obj_id
@@ -189,11 +213,11 @@
/////////////////////////////////////////////////////////////////////////
async function promiseDelay (ms) {
async function promiseDelay(ms) {
   return new Promise(resolve => setTimeout(resolve, ms))
}
async function joinUDF (user) {
async function joinUDF(user) {
   if (!user) return user
   const fields = await getUserDefinedField(user.usr_id)
   for (const field of fields) {
@@ -204,9 +228,9 @@
/////// Kurs ////////////////////////////////////////////////////////////////
async function getKurse () {
async function getKurse() {
   const pool = await poolP
   const q = `SELECT or2.ref_id, or2.obj_id, od.title, od.description, od.type
   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'
@@ -221,19 +245,20 @@
//    return getKursByObjId(obj_id)
// }
async function getKurs (ref_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
   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) {
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
@@ -252,32 +277,33 @@
   return results
}
async function getKursItems2 (ref_id) {
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
       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
                           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 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
@@ -286,7 +312,7 @@
   return results
}
async function getKursTeilnehmer (ref_id) {
async function getKursTeilnehmer(ref_id) {
   const pool = await poolP
   const q = `SELECT ci.parent_id,
                     or2.ref_id,
@@ -308,13 +334,46 @@
                       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 getKursTeilnehmerCount (ref_id) {
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
@@ -333,7 +392,7 @@
   return {ref_id, count: results.count}
}
async function getUserTeilnahmen (usr_id) {
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
@@ -345,7 +404,361 @@
              WHERE om.usr_id = ${usr_id}
                AND om.member = 1
   `
   console.log(q)
   // 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
// }