commit 17838c4f1e59706452cbf614d413f0ad7154f8f7 Author: Carsten Graf Date: Thu Jan 22 01:13:28 2026 +0100 FirstCommit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b0f145d --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# Dependencies +node_modules/ +npm-debug.log* + +# Database +*.db +*.sqlite +*.sqlite3 + +# Environment variables +.env +.env.local + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Logs +logs/ +*.log diff --git a/README.md b/README.md new file mode 100644 index 0000000..9ca15e4 --- /dev/null +++ b/README.md @@ -0,0 +1,152 @@ +# Stundenerfassungs-System + +Eine webbasierte Anwendung zur Erfassung von Arbeitszeiten mit Admin-Bereich und PDF-Export. + +## Features + +### Für Mitarbeiter +- ✅ Login mit Benutzername und Passwort +- ✅ Wöchentliche Stundenerfassung (Montag - Sonntag) +- ✅ Automatisches Speichern der Einträge +- ✅ Eingabe von Start-/Endzeit, Pausen und Notizen +- ✅ Automatische Berechnung der Gesamtstunden +- ✅ Wöchentliches Abschicken des Stundenzettels + +### Für Administratoren +- ✅ Benutzerverwaltung (Anlegen, Löschen) +- ✅ Rollenvergabe (Mitarbeiter, Verwaltung, Admin) +- ✅ Übersicht aller Benutzer + +### Für Verwaltung +- ✅ Postfach mit eingereichten Stundenzetteln +- ✅ PDF-Generierung und Download +- ✅ Übersichtliche Darstellung aller Einreichungen + +## Installation + +### Voraussetzungen +- Node.js (Version 14 oder höher) +- npm (wird mit Node.js installiert) + +### Schritt 1: Dependencies installieren + +```bash +npm install +``` + +### Schritt 2: Server starten + +```bash +npm start +``` + +Der Server läuft nun auf `http://localhost:3000` + +Für Entwicklung mit automatischem Neustart: +```bash +npm run dev +``` + +## Standard-Zugangsdaten + +Nach der Installation sind folgende Benutzer verfügbar: + +### Administrator +- **Benutzername:** admin +- **Passwort:** admin123 +- **Funktion:** Kann Benutzer anlegen und verwalten + +### Verwaltung +- **Benutzername:** verwaltung +- **Passwort:** verwaltung123 +- **Funktion:** Kann eingereichte Stundenzettel einsehen und PDFs erstellen + +**WICHTIG:** Bitte ändern Sie diese Passwörter nach der ersten Anmeldung! + +## Verwendung + +### Für Mitarbeiter + +1. Melden Sie sich mit Ihren Zugangsdaten an +2. Wählen Sie die gewünschte Woche aus (Pfeiltasten) +3. Tragen Sie Ihre Arbeitszeiten ein: + - **Start:** Arbeitsbeginn + - **Ende:** Arbeitsende + - **Pause:** Pausenzeit in Minuten + - **Notizen:** Optional, z.B. Projekt oder Tätigkeit +4. Die Einträge werden automatisch gespeichert +5. Am Ende der Woche: Klicken Sie auf **"Woche abschicken"** +6. Nach dem Abschicken können keine Änderungen mehr vorgenommen werden + +### Für Administratoren + +1. Melden Sie sich als Admin an +2. Sie gelangen automatisch zur Benutzerverwaltung +3. **Neuen Benutzer anlegen:** + - Füllen Sie das Formular aus + - Wählen Sie die passende Rolle + - Klicken Sie auf "Benutzer anlegen" +4. **Benutzer löschen:** + - Klicken Sie auf "Löschen" neben dem gewünschten Benutzer + - System-Benutzer (Admin, Verwaltung) können nicht gelöscht werden + +### Für Verwaltung + +1. Melden Sie sich als Verwaltungs-Benutzer an +2. Sie sehen alle eingereichten Stundenzettel im Postfach +3. **PDF erstellen:** + - Klicken Sie auf "PDF herunterladen" neben dem gewünschten Stundenzettel + - Die PDF wird automatisch generiert und heruntergeladen +4. Die PDF enthält: + - Mitarbeitername + - Zeitraum + - Alle Tageseinträge mit Start, Ende, Pause, Stunden und Notizen + - Gesamtstundensumme + +## Technologie-Stack + +- **Backend:** Node.js + Express +- **Datenbank:** SQLite3 +- **Template Engine:** EJS +- **PDF-Generierung:** PDFKit +- **Authentifizierung:** bcryptjs + express-session + +## Datenbankstruktur + +### Tabelle: users +- Speichert Benutzerinformationen und Zugangsdaten +- Passwörter werden verschlüsselt gespeichert + +### Tabelle: timesheet_entries +- Speichert einzelne Tageseinträge +- Automatische Berechnung der Gesamtstunden + +### Tabelle: weekly_timesheets +- Speichert eingereichte Wochenstundenzettel +- Verknüpfung mit Benutzer und Status + +## Sicherheit + +- ✅ Passwörter werden mit bcrypt verschlüsselt +- ✅ Session-basierte Authentifizierung +- ✅ Rollenbasierte Zugriffskontrolle +- ✅ CSRF-Schutz durch Sessions + +## Anpassungen + +### Port ändern +Bearbeiten Sie in `server.js` die Zeile: +```javascript +const PORT = 3000; // Ändern Sie hier den Port +``` + +### Datenbank-Speicherort +Die Datenbank wird standardmäßig als `stundenerfassung.db` im Projektverzeichnis gespeichert. + +## Lizenz + +Proprietär - Für interne Firmennutzung + +## Support + +Bei Fragen oder Problemen wenden Sie sich bitte an Ihre IT-Abteilung. diff --git a/SCHNELLSTART.md b/SCHNELLSTART.md new file mode 100644 index 0000000..7683813 --- /dev/null +++ b/SCHNELLSTART.md @@ -0,0 +1,110 @@ +# Schnellstart-Anleitung + +## Installation in 3 Schritten + +### 1. Projekt entpacken +Entpacken Sie das Projekt-Archiv in einen beliebigen Ordner auf Ihrem Server. + +### 2. Dependencies installieren +Öffnen Sie ein Terminal/Kommandozeile im Projekt-Ordner und führen Sie aus: + +```bash +npm install +``` + +Dies installiert alle benötigten Pakete: +- express (Webserver) +- sqlite3 (Datenbank) +- bcryptjs (Passwort-Verschlüsselung) +- express-session (Session-Verwaltung) +- ejs (Template Engine) +- pdfkit (PDF-Generierung) +- body-parser (Request-Verarbeitung) + +### 3. Server starten +```bash +npm start +``` + +Die Anwendung ist nun unter `http://localhost:3000` erreichbar. + +## Erster Login + +### Als Administrator +- URL: `http://localhost:3000` +- Benutzername: `admin` +- Passwort: `admin123` + +Nach dem Login können Sie: +- Neue Mitarbeiter anlegen +- Rollen vergeben (Mitarbeiter, Verwaltung, Admin) +- Benutzer verwalten + +### Als Verwaltung +- URL: `http://localhost:3000` +- Benutzername: `verwaltung` +- Passwort: `verwaltung123` + +Nach dem Login können Sie: +- Eingereichte Stundenzettel einsehen +- PDFs erstellen und herunterladen + +## Wichtige Hinweise + +⚠️ **Passwörter ändern!** +Bitte ändern Sie die Standard-Passwörter nach der ersten Anmeldung! + +⚠️ **Firewall-Einstellungen** +Stellen Sie sicher, dass Port 3000 in Ihrer Firewall geöffnet ist, falls Sie von anderen Computern darauf zugreifen möchten. + +⚠️ **Produktiv-Einsatz** +Für den Produktiv-Einsatz empfehlen wir: +- HTTPS verwenden (z.B. mit nginx als Reverse Proxy) +- Starke Passwörter verwenden +- Regelmäßige Backups der Datenbank erstellen + +## Port ändern + +Falls Port 3000 bereits belegt ist, können Sie den Port ändern: + +1. Öffnen Sie `server.js` +2. Ändern Sie die Zeile `const PORT = 3000;` auf den gewünschten Port +3. Speichern und Server neu starten + +## Datenbank-Speicherort + +Die SQLite-Datenbank wird automatisch als `stundenerfassung.db` im Projekt-Verzeichnis erstellt. + +**Backup erstellen:** +Kopieren Sie einfach die Datei `stundenerfassung.db` an einen sicheren Ort. + +## Problemlösung + +### Server startet nicht +- Prüfen Sie, ob Port 3000 bereits belegt ist +- Prüfen Sie, ob Node.js installiert ist: `node --version` +- Prüfen Sie, ob alle Dependencies installiert sind: `npm install` + +### Login funktioniert nicht +- Löschen Sie die Datei `stundenerfassung.db` und starten Sie den Server neu +- Die Datenbank wird dann mit den Standard-Benutzern neu erstellt + +### PDF-Download funktioniert nicht +- Prüfen Sie die Browser-Konsole auf Fehler +- Stellen Sie sicher, dass Popups für die Seite erlaubt sind + +## Workflow + +1. **Admin** legt neue Mitarbeiter an +2. **Mitarbeiter** melden sich an und erfassen ihre Stunden +3. Mitarbeiter sehen ihre Woche (Montag-Sonntag) +4. Einträge werden automatisch beim Ausfüllen gespeichert +5. Am Ende der Woche: "Woche abschicken" klicken +6. **Verwaltung** sieht eingereichte Stundenzettel im Postfach +7. Verwaltung kann PDFs erstellen und herunterladen + +## Support + +Bei Fragen oder Problemen: +- Prüfen Sie die ausführliche README.md +- Kontaktieren Sie Ihre IT-Abteilung diff --git a/database.js b/database.js new file mode 100644 index 0000000..ad8de1b --- /dev/null +++ b/database.js @@ -0,0 +1,250 @@ +const sqlite3 = require('sqlite3').verbose(); +const bcrypt = require('bcryptjs'); +const path = require('path'); + +const dbPath = path.join(__dirname, 'stundenerfassung.db'); +const db = new sqlite3.Database(dbPath); + +// Datenbank initialisieren +function initDatabase() { + db.serialize(() => { + // Benutzer-Tabelle + db.run(`CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT UNIQUE NOT NULL, + password TEXT NOT NULL, + firstname TEXT NOT NULL, + lastname TEXT NOT NULL, + role TEXT NOT NULL DEFAULT 'mitarbeiter', + last_week_start TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP + )`); + + // Migration: last_week_start Spalte hinzufügen falls sie nicht existiert + db.run(`ALTER TABLE users ADD COLUMN last_week_start TEXT`, (err) => { + // Fehler ignorieren wenn Spalte bereits existiert + }); + + // Stundenerfassung-Tabelle + db.run(`CREATE TABLE IF NOT EXISTS timesheet_entries ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + date TEXT NOT NULL, + start_time TEXT, + end_time TEXT, + break_minutes INTEGER DEFAULT 0, + total_hours REAL, + notes TEXT, + activity1_desc TEXT, + activity1_hours REAL, + activity2_desc TEXT, + activity2_hours REAL, + activity3_desc TEXT, + activity3_hours REAL, + activity4_desc TEXT, + activity4_hours REAL, + activity5_desc TEXT, + activity5_hours REAL, + status TEXT DEFAULT 'offen', + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) + )`); + + // Migration: Tätigkeitsfelder hinzufügen falls sie nicht existieren + const activityColumns = [ + 'activity1_desc', 'activity1_hours', + 'activity2_desc', 'activity2_hours', + 'activity3_desc', 'activity3_hours', + 'activity4_desc', 'activity4_hours', + 'activity5_desc', 'activity5_hours' + ]; + + activityColumns.forEach(col => { + const colType = col.includes('_hours') ? 'REAL' : 'TEXT'; + db.run(`ALTER TABLE timesheet_entries ADD COLUMN ${col} ${colType}`, (err) => { + // Fehler ignorieren wenn Spalte bereits existiert + }); + }); + + // Wöchentliche Stundenzettel-Tabelle + db.run(`CREATE TABLE IF NOT EXISTS weekly_timesheets ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + week_start TEXT NOT NULL, + week_end TEXT NOT NULL, + version INTEGER DEFAULT 1, + status TEXT DEFAULT 'eingereicht', + submitted_at DATETIME DEFAULT CURRENT_TIMESTAMP, + reviewed_by INTEGER, + reviewed_at DATETIME, + pdf_downloaded_at DATETIME, + pdf_downloaded_by INTEGER, + FOREIGN KEY (user_id) REFERENCES users(id), + FOREIGN KEY (reviewed_by) REFERENCES users(id), + FOREIGN KEY (pdf_downloaded_by) REFERENCES users(id) + )`); + + // Migration: version Spalte hinzufügen falls sie nicht existiert + db.run(`ALTER TABLE weekly_timesheets ADD COLUMN version INTEGER DEFAULT 1`, (err) => { + // Fehler ignorieren wenn Spalte bereits existiert + // Wenn Spalte neu erstellt wurde, bestehende Einträge haben automatisch version = 1 + }); + + // Migration: pdf_downloaded_at Spalte hinzufügen falls sie nicht existiert + db.run(`ALTER TABLE weekly_timesheets ADD COLUMN pdf_downloaded_at DATETIME`, (err) => { + // Fehler ignorieren wenn Spalte bereits existiert + }); + + // Migration: pdf_downloaded_by Spalte hinzufügen falls sie nicht existiert + db.run(`ALTER TABLE weekly_timesheets ADD COLUMN pdf_downloaded_by INTEGER`, (err) => { + // Fehler ignorieren wenn Spalte bereits existiert + }); + + // Migration: version_reason Spalte hinzufügen + db.run(`ALTER TABLE weekly_timesheets ADD COLUMN version_reason TEXT`, (err) => { + // Fehler ignorieren wenn Spalte bereits existiert + if (err && !err.message.includes('duplicate column')) { + console.warn('Warnung beim Hinzufügen der Spalte version_reason:', err.message); + } + }); + + // Migration: admin_comment Spalte hinzufügen + db.run(`ALTER TABLE weekly_timesheets ADD COLUMN admin_comment TEXT`, (err) => { + // Fehler ignorieren wenn Spalte bereits existiert + if (err && !err.message.includes('duplicate column')) { + console.warn('Warnung beim Hinzufügen der Spalte admin_comment:', err.message); + } + }); + + // Migration: Projektnummern für Tätigkeiten hinzufügen + const projectNumberColumns = [ + 'activity1_project_number', 'activity2_project_number', + 'activity3_project_number', 'activity4_project_number', + 'activity5_project_number' + ]; + + projectNumberColumns.forEach(col => { + db.run(`ALTER TABLE timesheet_entries ADD COLUMN ${col} TEXT`, (err) => { + // Fehler ignorieren wenn Spalte bereits existiert + if (err && !err.message.includes('duplicate column')) { + console.warn(`Warnung beim Hinzufügen der Spalte ${col}:`, err.message); + } + }); + }); + + // Migration: Überstunden und Urlaub hinzufügen + db.run(`ALTER TABLE timesheet_entries ADD COLUMN overtime_taken_hours REAL`, (err) => { + // Fehler ignorieren wenn Spalte bereits existiert + if (err && !err.message.includes('duplicate column')) { + console.warn('Warnung beim Hinzufügen der Spalte overtime_taken_hours:', err.message); + } + }); + + db.run(`ALTER TABLE timesheet_entries ADD COLUMN vacation_type TEXT`, (err) => { + // Fehler ignorieren wenn Spalte bereits existiert + if (err && !err.message.includes('duplicate column')) { + console.warn('Warnung beim Hinzufügen der Spalte vacation_type:', err.message); + } + }); + + // Migration: Pausen-Zeiten für API-Zeiterfassung hinzufügen + db.run(`ALTER TABLE timesheet_entries ADD COLUMN pause_start_time TEXT`, (err) => { + // Fehler ignorieren wenn Spalte bereits existiert + if (err && !err.message.includes('duplicate column')) { + console.warn('Warnung beim Hinzufügen der Spalte pause_start_time:', err.message); + } + }); + + db.run(`ALTER TABLE timesheet_entries ADD COLUMN pause_end_time TEXT`, (err) => { + // Fehler ignorieren wenn Spalte bereits existiert + if (err && !err.message.includes('duplicate column')) { + console.warn('Warnung beim Hinzufügen der Spalte pause_end_time:', err.message); + } + }); + + // Migration: User-Felder hinzufügen (Personalnummer, Wochenstunden, Urlaubstage) + db.run(`ALTER TABLE users ADD COLUMN personalnummer TEXT`, (err) => { + // Fehler ignorieren wenn Spalte bereits existiert + }); + + db.run(`ALTER TABLE users ADD COLUMN wochenstunden REAL`, (err) => { + // Fehler ignorieren wenn Spalte bereits existiert + }); + + db.run(`ALTER TABLE users ADD COLUMN urlaubstage REAL`, (err) => { + // Fehler ignorieren wenn Spalte bereits existiert + }); + + // LDAP-Konfiguration-Tabelle + db.run(`CREATE TABLE IF NOT EXISTS ldap_config ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + enabled INTEGER DEFAULT 0, + url TEXT, + bind_dn TEXT, + bind_password TEXT, + base_dn TEXT, + user_search_filter TEXT, + username_attribute TEXT DEFAULT 'cn', + firstname_attribute TEXT DEFAULT 'givenName', + lastname_attribute TEXT DEFAULT 'sn', + sync_interval INTEGER DEFAULT 0, + last_sync DATETIME, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP + )`); + + // LDAP-Sync-Log-Tabelle + db.run(`CREATE TABLE IF NOT EXISTS ldap_sync_log ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + sync_type TEXT NOT NULL, + status TEXT NOT NULL, + users_synced INTEGER DEFAULT 0, + error_message TEXT, + sync_started_at DATETIME DEFAULT CURRENT_TIMESTAMP, + sync_completed_at DATETIME + )`); + + // Migration: Bestehende Rollen zu JSON-Arrays konvertieren + // Prüfe ob Rollen noch als einfache Strings gespeichert sind (nicht als JSON-Array) + db.all('SELECT id, role FROM users', (err, users) => { + if (!err && users) { + users.forEach(user => { + let roleValue = user.role; + // Prüfe ob es bereits ein JSON-Array ist + try { + const parsed = JSON.parse(roleValue); + // Wenn erfolgreich geparst und es ist ein Array, nichts tun + if (Array.isArray(parsed)) { + return; // Bereits JSON-Array + } + } catch (e) { + // Nicht JSON, konvertiere zu JSON-Array + } + + // Konvertiere zu JSON-Array + const roleArray = JSON.stringify([roleValue]); + db.run('UPDATE users SET role = ? WHERE id = ?', [roleArray, user.id], (err) => { + if (err) { + console.warn(`Warnung beim Konvertieren der Rolle für User ${user.id}:`, err.message); + } + }); + }); + } + }); + + // Standard Admin-Benutzer erstellen + const adminPassword = bcrypt.hashSync('admin123', 10); + db.run(`INSERT OR IGNORE INTO users (id, username, password, firstname, lastname, role) + VALUES (1, 'admin', ?, 'System', 'Administrator', ?)`, + [adminPassword, JSON.stringify(['admin'])]); + + // Standard Verwaltungs-Benutzer erstellen + const verwaltungPassword = bcrypt.hashSync('verwaltung123', 10); + db.run(`INSERT OR IGNORE INTO users (id, username, password, firstname, lastname, role) + VALUES (2, 'verwaltung', ?, 'Verwaltung', 'User', ?)`, + [verwaltungPassword, JSON.stringify(['verwaltung'])]); + }); +} + +module.exports = { db, initDatabase }; diff --git a/dev/ldapserver/docker-compose.yml b/dev/ldapserver/docker-compose.yml new file mode 100644 index 0000000..af79a53 --- /dev/null +++ b/dev/ldapserver/docker-compose.yml @@ -0,0 +1,44 @@ +version: "3" + +volumes: + lldap_data: + driver: local + +services: + lldap: + image: lldap/lldap:stable + ports: + # For LDAP, not recommended to expose, see Usage section. + - "3890:3890" + # For LDAPS (LDAP Over SSL), enable port if LLDAP_LDAPS_OPTIONS__ENABLED set true, look env below + #- "6360:6360" + # For the web front-end + - "17170:17170" + volumes: + - "lldap_data:/data" + # Alternatively, you can mount a local folder + # - "./lldap_data:/data" + environment: + - UID=1000 + - GID=1000 + - TZ=Europe/Berlin + - LLDAP_JWT_SECRET=1omV4UDprT1PAJFYXGisVlei/V5b5Uaiqssl9qburwV+T1S0ox8iurI6FJkWPnX5xRUMbHswJwZLG7QzUnzdZw== + - LLDAP_KEY_SEED=ffcomviFeT8RByJf7jT3AuzDcFbrgWb+oSuMDp72pql96J4Rdq5gno2Dk1xfWrYPLH5OoS/bpDuOp/oE9T5+sA== + - LLDAP_LDAP_BASE_DN=dc=gmbh,dc=de + - LLDAP_LDAP_USER_PASS=Delfine1!!! # If the password contains '$', escape it (e.g. Pas$$word sets Pas$word) + # If using LDAPS, set enabled true and configure cert and key path + # - LLDAP_LDAPS_OPTIONS__ENABLED=true + # - LLDAP_LDAPS_OPTIONS__CERT_FILE=/path/to/certfile.crt + # - LLDAP_LDAPS_OPTIONS__KEY_FILE=/path/to/keyfile.key + # You can also set a different database: + # - LLDAP_DATABASE_URL=mysql://mysql-user:password@mysql-server/my-database + # - LLDAP_DATABASE_URL=postgres://postgres-user:password@postgres-server/my-database + # If using SMTP, set the following variables + # - LLDAP_SMTP_OPTIONS__ENABLE_PASSWORD_RESET=true + # - LLDAP_SMTP_OPTIONS__SERVER=smtp.example.com + # - LLDAP_SMTP_OPTIONS__PORT=465 # Check your smtp provider's documentation for this setting + # - LLDAP_SMTP_OPTIONS__SMTP_ENCRYPTION=TLS # How the connection is encrypted, either "NONE" (no encryption, port 25), "TLS" (sometimes called SSL, port 465) or "STARTTLS" (sometimes called TLS, port 587). + # - LLDAP_SMTP_OPTIONS__USER=no-reply@example.com # The SMTP user, usually your email address + # - LLDAP_SMTP_OPTIONS__PASSWORD=PasswordGoesHere # The SMTP password + # - LLDAP_SMTP_OPTIONS__FROM=no-reply # The header field, optional: how the sender appears in the email. The first is a free-form name, followed by an email between <>. + # - LLDAP_SMTP_OPTIONS__TO=admin # Same for reply-to, optional. \ No newline at end of file diff --git a/ldap-service.js b/ldap-service.js new file mode 100644 index 0000000..2a7ba06 --- /dev/null +++ b/ldap-service.js @@ -0,0 +1,279 @@ +const ldap = require('ldapjs'); +const { db } = require('./database'); +const bcrypt = require('bcryptjs'); + +/** + * LDAP-Service für Benutzer-Synchronisation + */ +class LDAPService { + /** + * LDAP-Konfiguration aus der Datenbank abrufen + */ + static getConfig(callback) { + db.get('SELECT * FROM ldap_config WHERE id = 1', (err, config) => { + if (err) { + return callback(err, null); + } + callback(null, config); + }); + } + + /** + * LDAP-Verbindung herstellen + */ + static connect(config, callback) { + if (!config || !config.enabled || !config.url) { + return callback(new Error('LDAP ist nicht konfiguriert oder deaktiviert')); + } + + const client = ldap.createClient({ + url: config.url, + timeout: 10000, + connectTimeout: 10000 + }); + + // Fehlerbehandlung + client.on('error', (err) => { + callback(err, null); + }); + + // Bind mit Credentials + const bindDN = config.bind_dn || ''; + const bindPassword = config.bind_password || ''; + + // Hinweis: Passwort wird im Klartext gespeichert + // In einer produktiven Umgebung sollte man eine Verschlüsselung mit einem Master-Key verwenden + + client.bind(bindDN, bindPassword, (err) => { + if (err) { + client.unbind(); + return callback(err, null); + } + callback(null, client); + }); + } + + /** + * Benutzer aus LDAP abrufen + */ + static searchUsers(client, config, callback) { + const baseDN = config.base_dn || ''; + const searchFilter = config.user_search_filter || '(objectClass=person)'; + const searchOptions = { + filter: searchFilter, + scope: 'sub', + attributes: [ + config.username_attribute || 'cn', + config.firstname_attribute || 'givenName', + config.lastname_attribute || 'sn' + ] + }; + + const users = []; + + client.search(baseDN, searchOptions, (err, res) => { + if (err) { + return callback(err, null); + } + + res.on('searchEntry', (entry) => { + const user = { + username: this.getAttributeValue(entry, config.username_attribute || 'cn'), + firstname: this.getAttributeValue(entry, config.firstname_attribute || 'givenName'), + lastname: this.getAttributeValue(entry, config.lastname_attribute || 'sn') + }; + + // Nur Benutzer mit allen erforderlichen Feldern hinzufügen + if (user.username && user.firstname && user.lastname) { + users.push(user); + } + }); + + res.on('error', (err) => { + callback(err, null); + }); + + res.on('end', (result) => { + if (result && result.status !== 0) { + return callback(new Error(`LDAP-Suche fehlgeschlagen: ${result.status}`), null); + } + callback(null, users); + }); + }); + } + + /** + * Wert eines LDAP-Attributs extrahieren + */ + static getAttributeValue(entry, attributeName) { + const attr = entry.attributes.find(a => a.type === attributeName); + if (!attr) { + return null; + } + return Array.isArray(attr.values) ? attr.values[0] : attr.values; + } + + /** + * Benutzer in SQLite synchronisieren + */ + static syncUsers(ldapUsers, callback) { + let syncedCount = 0; + let errorCount = 0; + const errors = []; + + if (!ldapUsers || ldapUsers.length === 0) { + return callback(null, { synced: 0, errors: [] }); + } + + // Verarbeite jeden Benutzer + const processUser = (index) => { + if (index >= ldapUsers.length) { + return callback(null, { synced: syncedCount, errors: errors }); + } + + const ldapUser = ldapUsers[index]; + const username = ldapUser.username.trim(); + const firstname = ldapUser.firstname.trim(); + const lastname = ldapUser.lastname.trim(); + + // Prüfe ob Benutzer bereits existiert + db.get('SELECT id, role FROM users WHERE username = ?', [username], (err, existingUser) => { + if (err) { + errors.push(`Fehler beim Prüfen von ${username}: ${err.message}`); + errorCount++; + return processUser(index + 1); + } + + if (existingUser) { + // Benutzer existiert - aktualisiere nur Name, behalte Rolle + db.run( + 'UPDATE users SET firstname = ?, lastname = ? WHERE username = ?', + [firstname, lastname, username], + (err) => { + if (err) { + errors.push(`Fehler beim Aktualisieren von ${username}: ${err.message}`); + errorCount++; + } else { + syncedCount++; + } + processUser(index + 1); + } + ); + } else { + // Neuer Benutzer - erstelle mit Standard-Rolle + // Generiere ein zufälliges Passwort (Benutzer muss es beim ersten Login ändern) + const defaultPassword = bcrypt.hashSync('changeme123', 10); + + db.run( + 'INSERT INTO users (username, password, firstname, lastname, role) VALUES (?, ?, ?, ?, ?)', + [username, defaultPassword, firstname, lastname, 'mitarbeiter'], + (err) => { + if (err) { + errors.push(`Fehler beim Erstellen von ${username}: ${err.message}`); + errorCount++; + } else { + syncedCount++; + } + processUser(index + 1); + } + ); + } + }); + }; + + processUser(0); + } + + /** + * Sync-Log-Eintrag erstellen + */ + static createSyncLog(syncType, status, usersSynced, errorMessage, callback) { + const startedAt = new Date().toISOString(); + const completedAt = new Date().toISOString(); + + db.run( + `INSERT INTO ldap_sync_log (sync_type, status, users_synced, error_message, sync_started_at, sync_completed_at) + VALUES (?, ?, ?, ?, ?, ?)`, + [syncType, status, usersSynced, errorMessage || null, startedAt, completedAt], + (err) => { + if (callback) { + callback(err); + } + } + ); + } + + /** + * Letzte Synchronisation aktualisieren + */ + static updateLastSync(callback) { + db.run( + 'UPDATE ldap_config SET last_sync = CURRENT_TIMESTAMP WHERE id = 1', + (err) => { + if (callback) { + callback(err); + } + } + ); + } + + /** + * Vollständige Synchronisation durchführen + */ + static performSync(syncType, callback) { + const startedAt = new Date(); + + // Konfiguration abrufen + this.getConfig((err, config) => { + if (err) { + this.createSyncLog(syncType, 'error', 0, `Fehler beim Abrufen der Konfiguration: ${err.message}`, () => {}); + return callback(err); + } + + if (!config || !config.enabled) { + const errorMsg = 'LDAP-Synchronisation ist nicht aktiviert'; + this.createSyncLog(syncType, 'error', 0, errorMsg, () => {}); + return callback(new Error(errorMsg)); + } + + // LDAP-Verbindung herstellen + this.connect(config, (err, client) => { + if (err) { + this.createSyncLog(syncType, 'error', 0, `LDAP-Verbindungsfehler: ${err.message}`, () => {}); + return callback(err); + } + + // Benutzer aus LDAP abrufen + this.searchUsers(client, config, (err, ldapUsers) => { + // Verbindung schließen + client.unbind(); + + if (err) { + this.createSyncLog(syncType, 'error', 0, `LDAP-Suchfehler: ${err.message}`, () => {}); + return callback(err); + } + + // Benutzer synchronisieren + this.syncUsers(ldapUsers, (err, result) => { + if (err) { + this.createSyncLog(syncType, 'error', result.synced, `Sync-Fehler: ${err.message}`, () => {}); + return callback(err); + } + + // Letzte Synchronisation aktualisieren + this.updateLastSync(() => { + const status = result.errors.length > 0 ? 'error' : 'success'; + const errorMsg = result.errors.length > 0 ? result.errors.join('; ') : null; + + this.createSyncLog(syncType, status, result.synced, errorMsg, () => { + callback(null, result); + }); + }); + }); + }); + }); + }); + } +} + +module.exports = LDAPService; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..be5afbb --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6021 @@ +{ + "name": "stundenerfassung", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "stundenerfassung", + "version": "1.0.0", + "dependencies": { + "bcryptjs": "^2.4.3", + "body-parser": "^1.20.2", + "ejs": "^3.1.9", + "express": "^4.18.2", + "express-session": "^1.17.3", + "ldapjs": "^3.0.7", + "node-cron": "^3.0.3", + "pdfkit": "^0.13.0", + "sqlite3": "^5.1.6" + }, + "devDependencies": { + "nodemon": "^3.0.1" + } + }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "optional": true + }, + "node_modules/@ldapjs/asn1": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@ldapjs/asn1/-/asn1-2.0.0.tgz", + "integrity": "sha512-G9+DkEOirNgdPmD0I8nu57ygQJKOOgFEMKknEuQvIHbGLwP3ny1mY+OTUYLCbCaGJP4sox5eYgBJRuSUpnAddA==", + "deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md" + }, + "node_modules/@ldapjs/attribute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@ldapjs/attribute/-/attribute-1.0.0.tgz", + "integrity": "sha512-ptMl2d/5xJ0q+RgmnqOi3Zgwk/TMJYG7dYMC0Keko+yZU6n+oFM59MjQOUht5pxJeS4FWrImhu/LebX24vJNRQ==", + "deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md", + "dependencies": { + "@ldapjs/asn1": "2.0.0", + "@ldapjs/protocol": "^1.2.1", + "process-warning": "^2.1.0" + } + }, + "node_modules/@ldapjs/change": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@ldapjs/change/-/change-1.0.0.tgz", + "integrity": "sha512-EOQNFH1RIku3M1s0OAJOzGfAohuFYXFY4s73wOhRm4KFGhmQQ7MChOh2YtYu9Kwgvuq1B0xKciXVzHCGkB5V+Q==", + "deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md", + "dependencies": { + "@ldapjs/asn1": "2.0.0", + "@ldapjs/attribute": "1.0.0" + } + }, + "node_modules/@ldapjs/controls": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@ldapjs/controls/-/controls-2.1.0.tgz", + "integrity": "sha512-2pFdD1yRC9V9hXfAWvCCO2RRWK9OdIEcJIos/9cCVP9O4k72BY1bLDQQ4KpUoJnl4y/JoD4iFgM+YWT3IfITWw==", + "deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md", + "dependencies": { + "@ldapjs/asn1": "^1.2.0", + "@ldapjs/protocol": "^1.2.1" + } + }, + "node_modules/@ldapjs/controls/node_modules/@ldapjs/asn1": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ldapjs/asn1/-/asn1-1.2.0.tgz", + "integrity": "sha512-KX/qQJ2xxzvO2/WOvr1UdQ+8P5dVvuOLk/C9b1bIkXxZss8BaR28njXdPgFCpj5aHaf1t8PmuVnea+N9YG9YMw==", + "deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md" + }, + "node_modules/@ldapjs/dn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@ldapjs/dn/-/dn-1.1.0.tgz", + "integrity": "sha512-R72zH5ZeBj/Fujf/yBu78YzpJjJXG46YHFo5E4W1EqfNpo1UsVPqdLrRMXeKIsJT3x9dJVIfR6OpzgINlKpi0A==", + "deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md", + "dependencies": { + "@ldapjs/asn1": "2.0.0", + "process-warning": "^2.1.0" + } + }, + "node_modules/@ldapjs/filter": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@ldapjs/filter/-/filter-2.1.1.tgz", + "integrity": "sha512-TwPK5eEgNdUO1ABPBUQabcZ+h9heDORE4V9WNZqCtYLKc06+6+UAJ3IAbr0L0bYTnkkWC/JEQD2F+zAFsuikNw==", + "deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md", + "dependencies": { + "@ldapjs/asn1": "2.0.0", + "@ldapjs/protocol": "^1.2.1", + "process-warning": "^2.1.0" + } + }, + "node_modules/@ldapjs/messages": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ldapjs/messages/-/messages-1.3.0.tgz", + "integrity": "sha512-K7xZpXJ21bj92jS35wtRbdcNrwmxAtPwy4myeh9duy/eR3xQKvikVycbdWVzkYEAVE5Ce520VXNOwCHjomjCZw==", + "deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md", + "dependencies": { + "@ldapjs/asn1": "^2.0.0", + "@ldapjs/attribute": "^1.0.0", + "@ldapjs/change": "^1.0.0", + "@ldapjs/controls": "^2.1.0", + "@ldapjs/dn": "^1.1.0", + "@ldapjs/filter": "^2.1.1", + "@ldapjs/protocol": "^1.2.1", + "process-warning": "^2.2.0" + } + }, + "node_modules/@ldapjs/protocol": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@ldapjs/protocol/-/protocol-1.2.1.tgz", + "integrity": "sha512-O89xFDLW2gBoZWNXuXpBSM32/KealKCTb3JGtJdtUQc7RjAk8XzrRgyz02cPAwGKwKPxy0ivuC7UP9bmN87egQ==", + "deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md" + }, + "node_modules/@npmcli/fs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", + "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "optional": true, + "dependencies": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + } + }, + "node_modules/@npmcli/move-file": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "optional": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/helpers": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.3.17.tgz", + "integrity": "sha512-tb7Iu+oZ+zWJZ3HJqwx8oNwSDIU440hmVMDPhpACWQWnrZHK99Bxs70gT1L2dnr5Hg50ZRWEFkQCAnOVVV0z1Q==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "optional": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "optional": true + }, + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "optional": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "optional": true + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "optional": true, + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "optional": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/aproba": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", + "optional": true + }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "deprecated": "This package is no longer supported.", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/backoff": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz", + "integrity": "sha512-wC5ihrnUXmR2douXmXLCe5O3zg3GKIyvRi/hi58a/XyRxVI+3/yM0PYueQOZXPXQ9pxBislYkw+sF9b7C/RuMA==", + "dependencies": { + "precond": "0.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "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.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brotli": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", + "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", + "dependencies": { + "base64-js": "^1.1.2" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacache": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", + "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", + "optional": true, + "dependencies": { + "@npmcli/fs": "^1.0.0", + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "optional": true, + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "devOptional": true + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "optional": true + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==" + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" + }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-equal": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", + "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.5", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.2", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "optional": true + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/dfa": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", + "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "optional": true + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "optional": true + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-session": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.2.tgz", + "integrity": "sha512-SZjssGQC7TzTs9rpPDuUrR23GNZ9+2+IkA/+IJWmvQilTr5OSliEHGF+D9scbIpdC6yGtTI0/VhaHoVes2AN/A==", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.7", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.1.0", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/extsprintf": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz", + "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==", + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fontkit": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-1.9.0.tgz", + "integrity": "sha512-HkW/8Lrk8jl18kzQHvAw9aTHe1cqsyx5sDnxncx652+CIfhawokEPkeM3BoIC+z/Xv7a0yMr0f3pRRwhGH455g==", + "dependencies": { + "@swc/helpers": "^0.3.13", + "brotli": "^1.3.2", + "clone": "^2.1.2", + "deep-equal": "^2.0.5", + "dfa": "^1.2.0", + "restructure": "^2.0.1", + "tiny-inflate": "^1.0.3", + "unicode-properties": "^1.3.1", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "optional": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "deprecated": "This package is no longer supported.", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "optional": true + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "optional": true + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "optional": true + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "optional": true, + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "optional": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/http-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "optional": true + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "optional": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "optional": true + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "optional": true, + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "optional": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "optional": true + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "optional": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "optional": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "optional": true + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "optional": true + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ldapjs": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/ldapjs/-/ldapjs-3.0.7.tgz", + "integrity": "sha512-1ky+WrN+4CFMuoekUOv7Y1037XWdjKpu0xAPwSP+9KdvmV9PG+qOKlssDV6a+U32apwxdD3is/BZcWOYzN30cg==", + "deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md", + "dependencies": { + "@ldapjs/asn1": "^2.0.0", + "@ldapjs/attribute": "^1.0.0", + "@ldapjs/change": "^1.0.0", + "@ldapjs/controls": "^2.1.0", + "@ldapjs/dn": "^1.1.0", + "@ldapjs/filter": "^2.1.1", + "@ldapjs/messages": "^1.3.0", + "@ldapjs/protocol": "^1.2.1", + "abstract-logging": "^2.0.1", + "assert-plus": "^1.0.0", + "backoff": "^2.5.0", + "once": "^1.4.0", + "vasync": "^2.2.1", + "verror": "^1.10.1" + } + }, + "node_modules/linebreak": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz", + "integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==", + "dependencies": { + "base64-js": "0.0.8", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/linebreak/node_modules/base64-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", + "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-fetch-happen": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", + "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", + "optional": true, + "dependencies": { + "agentkeepalive": "^4.1.3", + "cacache": "^15.2.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^6.0.0", + "minipass": "^3.1.3", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^1.3.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.2", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^6.0.0", + "ssri": "^8.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-fetch": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", + "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", + "optional": true, + "dependencies": { + "minipass": "^3.1.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "optionalDependencies": { + "encoding": "^0.1.12" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-abi": { + "version": "3.87.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz", + "integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==" + }, + "node_modules/node-cron": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz", + "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==", + "dependencies": { + "uuid": "8.3.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/node-gyp": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", + "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", + "optional": true, + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^9.1.0", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": ">= 10.12.0" + } + }, + "node_modules/nodemon": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz", + "integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nodemon/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "optional": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "deprecated": "This package is no longer supported.", + "optional": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "optional": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" + }, + "node_modules/pdfkit": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/pdfkit/-/pdfkit-0.13.0.tgz", + "integrity": "sha512-AW79eHU5eLd2vgRDS9z3bSoi0FA+gYm+100LLosrQQMLUzOBGVOhG7ABcMFpJu7Bpg+MT74XYHi4k9EuU/9EZw==", + "dependencies": { + "crypto-js": "^4.0.0", + "fontkit": "^1.8.1", + "linebreak": "^1.0.2", + "png-js": "^1.0.0" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/png-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/png-js/-/png-js-1.0.0.tgz", + "integrity": "sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==" + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/precond": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz", + "integrity": "sha512-QCYG84SgGyGzqJ/vlMsxeXd/pgL/I94ixdNFyh1PusWmTCyVfPJjZ1K1jvHtsbfnXQs2TSkEP2fR7QiMZAnKFQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/process-warning": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-2.3.2.tgz", + "integrity": "sha512-n9wh8tvBe5sFmsqlg+XQhaQLumwpqoAUruLwjCopgTmUBjJ/fjtBsJzKleCaIGBOMXYEhp1YfKl4d7rJ5ZKJGA==" + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "optional": true + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "optional": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/restructure": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/restructure/-/restructure-2.0.1.tgz", + "integrity": "sha512-e0dOpjm5DseomnXx2M5lpdZ5zoHqF1+bqdMJUohoYVVQa7cBdnk7fdmeI6byNWP/kiME72EeTiSypTCVnpLiDg==" + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "optional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "optional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "optional": true + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "optional": true + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "optional": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "optional": true, + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", + "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", + "optional": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/socks-proxy-agent/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "optional": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socks-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "optional": true + }, + "node_modules/sqlite3": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz", + "integrity": "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==", + "hasInstallScript": true, + "dependencies": { + "bindings": "^1.5.0", + "node-addon-api": "^7.0.0", + "prebuild-install": "^7.1.1", + "tar": "^6.1.11" + }, + "optionalDependencies": { + "node-gyp": "8.x" + }, + "peerDependencies": { + "node-gyp": "8.x" + }, + "peerDependenciesMeta": { + "node-gyp": { + "optional": true + } + } + }, + "node_modules/ssri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "optional": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "optional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exhorbitant rates) by contacting i@izs.me", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "node_modules/unicode-properties": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", + "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", + "dependencies": { + "base64-js": "^1.3.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, + "node_modules/unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "optional": true, + "dependencies": { + "unique-slug": "^2.0.0" + } + }, + "node_modules/unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "optional": true, + "dependencies": { + "imurmurhash": "^0.1.4" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vasync": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vasync/-/vasync-2.2.1.tgz", + "integrity": "sha512-Hq72JaTpcTFdWiNA4Y22Amej2GH3BFmBaKPPlDZ4/oC8HNn2ISHLkFrJU4Ds8R3jcUi7oo5Y9jcMHKjES+N9wQ==", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "verror": "1.10.0" + } + }, + "node_modules/vasync/node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/verror": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", + "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "optional": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + }, + "dependencies": { + "@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "optional": true + }, + "@ldapjs/asn1": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@ldapjs/asn1/-/asn1-2.0.0.tgz", + "integrity": "sha512-G9+DkEOirNgdPmD0I8nu57ygQJKOOgFEMKknEuQvIHbGLwP3ny1mY+OTUYLCbCaGJP4sox5eYgBJRuSUpnAddA==" + }, + "@ldapjs/attribute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@ldapjs/attribute/-/attribute-1.0.0.tgz", + "integrity": "sha512-ptMl2d/5xJ0q+RgmnqOi3Zgwk/TMJYG7dYMC0Keko+yZU6n+oFM59MjQOUht5pxJeS4FWrImhu/LebX24vJNRQ==", + "requires": { + "@ldapjs/asn1": "2.0.0", + "@ldapjs/protocol": "^1.2.1", + "process-warning": "^2.1.0" + } + }, + "@ldapjs/change": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@ldapjs/change/-/change-1.0.0.tgz", + "integrity": "sha512-EOQNFH1RIku3M1s0OAJOzGfAohuFYXFY4s73wOhRm4KFGhmQQ7MChOh2YtYu9Kwgvuq1B0xKciXVzHCGkB5V+Q==", + "requires": { + "@ldapjs/asn1": "2.0.0", + "@ldapjs/attribute": "1.0.0" + } + }, + "@ldapjs/controls": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@ldapjs/controls/-/controls-2.1.0.tgz", + "integrity": "sha512-2pFdD1yRC9V9hXfAWvCCO2RRWK9OdIEcJIos/9cCVP9O4k72BY1bLDQQ4KpUoJnl4y/JoD4iFgM+YWT3IfITWw==", + "requires": { + "@ldapjs/asn1": "^1.2.0", + "@ldapjs/protocol": "^1.2.1" + }, + "dependencies": { + "@ldapjs/asn1": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ldapjs/asn1/-/asn1-1.2.0.tgz", + "integrity": "sha512-KX/qQJ2xxzvO2/WOvr1UdQ+8P5dVvuOLk/C9b1bIkXxZss8BaR28njXdPgFCpj5aHaf1t8PmuVnea+N9YG9YMw==" + } + } + }, + "@ldapjs/dn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@ldapjs/dn/-/dn-1.1.0.tgz", + "integrity": "sha512-R72zH5ZeBj/Fujf/yBu78YzpJjJXG46YHFo5E4W1EqfNpo1UsVPqdLrRMXeKIsJT3x9dJVIfR6OpzgINlKpi0A==", + "requires": { + "@ldapjs/asn1": "2.0.0", + "process-warning": "^2.1.0" + } + }, + "@ldapjs/filter": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@ldapjs/filter/-/filter-2.1.1.tgz", + "integrity": "sha512-TwPK5eEgNdUO1ABPBUQabcZ+h9heDORE4V9WNZqCtYLKc06+6+UAJ3IAbr0L0bYTnkkWC/JEQD2F+zAFsuikNw==", + "requires": { + "@ldapjs/asn1": "2.0.0", + "@ldapjs/protocol": "^1.2.1", + "process-warning": "^2.1.0" + } + }, + "@ldapjs/messages": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ldapjs/messages/-/messages-1.3.0.tgz", + "integrity": "sha512-K7xZpXJ21bj92jS35wtRbdcNrwmxAtPwy4myeh9duy/eR3xQKvikVycbdWVzkYEAVE5Ce520VXNOwCHjomjCZw==", + "requires": { + "@ldapjs/asn1": "^2.0.0", + "@ldapjs/attribute": "^1.0.0", + "@ldapjs/change": "^1.0.0", + "@ldapjs/controls": "^2.1.0", + "@ldapjs/dn": "^1.1.0", + "@ldapjs/filter": "^2.1.1", + "@ldapjs/protocol": "^1.2.1", + "process-warning": "^2.2.0" + } + }, + "@ldapjs/protocol": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@ldapjs/protocol/-/protocol-1.2.1.tgz", + "integrity": "sha512-O89xFDLW2gBoZWNXuXpBSM32/KealKCTb3JGtJdtUQc7RjAk8XzrRgyz02cPAwGKwKPxy0ivuC7UP9bmN87egQ==" + }, + "@npmcli/fs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", + "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "optional": true, + "requires": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + } + }, + "@npmcli/move-file": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "optional": true, + "requires": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + } + }, + "@swc/helpers": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.3.17.tgz", + "integrity": "sha512-tb7Iu+oZ+zWJZ3HJqwx8oNwSDIU440hmVMDPhpACWQWnrZHK99Bxs70gT1L2dnr5Hg50ZRWEFkQCAnOVVV0z1Q==", + "requires": { + "tslib": "^2.4.0" + } + }, + "@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "optional": true + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "optional": true + }, + "abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==" + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "optional": true, + "requires": { + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "optional": true, + "requires": { + "ms": "^2.1.3" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "optional": true + } + } + }, + "agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "optional": true, + "requires": { + "humanize-ms": "^1.2.1" + } + }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "optional": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "optional": true + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "aproba": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", + "optional": true + }, + "are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + } + }, + "array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "requires": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==" + }, + "async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" + }, + "available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "requires": { + "possible-typed-array-names": "^1.0.0" + } + }, + "backoff": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz", + "integrity": "sha512-wC5ihrnUXmR2douXmXLCe5O3zg3GKIyvRi/hi58a/XyRxVI+3/yM0PYueQOZXPXQ9pxBislYkw+sF9b7C/RuMA==", + "requires": { + "precond": "0.2" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==" + }, + "binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "requires": { + "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.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + } + }, + "brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "requires": { + "balanced-match": "^1.0.0" + } + }, + "braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "requires": { + "fill-range": "^7.1.1" + } + }, + "brotli": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", + "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", + "requires": { + "base64-js": "^1.1.2" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "cacache": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", + "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", + "optional": true, + "requires": { + "@npmcli/fs": "^1.0.0", + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + } + }, + "call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "requires": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + } + }, + "call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + } + }, + "call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + } + }, + "chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "optional": true + }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==" + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "devOptional": true + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "optional": true + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "requires": { + "safe-buffer": "5.2.1" + } + }, + "content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" + }, + "cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==" + }, + "cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" + }, + "crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "requires": { + "mimic-response": "^3.1.0" + } + }, + "deep-equal": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", + "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", + "requires": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.5", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.2", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.13" + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, + "define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + } + }, + "define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "requires": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "optional": true + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" + }, + "detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==" + }, + "dfa": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", + "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==" + }, + "dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "requires": { + "jake": "^10.8.5" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "optional": true + }, + "encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" + }, + "encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "optional": true, + "requires": { + "iconv-lite": "^0.6.2" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, + "end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "requires": { + "once": "^1.4.0" + } + }, + "env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "optional": true + }, + "err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "optional": true + }, + "es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, + "es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + } + }, + "es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "requires": { + "es-errors": "^1.3.0" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" + }, + "expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==" + }, + "express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "express-session": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.2.tgz", + "integrity": "sha512-SZjssGQC7TzTs9rpPDuUrR23GNZ9+2+IkA/+IJWmvQilTr5OSliEHGF+D9scbIpdC6yGtTI0/VhaHoVes2AN/A==", + "requires": { + "cookie": "0.7.2", + "cookie-signature": "1.0.7", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.1.0", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + } + }, + "extsprintf": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz", + "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==" + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "requires": { + "minimatch": "^5.0.1" + } + }, + "fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + } + }, + "fontkit": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-1.9.0.tgz", + "integrity": "sha512-HkW/8Lrk8jl18kzQHvAw9aTHe1cqsyx5sDnxncx652+CIfhawokEPkeM3BoIC+z/Xv7a0yMr0f3pRRwhGH455g==", + "requires": { + "@swc/helpers": "^0.3.13", + "brotli": "^1.3.2", + "clone": "^2.1.2", + "deep-equal": "^2.0.5", + "dfa": "^1.2.0", + "restructure": "^2.0.1", + "tiny-inflate": "^1.0.3", + "unicode-properties": "^1.3.1", + "unicode-trie": "^2.0.0" + } + }, + "for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "requires": { + "is-callable": "^1.2.7" + } + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "requires": { + "minipass": "^3.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "optional": true + }, + "fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" + }, + "gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "optional": true, + "requires": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + } + }, + "get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + } + }, + "get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "requires": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + } + }, + "github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "optional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" + }, + "graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "optional": true + }, + "has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "requires": { + "es-define-property": "^1.0.0" + } + }, + "has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" + }, + "has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "requires": { + "has-symbols": "^1.0.3" + } + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "optional": true + }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "requires": { + "function-bind": "^1.1.2" + } + }, + "http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "optional": true + }, + "http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "requires": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + } + }, + "http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "optional": true, + "requires": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "optional": true, + "requires": { + "ms": "^2.1.3" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "optional": true + } + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "optional": true, + "requires": { + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "optional": true, + "requires": { + "ms": "^2.1.3" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "optional": true + } + } + }, + "humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "optional": true, + "requires": { + "ms": "^2.0.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "optional": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "optional": true + }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "optional": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "requires": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + } + }, + "ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "optional": true + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "requires": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + } + }, + "is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + } + }, + "is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "requires": { + "has-bigints": "^1.0.2" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "requires": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + } + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" + }, + "is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "requires": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "optional": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "optional": true + }, + "is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==" + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "requires": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + } + }, + "is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "requires": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + } + }, + "is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==" + }, + "is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "requires": { + "call-bound": "^1.0.3" + } + }, + "is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "requires": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + } + }, + "is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "requires": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + } + }, + "is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==" + }, + "is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "requires": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + } + }, + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "optional": true + }, + "jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "requires": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + } + }, + "ldapjs": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/ldapjs/-/ldapjs-3.0.7.tgz", + "integrity": "sha512-1ky+WrN+4CFMuoekUOv7Y1037XWdjKpu0xAPwSP+9KdvmV9PG+qOKlssDV6a+U32apwxdD3is/BZcWOYzN30cg==", + "requires": { + "@ldapjs/asn1": "^2.0.0", + "@ldapjs/attribute": "^1.0.0", + "@ldapjs/change": "^1.0.0", + "@ldapjs/controls": "^2.1.0", + "@ldapjs/dn": "^1.1.0", + "@ldapjs/filter": "^2.1.1", + "@ldapjs/messages": "^1.3.0", + "@ldapjs/protocol": "^1.2.1", + "abstract-logging": "^2.0.1", + "assert-plus": "^1.0.0", + "backoff": "^2.5.0", + "once": "^1.4.0", + "vasync": "^2.2.1", + "verror": "^1.10.1" + } + }, + "linebreak": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz", + "integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==", + "requires": { + "base64-js": "0.0.8", + "unicode-trie": "^2.0.0" + }, + "dependencies": { + "base64-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", + "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==" + } + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "make-fetch-happen": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", + "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", + "optional": true, + "requires": { + "agentkeepalive": "^4.1.3", + "cacache": "^15.2.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^6.0.0", + "minipass": "^3.1.3", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^1.3.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.2", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^6.0.0", + "ssri": "^8.0.0" + } + }, + "math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" + }, + "merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" + }, + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "requires": { + "yallist": "^4.0.0" + } + }, + "minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "optional": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-fetch": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", + "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", + "optional": true, + "requires": { + "encoding": "^0.1.12", + "minipass": "^3.1.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.0.0" + } + }, + "minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "optional": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "optional": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "optional": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==" + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, + "node-abi": { + "version": "3.87.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz", + "integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==", + "requires": { + "semver": "^7.3.5" + } + }, + "node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==" + }, + "node-cron": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz", + "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==", + "requires": { + "uuid": "8.3.2" + } + }, + "node-gyp": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", + "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", + "optional": true, + "requires": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^9.1.0", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + } + }, + "nodemon": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz", + "integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==", + "dev": true, + "requires": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "requires": { + "ms": "^2.1.3" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, + "nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "optional": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "optional": true, + "requires": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + } + }, + "object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==" + }, + "object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + } + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "requires": { + "wrappy": "1" + } + }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "optional": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==" + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "optional": true + }, + "path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" + }, + "pdfkit": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/pdfkit/-/pdfkit-0.13.0.tgz", + "integrity": "sha512-AW79eHU5eLd2vgRDS9z3bSoi0FA+gYm+100LLosrQQMLUzOBGVOhG7ABcMFpJu7Bpg+MT74XYHi4k9EuU/9EZw==", + "requires": { + "crypto-js": "^4.0.0", + "fontkit": "^1.8.1", + "linebreak": "^1.0.2", + "png-js": "^1.0.0" + } + }, + "picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "png-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/png-js/-/png-js-1.0.0.tgz", + "integrity": "sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==" + }, + "possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==" + }, + "prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "requires": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + } + }, + "precond": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz", + "integrity": "sha512-QCYG84SgGyGzqJ/vlMsxeXd/pgL/I94ixdNFyh1PusWmTCyVfPJjZ1K1jvHtsbfnXQs2TSkEP2fR7QiMZAnKFQ==" + }, + "process-warning": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-2.3.2.tgz", + "integrity": "sha512-n9wh8tvBe5sFmsqlg+XQhaQLumwpqoAUruLwjCopgTmUBjJ/fjtBsJzKleCaIGBOMXYEhp1YfKl4d7rJ5ZKJGA==" + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "optional": true + }, + "promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "optional": true, + "requires": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + } + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "requires": { + "side-channel": "^1.1.0" + } + }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==" + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "requires": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + } + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "requires": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + } + }, + "restructure": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/restructure/-/restructure-2.0.1.tgz", + "integrity": "sha512-e0dOpjm5DseomnXx2M5lpdZ5zoHqF1+bqdMJUohoYVVQa7cBdnk7fdmeI6byNWP/kiME72EeTiSypTCVnpLiDg==" + }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "optional": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==" + }, + "send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "requires": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "optional": true + }, + "set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + } + }, + "set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + } + }, + "side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + } + }, + "side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + } + }, + "side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + } + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "optional": true + }, + "simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" + }, + "simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "requires": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "requires": { + "semver": "^7.5.3" + } + }, + "smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "optional": true + }, + "socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "optional": true, + "requires": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + } + }, + "socks-proxy-agent": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", + "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", + "optional": true, + "requires": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "dependencies": { + "debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "optional": true, + "requires": { + "ms": "^2.1.3" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "optional": true + } + } + }, + "sqlite3": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz", + "integrity": "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==", + "requires": { + "bindings": "^1.5.0", + "node-addon-api": "^7.0.0", + "node-gyp": "8.x", + "prebuild-install": "^7.1.1", + "tar": "^6.1.11" + } + }, + "ssri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "optional": true, + "requires": { + "minipass": "^3.1.1" + } + }, + "statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==" + }, + "stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "requires": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "optional": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "optional": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "dependencies": { + "minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==" + } + } + }, + "tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + }, + "dependencies": { + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + } + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + } + }, + "tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==" + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, + "touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true + }, + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "requires": { + "random-bytes": "~1.0.0" + } + }, + "undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "unicode-properties": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", + "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", + "requires": { + "base64-js": "^1.3.0", + "unicode-trie": "^2.0.0" + } + }, + "unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "requires": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "optional": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "optional": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" + }, + "vasync": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vasync/-/vasync-2.2.1.tgz", + "integrity": "sha512-Hq72JaTpcTFdWiNA4Y22Amej2GH3BFmBaKPPlDZ4/oC8HNn2ISHLkFrJU4Ds8R3jcUi7oo5Y9jcMHKjES+N9wQ==", + "requires": { + "verror": "1.10.0" + }, + "dependencies": { + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + } + } + }, + "verror": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", + "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "optional": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "requires": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + } + }, + "which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "requires": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + } + }, + "which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "requires": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + } + }, + "wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..5dacbe4 --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "stundenerfassung", + "version": "1.0.0", + "description": "Stundenerfassungs-System für Mitarbeiter", + "main": "server.js", + "scripts": { + "start": "node server.js", + "dev": "nodemon server.js", + "reset-db": "node reset-db.js" + }, + "dependencies": { + "express": "^4.18.2", + "express-session": "^1.17.3", + "bcryptjs": "^2.4.3", + "sqlite3": "^5.1.6", + "body-parser": "^1.20.2", + "ejs": "^3.1.9", + "pdfkit": "^0.13.0", + "ldapjs": "^3.0.7", + "node-cron": "^3.0.3" + }, + "devDependencies": { + "nodemon": "^3.0.1" + } +} diff --git a/public/css/style.css b/public/css/style.css new file mode 100644 index 0000000..8e0863a --- /dev/null +++ b/public/css/style.css @@ -0,0 +1,1002 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + background-color: #f5f5f5; + color: #333; + line-height: 1.6; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 20px; +} + +.dashboard-container { + max-width: 100% !important; + padding: 20px 30px; +} + +/* Navbar */ +.navbar { + background-color: #2c3e50; + color: white; + padding: 15px 0; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +.navbar .container { + display: flex; + justify-content: space-between; + align-items: center; + max-width: 100%; + padding: 15px 30px; +} + +.navbar h1 { + font-size: 24px; + margin: 0; +} + +.nav-right { + display: flex; + align-items: center; + gap: 15px; +} + +/* Buttons */ +.btn { + padding: 10px 20px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + text-decoration: none; + display: inline-block; + transition: background-color 0.3s; +} + +.btn-primary { + background-color: #3498db; + color: white; +} + +.btn-primary:hover { + background-color: #2980b9; +} + +.btn-secondary { + background-color: #95a5a6; + color: white; +} + +.btn-secondary:hover { + background-color: #7f8c8d; +} + +.btn-success { + background-color: #27ae60; + color: white; +} + +.btn-success:hover { + background-color: #229954; +} + +.btn-danger { + background-color: #e74c3c; + color: white; +} + +.btn-danger:hover { + background-color: #c0392b; +} + +.btn-sm { + padding: 5px 10px; + font-size: 12px; +} + +/* Login Page */ +.login-container { + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); +} + +.login-box { + background: white; + padding: 40px; + border-radius: 8px; + box-shadow: 0 10px 25px rgba(0,0,0,0.2); + width: 100%; + max-width: 400px; +} + +.login-box h1 { + text-align: center; + color: #2c3e50; + margin-bottom: 10px; +} + +.login-box h2 { + text-align: center; + color: #7f8c8d; + font-size: 18px; + margin-bottom: 30px; + font-weight: normal; +} + +/* Forms */ +.form-group { + margin-bottom: 20px; +} + +.form-group label { + display: block; + margin-bottom: 5px; + color: #555; + font-weight: 500; +} + +.form-group input, +.form-group select, +.form-group textarea { + width: 100%; + padding: 10px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 14px; +} + +.form-group input:focus, +.form-group select:focus, +.form-group textarea:focus { + outline: none; + border-color: #3498db; +} + +.form-help-text { + display: block; + margin-top: 8px; + color: #666; + font-size: 13px; + font-style: italic; +} + +/* Rollen-Checkboxen Styling */ +.roles-checkbox-group { + display: flex; + flex-direction: column; + gap: 12px; + margin-top: 8px; + padding: 15px; + background-color: #f9f9f9; + border: 1px solid #e0e0e0; + border-radius: 6px; +} + +.roles-checkbox-group-inline { + padding: 10px; + gap: 8px; +} + +.role-checkbox-label { + display: flex; + align-items: center; + cursor: pointer; + padding: 10px 12px; + background-color: white; + border: 2px solid #e0e0e0; + border-radius: 6px; + transition: all 0.2s ease; + user-select: none; +} + +.role-checkbox-label:hover { + border-color: #3498db; + background-color: #f0f8ff; + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +.role-checkbox-label-small { + padding: 6px 10px; + font-size: 13px; +} + +.role-checkbox-input { + width: 18px; + height: 18px; + margin-right: 12px; + cursor: pointer; + accent-color: #3498db; + flex-shrink: 0; +} + +.role-checkbox-input:checked + .role-checkbox-text { + font-weight: 600; + color: #2c3e50; +} + +.role-checkbox-label:has(.role-checkbox-input:checked) { + border-color: #3498db; + background-color: #e8f4f8; + box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1); +} + +.role-checkbox-text { + font-size: 14px; + color: #333; + transition: all 0.2s ease; +} + +.role-checkbox-label-small .role-checkbox-text { + font-size: 13px; +} + +.form-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 15px; +} + +/* Error Messages */ +.error-message { + background-color: #e74c3c; + color: white; + padding: 10px; + border-radius: 4px; + margin-bottom: 20px; + text-align: center; +} + +/* Dashboard Layout */ +.dashboard-layout { + display: flex; + gap: 20px; + align-items: flex-start; +} + +/* Dashboard */ +.dashboard { + background: white; + padding: 30px; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + flex: 1; + min-width: 0; /* Ermöglicht Flexbox-Shrinking */ +} + +/* User Stats Panel */ +.user-stats-panel { + background: white; + padding: 20px; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + width: 280px; + flex-shrink: 0; + box-sizing: border-box; + overflow: hidden; +} + +.user-stats-panel h3 { + margin-top: 0; + margin-bottom: 15px; + color: #2c3e50; + font-size: 18px; + font-weight: 600; +} + +.stat-card { + background: #f8f9fa; + padding: 15px; + border-radius: 6px; + margin-bottom: 12px; + border-left: 4px solid #3498db; + box-sizing: border-box; + width: 100%; + overflow: hidden; +} + +.stat-card.stat-vacation { + border-left-color: #27ae60; +} + +.stat-label { + font-size: 12px; + color: #666; + margin-bottom: 6px; + font-weight: 500; +} + +.stat-value { + font-size: 24px; + font-weight: bold; + color: #2c3e50; + margin-bottom: 4px; +} + +.stat-unit { + font-size: 11px; + color: #999; +} + +.week-selector { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 30px; +} + +.week-selector h2 { + flex: 1; + text-align: center; + color: #2c3e50; +} + +/* Timesheet Table */ +.timesheet-grid { + overflow-x: auto; +} + +table { + width: 100%; + border-collapse: collapse; + margin-bottom: 20px; +} + +table th, +table td { + padding: 12px; + text-align: left; + border-bottom: 1px solid #ddd; +} + +table th { + background-color: #f8f9fa; + font-weight: 600; + color: #555; +} + +table tr:hover { + background-color: #f8f9fa; +} + +table input[type="time"], +table input[type="number"], +table input[type="text"] { + width: 100%; + padding: 8px; + border: 1px solid #ddd; + border-radius: 4px; +} + +/* Summary */ +.summary { + background-color: #ecf0f1; + padding: 20px; + border-radius: 4px; + margin-bottom: 20px; +} + +.summary-item { + display: flex; + justify-content: space-between; + font-size: 18px; +} + +/* Actions */ +.actions { + text-align: center; +} + +.actions .help-text { + margin-top: 10px; + color: #7f8c8d; + font-size: 14px; +} + +/* Admin Panel */ +.admin-panel { + background: white; + padding: 30px; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +.add-user-form { + background-color: #f8f9fa; + padding: 20px; + border-radius: 4px; + margin-bottom: 30px; +} + +.add-user-form h3 { + margin-bottom: 20px; + color: #2c3e50; +} + +/* Role Badges */ +.role-badge { + padding: 4px 12px; + border-radius: 12px; + font-size: 12px; + font-weight: 500; + text-transform: capitalize; +} + +.role-admin { + background-color: #e74c3c; + color: white; +} + +.role-verwaltung { + background-color: #3498db; + color: white; +} + +.role-mitarbeiter { + background-color: #27ae60; + color: white; +} + +/* Status Badges */ +.status-badge { + padding: 4px 12px; + border-radius: 12px; + font-size: 12px; + font-weight: 500; +} + +.status-eingereicht { + background-color: #f39c12; + color: white; +} + +.status-genehmigt { + background-color: #27ae60; + color: white; +} + +.status-abgelehnt { + background-color: #e74c3c; + color: white; +} + +/* Version Badge */ +.version-badge { + padding: 4px 12px; + border-radius: 12px; + font-size: 12px; + font-weight: 500; + background-color: #6c757d; + color: white; + display: inline-flex; + align-items: center; + gap: 5px; + margin-right: 10px; +} + +.version-info { + font-size: 11px; + opacity: 0.9; + font-weight: normal; +} + +/* PDF Download Marker */ +.pdf-downloaded-marker { + display: inline-block; + padding: 4px 10px; + border-radius: 12px; + font-size: 11px; + font-weight: 500; + background-color: #27ae60; + color: white; + margin-left: 5px; +} + +.pdf-not-downloaded-marker { + display: inline-block; + padding: 4px 10px; + border-radius: 12px; + font-size: 11px; + font-weight: 500; + background-color: #e0e0e0; + color: #7f8c8d; + margin-left: 5px; +} + +/* Verwaltung Container - Volle Breite */ +.verwaltung-container { + max-width: 100% !important; + padding: 20px 30px; +} + +/* Verwaltung Panel */ +.verwaltung-panel { + background: white; + padding: 30px 40px; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + width: 100%; +} + +.empty-state { + text-align: center; + padding: 40px; + color: #7f8c8d; +} + +/* Timesheet Groups */ +.timesheet-groups { + display: flex; + flex-direction: column; + gap: 25px; + width: 100%; +} + +.timesheet-group { + border: 1px solid #e0e0e0; + border-radius: 8px; + overflow: hidden; + background-color: #fff; +} + +.group-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 15px 20px; + background-color: #f8f9fa; + border-bottom: 2px solid #e0e0e0; + cursor: pointer; +} + +.group-header:hover { + background-color: #e9ecef; +} + +.group-info { + flex: 1; + display: flex; + flex-direction: column; + gap: 5px; +} + +.group-employee { + font-size: 16px; + color: #2c3e50; +} + +.group-week { + font-size: 14px; + color: #555; +} + +.group-versions-info { + font-size: 12px; + color: #7f8c8d; +} + +.version-count { + display: inline-block; + padding: 2px 8px; + background-color: #3498db; + color: white; + border-radius: 12px; + font-size: 11px; + font-weight: 600; +} + +.toggle-versions-btn { + display: flex; + align-items: center; + gap: 5px; +} + +.toggle-icon { + font-size: 12px; + transition: transform 0.2s ease; +} + +.toggle-versions-btn.active .toggle-icon { + transform: rotate(180deg); +} + +.versions-container { + padding: 0; + background-color: #fff; + width: 100%; + overflow-x: auto; +} + +.versions-table { + margin: 0; + border-top: none; + width: 100%; + table-layout: auto; +} + +.versions-table th, +.versions-table td { + padding: 15px 25px; +} + +.versions-table tbody tr:hover { + background-color: #f8f9fa; +} + +/* Timesheet Table */ +.timesheet-table { + width: 100%; + border-collapse: collapse; + margin-top: 20px; + table-layout: auto; +} + +.timesheet-table th, +.timesheet-table td { + padding: 15px 20px; + text-align: left; + border-bottom: 1px solid #e0e0e0; +} + +.timesheet-table th { + background-color: #f8f9fa; + font-weight: 600; + color: #2c3e50; +} + +.timesheet-table tr:hover { + background-color: #f8f9fa; +} + +/* PDF Preview */ +.pdf-preview-row { + background-color: #f8f9fa; +} + +.pdf-preview-row td { + padding: 0 !important; +} + +.pdf-preview-container { + padding: 20px; + background-color: white; + border-top: 2px solid #3498db; +} + +.pdf-preview-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 15px; + padding-bottom: 15px; + border-bottom: 1px solid #e0e0e0; +} + +.pdf-preview-header h3 { + margin: 0; + color: #2c3e50; + font-size: 18px; +} + +.pdf-viewer-wrapper { + position: relative; + width: 100%; + min-height: 800px; + border: 1px solid #ddd; + border-radius: 4px; + background-color: #f5f5f5; +} + +.pdf-iframe { + width: 100%; + height: 800px; + border: none; + border-radius: 4px; + background-color: white; + display: block; +} + +.pdf-fallback { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + text-align: center; + z-index: 1; +} + +.pdf-iframe:not([src=""]) ~ .pdf-fallback { + display: none; +} + +/* Toggle PDF Button */ +.toggle-pdf-btn { + margin-right: 10px; + display: inline-flex; + align-items: center; + gap: 5px; +} + +.toggle-pdf-btn .arrow-icon { + display: inline-block; + transition: transform 0.3s ease; + font-size: 12px; +} + +.toggle-pdf-btn.active .arrow-icon { + transform: rotate(90deg); +} + +.btn-info { + background-color: #17a2b8; + color: white; +} + +.btn-info:hover { + background-color: #138496; +} + +/* Utility Classes */ +.text-muted { + color: #7f8c8d; +} + +/* Activities/Tätigkeiten */ +.activities-row { + background-color: #f8f9fa; + border-top: 2px solid #e0e0e0; +} + +.activities-cell { + padding: 15px !important; +} + +.activities-form { + display: flex; + flex-direction: column; + gap: 10px; +} + +.activities-header { + margin-bottom: 8px; + color: #2c3e50; + font-size: 14px; +} + +.activity-row { + display: grid; + grid-template-columns: 1fr 150px 120px; + gap: 15px; + align-items: center; +} + +.activity-desc { + display: flex; + align-items: center; +} + +.activity-hours { + display: flex; + align-items: center; + gap: 5px; +} + +.activity-input, +.activity-hours-input { + padding: 8px 10px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 14px; +} + +.activity-input { + width: 100%; +} + +.activity-hours-input { + width: 80px; +} + +.activity-hours-label { + font-size: 14px; + color: #555; +} + +.activity-project { + display: flex; + align-items: center; +} + +.activity-project-input { + padding: 8px 10px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 14px; + width: 100%; +} + +.overtime-vacation-controls { + margin-top: 15px; + padding-top: 15px; + border-top: 1px solid #eee; + display: flex; + gap: 15px; + align-items: center; + flex-wrap: wrap; +} + +.overtime-control, +.vacation-control { + display: flex; + align-items: center; + gap: 5px; +} + +.overtime-input, +.vacation-select { + padding: 6px 10px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 14px; +} + +.user-field-display { + display: inline-block; +} + +.user-field-edit { + display: none; +} + +/* Mitarbeiter-Gruppe (Level 1) */ +.employee-group { + background-color: white; + border: 1px solid #ddd; + border-radius: 8px; + padding: 25px 30px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + margin-bottom: 20px; +} + +.employee-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + padding-bottom: 20px; + border-bottom: 2px solid #3498db; + margin-bottom: 20px; +} + +.employee-info { + flex: 1; +} + +.employee-name { + font-size: 18px; + color: #2c3e50; + margin-bottom: 10px; +} + +.employee-details { + font-size: 14px; + color: #666; +} + +.toggle-employee-btn { + display: flex; + align-items: center; + gap: 5px; +} + +/* Wochen-Container (Level 2) */ +.weeks-container { + margin-top: 20px; + padding-left: 25px; + border-left: 3px solid #e0e0e0; + width: 100%; +} + +.week-group { + background-color: #f8f9fa; + border: 1px solid #e0e0e0; + border-radius: 6px; + padding: 20px 25px; + margin-bottom: 20px; +} + +.week-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + padding-bottom: 15px; + border-bottom: 1px solid #ddd; + margin-bottom: 15px; +} + +.week-info { + flex: 1; +} + +.week-dates { + font-size: 16px; + color: #2c3e50; + margin-bottom: 10px; +} + +.week-versions-info { + font-size: 12px; + color: #7f8c8d; + margin-top: 5px; +} + +/* Responsive */ +@media (max-width: 1024px) { + .dashboard-layout { + flex-direction: column; + } + + .user-stats-panel { + width: 100%; + display: flex; + gap: 15px; + flex-wrap: wrap; + } + + .stat-card { + flex: 1; + min-width: 200px; + margin-bottom: 0; + } +} + +@media (max-width: 768px) { + .dashboard-container { + padding: 15px; + } + + .form-row { + grid-template-columns: 1fr; + } + + .week-selector { + flex-direction: column; + gap: 15px; + } + + table { + font-size: 12px; + } + + table th, + table td { + padding: 8px; + } + + .user-stats-panel { + flex-direction: column; + } + + .stat-card { + width: 100%; + } + + .activity-row { + grid-template-columns: 1fr; + } + + .overtime-vacation-controls { + flex-direction: column; + align-items: flex-start; + } +} diff --git a/public/js/admin.js b/public/js/admin.js new file mode 100644 index 0000000..db21727 --- /dev/null +++ b/public/js/admin.js @@ -0,0 +1,342 @@ +// Admin JavaScript + +document.addEventListener('DOMContentLoaded', function() { + // Benutzer-Formular + const form = document.getElementById('addUserForm'); + + form.addEventListener('submit', async function(e) { + e.preventDefault(); + + // Rollen aus Checkboxen sammeln + const roleCheckboxes = document.querySelectorAll('input[name="roles"]:checked'); + const roles = Array.from(roleCheckboxes).map(cb => cb.value); + + // Validierung: Mindestens eine Rolle muss ausgewählt sein + if (roles.length === 0) { + alert('Bitte wählen Sie mindestens eine Rolle aus.'); + return; + } + + const formData = { + username: document.getElementById('username').value, + password: document.getElementById('password').value, + firstname: document.getElementById('firstname').value, + lastname: document.getElementById('lastname').value, + roles: roles, + personalnummer: document.getElementById('personalnummer').value, + wochenstunden: document.getElementById('wochenstunden').value, + urlaubstage: document.getElementById('urlaubstage').value + }; + + try { + const response = await fetch('/admin/users', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(formData) + }); + + const result = await response.json(); + + if (result.success) { + alert('Benutzer wurde erfolgreich angelegt!'); + location.reload(); + } else { + alert('Fehler: ' + (result.error || 'Benutzer konnte nicht angelegt werden')); + } + } catch (error) { + console.error('Fehler:', error); + alert('Fehler beim Anlegen des Benutzers'); + } + }); + + // LDAP-Konfiguration laden + loadLDAPConfig(); + + // LDAP-Konfigurationsformular + const ldapConfigForm = document.getElementById('ldapConfigForm'); + if (ldapConfigForm) { + ldapConfigForm.addEventListener('submit', async function(e) { + e.preventDefault(); + + const enabled = document.getElementById('ldapEnabled').checked; + const url = document.getElementById('ldapUrl').value; + const baseDn = document.getElementById('ldapBaseDn').value; + + // Validierung: URL und Base DN sind erforderlich wenn aktiviert + if (enabled && (!url || !baseDn)) { + alert('Bitte füllen Sie URL und Base DN aus, wenn LDAP aktiviert ist.'); + return; + } + + const formData = { + enabled: enabled, + url: url, + bind_dn: document.getElementById('ldapBindDn').value, + bind_password: document.getElementById('ldapBindPassword').value, + base_dn: baseDn, + user_search_filter: document.getElementById('ldapSearchFilter').value, + username_attribute: document.getElementById('ldapUsernameAttr').value, + firstname_attribute: document.getElementById('ldapFirstnameAttr').value, + lastname_attribute: document.getElementById('ldapLastnameAttr').value, + sync_interval: parseInt(document.getElementById('ldapSyncInterval').value) || 0 + }; + + try { + const response = await fetch('/admin/ldap/config', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(formData) + }); + + const result = await response.json(); + + if (result.success) { + alert('LDAP-Konfiguration wurde erfolgreich gespeichert!'); + location.reload(); + } else { + alert('Fehler: ' + (result.error || 'Konfiguration konnte nicht gespeichert werden')); + } + } catch (error) { + console.error('Fehler:', error); + alert('Fehler beim Speichern der Konfiguration'); + } + }); + } + + // Sync-Button + const syncNowBtn = document.getElementById('syncNowBtn'); + if (syncNowBtn) { + syncNowBtn.addEventListener('click', async function() { + if (!confirm('Möchten Sie die LDAP-Synchronisation jetzt starten?')) { + return; + } + + const statusEl = document.getElementById('syncStatus'); + syncNowBtn.disabled = true; + statusEl.textContent = 'Synchronisation läuft...'; + statusEl.style.color = 'blue'; + + try { + const response = await fetch('/admin/ldap/sync', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }); + + const result = await response.json(); + + if (result.success) { + statusEl.textContent = `Erfolgreich: ${result.synced} Benutzer synchronisiert`; + statusEl.style.color = 'green'; + + if (result.errors && result.errors.length > 0) { + alert('Synchronisation abgeschlossen mit Warnungen:\n' + result.errors.join('\n')); + } else { + alert(`Synchronisation erfolgreich abgeschlossen: ${result.synced} Benutzer synchronisiert`); + } + + // Seite neu laden nach kurzer Verzögerung + setTimeout(() => { + location.reload(); + }, 2000); + } else { + statusEl.textContent = 'Fehler: ' + (result.error || 'Synchronisation fehlgeschlagen'); + statusEl.style.color = 'red'; + alert('Fehler: ' + (result.error || 'Synchronisation fehlgeschlagen')); + syncNowBtn.disabled = false; + } + } catch (error) { + console.error('Fehler:', error); + statusEl.textContent = 'Fehler bei der Synchronisation'; + statusEl.style.color = 'red'; + alert('Fehler bei der Synchronisation'); + syncNowBtn.disabled = false; + } + }); + } +}); + +// LDAP-Konfiguration laden und Formular ausfüllen +async function loadLDAPConfig() { + try { + const response = await fetch('/admin/ldap/config'); + const result = await response.json(); + + if (result.config) { + const config = result.config; + + if (document.getElementById('ldapEnabled')) { + document.getElementById('ldapEnabled').checked = config.enabled === 1; + } + if (document.getElementById('ldapUrl')) { + document.getElementById('ldapUrl').value = config.url || ''; + } + if (document.getElementById('ldapBaseDn')) { + document.getElementById('ldapBaseDn').value = config.base_dn || ''; + } + if (document.getElementById('ldapBindDn')) { + document.getElementById('ldapBindDn').value = config.bind_dn || ''; + } + if (document.getElementById('ldapSearchFilter')) { + document.getElementById('ldapSearchFilter').value = config.user_search_filter || '(objectClass=person)'; + } + if (document.getElementById('ldapUsernameAttr')) { + document.getElementById('ldapUsernameAttr').value = config.username_attribute || 'cn'; + } + if (document.getElementById('ldapFirstnameAttr')) { + document.getElementById('ldapFirstnameAttr').value = config.firstname_attribute || 'givenName'; + } + if (document.getElementById('ldapLastnameAttr')) { + document.getElementById('ldapLastnameAttr').value = config.lastname_attribute || 'sn'; + } + if (document.getElementById('ldapSyncInterval')) { + document.getElementById('ldapSyncInterval').value = config.sync_interval || 0; + } + } + } catch (error) { + console.error('Fehler beim Laden der LDAP-Konfiguration:', error); + } +} + +async function deleteUser(userId, username) { + const confirmed = confirm(`Möchten Sie den Benutzer "${username}" wirklich löschen?`); + + if (!confirmed) return; + + try { + const response = await fetch(`/admin/users/${userId}`, { + method: 'DELETE' + }); + + const result = await response.json(); + + if (result.success) { + alert('Benutzer wurde erfolgreich gelöscht!'); + location.reload(); + } else { + alert('Fehler: ' + (result.error || 'Benutzer konnte nicht gelöscht werden')); + } + } catch (error) { + console.error('Fehler:', error); + alert('Fehler beim Löschen des Benutzers'); + } +} + +// User bearbeiten +function editUser(userId) { + const row = document.querySelector(`tr[data-user-id="${userId}"]`); + if (!row) return; + + // Alle Display-Felder ausblenden und Edit-Felder einblenden + row.querySelectorAll('.user-field-display').forEach(display => { + display.style.display = 'none'; + }); + row.querySelectorAll('.user-field-edit').forEach(edit => { + edit.style.display = 'inline-block'; + }); + + // Buttons umschalten + row.querySelector('.edit-user-btn').style.display = 'none'; + row.querySelector('.save-user-btn').style.display = 'inline-block'; + row.querySelector('.cancel-user-btn').style.display = 'inline-block'; +} + +// User speichern +async function saveUser(userId) { + const row = document.querySelector(`tr[data-user-id="${userId}"]`); + if (!row) return; + + const personalnummer = row.querySelector('input[data-field="personalnummer"]').value; + const wochenstunden = row.querySelector('input[data-field="wochenstunden"]').value; + const urlaubstage = row.querySelector('input[data-field="urlaubstage"]').value; + + // Rollen aus Checkboxen sammeln + const roleCheckboxes = row.querySelectorAll('.role-checkbox:checked'); + const roles = Array.from(roleCheckboxes).map(cb => cb.value); + + // Validierung: Mindestens eine Rolle erforderlich + if (roles.length === 0) { + alert('Mindestens eine Rolle muss ausgewählt sein.'); + return; + } + + try { + const response = await fetch(`/admin/users/${userId}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + personalnummer: personalnummer || null, + wochenstunden: wochenstunden || null, + urlaubstage: urlaubstage || null, + roles: roles + }) + }); + + const result = await response.json(); + + if (result.success) { + // Werte in Display-Felder übernehmen + row.querySelector('span[data-field="personalnummer"]').textContent = personalnummer || '-'; + row.querySelector('span[data-field="wochenstunden"]').textContent = wochenstunden || '-'; + row.querySelector('span[data-field="urlaubstage"]').textContent = urlaubstage || '-'; + + // Rollen-Display aktualisieren + const rolesDisplay = row.querySelector('div[data-field="roles"]'); + if (rolesDisplay) { + const roleLabels = { 'mitarbeiter': 'Mitarbeiter', 'verwaltung': 'Verwaltung', 'admin': 'Admin' }; + rolesDisplay.innerHTML = roles.map(role => + `${roleLabels[role] || role}` + ).join(''); + } + + // Bearbeitung beenden + cancelEditUser(userId); + alert('Benutzerdaten wurden erfolgreich gespeichert!'); + // Seite neu laden um sicherzustellen dass alles korrekt ist + location.reload(); + } else { + alert('Fehler: ' + (result.error || 'Daten konnten nicht gespeichert werden')); + } + } catch (error) { + console.error('Fehler:', error); + alert('Fehler beim Speichern der Benutzerdaten'); + } +} + +// Bearbeitung abbrechen +function cancelEditUser(userId) { + const row = document.querySelector(`tr[data-user-id="${userId}"]`); + if (!row) return; + + // Alle Edit-Felder ausblenden und Display-Felder einblenden + row.querySelectorAll('.user-field-edit').forEach(edit => { + edit.style.display = 'none'; + // Wert zurücksetzen (nur für Input-Felder, nicht für Rollen) + const field = edit.dataset.field; + if (field !== 'roles') { + const display = row.querySelector(`span[data-field="${field}"]`); + if (display && edit.tagName === 'INPUT') { + edit.value = display.textContent === '-' ? '' : display.textContent; + } + } + }); + row.querySelectorAll('.user-field-display').forEach(display => { + if (display.tagName === 'DIV' || display.tagName === 'SPAN') { + display.style.display = 'block'; + } else { + display.style.display = 'inline'; + } + }); + + // Buttons umschalten + row.querySelector('.edit-user-btn').style.display = 'inline-block'; + row.querySelector('.save-user-btn').style.display = 'none'; + row.querySelector('.cancel-user-btn').style.display = 'none'; +} diff --git a/public/js/dashboard.js b/public/js/dashboard.js new file mode 100644 index 0000000..020328d --- /dev/null +++ b/public/js/dashboard.js @@ -0,0 +1,1107 @@ +// Dashboard JavaScript + +let currentWeekStart = getMonday(new Date()); +let currentEntries = {}; +let userWochenstunden = 0; // Wochenstunden des Users + +// Statistiken laden +async function loadUserStats() { + try { + const response = await fetch('/api/user/stats'); + if (!response.ok) { + throw new Error('Fehler beim Laden der Statistiken'); + } + const stats = await response.json(); + + // Überstunden anzeigen + const currentOvertimeEl = document.getElementById('currentOvertime'); + if (currentOvertimeEl) { + const overtime = stats.currentOvertime || 0; + currentOvertimeEl.textContent = overtime >= 0 ? `+${overtime.toFixed(2)}` : overtime.toFixed(2); + currentOvertimeEl.style.color = overtime >= 0 ? '#27ae60' : '#e74c3c'; + // Auch die Border-Farbe des Cards anpassen + const overtimeCard = currentOvertimeEl.closest('.stat-card'); + if (overtimeCard) { + overtimeCard.style.borderLeftColor = overtime >= 0 ? '#27ae60' : '#e74c3c'; + } + } + + // Urlaubstage anzeigen + const remainingVacationEl = document.getElementById('remainingVacation'); + if (remainingVacationEl) { + remainingVacationEl.textContent = (stats.remainingVacation || 0).toFixed(1); + } + + const totalVacationEl = document.getElementById('totalVacation'); + if (totalVacationEl) { + totalVacationEl.textContent = (stats.urlaubstage || 0).toFixed(1); + } + } catch (error) { + console.error('Fehler beim Laden der Statistiken:', error); + // Fehlerbehandlung: Zeige "-" oder "Fehler" + const currentOvertimeEl = document.getElementById('currentOvertime'); + if (currentOvertimeEl) currentOvertimeEl.textContent = '-'; + const remainingVacationEl = document.getElementById('remainingVacation'); + if (remainingVacationEl) remainingVacationEl.textContent = '-'; + const totalVacationEl = document.getElementById('totalVacation'); + if (totalVacationEl) totalVacationEl.textContent = '-'; + } +} + +// Beim Laden der Seite +document.addEventListener('DOMContentLoaded', async function() { + // Letzte Woche vom Server laden + try { + const response = await fetch('/api/user/last-week'); + const data = await response.json(); + if (data.last_week_start) { + currentWeekStart = data.last_week_start; + } + } catch (error) { + console.warn('Konnte letzte Woche nicht vom Server laden:', error); + } + + // Statistiken laden + loadUserStats(); + + loadWeek(); + + document.getElementById('prevWeek').addEventListener('click', function() { + const date = new Date(currentWeekStart); + date.setDate(date.getDate() - 7); + currentWeekStart = formatDate(date); + saveLastWeek(); + loadWeek(); + }); + + document.getElementById('nextWeek').addEventListener('click', function() { + const date = new Date(currentWeekStart); + date.setDate(date.getDate() + 7); + currentWeekStart = formatDate(date); + saveLastWeek(); + loadWeek(); + }); + + // Event-Listener für Submit-Button - mit Event-Delegation für bessere Zuverlässigkeit + document.addEventListener('click', function(e) { + if (e.target && e.target.id === 'submitWeek') { + e.preventDefault(); + e.stopPropagation(); + const submitButton = e.target; + console.log('Submit-Button wurde geklickt!'); + console.log('Button disabled?', submitButton.disabled); + console.log('Button element:', submitButton); + + if (!submitButton.disabled) { + submitWeek(); + } else { + console.warn('Submit-Button ist deaktiviert!'); + const tooltip = submitButton.title || 'Bitte füllen Sie alle Werktage aus.'; + alert(tooltip); + } + } + }); + + // Zusätzlicher direkter Event-Listener als Fallback + const submitButton = document.getElementById('submitWeek'); + if (submitButton) { + submitButton.onclick = function(e) { + e.preventDefault(); + e.stopPropagation(); + console.log('Submit-Button onclick wurde ausgelöst!'); + if (!this.disabled) { + submitWeek(); + } + return false; + }; + console.log('Submit-Button Event-Listener gesetzt'); + } else { + console.error('Submit-Button nicht gefunden beim Initialisieren!'); + } +}); + +// Letzte Woche auf dem Server speichern +async function saveLastWeek() { + try { + await fetch('/api/user/last-week', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + week_start: currentWeekStart + }) + }); + } catch (error) { + console.warn('Konnte letzte Woche nicht auf dem Server speichern:', error); + } +} + +// Montag der aktuellen Woche ermitteln +function getMonday(date) { + const d = new Date(date); + const day = d.getDay(); + const diff = d.getDate() - day + (day === 0 ? -6 : 1); + d.setDate(diff); + return formatDate(d); +} + +// Kalenderwoche berechnen (ISO 8601 - Woche beginnt Montag) +function getCalendarWeek(dateStr) { + const date = new Date(dateStr); + const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())); + const dayNum = d.getUTCDay() || 7; + d.setUTCDate(d.getUTCDate() + 4 - dayNum); + const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1)); + const weekNo = Math.ceil((((d - yearStart) / 86400000) + 1) / 7); + return weekNo; +} + +// Datum formatieren (YYYY-MM-DD) +function formatDate(date) { + const d = new Date(date); + return d.toISOString().split('T')[0]; +} + +// Datum formatieren (DD.MM.YYYY) +function formatDateDE(dateStr) { + const d = new Date(dateStr); + return d.toLocaleDateString('de-DE'); +} + +// Wochentag ermitteln +function getWeekday(dateStr) { + const days = ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag']; + const d = new Date(dateStr); + return days[d.getDay()]; +} + +// Woche laden +async function loadWeek() { + try { + // User-Daten laden (Wochenstunden) + try { + const userResponse = await fetch('/api/user/data'); + const userData = await userResponse.json(); + userWochenstunden = userData.wochenstunden || 0; + } catch (error) { + console.warn('Konnte User-Daten nicht laden:', error); + userWochenstunden = 0; + } + + const response = await fetch(`/api/timesheet/week/${currentWeekStart}`); + const entries = await response.json(); + + // Entries in Object umwandeln für schnellen Zugriff + currentEntries = {}; + let weekIsSubmitted = false; + + entries.forEach(entry => { + currentEntries[entry.date] = entry; + // Prüfe ob die Woche bereits eingereicht wurde + if (entry.week_submitted) { + weekIsSubmitted = true; + } + }); + + // Speichere den Status ob die Woche bereits eingereicht wurde + currentEntries._weekSubmitted = weekIsSubmitted; + // Speichere ob bereits eine Version existiert (für Grund-Validierung) + if (entries.length > 0) { + currentEntries._hasExistingVersion = entries[0].has_existing_version || false; + currentEntries._latestVersion = entries[0].latest_version || 0; + } + + renderWeek(); + } catch (error) { + console.error('Fehler beim Laden der Woche:', error); + alert('Fehler beim Laden der Daten'); + } +} + +// Woche rendern +function renderWeek() { + const startDate = new Date(currentWeekStart); + const endDate = new Date(startDate); + endDate.setDate(endDate.getDate() + 6); + + const calendarWeek = getCalendarWeek(currentWeekStart); + document.getElementById('weekTitle').innerHTML = + `Kalenderwoche ${calendarWeek}
${formatDateDE(currentWeekStart)} - ${formatDateDE(formatDate(endDate))}`; + + let html = ` +
+ + + + + + + + + + + + + `; + + let totalHours = 0; + let allWeekdaysFilled = true; + + // 7 Tage (Montag bis Sonntag) + for (let i = 0; i < 7; i++) { + const date = new Date(startDate); + date.setDate(date.getDate() + i); + const dateStr = formatDate(date); + const entry = currentEntries[dateStr] || {}; + + const startTime = entry.start_time || ''; + const endTime = entry.end_time || ''; + const breakMinutes = entry.break_minutes || 0; + const hours = entry.total_hours || 0; + const overtimeTaken = entry.overtime_taken_hours || ''; + const vacationType = entry.vacation_type || ''; + + // Tätigkeiten laden + const activities = [ + { desc: entry.activity1_desc || '', hours: entry.activity1_hours || 0, projectNumber: entry.activity1_project_number || '' }, + { desc: entry.activity2_desc || '', hours: entry.activity2_hours || 0, projectNumber: entry.activity2_project_number || '' }, + { desc: entry.activity3_desc || '', hours: entry.activity3_hours || 0, projectNumber: entry.activity3_project_number || '' }, + { desc: entry.activity4_desc || '', hours: entry.activity4_hours || 0, projectNumber: entry.activity4_project_number || '' }, + { desc: entry.activity5_desc || '', hours: entry.activity5_hours || 0, projectNumber: entry.activity5_project_number || '' } + ]; + + // Prüfen ob Werktag (Montag-Freitag, i < 5) ausgefüllt ist + // Bei ganztägigem Urlaub gilt der Tag als ausgefüllt + if (i < 5 && vacationType !== 'full' && (!startTime || !endTime || startTime.trim() === '' || endTime.trim() === '')) { + allWeekdaysFilled = false; + } + + // Stunden zur Summe hinzufügen + // Bei ganztägigem Urlaub sollten es bereits 8 Stunden sein (vom Backend gesetzt) + // Bei halbem Tag Urlaub werden die Urlaubsstunden später in der Überstunden-Berechnung hinzugezählt + totalHours += hours; + + // Bearbeitung ist immer möglich, auch nach Abschicken + // Bei ganztägigem Urlaub werden Zeitfelder deaktiviert + const isFullDayVacation = vacationType === 'full'; + const timeFieldsDisabled = isFullDayVacation ? 'disabled' : ''; + const disabled = ''; + + html += ` + + + + + + + + + + + + `; + } + + html += ` + +
TagDatumStartEndePause (Min)Stunden
${getWeekday(dateStr)}${formatDateDE(dateStr)}${isFullDayVacation ? ' (Urlaub - ganzer Tag)' : ''} + + + + + + ${isFullDayVacation ? '8.00 h (Urlaub)' : hours.toFixed(2) + ' h'}
+
+
Tätigkeiten:
+ ${activities.map((activity, idx) => ` +
+
+ +
+
+ +
+
+ + h +
+
+ `).join('')} +
+
+
+ +
+ + h +
+
+
+ +
+ +
+
+
+
+
+ `; + + document.getElementById('timesheetTable').innerHTML = html; + document.getElementById('totalHours').textContent = totalHours.toFixed(2) + ' h'; + + // Überstunden-Berechnung (startDate und endDate sind bereits oben deklariert) + + // Anzahl Werktage berechnen (Montag-Freitag) + let workdays = 0; + for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) { + const day = d.getDay(); + if (day >= 1 && day <= 5) { // Montag bis Freitag + workdays++; + } + } + + // Sollstunden berechnen + const sollStunden = (userWochenstunden / 5) * workdays; + + // Urlaubsstunden berechnen (Urlaub zählt als normale Arbeitszeit) + let vacationHours = 0; + Object.values(currentEntries).forEach(e => { + if (e.vacation_type === 'full') { + vacationHours += 8; // Ganzer Tag = 8 Stunden + } else if (e.vacation_type === 'half') { + vacationHours += 4; // Halber Tag = 4 Stunden + } + }); + + // Überstunden berechnen + let overtimeTaken = 0; + Object.values(currentEntries).forEach(e => { + if (e.overtime_taken_hours) { + overtimeTaken += parseFloat(e.overtime_taken_hours) || 0; + } + }); + + // Überstunden-Anzeige erstellen (falls noch nicht vorhanden) + let overtimeDisplay = document.getElementById('overtimeDisplay'); + if (!overtimeDisplay) { + const summaryDiv = document.querySelector('.summary'); + overtimeDisplay = document.createElement('div'); + overtimeDisplay.id = 'overtimeDisplay'; + overtimeDisplay.className = 'summary-item'; + summaryDiv.appendChild(overtimeDisplay); + } + + // Überstunden-Berechnung aufrufen + updateOvertimeDisplay(); + + // Nach dem Rendern die vollständige Validierung durchführen + // Dies prüft auch direkt die Input-Felder im DOM + checkWeekComplete(); +} + +// Überstunden-Anzeige aktualisieren (wird bei jeder Änderung aufgerufen) +function updateOvertimeDisplay() { + const startDate = new Date(currentWeekStart); + const endDate = new Date(startDate); + endDate.setDate(endDate.getDate() + 6); + + // Anzahl Werktage berechnen (Montag-Freitag) + let workdays = 0; + for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) { + const day = d.getDay(); + if (day >= 1 && day <= 5) { // Montag bis Freitag + workdays++; + } + } + + // Sollstunden berechnen + const sollStunden = (userWochenstunden / 5) * workdays; + + // Gesamtstunden berechnen - direkt aus DOM-Elementen lesen für Echtzeit-Aktualisierung + let totalHours = 0; + const startDateObj = new Date(startDate); + for (let i = 0; i < 7; i++) { + const date = new Date(startDateObj); + date.setDate(date.getDate() + i); + const dateStr = formatDate(date); + + // Prüfe Urlaub-Status + const vacationSelect = document.querySelector(`select[data-date="${dateStr}"][data-field="vacation_type"]`); + const vacationType = vacationSelect ? vacationSelect.value : (currentEntries[dateStr]?.vacation_type || ''); + + if (vacationType === 'full') { + totalHours += 8; // Ganzer Tag Urlaub = 8 Stunden + } else { + // Berechne Stunden direkt aus Start-/Endzeit und Pause + const startInput = document.querySelector(`input[data-date="${dateStr}"][data-field="start_time"]`); + const endInput = document.querySelector(`input[data-date="${dateStr}"][data-field="end_time"]`); + const breakInput = document.querySelector(`input[data-date="${dateStr}"][data-field="break_minutes"]`); + + const startTime = startInput ? startInput.value : ''; + const endTime = endInput ? endInput.value : ''; + const breakMinutes = parseInt(breakInput ? breakInput.value : 0) || 0; + + if (startTime && endTime) { + const start = new Date(`2000-01-01T${startTime}`); + const end = new Date(`2000-01-01T${endTime}`); + const diffMs = end - start; + const hours = (diffMs / (1000 * 60 * 60)) - (breakMinutes / 60); + totalHours += hours; + } else if (currentEntries[dateStr]?.total_hours) { + // Fallback auf gespeicherte Werte + totalHours += parseFloat(currentEntries[dateStr].total_hours) || 0; + } + } + } + + // Urlaubsstunden berechnen (Urlaub zählt als normale Arbeitszeit) + let vacationHours = 0; + const startDateObj2 = new Date(startDate); + for (let i = 0; i < 7; i++) { + const date = new Date(startDateObj2); + date.setDate(date.getDate() + i); + const dateStr = formatDate(date); + + const vacationSelect = document.querySelector(`select[data-date="${dateStr}"][data-field="vacation_type"]`); + const vacationType = vacationSelect ? vacationSelect.value : (currentEntries[dateStr]?.vacation_type || ''); + + if (vacationType === 'full') { + vacationHours += 8; // Ganzer Tag = 8 Stunden + } else if (vacationType === 'half') { + vacationHours += 4; // Halber Tag = 4 Stunden + } + } + + // Genommene Überstunden berechnen - direkt aus DOM lesen + let overtimeTaken = 0; + const startDateObj3 = new Date(startDate); + for (let i = 0; i < 7; i++) { + const date = new Date(startDateObj3); + date.setDate(date.getDate() + i); + const dateStr = formatDate(date); + + const overtimeInput = document.querySelector(`input[data-date="${dateStr}"][data-field="overtime_taken_hours"]`); + if (overtimeInput && overtimeInput.value) { + overtimeTaken += parseFloat(overtimeInput.value) || 0; + } else if (currentEntries[dateStr]?.overtime_taken_hours) { + overtimeTaken += parseFloat(currentEntries[dateStr].overtime_taken_hours) || 0; + } + } + + // Überstunden = (Tatsächliche Stunden + Urlaubsstunden) - Sollstunden + const totalHoursWithVacation = totalHours + vacationHours; + const overtimeHours = totalHoursWithVacation - sollStunden; + const remainingOvertime = overtimeHours - overtimeTaken; + + // Überstunden-Anzeige aktualisieren + const overtimeDisplay = document.getElementById('overtimeDisplay'); + if (overtimeDisplay) { + overtimeDisplay.innerHTML = ` + Überstunden diese Woche: + ${overtimeHours.toFixed(2)} h + ${overtimeTaken > 0 ? `
Davon genommen: ${overtimeTaken.toFixed(2)} h` : ''} + ${remainingOvertime !== overtimeHours ? `
Verbleibend: ${remainingOvertime.toFixed(2)} h` : ''} + `; + } +} + +// Eintrag speichern +async function saveEntry(input) { + const date = input.dataset.date; + const field = input.dataset.field; + const value = input.value; + + // Entferne Fehlermarkierung wenn Feld ausgefüllt wird + if (input.classList.contains('missing-field')) { + input.classList.remove('missing-field'); + input.style.borderColor = ''; + input.style.backgroundColor = ''; + } + + // Aktualisiere currentEntries + if (!currentEntries[date]) { + currentEntries[date] = { date }; + } + currentEntries[date][field] = value; + + // Lese alle aktuellen Werte direkt aus dem DOM, nicht nur aus currentEntries + // Das stellt sicher, dass auch Werte gespeichert werden, die noch nicht in currentEntries sind + // WICHTIG: Wenn das aktuelle Input-Element das Feld ist, das wir suchen, verwende direkt dessen Wert + const startInput = document.querySelector(`input[data-date="${date}"][data-field="start_time"]`); + const endInput = document.querySelector(`input[data-date="${date}"][data-field="end_time"]`); + const breakInput = document.querySelector(`input[data-date="${date}"][data-field="break_minutes"]`); + const notesInput = document.querySelector(`textarea[data-date="${date}"][data-field="notes"]`); + const vacationSelect = document.querySelector(`select[data-date="${date}"][data-field="vacation_type"]`); + const overtimeInput = document.querySelector(`input[data-date="${date}"][data-field="overtime_taken_hours"]`); + + // Wenn das aktuelle Input-Element das gesuchte Feld ist, verwende dessen Wert direkt + // Das stellt sicher, dass der Wert auch bei oninput/onchange sofort verfügbar ist + const actualStartTime = (input.dataset.field === 'start_time' && input.value) ? input.value : + (startInput && startInput.value && startInput.value.trim() !== '') ? startInput.value : + (currentEntries[date].start_time || null); + const actualEndTime = (input.dataset.field === 'end_time' && input.value) ? input.value : + (endInput && endInput.value && endInput.value.trim() !== '') ? endInput.value : + (currentEntries[date].end_time || null); + + // Aktuelle Werte aus DOM lesen (falls vorhanden), sonst aus currentEntries + // Wichtig: Leere Strings werden zu null konvertiert, aber ein Wert sollte vorhanden sein + const start_time = actualStartTime; + const end_time = actualEndTime; + const break_minutes = breakInput && breakInput.value ? (parseInt(breakInput.value) || 0) : (parseInt(currentEntries[date].break_minutes) || 0); + const notes = notesInput ? (notesInput.value || '') : (currentEntries[date].notes || ''); + const vacation_type = vacationSelect && vacationSelect.value ? vacationSelect.value : (currentEntries[date].vacation_type || null); + const overtime_taken_hours = overtimeInput && overtimeInput.value ? overtimeInput.value : (currentEntries[date].overtime_taken_hours || null); + + // Activity-Felder aus DOM lesen + const activities = []; + for (let i = 1; i <= 5; i++) { + const descInput = document.querySelector(`input[data-date="${date}"][data-field="activity${i}_desc"]`); + const hoursInput = document.querySelector(`input[data-date="${date}"][data-field="activity${i}_hours"]`); + const projectInput = document.querySelector(`input[data-date="${date}"][data-field="activity${i}_project_number"]`); + + activities.push({ + desc: descInput ? (descInput.value || null) : (currentEntries[date][`activity${i}_desc`] || null), + hours: hoursInput ? (parseFloat(hoursInput.value) || 0) : (parseFloat(currentEntries[date][`activity${i}_hours`]) || 0), + projectNumber: projectInput ? (projectInput.value || null) : (currentEntries[date][`activity${i}_project_number`] || null) + }); + } + + // Aktualisiere currentEntries mit den DOM-Werten + currentEntries[date].start_time = start_time; + currentEntries[date].end_time = end_time; + currentEntries[date].break_minutes = break_minutes; + currentEntries[date].notes = notes; + currentEntries[date].vacation_type = vacation_type; + currentEntries[date].overtime_taken_hours = overtime_taken_hours; + for (let i = 1; i <= 5; i++) { + currentEntries[date][`activity${i}_desc`] = activities[i-1].desc; + currentEntries[date][`activity${i}_hours`] = activities[i-1].hours; + currentEntries[date][`activity${i}_project_number`] = activities[i-1].projectNumber; + } + + const entry = currentEntries[date]; + + + try { + const response = await fetch('/api/timesheet/save', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + date: date, + start_time: start_time, + end_time: end_time, + break_minutes: break_minutes, + notes: notes, + activity1_desc: activities[0].desc, + activity1_hours: activities[0].hours, + activity1_project_number: activities[0].projectNumber, + activity2_desc: activities[1].desc, + activity2_hours: activities[1].hours, + activity2_project_number: activities[1].projectNumber, + activity3_desc: activities[2].desc, + activity3_hours: activities[2].hours, + activity3_project_number: activities[2].projectNumber, + activity4_desc: activities[3].desc, + activity4_hours: activities[3].hours, + activity4_project_number: activities[3].projectNumber, + activity5_desc: activities[4].desc, + activity5_hours: activities[4].hours, + activity5_project_number: activities[4].projectNumber, + overtime_taken_hours: overtime_taken_hours, + vacation_type: vacation_type + }) + }); + + const result = await response.json(); + + if (result.success) { + // Aktualisiere Stunden-Anzeige + const hoursElement = document.getElementById(`hours_${date}`); + if (hoursElement && result.total_hours !== undefined) { + hoursElement.textContent = result.total_hours.toFixed(2) + ' h'; + currentEntries[date].total_hours = result.total_hours; + } + + // Gesamtstunden neu berechnen + let totalHours = 0; + Object.values(currentEntries).forEach(e => { + totalHours += e.total_hours || 0; + }); + document.getElementById('totalHours').textContent = totalHours.toFixed(2) + ' h'; + + // Überstunden-Anzeige aktualisieren (bei jeder Änderung) + updateOvertimeDisplay(); + + // Submit-Button Status prüfen (nach jedem Speichern) + checkWeekComplete(); + + // Visuelles Feedback + input.style.backgroundColor = '#d4edda'; + setTimeout(() => { + input.style.backgroundColor = ''; + }, 500); + + // Statistiken aktualisieren (nur wenn es sich um eingereichte Wochen handelt) + // Wir aktualisieren die Statistiken nicht bei jedem Speichern, da sie nur für eingereichte Wochen relevant sind + // Die Statistiken werden beim Laden der Seite und nach dem Abschicken aktualisiert + } + } catch (error) { + console.error('Fehler beim Speichern:', error); + alert('Fehler beim Speichern'); + } +} + +// Prüfen ob alle Werktage der Woche ausgefüllt sind +function checkWeekComplete() { + const startDate = new Date(currentWeekStart); + let allWeekdaysFilled = true; + const missingFields = []; + + // Prüfe nur Werktage (Montag-Freitag, i < 5) + // Samstag und Sonntag (i >= 5) sind optional + for (let i = 0; i < 5; i++) { + const date = new Date(startDate); + date.setDate(date.getDate() + i); + const dateStr = formatDate(date); + + // Prüfe Urlaub-Status + const entry = currentEntries[dateStr] || {}; + const vacationType = entry.vacation_type; + const vacationSelect = document.querySelector(`select[data-date="${dateStr}"][data-field="vacation_type"]`); + const vacationValue = vacationSelect ? vacationSelect.value : (vacationType || ''); + + // Wenn ganzer Tag Urlaub, dann ist der Tag als ausgefüllt zu betrachten + if (vacationValue === 'full') { + continue; // Tag ist ausgefüllt (ganzer Tag Urlaub) + } + + // Prüfe IMMER direkt die Input-Felder im DOM (das ist die zuverlässigste Quelle) + // Auch bei manueller Eingabe werden die Werte hier erkannt + const startInput = document.querySelector(`input[data-date="${dateStr}"][data-field="start_time"]`); + const endInput = document.querySelector(`input[data-date="${dateStr}"][data-field="end_time"]`); + + // Hole die Werte direkt aus den Input-Feldern + const startTime = startInput ? (startInput.value || '').trim() : ''; + const endTime = endInput ? (endInput.value || '').trim() : ''; + + // Debug-Ausgabe - zeigt auch den tatsächlichen Wert im Input-Feld + console.log(`Tag ${i + 1} (${dateStr}): Start="${startTime || 'LEER'}", Ende="${endTime || 'LEER'}", Urlaub="${vacationValue || 'KEIN'}"`, { + startInputExists: !!startInput, + endInputExists: !!endInput, + startInputValue: startInput ? startInput.value : 'N/A', + endInputValue: endInput ? endInput.value : 'N/A', + vacationValue: vacationValue + }); + + // Bei halbem Tag Urlaub oder keinem Urlaub müssen Start- und Endzeit vorhanden sein + if (!startTime || !endTime || startTime === '' || endTime === '') { + allWeekdaysFilled = false; + missingFields.push(formatDateDE(dateStr)); + } + } + + // Prüfe ob die Woche bereits eingereicht wurde (nicht der Status einzelner Einträge!) + const weekIsSubmitted = currentEntries._weekSubmitted === true; + const submitButton = document.getElementById('submitWeek'); + if (submitButton) { + submitButton.disabled = weekIsSubmitted || !allWeekdaysFilled; + + if (weekIsSubmitted) { + submitButton.title = 'Diese Woche wurde bereits eingereicht und kann nicht mehr geändert werden.'; + } else if (!allWeekdaysFilled) { + submitButton.title = `Bitte füllen Sie alle Werktage (Montag bis Freitag) aus (Start- und Endzeit). Wochenende ist optional. Fehlend: ${missingFields.join(', ')}`; + } else { + submitButton.title = ''; + } + + console.log(`Submit-Button Status: disabled=${submitButton.disabled}, allWeekdaysFilled=${allWeekdaysFilled}, weekIsSubmitted=${weekIsSubmitted}`); + } +} + +// Globaler Handler für onclick-Attribut (im HTML) +window.submitWeekHandler = function(e) { + e.preventDefault(); + e.stopPropagation(); + console.log('submitWeekHandler wurde aufgerufen'); + const submitButton = document.getElementById('submitWeek'); + + // Auch wenn der Button disabled ist, versuchen wir zu prüfen was fehlt + if (submitButton) { + if (!submitButton.disabled) { + submitWeek(); + } else { + // Button ist disabled - zeige was fehlt + console.warn('Button ist disabled - zeige fehlende Felder'); + // Rufe submitWeek auf, um die Validierung durchzuführen (wird wegen fehlender Felder abgebrochen) + submitWeek(); + } + } else { + console.error('Button nicht gefunden'); + alert('Fehler: Button nicht gefunden'); + } + return false; +}; + +// Woche abschicken +async function submitWeek() { + console.log('submitWeek() wurde aufgerufen'); + const startDate = new Date(currentWeekStart); + const endDate = new Date(startDate); + endDate.setDate(endDate.getDate() + 6); + + console.log('Prüfe Validierung für Woche:', currentWeekStart); + + // Frontend-Validierung: Prüfen ob alle Werktage (Montag-Freitag) ausgefüllt sind + let missingFields = []; + let firstMissingInput = null; + + // Entferne vorherige Fehlermarkierungen + document.querySelectorAll('.missing-field').forEach(el => { + el.classList.remove('missing-field'); + el.style.borderColor = ''; + el.style.backgroundColor = ''; + }); + + for (let i = 0; i < 5; i++) { + const date = new Date(startDate); + date.setDate(date.getDate() + i); + const dateStr = formatDate(date); + const weekday = getWeekday(dateStr); + const dateDisplay = formatDateDE(dateStr); + + // Prüfe Urlaub-Status + const entry = currentEntries[dateStr] || {}; + const vacationSelect = document.querySelector(`select[data-date="${dateStr}"][data-field="vacation_type"]`); + const vacationValue = vacationSelect ? vacationSelect.value : (entry.vacation_type || ''); + + // Wenn ganzer Tag Urlaub, dann ist der Tag als ausgefüllt zu betrachten + if (vacationValue === 'full') { + continue; // Tag ist ausgefüllt (ganzer Tag Urlaub) + } + + // Prüfe IMMER direkt die Input-Felder im DOM - auch bei manueller Eingabe + const startInput = document.querySelector(`input[data-date="${dateStr}"][data-field="start_time"]`); + const endInput = document.querySelector(`input[data-date="${dateStr}"][data-field="end_time"]`); + + // Hole die Werte direkt aus den Input-Feldern (nicht aus currentEntries!) + const startTime = startInput ? (startInput.value || '').trim() : ''; + const endTime = endInput ? (endInput.value || '').trim() : ''; + + // Debug-Ausgabe mit detaillierten Informationen + console.log(`Tag ${i + 1} (${dateStr}): Start="${startTime || 'LEER'}", Ende="${endTime || 'LEER'}", Urlaub="${vacationValue || 'KEIN'}"`, { + startInputExists: !!startInput, + endInputExists: !!endInput, + startInputValue: startInput ? `"${startInput.value}"` : 'N/A', + endInputValue: endInput ? `"${endInput.value}"` : 'N/A', + startInputType: startInput ? typeof startInput.value : 'N/A', + vacationValue: vacationValue + }); + + const missing = []; + if (!startTime || startTime === '') { + missing.push('Startzeit'); + if (startInput) { + startInput.classList.add('missing-field'); + startInput.style.borderColor = '#dc3545'; + startInput.style.backgroundColor = '#fff5f5'; + if (!firstMissingInput) firstMissingInput = startInput; + } + } + + if (!endTime || endTime === '') { + missing.push('Endzeit'); + if (endInput) { + endInput.classList.add('missing-field'); + endInput.style.borderColor = '#dc3545'; + endInput.style.backgroundColor = '#fff5f5'; + if (!firstMissingInput) firstMissingInput = endInput; + } + } + + if (missing.length > 0) { + missingFields.push(`${weekday} (${dateDisplay}): ${missing.join(' und ')}`); + } + } + + if (missingFields.length > 0) { + console.warn('Fehlende Felder:', missingFields); + + // Scroll zum ersten fehlenden Feld + if (firstMissingInput) { + firstMissingInput.scrollIntoView({ behavior: 'smooth', block: 'center' }); + setTimeout(() => firstMissingInput.focus(), 300); + } + + // Detaillierte Fehlermeldung + const message = `❌ Bitte füllen Sie alle Werktage (Montag bis Freitag) vollständig aus!\n\n` + + `Fehlende Eingaben:\n${missingFields.map((field, index) => `\n${index + 1}. ${field}`).join('')}\n\n` + + `Die fehlenden Felder wurden rot markiert.\n` + + `Hinweis: Samstag und Sonntag sind optional.`; + + alert(message); + return; + } + + console.log('Alle Werktage sind ausgefüllt'); + + // Prüfe ob bereits eine Version existiert + const hasExistingVersion = currentEntries._hasExistingVersion || false; + + if (hasExistingVersion) { + // Zeige Modal für Grund-Eingabe + showVersionReasonModal((reason) => { + if (reason) { + submitWeekWithReason(reason); + } + }); + } else { + // Erste Version - normale Bestätigung + const confirmed = confirm( + `Möchten Sie die Stundenerfassung für die Woche vom ${formatDateDE(currentWeekStart)} bis ${formatDateDE(formatDate(endDate))} wirklich abschicken?` + ); + + if (!confirmed) { + console.log('Benutzer hat abgebrochen'); + return; + } + + submitWeekWithReason(null); + } +} + +// Modal für Grund-Eingabe anzeigen +function showVersionReasonModal(callback) { + // Erstelle Modal + const modal = document.createElement('div'); + modal.id = 'versionReasonModal'; + modal.style.cssText = ` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + justify-content: center; + align-items: center; + z-index: 10000; + `; + + const modalContent = document.createElement('div'); + modalContent.style.cssText = ` + background-color: white; + padding: 30px; + border-radius: 8px; + max-width: 500px; + width: 90%; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + `; + + modalContent.innerHTML = ` +

