feat: migrate backend to cosmiconfig

This commit is contained in:
2023-04-25 15:54:06 +02:00
parent d8c79256c9
commit 0a76427ba8
70 changed files with 1786 additions and 1635 deletions

View File

@@ -0,0 +1,20 @@
// @ts-check
/**
* This is the database configuration for the technical university of berlin
*
* @type {import('@openstapps/logger').RecursivePartial<import('../../src/storage/elasticsearch/types/elasticsearch.js').ElasticsearchConfigFile>}
*/
const config = {
internal: {
database: {
name: 'elasticsearch',
query: {
minMatch: '60%',
queryType: 'query_string',
},
},
},
};
export default config;

View File

@@ -1,11 +0,0 @@
// tslint:disable:no-default-export
// tslint:disable:no-magic-numbers
import {SCConfigFile} from '@openstapps/core';
import {RecursivePartial} from '@openstapps/logger/lib/common';
/**
* This is the default configuration for the technical university of Berlin
*/
const config: RecursivePartial<SCConfigFile> = {};
export default config;

View File

@@ -1,610 +0,0 @@
// tslint:disable:no-default-export
// tslint:disable:no-magic-numbers
import {SCAboutPageContentType, SCConfigFile, SCLanguageCode} from '@openstapps/core';
import {RecursivePartial} from '@openstapps/logger/lib/common';
const markdownSources: {
[key in SCLanguageCode]?: {
/**
* Privacy policy markdown
*/
privacyPolicy?: string;
/**
* Terms and conditions markdown
*/
termsAndConditions?: string;
};
} = {en: {}, de: {}};
markdownSources.de!.privacyPolicy = `
# Datenschutzerklärung
## Kontaktdaten des Verantwortlichen
Verantwortlich im Sinne der Datenschutz-Grundverordnung und weiterer Vorschriften zum Datenschutz ist die:
Johann Wolfgang Goethe-Universität Frankfurt am Main vertreten durch ihren Präsidenten<br />
Theodor-W.-Adorno-Platz 1<br />
60323 Frankfurt am Main
Postanschrift:<br />
Goethe-Universität Frankfurt am Main<br />
60629 Frankfurt
Website: http://www.uni-frankfurt.de
## Kontaktdaten der Datenschutzbeauftragten an der Goethe-Universität
Sie erreichen die behördlichen Datenschutzbeauftragten der Johann Wolfgang Goethe-Universität Frankfurt am Main unter:<br />
Mail: <dsb@uni-frankfurt.de><br />
Website: http://www.uni-frankfurt.de/47859992/datenschutzbeauftragte
## Informationen zur Verarbeitung personenbezogener Daten
### <u>1. Umfang der Verarbeitung personenbezogener Daten</u>
Personenbezogene Daten sind gemäß Artikel 4 DSGVO alle Informationen, die sich auf eine identifizierte oder identifizierbare natürliche Person beziehen.
Wir verarbeiten personenbezogene Daten von Ihnen als Nutzer:innen der Goethe-Uni-App, soweit dies zur Bereitstellung einer **funktionsfähigen Applikation** technisch erforderlich ist.
Weiterhin kann eine Datenverarbeitung auf Ihrer freiwilligen Einwilligung basieren, wenn Sie **spezifische Funktionen** nutzen möchten.
Wir unterscheiden daher nachfolgend zwischen
- Zugriffsdaten bei der Nutzung der App: Inhalt der Anfragen, IP-Adressen, Datum/Uhrzeit der Anfrage, Angefragte URL, Fehlermeldungen, Browser-Kennung, HTTP-Header
- Standortbestimmung und Navigation: freiwillige Standortangaben
- Nutzer:inneneinstellungen: freiwillige Angabe von a) Sprachpräferenzen (derzeit: deutsch/englisch), b) Status (z. B. Gast/Student) oder c) spezifischen Suchanfragen und Suchergebnissen (Notifications)
- Kalenderfunktion: freiwillige Nutzung der Kalenderfunktion (optional mit freiwilliger Nutzung einer Synchronisationsfunktion: Opt-in) oder der integrierten Stundenplanfunktion, hierbei werden folgende Daten auf dem Endgerät verarbeitet und gespeichert: Termine und Veranstaltungen
- Feedbackfunktion und Kontaktaufnahme: freiwillige Nutzung mit der Angabe von Kontaktdaten und ggf. freiwilliger Übermittlung von Protokolldaten
- Campus Dienste: freiwillige Nutzung mit Verarbeitung von Notenansicht, Matrikelnummer, E-Mailadresse, Name
- Funktionen der Bibliothek: freiwillige Nutzung mit Verarbeitung von Bibliothekskontodaten, wie z.B. Ausweisnummer mit Name, E-Mailadresse, postalischer Adresse, Nutzungsberechtigung, Bestelldaten, Gebühren, Vormerkung, Ausleihdaten. Die vollständigen Angaben zur Verarbeitung finden Sie in der Datenschutzerklärung der Bibliothek:<br />
https://www.ub.uni-frankfurt.de/benutzung/datenschutz.html
Die App verlinkt an einigen Stellen auf die Website der Goethe-Universität sowie auf andere, externe Websites, die in einem In-App-Browser dargestellt werden. Wir bitten Sie bei Aufruf dieser Websites, die dort geltenden gesonderte Datenschutzhinweise und Erklärungen zu beachten.
### <u>2. Zweck(e) der Datenverarbeitung</u>
**Zugriff auf Standortdaten**
Für die Navigation benötigt die Goethe-Uni-App Zugriff auf den Standort des verwendeten Endgerätes (Location Based Services). Bei einer Anfrage erhebt die App den aktuellen Standort über GPS, Funkzellendaten und WLAN-Datenbanken, um Ihnen als Nutzer:in Informationen zu Ihrer unmittelbaren Umgebung geben zu können. Der Zugriff auf die Standortdaten erfolgt nur, wenn Sie den Zugriff auf die Standortdaten erlauben. Daten zu Ihrem Standort werden ausschließlich für die Bearbeitung von standortbezogenen Anfragen genutzt und um Ihren Standort auf der Karte anzuzeigen.
**Zugriff auf Zugriffsdaten**
Die Speicherung und Verarbeitung von Protokolldateien erfolgt, um die Funktionsfähigkeit der Goethe Uni-App für Sie sicherzustellen. Zudem benötigen wir die die Daten aus Gründen der Sicherheit unserer informationstechnischen Systeme. Eine anderweitige Auswertung oder Weitergabe findet in diesem Zusammenhang nicht statt.
**Zugriff auf Spracheinstellungen**
Der Zugriff auf die Spracheinstellung erfolgt um Ihnen die Oberfläche der App in der von Ihnen gewünschten Sprache anzuzeigen.
**Zugriff auf die Einstellung der Statusgruppe**
Der Zugriff auf die Einstellung der Statusgruppe erfolgt um Ihnen in der App die für Ihre Gruppe zutreffenden Informationen anzuzeigen, z.B. Mensapreise
**Zugriff auf personenbezogene Daten bei der Nutzung der Feedbackfunktion**
Die Verarbeitung der personenbezogenen Daten aus der Feedbackfunktion dient uns zur Kontaktaufnahme und Fehlerbehebung.
**Zugriff auf personenbezogene Daten bei der Kalendersynchronisation**
Der Zugriff auf die Termindaten erfolgt um sie bei aktivierter Kalenderfunktion in den Gerätekalender zu schreiben.
**Zugriff auf Daten der Campus Dienste**
Der Zugriff auf das Campus Management Systems erfolgt ausschließlich um persönliche Daten der Studierendenverwaltung in der App anzuzeigen (z.B. Prüfungsnoten).
**Zugriff auf bibliotheksspezifische personenbezogene Daten**
Der Zugriff auf die Daten (z.B. Ausweisnummer, Name, Postanschrift) erfolgt zur Durchführung von Bestell- und Ausleihverfahren von Büchern und sonstigen Materialien der Universitätsbibliothek. Die vollständigen Angaben zu den Verarbeitungszwecken finden Sie in der Datenschutzerklärung der Bibliothek: https://www.ub.uni-frankfurt.de/benutzung/datenschutz.html
### <u>3. Rechtsgrundlage(n) für die Datenverarbeitung</u>
Die Nutzung der Nutzungs-/Zugriffsdaten („Protokolldateien") basiert auf Artikel 6 Absatz 1 lit. f) DSGVO.
Für alle spezifischen Funktionen, bei denen die Datenverarbeitung auf Ihrer freiwilligen Einwilligung als Nutzer:innen basiert, werden explizit Einwilligungen bzw. aktive Zustimmungsakte („Opt-In") eingeholt. Die Bereitstellung personenbezogener Daten zu Ihrer Person gegenüber der Goethe-Universität erfolgen dabei auf freiwilliger Basis. Die Rechtsgrundlage ist in diesen Fällen jeweils Artikel 6 Absatz 1 lit. a) DSGVO. Sie können Ihre jeweilige Einwilligung jederzeit einzeln widerrufen bzw. Ihre Einstellungen ändern.
### <u>4. Datenlöschung und Speicherdauer</u>
Die in den Protokolldateien der App erfassten Daten werden sieben Tage nach dem Ende des Zugriffs automatisch gelöscht oder anonymisiert.
Die Löschfristen bzw. Speicherdauer der in den Bibliotheksystemen erfassten Daten finden Sie in der Datenschutzerklärung der Bibliothek: https://www.ub.uni-frankfurt.de/benutzung/datenschutz.html
Für alle anderen Funktionen und Dienste gilt: Die Löschung erfolgt hier je nach Vorgabe des genutzten Dienstes. Die personenbezogenen Daten der betroffenen Person werden gelöscht oder gesperrt, sobald der Zweck der Speicherung entfällt.
### <u>5. Datenweitergabe/Datenübermittlung</u>
Ihre personenbezogenen Daten werden von uns nicht an Dritte weitergegeben.
Von Betreiberseite wird durch technische und organisatorische Maßnahmen sichergestellt, dass Dritte keinen Zugriff auf die verarbeiteten Daten, wie z. B. Nutzungsdaten, erhalten. Ein Auftragsverarbeitungsverhältnis nach Art. 28 DSGVO besteht nicht, da ausschließlich eigene Server verwendet werden.
### <u>6. Automatisierte Entscheidungsfindung</u>
Eine automatisierte Entscheidungsfindung einschließlich Profiling erfolgt nicht.
## Rechte der betroffenen Person
Werden personenbezogene Daten von Ihnen verarbeitet, sind Sie Betroffener im Sinne der DSGVO. Die Geltendmachung Ihrer Betroffenenrechte ist kostenfrei. Sie können sich dafür selbstverständlich an uns wenden. Es stehen Ihnen folgende Betroffenenrechte gegenüber der Goethe-Universität zu:
### <u>1. Auskunftsrecht</u>
Sie können von uns als verantwortlicher Stelle eine Bestätigung darüber verlangen, ob und welche Ihrer personenbezogenen Daten von uns verarbeitet werden. Sie haben das Recht, von uns Kopien Ihrer personenbezogenen Daten zu verlangen. Bitte beachten Sie die Ausnahmen, die sich durch spezifische Vorschriften ergeben können.
### <u>2. Recht auf Berichtigung</u>
Sie haben das Recht von uns die Berichtigung und/oder Vervollständigung zu verlangen, sofern die verarbeiteten personenbezogenen Daten, die Sie betreffen, nicht (mehr) richtig oder nicht (mehr) vollständig sind.
### <u>3. Recht auf Einschränkung der Verarbeitung</u>
Unter bestimmten Voraussetzungen können Sie die Einschränkung der Verarbeitung der Sie betreffenden personenbezogenen Daten verlangen, d. h. dass dann Ihre personenbezogenen Daten zwar nicht gelöscht, aber gekennzeichnet werden, so dass eine weitere Verarbeitung eingeschränkt ist.
### <u>4. Recht auf Löschung</u>
Sie können unter bestimmten Voraussetzungen von uns verlangen, dass die Sie betreffenden personenbezogenen Daten unverzüglich gelöscht werden. Dies ist insbesondere der Fall, wenn die personenbezogenen Daten zu dem Zweck, zu dem sie ursprünglich erhoben oder verarbeitet wurden, nicht mehr erforderlich sind.
### <u>5. Recht auf Unterrichtung</u>
Haben Sie das Recht auf Berichtigung, Löschung oder Einschränkung der Verarbeitung uns gegenüber geltend gemacht, sind wir verpflichtet, allen Empfänger/innen, denen die Sie betreffenden personenbezogenen Daten offengelegt wurden, diese Berichtigung oder Löschung der Daten oder Einschränkung der Verarbeitung mitzuteilen, es sei denn, dies erweist sich als unmöglich oder ist mit einem unverhältnismäßigen Aufwand verbunden. Sie sind berechtigt, über diese Empfänger unterrichtet zu werden.
### <u>6. Recht auf Datenübertragbarkeit</u>
Sie haben unter bestimmten Voraussetzungen das Recht von uns zu verlangen, dass Ihre personenbezogenen Daten von uns direkt an einen anderen Verantwortlichen oder an eine andere Organisation übermittelt werden. Alternativ haben Sie unter bestimmten Voraussetzungen das Recht von uns zu verlangen, dass wir Ihnen selbst die Daten in einem maschinenlesbaren Format bereitstellen.
### <u>7. Widerspruchsrecht</u>
Wenn wir Ihre personenbezogenen Daten verarbeiten, weil die Verarbeitung im öffentlichen Interesse, Teil unserer öffentlichen Aufgaben ist bzw. wenn wir Ihre Daten auf Basis eines berechtigten Interesses verarbeiten, haben Sie aus Gründen, die sich aus Ihrer besonderen Situation ergeben, das Recht, jederzeit der Verarbeitung der Sie betreffenden Daten zu widersprechen.
### <u>8. Recht auf Widerruf der datenschutzrechtlichen Einwilligungserklärung</u>
Wenn wir Ihre personenbezogenen Daten verarbeiten, weil Sie uns Ihre Einwilligung gegeben haben, haben Sie jederzeit das Recht, Ihre Einwilligungserklärung zu widerrufen.
### <u>9. Recht auf Beschwerde bei einer Aufsichtsbehörde</u>
Sie haben ferner das Recht auf Beschwerde bei einer Aufsichtsbehörde. Die zuständige Aufsichtsbehörde wird Ihre Beschwerde prüfen.
## **Kontaktdaten der Aufsichtsbehörde im Bereich Datenschutz**
Wenn Sie der Ansicht sind, dass eine Verarbeitung der Sie betreffenden personenbezogenen Daten gegen Datenschutzvorschriften verstößt, wenn Sie eine allgemeine Anfrage haben oder wenn Sie sich bei einer zuständigen Fachaufsichtsbehörde beschweren wollen, können Sie sich an den Hessischen Beauftragten für Datenschutz und Informationsfreiheit (HBDI) wenden.
**Der Hessische Beauftragte für Datenschutz und Informationsfreiheit ist auf unterschiedlichen Wegen erreichbar:**
<u>**Der Hessische Beauftragte für Datenschutz und Informationsfreiheit**</u><br />
Postfach 3163<br />
65021 Wiesbaden
Telefon: +49 611 1408 -- 0
Für allgemeine Anfragen können Sie ein Kontaktformular nutzen:<br />
<https://datenschutz.hessen.de/kontakt><br />
<br />
Für Beschwerden steht Ihnen zudem ein Beschwerdeformular zur Verfügung:<br />
<https://datenschutz.hessen.de/service/beschwerde-uebermitteln>
`;
markdownSources.en!.privacyPolicy = `
# Privacy policy
## Contact details of the person responsible
Responsible in the sense of the General Data Protection Regulation and further regulations on data protection is the:
Johann Wolfgang Goethe-Universität Frankfurt am Main represented by its president<br />
Theodor-W.-Adorno-Platz 1<br />
60323 Frankfurt am Main
Postanschrift:<br />
Goethe-Universität Frankfurt am Main<br />
60629 Frankfurt
Website: http://www.uni-frankfurt.de
## Contact details of the data protection officer at Goethe University
You can reach the data protection officers at Johann Wolfgang Goethe University Frankfurt am Main at:<br />
Mail: <dsb@uni-frankfurt.de><br />
Website: http://www.uni-frankfurt.de/47859992/datenschutzbeauftragte
## Information on the processing of personal data
### <u>1. Scope of the processing of personal data</u>.
According to Article 4 DSGVO, personal data is any information relating to an identified or identifiable natural person.
We process personal data of you as a user inside of the Goethe University App to the extent that this is technically necessary for the provision of a **functional application**.
Furthermore, data processing may be based on your voluntary consent if you wish to use **specific functions**.
We therefore distinguish below between
- Access data when using the app: content of requests, IP addresses, date/time of request, requested URL, error codes, browser identifier, HTTP header.
- Location and navigation: voluntary location information
- User settings: voluntary specification of a) language preferences (currently: German/English), b) status (e.g. guest/student) or c) specific search queries and search results (notifications)
- Calendar function: voluntary use of the calendar function (optional with voluntary use of a sync function: opt-in) or the integrated timetable function. The following data is processed and stored on the users device: appointments and events
- Feedback function and contacting: voluntary use with the provision of contact data and, if applicable, voluntary transmission of log data
- Campus services: voluntary use with processing of grade view, matriculation number, email address, name
- Services of the library: voluntary use with processing of library account data, such as ID number with name, e-mail address, postal address, right of use, order data, fees, reservation, loan data. Full details of processing can be found in the library's privacy policy:<br />
https://www.ub.uni-frankfurt.de/benutzung/datenschutz.html
In some places, the app links to the Goethe University website and to other external websites that are displayed in an in-app browser. When you visit these websites, we ask you to pay attention to the separate data protection notices and declarations that apply there.
### <u>2. Purpose(s) of data processing</u>
**Access to location data**.
For navigation, the Goethe University app requires access to the location of the end device used (location-based services). When a request is made, the app collects the current location via GPS, radio cell data and WLAN databases in order to be able to give you as a user:in information about your immediate surroundings. The location data is only accessed if you allow access to the location data. Data about your location is only used to process location-related requests and to display your location on the map.
**Access to access data**.
Log files are stored and processed to ensure that the Goethe Uni app functions properly for you. In addition, we need the data for reasons of security of our information technology systems. No other evaluation or disclosure takes place in this context.
**Access to language settings**.
Access to the language setting is made in order to display the interface of the app in the language of your choice.
**Access to the status group setting**.
Access to the status group setting is provided to show you the information in the app that applies to your group, e.g. canteen prices.
**Access to personal data when using the feedback function**.
We use the processing of personal data from the feedback function to contact you and troubleshoot problems.
**Access to personal data when synchronizing calendars**.
Appointment data is accessed in order to write it to the device calendar when the calendar function is enabled.
**Access to Campus Services data**.
Access to the Campus Management System is solely for the purpose of displaying personal student management data in the app (e.g., exam grades).
**Access to library-specific personal data**.
Access to data (e.g., ID number, name, mailing address) is for the purpose of carrying out ordering and borrowing procedures for books and other materials from the University Library. Full details of the purposes of processing can be found in the Library's Privacy Policy: https://www.ub.uni-frankfurt.de/benutzung/datenschutz.html
### <u>3. Rechtsgrundlage(n) für die Datenverarbeitung</u>
The use of usage/access data ("log files") is based on Article 6(1)(f) DSGVO.
For all specific functions where data processing is based on your voluntary consent as a user:in, explicit consent or active acts of consent ("opt-in") are obtained. The provision of personal data about you to Goethe University is done on a voluntary basis. The legal basis in each of these cases is Article 6 (1) a) DSGVO. You can individually revoke your respective consent or change your settings at any time.
### <u>4. Data deletion and storage duration</u>
The data collected in the log files of the app are automatically deleted or anonymized seven days after the end of the access.
The deletion periods or storage duration of the data collected in the library systems can be found in the library's privacy policy: https://www.ub.uni-frankfurt.de/benutzung/datenschutz.html
For all other functions and services, the following applies: deletion takes place here depending on the specifications of the service used. The personal data of the data subject will be deleted or blocked as soon as the purpose of the storage no longer applies.
### <u>5. Data disclosure/data transfer</u>
We will not pass on your personal data to third parties.
On the part of the operator, technical and organizational measures are taken to ensure that third parties do not gain access to the processed data, such as usage data. An order processing relationship according to Art. 28 DSGVO does not exist, as only our own servers are used.
### <u>6. Automated decision-making</u>
Automated decision-making, including profiling, does not take place.
## Rights of the data subject
If personal data is processed by you, you are a data subject within the meaning of the GDPR. The assertion of your data subject rights is free of charge. You can, of course, contact us for this purpose. You are entitled to the following data subject rights vis-à-vis Goethe University:
### <u>1. Right of access</u>
You can request confirmation from us as the controller as to whether and which of your personal data is being processed by us. You have the right to request copies of your personal data from us. Please note the exceptions that may arise due to specific regulations.
### <u>2. Right of rectification</u>
You have the right to request us to rectify and/or complete, if the processed personal data concerning you is not (anymore) accurate or not (anymore) complete.
### <u>3. Right to restriction of processing</u>
Under certain conditions, you can request the restriction of the processing of personal data concerning you, i.e. that your personal data is then not deleted, but marked so that further processing is restricted.
### <u>4. Right to erasure</u>
Under certain conditions, you can demand that we delete the personal data concerning you without delay. This is particularly the case if the personal data is no longer necessary for the purpose for which it was originally collected or processed.
### <u>5. Right to information</u>
If you have asserted the right to rectification, erasure or restriction of processing against us, we are obliged to inform all recipients to whom the personal data concerning you have been disclosed of this rectification or erasure of the data or restriction of processing, unless this proves impossible or involves a disproportionate effort. You are entitled to be informed about these recipients.
### <u>6. Right to data portability</u>
Under certain conditions, you have the right to request that we transfer your personal data directly to another controller or organization. Alternatively, under certain conditions, you have the right to request that we ourselves provide you with the data in a machine-readable format.
### <u>7. Right to object</u>
If we process your personal data because the processing is in the public interest, part of our public duties, or if we process your data on the basis of a legitimate interest, you have the right to object at any time to the processing of data relating to you for reasons arising from your particular situation.
### <u>8. Right to revoke the declaration of consent under data protection law</u>
If we process your personal data because you have given us your consent, you have the right to revoke your declaration of consent at any time.
### <u>9. Right to lodge a complaint with a supervisory authority</u>
You also have the right to lodge a complaint with a supervisory authority. The competent supervisory authority will examine your complaint.
## **Contact details of the supervisory authority in the area of data protection**
If you believe that the processing of your personal data violates data protection regulations, if you have a general inquiry or if you want to complain to a competent supervisory authority, you can contact the Hessian Commissioner for Data Protection and Freedom of Information (HBDI).
**The Hessian Commissioner for Data Protection and Freedom of Information can be reached in different ways:**
<u>**The Hessian Commissioner for Data Protection and Freedom of Information**</u><br />
PO Box 3163<br />
65021 Wiesbaden
Telephone: +49 611 1408 -- 0
For general inquiries you can use a contact form:<br />
<https://datenschutz.hessen.de/kontakt><br />
<br />
A complaint form is also available for complaints:<br />
<https://datenschutz.hessen.de/service/beschwerde-uebermitteln>
`;
/**
* This is the default configuration for the Goethe university of Frankfurt
*/
const config: RecursivePartial<SCConfigFile> = {
auth: {
default: {
client: {
clientId: '1cac3f99-33fa-4234-8438-979f07e0cdab',
scopes: '',
url: 'https://cas.rz.uni-frankfurt.de/cas/oauth2.0',
},
endpoints: {
authorization: 'https://cas.rz.uni-frankfurt.de/cas/oauth2.0/authorize',
endSession: 'https://cas.rz.uni-frankfurt.de/cas/logout',
mapping: {
id: '$.id',
email: '$.attributes.mailPrimaryAddress',
familyName: '$.attributes.sn',
givenName: '$.attributes.givenName',
name: '$.attributes.givenName',
role: '$.attributes.eduPersonPrimaryAffiliation',
studentId: '$.attributes.employeeNumber',
},
token: 'https://cas.rz.uni-frankfurt.de/cas/oauth2.0/accessToken',
userinfo: 'https://cas.rz.uni-frankfurt.de/cas/oauth2.0/profile',
},
},
paia: {
client: {
clientId: '',
scopes: '',
url: 'https://hds.hebis.de/Shibboleth.sso/UBFFM?target=https://hds.hebis.de/ubffm/paia_login_stub.php',
},
endpoints: {
authorization:
'https://hds.hebis.de/Shibboleth.sso/UBFFM?target=https://hds.hebis.de/ubffm/paia_login_stub.php',
endSession: 'https://ubffm.hds.hebis.de/Shibboleth.sso/Logout',
mapping: {
id: '$.email',
name: '$.name',
role: '$.type',
},
token: 'https://hds.hebis.de/paia/auth/login',
userinfo: 'https://hds.hebis.de/paia/core',
},
},
},
app: {
features: {
extern: {
hisometry: {
authProvider: 'default',
url: 'https://his-self-service.rz.uni-frankfurt.de',
},
daia: {
url: 'https://daia.hebis.de/DAIA2/UB_Frankfurt',
},
hebisProxy: {
url: 'https://proxy.ub.uni-frankfurt.de/login?qurl=',
},
paia: {
authProvider: 'paia',
url: 'https://hds.hebis.de/paia/core',
},
},
},
aboutPages: {
'about': {
title: 'Über Open StApps',
content: [
{
title: 'Verbundprojekt mehrerer Hochschulen für eine generische Studierenden-App',
content: {
type: SCAboutPageContentType.MARKDOWN,
value: `
Open StApps bietet Studierenden aller beteiligten Hochschulen eine qualitativ
hochwertige App für den Studienalltag. Open StApps-Verbundpartner integrieren
generalisierbare Studierendenprozesse so in App-Module, dass diese auch
von anderen Hochschulen verwendet werden können. Die in der Open StApps App
verwendeten Daten einer Datenquelle sind in einem generalisierten Datenmodell
so aufbereitet, dass ein Austausch oder Abschaltung der Datenquelle problemlos möglich
ist und die Open StApps App problemlos weiterhin funktionsfähig bleibt.
`,
translations: {
en: {
value: `Open StApps provides students from all participating universities with a
high-quality app for everyday study. Open StApps partners integrate
generalizable student processes into app modules in such a way that they can be
can be used by other universities. The data of a data source used in the Open StApps app
is prepared in a generalized data model in a way that the data source can be easily
exchanged or switched off while the app continues to function without any problems.
`,
},
},
},
translations: {
en: {
title: 'Collaborative project of multiple universities for a single generic study app',
},
},
type: SCAboutPageContentType.SECTION,
},
{
title: 'Goethe-Uni Kontakt',
content: {
rows: [
[
{
value: 'Adresse',
translations: {
en: {
value: 'Address',
},
},
type: SCAboutPageContentType.MARKDOWN,
},
{
value: `
Goethe Universität<br>
Hochschulrechenzentrum (HRZ)<br>
Norbert-Wollheim-Platz 1<br>
60629 Frankfurt
`,
translations: {},
type: SCAboutPageContentType.MARKDOWN,
},
],
[
{
value: 'Kontaktinformation',
translations: {
en: {
value: 'Contact information',
},
},
type: SCAboutPageContentType.MARKDOWN,
},
{
value:
'[app@rz.uni-frankfurt.de](mailto:app@rz.uni-frankfurt.de)<br>' +
'[+49 69 798 32936](tel:+496979832936)<br>' +
'[https://app.rz.uni-frankfurt.de](https://app.rz.uni-frankfurt.de)',
translations: {},
type: SCAboutPageContentType.MARKDOWN,
},
],
],
type: SCAboutPageContentType.TABLE,
},
translations: {
en: {
title: 'Goethe-Uni Contact',
},
},
type: SCAboutPageContentType.SECTION,
},
{
icon: 'newspaper',
title: 'Neue Funktionen / Gelöste Probleme',
link: 'changelog',
translations: {
en: {
title: 'New features / Resolved issues',
},
},
type: SCAboutPageContentType.ROUTER_LINK,
},
{
icon: 'description',
title: 'Impressum',
link: 'imprint',
translations: {
en: {
title: 'Imprint',
},
},
type: SCAboutPageContentType.ROUTER_LINK,
},
{
icon: 'policy',
title: 'Datenschutz',
link: 'privacy',
translations: {
en: {
title: 'Privacy policy',
},
},
type: SCAboutPageContentType.ROUTER_LINK,
},
{
icon: 'copyright',
title: 'Bibliotheken und Lizenzen',
link: 'licenses',
translations: {
en: {
title: 'Libraries and licenses',
},
},
type: SCAboutPageContentType.ROUTER_LINK,
},
],
translations: {
en: {
title: 'About Open StApps',
},
},
},
'about/imprint': {
title: 'Impressum',
content: [
{
value: `
[Impressum der Johann Wolfgang Goethe-Universität Frankfurt am Main](https://www.uni-frankfurt.de/impressum)
`,
translations: {
en: {
value: `
[Imprint of the Goethe University Frankfurt](https://www.uni-frankfurt.de/impressum)
`,
},
},
type: SCAboutPageContentType.MARKDOWN,
},
],
translations: {
en: {
title: 'Imprint',
},
},
},
'about/privacy': {
title: 'Datenschutz',
content: [
{
value: markdownSources.de!.privacyPolicy,
translations: {
en: {
value: markdownSources.en!.privacyPolicy,
},
},
type: SCAboutPageContentType.MARKDOWN,
},
],
translations: {
en: {
title: 'Privacy Policy',
},
},
},
},
},
};
export default config;

