Compare commits
5 Commits
main
...
falukant-3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d74f7b852b | ||
|
|
92d6b15c3f | ||
|
|
91f59062f5 | ||
|
|
1674086c73 | ||
|
|
5ddb099f5a |
@@ -1,34 +0,0 @@
|
||||
name: Deploy to production
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Prepare SSH
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
printf "%s" "${{ secrets.PROD_SSH_KEY }}" > ~/.ssh/id_ed25519
|
||||
chmod 600 ~/.ssh/id_ed25519
|
||||
ssh-keyscan -p "${{ secrets.PROD_PORT }}" "${{ secrets.PROD_HOST }}" >> ~/.ssh/known_hosts
|
||||
|
||||
- name: Test SSH connection
|
||||
run: |
|
||||
ssh -i ~/.ssh/id_ed25519 \
|
||||
-o StrictHostKeyChecking=no \
|
||||
-o BatchMode=yes \
|
||||
-p "${{ secrets.PROD_PORT }}" \
|
||||
"${{ secrets.PROD_USER }}@${{ secrets.PROD_HOST }}" \
|
||||
"echo SSH OK"
|
||||
|
||||
- name: Run deployment script
|
||||
run: |
|
||||
ssh -i ~/.ssh/id_ed25519 \
|
||||
-p "${{ secrets.PROD_PORT }}" \
|
||||
"${{ secrets.PROD_USER }}@${{ secrets.PROD_HOST }}" \
|
||||
"/home/tsschulz/deploy-yourpart-bluegreen.sh"
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -5,9 +5,7 @@
|
||||
.depbe.sh
|
||||
node_modules
|
||||
node_modules/*
|
||||
# package-lock.json wird versioniert (npm ci im Deploy braucht konsistente Locks zu package.json)
|
||||
backend/.env
|
||||
backend/.env.local
|
||||
backend/images
|
||||
backend/images/*
|
||||
backend/node_modules
|
||||
@@ -19,9 +17,3 @@ frontend/dist
|
||||
frontend/dist/*
|
||||
frontedtree.txt
|
||||
backend/dist/
|
||||
backend/data/model-cache
|
||||
build
|
||||
build/*
|
||||
.vscode
|
||||
.vscode/*
|
||||
.clang-format
|
||||
|
||||
156
CHURCH_MODELS.md
156
CHURCH_MODELS.md
@@ -1,156 +0,0 @@
|
||||
# Church Models - Übersicht für Daemon-Entwicklung
|
||||
|
||||
## 1. ChurchOfficeType (falukant_type.church_office_type)
|
||||
|
||||
**Schema:** `falukant_type`
|
||||
**Tabelle:** `church_office_type`
|
||||
**Zweck:** Definiert die verschiedenen Kirchenämter-Typen
|
||||
|
||||
```javascript
|
||||
{
|
||||
id: INTEGER (PK, auto-increment)
|
||||
name: STRING (z.B. "pope", "cardinal", "lay-preacher")
|
||||
seatsPerRegion: INTEGER (Anzahl verfügbarer Plätze pro Region)
|
||||
regionType: STRING (z.B. "country", "duchy", "city")
|
||||
hierarchyLevel: INTEGER (0-8, höhere Zahl = höhere Position)
|
||||
}
|
||||
```
|
||||
|
||||
**Beziehungen:**
|
||||
- `hasMany` ChurchOffice (als `offices`)
|
||||
- `hasMany` ChurchApplication (als `applications`)
|
||||
- `hasMany` ChurchOfficeRequirement (als `requirements`)
|
||||
|
||||
---
|
||||
|
||||
## 2. ChurchOfficeRequirement (falukant_predefine.church_office_requirement)
|
||||
|
||||
**Schema:** `falukant_predefine`
|
||||
**Tabelle:** `church_office_requirement`
|
||||
**Zweck:** Definiert Voraussetzungen für Kirchenämter
|
||||
|
||||
```javascript
|
||||
{
|
||||
id: INTEGER (PK, auto-increment)
|
||||
officeTypeId: INTEGER (FK -> ChurchOfficeType.id)
|
||||
prerequisiteOfficeTypeId: INTEGER (FK -> ChurchOfficeType.id, nullable)
|
||||
minTitleLevel: INTEGER (nullable, optional)
|
||||
}
|
||||
```
|
||||
|
||||
**Beziehungen:**
|
||||
- `belongsTo` ChurchOfficeType (als `officeType`)
|
||||
- `belongsTo` ChurchOfficeType (als `prerequisiteOfficeType`)
|
||||
|
||||
---
|
||||
|
||||
## 3. ChurchOffice (falukant_data.church_office)
|
||||
|
||||
**Schema:** `falukant_data`
|
||||
**Tabelle:** `church_office`
|
||||
**Zweck:** Speichert tatsächlich besetzte Kirchenämter
|
||||
|
||||
```javascript
|
||||
{
|
||||
id: INTEGER (PK, auto-increment)
|
||||
officeTypeId: INTEGER (FK -> ChurchOfficeType.id)
|
||||
characterId: INTEGER (FK -> FalukantCharacter.id)
|
||||
regionId: INTEGER (FK -> RegionData.id)
|
||||
supervisorId: INTEGER (FK -> FalukantCharacter.id, nullable)
|
||||
createdAt: DATE
|
||||
updatedAt: DATE
|
||||
}
|
||||
```
|
||||
|
||||
**Beziehungen:**
|
||||
- `belongsTo` ChurchOfficeType (als `type`)
|
||||
- `belongsTo` FalukantCharacter (als `holder`)
|
||||
- `belongsTo` FalukantCharacter (als `supervisor`)
|
||||
- `belongsTo` RegionData (als `region`)
|
||||
|
||||
---
|
||||
|
||||
## 4. ChurchApplication (falukant_data.church_application)
|
||||
|
||||
**Schema:** `falukant_data`
|
||||
**Tabelle:** `church_application`
|
||||
**Zweck:** Speichert Bewerbungen für Kirchenämter
|
||||
|
||||
```javascript
|
||||
{
|
||||
id: INTEGER (PK, auto-increment)
|
||||
officeTypeId: INTEGER (FK -> ChurchOfficeType.id)
|
||||
characterId: INTEGER (FK -> FalukantCharacter.id)
|
||||
regionId: INTEGER (FK -> RegionData.id)
|
||||
supervisorId: INTEGER (FK -> FalukantCharacter.id)
|
||||
status: ENUM('pending', 'approved', 'rejected')
|
||||
decisionDate: DATE (nullable)
|
||||
createdAt: DATE
|
||||
updatedAt: DATE
|
||||
}
|
||||
```
|
||||
|
||||
**Beziehungen:**
|
||||
- `belongsTo` ChurchOfficeType (als `officeType`)
|
||||
- `belongsTo` FalukantCharacter (als `applicant`)
|
||||
- `belongsTo` FalukantCharacter (als `supervisor`)
|
||||
- `belongsTo` RegionData (als `region`)
|
||||
|
||||
---
|
||||
|
||||
## Zusätzlich benötigte Models (für Daemon)
|
||||
|
||||
### RegionData (falukant_data.region)
|
||||
- Wird für `regionId` in ChurchOffice und ChurchApplication benötigt
|
||||
- Enthält `regionType` (country, duchy, markgravate, shire, county, city)
|
||||
- Enthält `parentId` für Hierarchie
|
||||
|
||||
### FalukantCharacter (falukant_data.character)
|
||||
- Wird für `characterId` (Inhaber/Bewerber) benötigt
|
||||
- Wird für `supervisorId` benötigt
|
||||
|
||||
---
|
||||
|
||||
## Wichtige Queries für Daemon
|
||||
|
||||
### Verfügbare Positionen finden
|
||||
```sql
|
||||
SELECT cot.*, COUNT(co.id) as occupied_seats
|
||||
FROM falukant_type.church_office_type cot
|
||||
LEFT JOIN falukant_data.church_office co
|
||||
ON cot.id = co.office_type_id
|
||||
AND co.region_id = ?
|
||||
WHERE cot.region_type = ?
|
||||
GROUP BY cot.id
|
||||
HAVING COUNT(co.id) < cot.seats_per_region
|
||||
```
|
||||
|
||||
### Supervisor finden
|
||||
```sql
|
||||
SELECT co.*
|
||||
FROM falukant_data.church_office co
|
||||
JOIN falukant_type.church_office_type cot ON co.office_type_id = cot.id
|
||||
WHERE co.region_id = ?
|
||||
AND cot.hierarchy_level > (
|
||||
SELECT hierarchy_level
|
||||
FROM falukant_type.church_office_type
|
||||
WHERE id = ?
|
||||
)
|
||||
ORDER BY cot.hierarchy_level ASC
|
||||
LIMIT 1
|
||||
```
|
||||
|
||||
### Voraussetzungen prüfen
|
||||
```sql
|
||||
SELECT cor.*
|
||||
FROM falukant_predefine.church_office_requirement cor
|
||||
WHERE cor.office_type_id = ?
|
||||
```
|
||||
|
||||
### Bewerbungen für Supervisor
|
||||
```sql
|
||||
SELECT ca.*
|
||||
FROM falukant_data.church_application ca
|
||||
WHERE ca.supervisor_id = ?
|
||||
AND ca.status = 'pending'
|
||||
```
|
||||
@@ -1,78 +0,0 @@
|
||||
# Kirchenämter - Hierarchie und Verfügbarkeit
|
||||
|
||||
## Regionstypen
|
||||
- **country** (Land): Falukant
|
||||
- **duchy** (Herzogtum): Hessen
|
||||
- **markgravate** (Markgrafschaft): Groß-Benbach
|
||||
- **shire** (Grafschaft): Siebenbachen
|
||||
- **county** (Kreis): Bad Homburg, Maintal
|
||||
- **city** (Stadt): Frankfurt, Oberursel, Offenbach, Königstein
|
||||
|
||||
## Kirchenämter (von höchstem zu niedrigstem Rang)
|
||||
|
||||
| Amt | Translation Key | Hierarchie-Level | Regionstyp | Plätze pro Region | Beschreibung |
|
||||
|-----|----------------|-------------------|------------|-------------------|--------------|
|
||||
| **Papst** | `pope` | 8 | country | 1 | Höchstes Amt, nur einer im ganzen Land |
|
||||
| **Kardinal** | `cardinal` | 7 | country | 3 | Höchste Kardinäle, mehrere pro Land möglich |
|
||||
| **Erzbischof** | `archbishop` | 6 | duchy | 1 | Pro Herzogtum ein Erzbischof |
|
||||
| **Bischof** | `bishop` | 5 | markgravate | 1 | Pro Markgrafschaft ein Bischof |
|
||||
| **Erzdiakon** | `archdeacon` | 4 | shire | 1 | Pro Grafschaft ein Erzdiakon |
|
||||
| **Dekan** | `dean` | 3 | county | 1 | Pro Kreis ein Dekan |
|
||||
| **Pfarrer** | `parish-priest` | 2 | city | 1 | Pro Stadt ein Pfarrer |
|
||||
| **Dorfgeistlicher** | `village-priest` | 1 | city | 1 | Pro Stadt ein Dorfgeistlicher (Einstiegsposition) |
|
||||
| **Laienprediger** | `lay-preacher` | 0 | city | 3 | Pro Stadt mehrere Laienprediger (niedrigste Position) |
|
||||
|
||||
## Verfügbare Positionen pro Regionstyp
|
||||
|
||||
### country (Land: Falukant)
|
||||
- **Papst**: 1 Platz
|
||||
- **Kardinal**: 3 Plätze
|
||||
- **Gesamt**: 4 Plätze
|
||||
|
||||
### duchy (Herzogtum: Hessen)
|
||||
- **Erzbischof**: 1 Platz
|
||||
- **Gesamt**: 1 Platz
|
||||
|
||||
### markgravate (Markgrafschaft: Groß-Benbach)
|
||||
- **Bischof**: 1 Platz
|
||||
- **Gesamt**: 1 Platz
|
||||
|
||||
### shire (Grafschaft: Siebenbachen)
|
||||
- **Erzdiakon**: 1 Platz
|
||||
- **Gesamt**: 1 Platz
|
||||
|
||||
### county (Kreis: Bad Homburg, Maintal)
|
||||
- **Dekan**: 1 Platz pro Kreis
|
||||
- **Gesamt**: 1 Platz pro Kreis
|
||||
|
||||
### city (Stadt: Frankfurt, Oberursel, Offenbach, Königstein)
|
||||
- **Pfarrer**: 1 Platz pro Stadt
|
||||
- **Dorfgeistlicher**: 1 Platz pro Stadt
|
||||
- **Laienprediger**: 3 Plätze pro Stadt
|
||||
- **Gesamt**: 5 Plätze pro Stadt
|
||||
|
||||
## Hierarchie und Beförderungsweg
|
||||
|
||||
1. **Laienprediger** (lay-preacher) - Einstiegsposition, keine Voraussetzung
|
||||
2. **Dorfgeistlicher** (village-priest) - Voraussetzung: Laienprediger
|
||||
3. **Pfarrer** (parish-priest) - Voraussetzung: Dorfgeistlicher
|
||||
4. **Dekan** (dean) - Voraussetzung: Pfarrer
|
||||
5. **Erzdiakon** (archdeacon) - Voraussetzung: Dekan
|
||||
6. **Bischof** (bishop) - Voraussetzung: Erzdiakon
|
||||
7. **Erzbischof** (archbishop) - Voraussetzung: Bischof
|
||||
8. **Kardinal** (cardinal) - Voraussetzung: Erzbischof
|
||||
9. **Papst** (pope) - Voraussetzung: Kardinal
|
||||
|
||||
## Gesamtübersicht verfügbarer Positionen
|
||||
|
||||
- **Papst**: 1 Position (Land)
|
||||
- **Kardinal**: 3 Positionen (Land)
|
||||
- **Erzbischof**: 1 Position (Herzogtum)
|
||||
- **Bischof**: 1 Position (Markgrafschaft)
|
||||
- **Erzdiakon**: 1 Position (Grafschaft)
|
||||
- **Dekan**: 2 Positionen (2 Kreise)
|
||||
- **Pfarrer**: 4 Positionen (4 Städte)
|
||||
- **Dorfgeistlicher**: 4 Positionen (4 Städte)
|
||||
- **Laienprediger**: 12 Positionen (4 Städte × 3)
|
||||
|
||||
**Gesamt**: 30 Positionen im System
|
||||
119
CMakeLists.txt
119
CMakeLists.txt
@@ -1,119 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(YourPartDaemon VERSION 1.0 LANGUAGES CXX)
|
||||
|
||||
# C++ Standard and Compiler Settings
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
# Use best available GCC for C++23 support (OpenSUSE Tumbleweed)
|
||||
# Try GCC 15 first (best C++23 support), then GCC 13, then system default
|
||||
find_program(GCC15_CC gcc-15)
|
||||
find_program(GCC15_CXX g++-15)
|
||||
find_program(GCC13_CC gcc-13)
|
||||
find_program(GCC13_CXX g++-13)
|
||||
|
||||
if(GCC15_CC AND GCC15_CXX)
|
||||
set(CMAKE_C_COMPILER ${GCC15_CC})
|
||||
set(CMAKE_CXX_COMPILER ${GCC15_CXX})
|
||||
message(STATUS "Using GCC 15 for best C++23 support")
|
||||
elseif(GCC13_CC AND GCC13_CXX)
|
||||
set(CMAKE_C_COMPILER ${GCC13_CC})
|
||||
set(CMAKE_CXX_COMPILER ${GCC13_CXX})
|
||||
message(STATUS "Using GCC 13 for C++23 support")
|
||||
else()
|
||||
message(STATUS "Using system default compiler")
|
||||
endif()
|
||||
# Optimize for GCC 13 with C++23
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -flto=auto -O3 -march=native -mtune=native")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "-O1 -g -DDEBUG")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG -march=native -mtune=native")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -flto")
|
||||
set(CMAKE_BUILD_TYPE Release)
|
||||
|
||||
# Include /usr/local if needed
|
||||
list(APPEND CMAKE_PREFIX_PATH /usr/local)
|
||||
|
||||
# Find libwebsockets via pkg-config
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(LWS REQUIRED libwebsockets)
|
||||
|
||||
# Find other dependencies
|
||||
find_package(PostgreSQL REQUIRED)
|
||||
find_package(Threads REQUIRED)
|
||||
find_package(nlohmann_json CONFIG REQUIRED)
|
||||
|
||||
# PostgreSQL C++ libpqxx
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(LIBPQXX REQUIRED libpqxx)
|
||||
|
||||
# Project sources and headers
|
||||
set(SOURCES
|
||||
src/main.cpp
|
||||
src/config.cpp
|
||||
src/connection_pool.cpp
|
||||
src/database.cpp
|
||||
src/character_creation_worker.cpp
|
||||
src/produce_worker.cpp
|
||||
src/message_broker.cpp
|
||||
src/websocket_server.cpp
|
||||
src/stockagemanager.cpp
|
||||
src/director_worker.cpp
|
||||
src/valuerecalculationworker.cpp
|
||||
src/usercharacterworker.cpp
|
||||
src/houseworker.cpp
|
||||
src/politics_worker.cpp
|
||||
)
|
||||
|
||||
set(HEADERS
|
||||
src/config.h
|
||||
src/database.h
|
||||
src/connection_pool.h
|
||||
src/worker.h
|
||||
src/character_creation_worker.h
|
||||
src/produce_worker.h
|
||||
src/message_broker.h
|
||||
src/websocket_server.h
|
||||
src/stockagemanager.h
|
||||
src/director_worker.h
|
||||
src/valuerecalculationworker.h
|
||||
src/usercharacterworker.h
|
||||
src/houseworker.h
|
||||
src/politics_worker.h
|
||||
)
|
||||
|
||||
# Define executable target
|
||||
add_executable(yourpart-daemon ${SOURCES} ${HEADERS}
|
||||
src/utils.h src/utils.cpp
|
||||
src/underground_worker.h src/underground_worker.cpp)
|
||||
|
||||
# Include directories
|
||||
target_include_directories(yourpart-daemon PRIVATE
|
||||
${PostgreSQL_INCLUDE_DIRS}
|
||||
${LIBPQXX_INCLUDE_DIRS}
|
||||
${LWS_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
# Find systemd
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(SYSTEMD REQUIRED libsystemd)
|
||||
|
||||
# Link libraries
|
||||
target_link_libraries(yourpart-daemon PRIVATE
|
||||
${PostgreSQL_LIBRARIES}
|
||||
Threads::Threads
|
||||
z ssl crypto
|
||||
${LIBPQXX_LIBRARIES}
|
||||
${LWS_LIBRARIES}
|
||||
nlohmann_json::nlohmann_json
|
||||
${SYSTEMD_LIBRARIES}
|
||||
)
|
||||
|
||||
# Installation rules
|
||||
install(TARGETS yourpart-daemon DESTINATION /usr/local/bin)
|
||||
|
||||
# Installiere Template als Referenz ZUERST (wird vom install-Skript benötigt)
|
||||
install(FILES daemon.conf DESTINATION /etc/yourpart/ RENAME daemon.conf.example)
|
||||
|
||||
# Intelligente Konfigurationsdatei-Installation
|
||||
# Verwendet ein CMake-Skript, das nur fehlende Keys hinzufügt, ohne bestehende zu überschreiben
|
||||
# Das Skript liest das Template aus /etc/yourpart/daemon.conf.example oder dem Source-Verzeichnis
|
||||
install(SCRIPT cmake/install-config.cmake)
|
||||
@@ -1,414 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE QtCreatorProject>
|
||||
<!-- Written by QtCreator 17.0.0, 2025-08-16T22:07:06. -->
|
||||
<qtcreator>
|
||||
<data>
|
||||
<variable>EnvironmentId</variable>
|
||||
<value type="QByteArray">{551ef6b3-a39b-43e2-9ee3-ad56e19ff4f4}</value>
|
||||
</data>
|
||||
<data>
|
||||
<variable>ProjectExplorer.Project.ActiveTarget</variable>
|
||||
<value type="qlonglong">0</value>
|
||||
</data>
|
||||
<data>
|
||||
<variable>ProjectExplorer.Project.EditorSettings</variable>
|
||||
<valuemap type="QVariantMap">
|
||||
<value type="bool" key="EditorConfiguration.AutoDetect">true</value>
|
||||
<value type="bool" key="EditorConfiguration.AutoIndent">true</value>
|
||||
<value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value>
|
||||
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0">
|
||||
<value type="QString" key="language">Cpp</value>
|
||||
<valuemap type="QVariantMap" key="value">
|
||||
<value type="QByteArray" key="CurrentPreferences">CppGlobal</value>
|
||||
</valuemap>
|
||||
</valuemap>
|
||||
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.1">
|
||||
<value type="QString" key="language">QmlJS</value>
|
||||
<valuemap type="QVariantMap" key="value">
|
||||
<value type="QByteArray" key="CurrentPreferences">QmlJSGlobal</value>
|
||||
</valuemap>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="EditorConfiguration.CodeStyle.Count">2</value>
|
||||
<value type="QByteArray" key="EditorConfiguration.Codec">UTF-8</value>
|
||||
<value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value>
|
||||
<value type="int" key="EditorConfiguration.IndentSize">4</value>
|
||||
<value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value>
|
||||
<value type="int" key="EditorConfiguration.LineEndingBehavior">0</value>
|
||||
<value type="int" key="EditorConfiguration.MarginColumn">80</value>
|
||||
<value type="bool" key="EditorConfiguration.MouseHiding">true</value>
|
||||
<value type="bool" key="EditorConfiguration.MouseNavigation">true</value>
|
||||
<value type="int" key="EditorConfiguration.PaddingMode">1</value>
|
||||
<value type="int" key="EditorConfiguration.PreferAfterWhitespaceComments">0</value>
|
||||
<value type="bool" key="EditorConfiguration.PreferSingleLineComments">false</value>
|
||||
<value type="bool" key="EditorConfiguration.ScrollWheelZooming">true</value>
|
||||
<value type="bool" key="EditorConfiguration.ShowMargin">false</value>
|
||||
<value type="int" key="EditorConfiguration.SmartBackspaceBehavior">2</value>
|
||||
<value type="bool" key="EditorConfiguration.SmartSelectionChanging">true</value>
|
||||
<value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
|
||||
<value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
|
||||
<value type="int" key="EditorConfiguration.TabSize">8</value>
|
||||
<value type="bool" key="EditorConfiguration.UseGlobal">true</value>
|
||||
<value type="bool" key="EditorConfiguration.UseIndenter">false</value>
|
||||
<value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value>
|
||||
<value type="bool" key="EditorConfiguration.addFinalNewLine">true</value>
|
||||
<value type="bool" key="EditorConfiguration.cleanIndentation">true</value>
|
||||
<value type="bool" key="EditorConfiguration.cleanWhitespace">true</value>
|
||||
<value type="QString" key="EditorConfiguration.ignoreFileTypes">*.md, *.MD, Makefile</value>
|
||||
<value type="bool" key="EditorConfiguration.inEntireDocument">false</value>
|
||||
<value type="bool" key="EditorConfiguration.skipTrailingWhitespace">true</value>
|
||||
<value type="bool" key="EditorConfiguration.tintMarginArea">true</value>
|
||||
</valuemap>
|
||||
</data>
|
||||
<data>
|
||||
<variable>ProjectExplorer.Project.PluginSettings</variable>
|
||||
<valuemap type="QVariantMap">
|
||||
<valuemap type="QVariantMap" key="AutoTest.ActiveFrameworks">
|
||||
<value type="bool" key="AutoTest.Framework.Boost">true</value>
|
||||
<value type="bool" key="AutoTest.Framework.CTest">false</value>
|
||||
<value type="bool" key="AutoTest.Framework.Catch">true</value>
|
||||
<value type="bool" key="AutoTest.Framework.GTest">true</value>
|
||||
<value type="bool" key="AutoTest.Framework.QtQuickTest">true</value>
|
||||
<value type="bool" key="AutoTest.Framework.QtTest">true</value>
|
||||
</valuemap>
|
||||
<value type="bool" key="AutoTest.ApplyFilter">false</value>
|
||||
<valuemap type="QVariantMap" key="AutoTest.CheckStates"/>
|
||||
<valuelist type="QVariantList" key="AutoTest.PathFilters"/>
|
||||
<value type="int" key="AutoTest.RunAfterBuild">0</value>
|
||||
<value type="bool" key="AutoTest.UseGlobal">true</value>
|
||||
<valuemap type="QVariantMap" key="ClangTools">
|
||||
<value type="bool" key="ClangTools.AnalyzeOpenFiles">true</value>
|
||||
<value type="bool" key="ClangTools.BuildBeforeAnalysis">true</value>
|
||||
<value type="QString" key="ClangTools.DiagnosticConfig">Builtin.DefaultTidyAndClazy</value>
|
||||
<value type="int" key="ClangTools.ParallelJobs">8</value>
|
||||
<value type="bool" key="ClangTools.PreferConfigFile">true</value>
|
||||
<valuelist type="QVariantList" key="ClangTools.SelectedDirs"/>
|
||||
<valuelist type="QVariantList" key="ClangTools.SelectedFiles"/>
|
||||
<valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/>
|
||||
<value type="bool" key="ClangTools.UseGlobalSettings">true</value>
|
||||
</valuemap>
|
||||
</valuemap>
|
||||
</data>
|
||||
<data>
|
||||
<variable>ProjectExplorer.Project.Target.0</variable>
|
||||
<valuemap type="QVariantMap">
|
||||
<value type="QString" key="DeviceType">Desktop</value>
|
||||
<value type="bool" key="HasPerBcDcs">true</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Importiertes Kit</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Importiertes Kit</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{78ff90a3-f672-45c2-ad08-343b0923896f}</value>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
|
||||
<value type="QString" key="CMake.Build.Type">Debug</value>
|
||||
<value type="int" key="CMake.Configure.BaseEnvironment">2</value>
|
||||
<value type="bool" key="CMake.Configure.ClearSystemEnvironment">false</value>
|
||||
<valuelist type="QVariantList" key="CMake.Configure.UserEnvironmentChanges"/>
|
||||
<value type="QString" key="CMake.Initial.Parameters">-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx}
|
||||
-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON
|
||||
-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
|
||||
-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
|
||||
-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}
|
||||
-DCMAKE_GENERATOR:STRING=Unix Makefiles
|
||||
-DCMAKE_BUILD_TYPE:STRING=Release
|
||||
-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}</value>
|
||||
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/torsten/Programs/yourpart-daemon/build/</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
|
||||
<value type="QString" key="CMakeProjectManager.MakeStep.BuildPreset"></value>
|
||||
<valuelist type="QVariantList" key="CMakeProjectManager.MakeStep.BuildTargets">
|
||||
<value type="QString">all</value>
|
||||
</valuelist>
|
||||
<value type="bool" key="CMakeProjectManager.MakeStep.ClearSystemEnvironment">false</value>
|
||||
<valuelist type="QVariantList" key="CMakeProjectManager.MakeStep.UserEnvironmentChanges"/>
|
||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Erstellen</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">CMakeProjectManager.MakeStep</value>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Erstellen</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Erstellen</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
|
||||
</valuemap>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
|
||||
<value type="QString" key="CMakeProjectManager.MakeStep.BuildPreset"></value>
|
||||
<valuelist type="QVariantList" key="CMakeProjectManager.MakeStep.BuildTargets">
|
||||
<value type="QString">clean</value>
|
||||
</valuelist>
|
||||
<value type="bool" key="CMakeProjectManager.MakeStep.ClearSystemEnvironment">false</value>
|
||||
<valuelist type="QVariantList" key="CMakeProjectManager.MakeStep.UserEnvironmentChanges"/>
|
||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Erstellen</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">CMakeProjectManager.MakeStep</value>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Bereinigen</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Bereinigen</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
|
||||
</valuemap>
|
||||
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
|
||||
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/>
|
||||
<value type="bool" key="ProjectExplorer.BuildConfiguration.ParseStandardOutput">false</value>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Release</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">CMakeProjectManager.CMakeBuildConfiguration</value>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
||||
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deployment</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deployment</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
|
||||
</valuemap>
|
||||
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
|
||||
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
|
||||
</valuemap>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.1">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
|
||||
<value type="QString" key="CMakeProjectManager.MakeStep.BuildPreset"></value>
|
||||
<valuelist type="QVariantList" key="CMakeProjectManager.MakeStep.BuildTargets">
|
||||
<value type="QString"></value>
|
||||
</valuelist>
|
||||
<value type="bool" key="CMakeProjectManager.MakeStep.ClearSystemEnvironment">false</value>
|
||||
<valuelist type="QVariantList" key="CMakeProjectManager.MakeStep.UserEnvironmentChanges"/>
|
||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ApplicationManagerPlugin.Deploy.CMakePackageStep</value>
|
||||
</valuemap>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1">
|
||||
<value type="QString" key="ApplicationManagerPlugin.Deploy.InstallPackageStep.Arguments">install-package --acknowledge</value>
|
||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Application Manager-Paket installieren</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ApplicationManagerPlugin.Deploy.InstallPackageStep</value>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.RunConfiguration.LastDeployedFiles"/>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.RunConfiguration.LastDeployedHosts"/>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.RunConfiguration.LastDeployedRemotePaths"/>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.RunConfiguration.LastDeployedSysroots"/>
|
||||
<valuelist type="QVariantList" key="RemoteLinux.LastDeployedLocalTimes"/>
|
||||
<valuelist type="QVariantList" key="RemoteLinux.LastDeployedRemoteTimes"/>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">2</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deployment</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deployment</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
|
||||
</valuemap>
|
||||
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
|
||||
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ApplicationManagerPlugin.Deploy.Configuration</value>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">2</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
|
||||
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
|
||||
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
|
||||
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
|
||||
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
|
||||
<valuelist type="QVariantList" key="CustomOutputParsers"/>
|
||||
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
|
||||
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
|
||||
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
|
||||
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">yourpart-daemon</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">CMakeProjectManager.CMakeRunConfiguration.</value>
|
||||
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">yourpart-daemon</value>
|
||||
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
|
||||
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
|
||||
<value type="bool" key="RunConfiguration.UseLibrarySearchPath">true</value>
|
||||
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
|
||||
<value type="QString" key="RunConfiguration.WorkingDirectory.default">/home/torsten/Programs/yourpart-daemon/build</value>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
|
||||
</valuemap>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.1">
|
||||
<value type="QString" key="CMake.Build.Type">Debug</value>
|
||||
<value type="int" key="CMake.Configure.BaseEnvironment">2</value>
|
||||
<value type="bool" key="CMake.Configure.ClearSystemEnvironment">false</value>
|
||||
<valuelist type="QVariantList" key="CMake.Configure.UserEnvironmentChanges"/>
|
||||
<value type="QString" key="CMake.Initial.Parameters">-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx}
|
||||
-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON
|
||||
-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
|
||||
-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
|
||||
-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}
|
||||
-DCMAKE_GENERATOR:STRING=Unix Makefiles
|
||||
-DCMAKE_BUILD_TYPE:STRING=Debug
|
||||
-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}</value>
|
||||
<value type="QString" key="CMake.Source.Directory">/mnt/share/torsten/Programs/yourpart-daemon</value>
|
||||
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/torsten/Programs/yourpart-daemon/build</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
|
||||
<value type="QString" key="CMakeProjectManager.MakeStep.BuildPreset"></value>
|
||||
<valuelist type="QVariantList" key="CMakeProjectManager.MakeStep.BuildTargets">
|
||||
<value type="QString">all</value>
|
||||
</valuelist>
|
||||
<value type="bool" key="CMakeProjectManager.MakeStep.ClearSystemEnvironment">false</value>
|
||||
<valuelist type="QVariantList" key="CMakeProjectManager.MakeStep.UserEnvironmentChanges"/>
|
||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">CMakeProjectManager.MakeStep</value>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Erstellen</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Erstellen</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
|
||||
</valuemap>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
|
||||
<value type="QString" key="CMakeProjectManager.MakeStep.BuildPreset"></value>
|
||||
<valuelist type="QVariantList" key="CMakeProjectManager.MakeStep.BuildTargets">
|
||||
<value type="QString">clean</value>
|
||||
</valuelist>
|
||||
<value type="bool" key="CMakeProjectManager.MakeStep.ClearSystemEnvironment">false</value>
|
||||
<valuelist type="QVariantList" key="CMakeProjectManager.MakeStep.UserEnvironmentChanges"/>
|
||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">CMakeProjectManager.MakeStep</value>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Bereinigen</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Bereinigen</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
|
||||
</valuemap>
|
||||
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
|
||||
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/>
|
||||
<value type="bool" key="ProjectExplorer.BuildConfiguration.ParseStandardOutput">false</value>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Debug (importiert)</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">CMakeProjectManager.CMakeBuildConfiguration</value>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">-1</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
||||
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deployment</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deployment</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
|
||||
</valuemap>
|
||||
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
|
||||
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
|
||||
</valuemap>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.1">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
|
||||
<value type="QString" key="CMakeProjectManager.MakeStep.BuildPreset"></value>
|
||||
<valuelist type="QVariantList" key="CMakeProjectManager.MakeStep.BuildTargets">
|
||||
<value type="QString">install</value>
|
||||
</valuelist>
|
||||
<value type="bool" key="CMakeProjectManager.MakeStep.ClearSystemEnvironment">false</value>
|
||||
<valuelist type="QVariantList" key="CMakeProjectManager.MakeStep.UserEnvironmentChanges"/>
|
||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ApplicationManagerPlugin.Deploy.CMakePackageStep</value>
|
||||
</valuemap>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1">
|
||||
<value type="QString" key="ApplicationManagerPlugin.Deploy.InstallPackageStep.Arguments">install-package --acknowledge</value>
|
||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Application Manager-Paket installieren</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ApplicationManagerPlugin.Deploy.InstallPackageStep</value>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.RunConfiguration.LastDeployedFiles"/>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.RunConfiguration.LastDeployedHosts"/>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.RunConfiguration.LastDeployedRemotePaths"/>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.RunConfiguration.LastDeployedSysroots"/>
|
||||
<valuelist type="QVariantList" key="RemoteLinux.LastDeployedLocalTimes"/>
|
||||
<valuelist type="QVariantList" key="RemoteLinux.LastDeployedRemoteTimes"/>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">2</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deployment</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deployment</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
|
||||
</valuemap>
|
||||
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
|
||||
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ApplicationManagerPlugin.Deploy.Configuration</value>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">2</value>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">0</value>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.BuildConfigurationCount">2</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
||||
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deployment</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deployment</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
|
||||
</valuemap>
|
||||
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
|
||||
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
|
||||
</valuemap>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.1">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
|
||||
<value type="QString" key="CMakeProjectManager.MakeStep.BuildPreset"></value>
|
||||
<valuelist type="QVariantList" key="CMakeProjectManager.MakeStep.BuildTargets">
|
||||
<value type="QString"></value>
|
||||
</valuelist>
|
||||
<value type="bool" key="CMakeProjectManager.MakeStep.ClearSystemEnvironment">false</value>
|
||||
<valuelist type="QVariantList" key="CMakeProjectManager.MakeStep.UserEnvironmentChanges"/>
|
||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ApplicationManagerPlugin.Deploy.CMakePackageStep</value>
|
||||
</valuemap>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1">
|
||||
<value type="QString" key="ApplicationManagerPlugin.Deploy.InstallPackageStep.Arguments">install-package --acknowledge</value>
|
||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Application Manager-Paket installieren</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ApplicationManagerPlugin.Deploy.InstallPackageStep</value>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.RunConfiguration.LastDeployedFiles"/>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.RunConfiguration.LastDeployedHosts"/>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.RunConfiguration.LastDeployedRemotePaths"/>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.RunConfiguration.LastDeployedSysroots"/>
|
||||
<valuelist type="QVariantList" key="RemoteLinux.LastDeployedLocalTimes"/>
|
||||
<valuelist type="QVariantList" key="RemoteLinux.LastDeployedRemoteTimes"/>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">2</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deployment</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deployment</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
|
||||
</valuemap>
|
||||
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
|
||||
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ApplicationManagerPlugin.Deploy.Configuration</value>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">2</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
|
||||
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
|
||||
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
|
||||
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
|
||||
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
|
||||
<valuelist type="QVariantList" key="CustomOutputParsers"/>
|
||||
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
|
||||
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
|
||||
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
|
||||
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">yourpart-daemon</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">CMakeProjectManager.CMakeRunConfiguration.</value>
|
||||
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">yourpart-daemon</value>
|
||||
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
|
||||
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
|
||||
<value type="bool" key="RunConfiguration.UseLibrarySearchPath">true</value>
|
||||
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
|
||||
<value type="QString" key="RunConfiguration.WorkingDirectory.default">/home/torsten/Programs/yourpart-daemon/build</value>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
|
||||
</valuemap>
|
||||
</data>
|
||||
<data>
|
||||
<variable>ProjectExplorer.Project.TargetCount</variable>
|
||||
<value type="qlonglong">1</value>
|
||||
</data>
|
||||
<data>
|
||||
<variable>ProjectExplorer.Project.Updater.FileVersion</variable>
|
||||
<value type="int">22</value>
|
||||
</data>
|
||||
<data>
|
||||
<variable>Version</variable>
|
||||
<value type="int">22</value>
|
||||
</data>
|
||||
</qtcreator>
|
||||
@@ -1,205 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE QtCreatorProject>
|
||||
<!-- Written by QtCreator 12.0.2, 2025-07-18T07:45:58. -->
|
||||
<qtcreator>
|
||||
<data>
|
||||
<variable>EnvironmentId</variable>
|
||||
<value type="QByteArray">{d36652ff-969b-426b-a63f-1edd325096c5}</value>
|
||||
</data>
|
||||
<data>
|
||||
<variable>ProjectExplorer.Project.ActiveTarget</variable>
|
||||
<value type="qlonglong">0</value>
|
||||
</data>
|
||||
<data>
|
||||
<variable>ProjectExplorer.Project.EditorSettings</variable>
|
||||
<valuemap type="QVariantMap">
|
||||
<value type="bool" key="EditorConfiguration.AutoIndent">true</value>
|
||||
<value type="bool" key="EditorConfiguration.AutoSpacesForTabs">false</value>
|
||||
<value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value>
|
||||
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0">
|
||||
<value type="QString" key="language">Cpp</value>
|
||||
<valuemap type="QVariantMap" key="value">
|
||||
<value type="QByteArray" key="CurrentPreferences">CppGlobal</value>
|
||||
</valuemap>
|
||||
</valuemap>
|
||||
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.1">
|
||||
<value type="QString" key="language">QmlJS</value>
|
||||
<valuemap type="QVariantMap" key="value">
|
||||
<value type="QByteArray" key="CurrentPreferences">QmlJSGlobal</value>
|
||||
</valuemap>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="EditorConfiguration.CodeStyle.Count">2</value>
|
||||
<value type="QByteArray" key="EditorConfiguration.Codec">UTF-8</value>
|
||||
<value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value>
|
||||
<value type="int" key="EditorConfiguration.IndentSize">4</value>
|
||||
<value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value>
|
||||
<value type="int" key="EditorConfiguration.MarginColumn">80</value>
|
||||
<value type="bool" key="EditorConfiguration.MouseHiding">true</value>
|
||||
<value type="bool" key="EditorConfiguration.MouseNavigation">true</value>
|
||||
<value type="int" key="EditorConfiguration.PaddingMode">1</value>
|
||||
<value type="int" key="EditorConfiguration.PreferAfterWhitespaceComments">0</value>
|
||||
<value type="bool" key="EditorConfiguration.PreferSingleLineComments">false</value>
|
||||
<value type="bool" key="EditorConfiguration.ScrollWheelZooming">true</value>
|
||||
<value type="bool" key="EditorConfiguration.ShowMargin">false</value>
|
||||
<value type="int" key="EditorConfiguration.SmartBackspaceBehavior">0</value>
|
||||
<value type="bool" key="EditorConfiguration.SmartSelectionChanging">true</value>
|
||||
<value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
|
||||
<value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
|
||||
<value type="int" key="EditorConfiguration.TabSize">8</value>
|
||||
<value type="bool" key="EditorConfiguration.UseGlobal">true</value>
|
||||
<value type="bool" key="EditorConfiguration.UseIndenter">false</value>
|
||||
<value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value>
|
||||
<value type="bool" key="EditorConfiguration.addFinalNewLine">true</value>
|
||||
<value type="bool" key="EditorConfiguration.cleanIndentation">true</value>
|
||||
<value type="bool" key="EditorConfiguration.cleanWhitespace">true</value>
|
||||
<value type="QString" key="EditorConfiguration.ignoreFileTypes">*.md, *.MD, Makefile</value>
|
||||
<value type="bool" key="EditorConfiguration.inEntireDocument">false</value>
|
||||
<value type="bool" key="EditorConfiguration.skipTrailingWhitespace">true</value>
|
||||
<value type="bool" key="EditorConfiguration.tintMarginArea">true</value>
|
||||
</valuemap>
|
||||
</data>
|
||||
<data>
|
||||
<variable>ProjectExplorer.Project.PluginSettings</variable>
|
||||
<valuemap type="QVariantMap">
|
||||
<valuemap type="QVariantMap" key="AutoTest.ActiveFrameworks">
|
||||
<value type="bool" key="AutoTest.Framework.Boost">true</value>
|
||||
<value type="bool" key="AutoTest.Framework.CTest">false</value>
|
||||
<value type="bool" key="AutoTest.Framework.Catch">true</value>
|
||||
<value type="bool" key="AutoTest.Framework.GTest">true</value>
|
||||
<value type="bool" key="AutoTest.Framework.QtQuickTest">true</value>
|
||||
<value type="bool" key="AutoTest.Framework.QtTest">true</value>
|
||||
</valuemap>
|
||||
<valuemap type="QVariantMap" key="AutoTest.CheckStates"/>
|
||||
<value type="int" key="AutoTest.RunAfterBuild">0</value>
|
||||
<value type="bool" key="AutoTest.UseGlobal">true</value>
|
||||
<valuemap type="QVariantMap" key="ClangTools">
|
||||
<value type="bool" key="ClangTools.AnalyzeOpenFiles">true</value>
|
||||
<value type="bool" key="ClangTools.BuildBeforeAnalysis">true</value>
|
||||
<value type="QString" key="ClangTools.DiagnosticConfig">Builtin.DefaultTidyAndClazy</value>
|
||||
<value type="int" key="ClangTools.ParallelJobs">8</value>
|
||||
<value type="bool" key="ClangTools.PreferConfigFile">true</value>
|
||||
<valuelist type="QVariantList" key="ClangTools.SelectedDirs"/>
|
||||
<valuelist type="QVariantList" key="ClangTools.SelectedFiles"/>
|
||||
<valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/>
|
||||
<value type="bool" key="ClangTools.UseGlobalSettings">true</value>
|
||||
</valuemap>
|
||||
<valuemap type="QVariantMap" key="CppEditor.QuickFix">
|
||||
<value type="bool" key="UseGlobalSettings">true</value>
|
||||
</valuemap>
|
||||
</valuemap>
|
||||
</data>
|
||||
<data>
|
||||
<variable>ProjectExplorer.Project.Target.0</variable>
|
||||
<valuemap type="QVariantMap">
|
||||
<value type="QString" key="DeviceType">Desktop</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Importiertes Kit</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Importiertes Kit</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{3c6cfc13-714d-4db1-bd45-b9794643cc67}</value>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
|
||||
<value type="QString" key="CMake.Build.Type">Debug</value>
|
||||
<value type="int" key="CMake.Configure.BaseEnvironment">2</value>
|
||||
<value type="bool" key="CMake.Configure.ClearSystemEnvironment">false</value>
|
||||
<valuelist type="QVariantList" key="CMake.Configure.UserEnvironmentChanges"/>
|
||||
<value type="QString" key="CMake.Initial.Parameters">-DCMAKE_GENERATOR:STRING=Unix Makefiles
|
||||
-DCMAKE_BUILD_TYPE:STRING=Build
|
||||
-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
|
||||
-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}
|
||||
-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}
|
||||
-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
|
||||
-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx}</value>
|
||||
<value type="QString" key="CMake.Source.Directory">/home/torsten/Programs/yourpart-daemon</value>
|
||||
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/torsten/Programs/yourpart-daemon/build</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
|
||||
<value type="QString" key="CMakeProjectManager.MakeStep.BuildPreset"></value>
|
||||
<valuelist type="QVariantList" key="CMakeProjectManager.MakeStep.BuildTargets">
|
||||
<value type="QString">all</value>
|
||||
</valuelist>
|
||||
<value type="bool" key="CMakeProjectManager.MakeStep.ClearSystemEnvironment">false</value>
|
||||
<valuelist type="QVariantList" key="CMakeProjectManager.MakeStep.UserEnvironmentChanges"/>
|
||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Erstellen</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">CMakeProjectManager.MakeStep</value>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Erstellen</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Erstellen</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
|
||||
</valuemap>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
|
||||
<value type="QString" key="CMakeProjectManager.MakeStep.BuildPreset"></value>
|
||||
<valuelist type="QVariantList" key="CMakeProjectManager.MakeStep.BuildTargets">
|
||||
<value type="QString">clean</value>
|
||||
</valuelist>
|
||||
<value type="bool" key="CMakeProjectManager.MakeStep.ClearSystemEnvironment">false</value>
|
||||
<valuelist type="QVariantList" key="CMakeProjectManager.MakeStep.UserEnvironmentChanges"/>
|
||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Erstellen</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">CMakeProjectManager.MakeStep</value>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Bereinigen</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Bereinigen</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
|
||||
</valuemap>
|
||||
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
|
||||
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/>
|
||||
<value type="bool" key="ProjectExplorer.BuildConfiguration.ParseStandardOutput">false</value>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Erstellen</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">CMakeProjectManager.CMakeBuildConfiguration</value>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.BuildConfigurationCount">1</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
||||
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deployment</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deployment</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
|
||||
</valuemap>
|
||||
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
|
||||
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
|
||||
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
|
||||
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
|
||||
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
|
||||
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
|
||||
<value type="QString" key="Analyzer.Valgrind.ValgrindExecutable">/usr/bin/valgrind</value>
|
||||
<valuelist type="QVariantList" key="CustomOutputParsers"/>
|
||||
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
|
||||
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
|
||||
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">yourpart-daemon</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">CMakeProjectManager.CMakeRunConfiguration.yourpart-daemon</value>
|
||||
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">yourpart-daemon</value>
|
||||
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
|
||||
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
|
||||
<value type="bool" key="RunConfiguration.UseLibrarySearchPath">true</value>
|
||||
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
|
||||
<value type="QString" key="RunConfiguration.WorkingDirectory.default">/home/torsten/Programs/yourpart-daemon/build</value>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
|
||||
</valuemap>
|
||||
</data>
|
||||
<data>
|
||||
<variable>ProjectExplorer.Project.TargetCount</variable>
|
||||
<value type="qlonglong">1</value>
|
||||
</data>
|
||||
<data>
|
||||
<variable>ProjectExplorer.Project.Updater.FileVersion</variable>
|
||||
<value type="int">22</value>
|
||||
</data>
|
||||
<data>
|
||||
<variable>Version</variable>
|
||||
<value type="int">22</value>
|
||||
</data>
|
||||
</qtcreator>
|
||||
168
SSL-SETUP.md
168
SSL-SETUP.md
@@ -1,168 +0,0 @@
|
||||
# SSL/TLS Setup für YourPart Daemon
|
||||
|
||||
Dieses Dokument beschreibt, wie Sie SSL/TLS-Zertifikate für den YourPart Daemon einrichten können.
|
||||
|
||||
## 🚀 Schnellstart
|
||||
|
||||
### 1. Self-Signed Certificate (Entwicklung/Testing)
|
||||
```bash
|
||||
./setup-ssl.sh
|
||||
# Wählen Sie Option 1
|
||||
```
|
||||
|
||||
### 2. Let's Encrypt Certificate (Produktion)
|
||||
```bash
|
||||
./setup-ssl.sh
|
||||
# Wählen Sie Option 2
|
||||
```
|
||||
|
||||
### 3. Apache2-Zertifikate verwenden (empfohlen für Ubuntu)
|
||||
```bash
|
||||
./setup-ssl.sh
|
||||
# Wählen Sie Option 4
|
||||
# Verwendet bereits vorhandene Apache2-Zertifikate
|
||||
# ⚠️ Warnung bei Snakeoil-Zertifikaten (nur für localhost)
|
||||
```
|
||||
|
||||
### 4. DNS-01 Challenge (für komplexe Setups)
|
||||
```bash
|
||||
./setup-ssl-dns.sh
|
||||
# Für Cloudflare, Route53, etc.
|
||||
```
|
||||
|
||||
## 📋 Voraussetzungen
|
||||
|
||||
### Für Apache2-Zertifikate:
|
||||
- Apache2 installiert oder Zertifikate in Standard-Pfaden
|
||||
- Unterstützte Pfade (priorisiert nach Qualität):
|
||||
- `/etc/letsencrypt/live/your-part.de/fullchain.pem` (Let's Encrypt - empfohlen)
|
||||
- `/etc/letsencrypt/live/$(hostname)/fullchain.pem` (Let's Encrypt)
|
||||
- `/etc/apache2/ssl/apache.crt` (Custom Apache2)
|
||||
- `/etc/ssl/certs/ssl-cert-snakeoil.pem` (Ubuntu Standard - nur localhost)
|
||||
|
||||
### Für Let's Encrypt (HTTP-01 Challenge):
|
||||
- Port 80 muss verfügbar sein
|
||||
- Domain `your-part.de` muss auf den Server zeigen
|
||||
- Kein anderer Service auf Port 80
|
||||
|
||||
### Für DNS-01 Challenge:
|
||||
- DNS-Provider Account (Cloudflare, Route53, etc.)
|
||||
- API-Credentials für DNS-Management
|
||||
|
||||
## 🔧 Konfiguration
|
||||
|
||||
Nach der Zertifikats-Erstellung:
|
||||
|
||||
1. **SSL in der Konfiguration aktivieren:**
|
||||
```ini
|
||||
# /etc/yourpart/daemon.conf
|
||||
WEBSOCKET_SSL_ENABLED=true
|
||||
WEBSOCKET_SSL_CERT_PATH=/etc/yourpart/server.crt
|
||||
WEBSOCKET_SSL_KEY_PATH=/etc/yourpart/server.key
|
||||
```
|
||||
|
||||
2. **Daemon neu starten:**
|
||||
```bash
|
||||
sudo systemctl restart yourpart-daemon
|
||||
```
|
||||
|
||||
3. **Verbindung testen:**
|
||||
```bash
|
||||
# WebSocket Secure
|
||||
wss://your-part.de:4551
|
||||
|
||||
# Oder ohne SSL
|
||||
ws://your-part.de:4551
|
||||
```
|
||||
|
||||
## 🔄 Automatische Erneuerung
|
||||
|
||||
### Let's Encrypt-Zertifikate:
|
||||
- **Cron Job:** Täglich um 2:30 Uhr
|
||||
- **Script:** `/etc/yourpart/renew-ssl.sh`
|
||||
- **Log:** `/var/log/yourpart/ssl-renewal.log`
|
||||
|
||||
### Apache2-Zertifikate:
|
||||
- **Ubuntu Snakeoil:** Automatisch von Apache2 verwaltet
|
||||
- **Let's Encrypt:** Automatische Erneuerung wenn erkannt
|
||||
- **Custom:** Manuelle Verwaltung erforderlich
|
||||
|
||||
## 📁 Dateistruktur
|
||||
|
||||
```
|
||||
/etc/yourpart/
|
||||
├── server.crt # Zertifikat (Symlink zu Let's Encrypt)
|
||||
├── server.key # Private Key (Symlink zu Let's Encrypt)
|
||||
├── renew-ssl.sh # Auto-Renewal Script
|
||||
└── cloudflare.ini # Cloudflare Credentials (falls verwendet)
|
||||
|
||||
/etc/letsencrypt/live/your-part.de/
|
||||
├── fullchain.pem # Vollständige Zertifikatskette
|
||||
├── privkey.pem # Private Key
|
||||
├── cert.pem # Zertifikat
|
||||
└── chain.pem # Intermediate Certificate
|
||||
```
|
||||
|
||||
## 🛠️ Troubleshooting
|
||||
|
||||
### Zertifikat wird nicht akzeptiert
|
||||
```bash
|
||||
# Prüfe Zertifikats-Gültigkeit
|
||||
openssl x509 -in /etc/yourpart/server.crt -text -noout
|
||||
|
||||
# Prüfe Berechtigungen
|
||||
ls -la /etc/yourpart/server.*
|
||||
```
|
||||
|
||||
### Let's Encrypt Challenge fehlgeschlagen
|
||||
```bash
|
||||
# Prüfe Port 80
|
||||
sudo netstat -tlnp | grep :80
|
||||
|
||||
# Prüfe DNS
|
||||
nslookup your-part.de
|
||||
|
||||
# Prüfe Firewall
|
||||
sudo ufw status
|
||||
```
|
||||
|
||||
### Auto-Renewal funktioniert nicht
|
||||
```bash
|
||||
# Prüfe Cron Jobs
|
||||
sudo crontab -l
|
||||
|
||||
# Teste Renewal Script
|
||||
sudo /etc/yourpart/renew-ssl.sh
|
||||
|
||||
# Prüfe Logs
|
||||
tail -f /var/log/yourpart/ssl-renewal.log
|
||||
```
|
||||
|
||||
## 🔒 Sicherheit
|
||||
|
||||
### Berechtigungen
|
||||
- **Zertifikat:** `644` (readable by all, writable by owner)
|
||||
- **Private Key:** `600` (readable/writable by owner only)
|
||||
- **Owner:** `yourpart:yourpart`
|
||||
|
||||
### Firewall
|
||||
```bash
|
||||
# Öffne Port 80 für Let's Encrypt Challenge
|
||||
sudo ufw allow 80/tcp
|
||||
|
||||
# Öffne Port 4551 für WebSocket
|
||||
sudo ufw allow 4551/tcp
|
||||
```
|
||||
|
||||
## 📚 Weitere Informationen
|
||||
|
||||
- [Let's Encrypt Dokumentation](https://letsencrypt.org/docs/)
|
||||
- [Certbot Dokumentation](https://certbot.eff.org/docs/)
|
||||
- [libwebsockets SSL](https://libwebsockets.org/lws-api-doc-master/html/group__ssl.html)
|
||||
|
||||
## 🆘 Support
|
||||
|
||||
Bei Problemen:
|
||||
1. Prüfen Sie die Logs: `sudo journalctl -u yourpart-daemon -f`
|
||||
2. Testen Sie die Zertifikate: `openssl s_client -connect your-part.de:4551`
|
||||
3. Prüfen Sie die Firewall: `sudo ufw status`
|
||||
@@ -1,184 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Script zur Analyse und Empfehlung von Indizes
|
||||
*
|
||||
* Analysiert:
|
||||
* - Tabellen mit vielen Sequential Scans
|
||||
* - Fehlende Composite Indizes für häufige JOINs
|
||||
* - Ungenutzte Indizes
|
||||
*/
|
||||
|
||||
import './config/loadEnv.js';
|
||||
import { sequelize } from './utils/sequelize.js';
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
console.log('🔍 Index-Analyse und Empfehlungen\n');
|
||||
console.log('='.repeat(60) + '\n');
|
||||
|
||||
// 1. Tabellen mit vielen Sequential Scans
|
||||
await analyzeSequentialScans();
|
||||
|
||||
// 2. Prüfe häufige JOIN-Patterns
|
||||
await analyzeJoinPatterns();
|
||||
|
||||
// 3. Ungenutzte Indizes
|
||||
await analyzeUnusedIndexes();
|
||||
|
||||
console.log('='.repeat(60));
|
||||
console.log('✅ Analyse abgeschlossen\n');
|
||||
|
||||
await sequelize.close();
|
||||
process.exit(0);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Fehler:', error.message);
|
||||
console.error(error.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async function analyzeSequentialScans() {
|
||||
console.log('📊 1. Tabellen mit vielen Sequential Scans\n');
|
||||
|
||||
const [tables] = await sequelize.query(`
|
||||
SELECT
|
||||
schemaname || '.' || relname as table_name,
|
||||
seq_scan,
|
||||
seq_tup_read,
|
||||
idx_scan,
|
||||
seq_tup_read / NULLIF(seq_scan, 0) as avg_rows_per_scan,
|
||||
CASE
|
||||
WHEN seq_scan + idx_scan > 0
|
||||
THEN round((seq_scan::numeric / (seq_scan + idx_scan)) * 100, 2)
|
||||
ELSE 0
|
||||
END as seq_scan_percent
|
||||
FROM pg_stat_user_tables
|
||||
WHERE schemaname IN ('falukant_data', 'falukant_type', 'community', 'logs')
|
||||
AND seq_scan > 1000
|
||||
ORDER BY seq_tup_read DESC
|
||||
LIMIT 10;
|
||||
`);
|
||||
|
||||
if (tables.length > 0) {
|
||||
console.log(' ⚠️ Tabellen mit vielen Sequential Scans:');
|
||||
tables.forEach(t => {
|
||||
console.log(`\n ${t.table_name}:`);
|
||||
console.log(` Sequential Scans: ${parseInt(t.seq_scan).toLocaleString()}`);
|
||||
console.log(` Zeilen gelesen: ${parseInt(t.seq_tup_read).toLocaleString()}`);
|
||||
console.log(` Index Scans: ${parseInt(t.idx_scan).toLocaleString()}`);
|
||||
console.log(` Seq Scan Anteil: ${t.seq_scan_percent}%`);
|
||||
console.log(` Ø Zeilen pro Scan: ${parseInt(t.avg_rows_per_scan).toLocaleString()}`);
|
||||
|
||||
if (t.seq_scan_percent > 50) {
|
||||
console.log(` ⚠️ KRITISCH: Mehr als 50% Sequential Scans!`);
|
||||
}
|
||||
});
|
||||
console.log('');
|
||||
}
|
||||
}
|
||||
|
||||
async function analyzeJoinPatterns() {
|
||||
console.log('🔗 2. Analyse häufiger JOIN-Patterns\n');
|
||||
|
||||
// Prüfe welche Indizes auf knowledge existieren
|
||||
const [knowledgeIndexes] = await sequelize.query(`
|
||||
SELECT
|
||||
indexname,
|
||||
indexdef
|
||||
FROM pg_indexes
|
||||
WHERE schemaname = 'falukant_data'
|
||||
AND tablename = 'knowledge'
|
||||
ORDER BY indexname;
|
||||
`);
|
||||
|
||||
console.log(' Indizes auf falukant_data.knowledge:');
|
||||
if (knowledgeIndexes.length > 0) {
|
||||
knowledgeIndexes.forEach(idx => {
|
||||
console.log(` - ${idx.indexname}: ${idx.indexdef}`);
|
||||
});
|
||||
} else {
|
||||
console.log(' Keine Indizes gefunden');
|
||||
}
|
||||
console.log('');
|
||||
|
||||
// Empfehlung: Composite Index auf (character_id, product_id)
|
||||
const [knowledgeUsage] = await sequelize.query(`
|
||||
SELECT
|
||||
idx_scan,
|
||||
idx_tup_read,
|
||||
idx_tup_fetch
|
||||
FROM pg_stat_user_indexes
|
||||
WHERE schemaname = 'falukant_data'
|
||||
AND relname = 'knowledge'
|
||||
AND indexrelname = 'idx_knowledge_character_id';
|
||||
`);
|
||||
|
||||
if (knowledgeUsage.length > 0) {
|
||||
const usage = knowledgeUsage[0];
|
||||
console.log(' Aktuelle Nutzung von idx_knowledge_character_id:');
|
||||
console.log(` Scans: ${parseInt(usage.idx_scan).toLocaleString()}`);
|
||||
console.log(` Zeilen gelesen: ${parseInt(usage.idx_tup_read).toLocaleString()}`);
|
||||
console.log('');
|
||||
|
||||
console.log(' 💡 Empfehlung:');
|
||||
console.log(' CREATE INDEX IF NOT EXISTS idx_knowledge_character_product');
|
||||
console.log(' ON falukant_data.knowledge(character_id, product_id);');
|
||||
console.log(' → Wird häufig für JOINs mit character_id UND product_id verwendet\n');
|
||||
}
|
||||
|
||||
// Prüfe character Indizes
|
||||
const [characterIndexes] = await sequelize.query(`
|
||||
SELECT
|
||||
indexname,
|
||||
indexdef
|
||||
FROM pg_indexes
|
||||
WHERE schemaname = 'falukant_data'
|
||||
AND tablename = 'character'
|
||||
ORDER BY indexname;
|
||||
`);
|
||||
|
||||
console.log(' Indizes auf falukant_data.character:');
|
||||
if (characterIndexes.length > 0) {
|
||||
characterIndexes.forEach(idx => {
|
||||
console.log(` - ${idx.indexname}: ${idx.indexdef}`);
|
||||
});
|
||||
}
|
||||
console.log('');
|
||||
}
|
||||
|
||||
async function analyzeUnusedIndexes() {
|
||||
console.log('🗑️ 3. Ungenutzte Indizes\n');
|
||||
|
||||
const [unused] = await sequelize.query(`
|
||||
SELECT
|
||||
schemaname || '.' || indexrelname as index_name,
|
||||
schemaname || '.' || relname as table_name,
|
||||
pg_size_pretty(pg_relation_size(indexrelid)) as index_size,
|
||||
idx_scan as scans,
|
||||
pg_relation_size(indexrelid) as size_bytes
|
||||
FROM pg_stat_user_indexes
|
||||
WHERE schemaname IN ('falukant_data', 'falukant_type', 'community', 'logs')
|
||||
AND idx_scan = 0
|
||||
AND pg_relation_size(indexrelid) > 1024 * 1024 -- Größer als 1MB
|
||||
ORDER BY pg_relation_size(indexrelid) DESC
|
||||
LIMIT 10;
|
||||
`);
|
||||
|
||||
if (unused.length > 0) {
|
||||
console.log(' ⚠️ Ungenutzte Indizes (> 1MB):');
|
||||
unused.forEach(idx => {
|
||||
console.log(` ${idx.index_name} auf ${idx.table_name}`);
|
||||
console.log(` Größe: ${idx.index_size}, Scans: ${idx.scans}`);
|
||||
});
|
||||
console.log('');
|
||||
console.log(' 💡 Überlege, ob diese Indizes gelöscht werden können:');
|
||||
console.log(' DROP INDEX IF EXISTS <index_name>;');
|
||||
console.log('');
|
||||
} else {
|
||||
console.log(' ✅ Keine großen ungenutzten Indizes gefunden\n');
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -12,7 +12,6 @@ import socialnetworkRouter from './routers/socialnetworkRouter.js';
|
||||
import forumRouter from './routers/forumRouter.js';
|
||||
import falukantRouter from './routers/falukantRouter.js';
|
||||
import friendshipRouter from './routers/friendshipRouter.js';
|
||||
import modelsProxyRouter from './routers/modelsProxyRouter.js';
|
||||
import blogRouter from './routers/blogRouter.js';
|
||||
import match3Router from './routers/match3Router.js';
|
||||
import taxiRouter from './routers/taxiRouter.js';
|
||||
@@ -20,9 +19,6 @@ import taxiMapRouter from './routers/taxiMapRouter.js';
|
||||
import taxiHighscoreRouter from './routers/taxiHighscoreRouter.js';
|
||||
import termineRouter from './routers/termineRouter.js';
|
||||
import vocabRouter from './routers/vocabRouter.js';
|
||||
import dashboardRouter from './routers/dashboardRouter.js';
|
||||
import newsRouter from './routers/newsRouter.js';
|
||||
import calendarRouter from './routers/calendarRouter.js';
|
||||
import cors from 'cors';
|
||||
import './jobs/sessionCleanup.js';
|
||||
|
||||
@@ -36,19 +32,6 @@ const app = express();
|
||||
// - LOG_ALL_REQ=1: Logge alle Requests
|
||||
const LOG_ALL_REQ = process.env.LOG_ALL_REQ === '1';
|
||||
const LOG_SLOW_REQ_MS = Number.parseInt(process.env.LOG_SLOW_REQ_MS || '500', 10);
|
||||
const defaultCorsOrigins = [
|
||||
'http://localhost:3000',
|
||||
'http://localhost:5173',
|
||||
'http://127.0.0.1:3000',
|
||||
'http://127.0.0.1:5173'
|
||||
];
|
||||
const corsOrigins = (process.env.CORS_ORIGINS || process.env.FRONTEND_URL || '')
|
||||
.split(',')
|
||||
.map((origin) => origin.trim())
|
||||
.filter(Boolean);
|
||||
const effectiveCorsOrigins = corsOrigins.length > 0 ? corsOrigins : defaultCorsOrigins;
|
||||
const corsAllowAll = process.env.CORS_ALLOW_ALL === '1';
|
||||
|
||||
app.use((req, res, next) => {
|
||||
const reqId = req.headers['x-request-id'] || (crypto.randomUUID ? crypto.randomUUID() : crypto.randomBytes(8).toString('hex'));
|
||||
req.reqId = reqId;
|
||||
@@ -64,31 +47,15 @@ app.use((req, res, next) => {
|
||||
});
|
||||
|
||||
const corsOptions = {
|
||||
origin(origin, callback) {
|
||||
if (!origin) {
|
||||
return callback(null, true);
|
||||
}
|
||||
|
||||
if (corsAllowAll || effectiveCorsOrigins.includes(origin)) {
|
||||
return callback(null, true);
|
||||
}
|
||||
|
||||
return callback(null, false);
|
||||
},
|
||||
origin: ['http://localhost:3000', 'http://localhost:5173', 'http://127.0.0.1:3000', 'http://127.0.0.1:5173'],
|
||||
methods: ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization', 'userid', 'authcode', 'userId', 'authCode'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization', 'userId', 'authCode'],
|
||||
credentials: true,
|
||||
preflightContinue: false,
|
||||
optionsSuccessStatus: 204
|
||||
};
|
||||
|
||||
app.use(cors(corsOptions));
|
||||
app.use((req, res, next) => {
|
||||
if (req.method === 'OPTIONS') {
|
||||
return cors(corsOptions)(req, res, next);
|
||||
}
|
||||
return next();
|
||||
});
|
||||
app.use(express.json()); // To handle JSON request bodies
|
||||
|
||||
app.use('/api/chat', chatRouter);
|
||||
@@ -107,33 +74,17 @@ app.use('/api/vocab', vocabRouter);
|
||||
app.use('/api/forum', forumRouter);
|
||||
app.use('/api/falukant', falukantRouter);
|
||||
app.use('/api/friendships', friendshipRouter);
|
||||
app.use('/api/models', modelsProxyRouter);
|
||||
app.use('/api/blog', blogRouter);
|
||||
app.use('/api/termine', termineRouter);
|
||||
app.use('/api/dashboard', dashboardRouter);
|
||||
app.use('/api/news', newsRouter);
|
||||
app.use('/api/calendar', calendarRouter);
|
||||
|
||||
// Serve frontend SPA for non-API routes to support history mode clean URLs
|
||||
// /models/* nicht statisch ausliefern – nur über /api/models (Proxy mit Komprimierung)
|
||||
const frontendDir = path.join(__dirname, '../frontend');
|
||||
app.use((req, res, next) => {
|
||||
if (req.path.startsWith('/models/')) {
|
||||
return res.status(404).send('Use /api/models/ for 3D models (optimized).');
|
||||
}
|
||||
next();
|
||||
});
|
||||
app.use(express.static(path.join(frontendDir, 'dist')));
|
||||
app.get(/^\/(?!api\/).*/, (req, res) => {
|
||||
res.sendFile(path.join(frontendDir, 'dist', 'index.html'));
|
||||
});
|
||||
|
||||
// Fallback 404 for unknown API routes
|
||||
app.use((req, res, next) => {
|
||||
if (req.path.startsWith('/api/')) {
|
||||
return res.status(404).send('404 Not Found');
|
||||
}
|
||||
return next();
|
||||
});
|
||||
app.use('/api/*', (req, res) => res.status(404).send('404 Not Found'));
|
||||
|
||||
export default app;
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Script zum Prüfen und Bereinigen von PostgreSQL-Verbindungen
|
||||
*/
|
||||
|
||||
import './config/loadEnv.js';
|
||||
import { sequelize } from './utils/sequelize.js';
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
console.log('🔍 Prüfe PostgreSQL-Verbindungen...\n');
|
||||
|
||||
// Prüfe aktive Verbindungen
|
||||
const [connections] = await sequelize.query(`
|
||||
SELECT
|
||||
count(*) as total,
|
||||
count(*) FILTER (WHERE state = 'active') as active,
|
||||
count(*) FILTER (WHERE state = 'idle') as idle,
|
||||
count(*) FILTER (WHERE state = 'idle in transaction') as idle_in_transaction,
|
||||
count(*) FILTER (WHERE usename = current_user) as my_connections
|
||||
FROM pg_stat_activity
|
||||
WHERE datname = current_database();
|
||||
`);
|
||||
|
||||
console.log('📊 Verbindungsstatistik:');
|
||||
console.log(` Gesamt: ${connections[0].total}`);
|
||||
console.log(` Aktiv: ${connections[0].active}`);
|
||||
console.log(` Idle: ${connections[0].idle}`);
|
||||
console.log(` Idle in Transaction: ${connections[0].idle_in_transaction}`);
|
||||
console.log(` Meine Verbindungen: ${connections[0].my_connections}\n`);
|
||||
|
||||
// Prüfe max_connections Limit
|
||||
const [maxConn] = await sequelize.query(`
|
||||
SELECT setting::int as max_connections
|
||||
FROM pg_settings
|
||||
WHERE name = 'max_connections';
|
||||
`);
|
||||
console.log(`📈 Max Connections Limit: ${maxConn[0].max_connections}`);
|
||||
console.log(`📉 Verfügbare Connections: ${maxConn[0].max_connections - connections[0].total}\n`);
|
||||
|
||||
// Zeige alte idle Verbindungen
|
||||
const [oldConnections] = await sequelize.query(`
|
||||
SELECT
|
||||
pid,
|
||||
usename,
|
||||
application_name,
|
||||
state,
|
||||
state_change,
|
||||
now() - state_change as idle_duration,
|
||||
query
|
||||
FROM pg_stat_activity
|
||||
WHERE datname = current_database()
|
||||
AND state = 'idle'
|
||||
AND state_change < now() - interval '1 minute'
|
||||
ORDER BY state_change ASC
|
||||
LIMIT 10;
|
||||
`);
|
||||
|
||||
if (oldConnections.length > 0) {
|
||||
console.log(`⚠️ Gefunden ${oldConnections.length} alte idle Verbindungen (> 1 Minute):`);
|
||||
oldConnections.forEach(conn => {
|
||||
console.log(` PID: ${conn.pid}, User: ${conn.usename}, Idle seit: ${conn.idle_duration}`);
|
||||
});
|
||||
console.log('\n💡 Tipp: Du kannst alte Verbindungen beenden mit:');
|
||||
console.log(' SELECT pg_terminate_backend(pid) FROM pg_stat_activity');
|
||||
console.log(' WHERE datname = current_database() AND state = \'idle\' AND state_change < now() - interval \'5 minutes\';\n');
|
||||
}
|
||||
|
||||
// Prüfe ob wir nahe am Limit sind
|
||||
const usagePercent = (connections[0].total / maxConn[0].max_connections) * 100;
|
||||
if (usagePercent > 80) {
|
||||
console.log(`⚠️ WARNUNG: ${usagePercent.toFixed(1)}% der verfügbaren Verbindungen werden verwendet!`);
|
||||
console.log(' Es könnte sein, dass nicht genug Verbindungen verfügbar sind.\n');
|
||||
}
|
||||
|
||||
await sequelize.close();
|
||||
process.exit(0);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Fehler:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,142 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Script zur Analyse des knowledge_pkey Problems
|
||||
*
|
||||
* Prüft warum knowledge_pkey nicht verwendet wird
|
||||
*/
|
||||
|
||||
import './config/loadEnv.js';
|
||||
import { sequelize } from './utils/sequelize.js';
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
console.log('🔍 Analyse knowledge_pkey Problem\n');
|
||||
console.log('='.repeat(60) + '\n');
|
||||
|
||||
// Prüfe ob knowledge einen Primary Key hat
|
||||
const [pkInfo] = await sequelize.query(`
|
||||
SELECT
|
||||
a.attname as column_name,
|
||||
t.conname as constraint_name,
|
||||
t.contype as constraint_type
|
||||
FROM pg_constraint t
|
||||
JOIN pg_class c ON c.oid = t.conrelid
|
||||
JOIN pg_namespace n ON n.oid = c.relnamespace
|
||||
JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum = ANY(t.conkey)
|
||||
WHERE n.nspname = 'falukant_data'
|
||||
AND c.relname = 'knowledge'
|
||||
AND t.contype = 'p';
|
||||
`);
|
||||
|
||||
console.log('📋 Primary Key Information:');
|
||||
if (pkInfo.length > 0) {
|
||||
pkInfo.forEach(pk => {
|
||||
console.log(` Constraint: ${pk.constraint_name}`);
|
||||
console.log(` Spalte: ${pk.column_name}`);
|
||||
console.log(` Typ: ${pk.constraint_type === 'p' ? 'PRIMARY KEY' : pk.constraint_type}`);
|
||||
});
|
||||
} else {
|
||||
console.log(' ⚠️ Kein Primary Key gefunden!');
|
||||
}
|
||||
console.log('');
|
||||
|
||||
// Prüfe alle Indizes auf knowledge
|
||||
const [allIndexes] = await sequelize.query(`
|
||||
SELECT
|
||||
indexname,
|
||||
indexdef,
|
||||
idx_scan,
|
||||
idx_tup_read,
|
||||
idx_tup_fetch
|
||||
FROM pg_indexes
|
||||
LEFT JOIN pg_stat_user_indexes
|
||||
ON pg_stat_user_indexes.indexrelname = pg_indexes.indexname
|
||||
AND pg_stat_user_indexes.schemaname = pg_indexes.schemaname
|
||||
WHERE pg_indexes.schemaname = 'falukant_data'
|
||||
AND pg_indexes.tablename = 'knowledge'
|
||||
ORDER BY indexname;
|
||||
`);
|
||||
|
||||
console.log('📊 Alle Indizes auf knowledge:');
|
||||
allIndexes.forEach(idx => {
|
||||
console.log(`\n ${idx.indexname}:`);
|
||||
console.log(` Definition: ${idx.indexdef}`);
|
||||
console.log(` Scans: ${idx.idx_scan ? parseInt(idx.idx_scan).toLocaleString() : 'N/A'}`);
|
||||
console.log(` Zeilen gelesen: ${idx.idx_tup_read ? parseInt(idx.idx_tup_read).toLocaleString() : 'N/A'}`);
|
||||
});
|
||||
console.log('');
|
||||
|
||||
// Prüfe Tabellenstruktur
|
||||
const [tableStructure] = await sequelize.query(`
|
||||
SELECT
|
||||
column_name,
|
||||
data_type,
|
||||
is_nullable,
|
||||
column_default
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'falukant_data'
|
||||
AND table_name = 'knowledge'
|
||||
ORDER BY ordinal_position;
|
||||
`);
|
||||
|
||||
console.log('📋 Tabellenstruktur:');
|
||||
tableStructure.forEach(col => {
|
||||
console.log(` ${col.column_name}: ${col.data_type} ${col.is_nullable === 'NO' ? 'NOT NULL' : 'NULL'}`);
|
||||
});
|
||||
console.log('');
|
||||
|
||||
// Erklärung: Warum knowledge_pkey ungenutzt ist
|
||||
const pkUnused = allIndexes.find(i => i.indexname === 'knowledge_pkey' && (i.idx_scan == null || parseInt(i.idx_scan) === 0));
|
||||
if (pkUnused) {
|
||||
console.log('💡 Warum knowledge_pkey (0 Scans) ungenutzt ist:');
|
||||
console.log(' Alle Zugriffe filtern nach (character_id, product_id), nie nach id.');
|
||||
console.log(' Der PK-Index wird nur für Eindeutigkeit/Referenzen genutzt, nicht für Lookups.');
|
||||
console.log(' idx_knowledge_character_product deckt die tatsächlichen Queries ab.\n');
|
||||
}
|
||||
|
||||
// Prüfe ob Queries mit id (Primary Key) gemacht werden
|
||||
let idUsage = [];
|
||||
try {
|
||||
const [rows] = await sequelize.query(`
|
||||
SELECT
|
||||
query,
|
||||
calls,
|
||||
total_exec_time,
|
||||
mean_exec_time
|
||||
FROM pg_stat_statements
|
||||
WHERE query LIKE '%knowledge%'
|
||||
AND (query LIKE '%knowledge.id%' OR query LIKE '%knowledge%id%')
|
||||
ORDER BY calls DESC
|
||||
LIMIT 5;
|
||||
`);
|
||||
idUsage = rows;
|
||||
} catch (e) {
|
||||
console.log(' ℹ️ pg_stat_statements nicht verfügbar – keine Query-Statistik.\n');
|
||||
}
|
||||
|
||||
if (idUsage.length > 0) {
|
||||
console.log('🔍 Queries die knowledge.id verwenden:');
|
||||
idUsage.forEach(q => {
|
||||
console.log(` Aufrufe: ${parseInt(q.calls).toLocaleString()}`);
|
||||
console.log(` Query: ${q.query.substring(0, 150)}...`);
|
||||
console.log('');
|
||||
});
|
||||
}
|
||||
|
||||
await sequelize.close();
|
||||
process.exit(0);
|
||||
|
||||
} catch (error) {
|
||||
if (error.message.includes('pg_stat_statements')) {
|
||||
console.log(' ⚠️ pg_stat_statements ist nicht aktiviert oder nicht verfügbar\n');
|
||||
} else {
|
||||
console.error('❌ Fehler:', error.message);
|
||||
console.error(error.stack);
|
||||
}
|
||||
await sequelize.close();
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,55 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Script zum Bereinigen von alten/idle PostgreSQL-Verbindungen
|
||||
*/
|
||||
|
||||
import './config/loadEnv.js';
|
||||
import { sequelize } from './utils/sequelize.js';
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
console.log('🧹 Bereinige alte PostgreSQL-Verbindungen...\n');
|
||||
|
||||
// Beende idle Verbindungen, die älter als 5 Minuten sind (außer unserer eigenen)
|
||||
const [result] = await sequelize.query(`
|
||||
SELECT pg_terminate_backend(pid) as terminated
|
||||
FROM pg_stat_activity
|
||||
WHERE datname = current_database()
|
||||
AND pid <> pg_backend_pid()
|
||||
AND state = 'idle'
|
||||
AND state_change < now() - interval '5 minutes';
|
||||
`);
|
||||
|
||||
const terminated = result.filter(r => r.terminated).length;
|
||||
console.log(`✅ ${terminated} alte idle Verbindungen wurden beendet\n`);
|
||||
|
||||
// Zeige verbleibende Verbindungen
|
||||
const [connections] = await sequelize.query(`
|
||||
SELECT
|
||||
count(*) as total,
|
||||
count(*) FILTER (WHERE state = 'active') as active,
|
||||
count(*) FILTER (WHERE state = 'idle') as idle
|
||||
FROM pg_stat_activity
|
||||
WHERE datname = current_database();
|
||||
`);
|
||||
|
||||
console.log('📊 Verbleibende Verbindungen:');
|
||||
console.log(` Gesamt: ${connections[0].total}`);
|
||||
console.log(` Aktiv: ${connections[0].active}`);
|
||||
console.log(` Idle: ${connections[0].idle}\n`);
|
||||
|
||||
await sequelize.close();
|
||||
process.exit(0);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Fehler:', error.message);
|
||||
if (error.message.includes('SUPERUSER')) {
|
||||
console.error('\n💡 Tipp: Du benötigst Superuser-Rechte oder musst warten, bis Verbindungen freigegeben werden.');
|
||||
console.error(' Versuche es in ein paar Minuten erneut.');
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"host": "localhost",
|
||||
"port": 1236
|
||||
"port": 1235
|
||||
}
|
||||
|
||||
@@ -7,90 +7,40 @@ import fs from 'fs';
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const quietEnv = process.env.QUIET_ENV_LOGS === '1';
|
||||
const dotenvQuiet = quietEnv || process.env.DOTENV_CONFIG_QUIET === '1';
|
||||
|
||||
function log(...args) {
|
||||
if (!quietEnv) console.log(...args);
|
||||
}
|
||||
function warn(...args) {
|
||||
console.warn(...args);
|
||||
}
|
||||
|
||||
// Versuche zuerst Produktions-.env, dann lokale .env
|
||||
const productionEnvPath = '/opt/yourpart/backend/.env';
|
||||
const localEnvPath = path.resolve(__dirname, '../.env');
|
||||
|
||||
let envPath = localEnvPath; // Fallback
|
||||
let usingProduction = false;
|
||||
if (fs.existsSync(productionEnvPath)) {
|
||||
// Prüfe Lesbarkeit bevor wir versuchen, sie zu laden
|
||||
try {
|
||||
fs.accessSync(productionEnvPath, fs.constants.R_OK);
|
||||
envPath = productionEnvPath;
|
||||
usingProduction = true;
|
||||
log('[env] Produktions-.env gefunden und lesbar:', productionEnvPath);
|
||||
} catch (err) {
|
||||
if (!quietEnv) {
|
||||
warn('[env] Produktions-.env vorhanden, aber nicht lesbar - verwende lokale .env stattdessen:', productionEnvPath);
|
||||
warn('[env] Fehler:', err && err.message);
|
||||
}
|
||||
envPath = localEnvPath;
|
||||
}
|
||||
console.log('[env] Lade Produktions-.env:', productionEnvPath);
|
||||
} else {
|
||||
log('[env] Produktions-.env nicht gefunden, lade lokale .env:', localEnvPath);
|
||||
console.log('[env] Lade lokale .env:', localEnvPath);
|
||||
}
|
||||
|
||||
// Lade .env-Datei (robust gegen Fehler)
|
||||
log('[env] Versuche .env zu laden von:', envPath);
|
||||
log('[env] Datei existiert:', fs.existsSync(envPath));
|
||||
let result;
|
||||
try {
|
||||
result = dotenv.config({ path: envPath, quiet: dotenvQuiet });
|
||||
if (result.error) {
|
||||
warn('[env] Konnte .env nicht laden:', result.error.message);
|
||||
warn('[env] Fehler-Details:', result.error);
|
||||
} else {
|
||||
log('[env] .env erfolgreich geladen von:', envPath, usingProduction ? '(production)' : '(local)');
|
||||
log('[env] Geladene Variablen:', Object.keys(result.parsed || {}));
|
||||
}
|
||||
} catch (err) {
|
||||
// Sollte nicht passieren, aber falls dotenv intern eine Exception wirft (z.B. EACCES), fange sie ab
|
||||
warn('[env] Unerwarteter Fehler beim Laden der .env:', err && err.message);
|
||||
warn('[env] Stack:', err && err.stack);
|
||||
if (envPath !== localEnvPath && fs.existsSync(localEnvPath)) {
|
||||
log('[env] Versuche stattdessen lokale .env:', localEnvPath);
|
||||
try {
|
||||
result = dotenv.config({ path: localEnvPath, quiet: dotenvQuiet });
|
||||
if (!result.error) {
|
||||
log('[env] Lokale .env erfolgreich geladen von:', localEnvPath);
|
||||
}
|
||||
} catch (err2) {
|
||||
warn('[env] Konnte lokale .env auch nicht laden:', err2 && err2.message);
|
||||
}
|
||||
}
|
||||
// Lade .env-Datei
|
||||
console.log('[env] Versuche .env zu laden von:', envPath);
|
||||
console.log('[env] Datei existiert:', fs.existsSync(envPath));
|
||||
console.log('[env] Datei lesbar:', fs.accessSync ? (() => { try { fs.accessSync(envPath, fs.constants.R_OK); return true; } catch { return false; } })() : 'unbekannt');
|
||||
|
||||
const result = dotenv.config({ path: envPath });
|
||||
if (result.error) {
|
||||
console.warn('[env] Konnte .env nicht laden:', result.error.message);
|
||||
console.warn('[env] Fehler-Details:', result.error);
|
||||
} else {
|
||||
console.log('[env] .env erfolgreich geladen von:', envPath);
|
||||
console.log('[env] Geladene Variablen:', Object.keys(result.parsed || {}));
|
||||
}
|
||||
|
||||
// Lokale Überschreibungen (nicht committen): z. B. SSH-Tunnel DB_HOST=127.0.0.1 DB_PORT=60000
|
||||
const localOverridePath = path.resolve(__dirname, '../.env.local');
|
||||
if (fs.existsSync(localOverridePath)) {
|
||||
const overrideResult = dotenv.config({ path: localOverridePath, override: true, quiet: dotenvQuiet });
|
||||
if (!overrideResult.error) {
|
||||
log('[env] .env.local geladen (überschreibt Werte, z. B. SSH-Tunnel)');
|
||||
} else {
|
||||
warn('[env] .env.local vorhanden, aber Laden fehlgeschlagen:', overrideResult.error?.message);
|
||||
}
|
||||
}
|
||||
|
||||
if (!quietEnv) {
|
||||
console.log('[env] Redis-Konfiguration:');
|
||||
console.log('[env] REDIS_HOST:', process.env.REDIS_HOST);
|
||||
console.log('[env] REDIS_PORT:', process.env.REDIS_PORT);
|
||||
console.log('[env] REDIS_PASSWORD:', process.env.REDIS_PASSWORD ? '***gesetzt***' : 'NICHT GESETZT');
|
||||
console.log('[env] REDIS_URL:', process.env.REDIS_URL);
|
||||
}
|
||||
// Debug: Zeige Redis-Konfiguration
|
||||
console.log('[env] Redis-Konfiguration:');
|
||||
console.log('[env] REDIS_HOST:', process.env.REDIS_HOST);
|
||||
console.log('[env] REDIS_PORT:', process.env.REDIS_PORT);
|
||||
console.log('[env] REDIS_PASSWORD:', process.env.REDIS_PASSWORD ? '***gesetzt***' : 'NICHT GESETZT');
|
||||
console.log('[env] REDIS_URL:', process.env.REDIS_URL);
|
||||
|
||||
if (!process.env.SECRET_KEY) {
|
||||
warn('[env] SECRET_KEY nicht gesetzt in .env');
|
||||
console.warn('[env] SECRET_KEY nicht gesetzt in .env');
|
||||
}
|
||||
export {};
|
||||
|
||||
@@ -13,9 +13,6 @@ class AdminController {
|
||||
this.searchUser = this.searchUser.bind(this);
|
||||
this.getFalukantUserById = this.getFalukantUserById.bind(this);
|
||||
this.changeFalukantUser = this.changeFalukantUser.bind(this);
|
||||
this.adminForceFalukantPregnancy = this.adminForceFalukantPregnancy.bind(this);
|
||||
this.adminClearFalukantPregnancy = this.adminClearFalukantPregnancy.bind(this);
|
||||
this.adminForceFalukantBirth = this.adminForceFalukantBirth.bind(this);
|
||||
this.getFalukantUserBranches = this.getFalukantUserBranches.bind(this);
|
||||
this.updateFalukantStock = this.updateFalukantStock.bind(this);
|
||||
this.addFalukantStock = this.addFalukantStock.bind(this);
|
||||
@@ -32,12 +29,6 @@ class AdminController {
|
||||
this.getUser = this.getUser.bind(this);
|
||||
this.getUsers = this.getUsers.bind(this);
|
||||
this.updateUser = this.updateUser.bind(this);
|
||||
this.getAdultVerificationRequests = this.getAdultVerificationRequests.bind(this);
|
||||
this.setAdultVerificationStatus = this.setAdultVerificationStatus.bind(this);
|
||||
this.getAdultVerificationDocument = this.getAdultVerificationDocument.bind(this);
|
||||
this.getEroticModerationReports = this.getEroticModerationReports.bind(this);
|
||||
this.applyEroticModerationAction = this.applyEroticModerationAction.bind(this);
|
||||
this.getEroticModerationPreview = this.getEroticModerationPreview.bind(this);
|
||||
|
||||
// Rights
|
||||
this.listRightTypes = this.listRightTypes.bind(this);
|
||||
@@ -128,97 +119,6 @@ class AdminController {
|
||||
}
|
||||
}
|
||||
|
||||
async getAdultVerificationRequests(req, res) {
|
||||
try {
|
||||
const { userid: requester } = req.headers;
|
||||
const { status = 'pending' } = req.query;
|
||||
const result = await AdminService.getAdultVerificationRequests(requester, status);
|
||||
res.status(200).json(result);
|
||||
} catch (error) {
|
||||
const status = error.message === 'noaccess' ? 403 : 500;
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async setAdultVerificationStatus(req, res) {
|
||||
const schema = Joi.object({
|
||||
status: Joi.string().valid('approved', 'rejected', 'pending').required()
|
||||
});
|
||||
const { error, value } = schema.validate(req.body || {});
|
||||
if (error) {
|
||||
return res.status(400).json({ error: error.details[0].message });
|
||||
}
|
||||
try {
|
||||
const { userid: requester } = req.headers;
|
||||
const { id } = req.params;
|
||||
const result = await AdminService.setAdultVerificationStatus(requester, id, value.status);
|
||||
res.status(200).json(result);
|
||||
} catch (err) {
|
||||
const status = err.message === 'noaccess' ? 403 : (['notfound', 'notadult', 'wrongstatus', 'missingparamtype'].includes(err.message) ? 400 : 500);
|
||||
res.status(status).json({ error: err.message });
|
||||
}
|
||||
}
|
||||
|
||||
async getAdultVerificationDocument(req, res) {
|
||||
try {
|
||||
const { userid: requester } = req.headers;
|
||||
const { id } = req.params;
|
||||
const result = await AdminService.getAdultVerificationDocument(requester, id);
|
||||
res.setHeader('Content-Type', result.mimeType);
|
||||
res.setHeader('Content-Disposition', `inline; filename="${encodeURIComponent(result.originalName)}"`);
|
||||
res.sendFile(result.filePath);
|
||||
} catch (err) {
|
||||
const status = err.message === 'noaccess' ? 403 : (['notfound', 'norequest', 'nofile'].includes(err.message) ? 404 : 500);
|
||||
res.status(status).json({ error: err.message });
|
||||
}
|
||||
}
|
||||
|
||||
async getEroticModerationReports(req, res) {
|
||||
try {
|
||||
const { userid: requester } = req.headers;
|
||||
const { status = 'open' } = req.query;
|
||||
const result = await AdminService.getEroticModerationReports(requester, status);
|
||||
res.status(200).json(result);
|
||||
} catch (error) {
|
||||
const status = error.message === 'noaccess' ? 403 : 500;
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async applyEroticModerationAction(req, res) {
|
||||
const schema = Joi.object({
|
||||
action: Joi.string().valid('dismiss', 'hide_content', 'restore_content', 'delete_content', 'block_uploads', 'revoke_access').required(),
|
||||
note: Joi.string().allow('', null).max(2000).optional()
|
||||
});
|
||||
const { error, value } = schema.validate(req.body || {});
|
||||
if (error) {
|
||||
return res.status(400).json({ error: error.details[0].message });
|
||||
}
|
||||
try {
|
||||
const { userid: requester } = req.headers;
|
||||
const { id } = req.params;
|
||||
const result = await AdminService.applyEroticModerationAction(requester, Number(id), value.action, value.note || null);
|
||||
res.status(200).json(result);
|
||||
} catch (err) {
|
||||
const status = err.message === 'noaccess' ? 403 : (['notfound', 'targetnotfound', 'wrongaction'].includes(err.message) ? 400 : 500);
|
||||
res.status(status).json({ error: err.message });
|
||||
}
|
||||
}
|
||||
|
||||
async getEroticModerationPreview(req, res) {
|
||||
try {
|
||||
const { userid: requester } = req.headers;
|
||||
const { type, targetId } = req.params;
|
||||
const result = await AdminService.getEroticModerationPreview(requester, type, Number(targetId));
|
||||
res.setHeader('Content-Type', result.mimeType);
|
||||
res.setHeader('Content-Disposition', `inline; filename="${encodeURIComponent(result.originalName)}"`);
|
||||
res.sendFile(result.filePath);
|
||||
} catch (err) {
|
||||
const status = err.message === 'noaccess' ? 403 : (['notfound', 'nofile', 'wrongtype'].includes(err.message) ? 404 : 500);
|
||||
res.status(status).json({ error: err.message });
|
||||
}
|
||||
}
|
||||
|
||||
// --- Rights ---
|
||||
async listRightTypes(req, res) {
|
||||
try {
|
||||
@@ -375,50 +275,6 @@ class AdminController {
|
||||
}
|
||||
}
|
||||
|
||||
async adminForceFalukantPregnancy(req, res) {
|
||||
try {
|
||||
const { userid: userId } = req.headers;
|
||||
const { characterId, fatherCharacterId, dueInDays } = req.body;
|
||||
const response = await AdminService.adminForceFalukantPregnancy(userId, characterId, {
|
||||
fatherCharacterId,
|
||||
dueInDays,
|
||||
});
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(400).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async adminClearFalukantPregnancy(req, res) {
|
||||
try {
|
||||
const { userid: userId } = req.headers;
|
||||
const { characterId } = req.body;
|
||||
const response = await AdminService.adminClearFalukantPregnancy(userId, characterId);
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(400).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async adminForceFalukantBirth(req, res) {
|
||||
try {
|
||||
const { userid: userId } = req.headers;
|
||||
const { motherCharacterId, fatherCharacterId, birthContext, legitimacy, gender } = req.body;
|
||||
const response = await AdminService.adminForceFalukantBirth(userId, motherCharacterId, {
|
||||
fatherCharacterId,
|
||||
birthContext,
|
||||
legitimacy,
|
||||
gender,
|
||||
});
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(400).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async getFalukantUserBranches(req, res) {
|
||||
try {
|
||||
const { userid: userId } = req.headers;
|
||||
@@ -667,7 +523,6 @@ class AdminController {
|
||||
title: Joi.string().min(1).max(255).required(),
|
||||
roomTypeId: Joi.number().integer().required(),
|
||||
isPublic: Joi.boolean().required(),
|
||||
isAdultOnly: Joi.boolean().allow(null),
|
||||
genderRestrictionId: Joi.number().integer().allow(null),
|
||||
minAge: Joi.number().integer().min(0).allow(null),
|
||||
maxAge: Joi.number().integer().min(0).allow(null),
|
||||
@@ -679,7 +534,7 @@ class AdminController {
|
||||
if (error) {
|
||||
return res.status(400).json({ error: error.details[0].message });
|
||||
}
|
||||
const room = await AdminService.updateRoom(userId, req.params.id, value);
|
||||
const room = await AdminService.updateRoom(req.params.id, value);
|
||||
res.status(200).json(room);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
@@ -698,7 +553,6 @@ class AdminController {
|
||||
title: Joi.string().min(1).max(255).required(),
|
||||
roomTypeId: Joi.number().integer().required(),
|
||||
isPublic: Joi.boolean().required(),
|
||||
isAdultOnly: Joi.boolean().allow(null),
|
||||
genderRestrictionId: Joi.number().integer().allow(null),
|
||||
minAge: Joi.number().integer().min(0).allow(null),
|
||||
maxAge: Joi.number().integer().min(0).allow(null),
|
||||
@@ -725,7 +579,7 @@ class AdminController {
|
||||
if (!userId || !(await AdminService.hasUserAccess(userId, 'chatrooms'))) {
|
||||
return res.status(403).json({ error: 'Keine Berechtigung.' });
|
||||
}
|
||||
await AdminService.deleteRoom(userId, req.params.id);
|
||||
await AdminService.deleteRoom(req.params.id);
|
||||
res.sendStatus(204);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
@@ -1,203 +0,0 @@
|
||||
import calendarService from '../services/calendarService.js';
|
||||
|
||||
function getHashedUserId(req) {
|
||||
return req.headers?.userid;
|
||||
}
|
||||
|
||||
export default {
|
||||
/**
|
||||
* GET /api/calendar/events
|
||||
* Get all events for the authenticated user
|
||||
* Query params: startDate, endDate (optional)
|
||||
*/
|
||||
async getEvents(req, res) {
|
||||
const hashedUserId = getHashedUserId(req);
|
||||
if (!hashedUserId) {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
|
||||
try {
|
||||
const { startDate, endDate } = req.query;
|
||||
const events = await calendarService.getEvents(hashedUserId, { startDate, endDate });
|
||||
res.json(events);
|
||||
} catch (error) {
|
||||
console.error('Calendar getEvents:', error);
|
||||
res.status(500).json({ error: error.message || 'Internal server error' });
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* GET /api/calendar/events/:id
|
||||
* Get a single event by ID
|
||||
*/
|
||||
async getEvent(req, res) {
|
||||
const hashedUserId = getHashedUserId(req);
|
||||
if (!hashedUserId) {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
|
||||
try {
|
||||
const event = await calendarService.getEvent(hashedUserId, req.params.id);
|
||||
res.json(event);
|
||||
} catch (error) {
|
||||
console.error('Calendar getEvent:', error);
|
||||
if (error.message === 'Event not found') {
|
||||
return res.status(404).json({ error: 'Event not found' });
|
||||
}
|
||||
res.status(500).json({ error: error.message || 'Internal server error' });
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* POST /api/calendar/events
|
||||
* Create a new event
|
||||
*/
|
||||
async createEvent(req, res) {
|
||||
const hashedUserId = getHashedUserId(req);
|
||||
if (!hashedUserId) {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
|
||||
try {
|
||||
const eventData = req.body;
|
||||
if (!eventData.title || !eventData.startDate) {
|
||||
return res.status(400).json({ error: 'Title and startDate are required' });
|
||||
}
|
||||
|
||||
const event = await calendarService.createEvent(hashedUserId, eventData);
|
||||
res.status(201).json(event);
|
||||
} catch (error) {
|
||||
console.error('Calendar createEvent:', error);
|
||||
res.status(500).json({ error: error.message || 'Internal server error' });
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* PUT /api/calendar/events/:id
|
||||
* Update an existing event
|
||||
*/
|
||||
async updateEvent(req, res) {
|
||||
const hashedUserId = getHashedUserId(req);
|
||||
if (!hashedUserId) {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
|
||||
try {
|
||||
const eventData = req.body;
|
||||
if (!eventData.title || !eventData.startDate) {
|
||||
return res.status(400).json({ error: 'Title and startDate are required' });
|
||||
}
|
||||
|
||||
const event = await calendarService.updateEvent(hashedUserId, req.params.id, eventData);
|
||||
res.json(event);
|
||||
} catch (error) {
|
||||
console.error('Calendar updateEvent:', error);
|
||||
if (error.message === 'Event not found') {
|
||||
return res.status(404).json({ error: 'Event not found' });
|
||||
}
|
||||
res.status(500).json({ error: error.message || 'Internal server error' });
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* DELETE /api/calendar/events/:id
|
||||
* Delete an event
|
||||
*/
|
||||
async deleteEvent(req, res) {
|
||||
const hashedUserId = getHashedUserId(req);
|
||||
if (!hashedUserId) {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
|
||||
try {
|
||||
await calendarService.deleteEvent(hashedUserId, req.params.id);
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Calendar deleteEvent:', error);
|
||||
if (error.message === 'Event not found') {
|
||||
return res.status(404).json({ error: 'Event not found' });
|
||||
}
|
||||
res.status(500).json({ error: error.message || 'Internal server error' });
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* GET /api/calendar/birthdays
|
||||
* Get friends' birthdays for a given year
|
||||
* Query params: year (required)
|
||||
*/
|
||||
async getFriendsBirthdays(req, res) {
|
||||
const hashedUserId = getHashedUserId(req);
|
||||
if (!hashedUserId) {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
|
||||
try {
|
||||
const year = parseInt(req.query.year) || new Date().getFullYear();
|
||||
const birthdays = await calendarService.getFriendsBirthdays(hashedUserId, year);
|
||||
res.json(birthdays);
|
||||
} catch (error) {
|
||||
console.error('Calendar getFriendsBirthdays:', error);
|
||||
res.status(500).json({ error: error.message || 'Internal server error' });
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* GET /api/calendar/widget/birthdays
|
||||
* Get upcoming birthdays for widget display
|
||||
*/
|
||||
async getWidgetBirthdays(req, res) {
|
||||
const hashedUserId = getHashedUserId(req);
|
||||
if (!hashedUserId) {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
|
||||
try {
|
||||
const limit = parseInt(req.query.limit) || 10;
|
||||
const birthdays = await calendarService.getUpcomingBirthdays(hashedUserId, limit);
|
||||
res.json(birthdays);
|
||||
} catch (error) {
|
||||
console.error('Calendar getWidgetBirthdays:', error);
|
||||
res.status(500).json({ error: error.message || 'Internal server error' });
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* GET /api/calendar/widget/upcoming
|
||||
* Get upcoming events for widget display
|
||||
*/
|
||||
async getWidgetUpcoming(req, res) {
|
||||
const hashedUserId = getHashedUserId(req);
|
||||
if (!hashedUserId) {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
|
||||
try {
|
||||
const limit = parseInt(req.query.limit) || 10;
|
||||
const events = await calendarService.getUpcomingEvents(hashedUserId, limit);
|
||||
res.json(events);
|
||||
} catch (error) {
|
||||
console.error('Calendar getWidgetUpcoming:', error);
|
||||
res.status(500).json({ error: error.message || 'Internal server error' });
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* GET /api/calendar/widget/mini
|
||||
* Get mini calendar data for widget display
|
||||
*/
|
||||
async getWidgetMiniCalendar(req, res) {
|
||||
const hashedUserId = getHashedUserId(req);
|
||||
if (!hashedUserId) {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await calendarService.getMiniCalendarData(hashedUserId);
|
||||
res.json(data);
|
||||
} catch (error) {
|
||||
console.error('Calendar getWidgetMiniCalendar:', error);
|
||||
res.status(500).json({ error: error.message || 'Internal server error' });
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -13,9 +13,6 @@ class ChatController {
|
||||
this.sendOneToOneMessage = this.sendOneToOneMessage.bind(this);
|
||||
this.getOneToOneMessageHistory = this.getOneToOneMessageHistory.bind(this);
|
||||
this.getRoomList = this.getRoomList.bind(this);
|
||||
this.getRoomCreateOptions = this.getRoomCreateOptions.bind(this);
|
||||
this.getOwnRooms = this.getOwnRooms.bind(this);
|
||||
this.deleteOwnRoom = this.deleteOwnRoom.bind(this);
|
||||
}
|
||||
|
||||
async getMessages(req, res) {
|
||||
@@ -172,49 +169,12 @@ class ChatController {
|
||||
async getRoomList(req, res) {
|
||||
// Öffentliche Räume für Chat-Frontend
|
||||
try {
|
||||
const { userid: hashedUserId } = req.headers;
|
||||
const adultOnly = String(req.query.adultOnly || '').toLowerCase() === 'true';
|
||||
const rooms = await chatService.getRoomList(hashedUserId, { adultOnly });
|
||||
const rooms = await chatService.getRoomList();
|
||||
res.status(200).json(rooms);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async getRoomCreateOptions(req, res) {
|
||||
try {
|
||||
const options = await chatService.getRoomCreateOptions();
|
||||
res.status(200).json(options);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async getOwnRooms(req, res) {
|
||||
try {
|
||||
const { userid: hashedUserId } = req.headers;
|
||||
const rooms = await chatService.getOwnRooms(hashedUserId);
|
||||
res.status(200).json(rooms);
|
||||
} catch (error) {
|
||||
const status = error.message === 'user_not_found' ? 404 : 500;
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async deleteOwnRoom(req, res) {
|
||||
try {
|
||||
const { userid: hashedUserId } = req.headers;
|
||||
const roomId = Number.parseInt(req.params.id, 10);
|
||||
if (!Number.isInteger(roomId) || roomId <= 0) {
|
||||
return res.status(400).json({ error: 'invalid_room_id' });
|
||||
}
|
||||
await chatService.deleteOwnRoom(hashedUserId, roomId);
|
||||
res.status(204).send();
|
||||
} catch (error) {
|
||||
const status = error.message === 'room_not_found_or_not_owner' || error.message === 'user_not_found' ? 404 : 500;
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ChatController;
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
import dashboardService from '../services/dashboardService.js';
|
||||
|
||||
function getHashedUserId(req) {
|
||||
return req.headers?.userid;
|
||||
}
|
||||
|
||||
export default {
|
||||
/** Liste der möglichen Widget-Typen (öffentlich, keine Auth nötig wenn gewünscht – aktuell mit Auth). */
|
||||
async getAvailableWidgets(req, res) {
|
||||
try {
|
||||
const list = await dashboardService.getAvailableWidgets();
|
||||
res.json(list);
|
||||
} catch (error) {
|
||||
console.error('Dashboard getAvailableWidgets:', error);
|
||||
res.status(500).json({ error: error.message || 'Internal server error' });
|
||||
}
|
||||
},
|
||||
|
||||
async getConfig(req, res) {
|
||||
const hashedUserId = getHashedUserId(req);
|
||||
if (!hashedUserId) {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
try {
|
||||
const config = await dashboardService.getConfig(hashedUserId);
|
||||
res.json(config);
|
||||
} catch (error) {
|
||||
console.error('Dashboard getConfig:', error);
|
||||
res.status(500).json({ error: error.message || 'Internal server error' });
|
||||
}
|
||||
},
|
||||
|
||||
async setConfig(req, res) {
|
||||
const hashedUserId = getHashedUserId(req);
|
||||
if (!hashedUserId) {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
const config = req.body;
|
||||
if (!config || typeof config !== 'object') {
|
||||
return res.status(400).json({ error: 'Invalid config' });
|
||||
}
|
||||
try {
|
||||
const result = await dashboardService.setConfig(hashedUserId, config);
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
console.error('Dashboard setConfig:', error);
|
||||
res.status(500).json({ error: error.message || 'Internal server error' });
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -26,51 +26,45 @@ class FalukantController {
|
||||
}, { successStatus: 201 });
|
||||
|
||||
this.getInfo = this._wrapWithUser((userId) => this.service.getInfo(userId));
|
||||
// Dashboard widget: originaler Endpoint (siehe Commit 62d8cd7)
|
||||
this.getDashboardWidget = this._wrapWithUser((userId) => this.service.getDashboardWidget(userId));
|
||||
this.getBranches = this._wrapWithUser((userId) => this.service.getBranches(userId));
|
||||
this.createBranch = this._wrapWithUser((userId, req) => this.service.createBranch(userId, req.body.cityId, req.body.branchTypeId), { blockInDebtorsPrison: true });
|
||||
this.createBranch = this._wrapWithUser((userId, req) => this.service.createBranch(userId, req.body.cityId, req.body.branchTypeId));
|
||||
this.getBranchTypes = this._wrapWithUser((userId) => this.service.getBranchTypes(userId));
|
||||
this.getBranch = this._wrapWithUser((userId, req) => this.service.getBranch(userId, req.params.branch));
|
||||
this.upgradeBranch = this._wrapWithUser((userId, req) => this.service.upgradeBranch(userId, req.body.branchId), { blockInDebtorsPrison: true });
|
||||
this.upgradeBranch = this._wrapWithUser((userId, req) => this.service.upgradeBranch(userId, req.body.branchId));
|
||||
this.createProduction = this._wrapWithUser((userId, req) => {
|
||||
const { branchId, productId, quantity } = req.body;
|
||||
return this.service.createProduction(userId, branchId, productId, quantity);
|
||||
}, { successStatus: 201, blockInDebtorsPrison: true });
|
||||
}, { successStatus: 201 });
|
||||
this.getProduction = this._wrapWithUser((userId, req) => this.service.getProduction(userId, req.params.branchId));
|
||||
this.getStock = this._wrapWithUser((userId, req) => this.service.getStock(userId, req.params.branchId || null));
|
||||
this.createStock = this._wrapWithUser((userId, req) => {
|
||||
const { branchId, stockTypeId, stockSize } = req.body;
|
||||
return this.service.createStock(userId, branchId, stockTypeId, stockSize);
|
||||
}, { successStatus: 201, blockInDebtorsPrison: true });
|
||||
}, { successStatus: 201 });
|
||||
this.getProducts = this._wrapWithUser((userId) => this.service.getProducts(userId));
|
||||
this.getInventory = this._wrapWithUser((userId, req) => this.service.getInventory(userId, req.params.branchId));
|
||||
this.sellProduct = this._wrapWithUser((userId, req) => {
|
||||
const { branchId, productId, quality, quantity } = req.body;
|
||||
return this.service.sellProduct(userId, branchId, productId, quality, quantity);
|
||||
}, { successStatus: 201, blockInDebtorsPrison: true });
|
||||
}, { successStatus: 201 });
|
||||
this.sellAllProducts = this._wrapWithUser((userId, req) => {
|
||||
const { branchId } = req.body;
|
||||
return this.service.sellAllProducts(userId, branchId);
|
||||
}, { successStatus: 201, blockInDebtorsPrison: true });
|
||||
}, { successStatus: 201 });
|
||||
this.moneyHistory = this._wrapWithUser((userId, req) => {
|
||||
let { page, filter } = req.body;
|
||||
if (!page) page = 1;
|
||||
return this.service.moneyHistory(userId, page, filter);
|
||||
});
|
||||
this.moneyHistoryGraph = this._wrapWithUser((userId, req) => {
|
||||
const { range } = req.body || {};
|
||||
return this.service.moneyHistoryGraph(userId, range || '24h');
|
||||
});
|
||||
this.getStorage = this._wrapWithUser((userId, req) => this.service.getStorage(userId, req.params.branchId));
|
||||
this.buyStorage = this._wrapWithUser((userId, req) => {
|
||||
const { branchId, amount, stockTypeId } = req.body;
|
||||
return this.service.buyStorage(userId, branchId, amount, stockTypeId);
|
||||
}, { successStatus: 201, blockInDebtorsPrison: true });
|
||||
}, { successStatus: 201 });
|
||||
this.sellStorage = this._wrapWithUser((userId, req) => {
|
||||
const { branchId, amount, stockTypeId } = req.body;
|
||||
return this.service.sellStorage(userId, branchId, amount, stockTypeId);
|
||||
}, { successStatus: 202, blockInDebtorsPrison: true });
|
||||
}, { successStatus: 202 });
|
||||
|
||||
this.getStockTypes = this._wrapSimple(() => this.service.getStockTypes());
|
||||
this.getStockOverview = this._wrapSimple(() => this.service.getStockOverview());
|
||||
@@ -80,137 +74,83 @@ class FalukantController {
|
||||
console.log('🔍 getDirectorProposals called with userId:', userId, 'branchId:', req.body.branchId);
|
||||
return this.service.getDirectorProposals(userId, req.body.branchId);
|
||||
});
|
||||
this.convertProposalToDirector = this._wrapWithUser((userId, req) => this.service.convertProposalToDirector(userId, req.body.proposalId), { blockInDebtorsPrison: true });
|
||||
this.convertProposalToDirector = this._wrapWithUser((userId, req) => this.service.convertProposalToDirector(userId, req.body.proposalId));
|
||||
this.getDirectorForBranch = this._wrapWithUser((userId, req) => this.service.getDirectorForBranch(userId, req.params.branchId));
|
||||
this.getAllDirectors = this._wrapWithUser((userId) => this.service.getAllDirectors(userId));
|
||||
this.updateDirector = this._wrapWithUser((userId, req) => {
|
||||
const { directorId, income } = req.body;
|
||||
return this.service.updateDirector(userId, directorId, income);
|
||||
}, { blockInDebtorsPrison: true });
|
||||
});
|
||||
|
||||
this.setSetting = this._wrapWithUser((userId, req) => {
|
||||
const { branchId, directorId, settingKey, value } = req.body;
|
||||
return this.service.setSetting(userId, branchId, directorId, settingKey, value);
|
||||
}, { blockInDebtorsPrison: true });
|
||||
});
|
||||
|
||||
this.getFamily = this._wrapWithUser(async (userId) => {
|
||||
const result = await this.service.getFamily(userId);
|
||||
if (!result) throw { status: 404, message: 'No family data found' };
|
||||
return result;
|
||||
});
|
||||
this.setHeir = this._wrapWithUser((userId, req) => this.service.setHeir(userId, req.body.childCharacterId));
|
||||
this.getPotentialHeirs = this._wrapWithUser((userId) => this.service.getPotentialHeirs(userId));
|
||||
this.selectHeir = this._wrapWithUser((userId, req) => this.service.selectHeir(userId, req.body.heirId), { blockInDebtorsPrison: true });
|
||||
this.setHeir = this._wrapWithUser((userId, req) => this.service.setHeir(userId, req.body.childCharacterId), { blockInDebtorsPrison: true });
|
||||
this.acceptMarriageProposal = this._wrapWithUser((userId, req) => this.service.acceptMarriageProposal(userId, req.body.proposalId), { blockInDebtorsPrison: true });
|
||||
this.cancelWooing = this._wrapWithUser(async (userId) => {
|
||||
try {
|
||||
return await this.service.cancelWooing(userId);
|
||||
} catch (e) {
|
||||
if (e && e.name === 'PreconditionError' && e.message === 'cancelTooSoon') {
|
||||
throw { status: 412, message: 'cancelTooSoon', retryAt: e.meta?.retryAt };
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}, { successStatus: 202, blockInDebtorsPrison: true });
|
||||
this.selectHeir = this._wrapWithUser((userId, req) => this.service.selectHeir(userId, req.body.heirId));
|
||||
this.acceptMarriageProposal = this._wrapWithUser((userId, req) => this.service.acceptMarriageProposal(userId, req.body.proposalId));
|
||||
this.getGifts = this._wrapWithUser((userId) => {
|
||||
console.log('🔍 getGifts called with userId:', userId);
|
||||
return this.service.getGifts(userId);
|
||||
});
|
||||
this.setLoverMaintenance = this._wrapWithUser((userId, req) =>
|
||||
this.service.setLoverMaintenance(userId, req.params.relationshipId, req.body?.maintenanceLevel), { blockInDebtorsPrison: true });
|
||||
this.createLoverRelationship = this._wrapWithUser((userId, req) =>
|
||||
this.service.createLoverRelationship(userId, req.body?.targetCharacterId, req.body?.loverRole), { successStatus: 201, blockInDebtorsPrison: true });
|
||||
this.spendTimeWithSpouse = this._wrapWithUser((userId) =>
|
||||
this.service.spendTimeWithSpouse(userId), { blockInDebtorsPrison: true });
|
||||
this.giftToSpouse = this._wrapWithUser((userId, req) =>
|
||||
this.service.giftToSpouse(userId, req.body?.giftLevel), { blockInDebtorsPrison: true });
|
||||
this.reconcileMarriage = this._wrapWithUser((userId) =>
|
||||
this.service.reconcileMarriage(userId), { blockInDebtorsPrison: true });
|
||||
this.acknowledgeLover = this._wrapWithUser((userId, req) =>
|
||||
this.service.acknowledgeLover(userId, req.params.relationshipId), { blockInDebtorsPrison: true });
|
||||
this.endLoverRelationship = this._wrapWithUser((userId, req) =>
|
||||
this.service.endLoverRelationship(userId, req.params.relationshipId), { blockInDebtorsPrison: true });
|
||||
this.getChildren = this._wrapWithUser((userId) => this.service.getChildren(userId));
|
||||
this.sendGift = this._wrapWithUser(async (userId, req) => {
|
||||
try {
|
||||
return await this.service.sendGift(userId, req.body.giftId);
|
||||
} catch (e) {
|
||||
if (e && e.name === 'PreconditionError' && e.message === 'tooOften') {
|
||||
throw { status: 412, message: 'tooOften', retryAt: e.meta?.retryAt };
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}, { blockInDebtorsPrison: true });
|
||||
this.sendGift = this._wrapWithUser((userId, req) => this.service.sendGift(userId, req.body.giftId));
|
||||
|
||||
this.getTitlesOfNobility = this._wrapWithUser((userId) => this.service.getTitlesOfNobility(userId));
|
||||
this.getReputationActions = this._wrapWithUser((userId) => this.service.getReputationActions(userId));
|
||||
this.executeReputationAction = this._wrapWithUser((userId, req) =>
|
||||
this.service.executeReputationAction(userId, req.body?.actionTypeId), { successStatus: 201, blockInDebtorsPrison: true });
|
||||
this.getHouseTypes = this._wrapWithUser((userId) => this.service.getHouseTypes(userId));
|
||||
this.getMoodAffect = this._wrapWithUser((userId) => this.service.getMoodAffect(userId));
|
||||
this.getCharacterAffect = this._wrapWithUser((userId) => this.service.getCharacterAffect(userId));
|
||||
this.getUserHouse = this._wrapWithUser((userId) => this.service.getUserHouse(userId));
|
||||
this.getBuyableHouses = this._wrapWithUser((userId) => this.service.getBuyableHouses(userId));
|
||||
this.buyUserHouse = this._wrapWithUser((userId, req) => this.service.buyUserHouse(userId, req.body.houseId), { successStatus: 201, blockInDebtorsPrison: true });
|
||||
this.hireServants = this._wrapWithUser((userId, req) => this.service.hireServants(userId, req.body?.amount), { successStatus: 201, blockInDebtorsPrison: true });
|
||||
this.dismissServants = this._wrapWithUser((userId, req) => this.service.dismissServants(userId, req.body?.amount), { blockInDebtorsPrison: true });
|
||||
this.setServantPayLevel = this._wrapWithUser((userId, req) => this.service.setServantPayLevel(userId, req.body?.payLevel), { blockInDebtorsPrison: true });
|
||||
this.tidyHousehold = this._wrapWithUser((userId) => this.service.tidyHousehold(userId), { blockInDebtorsPrison: true });
|
||||
this.buyUserHouse = this._wrapWithUser((userId, req) => this.service.buyUserHouse(userId, req.body.houseId), { successStatus: 201 });
|
||||
|
||||
this.getPartyTypes = this._wrapWithUser((userId) => this.service.getPartyTypes(userId));
|
||||
this.createParty = this._wrapWithUser((userId, req) => {
|
||||
const { partyTypeId, musicId, banquetteId, nobilityIds, servantRatio } = req.body;
|
||||
return this.service.createParty(userId, partyTypeId, musicId, banquetteId, nobilityIds, servantRatio);
|
||||
}, { successStatus: 201, blockInDebtorsPrison: true });
|
||||
}, { successStatus: 201 });
|
||||
this.getParties = this._wrapWithUser((userId) => this.service.getParties(userId));
|
||||
|
||||
this.getReputationActions = this._wrapWithUser((userId) => this.service.getReputationActions(userId));
|
||||
this.executeReputationAction = this._wrapWithUser((userId, req) => {
|
||||
const { actionTypeId } = req.body;
|
||||
return this.service.executeReputationAction(userId, actionTypeId);
|
||||
}, { successStatus: 201 });
|
||||
|
||||
this.getNotBaptisedChildren = this._wrapWithUser((userId) => this.service.getNotBaptisedChildren(userId));
|
||||
this.baptise = this._wrapWithUser((userId, req) => {
|
||||
const { characterId: childId, firstName } = req.body;
|
||||
return this.service.baptise(userId, childId, firstName);
|
||||
}, { blockInDebtorsPrison: true });
|
||||
this.getChurchOverview = this._wrapWithUser((userId) => this.service.getChurchOverview(userId));
|
||||
this.getAvailableChurchPositions = this._wrapWithUser((userId) => this.service.getAvailableChurchPositions(userId));
|
||||
this.getSupervisedApplications = this._wrapWithUser((userId) => this.service.getSupervisedApplications(userId));
|
||||
this.applyForChurchPosition = this._wrapWithUser((userId, req) => {
|
||||
const { officeTypeId, regionId } = req.body;
|
||||
return this.service.applyForChurchPosition(userId, officeTypeId, regionId);
|
||||
}, { blockInDebtorsPrison: true });
|
||||
this.decideOnChurchApplication = this._wrapWithUser((userId, req) => {
|
||||
const { applicationId, decision } = req.body;
|
||||
return this.service.decideOnChurchApplication(userId, applicationId, decision);
|
||||
}, { blockInDebtorsPrison: true });
|
||||
});
|
||||
|
||||
this.getEducation = this._wrapWithUser((userId) => this.service.getEducation(userId));
|
||||
this.sendToSchool = this._wrapWithUser((userId, req) => {
|
||||
const { item, student, studentId } = req.body;
|
||||
return this.service.sendToSchool(userId, item, student, studentId);
|
||||
}, { blockInDebtorsPrison: true });
|
||||
});
|
||||
|
||||
this.getBankOverview = this._wrapWithUser((userId) => this.service.getBankOverview(userId));
|
||||
this.getBankCredits = this._wrapWithUser((userId) => this.service.getBankCredits(userId));
|
||||
this.takeBankCredits = this._wrapWithUser((userId, req) => this.service.takeBankCredits(userId, req.body.height), { blockInDebtorsPrison: true });
|
||||
this.takeBankCredits = this._wrapWithUser((userId, req) => this.service.takeBankCredits(userId, req.body.height));
|
||||
|
||||
this.getNobility = this._wrapWithUser((userId) => this.service.getNobility(userId));
|
||||
this.advanceNobility = this._wrapWithUser((userId) => this.service.advanceNobility(userId), { blockInDebtorsPrison: true });
|
||||
this.advanceNobility = this._wrapWithUser((userId) => this.service.advanceNobility(userId));
|
||||
|
||||
this.getHealth = this._wrapWithUser((userId) => this.service.getHealth(userId));
|
||||
this.healthActivity = this._wrapWithUser(async (userId, req) => {
|
||||
try {
|
||||
return await this.service.healthActivity(userId, req.body.measureTr);
|
||||
} catch (e) {
|
||||
if (e && e.name === 'PreconditionError' && e.message === 'tooClose') {
|
||||
throw { status: 412, message: 'tooClose', retryAt: e.meta?.retryAt };
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}, { blockInDebtorsPrison: true });
|
||||
this.healthActivity = this._wrapWithUser((userId, req) => this.service.healthActivity(userId, req.body.measureTr));
|
||||
|
||||
this.getPoliticsOverview = this._wrapWithUser((userId) => this.service.getPoliticsOverview(userId));
|
||||
this.getOpenPolitics = this._wrapWithUser((userId) => this.service.getOpenPolitics(userId));
|
||||
this.getElections = this._wrapWithUser((userId) => this.service.getElections(userId));
|
||||
this.vote = this._wrapWithUser((userId, req) => this.service.vote(userId, req.body.votes), { blockInDebtorsPrison: true });
|
||||
this.applyForElections = this._wrapWithUser((userId, req) => this.service.applyForElections(userId, req.body.electionIds), { blockInDebtorsPrison: true });
|
||||
this.vote = this._wrapWithUser((userId, req) => this.service.vote(userId, req.body.votes));
|
||||
this.applyForElections = this._wrapWithUser((userId, req) => this.service.applyForElections(userId, req.body.electionIds));
|
||||
|
||||
this.getRegions = this._wrapWithUser((userId) => this.service.getRegions(userId));
|
||||
this.getBranchTaxes = this._wrapWithUser((userId, req) => this.service.getBranchTaxes(userId, req.params.branchId));
|
||||
@@ -222,13 +162,6 @@ class FalukantController {
|
||||
}
|
||||
return this.service.getProductPriceInRegion(userId, productId, regionId);
|
||||
});
|
||||
this.getAllProductPricesInRegion = this._wrapWithUser((userId, req) => {
|
||||
const regionId = parseInt(req.query.regionId, 10);
|
||||
if (Number.isNaN(regionId)) {
|
||||
throw new Error('regionId is required');
|
||||
}
|
||||
return this.service.getAllProductPricesInRegion(userId, regionId);
|
||||
});
|
||||
this.getProductPricesInCities = this._wrapWithUser((userId, req) => {
|
||||
const productId = parseInt(req.query.productId, 10);
|
||||
const currentPrice = parseFloat(req.query.currentPrice);
|
||||
@@ -238,26 +171,13 @@ class FalukantController {
|
||||
}
|
||||
return this.service.getProductPricesInCities(userId, productId, currentPrice, currentRegionId);
|
||||
});
|
||||
this.getProductPricesInCitiesBatch = this._wrapWithUser((userId, req) => {
|
||||
const body = req.body || {};
|
||||
const items = Array.isArray(body.items) ? body.items : [];
|
||||
const currentRegionId = body.currentRegionId != null ? parseInt(body.currentRegionId, 10) : null;
|
||||
const valid = items.map(i => ({
|
||||
productId: parseInt(i.productId, 10),
|
||||
currentPrice: parseFloat(i.currentPrice)
|
||||
})).filter(i => !Number.isNaN(i.productId) && !Number.isNaN(i.currentPrice));
|
||||
return this.service.getProductPricesInCitiesBatch(userId, valid, Number.isNaN(currentRegionId) ? null : currentRegionId);
|
||||
});
|
||||
this.renovate = this._wrapWithUser((userId, req) => this.service.renovate(userId, req.body.element), { blockInDebtorsPrison: true });
|
||||
this.renovateAll = this._wrapWithUser((userId) => this.service.renovateAll(userId), { blockInDebtorsPrison: true });
|
||||
this.renovate = this._wrapWithUser((userId, req) => this.service.renovate(userId, req.body.element));
|
||||
this.renovateAll = this._wrapWithUser((userId) => this.service.renovateAll(userId));
|
||||
|
||||
this.getUndergroundTypes = this._wrapWithUser((userId) => this.service.getUndergroundTypes(userId));
|
||||
this.getRaidTransportRegions = this._wrapWithUser((userId) => this.service.getRaidTransportRegions(userId));
|
||||
this.getUndergroundActivities = this._wrapWithUser((userId) => this.service.getUndergroundActivities(userId));
|
||||
this.getNotifications = this._wrapWithUser((userId) => this.service.getNotifications(userId));
|
||||
this.getAllNotifications = this._wrapWithUser((userId, req) => this.service.getAllNotifications(userId, req.query.page, req.query.size));
|
||||
this.markNotificationsShown = this._wrapWithUser((userId) => this.service.markNotificationsShown(userId), { successStatus: 202 });
|
||||
this.getDashboardWidget = this._wrapWithUser((userId) => this.service.getDashboardWidget(userId));
|
||||
this.getUndergroundTargets = this._wrapWithUser((userId) => this.service.getPoliticalOfficeHolders(userId));
|
||||
|
||||
this.searchUsers = this._wrapWithUser((userId, req) => {
|
||||
@@ -278,7 +198,7 @@ class FalukantController {
|
||||
throw { status: 400, message: 'goal is required for corrupt_politician' };
|
||||
}
|
||||
return this.service.createUndergroundActivity(userId, payload);
|
||||
}, { successStatus: 201, blockInDebtorsPrison: true });
|
||||
}, { successStatus: 201 });
|
||||
|
||||
this.getUndergroundAttacks = this._wrapWithUser((userId, req) => {
|
||||
const direction = (req.query.direction || '').toLowerCase();
|
||||
@@ -292,14 +212,14 @@ class FalukantController {
|
||||
this.getVehicleTypes = this._wrapWithUser((userId) => this.service.getVehicleTypes(userId));
|
||||
this.buyVehicles = this._wrapWithUser(
|
||||
(userId, req) => this.service.buyVehicles(userId, req.body),
|
||||
{ successStatus: 201, blockInDebtorsPrison: true }
|
||||
{ successStatus: 201 }
|
||||
);
|
||||
this.getVehicles = this._wrapWithUser(
|
||||
(userId, req) => this.service.getVehicles(userId, req.query.regionId)
|
||||
);
|
||||
this.createTransport = this._wrapWithUser(
|
||||
(userId, req) => this.service.createTransport(userId, req.body),
|
||||
{ successStatus: 201, blockInDebtorsPrison: true }
|
||||
{ successStatus: 201 }
|
||||
);
|
||||
this.getTransportRoute = this._wrapWithUser(
|
||||
(userId, req) => this.service.getTransportRoute(userId, req.query)
|
||||
@@ -309,40 +229,31 @@ class FalukantController {
|
||||
);
|
||||
this.repairVehicle = this._wrapWithUser(
|
||||
(userId, req) => this.service.repairVehicle(userId, req.params.vehicleId),
|
||||
{ successStatus: 200, blockInDebtorsPrison: true }
|
||||
{ successStatus: 200 }
|
||||
);
|
||||
this.repairAllVehicles = this._wrapWithUser(
|
||||
(userId, req) => this.service.repairAllVehicles(userId, req.body.vehicleIds),
|
||||
{ successStatus: 200, blockInDebtorsPrison: true }
|
||||
{ successStatus: 200 }
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
_wrapWithUser(fn, { successStatus = 200, postProcess, blockInDebtorsPrison = false } = {}) {
|
||||
_wrapWithUser(fn, { successStatus = 200, postProcess } = {}) {
|
||||
return async (req, res) => {
|
||||
try {
|
||||
const hashedUserId = extractHashedUserId(req);
|
||||
if (!hashedUserId) {
|
||||
return res.status(400).json({ error: 'Missing user identifier' });
|
||||
}
|
||||
if (blockInDebtorsPrison) {
|
||||
await this.service.assertActionAllowedOutsideDebtorsPrison(hashedUserId);
|
||||
}
|
||||
const result = await fn(hashedUserId, req, res);
|
||||
const toSend = postProcess ? postProcess(result) : result;
|
||||
res.status(successStatus).json(toSend);
|
||||
} catch (error) {
|
||||
console.error('Controller error:', error);
|
||||
const status = error.status && typeof error.status === 'number' ? error.status : 500;
|
||||
// Wenn error ein Objekt mit status ist, alle Felder außer status übernehmen
|
||||
if (error && typeof error === 'object' && error.status && typeof error.status === 'number') {
|
||||
const { status: errorStatus, ...errorData } = error;
|
||||
res.status(errorStatus).json({ error: error.message || errorData.message || 'Internal error', ...errorData });
|
||||
} else {
|
||||
res.status(status).json({ error: error.message || 'Internal error' });
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,11 @@ const menuStructure = {
|
||||
visible: ["all"],
|
||||
path: "/socialnetwork/gallery"
|
||||
},
|
||||
vocabtrainer: {
|
||||
visible: ["all"],
|
||||
path: "/socialnetwork/vocab",
|
||||
children: {}
|
||||
},
|
||||
blockedUsers: {
|
||||
visible: ["all"],
|
||||
path: "/socialnetwork/blocked"
|
||||
@@ -96,7 +101,9 @@ const menuStructure = {
|
||||
},
|
||||
eroticChat: {
|
||||
visible: ["over18"],
|
||||
action: "openEroticChat"
|
||||
action: "openEroticChat",
|
||||
view: "window",
|
||||
class: "eroticChatWindow"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -176,30 +183,6 @@ const menuStructure = {
|
||||
}
|
||||
}
|
||||
},
|
||||
personal: {
|
||||
visible: ["all"],
|
||||
icon: "profile16.png",
|
||||
children: {
|
||||
sprachenlernen: {
|
||||
visible: ["all"],
|
||||
children: {
|
||||
vocabtrainer: {
|
||||
visible: ["all"],
|
||||
path: "/socialnetwork/vocab",
|
||||
showVocabLanguages: 1 // Flag für dynamische Sprachen-Liste
|
||||
},
|
||||
sprachkurse: {
|
||||
visible: ["all"],
|
||||
path: "/socialnetwork/vocab/courses"
|
||||
}
|
||||
}
|
||||
},
|
||||
calendar: {
|
||||
visible: ["all"],
|
||||
path: "/personal/calendar"
|
||||
}
|
||||
}
|
||||
},
|
||||
settings: {
|
||||
visible: ["all"],
|
||||
icon: "settings16.png",
|
||||
@@ -212,10 +195,6 @@ const menuStructure = {
|
||||
visible: ["all"],
|
||||
path: "/settings/account"
|
||||
},
|
||||
languageAssistant: {
|
||||
visible: ["all"],
|
||||
path: "/settings/language-assistant"
|
||||
},
|
||||
personal: {
|
||||
visible: ["all"],
|
||||
path: "/settings/personal"
|
||||
@@ -256,14 +235,6 @@ const menuStructure = {
|
||||
visible: ["mainadmin", "useradministration"],
|
||||
path: "/admin/users"
|
||||
},
|
||||
adultverification: {
|
||||
visible: ["mainadmin", "useradministration"],
|
||||
path: "/admin/users/adult-verification"
|
||||
},
|
||||
eroticmoderation: {
|
||||
visible: ["mainadmin", "useradministration"],
|
||||
path: "/admin/users/erotic-moderation"
|
||||
},
|
||||
userstatistics: {
|
||||
visible: ["mainadmin"],
|
||||
path: "/admin/users/statistics"
|
||||
@@ -349,14 +320,7 @@ class NavigationController {
|
||||
return age;
|
||||
}
|
||||
|
||||
normalizeAdultVerificationStatus(value) {
|
||||
if (['pending', 'approved', 'rejected'].includes(value)) {
|
||||
return value;
|
||||
}
|
||||
return 'none';
|
||||
}
|
||||
|
||||
async filterMenu(menu, rights, age, userId, adultVerificationStatus = 'none') {
|
||||
async filterMenu(menu, rights, age, userId) {
|
||||
const filteredMenu = {};
|
||||
try {
|
||||
const hasFalukantAccount = await this.hasFalukantAccount(userId);
|
||||
@@ -370,17 +334,8 @@ class NavigationController {
|
||||
|| (value.visible.includes('hasfalukantaccount') && hasFalukantAccount)) {
|
||||
const { visible, ...itemWithoutVisible } = value;
|
||||
filteredMenu[key] = { ...itemWithoutVisible };
|
||||
if (
|
||||
value.visible.includes("over18")
|
||||
&& age >= 18
|
||||
&& adultVerificationStatus !== 'approved'
|
||||
&& (value.path || value.action || value.view)
|
||||
) {
|
||||
filteredMenu[key].disabled = true;
|
||||
filteredMenu[key].disabledReasonKey = 'socialnetwork.erotic.lockedShort';
|
||||
}
|
||||
if (value.children) {
|
||||
filteredMenu[key].children = await this.filterMenu(value.children, rights, age, userId, adultVerificationStatus);
|
||||
filteredMenu[key].children = await this.filterMenu(value.children, rights, age, userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -407,33 +362,37 @@ class NavigationController {
|
||||
required: false
|
||||
}]
|
||||
});
|
||||
const userParams = await UserParam.findAll({
|
||||
const userBirthdateParams = await UserParam.findAll({
|
||||
where: { userId: user.id },
|
||||
include: [
|
||||
{
|
||||
model: UserParamType,
|
||||
as: 'paramType',
|
||||
where: { description: ['birthdate', 'adult_verification_status'] }
|
||||
where: { description: 'birthdate' }
|
||||
}
|
||||
]
|
||||
});
|
||||
let birthDate = (new Date()).toDateString();
|
||||
let adultVerificationStatus = 'none';
|
||||
for (const param of userParams) {
|
||||
if (param.paramType?.description === 'birthdate' && param.value) {
|
||||
birthDate = param.value;
|
||||
}
|
||||
if (param.paramType?.description === 'adult_verification_status') {
|
||||
adultVerificationStatus = this.normalizeAdultVerificationStatus(param.value);
|
||||
}
|
||||
}
|
||||
const birthDate = userBirthdateParams.length > 0 ? userBirthdateParams[0].value : (new Date()).toDateString();
|
||||
const age = this.calculateAge(birthDate);
|
||||
const rights = userRights.map(ur => ur.rightType?.title).filter(Boolean);
|
||||
const filteredMenu = await this.filterMenu(menuStructure, rights, age, user.id, adultVerificationStatus);
|
||||
const filteredMenu = await this.filterMenu(menuStructure, rights, age, user.id);
|
||||
|
||||
// Vokabeltrainer: Sprachen werden im Frontend dynamisch geladen (wie Forum)
|
||||
// Keine children mehr, da das Menü nur 2 Ebenen unterstützt
|
||||
// Das Frontend lädt die Sprachen separat und zeigt sie als submenu2 an
|
||||
// Dynamisches Submenü: Treffpunkt → Vokabeltrainer → (Neue Sprache + abonnierte/angelegte)
|
||||
// Wichtig: "Neue Sprache" soll IMMER sichtbar sein – auch wenn die DB-Abfrage (noch) fehlschlägt.
|
||||
if (filteredMenu?.socialnetwork?.children?.vocabtrainer) {
|
||||
const children = {
|
||||
newLanguage: { path: '/socialnetwork/vocab/new' },
|
||||
};
|
||||
try {
|
||||
const langs = await this.vocabService.listLanguagesForMenu(user.id);
|
||||
for (const l of langs) {
|
||||
children[`lang_${l.id}`] = { path: `/socialnetwork/vocab/${l.id}`, label: l.name };
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[menu] Konnte Vokabeltrainer-Sprachen nicht laden:', e?.message || e);
|
||||
}
|
||||
filteredMenu.socialnetwork.children.vocabtrainer.children = children;
|
||||
}
|
||||
|
||||
res.status(200).json(filteredMenu);
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import newsService from '../services/newsService.js';
|
||||
|
||||
/**
|
||||
* GET /api/news?counter=0&language=de&category=top
|
||||
* counter = wievieltes News-Widget aufgerufen wird (0, 1, 2, …), damit keine doppelten Artikel.
|
||||
*/
|
||||
export default {
|
||||
async getNews(req, res) {
|
||||
const counter = Math.max(0, parseInt(req.query.counter, 10) || 0);
|
||||
const language = (req.query.language || 'de').slice(0, 10);
|
||||
const category = (req.query.category || 'top').slice(0, 50);
|
||||
|
||||
try {
|
||||
const { results, nextPage } = await newsService.getNews({ counter, language, category });
|
||||
res.json({ results, nextPage });
|
||||
} catch (error) {
|
||||
console.error('News getNews:', error);
|
||||
res.status(500).json({ error: error.message || 'News konnten nicht geladen werden.' });
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -185,57 +185,6 @@ class SettingsController {
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
}
|
||||
|
||||
async getLlmSettings(req, res) {
|
||||
try {
|
||||
const hashedUserId = req.headers.userid;
|
||||
const data = await settingsService.getLlmSettings(hashedUserId);
|
||||
res.status(200).json(data);
|
||||
} catch (error) {
|
||||
console.error('Error retrieving LLM settings:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
}
|
||||
|
||||
async saveLlmSettings(req, res) {
|
||||
const schema = Joi.object({
|
||||
baseUrl: Joi.string().allow('').optional(),
|
||||
model: Joi.string().allow('').optional(),
|
||||
enabled: Joi.boolean().optional(),
|
||||
apiKey: Joi.string().allow('').optional(),
|
||||
clearKey: Joi.boolean().optional()
|
||||
});
|
||||
const { error, value } = schema.validate(req.body || {});
|
||||
if (error) {
|
||||
return res.status(400).json({ error: error.details[0].message });
|
||||
}
|
||||
try {
|
||||
await settingsService.saveLlmSettings(req.headers.userid, value);
|
||||
res.status(200).json({ success: true });
|
||||
} catch (err) {
|
||||
console.error('Error saving LLM settings:', err);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
}
|
||||
|
||||
async submitAdultVerificationRequest(req, res) {
|
||||
try {
|
||||
const hashedUserId = req.headers.userid;
|
||||
const note = req.body?.note || '';
|
||||
const file = req.file || null;
|
||||
const result = await settingsService.submitAdultVerificationRequest(hashedUserId, { note }, file);
|
||||
res.status(200).json(result);
|
||||
} catch (error) {
|
||||
console.error('Error submitting adult verification request:', error);
|
||||
const status = [
|
||||
'User not found',
|
||||
'Adult verification can only be requested by adult users',
|
||||
'No verification document provided',
|
||||
'Unsupported verification document type'
|
||||
].includes(error.message) ? 400 : 500;
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default SettingsController;
|
||||
|
||||
@@ -15,19 +15,6 @@ class SocialNetworkController {
|
||||
this.changeImage = this.changeImage.bind(this);
|
||||
this.getFoldersByUsername = this.getFoldersByUsername.bind(this);
|
||||
this.deleteFolder = this.deleteFolder.bind(this);
|
||||
this.getAdultFolders = this.getAdultFolders.bind(this);
|
||||
this.getAdultFoldersByUsername = this.getAdultFoldersByUsername.bind(this);
|
||||
this.createAdultFolder = this.createAdultFolder.bind(this);
|
||||
this.getAdultFolderImageList = this.getAdultFolderImageList.bind(this);
|
||||
this.uploadAdultImage = this.uploadAdultImage.bind(this);
|
||||
this.getAdultImageByHash = this.getAdultImageByHash.bind(this);
|
||||
this.changeAdultImage = this.changeAdultImage.bind(this);
|
||||
this.listEroticVideos = this.listEroticVideos.bind(this);
|
||||
this.getEroticVideosByUsername = this.getEroticVideosByUsername.bind(this);
|
||||
this.uploadEroticVideo = this.uploadEroticVideo.bind(this);
|
||||
this.changeEroticVideo = this.changeEroticVideo.bind(this);
|
||||
this.getEroticVideoByHash = this.getEroticVideoByHash.bind(this);
|
||||
this.reportEroticContent = this.reportEroticContent.bind(this);
|
||||
this.createGuestbookEntry = this.createGuestbookEntry.bind(this);
|
||||
this.getGuestbookEntries = this.getGuestbookEntries.bind(this);
|
||||
this.deleteGuestbookEntry = this.deleteGuestbookEntry.bind(this);
|
||||
@@ -160,8 +147,8 @@ class SocialNetworkController {
|
||||
try {
|
||||
const userId = req.headers.userid;
|
||||
const { imageId } = req.params;
|
||||
const { title, visibilities, selectedUsers } = req.body;
|
||||
const folderId = await this.socialNetworkService.changeImage(userId, imageId, title, visibilities, selectedUsers);
|
||||
const { title, visibilities } = req.body;
|
||||
const folderId = await this.socialNetworkService.changeImage(userId, imageId, title, visibilities);
|
||||
console.log('--->', folderId);
|
||||
res.status(201).json(await this.socialNetworkService.getFolderImageList(userId, folderId));
|
||||
} catch (error) {
|
||||
@@ -200,177 +187,6 @@ class SocialNetworkController {
|
||||
}
|
||||
}
|
||||
|
||||
async getAdultFolders(req, res) {
|
||||
try {
|
||||
const userId = req.headers.userid;
|
||||
const folders = await this.socialNetworkService.getAdultFolders(userId);
|
||||
res.status(200).json(folders);
|
||||
} catch (error) {
|
||||
console.error('Error in getAdultFolders:', error);
|
||||
res.status(error.status || 500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async getAdultFoldersByUsername(req, res) {
|
||||
try {
|
||||
const requestingUserId = req.headers.userid;
|
||||
const { username } = req.params;
|
||||
const folders = await this.socialNetworkService.getAdultFoldersByUsername(username, requestingUserId);
|
||||
if (!folders) {
|
||||
return res.status(404).json({ error: 'No folders found or access denied.' });
|
||||
}
|
||||
res.status(200).json(folders);
|
||||
} catch (error) {
|
||||
console.error('Error in getAdultFoldersByUsername:', error);
|
||||
res.status(error.status || 500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async createAdultFolder(req, res) {
|
||||
try {
|
||||
const userId = req.headers.userid;
|
||||
const folderData = req.body;
|
||||
const { folderId } = req.params;
|
||||
const folder = await this.socialNetworkService.createAdultFolder(userId, folderData, folderId);
|
||||
res.status(201).json(folder);
|
||||
} catch (error) {
|
||||
console.error('Error in createAdultFolder:', error);
|
||||
res.status(error.status || 500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async getAdultFolderImageList(req, res) {
|
||||
try {
|
||||
const userId = req.headers.userid;
|
||||
const { folderId } = req.params;
|
||||
const images = await this.socialNetworkService.getAdultFolderImageList(userId, folderId);
|
||||
res.status(200).json(images);
|
||||
} catch (error) {
|
||||
console.error('Error in getAdultFolderImageList:', error);
|
||||
res.status(error.status || 500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async uploadAdultImage(req, res) {
|
||||
try {
|
||||
const userId = req.headers.userid;
|
||||
const file = req.file;
|
||||
const formData = req.body;
|
||||
const image = await this.socialNetworkService.uploadAdultImage(userId, file, formData);
|
||||
res.status(201).json(image);
|
||||
} catch (error) {
|
||||
console.error('Error in uploadAdultImage:', error);
|
||||
res.status(error.status || 500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async getAdultImageByHash(req, res) {
|
||||
try {
|
||||
const userId = req.headers.userid;
|
||||
const { hash } = req.params;
|
||||
const filePath = await this.socialNetworkService.getAdultImageFilePath(userId, hash);
|
||||
res.sendFile(filePath, err => {
|
||||
if (err) {
|
||||
console.error('Error sending adult file:', err);
|
||||
res.status(500).json({ error: 'Error sending file' });
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in getAdultImageByHash:', error);
|
||||
res.status(error.status || 403).json({ error: error.message || 'Access denied or image not found' });
|
||||
}
|
||||
}
|
||||
|
||||
async changeAdultImage(req, res) {
|
||||
try {
|
||||
const userId = req.headers.userid;
|
||||
const { imageId } = req.params;
|
||||
const { title, visibilities, selectedUsers } = req.body;
|
||||
const folderId = await this.socialNetworkService.changeAdultImage(userId, imageId, title, visibilities, selectedUsers);
|
||||
res.status(201).json(await this.socialNetworkService.getAdultFolderImageList(userId, folderId));
|
||||
} catch (error) {
|
||||
console.error('Error in changeAdultImage:', error);
|
||||
res.status(error.status || 403).json({ error: error.message || 'Access denied or image not found' });
|
||||
}
|
||||
}
|
||||
|
||||
async listEroticVideos(req, res) {
|
||||
try {
|
||||
const userId = req.headers.userid;
|
||||
const videos = await this.socialNetworkService.listEroticVideos(userId);
|
||||
res.status(200).json(videos);
|
||||
} catch (error) {
|
||||
console.error('Error in listEroticVideos:', error);
|
||||
res.status(error.status || 500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async getEroticVideosByUsername(req, res) {
|
||||
try {
|
||||
const userId = req.headers.userid;
|
||||
const { username } = req.params;
|
||||
const videos = await this.socialNetworkService.getEroticVideosByUsername(username, userId);
|
||||
res.status(200).json(videos);
|
||||
} catch (error) {
|
||||
console.error('Error in getEroticVideosByUsername:', error);
|
||||
res.status(error.status || 500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async uploadEroticVideo(req, res) {
|
||||
try {
|
||||
const userId = req.headers.userid;
|
||||
const file = req.file;
|
||||
const formData = req.body;
|
||||
const video = await this.socialNetworkService.uploadEroticVideo(userId, file, formData);
|
||||
res.status(201).json(video);
|
||||
} catch (error) {
|
||||
console.error('Error in uploadEroticVideo:', error);
|
||||
res.status(error.status || 500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async changeEroticVideo(req, res) {
|
||||
try {
|
||||
const userId = req.headers.userid;
|
||||
const { videoId } = req.params;
|
||||
const updatedVideo = await this.socialNetworkService.changeEroticVideo(userId, videoId, req.body);
|
||||
res.status(200).json(updatedVideo);
|
||||
} catch (error) {
|
||||
console.error('Error in changeEroticVideo:', error);
|
||||
res.status(error.status || 500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async getEroticVideoByHash(req, res) {
|
||||
try {
|
||||
const userId = req.headers.userid;
|
||||
const { hash } = req.params;
|
||||
const { filePath, mimeType } = await this.socialNetworkService.getEroticVideoFilePath(userId, hash);
|
||||
res.type(mimeType);
|
||||
res.sendFile(filePath, err => {
|
||||
if (err) {
|
||||
console.error('Error sending adult video:', err);
|
||||
res.status(500).json({ error: 'Error sending file' });
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in getEroticVideoByHash:', error);
|
||||
res.status(error.status || 403).json({ error: error.message || 'Access denied or video not found' });
|
||||
}
|
||||
}
|
||||
|
||||
async reportEroticContent(req, res) {
|
||||
try {
|
||||
const userId = req.headers.userid;
|
||||
const result = await this.socialNetworkService.createEroticContentReport(userId, req.body || {});
|
||||
res.status(201).json(result);
|
||||
} catch (error) {
|
||||
console.error('Error in reportEroticContent:', error);
|
||||
res.status(error.status || 400).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async createGuestbookEntry(req, res) {
|
||||
try {
|
||||
const { htmlContent, recipientName } = req.body;
|
||||
|
||||
@@ -9,7 +9,6 @@ class VocabController {
|
||||
this.service = new VocabService();
|
||||
|
||||
this.listLanguages = this._wrapWithUser((userId) => this.service.listLanguages(userId));
|
||||
this.listAllLanguages = this._wrapWithUser(() => this.service.listAllLanguages());
|
||||
this.createLanguage = this._wrapWithUser((userId, req) => this.service.createLanguage(userId, req.body), { successStatus: 201 });
|
||||
this.subscribe = this._wrapWithUser((userId, req) => this.service.subscribeByShareCode(userId, req.body), { successStatus: 201 });
|
||||
this.getLanguage = this._wrapWithUser((userId, req) => this.service.getLanguage(userId, req.params.languageId));
|
||||
@@ -22,40 +21,6 @@ class VocabController {
|
||||
this.getChapter = this._wrapWithUser((userId, req) => this.service.getChapter(userId, req.params.chapterId));
|
||||
this.listChapterVocabs = this._wrapWithUser((userId, req) => this.service.listChapterVocabs(userId, req.params.chapterId));
|
||||
this.addVocabToChapter = this._wrapWithUser((userId, req) => this.service.addVocabToChapter(userId, req.params.chapterId, req.body), { successStatus: 201 });
|
||||
|
||||
// Courses
|
||||
this.createCourse = this._wrapWithUser((userId, req) => this.service.createCourse(userId, req.body), { successStatus: 201 });
|
||||
this.getCourses = this._wrapWithUser((userId, req) => this.service.getCourses(userId, req.query));
|
||||
this.getCourse = this._wrapWithUser((userId, req) => this.service.getCourse(userId, req.params.courseId));
|
||||
this.getCourseByShareCode = this._wrapWithUser((userId, req) => this.service.getCourseByShareCode(userId, req.body.shareCode));
|
||||
this.updateCourse = this._wrapWithUser((userId, req) => this.service.updateCourse(userId, req.params.courseId, req.body));
|
||||
this.deleteCourse = this._wrapWithUser((userId, req) => this.service.deleteCourse(userId, req.params.courseId));
|
||||
|
||||
// Lessons
|
||||
this.getLesson = this._wrapWithUser((userId, req) => this.service.getLesson(userId, req.params.lessonId));
|
||||
this.addLessonToCourse = this._wrapWithUser((userId, req) => this.service.addLessonToCourse(userId, req.params.courseId, req.body), { successStatus: 201 });
|
||||
this.updateLesson = this._wrapWithUser((userId, req) => this.service.updateLesson(userId, req.params.lessonId, req.body));
|
||||
this.deleteLesson = this._wrapWithUser((userId, req) => this.service.deleteLesson(userId, req.params.lessonId));
|
||||
|
||||
// Enrollment
|
||||
this.enrollInCourse = this._wrapWithUser((userId, req) => this.service.enrollInCourse(userId, req.params.courseId), { successStatus: 201 });
|
||||
this.unenrollFromCourse = this._wrapWithUser((userId, req) => this.service.unenrollFromCourse(userId, req.params.courseId));
|
||||
this.getMyCourses = this._wrapWithUser((userId) => this.service.getMyCourses(userId));
|
||||
|
||||
// Progress
|
||||
this.getCourseProgress = this._wrapWithUser((userId, req) => this.service.getCourseProgress(userId, req.params.courseId));
|
||||
this.updateLessonProgress = this._wrapWithUser((userId, req) => this.service.updateLessonProgress(userId, req.params.lessonId, req.body));
|
||||
|
||||
// Grammar Exercises
|
||||
this.getExerciseTypes = this._wrapWithUser((userId) => this.service.getExerciseTypes());
|
||||
this.createGrammarExercise = this._wrapWithUser((userId, req) => this.service.createGrammarExercise(userId, req.params.lessonId, req.body), { successStatus: 201 });
|
||||
this.getGrammarExercisesForLesson = this._wrapWithUser((userId, req) => this.service.getGrammarExercisesForLesson(userId, req.params.lessonId));
|
||||
this.getGrammarExercise = this._wrapWithUser((userId, req) => this.service.getGrammarExercise(userId, req.params.exerciseId));
|
||||
this.checkGrammarExerciseAnswer = this._wrapWithUser((userId, req) => this.service.checkGrammarExerciseAnswer(userId, req.params.exerciseId, req.body.answer));
|
||||
this.getGrammarExerciseProgress = this._wrapWithUser((userId, req) => this.service.getGrammarExerciseProgress(userId, req.params.lessonId));
|
||||
this.updateGrammarExercise = this._wrapWithUser((userId, req) => this.service.updateGrammarExercise(userId, req.params.exerciseId, req.body));
|
||||
this.deleteGrammarExercise = this._wrapWithUser((userId, req) => this.service.deleteGrammarExercise(userId, req.params.exerciseId));
|
||||
this.sendLessonAssistantMessage = this._wrapWithUser((userId, req) => this.service.sendLessonAssistantMessage(userId, req.params.lessonId, req.body), { successStatus: 201 });
|
||||
}
|
||||
|
||||
_wrapWithUser(fn, { successStatus = 200 } = {}) {
|
||||
@@ -78,3 +43,4 @@ class VocabController {
|
||||
|
||||
export default VocabController;
|
||||
|
||||
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Script zum Erstellen von Performance-Indizes
|
||||
*
|
||||
* Erstellt Indizes basierend auf der Analyse häufiger Queries:
|
||||
* - inventory: stock_id
|
||||
* - stock: branch_id
|
||||
* - production: branch_id
|
||||
* - director: employer_user_id
|
||||
* - knowledge: (character_id, product_id) composite
|
||||
*/
|
||||
|
||||
import './config/loadEnv.js';
|
||||
import { sequelize } from './utils/sequelize.js';
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
console.log('🔧 Erstelle Performance-Indizes\n');
|
||||
console.log('='.repeat(60) + '\n');
|
||||
|
||||
const indexes = [
|
||||
{
|
||||
name: 'idx_knowledge_character_product',
|
||||
table: 'falukant_data.knowledge',
|
||||
columns: '(character_id, product_id)',
|
||||
description: 'Composite Index für JOINs mit character_id UND product_id',
|
||||
critical: true
|
||||
},
|
||||
{
|
||||
name: 'idx_inventory_stock_id',
|
||||
table: 'falukant_data.inventory',
|
||||
columns: '(stock_id)',
|
||||
description: 'Index für WHERE inventory.stock_id = ...',
|
||||
critical: true
|
||||
},
|
||||
{
|
||||
name: 'idx_stock_branch_id',
|
||||
table: 'falukant_data.stock',
|
||||
columns: '(branch_id)',
|
||||
description: 'Index für WHERE stock.branch_id = ...',
|
||||
critical: true
|
||||
},
|
||||
{
|
||||
name: 'idx_production_branch_id',
|
||||
table: 'falukant_data.production',
|
||||
columns: '(branch_id)',
|
||||
description: 'Index für WHERE production.branch_id = ...',
|
||||
critical: true
|
||||
},
|
||||
{
|
||||
name: 'idx_director_employer_user_id',
|
||||
table: 'falukant_data.director',
|
||||
columns: '(employer_user_id)',
|
||||
description: 'Index für WHERE director.employer_user_id = ...',
|
||||
critical: true
|
||||
},
|
||||
{
|
||||
name: 'idx_production_start_timestamp',
|
||||
table: 'falukant_data.production',
|
||||
columns: '(start_timestamp)',
|
||||
description: 'Index für WHERE production.start_timestamp <= ...',
|
||||
critical: false
|
||||
},
|
||||
{
|
||||
name: 'idx_director_last_salary_payout',
|
||||
table: 'falukant_data.director',
|
||||
columns: '(last_salary_payout)',
|
||||
description: 'Index für WHERE director.last_salary_payout < ...',
|
||||
critical: false
|
||||
}
|
||||
];
|
||||
|
||||
console.log(`📋 ${indexes.length} Indizes werden erstellt:\n`);
|
||||
|
||||
let created = 0;
|
||||
let skipped = 0;
|
||||
let errors = 0;
|
||||
|
||||
for (let i = 0; i < indexes.length; i++) {
|
||||
const idx = indexes[i];
|
||||
const criticalMark = idx.critical ? ' ⚠️ KRITISCH' : '';
|
||||
|
||||
console.log(`[${i + 1}/${indexes.length}] ${idx.name}${criticalMark}`);
|
||||
console.log(` Tabelle: ${idx.table}`);
|
||||
console.log(` Spalten: ${idx.columns}`);
|
||||
console.log(` Beschreibung: ${idx.description}`);
|
||||
|
||||
try {
|
||||
// Prüfe ob Index bereits existiert
|
||||
const [existing] = await sequelize.query(`
|
||||
SELECT EXISTS(
|
||||
SELECT 1 FROM pg_indexes
|
||||
WHERE schemaname || '.' || tablename = '${idx.table}'
|
||||
AND indexname = '${idx.name}'
|
||||
) as exists;
|
||||
`);
|
||||
|
||||
if (existing[0].exists) {
|
||||
console.log(` ⏭️ Index existiert bereits, überspringe\n`);
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Erstelle Index
|
||||
const startTime = Date.now();
|
||||
await sequelize.query(`
|
||||
CREATE INDEX IF NOT EXISTS ${idx.name}
|
||||
ON ${idx.table} USING btree ${idx.columns};
|
||||
`);
|
||||
|
||||
const duration = ((Date.now() - startTime) / 1000).toFixed(2);
|
||||
console.log(` ✅ Erstellt in ${duration}s\n`);
|
||||
created++;
|
||||
|
||||
} catch (error) {
|
||||
console.error(` ❌ Fehler: ${error.message}\n`);
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('='.repeat(60));
|
||||
console.log(`✅ Zusammenfassung:`);
|
||||
console.log(` Erstellt: ${created}`);
|
||||
console.log(` Übersprungen: ${skipped}`);
|
||||
console.log(` Fehler: ${errors}\n`);
|
||||
|
||||
// ANALYZE ausführen, damit PostgreSQL die neuen Indizes berücksichtigt
|
||||
const tablesToAnalyze = [
|
||||
'falukant_data.knowledge',
|
||||
'falukant_data.inventory',
|
||||
'falukant_data.stock',
|
||||
'falukant_data.production',
|
||||
'falukant_data.director'
|
||||
];
|
||||
if (created > 0) {
|
||||
console.log('📊 Führe ANALYZE auf betroffenen Tabellen aus...\n');
|
||||
for (const table of tablesToAnalyze) {
|
||||
try {
|
||||
await sequelize.query(`ANALYZE ${table};`);
|
||||
console.log(` ✅ ANALYZE ${table};`);
|
||||
} catch (err) {
|
||||
console.log(` ⚠️ ${table}: ${err.message}`);
|
||||
}
|
||||
}
|
||||
console.log('');
|
||||
}
|
||||
|
||||
await sequelize.close();
|
||||
process.exit(0);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Fehler:', error.message);
|
||||
console.error(error.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -25,13 +25,11 @@ function createServer() {
|
||||
ca: TLS_CA_PATH ? fs.readFileSync(TLS_CA_PATH) : undefined,
|
||||
});
|
||||
wss = new WebSocketServer({ server: httpsServer });
|
||||
// Direkte Verbindung: lausche auf allen Interfaces (0.0.0.0)
|
||||
httpsServer.listen(PORT, '0.0.0.0', () => {
|
||||
console.log(`[Daemon] WSS (TLS) Server gestartet auf Port ${PORT}`);
|
||||
});
|
||||
} else {
|
||||
// Direkte Verbindung: lausche auf allen Interfaces (0.0.0.0)
|
||||
wss = new WebSocketServer({ port: PORT, host: '0.0.0.0' });
|
||||
wss = new WebSocketServer({ port: PORT });
|
||||
console.log(`[Daemon] WS (ohne TLS) Server startet auf Port ${PORT} ...`);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,479 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Umfassendes Diagnose-Script für Datenbank-Performance
|
||||
*
|
||||
* Untersucht:
|
||||
* - Verbindungsstatistiken
|
||||
* - Langsame Queries
|
||||
* - Tabellengrößen und Bloat
|
||||
* - Indizes (fehlende/ungenutzte)
|
||||
* - Vacuum/Analyze Status
|
||||
* - Locking/Blocking
|
||||
* - Query-Statistiken
|
||||
*/
|
||||
|
||||
import './config/loadEnv.js';
|
||||
import { sequelize } from './utils/sequelize.js';
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
console.log('🔍 Datenbank-Performance-Diagnose\n');
|
||||
console.log('='.repeat(60) + '\n');
|
||||
|
||||
// 1. Verbindungsstatistiken
|
||||
await checkConnections();
|
||||
|
||||
// 2. Langsame Queries (wenn pg_stat_statements aktiviert ist)
|
||||
await checkSlowQueries();
|
||||
|
||||
// 3. Tabellengrößen und Bloat
|
||||
await checkTableSizes();
|
||||
|
||||
// 4. Indizes prüfen
|
||||
await checkIndexes();
|
||||
|
||||
// 5. Vacuum/Analyze Status
|
||||
await checkVacuumStatus();
|
||||
|
||||
// 6. Locking/Blocking
|
||||
await checkLocks();
|
||||
|
||||
// 7. Query-Statistiken (wenn pg_stat_statements aktiviert ist)
|
||||
await checkQueryStats();
|
||||
|
||||
// 8. Connection Pool Status
|
||||
await checkConnectionPool();
|
||||
|
||||
console.log('\n' + '='.repeat(60));
|
||||
console.log('✅ Diagnose abgeschlossen\n');
|
||||
|
||||
await sequelize.close();
|
||||
process.exit(0);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Fehler:', error.message);
|
||||
console.error(error.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async function checkConnections() {
|
||||
console.log('📊 1. Verbindungsstatistiken\n');
|
||||
|
||||
const [connections] = await sequelize.query(`
|
||||
SELECT
|
||||
count(*) as total,
|
||||
count(*) FILTER (WHERE state = 'active') as active,
|
||||
count(*) FILTER (WHERE state = 'idle') as idle,
|
||||
count(*) FILTER (WHERE state = 'idle in transaction') as idle_in_transaction,
|
||||
count(*) FILTER (WHERE wait_event_type IS NOT NULL) as waiting
|
||||
FROM pg_stat_activity
|
||||
WHERE datname = current_database();
|
||||
`);
|
||||
|
||||
const conn = connections[0];
|
||||
console.log(` Gesamt: ${conn.total}`);
|
||||
console.log(` Aktiv: ${conn.active}`);
|
||||
console.log(` Idle: ${conn.idle}`);
|
||||
console.log(` Idle in Transaction: ${conn.idle_in_transaction}`);
|
||||
console.log(` Wartend: ${conn.waiting}\n`);
|
||||
|
||||
const [maxConn] = await sequelize.query(`
|
||||
SELECT setting::int as max_connections
|
||||
FROM pg_settings
|
||||
WHERE name = 'max_connections';
|
||||
`);
|
||||
|
||||
const usagePercent = (conn.total / maxConn[0].max_connections) * 100;
|
||||
console.log(` Max Connections: ${maxConn[0].max_connections}`);
|
||||
console.log(` Auslastung: ${usagePercent.toFixed(1)}%\n`);
|
||||
|
||||
if (usagePercent > 80) {
|
||||
console.log(' ⚠️ WARNUNG: Hohe Verbindungsauslastung!\n');
|
||||
}
|
||||
|
||||
// Zeige lange laufende Queries
|
||||
const [longRunning] = await sequelize.query(`
|
||||
SELECT
|
||||
pid,
|
||||
usename,
|
||||
application_name,
|
||||
state,
|
||||
now() - query_start as duration,
|
||||
wait_event_type,
|
||||
wait_event,
|
||||
left(query, 100) as query_preview
|
||||
FROM pg_stat_activity
|
||||
WHERE datname = current_database()
|
||||
AND state != 'idle'
|
||||
AND now() - query_start > interval '5 seconds'
|
||||
ORDER BY query_start ASC
|
||||
LIMIT 10;
|
||||
`);
|
||||
|
||||
if (longRunning.length > 0) {
|
||||
console.log(' ⚠️ Lange laufende Queries (> 5 Sekunden):');
|
||||
longRunning.forEach(q => {
|
||||
const duration = Math.round(q.duration.total_seconds);
|
||||
console.log(` PID ${q.pid}: ${duration}s - ${q.query_preview}...`);
|
||||
});
|
||||
console.log('');
|
||||
}
|
||||
}
|
||||
|
||||
async function checkSlowQueries() {
|
||||
console.log('🐌 2. Langsame Queries (pg_stat_statements)\n');
|
||||
|
||||
try {
|
||||
// Prüfe ob pg_stat_statements aktiviert ist
|
||||
const [extension] = await sequelize.query(`
|
||||
SELECT EXISTS(
|
||||
SELECT 1 FROM pg_extension WHERE extname = 'pg_stat_statements'
|
||||
) as exists;
|
||||
`);
|
||||
|
||||
if (!extension[0].exists) {
|
||||
console.log(' ℹ️ pg_stat_statements ist nicht aktiviert.');
|
||||
console.log(' 💡 Aktivieren mit: CREATE EXTENSION IF NOT EXISTS pg_stat_statements;\n');
|
||||
return;
|
||||
}
|
||||
|
||||
const [slowQueries] = await sequelize.query(`
|
||||
SELECT
|
||||
left(query, 100) as query_preview,
|
||||
calls,
|
||||
total_exec_time,
|
||||
mean_exec_time,
|
||||
max_exec_time,
|
||||
(total_exec_time / sum(total_exec_time) OVER ()) * 100 as percent_total
|
||||
FROM pg_stat_statements
|
||||
WHERE mean_exec_time > 100 -- Queries mit > 100ms Durchschnitt
|
||||
ORDER BY total_exec_time DESC
|
||||
LIMIT 10;
|
||||
`);
|
||||
|
||||
if (slowQueries.length > 0) {
|
||||
console.log(' Top 10 langsamste Queries (nach Gesamtzeit):');
|
||||
slowQueries.forEach((q, i) => {
|
||||
console.log(` ${i + 1}. ${q.query_preview}...`);
|
||||
console.log(` Aufrufe: ${q.calls}, Durchschnitt: ${q.mean_exec_time.toFixed(2)}ms, Max: ${q.max_exec_time.toFixed(2)}ms`);
|
||||
console.log(` Gesamtzeit: ${q.total_exec_time.toFixed(2)}ms (${q.percent_total.toFixed(1)}%)\n`);
|
||||
});
|
||||
} else {
|
||||
console.log(' ✅ Keine sehr langsamen Queries gefunden (> 100ms Durchschnitt)\n');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(` ⚠️ Fehler beim Abrufen der Query-Statistiken: ${error.message}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
async function checkTableSizes() {
|
||||
console.log('📦 3. Tabellengrößen und Bloat\n');
|
||||
|
||||
const [tableSizes] = await sequelize.query(`
|
||||
SELECT
|
||||
schemaname || '.' || relname as full_table_name,
|
||||
pg_size_pretty(pg_total_relation_size(schemaname||'.'||relname)) as total_size,
|
||||
pg_size_pretty(pg_relation_size(schemaname||'.'||relname)) as table_size,
|
||||
pg_size_pretty(pg_total_relation_size(schemaname||'.'||relname) - pg_relation_size(schemaname||'.'||relname)) as indexes_size,
|
||||
n_live_tup as row_count,
|
||||
n_dead_tup as dead_rows,
|
||||
CASE
|
||||
WHEN n_live_tup > 0 THEN round((n_dead_tup::numeric / n_live_tup::numeric) * 100, 2)
|
||||
ELSE 0
|
||||
END as dead_row_percent,
|
||||
last_vacuum,
|
||||
last_autovacuum,
|
||||
last_analyze,
|
||||
last_autoanalyze
|
||||
FROM pg_stat_user_tables
|
||||
WHERE schemaname IN ('falukant_data', 'falukant_type', 'community', 'logs')
|
||||
ORDER BY pg_total_relation_size(schemaname||'.'||relname) DESC
|
||||
LIMIT 20;
|
||||
`);
|
||||
|
||||
if (tableSizes.length > 0) {
|
||||
console.log(' Top 20 größte Tabellen:');
|
||||
tableSizes.forEach((t, i) => {
|
||||
console.log(` ${i + 1}. ${t.full_table_name}`);
|
||||
console.log(` Größe: ${t.total_size} (Tabelle: ${t.table_size}, Indizes: ${t.indexes_size})`);
|
||||
console.log(` Zeilen: ${parseInt(t.row_count).toLocaleString()}, Tote Zeilen: ${parseInt(t.dead_rows).toLocaleString()} (${t.dead_row_percent}%)`);
|
||||
|
||||
if (parseFloat(t.dead_row_percent) > 20) {
|
||||
console.log(` ⚠️ Hoher Bloat-Anteil! Vacuum empfohlen.`);
|
||||
}
|
||||
|
||||
if (t.last_vacuum || t.last_autovacuum) {
|
||||
const lastVacuum = t.last_vacuum || t.last_autovacuum;
|
||||
const daysSinceVacuum = Math.floor((new Date() - new Date(lastVacuum)) / (1000 * 60 * 60 * 24));
|
||||
if (daysSinceVacuum > 7) {
|
||||
console.log(` ⚠️ Letztes Vacuum: ${daysSinceVacuum} Tage her`);
|
||||
}
|
||||
}
|
||||
console.log('');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function checkIndexes() {
|
||||
console.log('🔍 4. Indizes-Analyse\n');
|
||||
|
||||
// Fehlende Indizes (basierend auf pg_stat_user_tables)
|
||||
const [missingIndexes] = await sequelize.query(`
|
||||
SELECT
|
||||
schemaname || '.' || relname as table_name,
|
||||
seq_scan,
|
||||
seq_tup_read,
|
||||
idx_scan,
|
||||
seq_tup_read / NULLIF(seq_scan, 0) as avg_seq_read
|
||||
FROM pg_stat_user_tables
|
||||
WHERE schemaname IN ('falukant_data', 'falukant_type', 'community', 'logs')
|
||||
AND seq_scan > 1000
|
||||
AND seq_tup_read / NULLIF(seq_scan, 0) > 1000
|
||||
ORDER BY seq_tup_read DESC
|
||||
LIMIT 10;
|
||||
`);
|
||||
|
||||
if (missingIndexes.length > 0) {
|
||||
console.log(' ⚠️ Tabellen mit vielen Sequential Scans (möglicherweise fehlende Indizes):');
|
||||
missingIndexes.forEach(t => {
|
||||
console.log(` ${t.table_name}: ${t.seq_scan} seq scans, ${parseInt(t.seq_tup_read).toLocaleString()} Zeilen gelesen`);
|
||||
});
|
||||
console.log('');
|
||||
}
|
||||
|
||||
// Ungenutzte Indizes
|
||||
const [unusedIndexes] = await sequelize.query(`
|
||||
SELECT
|
||||
schemaname || '.' || indexrelname as index_name,
|
||||
schemaname || '.' || relname as table_name,
|
||||
pg_size_pretty(pg_relation_size(indexrelid)) as index_size,
|
||||
idx_scan as scans
|
||||
FROM pg_stat_user_indexes
|
||||
WHERE schemaname IN ('falukant_data', 'falukant_type', 'community', 'logs')
|
||||
AND idx_scan = 0
|
||||
AND pg_relation_size(indexrelid) > 1024 * 1024 -- Größer als 1MB
|
||||
ORDER BY pg_relation_size(indexrelid) DESC
|
||||
LIMIT 10;
|
||||
`);
|
||||
|
||||
if (unusedIndexes.length > 0) {
|
||||
console.log(' ⚠️ Ungenutzte Indizes (> 1MB, nie verwendet):');
|
||||
unusedIndexes.forEach(idx => {
|
||||
console.log(` ${idx.index_name} auf ${idx.table_name}: ${idx.index_size} (0 Scans)`);
|
||||
});
|
||||
console.log('');
|
||||
}
|
||||
|
||||
// Index Bloat
|
||||
const [indexBloat] = await sequelize.query(`
|
||||
SELECT
|
||||
schemaname || '.' || indexrelname as index_name,
|
||||
schemaname || '.' || relname as table_name,
|
||||
pg_size_pretty(pg_relation_size(indexrelid)) as index_size,
|
||||
idx_scan as scans,
|
||||
idx_tup_read as tuples_read,
|
||||
idx_tup_fetch as tuples_fetched
|
||||
FROM pg_stat_user_indexes
|
||||
WHERE schemaname IN ('falukant_data', 'falukant_type', 'community', 'logs')
|
||||
AND pg_relation_size(indexrelid) > 10 * 1024 * 1024 -- Größer als 10MB
|
||||
ORDER BY pg_relation_size(indexrelid) DESC
|
||||
LIMIT 10;
|
||||
`);
|
||||
|
||||
if (indexBloat.length > 0) {
|
||||
console.log(' Top 10 größte Indizes:');
|
||||
indexBloat.forEach(idx => {
|
||||
console.log(` ${idx.index_name} auf ${idx.table_name}: ${idx.index_size} (${idx.scans} Scans)`);
|
||||
});
|
||||
console.log('');
|
||||
}
|
||||
}
|
||||
|
||||
async function checkVacuumStatus() {
|
||||
console.log('🧹 5. Vacuum/Analyze Status\n');
|
||||
|
||||
const [vacuumStats] = await sequelize.query(`
|
||||
SELECT
|
||||
schemaname || '.' || relname as table_name,
|
||||
last_vacuum,
|
||||
last_autovacuum,
|
||||
last_analyze,
|
||||
last_autoanalyze,
|
||||
n_dead_tup,
|
||||
n_live_tup,
|
||||
CASE
|
||||
WHEN n_live_tup > 0 THEN round((n_dead_tup::numeric / n_live_tup::numeric) * 100, 2)
|
||||
ELSE 0
|
||||
END as dead_percent
|
||||
FROM pg_stat_user_tables
|
||||
WHERE schemaname IN ('falukant_data', 'falukant_type', 'community', 'logs')
|
||||
AND (
|
||||
(last_vacuum IS NULL AND last_autovacuum IS NULL)
|
||||
OR (last_vacuum < now() - interval '7 days' AND last_autovacuum < now() - interval '7 days')
|
||||
OR n_dead_tup > 10000
|
||||
)
|
||||
ORDER BY n_dead_tup DESC
|
||||
LIMIT 10;
|
||||
`);
|
||||
|
||||
if (vacuumStats.length > 0) {
|
||||
console.log(' ⚠️ Tabellen, die Vacuum benötigen könnten:');
|
||||
vacuumStats.forEach(t => {
|
||||
const lastVacuum = t.last_vacuum || t.last_autovacuum || 'Nie';
|
||||
const daysSince = lastVacuum !== 'Nie'
|
||||
? Math.floor((new Date() - new Date(lastVacuum)) / (1000 * 60 * 60 * 24))
|
||||
: '∞';
|
||||
console.log(` ${t.table_name}:`);
|
||||
console.log(` Tote Zeilen: ${parseInt(t.n_dead_tup).toLocaleString()} (${t.dead_percent}%)`);
|
||||
console.log(` Letztes Vacuum: ${lastVacuum} (${daysSince} Tage)`);
|
||||
});
|
||||
console.log('');
|
||||
} else {
|
||||
console.log(' ✅ Alle Tabellen sind aktuell gevacuumt\n');
|
||||
}
|
||||
}
|
||||
|
||||
async function checkLocks() {
|
||||
console.log('🔒 6. Locking/Blocking\n');
|
||||
|
||||
const [locks] = await sequelize.query(`
|
||||
SELECT
|
||||
blocked_locks.pid AS blocked_pid,
|
||||
blocked_activity.usename AS blocked_user,
|
||||
blocking_locks.pid AS blocking_pid,
|
||||
blocking_activity.usename AS blocking_user,
|
||||
blocked_activity.query AS blocked_statement,
|
||||
blocking_activity.query AS blocking_statement,
|
||||
blocked_activity.application_name AS blocked_app,
|
||||
blocking_activity.application_name AS blocking_app
|
||||
FROM pg_catalog.pg_locks blocked_locks
|
||||
JOIN pg_catalog.pg_stat_activity blocked_activity ON blocked_activity.pid = blocked_locks.pid
|
||||
JOIN pg_catalog.pg_locks blocking_locks
|
||||
ON blocking_locks.locktype = blocked_locks.locktype
|
||||
AND blocking_locks.database IS NOT DISTINCT FROM blocked_locks.database
|
||||
AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation
|
||||
AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page
|
||||
AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple
|
||||
AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid
|
||||
AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid
|
||||
AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid
|
||||
AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid
|
||||
AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid
|
||||
AND blocking_locks.pid != blocked_locks.pid
|
||||
JOIN pg_catalog.pg_stat_activity blocking_activity ON blocking_activity.pid = blocking_locks.pid
|
||||
WHERE NOT blocked_locks.granted;
|
||||
`);
|
||||
|
||||
if (locks.length > 0) {
|
||||
console.log(' ⚠️ Blockierte Queries gefunden:');
|
||||
locks.forEach(lock => {
|
||||
console.log(` Blockiert: PID ${lock.blocked_pid} (${lock.blocked_user})`);
|
||||
console.log(` Blockiert von: PID ${lock.blocking_pid} (${lock.blocking_user})`);
|
||||
console.log(` Blockierte Query: ${lock.blocked_statement.substring(0, 100)}...`);
|
||||
console.log(` Blockierende Query: ${lock.blocking_statement.substring(0, 100)}...\n`);
|
||||
});
|
||||
} else {
|
||||
console.log(' ✅ Keine blockierten Queries gefunden\n');
|
||||
}
|
||||
|
||||
// Zeige alle aktiven Locks
|
||||
const [allLocks] = await sequelize.query(`
|
||||
SELECT
|
||||
locktype,
|
||||
relation::regclass as relation,
|
||||
mode,
|
||||
granted,
|
||||
pid
|
||||
FROM pg_locks
|
||||
WHERE relation IS NOT NULL
|
||||
AND NOT granted
|
||||
LIMIT 10;
|
||||
`);
|
||||
|
||||
if (allLocks.length > 0) {
|
||||
console.log(' ⚠️ Wartende Locks:');
|
||||
allLocks.forEach(lock => {
|
||||
console.log(` ${lock.locktype} auf ${lock.relation}: ${lock.mode} (PID ${lock.pid})`);
|
||||
});
|
||||
console.log('');
|
||||
}
|
||||
}
|
||||
|
||||
async function checkQueryStats() {
|
||||
console.log('📈 7. Query-Statistiken\n');
|
||||
|
||||
try {
|
||||
const [extension] = await sequelize.query(`
|
||||
SELECT EXISTS(
|
||||
SELECT 1 FROM pg_extension WHERE extname = 'pg_stat_statements'
|
||||
) as exists;
|
||||
`);
|
||||
|
||||
if (!extension[0].exists) {
|
||||
console.log(' ℹ️ pg_stat_statements ist nicht aktiviert.\n');
|
||||
return;
|
||||
}
|
||||
|
||||
const [topQueries] = await sequelize.query(`
|
||||
SELECT
|
||||
left(query, 80) as query_preview,
|
||||
calls,
|
||||
total_exec_time,
|
||||
mean_exec_time,
|
||||
(100 * total_exec_time / sum(total_exec_time) OVER ()) as percent_total
|
||||
FROM pg_stat_statements
|
||||
WHERE query NOT LIKE '%pg_stat_statements%'
|
||||
ORDER BY calls DESC
|
||||
LIMIT 5;
|
||||
`);
|
||||
|
||||
if (topQueries.length > 0) {
|
||||
console.log(' Top 5 häufigste Queries:');
|
||||
topQueries.forEach((q, i) => {
|
||||
console.log(` ${i + 1}. ${q.query_preview}...`);
|
||||
console.log(` Aufrufe: ${parseInt(q.calls).toLocaleString()}, Durchschnitt: ${q.mean_exec_time.toFixed(2)}ms`);
|
||||
});
|
||||
console.log('');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(` ⚠️ Fehler: ${error.message}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
async function checkConnectionPool() {
|
||||
console.log('🏊 8. Connection Pool Status\n');
|
||||
|
||||
try {
|
||||
// Hole Pool-Konfiguration aus Sequelize Config
|
||||
const config = sequelize.config;
|
||||
const poolConfig = config.pool || {};
|
||||
|
||||
console.log(` Pool-Konfiguration:`);
|
||||
console.log(` Max: ${poolConfig.max || 'N/A'}`);
|
||||
console.log(` Min: ${poolConfig.min || 'N/A'}`);
|
||||
console.log(` Acquire Timeout: ${poolConfig.acquire || 'N/A'}ms`);
|
||||
console.log(` Idle Timeout: ${poolConfig.idle || 'N/A'}ms`);
|
||||
console.log(` Evict Interval: ${poolConfig.evict || 'N/A'}ms\n`);
|
||||
|
||||
// Versuche Pool-Status zu bekommen
|
||||
const pool = sequelize.connectionManager.pool;
|
||||
if (pool) {
|
||||
const poolSize = pool.size || 0;
|
||||
const poolUsed = pool.used || 0;
|
||||
const poolPending = pool.pending || 0;
|
||||
|
||||
console.log(` Pool-Status:`);
|
||||
console.log(` Größe: ${poolSize}`);
|
||||
console.log(` Verwendet: ${poolUsed}`);
|
||||
console.log(` Wartend: ${poolPending}\n`);
|
||||
} else {
|
||||
console.log(` ℹ️ Pool-Objekt nicht verfügbar\n`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(` ⚠️ Fehler beim Abrufen der Pool-Informationen: ${error.message}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,21 +0,0 @@
|
||||
# Kopie nach `backend/.env` — nicht committen.
|
||||
#
|
||||
# Produktion / direkter DB-Host steht typischerweise in `.env`.
|
||||
# Für Entwicklung mit SSH-Tunnel: Datei `backend/.env.local` anlegen (wird nach `.env`
|
||||
# geladen und überschreibt). So bleibt `.env` mit echtem Host, Tunnel nur lokal.
|
||||
#
|
||||
# Beispiel backend/.env.local:
|
||||
# DB_HOST=127.0.0.1
|
||||
# DB_PORT=60000
|
||||
# # DB_SSL=0
|
||||
# (Tunnel z. B.: ssh -L 60000:127.0.0.1:5432 user@server)
|
||||
#
|
||||
DB_NAME=
|
||||
DB_USER=
|
||||
DB_PASS=
|
||||
# DB_HOST=
|
||||
# DB_PORT=5432
|
||||
# DB_SSL=0
|
||||
#
|
||||
# Optional (Defaults siehe utils/sequelize.js)
|
||||
# DB_CONNECT_TIMEOUT_MS=30000
|
||||
@@ -1,5 +0,0 @@
|
||||
# Kopie nach backend/.env.local (liegt neben .env, wird nicht committet).
|
||||
# Überschreibt nur bei dir lokal z. B. SSH-Tunnel — .env kann weiter DB_HOST=tsschulz.de haben.
|
||||
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=60000
|
||||
@@ -1,13 +0,0 @@
|
||||
-- Rollback: Remove indexes for director proposals and character queries
|
||||
-- Created: 2026-01-12
|
||||
|
||||
DROP INDEX IF EXISTS falukant_data.idx_character_region_user_created;
|
||||
DROP INDEX IF EXISTS falukant_data.idx_character_region_user;
|
||||
DROP INDEX IF EXISTS falukant_data.idx_character_user_id;
|
||||
DROP INDEX IF EXISTS falukant_data.idx_director_proposal_employer_character;
|
||||
DROP INDEX IF EXISTS falukant_data.idx_director_character_id;
|
||||
DROP INDEX IF EXISTS falukant_data.idx_director_employer_user_id;
|
||||
DROP INDEX IF EXISTS falukant_data.idx_knowledge_character_id;
|
||||
DROP INDEX IF EXISTS falukant_data.idx_relationship_character1_id;
|
||||
DROP INDEX IF EXISTS falukant_data.idx_child_relation_father_id;
|
||||
DROP INDEX IF EXISTS falukant_data.idx_child_relation_mother_id;
|
||||
@@ -1,43 +0,0 @@
|
||||
-- Migration: Add indexes for director proposals and character queries
|
||||
-- Created: 2026-01-12
|
||||
|
||||
-- Index für schnelle Suche nach NPCs in einer Region (mit Altersbeschränkung)
|
||||
CREATE INDEX IF NOT EXISTS idx_character_region_user_created
|
||||
ON falukant_data.character (region_id, user_id, created_at)
|
||||
WHERE user_id IS NULL;
|
||||
|
||||
-- Index für schnelle Suche nach NPCs ohne Altersbeschränkung
|
||||
CREATE INDEX IF NOT EXISTS idx_character_region_user
|
||||
ON falukant_data.character (region_id, user_id)
|
||||
WHERE user_id IS NULL;
|
||||
|
||||
-- Index für Character-Suche nach user_id (wichtig für getFamily, getDirectorForBranch)
|
||||
CREATE INDEX IF NOT EXISTS idx_character_user_id
|
||||
ON falukant_data.character (user_id);
|
||||
|
||||
-- Index für Director-Proposals
|
||||
CREATE INDEX IF NOT EXISTS idx_director_proposal_employer_character
|
||||
ON falukant_data.director_proposal (employer_user_id, director_character_id);
|
||||
|
||||
-- Index für aktive Direktoren
|
||||
CREATE INDEX IF NOT EXISTS idx_director_character_id
|
||||
ON falukant_data.director (director_character_id);
|
||||
|
||||
-- Index für Director-Suche nach employer_user_id
|
||||
CREATE INDEX IF NOT EXISTS idx_director_employer_user_id
|
||||
ON falukant_data.director (employer_user_id);
|
||||
|
||||
-- Index für Knowledge-Berechnung
|
||||
CREATE INDEX IF NOT EXISTS idx_knowledge_character_id
|
||||
ON falukant_data.knowledge (character_id);
|
||||
|
||||
-- Index für Relationships (getFamily)
|
||||
CREATE INDEX IF NOT EXISTS idx_relationship_character1_id
|
||||
ON falukant_data.relationship (character1_id);
|
||||
|
||||
-- Index für ChildRelations (getFamily)
|
||||
CREATE INDEX IF NOT EXISTS idx_child_relation_father_id
|
||||
ON falukant_data.child_relation (father_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_child_relation_mother_id
|
||||
ON falukant_data.child_relation (mother_id);
|
||||
@@ -1,132 +0,0 @@
|
||||
/* eslint-disable */
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface) {
|
||||
// Kurs-Tabelle
|
||||
await queryInterface.sequelize.query(`
|
||||
CREATE TABLE IF NOT EXISTS community.vocab_course (
|
||||
id SERIAL PRIMARY KEY,
|
||||
owner_user_id INTEGER NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
language_id INTEGER NOT NULL,
|
||||
difficulty_level INTEGER DEFAULT 1,
|
||||
is_public BOOLEAN DEFAULT false,
|
||||
share_code TEXT,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT vocab_course_owner_fk
|
||||
FOREIGN KEY (owner_user_id)
|
||||
REFERENCES community."user"(id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT vocab_course_language_fk
|
||||
FOREIGN KEY (language_id)
|
||||
REFERENCES community.vocab_language(id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT vocab_course_share_code_uniq UNIQUE (share_code)
|
||||
);
|
||||
`);
|
||||
|
||||
// Lektionen innerhalb eines Kurses
|
||||
await queryInterface.sequelize.query(`
|
||||
CREATE TABLE IF NOT EXISTS community.vocab_course_lesson (
|
||||
id SERIAL PRIMARY KEY,
|
||||
course_id INTEGER NOT NULL,
|
||||
chapter_id INTEGER NOT NULL,
|
||||
lesson_number INTEGER NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT vocab_course_lesson_course_fk
|
||||
FOREIGN KEY (course_id)
|
||||
REFERENCES community.vocab_course(id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT vocab_course_lesson_chapter_fk
|
||||
FOREIGN KEY (chapter_id)
|
||||
REFERENCES community.vocab_chapter(id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT vocab_course_lesson_unique UNIQUE (course_id, lesson_number)
|
||||
);
|
||||
`);
|
||||
|
||||
// Einschreibungen in Kurse
|
||||
await queryInterface.sequelize.query(`
|
||||
CREATE TABLE IF NOT EXISTS community.vocab_course_enrollment (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL,
|
||||
course_id INTEGER NOT NULL,
|
||||
enrolled_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT vocab_course_enrollment_user_fk
|
||||
FOREIGN KEY (user_id)
|
||||
REFERENCES community."user"(id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT vocab_course_enrollment_course_fk
|
||||
FOREIGN KEY (course_id)
|
||||
REFERENCES community.vocab_course(id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT vocab_course_enrollment_unique UNIQUE (user_id, course_id)
|
||||
);
|
||||
`);
|
||||
|
||||
// Fortschritt pro User und Lektion
|
||||
await queryInterface.sequelize.query(`
|
||||
CREATE TABLE IF NOT EXISTS community.vocab_course_progress (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL,
|
||||
course_id INTEGER NOT NULL,
|
||||
lesson_id INTEGER NOT NULL,
|
||||
completed BOOLEAN DEFAULT false,
|
||||
score INTEGER DEFAULT 0,
|
||||
last_accessed_at TIMESTAMP WITHOUT TIME ZONE,
|
||||
completed_at TIMESTAMP WITHOUT TIME ZONE,
|
||||
CONSTRAINT vocab_course_progress_user_fk
|
||||
FOREIGN KEY (user_id)
|
||||
REFERENCES community."user"(id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT vocab_course_progress_course_fk
|
||||
FOREIGN KEY (course_id)
|
||||
REFERENCES community.vocab_course(id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT vocab_course_progress_lesson_fk
|
||||
FOREIGN KEY (lesson_id)
|
||||
REFERENCES community.vocab_course_lesson(id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT vocab_course_progress_unique UNIQUE (user_id, lesson_id)
|
||||
);
|
||||
`);
|
||||
|
||||
// Indizes
|
||||
await queryInterface.sequelize.query(`
|
||||
CREATE INDEX IF NOT EXISTS vocab_course_owner_idx
|
||||
ON community.vocab_course(owner_user_id);
|
||||
CREATE INDEX IF NOT EXISTS vocab_course_language_idx
|
||||
ON community.vocab_course(language_id);
|
||||
CREATE INDEX IF NOT EXISTS vocab_course_public_idx
|
||||
ON community.vocab_course(is_public);
|
||||
CREATE INDEX IF NOT EXISTS vocab_course_lesson_course_idx
|
||||
ON community.vocab_course_lesson(course_id);
|
||||
CREATE INDEX IF NOT EXISTS vocab_course_lesson_chapter_idx
|
||||
ON community.vocab_course_lesson(chapter_id);
|
||||
CREATE INDEX IF NOT EXISTS vocab_course_enrollment_user_idx
|
||||
ON community.vocab_course_enrollment(user_id);
|
||||
CREATE INDEX IF NOT EXISTS vocab_course_enrollment_course_idx
|
||||
ON community.vocab_course_enrollment(course_id);
|
||||
CREATE INDEX IF NOT EXISTS vocab_course_progress_user_idx
|
||||
ON community.vocab_course_progress(user_id);
|
||||
CREATE INDEX IF NOT EXISTS vocab_course_progress_course_idx
|
||||
ON community.vocab_course_progress(course_id);
|
||||
CREATE INDEX IF NOT EXISTS vocab_course_progress_lesson_idx
|
||||
ON community.vocab_course_progress(lesson_id);
|
||||
`);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.sequelize.query(`
|
||||
DROP TABLE IF EXISTS community.vocab_course_progress CASCADE;
|
||||
DROP TABLE IF EXISTS community.vocab_course_enrollment CASCADE;
|
||||
DROP TABLE IF EXISTS community.vocab_course_lesson CASCADE;
|
||||
DROP TABLE IF EXISTS community.vocab_course CASCADE;
|
||||
`);
|
||||
}
|
||||
};
|
||||
@@ -1,101 +0,0 @@
|
||||
/* eslint-disable */
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface) {
|
||||
// Grammatik-Übungstypen (z.B. "gap_fill", "multiple_choice", "sentence_building", "transformation")
|
||||
await queryInterface.sequelize.query(`
|
||||
CREATE TABLE IF NOT EXISTS community.vocab_grammar_exercise_type (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW()
|
||||
);
|
||||
`);
|
||||
|
||||
// Grammatik-Übungen (verknüpft mit Lektionen)
|
||||
await queryInterface.sequelize.query(`
|
||||
CREATE TABLE IF NOT EXISTS community.vocab_grammar_exercise (
|
||||
id SERIAL PRIMARY KEY,
|
||||
lesson_id INTEGER NOT NULL,
|
||||
exercise_type_id INTEGER NOT NULL,
|
||||
exercise_number INTEGER NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
instruction TEXT,
|
||||
question_data JSONB NOT NULL,
|
||||
answer_data JSONB NOT NULL,
|
||||
explanation TEXT,
|
||||
created_by_user_id INTEGER NOT NULL,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT vocab_grammar_exercise_lesson_fk
|
||||
FOREIGN KEY (lesson_id)
|
||||
REFERENCES community.vocab_course_lesson(id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT vocab_grammar_exercise_type_fk
|
||||
FOREIGN KEY (exercise_type_id)
|
||||
REFERENCES community.vocab_grammar_exercise_type(id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT vocab_grammar_exercise_creator_fk
|
||||
FOREIGN KEY (created_by_user_id)
|
||||
REFERENCES community."user"(id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT vocab_grammar_exercise_unique UNIQUE (lesson_id, exercise_number)
|
||||
);
|
||||
`);
|
||||
|
||||
// Fortschritt für Grammatik-Übungen
|
||||
await queryInterface.sequelize.query(`
|
||||
CREATE TABLE IF NOT EXISTS community.vocab_grammar_exercise_progress (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL,
|
||||
exercise_id INTEGER NOT NULL,
|
||||
attempts INTEGER DEFAULT 0,
|
||||
correct_attempts INTEGER DEFAULT 0,
|
||||
last_attempt_at TIMESTAMP WITHOUT TIME ZONE,
|
||||
completed BOOLEAN DEFAULT false,
|
||||
completed_at TIMESTAMP WITHOUT TIME ZONE,
|
||||
CONSTRAINT vocab_grammar_exercise_progress_user_fk
|
||||
FOREIGN KEY (user_id)
|
||||
REFERENCES community."user"(id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT vocab_grammar_exercise_progress_exercise_fk
|
||||
FOREIGN KEY (exercise_id)
|
||||
REFERENCES community.vocab_grammar_exercise(id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT vocab_grammar_exercise_progress_unique UNIQUE (user_id, exercise_id)
|
||||
);
|
||||
`);
|
||||
|
||||
// Indizes
|
||||
await queryInterface.sequelize.query(`
|
||||
CREATE INDEX IF NOT EXISTS vocab_grammar_exercise_lesson_idx
|
||||
ON community.vocab_grammar_exercise(lesson_id);
|
||||
CREATE INDEX IF NOT EXISTS vocab_grammar_exercise_type_idx
|
||||
ON community.vocab_grammar_exercise(exercise_type_id);
|
||||
CREATE INDEX IF NOT EXISTS vocab_grammar_exercise_progress_user_idx
|
||||
ON community.vocab_grammar_exercise_progress(user_id);
|
||||
CREATE INDEX IF NOT EXISTS vocab_grammar_exercise_progress_exercise_idx
|
||||
ON community.vocab_grammar_exercise_progress(exercise_id);
|
||||
`);
|
||||
|
||||
// Standard-Übungstypen einfügen
|
||||
await queryInterface.sequelize.query(`
|
||||
INSERT INTO community.vocab_grammar_exercise_type (name, description) VALUES
|
||||
('gap_fill', 'Lückentext-Übung'),
|
||||
('multiple_choice', 'Multiple-Choice-Fragen'),
|
||||
('sentence_building', 'Satzbau-Übung'),
|
||||
('transformation', 'Satzumformung'),
|
||||
('conjugation', 'Konjugations-Übung'),
|
||||
('declension', 'Deklinations-Übung')
|
||||
ON CONFLICT (name) DO NOTHING;
|
||||
`);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.sequelize.query(`
|
||||
DROP TABLE IF EXISTS community.vocab_grammar_exercise_progress CASCADE;
|
||||
DROP TABLE IF EXISTS community.vocab_grammar_exercise CASCADE;
|
||||
DROP TABLE IF EXISTS community.vocab_grammar_exercise_type CASCADE;
|
||||
`);
|
||||
}
|
||||
};
|
||||
@@ -1,47 +0,0 @@
|
||||
/* eslint-disable */
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface) {
|
||||
// chapter_id optional machen (nicht alle Lektionen brauchen ein Kapitel)
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TABLE community.vocab_course_lesson
|
||||
ALTER COLUMN chapter_id DROP NOT NULL;
|
||||
`);
|
||||
|
||||
// Kurs-Wochen/Module hinzufügen
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TABLE community.vocab_course_lesson
|
||||
ADD COLUMN IF NOT EXISTS week_number INTEGER,
|
||||
ADD COLUMN IF NOT EXISTS day_number INTEGER,
|
||||
ADD COLUMN IF NOT EXISTS lesson_type TEXT DEFAULT 'vocab',
|
||||
ADD COLUMN IF NOT EXISTS audio_url TEXT,
|
||||
ADD COLUMN IF NOT EXISTS cultural_notes TEXT;
|
||||
`);
|
||||
|
||||
// Indizes für Wochen/Tage
|
||||
await queryInterface.sequelize.query(`
|
||||
CREATE INDEX IF NOT EXISTS vocab_course_lesson_week_idx
|
||||
ON community.vocab_course_lesson(course_id, week_number);
|
||||
CREATE INDEX IF NOT EXISTS vocab_course_lesson_type_idx
|
||||
ON community.vocab_course_lesson(lesson_type);
|
||||
`);
|
||||
|
||||
// Kommentar hinzufügen für lesson_type
|
||||
await queryInterface.sequelize.query(`
|
||||
COMMENT ON COLUMN community.vocab_course_lesson.lesson_type IS
|
||||
'Type: vocab, grammar, conversation, culture, review';
|
||||
`);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TABLE community.vocab_course_lesson
|
||||
DROP COLUMN IF EXISTS week_number,
|
||||
DROP COLUMN IF EXISTS day_number,
|
||||
DROP COLUMN IF EXISTS lesson_type,
|
||||
DROP COLUMN IF EXISTS audio_url,
|
||||
DROP COLUMN IF EXISTS cultural_notes;
|
||||
`);
|
||||
}
|
||||
};
|
||||
@@ -1,33 +0,0 @@
|
||||
/* eslint-disable */
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface) {
|
||||
// Lernziele für Lektionen
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TABLE community.vocab_course_lesson
|
||||
ADD COLUMN IF NOT EXISTS target_minutes INTEGER,
|
||||
ADD COLUMN IF NOT EXISTS target_score_percent INTEGER DEFAULT 80,
|
||||
ADD COLUMN IF NOT EXISTS requires_review BOOLEAN DEFAULT false;
|
||||
`);
|
||||
|
||||
// Kommentare hinzufügen
|
||||
await queryInterface.sequelize.query(`
|
||||
COMMENT ON COLUMN community.vocab_course_lesson.target_minutes IS
|
||||
'Zielzeit in Minuten für diese Lektion';
|
||||
COMMENT ON COLUMN community.vocab_course_lesson.target_score_percent IS
|
||||
'Mindestpunktzahl in Prozent zum Abschluss (z.B. 80)';
|
||||
COMMENT ON COLUMN community.vocab_course_lesson.requires_review IS
|
||||
'Muss diese Lektion wiederholt werden, wenn Ziel nicht erreicht?';
|
||||
`);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TABLE community.vocab_course_lesson
|
||||
DROP COLUMN IF EXISTS target_minutes,
|
||||
DROP COLUMN IF EXISTS target_score_percent,
|
||||
DROP COLUMN IF EXISTS requires_review;
|
||||
`);
|
||||
}
|
||||
};
|
||||
@@ -1,122 +0,0 @@
|
||||
/* eslint-disable */
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.sequelize.query(`
|
||||
CREATE TABLE IF NOT EXISTS falukant_data.relationship_state (
|
||||
id serial PRIMARY KEY,
|
||||
relationship_id integer NOT NULL UNIQUE,
|
||||
marriage_satisfaction integer NOT NULL DEFAULT 55 CHECK (marriage_satisfaction >= 0 AND marriage_satisfaction <= 100),
|
||||
marriage_public_stability integer NOT NULL DEFAULT 55 CHECK (marriage_public_stability >= 0 AND marriage_public_stability <= 100),
|
||||
lover_role text NULL CHECK (lover_role IN ('secret_affair', 'lover', 'mistress_or_favorite')),
|
||||
affection integer NOT NULL DEFAULT 50 CHECK (affection >= 0 AND affection <= 100),
|
||||
visibility integer NOT NULL DEFAULT 15 CHECK (visibility >= 0 AND visibility <= 100),
|
||||
discretion integer NOT NULL DEFAULT 50 CHECK (discretion >= 0 AND discretion <= 100),
|
||||
maintenance_level integer NOT NULL DEFAULT 50 CHECK (maintenance_level >= 0 AND maintenance_level <= 100),
|
||||
status_fit integer NOT NULL DEFAULT 0 CHECK (status_fit >= -2 AND status_fit <= 2),
|
||||
monthly_base_cost integer NOT NULL DEFAULT 0 CHECK (monthly_base_cost >= 0),
|
||||
months_underfunded integer NOT NULL DEFAULT 0 CHECK (months_underfunded >= 0),
|
||||
active boolean NOT NULL DEFAULT true,
|
||||
acknowledged boolean NOT NULL DEFAULT false,
|
||||
exclusive_flag boolean NOT NULL DEFAULT false,
|
||||
last_monthly_processed_at timestamp with time zone NULL,
|
||||
last_daily_processed_at timestamp with time zone NULL,
|
||||
notes_json jsonb NULL,
|
||||
flags_json jsonb NULL,
|
||||
created_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT relationship_state_relationship_fk
|
||||
FOREIGN KEY (relationship_id)
|
||||
REFERENCES falukant_data.relationship(id)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
`);
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
CREATE INDEX IF NOT EXISTS relationship_state_active_idx
|
||||
ON falukant_data.relationship_state (active);
|
||||
`);
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
CREATE INDEX IF NOT EXISTS relationship_state_lover_role_idx
|
||||
ON falukant_data.relationship_state (lover_role);
|
||||
`);
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TABLE falukant_data.child_relation
|
||||
ADD COLUMN IF NOT EXISTS legitimacy text NOT NULL DEFAULT 'legitimate';
|
||||
`);
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TABLE falukant_data.child_relation
|
||||
ADD COLUMN IF NOT EXISTS birth_context text NOT NULL DEFAULT 'marriage';
|
||||
`);
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TABLE falukant_data.child_relation
|
||||
ADD COLUMN IF NOT EXISTS public_known boolean NOT NULL DEFAULT false;
|
||||
`);
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_constraint
|
||||
WHERE conname = 'child_relation_legitimacy_chk'
|
||||
) THEN
|
||||
ALTER TABLE falukant_data.child_relation
|
||||
ADD CONSTRAINT child_relation_legitimacy_chk
|
||||
CHECK (legitimacy IN ('legitimate', 'acknowledged_bastard', 'hidden_bastard'));
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
`);
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_constraint
|
||||
WHERE conname = 'child_relation_birth_context_chk'
|
||||
) THEN
|
||||
ALTER TABLE falukant_data.child_relation
|
||||
ADD CONSTRAINT child_relation_birth_context_chk
|
||||
CHECK (birth_context IN ('marriage', 'lover'));
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
`);
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TABLE falukant_data.child_relation
|
||||
DROP CONSTRAINT IF EXISTS child_relation_birth_context_chk;
|
||||
`);
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TABLE falukant_data.child_relation
|
||||
DROP CONSTRAINT IF EXISTS child_relation_legitimacy_chk;
|
||||
`);
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TABLE falukant_data.child_relation
|
||||
DROP COLUMN IF EXISTS public_known;
|
||||
`);
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TABLE falukant_data.child_relation
|
||||
DROP COLUMN IF EXISTS birth_context;
|
||||
`);
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TABLE falukant_data.child_relation
|
||||
DROP COLUMN IF EXISTS legitimacy;
|
||||
`);
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
DROP TABLE IF EXISTS falukant_data.relationship_state;
|
||||
`);
|
||||
},
|
||||
};
|
||||
@@ -1,60 +0,0 @@
|
||||
/* eslint-disable */
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.sequelize.query(`
|
||||
INSERT INTO falukant_data.relationship_state (
|
||||
relationship_id,
|
||||
marriage_satisfaction,
|
||||
marriage_public_stability,
|
||||
lover_role,
|
||||
affection,
|
||||
visibility,
|
||||
discretion,
|
||||
maintenance_level,
|
||||
status_fit,
|
||||
monthly_base_cost,
|
||||
months_underfunded,
|
||||
active,
|
||||
acknowledged,
|
||||
exclusive_flag,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
SELECT
|
||||
r.id,
|
||||
55,
|
||||
55,
|
||||
CASE WHEN rt.tr = 'lover' THEN 'lover' ELSE NULL END,
|
||||
50,
|
||||
CASE WHEN rt.tr = 'lover' THEN 20 ELSE 15 END,
|
||||
CASE WHEN rt.tr = 'lover' THEN 45 ELSE 50 END,
|
||||
50,
|
||||
0,
|
||||
CASE WHEN rt.tr = 'lover' THEN 30 ELSE 0 END,
|
||||
0,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP
|
||||
FROM falukant_data.relationship r
|
||||
INNER JOIN falukant_type.relationship rt
|
||||
ON rt.id = r.relationship_type_id
|
||||
LEFT JOIN falukant_data.relationship_state rs
|
||||
ON rs.relationship_id = r.id
|
||||
WHERE rs.id IS NULL
|
||||
AND rt.tr IN ('lover', 'wooing', 'engaged', 'married');
|
||||
`);
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
await queryInterface.sequelize.query(`
|
||||
DELETE FROM falukant_data.relationship_state rs
|
||||
USING falukant_data.relationship r
|
||||
INNER JOIN falukant_type.relationship rt
|
||||
ON rt.id = r.relationship_type_id
|
||||
WHERE rs.relationship_id = r.id
|
||||
AND rt.tr IN ('lover', 'wooing', 'engaged', 'married');
|
||||
`);
|
||||
},
|
||||
};
|
||||
@@ -1,53 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/** @type {import('sequelize-cli').Migration} */
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.addColumn(
|
||||
{ schema: 'falukant_data', tableName: 'user_house' },
|
||||
'servant_count',
|
||||
{
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
}
|
||||
);
|
||||
|
||||
await queryInterface.addColumn(
|
||||
{ schema: 'falukant_data', tableName: 'user_house' },
|
||||
'servant_quality',
|
||||
{
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 50
|
||||
}
|
||||
);
|
||||
|
||||
await queryInterface.addColumn(
|
||||
{ schema: 'falukant_data', tableName: 'user_house' },
|
||||
'servant_pay_level',
|
||||
{
|
||||
type: Sequelize.STRING(20),
|
||||
allowNull: false,
|
||||
defaultValue: 'normal'
|
||||
}
|
||||
);
|
||||
|
||||
await queryInterface.addColumn(
|
||||
{ schema: 'falukant_data', tableName: 'user_house' },
|
||||
'household_order',
|
||||
{
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 55
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.removeColumn({ schema: 'falukant_data', tableName: 'user_house' }, 'household_order');
|
||||
await queryInterface.removeColumn({ schema: 'falukant_data', tableName: 'user_house' }, 'servant_pay_level');
|
||||
await queryInterface.removeColumn({ schema: 'falukant_data', tableName: 'user_house' }, 'servant_quality');
|
||||
await queryInterface.removeColumn({ schema: 'falukant_data', tableName: 'user_house' }, 'servant_count');
|
||||
}
|
||||
};
|
||||
@@ -1,36 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/** @type {import('sequelize-cli').Migration} */
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.addColumn(
|
||||
{ schema: 'falukant_data', tableName: 'user_house' },
|
||||
'household_tension_score',
|
||||
{
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 10
|
||||
}
|
||||
);
|
||||
|
||||
await queryInterface.addColumn(
|
||||
{ schema: 'falukant_data', tableName: 'user_house' },
|
||||
'household_tension_reasons_json',
|
||||
{
|
||||
type: Sequelize.JSONB,
|
||||
allowNull: true
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.removeColumn(
|
||||
{ schema: 'falukant_data', tableName: 'user_house' },
|
||||
'household_tension_reasons_json'
|
||||
);
|
||||
await queryInterface.removeColumn(
|
||||
{ schema: 'falukant_data', tableName: 'user_house' },
|
||||
'household_tension_score'
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -1,83 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/** @type {import('sequelize-cli').Migration} */
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
const table = { schema: 'falukant_data', tableName: 'debtors_prism' };
|
||||
|
||||
await queryInterface.addColumn(table, 'status', {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false,
|
||||
defaultValue: 'delinquent'
|
||||
});
|
||||
|
||||
await queryInterface.addColumn(table, 'entered_at', {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: true
|
||||
});
|
||||
|
||||
await queryInterface.addColumn(table, 'released_at', {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: true
|
||||
});
|
||||
|
||||
await queryInterface.addColumn(table, 'debt_at_entry', {
|
||||
type: Sequelize.DECIMAL(14, 2),
|
||||
allowNull: true
|
||||
});
|
||||
|
||||
await queryInterface.addColumn(table, 'remaining_debt', {
|
||||
type: Sequelize.DECIMAL(14, 2),
|
||||
allowNull: true
|
||||
});
|
||||
|
||||
await queryInterface.addColumn(table, 'days_overdue', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
});
|
||||
|
||||
await queryInterface.addColumn(table, 'reason', {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true
|
||||
});
|
||||
|
||||
await queryInterface.addColumn(table, 'creditworthiness_penalty', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
});
|
||||
|
||||
await queryInterface.addColumn(table, 'next_forced_action', {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true
|
||||
});
|
||||
|
||||
await queryInterface.addColumn(table, 'assets_seized_json', {
|
||||
type: Sequelize.JSONB,
|
||||
allowNull: true
|
||||
});
|
||||
|
||||
await queryInterface.addColumn(table, 'public_known', {
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false
|
||||
});
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
const table = { schema: 'falukant_data', tableName: 'debtors_prism' };
|
||||
|
||||
await queryInterface.removeColumn(table, 'public_known');
|
||||
await queryInterface.removeColumn(table, 'assets_seized_json');
|
||||
await queryInterface.removeColumn(table, 'next_forced_action');
|
||||
await queryInterface.removeColumn(table, 'creditworthiness_penalty');
|
||||
await queryInterface.removeColumn(table, 'reason');
|
||||
await queryInterface.removeColumn(table, 'days_overdue');
|
||||
await queryInterface.removeColumn(table, 'remaining_debt');
|
||||
await queryInterface.removeColumn(table, 'debt_at_entry');
|
||||
await queryInterface.removeColumn(table, 'released_at');
|
||||
await queryInterface.removeColumn(table, 'entered_at');
|
||||
await queryInterface.removeColumn(table, 'status');
|
||||
}
|
||||
};
|
||||
@@ -1,22 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TABLE falukant_data.transport
|
||||
ADD COLUMN IF NOT EXISTS guard_count integer NOT NULL DEFAULT 0;
|
||||
`);
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TABLE falukant_data.underground
|
||||
ALTER COLUMN victim_id DROP NOT NULL;
|
||||
`);
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TABLE falukant_data.transport
|
||||
DROP COLUMN IF EXISTS guard_count;
|
||||
`);
|
||||
}
|
||||
};
|
||||
@@ -1,36 +0,0 @@
|
||||
/* eslint-disable */
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface) {
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TABLE community.vocab_course_lesson
|
||||
ADD COLUMN IF NOT EXISTS learning_goals JSONB,
|
||||
ADD COLUMN IF NOT EXISTS core_patterns JSONB,
|
||||
ADD COLUMN IF NOT EXISTS grammar_focus JSONB,
|
||||
ADD COLUMN IF NOT EXISTS speaking_prompts JSONB,
|
||||
ADD COLUMN IF NOT EXISTS practical_tasks JSONB;
|
||||
`);
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
INSERT INTO community.vocab_grammar_exercise_type (name, description) VALUES
|
||||
('dialog_completion', 'Dialogergänzung'),
|
||||
('situational_response', 'Situative Antwort'),
|
||||
('pattern_drill', 'Muster-Drill'),
|
||||
('reading_aloud', 'Lautlese-Übung'),
|
||||
('speaking_from_memory', 'Freies Sprechen')
|
||||
ON CONFLICT (name) DO NOTHING;
|
||||
`);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TABLE community.vocab_course_lesson
|
||||
DROP COLUMN IF EXISTS practical_tasks,
|
||||
DROP COLUMN IF EXISTS speaking_prompts,
|
||||
DROP COLUMN IF EXISTS grammar_focus,
|
||||
DROP COLUMN IF EXISTS core_patterns,
|
||||
DROP COLUMN IF EXISTS learning_goals;
|
||||
`);
|
||||
}
|
||||
};
|
||||
@@ -1,18 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/** @type {import('sequelize-cli').Migration} */
|
||||
module.exports = {
|
||||
async up(queryInterface) {
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TABLE community.user_param
|
||||
ALTER COLUMN value TYPE TEXT;
|
||||
`);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TABLE community.user_param
|
||||
ALTER COLUMN value TYPE VARCHAR(255);
|
||||
`);
|
||||
}
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/** @type {import('sequelize-cli').Migration} */
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.addColumn(
|
||||
{ schema: 'community', tableName: 'folder' },
|
||||
'is_adult_area',
|
||||
{
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
},
|
||||
);
|
||||
|
||||
await queryInterface.addColumn(
|
||||
{ schema: 'community', tableName: 'image' },
|
||||
'is_adult_content',
|
||||
{
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.removeColumn({ schema: 'community', tableName: 'image' }, 'is_adult_content');
|
||||
await queryInterface.removeColumn({ schema: 'community', tableName: 'folder' }, 'is_adult_area');
|
||||
},
|
||||
};
|
||||
@@ -1,63 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/** @type {import('sequelize-cli').Migration} */
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable(
|
||||
{ schema: 'community', tableName: 'erotic_video' },
|
||||
{
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false,
|
||||
},
|
||||
title: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
description: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
},
|
||||
original_file_name: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
hash: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
},
|
||||
mime_type: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
user_id: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: { schema: 'community', tableName: 'user' },
|
||||
key: 'id',
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'CASCADE',
|
||||
},
|
||||
created_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
|
||||
},
|
||||
updated_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
|
||||
},
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable({ schema: 'community', tableName: 'erotic_video' });
|
||||
},
|
||||
};
|
||||
@@ -1,20 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/** @type {import('sequelize-cli').Migration} */
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.addColumn(
|
||||
{ schema: 'chat', tableName: 'room' },
|
||||
'is_adult_only',
|
||||
{
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.removeColumn({ schema: 'chat', tableName: 'room' }, 'is_adult_only');
|
||||
},
|
||||
};
|
||||
@@ -1,95 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/** @type {import('sequelize-cli').Migration} */
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.addColumn(
|
||||
{ schema: 'community', tableName: 'image' },
|
||||
'is_moderated_hidden',
|
||||
{
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false
|
||||
}
|
||||
).catch(() => {});
|
||||
|
||||
await queryInterface.addColumn(
|
||||
{ schema: 'community', tableName: 'erotic_video' },
|
||||
'is_moderated_hidden',
|
||||
{
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false
|
||||
}
|
||||
).catch(() => {});
|
||||
|
||||
await queryInterface.createTable(
|
||||
{ schema: 'community', tableName: 'erotic_content_report' },
|
||||
{
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false
|
||||
},
|
||||
reporter_id: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
references: { model: { schema: 'community', tableName: 'user' }, key: 'id' },
|
||||
onDelete: 'CASCADE'
|
||||
},
|
||||
target_type: {
|
||||
type: Sequelize.STRING(20),
|
||||
allowNull: false
|
||||
},
|
||||
target_id: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false
|
||||
},
|
||||
reason: {
|
||||
type: Sequelize.STRING(80),
|
||||
allowNull: false
|
||||
},
|
||||
note: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true
|
||||
},
|
||||
status: {
|
||||
type: Sequelize.STRING(20),
|
||||
allowNull: false,
|
||||
defaultValue: 'open'
|
||||
},
|
||||
action_taken: {
|
||||
type: Sequelize.STRING(40),
|
||||
allowNull: true
|
||||
},
|
||||
handled_by: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
references: { model: { schema: 'community', tableName: 'user' }, key: 'id' },
|
||||
onDelete: 'SET NULL'
|
||||
},
|
||||
handled_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: true
|
||||
},
|
||||
created_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('NOW()')
|
||||
},
|
||||
updated_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('NOW()')
|
||||
}
|
||||
}
|
||||
).catch(() => {});
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable({ schema: 'community', tableName: 'erotic_content_report' }).catch(() => {});
|
||||
await queryInterface.removeColumn({ schema: 'community', tableName: 'erotic_video' }, 'is_moderated_hidden').catch(() => {});
|
||||
await queryInterface.removeColumn({ schema: 'community', tableName: 'image' }, 'is_moderated_hidden').catch(() => {});
|
||||
}
|
||||
};
|
||||
@@ -1,89 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/** @type {import('sequelize-cli').Migration} */
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable(
|
||||
{ schema: 'community', tableName: 'erotic_video_image_visibility' },
|
||||
{
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false,
|
||||
},
|
||||
erotic_video_id: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: { schema: 'community', tableName: 'erotic_video' },
|
||||
key: 'id',
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'CASCADE',
|
||||
},
|
||||
visibility_type_id: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: { schema: 'type', tableName: 'image_visibility' },
|
||||
key: 'id',
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'CASCADE',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
await queryInterface.createTable(
|
||||
{ schema: 'community', tableName: 'erotic_video_visibility_user' },
|
||||
{
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false,
|
||||
},
|
||||
erotic_video_id: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: { schema: 'community', tableName: 'erotic_video' },
|
||||
key: 'id',
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'CASCADE',
|
||||
},
|
||||
user_id: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: { schema: 'community', tableName: 'user' },
|
||||
key: 'id',
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'CASCADE',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
INSERT INTO community.erotic_video_image_visibility (erotic_video_id, visibility_type_id)
|
||||
SELECT ev.id, iv.id
|
||||
FROM community.erotic_video ev
|
||||
CROSS JOIN type.image_visibility iv
|
||||
WHERE iv.description = 'adults'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM community.erotic_video_image_visibility eviv
|
||||
WHERE eviv.erotic_video_id = ev.id
|
||||
AND eviv.visibility_type_id = iv.id
|
||||
)
|
||||
`);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable({ schema: 'community', tableName: 'erotic_video_visibility_user' });
|
||||
await queryInterface.dropTable({ schema: 'community', tableName: 'erotic_video_image_visibility' });
|
||||
},
|
||||
};
|
||||
@@ -1,36 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
/** Schwangerschaft (Admin / Spiel): erwarteter Geburtstermin + optionaler Vater-Charakter */
|
||||
module.exports = {
|
||||
async up(queryInterface) {
|
||||
await queryInterface.sequelize.query(`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'falukant_data' AND table_name = 'character' AND column_name = 'pregnancy_due_at'
|
||||
) THEN
|
||||
ALTER TABLE falukant_data."character"
|
||||
ADD COLUMN pregnancy_due_at TIMESTAMPTZ NULL;
|
||||
END IF;
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'falukant_data' AND table_name = 'character' AND column_name = 'pregnancy_father_character_id'
|
||||
) THEN
|
||||
ALTER TABLE falukant_data."character"
|
||||
ADD COLUMN pregnancy_father_character_id INTEGER NULL
|
||||
REFERENCES falukant_data."character"(id) ON DELETE SET NULL;
|
||||
END IF;
|
||||
END$$;
|
||||
`);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TABLE falukant_data."character" DROP COLUMN IF EXISTS pregnancy_father_character_id;
|
||||
`);
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TABLE falukant_data."character" DROP COLUMN IF EXISTS pregnancy_due_at;
|
||||
`);
|
||||
},
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE chat.room
|
||||
ADD COLUMN IF NOT EXISTS gender_restriction_id INTEGER,
|
||||
ADD COLUMN IF NOT EXISTS min_age INTEGER,
|
||||
ADD COLUMN IF NOT EXISTS max_age INTEGER,
|
||||
ADD COLUMN IF NOT EXISTS password VARCHAR(255),
|
||||
ADD COLUMN IF NOT EXISTS friends_of_owner_only BOOLEAN DEFAULT FALSE,
|
||||
ADD COLUMN IF NOT EXISTS required_user_right_id INTEGER;
|
||||
|
||||
UPDATE chat.room
|
||||
SET friends_of_owner_only = FALSE
|
||||
WHERE friends_of_owner_only IS NULL;
|
||||
|
||||
ALTER TABLE chat.room
|
||||
ALTER COLUMN friends_of_owner_only SET DEFAULT FALSE,
|
||||
ALTER COLUMN friends_of_owner_only SET NOT NULL;
|
||||
|
||||
COMMIT;
|
||||
@@ -5,7 +5,6 @@ import ChatUser from './chat/user.js';
|
||||
import Room from './chat/room.js';
|
||||
import User from './community/user.js';
|
||||
import UserParam from './community/user_param.js';
|
||||
import UserDashboard from './community/user_dashboard.js';
|
||||
import UserParamType from './type/user_param.js';
|
||||
import UserRightType from './type/user_right.js';
|
||||
import UserRight from './community/user_right.js';
|
||||
@@ -18,15 +17,11 @@ import UserParamVisibilityType from './type/user_param_visibility.js';
|
||||
import UserParamVisibility from './community/user_param_visibility.js';
|
||||
import Folder from './community/folder.js';
|
||||
import Image from './community/image.js';
|
||||
import EroticVideo from './community/erotic_video.js';
|
||||
import EroticContentReport from './community/erotic_content_report.js';
|
||||
import ImageVisibilityType from './type/image_visibility.js';
|
||||
import ImageVisibilityUser from './community/image_visibility_user.js';
|
||||
import FolderImageVisibility from './community/folder_image_visibility.js';
|
||||
import ImageImageVisibility from './community/image_image_visibility.js';
|
||||
import FolderVisibilityUser from './community/folder_visibility_user.js';
|
||||
import EroticVideoImageVisibility from './community/erotic_video_image_visibility.js';
|
||||
import EroticVideoVisibilityUser from './community/erotic_video_visibility_user.js';
|
||||
import GuestbookEntry from './community/guestbook.js';
|
||||
import Forum from './forum/forum.js';
|
||||
import Title from './forum/title.js';
|
||||
@@ -49,7 +44,6 @@ import FalukantStockType from './falukant/type/stock.js';
|
||||
import Knowledge from './falukant/data/product_knowledge.js';
|
||||
import ProductType from './falukant/type/product.js';
|
||||
import TitleOfNobility from './falukant/type/title_of_nobility.js';
|
||||
import TitleBenefit from './falukant/type/title_benefit.js';
|
||||
import TitleRequirement from './falukant/type/title_requirement.js';
|
||||
import Branch from './falukant/data/branch.js';
|
||||
import BranchType from './falukant/type/branch.js';
|
||||
@@ -72,7 +66,6 @@ import PromotionalGiftCharacterTrait from './falukant/predefine/promotional_gift
|
||||
import PromotionalGiftMood from './falukant/predefine/promotional_gift_mood.js';
|
||||
import RelationshipType from './falukant/type/relationship.js';
|
||||
import Relationship from './falukant/data/relationship.js';
|
||||
import RelationshipState from './falukant/data/relationship_state.js';
|
||||
import PromotionalGiftLog from './falukant/log/promotional_gift.js';
|
||||
import HouseType from './falukant/type/house.js';
|
||||
import BuyableHouse from './falukant/data/buyable_house.js';
|
||||
@@ -100,10 +93,6 @@ import PoliticalOfficeRequirement from './falukant/predefine/political_office_pr
|
||||
import PoliticalOfficePrerequisite from './falukant/predefine/political_office_prerequisite.js';
|
||||
import PoliticalOfficeHistory from './falukant/log/political_office_history.js';
|
||||
import ElectionHistory from './falukant/log/election_history.js';
|
||||
import ChurchOfficeType from './falukant/type/church_office_type.js';
|
||||
import ChurchOfficeRequirement from './falukant/predefine/church_office_requirement.js';
|
||||
import ChurchOffice from './falukant/data/church_office.js';
|
||||
import ChurchApplication from './falukant/data/church_application.js';
|
||||
import Underground from './falukant/data/underground.js';
|
||||
import UndergroundType from './falukant/type/underground.js';
|
||||
import VehicleType from './falukant/type/vehicle.js';
|
||||
@@ -113,17 +102,8 @@ import RegionDistance from './falukant/data/region_distance.js';
|
||||
import WeatherType from './falukant/type/weather.js';
|
||||
import Weather from './falukant/data/weather.js';
|
||||
import ProductWeatherEffect from './falukant/type/product_weather_effect.js';
|
||||
import ProductPriceHistory from './falukant/log/product_price_history.js';
|
||||
import Blog from './community/blog.js';
|
||||
import BlogPost from './community/blog_post.js';
|
||||
import VocabCourse from './community/vocab_course.js';
|
||||
import VocabCourseLesson from './community/vocab_course_lesson.js';
|
||||
import VocabCourseEnrollment from './community/vocab_course_enrollment.js';
|
||||
import VocabCourseProgress from './community/vocab_course_progress.js';
|
||||
import VocabGrammarExerciseType from './community/vocab_grammar_exercise_type.js';
|
||||
import VocabGrammarExercise from './community/vocab_grammar_exercise.js';
|
||||
import VocabGrammarExerciseProgress from './community/vocab_grammar_exercise_progress.js';
|
||||
import CalendarEvent from './community/calendar_event.js';
|
||||
import Campaign from './match3/campaign.js';
|
||||
import Match3Level from './match3/level.js';
|
||||
import Objective from './match3/objective.js';
|
||||
@@ -175,9 +155,6 @@ export default function setupAssociations() {
|
||||
User.hasMany(UserParam, { foreignKey: 'userId', as: 'user_params' });
|
||||
UserParam.belongsTo(User, { foreignKey: 'userId', as: 'user' });
|
||||
|
||||
User.hasOne(UserDashboard, { foreignKey: 'userId', as: 'dashboard' });
|
||||
UserDashboard.belongsTo(User, { foreignKey: 'userId', as: 'user' });
|
||||
|
||||
UserParamValue.belongsTo(UserParamType, { foreignKey: 'userParamTypeId', as: 'user_param_value_type' });
|
||||
UserParamType.hasMany(UserParamValue, { foreignKey: 'userParamTypeId', as: 'user_param_type_value' });
|
||||
|
||||
@@ -213,14 +190,6 @@ export default function setupAssociations() {
|
||||
Image.belongsTo(User, { foreignKey: 'userId' });
|
||||
User.hasMany(Image, { foreignKey: 'userId' });
|
||||
|
||||
EroticVideo.belongsTo(User, { foreignKey: 'userId', as: 'owner' });
|
||||
User.hasMany(EroticVideo, { foreignKey: 'userId', as: 'eroticVideos' });
|
||||
|
||||
EroticContentReport.belongsTo(User, { foreignKey: 'reporterId', as: 'reporter' });
|
||||
User.hasMany(EroticContentReport, { foreignKey: 'reporterId', as: 'eroticContentReports' });
|
||||
EroticContentReport.belongsTo(User, { foreignKey: 'handledBy', as: 'moderator' });
|
||||
User.hasMany(EroticContentReport, { foreignKey: 'handledBy', as: 'handledEroticContentReports' });
|
||||
|
||||
// Image visibility associations
|
||||
Folder.belongsToMany(ImageVisibilityType, {
|
||||
through: FolderImageVisibility,
|
||||
@@ -244,17 +213,6 @@ export default function setupAssociations() {
|
||||
otherKey: 'imageId'
|
||||
});
|
||||
|
||||
EroticVideo.belongsToMany(ImageVisibilityType, {
|
||||
through: EroticVideoImageVisibility,
|
||||
foreignKey: 'eroticVideoId',
|
||||
otherKey: 'visibilityTypeId'
|
||||
});
|
||||
ImageVisibilityType.belongsToMany(EroticVideo, {
|
||||
through: EroticVideoImageVisibility,
|
||||
foreignKey: 'visibilityTypeId',
|
||||
otherKey: 'eroticVideoId'
|
||||
});
|
||||
|
||||
Folder.belongsToMany(ImageVisibilityUser, {
|
||||
through: FolderVisibilityUser,
|
||||
foreignKey: 'folderId',
|
||||
@@ -266,19 +224,6 @@ export default function setupAssociations() {
|
||||
otherKey: 'folderId'
|
||||
});
|
||||
|
||||
EroticVideo.belongsToMany(User, {
|
||||
through: EroticVideoVisibilityUser,
|
||||
foreignKey: 'eroticVideoId',
|
||||
otherKey: 'userId',
|
||||
as: 'selectedVisibilityUsers'
|
||||
});
|
||||
User.belongsToMany(EroticVideo, {
|
||||
through: EroticVideoVisibilityUser,
|
||||
foreignKey: 'userId',
|
||||
otherKey: 'eroticVideoId',
|
||||
as: 'visibleEroticVideos'
|
||||
});
|
||||
|
||||
// Guestbook related associations
|
||||
User.hasMany(GuestbookEntry, { foreignKey: 'recipientId', as: 'receivedEntries' });
|
||||
User.hasMany(GuestbookEntry, { foreignKey: 'senderId', as: 'sentEntries' });
|
||||
@@ -379,8 +324,6 @@ export default function setupAssociations() {
|
||||
FalukantCharacter.belongsTo(RegionData, { foreignKey: 'regionId', as: 'region' });
|
||||
RegionData.hasMany(FalukantCharacter, { foreignKey: 'regionId', as: 'charactersInRegion' });
|
||||
|
||||
FalukantCharacter.belongsTo(FalukantCharacter, { foreignKey: 'pregnancyFatherCharacterId', as: 'pregnancyFather' });
|
||||
|
||||
FalukantStock.belongsTo(FalukantStockType, { foreignKey: 'stockTypeId', as: 'stockType' });
|
||||
FalukantStockType.hasMany(FalukantStock, { foreignKey: 'stockTypeId', as: 'stocks' });
|
||||
|
||||
@@ -392,8 +335,6 @@ export default function setupAssociations() {
|
||||
|
||||
TitleRequirement.belongsTo(TitleOfNobility, { foreignKey: 'titleId', as: 'title' });
|
||||
TitleOfNobility.hasMany(TitleRequirement, { foreignKey: 'titleId', as: 'requirements' });
|
||||
TitleOfNobility.hasMany(TitleBenefit, { foreignKey: 'titleId', as: 'benefits' });
|
||||
TitleBenefit.belongsTo(TitleOfNobility, { foreignKey: 'titleId', as: 'title' });
|
||||
|
||||
Branch.belongsTo(RegionData, { foreignKey: 'regionId', as: 'region' });
|
||||
RegionData.hasMany(Branch, { foreignKey: 'regionId', as: 'branches' });
|
||||
@@ -464,13 +405,6 @@ export default function setupAssociations() {
|
||||
DaySell.belongsTo(FalukantUser, { foreignKey: 'sellerId', as: 'user' });
|
||||
FalukantUser.hasMany(DaySell, { foreignKey: 'sellerId', as: 'daySells' });
|
||||
|
||||
// Produkt-Preishistorie (Zeitreihe für Preiskurven)
|
||||
ProductPriceHistory.belongsTo(ProductType, { foreignKey: 'productId', as: 'productType' });
|
||||
ProductType.hasMany(ProductPriceHistory, { foreignKey: 'productId', as: 'priceHistory' });
|
||||
|
||||
ProductPriceHistory.belongsTo(RegionData, { foreignKey: 'regionId', as: 'region' });
|
||||
RegionData.hasMany(ProductPriceHistory, { foreignKey: 'regionId', as: 'productPriceHistory' });
|
||||
|
||||
Notification.belongsTo(FalukantUser, { foreignKey: 'userId', as: 'user' });
|
||||
FalukantUser.hasMany(Notification, { foreignKey: 'userId', as: 'notifications' });
|
||||
|
||||
@@ -499,8 +433,6 @@ export default function setupAssociations() {
|
||||
Relationship.belongsTo(FalukantCharacter, { foreignKey: 'character2Id', as: 'character2', });
|
||||
FalukantCharacter.hasMany(Relationship, { foreignKey: 'character1Id', as: 'relationshipsAsCharacter1', });
|
||||
FalukantCharacter.hasMany(Relationship, { foreignKey: 'character2Id', as: 'relationshipsAsCharacter2', });
|
||||
Relationship.hasOne(RelationshipState, { foreignKey: 'relationshipId', as: 'state' });
|
||||
RelationshipState.belongsTo(Relationship, { foreignKey: 'relationshipId', as: 'relationship' });
|
||||
|
||||
PromotionalGiftLog.belongsTo(PromotionalGift, { foreignKey: 'giftId', as: 'gift' });
|
||||
PromotionalGift.hasMany(PromotionalGiftLog, { foreignKey: 'giftId', as: 'logs' });
|
||||
@@ -626,14 +558,14 @@ export default function setupAssociations() {
|
||||
|
||||
Party.belongsToMany(TitleOfNobility, {
|
||||
through: PartyInvitedNobility,
|
||||
foreignKey: 'partyId',
|
||||
otherKey: 'titleOfNobilityId',
|
||||
foreignKey: 'party_id',
|
||||
otherKey: 'title_of_nobility_id',
|
||||
as: 'invitedNobilities',
|
||||
});
|
||||
TitleOfNobility.belongsToMany(Party, {
|
||||
through: PartyInvitedNobility,
|
||||
foreignKey: 'titleOfNobilityId',
|
||||
otherKey: 'partyId',
|
||||
foreignKey: 'title_of_nobility_id',
|
||||
otherKey: 'party_id',
|
||||
as: 'partiesInvitedTo',
|
||||
});
|
||||
|
||||
@@ -927,96 +859,6 @@ export default function setupAssociations() {
|
||||
}
|
||||
);
|
||||
|
||||
// — Church Offices —
|
||||
|
||||
// Requirements for church office
|
||||
ChurchOfficeRequirement.belongsTo(ChurchOfficeType, {
|
||||
foreignKey: 'officeTypeId',
|
||||
as: 'officeType'
|
||||
});
|
||||
ChurchOfficeType.hasMany(ChurchOfficeRequirement, {
|
||||
foreignKey: 'officeTypeId',
|
||||
as: 'requirements'
|
||||
});
|
||||
|
||||
// Prerequisite office type
|
||||
ChurchOfficeRequirement.belongsTo(ChurchOfficeType, {
|
||||
foreignKey: 'prerequisiteOfficeTypeId',
|
||||
as: 'prerequisiteOfficeType'
|
||||
});
|
||||
|
||||
// Actual church office holdings
|
||||
ChurchOffice.belongsTo(ChurchOfficeType, {
|
||||
foreignKey: 'officeTypeId',
|
||||
as: 'type'
|
||||
});
|
||||
ChurchOfficeType.hasMany(ChurchOffice, {
|
||||
foreignKey: 'officeTypeId',
|
||||
as: 'offices'
|
||||
});
|
||||
|
||||
ChurchOffice.belongsTo(FalukantCharacter, {
|
||||
foreignKey: 'characterId',
|
||||
as: 'holder'
|
||||
});
|
||||
FalukantCharacter.hasOne(ChurchOffice, {
|
||||
foreignKey: 'characterId',
|
||||
as: 'heldChurchOffice'
|
||||
});
|
||||
|
||||
// Supervisor relationship
|
||||
ChurchOffice.belongsTo(FalukantCharacter, {
|
||||
foreignKey: 'supervisorId',
|
||||
as: 'supervisor'
|
||||
});
|
||||
|
||||
// Region relationship
|
||||
ChurchOffice.belongsTo(RegionData, {
|
||||
foreignKey: 'regionId',
|
||||
as: 'region'
|
||||
});
|
||||
RegionData.hasMany(ChurchOffice, {
|
||||
foreignKey: 'regionId',
|
||||
as: 'churchOffices'
|
||||
});
|
||||
|
||||
// Applications for church office
|
||||
ChurchApplication.belongsTo(ChurchOfficeType, {
|
||||
foreignKey: 'officeTypeId',
|
||||
as: 'officeType'
|
||||
});
|
||||
ChurchOfficeType.hasMany(ChurchApplication, {
|
||||
foreignKey: 'officeTypeId',
|
||||
as: 'applications'
|
||||
});
|
||||
|
||||
ChurchApplication.belongsTo(FalukantCharacter, {
|
||||
foreignKey: 'characterId',
|
||||
as: 'applicant'
|
||||
});
|
||||
FalukantCharacter.hasMany(ChurchApplication, {
|
||||
foreignKey: 'characterId',
|
||||
as: 'churchApplications'
|
||||
});
|
||||
|
||||
ChurchApplication.belongsTo(FalukantCharacter, {
|
||||
foreignKey: 'supervisorId',
|
||||
as: 'supervisor'
|
||||
});
|
||||
FalukantCharacter.hasMany(ChurchApplication, {
|
||||
foreignKey: 'supervisorId',
|
||||
as: 'supervisedApplications'
|
||||
});
|
||||
|
||||
ChurchApplication.belongsTo(RegionData, {
|
||||
foreignKey: 'regionId',
|
||||
as: 'region'
|
||||
});
|
||||
RegionData.hasMany(ChurchApplication, {
|
||||
foreignKey: 'regionId',
|
||||
as: 'churchApplications'
|
||||
});
|
||||
|
||||
Underground.belongsTo(UndergroundType, {
|
||||
foreignKey: 'undergroundTypeId',
|
||||
as: 'undergroundType'
|
||||
@@ -1099,40 +941,5 @@ export default function setupAssociations() {
|
||||
|
||||
TaxiHighscore.belongsTo(TaxiMap, { foreignKey: 'mapId', as: 'map' });
|
||||
TaxiMap.hasMany(TaxiHighscore, { foreignKey: 'mapId', as: 'highscores' });
|
||||
|
||||
// Vocab Course associations
|
||||
VocabCourse.belongsTo(User, { foreignKey: 'ownerUserId', as: 'owner' });
|
||||
User.hasMany(VocabCourse, { foreignKey: 'ownerUserId', as: 'ownedCourses' });
|
||||
|
||||
VocabCourseLesson.belongsTo(VocabCourse, { foreignKey: 'courseId', as: 'course' });
|
||||
VocabCourse.hasMany(VocabCourseLesson, { foreignKey: 'courseId', as: 'lessons' });
|
||||
|
||||
VocabCourseEnrollment.belongsTo(User, { foreignKey: 'userId', as: 'user' });
|
||||
User.hasMany(VocabCourseEnrollment, { foreignKey: 'userId', as: 'courseEnrollments' });
|
||||
VocabCourseEnrollment.belongsTo(VocabCourse, { foreignKey: 'courseId', as: 'course' });
|
||||
VocabCourse.hasMany(VocabCourseEnrollment, { foreignKey: 'courseId', as: 'enrollments' });
|
||||
|
||||
VocabCourseProgress.belongsTo(User, { foreignKey: 'userId', as: 'user' });
|
||||
User.hasMany(VocabCourseProgress, { foreignKey: 'userId', as: 'courseProgress' });
|
||||
VocabCourseProgress.belongsTo(VocabCourse, { foreignKey: 'courseId', as: 'course' });
|
||||
VocabCourse.hasMany(VocabCourseProgress, { foreignKey: 'courseId', as: 'progress' });
|
||||
VocabCourseProgress.belongsTo(VocabCourseLesson, { foreignKey: 'lessonId', as: 'lesson' });
|
||||
VocabCourseLesson.hasMany(VocabCourseProgress, { foreignKey: 'lessonId', as: 'progress' });
|
||||
|
||||
// Grammar Exercise associations
|
||||
VocabGrammarExercise.belongsTo(VocabCourseLesson, { foreignKey: 'lessonId', as: 'lesson' });
|
||||
VocabCourseLesson.hasMany(VocabGrammarExercise, { foreignKey: 'lessonId', as: 'grammarExercises' });
|
||||
VocabGrammarExercise.belongsTo(VocabGrammarExerciseType, { foreignKey: 'exerciseTypeId', as: 'exerciseType' });
|
||||
VocabGrammarExerciseType.hasMany(VocabGrammarExercise, { foreignKey: 'exerciseTypeId', as: 'exercises' });
|
||||
VocabGrammarExercise.belongsTo(User, { foreignKey: 'createdByUserId', as: 'creator' });
|
||||
User.hasMany(VocabGrammarExercise, { foreignKey: 'createdByUserId', as: 'createdGrammarExercises' });
|
||||
|
||||
VocabGrammarExerciseProgress.belongsTo(User, { foreignKey: 'userId', as: 'user' });
|
||||
User.hasMany(VocabGrammarExerciseProgress, { foreignKey: 'userId', as: 'grammarExerciseProgress' });
|
||||
VocabGrammarExerciseProgress.belongsTo(VocabGrammarExercise, { foreignKey: 'exerciseId', as: 'exercise' });
|
||||
VocabGrammarExercise.hasMany(VocabGrammarExerciseProgress, { foreignKey: 'exerciseId', as: 'progress' });
|
||||
|
||||
// Calendar associations
|
||||
CalendarEvent.belongsTo(User, { foreignKey: 'userId', as: 'user' });
|
||||
User.hasMany(CalendarEvent, { foreignKey: 'userId', as: 'calendarEvents' });
|
||||
}
|
||||
|
||||
|
||||
@@ -20,10 +20,6 @@ const Room = sequelize.define('Room', {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true},
|
||||
isAdultOnly: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false},
|
||||
genderRestrictionId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true},
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../utils/sequelize.js';
|
||||
|
||||
class CalendarEvent extends Model { }
|
||||
|
||||
CalendarEvent.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
userId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'user',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
title: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: false
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
},
|
||||
categoryId: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
defaultValue: 'personal',
|
||||
comment: 'Category key: personal, work, family, health, birthday, holiday, reminder, other'
|
||||
},
|
||||
startDate: {
|
||||
type: DataTypes.DATEONLY,
|
||||
allowNull: false
|
||||
},
|
||||
endDate: {
|
||||
type: DataTypes.DATEONLY,
|
||||
allowNull: true,
|
||||
comment: 'End date for multi-day events, null means same as startDate'
|
||||
},
|
||||
startTime: {
|
||||
type: DataTypes.TIME,
|
||||
allowNull: true,
|
||||
comment: 'Start time, null for all-day events'
|
||||
},
|
||||
endTime: {
|
||||
type: DataTypes.TIME,
|
||||
allowNull: true,
|
||||
comment: 'End time, null for all-day events'
|
||||
},
|
||||
allDay: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false
|
||||
},
|
||||
createdAt: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW
|
||||
},
|
||||
updatedAt: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'CalendarEvent',
|
||||
tableName: 'calendar_event',
|
||||
schema: 'community',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
indexes: [
|
||||
{
|
||||
fields: ['user_id']
|
||||
},
|
||||
{
|
||||
fields: ['user_id', 'start_date']
|
||||
},
|
||||
{
|
||||
fields: ['user_id', 'start_date', 'end_date']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
export default CalendarEvent;
|
||||
@@ -1,64 +0,0 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../utils/sequelize.js';
|
||||
|
||||
class EroticContentReport extends Model {}
|
||||
|
||||
EroticContentReport.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
reporterId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
field: 'reporter_id'
|
||||
},
|
||||
targetType: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
field: 'target_type'
|
||||
},
|
||||
targetId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
field: 'target_id'
|
||||
},
|
||||
reason: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
note: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
defaultValue: 'open'
|
||||
},
|
||||
actionTaken: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
field: 'action_taken'
|
||||
},
|
||||
handledBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
field: 'handled_by'
|
||||
},
|
||||
handledAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
field: 'handled_at'
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'EroticContentReport',
|
||||
tableName: 'erotic_content_report',
|
||||
schema: 'community',
|
||||
timestamps: true,
|
||||
underscored: true
|
||||
});
|
||||
|
||||
export default EroticContentReport;
|
||||
@@ -1,55 +0,0 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../utils/sequelize.js';
|
||||
|
||||
class EroticVideo extends Model {}
|
||||
|
||||
EroticVideo.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
title: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
},
|
||||
originalFileName: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
field: 'original_file_name'
|
||||
},
|
||||
hash: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
unique: true
|
||||
},
|
||||
mimeType: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
field: 'mime_type'
|
||||
},
|
||||
isModeratedHidden: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
field: 'is_moderated_hidden'
|
||||
},
|
||||
userId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
field: 'user_id'
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'EroticVideo',
|
||||
tableName: 'erotic_video',
|
||||
schema: 'community',
|
||||
timestamps: true,
|
||||
underscored: true
|
||||
});
|
||||
|
||||
export default EroticVideo;
|
||||
@@ -1,26 +0,0 @@
|
||||
import { sequelize } from '../../utils/sequelize.js';
|
||||
import { DataTypes } from 'sequelize';
|
||||
|
||||
const EroticVideoImageVisibility = sequelize.define('erotic_video_image_visibility', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
allowNull: false
|
||||
},
|
||||
eroticVideoId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false
|
||||
},
|
||||
visibilityTypeId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false
|
||||
}
|
||||
}, {
|
||||
tableName: 'erotic_video_image_visibility',
|
||||
timestamps: false,
|
||||
underscored: true,
|
||||
schema: 'community'
|
||||
});
|
||||
|
||||
export default EroticVideoImageVisibility;
|
||||
@@ -1,26 +0,0 @@
|
||||
import { sequelize } from '../../utils/sequelize.js';
|
||||
import { DataTypes } from 'sequelize';
|
||||
|
||||
const EroticVideoVisibilityUser = sequelize.define('erotic_video_visibility_user', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
allowNull: false
|
||||
},
|
||||
eroticVideoId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false
|
||||
},
|
||||
userId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false
|
||||
}
|
||||
}, {
|
||||
tableName: 'erotic_video_visibility_user',
|
||||
timestamps: false,
|
||||
underscored: true,
|
||||
schema: 'community'
|
||||
});
|
||||
|
||||
export default EroticVideoVisibilityUser;
|
||||
@@ -6,11 +6,6 @@ const Folder = sequelize.define('folder', {
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false},
|
||||
isAdultArea: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false
|
||||
},
|
||||
parentId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true
|
||||
|
||||
@@ -6,16 +6,6 @@ const Image = sequelize.define('image', {
|
||||
title: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false},
|
||||
isAdultContent: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false
|
||||
},
|
||||
isModeratedHidden: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true},
|
||||
|
||||
@@ -3,44 +3,6 @@ import { DataTypes } from 'sequelize';
|
||||
import { encrypt, decrypt } from '../../utils/encryption.js';
|
||||
import crypto from 'crypto';
|
||||
|
||||
function encodeEncryptedValueToBlob(value) {
|
||||
const encrypted = encrypt(value);
|
||||
return Buffer.from(encrypted, 'utf8');
|
||||
}
|
||||
|
||||
function decodeEncryptedBlob(value) {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const encryptedUtf8 = value.toString('utf8');
|
||||
const decryptedUtf8 = decrypt(encryptedUtf8);
|
||||
if (decryptedUtf8) {
|
||||
return decryptedUtf8;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Email utf8 decryption failed, trying legacy hex format:', error.message);
|
||||
}
|
||||
|
||||
try {
|
||||
const encryptedHex = value.toString('hex');
|
||||
const decryptedHex = decrypt(encryptedHex);
|
||||
if (decryptedHex) {
|
||||
return decryptedHex;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Email legacy hex decryption failed:', error.message);
|
||||
}
|
||||
|
||||
try {
|
||||
return value.toString('utf8');
|
||||
} catch (error) {
|
||||
console.warn('Email could not be read as plain text:', error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const User = sequelize.define('user', {
|
||||
email: {
|
||||
type: DataTypes.BLOB,
|
||||
@@ -48,12 +10,35 @@ const User = sequelize.define('user', {
|
||||
unique: true,
|
||||
set(value) {
|
||||
if (value) {
|
||||
this.setDataValue('email', encodeEncryptedValueToBlob(value));
|
||||
const encrypted = encrypt(value);
|
||||
// Konvertiere Hex-String zu Buffer für die Speicherung
|
||||
const buffer = Buffer.from(encrypted, 'hex');
|
||||
this.setDataValue('email', buffer);
|
||||
}
|
||||
},
|
||||
get() {
|
||||
const encrypted = this.getDataValue('email');
|
||||
return decodeEncryptedBlob(encrypted);
|
||||
if (encrypted) {
|
||||
try {
|
||||
// Konvertiere Buffer zu String für die Entschlüsselung
|
||||
const encryptedString = encrypted.toString('hex');
|
||||
const decrypted = decrypt(encryptedString);
|
||||
if (decrypted) {
|
||||
return decrypted;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Email decryption failed, treating as plain text:', error.message);
|
||||
}
|
||||
|
||||
// Fallback: Versuche es als Klartext zu lesen
|
||||
try {
|
||||
return encrypted.toString('utf8');
|
||||
} catch (error) {
|
||||
console.warn('Email could not be read as plain text:', error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
},
|
||||
salt: {
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import { sequelize } from '../../utils/sequelize.js';
|
||||
import { DataTypes } from 'sequelize';
|
||||
import User from './user.js';
|
||||
|
||||
const UserDashboard = sequelize.define('user_dashboard', {
|
||||
userId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
primaryKey: true,
|
||||
references: { model: User, key: 'id' }
|
||||
},
|
||||
config: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: false,
|
||||
defaultValue: { widgets: [] }
|
||||
}
|
||||
}, {
|
||||
tableName: 'user_dashboard',
|
||||
schema: 'community',
|
||||
underscored: true,
|
||||
timestamps: false
|
||||
});
|
||||
|
||||
export default UserDashboard;
|
||||
@@ -14,7 +14,7 @@ const UserParam = sequelize.define('user_param', {
|
||||
allowNull: false
|
||||
},
|
||||
value: {
|
||||
type: DataTypes.TEXT,
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
set(value) {
|
||||
if (value) {
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../utils/sequelize.js';
|
||||
|
||||
class VocabCourse extends Model {}
|
||||
|
||||
VocabCourse.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
ownerUserId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
field: 'owner_user_id'
|
||||
},
|
||||
title: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
},
|
||||
languageId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
field: 'language_id'
|
||||
},
|
||||
nativeLanguageId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
field: 'native_language_id',
|
||||
comment: 'Muttersprache des Lerners (z.B. Deutsch, Englisch). NULL bedeutet "für alle Sprachen".'
|
||||
},
|
||||
difficultyLevel: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 1,
|
||||
field: 'difficulty_level'
|
||||
},
|
||||
isPublic: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
field: 'is_public'
|
||||
},
|
||||
shareCode: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
unique: true,
|
||||
field: 'share_code'
|
||||
},
|
||||
createdAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW,
|
||||
field: 'created_at'
|
||||
},
|
||||
updatedAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW,
|
||||
field: 'updated_at'
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'VocabCourse',
|
||||
tableName: 'vocab_course',
|
||||
schema: 'community',
|
||||
timestamps: true,
|
||||
underscored: true
|
||||
});
|
||||
|
||||
export default VocabCourse;
|
||||
@@ -1,37 +0,0 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../utils/sequelize.js';
|
||||
|
||||
class VocabCourseEnrollment extends Model {}
|
||||
|
||||
VocabCourseEnrollment.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
userId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
field: 'user_id'
|
||||
},
|
||||
courseId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
field: 'course_id'
|
||||
},
|
||||
enrolledAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW,
|
||||
field: 'enrolled_at'
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'VocabCourseEnrollment',
|
||||
tableName: 'vocab_course_enrollment',
|
||||
schema: 'community',
|
||||
timestamps: false,
|
||||
underscored: true
|
||||
});
|
||||
|
||||
export default VocabCourseEnrollment;
|
||||
@@ -1,118 +0,0 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../utils/sequelize.js';
|
||||
|
||||
class VocabCourseLesson extends Model {}
|
||||
|
||||
VocabCourseLesson.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
courseId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
field: 'course_id'
|
||||
},
|
||||
chapterId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
field: 'chapter_id'
|
||||
},
|
||||
lessonNumber: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
field: 'lesson_number'
|
||||
},
|
||||
title: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
},
|
||||
weekNumber: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
field: 'week_number'
|
||||
},
|
||||
dayNumber: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
field: 'day_number'
|
||||
},
|
||||
lessonType: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
defaultValue: 'vocab',
|
||||
field: 'lesson_type'
|
||||
},
|
||||
audioUrl: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'audio_url'
|
||||
},
|
||||
culturalNotes: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'cultural_notes'
|
||||
},
|
||||
learningGoals: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: true,
|
||||
field: 'learning_goals'
|
||||
},
|
||||
corePatterns: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: true,
|
||||
field: 'core_patterns'
|
||||
},
|
||||
grammarFocus: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: true,
|
||||
field: 'grammar_focus'
|
||||
},
|
||||
speakingPrompts: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: true,
|
||||
field: 'speaking_prompts'
|
||||
},
|
||||
practicalTasks: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: true,
|
||||
field: 'practical_tasks'
|
||||
},
|
||||
targetMinutes: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
field: 'target_minutes'
|
||||
},
|
||||
targetScorePercent: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 80,
|
||||
field: 'target_score_percent'
|
||||
},
|
||||
requiresReview: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
field: 'requires_review'
|
||||
},
|
||||
createdAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW,
|
||||
field: 'created_at'
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'VocabCourseLesson',
|
||||
tableName: 'vocab_course_lesson',
|
||||
schema: 'community',
|
||||
timestamps: false,
|
||||
underscored: true
|
||||
});
|
||||
|
||||
export default VocabCourseLesson;
|
||||
@@ -1,56 +0,0 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../utils/sequelize.js';
|
||||
|
||||
class VocabCourseProgress extends Model {}
|
||||
|
||||
VocabCourseProgress.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
userId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
field: 'user_id'
|
||||
},
|
||||
courseId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
field: 'course_id'
|
||||
},
|
||||
lessonId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
field: 'lesson_id'
|
||||
},
|
||||
completed: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false
|
||||
},
|
||||
score: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
},
|
||||
lastAccessedAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
field: 'last_accessed_at'
|
||||
},
|
||||
completedAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
field: 'completed_at'
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'VocabCourseProgress',
|
||||
tableName: 'vocab_course_progress',
|
||||
schema: 'community',
|
||||
timestamps: false,
|
||||
underscored: true
|
||||
});
|
||||
|
||||
export default VocabCourseProgress;
|
||||
@@ -1,69 +0,0 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../utils/sequelize.js';
|
||||
|
||||
class VocabGrammarExercise extends Model {}
|
||||
|
||||
VocabGrammarExercise.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
lessonId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
field: 'lesson_id'
|
||||
},
|
||||
exerciseTypeId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
field: 'exercise_type_id'
|
||||
},
|
||||
exerciseNumber: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
field: 'exercise_number'
|
||||
},
|
||||
title: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false
|
||||
},
|
||||
instruction: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
},
|
||||
questionData: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: false,
|
||||
field: 'question_data'
|
||||
},
|
||||
answerData: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: false,
|
||||
field: 'answer_data'
|
||||
},
|
||||
explanation: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
},
|
||||
createdByUserId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
field: 'created_by_user_id'
|
||||
},
|
||||
createdAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW,
|
||||
field: 'created_at'
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'VocabGrammarExercise',
|
||||
tableName: 'vocab_grammar_exercise',
|
||||
schema: 'community',
|
||||
timestamps: false,
|
||||
underscored: true
|
||||
});
|
||||
|
||||
export default VocabGrammarExercise;
|
||||
@@ -1,57 +0,0 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../utils/sequelize.js';
|
||||
|
||||
class VocabGrammarExerciseProgress extends Model {}
|
||||
|
||||
VocabGrammarExerciseProgress.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
userId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
field: 'user_id'
|
||||
},
|
||||
exerciseId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
field: 'exercise_id'
|
||||
},
|
||||
attempts: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
},
|
||||
correctAttempts: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
field: 'correct_attempts'
|
||||
},
|
||||
lastAttemptAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
field: 'last_attempt_at'
|
||||
},
|
||||
completed: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false
|
||||
},
|
||||
completedAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
field: 'completed_at'
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'VocabGrammarExerciseProgress',
|
||||
tableName: 'vocab_grammar_exercise_progress',
|
||||
schema: 'community',
|
||||
timestamps: false,
|
||||
underscored: true
|
||||
});
|
||||
|
||||
export default VocabGrammarExerciseProgress;
|
||||
@@ -1,36 +0,0 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../utils/sequelize.js';
|
||||
|
||||
class VocabGrammarExerciseType extends Model {}
|
||||
|
||||
VocabGrammarExerciseType.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
unique: true
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
},
|
||||
createdAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW,
|
||||
field: 'created_at'
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'VocabGrammarExerciseType',
|
||||
tableName: 'vocab_grammar_exercise_type',
|
||||
schema: 'community',
|
||||
timestamps: false,
|
||||
underscored: true
|
||||
});
|
||||
|
||||
export default VocabGrammarExerciseType;
|
||||
@@ -45,14 +45,6 @@ FalukantCharacter.init(
|
||||
min: 0,
|
||||
max: 100
|
||||
}
|
||||
},
|
||||
pregnancyDueAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
},
|
||||
pregnancyFatherCharacterId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -61,12 +53,7 @@ FalukantCharacter.init(
|
||||
tableName: 'character',
|
||||
schema: 'falukant_data',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
// Spalten erst nach Migration 20260330000000; ohne Exclude würde SELECT/INSERT auf alten DBs fehlschlagen
|
||||
defaultScope: {
|
||||
attributes: { exclude: ['pregnancyDueAt', 'pregnancyFatherCharacterId'] },
|
||||
},
|
||||
}
|
||||
underscored: true}
|
||||
);
|
||||
|
||||
export default FalukantCharacter;
|
||||
|
||||
@@ -27,25 +27,7 @@ ChildRelation.init(
|
||||
isHeir: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: true,
|
||||
default: false},
|
||||
legitimacy: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
defaultValue: 'legitimate',
|
||||
validate: {
|
||||
isIn: [['legitimate', 'acknowledged_bastard', 'hidden_bastard']]
|
||||
}},
|
||||
birthContext: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
defaultValue: 'marriage',
|
||||
validate: {
|
||||
isIn: [['marriage', 'lover']]
|
||||
}},
|
||||
publicKnown: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false}
|
||||
default: false}
|
||||
},
|
||||
{
|
||||
sequelize,
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../../utils/sequelize.js';
|
||||
|
||||
class ChurchApplication extends Model {}
|
||||
|
||||
ChurchApplication.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
officeTypeId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false
|
||||
},
|
||||
characterId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false
|
||||
},
|
||||
regionId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false
|
||||
},
|
||||
supervisorId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: 'ID des Vorgesetzten, der über die Bewerbung entscheidet (null für Einstiegspositionen ohne Supervisor)'
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('pending', 'approved', 'rejected'),
|
||||
allowNull: false,
|
||||
defaultValue: 'pending'
|
||||
},
|
||||
decisionDate: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'ChurchApplication',
|
||||
tableName: 'church_application',
|
||||
schema: 'falukant_data',
|
||||
timestamps: true,
|
||||
underscored: true
|
||||
});
|
||||
|
||||
export default ChurchApplication;
|
||||
@@ -1,38 +0,0 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../../utils/sequelize.js';
|
||||
|
||||
class ChurchOffice extends Model {}
|
||||
|
||||
ChurchOffice.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
officeTypeId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false
|
||||
},
|
||||
characterId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false
|
||||
},
|
||||
regionId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false
|
||||
},
|
||||
supervisorId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: 'ID des Vorgesetzten (höhere Position in der Hierarchie)'
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'ChurchOffice',
|
||||
tableName: 'church_office',
|
||||
schema: 'falukant_data',
|
||||
timestamps: true,
|
||||
underscored: true
|
||||
});
|
||||
|
||||
export default ChurchOffice;
|
||||
@@ -7,57 +7,7 @@ DebtorsPrism.init({
|
||||
// Verknüpfung auf FalukantCharacter
|
||||
characterId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
defaultValue: 'delinquent'
|
||||
},
|
||||
enteredAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true
|
||||
},
|
||||
releasedAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true
|
||||
},
|
||||
debtAtEntry: {
|
||||
type: DataTypes.DECIMAL(14, 2),
|
||||
allowNull: true
|
||||
},
|
||||
remainingDebt: {
|
||||
type: DataTypes.DECIMAL(14, 2),
|
||||
allowNull: true
|
||||
},
|
||||
daysOverdue: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
},
|
||||
reason: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
},
|
||||
creditworthinessPenalty: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
},
|
||||
nextForcedAction: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
},
|
||||
assetsSeizedJson: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: true
|
||||
},
|
||||
publicKnown: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false
|
||||
}
|
||||
}, {
|
||||
allowNull: false}}, {
|
||||
sequelize,
|
||||
modelName: 'DebtorsPrism',
|
||||
tableName: 'debtors_prism',
|
||||
|
||||
@@ -22,12 +22,7 @@ Production.init({
|
||||
startTimestamp: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: sequelize.literal('CURRENT_TIMESTAMP')},
|
||||
sleep: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
comment: 'Produktion ist zurückgestellt'}
|
||||
defaultValue: sequelize.literal('CURRENT_TIMESTAMP')}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'Production',
|
||||
|
||||
@@ -10,20 +10,11 @@ RegionData.init({
|
||||
allowNull: false},
|
||||
regionTypeId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: RegionType,
|
||||
key: 'id',
|
||||
schema: 'falukant_type'
|
||||
}
|
||||
allowNull: false
|
||||
},
|
||||
parentId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'region',
|
||||
key: 'id',
|
||||
schema: 'falukant_data'}
|
||||
allowNull: true
|
||||
},
|
||||
map: {
|
||||
type: DataTypes.JSONB,
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../../utils/sequelize.js';
|
||||
|
||||
class RelationshipState extends Model {}
|
||||
|
||||
RelationshipState.init(
|
||||
{
|
||||
relationshipId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
},
|
||||
marriageSatisfaction: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 55,
|
||||
validate: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
},
|
||||
},
|
||||
marriagePublicStability: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 55,
|
||||
validate: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
},
|
||||
},
|
||||
loverRole: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
validate: {
|
||||
isIn: [[null, 'secret_affair', 'lover', 'mistress_or_favorite'].filter(Boolean)],
|
||||
},
|
||||
},
|
||||
affection: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 50,
|
||||
validate: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
},
|
||||
},
|
||||
visibility: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 15,
|
||||
validate: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
},
|
||||
},
|
||||
discretion: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 50,
|
||||
validate: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
},
|
||||
},
|
||||
maintenanceLevel: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 50,
|
||||
validate: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
},
|
||||
},
|
||||
statusFit: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
validate: {
|
||||
min: -2,
|
||||
max: 2,
|
||||
},
|
||||
},
|
||||
monthlyBaseCost: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
validate: {
|
||||
min: 0,
|
||||
},
|
||||
},
|
||||
monthsUnderfunded: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
validate: {
|
||||
min: 0,
|
||||
},
|
||||
},
|
||||
active: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true,
|
||||
},
|
||||
acknowledged: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
},
|
||||
exclusiveFlag: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
},
|
||||
lastMonthlyProcessedAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
},
|
||||
lastDailyProcessedAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
},
|
||||
notesJson: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: true,
|
||||
},
|
||||
flagsJson: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
sequelize,
|
||||
modelName: 'RelationshipState',
|
||||
tableName: 'relationship_state',
|
||||
schema: 'falukant_data',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
}
|
||||
);
|
||||
|
||||
export default RelationshipState;
|
||||
@@ -6,8 +6,7 @@ class FalukantStock extends Model { }
|
||||
FalukantStock.init({
|
||||
branchId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
allowNull: false
|
||||
},
|
||||
stockTypeId: {
|
||||
type: DataTypes.INTEGER,
|
||||
|
||||
@@ -22,9 +22,8 @@ TownProductWorth.init({
|
||||
timestamps: false,
|
||||
underscored: true,
|
||||
hooks: {
|
||||
// Neu: 55–85 %; ältere Einträge können 40–60 % sein (Preislogik im Service deckelt nach unten ab).
|
||||
beforeCreate: (worthPercent) => {
|
||||
worthPercent.worthPercent = Math.floor(Math.random() * 31) + 55;
|
||||
worthPercent.worthPercent = Math.floor(Math.random() * 20) + 40;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -25,11 +25,6 @@ Transport.init(
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
guardCount: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
sequelize,
|
||||
@@ -43,3 +38,4 @@ Transport.init(
|
||||
|
||||
export default Transport;
|
||||
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Underground.init({
|
||||
allowNull: false},
|
||||
victimId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true},
|
||||
allowNull: false},
|
||||
parameters: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true},
|
||||
|
||||
@@ -24,35 +24,6 @@ UserHouse.init({
|
||||
allowNull: false,
|
||||
defaultValue: 100
|
||||
},
|
||||
servantCount: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
},
|
||||
servantQuality: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 50
|
||||
},
|
||||
servantPayLevel: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: false,
|
||||
defaultValue: 'normal'
|
||||
},
|
||||
householdOrder: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 55
|
||||
},
|
||||
householdTensionScore: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 10
|
||||
},
|
||||
householdTensionReasonsJson: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: true
|
||||
},
|
||||
houseTypeId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../../utils/sequelize.js';
|
||||
|
||||
/**
|
||||
* Preishistorie pro Produkt und Region (Zeitreihe für Preis-Graphen).
|
||||
* Aktuell wird diese Tabelle noch nicht befüllt; sie dient nur als Grundlage.
|
||||
*/
|
||||
class ProductPriceHistory extends Model { }
|
||||
|
||||
ProductPriceHistory.init({
|
||||
productId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false
|
||||
},
|
||||
regionId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false
|
||||
},
|
||||
price: {
|
||||
type: DataTypes.DECIMAL(12, 2),
|
||||
allowNull: false
|
||||
},
|
||||
recordedAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: sequelize.literal('CURRENT_TIMESTAMP')
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'ProductPriceHistory',
|
||||
tableName: 'product_price_history',
|
||||
schema: 'falukant_log',
|
||||
timestamps: false,
|
||||
underscored: true,
|
||||
indexes: [
|
||||
{
|
||||
name: 'product_price_history_product_region_recorded_idx',
|
||||
fields: ['product_id', 'region_id', 'recorded_at']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
export default ProductPriceHistory;
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../../utils/sequelize.js';
|
||||
|
||||
/**
|
||||
* Log aller Änderungen an relationship und marriage_proposals.
|
||||
* Einträge werden ausschließlich durch DB-Trigger geschrieben und nicht gelöscht.
|
||||
* Hilft zu analysieren, warum z.B. Werbungen um einen Partner verschwinden.
|
||||
*/
|
||||
class RelationshipChangeLog extends Model {}
|
||||
|
||||
RelationshipChangeLog.init(
|
||||
{
|
||||
changedAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW
|
||||
},
|
||||
tableName: {
|
||||
type: DataTypes.STRING(64),
|
||||
allowNull: false
|
||||
},
|
||||
operation: {
|
||||
type: DataTypes.STRING(16),
|
||||
allowNull: false
|
||||
},
|
||||
recordId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true
|
||||
},
|
||||
payloadOld: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: true
|
||||
},
|
||||
payloadNew: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: true
|
||||
}
|
||||
},
|
||||
{
|
||||
sequelize,
|
||||
modelName: 'RelationshipChangeLog',
|
||||
tableName: 'relationship_change_log',
|
||||
schema: 'falukant_log',
|
||||
timestamps: false,
|
||||
underscored: true
|
||||
}
|
||||
);
|
||||
|
||||
export default RelationshipChangeLog;
|
||||
@@ -1,35 +0,0 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../../utils/sequelize.js';
|
||||
|
||||
class ChurchOfficeRequirement extends Model {}
|
||||
|
||||
ChurchOfficeRequirement.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
officeTypeId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false
|
||||
},
|
||||
prerequisiteOfficeTypeId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: 'Erforderliche niedrigere Position in der Hierarchie'
|
||||
},
|
||||
minTitleLevel: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: 'Mindest-Titel-Level (optional)'
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'ChurchOfficeRequirement',
|
||||
tableName: 'church_office_requirement',
|
||||
schema: 'falukant_predefine',
|
||||
timestamps: false,
|
||||
underscored: true
|
||||
});
|
||||
|
||||
export default ChurchOfficeRequirement;
|
||||
@@ -10,14 +10,12 @@ PromotionalGiftCharacterTrait.init(
|
||||
giftId: {
|
||||
type: DataTypes.INTEGER,
|
||||
field: 'gift_id',
|
||||
allowNull: false,
|
||||
primaryKey: true
|
||||
allowNull: false
|
||||
},
|
||||
traitId: {
|
||||
type: DataTypes.INTEGER,
|
||||
field: 'trait_id',
|
||||
allowNull: false,
|
||||
primaryKey: true
|
||||
allowNull: false
|
||||
},
|
||||
suitability: {
|
||||
type: DataTypes.INTEGER,
|
||||
|
||||
@@ -10,14 +10,12 @@ PromotionalGiftMood.init(
|
||||
giftId: {
|
||||
type: DataTypes.INTEGER,
|
||||
field: 'gift_id',
|
||||
allowNull: false,
|
||||
primaryKey: true
|
||||
allowNull: false
|
||||
},
|
||||
moodId: {
|
||||
type: DataTypes.INTEGER,
|
||||
field: 'mood_id',
|
||||
allowNull: false,
|
||||
primaryKey: true
|
||||
allowNull: false
|
||||
},
|
||||
suitability: {
|
||||
type: DataTypes.INTEGER,
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../../utils/sequelize.js';
|
||||
|
||||
class ChurchOfficeType extends Model {}
|
||||
|
||||
ChurchOfficeType.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
seatsPerRegion: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false
|
||||
},
|
||||
regionType: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
hierarchyLevel: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: 'Höhere Zahl = höhere Position in der Hierarchie'
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'ChurchOfficeType',
|
||||
tableName: 'church_office_type',
|
||||
schema: 'falukant_type',
|
||||
timestamps: false,
|
||||
underscored: true
|
||||
});
|
||||
|
||||
export default ChurchOfficeType;
|
||||
@@ -15,8 +15,7 @@ ProductType.init({
|
||||
allowNull: false},
|
||||
sellCost: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false
|
||||
}
|
||||
allowNull: false}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'ProductType',
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../../utils/sequelize.js';
|
||||
|
||||
/**
|
||||
* Vorteile pro Stand (Adelstitel).
|
||||
* benefit_type: 'tax_share' | 'tax_exempt' | 'office_eligibility' | 'free_party_type' | 'reputation_bonus'
|
||||
* parameters: JSONB, z.B. { officeTypeNames: [] }, { partyTypeIds: [] }, { minPercent: 5, maxPercent: 15 }
|
||||
*/
|
||||
class TitleBenefit extends Model {}
|
||||
|
||||
TitleBenefit.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
titleId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
field: 'title_id'
|
||||
},
|
||||
benefitType: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
field: 'benefit_type'
|
||||
},
|
||||
parameters: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: true,
|
||||
defaultValue: {}
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'TitleBenefit',
|
||||
tableName: 'title_benefit',
|
||||
schema: 'falukant_type',
|
||||
timestamps: false,
|
||||
underscored: true
|
||||
});
|
||||
|
||||
export default TitleBenefit;
|
||||
@@ -4,10 +4,8 @@ import SettingsType from './type/settings.js';
|
||||
import UserParamValue from './type/user_param_value.js';
|
||||
import UserParamType from './type/user_param.js';
|
||||
import UserRightType from './type/user_right.js';
|
||||
import WidgetType from './type/widget_type.js';
|
||||
import User from './community/user.js';
|
||||
import UserParam from './community/user_param.js';
|
||||
import UserDashboard from './community/user_dashboard.js';
|
||||
import Login from './logs/login.js';
|
||||
import UserRight from './community/user_right.js';
|
||||
import InterestType from './type/interest.js';
|
||||
@@ -18,15 +16,11 @@ import UserParamVisibilityType from './type/user_param_visibility.js';
|
||||
import UserParamVisibility from './community/user_param_visibility.js';
|
||||
import Folder from './community/folder.js';
|
||||
import Image from './community/image.js';
|
||||
import EroticVideo from './community/erotic_video.js';
|
||||
import EroticContentReport from './community/erotic_content_report.js';
|
||||
import ImageVisibilityType from './type/image_visibility.js';
|
||||
import ImageVisibilityUser from './community/image_visibility_user.js';
|
||||
import FolderImageVisibility from './community/folder_image_visibility.js';
|
||||
import ImageImageVisibility from './community/image_image_visibility.js';
|
||||
import FolderVisibilityUser from './community/folder_visibility_user.js';
|
||||
import EroticVideoImageVisibility from './community/erotic_video_image_visibility.js';
|
||||
import EroticVideoVisibilityUser from './community/erotic_video_visibility_user.js';
|
||||
import GuestbookEntry from './community/guestbook.js';
|
||||
import DiaryHistory from './community/diary_history.js';
|
||||
import Diary from './community/diary.js';
|
||||
@@ -55,7 +49,6 @@ import ProductType from './falukant/type/product.js';
|
||||
import Knowledge from './falukant/data/product_knowledge.js';
|
||||
import TitleRequirement from './falukant/type/title_requirement.js';
|
||||
import TitleOfNobility from './falukant/type/title_of_nobility.js';
|
||||
import TitleBenefit from './falukant/type/title_benefit.js';
|
||||
import BranchType from './falukant/type/branch.js';
|
||||
import Branch from './falukant/data/branch.js';
|
||||
import Production from './falukant/data/production.js';
|
||||
@@ -71,7 +64,6 @@ import Notification from './falukant/log/notification.js';
|
||||
import MarriageProposal from './falukant/data/marriage_proposal.js';
|
||||
import RelationshipType from './falukant/type/relationship.js';
|
||||
import Relationship from './falukant/data/relationship.js';
|
||||
import RelationshipState from './falukant/data/relationship_state.js';
|
||||
import CharacterTrait from './falukant/type/character_trait.js';
|
||||
import FalukantCharacterTrait from './falukant/data/falukant_character_trait.js';
|
||||
import Mood from './falukant/type/mood.js';
|
||||
@@ -95,7 +87,6 @@ import Learning from './falukant/data/learning.js';
|
||||
import Credit from './falukant/data/credit.js';
|
||||
import DebtorsPrism from './falukant/data/debtors_prism.js';
|
||||
import HealthActivity from './falukant/log/health_activity.js';
|
||||
import ProductPriceHistory from './falukant/log/product_price_history.js';
|
||||
|
||||
// — Match3 Minigame —
|
||||
import Match3Campaign from './match3/campaign.js';
|
||||
@@ -122,13 +113,6 @@ import Vote from './falukant/data/vote.js';
|
||||
import ElectionResult from './falukant/data/election_result.js';
|
||||
import PoliticalOfficeHistory from './falukant/log/political_office_history.js';
|
||||
import ElectionHistory from './falukant/log/election_history.js';
|
||||
import RelationshipChangeLog from './falukant/log/relationship_change_log.js';
|
||||
|
||||
// — Kirchliche Ämter (Church) —
|
||||
import ChurchOfficeType from './falukant/type/church_office_type.js';
|
||||
import ChurchOfficeRequirement from './falukant/predefine/church_office_requirement.js';
|
||||
import ChurchOffice from './falukant/data/church_office.js';
|
||||
import ChurchApplication from './falukant/data/church_application.js';
|
||||
import UndergroundType from './falukant/type/underground.js';
|
||||
import Underground from './falukant/data/underground.js';
|
||||
import VehicleType from './falukant/type/vehicle.js';
|
||||
@@ -145,25 +129,13 @@ import ChatRight from './chat/rights.js';
|
||||
import ChatUserRight from './chat/user_rights.js';
|
||||
import RoomType from './chat/room_type.js';
|
||||
|
||||
// — Vocab Courses —
|
||||
import VocabCourse from './community/vocab_course.js';
|
||||
import VocabCourseLesson from './community/vocab_course_lesson.js';
|
||||
import VocabCourseEnrollment from './community/vocab_course_enrollment.js';
|
||||
import VocabCourseProgress from './community/vocab_course_progress.js';
|
||||
import VocabGrammarExerciseType from './community/vocab_grammar_exercise_type.js';
|
||||
import VocabGrammarExercise from './community/vocab_grammar_exercise.js';
|
||||
import VocabGrammarExerciseProgress from './community/vocab_grammar_exercise_progress.js';
|
||||
import CalendarEvent from './community/calendar_event.js';
|
||||
|
||||
const models = {
|
||||
SettingsType,
|
||||
UserParamValue,
|
||||
UserParamType,
|
||||
UserRightType,
|
||||
WidgetType,
|
||||
User,
|
||||
UserParam,
|
||||
UserDashboard,
|
||||
Login,
|
||||
UserRight,
|
||||
InterestType,
|
||||
@@ -174,15 +146,11 @@ const models = {
|
||||
UserParamVisibility,
|
||||
Folder,
|
||||
Image,
|
||||
EroticVideo,
|
||||
EroticContentReport,
|
||||
ImageVisibilityType,
|
||||
ImageVisibilityUser,
|
||||
FolderImageVisibility,
|
||||
ImageImageVisibility,
|
||||
FolderVisibilityUser,
|
||||
EroticVideoImageVisibility,
|
||||
EroticVideoVisibilityUser,
|
||||
GuestbookEntry,
|
||||
DiaryHistory,
|
||||
Diary,
|
||||
@@ -211,7 +179,6 @@ const models = {
|
||||
ProductType,
|
||||
Knowledge,
|
||||
TitleOfNobility,
|
||||
TitleBenefit,
|
||||
TitleRequirement,
|
||||
BranchType,
|
||||
Branch,
|
||||
@@ -228,7 +195,6 @@ const models = {
|
||||
MarriageProposal,
|
||||
RelationshipType,
|
||||
Relationship,
|
||||
RelationshipState,
|
||||
CharacterTrait,
|
||||
FalukantCharacterTrait,
|
||||
Mood,
|
||||
@@ -252,7 +218,6 @@ const models = {
|
||||
Credit,
|
||||
DebtorsPrism,
|
||||
HealthActivity,
|
||||
ProductPriceHistory,
|
||||
RegionDistance,
|
||||
VehicleType,
|
||||
Vehicle,
|
||||
@@ -268,11 +233,6 @@ const models = {
|
||||
ElectionResult,
|
||||
PoliticalOfficeHistory,
|
||||
ElectionHistory,
|
||||
RelationshipChangeLog,
|
||||
ChurchOfficeType,
|
||||
ChurchOfficeRequirement,
|
||||
ChurchOffice,
|
||||
ChurchApplication,
|
||||
UndergroundType,
|
||||
Underground,
|
||||
WeatherType,
|
||||
@@ -303,18 +263,6 @@ const models = {
|
||||
TaxiMapTileStreet,
|
||||
TaxiMapTileHouse,
|
||||
TaxiHighscore,
|
||||
|
||||
// Vocab Courses
|
||||
VocabCourse,
|
||||
VocabCourseLesson,
|
||||
VocabCourseEnrollment,
|
||||
VocabCourseProgress,
|
||||
VocabGrammarExerciseType,
|
||||
VocabGrammarExercise,
|
||||
VocabGrammarExerciseProgress,
|
||||
|
||||
// Calendar
|
||||
CalendarEvent,
|
||||
};
|
||||
|
||||
export default models;
|
||||
|
||||
@@ -9,13 +9,13 @@ const ContactMessage = sequelize.define('contact_message', {
|
||||
set(value) {
|
||||
if (value) {
|
||||
const encryptedValue = encrypt(value);
|
||||
this.setDataValue('email', encryptedValue);
|
||||
this.setDataValue('email', encryptedValue.toString('hex'));
|
||||
}
|
||||
},
|
||||
get() {
|
||||
const value = this.getDataValue('email');
|
||||
if (value) {
|
||||
return decrypt(value);
|
||||
return decrypt(Buffer.from(value, 'hex'));
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -25,13 +25,13 @@ const ContactMessage = sequelize.define('contact_message', {
|
||||
set(value) {
|
||||
if (value) {
|
||||
const encryptedValue = encrypt(value);
|
||||
this.setDataValue('message', encryptedValue);
|
||||
this.setDataValue('message', encryptedValue.toString('hex'));
|
||||
}
|
||||
},
|
||||
get() {
|
||||
const value = this.getDataValue('message');
|
||||
if (value) {
|
||||
return decrypt(value);
|
||||
return decrypt(Buffer.from(value, 'hex'));
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -41,13 +41,13 @@ const ContactMessage = sequelize.define('contact_message', {
|
||||
set(value) {
|
||||
if (value) {
|
||||
const encryptedValue = encrypt(value);
|
||||
this.setDataValue('name', encryptedValue);
|
||||
this.setDataValue('name', encryptedValue.toString('hex'));
|
||||
}
|
||||
},
|
||||
get() {
|
||||
const value = this.getDataValue('name');
|
||||
if (value) {
|
||||
return decrypt(value);
|
||||
return decrypt(Buffer.from(value, 'hex'));
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -67,13 +67,13 @@ const ContactMessage = sequelize.define('contact_message', {
|
||||
set(value) {
|
||||
if (value) {
|
||||
const encryptedValue = encrypt(value);
|
||||
this.setDataValue('answer', encryptedValue);
|
||||
this.setDataValue('answer', encryptedValue.toString('hex'));
|
||||
}
|
||||
},
|
||||
get() {
|
||||
const value = this.getDataValue('answer');
|
||||
if (value) {
|
||||
return decrypt(value);
|
||||
return decrypt(Buffer.from(value, 'hex'));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -350,16 +350,15 @@ export async function createTriggers() {
|
||||
SELECT * FROM random_fill
|
||||
),
|
||||
|
||||
-- 8) Neue Ämter anlegen – created_at = Wahldatum (Amtsbeginn), nicht NOW()
|
||||
-- damit termEnds = Amtsbeginn + termLength korrekt berechnet werden kann
|
||||
-- 8) Neue Ämter anlegen und sofort zurückliefern
|
||||
created_offices AS (
|
||||
INSERT INTO falukant_data.political_office
|
||||
(office_type_id, character_id, created_at, updated_at, region_id)
|
||||
SELECT
|
||||
tp.tp_office_type_id,
|
||||
fw.character_id,
|
||||
tp.tp_election_date AS created_at,
|
||||
tp.tp_election_date AS updated_at,
|
||||
NOW() AS created_at,
|
||||
NOW() AS updated_at,
|
||||
tp.tp_region_id
|
||||
FROM final_winners fw
|
||||
JOIN to_process tp
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import { sequelize } from '../../utils/sequelize.js';
|
||||
import { DataTypes } from 'sequelize';
|
||||
|
||||
const WidgetType = sequelize.define('widget_type', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
label: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
comment: 'Anzeigename des Widgets (z. B. "Termine")'
|
||||
},
|
||||
endpoint: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
comment: 'API-Pfad (z. B. "/api/termine")'
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
comment: 'Optionale Beschreibung'
|
||||
},
|
||||
orderId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
field: 'order_id',
|
||||
comment: 'Sortierreihenfolge'
|
||||
}
|
||||
}, {
|
||||
tableName: 'widget_type',
|
||||
schema: 'type',
|
||||
underscored: true,
|
||||
timestamps: false
|
||||
});
|
||||
|
||||
export default WidgetType;
|
||||
3221
backend/package-lock.json
generated
3221
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "backend",
|
||||
"version": "1.0.0",
|
||||
"description": "Nach Änderungen an dependencies: npm install ausführen und package-lock.json committen (npm ci im Deploy).",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
@@ -9,47 +9,37 @@
|
||||
"dev": "NODE_ENV=development node server.js",
|
||||
"start-daemon": "node daemonServer.js",
|
||||
"sync-db": "node sync-database.js",
|
||||
"sync-tables": "node sync-tables-only.js",
|
||||
"check-connections": "node check-connections.js",
|
||||
"cleanup-connections": "node cleanup-connections.js",
|
||||
"diag:town-worth": "QUIET_ENV_LOGS=1 DOTENV_CONFIG_QUIET=1 node scripts/falukant-town-product-worth-stats.mjs",
|
||||
"diag:moneyflow": "QUIET_ENV_LOGS=1 DOTENV_CONFIG_QUIET=1 node scripts/falukant-moneyflow-report.mjs",
|
||||
"lockfile:sync": "npm install --package-lock-only",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@gltf-transform/cli": "^4.3.0",
|
||||
"amqplib": "^0.10.9",
|
||||
"bcryptjs": "^3.0.3",
|
||||
"connect-redis": "^9.0.0",
|
||||
"cors": "^2.8.6",
|
||||
"amqplib": "^0.10.4",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"connect-redis": "^7.1.1",
|
||||
"cors": "^2.8.5",
|
||||
"date-fns": "^4.1.0",
|
||||
"dompurify": "^3.3.3",
|
||||
"dotenv": "^17.3.1",
|
||||
"express": "^5.2.1",
|
||||
"express-session": "^1.19.0",
|
||||
"i18n": "^0.15.3",
|
||||
"joi": "^18.0.2",
|
||||
"jsdom": "^29.0.1",
|
||||
"multer": "^2.1.1",
|
||||
"mysql2": "^3.20.0",
|
||||
"nodemailer": "^8.0.3",
|
||||
"pg": "^8.20.0",
|
||||
"dompurify": "^3.1.7",
|
||||
"dotenv": "^17.2.1",
|
||||
"express": "^4.19.2",
|
||||
"express-session": "^1.18.1",
|
||||
"i18n": "^0.15.1",
|
||||
"joi": "^17.13.3",
|
||||
"jsdom": "^26.1.0",
|
||||
"multer": "^2.0.0",
|
||||
"mysql2": "^3.10.3",
|
||||
"nodemailer": "^7.0.11",
|
||||
"pg": "^8.12.0",
|
||||
"pg-hstore": "^2.3.4",
|
||||
"redis": "^5.11.0",
|
||||
"sequelize": "^6.37.8",
|
||||
"sharp": "^0.34.5",
|
||||
"socket.io": "^4.8.3",
|
||||
"uuid": "^13.0.0",
|
||||
"ws": "^8.20.0"
|
||||
"redis": "^4.7.0",
|
||||
"sequelize": "^6.37.3",
|
||||
"sharp": "^0.34.3",
|
||||
"socket.io": "^4.7.5",
|
||||
"uuid": "^11.1.0",
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"sequelize-cli": "^6.6.5"
|
||||
},
|
||||
"overrides": {
|
||||
"minimatch": "10.2.4"
|
||||
"sequelize-cli": "^6.6.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,12 +19,6 @@ router.delete('/chat/rooms/:id', authenticate, adminController.deleteRoom);
|
||||
router.get('/users/search', authenticate, adminController.searchUsers);
|
||||
router.get('/users/statistics', authenticate, adminController.getUserStatistics);
|
||||
router.get('/users/batch', authenticate, adminController.getUsers);
|
||||
router.get('/users/adult-verification', authenticate, adminController.getAdultVerificationRequests);
|
||||
router.get('/users/:id/adult-verification/document', authenticate, adminController.getAdultVerificationDocument);
|
||||
router.put('/users/:id/adult-verification', authenticate, adminController.setAdultVerificationStatus);
|
||||
router.get('/users/erotic-moderation', authenticate, adminController.getEroticModerationReports);
|
||||
router.get('/users/erotic-moderation/preview/:type/:targetId', authenticate, adminController.getEroticModerationPreview);
|
||||
router.put('/users/erotic-moderation/:id', authenticate, adminController.applyEroticModerationAction);
|
||||
router.get('/users/:id', authenticate, adminController.getUser);
|
||||
router.put('/users/:id', authenticate, adminController.updateUser);
|
||||
|
||||
@@ -43,9 +37,6 @@ router.post('/contacts/answer', authenticate, adminController.answerContact);
|
||||
router.post('/falukant/searchuser', authenticate, adminController.searchUser);
|
||||
router.get('/falukant/getuser/:id', authenticate, adminController.getFalukantUserById);
|
||||
router.post('/falukant/edituser', authenticate, adminController.changeFalukantUser);
|
||||
router.post('/falukant/character/force-pregnancy', authenticate, adminController.adminForceFalukantPregnancy);
|
||||
router.post('/falukant/character/clear-pregnancy', authenticate, adminController.adminClearFalukantPregnancy);
|
||||
router.post('/falukant/character/force-birth', authenticate, adminController.adminForceFalukantBirth);
|
||||
router.get('/falukant/branches/:falukantUserId', authenticate, adminController.getFalukantUserBranches);
|
||||
router.put('/falukant/stock/:stockId', authenticate, adminController.updateFalukantStock);
|
||||
router.post('/falukant/stock', authenticate, adminController.addFalukantStock);
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import { Router } from 'express';
|
||||
import { authenticate } from '../middleware/authMiddleware.js';
|
||||
import calendarController from '../controllers/calendarController.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
// All routes require authentication
|
||||
router.get('/events', authenticate, calendarController.getEvents);
|
||||
router.get('/events/:id', authenticate, calendarController.getEvent);
|
||||
router.post('/events', authenticate, calendarController.createEvent);
|
||||
router.put('/events/:id', authenticate, calendarController.updateEvent);
|
||||
router.delete('/events/:id', authenticate, calendarController.deleteEvent);
|
||||
router.get('/birthdays', authenticate, calendarController.getFriendsBirthdays);
|
||||
|
||||
// Widget endpoints
|
||||
router.get('/widget/birthdays', authenticate, calendarController.getWidgetBirthdays);
|
||||
router.get('/widget/upcoming', authenticate, calendarController.getWidgetUpcoming);
|
||||
router.get('/widget/mini', authenticate, calendarController.getWidgetMiniCalendar);
|
||||
|
||||
export default router;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user