Neue Version einreichen

+

+ Es existiert bereits eine Version für diese Woche. Bitte geben Sie einen Grund an, warum Sie eine neue Version einreichen: +

+ +
+ + +
+ `; + + modal.appendChild(modalContent); + document.body.appendChild(modal); + + const reasonInput = document.getElementById('versionReasonInput'); + reasonInput.focus(); + + // Event-Handler + document.getElementById('cancelReasonBtn').addEventListener('click', () => { + document.body.removeChild(modal); + callback(null); + }); + + document.getElementById('submitReasonBtn').addEventListener('click', () => { + const reason = reasonInput.value.trim(); + if (!reason) { + alert('Bitte geben Sie einen Grund für die neue Version an.'); + reasonInput.focus(); + return; + } + document.body.removeChild(modal); + callback(reason); + }); + + // Enter-Taste im Textarea + reasonInput.addEventListener('keydown', (e) => { + if (e.key === 'Enter' && e.ctrlKey) { + document.getElementById('submitReasonBtn').click(); + } + }); + + // ESC-Taste zum Schließen + modal.addEventListener('click', (e) => { + if (e.target === modal) { + document.body.removeChild(modal); + callback(null); + } + }); +} + +// Woche mit Grund abschicken +async function submitWeekWithReason(versionReason) { + const startDate = new Date(currentWeekStart); + const endDate = new Date(startDate); + endDate.setDate(endDate.getDate() + 6); + + console.log('Sende Anfrage an Server...'); + try { + const response = await fetch('/api/timesheet/submit', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + week_start: currentWeekStart, + week_end: formatDate(endDate), + version_reason: versionReason || null + }) + }); + + console.log('Server-Antwort erhalten, Status:', response.status); + const result = await response.json(); + console.log('Server-Antwort:', result); + + if (result.success) { + const versionText = result.version ? ` (Version ${result.version})` : ''; + alert(`Stundenzettel wurde erfolgreich eingereicht${versionText}!`); + loadWeek(); // Neu laden um Status zu aktualisieren + loadUserStats(); // Statistiken aktualisieren + } else { + console.error('Fehler-Details:', result); + alert(result.error || 'Fehler beim Einreichen des Stundenzettels'); + } + } catch (error) { + console.error('Fehler beim Abschicken:', error); + alert('Fehler beim Abschicken: ' + error.message); + } +} + +// Überstunden-Eingabefeld ein-/ausblenden +function toggleOvertimeInput(dateStr) { + const inputDiv = document.getElementById(`overtime-input-${dateStr}`); + if (inputDiv) { + if (inputDiv.style.display === 'none' || !inputDiv.style.display) { + inputDiv.style.display = 'inline-block'; + const input = inputDiv.querySelector('input'); + if (input) { + input.focus(); + } + } else { + inputDiv.style.display = 'none'; + // Wert löschen wenn ausgeblendet + const input = inputDiv.querySelector('input'); + if (input) { + input.value = ''; + // Speichern + if (currentEntries[dateStr]) { + currentEntries[dateStr].overtime_taken_hours = null; + saveEntry(input); + } + } + } + } +} + +// Urlaub-Auswahl ein-/ausblenden +function toggleVacationSelect(dateStr) { + const selectDiv = document.getElementById(`vacation-select-${dateStr}`); + if (selectDiv) { + if (selectDiv.style.display === 'none' || !selectDiv.style.display) { + selectDiv.style.display = 'inline-block'; + const select = selectDiv.querySelector('select'); + if (select) { + select.focus(); + } + } else { + selectDiv.style.display = 'none'; + // Wert löschen wenn ausgeblendet + const select = selectDiv.querySelector('select'); + if (select) { + select.value = ''; + // Speichern + if (currentEntries[dateStr]) { + currentEntries[dateStr].vacation_type = null; + saveEntry(select); + } + } + } + } +} diff --git a/reset-db.js b/reset-db.js new file mode 100644 index 0000000..9da2a72 --- /dev/null +++ b/reset-db.js @@ -0,0 +1,207 @@ +const sqlite3 = require('sqlite3').verbose(); +const bcrypt = require('bcryptjs'); +const path = require('path'); +const fs = require('fs'); + +const dbPath = path.join(__dirname, 'stundenerfassung.db'); + +console.log('🔄 Setze Datenbank zurück...\n'); + +// Datenbank schließen falls offen +let db = null; + +try { + // Prüfe ob Datenbank existiert + if (fs.existsSync(dbPath)) { + console.log('📁 Datenbankdatei gefunden, lösche sie...'); + fs.unlinkSync(dbPath); + console.log('✅ Datenbankdatei gelöscht\n'); + } else { + console.log('ℹ️ Datenbankdatei existiert nicht, erstelle neue...\n'); + } + + // Neue Datenbank erstellen + db = new sqlite3.Database(dbPath); + + db.serialize(() => { + console.log('📊 Erstelle Tabellen...\n'); + + // Benutzer-Tabelle + db.run(`CREATE TABLE users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT UNIQUE NOT NULL, + password TEXT NOT NULL, + firstname TEXT NOT NULL, + lastname TEXT NOT NULL, + role TEXT NOT NULL DEFAULT 'mitarbeiter', + last_week_start TEXT, + personalnummer TEXT, + wochenstunden REAL, + urlaubstage REAL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP + )`, (err) => { + if (err) console.error('Fehler bei users:', err); + else console.log('✅ Tabelle users erstellt'); + }); + + // Stundenerfassung-Tabelle + db.run(`CREATE TABLE timesheet_entries ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + date TEXT NOT NULL, + start_time TEXT, + end_time TEXT, + break_minutes INTEGER DEFAULT 0, + total_hours REAL, + notes TEXT, + activity1_desc TEXT, + activity1_hours REAL, + activity1_project_number TEXT, + activity2_desc TEXT, + activity2_hours REAL, + activity2_project_number TEXT, + activity3_desc TEXT, + activity3_hours REAL, + activity3_project_number TEXT, + activity4_desc TEXT, + activity4_hours REAL, + activity4_project_number TEXT, + activity5_desc TEXT, + activity5_hours REAL, + activity5_project_number TEXT, + overtime_taken_hours REAL, + vacation_type TEXT, + status TEXT DEFAULT 'offen', + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) + )`, (err) => { + if (err) console.error('Fehler bei timesheet_entries:', err); + else console.log('✅ Tabelle timesheet_entries erstellt'); + }); + + // Wöchentliche Stundenzettel-Tabelle + db.run(`CREATE TABLE weekly_timesheets ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + week_start TEXT NOT NULL, + week_end TEXT NOT NULL, + version INTEGER DEFAULT 1, + status TEXT DEFAULT 'eingereicht', + submitted_at DATETIME DEFAULT CURRENT_TIMESTAMP, + reviewed_by INTEGER, + reviewed_at DATETIME, + pdf_downloaded_at DATETIME, + pdf_downloaded_by INTEGER, + version_reason TEXT, + admin_comment TEXT, + FOREIGN KEY (user_id) REFERENCES users(id), + FOREIGN KEY (reviewed_by) REFERENCES users(id), + FOREIGN KEY (pdf_downloaded_by) REFERENCES users(id) + )`, (err) => { + if (err) console.error('Fehler bei weekly_timesheets:', err); + else console.log('✅ Tabelle weekly_timesheets erstellt'); + }); + + // LDAP-Konfiguration-Tabelle + db.run(`CREATE TABLE ldap_config ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + enabled INTEGER DEFAULT 0, + url TEXT, + bind_dn TEXT, + bind_password TEXT, + base_dn TEXT, + user_search_filter TEXT, + username_attribute TEXT DEFAULT 'cn', + firstname_attribute TEXT DEFAULT 'givenName', + lastname_attribute TEXT DEFAULT 'sn', + sync_interval INTEGER DEFAULT 0, + last_sync DATETIME, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP + )`, (err) => { + if (err) console.error('Fehler bei ldap_config:', err); + else console.log('✅ Tabelle ldap_config erstellt'); + }); + + // LDAP-Sync-Log-Tabelle + db.run(`CREATE TABLE ldap_sync_log ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + sync_type TEXT NOT NULL, + status TEXT NOT NULL, + users_synced INTEGER DEFAULT 0, + error_message TEXT, + sync_started_at DATETIME DEFAULT CURRENT_TIMESTAMP, + sync_completed_at DATETIME + )`, (err) => { + if (err) console.error('Fehler bei ldap_sync_log:', err); + else console.log('✅ Tabelle ldap_sync_log erstellt'); + }); + + // Warte bis alle Tabellen erstellt sind + db.run('SELECT 1', (err) => { + if (err) { + console.error('Fehler beim Warten:', err); + return; + } + + console.log('\n👤 Erstelle Standard-Benutzer...\n'); + + // Standard Admin-Benutzer + const adminPassword = bcrypt.hashSync('admin123', 10); + db.run(`INSERT INTO users (id, username, password, firstname, lastname, role) + VALUES (1, 'admin', ?, 'System', 'Administrator', 'admin')`, + [adminPassword], (err) => { + if (err) console.error('Fehler beim Erstellen des Admin-Users:', err); + else console.log('✅ Admin-User erstellt (admin / admin123)'); + }); + + // Standard Verwaltungs-Benutzer + const verwaltungPassword = bcrypt.hashSync('verwaltung123', 10); + db.run(`INSERT INTO users (id, username, password, firstname, lastname, role) + VALUES (2, 'verwaltung', ?, 'Verwaltung', 'User', 'verwaltung')`, + [verwaltungPassword], (err) => { + if (err) console.error('Fehler beim Erstellen des Verwaltungs-Users:', err); + else console.log('✅ Verwaltungs-User erstellt (verwaltung / verwaltung123)'); + }); + + // Test-Mitarbeiter (optional) + const mitarbeiterPassword = bcrypt.hashSync('test123', 10); + db.run(`INSERT INTO users (id, username, password, firstname, lastname, role, wochenstunden, urlaubstage) + VALUES (3, 'test', ?, 'Test', 'Mitarbeiter', 'mitarbeiter', 40, 25)`, + [mitarbeiterPassword], (err) => { + if (err && !err.message.includes('UNIQUE constraint')) { + console.error('Fehler beim Erstellen des Test-Users:', err); + } else if (!err) { + console.log('✅ Test-Mitarbeiter erstellt (test / test123, 40h/Woche, 25 Urlaubstage)'); + } + }); + + // Warte bis alle Benutzer erstellt sind + setTimeout(() => { + console.log('\n✨ Datenbank erfolgreich zurückgesetzt!\n'); + console.log('📋 Standard-Zugangsdaten:'); + console.log(' Admin: admin / admin123'); + console.log(' Verwaltung: verwaltung / verwaltung123'); + console.log(' Test-User: test / test123\n'); + + db.close((err) => { + if (err) { + console.error('Fehler beim Schließen der Datenbank:', err); + process.exit(1); + } else { + console.log('✅ Datenbank geschlossen\n'); + process.exit(0); + } + }); + }, 500); + }); + }); + +} catch (error) { + console.error('❌ Fehler beim Zurücksetzen der Datenbank:', error); + if (db) { + db.close(); + } + process.exit(1); +} diff --git a/server.js b/server.js new file mode 100644 index 0000000..5b30460 --- /dev/null +++ b/server.js @@ -0,0 +1,1595 @@ +const express = require('express'); +const session = require('express-session'); +const bodyParser = require('body-parser'); +const bcrypt = require('bcryptjs'); +const path = require('path'); +const { db, initDatabase } = require('./database'); +const LDAPService = require('./ldap-service'); +const cron = require('node-cron'); + +const app = express(); +const PORT = 3000; + +// Middleware +app.use(bodyParser.urlencoded({ extended: true })); +app.use(bodyParser.json()); +app.use(express.static('public')); +app.set('view engine', 'ejs'); +app.set('views', path.join(__dirname, 'views')); + +// Session-Konfiguration +app.use(session({ + secret: 'stundenerfassung-geheim-2024', + resave: false, + saveUninitialized: false, + cookie: { maxAge: 24 * 60 * 60 * 1000 } // 24 Stunden +})); + +// Datenbank initialisieren +initDatabase(); + +// Helper: Prüft ob User eine bestimmte Rolle hat +function hasRole(req, role) { + if (!req.session.roles || !Array.isArray(req.session.roles)) { + return false; + } + return req.session.roles.includes(role); +} + +// Helper: Bestimmt die Standard-Rolle (höchste Priorität: admin > verwaltung > mitarbeiter) +function getDefaultRole(roles) { + if (!Array.isArray(roles) || roles.length === 0) { + return 'mitarbeiter'; + } + if (roles.includes('admin')) return 'admin'; + if (roles.includes('verwaltung')) return 'verwaltung'; + return roles[0]; // Fallback auf erste Rolle +} + +// Helper: Gibt aktuelles Datum als YYYY-MM-DD zurück +function getCurrentDate() { + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const day = String(now.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; +} + +// Helper: Gibt aktuelle Zeit als HH:MM zurück +function getCurrentTime() { + const now = new Date(); + const hours = String(now.getHours()).padStart(2, '0'); + const minutes = String(now.getMinutes()).padStart(2, '0'); + return `${hours}:${minutes}`; +} + +// Helper: Berechnet Pausenzeit in Minuten zwischen zwei Zeiten +function calculateBreakMinutes(pauseStart, pauseEnd) { + if (!pauseStart || !pauseEnd) return 0; + + const [startHours, startMinutes] = pauseStart.split(':').map(Number); + const [endHours, endMinutes] = pauseEnd.split(':').map(Number); + + const startTotalMinutes = startHours * 60 + startMinutes; + const endTotalMinutes = endHours * 60 + endMinutes; + + return endTotalMinutes - startTotalMinutes; +} + +// Helper: Berechnet total_hours basierend auf start_time, end_time und break_minutes +function updateTotalHours(startTime, endTime, breakMinutes) { + if (!startTime || !endTime) return 0; + + const [startHours, startMinutes] = startTime.split(':').map(Number); + const [endHours, endMinutes] = endTime.split(':').map(Number); + + const startTotalMinutes = startHours * 60 + startMinutes; + const endTotalMinutes = endHours * 60 + endMinutes; + + const totalMinutes = endTotalMinutes - startTotalMinutes - (breakMinutes || 0); + return totalMinutes / 60; // Konvertiere zu Stunden +} + +// Middleware: Authentifizierung prüfen +function requireAuth(req, res, next) { + if (req.session.userId) { + next(); + } else { + res.redirect('/login'); + } +} + +// Middleware: Prüft ob User eine bestimmte Rolle hat +function requireRole(role) { + return (req, res, next) => { + if (req.session.userId && hasRole(req, role)) { + next(); + } else { + res.status(403).send('Zugriff verweigert'); + } + }; +} + +// Middleware: Admin-Rolle prüfen +function requireAdmin(req, res, next) { + if (req.session.userId && hasRole(req, 'admin')) { + next(); + } else { + res.status(403).send('Zugriff verweigert'); + } +} + +// Middleware: Verwaltung-Rolle prüfen (Verwaltung oder Admin) +function requireVerwaltung(req, res, next) { + if (req.session.userId && (hasRole(req, 'verwaltung') || hasRole(req, 'admin'))) { + next(); + } else { + res.status(403).send('Zugriff verweigert'); + } +} + +// ROUTEN + +// Login-Seite +app.get('/login', (req, res) => { + res.render('login', { error: null }); +}); + +// Login-Verarbeitung +app.post('/login', (req, res) => { + const { username, password } = req.body; + + db.get('SELECT * FROM users WHERE username = ?', [username], (err, user) => { + if (err || !user) { + return res.render('login', { error: 'Ungültiger Benutzername oder Passwort' }); + } + + if (bcrypt.compareSync(password, user.password)) { + // Rollen als JSON-Array parsen + let roles = []; + try { + roles = JSON.parse(user.role); + if (!Array.isArray(roles)) { + // Fallback: Falls kein Array, erstelle Array mit vorhandener Rolle + roles = [user.role]; + } + } catch (e) { + // Fallback: Falls kein JSON, erstelle Array mit vorhandener Rolle + roles = [user.role || 'mitarbeiter']; + } + + // Standard-Rolle bestimmen: Immer "mitarbeiter" wenn vorhanden, sonst höchste Priorität + let defaultRole; + if (roles.includes('mitarbeiter')) { + defaultRole = 'mitarbeiter'; + } else { + defaultRole = getDefaultRole(roles); + } + + req.session.userId = user.id; + req.session.username = user.username; + req.session.roles = roles; + req.session.currentRole = defaultRole; + req.session.firstname = user.firstname; + req.session.lastname = user.lastname; + + // Redirect: Immer zu Dashboard wenn Mitarbeiter-Rolle vorhanden, sonst basierend auf Standard-Rolle + if (roles.includes('mitarbeiter')) { + res.redirect('/dashboard'); + } else if (defaultRole === 'admin') { + res.redirect('/admin'); + } else if (defaultRole === 'verwaltung') { + res.redirect('/verwaltung'); + } else { + res.redirect('/dashboard'); + } + } else { + res.render('login', { error: 'Ungültiger Benutzername oder Passwort' }); + } + }); +}); + +// Logout +app.get('/logout', (req, res) => { + req.session.destroy(); + res.redirect('/login'); +}); + +// Dashboard für Mitarbeiter +app.get('/dashboard', requireAuth, (req, res) => { + // Prüfe ob User Mitarbeiter-Rolle hat + if (!hasRole(req, 'mitarbeiter')) { + // Wenn User keine Mitarbeiter-Rolle hat, aber andere Rollen, redirecte entsprechend + if (hasRole(req, 'admin')) { + return res.redirect('/admin'); + } + if (hasRole(req, 'verwaltung')) { + return res.redirect('/verwaltung'); + } + return res.status(403).send('Zugriff verweigert'); + } + + res.render('dashboard', { + user: { + id: req.session.userId, + firstname: req.session.firstname, + lastname: req.session.lastname, + username: req.session.username, + roles: req.session.roles || [], + currentRole: req.session.currentRole || 'mitarbeiter' + } + }); +}); + +// API: Letzte bearbeitete Woche abrufen +app.get('/api/user/last-week', requireAuth, (req, res) => { + const userId = req.session.userId; + + db.get('SELECT last_week_start FROM users WHERE id = ?', [userId], (err, user) => { + if (err) { + return res.status(500).json({ error: 'Fehler beim Abrufen der letzten Woche' }); + } + + res.json({ last_week_start: user?.last_week_start || null }); + }); +}); + +// API: Letzte bearbeitete Woche speichern +app.post('/api/user/last-week', requireAuth, (req, res) => { + const userId = req.session.userId; + const { week_start } = req.body; + + if (!week_start) { + return res.status(400).json({ error: 'week_start ist erforderlich' }); + } + + db.run('UPDATE users SET last_week_start = ? WHERE id = ?', + [week_start, userId], + (err) => { + if (err) { + return res.status(500).json({ error: 'Fehler beim Speichern der letzten Woche' }); + } + res.json({ success: true }); + }); +}); + +// API: User-Daten abrufen (Wochenstunden) +app.get('/api/user/data', requireAuth, (req, res) => { + const userId = req.session.userId; + + db.get('SELECT wochenstunden FROM users WHERE id = ?', [userId], (err, user) => { + if (err) { + return res.status(500).json({ error: 'Fehler beim Abrufen der User-Daten' }); + } + + res.json({ wochenstunden: user?.wochenstunden || 0 }); + }); +}); + +// API: Rollenwechsel +app.post('/api/user/switch-role', requireAuth, (req, res) => { + const { role } = req.body; + + if (!role) { + return res.status(400).json({ error: 'Rolle ist erforderlich' }); + } + + // Prüfe ob User diese Rolle hat + if (!hasRole(req, role)) { + return res.status(403).json({ error: 'Sie haben keine Berechtigung für diese Rolle' }); + } + + // Validiere dass die Rolle eine gültige Rolle ist + const validRoles = ['mitarbeiter', 'verwaltung', 'admin']; + if (!validRoles.includes(role)) { + return res.status(400).json({ error: 'Ungültige Rolle' }); + } + + // Setze aktuelle Rolle + req.session.currentRole = role; + + res.json({ success: true, currentRole: role }); +}); + +// API: Gesamtstatistiken für Mitarbeiter (Überstunden und Urlaubstage) +app.get('/api/user/stats', requireAuth, (req, res) => { + const userId = req.session.userId; + + // User-Daten abrufen + db.get('SELECT wochenstunden, urlaubstage FROM users WHERE id = ?', [userId], (err, user) => { + if (err || !user) { + return res.status(500).json({ error: 'Fehler beim Abrufen der User-Daten' }); + } + + const wochenstunden = user.wochenstunden || 0; + const urlaubstage = user.urlaubstage || 0; + + // Alle eingereichten Wochen abrufen + db.all(`SELECT DISTINCT week_start, week_end + FROM weekly_timesheets + WHERE user_id = ? AND status = 'eingereicht' + ORDER BY week_start`, + [userId], + (err, weeks) => { + if (err) { + return res.status(500).json({ error: 'Fehler beim Abrufen der Wochen' }); + } + + // Wenn keine Wochen vorhanden + if (!weeks || weeks.length === 0) { + return res.json({ + currentOvertime: 0, + remainingVacation: urlaubstage, + totalOvertimeHours: 0, + totalOvertimeTaken: 0, + totalVacationDays: 0, + urlaubstage: urlaubstage + }); + } + + let totalOvertimeHours = 0; + let totalOvertimeTaken = 0; + let totalVacationDays = 0; + let processedWeeks = 0; + let hasError = false; + + // Für jede Woche die Statistiken berechnen + weeks.forEach((week) => { + // Einträge für diese Woche abrufen + db.all(`SELECT date, total_hours, overtime_taken_hours, vacation_type + FROM timesheet_entries + WHERE user_id = ? AND date >= ? AND date <= ? + ORDER BY date`, + [userId, week.week_start, week.week_end], + (err, entries) => { + if (hasError) return; // Wenn bereits ein Fehler aufgetreten ist, ignoriere weitere Ergebnisse + + if (err) { + hasError = true; + return res.status(500).json({ error: 'Fehler beim Abrufen der Einträge' }); + } + + // Berechnungen für diese Woche + let weekTotalHours = 0; + let weekOvertimeTaken = 0; + let weekVacationDays = 0; + let weekVacationHours = 0; + + entries.forEach(entry => { + if (entry.total_hours) { + weekTotalHours += entry.total_hours; + } + if (entry.overtime_taken_hours) { + weekOvertimeTaken += entry.overtime_taken_hours; + } + if (entry.vacation_type === 'full') { + weekVacationDays += 1; + weekVacationHours += 8; + } else if (entry.vacation_type === 'half') { + weekVacationDays += 0.5; + weekVacationHours += 4; + } + }); + + // Anzahl Werktage berechnen (Montag-Freitag) + const startDate = new Date(week.week_start); + const endDate = new Date(week.week_end); + let workdays = 0; + for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) { + const day = d.getDay(); + if (day >= 1 && day <= 5) { // Montag bis Freitag + workdays++; + } + } + + // Sollstunden berechnen + const sollStunden = (wochenstunden / 5) * workdays; + + // Überstunden für diese Woche: Urlaub zählt als normale Arbeitszeit + const weekTotalHoursWithVacation = weekTotalHours + weekVacationHours; + const weekOvertimeHours = weekTotalHoursWithVacation - sollStunden; + + // Kumulativ addieren + totalOvertimeHours += weekOvertimeHours; + totalOvertimeTaken += weekOvertimeTaken; + totalVacationDays += weekVacationDays; + + processedWeeks++; + + // Wenn alle Wochen verarbeitet wurden, Antwort senden + if (processedWeeks === weeks.length && !hasError) { + const currentOvertime = totalOvertimeHours - totalOvertimeTaken; + const remainingVacation = urlaubstage - totalVacationDays; + + res.json({ + currentOvertime: currentOvertime, + remainingVacation: remainingVacation, + totalOvertimeHours: totalOvertimeHours, + totalOvertimeTaken: totalOvertimeTaken, + totalVacationDays: totalVacationDays, + urlaubstage: urlaubstage + }); + } + }); + }); + }); + }); +}); + +// Admin-Bereich +app.get('/admin', requireAdmin, (req, res) => { + db.all('SELECT id, username, firstname, lastname, role, personalnummer, wochenstunden, urlaubstage, created_at FROM users ORDER BY created_at DESC', + (err, users) => { + // LDAP-Konfiguration und Sync-Log abrufen + db.get('SELECT * FROM ldap_config WHERE id = 1', (err, ldapConfig) => { + db.all('SELECT * FROM ldap_sync_log ORDER BY sync_started_at DESC LIMIT 10', (err, syncLogs) => { + // Parse Rollen für jeden User + const usersWithRoles = (users || []).map(u => { + let roles = []; + try { + roles = JSON.parse(u.role); + if (!Array.isArray(roles)) { + roles = [u.role]; + } + } catch (e) { + roles = [u.role || 'mitarbeiter']; + } + return { ...u, roles }; + }); + + res.render('admin', { + users: usersWithRoles, + ldapConfig: ldapConfig || null, + syncLogs: syncLogs || [], + user: { + firstname: req.session.firstname, + lastname: req.session.lastname, + roles: req.session.roles || [], + currentRole: req.session.currentRole || 'admin' + } + }); + }); + }); + }); +}); + +// Benutzer erstellen +app.post('/admin/users', requireAdmin, (req, res) => { + const { username, password, firstname, lastname, roles, personalnummer, wochenstunden, urlaubstage } = req.body; + const hashedPassword = bcrypt.hashSync(password, 10); + + // Normalisiere die optionalen Felder + const normalizedPersonalnummer = personalnummer && personalnummer.trim() !== '' ? personalnummer.trim() : null; + const normalizedWochenstunden = wochenstunden && wochenstunden !== '' ? parseFloat(wochenstunden) : null; + const normalizedUrlaubstage = urlaubstage && urlaubstage !== '' ? parseFloat(urlaubstage) : null; + + // Rollen verarbeiten: Erwarte Array, konvertiere zu JSON-String + let rolesArray = []; + if (Array.isArray(roles)) { + rolesArray = roles.filter(r => r && ['mitarbeiter', 'verwaltung', 'admin'].includes(r)); + } else if (roles) { + // Fallback: Einzelne Rolle als Array + rolesArray = [roles]; + } + + // Mindestens eine Rolle erforderlich + if (rolesArray.length === 0) { + rolesArray = ['mitarbeiter']; // Standard-Rolle + } + + const rolesJson = JSON.stringify(rolesArray); + + db.run('INSERT INTO users (username, password, firstname, lastname, role, personalnummer, wochenstunden, urlaubstage) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', + [username, hashedPassword, firstname, lastname, rolesJson, normalizedPersonalnummer, normalizedWochenstunden, normalizedUrlaubstage], + (err) => { + if (err) { + return res.status(400).json({ error: 'Benutzername existiert bereits' }); + } + res.json({ success: true }); + }); +}); + +// Benutzer löschen +app.delete('/admin/users/:id', requireAdmin, (req, res) => { + const userId = req.params.id; + + // Admin darf sich nicht selbst löschen + if (userId == req.session.userId) { + return res.status(400).json({ error: 'Sie können sich nicht selbst löschen' }); + } + + db.run('DELETE FROM users WHERE id = ?', [userId], (err) => { + if (err) { + return res.status(500).json({ error: 'Fehler beim Löschen' }); + } + res.json({ success: true }); + }); +}); + +// Benutzer aktualisieren (Personalnummer, Wochenstunden, Urlaubstage, Rollen) +app.put('/admin/users/:id', requireAdmin, (req, res) => { + const userId = req.params.id; + const { personalnummer, wochenstunden, urlaubstage, roles } = req.body; + + // Rollen verarbeiten falls vorhanden + let rolesJson = null; + if (roles !== undefined) { + let rolesArray = []; + if (Array.isArray(roles)) { + rolesArray = roles.filter(r => r && ['mitarbeiter', 'verwaltung', 'admin'].includes(r)); + } + // Mindestens eine Rolle erforderlich + if (rolesArray.length === 0) { + return res.status(400).json({ error: 'Mindestens eine Rolle ist erforderlich' }); + } + rolesJson = JSON.stringify(rolesArray); + } + + // SQL-Query dynamisch zusammenstellen + if (rolesJson !== null) { + // Aktualisiere auch Rollen + db.run('UPDATE users SET personalnummer = ?, wochenstunden = ?, urlaubstage = ?, role = ? WHERE id = ?', + [ + personalnummer || null, + wochenstunden ? parseFloat(wochenstunden) : null, + urlaubstage ? parseFloat(urlaubstage) : null, + rolesJson, + userId + ], + (err) => { + if (err) { + return res.status(500).json({ error: 'Fehler beim Aktualisieren' }); + } + res.json({ success: true }); + }); + } else { + // Nur andere Felder aktualisieren + db.run('UPDATE users SET personalnummer = ?, wochenstunden = ?, urlaubstage = ? WHERE id = ?', + [ + personalnummer || null, + wochenstunden ? parseFloat(wochenstunden) : null, + urlaubstage ? parseFloat(urlaubstage) : null, + userId + ], + (err) => { + if (err) { + return res.status(500).json({ error: 'Fehler beim Aktualisieren' }); + } + res.json({ success: true }); + }); + } +}); + +// LDAP-Konfiguration abrufen +app.get('/admin/ldap/config', requireAdmin, (req, res) => { + db.get('SELECT * FROM ldap_config WHERE id = 1', (err, config) => { + if (err) { + return res.status(500).json({ error: 'Fehler beim Abrufen der Konfiguration' }); + } + + // Passwort nicht zurückgeben + if (config) { + delete config.bind_password; + } + + res.json({ config: config || null }); + }); +}); + +// LDAP-Konfiguration speichern +app.post('/admin/ldap/config', requireAdmin, (req, res) => { + const { + enabled, + url, + bind_dn, + bind_password, + base_dn, + user_search_filter, + username_attribute, + firstname_attribute, + lastname_attribute, + sync_interval + } = req.body; + + // Validierung - nur wenn aktiviert + if (enabled && (!url || !base_dn)) { + return res.status(400).json({ error: 'URL und Base DN sind erforderlich wenn LDAP aktiviert ist' }); + } + + // Prüfe ob Konfiguration bereits existiert + db.get('SELECT id FROM ldap_config WHERE id = 1', (err, existing) => { + if (err) { + return res.status(500).json({ error: 'Fehler beim Prüfen der Konfiguration' }); + } + + const configData = { + enabled: enabled ? 1 : 0, + url: url.trim(), + bind_dn: bind_dn ? bind_dn.trim() : null, + bind_password: bind_password ? bind_password.trim() : null, + base_dn: base_dn.trim(), + user_search_filter: user_search_filter ? user_search_filter.trim() : '(objectClass=person)', + username_attribute: username_attribute ? username_attribute.trim() : 'cn', + firstname_attribute: firstname_attribute ? firstname_attribute.trim() : 'givenName', + lastname_attribute: lastname_attribute ? lastname_attribute.trim() : 'sn', + sync_interval: parseInt(sync_interval) || 0, + updated_at: new Date().toISOString() + }; + + if (existing) { + // Update - Passwort nur aktualisieren wenn angegeben + if (configData.bind_password) { + db.run( + `UPDATE ldap_config SET + enabled = ?, url = ?, bind_dn = ?, bind_password = ?, base_dn = ?, + user_search_filter = ?, username_attribute = ?, firstname_attribute = ?, + lastname_attribute = ?, sync_interval = ?, updated_at = ? + WHERE id = 1`, + [ + configData.enabled, configData.url, configData.bind_dn, configData.bind_password, + configData.base_dn, configData.user_search_filter, configData.username_attribute, + configData.firstname_attribute, configData.lastname_attribute, configData.sync_interval, + configData.updated_at + ], + (err) => { + if (err) { + return res.status(500).json({ error: 'Fehler beim Speichern der Konfiguration' }); + } + res.json({ success: true }); + } + ); + } else { + // Passwort nicht ändern + db.run( + `UPDATE ldap_config SET + enabled = ?, url = ?, bind_dn = ?, base_dn = ?, + user_search_filter = ?, username_attribute = ?, firstname_attribute = ?, + lastname_attribute = ?, sync_interval = ?, updated_at = ? + WHERE id = 1`, + [ + configData.enabled, configData.url, configData.bind_dn, + configData.base_dn, configData.user_search_filter, configData.username_attribute, + configData.firstname_attribute, configData.lastname_attribute, configData.sync_interval, + configData.updated_at + ], + (err) => { + if (err) { + return res.status(500).json({ error: 'Fehler beim Speichern der Konfiguration' }); + } + res.json({ success: true }); + } + ); + } + } else { + // Insert + db.run( + `INSERT INTO ldap_config ( + enabled, url, bind_dn, bind_password, base_dn, user_search_filter, + username_attribute, firstname_attribute, lastname_attribute, sync_interval, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [ + configData.enabled, configData.url, configData.bind_dn, configData.bind_password, + configData.base_dn, configData.user_search_filter, configData.username_attribute, + configData.firstname_attribute, configData.lastname_attribute, configData.sync_interval, + configData.updated_at + ], + (err) => { + if (err) { + return res.status(500).json({ error: 'Fehler beim Erstellen der Konfiguration' }); + } + res.json({ success: true }); + } + ); + } + }); +}); + +// Manuelle LDAP-Synchronisation starten +app.post('/admin/ldap/sync', requireAdmin, (req, res) => { + LDAPService.performSync('manual', (err, result) => { + if (err) { + return res.status(500).json({ + error: err.message || 'Fehler bei der Synchronisation', + synced: result ? result.synced : 0, + errors: result ? result.errors : [] + }); + } + res.json({ + success: true, + synced: result.synced, + errors: result.errors || [] + }); + }); +}); + +// Sync-Log abrufen +app.get('/admin/ldap/sync/log', requireAdmin, (req, res) => { + const limit = parseInt(req.query.limit) || 10; + db.all( + 'SELECT * FROM ldap_sync_log ORDER BY sync_started_at DESC LIMIT ?', + [limit], + (err, logs) => { + if (err) { + return res.status(500).json({ error: 'Fehler beim Abrufen des Logs' }); + } + res.json({ logs: logs || [] }); + } + ); +}); + +// Verwaltungs-Bereich +app.get('/verwaltung', requireVerwaltung, (req, res) => { + db.all(` + SELECT wt.*, u.firstname, u.lastname, u.username, u.personalnummer, u.wochenstunden, u.urlaubstage, + dl.firstname as downloaded_by_firstname, + dl.lastname as downloaded_by_lastname, + (SELECT COUNT(*) FROM weekly_timesheets wt2 + WHERE wt2.user_id = wt.user_id + AND wt2.week_start = wt.week_start + AND wt2.week_end = wt.week_end) as total_versions + FROM weekly_timesheets wt + JOIN users u ON wt.user_id = u.id + LEFT JOIN users dl ON wt.pdf_downloaded_by = dl.id + WHERE wt.status = 'eingereicht' + ORDER BY wt.week_start DESC, wt.user_id, wt.version DESC + `, (err, timesheets) => { + // Gruppiere nach Mitarbeiter, dann nach Kalenderwoche + // Struktur: { [user_id]: { user: {...}, weeks: { [week_key]: {...} } } } + const groupedByEmployee = {}; + + (timesheets || []).forEach(ts => { + const userId = ts.user_id; + const weekKey = `${ts.week_start}_${ts.week_end}`; + + // Level 1: Mitarbeiter + if (!groupedByEmployee[userId]) { + groupedByEmployee[userId] = { + user: { + id: ts.user_id, + firstname: ts.firstname, + lastname: ts.lastname, + username: ts.username, + personalnummer: ts.personalnummer, + wochenstunden: ts.wochenstunden, + urlaubstage: ts.urlaubstage + }, + weeks: {} + }; + } + + // Level 2: Kalenderwoche + if (!groupedByEmployee[userId].weeks[weekKey]) { + groupedByEmployee[userId].weeks[weekKey] = { + week_start: ts.week_start, + week_end: ts.week_end, + total_versions: ts.total_versions, + versions: [] + }; + } + + // Level 3: Versionen + groupedByEmployee[userId].weeks[weekKey].versions.push(ts); + }); + + // Sortierung: Mitarbeiter nach Name, Wochen nach Datum (neueste zuerst) + const sortedEmployees = Object.values(groupedByEmployee).map(employee => { + // Wochen innerhalb jedes Mitarbeiters sortieren + const sortedWeeks = Object.values(employee.weeks).sort((a, b) => { + return new Date(b.week_start) - new Date(a.week_start); + }); + + return { + ...employee, + weeks: sortedWeeks + }; + }).sort((a, b) => { + // Mitarbeiter nach Nachname, dann Vorname sortieren + const nameA = `${a.user.lastname} ${a.user.firstname}`.toLowerCase(); + const nameB = `${b.user.lastname} ${b.user.firstname}`.toLowerCase(); + return nameA.localeCompare(nameB); + }); + + res.render('verwaltung', { + groupedByEmployee: sortedEmployees, + user: { + firstname: req.session.firstname, + lastname: req.session.lastname, + roles: req.session.roles || [], + currentRole: req.session.currentRole || 'verwaltung' + } + }); + }); +}); + +// API: Stundenerfassung speichern +app.post('/api/timesheet/save', requireAuth, (req, res) => { + const { + date, start_time, end_time, break_minutes, notes, + activity1_desc, activity1_hours, activity1_project_number, + activity2_desc, activity2_hours, activity2_project_number, + activity3_desc, activity3_hours, activity3_project_number, + activity4_desc, activity4_hours, activity4_project_number, + activity5_desc, activity5_hours, activity5_project_number, + overtime_taken_hours, vacation_type + } = req.body; + const userId = req.session.userId; + + // Normalisiere end_time: Leere Strings werden zu null + const normalizedEndTime = (end_time && typeof end_time === 'string' && end_time.trim() !== '') ? end_time.trim() : (end_time || null); + const normalizedStartTime = (start_time && typeof start_time === 'string' && start_time.trim() !== '') ? start_time.trim() : (start_time || null); + + // Gesamtstunden berechnen (aus Start- und Endzeit, nicht aus Tätigkeiten) + // Wenn ganzer Tag Urlaub, dann zählt dieser als 8 Stunden normale Arbeitszeit + let total_hours = 0; + if (vacation_type === 'full') { + total_hours = 8; // Ganzer Tag Urlaub = 8 Stunden normale Arbeitszeit + } else if (normalizedStartTime && normalizedEndTime) { + const start = new Date(`2000-01-01T${normalizedStartTime}`); + const end = new Date(`2000-01-01T${normalizedEndTime}`); + const diffMs = end - start; + total_hours = (diffMs / (1000 * 60 * 60)) - (break_minutes / 60); + // Bei halbem Tag Urlaub: total_hours bleibt die tatsächlich gearbeiteten Stunden + // Die 4 Stunden Urlaub werden nur in der Überstunden-Berechnung hinzugezählt + } + + // Prüfen ob Eintrag existiert - verwende den neuesten Eintrag falls mehrere existieren + db.get('SELECT id FROM timesheet_entries WHERE user_id = ? AND date = ? ORDER BY updated_at DESC, id DESC LIMIT 1', + [userId, date], (err, row) => { + if (row) { + // Update + db.run(`UPDATE timesheet_entries + SET start_time = ?, end_time = ?, break_minutes = ?, total_hours = ?, notes = ?, + activity1_desc = ?, activity1_hours = ?, activity1_project_number = ?, + activity2_desc = ?, activity2_hours = ?, activity2_project_number = ?, + activity3_desc = ?, activity3_hours = ?, activity3_project_number = ?, + activity4_desc = ?, activity4_hours = ?, activity4_project_number = ?, + activity5_desc = ?, activity5_hours = ?, activity5_project_number = ?, + overtime_taken_hours = ?, vacation_type = ?, + updated_at = CURRENT_TIMESTAMP + WHERE id = ?`, + [ + normalizedStartTime, normalizedEndTime, break_minutes, total_hours, notes, + activity1_desc || null, parseFloat(activity1_hours) || 0, activity1_project_number || null, + activity2_desc || null, parseFloat(activity2_hours) || 0, activity2_project_number || null, + activity3_desc || null, parseFloat(activity3_hours) || 0, activity3_project_number || null, + activity4_desc || null, parseFloat(activity4_hours) || 0, activity4_project_number || null, + activity5_desc || null, parseFloat(activity5_hours) || 0, activity5_project_number || null, + overtime_taken_hours ? parseFloat(overtime_taken_hours) : null, + vacation_type || null, + row.id + ], + (err) => { + if (err) { + console.error('Fehler beim Update:', err); + return res.status(500).json({ error: 'Fehler beim Speichern: ' + err.message }); + } + res.json({ success: true, total_hours }); + }); + } else { + // Insert + db.run(`INSERT INTO timesheet_entries + (user_id, date, start_time, end_time, break_minutes, total_hours, notes, + activity1_desc, activity1_hours, activity1_project_number, + activity2_desc, activity2_hours, activity2_project_number, + activity3_desc, activity3_hours, activity3_project_number, + activity4_desc, activity4_hours, activity4_project_number, + activity5_desc, activity5_hours, activity5_project_number, + overtime_taken_hours, vacation_type) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [ + userId, date, normalizedStartTime, normalizedEndTime, break_minutes, total_hours, notes, + activity1_desc || null, parseFloat(activity1_hours) || 0, activity1_project_number || null, + activity2_desc || null, parseFloat(activity2_hours) || 0, activity2_project_number || null, + activity3_desc || null, parseFloat(activity3_hours) || 0, activity3_project_number || null, + activity4_desc || null, parseFloat(activity4_hours) || 0, activity4_project_number || null, + activity5_desc || null, parseFloat(activity5_hours) || 0, activity5_project_number || null, + overtime_taken_hours ? parseFloat(overtime_taken_hours) : null, + vacation_type || null + ], + (err) => { + if (err) { + console.error('Fehler beim Insert:', err); + return res.status(500).json({ error: 'Fehler beim Speichern: ' + err.message }); + } + res.json({ success: true, total_hours }); + }); + } + }); +}); + +// API: Check-in (Kommen) +app.get('/api/checkin/:userId', (req, res) => { + const userId = parseInt(req.params.userId); + const currentDate = getCurrentDate(); + const currentTime = getCurrentTime(); + + // Prüfe ob User existiert + db.get('SELECT id FROM users WHERE id = ?', [userId], (err, user) => { + if (err || !user) { + return res.status(404).json({ success: false, error: 'Benutzer nicht gefunden' }); + } + + // Prüfe ob bereits ein Eintrag für heute existiert + db.get('SELECT * FROM timesheet_entries WHERE user_id = ? AND date = ? ORDER BY updated_at DESC, id DESC LIMIT 1', + [userId, currentDate], (err, entry) => { + if (err) { + return res.status(500).json({ success: false, error: 'Fehler beim Abrufen des Eintrags' }); + } + + if (!entry) { + // Kein Eintrag existiert → Erstelle neuen mit start_time + db.run(`INSERT INTO timesheet_entries (user_id, date, start_time, updated_at) VALUES (?, ?, ?, CURRENT_TIMESTAMP)`, + [userId, currentDate, currentTime], (err) => { + if (err) { + return res.status(500).json({ success: false, error: 'Fehler beim Erstellen des Eintrags' }); + } + res.json({ + success: true, + message: `Start-Zeit erfasst: ${currentTime}`, + start_time: currentTime, + date: currentDate + }); + }); + } else if (!entry.start_time) { + // Eintrag existiert, aber keine Start-Zeit → Setze start_time + db.run('UPDATE timesheet_entries SET start_time = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?', + [currentTime, entry.id], (err) => { + if (err) { + return res.status(500).json({ success: false, error: 'Fehler beim Aktualisieren' }); + } + res.json({ + success: true, + message: `Start-Zeit erfasst: ${currentTime}`, + start_time: currentTime, + date: currentDate + }); + }); + } else { + // Start-Zeit bereits vorhanden → Ignoriere weiteren Check-in + res.json({ + success: true, + message: `Bereits eingecheckt um ${entry.start_time}. Check-in ignoriert.`, + start_time: entry.start_time, + date: currentDate + }); + } + }); + }); +}); + +// API: Check-out (Gehen) +app.get('/api/checkout/:userId', (req, res) => { + const userId = parseInt(req.params.userId); + const currentDate = getCurrentDate(); + const currentTime = getCurrentTime(); + + // Prüfe ob User existiert + db.get('SELECT id FROM users WHERE id = ?', [userId], (err, user) => { + if (err || !user) { + return res.status(404).json({ success: false, error: 'Benutzer nicht gefunden' }); + } + + // Prüfe ob bereits ein Eintrag für heute existiert + db.get('SELECT * FROM timesheet_entries WHERE user_id = ? AND date = ? ORDER BY updated_at DESC, id DESC LIMIT 1', + [userId, currentDate], (err, entry) => { + if (err) { + return res.status(500).json({ success: false, error: 'Fehler beim Abrufen des Eintrags' }); + } + + if (!entry || !entry.start_time) { + // Kein Eintrag oder keine Start-Zeit → Fehler + return res.status(400).json({ + success: false, + error: 'Bitte zuerst einchecken (Kommen).' + }); + } + + // Berechne total_hours basierend auf start_time, end_time und break_minutes + const breakMinutes = entry.break_minutes || 0; + const totalHours = updateTotalHours(entry.start_time, currentTime, breakMinutes); + + // Setze end_time (überschreibt vorherige End-Zeit falls vorhanden) + db.run('UPDATE timesheet_entries SET end_time = ?, total_hours = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?', + [currentTime, totalHours, entry.id], (err) => { + if (err) { + return res.status(500).json({ success: false, error: 'Fehler beim Aktualisieren' }); + } + res.json({ + success: true, + message: `End-Zeit erfasst: ${currentTime}. Gesamtstunden: ${totalHours.toFixed(2)} h`, + end_time: currentTime, + total_hours: totalHours, + date: currentDate + }); + }); + }); + }); +}); + +// API: Stundenerfassung für Woche laden +app.get('/api/timesheet/week/:weekStart', requireAuth, (req, res) => { + const userId = req.session.userId; + const weekStart = req.params.weekStart; + + // Berechne Wochenende + const startDate = new Date(weekStart); + const endDate = new Date(startDate); + endDate.setDate(endDate.getDate() + 6); + const weekEnd = endDate.toISOString().split('T')[0]; + + // Prüfe ob die Woche bereits eingereicht wurde (aber ermögliche Bearbeitung) + db.get(`SELECT id, version FROM weekly_timesheets + WHERE user_id = ? AND week_start = ? AND week_end = ? + ORDER BY version DESC LIMIT 1`, + [userId, weekStart, weekEnd], + (err, weeklySheet) => { + const hasSubmittedVersion = !!weeklySheet; + const latestVersion = weeklySheet ? weeklySheet.version : 0; + + // Lade alle Einträge für die Woche + db.all(`SELECT * FROM timesheet_entries + WHERE user_id = ? AND date >= ? AND date <= ? + ORDER BY date`, + [userId, weekStart, weekEnd], + (err, entries) => { + // Füge Status-Info hinzu (Bearbeitung ist immer möglich) + const entriesWithStatus = (entries || []).map(entry => ({ + ...entry, + week_submitted: false, // Immer false, damit Bearbeitung möglich ist + latest_version: latestVersion, + has_existing_version: latestVersion > 0 + })); + res.json(entriesWithStatus); + }); + }); +}); + +// API: Woche abschicken +app.post('/api/timesheet/submit', requireAuth, (req, res) => { + const { week_start, week_end, version_reason } = req.body; + const userId = req.session.userId; + + // Validierung: Prüfen ob alle 7 Tage der Woche ausgefüllt sind + db.all(`SELECT id, date, start_time, end_time, vacation_type, updated_at FROM timesheet_entries + WHERE user_id = ? AND date >= ? AND date <= ? + ORDER BY date, updated_at DESC, id DESC`, + [userId, week_start, week_end], + (err, entries) => { + if (err) { + return res.status(500).json({ error: 'Fehler beim Prüfen der Daten' }); + } + + // Erstelle Set mit vorhandenen Daten + // WICHTIG: Wenn mehrere Einträge für denselben Tag existieren, nimm den neuesten + const entriesByDate = {}; + entries.forEach(entry => { + const existing = entriesByDate[entry.date]; + // Wenn noch kein Eintrag existiert oder dieser neuer ist, verwende ihn + if (!existing) { + entriesByDate[entry.date] = entry; + } else { + // Vergleiche updated_at (falls vorhanden) oder id (höhere ID = neuer) + const existingTime = existing.updated_at ? new Date(existing.updated_at).getTime() : 0; + const currentTime = entry.updated_at ? new Date(entry.updated_at).getTime() : 0; + if (currentTime > existingTime || (currentTime === existingTime && entry.id > existing.id)) { + entriesByDate[entry.date] = entry; + } + } + }); + + // Prüfe nur Werktage (Montag-Freitag, erste 5 Tage) + // Samstag und Sonntag sind optional + // Bei ganztägigem Urlaub (vacation_type = 'full') ist der Tag als ausgefüllt zu betrachten + // week_start ist bereits im Format YYYY-MM-DD + const startDateParts = week_start.split('-'); + const startYear = parseInt(startDateParts[0]); + const startMonth = parseInt(startDateParts[1]) - 1; // Monat ist 0-basiert + const startDay = parseInt(startDateParts[2]); + + let missingDays = []; + + for (let i = 0; i < 5; i++) { + // Datum direkt berechnen ohne Zeitzonenprobleme + const date = new Date(startYear, startMonth, startDay + i); + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + const dateStr = `${year}-${month}-${day}`; + const entry = entriesByDate[dateStr]; + + // Wenn ganztägiger Urlaub, dann ist der Tag als ausgefüllt zu betrachten + if (entry && entry.vacation_type === 'full') { + continue; // Tag ist ausgefüllt + } + + // Bei halbem Tag Urlaub oder keinem Urlaub müssen Start- und Endzeit vorhanden sein + // start_time und end_time könnten null, undefined oder leer strings sein + const hasStartTime = entry && entry.start_time && entry.start_time.toString().trim() !== ''; + const hasEndTime = entry && entry.end_time && entry.end_time.toString().trim() !== ''; + + if (!entry || !hasStartTime || !hasEndTime) { + missingDays.push(dateStr); + } + } + + if (missingDays.length > 0) { + return res.status(400).json({ + error: `Nicht alle Werktage (Montag bis Freitag) sind ausgefüllt. Fehlende Tage: ${missingDays.join(', ')}. Bitte füllen Sie alle Werktage mit Start- und Endzeit aus. Wochenende ist optional.` + }); + } + + // Alle Tage ausgefüllt - Woche abschicken (immer neue Version erstellen) + // Prüfe welche Version die letzte ist + db.get(`SELECT MAX(version) as max_version FROM weekly_timesheets + WHERE user_id = ? AND week_start = ? AND week_end = ?`, + [userId, week_start, week_end], + (err, result) => { + if (err) return res.status(500).json({ error: 'Fehler beim Prüfen der Version' }); + + const maxVersion = result && result.max_version ? result.max_version : 0; + const newVersion = maxVersion + 1; + + // Wenn bereits eine Version existiert, ist version_reason erforderlich + if (maxVersion > 0 && (!version_reason || version_reason.trim() === '')) { + return res.status(400).json({ + error: 'Bitte geben Sie einen Grund für die neue Version an.' + }); + } + + // Neue Version erstellen (nicht überschreiben) + db.run(`INSERT INTO weekly_timesheets (user_id, week_start, week_end, version, status, version_reason) + VALUES (?, ?, ?, ?, 'eingereicht', ?)`, + [userId, week_start, week_end, newVersion, version_reason ? version_reason.trim() : null], + (err) => { + if (err) return res.status(500).json({ error: 'Fehler beim Abschicken' }); + + // Status der Einträge aktualisieren (optional - für Nachverfolgung) + db.run(`UPDATE timesheet_entries + SET status = 'eingereicht' + WHERE user_id = ? AND date >= ? AND date <= ?`, + [userId, week_start, week_end], + (err) => { + if (err) return res.status(500).json({ error: 'Fehler beim Aktualisieren des Status' }); + res.json({ success: true, version: newVersion }); + }); + }); + }); + }); +}); + +// API: Überstunden- und Urlaubsstatistiken für einen User abrufen +app.get('/api/verwaltung/user/:id/stats', requireVerwaltung, (req, res) => { + const userId = req.params.id; + const { week_start, week_end } = req.query; + + // User-Daten abrufen + db.get('SELECT wochenstunden, urlaubstage FROM users WHERE id = ?', [userId], (err, user) => { + if (err || !user) { + return res.status(500).json({ error: 'Fehler beim Abrufen der User-Daten' }); + } + + const wochenstunden = user.wochenstunden || 0; + const urlaubstage = user.urlaubstage || 0; + + // Einträge für die Woche abrufen + db.all(`SELECT date, total_hours, overtime_taken_hours, vacation_type + FROM timesheet_entries + WHERE user_id = ? AND date >= ? AND date <= ? + ORDER BY date`, + [userId, week_start, week_end], + (err, entries) => { + if (err) { + return res.status(500).json({ error: 'Fehler beim Abrufen der Einträge' }); + } + + // Berechnungen + let totalHours = 0; + let overtimeTaken = 0; + let vacationDays = 0; + let vacationHours = 0; + + entries.forEach(entry => { + if (entry.total_hours) { + totalHours += entry.total_hours; + } + if (entry.overtime_taken_hours) { + overtimeTaken += entry.overtime_taken_hours; + } + if (entry.vacation_type === 'full') { + vacationDays += 1; + vacationHours += 8; // Ganzer Tag = 8 Stunden + } else if (entry.vacation_type === 'half') { + vacationDays += 0.5; + vacationHours += 4; // Halber Tag = 4 Stunden + } + }); + + // Anzahl Werktage berechnen (Montag-Freitag) + const startDate = new Date(week_start); + const endDate = new Date(week_end); + let workdays = 0; + for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) { + const day = d.getDay(); + if (day >= 1 && day <= 5) { // Montag bis Freitag + workdays++; + } + } + + // Sollstunden berechnen + const sollStunden = (wochenstunden / 5) * workdays; + + // Überstunden berechnen: Urlaub zählt als normale Arbeitszeit + // Überstunden = (Tatsächliche Stunden + Urlaubsstunden) - Sollstunden + const totalHoursWithVacation = totalHours + vacationHours; + const overtimeHours = totalHoursWithVacation - sollStunden; + const remainingOvertime = overtimeHours - overtimeTaken; + + // Verbleibende Urlaubstage + const remainingVacation = urlaubstage - vacationDays; + + res.json({ + wochenstunden, + urlaubstage, + totalHours, + sollStunden, + overtimeHours, + overtimeTaken, + remainingOvertime, + vacationDays, + remainingVacation, + workdays + }); + }); + }); +}); + +// API: Admin-Kommentar speichern +app.put('/api/verwaltung/timesheet/:id/comment', requireVerwaltung, (req, res) => { + const timesheetId = req.params.id; + const { comment } = req.body; + + db.run('UPDATE weekly_timesheets SET admin_comment = ? WHERE id = ?', + [comment ? comment.trim() : null, timesheetId], + (err) => { + if (err) { + console.error('Fehler beim Speichern des Kommentars:', err); + return res.status(500).json({ error: 'Fehler beim Speichern des Kommentars' }); + } + res.json({ success: true }); + }); +}); + +// API: PDF Download-Info abrufen +app.get('/api/timesheet/download-info/:id', requireVerwaltung, (req, res) => { + const timesheetId = req.params.id; + + db.get(`SELECT wt.pdf_downloaded_at, + dl.firstname as downloaded_by_firstname, + dl.lastname as downloaded_by_lastname + FROM weekly_timesheets wt + LEFT JOIN users dl ON wt.pdf_downloaded_by = dl.id + WHERE wt.id = ?`, [timesheetId], (err, result) => { + + if (err) { + console.error('Fehler beim Abrufen der Download-Info:', err); + return res.status(500).json({ error: 'Fehler beim Abrufen der Informationen' }); + } + + if (!result) { + return res.status(404).json({ error: 'Stundenzettel nicht gefunden' }); + } + + res.json({ + downloaded: !!result.pdf_downloaded_at, + downloaded_at: result.pdf_downloaded_at, + downloaded_by_firstname: result.downloaded_by_firstname, + downloaded_by_lastname: result.downloaded_by_lastname + }); + }); +}); + +// API: PDF generieren +app.get('/api/timesheet/pdf/:id', requireVerwaltung, (req, res) => { + const timesheetId = req.params.id; + + db.get(`SELECT wt.*, u.firstname, u.lastname, u.username, u.wochenstunden + FROM weekly_timesheets wt + JOIN users u ON wt.user_id = u.id + WHERE wt.id = ?`, [timesheetId], (err, timesheet) => { + + if (err || !timesheet) { + return res.status(404).send('Stundenzettel nicht gefunden'); + } + + db.all(`SELECT * FROM timesheet_entries + WHERE user_id = ? AND date >= ? AND date <= ? + ORDER BY date`, + [timesheet.user_id, timesheet.week_start, timesheet.week_end], + (err, entries) => { + + const PDFDocument = require('pdfkit'); + const doc = new PDFDocument({ margin: 50 }); + + // Prüfe ob inline angezeigt werden soll (für Vorschau) + const inline = req.query.inline === 'true'; + + // Kalenderwoche berechnen (Funktion wird später definiert) + function getCalendarWeek(dateStr) { + const date = new Date(dateStr); + const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())); + const dayNum = d.getUTCDay() || 7; + d.setUTCDate(d.getUTCDate() + 4 - dayNum); + const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1)); + const weekNo = Math.ceil((((d - yearStart) / 86400000) + 1) / 7); + return weekNo; + } + + // Dateinamen generieren: Stundenzettel_KWxxx_NameMitarbeiter_heutigesDatum.pdf + const calendarWeek = getCalendarWeek(timesheet.week_start); + const today = new Date(); + const todayStr = today.getFullYear() + '-' + + String(today.getMonth() + 1).padStart(2, '0') + '-' + + String(today.getDate()).padStart(2, '0'); + const employeeName = `${timesheet.firstname}${timesheet.lastname}`.replace(/\s+/g, ''); + const filename = `Stundenzettel_KW${String(calendarWeek).padStart(2, '0')}_${employeeName}_${todayStr}.pdf`; + + res.setHeader('Content-Type', 'application/pdf'); + res.setHeader('X-Content-Type-Options', 'nosniff'); + + if (inline) { + res.setHeader('Content-Disposition', `inline; filename="${filename}"`); + // Zusätzliche Header für iframe-Unterstützung + res.setHeader('X-Frame-Options', 'SAMEORIGIN'); + } else { + res.setHeader('Content-Disposition', `attachment; filename="${filename}"`); + + // Marker setzen, dass PDF heruntergeladen wurde (nur bei Download, nicht bei Vorschau) + const downloadedBy = req.session.userId; // User der die PDF herunterlädt + console.log('PDF Download - User ID:', downloadedBy, 'Timesheet ID:', timesheetId); + + if (downloadedBy) { + db.run(`UPDATE weekly_timesheets + SET pdf_downloaded_at = CURRENT_TIMESTAMP, + pdf_downloaded_by = ? + WHERE id = ?`, + [downloadedBy, timesheetId], + (err) => { + if (err) { + console.error('Fehler beim Setzen des Download-Markers:', err); + } else { + console.log('Download-Marker erfolgreich gesetzt für User:', downloadedBy); + } + // Fehler wird ignoriert, damit PDF trotzdem generiert wird + }); + } else { + console.warn('PDF Download - Keine User ID in Session gefunden!'); + } + } + + doc.pipe(res); + + // Header (Kalenderwoche wurde bereits oben berechnet) + doc.fontSize(20).text(`Stundenzettel für KW ${calendarWeek}`, { align: 'center' }); + doc.moveDown(); + + // Mitarbeiter-Info + doc.fontSize(12); + doc.text(`Mitarbeiter: ${timesheet.firstname} ${timesheet.lastname}`); + doc.text(`Zeitraum: ${formatDate(timesheet.week_start)} - ${formatDate(timesheet.week_end)}`); + doc.text(`Eingereicht am: ${formatDateTime(timesheet.submitted_at)}`); + doc.moveDown(); + + // Tabelle - Basis-Informationen + const tableTop = doc.y; + const colWidths = [80, 80, 80, 60, 80]; + const headers = ['Datum', 'Start', 'Ende', 'Pause', 'Stunden']; + + // Tabellen-Header + doc.fontSize(10).font('Helvetica-Bold'); + let x = 50; + headers.forEach((header, i) => { + doc.text(header, x, tableTop, { width: colWidths[i], align: 'left' }); + x += colWidths[i]; + }); + + doc.moveDown(); + let y = doc.y; + doc.moveTo(50, y).lineTo(430, y).stroke(); + doc.moveDown(0.5); + + // Tabellen-Daten + doc.font('Helvetica'); + let totalHours = 0; + let vacationHours = 0; // Urlaubsstunden für Überstunden-Berechnung + + entries.forEach((entry) => { + y = doc.y; + x = 50; + + // Basis-Zeile + const rowData = [ + formatDate(entry.date), + entry.start_time || '-', + entry.end_time || '-', + entry.break_minutes ? `${entry.break_minutes} min` : '-', + entry.total_hours ? entry.total_hours.toFixed(2) + ' h' : '-' + ]; + + rowData.forEach((data, i) => { + doc.text(data, x, y, { width: colWidths[i], align: 'left' }); + x += colWidths[i]; + }); + + // Tätigkeiten sammeln + const activities = []; + for (let i = 1; i <= 5; i++) { + const desc = entry[`activity${i}_desc`]; + const hours = entry[`activity${i}_hours`]; + const projectNumber = entry[`activity${i}_project_number`]; + if (desc && desc.trim() && hours > 0) { + activities.push({ + desc: desc.trim(), + hours: parseFloat(hours), + projectNumber: projectNumber ? projectNumber.trim() : null + }); + } + } + + // Tätigkeiten anzeigen + if (activities.length > 0) { + doc.moveDown(0.3); + doc.fontSize(9).font('Helvetica-Oblique'); + doc.text('Tätigkeiten:', 60, doc.y, { width: 380 }); + doc.moveDown(0.2); + + activities.forEach((activity, idx) => { + let activityText = `${idx + 1}. ${activity.desc}`; + if (activity.projectNumber) { + activityText += ` (Projekt: ${activity.projectNumber})`; + } + activityText += ` - ${activity.hours.toFixed(2)} h`; + doc.fontSize(9).font('Helvetica'); + doc.text(activityText, 70, doc.y, { width: 360 }); + doc.moveDown(0.2); + }); + doc.fontSize(10); + } + + // Überstunden und Urlaub anzeigen + const overtimeInfo = []; + if (entry.overtime_taken_hours && parseFloat(entry.overtime_taken_hours) > 0) { + overtimeInfo.push(`Überstunden genommen: ${parseFloat(entry.overtime_taken_hours).toFixed(2)} h`); + } + if (entry.vacation_type) { + const vacationText = entry.vacation_type === 'full' ? 'Ganzer Tag' : 'Halber Tag'; + overtimeInfo.push(`Urlaub: ${vacationText}`); + } + + if (overtimeInfo.length > 0) { + doc.moveDown(0.2); + doc.fontSize(9).font('Helvetica-Oblique'); + overtimeInfo.forEach((info, idx) => { + doc.text(info, 70, doc.y, { width: 360 }); + doc.moveDown(0.15); + }); + doc.fontSize(10); + } + + if (entry.total_hours) { + totalHours += entry.total_hours; + } + + // Urlaubsstunden für Überstunden-Berechnung sammeln + if (entry.vacation_type === 'full') { + vacationHours += 8; // Ganzer Tag = 8 Stunden + } else if (entry.vacation_type === 'half') { + vacationHours += 4; // Halber Tag = 4 Stunden + } + + doc.moveDown(0.5); + + // Trennlinie zwischen Einträgen + y = doc.y; + doc.moveTo(50, y).lineTo(430, y).stroke(); + doc.moveDown(0.3); + }); + + // Summe + y = doc.y; + doc.moveTo(50, y).lineTo(550, y).stroke(); + doc.moveDown(0.5); + doc.font('Helvetica-Bold'); + doc.text(`Gesamtstunden: ${totalHours.toFixed(2)} h`, 50, doc.y); + + // Überstunden berechnen und anzeigen + const wochenstunden = timesheet.wochenstunden || 0; + // Überstunden = Gesamtstunden - Wochenstunden + // Urlaub zählt als normale Arbeitszeit, daher sind Urlaubsstunden bereits in totalHours enthalten + const overtimeHours = totalHours - wochenstunden; + + doc.moveDown(0.3); + doc.font('Helvetica-Bold'); + if (overtimeHours > 0) { + doc.text(`Überstunden: +${overtimeHours.toFixed(2)} h`, 50, doc.y); + } else if (overtimeHours < 0) { + doc.text(`Überstunden: ${overtimeHours.toFixed(2)} h`, 50, doc.y); + } else { + doc.text(`Überstunden: 0.00 h`, 50, doc.y); + } + + doc.end(); + }); + }); +}); + +// Hilfsfunktionen +function formatDate(dateStr) { + const date = new Date(dateStr); + return date.toLocaleDateString('de-DE'); +} + +function formatDateTime(dateStr) { + const date = new Date(dateStr); + return date.toLocaleString('de-DE'); +} + +// Start-Route +app.get('/', (req, res) => { + if (req.session.userId) { + // Redirect: Immer zu Dashboard wenn Mitarbeiter-Rolle vorhanden, sonst basierend auf currentRole + const roles = req.session.roles || []; + if (roles.includes('mitarbeiter')) { + res.redirect('/dashboard'); + } else { + const currentRole = req.session.currentRole || getDefaultRole(roles); + if (currentRole === 'admin') { + res.redirect('/admin'); + } else if (currentRole === 'verwaltung') { + res.redirect('/verwaltung'); + } else { + res.redirect('/dashboard'); + } + } + } else { + res.redirect('/login'); + } +}); + +// Automatische LDAP-Synchronisation einrichten +function setupLDAPScheduler() { + // Prüfe alle 5 Minuten, ob eine Synchronisation notwendig ist + setInterval(() => { + db.get('SELECT * FROM ldap_config WHERE id = 1 AND enabled = 1 AND sync_interval > 0', (err, config) => { + if (err || !config) { + return; // Keine aktive Konfiguration + } + + const now = new Date(); + const lastSync = config.last_sync ? new Date(config.last_sync) : null; + const syncIntervalMs = config.sync_interval * 60 * 1000; // Minuten in Millisekunden + + // Prüfe ob Synchronisation fällig ist + if (!lastSync || (now - lastSync) >= syncIntervalMs) { + console.log('Starte automatische LDAP-Synchronisation...'); + LDAPService.performSync('scheduled', (err, result) => { + if (err) { + console.error('Fehler bei automatischer LDAP-Synchronisation:', err.message); + } else { + console.log(`Automatische LDAP-Synchronisation abgeschlossen: ${result.synced} Benutzer synchronisiert`); + } + }); + } + }); + }, 5 * 60 * 1000); // Alle 5 Minuten prüfen +} + +// Server starten +app.listen(PORT, () => { + console.log(`Server läuft auf http://localhost:${PORT}`); + console.log('Standard-Zugangsdaten:'); + console.log('Admin: admin / admin123'); + console.log('Verwaltung: verwaltung / verwaltung123'); + + // LDAP-Scheduler starten + setupLDAPScheduler(); +}); diff --git a/views/admin.ejs b/views/admin.ejs new file mode 100644 index 0000000..22b8719 --- /dev/null +++ b/views/admin.ejs @@ -0,0 +1,348 @@ + + + + + + Admin - Stundenerfassung + + + + + +
+
+