View File

@@ -1,11 +0,0 @@
// tslint:disable:no-default-export
// tslint:disable:no-magic-numbers
import {SCConfigFile} from '@openstapps/core';
import {RecursivePartial} from '@openstapps/logger/lib/common';
/**
* This is the default configuration for the university of Kassel
*/
const config: RecursivePartial<SCConfigFile> = {};
export default config;

View File

@@ -1,11 +0,0 @@
// tslint:disable:no-default-export
// tslint:disable:no-magic-numbers
import {SCConfigFile} from '@openstapps/core';
import {RecursivePartial} from '@openstapps/logger/lib/common';
/**
* This is the default configuration for the university of Kassel
*/
const config: RecursivePartial<SCConfigFile> = {};
export default config;

View File

@@ -1,741 +0,0 @@
import {
SCAboutPageContentType,
SCConfigFile,
SCLanguageSetting,
SCSettingInputType,
SCThingOriginType,
SCThingType,
SCUserGroupSetting,
} from '@openstapps/core';
import {readFileSync} from 'fs';
import path from 'path';
/**
* Generates a range of numbers that represent consecutive calendric months
*
* @param startMonth The month to start with (inclusive)
* @param endMonth The month to end with (inclusive)
*/
export function yearSlice(startMonth: number, endMonth: number) {
let months = [...Array.from({length: 13}).keys()].slice(1);
months = [...months, ...months];
if (!months.includes(startMonth) || !months.includes(endMonth)) {
throw new Error(`Given months not part of a year! Check ${startMonth} or ${endMonth}!`);
}
const startIndex = months.indexOf(startMonth);
const endIndex =
months.indexOf(endMonth) <= startIndex ? months.lastIndexOf(endMonth) : months.indexOf(endMonth);
return months.slice(startIndex, endIndex + 1);
}
const sommerRange = yearSlice(3, 8);
const winterRange = yearSlice(9, 2);
const month = new Date().getMonth();
const year = new Date().getFullYear();
const winterYearOffset = month < winterRange[0] ? -1 : 0;
const sommerYear = year + (month <= winterRange[winterRange.length] ? -1 : 0);
const winterYear = `${year + winterYearOffset}/${(year + 1 + winterYearOffset).toString().slice(-2)}`;
const wsAcronymShort = `WS ${winterYear}`;
const ssAcronymShort = `SS ${sommerYear}`;
const wsAcronymLong = `WiSe ${winterYear}`;
const ssAcronymLong = `SoSe ${sommerYear}`;
const userGroupSetting: SCUserGroupSetting = {
categories: ['profile'],
defaultValue: 'students',
description:
'The user group the app is going to be used.' +
'This settings for example is getting used for the predefined price category of mensa meals.',
inputType: SCSettingInputType.SingleChoice,
name: 'group',
order: 1,
origin: {
indexed: '2018-09-11T12:30:00Z',
name: 'SCConfigFile Default Values',
type: SCThingOriginType.Remote,
},
translations: {
de: {
description:
'Mit welcher Benutzergruppe soll die App verwendet werden?' +
' Die Einstellung wird beispielsweise für die Vorauswahl der Preiskategorie der Mensa verwendet.',
name: 'Gruppe',
values: ['Studierende', 'Angestellte', 'Gäste'],
},
en: {
description:
'The user group the app is going to be used.' +
' This settings for example is getting used for the predefined price category of mensa meals.',
name: 'Group',
values: ['students', 'employees', 'guests'],
},
},
type: SCThingType.Setting,
uid: '2c97aa36-4aa2-43de-bc5d-a2b2cb3a530e',
values: ['students', 'employees', 'guests'],
};
const languageSetting: SCLanguageSetting = {
categories: ['profile'],
defaultValue: 'en',
description: 'The language this app is going to use.',
inputType: SCSettingInputType.SingleChoice,
name: 'language',
order: 0,
origin: {
indexed: '2018-09-11T12:30:00Z',
name: 'SCConfigFile Default Values',
type: SCThingOriginType.Remote,
},
translations: {
de: {
description: 'Die Sprache in der die App angezeigt wird.',
name: 'Sprache',
values: ['Deutsch', 'English'],
},
en: {
description: 'The language this app is going to use.',
name: 'Language',
values: ['Deutsch', 'English'],
},
},
type: SCThingType.Setting,
uid: 'dc9d6dec-6576-45ef-9e35-3598c0d6a662',
values: ['de', 'en'],
};
/**
* This is the default configuration for app and backend
*
* University specific files can be created with following naming scheme: default-<university license plate>.ts
*
* To select your university specific configuration which is merged from this default file and your university specific
* file, you have to supply the `NODE_APP_INSTANCE` environment variable with your license plate
*
* To get more information about the meaning of specific fields please have a look at `@openstapps/core` or use your
* IDE to read the TSDoc documentation.
*/
const config: SCConfigFile = {
app: {
aboutPages: {
'about': {
title: 'Über das Open StApps Projekt',
content: [
{
title: 'Verbundprojekt mehrerer Hochschulen für eine generische Studierenden-App',
content: {
type: SCAboutPageContentType.MARKDOWN,
value: `
Open StApps bietet Studierenden aller beteiligten Hochschulen eine qualitativ
hochwertige App für den Studienalltag. Open StApps-Verbundpartner integrieren
generalisierbare Studierendenprozesse so in App-Module, dass diese auch
von anderen Hochschulen verwendet werden können. Die in der Open StApps App
verwendeten Daten einer Datenquelle sind in einem generalisierten Datenmodell
so aufbereitet, dass ein Austausch oder Abschaltung der Datenquelle problemlos möglich
ist und die Open StApps App problemlos weiterhin funktionsfähig bleibt.
`,
translations: {
en: {
value: `
Open StApps provides students from all participating universities with a
high-quality app for everyday study. Open StApps partners integrate
generalizable student processes into app modules in such a way that they can be
can be used by other universities. The data of a data source used in the Open StApps app
is prepared in a generalized data model in a way that the data source can be easily
exchanged or switched off while the app continues to function without any problems.
`,
},
},
},
translations: {
en: {
title: 'Collaborative project of multiple universities for a single generic study app',
},
},
type: SCAboutPageContentType.SECTION,
},
{
title: 'Universitätskontakt',
content: {
rows: [
[
{
value: 'Adresse',
translations: {
en: {
value: 'address',
},
},
type: SCAboutPageContentType.MARKDOWN,
},
{
value: `
Nirmasi Universität<br>
Abteilung für digitale Angelegenheiten (AbtDigi)<br>
Null Island 1<br>
999999 Atlantic Ocean
`,
translations: {
en: {
value: 'This would be the english address',
},
},
type: SCAboutPageContentType.MARKDOWN,
},
],
[
{
value: 'Kontaktinformationen',
translations: {
en: {
value: 'Contact information',
},
},
type: SCAboutPageContentType.MARKDOWN,
},
{
value: '[mail]()<br>' + '[+49 12 345 67890]()<br>' + '[https://localhost/]()',
translations: {
en: {
value: 'This would be the english contact information',
},
},
type: SCAboutPageContentType.MARKDOWN,
},
],
],
type: SCAboutPageContentType.TABLE,
},
translations: {
en: {
title: 'Nimrasi-Uni Kontakt',
},
},
type: SCAboutPageContentType.SECTION,
},
{
icon: 'campaign',
title: 'Neue Funktionen / Gelöste Probleme',
link: 'changelog',
translations: {
en: {
title: 'Changelog / Resolved issues',
},
},
type: SCAboutPageContentType.ROUTER_LINK,
},
{
icon: 'description',
title: 'Impressum',
link: 'imprint',
translations: {
en: {
title: 'Imprint',
},
},
type: SCAboutPageContentType.ROUTER_LINK,
},
{
icon: 'policy',
title: 'Datenschutz',
link: 'privacy',
translations: {
en: {
title: 'Privacy policy',
},
},
type: SCAboutPageContentType.ROUTER_LINK,
},
{
icon: 'copyright',
title: 'Bibliotheken und Lizenzen',
link: 'licenses',
translations: {
en: {
title: 'Libraries and licenses',
},
},
type: SCAboutPageContentType.ROUTER_LINK,
},
],
translations: {
en: {
title: 'About',
},
},
},
'about/imprint': {
title: 'Impressum',
content: [
{
title: 'Beteiligte Universitäten',
card: true,
content: {
value: `
[Johann Wolfgang Goethe-Universität Frankfurt am Main](https://uni-frankfurt.de)<br>
[Philipps-Universität Marburg](https://www.uni-marburg.de)<br>
[Technische Hochschule Mittelhessen](https://www.thm.de)<br>
[Universität Kassel](https://www.uni-kassel.de)<br>
weitere Hochschulen und Mitarbeitende
`,
translations: {
en: {
value: `
[Goethe University Frankfurt](https://uni-frankfurt.de)<br>
[University of Marburg](https://www.uni-marburg.de)<br>
[University of Applied Sciences Mittelhessen](https://www.thm.de)<br>
[University of Kassel](https://www.uni-kassel.de)<br>
further universities and developers
`,
},
},
type: SCAboutPageContentType.MARKDOWN,
},
translations: {
en: {
title: 'Collaborating Universities',
},
},
type: SCAboutPageContentType.SECTION,
},
],
translations: {
en: {
title: 'Imprint',
},
},
},
'about/privacy': {
title: 'Datenschutz',
content: [
{
value: 'Hier wäre der Datenschutz',
translations: {
en: {
value: 'This would be the privacy policy',
},
},
type: SCAboutPageContentType.MARKDOWN,
},
],
translations: {
en: {
title: 'Privacy Policy',
},
},
},
},
campusPolygon: {
coordinates: [
[
[8.660_432_999_690_723, 50.123_027_017_044_436],
[8.675_496_285_518_358, 50.123_027_017_044_436],
[8.675_496_285_518_358, 50.130_661_764_486_42],
[8.660_432_999_690_723, 50.130_661_764_486_42],
[8.660_432_999_690_723, 50.123_027_017_044_436],
],
],
type: 'Polygon',
},
features: {},
menus: [
{
icon: 'home',
items: [
{
icon: 'newspaper',
route: '/news',
title: 'news',
translations: {
de: {
title: 'Aktuelles',
},
en: {
title: 'news',
},
},
},
{
icon: 'search',
route: '/search',
title: 'search',
translations: {
de: {
title: 'Suche',
},
en: {
title: 'search',
},
},
},
{
icon: 'local_library',
route: '/hebis-search',
title: 'library catalog',
translations: {
de: {
title: 'Bibliothekskatalog',
},
en: {
title: 'library catalog',
},
},
},
{
icon: 'inventory_2',
route: '/catalog',
title: 'course catalog',
translations: {
de: {
title: 'Vorlesungsverzeichnis',
},
en: {
title: 'course catalog',
},
},
},
],
title: 'overview',
route: '/overview',
translations: {
de: {
title: 'Übersicht',
},
en: {
title: 'overview',
},
},
},
{
icon: 'local_cafe',
items: [],
route: '/canteen',
title: 'canteen',
translations: {
de: {
title: 'Mensa',
},
en: {
title: 'canteen',
},
},
},
{
icon: 'map',
items: [],
route: '/map',
title: 'campus map',
translations: {
de: {
title: 'Campus Karte',
},
en: {
title: 'campus map',
},
},
},
{
icon: 'school',
items: [
{
icon: 'grade',
route: '/favorites',
title: 'favorites',
translations: {
de: {
title: 'Favoriten',
},
en: {
title: 'favorites',
},
},
},
{
icon: 'calendar_today',
route: '/schedule',
title: 'schedule',
translations: {
de: {
title: 'Stundenplan',
},
en: {
title: 'schedule',
},
},
},
{
authProvider: 'paia',
icon: 'badge',
route: '/library-account',
title: 'library account',
translations: {
de: {
title: 'Bibliothekskonto',
},
en: {
title: 'library account',
},
},
},
{
icon: 'settings',
route: '/settings',
title: 'settings',
translations: {
de: {
title: 'Einstellungen',
},
en: {
title: 'settings',
},
},
},
{
icon: 'rate_review',
route: '/feedback',
title: 'feedback',
translations: {
de: {
title: 'Feedback',
},
en: {
title: 'feedback',
},
},
},
{
icon: 'info',
route: '/about',
title: 'about',
translations: {
de: {
title: 'Über die App',
},
en: {
title: 'About the App',
},
},
},
],
title: 'my app',
route: '/profile',
translations: {
de: {
title: 'Meine App',
},
en: {
title: 'my app',
},
},
},
],
name: 'Goethe-Uni',
privacyPolicyUrl: 'https://mobile.server.uni-frankfurt.de/_static/privacy.md',
settings: [userGroupSetting, languageSetting],
},
auth: {},
backend: {
SCVersion: JSON.parse(readFileSync(path.resolve('.', '.', 'package.json'), 'utf8').toString())
.dependencies['@openstapps/core'],
externalRequestTimeout: 5000,
hiddenTypes: [SCThingType.DateSeries, SCThingType.Diff, SCThingType.Floor],
mappingIgnoredTags: ['minlength', 'pattern', 'see', 'tjs-format'],
maxMultiSearchRouteQueries: 5,
maxRequestBodySize: 512 * 1024,
name: 'Goethe-Universität Frankfurt am Main',
namespace: '909a8cbc-8520-456c-b474-ef1525f14209',
sortableFields: [
{
fieldName: 'name',
sortTypes: ['ducet'],
},
{
fieldName: 'type',
sortTypes: ['ducet'],
},
{
fieldName: 'categories',
onlyOnTypes: [
SCThingType.AcademicEvent,
SCThingType.Building,
SCThingType.Catalog,
SCThingType.Dish,
SCThingType.PointOfInterest,
SCThingType.Room,
],
sortTypes: ['ducet'],
},
{
fieldName: 'geo',
onlyOnTypes: [SCThingType.Building, SCThingType.PointOfInterest, SCThingType.Room],
sortTypes: ['distance'],
},
{
fieldName: 'geo',
onlyOnTypes: [SCThingType.Building, SCThingType.PointOfInterest, SCThingType.Room],
sortTypes: ['distance'],
},
{
fieldName: 'inPlace.geo',
onlyOnTypes: [
SCThingType.DateSeries,
SCThingType.Dish,
SCThingType.Floor,
SCThingType.Organization,
SCThingType.PointOfInterest,
SCThingType.Room,
SCThingType.Ticket,
],
sortTypes: ['distance'],
},
{
fieldName: 'offers',
onlyOnTypes: [SCThingType.Dish],
sortTypes: ['price'],
},
],
},
internal: {
aggregations: [
{
fieldName: 'categories',
onlyOnTypes: [
SCThingType.AcademicEvent,
SCThingType.Article,
SCThingType.Building,
SCThingType.Catalog,
SCThingType.Dish,
SCThingType.PointOfInterest,
SCThingType.Room,
],
},
{
fieldName: 'inPlace.name',
onlyOnTypes: [
SCThingType.DateSeries,
SCThingType.Dish,
SCThingType.Floor,
SCThingType.Organization,
SCThingType.PointOfInterest,
SCThingType.Room,
SCThingType.Ticket,
],
},
{
fieldName: 'academicTerms.acronym',
onlyOnTypes: [SCThingType.AcademicEvent, SCThingType.SportCourse],
},
{
fieldName: 'academicTerm.acronym',
onlyOnTypes: [SCThingType.Catalog],
},
{
fieldName: 'majors',
onlyOnTypes: [SCThingType.AcademicEvent],
},
{
fieldName: 'keywords',
onlyOnTypes: [SCThingType.Article, SCThingType.Book, SCThingType.Message, SCThingType.Video],
},
{
fieldName: 'type',
},
],
boostings: {
default: [
{
factor: 1,
fields: {
'academicTerms.acronym': {
[ssAcronymShort]: sommerRange.includes(month) ? 1.1 : 1.05,
[wsAcronymShort]: winterRange.includes(month) ? 1.1 : 1.05,
[ssAcronymLong]: sommerRange.includes(month) ? 1.1 : 1.05,
[wsAcronymLong]: winterRange.includes(month) ? 1.1 : 1.05,
},
},
type: SCThingType.AcademicEvent,
},
{
factor: 1,
fields: {
categories: {
'course': 1.08,
'integrated course': 1.08,
'introductory class': 1.05,
'lecture': 1.1,
'seminar': 1.01,
'tutorial': 1.05,
},
},
type: SCThingType.AcademicEvent,
},
{
factor: 1.6,
type: SCThingType.Building,
},
{
factor: 1,
fields: {
categories: {
cafe: 1.1,
learn: 1.1,
library: 1.2,
restaurant: 1.1,
},
},
type: SCThingType.PointOfInterest,
},
{
factor: 1,
fields: {
categories: {
'main dish': 2,
},
},
type: SCThingType.Dish,
},
],
dining: [
{
factor: 1,
fields: {
categories: {
'cafe': 2,
'canteen': 2,
'restaurant': 2,
'restroom': 1.2,
'student canteen': 2,
},
},
type: SCThingType.Building,
},
{
factor: 2,
type: SCThingType.Dish,
},
],
place: [
{
factor: 2,
type: SCThingType.Building,
},
{
factor: 2,
type: SCThingType.PointOfInterest,
},
{
factor: 2,
type: SCThingType.Room,
},
],
},
},
uid: 'f-u',
};
// tslint:disable-next-line:no-default-export
export default config;

