Compare commits
210 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf3bd3cd6d | ||
|
|
4c1a919d17 | ||
|
|
eb9748dd89 | ||
|
|
e295657621 | ||
|
|
da351b40b2 | ||
|
|
3c64e2e92d | ||
|
|
45381707ea | ||
|
|
b166f7c7d5 | ||
|
|
1f20737721 | ||
|
|
8ef4e1dc9d | ||
|
|
98c50bc03a | ||
|
|
7b28eb04ac | ||
|
|
ed15137003 | ||
|
|
2bf5c0137b | ||
|
|
22e6913005 | ||
|
|
f7eff0bcb7 | ||
|
|
45c90280f8 | ||
|
|
a4890f241b | ||
|
|
684409491f | ||
|
|
15b88f8177 | ||
|
|
20f204e70b | ||
|
|
b8191e41ee | ||
|
|
b906a218a5 | ||
|
|
6c3b46c037 | ||
|
|
3f1018ef93 | ||
|
|
620b065ac8 | ||
|
|
4cfa03834e | ||
|
|
d94238f6df | ||
|
|
eb37532de2 | ||
|
|
d79e71d6d7 | ||
|
|
f0e3c6a717 | ||
|
|
b2d47c7a37 | ||
|
|
498742e6ae | ||
|
|
94aab93f7d | ||
|
|
eba8ba30aa | ||
|
|
9d24c6ae7b | ||
|
|
02b8ba3d0a | ||
|
|
fc7b70b307 | ||
|
|
9cdbd60a23 | ||
|
|
5a4553a8a0 | ||
|
|
98637eec00 | ||
|
|
106c63890e | ||
|
|
f6b8388819 | ||
|
|
f1a29e4111 | ||
|
|
c9d82827ff | ||
|
|
75242f63fc | ||
|
|
2f161d1eb5 | ||
|
|
cad76edaad | ||
|
|
d0a8ef5ff2 | ||
|
|
a0d12a895e | ||
|
|
ad99787f75 | ||
|
|
c05cfbbe38 | ||
|
|
1f47a11091 | ||
|
|
5bba9522b3 | ||
|
|
5bdcd946cf | ||
|
|
6500493314 | ||
|
|
d0e3ae3610 | ||
|
|
8db827adeb | ||
|
|
d40eea5e46 | ||
|
|
a3ed130211 | ||
|
|
e8766b919a | ||
|
|
76ee9ee742 | ||
|
|
84ff4e126e | ||
|
|
23708b99b5 | ||
|
|
acf2cf00bd | ||
|
|
bb3f0f3a03 | ||
|
|
f4411a4ee5 | ||
|
|
e32871a005 | ||
|
|
a8318c74cf | ||
|
|
7e85926aa1 | ||
|
|
91fc3e9d13 | ||
|
|
6a333f198d | ||
|
|
7ea719c178 | ||
|
|
e23d9fbc44 | ||
|
|
3f2b92d886 | ||
|
|
89329607dc | ||
|
|
c2b8656783 | ||
|
|
0b1e745f03 | ||
|
|
7a35a0a1d3 | ||
|
|
bb2164f666 | ||
|
|
d16f250f80 | ||
|
|
c18b70c6f6 | ||
|
|
67f4f728fe | ||
|
|
b69684ad03 | ||
|
|
4ff021a85c | ||
|
|
f1b37d131f | ||
|
|
48bbc8015b | ||
|
|
56f0ce2f27 | ||
|
|
2dd5e28cbc | ||
|
|
c74217f6d8 | ||
|
|
01bbb85485 | ||
|
|
24aaa9c150 | ||
|
|
ea3cca563b | ||
|
|
e0d56ddadd | ||
|
|
32f06d7399 | ||
|
|
36bf99c013 | ||
|
|
7549fb5730 | ||
|
|
1517d83f6c | ||
|
|
993e12d4a5 | ||
|
|
806cb527d4 | ||
|
|
7e9d2d2c4f | ||
|
|
ec9b92000e | ||
|
|
d110900e85 | ||
|
|
cd3c3502f6 | ||
|
|
ccce9bffac | ||
|
|
f1ba25f9f5 | ||
|
|
548f51ac54 | ||
|
|
946e4fce1e | ||
|
|
40dcd0e54c | ||
|
|
bd338b86df | ||
|
|
1d4aa43b02 | ||
|
|
cc08f4ba43 | ||
|
|
d0ccaa9e54 | ||
|
|
dc0eff4e4c | ||
|
|
db9e404372 | ||
|
|
60ac89636e | ||
|
|
2b1365339e | ||
|
|
0cf2351c79 | ||
|
|
5c32fad34e | ||
|
|
7f0b681e88 | ||
|
|
e823af064e | ||
|
|
3bc6a465a2 | ||
|
|
e8b6578bd4 | ||
|
|
280c1432b7 | ||
|
|
fd82efdcee | ||
|
|
e354d82969 | ||
|
|
049ee56571 | ||
|
|
c6bb534a0d | ||
|
|
a0fdf256e7 | ||
|
|
d23a9f086c | ||
|
|
ac727c6c5b | ||
|
|
4b1a046149 | ||
|
|
cc964da9cf | ||
|
|
dbede48d4f | ||
|
|
6cd3c3a020 | ||
|
|
7ecbef806d | ||
|
|
1c70ca97bb | ||
|
|
a6493990d3 | ||
|
|
f8f4d23c4e | ||
|
|
1ef1711eea | ||
|
|
85981a880d | ||
|
|
84503b6404 | ||
|
|
bcc3ce036d | ||
|
|
0fe0514660 | ||
|
|
431ec861ba | ||
|
|
648b608036 | ||
|
|
4ac71d967f | ||
|
|
75d304ec6d | ||
|
|
afd96f5df1 | ||
|
|
4bfa6a5889 | ||
|
|
144034a305 | ||
|
|
f4187512ba | ||
|
|
b557297bf0 | ||
|
|
eb2273e28c | ||
|
|
091599b745 | ||
|
|
d70a5ca63e | ||
|
|
09ffd1db3d | ||
|
|
d90acf43e1 | ||
|
|
adb93af906 | ||
|
|
a36f0ea446 | ||
|
|
e4fcf2eca2 | ||
|
|
0ee16c7766 | ||
|
|
21c19298da | ||
|
|
3c65fed994 | ||
|
|
66046ddccd | ||
|
|
561d8186d3 | ||
|
|
312f8f24ab | ||
|
|
ba4b56360d | ||
|
|
02732a01da | ||
|
|
4307fa7d82 | ||
|
|
a1dc6afb2c | ||
|
|
92ce64b807 | ||
|
|
296939d1a0 | ||
|
|
dc8a5778d6 | ||
|
|
cf04e5bfe8 | ||
|
|
ace15ae1d3 | ||
|
|
d4b82a3a6f | ||
|
|
48cd0921df | ||
|
|
df02e48cfd | ||
|
|
4a6d868820 | ||
|
|
52556a4292 | ||
|
|
3a02ffb3e3 | ||
|
|
c4b9a7d782 | ||
|
|
5e8b221541 | ||
|
|
26720c8df3 | ||
|
|
a1ab742126 | ||
|
|
f21ad3d8a3 | ||
|
|
51d3087006 | ||
|
|
a08588a075 | ||
|
|
5d67a52b45 | ||
|
|
f29425c987 | ||
|
|
e3b8488d2b | ||
|
|
f49e1896b9 | ||
|
|
2092473cf3 | ||
|
|
c00849a154 | ||
|
|
8069946154 | ||
|
|
975800c1ab | ||
|
|
b82a80a11d | ||
|
|
244b61c901 | ||
|
|
c7325ac982 | ||
|
|
8fbdc68016 | ||
|
|
455b2c94cd | ||
|
|
c9a1026b50 | ||
|
|
f6f1ea0403 | ||
|
|
a636b32510 | ||
|
|
8ee1203ec6 | ||
|
|
bce5150757 | ||
|
|
117f6b4c93 | ||
|
|
6a8b0e35d7 | ||
|
|
ed96fc5f27 |
0
.cursor/commands/oldfkchecks.md
Normal file
0
.cursor/commands/oldfkchecks.md
Normal file
2
.gitignore
vendored
2
.gitignore
vendored
@@ -6,3 +6,5 @@ frontend/.env
|
||||
backend/.env
|
||||
|
||||
backend/images/*
|
||||
backend/backend-debug.log
|
||||
backend/*.log
|
||||
210
PERMISSIONS_GUIDE.md
Normal file
210
PERMISSIONS_GUIDE.md
Normal file
@@ -0,0 +1,210 @@
|
||||
# Berechtigungssystem - Dokumentation
|
||||
|
||||
## Übersicht
|
||||
|
||||
Das Trainingstagebuch verfügt nun über ein vollständiges rollenbasiertes Berechtigungssystem (RBAC - Role-Based Access Control). Der Club-Ersteller hat automatisch Admin-Rechte und kann anderen Mitgliedern Rollen und spezifische Berechtigungen zuweisen.
|
||||
|
||||
## Rollen
|
||||
|
||||
### 1. Administrator (admin)
|
||||
- **Vollzugriff** auf alle Funktionen
|
||||
- Kann Berechtigungen anderer Benutzer verwalten
|
||||
- Der Club-Ersteller ist automatisch Administrator und kann nicht degradiert werden
|
||||
|
||||
### 2. Trainer (trainer)
|
||||
- Kann Trainingseinheiten planen und verwalten
|
||||
- Kann Mitglieder anlegen und bearbeiten
|
||||
- Kann Spielpläne einsehen und bearbeiten
|
||||
- Kann Turniere organisieren
|
||||
- **Kann nicht**: Einstellungen ändern, Berechtigungen verwalten
|
||||
|
||||
### 3. Mannschaftsführer (team_manager)
|
||||
- Kann Teams und Spielpläne verwalten
|
||||
- Kann Spieler für Matches einteilen
|
||||
- Kann Spielergebnisse eintragen
|
||||
- **Kann nicht**: Trainingseinheiten planen, Mitglieder verwalten
|
||||
|
||||
### 4. Mitglied (member)
|
||||
- Nur Lesezugriff auf alle Bereiche
|
||||
- Kann eigene Daten einsehen
|
||||
- **Kann nicht**: Daten ändern oder löschen
|
||||
|
||||
## Berechtigungsbereiche
|
||||
|
||||
- **diary**: Trainingstagebuch
|
||||
- **members**: Mitgliederverwaltung
|
||||
- **teams**: Team-Management
|
||||
- **schedule**: Spielpläne
|
||||
- **tournaments**: Turniere
|
||||
- **statistics**: Statistiken
|
||||
- **settings**: Einstellungen
|
||||
- **permissions**: Berechtigungsverwaltung
|
||||
- **mytischtennis**: MyTischtennis-Integration (für alle zugänglich)
|
||||
|
||||
## Backend-Integration
|
||||
|
||||
### Migration ausführen
|
||||
|
||||
```sql
|
||||
mysql -u username -p database_name < backend/migrations/add_permissions_to_user_club.sql
|
||||
```
|
||||
|
||||
### Authorization Middleware verwenden
|
||||
|
||||
```javascript
|
||||
import { authorize, requireAdmin, requireOwner } from '../middleware/authorizationMiddleware.js';
|
||||
|
||||
// Beispiel: Nur Lesezugriff erforderlich
|
||||
router.get('/diary/:clubId', authenticate, authorize('diary', 'read'), getDiary);
|
||||
|
||||
// Beispiel: Schreibzugriff erforderlich
|
||||
router.post('/diary/:clubId', authenticate, authorize('diary', 'write'), createDiary);
|
||||
|
||||
// Beispiel: Admin-Rechte erforderlich
|
||||
router.put('/settings/:clubId', authenticate, requireAdmin(), updateSettings);
|
||||
|
||||
// Beispiel: Nur Owner
|
||||
router.delete('/club/:clubId', authenticate, requireOwner(), deleteClub);
|
||||
```
|
||||
|
||||
### Permission Service verwenden
|
||||
|
||||
```javascript
|
||||
import permissionService from '../services/permissionService.js';
|
||||
|
||||
// Berechtigungen prüfen
|
||||
const hasPermission = await permissionService.hasPermission(userId, clubId, 'diary', 'write');
|
||||
|
||||
// Rolle setzen
|
||||
await permissionService.setUserRole(userId, clubId, 'trainer', adminUserId);
|
||||
|
||||
// Custom Permissions setzen
|
||||
await permissionService.setCustomPermissions(
|
||||
userId,
|
||||
clubId,
|
||||
{ diary: { write: false }, members: { write: true } },
|
||||
adminUserId
|
||||
);
|
||||
```
|
||||
|
||||
## Frontend-Integration
|
||||
|
||||
### Composable verwenden
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { usePermissions } from '@/composables/usePermissions.js';
|
||||
|
||||
const { can, canWrite, canDelete, isAdmin, isOwner, userRole } = usePermissions();
|
||||
|
||||
// Beispiel
|
||||
if (can('diary', 'write')) {
|
||||
// Zeige Bearbeitungsbutton
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### Direktiven verwenden
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<!-- Nur anzeigen, wenn Schreibrechte für diary vorhanden -->
|
||||
<button v-can:diary.write>Bearbeiten</button>
|
||||
|
||||
<!-- Nur anzeigen, wenn Löschrechte für members vorhanden -->
|
||||
<button v-can:members.delete>Löschen</button>
|
||||
|
||||
<!-- Alternative Syntax -->
|
||||
<div v-can="'diary.write'">Inhalt nur für Berechtigte</div>
|
||||
|
||||
<!-- Nur für Admins -->
|
||||
<div v-admin>Admin-Bereich</div>
|
||||
|
||||
<!-- Nur für Owner -->
|
||||
<div v-owner>Owner-Bereich</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
### Store verwenden
|
||||
|
||||
```javascript
|
||||
import { useStore } from 'vuex';
|
||||
|
||||
const store = useStore();
|
||||
|
||||
// Berechtigungen abrufen
|
||||
const permissions = store.getters.currentPermissions;
|
||||
const hasPermission = store.getters.hasPermission('diary', 'write');
|
||||
const isOwner = store.getters.isClubOwner;
|
||||
const userRole = store.getters.userRole;
|
||||
|
||||
// Berechtigungen laden (wird automatisch beim Club-Wechsel gemacht)
|
||||
await store.dispatch('loadPermissions', clubId);
|
||||
```
|
||||
|
||||
## Admin-UI
|
||||
|
||||
Die Berechtigungsverwaltung ist unter `/permissions` verfügbar und nur für Administratoren sichtbar.
|
||||
|
||||
**Funktionen:**
|
||||
- Übersicht aller Clubmitglieder mit ihren Rollen
|
||||
- Rollen zuweisen/ändern
|
||||
- Custom Permissions für einzelne Benutzer definieren
|
||||
- Erklärung der verfügbaren Rollen
|
||||
|
||||
## MyTischtennis-Integration
|
||||
|
||||
Die MyTischtennis-Einstellungen und -Funktionen sind für **alle Club-Mitglieder** zugänglich, unabhängig von ihrer Rolle. Dies ermöglicht es jedem, die Anbindung einzurichten und Daten abzurufen.
|
||||
|
||||
## Sicherheitshinweise
|
||||
|
||||
1. **Der Club-Ersteller** (Owner) kann nicht degradiert oder gelöscht werden
|
||||
2. **Owner-Rechte** können nicht übertragen werden
|
||||
3. **Backend-Validierung** wird immer durchgeführt, auch wenn das Frontend Elemente ausblendet
|
||||
4. **Alle API-Routen** sind durch Middleware geschützt
|
||||
5. **Permissions werden gecacht** im localStorage für bessere Performance
|
||||
|
||||
## Beispiel-Szenarien
|
||||
|
||||
### Szenario 1: Trainer hinzufügen
|
||||
1. Admin öffnet `/permissions`
|
||||
2. Wählt Benutzer aus
|
||||
3. Ändert Rolle zu "Trainer"
|
||||
4. Benutzer kann jetzt Trainingseinheiten planen
|
||||
|
||||
### Szenario 2: Custom Permissions
|
||||
1. Admin öffnet `/permissions`
|
||||
2. Wählt Benutzer aus
|
||||
3. Klickt auf "Anpassen"
|
||||
4. Setzt individuelle Berechtigungen (z.B. nur Diary-Schreibrecht)
|
||||
5. Speichert
|
||||
|
||||
### Szenario 3: Neues Mitglied
|
||||
1. Mitglied registriert sich und fordert Zugang an
|
||||
2. Admin genehmigt Anfrage (Standardrolle: "member")
|
||||
3. Mitglied hat Lesezugriff
|
||||
4. Bei Bedarf kann Admin die Rolle später ändern
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Problem**: Berechtigungen werden nicht aktualisiert
|
||||
- **Lösung**: Seite neu laden oder Club neu auswählen
|
||||
|
||||
**Problem**: "Keine Berechtigung" trotz korrekter Rolle
|
||||
- **Lösung**: Prüfen, ob Custom Permissions die Rolle überschreiben
|
||||
|
||||
**Problem**: Owner kann keine Änderungen vornehmen
|
||||
- **Lösung**: Owner sollte automatisch alle Rechte haben. Prüfen Sie die `isOwner`-Flag in der Datenbank
|
||||
|
||||
## API-Endpunkte
|
||||
|
||||
```
|
||||
GET /api/permissions/:clubId - Eigene Berechtigungen abrufen
|
||||
GET /api/permissions/:clubId/members - Alle Mitglieder mit Berechtigungen (Admin)
|
||||
PUT /api/permissions/:clubId/user/:userId/role - Rolle ändern (Admin)
|
||||
PUT /api/permissions/:clubId/user/:userId/permissions - Custom Permissions setzen (Admin)
|
||||
GET /api/permissions/roles/available - Verfügbare Rollen abrufen
|
||||
GET /api/permissions/structure/all - Berechtigungsstruktur abrufen
|
||||
```
|
||||
|
||||
|
||||
235
PERMISSIONS_MIGRATION.md
Normal file
235
PERMISSIONS_MIGRATION.md
Normal file
@@ -0,0 +1,235 @@
|
||||
# Berechtigungssystem - Migrations-Anleitung
|
||||
|
||||
## Übersicht
|
||||
|
||||
Diese Anleitung hilft Ihnen, das neue Berechtigungssystem für bestehende Clubs einzurichten.
|
||||
|
||||
## Schritt 1: Datenbank-Schema erweitern
|
||||
|
||||
Führen Sie zuerst die SQL-Migration aus, um die neuen Spalten hinzuzufügen:
|
||||
|
||||
```bash
|
||||
mysql -u username -p database_name < backend/migrations/add_permissions_to_user_club.sql
|
||||
```
|
||||
|
||||
Dies fügt folgende Spalten zur `user_club` Tabelle hinzu:
|
||||
- `role` (VARCHAR) - Benutzerrolle (admin, trainer, team_manager, member)
|
||||
- `permissions` (JSON) - Custom Permissions
|
||||
- `is_owner` (BOOLEAN) - Markiert den Club-Ersteller
|
||||
|
||||
## Schritt 2: Bestehende Daten migrieren
|
||||
|
||||
Sie haben zwei Optionen:
|
||||
|
||||
### Option A: Node.js Script (Empfohlen)
|
||||
|
||||
Das Script identifiziert automatisch den ersten Benutzer jedes Clubs (nach `createdAt`) und setzt ihn als Owner.
|
||||
|
||||
```bash
|
||||
cd /home/torsten/Programs/trainingstagebuch/backend
|
||||
node scripts/migratePermissions.js
|
||||
```
|
||||
|
||||
**Ausgabe:**
|
||||
```
|
||||
Starting permissions migration...
|
||||
|
||||
Found 3 club(s)
|
||||
|
||||
--- Club: TTC Beispiel (ID: 1) ---
|
||||
Members found: 5
|
||||
First member (will be owner): admin@example.com
|
||||
✓ Updated admin@example.com: role=admin, isOwner=true
|
||||
✓ Updated user1@example.com: role=member, isOwner=false
|
||||
✓ Updated user2@example.com: role=member, isOwner=false
|
||||
...
|
||||
|
||||
✅ Migration completed successfully!
|
||||
|
||||
Summary:
|
||||
Club Owners (3):
|
||||
- TTC Beispiel: admin@example.com
|
||||
- SV Teststadt: owner@test.de
|
||||
- TSC Demo: demo@example.com
|
||||
|
||||
Role Distribution:
|
||||
- Admins: 3
|
||||
- Members: 12
|
||||
```
|
||||
|
||||
### Option B: SQL Script
|
||||
|
||||
Wenn Sie lieber SQL verwenden möchten:
|
||||
|
||||
```bash
|
||||
mysql -u username -p database_name < backend/migrations/update_existing_user_club_permissions.sql
|
||||
```
|
||||
|
||||
Dieses Script:
|
||||
1. Setzt `role = 'member'` für alle genehmigten Benutzer ohne Rolle
|
||||
2. Markiert den Benutzer mit der niedrigsten `user_id` pro Club als Owner
|
||||
|
||||
## Schritt 3: Manuelle Anpassungen (Optional)
|
||||
|
||||
### Falscher Owner?
|
||||
|
||||
Falls das Script den falschen Benutzer als Owner markiert hat, können Sie dies manuell korrigieren:
|
||||
|
||||
```sql
|
||||
-- Alten Owner zurücksetzen
|
||||
UPDATE user_club
|
||||
SET is_owner = 0, role = 'member'
|
||||
WHERE club_id = 1 AND user_id = 123;
|
||||
|
||||
-- Neuen Owner setzen
|
||||
UPDATE user_club
|
||||
SET is_owner = 1, role = 'admin'
|
||||
WHERE club_id = 1 AND user_id = 456;
|
||||
```
|
||||
|
||||
### Weitere Admins ernennen
|
||||
|
||||
```sql
|
||||
UPDATE user_club
|
||||
SET role = 'admin'
|
||||
WHERE club_id = 1 AND user_id = 789;
|
||||
```
|
||||
|
||||
### Trainer ernennen
|
||||
|
||||
```sql
|
||||
UPDATE user_club
|
||||
SET role = 'trainer'
|
||||
WHERE club_id = 1 AND user_id = 101;
|
||||
```
|
||||
|
||||
## Schritt 4: Verifizierung
|
||||
|
||||
### Backend neu starten
|
||||
|
||||
```bash
|
||||
# Server neu starten (wenn er läuft)
|
||||
sudo systemctl restart tt-tagebuch
|
||||
```
|
||||
|
||||
### Im Browser testen
|
||||
|
||||
1. Loggen Sie sich ein
|
||||
2. Wählen Sie einen Club aus
|
||||
3. Navigieren Sie zu "Berechtigungen" (nur für Admins sichtbar)
|
||||
4. Überprüfen Sie, dass alle Mitglieder korrekt angezeigt werden
|
||||
|
||||
### SQL Verifizierung
|
||||
|
||||
```sql
|
||||
-- Alle Club-Mitglieder mit ihren Berechtigungen anzeigen
|
||||
SELECT
|
||||
c.name as club_name,
|
||||
u.email as user_email,
|
||||
uc.role,
|
||||
uc.is_owner,
|
||||
uc.approved
|
||||
FROM user_club uc
|
||||
JOIN club c ON c.id = uc.club_id
|
||||
JOIN user u ON u.id = uc.user_id
|
||||
WHERE uc.approved = 1
|
||||
ORDER BY c.name, uc.is_owner DESC, uc.role, u.email;
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Problem: "Keine Berechtigung" trotz Owner-Status
|
||||
|
||||
**Lösung:** Überprüfen Sie in der Datenbank:
|
||||
|
||||
```sql
|
||||
SELECT role, is_owner, approved
|
||||
FROM user_club
|
||||
WHERE user_id = YOUR_USER_ID AND club_id = YOUR_CLUB_ID;
|
||||
```
|
||||
|
||||
Sollte sein: `role='admin'`, `is_owner=1`, `approved=1`
|
||||
|
||||
### Problem: Owner kann nicht geändert werden
|
||||
|
||||
Das ist korrekt! Der Owner (Club-Ersteller) kann seine eigenen Rechte nicht verlieren. Dies ist eine Sicherheitsmaßnahme.
|
||||
|
||||
### Problem: Berechtigungen werden nicht geladen
|
||||
|
||||
**Lösung:**
|
||||
1. Browser-Cache leeren
|
||||
2. LocalStorage leeren: `localStorage.clear()` in der Browser-Console
|
||||
3. Neu einloggen
|
||||
|
||||
### Problem: "Lade Mitglieder..." bleibt hängen
|
||||
|
||||
**Mögliche Ursachen:**
|
||||
1. Migration noch nicht ausgeführt
|
||||
2. Backend nicht neu gestartet
|
||||
3. Frontend nicht neu gebaut
|
||||
|
||||
**Lösung:**
|
||||
```bash
|
||||
# Backend
|
||||
cd /home/torsten/Programs/trainingstagebuch/backend
|
||||
node scripts/migratePermissions.js
|
||||
|
||||
# Frontend
|
||||
cd /home/torsten/Programs/trainingstagebuch/frontend
|
||||
npm run build
|
||||
|
||||
# Server neu starten
|
||||
sudo systemctl restart tt-tagebuch
|
||||
```
|
||||
|
||||
## Nach der Migration
|
||||
|
||||
### Neue Clubs
|
||||
|
||||
Bei neuen Clubs wird der Ersteller automatisch als Owner mit Admin-Rechten eingerichtet. Keine manuelle Aktion erforderlich.
|
||||
|
||||
### Neue Mitglieder
|
||||
|
||||
Neue Mitglieder erhalten automatisch die Rolle "member" (Lesezugriff). Admins können die Rolle später ändern.
|
||||
|
||||
### Berechtigungen verwalten
|
||||
|
||||
Admins können über die Web-UI unter `/permissions` Berechtigungen verwalten:
|
||||
1. Rollen zuweisen (Admin, Trainer, Mannschaftsführer, Mitglied)
|
||||
2. Custom Permissions definieren (für spezielle Anwendungsfälle)
|
||||
|
||||
## Wichtige Hinweise
|
||||
|
||||
⚠️ **Sicherung erstellen:**
|
||||
```bash
|
||||
mysqldump -u username -p database_name > backup_before_permissions_$(date +%Y%m%d).sql
|
||||
```
|
||||
|
||||
⚠️ **Owner-Rechte:**
|
||||
- Der Owner (is_owner=1) kann nicht degradiert oder gelöscht werden
|
||||
- Jeder Club hat genau einen Owner
|
||||
- Owner-Rechte können nicht übertragen werden (nur durch direkte DB-Änderung)
|
||||
|
||||
⚠️ **MyTischtennis:**
|
||||
- MyTischtennis-Funktionen sind für ALLE Mitglieder zugänglich
|
||||
- Keine Berechtigungsprüfung für MyTischtennis-Endpunkte
|
||||
|
||||
## Rollback (falls nötig)
|
||||
|
||||
Falls Sie das Berechtigungssystem zurücknehmen müssen:
|
||||
|
||||
```sql
|
||||
-- Spalten entfernen (Achtung: Datenverlust!)
|
||||
ALTER TABLE user_club
|
||||
DROP COLUMN role,
|
||||
DROP COLUMN permissions,
|
||||
DROP COLUMN is_owner;
|
||||
|
||||
-- Indizes entfernen
|
||||
DROP INDEX idx_user_club_role ON user_club;
|
||||
DROP INDEX idx_user_club_owner ON user_club;
|
||||
```
|
||||
|
||||
Dann Backend-Code auf vorherige Version zurücksetzen.
|
||||
|
||||
|
||||
212
backend/MYTISCHTENNIS_AUTO_FETCH_README.md
Normal file
212
backend/MYTISCHTENNIS_AUTO_FETCH_README.md
Normal file
@@ -0,0 +1,212 @@
|
||||
# MyTischtennis Automatischer Datenabruf
|
||||
|
||||
## Übersicht
|
||||
|
||||
Dieses System ermöglicht den automatischen Abruf von Spielergebnissen und Statistiken von myTischtennis.de.
|
||||
|
||||
## Scheduler
|
||||
|
||||
### 6:00 Uhr - Rating Updates
|
||||
- **Service:** `autoUpdateRatingsService.js`
|
||||
- **Funktion:** Aktualisiert TTR/QTTR-Werte für Spieler
|
||||
- **TODO:** Implementierung der eigentlichen Rating-Update-Logik
|
||||
|
||||
### 6:30 Uhr - Spielergebnisse
|
||||
- **Service:** `autoFetchMatchResultsService.js`
|
||||
- **Funktion:** Ruft Spielerbilanzen für konfigurierte Teams ab
|
||||
- **Status:** ✅ Grundlegende Implementierung fertig
|
||||
|
||||
## Benötigte Konfiguration
|
||||
|
||||
### 1. MyTischtennis-Account
|
||||
- Account muss in den MyTischtennis-Settings verknüpft sein
|
||||
- Checkbox "Automatische Updates" aktivieren
|
||||
- Passwort speichern (erforderlich für automatische Re-Authentifizierung)
|
||||
|
||||
### 2. League-Konfiguration
|
||||
|
||||
Für jede Liga müssen folgende Felder ausgefüllt werden:
|
||||
|
||||
```sql
|
||||
UPDATE league SET
|
||||
my_tischtennis_group_id = '504417', -- Group ID von myTischtennis
|
||||
association = 'HeTTV', -- Verband (z.B. HeTTV, DTTB)
|
||||
groupname = '1.Kreisklasse' -- Gruppenname für URL
|
||||
WHERE id = 1;
|
||||
```
|
||||
|
||||
**Beispiel-URL:**
|
||||
```
|
||||
https://www.mytischtennis.de/click-tt/HeTTV/25--26/ligen/1.Kreisklasse/gruppe/504417/...
|
||||
^^^^^ ^^^^^^^^^^^^^^ ^^^^^^
|
||||
association groupname group_id
|
||||
```
|
||||
|
||||
### 3. Team-Konfiguration
|
||||
|
||||
Für jedes Team muss die myTischtennis Team-ID gesetzt werden:
|
||||
|
||||
```sql
|
||||
UPDATE club_team SET
|
||||
my_tischtennis_team_id = '2995094' -- Team ID von myTischtennis
|
||||
WHERE id = 1;
|
||||
```
|
||||
|
||||
**Beispiel-URL:**
|
||||
```
|
||||
.../mannschaft/2995094/Harheimer_TC_(J11)/spielerbilanzen/gesamt
|
||||
^^^^^^^
|
||||
team_id
|
||||
```
|
||||
|
||||
### 4. Spieler-Zuordnung (Optional)
|
||||
|
||||
Spieler werden automatisch anhand des Namens zugeordnet. Für genauere Zuordnung kann die myTischtennis Player-ID gesetzt werden:
|
||||
|
||||
```sql
|
||||
UPDATE member SET
|
||||
my_tischtennis_player_id = 'NU2705037' -- Player ID von myTischtennis
|
||||
WHERE id = 1;
|
||||
```
|
||||
|
||||
## Migrationen
|
||||
|
||||
Folgende Migrationen müssen ausgeführt werden:
|
||||
|
||||
```bash
|
||||
# 1. MyTischtennis Auto-Update-Felder
|
||||
mysql -u root -p trainingstagebuch < backend/migrations/add_auto_update_ratings_to_my_tischtennis.sql
|
||||
|
||||
# 2. MyTischtennis Update-History-Tabelle
|
||||
mysql -u root -p trainingstagebuch < backend/migrations/create_my_tischtennis_update_history.sql
|
||||
|
||||
# 3. League MyTischtennis-Felder
|
||||
mysql -u root -p trainingstagebuch < backend/migrations/add_mytischtennis_fields_to_league.sql
|
||||
|
||||
# 4. Team MyTischtennis-ID
|
||||
mysql -u root -p trainingstagebuch < backend/migrations/add_mytischtennis_team_id_to_club_team.sql
|
||||
|
||||
# 5. Member MyTischtennis Player-ID
|
||||
mysql -u root -p trainingstagebuch < backend/migrations/add_mytischtennis_player_id_to_member.sql
|
||||
|
||||
# 6. Match Result-Felder
|
||||
mysql -u root -p trainingstagebuch < backend/migrations/add_match_result_fields.sql
|
||||
```
|
||||
|
||||
## Abgerufene Daten
|
||||
|
||||
Von der myTischtennis API werden folgende Daten abgerufen:
|
||||
|
||||
### Einzelstatistiken
|
||||
- Player ID, Vorname, Nachname
|
||||
- Gewonnene/Verlorene Punkte
|
||||
- Anzahl Spiele
|
||||
- Detaillierte Statistiken nach Gegner-Position
|
||||
|
||||
### Doppelstatistiken
|
||||
- Player IDs, Namen der beiden Spieler
|
||||
- Gewonnene/Verlorene Punkte
|
||||
- Anzahl Spiele
|
||||
|
||||
### Team-Informationen
|
||||
- Teamname, Liga, Saison
|
||||
- Gesamtpunkte (gewonnen/verloren)
|
||||
- Doppel- und Einzelpunkte
|
||||
|
||||
## Implementierungsdetails
|
||||
|
||||
### Datenfluss
|
||||
|
||||
1. **Scheduler** (6:30 Uhr):
|
||||
- `schedulerService.js` triggert `autoFetchMatchResultsService.executeAutomaticFetch()`
|
||||
|
||||
2. **Account-Verarbeitung**:
|
||||
- Lädt alle MyTischtennis-Accounts mit `autoUpdateRatings = true`
|
||||
- Prüft Session-Gültigkeit
|
||||
- Re-Authentifizierung bei abgelaufener Session
|
||||
|
||||
3. **Team-Abfrage**:
|
||||
- Lädt alle Teams mit konfigurierten myTischtennis-IDs
|
||||
- Baut API-URL dynamisch zusammen
|
||||
- Führt authentifizierten GET-Request durch
|
||||
|
||||
4. **Datenverarbeitung**:
|
||||
- Parst JSON-Response
|
||||
- Matched Spieler anhand von ID oder Name
|
||||
- Speichert myTischtennis Player-ID bei Mitgliedern
|
||||
- Loggt Statistiken
|
||||
|
||||
### Player-Matching-Algorithmus
|
||||
|
||||
```javascript
|
||||
1. Suche nach myTischtennis Player-ID (exakte Übereinstimmung)
|
||||
2. Falls nicht gefunden: Suche nach Name (case-insensitive)
|
||||
3. Falls gefunden: Speichere myTischtennis Player-ID für zukünftige Abfragen
|
||||
```
|
||||
|
||||
**Hinweis:** Da Namen verschlüsselt gespeichert werden, müssen für den Namens-Abgleich alle Members geladen und entschlüsselt werden. Dies ist bei großen Datenbanken ineffizient.
|
||||
|
||||
## TODO / Offene Punkte
|
||||
|
||||
### Noch zu implementieren:
|
||||
|
||||
1. **TTR/QTTR Updates** (6:00 Uhr Job):
|
||||
- Endpoint für TTR/QTTR-Daten identifizieren
|
||||
- Daten abrufen und in Member-Tabelle speichern
|
||||
|
||||
2. **Spielergebnis-Details**:
|
||||
- Einzelne Matches mit Satzständen speichern
|
||||
- Tabelle für Match-Historie erstellen
|
||||
|
||||
3. **History-Tabelle für Spielergebnis-Abrufe** (optional):
|
||||
- Ähnlich zu `my_tischtennis_update_history`
|
||||
- Speichert Erfolg/Fehler der Abrufe
|
||||
|
||||
4. **Benachrichtigungen** (optional):
|
||||
- Email/Push bei neuen Ergebnissen
|
||||
- Highlights für besondere Siege
|
||||
|
||||
5. **Performance-Optimierung**:
|
||||
- Caching für Player-Matches
|
||||
- Incremental Updates (nur neue Daten)
|
||||
|
||||
## Manueller Test
|
||||
|
||||
```javascript
|
||||
// Im Node-Backend-Code oder über API-Endpoint:
|
||||
import schedulerService from './services/schedulerService.js';
|
||||
|
||||
// Rating Updates manuell triggern
|
||||
await schedulerService.triggerRatingUpdates();
|
||||
|
||||
// Spielergebnisse manuell abrufen
|
||||
await schedulerService.triggerMatchResultsFetch();
|
||||
```
|
||||
|
||||
## API-Dokumentation
|
||||
|
||||
### MyTischtennis Spielerbilanzen-Endpoint
|
||||
|
||||
**URL-Format:**
|
||||
```
|
||||
https://www.mytischtennis.de/click-tt/{association}/{season}/ligen/{groupname}/gruppe/{groupId}/mannschaft/{teamId}/{teamname}/spielerbilanzen/gesamt?_data=routes%2Fclick-tt%2B%2F%24association%2B%2F%24season%2B%2F%24type%2B%2F%28%24groupname%29.gruppe.%24urlid_.mannschaft.%24teamid.%24teamname%2B%2Fspielerbilanzen.%24filter
|
||||
```
|
||||
|
||||
**Parameter:**
|
||||
- `{association}`: Verband (z.B. "HeTTV")
|
||||
- `{season}`: Saison im Format "25--26"
|
||||
- `{groupname}`: Gruppenname URL-encoded (z.B. "1.Kreisklasse")
|
||||
- `{groupId}`: Gruppen-ID (numerisch, z.B. "504417")
|
||||
- `{teamId}`: Team-ID (numerisch, z.B. "2995094")
|
||||
- `{teamname}`: Teamname URL-encoded mit Underscores (z.B. "Harheimer_TC_(J11)")
|
||||
|
||||
**Response:** JSON mit `data.balancesheet` Array
|
||||
|
||||
## Sicherheit
|
||||
|
||||
- ✅ Automatische Session-Verwaltung
|
||||
- ✅ Re-Authentifizierung bei abgelaufenen Sessions
|
||||
- ✅ Passwörter verschlüsselt gespeichert
|
||||
- ✅ Fehlerbehandlung und Logging
|
||||
- ✅ Graceful Degradation (einzelne Team-Fehler stoppen nicht den gesamten Prozess)
|
||||
|
||||
332
backend/MYTISCHTENNIS_URL_PARSER_README.md
Normal file
332
backend/MYTISCHTENNIS_URL_PARSER_README.md
Normal file
@@ -0,0 +1,332 @@
|
||||
# MyTischtennis URL Parser
|
||||
|
||||
## Übersicht
|
||||
|
||||
Der URL-Parser ermöglicht es, myTischtennis-Team-URLs automatisch zu parsen und die Konfiguration für automatische Datenabrufe vorzunehmen.
|
||||
|
||||
## Verwendung
|
||||
|
||||
### 1. URL Parsen
|
||||
|
||||
**Endpoint:** `POST /api/mytischtennis/parse-url`
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"url": "https://www.mytischtennis.de/click-tt/HeTTV/25--26/ligen/1.Kreisklasse/gruppe/504417/mannschaft/2995094/Harheimer_TC_(J11)/spielerbilanzen/gesamt"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"association": "HeTTV",
|
||||
"season": "25/26",
|
||||
"type": "ligen",
|
||||
"groupname": "1.Kreisklasse",
|
||||
"groupId": "504417",
|
||||
"teamId": "2995094",
|
||||
"teamname": "Harheimer TC (J11)",
|
||||
"originalUrl": "https://www.mytischtennis.de/click-tt/...",
|
||||
"clubId": "43030",
|
||||
"clubName": "Harheimer TC",
|
||||
"teamName": "Jugend 11",
|
||||
"leagueName": "Jugend 13 1. Kreisklasse",
|
||||
"region": "Frankfurt",
|
||||
"tableRank": 8,
|
||||
"matchesWon": 0,
|
||||
"matchesLost": 3
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Team Automatisch Konfigurieren
|
||||
|
||||
**Endpoint:** `POST /api/mytischtennis/configure-team`
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"url": "https://www.mytischtennis.de/click-tt/HeTTV/25--26/ligen/1.Kreisklasse/gruppe/504417/mannschaft/2995094/Harheimer_TC_(J11)/spielerbilanzen/gesamt",
|
||||
"clubTeamId": 1,
|
||||
"createLeague": false,
|
||||
"createSeason": false
|
||||
}
|
||||
```
|
||||
|
||||
**Parameter:**
|
||||
- `url` (required): Die myTischtennis-URL
|
||||
- `clubTeamId` (required): Die ID des lokalen Club-Teams
|
||||
- `createLeague` (optional): Wenn `true`, wird eine neue League erstellt
|
||||
- `createSeason` (optional): Wenn `true`, wird eine neue Season erstellt
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Team configured successfully",
|
||||
"data": {
|
||||
"team": {
|
||||
"id": 1,
|
||||
"name": "Jugend 11",
|
||||
"myTischtennisTeamId": "2995094"
|
||||
},
|
||||
"league": {
|
||||
"id": 5,
|
||||
"name": "Jugend 13 1. Kreisklasse",
|
||||
"myTischtennisGroupId": "504417",
|
||||
"association": "HeTTV",
|
||||
"groupname": "1.Kreisklasse"
|
||||
},
|
||||
"season": {
|
||||
"id": 2,
|
||||
"name": "25/26"
|
||||
},
|
||||
"parsedData": { ... }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. URL für Team Abrufen
|
||||
|
||||
**Endpoint:** `GET /api/mytischtennis/team-url/:teamId`
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"url": "https://www.mytischtennis.de/click-tt/HeTTV/25--26/ligen/1.Kreisklasse/gruppe/504417/mannschaft/2995094/Harheimer%20TC%20%28J11%29/spielerbilanzen/gesamt"
|
||||
}
|
||||
```
|
||||
|
||||
## URL-Format
|
||||
|
||||
### Unterstützte URL-Muster
|
||||
|
||||
```
|
||||
https://www.mytischtennis.de/click-tt/{association}/{season}/{type}/{groupname}/gruppe/{groupId}/mannschaft/{teamId}/{teamname}/...
|
||||
```
|
||||
|
||||
**Komponenten:**
|
||||
- `{association}`: Verband (z.B. "HeTTV", "DTTB", "WestD")
|
||||
- `{season}`: Saison im Format "YY--YY" (z.B. "25--26" für 2025/2026)
|
||||
- `{type}`: Typ (meist "ligen")
|
||||
- `{groupname}`: Gruppenname URL-encoded (z.B. "1.Kreisklasse", "Kreisliga")
|
||||
- `{groupId}`: Numerische Gruppen-ID (z.B. "504417")
|
||||
- `{teamId}`: Numerische Team-ID (z.B. "2995094")
|
||||
- `{teamname}`: Teamname URL-encoded mit Underscores (z.B. "Harheimer_TC_(J11)")
|
||||
|
||||
### Beispiel-URLs
|
||||
|
||||
**Spielerbilanzen:**
|
||||
```
|
||||
https://www.mytischtennis.de/click-tt/HeTTV/25--26/ligen/1.Kreisklasse/gruppe/504417/mannschaft/2995094/Harheimer_TC_(J11)/spielerbilanzen/gesamt
|
||||
```
|
||||
|
||||
**Spielplan:**
|
||||
```
|
||||
https://www.mytischtennis.de/click-tt/HeTTV/25--26/ligen/1.Kreisklasse/gruppe/504417/mannschaft/2995094/Harheimer_TC_(J11)/spielplan
|
||||
```
|
||||
|
||||
**Tabelle:**
|
||||
```
|
||||
https://www.mytischtennis.de/click-tt/HeTTV/25--26/ligen/1.Kreisklasse/gruppe/504417/mannschaft/2995094/Harheimer_TC_(J11)/tabelle
|
||||
```
|
||||
|
||||
## Datenfluss
|
||||
|
||||
### Ohne MyTischtennis-Login
|
||||
|
||||
1. URL wird geparst
|
||||
2. Nur URL-Komponenten werden extrahiert
|
||||
3. Zusätzliche Daten (clubName, leagueName, etc.) sind nicht verfügbar
|
||||
|
||||
### Mit MyTischtennis-Login
|
||||
|
||||
1. URL wird geparst
|
||||
2. API-Request an myTischtennis mit Authentication
|
||||
3. Vollständige Team-Daten werden abgerufen
|
||||
4. Alle Felder sind verfügbar
|
||||
|
||||
## Frontend-Integration
|
||||
|
||||
### Vue.js Beispiel
|
||||
|
||||
```javascript
|
||||
<template>
|
||||
<div>
|
||||
<input
|
||||
v-model="myTischtennisUrl"
|
||||
placeholder="MyTischtennis URL einfügen..."
|
||||
@blur="parseUrl"
|
||||
/>
|
||||
|
||||
<div v-if="parsedData">
|
||||
<h3>{{ parsedData.teamname }}</h3>
|
||||
<p>Liga: {{ parsedData.leagueName }}</p>
|
||||
<p>Verband: {{ parsedData.association }}</p>
|
||||
<p>Tabelle: Platz {{ parsedData.tableRank }}</p>
|
||||
|
||||
<button @click="configureTeam">Team konfigurieren</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
myTischtennisUrl: '',
|
||||
parsedData: null
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async parseUrl() {
|
||||
if (!this.myTischtennisUrl) return;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/mytischtennis/parse-url', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'userid': this.userId,
|
||||
'authcode': this.authCode
|
||||
},
|
||||
body: JSON.stringify({
|
||||
url: this.myTischtennisUrl
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
this.parsedData = result.data;
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Parsen:', error);
|
||||
// Hinweis: Im Frontend stattdessen InfoDialog/ConfirmDialog verwenden
|
||||
// alert('URL konnte nicht geparst werden');
|
||||
}
|
||||
},
|
||||
|
||||
async configureTeam() {
|
||||
try {
|
||||
const response = await fetch('/api/mytischtennis/configure-team', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'userid': this.userId,
|
||||
'authcode': this.authCode
|
||||
},
|
||||
body: JSON.stringify({
|
||||
url: this.myTischtennisUrl,
|
||||
clubTeamId: this.selectedTeamId,
|
||||
createLeague: false,
|
||||
createSeason: true
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
// In der Anwendung bitte InfoDialog nutzen
|
||||
// alert('Team erfolgreich konfiguriert!');
|
||||
} else {
|
||||
// alert('Team konnte nicht konfiguriert werden');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler bei Konfiguration:', error);
|
||||
// alert('Team konnte nicht konfiguriert werden');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
## Workflow
|
||||
|
||||
### Empfohlener Workflow für Benutzer
|
||||
|
||||
1. **MyTischtennis-URL kopieren:**
|
||||
- Auf myTischtennis.de zum Team navigieren
|
||||
- URL aus Adresszeile kopieren
|
||||
|
||||
2. **URL in Trainingstagebuch einfügen:**
|
||||
- Zu Team-Verwaltung navigieren
|
||||
- URL einfügen
|
||||
- Automatisches Parsen
|
||||
|
||||
3. **Konfiguration überprüfen:**
|
||||
- Geparste Daten werden angezeigt
|
||||
- Benutzer kann Daten überprüfen und bei Bedarf anpassen
|
||||
|
||||
4. **Team konfigurieren:**
|
||||
- Auf "Konfigurieren" klicken
|
||||
- System speichert alle benötigten IDs
|
||||
- Automatischer Datenabruf ist ab sofort aktiv
|
||||
|
||||
## Fehlerbehandlung
|
||||
|
||||
### Häufige Fehler
|
||||
|
||||
**"Invalid myTischtennis URL format"**
|
||||
- URL entspricht nicht dem erwarteten Format
|
||||
- Lösung: Vollständige URL von der Spielerbilanzen-Seite kopieren
|
||||
|
||||
**"Season not found"**
|
||||
- Saison existiert noch nicht in der Datenbank
|
||||
- Lösung: `createSeason: true` setzen
|
||||
|
||||
**"Team has no league assigned"**
|
||||
- Team hat keine verknüpfte Liga
|
||||
- Lösung: `createLeague: true` setzen oder Liga manuell zuweisen
|
||||
|
||||
**"HTTP 401: Unauthorized"**
|
||||
- MyTischtennis-Login abgelaufen oder nicht vorhanden
|
||||
- Lösung: In MyTischtennis-Settings erneut anmelden
|
||||
|
||||
## Sicherheit
|
||||
|
||||
- ✅ Alle Endpoints erfordern Authentifizierung
|
||||
- ✅ UserID wird aus Header-Parameter gelesen
|
||||
- ✅ MyTischtennis-Credentials werden sicher gespeichert
|
||||
- ✅ Keine sensiblen Daten in URLs
|
||||
|
||||
## Technische Details
|
||||
|
||||
### Service: `myTischtennisUrlParserService`
|
||||
|
||||
**Methoden:**
|
||||
- `parseUrl(url)` - Parst URL und extrahiert Komponenten
|
||||
- `fetchTeamData(parsedUrl, cookie, accessToken)` - Ruft zusätzliche Daten ab
|
||||
- `getCompleteConfig(url, cookie, accessToken)` - Kombination aus Parsen + Abrufen
|
||||
- `isValidTeamUrl(url)` - Validiert URL-Format
|
||||
- `buildUrl(config)` - Baut URL aus Komponenten
|
||||
|
||||
### Controller: `myTischtennisUrlController`
|
||||
|
||||
**Endpoints:**
|
||||
- `POST /api/mytischtennis/parse-url` - URL parsen
|
||||
- `POST /api/mytischtennis/configure-team` - Team konfigurieren
|
||||
- `GET /api/mytischtennis/team-url/:teamId` - URL abrufen
|
||||
|
||||
## Zukünftige Erweiterungen
|
||||
|
||||
### Geplante Features
|
||||
|
||||
1. **Bulk-Import:**
|
||||
- Mehrere URLs gleichzeitig importieren
|
||||
- Alle Teams einer Liga auf einmal konfigurieren
|
||||
|
||||
2. **Auto-Discovery:**
|
||||
- Automatisches Finden aller Teams eines Vereins
|
||||
- Vorschläge für ähnliche Teams
|
||||
|
||||
3. **Validierung:**
|
||||
- Prüfung, ob Team bereits konfiguriert ist
|
||||
- Warnung bei Duplikaten
|
||||
|
||||
4. **History:**
|
||||
- Speichern der URL-Konfigurationen
|
||||
- Versionierung bei Änderungen
|
||||
|
||||
@@ -9,15 +9,18 @@ const dbConfig = {
|
||||
database: process.env.DB_NAME || 'trainingsdiary'
|
||||
};
|
||||
|
||||
const report = [];
|
||||
|
||||
async function cleanupKeys() {
|
||||
let connection;
|
||||
|
||||
try {
|
||||
console.log('🔌 Verbinde mit der Datenbank...');
|
||||
report.push('🔌 Verbinde mit der Datenbank...');
|
||||
connection = await mysql.createConnection(dbConfig);
|
||||
|
||||
// 1. Status vor dem Cleanup
|
||||
console.log('\n📊 STATUS VOR DEM CLEANUP:');
|
||||
report.push('');
|
||||
report.push('📊 STATUS VOR DEM CLEANUP:');
|
||||
const [tablesBefore] = await connection.execute(`
|
||||
SELECT
|
||||
TABLE_NAME,
|
||||
@@ -29,57 +32,60 @@ async function cleanupKeys() {
|
||||
`, [dbConfig.database]);
|
||||
|
||||
tablesBefore.forEach(table => {
|
||||
console.log(` ${table.TABLE_NAME}: ${table.key_count} Keys`);
|
||||
report.push(` ${table.TABLE_NAME}: ${table.key_count} Keys`);
|
||||
});
|
||||
|
||||
// 2. Alle INDEX der Problem-Tabellen anzeigen
|
||||
const problemTables = ['member', 'diary_tags', 'season'];
|
||||
|
||||
for (const tableName of problemTables) {
|
||||
console.log(`\n🔍 INDEX für Tabelle '${tableName}':`);
|
||||
report.push('');
|
||||
report.push(`🔍 INDEX für Tabelle '${tableName}':`);
|
||||
|
||||
try {
|
||||
const [indexes] = await connection.execute(`SHOW INDEX FROM \`${tableName}\``);
|
||||
|
||||
if (indexes.length === 0) {
|
||||
console.log(` Keine INDEX gefunden für Tabelle '${tableName}'`);
|
||||
report.push(` Keine INDEX gefunden für Tabelle '${tableName}'`);
|
||||
continue;
|
||||
}
|
||||
|
||||
indexes.forEach(index => {
|
||||
console.log(` - ${index.Key_name} (${index.Column_name}) - ${index.Non_unique === 0 ? 'UNIQUE' : 'NON-UNIQUE'}`);
|
||||
report.push(` - ${index.Key_name} (${index.Column_name}) - ${index.Non_unique === 0 ? 'UNIQUE' : 'NON-UNIQUE'}`);
|
||||
});
|
||||
|
||||
// 3. Überflüssige INDEX entfernen (alle außer PRIMARY und UNIQUE)
|
||||
console.log(`\n🗑️ Entferne überflüssige INDEX aus '${tableName}':`);
|
||||
report.push('');
|
||||
report.push(`🗑️ Entferne überflüssige INDEX aus '${tableName}':`);
|
||||
|
||||
for (const index of indexes) {
|
||||
// Behalte PRIMARY KEY und UNIQUE constraints
|
||||
if (index.Key_name === 'PRIMARY' || index.Non_unique === 0) {
|
||||
console.log(` ✅ Behalte: ${index.Key_name} (${index.Column_name})`);
|
||||
report.push(` ✅ Behalte: ${index.Key_name} (${index.Column_name})`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Entferne alle anderen INDEX
|
||||
try {
|
||||
await connection.execute(`DROP INDEX \`${index.Key_name}\` ON \`${tableName}\``);
|
||||
console.log(` ❌ Entfernt: ${index.Key_name} (${index.Column_name})`);
|
||||
report.push(` ❌ Entfernt: ${index.Key_name} (${index.Column_name})`);
|
||||
} catch (error) {
|
||||
if (error.code === 'ER_CANT_DROP_FIELD_OR_KEY') {
|
||||
console.log(` ⚠️ Kann nicht entfernen: ${index.Key_name} (${index.Column_name}) - ${error.message}`);
|
||||
report.push(` ⚠️ Kann nicht entfernen: ${index.Key_name} (${index.Column_name}) - ${error.message}`);
|
||||
} else {
|
||||
console.log(` ❌ Fehler beim Entfernen von ${index.Key_name}: ${error.message}`);
|
||||
report.push(` ❌ Fehler beim Entfernen von ${index.Key_name}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.log(` ⚠️ Fehler beim Zugriff auf Tabelle '${tableName}': ${error.message}`);
|
||||
report.push(` ⚠️ Fehler beim Zugriff auf Tabelle '${tableName}': ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Status nach dem Cleanup
|
||||
console.log('\n📊 STATUS NACH DEM CLEANUP:');
|
||||
report.push('');
|
||||
report.push('📊 STATUS NACH DEM CLEANUP:');
|
||||
const [tablesAfter] = await connection.execute(`
|
||||
SELECT
|
||||
TABLE_NAME,
|
||||
@@ -96,7 +102,7 @@ async function cleanupKeys() {
|
||||
const diff = beforeCount - table.key_count;
|
||||
const status = table.key_count <= 5 ? '✅' : table.key_count <= 10 ? '⚠️' : '❌';
|
||||
|
||||
console.log(` ${status} ${table.TABLE_NAME}: ${table.key_count} Keys (${diff > 0 ? `-${diff}` : `+${Math.abs(diff)}`})`);
|
||||
report.push(` ${status} ${table.TABLE_NAME}: ${table.key_count} Keys (${diff > 0 ? `-${diff}` : `+${Math.abs(diff)}`})`);
|
||||
});
|
||||
|
||||
// 5. Gesamtanzahl der Keys
|
||||
@@ -106,18 +112,20 @@ async function cleanupKeys() {
|
||||
WHERE TABLE_SCHEMA = ?
|
||||
`, [dbConfig.database]);
|
||||
|
||||
console.log(`\n📈 GESAMTANZAHL KEYS: ${totalKeys[0].total_keys}`);
|
||||
|
||||
report.push('');
|
||||
report.push(`📈 GESAMTANZAHL KEYS: ${totalKeys[0].total_keys}`);
|
||||
|
||||
// 6. Zusammenfassung
|
||||
console.log('\n🎯 ZUSAMMENFASSUNG:');
|
||||
report.push('');
|
||||
report.push('🎯 ZUSAMMENFASSUNG:');
|
||||
const problemTablesAfter = tablesAfter.filter(t => t.key_count > 10);
|
||||
|
||||
if (problemTablesAfter.length === 0) {
|
||||
console.log(' ✅ Alle Tabellen haben jetzt weniger als 10 Keys!');
|
||||
report.push(' ✅ Alle Tabellen haben jetzt weniger als 10 Keys!');
|
||||
} else {
|
||||
console.log(' ⚠️ Folgende Tabellen haben immer noch zu viele Keys:');
|
||||
report.push(' ⚠️ Folgende Tabellen haben immer noch zu viele Keys:');
|
||||
problemTablesAfter.forEach(table => {
|
||||
console.log(` - ${table.TABLE_NAME}: ${table.key_count} Keys`);
|
||||
report.push(` - ${table.TABLE_NAME}: ${table.key_count} Keys`);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -126,15 +134,18 @@ async function cleanupKeys() {
|
||||
} finally {
|
||||
if (connection) {
|
||||
await connection.end();
|
||||
console.log('\n🔌 Datenbankverbindung geschlossen.');
|
||||
report.push('');
|
||||
report.push('🔌 Datenbankverbindung geschlossen.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Script ausführen
|
||||
console.log('🚀 Starte intelligentes INDEX-Cleanup...\n');
|
||||
report.push('🚀 Starte intelligentes INDEX-Cleanup...');
|
||||
cleanupKeys().then(() => {
|
||||
console.log('\n✨ Cleanup abgeschlossen!');
|
||||
report.push('');
|
||||
report.push('✨ Cleanup abgeschlossen!');
|
||||
process.stdout.write(`${report.join('\n')}\n`);
|
||||
process.exit(0);
|
||||
}).catch(error => {
|
||||
console.error('\n💥 Fehler beim Cleanup:', error);
|
||||
|
||||
@@ -7,41 +7,23 @@ import { fileURLToPath } from 'url';
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const report = [];
|
||||
|
||||
// Umgebungsvariablen aus dem Root-Verzeichnis laden
|
||||
//const envPath = path.join(__dirname, '..', '.env');
|
||||
//console.log('🔍 Lade .env-Datei von:', envPath);
|
||||
const envPath = path.join(__dirname, '..', '.env');
|
||||
dotenv.config();
|
||||
|
||||
// Debug: Zeige geladene Umgebungsvariablen
|
||||
console.log('🔍 Geladene Umgebungsvariablen:');
|
||||
console.log(' DB_HOST:', process.env.DB_HOST);
|
||||
console.log(' DB_USER:', process.env.DB_USER);
|
||||
console.log(' DB_NAME:', process.env.DB_NAME);
|
||||
console.log(' DB_PASSWORD:', process.env.DB_PASSWORD ? '***gesetzt***' : 'nicht gesetzt');
|
||||
|
||||
// Datenbankverbindung
|
||||
const dbConfig = {
|
||||
host: process.env.DB_HOST || 'localhost',
|
||||
user: process.env.DB_USER || 'root',
|
||||
password: process.env.DB_PASSWORD || '',
|
||||
database: process.env.DB_NAME || 'trainingsdiary'
|
||||
};
|
||||
|
||||
console.log('🔍 Datenbankverbindung:');
|
||||
console.log(' Host:', dbConfig.host);
|
||||
console.log(' User:', dbConfig.user);
|
||||
console.log(' Database:', dbConfig.database);
|
||||
console.log(' Password:', dbConfig.password ? '***gesetzt***' : 'nicht gesetzt');
|
||||
report.push('Environment variables loaded');
|
||||
|
||||
async function cleanupKeys() {
|
||||
let connection;
|
||||
|
||||
try {
|
||||
console.log('🔌 Verbinde mit der Datenbank...');
|
||||
report.push('🔌 Verbinde mit der Datenbank...');
|
||||
connection = await mysql.createConnection(dbConfig);
|
||||
|
||||
// 1. Status vor dem Cleanup
|
||||
console.log('\n📊 STATUS VOR DEM CLEANUP:');
|
||||
report.push('');
|
||||
report.push('📊 STATUS VOR DEM CLEANUP:');
|
||||
const [tablesBefore] = await connection.execute(`
|
||||
SELECT
|
||||
TABLE_NAME,
|
||||
@@ -53,57 +35,60 @@ async function cleanupKeys() {
|
||||
`, [dbConfig.database]);
|
||||
|
||||
tablesBefore.forEach(table => {
|
||||
console.log(` ${table.TABLE_NAME}: ${table.key_count} Keys`);
|
||||
report.push(` ${table.TABLE_NAME}: ${table.key_count} Keys`);
|
||||
});
|
||||
|
||||
// 2. Alle INDEX der Problem-Tabellen anzeigen
|
||||
const problemTables = ['member', 'diary_tags', 'season'];
|
||||
|
||||
for (const tableName of problemTables) {
|
||||
console.log(`\n🔍 INDEX für Tabelle '${tableName}':`);
|
||||
report.push('');
|
||||
report.push(`🔍 INDEX für Tabelle '${tableName}':`);
|
||||
|
||||
try {
|
||||
const [indexes] = await connection.execute(`SHOW INDEX FROM \`${tableName}\``);
|
||||
|
||||
if (indexes.length === 0) {
|
||||
console.log(` Keine INDEX gefunden für Tabelle '${tableName}'`);
|
||||
report.push(` Keine INDEX gefunden für Tabelle '${tableName}'`);
|
||||
continue;
|
||||
}
|
||||
|
||||
indexes.forEach(index => {
|
||||
console.log(` - ${index.Key_name} (${index.Column_name}) - ${index.Non_unique === 0 ? 'UNIQUE' : 'NON-UNIQUE'}`);
|
||||
report.push(` - ${index.Key_name} (${index.Column_name}) - ${index.Non_unique === 0 ? 'UNIQUE' : 'NON-UNIQUE'}`);
|
||||
});
|
||||
|
||||
// 3. Überflüssige INDEX entfernen (alle außer PRIMARY und UNIQUE)
|
||||
console.log(`\n🗑️ Entferne überflüssige INDEX aus '${tableName}':`);
|
||||
report.push('');
|
||||
report.push(`🗑️ Entferne überflüssige INDEX aus '${tableName}':`);
|
||||
|
||||
for (const index of indexes) {
|
||||
// Behalte PRIMARY KEY und UNIQUE constraints
|
||||
if (index.Key_name === 'PRIMARY' || index.Non_unique === 0) {
|
||||
console.log(` ✅ Behalte: ${index.Key_name} (${index.Column_name})`);
|
||||
report.push(` ✅ Behalte: ${index.Key_name} (${index.Column_name})`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Entferne alle anderen INDEX
|
||||
try {
|
||||
await connection.execute(`DROP INDEX \`${index.Key_name}\` ON \`${tableName}\``);
|
||||
console.log(` ❌ Entfernt: ${index.Key_name} (${index.Column_name})`);
|
||||
report.push(` ❌ Entfernt: ${index.Key_name} (${index.Column_name})`);
|
||||
} catch (error) {
|
||||
if (error.code === 'ER_CANT_DROP_FIELD_OR_KEY') {
|
||||
console.log(` ⚠️ Kann nicht entfernen: ${index.Key_name} (${index.Column_name}) - ${error.message}`);
|
||||
report.push(` ⚠️ Kann nicht entfernen: ${index.Key_name} (${index.Column_name}) - ${error.message}`);
|
||||
} else {
|
||||
console.log(` ❌ Fehler beim Entfernen von ${index.Key_name}: ${error.message}`);
|
||||
report.push(` ❌ Fehler beim Entfernen von ${index.Key_name}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.log(` ⚠️ Fehler beim Zugriff auf Tabelle '${tableName}': ${error.message}`);
|
||||
report.push(` ⚠️ Fehler beim Zugriff auf Tabelle '${tableName}': ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Status nach dem Cleanup
|
||||
console.log('\n📊 STATUS NACH DEM CLEANUP:');
|
||||
report.push('');
|
||||
report.push('📊 STATUS NACH DEM CLEANUP:');
|
||||
const [tablesAfter] = await connection.execute(`
|
||||
SELECT
|
||||
TABLE_NAME,
|
||||
@@ -120,7 +105,7 @@ async function cleanupKeys() {
|
||||
const diff = beforeCount - table.key_count;
|
||||
const status = table.key_count <= 5 ? '✅' : table.key_count <= 10 ? '⚠️' : '❌';
|
||||
|
||||
console.log(` ${status} ${table.TABLE_NAME}: ${table.key_count} Keys (${diff > 0 ? `-${diff}` : `+${Math.abs(diff)}`})`);
|
||||
report.push(` ${status} ${table.TABLE_NAME}: ${table.key_count} Keys (${diff > 0 ? `-${diff}` : `+${Math.abs(diff)}`})`);
|
||||
});
|
||||
|
||||
// 5. Gesamtanzahl der Keys
|
||||
@@ -130,18 +115,20 @@ async function cleanupKeys() {
|
||||
WHERE TABLE_SCHEMA = ?
|
||||
`, [dbConfig.database]);
|
||||
|
||||
console.log(`\n📈 GESAMTANZAHL KEYS: ${totalKeys[0].total_keys}`);
|
||||
report.push('');
|
||||
report.push(`📈 GESAMTANZAHL KEYS: ${totalKeys[0].total_keys}`);
|
||||
|
||||
// 6. Zusammenfassung
|
||||
console.log('\n🎯 ZUSAMMENFASSUNG:');
|
||||
report.push('');
|
||||
report.push('🎯 ZUSAMMENFASSUNG:');
|
||||
const problemTablesAfter = tablesAfter.filter(t => t.key_count > 10);
|
||||
|
||||
if (problemTablesAfter.length === 0) {
|
||||
console.log(' ✅ Alle Tabellen haben jetzt weniger als 10 Keys!');
|
||||
report.push(' ✅ Alle Tabellen haben jetzt weniger als 10 Keys!');
|
||||
} else {
|
||||
console.log(' ⚠️ Folgende Tabellen haben immer noch zu viele Keys:');
|
||||
report.push(' ⚠️ Folgende Tabellen haben immer noch zu viele Keys:');
|
||||
problemTablesAfter.forEach(table => {
|
||||
console.log(` - ${table.TABLE_NAME}: ${table.key_count} Keys`);
|
||||
report.push(` - ${table.TABLE_NAME}: ${table.key_count} Keys`);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -150,15 +137,18 @@ async function cleanupKeys() {
|
||||
} finally {
|
||||
if (connection) {
|
||||
await connection.end();
|
||||
console.log('\n🔌 Datenbankverbindung geschlossen.');
|
||||
report.push('');
|
||||
report.push('🔌 Datenbankverbindung geschlossen.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Script ausführen
|
||||
console.log('🚀 Starte intelligentes INDEX-Cleanup...\n');
|
||||
report.push('🚀 Starte intelligentes INDEX-Cleanup...');
|
||||
cleanupKeys().then(() => {
|
||||
console.log('\n✨ Cleanup abgeschlossen!');
|
||||
report.push('');
|
||||
report.push('✨ Cleanup abgeschlossen!');
|
||||
process.stdout.write(`${report.join('\n')}\n`);
|
||||
process.exit(0);
|
||||
}).catch(error => {
|
||||
console.error('\n💥 Fehler beim Cleanup:', error);
|
||||
|
||||
262
backend/clients/myTischtennisClient.js
Normal file
262
backend/clients/myTischtennisClient.js
Normal file
@@ -0,0 +1,262 @@
|
||||
import axios from 'axios';
|
||||
|
||||
const BASE_URL = 'https://www.mytischtennis.de';
|
||||
|
||||
class MyTischtennisClient {
|
||||
constructor() {
|
||||
this.baseURL = BASE_URL;
|
||||
this.client = axios.create({
|
||||
baseURL: this.baseURL,
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Accept': '*/*'
|
||||
},
|
||||
maxRedirects: 0, // Don't follow redirects automatically
|
||||
validateStatus: (status) => status >= 200 && status < 400 // Accept 3xx as success
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Login to myTischtennis API
|
||||
* @param {string} email - myTischtennis email (not username!)
|
||||
* @param {string} password - myTischtennis password
|
||||
* @returns {Promise<Object>} Login response with token and session data
|
||||
*/
|
||||
async login(email, password) {
|
||||
try {
|
||||
// Create form data
|
||||
const formData = new URLSearchParams();
|
||||
formData.append('email', email);
|
||||
formData.append('password', password);
|
||||
formData.append('intent', 'login');
|
||||
|
||||
const response = await this.client.post(
|
||||
'/login?next=%2F&_data=routes%2F_auth%2B%2Flogin',
|
||||
formData.toString()
|
||||
);
|
||||
|
||||
// Extract the cookie from response headers
|
||||
const setCookie = response.headers['set-cookie'];
|
||||
if (!setCookie || !Array.isArray(setCookie)) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Keine Session-Cookie erhalten'
|
||||
};
|
||||
}
|
||||
|
||||
// Find the sb-10-auth-token cookie
|
||||
const authCookie = setCookie.find(cookie => cookie.startsWith('sb-10-auth-token='));
|
||||
if (!authCookie) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Kein Auth-Token in Response gefunden'
|
||||
};
|
||||
}
|
||||
|
||||
// Extract and decode the token
|
||||
const tokenMatch = authCookie.match(/sb-10-auth-token=base64-([^;]+)/);
|
||||
if (!tokenMatch) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Token-Format ungültig'
|
||||
};
|
||||
}
|
||||
|
||||
const base64Token = tokenMatch[1];
|
||||
let tokenData;
|
||||
try {
|
||||
const decodedToken = Buffer.from(base64Token, 'base64').toString('utf-8');
|
||||
tokenData = JSON.parse(decodedToken);
|
||||
} catch (decodeError) {
|
||||
console.error('Error decoding token:', decodeError);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Token konnte nicht dekodiert werden'
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
accessToken: tokenData.access_token,
|
||||
refreshToken: tokenData.refresh_token,
|
||||
expiresAt: tokenData.expires_at,
|
||||
expiresIn: tokenData.expires_in,
|
||||
user: tokenData.user,
|
||||
cookie: authCookie.split(';')[0] // Just the cookie value without attributes
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('MyTischtennis login error:', error.message);
|
||||
return {
|
||||
success: false,
|
||||
error: error.response?.data?.message || 'Login fehlgeschlagen',
|
||||
status: error.response?.status || 500
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify login credentials
|
||||
* @param {string} email - myTischtennis email
|
||||
* @param {string} password - myTischtennis password
|
||||
* @returns {Promise<boolean>} True if credentials are valid
|
||||
*/
|
||||
async verifyCredentials(email, password) {
|
||||
const result = await this.login(email, password);
|
||||
return result.success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an authenticated request
|
||||
* @param {string} endpoint - API endpoint
|
||||
* @param {string} cookie - Authentication cookie (sb-10-auth-token)
|
||||
* @param {Object} options - Additional axios options
|
||||
* @returns {Promise<Object>} API response
|
||||
*/
|
||||
async authenticatedRequest(endpoint, cookie, options = {}) {
|
||||
try {
|
||||
const response = await this.client.request({
|
||||
url: endpoint,
|
||||
...options,
|
||||
headers: {
|
||||
...options.headers,
|
||||
'Cookie': cookie,
|
||||
'Accept': '*/*',
|
||||
'Accept-Language': 'de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7',
|
||||
'Referer': 'https://www.mytischtennis.de/',
|
||||
'sec-fetch-dest': 'empty',
|
||||
'sec-fetch-mode': 'cors',
|
||||
'sec-fetch-site': 'same-origin'
|
||||
}
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
data: response.data
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('MyTischtennis API error:', error.message);
|
||||
return {
|
||||
success: false,
|
||||
error: error.response?.data?.message || 'API-Anfrage fehlgeschlagen',
|
||||
status: error.response?.status || 500
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user profile and club information
|
||||
* @param {string} cookie - Authentication cookie (sb-10-auth-token)
|
||||
* @returns {Promise<Object>} User profile with club info
|
||||
*/
|
||||
async getUserProfile(cookie) {
|
||||
|
||||
const result = await this.authenticatedRequest('/?_data=root', cookie, {
|
||||
method: 'GET'
|
||||
});
|
||||
|
||||
|
||||
if (result.success) {
|
||||
|
||||
return {
|
||||
success: true,
|
||||
clubId: result.data?.userProfile?.club?.clubnr || null,
|
||||
clubName: result.data?.userProfile?.club?.name || null,
|
||||
fedNickname: result.data?.userProfile?.organization?.short || null,
|
||||
ttr: result.data?.userProfile?.ttr || null,
|
||||
qttr: result.data?.userProfile?.qttr || null,
|
||||
userProfile: result.data?.userProfile || null
|
||||
};
|
||||
}
|
||||
|
||||
console.error('[getUserProfile] - Failed:', result.error);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get club rankings (andro-Rangliste)
|
||||
* @param {string} cookie - Authentication cookie
|
||||
* @param {string} clubId - Club number (e.g., "43030")
|
||||
* @param {string} fedNickname - Federation nickname (e.g., "HeTTV")
|
||||
* @returns {Promise<Object>} Rankings with player entries (all pages)
|
||||
*/
|
||||
async getClubRankings(cookie, clubId, fedNickname, currentRanking = 'yes') {
|
||||
const allEntries = [];
|
||||
let currentPage = 0;
|
||||
let hasMorePages = true;
|
||||
|
||||
|
||||
while (hasMorePages) {
|
||||
const endpoint = `/rankings/andro-rangliste?all-players=on&clubnr=${clubId}&fednickname=${fedNickname}¤t-ranking=${currentRanking}&results-per-page=100&page=${currentPage}&_data=routes%2F%24`;
|
||||
|
||||
|
||||
const result = await this.authenticatedRequest(endpoint, cookie, {
|
||||
method: 'GET'
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
console.error(`[getClubRankings] - Failed to fetch page ${currentPage}:`, result.error);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Find the dynamic key that contains entries
|
||||
const blockLoaderData = result.data?.pageContent?.blockLoaderData;
|
||||
if (!blockLoaderData) {
|
||||
console.error('[getClubRankings] - No blockLoaderData found');
|
||||
return {
|
||||
success: false,
|
||||
error: 'Keine blockLoaderData gefunden'
|
||||
};
|
||||
}
|
||||
|
||||
// Finde den Schlüssel, der entries enthält
|
||||
let entries = null;
|
||||
let rankingData = null;
|
||||
|
||||
for (const key in blockLoaderData) {
|
||||
if (blockLoaderData[key]?.entries) {
|
||||
entries = blockLoaderData[key].entries;
|
||||
rankingData = blockLoaderData[key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!entries) {
|
||||
console.error('[getClubRankings] - No entries found in blockLoaderData');
|
||||
return {
|
||||
success: false,
|
||||
error: 'Keine entries in blockLoaderData gefunden'
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Füge Entries hinzu
|
||||
allEntries.push(...entries);
|
||||
|
||||
// Prüfe ob es weitere Seiten gibt
|
||||
// Wenn die aktuelle Seite weniger Einträge hat als das Limit, sind wir am Ende
|
||||
// Oder wenn wir alle erwarteten Einträge haben
|
||||
if (entries.length === 0) {
|
||||
hasMorePages = false;
|
||||
} else if (rankingData.numberOfPages && currentPage >= rankingData.numberOfPages - 1) {
|
||||
hasMorePages = false;
|
||||
} else if (allEntries.length >= rankingData.resultLength) {
|
||||
hasMorePages = false;
|
||||
} else {
|
||||
currentPage++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
success: true,
|
||||
entries: allEntries,
|
||||
metadata: {
|
||||
totalEntries: allEntries.length,
|
||||
pagesFetched: currentPage + 1
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default new MyTischtennisClient();
|
||||
|
||||
@@ -1,17 +1,41 @@
|
||||
import dotenv from 'dotenv';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
dotenv.config();
|
||||
// Ensure .env is loaded from the backend folder (not dependent on process.cwd())
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
dotenv.config({ path: path.resolve(__dirname, '.env') });
|
||||
|
||||
export const development = {
|
||||
const isTestEnv = process.env.NODE_ENV === 'test';
|
||||
|
||||
const baseConfig = {
|
||||
username: process.env.DB_USER || 'root',
|
||||
password: process.env.DB_PASSWORD || 'hitomisan',
|
||||
database: process.env.DB_NAME || 'trainingdiary',
|
||||
host: process.env.DB_HOST,
|
||||
dialect: process.env.DB_DIALECT,
|
||||
host: process.env.DB_HOST || 'localhost',
|
||||
dialect: process.env.DB_DIALECT || 'mysql',
|
||||
define: {
|
||||
freezeTableName: true,
|
||||
underscored: true,
|
||||
underscoredAll: true,
|
||||
},
|
||||
logging: false,
|
||||
storage: process.env.DB_STORAGE,
|
||||
};
|
||||
|
||||
if (isTestEnv) {
|
||||
baseConfig.username = 'sqlite';
|
||||
baseConfig.password = '';
|
||||
baseConfig.database = 'sqlite';
|
||||
baseConfig.host = 'localhost';
|
||||
baseConfig.dialect = 'sqlite';
|
||||
baseConfig.storage = process.env.DB_STORAGE || ':memory:';
|
||||
}
|
||||
|
||||
if (baseConfig.dialect === 'sqlite' && !baseConfig.storage) {
|
||||
baseConfig.storage = ':memory:';
|
||||
}
|
||||
|
||||
export const development = baseConfig;
|
||||
|
||||
85
backend/controllers/apiLogController.js
Normal file
85
backend/controllers/apiLogController.js
Normal file
@@ -0,0 +1,85 @@
|
||||
import apiLogService from '../services/apiLogService.js';
|
||||
import HttpError from '../exceptions/HttpError.js';
|
||||
|
||||
class ApiLogController {
|
||||
/**
|
||||
* GET /api/logs
|
||||
* Get API logs with optional filters
|
||||
*/
|
||||
async getLogs(req, res, next) {
|
||||
try {
|
||||
const {
|
||||
userId,
|
||||
logType,
|
||||
method,
|
||||
path,
|
||||
statusCode,
|
||||
startDate,
|
||||
endDate,
|
||||
limit = 100,
|
||||
offset = 0
|
||||
} = req.query;
|
||||
|
||||
const result = await apiLogService.getLogs({
|
||||
userId: userId ? parseInt(userId) : null,
|
||||
logType,
|
||||
method,
|
||||
path,
|
||||
statusCode: statusCode ? parseInt(statusCode) : null,
|
||||
startDate,
|
||||
endDate,
|
||||
limit: parseInt(limit),
|
||||
offset: parseInt(offset)
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: result
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/logs/:id
|
||||
* Get a single log entry by ID
|
||||
*/
|
||||
async getLogById(req, res, next) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const log = await apiLogService.getLogById(parseInt(id));
|
||||
|
||||
if (!log) {
|
||||
throw new HttpError('Log entry not found', 404);
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: log
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/logs/scheduler/last-executions
|
||||
* Get last execution info for scheduler jobs
|
||||
*/
|
||||
async getLastSchedulerExecutions(req, res, next) {
|
||||
try {
|
||||
const { clubId } = req.query;
|
||||
const results = await apiLogService.getLastSchedulerExecutions(clubId ? parseInt(clubId) : null);
|
||||
res.json({
|
||||
success: true,
|
||||
data: results
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new ApiLogController();
|
||||
|
||||
128
backend/controllers/clubTeamController.js
Normal file
128
backend/controllers/clubTeamController.js
Normal file
@@ -0,0 +1,128 @@
|
||||
import ClubTeamService from '../services/clubTeamService.js';
|
||||
import { getUserByToken } from '../utils/userUtils.js';
|
||||
import { devLog } from '../utils/logger.js';
|
||||
|
||||
export const getClubTeams = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubid: clubId } = req.params;
|
||||
const { seasonid: seasonId } = req.query;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
// Check if user has access to this club
|
||||
const clubTeams = await ClubTeamService.getAllClubTeamsByClub(clubId, seasonId);
|
||||
|
||||
res.status(200).json(clubTeams);
|
||||
} catch (error) {
|
||||
console.error('[getClubTeams] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const getClubTeam = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubteamid: clubTeamId } = req.params;
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const clubTeam = await ClubTeamService.getClubTeamById(clubTeamId);
|
||||
if (!clubTeam) {
|
||||
return res.status(404).json({ error: "notfound" });
|
||||
}
|
||||
|
||||
res.status(200).json(clubTeam);
|
||||
} catch (error) {
|
||||
console.error('[getClubTeam] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const createClubTeam = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubid: clubId } = req.params;
|
||||
const { name, leagueId, seasonId } = req.body;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
if (!name) {
|
||||
return res.status(400).json({ error: "missingname" });
|
||||
}
|
||||
|
||||
const clubTeamData = {
|
||||
name,
|
||||
clubId: parseInt(clubId),
|
||||
leagueId: leagueId ? parseInt(leagueId) : null,
|
||||
seasonId: seasonId ? parseInt(seasonId) : null
|
||||
};
|
||||
|
||||
const newClubTeam = await ClubTeamService.createClubTeam(clubTeamData);
|
||||
|
||||
res.status(201).json(newClubTeam);
|
||||
} catch (error) {
|
||||
console.error('[createClubTeam] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const updateClubTeam = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubteamid: clubTeamId } = req.params;
|
||||
const { name, leagueId, seasonId } = req.body;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const updateData = {};
|
||||
if (name !== undefined) updateData.name = name;
|
||||
if (leagueId !== undefined) updateData.leagueId = leagueId ? parseInt(leagueId) : null;
|
||||
if (seasonId !== undefined) updateData.seasonId = seasonId ? parseInt(seasonId) : null;
|
||||
|
||||
const success = await ClubTeamService.updateClubTeam(clubTeamId, updateData);
|
||||
if (!success) {
|
||||
return res.status(404).json({ error: "notfound" });
|
||||
}
|
||||
|
||||
const updatedClubTeam = await ClubTeamService.getClubTeamById(clubTeamId);
|
||||
res.status(200).json(updatedClubTeam);
|
||||
} catch (error) {
|
||||
console.error('[updateClubTeam] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteClubTeam = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubteamid: clubTeamId } = req.params;
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const success = await ClubTeamService.deleteClubTeam(clubTeamId);
|
||||
if (!success) {
|
||||
return res.status(404).json({ error: "notfound" });
|
||||
}
|
||||
|
||||
res.status(200).json({ message: "Club team deleted successfully" });
|
||||
} catch (error) {
|
||||
console.error('[deleteClubTeam] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const getLeagues = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubid: clubId } = req.params;
|
||||
const { seasonid: seasonId } = req.query;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const leagues = await ClubTeamService.getLeaguesByClub(clubId, seasonId);
|
||||
|
||||
res.status(200).json(leagues);
|
||||
} catch (error) {
|
||||
console.error('[getLeagues] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
@@ -1,88 +1,86 @@
|
||||
import ClubService from '../services/clubService.js';
|
||||
import { getUserByToken } from '../utils/userUtils.js';
|
||||
import { devLog } from '../utils/logger.js';
|
||||
|
||||
export const getClubs = async (req, res) => {
|
||||
try {
|
||||
console.log('[getClubs] - get clubs');
|
||||
const clubs = await ClubService.getAllClubs();
|
||||
console.log('[getClubs] - prepare response');
|
||||
res.status(200).json(clubs);
|
||||
console.log('[getClubs] - done');
|
||||
} catch (error) {
|
||||
console.log('[getClubs] - error');
|
||||
console.log(error);
|
||||
console.error('[getClubs] - error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const addClub = async (req, res) => {
|
||||
console.log('[addClub] - Read out parameters');
|
||||
const { authcode: token } = req.headers;
|
||||
const { name: clubName } = req.body;
|
||||
|
||||
try {
|
||||
console.log('[addClub] - find club by name');
|
||||
const club = await ClubService.findClubByName(clubName);
|
||||
console.log('[addClub] - get user');
|
||||
const user = await getUserByToken(token);
|
||||
console.log('[addClub] - check if club already exists');
|
||||
if (club) {
|
||||
res.status(409).json({ error: "alreadyexists" });
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[addClub] - create club');
|
||||
const newClub = await ClubService.createClub(clubName);
|
||||
console.log('[addClub] - add user to new club');
|
||||
await ClubService.addUserToClub(user.id, newClub.id);
|
||||
console.log('[addClub] - prepare response');
|
||||
await ClubService.addUserToClub(user.id, newClub.id, true); // true = isOwner
|
||||
res.status(200).json(newClub);
|
||||
console.log('[addClub] - done');
|
||||
} catch (error) {
|
||||
console.log('[addClub] - error');
|
||||
console.log(error);
|
||||
console.error('[addClub] - error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const getClub = async (req, res) => {
|
||||
console.log('[getClub] - start');
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubid: clubId } = req.params;
|
||||
console.log('[getClub] - get user');
|
||||
const user = await getUserByToken(token);
|
||||
console.log('[getClub] - get users club');
|
||||
const access = await ClubService.getUserClubAccess(user.id, clubId);
|
||||
console.log('[getClub] - check access');
|
||||
if (access.length === 0 || !access[0].approved) {
|
||||
res.status(403).json({ error: "noaccess", status: access.length === 0 ? "notrequested" : "requested" });
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[getClub] - get club');
|
||||
const club = await ClubService.findClubById(clubId);
|
||||
console.log('[getClub] - check club exists');
|
||||
if (!club) {
|
||||
return res.status(404).json({ message: 'Club not found' });
|
||||
}
|
||||
|
||||
console.log('[getClub] - set response');
|
||||
res.status(200).json(club);
|
||||
console.log('[getClub] - done');
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
console.error('[getClub] - error:', error);
|
||||
res.status(500).json({ message: 'Server error' });
|
||||
}
|
||||
};
|
||||
|
||||
export const updateClubSettings = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubid } = req.params;
|
||||
const { greetingText, associationMemberNumber } = req.body;
|
||||
const updated = await ClubService.updateClubSettings(token, clubid, { greetingText, associationMemberNumber });
|
||||
res.status(200).json(updated);
|
||||
} catch (error) {
|
||||
if (error.message === 'noaccess') {
|
||||
res.status(403).json({ error: 'noaccess' });
|
||||
} else if (error.message === 'clubnotfound') {
|
||||
res.status(404).json({ error: 'clubnotfound' });
|
||||
} else {
|
||||
console.error('[updateClubSettings] - error:', error);
|
||||
res.status(500).json({ error: 'internalerror' });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const requestClubAccess = async (req, res) => {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubid: clubId } = req.params;
|
||||
|
||||
try {
|
||||
const user = await getUserByToken(token);
|
||||
console.log(user);
|
||||
|
||||
await ClubService.requestAccessToClub(user.id, clubId);
|
||||
res.status(200).json({});
|
||||
@@ -92,6 +90,7 @@ export const requestClubAccess = async (req, res) => {
|
||||
} else if (error.message === 'clubnotfound') {
|
||||
res.status(404).json({ err: "clubnotfound" });
|
||||
} else {
|
||||
console.error('[requestClubAccess] - error:', error);
|
||||
res.status(500).json({ err: "internalerror" });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import diaryService from '../services/diaryService.js';
|
||||
import HttpError from '../exceptions/HttpError.js';
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
const getDatesForClub = async (req, res) => {
|
||||
try {
|
||||
const { clubId } = req.params;
|
||||
@@ -38,7 +39,7 @@ const updateTrainingTimes = async (req, res) => {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { dateId, trainingStart, trainingEnd } = req.body;
|
||||
if (!dateId || !trainingStart) {
|
||||
console.log(dateId, trainingStart, trainingEnd);
|
||||
devLog(dateId, trainingStart, trainingEnd);
|
||||
throw new HttpError('notallfieldsfilled', 400);
|
||||
}
|
||||
const updatedDate = await diaryService.updateTrainingTimes(userToken, clubId, dateId, trainingStart, trainingEnd);
|
||||
@@ -116,3 +117,15 @@ const deleteTagFromDiaryDate = async (req, res) => {
|
||||
|
||||
export { getDatesForClub, createDateForClub, updateTrainingTimes, addDiaryNote, deleteDiaryNote, addDiaryTag,
|
||||
addTagToDiaryDate, deleteTagFromDiaryDate };
|
||||
|
||||
export const deleteDateForClub = async (req, res) => {
|
||||
try {
|
||||
const { clubId, dateId } = req.params;
|
||||
const { authcode: userToken } = req.headers;
|
||||
const result = await diaryService.removeDateForClub(userToken, clubId, dateId);
|
||||
res.status(200).json(result);
|
||||
} catch (error) {
|
||||
console.error('[deleteDateForClub] - Error:', error);
|
||||
res.status(error.statusCode || 500).json({ error: error.message || 'systemerror' });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import diaryDateActivityService from '../services/diaryDateActivityService.js';
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
export const createDiaryDateActivity = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
@@ -15,7 +16,7 @@ export const createDiaryDateActivity = async (req, res) => {
|
||||
});
|
||||
res.status(201).json(activityItem);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
devLog(error);
|
||||
res.status(500).json({ error: 'Error creating activity' });
|
||||
}
|
||||
};
|
||||
@@ -58,7 +59,7 @@ export const updateDiaryDateActivityOrder = async (req, res) => {
|
||||
const updatedActivity = await diaryDateActivityService.updateActivityOrder(userToken, clubId, id, orderId);
|
||||
res.status(200).json(updatedActivity);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
devLog(error);
|
||||
res.status(500).json({ error: 'Error updating activity order' });
|
||||
}
|
||||
};
|
||||
@@ -70,7 +71,7 @@ export const getDiaryDateActivities = async (req, res) => {
|
||||
const activities = await diaryDateActivityService.getActivities(userToken, clubId, diaryDateId);
|
||||
res.status(200).json(activities);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
devLog(error);
|
||||
res.status(500).json({ error: 'Error getting activities' });
|
||||
}
|
||||
}
|
||||
@@ -78,11 +79,23 @@ export const getDiaryDateActivities = async (req, res) => {
|
||||
export const addGroupActivity = async(req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId, diaryDateId, groupId, activity } = req.body;
|
||||
const activityItem = await diaryDateActivityService.addGroupActivity(userToken, clubId, diaryDateId, groupId, activity);
|
||||
const { clubId, diaryDateId, groupId, activity, timeblockId } = req.body;
|
||||
const activityItem = await diaryDateActivityService.addGroupActivity(userToken, clubId, diaryDateId, groupId, activity, timeblockId);
|
||||
res.status(201).json(activityItem);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
devLog(error);
|
||||
res.status(500).json({ error: 'Error adding group activity' });
|
||||
}
|
||||
}
|
||||
|
||||
export const deleteGroupActivity = async(req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId, groupActivityId } = req.params;
|
||||
await diaryDateActivityService.deleteGroupActivity(userToken, clubId, groupActivityId);
|
||||
res.status(200).json({ message: 'Group activity deleted' });
|
||||
} catch (error) {
|
||||
devLog(error);
|
||||
res.status(500).json({ error: 'Error deleting group activity' });
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import diaryDateTagService from "../services/diaryDateTagService.js"
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
export const getDiaryDateMemberTags = async (req, res) => {
|
||||
console.log("getDiaryDateMemberTags");
|
||||
devLog("getDiaryDateMemberTags");
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId, memberId } = req.params;
|
||||
@@ -14,7 +15,7 @@ export const getDiaryDateMemberTags = async (req, res) => {
|
||||
}
|
||||
|
||||
export const addDiaryDateTag = async (req, res) => {
|
||||
console.log("addDiaryDateTag");
|
||||
devLog("addDiaryDateTag");
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId } = req.params;
|
||||
|
||||
64
backend/controllers/diaryMemberActivityController.js
Normal file
64
backend/controllers/diaryMemberActivityController.js
Normal file
@@ -0,0 +1,64 @@
|
||||
import DiaryMemberActivity from '../models/DiaryMemberActivity.js';
|
||||
import Participant from '../models/Participant.js';
|
||||
import { checkAccess } from '../utils/userUtils.js';
|
||||
|
||||
export const getMembersForActivity = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId, diaryDateActivityId } = req.params;
|
||||
await checkAccess(userToken, clubId);
|
||||
const list = await DiaryMemberActivity.findAll({ where: { diaryDateActivityId } });
|
||||
res.status(200).json(list);
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: 'Error fetching members for activity' });
|
||||
}
|
||||
};
|
||||
|
||||
export const addMembersToActivity = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId, diaryDateActivityId } = req.params;
|
||||
const { participantIds } = req.body; // array of participant ids
|
||||
|
||||
await checkAccess(userToken, clubId);
|
||||
|
||||
if (!participantIds || !Array.isArray(participantIds)) {
|
||||
console.error('[addMembersToActivity] Invalid participantIds:', participantIds);
|
||||
return res.status(400).json({ error: 'participantIds must be an array' });
|
||||
}
|
||||
|
||||
const validParticipants = await Participant.findAll({ where: { id: participantIds } });
|
||||
|
||||
const validIds = new Set(validParticipants.map(p => p.id));
|
||||
const created = [];
|
||||
for (const pid of participantIds) {
|
||||
if (!validIds.has(pid)) {
|
||||
continue;
|
||||
}
|
||||
const existing = await DiaryMemberActivity.findOne({ where: { diaryDateActivityId, participantId: pid } });
|
||||
if (!existing) {
|
||||
const rec = await DiaryMemberActivity.create({ diaryDateActivityId, participantId: pid });
|
||||
created.push(rec);
|
||||
} else {
|
||||
}
|
||||
}
|
||||
res.status(201).json(created);
|
||||
} catch (e) {
|
||||
console.error('[addMembersToActivity] Error:', e);
|
||||
res.status(500).json({ error: 'Error adding members to activity' });
|
||||
}
|
||||
};
|
||||
|
||||
export const removeMemberFromActivity = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId, diaryDateActivityId, participantId } = req.params;
|
||||
await checkAccess(userToken, clubId);
|
||||
await DiaryMemberActivity.destroy({ where: { diaryDateActivityId, participantId } });
|
||||
res.status(200).json({ ok: true });
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: 'Error removing member from activity' });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import DiaryMemberService from '../services/diaryMemberService.js';
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
const getMemberTags = async (req, res) => {
|
||||
try {
|
||||
const { diaryDateId, memberId } = req.query;
|
||||
const { clubId } = req.params;
|
||||
const { authcode: userToken } = req.headers;
|
||||
console.log(diaryDateId, memberId, clubId);
|
||||
devLog(diaryDateId, memberId, clubId);
|
||||
const tags = await DiaryMemberService.getTagsForMemberAndDate(userToken, clubId, diaryDateId, memberId);
|
||||
res.status(200).json(tags);
|
||||
} catch (error) {
|
||||
@@ -19,7 +20,7 @@ const getMemberNotes = async (req, res) => {
|
||||
const { diaryDateId, memberId } = req.query;
|
||||
const { clubId } = req.params;
|
||||
const { authcode: userToken } = req.headers;
|
||||
console.log('---------->', userToken, clubId);
|
||||
devLog('---------->', userToken, clubId);
|
||||
const notes = await DiaryMemberService.getNotesForMember(userToken, clubId, diaryDateId, memberId);
|
||||
res.status(200).json(notes);
|
||||
} catch (error) {
|
||||
|
||||
@@ -18,17 +18,28 @@ export const getNotes = async (req, res) => {
|
||||
|
||||
export const createNote = async (req, res) => {
|
||||
try {
|
||||
const { memberId, content, tags } = req.body;
|
||||
const newNote = await DiaryNote.create({ memberId, content });
|
||||
if (tags && tags.length > 0) {
|
||||
const { memberId, diaryDateId, content, tags } = req.body;
|
||||
|
||||
if (!memberId || !diaryDateId || !content) {
|
||||
return res.status(400).json({ error: 'memberId, diaryDateId und content sind erforderlich.' });
|
||||
}
|
||||
|
||||
const newNote = await DiaryNote.create({ memberId, diaryDateId, content });
|
||||
|
||||
if (Array.isArray(tags) && tags.length > 0 && typeof newNote.addTags === 'function') {
|
||||
const tagInstances = await DiaryTag.findAll({ where: { id: tags } });
|
||||
await newNote.addTags(tagInstances);
|
||||
|
||||
const noteWithTags = await DiaryNote.findByPk(newNote.id, {
|
||||
include: [{ model: DiaryTag, as: 'tags', required: false }],
|
||||
});
|
||||
|
||||
return res.status(201).json(noteWithTags ?? newNote);
|
||||
}
|
||||
const noteWithTags = await DiaryNote.findByPk(newNote.id, {
|
||||
include: [{ model: DiaryTag, as: 'tags' }],
|
||||
});
|
||||
res.status(201).json(noteWithTags);
|
||||
|
||||
res.status(201).json(newNote);
|
||||
} catch (error) {
|
||||
console.error('[createNote] - Error:', error);
|
||||
res.status(500).json({ error: 'Error creating note' });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -12,11 +12,13 @@ export const getTags = async (req, res) => {
|
||||
export const createTag = async (req, res) => {
|
||||
try {
|
||||
const { name } = req.body;
|
||||
console.log(name);
|
||||
const newTag = await DiaryTag.findOrCreate({ where: { name }, defaults: { name } });
|
||||
res.status(201).json(newTag);
|
||||
if (!name) {
|
||||
return res.status(400).json({ error: 'Der Name des Tags ist erforderlich.' });
|
||||
}
|
||||
|
||||
const [tag, created] = await DiaryTag.findOrCreate({ where: { name }, defaults: { name } });
|
||||
res.status(created ? 201 : 200).json(tag);
|
||||
} catch (error) {
|
||||
console.log('[createTag] - Error:', error);
|
||||
res.status(500).json({ error: 'Error creating tag' });
|
||||
}
|
||||
};
|
||||
@@ -24,9 +26,14 @@ export const createTag = async (req, res) => {
|
||||
export const deleteTag = async (req, res) => {
|
||||
try {
|
||||
const { tagId } = req.params;
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId } = req.params;
|
||||
await diaryService.removeTagFromDiaryDate(userToken, clubId, tagId);
|
||||
|
||||
await DiaryDateTag.destroy({ where: { tagId } });
|
||||
const deleted = await DiaryTag.destroy({ where: { id: tagId } });
|
||||
|
||||
if (!deleted) {
|
||||
return res.status(404).json({ error: 'Tag nicht gefunden' });
|
||||
}
|
||||
|
||||
res.status(200).json({ message: 'Tag deleted' });
|
||||
} catch (error) {
|
||||
console.error('[deleteTag] - Error:', error);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import HttpError from '../exceptions/HttpError.js';
|
||||
import groupService from '../services/groupService.js';
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
const addGroup = async(req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
@@ -9,7 +10,7 @@ const addGroup = async(req, res) => {
|
||||
res.status(201).json(result);
|
||||
} catch (error) {
|
||||
console.error('[addGroup] - Error:', error);
|
||||
console.log(req.params, req.headers, req.body)
|
||||
devLog(req.params, req.headers, req.body)
|
||||
res.status(error.statusCode || 500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import MatchService from '../services/matchService.js';
|
||||
import fs from 'fs';
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
export const uploadCSV = async (req, res) => {
|
||||
try {
|
||||
const { clubId } = req.body;
|
||||
@@ -21,10 +22,11 @@ export const uploadCSV = async (req, res) => {
|
||||
|
||||
export const getLeaguesForCurrentSeason = async (req, res) => {
|
||||
try {
|
||||
console.log(req.headers, req.params);
|
||||
devLog(req.headers, req.params);
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId } = req.params;
|
||||
const leagues = await MatchService.getLeaguesForCurrentSeason(userToken, clubId);
|
||||
const { seasonid: seasonId } = req.query;
|
||||
const leagues = await MatchService.getLeaguesForCurrentSeason(userToken, clubId, seasonId);
|
||||
return res.status(200).json(leagues);
|
||||
} catch (error) {
|
||||
console.error('Error retrieving leagues:', error);
|
||||
@@ -36,7 +38,8 @@ export const getMatchesForLeagues = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId } = req.params;
|
||||
const matches = await MatchService.getMatchesForLeagues(userToken, clubId);
|
||||
const { seasonid: seasonId } = req.query;
|
||||
const matches = await MatchService.getMatchesForLeagues(userToken, clubId, seasonId);
|
||||
return res.status(200).json(matches);
|
||||
} catch (error) {
|
||||
console.error('Error retrieving matches:', error);
|
||||
@@ -55,3 +58,90 @@ export const getMatchesForLeague = async (req, res) => {
|
||||
return res.status(500).json({ error: 'Failed to retrieve matches' });
|
||||
}
|
||||
};
|
||||
|
||||
export const getLeagueTable = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId, leagueId } = req.params;
|
||||
const table = await MatchService.getLeagueTable(userToken, clubId, leagueId);
|
||||
return res.status(200).json(table);
|
||||
} catch (error) {
|
||||
console.error('Error retrieving league table:', error);
|
||||
return res.status(500).json({ error: 'Failed to retrieve league table' });
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchLeagueTableFromMyTischtennis = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId, leagueId } = req.params;
|
||||
const { userid: userIdOrEmail } = req.headers;
|
||||
|
||||
// Convert email to userId if needed
|
||||
let userId = userIdOrEmail;
|
||||
if (isNaN(userIdOrEmail)) {
|
||||
const User = (await import('../models/User.js')).default;
|
||||
const user = await User.findOne({ where: { email: userIdOrEmail } });
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
userId = user.id;
|
||||
}
|
||||
|
||||
const autoFetchService = (await import('../services/autoFetchMatchResultsService.js')).default;
|
||||
await autoFetchService.fetchAndUpdateLeagueTable(userId, leagueId);
|
||||
|
||||
// Return updated table data
|
||||
const table = await MatchService.getLeagueTable(userToken, clubId, leagueId);
|
||||
return res.status(200).json({
|
||||
message: 'League table updated from MyTischtennis',
|
||||
data: table
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching league table from MyTischtennis:', error);
|
||||
return res.status(500).json({ error: 'Failed to fetch league table from MyTischtennis' });
|
||||
}
|
||||
};
|
||||
|
||||
export const updateMatchPlayers = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { matchId } = req.params;
|
||||
const { playersReady, playersPlanned, playersPlayed } = req.body;
|
||||
|
||||
const result = await MatchService.updateMatchPlayers(
|
||||
userToken,
|
||||
matchId,
|
||||
playersReady,
|
||||
playersPlanned,
|
||||
playersPlayed
|
||||
);
|
||||
|
||||
return res.status(200).json({
|
||||
message: 'Match players updated successfully',
|
||||
data: result
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error updating match players:', error);
|
||||
return res.status(error.statusCode || 500).json({
|
||||
error: error.message || 'Failed to update match players'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const getPlayerMatchStats = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId, leagueId } = req.params;
|
||||
const { seasonid: seasonId } = req.query;
|
||||
|
||||
const stats = await MatchService.getPlayerMatchStats(userToken, clubId, leagueId, seasonId);
|
||||
|
||||
return res.status(200).json(stats);
|
||||
} catch (error) {
|
||||
console.error('Error retrieving player match stats:', error);
|
||||
return res.status(error.statusCode || 500).json({
|
||||
error: error.message || 'Failed to retrieve player match stats'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
232
backend/controllers/memberActivityController.js
Normal file
232
backend/controllers/memberActivityController.js
Normal file
@@ -0,0 +1,232 @@
|
||||
import { checkAccess } from '../utils/userUtils.js';
|
||||
import DiaryMemberActivity from '../models/DiaryMemberActivity.js';
|
||||
import DiaryDateActivity from '../models/DiaryDateActivity.js';
|
||||
import DiaryDates from '../models/DiaryDates.js';
|
||||
import Participant from '../models/Participant.js';
|
||||
import PredefinedActivity from '../models/PredefinedActivity.js';
|
||||
import GroupActivity from '../models/GroupActivity.js';
|
||||
import { Op } from 'sequelize';
|
||||
|
||||
export const getMemberActivities = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId, memberId } = req.params;
|
||||
const { period } = req.query; // 'month', '3months', '6months', 'year', 'all'
|
||||
|
||||
await checkAccess(userToken, clubId);
|
||||
|
||||
// Calculate date range based on period
|
||||
const now = new Date();
|
||||
let startDate = null;
|
||||
|
||||
switch (period) {
|
||||
case 'month':
|
||||
startDate = new Date(now.getFullYear(), now.getMonth() - 1, now.getDate());
|
||||
break;
|
||||
case '3months':
|
||||
startDate = new Date(now.getFullYear(), now.getMonth() - 3, now.getDate());
|
||||
break;
|
||||
case '6months':
|
||||
startDate = new Date(now.getFullYear(), now.getMonth() - 6, now.getDate());
|
||||
break;
|
||||
case 'year':
|
||||
startDate = new Date(now.getFullYear() - 1, now.getMonth(), now.getDate());
|
||||
break;
|
||||
case 'all':
|
||||
default:
|
||||
startDate = null;
|
||||
break;
|
||||
}
|
||||
|
||||
// Get participant ID for this member
|
||||
const participants = await Participant.findAll({
|
||||
where: { memberId: memberId }
|
||||
});
|
||||
|
||||
if (participants.length === 0) {
|
||||
return res.status(200).json([]);
|
||||
}
|
||||
|
||||
const participantIds = participants.map(p => p.id);
|
||||
|
||||
// Get all diary member activities for this member
|
||||
const whereClause = {
|
||||
participantId: participantIds
|
||||
};
|
||||
|
||||
const memberActivities = await DiaryMemberActivity.findAll({
|
||||
where: whereClause,
|
||||
include: [
|
||||
{
|
||||
model: Participant,
|
||||
as: 'participant',
|
||||
attributes: ['id', 'groupId', 'diaryDateId']
|
||||
},
|
||||
{
|
||||
model: DiaryDateActivity,
|
||||
as: 'activity',
|
||||
include: [
|
||||
{
|
||||
model: DiaryDates,
|
||||
as: 'diaryDate',
|
||||
where: startDate ? {
|
||||
date: {
|
||||
[Op.gte]: startDate
|
||||
}
|
||||
} : {}
|
||||
},
|
||||
{
|
||||
model: PredefinedActivity,
|
||||
as: 'predefinedActivity'
|
||||
},
|
||||
{
|
||||
model: GroupActivity,
|
||||
as: 'groupActivities',
|
||||
required: false
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
order: [[{ model: DiaryDateActivity, as: 'activity' }, { model: DiaryDates, as: 'diaryDate' }, 'date', 'DESC']]
|
||||
});
|
||||
|
||||
// Group activities by name and count occurrences, considering group assignment
|
||||
const activityMap = new Map();
|
||||
|
||||
for (const ma of memberActivities) {
|
||||
if (!ma.activity || !ma.activity.predefinedActivity || !ma.participant) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check group assignment
|
||||
const participantGroupId = ma.participant.groupId;
|
||||
const activityGroupIds = ma.activity.groupActivities?.map(ga => ga.groupId) || [];
|
||||
|
||||
// Filter: Only count if:
|
||||
// 1. Activity has no group assignment (empty activityGroupIds) - activity is for all groups OR
|
||||
// 2. Participant's group matches one of the activity's groups
|
||||
const shouldCount = activityGroupIds.length === 0 ||
|
||||
(participantGroupId !== null && activityGroupIds.includes(participantGroupId));
|
||||
|
||||
if (!shouldCount) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const activity = ma.activity.predefinedActivity;
|
||||
const activityName = activity.name;
|
||||
const date = ma.activity.diaryDate?.date;
|
||||
|
||||
if (!activityMap.has(activityName)) {
|
||||
activityMap.set(activityName, {
|
||||
name: activityName,
|
||||
count: 0,
|
||||
dates: []
|
||||
});
|
||||
}
|
||||
|
||||
const activityData = activityMap.get(activityName);
|
||||
activityData.count++;
|
||||
if (date) {
|
||||
activityData.dates.push(date);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert map to array and sort by count
|
||||
const activities = Array.from(activityMap.values())
|
||||
.sort((a, b) => b.count - a.count);
|
||||
|
||||
return res.status(200).json(activities);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching member activities:', error);
|
||||
return res.status(500).json({ error: 'Failed to fetch member activities' });
|
||||
}
|
||||
};
|
||||
|
||||
export const getMemberLastParticipations = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId, memberId } = req.params;
|
||||
const { limit = 3 } = req.query;
|
||||
|
||||
await checkAccess(userToken, clubId);
|
||||
|
||||
// Get participant ID for this member
|
||||
const participants = await Participant.findAll({
|
||||
where: { memberId: memberId }
|
||||
});
|
||||
|
||||
if (participants.length === 0) {
|
||||
return res.status(200).json([]);
|
||||
}
|
||||
|
||||
const participantIds = participants.map(p => p.id);
|
||||
|
||||
// Get last participations for this member
|
||||
const memberActivities = await DiaryMemberActivity.findAll({
|
||||
where: {
|
||||
participantId: participantIds
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: Participant,
|
||||
as: 'participant',
|
||||
attributes: ['id', 'groupId', 'diaryDateId']
|
||||
},
|
||||
{
|
||||
model: DiaryDateActivity,
|
||||
as: 'activity',
|
||||
include: [
|
||||
{
|
||||
model: DiaryDates,
|
||||
as: 'diaryDate'
|
||||
},
|
||||
{
|
||||
model: PredefinedActivity,
|
||||
as: 'predefinedActivity'
|
||||
},
|
||||
{
|
||||
model: GroupActivity,
|
||||
as: 'groupActivities',
|
||||
required: false
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
order: [[{ model: DiaryDateActivity, as: 'activity' }, { model: DiaryDates, as: 'diaryDate' }, 'date', 'DESC']],
|
||||
limit: parseInt(limit) * 10 // Get more to filter by group
|
||||
});
|
||||
|
||||
// Format the results, considering group assignment
|
||||
const participations = memberActivities
|
||||
.filter(ma => {
|
||||
if (!ma.activity || !ma.activity.predefinedActivity || !ma.activity.diaryDate || !ma.participant) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check group assignment
|
||||
const participantGroupId = ma.participant.groupId;
|
||||
const activityGroupIds = ma.activity.groupActivities?.map(ga => ga.groupId) || [];
|
||||
|
||||
// Filter: Only count if:
|
||||
// 1. Activity has no group assignment (empty activityGroupIds) - activity is for all groups OR
|
||||
// 2. Participant's group matches one of the activity's groups
|
||||
return activityGroupIds.length === 0 ||
|
||||
(participantGroupId !== null && activityGroupIds.includes(participantGroupId));
|
||||
})
|
||||
.slice(0, parseInt(limit)) // Limit after filtering
|
||||
.map(ma => ({
|
||||
id: ma.id,
|
||||
activityName: ma.activity.predefinedActivity.name,
|
||||
date: ma.activity.diaryDate.date,
|
||||
diaryDateId: ma.activity.diaryDate.id
|
||||
}));
|
||||
|
||||
return res.status(200).json(participations);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching member last participations:', error);
|
||||
return res.status(500).json({ error: 'Failed to fetch member last participations' });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,48 +1,41 @@
|
||||
import MemberService from "../services/memberService.js";
|
||||
import MemberTransferService from "../services/memberTransferService.js";
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
const getClubMembers = async(req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { id: clubId, showAll } = req.params;
|
||||
if (showAll === null) {
|
||||
showAll = false;
|
||||
}
|
||||
const { id: clubId } = req.params;
|
||||
const showAll = req.params.showAll ?? 'false';
|
||||
res.status(200).json(await MemberService.getClubMembers(userToken, clubId, showAll));
|
||||
} catch(error) {
|
||||
console.log('[getClubMembers] - Error: ', error);
|
||||
res.status(500).json({ error: 'systemerror' });
|
||||
}
|
||||
}
|
||||
|
||||
const getWaitingApprovals = async(req, res) => {
|
||||
try {
|
||||
console.log('[getWaitingApprovals] - Start');
|
||||
const { id: clubId } = req.params;
|
||||
console.log('[getWaitingApprovals] - get token');
|
||||
const { authcode: userToken } = req.headers;
|
||||
console.log('[getWaitingApprovals] - load for waiting approvals');
|
||||
const waitingApprovals = await MemberService.getApprovalRequests(userToken, clubId);
|
||||
console.log('[getWaitingApprovals] - set response');
|
||||
res.status(200).json(waitingApprovals);
|
||||
console.log('[getWaitingApprovals] - done');
|
||||
} catch(error) {
|
||||
console.log('[getWaitingApprovals] - Error: ', error);
|
||||
res.status(403).json({ error: error });
|
||||
}
|
||||
}
|
||||
|
||||
const setClubMembers = async (req, res) => {
|
||||
try {
|
||||
const { id: memberId, firstname: firstName, lastname: lastName, street, city, birthdate, phone, email, active,
|
||||
testMembership, picsInInternetAllowed } = req.body;
|
||||
const { id: memberId, firstname: firstName, lastname: lastName, street, city, postalCode, birthdate, phone, email, active,
|
||||
testMembership, picsInInternetAllowed, gender, ttr, qttr, memberFormHandedOver, contacts } = req.body;
|
||||
const { id: clubId } = req.params;
|
||||
const { authcode: userToken } = req.headers;
|
||||
const addResult = await MemberService.setClubMember(userToken, clubId, memberId, firstName, lastName, street, city, birthdate,
|
||||
phone, email, active, testMembership, picsInInternetAllowed);
|
||||
const addResult = await MemberService.setClubMember(userToken, clubId, memberId, firstName, lastName, street, city, postalCode, birthdate,
|
||||
phone, email, active, testMembership, picsInInternetAllowed, gender, ttr, qttr, memberFormHandedOver, contacts);
|
||||
res.status(addResult.status || 500).json(addResult.response);
|
||||
} catch (error) {
|
||||
console.error('[setClubMembers] - Error:', error);
|
||||
res.status(500).json({ error: 'Failed to upload image' });
|
||||
res.status(500).json({ error: 'Failed to save member' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,8 +43,12 @@ const uploadMemberImage = async (req, res) => {
|
||||
try {
|
||||
const { clubId, memberId } = req.params;
|
||||
const { authcode: userToken } = req.headers;
|
||||
const result = await MemberService.uploadMemberImage(userToken, clubId, memberId, req.file.buffer);
|
||||
res.status(result.status).json(result.message ? { message: result.message } : { error: result.error });
|
||||
const makePrimary =
|
||||
req.body?.makePrimary === true ||
|
||||
req.body?.makePrimary === 'true' ||
|
||||
req.query?.makePrimary === 'true';
|
||||
const result = await MemberService.uploadMemberImage(userToken, clubId, memberId, req.file.buffer, { makePrimary });
|
||||
res.status(result.status).json(result.response ?? { success: false, error: 'Unknown upload result' });
|
||||
} catch (error) {
|
||||
console.error('[uploadMemberImage] - Error:', error);
|
||||
res.status(500).json({ error: 'Failed to upload image' });
|
||||
@@ -59,11 +56,12 @@ const uploadMemberImage = async (req, res) => {
|
||||
};
|
||||
|
||||
const getMemberImage = async (req, res) => {
|
||||
console.log('[getMemberImage]');
|
||||
try {
|
||||
const { clubId, memberId } = req.params;
|
||||
const { clubId, memberId, imageId } = req.params;
|
||||
const { authcode: userToken } = req.headers;
|
||||
const result = await MemberService.getMemberImage(userToken, clubId, memberId);
|
||||
// Support "latest" as imageId to get the latest image
|
||||
const actualImageId = imageId === 'latest' ? null : (imageId || null);
|
||||
const result = await MemberService.getMemberImage(userToken, clubId, memberId, actualImageId);
|
||||
if (result.status === 200) {
|
||||
res.sendFile(result.imagePath);
|
||||
} else {
|
||||
@@ -75,4 +73,174 @@ const getMemberImage = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
export { getClubMembers, getWaitingApprovals, setClubMembers, uploadMemberImage, getMemberImage };
|
||||
const updateRatingsFromMyTischtennis = async (req, res) => {
|
||||
try {
|
||||
const { id: clubId } = req.params;
|
||||
const { authcode: userToken } = req.headers;
|
||||
const result = await MemberService.updateRatingsFromMyTischtennis(userToken, clubId);
|
||||
res.status(result.status).json(result.response);
|
||||
} catch (error) {
|
||||
console.error('[updateRatingsFromMyTischtennis] - Error:', error);
|
||||
res.status(500).json({ error: 'Failed to update ratings' });
|
||||
}
|
||||
};
|
||||
|
||||
const rotateMemberImage = async (req, res) => {
|
||||
try {
|
||||
const { clubId, memberId, imageId } = req.params;
|
||||
const { direction } = req.body;
|
||||
const { authcode: userToken } = req.headers;
|
||||
|
||||
if (!direction || !['left', 'right'].includes(direction)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Ungültige Drehrichtung. Verwenden Sie "left" oder "right".'
|
||||
});
|
||||
}
|
||||
|
||||
const result = await MemberService.rotateMemberImage(userToken, clubId, memberId, imageId, direction);
|
||||
res.status(result.status).json(result.response);
|
||||
} catch (error) {
|
||||
console.error('[rotateMemberImage] - Error:', error);
|
||||
res.status(500).json({ success: false, error: 'Failed to rotate image' });
|
||||
}
|
||||
};
|
||||
|
||||
const deleteMemberImage = async (req, res) => {
|
||||
try {
|
||||
const { clubId, memberId, imageId } = req.params;
|
||||
const { authcode: userToken } = req.headers;
|
||||
const result = await MemberService.deleteMemberImage(userToken, clubId, memberId, imageId);
|
||||
res.status(result.status).json(result.response);
|
||||
} catch (error) {
|
||||
console.error('[deleteMemberImage] - Error:', error);
|
||||
res.status(500).json({ success: false, error: 'Failed to delete image' });
|
||||
}
|
||||
};
|
||||
|
||||
const generateMemberGallery = async (req, res) => {
|
||||
try {
|
||||
const { clubId } = req.params;
|
||||
const { authcode: userToken } = req.headers;
|
||||
const size = parseInt(req.query.size) || 200; // Default: 200x200
|
||||
const format = req.query.format || 'image'; // 'image' or 'json'
|
||||
const result = await MemberService.generateMemberGallery(userToken, clubId, size);
|
||||
if (result.status === 200) {
|
||||
if (format === 'json') {
|
||||
// Return member information for interactive gallery
|
||||
return res.status(200).json({
|
||||
members: result.galleryEntries.map(entry => ({
|
||||
memberId: entry.memberId,
|
||||
firstName: entry.firstName,
|
||||
lastName: entry.lastName,
|
||||
fullName: entry.fullName
|
||||
}))
|
||||
});
|
||||
}
|
||||
res.setHeader('Content-Type', 'image/png');
|
||||
res.setHeader('Cache-Control', 'no-store');
|
||||
return res.status(200).send(result.buffer);
|
||||
}
|
||||
return res.status(result.status).json({ error: result.error || 'Galerie konnte nicht erstellt werden' });
|
||||
} catch (error) {
|
||||
console.error('[generateMemberGallery] - Error:', error);
|
||||
res.status(500).json({ error: 'Failed to generate member gallery' });
|
||||
}
|
||||
};
|
||||
|
||||
const setPrimaryMemberImage = async (req, res) => {
|
||||
try {
|
||||
const { clubId, memberId, imageId } = req.params;
|
||||
const { authcode: userToken } = req.headers;
|
||||
const result = await MemberService.setPrimaryMemberImage(userToken, clubId, memberId, imageId);
|
||||
res.status(result.status).json(result.response);
|
||||
} catch (error) {
|
||||
console.error('[setPrimaryMemberImage] - Error:', error);
|
||||
res.status(500).json({ success: false, error: 'Failed to update primary image' });
|
||||
}
|
||||
};
|
||||
|
||||
const quickUpdateTestMembership = async (req, res) => {
|
||||
try {
|
||||
const { clubId, memberId } = req.params;
|
||||
const { authcode: userToken } = req.headers;
|
||||
const result = await MemberService.quickUpdateTestMembership(userToken, clubId, memberId);
|
||||
res.status(result.status).json(result.response);
|
||||
} catch (error) {
|
||||
console.error('[quickUpdateTestMembership] - Error:', error);
|
||||
res.status(500).json({ error: 'Failed to update test membership' });
|
||||
}
|
||||
};
|
||||
|
||||
const quickUpdateMemberFormHandedOver = async (req, res) => {
|
||||
try {
|
||||
const { clubId, memberId } = req.params;
|
||||
const { authcode: userToken } = req.headers;
|
||||
const result = await MemberService.quickUpdateMemberFormHandedOver(userToken, clubId, memberId);
|
||||
res.status(result.status).json(result.response);
|
||||
} catch (error) {
|
||||
console.error('[quickUpdateMemberFormHandedOver] - Error:', error);
|
||||
res.status(500).json({ error: 'Failed to update member form status' });
|
||||
}
|
||||
};
|
||||
|
||||
const quickDeactivateMember = async (req, res) => {
|
||||
try {
|
||||
const { clubId, memberId } = req.params;
|
||||
const { authcode: userToken } = req.headers;
|
||||
const result = await MemberService.quickDeactivateMember(userToken, clubId, memberId);
|
||||
res.status(result.status).json(result.response);
|
||||
} catch (error) {
|
||||
console.error('[quickDeactivateMember] - Error:', error);
|
||||
res.status(500).json({ error: 'Failed to deactivate member' });
|
||||
}
|
||||
};
|
||||
|
||||
const transferMembers = async (req, res) => {
|
||||
try {
|
||||
const { id: clubId } = req.params;
|
||||
const { authcode: userToken } = req.headers;
|
||||
const config = req.body;
|
||||
|
||||
// Validierung
|
||||
if (!config.transferEndpoint) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Übertragungs-Endpoint ist erforderlich'
|
||||
});
|
||||
}
|
||||
|
||||
if (!config.transferTemplate) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Übertragungs-Template ist erforderlich'
|
||||
});
|
||||
}
|
||||
|
||||
const result = await MemberTransferService.transferMembers(userToken, clubId, config);
|
||||
res.status(result.status).json(result.response);
|
||||
} catch (error) {
|
||||
console.error('[transferMembers] - Error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Fehler bei der Übertragung: ' + error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
getClubMembers,
|
||||
getWaitingApprovals,
|
||||
setClubMembers,
|
||||
uploadMemberImage,
|
||||
getMemberImage,
|
||||
updateRatingsFromMyTischtennis,
|
||||
rotateMemberImage,
|
||||
transferMembers,
|
||||
quickUpdateTestMembership,
|
||||
quickUpdateMemberFormHandedOver,
|
||||
quickDeactivateMember,
|
||||
deleteMemberImage,
|
||||
setPrimaryMemberImage,
|
||||
generateMemberGallery
|
||||
};
|
||||
@@ -1,15 +1,15 @@
|
||||
import MemberNoteService from "../services/memberNoteService.js";
|
||||
import MemberNote from '../models/MemberNote.js';
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
const getMemberNotes = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { memberId } = req.params;
|
||||
const { clubId } = req.query;
|
||||
console.log('[getMemberNotes]', userToken, memberId, clubId);
|
||||
const notes = await MemberNoteService.getNotesForMember(userToken, clubId, memberId);
|
||||
res.status(200).json(notes);
|
||||
} catch (error) {
|
||||
console.log('[getMemberNotes] - Error: ', error);
|
||||
res.status(500).json({ error: 'systemerror' });
|
||||
}
|
||||
};
|
||||
@@ -18,12 +18,11 @@ const addMemberNote = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { memberId, content, clubId } = req.body;
|
||||
console.log('[addMemberNote]', userToken, memberId, content, clubId);
|
||||
await MemberNoteService.addNoteToMember(userToken, clubId, memberId, content);
|
||||
const notes = await MemberNoteService.getNotesForMember(userToken, clubId, memberId);
|
||||
res.status(201).json(notes);
|
||||
} catch (error) {
|
||||
console.log('[addMemberNote] - Error: ', error);
|
||||
console.error('[addMemberNote] - Error:', error);
|
||||
res.status(500).json({ error: 'systemerror' });
|
||||
}
|
||||
};
|
||||
@@ -33,13 +32,16 @@ const deleteMemberNote = async (req, res) => {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { noteId } = req.params;
|
||||
const { clubId } = req.body;
|
||||
console.log('[deleteMemberNote]', userToken, noteId, clubId);
|
||||
const memberId = await MemberNoteService.getMemberIdForNote(noteId); // Member ID ermitteln
|
||||
const note = await MemberNote.findByPk(noteId);
|
||||
if (!note) {
|
||||
return res.status(404).json({ error: 'notfound' });
|
||||
}
|
||||
const memberId = note.memberId;
|
||||
await MemberNoteService.deleteNoteForMember(userToken, clubId, noteId);
|
||||
const notes = await MemberNoteService.getNotesForMember(userToken, clubId, memberId);
|
||||
res.status(200).json(notes);
|
||||
} catch (error) {
|
||||
console.log('[deleteMemberNote] - Error: ', error);
|
||||
console.error('[deleteMemberNote] - Error:', error);
|
||||
res.status(500).json({ error: 'systemerror' });
|
||||
}
|
||||
};
|
||||
|
||||
51
backend/controllers/memberTransferConfigController.js
Normal file
51
backend/controllers/memberTransferConfigController.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import MemberTransferConfigService from '../services/memberTransferConfigService.js';
|
||||
|
||||
export const getConfig = async (req, res) => {
|
||||
try {
|
||||
const { id: clubId } = req.params;
|
||||
const { authcode: userToken } = req.headers;
|
||||
|
||||
const result = await MemberTransferConfigService.getConfig(userToken, clubId);
|
||||
res.status(result.status).json(result.response);
|
||||
} catch (error) {
|
||||
console.error('[getConfig] Error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Fehler beim Laden der Konfiguration'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const saveConfig = async (req, res) => {
|
||||
try {
|
||||
const { id: clubId } = req.params;
|
||||
const { authcode: userToken } = req.headers;
|
||||
const configData = req.body;
|
||||
|
||||
const result = await MemberTransferConfigService.saveConfig(userToken, clubId, configData);
|
||||
res.status(result.status).json(result.response);
|
||||
} catch (error) {
|
||||
console.error('[saveConfig] Error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Fehler beim Speichern der Konfiguration'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteConfig = async (req, res) => {
|
||||
try {
|
||||
const { id: clubId } = req.params;
|
||||
const { authcode: userToken } = req.headers;
|
||||
|
||||
const result = await MemberTransferConfigService.deleteConfig(userToken, clubId);
|
||||
res.status(result.status).json(result.response);
|
||||
} catch (error) {
|
||||
console.error('[deleteConfig] Error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Fehler beim Löschen der Konfiguration'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
205
backend/controllers/myTischtennisController.js
Normal file
205
backend/controllers/myTischtennisController.js
Normal file
@@ -0,0 +1,205 @@
|
||||
import myTischtennisService from '../services/myTischtennisService.js';
|
||||
import HttpError from '../exceptions/HttpError.js';
|
||||
|
||||
class MyTischtennisController {
|
||||
/**
|
||||
* GET /api/mytischtennis/account
|
||||
* Get current user's myTischtennis account
|
||||
*/
|
||||
async getAccount(req, res, next) {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
const account = await myTischtennisService.getAccount(userId);
|
||||
|
||||
if (!account) {
|
||||
return res.status(200).json({ account: null });
|
||||
}
|
||||
|
||||
res.status(200).json({ account });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/mytischtennis/status
|
||||
* Check account configuration status
|
||||
*/
|
||||
async getStatus(req, res, next) {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
const status = await myTischtennisService.checkAccountStatus(userId);
|
||||
res.status(200).json(status);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/mytischtennis/account
|
||||
* Create or update myTischtennis account
|
||||
*/
|
||||
async upsertAccount(req, res, next) {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
const { email, password, savePassword, autoUpdateRatings, userPassword } = req.body;
|
||||
|
||||
if (!email) {
|
||||
throw new HttpError('E-Mail-Adresse erforderlich', 400);
|
||||
}
|
||||
|
||||
// Wenn ein Passwort gesetzt wird, muss das App-Passwort angegeben werden
|
||||
if (password && !userPassword) {
|
||||
throw new HttpError('App-Passwort erforderlich zum Setzen des myTischtennis-Passworts', 400);
|
||||
}
|
||||
|
||||
const account = await myTischtennisService.upsertAccount(
|
||||
userId,
|
||||
email,
|
||||
password,
|
||||
savePassword || false,
|
||||
autoUpdateRatings || false,
|
||||
userPassword
|
||||
);
|
||||
|
||||
res.status(200).json({
|
||||
message: 'myTischtennis-Account erfolgreich gespeichert',
|
||||
account
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /api/mytischtennis/account
|
||||
* Delete myTischtennis account
|
||||
*/
|
||||
async deleteAccount(req, res, next) {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
const deleted = await myTischtennisService.deleteAccount(userId);
|
||||
|
||||
if (!deleted) {
|
||||
throw new HttpError('Kein myTischtennis-Account gefunden', 404);
|
||||
}
|
||||
|
||||
res.status(200).json({ message: 'myTischtennis-Account gelöscht' });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/mytischtennis/verify
|
||||
* Verify login credentials
|
||||
*/
|
||||
async verifyLogin(req, res, next) {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
const { password } = req.body;
|
||||
|
||||
const result = await myTischtennisService.verifyLogin(userId, password);
|
||||
|
||||
res.status(200).json({
|
||||
message: 'Login erfolgreich',
|
||||
success: true,
|
||||
accessToken: result.accessToken,
|
||||
expiresAt: result.expiresAt,
|
||||
clubId: result.clubId,
|
||||
clubName: result.clubName
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/mytischtennis/session
|
||||
* Get stored session data for authenticated requests
|
||||
*/
|
||||
async getSession(req, res, next) {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
const session = await myTischtennisService.getSession(userId);
|
||||
|
||||
res.status(200).json({ session });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/mytischtennis/update-history
|
||||
* Get update ratings history
|
||||
*/
|
||||
async getUpdateHistory(req, res, next) {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
const history = await myTischtennisService.getUpdateHistory(userId);
|
||||
res.status(200).json({ history });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get fetch logs for current user
|
||||
*/
|
||||
async getFetchLogs(req, res, next) {
|
||||
try {
|
||||
const { userid: userIdOrEmail } = req.headers;
|
||||
|
||||
// Convert email to userId if needed
|
||||
let userId = userIdOrEmail;
|
||||
if (isNaN(userIdOrEmail)) {
|
||||
const User = (await import('../models/User.js')).default;
|
||||
const user = await User.findOne({ where: { email: userIdOrEmail } });
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
userId = user.id;
|
||||
}
|
||||
|
||||
const fetchLogService = (await import('../services/myTischtennisFetchLogService.js')).default;
|
||||
const logs = await fetchLogService.getFetchLogs(userId, {
|
||||
limit: req.query.limit ? parseInt(req.query.limit) : 50,
|
||||
fetchType: req.query.type
|
||||
});
|
||||
|
||||
res.status(200).json({ logs });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get latest successful fetches for each type
|
||||
*/
|
||||
async getLatestFetches(req, res, next) {
|
||||
try {
|
||||
const { userid: userIdOrEmail } = req.headers;
|
||||
|
||||
// Convert email to userId if needed
|
||||
let userId = userIdOrEmail;
|
||||
if (isNaN(userIdOrEmail)) {
|
||||
const User = (await import('../models/User.js')).default;
|
||||
const user = await User.findOne({ where: { email: userIdOrEmail } });
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
userId = user.id;
|
||||
}
|
||||
|
||||
const fetchLogService = (await import('../services/myTischtennisFetchLogService.js')).default;
|
||||
const latestFetches = await fetchLogService.getLatestSuccessfulFetches(userId);
|
||||
|
||||
res.status(200).json({ latestFetches });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new MyTischtennisController();
|
||||
|
||||
601
backend/controllers/myTischtennisUrlController.js
Normal file
601
backend/controllers/myTischtennisUrlController.js
Normal file
@@ -0,0 +1,601 @@
|
||||
import myTischtennisUrlParserService from '../services/myTischtennisUrlParserService.js';
|
||||
import myTischtennisService from '../services/myTischtennisService.js';
|
||||
import MemberService from '../services/memberService.js';
|
||||
import autoFetchMatchResultsService from '../services/autoFetchMatchResultsService.js';
|
||||
import apiLogService from '../services/apiLogService.js';
|
||||
import ClubTeam from '../models/ClubTeam.js';
|
||||
import League from '../models/League.js';
|
||||
import Season from '../models/Season.js';
|
||||
import User from '../models/User.js';
|
||||
import HttpError from '../exceptions/HttpError.js';
|
||||
import { devLog } from '../utils/logger.js';
|
||||
|
||||
class MyTischtennisUrlController {
|
||||
/**
|
||||
* Parse myTischtennis URL and return configuration data
|
||||
* POST /api/mytischtennis/parse-url
|
||||
* Body: { url: string }
|
||||
*/
|
||||
async parseUrl(req, res, next) {
|
||||
try {
|
||||
const { url } = req.body;
|
||||
|
||||
if (!url) {
|
||||
throw new HttpError('URL is required', 400);
|
||||
}
|
||||
|
||||
// Validate URL
|
||||
if (!myTischtennisUrlParserService.isValidTeamUrl(url)) {
|
||||
throw new HttpError('Invalid myTischtennis URL format', 400);
|
||||
}
|
||||
|
||||
// Parse URL
|
||||
const parsedData = myTischtennisUrlParserService.parseUrl(url);
|
||||
|
||||
// Try to fetch additional data if user is authenticated
|
||||
const userIdOrEmail = req.headers.userid;
|
||||
let completeData = parsedData;
|
||||
|
||||
if (userIdOrEmail) {
|
||||
// Get actual user ID
|
||||
let userId = userIdOrEmail;
|
||||
if (isNaN(userIdOrEmail)) {
|
||||
const user = await User.findOne({ where: { email: userIdOrEmail } });
|
||||
if (user) userId = user.id;
|
||||
}
|
||||
|
||||
try {
|
||||
const account = await myTischtennisService.getAccount(userId);
|
||||
|
||||
if (account && account.accessToken) {
|
||||
completeData = await myTischtennisUrlParserService.fetchTeamData(
|
||||
parsedData,
|
||||
account.cookie,
|
||||
account.accessToken
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
// Continue with parsed data only
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: completeData
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure team from myTischtennis URL
|
||||
* POST /api/mytischtennis/configure-team
|
||||
* Body: { url: string, clubTeamId: number, createLeague?: boolean, createSeason?: boolean }
|
||||
*/
|
||||
async configureTeam(req, res, next) {
|
||||
try {
|
||||
const { url, clubTeamId, createLeague, createSeason } = req.body;
|
||||
const userIdOrEmail = req.headers.userid;
|
||||
|
||||
if (!url || !clubTeamId) {
|
||||
throw new HttpError('URL and clubTeamId are required', 400);
|
||||
}
|
||||
|
||||
// Get actual user ID
|
||||
let userId = userIdOrEmail;
|
||||
if (isNaN(userIdOrEmail)) {
|
||||
const user = await User.findOne({ where: { email: userIdOrEmail } });
|
||||
if (!user) {
|
||||
throw new HttpError('User not found', 404);
|
||||
}
|
||||
userId = user.id;
|
||||
}
|
||||
|
||||
// Parse URL
|
||||
const parsedData = myTischtennisUrlParserService.parseUrl(url);
|
||||
|
||||
// Try to fetch additional data
|
||||
let completeData = parsedData;
|
||||
const account = await myTischtennisService.getAccount(userId);
|
||||
|
||||
if (account && account.accessToken) {
|
||||
try {
|
||||
completeData = await myTischtennisUrlParserService.fetchTeamData(
|
||||
parsedData,
|
||||
account.cookie,
|
||||
account.accessToken
|
||||
);
|
||||
} catch (error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Find or create season
|
||||
let season = await Season.findOne({
|
||||
where: { season: completeData.season }
|
||||
});
|
||||
|
||||
if (!season && createSeason) {
|
||||
season = await Season.create({
|
||||
season: completeData.season
|
||||
});
|
||||
}
|
||||
|
||||
if (!season) {
|
||||
throw new HttpError(`Season ${completeData.season} not found. Set createSeason=true to create it.`, 404);
|
||||
}
|
||||
|
||||
// Find or create league
|
||||
const team = await ClubTeam.findByPk(clubTeamId);
|
||||
if (!team) {
|
||||
throw new HttpError('Club team not found', 404);
|
||||
}
|
||||
|
||||
let league;
|
||||
|
||||
// First, try to find existing league by name and season
|
||||
const leagueName = completeData.leagueName || completeData.groupname;
|
||||
league = await League.findOne({
|
||||
where: {
|
||||
name: leagueName,
|
||||
seasonId: season.id,
|
||||
clubId: team.clubId
|
||||
}
|
||||
});
|
||||
|
||||
if (league) {
|
||||
devLog(`Found existing league: ${league.name} (ID: ${league.id})`);
|
||||
// Update myTischtennis fields
|
||||
await league.update({
|
||||
myTischtennisGroupId: completeData.groupId,
|
||||
association: completeData.association,
|
||||
groupname: completeData.groupname
|
||||
});
|
||||
} else if (team.leagueId) {
|
||||
// Team has a league assigned, update it
|
||||
league = await League.findByPk(team.leagueId);
|
||||
|
||||
if (league) {
|
||||
devLog(`Updating team's existing league: ${league.name} (ID: ${league.id})`);
|
||||
await league.update({
|
||||
name: leagueName,
|
||||
myTischtennisGroupId: completeData.groupId,
|
||||
association: completeData.association,
|
||||
groupname: completeData.groupname
|
||||
});
|
||||
}
|
||||
} else if (createLeague) {
|
||||
// Create new league
|
||||
devLog(`Creating new league: ${leagueName}`);
|
||||
league = await League.create({
|
||||
name: leagueName,
|
||||
seasonId: season.id,
|
||||
clubId: team.clubId,
|
||||
myTischtennisGroupId: completeData.groupId,
|
||||
association: completeData.association,
|
||||
groupname: completeData.groupname
|
||||
});
|
||||
} else {
|
||||
throw new HttpError('League not found and team has no league assigned. Set createLeague=true to create one.', 400);
|
||||
}
|
||||
|
||||
// Update team
|
||||
await team.update({
|
||||
myTischtennisTeamId: completeData.teamId,
|
||||
leagueId: league.id,
|
||||
seasonId: season.id
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Team configured successfully',
|
||||
data: {
|
||||
team: {
|
||||
id: team.id,
|
||||
name: team.name,
|
||||
myTischtennisTeamId: completeData.teamId
|
||||
},
|
||||
league: {
|
||||
id: league.id,
|
||||
name: league.name,
|
||||
myTischtennisGroupId: completeData.groupId,
|
||||
association: completeData.association,
|
||||
groupname: completeData.groupname
|
||||
},
|
||||
season: {
|
||||
id: season.id,
|
||||
name: season.season
|
||||
},
|
||||
parsedData: completeData
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually fetch team data from myTischtennis
|
||||
* POST /api/mytischtennis/fetch-team-data
|
||||
* Body: { clubTeamId: number }
|
||||
*/
|
||||
async fetchTeamData(req, res, next) {
|
||||
// Define outside of try/catch so catch has access
|
||||
let account = null;
|
||||
let team = null;
|
||||
let myTischtennisUrl = null;
|
||||
let requestStartTime = null;
|
||||
try {
|
||||
const { clubTeamId } = req.body;
|
||||
const userIdOrEmail = req.headers.userid;
|
||||
|
||||
if (!clubTeamId) {
|
||||
throw new HttpError('clubTeamId is required', 400);
|
||||
}
|
||||
|
||||
if (!userIdOrEmail) {
|
||||
throw new HttpError('User-ID fehlt. Bitte melden Sie sich an.', 401);
|
||||
}
|
||||
|
||||
// Get actual user ID (userid header might be email address)
|
||||
let userId = userIdOrEmail;
|
||||
if (isNaN(userIdOrEmail)) {
|
||||
// It's an email, find the user
|
||||
const user = await User.findOne({ where: { email: userIdOrEmail } });
|
||||
if (!user) {
|
||||
throw new HttpError('User not found', 404);
|
||||
}
|
||||
userId = user.id;
|
||||
}
|
||||
|
||||
// Get myTischtennis session (similar to memberService.updateRatingsFromMyTischtennis)
|
||||
let session;
|
||||
|
||||
try {
|
||||
session = await myTischtennisService.getSession(userId);
|
||||
} catch (sessionError) {
|
||||
// Versuche automatischen Login mit gespeicherten Credentials
|
||||
try {
|
||||
// Check if account exists and has password
|
||||
const accountCheck = await myTischtennisService.getAccount(userId);
|
||||
if (!accountCheck) {
|
||||
throw new Error('MyTischtennis-Account nicht gefunden');
|
||||
}
|
||||
|
||||
if (!accountCheck.encryptedPassword) {
|
||||
throw new Error('Kein Passwort gespeichert. Bitte melden Sie sich in den MyTischtennis-Einstellungen an und speichern Sie Ihr Passwort.');
|
||||
}
|
||||
|
||||
await myTischtennisService.verifyLogin(userId);
|
||||
session = await myTischtennisService.getSession(userId);
|
||||
} catch (loginError) {
|
||||
const errorMessage = loginError.message || 'Automatischer Login fehlgeschlagen';
|
||||
throw new HttpError(`MyTischtennis-Session abgelaufen und automatischer Login fehlgeschlagen: ${errorMessage}. Bitte melden Sie sich in den MyTischtennis-Einstellungen an.`, 401);
|
||||
}
|
||||
}
|
||||
|
||||
// Get account data (for clubId, etc.)
|
||||
account = await myTischtennisService.getAccount(userId);
|
||||
|
||||
if (!account) {
|
||||
throw new HttpError('MyTischtennis-Account nicht verknüpft. Bitte verknüpfen Sie Ihren Account in den MyTischtennis-Einstellungen.', 404);
|
||||
}
|
||||
|
||||
|
||||
// Get team with league and season
|
||||
team = await ClubTeam.findByPk(clubTeamId, {
|
||||
include: [
|
||||
{
|
||||
model: League,
|
||||
as: 'league',
|
||||
include: [
|
||||
{
|
||||
model: Season,
|
||||
as: 'season'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!team) {
|
||||
throw new HttpError(`Team mit ID ${clubTeamId} nicht gefunden`, 404);
|
||||
}
|
||||
|
||||
// Verbesserte Validierung mit detaillierten Fehlermeldungen
|
||||
if (!team.myTischtennisTeamId) {
|
||||
throw new HttpError(`Team "${team.name}" (interne ID: ${team.id}) ist nicht für myTischtennis konfiguriert: myTischtennisTeamId fehlt. Bitte konfigurieren Sie das Team zuerst über die MyTischtennis-URL.`, 400);
|
||||
}
|
||||
|
||||
if (!team.league) {
|
||||
throw new HttpError('Team ist keiner Liga zugeordnet. Bitte ordnen Sie das Team einer Liga zu.', 400);
|
||||
}
|
||||
|
||||
if (!team.league.myTischtennisGroupId) {
|
||||
throw new HttpError('Liga ist nicht für myTischtennis konfiguriert: myTischtennisGroupId fehlt. Bitte konfigurieren Sie die Liga zuerst über die MyTischtennis-URL.', 400);
|
||||
}
|
||||
|
||||
// Validate season before proceeding
|
||||
if (!team.league.season || !team.league.season.season) {
|
||||
throw new HttpError('Liga ist keiner Saison zugeordnet. Bitte ordnen Sie die Liga einer Saison zu.', 400);
|
||||
}
|
||||
|
||||
// Build the URL that will be used - do this early so we can log it even if errors occur
|
||||
const seasonFull = team.league.season.season;
|
||||
const seasonParts = seasonFull.split('/');
|
||||
const seasonShort = seasonParts.length === 2
|
||||
? `${seasonParts[0].slice(-2)}/${seasonParts[1].slice(-2)}`
|
||||
: seasonFull;
|
||||
const seasonStr = seasonShort.replace('/', '--');
|
||||
const teamnameEncoded = encodeURIComponent(team.name.replace(/\s/g, '_'));
|
||||
myTischtennisUrl = `https://www.mytischtennis.de/click-tt/${team.league.association}/${seasonStr}/ligen/${team.league.groupname}/gruppe/${team.league.myTischtennisGroupId}/mannschaft/${team.myTischtennisTeamId}/${teamnameEncoded}/spielerbilanzen/gesamt`;
|
||||
|
||||
// Log the request to myTischtennis BEFORE making the call
|
||||
// This ensures we always see what WILL BE sent, even if the call fails
|
||||
requestStartTime = Date.now();
|
||||
try {
|
||||
await apiLogService.logRequest({
|
||||
userId: account.userId,
|
||||
method: 'GET',
|
||||
path: myTischtennisUrl.replace('https://www.mytischtennis.de', ''),
|
||||
statusCode: null,
|
||||
requestBody: JSON.stringify({
|
||||
url: myTischtennisUrl,
|
||||
myTischtennisTeamId: team.myTischtennisTeamId,
|
||||
clubTeamId: team.id,
|
||||
teamName: team.name,
|
||||
leagueName: team.league.name,
|
||||
association: team.league.association,
|
||||
groupId: team.league.myTischtennisGroupId,
|
||||
groupname: team.league.groupname,
|
||||
season: seasonFull
|
||||
}),
|
||||
responseBody: null,
|
||||
executionTime: null,
|
||||
errorMessage: 'Request wird ausgeführt...',
|
||||
logType: 'api_request',
|
||||
schedulerJobType: 'mytischtennis_fetch'
|
||||
});
|
||||
} catch (logError) {
|
||||
// Silent fail - logging errors shouldn't break the request
|
||||
}
|
||||
|
||||
// Fetch data for this specific team
|
||||
// Note: fetchTeamResults will also log and update with actual response
|
||||
const result = await autoFetchMatchResultsService.fetchTeamResults(
|
||||
{
|
||||
userId: account.userId,
|
||||
email: account.email,
|
||||
cookie: session.cookie,
|
||||
accessToken: session.accessToken,
|
||||
expiresAt: session.expiresAt,
|
||||
getPassword: () => null // Not needed for manual fetch
|
||||
},
|
||||
team
|
||||
);
|
||||
|
||||
// Also fetch and update league table data
|
||||
let tableUpdateResult = null;
|
||||
try {
|
||||
await autoFetchMatchResultsService.fetchAndUpdateLeagueTable(account.userId, team.league.id);
|
||||
tableUpdateResult = 'League table updated successfully';
|
||||
} catch (error) {
|
||||
tableUpdateResult = 'League table update failed: ' + error.message;
|
||||
// Don't fail the entire request if table update fails
|
||||
}
|
||||
|
||||
// Additionally update (Q)TTR ratings for the club
|
||||
let ratingsUpdate = null;
|
||||
try {
|
||||
// Use already resolved userId instead of authcode to avoid header dependency
|
||||
const ratingsResult = await MemberService.updateRatingsFromMyTischtennisByUserId(userId, team.clubId);
|
||||
ratingsUpdate = ratingsResult?.response?.message || `Ratings update status: ${ratingsResult?.status}`;
|
||||
} catch (ratingsErr) {
|
||||
ratingsUpdate = 'Ratings update failed: ' + (ratingsErr.message || String(ratingsErr));
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `${result.fetchedCount} Datensätze abgerufen und verarbeitet`,
|
||||
data: {
|
||||
fetchedCount: result.fetchedCount,
|
||||
teamName: team.name,
|
||||
tableUpdate: tableUpdateResult,
|
||||
ratingsUpdate
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
// Update log with error information if we got far enough to build the URL
|
||||
if (myTischtennisUrl && account && team) {
|
||||
const requestExecutionTime = requestStartTime ? (Date.now() - requestStartTime) : null;
|
||||
try {
|
||||
await apiLogService.logRequest({
|
||||
userId: account.userId,
|
||||
method: 'GET',
|
||||
path: myTischtennisUrl.replace('https://www.mytischtennis.de', ''),
|
||||
statusCode: 0,
|
||||
requestBody: JSON.stringify({
|
||||
url: myTischtennisUrl,
|
||||
myTischtennisTeamId: team.myTischtennisTeamId,
|
||||
clubTeamId: team.id,
|
||||
teamName: team.name,
|
||||
leagueName: team.league?.name,
|
||||
association: team.league?.association,
|
||||
groupname: team.league?.groupname,
|
||||
groupId: team.league?.myTischtennisGroupId
|
||||
}),
|
||||
responseBody: null,
|
||||
executionTime: requestExecutionTime,
|
||||
errorMessage: error.message || String(error),
|
||||
logType: 'api_request',
|
||||
schedulerJobType: 'mytischtennis_fetch'
|
||||
});
|
||||
} catch (logError) {
|
||||
// Silent fail - logging errors shouldn't break the request
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize HTTP status code (guard against strings)
|
||||
const rawCode = error && (error.statusCode != null ? error.statusCode : error.status);
|
||||
const parsed = Number(rawCode);
|
||||
const status = Number.isInteger(parsed) && parsed >= 100 && parsed <= 599 ? parsed : 500;
|
||||
const debug = {
|
||||
message: error.message || String(error),
|
||||
name: error.name,
|
||||
stack: (process.env.NODE_ENV === 'dev' || process.env.NODE_ENV === 'development') ? (error.stack || null) : undefined,
|
||||
team: team ? { id: team.id, name: team.name } : null,
|
||||
league: team && team.league ? { id: team.league.id, name: team.league.name, association: team.league.association, groupId: team.league.myTischtennisGroupId, groupname: team.league.groupname } : null,
|
||||
url: typeof myTischtennisUrl !== 'undefined' ? myTischtennisUrl : null
|
||||
};
|
||||
try {
|
||||
if (!res.headersSent) {
|
||||
// Spezieller Fall: myTischtennis-Reauth nötig → nicht 401 an FE senden, um App-Logout zu vermeiden
|
||||
const isMyTischtennisAuthIssue = status === 401 && /MyTischtennis-Session abgelaufen|Automatischer Login fehlgeschlagen|Passwort gespeichert/i.test(debug.message || '');
|
||||
if (isMyTischtennisAuthIssue) {
|
||||
return res.status(200).json({ success: false, error: debug.message, debug, needsMyTischtennisReauth: true });
|
||||
}
|
||||
res.status(status).json({ success: false, error: debug.message, debug });
|
||||
}
|
||||
} catch (writeErr) {
|
||||
// Fallback, falls Headers schon gesendet wurden
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('[fetchTeamData] Response write failed:', writeErr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get myTischtennis URL for a team
|
||||
* GET /api/mytischtennis/team-url/:teamId
|
||||
*/
|
||||
async getTeamUrl(req, res, next) {
|
||||
try {
|
||||
const { teamId } = req.params;
|
||||
|
||||
const team = await ClubTeam.findByPk(teamId, {
|
||||
include: [
|
||||
{
|
||||
model: League,
|
||||
as: 'league',
|
||||
include: [
|
||||
{
|
||||
model: Season,
|
||||
as: 'season'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!team) {
|
||||
throw new HttpError('Team not found', 404);
|
||||
}
|
||||
|
||||
if (!team.myTischtennisTeamId || !team.league || !team.league.myTischtennisGroupId) {
|
||||
throw new HttpError('Team is not configured for myTischtennis', 400);
|
||||
}
|
||||
|
||||
const url = myTischtennisUrlParserService.buildUrl({
|
||||
association: team.league.association,
|
||||
season: team.league.season?.season,
|
||||
groupname: team.league.groupname,
|
||||
groupId: team.league.myTischtennisGroupId,
|
||||
teamId: team.myTischtennisTeamId,
|
||||
teamname: team.name
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
url
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure league from myTischtennis table URL
|
||||
* POST /api/mytischtennis/configure-league
|
||||
* Body: { url: string, createSeason?: boolean }
|
||||
*/
|
||||
async configureLeague(req, res, next) {
|
||||
try {
|
||||
const { url, createSeason } = req.body;
|
||||
const userIdOrEmail = req.headers.userid;
|
||||
|
||||
if (!url) {
|
||||
throw new HttpError('URL is required', 400);
|
||||
}
|
||||
|
||||
// Parse URL
|
||||
const parsedData = myTischtennisUrlParserService.parseUrl(url);
|
||||
|
||||
if (parsedData.urlType !== 'table') {
|
||||
throw new HttpError('URL must be a table URL (not a team URL)', 400);
|
||||
}
|
||||
|
||||
// Find or create season
|
||||
let season = await Season.findOne({
|
||||
where: { season: parsedData.season }
|
||||
});
|
||||
|
||||
if (!season && createSeason) {
|
||||
season = await Season.create({
|
||||
season: parsedData.season,
|
||||
startDate: new Date(),
|
||||
endDate: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000) // 1 Jahr später
|
||||
});
|
||||
}
|
||||
|
||||
// Find or create league
|
||||
let league = await League.findOne({
|
||||
where: {
|
||||
myTischtennisGroupId: parsedData.groupId,
|
||||
association: parsedData.association
|
||||
}
|
||||
});
|
||||
|
||||
if (!league) {
|
||||
league = await League.create({
|
||||
name: parsedData.groupnameOriginal, // Verwende die originale URL-kodierte Version
|
||||
myTischtennisGroupId: parsedData.groupId,
|
||||
association: parsedData.association,
|
||||
groupname: parsedData.groupnameOriginal, // Verwende die originale URL-kodierte Version
|
||||
seasonId: season?.id || null
|
||||
});
|
||||
} else {
|
||||
// Update existing league - aber nur wenn es sich wirklich geändert hat
|
||||
if (league.name !== parsedData.groupnameOriginal) {
|
||||
league.name = parsedData.groupnameOriginal;
|
||||
league.groupname = parsedData.groupnameOriginal;
|
||||
}
|
||||
league.seasonId = season?.id || league.seasonId;
|
||||
await league.save();
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'League configured successfully',
|
||||
data: {
|
||||
league: {
|
||||
id: league.id,
|
||||
name: league.name,
|
||||
myTischtennisGroupId: league.myTischtennisGroupId,
|
||||
association: league.association,
|
||||
groupname: league.groupname
|
||||
},
|
||||
season: season ? {
|
||||
id: season.id,
|
||||
name: season.season
|
||||
} : null,
|
||||
parsedData
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new MyTischtennisUrlController();
|
||||
619
backend/controllers/officialTournamentController.js
Normal file
619
backend/controllers/officialTournamentController.js
Normal file
@@ -0,0 +1,619 @@
|
||||
import { createRequire } from 'module';
|
||||
const require = createRequire(import.meta.url);
|
||||
const pdfParse = require('pdf-parse/lib/pdf-parse.js');
|
||||
import { checkAccess } from '../utils/userUtils.js';
|
||||
import OfficialTournament from '../models/OfficialTournament.js';
|
||||
import OfficialCompetition from '../models/OfficialCompetition.js';
|
||||
import OfficialCompetitionMember from '../models/OfficialCompetitionMember.js';
|
||||
import Member from '../models/Member.js';
|
||||
import { Op } from 'sequelize';
|
||||
|
||||
// In-Memory Store (einfacher Start); später DB-Modell
|
||||
const parsedTournaments = new Map(); // key: id, value: { id, clubId, rawText, parsedData }
|
||||
let seq = 1;
|
||||
|
||||
export const uploadTournamentPdf = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId } = req.params;
|
||||
await checkAccess(userToken, clubId);
|
||||
if (!req.file || !req.file.buffer) return res.status(400).json({ error: 'No pdf provided' });
|
||||
const data = await pdfParse(req.file.buffer);
|
||||
const parsed = parseTournamentText(data.text);
|
||||
const t = await OfficialTournament.create({
|
||||
clubId,
|
||||
title: parsed.title || null,
|
||||
eventDate: parsed.termin || null,
|
||||
organizer: null,
|
||||
host: null,
|
||||
venues: JSON.stringify(parsed.austragungsorte || []),
|
||||
competitionTypes: JSON.stringify(parsed.konkurrenztypen || []),
|
||||
registrationDeadlines: JSON.stringify(parsed.meldeschluesse || []),
|
||||
entryFees: JSON.stringify(parsed.entryFees || {}),
|
||||
});
|
||||
// competitions persistieren
|
||||
for (const c of parsed.competitions || []) {
|
||||
// Korrigiere Fehlzuordnung: Wenn die Zeile mit "Stichtag" fälschlich in performanceClass steht
|
||||
let performanceClass = c.leistungsklasse || c.performanceClass || null;
|
||||
let cutoffDate = c.stichtag || c.cutoffDate || null;
|
||||
if (performanceClass && /^stichtag\b/i.test(performanceClass)) {
|
||||
cutoffDate = performanceClass.replace(/^stichtag\s*:?\s*/i, '').trim();
|
||||
performanceClass = null;
|
||||
}
|
||||
await OfficialCompetition.create({
|
||||
tournamentId: t.id,
|
||||
ageClassCompetition: c.altersklasseWettbewerb || c.ageClassCompetition || null,
|
||||
performanceClass,
|
||||
startTime: c.startzeit || c.startTime || null,
|
||||
registrationDeadlineDate: c.meldeschlussDatum || c.registrationDeadlineDate || null,
|
||||
registrationDeadlineOnline: c.meldeschlussOnline || c.registrationDeadlineOnline || null,
|
||||
cutoffDate,
|
||||
ttrRelevant: c.ttrRelevant || null,
|
||||
openTo: c.offenFuer || c.openTo || null,
|
||||
preliminaryRound: c.vorrunde || c.preliminaryRound || null,
|
||||
finalRound: c.endrunde || c.finalRound || null,
|
||||
maxParticipants: c.maxTeilnehmer || c.maxParticipants || null,
|
||||
entryFee: c.startgeld || c.entryFee || null,
|
||||
});
|
||||
}
|
||||
res.status(201).json({ id: String(t.id) });
|
||||
} catch (e) {
|
||||
console.error('[uploadTournamentPdf] Error:', e);
|
||||
res.status(500).json({ error: 'Failed to parse pdf' });
|
||||
}
|
||||
};
|
||||
|
||||
export const getParsedTournament = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId, id } = req.params;
|
||||
await checkAccess(userToken, clubId);
|
||||
const t = await OfficialTournament.findOne({ where: { id, clubId } });
|
||||
if (!t) return res.status(404).json({ error: 'not found' });
|
||||
const comps = await OfficialCompetition.findAll({ where: { tournamentId: id } });
|
||||
const entries = await OfficialCompetitionMember.findAll({ where: { tournamentId: id } });
|
||||
const competitions = comps.map((c) => {
|
||||
const j = c.toJSON();
|
||||
return {
|
||||
id: j.id,
|
||||
tournamentId: j.tournamentId,
|
||||
ageClassCompetition: j.ageClassCompetition || null,
|
||||
performanceClass: j.performanceClass || null,
|
||||
startTime: j.startTime || null,
|
||||
registrationDeadlineDate: j.registrationDeadlineDate || null,
|
||||
registrationDeadlineOnline: j.registrationDeadlineOnline || null,
|
||||
cutoffDate: j.cutoffDate || null,
|
||||
ttrRelevant: j.ttrRelevant || null,
|
||||
openTo: j.openTo || null,
|
||||
preliminaryRound: j.preliminaryRound || null,
|
||||
finalRound: j.finalRound || null,
|
||||
maxParticipants: j.maxParticipants || null,
|
||||
entryFee: j.entryFee || null,
|
||||
// Legacy Felder zusätzlich, falls Frontend sie noch nutzt
|
||||
altersklasseWettbewerb: j.ageClassCompetition || null,
|
||||
leistungsklasse: j.performanceClass || null,
|
||||
startzeit: j.startTime || null,
|
||||
meldeschlussDatum: j.registrationDeadlineDate || null,
|
||||
meldeschlussOnline: j.registrationDeadlineOnline || null,
|
||||
stichtag: j.cutoffDate || null,
|
||||
offenFuer: j.openTo || null,
|
||||
vorrunde: j.preliminaryRound || null,
|
||||
endrunde: j.finalRound || null,
|
||||
maxTeilnehmer: j.maxParticipants || null,
|
||||
startgeld: j.entryFee || null,
|
||||
};
|
||||
});
|
||||
res.status(200).json({
|
||||
id: String(t.id),
|
||||
clubId: String(t.clubId),
|
||||
parsedData: {
|
||||
title: t.title,
|
||||
termin: t.eventDate,
|
||||
austragungsorte: JSON.parse(t.venues || '[]'),
|
||||
konkurrenztypen: JSON.parse(t.competitionTypes || '[]'),
|
||||
meldeschluesse: JSON.parse(t.registrationDeadlines || '[]'),
|
||||
entryFees: JSON.parse(t.entryFees || '{}'),
|
||||
competitions,
|
||||
},
|
||||
participation: entries.map(e => ({
|
||||
id: e.id,
|
||||
tournamentId: e.tournamentId,
|
||||
competitionId: e.competitionId,
|
||||
memberId: e.memberId,
|
||||
wants: !!e.wants,
|
||||
registered: !!e.registered,
|
||||
participated: !!e.participated,
|
||||
placement: e.placement || null,
|
||||
})),
|
||||
});
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: 'Failed to fetch parsed tournament' });
|
||||
}
|
||||
};
|
||||
|
||||
export const upsertCompetitionMember = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId, id } = req.params; // id = tournamentId
|
||||
await checkAccess(userToken, clubId);
|
||||
const { competitionId, memberId, wants, registered, participated, placement } = req.body;
|
||||
if (!competitionId || !memberId) return res.status(400).json({ error: 'competitionId and memberId required' });
|
||||
const [row] = await OfficialCompetitionMember.findOrCreate({
|
||||
where: { competitionId, memberId },
|
||||
defaults: {
|
||||
tournamentId: id,
|
||||
competitionId,
|
||||
memberId,
|
||||
wants: !!wants,
|
||||
registered: !!registered,
|
||||
participated: !!participated,
|
||||
placement: placement || null,
|
||||
}
|
||||
});
|
||||
row.wants = wants !== undefined ? !!wants : row.wants;
|
||||
row.registered = registered !== undefined ? !!registered : row.registered;
|
||||
row.participated = participated !== undefined ? !!participated : row.participated;
|
||||
if (placement !== undefined) row.placement = placement;
|
||||
await row.save();
|
||||
return res.status(200).json({ success: true, id: row.id });
|
||||
} catch (e) {
|
||||
console.error('[upsertCompetitionMember] Error:', e);
|
||||
res.status(500).json({ error: 'Failed to save participation' });
|
||||
}
|
||||
};
|
||||
|
||||
export const updateParticipantStatus = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId, id } = req.params; // id = tournamentId
|
||||
await checkAccess(userToken, clubId);
|
||||
const { competitionId, memberId, action } = req.body;
|
||||
|
||||
if (!competitionId || !memberId || !action) {
|
||||
return res.status(400).json({ error: 'competitionId, memberId and action required' });
|
||||
}
|
||||
|
||||
const [row] = await OfficialCompetitionMember.findOrCreate({
|
||||
where: { competitionId, memberId },
|
||||
defaults: {
|
||||
tournamentId: id,
|
||||
competitionId,
|
||||
memberId,
|
||||
wants: false,
|
||||
registered: false,
|
||||
participated: false,
|
||||
placement: null,
|
||||
}
|
||||
});
|
||||
|
||||
// Status-Update basierend auf Aktion
|
||||
switch (action) {
|
||||
case 'register':
|
||||
// Von "möchte teilnehmen" zu "angemeldet"
|
||||
row.wants = true;
|
||||
row.registered = true;
|
||||
row.participated = false;
|
||||
break;
|
||||
case 'participate':
|
||||
// Von "angemeldet" zu "hat gespielt"
|
||||
row.wants = true;
|
||||
row.registered = true;
|
||||
row.participated = true;
|
||||
break;
|
||||
case 'reset':
|
||||
// Zurück zu "möchte teilnehmen"
|
||||
row.wants = true;
|
||||
row.registered = false;
|
||||
row.participated = false;
|
||||
break;
|
||||
default:
|
||||
return res.status(400).json({ error: 'Invalid action. Use: register, participate, or reset' });
|
||||
}
|
||||
|
||||
await row.save();
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
id: row.id,
|
||||
status: {
|
||||
wants: row.wants,
|
||||
registered: row.registered,
|
||||
participated: row.participated,
|
||||
placement: row.placement
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('[updateParticipantStatus] Error:', e);
|
||||
res.status(500).json({ error: 'Failed to update participant status' });
|
||||
}
|
||||
};
|
||||
|
||||
export const listOfficialTournaments = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId } = req.params;
|
||||
await checkAccess(userToken, clubId);
|
||||
const list = await OfficialTournament.findAll({ where: { clubId } });
|
||||
res.status(200).json(list);
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: 'Failed to list tournaments' });
|
||||
}
|
||||
};
|
||||
|
||||
export const listClubParticipations = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId } = req.params;
|
||||
await checkAccess(userToken, clubId);
|
||||
const tournaments = await OfficialTournament.findAll({ where: { clubId } });
|
||||
if (!tournaments || tournaments.length === 0) return res.status(200).json([]);
|
||||
const tournamentIds = tournaments.map(t => t.id);
|
||||
|
||||
const rows = await OfficialCompetitionMember.findAll({
|
||||
where: { tournamentId: { [Op.in]: tournamentIds }, participated: true },
|
||||
include: [
|
||||
{ model: OfficialCompetition, as: 'competition', attributes: ['id', 'tournamentId', 'ageClassCompetition', 'startTime'] },
|
||||
{ model: OfficialTournament, as: 'tournament', attributes: ['id', 'title', 'eventDate'] },
|
||||
{ model: Member, as: 'member', attributes: ['id', 'firstName', 'lastName'] },
|
||||
]
|
||||
});
|
||||
|
||||
const parseDmy = (s) => {
|
||||
if (!s) return null;
|
||||
const m = String(s).match(/(\d{1,2})\.(\d{1,2})\.(\d{4})/);
|
||||
if (!m) return null;
|
||||
const d = new Date(Number(m[3]), Number(m[2]) - 1, Number(m[1]));
|
||||
return isNaN(d.getTime()) ? null : d;
|
||||
};
|
||||
const fmtDmy = (d) => {
|
||||
const dd = String(d.getDate()).padStart(2, '0');
|
||||
const mm = String(d.getMonth() + 1).padStart(2, '0');
|
||||
const yyyy = d.getFullYear();
|
||||
return `${dd}.${mm}.${yyyy}`;
|
||||
};
|
||||
|
||||
const byTournament = new Map();
|
||||
for (const r of rows) {
|
||||
const t = r.tournament;
|
||||
const c = r.competition;
|
||||
const m = r.member;
|
||||
if (!t || !c || !m) continue;
|
||||
if (!byTournament.has(t.id)) {
|
||||
byTournament.set(t.id, {
|
||||
tournamentId: String(t.id),
|
||||
title: t.title || null,
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
entries: [],
|
||||
_dates: [],
|
||||
_eventDate: t.eventDate || null,
|
||||
});
|
||||
}
|
||||
const bucket = byTournament.get(t.id);
|
||||
const compDate = parseDmy(c.startTime || '') || null;
|
||||
if (compDate) bucket._dates.push(compDate);
|
||||
bucket.entries.push({
|
||||
memberId: m.id,
|
||||
memberName: `${m.firstName || ''} ${m.lastName || ''}`.trim(),
|
||||
competitionId: c.id,
|
||||
competitionName: c.ageClassCompetition || '',
|
||||
placement: r.placement || null,
|
||||
date: compDate ? fmtDmy(compDate) : null,
|
||||
});
|
||||
}
|
||||
|
||||
const out = [];
|
||||
for (const t of tournaments) {
|
||||
const bucket = byTournament.get(t.id) || {
|
||||
tournamentId: String(t.id),
|
||||
title: t.title || null,
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
entries: [],
|
||||
_dates: [],
|
||||
_eventDate: t.eventDate || null,
|
||||
};
|
||||
// Ableiten Start/Ende
|
||||
if (bucket._dates.length) {
|
||||
bucket._dates.sort((a, b) => a - b);
|
||||
bucket.startDate = fmtDmy(bucket._dates[0]);
|
||||
bucket.endDate = fmtDmy(bucket._dates[bucket._dates.length - 1]);
|
||||
} else if (bucket._eventDate) {
|
||||
const all = String(bucket._eventDate).match(/(\d{1,2}\.\d{1,2}\.\d{4})/g) || [];
|
||||
if (all.length >= 1) {
|
||||
const d1 = parseDmy(all[0]);
|
||||
const d2 = all.length >= 2 ? parseDmy(all[1]) : d1;
|
||||
if (d1) bucket.startDate = fmtDmy(d1);
|
||||
if (d2) bucket.endDate = fmtDmy(d2);
|
||||
}
|
||||
}
|
||||
// Sort entries: Mitglied, dann Konkurrenz
|
||||
bucket.entries.sort((a, b) => {
|
||||
const mcmp = (a.memberName || '').localeCompare(b.memberName || '', 'de', { sensitivity: 'base' });
|
||||
if (mcmp !== 0) return mcmp;
|
||||
return (a.competitionName || '').localeCompare(b.competitionName || '', 'de', { sensitivity: 'base' });
|
||||
});
|
||||
delete bucket._dates;
|
||||
delete bucket._eventDate;
|
||||
out.push(bucket);
|
||||
}
|
||||
|
||||
res.status(200).json(out);
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: 'Failed to list club participations' });
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteOfficialTournament = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId, id } = req.params;
|
||||
await checkAccess(userToken, clubId);
|
||||
const t = await OfficialTournament.findOne({ where: { id, clubId } });
|
||||
if (!t) return res.status(404).json({ error: 'not found' });
|
||||
await OfficialCompetition.destroy({ where: { tournamentId: id } });
|
||||
await OfficialTournament.destroy({ where: { id } });
|
||||
res.status(204).send();
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: 'Failed to delete tournament' });
|
||||
}
|
||||
};
|
||||
|
||||
function parseTournamentText(text) {
|
||||
const lines = text.split(/\r?\n/);
|
||||
const normLines = lines.map(l => l.replace(/\s+/g, ' ').trim());
|
||||
|
||||
const findTitle = () => {
|
||||
const idx = normLines.findIndex(l => /Kreiseinzelmeisterschaften/i.test(l));
|
||||
return idx >= 0 ? normLines[idx] : null;
|
||||
};
|
||||
|
||||
// Neue Funktion: Teilnahmegebühren pro Spielklasse extrahieren
|
||||
const extractEntryFees = () => {
|
||||
const entryFees = {};
|
||||
|
||||
// Verschiedene Patterns für Teilnahmegebühren suchen
|
||||
const feePatterns = [
|
||||
// Pattern 1: "Startgeld: U12: 5€, U14: 7€, U16: 10€"
|
||||
/startgeld\s*:?\s*(.+)/i,
|
||||
// Pattern 2: "Teilnahmegebühr: U12: 5€, U14: 7€"
|
||||
/teilnahmegebühr\s*:?\s*(.+)/i,
|
||||
// Pattern 3: "Gebühr: U12: 5€, U14: 7€"
|
||||
/gebühr\s*:?\s*(.+)/i,
|
||||
// Pattern 4: "Einschreibegebühr: U12: 5€, U14: 7€"
|
||||
/einschreibegebühr\s*:?\s*(.+)/i,
|
||||
// Pattern 5: "Anmeldegebühr: U12: 5€, U14: 7€"
|
||||
/anmeldegebühr\s*:?\s*(.+)/i
|
||||
];
|
||||
|
||||
for (const pattern of feePatterns) {
|
||||
for (let i = 0; i < normLines.length; i++) {
|
||||
const line = normLines[i];
|
||||
const match = line.match(pattern);
|
||||
if (match) {
|
||||
const feeText = match[1];
|
||||
|
||||
// Extrahiere Gebühren aus dem Text
|
||||
// Unterstützt verschiedene Formate:
|
||||
// "U12: 5€, U14: 7€, U16: 10€"
|
||||
// "U12: 5 Euro, U14: 7 Euro"
|
||||
// "U12 5€, U14 7€"
|
||||
// "U12: 5,00€, U14: 7,00€"
|
||||
const feeMatches = feeText.matchAll(/(U\d+|AK\s*\d+)\s*:?\s*(\d+(?:[,.]\d+)?)\s*(?:€|Euro|EUR)?/gi);
|
||||
|
||||
for (const feeMatch of feeMatches) {
|
||||
const ageClass = feeMatch[1].toUpperCase().replace(/\s+/g, '');
|
||||
const amount = feeMatch[2].replace(',', '.');
|
||||
const numericAmount = parseFloat(amount);
|
||||
|
||||
if (!isNaN(numericAmount)) {
|
||||
entryFees[ageClass] = {
|
||||
amount: numericAmount,
|
||||
currency: '€',
|
||||
rawText: feeMatch[0]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Wenn wir Gebühren gefunden haben, brechen wir ab
|
||||
if (Object.keys(entryFees).length > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Object.keys(entryFees).length > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return entryFees;
|
||||
};
|
||||
|
||||
const extractBlockAfter = (labels, multiline = false) => {
|
||||
const idx = normLines.findIndex(l => labels.some(lb => l.toLowerCase().startsWith(lb)));
|
||||
if (idx === -1) return multiline ? [] : null;
|
||||
const line = normLines[idx];
|
||||
const afterColon = line.includes(':') ? line.split(':').slice(1).join(':').trim() : '';
|
||||
if (!multiline) {
|
||||
if (afterColon) return afterColon;
|
||||
// sonst nächste nicht-leere Zeile
|
||||
for (let i = idx + 1; i < normLines.length; i++) {
|
||||
if (normLines[i]) return normLines[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// multiline bis zur nächsten Leerzeile oder nächsten bekannten Section
|
||||
const out = [];
|
||||
if (afterColon) out.push(afterColon);
|
||||
for (let i = idx + 1; i < normLines.length; i++) {
|
||||
const ln = normLines[i];
|
||||
if (!ln) break;
|
||||
if (/^(termin|austragungsort|austragungsorte|konkurrenz|konkurrenzen|konkurrenztypen|meldeschluss|altersklassen|startzeiten)/i.test(ln)) break;
|
||||
out.push(ln);
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
const extractAllMatches = (regex) => {
|
||||
const results = [];
|
||||
for (const l of normLines) {
|
||||
const m = l.match(regex);
|
||||
if (m) results.push(m);
|
||||
}
|
||||
return results;
|
||||
};
|
||||
|
||||
const title = findTitle();
|
||||
const termin = extractBlockAfter(['termin', 'termin '], false);
|
||||
const austragungsorte = extractBlockAfter(['austragungsort', 'austragungsorte'], true);
|
||||
let konkurrenzRaw = extractBlockAfter(['konkurrenz', 'konkurrenzen', 'konkurrenztypen'], true);
|
||||
if (konkurrenzRaw && !Array.isArray(konkurrenzRaw)) konkurrenzRaw = [konkurrenzRaw];
|
||||
const konkurrenztypen = (konkurrenzRaw || []).flatMap(l => l.split(/[;,]/)).map(s => s.trim()).filter(Boolean);
|
||||
|
||||
// Meldeschlüsse mit Position und Zuordnung zu AK ermitteln
|
||||
const meldeschluesseRaw = [];
|
||||
for (let i = 0; i < normLines.length; i++) {
|
||||
const l = normLines[i];
|
||||
const m = l.match(/meldeschluss\s*:?\s*(.+)$/i);
|
||||
if (m) meldeschluesseRaw.push({ line: i, value: m[1].trim() });
|
||||
}
|
||||
|
||||
let altersRaw = extractBlockAfter(['altersklassen', 'altersklasse'], true);
|
||||
if (altersRaw && !Array.isArray(altersRaw)) altersRaw = [altersRaw];
|
||||
const altersklassen = (altersRaw || []).flatMap(l => l.split(/[;,]/)).map(s => s.trim()).filter(Boolean);
|
||||
|
||||
// Wettbewerbe/Konkurrenzen parsen (Block ab "3. Konkurrenzen")
|
||||
const competitions = [];
|
||||
const konkIdx = normLines.findIndex(l => /^\s*3\.?\s+Konkurrenzen/i.test(l) || /^Konkurrenzen\b/i.test(l));
|
||||
// Bestimme Start-Sektionsnummer (z. B. 3 bei "3. Konkurrenzen"), fallback 3
|
||||
const startSectionNum = (() => {
|
||||
if (konkIdx === -1) return 3;
|
||||
const m = normLines[konkIdx].match(/^\s*(\d+)\./);
|
||||
return m ? parseInt(m[1], 10) : 3;
|
||||
})();
|
||||
const nextSectionIdx = () => {
|
||||
for (let i = konkIdx + 1; i < normLines.length; i++) {
|
||||
const m = normLines[i].match(/^\s*(\d+)\.\s+/);
|
||||
if (m) {
|
||||
const num = parseInt(m[1], 10);
|
||||
if (!Number.isNaN(num) && num > startSectionNum) return i;
|
||||
}
|
||||
// Hinweis: Seitenfußzeilen wie "nu.Dokument ..." ignorieren wir, damit mehrseitige Blöcke nicht abbrechen
|
||||
}
|
||||
return normLines.length;
|
||||
};
|
||||
if (konkIdx !== -1) {
|
||||
const endIdx = nextSectionIdx();
|
||||
let i = konkIdx + 1;
|
||||
while (i < endIdx) {
|
||||
const line = normLines[i];
|
||||
if (/^Altersklasse\/Wettbewerb\s*:/i.test(line)) {
|
||||
const comp = {};
|
||||
comp.altersklasseWettbewerb = line.split(':').slice(1).join(':').trim();
|
||||
i++;
|
||||
while (i < endIdx && !/^Altersklasse\/Wettbewerb\s*:/i.test(normLines[i])) {
|
||||
const ln = normLines[i];
|
||||
const m = ln.match(/^([^:]+):\s*(.*)$/);
|
||||
if (m) {
|
||||
const key = m[1].trim().toLowerCase();
|
||||
const val = m[2].trim();
|
||||
if (key.startsWith('leistungsklasse')) comp.leistungsklasse = val;
|
||||
else if (key === 'startzeit') {
|
||||
// Erwartet: 20.09.2025 13:30 Uhr -> wir extrahieren Datum+Zeit
|
||||
const sm = val.match(/(\d{2}\.\d{2}\.\d{4})\s+(\d{1,2}:\d{2})/);
|
||||
comp.startzeit = sm ? `${sm[1]} ${sm[2]}` : val;
|
||||
}
|
||||
else if (key.startsWith('meldeschluss datum')) comp.meldeschlussDatum = val;
|
||||
else if (key.startsWith('meldeschluss online')) comp.meldeschlussOnline = val;
|
||||
else if (key === 'stichtag') comp.stichtag = val;
|
||||
else if (key === 'ttr-relevant') comp.ttrRelevant = val;
|
||||
else if (key === 'offen für') comp.offenFuer = val;
|
||||
else if (key.startsWith('austragungssys. vorrunde')) comp.vorrunde = val;
|
||||
else if (key.startsWith('austragungssys. endrunde')) comp.endrunde = val;
|
||||
else if (key.startsWith('max. teilnehmerzahl')) comp.maxTeilnehmer = val;
|
||||
else if (key === 'startgeld') {
|
||||
comp.startgeld = val;
|
||||
// Versuche auch spezifische Gebühren für diese Altersklasse zu extrahieren
|
||||
const ageClassMatch = comp.altersklasseWettbewerb?.match(/(U\d+|AK\s*\d+)/i);
|
||||
if (ageClassMatch) {
|
||||
const ageClass = ageClassMatch[1].toUpperCase().replace(/\s+/g, '');
|
||||
const feeMatch = val.match(/(\d+(?:[,.]\d+)?)\s*(?:€|Euro|EUR)?/);
|
||||
if (feeMatch) {
|
||||
const amount = feeMatch[1].replace(',', '.');
|
||||
const numericAmount = parseFloat(amount);
|
||||
if (!isNaN(numericAmount)) {
|
||||
comp.entryFeeDetails = {
|
||||
amount: numericAmount,
|
||||
currency: '€',
|
||||
ageClass: ageClass
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
competitions.push(comp);
|
||||
continue; // schon auf nächster Zeile
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// Altersklassen-Positionen im Text (zur Zuordnung von Meldeschlüssen)
|
||||
const akPositions = [];
|
||||
for (let i = 0; i < normLines.length; i++) {
|
||||
const l = normLines[i];
|
||||
const m = l.match(/\b(U\d+|AK\s*\d+)\b/i);
|
||||
if (m) akPositions.push({ line: i, ak: m[1].toUpperCase().replace(/\s+/g, '') });
|
||||
}
|
||||
|
||||
const meldeschluesseByAk = {};
|
||||
for (const ms of meldeschluesseRaw) {
|
||||
// Nächste AK im Umkreis von 3 Zeilen suchen
|
||||
let best = null;
|
||||
let bestDist = Infinity;
|
||||
for (const ak of akPositions) {
|
||||
const dist = Math.abs(ak.line - ms.line);
|
||||
if (dist < bestDist && dist <= 3) { best = ak; bestDist = dist; }
|
||||
}
|
||||
if (best) {
|
||||
if (!meldeschluesseByAk[best.ak]) meldeschluesseByAk[best.ak] = new Set();
|
||||
meldeschluesseByAk[best.ak].add(ms.value);
|
||||
}
|
||||
}
|
||||
|
||||
// Dedup global
|
||||
const meldeschluesse = Array.from(new Set(meldeschluesseRaw.map(x => x.value)));
|
||||
// Sets zu Arrays
|
||||
const meldeschluesseByAkOut = Object.fromEntries(Object.entries(meldeschluesseByAk).map(([k,v]) => [k, Array.from(v)]));
|
||||
|
||||
// Vorhandene einfache Personenerkennung (optional, zu Analysezwecken)
|
||||
const entries = [];
|
||||
for (const l of normLines) {
|
||||
const m = l.match(/^([A-Za-zÄÖÜäöüß\-\s']{3,})(?:\s+\((m|w|d)\))?$/i);
|
||||
if (m && /\s/.test(m[1])) {
|
||||
entries.push({ name: m[1].trim(), genderHint: m[2] || null });
|
||||
}
|
||||
}
|
||||
|
||||
// Extrahiere Teilnahmegebühren
|
||||
const entryFees = extractEntryFees();
|
||||
|
||||
return {
|
||||
title,
|
||||
termin,
|
||||
austragungsorte,
|
||||
konkurrenztypen,
|
||||
meldeschluesse,
|
||||
meldeschluesseByAk: meldeschluesseByAkOut,
|
||||
altersklassen,
|
||||
startzeiten: {},
|
||||
competitions,
|
||||
entries,
|
||||
entryFees, // Neue: Teilnahmegebühren pro Spielklasse
|
||||
debug: { normLines },
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,53 @@
|
||||
import Participant from '../models/Participant.js';
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
export const getParticipants = async (req, res) => {
|
||||
try {
|
||||
const { dateId } = req.params;
|
||||
const participants = await Participant.findAll({ where: { diaryDateId: dateId } });
|
||||
const participants = await Participant.findAll({
|
||||
where: { diaryDateId: dateId },
|
||||
attributes: ['id', 'diaryDateId', 'memberId', 'groupId', 'notes', 'createdAt', 'updatedAt']
|
||||
});
|
||||
res.status(200).json(participants);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
devLog(error);
|
||||
res.status(500).json({ error: 'Fehler beim Abrufen der Teilnehmer' });
|
||||
}
|
||||
};
|
||||
|
||||
export const updateParticipantGroup = async (req, res) => {
|
||||
try {
|
||||
const { dateId, memberId } = req.params;
|
||||
const { groupId } = req.body;
|
||||
|
||||
const participant = await Participant.findOne({
|
||||
where: {
|
||||
diaryDateId: dateId,
|
||||
memberId: memberId
|
||||
}
|
||||
});
|
||||
|
||||
if (!participant) {
|
||||
return res.status(404).json({ error: 'Teilnehmer nicht gefunden' });
|
||||
}
|
||||
|
||||
participant.groupId = groupId || null;
|
||||
await participant.save();
|
||||
|
||||
res.status(200).json(participant);
|
||||
} catch (error) {
|
||||
devLog(error);
|
||||
res.status(500).json({ error: 'Fehler beim Aktualisieren der Teilnehmer-Gruppenzuordnung' });
|
||||
}
|
||||
};
|
||||
|
||||
export const addParticipant = async (req, res) => {
|
||||
try {
|
||||
const { diaryDateId, memberId } = req.body;
|
||||
const participant = await Participant.create({ diaryDateId, memberId });
|
||||
res.status(201).json(participant);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
devLog(error);
|
||||
res.status(500).json({ error: 'Fehler beim Hinzufügen des Teilnehmers' });
|
||||
}
|
||||
};
|
||||
@@ -28,7 +58,7 @@ export const removeParticipant = async (req, res) => {
|
||||
await Participant.destroy({ where: { diaryDateId, memberId } });
|
||||
res.status(200).json({ message: 'Teilnehmer entfernt' });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
devLog(error);
|
||||
res.status(500).json({ error: 'Fehler beim Entfernen des Teilnehmers' });
|
||||
}
|
||||
};
|
||||
|
||||
167
backend/controllers/permissionController.js
Normal file
167
backend/controllers/permissionController.js
Normal file
@@ -0,0 +1,167 @@
|
||||
import permissionService from '../services/permissionService.js';
|
||||
|
||||
/**
|
||||
* Get user's permissions for a club
|
||||
*/
|
||||
export const getUserPermissions = async (req, res) => {
|
||||
try {
|
||||
const { clubId } = req.params;
|
||||
const userId = req.user.id;
|
||||
|
||||
// Validierung: clubId muss eine gültige Zahl sein
|
||||
const parsedClubId = parseInt(clubId, 10);
|
||||
if (isNaN(parsedClubId) || parsedClubId <= 0) {
|
||||
return res.status(400).json({ error: 'Ungültige Club-ID' });
|
||||
}
|
||||
|
||||
const permissions = await permissionService.getUserClubPermissions(userId, parsedClubId);
|
||||
|
||||
if (!permissions) {
|
||||
return res.status(404).json({ error: 'Keine Berechtigungen gefunden' });
|
||||
}
|
||||
|
||||
res.json(permissions);
|
||||
} catch (error) {
|
||||
console.error('Error getting user permissions:', error);
|
||||
res.status(500).json({ error: 'Fehler beim Abrufen der Berechtigungen' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all club members with their permissions
|
||||
*/
|
||||
export const getClubMembersWithPermissions = async (req, res) => {
|
||||
try {
|
||||
const { clubId } = req.params;
|
||||
const userId = req.user.id;
|
||||
|
||||
const members = await permissionService.getClubMembersWithPermissions(
|
||||
parseInt(clubId),
|
||||
userId
|
||||
);
|
||||
|
||||
res.json(members);
|
||||
} catch (error) {
|
||||
console.error('Error getting club members with permissions:', error);
|
||||
if (error.message === 'Keine Berechtigung zum Anzeigen von Berechtigungen') {
|
||||
return res.status(403).json({ error: error.message });
|
||||
}
|
||||
res.status(500).json({ error: 'Fehler beim Abrufen der Mitglieder' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update user role
|
||||
*/
|
||||
export const updateUserRole = async (req, res) => {
|
||||
try {
|
||||
const { clubId, userId: targetUserId } = req.params;
|
||||
const { role } = req.body;
|
||||
const updatingUserId = req.user.id;
|
||||
|
||||
const result = await permissionService.setUserRole(
|
||||
parseInt(targetUserId),
|
||||
parseInt(clubId),
|
||||
role,
|
||||
updatingUserId
|
||||
);
|
||||
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
console.error('Error updating user role:', error);
|
||||
if (error.message && error.message.toLowerCase().includes('keine berechtigung')) {
|
||||
return res.status(403).json({ error: error.message });
|
||||
}
|
||||
res.status(400).json({ error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update user custom permissions
|
||||
*/
|
||||
export const updateUserPermissions = async (req, res) => {
|
||||
try {
|
||||
const { clubId, userId: targetUserId } = req.params;
|
||||
const { permissions } = req.body;
|
||||
const updatingUserId = req.user.id;
|
||||
|
||||
const result = await permissionService.setCustomPermissions(
|
||||
parseInt(targetUserId),
|
||||
parseInt(clubId),
|
||||
permissions,
|
||||
updatingUserId
|
||||
);
|
||||
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
console.error('Error updating user permissions:', error);
|
||||
if (error.message && error.message.toLowerCase().includes('keine berechtigung')) {
|
||||
return res.status(403).json({ error: error.message });
|
||||
}
|
||||
res.status(400).json({ error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get available roles
|
||||
*/
|
||||
export const getAvailableRoles = async (req, res) => {
|
||||
try {
|
||||
const roles = permissionService.getAvailableRoles();
|
||||
res.json(roles);
|
||||
} catch (error) {
|
||||
console.error('Error getting available roles:', error);
|
||||
res.status(500).json({ error: 'Fehler beim Abrufen der Rollen' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get permission structure
|
||||
*/
|
||||
export const getPermissionStructure = async (req, res) => {
|
||||
try {
|
||||
const structure = permissionService.getPermissionStructure();
|
||||
res.json(structure);
|
||||
} catch (error) {
|
||||
console.error('Error getting permission structure:', error);
|
||||
res.status(500).json({ error: 'Fehler beim Abrufen der Berechtigungsstruktur' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update user status (activate/deactivate)
|
||||
*/
|
||||
export const updateUserStatus = async (req, res) => {
|
||||
try {
|
||||
const { clubId, userId: targetUserId } = req.params;
|
||||
const { approved } = req.body;
|
||||
const updatingUserId = req.user.id;
|
||||
|
||||
const result = await permissionService.setUserStatus(
|
||||
parseInt(targetUserId),
|
||||
parseInt(clubId),
|
||||
approved,
|
||||
updatingUserId
|
||||
);
|
||||
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
console.error('Error updating user status:', error);
|
||||
if (error.message && error.message.toLowerCase().includes('keine berechtigung')) {
|
||||
return res.status(403).json({ error: error.message });
|
||||
}
|
||||
res.status(400).json({ error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
getUserPermissions,
|
||||
getClubMembersWithPermissions,
|
||||
updateUserRole,
|
||||
updateUserPermissions,
|
||||
updateUserStatus,
|
||||
getAvailableRoles,
|
||||
getPermissionStructure
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import predefinedActivityService from '../services/predefinedActivityService.js';
|
||||
import PredefinedActivityImage from '../models/PredefinedActivityImage.js';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
export const createPredefinedActivity = async (req, res) => {
|
||||
try {
|
||||
const { name, description, durationText, duration } = req.body;
|
||||
const predefinedActivity = await predefinedActivityService.createPredefinedActivity({ name, description, durationText, duration });
|
||||
const { name, code, description, durationText, duration, imageLink, drawingData } = req.body;
|
||||
const predefinedActivity = await predefinedActivityService.createPredefinedActivity({ name, code, description, durationText, duration, imageLink, drawingData });
|
||||
res.status(201).json(predefinedActivity);
|
||||
} catch (error) {
|
||||
console.error('[createPredefinedActivity] - Error:', error);
|
||||
@@ -25,10 +28,11 @@ export const getPredefinedActivityById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const predefinedActivity = await predefinedActivityService.getPredefinedActivityById(id);
|
||||
const images = await PredefinedActivityImage.findAll({ where: { predefinedActivityId: id } });
|
||||
if (!predefinedActivity) {
|
||||
return res.status(404).json({ error: 'Predefined activity not found' });
|
||||
}
|
||||
res.status(200).json(predefinedActivity);
|
||||
res.status(200).json({ ...predefinedActivity.toJSON(), images });
|
||||
} catch (error) {
|
||||
console.error('[getPredefinedActivityById] - Error:', error);
|
||||
res.status(500).json({ error: 'Error fetching predefined activity' });
|
||||
@@ -38,11 +42,43 @@ export const getPredefinedActivityById = async (req, res) => {
|
||||
export const updatePredefinedActivity = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { name, description, durationText, duration } = req.body;
|
||||
const updatedActivity = await predefinedActivityService.updatePredefinedActivity(id, { name, description, durationText, duration });
|
||||
const { name, code, description, durationText, duration, imageLink, drawingData } = req.body;
|
||||
const updatedActivity = await predefinedActivityService.updatePredefinedActivity(id, { name, code, description, durationText, duration, imageLink, drawingData });
|
||||
res.status(200).json(updatedActivity);
|
||||
} catch (error) {
|
||||
console.error('[updatePredefinedActivity] - Error:', error);
|
||||
res.status(500).json({ error: 'Error updating predefined activity' });
|
||||
}
|
||||
};
|
||||
|
||||
export const searchPredefinedActivities = async (req, res) => {
|
||||
try {
|
||||
const { q, limit } = req.query;
|
||||
const result = await predefinedActivityService.searchPredefinedActivities(q, limit);
|
||||
res.status(200).json(result);
|
||||
} catch (error) {
|
||||
console.error('[searchPredefinedActivities] - Error:', error);
|
||||
res.status(500).json({ error: 'Error searching predefined activities' });
|
||||
}
|
||||
};
|
||||
|
||||
export const mergePredefinedActivities = async (req, res) => {
|
||||
try {
|
||||
const { sourceId, targetId } = req.body;
|
||||
await predefinedActivityService.mergeActivities(sourceId, targetId);
|
||||
res.status(200).json({ ok: true });
|
||||
} catch (error) {
|
||||
console.error('[mergePredefinedActivities] - Error:', error);
|
||||
res.status(500).json({ error: 'Error merging predefined activities' });
|
||||
}
|
||||
};
|
||||
|
||||
export const deduplicatePredefinedActivities = async (req, res) => {
|
||||
try {
|
||||
const result = await predefinedActivityService.deduplicateActivities();
|
||||
res.status(200).json(result);
|
||||
} catch (error) {
|
||||
console.error('[deduplicatePredefinedActivities] - Error:', error);
|
||||
res.status(500).json({ error: 'Error deduplicating predefined activities' });
|
||||
}
|
||||
};
|
||||
|
||||
97
backend/controllers/predefinedActivityImageController.js
Normal file
97
backend/controllers/predefinedActivityImageController.js
Normal file
@@ -0,0 +1,97 @@
|
||||
import PredefinedActivity from '../models/PredefinedActivity.js';
|
||||
import PredefinedActivityImage from '../models/PredefinedActivityImage.js';
|
||||
import { checkGlobalAccess } from '../utils/userUtils.js';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import sharp from 'sharp';
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
export const uploadPredefinedActivityImage = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params; // predefinedActivityId
|
||||
const { authcode: userToken } = req.headers;
|
||||
await checkGlobalAccess(userToken); // Predefined Activities sind global, keine Club-Zugriffskontrolle nötig
|
||||
|
||||
const activity = await PredefinedActivity.findByPk(id);
|
||||
if (!activity) {
|
||||
return res.status(404).json({ error: 'Predefined activity not found' });
|
||||
}
|
||||
|
||||
if (!req.file || !req.file.buffer) {
|
||||
return res.status(400).json({ error: 'No image uploaded' });
|
||||
}
|
||||
|
||||
const imagesDir = path.join('images', 'predefined');
|
||||
if (!fs.existsSync(imagesDir)) {
|
||||
fs.mkdirSync(imagesDir, { recursive: true });
|
||||
}
|
||||
|
||||
const fileName = `${id}-${Date.now()}.jpg`;
|
||||
const filePath = path.join(imagesDir, fileName);
|
||||
|
||||
await sharp(req.file.buffer)
|
||||
.resize(800, 800, { fit: 'inside' })
|
||||
.jpeg({ quality: 85 })
|
||||
.toFile(filePath);
|
||||
|
||||
// Extrahiere Zeichnungsdaten aus dem Request
|
||||
const drawingData = req.body.drawingData ? JSON.parse(req.body.drawingData) : null;
|
||||
|
||||
const imageRecord = await PredefinedActivityImage.create({
|
||||
predefinedActivityId: id,
|
||||
imagePath: filePath,
|
||||
mimeType: 'image/jpeg',
|
||||
drawingData: drawingData ? JSON.stringify(drawingData) : null,
|
||||
});
|
||||
|
||||
// Optional: als imageLink am Activity-Datensatz setzen
|
||||
activity.imageLink = `/api/predefined-activities/${id}/image/${imageRecord.id}`;
|
||||
await activity.save();
|
||||
|
||||
res.status(201).json({ id: imageRecord.id, imageLink: activity.imageLink });
|
||||
} catch (error) {
|
||||
console.error('[uploadPredefinedActivityImage] - Error:', error);
|
||||
res.status(500).json({ error: 'Failed to upload image' });
|
||||
}
|
||||
};
|
||||
|
||||
export const deletePredefinedActivityImage = async (req, res) => {
|
||||
try {
|
||||
const { id, imageId } = req.params; // predefinedActivityId, imageId
|
||||
const { authcode: userToken } = req.headers;
|
||||
await checkGlobalAccess(userToken);
|
||||
|
||||
const activity = await PredefinedActivity.findByPk(id);
|
||||
if (!activity) {
|
||||
return res.status(404).json({ error: 'Predefined activity not found' });
|
||||
}
|
||||
|
||||
const image = await PredefinedActivityImage.findOne({
|
||||
where: { id: imageId, predefinedActivityId: id }
|
||||
});
|
||||
if (!image) {
|
||||
return res.status(404).json({ error: 'Image not found' });
|
||||
}
|
||||
|
||||
// Datei vom Dateisystem löschen
|
||||
if (fs.existsSync(image.imagePath)) {
|
||||
fs.unlinkSync(image.imagePath);
|
||||
}
|
||||
|
||||
// Datensatz aus der Datenbank löschen
|
||||
await image.destroy();
|
||||
|
||||
// Falls das gelöschte Bild der aktuelle imageLink war, diesen zurücksetzen
|
||||
if (activity.imageLink === `/api/predefined-activities/${id}/image/${imageId}`) {
|
||||
activity.imageLink = null;
|
||||
await activity.save();
|
||||
}
|
||||
|
||||
res.status(200).json({ message: 'Image deleted successfully' });
|
||||
} catch (error) {
|
||||
console.error('[deletePredefinedActivityImage] - Error:', error);
|
||||
res.status(500).json({ error: 'Failed to delete image' });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
103
backend/controllers/seasonController.js
Normal file
103
backend/controllers/seasonController.js
Normal file
@@ -0,0 +1,103 @@
|
||||
import SeasonService from '../services/seasonService.js';
|
||||
import { getUserByToken } from '../utils/userUtils.js';
|
||||
import { devLog } from '../utils/logger.js';
|
||||
|
||||
export const getSeasons = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const seasons = await SeasonService.getAllSeasons();
|
||||
|
||||
res.status(200).json(seasons);
|
||||
} catch (error) {
|
||||
console.error('[getSeasons] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const getCurrentSeason = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const season = await SeasonService.getOrCreateCurrentSeason();
|
||||
|
||||
res.status(200).json(season);
|
||||
} catch (error) {
|
||||
console.error('[getCurrentSeason] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const createSeason = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { season } = req.body;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
if (!season) {
|
||||
return res.status(400).json({ error: "missingseason" });
|
||||
}
|
||||
|
||||
// Validiere Saison-Format (z.B. "2023/2024")
|
||||
const seasonRegex = /^\d{4}\/\d{4}$/;
|
||||
if (!seasonRegex.test(season)) {
|
||||
return res.status(400).json({ error: "invalidseasonformat" });
|
||||
}
|
||||
|
||||
const newSeason = await SeasonService.createSeason(season);
|
||||
|
||||
res.status(201).json(newSeason);
|
||||
} catch (error) {
|
||||
console.error('[createSeason] - Error:', error);
|
||||
if (error.message === 'Season already exists') {
|
||||
res.status(409).json({ error: "alreadyexists" });
|
||||
} else {
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const getSeason = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { seasonid: seasonId } = req.params;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const season = await SeasonService.getSeasonById(seasonId);
|
||||
if (!season) {
|
||||
return res.status(404).json({ error: "notfound" });
|
||||
}
|
||||
|
||||
res.status(200).json(season);
|
||||
} catch (error) {
|
||||
console.error('[getSeason] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteSeason = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { seasonid: seasonId } = req.params;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const success = await SeasonService.deleteSeason(seasonId);
|
||||
if (!success) {
|
||||
return res.status(404).json({ error: "notfound" });
|
||||
}
|
||||
|
||||
res.status(200).json({ message: "deleted" });
|
||||
} catch (error) {
|
||||
console.error('[deleteSeason] - Error:', error);
|
||||
if (error.message === 'Season is used by teams' || error.message === 'Season is used by leagues') {
|
||||
res.status(409).json({ error: "seasoninuse" });
|
||||
} else {
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
}
|
||||
};
|
||||
130
backend/controllers/teamController.js
Normal file
130
backend/controllers/teamController.js
Normal file
@@ -0,0 +1,130 @@
|
||||
import TeamService from '../services/teamService.js';
|
||||
import { getUserByToken } from '../utils/userUtils.js';
|
||||
import { devLog } from '../utils/logger.js';
|
||||
|
||||
export const getTeams = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubid: clubId } = req.params;
|
||||
const { seasonid: seasonId } = req.query;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
// Check if user has access to this club
|
||||
const teams = await TeamService.getAllTeamsByClub(clubId, seasonId);
|
||||
|
||||
res.status(200).json(teams);
|
||||
} catch (error) {
|
||||
console.error('[getTeams] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const getTeam = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { teamid: teamId } = req.params;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const team = await TeamService.getTeamById(teamId);
|
||||
if (!team) {
|
||||
return res.status(404).json({ error: "notfound" });
|
||||
}
|
||||
|
||||
res.status(200).json(team);
|
||||
} catch (error) {
|
||||
console.error('[getTeam] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const createTeam = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubid: clubId } = req.params;
|
||||
const { name, leagueId, seasonId } = req.body;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
if (!name) {
|
||||
return res.status(400).json({ error: "missingname" });
|
||||
}
|
||||
|
||||
const teamData = {
|
||||
name,
|
||||
clubId: parseInt(clubId),
|
||||
leagueId: leagueId ? parseInt(leagueId) : null,
|
||||
seasonId: seasonId ? parseInt(seasonId) : null
|
||||
};
|
||||
|
||||
const newTeam = await TeamService.createTeam(teamData);
|
||||
|
||||
res.status(201).json(newTeam);
|
||||
} catch (error) {
|
||||
console.error('[createTeam] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const updateTeam = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { teamid: teamId } = req.params;
|
||||
const { name, leagueId, seasonId } = req.body;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const updateData = {};
|
||||
if (name !== undefined) updateData.name = name;
|
||||
if (leagueId !== undefined) updateData.leagueId = leagueId ? parseInt(leagueId) : null;
|
||||
if (seasonId !== undefined) updateData.seasonId = seasonId ? parseInt(seasonId) : null;
|
||||
|
||||
const success = await TeamService.updateTeam(teamId, updateData);
|
||||
if (!success) {
|
||||
return res.status(404).json({ error: "notfound" });
|
||||
}
|
||||
|
||||
const updatedTeam = await TeamService.getTeamById(teamId);
|
||||
res.status(200).json(updatedTeam);
|
||||
} catch (error) {
|
||||
console.error('[updateTeam] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteTeam = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { teamid: teamId } = req.params;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const success = await TeamService.deleteTeam(teamId);
|
||||
if (!success) {
|
||||
return res.status(404).json({ error: "notfound" });
|
||||
}
|
||||
|
||||
res.status(200).json({ message: "deleted" });
|
||||
} catch (error) {
|
||||
console.error('[deleteTeam] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const getLeagues = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubid: clubId } = req.params;
|
||||
const { seasonid: seasonId } = req.query;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const leagues = await TeamService.getLeaguesByClub(clubId, seasonId);
|
||||
|
||||
res.status(200).json(leagues);
|
||||
} catch (error) {
|
||||
console.error('[getLeagues] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
223
backend/controllers/teamDocumentController.js
Normal file
223
backend/controllers/teamDocumentController.js
Normal file
@@ -0,0 +1,223 @@
|
||||
import multer from 'multer';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import TeamDocumentService from '../services/teamDocumentService.js';
|
||||
import PDFParserService from '../services/pdfParserService.js';
|
||||
import { getUserByToken } from '../utils/userUtils.js';
|
||||
import { devLog } from '../utils/logger.js';
|
||||
|
||||
// Multer-Konfiguration für Datei-Uploads
|
||||
const storage = multer.diskStorage({
|
||||
destination: (req, file, cb) => {
|
||||
try {
|
||||
fs.mkdirSync('uploads/temp', { recursive: true });
|
||||
} catch (mkdirError) {
|
||||
console.error('[multer] - Failed to ensure temp upload directory exists:', mkdirError);
|
||||
}
|
||||
cb(null, 'uploads/temp/');
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
|
||||
cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
|
||||
}
|
||||
});
|
||||
|
||||
const upload = multer({
|
||||
storage: storage,
|
||||
limits: {
|
||||
fileSize: 10 * 1024 * 1024 // 10MB Limit
|
||||
},
|
||||
fileFilter: (req, file, cb) => {
|
||||
// Erlaube nur PDF, DOC, DOCX, TXT, CSV Dateien
|
||||
const allowedExtensions = ['.pdf', '.doc', '.docx', '.txt', '.csv'];
|
||||
const allowedMimePatterns = ['pdf', 'msword', 'wordprocessingml.document', 'text/plain', 'csv', 'excel'];
|
||||
|
||||
const extensionValid = allowedExtensions.includes(path.extname(file.originalname).toLowerCase());
|
||||
const mimetypeValid = allowedMimePatterns.some((pattern) => file.mimetype && file.mimetype.toLowerCase().includes(pattern));
|
||||
|
||||
if (extensionValid && mimetypeValid) {
|
||||
return cb(null, true);
|
||||
}
|
||||
|
||||
cb(new Error('Nur PDF, DOC, DOCX, TXT und CSV Dateien sind erlaubt!'));
|
||||
}
|
||||
});
|
||||
|
||||
export const uploadMiddleware = upload.single('document');
|
||||
|
||||
export const uploadDocument = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubteamid: clubTeamId } = req.params;
|
||||
const { documentType } = req.body;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
if (!req.file) {
|
||||
return res.status(400).json({ error: "nofile" });
|
||||
}
|
||||
|
||||
if (!documentType || !['code_list', 'pin_list'].includes(documentType)) {
|
||||
return res.status(400).json({ error: "invaliddocumenttype" });
|
||||
}
|
||||
|
||||
const document = await TeamDocumentService.uploadDocument(req.file, clubTeamId, documentType);
|
||||
|
||||
res.status(201).json(document);
|
||||
} catch (error) {
|
||||
console.error('[uploadDocument] - Error:', error);
|
||||
|
||||
// Lösche temporäre Datei bei Fehler
|
||||
if (req.file && req.file.path) {
|
||||
try {
|
||||
const fs = await import('fs');
|
||||
fs.unlinkSync(req.file.path);
|
||||
} catch (cleanupError) {
|
||||
console.error('Fehler beim Löschen der temporären Datei:', cleanupError);
|
||||
}
|
||||
}
|
||||
|
||||
if (error.message === 'Club-Team nicht gefunden') {
|
||||
return res.status(404).json({ error: "clubteamnotfound" });
|
||||
}
|
||||
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const getDocuments = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubteamid: clubTeamId } = req.params;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const documents = await TeamDocumentService.getDocumentsByClubTeam(clubTeamId);
|
||||
|
||||
res.status(200).json(documents);
|
||||
} catch (error) {
|
||||
console.error('[getDocuments] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const getDocument = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { documentid: documentId } = req.params;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const document = await TeamDocumentService.getDocumentById(documentId);
|
||||
if (!document) {
|
||||
return res.status(404).json({ error: "notfound" });
|
||||
}
|
||||
|
||||
res.status(200).json(document);
|
||||
} catch (error) {
|
||||
console.error('[getDocument] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const downloadDocument = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { documentid: documentId } = req.params;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const document = await TeamDocumentService.getDocumentById(documentId);
|
||||
if (!document) {
|
||||
return res.status(404).json({ error: "notfound" });
|
||||
}
|
||||
|
||||
const filePath = await TeamDocumentService.getDocumentPath(documentId);
|
||||
if (!filePath) {
|
||||
return res.status(404).json({ error: "filenotfound" });
|
||||
}
|
||||
|
||||
// Prüfe ob Datei existiert
|
||||
const fs = await import('fs');
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return res.status(404).json({ error: "filenotfound" });
|
||||
}
|
||||
|
||||
// Setze Headers für Inline-Anzeige (PDF-Viewer)
|
||||
res.setHeader('Content-Disposition', `inline; filename="${document.originalFileName}"`);
|
||||
res.setHeader('Content-Type', document.mimeType);
|
||||
|
||||
// Sende die Datei
|
||||
res.sendFile(filePath);
|
||||
} catch (error) {
|
||||
console.error('[downloadDocument] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteDocument = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { documentid: documentId } = req.params;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const success = await TeamDocumentService.deleteDocument(documentId);
|
||||
if (!success) {
|
||||
return res.status(404).json({ error: "notfound" });
|
||||
}
|
||||
|
||||
res.status(200).json({ message: "Document deleted successfully" });
|
||||
} catch (error) {
|
||||
console.error('[deleteDocument] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const parsePDF = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { documentid: documentId } = req.params;
|
||||
const { leagueid: leagueId } = req.query;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
if (!leagueId) {
|
||||
return res.status(400).json({ error: "missingleagueid" });
|
||||
}
|
||||
|
||||
// Hole Dokument-Informationen
|
||||
const document = await TeamDocumentService.getDocumentById(documentId);
|
||||
if (!document) {
|
||||
return res.status(404).json({ error: "documentnotfound" });
|
||||
}
|
||||
|
||||
// Prüfe ob es eine PDF- oder TXT-Datei ist
|
||||
if (!document.mimeType.includes('pdf') && !document.mimeType.includes('text/plain')) {
|
||||
return res.status(400).json({ error: "notapdfortxt" });
|
||||
}
|
||||
|
||||
// Parse PDF
|
||||
const parseResult = await PDFParserService.parsePDF(document.filePath, document.clubTeam.clubId);
|
||||
|
||||
// Speichere Matches in Datenbank
|
||||
const saveResult = await PDFParserService.saveMatchesToDatabase(parseResult.matches, parseInt(leagueId));
|
||||
|
||||
res.status(200).json({
|
||||
parseResult: {
|
||||
matchesFound: parseResult.matches.length,
|
||||
debugInfo: parseResult.debugInfo,
|
||||
allLines: parseResult.allLines,
|
||||
rawText: parseResult.rawText
|
||||
},
|
||||
saveResult: {
|
||||
created: saveResult.created,
|
||||
updated: saveResult.updated,
|
||||
errors: saveResult.errors
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[parsePDF] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
@@ -11,10 +11,30 @@ class TrainingStatsController {
|
||||
const twelveMonthsAgo = new Date(now.getFullYear() - 1, now.getMonth(), now.getDate());
|
||||
const threeMonthsAgo = new Date(now.getFullYear(), now.getMonth() - 3, now.getDate());
|
||||
|
||||
// Alle aktiven Mitglieder des Vereins laden
|
||||
// Alle aktiven Mitglieder des spezifischen Vereins laden
|
||||
const members = await Member.findAll({
|
||||
where: {
|
||||
active: true
|
||||
active: true,
|
||||
clubId: parseInt(clubId)
|
||||
}
|
||||
});
|
||||
|
||||
// Anzahl der Trainings im jeweiligen Zeitraum berechnen
|
||||
const trainingsCount12Months = await DiaryDate.count({
|
||||
where: {
|
||||
clubId: parseInt(clubId),
|
||||
date: {
|
||||
[Op.gte]: twelveMonthsAgo
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const trainingsCount3Months = await DiaryDate.count({
|
||||
where: {
|
||||
clubId: parseInt(clubId),
|
||||
date: {
|
||||
[Op.gte]: threeMonthsAgo
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -94,6 +114,10 @@ class TrainingStatsController {
|
||||
endTime: '--:--'
|
||||
}));
|
||||
|
||||
// Letztes Training
|
||||
const lastTrainingDate = trainingDetails.length ? trainingDetails[0].diaryDate.date : null;
|
||||
const lastTrainingTs = lastTrainingDate ? new Date(lastTrainingDate).getTime() : 0;
|
||||
|
||||
stats.push({
|
||||
id: member.id,
|
||||
firstName: member.firstName,
|
||||
@@ -102,6 +126,8 @@ class TrainingStatsController {
|
||||
participation12Months,
|
||||
participation3Months,
|
||||
participationTotal,
|
||||
lastTraining: lastTrainingDate,
|
||||
lastTrainingTs,
|
||||
trainingDetails: formattedTrainingDetails
|
||||
});
|
||||
}
|
||||
@@ -109,7 +135,36 @@ class TrainingStatsController {
|
||||
// Nach Gesamtteilnahme absteigend sortieren
|
||||
stats.sort((a, b) => b.participationTotal - a.participationTotal);
|
||||
|
||||
res.json(stats);
|
||||
// Trainingstage mit Teilnehmerzahlen abrufen (letzte 12 Monate, absteigend sortiert)
|
||||
const trainingDays = await DiaryDate.findAll({
|
||||
where: {
|
||||
clubId: parseInt(clubId),
|
||||
date: {
|
||||
[Op.gte]: twelveMonthsAgo
|
||||
}
|
||||
},
|
||||
include: [{
|
||||
model: Participant,
|
||||
as: 'participantList',
|
||||
attributes: ['id']
|
||||
}],
|
||||
order: [['date', 'DESC']]
|
||||
});
|
||||
|
||||
// Formatiere Trainingstage mit Teilnehmerzahl
|
||||
const formattedTrainingDays = trainingDays.map(day => ({
|
||||
id: day.id,
|
||||
date: day.date,
|
||||
participantCount: day.participantList ? day.participantList.length : 0
|
||||
}));
|
||||
|
||||
// Zusätzliche Metadaten mit Trainingsanzahl zurückgeben
|
||||
res.json({
|
||||
members: stats,
|
||||
trainingsCount12Months,
|
||||
trainingsCount3Months,
|
||||
trainingDays: formattedTrainingDays
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Trainings-Statistik:', error);
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import { Sequelize } from 'sequelize';
|
||||
import { development } from './config.js';
|
||||
|
||||
|
||||
const sequelize = new Sequelize(
|
||||
development.database,
|
||||
development.database,
|
||||
development.username,
|
||||
development.password,
|
||||
{
|
||||
host: development.host,
|
||||
dialect: development.dialect,
|
||||
define: development.define,
|
||||
logging: development.logging ?? false,
|
||||
storage: development.storage,
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -6,6 +6,9 @@ export const authenticate = async (req, res, next) => {
|
||||
if (!token) {
|
||||
token = req.headers['authcode'];
|
||||
}
|
||||
if (!token) {
|
||||
token = req.query?.authcode || req.query?.token || null;
|
||||
}
|
||||
if (!token) {
|
||||
return res.status(401).json({ error: 'Unauthorized: Token fehlt' });
|
||||
}
|
||||
|
||||
215
backend/middleware/authorizationMiddleware.js
Normal file
215
backend/middleware/authorizationMiddleware.js
Normal file
@@ -0,0 +1,215 @@
|
||||
import permissionService from '../services/permissionService.js';
|
||||
|
||||
/**
|
||||
* Authorization Middleware
|
||||
* Checks if user has permission to access a resource
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check if user has permission for a specific resource and action
|
||||
* @param {string} resource - Resource name (diary, members, teams, etc.)
|
||||
* @param {string} action - Action type (read, write, delete)
|
||||
* @returns {Function} Express middleware function
|
||||
*/
|
||||
export const authorize = (resource, action = 'read') => {
|
||||
return async (req, res, next) => {
|
||||
try {
|
||||
const userId = req.user?.id;
|
||||
|
||||
if (!userId) {
|
||||
return res.status(401).json({ error: 'Nicht authentifiziert' });
|
||||
}
|
||||
|
||||
// Get clubId from various possible sources
|
||||
const clubId =
|
||||
req.params.clubId ??
|
||||
req.params.clubid ??
|
||||
req.params.id ??
|
||||
req.body.clubId ??
|
||||
req.body.clubid ??
|
||||
req.query.clubId ??
|
||||
req.query.clubid;
|
||||
|
||||
if (!clubId) {
|
||||
return res.status(400).json({ error: 'Club-ID fehlt' });
|
||||
}
|
||||
|
||||
// Check permission
|
||||
const hasPermission = await permissionService.hasPermission(
|
||||
userId,
|
||||
parseInt(clubId),
|
||||
resource,
|
||||
action
|
||||
);
|
||||
|
||||
if (!hasPermission) {
|
||||
return res.status(403).json({
|
||||
error: 'Keine Berechtigung',
|
||||
details: `Fehlende Berechtigung: ${resource}.${action}`
|
||||
});
|
||||
}
|
||||
|
||||
// Store permissions in request for later use
|
||||
const userPermissions = await permissionService.getUserClubPermissions(
|
||||
userId,
|
||||
parseInt(clubId)
|
||||
);
|
||||
req.userPermissions = userPermissions;
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error('Authorization error:', error);
|
||||
res.status(500).json({ error: 'Fehler bei der Berechtigungsprüfung' });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if user is club owner
|
||||
* @returns {Function} Express middleware function
|
||||
*/
|
||||
export const requireOwner = () => {
|
||||
return async (req, res, next) => {
|
||||
try {
|
||||
const userId = req.user?.id;
|
||||
|
||||
if (!userId) {
|
||||
return res.status(401).json({ error: 'Nicht authentifiziert' });
|
||||
}
|
||||
|
||||
const clubId =
|
||||
req.params.clubId ??
|
||||
req.params.clubid ??
|
||||
req.params.id ??
|
||||
req.body.clubId ??
|
||||
req.body.clubid ??
|
||||
req.query.clubId ??
|
||||
req.query.clubid;
|
||||
|
||||
if (!clubId) {
|
||||
return res.status(400).json({ error: 'Club-ID fehlt' });
|
||||
}
|
||||
|
||||
const userPermissions = await permissionService.getUserClubPermissions(
|
||||
userId,
|
||||
parseInt(clubId)
|
||||
);
|
||||
|
||||
if (!userPermissions || !userPermissions.isOwner) {
|
||||
return res.status(403).json({
|
||||
error: 'Keine Berechtigung',
|
||||
details: 'Nur der Club-Ersteller hat Zugriff'
|
||||
});
|
||||
}
|
||||
|
||||
req.userPermissions = userPermissions;
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error('Owner check error:', error);
|
||||
res.status(500).json({ error: 'Fehler bei der Berechtigungsprüfung' });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if user is admin (owner or admin role)
|
||||
* @returns {Function} Express middleware function
|
||||
*/
|
||||
export const requireAdmin = () => {
|
||||
return async (req, res, next) => {
|
||||
try {
|
||||
const userId = req.user?.id;
|
||||
|
||||
if (!userId) {
|
||||
return res.status(401).json({ error: 'Nicht authentifiziert' });
|
||||
}
|
||||
|
||||
const clubId =
|
||||
req.params.clubId ??
|
||||
req.params.clubid ??
|
||||
req.params.id ??
|
||||
req.body.clubId ??
|
||||
req.body.clubid ??
|
||||
req.query.clubId ??
|
||||
req.query.clubid;
|
||||
|
||||
if (!clubId) {
|
||||
return res.status(400).json({ error: 'Club-ID fehlt' });
|
||||
}
|
||||
|
||||
const userPermissions = await permissionService.getUserClubPermissions(
|
||||
userId,
|
||||
parseInt(clubId)
|
||||
);
|
||||
|
||||
if (!userPermissions || (userPermissions.role !== 'admin' && !userPermissions.isOwner)) {
|
||||
return res.status(403).json({
|
||||
error: 'Keine Berechtigung',
|
||||
details: 'Administrator-Rechte erforderlich'
|
||||
});
|
||||
}
|
||||
|
||||
req.userPermissions = userPermissions;
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error('Admin check error:', error);
|
||||
res.status(500).json({ error: 'Fehler bei der Berechtigungsprüfung' });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if user has any of the specified roles
|
||||
* @param {string[]} roles - Array of allowed roles
|
||||
* @returns {Function} Express middleware function
|
||||
*/
|
||||
export const requireRole = (roles) => {
|
||||
return async (req, res, next) => {
|
||||
try {
|
||||
const userId = req.user?.id;
|
||||
|
||||
if (!userId) {
|
||||
return res.status(401).json({ error: 'Nicht authentifiziert' });
|
||||
}
|
||||
|
||||
const clubId =
|
||||
req.params.clubId ??
|
||||
req.params.clubid ??
|
||||
req.params.id ??
|
||||
req.body.clubId ??
|
||||
req.body.clubid ??
|
||||
req.query.clubId ??
|
||||
req.query.clubid;
|
||||
|
||||
if (!clubId) {
|
||||
return res.status(400).json({ error: 'Club-ID fehlt' });
|
||||
}
|
||||
|
||||
const userPermissions = await permissionService.getUserClubPermissions(
|
||||
userId,
|
||||
parseInt(clubId)
|
||||
);
|
||||
|
||||
if (!userPermissions || !roles.includes(userPermissions.role)) {
|
||||
return res.status(403).json({
|
||||
error: 'Keine Berechtigung',
|
||||
details: `Erforderliche Rolle: ${roles.join(', ')}`
|
||||
});
|
||||
}
|
||||
|
||||
req.userPermissions = userPermissions;
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error('Role check error:', error);
|
||||
res.status(500).json({ error: 'Fehler bei der Berechtigungsprüfung' });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default {
|
||||
authorize,
|
||||
requireOwner,
|
||||
requireAdmin,
|
||||
requireRole
|
||||
};
|
||||
|
||||
87
backend/middleware/requestLoggingMiddleware.js
Normal file
87
backend/middleware/requestLoggingMiddleware.js
Normal file
@@ -0,0 +1,87 @@
|
||||
import ApiLog from '../models/ApiLog.js';
|
||||
|
||||
/**
|
||||
* Middleware to log all API requests and responses
|
||||
* Should be added early in the middleware chain, but after authentication
|
||||
*/
|
||||
export const requestLoggingMiddleware = async (req, res, next) => {
|
||||
const startTime = Date.now();
|
||||
const originalSend = res.send;
|
||||
|
||||
// Get request body (but limit size for sensitive data)
|
||||
let requestBody = null;
|
||||
if (req.body && Object.keys(req.body).length > 0) {
|
||||
const bodyStr = JSON.stringify(req.body);
|
||||
// Truncate very long bodies
|
||||
requestBody = bodyStr.length > 10000 ? bodyStr.substring(0, 10000) + '... (truncated)' : bodyStr;
|
||||
}
|
||||
|
||||
// Capture response
|
||||
let responseBody = null;
|
||||
res.send = function(data) {
|
||||
// Try to parse response as JSON
|
||||
try {
|
||||
const parsed = JSON.parse(data);
|
||||
const responseStr = JSON.stringify(parsed);
|
||||
// Truncate very long responses
|
||||
responseBody = responseStr.length > 10000 ? responseStr.substring(0, 10000) + '... (truncated)' : responseStr;
|
||||
} catch (e) {
|
||||
// Not JSON, just use raw data (truncated)
|
||||
responseBody = typeof data === 'string' ? data.substring(0, 1000) : String(data).substring(0, 1000);
|
||||
}
|
||||
|
||||
// Restore original send
|
||||
res.send = originalSend;
|
||||
return res.send.apply(res, arguments);
|
||||
};
|
||||
|
||||
// Log after response is sent
|
||||
res.on('finish', async () => {
|
||||
const executionTime = Date.now() - startTime;
|
||||
const ipAddress = req.ip || req.connection.remoteAddress || req.headers['x-forwarded-for'];
|
||||
const path = req.path || req.url;
|
||||
|
||||
// Nur myTischtennis-Requests loggen
|
||||
// Skip logging for non-data endpoints (Status-Checks, Health-Checks, etc.)
|
||||
// Exclude any endpoint containing 'status' or root paths
|
||||
if (
|
||||
path.includes('/status') ||
|
||||
path === '/' ||
|
||||
path === '/health' ||
|
||||
path.endsWith('/status') ||
|
||||
path.includes('/scheduler-status')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Nur myTischtennis-Endpunkte loggen (z.B. /api/mytischtennis/*)
|
||||
if (!path.includes('/mytischtennis')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get user ID if available (wird von authMiddleware gesetzt)
|
||||
const userId = req.user?.id || null;
|
||||
|
||||
try {
|
||||
await ApiLog.create({
|
||||
userId,
|
||||
method: req.method,
|
||||
path: path,
|
||||
statusCode: res.statusCode,
|
||||
requestBody,
|
||||
responseBody,
|
||||
executionTime,
|
||||
errorMessage: res.statusCode >= 400 ? `HTTP ${res.statusCode}` : null,
|
||||
ipAddress,
|
||||
userAgent: req.headers['user-agent'],
|
||||
logType: 'api_request'
|
||||
});
|
||||
} catch (error) {
|
||||
// Don't let logging errors break the request
|
||||
console.error('Error logging API request:', error);
|
||||
}
|
||||
});
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
17
backend/migrations/20251111_add_member_images.sql
Normal file
17
backend/migrations/20251111_add_member_images.sql
Normal file
@@ -0,0 +1,17 @@
|
||||
-- Create table for storing multiple images per member
|
||||
CREATE TABLE IF NOT EXISTS `member_image` (
|
||||
`id` INT NOT NULL AUTO_INCREMENT,
|
||||
`member_id` INT NOT NULL,
|
||||
`file_name` VARCHAR(255) NOT NULL,
|
||||
`sort_order` INT NOT NULL DEFAULT 0,
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `idx_member_image_member_id` (`member_id`),
|
||||
CONSTRAINT `fk_member_image_member`
|
||||
FOREIGN KEY (`member_id`)
|
||||
REFERENCES `member` (`id`)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
-- Migration: Add auto update ratings fields to my_tischtennis table
|
||||
-- Date: 2025-01-27
|
||||
|
||||
-- Add auto_update_ratings column
|
||||
ALTER TABLE my_tischtennis
|
||||
ADD COLUMN auto_update_ratings BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
|
||||
-- Add last_update_ratings column
|
||||
ALTER TABLE my_tischtennis
|
||||
ADD COLUMN last_update_ratings TIMESTAMP NULL;
|
||||
|
||||
-- Create index for auto_update_ratings for efficient querying
|
||||
CREATE INDEX idx_my_tischtennis_auto_update_ratings ON my_tischtennis(auto_update_ratings);
|
||||
@@ -0,0 +1,11 @@
|
||||
-- Migration: Add drawing_data column to predefined_activity_images table
|
||||
-- Date: 2025-09-22
|
||||
-- Description: Adds drawing_data column to store Court Drawing Tool metadata
|
||||
|
||||
ALTER TABLE `predefined_activity_images`
|
||||
ADD COLUMN `drawing_data` TEXT NULL
|
||||
COMMENT 'JSON string containing drawing metadata for Court Drawing Tool'
|
||||
AFTER `mime_type`;
|
||||
|
||||
-- Verify the column was added
|
||||
DESCRIBE `predefined_activity_images`;
|
||||
@@ -0,0 +1,5 @@
|
||||
ALTER TABLE clubs
|
||||
ADD COLUMN IF NOT EXISTS greeting_text TEXT NULL,
|
||||
ADD COLUMN IF NOT EXISTS association_member_number VARCHAR(255) NULL;
|
||||
|
||||
|
||||
9
backend/migrations/add_group_id_to_participants.sql
Normal file
9
backend/migrations/add_group_id_to_participants.sql
Normal file
@@ -0,0 +1,9 @@
|
||||
-- Migration: Add group_id to participants table
|
||||
-- This allows assigning participants to groups for training organization
|
||||
|
||||
ALTER TABLE participants
|
||||
ADD COLUMN group_id INTEGER NULL REFERENCES "group"(id) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- Add index for better query performance
|
||||
CREATE INDEX IF NOT EXISTS idx_participants_group_id ON participants(group_id);
|
||||
|
||||
28
backend/migrations/add_match_result_fields.sql
Normal file
28
backend/migrations/add_match_result_fields.sql
Normal file
@@ -0,0 +1,28 @@
|
||||
-- Migration: Add match result fields to match table
|
||||
-- Date: 2025-01-27
|
||||
-- For MariaDB
|
||||
|
||||
-- Add myTischtennis meeting ID
|
||||
ALTER TABLE `match`
|
||||
ADD COLUMN my_tischtennis_meeting_id VARCHAR(255) NULL UNIQUE COMMENT 'Meeting ID from myTischtennis (e.g. 15440488)';
|
||||
|
||||
-- Add home match points
|
||||
ALTER TABLE `match`
|
||||
ADD COLUMN home_match_points INT DEFAULT 0 NULL COMMENT 'Match points won by home team';
|
||||
|
||||
-- Add guest match points
|
||||
ALTER TABLE `match`
|
||||
ADD COLUMN guest_match_points INT DEFAULT 0 NULL COMMENT 'Match points won by guest team';
|
||||
|
||||
-- Add is_completed flag
|
||||
ALTER TABLE `match`
|
||||
ADD COLUMN is_completed BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'Whether the match is completed';
|
||||
|
||||
-- Add PDF URL
|
||||
ALTER TABLE `match`
|
||||
ADD COLUMN pdf_url VARCHAR(512) NULL COMMENT 'PDF URL from myTischtennis';
|
||||
|
||||
-- Create indexes
|
||||
CREATE INDEX idx_match_my_tischtennis_meeting_id ON `match`(my_tischtennis_meeting_id);
|
||||
CREATE INDEX idx_match_is_completed ON `match`(is_completed);
|
||||
|
||||
4
backend/migrations/add_matches_tied_to_team.sql
Normal file
4
backend/migrations/add_matches_tied_to_team.sql
Normal file
@@ -0,0 +1,4 @@
|
||||
-- Add matches_tied column to team table
|
||||
ALTER TABLE team
|
||||
ADD COLUMN matches_tied INTEGER NOT NULL DEFAULT 0 AFTER matches_lost;
|
||||
|
||||
57
backend/migrations/add_member_contact_and_postal_code.sql
Normal file
57
backend/migrations/add_member_contact_and_postal_code.sql
Normal file
@@ -0,0 +1,57 @@
|
||||
-- Add postal_code column to member table
|
||||
ALTER TABLE `member`
|
||||
ADD COLUMN `postal_code` TEXT NULL COMMENT 'Postal code (PLZ)' AFTER `city`;
|
||||
|
||||
-- Create member_contact table for multiple phone numbers and email addresses
|
||||
CREATE TABLE IF NOT EXISTS `member_contact` (
|
||||
`id` INT NOT NULL AUTO_INCREMENT,
|
||||
`member_id` INT NOT NULL,
|
||||
`type` ENUM('phone', 'email') NOT NULL COMMENT 'Type of contact: phone or email',
|
||||
`value` TEXT NOT NULL,
|
||||
`is_parent` BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'Whether this contact belongs to a parent',
|
||||
`parent_name` TEXT NULL COMMENT 'Name of the parent (e.g. "Mutter", "Vater", "Elternteil 1")',
|
||||
`is_primary` BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'Whether this is the primary contact of this type',
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `idx_member_id` (`member_id`),
|
||||
INDEX `idx_type` (`type`),
|
||||
CONSTRAINT `fk_member_contact_member`
|
||||
FOREIGN KEY (`member_id`)
|
||||
REFERENCES `member` (`id`)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Migrate existing phone numbers from member.phone to member_contact
|
||||
INSERT INTO `member_contact` (`member_id`, `type`, `value`, `is_parent`, `parent_name`, `is_primary`, `created_at`, `updated_at`)
|
||||
SELECT
|
||||
`id` AS `member_id`,
|
||||
'phone' AS `type`,
|
||||
`phone` AS `value`,
|
||||
FALSE AS `is_parent`,
|
||||
NULL AS `parent_name`,
|
||||
TRUE AS `is_primary`,
|
||||
NOW() AS `created_at`,
|
||||
NOW() AS `updated_at`
|
||||
FROM `member`
|
||||
WHERE `phone` IS NOT NULL
|
||||
AND `phone` != ''
|
||||
AND TRIM(`phone`) != '';
|
||||
|
||||
-- Migrate existing email addresses from member.email to member_contact
|
||||
INSERT INTO `member_contact` (`member_id`, `type`, `value`, `is_parent`, `parent_name`, `is_primary`, `created_at`, `updated_at`)
|
||||
SELECT
|
||||
`id` AS `member_id`,
|
||||
'email' AS `type`,
|
||||
`email` AS `value`,
|
||||
FALSE AS `is_parent`,
|
||||
NULL AS `parent_name`,
|
||||
TRUE AS `is_primary`,
|
||||
NOW() AS `created_at`,
|
||||
NOW() AS `updated_at`
|
||||
FROM `member`
|
||||
WHERE `email` IS NOT NULL
|
||||
AND `email` != ''
|
||||
AND TRIM(`email`) != '';
|
||||
|
||||
19
backend/migrations/add_mytischtennis_fields_to_league.sql
Normal file
19
backend/migrations/add_mytischtennis_fields_to_league.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
-- Migration: Add myTischtennis fields to league table
|
||||
-- Date: 2025-01-27
|
||||
-- For MariaDB
|
||||
|
||||
-- Add my_tischtennis_group_id column
|
||||
ALTER TABLE league
|
||||
ADD COLUMN my_tischtennis_group_id VARCHAR(255) NULL COMMENT 'Group ID from myTischtennis (e.g. 504417)';
|
||||
|
||||
-- Add association column
|
||||
ALTER TABLE league
|
||||
ADD COLUMN association VARCHAR(255) NULL COMMENT 'Association/Verband (e.g. HeTTV)';
|
||||
|
||||
-- Add groupname column
|
||||
ALTER TABLE league
|
||||
ADD COLUMN groupname VARCHAR(255) NULL COMMENT 'Group name for URL (e.g. 1.Kreisklasse)';
|
||||
|
||||
-- Create index for efficient querying
|
||||
CREATE INDEX idx_league_my_tischtennis_group_id ON league(my_tischtennis_group_id);
|
||||
|
||||
11
backend/migrations/add_mytischtennis_player_id_to_member.sql
Normal file
11
backend/migrations/add_mytischtennis_player_id_to_member.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
-- Migration: Add myTischtennis player ID to member table
|
||||
-- Date: 2025-01-27
|
||||
-- For MariaDB
|
||||
|
||||
-- Add my_tischtennis_player_id column
|
||||
ALTER TABLE member
|
||||
ADD COLUMN my_tischtennis_player_id VARCHAR(255) NULL COMMENT 'Player ID from myTischtennis (e.g. NU2705037)';
|
||||
|
||||
-- Create index for efficient querying
|
||||
CREATE INDEX idx_member_my_tischtennis_player_id ON member(my_tischtennis_player_id);
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
-- Migration: Add myTischtennis team ID to club_team table
|
||||
-- Date: 2025-01-27
|
||||
-- For MariaDB
|
||||
|
||||
-- Add my_tischtennis_team_id column
|
||||
ALTER TABLE club_team
|
||||
ADD COLUMN my_tischtennis_team_id VARCHAR(255) NULL COMMENT 'Team ID from myTischtennis (e.g. 2995094)';
|
||||
|
||||
-- Create index for efficient querying
|
||||
CREATE INDEX idx_club_team_my_tischtennis_team_id ON club_team(my_tischtennis_team_id);
|
||||
|
||||
17
backend/migrations/add_permissions_to_user_club.sql
Normal file
17
backend/migrations/add_permissions_to_user_club.sql
Normal file
@@ -0,0 +1,17 @@
|
||||
-- Add role and permissions columns to user_club table
|
||||
ALTER TABLE `user_club`
|
||||
ADD COLUMN `role` VARCHAR(50) DEFAULT 'member' COMMENT 'User role: admin, trainer, team_manager, member' AFTER `approved`,
|
||||
ADD COLUMN `permissions` JSON NULL COMMENT 'Specific permissions: {diary: {read: true, write: true}, members: {...}, ...}' AFTER `role`,
|
||||
ADD COLUMN `is_owner` BOOLEAN DEFAULT FALSE COMMENT 'True if user created the club' AFTER `permissions`;
|
||||
|
||||
-- Create index for faster role lookups
|
||||
CREATE INDEX `idx_user_club_role` ON `user_club` (`role`);
|
||||
CREATE INDEX `idx_user_club_owner` ON `user_club` (`is_owner`);
|
||||
|
||||
-- Set existing approved users as members
|
||||
UPDATE `user_club` SET `role` = 'member' WHERE `approved` = 1 AND `role` IS NULL;
|
||||
|
||||
-- If there's a user who created the club (we need to identify them somehow)
|
||||
-- For now, we'll need to manually set the owner after migration
|
||||
|
||||
|
||||
8
backend/migrations/add_player_tracking_to_match.sql
Normal file
8
backend/migrations/add_player_tracking_to_match.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
-- Add player tracking fields to match table
|
||||
-- These fields store arrays of member IDs for different participation states
|
||||
|
||||
ALTER TABLE `match`
|
||||
ADD COLUMN `players_ready` JSON NULL COMMENT 'Array of member IDs who are ready to play' AFTER `pdf_url`,
|
||||
ADD COLUMN `players_planned` JSON NULL COMMENT 'Array of member IDs who are planned to play' AFTER `players_ready`,
|
||||
ADD COLUMN `players_played` JSON NULL COMMENT 'Array of member IDs who actually played' AFTER `players_planned`;
|
||||
|
||||
44
backend/migrations/add_season_to_teams.sql
Normal file
44
backend/migrations/add_season_to_teams.sql
Normal file
@@ -0,0 +1,44 @@
|
||||
-- Migration: Add season_id to teams table
|
||||
-- First, add the column as nullable
|
||||
ALTER TABLE `team` ADD COLUMN `season_id` INT NULL;
|
||||
|
||||
-- Get or create current season
|
||||
SET @current_season_id = (
|
||||
SELECT id FROM `season`
|
||||
WHERE season = (
|
||||
CASE
|
||||
WHEN MONTH(CURDATE()) >= 7 THEN CONCAT(YEAR(CURDATE()), '/', YEAR(CURDATE()) + 1)
|
||||
ELSE CONCAT(YEAR(CURDATE()) - 1, '/', YEAR(CURDATE()))
|
||||
END
|
||||
)
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
-- If no season exists, create it
|
||||
INSERT IGNORE INTO `season` (season) VALUES (
|
||||
CASE
|
||||
WHEN MONTH(CURDATE()) >= 7 THEN CONCAT(YEAR(CURDATE()), '/', YEAR(CURDATE()) + 1)
|
||||
ELSE CONCAT(YEAR(CURDATE()) - 1, '/', YEAR(CURDATE()))
|
||||
END
|
||||
);
|
||||
|
||||
-- Get the season ID again (in case we just created it)
|
||||
SET @current_season_id = (
|
||||
SELECT id FROM `season`
|
||||
WHERE season = (
|
||||
CASE
|
||||
WHEN MONTH(CURDATE()) >= 7 THEN CONCAT(YEAR(CURDATE()), '/', YEAR(CURDATE()) + 1)
|
||||
ELSE CONCAT(YEAR(CURDATE()) - 1, '/', YEAR(CURDATE()))
|
||||
END
|
||||
)
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
-- Update all existing teams to use the current season
|
||||
UPDATE `team` SET `season_id` = @current_season_id WHERE `season_id` IS NULL;
|
||||
|
||||
-- Now make the column NOT NULL and add the foreign key constraint
|
||||
ALTER TABLE `team` MODIFY COLUMN `season_id` INT NOT NULL;
|
||||
ALTER TABLE `team` ADD CONSTRAINT `team_season_id_foreign_idx`
|
||||
FOREIGN KEY (`season_id`) REFERENCES `season` (`id`)
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
11
backend/migrations/add_table_fields_to_team.sql
Normal file
11
backend/migrations/add_table_fields_to_team.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
-- Migration: Add table fields to team table
|
||||
-- Add fields for league table calculations
|
||||
|
||||
ALTER TABLE team ADD COLUMN matches_played INT NOT NULL DEFAULT 0;
|
||||
ALTER TABLE team ADD COLUMN matches_won INT NOT NULL DEFAULT 0;
|
||||
ALTER TABLE team ADD COLUMN matches_lost INT NOT NULL DEFAULT 0;
|
||||
ALTER TABLE team ADD COLUMN sets_won INT NOT NULL DEFAULT 0;
|
||||
ALTER TABLE team ADD COLUMN sets_lost INT NOT NULL DEFAULT 0;
|
||||
ALTER TABLE team ADD COLUMN points_won INT NOT NULL DEFAULT 0;
|
||||
ALTER TABLE team ADD COLUMN points_lost INT NOT NULL DEFAULT 0;
|
||||
ALTER TABLE team ADD COLUMN table_points INT NOT NULL DEFAULT 0;
|
||||
5
backend/migrations/add_table_points_won_lost_to_team.sql
Normal file
5
backend/migrations/add_table_points_won_lost_to_team.sql
Normal file
@@ -0,0 +1,5 @@
|
||||
-- Add table_points_won and table_points_lost columns to team table
|
||||
ALTER TABLE team
|
||||
ADD COLUMN table_points_won INTEGER NOT NULL DEFAULT 0 AFTER table_points,
|
||||
ADD COLUMN table_points_lost INTEGER NOT NULL DEFAULT 0 AFTER table_points_won;
|
||||
|
||||
26
backend/migrations/create_api_log_table.sql
Normal file
26
backend/migrations/create_api_log_table.sql
Normal file
@@ -0,0 +1,26 @@
|
||||
-- Migration: Create api_log table for comprehensive request/response and execution logging
|
||||
|
||||
CREATE TABLE IF NOT EXISTS api_log (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT NULL,
|
||||
method VARCHAR(10) NOT NULL COMMENT 'HTTP method (GET, POST, PUT, DELETE, etc.)',
|
||||
path VARCHAR(500) NOT NULL COMMENT 'Request path',
|
||||
status_code INT NULL COMMENT 'HTTP status code',
|
||||
request_body TEXT NULL COMMENT 'Request body (truncated if too long)',
|
||||
response_body TEXT NULL COMMENT 'Response body (truncated if too long)',
|
||||
execution_time INT NULL COMMENT 'Execution time in milliseconds',
|
||||
error_message TEXT NULL COMMENT 'Error message if request failed',
|
||||
ip_address VARCHAR(45) NULL COMMENT 'Client IP address',
|
||||
user_agent VARCHAR(500) NULL COMMENT 'User agent string',
|
||||
log_type ENUM('api_request', 'scheduler', 'cron_job', 'manual') NOT NULL DEFAULT 'api_request' COMMENT 'Type of log entry',
|
||||
scheduler_job_type VARCHAR(50) NULL COMMENT 'Type of scheduler job (rating_updates, match_results, etc.)',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
INDEX idx_api_log_user_id (user_id, created_at),
|
||||
INDEX idx_api_log_path (path, created_at),
|
||||
INDEX idx_api_log_log_type (log_type, created_at),
|
||||
INDEX idx_api_log_created_at (created_at),
|
||||
INDEX idx_api_log_status_code (status_code)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
20
backend/migrations/create_my_tischtennis_fetch_log.sql
Normal file
20
backend/migrations/create_my_tischtennis_fetch_log.sql
Normal file
@@ -0,0 +1,20 @@
|
||||
-- Create my_tischtennis_fetch_log table for tracking data fetches
|
||||
CREATE TABLE IF NOT EXISTS my_tischtennis_fetch_log (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
fetch_type ENUM('ratings', 'match_results', 'league_table') NOT NULL COMMENT 'Type of data fetch',
|
||||
success BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
message TEXT,
|
||||
error_details TEXT,
|
||||
records_processed INT NOT NULL DEFAULT 0 COMMENT 'Number of records processed',
|
||||
execution_time INT COMMENT 'Execution time in milliseconds',
|
||||
is_automatic BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'Automatic or manual fetch',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
|
||||
INDEX idx_user_fetch_type_created (user_id, fetch_type, created_at),
|
||||
INDEX idx_created_at (created_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
23
backend/migrations/create_my_tischtennis_update_history.sql
Normal file
23
backend/migrations/create_my_tischtennis_update_history.sql
Normal file
@@ -0,0 +1,23 @@
|
||||
-- Migration: Create my_tischtennis_update_history table
|
||||
-- Date: 2025-01-27
|
||||
-- For MariaDB
|
||||
|
||||
CREATE TABLE IF NOT EXISTS my_tischtennis_update_history (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
success BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
message TEXT,
|
||||
error_details TEXT,
|
||||
updated_count INT DEFAULT 0,
|
||||
execution_time INT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT fk_my_tischtennis_update_history_user_id
|
||||
FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Create indexes for efficient querying
|
||||
CREATE INDEX idx_my_tischtennis_update_history_user_id ON my_tischtennis_update_history(user_id);
|
||||
CREATE INDEX idx_my_tischtennis_update_history_created_at ON my_tischtennis_update_history(created_at);
|
||||
CREATE INDEX idx_my_tischtennis_update_history_success ON my_tischtennis_update_history(success);
|
||||
8
backend/migrations/make_location_optional_in_match.sql
Normal file
8
backend/migrations/make_location_optional_in_match.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
-- Migration: Make locationId optional in match table
|
||||
-- Date: 2025-01-27
|
||||
-- For MariaDB
|
||||
|
||||
-- Modify locationId to allow NULL
|
||||
ALTER TABLE `match`
|
||||
MODIFY COLUMN location_id INT NULL;
|
||||
|
||||
38
backend/migrations/update_existing_user_club_permissions.sql
Normal file
38
backend/migrations/update_existing_user_club_permissions.sql
Normal file
@@ -0,0 +1,38 @@
|
||||
-- Update existing user_club entries with default permissions
|
||||
-- This migration sets default values for role and is_owner for existing club memberships
|
||||
|
||||
-- Set default role to 'member' for all approved users who don't have a role yet
|
||||
UPDATE `user_club`
|
||||
SET `role` = 'member'
|
||||
WHERE `approved` = 1
|
||||
AND (`role` IS NULL OR `role` = '');
|
||||
|
||||
-- Optionally: Set the first approved user of each club as owner
|
||||
-- This finds the user with the lowest user_id per club (oldest member) and marks them as owner
|
||||
UPDATE `user_club` AS uc1
|
||||
INNER JOIN (
|
||||
SELECT `club_id`, MIN(`user_id`) as `first_user_id`
|
||||
FROM `user_club`
|
||||
WHERE `approved` = 1
|
||||
GROUP BY `club_id`
|
||||
) AS uc2 ON uc1.`club_id` = uc2.`club_id` AND uc1.`user_id` = uc2.`first_user_id`
|
||||
SET
|
||||
uc1.`is_owner` = 1,
|
||||
uc1.`role` = 'admin';
|
||||
|
||||
-- Verify the changes
|
||||
SELECT
|
||||
uc.`club_id`,
|
||||
c.`name` as club_name,
|
||||
uc.`user_id`,
|
||||
u.`email` as user_email,
|
||||
uc.`role`,
|
||||
uc.`is_owner`,
|
||||
uc.`approved`
|
||||
FROM `user_club` uc
|
||||
LEFT JOIN `club` c ON c.`id` = uc.`club_id`
|
||||
LEFT JOIN `user` u ON u.`id` = uc.`user_id`
|
||||
WHERE uc.`approved` = 1
|
||||
ORDER BY uc.`club_id`, uc.`is_owner` DESC, uc.`user_id`;
|
||||
|
||||
|
||||
102
backend/models/ApiLog.js
Normal file
102
backend/models/ApiLog.js
Normal file
@@ -0,0 +1,102 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
import User from './User.js';
|
||||
|
||||
const ApiLog = sequelize.define('ApiLog', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false,
|
||||
},
|
||||
userId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: User,
|
||||
key: 'id',
|
||||
},
|
||||
onDelete: 'SET NULL',
|
||||
onUpdate: 'CASCADE',
|
||||
},
|
||||
method: {
|
||||
type: DataTypes.STRING(10),
|
||||
allowNull: false,
|
||||
comment: 'HTTP method (GET, POST, PUT, DELETE, etc.)'
|
||||
},
|
||||
path: {
|
||||
type: DataTypes.STRING(500),
|
||||
allowNull: false,
|
||||
comment: 'Request path'
|
||||
},
|
||||
statusCode: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: 'HTTP status code'
|
||||
},
|
||||
requestBody: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: 'Request body (truncated if too long)'
|
||||
},
|
||||
responseBody: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: 'Response body (truncated if too long)'
|
||||
},
|
||||
executionTime: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: 'Execution time in milliseconds'
|
||||
},
|
||||
errorMessage: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: 'Error message if completes failed'
|
||||
},
|
||||
ipAddress: {
|
||||
type: DataTypes.STRING(45),
|
||||
allowNull: true,
|
||||
comment: 'Client IP address'
|
||||
},
|
||||
userAgent: {
|
||||
type: DataTypes.STRING(500),
|
||||
allowNull: true,
|
||||
comment: 'User agent string'
|
||||
},
|
||||
logType: {
|
||||
type: DataTypes.ENUM('api_request', 'scheduler', 'cron_job', 'manual'),
|
||||
allowNull: false,
|
||||
defaultValue: 'api_request',
|
||||
comment: 'Type of log entry'
|
||||
},
|
||||
schedulerJobType: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
comment: 'Type of scheduler job (rating_updates, match_results, etc.)'
|
||||
},
|
||||
}, {
|
||||
underscored: true,
|
||||
tableName: 'api_log',
|
||||
timestamps: true,
|
||||
indexes: [
|
||||
{
|
||||
fields: ['user_id', 'created_at']
|
||||
},
|
||||
{
|
||||
fields: ['path', 'created_at']
|
||||
},
|
||||
{
|
||||
fields: ['log_type', 'created_at']
|
||||
},
|
||||
{
|
||||
fields: ['created_at']
|
||||
},
|
||||
{
|
||||
fields: ['status_code']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
export default ApiLog;
|
||||
|
||||
@@ -7,6 +7,16 @@ const Club = sequelize.define('Club', {
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
},
|
||||
greetingText: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'greeting_text'
|
||||
},
|
||||
associationMemberNumber: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
field: 'association_member_number'
|
||||
},
|
||||
}, {
|
||||
tableName: 'clubs',
|
||||
underscored: true,
|
||||
|
||||
60
backend/models/ClubTeam.js
Normal file
60
backend/models/ClubTeam.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
import Club from './Club.js';
|
||||
import League from './League.js';
|
||||
import Season from './Season.js';
|
||||
|
||||
const ClubTeam = sequelize.define('ClubTeam', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false,
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
clubId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: Club,
|
||||
key: 'id',
|
||||
},
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
},
|
||||
leagueId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: League,
|
||||
key: 'id',
|
||||
},
|
||||
onDelete: 'SET NULL',
|
||||
onUpdate: 'CASCADE',
|
||||
},
|
||||
seasonId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: Season,
|
||||
key: 'id',
|
||||
},
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
},
|
||||
myTischtennisTeamId: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
comment: 'Team ID from myTischtennis (e.g. 2995094)',
|
||||
field: 'my_tischtennis_team_id'
|
||||
},
|
||||
}, {
|
||||
underscored: true,
|
||||
tableName: 'club_team',
|
||||
timestamps: true,
|
||||
});
|
||||
|
||||
export default ClubTeam;
|
||||
26
backend/models/DiaryMemberActivity.js
Normal file
26
backend/models/DiaryMemberActivity.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
|
||||
const DiaryMemberActivity = sequelize.define('DiaryMemberActivity', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
},
|
||||
diaryDateActivityId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
participantId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
}, {
|
||||
tableName: 'diary_member_activities',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
});
|
||||
|
||||
export default DiaryMemberActivity;
|
||||
|
||||
|
||||
@@ -34,6 +34,22 @@ const League = sequelize.define('League', {
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
},
|
||||
myTischtennisGroupId: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
comment: 'Group ID from myTischtennis (e.g. 504417)',
|
||||
field: 'my_tischtennis_group_id'
|
||||
},
|
||||
association: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
comment: 'Association/Verband (e.g. HeTTV)',
|
||||
},
|
||||
groupname: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
comment: 'Group name for URL (e.g. 1.Kreisklasse)',
|
||||
},
|
||||
}, {
|
||||
underscored: true,
|
||||
tableName: 'league',
|
||||
|
||||
@@ -3,7 +3,6 @@ import sequelize from '../database.js';
|
||||
import Club from './Club.js';
|
||||
import League from './League.js';
|
||||
import Team from './Team.js';
|
||||
import Season from './Season.js';
|
||||
import Location from './Location.js';
|
||||
|
||||
const Match = sequelize.define('Match', {
|
||||
@@ -21,21 +20,13 @@ const Match = sequelize.define('Match', {
|
||||
type: DataTypes.TIME,
|
||||
allowNull: true,
|
||||
},
|
||||
seasonId: {
|
||||
type: DataTypes.INTEGER,
|
||||
references: {
|
||||
model: Season,
|
||||
key: 'id',
|
||||
},
|
||||
allowNull: false,
|
||||
},
|
||||
locationId: {
|
||||
type: DataTypes.INTEGER,
|
||||
references: {
|
||||
model: Location,
|
||||
key: 'id',
|
||||
},
|
||||
allowNull: false,
|
||||
allowNull: true,
|
||||
},
|
||||
homeTeamId: {
|
||||
type: DataTypes.INTEGER,
|
||||
@@ -69,6 +60,73 @@ const Match = sequelize.define('Match', {
|
||||
},
|
||||
allowNull: false,
|
||||
},
|
||||
code: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
comment: 'Spiel-Code aus PDF-Parsing'
|
||||
},
|
||||
homePin: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
comment: 'Pin-Code für Heimteam aus PDF-Parsing'
|
||||
},
|
||||
guestPin: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
comment: 'Pin-Code für Gastteam aus PDF-Parsing'
|
||||
},
|
||||
myTischtennisMeetingId: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
unique: true,
|
||||
comment: 'Meeting ID from myTischtennis (e.g. 15440488)',
|
||||
field: 'my_tischtennis_meeting_id'
|
||||
},
|
||||
homeMatchPoints: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
defaultValue: 0,
|
||||
comment: 'Match points won by home team',
|
||||
field: 'home_match_points'
|
||||
},
|
||||
guestMatchPoints: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
defaultValue: 0,
|
||||
comment: 'Match points won by guest team',
|
||||
field: 'guest_match_points'
|
||||
},
|
||||
isCompleted: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
comment: 'Whether the match is completed',
|
||||
field: 'is_completed'
|
||||
},
|
||||
pdfUrl: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
comment: 'PDF URL from myTischtennis',
|
||||
field: 'pdf_url'
|
||||
},
|
||||
playersReady: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
comment: 'Array of member IDs who are ready to play',
|
||||
field: 'players_ready'
|
||||
},
|
||||
playersPlanned: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
comment: 'Array of member IDs who are planned to play',
|
||||
field: 'players_planned'
|
||||
},
|
||||
playersPlayed: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
comment: 'Array of member IDs who actually played',
|
||||
field: 'players_played'
|
||||
},
|
||||
}, {
|
||||
underscored: true,
|
||||
tableName: 'match',
|
||||
|
||||
@@ -45,13 +45,14 @@ const Member = sequelize.define('Member', {
|
||||
},
|
||||
birthDate: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
allowNull: true,
|
||||
set(value) {
|
||||
const encryptedValue = encryptData(value);
|
||||
const encryptedValue = encryptData(value || '');
|
||||
this.setDataValue('birthDate', encryptedValue);
|
||||
},
|
||||
get() {
|
||||
const encryptedValue = this.getDataValue('birthDate');
|
||||
if (!encryptedValue) return null;
|
||||
return decryptData(encryptedValue);
|
||||
}
|
||||
},
|
||||
@@ -91,6 +92,21 @@ const Member = sequelize.define('Member', {
|
||||
return decryptData(encryptedValue);
|
||||
}
|
||||
},
|
||||
postalCode: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
set(value) {
|
||||
const encryptedValue = encryptData(value || '');
|
||||
this.setDataValue('postalCode', encryptedValue);
|
||||
},
|
||||
get() {
|
||||
const encryptedValue = this.getDataValue('postalCode');
|
||||
if (!encryptedValue) return null;
|
||||
return decryptData(encryptedValue);
|
||||
},
|
||||
field: 'postal_code',
|
||||
comment: 'Postal code (PLZ)'
|
||||
},
|
||||
email: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
@@ -122,6 +138,35 @@ const Member = sequelize.define('Member', {
|
||||
allowNull: false,
|
||||
default: false,
|
||||
}
|
||||
,
|
||||
gender: {
|
||||
type: DataTypes.ENUM('male','female','diverse','unknown'),
|
||||
allowNull: true,
|
||||
defaultValue: 'unknown'
|
||||
},
|
||||
ttr: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
defaultValue: null
|
||||
},
|
||||
qttr: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
defaultValue: null
|
||||
},
|
||||
memberFormHandedOver: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
field: 'member_form_handed_over',
|
||||
comment: 'Mitgliedsformular ausgehändigt'
|
||||
},
|
||||
myTischtennisPlayerId: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
comment: 'Player ID from myTischtennis (e.g. NU2705037)',
|
||||
field: 'my_tischtennis_player_id'
|
||||
}
|
||||
}, {
|
||||
underscored: true,
|
||||
sequelize,
|
||||
@@ -135,13 +180,7 @@ const Member = sequelize.define('Member', {
|
||||
member.save();
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
underscored: true,
|
||||
tableName: 'log',
|
||||
timestamps: true
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
Member.belongsTo(Club, { as: 'club' });
|
||||
Club.hasMany(Member, { as: 'members' });
|
||||
|
||||
89
backend/models/MemberContact.js
Normal file
89
backend/models/MemberContact.js
Normal file
@@ -0,0 +1,89 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
import Member from './Member.js';
|
||||
import { encryptData, decryptData } from '../utils/encrypt.js';
|
||||
|
||||
const MemberContact = sequelize.define('MemberContact', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false
|
||||
},
|
||||
memberId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'member',
|
||||
key: 'id'
|
||||
},
|
||||
onDelete: 'CASCADE',
|
||||
field: 'member_id'
|
||||
},
|
||||
type: {
|
||||
type: DataTypes.ENUM('phone', 'email'),
|
||||
allowNull: false,
|
||||
comment: 'Type of contact: phone or email'
|
||||
},
|
||||
value: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
set(value) {
|
||||
const encryptedValue = encryptData(value);
|
||||
this.setDataValue('value', encryptedValue);
|
||||
},
|
||||
get() {
|
||||
const encryptedValue = this.getDataValue('value');
|
||||
if (!encryptedValue) return null;
|
||||
try {
|
||||
return decryptData(encryptedValue);
|
||||
} catch (error) {
|
||||
console.error('[MemberContact] Error decrypting value:', error);
|
||||
return encryptedValue; // Fallback: return encrypted value if decryption fails
|
||||
}
|
||||
}
|
||||
},
|
||||
isParent: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
field: 'is_parent',
|
||||
comment: 'Whether this contact belongs to a parent'
|
||||
},
|
||||
parentName: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
set(value) {
|
||||
if (value) {
|
||||
const encryptedValue = encryptData(value);
|
||||
this.setDataValue('parentName', encryptedValue);
|
||||
} else {
|
||||
this.setDataValue('parentName', null);
|
||||
}
|
||||
},
|
||||
get() {
|
||||
const encryptedValue = this.getDataValue('parentName');
|
||||
return encryptedValue ? decryptData(encryptedValue) : null;
|
||||
},
|
||||
field: 'parent_name',
|
||||
comment: 'Name of the parent (e.g. "Mutter", "Vater", "Elternteil 1")'
|
||||
},
|
||||
isPrimary: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
field: 'is_primary',
|
||||
comment: 'Whether this is the primary contact of this type'
|
||||
}
|
||||
}, {
|
||||
underscored: true,
|
||||
sequelize,
|
||||
modelName: 'MemberContact',
|
||||
tableName: 'member_contact',
|
||||
timestamps: true
|
||||
});
|
||||
|
||||
// Associations are defined in models/index.js to avoid duplicate alias errors
|
||||
|
||||
export default MemberContact;
|
||||
|
||||
40
backend/models/MemberImage.js
Normal file
40
backend/models/MemberImage.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
|
||||
const MemberImage = sequelize.define('MemberImage', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
allowNull: false
|
||||
},
|
||||
memberId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
field: 'member_id',
|
||||
references: {
|
||||
model: 'member',
|
||||
key: 'id'
|
||||
},
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE'
|
||||
},
|
||||
fileName: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: false,
|
||||
field: 'file_name'
|
||||
},
|
||||
sortOrder: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
field: 'sort_order'
|
||||
}
|
||||
}, {
|
||||
tableName: 'member_image',
|
||||
underscored: true,
|
||||
timestamps: true
|
||||
});
|
||||
|
||||
export default MemberImage;
|
||||
|
||||
117
backend/models/MemberTransferConfig.js
Normal file
117
backend/models/MemberTransferConfig.js
Normal file
@@ -0,0 +1,117 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
import Club from './Club.js';
|
||||
import { encryptData, decryptData } from '../utils/encrypt.js';
|
||||
|
||||
const MemberTransferConfig = sequelize.define('MemberTransferConfig', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false
|
||||
},
|
||||
clubId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
references: {
|
||||
model: Club,
|
||||
key: 'id'
|
||||
},
|
||||
onDelete: 'CASCADE'
|
||||
},
|
||||
server: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
field: 'server',
|
||||
comment: 'Base URL des Servers (z.B. https://example.com)'
|
||||
},
|
||||
loginEndpoint: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
field: 'login_endpoint',
|
||||
comment: 'Relativer Pfad zum Login-Endpoint (z.B. /api/auth/login)'
|
||||
},
|
||||
loginFormat: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
defaultValue: 'json',
|
||||
field: 'login_format'
|
||||
},
|
||||
encryptedLoginCredentials: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'encrypted_login_credentials',
|
||||
comment: 'Verschlüsselte Login-Daten als JSON-String'
|
||||
},
|
||||
transferEndpoint: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
field: 'transfer_endpoint',
|
||||
comment: 'Relativer Pfad zum Übertragungs-Endpoint (z.B. /api/members/bulk)'
|
||||
},
|
||||
transferMethod: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
defaultValue: 'POST',
|
||||
field: 'transfer_method'
|
||||
},
|
||||
transferFormat: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
defaultValue: 'json',
|
||||
field: 'transfer_format'
|
||||
},
|
||||
transferTemplate: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
field: 'transfer_template'
|
||||
},
|
||||
useBulkMode: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
field: 'use_bulk_mode'
|
||||
},
|
||||
bulkWrapperTemplate: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'bulk_wrapper_template',
|
||||
comment: 'Optionales Template für die äußere Struktur im Bulk-Modus (z.B. {"data": {"members": "{{members}}"}})'
|
||||
}
|
||||
}, {
|
||||
underscored: true,
|
||||
tableName: 'member_transfer_config',
|
||||
timestamps: true
|
||||
});
|
||||
|
||||
// Getter/Setter für verschlüsselte Login-Daten
|
||||
MemberTransferConfig.prototype.getLoginCredentials = function() {
|
||||
if (!this.encryptedLoginCredentials) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const decrypted = decryptData(this.encryptedLoginCredentials);
|
||||
return JSON.parse(decrypted);
|
||||
} catch (error) {
|
||||
console.error('[MemberTransferConfig] Error decrypting login credentials:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
MemberTransferConfig.prototype.setLoginCredentials = function(credentials) {
|
||||
if (!credentials || Object.keys(credentials).length === 0) {
|
||||
this.encryptedLoginCredentials = null;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const jsonString = JSON.stringify(credentials);
|
||||
this.encryptedLoginCredentials = encryptData(jsonString);
|
||||
} catch (error) {
|
||||
console.error('[MemberTransferConfig] Error encrypting login credentials:', error);
|
||||
this.encryptedLoginCredentials = null;
|
||||
}
|
||||
};
|
||||
|
||||
export default MemberTransferConfig;
|
||||
|
||||
133
backend/models/MyTischtennis.js
Normal file
133
backend/models/MyTischtennis.js
Normal file
@@ -0,0 +1,133 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
import { encryptData, decryptData } from '../utils/encrypt.js';
|
||||
|
||||
const MyTischtennis = sequelize.define('MyTischtennis', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false
|
||||
},
|
||||
userId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
references: {
|
||||
model: 'user',
|
||||
key: 'id'
|
||||
},
|
||||
onDelete: 'CASCADE'
|
||||
},
|
||||
email: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
encryptedPassword: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'encrypted_password'
|
||||
},
|
||||
savePassword: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false,
|
||||
allowNull: false,
|
||||
field: 'save_password'
|
||||
},
|
||||
autoUpdateRatings: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false,
|
||||
allowNull: false,
|
||||
field: 'auto_update_ratings'
|
||||
},
|
||||
accessToken: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'access_token'
|
||||
},
|
||||
refreshToken: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'refresh_token'
|
||||
},
|
||||
expiresAt: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: true,
|
||||
field: 'expires_at'
|
||||
},
|
||||
cookie: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
},
|
||||
userData: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
field: 'user_data'
|
||||
},
|
||||
clubId: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
field: 'club_id'
|
||||
},
|
||||
clubName: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
field: 'club_name'
|
||||
},
|
||||
fedNickname: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
field: 'fed_nickname'
|
||||
},
|
||||
lastLoginAttempt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
field: 'last_login_attempt'
|
||||
},
|
||||
lastLoginSuccess: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
field: 'last_login_success'
|
||||
},
|
||||
lastUpdateRatings: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
field: 'last_update_ratings'
|
||||
}
|
||||
}, {
|
||||
underscored: true,
|
||||
tableName: 'my_tischtennis',
|
||||
timestamps: true,
|
||||
hooks: {
|
||||
beforeSave: async (instance) => {
|
||||
// Wenn savePassword false ist, password auf null setzen
|
||||
if (!instance.savePassword) {
|
||||
instance.encryptedPassword = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Virtuelle Felder für password handling
|
||||
MyTischtennis.prototype.setPassword = function(password) {
|
||||
if (password && this.savePassword) {
|
||||
this.encryptedPassword = encryptData(password);
|
||||
} else {
|
||||
this.encryptedPassword = null;
|
||||
}
|
||||
};
|
||||
|
||||
MyTischtennis.prototype.getPassword = function() {
|
||||
if (this.encryptedPassword) {
|
||||
try {
|
||||
return decryptData(this.encryptedPassword);
|
||||
} catch (error) {
|
||||
console.error('Error decrypting myTischtennis password:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export default MyTischtennis;
|
||||
|
||||
72
backend/models/MyTischtennisFetchLog.js
Normal file
72
backend/models/MyTischtennisFetchLog.js
Normal file
@@ -0,0 +1,72 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
import User from './User.js';
|
||||
|
||||
const MyTischtennisFetchLog = sequelize.define('MyTischtennisFetchLog', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false,
|
||||
},
|
||||
userId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: User,
|
||||
key: 'id',
|
||||
},
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
},
|
||||
fetchType: {
|
||||
type: DataTypes.ENUM('ratings', 'match_results', 'league_table'),
|
||||
allowNull: false,
|
||||
comment: 'Type of data fetch: ratings, match_results, or league_table'
|
||||
},
|
||||
success: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
},
|
||||
message: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
},
|
||||
errorDetails: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
},
|
||||
recordsProcessed: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: 'Number of records processed (e.g., players updated, matches fetched)'
|
||||
},
|
||||
executionTime: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: 'Execution time in milliseconds'
|
||||
},
|
||||
isAutomatic: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
comment: 'Whether this was an automatic or manual fetch'
|
||||
},
|
||||
}, {
|
||||
underscored: true,
|
||||
tableName: 'my_tischtennis_fetch_log',
|
||||
timestamps: true,
|
||||
indexes: [
|
||||
{
|
||||
fields: ['user_id', 'fetch_type', 'created_at']
|
||||
},
|
||||
{
|
||||
fields: ['created_at']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
export default MyTischtennisFetchLog;
|
||||
|
||||
63
backend/models/MyTischtennisUpdateHistory.js
Normal file
63
backend/models/MyTischtennisUpdateHistory.js
Normal file
@@ -0,0 +1,63 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
|
||||
const MyTischtennisUpdateHistory = sequelize.define('MyTischtennisUpdateHistory', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false
|
||||
},
|
||||
userId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'user',
|
||||
key: 'id'
|
||||
},
|
||||
onDelete: 'CASCADE'
|
||||
},
|
||||
success: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false
|
||||
},
|
||||
message: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
},
|
||||
errorDetails: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'error_details'
|
||||
},
|
||||
updatedCount: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
defaultValue: 0,
|
||||
field: 'updated_count'
|
||||
},
|
||||
executionTime: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: 'Execution time in milliseconds',
|
||||
field: 'execution_time'
|
||||
}
|
||||
}, {
|
||||
underscored: true,
|
||||
tableName: 'my_tischtennis_update_history',
|
||||
timestamps: true,
|
||||
indexes: [
|
||||
{
|
||||
fields: ['user_id']
|
||||
},
|
||||
{
|
||||
fields: ['created_at']
|
||||
},
|
||||
{
|
||||
fields: ['success']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
export default MyTischtennisUpdateHistory;
|
||||
28
backend/models/OfficialCompetition.js
Normal file
28
backend/models/OfficialCompetition.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
|
||||
const OfficialCompetition = sequelize.define('OfficialCompetition', {
|
||||
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
|
||||
tournamentId: { type: DataTypes.INTEGER, allowNull: false },
|
||||
// Englische Attributnamen, gemappt auf bestehende DB-Spalten
|
||||
ageClassCompetition: { type: DataTypes.STRING, allowNull: true, field: 'age_class_competition' },
|
||||
performanceClass: { type: DataTypes.STRING, allowNull: true, field: 'performance_class' },
|
||||
startTime: { type: DataTypes.STRING, allowNull: true, field: 'start_time' },
|
||||
registrationDeadlineDate: { type: DataTypes.STRING, allowNull: true, field: 'registration_deadline_date' },
|
||||
registrationDeadlineOnline: { type: DataTypes.STRING, allowNull: true, field: 'registration_deadline_online' },
|
||||
cutoffDate: { type: DataTypes.STRING, allowNull: true, field: 'cutoff_date' },
|
||||
ttrRelevant: { type: DataTypes.STRING, allowNull: true },
|
||||
openTo: { type: DataTypes.STRING, allowNull: true, field: 'open_to' },
|
||||
preliminaryRound: { type: DataTypes.STRING, allowNull: true, field: 'preliminary_round' },
|
||||
finalRound: { type: DataTypes.STRING, allowNull: true, field: 'final_round' },
|
||||
maxParticipants: { type: DataTypes.STRING, allowNull: true, field: 'max_participants' },
|
||||
entryFee: { type: DataTypes.STRING, allowNull: true, field: 'entry_fee' },
|
||||
}, {
|
||||
tableName: 'official_competitions',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
});
|
||||
|
||||
export default OfficialCompetition;
|
||||
|
||||
|
||||
25
backend/models/OfficialCompetitionMember.js
Normal file
25
backend/models/OfficialCompetitionMember.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
|
||||
const OfficialCompetitionMember = sequelize.define('OfficialCompetitionMember', {
|
||||
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
|
||||
tournamentId: { type: DataTypes.INTEGER, allowNull: false },
|
||||
competitionId: { type: DataTypes.INTEGER, allowNull: false },
|
||||
memberId: { type: DataTypes.INTEGER, allowNull: false },
|
||||
wants: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
|
||||
registered: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
|
||||
participated: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
|
||||
placement: { type: DataTypes.STRING, allowNull: true },
|
||||
}, {
|
||||
tableName: 'official_competition_members',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
indexes: [
|
||||
{ unique: true, fields: ['competition_id', 'member_id'] },
|
||||
{ fields: ['tournament_id'] },
|
||||
],
|
||||
});
|
||||
|
||||
export default OfficialCompetitionMember;
|
||||
|
||||
|
||||
23
backend/models/OfficialTournament.js
Normal file
23
backend/models/OfficialTournament.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
|
||||
const OfficialTournament = sequelize.define('OfficialTournament', {
|
||||
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
|
||||
clubId: { type: DataTypes.INTEGER, allowNull: false },
|
||||
title: { type: DataTypes.STRING, allowNull: true },
|
||||
eventDate: { type: DataTypes.STRING, allowNull: true },
|
||||
organizer: { type: DataTypes.STRING, allowNull: true },
|
||||
host: { type: DataTypes.STRING, allowNull: true },
|
||||
venues: { type: DataTypes.TEXT, allowNull: true }, // JSON.stringify(Array)
|
||||
competitionTypes: { type: DataTypes.TEXT, allowNull: true }, // JSON.stringify(Array)
|
||||
registrationDeadlines: { type: DataTypes.TEXT, allowNull: true }, // JSON.stringify(Array)
|
||||
entryFees: { type: DataTypes.TEXT, allowNull: true }, // JSON.stringify(Object) - Teilnahmegebühren pro Spielklasse
|
||||
}, {
|
||||
tableName: 'official_tournaments',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
});
|
||||
|
||||
export default OfficialTournament;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
import Member from './Member.js';
|
||||
import DiaryDate from './DiaryDates.js';
|
||||
import Group from './Group.js';
|
||||
import { encryptData, decryptData } from '../utils/encrypt.js';
|
||||
|
||||
const Participant = sequelize.define('Participant', {
|
||||
@@ -27,6 +28,16 @@ const Participant = sequelize.define('Participant', {
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
groupId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: Group,
|
||||
key: 'id'
|
||||
},
|
||||
onDelete: 'SET NULL',
|
||||
onUpdate: 'CASCADE'
|
||||
},
|
||||
notes: {
|
||||
type: DataTypes.STRING(4096),
|
||||
allowNull: true,
|
||||
|
||||
@@ -11,10 +11,19 @@ const PredefinedActivity = sequelize.define('PredefinedActivity', {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
code: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
},
|
||||
drawingData: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: 'JSON string with metadata for Court Drawing Tool'
|
||||
},
|
||||
durationText: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
@@ -23,6 +32,10 @@ const PredefinedActivity = sequelize.define('PredefinedActivity', {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
},
|
||||
imageLink: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
},
|
||||
}, {
|
||||
tableName: 'predefined_activities',
|
||||
timestamps: true,
|
||||
|
||||
35
backend/models/PredefinedActivityImage.js
Normal file
35
backend/models/PredefinedActivityImage.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
|
||||
const PredefinedActivityImage = sequelize.define('PredefinedActivityImage', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
},
|
||||
predefinedActivityId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
imagePath: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
mimeType: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
},
|
||||
drawingData: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: 'JSON string containing drawing metadata for Court Drawing Tool'
|
||||
},
|
||||
}, {
|
||||
tableName: 'predefined_activity_images',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
});
|
||||
|
||||
export default PredefinedActivityImage;
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
import Club from './Club.js';
|
||||
import League from './League.js';
|
||||
import Season from './Season.js';
|
||||
|
||||
const Team = sequelize.define('Team', {
|
||||
id: {
|
||||
@@ -23,6 +25,82 @@ const Team = sequelize.define('Team', {
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
},
|
||||
leagueId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: League,
|
||||
key: 'id',
|
||||
},
|
||||
onDelete: 'SET NULL',
|
||||
onUpdate: 'CASCADE',
|
||||
},
|
||||
seasonId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: Season,
|
||||
key: 'id',
|
||||
},
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
},
|
||||
// Tabellenfelder
|
||||
matchesPlayed: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
},
|
||||
matchesWon: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
},
|
||||
matchesLost: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
},
|
||||
matchesTied: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
},
|
||||
setsWon: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
},
|
||||
setsLost: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
},
|
||||
pointsWon: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
},
|
||||
pointsLost: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
},
|
||||
tablePoints: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
},
|
||||
tablePointsWon: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
},
|
||||
tablePointsLost: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
},
|
||||
}, {
|
||||
underscored: true,
|
||||
tableName: 'team',
|
||||
|
||||
52
backend/models/TeamDocument.js
Normal file
52
backend/models/TeamDocument.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
import ClubTeam from './ClubTeam.js';
|
||||
|
||||
const TeamDocument = sequelize.define('TeamDocument', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false,
|
||||
},
|
||||
fileName: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
originalFileName: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
filePath: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
fileSize: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
mimeType: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
documentType: {
|
||||
type: DataTypes.ENUM('code_list', 'pin_list'),
|
||||
allowNull: false,
|
||||
},
|
||||
clubTeamId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: ClubTeam,
|
||||
key: 'id',
|
||||
},
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
},
|
||||
}, {
|
||||
underscored: true,
|
||||
tableName: 'team_document',
|
||||
timestamps: true,
|
||||
});
|
||||
|
||||
export default TeamDocument;
|
||||
@@ -6,6 +6,7 @@ import Club from './Club.js';
|
||||
const UserClub = sequelize.define('UserClub', {
|
||||
userId: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
references: {
|
||||
model: User,
|
||||
key: 'id',
|
||||
@@ -13,6 +14,7 @@ const UserClub = sequelize.define('UserClub', {
|
||||
},
|
||||
clubId: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
references: {
|
||||
model: Club,
|
||||
key: 'id',
|
||||
@@ -22,6 +24,23 @@ const UserClub = sequelize.define('UserClub', {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false,
|
||||
},
|
||||
role: {
|
||||
type: DataTypes.STRING(50),
|
||||
defaultValue: 'member',
|
||||
allowNull: false,
|
||||
comment: 'User role: admin, trainer, team_manager, member'
|
||||
},
|
||||
permissions: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
comment: 'Specific permissions: {diary: {read: true, write: true}, members: {...}, ...}'
|
||||
},
|
||||
isOwner: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false,
|
||||
allowNull: false,
|
||||
comment: 'True if user created the club'
|
||||
}
|
||||
}, {
|
||||
underscored: true,
|
||||
tableName: 'user_club',
|
||||
|
||||
@@ -13,10 +13,14 @@ import DiaryDateTag from './DiaryDateTag.js';
|
||||
import DiaryMemberNote from './DiaryMemberNote.js';
|
||||
import DiaryMemberTag from './DiaryMemberTag.js';
|
||||
import PredefinedActivity from './PredefinedActivity.js';
|
||||
import DiaryMemberActivity from './DiaryMemberActivity.js';
|
||||
import PredefinedActivityImage from './PredefinedActivityImage.js';
|
||||
import DiaryDateActivity from './DiaryDateActivity.js';
|
||||
import Match from './Match.js';
|
||||
import League from './League.js';
|
||||
import Team from './Team.js';
|
||||
import ClubTeam from './ClubTeam.js';
|
||||
import TeamDocument from './TeamDocument.js';
|
||||
import Season from './Season.js';
|
||||
import Location from './Location.js';
|
||||
import Group from './Group.js';
|
||||
@@ -28,6 +32,26 @@ import TournamentMatch from './TournamentMatch.js';
|
||||
import TournamentResult from './TournamentResult.js';
|
||||
import Accident from './Accident.js';
|
||||
import UserToken from './UserToken.js';
|
||||
import OfficialTournament from './OfficialTournament.js';
|
||||
import OfficialCompetition from './OfficialCompetition.js';
|
||||
import OfficialCompetitionMember from './OfficialCompetitionMember.js';
|
||||
import MyTischtennis from './MyTischtennis.js';
|
||||
import MyTischtennisUpdateHistory from './MyTischtennisUpdateHistory.js';
|
||||
import MyTischtennisFetchLog from './MyTischtennisFetchLog.js';
|
||||
import ApiLog from './ApiLog.js';
|
||||
import MemberTransferConfig from './MemberTransferConfig.js';
|
||||
import MemberContact from './MemberContact.js';
|
||||
import MemberImage from './MemberImage.js';
|
||||
// Official tournaments relations
|
||||
OfficialTournament.hasMany(OfficialCompetition, { foreignKey: 'tournamentId', as: 'competitions' });
|
||||
OfficialCompetition.belongsTo(OfficialTournament, { foreignKey: 'tournamentId', as: 'tournament' });
|
||||
// Official competition participations
|
||||
OfficialCompetition.hasMany(OfficialCompetitionMember, { foreignKey: 'competitionId', as: 'members' });
|
||||
OfficialCompetitionMember.belongsTo(OfficialCompetition, { foreignKey: 'competitionId', as: 'competition' });
|
||||
OfficialTournament.hasMany(OfficialCompetitionMember, { foreignKey: 'tournamentId', as: 'competitionMembers' });
|
||||
OfficialCompetitionMember.belongsTo(OfficialTournament, { foreignKey: 'tournamentId', as: 'tournament' });
|
||||
Member.hasMany(OfficialCompetitionMember, { foreignKey: 'memberId', as: 'officialCompetitionEntries' });
|
||||
OfficialCompetitionMember.belongsTo(Member, { foreignKey: 'memberId', as: 'member' });
|
||||
|
||||
User.hasMany(Log, { foreignKey: 'userId' });
|
||||
Log.belongsTo(User, { foreignKey: 'userId' });
|
||||
@@ -76,6 +100,14 @@ DiaryDateActivity.belongsTo(DiaryDate, { foreignKey: 'diaryDateId', as: 'diaryDa
|
||||
|
||||
PredefinedActivity.hasMany(DiaryDateActivity, { foreignKey: 'predefinedActivityId', as: 'predefinedActivities' });
|
||||
DiaryDateActivity.belongsTo(PredefinedActivity, { foreignKey: 'predefinedActivityId', as: 'predefinedActivity' });
|
||||
// DiaryMemberActivity links a Participant to a DiaryDateActivity
|
||||
DiaryMemberActivity.belongsTo(DiaryDateActivity, { foreignKey: 'diaryDateActivityId', as: 'activity' });
|
||||
DiaryDateActivity.hasMany(DiaryMemberActivity, { foreignKey: 'diaryDateActivityId', as: 'activityMembers' });
|
||||
DiaryMemberActivity.belongsTo(Participant, { foreignKey: 'participantId', as: 'participant' });
|
||||
Participant.hasMany(DiaryMemberActivity, { foreignKey: 'participantId', as: 'memberActivities' });
|
||||
// PredefinedActivity Images
|
||||
PredefinedActivity.hasMany(PredefinedActivityImage, { foreignKey: 'predefinedActivityId', as: 'images' });
|
||||
PredefinedActivityImage.belongsTo(PredefinedActivity, { foreignKey: 'predefinedActivityId', as: 'predefinedActivity' });
|
||||
|
||||
Club.hasMany(Match, { foreignKey: 'clubId', as: 'matches' });
|
||||
Match.belongsTo(Club, { foreignKey: 'clubId', as: 'club' });
|
||||
@@ -94,8 +126,28 @@ Team.belongsTo(Club, { foreignKey: 'clubId', as: 'club' });
|
||||
Club.hasMany(League, { foreignKey: 'clubId', as: 'leagues' });
|
||||
League.belongsTo(Club, { foreignKey: 'clubId', as: 'club' });
|
||||
|
||||
Match.belongsTo(Season, { foreignKey: 'seasonId', as: 'season' });
|
||||
Season.hasMany(Match, { foreignKey: 'seasonId', as: 'matches' });
|
||||
Season.hasMany(League, { foreignKey: 'seasonId', as: 'leagues' });
|
||||
League.belongsTo(Season, { foreignKey: 'seasonId', as: 'season' });
|
||||
|
||||
League.hasMany(Team, { foreignKey: 'leagueId', as: 'teams' });
|
||||
Team.belongsTo(League, { foreignKey: 'leagueId', as: 'league' });
|
||||
|
||||
Season.hasMany(Team, { foreignKey: 'seasonId', as: 'teams' });
|
||||
Team.belongsTo(Season, { foreignKey: 'seasonId', as: 'season' });
|
||||
|
||||
// ClubTeam relationships
|
||||
Club.hasMany(ClubTeam, { foreignKey: 'clubId', as: 'clubTeams' });
|
||||
ClubTeam.belongsTo(Club, { foreignKey: 'clubId', as: 'club' });
|
||||
|
||||
League.hasMany(ClubTeam, { foreignKey: 'leagueId', as: 'clubTeams' });
|
||||
ClubTeam.belongsTo(League, { foreignKey: 'leagueId', as: 'league' });
|
||||
|
||||
Season.hasMany(ClubTeam, { foreignKey: 'seasonId', as: 'clubTeams' });
|
||||
ClubTeam.belongsTo(Season, { foreignKey: 'seasonId', as: 'season' });
|
||||
|
||||
// TeamDocument relationships
|
||||
ClubTeam.hasMany(TeamDocument, { foreignKey: 'clubTeamId', as: 'documents' });
|
||||
TeamDocument.belongsTo(ClubTeam, { foreignKey: 'clubTeamId', as: 'clubTeam' });
|
||||
|
||||
Match.belongsTo(Location, { foreignKey: 'locationId', as: 'location' });
|
||||
Location.hasMany(Match, { foreignKey: 'locationId', as: 'matches' });
|
||||
@@ -112,7 +164,7 @@ Club.hasMany(UserClub, { foreignKey: 'clubId' });
|
||||
Group.belongsTo(DiaryDate, { foreignKey: 'diaryDateId', as: 'diaryDateGroup' });
|
||||
DiaryDate.hasMany(Group, { foreignKey: 'diaryDateId', as: 'groupsDiaryDate' });
|
||||
|
||||
GroupActivity.belongsTo(DiaryDateActivity, { foreignKey: 'id', as: 'activityGroupActivity' });
|
||||
GroupActivity.belongsTo(DiaryDateActivity, { foreignKey: 'diaryDateActivity', as: 'activityGroupActivity' });
|
||||
DiaryDateActivity.hasMany(GroupActivity, { foreignKey: 'diaryDateActivity', as: 'groupActivities' });
|
||||
|
||||
Group.hasOne(GroupActivity, { foreignKey: 'groupId', as: 'groupGroupActivity' });
|
||||
@@ -181,6 +233,27 @@ Member.hasMany(Accident, { foreignKey: 'memberId', as: 'accidents' });
|
||||
Accident.belongsTo(DiaryDate, { foreignKey: 'diaryDateId', as: 'diaryDates' });
|
||||
DiaryDate.hasMany(Accident, { foreignKey: 'diaryDateId', as: 'accidents' });
|
||||
|
||||
User.hasOne(MyTischtennis, { foreignKey: 'userId', as: 'myTischtennis' });
|
||||
MyTischtennis.belongsTo(User, { foreignKey: 'userId', as: 'user' });
|
||||
|
||||
User.hasMany(MyTischtennisUpdateHistory, { foreignKey: 'userId', as: 'updateHistory' });
|
||||
MyTischtennisUpdateHistory.belongsTo(User, { foreignKey: 'userId', as: 'user' });
|
||||
|
||||
User.hasMany(MyTischtennisFetchLog, { foreignKey: 'userId', as: 'fetchLogs' });
|
||||
MyTischtennisFetchLog.belongsTo(User, { foreignKey: 'userId', as: 'user' });
|
||||
|
||||
User.hasMany(ApiLog, { foreignKey: 'userId', as: 'apiLogs' });
|
||||
ApiLog.belongsTo(User, { foreignKey: 'userId', as: 'user' });
|
||||
|
||||
Club.hasOne(MemberTransferConfig, { foreignKey: 'clubId', as: 'memberTransferConfig' });
|
||||
MemberTransferConfig.belongsTo(Club, { foreignKey: 'clubId', as: 'club' });
|
||||
|
||||
Member.hasMany(MemberContact, { foreignKey: 'memberId', as: 'contacts' });
|
||||
MemberContact.belongsTo(Member, { foreignKey: 'memberId', as: 'member' });
|
||||
|
||||
Member.hasMany(MemberImage, { foreignKey: 'memberId', as: 'images' });
|
||||
MemberImage.belongsTo(Member, { foreignKey: 'memberId', as: 'member' });
|
||||
|
||||
export {
|
||||
User,
|
||||
Log,
|
||||
@@ -198,10 +271,14 @@ export {
|
||||
DiaryMemberNote,
|
||||
DiaryMemberTag,
|
||||
PredefinedActivity,
|
||||
DiaryMemberActivity,
|
||||
PredefinedActivityImage,
|
||||
DiaryDateActivity,
|
||||
Match,
|
||||
League,
|
||||
Team,
|
||||
ClubTeam,
|
||||
TeamDocument,
|
||||
Group,
|
||||
GroupActivity,
|
||||
Tournament,
|
||||
@@ -211,4 +288,14 @@ export {
|
||||
TournamentResult,
|
||||
Accident,
|
||||
UserToken,
|
||||
OfficialTournament,
|
||||
OfficialCompetition,
|
||||
OfficialCompetitionMember,
|
||||
MyTischtennis,
|
||||
MyTischtennisUpdateHistory,
|
||||
MyTischtennisFetchLog,
|
||||
ApiLog,
|
||||
MemberTransferConfig,
|
||||
MemberContact,
|
||||
MemberImage,
|
||||
};
|
||||
|
||||
3204
backend/node_modules/.package-lock.json
generated
vendored
3204
backend/node_modules/.package-lock.json
generated
vendored
File diff suppressed because it is too large
Load Diff
4
backend/node_modules/@types/node/README.md
generated
vendored
4
backend/node_modules/@types/node/README.md
generated
vendored
@@ -8,8 +8,8 @@ This package contains type definitions for node (https://nodejs.org/).
|
||||
Files were exported from https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node.
|
||||
|
||||
### Additional Details
|
||||
* Last updated: Tue, 23 Jul 2024 18:09:25 GMT
|
||||
* Last updated: Mon, 03 Nov 2025 01:29:59 GMT
|
||||
* Dependencies: [undici-types](https://npmjs.com/package/undici-types)
|
||||
|
||||
# Credits
|
||||
These definitions were written by [Microsoft TypeScript](https://github.com/Microsoft), [Alberto Schiabel](https://github.com/jkomyno), [Alvis HT Tang](https://github.com/alvis), [Andrew Makarov](https://github.com/r3nya), [Benjamin Toueg](https://github.com/btoueg), [Chigozirim C.](https://github.com/smac89), [David Junger](https://github.com/touffy), [Deividas Bakanas](https://github.com/DeividasBakanas), [Eugene Y. Q. Shen](https://github.com/eyqs), [Hannes Magnusson](https://github.com/Hannes-Magnusson-CK), [Huw](https://github.com/hoo29), [Kelvin Jin](https://github.com/kjin), [Klaus Meinhardt](https://github.com/ajafff), [Lishude](https://github.com/islishude), [Mariusz Wiktorczyk](https://github.com/mwiktorczyk), [Mohsen Azimi](https://github.com/mohsen1), [Nikita Galkin](https://github.com/galkin), [Parambir Singh](https://github.com/parambirs), [Sebastian Silbermann](https://github.com/eps1lon), [Thomas den Hollander](https://github.com/ThomasdenH), [Wilco Bakker](https://github.com/WilcoBakker), [wwwy3y3](https://github.com/wwwy3y3), [Samuel Ainsworth](https://github.com/samuela), [Kyle Uehlein](https://github.com/kuehlein), [Thanik Bhongbhibhat](https://github.com/bhongy), [Marcin Kopacz](https://github.com/chyzwar), [Trivikram Kamat](https://github.com/trivikr), [Junxiao Shi](https://github.com/yoursunny), [Ilia Baryshnikov](https://github.com/qwelias), [ExE Boss](https://github.com/ExE-Boss), [Piotr Błażejewicz](https://github.com/peterblazejewicz), [Anna Henningsen](https://github.com/addaleax), [Victor Perin](https://github.com/victorperin), [Yongsheng Zhang](https://github.com/ZYSzys), [NodeJS Contributors](https://github.com/NodeJS), [Linus Unnebäck](https://github.com/LinusU), [wafuwafu13](https://github.com/wafuwafu13), [Matteo Collina](https://github.com/mcollina), and [Dmitry Semigradsky](https://github.com/Semigradsky).
|
||||
These definitions were written by [Microsoft TypeScript](https://github.com/Microsoft), [Alberto Schiabel](https://github.com/jkomyno), [Andrew Makarov](https://github.com/r3nya), [Benjamin Toueg](https://github.com/btoueg), [David Junger](https://github.com/touffy), [Mohsen Azimi](https://github.com/mohsen1), [Nikita Galkin](https://github.com/galkin), [Sebastian Silbermann](https://github.com/eps1lon), [Wilco Bakker](https://github.com/WilcoBakker), [Marcin Kopacz](https://github.com/chyzwar), [Trivikram Kamat](https://github.com/trivikr), [Junxiao Shi](https://github.com/yoursunny), [Ilia Baryshnikov](https://github.com/qwelias), [ExE Boss](https://github.com/ExE-Boss), [Piotr Błażejewicz](https://github.com/peterblazejewicz), [Anna Henningsen](https://github.com/addaleax), [Victor Perin](https://github.com/victorperin), [NodeJS Contributors](https://github.com/NodeJS), [Linus Unnebäck](https://github.com/LinusU), [wafuwafu13](https://github.com/wafuwafu13), [Matteo Collina](https://github.com/mcollina), [Dmitry Semigradsky](https://github.com/Semigradsky), [René](https://github.com/Renegade334), and [Yagiz Nizipli](https://github.com/anonrig).
|
||||
|
||||
269
backend/node_modules/@types/node/assert.d.ts
generated
vendored
269
backend/node_modules/@types/node/assert.d.ts
generated
vendored
@@ -1,20 +1,166 @@
|
||||
/**
|
||||
* The `node:assert` module provides a set of assertion functions for verifying
|
||||
* invariants.
|
||||
* @see [source](https://github.com/nodejs/node/blob/v20.13.1/lib/assert.js)
|
||||
* @see [source](https://github.com/nodejs/node/blob/v24.x/lib/assert.js)
|
||||
*/
|
||||
declare module "assert" {
|
||||
import strict = require("assert/strict");
|
||||
/**
|
||||
* An alias of {@link ok}.
|
||||
* An alias of {@link assert.ok}.
|
||||
* @since v0.5.9
|
||||
* @param value The input that is checked for being truthy.
|
||||
*/
|
||||
function assert(value: unknown, message?: string | Error): asserts value;
|
||||
const kOptions: unique symbol;
|
||||
namespace assert {
|
||||
type AssertMethodNames =
|
||||
| "deepEqual"
|
||||
| "deepStrictEqual"
|
||||
| "doesNotMatch"
|
||||
| "doesNotReject"
|
||||
| "doesNotThrow"
|
||||
| "equal"
|
||||
| "fail"
|
||||
| "ifError"
|
||||
| "match"
|
||||
| "notDeepEqual"
|
||||
| "notDeepStrictEqual"
|
||||
| "notEqual"
|
||||
| "notStrictEqual"
|
||||
| "ok"
|
||||
| "partialDeepStrictEqual"
|
||||
| "rejects"
|
||||
| "strictEqual"
|
||||
| "throws";
|
||||
interface AssertOptions {
|
||||
/**
|
||||
* If set to `'full'`, shows the full diff in assertion errors.
|
||||
* @default 'simple'
|
||||
*/
|
||||
diff?: "simple" | "full" | undefined;
|
||||
/**
|
||||
* If set to `true`, non-strict methods behave like their
|
||||
* corresponding strict methods.
|
||||
* @default true
|
||||
*/
|
||||
strict?: boolean | undefined;
|
||||
/**
|
||||
* If set to `true`, skips prototype and constructor
|
||||
* comparison in deep equality checks.
|
||||
* @since v24.9.0
|
||||
* @default false
|
||||
*/
|
||||
skipPrototype?: boolean | undefined;
|
||||
}
|
||||
interface Assert extends Pick<typeof assert, AssertMethodNames> {
|
||||
readonly [kOptions]: AssertOptions & { strict: false };
|
||||
}
|
||||
interface AssertStrict extends Pick<typeof strict, AssertMethodNames> {
|
||||
readonly [kOptions]: AssertOptions & { strict: true };
|
||||
}
|
||||
/**
|
||||
* The `Assert` class allows creating independent assertion instances with custom options.
|
||||
* @since v24.6.0
|
||||
*/
|
||||
var Assert: {
|
||||
/**
|
||||
* Creates a new assertion instance. The `diff` option controls the verbosity of diffs in assertion error messages.
|
||||
*
|
||||
* ```js
|
||||
* const { Assert } = require('node:assert');
|
||||
* const assertInstance = new Assert({ diff: 'full' });
|
||||
* assertInstance.deepStrictEqual({ a: 1 }, { a: 2 });
|
||||
* // Shows a full diff in the error message.
|
||||
* ```
|
||||
*
|
||||
* **Important**: When destructuring assertion methods from an `Assert` instance,
|
||||
* the methods lose their connection to the instance's configuration options (such
|
||||
* as `diff`, `strict`, and `skipPrototype` settings).
|
||||
* The destructured methods will fall back to default behavior instead.
|
||||
*
|
||||
* ```js
|
||||
* const myAssert = new Assert({ diff: 'full' });
|
||||
*
|
||||
* // This works as expected - uses 'full' diff
|
||||
* myAssert.strictEqual({ a: 1 }, { b: { c: 1 } });
|
||||
*
|
||||
* // This loses the 'full' diff setting - falls back to default 'simple' diff
|
||||
* const { strictEqual } = myAssert;
|
||||
* strictEqual({ a: 1 }, { b: { c: 1 } });
|
||||
* ```
|
||||
*
|
||||
* The `skipPrototype` option affects all deep equality methods:
|
||||
*
|
||||
* ```js
|
||||
* class Foo {
|
||||
* constructor(a) {
|
||||
* this.a = a;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* class Bar {
|
||||
* constructor(a) {
|
||||
* this.a = a;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* const foo = new Foo(1);
|
||||
* const bar = new Bar(1);
|
||||
*
|
||||
* // Default behavior - fails due to different constructors
|
||||
* const assert1 = new Assert();
|
||||
* assert1.deepStrictEqual(foo, bar); // AssertionError
|
||||
*
|
||||
* // Skip prototype comparison - passes if properties are equal
|
||||
* const assert2 = new Assert({ skipPrototype: true });
|
||||
* assert2.deepStrictEqual(foo, bar); // OK
|
||||
* ```
|
||||
*
|
||||
* When destructured, methods lose access to the instance's `this` context and revert to default assertion behavior
|
||||
* (diff: 'simple', non-strict mode).
|
||||
* To maintain custom options when using destructured methods, avoid
|
||||
* destructuring and call methods directly on the instance.
|
||||
* @since v24.6.0
|
||||
*/
|
||||
new(
|
||||
options?: AssertOptions & { strict?: true | undefined },
|
||||
): AssertStrict;
|
||||
new(
|
||||
options: AssertOptions,
|
||||
): Assert;
|
||||
};
|
||||
interface AssertionErrorOptions {
|
||||
/**
|
||||
* If provided, the error message is set to this value.
|
||||
*/
|
||||
message?: string | undefined;
|
||||
/**
|
||||
* The `actual` property on the error instance.
|
||||
*/
|
||||
actual?: unknown;
|
||||
/**
|
||||
* The `expected` property on the error instance.
|
||||
*/
|
||||
expected?: unknown;
|
||||
/**
|
||||
* The `operator` property on the error instance.
|
||||
*/
|
||||
operator?: string | undefined;
|
||||
/**
|
||||
* If provided, the generated stack trace omits frames before this function.
|
||||
*/
|
||||
stackStartFn?: Function | undefined;
|
||||
/**
|
||||
* If set to `'full'`, shows the full diff in assertion errors.
|
||||
* @default 'simple'
|
||||
*/
|
||||
diff?: "simple" | "full" | undefined;
|
||||
}
|
||||
/**
|
||||
* Indicates the failure of an assertion. All errors thrown by the `node:assert` module will be instances of the `AssertionError` class.
|
||||
*/
|
||||
class AssertionError extends Error {
|
||||
constructor(options: AssertionErrorOptions);
|
||||
/**
|
||||
* Set to the `actual` argument for methods such as {@link assert.strictEqual()}.
|
||||
*/
|
||||
@@ -23,10 +169,6 @@ declare module "assert" {
|
||||
* Set to the `expected` argument for methods such as {@link assert.strictEqual()}.
|
||||
*/
|
||||
expected: unknown;
|
||||
/**
|
||||
* Set to the passed in operator value.
|
||||
*/
|
||||
operator: string;
|
||||
/**
|
||||
* Indicates if the message was auto-generated (`true`) or not.
|
||||
*/
|
||||
@@ -35,19 +177,10 @@ declare module "assert" {
|
||||
* Value is always `ERR_ASSERTION` to show that the error is an assertion error.
|
||||
*/
|
||||
code: "ERR_ASSERTION";
|
||||
constructor(options?: {
|
||||
/** If provided, the error message is set to this value. */
|
||||
message?: string | undefined;
|
||||
/** The `actual` property on the error instance. */
|
||||
actual?: unknown | undefined;
|
||||
/** The `expected` property on the error instance. */
|
||||
expected?: unknown | undefined;
|
||||
/** The `operator` property on the error instance. */
|
||||
operator?: string | undefined;
|
||||
/** If provided, the generated stack trace omits frames before this function. */
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
stackStartFn?: Function | undefined;
|
||||
});
|
||||
/**
|
||||
* Set to the passed in operator value.
|
||||
*/
|
||||
operator: string;
|
||||
}
|
||||
/**
|
||||
* This feature is deprecated and will be removed in a future version.
|
||||
@@ -79,7 +212,9 @@ declare module "assert" {
|
||||
* @return A function that wraps `fn`.
|
||||
*/
|
||||
calls(exact?: number): () => void;
|
||||
calls<Func extends (...args: any[]) => any>(fn?: Func, exact?: number): Func;
|
||||
calls(fn: undefined, exact?: number): () => void;
|
||||
calls<Func extends (...args: any[]) => any>(fn: Func, exact?: number): Func;
|
||||
calls<Func extends (...args: any[]) => any>(fn?: Func, exact?: number): Func | (() => void);
|
||||
/**
|
||||
* Example:
|
||||
*
|
||||
@@ -226,7 +361,7 @@ declare module "assert" {
|
||||
expected: unknown,
|
||||
message?: string | Error,
|
||||
operator?: string,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||
stackStartFn?: Function,
|
||||
): never;
|
||||
/**
|
||||
@@ -796,7 +931,7 @@ declare module "assert" {
|
||||
* check that the promise is rejected.
|
||||
*
|
||||
* If `asyncFn` is a function and it throws an error synchronously, `assert.rejects()` will return a rejected `Promise` with that error. If the
|
||||
* function does not return a promise, `assert.rejects()` will return a rejected `Promise` with an [ERR_INVALID_RETURN_VALUE](https://nodejs.org/docs/latest-v20.x/api/errors.html#err_invalid_return_value)
|
||||
* function does not return a promise, `assert.rejects()` will return a rejected `Promise` with an [ERR_INVALID_RETURN_VALUE](https://nodejs.org/docs/latest-v24.x/api/errors.html#err_invalid_return_value)
|
||||
* error. In both cases the error handler is skipped.
|
||||
*
|
||||
* Besides the async nature to await the completion behaves identically to {@link throws}.
|
||||
@@ -866,7 +1001,7 @@ declare module "assert" {
|
||||
*
|
||||
* If `asyncFn` is a function and it throws an error synchronously, `assert.doesNotReject()` will return a rejected `Promise` with that error. If
|
||||
* the function does not return a promise, `assert.doesNotReject()` will return a
|
||||
* rejected `Promise` with an [ERR_INVALID_RETURN_VALUE](https://nodejs.org/docs/latest-v20.x/api/errors.html#err_invalid_return_value) error. In both cases
|
||||
* rejected `Promise` with an [ERR_INVALID_RETURN_VALUE](https://nodejs.org/docs/latest-v24.x/api/errors.html#err_invalid_return_value) error. In both cases
|
||||
* the error handler is skipped.
|
||||
*
|
||||
* Using `assert.doesNotReject()` is actually not useful because there is little
|
||||
@@ -929,7 +1064,7 @@ declare module "assert" {
|
||||
* If the values do not match, or if the `string` argument is of another type than `string`, an `{@link AssertionError}` is thrown with a `message` property set equal
|
||||
* to the value of the `message` parameter. If the `message` parameter is
|
||||
* undefined, a default error message is assigned. If the `message` parameter is an
|
||||
* instance of an [Error](https://nodejs.org/docs/latest-v20.x/api/errors.html#class-error) then it will be thrown instead of the `{@link AssertionError}`.
|
||||
* instance of an [Error](https://nodejs.org/docs/latest-v24.x/api/errors.html#class-error) then it will be thrown instead of the `{@link AssertionError}`.
|
||||
* @since v13.6.0, v12.16.0
|
||||
*/
|
||||
function match(value: string, regExp: RegExp, message?: string | Error): void;
|
||||
@@ -952,85 +1087,25 @@ declare module "assert" {
|
||||
* If the values do match, or if the `string` argument is of another type than `string`, an `{@link AssertionError}` is thrown with a `message` property set equal
|
||||
* to the value of the `message` parameter. If the `message` parameter is
|
||||
* undefined, a default error message is assigned. If the `message` parameter is an
|
||||
* instance of an [Error](https://nodejs.org/docs/latest-v20.x/api/errors.html#class-error) then it will be thrown instead of the `{@link AssertionError}`.
|
||||
* instance of an [Error](https://nodejs.org/docs/latest-v24.x/api/errors.html#class-error) then it will be thrown instead of the `{@link AssertionError}`.
|
||||
* @since v13.6.0, v12.16.0
|
||||
*/
|
||||
function doesNotMatch(value: string, regExp: RegExp, message?: string | Error): void;
|
||||
/**
|
||||
* In strict assertion mode, non-strict methods behave like their corresponding strict methods. For example,
|
||||
* {@link deepEqual} will behave like {@link deepStrictEqual}.
|
||||
* Tests for partial deep equality between the `actual` and `expected` parameters.
|
||||
* "Deep" equality means that the enumerable "own" properties of child objects
|
||||
* are recursively evaluated also by the following rules. "Partial" equality means
|
||||
* that only properties that exist on the `expected` parameter are going to be
|
||||
* compared.
|
||||
*
|
||||
* In strict assertion mode, error messages for objects display a diff. In legacy assertion mode, error
|
||||
* messages for objects display the objects, often truncated.
|
||||
*
|
||||
* To use strict assertion mode:
|
||||
*
|
||||
* ```js
|
||||
* import { strict as assert } from 'node:assert';COPY
|
||||
* import assert from 'node:assert/strict';
|
||||
* ```
|
||||
*
|
||||
* Example error diff:
|
||||
*
|
||||
* ```js
|
||||
* import { strict as assert } from 'node:assert';
|
||||
*
|
||||
* assert.deepEqual([[[1, 2, 3]], 4, 5], [[[1, 2, '3']], 4, 5]);
|
||||
* // AssertionError: Expected inputs to be strictly deep-equal:
|
||||
* // + actual - expected ... Lines skipped
|
||||
* //
|
||||
* // [
|
||||
* // [
|
||||
* // ...
|
||||
* // 2,
|
||||
* // + 3
|
||||
* // - '3'
|
||||
* // ],
|
||||
* // ...
|
||||
* // 5
|
||||
* // ]
|
||||
* ```
|
||||
*
|
||||
* To deactivate the colors, use the `NO_COLOR` or `NODE_DISABLE_COLORS` environment variables. This will also
|
||||
* deactivate the colors in the REPL. For more on color support in terminal environments, read the tty
|
||||
* `getColorDepth()` documentation.
|
||||
*
|
||||
* @since v15.0.0, v13.9.0, v12.16.2, v9.9.0
|
||||
* This method always passes the same test cases as `assert.deepStrictEqual()`,
|
||||
* behaving as a super set of it.
|
||||
* @since v22.13.0
|
||||
*/
|
||||
namespace strict {
|
||||
type AssertionError = assert.AssertionError;
|
||||
type AssertPredicate = assert.AssertPredicate;
|
||||
type CallTrackerCall = assert.CallTrackerCall;
|
||||
type CallTrackerReportInformation = assert.CallTrackerReportInformation;
|
||||
}
|
||||
const strict:
|
||||
& Omit<
|
||||
typeof assert,
|
||||
| "equal"
|
||||
| "notEqual"
|
||||
| "deepEqual"
|
||||
| "notDeepEqual"
|
||||
| "ok"
|
||||
| "strictEqual"
|
||||
| "deepStrictEqual"
|
||||
| "ifError"
|
||||
| "strict"
|
||||
>
|
||||
& {
|
||||
(value: unknown, message?: string | Error): asserts value;
|
||||
equal: typeof strictEqual;
|
||||
notEqual: typeof notStrictEqual;
|
||||
deepEqual: typeof deepStrictEqual;
|
||||
notDeepEqual: typeof notDeepStrictEqual;
|
||||
// Mapped types and assertion functions are incompatible?
|
||||
// TS2775: Assertions require every name in the call target
|
||||
// to be declared with an explicit type annotation.
|
||||
ok: typeof ok;
|
||||
strictEqual: typeof strictEqual;
|
||||
deepStrictEqual: typeof deepStrictEqual;
|
||||
ifError: typeof ifError;
|
||||
strict: typeof strict;
|
||||
};
|
||||
function partialDeepStrictEqual(actual: unknown, expected: unknown, message?: string | Error): void;
|
||||
}
|
||||
namespace assert {
|
||||
export { strict };
|
||||
}
|
||||
export = assert;
|
||||
}
|
||||
|
||||
107
backend/node_modules/@types/node/assert/strict.d.ts
generated
vendored
107
backend/node_modules/@types/node/assert/strict.d.ts
generated
vendored
@@ -1,8 +1,111 @@
|
||||
/**
|
||||
* In strict assertion mode, non-strict methods behave like their corresponding
|
||||
* strict methods. For example, `assert.deepEqual()` will behave like
|
||||
* `assert.deepStrictEqual()`.
|
||||
*
|
||||
* In strict assertion mode, error messages for objects display a diff. In legacy
|
||||
* assertion mode, error messages for objects display the objects, often truncated.
|
||||
*
|
||||
* To use strict assertion mode:
|
||||
*
|
||||
* ```js
|
||||
* import { strict as assert } from 'node:assert';
|
||||
* ```
|
||||
*
|
||||
* ```js
|
||||
* import assert from 'node:assert/strict';
|
||||
* ```
|
||||
*
|
||||
* Example error diff:
|
||||
*
|
||||
* ```js
|
||||
* import { strict as assert } from 'node:assert';
|
||||
*
|
||||
* assert.deepEqual([[[1, 2, 3]], 4, 5], [[[1, 2, '3']], 4, 5]);
|
||||
* // AssertionError: Expected inputs to be strictly deep-equal:
|
||||
* // + actual - expected ... Lines skipped
|
||||
* //
|
||||
* // [
|
||||
* // [
|
||||
* // ...
|
||||
* // 2,
|
||||
* // + 3
|
||||
* // - '3'
|
||||
* // ],
|
||||
* // ...
|
||||
* // 5
|
||||
* // ]
|
||||
* ```
|
||||
*
|
||||
* To deactivate the colors, use the `NO_COLOR` or `NODE_DISABLE_COLORS`
|
||||
* environment variables. This will also deactivate the colors in the REPL. For
|
||||
* more on color support in terminal environments, read the tty
|
||||
* [`getColorDepth()`](https://nodejs.org/docs/latest-v24.x/api/tty.html#writestreamgetcolordepthenv) documentation.
|
||||
* @since v15.0.0
|
||||
* @see [source](https://github.com/nodejs/node/blob/v24.x/lib/assert/strict.js)
|
||||
*/
|
||||
declare module "assert/strict" {
|
||||
import { strict } from "node:assert";
|
||||
import {
|
||||
Assert,
|
||||
AssertionError,
|
||||
AssertionErrorOptions,
|
||||
AssertOptions,
|
||||
AssertPredicate,
|
||||
AssertStrict,
|
||||
CallTracker,
|
||||
CallTrackerCall,
|
||||
CallTrackerReportInformation,
|
||||
deepStrictEqual,
|
||||
doesNotMatch,
|
||||
doesNotReject,
|
||||
doesNotThrow,
|
||||
fail,
|
||||
ifError,
|
||||
match,
|
||||
notDeepStrictEqual,
|
||||
notStrictEqual,
|
||||
ok,
|
||||
partialDeepStrictEqual,
|
||||
rejects,
|
||||
strictEqual,
|
||||
throws,
|
||||
} from "node:assert";
|
||||
function strict(value: unknown, message?: string | Error): asserts value;
|
||||
namespace strict {
|
||||
export {
|
||||
Assert,
|
||||
AssertionError,
|
||||
AssertionErrorOptions,
|
||||
AssertOptions,
|
||||
AssertPredicate,
|
||||
AssertStrict,
|
||||
CallTracker,
|
||||
CallTrackerCall,
|
||||
CallTrackerReportInformation,
|
||||
deepStrictEqual,
|
||||
deepStrictEqual as deepEqual,
|
||||
doesNotMatch,
|
||||
doesNotReject,
|
||||
doesNotThrow,
|
||||
fail,
|
||||
ifError,
|
||||
match,
|
||||
notDeepStrictEqual,
|
||||
notDeepStrictEqual as notDeepEqual,
|
||||
notStrictEqual,
|
||||
notStrictEqual as notEqual,
|
||||
ok,
|
||||
partialDeepStrictEqual,
|
||||
rejects,
|
||||
strict,
|
||||
strictEqual,
|
||||
strictEqual as equal,
|
||||
throws,
|
||||
};
|
||||
}
|
||||
export = strict;
|
||||
}
|
||||
declare module "node:assert/strict" {
|
||||
import { strict } from "node:assert";
|
||||
import strict = require("assert/strict");
|
||||
export = strict;
|
||||
}
|
||||
|
||||
100
backend/node_modules/@types/node/async_hooks.d.ts
generated
vendored
100
backend/node_modules/@types/node/async_hooks.d.ts
generated
vendored
@@ -2,8 +2,8 @@
|
||||
* We strongly discourage the use of the `async_hooks` API.
|
||||
* Other APIs that can cover most of its use cases include:
|
||||
*
|
||||
* * [`AsyncLocalStorage`](https://nodejs.org/docs/latest-v20.x/api/async_context.html#class-asynclocalstorage) tracks async context
|
||||
* * [`process.getActiveResourcesInfo()`](https://nodejs.org/docs/latest-v20.x/api/process.html#processgetactiveresourcesinfo) tracks active resources
|
||||
* * [`AsyncLocalStorage`](https://nodejs.org/docs/latest-v24.x/api/async_context.html#class-asynclocalstorage) tracks async context
|
||||
* * [`process.getActiveResourcesInfo()`](https://nodejs.org/docs/latest-v24.x/api/process.html#processgetactiveresourcesinfo) tracks active resources
|
||||
*
|
||||
* The `node:async_hooks` module provides an API to track asynchronous resources.
|
||||
* It can be accessed using:
|
||||
@@ -12,7 +12,7 @@
|
||||
* import async_hooks from 'node:async_hooks';
|
||||
* ```
|
||||
* @experimental
|
||||
* @see [source](https://github.com/nodejs/node/blob/v20.13.1/lib/async_hooks.js)
|
||||
* @see [source](https://github.com/nodejs/node/blob/v24.x/lib/async_hooks.js)
|
||||
*/
|
||||
declare module "async_hooks" {
|
||||
/**
|
||||
@@ -44,7 +44,7 @@ declare module "async_hooks" {
|
||||
* ```
|
||||
*
|
||||
* Promise contexts may not get precise `executionAsyncIds` by default.
|
||||
* See the section on [promise execution tracking](https://nodejs.org/docs/latest-v20.x/api/async_hooks.html#promise-execution-tracking).
|
||||
* See the section on [promise execution tracking](https://nodejs.org/docs/latest-v24.x/api/async_hooks.html#promise-execution-tracking).
|
||||
* @since v8.1.0
|
||||
* @return The `asyncId` of the current execution context. Useful to track when something calls.
|
||||
*/
|
||||
@@ -77,7 +77,7 @@ declare module "async_hooks" {
|
||||
* executionAsyncId,
|
||||
* executionAsyncResource,
|
||||
* createHook,
|
||||
* } from 'async_hooks';
|
||||
* } from 'node:async_hooks';
|
||||
* const sym = Symbol('state'); // Private symbol to avoid pollution
|
||||
*
|
||||
* createHook({
|
||||
@@ -117,7 +117,7 @@ declare module "async_hooks" {
|
||||
* ```
|
||||
*
|
||||
* Promise contexts may not get valid `triggerAsyncId`s by default. See
|
||||
* the section on [promise execution tracking](https://nodejs.org/docs/latest-v20.x/api/async_hooks.html#promise-execution-tracking).
|
||||
* the section on [promise execution tracking](https://nodejs.org/docs/latest-v24.x/api/async_hooks.html#promise-execution-tracking).
|
||||
* @return The ID of the resource responsible for calling the callback that is currently being executed.
|
||||
*/
|
||||
function triggerAsyncId(): number;
|
||||
@@ -320,6 +320,16 @@ declare module "async_hooks" {
|
||||
*/
|
||||
triggerAsyncId(): number;
|
||||
}
|
||||
interface AsyncLocalStorageOptions {
|
||||
/**
|
||||
* The default value to be used when no store is provided.
|
||||
*/
|
||||
defaultValue?: any;
|
||||
/**
|
||||
* A name for the `AsyncLocalStorage` value.
|
||||
*/
|
||||
name?: string | undefined;
|
||||
}
|
||||
/**
|
||||
* This class creates stores that stay coherent through asynchronous operations.
|
||||
*
|
||||
@@ -358,8 +368,8 @@ declare module "async_hooks" {
|
||||
* http.get('http://localhost:8080');
|
||||
* // Prints:
|
||||
* // 0: start
|
||||
* // 1: start
|
||||
* // 0: finish
|
||||
* // 1: start
|
||||
* // 1: finish
|
||||
* ```
|
||||
*
|
||||
@@ -369,10 +379,14 @@ declare module "async_hooks" {
|
||||
* @since v13.10.0, v12.17.0
|
||||
*/
|
||||
class AsyncLocalStorage<T> {
|
||||
/**
|
||||
* Creates a new instance of `AsyncLocalStorage`. Store is only provided within a
|
||||
* `run()` call or after an `enterWith()` call.
|
||||
*/
|
||||
constructor(options?: AsyncLocalStorageOptions);
|
||||
/**
|
||||
* Binds the given function to the current execution context.
|
||||
* @since v19.8.0
|
||||
* @experimental
|
||||
* @param fn The function to bind to the current execution context.
|
||||
* @return A new function that calls `fn` within the captured execution context.
|
||||
*/
|
||||
@@ -403,7 +417,6 @@ declare module "async_hooks" {
|
||||
* console.log(asyncLocalStorage.run(321, () => foo.get())); // returns 123
|
||||
* ```
|
||||
* @since v19.8.0
|
||||
* @experimental
|
||||
* @return A new function with the signature `(fn: (...args) : R, ...args) : R`.
|
||||
*/
|
||||
static snapshot(): <R, TArgs extends any[]>(fn: (...args: TArgs) => R, ...args: TArgs) => R;
|
||||
@@ -432,6 +445,11 @@ declare module "async_hooks" {
|
||||
* @since v13.10.0, v12.17.0
|
||||
*/
|
||||
getStore(): T | undefined;
|
||||
/**
|
||||
* The name of the `AsyncLocalStorage` instance if provided.
|
||||
* @since v24.0.0
|
||||
*/
|
||||
readonly name: string;
|
||||
/**
|
||||
* Runs a function synchronously within a context and returns its
|
||||
* return value. The store is not accessible outside of the callback function.
|
||||
@@ -535,6 +553,70 @@ declare module "async_hooks" {
|
||||
*/
|
||||
enterWith(store: T): void;
|
||||
}
|
||||
/**
|
||||
* @since v17.2.0, v16.14.0
|
||||
* @return A map of provider types to the corresponding numeric id.
|
||||
* This map contains all the event types that might be emitted by the `async_hooks.init()` event.
|
||||
*/
|
||||
namespace asyncWrapProviders {
|
||||
const NONE: number;
|
||||
const DIRHANDLE: number;
|
||||
const DNSCHANNEL: number;
|
||||
const ELDHISTOGRAM: number;
|
||||
const FILEHANDLE: number;
|
||||
const FILEHANDLECLOSEREQ: number;
|
||||
const FIXEDSIZEBLOBCOPY: number;
|
||||
const FSEVENTWRAP: number;
|
||||
const FSREQCALLBACK: number;
|
||||
const FSREQPROMISE: number;
|
||||
const GETADDRINFOREQWRAP: number;
|
||||
const GETNAMEINFOREQWRAP: number;
|
||||
const HEAPSNAPSHOT: number;
|
||||
const HTTP2SESSION: number;
|
||||
const HTTP2STREAM: number;
|
||||
const HTTP2PING: number;
|
||||
const HTTP2SETTINGS: number;
|
||||
const HTTPINCOMINGMESSAGE: number;
|
||||
const HTTPCLIENTREQUEST: number;
|
||||
const JSSTREAM: number;
|
||||
const JSUDPWRAP: number;
|
||||
const MESSAGEPORT: number;
|
||||
const PIPECONNECTWRAP: number;
|
||||
const PIPESERVERWRAP: number;
|
||||
const PIPEWRAP: number;
|
||||
const PROCESSWRAP: number;
|
||||
const PROMISE: number;
|
||||
const QUERYWRAP: number;
|
||||
const SHUTDOWNWRAP: number;
|
||||
const SIGNALWRAP: number;
|
||||
const STATWATCHER: number;
|
||||
const STREAMPIPE: number;
|
||||
const TCPCONNECTWRAP: number;
|
||||
const TCPSERVERWRAP: number;
|
||||
const TCPWRAP: number;
|
||||
const TTYWRAP: number;
|
||||
const UDPSENDWRAP: number;
|
||||
const UDPWRAP: number;
|
||||
const SIGINTWATCHDOG: number;
|
||||
const WORKER: number;
|
||||
const WORKERHEAPSNAPSHOT: number;
|
||||
const WRITEWRAP: number;
|
||||
const ZLIB: number;
|
||||
const CHECKPRIMEREQUEST: number;
|
||||
const PBKDF2REQUEST: number;
|
||||
const KEYPAIRGENREQUEST: number;
|
||||
const KEYGENREQUEST: number;
|
||||
const KEYEXPORTREQUEST: number;
|
||||
const CIPHERREQUEST: number;
|
||||
const DERIVEBITSREQUEST: number;
|
||||
const HASHREQUEST: number;
|
||||
const RANDOMBYTESREQUEST: number;
|
||||
const RANDOMPRIMEREQUEST: number;
|
||||
const SCRYPTREQUEST: number;
|
||||
const SIGNREQUEST: number;
|
||||
const TLSWRAP: number;
|
||||
const VERIFYREQUEST: number;
|
||||
}
|
||||
}
|
||||
declare module "node:async_hooks" {
|
||||
export * from "async_hooks";
|
||||
|
||||
470
backend/node_modules/@types/node/buffer.d.ts
generated
vendored
470
backend/node_modules/@types/node/buffer.d.ts
generated
vendored
@@ -1,3 +1,8 @@
|
||||
// If lib.dom.d.ts or lib.webworker.d.ts is loaded, then use the global types.
|
||||
// Otherwise, use the types from node.
|
||||
type _Blob = typeof globalThis extends { onmessage: any; Blob: any } ? {} : import("buffer").Blob;
|
||||
type _File = typeof globalThis extends { onmessage: any; File: any } ? {} : import("buffer").File;
|
||||
|
||||
/**
|
||||
* `Buffer` objects are used to represent a fixed-length sequence of bytes. Many
|
||||
* Node.js APIs support `Buffer`s.
|
||||
@@ -41,7 +46,7 @@
|
||||
* // Creates a Buffer containing the Latin-1 bytes [0x74, 0xe9, 0x73, 0x74].
|
||||
* const buf7 = Buffer.from('tést', 'latin1');
|
||||
* ```
|
||||
* @see [source](https://github.com/nodejs/node/blob/v20.13.1/lib/buffer.js)
|
||||
* @see [source](https://github.com/nodejs/node/blob/v24.x/lib/buffer.js)
|
||||
*/
|
||||
declare module "buffer" {
|
||||
import { BinaryLike } from "node:crypto";
|
||||
@@ -54,7 +59,7 @@ declare module "buffer" {
|
||||
* @since v19.4.0, v18.14.0
|
||||
* @param input The input to validate.
|
||||
*/
|
||||
export function isUtf8(input: Buffer | ArrayBuffer | NodeJS.TypedArray): boolean;
|
||||
export function isUtf8(input: ArrayBuffer | NodeJS.TypedArray): boolean;
|
||||
/**
|
||||
* This function returns `true` if `input` contains only valid ASCII-encoded data,
|
||||
* including the case in which `input` is empty.
|
||||
@@ -63,8 +68,8 @@ declare module "buffer" {
|
||||
* @since v19.6.0, v18.15.0
|
||||
* @param input The input to validate.
|
||||
*/
|
||||
export function isAscii(input: Buffer | ArrayBuffer | NodeJS.TypedArray): boolean;
|
||||
export const INSPECT_MAX_BYTES: number;
|
||||
export function isAscii(input: ArrayBuffer | NodeJS.TypedArray): boolean;
|
||||
export let INSPECT_MAX_BYTES: number;
|
||||
export const kMaxLength: number;
|
||||
export const kStringMaxLength: number;
|
||||
export const constants: {
|
||||
@@ -108,28 +113,26 @@ declare module "buffer" {
|
||||
* @param fromEnc The current encoding.
|
||||
* @param toEnc To target encoding.
|
||||
*/
|
||||
export function transcode(source: Uint8Array, fromEnc: TranscodeEncoding, toEnc: TranscodeEncoding): Buffer;
|
||||
export const SlowBuffer: {
|
||||
/** @deprecated since v6.0.0, use `Buffer.allocUnsafeSlow()` */
|
||||
new(size: number): Buffer;
|
||||
prototype: Buffer;
|
||||
};
|
||||
export function transcode(
|
||||
source: Uint8Array,
|
||||
fromEnc: TranscodeEncoding,
|
||||
toEnc: TranscodeEncoding,
|
||||
): NonSharedBuffer;
|
||||
/**
|
||||
* Resolves a `'blob:nodedata:...'` an associated `Blob` object registered using
|
||||
* a prior call to `URL.createObjectURL()`.
|
||||
* @since v16.7.0
|
||||
* @experimental
|
||||
* @param id A `'blob:nodedata:...` URL string returned by a prior call to `URL.createObjectURL()`.
|
||||
*/
|
||||
export function resolveObjectURL(id: string): Blob | undefined;
|
||||
export { Buffer };
|
||||
export { type AllowSharedBuffer, Buffer, type NonSharedBuffer };
|
||||
/**
|
||||
* @experimental
|
||||
*/
|
||||
export interface BlobOptions {
|
||||
/**
|
||||
* One of either `'transparent'` or `'native'`. When set to `'native'`, line endings in string source parts
|
||||
* will be converted to the platform native line-ending as specified by `require('node:os').EOL`.
|
||||
* will be converted to the platform native line-ending as specified by `import { EOL } from 'node:os'`.
|
||||
*/
|
||||
endings?: "transparent" | "native";
|
||||
/**
|
||||
@@ -140,7 +143,7 @@ declare module "buffer" {
|
||||
type?: string | undefined;
|
||||
}
|
||||
/**
|
||||
* A [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) encapsulates immutable, raw data that can be safely shared across
|
||||
* A `Blob` encapsulates immutable, raw data that can be safely shared across
|
||||
* multiple worker threads.
|
||||
* @since v15.7.0, v14.18.0
|
||||
*/
|
||||
@@ -170,6 +173,17 @@ declare module "buffer" {
|
||||
* @since v15.7.0, v14.18.0
|
||||
*/
|
||||
arrayBuffer(): Promise<ArrayBuffer>;
|
||||
/**
|
||||
* The `blob.bytes()` method returns the byte of the `Blob` object as a `Promise<Uint8Array>`.
|
||||
*
|
||||
* ```js
|
||||
* const blob = new Blob(['hello']);
|
||||
* blob.bytes().then((bytes) => {
|
||||
* console.log(bytes); // Outputs: Uint8Array(5) [ 104, 101, 108, 108, 111 ]
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
bytes(): Promise<Uint8Array>;
|
||||
/**
|
||||
* Creates and returns a new `Blob` containing a subset of this `Blob` objects
|
||||
* data. The original `Blob` is not altered.
|
||||
@@ -194,7 +208,7 @@ declare module "buffer" {
|
||||
export interface FileOptions {
|
||||
/**
|
||||
* One of either `'transparent'` or `'native'`. When set to `'native'`, line endings in string source parts will be
|
||||
* converted to the platform native line-ending as specified by `require('node:os').EOL`.
|
||||
* converted to the platform native line-ending as specified by `import { EOL } from 'node:os'`.
|
||||
*/
|
||||
endings?: "native" | "transparent";
|
||||
/** The File content-type. */
|
||||
@@ -221,10 +235,10 @@ declare module "buffer" {
|
||||
}
|
||||
export import atob = globalThis.atob;
|
||||
export import btoa = globalThis.btoa;
|
||||
import { Blob as NodeBlob } from "buffer";
|
||||
// This conditional type will be the existing global Blob in a browser, or
|
||||
// the copy below in a Node environment.
|
||||
type __Blob = typeof globalThis extends { onmessage: any; Blob: any } ? {} : NodeBlob;
|
||||
export type WithImplicitCoercion<T> =
|
||||
| T
|
||||
| { valueOf(): T }
|
||||
| (T extends string ? { [Symbol.toPrimitive](hint: "string"): T } : never);
|
||||
global {
|
||||
namespace NodeJS {
|
||||
export { BufferEncoding };
|
||||
@@ -243,111 +257,15 @@ declare module "buffer" {
|
||||
| "latin1"
|
||||
| "binary"
|
||||
| "hex";
|
||||
type WithImplicitCoercion<T> =
|
||||
| T
|
||||
| {
|
||||
valueOf(): T;
|
||||
};
|
||||
/**
|
||||
* Raw data is stored in instances of the Buffer class.
|
||||
* A Buffer is similar to an array of integers but corresponds to a raw memory allocation outside the V8 heap. A Buffer cannot be resized.
|
||||
* Valid string encodings: 'ascii'|'utf8'|'utf16le'|'ucs2'(alias of 'utf16le')|'base64'|'base64url'|'binary'(deprecated)|'hex'
|
||||
*/
|
||||
interface BufferConstructor {
|
||||
/**
|
||||
* Allocates a new buffer containing the given {str}.
|
||||
*
|
||||
* @param str String to store in buffer.
|
||||
* @param encoding encoding to use, optional. Default is 'utf8'
|
||||
* @deprecated since v10.0.0 - Use `Buffer.from(string[, encoding])` instead.
|
||||
*/
|
||||
new(str: string, encoding?: BufferEncoding): Buffer;
|
||||
/**
|
||||
* Allocates a new buffer of {size} octets.
|
||||
*
|
||||
* @param size count of octets to allocate.
|
||||
* @deprecated since v10.0.0 - Use `Buffer.alloc()` instead (also see `Buffer.allocUnsafe()`).
|
||||
*/
|
||||
new(size: number): Buffer;
|
||||
/**
|
||||
* Allocates a new buffer containing the given {array} of octets.
|
||||
*
|
||||
* @param array The octets to store.
|
||||
* @deprecated since v10.0.0 - Use `Buffer.from(array)` instead.
|
||||
*/
|
||||
new(array: Uint8Array): Buffer;
|
||||
/**
|
||||
* Produces a Buffer backed by the same allocated memory as
|
||||
* the given {ArrayBuffer}/{SharedArrayBuffer}.
|
||||
*
|
||||
* @param arrayBuffer The ArrayBuffer with which to share memory.
|
||||
* @deprecated since v10.0.0 - Use `Buffer.from(arrayBuffer[, byteOffset[, length]])` instead.
|
||||
*/
|
||||
new(arrayBuffer: ArrayBuffer | SharedArrayBuffer): Buffer;
|
||||
/**
|
||||
* Allocates a new buffer containing the given {array} of octets.
|
||||
*
|
||||
* @param array The octets to store.
|
||||
* @deprecated since v10.0.0 - Use `Buffer.from(array)` instead.
|
||||
*/
|
||||
new(array: readonly any[]): Buffer;
|
||||
/**
|
||||
* Copies the passed {buffer} data onto a new {Buffer} instance.
|
||||
*
|
||||
* @param buffer The buffer to copy.
|
||||
* @deprecated since v10.0.0 - Use `Buffer.from(buffer)` instead.
|
||||
*/
|
||||
new(buffer: Buffer): Buffer;
|
||||
/**
|
||||
* Allocates a new `Buffer` using an `array` of bytes in the range `0` – `255`.
|
||||
* Array entries outside that range will be truncated to fit into it.
|
||||
*
|
||||
* ```js
|
||||
* import { Buffer } from 'node:buffer';
|
||||
*
|
||||
* // Creates a new Buffer containing the UTF-8 bytes of the string 'buffer'.
|
||||
* const buf = Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]);
|
||||
* ```
|
||||
*
|
||||
* If `array` is an `Array`\-like object (that is, one with a `length` property of
|
||||
* type `number`), it is treated as if it is an array, unless it is a `Buffer` or
|
||||
* a `Uint8Array`. This means all other `TypedArray` variants get treated as an `Array`. To create a `Buffer` from the bytes backing a `TypedArray`, use `Buffer.copyBytesFrom()`.
|
||||
*
|
||||
* A `TypeError` will be thrown if `array` is not an `Array` or another type
|
||||
* appropriate for `Buffer.from()` variants.
|
||||
*
|
||||
* `Buffer.from(array)` and `Buffer.from(string)` may also use the internal `Buffer` pool like `Buffer.allocUnsafe()` does.
|
||||
* @since v5.10.0
|
||||
*/
|
||||
from(
|
||||
arrayBuffer: WithImplicitCoercion<ArrayBuffer | SharedArrayBuffer>,
|
||||
byteOffset?: number,
|
||||
length?: number,
|
||||
): Buffer;
|
||||
/**
|
||||
* Creates a new Buffer using the passed {data}
|
||||
* @param data data to create a new Buffer
|
||||
*/
|
||||
from(data: Uint8Array | readonly number[]): Buffer;
|
||||
from(data: WithImplicitCoercion<Uint8Array | readonly number[] | string>): Buffer;
|
||||
/**
|
||||
* Creates a new Buffer containing the given JavaScript string {str}.
|
||||
* If provided, the {encoding} parameter identifies the character encoding.
|
||||
* If not provided, {encoding} defaults to 'utf8'.
|
||||
*/
|
||||
from(
|
||||
str:
|
||||
| WithImplicitCoercion<string>
|
||||
| {
|
||||
[Symbol.toPrimitive](hint: "string"): string;
|
||||
},
|
||||
encoding?: BufferEncoding,
|
||||
): Buffer;
|
||||
/**
|
||||
* Creates a new Buffer using the passed {data}
|
||||
* @param values to create a new Buffer
|
||||
*/
|
||||
of(...items: number[]): Buffer;
|
||||
// see buffer.buffer.d.ts for implementation specific to TypeScript 5.7 and later
|
||||
// see ts5.6/buffer.buffer.d.ts for implementation specific to TypeScript 5.6 and earlier
|
||||
|
||||
/**
|
||||
* Returns `true` if `obj` is a `Buffer`, `false` otherwise.
|
||||
*
|
||||
@@ -416,65 +334,9 @@ declare module "buffer" {
|
||||
* @return The number of bytes contained within `string`.
|
||||
*/
|
||||
byteLength(
|
||||
string: string | Buffer | NodeJS.ArrayBufferView | ArrayBuffer | SharedArrayBuffer,
|
||||
string: string | NodeJS.ArrayBufferView | ArrayBufferLike,
|
||||
encoding?: BufferEncoding,
|
||||
): number;
|
||||
/**
|
||||
* Returns a new `Buffer` which is the result of concatenating all the `Buffer` instances in the `list` together.
|
||||
*
|
||||
* If the list has no items, or if the `totalLength` is 0, then a new zero-length `Buffer` is returned.
|
||||
*
|
||||
* If `totalLength` is not provided, it is calculated from the `Buffer` instances
|
||||
* in `list` by adding their lengths.
|
||||
*
|
||||
* If `totalLength` is provided, it is coerced to an unsigned integer. If the
|
||||
* combined length of the `Buffer`s in `list` exceeds `totalLength`, the result is
|
||||
* truncated to `totalLength`.
|
||||
*
|
||||
* ```js
|
||||
* import { Buffer } from 'node:buffer';
|
||||
*
|
||||
* // Create a single `Buffer` from a list of three `Buffer` instances.
|
||||
*
|
||||
* const buf1 = Buffer.alloc(10);
|
||||
* const buf2 = Buffer.alloc(14);
|
||||
* const buf3 = Buffer.alloc(18);
|
||||
* const totalLength = buf1.length + buf2.length + buf3.length;
|
||||
*
|
||||
* console.log(totalLength);
|
||||
* // Prints: 42
|
||||
*
|
||||
* const bufA = Buffer.concat([buf1, buf2, buf3], totalLength);
|
||||
*
|
||||
* console.log(bufA);
|
||||
* // Prints: <Buffer 00 00 00 00 ...>
|
||||
* console.log(bufA.length);
|
||||
* // Prints: 42
|
||||
* ```
|
||||
*
|
||||
* `Buffer.concat()` may also use the internal `Buffer` pool like `Buffer.allocUnsafe()` does.
|
||||
* @since v0.7.11
|
||||
* @param list List of `Buffer` or {@link Uint8Array} instances to concatenate.
|
||||
* @param totalLength Total length of the `Buffer` instances in `list` when concatenated.
|
||||
*/
|
||||
concat(list: readonly Uint8Array[], totalLength?: number): Buffer;
|
||||
/**
|
||||
* Copies the underlying memory of `view` into a new `Buffer`.
|
||||
*
|
||||
* ```js
|
||||
* const u16 = new Uint16Array([0, 0xffff]);
|
||||
* const buf = Buffer.copyBytesFrom(u16, 1, 1);
|
||||
* u16[1] = 0;
|
||||
* console.log(buf.length); // 2
|
||||
* console.log(buf[0]); // 255
|
||||
* console.log(buf[1]); // 255
|
||||
* ```
|
||||
* @since v19.8.0
|
||||
* @param view The {TypedArray} to copy.
|
||||
* @param [offset=0] The starting offset within `view`.
|
||||
* @param [length=view.length - offset] The number of elements from `view` to copy.
|
||||
*/
|
||||
copyBytesFrom(view: NodeJS.TypedArray, offset?: number, length?: number): Buffer;
|
||||
/**
|
||||
* Compares `buf1` to `buf2`, typically for the purpose of sorting arrays of `Buffer` instances. This is equivalent to calling `buf1.compare(buf2)`.
|
||||
*
|
||||
@@ -493,135 +355,6 @@ declare module "buffer" {
|
||||
* @return Either `-1`, `0`, or `1`, depending on the result of the comparison. See `compare` for details.
|
||||
*/
|
||||
compare(buf1: Uint8Array, buf2: Uint8Array): -1 | 0 | 1;
|
||||
/**
|
||||
* Allocates a new `Buffer` of `size` bytes. If `fill` is `undefined`, the`Buffer` will be zero-filled.
|
||||
*
|
||||
* ```js
|
||||
* import { Buffer } from 'node:buffer';
|
||||
*
|
||||
* const buf = Buffer.alloc(5);
|
||||
*
|
||||
* console.log(buf);
|
||||
* // Prints: <Buffer 00 00 00 00 00>
|
||||
* ```
|
||||
*
|
||||
* If `size` is larger than {@link constants.MAX_LENGTH} or smaller than 0, `ERR_OUT_OF_RANGE` is thrown.
|
||||
*
|
||||
* If `fill` is specified, the allocated `Buffer` will be initialized by calling `buf.fill(fill)`.
|
||||
*
|
||||
* ```js
|
||||
* import { Buffer } from 'node:buffer';
|
||||
*
|
||||
* const buf = Buffer.alloc(5, 'a');
|
||||
*
|
||||
* console.log(buf);
|
||||
* // Prints: <Buffer 61 61 61 61 61>
|
||||
* ```
|
||||
*
|
||||
* If both `fill` and `encoding` are specified, the allocated `Buffer` will be
|
||||
* initialized by calling `buf.fill(fill, encoding)`.
|
||||
*
|
||||
* ```js
|
||||
* import { Buffer } from 'node:buffer';
|
||||
*
|
||||
* const buf = Buffer.alloc(11, 'aGVsbG8gd29ybGQ=', 'base64');
|
||||
*
|
||||
* console.log(buf);
|
||||
* // Prints: <Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64>
|
||||
* ```
|
||||
*
|
||||
* Calling `Buffer.alloc()` can be measurably slower than the alternative `Buffer.allocUnsafe()` but ensures that the newly created `Buffer` instance
|
||||
* contents will never contain sensitive data from previous allocations, including
|
||||
* data that might not have been allocated for `Buffer`s.
|
||||
*
|
||||
* A `TypeError` will be thrown if `size` is not a number.
|
||||
* @since v5.10.0
|
||||
* @param size The desired length of the new `Buffer`.
|
||||
* @param [fill=0] A value to pre-fill the new `Buffer` with.
|
||||
* @param [encoding='utf8'] If `fill` is a string, this is its encoding.
|
||||
*/
|
||||
alloc(size: number, fill?: string | Uint8Array | number, encoding?: BufferEncoding): Buffer;
|
||||
/**
|
||||
* Allocates a new `Buffer` of `size` bytes. If `size` is larger than {@link constants.MAX_LENGTH} or smaller than 0, `ERR_OUT_OF_RANGE` is thrown.
|
||||
*
|
||||
* The underlying memory for `Buffer` instances created in this way is _not_
|
||||
* _initialized_. The contents of the newly created `Buffer` are unknown and _may contain sensitive data_. Use `Buffer.alloc()` instead to initialize`Buffer` instances with zeroes.
|
||||
*
|
||||
* ```js
|
||||
* import { Buffer } from 'node:buffer';
|
||||
*
|
||||
* const buf = Buffer.allocUnsafe(10);
|
||||
*
|
||||
* console.log(buf);
|
||||
* // Prints (contents may vary): <Buffer a0 8b 28 3f 01 00 00 00 50 32>
|
||||
*
|
||||
* buf.fill(0);
|
||||
*
|
||||
* console.log(buf);
|
||||
* // Prints: <Buffer 00 00 00 00 00 00 00 00 00 00>
|
||||
* ```
|
||||
*
|
||||
* A `TypeError` will be thrown if `size` is not a number.
|
||||
*
|
||||
* The `Buffer` module pre-allocates an internal `Buffer` instance of
|
||||
* size `Buffer.poolSize` that is used as a pool for the fast allocation of new `Buffer` instances created using `Buffer.allocUnsafe()`, `Buffer.from(array)`,
|
||||
* and `Buffer.concat()` only when `size` is less than `Buffer.poolSize >>> 1` (floor of `Buffer.poolSize` divided by two).
|
||||
*
|
||||
* Use of this pre-allocated internal memory pool is a key difference between
|
||||
* calling `Buffer.alloc(size, fill)` vs. `Buffer.allocUnsafe(size).fill(fill)`.
|
||||
* Specifically, `Buffer.alloc(size, fill)` will _never_ use the internal `Buffer`pool, while `Buffer.allocUnsafe(size).fill(fill)`_will_ use the internal`Buffer` pool if `size` is less
|
||||
* than or equal to half `Buffer.poolSize`. The
|
||||
* difference is subtle but can be important when an application requires the
|
||||
* additional performance that `Buffer.allocUnsafe()` provides.
|
||||
* @since v5.10.0
|
||||
* @param size The desired length of the new `Buffer`.
|
||||
*/
|
||||
allocUnsafe(size: number): Buffer;
|
||||
/**
|
||||
* Allocates a new `Buffer` of `size` bytes. If `size` is larger than {@link constants.MAX_LENGTH} or smaller than 0, `ERR_OUT_OF_RANGE` is thrown. A zero-length `Buffer` is created if
|
||||
* `size` is 0.
|
||||
*
|
||||
* The underlying memory for `Buffer` instances created in this way is _not_
|
||||
* _initialized_. The contents of the newly created `Buffer` are unknown and _may contain sensitive data_. Use `buf.fill(0)` to initialize
|
||||
* such `Buffer` instances with zeroes.
|
||||
*
|
||||
* When using `Buffer.allocUnsafe()` to allocate new `Buffer` instances,
|
||||
* allocations under 4 KiB are sliced from a single pre-allocated `Buffer`. This
|
||||
* allows applications to avoid the garbage collection overhead of creating many
|
||||
* individually allocated `Buffer` instances. This approach improves both
|
||||
* performance and memory usage by eliminating the need to track and clean up as
|
||||
* many individual `ArrayBuffer` objects.
|
||||
*
|
||||
* However, in the case where a developer may need to retain a small chunk of
|
||||
* memory from a pool for an indeterminate amount of time, it may be appropriate
|
||||
* to create an un-pooled `Buffer` instance using `Buffer.allocUnsafeSlow()` and
|
||||
* then copying out the relevant bits.
|
||||
*
|
||||
* ```js
|
||||
* import { Buffer } from 'node:buffer';
|
||||
*
|
||||
* // Need to keep around a few small chunks of memory.
|
||||
* const store = [];
|
||||
*
|
||||
* socket.on('readable', () => {
|
||||
* let data;
|
||||
* while (null !== (data = readable.read())) {
|
||||
* // Allocate for retained data.
|
||||
* const sb = Buffer.allocUnsafeSlow(10);
|
||||
*
|
||||
* // Copy the data into the new allocation.
|
||||
* data.copy(sb, 0, 0, 10);
|
||||
*
|
||||
* store.push(sb);
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* A `TypeError` will be thrown if `size` is not a number.
|
||||
* @since v5.12.0
|
||||
* @param size The desired length of the new `Buffer`.
|
||||
*/
|
||||
allocUnsafeSlow(size: number): Buffer;
|
||||
/**
|
||||
* This is the size (in bytes) of pre-allocated internal `Buffer` instances used
|
||||
* for pooling. This value may be modified.
|
||||
@@ -629,7 +362,10 @@ declare module "buffer" {
|
||||
*/
|
||||
poolSize: number;
|
||||
}
|
||||
interface Buffer extends Uint8Array {
|
||||
interface Buffer {
|
||||
// see buffer.buffer.d.ts for implementation specific to TypeScript 5.7 and later
|
||||
// see ts5.6/buffer.buffer.d.ts for implementation specific to TypeScript 5.6 and earlier
|
||||
|
||||
/**
|
||||
* Writes `string` to `buf` at `offset` according to the character encoding in`encoding`. The `length` parameter is the number of bytes to write. If `buf` did
|
||||
* not contain enough space to fit the entire string, only part of `string` will be
|
||||
@@ -866,100 +602,6 @@ declare module "buffer" {
|
||||
* @return The number of bytes copied.
|
||||
*/
|
||||
copy(target: Uint8Array, targetStart?: number, sourceStart?: number, sourceEnd?: number): number;
|
||||
/**
|
||||
* Returns a new `Buffer` that references the same memory as the original, but
|
||||
* offset and cropped by the `start` and `end` indices.
|
||||
*
|
||||
* This method is not compatible with the `Uint8Array.prototype.slice()`,
|
||||
* which is a superclass of `Buffer`. To copy the slice, use`Uint8Array.prototype.slice()`.
|
||||
*
|
||||
* ```js
|
||||
* import { Buffer } from 'node:buffer';
|
||||
*
|
||||
* const buf = Buffer.from('buffer');
|
||||
*
|
||||
* const copiedBuf = Uint8Array.prototype.slice.call(buf);
|
||||
* copiedBuf[0]++;
|
||||
* console.log(copiedBuf.toString());
|
||||
* // Prints: cuffer
|
||||
*
|
||||
* console.log(buf.toString());
|
||||
* // Prints: buffer
|
||||
*
|
||||
* // With buf.slice(), the original buffer is modified.
|
||||
* const notReallyCopiedBuf = buf.slice();
|
||||
* notReallyCopiedBuf[0]++;
|
||||
* console.log(notReallyCopiedBuf.toString());
|
||||
* // Prints: cuffer
|
||||
* console.log(buf.toString());
|
||||
* // Also prints: cuffer (!)
|
||||
* ```
|
||||
* @since v0.3.0
|
||||
* @deprecated Use `subarray` instead.
|
||||
* @param [start=0] Where the new `Buffer` will start.
|
||||
* @param [end=buf.length] Where the new `Buffer` will end (not inclusive).
|
||||
*/
|
||||
slice(start?: number, end?: number): Buffer;
|
||||
/**
|
||||
* Returns a new `Buffer` that references the same memory as the original, but
|
||||
* offset and cropped by the `start` and `end` indices.
|
||||
*
|
||||
* Specifying `end` greater than `buf.length` will return the same result as
|
||||
* that of `end` equal to `buf.length`.
|
||||
*
|
||||
* This method is inherited from [`TypedArray.prototype.subarray()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/subarray).
|
||||
*
|
||||
* Modifying the new `Buffer` slice will modify the memory in the original `Buffer`because the allocated memory of the two objects overlap.
|
||||
*
|
||||
* ```js
|
||||
* import { Buffer } from 'node:buffer';
|
||||
*
|
||||
* // Create a `Buffer` with the ASCII alphabet, take a slice, and modify one byte
|
||||
* // from the original `Buffer`.
|
||||
*
|
||||
* const buf1 = Buffer.allocUnsafe(26);
|
||||
*
|
||||
* for (let i = 0; i < 26; i++) {
|
||||
* // 97 is the decimal ASCII value for 'a'.
|
||||
* buf1[i] = i + 97;
|
||||
* }
|
||||
*
|
||||
* const buf2 = buf1.subarray(0, 3);
|
||||
*
|
||||
* console.log(buf2.toString('ascii', 0, buf2.length));
|
||||
* // Prints: abc
|
||||
*
|
||||
* buf1[0] = 33;
|
||||
*
|
||||
* console.log(buf2.toString('ascii', 0, buf2.length));
|
||||
* // Prints: !bc
|
||||
* ```
|
||||
*
|
||||
* Specifying negative indexes causes the slice to be generated relative to the
|
||||
* end of `buf` rather than the beginning.
|
||||
*
|
||||
* ```js
|
||||
* import { Buffer } from 'node:buffer';
|
||||
*
|
||||
* const buf = Buffer.from('buffer');
|
||||
*
|
||||
* console.log(buf.subarray(-6, -1).toString());
|
||||
* // Prints: buffe
|
||||
* // (Equivalent to buf.subarray(0, 5).)
|
||||
*
|
||||
* console.log(buf.subarray(-6, -2).toString());
|
||||
* // Prints: buff
|
||||
* // (Equivalent to buf.subarray(0, 4).)
|
||||
*
|
||||
* console.log(buf.subarray(-5, -2).toString());
|
||||
* // Prints: uff
|
||||
* // (Equivalent to buf.subarray(1, 4).)
|
||||
* ```
|
||||
* @since v3.0.0
|
||||
* @param [start=0] Where the new `Buffer` will start.
|
||||
* @param [end=buf.length] Where the new `Buffer` will end (not inclusive).
|
||||
*/
|
||||
subarray(start?: number, end?: number): Buffer;
|
||||
/**
|
||||
* Writes `value` to `buf` at the specified `offset` as big-endian.
|
||||
*
|
||||
@@ -1617,7 +1259,7 @@ declare module "buffer" {
|
||||
* @since v5.10.0
|
||||
* @return A reference to `buf`.
|
||||
*/
|
||||
swap16(): Buffer;
|
||||
swap16(): this;
|
||||
/**
|
||||
* Interprets `buf` as an array of unsigned 32-bit integers and swaps the
|
||||
* byte order _in-place_. Throws `ERR_INVALID_BUFFER_SIZE` if `buf.length` is not a multiple of 4.
|
||||
@@ -1643,7 +1285,7 @@ declare module "buffer" {
|
||||
* @since v5.10.0
|
||||
* @return A reference to `buf`.
|
||||
*/
|
||||
swap32(): Buffer;
|
||||
swap32(): this;
|
||||
/**
|
||||
* Interprets `buf` as an array of 64-bit numbers and swaps byte order _in-place_.
|
||||
* Throws `ERR_INVALID_BUFFER_SIZE` if `buf.length` is not a multiple of 8.
|
||||
@@ -1669,7 +1311,7 @@ declare module "buffer" {
|
||||
* @since v6.3.0
|
||||
* @return A reference to `buf`.
|
||||
*/
|
||||
swap64(): Buffer;
|
||||
swap64(): this;
|
||||
/**
|
||||
* Writes `value` to `buf` at the specified `offset`. `value` must be a
|
||||
* valid unsigned 8-bit integer. Behavior is undefined when `value` is anything
|
||||
@@ -2063,6 +1705,8 @@ declare module "buffer" {
|
||||
* @return A reference to `buf`.
|
||||
*/
|
||||
fill(value: string | Uint8Array | number, offset?: number, end?: number, encoding?: BufferEncoding): this;
|
||||
fill(value: string | Uint8Array | number, offset: number, encoding: BufferEncoding): this;
|
||||
fill(value: string | Uint8Array | number, encoding: BufferEncoding): this;
|
||||
/**
|
||||
* If `value` is:
|
||||
*
|
||||
@@ -2132,6 +1776,7 @@ declare module "buffer" {
|
||||
* @return The index of the first occurrence of `value` in `buf`, or `-1` if `buf` does not contain `value`.
|
||||
*/
|
||||
indexOf(value: string | number | Uint8Array, byteOffset?: number, encoding?: BufferEncoding): number;
|
||||
indexOf(value: string | number | Uint8Array, encoding: BufferEncoding): number;
|
||||
/**
|
||||
* Identical to `buf.indexOf()`, except the last occurrence of `value` is found
|
||||
* rather than the first occurrence.
|
||||
@@ -2200,6 +1845,7 @@ declare module "buffer" {
|
||||
* @return The index of the last occurrence of `value` in `buf`, or `-1` if `buf` does not contain `value`.
|
||||
*/
|
||||
lastIndexOf(value: string | number | Uint8Array, byteOffset?: number, encoding?: BufferEncoding): number;
|
||||
lastIndexOf(value: string | number | Uint8Array, encoding: BufferEncoding): number;
|
||||
/**
|
||||
* Equivalent to `buf.indexOf() !== -1`.
|
||||
*
|
||||
@@ -2230,6 +1876,7 @@ declare module "buffer" {
|
||||
* @return `true` if `value` was found in `buf`, `false` otherwise.
|
||||
*/
|
||||
includes(value: string | number | Buffer, byteOffset?: number, encoding?: BufferEncoding): boolean;
|
||||
includes(value: string | number | Buffer, encoding: BufferEncoding): boolean;
|
||||
}
|
||||
var Buffer: BufferConstructor;
|
||||
/**
|
||||
@@ -2264,17 +1911,22 @@ declare module "buffer" {
|
||||
* @param data An ASCII (Latin1) string.
|
||||
*/
|
||||
function btoa(data: string): string;
|
||||
interface Blob extends __Blob {}
|
||||
interface Blob extends _Blob {}
|
||||
/**
|
||||
* `Blob` class is a global reference for `require('node:buffer').Blob`
|
||||
* `Blob` class is a global reference for `import { Blob } from 'node:buffer'`
|
||||
* https://nodejs.org/api/buffer.html#class-blob
|
||||
* @since v18.0.0
|
||||
*/
|
||||
var Blob: typeof globalThis extends {
|
||||
onmessage: any;
|
||||
Blob: infer T;
|
||||
} ? T
|
||||
: typeof NodeBlob;
|
||||
var Blob: typeof globalThis extends { onmessage: any; Blob: infer T } ? T
|
||||
: typeof import("buffer").Blob;
|
||||
interface File extends _File {}
|
||||
/**
|
||||
* `File` class is a global reference for `import { File } from 'node:buffer'`
|
||||
* https://nodejs.org/api/buffer.html#class-file
|
||||
* @since v20.0.0
|
||||
*/
|
||||
var File: typeof globalThis extends { onmessage: any; File: infer T } ? T
|
||||
: typeof import("buffer").File;
|
||||
}
|
||||
}
|
||||
declare module "node:buffer" {
|
||||
|
||||
318
backend/node_modules/@types/node/child_process.d.ts
generated
vendored
318
backend/node_modules/@types/node/child_process.d.ts
generated
vendored
@@ -4,7 +4,7 @@
|
||||
* is primarily provided by the {@link spawn} function:
|
||||
*
|
||||
* ```js
|
||||
* const { spawn } = require('node:child_process');
|
||||
* import { spawn } from 'node:child_process';
|
||||
* const ls = spawn('ls', ['-lh', '/usr']);
|
||||
*
|
||||
* ls.stdout.on('data', (data) => {
|
||||
@@ -24,7 +24,7 @@
|
||||
* the parent Node.js process and the spawned subprocess. These pipes have
|
||||
* limited (and platform-specific) capacity. If the subprocess writes to
|
||||
* stdout in excess of that limit without the output being captured, the
|
||||
* subprocess blocks waiting for the pipe buffer to accept more data. This is
|
||||
* subprocess blocks, waiting for the pipe buffer to accept more data. This is
|
||||
* identical to the behavior of pipes in the shell. Use the `{ stdio: 'ignore' }` option if the output will not be consumed.
|
||||
*
|
||||
* The command lookup is performed using the `options.env.PATH` environment
|
||||
@@ -63,14 +63,14 @@
|
||||
* For certain use cases, such as automating shell scripts, the `synchronous counterparts` may be more convenient. In many cases, however,
|
||||
* the synchronous methods can have significant impact on performance due to
|
||||
* stalling the event loop while spawned processes complete.
|
||||
* @see [source](https://github.com/nodejs/node/blob/v20.13.1/lib/child_process.js)
|
||||
* @see [source](https://github.com/nodejs/node/blob/v24.x/lib/child_process.js)
|
||||
*/
|
||||
declare module "child_process" {
|
||||
import { ObjectEncodingOptions } from "node:fs";
|
||||
import { NonSharedBuffer } from "node:buffer";
|
||||
import { Abortable, EventEmitter } from "node:events";
|
||||
import * as dgram from "node:dgram";
|
||||
import * as net from "node:net";
|
||||
import { Pipe, Readable, Stream, Writable } from "node:stream";
|
||||
import { Readable, Stream, Writable } from "node:stream";
|
||||
import { URL } from "node:url";
|
||||
type Serializable = string | object | number | boolean | bigint;
|
||||
type SendHandle = net.Socket | net.Server | dgram.Socket | undefined;
|
||||
@@ -109,7 +109,7 @@ declare module "child_process" {
|
||||
* refer to the same value.
|
||||
*
|
||||
* ```js
|
||||
* const { spawn } = require('node:child_process');
|
||||
* import { spawn } from 'node:child_process';
|
||||
*
|
||||
* const subprocess = spawn('ls');
|
||||
*
|
||||
@@ -140,7 +140,7 @@ declare module "child_process" {
|
||||
* no IPC channel exists, this property is `undefined`.
|
||||
* @since v7.1.0
|
||||
*/
|
||||
readonly channel?: Pipe | null | undefined;
|
||||
readonly channel?: Control | null;
|
||||
/**
|
||||
* A sparse array of pipes to the child process, corresponding with positions in
|
||||
* the `stdio` option passed to {@link spawn} that have been set
|
||||
@@ -152,9 +152,9 @@ declare module "child_process" {
|
||||
* in the array are `null`.
|
||||
*
|
||||
* ```js
|
||||
* const assert = require('node:assert');
|
||||
* const fs = require('node:fs');
|
||||
* const child_process = require('node:child_process');
|
||||
* import assert from 'node:assert';
|
||||
* import fs from 'node:fs';
|
||||
* import child_process from 'node:child_process';
|
||||
*
|
||||
* const subprocess = child_process.spawn('ls', {
|
||||
* stdio: [
|
||||
@@ -202,7 +202,7 @@ declare module "child_process" {
|
||||
* emitted.
|
||||
*
|
||||
* ```js
|
||||
* const { spawn } = require('node:child_process');
|
||||
* import { spawn } from 'node:child_process';
|
||||
* const grep = spawn('grep', ['ssh']);
|
||||
*
|
||||
* console.log(`Spawned child pid: ${grep.pid}`);
|
||||
@@ -249,7 +249,7 @@ declare module "child_process" {
|
||||
* returns `true` if [`kill(2)`](http://man7.org/linux/man-pages/man2/kill.2.html) succeeds, and `false` otherwise.
|
||||
*
|
||||
* ```js
|
||||
* const { spawn } = require('node:child_process');
|
||||
* import { spawn } from 'node:child_process';
|
||||
* const grep = spawn('grep', ['ssh']);
|
||||
*
|
||||
* grep.on('close', (code, signal) => {
|
||||
@@ -282,7 +282,7 @@ declare module "child_process" {
|
||||
*
|
||||
* ```js
|
||||
* 'use strict';
|
||||
* const { spawn } = require('node:child_process');
|
||||
* import { spawn } from 'node:child_process';
|
||||
*
|
||||
* const subprocess = spawn(
|
||||
* 'sh',
|
||||
@@ -320,7 +320,7 @@ declare module "child_process" {
|
||||
* For example, in the parent script:
|
||||
*
|
||||
* ```js
|
||||
* const cp = require('node:child_process');
|
||||
* import cp from 'node:child_process';
|
||||
* const n = cp.fork(`${__dirname}/sub.js`);
|
||||
*
|
||||
* n.on('message', (m) => {
|
||||
@@ -374,10 +374,12 @@ declare module "child_process" {
|
||||
* a TCP server object to the child process as illustrated in the example below:
|
||||
*
|
||||
* ```js
|
||||
* const subprocess = require('node:child_process').fork('subprocess.js');
|
||||
* import { createServer } from 'node:net';
|
||||
* import { fork } from 'node:child_process';
|
||||
* const subprocess = fork('subprocess.js');
|
||||
*
|
||||
* // Open up the server object and send the handle.
|
||||
* const server = require('node:net').createServer();
|
||||
* const server = createServer();
|
||||
* server.on('connection', (socket) => {
|
||||
* socket.end('handled by parent');
|
||||
* });
|
||||
@@ -412,13 +414,14 @@ declare module "child_process" {
|
||||
* handle connections with "normal" or "special" priority:
|
||||
*
|
||||
* ```js
|
||||
* const { fork } = require('node:child_process');
|
||||
* import { createServer } from 'node:net';
|
||||
* import { fork } from 'node:child_process';
|
||||
* const normal = fork('subprocess.js', ['normal']);
|
||||
* const special = fork('subprocess.js', ['special']);
|
||||
*
|
||||
* // Open up the server and send sockets to child. Use pauseOnConnect to prevent
|
||||
* // the sockets from being read before they are sent to the child process.
|
||||
* const server = require('node:net').createServer({ pauseOnConnect: true });
|
||||
* const server = createServer({ pauseOnConnect: true });
|
||||
* server.on('connection', (socket) => {
|
||||
*
|
||||
* // If this is special priority...
|
||||
@@ -455,7 +458,7 @@ declare module "child_process" {
|
||||
* as the connection may have been closed during the time it takes to send the
|
||||
* connection to the child.
|
||||
* @since v0.5.9
|
||||
* @param sendHandle `undefined`, or a [`net.Socket`](https://nodejs.org/docs/latest-v20.x/api/net.html#class-netsocket), [`net.Server`](https://nodejs.org/docs/latest-v20.x/api/net.html#class-netserver), or [`dgram.Socket`](https://nodejs.org/docs/latest-v20.x/api/dgram.html#class-dgramsocket) object.
|
||||
* @param sendHandle `undefined`, or a [`net.Socket`](https://nodejs.org/docs/latest-v24.x/api/net.html#class-netsocket), [`net.Server`](https://nodejs.org/docs/latest-v24.x/api/net.html#class-netserver), or [`dgram.Socket`](https://nodejs.org/docs/latest-v24.x/api/dgram.html#class-dgramsocket) object.
|
||||
* @param options The `options` argument, if present, is an object used to parameterize the sending of certain types of handles. `options` supports the following properties:
|
||||
*/
|
||||
send(message: Serializable, callback?: (error: Error | null) => void): boolean;
|
||||
@@ -490,7 +493,7 @@ declare module "child_process" {
|
||||
* the child and the parent.
|
||||
*
|
||||
* ```js
|
||||
* const { spawn } = require('node:child_process');
|
||||
* import { spawn } from 'node:child_process';
|
||||
*
|
||||
* const subprocess = spawn(process.argv[0], ['child_program.js'], {
|
||||
* detached: true,
|
||||
@@ -508,7 +511,7 @@ declare module "child_process" {
|
||||
* to wait for the child to exit before exiting itself.
|
||||
*
|
||||
* ```js
|
||||
* const { spawn } = require('node:child_process');
|
||||
* import { spawn } from 'node:child_process';
|
||||
*
|
||||
* const subprocess = spawn(process.argv[0], ['child_program.js'], {
|
||||
* detached: true,
|
||||
@@ -610,6 +613,10 @@ declare module "child_process" {
|
||||
Readable | Writable | null | undefined, // extra, no modification
|
||||
];
|
||||
}
|
||||
interface Control extends EventEmitter {
|
||||
ref(): void;
|
||||
unref(): void;
|
||||
}
|
||||
interface MessageOptions {
|
||||
keepOpen?: boolean | undefined;
|
||||
}
|
||||
@@ -711,7 +718,7 @@ declare module "child_process" {
|
||||
* exit code:
|
||||
*
|
||||
* ```js
|
||||
* const { spawn } = require('node:child_process');
|
||||
* import { spawn } from 'node:child_process';
|
||||
* const ls = spawn('ls', ['-lh', '/usr']);
|
||||
*
|
||||
* ls.stdout.on('data', (data) => {
|
||||
@@ -730,7 +737,7 @@ declare module "child_process" {
|
||||
* Example: A very elaborate way to run `ps ax | grep ssh`
|
||||
*
|
||||
* ```js
|
||||
* const { spawn } = require('node:child_process');
|
||||
* import { spawn } from 'node:child_process';
|
||||
* const ps = spawn('ps', ['ax']);
|
||||
* const grep = spawn('grep', ['ssh']);
|
||||
*
|
||||
@@ -767,7 +774,7 @@ declare module "child_process" {
|
||||
* Example of checking for failed `spawn`:
|
||||
*
|
||||
* ```js
|
||||
* const { spawn } = require('node:child_process');
|
||||
* import { spawn } from 'node:child_process';
|
||||
* const subprocess = spawn('bad_command');
|
||||
*
|
||||
* subprocess.on('error', (err) => {
|
||||
@@ -785,7 +792,7 @@ declare module "child_process" {
|
||||
* the error passed to the callback will be an `AbortError`:
|
||||
*
|
||||
* ```js
|
||||
* const { spawn } = require('node:child_process');
|
||||
* import { spawn } from 'node:child_process';
|
||||
* const controller = new AbortController();
|
||||
* const { signal } = controller;
|
||||
* const grep = spawn('grep', ['ssh'], { signal });
|
||||
@@ -884,18 +891,20 @@ declare module "child_process" {
|
||||
signal?: AbortSignal | undefined;
|
||||
maxBuffer?: number | undefined;
|
||||
killSignal?: NodeJS.Signals | number | undefined;
|
||||
encoding?: string | null | undefined;
|
||||
}
|
||||
interface ExecOptionsWithStringEncoding extends ExecOptions {
|
||||
encoding: BufferEncoding;
|
||||
encoding?: BufferEncoding | undefined;
|
||||
}
|
||||
interface ExecOptionsWithBufferEncoding extends ExecOptions {
|
||||
encoding: BufferEncoding | null; // specify `null`.
|
||||
encoding: "buffer" | null; // specify `null`.
|
||||
}
|
||||
// TODO: Just Plain Wrong™ (see also nodejs/node#57392)
|
||||
interface ExecException extends Error {
|
||||
cmd?: string | undefined;
|
||||
killed?: boolean | undefined;
|
||||
code?: number | undefined;
|
||||
signal?: NodeJS.Signals | undefined;
|
||||
cmd?: string;
|
||||
killed?: boolean;
|
||||
code?: number;
|
||||
signal?: NodeJS.Signals;
|
||||
stdout?: string;
|
||||
stderr?: string;
|
||||
}
|
||||
@@ -906,7 +915,7 @@ declare module "child_process" {
|
||||
* need to be dealt with accordingly:
|
||||
*
|
||||
* ```js
|
||||
* const { exec } = require('node:child_process');
|
||||
* import { exec } from 'node:child_process';
|
||||
*
|
||||
* exec('"/path/to/test file/test.sh" arg1 arg2');
|
||||
* // Double quotes are used so that the space in the path is not interpreted as
|
||||
@@ -932,7 +941,7 @@ declare module "child_process" {
|
||||
* encoding, `Buffer` objects will be passed to the callback instead.
|
||||
*
|
||||
* ```js
|
||||
* const { exec } = require('node:child_process');
|
||||
* import { exec } from 'node:child_process';
|
||||
* exec('cat *.js missing_file | wc -l', (error, stdout, stderr) => {
|
||||
* if (error) {
|
||||
* console.error(`exec error: ${error}`);
|
||||
@@ -957,8 +966,9 @@ declare module "child_process" {
|
||||
* callback, but with two additional properties `stdout` and `stderr`.
|
||||
*
|
||||
* ```js
|
||||
* const util = require('node:util');
|
||||
* const exec = util.promisify(require('node:child_process').exec);
|
||||
* import util from 'node:util';
|
||||
* import child_process from 'node:child_process';
|
||||
* const exec = util.promisify(child_process.exec);
|
||||
*
|
||||
* async function lsExample() {
|
||||
* const { stdout, stderr } = await exec('ls');
|
||||
@@ -972,7 +982,7 @@ declare module "child_process" {
|
||||
* the error passed to the callback will be an `AbortError`:
|
||||
*
|
||||
* ```js
|
||||
* const { exec } = require('node:child_process');
|
||||
* import { exec } from 'node:child_process';
|
||||
* const controller = new AbortController();
|
||||
* const { signal } = controller;
|
||||
* const child = exec('grep ssh', { signal }, (error) => {
|
||||
@@ -991,39 +1001,24 @@ declare module "child_process" {
|
||||
// `options` with `"buffer"` or `null` for `encoding` means stdout/stderr are definitely `Buffer`.
|
||||
function exec(
|
||||
command: string,
|
||||
options: {
|
||||
encoding: "buffer" | null;
|
||||
} & ExecOptions,
|
||||
callback?: (error: ExecException | null, stdout: Buffer, stderr: Buffer) => void,
|
||||
options: ExecOptionsWithBufferEncoding,
|
||||
callback?: (error: ExecException | null, stdout: NonSharedBuffer, stderr: NonSharedBuffer) => void,
|
||||
): ChildProcess;
|
||||
// `options` with well known `encoding` means stdout/stderr are definitely `string`.
|
||||
// `options` with well-known or absent `encoding` means stdout/stderr are definitely `string`.
|
||||
function exec(
|
||||
command: string,
|
||||
options: {
|
||||
encoding: BufferEncoding;
|
||||
} & ExecOptions,
|
||||
callback?: (error: ExecException | null, stdout: string, stderr: string) => void,
|
||||
): ChildProcess;
|
||||
// `options` with an `encoding` whose type is `string` means stdout/stderr could either be `Buffer` or `string`.
|
||||
// There is no guarantee the `encoding` is unknown as `string` is a superset of `BufferEncoding`.
|
||||
function exec(
|
||||
command: string,
|
||||
options: {
|
||||
encoding: BufferEncoding;
|
||||
} & ExecOptions,
|
||||
callback?: (error: ExecException | null, stdout: string | Buffer, stderr: string | Buffer) => void,
|
||||
): ChildProcess;
|
||||
// `options` without an `encoding` means stdout/stderr are definitely `string`.
|
||||
function exec(
|
||||
command: string,
|
||||
options: ExecOptions,
|
||||
options: ExecOptionsWithStringEncoding,
|
||||
callback?: (error: ExecException | null, stdout: string, stderr: string) => void,
|
||||
): ChildProcess;
|
||||
// fallback if nothing else matches. Worst case is always `string | Buffer`.
|
||||
function exec(
|
||||
command: string,
|
||||
options: (ObjectEncodingOptions & ExecOptions) | undefined | null,
|
||||
callback?: (error: ExecException | null, stdout: string | Buffer, stderr: string | Buffer) => void,
|
||||
options: ExecOptions | undefined | null,
|
||||
callback?: (
|
||||
error: ExecException | null,
|
||||
stdout: string | NonSharedBuffer,
|
||||
stderr: string | NonSharedBuffer,
|
||||
) => void,
|
||||
): ChildProcess;
|
||||
interface PromiseWithChild<T> extends Promise<T> {
|
||||
child: ChildProcess;
|
||||
@@ -1035,35 +1030,24 @@ declare module "child_process" {
|
||||
}>;
|
||||
function __promisify__(
|
||||
command: string,
|
||||
options: {
|
||||
encoding: "buffer" | null;
|
||||
} & ExecOptions,
|
||||
options: ExecOptionsWithBufferEncoding,
|
||||
): PromiseWithChild<{
|
||||
stdout: Buffer;
|
||||
stderr: Buffer;
|
||||
stdout: NonSharedBuffer;
|
||||
stderr: NonSharedBuffer;
|
||||
}>;
|
||||
function __promisify__(
|
||||
command: string,
|
||||
options: {
|
||||
encoding: BufferEncoding;
|
||||
} & ExecOptions,
|
||||
options: ExecOptionsWithStringEncoding,
|
||||
): PromiseWithChild<{
|
||||
stdout: string;
|
||||
stderr: string;
|
||||
}>;
|
||||
function __promisify__(
|
||||
command: string,
|
||||
options: ExecOptions,
|
||||
options: ExecOptions | undefined | null,
|
||||
): PromiseWithChild<{
|
||||
stdout: string;
|
||||
stderr: string;
|
||||
}>;
|
||||
function __promisify__(
|
||||
command: string,
|
||||
options?: (ObjectEncodingOptions & ExecOptions) | null,
|
||||
): PromiseWithChild<{
|
||||
stdout: string | Buffer;
|
||||
stderr: string | Buffer;
|
||||
stdout: string | NonSharedBuffer;
|
||||
stderr: string | NonSharedBuffer;
|
||||
}>;
|
||||
}
|
||||
interface ExecFileOptions extends CommonOptions, Abortable {
|
||||
@@ -1072,20 +1056,21 @@ declare module "child_process" {
|
||||
windowsVerbatimArguments?: boolean | undefined;
|
||||
shell?: boolean | string | undefined;
|
||||
signal?: AbortSignal | undefined;
|
||||
encoding?: string | null | undefined;
|
||||
}
|
||||
interface ExecFileOptionsWithStringEncoding extends ExecFileOptions {
|
||||
encoding: BufferEncoding;
|
||||
encoding?: BufferEncoding | undefined;
|
||||
}
|
||||
interface ExecFileOptionsWithBufferEncoding extends ExecFileOptions {
|
||||
encoding: "buffer" | null;
|
||||
}
|
||||
interface ExecFileOptionsWithOtherEncoding extends ExecFileOptions {
|
||||
encoding: BufferEncoding;
|
||||
}
|
||||
/** @deprecated Use `ExecFileOptions` instead. */
|
||||
interface ExecFileOptionsWithOtherEncoding extends ExecFileOptions {}
|
||||
// TODO: execFile exceptions can take many forms... this accurately describes none of them
|
||||
type ExecFileException =
|
||||
& Omit<ExecException, "code">
|
||||
& Omit<NodeJS.ErrnoException, "code">
|
||||
& { code?: string | number | undefined | null };
|
||||
& { code?: string | number | null };
|
||||
/**
|
||||
* The `child_process.execFile()` function is similar to {@link exec} except that it does not spawn a shell by default. Rather, the specified
|
||||
* executable `file` is spawned directly as a new process making it slightly more
|
||||
@@ -1096,7 +1081,7 @@ declare module "child_process" {
|
||||
* supported.
|
||||
*
|
||||
* ```js
|
||||
* const { execFile } = require('node:child_process');
|
||||
* import { execFile } from 'node:child_process';
|
||||
* const child = execFile('node', ['--version'], (error, stdout, stderr) => {
|
||||
* if (error) {
|
||||
* throw error;
|
||||
@@ -1119,8 +1104,9 @@ declare module "child_process" {
|
||||
* callback, but with two additional properties `stdout` and `stderr`.
|
||||
*
|
||||
* ```js
|
||||
* const util = require('node:util');
|
||||
* const execFile = util.promisify(require('node:child_process').execFile);
|
||||
* import util from 'node:util';
|
||||
* import child_process from 'node:child_process';
|
||||
* const execFile = util.promisify(child_process.execFile);
|
||||
* async function getVersion() {
|
||||
* const { stdout } = await execFile('node', ['--version']);
|
||||
* console.log(stdout);
|
||||
@@ -1136,7 +1122,7 @@ declare module "child_process" {
|
||||
* the error passed to the callback will be an `AbortError`:
|
||||
*
|
||||
* ```js
|
||||
* const { execFile } = require('node:child_process');
|
||||
* import { execFile } from 'node:child_process';
|
||||
* const controller = new AbortController();
|
||||
* const { signal } = controller;
|
||||
* const child = execFile('node', ['--version'], { signal }, (error) => {
|
||||
@@ -1149,91 +1135,63 @@ declare module "child_process" {
|
||||
* @param args List of string arguments.
|
||||
* @param callback Called with the output when process terminates.
|
||||
*/
|
||||
function execFile(file: string): ChildProcess;
|
||||
function execFile(
|
||||
file: string,
|
||||
options: (ObjectEncodingOptions & ExecFileOptions) | undefined | null,
|
||||
): ChildProcess;
|
||||
function execFile(file: string, args?: readonly string[] | null): ChildProcess;
|
||||
function execFile(
|
||||
file: string,
|
||||
args: readonly string[] | undefined | null,
|
||||
options: (ObjectEncodingOptions & ExecFileOptions) | undefined | null,
|
||||
): ChildProcess;
|
||||
// no `options` definitely means stdout/stderr are `string`.
|
||||
function execFile(
|
||||
file: string,
|
||||
callback: (error: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||
callback?: (error: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||
): ChildProcess;
|
||||
function execFile(
|
||||
file: string,
|
||||
args: readonly string[] | undefined | null,
|
||||
callback: (error: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||
callback?: (error: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||
): ChildProcess;
|
||||
// `options` with `"buffer"` or `null` for `encoding` means stdout/stderr are definitely `Buffer`.
|
||||
function execFile(
|
||||
file: string,
|
||||
options: ExecFileOptionsWithBufferEncoding,
|
||||
callback: (error: ExecFileException | null, stdout: Buffer, stderr: Buffer) => void,
|
||||
callback?: (error: ExecFileException | null, stdout: NonSharedBuffer, stderr: NonSharedBuffer) => void,
|
||||
): ChildProcess;
|
||||
function execFile(
|
||||
file: string,
|
||||
args: readonly string[] | undefined | null,
|
||||
options: ExecFileOptionsWithBufferEncoding,
|
||||
callback: (error: ExecFileException | null, stdout: Buffer, stderr: Buffer) => void,
|
||||
callback?: (error: ExecFileException | null, stdout: NonSharedBuffer, stderr: NonSharedBuffer) => void,
|
||||
): ChildProcess;
|
||||
// `options` with well known `encoding` means stdout/stderr are definitely `string`.
|
||||
// `options` with well-known or absent `encoding` means stdout/stderr are definitely `string`.
|
||||
function execFile(
|
||||
file: string,
|
||||
options: ExecFileOptionsWithStringEncoding,
|
||||
callback: (error: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||
callback?: (error: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||
): ChildProcess;
|
||||
function execFile(
|
||||
file: string,
|
||||
args: readonly string[] | undefined | null,
|
||||
options: ExecFileOptionsWithStringEncoding,
|
||||
callback: (error: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||
): ChildProcess;
|
||||
// `options` with an `encoding` whose type is `string` means stdout/stderr could either be `Buffer` or `string`.
|
||||
// There is no guarantee the `encoding` is unknown as `string` is a superset of `BufferEncoding`.
|
||||
function execFile(
|
||||
file: string,
|
||||
options: ExecFileOptionsWithOtherEncoding,
|
||||
callback: (error: ExecFileException | null, stdout: string | Buffer, stderr: string | Buffer) => void,
|
||||
): ChildProcess;
|
||||
function execFile(
|
||||
file: string,
|
||||
args: readonly string[] | undefined | null,
|
||||
options: ExecFileOptionsWithOtherEncoding,
|
||||
callback: (error: ExecFileException | null, stdout: string | Buffer, stderr: string | Buffer) => void,
|
||||
): ChildProcess;
|
||||
// `options` without an `encoding` means stdout/stderr are definitely `string`.
|
||||
function execFile(
|
||||
file: string,
|
||||
options: ExecFileOptions,
|
||||
callback: (error: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||
): ChildProcess;
|
||||
function execFile(
|
||||
file: string,
|
||||
args: readonly string[] | undefined | null,
|
||||
options: ExecFileOptions,
|
||||
callback: (error: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||
callback?: (error: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||
): ChildProcess;
|
||||
// fallback if nothing else matches. Worst case is always `string | Buffer`.
|
||||
function execFile(
|
||||
file: string,
|
||||
options: (ObjectEncodingOptions & ExecFileOptions) | undefined | null,
|
||||
options: ExecFileOptions | undefined | null,
|
||||
callback:
|
||||
| ((error: ExecFileException | null, stdout: string | Buffer, stderr: string | Buffer) => void)
|
||||
| ((
|
||||
error: ExecFileException | null,
|
||||
stdout: string | NonSharedBuffer,
|
||||
stderr: string | NonSharedBuffer,
|
||||
) => void)
|
||||
| undefined
|
||||
| null,
|
||||
): ChildProcess;
|
||||
function execFile(
|
||||
file: string,
|
||||
args: readonly string[] | undefined | null,
|
||||
options: (ObjectEncodingOptions & ExecFileOptions) | undefined | null,
|
||||
options: ExecFileOptions | undefined | null,
|
||||
callback:
|
||||
| ((error: ExecFileException | null, stdout: string | Buffer, stderr: string | Buffer) => void)
|
||||
| ((
|
||||
error: ExecFileException | null,
|
||||
stdout: string | NonSharedBuffer,
|
||||
stderr: string | NonSharedBuffer,
|
||||
) => void)
|
||||
| undefined
|
||||
| null,
|
||||
): ChildProcess;
|
||||
@@ -1253,16 +1211,16 @@ declare module "child_process" {
|
||||
file: string,
|
||||
options: ExecFileOptionsWithBufferEncoding,
|
||||
): PromiseWithChild<{
|
||||
stdout: Buffer;
|
||||
stderr: Buffer;
|
||||
stdout: NonSharedBuffer;
|
||||
stderr: NonSharedBuffer;
|
||||
}>;
|
||||
function __promisify__(
|
||||
file: string,
|
||||
args: readonly string[] | undefined | null,
|
||||
options: ExecFileOptionsWithBufferEncoding,
|
||||
): PromiseWithChild<{
|
||||
stdout: Buffer;
|
||||
stderr: Buffer;
|
||||
stdout: NonSharedBuffer;
|
||||
stderr: NonSharedBuffer;
|
||||
}>;
|
||||
function __promisify__(
|
||||
file: string,
|
||||
@@ -1281,48 +1239,18 @@ declare module "child_process" {
|
||||
}>;
|
||||
function __promisify__(
|
||||
file: string,
|
||||
options: ExecFileOptionsWithOtherEncoding,
|
||||
options: ExecFileOptions | undefined | null,
|
||||
): PromiseWithChild<{
|
||||
stdout: string | Buffer;
|
||||
stderr: string | Buffer;
|
||||
stdout: string | NonSharedBuffer;
|
||||
stderr: string | NonSharedBuffer;
|
||||
}>;
|
||||
function __promisify__(
|
||||
file: string,
|
||||
args: readonly string[] | undefined | null,
|
||||
options: ExecFileOptionsWithOtherEncoding,
|
||||
options: ExecFileOptions | undefined | null,
|
||||
): PromiseWithChild<{
|
||||
stdout: string | Buffer;
|
||||
stderr: string | Buffer;
|
||||
}>;
|
||||
function __promisify__(
|
||||
file: string,
|
||||
options: ExecFileOptions,
|
||||
): PromiseWithChild<{
|
||||
stdout: string;
|
||||
stderr: string;
|
||||
}>;
|
||||
function __promisify__(
|
||||
file: string,
|
||||
args: readonly string[] | undefined | null,
|
||||
options: ExecFileOptions,
|
||||
): PromiseWithChild<{
|
||||
stdout: string;
|
||||
stderr: string;
|
||||
}>;
|
||||
function __promisify__(
|
||||
file: string,
|
||||
options: (ObjectEncodingOptions & ExecFileOptions) | undefined | null,
|
||||
): PromiseWithChild<{
|
||||
stdout: string | Buffer;
|
||||
stderr: string | Buffer;
|
||||
}>;
|
||||
function __promisify__(
|
||||
file: string,
|
||||
args: readonly string[] | undefined | null,
|
||||
options: (ObjectEncodingOptions & ExecFileOptions) | undefined | null,
|
||||
): PromiseWithChild<{
|
||||
stdout: string | Buffer;
|
||||
stderr: string | Buffer;
|
||||
stdout: string | NonSharedBuffer;
|
||||
stderr: string | NonSharedBuffer;
|
||||
}>;
|
||||
}
|
||||
interface ForkOptions extends ProcessEnvOptions, MessagingOptions, Abortable {
|
||||
@@ -1377,7 +1305,7 @@ declare module "child_process" {
|
||||
* console.log(`Hello from ${process.argv[2]}!`);
|
||||
* }, 1_000);
|
||||
* } else {
|
||||
* const { fork } = require('node:child_process');
|
||||
* import { fork } from 'node:child_process';
|
||||
* const controller = new AbortController();
|
||||
* const { signal } = controller;
|
||||
* const child = fork(__filename, ['child'], { signal });
|
||||
@@ -1411,7 +1339,7 @@ declare module "child_process" {
|
||||
stderr: T;
|
||||
status: number | null;
|
||||
signal: NodeJS.Signals | null;
|
||||
error?: Error | undefined;
|
||||
error?: Error;
|
||||
}
|
||||
/**
|
||||
* The `child_process.spawnSync()` method is generally identical to {@link spawn} with the exception that the function will not return
|
||||
@@ -1428,11 +1356,11 @@ declare module "child_process" {
|
||||
* @param command The command to run.
|
||||
* @param args List of string arguments.
|
||||
*/
|
||||
function spawnSync(command: string): SpawnSyncReturns<Buffer>;
|
||||
function spawnSync(command: string): SpawnSyncReturns<NonSharedBuffer>;
|
||||
function spawnSync(command: string, options: SpawnSyncOptionsWithStringEncoding): SpawnSyncReturns<string>;
|
||||
function spawnSync(command: string, options: SpawnSyncOptionsWithBufferEncoding): SpawnSyncReturns<Buffer>;
|
||||
function spawnSync(command: string, options?: SpawnSyncOptions): SpawnSyncReturns<string | Buffer>;
|
||||
function spawnSync(command: string, args: readonly string[]): SpawnSyncReturns<Buffer>;
|
||||
function spawnSync(command: string, options: SpawnSyncOptionsWithBufferEncoding): SpawnSyncReturns<NonSharedBuffer>;
|
||||
function spawnSync(command: string, options?: SpawnSyncOptions): SpawnSyncReturns<string | NonSharedBuffer>;
|
||||
function spawnSync(command: string, args: readonly string[]): SpawnSyncReturns<NonSharedBuffer>;
|
||||
function spawnSync(
|
||||
command: string,
|
||||
args: readonly string[],
|
||||
@@ -1442,12 +1370,12 @@ declare module "child_process" {
|
||||
command: string,
|
||||
args: readonly string[],
|
||||
options: SpawnSyncOptionsWithBufferEncoding,
|
||||
): SpawnSyncReturns<Buffer>;
|
||||
): SpawnSyncReturns<NonSharedBuffer>;
|
||||
function spawnSync(
|
||||
command: string,
|
||||
args?: readonly string[],
|
||||
options?: SpawnSyncOptions,
|
||||
): SpawnSyncReturns<string | Buffer>;
|
||||
): SpawnSyncReturns<string | NonSharedBuffer>;
|
||||
interface CommonExecOptions extends CommonOptions {
|
||||
input?: string | NodeJS.ArrayBufferView | undefined;
|
||||
/**
|
||||
@@ -1489,10 +1417,10 @@ declare module "child_process" {
|
||||
* @param command The command to run.
|
||||
* @return The stdout from the command.
|
||||
*/
|
||||
function execSync(command: string): Buffer;
|
||||
function execSync(command: string): NonSharedBuffer;
|
||||
function execSync(command: string, options: ExecSyncOptionsWithStringEncoding): string;
|
||||
function execSync(command: string, options: ExecSyncOptionsWithBufferEncoding): Buffer;
|
||||
function execSync(command: string, options?: ExecSyncOptions): string | Buffer;
|
||||
function execSync(command: string, options: ExecSyncOptionsWithBufferEncoding): NonSharedBuffer;
|
||||
function execSync(command: string, options?: ExecSyncOptions): string | NonSharedBuffer;
|
||||
interface ExecFileSyncOptions extends CommonExecOptions {
|
||||
shell?: boolean | string | undefined;
|
||||
}
|
||||
@@ -1500,7 +1428,7 @@ declare module "child_process" {
|
||||
encoding: BufferEncoding;
|
||||
}
|
||||
interface ExecFileSyncOptionsWithBufferEncoding extends ExecFileSyncOptions {
|
||||
encoding?: "buffer" | null; // specify `null`.
|
||||
encoding?: "buffer" | null | undefined; // specify `null`.
|
||||
}
|
||||
/**
|
||||
* The `child_process.execFileSync()` method is generally identical to {@link execFile} with the exception that the method will not
|
||||
@@ -1522,11 +1450,11 @@ declare module "child_process" {
|
||||
* @param args List of string arguments.
|
||||
* @return The stdout from the command.
|
||||
*/
|
||||
function execFileSync(file: string): Buffer;
|
||||
function execFileSync(file: string): NonSharedBuffer;
|
||||
function execFileSync(file: string, options: ExecFileSyncOptionsWithStringEncoding): string;
|
||||
function execFileSync(file: string, options: ExecFileSyncOptionsWithBufferEncoding): Buffer;
|
||||
function execFileSync(file: string, options?: ExecFileSyncOptions): string | Buffer;
|
||||
function execFileSync(file: string, args: readonly string[]): Buffer;
|
||||
function execFileSync(file: string, options: ExecFileSyncOptionsWithBufferEncoding): NonSharedBuffer;
|
||||
function execFileSync(file: string, options?: ExecFileSyncOptions): string | NonSharedBuffer;
|
||||
function execFileSync(file: string, args: readonly string[]): NonSharedBuffer;
|
||||
function execFileSync(
|
||||
file: string,
|
||||
args: readonly string[],
|
||||
@@ -1536,8 +1464,12 @@ declare module "child_process" {
|
||||
file: string,
|
||||
args: readonly string[],
|
||||
options: ExecFileSyncOptionsWithBufferEncoding,
|
||||
): Buffer;
|
||||
function execFileSync(file: string, args?: readonly string[], options?: ExecFileSyncOptions): string | Buffer;
|
||||
): NonSharedBuffer;
|
||||
function execFileSync(
|
||||
file: string,
|
||||
args?: readonly string[],
|
||||
options?: ExecFileSyncOptions,
|
||||
): string | NonSharedBuffer;
|
||||
}
|
||||
declare module "node:child_process" {
|
||||
export * from "child_process";
|
||||
|
||||
44
backend/node_modules/@types/node/cluster.d.ts
generated
vendored
44
backend/node_modules/@types/node/cluster.d.ts
generated
vendored
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Clusters of Node.js processes can be used to run multiple instances of Node.js
|
||||
* that can distribute workloads among their application threads. When process isolation
|
||||
* is not needed, use the [`worker_threads`](https://nodejs.org/docs/latest-v20.x/api/worker_threads.html)
|
||||
* is not needed, use the [`worker_threads`](https://nodejs.org/docs/latest-v24.x/api/worker_threads.html)
|
||||
* module instead, which allows running multiple application threads within a single Node.js instance.
|
||||
*
|
||||
* The cluster module allows easy creation of child processes that all share
|
||||
@@ -50,7 +50,7 @@
|
||||
* ```
|
||||
*
|
||||
* On Windows, it is not yet possible to set up a named pipe server in a worker.
|
||||
* @see [source](https://github.com/nodejs/node/blob/v20.13.1/lib/cluster.js)
|
||||
* @see [source](https://github.com/nodejs/node/blob/v24.x/lib/cluster.js)
|
||||
*/
|
||||
declare module "cluster" {
|
||||
import * as child from "node:child_process";
|
||||
@@ -72,7 +72,7 @@ declare module "cluster" {
|
||||
* String arguments passed to worker.
|
||||
* @default process.argv.slice(2)
|
||||
*/
|
||||
args?: string[] | undefined;
|
||||
args?: readonly string[] | undefined;
|
||||
/**
|
||||
* Whether or not to send output to parent's stdio.
|
||||
* @default false
|
||||
@@ -80,8 +80,8 @@ declare module "cluster" {
|
||||
silent?: boolean | undefined;
|
||||
/**
|
||||
* Configures the stdio of forked processes. Because the cluster module relies on IPC to function, this configuration must
|
||||
* contain an `'ipc'` entry. When this option is provided, it overrides `silent`. See [`child_prcess.spawn()`](https://nodejs.org/docs/latest-v20.x/api/child_process.html#child_processspawncommand-args-options)'s
|
||||
* [`stdio`](https://nodejs.org/docs/latest-v20.x/api/child_process.html#optionsstdio).
|
||||
* contain an `'ipc'` entry. When this option is provided, it overrides `silent`. See [`child_prcess.spawn()`](https://nodejs.org/docs/latest-v24.x/api/child_process.html#child_processspawncommand-args-options)'s
|
||||
* [`stdio`](https://nodejs.org/docs/latest-v24.x/api/child_process.html#optionsstdio).
|
||||
*/
|
||||
stdio?: any[] | undefined;
|
||||
/**
|
||||
@@ -99,7 +99,7 @@ declare module "cluster" {
|
||||
inspectPort?: number | (() => number) | undefined;
|
||||
/**
|
||||
* Specify the kind of serialization used for sending messages between processes. Possible values are `'json'` and `'advanced'`.
|
||||
* See [Advanced serialization for `child_process`](https://nodejs.org/docs/latest-v20.x/api/child_process.html#advanced-serialization) for more details.
|
||||
* See [Advanced serialization for `child_process`](https://nodejs.org/docs/latest-v24.x/api/child_process.html#advanced-serialization) for more details.
|
||||
* @default false
|
||||
*/
|
||||
serialization?: SerializationType | undefined;
|
||||
@@ -142,10 +142,10 @@ declare module "cluster" {
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* All workers are created using [`child_process.fork()`](https://nodejs.org/docs/latest-v20.x/api/child_process.html#child_processforkmodulepath-args-options), the returned object
|
||||
* All workers are created using [`child_process.fork()`](https://nodejs.org/docs/latest-v24.x/api/child_process.html#child_processforkmodulepath-args-options), the returned object
|
||||
* from this function is stored as `.process`. In a worker, the global `process` is stored.
|
||||
*
|
||||
* See: [Child Process module](https://nodejs.org/docs/latest-v20.x/api/child_process.html#child_processforkmodulepath-args-options).
|
||||
* See: [Child Process module](https://nodejs.org/docs/latest-v24.x/api/child_process.html#child_processforkmodulepath-args-options).
|
||||
*
|
||||
* Workers will call `process.exit(0)` if the `'disconnect'` event occurs
|
||||
* on `process` and `.exitedAfterDisconnect` is not `true`. This protects against
|
||||
@@ -156,7 +156,7 @@ declare module "cluster" {
|
||||
/**
|
||||
* Send a message to a worker or primary, optionally with a handle.
|
||||
*
|
||||
* In the primary, this sends a message to a specific worker. It is identical to [`ChildProcess.send()`](https://nodejs.org/docs/latest-v20.x/api/child_process.html#subprocesssendmessage-sendhandle-options-callback).
|
||||
* In the primary, this sends a message to a specific worker. It is identical to [`ChildProcess.send()`](https://nodejs.org/docs/latest-v24.x/api/child_process.html#subprocesssendmessage-sendhandle-options-callback).
|
||||
*
|
||||
* In a worker, this sends a message to the primary. It is identical to `process.send()`.
|
||||
*
|
||||
@@ -198,7 +198,7 @@ declare module "cluster" {
|
||||
* This method is aliased as `worker.destroy()` for backwards compatibility.
|
||||
*
|
||||
* In a worker, `process.kill()` exists, but it is not this function;
|
||||
* it is [`kill()`](https://nodejs.org/docs/latest-v20.x/api/process.html#processkillpid-signal).
|
||||
* it is [`kill()`](https://nodejs.org/docs/latest-v24.x/api/process.html#processkillpid-signal).
|
||||
* @since v0.9.12
|
||||
* @param [signal='SIGTERM'] Name of the kill signal to send to the worker process.
|
||||
*/
|
||||
@@ -231,6 +231,8 @@ declare module "cluster" {
|
||||
* the `'disconnect'` event has not been emitted after some time.
|
||||
*
|
||||
* ```js
|
||||
* import net from 'node:net';
|
||||
*
|
||||
* if (cluster.isPrimary) {
|
||||
* const worker = cluster.fork();
|
||||
* let timeout;
|
||||
@@ -248,7 +250,6 @@ declare module "cluster" {
|
||||
* });
|
||||
*
|
||||
* } else if (cluster.isWorker) {
|
||||
* const net = require('node:net');
|
||||
* const server = net.createServer((socket) => {
|
||||
* // Connections never end
|
||||
* });
|
||||
@@ -265,7 +266,7 @@ declare module "cluster" {
|
||||
* @since v0.7.7
|
||||
* @return A reference to `worker`.
|
||||
*/
|
||||
disconnect(): void;
|
||||
disconnect(): this;
|
||||
/**
|
||||
* This function returns `true` if the worker is connected to its primary via its
|
||||
* IPC channel, `false` otherwise. A worker is connected to its primary after it
|
||||
@@ -411,7 +412,7 @@ declare module "cluster" {
|
||||
readonly isWorker: boolean;
|
||||
/**
|
||||
* The scheduling policy, either `cluster.SCHED_RR` for round-robin or `cluster.SCHED_NONE` to leave it to the operating system. This is a
|
||||
* global setting and effectively frozen once either the first worker is spawned, or [`.setupPrimary()`](https://nodejs.org/docs/latest-v20.x/api/cluster.html#clustersetupprimarysettings)
|
||||
* global setting and effectively frozen once either the first worker is spawned, or [`.setupPrimary()`](https://nodejs.org/docs/latest-v24.x/api/cluster.html#clustersetupprimarysettings)
|
||||
* is called, whichever comes first.
|
||||
*
|
||||
* `SCHED_RR` is the default on all operating systems except Windows. Windows will change to `SCHED_RR` once libuv is able to effectively distribute
|
||||
@@ -422,24 +423,24 @@ declare module "cluster" {
|
||||
*/
|
||||
schedulingPolicy: number;
|
||||
/**
|
||||
* After calling [`.setupPrimary()`](https://nodejs.org/docs/latest-v20.x/api/cluster.html#clustersetupprimarysettings)
|
||||
* (or [`.fork()`](https://nodejs.org/docs/latest-v20.x/api/cluster.html#clusterforkenv)) this settings object will contain
|
||||
* After calling [`.setupPrimary()`](https://nodejs.org/docs/latest-v24.x/api/cluster.html#clustersetupprimarysettings)
|
||||
* (or [`.fork()`](https://nodejs.org/docs/latest-v24.x/api/cluster.html#clusterforkenv)) this settings object will contain
|
||||
* the settings, including the default values.
|
||||
*
|
||||
* This object is not intended to be changed or set manually.
|
||||
* @since v0.7.1
|
||||
*/
|
||||
readonly settings: ClusterSettings;
|
||||
/** @deprecated since v16.0.0 - use [`.setupPrimary()`](https://nodejs.org/docs/latest-v20.x/api/cluster.html#clustersetupprimarysettings) instead. */
|
||||
/** @deprecated since v16.0.0 - use [`.setupPrimary()`](https://nodejs.org/docs/latest-v24.x/api/cluster.html#clustersetupprimarysettings) instead. */
|
||||
setupMaster(settings?: ClusterSettings): void;
|
||||
/**
|
||||
* `setupPrimary` is used to change the default 'fork' behavior. Once called, the settings will be present in `cluster.settings`.
|
||||
*
|
||||
* Any settings changes only affect future calls to [`.fork()`](https://nodejs.org/docs/latest-v20.x/api/cluster.html#clusterforkenv)
|
||||
* Any settings changes only affect future calls to [`.fork()`](https://nodejs.org/docs/latest-v24.x/api/cluster.html#clusterforkenv)
|
||||
* and have no effect on workers that are already running.
|
||||
*
|
||||
* The only attribute of a worker that cannot be set via `.setupPrimary()` is the `env` passed to
|
||||
* [`.fork()`](https://nodejs.org/docs/latest-v20.x/api/cluster.html#clusterforkenv).
|
||||
* [`.fork()`](https://nodejs.org/docs/latest-v24.x/api/cluster.html#clusterforkenv).
|
||||
*
|
||||
* The defaults above apply to the first call only; the defaults for later calls are the current values at the time of
|
||||
* `cluster.setupPrimary()` is called.
|
||||
@@ -480,7 +481,7 @@ declare module "cluster" {
|
||||
* ```
|
||||
* @since v0.7.0
|
||||
*/
|
||||
readonly worker?: Worker | undefined;
|
||||
readonly worker?: Worker;
|
||||
/**
|
||||
* A hash that stores the active worker objects, keyed by `id` field. This makes it easy to loop through all the workers. It is only available in the primary process.
|
||||
*
|
||||
@@ -496,7 +497,7 @@ declare module "cluster" {
|
||||
* ```
|
||||
* @since v0.7.0
|
||||
*/
|
||||
readonly workers?: NodeJS.Dict<Worker> | undefined;
|
||||
readonly workers?: NodeJS.Dict<Worker>;
|
||||
readonly SCHED_NONE: number;
|
||||
readonly SCHED_RR: number;
|
||||
/**
|
||||
@@ -549,10 +550,9 @@ declare module "cluster" {
|
||||
prependListener(event: "exit", listener: (worker: Worker, code: number, signal: string) => void): this;
|
||||
prependListener(event: "fork", listener: (worker: Worker) => void): this;
|
||||
prependListener(event: "listening", listener: (worker: Worker, address: Address) => void): this;
|
||||
// the handle is a net.Socket or net.Server object, or undefined.
|
||||
prependListener(
|
||||
event: "message",
|
||||
listener: (worker: Worker, message: any, handle?: net.Socket | net.Server) => void,
|
||||
listener: (worker: Worker, message: any, handle: net.Socket | net.Server) => void,
|
||||
): this;
|
||||
prependListener(event: "online", listener: (worker: Worker) => void): this;
|
||||
prependListener(event: "setup", listener: (settings: ClusterSettings) => void): this;
|
||||
|
||||
37
backend/node_modules/@types/node/console.d.ts
generated
vendored
37
backend/node_modules/@types/node/console.d.ts
generated
vendored
@@ -5,12 +5,12 @@
|
||||
* The module exports two specific components:
|
||||
*
|
||||
* * A `Console` class with methods such as `console.log()`, `console.error()`, and `console.warn()` that can be used to write to any Node.js stream.
|
||||
* * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstdout) and
|
||||
* [`process.stderr`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstderr). The global `console` can be used without calling `require('node:console')`.
|
||||
* * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v24.x/api/process.html#processstdout) and
|
||||
* [`process.stderr`](https://nodejs.org/docs/latest-v24.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module.
|
||||
*
|
||||
* _**Warning**_: The global console object's methods are neither consistently
|
||||
* synchronous like the browser APIs they resemble, nor are they consistently
|
||||
* asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v20.x/api/process.html#a-note-on-process-io) for
|
||||
* asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v24.x/api/process.html#a-note-on-process-io) for
|
||||
* more information.
|
||||
*
|
||||
* Example using the global `console`:
|
||||
@@ -54,7 +54,7 @@
|
||||
* myConsole.warn(`Danger ${name}! Danger!`);
|
||||
* // Prints: Danger Will Robinson! Danger!, to err
|
||||
* ```
|
||||
* @see [source](https://github.com/nodejs/node/blob/v20.13.1/lib/console.js)
|
||||
* @see [source](https://github.com/nodejs/node/blob/v24.x/lib/console.js)
|
||||
*/
|
||||
declare module "console" {
|
||||
import console = require("node:console");
|
||||
@@ -70,7 +70,7 @@ declare module "node:console" {
|
||||
* `console.assert()` writes a message if `value` is [falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy) or omitted. It only
|
||||
* writes a message and does not otherwise affect execution. The output always
|
||||
* starts with `"Assertion failed"`. If provided, `message` is formatted using
|
||||
* [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args).
|
||||
* [`util.format()`](https://nodejs.org/docs/latest-v24.x/api/util.html#utilformatformat-args).
|
||||
*
|
||||
* If `value` is [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy), nothing happens.
|
||||
*
|
||||
@@ -152,7 +152,7 @@ declare module "node:console" {
|
||||
*/
|
||||
debug(message?: any, ...optionalParams: any[]): void;
|
||||
/**
|
||||
* Uses [`util.inspect()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilinspectobject-options) on `obj` and prints the resulting string to `stdout`.
|
||||
* Uses [`util.inspect()`](https://nodejs.org/docs/latest-v24.x/api/util.html#utilinspectobject-options) on `obj` and prints the resulting string to `stdout`.
|
||||
* This function bypasses any custom `inspect()` function defined on `obj`.
|
||||
* @since v0.1.101
|
||||
*/
|
||||
@@ -167,7 +167,7 @@ declare module "node:console" {
|
||||
* Prints to `stderr` with newline. Multiple arguments can be passed, with the
|
||||
* first used as the primary message and all additional used as substitution
|
||||
* values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html)
|
||||
* (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args)).
|
||||
* (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v24.x/api/util.html#utilformatformat-args)).
|
||||
*
|
||||
* ```js
|
||||
* const code = 5;
|
||||
@@ -178,8 +178,8 @@ declare module "node:console" {
|
||||
* ```
|
||||
*
|
||||
* If formatting elements (e.g. `%d`) are not found in the first string then
|
||||
* [`util.inspect()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilinspectobject-options) is called on each argument and the
|
||||
* resulting string values are concatenated. See [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args)
|
||||
* [`util.inspect()`](https://nodejs.org/docs/latest-v24.x/api/util.html#utilinspectobject-options) is called on each argument and the
|
||||
* resulting string values are concatenated. See [`util.format()`](https://nodejs.org/docs/latest-v24.x/api/util.html#utilformatformat-args)
|
||||
* for more information.
|
||||
* @since v0.1.100
|
||||
*/
|
||||
@@ -211,7 +211,7 @@ declare module "node:console" {
|
||||
* Prints to `stdout` with newline. Multiple arguments can be passed, with the
|
||||
* first used as the primary message and all additional used as substitution
|
||||
* values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html)
|
||||
* (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args)).
|
||||
* (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v24.x/api/util.html#utilformatformat-args)).
|
||||
*
|
||||
* ```js
|
||||
* const count = 5;
|
||||
@@ -221,7 +221,7 @@ declare module "node:console" {
|
||||
* // Prints: count: 5, to stdout
|
||||
* ```
|
||||
*
|
||||
* See [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args) for more information.
|
||||
* See [`util.format()`](https://nodejs.org/docs/latest-v24.x/api/util.html#utilformatformat-args) for more information.
|
||||
* @since v0.1.100
|
||||
*/
|
||||
log(message?: any, ...optionalParams: any[]): void;
|
||||
@@ -297,7 +297,7 @@ declare module "node:console" {
|
||||
*/
|
||||
timeLog(label?: string, ...data: any[]): void;
|
||||
/**
|
||||
* Prints to `stderr` the string `'Trace: '`, followed by the [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args)
|
||||
* Prints to `stderr` the string `'Trace: '`, followed by the [`util.format()`](https://nodejs.org/docs/latest-v24.x/api/util.html#utilformatformat-args)
|
||||
* formatted message and stack trace to the current position in the code.
|
||||
*
|
||||
* ```js
|
||||
@@ -361,12 +361,12 @@ declare module "node:console" {
|
||||
* The module exports two specific components:
|
||||
*
|
||||
* * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream.
|
||||
* * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstdout) and
|
||||
* [`process.stderr`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstderr). The global `console` can be used without calling `require('console')`.
|
||||
* * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v24.x/api/process.html#processstdout) and
|
||||
* [`process.stderr`](https://nodejs.org/docs/latest-v24.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module.
|
||||
*
|
||||
* _**Warning**_: The global console object's methods are neither consistently
|
||||
* synchronous like the browser APIs they resemble, nor are they consistently
|
||||
* asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v20.x/api/process.html#a-note-on-process-io) for
|
||||
* asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v24.x/api/process.html#a-note-on-process-io) for
|
||||
* more information.
|
||||
*
|
||||
* Example using the global `console`:
|
||||
@@ -410,7 +410,7 @@ declare module "node:console" {
|
||||
* myConsole.warn(`Danger ${name}! Danger!`);
|
||||
* // Prints: Danger Will Robinson! Danger!, to err
|
||||
* ```
|
||||
* @see [source](https://github.com/nodejs/node/blob/v20.11.1/lib/console.js)
|
||||
* @see [source](https://github.com/nodejs/node/blob/v24.x/lib/console.js)
|
||||
*/
|
||||
namespace console {
|
||||
interface ConsoleConstructorOptions {
|
||||
@@ -431,9 +431,10 @@ declare module "node:console" {
|
||||
colorMode?: boolean | "auto" | undefined;
|
||||
/**
|
||||
* Specifies options that are passed along to
|
||||
* [`util.inspect()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilinspectobject-options).
|
||||
* `util.inspect()`. Can be an options object or, if different options
|
||||
* for stdout and stderr are desired, a `Map` from stream objects to options.
|
||||
*/
|
||||
inspectOptions?: InspectOptions | undefined;
|
||||
inspectOptions?: InspectOptions | ReadonlyMap<NodeJS.WritableStream, InspectOptions> | undefined;
|
||||
/**
|
||||
* Set group indentation.
|
||||
* @default 2
|
||||
|
||||
26
backend/node_modules/@types/node/constants.d.ts
generated
vendored
26
backend/node_modules/@types/node/constants.d.ts
generated
vendored
@@ -1,16 +1,18 @@
|
||||
/** @deprecated since v6.3.0 - use constants property exposed by the relevant module instead. */
|
||||
/**
|
||||
* @deprecated The `node:constants` module is deprecated. When requiring access to constants
|
||||
* relevant to specific Node.js builtin modules, developers should instead refer
|
||||
* to the `constants` property exposed by the relevant module. For instance,
|
||||
* `require('node:fs').constants` and `require('node:os').constants`.
|
||||
*/
|
||||
declare module "constants" {
|
||||
import { constants as osConstants, SignalConstants } from "node:os";
|
||||
import { constants as cryptoConstants } from "node:crypto";
|
||||
import { constants as fsConstants } from "node:fs";
|
||||
|
||||
const exp:
|
||||
& typeof osConstants.errno
|
||||
& typeof osConstants.priority
|
||||
& SignalConstants
|
||||
& typeof cryptoConstants
|
||||
& typeof fsConstants;
|
||||
export = exp;
|
||||
const constants:
|
||||
& typeof import("node:os").constants.dlopen
|
||||
& typeof import("node:os").constants.errno
|
||||
& typeof import("node:os").constants.priority
|
||||
& typeof import("node:os").constants.signals
|
||||
& typeof import("node:fs").constants
|
||||
& typeof import("node:crypto").constants;
|
||||
export = constants;
|
||||
}
|
||||
|
||||
declare module "node:constants" {
|
||||
|
||||
1779
backend/node_modules/@types/node/crypto.d.ts
generated
vendored
1779
backend/node_modules/@types/node/crypto.d.ts
generated
vendored
File diff suppressed because it is too large
Load Diff
36
backend/node_modules/@types/node/dgram.d.ts
generated
vendored
36
backend/node_modules/@types/node/dgram.d.ts
generated
vendored
@@ -23,10 +23,11 @@
|
||||
* server.bind(41234);
|
||||
* // Prints: server listening 0.0.0.0:41234
|
||||
* ```
|
||||
* @see [source](https://github.com/nodejs/node/blob/v20.13.1/lib/dgram.js)
|
||||
* @see [source](https://github.com/nodejs/node/blob/v24.x/lib/dgram.js)
|
||||
*/
|
||||
declare module "dgram" {
|
||||
import { AddressInfo } from "node:net";
|
||||
import { NonSharedBuffer } from "node:buffer";
|
||||
import { AddressInfo, BlockList } from "node:net";
|
||||
import * as dns from "node:dns";
|
||||
import { Abortable, EventEmitter } from "node:events";
|
||||
interface RemoteInfo {
|
||||
@@ -45,6 +46,7 @@ declare module "dgram" {
|
||||
interface SocketOptions extends Abortable {
|
||||
type: SocketType;
|
||||
reuseAddr?: boolean | undefined;
|
||||
reusePort?: boolean | undefined;
|
||||
/**
|
||||
* @default false
|
||||
*/
|
||||
@@ -58,6 +60,8 @@ declare module "dgram" {
|
||||
callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void,
|
||||
) => void)
|
||||
| undefined;
|
||||
receiveBlockList?: BlockList | undefined;
|
||||
sendBlockList?: BlockList | undefined;
|
||||
}
|
||||
/**
|
||||
* Creates a `dgram.Socket` object. Once the socket is created, calling `socket.bind()` will instruct the socket to begin listening for datagram
|
||||
@@ -82,8 +86,8 @@ declare module "dgram" {
|
||||
* @param options Available options are:
|
||||
* @param callback Attached as a listener for `'message'` events. Optional.
|
||||
*/
|
||||
function createSocket(type: SocketType, callback?: (msg: Buffer, rinfo: RemoteInfo) => void): Socket;
|
||||
function createSocket(options: SocketOptions, callback?: (msg: Buffer, rinfo: RemoteInfo) => void): Socket;
|
||||
function createSocket(type: SocketType, callback?: (msg: NonSharedBuffer, rinfo: RemoteInfo) => void): Socket;
|
||||
function createSocket(options: SocketOptions, callback?: (msg: NonSharedBuffer, rinfo: RemoteInfo) => void): Socket;
|
||||
/**
|
||||
* Encapsulates the datagram functionality.
|
||||
*
|
||||
@@ -352,22 +356,22 @@ declare module "dgram" {
|
||||
* @param callback Called when the message has been sent.
|
||||
*/
|
||||
send(
|
||||
msg: string | Uint8Array | readonly any[],
|
||||
msg: string | NodeJS.ArrayBufferView | readonly any[],
|
||||
port?: number,
|
||||
address?: string,
|
||||
callback?: (error: Error | null, bytes: number) => void,
|
||||
): void;
|
||||
send(
|
||||
msg: string | Uint8Array | readonly any[],
|
||||
msg: string | NodeJS.ArrayBufferView | readonly any[],
|
||||
port?: number,
|
||||
callback?: (error: Error | null, bytes: number) => void,
|
||||
): void;
|
||||
send(
|
||||
msg: string | Uint8Array | readonly any[],
|
||||
msg: string | NodeJS.ArrayBufferView | readonly any[],
|
||||
callback?: (error: Error | null, bytes: number) => void,
|
||||
): void;
|
||||
send(
|
||||
msg: string | Uint8Array,
|
||||
msg: string | NodeJS.ArrayBufferView,
|
||||
offset: number,
|
||||
length: number,
|
||||
port?: number,
|
||||
@@ -375,14 +379,14 @@ declare module "dgram" {
|
||||
callback?: (error: Error | null, bytes: number) => void,
|
||||
): void;
|
||||
send(
|
||||
msg: string | Uint8Array,
|
||||
msg: string | NodeJS.ArrayBufferView,
|
||||
offset: number,
|
||||
length: number,
|
||||
port?: number,
|
||||
callback?: (error: Error | null, bytes: number) => void,
|
||||
): void;
|
||||
send(
|
||||
msg: string | Uint8Array,
|
||||
msg: string | NodeJS.ArrayBufferView,
|
||||
offset: number,
|
||||
length: number,
|
||||
callback?: (error: Error | null, bytes: number) => void,
|
||||
@@ -553,37 +557,37 @@ declare module "dgram" {
|
||||
addListener(event: "connect", listener: () => void): this;
|
||||
addListener(event: "error", listener: (err: Error) => void): this;
|
||||
addListener(event: "listening", listener: () => void): this;
|
||||
addListener(event: "message", listener: (msg: Buffer, rinfo: RemoteInfo) => void): this;
|
||||
addListener(event: "message", listener: (msg: NonSharedBuffer, rinfo: RemoteInfo) => void): this;
|
||||
emit(event: string | symbol, ...args: any[]): boolean;
|
||||
emit(event: "close"): boolean;
|
||||
emit(event: "connect"): boolean;
|
||||
emit(event: "error", err: Error): boolean;
|
||||
emit(event: "listening"): boolean;
|
||||
emit(event: "message", msg: Buffer, rinfo: RemoteInfo): boolean;
|
||||
emit(event: "message", msg: NonSharedBuffer, rinfo: RemoteInfo): boolean;
|
||||
on(event: string, listener: (...args: any[]) => void): this;
|
||||
on(event: "close", listener: () => void): this;
|
||||
on(event: "connect", listener: () => void): this;
|
||||
on(event: "error", listener: (err: Error) => void): this;
|
||||
on(event: "listening", listener: () => void): this;
|
||||
on(event: "message", listener: (msg: Buffer, rinfo: RemoteInfo) => void): this;
|
||||
on(event: "message", listener: (msg: NonSharedBuffer, rinfo: RemoteInfo) => void): this;
|
||||
once(event: string, listener: (...args: any[]) => void): this;
|
||||
once(event: "close", listener: () => void): this;
|
||||
once(event: "connect", listener: () => void): this;
|
||||
once(event: "error", listener: (err: Error) => void): this;
|
||||
once(event: "listening", listener: () => void): this;
|
||||
once(event: "message", listener: (msg: Buffer, rinfo: RemoteInfo) => void): this;
|
||||
once(event: "message", listener: (msg: NonSharedBuffer, rinfo: RemoteInfo) => void): this;
|
||||
prependListener(event: string, listener: (...args: any[]) => void): this;
|
||||
prependListener(event: "close", listener: () => void): this;
|
||||
prependListener(event: "connect", listener: () => void): this;
|
||||
prependListener(event: "error", listener: (err: Error) => void): this;
|
||||
prependListener(event: "listening", listener: () => void): this;
|
||||
prependListener(event: "message", listener: (msg: Buffer, rinfo: RemoteInfo) => void): this;
|
||||
prependListener(event: "message", listener: (msg: NonSharedBuffer, rinfo: RemoteInfo) => void): this;
|
||||
prependOnceListener(event: string, listener: (...args: any[]) => void): this;
|
||||
prependOnceListener(event: "close", listener: () => void): this;
|
||||
prependOnceListener(event: "connect", listener: () => void): this;
|
||||
prependOnceListener(event: "error", listener: (err: Error) => void): this;
|
||||
prependOnceListener(event: "listening", listener: () => void): this;
|
||||
prependOnceListener(event: "message", listener: (msg: Buffer, rinfo: RemoteInfo) => void): this;
|
||||
prependOnceListener(event: "message", listener: (msg: NonSharedBuffer, rinfo: RemoteInfo) => void): this;
|
||||
/**
|
||||
* Calls `socket.close()` and returns a promise that fulfills when the socket has closed.
|
||||
* @since v20.5.0
|
||||
|
||||
58
backend/node_modules/@types/node/diagnostics_channel.d.ts
generated
vendored
58
backend/node_modules/@types/node/diagnostics_channel.d.ts
generated
vendored
@@ -20,7 +20,7 @@
|
||||
* should generally include the module name to avoid collisions with data from
|
||||
* other modules.
|
||||
* @since v15.1.0, v14.17.0
|
||||
* @see [source](https://github.com/nodejs/node/blob/v20.13.1/lib/diagnostics_channel.js)
|
||||
* @see [source](https://github.com/nodejs/node/blob/v24.x/lib/diagnostics_channel.js)
|
||||
*/
|
||||
declare module "diagnostics_channel" {
|
||||
import { AsyncLocalStorage } from "node:async_hooks";
|
||||
@@ -189,7 +189,6 @@ declare module "diagnostics_channel" {
|
||||
* });
|
||||
* ```
|
||||
* @since v15.1.0, v14.17.0
|
||||
* @deprecated Since v18.7.0,v16.17.0 - Use {@link subscribe(name, onMessage)}
|
||||
* @param onMessage The handler to receive channel messages
|
||||
*/
|
||||
subscribe(onMessage: ChannelListener): void;
|
||||
@@ -210,7 +209,6 @@ declare module "diagnostics_channel" {
|
||||
* channel.unsubscribe(onMessage);
|
||||
* ```
|
||||
* @since v15.1.0, v14.17.0
|
||||
* @deprecated Since v18.7.0,v16.17.0 - Use {@link unsubscribe(name, onMessage)}
|
||||
* @param onMessage The previous subscribed handler to remove
|
||||
* @return `true` if the handler was found, `false` otherwise.
|
||||
*/
|
||||
@@ -259,7 +257,7 @@ declare module "diagnostics_channel" {
|
||||
* @param store The store to unbind from the channel.
|
||||
* @return `true` if the store was found, `false` otherwise.
|
||||
*/
|
||||
unbindStore(store: any): void;
|
||||
unbindStore(store: AsyncLocalStorage<StoreType>): boolean;
|
||||
/**
|
||||
* Applies the given data to any AsyncLocalStorage instances bound to the channel
|
||||
* for the duration of the given function, then publishes to the channel within
|
||||
@@ -297,7 +295,12 @@ declare module "diagnostics_channel" {
|
||||
* @param thisArg The receiver to be used for the function call.
|
||||
* @param args Optional arguments to pass to the function.
|
||||
*/
|
||||
runStores(): void;
|
||||
runStores<ThisArg = any, Args extends any[] = any[], Result = any>(
|
||||
context: ContextType,
|
||||
fn: (this: ThisArg, ...args: Args) => Result,
|
||||
thisArg?: ThisArg,
|
||||
...args: Args
|
||||
): Result;
|
||||
}
|
||||
interface TracingChannelSubscribers<ContextType extends object> {
|
||||
start: (message: ContextType) => void;
|
||||
@@ -441,12 +444,12 @@ declare module "diagnostics_channel" {
|
||||
* @param args Optional arguments to pass to the function
|
||||
* @return The return value of the given function
|
||||
*/
|
||||
traceSync<ThisArg = any, Args extends any[] = any[]>(
|
||||
fn: (this: ThisArg, ...args: Args) => any,
|
||||
traceSync<ThisArg = any, Args extends any[] = any[], Result = any>(
|
||||
fn: (this: ThisArg, ...args: Args) => Result,
|
||||
context?: ContextType,
|
||||
thisArg?: ThisArg,
|
||||
...args: Args
|
||||
): void;
|
||||
): Result;
|
||||
/**
|
||||
* Trace a promise-returning function call. This will always produce a `start event` and `end event` around the synchronous portion of the
|
||||
* function execution, and will produce an `asyncStart event` and `asyncEnd event` when a promise continuation is reached. It may also
|
||||
@@ -476,12 +479,12 @@ declare module "diagnostics_channel" {
|
||||
* @param args Optional arguments to pass to the function
|
||||
* @return Chained from promise returned by the given function
|
||||
*/
|
||||
tracePromise<ThisArg = any, Args extends any[] = any[]>(
|
||||
fn: (this: ThisArg, ...args: Args) => Promise<any>,
|
||||
tracePromise<ThisArg = any, Args extends any[] = any[], Result = any>(
|
||||
fn: (this: ThisArg, ...args: Args) => Promise<Result>,
|
||||
context?: ContextType,
|
||||
thisArg?: ThisArg,
|
||||
...args: Args
|
||||
): void;
|
||||
): Promise<Result>;
|
||||
/**
|
||||
* Trace a callback-receiving function call. This will always produce a `start event` and `end event` around the synchronous portion of the
|
||||
* function execution, and will produce a `asyncStart event` and `asyncEnd event` around the callback execution. It may also produce an `error event` if the given function throws an error or
|
||||
@@ -540,13 +543,32 @@ declare module "diagnostics_channel" {
|
||||
* @param args Optional arguments to pass to the function
|
||||
* @return The return value of the given function
|
||||
*/
|
||||
traceCallback<Fn extends (this: any, ...args: any) => any>(
|
||||
fn: Fn,
|
||||
position: number | undefined,
|
||||
context: ContextType | undefined,
|
||||
thisArg: any,
|
||||
...args: Parameters<Fn>
|
||||
): void;
|
||||
traceCallback<ThisArg = any, Args extends any[] = any[], Result = any>(
|
||||
fn: (this: ThisArg, ...args: Args) => Result,
|
||||
position?: number,
|
||||
context?: ContextType,
|
||||
thisArg?: ThisArg,
|
||||
...args: Args
|
||||
): Result;
|
||||
/**
|
||||
* `true` if any of the individual channels has a subscriber, `false` if not.
|
||||
*
|
||||
* This is a helper method available on a {@link TracingChannel} instance to check
|
||||
* if any of the [TracingChannel Channels](https://nodejs.org/api/diagnostics_channel.html#tracingchannel-channels) have subscribers.
|
||||
* A `true` is returned if any of them have at least one subscriber, a `false` is returned otherwise.
|
||||
*
|
||||
* ```js
|
||||
* const diagnostics_channel = require('node:diagnostics_channel');
|
||||
*
|
||||
* const channels = diagnostics_channel.tracingChannel('my-channel');
|
||||
*
|
||||
* if (channels.hasSubscribers) {
|
||||
* // Do something
|
||||
* }
|
||||
* ```
|
||||
* @since v22.0.0, v20.13.0
|
||||
*/
|
||||
readonly hasSubscribers: boolean;
|
||||
}
|
||||
}
|
||||
declare module "node:diagnostics_channel" {
|
||||
|
||||
145
backend/node_modules/@types/node/dns.d.ts
generated
vendored
145
backend/node_modules/@types/node/dns.d.ts
generated
vendored
@@ -9,7 +9,7 @@
|
||||
* system do, use {@link lookup}.
|
||||
*
|
||||
* ```js
|
||||
* const dns = require('node:dns');
|
||||
* import dns from 'node:dns';
|
||||
*
|
||||
* dns.lookup('example.org', (err, address, family) => {
|
||||
* console.log('address: %j family: IPv%s', address, family);
|
||||
@@ -23,7 +23,7 @@
|
||||
* DNS queries, bypassing other name-resolution facilities.
|
||||
*
|
||||
* ```js
|
||||
* const dns = require('node:dns');
|
||||
* import dns from 'node:dns';
|
||||
*
|
||||
* dns.resolve4('archive.org', (err, addresses) => {
|
||||
* if (err) throw err;
|
||||
@@ -41,8 +41,8 @@
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* See the [Implementation considerations section](https://nodejs.org/docs/latest-v20.x/api/dns.html#implementation-considerations) for more information.
|
||||
* @see [source](https://github.com/nodejs/node/blob/v20.13.1/lib/dns.js)
|
||||
* See the [Implementation considerations section](https://nodejs.org/docs/latest-v24.x/api/dns.html#implementation-considerations) for more information.
|
||||
* @see [source](https://github.com/nodejs/node/blob/v24.x/lib/dns.js)
|
||||
*/
|
||||
declare module "dns" {
|
||||
import * as dnsPromises from "node:dns/promises";
|
||||
@@ -71,7 +71,7 @@ declare module "dns" {
|
||||
*/
|
||||
family?: number | "IPv4" | "IPv6" | undefined;
|
||||
/**
|
||||
* One or more [supported `getaddrinfo`](https://nodejs.org/docs/latest-v20.x/api/dns.html#supported-getaddrinfo-flags) flags. Multiple flags may be
|
||||
* One or more [supported `getaddrinfo`](https://nodejs.org/docs/latest-v24.x/api/dns.html#supported-getaddrinfo-flags) flags. Multiple flags may be
|
||||
* passed by bitwise `OR`ing their values.
|
||||
*/
|
||||
hints?: number | undefined;
|
||||
@@ -84,16 +84,17 @@ declare module "dns" {
|
||||
* When `verbatim`, the resolved addresses are return unsorted. When `ipv4first`, the resolved addresses are sorted
|
||||
* by placing IPv4 addresses before IPv6 addresses. When `ipv6first`, the resolved addresses are sorted by placing IPv6
|
||||
* addresses before IPv4 addresses. Default value is configurable using
|
||||
* {@link setDefaultResultOrder} or [`--dns-result-order`](https://nodejs.org/docs/latest-v20.x/api/cli.html#--dns-result-orderorder).
|
||||
* {@link setDefaultResultOrder} or [`--dns-result-order`](https://nodejs.org/docs/latest-v24.x/api/cli.html#--dns-result-orderorder).
|
||||
* @default `verbatim` (addresses are not reordered)
|
||||
* @since v22.1.0
|
||||
*/
|
||||
order?: "ipv4first" | "ipv6first" | "verbatim" | undefined;
|
||||
/**
|
||||
* When `true`, the callback receives IPv4 and IPv6 addresses in the order the DNS resolver returned them. When `false`, IPv4
|
||||
* addresses are placed before IPv6 addresses. This option will be deprecated in favor of `order`. When both are specified,
|
||||
* `order` has higher precedence. New code should only use `order`. Default value is configurable using {@link setDefaultResultOrder}
|
||||
* or [`--dns-result-order`](https://nodejs.org/docs/latest-v20.x/api/cli.html#--dns-result-orderorder).
|
||||
* @default true (addresses are not reordered)
|
||||
* @deprecated Please use `order` option
|
||||
*/
|
||||
verbatim?: boolean | undefined;
|
||||
}
|
||||
@@ -132,13 +133,13 @@ declare module "dns" {
|
||||
* The implementation uses an operating system facility that can associate names
|
||||
* with addresses and vice versa. This implementation can have subtle but
|
||||
* important consequences on the behavior of any Node.js program. Please take some
|
||||
* time to consult the [Implementation considerations section](https://nodejs.org/docs/latest-v20.x/api/dns.html#implementation-considerations)
|
||||
* time to consult the [Implementation considerations section](https://nodejs.org/docs/latest-v24.x/api/dns.html#implementation-considerations)
|
||||
* before using `dns.lookup()`.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* ```js
|
||||
* const dns = require('node:dns');
|
||||
* import dns from 'node:dns';
|
||||
* const options = {
|
||||
* family: 6,
|
||||
* hints: dns.ADDRCONFIG | dns.V4MAPPED,
|
||||
@@ -154,7 +155,7 @@ declare module "dns" {
|
||||
* // addresses: [{"address":"2606:2800:220:1:248:1893:25c8:1946","family":6}]
|
||||
* ```
|
||||
*
|
||||
* If this method is invoked as its [util.promisify()](https://nodejs.org/docs/latest-v20.x/api/util.html#utilpromisifyoriginal) ed
|
||||
* If this method is invoked as its [util.promisify()](https://nodejs.org/docs/latest-v24.x/api/util.html#utilpromisifyoriginal) ed
|
||||
* version, and `all` is not set to `true`, it returns a `Promise` for an `Object` with `address` and `family` properties.
|
||||
* @since v0.1.90
|
||||
*/
|
||||
@@ -194,18 +195,18 @@ declare module "dns" {
|
||||
* If `address` is not a valid IP address, a `TypeError` will be thrown.
|
||||
* The `port` will be coerced to a number. If it is not a legal port, a `TypeError` will be thrown.
|
||||
*
|
||||
* On an error, `err` is an [`Error`](https://nodejs.org/docs/latest-v20.x/api/errors.html#class-error) object,
|
||||
* On an error, `err` is an [`Error`](https://nodejs.org/docs/latest-v24.x/api/errors.html#class-error) object,
|
||||
* where `err.code` is the error code.
|
||||
*
|
||||
* ```js
|
||||
* const dns = require('node:dns');
|
||||
* import dns from 'node:dns';
|
||||
* dns.lookupService('127.0.0.1', 22, (err, hostname, service) => {
|
||||
* console.log(hostname, service);
|
||||
* // Prints: localhost ssh
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* If this method is invoked as its [util.promisify()](https://nodejs.org/docs/latest-v20.x/api/util.html#utilpromisifyoriginal) ed
|
||||
* If this method is invoked as its [util.promisify()](https://nodejs.org/docs/latest-v24.x/api/util.html#utilpromisifyoriginal) ed
|
||||
* version, it returns a `Promise` for an `Object` with `hostname` and `service` properties.
|
||||
* @since v0.11.14
|
||||
*/
|
||||
@@ -249,6 +250,9 @@ declare module "dns" {
|
||||
contactemail?: string | undefined;
|
||||
contactphone?: string | undefined;
|
||||
}
|
||||
export interface AnyCaaRecord extends CaaRecord {
|
||||
type: "CAA";
|
||||
}
|
||||
export interface MxRecord {
|
||||
priority: number;
|
||||
exchange: string;
|
||||
@@ -288,6 +292,15 @@ declare module "dns" {
|
||||
export interface AnySrvRecord extends SrvRecord {
|
||||
type: "SRV";
|
||||
}
|
||||
export interface TlsaRecord {
|
||||
certUsage: number;
|
||||
selector: number;
|
||||
match: number;
|
||||
data: ArrayBuffer;
|
||||
}
|
||||
export interface AnyTlsaRecord extends TlsaRecord {
|
||||
type: "TLSA";
|
||||
}
|
||||
export interface AnyTxtRecord {
|
||||
type: "TXT";
|
||||
entries: string[];
|
||||
@@ -307,6 +320,7 @@ declare module "dns" {
|
||||
export type AnyRecord =
|
||||
| AnyARecord
|
||||
| AnyAaaaRecord
|
||||
| AnyCaaRecord
|
||||
| AnyCnameRecord
|
||||
| AnyMxRecord
|
||||
| AnyNaptrRecord
|
||||
@@ -314,6 +328,7 @@ declare module "dns" {
|
||||
| AnyPtrRecord
|
||||
| AnySoaRecord
|
||||
| AnySrvRecord
|
||||
| AnyTlsaRecord
|
||||
| AnyTxtRecord;
|
||||
/**
|
||||
* Uses the DNS protocol to resolve a host name (e.g. `'nodejs.org'`) into an array
|
||||
@@ -322,7 +337,7 @@ declare module "dns" {
|
||||
*
|
||||
* <omitted>
|
||||
*
|
||||
* On error, `err` is an [`Error`](https://nodejs.org/docs/latest-v20.x/api/errors.html#class-error) object,
|
||||
* On error, `err` is an [`Error`](https://nodejs.org/docs/latest-v24.x/api/errors.html#class-error) object,
|
||||
* where `err.code` is one of the `DNS error codes`.
|
||||
* @since v0.1.27
|
||||
* @param hostname Host name to resolve.
|
||||
@@ -334,12 +349,7 @@ declare module "dns" {
|
||||
): void;
|
||||
export function resolve(
|
||||
hostname: string,
|
||||
rrtype: "A",
|
||||
callback: (err: NodeJS.ErrnoException | null, addresses: string[]) => void,
|
||||
): void;
|
||||
export function resolve(
|
||||
hostname: string,
|
||||
rrtype: "AAAA",
|
||||
rrtype: "A" | "AAAA" | "CNAME" | "NS" | "PTR",
|
||||
callback: (err: NodeJS.ErrnoException | null, addresses: string[]) => void,
|
||||
): void;
|
||||
export function resolve(
|
||||
@@ -349,8 +359,8 @@ declare module "dns" {
|
||||
): void;
|
||||
export function resolve(
|
||||
hostname: string,
|
||||
rrtype: "CNAME",
|
||||
callback: (err: NodeJS.ErrnoException | null, addresses: string[]) => void,
|
||||
rrtype: "CAA",
|
||||
callback: (err: NodeJS.ErrnoException | null, address: CaaRecord[]) => void,
|
||||
): void;
|
||||
export function resolve(
|
||||
hostname: string,
|
||||
@@ -362,16 +372,6 @@ declare module "dns" {
|
||||
rrtype: "NAPTR",
|
||||
callback: (err: NodeJS.ErrnoException | null, addresses: NaptrRecord[]) => void,
|
||||
): void;
|
||||
export function resolve(
|
||||
hostname: string,
|
||||
rrtype: "NS",
|
||||
callback: (err: NodeJS.ErrnoException | null, addresses: string[]) => void,
|
||||
): void;
|
||||
export function resolve(
|
||||
hostname: string,
|
||||
rrtype: "PTR",
|
||||
callback: (err: NodeJS.ErrnoException | null, addresses: string[]) => void,
|
||||
): void;
|
||||
export function resolve(
|
||||
hostname: string,
|
||||
rrtype: "SOA",
|
||||
@@ -382,6 +382,11 @@ declare module "dns" {
|
||||
rrtype: "SRV",
|
||||
callback: (err: NodeJS.ErrnoException | null, addresses: SrvRecord[]) => void,
|
||||
): void;
|
||||
export function resolve(
|
||||
hostname: string,
|
||||
rrtype: "TLSA",
|
||||
callback: (err: NodeJS.ErrnoException | null, addresses: TlsaRecord[]) => void,
|
||||
): void;
|
||||
export function resolve(
|
||||
hostname: string,
|
||||
rrtype: "TXT",
|
||||
@@ -392,21 +397,42 @@ declare module "dns" {
|
||||
rrtype: string,
|
||||
callback: (
|
||||
err: NodeJS.ErrnoException | null,
|
||||
addresses: string[] | MxRecord[] | NaptrRecord[] | SoaRecord | SrvRecord[] | string[][] | AnyRecord[],
|
||||
addresses:
|
||||
| string[]
|
||||
| CaaRecord[]
|
||||
| MxRecord[]
|
||||
| NaptrRecord[]
|
||||
| SoaRecord
|
||||
| SrvRecord[]
|
||||
| TlsaRecord[]
|
||||
| string[][]
|
||||
| AnyRecord[],
|
||||
) => void,
|
||||
): void;
|
||||
export namespace resolve {
|
||||
function __promisify__(hostname: string, rrtype?: "A" | "AAAA" | "CNAME" | "NS" | "PTR"): Promise<string[]>;
|
||||
function __promisify__(hostname: string, rrtype: "ANY"): Promise<AnyRecord[]>;
|
||||
function __promisify__(hostname: string, rrtype: "CAA"): Promise<CaaRecord[]>;
|
||||
function __promisify__(hostname: string, rrtype: "MX"): Promise<MxRecord[]>;
|
||||
function __promisify__(hostname: string, rrtype: "NAPTR"): Promise<NaptrRecord[]>;
|
||||
function __promisify__(hostname: string, rrtype: "SOA"): Promise<SoaRecord>;
|
||||
function __promisify__(hostname: string, rrtype: "SRV"): Promise<SrvRecord[]>;
|
||||
function __promisify__(hostname: string, rrtype: "TLSA"): Promise<TlsaRecord[]>;
|
||||
function __promisify__(hostname: string, rrtype: "TXT"): Promise<string[][]>;
|
||||
function __promisify__(
|
||||
hostname: string,
|
||||
rrtype: string,
|
||||
): Promise<string[] | MxRecord[] | NaptrRecord[] | SoaRecord | SrvRecord[] | string[][] | AnyRecord[]>;
|
||||
): Promise<
|
||||
| string[]
|
||||
| CaaRecord[]
|
||||
| MxRecord[]
|
||||
| NaptrRecord[]
|
||||
| SoaRecord
|
||||
| SrvRecord[]
|
||||
| TlsaRecord[]
|
||||
| string[][]
|
||||
| AnyRecord[]
|
||||
>;
|
||||
}
|
||||
/**
|
||||
* Uses the DNS protocol to resolve a IPv4 addresses (`A` records) for the `hostname`. The `addresses` argument passed to the `callback` function
|
||||
@@ -608,6 +634,33 @@ declare module "dns" {
|
||||
export namespace resolveSrv {
|
||||
function __promisify__(hostname: string): Promise<SrvRecord[]>;
|
||||
}
|
||||
/**
|
||||
* Uses the DNS protocol to resolve certificate associations (`TLSA` records) for
|
||||
* the `hostname`. The `records` argument passed to the `callback` function is an
|
||||
* array of objects with these properties:
|
||||
*
|
||||
* * `certUsage`
|
||||
* * `selector`
|
||||
* * `match`
|
||||
* * `data`
|
||||
*
|
||||
* ```js
|
||||
* {
|
||||
* certUsage: 3,
|
||||
* selector: 1,
|
||||
* match: 1,
|
||||
* data: [ArrayBuffer]
|
||||
* }
|
||||
* ```
|
||||
* @since v23.9.0, v22.15.0
|
||||
*/
|
||||
export function resolveTlsa(
|
||||
hostname: string,
|
||||
callback: (err: NodeJS.ErrnoException | null, addresses: TlsaRecord[]) => void,
|
||||
): void;
|
||||
export namespace resolveTlsa {
|
||||
function __promisify__(hostname: string): Promise<TlsaRecord[]>;
|
||||
}
|
||||
/**
|
||||
* Uses the DNS protocol to resolve text queries (`TXT` records) for the `hostname`. The `records` argument passed to the `callback` function is a
|
||||
* two-dimensional array of the text records available for `hostname` (e.g.`[ ['v=spf1 ip4:0.0.0.0 ', '~all' ] ]`). Each sub-array contains TXT chunks of
|
||||
@@ -663,8 +716,8 @@ declare module "dns" {
|
||||
* Performs a reverse DNS query that resolves an IPv4 or IPv6 address to an
|
||||
* array of host names.
|
||||
*
|
||||
* On error, `err` is an [`Error`](https://nodejs.org/docs/latest-v20.x/api/errors.html#class-error) object, where `err.code` is
|
||||
* one of the [DNS error codes](https://nodejs.org/docs/latest-v20.x/api/dns.html#error-codes).
|
||||
* On error, `err` is an [`Error`](https://nodejs.org/docs/latest-v24.x/api/errors.html#class-error) object, where `err.code` is
|
||||
* one of the [DNS error codes](https://nodejs.org/docs/latest-v24.x/api/dns.html#error-codes).
|
||||
* @since v0.1.16
|
||||
*/
|
||||
export function reverse(
|
||||
@@ -672,7 +725,7 @@ declare module "dns" {
|
||||
callback: (err: NodeJS.ErrnoException | null, hostnames: string[]) => void,
|
||||
): void;
|
||||
/**
|
||||
* Get the default value for `order` in {@link lookup} and [`dnsPromises.lookup()`](https://nodejs.org/docs/latest-v20.x/api/dns.html#dnspromiseslookuphostname-options).
|
||||
* Get the default value for `order` in {@link lookup} and [`dnsPromises.lookup()`](https://nodejs.org/docs/latest-v24.x/api/dns.html#dnspromiseslookuphostname-options).
|
||||
* The value could be:
|
||||
*
|
||||
* * `ipv4first`: for `order` defaulting to `ipv4first`.
|
||||
@@ -727,7 +780,7 @@ declare module "dns" {
|
||||
*/
|
||||
export function getServers(): string[];
|
||||
/**
|
||||
* Set the default value of `order` in {@link lookup} and [`dnsPromises.lookup()`](https://nodejs.org/docs/latest-v20.x/api/dns.html#dnspromiseslookuphostname-options).
|
||||
* Set the default value of `order` in {@link lookup} and [`dnsPromises.lookup()`](https://nodejs.org/docs/latest-v24.x/api/dns.html#dnspromiseslookuphostname-options).
|
||||
* The value could be:
|
||||
*
|
||||
* * `ipv4first`: sets default `order` to `ipv4first`.
|
||||
@@ -735,8 +788,8 @@ declare module "dns" {
|
||||
* * `verbatim`: sets default `order` to `verbatim`.
|
||||
*
|
||||
* The default is `verbatim` and {@link setDefaultResultOrder} have higher
|
||||
* priority than [`--dns-result-order`](https://nodejs.org/docs/latest-v20.x/api/cli.html#--dns-result-orderorder). When using
|
||||
* [worker threads](https://nodejs.org/docs/latest-v20.x/api/worker_threads.html), {@link setDefaultResultOrder} from the main
|
||||
* priority than [`--dns-result-order`](https://nodejs.org/docs/latest-v24.x/api/cli.html#--dns-result-orderorder). When using
|
||||
* [worker threads](https://nodejs.org/docs/latest-v24.x/api/worker_threads.html), {@link setDefaultResultOrder} from the main
|
||||
* thread won't affect the default dns orders in workers.
|
||||
* @since v16.4.0, v14.18.0
|
||||
* @param order must be `'ipv4first'`, `'ipv6first'` or `'verbatim'`.
|
||||
@@ -776,17 +829,22 @@ declare module "dns" {
|
||||
* The number of tries the resolver will try contacting each name server before giving up.
|
||||
* @default 4
|
||||
*/
|
||||
tries?: number;
|
||||
tries?: number | undefined;
|
||||
/**
|
||||
* The max retry timeout, in milliseconds.
|
||||
* @default 0
|
||||
*/
|
||||
maxTimeout?: number | undefined;
|
||||
}
|
||||
/**
|
||||
* An independent resolver for DNS requests.
|
||||
*
|
||||
* Creating a new resolver uses the default server settings. Setting
|
||||
* the servers used for a resolver using [`resolver.setServers()`](https://nodejs.org/docs/latest-v20.x/api/dns.html#dnssetserversservers) does not affect
|
||||
* the servers used for a resolver using [`resolver.setServers()`](https://nodejs.org/docs/latest-v24.x/api/dns.html#dnssetserversservers) does not affect
|
||||
* other resolvers:
|
||||
*
|
||||
* ```js
|
||||
* const { Resolver } = require('node:dns');
|
||||
* import { Resolver } from 'node:dns';
|
||||
* const resolver = new Resolver();
|
||||
* resolver.setServers(['4.4.4.4']);
|
||||
*
|
||||
@@ -837,6 +895,7 @@ declare module "dns" {
|
||||
resolvePtr: typeof resolvePtr;
|
||||
resolveSoa: typeof resolveSoa;
|
||||
resolveSrv: typeof resolveSrv;
|
||||
resolveTlsa: typeof resolveTlsa;
|
||||
resolveTxt: typeof resolveTxt;
|
||||
reverse: typeof reverse;
|
||||
/**
|
||||
|
||||
55
backend/node_modules/@types/node/dns/promises.d.ts
generated
vendored
55
backend/node_modules/@types/node/dns/promises.d.ts
generated
vendored
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* The `dns.promises` API provides an alternative set of asynchronous DNS methods
|
||||
* that return `Promise` objects rather than using callbacks. The API is accessible
|
||||
* via `require('node:dns').promises` or `require('node:dns/promises')`.
|
||||
* via `import { promises as dnsPromises } from 'node:dns'` or `import dnsPromises from 'node:dns/promises'`.
|
||||
* @since v10.6.0
|
||||
*/
|
||||
declare module "dns/promises" {
|
||||
@@ -20,6 +20,7 @@ declare module "dns/promises" {
|
||||
ResolveWithTtlOptions,
|
||||
SoaRecord,
|
||||
SrvRecord,
|
||||
TlsaRecord,
|
||||
} from "node:dns";
|
||||
/**
|
||||
* Returns an array of IP address strings, formatted according to [RFC 5952](https://tools.ietf.org/html/rfc5952#section-6),
|
||||
@@ -60,7 +61,7 @@ declare module "dns/promises" {
|
||||
* Example usage:
|
||||
*
|
||||
* ```js
|
||||
* const dns = require('node:dns');
|
||||
* import dns from 'node:dns';
|
||||
* const dnsPromises = dns.promises;
|
||||
* const options = {
|
||||
* family: 6,
|
||||
@@ -96,7 +97,7 @@ declare module "dns/promises" {
|
||||
* On error, the `Promise` is rejected with an [`Error`](https://nodejs.org/docs/latest-v20.x/api/errors.html#class-error) object, where `err.code` is the error code.
|
||||
*
|
||||
* ```js
|
||||
* const dnsPromises = require('node:dns').promises;
|
||||
* import dnsPromises from 'node:dns';
|
||||
* dnsPromises.lookupService('127.0.0.1', 22).then((result) => {
|
||||
* console.log(result.hostname, result.service);
|
||||
* // Prints: localhost ssh
|
||||
@@ -126,22 +127,26 @@ declare module "dns/promises" {
|
||||
* @param [rrtype='A'] Resource record type.
|
||||
*/
|
||||
function resolve(hostname: string): Promise<string[]>;
|
||||
function resolve(hostname: string, rrtype: "A"): Promise<string[]>;
|
||||
function resolve(hostname: string, rrtype: "AAAA"): Promise<string[]>;
|
||||
function resolve(hostname: string, rrtype: "A" | "AAAA" | "CNAME" | "NS" | "PTR"): Promise<string[]>;
|
||||
function resolve(hostname: string, rrtype: "ANY"): Promise<AnyRecord[]>;
|
||||
function resolve(hostname: string, rrtype: "CAA"): Promise<CaaRecord[]>;
|
||||
function resolve(hostname: string, rrtype: "CNAME"): Promise<string[]>;
|
||||
function resolve(hostname: string, rrtype: "MX"): Promise<MxRecord[]>;
|
||||
function resolve(hostname: string, rrtype: "NAPTR"): Promise<NaptrRecord[]>;
|
||||
function resolve(hostname: string, rrtype: "NS"): Promise<string[]>;
|
||||
function resolve(hostname: string, rrtype: "PTR"): Promise<string[]>;
|
||||
function resolve(hostname: string, rrtype: "SOA"): Promise<SoaRecord>;
|
||||
function resolve(hostname: string, rrtype: "SRV"): Promise<SrvRecord[]>;
|
||||
function resolve(hostname: string, rrtype: "TLSA"): Promise<TlsaRecord[]>;
|
||||
function resolve(hostname: string, rrtype: "TXT"): Promise<string[][]>;
|
||||
function resolve(
|
||||
hostname: string,
|
||||
rrtype: string,
|
||||
): Promise<string[] | MxRecord[] | NaptrRecord[] | SoaRecord | SrvRecord[] | string[][] | AnyRecord[]>;
|
||||
function resolve(hostname: string, rrtype: string): Promise<
|
||||
| string[]
|
||||
| CaaRecord[]
|
||||
| MxRecord[]
|
||||
| NaptrRecord[]
|
||||
| SoaRecord
|
||||
| SrvRecord[]
|
||||
| TlsaRecord[]
|
||||
| string[][]
|
||||
| AnyRecord[]
|
||||
>;
|
||||
/**
|
||||
* Uses the DNS protocol to resolve IPv4 addresses (`A` records) for the `hostname`. On success, the `Promise` is resolved with an array of IPv4
|
||||
* addresses (e.g. `['74.125.79.104', '74.125.79.105', '74.125.79.106']`).
|
||||
@@ -292,6 +297,27 @@ declare module "dns/promises" {
|
||||
* @since v10.6.0
|
||||
*/
|
||||
function resolveSrv(hostname: string): Promise<SrvRecord[]>;
|
||||
/**
|
||||
* Uses the DNS protocol to resolve certificate associations (`TLSA` records) for
|
||||
* the `hostname`. On success, the `Promise` is resolved with an array of objectsAdd commentMore actions
|
||||
* with these properties:
|
||||
*
|
||||
* * `certUsage`
|
||||
* * `selector`
|
||||
* * `match`
|
||||
* * `data`
|
||||
*
|
||||
* ```js
|
||||
* {
|
||||
* certUsage: 3,
|
||||
* selector: 1,
|
||||
* match: 1,
|
||||
* data: [ArrayBuffer]
|
||||
* }
|
||||
* ```
|
||||
* @since v23.9.0, v22.15.0
|
||||
*/
|
||||
function resolveTlsa(hostname: string): Promise<TlsaRecord[]>;
|
||||
/**
|
||||
* Uses the DNS protocol to resolve text queries (`TXT` records) for the `hostname`. On success, the `Promise` is resolved with a two-dimensional array
|
||||
* of the text records available for `hostname` (e.g.`[ ['v=spf1 ip4:0.0.0.0 ', '~all' ] ]`). Each sub-array contains TXT chunks of
|
||||
@@ -394,8 +420,8 @@ declare module "dns/promises" {
|
||||
* other resolvers:
|
||||
*
|
||||
* ```js
|
||||
* const { Resolver } = require('node:dns').promises;
|
||||
* const resolver = new Resolver();
|
||||
* import { promises } from 'node:dns';
|
||||
* const resolver = new promises.Resolver();
|
||||
* resolver.setServers(['4.4.4.4']);
|
||||
*
|
||||
* // This request will use the server at 4.4.4.4, independent of global settings.
|
||||
@@ -450,6 +476,7 @@ declare module "dns/promises" {
|
||||
resolvePtr: typeof resolvePtr;
|
||||
resolveSoa: typeof resolveSoa;
|
||||
resolveSrv: typeof resolveSrv;
|
||||
resolveTlsa: typeof resolveTlsa;
|
||||
resolveTxt: typeof resolveTxt;
|
||||
reverse: typeof reverse;
|
||||
/**
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user