Benutzerverwaltung

+ +
+

Neuen Benutzer anlegen

+
+
+
+ + +
+ +
+ + +
+
+ +
+
+ + +
+ +
+ + +
+
+ +
+ +
+ + + +
+ Wählen Sie eine oder mehrere Rollen aus +
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
+
+ +
+

Benutzer-Liste

+ + + + + + + + + + + + + + + + + <% users.forEach(function(u) { %> + + + + + + + + + + + + + <% }); %> + +
IDBenutzernameVornameNachnameRollePersonalnummerWochenstundenUrlaubstageErstellt amAktionen
<%= u.id %><%= u.username %><%= u.firstname %><%= u.lastname %> +
+ <% + const roleLabels = { 'mitarbeiter': 'Mitarbeiter', 'verwaltung': 'Verwaltung', 'admin': 'Admin' }; + const userRoles = u.roles || []; + if (userRoles.length > 0) { + userRoles.forEach(function(role, idx) { %> + <%= roleLabels[role] || role %> + <% }); + } else { + %> + Mitarbeiter + <% } %> +
+ +
+ <%= u.personalnummer || '-' %> + + + <%= u.wochenstunden || '-' %> + + + <%= u.urlaubstage || '-' %> + + <%= new Date(u.created_at).toLocaleDateString('de-DE') %> + + + + <% if (u.id > 2) { %> + + <% } else { %> + System + <% } %> +
+
+ +
+