View File

@@ -0,0 +1,27 @@
import userGroupSetting from './user-group-setting.js';
import languageSetting from './language-setting.js';
import menus from './menu.js';
/** @type {import('@openstapps/core').SCAppConfiguration} */
const app = {
aboutPages: {},
campusPolygon: {
coordinates: [
[
[8.660_432_999_690_723, 50.123_027_017_044_436],
[8.675_496_285_518_358, 50.123_027_017_044_436],
[8.675_496_285_518_358, 50.130_661_764_486_42],
[8.660_432_999_690_723, 50.130_661_764_486_42],
[8.660_432_999_690_723, 50.123_027_017_044_436],
],
],
type: 'Polygon',
},
features: {},
menus,
name: 'Goethe-Uni',
privacyPolicyUrl: 'https://mobile.server.uni-frankfurt.de/_static/privacy.md',
settings: [userGroupSetting, languageSetting],
};
export default app;

View File

@@ -0,0 +1,34 @@
// @ts-check
import {SCSettingInputType, SCThingOriginType, SCThingType} from '@openstapps/core';
/** @type {import('@openstapps/core').SCLanguageSetting} */
const languageSetting = {
categories: ['profile'],
defaultValue: 'en',
description: 'The language this app is going to use.',
inputType: SCSettingInputType.SingleChoice,
name: 'language',
order: 0,
origin: {
indexed: '2018-09-11T12:30:00Z',
name: 'SCConfigFile Default Values',
type: SCThingOriginType.Remote,
},
translations: {
de: {
description: 'Die Sprache in der die App angezeigt wird.',
name: 'Sprache',
values: ['Deutsch', 'English'],
},
en: {
description: 'The language this app is going to use.',
name: 'Language',
values: ['Deutsch', 'English'],
},
},
type: SCThingType.Setting,
uid: 'dc9d6dec-6576-45ef-9e35-3598c0d6a662',
values: ['de', 'en'],
};
export default languageSetting;

View File

@@ -0,0 +1,195 @@
// @ts-check
/** @type {import('@openstapps/core').SCAppConfigurationMenuCategory[]} */
const menus = [
{
icon: 'home',
items: [
{
icon: 'newspaper',
route: '/news',
title: 'news',
translations: {
de: {
title: 'Aktuelles',
},
en: {
title: 'news',
},
},
},
{
icon: 'search',
route: '/search',
title: 'search',
translations: {
de: {
title: 'Suche',
},
en: {
title: 'search',
},
},
},
{
icon: 'local_library',
route: '/hebis-search',
title: 'library catalog',
translations: {
de: {
title: 'Bibliothekskatalog',
},
en: {
title: 'library catalog',
},
},
},
{
icon: 'inventory_2',
route: '/catalog',
title: 'course catalog',
translations: {
de: {
title: 'Vorlesungsverzeichnis',
},
en: {
title: 'course catalog',
},
},
},
],
title: 'overview',
route: '/overview',
translations: {
de: {
title: 'Übersicht',
},
en: {
title: 'overview',
},
},
},
{
icon: 'local_cafe',
items: [],
route: '/canteen',
title: 'canteen',
translations: {
de: {
title: 'Mensa',
},
en: {
title: 'canteen',
},
},
},
{
icon: 'map',
items: [],
route: '/map',
title: 'campus map',
translations: {
de: {
title: 'Campus Karte',
},
en: {
title: 'campus map',
},
},
},
{
icon: 'school',
items: [
{
icon: 'grade',
route: '/favorites',
title: 'favorites',
translations: {
de: {
title: 'Favoriten',
},
en: {
title: 'favorites',
},
},
},
{
icon: 'calendar_today',
route: '/schedule',
title: 'schedule',
translations: {
de: {
title: 'Stundenplan',
},
en: {
title: 'schedule',
},
},
},
{
authProvider: 'paia',
icon: 'badge',
route: '/library-account',
title: 'library account',
translations: {
de: {
title: 'Bibliothekskonto',
},
en: {
title: 'library account',
},
},
},
{
icon: 'settings',
route: '/settings',
title: 'settings',
translations: {
de: {
title: 'Einstellungen',
},
en: {
title: 'settings',
},
},
},
{
icon: 'rate_review',
route: '/feedback',
title: 'feedback',
translations: {
de: {
title: 'Feedback',
},
en: {
title: 'feedback',
},
},
},
{
icon: 'info',
route: '/about',
title: 'about',
translations: {
de: {
title: 'Über die App',
},
en: {
title: 'About the App',
},
},
},
],
title: 'my app',
route: '/profile',
translations: {
de: {
title: 'Meine App',
},
en: {
title: 'my app',
},
},
},
];
export default menus;

View File

@@ -0,0 +1,40 @@
// @ts-check
import {SCSettingInputType, SCThingOriginType, SCThingType} from '@openstapps/core';
/** @type {import('@openstapps/core').SCUserGroupSetting} */
const userGroupSetting = {
categories: ['profile'],
defaultValue: 'students',
description:
'The user group the app is going to be used.' +
'This settings for example is getting used for the predefined price category of mensa meals.',
inputType: SCSettingInputType.SingleChoice,
name: 'group',
order: 1,
origin: {
indexed: '2018-09-11T12:30:00Z',
name: 'SCConfigFile Default Values',
type: SCThingOriginType.Remote,
},
translations: {
de: {
description:
'Mit welcher Benutzergruppe soll die App verwendet werden?' +
' Die Einstellung wird beispielsweise für die Vorauswahl der Preiskategorie der Mensa verwendet.',
name: 'Gruppe',
values: ['Studierende', 'Angestellte', 'Gäste'],
},
en: {
description:
'The user group the app is going to be used.' +
' This settings for example is getting used for the predefined price category of mensa meals.',
name: 'Group',
values: ['students', 'employees', 'guests'],
},
},
type: SCThingType.Setting,
uid: '2c97aa36-4aa2-43de-bc5d-a2b2cb3a530e',
values: ['students', 'employees', 'guests'],
};
export default userGroupSetting;

View File

@@ -0,0 +1,51 @@
// @ts-check
import {SCThingType} from '@openstapps/core';
/** @type {import('@openstapps/core').SCBackendAggregationConfiguration[]} */
const aggregations = [
{
fieldName: 'categories',
onlyOnTypes: [
SCThingType.AcademicEvent,
SCThingType.Article,
SCThingType.Building,
SCThingType.Catalog,
SCThingType.Dish,
SCThingType.PointOfInterest,
SCThingType.Room,
],
},
{
fieldName: 'inPlace.name',
onlyOnTypes: [
SCThingType.DateSeries,
SCThingType.Dish,
SCThingType.Floor,
SCThingType.Organization,
SCThingType.PointOfInterest,
SCThingType.Room,
SCThingType.Ticket,
],
},
{
fieldName: 'academicTerms.acronym',
onlyOnTypes: [SCThingType.AcademicEvent, SCThingType.SportCourse],
},
{
fieldName: 'academicTerm.acronym',
onlyOnTypes: [SCThingType.Catalog],
},
{
fieldName: 'majors',
onlyOnTypes: [SCThingType.AcademicEvent],
},
{
fieldName: 'keywords',
onlyOnTypes: [SCThingType.Article, SCThingType.Book, SCThingType.Message, SCThingType.Video],
},
{
fieldName: 'type',
},
];
export default aggregations;

View File

@@ -0,0 +1,103 @@
// @ts-check
import {
month,
sommerRange,
ssAcronymLong,
ssAcronymShort,
winterRange,
wsAcronymLong,
wsAcronymShort,
} from '../tools/semester-acronym.js';
import {SCThingType} from '@openstapps/core';
/** @type {import('@openstapps/core').SCBackendConfigurationSearchBoostingContext} */
const boostings = {
default: [
{
factor: 1,
fields: {
'academicTerms.acronym': {
[ssAcronymShort]: sommerRange.includes(month) ? 1.1 : 1.05,
[wsAcronymShort]: winterRange.includes(month) ? 1.1 : 1.05,
[ssAcronymLong]: sommerRange.includes(month) ? 1.1 : 1.05,
[wsAcronymLong]: winterRange.includes(month) ? 1.1 : 1.05,
},
},
type: SCThingType.AcademicEvent,
},
{
factor: 1,
fields: {
categories: {
'course': 1.08,
'integrated course': 1.08,
'introductory class': 1.05,
'lecture': 1.1,
'seminar': 1.01,
'tutorial': 1.05,
},
},
type: SCThingType.AcademicEvent,
},
{
factor: 1.6,
type: SCThingType.Building,
},
{
factor: 1,
fields: {
categories: {
cafe: 1.1,
learn: 1.1,
library: 1.2,
restaurant: 1.1,
},
},
type: SCThingType.PointOfInterest,
},
{
factor: 1,
fields: {
categories: {
'main dish': 2,
},
},
type: SCThingType.Dish,
},
],
dining: [
{
factor: 1,
fields: {
categories: {
'cafe': 2,
'canteen': 2,
'restaurant': 2,
'restroom': 1.2,
'student canteen': 2,
},
},
type: SCThingType.Building,
},
{
factor: 2,
type: SCThingType.Dish,
},
],
place: [
{
factor: 2,
type: SCThingType.Building,
},
{
factor: 2,
type: SCThingType.PointOfInterest,
},
{
factor: 2,
type: SCThingType.Room,
},
],
};
export default boostings;

View File

