REST Service for POPCORN - ILIAS
alex
2025-11-28 21cfe68a2ac2304d9c034b9d247a61e9861af666
GS-2333
1 files added
7 files modified
387 ■■■■ changed files
lib/db.js 4 ●●● patch | view | raw | blame | history
package-lock.json 240 ●●●● patch | view | raw | blame | history
package.json 2 ●●● patch | view | raw | blame | history
vue/src/assets/styles.styl 3 ●●●●● patch | view | raw | blame | history
vue/src/components/Header.vue 1 ●●●● patch | view | raw | blame | history
vue/src/lib/api.js 12 ●●●●● patch | view | raw | blame | history
vue/src/pages/CourseAdmins.vue 119 ●●●●● patch | view | raw | blame | history
vue/src/router.js 6 ●●●●● patch | view | raw | blame | history
lib/db.js
@@ -691,6 +691,7 @@
                INNER JOIN ${database}.object_data od2 ON od2.obj_id = rf.rol_id
       WHERE od.\`type\` = "crs"
         AND od2.title LIKE "%admin%"
         AND t.deleted is NULL
   `
   const [results] = await pool.query(q)
   return results
@@ -706,7 +707,8 @@
                      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
               AND od2.title LIKE "%admin%"
               AND t.deleted is NULL) adminRoles
       WHERE adminRoles.rol_id NOT IN (SELECT ru2.rol_id
                                       FROM ${database}.rbac_ua ru2)
   `
package-lock.json
@@ -28,7 +28,7 @@
            "stylus": "^0.64.0",
            "vue": "^3.5.13",
            "vue-router": "^4.5.1",
            "yargs": "^15.4.1"
            "yargs": "^17.7.2"
         },
         "devDependencies": {
            "@vitejs/plugin-vue": "^5.2.3",
@@ -2281,15 +2281,6 @@
            "url": "https://github.com/sponsors/sindresorhus"
         }
      },
      "node_modules/camelcase": {
         "version": "5.3.1",
         "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
         "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
         "license": "MIT",
         "engines": {
            "node": ">=6"
         }
      },
      "node_modules/caniuse-lite": {
         "version": "1.0.30001720",
         "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001720.tgz",
@@ -2361,14 +2352,17 @@
         }
      },
      "node_modules/cliui": {
         "version": "6.0.0",
         "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
         "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
         "version": "8.0.1",
         "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
         "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
         "license": "ISC",
         "dependencies": {
            "string-width": "^4.2.0",
            "strip-ansi": "^6.0.0",
            "wrap-ansi": "^6.2.0"
            "strip-ansi": "^6.0.1",
            "wrap-ansi": "^7.0.0"
         },
         "engines": {
            "node": ">=12"
         }
      },
      "node_modules/color-convert": {
@@ -2495,15 +2489,6 @@
            "supports-color": {
               "optional": true
            }
         }
      },
      "node_modules/decamelize": {
         "version": "1.2.0",
         "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
         "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
         "license": "MIT",
         "engines": {
            "node": ">=0.10.0"
         }
      },
      "node_modules/deeks": {
@@ -3006,19 +2991,6 @@
         },
         "engines": {
            "node": ">=20"
         }
      },
      "node_modules/find-up": {
         "version": "4.1.0",
         "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
         "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
         "license": "MIT",
         "dependencies": {
            "locate-path": "^5.0.0",
            "path-exists": "^4.0.0"
         },
         "engines": {
            "node": ">=8"
         }
      },
      "node_modules/flat": {
@@ -3659,18 +3631,6 @@
         ],
         "license": "MIT"
      },
      "node_modules/locate-path": {
         "version": "5.0.0",
         "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
         "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
         "license": "MIT",
         "dependencies": {
            "p-locate": "^4.1.0"
         },
         "engines": {
            "node": ">=8"
         }
      },
      "node_modules/lodash": {
         "version": "4.17.21",
         "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
@@ -3895,20 +3855,6 @@
            "url": "https://paulmillr.com/funding/"
         }
      },
      "node_modules/mocha/node_modules/cliui": {
         "version": "8.0.1",
         "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
         "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
         "license": "ISC",
         "dependencies": {
            "string-width": "^4.2.0",
            "strip-ansi": "^6.0.1",
            "wrap-ansi": "^7.0.0"
         },
         "engines": {
            "node": ">=12"
         }
      },
      "node_modules/mocha/node_modules/find-up": {
         "version": "5.0.0",
         "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@@ -4079,50 +4025,6 @@
            "url": "https://github.com/chalk/supports-color?sponsor=1"
         }
      },
      "node_modules/mocha/node_modules/wrap-ansi": {
         "version": "7.0.0",
         "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
         "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
         "license": "MIT",
         "dependencies": {
            "ansi-styles": "^4.0.0",
            "string-width": "^4.1.0",
            "strip-ansi": "^6.0.0"
         },
         "engines": {
            "node": ">=10"
         },
         "funding": {
            "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
         }
      },
      "node_modules/mocha/node_modules/yargs": {
         "version": "17.7.2",
         "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
         "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
         "license": "MIT",
         "dependencies": {
            "cliui": "^8.0.1",
            "escalade": "^3.1.1",
            "get-caller-file": "^2.0.5",
            "require-directory": "^2.1.1",
            "string-width": "^4.2.3",
            "y18n": "^5.0.5",
            "yargs-parser": "^21.1.1"
         },
         "engines": {
            "node": ">=12"
         }
      },
      "node_modules/mocha/node_modules/yargs-parser": {
         "version": "21.1.1",
         "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
         "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
         "license": "ISC",
         "engines": {
            "node": ">=12"
         }
      },
      "node_modules/mrmime": {
         "version": "2.0.1",
         "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
@@ -4213,23 +4115,6 @@
            "string-width": "^4.2.0",
            "strip-ansi": "^6.0.0",
            "wrap-ansi": "^7.0.0"
         }
      },
      "node_modules/nconf/node_modules/wrap-ansi": {
         "version": "7.0.0",
         "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
         "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
         "license": "MIT",
         "dependencies": {
            "ansi-styles": "^4.0.0",
            "string-width": "^4.1.0",
            "strip-ansi": "^6.0.0"
         },
         "engines": {
            "node": ">=10"
         },
         "funding": {
            "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
         }
      },
      "node_modules/nconf/node_modules/yargs": {
@@ -4368,42 +4253,6 @@
         },
         "funding": {
            "url": "https://github.com/sponsors/sindresorhus"
         }
      },
      "node_modules/p-limit": {
         "version": "2.3.0",
         "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
         "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
         "license": "MIT",
         "dependencies": {
            "p-try": "^2.0.0"
         },
         "engines": {
            "node": ">=6"
         },
         "funding": {
            "url": "https://github.com/sponsors/sindresorhus"
         }
      },
      "node_modules/p-locate": {
         "version": "4.1.0",
         "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
         "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
         "license": "MIT",
         "dependencies": {
            "p-limit": "^2.2.0"
         },
         "engines": {
            "node": ">=8"
         }
      },
      "node_modules/p-try": {
         "version": "2.2.0",
         "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
         "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
         "license": "MIT",
         "engines": {
            "node": ">=6"
         }
      },
      "node_modules/package-json-from-dist": {
@@ -4771,12 +4620,6 @@
            "node": ">=0.10.0"
         }
      },
      "node_modules/require-main-filename": {
         "version": "2.0.0",
         "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
         "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
         "license": "ISC"
      },
      "node_modules/ret": {
         "version": "0.5.0",
         "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz",
@@ -4962,12 +4805,6 @@
         "dependencies": {
            "randombytes": "^2.1.0"
         }
      },
      "node_modules/set-blocking": {
         "version": "2.0.0",
         "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
         "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
         "license": "ISC"
      },
      "node_modules/set-cookie-parser": {
         "version": "2.7.1",
@@ -5778,12 +5615,6 @@
            "node": ">= 8"
         }
      },
      "node_modules/which-module": {
         "version": "2.0.1",
         "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
         "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
         "license": "ISC"
      },
      "node_modules/workerpool": {
         "version": "9.3.2",
         "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.2.tgz",
@@ -5791,9 +5622,9 @@
         "license": "Apache-2.0"
      },
      "node_modules/wrap-ansi": {
         "version": "6.2.0",
         "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
         "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
         "version": "7.0.0",
         "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
         "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
         "license": "MIT",
         "dependencies": {
            "ansi-styles": "^4.0.0",
@@ -5801,7 +5632,10 @@
            "strip-ansi": "^6.0.0"
         },
         "engines": {
            "node": ">=8"
            "node": ">=10"
         },
         "funding": {
            "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
         }
      },
      "node_modules/wrap-ansi-cjs": {
@@ -5854,38 +5688,30 @@
         "license": "ISC"
      },
      "node_modules/yargs": {
         "version": "15.4.1",
         "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
         "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
         "version": "17.7.2",
         "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
         "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
         "license": "MIT",
         "dependencies": {
            "cliui": "^6.0.0",
            "decamelize": "^1.2.0",
            "find-up": "^4.1.0",
            "get-caller-file": "^2.0.1",
            "cliui": "^8.0.1",
            "escalade": "^3.1.1",
            "get-caller-file": "^2.0.5",
            "require-directory": "^2.1.1",
            "require-main-filename": "^2.0.0",
            "set-blocking": "^2.0.0",
            "string-width": "^4.2.0",
            "which-module": "^2.0.0",
            "y18n": "^4.0.0",
            "yargs-parser": "^18.1.2"
            "string-width": "^4.2.3",
            "y18n": "^5.0.5",
            "yargs-parser": "^21.1.1"
         },
         "engines": {
            "node": ">=8"
            "node": ">=12"
         }
      },
      "node_modules/yargs-parser": {
         "version": "18.1.3",
         "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
         "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
         "version": "21.1.1",
         "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
         "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
         "license": "ISC",
         "dependencies": {
            "camelcase": "^5.0.0",
            "decamelize": "^1.2.0"
         },
         "engines": {
            "node": ">=6"
            "node": ">=12"
         }
      },
      "node_modules/yargs-unparser": {
@@ -5935,12 +5761,6 @@
         "engines": {
            "node": ">=8"
         }
      },
      "node_modules/yargs/node_modules/y18n": {
         "version": "4.0.3",
         "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
         "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
         "license": "ISC"
      },
      "node_modules/yocto-queue": {
         "version": "0.1.0",
package.json
@@ -34,7 +34,7 @@
      "stylus": "^0.64.0",
      "vue": "^3.5.13",
      "vue-router": "^4.5.1",
      "yargs": "^15.4.1"
      "yargs": "^17.7.2"
   },
   "devDependencies": {
      "@vitejs/plugin-vue": "^5.2.3",
vue/src/assets/styles.styl
@@ -34,6 +34,9 @@
.nowrap
   white-space nowrap
.muted
  color #666
input[type=text], input[type=password]
   padding .11em .22em
vue/src/components/Header.vue
@@ -16,6 +16,7 @@
      <div>|</div>
      <RouterLink :to="`${routerBase}/ui/user`">Users</RouterLink>
      <RouterLink :to="`${routerBase}/ui/kurs`">Courses</RouterLink>
      <RouterLink :to="`${routerBase}/ui/kurs/rolle/admin`">Course-Admins</RouterLink>
      <div style="flex-grow: 1" />
      <div>
         <button type="button" title="reindex search index" @click="doReindex">♻</button>
vue/src/lib/api.js
@@ -91,3 +91,15 @@
   console.log(data)
   return data
}
/////// Course Admin ////////////////////////////////////////////////////////////////
export async function getCourseAdmins () {
   const res = await fetch(`${apiBase}/kurs/rolle/admin?token=${apiToken.value}`)
   return res.json()
}
export async function getCourseNoAdmins () {
   const res = await fetch(`${apiBase}/kurs/rolle/noadmin?token=${apiToken.value}`)
   return res.json()
}
vue/src/pages/CourseAdmins.vue
New file
@@ -0,0 +1,119 @@
<script setup>
import {onMounted, reactive, ref} from "vue"
import {useRoute} from "vue-router"
import Pagination from "../components/Pagination.vue"
import {getCourseAdmins, getCourseNoAdmins, getUsers, iliasBase, routerBase, searchUsers} from "@/lib/api"
import {useRouteQuery} from '@vueuse/router'
import {onKeyStroke, useDebounceFn} from "@vueuse/core"
import LinkExtern from "@/components/LinkExtern.vue";
document.title = `Course-Admins | globus-ilias-rest`
const route = useRoute()
const adminRoles = ref([])
const adminRoles2 = ref([])
const error = ref(null)
onMounted(init)
/////////////////////////////////////////////////////////////////////////
async function init() {
   console.log(">>> INIT")
   const data1 = await getCourseAdmins()
   console.log({adminRoles:data1})
   adminRoles.value = data1
   const data2 = await getCourseNoAdmins()
   console.log({noAdminRoles:data2})
   adminRoles2.value = data2
}
</script>
<template>
   <div>
      <div>
         <h1>
            Admin-Rollen
            <small>{{ adminRoles.length }}</small>
         </h1>
         <p v-if="error">{{ error }}</p>
         <table class="w100p">
            <thead>
            <tr>
               <th>crs_obj_id</th>
               <th>crs_ref_id</th>
               <th>crs_title</th>
               <th>rol_id</th>
               <th>role</th>
            </tr>
            </thead>
            <tbody>
            <tr v-if="adminRoles" v-for="item in adminRoles">
               <td>{{ item.crs_obj_id }}</td>
               <td>
                  <a :href="`${iliasBase}/goto.php?target=crs_${item.crs_ref_id}`" target="_blank">
                     {{ item.crs_ref_id }}
                     <LinkExtern/>
                  </a>
               </td>
               <td>{{ item.crs_title }}</td>
               <td>{{ item.rol_id }}</td>
               <td>{{ item.role }}</td>
            </tr>
            </tbody>
         </table>
      </div>
      <br>
      <br>
      <div>
         <h1>Admin-Rollen ohne Benutzer
            <small>{{ adminRoles2.length }}</small>
         </h1>
         <p class="muted">Wenn ein Kurs <strong>keinen</strong> Benutzer mit einer Admin-Rolle zugewiesen hat, so kann
         von diesem Kurs kein TN gelöscht werden.</p>
         <table class="w100p">
            <thead>
            <tr>
               <th>crs_obj_id</th>
               <th>crs_ref_id</th>
               <th>crs_title</th>
               <th>rol_id</th>
               <th>role</th>
            </tr>
            </thead>
            <tbody>
            <tr v-if="adminRoles2" v-for="item in adminRoles2">
               <td>{{ item.crs_obj_id }}</td>
               <td>
                  <a :href="`${iliasBase}/goto.php?target=crs_${item.crs_ref_id}`" target="_blank">
                     {{ item.crs_ref_id }}
                     <LinkExtern/>
                  </a>
               </td>
               <td>{{ item.crs_title }}</td>
               <td>{{ item.rol_id }}</td>
               <td>{{ item.role }}</td>
            </tr>
            </tbody>
         </table>
      </div>
   </div>
</template>
<style scoped lang="stylus">
</style>
vue/src/router.js
@@ -9,6 +9,7 @@
import KursDetail from './pages/KursDetail.vue'
import KursDetailLp from "@/pages/KursDetailLp.vue";
import {routerBase} from "@/lib/api"
import CourseAdmins from "@/pages/CourseAdmins.vue";
const routes = [
@@ -43,6 +44,11 @@
      component: KursDetailLp,
      meta: { title: 'KursDetail LP | globus-ilias-rest' }
   },
   {
      path: `${routerBase}/ui/kurs/rolle/admin`,
      component: CourseAdmins,
      meta: { title: 'Course-Admins | globus-ilias-rest' }
   },
]
// console.log(routes)