LDAP-Synchronisation

+ +
+

LDAP-Konfiguration

+
+
+ +
+ +
+
+ + +
+ +
+ + +
+
+ +
+
+ + +
+ +
+ + +
+
+ +
+ + +
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+ + + 0 = nur manuelle Synchronisation +
+ + +
+
+ +
+

Synchronisation

+
+ + +
+ + <% if (ldapConfig && ldapConfig.last_sync) { %> +

Letzte Synchronisation: <%= new Date(ldapConfig.last_sync).toLocaleString('de-DE') %>

+ <% } else { %> +

Letzte Synchronisation: Noch keine Synchronisation durchgeführt

+ <% } %> +
+ +
+

Sync-Log (letzte 10 Einträge)

+ + + + + + + + + + + + <% if (syncLogs && syncLogs.length > 0) { %> + <% syncLogs.forEach(function(log) { %> + + + + + + + + <% }); %> + <% } else { %> + + + + <% } %> + +
ZeitpunktTypStatusBenutzer synchronisiertFehlermeldung
<%= new Date(log.sync_started_at).toLocaleString('de-DE') %><%= log.sync_type === 'manual' ? 'Manuell' : 'Automatisch' %> + + <%= log.status === 'success' ? 'Erfolg' : 'Fehler' %> + + <%= log.users_synced %><%= log.error_message || '-' %>
Keine Log-Einträge vorhanden
+
+
+
+
+ + + + + diff --git a/views/dashboard.ejs b/views/dashboard.ejs new file mode 100644 index 0000000..7acaf7b --- /dev/null +++ b/views/dashboard.ejs @@ -0,0 +1,167 @@ + + + + + + + Dashboard - Stundenerfassung + + + + + +
+
+
+
+ +