@@ -0,0 +1,73 @@
// @ts-check
import {SCThingType} from '@openstapps/core';
import aggregations from './aggregations.js';
import boostings from './boostings.js';
import {readFile} from 'fs/promises';
/** @type {import('@openstapps/core').SCBackendInternalConfiguration} */
export const internal = {
aggregations,
boostings,
};
/** @type {import('@openstapps/core').SCBackendConfiguration} */
export const backend = {
SCVersion: JSON.parse(await readFile('package.json', 'utf8')).version,
externalRequestTimeout: 5000,
hiddenTypes: [SCThingType.DateSeries, SCThingType.Diff, SCThingType.Floor],
mappingIgnoredTags: ['minlength', 'pattern', 'see', 'tjs-format'],
maxMultiSearchRouteQueries: 5,
maxRequestBodySize: 512 * 1024,
name: 'Goethe-Universität Frankfurt am Main',
namespace: '909a8cbc-8520-456c-b474-ef1525f14209',
sortableFields: [
{
fieldName: 'name',
sortTypes: ['ducet'],
},
{
fieldName: 'type',
sortTypes: ['ducet'],
},
{
fieldName: 'categories',
onlyOnTypes: [
SCThingType.AcademicEvent,
SCThingType.Building,
SCThingType.Catalog,
SCThingType.Dish,
SCThingType.PointOfInterest,
SCThingType.Room,
],
sortTypes: ['ducet'],
},
{
fieldName: 'geo',
onlyOnTypes: [SCThingType.Building, SCThingType.PointOfInterest, SCThingType.Room],
sortTypes: ['distance'],
},
{
fieldName: 'geo',
onlyOnTypes: [SCThingType.Building, SCThingType.PointOfInterest, SCThingType.Room],
sortTypes: ['distance'],
},
{
fieldName: 'inPlace.geo',
onlyOnTypes: [
SCThingType.DateSeries,
SCThingType.Dish,
SCThingType.Floor,
SCThingType.Organization,
SCThingType.PointOfInterest,
SCThingType.Room,
SCThingType.Ticket,
],
sortTypes: ['distance'],
},
{
fieldName: 'offers',
onlyOnTypes: [SCThingType.Dish],
sortTypes: ['price'],
},
],
};

View File

@@ -0,0 +1,26 @@
// @ts-check
import app from './app/index.js';
import {backend, internal} from './backend/index.js';
/**
* This is the default configuration for app and backend
*
* University-specific files can be created with the following naming scheme: default-<university license plate>.ts
*
* To select your university-specific configuration which is merged from this default file and your university-specific
* file, you have to supply the `NODE_APP_INSTANCE` environment variable with your license plate
*
* To get more information about the meaning of specific fields, please have a look at `@openstapps/core` or use your
* IDE to read the TSDoc documentation.
*
* @type {import('@openstapps/core').SCConfigFile}
*/
const config = {
app,
auth: {},
backend,
internal,
uid: 'f-u',
};
export default config;

View File

@@ -0,0 +1,33 @@
// @ts-check
/**
* This is the default configuration for elasticsearch (a database)
*
* To select your university-specific configuration which is merged from this default file and your university-specific
* file, you have to supply the `NODE_APP_INSTANCE` environment variable with your license plate
*
* To select a different database, you have to supply the `NODE_CONFIG_ENV` environment variable with a database name
* that is implemented in the backend
*
* To get more information about the meaning of specific fields, please use your IDE to read the TSDoc documentation.
*
* @type {import('../../src/storage/elasticsearch/types/elasticsearch.js')}
*/
const config = {
internal: {
database: {
name: 'elasticsearch',
version: '5.6',
query: {
minMatch: '75%',
queryType: 'dis_max',
matchBoosting: 1.3,
fuzziness: 'AUTO',
cutoffFrequency: 0,
tieBreaker: 0,
},
},
},
};
export default config;

View File

@@ -0,0 +1,29 @@
// @ts-check
import {readFile} from 'fs/promises';
import {SCAboutPageContentType} from '@openstapps/core';
/**
* Usage:
*
* ```js
* await markdown('./page.md', import.meta.url)
* ```
*
* @param {string} path relative path to the file, omitting the language marker
* @param {string | URL} base base path, usually import.meta.url
* @returns {Promise<import('@openstapps/core').SCAboutPageMarkdown>}
*/
export async function markdown(path, base) {
const de = await readFile(new URL(path.replace(/\.md$/, '.de.md'), base), 'utf8');
const en = await readFile(new URL(path.replace(/\.md$/, '.en.md'), base), 'utf8');
return {
value: de,
translations: {
en: {
value: en,
},
},
type: SCAboutPageContentType.MARKDOWN,
};
}

View File

@@ -0,0 +1,34 @@
// @ts-check
/**
* Generates a range of numbers that represent consecutive calendar months
*
* @param {number} startMonth The month to start with (inclusive)
* @param {number} endMonth The month to end with (inclusive)
* @returns {number[]}
*/
export function yearSlice(startMonth, endMonth) {
let months = [...Array.from({length: 13}).keys()].slice(1);
months = [...months, ...months];
if (!months.includes(startMonth) || !months.includes(endMonth)) {
throw new Error(`Given months not part of a year! Check ${startMonth} or ${endMonth}!`);
}
const startIndex = months.indexOf(startMonth);
const endIndex =
months.indexOf(endMonth) <= startIndex ? months.lastIndexOf(endMonth) : months.indexOf(endMonth);
return months.slice(startIndex, endIndex + 1);
}
export const sommerRange = yearSlice(3, 8);
export const winterRange = yearSlice(9, 2);
export const month = new Date().getMonth();
export const year = new Date().getFullYear();
export const winterYearOffset = month < winterRange[0] ? -1 : 0;
export const sommerYear = year + (month <= winterRange[winterRange.length] ? -1 : 0);
export const winterYear = `${year + winterYearOffset}/${(year + 1 + winterYearOffset).toString().slice(-2)}`;
export const wsAcronymShort = `WS ${winterYear}`;
export const ssAcronymShort = `SS ${sommerYear}`;
export const wsAcronymLong = `WiSe ${winterYear}`;
export const ssAcronymLong = `SoSe ${sommerYear}`;

View File

@@ -1,21 +0,0 @@
// tslint:disable:no-default-export
// tslint:disable:no-magic-numbers
import {RecursivePartial} from '@openstapps/logger/lib/common';
import {ElasticsearchConfigFile} from '../src/storage/elasticsearch/types/elasticsearch.js';
/**
* This is the database configuration for the technical university of berlin
*/
const config: RecursivePartial<ElasticsearchConfigFile> = {
internal: {
database: {
name: 'elasticsearch',
query: {
minMatch: '60%',
queryType: 'query_string',
},
},
},
};
export default config;

View File

@@ -1,35 +0,0 @@
// tslint:disable:no-default-export
// tslint:disable:no-magic-numbers
import {ElasticsearchConfigFile} from '../src/storage/elasticsearch/types/elasticsearch-config.js';
/**
* This is the default configuration for elasticsearch (a database)
*
* University specific files can be created with following naming scheme: elasticsearch-<university license plate>.ts
*
* To select your university specific configuration which is merged from this default file and your university specific
* file, you have to supply the `NODE_APP_INSTANCE` environment variable with your license plate
*
* To select a different database you have to supply the `NODE_CONFIG_ENV` environment variable with a database name
* that is implemented in the backend
*
* To get more information about the meaning of specific fields please use your IDE to read the TSDoc documentation.
*/
const config: ElasticsearchConfigFile = {
internal: {
database: {
name: 'elasticsearch',
version: '8.4',
query: {
minMatch: '75%',
queryType: 'dis_max',
matchBoosting: 1.3,
fuzziness: 'AUTO',
cutoffFrequency: 0,
tieBreaker: 0,
},
},
},
};
export default config;

View File

@@ -0,0 +1,7 @@
Open StApps bietet Studierenden aller beteiligten Hochschulen eine qualitativ
hochwertige App für den Studienalltag. Open StApps-Verbundpartner integrieren
generalisierbare Studierendenprozesse so in App-Module, dass diese auch
von anderen Hochschulen verwendet werden können. Die in der Open StApps App
verwendeten Daten einer Datenquelle sind in einem generalisierten Datenmodell
so aufbereitet, dass ein Austausch oder Abschaltung der Datenquelle problemlos möglich
ist und die Open StApps App problemlos weiterhin funktionsfähig bleibt.

View File

@@ -0,0 +1,6 @@
Open StApps provides students from all participating universities with a
high-quality app for everyday study. Open StApps partners integrate
generalizable student processes into app modules in such a way that they can be
used by other universities. The data of a data source used in the Open StApps app
is prepared in a generalized data model in a way that the data source can be easily
exchanged or switched off while the app continues to function without any problems.

View File

@@ -0,0 +1,126 @@
// @ts-check
import {SCAboutPageContentType} from '@openstapps/core';
import {markdown} from '../../default/tools/markdown.js';
/** @type {import('@openstapps/core').SCAboutPage} */
export const about = {
title: 'Über Open StApps',
content: [
{
title: 'Verbundprojekt mehrerer Hochschulen für eine generische Studierenden-App',
content: await markdown('./about.md', import.meta.url),
translations: {
en: {
title: 'Collaborative project of multiple universities for a single generic study app',
},
},
type: SCAboutPageContentType.SECTION,
},
{
title: 'Goethe-Uni Kontakt',
content: {
rows: [
[
{
value: 'Adresse',
translations: {
en: {
value: 'Address',
},
},
type: SCAboutPageContentType.MARKDOWN,
},
{
// language=Markdown
value:
'Goethe Universität<br>' +
'Hochschulrechenzentrum (HRZ)<br>' +
'Norbert-Wollheim-Platz 1<br>' +
'60629 Frankfurt',
translations: {},
type: SCAboutPageContentType.MARKDOWN,
},
],
[
{
value: 'Kontaktinformation',
translations: {
en: {
value: 'Contact information',
},
},
type: SCAboutPageContentType.MARKDOWN,
},
{
// language=Markdown
value:
'[app@rz.uni-frankfurt.de](mailto:app@rz.uni-frankfurt.de)<br>' +
'[+49 69 798 32936](tel:+496979832936)<br>' +
'[https://app.rz.uni-frankfurt.de](https://app.rz.uni-frankfurt.de)',
translations: {},
type: SCAboutPageContentType.MARKDOWN,
},
],
],
type: SCAboutPageContentType.TABLE,
},
translations: {
en: {
title: 'Goethe-Uni Contact',
},
},
type: SCAboutPageContentType.SECTION,
},
{
icon: 'newspaper',
title: 'Neue Funktionen / Gelöste Probleme',
link: 'changelog',
translations: {
en: {
title: 'New features / Resolved issues',
},
},
type: SCAboutPageContentType.ROUTER_LINK,
},
{
icon: 'description',
title: 'Impressum',
link: 'imprint',
translations: {
en: {
title: 'Imprint',
},
},
type: SCAboutPageContentType.ROUTER_LINK,
},
{
icon: 'policy',
title: 'Datenschutz',
link: 'privacy',
translations: {
en: {
title: 'Privacy policy',
},
},
type: SCAboutPageContentType.ROUTER_LINK,
},
{
icon: 'copyright',
title: 'Bibliotheken und Lizenzen',
link: 'licenses',
translations: {
en: {
title: 'Libraries and licenses',
},
},
type: SCAboutPageContentType.ROUTER_LINK,
},
],
translations: {
en: {
title: 'About Open StApps',
},
},
};
export default about;

View File

@@ -0,0 +1,27 @@
// @ts-check
import {SCAboutPageContentType} from '@openstapps/core';
/** @type {import('@openstapps/core').SCAboutPage} */
export const imprint = {
title: 'Impressum',
content: [
{
// language=Markdown
value: `[Impressum der Johann Wolfgang Goethe-Universität Frankfurt am Main](https://www.uni-frankfurt.de/impressum)`,
translations: {
en: {
// language=Markdown
value: `[Imprint of the Goethe University Frankfurt](https://www.uni-frankfurt.de/impressum)`,
},
},
type: SCAboutPageContentType.MARKDOWN,
},
],
translations: {
en: {
title: 'Imprint',
},
},
};
export default imprint;

View File

@@ -0,0 +1,13 @@
// @ts-check
import about from './about.js';
import imprint from './imprint.js';
import privacy from './privacy.js';
/** @type {import('@openstapps/core').SCMap<import('@openstapps/core').SCAboutPage>} */
const aboutPages = {
'about': about,
'about/imprint': imprint,
'about/privacy': privacy,
};
export default aboutPages;

View File

