diff --git a/INSTALL.md b/INSTALL.md deleted file mode 100644 index 99c08ab..0000000 --- a/INSTALL.md +++ /dev/null @@ -1,242 +0,0 @@ -# MYP System - Installationsanleitung - -Dieses Dokument beschreibt die Installation des MYP-Systems, bestehend aus einem Frontend und einem Backend. - -## Systemanforderungen - -- **Frontend**: - - Raspberry Pi 3B+ oder neuer (empfohlen: Pi 4 mit mindestens 2GB RAM) - - Raspbian/Raspberry Pi OS (64-bit empfohlen) - - Docker und Docker Compose (werden automatisch installiert) - - Internet-Zugang für die Installation - - Zwei Netzwerk-Schnittstellen: - - Eine mit Internet-Zugang - - Eine für die Verbindung zum Backend-Netzwerk - -- **Backend**: - - Raspberry Pi 3B+ oder neuer (empfohlen: Pi 4 mit mindestens 2GB RAM) - - Raspbian/Raspberry Pi OS (64-bit empfohlen) - - Docker und Docker Compose (werden automatisch installiert) - - Verbindung zum Druckernetzwerk - -## 1. Installation des Backends - -Das Backend wird auf dem ersten Raspberry Pi installiert, der mit den Smart Plugs verbunden ist. - -```bash -# Den Code auf den Raspberry Pi kopieren -scp -r /pfad/zum/projektverzeichnis pi@raspberry-backend:/home/pi/myp - -# SSH-Verbindung herstellen -ssh pi@raspberry-backend - -# In das Projektverzeichnis wechseln -cd /home/pi/myp - -# Installations-Skript ausführbar machen und ausführen -chmod +x install-backend.sh -./install-backend.sh -``` - -Das Skript erledigt folgende Aufgaben: -- Installation von Docker und Docker Compose (falls nicht vorhanden) -- Erstellung der nötigen Verzeichnisse und Dateien -- Erstellung und Start des Docker-Containers -- Initialisierung der Datenbank -- Überprüfung, ob der Service korrekt läuft - -Nach erfolgreicher Installation ist das Backend unter `http://raspberry-backend:5000` erreichbar. - -## 2. Installation des Frontends - -Das Frontend wird auf dem zweiten Raspberry Pi installiert, der mit dem Unternehmensnetzwerk verbunden ist. - -```bash -# Den Code auf den Raspberry Pi kopieren -scp -r /pfad/zum/projektverzeichnis pi@raspberry-frontend:/home/pi/myp - -# SSH-Verbindung herstellen -ssh pi@raspberry-frontend - -# In das Projektverzeichnis wechseln -cd /home/pi/myp - -# Installations-Skript ausführbar machen und ausführen -chmod +x install-frontend.sh -./install-frontend.sh -``` - -Das Skript erledigt folgende Aufgaben: -- Installation von Docker und Docker Compose (falls nicht vorhanden) -- Erstellung der benötigten Verzeichnisse und Dateien -- Erstellung und Start des Docker-Containers -- Überprüfung, ob der Service korrekt läuft - -Nach erfolgreicher Installation ist das Frontend unter `http://raspberry-frontend:3000` erreichbar. - -## 3. Konfiguration der Verbindung zwischen Frontend und Backend - -Für die Kommunikation zwischen Frontend und Backend muss die API-URL im Frontend konfiguriert werden: - -1. Die Datei `/home/pi/myp/packages/reservation-platform/.env` auf dem Frontend-Raspberry Pi bearbeiten: - -``` -# Basic Server Configuration -RUNTIME_ENVIRONMENT=prod -DB_PATH=db/sqlite.db - -# OAuth Configuration -OAUTH_CLIENT_ID=client_id -OAUTH_CLIENT_SECRET=client_secret - -# Backend-URL (Hostname oder IP-Adresse des Backend-Raspberry Pi) -NEXT_PUBLIC_API_URL=http://raspberry-backend:5000 -``` - -2. Frontend-Container neu starten: - -```bash -cd /home/pi/myp/packages/reservation-platform -docker-compose down -docker-compose up -d -``` - -## 4. Wartung und Fehlerbehebung - -### Logs anzeigen - -**Backend:** -```bash -docker logs -f myp-backend -``` - -**Frontend:** -```bash -docker logs -f myp-frontend -``` - -### Container neustarten - -**Backend:** -```bash -cd /pfad/zum/backend -docker-compose restart -``` - -**Frontend:** -```bash -cd /pfad/zum/frontend -docker-compose restart -``` - -### Datenbank-Reset - -Sollte die Datenbank zurückgesetzt werden müssen: - -```bash -# Auf dem Backend-Raspberry Pi -cd /home/pi/myp/backend -docker-compose down -rm -f instance/myp.db -docker-compose up -d -``` - -### Docker Compose YAML-Fehler - -Wenn Sie einen YAML-Fehler in der Docker Compose-Datei erhalten: - -``` -yaml: line 12: did not find expected key -``` - -Überprüfen Sie folgende Punkte: -1. Die Docker Compose-Version könnte veraltet sein. Die Installationsskripte installieren automatisch die richtige Version. -2. Es könnte ein Syntaxfehler in der YAML-Datei vorliegen. Prüfen Sie insbesondere komplexe Werte wie JSON-Strings. - -Fehlerbehebung: -```bash -# Auf dem betroffenen Server -cd /home/pi/myp -# Für das Backend -nano backend/docker-compose.yml -# Für das Frontend -nano packages/reservation-platform/docker-compose.yml -``` - -### Docker-Daemon läuft nicht - -Wenn Sie die Fehlermeldung erhalten, dass der Docker-Daemon nicht läuft: - -``` -Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? -``` - -Starten Sie den Docker-Daemon: -```bash -sudo systemctl start docker -# oder -sudo service docker start -``` - -### Container startet nicht - -Wenn der Container nicht startet, prüfen Sie die Logs: - -```bash -docker logs myp-backend -# oder -docker logs myp-frontend -``` - -### Frontend kann nicht auf Backend zugreifen - -1. Stellen Sie sicher, dass beide Server im selben Netzwerk sind -2. Überprüfen Sie die Firewall-Einstellungen -3. Stellen Sie sicher, dass der Backend-Service auf Port 5000 läuft -4. Stellen Sie sicher, dass die richtige Backend-URL in der .env-Datei des Frontends eingestellt ist - -## 5. Automatischer Start beim Systemstart - -Die Docker-Container sind so konfiguriert, dass sie automatisch beim Neustart der Geräte starten (`restart: unless-stopped`). - -Sollte dies nicht funktionieren, kann der Start in die `/etc/rc.local` eingetragen werden: - -```bash -# Auf dem Backend-Raspberry Pi -echo "cd /home/pi/myp/backend && docker-compose up -d" >> /etc/rc.local - -# Auf dem Frontend-Raspberry Pi -echo "cd /home/pi/myp/packages/reservation-platform && docker-compose up -d" >> /etc/rc.local -``` - -## 6. Technische Details - -- Das Backend ist eine Flask-Anwendung, die mit den Smart Plugs kommuniziert -- Das Frontend ist eine Next.js-Anwendung -- Beide Komponenten laufen in Docker-Containern mit Host-Netzwerkanbindung -- Die Datenbanken werden in Docker-Volumes persistiert - -## 7. Raspberry Pi-spezifische Anmerkungen - -Wenn Sie Probleme mit der Docker-Installation auf dem Raspberry Pi haben, können Sie folgende Schritte manuell ausführen: - -```bash -# Docker für Raspberry Pi installieren -curl -fsSL https://get.docker.com -o get-docker.sh -sudo sh get-docker.sh -sudo usermod -aG docker $USER - -# Docker Compose für die richtige Architektur installieren -# Für 32-bit (armhf): -sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-Linux-armv7" -o /usr/local/bin/docker-compose - -# Für 64-bit (arm64): -sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-Linux-aarch64" -o /usr/local/bin/docker-compose - -sudo chmod +x /usr/local/bin/docker-compose -``` - -## 8. Unterstützung - -Bei Fragen oder Problemen wenden Sie sich an: -- Till Tomczak (Projektentwickler) \ No newline at end of file diff --git a/README.Frontend.md b/README.Frontend.md deleted file mode 100644 index e8720ef..0000000 --- a/README.Frontend.md +++ /dev/null @@ -1,79 +0,0 @@ -# Frontend-Wiederherstellung und Installation - -Diese Anleitung erklärt, wie du das Frontend auf den Stand von Torbens letztem Commit zurücksetzen und installieren kannst. - -## Vorhandene Skripte - -Es wurden drei Skripte erstellt, um die Wiederherstellung des Frontends zu erleichtern: - -1. `fix-frontend-install.sh` - Master-Skript, das beide unten genannten Skripte ausführt -2. `packages/restore-torben-frontend.sh` - Setzt das Frontend auf Torbens letzten Commit zurück -3. `packages/install-torben-frontend.sh` - Installiert die Abhängigkeiten des wiederhergestellten Frontends - -## Schnelle Lösung - -Für die schnellste Lösung führe einfach das Master-Skript aus: - -```bash -chmod +x fix-frontend-install.sh -./fix-frontend-install.sh -``` - -Das Skript wird: -1. Das aktuelle Frontend-Verzeichnis sichern (optional) -2. Das Frontend auf Torbens letzten Commit (27. Mai 2024) zurücksetzen -3. Die Änderungen committen (optional) -4. Die Frontend-Abhängigkeiten installieren -5. Das Frontend bauen, um die Installation zu verifizieren - -## Manuelle Schritte - -Wenn du die Schritte manuell ausführen möchtest: - -### 1. Frontend zurücksetzen - -```bash -chmod +x packages/restore-torben-frontend.sh -./packages/restore-torben-frontend.sh -``` - -### 2. Frontend installieren - -```bash -chmod +x packages/install-torben-frontend.sh -./packages/install-torben-frontend.sh -``` - -## Das System starten - -### Frontend starten - -```bash -cd packages/reservation-platform -pnpm dev -``` - -Das Frontend ist dann unter http://localhost:3000 erreichbar. - -### Backend starten - -```bash -cd backend -source venv/bin/activate -python app.py -``` - -Das Backend läuft dann auf http://localhost:5000. - -### Backend-Autostart konfigurieren - -Für den automatischen Start des Backends beim Hochfahren: - -```bash -sudo ./backend/autostart-backend.sh -``` - -## Bekannte Probleme - -- Wenn beim Frontend-Start Fehler auftreten, überprüfe die .env-Datei in packages/reservation-platform/ -- Stelle sicher, dass das Backend erreichbar ist unter der URL, die in NEXT_PUBLIC_API_URL konfiguriert ist \ No newline at end of file diff --git a/archiv/NETWORK-api-backend_blueprint/.env b/archiv/NETWORK-api-backend_blueprint/.env deleted file mode 100755 index 471cbfc..0000000 --- a/archiv/NETWORK-api-backend_blueprint/.env +++ /dev/null @@ -1 +0,0 @@ -PRINTER_IPS=192.168.0.10,192.168.0.11,192.168.0.12 diff --git a/archiv/NETWORK-api-backend_blueprint/README.md b/archiv/NETWORK-api-backend_blueprint/README.md deleted file mode 100755 index 380bc5d..0000000 --- a/archiv/NETWORK-api-backend_blueprint/README.md +++ /dev/null @@ -1,106 +0,0 @@ -# 🖨️ 3D-Drucker Status API 📊 - -Willkommen beim Blueprint der 3D-Drucker Status API! Diese API ermöglicht es Ihnen, den Status mehrerer über LAN verbundener 3D-Drucker zu überwachen und Druckaufträge an sie zu senden. - -## 🌟 Funktionen - -- 🔍 Abrufen des Status von 3D-Druckern, einschließlich ihres aktuellen Status, Fortschrittes und Temperatur. -- 📥 Senden von Druckaufträgen an verfügbare 3D-Drucker. -- 💾 Speichern und Aktualisieren des Status jedes Druckers in einer SQLite-Datenbank. - -## 🛠️Verwendete Technologien - -- 🐍 Python -- 🌶️ Flask -- 🗄️ SQLite -- 🌐 HTTP-Anfragen - -## 📋 Verordnungen - -Bevor Sie die API starten, stellen Sie sicher, dass Sie folgendes haben: - -- Python 3.x installiert -- Flask und python-dotenv-Bibliotheken installiert (`pip install flask python-dotenv`) -- Eine Liste von IP-Adressen der 3D-Drucker, die Sie überwachen möchten - -## 🚀 Erste Schritte - -1. Klonen Sie das Repository: - ``` - git clone https://git.i.mercedes-benz.com/TBA-Berlin-FI/MYP - ``` - -2. Installieren Sie die erforderlichen Abhängigkeiten: - ``` - pip install -r requirements.txt - ``` - -3. Erstellen Sie eine `.env`-Datei im Projektverzeichnis und geben Sie die IP-Adressen Ihrer 3D-Drucker an: - ``` - PRINTER_IPS=192.168.0.10,192.168.0.11,192.168.0.12 - ``` - -4. Starten Sie das Skript, um die SQLite-Datenbank zu erstellen: - ``` - python create_db.py - ``` - -5. Starten Sie den API-Server: - ``` - python app.py - ``` - -6. Die API ist unter `http://localhost:5000` erreichbar. - -## 📡 API-Endpunkte - -- `GET /printer_status`: Rufen Sie den Status aller 3D-Drucker ab. -- `POST /print_job`: Senden Sie einen Druckauftrag an einen bestimmten 3D-Drucker. - -## 📝 API-Nutzung - -### Druckerstatus abrufen - -Senden Sie eine `GET`-Anfrage an `/printer_status`, um den Status aller 3D-Drucker abzurufen. - -Antwort: -```json -[ - { - "ip": "192.168.0.10", - "status": "frei", - "progress": 0, - "temperature": 25 - }, - { - "ip": "192.168.0.11", - "status": "besetzt", - "progress": 50, - "temperature": 180 - }, - ... -] -``` - -### Druckauftrag senden - -Senden Sie eine `POST`-Anfrage an `/print_job` mit der folgenden JSON-Last, um einen Druckauftrag an einen bestimmten 3D-Drucker zu senden: - -```json -{ - "printer_ip": "192.168.0.10", - "file_url": "http://example.com/print_file.gcode" -} -``` - -Antwort: -```json -{ - "message": "Druckauftrag gestartet" -} -``` - - -## 📄 Lizenz - -- --> Noch nicht verfügbar diff --git a/archiv/NETWORK-api-backend_blueprint/datenbank_erstellen.py b/archiv/NETWORK-api-backend_blueprint/datenbank_erstellen.py deleted file mode 100755 index efa3eac..0000000 --- a/archiv/NETWORK-api-backend_blueprint/datenbank_erstellen.py +++ /dev/null @@ -1,25 +0,0 @@ -import sqlite3 -from dotenv import load_dotenv -import os - -load_dotenv() -printers = os.getenv('PRINTER_IPS').split(',') - -def create_db(): - conn = sqlite3.connect('printers.db') - c = conn.cursor() - - # Tabelle 'printers' erstellen, falls sie nicht existiert - c.execute('''CREATE TABLE IF NOT EXISTS printers - (ip TEXT PRIMARY KEY, status TEXT)''') - - # Drucker-IPs in die Tabelle einfügen, falls sie noch nicht vorhanden sind - for printer_ip in printers: - c.execute("INSERT OR IGNORE INTO printers (ip, status) VALUES (?, ?)", (printer_ip, "frei")) - - conn.commit() - conn.close() - print("Datenbank 'printers.db' erfolgreich erstellt.") - -if __name__ == '__main__': - create_db() diff --git a/archiv/NETWORK-api-backend_blueprint/requirements.txt b/archiv/NETWORK-api-backend_blueprint/requirements.txt deleted file mode 100755 index d4d5a7f..0000000 --- a/archiv/NETWORK-api-backend_blueprint/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -flask==2.1.0 -requests==2.25.1 -python-dotenv==0.20.0 diff --git a/archiv/NETWORK-api-backend_blueprint/server.py b/archiv/NETWORK-api-backend_blueprint/server.py deleted file mode 100755 index 366f8ea..0000000 --- a/archiv/NETWORK-api-backend_blueprint/server.py +++ /dev/null @@ -1,94 +0,0 @@ -from flask import Flask, jsonify, request -import requests -import sqlite3 -from dotenv import load_dotenv -import os - -load_dotenv() -printers = os.getenv('PRINTER_IPS').split(',') - -app = Flask(__name__) - -# SQLite-Datenbank initialisieren -def init_db(): - conn = sqlite3.connect('printers.db') - c = conn.cursor() - c.execute('''CREATE TABLE IF NOT EXISTS printers - (ip TEXT PRIMARY KEY, status TEXT)''') - for printer_ip in printers: - c.execute("INSERT OR IGNORE INTO printers (ip, status) VALUES (?, ?)", (printer_ip, "frei")) - conn.commit() - conn.close() - -@app.route('/printer_status', methods=['GET']) -def get_printer_status(): - printer_status = [] - conn = sqlite3.connect('printers.db') - c = conn.cursor() - - for printer_ip in printers: - c.execute("SELECT status FROM printers WHERE ip = ?", (printer_ip,)) - status = c.fetchone()[0] - - try: - response = requests.get(f"http://{printer_ip}/api/printer/status") - - if response.status_code == 200: - status_data = response.json() - printer_status.append({ - "ip": printer_ip, - "status": status, - "progress": status_data["progress"], - "temperature": status_data["temperature"] - }) - else: - printer_status.append({ - "ip": printer_ip, - "status": "Fehler bei der Abfrage", - "progress": None, - "temperature": None - }) - except: - printer_status.append({ - "ip": printer_ip, - "status": "Drucker nicht erreichbar", - "progress": None, - "temperature": None - }) - - conn.close() - return jsonify(printer_status) - -@app.route('/print_job', methods=['POST']) -def submit_print_job(): - print_job = request.json - printer_ip = print_job["printer_ip"] - file_url = print_job["file_url"] - - conn = sqlite3.connect('printers.db') - c = conn.cursor() - c.execute("SELECT status FROM printers WHERE ip = ?", (printer_ip,)) - status = c.fetchone()[0] - - if status == "frei": - try: - response = requests.post(f"http://{printer_ip}/api/print_job", json={"file_url": file_url}) - - if response.status_code == 200: - c.execute("UPDATE printers SET status = 'besetzt' WHERE ip = ?", (printer_ip,)) - conn.commit() - conn.close() - return jsonify({"message": "Druckauftrag gestartet"}), 200 - else: - conn.close() - return jsonify({"message": "Fehler beim Starten des Druckauftrags"}), 500 - except: - conn.close() - return jsonify({"message": "Drucker nicht erreichbar"}), 500 - else: - conn.close() - return jsonify({"message": "Drucker ist nicht frei"}), 400 - -if __name__ == '__main__': - init_db() - app.run(host='0.0.0.0', port=5000) diff --git a/archiv/NETWORK-api-backend_blueprint/ultimaker_example-integration.py b/archiv/NETWORK-api-backend_blueprint/ultimaker_example-integration.py deleted file mode 100755 index 9adfd56..0000000 --- a/archiv/NETWORK-api-backend_blueprint/ultimaker_example-integration.py +++ /dev/null @@ -1,38 +0,0 @@ -# entwendet aus: -# https://github.com/ut-hnl-lab/ultimakerpy - -# auch zum lesen: -# https://github.com/MartinBienz/SDPremote?tab=readme-ov-file - -import time -from ultimakerpy import UMS3, JobState - -def print_started(state): - if state == JobState.PRINTING: - time.sleep(6.0) - return True - return False - -def layer_reached(pos, n): - if round(pos / 0.2) >= n: # set layer pitch: 0.2 mm - return True - return False - -printer = UMS3(name='MyPrinterName') -targets = { - 'job_state': printer.job_state, - 'bed_pos': printer.bed.position, -} - -printer.print_from_dialog() # select file to print -printer.peripherals.camera_streaming() -with printer.data_logger('output2.csv', targets) as dl: - timer = dl.get_timer() - - # sleep until active leveling finishes - timer.wait_for_datalog('job_state', print_started) - - for n in range(1, 101): - # sleep until the printing of specified layer to start - timer.wait_for_datalog('bed_pos', lambda v: layer_reached(v, n)) - print('printing layer:', n) diff --git a/archiv/backend/myp_backend.db b/archiv/backend/myp_backend.db deleted file mode 100755 index e69de29..0000000 diff --git a/archiv/backend/myp_backend.py b/archiv/backend/myp_backend.py deleted file mode 100755 index e79a8af..0000000 --- a/archiv/backend/myp_backend.py +++ /dev/null @@ -1,148 +0,0 @@ -from flask import Flask, render_template, request, redirect, url_for, jsonify, session -import sqlite3 -import bcrypt - -app = Flask(__name__) -app.secret_key = 'supersecretkey' - -# Database setup -def init_db(): - conn = sqlite3.connect('database.db') - c = conn.cursor() - c.execute('''CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, username TEXT, password TEXT)''') - c.execute('''CREATE TABLE IF NOT EXISTS printers (id INTEGER PRIMARY KEY, name TEXT, status TEXT)''') - c.execute('''CREATE TABLE IF NOT EXISTS jobs (id INTEGER PRIMARY KEY, printer_id INTEGER, user TEXT, date TEXT, status TEXT)''') - conn.commit() - conn.close() - -init_db() - -# User registration (Admin setup) -def add_admin(): - conn = sqlite3.connect('database.db') - c = conn.cursor() - hashed_pw = bcrypt.hashpw('adminpassword'.encode('utf-8'), bcrypt.gensalt()) - c.execute("INSERT INTO users (username, password) VALUES (?, ?)", ('admin', hashed_pw)) - conn.commit() - conn.close() - -# Comment the next line after the first run -# add_admin() - -# API Endpoints -@app.route('/api/printers/status', methods=['GET']) -def get_printer_status(): - conn = sqlite3.connect('database.db') - c = conn.cursor() - c.execute("SELECT * FROM printers") - printers = c.fetchall() - conn.close() - return jsonify(printers) - -@app.route('/api/printers/job', methods=['POST']) -def create_job(): - if not session.get('logged_in'): - return jsonify({'error': 'Unauthorized'}), 403 - - data = request.json - user = session['username'] - printer_id = data['printer_id'] - conn = sqlite3.connect('database.db') - c = conn.cursor() - - c.execute("SELECT status FROM printers WHERE id=?", (printer_id,)) - status = c.fetchone()[0] - - if status == 'frei': - c.execute("INSERT INTO jobs (printer_id, user, date, status) VALUES (?, ?, datetime('now'), 'in progress')", - (printer_id, user)) - c.execute("UPDATE printers SET status='belegt' WHERE id=?", (printer_id,)) - conn.commit() - elif status == 'belegt': - return jsonify({'error': 'Printer already in use'}), 409 - else: - return jsonify({'error': 'Invalid printer status'}), 400 - - conn.close() - return jsonify({'message': 'Job created and printer turned on'}), 200 - -@app.route('/api/printers/reserve', methods=['POST']) -def reserve_printer(): - if not session.get('logged_in'): - return jsonify({'error': 'Unauthorized'}), 403 - - data = request.json - printer_id = data['printer_id'] - conn = sqlite3.connect('database.db') - c = conn.cursor() - - c.execute("SELECT status FROM printers WHERE id=?", (printer_id,)) - status = c.fetchone()[0] - - if status == 'frei': - c.execute("UPDATE printers SET status='reserviert' WHERE id=?", (printer_id,)) - conn.commit() - message = 'Printer reserved' - else: - message = 'Printer cannot be reserved' - - conn.close() - return jsonify({'message': message}), 200 - -@app.route('/api/printers/release', methods=['POST']) -def release_printer(): - if not session.get('logged_in'): - return jsonify({'error': 'Unauthorized'}), 403 - - data = request.json - printer_id = data['printer_id'] - conn = sqlite3.connect('database.db') - c = conn.cursor() - - c.execute("UPDATE printers SET status='frei' WHERE id=?", (printer_id,)) - conn.commit() - conn.close() - return jsonify({'message': 'Printer released'}), 200 - -# Authentication routes -@app.route('/login', methods=['GET', 'POST']) -def login(): - if request.method == 'POST': - username = request.form['username'] - password = request.form['password'].encode('utf-8') - - conn = sqlite3.connect('database.db') - c = conn.cursor() - c.execute("SELECT * FROM users WHERE username=?", (username,)) - user = c.fetchone() - conn.close() - - if user and bcrypt.checkpw(password, user[2].encode('utf-8')): - session['logged_in'] = True - session['username'] = username - return redirect(url_for('dashboard')) - else: - return render_template('login.html', error='Invalid Credentials') - - return render_template('login.html') - -@app.route('/dashboard') -def dashboard(): - if not session.get('logged_in'): - return redirect(url_for('login')) - - conn = sqlite3.connect('database.db') - c = conn.cursor() - c.execute("SELECT * FROM printers") - printers = c.fetchall() - conn.close() - - return render_template('dashboard.html', printers=printers) - -@app.route('/logout') -def logout(): - session.clear() - return redirect(url_for('login')) - -if __name__ == '__main__': - app.run(debug=True) diff --git a/archiv/backend/templates/base.html b/archiv/backend/templates/base.html deleted file mode 100755 index 092d9aa..0000000 --- a/archiv/backend/templates/base.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - 3D Printer Management - - - - - -
- {% block content %}{% endblock %} -
- - diff --git a/archiv/backend/templates/dashboard.html b/archiv/backend/templates/dashboard.html deleted file mode 100755 index 812b5a6..0000000 --- a/archiv/backend/templates/dashboard.html +++ /dev/null @@ -1,29 +0,0 @@ -{% extends "base.html" %} - -{% block content %} -

Printer Status

-
- {% for printer in printers %} -
-
-

{{ printer[1] }}

-

Status: {{ printer[2] }}

- {% if printer[2] == 'frei' %} -
- - -
- {% elif printer[2] == 'belegt' %} - - {% elif printer[2] == 'reserviert' %} -
- - -
- {% endif %} -
-
- {% endfor %} -
-Logout -{% endblock %} diff --git a/archiv/backend/templates/login.html b/archiv/backend/templates/login.html deleted file mode 100755 index f91fdca..0000000 --- a/archiv/backend/templates/login.html +++ /dev/null @@ -1,33 +0,0 @@ -{% extends "base.html" %} - -{% block content %} -
-
-
-

Login

-
-
- - -
-
- - -
-
- -
-
- {% if error %} -
- {{ error }} -
- {% endif %} -
-
-
-{% endblock %} diff --git a/archiv/flask-backend/.env b/archiv/flask-backend/.env deleted file mode 100755 index 87450c8..0000000 --- a/archiv/flask-backend/.env +++ /dev/null @@ -1,3 +0,0 @@ -SECRET_KEY=dev-secret-key-change-in-production -DATABASE_URL=sqlite:///app.db -JWT_SECRET=dev-jwt-secret-change-in-production \ No newline at end of file diff --git a/archiv/flask-backend/.env.example b/archiv/flask-backend/.env.example deleted file mode 100755 index 3f4988c..0000000 --- a/archiv/flask-backend/.env.example +++ /dev/null @@ -1,3 +0,0 @@ -SECRET_KEY=change-me-to-a-real-secret-key -DATABASE_URL=sqlite:///app.db -JWT_SECRET=change-me-to-a-real-jwt-secret \ No newline at end of file diff --git a/archiv/flask-backend/Dockerfile b/archiv/flask-backend/Dockerfile deleted file mode 100755 index 0987f07..0000000 --- a/archiv/flask-backend/Dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -FROM python:3.11-slim - -WORKDIR /app - -# Install dependencies -COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt - -# Copy application code -COPY . . - -# Run database migrations -RUN mkdir -p /app/instance -ENV FLASK_APP=wsgi.py - -# Expose port -EXPOSE 5000 - -# Run the application -CMD ["gunicorn", "--bind", "0.0.0.0:5000", "wsgi:app"] \ No newline at end of file diff --git a/archiv/flask-backend/README.md b/archiv/flask-backend/README.md deleted file mode 100755 index 7e6bc7f..0000000 --- a/archiv/flask-backend/README.md +++ /dev/null @@ -1,96 +0,0 @@ -# Reservation Platform Backend - -This is the Flask backend for the 3D Printer Reservation Platform, providing a RESTful API for managing printers, reservations, and users. - -## Features - -- User authentication with email/password -- Role-based permission system (admin, user) -- Printer management -- Reservation system -- User management - -## API Endpoints - -### Authentication -- `POST /auth/register` - Register a new user -- `POST /auth/login` - Login with username/email and password -- `POST /auth/logout` - Log out a user by invalidating their session - -### Printers -- `GET /api/printers` - Get all printers -- `GET /api/printers/` - Get a specific printer -- `POST /api/printers` - Create a new printer (admin only) -- `PUT /api/printers/` - Update a printer (admin only) -- `DELETE /api/printers/` - Delete a printer (admin only) -- `GET /api/printers/availability` - Get availability information for all printers - -### Print Jobs -- `GET /api/jobs` - Get jobs for the current user or all jobs for admin -- `GET /api/jobs/` - Get a specific job -- `POST /api/jobs` - Create a new print job (reserve a printer) -- `PUT /api/jobs/` - Update a job -- `DELETE /api/jobs/` - Delete a job (cancel reservation) -- `GET /api/jobs//remaining-time` - Get remaining time for a job (public endpoint) - -### Users -- `GET /api/users` - Get all users (admin only) -- `GET /api/users/` - Get a specific user (admin only) -- `PUT /api/users/` - Update a user (admin only) -- `DELETE /api/users/` - Delete a user (admin only) -- `GET /api/me` - Get the current user's profile -- `PUT /api/me` - Update the current user's profile - -## Installation - -### Prerequisites -- Python 3.11 or higher -- pip - -### Setup - -1. Clone the repository - ```bash - git clone https://github.com/your-repo/reservation-platform.git - cd reservation-platform/packages/flask-backend - ``` - -2. Install dependencies - ```bash - pip install -r requirements.txt - ``` - -3. Create a `.env` file with the following variables: - ``` - SECRET_KEY=your-secret-key - DATABASE_URL=sqlite:///app.db - JWT_SECRET=your-jwt-secret - ``` - -4. Initialize the database - ```bash - flask db upgrade - python scripts/init_db.py - ``` - -5. Run the development server - ```bash - python wsgi.py - ``` - -## Docker Deployment - -1. Build and run with Docker Compose - ```bash - docker-compose up -d - ``` - -## Development - -### Running Migrations - -To create a new migration after updating models: -```bash -flask db migrate -m "Description of changes" -flask db upgrade -``` \ No newline at end of file diff --git a/archiv/flask-backend/app/__init__.py b/archiv/flask-backend/app/__init__.py deleted file mode 100755 index 49bf19d..0000000 --- a/archiv/flask-backend/app/__init__.py +++ /dev/null @@ -1,32 +0,0 @@ -from flask import Flask -from flask_sqlalchemy import SQLAlchemy -from flask_migrate import Migrate -from flask_cors import CORS -from config import Config - -db = SQLAlchemy() -migrate = Migrate() - -def create_app(config_class=Config): - app = Flask(__name__) - app.config.from_object(config_class) - - # Initialize extensions - db.init_app(app) - migrate.init_app(app, db) - CORS(app) - - # Register blueprints - from app.api import bp as api_bp - app.register_blueprint(api_bp, url_prefix='/api') - - from app.auth import bp as auth_bp - app.register_blueprint(auth_bp, url_prefix='/auth') - - @app.route('/health') - def health_check(): - return {'status': 'ok'} - - return app - -from app import models \ No newline at end of file diff --git a/archiv/flask-backend/app/api/__init__.py b/archiv/flask-backend/app/api/__init__.py deleted file mode 100755 index da5c014..0000000 --- a/archiv/flask-backend/app/api/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from flask import Blueprint - -bp = Blueprint('api', __name__) - -from app.api import printers, jobs, users \ No newline at end of file diff --git a/archiv/flask-backend/app/api/jobs.py b/archiv/flask-backend/app/api/jobs.py deleted file mode 100755 index d981ab7..0000000 --- a/archiv/flask-backend/app/api/jobs.py +++ /dev/null @@ -1,219 +0,0 @@ -from flask import request, jsonify -from app import db -from app.api import bp -from app.models import PrintJob, Printer, User -from app.auth.routes import token_required, admin_required -from datetime import datetime, timedelta - -@bp.route('/jobs', methods=['GET']) -@token_required -def get_jobs(): - """Get jobs for the current user or all jobs for admin""" - is_admin = request.user_role == 'admin' - user_id = request.user_id - - # Parse query parameters - status = request.args.get('status') # active, upcoming, completed, aborted, all - printer_id = request.args.get('printer_id') - - # Base query - query = PrintJob.query - - # Filter by user unless admin - if not is_admin: - query = query.filter_by(user_id=user_id) - - # Filter by printer if provided - if printer_id: - query = query.filter_by(printer_id=printer_id) - - # Apply status filter - now = datetime.utcnow() - if status == 'active': - query = query.filter_by(aborted=False) \ - .filter(PrintJob.start_at <= now) \ - .filter(PrintJob.start_at.op('+')(PrintJob.duration_in_minutes * 60) > now) - elif status == 'upcoming': - query = query.filter_by(aborted=False) \ - .filter(PrintJob.start_at > now) - elif status == 'completed': - query = query.filter_by(aborted=False) \ - .filter(PrintJob.start_at.op('+')(PrintJob.duration_in_minutes * 60) <= now) - elif status == 'aborted': - query = query.filter_by(aborted=True) - - # Order by start time, most recent first - query = query.order_by(PrintJob.start_at.desc()) - - # Execute query - jobs = query.all() - result = [job.to_dict() for job in jobs] - - return jsonify(result) - -@bp.route('/jobs/', methods=['GET']) -@token_required -def get_job(job_id): - """Get a specific job""" - job = PrintJob.query.get_or_404(job_id) - - # Check permissions - is_admin = request.user_role == 'admin' - user_id = request.user_id - - if not is_admin and job.user_id != user_id: - return jsonify({'error': 'Not authorized to view this job'}), 403 - - return jsonify(job.to_dict()) - -@bp.route('/jobs', methods=['POST']) -@token_required -def create_job(): - """Create a new print job (reserve a printer)""" - data = request.get_json() or {} - - required_fields = ['printer_id', 'start_at', 'duration_in_minutes'] - for field in required_fields: - if field not in data: - return jsonify({'error': f'Missing required field: {field}'}), 400 - - # Validate printer - printer = Printer.query.get(data['printer_id']) - if not printer: - return jsonify({'error': 'Printer not found'}), 404 - - if printer.status != 0: # Not operational - return jsonify({'error': 'Printer is not operational'}), 400 - - # Parse start time - try: - start_at = datetime.fromisoformat(data['start_at'].replace('Z', '+00:00')) - except ValueError: - return jsonify({'error': 'Invalid start_at format'}), 400 - - # Validate duration - try: - duration = int(data['duration_in_minutes']) - if duration <= 0 or duration > 480: # Max 8 hours - return jsonify({'error': 'Invalid duration (must be between 1 and 480 minutes)'}), 400 - except ValueError: - return jsonify({'error': 'Duration must be a number'}), 400 - - end_at = start_at + timedelta(minutes=duration) - - # Check if the printer is available during the requested time - conflicting_jobs = PrintJob.query.filter_by(printer_id=printer.id, aborted=False) \ - .filter( - (PrintJob.start_at < end_at) & - (PrintJob.start_at.op('+')(PrintJob.duration_in_minutes * 60) > start_at) - ) \ - .all() - - if conflicting_jobs: - return jsonify({'error': 'Printer is not available during the requested time'}), 409 - - # Create job - job = PrintJob( - printer_id=data['printer_id'], - user_id=request.user_id, - start_at=start_at, - duration_in_minutes=duration, - comments=data.get('comments', '') - ) - - db.session.add(job) - db.session.commit() - - return jsonify(job.to_dict()), 201 - -@bp.route('/jobs/', methods=['PUT']) -@token_required -def update_job(job_id): - """Update a job""" - job = PrintJob.query.get_or_404(job_id) - - # Check permissions - is_admin = request.user_role == 'admin' - user_id = request.user_id - - if not is_admin and job.user_id != user_id: - return jsonify({'error': 'Not authorized to update this job'}), 403 - - data = request.get_json() or {} - - # Only allow certain fields to be updated - if 'comments' in data: - job.comments = data['comments'] - - # Admin or owner can abort a job - if 'aborted' in data and data['aborted'] and not job.aborted: - job.aborted = True - job.abort_reason = data.get('abort_reason', '') - - # Admin or owner can extend a job if it's active - now = datetime.utcnow() - is_active = (not job.aborted and - job.start_at <= now and - job.get_end_time() > now) - - if 'extend_minutes' in data and is_active: - try: - extend_minutes = int(data['extend_minutes']) - if extend_minutes <= 0 or extend_minutes > 120: # Max extend 2 hours - return jsonify({'error': 'Invalid extension (must be between 1 and 120 minutes)'}), 400 - - new_end_time = job.get_end_time() + timedelta(minutes=extend_minutes) - - # Check for conflicts with the extension - conflicting_jobs = PrintJob.query.filter_by(printer_id=job.printer_id, aborted=False) \ - .filter(PrintJob.id != job.id) \ - .filter(PrintJob.start_at < new_end_time) \ - .filter(PrintJob.start_at > job.get_end_time()) \ - .all() - - if conflicting_jobs: - return jsonify({'error': 'Cannot extend job due to conflicts with other reservations'}), 409 - - job.duration_in_minutes += extend_minutes - except ValueError: - return jsonify({'error': 'Extend minutes must be a number'}), 400 - - db.session.commit() - - return jsonify(job.to_dict()) - -@bp.route('/jobs/', methods=['DELETE']) -@token_required -def delete_job(job_id): - """Delete a job (cancel reservation)""" - job = PrintJob.query.get_or_404(job_id) - - # Check permissions - is_admin = request.user_role == 'admin' - user_id = request.user_id - - if not is_admin and job.user_id != user_id: - return jsonify({'error': 'Not authorized to delete this job'}), 403 - - # Only allow deletion of upcoming jobs - now = datetime.utcnow() - if job.start_at <= now and not is_admin: - return jsonify({'error': 'Cannot delete an active or completed job'}), 400 - - db.session.delete(job) - db.session.commit() - - return jsonify({'message': 'Job deleted successfully'}) - -@bp.route('/jobs//remaining-time', methods=['GET']) -def get_remaining_time(job_id): - """Get remaining time for a job (public endpoint)""" - job = PrintJob.query.get_or_404(job_id) - - remaining_seconds = job.get_remaining_time() - - return jsonify({ - 'job_id': job.id, - 'remaining_seconds': remaining_seconds, - 'is_active': job.is_active() - }) \ No newline at end of file diff --git a/archiv/flask-backend/app/api/printers.py b/archiv/flask-backend/app/api/printers.py deleted file mode 100755 index 57d01e1..0000000 --- a/archiv/flask-backend/app/api/printers.py +++ /dev/null @@ -1,177 +0,0 @@ -from flask import request, jsonify -from app import db -from app.api import bp -from app.models import Printer, PrintJob -from app.auth.routes import token_required, admin_required -from datetime import datetime - -@bp.route('/printers', methods=['GET']) -def get_printers(): - """Get all printers""" - printers = Printer.query.all() - result = [] - - for printer in printers: - # Get active job for the printer if any - now = datetime.utcnow() - active_job = PrintJob.query.filter_by(printer_id=printer.id, aborted=False) \ - .filter(PrintJob.start_at <= now) \ - .filter(PrintJob.start_at.op('+')(PrintJob.duration_in_minutes * 60) > now) \ - .first() - - printer_data = { - 'id': printer.id, - 'name': printer.name, - 'description': printer.description, - 'status': printer.status, - 'is_available': printer.status == 0 and active_job is None, - 'active_job': active_job.to_dict() if active_job else None - } - result.append(printer_data) - - return jsonify(result) - -@bp.route('/printers/', methods=['GET']) -def get_printer(printer_id): - """Get a specific printer""" - printer = Printer.query.get_or_404(printer_id) - - # Get active job for the printer if any - now = datetime.utcnow() - active_job = PrintJob.query.filter_by(printer_id=printer.id, aborted=False) \ - .filter(PrintJob.start_at <= now) \ - .filter(PrintJob.start_at.op('+')(PrintJob.duration_in_minutes * 60) > now) \ - .first() - - # Get upcoming jobs - upcoming_jobs = PrintJob.query.filter_by(printer_id=printer.id, aborted=False) \ - .filter(PrintJob.start_at > now) \ - .order_by(PrintJob.start_at) \ - .limit(5) \ - .all() - - result = { - 'id': printer.id, - 'name': printer.name, - 'description': printer.description, - 'status': printer.status, - 'is_available': printer.status == 0 and active_job is None, - 'active_job': active_job.to_dict() if active_job else None, - 'upcoming_jobs': [job.to_dict() for job in upcoming_jobs] - } - - return jsonify(result) - -@bp.route('/printers', methods=['POST']) -@admin_required -def create_printer(): - """Create a new printer (admin only)""" - data = request.get_json() or {} - - required_fields = ['name', 'description'] - for field in required_fields: - if field not in data: - return jsonify({'error': f'Missing required field: {field}'}), 400 - - printer = Printer( - name=data['name'], - description=data['description'], - status=data.get('status', 0) - ) - - db.session.add(printer) - db.session.commit() - - return jsonify({ - 'id': printer.id, - 'name': printer.name, - 'description': printer.description, - 'status': printer.status - }), 201 - -@bp.route('/printers/', methods=['PUT']) -@admin_required -def update_printer(printer_id): - """Update a printer (admin only)""" - printer = Printer.query.get_or_404(printer_id) - data = request.get_json() or {} - - if 'name' in data: - printer.name = data['name'] - if 'description' in data: - printer.description = data['description'] - if 'status' in data: - printer.status = data['status'] - - db.session.commit() - - return jsonify({ - 'id': printer.id, - 'name': printer.name, - 'description': printer.description, - 'status': printer.status - }) - -@bp.route('/printers/', methods=['DELETE']) -@admin_required -def delete_printer(printer_id): - """Delete a printer (admin only)""" - printer = Printer.query.get_or_404(printer_id) - - # Check if the printer has active jobs - now = datetime.utcnow() - active_jobs = PrintJob.query.filter_by(printer_id=printer.id, aborted=False) \ - .filter(PrintJob.start_at <= now) \ - .filter(PrintJob.start_at.op('+')(PrintJob.duration_in_minutes * 60) > now) \ - .all() - - if active_jobs: - return jsonify({'error': 'Cannot delete printer with active jobs'}), 400 - - db.session.delete(printer) - db.session.commit() - - return jsonify({'message': 'Printer deleted successfully'}) - -@bp.route('/printers/availability', methods=['GET']) -def get_availability(): - """Get availability information for all printers""" - start_date = request.args.get('start_date') - end_date = request.args.get('end_date') - - if not start_date or not end_date: - return jsonify({'error': 'start_date and end_date are required'}), 400 - - try: - start = datetime.fromisoformat(start_date.replace('Z', '+00:00')) - end = datetime.fromisoformat(end_date.replace('Z', '+00:00')) - except ValueError: - return jsonify({'error': 'Invalid date format'}), 400 - - if start >= end: - return jsonify({'error': 'start_date must be before end_date'}), 400 - - printers = Printer.query.all() - result = [] - - for printer in printers: - # Get all jobs for this printer in the date range - jobs = PrintJob.query.filter_by(printer_id=printer.id, aborted=False) \ - .filter( - (PrintJob.start_at <= end) & - (PrintJob.start_at.op('+')(PrintJob.duration_in_minutes * 60) >= start) - ) \ - .order_by(PrintJob.start_at) \ - .all() - - # Convert to availability slots - availability = { - 'printer_id': printer.id, - 'printer_name': printer.name, - 'status': printer.status, - 'jobs': [job.to_dict() for job in jobs] - } - - result.append(availability) - - return jsonify(result) \ No newline at end of file diff --git a/archiv/flask-backend/app/api/users.py b/archiv/flask-backend/app/api/users.py deleted file mode 100755 index 3c348fc..0000000 --- a/archiv/flask-backend/app/api/users.py +++ /dev/null @@ -1,139 +0,0 @@ -from flask import request, jsonify -from app import db -from app.api import bp -from app.models import User, PrintJob -from app.auth.routes import admin_required, token_required - -@bp.route('/users', methods=['GET']) -@admin_required -def get_users(): - """Get all users (admin only)""" - users = User.query.all() - result = [] - - for user in users: - # Count jobs - total_jobs = PrintJob.query.filter_by(user_id=user.id).count() - active_jobs = PrintJob.query.filter_by(user_id=user.id, aborted=False).count() - - user_data = { - 'id': user.id, - 'github_id': user.github_id, - 'username': user.username, - 'display_name': user.display_name, - 'email': user.email, - 'role': user.role, - 'job_count': total_jobs, - 'active_job_count': active_jobs - } - result.append(user_data) - - return jsonify(result) - -@bp.route('/users/', methods=['GET']) -@admin_required -def get_user(user_id): - """Get a specific user (admin only)""" - user = User.query.get_or_404(user_id) - - # Count jobs - total_jobs = PrintJob.query.filter_by(user_id=user.id).count() - active_jobs = PrintJob.query.filter_by(user_id=user.id, aborted=False).count() - - result = { - 'id': user.id, - 'github_id': user.github_id, - 'username': user.username, - 'display_name': user.display_name, - 'email': user.email, - 'role': user.role, - 'job_count': total_jobs, - 'active_job_count': active_jobs - } - - return jsonify(result) - -@bp.route('/users/', methods=['PUT']) -@admin_required -def update_user(user_id): - """Update a user (admin only)""" - user = User.query.get_or_404(user_id) - data = request.get_json() or {} - - if 'role' in data and data['role'] in ['admin', 'user', 'guest']: - user.role = data['role'] - - if 'display_name' in data: - user.display_name = data['display_name'] - - db.session.commit() - - return jsonify({ - 'id': user.id, - 'github_id': user.github_id, - 'username': user.username, - 'display_name': user.display_name, - 'email': user.email, - 'role': user.role - }) - -@bp.route('/users/', methods=['DELETE']) -@admin_required -def delete_user(user_id): - """Delete a user (admin only)""" - user = User.query.get_or_404(user_id) - - # Check if user has active jobs - active_jobs = PrintJob.query.filter_by(user_id=user.id, aborted=False).first() - if active_jobs: - return jsonify({'error': 'Cannot delete user with active jobs'}), 400 - - db.session.delete(user) - db.session.commit() - - return jsonify({'message': 'User deleted successfully'}) - -@bp.route('/me', methods=['GET']) -@token_required -def get_current_user(): - """Get the current user's profile""" - user = User.query.get(request.user_id) - if not user: - return jsonify({'error': 'User not found'}), 404 - - result = { - 'id': user.id, - 'github_id': user.github_id, - 'username': user.username, - 'display_name': user.display_name, - 'email': user.email, - 'role': user.role - } - - return jsonify(result) - -@bp.route('/me', methods=['PUT']) -@token_required -def update_current_user(): - """Update the current user's profile""" - user = User.query.get(request.user_id) - if not user: - return jsonify({'error': 'User not found'}), 404 - - data = request.get_json() or {} - - if 'display_name' in data: - user.display_name = data['display_name'] - - db.session.commit() - - result = { - 'id': user.id, - 'github_id': user.github_id, - 'username': user.username, - 'display_name': user.display_name, - 'email': user.email, - 'role': user.role - } - - return jsonify(result) \ No newline at end of file diff --git a/archiv/flask-backend/app/auth/__init__.py b/archiv/flask-backend/app/auth/__init__.py deleted file mode 100755 index 2834ca1..0000000 --- a/archiv/flask-backend/app/auth/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from flask import Blueprint - -bp = Blueprint('auth', __name__) - -from app.auth import routes \ No newline at end of file diff --git a/archiv/flask-backend/app/auth/routes.py b/archiv/flask-backend/app/auth/routes.py deleted file mode 100755 index 69380a1..0000000 --- a/archiv/flask-backend/app/auth/routes.py +++ /dev/null @@ -1,156 +0,0 @@ -from flask import request, jsonify, current_app -from app import db -from app.auth import bp -from app.models import User, Session -from datetime import datetime, timedelta -import time -import functools -import re - -@bp.route('/register', methods=['POST']) -def register(): - """Register a new user""" - data = request.get_json() or {} - - # Validate required fields - required_fields = ['username', 'email', 'password'] - for field in required_fields: - if field not in data: - return jsonify({'error': f'Missing required field: {field}'}), 400 - - # Validate email format - email_regex = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' - if not re.match(email_regex, data['email']): - return jsonify({'error': 'Invalid email format'}), 400 - - # Validate password strength (at least 8 characters) - if len(data['password']) < 8: - return jsonify({'error': 'Password must be at least 8 characters long'}), 400 - - # Check if username already exists - if User.query.filter_by(username=data['username']).first(): - return jsonify({'error': 'Username already exists'}), 400 - - # Check if email already exists - if User.query.filter_by(email=data['email']).first(): - return jsonify({'error': 'Email already exists'}), 400 - - # Create new user - user = User( - username=data['username'], - email=data['email'], - display_name=data.get('display_name', data['username']), - role='user' # Default role - ) - user.set_password(data['password']) - - db.session.add(user) - db.session.commit() - - return jsonify({ - 'id': user.id, - 'username': user.username, - 'email': user.email, - 'display_name': user.display_name, - 'role': user.role - }), 201 - -@bp.route('/login', methods=['POST']) -def login(): - """Login a user with username/email and password""" - data = request.get_json() or {} - - # Validate required fields - if 'password' not in data: - return jsonify({'error': 'Password is required'}), 400 - - if 'username' not in data and 'email' not in data: - return jsonify({'error': 'Username or email is required'}), 400 - - # Find user by username or email - user = None - if 'username' in data: - user = User.query.filter_by(username=data['username']).first() - else: - user = User.query.filter_by(email=data['email']).first() - - # Check if user exists and verify password - if not user or not user.check_password(data['password']): - return jsonify({'error': 'Invalid credentials'}), 401 - - # Create a session for the user - expires_at = int((datetime.utcnow() + timedelta(days=7)).timestamp()) - session = Session( - user_id=user.id, - expires_at=expires_at - ) - db.session.add(session) - db.session.commit() - - # Generate JWT token - token = user.generate_token() - - return jsonify({ - 'token': token, - 'user': { - 'id': user.id, - 'username': user.username, - 'email': user.email, - 'display_name': user.display_name, - 'role': user.role - } - }) - -@bp.route('/logout', methods=['POST']) -def logout(): - """Log out a user by invalidating their session""" - auth_header = request.headers.get('Authorization') - if not auth_header or not auth_header.startswith('Bearer '): - return jsonify({'error': 'Authorization header required'}), 401 - - token = auth_header.split(' ')[1] - payload = User.verify_token(token) - if not payload: - return jsonify({'error': 'Invalid token'}), 401 - - # Delete all sessions for this user - Session.query.filter_by(user_id=payload['user_id']).delete() - db.session.commit() - - return jsonify({'message': 'Successfully logged out'}) - -def token_required(f): - @functools.wraps(f) - def decorated(*args, **kwargs): - auth_header = request.headers.get('Authorization') - if not auth_header or not auth_header.startswith('Bearer '): - return jsonify({'error': 'Authorization header required'}), 401 - - token = auth_header.split(' ')[1] - payload = User.verify_token(token) - if not payload: - return jsonify({'error': 'Invalid token'}), 401 - - # Check if user has an active session - user_id = payload['user_id'] - current_time = int(time.time()) - session = Session.query.filter_by(user_id=user_id).filter(Session.expires_at > current_time).first() - if not session: - return jsonify({'error': 'No active session found'}), 401 - - # Add user to request context - request.user_id = user_id - request.user_role = payload['role'] - - return f(*args, **kwargs) - return decorated - -def admin_required(f): - @functools.wraps(f) - @token_required - def decorated(*args, **kwargs): - if request.user_role != 'admin': - return jsonify({'error': 'Admin privileges required'}), 403 - - return f(*args, **kwargs) - return decorated \ No newline at end of file diff --git a/archiv/flask-backend/app/models.py b/archiv/flask-backend/app/models.py deleted file mode 100755 index a3b8e80..0000000 --- a/archiv/flask-backend/app/models.py +++ /dev/null @@ -1,124 +0,0 @@ -from app import db -import uuid -from datetime import datetime, timedelta -import jwt -from config import Config -import bcrypt - -class User(db.Model): - __tablename__ = 'user' - - id = db.Column(db.String(36), primary_key=True, default=lambda: str(uuid.uuid4())) - username = db.Column(db.String(64), index=True, unique=True, nullable=False) - display_name = db.Column(db.String(120)) - email = db.Column(db.String(120), index=True, unique=True, nullable=False) - password_hash = db.Column(db.String(128), nullable=False) - role = db.Column(db.String(20), default='user') - - print_jobs = db.relationship('PrintJob', backref='user', lazy='dynamic', cascade='all, delete-orphan') - sessions = db.relationship('Session', backref='user', lazy='dynamic', cascade='all, delete-orphan') - - def set_password(self, password): - """Hash and set the user's password""" - password_bytes = password.encode('utf-8') - salt = bcrypt.gensalt() - self.password_hash = bcrypt.hashpw(password_bytes, salt).decode('utf-8') - - def check_password(self, password): - """Check if the provided password matches the stored hash""" - password_bytes = password.encode('utf-8') - stored_hash = self.password_hash.encode('utf-8') - return bcrypt.checkpw(password_bytes, stored_hash) - - def generate_token(self): - """Generate a JWT token for this user""" - payload = { - 'user_id': self.id, - 'username': self.username, - 'email': self.email, - 'role': self.role, - 'exp': datetime.utcnow() + timedelta(seconds=Config.JWT_ACCESS_TOKEN_EXPIRES) - } - return jwt.encode(payload, Config.JWT_SECRET, algorithm='HS256') - - @staticmethod - def verify_token(token): - """Verify and decode a JWT token""" - try: - payload = jwt.decode(token, Config.JWT_SECRET, algorithms=['HS256']) - return payload - except: - return None - - -class Session(db.Model): - __tablename__ = 'session' - - id = db.Column(db.String(36), primary_key=True, default=lambda: str(uuid.uuid4())) - user_id = db.Column(db.String(36), db.ForeignKey('user.id', ondelete='CASCADE'), nullable=False) - expires_at = db.Column(db.Integer, nullable=False) - - -class Printer(db.Model): - __tablename__ = 'printer' - - id = db.Column(db.String(36), primary_key=True, default=lambda: str(uuid.uuid4())) - name = db.Column(db.String(120), nullable=False) - description = db.Column(db.Text, nullable=False) - status = db.Column(db.Integer, nullable=False, default=0) # 0: OPERATIONAL, 1: OUT_OF_ORDER - - print_jobs = db.relationship('PrintJob', backref='printer', lazy='dynamic', cascade='all, delete-orphan') - - -class PrintJob(db.Model): - __tablename__ = 'printJob' - - id = db.Column(db.String(36), primary_key=True, default=lambda: str(uuid.uuid4())) - printer_id = db.Column(db.String(36), db.ForeignKey('printer.id', ondelete='CASCADE'), nullable=False) - user_id = db.Column(db.String(36), db.ForeignKey('user.id', ondelete='CASCADE'), nullable=False) - start_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) - duration_in_minutes = db.Column(db.Integer, nullable=False) - comments = db.Column(db.Text) - aborted = db.Column(db.Boolean, nullable=False, default=False) - abort_reason = db.Column(db.Text) - - def get_end_time(self): - return self.start_at + timedelta(minutes=self.duration_in_minutes) - - def is_active(self): - now = datetime.utcnow() - return (not self.aborted and - self.start_at <= now and - now < self.get_end_time()) - - def get_remaining_time(self): - if self.aborted: - return 0 - - now = datetime.utcnow() - if now < self.start_at: - # Job hasn't started yet - return self.duration_in_minutes * 60 - - end_time = self.get_end_time() - if now >= end_time: - # Job has ended - return 0 - - # Job is ongoing - remaining_seconds = (end_time - now).total_seconds() - return int(remaining_seconds) - - def to_dict(self): - return { - 'id': self.id, - 'printer_id': self.printer_id, - 'user_id': self.user_id, - 'start_at': self.start_at.isoformat(), - 'duration_in_minutes': self.duration_in_minutes, - 'comments': self.comments, - 'aborted': self.aborted, - 'abort_reason': self.abort_reason, - 'remaining_time': self.get_remaining_time(), - 'is_active': self.is_active() - } \ No newline at end of file diff --git a/archiv/flask-backend/config.py b/archiv/flask-backend/config.py deleted file mode 100755 index b2766c5..0000000 --- a/archiv/flask-backend/config.py +++ /dev/null @@ -1,13 +0,0 @@ -import os -from dotenv import load_dotenv - -basedir = os.path.abspath(os.path.dirname(__file__)) -load_dotenv(os.path.join(basedir, '.env')) - -class Config: - SECRET_KEY = os.environ.get('SECRET_KEY') or 'you-will-never-guess' - SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \ - 'sqlite:///' + os.path.join(basedir, 'app.db') - SQLALCHEMY_TRACK_MODIFICATIONS = False - JWT_SECRET = os.environ.get('JWT_SECRET') or 'jwt-secret-key' - JWT_ACCESS_TOKEN_EXPIRES = 3600 # 1 hour in seconds \ No newline at end of file diff --git a/archiv/flask-backend/docker-compose.yml b/archiv/flask-backend/docker-compose.yml deleted file mode 100755 index 8747837..0000000 --- a/archiv/flask-backend/docker-compose.yml +++ /dev/null @@ -1,20 +0,0 @@ -version: '3.8' - -services: - flask-backend: - build: - context: . - dockerfile: Dockerfile - restart: always - ports: - - "5000:5000" - environment: - - SECRET_KEY=your-secret-key - - DATABASE_URL=sqlite:///app.db - - JWT_SECRET=your-jwt-secret - volumes: - - ./instance:/app/instance - command: > - bash -c "python -m flask db upgrade && - python scripts/init_db.py && - gunicorn --bind 0.0.0.0:5000 wsgi:app" \ No newline at end of file diff --git a/archiv/flask-backend/migrations/alembic.ini b/archiv/flask-backend/migrations/alembic.ini deleted file mode 100755 index 187d090..0000000 --- a/archiv/flask-backend/migrations/alembic.ini +++ /dev/null @@ -1,89 +0,0 @@ -# A generic, single database configuration. - -[alembic] -# path to migration scripts -script_location = migrations - -# template used to generate migration files -file_template = %%(year)d%%(month).2d%%(day).2d_%%(hour).2d%%(minute).2d%%(second).2d_%%(slug)s - -# sys.path path, will be prepended to sys.path if present. -# defaults to the current working directory. -prepend_sys_path = . - -# timezone to use when rendering the date -# within the migration file as well as the filename. -# string value is passed to dateutil.tz.gettz() -# leave blank for localtime -# timezone = - -# max length of characters to apply to the -# "slug" field -truncate_slug_length = 40 - -# set to 'true' to run the environment during -# the 'revision' command, regardless of autogenerate -# revision_environment = false - -# set to 'true' to allow .pyc and .pyo files without -# a source .py file to be detected as revisions in the -# versions/ directory -# sourceless = false - -# version location specification; this defaults -# to migrations/versions. When using multiple version -# directories, initial revisions must be specified with --version-path -# version_locations = %(here)s/bar %(here)s/bat migrations/versions - -# the output encoding used when revision files -# are written from script.py.mako -# output_encoding = utf-8 - -sqlalchemy.url = driver://user:pass@localhost/dbname - - -[post_write_hooks] -# post_write_hooks defines scripts or Python functions that are run -# on newly generated revision scripts. See the documentation for further -# detail and examples - -# format using "black" - use the console_scripts runner, against the "black" entrypoint -# hooks = black -# black.type = console_scripts -# black.entrypoint = black -# black.options = -l 79 REVISION_SCRIPT_FILENAME - -# Logging configuration -[loggers] -keys = root,sqlalchemy,alembic - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = WARN -handlers = console -qualname = - -[logger_sqlalchemy] -level = WARN -handlers = -qualname = sqlalchemy.engine - -[logger_alembic] -level = INFO -handlers = -qualname = alembic - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(levelname)-5.5s [%(name)s] %(message)s -datefmt = %H:%M:%S \ No newline at end of file diff --git a/archiv/flask-backend/migrations/env.py b/archiv/flask-backend/migrations/env.py deleted file mode 100755 index 29192f0..0000000 --- a/archiv/flask-backend/migrations/env.py +++ /dev/null @@ -1,91 +0,0 @@ -from __future__ import with_statement - -import logging -from logging.config import fileConfig - -from flask import current_app - -from alembic import context - -# this is the Alembic Config object, which provides -# access to the values within the .ini file in use. -config = context.config - -# Interpret the config file for Python logging. -# This line sets up loggers basically. -fileConfig(config.config_file_name) -logger = logging.getLogger('alembic.env') - -# add your model's MetaData object here -# for 'autogenerate' support -# from myapp import mymodel -# target_metadata = mymodel.Base.metadata -config.set_main_option( - 'sqlalchemy.url', - str(current_app.extensions['migrate'].db.get_engine().url).replace( - '%', '%%')) -target_metadata = current_app.extensions['migrate'].db.metadata - -# other values from the config, defined by the needs of env.py, -# can be acquired: -# my_important_option = config.get_main_option("my_important_option") -# ... etc. - - -def run_migrations_offline(): - """Run migrations in 'offline' mode. - - This configures the context with just a URL - and not an Engine, though an Engine is acceptable - here as well. By skipping the Engine creation - we don't even need a DBAPI to be available. - - Calls to context.execute() here emit the given string to the - script output. - - """ - url = config.get_main_option("sqlalchemy.url") - context.configure( - url=url, target_metadata=target_metadata, literal_binds=True - ) - - with context.begin_transaction(): - context.run_migrations() - - -def run_migrations_online(): - """Run migrations in 'online' mode. - - In this scenario we need to create an Engine - and associate a connection with the context. - - """ - - # this callback is used to prevent an auto-migration from being generated - # when there are no changes to the schema - # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html - def process_revision_directives(context, revision, directives): - if getattr(config.cmd_opts, 'autogenerate', False): - script = directives[0] - if script.upgrade_ops.is_empty(): - directives[:] = [] - logger.info('No changes in schema detected.') - - connectable = current_app.extensions['migrate'].db.get_engine() - - with connectable.connect() as connection: - context.configure( - connection=connection, - target_metadata=target_metadata, - process_revision_directives=process_revision_directives, - **current_app.extensions['migrate'].configure_args - ) - - with context.begin_transaction(): - context.run_migrations() - - -if context.is_offline_mode(): - run_migrations_offline() -else: - run_migrations_online() \ No newline at end of file diff --git a/archiv/flask-backend/migrations/script.py.mako b/archiv/flask-backend/migrations/script.py.mako deleted file mode 100755 index 1e4564e..0000000 --- a/archiv/flask-backend/migrations/script.py.mako +++ /dev/null @@ -1,24 +0,0 @@ -"""${message} - -Revision ID: ${up_revision} -Revises: ${down_revision | comma,n} -Create Date: ${create_date} - -""" -from alembic import op -import sqlalchemy as sa -${imports if imports else ""} - -# revision identifiers, used by Alembic. -revision = ${repr(up_revision)} -down_revision = ${repr(down_revision)} -branch_labels = ${repr(branch_labels)} -depends_on = ${repr(depends_on)} - - -def upgrade(): - ${upgrades if upgrades else "pass"} - - -def downgrade(): - ${downgrades if downgrades else "pass"} \ No newline at end of file diff --git a/archiv/flask-backend/migrations/versions/initial_migration.py b/archiv/flask-backend/migrations/versions/initial_migration.py deleted file mode 100755 index 9883ff4..0000000 --- a/archiv/flask-backend/migrations/versions/initial_migration.py +++ /dev/null @@ -1,75 +0,0 @@ -"""Initial migration - -Revision ID: initial_migration -Revises: -Create Date: 2025-03-06 12:00:00.000000 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'initial_migration' -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade(): - # Create user table - op.create_table('user', - sa.Column('id', sa.String(length=36), nullable=False), - sa.Column('username', sa.String(length=64), nullable=False), - sa.Column('display_name', sa.String(length=120), nullable=True), - sa.Column('email', sa.String(length=120), nullable=False), - sa.Column('password_hash', sa.String(length=128), nullable=False), - sa.Column('role', sa.String(length=20), nullable=True), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('email'), - sa.UniqueConstraint('username') - ) - op.create_index(op.f('ix_user_email'), 'user', ['email'], unique=True) - op.create_index(op.f('ix_user_username'), 'user', ['username'], unique=True) - - # Create session table - op.create_table('session', - sa.Column('id', sa.String(length=36), nullable=False), - sa.Column('user_id', sa.String(length=36), nullable=False), - sa.Column('expires_at', sa.Integer(), nullable=False), - sa.ForeignKeyConstraint(['user_id'], ['user.id'], ondelete='CASCADE'), - sa.PrimaryKeyConstraint('id') - ) - - # Create printer table - op.create_table('printer', - sa.Column('id', sa.String(length=36), nullable=False), - sa.Column('name', sa.String(length=120), nullable=False), - sa.Column('description', sa.Text(), nullable=False), - sa.Column('status', sa.Integer(), nullable=False), - sa.PrimaryKeyConstraint('id') - ) - - # Create printJob table - op.create_table('printJob', - sa.Column('id', sa.String(length=36), nullable=False), - sa.Column('printer_id', sa.String(length=36), nullable=False), - sa.Column('user_id', sa.String(length=36), nullable=False), - sa.Column('start_at', sa.DateTime(), nullable=False), - sa.Column('duration_in_minutes', sa.Integer(), nullable=False), - sa.Column('comments', sa.Text(), nullable=True), - sa.Column('aborted', sa.Boolean(), nullable=False), - sa.Column('abort_reason', sa.Text(), nullable=True), - sa.ForeignKeyConstraint(['printer_id'], ['printer.id'], ondelete='CASCADE'), - sa.ForeignKeyConstraint(['user_id'], ['user.id'], ondelete='CASCADE'), - sa.PrimaryKeyConstraint('id') - ) - - -def downgrade(): - op.drop_table('printJob') - op.drop_table('printer') - op.drop_table('session') - op.drop_index(op.f('ix_user_username'), table_name='user') - op.drop_index(op.f('ix_user_email'), table_name='user') - op.drop_table('user') \ No newline at end of file diff --git a/archiv/flask-backend/requirements.txt b/archiv/flask-backend/requirements.txt deleted file mode 100755 index 06d4a3e..0000000 --- a/archiv/flask-backend/requirements.txt +++ /dev/null @@ -1,9 +0,0 @@ -Flask==2.3.3 -Flask-SQLAlchemy==3.1.1 -Flask-Migrate==4.0.5 -Flask-CORS==4.0.0 -python-dotenv==1.0.0 -SQLAlchemy==2.0.25 -pyjwt==2.8.0 -bcrypt==4.1.2 -gunicorn==21.2.0 \ No newline at end of file diff --git a/archiv/flask-backend/run.sh b/archiv/flask-backend/run.sh deleted file mode 100755 index 301fe35..0000000 --- a/archiv/flask-backend/run.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash - -# Initialize virtual environment if it doesn't exist -if [ ! -d "venv" ]; then - echo "Creating virtual environment..." - python3 -m venv venv -fi - -# Activate virtual environment -source venv/bin/activate - -# Install dependencies -echo "Installing dependencies..." -pip install -r requirements.txt - -# Initialize database -echo "Initializing database..." -flask db upgrade -python scripts/init_db.py - -# Run the application -echo "Starting Flask application..." -python wsgi.py \ No newline at end of file diff --git a/archiv/flask-backend/scripts/init_db.py b/archiv/flask-backend/scripts/init_db.py deleted file mode 100755 index 67ee41b..0000000 --- a/archiv/flask-backend/scripts/init_db.py +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env python -from app import create_app, db -from app.models import User, Printer -import uuid - -def init_db(): - app = create_app() - with app.app_context(): - # Create tables - db.create_all() - - # Check if we already have an admin user - admin = User.query.filter_by(role='admin').first() - if not admin: - # Create admin user - admin = User( - id=str(uuid.uuid4()), - username='admin', - display_name='Administrator', - email='admin@example.com', - role='admin' - ) - admin.set_password('admin123') # Default password, change in production! - db.session.add(admin) - print("Created admin user with username 'admin' and password 'admin123'") - - # Check if we have any printers - printer_count = Printer.query.count() - if printer_count == 0: - # Create sample printers - printers = [ - Printer( - name='Printer 1', - description='3D Printer for general use', - status=0 # OPERATIONAL - ), - Printer( - name='Printer 2', - description='High resolution printer for detailed work', - status=0 # OPERATIONAL - ), - Printer( - name='Printer 3', - description='Large format printer for big projects', - status=0 # OPERATIONAL - ) - ] - db.session.add_all(printers) - print("Created sample printers") - - db.session.commit() - print("Database initialized successfully!") - -if __name__ == '__main__': - init_db() \ No newline at end of file diff --git a/archiv/flask-backend/wsgi.py b/archiv/flask-backend/wsgi.py deleted file mode 100755 index 7425f4c..0000000 --- a/archiv/flask-backend/wsgi.py +++ /dev/null @@ -1,6 +0,0 @@ -from app import create_app - -app = create_app() - -if __name__ == '__main__': - app.run(host='0.0.0.0', port=5000, debug=True) \ No newline at end of file diff --git a/docker-fehler.txt b/docker-fehler.txt deleted file mode 100644 index ae29e6b..0000000 --- a/docker-fehler.txt +++ /dev/null @@ -1,71 +0,0 @@ -root@raspberrypi:/home/user/Projektarbeit-MYP# ./install-frontend.sh -[2025-04-01 10:58:30] Bereinige vorhandene Installation... -[2025-04-01 10:58:30] Bereinigung abgeschlossen. -[2025-04-01 10:58:30] Docker Compose v2 Plugin ist bereits installiert. -[2025-04-01 10:58:30] Wechsle ins Verzeichnis: /home/user/Projektarbeit-MYP/packages/reservation-platform -[2025-04-01 10:58:30] Erstelle .env Datei... -[2025-04-01 10:58:30] .env Datei erfolgreich erstellt -[2025-04-01 10:58:30] HINWEIS: Bitte passen Sie die Backend-URL in der .env-Datei an, falls das Backend auf einem anderen Server läuft. -[2025-04-01 10:58:30] Erstelle Datenbankverzeichnis -[2025-04-01 10:58:30] Baue und starte Frontend-Container... -[2025-04-01 10:58:30] Dies kann auf einem Raspberry Pi mehrere Minuten dauern - bitte geduldig sein -[2025-04-01 10:58:30] Baue lokales Image... -WARN[0000] /home/user/Projektarbeit-MYP/packages/reservation-platform/docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion -Compose can now delegate builds to bake for better performance. - To do so, set COMPOSE_BAKE=true. -[+] Building 95.0s (11/16) docker-container:myp-rp-arm64-builder - => [frontend internal] load build definition from Dockerfile 0.0s - => => transferring dockerfile: 3.60kB 0.0s - => [frontend internal] load metadata for docker.io/library/node:alpine 2.4s - => [frontend internal] load .dockerignore 0.1s - => => transferring context: 2B 0.0s - => [frontend 1/12] FROM docker.io/library/node:alpine@sha256:6eae672406a2bc8ed93eab6f9f76a02eb247e06ba82b2f5032c0a4ae07e825ba 0.1s - => => resolve docker.io/library/node:alpine@sha256:6eae672406a2bc8ed93eab6f9f76a02eb247e06ba82b2f5032c0a4ae07e825ba 0.1s - => [frontend internal] load build context 0.1s - => => transferring context: 11.08kB 0.0s - => CACHED [frontend 2/12] WORKDIR /app 0.0s - => [frontend 3/12] RUN apk add --no-cache python3 build-base g++ make sqlite sqlite-dev gcc musl-dev git libffi-dev openssl-dev cmake 58.0s - => [frontend 4/12] RUN npm install -g pnpm 4.4s - => [frontend 5/12] COPY package.json pnpm-lock.yaml ./ 0.3s - => [frontend 6/12] RUN pnpm install --unsafe-perm --no-optional --frozen-lockfile 27.7s - => ERROR [frontend 7/12] RUN npm install -g npx 1.8s ------- - > [frontend 7/12] RUN npm install -g npx: -1.480 npm error code EEXIST -1.480 npm error path /usr/local/bin/npx -1.480 npm error EEXIST: file already exists -1.480 npm error File exists: /usr/local/bin/npx -1.481 npm error Remove the existing file and try again, or run npm -1.481 npm error with --force to overwrite files recklessly. -1.483 npm error A complete log of this run can be found in: /root/.npm/_logs/2025-04-01T09_00_04_989Z-debug-0.log ------- -failed to solve: process "/bin/sh -c npm install -g npx" did not complete successfully: exit code: 1 -[2025-04-01 11:00:06] FEHLER: Docker Compose Build (v2) fehlgeschlagen. Versuche mit v1 Format... -[+] Building 83.3s (11/16) docker-container:myp-rp-arm64-builder - => [frontend internal] load build definition from Dockerfile 0.0s - => => transferring dockerfile: 3.60kB 0.0s - => [frontend internal] load metadata for docker.io/library/node:alpine 0.6s - => [frontend internal] load .dockerignore 0.0s - => => transferring context: 2B 0.0s - => [frontend internal] load build context 0.2s - => => transferring context: 7.22kB 0.0s - => [frontend 1/12] FROM docker.io/library/node:alpine@sha256:6eae672406a2bc8ed93eab6f9f76a02eb247e06ba82b2f5032c0a4ae07e825ba 0.1s - => => resolve docker.io/library/node:alpine@sha256:6eae672406a2bc8ed93eab6f9f76a02eb247e06ba82b2f5032c0a4ae07e825ba 0.1s - => CACHED [frontend 2/12] WORKDIR /app 0.0s - => [frontend 3/12] RUN apk add --no-cache python3 build-base g++ make sqlite sqlite-dev gcc musl-dev git libffi-dev openssl-dev cmake 51.4s - => [frontend 4/12] RUN npm install -g pnpm 5.1s - => [frontend 5/12] COPY package.json pnpm-lock.yaml ./ 0.3s - => [frontend 6/12] RUN pnpm install --unsafe-perm --no-optional --frozen-lockfile 23.2s - => ERROR [frontend 7/12] RUN npm install -g npx 2.3s ------- - > [frontend 7/12] RUN npm install -g npx: -1.975 npm error code EEXIST -1.975 npm error path /usr/local/bin/npx -1.975 npm error EEXIST: file already exists -1.975 npm error File exists: /usr/local/bin/npx -1.975 npm error Remove the existing file and try again, or run npm -1.975 npm error with --force to overwrite files recklessly. -1.989 npm error A complete log of this run can be found in: /root/.npm/_logs/2025-04-01T09_01_27_844Z-debug-0.log ------- -failed to solve: process "/bin/sh -c npm install -g npx" did not complete successfully: exit code: 1 -[2025-04-01 11:01:29] FEHLER: Docker Compose Build fehlgeschlagen. Siehe Fehlermeldung oben. diff --git a/frontend-aenderungen.md b/frontend-aenderungen.md deleted file mode 100755 index 97b5bba..0000000 --- a/frontend-aenderungen.md +++ /dev/null @@ -1,22 +0,0 @@ -# Notwendige Frontend-Änderungen - -1. Frontend-Authentifizierung anpassen: - - GitHub OAuth durch lokale Authentifizierung ersetzen - - Login-Komponenten für Benutzername/Passwort erstellen - - Registrierungs-Formular implementieren - - API-Routen für Login- und Registrierungsprozess anpassen - -2. Datenbankschema: - - Users-Tabelle anpassen um Passwort-Hash zu unterstützen - - GitHub-ID entfernen oder optional machen - -3. Auth-System: - - Lucia.js: Anpassung von OAuth auf Formular-basierte Authentifizierung - - Session-Management beibehalten - -4. API-Endpunktanpassungen: - - Neue Login und Register-Endpunkte erstellen - - Route für initialen Admin-Setup - -Die Änderungen im Frontend sind umfangreicher, da das aktuelle System stark auf GitHub OAuth ausgerichtet ist und komplett umgestellt werden muss. - diff --git a/install-frontend.sh b/install-frontend.sh deleted file mode 100755 index c5435b4..0000000 --- a/install-frontend.sh +++ /dev/null @@ -1,591 +0,0 @@ -#!/bin/bash - -# MYP Frontend Installations-Skript -# Dieses Skript installiert das Frontend mit Docker und Host-Netzwerkanbindung - -# Farbcodes für Ausgabe -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[0;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# Funktion zur Ausgabe mit Zeitstempel -log() { - echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1" -} - -error_log() { - echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] FEHLER:${NC} $1" >&2 -} - -# Funktion zum Bereinigen vorhandener Installationen -cleanup_existing_installation() { - log "${YELLOW}Bereinige vorhandene Installation...${NC}" - - # Stoppe und entferne existierende Container - if docker ps -a | grep -q "myp-frontend"; then - log "Stoppe und entferne existierenden Frontend-Container..." - docker stop myp-frontend &>/dev/null || true - docker rm myp-frontend &>/dev/null || true - fi - - # Entferne Docker Images - if docker images | grep -q "myp-frontend"; then - log "Entferne existierendes Frontend-Image..." - docker rmi myp-frontend &>/dev/null || true - fi - - log "${GREEN}Bereinigung abgeschlossen.${NC}" -} - -# Pfade definieren -SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -FRONTEND_DIR="$SCRIPT_DIR/packages/reservation-platform" - -# Prüfen ob Verzeichnis existiert -if [ ! -d "$FRONTEND_DIR" ]; then - error_log "Frontend-Verzeichnis '$FRONTEND_DIR' nicht gefunden." - exit 1 -fi - -# Bereinige existierende Installation -cleanup_existing_installation - -# Funktion zur Installation von Docker und Docker Compose für Raspberry Pi -install_docker() { - log "${YELLOW}Docker ist nicht installiert. Installation wird gestartet...${NC}" - - # Erkenne Raspberry Pi - if [ -f /proc/device-tree/model ] && grep -q "Raspberry Pi" /proc/device-tree/model; then - log "${GREEN}Raspberry Pi erkannt. Installiere Docker für ARM-Architektur...${NC}" - IS_RASPBERRY_PI=true - else - IS_RASPBERRY_PI=false - fi - - # Aktualisiere Paketindex - if ! sudo apt-get update; then - error_log "Konnte Paketindex nicht aktualisieren. Bitte manuell installieren." - exit 1 - fi - - # Installiere erforderliche Pakete - if ! sudo apt-get install -y apt-transport-https ca-certificates curl gnupg software-properties-common; then - error_log "Konnte erforderliche Pakete nicht installieren. Bitte manuell installieren." - exit 1 - fi - - # Raspberry Pi-spezifische Installation - if [ "$IS_RASPBERRY_PI" = true ]; then - # Setze Systemarchitektur für Raspberry Pi (armhf oder arm64) - ARCH=$(dpkg --print-architecture) - log "Erkannte Systemarchitektur: ${ARCH}" - - # Installiere Docker mit convenience script (für Raspberry Pi empfohlen) - log "${YELLOW}Installiere Docker mit dem convenience script...${NC}" - curl -fsSL https://get.docker.com -o get-docker.sh - sudo sh get-docker.sh - - if [ $? -ne 0 ]; then - error_log "Docker-Installation fehlgeschlagen. Bitte manuell installieren." - exit 1 - fi - else - # Standard-Installation für andere Systeme - # Füge Docker's offiziellen GPG-Schlüssel hinzu - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - - - # Füge Docker-Repository hinzu - if ! sudo add-apt-repository "deb [arch=$(dpkg --print-architecture)] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"; then - error_log "Konnte Docker-Repository nicht hinzufügen. Prüfen Sie, ob Ihr System unterstützt wird." - exit 1 - fi - - # Aktualisiere Paketindex erneut - sudo apt-get update - - # Installiere Docker - if ! sudo apt-get install -y docker-ce docker-ce-cli containerd.io; then - error_log "Konnte Docker nicht installieren. Bitte manuell installieren." - exit 1 - fi - fi - - # Füge aktuellen Benutzer zur Docker-Gruppe hinzu - sudo usermod -aG docker "$USER" - - log "${GREEN}Docker wurde installiert.${NC}" - log "${YELLOW}WICHTIG: Möglicherweise müssen Sie sich neu anmelden, damit die Gruppenänderung wirksam wird.${NC}" - - # Prüfen, ob Docker Compose v2 Plugin verfügbar ist (bevorzugt, da moderner) - log "${YELLOW}Prüfe Docker Compose Version...${NC}" - - if docker compose version &> /dev/null; then - log "${GREEN}Docker Compose v2 Plugin ist bereits installiert.${NC}" - DOCKER_COMPOSE_V2=true - else - log "${YELLOW}Docker Compose v2 Plugin nicht gefunden. Versuche Docker Compose v1 zu installieren...${NC}" - DOCKER_COMPOSE_V2=false - - if [ "$IS_RASPBERRY_PI" = true ]; then - # Für Raspberry Pi ist es besser, die richtige Architektur zu verwenden - if [ "$ARCH" = "armhf" ]; then - log "Installiere Docker Compose für armhf (32-bit)..." - sudo curl -L "https://github.com/docker/compose/releases/download/v2.6.1/docker-compose-linux-armv7" -o /usr/local/bin/docker-compose - elif [ "$ARCH" = "arm64" ]; then - log "Installiere Docker Compose für arm64 (64-bit)..." - sudo curl -L "https://github.com/docker/compose/releases/download/v2.6.1/docker-compose-linux-aarch64" -o /usr/local/bin/docker-compose - else - # Fallback auf v1.29.2 für unbekannte ARM-Architekturen - log "Verwende automatische Architekturerkennung für Docker Compose v1.29.2..." - sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose - fi - else - # Für andere Systeme versuche zuerst v2, dann v1.29.2 als Fallback - log "Installiere Docker Compose v2 für $(uname -s)/$(uname -m)..." - if ! sudo curl -L "https://github.com/docker/compose/releases/download/v2.6.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose; then - log "${YELLOW}Konnte Docker Compose v2 nicht herunterladen. Versuche v1.29.2...${NC}" - sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose - fi - fi - - if [ $? -ne 0 ]; then - error_log "Konnte Docker Compose nicht herunterladen. Bitte manuell installieren." - exit 1 - fi - - sudo chmod +x /usr/local/bin/docker-compose - - log "${GREEN}Docker Compose wurde installiert.${NC}" - fi - - # Starte Docker-Dienst - if command -v systemctl &> /dev/null; then - sudo systemctl enable docker - sudo systemctl start docker - elif command -v service &> /dev/null; then - sudo service docker enable - sudo service docker start - fi -} - -# Prüfen ob Docker installiert ist -if ! command -v docker &> /dev/null; then - log "${YELLOW}Docker ist nicht installiert.${NC}" - read -p "Möchten Sie Docker installieren? (j/n): " install_docker_choice - if [[ "$install_docker_choice" == "j" ]]; then - install_docker - else - error_log "Docker wird für die Installation benötigt. Bitte installieren Sie Docker manuell." - log "Siehe: https://docs.docker.com/get-docker/" - exit 1 - fi -fi - -# Prüfen ob Docker Daemon läuft -if ! docker info &> /dev/null; then - log "${YELLOW}Docker-Daemon läuft nicht. Versuche, den Dienst zu starten...${NC}" - - # Versuche, Docker zu starten - if command -v systemctl &> /dev/null; then - sudo systemctl start docker - elif command -v service &> /dev/null; then - sudo service docker start - else - error_log "Konnte Docker-Daemon nicht starten. Bitte starten Sie den Docker-Dienst manuell." - log "Starten mit: sudo systemctl start docker oder sudo service docker start" - exit 1 - fi - - # Prüfe erneut, ob Docker läuft - if ! docker info &> /dev/null; then - error_log "Docker-Daemon konnte nicht gestartet werden. Bitte starten Sie den Docker-Dienst manuell." - exit 1 - fi - - log "${GREEN}Docker-Daemon wurde erfolgreich gestartet.${NC}" -fi - -# Prüfen ob Docker Compose installiert ist -if docker compose version &> /dev/null; then - log "${GREEN}Docker Compose v2 Plugin ist bereits installiert.${NC}" - DOCKER_COMPOSE_V2=true -elif command -v docker-compose &> /dev/null; then - log "${GREEN}Docker Compose v1 ist bereits installiert.${NC}" - DOCKER_COMPOSE_V2=false -else - log "${YELLOW}Docker Compose ist nicht installiert.${NC}" - DOCKER_COMPOSE_V2=false - read -p "Möchten Sie Docker Compose installieren? (j/n): " install_compose_choice - if [[ "$install_compose_choice" == "j" ]]; then - log "${YELLOW}Installiere Docker Compose...${NC}" - - # Prüfe ob das Betriebssystem ARM-basiert ist (z.B. Raspberry Pi) - if grep -q "arm" /proc/cpuinfo 2> /dev/null; then - ARCH=$(dpkg --print-architecture 2> /dev/null || echo "unknown") - IS_RASPBERRY_PI=true - else - IS_RASPBERRY_PI=false - fi - - # Versuche zuerst Docker Compose v2 zu installieren - if [ "$IS_RASPBERRY_PI" = true ]; then - if [ "$ARCH" = "armhf" ]; then - log "Installiere Docker Compose für armhf (32-bit)..." - sudo curl -L "https://github.com/docker/compose/releases/download/v2.6.1/docker-compose-linux-armv7" -o /usr/local/bin/docker-compose - elif [ "$ARCH" = "arm64" ]; then - log "Installiere Docker Compose für arm64 (64-bit)..." - sudo curl -L "https://github.com/docker/compose/releases/download/v2.6.1/docker-compose-linux-aarch64" -o /usr/local/bin/docker-compose - else - log "Verwende automatische Architekturerkennung für Docker Compose v1.29.2..." - sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose - fi - else - log "Installiere Docker Compose v2 für $(uname -s)/$(uname -m)..." - if ! sudo curl -L "https://github.com/docker/compose/releases/download/v2.6.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose; then - log "${YELLOW}Konnte Docker Compose v2 nicht herunterladen. Versuche v1.29.2...${NC}" - sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose - fi - fi - - if [ $? -ne 0 ]; then - error_log "Konnte Docker Compose nicht herunterladen. Bitte manuell installieren." - exit 1 - fi - - sudo chmod +x /usr/local/bin/docker-compose - - log "${GREEN}Docker Compose wurde installiert.${NC}" - else - error_log "Docker Compose wird für die Installation benötigt. Bitte installieren Sie es manuell." - log "Siehe: https://docs.docker.com/compose/install/" - exit 1 - fi -fi - -# Prüfen ob wget installiert ist (wird für healthcheck verwendet) -if ! command -v wget &> /dev/null; then - error_log "wget ist nicht installiert, wird aber für den Container-Healthcheck benötigt." - log "Installation mit: sudo apt-get install wget" - exit 1 -fi - -# Wechsle ins Frontend-Verzeichnis -log "Wechsle ins Verzeichnis: $FRONTEND_DIR" -cd "$FRONTEND_DIR" || { - error_log "Konnte nicht ins Verzeichnis $FRONTEND_DIR wechseln." - exit 1 -} - -# Prüfe ob Dockerfile existiert -if [ ! -f "Dockerfile" ]; then - error_log "Dockerfile nicht gefunden in $FRONTEND_DIR." - exit 1 -fi - -# Prüfe ob docker-compose.yml existiert -if [ ! -f "docker-compose.yml" ]; then - error_log "docker-compose.yml nicht gefunden in $FRONTEND_DIR." - exit 1 -fi - -# Prüfe ob package.json existiert -if [ ! -f "package.json" ]; then - error_log "package.json nicht gefunden in $FRONTEND_DIR." - exit 1 -fi - -# Erstelle .env-Datei -log "${YELLOW}Erstelle .env Datei...${NC}" -cat > .env << EOL -# Basic Server Configuration -RUNTIME_ENVIRONMENT=prod -DB_PATH=db/sqlite.db - -# OAuth Configuration (Bitte anpassen) -OAUTH_CLIENT_ID=client_id -OAUTH_CLIENT_SECRET=client_secret - -# Backend-API URL (IP-Adresse oder Hostname des Backend-Servers) -NEXT_PUBLIC_API_URL=http://localhost:5000 -EOL - -if [ ! -f ".env" ]; then - error_log "Konnte .env-Datei nicht erstellen. Prüfen Sie die Berechtigungen." - exit 1 -fi -log "${GREEN}.env Datei erfolgreich erstellt${NC}" -log "${YELLOW}HINWEIS: Bitte passen Sie die Backend-URL in der .env-Datei an, falls das Backend auf einem anderen Server läuft.${NC}" - -# Datenbank-Verzeichnis erstellen -log "Erstelle Datenbankverzeichnis" -if ! mkdir -p db; then - error_log "Konnte Verzeichnis 'db' nicht erstellen. Prüfen Sie die Berechtigungen." - exit 1 -fi - -# Docker-Image bauen und starten -log "${YELLOW}Baue und starte Frontend-Container...${NC}" -log "${YELLOW}Dies kann auf einem Raspberry Pi mehrere Minuten dauern - bitte geduldig sein${NC}" - -# Prüfe, ob Docker-Daemon läuft -if ! docker info &>/dev/null; then - log "${YELLOW}Docker-Daemon scheint nicht zu laufen. Versuche zu starten...${NC}" - - # Versuche Docker zu starten - if command -v systemctl &>/dev/null; then - sudo systemctl start docker || true - sleep 5 - elif command -v service &>/dev/null; then - sudo service docker start || true - sleep 5 - fi - - # Prüfe erneut, ob Docker jetzt läuft - if ! docker info &>/dev/null; then - error_log "Docker-Daemon konnte nicht gestartet werden." - log "Führen Sie vor der Installation bitte folgende Befehle aus:" - log " sudo systemctl start docker" - log " sudo systemctl enable docker" - log "Starten Sie dann das Installationsskript erneut." - exit 1 - fi -fi - -# Docker-Rechte prüfen -if ! docker ps &>/dev/null; then - error_log "Sie haben keine Berechtigung, Docker ohne sudo zu verwenden." - log "Bitte führen Sie folgenden Befehl aus und melden Sie sich danach neu an:" - log " sudo usermod -aG docker $USER" - exit 1 -fi - -# Prüfen, ob erforderliche Basis-Images lokal verfügbar sind -if ! docker image inspect node:lts-alpine &>/dev/null; then - log "${YELLOW}Prüfe und setze DNS-Server für Docker...${NC}" - - # DNS-Einstellungen prüfen und anpassen - if [ -f /etc/docker/daemon.json ]; then - log "Bestehende Docker-Konfiguration gefunden." - else - log "Erstelle Docker-Konfiguration mit Google DNS..." - sudo mkdir -p /etc/docker - echo '{ - "dns": ["8.8.8.8", "8.8.4.4"] -}' | sudo tee /etc/docker/daemon.json > /dev/null - - # Docker neu starten, damit die Änderungen wirksam werden - if command -v systemctl &>/dev/null; then - sudo systemctl restart docker - sleep 5 - elif command -v service &>/dev/null; then - sudo service docker restart - sleep 5 - fi - fi - - # Versuche Image explizit mit anderen Tags herunterzuladen - log "${YELLOW}Versuche lokal vorhandene Node-Version zu finden...${NC}" - - # Suche nach allen verfügbaren Node-Images - NODE_IMAGES=$(docker images --format "{{.Repository}}:{{.Tag}}" | grep "node:") - - if [ -n "$NODE_IMAGES" ]; then - log "Gefundene Node-Images: $NODE_IMAGES" - # Verwende das erste gefundene Node-Image - FIRST_NODE=$(echo "$NODE_IMAGES" | head -n 1) - log "${GREEN}Verwende vorhandenes Node-Image: $FIRST_NODE${NC}" - - # Aktualisiere den Dockerfile - sed -i "s|FROM node:lts-alpine|FROM $FIRST_NODE|g" Dockerfile - log "Dockerfile aktualisiert, um lokales Image zu verwenden." - else - # Versuche unterschiedliche Node-Versionen - for NODE_VERSION in "node:20-alpine" "node:18-alpine" "node:16-alpine" "node:alpine" "node:slim"; do - log "Versuche $NODE_VERSION zu laden..." - if docker pull $NODE_VERSION; then - log "${GREEN}Erfolgreich $NODE_VERSION heruntergeladen${NC}" - # Aktualisiere den Dockerfile - sed -i "s|FROM node:lts-alpine|FROM $NODE_VERSION|g" Dockerfile - log "Dockerfile aktualisiert, um $NODE_VERSION zu verwenden." - break - fi - done - fi -fi - -# Erhöhe Docker-Timeout für langsame Verbindungen und Raspberry Pi -export DOCKER_CLIENT_TIMEOUT=300 -export COMPOSE_HTTP_TIMEOUT=300 - -# Verwende die richtige Docker Compose Version -if [ "${DOCKER_COMPOSE_V2:-false}" = true ]; then - # Docker Compose V2 Plugin (docker compose) - log "Baue lokales Image..." - if ! docker compose build --no-cache; then - error_log "Docker Compose Build (v2) fehlgeschlagen. Versuche mit v1 Format..." - if ! docker-compose build --no-cache; then - error_log "Docker Compose Build fehlgeschlagen. Siehe Fehlermeldung oben." - exit 1 - fi - fi - - log "Starte Container aus lokalem Image..." - if ! docker compose up -d; then - error_log "Docker Compose Up (v2) fehlgeschlagen. Versuche mit v1 Format..." - if ! docker-compose up -d; then - error_log "Docker Compose Up fehlgeschlagen. Siehe Fehlermeldung oben." - exit 1 - fi - fi -else - # Docker Compose V1 (docker-compose) - log "Baue lokales Image..." - if ! docker-compose build --no-cache; then - error_log "Docker Compose Build fehlgeschlagen. Siehe Fehlermeldung oben." - exit 1 - fi - - log "Starte Container aus lokalem Image..." - if ! docker-compose up -d; then - error_log "Docker Compose Up fehlgeschlagen. Siehe Fehlermeldung oben." - exit 1 - fi -fi - -# Prüfe, ob der Container läuft -log "Warte 10 Sekunden, bis der Container gestartet ist..." -sleep 10 - -# Prüfe mehrmals, da der Container möglicherweise länger zum Starten braucht -MAX_ATTEMPTS=5 -CURRENT_ATTEMPT=1 - -while [ $CURRENT_ATTEMPT -le $MAX_ATTEMPTS ]; do - log "Prüfe Container-Status (Versuch $CURRENT_ATTEMPT von $MAX_ATTEMPTS)..." - - if docker ps | grep -q "myp-frontend"; then - log "${GREEN}Frontend-Container läuft${NC}" - break - else - CONTAINER_STATUS=$(docker ps -a | grep myp-frontend) - CONTAINER_CREATED=$(echo "$CONTAINER_STATUS" | grep -q "Created" && echo "true" || echo "false") - CONTAINER_EXITED=$(echo "$CONTAINER_STATUS" | grep -q "Exited" && echo "true" || echo "false") - - if [ "$CONTAINER_EXITED" = "true" ]; then - log "${YELLOW}Container wurde beendet. Prüfe Logs...${NC}" - docker logs myp-frontend - - log "${YELLOW}Starte Container neu mit besserer Debug-Ausgabe...${NC}" - docker rm -f myp-frontend - - if [ "${DOCKER_COMPOSE_V2:-false}" = true ]; then - docker compose up -d - else - docker-compose up -d - fi - - sleep 10 - fi - - if [ $CURRENT_ATTEMPT -eq $MAX_ATTEMPTS ]; then - error_log "Frontend-Container läuft nach mehreren Versuchen nicht. Container-Status:" - docker ps -a | grep myp-frontend - log "Container-Logs:" - docker logs myp-frontend - exit 1 - fi - fi - - CURRENT_ATTEMPT=$((CURRENT_ATTEMPT + 1)) - sleep 20 -done - -# Teste ob der Server erreichbar ist -log "${YELLOW}Teste ob Frontend-Server erreichbar ist...${NC}" -log "${YELLOW}HINWEIS: Bei der Erstinstallation kann es einige Minuten dauern, bis der Server erreichbar ist${NC}" -log "${YELLOW}Bei anhaltenden Problemen kann ein Neustart des Systems helfen${NC}" - -MAX_ATTEMPTS=3 -ATTEMPT=1 - -while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do - log "Versuch $ATTEMPT/$MAX_ATTEMPTS..." - if curl -s -o /dev/null -w "%{http_code}" http://localhost:3000 2>/dev/null | grep -q "200\|304"; then - log "${GREEN}Frontend-Server ist erreichbar!${NC}" - break - else - # Prüfe, ob der Container noch läuft oder Fehler aufweist - CONTAINER_STATUS=$(docker inspect --format='{{.State.Status}}' myp-frontend 2>/dev/null || echo "nicht gefunden") - - if [ "$CONTAINER_STATUS" != "running" ]; then - log "${YELLOW}Container ist nicht aktiv (Status: $CONTAINER_STATUS). Prüfe Logs...${NC}" - docker logs myp-frontend --tail 20 - - # Wenn der Container gestoppt wurde, starte ihn neu - if [ "$CONTAINER_STATUS" = "exited" ] || [ "$CONTAINER_STATUS" = "created" ]; then - log "${YELLOW}Versuche, den Container neuzustarten...${NC}" - docker start myp-frontend - sleep 10 - fi - fi - - if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then - log "${YELLOW}Server noch nicht erreichbar. Das ist bei der Erstinstallation normal.${NC}" - log "${GREEN}Der Container ist installiert und sollte nach einem System-Neustart korrekt funktionieren.${NC}" - log "${GREEN}Beim ersten Start kann die Datenbank-Migration und der Build länger dauern.${NC}" - log "Überprüfen Sie später den Container-Status mit: docker logs myp-frontend" - else - log "Server noch nicht erreichbar. Warte 10 Sekunden..." - sleep 10 - fi - fi - ATTEMPT=$((ATTEMPT+1)) -done - -# Stellen Sie sicher, dass Verzeichnis- und Datei-Berechtigungen korrekt gesetzt sind -log "${YELLOW}Bereite Datenbank vor...${NC}" -mkdir -p db -touch db/sqlite.db -chmod 666 db/sqlite.db -log "${GREEN}Datenbank vorbereitet${NC}" - -# Führe bei Bedarf SQLite-Rebuild im Container aus -log "${YELLOW}Führe SQLite-Rebuild im Container durch...${NC}" -docker exec myp-frontend npm_config_build_from_source=true pnpm rebuild better-sqlite3 || { - log "${YELLOW}Rebuild im laufenden Container nicht möglich. Wird beim nächsten Start automatisch ausgeführt.${NC}" -} - -# Prüfe ob die Datenbank-Migration erfolgt ist -log "${YELLOW}Prüfe Datenbank-Migration...${NC}" -log "${YELLOW}Hinweis: Die Migration wird beim ersten Start nach dem Systemneustart automatisch ausgeführt${NC}" - -if [ -f "db/sqlite.db" ]; then - log "${GREEN}Datenbank existiert${NC}" - - # Setze Berechtigungen - chmod 666 db/sqlite.db - - # Prüfe Datenbankgröße - DB_SIZE=$(du -b db/sqlite.db 2>/dev/null | cut -f1 || echo "0") - if [ "$DB_SIZE" -gt 1000 ]; then - log "${GREEN}Datenbank scheint initialisiert zu sein (Größe: $DB_SIZE Bytes)${NC}" - else - log "${YELLOW}Datenbank ist leer oder sehr klein. Die Migration wird beim ersten Start ausgeführt.${NC}" - fi -else - log "${YELLOW}Konnte Datenbank-Datei nicht finden. Wird beim Neustart automatisch erstellt.${NC}" -fi - -log "${GREEN}=== Installation abgeschlossen ===${NC}" -log "${YELLOW}WICHTIG: Nach der Erstinstallation ist ein Systemneustart erforderlich${NC}" -log "${YELLOW}Danach ist das Frontend unter http://localhost:3000 erreichbar${NC}" -log "Anzeigen der Logs: docker logs -f myp-frontend" - -# Verwende die richtige Docker Compose Version für Hinweis -if [ "${DOCKER_COMPOSE_V2:-false}" = true ]; then - log "Frontend stoppen: docker compose -f $FRONTEND_DIR/docker-compose.yml down" -else - log "Frontend stoppen: docker-compose -f $FRONTEND_DIR/docker-compose.yml down" -fi \ No newline at end of file diff --git a/log.txt b/log.txt deleted file mode 100644 index 1667646..0000000 --- a/log.txt +++ /dev/null @@ -1,536 +0,0 @@ -⨯ Error: Could not locate the bindings file. Tried: - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/Debug/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/Release/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/out/Debug/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/Debug/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/out/Release/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/Release/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/default/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/compiled/20.19.0/linux/arm64/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/addon-build/release/install-root/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/addon-build/debug/install-root/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/addon-build/default/install-root/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/lib/binding/node-v115-linux-arm64/better_sqlite3.node - at bindings (/app/node_modules/.pnpm/bindings@1.5.0/node_modules/bindings/bindings.js:126:9) - at new Database (/app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/lib/database.js:48:64) - at eval (webpack-internal:///(rsc)/./src/server/db/index.ts:14:16) - at (rsc)/./src/server/db/index.ts (/app/.next/server/app/page.js:1001:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (webpack-internal:///(rsc)/./src/server/auth/index.ts:6:68) - at (rsc)/./src/server/auth/index.ts (/app/.next/server/app/page.js:957:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (webpack-internal:///(rsc)/./src/components/header/index.tsx:12:70) - at (rsc)/./src/components/header/index.tsx (/app/.next/server/app/page.js:697:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (webpack-internal:///(rsc)/./src/app/layout.tsx:10:76) - at (rsc)/./src/app/layout.tsx (/app/.next/server/app/page.js:594:1) - at Function.__webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at async e9 (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:396515) - at async tb (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:400212) - at async tS (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:400773) - at async tR (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:36:2130) - at async /app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:36:2722 { - digest: '4214325463', - page: '/' -} - GET / 500 in 40ms - ⨯ Error: Could not locate the bindings file. Tried: - at eval (./src/server/db/index.ts:14:16) - at (rsc)/./src/server/db/index.ts (/app/.next/server/app/page.js:1001:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/server/auth/index.ts:6:68) - at (rsc)/./src/server/auth/index.ts (/app/.next/server/app/page.js:957:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/components/header/index.tsx:12:70) - at (rsc)/./src/components/header/index.tsx (/app/.next/server/app/page.js:697:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/app/layout.tsx:10:76) - at (rsc)/./src/app/layout.tsx (/app/.next/server/app/page.js:594:1) - at Function.__webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) -digest: "3425251174" - ⨯ Error: Could not locate the bindings file. Tried: - at eval (./src/server/db/index.ts:14:16) - at (rsc)/./src/server/db/index.ts (/app/.next/server/app/page.js:1001:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/server/auth/index.ts:6:68) - at (rsc)/./src/server/auth/index.ts (/app/.next/server/app/page.js:957:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/components/header/index.tsx:12:70) - at (rsc)/./src/components/header/index.tsx (/app/.next/server/app/page.js:697:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/app/layout.tsx:10:76) - at (rsc)/./src/app/layout.tsx (/app/.next/server/app/page.js:594:1) - at Function.__webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) -digest: "3425251174" -Error: Could not locate the bindings file. Tried: - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/Debug/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/Release/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/out/Debug/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/Debug/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/out/Release/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/Release/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/default/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/compiled/20.19.0/linux/arm64/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/addon-build/release/install-root/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/addon-build/debug/install-root/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/addon-build/default/install-root/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/lib/binding/node-v115-linux-arm64/better_sqlite3.node - at bindings (/app/node_modules/.pnpm/bindings@1.5.0/node_modules/bindings/bindings.js:126:9) - at new Database (/app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/lib/database.js:48:64) - at eval (webpack-internal:///(rsc)/./src/server/db/index.ts:14:16) - at (rsc)/./src/server/db/index.ts (/app/.next/server/app/page.js:1001:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (webpack-internal:///(rsc)/./src/server/auth/index.ts:6:68) - at (rsc)/./src/server/auth/index.ts (/app/.next/server/app/page.js:957:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (webpack-internal:///(rsc)/./src/components/header/index.tsx:12:70) - at (rsc)/./src/components/header/index.tsx (/app/.next/server/app/page.js:697:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (webpack-internal:///(rsc)/./src/app/layout.tsx:10:76) - at (rsc)/./src/app/layout.tsx (/app/.next/server/app/page.js:594:1) - at Function.__webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at async e9 (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:396515) - at async tb (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:400212) - at async tS (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:400773) - at async tR (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:36:2130) - at async /app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:36:2722 { - digest: '4214325463' -} - ⨯ Error: Could not locate the bindings file. Tried: - at eval (./src/server/db/index.ts:14:16) - at (rsc)/./src/server/db/index.ts (/app/.next/server/app/page.js:1001:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/server/auth/index.ts:6:68) - at (rsc)/./src/server/auth/index.ts (/app/.next/server/app/page.js:957:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/components/header/index.tsx:12:70) - at (rsc)/./src/components/header/index.tsx (/app/.next/server/app/page.js:697:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/app/layout.tsx:10:76) - at (rsc)/./src/app/layout.tsx (/app/.next/server/app/page.js:594:1) - at Function.__webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) -digest: "4214325463" - ⨯ Error: Could not locate the bindings file. Tried: - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/Debug/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/Release/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/out/Debug/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/Debug/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/out/Release/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/Release/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/default/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/compiled/20.19.0/linux/arm64/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/addon-build/release/install-root/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/addon-build/debug/install-root/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/addon-build/default/install-root/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/lib/binding/node-v115-linux-arm64/better_sqlite3.node - at bindings (/app/node_modules/.pnpm/bindings@1.5.0/node_modules/bindings/bindings.js:126:9) - at new Database (/app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/lib/database.js:48:64) - at eval (webpack-internal:///(rsc)/./src/server/db/index.ts:14:16) - at (rsc)/./src/server/db/index.ts (/app/.next/server/app/page.js:1001:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (webpack-internal:///(rsc)/./src/server/auth/index.ts:6:68) - at (rsc)/./src/server/auth/index.ts (/app/.next/server/app/page.js:957:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (webpack-internal:///(rsc)/./src/components/header/index.tsx:12:70) - at (rsc)/./src/components/header/index.tsx (/app/.next/server/app/page.js:697:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (webpack-internal:///(rsc)/./src/app/layout.tsx:10:76) - at (rsc)/./src/app/layout.tsx (/app/.next/server/app/page.js:594:1) - at Function.__webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at async e9 (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:396515) - at async tb (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:400212) - at async tS (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:400773) - at async tR (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:36:2130) - at async /app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:36:2722 { - digest: '4214325463', - page: '/' -} - GET / 500 in 39ms - ⨯ Error: Could not locate the bindings file. Tried: - at eval (./src/server/db/index.ts:14:16) - at (rsc)/./src/server/db/index.ts (/app/.next/server/app/page.js:1001:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/server/auth/index.ts:6:68) - at (rsc)/./src/server/auth/index.ts (/app/.next/server/app/page.js:957:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/components/header/index.tsx:12:70) - at (rsc)/./src/components/header/index.tsx (/app/.next/server/app/page.js:697:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/app/layout.tsx:10:76) - at (rsc)/./src/app/layout.tsx (/app/.next/server/app/page.js:594:1) - at Function.__webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) -digest: "3020338880" - ⨯ Error: Could not locate the bindings file. Tried: - at eval (./src/server/db/index.ts:14:16) - at (rsc)/./src/server/db/index.ts (/app/.next/server/app/page.js:1001:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/server/auth/index.ts:6:68) - at (rsc)/./src/server/auth/index.ts (/app/.next/server/app/page.js:957:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/components/header/index.tsx:12:70) - at (rsc)/./src/components/header/index.tsx (/app/.next/server/app/page.js:697:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/app/layout.tsx:10:76) - at (rsc)/./src/app/layout.tsx (/app/.next/server/app/page.js:594:1) - at Function.__webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) -digest: "3020338880" -Error: Could not locate the bindings file. Tried: - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/Debug/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/Release/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/out/Debug/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/Debug/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/out/Release/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/Release/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/default/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/compiled/20.19.0/linux/arm64/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/addon-build/release/install-root/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/addon-build/debug/install-root/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/addon-build/default/install-root/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/lib/binding/node-v115-linux-arm64/better_sqlite3.node - at bindings (/app/node_modules/.pnpm/bindings@1.5.0/node_modules/bindings/bindings.js:126:9) - at new Database (/app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/lib/database.js:48:64) - at eval (webpack-internal:///(rsc)/./src/server/db/index.ts:14:16) - at (rsc)/./src/server/db/index.ts (/app/.next/server/app/page.js:1001:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (webpack-internal:///(rsc)/./src/server/auth/index.ts:6:68) - at (rsc)/./src/server/auth/index.ts (/app/.next/server/app/page.js:957:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (webpack-internal:///(rsc)/./src/components/header/index.tsx:12:70) - at (rsc)/./src/components/header/index.tsx (/app/.next/server/app/page.js:697:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (webpack-internal:///(rsc)/./src/app/layout.tsx:10:76) - at (rsc)/./src/app/layout.tsx (/app/.next/server/app/page.js:594:1) - at Function.__webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at async e9 (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:396515) - at async tb (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:400212) - at async tS (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:400773) - at async tR (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:36:2130) - at async /app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:36:2722 { - digest: '4214325463' -} - ⨯ Error: Could not locate the bindings file. Tried: - at eval (./src/server/db/index.ts:14:16) - at (rsc)/./src/server/db/index.ts (/app/.next/server/app/page.js:1001:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/server/auth/index.ts:6:68) - at (rsc)/./src/server/auth/index.ts (/app/.next/server/app/page.js:957:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/components/header/index.tsx:12:70) - at (rsc)/./src/components/header/index.tsx (/app/.next/server/app/page.js:697:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/app/layout.tsx:10:76) - at (rsc)/./src/app/layout.tsx (/app/.next/server/app/page.js:594:1) - at Function.__webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) -digest: "4214325463" - ⨯ Error: Could not locate the bindings file. Tried: - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/Debug/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/Release/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/out/Debug/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/Debug/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/out/Release/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/Release/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/default/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/compiled/20.19.0/linux/arm64/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/addon-build/release/install-root/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/addon-build/debug/install-root/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/addon-build/default/install-root/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/lib/binding/node-v115-linux-arm64/better_sqlite3.node - at bindings (/app/node_modules/.pnpm/bindings@1.5.0/node_modules/bindings/bindings.js:126:9) - at new Database (/app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/lib/database.js:48:64) - at eval (webpack-internal:///(rsc)/./src/server/db/index.ts:14:16) - at (rsc)/./src/server/db/index.ts (/app/.next/server/app/page.js:1001:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (webpack-internal:///(rsc)/./src/server/auth/index.ts:6:68) - at (rsc)/./src/server/auth/index.ts (/app/.next/server/app/page.js:957:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (webpack-internal:///(rsc)/./src/components/header/index.tsx:12:70) - at (rsc)/./src/components/header/index.tsx (/app/.next/server/app/page.js:697:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (webpack-internal:///(rsc)/./src/app/layout.tsx:10:76) - at (rsc)/./src/app/layout.tsx (/app/.next/server/app/page.js:594:1) - at Function.__webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at async e9 (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:396515) - at async tb (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:400212) - at async tS (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:400773) - at async tR (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:36:2130) - at async /app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:36:2722 { - digest: '4214325463', - page: '/' -} - GET / 500 in 37ms - ⨯ Error: Could not locate the bindings file. Tried: - at eval (./src/server/db/index.ts:14:16) - at (rsc)/./src/server/db/index.ts (/app/.next/server/app/page.js:1001:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/server/auth/index.ts:6:68) - at (rsc)/./src/server/auth/index.ts (/app/.next/server/app/page.js:957:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/components/header/index.tsx:12:70) - at (rsc)/./src/components/header/index.tsx (/app/.next/server/app/page.js:697:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/app/layout.tsx:10:76) - at (rsc)/./src/app/layout.tsx (/app/.next/server/app/page.js:594:1) - at Function.__webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) -digest: "3425251174" - ⨯ Error: Could not locate the bindings file. Tried: - at eval (./src/server/db/index.ts:14:16) - at (rsc)/./src/server/db/index.ts (/app/.next/server/app/page.js:1001:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/server/auth/index.ts:6:68) - at (rsc)/./src/server/auth/index.ts (/app/.next/server/app/page.js:957:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/components/header/index.tsx:12:70) - at (rsc)/./src/components/header/index.tsx (/app/.next/server/app/page.js:697:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/app/layout.tsx:10:76) - at (rsc)/./src/app/layout.tsx (/app/.next/server/app/page.js:594:1) - at Function.__webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) -digest: "3425251174" - ⨯ Error: Could not locate the bindings file. Tried: - at eval (./src/server/db/index.ts:14:16) - at (rsc)/./src/server/db/index.ts (/app/.next/server/app/page.js:1001:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/server/auth/index.ts:6:68) - at (rsc)/./src/server/auth/index.ts (/app/.next/server/app/page.js:957:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/components/header/index.tsx:12:70) - at (rsc)/./src/components/header/index.tsx (/app/.next/server/app/page.js:697:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/app/layout.tsx:10:76) - at (rsc)/./src/app/layout.tsx (/app/.next/server/app/page.js:594:1) - at Function.__webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) -digest: "4214325463" -Error: Could not locate the bindings file. Tried: - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/Debug/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/Release/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/out/Debug/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/Debug/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/out/Release/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/Release/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/default/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/compiled/20.19.0/linux/arm64/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/addon-build/release/install-root/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/addon-build/debug/install-root/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/addon-build/default/install-root/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/lib/binding/node-v115-linux-arm64/better_sqlite3.node - at bindings (/app/node_modules/.pnpm/bindings@1.5.0/node_modules/bindings/bindings.js:126:9) - at new Database (/app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/lib/database.js:48:64) - at eval (webpack-internal:///(rsc)/./src/server/db/index.ts:14:16) - at (rsc)/./src/server/db/index.ts (/app/.next/server/app/page.js:1001:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (webpack-internal:///(rsc)/./src/server/auth/index.ts:6:68) - at (rsc)/./src/server/auth/index.ts (/app/.next/server/app/page.js:957:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (webpack-internal:///(rsc)/./src/components/header/index.tsx:12:70) - at (rsc)/./src/components/header/index.tsx (/app/.next/server/app/page.js:697:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (webpack-internal:///(rsc)/./src/app/layout.tsx:10:76) - at (rsc)/./src/app/layout.tsx (/app/.next/server/app/page.js:594:1) - at Function.__webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at async e9 (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:396515) - at async tb (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:400212) - at async tS (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:400773) - at async tR (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:36:2130) - at async /app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:36:2722 { - digest: '4214325463' -} - ⨯ Error: Could not locate the bindings file. Tried: - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/Debug/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/Release/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/out/Debug/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/Debug/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/out/Release/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/Release/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/default/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/compiled/20.19.0/linux/arm64/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/addon-build/release/install-root/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/addon-build/debug/install-root/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/addon-build/default/install-root/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/lib/binding/node-v115-linux-arm64/better_sqlite3.node - at bindings (/app/node_modules/.pnpm/bindings@1.5.0/node_modules/bindings/bindings.js:126:9) - at new Database (/app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/lib/database.js:48:64) - at eval (webpack-internal:///(rsc)/./src/server/db/index.ts:14:16) - at (rsc)/./src/server/db/index.ts (/app/.next/server/app/page.js:1001:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (webpack-internal:///(rsc)/./src/server/auth/index.ts:6:68) - at (rsc)/./src/server/auth/index.ts (/app/.next/server/app/page.js:957:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (webpack-internal:///(rsc)/./src/components/header/index.tsx:12:70) - at (rsc)/./src/components/header/index.tsx (/app/.next/server/app/page.js:697:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (webpack-internal:///(rsc)/./src/app/layout.tsx:10:76) - at (rsc)/./src/app/layout.tsx (/app/.next/server/app/page.js:594:1) - at Function.__webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at async e9 (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:396515) - at async tb (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:400212) - at async tS (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:400773) - at async tR (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:36:2130) - at async /app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:36:2722 { - digest: '4214325463', - page: '/' -} - GET / 500 in 32ms - ⨯ Error: Could not locate the bindings file. Tried: - at eval (./src/server/db/index.ts:14:16) - at (rsc)/./src/server/db/index.ts (/app/.next/server/app/page.js:1001:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/server/auth/index.ts:6:68) - at (rsc)/./src/server/auth/index.ts (/app/.next/server/app/page.js:957:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/components/header/index.tsx:12:70) - at (rsc)/./src/components/header/index.tsx (/app/.next/server/app/page.js:697:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/app/layout.tsx:10:76) - at (rsc)/./src/app/layout.tsx (/app/.next/server/app/page.js:594:1) - at Function.__webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) -digest: "3425251174" - ⨯ Error: Could not locate the bindings file. Tried: - at eval (./src/server/db/index.ts:14:16) - at (rsc)/./src/server/db/index.ts (/app/.next/server/app/page.js:1001:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/server/auth/index.ts:6:68) - at (rsc)/./src/server/auth/index.ts (/app/.next/server/app/page.js:957:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/components/header/index.tsx:12:70) - at (rsc)/./src/components/header/index.tsx (/app/.next/server/app/page.js:697:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/app/layout.tsx:10:76) - at (rsc)/./src/app/layout.tsx (/app/.next/server/app/page.js:594:1) - at Function.__webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) -digest: "3425251174" -Error: Could not locate the bindings file. Tried: - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/Debug/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/Release/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/out/Debug/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/Debug/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/out/Release/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/Release/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/default/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/compiled/20.19.0/linux/arm64/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/addon-build/release/install-root/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/addon-build/debug/install-root/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/addon-build/default/install-root/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/lib/binding/node-v115-linux-arm64/better_sqlite3.node - at bindings (/app/node_modules/.pnpm/bindings@1.5.0/node_modules/bindings/bindings.js:126:9) - at new Database (/app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/lib/database.js:48:64) - at eval (webpack-internal:///(rsc)/./src/server/db/index.ts:14:16) - at (rsc)/./src/server/db/index.ts (/app/.next/server/app/page.js:1001:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (webpack-internal:///(rsc)/./src/server/auth/index.ts:6:68) - at (rsc)/./src/server/auth/index.ts (/app/.next/server/app/page.js:957:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (webpack-internal:///(rsc)/./src/components/header/index.tsx:12:70) - at (rsc)/./src/components/header/index.tsx (/app/.next/server/app/page.js:697:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (webpack-internal:///(rsc)/./src/app/layout.tsx:10:76) - at (rsc)/./src/app/layout.tsx (/app/.next/server/app/page.js:594:1) - at Function.__webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at async e9 (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:396515) - at async tb (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:400212) - at async tS (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:400773) - at async tR (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:36:2130) - at async /app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:36:2722 { - digest: '4214325463' -} - ⨯ Error: Could not locate the bindings file. Tried: - at eval (./src/server/db/index.ts:14:16) - at (rsc)/./src/server/db/index.ts (/app/.next/server/app/page.js:1001:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/server/auth/index.ts:6:68) - at (rsc)/./src/server/auth/index.ts (/app/.next/server/app/page.js:957:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/components/header/index.tsx:12:70) - at (rsc)/./src/components/header/index.tsx (/app/.next/server/app/page.js:697:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/app/layout.tsx:10:76) - at (rsc)/./src/app/layout.tsx (/app/.next/server/app/page.js:594:1) - at Function.__webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) -digest: "4214325463" - ⨯ Error: Could not locate the bindings file. Tried: - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/Debug/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/Release/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/out/Debug/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/Debug/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/out/Release/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/Release/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/default/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/compiled/20.19.0/linux/arm64/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/addon-build/release/install-root/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/addon-build/debug/install-root/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/addon-build/default/install-root/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/lib/binding/node-v115-linux-arm64/better_sqlite3.node - at bindings (/app/node_modules/.pnpm/bindings@1.5.0/node_modules/bindings/bindings.js:126:9) - at new Database (/app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/lib/database.js:48:64) - at eval (webpack-internal:///(rsc)/./src/server/db/index.ts:14:16) - at (rsc)/./src/server/db/index.ts (/app/.next/server/app/page.js:1001:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (webpack-internal:///(rsc)/./src/server/auth/index.ts:6:68) - at (rsc)/./src/server/auth/index.ts (/app/.next/server/app/page.js:957:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (webpack-internal:///(rsc)/./src/components/header/index.tsx:12:70) - at (rsc)/./src/components/header/index.tsx (/app/.next/server/app/page.js:697:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (webpack-internal:///(rsc)/./src/app/layout.tsx:10:76) - at (rsc)/./src/app/layout.tsx (/app/.next/server/app/page.js:594:1) - at Function.__webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at async e9 (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:396515) - at async tb (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:400212) - at async tS (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:400773) - at async tR (/app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:36:2130) - at async /app/node_modules/.pnpm/next@14.2.3_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:36:2722 { - digest: '4214325463', - page: '/' -} - GET / 500 in 31ms - ⨯ Error: Could not locate the bindings file. Tried: - at eval (./src/server/db/index.ts:14:16) - at (rsc)/./src/server/db/index.ts (/app/.next/server/app/page.js:1001:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/server/auth/index.ts:6:68) - at (rsc)/./src/server/auth/index.ts (/app/.next/server/app/page.js:957:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/components/header/index.tsx:12:70) - at (rsc)/./src/components/header/index.tsx (/app/.next/server/app/page.js:697:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/app/layout.tsx:10:76) - at (rsc)/./src/app/layout.tsx (/app/.next/server/app/page.js:594:1) - at Function.__webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) -digest: "3020338880" - ⨯ Error: Could not locate the bindings file. Tried: - at eval (./src/server/db/index.ts:14:16) - at (rsc)/./src/server/db/index.ts (/app/.next/server/app/page.js:1001:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/server/auth/index.ts:6:68) - at (rsc)/./src/server/auth/index.ts (/app/.next/server/app/page.js:957:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/components/header/index.tsx:12:70) - at (rsc)/./src/components/header/index.tsx (/app/.next/server/app/page.js:697:1) - at __webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) - at eval (./src/app/layout.tsx:10:76) - at (rsc)/./src/app/layout.tsx (/app/.next/server/app/page.js:594:1) - at Function.__webpack_require__ (/app/.next/server/webpack-runtime.js:33:42) -digest: "3020338880" -Error: Could not locate the bindings file. Tried: - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/Debug/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/Release/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/out/Debug/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/Debug/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/out/Release/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/Release/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build/default/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/compiled/20.19.0/linux/arm64/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/addon-build/release/install-root/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/addon-build/debug/install-root/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/addon-build/default/install-root/better_sqlite3.node - → /app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/lib/binding/node-v115-linux-arm64/better_sqlite3.node - at bindings (/app/node_modules/.pnpm/bindings@1.5.0/node_modules/bindings/bindings.js:126:9) diff --git a/packages/reservation-platform/.dockerignore b/packages/reservation-platform/.dockerignore new file mode 100644 index 0000000..036c1c8 --- /dev/null +++ b/packages/reservation-platform/.dockerignore @@ -0,0 +1,27 @@ +# Build and utility assets +docker/ +scripts/ + +# Ignore node_modules as they will be installed in the container +node_modules + +# Ignore build artifacts +.next + +# Ignore runtime data +db/ + +# Ignore local configuration files +.env +.env.example + +# Ignore version control files +.git +.gitignore + +# Ignore IDE/editor specific files +*.log +*.tmp +*.DS_Store +.vscode/ +.idea/ \ No newline at end of file diff --git a/packages/reservation-platform/.env b/packages/reservation-platform/.env deleted file mode 100644 index b323bfa..0000000 --- a/packages/reservation-platform/.env +++ /dev/null @@ -1,10 +0,0 @@ -# Basic Server Configuration -RUNTIME_ENVIRONMENT=prod -DB_PATH=db/sqlite.db - -# OAuth Configuration (Bitte anpassen) -OAUTH_CLIENT_ID=client_id -OAUTH_CLIENT_SECRET=client_secret - -# Backend-API URL (IP-Adresse oder Hostname des Backend-Servers) -NEXT_PUBLIC_API_URL=http://localhost:5000 diff --git a/packages/reservation-platform/.env.example b/packages/reservation-platform/.env.example index de2f268..4ab3091 100644 --- a/packages/reservation-platform/.env.example +++ b/packages/reservation-platform/.env.example @@ -1,7 +1,3 @@ -# Basic Server Configuration -RUNTIME_ENVIRONMENT=dev -DB_PATH=db/sqlite.db - # OAuth Configuration OAUTH_CLIENT_ID=client_id OAUTH_CLIENT_SECRET=client_secret \ No newline at end of file diff --git a/packages/reservation-platform/.gitignore b/packages/reservation-platform/.gitignore index 2b832b5..7002099 100644 --- a/packages/reservation-platform/.gitignore +++ b/packages/reservation-platform/.gitignore @@ -1,7 +1,10 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # db folder -/db +db/ + +# Env file +.env # dependencies diff --git a/packages/reservation-platform/Dockerfile b/packages/reservation-platform/Dockerfile index eac5fe3..11ed8ad 100644 --- a/packages/reservation-platform/Dockerfile +++ b/packages/reservation-platform/Dockerfile @@ -1,88 +1,34 @@ -FROM node:alpine +FROM node:20-bookworm-slim -WORKDIR /app +# Create application directory +RUN mkdir -p /usr/src/app -# Install system dependencies for SQLite and native modules -RUN apk add --no-cache python3 build-base g++ make sqlite sqlite-dev gcc musl-dev git libffi-dev openssl-dev cmake +# Set environment variables +ENV PORT=3000 +ENV NEXT_TELEMETRY_DISABLED=1 + +WORKDIR /usr/src/app + +# Copy package.json and pnpm-lock.yaml +COPY package.json /usr/src/app +COPY pnpm-lock.yaml /usr/src/app # Install pnpm -RUN npm install -g pnpm +RUN corepack enable pnpm -# Copy package files -COPY package.json pnpm-lock.yaml ./ +# Install dependencies +RUN pnpm install -# Install dependencies with native bindings build approval, ensuring to build from source for all platforms -ENV CFLAGS="-fPIC" \ - LDFLAGS="-fPIC" \ - CXXFLAGS="-fPIC" \ - npm_config_build_from_source=true \ - npm_config_sqlite=/usr/local \ - npm_config_sqlite_libname=sqlite3 +# Copy the rest of the application code +COPY . /usr/src/app -# Durchführen der Installation mit umfassenden Flags für native Bindungen -RUN pnpm install --unsafe-perm --no-optional --frozen-lockfile +# Initialize Database, if it not already exists +RUN pnpm run db -# Hinweis: better-sqlite3 neu bauen verursacht Fehler mit Node 23.10 -# npx ist bereits in Node.js integriert - kein zusätzlicher Install nötig +# Build the application +RUN pnpm run build -# Install tsx for running TypeScript files directly -RUN pnpm add -D tsx - -# Copy source code -COPY . . - -# Create database directory -RUN mkdir -p db/ - -# Build the Next.js application -RUN pnpm build || echo "Generate schema failed, but continuing..." - -# Expose the port EXPOSE 3000 -# Startup script with robust JSON fallback approach -RUN echo '#!/bin/sh' > /app/startup.sh && \ - echo 'set -e' >> /app/startup.sh && \ - echo 'mkdir -p /app/db' >> /app/startup.sh && \ - echo 'echo "Starting application..."' >> /app/startup.sh && \ - echo 'echo "Konfiguriere DB-Verzeichnis..."' >> /app/startup.sh && \ - echo 'DB_FILE="/app/db/sqlite.db"' >> /app/startup.sh && \ - echo 'DB_JSON="/app/db/db.json"' >> /app/startup.sh && \ - echo 'if [ ! -f "$DB_FILE" ]; then' >> /app/startup.sh && \ - echo ' echo "Creating empty database file..."' >> /app/startup.sh && \ - echo ' touch "$DB_FILE"' >> /app/startup.sh && \ - echo 'fi' >> /app/startup.sh && \ - echo 'if [ ! -f "$DB_JSON" ]; then' >> /app/startup.sh && \ - echo ' echo "Creating empty JSON DB file (fallback)..."' >> /app/startup.sh && \ - echo ' echo "{}" > "$DB_JSON"' >> /app/startup.sh && \ - echo 'fi' >> /app/startup.sh && \ - echo 'chmod 666 "$DB_FILE"' >> /app/startup.sh && \ - echo 'chmod 666 "$DB_JSON"' >> /app/startup.sh && \ - echo 'chmod -R 777 /app/db' >> /app/startup.sh && \ - echo 'export DB_PATH=$DB_FILE' >> /app/startup.sh && \ - echo 'export DB_JSON_PATH=$DB_JSON' >> /app/startup.sh && \ - echo 'echo "Datenbank wird unter $DB_PATH verwendet"' >> /app/startup.sh && \ - echo 'echo "JSON Fallback unter $DB_JSON_PATH"' >> /app/startup.sh && \ - echo '' >> /app/startup.sh && \ - echo '# Try to rebuild better-sqlite3 for current platform, but continue if it fails' >> /app/startup.sh && \ - echo 'if [ ! -d "/app/node_modules/.pnpm/better-sqlite3@9.6.0/node_modules/better-sqlite3/build" ]; then' >> /app/startup.sh && \ - echo ' echo "SQLite Bindings nicht gefunden, versuche sie zu bauen..."' >> /app/startup.sh && \ - echo ' cd /app && CFLAGS="-fPIC" LDFLAGS="-fPIC" CXXFLAGS="-fPIC" npm_config_build_from_source=true npm_config_sqlite=/usr/local npm_config_sqlite_libname=sqlite3 pnpm rebuild better-sqlite3 || echo "SQLite Rebuild fehlgeschlagen - wird JSON-Fallback verwenden"' >> /app/startup.sh && \ - echo 'fi' >> /app/startup.sh && \ - echo '' >> /app/startup.sh && \ - echo 'echo "Führe Datenbank-Migration aus..."' >> /app/startup.sh && \ - echo 'NODE_ENV=production npx tsx ./src/server/db/migrate.ts || echo "SQLite Migration fehlgeschlagen - wird beim Neustart erneut versucht"' >> /app/startup.sh && \ - echo 'echo "Migration abgeschlossen"' >> /app/startup.sh && \ - echo '' >> /app/startup.sh && \ - echo 'echo "Starte Next.js Anwendung..."' >> /app/startup.sh && \ - echo 'if [ -d ".next" ]; then' >> /app/startup.sh && \ - echo ' NODE_OPTIONS="--no-warnings" pnpm start' >> /app/startup.sh && \ - echo 'else' >> /app/startup.sh && \ - echo ' echo "Build directory nicht gefunden, führe Build aus..."' >> /app/startup.sh && \ - echo ' NODE_OPTIONS="--no-warnings" pnpm build || echo "Build fehlgeschlagen - wird beim Neustart erneut versucht"' >> /app/startup.sh && \ - echo ' NODE_OPTIONS="--no-warnings" pnpm start || NODE_OPTIONS="--no-warnings" pnpm dev' >> /app/startup.sh && \ - echo 'fi' >> /app/startup.sh && \ - chmod +x /app/startup.sh - # Start the application -CMD ["/app/startup.sh"] \ No newline at end of file +CMD ["/bin/sh", "-c", "if [ ! -f ./db/sqlite.db ]; then pnpm db; fi && pnpm start"] diff --git a/packages/reservation-platform/README.md b/packages/reservation-platform/README.md index ca46ff0..8faebf0 100644 --- a/packages/reservation-platform/README.md +++ b/packages/reservation-platform/README.md @@ -1,217 +1,32 @@ -utilss/analytics/(scope).ts -deriver.ts -utils/sentinel.ts -> auth guard +# MYP - Manage Your Printer +MYP (Manage Your Printer) ist eine Webanwendung zur Reservierung von 3D-Druckern. +Sie wurde im Rahmen des Abschlussprojektes der Fachinformatiker Ausbildung für Daten- und Prozessanalyse für die Technische Berufsausbildung des Mercedes-Benz Werkes Berlin-Marienfelde entwickelt. ---- +## Deployment -Basierend auf den erwähnten Anforderungen, hier sind einige zusätzliche Spalten, die Sie zu Ihrer Datenbank hinzufügen könnten: +### Voraussetzungen -Für die Tabelle printers: +- Netzwerk auf Raspberry Pi ist eingerichtet +- Docker ist installiert -total_print_jobs: Die Gesamtzahl der Druckaufträge, die ein Drucker ausgeführt hat. +### Schritte -total_active_time: Die Gesamtzeit, in der der Drucker aktiv war (in Minuten). +1. Docker-Container bauen (docker/build.sh) +2. Docker-Container speichern (docker/save.sh caddy:2.8 myp-rp:latest) +3. Docker-Container auf Raspberry Pi bereitstellen (docker/deploy.sh) -total_error_jobs: Die Gesamtzahl der Druckaufträge, die aufgrund eines Fehlers abgebrochen wurden. +## Entwicklerinformationen -last_maintenance_date: Das Datum der letzten Wartung des Druckers. +### Raspberry Pi Einstellungen -Für die Tabelle printJobs: +Auf dem Raspberry Pi wurde Raspbian Lite installiert. +Unter /srv/* sind die Projektdateien zu finden. -end_time: Die Zeit, zu der der Druckauftrag beendet wurde. +### Anmeldedaten -was_successful: Ein boolescher Wert, der angibt, ob der Druckauftrag erfolgreich abgeschlossen wurde. - -error_code: Ein Code, der einen bestimmten Fehler identifiziert, wenn der Druckauftrag abgebrochen wurde. - -Für die Tabelle users: - -total_print_jobs: Die Gesamtzahl der Druckaufträge, die ein Benutzer gestartet hat. - -preferred_printer_id: Die ID des Druckers, den der Benutzer am häufigsten verwendet. - -last_login_date: Das Datum des letzten Logins des Benutzers. - -Diese zusätzlichen Spalten könnten Ihnen dabei helfen, die oben genannten statistischen Analysen und Machine Learning-Vorhersagen durchzuführen. Bitte beachten Sie, dass Sie möglicherweise zusätzliche Logik in Ihrer Anwendung implementieren müssen, um diese Spalten zu aktualisieren, wenn entsprechende Ereignisse eintreten (z.B. ein Druckauftrag wird gestartet oder beendet, ein Benutzer loggt sich ein usw.). - ---- - -Basierend auf Ihrem Datenbankschema, das Informationen über Drucker, Druckaufträge und Benutzer enthält, könnten Sie eine Vielzahl von statistischen Analysen und Machine Learning-Vorhersagen treffen. Hier sind einige konkrete Vorschläge: - -### Statistische Analysen: -1. **Auslastungsanalyse**: Bestimmen Sie die Auslastung der Drucker, indem Sie die Anzahl und Dauer der Druckaufträge analysieren. -2. **Fehleranalyse**: Untersuchen Sie die Häufigkeit und Ursachen von abgebrochenen Druckaufträgen, um Muster zu erkennen. -3. **Benutzerverhalten**: Analysieren Sie das Verhalten der Benutzer, z.B. welche Drucker am häufigsten verwendet werden oder zu welchen Zeiten die meisten Druckaufträge eingehen. - -### Machine Learning-Vorhersagen: -1. **Vorhersage der Druckerauslastung**: Verwenden Sie Zeitreihenanalysen, um zukünftige Auslastungsmuster der Drucker vorherzusagen. -2. **Anomalieerkennung**: Setzen Sie Machine Learning ein, um Anomalien im Druckverhalten zu erkennen, die auf potenzielle Probleme hinweisen könnten. -3. **Empfehlungssystem**: Entwickeln Sie ein Modell, das Benutzern basierend auf ihren bisherigen Druckaufträgen und Präferenzen Drucker empfiehlt. - -### Konkrete Umsetzungsempfehlungen: -- **Daten vorbereiten**: Reinigen und transformieren Sie Ihre Daten, um sie für die Analyse vorzubereiten. Entfernen Sie Duplikate, behandeln Sie fehlende Werte und konvertieren Sie kategoriale Daten in ein format, das von Machine Learning-Algorithmen verarbeitet werden kann. -- **Feature Engineering**: Erstellen Sie neue Merkmale (Features), die für Vorhersagemodelle nützlich sein könnten, wie z.B. die durchschnittliche Dauer der Druckaufträge pro Benutzer oder die Gesamtzahl der Druckaufträge pro Drucker. -- **Modellauswahl**: Wählen Sie geeignete Machine Learning-Modelle aus. Für Zeitreihenprognosen könnten ARIMA-Modelle geeignet sein, während für die Klassifizierung von Benutzerverhalten Entscheidungsbäume oder Random Forests verwendet werden könnten. -- **Modelltraining und -validierung**: Trainieren Sie Ihre Modelle mit einem Teil Ihrer Daten und validieren Sie sie mit einem anderen Teil, um sicherzustellen, dass die Modelle gut generalisieren und nicht überangepasst sind. -- **Ergebnisinterpretation**: Interpretieren Sie die Ergebnisse Ihrer Modelle und nutzen Sie sie, um geschäftliche Entscheidungen zu treffen oder die Benutzererfahrung auf Ihrer Plattform zu verbessern. - -Diese Vorschläge sind abhängig von der Qualität und Quantität Ihrer Daten sowie den spezifischen Zielen, die Sie mit Ihrer Plattform verfolgen. Es ist wichtig, dass Sie die Modelle regelmäßig aktualisieren, um die Genauigkeit der Vorhersagen zu erhalten und zu verbessern. - -Quelle: Unterhaltung mit Bing, 11.5.2024 -(1) Data Science – Nutzung von KI für Predictive Analytics - Springer. https://link.springer.com/content/pdf/10.1007/978-3-658-33731-5_27.pdf. -(2) Predictive Analytics: Grundlagen, Projektbeispiele und Lessons ... - Haufe. https://www.haufe.de/finance/haufe-finance-office-premium/predictive-analytics-grundlagen-projektbeispiele-und-lessons-learned_idesk_PI20354_HI13561373.html. -(3) Predictive Modelling: Was es ist und wie es dir dabei helfen kann, dein .... https://www.acquisa.de/magazin/predictive-modelling. -(4) Deep Learning und Predictive Analytics: Vorhersage von Kundenverhalten .... https://www.hagel-it.de/it-insights/deep-learning-und-predictive-analytics-vorhersage-von-kundenverhalten-und-markttrends.html. -(5) undefined. https://doi.org/10.1007/978-3-658-33731-5_27. - ---- - -https://github.com/drizzle-team/drizzle-orm/discussions/1480#discussioncomment-9363695 - ---- - -Um eine 3D-Drucker Reservierungsplattform zu entwickeln und die genannten Kriterien umzusetzen, empfehle ich folgende Schritte: - -### Kundenspezifische Anforderungen analysieren: -1. **Stakeholder-Interviews** durchführen, um Bedürfnisse und Erwartungen zu verstehen. -2. **Umfragen** erstellen, um Feedback von potenziellen Nutzern zu sammeln. -3. **Anforderungsworkshops** abhalten, um gemeinsam mit den Stakeholdern Anforderungen zu definieren. -4. **User Stories** und **Use Cases** entwickeln, um die Anforderungen zu konkretisieren. - -### Projektumsetzung planen: -1. **Projektziele** klar definieren und mit den betrieblichen Zielen abstimmen. -2. **Ressourcenplanung** vornehmen, um Personal, Zeit und Budget effizient einzusetzen. -3. **Risikoanalyse** durchführen, um potenzielle Hindernisse frühzeitig zu erkennen. -4. **Meilensteinplanung** erstellen, um wichtige Projektphasen zu strukturieren. - -### Daten identifizieren, klassifizieren und modellieren: -1. **Datenquellen** identifizieren, die für die Reservierungsplattform relevant sind. -2. **Datenklassifikation** vornehmen, um die Daten nach Typ und Sensibilität zu ordnen. -3. **Entity-Relationship-Modelle** (ERM) erstellen, um die Beziehungen zwischen den Daten zu visualisieren. - -### Mathematische Vorhersagemodelle und statistische Verfahren nutzen: -1. **Regressionsanalysen** durchführen, um zukünftige Nutzungsmuster vorherzusagen. -2. **Clusteranalysen** anwenden, um Nutzergruppen zu identifizieren und zu segmentieren. -3. **Zeitreihenanalysen** nutzen, um Trends und saisonale Schwankungen zu erkennen. - -### Datenqualität sicherstellen: -1. **Validierungsregeln** implementieren, um die Eingabe korrekter Daten zu gewährleisten. -2. **Datenbereinigung** regelmäßig durchführen, um Duplikate und Inkonsistenzen zu entfernen. -3. **Datenintegrität** durch Referenzintegritätsprüfungen sicherstellen. - -### Analyseergebnisse aufbereiten und Optimierungsmöglichkeiten aufzeigen: -1. **Dashboards** entwickeln, um die wichtigsten Kennzahlen übersichtlich darzustellen. -2. **Berichte** generieren, die detaillierte Einblicke in die Nutzungsdaten bieten. -3. **Handlungsempfehlungen** ableiten, um die Plattform kontinuierlich zu verbessern. - -### Projektdokumentation anforderungsgerecht erstellen: -1. **Dokumentationsstandards** festlegen, um Einheitlichkeit zu gewährleisten. -2. **Versionskontrolle** nutzen, um Änderungen nachvollziehbar zu machen. -3. **Projektfortschritt** dokumentieren, um den Überblick über den aktuellen Stand zu behalten. - -Diese Empfehlungen sollen als Leitfaden dienen, um die genannten Kriterien systematisch und strukturiert in Ihrem Abschlussprojekt umzusetzen. - -Quelle: Unterhaltung mit Bing, 11.5.2024 -(1) Erfolgreiche Datenanalyseprojekte: Diese Strategien sollten Sie kennen. https://www.b2bsmartdata.de/blog/erfolgreiche-datenanalyseprojekte-diese-strategien-sollten-sie-kennen. -(2) Projektdokumentation - wichtige Grundregeln | dieprojektmanager. https://dieprojektmanager.com/projektdokumentation-wichtige-grundregeln/. -(3) Projektdokumentation: Definition, Aufbau, Inhalte und Beispiel. https://www.wirtschaftswissen.de/unternehmensfuehrung/projektmanagement/projektdokumentation-je-genauer-sie-ist-desto-weniger-arbeit-haben-sie-mit-nachfolgeprojekten/. -(4) Was ist Datenmodellierung? | IBM. https://www.ibm.com/de-de/topics/data-modeling. -(5) Was ist Datenmodellierung? | Microsoft Power BI. https://powerbi.microsoft.com/de-de/what-is-data-modeling/. -(6) Inhalte Datenmodelle und Datenmodellierung Datenmodellierung ... - TUM. https://wwwbroy.in.tum.de/lehre/vorlesungen/mbe/SS07/vorlfolien/02_Datenmodellierung.pdf. -(7) Definition von Datenmodellierung: Einsatzbereiche und Typen.. https://business.adobe.com/de/blog/basics/define-data-modeling. -(8) 3. Informations- und Datenmodelle - RPTU. http://lgis.informatik.uni-kl.de/archiv/wwwdvs.informatik.uni-kl.de/courses/DBS/WS2000/Vorlesungsunterlagen/Kapitel.03.pdf. -(9) Prozessoptimierung: 7 Methoden im Überblick! [2024] • Asana. https://asana.com/de/resources/process-improvement-methodologies. -(10) Prozessoptimierung: Definition, Methoden & Praxis-Beispiele. https://peras.de/hr-blog/detail/hr-blog/prozessoptimierung. -(11) Optimierungspotenzial erkennen - OPTANO. https://optano.com/blog/optimierungspotenzial-erkennen/. -(12) Projektplanung: Definition, Ziele und Ablauf - wirtschaftswissen.de. https://www.wirtschaftswissen.de/unternehmensfuehrung/projektmanagement/in-nur-5-schritten-zur-fehlerfreien-projektplanung/. -(13) Projektphasen: Die Vier! Von der Planung zur Umsetzung. https://www.pureconsultant.de/de/wissen/projektphasen/. -(14) Hinweise zur Abschlussprüfung in den IT-Berufen (VO 2020) - IHK_DE. https://www.ihk.de/blueprint/servlet/resource/blob/5361152/008d092b38f621b2c97c66d5193d9f6c/pruefungshinweise-neue-vo-2020-data.pdf. -(15) PAO – Projektantrag Fachinformatiker Daten- und Prozessanalyse - IHK_DE. https://www.ihk.de/blueprint/servlet/resource/blob/5673390/37eb05e451ed6051f6316f66d012cc50/projektantrag-fachinformatiker-daten-und-prozessanalyse-data.pdf. -(16) IT-BERUFE Leitfaden zur IHK-Abschlussprüfung Fachinformatikerinnen und .... https://www.ihk.de/blueprint/servlet/resource/blob/5439816/6570224fb196bc7e10d16beeeb75fec1/neu-leitfaden-fian-data.pdf. -(17) Fachinformatiker/-in Daten- und Prozessanalyse - IHK Nord Westfalen. https://www.ihk.de/nordwestfalen/bildung/ausbildung/ausbildungsberufe-a-z/fachinformatiker-daten-und-prozessanalyse-4767680. -(18) Leitfaden zur IHK-Abschlussprüfung Fachinformatiker/-in .... https://www.ihk.de/blueprint/servlet/resource/blob/5682602/2fbedf4b4f33f7522d28ebc611adc909/fachinformatikerin-daten-und-prozessanalyse-data.pdf. -(19) § 28 FIAusbV - Einzelnorm - Gesetze im Internet. https://www.gesetze-im-internet.de/fiausbv/__28.html. -(20) Hinweise des Prüfungsausschusses zur Projektarbeit. https://www.neubrandenburg.ihk.de/fileadmin/user_upload/Aus_und_Weiterbildung/Ausbildung/Projektarbeit_Fachinformatiker_FR._Daten-_und_Prozessanalyse.pdf. -(21) Datenqualität: Definition und Methoden zur kontinuierlichen .... https://www.acquisa.de/magazin/datenqualitaet. -(22) Datenqualität: Definition, Merkmale und Analyse (Guide) - Kobold AI. https://www.kobold.ai/datenqualitaet-guide/. -(23) Datenqualität: Definition und Methoden zur kontinuierlichen .... https://bing.com/search?q=Sicherstellung+der+Datenqualit%c3%a4t. -(24) Datenqualitätsmanagement: Sicherstellung hoher Datenstandards. https://www.data-analyst.de/glossar/data-quality-management/. -(25) Kundenspezifische Anforderungen CSR - Beratung für Managementsysteme. https://smct-management.de/kundenspezifische-anforderungen-csr-im-sinne-der-iatf-16949/. -(26) CSR Sys - Kundenspezifische Anforderungen verwalten und bewerten. https://smct-management.de/csr-sys-kundenspezifische-anforderungen/. -(27) Beauftragter für Customer Specific Requirements (CSR). https://www.tuev-nord.de/de/weiterbildung/seminare/beauftragter-fuer-customer-specific-requirements-csr-a/. -(28) Kundenspezifische Anforderungen Seminar | Jetzt anfragen! - qdc. https://qdc.de/kundenspezifische-anforderungen-seminar/. - ---- - -Um die Punkte zur Datenidentifikation, -klassifikation, -modellierung und zur Nutzung mathematischer Modelle und statistischer Verfahren weiter zu konkretisieren, finden Sie hier detaillierte Empfehlungen: - -### Datenquellen identifizieren: -1. **Bestandsaufnahme** der aktuellen Daten: Erfassen Sie alle Daten, die bereits im Unternehmen vorhanden sind, wie z.B. Kundeninformationen, Transaktionsdaten und Gerätenutzungsdaten. -2. **Externe Datenquellen** prüfen: Untersuchen Sie, ob und welche externen Datenquellen wie Materiallieferanten oder Wartungsdienstleister relevant sein könnten. -3. **IoT-Sensordaten**: Berücksichtigen Sie die Integration von IoT-Geräten, die in Echtzeit Daten über den Zustand und die Nutzung der 3D-Drucker liefern. - -### Datenklassifikation: -1. **Sensibilitätsstufen** festlegen: Bestimmen Sie, welche Daten sensibel sind (z.B. personenbezogene Daten) und einer besonderen Schutzstufe bedürfen. -2. **Datenkategorien** erstellen: Ordnen Sie die Daten in Kategorien wie Nutzungsdaten, Finanzdaten, Betriebsdaten etc. -3. **Zugriffsrechte** definieren: Legen Sie fest, wer Zugriff auf welche Daten haben darf, um die Datensicherheit zu gewährleisten. - -### Entity-Relationship-Modelle (ERM): -1. **Datenentitäten** identifizieren: Bestimmen Sie die Kernentitäten wie Benutzer, Drucker, Reservierungen und Materialien. -2. **Beziehungen** festlegen: Definieren Sie, wie diese Entitäten miteinander in Beziehung stehen (z.B. ein Benutzer kann mehrere Reservierungen haben). -3. **ERM-Tools** nutzen: Verwenden Sie Software wie Lucidchart oder Microsoft Visio, um die ERMs zu visualisieren. - -### Regressionsanalysen: -1. **Historische Daten** sammeln: Nutzen Sie vergangene Nutzungsdaten, um Muster zu erkennen. -2. **Prädiktive Variablen** wählen: Identifizieren Sie Faktoren, die die Nutzung beeinflussen könnten, wie z.B. Uhrzeit, Wochentag oder Materialtyp. -3. **Regressionsmodelle** anwenden: Nutzen Sie lineare oder logistische Regression, um zukünftige Nutzungsmuster vorherzusagen. - -### Clusteranalysen: -1. **Nutzersegmentierung**: Teilen Sie Nutzer basierend auf ihrem Verhalten in Gruppen ein, z.B. nach Häufigkeit der Nutzung oder bevorzugten Materialien. -2. **K-Means-Clustering**: Verwenden Sie Algorithmen wie K-Means, um die Nutzer in sinnvolle Cluster zu segmentieren. -3. **Cluster-Validierung**: Überprüfen Sie die Güte der Clusterbildung, um sicherzustellen, dass die Segmente aussagekräftig sind. - -### Zeitreihenanalysen: -1. **Zeitstempel-Daten** analysieren: Untersuchen Sie Daten mit Zeitstempeln, um Trends und Muster über die Zeit zu erkennen. -2. **Saisonale Effekte** berücksichtigen: Identifizieren Sie saisonale Schwankungen in der Nutzung der 3D-Drucker. -3. **ARIMA-Modelle**: Nutzen Sie autoregressive integrierte gleitende Durchschnitte (ARIMA), um zukünftige Trends zu prognostizieren. - -Diese Methoden helfen Ihnen, ein tiefes Verständnis der Daten zu entwickeln, das für die erfolgreiche Umsetzung Ihrer Reservierungsplattform unerlässlich ist. Denken Sie daran, dass die genaue Anwendung dieser Techniken von den spezifischen Daten und Anforderungen Ihres Projekts abhängt. Es ist wichtig, dass Sie sich mit den Grundlagen der Datenanalyse und statistischen Modellierung vertraut machen, um diese Methoden effektiv anwenden zu können. - ----- -This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). - -## Getting Started - -First, run the development server: - -```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev +``` +Benutzer: myp +Passwort: (persönlich bekannt) ``` -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. - -This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. - -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. diff --git a/packages/reservation-platform/docker-compose.yml b/packages/reservation-platform/docker-compose.yml deleted file mode 100644 index f5bd3a3..0000000 --- a/packages/reservation-platform/docker-compose.yml +++ /dev/null @@ -1,23 +0,0 @@ -version: '3' - -services: - frontend: - build: - context: . - dockerfile: Dockerfile - container_name: myp-frontend - network_mode: host - environment: - - RUNTIME_ENVIRONMENT=${RUNTIME_ENVIRONMENT:-prod} - - OAUTH_CLIENT_ID=${OAUTH_CLIENT_ID:-client_id} - - OAUTH_CLIENT_SECRET=${OAUTH_CLIENT_SECRET:-client_secret} - - NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL:-http://localhost:5000} - volumes: - - ./db:/app/db - restart: unless-stopped - healthcheck: - test: ["CMD", "wget", "--spider", "http://localhost:3000"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 40s \ No newline at end of file diff --git a/packages/reservation-platform/docker/build.sh b/packages/reservation-platform/docker/build.sh new file mode 100644 index 0000000..8b04b01 --- /dev/null +++ b/packages/reservation-platform/docker/build.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +# Define image name +MYP_RP_IMAGE_NAME="myp-rp" + +# Function to build Docker image +build_image() { + local image_name=$1 + local dockerfile=$2 + local platform=$3 + + echo "Building $image_name Docker image for $platform..." + + docker buildx build --platform $platform -t ${image_name}:latest -f $dockerfile --load . + if [ $? -eq 0 ]; then + echo "$image_name Docker image built successfully" + else + echo "Error occurred while building $image_name Docker image" + exit 1 + fi +} + +# Create and use a builder instance (if not already created) +BUILDER_NAME="myp-rp-arm64-builder" +docker buildx create --name $BUILDER_NAME --use || docker buildx use $BUILDER_NAME + +# Build myp-rp image +build_image "$MYP_RP_IMAGE_NAME" "$PWD/Dockerfile" "linux/arm64" + +# Remove the builder instance +docker buildx rm $BUILDER_NAME diff --git a/packages/reservation-platform/docker/caddy/Caddyfile b/packages/reservation-platform/docker/caddy/Caddyfile new file mode 100644 index 0000000..beacec7 --- /dev/null +++ b/packages/reservation-platform/docker/caddy/Caddyfile @@ -0,0 +1,8 @@ +{ + debug +} + +m040tbaraspi001.de040.corpintra.net, m040tbaraspi001.de040.corpinter.net { + reverse_proxy myp-rp:3000 + tls internal +} \ No newline at end of file diff --git a/packages/reservation-platform/docker/compose.yml b/packages/reservation-platform/docker/compose.yml new file mode 100644 index 0000000..538564f --- /dev/null +++ b/packages/reservation-platform/docker/compose.yml @@ -0,0 +1,19 @@ +services: + caddy: + image: caddy:2.8 + container_name: caddy + restart: unless-stopped + ports: + - 80:80 + - 443:443 + volumes: + - ./caddy/data:/data + - ./caddy/config:/config + - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro + myp-rp: + image: myp-rp:latest + container_name: myp-rp + env_file: "/srv/myp-env/github.env" + volumes: + - /srv/MYP-DB:/usr/src/app/db + restart: unless-stopped diff --git a/packages/reservation-platform/docker/deploy.sh b/packages/reservation-platform/docker/deploy.sh new file mode 100644 index 0000000..09b73d6 --- /dev/null +++ b/packages/reservation-platform/docker/deploy.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +# Directory containing the Docker images +IMAGE_DIR="docker/images" + +# Load all Docker images from the tar.xz files in the IMAGE_DIR +echo "Loading Docker images from $IMAGE_DIR..." + +for image_file in "$IMAGE_DIR"/*.tar.xz; do + if [ -f "$image_file" ]; then + echo "Loading Docker image from $image_file..." + docker load -i "$image_file" + + # Check if the image loading was successful + if [ $? -ne 0 ]; then + echo "Error occurred while loading Docker image from $image_file" + exit 1 + fi + else + echo "No Docker image tar.xz files found in $IMAGE_DIR." + fi +done + +# Execute docker compose +echo "Running docker compose..." +docker compose -f "docker/compose.yml" up -d + +# Check if the operation was successful +if [ $? -eq 0 ]; then + echo "Docker compose executed successfully" +else + echo "Error occurred while executing docker compose" + exit 1 +fi + +echo "Deployment completed successfully" diff --git a/packages/reservation-platform/docker/images/.gitattributes b/packages/reservation-platform/docker/images/.gitattributes new file mode 100644 index 0000000..8b3e666 --- /dev/null +++ b/packages/reservation-platform/docker/images/.gitattributes @@ -0,0 +1,2 @@ +caddy_2.8.tar.xz filter=lfs diff=lfs merge=lfs -text +myp-rp_latest.tar.xz filter=lfs diff=lfs merge=lfs -text diff --git a/packages/reservation-platform/docker/images/caddy_2.8.tar.xz b/packages/reservation-platform/docker/images/caddy_2.8.tar.xz new file mode 100644 index 0000000..1b4070e --- /dev/null +++ b/packages/reservation-platform/docker/images/caddy_2.8.tar.xz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e04636560b45cf05774bb4fb734329951c1693a3febd56358ba4d58b10fee82 +size 12365924 diff --git a/packages/reservation-platform/docker/images/myp-rp_latest.tar.xz b/packages/reservation-platform/docker/images/myp-rp_latest.tar.xz new file mode 100644 index 0000000..f617dcd --- /dev/null +++ b/packages/reservation-platform/docker/images/myp-rp_latest.tar.xz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1e6b377b9a7a36b32f229567ad766eb06b1e959c7572b7e92dc81c6a3d03e13b +size 191478008 diff --git a/packages/reservation-platform/docker/save.sh b/packages/reservation-platform/docker/save.sh new file mode 100644 index 0000000..2ce2712 --- /dev/null +++ b/packages/reservation-platform/docker/save.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +# Get image name as argument +IMAGE_NAME=$1 +PLATFORM="linux/arm64" + +# Define paths +IMAGE_DIR="docker/images" +IMAGE_FILE="${IMAGE_DIR}/${IMAGE_NAME//[:\/]/_}.tar" +COMPRESSED_FILE="${IMAGE_FILE}.xz" + +# Function to pull the image +pull_image() { + local image=$1 + if [[ $image == arm64v8/* ]]; then + echo "Pulling image $image without platform specification..." + docker pull $image + else + echo "Pulling image $image for platform $PLATFORM..." + docker pull --platform $PLATFORM $image + fi + return $? +} + +# Pull the image if it is not available locally +if ! docker image inspect ${IMAGE_NAME} &>/dev/null; then + if pull_image ${IMAGE_NAME}; then + echo "Image $IMAGE_NAME pulled successfully." + else + echo "Error occurred while pulling $IMAGE_NAME for platform $PLATFORM" + echo "Trying to pull $IMAGE_NAME without platform specification..." + + # Attempt to pull again without platform + if pull_image ${IMAGE_NAME}; then + echo "Image $IMAGE_NAME pulled successfully without platform." + else + echo "Error occurred while pulling $IMAGE_NAME without platform." + echo "Trying to pull arm64v8/${IMAGE_NAME} instead..." + + # Construct new image name + NEW_IMAGE_NAME="arm64v8/${IMAGE_NAME}" + if pull_image ${NEW_IMAGE_NAME}; then + echo "Image $NEW_IMAGE_NAME pulled successfully." + IMAGE_NAME=${NEW_IMAGE_NAME} # Update IMAGE_NAME to use the new one + else + echo "Error occurred while pulling $NEW_IMAGE_NAME" + exit 1 + fi + fi + fi +else + echo "Image $IMAGE_NAME found locally. Skipping pull." +fi + +# Save the Docker image +echo "Saving $IMAGE_NAME Docker image..." +docker save ${IMAGE_NAME} > $IMAGE_FILE + +# Compress the Docker image (overwriting if file exists) +echo "Compressing $IMAGE_FILE..." +xz -z --force $IMAGE_FILE + +if [ $? -eq 0 ]; then + echo "$IMAGE_NAME Docker image saved and compressed successfully as $COMPRESSED_FILE" +else + echo "Error occurred while compressing $IMAGE_NAME Docker image" + exit 1 +fi diff --git a/packages/reservation-platform/docs/Admin-Dashboard.md b/packages/reservation-platform/docs/Admin-Dashboard.md new file mode 100644 index 0000000..e192aa6 --- /dev/null +++ b/packages/reservation-platform/docs/Admin-Dashboard.md @@ -0,0 +1,116 @@ +# **Detaillierte Dokumentation des Admin-Dashboards** + +In diesem Abschnitt werde ich die Funktionen und Nutzung des Admin-Dashboards genauer beschreiben, einschließlich der verschiedenen Module, Diagramme und deren Zweck. + +--- + +## **1. Überblick über das Admin-Dashboard** + +Das Admin-Dashboard ist der zentrale Verwaltungsbereich für Administratoren. Es bietet Funktionen wie die Verwaltung von Druckern, Benutzern und Druckaufträgen sowie detaillierte Statistiken und Analysen. + +### **1.1. Navigation** +Das Dashboard enthält ein Sidebar-Menü mit den folgenden Hauptbereichen: +1. **Dashboard:** Übersicht der wichtigsten Statistiken. +2. **Benutzer:** Verwaltung von Benutzerkonten. +3. **Drucker:** Hinzufügen, Bearbeiten und Verwalten von Druckern. +4. **Druckaufträge:** Einsicht in alle Druckaufträge und deren Status. +5. **Einstellungen:** Konfiguration der Anwendung. +6. **Über MYP:** Informationen über das Projekt und den Entwickler. + +Die Sidebar wird in der Datei `src/app/admin/admin-sidebar.tsx` definiert und dynamisch basierend auf der aktuellen Seite hervorgehoben. + +--- + +## **2. Funktionen des Admin-Dashboards** + +### **2.1. Benutzerverwaltung** +- **Datei:** `src/app/admin/users/page.tsx` +- **Beschreibung:** Ermöglicht das Anzeigen, Bearbeiten und Löschen von Benutzerkonten. +- **Funktionen:** + - Anzeige einer Liste aller registrierten Benutzer. + - Bearbeiten von Benutzerrollen (z. B. „admin“ oder „user“). + - Deaktivieren oder Löschen von Benutzerkonten. + +--- + +### **2.2. Druckerverwaltung** +- **Datei:** `src/app/admin/printers/page.tsx` +- **Beschreibung:** Verwaltung der Drucker, einschließlich Hinzufügen, Bearbeiten und Deaktivieren. +- **Funktionen:** + - Statusanzeige der Drucker (aktiv/inaktiv). + - Hinzufügen neuer Drucker mit Namen und Beschreibung. + - Löschen oder Bearbeiten bestehender Drucker. + +--- + +### **2.3. Druckaufträge** +- **Datei:** `src/app/admin/jobs/page.tsx` +- **Beschreibung:** Übersicht aller Druckaufträge, einschließlich Details wie Startzeit, Dauer und Status. +- **Funktionen:** + - Filtern nach Benutzern, Druckern oder Status (abgeschlossen, abgebrochen). + - Anzeigen von Abbruchgründen und Fehlermeldungen. + - Sortieren nach Zeit oder Benutzer. + +--- + +### **2.4. Einstellungen** +- **Datei:** `src/app/admin/settings/page.tsx` +- **Beschreibung:** Konfigurationsseite für die Anwendung. +- **Funktionen:** + - Ändern von globalen Einstellungen wie Standardzeiten oder Fehlerrichtlinien. + - Download von Daten (z. B. Export der Druckhistorie). + +--- + +## **3. Statistiken und Diagramme** + +Das Admin-Dashboard enthält interaktive Diagramme, die wichtige Statistiken visualisieren. Hier einige der zentralen Diagramme: + +### **3.1. Abbruchgründe** +- **Datei:** `src/app/admin/charts/printer-error-chart.tsx` +- **Beschreibung:** Zeigt die Häufigkeit der Abbruchgründe für Druckaufträge in einem Balkendiagramm. +- **Nutzen:** Identifiziert häufige Probleme wie Materialmangel oder Düsenverstopfungen. + +--- + +### **3.2. Fehlerraten** +- **Datei:** `src/app/admin/charts/printer-error-rate.tsx` +- **Beschreibung:** Zeigt die prozentuale Fehlerrate für jeden Drucker in einem Balkendiagramm. +- **Nutzen:** Ermöglicht die Überwachung und Identifizierung von problematischen Druckern. + +--- + +### **3.3. Druckvolumen** +- **Datei:** `src/app/admin/charts/printer-volume.tsx` +- **Beschreibung:** Zeigt das Druckvolumen für heute, diese Woche und diesen Monat. +- **Nutzen:** Vergleich des Druckeroutputs über verschiedene Zeiträume. + +--- + +### **3.4. Prognostizierte Nutzung** +- **Datei:** `src/app/admin/charts/printer-forecast.tsx` +- **Beschreibung:** Ein Bereichsdiagramm zeigt die erwartete Druckernutzung pro Wochentag. +- **Nutzen:** Hilft bei der Planung von Wartungsarbeiten oder Ressourcenzuweisungen. + +--- + +### **3.5. Druckerauslastung** +- **Datei:** `src/app/admin/charts/printer-utilization.tsx` +- **Beschreibung:** Zeigt die aktuelle Nutzung eines Druckers in Prozent in einem Kreisdiagramm. +- **Nutzen:** Überwacht die Auslastung und identifiziert ungenutzte Ressourcen. + +--- + +## **4. Rollenbasierte Zugriffssteuerung** + +Das Admin-Dashboard ist nur für Benutzer mit der Rolle „admin“ zugänglich. Nicht berechtigte Benutzer werden auf die Startseite umgeleitet. Die Zugriffssteuerung erfolgt durch folgende Logik: +- **Datei:** `src/app/admin/layout.tsx` +- **Funktion:** `validateRequest` prüft die Rolle des aktuellen Benutzers. +- **Umleitung:** Falls die Rolle unzureichend ist, wird der Benutzer automatisch umgeleitet: + ```typescript + if (guard(user, IS_NOT, UserRole.ADMIN)) { + redirect("/"); + } + ``` + +Nächster Schritt: [=> API-Endpunkte und deren Nutzung](./API.md) \ No newline at end of file diff --git a/packages/reservation-platform/docs/Architektur.md b/packages/reservation-platform/docs/Architektur.md new file mode 100644 index 0000000..f64fc96 --- /dev/null +++ b/packages/reservation-platform/docs/Architektur.md @@ -0,0 +1,79 @@ +# **Technische Architektur und Codeaufbau** + +In diesem Abschnitt erläutere ich die Architektur und Struktur des MYP-Projekts sowie die Funktionalitäten der zentralen Komponenten. + +--- + +## **1. Technische Architektur** + +### **1.1. Architekturübersicht** +MYP basiert auf einer modernen Webanwendungsarchitektur: +- **Frontend:** Entwickelt mit React und Next.js. Stellt die Benutzeroberfläche bereit. +- **Backend:** Nutzt Node.js und Drizzle ORM für die Datenbankinteraktion und Geschäftslogik. +- **Datenbank:** SQLite zur Speicherung von Nutzerdaten, Druckaufträgen und Druckerkonfigurationen. +- **Containerisierung:** Docker wird verwendet, um die Anwendung in isolierten Containern bereitzustellen. +- **Webserver:** Caddy dient als Reverse Proxy mit HTTPS-Unterstützung. + +### **1.2. Modulübersicht** +- **Datenfluss:** Die Anwendung ist stark datengetrieben. API-Routen werden genutzt, um Daten zwischen Frontend und Backend auszutauschen. +- **Rollenbasierter Zugriff:** Über ein Berechtigungssystem können Administratoren und Benutzer unterschiedliche Funktionen nutzen. + +--- + +## **2. Codeaufbau** + +### **2.1. Ordnerstruktur** +Die Datei `repomix-output.txt` zeigt eine strukturierte Übersicht des Projekts. Nachfolgend einige wichtige Verzeichnisse: + +| **Verzeichnis** | **Inhalt** | +|--------------------------|---------------------------------------------------------------------------| +| `src/app` | Next.js-Seiten und Komponenten für Benutzer und Admins. | +| `src/components` | Wiederverwendbare UI-Komponenten wie Karten, Diagramme, Buttons etc. | +| `src/server` | Backend-Logik, Authentifizierung und Datenbankinteraktionen. | +| `src/utils` | Hilfsfunktionen für Analysen, Validierungen und Datenbankzugriffe. | +| `drizzle` | Datenbank-Migrationsdateien und Metadaten. | +| `docker` | Docker-Konfigurations- und Bereitstellungsskripte. | + +--- + +### **2.2. Hauptdateien** +#### **Frontend** +- **`src/app/page.tsx`:** Startseite der Anwendung. +- **`src/app/admin/`:** Admin-spezifische Seiten, z. B. Druckerverwaltung oder Fehlerstatistiken. +- **`src/components/ui/`:** UI-Komponenten wie Dialoge, Formulare und Tabellen. + +#### **Backend** +- **`src/server/auth/`:** Authentifizierung und Benutzerrollenmanagement. +- **`src/server/actions/`:** Funktionen zur Interaktion mit Druckaufträgen und Druckern. +- **`src/utils/`:** Analyse und Verarbeitung von Druckdaten (z. B. Fehlerquoten und Auslastung). + +#### **Datenbank** +- **`drizzle/0000_overjoyed_strong_guy.sql`:** SQLite-Datenbankschema mit Tabellen für Drucker, Benutzer und Druckaufträge. +- **`drizzle.meta/`:** Metadaten zur Datenbankmigration. + +--- + +### **2.3. Datenbankschema** +Das Schema enthält vier Haupttabellen: +1. **`user`:** Speichert Benutzerinformationen, einschließlich Rollen und E-Mail-Adressen. +2. **`printer`:** Beschreibt die Drucker, ihren Status und ihre Eigenschaften. +3. **`printJob`:** Zeichnet Druckaufträge auf, einschließlich Startzeit, Dauer und Abbruchgrund. +4. **`session`:** Verwaltert Benutzer-Sitzungen und Ablaufzeiten. + +--- + +## **3. Wichtige Funktionen** + +### **3.1. Authentifizierung** +Das System nutzt OAuth zur Anmeldung. Benutzerrollen werden in der Tabelle `user` gespeichert und im Backend überprüft. + +### **3.2. Statistiken** +- **Fehlerrate:** Berechnet die Häufigkeit von Abbrüchen für jeden Drucker. +- **Auslastung:** Prozentuale Nutzung der Drucker, basierend auf geplanten und abgeschlossenen Druckaufträgen. +- **Prognosen:** Verwenden historische Daten, um zukünftige Drucknutzungen vorherzusagen. + +### **3.3. API-Endpunkte** +- **`src/app/api/printers/`:** Zugriff auf Druckerkonfigurationsdaten. +- **`src/app/api/job/[jobId]/`:** Verwaltung einzelner Druckaufträge. + +Nächster Schritt: [=> Datenbank und Analytik-Funktionen](./Datenbank.md) \ No newline at end of file diff --git a/packages/reservation-platform/docs/Bereitstellungsdetails .md b/packages/reservation-platform/docs/Bereitstellungsdetails .md new file mode 100644 index 0000000..78a5b5a --- /dev/null +++ b/packages/reservation-platform/docs/Bereitstellungsdetails .md @@ -0,0 +1,150 @@ +# **Bereitstellungsdetails und Best Practices** + +In diesem Abschnitt erläutere ich, wie das MYP-Projekt auf einem Server bereitgestellt wird, sowie empfohlene Praktiken zur Verwaltung und Optimierung des Systems. + +--- + +## **1. Bereitstellungsschritte** + +### **1.1. Voraussetzungen** +- **Server:** Raspberry Pi mit installiertem Raspbian Lite. +- **Docker:** Docker und Docker Compose müssen vorab installiert sein. +- **Netzwerk:** Der Server muss über eine statische IP-Adresse oder einen DNS-Namen erreichbar sein. + +### **1.2. Vorbereitung** +#### **1.2.1. Docker-Images erstellen und speichern** +Führen Sie die folgenden Schritte auf dem Entwicklungssystem aus: +1. **Images erstellen:** + ```bash + bash docker/build.sh + ``` +2. **Images exportieren und komprimieren:** + ```bash + bash docker/save.sh + ``` + Dies speichert die Docker-Images im Verzeichnis `docker/images/`. + +#### **1.2.2. Übertragung auf den Server** +Kopieren Sie die erzeugten `.tar.xz`-Dateien auf den Raspberry Pi: +```bash +scp docker/images/*.tar.xz @:/path/to/destination/ +``` + +--- + +### **1.3. Images auf dem Server laden** +Loggen Sie sich auf dem Server ein und laden Sie die Docker-Images: +```bash +docker load -i /path/to/destination/.tar.xz +``` + +--- + +### **1.4. Starten der Anwendung** +Führen Sie das Bereitstellungsskript aus: +```bash +bash docker/deploy.sh +``` +Dieses Skript: +- Startet die Docker-Container mithilfe von `docker compose`. +- Verbindet den Reverse Proxy (Caddy) mit der Anwendung. + +Die Anwendung sollte unter `http://` oder der konfigurierten Domain erreichbar sein. + +--- + +## **2. Best Practices** + +### **2.1. Sicherheit** +1. **Umgebungsvariablen schützen:** + - Stellen Sie sicher, dass die Datei `.env` nicht versehentlich in ein öffentliches Repository hochgeladen wird. + - Verwenden Sie geeignete Zugriffsrechte: + ```bash + chmod 600 .env + ``` +2. **HTTPS aktivieren:** + - Der Caddy-Webserver unterstützt automatisch HTTPS. Stellen Sie sicher, dass eine gültige Domain konfiguriert ist. + +3. **Zugriffsrechte beschränken:** + - Verwenden Sie Benutzerrollen („admin“, „guest“), um den Zugriff auf kritische Funktionen zu steuern. + +--- + +### **2.2. Performance** +1. **Docker-Container optimieren:** + - Reduzieren Sie die Größe der Docker-Images, indem Sie unnötige Dateien in `.dockerignore` ausschließen. + +2. **Datenbankwartung:** + - Führen Sie regelmäßige Backups der SQLite-Datenbank durch: + ```bash + cp db/sqlite.db /path/to/backup/location/ + ``` + - Optimieren Sie die Datenbank regelmäßig: + ```sql + VACUUM; + ``` + +3. **Skalierung:** + - Bei hoher Last kann die Anwendung mit Kubernetes oder einer Cloud-Lösung (z. B. AWS oder Azure) skaliert werden. + +--- + +### **2.3. Fehlerbehebung** +1. **Logs überprüfen:** + - Docker-Logs können wichtige Debug-Informationen liefern: + ```bash + docker logs + ``` + +2. **Health Checks:** + - Integrieren Sie Health Checks in die Docker Compose-Datei, um sicherzustellen, dass die Dienste korrekt laufen. + +3. **Fehlerhafte Drucker deaktivieren:** + - Deaktivieren Sie Drucker mit einer hohen Fehlerrate über das Admin-Dashboard, um die Benutzererfahrung zu verbessern. + +--- + +### **2.4. Updates** +1. **Neue Funktionen hinzufügen:** + - Aktualisieren Sie die Anwendung und erstellen Sie neue Docker-Images: + ```bash + git pull origin main + bash docker/build.sh + ``` + - Stellen Sie die aktualisierten Images bereit: + ```bash + bash docker/deploy.sh + ``` + +2. **Datenbankmigrationen:** + - Führen Sie neue Migrationsskripte mit folgendem Befehl aus: + ```bash + pnpm run db:migrate + ``` + +--- + +## **3. Backup und Wiederherstellung** + +### **3.1. Backups erstellen** +Sichern Sie wichtige Dateien und Datenbanken regelmäßig: +- **SQLite-Datenbank:** + ```bash + cp db/sqlite.db /backup/location/sqlite-$(date +%F).db + ``` +- **Docker-Images:** + ```bash + docker save myp-rp:latest | gzip > /backup/location/myp-rp-$(date +%F).tar.gz + ``` + +### **3.2. Wiederherstellung** +- **Datenbank wiederherstellen:** + ```bash + cp /backup/location/sqlite-.db db/sqlite.db + ``` +- **Docker-Images importieren:** + ```bash + docker load < /backup/location/myp-rp-.tar.gz + ``` + +Nächster Schritt: [=> Admin-Dashboard](./Admin-Dashboard.md) \ No newline at end of file diff --git a/packages/reservation-platform/docs/Datenbank.md b/packages/reservation-platform/docs/Datenbank.md new file mode 100644 index 0000000..253a16e --- /dev/null +++ b/packages/reservation-platform/docs/Datenbank.md @@ -0,0 +1,153 @@ +# **Datenbank und Analytik-Funktionen** + +Dieser Abschnitt konzentriert sich auf die Struktur der Datenbank sowie die Analyse- und Prognosefunktionen, die im Projekt verwendet werden. + +--- + +## **1. Datenbankstruktur** + +Das Datenbankschema wurde mit **Drizzle ORM** definiert und basiert auf SQLite. Die wichtigsten Tabellen und ihre Zwecke sind: + +### **1.1. Tabellenübersicht** + +#### **`user`** +- Speichert Benutzerinformationen. +- Enthält Rollen wie „admin“ oder „guest“ zur Verwaltung von Berechtigungen. + +| **Feld** | **Typ** | **Beschreibung** | +|-------------------|------------|-------------------------------------------| +| `id` | `text` | Eindeutige ID des Benutzers. | +| `github_id` | `integer` | ID des Benutzers aus dem OAuth-Dienst. | +| `name` | `text` | Benutzername. | +| `displayName` | `text` | Angezeigter Name. | +| `email` | `text` | E-Mail-Adresse. | +| `role` | `text` | Benutzerrolle, Standardwert: „guest“. | + +--- + +#### **`printer`** +- Beschreibt verfügbare Drucker und deren Status. + +| **Feld** | **Typ** | **Beschreibung** | +|-------------------|------------|-------------------------------------------| +| `id` | `text` | Eindeutige Drucker-ID. | +| `name` | `text` | Name des Druckers. | +| `description` | `text` | Beschreibung oder Spezifikationen. | +| `status` | `integer` | Betriebsstatus (0 = inaktiv, 1 = aktiv). | + +--- + +#### **`printJob`** +- Speichert Informationen zu Druckaufträgen. + +| **Feld** | **Typ** | **Beschreibung** | +|-----------------------|---------------|-------------------------------------------------------| +| `id` | `text` | Eindeutige Auftrags-ID. | +| `printerId` | `text` | Verweis auf die ID des Druckers. | +| `userId` | `text` | Verweis auf die ID des Benutzers. | +| `startAt` | `integer` | Startzeit des Druckauftrags (Unix-Timestamp). | +| `durationInMinutes` | `integer` | Dauer des Druckauftrags in Minuten. | +| `comments` | `text` | Zusätzliche Kommentare. | +| `aborted` | `integer` | 1 = Abgebrochen, 0 = Erfolgreich abgeschlossen. | +| `abortReason` | `text` | Grund für den Abbruch (falls zutreffend). | + +--- + +#### **`session`** +- Verwaltert Benutzer-Sitzungen und Ablaufzeiten. + +| **Feld** | **Typ** | **Beschreibung** | +|-------------------|------------|-------------------------------------------| +| `id` | `text` | Eindeutige Sitzungs-ID. | +| `user_id` | `text` | Verweis auf die ID des Benutzers. | +| `expires_at` | `integer` | Zeitpunkt, wann die Sitzung abläuft. | + +--- + +### **1.2. Relationen** +- `printer` → `printJob`: Druckaufträge sind an spezifische Drucker gebunden. +- `user` → `printJob`: Druckaufträge werden Benutzern zugewiesen. +- `user` → `session`: Sitzungen verknüpfen Benutzer mit Login-Details. + +--- + +## **2. Analytik-Funktionen** + +Das Projekt bietet verschiedene Analytik- und Prognosetools, um die Druckernutzung und Fehler zu überwachen. + +### **2.1. Fehlerratenanalyse** +- Funktion: `calculatePrinterErrorRate` (in `src/utils/analytics/error-rate.ts`). +- Berechnet die prozentuale Fehlerrate für jeden Drucker basierend auf abgebrochenen Aufträgen. + +Beispielausgabe: +```json +[ + { "name": "Drucker 1", "errorRate": 5.2 }, + { "name": "Drucker 2", "errorRate": 3.7 } +] +``` + +--- + +### **2.2. Abbruchgründe** +- Funktion: `calculateAbortReasonsCount` (in `src/utils/analytics/errors.ts`). +- Zählt die Häufigkeit der Abbruchgründe aus der Tabelle `printJob`. + +Beispielausgabe: +```json +[ + { "abortReason": "Materialmangel", "count": 10 }, + { "abortReason": "Düsenverstopfung", "count": 7 } +] +``` + +--- + +### **2.3. Nutzung und Prognosen** +#### Nutzung: +- Funktion: `calculatePrinterUtilization` (in `src/utils/analytics/utilization.ts`). +- Berechnet die Nutzung der Drucker in Prozent. + +Beispielausgabe: +```json +{ "printerId": "1", "utilizationPercentage": 85 } +``` + +#### Prognosen: +- Funktion: `forecastPrinterUsage` (in `src/utils/analytics/forecast.ts`). +- Nutzt historische Daten, um die erwartete Druckernutzung für kommende Tage/Wochen zu schätzen. + +Beispielausgabe: +```json +[ + { "day": 1, "usageMinutes": 300 }, + { "day": 2, "usageMinutes": 200 } +] +``` + +--- + +### **2.4. Druckvolumen** +- Funktion: `calculatePrintVolumes` (in `src/utils/analytics/volume.ts`). +- Vergleicht die Anzahl der abgeschlossenen Druckaufträge für heute, diese Woche und diesen Monat. + +Beispielausgabe: +```json +{ + "today": 15, + "thisWeek": 90, + "thisMonth": 300 +} +``` + +--- + +## **3. Datenbankinitialisierung** +Die Datenbank wird über Skripte in der `package.json` initialisiert: +```bash +pnpm run db:clean # Datenbank und Migrationsordner löschen +pnpm run db:generate # Neues Schema generieren +pnpm run db:migrate # Migrationsskripte ausführen +``` + +Nächster Schritt: [=> Bereitstellungsdetails und Best Practices](./Bereitstellungsdetails.md) \ No newline at end of file diff --git a/packages/reservation-platform/docs/Installation.md b/packages/reservation-platform/docs/Installation.md new file mode 100644 index 0000000..c1d866d --- /dev/null +++ b/packages/reservation-platform/docs/Installation.md @@ -0,0 +1,93 @@ +# **Installation und Einrichtung** + +In diesem Abschnitt wird beschrieben, wie die MYP-Anwendung installiert und eingerichtet wird. Diese Schritte umfassen die Vorbereitung der Umgebung, das Konfigurieren der notwendigen Dienste und die Bereitstellung des Projekts. + +--- + +## **Voraussetzungen** +### **Hardware und Software** +- **Raspberry Pi:** Die Anwendung ist für den Einsatz auf einem Raspberry Pi optimiert, auf dem Raspbian Lite installiert sein sollte. +- **Docker:** Docker und Docker Compose müssen installiert sein. +- **Netzwerkzugriff:** Der Raspberry Pi muss im Netzwerk erreichbar sein. + +### **Abhängigkeiten** +- Node.js (mindestens Version 20) +- PNPM (Paketmanager) +- SQLite (für lokale Datenbankverwaltung) + +--- + +## **Schritte zur Einrichtung** + +### **1. Repository klonen** +Klonen Sie das Repository auf Ihr System: +```bash +git clone +cd +``` + +### **2. Konfiguration der Umgebungsvariablen** +Passen Sie die Datei `.env.example` an und benennen Sie sie in `.env` um: +```bash +cp .env.example .env +``` +Erforderliche Variablen: +- `OAUTH_CLIENT_ID`: Client-ID für die OAuth-Authentifizierung +- `OAUTH_CLIENT_SECRET`: Geheimnis für die OAuth-Authentifizierung + +### **3. Docker-Container erstellen** +Führen Sie das Skript `build.sh` aus, um Docker-Images zu erstellen: +```bash +bash docker/build.sh +``` +Dies erstellt die notwendigen Docker-Images, einschließlich der Anwendung und eines Caddy-Webservers. + +### **4. Docker-Images speichern** +Speichern Sie die Images in komprimierter Form, um sie auf anderen Geräten bereitzustellen: +```bash +bash docker/save.sh +``` + +### **5. Bereitstellung** +Kopieren Sie die Docker-Images auf den Zielserver (z. B. Raspberry Pi) und führen Sie `deploy.sh` aus: +```bash +scp docker/images/*.tar.xz :/path/to/deployment/ +bash docker/deploy.sh +``` +Das Skript führt die Docker Compose-Konfiguration aus und startet die Anwendung. + +### **(Optional: 6. Admin-User anlegen)** + +Um einen Admin-User anzulegen, muss zuerst das Container-Image gestartet werden. Anschließend meldet man sich mittels +der GitHub-Authentifizierung bei der Anwendung an. + +Der nun in der Datenbank angelegte User hat die Rolle `guest`. Über das CLI muss man nun in die SQLite-Datenbank (die Datenbank sollte außerhalb des Container-Images liegen) wechseln und +den User updaten. + + +#### SQL-Befehl, um den User zu updaten: +```bash +sqlite3 db.sqlite3 +UPDATE users SET role = 'admin' WHERE id = ; +``` + +--- + +## **Start der Anwendung** +Sobald die Docker-Container laufen, ist die Anwendung unter der angegebenen Domain oder IP-Adresse erreichbar. Standardmäßig verwendet der Caddy-Webserver Port 80 (HTTP) und 443 (HTTPS). + +--- + +## **Optional: Entwicklungsmodus** +Für lokale Tests können Sie die Anwendung ohne Docker starten: +1. Installieren Sie Abhängigkeiten: + ```bash + pnpm install + ``` +2. Starten Sie den Entwicklungsserver: + ```bash + pnpm dev + ``` + Die Anwendung ist dann unter `http://localhost:3000` verfügbar. + +Nächster Schritt: [=> Nutzung](./Nutzung.md) \ No newline at end of file diff --git a/packages/reservation-platform/docs/Nutzung.md b/packages/reservation-platform/docs/Nutzung.md new file mode 100644 index 0000000..2080933 --- /dev/null +++ b/packages/reservation-platform/docs/Nutzung.md @@ -0,0 +1,75 @@ +# **Features und Nutzung der Anwendung** + +In diesem Abschnitt beschreibe ich die Hauptfunktionen von MYP (Manage Your Printer) und gebe Anweisungen zur Nutzung der verschiedenen Module. + +--- + +## **1. Hauptfunktionen** + +### **1.1. Druckerreservierung** +- Nutzer können Drucker für einen definierten Zeitraum reservieren. +- Konflikte bei Reservierungen werden durch ein Echtzeit-Überprüfungssystem verhindert. + +### **1.2. Fehler- und Auslastungsanalyse** +- Darstellung von Druckfehlern nach Kategorien und Häufigkeiten. +- Übersicht der aktuellen und historischen Druckernutzung. +- Diagramme zur Fehlerrate, Nutzung und Druckvolumen. + +### **1.3. Admin-Dashboard** +- Verwaltung von Druckern, Nutzern und Druckaufträgen. +- Überblick über alle Abbruchgründe und Druckfehler. +- Zugriff auf erweiterte Statistiken und Prognosen. + +--- + +## **2. Nutzung der Anwendung** + +### **2.1. Login und Authentifizierung** +- Die Anwendung unterstützt OAuth-basierte Authentifizierung. +- Nutzer müssen sich mit einem gültigen Konto anmelden, um Zugriff auf die Funktionen zu erhalten. + +### **2.2. Dashboard** +- Nach dem Login gelangen die Nutzer auf das Dashboard, das einen Überblick über die aktuelle Druckernutzung bietet. +- Administratoren haben Zugriff auf zusätzliche Menüpunkte, wie z. B. Benutzerverwaltung. + +--- + +## **3. Admin-Funktionen** + +### **3.1. Druckerverwaltung** +- Administratoren können Drucker hinzufügen, bearbeiten oder löschen. +- Status eines Druckers (z. B. „in Betrieb“, „außer Betrieb“) kann angepasst werden. + +### **3.2. Nutzerverwaltung** +- Verwalten von Benutzerkonten, einschließlich Rollen (z. B. „Admin“ oder „User“). +- Benutzer können aktiviert oder deaktiviert werden. + +### **3.3. Statistiken und Berichte** +- Diagramme wie: + - **Abbruchgründe:** Zeigt häufige Fehlerursachen. + - **Fehlerrate:** Prozentuale Fehlerquote der Drucker. + - **Nutzung:** Prognosen für die Druckernutzung pro Wochentag. + +--- + +## **4. Diagramme und Visualisierungen** + +### **4.1. Abbruchgründe** +- Ein Säulendiagramm zeigt die Häufigkeiten der Fehlerursachen. +- Nutzt Echtzeit-Daten aus der Druckhistorie. + +### **4.2. Prognostizierte Nutzung** +- Ein Liniendiagramm zeigt die erwartete Druckernutzung pro Tag. +- Hilft bei der Planung von Wartungszeiten. + +### **4.3. Druckvolumen** +- Balkendiagramme vergleichen Druckaufträge heute, diese Woche und diesen Monat. + +--- + +## **5. Interaktive Komponenten** +- **Benachrichtigungen:** Informieren über Druckaufträge, Fehler oder Systemereignisse. +- **Filter und Suchfunktionen:** Erleichtern das Auffinden von Druckern oder Druckaufträgen. +- **Rollenbasierter Zugriff:** Funktionen sind je nach Benutzerrolle eingeschränkt. + +Nächster Schritt: [=> Technische Architektur und Codeaufbau](./Architektur.md) \ No newline at end of file diff --git a/packages/reservation-platform/docs/README.md b/packages/reservation-platform/docs/README.md new file mode 100644 index 0000000..741f73b --- /dev/null +++ b/packages/reservation-platform/docs/README.md @@ -0,0 +1,37 @@ +# **Einleitung** + +> Information: Die Dokumenation wurde mit generativer AI erstellt und kann fehlerhaft sein. Im Zweifel bitte die Quellcode-Dateien anschauen oder die Entwickler kontaktieren. + +## **Projektbeschreibung** +MYP (Manage Your Printer) ist eine Webanwendung zur Verwaltung und Reservierung von 3D-Druckern. Das Projekt wurde als Abschlussarbeit im Rahmen der Fachinformatiker-Ausbildung mit Schwerpunkt Daten- und Prozessanalyse entwickelt und dient als Plattform zur einfachen Koordination und Überwachung von Druckressourcen. Es wurde speziell für die Technische Berufsausbildung des Mercedes-Benz Werkes in Berlin-Marienfelde erstellt. + +--- + +## **Hauptmerkmale** +- **Druckerreservierungen:** Nutzer können 3D-Drucker in definierten Zeitfenstern reservieren. +- **Fehleranalyse:** Statistiken über Druckfehler und Abbruchgründe werden visuell dargestellt. +- **Druckauslastung:** Echtzeit-Daten über die Nutzung der Drucker. +- **Admin-Dashboard:** Übersichtliche Verwaltung und Konfiguration von Druckern, Benutzern und Druckaufträgen. +- **Datenbankintegration:** Alle Daten werden in einer SQLite-Datenbank gespeichert und verwaltet. + +--- + +## **Technologien** +- **Frontend:** React, Next.js, TailwindCSS +- **Backend:** Node.js, Drizzle ORM +- **Datenbank:** SQLite +- **Deployment:** Docker und Raspberry Pi +- **Zusätzliche Bibliotheken:** recharts für Diagramme, Faker.js für Testdaten, sowie diverse Radix-UI-Komponenten. + +--- + +## **Dateistruktur** +Die Repository-Dateien sind in logische Abschnitte unterteilt: +1. **Docker-Konfigurationen** (`docker/`) - Skripte und Konfigurationsdateien für die Bereitstellung. +2. **Frontend-Komponenten** (`src/app/`) - Weboberfläche und deren Funktionalitäten. +3. **Backend-Funktionen** (`src/server/`) - Datenbankinteraktionen und Authentifizierungslogik. +4. **Utils und Helferfunktionen** (`src/utils/`) - Wiederverwendbare Dienste und Hilfsmethoden. +5. **Datenbank-Skripte** (`drizzle/`) - Datenbankschemas und Migrationsdateien. + + +Nächster Schritt: [=> Installation](./Installation.md) diff --git a/packages/reservation-platform/drizzle.config.ts b/packages/reservation-platform/drizzle.config.ts index 302df1e..965ecba 100644 --- a/packages/reservation-platform/drizzle.config.ts +++ b/packages/reservation-platform/drizzle.config.ts @@ -5,8 +5,8 @@ export default defineConfig({ dialect: "sqlite", schema: "./src/server/db/schema.ts", out: "./drizzle", - driver: "better-sqlite", + driver: "libsql", dbCredentials: { - url: "db/sqlite.db", + url: "file:./db/sqlite.db", }, }); diff --git a/packages/reservation-platform/next.config.mjs b/packages/reservation-platform/next.config.mjs index 4678774..887b2d2 100644 --- a/packages/reservation-platform/next.config.mjs +++ b/packages/reservation-platform/next.config.mjs @@ -1,4 +1,26 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {}; +const nextConfig = { + async headers() { + return [ + { + source: "/:path*", + headers: [ + { + key: "Access-Control-Allow-Origin", + value: "m040tbaraspi001.de040.corpintra.net", + }, + { + key: "Access-Control-Allow-Methods", + value: "GET, POST, PUT, DELETE, OPTIONS", + }, + { + key: "Access-Control-Allow-Headers", + value: "Content-Type, Authorization", + }, + ], + }, + ]; + }, +}; export default nextConfig; diff --git a/packages/reservation-platform/package.json b/packages/reservation-platform/package.json index 1cd35b8..d3cc006 100644 --- a/packages/reservation-platform/package.json +++ b/packages/reservation-platform/package.json @@ -1,7 +1,8 @@ { "name": "myp-rp", - "version": "0.1.0", + "version": "1.0.0", "private": true, + "packageManager": "pnpm@9.12.1", "scripts": { "dev": "next dev", "build": "next build", @@ -15,59 +16,68 @@ "db:reset": "pnpm db:clean && pnpm db" }, "dependencies": { - "@headlessui/react": "^2.0.3", - "@headlessui/tailwindcss": "^0.2.0", - "@hookform/resolvers": "^3.3.4", - "@lucia-auth/adapter-drizzle": "^1.0.7", - "@radix-ui/react-alert-dialog": "^1.0.5", - "@radix-ui/react-avatar": "^1.0.4", - "@radix-ui/react-dialog": "^1.0.5", - "@radix-ui/react-dropdown-menu": "^2.0.6", - "@radix-ui/react-hover-card": "^1.0.7", + "@faker-js/faker": "^9.2.0", + "@headlessui/react": "^2.1.10", + "@headlessui/tailwindcss": "^0.2.1", + "@hookform/resolvers": "^3.9.0", + "@libsql/client": "^0.14.0", + "@lucia-auth/adapter-drizzle": "^1.1.0", + "@radix-ui/react-alert-dialog": "^1.1.2", + "@radix-ui/react-avatar": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.2", + "@radix-ui/react-dropdown-menu": "^2.1.2", + "@radix-ui/react-hover-card": "^1.1.2", "@radix-ui/react-icons": "^1.3.0", - "@radix-ui/react-label": "^2.0.2", - "@radix-ui/react-scroll-area": "^1.0.5", - "@radix-ui/react-select": "^2.0.0", - "@radix-ui/react-slot": "^1.0.2", - "@radix-ui/react-tabs": "^1.0.4", - "@radix-ui/react-toast": "^1.1.5", - "@remixicon/react": "^4.2.0", - "@tanstack/react-table": "^8.16.0", - "@tremor/react": "^3.16.2", - "arctic": "^1.8.1", - "better-sqlite3": "^9.6.0", + "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-scroll-area": "^1.2.0", + "@radix-ui/react-select": "^2.1.2", + "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-tabs": "^1.1.1", + "@radix-ui/react-toast": "^1.2.2", + "@remixicon/react": "^4.3.0", + "@tanstack/react-table": "^8.20.5", + "@tremor/react": "^3.18.3", + "arctic": "^1.9.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "date-fns": "^4.1.0", "drizzle-orm": "^0.30.10", - "drizzle-json-db": "^0.1.1", - "lucia": "^3.2.0", + "lodash": "^4.17.21", + "lucia": "^3.2.1", "lucide-react": "^0.378.0", + "luxon": "^3.5.0", "next": "14.2.3", "next-themes": "^0.3.0", - "oslo": "^1.2.0", + "oslo": "^1.2.1", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-hook-form": "^7.51.4", + "react-hook-form": "^7.53.0", "react-if": "^4.1.5", "react-timer-hook": "^3.0.7", + "recharts": "^2.13.3", "regression": "^2.0.1", - "sonner": "^1.4.41", + "sonner": "^1.5.0", + "sqlite": "^5.1.1", + "sqlite3": "^5.1.7", "swr": "^2.2.5", - "tailwind-merge": "^2.3.0", + "tailwind-merge": "^2.5.3", "tailwindcss-animate": "^1.0.7", - "use-debounce": "^10.0.0", + "use-debounce": "^10.0.3", + "uuid": "^11.0.2", "zod": "^3.23.8" }, "devDependencies": { - "@biomejs/biome": "^1.7.3", - "@tailwindcss/forms": "^0.5.7", - "@types/better-sqlite3": "^7.6.10", - "@types/node": "^20.12.11", - "@types/react": "^18.3.1", - "@types/react-dom": "^18.3.0", - "drizzle-kit": "^0.21.1", - "postcss": "^8.4.38", - "tailwindcss": "^3.4.3", - "typescript": "^5.4.5" + "@biomejs/biome": "^1.9.3", + "@tailwindcss/forms": "^0.5.9", + "@types/lodash": "^4.17.13", + "@types/luxon": "^3.4.2", + "@types/node": "^20.16.11", + "@types/react": "^18.3.11", + "@types/react-dom": "^18.3.1", + "drizzle-kit": "^0.21.4", + "postcss": "^8.4.47", + "tailwindcss": "^3.4.13", + "ts-node": "^10.9.2", + "typescript": "^5.6.3" } } diff --git a/packages/reservation-platform/pnpm-lock.yaml b/packages/reservation-platform/pnpm-lock.yaml index e869505..cef9b75 100644 --- a/packages/reservation-platform/pnpm-lock.yaml +++ b/packages/reservation-platform/pnpm-lock.yaml @@ -8,84 +8,96 @@ importers: .: dependencies: + '@faker-js/faker': + specifier: ^9.2.0 + version: 9.2.0 '@headlessui/react': - specifier: ^2.0.3 - version: 2.0.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^2.1.10 + version: 2.1.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@headlessui/tailwindcss': - specifier: ^0.2.0 - version: 0.2.0(tailwindcss@3.4.3) + specifier: ^0.2.1 + version: 0.2.1(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.3))) '@hookform/resolvers': - specifier: ^3.3.4 - version: 3.3.4(react-hook-form@7.51.4(react@18.3.1)) + specifier: ^3.9.0 + version: 3.9.0(react-hook-form@7.53.0(react@18.3.1)) + '@libsql/client': + specifier: ^0.14.0 + version: 0.14.0 '@lucia-auth/adapter-drizzle': - specifier: ^1.0.7 - version: 1.0.7(lucia@3.2.0) + specifier: ^1.1.0 + version: 1.1.0(drizzle-orm@0.30.10(@libsql/client@0.14.0)(@types/better-sqlite3@7.6.11)(@types/react@18.3.11)(better-sqlite3@9.6.0)(react@18.3.1)(sqlite3@5.1.7))(lucia@3.2.1) '@radix-ui/react-alert-dialog': - specifier: ^1.0.5 - version: 1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^1.1.2 + version: 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-avatar': - specifier: ^1.0.4 - version: 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^1.1.1 + version: 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-dialog': - specifier: ^1.0.5 - version: 1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^1.1.2 + version: 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-dropdown-menu': - specifier: ^2.0.6 - version: 2.0.6(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^2.1.2 + version: 2.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-hover-card': - specifier: ^1.0.7 - version: 1.0.7(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^1.1.2 + version: 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-icons': specifier: ^1.3.0 version: 1.3.0(react@18.3.1) '@radix-ui/react-label': - specifier: ^2.0.2 - version: 2.0.2(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^2.1.0 + version: 2.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-scroll-area': - specifier: ^1.0.5 - version: 1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^1.2.0 + version: 1.2.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-select': - specifier: ^2.0.0 - version: 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^2.1.2 + version: 2.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-slot': - specifier: ^1.0.2 - version: 1.0.2(@types/react@18.3.1)(react@18.3.1) + specifier: ^1.1.0 + version: 1.1.0(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-tabs': - specifier: ^1.0.4 - version: 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^1.1.1 + version: 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-toast': - specifier: ^1.1.5 - version: 1.1.5(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^1.2.2 + version: 1.2.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@remixicon/react': - specifier: ^4.2.0 - version: 4.2.0(react@18.3.1) + specifier: ^4.3.0 + version: 4.3.0(react@18.3.1) '@tanstack/react-table': - specifier: ^8.16.0 - version: 8.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^8.20.5 + version: 8.20.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@tremor/react': - specifier: ^3.16.2 - version: 3.16.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.3) + specifier: ^3.18.3 + version: 3.18.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.3))) arctic: - specifier: ^1.8.1 - version: 1.8.1 - better-sqlite3: - specifier: ^9.6.0 - version: 9.6.0 + specifier: ^1.9.2 + version: 1.9.2 class-variance-authority: specifier: ^0.7.0 version: 0.7.0 clsx: specifier: ^2.1.1 version: 2.1.1 + date-fns: + specifier: ^4.1.0 + version: 4.1.0 drizzle-orm: specifier: ^0.30.10 - version: 0.30.10(@types/better-sqlite3@7.6.10)(@types/react@18.3.1)(better-sqlite3@9.6.0)(react@18.3.1) + version: 0.30.10(@libsql/client@0.14.0)(@types/better-sqlite3@7.6.11)(@types/react@18.3.11)(better-sqlite3@9.6.0)(react@18.3.1)(sqlite3@5.1.7) + lodash: + specifier: ^4.17.21 + version: 4.17.21 lucia: - specifier: ^3.2.0 - version: 3.2.0 + specifier: ^3.2.1 + version: 3.2.1 lucide-react: specifier: ^0.378.0 version: 0.378.0(react@18.3.1) + luxon: + specifier: ^3.5.0 + version: 3.5.0 next: specifier: 14.2.3 version: 14.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -93,8 +105,8 @@ importers: specifier: ^0.3.0 version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) oslo: - specifier: ^1.2.0 - version: 1.2.0 + specifier: ^1.2.1 + version: 1.2.1 react: specifier: ^18.3.1 version: 18.3.1 @@ -102,66 +114,84 @@ importers: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) react-hook-form: - specifier: ^7.51.4 - version: 7.51.4(react@18.3.1) + specifier: ^7.53.0 + version: 7.53.0(react@18.3.1) react-if: specifier: ^4.1.5 version: 4.1.5(react@18.3.1) react-timer-hook: specifier: ^3.0.7 version: 3.0.7(react@18.3.1) + recharts: + specifier: ^2.13.3 + version: 2.13.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) regression: specifier: ^2.0.1 version: 2.0.1 sonner: - specifier: ^1.4.41 - version: 1.4.41(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^1.5.0 + version: 1.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + sqlite: + specifier: ^5.1.1 + version: 5.1.1 + sqlite3: + specifier: ^5.1.7 + version: 5.1.7 swr: specifier: ^2.2.5 version: 2.2.5(react@18.3.1) tailwind-merge: - specifier: ^2.3.0 - version: 2.3.0 + specifier: ^2.5.3 + version: 2.5.3 tailwindcss-animate: specifier: ^1.0.7 - version: 1.0.7(tailwindcss@3.4.3) + version: 1.0.7(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.3))) use-debounce: - specifier: ^10.0.0 - version: 10.0.0(react@18.3.1) + specifier: ^10.0.3 + version: 10.0.3(react@18.3.1) + uuid: + specifier: ^11.0.2 + version: 11.0.2 zod: specifier: ^3.23.8 version: 3.23.8 devDependencies: '@biomejs/biome': - specifier: ^1.7.3 - version: 1.7.3 + specifier: ^1.9.3 + version: 1.9.3 '@tailwindcss/forms': - specifier: ^0.5.7 - version: 0.5.7(tailwindcss@3.4.3) - '@types/better-sqlite3': - specifier: ^7.6.10 - version: 7.6.10 + specifier: ^0.5.9 + version: 0.5.9(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.3))) + '@types/lodash': + specifier: ^4.17.13 + version: 4.17.13 + '@types/luxon': + specifier: ^3.4.2 + version: 3.4.2 '@types/node': - specifier: ^20.12.11 - version: 20.12.11 + specifier: ^20.16.11 + version: 20.16.11 '@types/react': + specifier: ^18.3.11 + version: 18.3.11 + '@types/react-dom': specifier: ^18.3.1 version: 18.3.1 - '@types/react-dom': - specifier: ^18.3.0 - version: 18.3.0 drizzle-kit: - specifier: ^0.21.1 - version: 0.21.1 + specifier: ^0.21.4 + version: 0.21.4 postcss: - specifier: ^8.4.38 - version: 8.4.38 + specifier: ^8.4.47 + version: 8.4.47 tailwindcss: - specifier: ^3.4.3 - version: 3.4.3 + specifier: ^3.4.13 + version: 3.4.13(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.3)) + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@20.16.11)(typescript@5.6.3) typescript: - specifier: ^5.4.5 - version: 5.4.5 + specifier: ^5.6.3 + version: 5.6.3 packages: @@ -173,59 +203,63 @@ packages: resolution: {integrity: sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==} engines: {node: '>=6.9.0'} - '@biomejs/biome@1.7.3': - resolution: {integrity: sha512-ogFQI+fpXftr+tiahA6bIXwZ7CSikygASdqMtH07J2cUzrpjyTMVc9Y97v23c7/tL1xCZhM+W9k4hYIBm7Q6cQ==} + '@biomejs/biome@1.9.3': + resolution: {integrity: sha512-POjAPz0APAmX33WOQFGQrwLvlu7WLV4CFJMlB12b6ZSg+2q6fYu9kZwLCOA+x83zXfcPd1RpuWOKJW0GbBwLIQ==} engines: {node: '>=14.21.3'} hasBin: true - '@biomejs/cli-darwin-arm64@1.7.3': - resolution: {integrity: sha512-eDvLQWmGRqrPIRY7AIrkPHkQ3visEItJKkPYSHCscSDdGvKzYjmBJwG1Gu8+QC5ed6R7eiU63LEC0APFBobmfQ==} + '@biomejs/cli-darwin-arm64@1.9.3': + resolution: {integrity: sha512-QZzD2XrjJDUyIZK+aR2i5DDxCJfdwiYbUKu9GzkCUJpL78uSelAHAPy7m0GuPMVtF/Uo+OKv97W3P9nuWZangQ==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] - '@biomejs/cli-darwin-x64@1.7.3': - resolution: {integrity: sha512-JXCaIseKRER7dIURsVlAJacnm8SG5I0RpxZ4ya3dudASYUc68WGl4+FEN03ABY3KMIq7hcK1tzsJiWlmXyosZg==} + '@biomejs/cli-darwin-x64@1.9.3': + resolution: {integrity: sha512-vSCoIBJE0BN3SWDFuAY/tRavpUtNoqiceJ5PrU3xDfsLcm/U6N93JSM0M9OAiC/X7mPPfejtr6Yc9vSgWlEgVw==} engines: {node: '>=14.21.3'} cpu: [x64] os: [darwin] - '@biomejs/cli-linux-arm64-musl@1.7.3': - resolution: {integrity: sha512-c8AlO45PNFZ1BYcwaKzdt46kYbuP6xPGuGQ6h4j3XiEDpyseRRUy/h+6gxj07XovmyxKnSX9GSZ6nVbZvcVUAw==} + '@biomejs/cli-linux-arm64-musl@1.9.3': + resolution: {integrity: sha512-VBzyhaqqqwP3bAkkBrhVq50i3Uj9+RWuj+pYmXrMDgjS5+SKYGE56BwNw4l8hR3SmYbLSbEo15GcV043CDSk+Q==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-arm64@1.7.3': - resolution: {integrity: sha512-phNTBpo7joDFastnmZsFjYcDYobLTx4qR4oPvc9tJ486Bd1SfEVPHEvJdNJrMwUQK56T+TRClOQd/8X1nnjA9w==} + '@biomejs/cli-linux-arm64@1.9.3': + resolution: {integrity: sha512-vJkAimD2+sVviNTbaWOGqEBy31cW0ZB52KtpVIbkuma7PlfII3tsLhFa+cwbRAcRBkobBBhqZ06hXoZAN8NODQ==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-x64-musl@1.7.3': - resolution: {integrity: sha512-UdEHKtYGWEX3eDmVWvQeT+z05T9/Sdt2+F/7zmMOFQ7boANeX8pcO6EkJPK3wxMudrApsNEKT26rzqK6sZRTRA==} + '@biomejs/cli-linux-x64-musl@1.9.3': + resolution: {integrity: sha512-TJmnOG2+NOGM72mlczEsNki9UT+XAsMFAOo8J0me/N47EJ/vkLXxf481evfHLlxMejTY6IN8SdRSiPVLv6AHlA==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-linux-x64@1.7.3': - resolution: {integrity: sha512-vnedYcd5p4keT3iD48oSKjOIRPYcjSNNbd8MO1bKo9ajg3GwQXZLAH+0Cvlr+eMsO67/HddWmscSQwTFrC/uPA==} + '@biomejs/cli-linux-x64@1.9.3': + resolution: {integrity: sha512-x220V4c+romd26Mu1ptU+EudMXVS4xmzKxPVb9mgnfYlN4Yx9vD5NZraSx/onJnd3Gh/y8iPUdU5CDZJKg9COA==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-win32-arm64@1.7.3': - resolution: {integrity: sha512-unNCDqUKjujYkkSxs7gFIfdasttbDC4+z0kYmcqzRk6yWVoQBL4dNLcCbdnJS+qvVDNdI9rHp2NwpQ0WAdla4Q==} + '@biomejs/cli-win32-arm64@1.9.3': + resolution: {integrity: sha512-lg/yZis2HdQGsycUvHWSzo9kOvnGgvtrYRgoCEwPBwwAL8/6crOp3+f47tPwI/LI1dZrhSji7PNsGKGHbwyAhw==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [win32] - '@biomejs/cli-win32-x64@1.7.3': - resolution: {integrity: sha512-ZmByhbrnmz/UUFYB622CECwhKIPjJLLPr5zr3edhu04LzbfcOrz16VYeNq5dpO1ADG70FORhAJkaIGdaVBG00w==} + '@biomejs/cli-win32-x64@1.9.3': + resolution: {integrity: sha512-cQMy2zanBkVLpmmxXdK6YePzmZx0s5Z7KEnwmrW54rcXK3myCNbQa09SwGZ8i/8sLw0H9F3X7K4rxVNGU8/D4Q==} engines: {node: '>=14.21.3'} cpu: [x64] os: [win32] + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + '@emnapi/core@0.45.0': resolution: {integrity: sha512-DPWjcUDQkCeEM4VnljEOEcXdAD7pp8zSZsgOujk/LGIwCXWbXJngin+MO4zbH429lzeC3WbYLGjE2MaUOwzpyw==} @@ -234,9 +268,11 @@ packages: '@esbuild-kit/core-utils@3.3.2': resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} + deprecated: 'Merged into tsx: https://tsx.is' '@esbuild-kit/esm-loader@2.6.5': resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==} + deprecated: 'Merged into tsx: https://tsx.is' '@esbuild/aix-ppc64@0.19.12': resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} @@ -508,6 +544,10 @@ packages: cpu: [x64] os: [win32] + '@faker-js/faker@9.2.0': + resolution: {integrity: sha512-ulqQu4KMr1/sTFIYvqSdegHT8NIkt66tFAkugGnHA+1WAfEn6hMzNR+svjXGFRVLnapxvej67Z/LwchFrnLBUg==} + engines: {node: '>=18.0.0', npm: '>=9.0.0'} + '@floating-ui/core@1.6.1': resolution: {integrity: sha512-42UH54oPZHPdRHdw6BgoBD6cg/eVTmVrFcgeRDM3jbO7uxSoipVcmcIGFcA5jmOHO5apcyvBhkSKES3fQJnu7A==} @@ -526,20 +566,29 @@ packages: react: '>=16.8.0' react-dom: '>=16.8.0' + '@floating-ui/react-dom@2.1.2': + resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + '@floating-ui/react@0.19.2': resolution: {integrity: sha512-JyNk4A0Ezirq8FlXECvRtQOX/iBe5Ize0W/pLkrZjfHW9GUV7Xnq6zm6fyZuQzaHHqEnVizmvlA96e1/CkZv+w==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' - '@floating-ui/react@0.26.13': - resolution: {integrity: sha512-kBa9wntpugzrZ8t/4yWelvSmEKZdeTXTJzrxqyrLmcU/n1SM4nvse8yQh2e1b37rJGvtu0EplV9+IkBrCJ1vkw==} + '@floating-ui/react@0.26.24': + resolution: {integrity: sha512-2ly0pCkZIGEQUq5H8bBK0XJmc1xIK/RM3tvVzY3GBER7IOD1UgmC2Y2tjj4AuS+TC+vTE1KJv2053290jua0Sw==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' - '@floating-ui/utils@0.2.2': - resolution: {integrity: sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw==} + '@floating-ui/utils@0.2.8': + resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==} + + '@gar/promisify@1.1.3': + resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} '@headlessui/react@1.7.19': resolution: {integrity: sha512-Ll+8q3OlMJfJbAKM/+/Y2q6PPYbryqNTXDbryx7SXLIDamkF6iQFbriYHga0dY44PvDhvvBWCx1Xj4U5+G4hOw==} @@ -548,21 +597,21 @@ packages: react: ^16 || ^17 || ^18 react-dom: ^16 || ^17 || ^18 - '@headlessui/react@2.0.3': - resolution: {integrity: sha512-Xd1h0YZgfhxZ7W1w4TvK0/TZ1c4qaX4liYVUkAXqW1HCLcXSqnMeYAUGJS/BBroBAUL9HErjyFcRpCWRQZ/0lA==} + '@headlessui/react@2.1.10': + resolution: {integrity: sha512-6mLa2fjMDAFQi+/R10B+zU3edsUk/MDtENB2zHho0lqKU1uzhAfJLUduWds4nCo8wbl3vULtC5rJfZAQ1yqIng==} engines: {node: '>=10'} peerDependencies: react: ^18 react-dom: ^18 - '@headlessui/tailwindcss@0.2.0': - resolution: {integrity: sha512-fpL830Fln1SykOCboExsWr3JIVeQKieLJ3XytLe/tt1A0XzqUthOftDmjcCYLW62w7mQI7wXcoPXr3tZ9QfGxw==} + '@headlessui/tailwindcss@0.2.1': + resolution: {integrity: sha512-2+5+NZ+RzMyrVeCZOxdbvkUSssSxGvcUxphkIfSVLpRiKsj+/63T2TOL9dBYMXVfj/CGr6hMxSRInzXv6YY7sA==} engines: {node: '>=10'} peerDependencies: tailwindcss: ^3.0 - '@hookform/resolvers@3.3.4': - resolution: {integrity: sha512-o5cgpGOuJYrd+iMKvkttOclgwRW86EsWJZZRC23prf0uU2i48Htq4PuT73AVb9ionFyZrwYEITuOFGF+BydEtQ==} + '@hookform/resolvers@3.9.0': + resolution: {integrity: sha512-bU0Gr4EepJ/EQsH/IwEzYLsT/PEj5C0ynLQ4m+GSHS+xKH4TfSelhluTgOaoc4kA5s7eCsQbM4wvZLzELmWzUg==} peerDependencies: react-hook-form: ^7.0.0 @@ -588,11 +637,69 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - '@lucia-auth/adapter-drizzle@1.0.7': - resolution: {integrity: sha512-X/V7fLBca8EC/gPXCntwbQpb0+F9oEuRoHElvsi9rCrdnGhCMNxHgwAvgiQ6pes+rIYpyvx4n3hvjqo/fPo03A==} + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@libsql/client@0.14.0': + resolution: {integrity: sha512-/9HEKfn6fwXB5aTEEoMeFh4CtG0ZzbncBb1e++OCdVpgKZ/xyMsIVYXm0w7Pv4RUel803vE6LwniB3PqD72R0Q==} + + '@libsql/core@0.14.0': + resolution: {integrity: sha512-nhbuXf7GP3PSZgdCY2Ecj8vz187ptHlZQ0VRc751oB2C1W8jQUXKKklvt7t1LJiUTQBVJuadF628eUk+3cRi4Q==} + + '@libsql/darwin-arm64@0.4.6': + resolution: {integrity: sha512-45i604CJ2Lubbg7NqtDodjarF6VgST8rS5R8xB++MoRqixtDns9PZ6tocT9pRJDWuTWEiy2sjthPOFWMKwYAsg==} + cpu: [arm64] + os: [darwin] + + '@libsql/darwin-x64@0.4.6': + resolution: {integrity: sha512-dRKliflhfr5zOPSNgNJ6C2nZDd4YA8bTXF3MUNqNkcxQ8BffaH9uUwL9kMq89LkFIZQHcyP75bBgZctxfJ/H5Q==} + cpu: [x64] + os: [darwin] + + '@libsql/hrana-client@0.7.0': + resolution: {integrity: sha512-OF8fFQSkbL7vJY9rfuegK1R7sPgQ6kFMkDamiEccNUvieQ+3urzfDFI616oPl8V7T9zRmnTkSjMOImYCAVRVuw==} + + '@libsql/isomorphic-fetch@0.3.1': + resolution: {integrity: sha512-6kK3SUK5Uu56zPq/Las620n5aS9xJq+jMBcNSOmjhNf/MUvdyji4vrMTqD7ptY7/4/CAVEAYDeotUz60LNQHtw==} + engines: {node: '>=18.0.0'} + + '@libsql/isomorphic-ws@0.1.5': + resolution: {integrity: sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==} + + '@libsql/linux-arm64-gnu@0.4.6': + resolution: {integrity: sha512-DMPavVyY6vYPAYcQR1iOotHszg+5xSjHSg6F9kNecPX0KKdGq84zuPJmORfKOPtaWvzPewNFdML/e+s1fu09XQ==} + cpu: [arm64] + os: [linux] + + '@libsql/linux-arm64-musl@0.4.6': + resolution: {integrity: sha512-whuHSYAZyclGjM3L0mKGXyWqdAy7qYvPPn+J1ve7FtGkFlM0DiIPjA5K30aWSGJSRh72sD9DBZfnu8CpfSjT6w==} + cpu: [arm64] + os: [linux] + + '@libsql/linux-x64-gnu@0.4.6': + resolution: {integrity: sha512-0ggx+5RwEbYabIlDBBAvavdfIJCZ757u6nDZtBeQIhzW99EKbWG3lvkXHM3qudFb/pDWSUY4RFBm6vVtF1cJGA==} + cpu: [x64] + os: [linux] + + '@libsql/linux-x64-musl@0.4.6': + resolution: {integrity: sha512-SWNrv7Hz72QWlbM/ZsbL35MPopZavqCUmQz2HNDZ55t0F+kt8pXuP+bbI2KvmaQ7wdsoqAA4qBmjol0+bh4ndw==} + cpu: [x64] + os: [linux] + + '@libsql/win32-x64-msvc@0.4.6': + resolution: {integrity: sha512-Q0axn110zDNELfkEog3Nl8p9BU4eI/UvgaHevGyOiSDN7s0KPfj0j6jwVHk4oz3o/d/Gg3DRIxomZ4ftfTOy/g==} + cpu: [x64] + os: [win32] + + '@lucia-auth/adapter-drizzle@1.1.0': + resolution: {integrity: sha512-iCTnZWvfI5lLZOdUHZYiXA1jaspIFEeo2extLxQ3DjP3uOVys7IPwBi7zezLIRu9dhro4H4Kji+7gSYyjcef2A==} peerDependencies: + drizzle-orm: '>= 0.29 <1' lucia: 3.x + '@neon-rs/load@0.0.4': + resolution: {integrity: sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==} + '@next/env@14.2.3': resolution: {integrity: sha512-W7fd7IbkfmeeY2gXrzJYDx8D2lWKbVoTIj1o1ScPHNzvp30s1AuoEFSdr39bC5sjxJaxTtq3OTCZboNp0lNWHA==} @@ -836,163 +943,192 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@npmcli/fs@1.1.1': + resolution: {integrity: sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==} + + '@npmcli/move-file@1.1.2': + resolution: {integrity: sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==} + engines: {node: '>=10'} + deprecated: This functionality has been moved to @npmcli/fs + + '@oslojs/asn1@1.0.0': + resolution: {integrity: sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA==} + + '@oslojs/binary@1.0.0': + resolution: {integrity: sha512-9RCU6OwXU6p67H4NODbuxv2S3eenuQ4/WFLrsq+K/k682xrznH5EVWA7N4VFk9VYVcbFtKqur5YQQZc0ySGhsQ==} + + '@oslojs/crypto@1.0.1': + resolution: {integrity: sha512-7n08G8nWjAr/Yu3vu9zzrd0L9XnrJfpMioQcvCMxBIiF5orECHe5/3J0jmXRVvgfqMm/+4oxlQ+Sq39COYLcNQ==} + + '@oslojs/encoding@1.1.0': + resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@radix-ui/number@1.0.1': - resolution: {integrity: sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg==} + '@radix-ui/number@1.1.0': + resolution: {integrity: sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==} - '@radix-ui/primitive@1.0.1': - resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==} + '@radix-ui/primitive@1.1.0': + resolution: {integrity: sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==} - '@radix-ui/react-alert-dialog@1.0.5': - resolution: {integrity: sha512-OrVIOcZL0tl6xibeuGt5/+UxoT2N27KCFOPjFyfXMnchxSHZ/OW7cCX2nGlIYJrbHK/fczPcFzAwvNBB6XBNMA==} + '@radix-ui/react-alert-dialog@1.1.2': + resolution: {integrity: sha512-eGSlLzPhKO+TErxkiGcCZGuvbVMnLA1MTnyBksGOeGRGkxHiiJUujsjmNTdWTm4iHVSRaUao9/4Ur671auMghQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true '@types/react-dom': optional: true - '@radix-ui/react-arrow@1.0.3': - resolution: {integrity: sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==} + '@radix-ui/react-arrow@1.1.0': + resolution: {integrity: sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true '@types/react-dom': optional: true - '@radix-ui/react-avatar@1.0.4': - resolution: {integrity: sha512-kVK2K7ZD3wwj3qhle0ElXhOjbezIgyl2hVvgwfIdexL3rN6zJmy5AqqIf+D31lxVppdzV8CjAfZ6PklkmInZLw==} + '@radix-ui/react-avatar@1.1.1': + resolution: {integrity: sha512-eoOtThOmxeoizxpX6RiEsQZ2wj5r4+zoeqAwO0cBaFQGjJwIH3dIX0OCxNrCyrrdxG+vBweMETh3VziQG7c1kw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true '@types/react-dom': optional: true - '@radix-ui/react-collection@1.0.3': - resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==} + '@radix-ui/react-collection@1.1.0': + resolution: {integrity: sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true '@types/react-dom': optional: true - '@radix-ui/react-compose-refs@1.0.1': - resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==} + '@radix-ui/react-compose-refs@1.1.0': + resolution: {integrity: sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==} peerDependencies: '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true - '@radix-ui/react-context@1.0.1': - resolution: {integrity: sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==} + '@radix-ui/react-context@1.1.0': + resolution: {integrity: sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==} peerDependencies: '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true - '@radix-ui/react-dialog@1.0.5': - resolution: {integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==} + '@radix-ui/react-context@1.1.1': + resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dialog@1.1.2': + resolution: {integrity: sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true '@types/react-dom': optional: true - '@radix-ui/react-direction@1.0.1': - resolution: {integrity: sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==} + '@radix-ui/react-direction@1.1.0': + resolution: {integrity: sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==} peerDependencies: '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true - '@radix-ui/react-dismissable-layer@1.0.5': - resolution: {integrity: sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==} + '@radix-ui/react-dismissable-layer@1.1.1': + resolution: {integrity: sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true '@types/react-dom': optional: true - '@radix-ui/react-dropdown-menu@2.0.6': - resolution: {integrity: sha512-i6TuFOoWmLWq+M/eCLGd/bQ2HfAX1RJgvrBQ6AQLmzfvsLdefxbWu8G9zczcPFfcSPehz9GcpF6K9QYreFV8hA==} + '@radix-ui/react-dropdown-menu@2.1.2': + resolution: {integrity: sha512-GVZMR+eqK8/Kes0a36Qrv+i20bAPXSn8rCBTHx30w+3ECnR5o3xixAlqcVaYvLeyKUsm0aqyhWfmUcqufM8nYA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true '@types/react-dom': optional: true - '@radix-ui/react-focus-guards@1.0.1': - resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==} + '@radix-ui/react-focus-guards@1.1.1': + resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==} peerDependencies: '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true - '@radix-ui/react-focus-scope@1.0.4': - resolution: {integrity: sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==} + '@radix-ui/react-focus-scope@1.1.0': + resolution: {integrity: sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true '@types/react-dom': optional: true - '@radix-ui/react-hover-card@1.0.7': - resolution: {integrity: sha512-OcUN2FU0YpmajD/qkph3XzMcK/NmSk9hGWnjV68p6QiZMgILugusgQwnLSDs3oFSJYGKf3Y49zgFedhGh04k9A==} + '@radix-ui/react-hover-card@1.1.2': + resolution: {integrity: sha512-Y5w0qGhysvmqsIy6nQxaPa6mXNKznfoGjOfBgzOjocLxr2XlSjqBMYQQL+FfyogsMuX+m8cZyQGYhJxvxUzO4w==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true @@ -1004,279 +1140,279 @@ packages: peerDependencies: react: ^16.x || ^17.x || ^18.x - '@radix-ui/react-id@1.0.1': - resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==} + '@radix-ui/react-id@1.1.0': + resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==} peerDependencies: '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true - '@radix-ui/react-label@2.0.2': - resolution: {integrity: sha512-N5ehvlM7qoTLx7nWPodsPYPgMzA5WM8zZChQg8nyFJKnDO5WHdba1vv5/H6IO5LtJMfD2Q3wh1qHFGNtK0w3bQ==} + '@radix-ui/react-label@2.1.0': + resolution: {integrity: sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true '@types/react-dom': optional: true - '@radix-ui/react-menu@2.0.6': - resolution: {integrity: sha512-BVkFLS+bUC8HcImkRKPSiVumA1VPOOEC5WBMiT+QAVsPzW1FJzI9KnqgGxVDPBcql5xXrHkD3JOVoXWEXD8SYA==} + '@radix-ui/react-menu@2.1.2': + resolution: {integrity: sha512-lZ0R4qR2Al6fZ4yCCZzu/ReTFrylHFxIqy7OezIpWF4bL0o9biKo0pFIvkaew3TyZ9Fy5gYVrR5zCGZBVbO1zg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true '@types/react-dom': optional: true - '@radix-ui/react-popper@1.1.3': - resolution: {integrity: sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==} + '@radix-ui/react-popper@1.2.0': + resolution: {integrity: sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true '@types/react-dom': optional: true - '@radix-ui/react-portal@1.0.4': - resolution: {integrity: sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==} + '@radix-ui/react-portal@1.1.2': + resolution: {integrity: sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true '@types/react-dom': optional: true - '@radix-ui/react-presence@1.0.1': - resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==} + '@radix-ui/react-presence@1.1.1': + resolution: {integrity: sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true '@types/react-dom': optional: true - '@radix-ui/react-primitive@1.0.3': - resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==} + '@radix-ui/react-primitive@2.0.0': + resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true '@types/react-dom': optional: true - '@radix-ui/react-roving-focus@1.0.4': - resolution: {integrity: sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==} + '@radix-ui/react-roving-focus@1.1.0': + resolution: {integrity: sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true '@types/react-dom': optional: true - '@radix-ui/react-scroll-area@1.0.5': - resolution: {integrity: sha512-b6PAgH4GQf9QEn8zbT2XUHpW5z8BzqEc7Kl11TwDrvuTrxlkcjTD5qa/bxgKr+nmuXKu4L/W5UZ4mlP/VG/5Gw==} + '@radix-ui/react-scroll-area@1.2.0': + resolution: {integrity: sha512-q2jMBdsJ9zB7QG6ngQNzNwlvxLQqONyL58QbEGwuyRZZb/ARQwk3uQVbCF7GvQVOtV6EU/pDxAw3zRzJZI3rpQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true '@types/react-dom': optional: true - '@radix-ui/react-select@2.0.0': - resolution: {integrity: sha512-RH5b7af4oHtkcHS7pG6Sgv5rk5Wxa7XI8W5gvB1N/yiuDGZxko1ynvOiVhFM7Cis2A8zxF9bTOUVbRDzPepe6w==} + '@radix-ui/react-select@2.1.2': + resolution: {integrity: sha512-rZJtWmorC7dFRi0owDmoijm6nSJH1tVw64QGiNIZ9PNLyBDtG+iAq+XGsya052At4BfarzY/Dhv9wrrUr6IMZA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true '@types/react-dom': optional: true - '@radix-ui/react-slot@1.0.2': - resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==} + '@radix-ui/react-slot@1.1.0': + resolution: {integrity: sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==} peerDependencies: '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true - '@radix-ui/react-tabs@1.0.4': - resolution: {integrity: sha512-egZfYY/+wRNCflXNHx+dePvnz9FbmssDTJBtgRfDY7e8SE5oIo3Py2eCB1ckAbh1Q7cQ/6yJZThJ++sgbxibog==} + '@radix-ui/react-tabs@1.1.1': + resolution: {integrity: sha512-3GBUDmP2DvzmtYLMsHmpA1GtR46ZDZ+OreXM/N+kkQJOPIgytFWWTfDQmBQKBvaFS0Vno0FktdbVzN28KGrMdw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true '@types/react-dom': optional: true - '@radix-ui/react-toast@1.1.5': - resolution: {integrity: sha512-fRLn227WHIBRSzuRzGJ8W+5YALxofH23y0MlPLddaIpLpCDqdE0NZlS2NRQDRiptfxDeeCjgFIpexB1/zkxDlw==} + '@radix-ui/react-toast@1.2.2': + resolution: {integrity: sha512-Z6pqSzmAP/bFJoqMAston4eSNa+ud44NSZTiZUmUen+IOZ5nBY8kzuU5WDBVyFXPtcW6yUalOHsxM/BP6Sv8ww==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true '@types/react-dom': optional: true - '@radix-ui/react-use-callback-ref@1.0.1': - resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==} + '@radix-ui/react-use-callback-ref@1.1.0': + resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==} peerDependencies: '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true - '@radix-ui/react-use-controllable-state@1.0.1': - resolution: {integrity: sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==} + '@radix-ui/react-use-controllable-state@1.1.0': + resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==} peerDependencies: '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true - '@radix-ui/react-use-escape-keydown@1.0.3': - resolution: {integrity: sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==} + '@radix-ui/react-use-escape-keydown@1.1.0': + resolution: {integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==} peerDependencies: '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true - '@radix-ui/react-use-layout-effect@1.0.1': - resolution: {integrity: sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==} + '@radix-ui/react-use-layout-effect@1.1.0': + resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==} peerDependencies: '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true - '@radix-ui/react-use-previous@1.0.1': - resolution: {integrity: sha512-cV5La9DPwiQ7S0gf/0qiD6YgNqM5Fk97Kdrlc5yBcrF3jyEZQwm7vYFqMo4IfeHgJXsRaMvLABFtd0OVEmZhDw==} + '@radix-ui/react-use-previous@1.1.0': + resolution: {integrity: sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==} peerDependencies: '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true - '@radix-ui/react-use-rect@1.0.1': - resolution: {integrity: sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==} + '@radix-ui/react-use-rect@1.1.0': + resolution: {integrity: sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==} peerDependencies: '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true - '@radix-ui/react-use-size@1.0.1': - resolution: {integrity: sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==} + '@radix-ui/react-use-size@1.1.0': + resolution: {integrity: sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==} peerDependencies: '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true - '@radix-ui/react-visually-hidden@1.0.3': - resolution: {integrity: sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==} + '@radix-ui/react-visually-hidden@1.1.0': + resolution: {integrity: sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true '@types/react-dom': optional: true - '@radix-ui/rect@1.0.1': - resolution: {integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==} + '@radix-ui/rect@1.1.0': + resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==} - '@react-aria/focus@3.17.0': - resolution: {integrity: sha512-aRzBw1WTUkcIV3xFrqPA6aB8ZVt3XyGpTaSHAypU0Pgoy2wRq9YeJYpbunsKj9CJmskuffvTqXwAjTcaQish1Q==} + '@react-aria/focus@3.18.3': + resolution: {integrity: sha512-WKUElg+5zS0D3xlVn8MntNnkzJql2J6MuzAMP8Sv5WTgFDse/XGR842dsxPTIyKKdrWVCRegCuwa4m3n/GzgJw==} peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 - '@react-aria/interactions@3.21.2': - resolution: {integrity: sha512-Ju706DtoEmI/2vsfu9DCEIjDqsRBVLm/wmt2fr0xKbBca7PtmK8daajxFWz+eTq+EJakvYfLr7gWgLau9HyWXg==} + '@react-aria/interactions@3.22.3': + resolution: {integrity: sha512-RRUb/aG+P0IKTIWikY/SylB6bIbLZeztnZY2vbe7RAG5MgVaCgn5HQ45SI15GlTmhsFG8CnF6slJsUFJiNHpbQ==} peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 - '@react-aria/ssr@3.9.3': - resolution: {integrity: sha512-5bUZ93dmvHFcmfUcEN7qzYe8yQQ8JY+nHN6m9/iSDCQ/QmCiE0kWXYwhurjw5ch6I8WokQzx66xKIMHBAa4NNA==} + '@react-aria/ssr@3.9.6': + resolution: {integrity: sha512-iLo82l82ilMiVGy342SELjshuWottlb5+VefO3jOQqQRNYnJBFpUSadswDPbRimSgJUZuFwIEYs6AabkP038fA==} engines: {node: '>= 12'} peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 - '@react-aria/utils@3.24.0': - resolution: {integrity: sha512-JAxkPhK5fCvFVNY2YG3TW3m1nTzwRcbz7iyTSkUzLFat4N4LZ7Kzh7NMHsgeE/oMOxd8zLY+XsUxMu/E/2GujA==} + '@react-aria/utils@3.25.3': + resolution: {integrity: sha512-PR5H/2vaD8fSq0H/UB9inNbc8KDcVmW6fYAfSWkkn+OAdhTTMVKqXXrZuZBWyFfSD5Ze7VN6acr4hrOQm2bmrA==} peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 - '@react-stately/utils@3.10.0': - resolution: {integrity: sha512-nji2i9fTYg65ZWx/3r11zR1F2tGya+mBubRCbMTwHyRnsSLFZaeq/W6lmrOyIy1uMJKBNKLJpqfmpT4x7rw6pg==} + '@react-stately/utils@3.10.4': + resolution: {integrity: sha512-gBEQEIMRh5f60KCm7QKQ2WfvhB2gLUr9b72sqUdIZ2EG+xuPgaIlCBeSicvjmjBvYZwOjoOEnmIkcx2GHp/HWw==} peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 - '@react-types/shared@3.23.0': - resolution: {integrity: sha512-GQm/iPiii3ikcaMNR4WdVkJ4w0mKtV3mLqeSfSqzdqbPr6vONkqXbh3RhPlPmAJs1b4QHnexd/wZQP3U9DHOwQ==} + '@react-types/shared@3.25.0': + resolution: {integrity: sha512-OZSyhzU6vTdW3eV/mz5i6hQwQUhkRs7xwY2d1aqPvTdMe0+2cY7Fwp45PAiwYLEj73i9ro2FxF9qC4DvHGSCgQ==} peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 - '@remixicon/react@4.2.0': - resolution: {integrity: sha512-eGhKpZ88OU0qkcY9pJu6khBmItDV82nU130E6C68yc+FbljueHlUYy/4CrJsmf860RIDMay2Rpzl27OSJ81miw==} + '@remixicon/react@4.3.0': + resolution: {integrity: sha512-mAVDn8pAa9dURltGwiYrf7bPIqjG4ZAnCUHfjpgz3g+HLSDNXOaJ67Z5wmjVB5KMGpp9JbbTN5vsp2z+ajVLWg==} peerDependencies: react: '>=18.2.0' @@ -1286,42 +1422,67 @@ packages: '@swc/helpers@0.5.5': resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} - '@tailwindcss/forms@0.5.7': - resolution: {integrity: sha512-QE7X69iQI+ZXwldE+rzasvbJiyV/ju1FGHH0Qn2W3FKbuYtqp8LKcy6iSw79fVUT5/Vvf+0XgLCeYVG+UV6hOw==} + '@tailwindcss/forms@0.5.9': + resolution: {integrity: sha512-tM4XVr2+UVTxXJzey9Twx48c1gcxFStqn1pQz0tRsX8o3DvxhN5oY5pvyAbUx7VTaZxpej4Zzvc6h+1RJBzpIg==} peerDependencies: - tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1' + tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20' - '@tanstack/react-table@8.16.0': - resolution: {integrity: sha512-rKRjnt8ostqN2fercRVOIH/dq7MAmOENCMvVlKx6P9Iokhh6woBGnIZEkqsY/vEJf1jN3TqLOb34xQGLVRuhAg==} + '@tanstack/react-table@8.20.5': + resolution: {integrity: sha512-WEHopKw3znbUZ61s9i0+i9g8drmDo6asTWbrQh8Us63DAk/M0FkmIqERew6P71HI75ksZ2Pxyuf4vvKh9rAkiA==} engines: {node: '>=12'} peerDependencies: react: '>=16.8' react-dom: '>=16.8' + '@tanstack/react-virtual@3.10.8': + resolution: {integrity: sha512-VbzbVGSsZlQktyLrP5nxE+vE1ZR+U0NFAWPbJLoG2+DKPwd2D7dVICTVIIaYlJqX1ZCEnYDbaOpmMwbsyhBoIA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + '@tanstack/react-virtual@3.5.0': resolution: {integrity: sha512-rtvo7KwuIvqK9zb0VZ5IL7fiJAEnG+0EiFZz8FUOs+2mhGqdGmjKIaT1XU7Zq0eFqL0jonLlhbayJI/J2SA/Bw==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - '@tanstack/table-core@8.16.0': - resolution: {integrity: sha512-dCG8vQGk4js5v88/k83tTedWOwjGnIyONrKpHpfmSJB8jwFHl8GSu1sBBxbtACVAPtAQgwNxl0rw1d3RqRM1Tg==} + '@tanstack/table-core@8.20.5': + resolution: {integrity: sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg==} engines: {node: '>=12'} + '@tanstack/virtual-core@3.10.8': + resolution: {integrity: sha512-PBu00mtt95jbKFi6Llk9aik8bnR3tR/oQP1o3TSi+iG//+Q2RTIzCEgKkHG8BB86kxMNW6O8wku+Lmi+QFR6jA==} + '@tanstack/virtual-core@3.5.0': resolution: {integrity: sha512-KnPRCkQTyqhanNC0K63GBG3wA8I+D1fQuVnAvcBF8f13akOKeQp1gSbu6f77zCxhEk727iV5oQnbHLYzHrECLg==} - '@tremor/react@3.16.2': - resolution: {integrity: sha512-Isdc+Sf4WHlnrAAO8Hk/nK84HiXzCZvb6ZFRHrzOkF+APm6nDhvKPRorXcXZ2BKSS5T5L0QVsid5fIxly8kRdA==} + '@tootallnate/once@1.1.2': + resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==} + engines: {node: '>= 6'} + + '@tremor/react@3.18.3': + resolution: {integrity: sha512-7QyGE2W9f2FpwH24TKy3/mqBgLl4sHZeQcXP3rxXZ8W2AUq7AVaG1+vIT3xXxISrkh7zknjWlZsuhoF8NWNVDw==} peerDependencies: react: ^18.0.0 react-dom: '>=16.6.0' + '@tsconfig/node10@1.0.11': + resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + '@tybys/wasm-util@0.8.3': resolution: {integrity: sha512-Z96T/L6dUFFxgFJ+pQtkPpne9q7i6kIPYCFnQBHSgSPV9idTsKfIhCss0h5iM9irweZCatkrdeP8yi5uM1eX6Q==} - '@types/better-sqlite3@7.6.10': - resolution: {integrity: sha512-TZBjD+yOsyrUJGmcUj6OS3JADk3+UZcNv3NOBqGkM09bZdi28fNZw8ODqbMOLfKCu7RYCO62/ldq1iHbzxqoPw==} + '@types/better-sqlite3@7.6.11': + resolution: {integrity: sha512-i8KcD3PgGtGBLl3+mMYA8PdKkButvPyARxA7IQAd6qeslht13qxb1zzO8dRCtE7U3IoJS782zDBAeoKiM695kg==} '@types/d3-array@3.2.1': resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==} @@ -1350,17 +1511,50 @@ packages: '@types/d3-timer@3.0.2': resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} - '@types/node@20.12.11': - resolution: {integrity: sha512-vDg9PZ/zi+Nqp6boSOT7plNuthRugEKixDv5sFTIpkE89MmNtEArAShI4mxuX2+UrLEe9pxC1vm2cjm9YlWbJw==} + '@types/lodash@4.17.13': + resolution: {integrity: sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==} + + '@types/luxon@3.4.2': + resolution: {integrity: sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==} + + '@types/node@20.16.11': + resolution: {integrity: sha512-y+cTCACu92FyA5fgQSAI8A1H429g7aSK2HsO7K4XYUWc4dY5IUz55JSDIYT6/VsOLfGy8vmvQYC2hfb0iF16Uw==} '@types/prop-types@15.7.12': resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} - '@types/react-dom@18.3.0': - resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} + '@types/react-dom@18.3.1': + resolution: {integrity: sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==} - '@types/react@18.3.1': - resolution: {integrity: sha512-V0kuGBX3+prX+DQ/7r2qsv1NsdfnCLnTgnRJ1pYnxykBhGMz+qj+box5lq7XsO5mtZsBqpjwwTu/7wszPfMBcw==} + '@types/react@18.3.11': + resolution: {integrity: sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ==} + + '@types/ws@8.5.12': + resolution: {integrity: sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==} + + abbrev@1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} + + acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + agentkeepalive@4.5.0: + resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} + engines: {node: '>= 8.0.0'} + + aggregate-error@3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} @@ -1385,8 +1579,19 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} - arctic@1.8.1: - resolution: {integrity: sha512-YmTcSfk8+NDSZt7qWYBX4HrikHZA3EQgmfFYlS4BwtYzPrji2VjAFrCFMElnjp72zmx3J3mz1ldjaXvjxmiW4Q==} + aproba@2.0.0: + resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} + + arctic@1.9.2: + resolution: {integrity: sha512-VTnGpYx+ypboJdNrWnK17WeD7zN/xSCHnpecd5QYsBfVZde/5i+7DJ1wrf/ioSDMiEjagXmyNWAE3V2C9f1hNg==} + + are-we-there-yet@3.0.1: + resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} @@ -1414,6 +1619,9 @@ packages: bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} @@ -1431,6 +1639,10 @@ packages: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} + cacache@15.3.0: + resolution: {integrity: sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==} + engines: {node: '>= 10'} + camelcase-css@2.0.1: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} @@ -1445,9 +1657,17 @@ packages: chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + class-variance-authority@0.7.0: resolution: {integrity: sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==} + clean-stack@2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + cli-color@2.0.4: resolution: {integrity: sha512-zlnpg0jNcibNrO7GG9IeHH7maWFeCz+Ja1wx/7tZNU5ASSSSZ+/qZciM0/LHCYxSdqv5h2sdbQ/PXYdOuetXvA==} engines: {node: '>=0.10'} @@ -1470,6 +1690,10 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -1478,6 +1702,15 @@ packages: resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} engines: {node: ^12.20.0 || >=14} + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + console-control-strings@1.1.0: + resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -1538,9 +1771,15 @@ packages: resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==} engines: {node: '>=0.12'} - date-fns@2.30.0: - resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} - engines: {node: '>=0.11'} + data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + + date-fns@3.6.0: + resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==} + + date-fns@4.1.0: + resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} debug@4.3.4: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} @@ -1562,6 +1801,13 @@ packages: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} + delegates@1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + + detect-libc@2.0.2: + resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} + engines: {node: '>=8'} + detect-libc@2.0.3: resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} engines: {node: '>=8'} @@ -1572,6 +1818,10 @@ packages: didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + difflib@0.2.4: resolution: {integrity: sha512-9YVwmMb0wQHQNr5J9m6BSj6fk4pfGITGQOOs+D9Fl+INODWFOfvhIU1hNv6GgR1RBoC/9NJcwu77zShxV0kT7w==} @@ -1585,8 +1835,8 @@ packages: resolution: {integrity: sha512-vyJTp8+mC+G+5dfgsY+r3ckxlz+QMX40VjPQsZc5gxVAxLmi64TBoVkP54A/pRAXMXsbu2GMMBrZPxNv23waMg==} engines: {node: '>=0.4.0'} - drizzle-kit@0.21.1: - resolution: {integrity: sha512-Sp7OnCdROiE2ebMuHsAfrnRoHVGYCvErQxUh7/0l6R1caHssZu9oZu/hW9rLU19xnTK4/y3iSe3sL0Cc530wCg==} + drizzle-kit@0.21.4: + resolution: {integrity: sha512-Nxcc1ONJLRgbhmR+azxjNF9Ly9privNLEIgW53c92whb4xp8jZLH1kMCh/54ci1mTMuYxPdOukqLwJ8wRudNwA==} hasBin: true drizzle-orm@0.30.10: @@ -1678,13 +1928,23 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + encoding@0.1.13: + resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} + end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + env-paths@3.0.0: resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + err-code@2.0.3: + resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} + es5-ext@0.10.64: resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==} engines: {node: '>=0.10'} @@ -1742,6 +2002,10 @@ packages: fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} @@ -1753,9 +2017,17 @@ packages: resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} engines: {node: '>=14'} + formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + fs-monkey@1.0.6: resolution: {integrity: sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==} @@ -1770,6 +2042,11 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + gauge@4.0.4: + resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + get-nonce@1.0.1: resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} engines: {node: '>=6'} @@ -1793,9 +2070,14 @@ packages: engines: {node: '>=16 || 14 >=14.17'} hasBin: true + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} engines: {node: '>=12'} + deprecated: Glob versions prior to v9 are no longer supported graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -1803,6 +2085,9 @@ packages: hanji@0.0.5: resolution: {integrity: sha512-Abxw1Lq+TnYiL4BueXqMau222fPSPMFtya8HdpWsz/xVAhifXou71mPh/kY2+08RgFcVccjG3uZHs6K5HAe3zw==} + has-unicode@2.0.1: + resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} @@ -1810,11 +2095,41 @@ packages: heap@0.2.7: resolution: {integrity: sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==} + http-cache-semantics@4.1.1: + resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} + + http-proxy-agent@4.0.1: + resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==} + engines: {node: '>= 6'} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + infer-owner@1.0.4: + resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==} + inflight@1.0.6: resolution: {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. inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -1829,6 +2144,10 @@ packages: invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + ip-address@9.0.5: + resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} + engines: {node: '>= 12'} + is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} @@ -1848,6 +2167,9 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-lambda@1.0.1: + resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -1866,13 +2188,23 @@ packages: resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==} hasBin: true + js-base64@3.7.7: + resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + jsbn@1.1.0: + resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} + json-diff@0.9.0: resolution: {integrity: sha512-cVnggDrVkAAA3OvFfHpFEhOnmcsUpleEKq4d4O8sQWWSH40MBrWstKigVB1kGrgLWzuom+7rRdaCsnBD6VyObQ==} hasBin: true + libsql@0.4.6: + resolution: {integrity: sha512-F5M+ltteK6dCcpjMahrkgT96uFJvVI8aQ4r9f2AzHQjC7BkAYtvfMSTWGvRBezRgMUIU2h1Sy0pF9nOGOD5iyA==} + os: [darwin, linux, win32] + lilconfig@2.1.0: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} @@ -1898,17 +2230,32 @@ packages: resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} engines: {node: 14 || >=16.14} + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + lru-queue@0.1.0: resolution: {integrity: sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==} - lucia@3.2.0: - resolution: {integrity: sha512-eXMxXwk6hqtjRTj4W/x3EnTUtAztLPm0p2N2TEBMDEbakDLXiYnDQ9z/qahjPdPdhPguQc+vwO0/88zIWxlpuw==} + lucia@3.2.1: + resolution: {integrity: sha512-yIVBS/wU3R+8cLClh2ksBNxqHkAd0VUcjvib53azkSdRT1cPkKuFglkxFsghuspaioX+AHhmIECEkdOz/vIJsQ==} lucide-react@0.378.0: resolution: {integrity: sha512-u6EPU8juLUk9ytRcyapkWI18epAv3RU+6+TC23ivjR0e+glWKBobFeSgRwOIJihzktILQuy6E0E80P2jVTDR5g==} peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 + luxon@3.5.0: + resolution: {integrity: sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==} + engines: {node: '>=12'} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + make-fetch-happen@9.1.0: + resolution: {integrity: sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==} + engines: {node: '>= 10'} + memfs-browser@3.5.10302: resolution: {integrity: sha512-JJTc/nh3ig05O0gBBGZjTCPOyydaTxNF0uHYBrcc1gHNnO+KIHIvo0Y1FKCJsaei6FCl8C6xfQomXqu+cuzkIw==} @@ -1935,6 +2282,9 @@ packages: resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} hasBin: true + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@5.1.6: resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} engines: {node: '>=10'} @@ -1946,13 +2296,50 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass-collect@1.0.2: + resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} + engines: {node: '>= 8'} + + minipass-fetch@1.4.1: + resolution: {integrity: sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==} + engines: {node: '>=8'} + + minipass-flush@1.0.5: + resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} + engines: {node: '>= 8'} + + minipass-pipeline@1.2.4: + resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} + engines: {node: '>=8'} + + minipass-sized@1.0.3: + resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} + engines: {node: '>=8'} + + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + minipass@7.1.1: resolution: {integrity: sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==} engines: {node: '>=16 || 14 >=14.17'} + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} @@ -1967,6 +2354,10 @@ packages: napi-build-utils@1.0.2: resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} + negotiator@0.6.4: + resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} + engines: {node: '>= 0.6'} + next-themes@0.3.0: resolution: {integrity: sha512-/QHIrsYpd6Kfk7xakK4svpDI5mmXP0gfvCoJdGpZQ2TOrQZmsW0QxjaiLn8wbIKjtm4BTSqLoix4lxYYOnLJ/w==} peerDependencies: @@ -1998,10 +2389,36 @@ packages: resolution: {integrity: sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g==} engines: {node: '>=10'} + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + + node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + node-gyp@8.4.1: + resolution: {integrity: sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==} + engines: {node: '>= 10.12.0'} + hasBin: true + + nopt@5.0.0: + resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} + engines: {node: '>=6'} + hasBin: true + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} + npmlog@6.0.2: + resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -2016,6 +2433,17 @@ packages: oslo@1.2.0: resolution: {integrity: sha512-OoFX6rDsNcOQVAD2gQD/z03u4vEjWZLzJtwkmgfRF+KpQUXwdgEXErD7zNhyowmHwHefP+PM9Pw13pgpHMRlzw==} + oslo@1.2.1: + resolution: {integrity: sha512-HfIhB5ruTdQv0XX2XlncWQiJ5SIHZ7NHZhVyHth0CSZ/xzge00etRyYy/3wp/Dsu+PkxMC+6+B2lS/GcKoewkA==} + + p-map@4.0.0: + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + engines: {node: '>=10'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -2030,6 +2458,9 @@ packages: picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + picocolors@1.1.0: + resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} + picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -2083,8 +2514,8 @@ packages: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} - postcss@8.4.38: - resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} + postcss@8.4.47: + resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} engines: {node: ^10 || ^12 || >=14} prebuild-install@7.1.2: @@ -2092,6 +2523,21 @@ packages: engines: {node: '>=10'} hasBin: true + promise-inflight@1.0.1: + resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} + peerDependencies: + bluebird: '*' + peerDependenciesMeta: + bluebird: + optional: true + + promise-limit@2.7.0: + resolution: {integrity: sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==} + + promise-retry@2.0.1: + resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} + engines: {node: '>=10'} + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -2116,11 +2562,11 @@ packages: peerDependencies: react: ^18.3.1 - react-hook-form@7.51.4: - resolution: {integrity: sha512-V14i8SEkh+V1gs6YtD0hdHYnoL4tp/HX/A45wWQN15CYr9bFRmmRdYStSO5L65lCCZRF+kYiSKhm9alqbcdiVA==} - engines: {node: '>=12.22.0'} + react-hook-form@7.53.0: + resolution: {integrity: sha512-M1n3HhqCww6S2hxLxciEXy2oISPnAzxY7gvwVPrtlczTM/1dDadXgUxDpHMrMTblDOcm/AXtXxHwZ3jpg1mqKQ==} + engines: {node: '>=18.0.0'} peerDependencies: - react: ^16.8.0 || ^17 || ^18 + react: ^16.8.0 || ^17 || ^18 || ^19 react-if@4.1.5: resolution: {integrity: sha512-Uk+Ub2gC83PAakuU4+7iLdTEP4LPi2ihNEPCtz/vr8SLGbzkMApbpYbkDZ5z9zYXurd0gg+EK/bpOLFFC1r1eQ==} @@ -2131,6 +2577,9 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-remove-scroll-bar@2.3.6: resolution: {integrity: sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==} engines: {node: '>=10'} @@ -2141,8 +2590,8 @@ packages: '@types/react': optional: true - react-remove-scroll@2.5.5: - resolution: {integrity: sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==} + react-remove-scroll@2.6.0: + resolution: {integrity: sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==} engines: {node: '>=10'} peerDependencies: '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -2178,8 +2627,8 @@ packages: react: '>=16.6.0' react-dom: '>=16.6.0' - react-transition-state@2.1.1: - resolution: {integrity: sha512-kQx5g1FVu9knoz1T1WkapjUgFz08qQ/g1OmuWGi3/AoEFfS0kStxrPlZx81urjCXdz2d+1DqLpU6TyLW/Ro04Q==} + react-transition-state@2.1.2: + resolution: {integrity: sha512-RkDYBkj1V1ZqBA5AwQPrMt2Uagwsx6b//GVJdRDhs/t0o66w2nhQiyHyFGQEI60mgtbaIdLm8yhBRCvhA+FxEg==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' @@ -2202,8 +2651,8 @@ packages: recharts-scale@0.4.5: resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==} - recharts@2.12.7: - resolution: {integrity: sha512-hlLJMhPQfv4/3NBSAyq3gzGg4h2v69RJh6KU7b3pXYNNAELs9kEoXOjbkxdXpALqKBoVmVptGfLpxdaVYqjmXQ==} + recharts@2.13.3: + resolution: {integrity: sha512-YDZ9dOfK9t3ycwxgKbrnDlRC4BHdjlY73fet3a0C1+qGMjXVZe6+VXmpOIIhzkje5MMEL8AN4hLIe4AMskBzlA==} engines: {node: '>=14'} peerDependencies: react: ^16.0.0 || ^17.0.0 || ^18.0.0 @@ -2222,16 +2671,28 @@ packages: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} @@ -2240,6 +2701,9 @@ packages: engines: {node: '>=10'} hasBin: true + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -2248,6 +2712,9 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -2261,8 +2728,20 @@ packages: sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - sonner@1.4.41: - resolution: {integrity: sha512-uG511ggnnsw6gcn/X+YKkWPo5ep9il9wYi3QJxHsYe7yTZ4+cOd1wuodOUmOpFuXL+/RE3R04LczdNCDygTDgQ==} + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + + socks-proxy-agent@6.2.1: + resolution: {integrity: sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==} + engines: {node: '>= 10'} + + socks@2.8.3: + resolution: {integrity: sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + + sonner@1.5.0: + resolution: {integrity: sha512-FBjhG/gnnbN6FY0jaNnqZOMmB73R+5IiyYAw8yBj7L54ER7HB3fOSE5OFiQiE2iXWxeXKvg6fIP4LtVppHEdJA==} peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 @@ -2271,6 +2750,10 @@ packages: resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} @@ -2278,6 +2761,19 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + sprintf-js@1.1.3: + resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + + sqlite3@5.1.7: + resolution: {integrity: sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==} + + sqlite@5.1.1: + resolution: {integrity: sha512-oBkezXa2hnkfuJwUo44Hl9hS3er+YFtueifoajrgidvqsJRQFpc5fKoAkAor1O5ZnLoa28GBScfHXs8j0K358Q==} + + ssri@8.0.1: + resolution: {integrity: sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==} + engines: {node: '>= 8'} + streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} @@ -2335,19 +2831,16 @@ packages: tabbable@6.2.0: resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} - tailwind-merge@1.14.0: - resolution: {integrity: sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==} - - tailwind-merge@2.3.0: - resolution: {integrity: sha512-vkYrLpIP+lgR0tQCG6AP7zZXCTLc1Lnv/CCRT3BqJ9CZ3ui2++GPaGb1x/ILsINIMSYqqvrpqjUFsMNLlW99EA==} + tailwind-merge@2.5.3: + resolution: {integrity: sha512-d9ZolCAIzom1nf/5p4LdD5zvjmgSxY0BGgdSvmXIoMYAiPdAW/dSpP7joCDYFY7r/HkEa2qmPtkgsu0xjQeQtw==} tailwindcss-animate@1.0.7: resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} peerDependencies: tailwindcss: '>=3.0.0 || insiders' - tailwindcss@3.4.3: - resolution: {integrity: sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==} + tailwindcss@3.4.13: + resolution: {integrity: sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==} engines: {node: '>=14.0.0'} hasBin: true @@ -2358,6 +2851,10 @@ packages: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} engines: {node: '>=0.8'} @@ -2378,6 +2875,20 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} @@ -2387,13 +2898,19 @@ packages: type@2.7.2: resolution: {integrity: sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==} - typescript@5.4.5: - resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} + typescript@5.6.3: + resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} engines: {node: '>=14.17'} hasBin: true - undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + + unique-filename@1.1.1: + resolution: {integrity: sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==} + + unique-slug@2.0.2: + resolution: {integrity: sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==} use-callback-ref@1.3.2: resolution: {integrity: sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==} @@ -2405,11 +2922,11 @@ packages: '@types/react': optional: true - use-debounce@10.0.0: - resolution: {integrity: sha512-XRjvlvCB46bah9IBXVnq/ACP2lxqXyZj0D9hj4K5OzNroMDpTEBg8Anuh1/UfRTRs7pLhQ+RiNxxwZu9+MVl1A==} + use-debounce@10.0.3: + resolution: {integrity: sha512-DxQSI9ZKso689WM1mjgGU3ozcxU1TJElBJ3X6S4SMzMNcm2lVH0AHmyXB+K7ewjz2BSUKJTDqTcwtSMRfB89dg==} engines: {node: '>= 16.0.0'} peerDependencies: - react: '>=16.8.0' + react: '*' use-sidecar@1.1.2: resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} @@ -2429,14 +2946,28 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + uuid@11.0.2: + resolution: {integrity: sha512-14FfcOJmqdjbBPdDjFQyk/SdT4NySW4eM0zcG+HqbHP5jzuH56xO3J1DGhgs/cEMCfwYi3HQI1gnTO62iaG+tQ==} + hasBin: true + + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + victory-vendor@36.9.2: resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==} + web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} hasBin: true + wide-align@1.1.5: + resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + wordwrap@1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} @@ -2451,11 +2982,30 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yaml@2.4.2: resolution: {integrity: sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==} engines: {node: '>= 14'} hasBin: true + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + zod@3.23.8: resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} @@ -2467,41 +3017,45 @@ snapshots: dependencies: regenerator-runtime: 0.14.1 - '@biomejs/biome@1.7.3': + '@biomejs/biome@1.9.3': optionalDependencies: - '@biomejs/cli-darwin-arm64': 1.7.3 - '@biomejs/cli-darwin-x64': 1.7.3 - '@biomejs/cli-linux-arm64': 1.7.3 - '@biomejs/cli-linux-arm64-musl': 1.7.3 - '@biomejs/cli-linux-x64': 1.7.3 - '@biomejs/cli-linux-x64-musl': 1.7.3 - '@biomejs/cli-win32-arm64': 1.7.3 - '@biomejs/cli-win32-x64': 1.7.3 + '@biomejs/cli-darwin-arm64': 1.9.3 + '@biomejs/cli-darwin-x64': 1.9.3 + '@biomejs/cli-linux-arm64': 1.9.3 + '@biomejs/cli-linux-arm64-musl': 1.9.3 + '@biomejs/cli-linux-x64': 1.9.3 + '@biomejs/cli-linux-x64-musl': 1.9.3 + '@biomejs/cli-win32-arm64': 1.9.3 + '@biomejs/cli-win32-x64': 1.9.3 - '@biomejs/cli-darwin-arm64@1.7.3': + '@biomejs/cli-darwin-arm64@1.9.3': optional: true - '@biomejs/cli-darwin-x64@1.7.3': + '@biomejs/cli-darwin-x64@1.9.3': optional: true - '@biomejs/cli-linux-arm64-musl@1.7.3': + '@biomejs/cli-linux-arm64-musl@1.9.3': optional: true - '@biomejs/cli-linux-arm64@1.7.3': + '@biomejs/cli-linux-arm64@1.9.3': optional: true - '@biomejs/cli-linux-x64-musl@1.7.3': + '@biomejs/cli-linux-x64-musl@1.9.3': optional: true - '@biomejs/cli-linux-x64@1.7.3': + '@biomejs/cli-linux-x64@1.9.3': optional: true - '@biomejs/cli-win32-arm64@1.7.3': + '@biomejs/cli-win32-arm64@1.9.3': optional: true - '@biomejs/cli-win32-x64@1.7.3': + '@biomejs/cli-win32-x64@1.9.3': optional: true + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + '@emnapi/core@0.45.0': dependencies: tslib: 2.6.2 @@ -2657,14 +3211,16 @@ snapshots: '@esbuild/win32-x64@0.19.12': optional: true + '@faker-js/faker@9.2.0': {} + '@floating-ui/core@1.6.1': dependencies: - '@floating-ui/utils': 0.2.2 + '@floating-ui/utils': 0.2.8 '@floating-ui/dom@1.6.5': dependencies: '@floating-ui/core': 1.6.1 - '@floating-ui/utils': 0.2.2 + '@floating-ui/utils': 0.2.8 '@floating-ui/react-dom@1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: @@ -2678,6 +3234,12 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + '@floating-ui/react-dom@2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/dom': 1.6.5 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + '@floating-ui/react@0.19.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@floating-ui/react-dom': 1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -2686,15 +3248,18 @@ snapshots: react-dom: 18.3.1(react@18.3.1) tabbable: 6.2.0 - '@floating-ui/react@0.26.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@floating-ui/react@0.26.24(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@floating-ui/react-dom': 2.0.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@floating-ui/utils': 0.2.2 + '@floating-ui/react-dom': 2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@floating-ui/utils': 0.2.8 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) tabbable: 6.2.0 - '@floating-ui/utils@0.2.2': {} + '@floating-ui/utils@0.2.8': {} + + '@gar/promisify@1.1.3': + optional: true '@headlessui/react@1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: @@ -2703,22 +3268,22 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@headlessui/react@2.0.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@headlessui/react@2.1.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@floating-ui/react': 0.26.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@react-aria/focus': 3.17.0(react@18.3.1) - '@react-aria/interactions': 3.21.2(react@18.3.1) - '@tanstack/react-virtual': 3.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@floating-ui/react': 0.26.24(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/focus': 3.18.3(react@18.3.1) + '@react-aria/interactions': 3.22.3(react@18.3.1) + '@tanstack/react-virtual': 3.10.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@headlessui/tailwindcss@0.2.0(tailwindcss@3.4.3)': + '@headlessui/tailwindcss@0.2.1(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.3)))': dependencies: - tailwindcss: 3.4.3 + tailwindcss: 3.4.13(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.3)) - '@hookform/resolvers@3.3.4(react-hook-form@7.51.4(react@18.3.1))': + '@hookform/resolvers@3.9.0(react-hook-form@7.53.0(react@18.3.1))': dependencies: - react-hook-form: 7.51.4(react@18.3.1) + react-hook-form: 7.53.0(react@18.3.1) '@isaacs/cliui@8.0.2': dependencies: @@ -2746,9 +3311,73 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.4.15 - '@lucia-auth/adapter-drizzle@1.0.7(lucia@3.2.0)': + '@jridgewell/trace-mapping@0.3.9': dependencies: - lucia: 3.2.0 + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + + '@libsql/client@0.14.0': + dependencies: + '@libsql/core': 0.14.0 + '@libsql/hrana-client': 0.7.0 + js-base64: 3.7.7 + libsql: 0.4.6 + promise-limit: 2.7.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@libsql/core@0.14.0': + dependencies: + js-base64: 3.7.7 + + '@libsql/darwin-arm64@0.4.6': + optional: true + + '@libsql/darwin-x64@0.4.6': + optional: true + + '@libsql/hrana-client@0.7.0': + dependencies: + '@libsql/isomorphic-fetch': 0.3.1 + '@libsql/isomorphic-ws': 0.1.5 + js-base64: 3.7.7 + node-fetch: 3.3.2 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@libsql/isomorphic-fetch@0.3.1': {} + + '@libsql/isomorphic-ws@0.1.5': + dependencies: + '@types/ws': 8.5.12 + ws: 8.18.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@libsql/linux-arm64-gnu@0.4.6': + optional: true + + '@libsql/linux-arm64-musl@0.4.6': + optional: true + + '@libsql/linux-x64-gnu@0.4.6': + optional: true + + '@libsql/linux-x64-musl@0.4.6': + optional: true + + '@libsql/win32-x64-msvc@0.4.6': + optional: true + + '@lucia-auth/adapter-drizzle@1.1.0(drizzle-orm@0.30.10(@libsql/client@0.14.0)(@types/better-sqlite3@7.6.11)(@types/react@18.3.11)(better-sqlite3@9.6.0)(react@18.3.1)(sqlite3@5.1.7))(lucia@3.2.1)': + dependencies: + drizzle-orm: 0.30.10(@libsql/client@0.14.0)(@types/better-sqlite3@7.6.11)(@types/react@18.3.11)(better-sqlite3@9.6.0)(react@18.3.1)(sqlite3@5.1.7) + lucia: 3.2.1 + + '@neon-rs/load@0.0.4': {} '@next/env@14.2.3': {} @@ -2919,498 +3548,490 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.17.1 + '@npmcli/fs@1.1.1': + dependencies: + '@gar/promisify': 1.1.3 + semver: 7.6.2 + optional: true + + '@npmcli/move-file@1.1.2': + dependencies: + mkdirp: 1.0.4 + rimraf: 3.0.2 + optional: true + + '@oslojs/asn1@1.0.0': + dependencies: + '@oslojs/binary': 1.0.0 + + '@oslojs/binary@1.0.0': {} + + '@oslojs/crypto@1.0.1': + dependencies: + '@oslojs/asn1': 1.0.0 + '@oslojs/binary': 1.0.0 + + '@oslojs/encoding@1.1.0': {} + '@pkgjs/parseargs@0.11.0': optional: true - '@radix-ui/number@1.0.1': - dependencies: - '@babel/runtime': 7.24.5 + '@radix-ui/number@1.1.0': {} - '@radix-ui/primitive@1.0.1': - dependencies: - '@babel/runtime': 7.24.5 + '@radix-ui/primitive@1.1.0': {} - '@radix-ui/react-alert-dialog@1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-alert-dialog@1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-context': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-dialog': 1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.0.2(@types/react@18.3.1)(react@18.3.1) + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-dialog': 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.1 - '@types/react-dom': 18.3.0 + '@types/react': 18.3.11 + '@types/react-dom': 18.3.1 - '@radix-ui/react-arrow@1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-arrow@1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.1 - '@types/react-dom': 18.3.0 + '@types/react': 18.3.11 + '@types/react-dom': 18.3.1 - '@radix-ui/react-avatar@1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-avatar@1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 - '@radix-ui/react-context': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.3.1)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.1 - '@types/react-dom': 18.3.0 + '@types/react': 18.3.11 + '@types/react-dom': 18.3.1 - '@radix-ui/react-collection@1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-collection@1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-context': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.0.2(@types/react@18.3.1)(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-context': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.1 - '@types/react-dom': 18.3.0 + '@types/react': 18.3.11 + '@types/react-dom': 18.3.1 - '@radix-ui/react-compose-refs@1.0.1(@types/react@18.3.1)(react@18.3.1)': + '@radix-ui/react-compose-refs@1.1.0(@types/react@18.3.11)(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 react: 18.3.1 optionalDependencies: - '@types/react': 18.3.1 + '@types/react': 18.3.11 - '@radix-ui/react-context@1.0.1(@types/react@18.3.1)(react@18.3.1)': + '@radix-ui/react-context@1.1.0(@types/react@18.3.11)(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 react: 18.3.1 optionalDependencies: - '@types/react': 18.3.1 + '@types/react': 18.3.11 - '@radix-ui/react-dialog@1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-context@1.1.1(@types/react@18.3.11)(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-context': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-focus-guards': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-focus-scope': 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-id': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-portal': 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.0.2(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.3.1)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.11 + + '@radix-ui/react-dialog@1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.11)(react@18.3.1) aria-hidden: 1.2.4 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.5.5(@types/react@18.3.1)(react@18.3.1) + react-remove-scroll: 2.6.0(@types/react@18.3.11)(react@18.3.1) optionalDependencies: - '@types/react': 18.3.1 - '@types/react-dom': 18.3.0 + '@types/react': 18.3.11 + '@types/react-dom': 18.3.1 - '@radix-ui/react-direction@1.0.1(@types/react@18.3.1)(react@18.3.1)': + '@radix-ui/react-direction@1.1.0(@types/react@18.3.11)(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 react: 18.3.1 optionalDependencies: - '@types/react': 18.3.1 + '@types/react': 18.3.11 - '@radix-ui/react-dismissable-layer@1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-dismissable-layer@1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-use-escape-keydown': 1.0.3(@types/react@18.3.1)(react@18.3.1) + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.1 - '@types/react-dom': 18.3.0 + '@types/react': 18.3.11 + '@types/react-dom': 18.3.1 - '@radix-ui/react-dropdown-menu@2.0.6(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-dropdown-menu@2.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-context': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-id': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-menu': 2.0.6(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.3.1)(react@18.3.1) + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-menu': 2.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.1 - '@types/react-dom': 18.3.0 + '@types/react': 18.3.11 + '@types/react-dom': 18.3.1 - '@radix-ui/react-focus-guards@1.0.1(@types/react@18.3.1)(react@18.3.1)': + '@radix-ui/react-focus-guards@1.1.1(@types/react@18.3.11)(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 react: 18.3.1 optionalDependencies: - '@types/react': 18.3.1 + '@types/react': 18.3.11 - '@radix-ui/react-focus-scope@1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-focus-scope@1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.3.1)(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.1 - '@types/react-dom': 18.3.0 + '@types/react': 18.3.11 + '@types/react-dom': 18.3.1 - '@radix-ui/react-hover-card@1.0.7(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-hover-card@1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-context': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-popper': 1.1.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-portal': 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.3.1)(react@18.3.1) + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-popper': 1.2.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.1 - '@types/react-dom': 18.3.0 + '@types/react': 18.3.11 + '@types/react-dom': 18.3.1 '@radix-ui/react-icons@1.3.0(react@18.3.1)': dependencies: react: 18.3.1 - '@radix-ui/react-id@1.0.1(@types/react@18.3.1)(react@18.3.1)': + '@radix-ui/react-id@1.1.0(@types/react@18.3.11)(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 - '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.3.1)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 optionalDependencies: - '@types/react': 18.3.1 + '@types/react': 18.3.11 - '@radix-ui/react-label@2.0.2(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-label@2.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.1 - '@types/react-dom': 18.3.0 + '@types/react': 18.3.11 + '@types/react-dom': 18.3.1 - '@radix-ui/react-menu@2.0.6(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-menu@2.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-collection': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-context': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-direction': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-focus-guards': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-focus-scope': 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-id': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-popper': 1.1.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-portal': 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-roving-focus': 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.0.2(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.3.1)(react@18.3.1) + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-popper': 1.2.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.11)(react@18.3.1) aria-hidden: 1.2.4 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.5.5(@types/react@18.3.1)(react@18.3.1) + react-remove-scroll: 2.6.0(@types/react@18.3.11)(react@18.3.1) optionalDependencies: - '@types/react': 18.3.1 - '@types/react-dom': 18.3.0 + '@types/react': 18.3.11 + '@types/react-dom': 18.3.1 - '@radix-ui/react-popper@1.1.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-popper@1.2.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 '@floating-ui/react-dom': 2.0.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-arrow': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-context': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-use-rect': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-use-size': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/rect': 1.0.1 + '@radix-ui/react-arrow': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-context': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-rect': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/rect': 1.1.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.1 - '@types/react-dom': 18.3.0 + '@types/react': 18.3.11 + '@types/react-dom': 18.3.1 - '@radix-ui/react-portal@1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-portal@1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.1 - '@types/react-dom': 18.3.0 + '@types/react': 18.3.11 + '@types/react-dom': 18.3.1 - '@radix-ui/react-presence@1.0.1(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-presence@1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.3.1)(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.1 - '@types/react-dom': 18.3.0 + '@types/react': 18.3.11 + '@types/react-dom': 18.3.1 - '@radix-ui/react-primitive@1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-primitive@2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 - '@radix-ui/react-slot': 1.0.2(@types/react@18.3.1)(react@18.3.1) + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.1 - '@types/react-dom': 18.3.0 + '@types/react': 18.3.11 + '@types/react-dom': 18.3.1 - '@radix-ui/react-roving-focus@1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-roving-focus@1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-collection': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-context': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-direction': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-id': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.3.1)(react@18.3.1) + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-context': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.1 - '@types/react-dom': 18.3.0 + '@types/react': 18.3.11 + '@types/react-dom': 18.3.1 - '@radix-ui/react-scroll-area@1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-scroll-area@1.2.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 - '@radix-ui/number': 1.0.1 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-context': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-direction': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.3.1)(react@18.3.1) + '@radix-ui/number': 1.1.0 + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.1 - '@types/react-dom': 18.3.0 + '@types/react': 18.3.11 + '@types/react-dom': 18.3.1 - '@radix-ui/react-select@2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-select@2.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 - '@radix-ui/number': 1.0.1 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-collection': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-context': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-direction': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-focus-guards': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-focus-scope': 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-id': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-popper': 1.1.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-portal': 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.0.2(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-use-previous': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-visually-hidden': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/number': 1.1.0 + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-popper': 1.2.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) aria-hidden: 1.2.4 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.5.5(@types/react@18.3.1)(react@18.3.1) + react-remove-scroll: 2.6.0(@types/react@18.3.11)(react@18.3.1) optionalDependencies: - '@types/react': 18.3.1 - '@types/react-dom': 18.3.0 + '@types/react': 18.3.11 + '@types/react-dom': 18.3.1 - '@radix-ui/react-slot@1.0.2(@types/react@18.3.1)(react@18.3.1)': + '@radix-ui/react-slot@1.1.0(@types/react@18.3.11)(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.1)(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 optionalDependencies: - '@types/react': 18.3.1 + '@types/react': 18.3.11 - '@radix-ui/react-tabs@1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-tabs@1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-context': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-direction': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-id': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-roving-focus': 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.3.1)(react@18.3.1) + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-context': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.1 - '@types/react-dom': 18.3.0 + '@types/react': 18.3.11 + '@types/react-dom': 18.3.1 - '@radix-ui/react-toast@1.1.5(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-toast@1.2.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-collection': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-context': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-portal': 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.3.1)(react@18.3.1) - '@radix-ui/react-visually-hidden': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.1 - '@types/react-dom': 18.3.0 + '@types/react': 18.3.11 + '@types/react-dom': 18.3.1 - '@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.3.1)(react@18.3.1)': + '@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.11)(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 react: 18.3.1 optionalDependencies: - '@types/react': 18.3.1 + '@types/react': 18.3.11 - '@radix-ui/react-use-controllable-state@1.0.1(@types/react@18.3.1)(react@18.3.1)': + '@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.3.11)(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.3.1)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 optionalDependencies: - '@types/react': 18.3.1 + '@types/react': 18.3.11 - '@radix-ui/react-use-escape-keydown@1.0.3(@types/react@18.3.1)(react@18.3.1)': + '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@18.3.11)(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.3.1)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 optionalDependencies: - '@types/react': 18.3.1 + '@types/react': 18.3.11 - '@radix-ui/react-use-layout-effect@1.0.1(@types/react@18.3.1)(react@18.3.1)': + '@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.3.11)(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 react: 18.3.1 optionalDependencies: - '@types/react': 18.3.1 + '@types/react': 18.3.11 - '@radix-ui/react-use-previous@1.0.1(@types/react@18.3.1)(react@18.3.1)': + '@radix-ui/react-use-previous@1.1.0(@types/react@18.3.11)(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 react: 18.3.1 optionalDependencies: - '@types/react': 18.3.1 + '@types/react': 18.3.11 - '@radix-ui/react-use-rect@1.0.1(@types/react@18.3.1)(react@18.3.1)': + '@radix-ui/react-use-rect@1.1.0(@types/react@18.3.11)(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 - '@radix-ui/rect': 1.0.1 + '@radix-ui/rect': 1.1.0 react: 18.3.1 optionalDependencies: - '@types/react': 18.3.1 + '@types/react': 18.3.11 - '@radix-ui/react-use-size@1.0.1(@types/react@18.3.1)(react@18.3.1)': + '@radix-ui/react-use-size@1.1.0(@types/react@18.3.11)(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 - '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.3.1)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 optionalDependencies: - '@types/react': 18.3.1 + '@types/react': 18.3.11 - '@radix-ui/react-visually-hidden@1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-visually-hidden@1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.5 - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.1 - '@types/react-dom': 18.3.0 + '@types/react': 18.3.11 + '@types/react-dom': 18.3.1 - '@radix-ui/rect@1.0.1': - dependencies: - '@babel/runtime': 7.24.5 + '@radix-ui/rect@1.1.0': {} - '@react-aria/focus@3.17.0(react@18.3.1)': + '@react-aria/focus@3.18.3(react@18.3.1)': dependencies: - '@react-aria/interactions': 3.21.2(react@18.3.1) - '@react-aria/utils': 3.24.0(react@18.3.1) - '@react-types/shared': 3.23.0(react@18.3.1) + '@react-aria/interactions': 3.22.3(react@18.3.1) + '@react-aria/utils': 3.25.3(react@18.3.1) + '@react-types/shared': 3.25.0(react@18.3.1) '@swc/helpers': 0.5.5 clsx: 2.1.1 react: 18.3.1 - '@react-aria/interactions@3.21.2(react@18.3.1)': + '@react-aria/interactions@3.22.3(react@18.3.1)': dependencies: - '@react-aria/ssr': 3.9.3(react@18.3.1) - '@react-aria/utils': 3.24.0(react@18.3.1) - '@react-types/shared': 3.23.0(react@18.3.1) + '@react-aria/ssr': 3.9.6(react@18.3.1) + '@react-aria/utils': 3.25.3(react@18.3.1) + '@react-types/shared': 3.25.0(react@18.3.1) '@swc/helpers': 0.5.5 react: 18.3.1 - '@react-aria/ssr@3.9.3(react@18.3.1)': + '@react-aria/ssr@3.9.6(react@18.3.1)': dependencies: '@swc/helpers': 0.5.5 react: 18.3.1 - '@react-aria/utils@3.24.0(react@18.3.1)': + '@react-aria/utils@3.25.3(react@18.3.1)': dependencies: - '@react-aria/ssr': 3.9.3(react@18.3.1) - '@react-stately/utils': 3.10.0(react@18.3.1) - '@react-types/shared': 3.23.0(react@18.3.1) + '@react-aria/ssr': 3.9.6(react@18.3.1) + '@react-stately/utils': 3.10.4(react@18.3.1) + '@react-types/shared': 3.25.0(react@18.3.1) '@swc/helpers': 0.5.5 clsx: 2.1.1 react: 18.3.1 - '@react-stately/utils@3.10.0(react@18.3.1)': + '@react-stately/utils@3.10.4(react@18.3.1)': dependencies: '@swc/helpers': 0.5.5 react: 18.3.1 - '@react-types/shared@3.23.0(react@18.3.1)': + '@react-types/shared@3.25.0(react@18.3.1)': dependencies: react: 18.3.1 - '@remixicon/react@4.2.0(react@18.3.1)': + '@remixicon/react@4.3.0(react@18.3.1)': dependencies: react: 18.3.1 @@ -3421,14 +4042,20 @@ snapshots: '@swc/counter': 0.1.3 tslib: 2.6.2 - '@tailwindcss/forms@0.5.7(tailwindcss@3.4.3)': + '@tailwindcss/forms@0.5.9(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.3)))': dependencies: mini-svg-data-uri: 1.4.4 - tailwindcss: 3.4.3 + tailwindcss: 3.4.13(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.3)) - '@tanstack/react-table@8.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@tanstack/react-table@8.20.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@tanstack/table-core': 8.16.0 + '@tanstack/table-core': 8.20.5 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@tanstack/react-virtual@3.10.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/virtual-core': 3.10.8 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -3438,33 +4065,47 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@tanstack/table-core@8.16.0': {} + '@tanstack/table-core@8.20.5': {} + + '@tanstack/virtual-core@3.10.8': {} '@tanstack/virtual-core@3.5.0': {} - '@tremor/react@3.16.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.3)': + '@tootallnate/once@1.1.2': + optional: true + + '@tremor/react@3.18.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.3)))': dependencies: '@floating-ui/react': 0.19.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@headlessui/react': 1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@headlessui/tailwindcss': 0.2.0(tailwindcss@3.4.3) - date-fns: 2.30.0 + '@headlessui/tailwindcss': 0.2.1(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.3))) + date-fns: 3.6.0 react: 18.3.1 - react-day-picker: 8.10.1(date-fns@2.30.0)(react@18.3.1) + react-day-picker: 8.10.1(date-fns@3.6.0)(react@18.3.1) react-dom: 18.3.1(react@18.3.1) - react-transition-state: 2.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - recharts: 2.12.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - tailwind-merge: 1.14.0 + react-transition-state: 2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + recharts: 2.13.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + tailwind-merge: 2.5.3 transitivePeerDependencies: - tailwindcss + '@tsconfig/node10@1.0.11': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + '@tybys/wasm-util@0.8.3': dependencies: tslib: 2.6.2 optional: true - '@types/better-sqlite3@7.6.10': + '@types/better-sqlite3@7.6.11': dependencies: - '@types/node': 20.12.11 + '@types/node': 20.16.11 + optional: true '@types/d3-array@3.2.1': {} @@ -3490,21 +4131,56 @@ snapshots: '@types/d3-timer@3.0.2': {} - '@types/node@20.12.11': + '@types/lodash@4.17.13': {} + + '@types/luxon@3.4.2': {} + + '@types/node@20.16.11': dependencies: - undici-types: 5.26.5 + undici-types: 6.19.8 '@types/prop-types@15.7.12': {} - '@types/react-dom@18.3.0': + '@types/react-dom@18.3.1': dependencies: - '@types/react': 18.3.1 + '@types/react': 18.3.11 - '@types/react@18.3.1': + '@types/react@18.3.11': dependencies: '@types/prop-types': 15.7.12 csstype: 3.1.3 + '@types/ws@8.5.12': + dependencies: + '@types/node': 20.16.11 + + abbrev@1.1.1: + optional: true + + acorn-walk@8.3.4: + dependencies: + acorn: 8.14.0 + + acorn@8.14.0: {} + + agent-base@6.0.2: + dependencies: + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + optional: true + + agentkeepalive@4.5.0: + dependencies: + humanize-ms: 1.2.1 + optional: true + + aggregate-error@3.1.0: + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + optional: true + ansi-regex@5.0.1: {} ansi-regex@6.0.1: {} @@ -3522,10 +4198,21 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 - arctic@1.8.1: + aproba@2.0.0: + optional: true + + arctic@1.9.2: dependencies: oslo: 1.2.0 + are-we-there-yet@3.0.1: + dependencies: + delegates: 1.0.0 + readable-stream: 3.6.2 + optional: true + + arg@4.1.3: {} + arg@5.0.2: {} aria-hidden@1.2.4: @@ -3540,6 +4227,7 @@ snapshots: dependencies: bindings: 1.5.0 prebuild-install: 7.1.2 + optional: true binary-extensions@2.3.0: {} @@ -3553,6 +4241,12 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + optional: true + brace-expansion@2.0.1: dependencies: balanced-match: 1.0.2 @@ -3572,6 +4266,30 @@ snapshots: dependencies: streamsearch: 1.1.0 + cacache@15.3.0: + dependencies: + '@npmcli/fs': 1.1.1 + '@npmcli/move-file': 1.1.2 + chownr: 2.0.0 + fs-minipass: 2.1.0 + glob: 7.2.3 + infer-owner: 1.0.4 + lru-cache: 6.0.0 + minipass: 3.3.6 + minipass-collect: 1.0.2 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + mkdirp: 1.0.4 + p-map: 4.0.0 + promise-inflight: 1.0.1 + rimraf: 3.0.2 + ssri: 8.0.1 + tar: 6.2.1 + unique-filename: 1.1.1 + transitivePeerDependencies: + - bluebird + optional: true + camelcase-css@2.0.1: {} caniuse-lite@1.0.30001617: {} @@ -3590,10 +4308,15 @@ snapshots: chownr@1.1.4: {} + chownr@2.0.0: {} + class-variance-authority@0.7.0: dependencies: clsx: 2.0.0 + clean-stack@2.2.0: + optional: true + cli-color@2.0.4: dependencies: d: 1.0.2 @@ -3614,10 +4337,21 @@ snapshots: color-name@1.1.4: {} + color-support@1.1.3: + optional: true + commander@4.1.1: {} commander@9.5.0: {} + concat-map@0.0.1: + optional: true + + console-control-strings@1.1.0: + optional: true + + create-require@1.1.1: {} + cross-spawn@7.0.3: dependencies: path-key: 3.1.1 @@ -3671,9 +4405,11 @@ snapshots: es5-ext: 0.10.64 type: 2.7.2 - date-fns@2.30.0: - dependencies: - '@babel/runtime': 7.24.5 + data-uri-to-buffer@4.0.1: {} + + date-fns@3.6.0: {} + + date-fns@4.1.0: {} debug@4.3.4: dependencies: @@ -3687,12 +4423,19 @@ snapshots: deep-extend@0.6.0: {} + delegates@1.0.0: + optional: true + + detect-libc@2.0.2: {} + detect-libc@2.0.3: {} detect-node-es@1.1.0: {} didyoumean@1.2.2: {} + diff@4.0.2: {} + difflib@0.2.4: dependencies: heap: 0.2.7 @@ -3708,7 +4451,7 @@ snapshots: dependencies: wordwrap: 1.0.0 - drizzle-kit@0.21.1: + drizzle-kit@0.21.4: dependencies: '@esbuild-kit/esm-loader': 2.6.5 commander: 9.5.0 @@ -3722,12 +4465,14 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-orm@0.30.10(@types/better-sqlite3@7.6.10)(@types/react@18.3.1)(better-sqlite3@9.6.0)(react@18.3.1): + drizzle-orm@0.30.10(@libsql/client@0.14.0)(@types/better-sqlite3@7.6.11)(@types/react@18.3.11)(better-sqlite3@9.6.0)(react@18.3.1)(sqlite3@5.1.7): optionalDependencies: - '@types/better-sqlite3': 7.6.10 - '@types/react': 18.3.1 + '@libsql/client': 0.14.0 + '@types/better-sqlite3': 7.6.11 + '@types/react': 18.3.11 better-sqlite3: 9.6.0 react: 18.3.1 + sqlite3: 5.1.7 eastasianwidth@0.2.0: {} @@ -3735,12 +4480,23 @@ snapshots: emoji-regex@9.2.2: {} + encoding@0.1.13: + dependencies: + iconv-lite: 0.6.3 + optional: true + end-of-stream@1.4.4: dependencies: once: 1.4.0 + env-paths@2.2.1: + optional: true + env-paths@3.0.0: {} + err-code@2.0.3: + optional: true + es5-ext@0.10.64: dependencies: es6-iterator: 2.0.3 @@ -3858,6 +4614,11 @@ snapshots: dependencies: reusify: 1.0.4 + fetch-blob@3.2.0: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + file-uri-to-path@1.0.0: {} fill-range@7.0.1: @@ -3869,8 +4630,16 @@ snapshots: cross-spawn: 7.0.3 signal-exit: 4.1.0 + formdata-polyfill@4.0.10: + dependencies: + fetch-blob: 3.2.0 + fs-constants@1.0.0: {} + fs-minipass@2.1.0: + dependencies: + minipass: 3.3.6 + fs-monkey@1.0.6: optional: true @@ -3881,6 +4650,18 @@ snapshots: function-bind@1.1.2: {} + gauge@4.0.4: + dependencies: + aproba: 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 + optional: true + get-nonce@1.0.1: {} get-tsconfig@4.7.5: @@ -3905,6 +4686,16 @@ snapshots: minipass: 7.1.1 path-scurry: 1.11.0 + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + optional: true + glob@8.1.0: dependencies: fs.realpath: 1.0.0 @@ -3920,14 +4711,56 @@ snapshots: lodash.throttle: 4.1.1 sisteransi: 1.0.5 + has-unicode@2.0.1: + optional: true + hasown@2.0.2: dependencies: function-bind: 1.1.2 heap@0.2.7: {} + http-cache-semantics@4.1.1: + optional: true + + http-proxy-agent@4.0.1: + dependencies: + '@tootallnate/once': 1.1.2 + agent-base: 6.0.2 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + optional: true + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + optional: true + + humanize-ms@1.2.1: + dependencies: + ms: 2.1.2 + optional: true + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + optional: true + ieee754@1.2.1: {} + imurmurhash@0.1.4: + optional: true + + indent-string@4.0.0: + optional: true + + infer-owner@1.0.4: + optional: true + inflight@1.0.6: dependencies: once: 1.4.0 @@ -3943,6 +4776,12 @@ snapshots: dependencies: loose-envify: 1.4.0 + ip-address@9.0.5: + dependencies: + jsbn: 1.1.0 + sprintf-js: 1.1.3 + optional: true + is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 @@ -3959,6 +4798,9 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-lambda@1.0.1: + optional: true + is-number@7.0.0: {} is-promise@2.2.2: {} @@ -3973,14 +4815,32 @@ snapshots: jiti@1.21.0: {} + js-base64@3.7.7: {} + js-tokens@4.0.0: {} + jsbn@1.1.0: + optional: true + json-diff@0.9.0: dependencies: cli-color: 2.0.4 difflib: 0.2.4 dreamopt: 0.8.0 + libsql@0.4.6: + dependencies: + '@neon-rs/load': 0.0.4 + detect-libc: 2.0.2 + optionalDependencies: + '@libsql/darwin-arm64': 0.4.6 + '@libsql/darwin-x64': 0.4.6 + '@libsql/linux-arm64-gnu': 0.4.6 + '@libsql/linux-arm64-musl': 0.4.6 + '@libsql/linux-x64-gnu': 0.4.6 + '@libsql/linux-x64-musl': 0.4.6 + '@libsql/win32-x64-msvc': 0.4.6 + lilconfig@2.1.0: {} lilconfig@3.1.1: {} @@ -3997,18 +4857,51 @@ snapshots: lru-cache@10.2.2: {} + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + optional: true + lru-queue@0.1.0: dependencies: es5-ext: 0.10.64 - lucia@3.2.0: + lucia@3.2.1: dependencies: - oslo: 1.2.0 + '@oslojs/crypto': 1.0.1 + '@oslojs/encoding': 1.1.0 lucide-react@0.378.0(react@18.3.1): dependencies: react: 18.3.1 + luxon@3.5.0: {} + + make-error@1.3.6: {} + + make-fetch-happen@9.1.0: + dependencies: + agentkeepalive: 4.5.0 + cacache: 15.3.0 + http-cache-semantics: 4.1.1 + http-proxy-agent: 4.0.1 + https-proxy-agent: 5.0.1 + is-lambda: 1.0.1 + lru-cache: 6.0.0 + minipass: 3.3.6 + minipass-collect: 1.0.2 + minipass-fetch: 1.4.1 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + negotiator: 0.6.4 + promise-retry: 2.0.1 + socks-proxy-agent: 6.2.1 + ssri: 8.0.1 + transitivePeerDependencies: + - bluebird + - supports-color + optional: true + memfs-browser@3.5.10302: dependencies: memfs: 3.5.3 @@ -4041,6 +4934,11 @@ snapshots: mini-svg-data-uri@1.4.4: {} + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + optional: true + minimatch@5.1.6: dependencies: brace-expansion: 2.0.1 @@ -4051,10 +4949,52 @@ snapshots: minimist@1.2.8: {} + minipass-collect@1.0.2: + dependencies: + minipass: 3.3.6 + optional: true + + minipass-fetch@1.4.1: + dependencies: + minipass: 3.3.6 + minipass-sized: 1.0.3 + minizlib: 2.1.2 + optionalDependencies: + encoding: 0.1.13 + optional: true + + minipass-flush@1.0.5: + dependencies: + minipass: 3.3.6 + optional: true + + minipass-pipeline@1.2.4: + dependencies: + minipass: 3.3.6 + optional: true + + minipass-sized@1.0.3: + dependencies: + minipass: 3.3.6 + optional: true + + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + + minipass@5.0.0: {} + minipass@7.1.1: {} + minizlib@2.1.2: + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + mkdirp-classic@0.5.3: {} + mkdirp@1.0.4: {} + ms@2.1.2: {} mz@2.7.0: @@ -4067,6 +5007,9 @@ snapshots: napi-build-utils@1.0.2: {} + negotiator@0.6.4: + optional: true + next-themes@0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: react: 18.3.1 @@ -4103,8 +5046,48 @@ snapshots: dependencies: semver: 7.6.2 + node-addon-api@7.1.1: {} + + node-domexception@1.0.0: {} + + node-fetch@3.3.2: + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + + node-gyp@8.4.1: + dependencies: + env-paths: 2.2.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + make-fetch-happen: 9.1.0 + nopt: 5.0.0 + npmlog: 6.0.2 + rimraf: 3.0.2 + semver: 7.6.2 + tar: 6.2.1 + which: 2.0.2 + transitivePeerDependencies: + - bluebird + - supports-color + optional: true + + nopt@5.0.0: + dependencies: + abbrev: 1.1.1 + optional: true + normalize-path@3.0.0: {} + npmlog@6.0.2: + dependencies: + are-we-there-yet: 3.0.1 + console-control-strings: 1.1.0 + gauge: 4.0.4 + set-blocking: 2.0.0 + optional: true + object-assign@4.1.1: {} object-hash@3.0.0: {} @@ -4118,6 +5101,19 @@ snapshots: '@node-rs/argon2': 1.7.0 '@node-rs/bcrypt': 1.9.0 + oslo@1.2.1: + dependencies: + '@node-rs/argon2': 1.7.0 + '@node-rs/bcrypt': 1.9.0 + + p-map@4.0.0: + dependencies: + aggregate-error: 3.1.0 + optional: true + + path-is-absolute@1.0.1: + optional: true + path-key@3.1.1: {} path-parse@1.0.7: {} @@ -4129,34 +5125,37 @@ snapshots: picocolors@1.0.0: {} + picocolors@1.1.0: {} + picomatch@2.3.1: {} pify@2.3.0: {} pirates@4.0.6: {} - postcss-import@15.1.0(postcss@8.4.38): + postcss-import@15.1.0(postcss@8.4.47): dependencies: - postcss: 8.4.38 + postcss: 8.4.47 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.8 - postcss-js@4.0.1(postcss@8.4.38): + postcss-js@4.0.1(postcss@8.4.47): dependencies: camelcase-css: 2.0.1 - postcss: 8.4.38 + postcss: 8.4.47 - postcss-load-config@4.0.2(postcss@8.4.38): + postcss-load-config@4.0.2(postcss@8.4.47)(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.3)): dependencies: lilconfig: 3.1.1 yaml: 2.4.2 optionalDependencies: - postcss: 8.4.38 + postcss: 8.4.47 + ts-node: 10.9.2(@types/node@20.16.11)(typescript@5.6.3) - postcss-nested@6.0.1(postcss@8.4.38): + postcss-nested@6.0.1(postcss@8.4.47): dependencies: - postcss: 8.4.38 + postcss: 8.4.47 postcss-selector-parser: 6.0.16 postcss-selector-parser@6.0.16: @@ -4172,11 +5171,11 @@ snapshots: picocolors: 1.0.0 source-map-js: 1.2.0 - postcss@8.4.38: + postcss@8.4.47: dependencies: nanoid: 3.3.7 - picocolors: 1.0.0 - source-map-js: 1.2.0 + picocolors: 1.1.0 + source-map-js: 1.2.1 prebuild-install@7.1.2: dependencies: @@ -4193,6 +5192,17 @@ snapshots: tar-fs: 2.1.1 tunnel-agent: 0.6.0 + promise-inflight@1.0.1: + optional: true + + promise-limit@2.7.0: {} + + promise-retry@2.0.1: + dependencies: + err-code: 2.0.3 + retry: 0.12.0 + optional: true + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -4213,9 +5223,9 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 - react-day-picker@8.10.1(date-fns@2.30.0)(react@18.3.1): + react-day-picker@8.10.1(date-fns@3.6.0)(react@18.3.1): dependencies: - date-fns: 2.30.0 + date-fns: 3.6.0 react: 18.3.1 react-dom@18.3.1(react@18.3.1): @@ -4224,7 +5234,7 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 - react-hook-form@7.51.4(react@18.3.1): + react-hook-form@7.53.0(react@18.3.1): dependencies: react: 18.3.1 @@ -4234,24 +5244,26 @@ snapshots: react-is@16.13.1: {} - react-remove-scroll-bar@2.3.6(@types/react@18.3.1)(react@18.3.1): - dependencies: - react: 18.3.1 - react-style-singleton: 2.2.1(@types/react@18.3.1)(react@18.3.1) - tslib: 2.6.2 - optionalDependencies: - '@types/react': 18.3.1 + react-is@18.3.1: {} - react-remove-scroll@2.5.5(@types/react@18.3.1)(react@18.3.1): + react-remove-scroll-bar@2.3.6(@types/react@18.3.11)(react@18.3.1): dependencies: react: 18.3.1 - react-remove-scroll-bar: 2.3.6(@types/react@18.3.1)(react@18.3.1) - react-style-singleton: 2.2.1(@types/react@18.3.1)(react@18.3.1) + react-style-singleton: 2.2.1(@types/react@18.3.11)(react@18.3.1) tslib: 2.6.2 - use-callback-ref: 1.3.2(@types/react@18.3.1)(react@18.3.1) - use-sidecar: 1.1.2(@types/react@18.3.1)(react@18.3.1) optionalDependencies: - '@types/react': 18.3.1 + '@types/react': 18.3.11 + + react-remove-scroll@2.6.0(@types/react@18.3.11)(react@18.3.1): + dependencies: + react: 18.3.1 + react-remove-scroll-bar: 2.3.6(@types/react@18.3.11)(react@18.3.1) + react-style-singleton: 2.2.1(@types/react@18.3.11)(react@18.3.1) + tslib: 2.6.2 + use-callback-ref: 1.3.2(@types/react@18.3.11)(react@18.3.1) + use-sidecar: 1.1.2(@types/react@18.3.11)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.11 react-smooth@4.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: @@ -4261,14 +5273,14 @@ snapshots: react-dom: 18.3.1(react@18.3.1) react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react-style-singleton@2.2.1(@types/react@18.3.1)(react@18.3.1): + react-style-singleton@2.2.1(@types/react@18.3.11)(react@18.3.1): dependencies: get-nonce: 1.0.1 invariant: 2.2.4 react: 18.3.1 tslib: 2.6.2 optionalDependencies: - '@types/react': 18.3.1 + '@types/react': 18.3.11 react-timer-hook@3.0.7(react@18.3.1): dependencies: @@ -4283,7 +5295,7 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-transition-state@2.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + react-transition-state@2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -4310,14 +5322,14 @@ snapshots: dependencies: decimal.js-light: 2.5.1 - recharts@2.12.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + recharts@2.13.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: clsx: 2.1.1 eventemitter3: 4.0.7 lodash: 4.17.21 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-is: 16.13.1 + react-is: 18.3.1 react-smooth: 4.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) recharts-scale: 0.4.5 tiny-invariant: 1.3.3 @@ -4335,26 +5347,43 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + retry@0.12.0: + optional: true + reusify@1.0.4: {} + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + optional: true + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 safe-buffer@5.2.1: {} + safer-buffer@2.1.2: + optional: true + scheduler@0.23.2: dependencies: loose-envify: 1.4.0 semver@7.6.2: {} + set-blocking@2.0.0: + optional: true + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 shebang-regex@3.0.0: {} + signal-exit@3.0.7: + optional: true + signal-exit@4.1.0: {} simple-concat@1.0.1: {} @@ -4367,13 +5396,33 @@ snapshots: sisteransi@1.0.5: {} - sonner@1.4.41(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + smart-buffer@4.2.0: + optional: true + + socks-proxy-agent@6.2.1: + dependencies: + agent-base: 6.0.2 + debug: 4.3.4 + socks: 2.8.3 + transitivePeerDependencies: + - supports-color + optional: true + + socks@2.8.3: + dependencies: + ip-address: 9.0.5 + smart-buffer: 4.2.0 + optional: true + + sonner@1.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) source-map-js@1.2.0: {} + source-map-js@1.2.1: {} + source-map-support@0.5.21: dependencies: buffer-from: 1.1.2 @@ -4381,6 +5430,28 @@ snapshots: source-map@0.6.1: {} + sprintf-js@1.1.3: + optional: true + + sqlite3@5.1.7: + dependencies: + bindings: 1.5.0 + node-addon-api: 7.1.1 + prebuild-install: 7.1.2 + tar: 6.2.1 + optionalDependencies: + node-gyp: 8.4.1 + transitivePeerDependencies: + - bluebird + - supports-color + + sqlite@5.1.1: {} + + ssri@8.0.1: + dependencies: + minipass: 3.3.6 + optional: true + streamsearch@1.1.0: {} string-width@4.2.3: @@ -4434,17 +5505,13 @@ snapshots: tabbable@6.2.0: {} - tailwind-merge@1.14.0: {} + tailwind-merge@2.5.3: {} - tailwind-merge@2.3.0: + tailwindcss-animate@1.0.7(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.3))): dependencies: - '@babel/runtime': 7.24.5 + tailwindcss: 3.4.13(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.3)) - tailwindcss-animate@1.0.7(tailwindcss@3.4.3): - dependencies: - tailwindcss: 3.4.3 - - tailwindcss@3.4.3: + tailwindcss@3.4.13(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.3)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -4460,11 +5527,11 @@ snapshots: normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.0.0 - postcss: 8.4.38 - postcss-import: 15.1.0(postcss@8.4.38) - postcss-js: 4.0.1(postcss@8.4.38) - postcss-load-config: 4.0.2(postcss@8.4.38) - postcss-nested: 6.0.1(postcss@8.4.38) + postcss: 8.4.47 + postcss-import: 15.1.0(postcss@8.4.47) + postcss-js: 4.0.1(postcss@8.4.47) + postcss-load-config: 4.0.2(postcss@8.4.47)(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.3)) + postcss-nested: 6.0.1(postcss@8.4.47) postcss-selector-parser: 6.0.16 resolve: 1.22.8 sucrase: 3.35.0 @@ -4486,6 +5553,15 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 + tar@6.2.1: + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + thenify-all@1.6.0: dependencies: thenify: 3.3.1 @@ -4507,6 +5583,24 @@ snapshots: ts-interface-checker@0.1.13: {} + ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.3): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.16.11 + acorn: 8.14.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.6.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + tslib@2.6.2: {} tunnel-agent@0.6.0: @@ -4515,28 +5609,38 @@ snapshots: type@2.7.2: {} - typescript@5.4.5: {} + typescript@5.6.3: {} - undici-types@5.26.5: {} + undici-types@6.19.8: {} - use-callback-ref@1.3.2(@types/react@18.3.1)(react@18.3.1): + unique-filename@1.1.1: + dependencies: + unique-slug: 2.0.2 + optional: true + + unique-slug@2.0.2: + dependencies: + imurmurhash: 0.1.4 + optional: true + + use-callback-ref@1.3.2(@types/react@18.3.11)(react@18.3.1): dependencies: react: 18.3.1 tslib: 2.6.2 optionalDependencies: - '@types/react': 18.3.1 + '@types/react': 18.3.11 - use-debounce@10.0.0(react@18.3.1): + use-debounce@10.0.3(react@18.3.1): dependencies: react: 18.3.1 - use-sidecar@1.1.2(@types/react@18.3.1)(react@18.3.1): + use-sidecar@1.1.2(@types/react@18.3.11)(react@18.3.1): dependencies: detect-node-es: 1.1.0 react: 18.3.1 tslib: 2.6.2 optionalDependencies: - '@types/react': 18.3.1 + '@types/react': 18.3.11 use-sync-external-store@1.2.2(react@18.3.1): dependencies: @@ -4544,6 +5648,10 @@ snapshots: util-deprecate@1.0.2: {} + uuid@11.0.2: {} + + v8-compile-cache-lib@3.0.1: {} + victory-vendor@36.9.2: dependencies: '@types/d3-array': 3.2.1 @@ -4561,10 +5669,17 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 + web-streams-polyfill@3.3.3: {} + which@2.0.2: dependencies: isexe: 2.0.0 + wide-align@1.1.5: + dependencies: + string-width: 4.2.3 + optional: true + wordwrap@1.0.0: {} wrap-ansi@7.0.0: @@ -4581,6 +5696,12 @@ snapshots: wrappy@1.0.2: {} + ws@8.18.0: {} + + yallist@4.0.0: {} + yaml@2.4.2: {} + yn@3.1.1: {} + zod@3.23.8: {} diff --git a/packages/reservation-platform/pnpm-workspace.yaml b/packages/reservation-platform/pnpm-workspace.yaml deleted file mode 100644 index d30257f..0000000 --- a/packages/reservation-platform/pnpm-workspace.yaml +++ /dev/null @@ -1,5 +0,0 @@ -ignoredBuiltDependencies: - - '@biomejs/biome' - - better-sqlite3 - - es5-ext - - esbuild diff --git a/packages/reservation-platform/repomix-output.txt b/packages/reservation-platform/repomix-output.txt new file mode 100644 index 0000000..68f0be1 --- /dev/null +++ b/packages/reservation-platform/repomix-output.txt @@ -0,0 +1,9279 @@ +This file is a merged representation of the entire codebase, combining all repository files into a single document. +Generated by Repomix on: 2024-12-09T06:29:51.427Z + +================================================================ +File Summary +================================================================ + +Purpose: +-------- +This file contains a packed representation of the entire repository's contents. +It is designed to be easily consumable by AI systems for analysis, code review, +or other automated processes. + +File Format: +------------ +The content is organized as follows: +1. This summary section +2. Repository information +3. Repository structure +4. Multiple file entries, each consisting of: + a. A separator line (================) + b. The file path (File: path/to/file) + c. Another separator line + d. The full contents of the file + e. A blank line + +Usage Guidelines: +----------------- +- This file should be treated as read-only. Any changes should be made to the + original repository files, not this packed version. +- When processing this file, use the file path to distinguish + between different files in the repository. +- Be aware that this file may contain sensitive information. Handle it with + the same level of security as you would the original repository. + +Notes: +------ +- Some files may have been excluded based on .gitignore rules and Repomix's + configuration. +- Binary files are not included in this packed representation. Please refer to + the Repository Structure section for a complete list of file paths, including + binary files. + +Additional Info: +---------------- + +For more information about Repomix, visit: https://github.com/yamadashy/repomix + +================================================================ +Repository Structure +================================================================ +.dockerignore +.env.example +.gitignore +biome.json +components.json +docker/build.sh +docker/caddy/Caddyfile +docker/compose.yml +docker/deploy.sh +docker/images/.gitattributes +docker/save.sh +Dockerfile +drizzle.config.ts +drizzle/0000_overjoyed_strong_guy.sql +drizzle/meta/_journal.json +drizzle/meta/0000_snapshot.json +next.config.mjs +package.json +postcss.config.mjs +public/next.svg +public/vercel.svg +README.md +scripts/generate-data.js +src/app/admin/about/page.tsx +src/app/admin/admin-sidebar.tsx +src/app/admin/charts/printer-error-chart.tsx +src/app/admin/charts/printer-error-rate.tsx +src/app/admin/charts/printer-forecast.tsx +src/app/admin/charts/printer-utilization.tsx +src/app/admin/charts/printer-volume.tsx +src/app/admin/jobs/page.tsx +src/app/admin/layout.tsx +src/app/admin/page.tsx +src/app/admin/printers/columns.tsx +src/app/admin/printers/data-table.tsx +src/app/admin/printers/dialogs/create-printer.tsx +src/app/admin/printers/dialogs/delete-printer.tsx +src/app/admin/printers/dialogs/edit-printer.tsx +src/app/admin/printers/form.tsx +src/app/admin/printers/page.tsx +src/app/admin/settings/download/route.ts +src/app/admin/settings/page.tsx +src/app/admin/users/columns.tsx +src/app/admin/users/data-table.tsx +src/app/admin/users/dialog.tsx +src/app/admin/users/form.tsx +src/app/admin/users/page.tsx +src/app/api/job/[jobId]/remaining-time/route.ts +src/app/api/printers/route.ts +src/app/auth/login/callback/route.ts +src/app/auth/login/route.ts +src/app/globals.css +src/app/job/[jobId]/cancel-form.tsx +src/app/job/[jobId]/edit-comments.tsx +src/app/job/[jobId]/extend-form.tsx +src/app/job/[jobId]/finish-form.tsx +src/app/job/[jobId]/page.tsx +src/app/layout.tsx +src/app/my/jobs/columns.tsx +src/app/my/jobs/data-table.tsx +src/app/my/profile/page.tsx +src/app/not-found.tsx +src/app/page.tsx +src/app/printer/[printerId]/reserve/form.tsx +src/app/printer/[printerId]/reserve/page.tsx +src/components/data-card.tsx +src/components/dynamic-printer-cards.tsx +src/components/header/index.tsx +src/components/header/navigation.tsx +src/components/login-button.tsx +src/components/logout-button.tsx +src/components/personalized-cards.tsx +src/components/printer-availability-badge.tsx +src/components/printer-card/countdown.tsx +src/components/printer-card/index.tsx +src/components/ui/alert-dialog.tsx +src/components/ui/alert.tsx +src/components/ui/avatar.tsx +src/components/ui/badge.tsx +src/components/ui/breadcrumb.tsx +src/components/ui/button.tsx +src/components/ui/card.tsx +src/components/ui/chart.tsx +src/components/ui/dialog.tsx +src/components/ui/dropdown-menu.tsx +src/components/ui/form.tsx +src/components/ui/hover-card.tsx +src/components/ui/input.tsx +src/components/ui/label.tsx +src/components/ui/scroll-area.tsx +src/components/ui/select.tsx +src/components/ui/skeleton.tsx +src/components/ui/sonner.tsx +src/components/ui/table.tsx +src/components/ui/tabs.tsx +src/components/ui/textarea.tsx +src/components/ui/toast.tsx +src/components/ui/toaster.tsx +src/components/ui/use-toast.ts +src/server/actions/authentication/logout.ts +src/server/actions/printers.ts +src/server/actions/printJobs.ts +src/server/actions/timer.ts +src/server/actions/user/delete.ts +src/server/actions/user/update.ts +src/server/actions/users.ts +src/server/auth/index.ts +src/server/auth/oauth.ts +src/server/auth/permissions.ts +src/utils/analytics/error-rate.ts +src/utils/analytics/errors.ts +src/utils/analytics/forecast.ts +src/utils/analytics/utilization.ts +src/utils/analytics/volume.ts +src/utils/drizzle.ts +src/utils/errors.ts +src/utils/fetch.ts +src/utils/guard.ts +src/utils/printers.ts +src/utils/strings.ts +src/utils/styles.ts +tailwind.config.ts +tsconfig.json + +================================================================ +Repository Files +================================================================ + +================ +File: .dockerignore +================ +# Build and utility assets +docker/ +scripts/ + +# Ignore node_modules as they will be installed in the container +node_modules + +# Ignore build artifacts +.next + +# Ignore runtime data +db/ + +# Ignore local configuration files +.env +.env.example + +# Ignore version control files +.git +.gitignore + +# Ignore IDE/editor specific files +*.log +*.tmp +*.DS_Store +.vscode/ +.idea/ + +================ +File: .env.example +================ +# OAuth Configuration +OAUTH_CLIENT_ID=client_id +OAUTH_CLIENT_SECRET=client_secret + +================ +File: .gitignore +================ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# db folder +db/ + +# Env file +.env + + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +================ +File: biome.json +================ +{ + "$schema": "https://biomejs.dev/schemas/1.7.0/schema.json", + "organizeImports": { + "enabled": true + }, + "formatter": { + "enabled": true, + "lineWidth": 120 + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "noUnusedImports": "error" + } + } + } +} + +================ +File: components.json +================ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "src/app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/utils/styles" + } +} + +================ +File: docker/build.sh +================ +#!/bin/bash + +# Define image name +MYP_RP_IMAGE_NAME="myp-rp" + +# Function to build Docker image +build_image() { + local image_name=$1 + local dockerfile=$2 + local platform=$3 + + echo "Building $image_name Docker image for $platform..." + + docker buildx build --platform $platform -t ${image_name}:latest -f $dockerfile --load . + if [ $? -eq 0 ]; then + echo "$image_name Docker image built successfully" + else + echo "Error occurred while building $image_name Docker image" + exit 1 + fi +} + +# Create and use a builder instance (if not already created) +BUILDER_NAME="myp-rp-arm64-builder" +docker buildx create --name $BUILDER_NAME --use || docker buildx use $BUILDER_NAME + +# Build myp-rp image +build_image "$MYP_RP_IMAGE_NAME" "$PWD/Dockerfile" "linux/arm64" + +# Remove the builder instance +docker buildx rm $BUILDER_NAME + +================ +File: docker/caddy/Caddyfile +================ +{ + debug +} + +m040tbaraspi001.de040.corpintra.net, m040tbaraspi001.de040.corpinter.net { + reverse_proxy myp-rp:3000 + tls internal +} + +================ +File: docker/compose.yml +================ +services: + caddy: + image: caddy:2.8 + container_name: caddy + restart: unless-stopped + ports: + - 80:80 + - 443:443 + volumes: + - ./caddy/data:/data + - ./caddy/config:/config + - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro + myp-rp: + image: myp-rp:latest + container_name: myp-rp + env_file: "/srv/myp-env/github.env" + volumes: + - /srv/MYP-DB:/usr/src/app/db + restart: unless-stopped + +================ +File: docker/deploy.sh +================ +#!/bin/bash + +# Directory containing the Docker images +IMAGE_DIR="docker/images" + +# Load all Docker images from the tar.xz files in the IMAGE_DIR +echo "Loading Docker images from $IMAGE_DIR..." + +for image_file in "$IMAGE_DIR"/*.tar.xz; do + if [ -f "$image_file" ]; then + echo "Loading Docker image from $image_file..." + docker load -i "$image_file" + + # Check if the image loading was successful + if [ $? -ne 0 ]; then + echo "Error occurred while loading Docker image from $image_file" + exit 1 + fi + else + echo "No Docker image tar.xz files found in $IMAGE_DIR." + fi +done + +# Execute docker compose +echo "Running docker compose..." +docker compose -f "docker/compose.yml" up -d + +# Check if the operation was successful +if [ $? -eq 0 ]; then + echo "Docker compose executed successfully" +else + echo "Error occurred while executing docker compose" + exit 1 +fi + +echo "Deployment completed successfully" + +================ +File: docker/images/.gitattributes +================ +caddy_2.8.tar.xz filter=lfs diff=lfs merge=lfs -text +myp-rp_latest.tar.xz filter=lfs diff=lfs merge=lfs -text + +================ +File: docker/save.sh +================ +#!/bin/bash + +# Get image name as argument +IMAGE_NAME=$1 +PLATFORM="linux/arm64" + +# Define paths +IMAGE_DIR="docker/images" +IMAGE_FILE="${IMAGE_DIR}/${IMAGE_NAME//[:\/]/_}.tar" +COMPRESSED_FILE="${IMAGE_FILE}.xz" + +# Function to pull the image +pull_image() { + local image=$1 + if [[ $image == arm64v8/* ]]; then + echo "Pulling image $image without platform specification..." + docker pull $image + else + echo "Pulling image $image for platform $PLATFORM..." + docker pull --platform $PLATFORM $image + fi + return $? +} + +# Pull the image if it is not available locally +if ! docker image inspect ${IMAGE_NAME} &>/dev/null; then + if pull_image ${IMAGE_NAME}; then + echo "Image $IMAGE_NAME pulled successfully." + else + echo "Error occurred while pulling $IMAGE_NAME for platform $PLATFORM" + echo "Trying to pull $IMAGE_NAME without platform specification..." + + # Attempt to pull again without platform + if pull_image ${IMAGE_NAME}; then + echo "Image $IMAGE_NAME pulled successfully without platform." + else + echo "Error occurred while pulling $IMAGE_NAME without platform." + echo "Trying to pull arm64v8/${IMAGE_NAME} instead..." + + # Construct new image name + NEW_IMAGE_NAME="arm64v8/${IMAGE_NAME}" + if pull_image ${NEW_IMAGE_NAME}; then + echo "Image $NEW_IMAGE_NAME pulled successfully." + IMAGE_NAME=${NEW_IMAGE_NAME} # Update IMAGE_NAME to use the new one + else + echo "Error occurred while pulling $NEW_IMAGE_NAME" + exit 1 + fi + fi + fi +else + echo "Image $IMAGE_NAME found locally. Skipping pull." +fi + +# Save the Docker image +echo "Saving $IMAGE_NAME Docker image..." +docker save ${IMAGE_NAME} > $IMAGE_FILE + +# Compress the Docker image (overwriting if file exists) +echo "Compressing $IMAGE_FILE..." +xz -z --force $IMAGE_FILE + +if [ $? -eq 0 ]; then + echo "$IMAGE_NAME Docker image saved and compressed successfully as $COMPRESSED_FILE" +else + echo "Error occurred while compressing $IMAGE_NAME Docker image" + exit 1 +fi + +================ +File: Dockerfile +================ +FROM node:20-bookworm-slim + +# Create application directory +RUN mkdir -p /usr/src/app + +# Set environment variables +ENV PORT=3000 +ENV NEXT_TELEMETRY_DISABLED=1 + +WORKDIR /usr/src/app + +# Copy package.json and pnpm-lock.yaml +COPY package.json /usr/src/app +COPY pnpm-lock.yaml /usr/src/app + +# Install pnpm +RUN corepack enable pnpm + +# Install dependencies +RUN pnpm install + +# Copy the rest of the application code +COPY . /usr/src/app + +# Initialize Database, if it not already exists +RUN pnpm run db + +# Build the application +RUN pnpm run build + +EXPOSE 3000 + +# Start the application +CMD ["/bin/sh", "-c", "if [ ! -f ./db/sqlite.db ]; then pnpm db; fi && pnpm start"] + +================ +File: drizzle.config.ts +================ +import { defineConfig } from "drizzle-kit"; + +//@ts-ignore - better-sqlite driver throws an error even though its an valid value +export default defineConfig({ + dialect: "sqlite", + schema: "./src/server/db/schema.ts", + out: "./drizzle", + driver: "libsql", + dbCredentials: { + url: "file:./db/sqlite.db", + }, +}); + +================ +File: drizzle/0000_overjoyed_strong_guy.sql +================ +CREATE TABLE `printJob` ( + `id` text PRIMARY KEY NOT NULL, + `printerId` text NOT NULL, + `userId` text NOT NULL, + `startAt` integer NOT NULL, + `durationInMinutes` integer NOT NULL, + `comments` text, + `aborted` integer DEFAULT false NOT NULL, + `abortReason` text, + FOREIGN KEY (`printerId`) REFERENCES `printer`(`id`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE TABLE `printer` ( + `id` text PRIMARY KEY NOT NULL, + `name` text NOT NULL, + `description` text NOT NULL, + `status` integer DEFAULT 0 NOT NULL +); +--> statement-breakpoint +CREATE TABLE `session` ( + `id` text PRIMARY KEY NOT NULL, + `user_id` text NOT NULL, + `expires_at` integer NOT NULL, + FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE TABLE `user` ( + `id` text PRIMARY KEY NOT NULL, + `github_id` integer NOT NULL, + `name` text, + `displayName` text, + `email` text NOT NULL, + `role` text DEFAULT 'guest' +); + +================ +File: drizzle/meta/_journal.json +================ +{ + "version": "6", + "dialect": "sqlite", + "entries": [ + { + "idx": 0, + "version": "6", + "when": 1715416514336, + "tag": "0000_overjoyed_strong_guy", + "breakpoints": true + } + ] +} + +================ +File: drizzle/meta/0000_snapshot.json +================ +{ + "version": "6", + "dialect": "sqlite", + "id": "791dc197-5254-4432-bd9f-1368d1a5aa6a", + "prevId": "00000000-0000-0000-0000-000000000000", + "tables": { + "printJob": { + "name": "printJob", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "printerId": { + "name": "printerId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "startAt": { + "name": "startAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "durationInMinutes": { + "name": "durationInMinutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "comments": { + "name": "comments", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "aborted": { + "name": "aborted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "abortReason": { + "name": "abortReason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "printJob_printerId_printer_id_fk": { + "name": "printJob_printerId_printer_id_fk", + "tableFrom": "printJob", + "tableTo": "printer", + "columnsFrom": [ + "printerId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "printJob_userId_user_id_fk": { + "name": "printJob_userId_user_id_fk", + "tableFrom": "printJob", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "printer": { + "name": "printer", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "github_id": { + "name": "github_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "displayName": { + "name": "displayName", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'guest'" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} + +================ +File: next.config.mjs +================ +/** @type {import('next').NextConfig} */ +const nextConfig = { + async headers() { + return [ + { + source: "/:path*", + headers: [ + { + key: "Access-Control-Allow-Origin", + value: "m040tbaraspi001.de040.corpintra.net", + }, + { + key: "Access-Control-Allow-Methods", + value: "GET, POST, PUT, DELETE, OPTIONS", + }, + { + key: "Access-Control-Allow-Headers", + value: "Content-Type, Authorization", + }, + ], + }, + ]; + }, +}; + +export default nextConfig; + +================ +File: package.json +================ +{ + "name": "myp-rp", + "version": "1.0.0", + "private": true, + "packageManager": "pnpm@9.12.1", + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint", + "db:create-default": "mkdir -p db/", + "db:generate-sqlite": "pnpm drizzle-kit generate", + "db:clean": "rm -rf db/ drizzle/", + "db:migrate": "pnpm drizzle-kit migrate", + "db": "pnpm db:create-default && pnpm db:generate-sqlite && pnpm db:migrate", + "db:reset": "pnpm db:clean && pnpm db" + }, + "dependencies": { + "@faker-js/faker": "^9.2.0", + "@headlessui/react": "^2.1.10", + "@headlessui/tailwindcss": "^0.2.1", + "@hookform/resolvers": "^3.9.0", + "@libsql/client": "^0.14.0", + "@lucia-auth/adapter-drizzle": "^1.1.0", + "@radix-ui/react-alert-dialog": "^1.1.2", + "@radix-ui/react-avatar": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.2", + "@radix-ui/react-dropdown-menu": "^2.1.2", + "@radix-ui/react-hover-card": "^1.1.2", + "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-scroll-area": "^1.2.0", + "@radix-ui/react-select": "^2.1.2", + "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-tabs": "^1.1.1", + "@radix-ui/react-toast": "^1.2.2", + "@remixicon/react": "^4.3.0", + "@tanstack/react-table": "^8.20.5", + "@tremor/react": "^3.18.3", + "arctic": "^1.9.2", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", + "date-fns": "^4.1.0", + "drizzle-orm": "^0.30.10", + "lodash": "^4.17.21", + "lucia": "^3.2.1", + "lucide-react": "^0.378.0", + "luxon": "^3.5.0", + "next": "14.2.3", + "next-themes": "^0.3.0", + "oslo": "^1.2.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-hook-form": "^7.53.0", + "react-if": "^4.1.5", + "react-timer-hook": "^3.0.7", + "recharts": "^2.13.3", + "regression": "^2.0.1", + "sonner": "^1.5.0", + "sqlite": "^5.1.1", + "sqlite3": "^5.1.7", + "swr": "^2.2.5", + "tailwind-merge": "^2.5.3", + "tailwindcss-animate": "^1.0.7", + "use-debounce": "^10.0.3", + "uuid": "^11.0.2", + "zod": "^3.23.8" + }, + "devDependencies": { + "@biomejs/biome": "^1.9.3", + "@tailwindcss/forms": "^0.5.9", + "@types/lodash": "^4.17.13", + "@types/luxon": "^3.4.2", + "@types/node": "^20.16.11", + "@types/react": "^18.3.11", + "@types/react-dom": "^18.3.1", + "drizzle-kit": "^0.21.4", + "postcss": "^8.4.47", + "tailwindcss": "^3.4.13", + "ts-node": "^10.9.2", + "typescript": "^5.6.3" + } +} + +================ +File: postcss.config.mjs +================ +/** @type {import('postcss-load-config').Config} */ +const config = { + plugins: { + tailwindcss: {}, + }, +}; + +export default config; + +================ +File: public/next.svg +================ + + +================ +File: public/vercel.svg +================ + + +================ +File: README.md +================ +# MYP - Manage Your Printer + +MYP (Manage Your Printer) ist eine Webanwendung zur Reservierung von 3D-Druckern. +Sie wurde im Rahmen des Abschlussprojektes der Fachinformatiker Ausbildung für Daten- und Prozessanalyse für die Technische Berufsausbildung des Mercedes-Benz Werkes Berlin-Marienfelde entwickelt. + +## Deployment + +### Voraussetzungen + +- Netzwerk auf Raspberry Pi ist eingerichtet +- Docker ist installiert + +### Schritte + +1. Docker-Container bauen (docker/build.sh) +2. Docker-Container speichern (docker/save.sh caddy:2.8 myp-rp:latest) +3. Docker-Container auf Raspberry Pi bereitstellen (docker/deploy.sh) + +## Entwicklerinformationen + +### Raspberry Pi Einstellungen + +Auf dem Raspberry Pi wurde Raspbian Lite installiert. +Unter /srv/* sind die Projektdateien zu finden. + +### Anmeldedaten + +``` +Benutzer: myp +Passwort: (persönlich bekannt) +``` + +================ +File: scripts/generate-data.js +================ +const sqlite3 = require("sqlite3"); +const faker = require("@faker-js/faker").faker; +const { random, sample, sampleSize, sum } = require("lodash"); +const { DateTime } = require("luxon"); +const { open } = require("sqlite"); +const { v4: uuidv4 } = require("uuid"); + +const dbPath = "./db/sqlite.db"; + +// Configuration for test data generation +let startDate = DateTime.fromISO("2024-10-08"); +let endDate = DateTime.fromISO("2024-11-08"); +let numberOfPrinters = 5; + +// Use weekday names for better readability and ease of setting trends +let avgPrintTimesPerDay = { + Monday: 4, + Tuesday: 2, + Wednesday: 5, + Thursday: 2, + Friday: 3, + Saturday: 0, + Sunday: 0, +}; // Average number of prints for each weekday + +let avgPrintDurationPerDay = { + Monday: 240, // Total average duration in minutes for Monday + Tuesday: 30, + Wednesday: 45, + Thursday: 40, + Friday: 120, + Saturday: 0, + Sunday: 0, +}; // Average total duration of prints for each weekday + +let printerUsage = { + "Drucker 1": 0.5, + "Drucker 2": 0.7, + "Drucker 3": 0.6, + "Drucker 4": 0.3, + "Drucker 5": 0.4, +}; // Usage percentages for each printer + +// **New Configurations for Error Rates** +let generalErrorRate = 0.05; // 5% chance any print job may fail +let printerErrorRates = { + "Drucker 1": 0.02, // 2% error rate for Printer 1 + "Drucker 2": 0.03, + "Drucker 3": 0.01, + "Drucker 4": 0.05, + "Drucker 5": 0.04, +}; // Error rates for each printer + +const holidays = []; // Example holidays +const existingJobs = []; + +const initDB = async () => { + console.log("Initializing database connection..."); + return open({ + filename: dbPath, + driver: sqlite3.Database, + }); +}; + +const createUser = (isPowerUser = false) => { + const name = [faker.person.firstName(), faker.person.lastName()]; + + const user = { + id: uuidv4(), + github_id: faker.number.int(), + username: `${name[0].slice(0, 2)}${name[1].slice(0, 6)}`.toUpperCase(), + displayName: `${name[0]} ${name[1]}`.toUpperCase(), + email: `${name[0]}.${name[1]}@example.com`, + role: sample(["user", "admin"]), + isPowerUser, + }; + console.log("Created user:", user); + return user; +}; + +const createPrinter = (index) => { + const printer = { + id: uuidv4(), + name: `Drucker ${index}`, + description: faker.lorem.sentence(), + status: random(0, 2), + }; + console.log("Created printer:", printer); + return printer; +}; + +const isPrinterAvailable = (printer, startAt, duration) => { + const endAt = startAt + duration * 60 * 1000; // Convert minutes to milliseconds + return !existingJobs.some((job) => { + const jobStart = job.startAt; + const jobEnd = job.startAt + job.durationInMinutes * 60 * 1000; + return ( + printer.id === job.printerId && + ((startAt >= jobStart && startAt < jobEnd) || + (endAt > jobStart && endAt <= jobEnd) || + (startAt <= jobStart && endAt >= jobEnd)) + ); + }); +}; + +const createPrintJob = (users, printers, startAt, duration) => { + const user = sample(users); + let printer; + + // Weighted selection based on printer usage + const printerNames = Object.keys(printerUsage); + const weightedPrinters = printers.filter((p) => printerNames.includes(p.name)); + + // Create a weighted array of printers based on usage percentages + const printerWeights = weightedPrinters.map((p) => ({ + printer: p, + weight: printerUsage[p.name], + })); + + const totalWeight = sum(printerWeights.map((pw) => pw.weight)); + const randomWeight = Math.random() * totalWeight; + let accumulatedWeight = 0; + for (const pw of printerWeights) { + accumulatedWeight += pw.weight; + if (randomWeight <= accumulatedWeight) { + printer = pw.printer; + break; + } + } + + if (!printer) { + printer = sample(printers); + } + + if (!isPrinterAvailable(printer, startAt, duration)) { + console.log("Printer not available, skipping job creation."); + return null; + } + + // **Determine if the job should be aborted based on error rates** + let aborted = false; + let abortReason = null; + + // Calculate the combined error rate + const printerErrorRate = printerErrorRates[printer.name] || 0; + const combinedErrorRate = 1 - (1 - generalErrorRate) * (1 - printerErrorRate); + + if (Math.random() < combinedErrorRate) { + aborted = true; + const errorMessages = [ + "Unbekannt", + "Keine Ahnung", + "Falsch gebucht", + "Filament gelöst", + "Druckabbruch", + "Düsenverstopfung", + "Schichthaftung fehlgeschlagen", + "Materialmangel", + "Dateifehler", + "Temperaturproblem", + "Mechanischer Fehler", + "Softwarefehler", + "Kalibrierungsfehler", + "Überhitzung", + ]; + abortReason = sample(errorMessages); // Generate a random abort reason + } + + const printJob = { + id: uuidv4(), + printerId: printer.id, + userId: user.id, + startAt, + durationInMinutes: duration, + comments: faker.lorem.sentence(), + aborted, + abortReason, + }; + console.log("Created print job:", printJob); + return printJob; +}; + +const generatePrintJobsForDay = async (users, printers, dayDate, totalJobsForDay, totalDurationForDay, db, dryRun) => { + console.log(`Generating print jobs for ${dayDate.toISODate()}...`); + + // Generate random durations that sum up approximately to totalDurationForDay + const durations = []; + let remainingDuration = totalDurationForDay; + for (let i = 0; i < totalJobsForDay; i++) { + const avgJobDuration = remainingDuration / (totalJobsForDay - i); + const jobDuration = Math.max( + Math.round(random(avgJobDuration * 0.8, avgJobDuration * 1.2)), + 5, // Minimum duration of 5 minutes + ); + durations.push(jobDuration); + remainingDuration -= jobDuration; + } + + // Shuffle durations to randomize job lengths + const shuffledDurations = sampleSize(durations, durations.length); + + for (let i = 0; i < totalJobsForDay; i++) { + const duration = shuffledDurations[i]; + + // Random start time between 8 AM and 6 PM, adjusted to avoid overlapping durations + const possibleStartHours = Array.from({ length: 10 }, (_, idx) => idx + 8); // 8 AM to 6 PM + let startAt; + let attempts = 0; + do { + const hour = sample(possibleStartHours); + const minute = random(0, 59); + startAt = dayDate.set({ hour, minute, second: 0, millisecond: 0 }).toMillis(); + attempts++; + if (attempts > 10) { + console.log("Unable to find available time slot, skipping job."); + break; + } + } while (!isPrinterAvailable(sample(printers), startAt, duration)); + + if (attempts > 10) continue; + + const printJob = createPrintJob(users, printers, startAt, duration); + if (printJob) { + if (!dryRun) { + await db.run( + `INSERT INTO printJob (id, printerId, userId, startAt, durationInMinutes, comments, aborted, abortReason) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, + [ + printJob.id, + printJob.printerId, + printJob.userId, + printJob.startAt, + printJob.durationInMinutes, + printJob.comments, + printJob.aborted ? 1 : 0, + printJob.abortReason, + ], + ); + } + existingJobs.push(printJob); + console.log("Inserted print job into database:", printJob.id); + } + } +}; + +const generateTestData = async (dryRun = false) => { + console.log("Starting test data generation..."); + const db = await initDB(); + + // Generate users and printers + const users = [ + ...Array.from({ length: 7 }, () => createUser(false)), + ...Array.from({ length: 3 }, () => createUser(true)), + ]; + const printers = Array.from({ length: numberOfPrinters }, (_, index) => createPrinter(index + 1)); + + if (!dryRun) { + // Insert users into the database + for (const user of users) { + await db.run( + `INSERT INTO user (id, github_id, name, displayName, email, role) + VALUES (?, ?, ?, ?, ?, ?)`, + [user.id, user.github_id, user.username, user.displayName, user.email, user.role], + ); + console.log("Inserted user into database:", user.id); + } + + // Insert printers into the database + for (const printer of printers) { + await db.run( + `INSERT INTO printer (id, name, description, status) + VALUES (?, ?, ?, ?)`, + [printer.id, printer.name, printer.description, printer.status], + ); + console.log("Inserted printer into database:", printer.id); + } + } + + // Generate print jobs for each day within the specified date range + let currentDay = startDate; + while (currentDay <= endDate) { + const weekdayName = currentDay.toFormat("EEEE"); // Get weekday name (e.g., 'Monday') + if (holidays.includes(currentDay.toISODate()) || avgPrintTimesPerDay[weekdayName] === 0) { + console.log(`Skipping holiday or no jobs scheduled: ${currentDay.toISODate()}`); + currentDay = currentDay.plus({ days: 1 }); + continue; + } + + const totalJobsForDay = avgPrintTimesPerDay[weekdayName]; + const totalDurationForDay = avgPrintDurationPerDay[weekdayName]; + + await generatePrintJobsForDay(users, printers, currentDay, totalJobsForDay, totalDurationForDay, db, dryRun); + currentDay = currentDay.plus({ days: 1 }); + } + + if (!dryRun) { + await db.close(); + console.log("Database connection closed. Test data generation complete."); + } else { + console.log("Dry run complete. No data was written to the database."); + } +}; + +const setConfigurations = (config) => { + if (config.startDate) startDate = DateTime.fromISO(config.startDate); + if (config.endDate) endDate = DateTime.fromISO(config.endDate); + if (config.numberOfPrinters) numberOfPrinters = config.numberOfPrinters; + if (config.avgPrintTimesPerDay) avgPrintTimesPerDay = config.avgPrintTimesPerDay; + if (config.avgPrintDurationPerDay) avgPrintDurationPerDay = config.avgPrintDurationPerDay; + if (config.printerUsage) printerUsage = config.printerUsage; + if (config.generalErrorRate !== undefined) generalErrorRate = config.generalErrorRate; + if (config.printerErrorRates) printerErrorRates = config.printerErrorRates; +}; + +// Example usage +setConfigurations({ + startDate: "2024-10-08", + endDate: "2024-11-08", + numberOfPrinters: 6, + avgPrintTimesPerDay: { + Monday: 4, // High usage + Tuesday: 2, // Low usage + Wednesday: 3, // Low usage + Thursday: 2, // Low usage + Friday: 8, // High usage + Saturday: 0, + Sunday: 0, + }, + avgPrintDurationPerDay: { + Monday: 300, // High total duration + Tuesday: 60, // Low total duration + Wednesday: 90, + Thursday: 60, + Friday: 240, + Saturday: 0, + Sunday: 0, + }, + printerUsage: { + "Drucker 1": 2.3, + "Drucker 2": 1.7, + "Drucker 3": 0.1, + "Drucker 4": 1.5, + "Drucker 5": 2.4, + "Drucker 6": 0.3, + "Drucker 7": 0.9, + "Drucker 8": 0.1, + }, + generalErrorRate: 0.05, // 5% general error rate + printerErrorRates: { + "Drucker 1": 0.02, + "Drucker 2": 0.03, + "Drucker 3": 0.1, + "Drucker 4": 0.05, + "Drucker 5": 0.04, + "Drucker 6": 0.02, + "Drucker 7": 0.01, + "PrinteDrucker 8": 0.03, + }, +}); + +generateTestData(process.argv.includes("--dry-run")) + .then(() => { + console.log("Test data generation script finished."); + }) + .catch((err) => { + console.error("Error generating test data:", err); + }); + +================ +File: src/app/admin/about/page.tsx +================ +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Über MYP", +}; + +export default async function AdminPage() { + return ( + + + Über MYP + + MYP — Manage Your Printer + + + +

+ MYP ist eine Webanwendung zur Reservierung von 3D-Druckern. Sie wurde im Rahmen des + Abschlussprojektes der Fachinformatiker Ausbildung für Daten- und Prozessanalyse für die Technische + Berufsausbildung des Mercedes-Benz Werkes Berlin-Marienfelde entwickelt. +

+

+ © 2024{" "} + + Torben Haack + +

+
+
+ ); +} + +================ +File: src/app/admin/admin-sidebar.tsx +================ +"use client"; + +import { cn } from "@/utils/styles"; +import { FileIcon, HeartIcon, LayoutDashboardIcon, PrinterIcon, UsersIcon, WrenchIcon } from "lucide-react"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; + +interface AdminSite { + name: string; + path: string; + icon: React.ReactNode; +} + +export function AdminSidebar() { + const pathname = usePathname(); + const adminSites: AdminSite[] = [ + { + name: "Dashboard", + path: "/admin", + icon: , + }, + { + name: "Benutzer", + path: "/admin/users", + icon: , + }, + { + name: "Drucker", + path: "/admin/printers", + icon: , + }, + { + name: "Druckaufträge", + path: "/admin/jobs", + icon: , + }, + { + name: "Einstellungen", + path: "/admin/settings", + icon: , + }, + { + name: "Über MYP", + path: "/admin/about", + icon: , + }, + ]; + + return ( +
    + {adminSites.map((site) => ( +
  • + + {site.icon} + {site.name} + +
  • + ))} +
+ ); +} + +================ +File: src/app/admin/charts/printer-error-chart.tsx +================ +"use client"; + +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart"; +import { Bar, BarChart, CartesianGrid, LabelList, XAxis, YAxis } from "recharts"; + +export const description = "Ein Säulendiagramm zur Darstellung der Abbruchgründe und ihrer Häufigkeit"; + +interface AbortReasonCountChartProps { + abortReasonCount: { + abortReason: string; + count: number; + }[]; +} + +const chartConfig = { + abortReason: { + label: "Abbruchgrund", + }, +} satisfies ChartConfig; + +export function AbortReasonCountChart({ abortReasonCount }: AbortReasonCountChartProps) { + // Transform data to fit the chart structure + const chartData = abortReasonCount.map((reason) => ({ + abortReason: reason.abortReason, + count: reason.count, + })); + + return ( + + + Abbruchgründe + Häufigkeit der Abbruchgründe für Druckaufträge + + + + + + value} + /> + `${value}`} /> + } /> + + `${value}`} + /> + + + + + + ); +} + +================ +File: src/app/admin/charts/printer-error-rate.tsx +================ +"use client"; +import { Bar, BarChart, CartesianGrid, LabelList, XAxis, YAxis } from "recharts"; + +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart"; +import type { PrinterErrorRate } from "@/utils/analytics/error-rate"; + +export const description = "Ein Säulendiagramm zur Darstellung der Fehlerrate"; + +interface PrinterErrorRateChartProps { + printerErrorRate: PrinterErrorRate[]; +} + +const chartConfig = { + errorRate: { + label: "Fehlerrate", + }, +} satisfies ChartConfig; + +export function PrinterErrorRateChart({ printerErrorRate }: PrinterErrorRateChartProps) { + // Transform data to fit the chart structure + const chartData = printerErrorRate.map((printer) => ({ + printer: printer.name, + errorRate: printer.errorRate, + })); + + return ( + + + Fehlerrate + Fehlerrate der Drucker in Prozent + + + + + + value} + /> + `${value}%`} /> + } /> + + `${value}%`} + /> + + + + + + ); +} + +================ +File: src/app/admin/charts/printer-forecast.tsx +================ +"use client"; + +import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; +import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart"; +import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"; + +export const description = "Ein Bereichsdiagramm zur Darstellung der prognostizierten Nutzung pro Wochentag"; + +interface ForecastData { + day: number; // 0 for Sunday, 1 for Monday, ..., 6 for Saturday + usageMinutes: number; +} + +interface ForecastChartProps { + forecastData: ForecastData[]; +} + +const chartConfig = { + usage: { + label: "Prognostizierte Nutzung", + color: "hsl(var(--chart-1))", + }, +} satisfies ChartConfig; + +const daysOfWeek = ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"]; + +export function ForecastPrinterUsageChart({ forecastData }: ForecastChartProps) { + // Transform and slice data to fit the chart structure + const chartData = forecastData.map((data) => ({ + //slice(1, forecastData.length - 1). + day: daysOfWeek[data.day], // Map day number to weekday name + usage: data.usageMinutes, + })); + + return ( + + + Prognostizierte Nutzung pro Wochentag + + + + + + + + } /> + + + + + +
+ Zeigt die prognostizierte Nutzungszeit pro Wochentag in Minuten. +
+
+ Besten Tage zur Wartung: {bestMaintenanceDays(forecastData)} +
+
+
+ ); +} + +function bestMaintenanceDays(forecastData: ForecastData[]) { + const sortedData = forecastData.map((a) => a).sort((a, b) => a.usageMinutes - b.usageMinutes); // Sort ascending + + const q1Index = Math.floor(sortedData.length * 0.33); + const q1 = sortedData[q1Index].usageMinutes; // First quartile (Q1) value + + const filteredData = sortedData.filter((data) => data.usageMinutes <= q1); + + return filteredData + .map((data) => { + const days = ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"]; + return days[data.day]; + }) + .join(", "); +} + +================ +File: src/app/admin/charts/printer-utilization.tsx +================ +"use client"; + +import { TrendingUp } from "lucide-react"; +import * as React from "react"; +import { Label, Pie, PieChart } from "recharts"; + +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; +import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart"; + +export const description = "Nutzung des Druckers"; + +interface ComponentProps { + data: { + printerId: string; + utilizationPercentage: number; + name: string; + }; +} + +const chartConfig = {} satisfies ChartConfig; + +export function PrinterUtilizationChart({ data }: ComponentProps) { + const totalUtilization = React.useMemo(() => data.utilizationPercentage, [data]); + const dataWithColor = { + ...data, + fill: "rgb(34 197 94)", + }; + const free = { + printerId: "-", + utilizationPercentage: 1 - data.utilizationPercentage, + name: "(Frei)", + fill: "rgb(212 212 212)", + }; + + return ( + + + {data.name} + Nutzung des ausgewählten Druckers + + + + + } /> + + + + + + +
+ Übersicht der Nutzung +
+
Aktuelle Auslastung des Druckers
+
+
+ ); +} + +================ +File: src/app/admin/charts/printer-volume.tsx +================ +"use client"; +import { Bar, BarChart, CartesianGrid, LabelList, XAxis } from "recharts"; + +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; +import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart"; + +export const description = "Ein Balkendiagramm mit Beschriftung"; + +interface PrintVolumes { + today: number; + thisWeek: number; + thisMonth: number; +} + +const chartConfig = { + volume: { + label: "Volumen", + }, +} satisfies ChartConfig; + +interface PrinterVolumeChartProps { + printerVolume: PrintVolumes; +} + +export function PrinterVolumeChart({ printerVolume }: PrinterVolumeChartProps) { + const chartData = [ + { period: "Heute", volume: printerVolume.today, color: "hsl(var(--chart-1))" }, + { period: "Diese Woche", volume: printerVolume.thisWeek, color: "hsl(var(--chart-2))" }, + { period: "Diesen Monat", volume: printerVolume.thisMonth, color: "hsl(var(--chart-3))" }, + ]; + + return ( + + + Druckvolumen + Vergleich: Heute, Diese Woche, Diesen Monat + + + + + + value} + /> + } /> + + + + + + + +
+ Zeigt das Druckvolumen für heute, diese Woche und diesen Monat +
+
+
+ ); +} + +================ +File: src/app/admin/jobs/page.tsx +================ +import { columns } from "@/app/my/jobs/columns"; +import { JobsTable } from "@/app/my/jobs/data-table"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { db } from "@/server/db"; +import { printJobs } from "@/server/db/schema"; +import { desc } from "drizzle-orm"; +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Alle Druckaufträge", +}; + +export default async function AdminJobsPage() { + const allJobs = await db.query.printJobs.findMany({ + orderBy: [desc(printJobs.startAt)], + with: { + user: true, + printer: true, + }, + }); + + return ( + + +
+ Druckaufträge + Alle Druckaufträge +
+
+ + + +
+ ); +} + +================ +File: src/app/admin/layout.tsx +================ +import { AdminSidebar } from "@/app/admin/admin-sidebar"; +import { validateRequest } from "@/server/auth"; +import { UserRole } from "@/server/auth/permissions"; +import { IS_NOT, guard } from "@/utils/guard"; +import { redirect } from "next/navigation"; + +interface AdminLayoutProps { + children: React.ReactNode; +} + +export const dynamic = "force-dynamic"; + +export default async function AdminLayout(props: AdminLayoutProps) { + const { children } = props; + const { user } = await validateRequest(); + + if (guard(user, IS_NOT, UserRole.ADMIN)) { + redirect("/"); + } + + return ( +
+
+

Admin

+
+
+ +
{children}
+
+
+ ); +} + +================ +File: src/app/admin/page.tsx +================ +import { AbortReasonCountChart } from "@/app/admin/charts/printer-error-chart"; +import { PrinterErrorRateChart } from "@/app/admin/charts/printer-error-rate"; +import { ForecastPrinterUsageChart } from "@/app/admin/charts/printer-forecast"; +import { PrinterUtilizationChart } from "@/app/admin/charts/printer-utilization"; +import { PrinterVolumeChart } from "@/app/admin/charts/printer-volume"; +import { DataCard } from "@/components/data-card"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { db } from "@/server/db"; +import { calculatePrinterErrorRate } from "@/utils/analytics/error-rate"; +import { calculateAbortReasonsCount } from "@/utils/analytics/errors"; +import { forecastPrinterUsage } from "@/utils/analytics/forecast"; +import { calculatePrinterUtilization } from "@/utils/analytics/utilization"; +import { calculatePrintVolumes } from "@/utils/analytics/volume"; +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Admin Dashboard", +}; + +export const dynamic = "force-dynamic"; + +export default async function AdminPage() { + const currentDate = new Date(); + + const lastMonth = new Date(); + lastMonth.setDate(currentDate.getDate() - 31); + const printers = await db.query.printers.findMany({}); + const printJobs = await db.query.printJobs.findMany({ + where: (job, { gte }) => gte(job.startAt, lastMonth), + with: { + printer: true, + }, + }); + if (printJobs.length < 1) { + return ( + + + Druckaufträge + Zurzeit sind keine Druckaufträge verfügbar. + + +

Aktualisieren Sie die Seite oder prüfen Sie später erneut, ob neue Druckaufträge verfügbar sind.

+
+
+ ); + } + + const currentPrintJobs = printJobs.filter((job) => { + if (job.aborted) return false; + + const endAt = job.startAt.getTime() + job.durationInMinutes * 1000 * 60; + + return endAt > currentDate.getTime(); + }); + const occupiedPrinters = currentPrintJobs.map((job) => job.printer.id); + const freePrinters = printers.filter((printer) => !occupiedPrinters.includes(printer.id)); + const printerUtilization = calculatePrinterUtilization(printJobs); + const printerVolume = calculatePrintVolumes(printJobs); + const printerAbortReasons = calculateAbortReasonsCount(printJobs); + const printerErrorRate = calculatePrinterErrorRate(printJobs); + const printerForecast = forecastPrinterUsage(printJobs); + + return ( + <> + + + Allgemein + Druckerauslastung + Fehlerberichte + Prognosen + + +
+
+ +
+ + +
+
+ +
+
+ +
+ {printerUtilization.map((data) => ( + + ))} +
+
+ +
+
+ +
+
+ +
+
+
+ +
+
+ ({ + day: index, + usageMinutes, + }))} + /> +
+
+
+
+ + ); +} + +================ +File: src/app/admin/printers/columns.tsx +================ +"use client"; +import type { printers } from "@/server/db/schema"; +import type { ColumnDef } from "@tanstack/react-table"; +import type { InferSelectModel } from "drizzle-orm"; +import { ArrowUpDown, MoreHorizontal, PencilIcon } from "lucide-react"; + +import { EditPrinterDialogContent, EditPrinterDialogTrigger } from "@/app/admin/printers/dialogs/edit-printer"; +import { Button } from "@/components/ui/button"; +import { Dialog } from "@/components/ui/dialog"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { type PrinterStatus, translatePrinterStatus } from "@/utils/printers"; +import { useState } from "react"; + +// This type is used to define the shape of our data. +// You can use a Zod schema here if you want. + +export const columns: ColumnDef>[] = [ + { + accessorKey: "id", + header: ({ column }) => { + return ( + + ); + }, + }, + { + accessorKey: "name", + header: "Name", + }, + { + accessorKey: "description", + header: "Beschreibung", + }, + { + accessorKey: "status", + header: "Status", + cell: ({ row }) => { + const status = row.getValue("status"); + const translated = translatePrinterStatus(status as PrinterStatus); + + return translated; + }, + }, + { + id: "actions", + cell: ({ row }) => { + const printer = row.original; + const [open, setOpen] = useState(false); + + return ( + + + + + + + Aktionen + ABC + + +
+ + Bearbeiten +
+
+
+
+
+ +
+ ); + }, + }, +]; + +================ +File: src/app/admin/printers/data-table.tsx +================ +"use client"; + +import { + type ColumnDef, + type ColumnFiltersState, + type SortingState, + type VisibilityState, + flexRender, + getCoreRowModel, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, +} from "@tanstack/react-table"; + +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { Input } from "@/components/ui/input"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; +import { SlidersHorizontalIcon } from "lucide-react"; +import { useState } from "react"; + +interface DataTableProps { + columns: ColumnDef[]; + data: TData[]; +} + +export function DataTable({ columns, data }: DataTableProps) { + const [sorting, setSorting] = useState([]); + const [columnFilters, setColumnFilters] = useState([]); + const [columnVisibility, setColumnVisibility] = useState({}); + + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + onSortingChange: setSorting, + getSortedRowModel: getSortedRowModel(), + onColumnFiltersChange: setColumnFilters, + getFilteredRowModel: getFilteredRowModel(), + onColumnVisibilityChange: setColumnVisibility, + state: { + sorting, + columnFilters, + columnVisibility, + }, + }); + + return ( +
+
+ table.getColumn("name")?.setFilterValue(event.target.value)} + className="max-w-sm" + /> + + + + + + {table + .getAllColumns() + .filter((column) => column.getCanHide()) + .map((column) => { + return ( + column.toggleVisibility(!!value)} + > + {column.id} + + ); + })} + + +
+ +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} + + ); + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + {flexRender(cell.column.columnDef.cell, cell.getContext())} + ))} + + )) + ) : ( + + + Keine Ergebnisse gefunden. + + + )} + +
+
+
+ + +
+
+ ); +} + +================ +File: src/app/admin/printers/dialogs/create-printer.tsx +================ +"use client"; + +import { PrinterForm } from "@/app/admin/printers/form"; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; +import { useState } from "react"; + +interface CreatePrinterDialogProps { + children: React.ReactNode; +} + +export function CreatePrinterDialog(props: CreatePrinterDialogProps) { + const { children } = props; + const [open, setOpen] = useState(false); + + return ( + + {children} + + + Drucker erstellen + + + + + ); +} + +================ +File: src/app/admin/printers/dialogs/delete-printer.tsx +================ +"use client"; + +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@/components/ui/alert-dialog"; +import { Button } from "@/components/ui/button"; +import { useToast } from "@/components/ui/use-toast"; +import { deletePrinter } from "@/server/actions/printers"; +import { TrashIcon } from "lucide-react"; + +interface DeletePrinterDialogProps { + printerId: string; + setOpen: (state: boolean) => void; +} +export function DeletePrinterDialog(props: DeletePrinterDialogProps) { + const { printerId, setOpen } = props; + const { toast } = useToast(); + + async function onSubmit() { + toast({ + description: "Drucker wird gelöscht...", + }); + try { + const result = await deletePrinter(printerId); + if (result?.error) { + toast({ + description: result.error, + variant: "destructive", + }); + } + toast({ + description: "Drucker wurde gelöscht.", + }); + setOpen(false); + } catch (error) { + if (error instanceof Error) { + toast({ + description: error.message, + variant: "destructive", + }); + } else { + toast({ + description: "Ein unbekannter Fehler ist aufgetreten.", + variant: "destructive", + }); + } + } + } + + return ( + + + + + + + Bist Du dir sicher? + + Diese Aktion kann nicht rückgängig gemacht werden. Der Drucker und die damit verbundenen Daten werden + unwiderruflich gelöscht. + + + + Abbrechen + + Ja, löschen + + + + + ); +} + +================ +File: src/app/admin/printers/dialogs/edit-printer.tsx +================ +import { PrinterForm } from "@/app/admin/printers/form"; +import { DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; +import type { InferResultType } from "@/utils/drizzle"; + +interface EditPrinterDialogTriggerProps { + children: React.ReactNode; +} + +export function EditPrinterDialogTrigger(props: EditPrinterDialogTriggerProps) { + const { children } = props; + + return {children}; +} + +interface EditPrinterDialogContentProps { + printer: InferResultType<"printers">; + setOpen: (open: boolean) => void; +} +export function EditPrinterDialogContent(props: EditPrinterDialogContentProps) { + const { printer, setOpen } = props; + + return ( + + + Drucker bearbeiten + + + + ); +} + +================ +File: src/app/admin/printers/form.tsx +================ +"use client"; +import { DeletePrinterDialog } from "@/app/admin/printers/dialogs/delete-printer"; +import { Button } from "@/components/ui/button"; +import { DialogClose } from "@/components/ui/dialog"; +import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { useToast } from "@/components/ui/use-toast"; +import { createPrinter, updatePrinter } from "@/server/actions/printers"; +import type { InferResultType } from "@/utils/drizzle"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { SaveIcon, XCircleIcon } from "lucide-react"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; + +export const formSchema = z.object({ + name: z + .string() + .min(2, { + message: "Der Name muss mindestens 2 Zeichen lang sein.", + }) + .max(50), + description: z + .string() + .min(2, { + message: "Die Beschreibung muss mindestens 2 Zeichen lang sein.", + }) + .max(50), + status: z.coerce.number().int().min(0).max(1), +}); + +interface PrinterFormProps { + printer?: InferResultType<"printers">; + setOpen: (state: boolean) => void; +} + +export function PrinterForm(props: PrinterFormProps) { + const { printer, setOpen } = props; + const { toast } = useToast(); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + name: printer?.name ?? "", + description: printer?.description ?? "", + status: printer?.status ?? 0, + }, + }); + + // 2. Define a submit handler. + async function onSubmit(values: z.infer) { + // TODO: create or update + if (printer) { + toast({ + description: "Drucker wird aktualisiert...", + }); + + // Update + try { + const result = await updatePrinter(printer.id, { + description: values.description, + name: values.name, + status: values.status, + }); + if (result?.error) { + toast({ + description: result.error, + variant: "destructive", + }); + } + + setOpen(false); + + toast({ + description: "Drucker wurde aktualisiert.", + variant: "default", + }); + } catch (error) { + if (error instanceof Error) { + toast({ + description: error.message, + variant: "destructive", + }); + } else { + toast({ + description: "Ein unbekannter Fehler ist aufgetreten.", + variant: "destructive", + }); + } + } + } else { + toast({ + description: "Drucker wird erstellt...", + variant: "default", + }); + + // Create + try { + const result = await createPrinter({ + description: values.description, + name: values.name, + status: values.status, + }); + if (result?.error) { + toast({ + description: result.error, + variant: "destructive", + }); + } + + setOpen(false); + + toast({ + description: "Drucker wurde erstellt.", + variant: "default", + }); + } catch (error) { + if (error instanceof Error) { + toast({ + description: error.message, + variant: "destructive", + }); + } else { + toast({ + description: "Ein unbekannter Fehler ist aufgetreten.", + variant: "destructive", + }); + } + } + } + } + + return ( +
+ + ( + + Name + + + + Bitte gib einen eindeutigen Namen für den Drucker ein. + + + )} + /> + ( + + Beschreibung + + + + Füge eine kurze Beschreibung des Druckers hinzu. + + + )} + /> + ( + + Status + + Wähle den aktuellen Status des Druckers. + + + )} + /> +
+ {printer && } + {!printer && ( + + + + )} + +
+ + + ); +} + +================ +File: src/app/admin/printers/page.tsx +================ +import { columns } from "@/app/admin/printers/columns"; +import { DataTable } from "@/app/admin/printers/data-table"; +import { CreatePrinterDialog } from "@/app/admin/printers/dialogs/create-printer"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { db } from "@/server/db"; +import { PlusCircleIcon } from "lucide-react"; + +export default async function AdminPage() { + const data = await db.query.printers.findMany(); + + return ( + + +
+ Druckerverwaltung + Suche, Bearbeite, Lösche und Erstelle Drucker +
+ + + +
+ + + +
+ ); +} + +================ +File: src/app/admin/settings/download/route.ts +================ +import fs from "node:fs"; + +export const dynamic = 'force-dynamic'; + +export async function GET() { + return new Response(fs.readFileSync("./db/sqlite.db")); +} + +================ +File: src/app/admin/settings/page.tsx +================ +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import Link from "next/link"; + +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Systemeinstellungen", +}; + +export default function AdminPage() { + return ( + + + Einstellungen + Systemeinstellungen + + +
+

Datenbank herunterladen

+ +
+
+
+ ); +} + +================ +File: src/app/admin/users/columns.tsx +================ +"use client"; + +import { type UserRole, translateUserRole } from "@/server/auth/permissions"; +import type { users } from "@/server/db/schema"; +import type { ColumnDef } from "@tanstack/react-table"; +import type { InferSelectModel } from "drizzle-orm"; +import { + ArrowUpDown, + MailIcon, + MessageCircleIcon, + MoreHorizontal, +} from "lucide-react"; + +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import Link from "next/link"; +import { + EditUserDialogContent, + EditUserDialogRoot, + EditUserDialogTrigger, +} from "@/app/admin/users/dialog"; + +// This type is used to define the shape of our data. +// You can use a Zod schema here if you want. +export type User = { + id: string; + github_id: number; + username: string; + displayName: string; + email: string; + role: string; +}; + +export const columns: ColumnDef>[] = [ + { + accessorKey: "id", + header: ({ column }) => { + return ( + + ); + }, + }, + { + accessorKey: "github_id", + header: "GitHub ID", + }, + { + accessorKey: "username", + header: "Username", + }, + { + accessorKey: "displayName", + header: "Name", + }, + { + accessorKey: "email", + header: "E-Mail", + }, + { + accessorKey: "role", + header: "Rolle", + cell: ({ row }) => { + const role = row.getValue("role"); + const translated = translateUserRole(role as UserRole); + + return translated; + }, + }, + { + id: "actions", + cell: ({ row }) => { + const user = row.original; + + return ( + + + + + + + Aktionen + + + + Teams-Chat öffnen + + + + + + E-Mail schicken + + + + + + + + + + + ); + }, + }, +]; + +function generateTeamsChatURL(email: string) { + return `https://teams.microsoft.com/l/chat/0/0?users=${email}`; +} + +function generateEMailURL(email: string) { + return `mailto:${email}`; +} + +================ +File: src/app/admin/users/data-table.tsx +================ +"use client"; + +import { + type ColumnDef, + type ColumnFiltersState, + type SortingState, + type VisibilityState, + flexRender, + getCoreRowModel, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, +} from "@tanstack/react-table"; + +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { Input } from "@/components/ui/input"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; +import { SlidersHorizontalIcon } from "lucide-react"; +import { useState } from "react"; + +interface DataTableProps { + columns: ColumnDef[]; + data: TData[]; +} + +export function DataTable({ columns, data }: DataTableProps) { + const [sorting, setSorting] = useState([]); + const [columnFilters, setColumnFilters] = useState([]); + const [columnVisibility, setColumnVisibility] = useState({}); + + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + onSortingChange: setSorting, + getSortedRowModel: getSortedRowModel(), + onColumnFiltersChange: setColumnFilters, + getFilteredRowModel: getFilteredRowModel(), + onColumnVisibilityChange: setColumnVisibility, + state: { + sorting, + columnFilters, + columnVisibility, + }, + }); + + return ( +
+
+ table.getColumn("email")?.setFilterValue(event.target.value)} + className="max-w-sm" + /> + + + + + + {table + .getAllColumns() + .filter((column) => column.getCanHide()) + .map((column) => { + return ( + column.toggleVisibility(!!value)} + > + {column.id} + + ); + })} + + +
+ +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} + + ); + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + {flexRender(cell.column.columnDef.cell, cell.getContext())} + ))} + + )) + ) : ( + + + Keine Ergebnisse gefunden. + + + )} + +
+
+
+ + +
+
+ ); +} + +================ +File: src/app/admin/users/dialog.tsx +================ +import type { User } from "@/app/admin/users/columns"; +import { ProfileForm } from "@/app/admin/users/form"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { PencilIcon } from "lucide-react"; + +interface EditUserDialogRootProps { + children: React.ReactNode; +} + +export function EditUserDialogRoot(props: EditUserDialogRootProps) { + const { children } = props; + + return {children}; +} + +export function EditUserDialogTrigger() { + return ( + + + Benutzer bearbeiten + + ); +} + +interface EditUserDialogContentProps { + user: User; +} + +export function EditUserDialogContent(props: EditUserDialogContentProps) { + const { user } = props; + + if (!user) { + return; + } + + return ( + + + Benutzer bearbeiten + + Hinweis: In den seltensten Fällen sollten die Daten + eines Benutzers geändert werden. Dies kann zu unerwarteten Problemen + führen. + + + + + ); +} + +================ +File: src/app/admin/users/form.tsx +================ +"use client"; + +import type { User } from "@/app/admin/users/columns"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@/components/ui/alert-dialog"; +import { Button } from "@/components/ui/button"; +import { DialogClose } from "@/components/ui/dialog"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { useToast } from "@/components/ui/use-toast"; +import { deleteUser, updateUser } from "@/server/actions/users"; +import type { UserRole } from "@/server/auth/permissions"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { SaveIcon, TrashIcon } from "lucide-react"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; + +export const formSchema = z.object({ + username: z + .string() + .min(2, { + message: "Der Benutzername muss mindestens 2 Zeichen lang sein.", + }) + .max(50), + displayName: z + .string() + .min(2, { + message: "Der Anzeigename muss mindestens 2 Zeichen lang sein.", + }) + .max(50), + email: z.string().email(), + role: z.enum(["admin", "user", "guest"]), +}); + +interface ProfileFormProps { + user: User; +} + +export function ProfileForm(props: ProfileFormProps) { + const { user } = props; + const { toast } = useToast(); + + // 1. Define your form. + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + username: user.username, + displayName: user.displayName, + email: user.email, + role: user.role as UserRole, + }, + }); + + // 2. Define a submit handler. + async function onSubmit(values: z.infer) { + toast({ description: "Benutzerprofil wird aktualisiert..." }); + + await updateUser(user.id, values); + + toast({ description: "Benutzerprofil wurde aktualisiert." }); + } + + return ( +
+ + ( + + Benutzername + + + + + Nur in Ausnahmefällen sollte der Benutzername geändert werden. + + + + )} + /> + ( + + Anzeigename + + + + + Der Anzeigename darf frei verändert werden. + + + + )} + /> + ( + + E-Mail Adresse + + + + + Nur in Ausnahmefällen sollte die E-Mail Adresse geändert werden. + + + + )} + /> + ( + + Benutzerrolle + + + Die Benutzerrolle bestimmt die Berechtigungen des Benutzers. + + + + )} + /> +
+ + + + + + + Bist du dir sicher? + + Diese Aktion kann nicht rückgängig gemacht werden. Das + Benutzerprofil und die damit verbundenen Daten werden + unwiderruflich gelöscht. + + + + Abbrechen + { + toast({ description: "Benutzerprofil wird gelöscht..." }); + deleteUser(user.id); + toast({ description: "Benutzerprofil wurde gelöscht." }); + }} + > + Ja, löschen + + + + + + + +
+ + + ); +} + +================ +File: src/app/admin/users/page.tsx +================ +import { columns } from "@/app/admin/users/columns"; +import { DataTable } from "@/app/admin/users/data-table"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { db } from "@/server/db"; + +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Alle Benutzer", +}; + +export default async function AdminPage() { + const data = await db.query.users.findMany(); + + return ( + + + Benutzerverwaltung + Suche, Bearbeite und Lösche Benutzer + + + + + + ); +} + +================ +File: src/app/api/job/[jobId]/remaining-time/route.ts +================ +import { db } from "@/server/db"; +import { printJobs } from "@/server/db/schema"; +import { eq } from "drizzle-orm"; + +export const dynamic = "force-dynamic"; + +interface RemainingTimeRouteProps { + params: { + jobId: string; + }; +} +export async function GET(request: Request, { params }: RemainingTimeRouteProps) { + // Trying to fix build error in container... + if (params.jobId === undefined) { + return Response.json({}); + } + + // Get the job details + const jobDetails = await db.query.printJobs.findFirst({ + where: eq(printJobs.id, params.jobId), + }); + + // Check if the job exists + if (!jobDetails) { + return Response.json({ + id: params.jobId, + error: "Job not found", + }); + } + + // Calculate the remaining time + const startAt = new Date(jobDetails.startAt).getTime(); + const endAt = startAt + jobDetails.durationInMinutes * 60 * 1000; + const remainingTime = Math.max(0, endAt - Date.now()); + + // Return the remaining time + return Response.json({ + id: params.jobId, + remainingTime, + }); +} + +================ +File: src/app/api/printers/route.ts +================ +import { getPrinters } from "@/server/actions/printers"; + +export const dynamic = "force-dynamic"; + +export async function GET() { + const printers = await getPrinters(); + + return Response.json(printers); +} + +================ +File: src/app/auth/login/callback/route.ts +================ +import { lucia } from "@/server/auth"; +import { type GitHubUserResult, github } from "@/server/auth/oauth"; +import { db } from "@/server/db"; +import { users } from "@/server/db/schema"; +import { OAuth2RequestError } from "arctic"; +import { eq } from "drizzle-orm"; +import { generateIdFromEntropySize } from "lucia"; +import { cookies } from "next/headers"; + +export const dynamic = "force-dynamic"; + +interface GithubEmailResponse { + email: string; + primary: boolean; + verified: boolean; + visibility: string; +} + +export async function GET(request: Request): Promise { + const url = new URL(request.url); + const code = url.searchParams.get("code"); + const state = url.searchParams.get("state"); + const storedState = cookies().get("github_oauth_state")?.value ?? null; + if (!code || !state || !storedState || state !== storedState) { + return new Response( + JSON.stringify({ + status_text: "Something is wrong", + data: { code, state, storedState }, + }), + { + status: 400, + }, + ); + } + + try { + const tokens = await github.validateAuthorizationCode(code); + const githubUserResponse = await fetch("https://git.i.mercedes-benz.com/api/v3/user", { + headers: { + Authorization: `Bearer ${tokens.accessToken}`, + }, + }); + const githubUser: GitHubUserResult = await githubUserResponse.json(); + + // Sometimes email can be null in the user query. + if (githubUser.email === null || githubUser.email === undefined) { + const githubEmailResponse = await fetch("https://git.i.mercedes-benz.com/api/v3/user/emails", { + headers: { + Authorization: `Bearer ${tokens.accessToken}`, + }, + }); + const githubUserEmail: GithubEmailResponse[] = await githubEmailResponse.json(); + githubUser.email = githubUserEmail[0].email; + } + const existingUser = await db.query.users.findFirst({ + where: eq(users.github_id, githubUser.id), + }); + + if (existingUser) { + const session = await lucia.createSession(existingUser.id, {}); + const sessionCookie = lucia.createSessionCookie(session.id); + cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); + return new Response(null, { + status: 302, + headers: { + Location: "/", + }, + }); + } + + const userId = generateIdFromEntropySize(10); // 16 characters long + + await db.insert(users).values({ + id: userId, + github_id: githubUser.id, + username: githubUser.login, + displayName: githubUser.name, + email: githubUser.email, + }); + + const session = await lucia.createSession(userId, {}); + const sessionCookie = lucia.createSessionCookie(session.id); + cookies().set(sessionCookie.name, sessionCookie.value, { + ...sessionCookie.attributes, + secure: false, // Else cookie does not get set cause IT has not provided us an SSL certificate yet + }); + return new Response(null, { + status: 302, + headers: { + Location: "/", + }, + }); + } catch (e) { + // the specific error message depends on the provider + if (e instanceof OAuth2RequestError) { + // invalid code + return new Response( + JSON.stringify({ + status_text: "Invalid code", + error: JSON.stringify(e), + }), + { + status: 400, + }, + ); + } + return new Response(null, { + status: 500, + }); + } +} + +================ +File: src/app/auth/login/route.ts +================ +import { github } from "@/server/auth/oauth"; +import { generateState } from "arctic"; +import { cookies } from "next/headers"; + +export const dynamic = "force-dynamic"; + +export async function GET(): Promise { + const state = generateState(); + const url = await github.createAuthorizationURL(state, { + scopes: ["user"], + }); + const ONE_HOUR = 60 * 60; + + cookies().set("github_oauth_state", state, { + path: "/", + secure: false, //process.env.NODE_ENV === "production", -- can't be used until SSL certificate is provided by IT + httpOnly: true, + maxAge: ONE_HOUR, + sameSite: "lax", + }); + + return Response.redirect(url); +} + +================ +File: src/app/globals.css +================ +@tailwind base; +@tailwind components; +@tailwind utilities; + + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 222.2 84% 4.9%; + --card: 0 0% 100%; + --card-foreground: 222.2 84% 4.9%; + --popover: 0 0% 100%; + --popover-foreground: 222.2 84% 4.9%; + --primary: 221.2 83.2% 53.3%; + --primary-foreground: 210 40% 98%; + --secondary: 210 40% 96.1%; + --secondary-foreground: 222.2 47.4% 11.2%; + --muted: 210 40% 96.1%; + --muted-foreground: 215.4 16.3% 46.9%; + --accent: 210 40% 96.1%; + --accent-foreground: 222.2 47.4% 11.2%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 40% 98%; + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + --ring: 221.2 83.2% 53.3%; + --radius: 0.75rem; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + } + + .dark { + --background: 222.2 84% 4.9%; + --foreground: 210 40% 98%; + --card: 222.2 84% 4.9%; + --card-foreground: 210 40% 98%; + --popover: 222.2 84% 4.9%; + --popover-foreground: 210 40% 98%; + --primary: 217.2 91.2% 59.8%; + --primary-foreground: 222.2 47.4% 11.2%; + --secondary: 217.2 32.6% 17.5%; + --secondary-foreground: 210 40% 98%; + --muted: 217.2 32.6% 17.5%; + --muted-foreground: 215 20.2% 65.1%; + --accent: 217.2 32.6% 17.5%; + --accent-foreground: 210 40% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 210 40% 98%; + --border: 217.2 32.6% 17.5%; + --input: 217.2 32.6% 17.5%; + --ring: 224.3 76.3% 48%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + } +} + +================ +File: src/app/job/[jobId]/cancel-form.tsx +================ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; + +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { useToast } from "@/components/ui/use-toast"; +import { abortPrintJob } from "@/server/actions/printJobs"; +import { TriangleAlertIcon } from "lucide-react"; +import { useState } from "react"; + +const formSchema = z.object({ + abortReason: z + .string() + .min(1, { + message: "Bitte gebe einen Grund für den Abbruch an.", + }) + .max(255, { + message: "Der Grund darf maximal 255 Zeichen lang sein.", + }), +}); + +interface CancelFormProps { + jobId: string; +} + +export function CancelForm(props: CancelFormProps) { + const { jobId } = props; + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + abortReason: "", + }, + }); + const { toast } = useToast(); + const [open, setOpen] = useState(false); + + async function onSubmit(values: z.infer) { + toast({ + description: "Druckauftrag wird abgebrochen...", + }); + try { + const result = await abortPrintJob(jobId, values.abortReason); + if (result?.error) { + toast({ + description: result.error, + variant: "destructive", + }); + } + setOpen(false); + toast({ + description: "Druckauftrag wurde abgebrochen.", + }); + } catch (error) { + if (error instanceof Error) { + toast({ + description: error.message, + variant: "destructive", + }); + } else { + toast({ + description: "Ein unbekannter Fehler ist aufgetreten.", + variant: "destructive", + }); + } + } + } + + return ( + + + + + + + Druckauftrag abbrechen? + + Du bist dabei, den Druckauftrag abzubrechen. Bitte beachte, dass ein abgebrochener Druckauftrag nicht wieder + aufgenommen werden kann und der Drucker sich automatisch abschaltet. + + +
+ + ( + + Grund für den Abbruch + + + + + Bitte teile uns den Grund für den Abbruch des Druckauftrags mit. Wenn der Drucker eine Fehlermeldung + anzeigt, gib bitte nur diese Fehlermeldung an. + + + + )} + /> +
+ + + + +
+ + +
+
+ ); +} + +================ +File: src/app/job/[jobId]/edit-comments.tsx +================ +"use client"; + +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { useToast } from "@/components/ui/use-toast"; +import { updatePrintComments } from "@/server/actions/printJobs"; +import { useDebouncedCallback } from "use-debounce"; + +interface EditCommentsProps { + defaultValue: string | null; + jobId: string; + disabled?: boolean; +} +export function EditComments(props: EditCommentsProps) { + const { defaultValue, jobId, disabled } = props; + const { toast } = useToast(); + + const debounced = useDebouncedCallback(async (value) => { + try { + const result = await updatePrintComments(jobId, value); + if (result?.error) { + toast({ + description: result.error, + variant: "destructive", + }); + } + toast({ + description: "Anmerkungen wurden gespeichert.", + }); + } catch (error) { + if (error instanceof Error) { + toast({ + description: error.message, + variant: "destructive", + }); + } else { + toast({ + description: "Ein unbekannter Fehler ist aufgetreten.", + variant: "destructive", + }); + } + } + }, 1000); + + return ( +
+ +