Kalenderwoche

+ +
+ +
+ +
+ +
+
+ Gesamtstunden diese Woche: + 0.00 h +
+
+ +
+ +

Stunden werden automatisch gespeichert. Am Ende der Woche können Sie die Stunden abschicken.

+
+
+ + +
+

Ihre Auswertung

+
+
Aktuelle Überstunden
+
-
+
Stunden
+
+
+
Verbleibende Urlaubstage
+
-
+
von - Tagen
+
+ + +
+

Schnelle Zeiterfassung

+
+ +
+ + +
+
+
+ +
+ + +
+
+

+ Diese URLs können Sie in einer App eintragen oder direkt im Browser aufrufen, um Ihre Arbeitszeiten zu erfassen. +

+
+
+
+
+ + + + + diff --git a/views/login.ejs b/views/login.ejs new file mode 100644 index 0000000..aa689ed --- /dev/null +++ b/views/login.ejs @@ -0,0 +1,35 @@ + + + + + + Login - Stundenerfassung + + + + + + diff --git a/views/verwaltung.ejs b/views/verwaltung.ejs new file mode 100644 index 0000000..adfb761 --- /dev/null +++ b/views/verwaltung.ejs @@ -0,0 +1,515 @@ + + + + + + Verwaltung - Stundenerfassung + + + + + +
+
+

Postfach - Eingereichte Stundenzettel

+ + <% if (!groupedByEmployee || groupedByEmployee.length === 0) { %> +
+

Keine eingereichten Stundenzettel vorhanden.

+
+ <% } else { %> +
+ <% groupedByEmployee.forEach(function(employee, employeeIndex) { %> + +
+
+
+
+ <%= employee.user.firstname %> <%= employee.user.lastname %> + <% if (employee.user.personalnummer) { %> + (Personalnummer: <%= employee.user.personalnummer %>) + <% } %> +
+
+
+ Wochenstunden: <%= employee.user.wochenstunden || '-' %> +
+
+ Urlaubstage: <%= employee.user.urlaubstage || '-' %> +
+
+ Kalenderwochen: <%= employee.weeks.length %> +
+
+
+ +
+ + + +
+ <% }); %> +
+ <% } %> +
+
+ + + + +