@@ -0,0 +1,166 @@
# Datenschutzerklärung
## Kontaktdaten des Verantwortlichen
Verantwortlich im Sinne der Datenschutz-Grundverordnung und weiterer Vorschriften zum Datenschutz ist die:
Johann Wolfgang Goethe-Universität Frankfurt am Main vertreten durch ihren Präsidenten<br />
Theodor-W.-Adorno-Platz 1<br />
60323 Frankfurt am Main
Postanschrift:<br />
Goethe-Universität Frankfurt am Main<br />
60629 Frankfurt
Website: http://www.uni-frankfurt.de
## Kontaktdaten der Datenschutzbeauftragten an der Goethe-Universität
Sie erreichen die behördlichen Datenschutzbeauftragten der Johann Wolfgang Goethe-Universität Frankfurt am Main unter:<br />
Mail: <dsb@uni-frankfurt.de><br />
Website: http://www.uni-frankfurt.de/47859992/datenschutzbeauftragte
## Informationen zur Verarbeitung personenbezogener Daten
### <u>1. Umfang der Verarbeitung personenbezogener Daten</u>
Personenbezogene Daten sind gemäß Artikel 4 DSGVO alle Informationen, die sich auf eine identifizierte oder identifizierbare natürliche Person beziehen.
Wir verarbeiten personenbezogene Daten von Ihnen als Nutzer:innen der Goethe-Uni-App, soweit dies zur Bereitstellung einer **funktionsfähigen Applikation** technisch erforderlich ist.
Weiterhin kann eine Datenverarbeitung auf Ihrer freiwilligen Einwilligung basieren, wenn Sie **spezifische Funktionen** nutzen möchten.
Wir unterscheiden daher nachfolgend zwischen
- Zugriffsdaten bei der Nutzung der App: Inhalt der Anfragen, IP-Adressen, Datum/Uhrzeit der Anfrage, Angefragte URL, Fehlermeldungen, Browser-Kennung, HTTP-Header
- Standortbestimmung und Navigation: freiwillige Standortangaben
- Nutzer:inneneinstellungen: freiwillige Angabe von a) Sprachpräferenzen (derzeit: deutsch/englisch), b) Status (z. B. Gast/Student) oder c) spezifischen Suchanfragen und Suchergebnissen (Notifications)
- Kalenderfunktion: freiwillige Nutzung der Kalenderfunktion (optional mit freiwilliger Nutzung einer Synchronisationsfunktion: Opt-in) oder der integrierten Stundenplanfunktion, hierbei werden folgende Daten auf dem Endgerät verarbeitet und gespeichert: Termine und Veranstaltungen
- Feedbackfunktion und Kontaktaufnahme: freiwillige Nutzung mit der Angabe von Kontaktdaten und ggf. freiwilliger Übermittlung von Protokolldaten
- Campus Dienste: freiwillige Nutzung mit Verarbeitung von Notenansicht, Matrikelnummer, E-Mailadresse, Name
- Funktionen der Bibliothek: freiwillige Nutzung mit Verarbeitung von Bibliothekskontodaten, wie z.B. Ausweisnummer mit Name, E-Mailadresse, postalischer Adresse, Nutzungsberechtigung, Bestelldaten, Gebühren, Vormerkung, Ausleihdaten. Die vollständigen Angaben zur Verarbeitung finden Sie in der Datenschutzerklärung der Bibliothek:<br />
https://www.ub.uni-frankfurt.de/benutzung/datenschutz.html
Die App verlinkt an einigen Stellen auf die Website der Goethe-Universität sowie auf andere, externe Websites, die in einem In-App-Browser dargestellt werden. Wir bitten Sie bei Aufruf dieser Websites, die dort geltenden gesonderte Datenschutzhinweise und Erklärungen zu beachten.
### <u>2. Zweck(e) der Datenverarbeitung</u>
**Zugriff auf Standortdaten**
Für die Navigation benötigt die Goethe-Uni-App Zugriff auf den Standort des verwendeten Endgerätes (Location Based Services). Bei einer Anfrage erhebt die App den aktuellen Standort über GPS, Funkzellendaten und WLAN-Datenbanken, um Ihnen als Nutzer:in Informationen zu Ihrer unmittelbaren Umgebung geben zu können. Der Zugriff auf die Standortdaten erfolgt nur, wenn Sie den Zugriff auf die Standortdaten erlauben. Daten zu Ihrem Standort werden ausschließlich für die Bearbeitung von standortbezogenen Anfragen genutzt und um Ihren Standort auf der Karte anzuzeigen.
**Zugriff auf Zugriffsdaten**
Die Speicherung und Verarbeitung von Protokolldateien erfolgt, um die Funktionsfähigkeit der Goethe Uni-App für Sie sicherzustellen. Zudem benötigen wir die die Daten aus Gründen der Sicherheit unserer informationstechnischen Systeme. Eine anderweitige Auswertung oder Weitergabe findet in diesem Zusammenhang nicht statt.
**Zugriff auf Spracheinstellungen**
Der Zugriff auf die Spracheinstellung erfolgt um Ihnen die Oberfläche der App in der von Ihnen gewünschten Sprache anzuzeigen.
**Zugriff auf die Einstellung der Statusgruppe**
Der Zugriff auf die Einstellung der Statusgruppe erfolgt um Ihnen in der App die für Ihre Gruppe zutreffenden Informationen anzuzeigen, z.B. Mensapreise
**Zugriff auf personenbezogene Daten bei der Nutzung der Feedbackfunktion**
Die Verarbeitung der personenbezogenen Daten aus der Feedbackfunktion dient uns zur Kontaktaufnahme und Fehlerbehebung.
**Zugriff auf personenbezogene Daten bei der Kalendersynchronisation**
Der Zugriff auf die Termindaten erfolgt um sie bei aktivierter Kalenderfunktion in den Gerätekalender zu schreiben.
**Zugriff auf Daten der Campus Dienste**
Der Zugriff auf das Campus Management Systems erfolgt ausschließlich um persönliche Daten der Studierendenverwaltung in der App anzuzeigen (z.B. Prüfungsnoten).
**Zugriff auf bibliotheksspezifische personenbezogene Daten**
Der Zugriff auf die Daten (z.B. Ausweisnummer, Name, Postanschrift) erfolgt zur Durchführung von Bestell- und Ausleihverfahren von Büchern und sonstigen Materialien der Universitätsbibliothek. Die vollständigen Angaben zu den Verarbeitungszwecken finden Sie in der Datenschutzerklärung der Bibliothek: https://www.ub.uni-frankfurt.de/benutzung/datenschutz.html
### <u>3. Rechtsgrundlage(n) für die Datenverarbeitung</u>
Die Nutzung der Nutzungs-/Zugriffsdaten („Protokolldateien") basiert auf Artikel 6 Absatz 1 lit. f) DSGVO.
Für alle spezifischen Funktionen, bei denen die Datenverarbeitung auf Ihrer freiwilligen Einwilligung als Nutzer:innen basiert, werden explizit Einwilligungen bzw. aktive Zustimmungsakte („Opt-In") eingeholt. Die Bereitstellung personenbezogener Daten zu Ihrer Person gegenüber der Goethe-Universität erfolgen dabei auf freiwilliger Basis. Die Rechtsgrundlage ist in diesen Fällen jeweils Artikel 6 Absatz 1 lit. a) DSGVO. Sie können Ihre jeweilige Einwilligung jederzeit einzeln widerrufen bzw. Ihre Einstellungen ändern.
### <u>4. Datenlöschung und Speicherdauer</u>
Die in den Protokolldateien der App erfassten Daten werden sieben Tage nach dem Ende des Zugriffs automatisch gelöscht oder anonymisiert.
Die Löschfristen bzw. Speicherdauer der in den Bibliotheksystemen erfassten Daten finden Sie in der Datenschutzerklärung der Bibliothek: https://www.ub.uni-frankfurt.de/benutzung/datenschutz.html
Für alle anderen Funktionen und Dienste gilt: Die Löschung erfolgt hier je nach Vorgabe des genutzten Dienstes. Die personenbezogenen Daten der betroffenen Person werden gelöscht oder gesperrt, sobald der Zweck der Speicherung entfällt.
### <u>5. Datenweitergabe/Datenübermittlung</u>
Ihre personenbezogenen Daten werden von uns nicht an Dritte weitergegeben.
Von Betreiberseite wird durch technische und organisatorische Maßnahmen sichergestellt, dass Dritte keinen Zugriff auf die verarbeiteten Daten, wie z. B. Nutzungsdaten, erhalten. Ein Auftragsverarbeitungsverhältnis nach Art. 28 DSGVO besteht nicht, da ausschließlich eigene Server verwendet werden.
### <u>6. Automatisierte Entscheidungsfindung</u>
Eine automatisierte Entscheidungsfindung einschließlich Profiling erfolgt nicht.
## Rechte der betroffenen Person
Werden personenbezogene Daten von Ihnen verarbeitet, sind Sie Betroffener im Sinne der DSGVO. Die Geltendmachung Ihrer Betroffenenrechte ist kostenfrei. Sie können sich dafür selbstverständlich an uns wenden. Es stehen Ihnen folgende Betroffenenrechte gegenüber der Goethe-Universität zu:
### <u>1. Auskunftsrecht</u>
Sie können von uns als verantwortlicher Stelle eine Bestätigung darüber verlangen, ob und welche Ihrer personenbezogenen Daten von uns verarbeitet werden. Sie haben das Recht, von uns Kopien Ihrer personenbezogenen Daten zu verlangen. Bitte beachten Sie die Ausnahmen, die sich durch spezifische Vorschriften ergeben können.
### <u>2. Recht auf Berichtigung</u>
Sie haben das Recht von uns die Berichtigung und/oder Vervollständigung zu verlangen, sofern die verarbeiteten personenbezogenen Daten, die Sie betreffen, nicht (mehr) richtig oder nicht (mehr) vollständig sind.
### <u>3. Recht auf Einschränkung der Verarbeitung</u>
Unter bestimmten Voraussetzungen können Sie die Einschränkung der Verarbeitung der Sie betreffenden personenbezogenen Daten verlangen, d. h. dass dann Ihre personenbezogenen Daten zwar nicht gelöscht, aber gekennzeichnet werden, so dass eine weitere Verarbeitung eingeschränkt ist.
### <u>4. Recht auf Löschung</u>
Sie können unter bestimmten Voraussetzungen von uns verlangen, dass die Sie betreffenden personenbezogenen Daten unverzüglich gelöscht werden. Dies ist insbesondere der Fall, wenn die personenbezogenen Daten zu dem Zweck, zu dem sie ursprünglich erhoben oder verarbeitet wurden, nicht mehr erforderlich sind.
### <u>5. Recht auf Unterrichtung</u>
Haben Sie das Recht auf Berichtigung, Löschung oder Einschränkung der Verarbeitung uns gegenüber geltend gemacht, sind wir verpflichtet, allen Empfänger/innen, denen die Sie betreffenden personenbezogenen Daten offengelegt wurden, diese Berichtigung oder Löschung der Daten oder Einschränkung der Verarbeitung mitzuteilen, es sei denn, dies erweist sich als unmöglich oder ist mit einem unverhältnismäßigen Aufwand verbunden. Sie sind berechtigt, über diese Empfänger unterrichtet zu werden.
### <u>6. Recht auf Datenübertragbarkeit</u>
Sie haben unter bestimmten Voraussetzungen das Recht von uns zu verlangen, dass Ihre personenbezogenen Daten von uns direkt an einen anderen Verantwortlichen oder an eine andere Organisation übermittelt werden. Alternativ haben Sie unter bestimmten Voraussetzungen das Recht von uns zu verlangen, dass wir Ihnen selbst die Daten in einem maschinenlesbaren Format bereitstellen.
### <u>7. Widerspruchsrecht</u>
Wenn wir Ihre personenbezogenen Daten verarbeiten, weil die Verarbeitung im öffentlichen Interesse, Teil unserer öffentlichen Aufgaben ist bzw. wenn wir Ihre Daten auf Basis eines berechtigten Interesses verarbeiten, haben Sie aus Gründen, die sich aus Ihrer besonderen Situation ergeben, das Recht, jederzeit der Verarbeitung der Sie betreffenden Daten zu widersprechen.
### <u>8. Recht auf Widerruf der datenschutzrechtlichen Einwilligungserklärung</u>
Wenn wir Ihre personenbezogenen Daten verarbeiten, weil Sie uns Ihre Einwilligung gegeben haben, haben Sie jederzeit das Recht, Ihre Einwilligungserklärung zu widerrufen.
### <u>9. Recht auf Beschwerde bei einer Aufsichtsbehörde</u>
Sie haben ferner das Recht auf Beschwerde bei einer Aufsichtsbehörde. Die zuständige Aufsichtsbehörde wird Ihre Beschwerde prüfen.
## **Kontaktdaten der Aufsichtsbehörde im Bereich Datenschutz**
Wenn Sie der Ansicht sind, dass eine Verarbeitung der Sie betreffenden personenbezogenen Daten gegen Datenschutzvorschriften verstößt, wenn Sie eine allgemeine Anfrage haben oder wenn Sie sich bei einer zuständigen Fachaufsichtsbehörde beschweren wollen, können Sie sich an den Hessischen Beauftragten für Datenschutz und Informationsfreiheit (HBDI) wenden.
**Der Hessische Beauftragte für Datenschutz und Informationsfreiheit ist auf unterschiedlichen Wegen erreichbar:**
<u>**Der Hessische Beauftragte für Datenschutz und Informationsfreiheit**</u><br />
Postfach 3163<br />
65021 Wiesbaden
Telefon: +49 611 1408 -- 0
Für allgemeine Anfragen können Sie ein Kontaktformular nutzen:<br />
<https://datenschutz.hessen.de/kontakt><br />
<br />
Für Beschwerden steht Ihnen zudem ein Beschwerdeformular zur Verfügung:<br />
<https://datenschutz.hessen.de/service/beschwerde-uebermitteln>

View File

@@ -0,0 +1,166 @@
# Privacy policy
## Contact details of the person responsible
Responsible in the sense of the General Data Protection Regulation and further regulations on data protection is the:
Johann Wolfgang Goethe-Universität Frankfurt am Main represented by its president<br />
Theodor-W.-Adorno-Platz 1<br />
60323 Frankfurt am Main
Postanschrift:<br />
Goethe-Universität Frankfurt am Main<br />
60629 Frankfurt
Website: http://www.uni-frankfurt.de
## Contact details of the data protection officer at Goethe University
You can reach the data protection officers at Johann Wolfgang Goethe University Frankfurt am Main at:<br />
Mail: <dsb@uni-frankfurt.de><br />
Website: http://www.uni-frankfurt.de/47859992/datenschutzbeauftragte
## Information on the processing of personal data
### <u>1. Scope of the processing of personal data</u>.
According to Article 4 DSGVO, personal data is any information relating to an identified or identifiable natural person.
We process personal data of you as a user inside of the Goethe University App to the extent that this is technically necessary for the provision of a **functional application**.
Furthermore, data processing may be based on your voluntary consent if you wish to use **specific functions**.
We therefore distinguish below between
- Access data when using the app: content of requests, IP addresses, date/time of request, requested URL, error codes, browser identifier, HTTP header.
- Location and navigation: voluntary location information
- User settings: voluntary specification of a) language preferences (currently: German/English), b) status (e.g. guest/student) or c) specific search queries and search results (notifications)
- Calendar function: voluntary use of the calendar function (optional with voluntary use of a sync function: opt-in) or the integrated timetable function. The following data is processed and stored on the users device: appointments and events
- Feedback function and contacting: voluntary use with the provision of contact data and, if applicable, voluntary transmission of log data
- Campus services: voluntary use with processing of grade view, matriculation number, email address, name
- Services of the library: voluntary use with processing of library account data, such as ID number with name, e-mail address, postal address, right of use, order data, fees, reservation, loan data. Full details of processing can be found in the library's privacy policy:<br />
https://www.ub.uni-frankfurt.de/benutzung/datenschutz.html
In some places, the app links to the Goethe University website and to other external websites that are displayed in an in-app browser. When you visit these websites, we ask you to pay attention to the separate data protection notices and declarations that apply there.
### <u>2. Purpose(s) of data processing</u>
**Access to location data**.
For navigation, the Goethe University app requires access to the location of the end device used (location-based services). When a request is made, the app collects the current location via GPS, radio cell data and WLAN databases in order to be able to give you as a user:in information about your immediate surroundings. The location data is only accessed if you allow access to the location data. Data about your location is only used to process location-related requests and to display your location on the map.
**Access to access data**.
Log files are stored and processed to ensure that the Goethe Uni app functions properly for you. In addition, we need the data for reasons of security of our information technology systems. No other evaluation or disclosure takes place in this context.
**Access to language settings**.
Access to the language setting is made in order to display the interface of the app in the language of your choice.
**Access to the status group setting**.
Access to the status group setting is provided to show you the information in the app that applies to your group, e.g. canteen prices.
**Access to personal data when using the feedback function**.
We use the processing of personal data from the feedback function to contact you and troubleshoot problems.
**Access to personal data when synchronizing calendars**.
Appointment data is accessed in order to write it to the device calendar when the calendar function is enabled.
**Access to Campus Services data**.
Access to the Campus Management System is solely for the purpose of displaying personal student management data in the app (e.g., exam grades).
**Access to library-specific personal data**.
Access to data (e.g., ID number, name, mailing address) is for the purpose of carrying out ordering and borrowing procedures for books and other materials from the University Library. Full details of the purposes of processing can be found in the Library's Privacy Policy: https://www.ub.uni-frankfurt.de/benutzung/datenschutz.html
### <u>3. Rechtsgrundlage(n) für die Datenverarbeitung</u>
The use of usage/access data ("log files") is based on Article 6(1)(f) DSGVO.
For all specific functions where data processing is based on your voluntary consent as a user:in, explicit consent or active acts of consent ("opt-in") are obtained. The provision of personal data about you to Goethe University is done on a voluntary basis. The legal basis in each of these cases is Article 6 (1) a) DSGVO. You can individually revoke your respective consent or change your settings at any time.
### <u>4. Data deletion and storage duration</u>
The data collected in the log files of the app are automatically deleted or anonymized seven days after the end of the access.
The deletion periods or storage duration of the data collected in the library systems can be found in the library's privacy policy: https://www.ub.uni-frankfurt.de/benutzung/datenschutz.html
For all other functions and services, the following applies: deletion takes place here depending on the specifications of the service used. The personal data of the data subject will be deleted or blocked as soon as the purpose of the storage no longer applies.
### <u>5. Data disclosure/data transfer</u>
We will not pass on your personal data to third parties.
On the part of the operator, technical and organizational measures are taken to ensure that third parties do not gain access to the processed data, such as usage data. An order processing relationship according to Art. 28 DSGVO does not exist, as only our own servers are used.
### <u>6. Automated decision-making</u>
Automated decision-making, including profiling, does not take place.
## Rights of the data subject
If personal data is processed by you, you are a data subject within the meaning of the GDPR. The assertion of your data subject rights is free of charge. You can, of course, contact us for this purpose. You are entitled to the following data subject rights vis-à-vis Goethe University:
### <u>1. Right of access</u>
You can request confirmation from us as the controller as to whether and which of your personal data is being processed by us. You have the right to request copies of your personal data from us. Please note the exceptions that may arise due to specific regulations.
### <u>2. Right of rectification</u>
You have the right to request us to rectify and/or complete, if the processed personal data concerning you is not (anymore) accurate or not (anymore) complete.
### <u>3. Right to restriction of processing</u>
Under certain conditions, you can request the restriction of the processing of personal data concerning you, i.e. that your personal data is then not deleted, but marked so that further processing is restricted.
### <u>4. Right to erasure</u>
Under certain conditions, you can demand that we delete the personal data concerning you without delay. This is particularly the case if the personal data is no longer necessary for the purpose for which it was originally collected or processed.
### <u>5. Right to information</u>
If you have asserted the right to rectification, erasure or restriction of processing against us, we are obliged to inform all recipients to whom the personal data concerning you have been disclosed of this rectification or erasure of the data or restriction of processing, unless this proves impossible or involves a disproportionate effort. You are entitled to be informed about these recipients.
### <u>6. Right to data portability</u>
Under certain conditions, you have the right to request that we transfer your personal data directly to another controller or organization. Alternatively, under certain conditions, you have the right to request that we ourselves provide you with the data in a machine-readable format.
### <u>7. Right to object</u>
If we process your personal data because the processing is in the public interest, part of our public duties, or if we process your data on the basis of a legitimate interest, you have the right to object at any time to the processing of data relating to you for reasons arising from your particular situation.
### <u>8. Right to revoke the declaration of consent under data protection law</u>
If we process your personal data because you have given us your consent, you have the right to revoke your declaration of consent at any time.
### <u>9. Right to lodge a complaint with a supervisory authority</u>
You also have the right to lodge a complaint with a supervisory authority. The competent supervisory authority will examine your complaint.
## **Contact details of the supervisory authority in the area of data protection**
If you believe that the processing of your personal data violates data protection regulations, if you have a general inquiry or if you want to complain to a competent supervisory authority, you can contact the Hessian Commissioner for Data Protection and Freedom of Information (HBDI).
**The Hessian Commissioner for Data Protection and Freedom of Information can be reached in different ways:**
<u>**The Hessian Commissioner for Data Protection and Freedom of Information**</u><br />
PO Box 3163<br />
65021 Wiesbaden
Telephone: +49 611 1408 -- 0
For general inquiries you can use a contact form:<br />
<https://datenschutz.hessen.de/kontakt><br />
<br />
A complaint form is also available for complaints:<br />
<https://datenschutz.hessen.de/service/beschwerde-uebermitteln>

View File

@@ -0,0 +1,15 @@
// @ts-check
import {markdown} from '../../default/tools/markdown.js';
/** @type {import('@openstapps/core').SCAboutPage} */
export const privacy = {
title: 'Datenschutz',
content: [await markdown('./privacy.md', import.meta.url)],
translations: {
en: {
title: 'Privacy Policy',
},
},
};
export default privacy;

View File

@@ -0,0 +1,82 @@
// @ts-check
import aboutPages from './about-pages/index.js';
import defaultApp from '../default/app/index.js';
import {backend as defaultBackend, internal as defaultInternal} from '../default/backend/index.js';
/**
* This is the default configuration for the Goethe university of Frankfurt
*
* @type {import('@openstapps/core').SCConfigFile}
*/
const config = {
auth: {
default: {
client: {
clientId: '1cac3f99-33fa-4234-8438-979f07e0cdab',
scopes: '',
url: 'https://cas.rz.uni-frankfurt.de/cas/oauth2.0',
},
endpoints: {
authorization: 'https://cas.rz.uni-frankfurt.de/cas/oauth2.0/authorize',
endSession: 'https://cas.rz.uni-frankfurt.de/cas/logout',
mapping: {
id: '$.id',
email: '$.attributes.mailPrimaryAddress',
familyName: '$.attributes.sn',
givenName: '$.attributes.givenName',
name: '$.attributes.givenName',
role: '$.attributes.eduPersonPrimaryAffiliation',
studentId: '$.attributes.employeeNumber',
},
token: 'https://cas.rz.uni-frankfurt.de/cas/oauth2.0/accessToken',
userinfo: 'https://cas.rz.uni-frankfurt.de/cas/oauth2.0/profile',
},
},
paia: {
client: {
clientId: '',
scopes: '',
url: 'https://hds.hebis.de/Shibboleth.sso/UBFFM?target=https://hds.hebis.de/ubffm/paia_login_stub.php',
},
endpoints: {
authorization:
'https://hds.hebis.de/Shibboleth.sso/UBFFM?target=https://hds.hebis.de/ubffm/paia_login_stub.php',
endSession: 'https://ubffm.hds.hebis.de/Shibboleth.sso/Logout',
mapping: {
id: '$.email',
name: '$.name',
role: '$.type',
},
token: 'https://hds.hebis.de/paia/auth/login',
userinfo: 'https://hds.hebis.de/paia/core',
},
},
},
app: {
...defaultApp,
features: {
extern: {
hisometry: {
authProvider: 'default',
url: 'https://his-self-service.rz.uni-frankfurt.de',
},
daia: {
url: 'https://daia.hebis.de/DAIA2/UB_Frankfurt',
},
hebisProxy: {
url: 'https://proxy.ub.uni-frankfurt.de/login?qurl=',
},
paia: {
authProvider: 'paia',
url: 'https://hds.hebis.de/paia/core',
},
},
},
aboutPages,
},
backend: defaultBackend,
internal: defaultInternal,
uid: 'f-u',
};
export default config;

View File

@@ -18,26 +18,25 @@
"main": "lib/app.js",
"bin": "app.js",
"scripts": {
"build": "tsup --dts",
"dev": "tsup --watch",
"build": "tsup",
"dev": "tsup --watch --onSuccess \"pnpm run start\"",
"format": "prettier . --ignore-path ../../.gitignore",
"format:fix": "prettier --write . --ignore-path ../../.gitignore",
"lint": "eslint --ext .ts src/",
"lint": "tsc --noEmit && eslint --ext .ts src/",
"lint:fix": "eslint --fix --ext .ts src/",
"start": "NODE_CONFIG_ENV=elasticsearch ALLOW_NO_TRANSPORT=true node ./lib/cli.js",
"start-debug": "STAPPS_LOG_LEVEL=31 NODE_CONFIG_ENV=elasticsearch ALLOW_NO_TRANSPORT=true node ./lib/cli.js --require ts-node/register",
"test": "pnpm run test-unit && pnpm run test-integration",
"test-integration": "docker-compose -f integration-test.yml pull && docker-compose -f integration-test.yml up --build --abort-on-container-exit --exit-code-from apicli",
"test-unit": "cross-env NODE_CONFIG_ENV=elasticsearch ALLOW_NO_TRANSPORT=true STAPPS_LOG_LEVEL=0 nyc mocha --require ts-node/register --exit 'test/**/*.spec.ts'"
"start": "cross-env NODE_CONFIG_ENV=elasticsearch ALLOW_NO_TRANSPORT=true node app.js",
"start:debug": "cross-env STAPPS_LOG_LEVEL=31 NODE_CONFIG_ENV=elasticsearch ALLOW_NO_TRANSPORT=true node app.js",
"test": "cross-env NODE_CONFIG_ENV=elasticsearch ALLOW_NO_TRANSPORT=true STAPPS_LOG_LEVEL=0 c8 mocha",
"test:integration": "docker-compose -f integration-test.yml pull && docker-compose -f integration-test.yml up --build --abort-on-container-exit --exit-code-from apicli",
"test:unit": "cross-env NODE_CONFIG_ENV=elasticsearch ALLOW_NO_TRANSPORT=true STAPPS_LOG_LEVEL=0 nyc mocha --require ts-node/register --exit 'test/**/*.spec.ts'"
},
"dependencies": {
"@elastic/elasticsearch": "8.4.0",
"@openstapps/core": "workspace:*",
"@openstapps/core-tools": "workspace:*",
"@openstapps/logger": "workspace:*",
"@redocly/cli": "1.0.0-beta.125",
"@types/node": "14.18.43",
"config": "3.3.9",
"body-parser": "1.20.2",
"cosmiconfig": "8.1.3",
"cors": "2.8.5",
"express": "4.18.2",
"express-prom-bundle": "6.6.0",
@@ -45,11 +44,11 @@
"got": "12.6.0",
"moment": "2.29.4",
"morgan": "1.10.0",
"nock": "13.3.1",
"nock": "13.3.0",
"node-cache": "5.1.2",
"node-cron": "3.0.2",
"nodemailer": "6.9.1",
"prom-client": "14.2.0",
"prom-client": "14.1.1",
"promise-queue": "2.2.5",
"ts-node": "10.9.1",
"uuid": "8.3.2"
@@ -61,6 +60,7 @@
"@openstapps/prettier-config": "workspace:*",
"@openstapps/tsconfig": "workspace:*",
"@testdeck/mocha": "0.3.3",
"@types/body-parser": "1.19.2",
"@types/chai": "4.3.4",
"@types/chai-as-promised": "7.1.5",
"@types/config": "3.3.0",
@@ -73,24 +73,20 @@
"@types/node-cron": "3.0.7",
"@types/nodemailer": "6.4.7",
"@types/promise-queue": "2.2.0",
"@types/sinon": "10.0.14",
"@types/sinon-express-mock": "1.3.9",
"@types/supertest": "2.0.12",
"@types/uuid": "8.3.4",
"chai": "4.3.7",
"chai-as-promised": "7.1.1",
"conventional-changelog-cli": "2.2.2",
"cross-env": "7.0.3",
"eslint": "8.39.0",
"eslint-config-prettier": "8.8.0",
"eslint-plugin-jsdoc": "39.9.1",
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-unicorn": "45.0.2",
"conventional-changelog-cli": "2.2.2",
"get-port": "5.1.1",
"mocha": "10.2.0",
"mocked-env": "1.3.5",
"nyc": "15.1.0",
"c8": "7.13.0",
"redoc-cli": "0.13.20",
"sinon": "14.0.2",
"sinon": "15.0.4",
"sinon-express-mock": "2.2.1",
"supertest": "6.3.3",
"tsup": "6.7.0",
@@ -99,11 +95,11 @@
},
"tsup": {
"entry": [
"src/app.ts",
"src/app.ts"
"src/cli.ts"
],
"sourcemap": true,
"clean": true,
"target": "es2022",
"format": "esm",
"outDir": "lib"
},
@@ -113,9 +109,6 @@
"@openstapps"
]
},
"nyc": {
"extends": "@openstapps/nyc-config"
},
"openstapps-configuration": {
"overrides": [
"test"

View File

@@ -20,12 +20,11 @@ import {
SCUnsupportedMediaTypeErrorResponse,
} from '@openstapps/core';
import {Logger} from '@openstapps/logger';
import config from 'config';
import cors from 'cors';
import {Express} from 'express';
import morgan from 'morgan';
import path from 'path';
import {configFile, DEFAULT_TIMEOUT, isTestEnvironment, mailer, plugins, validator} from './common.js';
import {DEFAULT_TIMEOUT, isTestEnvironment, mailer, plugins, validator} from './common.js';
import {getPrometheusMiddleware} from './middleware/prometheus.js';
import {MailQueue} from './notification/mail-queue.js';
import {bulkAddRouter} from './routes/bulk-add-route.js';
@@ -39,6 +38,7 @@ import {thingUpdateRouter} from './routes/thing-update-route.js';
import {virtualPluginRoute} from './routes/virtual-plugin-route.js';
import {BulkStorage} from './storage/bulk-storage.js';
import {DatabaseConstructor} from './storage/database.js';
import {backendConfig} from './config.js';
/**
* Configure the backend
@@ -93,9 +93,9 @@ export async function configureApp(app: Express, databases: {[name: string]: Dat
// allow cors preflight requests on every route
app.options('*', [cors(corsOptions)]);
// only accept json as content type for all requests
// only accept json as content-type for all requests
app.use((request, response, next) => {
// Only accept json as content type
// Only accept json as content-type
if (request.is('application/json') !== 'application/json') {
// return an error in the response
const error = new SCUnsupportedMediaTypeErrorResponse(isTestEnvironment);
@@ -111,7 +111,7 @@ export async function configureApp(app: Express, databases: {[name: string]: Dat
const chunkGatherer = (chunk: Buffer) => {
bodySize += chunk.byteLength;
// when adding each chunk size to the total size, check how large it now is.
if (bodySize > configFile.backend.maxRequestBodySize) {
if (bodySize > backendConfig.backend.maxRequestBodySize) {
request.off('data', chunkGatherer);
request.off('end', endCallback);
// return an error in the response
@@ -149,7 +149,7 @@ export async function configureApp(app: Express, databases: {[name: string]: Dat
);
// validate the config file
const configValidation = validator.validate(configFile, 'SCConfigFile');
const configValidation = validator.validate(backendConfig, 'SCConfigFile');
// validation failed
if (configValidation.errors.length > 0) {
@@ -159,14 +159,14 @@ export async function configureApp(app: Express, databases: {[name: string]: Dat
}
// check if a database name was given
if (!config.has('internal.database.name')) {
if (!backendConfig.internal.database?.name) {
throw new Error('You have to configure a database');
}
const database = new databases[config.get<string>('internal.database.name')](
configFile,
const database = new databases[backendConfig.internal.database.name](
backendConfig,
// mailQueue
typeof mailer !== 'undefined' && config.has('internal.monitoring') ? new MailQueue(mailer) : undefined,
typeof mailer !== 'undefined' && backendConfig.internal.monitoring ? new MailQueue(mailer) : undefined,
);
await database.init();
@@ -201,12 +201,12 @@ export async function configureApp(app: Express, databases: {[name: string]: Dat
// for plugins, as Express doesn't really want you to unregister routes (and doesn't offer any method to do so at all)
app.all('*', async (request, response, next) => {
// if the route exists then call virtual route on the plugin that registered that route
// if the route exists, call virtual route on the plugin that registered that route
if (plugins.has(request.originalUrl)) {
try {
response.json(await virtualPluginRoute(request, plugins.get(request.originalUrl)!));
} catch (error) {
// in case of error send an error response
// in case of an error: send an error response
response.status(error.statusCode);
response.json(error);
}

View File

@@ -13,9 +13,8 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {SCConfigFile, SCPluginMetaData} from '@openstapps/core';
import {SCPluginMetaData} from '@openstapps/core';
import {Validator} from '@openstapps/core-tools';
import config from 'config';
import {BackendTransport} from './notification/backend-transport.js';
/**
@@ -23,11 +22,6 @@ import {BackendTransport} from './notification/backend-transport.js';
*/
export const mailer = BackendTransport.getTransportInstance();
/**
* Config file content
*/
export const configFile: SCConfigFile = config.util.toObject();
/**
* A validator instance to check if something is a valid JSON object (e.g. a request or a thing)
*/
@@ -43,11 +37,6 @@ export const isTestEnvironment = process.env.NODE_ENV !== 'production';
*/
export const plugins = new Map<string, SCPluginMetaData>();
/**
* The version of the installed core
*/
export const coreVersion: string = configFile.backend.SCVersion;
/**
* The default timeout in milliseconds
*/

View File

@@ -0,0 +1,54 @@
import {cosmiconfig, PublicExplorer} from 'cosmiconfig';
import {SCConfigFile} from '@openstapps/core';
import path from 'path';
import deepmerge from 'deepmerge';
const fallbackNamespace = 'default';
const configPath = 'config';
/**
* Creates a config loader
*
* @param moduleName the name of the config file (module)
*/
function configLoader(moduleName: string): PublicExplorer {
return cosmiconfig(moduleName, {
searchPlaces: ['js', 'json', 'yml', 'yaml'].map(it => `${moduleName}rc.${it}`),
loaders: {
'.js': filepath => import(`file://${filepath}`).then(it => it.default),
},
});
}
/**
* Find and load a config file
*/
async function findConfig<T>(moduleName: string, namespace = fallbackNamespace): Promise<T> {
return configLoader(moduleName)
.search(path.posix.join('.', configPath, namespace))
.then(it => it!.config as T)
.catch(() =>
configLoader(moduleName)
.search(path.posix.join('.', configPath, fallbackNamespace))
.then(it => it!.config),
);
}
/**
* Loads a config file
*/
async function loadConfig<T>(moduleName: string): Promise<T> {
const namespace = process.env.NODE_APP_INSTANCE;
const database = process.env.NODE_CONFIG_ENV;
const config = await findConfig<T>(moduleName, namespace);
if (database) {
const databaseConfig = await findConfig<T>(database, namespace);
return deepmerge(config, databaseConfig);
}
return config;
}
export const backendConfig = await loadConfig<SCConfigFile>('backend');
export const prometheusConfig = await loadConfig<unknown>('prometheus');

14
backend/backend/src/environment.d.ts vendored Normal file
View File

@@ -0,0 +1,14 @@
declare global {
namespace NodeJS {
interface ProcessEnv {
NODE_APP_INSTANCE: 'default' | 'b-tu' | 'f-u' | 'fb-fh' | 'ks-ug' | string | undefined;
NODE_CONFIG_ENV: 'elasticsearch' | string | undefined;
STAPPS_LOG_LEVEL: `${number}`;
ALLOW_NO_TRANSPORT: `${boolean}`;
PORT: `${number}`;
ES_DEBUG: `${boolean}`;
ES_ADDR: string;
PROMETHEUS_MIDDLEWARE: `${boolean}`;
}
}
}

View File

@@ -14,8 +14,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {SCIndexResponse, SCIndexRoute} from '@openstapps/core';
import {configFile} from '../common.js';
import {createRoute} from './route.js';
import {backendConfig} from '../config.js';
/**
* Contains information for using the index route
@@ -29,9 +29,9 @@ export const indexRouter = createRoute<unknown, SCIndexResponse>(
indexRouteModel,
async (): Promise<SCIndexResponse> => {
return {
app: configFile.app,
auth: configFile.auth,
backend: configFile.backend,
app: backendConfig.app,
auth: backendConfig.auth,
backend: backendConfig.backend,
};
},
);

View File

@@ -20,9 +20,10 @@ import {
SCSearchResponse,
SCTooManyRequestsErrorResponse,
} from '@openstapps/core';
import {configFile, isTestEnvironment} from '../common.js';
import {isTestEnvironment} from '../common.js';
import {BulkStorage} from '../storage/bulk-storage.js';
import {createRoute} from './route.js';
import {backendConfig} from '../config.js';
/**
* Contains information for using the multi search route
*/
@@ -38,7 +39,7 @@ export const multiSearchRouter = createRoute<
const bulkMemory: BulkStorage = app.get('bulk');
const queryNames = Object.keys(request);
if (queryNames.length > configFile.backend.maxMultiSearchRouteQueries) {
if (queryNames.length > backendConfig.backend.maxMultiSearchRouteQueries) {
throw new SCTooManyRequestsErrorResponse(isTestEnvironment);
}

View File

@@ -23,8 +23,9 @@ import {
} from '@openstapps/core';
import {Logger} from '@openstapps/logger';
import {deepStrictEqual} from 'assert';
import {configFile, isTestEnvironment, plugins} from '../common.js';
import {isTestEnvironment, plugins} from '../common.js';
import {createRoute} from './route.js';
import {backendConfig} from '../config.js';
/**
* Contains information for using the route for registering routes
@@ -80,10 +81,10 @@ function addPlugin(plugin: SCPluginMetaData): SCPluginRegisterResponse {
// it's a new plugin so it can be added to the map of plugins
plugins.set(plugin.route, plugin);
// add plugin info to app config
if (typeof configFile.app.features.plugins === 'undefined') {
configFile.app.features.plugins = {};
if (typeof backendConfig.app.features.plugins === 'undefined') {
backendConfig.app.features.plugins = {};
}
configFile.app.features.plugins[plugin.name] = {urlPath: plugin.route};
backendConfig.app.features.plugins[plugin.name] = {urlPath: plugin.route};
Logger.log(
`Registered plugin (name: ${plugin.name}, address: ${plugin.address}) on the route "${plugin.route}".`,
);
@@ -102,7 +103,7 @@ function removePlugin(route: string): SCPluginRegisterResponse {
}
if (plugins.has(route)) {
const plugin = plugins.get(route)!;
delete configFile.app.features.plugins?.[plugin.name];
delete backendConfig.app.features.plugins?.[plugin.name];
}
// remove the plugin information using its route as a key
plugins.delete(route);

View File

@@ -17,7 +17,8 @@
import {SCInternalServerErrorResponse, SCPluginMetaData, SCValidationErrorResponse} from '@openstapps/core';
import {Request} from 'express';
import got from 'got';
import {configFile, isTestEnvironment, validator} from '../common.js';
import {isTestEnvironment, validator} from '../common.js';
import {backendConfig} from '../config.js';
/**
* Generic route function used to proxy actual requests to plugins
@@ -38,7 +39,7 @@ export async function virtualPluginRoute(request: Request, plugin: SCPluginMetaD
prefixUrl: plugin.address,
json: request.body,
timeout: {
response: configFile.backend.externalRequestTimeout,
response: backendConfig.backend.externalRequestTimeout,
},
responseType: 'json',
});

View File

@@ -23,6 +23,7 @@ import {
} from '@elastic/elasticsearch/lib/api/types';
import {SCConfigFile, SCSearchQuery, SCSearchResponse, SCThings, SCUuid} from '@openstapps/core';
import {Logger} from '@openstapps/logger';
import {IndicesUpdateAliasesParamsAction, SearchResponse} from 'elasticsearch';
import moment from 'moment';
import {MailQueue} from '../../notification/mail-queue';
import {Bulk} from '../bulk-storage';

View File

@@ -20,11 +20,12 @@ import {
SCUnsupportedMediaTypeErrorResponse,
} from '@openstapps/core';
import {expect} from 'chai';
import {configFile, DEFAULT_TIMEOUT} from '../src/common.js';
import {DEFAULT_TIMEOUT} from '../src/common.js';
import {DEFAULT_TEST_TIMEOUT} from './common.js';
import {testApp} from './tests-setup.js';
import sinon from 'sinon';
import mockedEnv from 'mocked-env';
import {backendConfig} from '../src/config.js';
describe('App', async function () {
// increase timeout for the suite
@@ -39,6 +40,7 @@ describe('App', async function () {
const clock = sandbox.useFakeTimers();
const processExitStub = sandbox.stub(process, 'exit');
// fake NODE_ENV as integration test
// @ts-expect-error type definitions are not working for some reason
const restore = mockedEnv({
NODE_ENV: 'integration-test',
});
@@ -55,7 +57,7 @@ describe('App', async function () {
});
it('should provide request body too large error in case of a body larger than the max size', async function () {
sandbox.stub(configFile.backend, 'maxRequestBodySize').value('3');
sandbox.stub(backendConfig.backend, 'maxRequestBodySize').value('3');
const {status} = await testApp
.post('/')

View File

@@ -13,8 +13,8 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {yearSlice} from '../config/default.js';
import {expect} from 'chai';
import {yearSlice} from '../config/default/tools/semester-acronym.js';
describe('Common', function () {
describe('yearSlice', function () {

View File

@@ -20,12 +20,12 @@ import {getIndexUID} from '../src/storage/elasticsearch/util.js';
import {configureApp} from '../src/app.js';
import express from 'express';
import http from 'http';
import {configFile} from '../src/common.js';
import {MailQueue} from '../src/notification/mail-queue.js';
import {Bulk, BulkStorage} from '../src/storage/bulk-storage.js';
import getPort from 'get-port';
import {Database} from '../src/storage/database.js';
import {v4} from 'uuid';
import {backendConfig} from '../src/config.js';
/**
* Adds routers and configures an (express) app
@@ -108,7 +108,7 @@ export class ElasticsearchMock implements Database {
}
}
export const bulkStorageMock = new BulkStorage(new ElasticsearchMock(configFile));
export const bulkStorageMock = new BulkStorage(new ElasticsearchMock(backendConfig));
export const bulk: Bulk = {
expiration: moment().add(3600, 'seconds').format(),

View File

@@ -13,8 +13,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {SMTP} from '@openstapps/logger/lib/smtp';
import {Transport} from '@openstapps/logger/lib/transport';
import {SMTP, Transport} from '@openstapps/logger';
import {expect} from 'chai';
import mockedEnv from 'mocked-env';
import {BackendTransport, isTransportWithVerification} from '../../src/notification/backend-transport.js';
@@ -54,6 +53,7 @@ describe('Backend transport', function () {
it('should not throw in case of error getting SMTP instance when transport not allowed', function () {
sandbox.stub(SMTP, 'getInstance').throws('Foo Error');
// @ts-expect-error wrong type defs for some reason
const restore = mockedEnv({
ALLOW_NO_TRANSPORT: 'true',
});
@@ -66,6 +66,7 @@ describe('Backend transport', function () {
it('should throw in case of error getting SMTP instance when transport is allowed', function () {
sandbox.stub(SMTP, 'getInstance').throws('Foo Error');
// @ts-expect-error wrong type defs for some reason
const restore = mockedEnv({
ALLOW_NO_TRANSPORT: undefined,
});

View File

@@ -21,9 +21,13 @@ import {
SCNotFoundErrorResponse,
} from '@openstapps/core';
import {expect} from 'chai';
import {instance as book} from '@openstapps/core/test/resources/indexable/Book.1.json';
import {bulk, DEFAULT_TEST_TIMEOUT} from '../common.js';
import {testApp} from '../tests-setup.js';
import {readFile} from 'fs/promises';
const book = JSON.parse(
await readFile('node_modules/@openstapps/core/test/resources/indexable/Book.1.json', 'utf8'),
);
describe('Bulk routes', async function () {
// increase timeout for the suite

View File

@@ -23,19 +23,24 @@ import {
SCValidationErrorResponse,
} from '@openstapps/core';
import nock from 'nock';
import {configFile, plugins} from '../../src/common.js';
import {plugins} from '../../src/common.js';
import {pluginRegisterHandler} from '../../src/routes/plugin-register-route.js';
import {expect, use} from 'chai';
import chaiAsPromised from 'chai-as-promised';
import {instance as registerRequest} from '@openstapps/core/test/resources/PluginRegisterRequest.1.json';
import {DEFAULT_TEST_TIMEOUT} from '../common.js';
import {testApp} from '../tests-setup.js';
import {backendConfig} from '../../src/config.js';
import {readFile} from 'fs/promises';
// for using promises in expectations (to.eventually.be...)
use(chaiAsPromised);
const registerRequest = JSON.parse(
await readFile('node_modules/@openstapps/core/test/resources/PluginRegisterRequest.1.json', 'utf8'),
);
// cast it because of "TS2322: Type 'string' is not assignable to type '"add"'"
export const registerAddRequest: SCPluginAdd = registerRequest as SCPluginAdd;
export const registerAddRequest: SCPluginAdd = registerRequest.instance as SCPluginAdd;
export const registerRemoveRequest: SCPluginRemove = {
action: 'remove',
@@ -48,7 +53,7 @@ describe('Plugin registration', async function () {
after(function () {
// remove plugins
plugins.clear();
configFile.app.features = {};
backendConfig.app.features = {};
});
it('should register a plugin', async function () {
@@ -57,7 +62,7 @@ describe('Plugin registration', async function () {
expect(response).to.deep.equal(bodySuccess) &&
expect(plugins.size).to.equal(1) &&
expect(configFile.app.features.plugins!['Foo Plugin']).to.not.be.empty;
expect(backendConfig.app.features.plugins!['Foo Plugin']).to.not.be.empty;
});
it('should allow re-registering the same plugin', async function () {
@@ -70,7 +75,7 @@ describe('Plugin registration', async function () {
return (
expect(response).to.deep.equal(bodySuccess) &&
expect(plugins.size).to.equal(1) &&
expect(configFile.app.features.plugins!['Foo Plugin']).to.not.be.empty
expect(backendConfig.app.features.plugins!['Foo Plugin']).to.not.be.empty
);
});
@@ -102,7 +107,7 @@ describe('Plugin registration', async function () {
expect(response).to.deep.equal(bodySuccess) &&
expect(plugins.size).to.equal(0) &&
expect(configFile.app.features.plugins).to.be.empty;
expect(backendConfig.app.features.plugins).to.be.empty;
});
it('should throw a "not found" error when removing a plugin whose registered route does not exist', async function () {

View File

@@ -21,7 +21,7 @@ import {
SCRouteHttpVerbs,
SCValidationErrorResponse,
} from '@openstapps/core';
import * as bodyParser from 'body-parser';
import bodyParser from 'body-parser';
import sinon from 'sinon';
import {expect} from 'chai';
import {Application} from 'express';

View File

@@ -21,10 +21,10 @@ import {
SCTooManyRequestsErrorResponse,
} from '@openstapps/core';
import {expect} from 'chai';
import {configFile} from '../../src/common.js';
import {DEFAULT_TEST_TIMEOUT} from '../common.js';
import {testApp} from '../tests-setup.js';
import sinon from 'sinon';
import {backendConfig} from '../../src/config.js';
describe('Search route', async function () {
// increase timeout for the suite
@@ -96,7 +96,7 @@ describe('Search route', async function () {
it('should respond with too many requests error if the number of sub-queries exceed their max number', async function () {
const sandbox = sinon.createSandbox();
sandbox.stub(configFile.backend, 'maxMultiSearchRouteQueries').value(2);
sandbox.stub(backendConfig.backend, 'maxMultiSearchRouteQueries').value(2);
const {status} = await testApp
.post(multiSearchRoute.urlPath)

View File

@@ -17,11 +17,15 @@ import {SCThingUpdateRoute} from '@openstapps/core';
import chaiAsPromised from 'chai-as-promised';
import {bulkStorageMock, DEFAULT_TEST_TIMEOUT} from '../common.js';
import {expect, use} from 'chai';
import {instance as book} from '@openstapps/core/test/resources/indexable/Book.1.json';
import {testApp} from '../tests-setup.js';
import {readFile} from 'fs/promises';
use(chaiAsPromised);
const book = JSON.parse(
await readFile('node_modules/@openstapps/core/test/resources/indexable/Book.1.json', 'utf8'),
);
describe('Thing update route', async function () {
// increase timeout for the suite
this.timeout(DEFAULT_TEST_TIMEOUT);

View File

@@ -25,7 +25,7 @@ import {mockReq} from 'sinon-express-mock';
import {plugins, validator} from '../../src/common.js';
import {virtualPluginRoute} from '../../src/routes/virtual-plugin-route.js';
import {DEFAULT_TEST_TIMEOUT, FooError} from '../common.js';
import {registerAddRequest} from './plugin-register-route.spec';
import {registerAddRequest} from './plugin-register-route.spec.js';
import {testApp} from '../tests-setup.js';
use(chaiAsPromised);
@@ -154,12 +154,12 @@ describe('Virtual plugin routes', async function () {
plugins.clear();
// // restore everything to default methods (remove stubs)
sandbox.restore();
// clean up request mocks (fixes issue with receiving response from mock from previous test case)
// cleanup request mocks (fixes issue with receiving response from mock from previous test case)
nock.cleanAll();
});
it('should properly provide the response of a plugin', async function () {
// lets simulate that the plugin is already registered
// let's simulate that the plugin is already registered
plugins.set(registerAddRequest.plugin.route, registerAddRequest.plugin);
// mock responses of the plugin, depending on the body sent
nock('http://foo.com:1234')
@@ -174,20 +174,20 @@ describe('Virtual plugin routes', async function () {
.set('Content-Type', 'application/json')
.set('Accept', 'application/json')
.send({query: 'foo'});
expect(fooResponse.status).to.be.equal(OK);
expect(fooResponse.body).to.be.deep.equal({result: [{foo: 'foo'}, {bar: 'foo'}]});
const barResponse = await testApp
.post('/foo')
.set('Content-Type', 'application/json')
.set('Accept', 'application/json')
.send({query: 'bar'});
expect(fooResponse.status).to.be.equal(OK);
expect(fooResponse.body).to.be.deep.equal({result: [{foo: 'foo'}, {bar: 'foo'}]});
expect(barResponse.status).to.be.equal(OK);
expect(barResponse.body).to.be.deep.equal({result: [{foo: 'bar'}, {bar: 'bar'}]});
});
it('should return error response if plugin address is not responding', async function () {
// lets simulate that the plugin is already registered
// let's simulate that the plugin is already registered
plugins.set(registerAddRequest.plugin.route, registerAddRequest.plugin);
class FooError extends Error {}

View File

@@ -17,12 +17,12 @@ import {SCBulkRequest, SCThingType} from '@openstapps/core';
import moment from 'moment';
// eslint-disable-next-line unicorn/import-style
import util from 'util';
import {configFile} from '../../src/common.js';
import {Bulk, BulkStorage} from '../../src/storage/bulk-storage.js';
import {expect} from 'chai';
import {ElasticsearchMock} from '../common.js';
import sinon from 'sinon';
import NodeCache from 'node-cache';
import {backendConfig} from '../../src/config.js';
describe('Bulk Storage', function () {
describe('Bulk', function () {
@@ -58,7 +58,7 @@ describe('Bulk Storage', function () {
let database: ElasticsearchMock;
beforeEach(function () {
database = new ElasticsearchMock(configFile);
database = new ElasticsearchMock(backendConfig);
esMock = sandbox.stub(database, 'bulkExpired');
});

View File

@@ -31,10 +31,7 @@ import {
SCThings,
SCThingType,
} from '@openstapps/core';
import {instance as book} from '@openstapps/core/test/resources/indexable/Book.1.json';
import {instance as message} from '@openstapps/core/test/resources/indexable/Message.1.json';
import {Logger} from '@openstapps/logger';
import {SMTP} from '@openstapps/logger/lib/smtp';
import {Logger, SMTP} from '@openstapps/logger';
import {expect, use} from 'chai';
import chaiAsPromised from 'chai-as-promised';
import {beforeEach} from 'mocha';
@@ -58,6 +55,8 @@ import * as Monitoring from '../../../src/storage/elasticsearch/monitoring.js';
import * as templating from '../../../src/storage/elasticsearch/templating.js';
import {bulk, DEFAULT_TEST_TIMEOUT, getTransport, getIndex} from '../../common.js';
import fs from 'fs';
import {backendConfig} from '../../../src/config.js';
import {readFile} from 'fs/promises';
use(chaiAsPromised);
@@ -68,6 +67,13 @@ function searchResponse<T>(...hits: SearchHit<T>[]): SearchResponse<T> {
return {hits: {hits}, took: 0, timed_out: false, _shards: {total: 1, failed: 0, successful: 1}};
}
const message = JSON.parse(
await readFile('node_modules/@openstapps/core/test/resources/indexable/Message.1.json', 'utf8'),
);
const book = JSON.parse(
await readFile('node_modules/@openstapps/core/test/resources/indexable/Book.1.json', 'utf8'),
);
describe('Elasticsearch', function () {
// increase timeout for the suite
this.timeout(DEFAULT_TEST_TIMEOUT);
@@ -85,6 +91,7 @@ describe('Elasticsearch', function () {
describe('getElasticsearchUrl', function () {
it('should provide custom elasticsearch URL if defined', function () {
const customAddress = 'http://foo-address:9200';
// @ts-expect-error wrong type defs for some reason
const restore = mockedEnv({
ES_ADDR: customAddress,
});
@@ -95,6 +102,7 @@ describe('Elasticsearch', function () {
});
it('should provide local URL as fallback', function () {
// @ts-expect-error wrong type defs for some reason
const restore = mockedEnv({
ES_ADDR: undefined,
});
@@ -193,16 +201,19 @@ describe('Elasticsearch', function () {
});
it('should complain (throw an error) if database in config is undefined', function () {
const config: SCConfigFile = {...configFile, internal: {...configFile.internal, database: undefined}};
const config: SCConfigFile = {
...backendConfig,
internal: {...backendConfig.internal, database: undefined},
};
expect(() => new Elasticsearch(config)).to.throw(Error);
});
it('should complain (throw an error) if database version is not a string', function () {
const config: SCConfigFile = {
...configFile,
...backendConfig,
internal: {
...configFile.internal,
...backendConfig.internal,
database: {
name: 'foo',
version: 123,
@@ -218,7 +229,7 @@ describe('Elasticsearch', function () {
const loggerErrorStub = sandbox.stub(Logger, 'error').resolves('foo');
sandbox.stub(Diagnostic.prototype, 'on').yields(error);
new Elasticsearch(configFile);
new Elasticsearch(backendConfig);
expect(loggerErrorStub.calledWith(error)).to.be.true;
});
@@ -228,13 +239,14 @@ describe('Elasticsearch', function () {
const loggerLogStub = sandbox.stub(Logger, 'log');
sandbox.stub(Diagnostic.prototype, 'on').yields(null, fakeResponse);
new Elasticsearch(configFile);
new Elasticsearch(backendConfig);
expect(loggerLogStub.calledWith(fakeResponse)).to.be.false;
// @ts-expect-error wrong type defs for some reason
const restore = mockedEnv({
ES_DEBUG: 'true',
});
new Elasticsearch(configFile);
new Elasticsearch(backendConfig);
expect(loggerLogStub.calledWith(fakeResponse)).to.be.true;
// restore env variables
@@ -249,9 +261,9 @@ describe('Elasticsearch', function () {
it('should complain (throw an error) if monitoring is set but mail queue is undefined', async function () {
const config: SCConfigFile = {
...configFile,
...backendConfig,
internal: {
...configFile.internal,
...backendConfig.internal,
monitoring: {
actions: [],
watchers: [],
@@ -266,9 +278,9 @@ describe('Elasticsearch', function () {
it('should setup the monitoring if there is monitoring is set and mail queue is defined', function () {
const config: SCConfigFile = {
...configFile,
...backendConfig,
internal: {
...configFile.internal,
...backendConfig.internal,
monitoring: {
actions: [],
watchers: [],
@@ -420,7 +432,7 @@ describe('Elasticsearch', function () {
const sandbox = sinon.createSandbox();
before(function () {
es = new Elasticsearch(configFile);
es = new Elasticsearch(backendConfig);
});
afterEach(function () {
@@ -451,7 +463,7 @@ describe('Elasticsearch', function () {
const sandbox = sinon.createSandbox();
before(function () {
es = new Elasticsearch(configFile);
es = new Elasticsearch(backendConfig);
});
beforeEach(function () {
@@ -510,7 +522,7 @@ describe('Elasticsearch', function () {
const sandbox = sinon.createSandbox();
before(function () {
es = new Elasticsearch(configFile);
es = new Elasticsearch(backendConfig);
});
afterEach(function () {
sandbox.restore();

View File

@@ -146,7 +146,7 @@ describe('Query', function () {
tieBreaker: 0,
};
const config: SCConfigFile = {
...configFile,
...backendConfig,
};
beforeEach(function () {
esConfig = {

View File

@@ -2,9 +2,7 @@
"extends": "@openstapps/tsconfig",
"compilerOptions": {
"resolveJsonModule": true,
"skipLibCheck": true,
"useUnknownInCatchVariables": false,
"lib": ["ES2020"]
},
"exclude": ["./config/", "./test"]
"allowJs": true
}
}

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="page">
<xs:complexType>
<xs:complexContent>
<xs:extension base="contents">
<xs:attribute name="href" type="xs:string" use="required"/>
<xs:attribute name="lang" type="xs:language"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:complexType name="contents">
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="section" type="section"/>
<xs:element name="link" type="link"/>
<xs:element name="markdown">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string"/>
</xs:simpleContent>
</xs:complexType>
</xs:element>
<xs:element name="rows" type="table"/>
</xs:choice>
</xs:sequence>
</xs:complexType>
<xs:complexType name="table">
<xs:sequence>
<xs:element name="columns" type="contents" maxOccurs="unbounded" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="section">
<xs:complexContent>
<xs:extension base="contents">
<xs:attribute name="title" type="xs:string" use="required"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="link">
<xs:complexContent>
<xs:extension base="xs:normalizedString">
<xs:attribute name="icon" type="xs:string" use="required"/>
<xs:attribute name="href" type="xs:string" use="required"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="menu">
<xs:complexType>
<xs:sequence>
<xs:element name="group" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="item" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:complexContent>
<xs:extension base="xs:normalizedString">
<xs:attributeGroup ref="link"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="title" type="xs:string" use="required"/>
<xs:attributeGroup ref="link"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="link">
<xs:attribute name="route" type="xs:string" use="required"/>
<xs:attribute name="icon" type="xs:string" use="required"/>
<xs:attribute name="authProvider" type="xs:string"/>
</xs:attributeGroup>
</xs:schema>

View File

@@ -0,0 +1,17 @@
{
"name": "@openstapps/backend-config",
"description": "Backend Configuration for OpenStApps",
"version": "2.1.0",
"type": "module",
"license": "GPL-3.0-only",
"author": "Thea Schöbl <dev@theaninova.de>",
"devDependencies": {
"@openstapps/tsconfig": "workspace:*",
"@openstapps/prettier-config": "workspace:*",
"@openstapps/eslint-config": "workspace:*"
},
"prettier": "@openstapps/prettier-config",
"eslintConfig": {
"extends": ["@openstapps/eslint-configt"]
}
}

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8" ?>
<page href="about"
lang="de"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="file://../about-page.xsd">
<section title="Über Open StApps">
<section title="Verbundprojekt mehrerer Hochschulen für eine generische Studierenden-App">
<markdown>
Open StApps bietet Studierenden aller beteiligten Hochschulen eine qualitativ
hochwertige App für den Studienalltag. Open StApps-Verbundpartner integrieren
generalisierbare Studierendenprozesse so in App-Module, dass diese auch
von anderen Hochschulen verwendet werden können. Die in der Open StApps App
verwendeten Daten einer Datenquelle sind in einem generalisierten Datenmodell
so aufbereitet, dass ein Austausch oder Abschaltung der Datenquelle problemlos möglich
ist und die Open StApps App problemlos weiterhin funktionsfähig bleibt.
</markdown>
</section>
<section title="Goethe-Uni Kontakt">
<rows>
<columns>
<markdown>Adresse</markdown>
<markdown>
Goethe Universität&lt;br>
Hochschulrechenzentrum (HRZ)&lt;br>
Norbert-Wollheim-Platz 1&lt;br>
60629 Frankfurt
</markdown>
</columns>
<columns>
<markdown>Kontaktinformationen</markdown>
<markdown>
[app@rz.uni-frankfurt.de](mailto:app@rz.uni-frankfurt.de)&lt;br>
[+49 69 798 32936](tel:+496979832936)&lt;br>
[https://app.rz.uni-frankfurt.de](https://app.rz.uni-frankfurt.de)
</markdown>
</columns>
</rows>
</section>
<link href="changelog" icon="newspaper">Neue Funktionen / Gelöste Probleme</link>
<link href="imprint" icon="description">Impressum</link>
<link href="privacy" icon="policy">Datenschutz</link>
<link href="licenses" icon="copyright">Bibliotheken und Lizenzen</link>
</section>
</page>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?>
<menu xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="file://../menu.xsd">
<group title="Übersicht" icon="home" route="/overview">
<item title="news" title.de="Aktuelles" icon="newspaper" route="/news">Aktuelles</item>
<item title="search" title.de="Suche" icon="search" route="/search"/>
<item title="library catalog" title.de="Bibliothekskatalog" icon="local_library" route="/hebis-search"/>
<item title="course catalog" title.de="Vorlesungsverzeichnis" icon="inventory_2" route="/catalog"/>
</group>
<group title="canteen" title.de="Mensa" icon="local_cafe" route="/canteen"/>
<group title="campus map" title.de="Campus Karte" icon="map" route="/map"/>
<group title="my app" title.de="Meine App" icon="school" route="/profile">
<item title="favorites" title.de="Favoriten" icon="grade" route="/favorites"/>
<item title="schedule" title.de="Stundenplan" icon="calendar_today" route="/schedule"/>
<item title="library account" title.de="Bibliothekskonto" icon="badge" route="/library-account" authProvider="paia"/>
<item title="settings" title.de="Einstellungen" icon="settings" route="/settings"/>
<item title="About the App" title.de="Über die App" icon="info" route="/about"/>
</group>
</menu>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?>
<menu xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="file://../menu.xsd">
<group title="overview" title.de="Übersicht" icon="home" route="/overview">
<item title="news" title.de="Aktuelles" icon="newspaper" route="/news" />
<item title="search" title.de="Suche" icon="search" route="/search"/>
<item title="library catalog" title.de="Bibliothekskatalog" icon="local_library" route="/hebis-search"/>
<item title="course catalog" title.de="Vorlesungsverzeichnis" icon="inventory_2" route="/catalog"/>
</group>
<group title="canteen" title.de="Mensa" icon="local_cafe" route="/canteen"/>
<group title="campus map" title.de="Campus Karte" icon="map" route="/map"/>
<group title="my app" title.de="Meine App" icon="school" route="/profile">
<item title="favorites" title.de="Favoriten" icon="grade" route="/favorites"/>
<item title="schedule" title.de="Stundenplan" icon="calendar_today" route="/schedule"/>
<item title="library account" title.de="Bibliothekskonto" icon="badge" route="/library-account" authProvider="paia"/>
<item title="settings" title.de="Einstellungen" icon="settings" route="/settings"/>
<item title="About the App" title.de="Über die App" icon="info" route="/about"/>
</group>
</menu>

View File

@@ -0,0 +1,3 @@
{
"extends": "@openstapps/tsconfig"
}

View File

@@ -3,14 +3,14 @@ module.exports = {
ignorePatterns: ['projects/**/*'],
parserOptions: {
sourceType: 'module',
ecmaVersion: 2021,
ecmaVersion: 2022,
},
overrides: [
{
files: ['*.ts'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2021,
ecmaVersion: 2022,
sourceType: 'module',
project: ['tsconfig.json'],
createDefaultProgram: true,

View File

@@ -1,6 +1,6 @@
{
"name": "@openstapps/eslint-config",
"description": "A collection of configuration base files for StApps projects.",
"description": "A collection of configuration base files for StApps projects. Just an (unused) experiment for now.",
"version": "2.1.0",
"type": "commonjs",
"license": "GPL-3.0-only",

View File

@@ -42,7 +42,7 @@ describe('Bulk', function () {
afterEach(function () {
sandbox.restore();
})
});
it('should add', async function () {
sandbox.on(client, 'invokeRoute', () => {

View File

@@ -35,10 +35,17 @@ import chaiSpies from 'chai-spies';
import clone = require('rfdc');
import moment from 'moment';
import traverse from 'traverse';
import {ConnectorClient, EmptyBulkError, NamespaceNotDefinedError, HttpClient, HttpClientRequest, HttpClientResponse} from '../src/index.js';
import path from "path";
import {fileURLToPath} from "url";
import {readdir, readFile} from "fs/promises";
import {
ConnectorClient,
EmptyBulkError,
NamespaceNotDefinedError,
HttpClient,
HttpClientRequest,
HttpClientResponse,
} from '../src/index.js';
import path from 'path';
import {fileURLToPath} from 'url';
import {readdir, readFile} from 'fs/promises';
chai.should();
chai.use(chaiSpies);
@@ -333,7 +340,7 @@ describe('ConnectorClient', function () {
expect(ConnectorClient.makeUUID('foo', 'f-u')).not.to.be.equal(uuid);
});
it('should fail making a uuid', async function (){
it('should fail making a uuid', async function () {
expect(() => {
ConnectorClient.makeUUID('foo', 'b-u');
}).to.throw(NamespaceNotDefinedError);
@@ -353,12 +360,14 @@ describe('ConnectorClient', function () {
const testFiles = await readdir(pathToTestFiles);
const testInstances = await Promise.all(testFiles.map(async testFile => {
const testInstances = await Promise.all(
testFiles.map(async testFile => {
const buffer = await readFile(path.join(pathToTestFiles, testFile));
const content = JSON.parse(buffer.toString());
return content.instance;
}));
}),
);
for (const testInstance of testInstances) {
const checkInstance = clone()(testInstance);

View File

@@ -33,9 +33,9 @@ import {createFileSync} from 'fs-extra';
// eslint-disable-next-line unicorn/prevent-abbreviations
import {e2eRun, getItemsFromSamples, ApiError, HttpClient, RequestOptions, Response} from '../src/index.js';
import {RecursivePartial} from './client.spec.js';
import {expect} from "chai";
import path from "path";
import {fileURLToPath} from "url";
import {expect} from 'chai';
import path from 'path';
import {fileURLToPath} from 'url';
chai.should();
chai.use(chaiSpies);
@@ -184,14 +184,18 @@ describe('e2e Connector', function () {
if (!existsSync(emptyDirectoryPath)) {
mkdirSync(emptyDirectoryPath);
}
await e2eRun(httpClient, {to: 'http://localhost', samplesLocation: emptyDirectoryPath}).should.be.rejectedWith(
'Could not index samples. None were retrieved from the file system.',
);
await e2eRun(httpClient, {
to: 'http://localhost',
samplesLocation: emptyDirectoryPath,
}).should.be.rejectedWith('Could not index samples. None were retrieved from the file system.');
rmdirSync(emptyDirectoryPath);
});
it('should fail to index directory without json data', async function () {
const somewhatFilledDirectoryPath = path.join(path.dirname(fileURLToPath(import.meta.url)), 'somewhatFilledDir');
const somewhatFilledDirectoryPath = path.join(
path.dirname(fileURLToPath(import.meta.url)),
'somewhatFilledDir',
);
if (!existsSync(somewhatFilledDirectoryPath)) {
mkdirSync(somewhatFilledDirectoryPath);
}

View File

@@ -21,7 +21,7 @@ import {HttpClient} from '../src/index.js';
describe('HttpClient', function () {
afterEach(function () {
nock.cleanAll();
})
});
it('should construct', function () {
expect(() => {

View File

@@ -19,9 +19,9 @@ import {expect} from 'chai';
import chaiSpies from 'chai-spies';
import {HttpClient} from '../src/index.js';
import {TestPlugin} from './plugin-resources/test-plugin.js';
import path from "path";
import {readFile} from "fs/promises";
import {fileURLToPath} from "url";
import path from 'path';
import {readFile} from 'fs/promises';
import {fileURLToPath} from 'url';
chai.use(chaiSpies);
@@ -56,12 +56,12 @@ describe('Plugin', function () {
'',
'',
);
})
});
afterEach(async function () {
await testPlugin.close();
sandbox.restore();
})
});
it('should construct', async function () {
const converter = new Converter(

View File

@@ -36,13 +36,14 @@ export class Validator {
*/
private readonly ajv = new Ajv.default({
verbose: true,
allowUnionTypes: true,
code: {regExp: re2 as never},
});
/**
* Map of schema names to schemas
*/
private readonly schemas: { [type: string]: Schema } = {};
private readonly schemas: {[type: string]: Schema} = {};
/**
* A wrapper function for Ajv that transforms the error into the compatible old error
@@ -94,7 +95,7 @@ export class Validator {
if (schema === undefined) {
if (isThingWithType(instance)) {
// schema name can be inferred from type string
const schemaSuffix = (instance as { type: string }).type
const schemaSuffix = (instance as {type: string}).type
.split(' ')
.map((part: string) => {
return part.slice(0, 1).toUpperCase() + part.slice(1);
@@ -178,7 +179,10 @@ export async function validateFiles(
await v.addSchemas(schemaDirectory);
// get a list of files to test
const testFiles = await glob(path.posix.join(resourcesDirectory.replaceAll(path.sep, path.posix.sep), '*.json'), {absolute: true});
const testFiles = await glob(
path.posix.join(resourcesDirectory.replaceAll(path.sep, path.posix.sep), '*.json'),
{absolute: true},
);
if (testFiles.length === 0) {
throw new Error(`No test files in ${resourcesDirectory}!`);

View File

@@ -8,7 +8,10 @@ describe('Schema', function () {
this.slow(10_000);
it('should validate against test files', async function () {
const errorsPerFile = await validateFiles(path.resolve('lib', 'schema'), path.resolve('test', 'resources'));
const errorsPerFile = await validateFiles(
path.resolve('lib', 'schema'),
path.resolve('test', 'resources'),
);
await mkdir('report', {recursive: true});
await writeReport(path.join('report', 'index.html'), errorsPerFile);

135
pnpm-lock.yaml generated
View File

@@ -34,6 +34,7 @@ importers:
'@openstapps/prettier-config': workspace:*
'@openstapps/tsconfig': workspace:*
'@testdeck/mocha': 0.3.3
'@types/body-parser': 1.19.2
'@types/chai': 4.3.4
'@types/chai-as-promised': 7.1.5
'@types/config': 3.3.0
@@ -47,16 +48,20 @@ importers:
'@types/node-cron': 3.0.7
'@types/nodemailer': 6.4.7
'@types/promise-queue': 2.2.0
'@types/sinon': 10.0.14
'@types/sinon-express-mock': 1.3.9
'@types/supertest': 2.0.12
'@types/uuid': 8.3.4
'@typescript-eslint/eslint-plugin': 5.49.0
'@typescript-eslint/parser': 5.49.0
body-parser: 1.20.2
c8: 7.13.0
chai: 4.3.7
chai-as-promised: 7.1.1
config: 3.3.9
conventional-changelog-cli: 2.2.2
cors: 2.8.5
cosmiconfig: 8.1.3
cross-env: 7.0.3
eslint: 8.33.0
eslint-config-prettier: 8.6.0
eslint-plugin-jsdoc: 39.7.4
@@ -75,12 +80,11 @@ importers:
node-cache: 5.1.2
node-cron: 3.0.2
nodemailer: 6.9.1
nyc: 15.1.0
prettier: 2.8.6
prom-client: 14.1.1
promise-queue: 2.2.5
redoc-cli: 0.13.20
sinon: 14.0.2
sinon: 15.0.4
sinon-express-mock: 2.2.1
supertest: 6.3.3
ts-node: 10.9.1
@@ -93,8 +97,9 @@ importers:
'@openstapps/core': link:../../packages/core
'@openstapps/core-tools': link:../../packages/core-tools
'@openstapps/logger': link:../../packages/logger
config: 3.3.9
body-parser: 1.20.2
cors: 2.8.5
cosmiconfig: 8.1.3
express: 4.18.2
express-prom-bundle: 6.6.0_prom-client@14.1.1
express-promise-router: 4.1.1_fhep4uswmxibn3567pnkkiq3fa
@@ -116,6 +121,7 @@ importers:
'@openstapps/prettier-config': link:../../configuration/prettier-config
'@openstapps/tsconfig': link:../../configuration/tsconfig
'@testdeck/mocha': 0.3.3
'@types/body-parser': 1.19.2
'@types/chai': 4.3.4
'@types/chai-as-promised': 7.1.5
'@types/config': 3.3.0
@@ -129,14 +135,17 @@ importers:
'@types/node-cron': 3.0.7
'@types/nodemailer': 6.4.7
'@types/promise-queue': 2.2.0
'@types/sinon': 10.0.14
'@types/sinon-express-mock': 1.3.9
'@types/supertest': 2.0.12
'@types/uuid': 8.3.4
'@typescript-eslint/eslint-plugin': 5.49.0_bhfhikta3fu7tua7ac2hrytgwe
'@typescript-eslint/parser': 5.49.0_o2s6jvgtr2hafiobaqfgu6k2l4
c8: 7.13.0
chai: 4.3.7
chai-as-promised: 7.1.1_chai@4.3.7
conventional-changelog-cli: 2.2.2
cross-env: 7.0.3
eslint: 8.33.0
eslint-config-prettier: 8.6.0_eslint@8.33.0
eslint-plugin-jsdoc: 39.7.4_eslint@8.33.0
@@ -145,11 +154,10 @@ importers:
get-port: 5.1.1
mocha: 10.2.0
mocked-env: 1.3.5
nyc: 15.1.0
prettier: 2.8.6
redoc-cli: 0.13.20_ouq63s25oqg3e72zt7ebre7ohe
sinon: 14.0.2
sinon-express-mock: 2.2.1_sinon@14.0.2
sinon: 15.0.4
sinon-express-mock: 2.2.1_sinon@15.0.4
supertest: 6.3.3
tsup: 6.7.0_mwhvu7sfp6vq5ryuwb6hlbjfka
typedoc: 0.23.28_typescript@4.8.4
@@ -236,6 +244,32 @@ importers:
tsup: 6.7.0_mwhvu7sfp6vq5ryuwb6hlbjfka
typedoc: 0.23.28_typescript@4.8.4
configuration/backend-config:
specifiers:
'@openstapps/eslint-config': workspace:*
'@openstapps/prettier-config': workspace:*
'@openstapps/tsconfig': workspace:*
'@typescript-eslint/eslint-plugin': 5.49.0
'@typescript-eslint/parser': 5.49.0
eslint: 8.33.0
eslint-config-prettier: 8.6.0
eslint-plugin-jsdoc: 39.7.4
eslint-plugin-prettier: 4.2.1
eslint-plugin-unicorn: 45.0.2
prettier: 2.8.6
devDependencies:
'@openstapps/eslint-config': link:../eslint-config
'@openstapps/prettier-config': link:../prettier-config
'@openstapps/tsconfig': link:../tsconfig
'@typescript-eslint/eslint-plugin': 5.49.0_bhfhikta3fu7tua7ac2hrytgwe
'@typescript-eslint/parser': 5.49.0_o2s6jvgtr2hafiobaqfgu6k2l4
eslint: 8.33.0
eslint-config-prettier: 8.6.0_eslint@8.33.0
eslint-plugin-jsdoc: 39.7.4_eslint@8.33.0
eslint-plugin-prettier: 4.2.1_2blprs6rvzrjlgi3xqpegp3hoe
eslint-plugin-unicorn: 45.0.2_eslint@8.33.0
prettier: 2.8.6
configuration/eslint-config:
specifiers:
'@typescript-eslint/eslint-plugin': 5.49.0
@@ -5588,12 +5622,6 @@ packages:
engines: {node: '>=14.16'}
dev: false
/@sinonjs/commons/1.8.6:
resolution: {integrity: sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==}
dependencies:
type-detect: 4.0.8
dev: true
/@sinonjs/commons/2.0.0:
resolution: {integrity: sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==}
dependencies:
@@ -5612,20 +5640,6 @@ packages:
'@sinonjs/commons': 2.0.0
dev: true
/@sinonjs/fake-timers/9.1.2:
resolution: {integrity: sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==}
dependencies:
'@sinonjs/commons': 1.8.6
dev: true
/@sinonjs/samsam/7.0.1:
resolution: {integrity: sha512-zsAk2Jkiq89mhZovB2LLOdTCxJF4hqqTToGP0ASWlhp4I1hqOjcfmZGafXntCN7MDC6yySH0mFHrYtHceOeLmw==}
dependencies:
'@sinonjs/commons': 2.0.0
lodash.get: 4.4.2
type-detect: 4.0.8
dev: true
/@sinonjs/samsam/8.0.0:
resolution: {integrity: sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==}
dependencies:
@@ -5763,7 +5777,7 @@ packages:
resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==}
dependencies:
'@types/connect': 3.4.35
'@types/node': 18.15.5
'@types/node': 18.15.3
/@types/bonjour/3.5.10:
resolution: {integrity: sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==}
@@ -5816,7 +5830,7 @@ packages:
/@types/connect/3.4.35:
resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==}
dependencies:
'@types/node': 18.15.5
'@types/node': 18.15.3
/@types/cookie/0.4.1:
resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==}
@@ -6117,13 +6131,7 @@ packages:
resolution: {integrity: sha512-wHtSYqZ/c1FytL4qEMVb3tp8UZk1EnUq6Qc4jQv5eJ9N2QtFdS4WU3Wijqzeu5r3/DfZqpvzkxbbZLa0+9F9sA==}
dependencies:
'@types/express': 4.17.16
'@types/sinon': 10.0.13
dev: true
/@types/sinon/10.0.13:
resolution: {integrity: sha512-UVjDqJblVNQYvVNUsj0PuYYw0ELRmgt1Nt5Vk0pT5f16ROGfcKJY8o1HVuMOJOpD727RrGB9EGvoaTQE5tgxZQ==}
dependencies:
'@types/sinonjs__fake-timers': 8.1.2
'@types/sinon': 10.0.14
dev: true
/@types/sinon/10.0.14:
@@ -7455,6 +7463,25 @@ packages:
transitivePeerDependencies:
- supports-color
/body-parser/1.20.2:
resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
dependencies:
bytes: 3.1.2
content-type: 1.0.5
debug: 2.6.9
depd: 2.0.0
destroy: 1.2.0
http-errors: 2.0.0
iconv-lite: 0.4.24
on-finished: 2.4.1
qs: 6.11.0
raw-body: 2.5.2
type-is: 1.6.18
unpipe: 1.0.0
transitivePeerDependencies:
- supports-color
/bonjour/3.5.0:
resolution: {integrity: sha512-RaVTblr+OnEli0r/ud8InrU7D+G0y6aJhlxaLa6Pwty4+xoxboF1BsUI45tujvRpbj9dQVoglChqonGAsjEBYg==}
dependencies:
@@ -8684,6 +8711,16 @@ packages:
path-type: 4.0.0
dev: true
/cosmiconfig/8.1.3:
resolution: {integrity: sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==}
engines: {node: '>=14'}
dependencies:
import-fresh: 3.3.0
js-yaml: 4.1.0
parse-json: 5.2.0
path-type: 4.0.0
dev: false
/cpu-features/0.0.4:
resolution: {integrity: sha512-fKiZ/zp1mUwQbnzb9IghXtHtDoTMtNeb8oYGx6kX2SYfhnG0HNdBEBIzB9b5KlXu5DQPhfy3mInbBxFcgwAr3A==}
engines: {node: '>=10.0.0'}
@@ -8736,6 +8773,14 @@ packages:
pretty-bytes: 5.6.0
dev: true
/cross-env/7.0.3:
resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==}
engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'}
hasBin: true
dependencies:
cross-spawn: 7.0.3
dev: true
/cross-spawn/5.1.0:
resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==}
dependencies:
@@ -12712,7 +12757,7 @@ packages:
hasBin: true
dependencies:
'@colors/colors': 1.5.0
body-parser: 1.20.1
body-parser: 1.20.2
braces: 3.0.2
chokidar: 3.5.3
connect: 3.7.0
@@ -15874,7 +15919,6 @@ packages:
http-errors: 2.0.0
iconv-lite: 0.4.24
unpipe: 1.0.0
dev: true
/rc/1.2.8:
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
@@ -16883,23 +16927,12 @@ packages:
sinon: 15.0.4
dev: true
/sinon-express-mock/2.2.1_sinon@14.0.2:
/sinon-express-mock/2.2.1_sinon@15.0.4:
resolution: {integrity: sha512-z1wqaPMwEnfn0SpigFhVYVS/ObX1tkqyRzRdccX99FgQaLkxGSo4684unr3NCqWeYZ1zchxPw7oFIDfzg1cAjg==}
peerDependencies:
sinon: '*'
dependencies:
sinon: 14.0.2
dev: true
/sinon/14.0.2:
resolution: {integrity: sha512-PDpV0ZI3ZCS3pEqx0vpNp6kzPhHrLx72wA0G+ZLaaJjLIYeE0n8INlgaohKuGy7hP0as5tbUd23QWu5U233t+w==}
dependencies:
'@sinonjs/commons': 2.0.0
'@sinonjs/fake-timers': 9.1.2
'@sinonjs/samsam': 7.0.1
diff: 5.1.0
nise: 5.1.4
supports-color: 7.2.0
sinon: 15.0.4
dev: true
/sinon/15.0.4: