524 Commits

Author SHA1 Message Date
Torsten Schulz (local)
9d44a265ca Refactor backend CORS settings to include default origins and improve error handling in chat services: Introduce dynamic CORS origin handling, enhance RabbitMQ message sending with fallback mechanisms, and update WebSocket service to manage pending messages. Update UI components for better accessibility and responsiveness, including adjustments to dialog and navigation elements. Enhance styling for improved user experience across various components. 2026-03-19 14:44:04 +01:00
Torsten Schulz (local)
4442937ebd Enhance backend configuration and error handling: Update CORS settings to allow dynamic origins, improve RabbitMQ connection handling in chat services, and adjust API server host configuration. Refactor environment variables for better flexibility and add fallback mechanisms for WebSocket and chat services. Update frontend environment files for consistent API and WebSocket URLs. 2026-03-18 22:45:22 +01:00
Torsten Schulz (local)
59869e077e Update SEO and meta tags in index.html, enhance robots.txt for better crawling control, and improve sitemap.xml priorities. Refactor blog routes to include SEO metadata and adjust blog view for canonical URLs. Implement blog URL generation in BlogListView and apply SEO dynamically in BlogView. 2026-03-18 22:02:44 +01:00
Torsten Schulz (local)
971e09a72a Add Bisaya course content for 'Ort & Richtung' and 'Alltagsgespräche - Teil 2' lessons: Introduce multiple-choice, gap-fill, and transformation exercises focusing on location and everyday conversation vocabulary, complete with translations and explanations. 2026-03-05 13:28:08 +01:00
Torsten Schulz (local)
bf2b490731 Add Bisaya course content for 'Haus & Familie' lesson: Introduce multiple-choice and gap-fill exercises for vocabulary related to house and family terms, including translations and explanations. Update lesson tracking to include the new lesson in the course content. 2026-03-05 13:23:56 +01:00
Torsten Schulz (local)
fd41a53404 Implement password prompt UI and logic in MultiChatDialog: Add a password entry panel with validation and error handling for room access. Update i18n files for localized password prompts in English, German, and Spanish. 2026-03-04 23:34:55 +01:00
Torsten Schulz (local)
a48e907e50 Add password protection feature in MultiChatDialog: Implement room password management, including prompts for password entry and error handling for invalid passwords. Update i18n files with localized messages for password prompts in English, German, and Spanish. 2026-03-04 23:32:32 +01:00
Torsten Schulz (local)
a117bad342 Enhance room creation tracking in MultiChatDialog: Implement logic to confirm room creation success, manage pending room creation attempts, and clear tracking on dialog close. Update i18n files with new localized messages for room creation status. 2026-03-04 23:28:54 +01:00
Torsten Schulz (local)
190cf626f9 Add functionality for managing user-owned chat rooms: Implement getOwnRooms and deleteOwnRoom methods in ChatController and ChatService, add corresponding API routes in chatRouter, and enhance MultiChatDialog for displaying and deleting owned rooms with localized messages. Update i18n files for new features. 2026-03-04 23:22:16 +01:00
Torsten Schulz (local)
2bc34acacf Add room creation options endpoint and integrate with chat UI: Implement getRoomCreateOptions in ChatController and ChatService, add corresponding API route, and enhance MultiChatDialog for room creation with localized labels and validation. Update i18n files for new room creation features. 2026-03-04 23:12:54 +01:00
Torsten Schulz (local)
5f4acbea51 Remove publicFlag from room creation form in MultiChatDialog: Simplify visibility handling by directly using the visibility state to determine public/private status, enhancing clarity and reducing redundancy. 2026-03-04 22:59:08 +01:00
Torsten Schulz (local)
6d4ada7b31 Refactor getRaceLimit method in MultiChatDialog: Simplify logic by returning a fixed value of 1, ensuring consistent race limit handling. 2026-03-04 22:53:39 +01:00
Torsten Schulz (local)
1bccee3429 Update WebSocket connection handling in MultiChatDialog: Change raceLimit to 1 to prevent duplicate daemon sockets and ensure only one connection attempt in parallel. 2026-03-04 22:50:54 +01:00
Torsten Schulz (local)
947d3d0694 Add validation and error handling for room creation form in MultiChatDialog: Implement input validation for room name, age restrictions, password, and access rights. Enhance UI with error messages and disable button when validation fails. 2026-03-04 22:44:15 +01:00
Torsten Schulz (local)
e76fdbe1ab Implement room creation panel in MultiChatDialog: Add functionality for users to create new chat rooms with customizable settings, including visibility, age restrictions, and password protection. Enhance UI with a toggle button and form for room details. 2026-03-04 22:42:48 +01:00
Torsten Schulz (local)
db8be34607 Update room management in AdminController: Modify updateRoom and deleteRoom methods to include userId as a parameter for improved access control. 2026-03-04 22:38:24 +01:00
Torsten Schulz (local)
407c3b359b Update chat configuration and remove MultiChat component: Change chat port to 1236 in chatBridge.json, update WebSocket URL in .env.local, and delete the MultiChat.vue component to streamline chat functionality. 2026-03-04 17:24:15 +01:00
Torsten Schulz (local)
a2652c983f Füge neue Funktionen zur Verwaltung von Erben hinzu: Implementiere die API-Endpunkte zum Abrufen potenzieller Erben und zum Auswählen eines Erben. Ergänze die Logik in FalukantService zur Verarbeitung dieser Funktionen. 2026-03-02 00:36:43 +01:00
Torsten Schulz (local)
42fe568e2b Verbessere die Behandlung von Charaktereigenschaften beim Versenden von Geschenken: Füge eine Überprüfung hinzu, um sicherzustellen, dass characterTraits ein Array ist, und behandle fehlende Traits als neutralen Wert. 2026-02-14 16:48:23 +01:00
Torsten Schulz (local)
ea7f8d1acc Verbessere die Sicherheitsüberprüfung der Benutzermerkmale in der Geschenksuche: Füge eine sichere Trait-Filterung hinzu, um Fehler bei undefinierten Eigenschaften zu vermeiden. 2026-02-14 16:44:51 +01:00
Torsten Schulz (local)
af4e5de1ad Normalisiere eingehende API-Daten: Akzeptiere sowohl camelCase als auch snake_case für die Eigenschaften des Falukant-Datenobjekts. 2026-02-14 16:41:14 +01:00
Torsten Schulz (local)
cc80081280 Passe die Schlüssel in den Arrays für Stimmungen und Charaktere an snake_case an 2026-02-14 16:38:57 +01:00
Torsten Schulz (local)
444a1b9dcc Verbessere die Handhabung des Ladens von .env-Dateien: Füge Lesbarkeitsprüfung für Produktions-.env hinzu und verbessere Fehlerbehandlung beim Laden. 2026-02-14 16:22:22 +01:00
Torsten Schulz (local)
91637ba7a3 Füge Validierung für Geschenke in FalukantService hinzu und erstelle Skripte zur Reparatur ungültiger Werte in PromotionalGift 2026-02-14 16:19:31 +01:00
Torsten Schulz (local)
be7db6ad96 Verbessere die Berechnung der Geschenkekosten in FalukantService und füge Tests für die Funktion hinzu 2026-02-14 16:12:07 +01:00
Torsten Schulz (local)
a3b550859c Korrigiere Zuweisung von Beziehungen und verbessere Trait-IDs-Verarbeitung in FalukantService 2026-02-14 15:58:01 +01:00
Torsten Schulz (local)
c58f8c0bf8 Entferne Debug-Logs für Alters- und Geschlechtsbezeichnungen in FalukantWidget 2026-02-09 17:29:10 +01:00
Torsten Schulz (local)
73304e8af4 Verbessere die Handhabung von Altersgruppen in FalukantWidget: Lese Rohwerte direkt aus i18n-Nachrichten, um Plural/Choice-Format zu vermeiden. 2026-02-09 17:26:36 +01:00
Torsten Schulz (local)
e21c61b5e3 Füge Debug-Logs für Alters- und Geschlechtsbezeichnungen in FalukantWidget hinzu 2026-02-09 17:18:44 +01:00
Torsten Schulz (local)
78a44b5189 Füge Altersgruppenübersetzungen in genderAge hinzu: Ergänze männliche, weibliche und neutrale Bezeichnungen für verschiedene Altersgruppen. 2026-02-09 17:12:49 +01:00
Torsten Schulz (local)
da1d912bdb Korrigiere Altersgruppenlogik in FalukantWidget: Überarbeite die Altersberechnung und passe die Kommentare für Klarheit an. 2026-02-09 17:06:30 +01:00
Torsten Schulz (local)
c45a843611 Aktualisiere die Altersberechnung in FalukantWidget: Ändere die Umrechnung von Tagen in Jahre und runde auf das nächste Jahr für eine erwartbare Anzeige. 2026-02-09 17:02:41 +01:00
Torsten Schulz (local)
b07099b57d Refactor code structure for improved readability and maintainability 2026-02-09 16:50:25 +01:00
Torsten Schulz (local)
a7688e4ed5 Revert "Refactor DirectorInfo and SaleSection components to unify speedLabel logic and remove unnecessary watch properties"
This reverts commit 8c40144734.
2026-02-09 15:56:48 +01:00
Torsten Schulz (local)
9c91d99bed Add Spanish locale files and initial translations 2026-02-09 15:55:04 +01:00
Torsten Schulz (local)
a1ea192a73 Update gender age labels in locale files: Adjusted age group definitions and refined translations for gender terms in both German and English locale files. 2026-02-09 15:21:41 +01:00
Torsten Schulz (local)
52fb1ec183 Implement age-based gender labels in FalukantWidget and update translations: Added logic to determine gender labels based on age groups and updated locale files for German and English. 2026-02-09 15:16:51 +01:00
Torsten Schulz (local)
b51d396afc Enhance FalukantWidget gender label logic and add age-based labels: Updated gender label computation to include age-based distinctions for children and adults. Added new translation key for "years" in both German and English locale files. 2026-02-09 15:12:23 +01:00
Torsten Schulz (local)
c8d8254fc1 Add dashboard widget endpoint: Implemented getDashboardWidget method in FalukantService and updated router to provide compact user data for the dashboard widget, ensuring frontend compatibility. 2026-02-09 15:05:17 +01:00
Torsten Schulz (local)
83455f1e83 Add legacy endpoint for dashboard widget: Implemented /dashboard-widget route to provide data expected by the Dashboard-Falukant widget, ensuring compatibility with frontend requirements. 2026-02-09 13:00:59 +01:00
Torsten Schulz (local)
c779be2897 Add error handling for nobility advancement in NobilityView: Implemented specific error messages for cooldown and unmet requirements, enhancing user feedback during advancement attempts. 2026-02-09 12:03:59 +01:00
Torsten Schulz (local)
8c40144734 Refactor DirectorInfo and SaleSection components to unify speedLabel logic and remove unnecessary watch properties
- Simplified speedLabel method in DirectorInfo.vue and SaleSection.vue to handle null values and translations more efficiently.
- Removed the watch property for branchId in DirectorInfo.vue as it was not needed.
- Cleaned up the code by eliminating redundant checks and improving readability.

Update translations in falukant.json files

- Removed unused keys and cleaned up the structure in both German and English translation files.
- Ensured that all necessary translations are still present while removing obsolete entries.

Refactor BranchView and PoliticsView components for improved performance and clarity

- Removed caching logic for product prices in BranchView.vue to simplify the loading process.
- Streamlined the loadCurrentPositions method in PoliticsView.vue by eliminating unnecessary character ID checks and logging.
- Enhanced the user experience by ensuring that the application submission process is clearer and more efficient.

Clean up MoneyHistoryView and FamilyView components

- Removed the graph section from MoneyHistoryView.vue to simplify the UI.
- Adjusted the mood display logic in FamilyView.vue to ensure proper translation handling.
2026-02-09 11:44:26 +01:00
Torsten Schulz (local)
022cd47e7e Enhance VocabService and VocabLessonView for improved vocabulary review: Updated VocabService to load previous lesson vocabulary for both review and mixed training modes. Added new computed properties and methods in VocabLessonView to manage vocabulary from previous lessons, including a mixed pool for enhanced training. Updated UI to reflect current lesson and mixed review status in the vocabulary trainer. 2026-02-09 11:17:04 +01:00
Torsten Schulz (local)
c7a05c3213 Add TitleBenefit model and integrate benefits into FalukantService: Introduced TitleBenefit for managing title-related advantages, including tax exemptions and office eligibility. Updated service methods to utilize these benefits for character reputation, tax distribution, and allowed office types, enhancing gameplay mechanics and user experience. 2026-02-06 09:50:31 +01:00
Torsten Schulz (local)
a60c6d173c Add knowledge retrieval methods in FalukantService: Implemented _resolveLearningCharacterId to determine character IDs based on user input, and added getKnowledgeSingle and getKnowledgeForAll methods to fetch knowledge data for specific characters. Enhanced error handling for missing character IDs and improved knowledge aggregation logic. 2026-02-06 07:44:49 +01:00
Torsten Schulz (local)
971da3e57c Refactor director data processing in FalukantService: Improved handling of null character references and added default values for character properties. This enhances data integrity and prevents potential runtime errors when processing director information. 2026-02-06 00:16:55 +01:00
Torsten Schulz (local)
2f29f43409 Refactor FalukantService and update UI components: Removed debug logging from FalukantService, added age requirement messages in i18n files, and improved age validation display in PoliticsView. This enhances user experience and code clarity regarding age eligibility for political applications. 2026-02-06 00:14:16 +01:00
Torsten Schulz (local)
56c38c04aa Refactor politics routes and service: Removed duplicate route for open politics in falukantRouter, updated falukantService to enhance logging for age validation in political applications, and ensured proper handling of open politics data in PoliticsView. This improves code clarity and debugging capabilities. 2026-02-06 00:04:49 +01:00
Torsten Schulz (local)
6b96ee9856 Implement age validation for political applications in FalukantService: Added MIN_AGE_POLITICS_DAYS constant and logic to check user age before allowing applications. Updated PoliticsView to reflect age eligibility and display appropriate hints. Enhanced error handling for age-related restrictions. 2026-02-06 00:00:11 +01:00
Torsten Schulz (local)
23b0c45518 Add executeReputationAction method in FalukantService: Implemented functionality to handle reputation actions for users, including daily limits, cooldowns, and cost checks. This update enhances user interaction with the reputation system and ensures proper transaction management. 2026-02-05 23:57:28 +01:00
Torsten Schulz (local)
10649d9fbf Add multiple-choice and gap-fill exercises for everyday conversations in Bisaya: Introduced new exercises focusing on common phrases and their meanings, enhancing language learning resources. Updated lesson duration for 'Alltagsgespräche - Teil 1' to 25 minutes for better engagement. 2026-02-05 23:42:17 +01:00
Torsten Schulz (local)
6a6cd7b910 Enhance OverviewView component: Added daemonSocket handling for WebSocket events, improved event processing, and removed deprecated production_ready event handling. This update ensures better responsiveness to socket messages and maintains data integrity in the UI. 2026-02-05 23:37:17 +01:00
Torsten Schulz (local)
2958d38c63 Update z-index values in DialogWidget and OverviewView components to improve layout stacking context and ensure proper element visibility. 2026-02-05 10:46:16 +01:00
Torsten Schulz (local)
aecd9a8245 Update z-index values in DialogWidget and StatusBar components, and remove negative z-index in OverviewView component for improved layout and stacking context. 2026-02-05 08:41:15 +01:00
Torsten Schulz (local)
4f3439e835 Update syncDatabase function to remove setupAssociations call: Added comments to clarify that associations should be set by the caller to prevent AssociationError during synchronization. 2026-02-05 08:25:38 +01:00
Torsten Schulz (local)
a5bec5baf7 Update CSS styles in OverviewView component: Added relative positioning and z-index to enhance layout control and ensure proper stacking of elements. 2026-02-05 08:19:29 +01:00
Torsten Schulz (local)
8d23453371 Add watcher for branchId in DirectorInfo component: Implemented a watcher to trigger loadDirector method when branchId changes, enhancing data responsiveness in the UI. 2026-02-04 15:44:16 +01:00
Torsten Schulz (local)
2184c4a7e1 Import ProductPriceHistory model in associations.js to establish necessary relationships for product pricing data management. 2026-02-04 15:37:41 +01:00
Torsten Schulz (local)
ba5e36fa55 Initialize model associations in server setup: Import and invoke setupAssociations to ensure all models share the same instance, preventing EagerLoadingError and enhancing data integrity before loading the app and services. 2026-02-04 15:27:46 +01:00
Torsten Schulz (local)
70d1d48fbc Refactor vehicle loading in FalukantService: Update logic to load vehicle types and transports using separate queries, improving performance and preventing EagerLoadingError. Simplify data retrieval by mapping vehicle types and organizing transports by vehicle ID, enhancing code clarity and efficiency. 2026-02-04 15:20:40 +01:00
Torsten Schulz (local)
d23026121e Refactor child character loading in FalukantService: Update logic to load child relations using separate queries, improving performance and preventing EagerLoadingError. Simplify data retrieval by eliminating unnecessary eager loading and enhancing clarity in the code structure. 2026-02-04 15:16:05 +01:00
Torsten Schulz (local)
057b038fac Refactor relationship loading in FalukantService: Update logic to load relationships without eager loading, preventing EagerLoadingError. Enhance data retrieval by using separate queries for traits and moods, improving performance and reliability. 2026-02-04 15:12:36 +01:00
Torsten Schulz (local)
0697f3d363 Refactor trait and mood field names in FalukantService: Update database query conditions to use camelCase for traitId and moodId, ensuring consistency with model definitions and improving code readability. 2026-02-04 15:07:05 +01:00
Torsten Schulz (local)
400d44289c Update primary key constraints in promotional gift models: Set giftId and traitId as primary keys in PromotionalGiftCharacterTrait and PromotionalGiftMood models to ensure data integrity. Enhance FalukantService to load character traits and user house data with separate queries, improving performance and preventing EagerLoadingError. 2026-02-04 15:05:19 +01:00
Torsten Schulz (local)
bbc3354f16 Refactor user house retrieval in FalukantService: Update logic to check for user ID before querying UserHouse, preventing unnecessary database calls and improving error handling. Maintain separate queries for UserHouse and relationships to avoid EagerLoadingError. 2026-02-04 14:20:22 +01:00
Torsten Schulz (local)
d038d72cde Refactor user and relationship data retrieval in FalukantService: Update loading logic to use separate queries for UserHouse and relationships, improving reliability and preventing EagerLoadingError. Enhance heir selection UI with new translations in German and English, providing better user guidance during character selection. 2026-02-04 14:12:25 +01:00
Torsten Schulz (local)
16e54d20d0 Refactor user and relationship loading in FalukantService: Replace eager loading of UserHouse and relationships with separate queries to prevent EagerLoadingError. This change enhances data retrieval reliability and maintains performance during user-related operations. 2026-02-04 14:01:28 +01:00
Torsten Schulz (local)
14775eb556 Add product price history model and database schema: Implement associations for ProductPriceHistory with ProductType and RegionData. Update FalukantService to utilize active character for user-related operations. Ensure product price history table exists in the database with appropriate structure and indexing. 2026-02-04 09:02:51 +01:00
Torsten Schulz (local)
ce34bae16a Enhance Bisaya course content creation: Update SQL query to include owner user ID and improve exercise handling logic. Implement checks for existing exercises and placeholder replacements for review lessons, ensuring better management of lesson content and user feedback. 2026-02-02 09:58:35 +01:00
Torsten Schulz (local)
640cdcf671 Add new exercises and vocabulary tests for Bisaya language course: Introduce multiple-choice questions for Week 1, covering greetings, family terms, and common phrases. Enhance learning materials with explanations for each question to aid understanding. 2026-02-02 09:47:59 +01:00
Torsten Schulz (local)
f15924c0be Adjust character positioning in OverviewView: Change the bottom offset of the character foreground from 10px to -15px to improve visual alignment within the user interface. 2026-02-02 09:35:16 +01:00
Torsten Schulz (local)
0d32c5b4b3 Update character styling in OverviewView: Adjust character positioning and dimensions by changing the bottom offset to 10px and increasing width and height to 55%. This enhances the visual representation of characters in the user interface. 2026-02-02 09:03:33 +01:00
Torsten Schulz (local)
101050ce58 Add avatar display logic in OverviewView: Introduce dynamic avatar styling based on user gender and age, enhancing character representation. Implement age group categorization for precise avatar positioning and dimensions, improving visual consistency in the user interface. 2026-02-02 08:53:47 +01:00
Torsten Schulz (local)
b16249e7c2 Implement noBackground prop in Character3D component: Add functionality to conditionally render the background and background image based on the noBackground prop. Update OverviewView to utilize the new prop, enhancing the character display logic and improving visual customization options. 2026-02-02 08:46:12 +01:00
Torsten Schulz (local)
8b63344bc2 Enhance proposal generation logic in FalukantService: Refactor character selection process to include region and age filters, improving the randomness and relevance of director character proposals. Implement fallback mechanism for character retrieval to ensure availability, enhancing overall proposal generation reliability. 2026-02-02 08:24:28 +01:00
Torsten Schulz (local)
b648175205 Refactor trigger creation logic: Update the created_at and updated_at fields to use the election date instead of the current timestamp, ensuring accurate term end calculations. Enhance socket event handling in StatusBar component by implementing setup and teardown methods for improved event management and user notifications. 2026-02-02 07:48:53 +01:00
Torsten Schulz (local)
4bf1bc35ae Enhance supervisor office validation in falukantService: Update error handling to provide clearer messages when no supervisor position exists or when the position is vacant. This change improves user feedback during application processes by ensuring users understand the requirements for supervisor office fulfillment. 2026-01-31 00:23:20 +01:00
Torsten Schulz (local)
067273d428 Refactor event handling in CalendarView: Simplify event update and creation logic by removing unnecessary response handling. Implement event reloading after save and delete operations to ensure a clean state. This enhancement improves the reliability of event management in the calendar. 2026-01-30 15:43:28 +01:00
Torsten Schulz (local)
7ed284d74b Add widget functionality for birthdays, upcoming events, and mini calendar: Implement new API endpoints in calendarController and calendarService to retrieve upcoming birthdays and events, as well as mini calendar data. Update calendarRouter to include widget routes and enhance DashboardWidget to dynamically render new widget components. This update improves user experience by providing quick access to important calendar information. 2026-01-30 15:14:37 +01:00
Torsten Schulz (local)
f65d3385ec Add friends' birthdays feature: Implement API endpoint to retrieve friends' birthdays for a specified year, enhance calendar service to handle visibility checks, and update CalendarView to display birthday events with distinct styling. This update improves user experience by allowing users to view important dates of their friends. 2026-01-30 14:59:32 +01:00
Torsten Schulz (local)
7635355e94 Add all-day event functionality to CalendarView: Introduce a new section for displaying all-day events in both week and day views. Update localization files to include translations for 'All Day' in English and German. Enhance event handling methods to support all-day events, improving the overall calendar experience. 2026-01-30 14:43:07 +01:00
Torsten Schulz (local)
ec75c7ecdb Improve date handling in CalendarView: Add comments to clarify the logic for setting the date to the first of the month to prevent overflow issues when navigating between months. This enhancement aids in maintaining accurate date transitions in the calendar view. 2026-01-30 14:41:02 +01:00
Torsten Schulz (local)
786420d1d2 Refactor date handling in CalendarView: Update date formatting to use local timezone instead of UTC, enhancing accuracy in date representation. Introduce a new helper method for consistent date formatting across the component. This change improves the overall user experience by ensuring dates are displayed correctly. 2026-01-30 14:35:08 +01:00
Torsten Schulz (local)
cff0ce1e1a Add calendar feature: Integrate calendarRouter and CalendarEvent model, enhance CalendarView with API interactions for event management, and update localization files for error handling in both English and German. This update improves the calendar functionality and user experience. 2026-01-30 14:29:11 +01:00
Torsten Schulz (local)
8355f985cd Update calendar localization and enhance CalendarView: Add new entries, edit options, selection info, and form fields in both English and German localization files. Improve CalendarView component with new event dialog and selection handling for a better user experience. 2026-01-30 14:17:04 +01:00
Torsten Schulz (local)
25af538c88 Enhance calendar functionality in personal section: Implement detailed calendar views (month, week, workweek, day) in CalendarView component. Update localization files for English and German to include translations for calendar features such as views, weekdays, and months. This update improves user experience by providing a comprehensive calendar interface. 2026-01-30 14:07:28 +01:00
Torsten Schulz (local)
d1503cd813 Implement personal section in navigation: Add a new 'personal' menu item with sub-items for language learning and calendar. Update localization files for English and German to include translations for the new section. Integrate personal routes into the router configuration for navigation. 2026-01-30 13:53:27 +01:00
Torsten Schulz (local)
7d2a33b3ec Refactor DashboardWidget to use dynamic widget components: Replace static slot content with a dynamic component rendering based on the endpoint prop. This change simplifies the widget structure and enhances flexibility by allowing different widget types to be displayed. Additionally, update error handling to provide more specific error messages. 2026-01-30 13:42:22 +01:00
Torsten Schulz (local)
752686e3e1 Remove unused drag-and-drop indicators in LoggedInView: Eliminate placeholder elements and associated styles for drop zones to streamline the widget rearrangement interface. This change simplifies the code and enhances clarity in the drag-and-drop functionality. 2026-01-30 13:38:35 +01:00
Torsten Schulz (local)
3870f34ef8 Refactor age display logic in DashboardWidget: Simplify age representation to always show years, removing the conditional check for days. This change streamlines the age display functionality. 2026-01-30 13:34:02 +01:00
Torsten Schulz (local)
ae71a066c7 Enhance drag-and-drop functionality in LoggedInView: Update event handling to include event parameters for improved debugging and clarity. Add console logs to track drag-and-drop actions, enhancing the user experience during widget rearrangement. 2026-01-30 13:29:01 +01:00
Torsten Schulz (local)
b52327db2e Refine drag-and-drop functionality in LoggedInView: Simplify event handling for drag-and-drop actions by removing unnecessary parameters and updating placeholder comments for clarity. This change aims to enhance user experience during widget rearrangement. 2026-01-30 13:15:03 +01:00
Torsten Schulz (local)
d5c089e07e Refine drag-and-drop behavior in LoggedInView: Update drop event handling to prevent event propagation and improve widget insertion logic. Adjust drop target determination to enhance user experience during widget rearrangement. 2026-01-30 12:03:12 +01:00
Torsten Schulz (local)
0f78c624b1 Improve drag-and-drop indicators in LoggedInView: Enhance visual cues for widget insertion by updating placeholder comments and adding event handlers for drag-and-drop actions. This update aims to provide clearer instructions for users during widget rearrangement. 2026-01-30 11:44:08 +01:00
Torsten Schulz (local)
e1632c41c2 Enhance drag-and-drop experience in DashboardWidget: Implement a visual drag ghost image during widget dragging for improved user feedback. Adjust dragging state styles, including opacity and border, to better indicate active dragging. This update aims to refine the overall interaction and visual cues during widget rearrangement. 2026-01-30 11:13:51 +01:00
Torsten Schulz (local)
323b051355 Refine DashboardWidget and LoggedInView styles: Adjust transition effects and dragging state in DashboardWidget for improved visual feedback. Update grid row height in LoggedInView for better responsiveness and enhance drop indicator styling for a more polished user experience. 2026-01-30 10:44:46 +01:00
Torsten Schulz (local)
3999b17e88 Enhance drag-and-drop functionality in Dashboard: Update styles for dragging state in DashboardWidget, including opacity and box-shadow adjustments. Improve LoggedInView by adding drop indicators for better user experience during widget rearrangement. Refactor drag-and-drop logic to maintain visual cues and ensure smoother interactions. 2026-01-30 10:30:07 +01:00
Torsten Schulz (local)
8fd15614af Update age representation in DashboardWidget: Modify falukantAgeLabel computed property to display age in days if less than a year, otherwise show in years. Add localization for "days" in both German and English language files to support the new age format. 2026-01-30 10:05:30 +01:00
Torsten Schulz (local)
ddefc2737b Adjust dashboard grid row height in LoggedInView for improved layout: Change grid-auto-rows from 420px to 200px to enhance responsiveness and better accommodate dynamic content. 2026-01-30 09:54:55 +01:00
Torsten Schulz (local)
05868d8a09 Update DashboardWidget and LoggedInView styles: Adjust min-height and max-height properties for better layout flexibility. Enhance dashboard grid cell styling to support dynamic content display and improve overall UI responsiveness. 2026-01-30 09:26:00 +01:00
Torsten Schulz (local)
b3afb988a3 Improve AppNavigation item handling: Update the logic to only expand items with non-empty children, enhancing user experience by preventing unnecessary interactions on items without submenus. 2026-01-30 09:05:55 +01:00
Torsten Schulz (local)
3b8e0573f2 Enhance DashboardWidget functionality: Integrate Vuex state management for socket connections, enabling real-time updates for Falukant widgets. Refactor computed properties and methods to handle socket events and improve data fetching logic. Update localization for age representation and adjust styles for better UI presentation. 2026-01-30 08:18:50 +01:00
Torsten Schulz (local)
4779a6e4af Refactor dashboard widget management: Update dashboardService to handle user-specific widget configurations with create and update logic. Enhance LoggedInView to support adding the same widget type and display error messages for save operations. Ensure effective endpoint handling for widgets and improve UI interactions. 2026-01-30 07:31:38 +01:00
Torsten Schulz (local)
39ac149430 Enhance character name display in DashboardWidget: Refactor character name construction logic in FalukantService to include title handling and create a new computed property for display name in DashboardWidget. Update styles for gender display and ensure proper localization for age representation. 2026-01-29 17:28:58 +01:00
Torsten Schulz (local)
8ec7db031b Refactor DashboardWidget and LoggedInView: Update DashboardWidget to display a single news article instead of a list, enhancing user experience. Remove logout button and related functionality from LoggedInView to streamline the interface. 2026-01-29 17:26:43 +01:00
Torsten Schulz (local)
25b5b91a19 Fix conditional rendering in DashboardWidget: Change v-else-if to v-if for newsDataResults to ensure proper display of news articles when data is available. 2026-01-29 17:22:53 +01:00
Torsten Schulz (local)
e8c6f6ffb9 Add news widget functionality: Integrate newsRouter for fetching news data, update initializeWidgetTypes to include news endpoint, and enhance DashboardWidget component to display news articles with pagination support. Update LoggedInView to manage widget request counters for unique endpoint handling. 2026-01-29 17:20:06 +01:00
Torsten Schulz (local)
62d8cd7b05 Add dashboard widget functionality: Implement getDashboardWidget method in FalukantService to retrieve compact user data for the dashboard. Update FalukantController and router to expose the new endpoint, and enhance DashboardWidget component to display user-specific information including character name, gender, age, money, unread notifications, and children count. 2026-01-29 17:03:32 +01:00
Torsten Schulz (local)
c09159d6ce Add widget management functionality: Implement getAvailableWidgets method in dashboardService to retrieve widget types, and create corresponding API endpoint in dashboardRouter. Update LoggedInView to allow users to select and add widgets dynamically, enhancing dashboard customization options. 2026-01-29 16:57:12 +01:00
Torsten Schulz (local)
8d2db95540 Add dashboard functionality: Integrate dashboardRouter and UserDashboard model, enabling user-specific dashboard configurations. Update LoggedInView to support dynamic widget management, including adding, removing, and saving widget configurations, enhancing user experience and interactivity. 2026-01-29 16:52:54 +01:00
Torsten Schulz (local)
9519846489 Refactor inventory handling in FalukantService: Update product transport logic to retrieve all stocks for a branch, improving inventory checks and ensuring accurate transport cost calculations. Implement locking during inventory updates to maintain data integrity during transactions. 2026-01-29 16:44:02 +01:00
Torsten Schulz (local)
f7a977df33 Enhance FalukantService with character caching and optimized city retrieval: Introduce caching for cities with branch types to reduce database queries, and streamline character retrieval logic. Update product and knowledge fetching to improve performance and maintainability. 2026-01-29 16:04:43 +01:00
Torsten Schulz (local)
f1717920b6 Add batch processing for product price retrieval: Implement getProductPricesInCitiesBatch method in FalukantService for handling multiple product price requests in a single API call. Update FalukantController and router to support new endpoint, and refactor RevenueSection and SaleSection components to utilize batch processing for improved performance and reduced API calls. 2026-01-29 15:58:31 +01:00
Torsten Schulz (local)
c5ab17ad99 Refactor FalukantService and SaleSection components: Optimize product and knowledge retrieval by using Promise.all for concurrent database queries, improving performance. Additionally, reorganize the speedLabel method for better readability and maintainability, ensuring accurate localization handling for transport speed values. 2026-01-29 15:20:38 +01:00
Torsten Schulz (local)
1839c3c57b Refactor price loading logic in RevenueSection and SaleSection components: Replace for-loops with Promise.all for concurrent API requests, improving performance and responsiveness. This change enhances the handling of loading states and ensures better management of price data retrieval. 2026-01-29 15:12:13 +01:00
Torsten Schulz (local)
ba63b3504f Refactor SaleSection component: Introduce methods section to organize component logic, enhancing readability and maintainability. This change improves the structure of the component by clearly separating computed properties and methods. 2026-01-29 15:11:16 +01:00
Torsten Schulz (local)
032e336b65 Add synchronous price calculation method: Introduce calcRegionalSellPriceSync for improved performance in price calculations when worthPercent is known. Refactor getAllProductPricesInRegion to utilize this new method, enhancing efficiency by reducing database calls. Update BranchView to manage product prices cache with regionId for better data handling. 2026-01-29 15:06:38 +01:00
Torsten Schulz (local)
474e46837a Refactor speedLabel method across components: Enhance localization handling for speed values by adding support for object types and improving fallback logic. This change ensures more accurate translations and better user experience in displaying transport speed information. 2026-01-29 14:22:25 +01:00
Torsten Schulz (local)
e7052636ba Remove unused logging for queries in check-knowledge-pkey.js to streamline output and enhance clarity in database performance diagnostics. 2026-01-29 14:06:43 +01:00
Torsten Schulz (local)
cb2631061e Enhance database performance diagnostics: Add detailed logging for unused primary key index and implement error handling for query statistics retrieval. Additionally, automate ANALYZE execution for affected tables after index creation to ensure PostgreSQL optimizes query performance. This improves clarity on index usage and enhances overall database performance management. 2026-01-29 14:05:24 +01:00
Torsten Schulz (local)
d1ddfe7d31 Refactor database performance diagnostics: Update connection pool diagnostics in diagnose-db-performance.js to improve error handling and enhance console output clarity. This change ensures more accurate user feedback regarding database connection pooling. 2026-01-29 14:03:15 +01:00
Torsten Schulz (local)
59cad22183 Refactor connection pool diagnostics in diagnose-db-performance.js: Update pool configuration retrieval to use Sequelize config, improve error handling, and enhance console output for better clarity on pool status. This change ensures more accurate diagnostics and user feedback regarding database connection pooling. 2026-01-29 13:56:46 +01:00
Torsten Schulz (local)
57d64a7ef8 Refactor database performance diagnostics: Update SQL queries in diagnose-db-performance.js to use 'relname' instead of 'tablename' for improved accuracy in table and index size calculations. This change enhances the clarity and correctness of performance diagnostics. 2026-01-29 13:55:07 +01:00
Torsten Schulz (local)
ae096eb4c3 Update StatusBar component: Refactor rendering logic for status items, improve icon and label display, and enhance CSS for better alignment and styling. This change aims to boost visual clarity and overall user experience in the status bar. 2026-01-29 13:29:42 +01:00
Torsten Schulz (local)
789861999c Refactor StatusBar component: Improve status item rendering by enhancing icon and label display, and update CSS for better alignment and styling. This change enhances the visual clarity and user experience of the status bar. 2026-01-29 11:58:15 +01:00
Torsten Schulz (local)
72f4bd066d Enhance MoneyHistoryGraphDialog: Add axis labels for better data visualization, improve graph scaling logic, and implement money and date formatting methods. Update localization files to include "yesterday" translations in German and English, enhancing user experience and clarity in financial data representation. 2026-01-29 11:05:56 +01:00
Torsten Schulz (local)
b3db65d1b8 Add money history graph feature: Implement moneyHistoryGraph method in FalukantService and corresponding controller and router updates. Enhance frontend with a new dialog for displaying money history over various time ranges, including localization updates for German and English. This improves user experience by providing visual insights into financial data. 2026-01-29 10:40:13 +01:00
Torsten Schulz (local)
506a9cd9c0 Fix localization key in NobilityView: Update the translation key for the next title display to ensure correct localization in the advance section, enhancing user experience. 2026-01-29 09:45:48 +01:00
Torsten Schulz (local)
1ead06fd4f Add reputation actions and localization updates: Implement getReputationActions method in FalukantService, enhancing reputation management. Update German and English localization files to include new reputation action terms and mood descriptions, improving user experience and clarity. 2026-01-29 09:37:34 +01:00
Torsten Schulz (local)
eecd947377 Enhance date formatting in NobilityView: Update formatDate method to display time for today's or future dates, improving user experience by providing more relevant information. 2026-01-29 09:26:21 +01:00
Torsten Schulz (local)
5351e3ea57 Refactor NobilityView: Remove debug logs and improve conditional rendering for advance section. Add cooldown message styling for better user feedback when advancement is not possible. 2026-01-29 09:23:24 +01:00
Torsten Schulz (local)
3bdb77888f Enhance NobilityView with debugging information: Add conditional rendering for advance section based on availability of next title information. Implement detailed console logs for loading and advancing nobility data, improving error handling and debugging capabilities. 2026-01-29 09:20:10 +01:00
Torsten Schulz (local)
c570fd6ae3 Refactor association keys in setupAssociations: Update foreign key naming conventions in Party and TitleOfNobility associations for consistency and clarity. Enhance error handling in FalukantService to ensure party ID is present before adding invited nobilities. 2026-01-29 09:05:38 +01:00
Torsten Schulz (local)
be3ed4af5d Add reputation actions endpoint: Implement getReputationActions method in FalukantService, update FalukantController to wrap the new method, and add corresponding route in falukantRouter for retrieving reputation actions. 2026-01-29 08:59:00 +01:00
Torsten Schulz (local)
4cce044128 Refactor character name handling in enrichNotificationsWithCharacterNames: Update comments for clarity on character_name and characterName usage. Enhance localization by adding success messages in German and English translations. 2026-01-29 07:46:17 +01:00
Torsten Schulz (local)
59875cf900 Implement migration to make church_application supervisor_id nullable: Enhance functionality by allowing supervisor_id to be null, improving flexibility in application processing. Add logging for migration success and error handling. 2026-01-28 17:07:11 +01:00
Torsten Schulz (local)
37129055e6 Update supervisorId handling in ChurchApplication and FalukantService: Allow supervisorId to be null for entry-level positions, enhancing flexibility in application processing. Improve prerequisite office type updates in initializeFalukantTypes for better data integrity. 2026-01-28 17:02:27 +01:00
Torsten Schulz (local)
934e80c2ab Enhance logging and validation in getAvailableChurchPositions: Add detailed console logs for office type requirements and user qualifications, improving debugging and ensuring correct handling of prerequisite office types. 2026-01-28 16:57:46 +01:00
Torsten Schulz (local)
8e20fbd24d Add church office requirements validation in FalukantService: Implement checks for prerequisite office types when determining available positions, enhancing the application process for church offices based on user qualifications. 2026-01-28 16:52:44 +01:00
Torsten Schulz (local)
f102069f5a Remove unused supervisor assignment in FalukantService: Clean up code by eliminating the conditional block that sets the supervisor object, streamlining the availablePositions logic. 2026-01-28 16:47:57 +01:00
Torsten Schulz (local)
afc36161ed Add region relationship to ChurchOffice model: Establish associations between ChurchOffice and RegionData, allowing for better organization of church offices by region. Enhance logging in getChurchOverview method for improved debugging and error handling. 2026-01-28 16:45:40 +01:00
Torsten Schulz (local)
a8b76bc21a Add church management features: Implement endpoints for church overview, available positions, supervised applications, and application processing in FalukantService and FalukantController. Update router to include new routes for these functionalities, enhancing church-related operations. 2026-01-28 16:41:19 +01:00
Torsten Schulz (local)
8550bd31d9 Add bulk pricing retrieval for products in region: Implement getAllProductPricesInRegion method in FalukantService, update FalukantController and router to support new endpoint, and modify BranchView to utilize bulk request for improved performance. 2026-01-28 15:18:26 +01:00
Torsten Schulz (local)
8837494a06 Refactor ProductType model: Remove unused sellCostMinNeutral and sellCostMaxNeutral fields for cleaner code and improved maintainability. 2026-01-28 15:13:39 +01:00
Torsten Schulz (local)
0c407b81b7 Enhance error handling in FalukantService: Add validation for product sellCost in calcRegionalSellPrice and getProductPriceInRegion methods, ensuring proper error messages are logged when sellCost is undefined or null. 2026-01-28 15:05:28 +01:00
Torsten Schulz (local)
71b4a02592 Update FalukantService to include character ID in attributes and enhance logging in PoliticsView for better debugging of character data retrieval. 2026-01-28 14:58:11 +01:00
Torsten Schulz (local)
83e5767812 Enhance logging in PoliticsView: Add detailed console logs for API responses and error handling in loadCurrentPositions and loadOwnCharacterId methods. Improve isOwnPosition method with additional logging for better debugging and clarity. 2026-01-28 14:56:07 +01:00
Torsten Schulz (local)
c544c2c7f9 Add holder ID to character object in FalukantService: Include the holder's ID in the character data structure to enhance user identification and data integrity. 2026-01-28 14:52:08 +01:00
Torsten Schulz (local)
818c8fbdf9 Implement own position highlighting in PoliticsView: Add functionality to load and display the user's own character ID, enhancing the UI by highlighting positions associated with the user. Update styles for better visibility of own positions in the table. 2026-01-28 14:47:20 +01:00
Torsten Schulz (local)
a6326f149d Add lay-preacher office and update prerequisites in FalukantTypes; enhance church localization in English and German 2026-01-28 14:24:24 +01:00
Torsten Schulz (local)
01679697b4 Remove debug logging from getFalukantUserByHashedId and getChildren methods in FalukantService for cleaner code and improved performance. 2026-01-28 13:44:12 +01:00
Torsten Schulz (local)
d4fb2a8ccc Enhance health activity error handling: Implement detailed error responses in FalukantController for 'tooClose' scenarios, including retry timing. Update localization files for improved user feedback on health measures and errors. Refactor error handling in HealthView to display appropriate messages based on error responses. 2026-01-28 13:34:42 +01:00
Torsten Schulz (local)
08b6437a1e Improve error handling in FalukantController: Enhance response structure for error objects by including additional error data while maintaining status code integrity. This change allows for more informative error messages in the API response. 2026-01-28 13:29:15 +01:00
Torsten Schulz (local)
baffd9d05c Update English localization file: Reorganize JSON structure for improved readability and maintainability, ensuring consistent formatting across keys and values. 2026-01-28 11:55:27 +01:00
Torsten Schulz (local)
cbff7c130c Füge Funktion zum Abbrechen der Werbung hinzu: Implementiere cancelWooing in FalukantService und FalukantController, aktualisiere FamilyView für die Benutzeroberfläche und verbessere die Fehlermeldungen bei vorzeitigen Abbrüchen. 2026-01-28 11:53:34 +01:00
Torsten Schulz (local)
16f3d1a320 Verbessere Fehlerbehandlung beim Geschenkeversand: Füge Unterstützung für Retry-Nachricht und verbessere die Lokalisierung der Fehlermeldungen in FamilyView hinzu. 2026-01-26 17:15:36 +01:00
Torsten Schulz (local)
955ea1a9ed Enhance gift sending logic: Implement retry mechanism for 'tooOften' error in FalukantService and update error handling in FamilyView to display retry time. 2026-01-26 16:45:13 +01:00
Torsten Schulz (local)
ca614f6cc2 Enhance database connection management by introducing configurable pool settings and implementing a retry mechanism for transient connection issues. Updated Sequelize pool options to use environment variables for better flexibility and added a retry wrapper for initializing settings to handle connection timeouts gracefully. 2026-01-26 16:27:36 +01:00
Torsten Schulz (local)
71748f6aa0 Refactor SaleSection component: Simplify sell item and sell all logic, remove unnecessary state management, and improve UI feedback. Update translations and clean up unused code in i18n files. Optimize price loading in BranchView and remove legacy product loading in MoneyHistoryView. Streamline PoliticsView by removing own character ID handling and related logic. 2026-01-26 16:03:48 +01:00
Torsten Schulz (local)
80b639b511 Remove deprecated relationship change log migration and enhance error handling in FalukantService for partner retrieval 2026-01-26 10:10:22 +01:00
Torsten Schulz (local)
bba68da488 Add RelationshipChangeLog model and enhance character loading logic 2026-01-26 09:54:40 +01:00
Torsten Schulz (local)
29c2b53f53 Refactor VocabLessonView route definition for improved readability
- Updated the routing configuration to directly reference the VocabLessonView component, enhancing code clarity and consistency in component usage.
2026-01-23 17:19:41 +01:00
Torsten Schulz (local)
c3cc248a39 Add VocabLessonView component to socialRoutes
- Imported VocabLessonView component and updated the route definition to use the imported component directly instead of a dynamic import.
- This change enhances code readability and maintains consistency in how components are referenced in the routing configuration.
2026-01-23 14:42:21 +01:00
Torsten Schulz (local)
fb821dbf21 Update color scheme across components to use new primary orange variable
- Replaced hardcoded orange color values with the new CSS variable for primary orange in multiple components, ensuring consistency in styling.
- Updated styles in DialogWidget, MessageboxWidget, SettingsWidget, SimpleTabs, and various Falukant components to enhance maintainability and readability.
2026-01-23 13:56:19 +01:00
Torsten Schulz (local)
079250fcd7 Add notification messages for office filling in German and English locales
- Added new notification messages for when a political office is filled in both the German and English locale files.
- Ensured consistency in messaging across different languages to enhance user experience.
2026-01-23 13:53:32 +01:00
Torsten Schulz (local)
120cb5fadd Refactor and reintroduce character name enrichment logic in FalukantService
- Moved the enrichNotificationsWithCharacterNames function back into the FalukantService class, ensuring character names are correctly parsed and attached to notifications.
- Implemented a comprehensive ID collection and resolution mechanism for character references, enhancing the accuracy of character name assignments.
- Improved error handling during JSON parsing and ID resolution to maintain robustness in notification processing.
2026-01-23 13:46:06 +01:00
Torsten Schulz (local)
d3a554108f Enhance syncModelsAlways function to handle problematic ENUM changes
- Added logic to skip synchronization for specific tables with known ENUM issues, improving stability during model synchronization.
- Restored associations and virtual fields for models when skipping sync, ensuring data integrity and consistency.
2026-01-23 13:39:14 +01:00
Torsten Schulz (local)
6471158847 Add connection management scripts to package.json
- Introduced new scripts: check-connections and cleanup-connections for managing database connections.
- These scripts enhance the backend's ability to monitor and maintain connection health.
2026-01-23 13:35:33 +01:00
Torsten Schulz (local)
1c442eb195 Update Sequelize configuration for improved connection management
- Reduced connection pool size and adjusted timeout settings to prevent connection limits.
- Added automatic reconnection handling for better resilience against connection losses.
- Introduced retry logic for specific connection errors to enhance stability during database interactions.
2026-01-23 13:28:31 +01:00
Torsten Schulz (local)
13f5660fee Add sync-tables script to package.json for table synchronization 2026-01-23 13:18:46 +01:00
Torsten Schulz (local)
9333a8318c Enhance query handling and foreign key management in sequelize.js
- Updated queryWithTimeout to support parameter replacements, improving query flexibility.
- Enhanced foreign key checks in syncModelsAlways to handle timeouts and errors more gracefully, ensuring robust logging and skipping problematic checks.
- Implemented a check for table existence before synchronization for large tables, preventing unnecessary sync operations and improving performance.
2026-01-23 13:13:35 +01:00
Torsten Schulz (local)
c1cda5fa62 Enhance Sequelize configuration and query handling in sequelize.js
- Added connection pool settings to optimize database connection management.
- Introduced a queryWithTimeout helper function to handle long-running queries, improving error handling and preventing indefinite hangs.
- Updated syncModelsAlways function to utilize queryWithTimeout for foreign key checks and cleanup operations, enhancing robustness and logging for better visibility during synchronization.
2026-01-23 12:48:26 +01:00
Torsten Schulz (local)
88967ba9d3 Implement model synchronization with timeout handling in sequelize.js
- Added a helper function to synchronize models with a timeout, improving error handling for long-running sync operations.
- Updated the syncModelsAlways function to utilize the new timeout feature, providing better control over model synchronization and preventing indefinite hangs.
- Enhanced logging to indicate model sync progress and timeout occurrences, improving visibility during deployment.
2026-01-23 10:37:47 +01:00
Torsten Schulz (local)
92d792246c Enhance database cleanup operations in syncDatabase.js
- Added a helper function to check for table existence before performing cleanup operations, ensuring that invalid queries are avoided.
- Updated cleanup logic for church_office and church_application tables to only execute if the respective tables exist, improving robustness and preventing errors.
- Maintained existing logging for cleanup operations to provide visibility into the process.
2026-01-23 09:42:49 +01:00
Torsten Schulz (local)
586aaec506 Add queryWithTimeout helper for database operations in syncDatabase.js
- Introduced a new helper function to execute database queries with a timeout, improving error handling for long-running queries.
- Updated multiple cleanup operations to utilize the new helper, enhancing code readability and maintainability.
- Added descriptive logging for each cleanup operation to provide better insights into the database synchronization process.
2026-01-22 17:18:27 +01:00
Torsten Schulz (local)
10690b5a6e Optimize database cleanup process in syncDatabase.js
- Enhanced orphaned entry cleanup queries with LEFT JOIN for improved performance.
- Added logging for each cleanup step to provide better visibility into the process.
- Included additional cleanup for church_office and church_application tables to remove invalid entries.
- Updated vehicle condition handling to set legacy NULLs to 100 and clamp values between 0 and 100.
2026-01-22 17:07:04 +01:00
Torsten Schulz (local)
bceef9777a Refactor church career validation in FalukantService
- Moved church career checks to a more logical position in the transaction flow.
- Improved error handling by ensuring character existence is validated before checking for church office.
- Cleaned up commented code for better readability and maintainability.
2026-01-22 16:54:29 +01:00
Torsten Schulz (local)
4f786cdcc3 Implement church career management features
- Added endpoints for church career functionalities including overview, available positions, application submission, and application decision-making.
- Enhanced the FalukantController to handle church-related requests.
- Updated associations and models to support church office types and requirements.
- Integrated new routes in the falukantRouter for church career operations.
- Implemented service methods for managing church applications and checking church career status.
- Updated frontend components to display current positions, available positions, and manage applications with appropriate UI elements and loading states.
- Localized new church-related strings in both English and German.
2026-01-22 16:46:42 +01:00
Torsten Schulz (local)
8e226615eb Refactor character avatar rendering in OverviewView.vue
- Replaced 3D character rendering with a 2D avatar display for improved performance.
- Introduced dynamic avatar styling based on user gender and age group.
- Added computed properties for avatar and house styles to enhance visual representation.
- Cleaned up CSS to support the new avatar display structure.
2026-01-22 16:02:41 +01:00
Torsten Schulz (local)
82734e8383 Refactor source directory handling in modelsProxyService.js
- Introduced a cached source directory variable to optimize the retrieval of model paths.
- Updated comments for clarity on the source directory logic and its impact on cache validation.
2026-01-22 15:50:46 +01:00
Torsten Schulz (local)
69a83c584b Enhance model path handling in modelsProxyService.js
- Refactored model source directory logic to dynamically select between production and local paths.
- Updated error messages to provide clearer context on model source lookup failures.
- Added package-lock.json to .gitignore to streamline dependency management.
2026-01-22 13:50:47 +01:00
Torsten Schulz (local)
a8fdcd179e Remove package-lock.json files from frontend, backend, and root directories to streamline dependency management and reduce repository size. 2026-01-22 13:50:38 +01:00
Torsten Schulz (local)
ace976965d Refactor model path handling in Character3D component
- Introduced a constant for the models API path to streamline model path construction.
- Updated modelPath method to utilize the new constant, improving code clarity and maintainability.
- Adjusted fallback model path logic to ensure consistent API usage.
2026-01-22 13:42:27 +01:00
Torsten Schulz (local)
7303d1ea0b Implement request handling for 3D models in app.js
- Added middleware to prevent direct access to /models/ paths, directing users to use /api/models/ instead for optimized 3D models.
- Updated comments to clarify the purpose of the new middleware and its role in serving models through the API.
2026-01-22 13:40:03 +01:00
Torsten Schulz (local)
4379b0b955 Implement model optimization and caching for 3D characters
- Added a new modelsProxyRouter to handle requests for optimized 3D character models.
- Introduced modelsProxyService to manage GLB file optimization using gltf-transform with Draco compression.
- Updated app.js to include the new modelsProxyRouter for API access.
- Enhanced .gitignore to exclude model cache files.
- Added scripts for optimizing GLB models and updated README with optimization instructions.
- Integrated DRACOLoader in Character3D.vue for loading compressed models.
- Updated FamilyView.vue to streamline character rendering logic.
2026-01-22 13:24:47 +01:00
Torsten Schulz (local)
09af7af228 Update color scheme in AppHeader and AppNavigation components for visual consistency
- Changed background color in AppHeader.vue and AppNavigation.vue to a new shade for improved aesthetics.
- Adjusted hover states and submenu background colors in AppNavigation.vue to match the updated theme.
- Refactored FamilyView.vue to enhance layout with additional padding for better alignment of elements.
2026-01-22 12:56:53 +01:00
Torsten Schulz (local)
dc08da211f Update AppHeader and FamilyView components for improved styling and layout
- Changed background color in AppHeader.vue for enhanced visual appeal.
- Refactored FamilyView.vue to improve layout by introducing a flexbox structure for better alignment of relationship details and 3D character models.
- Added new CSS classes to support the updated layout and ensure responsive design.
2026-01-22 12:49:48 +01:00
Torsten Schulz (local)
30e1df0dd8 Enhance deployment scripts and frontend components for improved functionality and styling
- Added dependency installation step in deploy-frontend.sh and update-frontend.sh to ensure all required packages are available before building the frontend.
- Updated AppNavigation.vue to change background color for better visual appeal.
- Refactored FamilyView.vue to include 3D character models for both the user and their relationships, enhancing the visual representation of family dynamics.
- Modified OverviewView.vue to switch from 3D character rendering to a 2D avatar display, improving loading performance and user experience.
2026-01-22 12:39:24 +01:00
Torsten Schulz (local)
95a4c977c1 Update Vite configuration for improved module resolution and build settings
- Changed the import path for GLTFLoader to align with the latest Three.js structure.
- Added preserveSymlinks option to the build configuration for better symlink handling.
- Updated Rollup options to explicitly define no external modules, enhancing build clarity.
2026-01-22 12:31:20 +01:00
Torsten Schulz (local)
6ce081196c Update Vite configuration and Character3D component for improved module handling
- Added 'three' to the dedupe array in Vite config to optimize dependency resolution.
- Updated CommonJS options to enable transformation of mixed ES modules for better compatibility.
- Changed the import path for GLTFLoader in Character3D.vue to align with the latest Three.js structure.
2026-01-22 12:26:52 +01:00
Torsten Schulz (local)
3d5342b314 Update Vite configuration to enhance dependency optimization and build settings
- Added 'three' and its GLTFLoader to the optimizeDeps include array for improved module resolution.
- Introduced custom Rollup options to prevent externalization of the 'three' library during the build process.
- Configured CommonJS options to include 'three' and node_modules for better compatibility with dependencies.
2026-01-22 12:24:57 +01:00
Torsten Schulz (local)
78d43e6859 Update color palette and styles across components for improved visual consistency
- Changed theme color in index.html to a brighter orange for better aesthetics.
- Introduced a modern color palette in styles.scss for enhanced readability and consistency.
- Updated various components (AppFooter, AppNavigation, DialogWidget, etc.) to utilize new color variables, ensuring a cohesive look throughout the application.
- Adjusted button styles and hover effects for improved user interaction feedback.
- Enhanced background colors and text colors for better contrast and visibility.
2026-01-22 12:22:05 +01:00
Torsten Schulz (local)
41106ae306 Add 3D character rendering to Character3D component
- Integrated Three.js for 3D character visualization based on user gender and age.
- Simplified the character structure by removing outdated HTML elements and replacing them with a dynamic 3D model loader.
- Implemented model loading with fallback options and added animation capabilities for enhanced visual appeal.
- Updated CSS for the character container to ensure proper rendering and responsiveness.
2026-01-22 11:53:40 +01:00
Torsten Schulz (local)
33aa2ddd45 Refactor OverviewView and NoLoginView to integrate Character3D component
- Replaced avatar display logic in OverviewView with a 3D character representation based on user gender and age.
- Updated NoLoginView to utilize Character3D for displaying mascots, enhancing visual consistency.
- Removed outdated avatar positioning logic and related computed properties for improved code clarity and maintainability.
- Adjusted CSS styles for better layout and responsiveness of character displays.
2026-01-22 11:06:38 +01:00
Torsten Schulz (local)
2be5505c55 Refactor MessagesDialog.vue for improved readability and functionality
- Replaced hardcoded button definitions with a computed property for better maintainability.
- Enhanced pagination logic by introducing a reset function and simplifying page navigation methods.
- Improved notification handling by restructuring parameter extraction and formatting, ensuring better clarity and consistency in displaying messages.
- Updated CSS styles for better visual presentation and consistency across the component.
2026-01-22 10:25:00 +01:00
Torsten Schulz (local)
8c0f07cc51 Optimize falukantService and DirectorInfo component for improved performance and user experience
- Refactored proposal handling in falukantService to load existing proposals before cleaning expired ones, reducing unnecessary database queries.
- Enhanced fetchProposals method with explicit joins for better performance and added a limit to avoid excessive data retrieval.
- Updated DirectorInfo component to reload data after hiring a director, ensuring the UI reflects the latest information.
2026-01-22 10:15:45 +01:00
Torsten Schulz (local)
3018b1f2e1 Refactor updateFoodCareExercises function to optimize database queries and improve code clarity
- Replaced the Sequelize findAll method with a raw SQL query to fetch Bisaya courses along with native language information, enhancing performance.
- Updated variable names for consistency and clarity, ensuring better readability of the code.
- Adjusted the handling of course owner IDs to align with the new query structure, improving data integrity.
2026-01-21 13:22:03 +01:00
Torsten Schulz (local)
a21a2314d7 Enhance survival sentences exercises and VocabCourseView for improved user experience
- Added multiple choice exercises for common phrases in Bisaya, including questions and explanations to aid learning.
- Introduced a gap fill exercise for completing survival sentences, enhancing interactive learning.
- Updated VocabCourseView to display a button for continuing the current lesson, ensuring smoother navigation.
- Implemented logic to prevent starting lessons without completing previous ones, improving course progression management.
2026-01-21 12:01:31 +01:00
Torsten Schulz (local)
a76aae3d12 Update falukantService and MessagesDialog for improved data handling and parameter extraction
- Changed the join in falukantService from 'title_of_nobility' to 'title' for better clarity in data relationships.
- Enhanced MessagesDialog.vue to directly extract parameters from parsed data when no value object is present, improving the handling of character-related parameters and ensuring backend names take precedence.
2026-01-21 08:33:59 +01:00
Torsten Schulz (local)
7765067d1b Refine distractor selection logic in VocabLessonView to enhance vocabulary options
- Updated the logic for generating distractors by collecting unique vocabulary entries from both learning and reference directions, ensuring no duplicates are included.
- Increased the maximum attempts for finding distractors and adjusted fallback mechanisms to ensure a minimum of two options are provided, improving the overall user experience.
2026-01-20 15:14:50 +01:00
Torsten Schulz (local)
eddbe5fa3f Refine vocabulary selection logic in VocabLessonView to prevent duplicates
- Updated the condition for adding vocabulary options to ensure that normalized vocabulary entries are checked against the exclusion set, preventing duplicates and enhancing the learning experience.
2026-01-20 15:09:37 +01:00
Torsten Schulz (local)
c907d2773d Enhance Bisaya course content and VocabLessonView for improved clarity and functionality
- Updated gap fill exercises in create-bisaya-course-content.js to include clearer instructions and contextual hints for better understanding.
- Refined the logic in VocabLessonView.vue to prevent duplicate vocabulary entries and ensure only distinct translations are added, enhancing the learning experience.
- Adjusted timing for transitioning between questions in VocabLessonView to improve user interaction flow.
2026-01-20 15:05:48 +01:00
Torsten Schulz (local)
5f71e56bf9 Update VocabCourseView to simplify lesson number display and improve table styling
- Changed the lesson number header to a simple "#" for clarity.
- Adjusted column widths for the lesson number to enhance layout consistency.
- Centered text alignment for lesson number cells to improve visual presentation.
2026-01-20 15:01:12 +01:00
Torsten Schulz (local)
adcbd1a95a Refactor VocabCourseView layout for improved structure and styling
- Introduced wrapper divs for lesson title, status, and actions to enhance layout organization and readability.
- Updated CSS styles to ensure consistent display and alignment of lesson elements, improving overall user experience.
- Enhanced flexbox usage for better responsiveness and visual clarity in the lessons table.
2026-01-20 14:50:09 +01:00
Torsten Schulz (local)
175a61c81c Enhance VocabService and VocabCourseView for improved multiple choice handling and table layout
- Updated VocabService to support multiple correct answers in multiple choice exercises, allowing for better answer validation and user feedback.
- Enhanced the extraction of correct answers and alternatives to accommodate both single and multiple correct indices.
- Improved CSS styles in VocabCourseView for better table layout, including adjustments for overflow handling and vertical alignment, enhancing overall user experience.
2026-01-20 14:46:07 +01:00
Torsten Schulz (local)
4d97f24531 Update VocabCourseView styles for improved table layout and responsiveness
- Changed table layout to 'separate' for better spacing and visual clarity.
- Adjusted column widths and added min/max width properties for lesson number, title, status, and actions to ensure consistent display.
- Enhanced CSS properties for table rows to manage text overflow and improve overall user experience.
2026-01-20 14:38:17 +01:00
Torsten Schulz (local)
8d32d704b5 Enhance exercise generation for family conversations and feelings & affection
- Updated multiple choice exercises to include randomized wrong options for improved engagement and challenge.
- Added new exercise types for reading aloud and speaking from memory, enhancing interactive learning experiences.
- Improved gap fill exercises with clearer instructions and multiple variants for better user understanding.
- Enhanced the vocabulary service to support new exercise types, ensuring robust answer checking and feedback mechanisms.
- Updated localization files to include new instructions and messages related to the new exercise types.
2026-01-20 14:30:19 +01:00
Torsten Schulz (local)
e5d4a5f95f Refactor VocabCourseView to enhance lesson display and user interaction
- Replaced the lesson item layout with a structured table format for improved readability and organization of lesson information.
- Updated lesson status indicators to include completion badges and scores, providing clearer feedback on progress.
- Enhanced action buttons with distinct styles for better user experience and accessibility.
- Improved CSS styles for a more modern and responsive design, ensuring a consistent look and feel across the application.
2026-01-20 14:16:22 +01:00
Torsten Schulz (local)
d4a0f78cd0 Implement watchers for courseId and lessonId in VocabLessonView to reset flags on changes
- Added watchers for courseId and lessonId to reset flags related to lesson completion and navigation when either value changes, ensuring proper lesson loading and state management.
- Removed redundant method definitions for courseId and lessonId, streamlining the component's code structure and improving maintainability.
2026-01-20 00:14:05 +01:00
Torsten Schulz (local)
7cd946181e Refactor _extractVocabFromExercises method for improved array handling
- Enhanced the method to better handle various input types, including array-like objects, with detailed console logging for conversion attempts and errors.
- Implemented a fallback mechanism for manual conversion of non-array inputs, ensuring robustness in vocabulary extraction.
- Updated comments for clarity on input expectations and processing logic, improving maintainability.
2026-01-20 00:10:51 +01:00
Torsten Schulz (local)
cf97a3ba5e Enhance _extractVocabFromExercises method for robust input handling
- Added checks to ensure the exercises parameter is an array, with console warnings for null, undefined, or non-array inputs.
- Implemented logic to convert non-array objects with a length property into arrays, improving flexibility in handling various input types.
- Enhanced error logging to provide clearer insights when input conversion fails, aiding in debugging and user feedback.
2026-01-20 00:00:57 +01:00
Torsten Schulz (local)
963e0c906c Refactor updateFamilyConversationExercises for improved database queries and clarity
- Replaced direct model queries with raw SQL queries for fetching the Bisaya language and associated courses, enhancing performance and readability.
- Simplified the retrieval of native language information for courses by using a single SQL query with a LEFT JOIN, reducing the number of database calls.
- Updated variable names for better clarity and consistency in the codebase.
2026-01-19 23:40:54 +01:00
Torsten Schulz (local)
089743ac23 Refactor VocabLessonView for improved lesson navigation and user feedback
- Enhanced the loadLesson and checkLessonCompletion methods to streamline lesson transitions and prevent redundant executions, improving user experience.
- Updated navigation logic to utilize more efficient history management, ensuring smoother course and lesson changes.
- Added detailed console logging for better insights during lesson loading and completion checks, aiding in debugging and user interaction.
2026-01-19 23:37:16 +01:00
Torsten Schulz (local)
69ef120677 Enhance VocabService and VocabLessonView for review lesson functionality
- Added logic in VocabService to retrieve vocabulary and lessons from previous lessons for review sessions, improving the learning experience.
- Implemented methods to gather review lessons and vocabulary exercises, ensuring users have access to relevant content during review lessons.
- Updated VocabLessonView to utilize review vocabulary exercises when in a review lesson, enhancing vocabulary extraction and user feedback.
- Improved console logging for better insights into the vocabulary processing flow, aiding in debugging and user interaction.
2026-01-19 23:33:45 +01:00
Torsten Schulz (local)
fe2e6a53e9 Implement dialog prompts for lesson navigation and error handling in VocabLessonView
- Removed the manual check answer button, transitioning to automatic answer verification upon option selection.
- Added dialog overlays for navigating to the next lesson, course completion notifications, and error messages, enhancing user interaction.
- Introduced methods to handle dialog confirmations and cancellations, improving the flow of lesson transitions and error management.
- Updated styles for dialog components to ensure a consistent and user-friendly interface.
2026-01-19 23:28:45 +01:00
Torsten Schulz (local)
cf1b5e7f71 Update VocabLessonView to enhance vocabulary mapping and logging for language exercises
- Refined vocabulary mapping logic to better reflect the relationship between native words and their translations, improving clarity in exercise generation.
- Enhanced console logging to provide more detailed insights into the vocabulary patterns being processed, aiding in debugging and user feedback.
- Updated comments to clarify the purpose of each pattern and the expected input/output, ensuring better maintainability of the code.
2026-01-19 23:14:18 +01:00
Torsten Schulz (local)
202002358a Enhance transformation exercise formatting and improve choice option generation in VocabLessonView
- Added formatting for transformation exercises to display prompts as "Übersetze 'X' ins Bisaya", improving clarity for users.
- Updated the buildChoiceOptions method to exclude the prompt from the choice options, ensuring a more relevant selection for multiple choice questions.
- Enhanced console logging to provide better insights into the prompt and answer during choice option creation, aiding in debugging and user feedback.
2026-01-19 23:10:01 +01:00
Torsten Schulz (local)
14eb28d37f Refactor family vocabulary exercises generation for improved flexibility
- Replaced static family vocabulary exercises with a dynamic structure that generates exercises based on native language input, enhancing adaptability for different languages.
- Introduced a mapping for family words and their translations, allowing for a more comprehensive and organized approach to exercise creation.
- Updated logging to include native language context during exercise generation, improving clarity for developers and users.
- Streamlined the gap fill and transformation exercises to ensure accurate and relevant content for learners.
2026-01-19 22:59:46 +01:00
Torsten Schulz (local)
81dbbdd6f5 Add family vocabulary exercises in Bisaya course content
- Introduced a new set of multiple choice and gap fill exercises focused on family-related vocabulary in Bisaya, enhancing language learning.
- Included detailed explanations for each term to provide context and aid understanding for learners.
- Removed dummy exercises for unknown lessons, streamlining the exercise return logic for better user experience.
2026-01-19 22:48:10 +01:00
Torsten Schulz (local)
9e6787fb3f Enhance logging and improve vocabulary trainer functionality in VocabLessonView
- Added detailed console logging to track the flow of the vocabulary trainer, including checks for available vocabulary and question generation.
- Implemented a safeguard against infinite loops when generating choice options by limiting attempts.
- Enhanced the buildChoiceOptions method to ensure a minimum number of options, adding generic choices if necessary.
- Improved the nextVocabQuestion method with additional logging for better debugging and user feedback.
2026-01-19 22:33:12 +01:00
Torsten Schulz (local)
2eee7bb0c1 Enhance logging and prevent redundant execution in VocabLessonView
- Added console logging to provide better feedback during lesson loading and completion checks.
- Improved loadLesson and checkLessonCompletion methods to prevent multiple executions and enhance user experience.
- Updated error handling to include more descriptive logging for easier debugging.
2026-01-19 22:27:22 +01:00
Torsten Schulz (local)
7f57ecc35e Refactor lesson loading and navigation logic in VocabLessonView
- Improved handling of course and lesson changes by resetting flags to prevent multiple executions during navigation.
- Enhanced the loadLesson method to prevent redundant loading and ensure a smoother user experience.
- Added console logging for better debugging and user feedback during navigation and lesson transitions.
- Updated navigation logic to use replace instead of push for better history management.
2026-01-19 22:15:06 +01:00
Torsten Schulz (local)
21f6130666 Enhance lesson completion checks and navigation in VocabLessonView
- Introduced flags to prevent multiple executions of lesson completion checks and navigation, improving user experience and preventing potential issues.
- Updated the checkLessonCompletion method to ensure accurate score calculation and progress updates.
- Enhanced the navigateToNextLesson method to handle course loading and user navigation more effectively, including user prompts for lesson completion.
- Improved the typing mode logic to ensure questions are only presented when available, enhancing the vocabulary trainer functionality.
2026-01-19 22:01:30 +01:00
Torsten Schulz (local)
594b3dac4a Refactor Bisaya course exercises for clarity and consistency
- Updated gap fill and multiple choice exercises to streamline content and improve user understanding.
- Simplified question structures and reduced the number of gaps in exercises for better engagement.
- Enhanced explanations for phrases to provide clearer context and meaning for learners.
2026-01-19 21:57:15 +01:00
Torsten Schulz (local)
ef2b279df6 Refactor exercise handling and improve user feedback in VocabLessonView
- Enhanced the exercise display logic to provide clearer feedback on available grammar exercises, improving user engagement.
- Updated conditional rendering to ensure accurate handling of lesson and exercise data, reducing potential errors.
- Improved logging for exercise counts and active tab states, aiding in debugging and performance monitoring.
2026-01-19 21:52:13 +01:00
Torsten Schulz (local)
2ffd7a6151 Add new survival phrases and exercises in Bisaya course content
- Introduced a comprehensive set of survival phrases in Bisaya, including multiple choice and gap fill exercises to enhance language learning.
- Added detailed explanations for each phrase to aid understanding and context for learners.
- Structured content into two parts for better organization and accessibility, ensuring a progressive learning experience.
- Updated existing exercises to include new vocabulary and improve user engagement with practical language use.
2026-01-19 21:31:18 +01:00
Torsten Schulz (local)
045d32c245 Enhance VocabLessonView with new vocabulary trainer features and improved statistics
- Added functionality for tracking total attempts and success rates in the vocabulary trainer, enhancing user feedback on performance.
- Introduced multiple choice and typing modes for vocabulary practice, allowing users to switch between different learning styles.
- Updated translations in both English and German to include new vocabulary terms and exercise instructions, ensuring consistency across languages.
- Improved UI layout for displaying vocabulary statistics and answer options, enhancing overall user experience.
2026-01-19 21:23:13 +01:00
Torsten Schulz (local)
053588ae74 Refactor hasExercises computed property in VocabLessonView for improved validation
- Enhanced the hasExercises method to include comprehensive checks for lesson and grammarExercises, ensuring robust validation before accessing properties.
- Removed redundant computed property declaration to streamline the code structure.
- Added additional logging for important vocabulary and grammar explanations to aid in debugging and provide better insights during lesson loading.
2026-01-19 21:10:20 +01:00
Torsten Schulz (local)
749a2d6f59 Refactor vocabulary extraction logic in VocabLessonView for multiple choice and gap fill exercises
- Enhanced the handling of multiple choice questions by extracting options and determining the correct answer index, improving accuracy in vocabulary mapping.
- Updated the question analysis to support different patterns for extracting German and Bisaya words, enhancing the learning experience.
- Improved gap fill answer extraction by iterating through possible answers and creating unique keys for vocabulary mapping, ensuring better context handling.
- Added detailed error logging to assist in debugging vocabulary extraction issues.
2026-01-19 21:04:01 +01:00
Torsten Schulz (local)
95ba8f0b33 Enhance VocabLessonView with vocabulary trainer and grammar explanations
- Introduced a vocabulary trainer feature allowing users to practice important vocabulary interactively, with options to start and stop the trainer.
- Added sections for grammar explanations and lesson descriptions to improve user understanding of the content.
- Updated translations in both English and German to reflect changes in vocabulary and exercise terminology.
- Enhanced conditional rendering to ensure proper display of vocabulary and grammar information based on lesson data.
2026-01-19 20:58:39 +01:00
Torsten Schulz (local)
dacf6cb7f8 Add vocabulary information text and improve conditional rendering in VocabLessonView
- Introduced a new translation key for vocabulary information text in both English and German, providing context for users on vocabulary usage in exercises.
- Enhanced conditional rendering in VocabLessonView to ensure lesson data is properly checked before displaying cultural notes and vocabulary lists, improving robustness and user experience.
2026-01-19 19:43:59 +01:00
Torsten Schulz (local)
656c3b3d09 Improve exercise display and logging in VocabLessonView
- Updated the exercise tab button to show the count of available grammar exercises, enhancing user feedback.
- Enhanced conditional rendering to ensure proper handling of lesson and exercise data, preventing potential errors.
- Added detailed logging for exercise count and active tab state, aiding in debugging and monitoring.
2026-01-19 19:40:36 +01:00
Torsten Schulz (local)
44ce6636c0 Refactor answer checking logic in VocabService to support multiple exercise types
- Updated the _checkAnswer method to handle both multiple choice and gap fill exercises more effectively.
- Enhanced the extraction of correct answers and alternatives based on exercise type, improving accuracy in answer validation.
- Added JSON parsing for answer and question data to ensure compatibility with various input formats.
- Improved fallback mechanisms for answer checking to accommodate different data structures.
2026-01-19 19:35:41 +01:00
Torsten Schulz (local)
1413630f11 Fix layout issue in VocabLessonView by closing a div tag for improved rendering of exercise details 2026-01-19 19:15:36 +01:00
Torsten Schulz (local)
8f55f63f77 Enhance logging and conditional rendering in VocabService and VocabLessonView
- Added detailed logging in VocabService for lesson retrieval, including lesson ID, title, and exercise count.
- Improved conditional rendering in VocabLessonView to handle cases where grammar exercises may not be present, enhancing user experience.
- Updated logging in VocabLessonView to provide insights into loaded lessons and exercises, aiding in debugging and monitoring.
2026-01-19 19:12:54 +01:00
Torsten Schulz (local)
0331ffeb93 Improve error handling and validation in importantVocab computed property of VocabLessonView
- Added checks to ensure importantVocab is only processed if lesson and grammarExercises are valid.
- Enhanced error handling with try-catch blocks to log issues during vocabulary extraction, improving robustness.
- Updated the condition for rendering the vocabulary list to prevent errors when importantVocab is undefined.
2026-01-19 19:01:49 +01:00
Torsten Schulz (local)
196b74bebb Enhance VocabLessonView and VocabService with new learning features
- Added a tabbed interface in VocabLessonView for 'Learn' and 'Exercises' sections, improving user navigation.
- Implemented logic to display important vocabulary and cultural notes in the learning section.
- Updated exercise result display to include correct answers and alternatives for better user feedback.
- Enhanced VocabService to extract correct answers and alternatives from exercise data, supporting the new UI features.
- Added new translations for vocabulary-related terms in both English and German, ensuring consistency across the application.
2026-01-19 16:41:10 +01:00
Torsten Schulz (local)
305e137a1a Reset exercise answers and results in VocabLessonView before loading new lessons
- Added logic to clear previous exercise answers and results when loading a new lesson, ensuring accurate tracking of user responses.
- Simplified the handling of exercise answers and results by directly assigning values instead of using Vue's `$set` method, improving code readability.
2026-01-19 16:08:39 +01:00
Torsten Schulz (local)
4e5ddc8027 Enhance VocabLessonView and VocabService for grammar exercise handling
- Added logic to initialize grammar exercises directly from lesson data or load them separately if not included.
- Introduced a new method to initialize answer arrays for gap fill exercises, improving user interaction and response tracking.
- Updated comments for clarity on the exercise loading process and initialization logic.
2026-01-19 15:33:15 +01:00
Torsten Schulz (local)
4bb75de3f0 Enhance grammar exercise functionality in VocabLessonView
- Added support for multiple exercise types including multiple choice, gap fill, and transformation.
- Updated UI to display exercise instructions and results with improved styling.
- Implemented logic to handle answer checking based on exercise type, enhancing user interaction.
- Added new translations for exercise-related terms in both English and German.
2026-01-19 15:23:16 +01:00
Torsten Schulz (local)
0572a0eb50 Add grammar exercise creation in course generation
- Integrated functionality to create example grammar exercises for grammar lessons during course creation.
- Added a new helper function to generate gap fill and multiple choice exercises based on lesson data.
- Enhanced logging to confirm the number of grammar exercises created, improving feedback during course setup.
2026-01-19 15:15:24 +01:00
Torsten Schulz (local)
c13cb40c7b Add lesson retrieval functionality in VocabController and VocabService
- Introduced a new method in VocabService to fetch lesson details, including access control based on user ownership and lesson visibility.
- Updated VocabController to wrap the new method for user access.
- Added a new route in VocabRouter to handle requests for specific lessons.
- Enhanced VocabCourseListView to support navigation to individual lesson views, improving user experience in accessing lesson content.
2026-01-19 15:07:52 +01:00
Torsten Schulz (local)
33787ba796 Refactor VocabCourseListView for improved layout and styling
- Updated the course item structure to enhance visual clarity and organization.
- Introduced new CSS classes for better styling and hover effects on course items.
- Adjusted padding, margins, and gaps for a more cohesive design.
- Improved button styles for enrollment, continuation, and editing actions, enhancing user interaction.
2026-01-19 14:27:41 +01:00
Torsten Schulz (local)
64f4468664 Add endpoint to retrieve all available languages in VocabController and VocabRouter
- Introduced a new method in VocabService to list all languages from the database.
- Updated VocabController to wrap the new method for user access.
- Added a new route in VocabRouter to handle requests for all languages.
- Modified VocabCourseListView to utilize the new endpoint for loading languages, enhancing the course selection experience.
2026-01-19 14:23:37 +01:00
Torsten Schulz (local)
408b65be30 Refactor native language loading in VocabCourseListView for improved clarity and error handling
- Renamed loadMyNativeLanguage to loadMyNativeLanguageId for better context.
- Enhanced error handling to log warnings when the languages list is empty or when the native language is not found.
- Improved debug logging to provide clearer insights into the native language loading process.
2026-01-19 14:11:22 +01:00
Torsten Schulz (local)
891420cb09 Refactor VocabService to improve direct property handling and enhance language loading
- Updated VocabService to calculate direct where properties after setting all direct properties, ensuring accurate query conditions.
- Enhanced filtering of AND conditions to remove empty objects before assignment, improving query efficiency.
- Added logic to load native language names for courses, ensuring accurate mapping of language IDs to names.
- Improved comments for clarity on the new logic and its implications on course retrieval.
2026-01-19 14:07:16 +01:00
Torsten Schulz (local)
a657c59b2c Add support for user's native language in VocabCourseListView
- Introduced a new option for selecting the user's native language in the course selection dropdown.
- Implemented logic to load the user's native language based on the UI language and map it to the corresponding language ID.
- Updated internationalization files to include strings for the user's native language in both German and English, enhancing the user experience.
2026-01-19 13:54:03 +01:00
Torsten Schulz (local)
89ec084106 Refactor VocabService to improve boolean parameter handling and enhance debugging
- Updated VocabService to convert string parameters for course retrieval into booleans, ensuring accurate filtering based on user input.
- Added detailed debug logging to track the state of query conditions and the final WHERE clause, aiding in troubleshooting and performance analysis.
- Improved comments for clarity on the logic and implications of the changes made.
2026-01-19 13:52:27 +01:00
Torsten Schulz (local)
a7a0daaf82 Enhance VocabService to combine AND conditions in query filtering
- Updated VocabService to combine AND conditions with direct where properties when both are present, improving query accuracy.
- Added comments for better understanding of the new logic and its implications on course retrieval.
2026-01-19 13:14:13 +01:00
Torsten Schulz (local)
df5c2a3141 Enhance VocabService logging and update VocabCourseListView state management
- Added debug logging in VocabService to track course retrieval details, aiding in troubleshooting and performance monitoring.
- Updated VocabCourseListView to include additional state properties for managing share codes and search functionality, improving user experience and interaction capabilities.
2026-01-19 13:03:06 +01:00
Torsten Schulz (local)
f902f5298c Refactor native language filtering in VocabService and update frontend handling
- Simplified the logic for filtering courses by native language in VocabService, allowing for better handling of undefined and null values.
- Enhanced the VocabCourseListView to clarify the behavior of nativeLanguageId based on user selection, ensuring accurate course retrieval based on language preferences.
- Improved comments in both files for better understanding of the filtering logic and its implications on course visibility.
2026-01-19 12:09:48 +01:00
Torsten Schulz (local)
ddd038761b Enhance language course creation script to support public courses
- Updated the script to create public language courses for various target and native languages without requiring an ownerHashedId.
- Implemented a function to find or create a system user for course ownership, ensuring automatic user assignment.
- Improved documentation to clarify the script's usage and the types of courses created.
2026-01-19 11:47:55 +01:00
Torsten Schulz (local)
09e53244d9 Add native language support in vocab course management
- Introduced a new field for native language in the VocabCourse model to allow learners to specify their native language.
- Updated the VocabService to handle native language during course creation and retrieval, including filtering options.
- Enhanced the database schema to include foreign key constraints for native language.
- Updated frontend components to support native language selection and display in course listings.
- Added internationalization strings for native language features in both German and English.
2026-01-19 11:43:38 +01:00
Torsten Schulz (local)
714e144329 Add course retrieval by share code feature and enhance course search functionality
- Implemented a new endpoint in VocabController to retrieve courses using a share code.
- Updated VocabService to include logic for validating share codes and checking course access permissions.
- Enhanced course listing functionality with search and language filtering options in the frontend.
- Added a dialog for users to input share codes and search for courses, improving user experience.
- Updated internationalization files to include new strings for share code functionality and search features.
2026-01-19 11:33:20 +01:00
Torsten Schulz (local)
e1b3dfb00a Refactor navigation structure to enhance language learning features
- Renamed 'vocabtrainer' to 'sprachenlernen' in the navigation structure for better clarity.
- Introduced a nested structure under 'sprachenlernen' for 'vocabtrainer' and 'sprachkurse', improving organization of language-related resources.
- Updated internationalization files for both German and English to reflect the new naming and structure, ensuring consistency across the application.
2026-01-19 11:24:46 +01:00
Torsten Schulz (local)
b6a4607e60 Implement vocab course and grammar exercise features in backend and frontend
- Added new course management functionalities in VocabController, including creating, updating, and deleting courses and lessons.
- Implemented enrollment and progress tracking for courses, along with grammar exercise creation and management.
- Updated database schema to include tables for courses, lessons, enrollments, and grammar exercises.
- Enhanced frontend with new routes and views for course listing and details, including internationalization support for course-related texts.
- Improved user experience by adding navigation to courses from the main vocab trainer view.
2026-01-19 10:58:53 +01:00
Torsten Schulz (local)
9553cc811a Update index.html and sitemap.xml for improved SEO and content visibility
- Updated the title and description in index.html to include new features like Vokabeltrainer and additional minigames.
- Added keywords meta tag to enhance search engine optimization.
- Modified sitemap.xml to reflect changes in URLs and update change frequencies for better indexing, including new entries for Vokabeltrainer and minigames.
2026-01-19 10:35:59 +01:00
Torsten Schulz (local)
59c05b3628 Implement job hierarchy and region depth calculations in FalukantService; enhance PoliticsView with own position highlighting
- Added a job hierarchy mapping to determine positions based on their rank.
- Introduced asynchronous region depth calculations to determine the hierarchy of regions.
- Updated the mapping of office data to include job hierarchy levels and region depths.
- Enhanced the PoliticsView to highlight the user's own positions with a distinct style.
- Implemented a method to load the user's character ID for position comparison.
2026-01-16 16:25:22 +01:00
Torsten Schulz (local)
d3629a8a09 Enhance character name resolution logic in MessagesDialog component
- Improved character name extraction by prioritizing names resolved from the backend over those from notification values.
- Simplified fallback mechanisms for character names, ensuring a more robust handling of character IDs.
- Streamlined the logic for handling character names in various effect types, enhancing clarity and maintainability of the code.
2026-01-15 14:08:02 +01:00
Torsten Schulz (local)
a17e8537fb Enhance character name resolution in enrichNotificationsWithCharacterNames function
- Introduced sets for collecting first name and last name IDs to improve character name enrichment.
- Implemented batch loading of first names and last names, optimizing database queries.
- Added a helper function to resolve names from character first and last name IDs, enhancing notification data with resolved character names.
- Improved logic for attaching resolved names to notifications, prioritizing name resolution from IDs over fallback methods.
2026-01-15 13:33:54 +01:00
Torsten Schulz (local)
a7f23c5885 Refactor update-backend.sh script for improved .env file handling and backup process
- Updated the script to securely back up .env files before deleting the old backend, ensuring no loss of environment configurations.
- Enhanced the restoration process of .env files with clearer logging for success and warnings when no files are found.
- Simplified the logic for copying the new backend while excluding .env files, improving clarity and maintainability of the script.
2026-01-15 13:11:44 +01:00
Torsten Schulz (local)
b706191a0e Refactor effect handling in enrichNotificationsWithCharacterNames to improve data parsing
- Simplified the logic for processing notification effects by consolidating the JSON parsing into a single conditional check, enhancing code readability and maintainability.
- Ensured consistent handling of effects regardless of their initial format, improving robustness in notification enrichment.
2026-01-15 09:28:00 +01:00
Torsten Schulz (local)
ba469ef900 Refactor notification handling in FalukantService to improve data processing
- Convert notification rows to plain objects before enriching with character names, ensuring compatibility with subsequent processing.
- Update return structure to include plain notification objects, enhancing data consistency in the response.
2026-01-15 08:16:04 +01:00
Torsten Schulz (local)
e852346b94 Update mood handling in FalukantService and enhance Socket.io configuration in store
- Modified mood assignment in FalukantService to conditionally wrap mood data in an object based on moodId presence, improving data structure consistency.
- Refactored Socket.io initialization in the store to dynamically set the secure option based on the URL scheme, enhancing connection security in production environments.
2026-01-14 16:22:05 +01:00
Torsten Schulz (local)
02d24eccd8 Add 'sleep' status to Production model and update related components
- Introduced a new 'sleep' boolean field in the Production model to indicate if production is suspended.
- Updated FalukantService to include 'sleep' in the production attributes.
- Enhanced MessagesDialog and ProductionSection components to display the production status and handle branch names.
- Added corresponding translations for 'status', 'sleep', and 'active' in both German and English locale files.
2026-01-14 15:29:53 +01:00
Torsten Schulz (local)
d1359ccc36 Refactor proposal mapping in FalukantService to handle null characters
- Updated the proposal mapping logic to filter out proposals with null proposed characters, enhancing data integrity.
- Ensured that noble title is safely accessed with a fallback to null, improving robustness in character data handling.
2026-01-14 14:58:59 +01:00
Torsten Schulz (local)
52c7f1c7ba Refactor sendMessageToConnection method to enhance user data validation and message handling
- Introduce a local copy of the message to ensure its validity during processing.
- Validate user data retrieved from the WebSocket interface to ensure consistency before queuing messages.
- Streamline logging by removing redundant checks and focusing on critical error handling, improving overall clarity and stability.
2026-01-14 14:38:43 +01:00
Torsten Schulz (local)
7a2749c405 Remove redundant exception handling in sendMessageToConnection method to streamline error logging and improve code clarity. 2026-01-14 14:38:43 +01:00
Torsten Schulz (local)
d71df901ed Refactor message sending logic in WebSocket server to improve direct transmission and error handling
- Attempt to send messages directly during the RECEIVE callback to avoid mutex issues, enhancing performance.
- Implement size checks for messages to prevent overflow, with logging for oversized messages.
- Introduce additional error handling and logging for socket write operations, ensuring robust message delivery and queue management.
- Maintain thread safety by validating user data and mutex locking before queuing messages when direct sending fails.
2026-01-14 14:38:43 +01:00
Torsten Schulz (local)
1af4b6c2e4 Enhance logging and error handling in sendMessageToConnection method
- Introduce detailed logging for the message sending process, including checks for user data validity and message queue status.
- Implement additional null checks for user data before and after locking the mutex to ensure thread safety.
- Ensure proper message copying to maintain validity during queuing, improving overall stability and error visibility.
2026-01-14 14:38:43 +01:00
Torsten Schulz (local)
2595cb8565 Enhance error handling and logging in WebSocket server message sending
- Introduce detailed logging for message creation and sending processes, including message size and success confirmation.
- Implement comprehensive null checks for instance, WebSocket interface, and user data before invoking sendMessageToConnection, improving stability.
- Add exception handling to capture and log errors during message sending, enhancing visibility into potential issues.
2026-01-14 14:38:43 +01:00
Torsten Schulz (local)
45d549aa4e Refactor message sending in WebSocket server to utilize sendMessageToConnection
- Replace manual message queuing and error handling with the sendMessageToConnection method, which consolidates necessary checks and improves code clarity.
- Remove redundant null checks and logging related to message queue access, streamlining the callback logic.
- Enhance overall stability by leveraging existing functionality for message delivery during WebSocket events.
2026-01-14 14:38:43 +01:00
Torsten Schulz (local)
7f65f5e40e Enhance message queuing with improved error handling and logging in WebSocket server
- Implement detailed logging for message queuing attempts, including message size and copy operations.
- Add comprehensive null checks for user data and message queue validity before pushing messages to the queue.
- Introduce exception handling to manage potential errors during message queuing, improving stability and error visibility.
2026-01-14 14:38:43 +01:00
Torsten Schulz (local)
5ce1cc4e6a Refine null checks and logging in WebSocket server message handling
- Introduce a local copy of user data before locking the mutex to ensure validity during message queuing.
- Enhance null checks and logging to provide clearer insights when user data or message queue access fails.
- Implement exception handling for message queue access to improve stability and error visibility.
2026-01-14 14:38:43 +01:00
Torsten Schulz (local)
3a6d60e9a8 Improve null checks and logging in WebSocket server message handling
- Add additional null checks for user data before and after locking the mutex to prevent potential crashes.
- Enhance logging to provide clearer insights when user data is invalid during message queuing.
- Ensure proper message copying to a local variable before accessing the message queue, improving thread safety and stability.
2026-01-14 14:38:43 +01:00
Torsten Schulz (local)
d5a09f359d Enhance logging and error handling in getConnections callback
- Add detailed logging to track the flow and validity of user data during the getConnections event.
- Implement exception handling to manage potential access issues with user data, improving stability and error visibility.
- Ensure clear output for both successful and failed user data access attempts, aiding in debugging and monitoring.
2026-01-14 14:38:43 +01:00
Torsten Schulz (local)
127e95ca1c Improve null checks and logging in WebSocket server callbacks
- Add checks for user data to prevent null pointer exceptions during message handling.
- Enhance logging to provide clearer insights when user data is invalid or when exceptions occur.
- Ensure proper mutex locking when accessing the message queue to maintain thread safety.
2026-01-14 14:38:43 +01:00
Torsten Schulz (local)
bb81126cd8 Enhance error response handling in WebSocket server
- Add detailed logging for error responses during WebSocket callbacks, improving visibility into the error handling process.
- Ensure that error responses are queued correctly without immediate sending, enhancing stability during callback execution.
- Utilize lws_cancel_service to notify the service of pending messages, ensuring proper message delivery after error handling.
2026-01-14 14:38:43 +01:00
Torsten Schulz (local)
2d3d120f81 Refactor WebSocket server message queuing and error handling
- Implement message queuing for error responses during WebSocket callbacks to prevent immediate sending, enhancing stability.
- Utilize lws_cancel_service to trigger the writable callback safely, ensuring messages are sent correctly after the callback execution.
- Improve error handling and logging for message sending operations, providing clearer insights into potential issues.
2026-01-14 14:38:43 +01:00
Torsten Schulz (local)
0c36c4a4e5 Refactor WebSocket server message handling to include user data
- Update sendMessageToConnection to accept user data, enhancing message delivery accuracy.
- Improve error handling in WebSocket callbacks by adding user data checks to prevent null pointer exceptions.
- Enhance logging for error responses to provide clearer insights into message handling issues.
2026-01-14 14:38:43 +01:00
Torsten Schulz (local)
88f6686809 Enhance WebSocket server message handling and error responses
- Improve asynchronous message sending to prevent connection issues during callbacks.
- Add error response handling for failed connection retrieval, ensuring clients receive feedback on errors.
- Implement message size checks to prevent oversized messages from being sent, enhancing stability and reliability.
2026-01-14 14:38:43 +01:00
Torsten Schulz (local)
9c7b682a36 Improve error handling and null checks in WebSocket server callbacks
- Add null checks for user data in various WebSocket callback functions to prevent crashes and improve stability.
- Enhance error logging to provide clearer insights into issues related to user data and connection management.
- Refactor the handling of active connections to ensure robust error handling during data processing and message sending.
2026-01-14 14:38:43 +01:00
Torsten Schulz (local)
dafdbf0a84 Refactor WebSocket server to use nlohmann::json for active connections
- Update the return type of getActiveConnections() in both websocket_server.cpp and websocket_server.h to nlohmann::json for consistency and clarity.
- Ensure proper usage of the nlohmann::json library in the WebSocket server implementation.
2026-01-14 14:38:43 +01:00
Torsten Schulz (local)
5ac8e9b484 Enhance WebSocket server connection management and error handling
- Introduce connection time tracking for WebSocket users to monitor connection duration.
- Implement user ID management to allow dynamic updates and removal of connections based on user ID changes.
- Add functionality to retrieve active connections, including unauthenticated ones, for administrative purposes.
- Improve error handling during connection closure and ensure proper cleanup of connection entries.
2026-01-14 14:38:43 +01:00
Torsten Schulz (local)
753c5929e1 Refactor configuration file installation and template handling
- Update CMakeLists.txt to install the template configuration file as an example, ensuring it is available for reference.
- Modify install-config.cmake to prioritize the installed template file, with fallbacks to source directory templates if the installed one is missing, enhancing the robustness of the configuration setup.
2026-01-14 14:38:43 +01:00
Torsten Schulz (local)
e3f46d775a Enhance WebSocket server ping/pong handling and timeout settings
- Introduce handling for LWS_CALLBACK_RECEIVE_PONG to manage Pong frames received from clients.
- Update the WebSocketUserData structure to increase MAX_PING_TIMEOUTS from 3 to 5, allowing more attempts before disconnection.
- Extend PONG_TIMEOUT_SECONDS from 10 to 60 to accommodate longer response times from browsers.
- Modify ping handling to send a WebSocket Ping frame instead of a text message for better protocol compliance.
2026-01-14 14:38:43 +01:00
Torsten Schulz (local)
0eb3a78332 Enhance configuration file installation process
- Implement a CMake script for intelligent merging of configuration files, ensuring only missing keys are added without overwriting existing ones.
- Install a template configuration file as an example, preventing overwriting of the original during installation.
2026-01-14 14:38:43 +01:00
Torsten Schulz (local)
3ac9f25284 Enhance WebSocket server socket options and connection handling
- Add support for setting SO_REUSEADDR in the WebSocket server to allow port reuse, improving server flexibility.
- Implement callbacks for socket adoption to ensure SO_REUSEADDR is set when applicable.
- Refine server options to streamline connection management and enhance overall performance.
2026-01-14 14:38:43 +01:00
Torsten Schulz (local)
b3c9c8f37c Improve WebSocket server startup and error reporting
- Introduce a brief wait time to ensure the port is released before starting the server.
- Update server options to allow port reuse, enhancing server flexibility.
- Enhance error handling during context creation to provide more informative error messages regarding port usage and permissions.
2026-01-14 14:38:43 +01:00
Torsten Schulz (local)
32bc126def Enhance WebSocket server options and error handling
- Update server options to support multiple simultaneous connections and improve security practices.
- Allow multiple connections per IP with configurable keep-alive settings.
- Improve error handling during WebSocket service operations, ensuring critical errors lead to server shutdown.
- Refine connection closure logic to handle user IDs more robustly and log connection states accurately.
- Enable WebSocket upgrade requests while rejecting other HTTP requests for better protocol management.
2026-01-14 14:38:43 +01:00
Torsten Schulz (local)
00a5f47cae Refactor WebSocket server connection management and message handling
- Update WebSocketUserData to use a message queue for handling outgoing messages, improving concurrency and message delivery.
- Modify pingClients method to handle multiple connections per user and implement timeout logic for ping responses.
- Enhance addConnection and removeConnection methods to manage multiple connections for each user, including detailed logging of connection states.
- Update handleBrokerMessage to send messages to all active connections for a user, ensuring proper queue management and callback invocation.
2026-01-14 14:38:42 +01:00
Torsten Schulz (local)
6a1260687b Implement comprehensive character deletion process in UserCharacterWorker
- Add queries and logic to delete associated data when a character dies, including directors, relationships, child relations, knowledge, debtors prism, political offices, and election candidates.
- Enhance error handling to log issues during the deletion process.
2026-01-14 14:38:42 +01:00
Torsten Schulz (local)
7591787583 Update configuration file path for daemon in main.cpp 2026-01-14 14:38:42 +01:00
Torsten Schulz (local)
bd961a03d4 Aktualisiere WebSocket-Server und Daemon-Konfiguration
- Ändere die Pfade für SSL-Zertifikate in der Konfigurationsdatei.
- Verbessere die Fehlerbehandlung beim Entfernen alter vorbereiteter Anweisungen in HouseWorker.
- Füge Debug-Ausgaben zur Nachverfolgung von Verbindungen und Nachrichten im WebSocket-Server hinzu.
- Implementiere Timeout-Logik für das Stoppen von Worker- und Watchdog-Threads.
- Optimiere die Signalverarbeitung und Shutdown-Logik in main.cpp für bessere Responsivität.
2026-01-14 14:38:42 +01:00
Torsten Schulz (local)
8fe816dddc WebSocket-Verbindungsverwaltung implementiert
- User-ID wird bei setUserId Event gespeichert
- Verbindungen werden in connections Map verwaltet
- Nachrichten werden über pendingMessage gesendet
- Statische Instanz-Referenz für Callback-Zugriff
- Explizite JSON-Konvertierung für Kompatibilität
2026-01-14 14:38:42 +01:00
Torsten Schulz (local)
e7a8dc86eb Füge Unterstützung für die Verwaltung von WebSocket-Verbindungen hinzu. Implementiere Methoden zum Hinzufügen und Entfernen von Verbindungen basierend auf Benutzer-IDs. Aktualisiere die WebSocket-Callback-Logik, um empfangene Nachrichten zu verarbeiten und Benutzer-IDs zu setzen. Verbessere die Ausgabe von Debug-Informationen zur Nachverfolgung von Verbindungen und Nachrichten. 2026-01-14 14:38:42 +01:00
Torsten (PC)
c9dc891481 updated rights 2026-01-14 14:38:42 +01:00
Torsten Schulz (local)
89c3873db7 Füge Überprüfung auf sudo-Rechte im SSL-Setup-Skript hinzu und aktualisiere die Pfade für Apache2-Zertifikate. Priorisiere Let's Encrypt-Zertifikate und füge Warnungen für Snakeoil-Zertifikate hinzu, um Benutzer über deren Einschränkungen zu informieren. Aktualisiere die Dokumentation entsprechend. 2026-01-14 14:38:42 +01:00
Torsten Schulz (local)
60352d7932 Erweitere das SSL/TLS Setup-Skript um Unterstützung für Apache2-Zertifikate. Füge eine neue Funktion zum Einrichten und Verlinken von Apache2-Zertifikaten hinzu, einschließlich der Überprüfung auf vorhandene Zertifikate und der automatischen Erneuerung für Let's Encrypt. Aktualisiere die Benutzerführung zur Auswahl von Zertifikatstypen und dokumentiere die neuen Optionen in der SSL-Setup-Dokumentation. 2026-01-14 14:38:42 +01:00
Torsten Schulz (local)
664f2af346 Erweitere das SSL/TLS Setup-Skript für den YourPart Daemon um Unterstützung für Let's Encrypt. Füge Funktionen zur Erstellung und Erneuerung von Let's Encrypt Zertifikaten hinzu, einschließlich automatischer Erneuerung über Cron Jobs. Ermögliche die Auswahl zwischen Self-Signed und Let's Encrypt Zertifikaten und verbessere die Benutzerführung bei der Zertifikatsauswahl. 2026-01-14 14:38:42 +01:00
Torsten Schulz (local)
8212e906a3 Füge Unterstützung für SSL/TLS in den WebSocket-Server hinzu. Aktualisiere die Konfigurationsdatei, um SSL-Optionen zu ermöglichen, und passe die WebSocketServer-Klasse an, um Zertifikat- und Schlüsselpfade zu akzeptieren. Verbessere die Serverstartlogik, um SSL korrekt zu initialisieren und entsprechende Meldungen auszugeben. 2026-01-14 14:38:42 +01:00
Torsten Schulz (local)
92e17a9f43 Verbessere die Verwaltung der Konfigurationsdatei im Skript deploy-server.sh. Füge eine Überprüfung hinzu, ob die Konfigurationsdatei existiert, und kopiere sie nur, wenn sie nicht vorhanden ist. Ergänze die Logik zum Hinzufügen fehlender Schlüssel in die bestehende Konfigurationsdatei. 2026-01-14 14:38:42 +01:00
Torsten Schulz (local)
d3727ad2f7 Ändere den Typ des Services in der Datei yourpart-daemon.service von 'simple' auf 'notify' und füge die Option NotifyAccess hinzu. Verbessere die Signalverarbeitung in main.cpp, um ein sauberes Herunterfahren der Anwendung zu ermöglichen und die Hauptschleife anzupassen. 2026-01-14 14:38:42 +01:00
Torsten Schulz (local)
391e5d9992 Ändere den Typ des Services in der Datei yourpart-daemon.service von 'notify' auf 'simple' für eine verbesserte Service-Verwaltung. Füge im Skript deploy-server.sh eine Verzögerung von 3 Sekunden nach dem Start des Services hinzu, um sicherzustellen, dass der Dienst ordnungsgemäß initialisiert wird. 2026-01-14 14:38:42 +01:00
Torsten Schulz (local)
a4bd585730 Füge Überprüfung und Stopp des Services vor dem Kopieren der Dateien im Skript deploy-server.sh hinzu. Aktualisiere die Nummerierung der Schritte für eine bessere Übersichtlichkeit und entferne die Überprüfung, ob der Service bereits läuft, bevor er neu gestartet wird. 2026-01-14 14:38:42 +01:00
Torsten Schulz (local)
c694769f4c Füge Überprüfung der Root-Rechte hinzu und aktualisiere Berechtigungen im Skript deploy-server.sh. Alle relevanten Operationen, die erhöhte Rechte benötigen, werden nun mit sudo ausgeführt, um die Sicherheit und Funktionalität zu verbessern. 2026-01-14 14:38:42 +01:00
Torsten Schulz (local)
8b9ff9793c Verbessere die Statusverarbeitung in der Methode spyIn, indem die Zuweisung des Status aus dem JSON-Objekt optimiert wird. Verwende nun die get-Methode für eine klarere und sicherere Zuweisung. 2026-01-14 14:38:42 +01:00
Torsten Schulz (local)
8ba4566d23 Füge Unterstützung für systemd hinzu, indem die systemd-Entwicklungslibraries in CMakeLists.txt und Installationsskripten für OpenSUSE und Ubuntu 22 integriert werden. Aktualisiere die yourpart-daemon.service-Datei für eine verbesserte Service-Verwaltung und implementiere die Benachrichtigung an systemd, wenn der Dienst bereit ist. 2026-01-14 14:38:42 +01:00
Torsten Schulz (local)
91420b9973 Erweitere die Unterstützung für vorbereitete Abfragen in der Datenbank, indem die Methode exec_params für bis zu 10 Parameter implementiert wird. Füge eine Fehlerbehandlung für zu viele Parameter hinzu. 2026-01-14 14:38:42 +01:00
Torsten Schulz (local)
8d3e0423e7 Füge Unterstützung für verschiedene Versionen von libpqxx hinzu, um die Kompatibilität mit libpqxx 6.x und 7.x zu gewährleisten. Implementiere unterschiedliche Methoden zur Ausführung vorbereiteter Abfragen basierend auf der Anzahl der Parameter. 2026-01-14 14:38:42 +01:00
Torsten Schulz (local)
4bafc3a61c Verbessere die CMake-Konfiguration zur Unterstützung von C++23, indem die Compiler-Auswahl dynamisch auf GCC 15 oder 13 basiert. Optimiere die Compiler-Flags für Leistung. In der Datenbankabfrage und im DirectorWorker werden konstante Referenzen und string_view verwendet, um die Leistung zu steigern. Reserviere Speicher für Vektoren in main.cpp zur Effizienzsteigerung. 2026-01-14 14:38:42 +01:00
Torsten Schulz (local)
1f43df6d41 Ändere die Berechtigungen des Skripts deploy-server.sh von 644 auf 755, um die Ausführbarkeit zu ermöglichen. 2026-01-14 14:38:42 +01:00
Torsten Schulz (local)
c2a54e29f8 Aktualisiere die Compiler-Version in CMakeLists.txt und install-dependencies-ubuntu22.sh von GCC 15 auf GCC 13 für bessere Unterstützung von C++23. Passe die Installationsmeldungen und Standard-Compiler-Einstellungen entsprechend an. 2026-01-14 14:38:42 +01:00
Torsten Schulz (local)
b1f9073f4d Ändere die Berechtigungen des Skripts install-dependencies-ubuntu22.sh von 644 auf 755, um die Ausführbarkeit zu ermöglichen. 2026-01-14 14:38:42 +01:00
Torsten Schulz (local)
1b38e2412c Aktualisiere das Build-Skript in deploy.sh, um den C++ Standard auf Version 23 zu setzen. Ändere die Installation des C++ Compilers in install-dependencies-ubuntu22.sh, um GCC 15 zu installieren und als Standard-Compiler festzulegen. Entferne die vorherige Installation von GCC 11. 2026-01-14 14:38:42 +01:00
Torsten Schulz (local)
4b9311713a Aktualisiere das Build-Skript, um C++ Standard auf Version 20 zu setzen. Ändere die Installation des C++ Compilers in install-dependencies.sh, um GCC 11 als Standard für Ubuntu 22 zu verwenden und entferne die Installation von GCC 15. 2026-01-14 14:38:42 +01:00
Torsten Schulz (local)
77520ee46a Ändere die Berechtigungen der Skripte deploy.sh und install-dependencies.sh von 644 auf 755, um die Ausführbarkeit zu ermöglichen. 2026-01-14 14:38:42 +01:00
Torsten Schulz (local)
23c07a3570 Füge UndergroundWorker hinzu und implementiere Logik für unterirdische Aufgaben. Aktualisiere CMakeLists.txt, um neue Quell- und Header-Dateien einzuschließen. Verbessere die Fehlerbehandlung in der Datenbank und sende Benachrichtigungen nach bestimmten Ereignissen. Integriere Hilfsfunktionen zur sicheren Verarbeitung von Daten. 2026-01-14 14:38:35 +01:00
Torsten Schulz
1451225978 stabilized app 2026-01-14 14:37:21 +01:00
Torsten Schulz
51fd9fcd13 Ändere die Überprüfung auf die Erstellung von Charakteren von "vorheriger Tag" zu "heutiger Tag" 2026-01-14 14:37:21 +01:00
Torsten (PC)
1fe77c0905 fix 2026-01-14 14:36:57 +01:00
Torsten Schulz (local)
cd739fb52e Refactor server.js for improved WebSocket and API configurations
- Updated API server to listen on a configurable port (2020) for better flexibility.
- Removed WebSocket setup from HTTP server, ensuring Socket.io is only available over HTTPS.
- Enhanced logging to clarify Socket.io availability based on TLS configuration.
2026-01-14 14:30:50 +01:00
Torsten Schulz (local)
9e845843d8 Update WebSocket and API configurations in yourpart-websocket-fixed.conf and daemonServer.js
- Adjusted WebSocket proxy settings in yourpart-websocket-fixed.conf to route traffic through port 4551 for both secure and non-secure connections.
- Enhanced daemonServer.js to listen on all interfaces (0.0.0.0) for both TLS and non-TLS WebSocket connections, improving accessibility.
2026-01-14 13:10:33 +01:00
Torsten Schulz (local)
0cc280ed55 Refactor WebSocket and API configurations in yourpart-https.conf and yourpart-websocket-fixed.conf
- Removed outdated WebSocket handling from yourpart-https.conf for improved clarity.
- Updated yourpart-websocket-fixed.conf to enable SSL and adjust WebSocket proxy settings.
- Streamlined fallback logic in frontend store to ensure direct connection to the daemon on port 4551.
- Enhanced logging for better debugging and monitoring of daemon connections.
2026-01-14 13:02:38 +01:00
Torsten Schulz (local)
b3707d21b2 Refactor yourpart-https.conf for enhanced WebSocket and API request handling
- Updated www redirect to exclude API and WebSocket paths for improved functionality.
- Organized WebSocket and API proxy settings within Location blocks for better clarity and maintainability.
- Consolidated WebSocket upgrade rules for Socket.io and daemon connections, ensuring consistent handling.
2026-01-14 12:07:04 +01:00
Torsten Schulz (local)
fbebd6c1c1 Refactor yourpart-https.conf for improved WebSocket handling and domain redirection
- Updated www redirect to exclude WebSocket paths for better functionality.
- Consolidated WebSocket upgrade rules for Socket.io and daemon connections.
- Enhanced organization of proxy settings for API and WebSocket requests, improving maintainability.
2026-01-14 12:02:49 +01:00
Torsten Schulz (local)
d7c2bda461 Enhance yourpart-https.conf with improved WebSocket and API configurations
- Added www redirect to ensure consistent domain usage.
- Consolidated WebSocket upgrade conditions for clarity.
- Streamlined API request forwarding and fallback proxy settings for better organization and maintainability.
2026-01-14 11:57:35 +01:00
Torsten Schulz (local)
2bf949513b Refactor yourpart-https.conf for improved WebSocket and API request handling
- Consolidated WebSocket upgrade conditions for clarity and consistency.
- Streamlined API request forwarding configuration.
- Removed redundant proxy settings to enhance organization and maintainability of the configuration file.
2026-01-14 10:28:23 +01:00
Torsten Schulz (local)
84619fb656 Update proxy settings in yourpart-https.conf for improved WebSocket and API handling
- Added ProxyPreserveHost and ProxyRequests directives for better request handling.
- Configured WebSocket upgrade headers for Socket.io and daemon connections.
- Established HTTP proxies for API and WebSocket requests to ensure fallback mechanisms are in place.
- Improved overall clarity and organization of proxy settings in the configuration file.
2026-01-12 16:55:09 +01:00
Torsten Schulz (local)
b600f16ecd Enhance MessagesDialog component and localization for overproduction notifications
- Updated MessagesDialog.vue to extract additional parameters (value, branch_id, region_id) for better handling of overproduction scenarios.
- Modified localization files (de/falukant.json and en/falukant.json) to reflect changes in the overproduction notification format, including branch information.
- Improved data formatting for clarity in notifications related to production levels.
2026-01-12 16:48:10 +01:00
Torsten Schulz (local)
9273066f61 Refactor trait handling in FalukantService and FamilyView for improved data consistency
- Updated trait loading in FalukantService to include trait types, enhancing the mapping of traits to characters.
- Adjusted FamilyView to utilize the new trait structure, ensuring accurate retrieval of traits associated with characters.
- Improved null checks for relationships to prevent potential errors during data access.
2026-01-12 13:48:37 +01:00
Torsten Schulz (local)
7d59dbcf84 Update mood and character traits handling in FalukantService to ensure default values are set when no data is available. This change improves robustness in data retrieval by preventing potential undefined values. 2026-01-12 13:44:29 +01:00
Torsten Schulz (local)
015d1ae95b Refactor getGiftCost method in FalukantService for improved performance
- Changed getGiftCost from an asynchronous to a synchronous method, eliminating unnecessary await for better efficiency.
- Updated comments for clarity regarding the synchronous nature of gift cost calculation.
- Adjusted related code to reflect the synchronous execution, enhancing overall performance in gift cost retrieval.
2026-01-12 12:02:46 +01:00
Torsten Schulz (local)
e2cd6e0e5e Refactor relationship retrieval in FalukantService for improved performance
- Optimized the process of finding relationships by using two separate queries for better index utilization, reducing data overhead.
- Enhanced loading of related characters and traits by implementing parallel data fetching, improving efficiency in data retrieval.
- Updated timing metrics to reflect changes in the relationship loading process, ensuring accurate performance tracking.
2026-01-12 11:57:17 +01:00
Torsten Schulz (local)
ec113058d0 Enhance getGifts method in FalukantService with detailed performance metrics and optimized data retrieval
- Implemented timing metrics to track performance across various steps of the getGifts method, improving traceability.
- Refactored data loading to optimize user, character, and relationship queries, ensuring only necessary fields are fetched.
- Enhanced error handling and logging for better debugging and performance insights during execution.
2026-01-12 11:49:49 +01:00
Torsten Schulz (local)
d2ac2bfdd8 Optimize gift retrieval in FalukantService by loading only necessary fields and implementing parallel data fetching. This change enhances performance and reduces data overhead during the gift and title of nobility retrieval process. 2026-01-12 11:46:16 +01:00
Torsten Schulz (local)
d75fe18e6a Optimize user and character loading in FalukantService by querying only necessary fields. This change enhances performance and reduces data overhead during retrieval. 2026-01-12 11:35:38 +01:00
Torsten Schulz (local)
479f222b54 Refactor character retrieval in FalukantService for improved clarity
- Changed the character retrieval method to directly access the user's character property instead of querying the database, enhancing code readability and performance.
2026-01-12 11:31:49 +01:00
Torsten Schulz (local)
013c536b47 Refactor firstNameMap creation in FalukantService for improved efficiency
- Simplified the construction of firstNameMap by removing unnecessary spread operator, enhancing performance and readability.
2026-01-12 11:28:32 +01:00
Torsten Schulz (local)
3b983a0db5 Update attribute mapping in FalukantService for mood data retrieval
- Changed mood attribute from 'labelTr' to 'tr' in the mood query to align with updated data structure.
- Adjusted mood mapping to reflect the new attribute, ensuring consistency in data handling.
2026-01-12 11:24:56 +01:00
Torsten Schulz (local)
5f9559ac8d Update FalukantService to utilize FalukantCharacterTrait for character relationships
- Added import for FalukantCharacterTrait to enhance data retrieval for character relationships.
- Refactored the relationship character query to use FalukantCharacterTrait instead of CharacterTrait, improving data accuracy and consistency.
2026-01-12 11:16:23 +01:00
Torsten Schulz (local)
f487e6d765 Enhance getFamily method in FalukantService for performance and data retrieval
- Implemented detailed timing metrics to track performance across various steps of the getFamily method.
- Refactored data retrieval to load relationships and children in parallel, improving efficiency.
- Enhanced mapping of relationship and child data, including additional attributes for characters.
- Introduced batch loading for related data to minimize database queries and optimize performance.
- Improved error handling and logging for better traceability during execution.
2026-01-12 11:09:21 +01:00
Torsten Schulz (local)
5e26422e9c Add batch price retrieval for products in region
- Implemented a new endpoint to fetch prices for multiple products in a specified region in a single request, improving efficiency.
- Added validation for input parameters to ensure proper data handling.
- Updated the FalukantService to calculate prices based on knowledge factors and worth percentages for each product.
- Modified the frontend to utilize the new batch endpoint, optimizing the loading of product prices.
2026-01-12 08:58:28 +01:00
Torsten Schulz (local)
64baebfaaa Optimize proposal generation in FalukantService using CTEs
- Replaced multiple SQL queries with a single query utilizing Common Table Expressions (CTEs) for improved performance.
- Streamlined the exclusion of existing proposals and active directors directly within the SQL query.
- Enhanced logging for SQL queries and results, providing better traceability during proposal generation.
- Simplified the process of calculating average knowledge for character proposals, ensuring more efficient data handling.
2026-01-12 08:46:54 +01:00
Torsten Schulz (local)
521dec24b2 Enhance proposal generation logic in FalukantService
- Implemented exclusion of existing proposals and active directors to avoid duplicate character selections.
- Added detailed logging for SQL queries and fallback mechanisms when insufficient older characters are found.
- Improved character selection process by combining excluded character IDs and ensuring a diverse set of proposals.
- Streamlined the batch creation of director proposals with average knowledge calculations for better income predictions.
2026-01-12 08:39:41 +01:00
Torsten Schulz (local)
36f0bd8eb9 Refactor MessagesDialog component to improve parameter interpolation and description formatting
- Enhanced the description formatting logic to ensure proper interpolation of parameters in both title and description fields.
- Updated the handling of monetary values to ensure they are correctly converted to numbers before formatting.
- Improved comments for clarity on the interpolation process and effects handling.
2026-01-09 14:44:20 +01:00
Torsten Schulz (local)
d0a2b122b2 Implement enhanced partner search and NPC creation logic in FalukantService
- Added detailed logging for partner search criteria and results, improving traceability.
- Refactored partner search logic to include a fallback mechanism for searching across all regions if no partners are found in the specified region.
- Introduced a new method for creating NPCs when no suitable partners are available, ensuring a continuous flow in the partner matching process.
- Improved the handling of character attributes such as age and title of nobility during partner searches and NPC creation.
2026-01-09 14:37:55 +01:00
Torsten Schulz (local)
c80cc8ec86 Enhance logging and error handling in FalukantService and FamilyView
- Added detailed logging for partner search and creation processes in FalukantService to improve traceability and debugging.
- Refactored the partner search logic to use a dynamic where clause for better readability and maintainability.
- Implemented error handling in FamilyView's loadGifts method to ensure an empty array is returned on API errors, enhancing user experience.
2026-01-09 14:32:27 +01:00
Torsten Schulz (local)
3722bcf8c8 Enhance parameter extraction in MessagesDialog component for money and effect changes
- Added extraction of money_change_absolute and money_change_percent from value to parameters.
- Updated effects handling to include money_change, storage_damage, production_quality_change, transport_speed_change, and storage_capacity_change types.
- Improved logic to prevent overwriting parameters already extracted from value, ensuring accurate data representation.
2026-01-09 09:31:33 +01:00
Torsten Schulz (local)
0372d213c0 Refine city filtering in NPC creation process within AdminService
- Added explicit filtering for city-type regions to ensure only valid cities are processed during NPC creation.
- Enhanced logging to provide feedback on the number of cities found and their names, improving traceability in the NPC creation workflow.
- Updated comments for clarity on the importance of using city regions exclusively.
2026-01-07 17:15:17 +01:00
Torsten Schulz (local)
c322eb1e5a Add NPC creation status tracking and progress reporting in Admin module
- Implemented getNPCsCreationStatus method in AdminController to retrieve the status of NPC creation jobs.
- Enhanced AdminService to manage NPC creation jobs, including job ID generation, progress updates, and error handling.
- Updated frontend CreateNPCView to display progress of NPC creation, including estimated time remaining and job status.
- Added localization strings for progress reporting in both German and English.
- Improved overall user experience by providing real-time feedback during NPC creation processes.
2026-01-07 17:09:54 +01:00
Torsten Schulz (local)
b34dcac685 Refactor CreateNPCView layout for improved structure and styling
- Updated the template structure in CreateNPCView.vue to enhance layout with additional div wrappers for better styling and organization.
- Ensured the main content is encapsulated within a scrollable area, improving user experience during NPC creation.
2026-01-07 17:00:56 +01:00
Torsten Schulz (local)
4850f50c66 Update package-lock.json dependencies for improved stability and security
- Upgraded 'glob' to version 10.5.0.
- Updated 'body-parser' to version 1.20.4 and adjusted its dependencies.
- Enhanced 'express' to version 4.22.1 with updated dependencies.
- Bumped 'qs' to version 6.14.1 and modified its dependencies.
- Updated 'raw-body' to version 2.5.3 and adjusted its dependencies.
- Ensured compatibility with newer versions of dependencies across the project.
2026-01-07 16:59:42 +01:00
Torsten Schulz (local)
5996f819e8 Enhance NPC creation functionality and validation in Admin module
- Updated AdminController to validate the count parameter, ensuring it is between 1 and 500.
- Refactored NPC creation logic in AdminService to create NPCs for each city-title combination, improving efficiency.
- Enhanced frontend localization files to reflect changes in count descriptions and validation messages.
- Updated CreateNPCView to provide user guidance on count input and display detailed creation results.
2026-01-07 16:57:50 +01:00
Torsten Schulz (local)
4d967fe7a2 Update German and English navigation localization files to include user rights translations 2026-01-07 16:49:33 +01:00
Torsten Schulz (local)
bb91c2bbe5 Add NPC creation and titles retrieval functionality in Admin module
- Implemented createNPCs method in AdminController to handle NPC creation with specified parameters including region, age, title, and count.
- Added getTitlesOfNobility method in AdminController to retrieve available titles for users.
- Updated adminRouter to include new routes for creating NPCs and fetching titles.
- Enhanced navigationController and frontend localization files to support new NPC creation feature.
- Introduced corresponding UI components and routes for NPC management in the admin interface.
2026-01-07 16:45:39 +01:00
Torsten Schulz (local)
511df52c3c Enhance MessagesDialog component to support HTML content and improve parameter extraction
- Updated notification description rendering to allow HTML content using v-html directive.
- Refactored formatBody method to better handle JSON formatted notifications and extract parameters from nested structures.
- Introduced new method for extracting parameters from value objects, improving compatibility with various notification types.
- Enhanced description formatting to include details from effects, providing richer user feedback in notifications.
2026-01-07 12:09:25 +01:00
Torsten Schulz (local)
d42e1da14b Refactor character creation and heir fetching logic in FalukantService and OverviewView
- Updated character creation process to utilize transactions for improved data integrity during heir generation.
- Enhanced heir fetching logic in OverviewView to check for both mainBranchRegion.id and mainBranchRegionId, adding error handling for missing regions.
- Added warnings for empty heir responses from the API to improve debugging and user feedback.
2026-01-07 11:20:03 +01:00
Torsten Schulz (local)
75dbd78da1 Add regional event handling and character creation logic in FalukantService
- Introduced new regional events ('regional_storm', 'regional_epidemic', 'earthquake') with specific health impact mechanics to enhance gameplay dynamics.
- Updated health modification logic to ensure regional events cause moderate health loss rather than fatal outcomes.
- Implemented character creation logic to generate potential heirs when none are found, including random gender, names, and age attributes.
- Enhanced heir retrieval process to include newly created characters, ensuring a seamless user experience.
2026-01-07 11:13:54 +01:00
Torsten Schulz (local)
c90b7785c0 Add heir selection functionality in Falukant module
- Implemented getPotentialHeirs and selectHeir methods in FalukantService to allow users to retrieve and select potential heirs based on specific criteria.
- Updated FalukantController to wrap new methods with user authentication and added corresponding routes in FalukantRouter.
- Enhanced OverviewView component to display heir selection UI when no character is present, including loading states and error handling.
- Added translations for heir selection messages in both German and English locales to improve user experience.
2026-01-07 10:29:16 +01:00
Torsten Schulz (local)
c17af04cbf Refactor vocabulary search functionality in VocabService and update UI components
- Modified the searchVocabs method in VocabService to consolidate search parameters into a single query term for improved flexibility.
- Updated VocabSearchDialog to replace separate input fields for mother tongue and learning language with a unified search term input.
- Adjusted button logic to enable search only when a term is provided, enhancing user experience.
- Added new translations for the search term in both German and English locales to support the updated UI.
2026-01-05 16:58:18 +01:00
Torsten Schulz (local)
f5e3a9a4a2 Add search functionality for vocabulary in VocabController and VocabService
- Implemented a new searchVocabs method in VocabService to allow users to search for vocabulary based on learning and mother tongue terms.
- Updated VocabController to include the searchVocabs method wrapped with user authentication.
- Added a new route in vocabRouter for searching vocabulary by language ID.
- Enhanced VocabChapterView and VocabLanguageView components to include a button for opening the search dialog.
- Added translations for search-related terms in both German and English locales, improving user accessibility.
2026-01-05 16:53:38 +01:00
Torsten Schulz (local)
dab3391aa2 Refactor socket.io URL normalization logic for improved clarity and robustness
- Simplified the normalization process for socket.io URLs by removing unnecessary production checks.
- Enhanced comments for better understanding of URL handling, particularly regarding environment variables and port management.
- Maintained fallback mechanisms for URL parsing failures to ensure consistent behavior across environments.
2026-01-05 16:43:42 +01:00
Torsten Schulz (local)
0336c55560 Enhance socket.io URL handling for production environments
- Added logic to normalize the socket.io URL based on the current origin and environment.
- Implemented fallback mechanisms for unusual ports in production to ensure secure connections.
- Included error handling for URL parsing failures to default to the current origin, improving robustness.
2026-01-05 16:26:23 +01:00
Torsten Schulz (local)
8e618ab443 Implement TLS support in WebSocket server for secure connections
- Added environment variable configuration for enabling TLS in the WebSocket server.
- Implemented logic to read TLS key, certificate, and optional CA paths from environment variables.
- Enhanced server initialization to handle both secure (WSS) and non-secure (WS) connections based on the TLS setting.
- Included error handling for missing TLS configuration to prevent server startup failures.
2026-01-05 16:06:37 +01:00
Torsten Schulz (local)
352d672bdd Add keyboard handling in VocabPracticeDialog for improved user interaction
- Implemented keydown event listener to manage Enter key functionality for navigating questions and submitting answers.
- Enhanced user experience by allowing Enter to trigger actions based on the current dialog state, improving accessibility during practice sessions.
- Ensured proper cleanup of event listeners when closing the dialog to prevent memory leaks.
2025-12-30 20:22:04 +01:00
Torsten Schulz (local)
df64c0a4b5 Update VocabPracticeDialog and VocabChapterView components to manage practice dialog state
- Changed the close button visibility in VocabPracticeDialog to false for a cleaner UI.
- Enhanced VocabChapterView to manage the practice dialog state with a new `practiceOpen` flag.
- Updated the `openPractice` method to handle the dialog's open and close events, ensuring proper state management.
2025-12-30 18:44:35 +01:00
Torsten Schulz (local)
83597d9e02 Add Vocab Trainer feature with routing, database schema, and translations
- Introduced Vocab Trainer functionality, including new routes for managing languages and chapters.
- Implemented database schema for vocab-related tables to ensure data integrity.
- Updated navigation and UI components to include Vocab Trainer in the social network menu.
- Added translations for Vocab Trainer in both German and English locales, enhancing user accessibility.
2025-12-30 18:34:32 +01:00
Torsten Schulz (local)
a09220b881 Add translations for reputation action school funding in German and English locales 2025-12-23 14:37:33 +01:00
Torsten Schulz (local)
5623f3af09 Refactor error handling in FalukantService and enhance user feedback in HealthView
- Changed error throwing in FalukantService to use PreconditionError for better clarity.
- Added translations for "too close" error in both German and English locales.
- Improved user feedback in HealthView by displaying error messages in a dialog upon measure execution failure.
2025-12-23 12:20:37 +01:00
Torsten Schulz (local)
820b5e8570 Enhance health management in FalukantService to improve user feedback and error handling
- Updated health deduction logic to clarify the distinction between health and monetary costs.
- Added error handling for money update failures to ensure robust transaction processing.
- Modified return value to include updated health status, providing clearer information for UI updates.
2025-12-23 10:51:22 +01:00
Torsten Schulz (local)
dc72ed2feb Add taxFromSaleProduct translation and enhance MoneyHistoryView for product loading
- Added new translation keys for "taxFromSaleProduct" in both German and English locales.
- Updated MoneyHistoryView component to load product data asynchronously and map product IDs to their labels for improved activity translation handling.
- Enhanced activity translation logic to support both legacy and new formats for tax-related activities.
2025-12-22 15:17:51 +01:00
Torsten Schulz (local)
ea468c9878 Improve conditionLabel method in BranchView component to handle edge cases and ensure accurate state representation. Added checks for null and undefined values, and clarified the return value for zero or negative conditions. 2025-12-22 14:54:51 +01:00
Torsten Schulz (local)
d1b683344e Update condition handling in FalukantService and syncDatabase utility for legacy data
- Enhanced condition processing in FalukantService to clamp values between 0 and 100, ensuring UI displays valid data.
- Implemented database cleanup in syncDatabase utility to set NULL conditions to 100 and clamp out-of-range values, improving data integrity.
2025-12-22 13:20:16 +01:00
Torsten Schulz (local)
a82ec7de3f Add cooldown feature for reputation actions in FalukantService and update UI components
- Introduced a cooldown mechanism for reputation actions, limiting execution to once per configured interval.
- Updated FalukantService to handle cooldown logic and return remaining cooldown time.
- Enhanced ReputationView component to display cooldown status and prevent action execution during cooldown.
- Added translations for cooldown messages in both German and English locales.
2025-12-21 22:18:29 +01:00
Torsten Schulz (local)
560a9efc69 Refactor ReputationView component to streamline action display and remove deprecated tab
- Added a new section for displaying reputation actions, including daily limits and action details.
- Removed the 'actions' tab from the navigation and adjusted the logic to reflect this change.
- Enhanced the user interface for executing reputation actions with improved button states and translations.
2025-12-21 21:37:22 +01:00
Torsten Schulz (local)
4f8b1e33fa Update message dialog parameters in ReputationView component for improved clarity
- Swapped the order of parameters in the message dialog open method to prioritize message content over the title, enhancing user experience during reputation action notifications.
2025-12-21 21:14:06 +01:00
Torsten Schulz (local)
38dd51f757 Add reputation actions feature to Falukant module
- Introduced new endpoints for retrieving and executing reputation actions in FalukantController and falukantRouter.
- Implemented service methods in FalukantService to handle reputation actions, including daily limits and action execution logic.
- Updated the frontend ReputationView component to display available actions and their details, including cost and potential reputation gain.
- Added translations for reputation actions in both German and English locales.
- Enhanced initialization logic to set up reputation action types in the database.
2025-12-21 21:09:31 +01:00
Torsten Schulz (local)
38f23cc6ae Enhance getFalukantUserOrFail and createParty methods in FalukantService to support transaction options
- Updated getFalukantUserOrFail to accept an options parameter for transaction handling.
- Refactored createParty to utilize transaction support, ensuring atomic operations for party creation and related financial transactions.
- Improved error handling for party creation, including checks for existing parties within a 24-hour window and validation of selected options.
2025-12-20 23:30:10 +01:00
Torsten Schulz (local)
6cf8fa8a9c Add reputation attribute to FalukantCharacter model and update related services and views
- Introduced a new 'reputation' attribute in the FalukantCharacter model with a default value and validation.
- Updated FalukantService to include 'reputation' in character attributes for API responses.
- Enhanced ReputationView component to display current reputation and load it from the API.
- Added translations for reputation in both German and English locales.
2025-12-20 23:00:31 +01:00
Torsten Schulz (local)
f9ea4715d7 Refactor BranchView component to replace JavaScript alerts with a message dialog for success and error notifications. This improves user experience by providing a more integrated feedback mechanism within the UI. 2025-12-20 22:14:39 +01:00
Torsten Schulz (local)
b34b374f76 Refactor sellAllProducts method in FalukantService to ensure atomic transactions for selling products, updating inventory, and handling financial transactions. Implement batch processing for sell items and enhance error handling for inventory deletions. Update updateFalukantUserMoney function to support transactions, improving consistency and reliability in financial operations. 2025-12-20 22:04:29 +01:00
Torsten Schulz (local)
83d1168f26 Refactor speedLabel method in SaleSection component to move it from computed properties to methods for better compatibility with Vue3. This change ensures the function is callable and maintains its intended functionality. 2025-12-20 21:32:53 +01:00
Torsten Schulz (local)
91009f52cd Refactor SaleSection component to utilize direct property assignment for betterPrices, enhancing reactivity in Vue3. Update inventory mapping to ensure betterPrices is always an array. 2025-12-20 21:28:01 +01:00
Torsten Schulz (local)
c6dfca7052 Enhance SaleSection component to improve UI responsiveness during sales. Reset selling state immediately after sell actions and update inventory handling to ensure user feedback is timely and accurate. 2025-12-20 21:09:10 +01:00
Torsten Schulz (local)
aaeaeeed24 Add request and SQL performance logging features to backend
- Implement request timing middleware in app.js to log slow requests and all requests based on environment variables.
- Enhance sequelize.js with optional SQL query timing and logging capabilities, allowing for performance monitoring of database queries.
2025-12-20 16:35:30 +01:00
Torsten Schulz (local)
c5804f764c Optimize getInventory method in FalukantService by replacing nested includes with a single SQL query for improved performance. Add error handling for invalid branchId input. 2025-12-20 16:13:33 +01:00
Torsten Schulz (local)
fbe0d1bcd1 Add error handling for missing branches in sell batch processing in FalukantService. Ensure that missing branch IDs trigger an error to prevent accounting mismatches. 2025-12-20 16:01:18 +01:00
Torsten Schulz (local)
2fb440f033 Implement synchronous price calculation for batch operations in FalukantService, optimizing performance by reducing database queries. Update inventory handling to batch delete items and enhance revenue calculations. Fix translation formatting in German locale for sellAllSuccess message. 2025-12-20 15:37:16 +01:00
Torsten Schulz (local)
a8a136a9ce Enhance SaleSection component to manage selling state with improved user feedback. Disable buttons during selling, show status messages for sellAll actions, and update translations for new statuses. 2025-12-20 15:35:20 +01:00
Torsten Schulz (local)
fcbb903839 Backend error fixed 2025-12-20 15:06:43 +01:00
Torsten Schulz (local)
ac45a2ba26 Refactor SQL joins in calcRegionalSellPrice function of FalukantService to use updated region type table for improved clarity and consistency in tax calculations. 2025-12-20 15:03:03 +01:00
Torsten Schulz (local)
afe15dd4f5 Refactor tax calculation in calcRegionalSellPrice function of FalukantService to convert exemptTypes Set to PostgreSQL array string for improved query performance and clarity. 2025-12-20 14:54:32 +01:00
Torsten Schulz (local)
e3df88bea0 Enhance getCumulativeTaxPercentWithExemptions function in FalukantService to first retrieve the character associated with the userId, ensuring accurate filtering of political offices by characterId for regional tax calculations. 2025-12-20 11:09:03 +01:00
Torsten Schulz (local)
c69a414f78 Fix cumulative tax calculation in FalukantService by using falukantUser.id instead of user.id for accurate regional tax assessments. 2025-12-20 11:04:21 +01:00
Torsten Schulz (local)
d08022ab94 Merge branch 'main' of ssh://tsschulz.de:2222/torsten/yourpart3 2025-12-20 10:54:58 +01:00
Torsten Schulz (local)
66e6fab663 Refactor getCumulativeTaxPercentWithExemptions function in falukantService.js to filter political offices by userId through the FalukantCharacter model. Update query structure to enhance clarity and ensure accurate data retrieval for regional tax calculations. 2025-12-20 10:48:56 +01:00
4da572822e Merge pull request 'Update dependency nodemon to v3.1.11' (#3) from renovate/nodemon-3.x-lockfile into main
Reviewed-on: #3
2025-12-19 16:16:21 +01:00
ee23bb3ba3 Merge pull request 'Replace dependency npm-run-all with npm-run-all2 ^5.0.0' (#2) from renovate/npm-run-all-replacement into main
Reviewed-on: #2
2025-12-19 16:16:12 +01:00
d002e340dd Update dependency nodemon to v3.1.11 2025-12-19 16:14:33 +01:00
0e1d87ddab Replace dependency npm-run-all with npm-run-all2 ^5.0.0 2025-12-19 16:14:29 +01:00
2a4928c1b6 Merge pull request 'Configure Renovate' (#1) from renovate/configure into main
Reviewed-on: #1
2025-12-19 16:07:26 +01:00
efe2bd57ab Add renovate.json 2025-12-19 16:00:42 +01:00
Torsten Schulz (local)
a0aa678e7d Implement logic to create tables without Foreign Key constraints in sequelize.js when referenced tables do not exist. Enhance error handling and logging to provide clear feedback during synchronization attempts, improving robustness in model management. 2025-12-19 08:37:40 +01:00
Torsten Schulz (local)
a1b6e6ab59 Enhance error handling in sequelize.js for Foreign Key Constraint Errors by adding logging for orphaned records and skipping problematic models during synchronization. Update syncDatabase.js to include cleanup logic for orphaned political_office entries, improving database integrity and user feedback during sync operations. 2025-12-19 08:34:04 +01:00
Torsten Schulz (local)
73acf1d1cd Refactor error handling in sequelize.js to skip model synchronization for cases with duplicate pg_description entries or multiple tables with the same name. Update logging to provide clearer feedback on sync failures and the reasons for skipping models, enhancing user understanding of potential issues. 2025-12-19 08:13:52 +01:00
Torsten Schulz (local)
48110e9a6f Improve error handling and logging for duplicate pg_description cleanup in sequelize.js. Update comments for clarity on permission requirements and provide detailed instructions for manual cleanup by database administrators. Enhance user feedback during synchronization attempts to address potential permission issues. 2025-12-19 07:56:07 +01:00
Torsten Schulz (local)
642e215c69 Refactor duplicate entry cleanup in sequelize.js by replacing DO $$ blocks with direct parameter substitution in SQL queries. This change enhances performance and security while maintaining the logic for cleaning up duplicate pg_description entries before and after model synchronization. 2025-12-19 07:53:34 +01:00
Torsten Schulz (local)
091b9ff70a Enhance model synchronization in sequelize.js by adding logic to clean up duplicate pg_description entries before and after sync attempts. Implement error handling for potential sync failures related to duplicate entries, improving robustness and clarity in foreign key management during model synchronization. 2025-12-18 17:53:24 +01:00
Torsten Schulz (local)
86f753c745 Refactor associations in models to include constraints: false, preventing automatic foreign key creation. Update sequelize.js to enhance foreign key management during model synchronization, ensuring associations are restored correctly after sync operations. 2025-12-18 17:44:17 +01:00
Torsten Schulz (local)
c28f8b1384 Enhance foreign key management in sequelize.js by refining schema handling and improving logging for foreign key removal during model synchronization. Add detailed console outputs for better visibility on foreign key operations and error handling. 2025-12-18 16:45:56 +01:00
Torsten Schulz (local)
9b36297171 Implement foreign key removal before model synchronization in sequelize.js to prevent conflicts during sync. Add error handling and logging for better visibility on foreign key management. 2025-12-18 16:39:34 +01:00
Torsten Schulz (local)
7beed235d7 Improve model synchronization in sequelize.js by temporarily removing associations to prevent automatic foreign key creation. Add logging for association management during the sync process, ensuring clarity in model handling. 2025-12-18 16:36:26 +01:00
Torsten Schulz (local)
a0206dc8cb Add logging for model synchronization and cache handling in syncDatabase.js
Enhance sequelize.js by adding a console log to indicate when models are being synced without constraints. Update syncDatabase.js to include important notes on caching issues with Node.js ES-Modules and log the model loading process during deployment synchronization.
2025-12-18 16:34:10 +01:00
Torsten Schulz (local)
bf0eed3b03 Update model synchronization in sequelize.js to prevent automatic foreign key creation by adding constraints: false, ensuring foreign keys are managed through migrations only. 2025-12-18 16:14:53 +01:00
Torsten Schulz (local)
c8072b8052 Refactor multiple models to remove foreign key references while maintaining required fields, enhancing data integrity and simplifying model definitions. 2025-12-18 16:08:30 +01:00
Torsten Schulz (local)
c66fbf1a62 Enhance syncDatabase function to include cleanup for orphaned child_relation entries with invalid father_character_id, mother_character_id, or child_character_id references, improving data integrity and logging consistency. 2025-12-18 15:59:35 +01:00
Torsten Schulz (local)
e13a711a60 Refactor user_house model to remove default values for houseTypeId and userId fields, and enhance syncDatabase function to include cleanup for orphaned user_house entries with invalid house_type_id or user_id references, improving data integrity and logging. 2025-12-18 15:57:39 +01:00
Torsten Schulz (local)
346a326bfd Enhance syncDatabase function to include cleanup for orphaned promotional_gift entries, removing invalid sender and recipient character references, and improve logging for orphaned entry detection. 2025-12-18 15:49:34 +01:00
Torsten Schulz (local)
addb8e9a6d Refactor Notification model to remove VIRTUAL field definition for characterName and implement a getter method for improved data handling and synchronization. 2025-12-18 15:43:54 +01:00
Torsten Schulz (local)
ea8b9e661d Refactor VIRTUAL field detection logic in sequelize.js to improve accuracy and add special handling for Notification model's characterName field, addressing a Sequelize bug related to field mapping. 2025-12-18 15:37:52 +01:00
Torsten Schulz (local)
339ae844e9 Enhance VIRTUAL field detection in sequelize.js by implementing multiple identification methods, ensuring accurate model synchronization and preventing unintended field removals. 2025-12-18 15:34:26 +01:00
Torsten Schulz (local)
a0a7e81927 Add socket notification for status bar updates in FalukantService and enhance model sync by handling VIRTUAL fields in sequelize.js 2025-12-18 15:25:24 +01:00
Torsten Schulz (local)
31c23a0c40 Refactor syncDatabase function to improve orphaned entry cleanup for knowledge and notification, ensuring data integrity and consistent logging. 2025-12-18 15:20:09 +01:00
Torsten Schulz (local)
c1f22246ea Add cleanup for orphaned notification entries in syncDatabase functions to remove invalid user_id references, improving data integrity and logging consistency. 2025-12-18 15:18:21 +01:00
Torsten Schulz (local)
0a1388bf06 Add cleanup for orphaned knowledge entries in syncDatabase functions to remove invalid character_id and product_id references, enhancing data integrity and logging. 2025-12-18 15:17:01 +01:00
Torsten Schulz (local)
1a69b83983 Refactor stock cleanup logic in syncDatabase functions to remove orphaned stock entries with invalid branch_id and streamline logging for orphaned entries. 2025-12-18 15:13:24 +01:00
Torsten Schulz (local)
63f9443b77 Implement cleanup of orphaned user_param_visibility entries before schema updates in syncDatabase functions 2025-12-18 15:11:50 +01:00
Torsten Schulz (local)
6a9b2b8d1d Add index on (user_id, shown) in notification table to optimize markNotificationsShown queries and prevent deadlocks. Implement transaction handling in markNotificationsShown method for atomic updates. 2025-12-18 15:04:37 +01:00
Torsten Schulz (local)
8e1e0968ae Refactor product model by removing unused sellCostMinNeutral and sellCostMaxNeutral fields, and simplify product insertion logic in initialization script. 2025-12-18 14:41:56 +01:00
Torsten Schulz (local)
a486292880 Activate pgcrypto extension for digest() function in database initialization and migration scripts 2025-12-18 14:11:15 +01:00
Torsten Schulz (local)
ee4b0ee7c2 Füge Spalte product_quality zur Tabelle stock hinzu und erstelle Migration für weather_type_id in production 2025-12-16 13:00:29 +01:00
Torsten Schulz (local)
43d86cce18 Implement tax handling for branches by adding tax percent to regions, updating product sell costs, and enhancing UI for tax summaries in BranchView 2025-12-09 16:16:08 +01:00
Torsten Schulz (local)
25d7c70058 Enhance transport mode handling by adding localized labels and updating related components in MapRegionsView and BranchView 2025-12-09 11:53:56 +01:00
Torsten Schulz (local)
71c62cf5e8 Enhance vehicle speed display by adding localized labels in DirectorInfo, SaleSection, and BranchView components 2025-12-09 11:45:35 +01:00
Torsten Schulz (local)
a7350282ee Enhance parameter extraction in MessagesDialog by merging nested parameters for improved notification handling 2025-12-09 00:12:05 +01:00
Torsten Schulz (local)
676629bd8d Enhance notification enrichment by recursively collecting character IDs and attaching character names 2025-12-09 00:06:09 +01:00
Torsten Schulz (local)
1892877b11 Enhance notification handling by enriching notifications with character names 2025-12-08 23:55:50 +01:00
Torsten Schulz (local)
be218aabf7 Add character_name field and trigger for notifications in Falukant module 2025-12-08 23:37:07 +01:00
Torsten Schulz (local)
856f7d56bf Enhance parameter extraction for notifications in MessagesDialog component 2025-12-08 16:12:05 +01:00
Torsten Schulz (local)
000ebbdc2b Enhance currency formatting in MoneyHistoryView component 2025-12-08 15:35:17 +01:00
Torsten Schulz (local)
791314bef2 Enhance notification display and localization in MessagesDialog component
- Updated the MessagesDialog component to display notifications with titles and descriptions, improving clarity and user experience.
- Enhanced the formatBody method to support new notification structures, including extraction and formatting of parameters for better message presentation.
- Added a new formatParams method to handle various parameter types, ensuring accurate representation of values in notifications.
- Updated localization files in both German and English to include structured titles and descriptions for random events, enriching the user experience with detailed information.
2025-12-08 14:42:17 +01:00
Torsten Schulz (local)
bcb0b01324 Enhance child management features in Falukant module
- Added new translations for gender, baptism status, and child details in both German and English localization files, improving user experience.
- Integrated ChildDetailsDialog component into FamilyView for displaying detailed information about children.
- Updated the showChildDetails method to utilize the new dialog for better user interaction.
- Modified button styles for improved visual feedback when setting heirs.
2025-12-08 13:30:11 +01:00
Torsten Schulz (local)
03e3a21a25 Add heir management functionality in Falukant module
- Implemented setHeir method in FalukantService to designate a child as heir, including validation checks for user and child relationships.
- Updated FalukantController to expose the setHeir endpoint, allowing users to set heirs via the API.
- Enhanced FalukantRouter with a new route for setting heirs.
- Modified FamilyView component to include UI elements for setting heirs, with success and error feedback.
- Updated localization files in both German and English to include new translations related to heir management, improving user experience.
2025-12-08 13:22:43 +01:00
Torsten Schulz (local)
e97a2a62c9 Enhance weather data handling in FalukantService and update localization files
- Modified the FalukantService to explicitly load weather data for all regions, ensuring accurate weather information is associated with branches.
- Updated the return logic to utilize the newly loaded weather data, improving data accuracy in branch responses.
- Added new random event messages in both German and English localization files, enhancing user experience with richer event descriptions.
2025-12-08 11:54:10 +01:00
Torsten Schulz (local)
814f972287 Update branch selection logic in BranchView component
- Enhanced the onBranchSelected method to reload branches for updated weather information and reset the selected branch after reloading.
- Improved user experience by ensuring the correct branch is selected post-refresh, maintaining data accuracy and consistency.
2025-12-08 11:34:50 +01:00
Torsten Schulz (local)
274c2a3292 Add income update success message in DirectorInfo component
- Implemented a success message display for income updates in the DirectorInfo component, enhancing user feedback after successful updates.
- Added a timeout to automatically hide the success message after 3 seconds.
- Updated localization files to include new translations for income-related messages in both German and English, improving user experience for multilingual users.
2025-12-08 11:30:31 +01:00
Torsten Schulz (local)
4dbcebfab8 Add handling for transport removal events in BranchView component
- Implemented logic to update vehicle and inventory data when a transport is removed, ensuring real-time synchronization with the selected branch.
- Enhanced the component to refresh relevant sections (vehicles, inventory, storage) based on the transport removal event, improving user experience and data accuracy.
2025-12-08 09:36:18 +01:00
Torsten Schulz (local)
fadc301d41 Add bulk vehicle repair functionality in Falukant module
- Implemented a new repairAllVehicles method in FalukantService to handle the repair of multiple vehicles at once, including cost calculation and precondition checks.
- Updated FalukantController to expose the repairAllVehicles endpoint, allowing users to initiate bulk repairs via the API.
- Enhanced FalukantRouter to include a new route for bulk vehicle repairs.
- Modified BranchView component to add UI elements for repairing all vehicles, including a dialog for confirmation and displaying repair details.
- Updated German localization files to include translations related to bulk vehicle repair actions, improving user experience for German-speaking users.
2025-12-08 08:36:21 +01:00
Torsten Schulz (local)
b1d29f2083 Enhance nobility ID validation in FalukantService
- Added checks to ensure that provided nobility IDs are valid and exist in the database, improving error handling and user feedback.
- Updated logic to use loaded nobility objects when adding invited nobilities to a party, optimizing database interactions.
2025-12-05 20:49:12 +01:00
Torsten Schulz (local)
e756b3692d Refactor availability status logic in FalukantService
- Enhanced the logic for determining the availability status of vehicles based on the 'availableFrom' date.
- Added conditions to differentiate between 'building' and 'available' statuses, improving clarity and accuracy in status reporting.
- Updated comments for better understanding of the code flow.
2025-12-05 17:23:54 +01:00
Torsten Schulz (local)
74a3d59800 Add vehicle repair functionality in Falukant module
- Implemented a new repairVehicle method in FalukantService to handle vehicle repairs, including cost calculation and precondition checks.
- Updated FalukantController to expose the repairVehicle endpoint, allowing users to initiate repairs via the API.
- Enhanced FalukantRouter to include a new route for vehicle repairs.
- Modified BranchView component to add UI elements for repairing vehicles, including a dialog for repair confirmation and displaying repair details.
- Updated German localization files to include translations related to vehicle repair actions, improving user experience for German-speaking users.
2025-12-05 14:40:55 +01:00
Torsten Schulz (local)
0544a3dfde Add transport and inventory update handling in BranchView component
- Implemented socket event listeners for 'transport_arrived' and 'inventory_updated' to manage real-time updates in the BranchView component.
- Enhanced event handling logic to refresh vehicle and inventory data based on the selected branch, improving user experience and data accuracy.
- Updated the component to ensure proper cleanup of socket listeners on component destruction, maintaining optimal performance.
2025-12-05 14:13:14 +01:00
Torsten Schulz (local)
656c821986 Enhance SaleSection component to group and display transport data
- Updated SaleSection.vue to group running transports by relevant attributes, improving data organization and readability.
- Added a new computed property to calculate vehicle counts and total quantities for grouped transports.
- Introduced a new column in the UI to display the count of vehicles associated with each transport group.
- Updated German localization file to include translation for 'runningVehicleCount', enhancing user experience for German-speaking users.
2025-12-05 13:12:24 +01:00
Torsten Schulz (local)
865ef81012 Enhance FalukantService and UI components for improved product handling
- Updated FalukantService to allow optional inclusion of productType in queries, enhancing flexibility in data retrieval.
- Modified SaleSection.vue to conditionally display product information and size, improving user experience by handling cases with no product.
- Added new German translation for 'runningNoProduct' to enhance localization support for users.
2025-12-05 13:07:31 +01:00
Torsten Schulz (local)
5ad27a87e5 Enhance vehicle transport functionality in FalukantService and update UI components
- Modified the createTransport method in FalukantService to support optional vehicleIds, allowing for more flexible vehicle selection.
- Implemented logic to ensure that either specific vehicleIds or a vehicleTypeId must be provided, improving error handling for vehicle availability.
- Updated the BranchView component to include new UI elements for sending vehicles, including buttons for sending single or multiple vehicles of the same type.
- Added a modal dialog for selecting target branches when sending vehicles, enhancing user experience and streamlining transport operations.
- Updated German localization files to include new translations related to vehicle actions and transport functionalities.
2025-12-05 12:49:37 +01:00
Torsten Schulz (local)
085b851925 Add German translation for 'townhouse' in falukant.json
- Updated the German localization file to include the translation for 'townhouse' as 'Stadthaus'.
- This addition enhances the application's multilingual support and improves user experience for German-speaking users.
2025-12-05 11:42:41 +01:00
Torsten Schulz (local)
98dea7dd39 Implement empty transport feature in DirectorInfo component
- Added functionality to allow directors to initiate empty transports without products, enhancing logistics management.
- Introduced a new transport form in the DirectorInfo component, enabling selection of vehicle types and target branches.
- Updated the i18n localization files to include new translations for the empty transport feature.
- Enhanced the BranchView to pass vehicle and branch data to the DirectorInfo component, ensuring proper functionality.
- This update aims to improve user experience and streamline transport operations within the application.
2025-12-04 14:48:55 +01:00
Torsten Schulz (local)
e5ef334f7c Update FalukantService and PoliticsView to enhance election data handling
- Modified the FalukantService to use getOpenPolitics instead of getElections for retrieving accessible elections, improving alignment with frontend data display.
- Updated the PoliticsView to handle the response from the application submission more effectively, ensuring that already applied positions remain pre-selected after submission.
- These changes aim to streamline the election data flow and enhance user experience in the application process.
2025-12-03 17:19:13 +01:00
Torsten Schulz (local)
d6ea09b3e2 Enhance RevenueSection UI and streamline price loading logic
- Updated the display of city prices in the RevenueSection component to include both city names and formatted price values, improving user experience.
- Removed unnecessary console logs from the loadPricesForAllProducts method to clean up the code and reduce clutter, while maintaining essential functionality.
- Simplified the getBetterPrices method by eliminating redundant logging, enhancing code clarity and performance.
2025-12-03 16:30:10 +01:00
Torsten Schulz (local)
a51b8a1ff6 Fix 2025-12-03 16:29:56 +01:00
Torsten Schulz (local)
3c885b6ab9 Add detailed debug logging in loadPricesForAllProducts method of RevenueSection
- Enhanced the loadPricesForAllProducts method with additional console logs to track the loading process of product prices, including the current region ID and the number of products being processed.
- Improved visibility into the state of betterPricesMap after updates and provided detailed logs for each product's price loading, facilitating easier debugging and monitoring of price retrieval.
- Aims to enhance traceability and provide clearer insights into the price handling process within the RevenueSection component.
2025-12-03 16:22:08 +01:00
Torsten Schulz (local)
6b3b30108b Refactor betterPricesMap updates in RevenueSection for Vue 3 reactivity
- Updated the handling of betterPricesMap to create a new object for state updates, ensuring reactivity in Vue 3.
- This change replaces direct assignments with spread operator syntax to maintain the integrity of the reactive system.
- Aims to improve performance and align with Vue 3 best practices for state management.
2025-12-03 16:15:01 +01:00
Torsten Schulz (local)
7fab23d22b Refactor betterPricesMap handling in RevenueSection for Vue 3 compatibility
- Removed the use of $set for updating betterPricesMap, leveraging direct assignment instead, which is now the standard in Vue 3.
- Simplified the getBetterPrices method by eliminating unnecessary logging, enhancing code clarity while maintaining functionality.
- These changes aim to improve performance and align with Vue 3 best practices for state management.
2025-12-03 16:03:06 +01:00
Torsten Schulz (local)
def88f6486 Add debug logging in RevenueSection for better price retrieval tracking
- Introduced console logs to track the number of better prices received for each product and the state of the betterPricesMap after updates.
- Enhanced the getBetterPrices method with logging to provide visibility into the prices being returned, improving traceability during price evaluations.
- These changes aim to facilitate debugging and provide clearer insights into the price handling process within the RevenueSection component.
2025-12-03 15:59:15 +01:00
Torsten Schulz (local)
1797ae3e58 Remove debug logging from getProductPricesInCities method in FalukantService
- Eliminated console logs that tracked various parameters and results within the getProductPricesInCities method, streamlining the code and reducing output clutter.
- This change aims to enhance code readability and maintain focus on essential functionality while maintaining the integrity of the price calculation process.
2025-12-03 15:55:30 +01:00
Torsten Schulz (local)
f768ba3b27 Add debug logging for priceInCity in getProductPricesInCities method of FalukantService
- Introduced a console log to capture the values of priceInCity, currentPrice, and PRICE_TOLERANCE, enhancing visibility into the price comparison process.
- This addition aims to improve traceability and facilitate debugging during price evaluations, building on previous logging enhancements.
2025-12-03 15:39:57 +01:00
Torsten Schulz (local)
b3e48a0b06 Refine price comparison logic in getProductPricesInCities method of FalukantService
- Introduced a small tolerance (0.01) for rounding errors in the price comparison, allowing for more accurate evaluations when determining if a city's price exceeds the current price.
- This change enhances the robustness of price calculations by accommodating potential floating-point inaccuracies.
2025-12-03 15:35:19 +01:00
Torsten Schulz (local)
3f56939421 Add detailed debug logging in getProductPricesInCities method of FalukantService
- Introduced console logs to trace the execution flow and key variables in the getProductPricesInCities method, enhancing visibility into product price calculations.
- Logged parameters such as productId, currentPrice, and currentRegionId at the start of the method.
- Added logs for the number of cities and town worth entries found, as well as details when skipping the current city and adding results, improving traceability during price evaluations.
- This update aims to facilitate debugging and performance monitoring by providing comprehensive insights into the pricing logic.
2025-12-03 13:39:09 +01:00
Torsten Schulz (local)
87c720c3fe Refactor RevenueSection to utilize a betterPricesMap for improved price handling
- Replaced direct product.betterPrices usage with a betterPricesMap to store prices separately, enhancing data management.
- Updated computed properties and methods to clear betterPricesMap when product list or region changes, ensuring accurate price loading.
- Introduced getBetterPrices method for cleaner access to price data, improving code readability and maintainability.
2025-12-03 13:32:02 +01:00
Torsten Schulz (local)
90fbcaf31d Refactor and remove debug logging in FalukantService and RevenueSection for cleaner code
- Eliminated console logs in the getProductPricesInCities method of FalukantService to streamline the price calculation process and reduce clutter in the output.
- Removed unnecessary debug logs in RevenueSection related to loading product prices, enhancing performance and focusing on essential functionality.
- Improved overall code readability by reducing logging noise while maintaining necessary functionality.
2025-12-03 11:35:13 +01:00
Torsten Schulz (local)
56c3569b68 Refactor debug logging in FalukantService for improved clarity and consistency
- Removed specific debug logs for the carrot product and replaced them with generalized logs for all products, enhancing the readability of the logging output.
- Updated console logs to provide clearer information about the processing of cities and the results returned, improving traceability during price calculations.
- Ensured that all relevant details are logged consistently, aiding in debugging and performance monitoring across different product types.
2025-12-03 11:32:06 +01:00
Torsten Schulz (local)
e2969c1837 Enhance RevenueSection to conditionally load product prices based on currentRegionId
- Updated watchers in RevenueSection to ensure product prices are only loaded when currentRegionId is not null.
- Added a check in loadPricesForAllProducts to skip execution if currentRegionId is null or undefined, improving performance and preventing unnecessary calls.
- Enhanced overall logic to ensure accurate price loading based on the selected region, contributing to a better user experience.
2025-12-03 09:19:46 +01:00
Torsten Schulz (local)
fe14c7b9f5 Add debug logging for product price retrieval in FalukantService and RevenueSection
- Introduced console logs in FalukantService to trace the parameters used in the getProductPricesInCities method, enhancing visibility into the product price retrieval process.
- Added logging in RevenueSection to capture the loading process and received better prices for products, improving traceability and debugging capabilities during price loading operations.
2025-12-03 08:58:07 +01:00
Torsten Schulz (local)
5d01b24c2d Add debug logging for carrot product pricing in FalukantService
- Introduced console logs to trace the price calculation process specifically for the carrot product (productId: 3).
- Enhanced visibility into the evaluation of cities and the resulting prices, aiding in debugging and performance monitoring.
- Logged details when skipping the current city, calculating prices, and adding cities to the results, improving traceability of pricing logic.
2025-12-03 08:44:45 +01:00
Torsten Schulz (local)
4eeb5021ee Enhance product price retrieval by including currentRegionId in FalukantController and FalukantService
- Updated the FalukantController to accept currentRegionId as a parameter for fetching product prices in cities.
- Modified the FalukantService to incorporate currentRegionId in the price calculation logic, allowing exclusion of the current region from results.
- Adjusted frontend components to pass currentRegionId, improving the accuracy of price comparisons and user experience.
2025-12-03 08:39:30 +01:00
Torsten Schulz (local)
6ec62af606 Add debug logging to FalukantService and RevenueSection for better price tracking
- Introduced console logs in FalukantService to trace product price calculations and city evaluations, aiding in debugging and performance monitoring.
- Added logging in RevenueSection to capture the loading process and received better prices for products, enhancing visibility into price retrieval operations.
2025-12-03 08:30:59 +01:00
Torsten Schulz (local)
3d6fdc65d2 Refine price comparison logic in FalukantService to include a tolerance for rounding errors
- Updated the price comparison condition to account for a small tolerance (0.001) when determining if the calculated price in a city exceeds the current price, improving accuracy in pricing evaluations.
2025-12-02 15:57:29 +01:00
Torsten Schulz (local)
956418f5f3 Enhance weather model and service logic; improve money history translation handling
- Added primary key to the Weather model for better data integrity.
- Updated FalukantService to include specific weather attributes in queries, enhancing data retrieval.
- Refactored money history view to utilize a dedicated translation method for improved localization handling.
2025-12-02 14:05:25 +01:00
Torsten Schulz (local)
e57de7f983 Fix typo in healthDrunkOfLife method and enhance health change logic in FalukantService; refactor health measures localization structure in English and German JSON files for better organization. 2025-12-02 13:05:39 +01:00
Torsten Schulz (local)
08e2c87de8 Enhance branch selection with weather information and localization updates
- Updated FalukantService to include weather data in branch retrieval, enhancing user context.
- Modified BranchSelection component to display current weather for selected branches, improving user experience.
- Added weather translations in both English and German localization files for better accessibility.
- Improved styling for weather information display in the frontend.
2025-12-02 12:53:02 +01:00
Torsten Schulz (local)
ba1a12402d Add product weather effects and regional pricing enhancements
- Introduced a new endpoint in FalukantController to retrieve product prices based on region and product ID.
- Implemented logic in FalukantService to calculate product prices considering user knowledge and regional factors.
- Added weather-related data models and associations to enhance product pricing accuracy based on weather conditions.
- Updated frontend components to cache and display regional product prices effectively, improving user experience.
2025-12-02 09:55:08 +01:00
Torsten Schulz (local)
39716b1f40 Add regional pricing calculation for products in FalukantService
- Introduced a new function `calcRegionalSellPrice` to compute product prices based on regional worth percentages.
- Updated existing methods to utilize the new pricing logic, ensuring revenue calculations reflect regional variations.
- Integrated retrieval of `TownProductWorth` data to enhance pricing accuracy across different regions.
2025-12-02 08:44:53 +01:00
Torsten Schulz (local)
adc7132404 Add product price retrieval feature in cities
- Implemented a new endpoint in FalukantController to fetch product prices in various cities based on product ID and current price.
- Developed the corresponding service method in FalukantService to calculate and return prices, considering user knowledge and city branches.
- Updated frontend components (RevenueSection and SaleSection) to display better prices for products, including loading logic and UI enhancements for price visibility.
- Added styling for price indicators based on branch types to improve user experience.
2025-12-01 16:42:54 +01:00
Torsten Schulz (local)
8c8841705c Implement daemon socket listener management in BranchView.vue
- Added a watcher for the daemon socket to properly register and unregister message event listeners on socket changes.
- Simplified the event listener setup for handling daemon messages, improving code clarity and maintainability.
- Ensured that listeners are removed during component unmount to prevent memory leaks.
2025-12-01 14:06:18 +01:00
Torsten Schulz (local)
f7fdd8ab08 Refactor localization structure for production notifications in English and German
- Updated the localization files to nest the "overproduction" notification under a "production" key for better organization and clarity.
- Ensured consistency in translation structure across both English and German localization files.
2025-12-01 11:51:37 +01:00
Torsten Schulz (local)
5807c6f3d3 Update daemon socket configuration and fallback logic in frontend scripts
- Changed the default value for `VITE_DAEMON_SOCKET` in `deploy-frontend.sh` and `update-frontend.sh` to connect directly to port 4551 instead of using the Apache proxy.
- Updated fallback logic in `frontend/src/store/index.js` to reflect the new direct connection to the daemon on port 4551, enhancing connection reliability.
2025-12-01 11:46:50 +01:00
Torsten Schulz (local)
7e0691eea3 Enhance message formatting and localization handling in MessagesDialog.vue
- Updated the formatBody method to support JSON formatted translation keys and improve key normalization for i18n.
- Ensured that keys are correctly prefixed with the "falukant.notifications." namespace when necessary, enhancing translation accuracy.
2025-12-01 11:26:46 +01:00
Torsten Schulz (local)
17d4d21620 Add new daemon start script and update localization for director salary
- Introduced a new script `start-daemon` in `package.json` for running the daemon server.
- Added translations for "director payed out" in both English and German localization files to enhance user notifications.
2025-12-01 10:06:06 +01:00
Torsten Schulz (local)
d19feb8bc1 Update daemon socket URL and enhance message rendering in frontend
- Changed the default value for `VITE_DAEMON_SOCKET` in `deploy-frontend.sh` and `update-frontend.sh` to use the `/ws/` path.
- Updated the message rendering logic in `MessagesDialog.vue` to utilize a new `formatBody` method for improved translation handling.
- Added a new translation for "overproduction" in both English and German localization files.
2025-12-01 09:47:16 +01:00
Torsten Schulz (local)
ab1e4bec60 Update localization for notifications in English and German
- Added new notification translations for election creation in both `falukant.json` files.
- Updated the message rendering in `MessagesDialog.vue` to include the new translation structure.
2025-12-01 09:32:59 +01:00
Torsten Schulz (local)
672cec9c2a Add localization updates for money history in English and German 2025-12-01 09:28:44 +01:00
Torsten Schulz (local)
c3ea7eecc2 Update dependencies and refactor authentication logic
- Replaced `bcrypt` with `bcryptjs` for compatibility in `authService.js` and `settingsService.js`.
- Updated package versions in `package.json` and `package-lock.json`, including `multer`, `nodemailer`, and others.
- Added storage management features in the frontend, including free storage calculation and localization updates for new terms in `falukant.json` files.
2025-11-26 18:14:36 +01:00
Torsten Schulz (local)
608e62c2bd Implement cooldown feature for nobility advancement
- Added logic in FalukantService to calculate the next available advancement date based on the user's last advancement.
- Updated the frontend to display a cooldown message indicating when the user can next advance in nobility.
- Enhanced the NobilityView component to handle and format the next advancement date appropriately.
2025-11-26 17:23:54 +01:00
Torsten Schulz (local)
c1b69389c6 Add lastNobilityAdvanceAt field and update logic in FalukantService
- Introduced a new field `lastNobilityAdvanceAt` in the FalukantUser model to track the last time a user advanced in nobility.
- Updated the `FalukantService` to enforce a one-week cooldown between nobility advancements, throwing an error if the user attempts to advance too soon.
- Ensured the `lastNobilityAdvanceAt` field is updated with the current date upon a successful nobility advancement.
2025-11-26 17:17:37 +01:00
Torsten (PC)
182f38597c update-funktion verbessert 2025-11-26 17:16:30 +01:00
Torsten Schulz (local)
06ea259dc9 Add Falukant region and transport management features
- Implemented new endpoints in AdminController for managing Falukant regions, including fetching, updating, and deleting region distances.
- Enhanced the FalukantService with methods for retrieving region distances and handling upsert operations.
- Updated the router to expose new routes for region management and transport creation.
- Introduced a transport management interface in the frontend, allowing users to create and manage transports between branches.
- Added localization for new transport-related terms and improved the vehicle management interface to include transport options.
- Enhanced the database initialization logic to support new region and transport models.
2025-11-26 16:44:27 +01:00
Torsten Schulz (local)
29dd7ec80c Refactor daemon connection logic and enhance error handling
- Simplified fallback logic for daemon URL generation, removing hardcoded values and using dynamic protocol and hostname.
- Added detailed error messages for common WebSocket connection issues, improving debugging capabilities.
- Updated reconnection warning messages to guide users on potential configuration issues with the daemon server.
2025-11-24 20:28:11 +01:00
Torsten Schulz (local)
3f043fc315 Add vehicle management features in Falukant
- Introduced vehicle types and transport management in the backend, including new models and associations for vehicles and transports.
- Implemented service methods to retrieve vehicle types and handle vehicle purchases, ensuring user validation and transaction management.
- Updated the FalukantController and router to expose new endpoints for fetching vehicle types and buying vehicles.
- Enhanced the frontend with a new transport tab in BranchView, allowing users to buy vehicles, and added localization for vehicle-related terms.
- Included initialization logic for vehicle types in the database setup.
2025-11-24 20:15:45 +01:00
Torsten Schulz (local)
5ed27e5a6a Refactor navigation and enhance director information display
- Removed the directors section from the navigation menu for a cleaner interface.
- Updated the FalukantService to include additional attributes for directors, such as knowledges and region.
- Enhanced the DirectorInfo component to display detailed information, including knowledge and income management features.
- Implemented tab navigation in BranchView for better organization of director, inventory, production, and storage sections.
- Updated localization files to reflect changes in navigation and tab labels.
2025-11-24 16:38:36 +01:00
Torsten Schulz (local)
23725c20ee Enhance mood change calculation in FalukantService
- Updated the mood change calculation to include a random bonus between 0 and 7 points, improving variability in user experience.
- Refactored the calculation logic for clarity, separating the base change value from the random bonus.
2025-11-24 15:51:27 +01:00
Torsten Schulz (local)
29b6db7ee9 Update dropdown positioning in FormattedDropdown component for improved visibility
- Changed the dropdown list positioning from normal document flow to absolute positioning, ensuring the list is reliably visible when opened.
2025-11-24 15:39:44 +01:00
Torsten Schulz (local)
6e7165fe7f Add console log to toggleDropdown method in FormattedDropdown component for debugging 2025-11-24 15:33:55 +01:00
Torsten Schulz (local)
43131250ed Fix dropdown toggle method in FormattedDropdown component to ensure proper function call 2025-11-24 15:26:44 +01:00
Torsten Schulz (local)
c3beb029e5 Refactor FormattedDropdown and enhance BranchView functionality
- Updated the FormattedDropdown component to use normal document flow for the dropdown list, ensuring visibility when opened.
- Enhanced the createBranch method in BranchView to automatically select the most recently created branch after a new branch is added, improving user experience.
2025-11-24 15:19:35 +01:00
Torsten Schulz (local)
9f10ac9e96 Enhance BranchSelection component to force re-render on branch list change
- Added a computed property `branchesKey` to generate a unique key based on branch IDs, ensuring the dropdown re-renders when the branch list updates.
- Updated the FormattedDropdown component to utilize this key for improved responsiveness to data changes.
2025-11-24 13:45:04 +01:00
Torsten Schulz (local)
d36901aa2b Refactor tab change logic in PoliticsView to simplify loading conditions
- Updated the onTabChange method to remove unnecessary checks for existing data before loading current positions, open politics, and elections.
- This change enhances the clarity of the method and ensures that data is always loaded when the respective tab is selected.
2025-11-24 12:24:31 +01:00
Torsten Schulz (local)
4510aa3d14 Implement politics overview feature in FalukantService and update UI
- Added a new method `getPoliticsOverview` in FalukantService to retrieve currently held offices, including office holders and term end dates.
- Enhanced the PoliticsView component to display the term end dates for current offices.
- Updated localization files to include a new message for applying to selected positions.
- Improved the handling of already applied positions in the open politics section, pre-selecting checkboxes accordingly.
2025-11-24 11:50:21 +01:00
Torsten Schulz (local)
3b8736acd7 Enhance WebSocketLogDialog to display enriched user information
- Updated the WebSocketLogDialog to use enriched log entries with resolved usernames for connection and target users.
- Implemented batch retrieval of user information from the API to improve user display in logs.
- Added error handling for user data fetching and fallback logic for missing usernames.
2025-11-22 13:32:44 +01:00
Torsten Schulz (local)
735075d1bd Add WebSocket Log feature to Services Status View
- Introduced a WebSocket Log section in the Services Status View, allowing users to view real-time logs.
- Updated localization files for both German and English to include WebSocket Log messages.
- Enhanced the UI with a button to open the WebSocket Log dialog, improving user interaction and monitoring capabilities.
2025-11-22 13:21:13 +01:00
Torsten Schulz (local)
dc7001a80c Implement batch user retrieval in AdminController and update routes
- Added a new method `getUsers` in AdminController to handle batch retrieval of user information based on hashed IDs.
- Updated adminRouter to include a new route for batch user retrieval.
- Enhanced AdminService with a method to fetch user details by hashed IDs, ensuring proper access control.
- Updated localization files to include the new "username" field for user connections in both German and English.
- Modified ServicesStatusView to utilize the new batch user retrieval for displaying usernames alongside connection counts.
2025-11-21 23:49:05 +01:00
Torsten Schulz (local)
8a9acf6c4a Refactor ServicesStatusView to handle daemon response structure
- Updated the handling of daemon responses to accommodate a new structure that includes user connection counts.
- Transformed the users object into an array for easier template rendering.
- Improved error handling for JSON parsing and daemon message processing.
2025-11-21 23:45:29 +01:00
Torsten Schulz (local)
5ca017950e Remove Google Chrome RPM package file 2025-11-20 15:52:04 +01:00
Torsten Schulz (local)
eadec50e30 Feature: Add Services Status page and update navigation
- Introduced a new Services Status page to monitor the status of Backend, Chat, and Daemon services.
- Updated navigation structure to include the new Services Status link for main admin users.
- Added German and English localization for the Services Status page, including titles, descriptions, and status messages.
2025-11-20 15:49:08 +01:00
Torsten Schulz (local)
e7f5918013 Enhance Vite configuration to load environment variables
- Refactored Vite configuration to load environment variables explicitly based on the current mode
- Added support for additional environment variables: VITE_DAEMON_SOCKET, VITE_API_BASE_URL, VITE_CHAT_WS_URL, and VITE_SOCKET_IO_URL
- Improved clarity and maintainability of the configuration structure
2025-11-18 08:59:20 +01:00
Torsten Schulz (local)
27b675cb19 Refactor daemon URL configuration and enhance logging
- Improved fallback logic for daemon URL based on hostname and environment
- Added detailed logging for daemon connection status and environment settings
- Streamlined handling of environment variables for better clarity
2025-11-18 08:50:25 +01:00
Torsten Schulz (local)
016a37c116 Refactor daemon connection logic and enhance logging
- Improved handling of daemon URL configuration based on environment variables
- Added detailed logging for daemon connection status and environment settings
- Streamlined fallback logic for local development and production environments
2025-11-18 08:37:02 +01:00
Torsten Schulz (local)
d8b1efc3ca Enhance StatusBar and daemon connection management
- Added image preloading for quick access in StatusBar component
- Implemented a watcher to reload images when the menu changes
- Introduced a delay before sending 'setUserId' to ensure daemon readiness
- Improved logging for WebSocket close events and errors
2025-11-17 16:19:43 +01:00
Torsten Schulz (local)
d13fe19198 Fix: Enhance daemon connection management and retry logic
- Clear socket reference on connection close and error
- Ensure reconnection attempts only occur if the user is logged in
- Improved logging for reconnection attempts and retry count
- Added maximum retry limit with extended wait time after reaching it
2025-11-16 11:33:20 +01:00
Torsten Schulz (local)
762a2e9cf0 Fix: Improve daemon connection handling and retry logic
- Reset daemon connection state on successful connection and errors
- Clear retry timer when connection is established
- Enhanced retry logic to prevent multiple simultaneous connection attempts
- Improved logging for daemon reconnection attempts
2025-10-31 16:24:35 +01:00
Torsten Schulz (local)
44a2c525e7 Fix: Restore original avatar images
- Avatar images should not be optimized as they are used for character display
- Restored original 1792x1024 resolution for proper character appearance
- Only small icons should be optimized, not character avatars
2025-10-24 23:22:22 +02:00
Torsten Schulz (local)
507b0275d3 Performance: Optimize all images and improve error handling
- Optimized Falukant shortmap icons: 1.4MB-2.9MB → 1.9KB-3.3KB (99%+ reduction)
- Optimized all large images: avatars, maps, passengers, products, etc.
- Improved error handling in getGifts method with better logging
- Fixed icon loading performance issues
- Maintained original design while dramatically improving load times
- Total space savings: ~100MB+
2025-10-24 23:18:18 +02:00
Torsten Schulz (local)
ccd8bfba0d Feature: Termine-Anzeige auf der Startseite
- Neue CSV-Datei backend/data/termine.csv für Termine-Speicherung
- Backend-Controller und Router für /api/termine Endpoint
- TermineWidget Component zur Anzeige von bevorstehenden Terminen
- Integration in LoggedInView (Startseite für eingeloggte User)
- Zeigt Datum, Titel, Beschreibung, Ort und Uhrzeit an
- Sortiert nach Datum, filtert automatisch vergangene Termine
2025-10-20 22:27:35 +02:00
Torsten Schulz (local)
47f5def67c Fix: Korrekter Tabellenname für UserRightType Model
- Ändere tableName von 'user_right_type' zu 'user_right'
- Die Tabelle heißt type.user_right, nicht type.user_right_type
- Behebt: Verwaltungsmenü wird nicht angezeigt für mainadmin
2025-10-20 21:33:12 +02:00
490 changed files with 59935 additions and 13288 deletions

7
.gitignore vendored
View File

@@ -5,6 +5,7 @@
.depbe.sh .depbe.sh
node_modules node_modules
node_modules/* node_modules/*
**/package-lock.json
backend/.env backend/.env
backend/images backend/images
backend/images/* backend/images/*
@@ -17,3 +18,9 @@ frontend/dist
frontend/dist/* frontend/dist/*
frontedtree.txt frontedtree.txt
backend/dist/ backend/dist/
backend/data/model-cache
build
build/*
.vscode
.vscode/*
.clang-format

156
CHURCH_MODELS.md Normal file
View File

@@ -0,0 +1,156 @@
# 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'
```

78
CHURCH_OFFICES.md Normal file
View File

@@ -0,0 +1,78 @@
# 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 Normal file
View File

@@ -0,0 +1,119 @@
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)

414
CMakeLists.txt.user Normal file
View File

@@ -0,0 +1,414 @@
<?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>

205
CMakeLists.txt.user.d36652f Normal file
View File

@@ -0,0 +1,205 @@
<?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>

230
PERFORMANCE_ANALYSIS.md Normal file
View File

@@ -0,0 +1,230 @@
# Backend Performance-Analyse: Sell-Funktionen
## Identifizierte Performance-Probleme
### 1. **N+1 Query Problem in `sellAllProducts()`**
**Problem:**
Die Funktion `sellAllProducts()` macht für jedes Inventory-Item mehrere separate Datenbankabfragen:
1. **Erste Schleife (Zeile 1702-1711):**
- `calcRegionalSellPrice()` → macht `TownProductWorth.findOne()` für jedes Item
- `getCumulativeTaxPercentWithExemptions()` → macht mehrere Queries pro Item:
- `FalukantCharacter.findOne()`
- `PoliticalOffice.findAll()` mit Includes
- Rekursive SQL-Query für Steuerberechnung
- `addSellItem()` → macht `Branch.findOne()` und `DaySell.findOne()`/`create()` für jedes Item
2. **Zweite Schleife (Zeile 1714-1724):**
- `RegionData.findOne()` für jedes Item
- `getCumulativeTaxPercent()` → rekursive SQL-Query für jedes Item
- `calcRegionalSellPrice()` → erneut `TownProductWorth.findOne()` für jedes Item
**Beispiel:** Bei 10 Items werden gemacht:
- 10x `TownProductWorth.findOne()` (2x pro Item)
- 10x `RegionData.findOne()`
- 10x `getCumulativeTaxPercentWithExemptions()` (mit mehreren Queries)
- 10x `getCumulativeTaxPercent()` (rekursive SQL)
- 10x `addSellItem()` (mit 2 Queries pro Item)
- = **~70+ Datenbankabfragen für 10 Items**
### 2. **Ineffiziente `addSellItem()` Implementierung**
**Problem:**
- Wird für jedes Item einzeln aufgerufen
- Macht `Branch.findOne()` für jedes Item (könnte gecacht werden)
- `DaySell.findOne()` und `create()`/`update()` für jedes Item
**Lösung:** Batch-Operation implementieren, die alle DaySell Einträge auf einmal verarbeitet.
### 3. **Doppelte Berechnungen in `sellAllProducts()`**
**Problem:**
- Preis wird zweimal berechnet (Zeile 1705 und 1718)
- Steuer wird zweimal berechnet (Zeile 1706 und 1717)
- `calcRegionalSellPrice()` wird zweimal aufgerufen mit denselben Parametern
### 4. **Fehlende Indizes**
**Potenzielle fehlende Indizes:**
- `falukant_data.town_product_worth(product_id, region_id)` - sollte unique sein
- `falukant_data.inventory(stock_id, product_id, quality)` - für schnelle Lookups
- `falukant_data.knowledge(character_id, product_id)` - für Knowledge-Lookups
- `falukant_data.political_office(character_id)` - für Steuerbefreiungen
### 5. **Ineffiziente `getCumulativeTaxPercentWithExemptions()`**
**Problem:**
- Lädt alle PoliticalOffices jedes Mal neu, auch wenn sich nichts geändert hat
- Macht komplexe rekursive SQL-Query für jedes Item separat
- Könnte gecacht werden (z.B. pro User+Region Kombination)
## Empfohlene Optimierungen
### 1. **Batch-Loading für `sellAllProducts()`**
```javascript
async sellAllProducts(hashedUserId, branchId) {
// ... existing code ...
// Batch-Load alle benötigten Daten VOR den Schleifen
const regionIds = [...new Set(inventory.map(item => item.stock.branch.regionId))];
const productIds = [...new Set(inventory.map(item => item.productType.id))];
// 1. Lade alle TownProductWorth Einträge auf einmal
const townWorths = await TownProductWorth.findAll({
where: {
productId: { [Op.in]: productIds },
regionId: { [Op.in]: regionIds }
}
});
const worthMap = new Map();
townWorths.forEach(tw => {
worthMap.set(`${tw.productId}-${tw.regionId}`, tw.worthPercent);
});
// 2. Lade alle RegionData auf einmal
const regions = await RegionData.findAll({
where: { id: { [Op.in]: regionIds } }
});
const regionMap = new Map(regions.map(r => [r.id, r]));
// 3. Berechne Steuern für alle Regionen auf einmal
const taxMap = new Map();
for (const regionId of regionIds) {
const tax = await getCumulativeTaxPercentWithExemptions(falukantUser.id, regionId);
taxMap.set(regionId, tax);
}
// 4. Berechne Preise und Steuern in einer Schleife
const sellItems = [];
for (const item of inventory) {
const regionId = item.stock.branch.regionId;
const worthPercent = worthMap.get(`${item.productType.id}-${regionId}`) || 50;
const knowledgeVal = item.productType.knowledges[0]?.knowledge || 0;
const pricePerUnit = calcRegionalSellPrice(item.productType, knowledgeVal, regionId, worthPercent);
const cumulativeTax = taxMap.get(regionId);
// ... rest of calculation ...
sellItems.push({ branchId: item.stock.branch.id, productId: item.productType.id, quantity: item.quantity });
}
// 5. Batch-Update DaySell Einträge
await this.addSellItemsBatch(sellItems);
// ... rest of code ...
}
```
### 2. **Batch-Operation für `addSellItem()`**
```javascript
async addSellItemsBatch(sellItems) {
// Gruppiere nach (regionId, productId, sellerId)
const grouped = new Map();
for (const item of sellItems) {
const branch = await Branch.findByPk(item.branchId);
if (!branch) continue;
const key = `${branch.regionId}-${item.productId}-${item.sellerId}`;
if (!grouped.has(key)) {
grouped.set(key, {
regionId: branch.regionId,
productId: item.productId,
sellerId: item.sellerId,
quantity: 0
});
}
grouped.get(key).quantity += item.quantity;
}
// Batch-Update oder Create
for (const [key, data] of grouped) {
const [daySell, created] = await DaySell.findOrCreate({
where: {
regionId: data.regionId,
productId: data.productId,
sellerId: data.sellerId
},
defaults: { quantity: data.quantity }
});
if (!created) {
daySell.quantity += data.quantity;
await daySell.save();
}
}
}
```
### 3. **Caching für `getCumulativeTaxPercentWithExemptions()`**
```javascript
// Cache für Steuerberechnungen (z.B. 5 Minuten)
const taxCache = new Map();
const CACHE_TTL = 5 * 60 * 1000; // 5 Minuten
async function getCumulativeTaxPercentWithExemptions(userId, regionId) {
const cacheKey = `${userId}-${regionId}`;
const cached = taxCache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
return cached.value;
}
// ... existing calculation ...
taxCache.set(cacheKey, { value: tax, timestamp: Date.now() });
return tax;
}
```
### 4. **Optimierte `calcRegionalSellPrice()`**
```javascript
async function calcRegionalSellPrice(product, knowledgeFactor, regionId, worthPercent = null) {
// Wenn worthPercent nicht übergeben wurde UND wir es nicht aus dem Cache haben,
// dann hole es aus der DB
if (worthPercent === null) {
const townWorth = await TownProductWorth.findOne({
where: { productId: product.id, regionId: regionId }
});
worthPercent = townWorth?.worthPercent || 50;
}
// ... rest of calculation ...
}
```
### 5. **Datenbank-Indizes hinzufügen**
```sql
-- Index für town_product_worth (sollte unique sein)
CREATE UNIQUE INDEX IF NOT EXISTS idx_town_product_worth_product_region
ON falukant_data.town_product_worth(product_id, region_id);
-- Index für inventory Lookups
CREATE INDEX IF NOT EXISTS idx_inventory_stock_product_quality
ON falukant_data.inventory(stock_id, product_id, quality);
-- Index für knowledge Lookups
CREATE INDEX IF NOT EXISTS idx_knowledge_character_product
ON falukant_data.knowledge(character_id, product_id);
-- Index für political_office Lookups
CREATE INDEX IF NOT EXISTS idx_political_office_character
ON falukant_data.political_office(character_id);
```
## Geschätzter Performance-Gewinn
- **Vorher:** ~70+ Queries für 10 Items
- **Nachher:** ~15-20 Queries für 10 Items (Batch-Loading + Caching)
- **Geschätzte Verbesserung:** 70-80% weniger Datenbankabfragen
## Priorität
1. **Hoch:** Batch-Loading für `sellAllProducts()` (größter Impact)
2. **Hoch:** Batch-Operation für `addSellItem()`
3. **Mittel:** Caching für Steuerberechnungen
4. **Mittel:** Datenbank-Indizes
5. **Niedrig:** Doppelte Berechnungen entfernen

601
SELL_OVERVIEW.md Normal file
View File

@@ -0,0 +1,601 @@
# Übersicht: Sell-Funktionen und verwendete Models/Tabellen
## Sell-Funktionen in `falukantService.js`
### 1. `sellProduct(hashedUserId, branchId, productId, quality, quantity)`
Verkauft ein einzelnes Produkt mit bestimmter Qualität.
**Ablauf:**
1. Lädt User, Branch, Character, Stock
2. Lädt Inventory mit ProductType und Knowledge
3. Berechnet Preis pro Einheit mit `calcRegionalSellPrice()`
4. Berechnet kumulative Steuer mit politischen Befreiungen
5. Passt Preis an (Inflation basierend auf Steuer)
6. Berechnet Revenue, Tax, Net
7. Aktualisiert Geld für Verkäufer und Treasury
8. Entfernt verkaufte Items aus Inventory
9. Erstellt/aktualisiert DaySell Eintrag
10. Sendet Socket-Notifications
**Verwendete Models/Tabellen:**
- `FalukantUser` (`falukant_data.falukant_user`)
- `Branch` (`falukant_data.branch`)
- `FalukantCharacter` (`falukant_data.character`)
- `FalukantStock` (`falukant_data.stock`)
- `Inventory` (`falukant_data.inventory`)
- `ProductType` (`falukant_type.product`)
- `Knowledge` (`falukant_data.knowledge`)
- `TownProductWorth` (`falukant_data.town_product_worth`)
- `RegionData` (`falukant_data.region`)
- `RegionType` (`falukant_type.region`)
- `PoliticalOffice` (`falukant_data.political_office`)
- `PoliticalOfficeType` (`falukant_type.political_office_type`)
- `DaySell` (`falukant_log.day_sell`)
- `MoneyFlow` (via `updateFalukantUserMoney`)
### 2. `sellAllProducts(hashedUserId, branchId)`
Verkauft alle Produkte eines Branches.
**Ablauf:**
1. Lädt User, Branch mit Stocks
2. Lädt alle Inventory Items mit ProductType, Knowledge, Stock, Branch
3. Für jedes Item:
- Berechnet Preis pro Einheit
- Berechnet kumulative Steuer
- Passt Preis an
- Erstellt/aktualisiert DaySell Eintrag
4. Berechnet Gesamt-Tax pro Region
5. Aktualisiert Geld für Verkäufer und Treasury
6. Löscht alle Inventory Items
7. Sendet Socket-Notifications
**Verwendete Models/Tabellen:**
- `FalukantUser` (`falukant_data.falukant_user`)
- `Branch` (`falukant_data.branch`)
- `FalukantStock` (`falukant_data.stock`)
- `FalukantStockType` (`falukant_type.stock`)
- `FalukantCharacter` (`falukant_data.character`)
- `Inventory` (`falukant_data.inventory`)
- `ProductType` (`falukant_type.product`)
- `Knowledge` (`falukant_data.knowledge`)
- `TownProductWorth` (`falukant_data.town_product_worth`)
- `RegionData` (`falukant_data.region`)
- `RegionType` (`falukant_type.region`)
- `PoliticalOffice` (`falukant_data.political_office`)
- `PoliticalOfficeType` (`falukant_type.political_office_type`)
- `DaySell` (`falukant_log.day_sell`)
- `MoneyFlow` (via `updateFalukantUserMoney`)
### 3. `addSellItem(branchId, userId, productId, quantity)`
Erstellt oder aktualisiert einen DaySell Eintrag für einen Verkauf.
**Ablauf:**
1. Lädt Branch
2. Sucht nach existierendem DaySell Eintrag
3. Erstellt neuen oder aktualisiert existierenden Eintrag
**Verwendete Models/Tabellen:**
- `Branch` (`falukant_data.branch`)
- `DaySell` (`falukant_log.day_sell`)
## Hilfsfunktionen
### `calcRegionalSellPrice(product, knowledgeFactor, regionId, worthPercent = null)`
Berechnet den Verkaufspreis eines Produkts basierend auf:
- Basispreis (`product.sellCost`)
- Regionalem Worth-Percent (`town_product_worth.worth_percent`)
- Knowledge-Faktor (0-100)
**Verwendete Models/Tabellen:**
- `ProductType` (`falukant_type.product`)
- `TownProductWorth` (`falukant_data.town_product_worth`)
### `getCumulativeTaxPercent(regionId)`
Berechnet die kumulative Steuer für eine Region und alle Vorfahren (rekursiv).
**SQL Query:**
```sql
WITH RECURSIVE ancestors AS (
SELECT id, parent_id, tax_percent
FROM falukant_data.region r
WHERE id = :id
UNION ALL
SELECT reg.id, reg.parent_id, reg.tax_percent
FROM falukant_data.region reg
JOIN ancestors a ON reg.id = a.parent_id
)
SELECT COALESCE(SUM(tax_percent),0) AS total FROM ancestors;
```
**Verwendete Tabellen:**
- `falukant_data.region`
### `getCumulativeTaxPercentWithExemptions(userId, regionId)`
Berechnet die kumulative Steuer mit politischen Befreiungen.
**Ablauf:**
1. Lädt Character des Users
2. Lädt alle PoliticalOffices des Characters
3. Bestimmt befreite Region-Typen basierend auf Ämtern
4. Berechnet kumulative Steuer, aber schließt befreite Region-Typen aus
**SQL Query:**
```sql
WITH RECURSIVE ancestors AS (
SELECT r.id, r.parent_id, r.tax_percent, rt.label_tr as region_type
FROM falukant_data.region r
JOIN falukant_type.region rt ON rt.id = r.region_type_id
WHERE r.id = :id
UNION ALL
SELECT reg.id, reg.parent_id, reg.tax_percent, rt2.label_tr
FROM falukant_data.region reg
JOIN falukant_type.region rt2 ON rt2.id = reg.region_type_id
JOIN ancestors a ON reg.id = a.parent_id
)
SELECT COALESCE(SUM(CASE WHEN ARRAY[...] && ARRAY[region_type]::text[] THEN 0 ELSE tax_percent END),0) AS total FROM ancestors;
```
**Verwendete Models/Tabellen:**
- `FalukantCharacter` (`falukant_data.character`)
- `PoliticalOffice` (`falukant_data.political_office`)
- `PoliticalOfficeType` (`falukant_type.political_office_type`)
- `RegionData` (`falukant_data.region`)
- `RegionType` (`falukant_type.region`)
**Politische Steuerbefreiungen:**
```javascript
const POLITICAL_TAX_EXEMPTIONS = {
'council': ['city'],
'taxman': ['city', 'county'],
'treasurerer': ['city', 'county', 'shire'],
'super-state-administrator': ['city', 'county', 'shire', 'markgrave', 'duchy'],
'chancellor': ['city','county','shire','markgrave','duchy'] // = alle Typen
};
```
## Model-Definitionen
### Inventory (`falukant_data.inventory`)
```javascript
// backend/models/falukant/data/inventory.js
- id
- stockId (FK zu falukant_data.stock)
- productId (FK zu falukant_type.product)
- quantity
- quality
- producedAt
```
### DaySell (`falukant_log.day_sell`)
```javascript
// backend/models/falukant/log/daysell.js
- id
- regionId (FK zu falukant_data.region)
- productId (FK zu falukant_type.product)
- sellerId (FK zu falukant_data.falukant_user)
- quantity
- createdAt
- updatedAt
```
### TownProductWorth (`falukant_data.town_product_worth`)
```javascript
// backend/models/falukant/data/town_product_worth.js
- id
- productId (FK zu falukant_type.product)
- regionId (FK zu falukant_data.region)
- worthPercent (0-100)
```
### Knowledge (`falukant_data.knowledge`)
```javascript
// backend/models/falukant/data/product_knowledge.js
- id
- productId (FK zu falukant_type.product)
- characterId (FK zu falukant_data.character)
- knowledge (0-99)
```
## Wichtige SQL-Queries
### 1. Inventory mit ProductType und Knowledge laden
```javascript
Inventory.findAll({
where: { quality },
include: [
{
model: ProductType,
as: 'productType',
required: true,
where: { id: productId },
include: [
{
model: Knowledge,
as: 'knowledges',
required: false,
where: { characterId: character.id }
}
]
}
]
})
```
### 2. Kumulative Steuer mit Befreiungen berechnen
Siehe `getCumulativeTaxPercentWithExemptions()` oben.
## Preisberechnung
### Formel für `calcRegionalSellPrice`:
1. Basispreis = `product.sellCost * (worthPercent / 100)`
2. Min = `basePrice * 0.6`
3. Max = `basePrice`
4. Preis = `min + (max - min) * (knowledgeFactor / 100)`
### Steueranpassung:
1. `inflationFactor = cumulativeTax >= 100 ? 1 : (1 / (1 - cumulativeTax / 100))`
2. `adjustedPricePerUnit = pricePerUnit * inflationFactor`
3. `revenue = quantity * adjustedPricePerUnit`
4. `taxValue = revenue * cumulativeTax / 100`
5. `net = revenue - taxValue`
## Vollständige Code-Snippets
### `calcRegionalSellPrice()`
```javascript
async function calcRegionalSellPrice(product, knowledgeFactor, regionId, worthPercent = null) {
if (worthPercent === null) {
const townWorth = await TownProductWorth.findOne({
where: { productId: product.id, regionId: regionId }
});
worthPercent = townWorth?.worthPercent || 50; // Default 50% wenn nicht gefunden
}
// Basispreis basierend auf regionalem worthPercent
const basePrice = product.sellCost * (worthPercent / 100);
// Dann Knowledge-Faktor anwenden
const min = basePrice * 0.6;
const max = basePrice;
return min + (max - min) * (knowledgeFactor / 100);
}
```
### `getCumulativeTaxPercent()`
```javascript
async function getCumulativeTaxPercent(regionId) {
if (!regionId) return 0;
const rows = await sequelize.query(
`WITH RECURSIVE ancestors AS (
SELECT id, parent_id, tax_percent
FROM falukant_data.region r
WHERE id = :id
UNION ALL
SELECT reg.id, reg.parent_id, reg.tax_percent
FROM falukant_data.region reg
JOIN ancestors a ON reg.id = a.parent_id
)
SELECT COALESCE(SUM(tax_percent),0) AS total FROM ancestors;`,
{
replacements: { id: regionId },
type: sequelize.QueryTypes.SELECT
}
);
const val = rows?.[0]?.total ?? 0;
return parseFloat(val) || 0;
}
```
### `getCumulativeTaxPercentWithExemptions()` (vereinfacht)
```javascript
async function getCumulativeTaxPercentWithExemptions(userId, regionId) {
if (!regionId) return 0;
// Character finden
const character = await FalukantCharacter.findOne({
where: { userId },
attributes: ['id']
});
if (!character) return 0;
// Politische Ämter laden
const offices = await PoliticalOffice.findAll({
where: { characterId: character.id },
include: [
{ model: PoliticalOfficeType, as: 'type', attributes: ['name'] },
{
model: RegionData,
as: 'region',
include: [{
model: RegionType,
as: 'regionType',
attributes: ['labelTr']
}]
}
]
});
// Befreite Region-Typen bestimmen
const exemptTypes = new Set();
let hasChancellor = false;
for (const o of offices) {
const name = o.type?.name;
if (!name) continue;
if (name === 'chancellor') { hasChancellor = true; break; }
const allowed = POLITICAL_TAX_EXEMPTIONS[name];
if (allowed && Array.isArray(allowed)) {
for (const t of allowed) exemptTypes.add(t);
}
}
if (hasChancellor) return 0;
// SQL Query mit Befreiungen
const exemptTypesArray = Array.from(exemptTypes);
const exemptTypesString = exemptTypesArray.length > 0
? `ARRAY[${exemptTypesArray.map(t => `'${t.replace(/'/g, "''")}'`).join(',')}]`
: `ARRAY[]::text[]`;
const rows = await sequelize.query(
`WITH RECURSIVE ancestors AS (
SELECT r.id, r.parent_id, r.tax_percent, rt.label_tr as region_type
FROM falukant_data.region r
JOIN falukant_type.region rt ON rt.id = r.region_type_id
WHERE r.id = :id
UNION ALL
SELECT reg.id, reg.parent_id, reg.tax_percent, rt2.label_tr
FROM falukant_data.region reg
JOIN falukant_type.region rt2 ON rt2.id = reg.region_type_id
JOIN ancestors a ON reg.id = a.parent_id
)
SELECT COALESCE(SUM(CASE WHEN ${exemptTypesString} && ARRAY[region_type]::text[] THEN 0 ELSE tax_percent END),0) AS total FROM ancestors;`,
{
replacements: { id: regionId },
type: sequelize.QueryTypes.SELECT
}
);
const val = rows?.[0]?.total ?? 0;
return parseFloat(val) || 0;
}
```
### `sellProduct()` (Kern-Logik)
```javascript
async sellProduct(hashedUserId, branchId, productId, quality, quantity) {
const user = await getFalukantUserOrFail(hashedUserId);
const branch = await getBranchOrFail(user.id, branchId);
const character = await FalukantCharacter.findOne({ where: { userId: user.id } });
if (!character) throw new Error('No character found for user');
const stock = await FalukantStock.findOne({ where: { branchId: branch.id } });
if (!stock) throw new Error('Stock not found');
// Inventory laden
const inventory = await Inventory.findAll({
where: { quality },
include: [
{
model: ProductType,
as: 'productType',
required: true,
where: { id: productId },
include: [
{
model: Knowledge,
as: 'knowledges',
required: false,
where: { characterId: character.id }
}
]
}
]
});
if (!inventory.length) throw new Error('No inventory found');
const available = inventory.reduce((sum, i) => sum + i.quantity, 0);
if (available < quantity) throw new Error('Not enough inventory available');
const item = inventory[0].productType;
const knowledgeVal = item.knowledges?.[0]?.knowledge || 0;
const pricePerUnit = await calcRegionalSellPrice(item, knowledgeVal, branch.regionId);
// Steuer berechnen
const cumulativeTax = await getCumulativeTaxPercentWithExemptions(user.id, branch.regionId);
const inflationFactor = cumulativeTax >= 100 ? 1 : (1 / (1 - cumulativeTax / 100));
const adjustedPricePerUnit = Math.round(pricePerUnit * inflationFactor * 100) / 100;
const revenue = quantity * adjustedPricePerUnit;
// Tax und Net berechnen
const taxValue = Math.round((revenue * cumulativeTax / 100) * 100) / 100;
const net = Math.round((revenue - taxValue) * 100) / 100;
// Geld aktualisieren
const moneyResult = await updateFalukantUserMoney(user.id, net, `Product sale (net)`, user.id);
if (!moneyResult.success) throw new Error('Failed to update money for seller');
// Steuer an Treasury buchen
const treasuryId = process.env.TREASURY_FALUKANT_USER_ID;
if (treasuryId && taxValue > 0) {
const taxResult = await updateFalukantUserMoney(parseInt(treasuryId, 10), taxValue, `Sales tax (${cumulativeTax}%)`, user.id);
if (!taxResult.success) throw new Error('Failed to update money for treasury');
}
// Inventory aktualisieren
let remaining = quantity;
for (const inv of inventory) {
if (inv.quantity <= remaining) {
remaining -= inv.quantity;
await inv.destroy();
} else {
await inv.update({ quantity: inv.quantity - remaining });
remaining = 0;
break;
}
}
// DaySell Eintrag erstellen/aktualisieren
await this.addSellItem(branchId, user.id, productId, quantity);
// Notifications senden
notifyUser(user.user.hashedId, 'falukantUpdateStatus', {});
notifyUser(user.user.hashedId, 'falukantBranchUpdate', { branchId: branch.id });
return { success: true };
}
```
### `addSellItem()`
```javascript
async addSellItem(branchId, userId, productId, quantity) {
const branch = await Branch.findOne({
where: { id: branchId },
});
const daySell = await DaySell.findOne({
where: {
regionId: branch.regionId,
productId: productId,
sellerId: userId,
}
});
if (daySell) {
daySell.quantity += quantity;
await daySell.save();
} else {
await DaySell.create({
regionId: branch.regionId,
productId: productId,
sellerId: userId,
quantity: quantity,
});
}
}
```
## Wichtige Hinweise
1. **Inventory wird nach Verkauf gelöscht/aktualisiert**: Items werden aus der Inventory entfernt oder die Menge reduziert.
2. **DaySell wird aggregiert**: Wenn bereits ein DaySell Eintrag für Region/Product/Seller existiert, wird die Menge addiert.
3. **Steuer wird an Treasury gebucht**: Wenn `TREASURY_FALUKANT_USER_ID` gesetzt ist, wird die Steuer an diesen User gebucht.
4. **Socket-Notifications**: Nach jedem Verkauf werden `falukantUpdateStatus` und `falukantBranchUpdate` Events gesendet.
5. **Politische Befreiungen**: Bestimmte politische Ämter befreien von Steuern in bestimmten Region-Typen. Chancellor befreit von allen Steuern.
6. **Preis-Inflation**: Der Preis wird basierend auf der Steuer inflatiert, damit der Netto-Betrag für den Verkäufer gleich bleibt.
## Tabellenübersicht
### `falukant_data.inventory`
- `id` (PK)
- `stock_id` (FK zu `falukant_data.stock`)
- `product_id` (FK zu `falukant_type.product`)
- `quantity` (INTEGER)
- `quality` (INTEGER)
- `produced_at` (DATE)
### `falukant_log.sell` (DaySell)
- `id` (PK)
- `region_id` (FK zu `falukant_data.region`)
- `product_id` (FK zu `falukant_type.product`)
- `seller_id` (FK zu `falukant_data.falukant_user`)
- `quantity` (INTEGER)
- `sell_timestamp` (DATE)
- **Unique Index**: `(seller_id, product_id, region_id)`
### `falukant_data.town_product_worth`
- `id` (PK)
- `product_id` (FK zu `falukant_type.product`)
- `region_id` (FK zu `falukant_data.region`)
- `worth_percent` (INTEGER, 0-100)
### `falukant_data.knowledge`
- `id` (PK)
- `product_id` (FK zu `falukant_type.product`)
- `character_id` (FK zu `falukant_data.character`)
- `knowledge` (INTEGER, 0-99)
### `falukant_data.political_office`
- `id` (PK)
- `office_type_id` (FK zu `falukant_type.political_office_type`)
- `character_id` (FK zu `falukant_data.character`)
- `region_id` (FK zu `falukant_data.region`)
- `created_at`, `updated_at`
### `falukant_type.political_office_type`
- `id` (PK)
- `name` (STRING) - z.B. 'council', 'taxman', 'treasurerer', 'super-state-administrator', 'chancellor'
- `seats_per_region` (INTEGER)
- `region_type` (STRING)
- `term_length` (INTEGER)
### `falukant_data.region`
- `id` (PK)
- `name` (STRING)
- `region_type_id` (FK zu `falukant_type.region`)
- `parent_id` (FK zu `falukant_data.region`, nullable)
- `map` (JSONB)
- `tax_percent` (DECIMAL)
### `falukant_type.region`
- `id` (PK)
- `label_tr` (STRING) - z.B. 'city', 'county', 'shire', 'markgrave', 'duchy'
- `parent_id` (FK zu `falukant_type.region`, nullable)
### `falukant_data.falukant_user`
- `id` (PK)
- `user_id` (FK zu `community.user`)
- `money` (DECIMAL)
- `credit_amount`, `today_credit_taken`, `credit_interest_rate`
- `certificate`
- `main_branch_region_id`
- `last_nobility_advance_at`
- `created_at`, `updated_at`
### `falukant_data.character`
- `id` (PK)
- `user_id` (FK zu `falukant_data.falukant_user`)
- `region_id` (FK zu `falukant_data.region`)
- `first_name`, `last_name`
- `birthdate`, `gender`, `health`
- `title_of_nobility` (FK zu `falukant_type.title_of_nobility`)
- `mood_id` (FK zu `falukant_type.mood`)
- `created_at`, `updated_at`
### `falukant_data.branch`
- `id` (PK)
- `branch_type_id` (FK zu `falukant_type.branch`)
- `region_id` (FK zu `falukant_data.region`)
- `falukant_user_id` (FK zu `falukant_data.falukant_user`)
### `falukant_data.stock`
- `id` (PK)
- `branch_id` (FK zu `falukant_data.branch`)
- `stock_type_id` (FK zu `falukant_type.stock`)
- `quantity` (INTEGER)
- `product_quality` (INTEGER, nullable)
### `falukant_type.product`
- `id` (PK)
- `label_tr` (STRING, unique)
- `category` (INTEGER)
- `production_time` (INTEGER)
- `sell_cost` (INTEGER)
## Dateipfade
- **Service**: `backend/services/falukantService.js`
- **Models**:
- `backend/models/falukant/data/inventory.js`
- `backend/models/falukant/log/daysell.js`
- `backend/models/falukant/data/town_product_worth.js`
- `backend/models/falukant/data/product_knowledge.js`
- `backend/models/falukant/data/political_office.js`
- `backend/models/falukant/type/political_office_type.js`
- `backend/models/falukant/data/region.js`
- `backend/models/falukant/type/region.js`
- `backend/models/falukant/data/character.js`
- `backend/models/falukant/data/user.js`
- `backend/models/falukant/data/branch.js`
- `backend/models/falukant/data/stock.js`
- `backend/models/falukant/type/product.js`

168
SSL-SETUP.md Normal file
View File

@@ -0,0 +1,168 @@
# 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`

23
backend/README_TAX.md Normal file
View File

@@ -0,0 +1,23 @@
# Falukant Tax Migration & Configuration
This project now supports a per-region sales tax (`tax_percent`) for Falukant.
Migration
- A SQL migration was added: `backend/migrations/20260101000000-add-tax-percent-to-region.cjs`.
- It adds `tax_percent` numeric NOT NULL DEFAULT 7 to `falukant_data.region`.
Runtime configuration
- If you want taxes to be forwarded to a treasury account, set environment variable `TREASURY_FALUKANT_USER_ID` to a valid `falukant_user.id`.
- If `TREASURY_FALUKANT_USER_ID` is not set, taxes will be calculated and currently not forwarded to any account.
Implementation notes
- Backend service `sellProduct` and `sellAllProducts` now compute tax per-region and credit net to seller and tax to treasury (if configured).
- Tax arithmetic uses rounding to 2 decimals. The current implementation performs two separate DB calls (seller, treasury). For strict ledger atomicity consider implementing DB-side booking.
Cumulative tax behavior
- The system now sums `tax_percent` from the sale region and all ancestor regions (recursive up the region tree). This allows defining different tax rates on up to 6 region levels and summing them for final tax percent.
- To avoid reducing seller net by taxes, sale prices are inflated by factor = 1 / (1 - cumulativeTax/100). This way the seller receives the original net and the tax is collected separately.
Testing
- After running the migration, test with a small sale and verify `falukant_log.moneyflow` entries for seller and treasury.

184
backend/analyze-indexes.js Executable file
View File

@@ -0,0 +1,184 @@
#!/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();

View File

@@ -1,6 +1,7 @@
import express from 'express'; import express from 'express';
import path from 'path'; import path from 'path';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import crypto from 'crypto';
import chatRouter from './routers/chatRouter.js'; import chatRouter from './routers/chatRouter.js';
import authRouter from './routers/authRouter.js'; import authRouter from './routers/authRouter.js';
import navigationRouter from './routers/navigationRouter.js'; import navigationRouter from './routers/navigationRouter.js';
@@ -11,11 +12,17 @@ import socialnetworkRouter from './routers/socialnetworkRouter.js';
import forumRouter from './routers/forumRouter.js'; import forumRouter from './routers/forumRouter.js';
import falukantRouter from './routers/falukantRouter.js'; import falukantRouter from './routers/falukantRouter.js';
import friendshipRouter from './routers/friendshipRouter.js'; import friendshipRouter from './routers/friendshipRouter.js';
import modelsProxyRouter from './routers/modelsProxyRouter.js';
import blogRouter from './routers/blogRouter.js'; import blogRouter from './routers/blogRouter.js';
import match3Router from './routers/match3Router.js'; import match3Router from './routers/match3Router.js';
import taxiRouter from './routers/taxiRouter.js'; import taxiRouter from './routers/taxiRouter.js';
import taxiMapRouter from './routers/taxiMapRouter.js'; import taxiMapRouter from './routers/taxiMapRouter.js';
import taxiHighscoreRouter from './routers/taxiHighscoreRouter.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 cors from 'cors';
import './jobs/sessionCleanup.js'; import './jobs/sessionCleanup.js';
@@ -24,16 +31,59 @@ const __dirname = path.dirname(__filename);
const app = express(); const app = express();
// Request-Timing (aktivierbar per ENV)
// - LOG_SLOW_REQ_MS=200: Logge Requests, die länger dauern als X ms (Default 500)
// - 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;
res.setHeader('x-request-id', reqId);
const t0 = Date.now();
res.on('finish', () => {
const ms = Date.now() - t0;
if (LOG_ALL_REQ || ms >= LOG_SLOW_REQ_MS) {
console.log(`⏱️ REQ ${ms}ms ${res.statusCode} ${req.method} ${req.originalUrl} rid=${reqId}`);
}
});
next();
});
const corsOptions = { const corsOptions = {
origin: ['http://localhost:3000', 'http://localhost:5173', 'http://127.0.0.1:3000', 'http://127.0.0.1:5173'], origin(origin, callback) {
if (!origin) {
return callback(null, true);
}
if (corsAllowAll || effectiveCorsOrigins.includes(origin)) {
return callback(null, true);
}
return callback(null, false);
},
methods: ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE'], methods: ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization', 'userId', 'authCode'], allowedHeaders: ['Content-Type', 'Authorization', 'userid', 'authcode', 'userId', 'authCode'],
credentials: true, credentials: true,
preflightContinue: false, preflightContinue: false,
optionsSuccessStatus: 204 optionsSuccessStatus: 204
}; };
app.use(cors(corsOptions)); app.use(cors(corsOptions));
app.options('*', cors(corsOptions));
app.use(express.json()); // To handle JSON request bodies app.use(express.json()); // To handle JSON request bodies
app.use('/api/chat', chatRouter); app.use('/api/chat', chatRouter);
@@ -48,13 +98,26 @@ app.use('/api/taxi/highscores', taxiHighscoreRouter);
app.use('/images', express.static(path.join(__dirname, '../frontend/public/images'))); app.use('/images', express.static(path.join(__dirname, '../frontend/public/images')));
app.use('/api/contact', contactRouter); app.use('/api/contact', contactRouter);
app.use('/api/socialnetwork', socialnetworkRouter); app.use('/api/socialnetwork', socialnetworkRouter);
app.use('/api/vocab', vocabRouter);
app.use('/api/forum', forumRouter); app.use('/api/forum', forumRouter);
app.use('/api/falukant', falukantRouter); app.use('/api/falukant', falukantRouter);
app.use('/api/friendships', friendshipRouter); app.use('/api/friendships', friendshipRouter);
app.use('/api/models', modelsProxyRouter);
app.use('/api/blog', blogRouter); 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 // 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'); 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.use(express.static(path.join(frontendDir, 'dist')));
app.get(/^\/(?!api\/).*/, (req, res) => { app.get(/^\/(?!api\/).*/, (req, res) => {
res.sendFile(path.join(frontendDir, 'dist', 'index.html')); res.sendFile(path.join(frontendDir, 'dist', 'index.html'));

View File

@@ -0,0 +1,86 @@
#!/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();

142
backend/check-knowledge-pkey.js Executable file
View File

@@ -0,0 +1,142 @@
#!/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();

View File

@@ -0,0 +1,55 @@
#!/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();

View File

@@ -1,4 +1,4 @@
{ {
"host": "localhost", "host": "localhost",
"port": 1235 "port": 1236
} }

View File

@@ -12,26 +12,52 @@ const productionEnvPath = '/opt/yourpart/backend/.env';
const localEnvPath = path.resolve(__dirname, '../.env'); const localEnvPath = path.resolve(__dirname, '../.env');
let envPath = localEnvPath; // Fallback let envPath = localEnvPath; // Fallback
let usingProduction = false;
if (fs.existsSync(productionEnvPath)) { if (fs.existsSync(productionEnvPath)) {
// Prüfe Lesbarkeit bevor wir versuchen, sie zu laden
try {
fs.accessSync(productionEnvPath, fs.constants.R_OK);
envPath = productionEnvPath; envPath = productionEnvPath;
console.log('[env] Lade Produktions-.env:', productionEnvPath); usingProduction = true;
console.log('[env] Produktions-.env gefunden und lesbar:', productionEnvPath);
} catch (err) {
console.warn('[env] Produktions-.env vorhanden, aber nicht lesbar - verwende lokale .env stattdessen:', productionEnvPath);
console.warn('[env] Fehler:', err && err.message);
envPath = localEnvPath;
}
} else { } else {
console.log('[env] Lade lokale .env:', localEnvPath); console.log('[env] Produktions-.env nicht gefunden, lade lokale .env:', localEnvPath);
} }
// Lade .env-Datei // Lade .env-Datei (robust gegen Fehler)
console.log('[env] Versuche .env zu laden von:', envPath); console.log('[env] Versuche .env zu laden von:', envPath);
console.log('[env] Datei existiert:', fs.existsSync(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'); let result;
try {
const result = dotenv.config({ path: envPath }); result = dotenv.config({ path: envPath });
if (result.error) { if (result.error) {
console.warn('[env] Konnte .env nicht laden:', result.error.message); console.warn('[env] Konnte .env nicht laden:', result.error.message);
console.warn('[env] Fehler-Details:', result.error); console.warn('[env] Fehler-Details:', result.error);
} else { } else {
console.log('[env] .env erfolgreich geladen von:', envPath); console.log('[env] .env erfolgreich geladen von:', envPath, usingProduction ? '(production)' : '(local)');
console.log('[env] Geladene Variablen:', Object.keys(result.parsed || {})); console.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
console.warn('[env] Unerwarteter Fehler beim Laden der .env:', err && err.message);
console.warn('[env] Stack:', err && err.stack);
if (envPath !== localEnvPath && fs.existsSync(localEnvPath)) {
console.log('[env] Versuche stattdessen lokale .env:', localEnvPath);
try {
result = dotenv.config({ path: localEnvPath });
if (!result.error) {
console.log('[env] Lokale .env erfolgreich geladen von:', localEnvPath);
}
} catch (err2) {
console.warn('[env] Konnte lokale .env auch nicht laden:', err2 && err2.message);
}
}
}
// Debug: Zeige Redis-Konfiguration // Debug: Zeige Redis-Konfiguration
console.log('[env] Redis-Konfiguration:'); console.log('[env] Redis-Konfiguration:');

View File

@@ -27,6 +27,7 @@ class AdminController {
// User administration // User administration
this.searchUsers = this.searchUsers.bind(this); this.searchUsers = this.searchUsers.bind(this);
this.getUser = this.getUser.bind(this); this.getUser = this.getUser.bind(this);
this.getUsers = this.getUsers.bind(this);
this.updateUser = this.updateUser.bind(this); this.updateUser = this.updateUser.bind(this);
// Rights // Rights
@@ -37,6 +38,14 @@ class AdminController {
// Statistics // Statistics
this.getUserStatistics = this.getUserStatistics.bind(this); this.getUserStatistics = this.getUserStatistics.bind(this);
this.getFalukantRegions = this.getFalukantRegions.bind(this);
this.updateFalukantRegionMap = this.updateFalukantRegionMap.bind(this);
this.getRegionDistances = this.getRegionDistances.bind(this);
this.upsertRegionDistance = this.upsertRegionDistance.bind(this);
this.deleteRegionDistance = this.deleteRegionDistance.bind(this);
this.createNPCs = this.createNPCs.bind(this);
this.getTitlesOfNobility = this.getTitlesOfNobility.bind(this);
this.getNPCsCreationStatus = this.getNPCsCreationStatus.bind(this);
} }
async getOpenInterests(req, res) { async getOpenInterests(req, res) {
@@ -74,6 +83,30 @@ class AdminController {
} }
} }
async getUsers(req, res) {
try {
const { userid: requester } = req.headers;
let { ids } = req.query;
if (!ids) {
return res.status(400).json({ error: 'ids query parameter is required' });
}
// Unterstütze sowohl Array-Format (ids[]=...) als auch komma-separierten String (ids=...)
let hashedIds;
if (Array.isArray(ids)) {
hashedIds = ids;
} else if (typeof ids === 'string') {
hashedIds = ids.split(',').map(id => id.trim()).filter(id => id.length > 0);
} else {
return res.status(400).json({ error: 'ids must be an array or comma-separated string' });
}
const result = await AdminService.getUsersByHashedIds(requester, hashedIds);
res.status(200).json(result);
} catch (error) {
const status = error.message === 'noaccess' ? 403 : 500;
res.status(status).json({ error: error.message });
}
}
async updateUser(req, res) { async updateUser(req, res) {
try { try {
const { userid: requester } = req.headers; const { userid: requester } = req.headers;
@@ -290,6 +323,122 @@ class AdminController {
} }
} }
async getFalukantRegions(req, res) {
try {
const { userid: userId } = req.headers;
const regions = await AdminService.getFalukantRegions(userId);
res.status(200).json(regions);
} catch (error) {
console.log(error);
const status = error.message === 'noaccess' ? 403 : 500;
res.status(status).json({ error: error.message });
}
}
async updateFalukantRegionMap(req, res) {
try {
const { userid: userId } = req.headers;
const { id } = req.params;
const { map } = req.body || {};
const region = await AdminService.updateFalukantRegionMap(userId, id, map);
res.status(200).json(region);
} catch (error) {
console.log(error);
const status = error.message === 'noaccess' ? 403 : (error.message === 'regionNotFound' ? 404 : 500);
res.status(status).json({ error: error.message });
}
}
async getRegionDistances(req, res) {
try {
const { userid: userId } = req.headers;
const distances = await AdminService.getRegionDistances(userId);
res.status(200).json(distances);
} catch (error) {
console.log(error);
const status = error.message === 'noaccess' ? 403 : 500;
res.status(status).json({ error: error.message });
}
}
async upsertRegionDistance(req, res) {
try {
const { userid: userId } = req.headers;
const record = await AdminService.upsertRegionDistance(userId, req.body || {});
res.status(200).json(record);
} catch (error) {
console.log(error);
const status = error.message === 'noaccess' ? 403 : 400;
res.status(status).json({ error: error.message });
}
}
async deleteRegionDistance(req, res) {
try {
const { userid: userId } = req.headers;
const { id } = req.params;
const result = await AdminService.deleteRegionDistance(userId, id);
res.status(200).json(result);
} catch (error) {
console.log(error);
const status = error.message === 'noaccess' ? 403 : (error.message === 'notfound' ? 404 : 500);
res.status(status).json({ error: error.message });
}
}
async createNPCs(req, res) {
try {
const { userid: userId } = req.headers;
const { regionIds, minAge, maxAge, minTitleId, maxTitleId, count } = req.body;
const countValue = parseInt(count) || 1;
if (countValue < 1 || countValue > 500) {
return res.status(400).json({ error: 'Count must be between 1 and 500' });
}
console.log('[createNPCs] Request received:', { userId, regionIds, minAge, maxAge, minTitleId, maxTitleId, count: countValue });
const result = await AdminService.createNPCs(userId, {
regionIds: regionIds && regionIds.length > 0 ? regionIds : null,
minAge: parseInt(minAge) || 0,
maxAge: parseInt(maxAge) || 100,
minTitleId: parseInt(minTitleId) || 1,
maxTitleId: parseInt(maxTitleId) || 19,
count: countValue
});
console.log('[createNPCs] Job created:', result);
res.status(200).json(result);
} catch (error) {
console.error('[createNPCs] Error:', error);
console.error('[createNPCs] Error stack:', error.stack);
const status = error.message === 'noaccess' ? 403 : 500;
res.status(status).json({ error: error.message || 'Internal server error' });
}
}
async getTitlesOfNobility(req, res) {
try {
const { userid: userId } = req.headers;
const titles = await AdminService.getTitlesOfNobility(userId);
res.status(200).json(titles);
} catch (error) {
console.log(error);
const status = error.message === 'noaccess' ? 403 : 500;
res.status(status).json({ error: error.message });
}
}
async getNPCsCreationStatus(req, res) {
try {
const { userid: userId } = req.headers;
const { jobId } = req.params;
const status = await AdminService.getNPCsCreationStatus(userId, jobId);
res.status(200).json(status);
} catch (error) {
console.log(error);
const status = error.message === 'noaccess' || error.message === 'Access denied' ? 403 :
error.message === 'Job not found' ? 404 : 500;
res.status(status).json({ error: error.message });
}
}
async getRoomTypes(req, res) { async getRoomTypes(req, res) {
try { try {
const userId = req.headers.userid; const userId = req.headers.userid;

View File

@@ -0,0 +1,203 @@
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' });
}
}
};

View File

@@ -13,6 +13,9 @@ class ChatController {
this.sendOneToOneMessage = this.sendOneToOneMessage.bind(this); this.sendOneToOneMessage = this.sendOneToOneMessage.bind(this);
this.getOneToOneMessageHistory = this.getOneToOneMessageHistory.bind(this); this.getOneToOneMessageHistory = this.getOneToOneMessageHistory.bind(this);
this.getRoomList = this.getRoomList.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) { async getMessages(req, res) {
@@ -175,6 +178,41 @@ class ChatController {
res.status(500).json({ error: error.message }); 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; export default ChatController;

View File

@@ -0,0 +1,50 @@
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' });
}
}
};

View File

@@ -26,10 +26,13 @@ class FalukantController {
}, { successStatus: 201 }); }, { successStatus: 201 });
this.getInfo = this._wrapWithUser((userId) => this.service.getInfo(userId)); 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.getBranches = this._wrapWithUser((userId) => this.service.getBranches(userId));
this.createBranch = this._wrapWithUser((userId, req) => this.service.createBranch(userId, req.body.cityId, req.body.branchTypeId)); 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.getBranchTypes = this._wrapWithUser((userId) => this.service.getBranchTypes(userId));
this.getBranch = this._wrapWithUser((userId, req) => this.service.getBranch(userId, req.params.branch)); 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));
this.createProduction = this._wrapWithUser((userId, req) => { this.createProduction = this._wrapWithUser((userId, req) => {
const { branchId, productId, quantity } = req.body; const { branchId, productId, quantity } = req.body;
return this.service.createProduction(userId, branchId, productId, quantity); return this.service.createProduction(userId, branchId, productId, quantity);
@@ -55,6 +58,10 @@ class FalukantController {
if (!page) page = 1; if (!page) page = 1;
return this.service.moneyHistory(userId, page, filter); 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.getStorage = this._wrapWithUser((userId, req) => this.service.getStorage(userId, req.params.branchId));
this.buyStorage = this._wrapWithUser((userId, req) => { this.buyStorage = this._wrapWithUser((userId, req) => {
const { branchId, amount, stockTypeId } = req.body; const { branchId, amount, stockTypeId } = req.body;
@@ -91,15 +98,40 @@ class FalukantController {
if (!result) throw { status: 404, message: 'No family data found' }; if (!result) throw { status: 404, message: 'No family data found' };
return result; return result;
}); });
this.getPotentialHeirs = this._wrapWithUser((userId) => this.service.getPotentialHeirs(userId));
this.selectHeir = this._wrapWithUser((userId, req) => this.service.selectHeir(userId, req.body.heirId));
this.setHeir = this._wrapWithUser((userId, req) => this.service.setHeir(userId, req.body.childCharacterId));
this.acceptMarriageProposal = this._wrapWithUser((userId, req) => this.service.acceptMarriageProposal(userId, req.body.proposalId)); this.acceptMarriageProposal = this._wrapWithUser((userId, req) => this.service.acceptMarriageProposal(userId, req.body.proposalId));
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 });
this.getGifts = this._wrapWithUser((userId) => { this.getGifts = this._wrapWithUser((userId) => {
console.log('🔍 getGifts called with userId:', userId); console.log('🔍 getGifts called with userId:', userId);
return this.service.getGifts(userId); return this.service.getGifts(userId);
}); });
this.getChildren = this._wrapWithUser((userId) => this.service.getChildren(userId)); this.getChildren = this._wrapWithUser((userId) => this.service.getChildren(userId));
this.sendGift = this._wrapWithUser((userId, req) => this.service.sendGift(userId, req.body.giftId)); 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;
}
});
this.getTitlesOfNobility = this._wrapWithUser((userId) => this.service.getTitlesOfNobility(userId)); 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 });
this.getHouseTypes = this._wrapWithUser((userId) => this.service.getHouseTypes(userId)); this.getHouseTypes = this._wrapWithUser((userId) => this.service.getHouseTypes(userId));
this.getMoodAffect = this._wrapWithUser((userId) => this.service.getMoodAffect(userId)); this.getMoodAffect = this._wrapWithUser((userId) => this.service.getMoodAffect(userId));
this.getCharacterAffect = this._wrapWithUser((userId) => this.service.getCharacterAffect(userId)); this.getCharacterAffect = this._wrapWithUser((userId) => this.service.getCharacterAffect(userId));
@@ -119,6 +151,17 @@ class FalukantController {
const { characterId: childId, firstName } = req.body; const { characterId: childId, firstName } = req.body;
return this.service.baptise(userId, childId, firstName); return this.service.baptise(userId, childId, firstName);
}); });
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);
});
this.decideOnChurchApplication = this._wrapWithUser((userId, req) => {
const { applicationId, decision } = req.body;
return this.service.decideOnChurchApplication(userId, applicationId, decision);
});
this.getEducation = this._wrapWithUser((userId) => this.service.getEducation(userId)); this.getEducation = this._wrapWithUser((userId) => this.service.getEducation(userId));
this.sendToSchool = this._wrapWithUser((userId, req) => { this.sendToSchool = this._wrapWithUser((userId, req) => {
@@ -134,7 +177,16 @@ class FalukantController {
this.advanceNobility = this._wrapWithUser((userId) => this.service.advanceNobility(userId)); this.advanceNobility = this._wrapWithUser((userId) => this.service.advanceNobility(userId));
this.getHealth = this._wrapWithUser((userId) => this.service.getHealth(userId)); this.getHealth = this._wrapWithUser((userId) => this.service.getHealth(userId));
this.healthActivity = this._wrapWithUser((userId, req) => this.service.healthActivity(userId, req.body.measureTr)); 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;
}
});
this.getPoliticsOverview = this._wrapWithUser((userId) => this.service.getPoliticsOverview(userId)); this.getPoliticsOverview = this._wrapWithUser((userId) => this.service.getPoliticsOverview(userId));
this.getOpenPolitics = this._wrapWithUser((userId) => this.service.getOpenPolitics(userId)); this.getOpenPolitics = this._wrapWithUser((userId) => this.service.getOpenPolitics(userId));
@@ -143,6 +195,41 @@ class FalukantController {
this.applyForElections = this._wrapWithUser((userId, req) => this.service.applyForElections(userId, req.body.electionIds)); this.applyForElections = this._wrapWithUser((userId, req) => this.service.applyForElections(userId, req.body.electionIds));
this.getRegions = this._wrapWithUser((userId) => this.service.getRegions(userId)); this.getRegions = this._wrapWithUser((userId) => this.service.getRegions(userId));
this.getBranchTaxes = this._wrapWithUser((userId, req) => this.service.getBranchTaxes(userId, req.params.branchId));
this.getProductPriceInRegion = this._wrapWithUser((userId, req) => {
const productId = parseInt(req.query.productId, 10);
const regionId = parseInt(req.query.regionId, 10);
if (Number.isNaN(productId) || Number.isNaN(regionId)) {
throw new Error('productId and regionId are required');
}
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);
const currentRegionId = req.query.currentRegionId ? parseInt(req.query.currentRegionId, 10) : null;
if (Number.isNaN(productId) || Number.isNaN(currentPrice)) {
throw new Error('productId and currentPrice are required');
}
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)); this.renovate = this._wrapWithUser((userId, req) => this.service.renovate(userId, req.body.element));
this.renovateAll = this._wrapWithUser((userId) => this.service.renovateAll(userId)); this.renovateAll = this._wrapWithUser((userId) => this.service.renovateAll(userId));
@@ -150,6 +237,7 @@ class FalukantController {
this.getNotifications = this._wrapWithUser((userId) => this.service.getNotifications(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.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.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.getUndergroundTargets = this._wrapWithUser((userId) => this.service.getPoliticalOfficeHolders(userId));
this.searchUsers = this._wrapWithUser((userId, req) => { this.searchUsers = this._wrapWithUser((userId, req) => {
@@ -181,6 +269,33 @@ 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 }
);
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 }
);
this.getTransportRoute = this._wrapWithUser(
(userId, req) => this.service.getTransportRoute(userId, req.query)
);
this.getBranchTransports = this._wrapWithUser(
(userId, req) => this.service.getBranchTransports(userId, req.params.branchId)
);
this.repairVehicle = this._wrapWithUser(
(userId, req) => this.service.repairVehicle(userId, req.params.vehicleId),
{ successStatus: 200 }
);
this.repairAllVehicles = this._wrapWithUser(
(userId, req) => this.service.repairAllVehicles(userId, req.body.vehicleIds),
{ successStatus: 200 }
);
} }
@@ -197,8 +312,14 @@ class FalukantController {
} catch (error) { } catch (error) {
console.error('Controller error:', error); console.error('Controller error:', error);
const status = error.status && typeof error.status === 'number' ? error.status : 500; 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' }); res.status(status).json({ error: error.message || 'Internal error' });
} }
}
}; };
} }

View File

@@ -4,6 +4,7 @@ import UserRight from '../models/community/user_right.js';
import UserRightType from '../models/type/user_right.js'; import UserRightType from '../models/type/user_right.js';
import UserParamType from '../models/type/user_param.js'; import UserParamType from '../models/type/user_param.js';
import FalukantUser from '../models/falukant/data/user.js'; import FalukantUser from '../models/falukant/data/user.js';
import VocabService from '../services/vocabService.js';
const menuStructure = { const menuStructure = {
home: { home: {
@@ -117,10 +118,6 @@ const menuStructure = {
visible: ["hasfalukantaccount"], visible: ["hasfalukantaccount"],
path: "/falukant/branch" path: "/falukant/branch"
}, },
directors: {
visible: ["hasfalukantaccount"],
path: "/falukant/directors"
},
family: { family: {
visible: ["hasfalukantaccount"], visible: ["hasfalukantaccount"],
path: "/falukant/family" path: "/falukant/family"
@@ -181,6 +178,30 @@ 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: { settings: {
visible: ["all"], visible: ["all"],
icon: "settings16.png", icon: "settings16.png",
@@ -251,6 +272,10 @@ const menuStructure = {
visible: ["mainadmin", "chatrooms"], visible: ["mainadmin", "chatrooms"],
path: "/admin/chatrooms" path: "/admin/chatrooms"
}, },
servicesStatus: {
visible: ["mainadmin"],
path: "/admin/services/status"
},
interests: { interests: {
visible: ["mainadmin", "interests"], visible: ["mainadmin", "interests"],
path: "/admin/interests" path: "/admin/interests"
@@ -270,6 +295,14 @@ const menuStructure = {
visible: ["mainadmin", "falukant"], visible: ["mainadmin", "falukant"],
path: "/admin/falukant/database" path: "/admin/falukant/database"
}, },
mapEditor: {
visible: ["mainadmin", "falukant"],
path: "/admin/falukant/map"
},
createNPC: {
visible: ["mainadmin", "falukant"],
path: "/admin/falukant/create-npc"
},
} }
}, },
minigames: { minigames: {
@@ -292,6 +325,7 @@ const menuStructure = {
class NavigationController { class NavigationController {
constructor() { constructor() {
this.menu = this.menu.bind(this); this.menu = this.menu.bind(this);
this.vocabService = new VocabService();
} }
calculateAge(birthDate) { calculateAge(birthDate) {
@@ -361,6 +395,11 @@ class NavigationController {
const age = this.calculateAge(birthDate); const age = this.calculateAge(birthDate);
const rights = userRights.map(ur => ur.rightType?.title).filter(Boolean); const rights = userRights.map(ur => ur.rightType?.title).filter(Boolean);
const filteredMenu = await this.filterMenu(menuStructure, rights, age, user.id); 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
res.status(200).json(filteredMenu); res.status(200).json(filteredMenu);
} catch (error) { } catch (error) {
console.error('Error fetching menu:', error); console.error('Error fetching menu:', error);

View File

@@ -0,0 +1,21 @@
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.' });
}
}
};

View File

@@ -0,0 +1,43 @@
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
class TermineController {
async getTermine(req, res) {
try {
const csvPath = path.join(__dirname, '../data/termine.csv');
const csvContent = fs.readFileSync(csvPath, 'utf-8');
const lines = csvContent.trim().split('\n');
const headers = lines[0].split(',');
const termine = lines.slice(1).map(line => {
const values = line.split(',');
const termin = {};
headers.forEach((header, index) => {
termin[header] = values[index] || '';
});
return termin;
});
// Sortiere nach Datum
termine.sort((a, b) => new Date(a.datum) - new Date(b.datum));
// Filtere nur zukünftige Termine
const heute = new Date();
heute.setHours(0, 0, 0, 0);
const zukuenftigeTermine = termine.filter(t => new Date(t.datum) >= heute);
res.status(200).json(zukuenftigeTermine);
} catch (error) {
console.error('Error reading termine.csv:', error);
res.status(500).json({ error: 'Could not load termine' });
}
}
}
export default new TermineController();

View File

@@ -0,0 +1,80 @@
import VocabService from '../services/vocabService.js';
function extractHashedUserId(req) {
return req.headers?.userid;
}
class VocabController {
constructor() {
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));
this.listChapters = this._wrapWithUser((userId, req) => this.service.listChapters(userId, req.params.languageId));
this.createChapter = this._wrapWithUser((userId, req) => this.service.createChapter(userId, req.params.languageId, req.body), { successStatus: 201 });
this.listLanguageVocabs = this._wrapWithUser((userId, req) => this.service.listLanguageVocabs(userId, req.params.languageId));
this.searchVocabs = this._wrapWithUser((userId, req) => this.service.searchVocabs(userId, req.params.languageId, req.query));
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));
}
_wrapWithUser(fn, { successStatus = 200 } = {}) {
return async (req, res) => {
try {
const hashedUserId = extractHashedUserId(req);
if (!hashedUserId) {
return res.status(400).json({ error: 'Missing user identifier' });
}
const result = await fn(hashedUserId, req, res);
res.status(successStatus).json(result);
} catch (error) {
console.error('Controller error:', error);
const status = error.status && typeof error.status === 'number' ? error.status : 500;
res.status(status).json({ error: error.message || 'Internal error' });
}
};
}
}
export default VocabController;

View File

@@ -0,0 +1,159 @@
#!/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();

123
backend/daemonServer.js Normal file
View File

@@ -0,0 +1,123 @@
import WebSocket, { WebSocketServer } from 'ws';
import https from 'https';
import fs from 'fs';
const PORT = Number.parseInt(process.env.DAEMON_PORT || '4551', 10);
const USE_TLS = process.env.DAEMON_TLS === '1';
const TLS_KEY_PATH = process.env.DAEMON_TLS_KEY_PATH;
const TLS_CERT_PATH = process.env.DAEMON_TLS_CERT_PATH;
const TLS_CA_PATH = process.env.DAEMON_TLS_CA_PATH; // optional
// Einfache In-Memory-Struktur für Verbindungen (für spätere Erweiterungen)
const connections = new Set();
function createServer() {
let wss;
if (USE_TLS) {
if (!TLS_KEY_PATH || !TLS_CERT_PATH) {
console.error('[Daemon] DAEMON_TLS=1 gesetzt, aber DAEMON_TLS_KEY_PATH/DAEMON_TLS_CERT_PATH fehlen.');
process.exit(1);
}
const httpsServer = https.createServer({
key: fs.readFileSync(TLS_KEY_PATH),
cert: fs.readFileSync(TLS_CERT_PATH),
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' });
console.log(`[Daemon] WS (ohne TLS) Server startet auf Port ${PORT} ...`);
}
wss.on('connection', (ws, req) => {
const peer = req.socket.remoteAddress + ':' + req.socket.remotePort;
ws.isAlive = true;
ws.userId = null;
connections.add(ws);
console.log(`[Daemon] Neue Verbindung von ${peer}`);
ws.on('message', (message) => {
try {
if (message.toString() === 'pong') {
// Client-Pong für unser Ping
ws.isAlive = true;
return;
}
const data = JSON.parse(message.toString());
// Vom Frontend gesendet nach Verbindungsaufbau
if (data.event === 'setUserId' && data.data?.userId) {
ws.userId = data.data.userId;
console.log(`[Daemon] setUserId erhalten: ${ws.userId}`);
return;
}
// Admin-Dialog: WebSocket-Log anfordern
if (data.event === 'getWebsocketLog') {
const response = {
event: 'getWebsocketLogResponse',
entries: [] // aktuell keine Log-Historie implementiert
};
ws.send(JSON.stringify(response));
return;
}
// Platzhalter für spätere Events
// console.log('[Daemon] Unbekanntes Event:', data);
} catch (err) {
console.error('[Daemon] Fehler beim Verarbeiten einer Nachricht:', err);
}
});
ws.on('close', () => {
connections.delete(ws);
console.log('[Daemon] Verbindung geschlossen');
});
ws.on('error', (err) => {
console.error('[Daemon] WebSocket-Fehler (Verbindung):', err);
});
});
// Einfache Ping/Pong-Mechanik, damit Verbindungen sauber erkannt werden
const interval = setInterval(() => {
for (const ws of connections) {
if (ws.isAlive === false) {
console.log('[Daemon] Verbindung wegen fehlendem Pong beendet');
ws.terminate();
connections.delete(ws);
continue;
}
ws.isAlive = false;
try {
ws.send('ping');
} catch (err) {
console.error('[Daemon] Fehler beim Senden von Ping:', err);
}
}
}, 30000);
wss.on('close', () => {
clearInterval(interval);
connections.clear();
console.log('[Daemon] Server gestoppt');
});
wss.on('error', (err) => {
console.error('[Daemon] Server-Fehler:', err);
});
return wss;
}
createServer();

7
backend/data/termine.csv Normal file
View File

@@ -0,0 +1,7 @@
datum,titel,beschreibung,ort,uhrzeit
2025-10-07,Vereinsmeisterschaften 2025 Doppel,Die Vereinsmeisterschaften 2025 im Doppel finden im Rahmen des Erwachsenentrainings statt.,,,
2026-01-17,Vereinsmeisterschaften 2025 Einzel,Die Vereinsmeisterschaften 2025 im Einzel finden in der Schulturnhalle statt. Bitte vormerken!,,10:00
2025-12-18,Weihnachtsfeier 2025,Die Weihnachtsfeier 2025 findet im Gasthaus „Zum Einhorn" in FFM-Bonames statt. Beginn 19:00 Uhr (bitte vormerken),Gasthaus „Zum Einhorn" FFM-Bonames,19:00
2025-09-14,VR-Cup,Zwei VR-Cups am 14.09.2025 (jeweils 12 und 16 Uhr),,12:00 und 16:00
2025-10-19,VR-Cup,Zwei VR-Cups am 19.10.2025 (jeweils 12 und 16 Uhr),,12:00 und 16:00
Can't render this file because it contains an unexpected character in line 4 and column 91.

View File

@@ -0,0 +1,479 @@
#!/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();

View File

@@ -0,0 +1,34 @@
import { sequelize } from './utils/sequelize.js';
async function fixPgCryptoExtension() {
try {
console.log('🔧 Aktiviere pgcrypto Erweiterung...');
await sequelize.query('CREATE EXTENSION IF NOT EXISTS pgcrypto;');
console.log('✅ pgcrypto Erweiterung erfolgreich aktiviert');
// Prüfe ob die Erweiterung aktiviert ist
const result = await sequelize.query(`
SELECT EXISTS(
SELECT 1 FROM pg_extension WHERE extname = 'pgcrypto'
) as extension_exists;
`, { type: sequelize.QueryTypes.SELECT });
if (result[0]?.extension_exists) {
console.log('✅ Bestätigung: pgcrypto Erweiterung ist aktiviert');
} else {
console.warn('⚠️ Warnung: pgcrypto Erweiterung konnte nicht aktiviert werden');
}
process.exit(0);
} catch (error) {
console.error('❌ Fehler beim Aktivieren der pgcrypto Erweiterung:', error.message);
console.error('Stack:', error.stack);
process.exit(1);
}
}
fixPgCryptoExtension();

View File

@@ -0,0 +1,29 @@
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.addColumn(
{
tableName: 'falukant_user',
schema: 'falukant_data'
},
'last_nobility_advance_at',
{
type: Sequelize.DATE,
allowNull: true
}
);
},
down: async (queryInterface, Sequelize) => {
await queryInterface.removeColumn(
{
tableName: 'falukant_user',
schema: 'falukant_data'
},
'last_nobility_advance_at'
);
}
};

View File

@@ -0,0 +1,135 @@
"use strict";
module.exports = {
async up(queryInterface, Sequelize) {
// 1) Add character_name column to notification table
await queryInterface.sequelize.query(`
ALTER TABLE IF EXISTS falukant_log.notification
ADD COLUMN IF NOT EXISTS character_name text;
`);
// 1b) Add character_id column so triggers and application can set a reference
await queryInterface.sequelize.query(`
ALTER TABLE IF EXISTS falukant_log.notification
ADD COLUMN IF NOT EXISTS character_id integer;
`);
// Create an index on character_id to speed lookups (if not exists)
await queryInterface.sequelize.query(`
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'i' AND c.relname = 'idx_notification_character_id' AND n.nspname = 'falukant_log'
) THEN
CREATE INDEX idx_notification_character_id ON falukant_log.notification (character_id);
END IF;
END$$;
`);
// 2) Create helper function to populate character_name from character_id or user_id
// - Resolve name via character_id if present
// - Fallback to a character for the same user_id when character_id is NULL
// - Only set NEW.character_name when the column exists and is NULL
await queryInterface.sequelize.query(`
CREATE OR REPLACE FUNCTION falukant_log.populate_notification_character_name()
RETURNS TRIGGER AS $function$
DECLARE
v_first_name TEXT;
v_last_name TEXT;
v_char_id INTEGER;
v_column_exists BOOLEAN;
BEGIN
-- check if target column exists in the notification table
SELECT EXISTS(
SELECT 1 FROM information_schema.columns
WHERE table_schema = 'falukant_log' AND table_name = 'notification' AND column_name = 'character_name'
) INTO v_column_exists;
IF NOT v_column_exists THEN
-- Nothing to do when target column absent
RETURN NEW;
END IF;
-- only populate when column is NULL
IF NEW.character_name IS NOT NULL THEN
RETURN NEW;
END IF;
-- prefer explicit character_id
v_char_id := NEW.character_id;
-- when character_id is null, try to find a character for the user_id
IF v_char_id IS NULL AND NEW.user_id IS NOT NULL THEN
-- choose a representative character: the one with highest id for this user (change if different policy required)
SELECT id INTO v_char_id
FROM falukant_data.character
WHERE user_id = NEW.user_id
ORDER BY id DESC
LIMIT 1;
END IF;
IF v_char_id IS NOT NULL THEN
SELECT pf.name, pl.name
INTO v_first_name, v_last_name
FROM falukant_data.character c
LEFT JOIN falukant_predefine.firstname pf ON pf.id = c.first_name
LEFT JOIN falukant_predefine.lastname pl ON pl.id = c.last_name
WHERE c.id = v_char_id;
IF v_first_name IS NOT NULL OR v_last_name IS NOT NULL THEN
NEW.character_name := COALESCE(v_first_name, '') || CASE WHEN v_first_name IS NOT NULL AND v_last_name IS NOT NULL THEN ' ' ELSE '' END || COALESCE(v_last_name, '');
ELSE
NEW.character_name := ('#' || v_char_id::text);
END IF;
ELSE
-- last resort fallback: use user_id as identifier if present
IF NEW.user_id IS NOT NULL THEN
NEW.character_name := ('#u' || NEW.user_id::text);
END IF;
END IF;
RETURN NEW;
END;
$function$ LANGUAGE plpgsql;
`);
// 3) Create trigger that runs before insert to populate the column
await queryInterface.sequelize.query(`
DROP TRIGGER IF EXISTS trg_populate_notification_character_name ON falukant_log.notification;
CREATE TRIGGER trg_populate_notification_character_name
BEFORE INSERT ON falukant_log.notification
FOR EACH ROW
EXECUTE FUNCTION falukant_log.populate_notification_character_name();
`);
},
async down(queryInterface, Sequelize) {
await queryInterface.sequelize.query(`
DROP TRIGGER IF EXISTS trg_populate_notification_character_name ON falukant_log.notification;
`);
await queryInterface.sequelize.query(`
DROP FUNCTION IF EXISTS falukant_log.populate_notification_character_name();
`);
await queryInterface.sequelize.query(`
-- drop index if exists
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'i' AND c.relname = 'idx_notification_character_id' AND n.nspname = 'falukant_log'
) THEN
EXECUTE 'DROP INDEX falukant_log.idx_notification_character_id';
END IF;
END$$;
`);
await queryInterface.sequelize.query(`
ALTER TABLE IF EXISTS falukant_log.notification
DROP COLUMN IF EXISTS character_name;
`);
await queryInterface.sequelize.query(`
ALTER TABLE IF EXISTS falukant_log.notification
DROP COLUMN IF EXISTS character_id;
`);
}
};

View File

@@ -0,0 +1,68 @@
"use strict";
module.exports = {
async up(queryInterface, Sequelize) {
// Add nullable weather_type_id column
await queryInterface.sequelize.query(`
ALTER TABLE IF EXISTS falukant_data.production
ADD COLUMN IF NOT EXISTS weather_type_id integer;
`);
// Add foreign key constraint if not exists
await queryInterface.sequelize.query(`
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu ON kcu.constraint_name = tc.constraint_name AND kcu.constraint_schema = tc.constraint_schema
WHERE tc.constraint_type = 'FOREIGN KEY'
AND tc.constraint_schema = 'falukant_data'
AND tc.table_name = 'production'
AND kcu.column_name = 'weather_type_id'
) THEN
ALTER TABLE falukant_data.production
ADD CONSTRAINT fk_production_weather_type
FOREIGN KEY (weather_type_id) REFERENCES falukant_type.weather(id);
END IF;
END$$;
`);
// create index to speed lookups
await queryInterface.sequelize.query(`
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'i' AND c.relname = 'idx_production_weather_type_id' AND n.nspname = 'falukant_data'
) THEN
CREATE INDEX idx_production_weather_type_id ON falukant_data.production (weather_type_id);
END IF;
END$$;
`);
},
async down(queryInterface, Sequelize) {
await queryInterface.sequelize.query(`
ALTER TABLE IF EXISTS falukant_data.production
DROP CONSTRAINT IF EXISTS fk_production_weather_type;
`);
await queryInterface.sequelize.query(`
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'i' AND c.relname = 'idx_production_weather_type_id' AND n.nspname = 'falukant_data'
) THEN
EXECUTE 'DROP INDEX falukant_data.idx_production_weather_type_id';
END IF;
END$$;
`);
await queryInterface.sequelize.query(`
ALTER TABLE IF EXISTS falukant_data.production
DROP COLUMN IF EXISTS weather_type_id;
`);
}
};

View File

@@ -0,0 +1,17 @@
"use strict";
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.sequelize.query(`
ALTER TABLE IF EXISTS falukant_data.stock
ADD COLUMN IF NOT EXISTS product_quality integer;
`);
},
async down(queryInterface, Sequelize) {
await queryInterface.sequelize.query(`
ALTER TABLE IF EXISTS falukant_data.stock
DROP COLUMN IF EXISTS product_quality;
`);
}
};

View File

@@ -0,0 +1,79 @@
"use strict";
module.exports = {
async up(queryInterface, Sequelize) {
// falukant_data.character.reputation (integer, default random 20..80)
// Wichtig: Schema explizit angeben
// Vorgehen:
// - Spalte anlegen (falls noch nicht vorhanden)
// - bestehende Zeilen initialisieren (random 20..80)
// - DEFAULT setzen (random 20..80)
// - NOT NULL + CHECK 0..100 erzwingen
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 = 'reputation'
) THEN
ALTER TABLE falukant_data."character"
ADD COLUMN reputation integer;
END IF;
END$$;
`);
// Backfill: nur NULLs initialisieren (damit bestehende Werte nicht überschrieben werden)
await queryInterface.sequelize.query(`
UPDATE falukant_data."character"
SET reputation = (floor(random()*61)+20)::int
WHERE reputation IS NULL;
`);
// DEFAULT + NOT NULL (nach Backfill)
await queryInterface.sequelize.query(`
ALTER TABLE falukant_data."character"
ALTER COLUMN reputation SET DEFAULT (floor(random()*61)+20)::int;
`);
await queryInterface.sequelize.query(`
ALTER TABLE falukant_data."character"
ALTER COLUMN reputation SET NOT NULL;
`);
// Enforce 0..100 at DB level (percent)
// (IF NOT EXISTS pattern, because deployments can be re-run)
await queryInterface.sequelize.query(`
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1
FROM pg_constraint c
JOIN pg_class t ON t.oid = c.conrelid
JOIN pg_namespace n ON n.oid = t.relnamespace
WHERE c.conname = 'character_reputation_0_100_chk'
AND n.nspname = 'falukant_data'
AND t.relname = 'character'
) THEN
ALTER TABLE falukant_data."character"
ADD CONSTRAINT character_reputation_0_100_chk
CHECK (reputation >= 0 AND reputation <= 100);
END IF;
END$$;
`);
},
async down(queryInterface, Sequelize) {
await queryInterface.sequelize.query(`
ALTER TABLE falukant_data."character"
DROP CONSTRAINT IF EXISTS character_reputation_0_100_chk;
`);
await queryInterface.sequelize.query(`
ALTER TABLE falukant_data."character"
DROP COLUMN IF EXISTS reputation;
`);
},
};

View File

@@ -0,0 +1,47 @@
/* eslint-disable */
module.exports = {
async up(queryInterface, Sequelize) {
// Typ-Tabelle (konfigurierbar ohne Code): falukant_type.reputation_action
await queryInterface.sequelize.query(`
CREATE TABLE IF NOT EXISTS falukant_type.reputation_action (
id serial PRIMARY KEY,
tr text NOT NULL UNIQUE,
cost integer NOT NULL CHECK (cost >= 0),
base_gain integer NOT NULL CHECK (base_gain >= 0),
decay_factor double precision NOT NULL CHECK (decay_factor > 0 AND decay_factor <= 1),
min_gain integer NOT NULL DEFAULT 0 CHECK (min_gain >= 0),
decay_window_days integer NOT NULL DEFAULT 7 CHECK (decay_window_days >= 1 AND decay_window_days <= 365)
);
`);
// Log-Tabelle: falukant_log.reputation_action
await queryInterface.sequelize.query(`
CREATE TABLE IF NOT EXISTS falukant_log.reputation_action (
id serial PRIMARY KEY,
falukant_user_id integer NOT NULL,
action_type_id integer NOT NULL,
cost integer NOT NULL CHECK (cost >= 0),
base_gain integer NOT NULL CHECK (base_gain >= 0),
gain integer NOT NULL CHECK (gain >= 0),
times_used_before integer NOT NULL CHECK (times_used_before >= 0),
action_timestamp timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP
);
`);
await queryInterface.sequelize.query(`
CREATE INDEX IF NOT EXISTS reputation_action_log_user_type_idx
ON falukant_log.reputation_action (falukant_user_id, action_type_id);
`);
await queryInterface.sequelize.query(`
CREATE INDEX IF NOT EXISTS reputation_action_log_ts_idx
ON falukant_log.reputation_action (action_timestamp);
`);
},
async down(queryInterface, Sequelize) {
await queryInterface.sequelize.query(`DROP TABLE IF EXISTS falukant_log.reputation_action;`);
await queryInterface.sequelize.query(`DROP TABLE IF EXISTS falukant_type.reputation_action;`);
},
};

View File

@@ -0,0 +1,46 @@
/* eslint-disable */
module.exports = {
async up(queryInterface, Sequelize) {
// Für bereits existierende Installationen: Spalte sicherstellen + Backfill
await queryInterface.sequelize.query(`
ALTER TABLE falukant_type.reputation_action
ADD COLUMN IF NOT EXISTS decay_window_days integer;
`);
await queryInterface.sequelize.query(`
UPDATE falukant_type.reputation_action
SET decay_window_days = 7
WHERE decay_window_days IS NULL;
`);
await queryInterface.sequelize.query(`
ALTER TABLE falukant_type.reputation_action
ALTER COLUMN decay_window_days SET DEFAULT 7;
`);
await queryInterface.sequelize.query(`
ALTER TABLE falukant_type.reputation_action
ALTER COLUMN decay_window_days SET NOT NULL;
`);
await queryInterface.sequelize.query(`
ALTER TABLE falukant_type.reputation_action
DROP CONSTRAINT IF EXISTS reputation_action_decay_window_days_chk;
`);
await queryInterface.sequelize.query(`
ALTER TABLE falukant_type.reputation_action
ADD CONSTRAINT reputation_action_decay_window_days_chk
CHECK (decay_window_days >= 1 AND decay_window_days <= 365);
`);
},
async down(queryInterface, Sequelize) {
// optional: wieder entfernen
await queryInterface.sequelize.query(`
ALTER TABLE falukant_type.reputation_action
DROP CONSTRAINT IF EXISTS reputation_action_decay_window_days_chk;
`);
await queryInterface.sequelize.query(`
ALTER TABLE falukant_type.reputation_action
DROP COLUMN IF EXISTS decay_window_days;
`);
},
};

View File

@@ -0,0 +1,50 @@
/* eslint-disable */
module.exports = {
async up(queryInterface, Sequelize) {
// Idempotentes Seed: legt Ruf-Aktionen an bzw. aktualisiert sie anhand "tr"
await queryInterface.sequelize.query(`
INSERT INTO falukant_type.reputation_action
(tr, cost, base_gain, decay_factor, min_gain, decay_window_days)
VALUES
('soup_kitchen', 500, 2, 0.85, 0, 7),
('library_donation', 5000, 4, 0.88, 0, 7),
('well_build', 8000, 4, 0.87, 0, 7),
('scholarships', 10000, 5, 0.87, 0, 7),
('church_hospice', 12000, 5, 0.87, 0, 7),
('school_funding', 15000, 6, 0.88, 0, 7),
('orphanage_build', 20000, 7, 0.90, 0, 7),
('bridge_build', 25000, 7, 0.90, 0, 7),
('hospital_donation', 30000, 8, 0.90, 0, 7),
('patronage', 40000, 9, 0.91, 0, 7),
('statue_build', 50000, 10, 0.92, 0, 7)
ON CONFLICT (tr) DO UPDATE SET
cost = EXCLUDED.cost,
base_gain = EXCLUDED.base_gain,
decay_factor = EXCLUDED.decay_factor,
min_gain = EXCLUDED.min_gain,
decay_window_days = EXCLUDED.decay_window_days;
`);
},
async down(queryInterface, Sequelize) {
// Entfernt nur die gesetzten Seeds (tr-basiert)
await queryInterface.sequelize.query(`
DELETE FROM falukant_type.reputation_action
WHERE tr IN (
'soup_kitchen',
'library_donation',
'well_build',
'scholarships',
'church_hospice',
'school_funding',
'orphanage_build',
'bridge_build',
'hospital_donation',
'patronage',
'statue_build'
);
`);
},
};

View File

@@ -0,0 +1,60 @@
/* eslint-disable */
module.exports = {
async up(queryInterface, Sequelize) {
// Ensure column exists
await queryInterface.sequelize.query(`
ALTER TABLE falukant_data.vehicle
ADD COLUMN IF NOT EXISTS condition integer;
`);
// Backfill nulls (legacy data)
await queryInterface.sequelize.query(`
UPDATE falukant_data.vehicle
SET condition = 100
WHERE condition IS NULL;
`);
// Clamp out-of-range values defensively
await queryInterface.sequelize.query(`
UPDATE falukant_data.vehicle
SET condition = GREATEST(0, LEAST(100, condition))
WHERE condition < 0 OR condition > 100;
`);
// Default + NOT NULL
await queryInterface.sequelize.query(`
ALTER TABLE falukant_data.vehicle
ALTER COLUMN condition SET DEFAULT 100;
`);
await queryInterface.sequelize.query(`
ALTER TABLE falukant_data.vehicle
ALTER COLUMN condition SET NOT NULL;
`);
// Check constraint 0..100
await queryInterface.sequelize.query(`
ALTER TABLE falukant_data.vehicle
DROP CONSTRAINT IF EXISTS vehicle_condition_0_100_chk;
`);
await queryInterface.sequelize.query(`
ALTER TABLE falukant_data.vehicle
ADD CONSTRAINT vehicle_condition_0_100_chk
CHECK (condition >= 0 AND condition <= 100);
`);
},
async down(queryInterface, Sequelize) {
// Keep the column, but remove constraint/default to be reversible
await queryInterface.sequelize.query(`
ALTER TABLE falukant_data.vehicle
DROP CONSTRAINT IF EXISTS vehicle_condition_0_100_chk;
`);
await queryInterface.sequelize.query(`
ALTER TABLE falukant_data.vehicle
ALTER COLUMN condition DROP DEFAULT;
`);
// NOT NULL not reverted to avoid introducing NULLs on rollback; can be adjusted if needed
},
};

View File

@@ -0,0 +1,32 @@
/* eslint-disable */
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.sequelize.query(`
ALTER TABLE falukant_data.director
ADD COLUMN IF NOT EXISTS may_repair_vehicles boolean;
`);
await queryInterface.sequelize.query(`
UPDATE falukant_data.director
SET may_repair_vehicles = true
WHERE may_repair_vehicles IS NULL;
`);
await queryInterface.sequelize.query(`
ALTER TABLE falukant_data.director
ALTER COLUMN may_repair_vehicles SET DEFAULT true;
`);
await queryInterface.sequelize.query(`
ALTER TABLE falukant_data.director
ALTER COLUMN may_repair_vehicles SET NOT NULL;
`);
},
async down(queryInterface, Sequelize) {
// optional rollback: drop column
await queryInterface.sequelize.query(`
ALTER TABLE falukant_data.director
DROP COLUMN IF EXISTS may_repair_vehicles;
`);
},
};

View File

@@ -0,0 +1,61 @@
/* eslint-disable */
'use strict';
module.exports = {
async up(queryInterface) {
// Sprache / Set, das geteilt werden kann
await queryInterface.sequelize.query(`
CREATE TABLE IF NOT EXISTS community.vocab_language (
id SERIAL PRIMARY KEY,
owner_user_id INTEGER NOT NULL,
name TEXT NOT NULL,
share_code TEXT NOT NULL,
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),
CONSTRAINT vocab_language_owner_fk
FOREIGN KEY (owner_user_id)
REFERENCES community."user"(id)
ON DELETE CASCADE,
CONSTRAINT vocab_language_share_code_uniq UNIQUE (share_code)
);
`);
// Abos (Freunde)
await queryInterface.sequelize.query(`
CREATE TABLE IF NOT EXISTS community.vocab_language_subscription (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL,
language_id INTEGER NOT NULL,
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),
CONSTRAINT vocab_language_subscription_user_fk
FOREIGN KEY (user_id)
REFERENCES community."user"(id)
ON DELETE CASCADE,
CONSTRAINT vocab_language_subscription_language_fk
FOREIGN KEY (language_id)
REFERENCES community.vocab_language(id)
ON DELETE CASCADE,
CONSTRAINT vocab_language_subscription_uniq UNIQUE (user_id, language_id)
);
`);
await queryInterface.sequelize.query(`
CREATE INDEX IF NOT EXISTS vocab_language_owner_idx
ON community.vocab_language(owner_user_id);
`);
await queryInterface.sequelize.query(`
CREATE INDEX IF NOT EXISTS vocab_language_subscription_user_idx
ON community.vocab_language_subscription(user_id);
`);
await queryInterface.sequelize.query(`
CREATE INDEX IF NOT EXISTS vocab_language_subscription_language_idx
ON community.vocab_language_subscription(language_id);
`);
},
async down(queryInterface) {
await queryInterface.sequelize.query(`DROP TABLE IF EXISTS community.vocab_language_subscription;`);
await queryInterface.sequelize.query(`DROP TABLE IF EXISTS community.vocab_language;`);
}
};

View File

@@ -0,0 +1,106 @@
/* eslint-disable */
'use strict';
module.exports = {
async up(queryInterface) {
// Kapitel innerhalb einer Sprache
await queryInterface.sequelize.query(`
CREATE TABLE IF NOT EXISTS community.vocab_chapter (
id SERIAL PRIMARY KEY,
language_id INTEGER NOT NULL,
title TEXT NOT NULL,
created_by_user_id INTEGER NOT NULL,
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),
CONSTRAINT vocab_chapter_language_fk
FOREIGN KEY (language_id)
REFERENCES community.vocab_language(id)
ON DELETE CASCADE,
CONSTRAINT vocab_chapter_creator_fk
FOREIGN KEY (created_by_user_id)
REFERENCES community."user"(id)
ON DELETE CASCADE
);
`);
await queryInterface.sequelize.query(`
CREATE INDEX IF NOT EXISTS vocab_chapter_language_idx
ON community.vocab_chapter(language_id);
`);
// Lexeme/Wörter (wir deduplizieren pro Sprache über normalized)
await queryInterface.sequelize.query(`
CREATE TABLE IF NOT EXISTS community.vocab_lexeme (
id SERIAL PRIMARY KEY,
language_id INTEGER NOT NULL,
text TEXT NOT NULL,
normalized TEXT NOT NULL,
created_by_user_id INTEGER NOT NULL,
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),
CONSTRAINT vocab_lexeme_language_fk
FOREIGN KEY (language_id)
REFERENCES community.vocab_language(id)
ON DELETE CASCADE,
CONSTRAINT vocab_lexeme_creator_fk
FOREIGN KEY (created_by_user_id)
REFERENCES community."user"(id)
ON DELETE CASCADE,
CONSTRAINT vocab_lexeme_unique_per_language UNIQUE (language_id, normalized)
);
`);
await queryInterface.sequelize.query(`
CREATE INDEX IF NOT EXISTS vocab_lexeme_language_idx
ON community.vocab_lexeme(language_id);
`);
// n:m Zuordnung pro Kapitel: Lernwort ↔ Referenzwort (Mehrdeutigkeiten möglich)
await queryInterface.sequelize.query(`
CREATE TABLE IF NOT EXISTS community.vocab_chapter_lexeme (
id SERIAL PRIMARY KEY,
chapter_id INTEGER NOT NULL,
learning_lexeme_id INTEGER NOT NULL,
reference_lexeme_id INTEGER NOT NULL,
created_by_user_id INTEGER NOT NULL,
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),
CONSTRAINT vocab_chlex_chapter_fk
FOREIGN KEY (chapter_id)
REFERENCES community.vocab_chapter(id)
ON DELETE CASCADE,
CONSTRAINT vocab_chlex_learning_fk
FOREIGN KEY (learning_lexeme_id)
REFERENCES community.vocab_lexeme(id)
ON DELETE CASCADE,
CONSTRAINT vocab_chlex_reference_fk
FOREIGN KEY (reference_lexeme_id)
REFERENCES community.vocab_lexeme(id)
ON DELETE CASCADE,
CONSTRAINT vocab_chlex_creator_fk
FOREIGN KEY (created_by_user_id)
REFERENCES community."user"(id)
ON DELETE CASCADE,
CONSTRAINT vocab_chlex_unique UNIQUE (chapter_id, learning_lexeme_id, reference_lexeme_id)
);
`);
await queryInterface.sequelize.query(`
CREATE INDEX IF NOT EXISTS vocab_chlex_chapter_idx
ON community.vocab_chapter_lexeme(chapter_id);
`);
await queryInterface.sequelize.query(`
CREATE INDEX IF NOT EXISTS vocab_chlex_learning_idx
ON community.vocab_chapter_lexeme(learning_lexeme_id);
`);
await queryInterface.sequelize.query(`
CREATE INDEX IF NOT EXISTS vocab_chlex_reference_idx
ON community.vocab_chapter_lexeme(reference_lexeme_id);
`);
},
async down(queryInterface) {
await queryInterface.sequelize.query(`DROP TABLE IF EXISTS community.vocab_chapter_lexeme;`);
await queryInterface.sequelize.query(`DROP TABLE IF EXISTS community.vocab_lexeme;`);
await queryInterface.sequelize.query(`DROP TABLE IF EXISTS community.vocab_chapter;`);
}
};

View File

@@ -0,0 +1,17 @@
"use strict";
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.sequelize.query(`
ALTER TABLE IF EXISTS falukant_data.region
ADD COLUMN IF NOT EXISTS tax_percent numeric NOT NULL DEFAULT 7;
`);
},
async down(queryInterface, Sequelize) {
await queryInterface.sequelize.query(`
ALTER TABLE IF EXISTS falukant_data.region
DROP COLUMN IF EXISTS tax_percent;
`);
}
};

View File

@@ -0,0 +1,50 @@
"use strict";
module.exports = {
async up(queryInterface, Sequelize) {
// 1) add backup column for original sell_cost (idempotent)
await queryInterface.sequelize.query(`
ALTER TABLE IF EXISTS falukant_type.product
ADD COLUMN IF NOT EXISTS original_sell_cost numeric;
`);
// 2) if original_sell_cost is not set, copy current sell_cost into it
await queryInterface.sequelize.query(`
UPDATE falukant_type.product
SET original_sell_cost = sell_cost
WHERE original_sell_cost IS NULL;
`);
// 3) compute max cumulative tax across regions and increase sell_cost accordingly
// We use the maximum cumulative tax (worst-case) so sellers are neutral across regions.
// Formula: neutral_sell = CEIL(original_sell_cost * (1 / (1 - max_total/100)))
await queryInterface.sequelize.query(`
WITH RECURSIVE ancestors AS (
SELECT id AS start_id, id, parent_id, tax_percent FROM falukant_data.region
UNION ALL
SELECT a.start_id, r.id, r.parent_id, r.tax_percent
FROM falukant_data.region r
JOIN ancestors a ON r.id = a.parent_id
), totals AS (
SELECT start_id, COALESCE(SUM(tax_percent), 0) AS total FROM ancestors GROUP BY start_id
), mm AS (
SELECT COALESCE(MAX(total),0) AS max_total FROM totals
)
UPDATE falukant_type.product
SET sell_cost = CEIL(original_sell_cost * (CASE WHEN (1 - mm.max_total/100) <= 0 THEN 1 ELSE (1 / (1 - mm.max_total/100)) END))
FROM mm
WHERE original_sell_cost IS NOT NULL;
`);
},
async down(queryInterface, Sequelize) {
await queryInterface.sequelize.query(`
ALTER TABLE IF EXISTS falukant_type.product
DROP COLUMN IF EXISTS sell_cost_min_neutral;
`);
await queryInterface.sequelize.query(`
ALTER TABLE IF EXISTS falukant_type.product
DROP COLUMN IF EXISTS sell_cost_max_neutral;
`);
}
};

View File

@@ -0,0 +1,13 @@
-- 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;

View File

@@ -0,0 +1,43 @@
-- 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);

View File

@@ -0,0 +1,132 @@
/* 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;
`);
}
};

View File

@@ -0,0 +1,101 @@
/* 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;
`);
}
};

View File

@@ -0,0 +1,47 @@
/* 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;
`);
}
};

View File

@@ -0,0 +1,33 @@
/* 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;
`);
}
};

View File

@@ -0,0 +1,30 @@
"use strict";
module.exports = {
async up(queryInterface, Sequelize) {
// Create index on (user_id, shown) to optimize markNotificationsShown queries
// This prevents deadlocks by allowing fast lookups and reducing lock contention
await queryInterface.sequelize.query(`
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'i'
AND c.relname = 'idx_notification_user_id_shown'
AND n.nspname = 'falukant_log'
) THEN
CREATE INDEX idx_notification_user_id_shown
ON falukant_log.notification (user_id, shown);
END IF;
END$$;
`);
},
async down(queryInterface, Sequelize) {
await queryInterface.sequelize.query(`
DROP INDEX IF EXISTS falukant_log.idx_notification_user_id_shown;
`);
}
};

View File

@@ -0,0 +1,19 @@
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;

View File

@@ -0,0 +1,20 @@
-- Migration: Add condition and available_from columns to vehicle table
-- Date: 2024-12-02
ALTER TABLE falukant_data.vehicle
ADD COLUMN IF NOT EXISTS condition INTEGER NOT NULL DEFAULT 100;
ALTER TABLE falukant_data.vehicle
ADD COLUMN IF NOT EXISTS available_from TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
COMMENT ON COLUMN falukant_data.vehicle.condition IS 'Current condition of the vehicle (0-100)';
COMMENT ON COLUMN falukant_data.vehicle.available_from IS 'Timestamp when the vehicle becomes available for use';
-- Migration: Add build_time_minutes column to vehicle type table
-- Date: 2024-12-03
ALTER TABLE falukant_type.vehicle
ADD COLUMN IF NOT EXISTS build_time_minutes INTEGER NOT NULL DEFAULT 0;
COMMENT ON COLUMN falukant_type.vehicle.build_time_minutes IS 'Time to construct the vehicle, in minutes';

View File

@@ -0,0 +1,9 @@
-- Migration: Add is_heir column to child_relation table
-- Date: 2025-12-08
-- Description: Adds a boolean field to mark a child as the heir
ALTER TABLE falukant_data.child_relation
ADD COLUMN IF NOT EXISTS is_heir BOOLEAN DEFAULT false;
COMMENT ON COLUMN falukant_data.child_relation.is_heir IS 'Marks whether this child is set as the heir';

View File

@@ -2,6 +2,11 @@
module.exports = { module.exports = {
up: async (queryInterface, Sequelize) => { up: async (queryInterface, Sequelize) => {
// Aktiviere die pgcrypto Erweiterung, die die digest() Funktion bereitstellt
await queryInterface.sequelize.query(`
CREATE EXTENSION IF NOT EXISTS pgcrypto;
`);
await queryInterface.sequelize.query(` await queryInterface.sequelize.query(`
CREATE OR REPLACE FUNCTION community.update_hashed_id() RETURNS TRIGGER AS $$ CREATE OR REPLACE FUNCTION community.update_hashed_id() RETURNS TRIGGER AS $$
BEGIN BEGIN

View File

@@ -0,0 +1,7 @@
-- Migration: Make productId and size nullable in transport table
-- This allows empty transports (moving vehicles without products)
ALTER TABLE falukant_data.transport
ALTER COLUMN product_id DROP NOT NULL,
ALTER COLUMN size DROP NOT NULL;

View File

@@ -5,6 +5,7 @@ import ChatUser from './chat/user.js';
import Room from './chat/room.js'; import Room from './chat/room.js';
import User from './community/user.js'; import User from './community/user.js';
import UserParam from './community/user_param.js'; import UserParam from './community/user_param.js';
import UserDashboard from './community/user_dashboard.js';
import UserParamType from './type/user_param.js'; import UserParamType from './type/user_param.js';
import UserRightType from './type/user_right.js'; import UserRightType from './type/user_right.js';
import UserRight from './community/user_right.js'; import UserRight from './community/user_right.js';
@@ -44,6 +45,7 @@ import FalukantStockType from './falukant/type/stock.js';
import Knowledge from './falukant/data/product_knowledge.js'; import Knowledge from './falukant/data/product_knowledge.js';
import ProductType from './falukant/type/product.js'; import ProductType from './falukant/type/product.js';
import TitleOfNobility from './falukant/type/title_of_nobility.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 TitleRequirement from './falukant/type/title_requirement.js';
import Branch from './falukant/data/branch.js'; import Branch from './falukant/data/branch.js';
import BranchType from './falukant/type/branch.js'; import BranchType from './falukant/type/branch.js';
@@ -93,10 +95,30 @@ import PoliticalOfficeRequirement from './falukant/predefine/political_office_pr
import PoliticalOfficePrerequisite from './falukant/predefine/political_office_prerequisite.js'; import PoliticalOfficePrerequisite from './falukant/predefine/political_office_prerequisite.js';
import PoliticalOfficeHistory from './falukant/log/political_office_history.js'; import PoliticalOfficeHistory from './falukant/log/political_office_history.js';
import ElectionHistory from './falukant/log/election_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 Underground from './falukant/data/underground.js';
import UndergroundType from './falukant/type/underground.js'; import UndergroundType from './falukant/type/underground.js';
import VehicleType from './falukant/type/vehicle.js';
import Vehicle from './falukant/data/vehicle.js';
import Transport from './falukant/data/transport.js';
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 Blog from './community/blog.js';
import BlogPost from './community/blog_post.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 Campaign from './match3/campaign.js';
import Match3Level from './match3/level.js'; import Match3Level from './match3/level.js';
import Objective from './match3/objective.js'; import Objective from './match3/objective.js';
@@ -148,6 +170,9 @@ export default function setupAssociations() {
User.hasMany(UserParam, { foreignKey: 'userId', as: 'user_params' }); User.hasMany(UserParam, { foreignKey: 'userId', as: 'user_params' });
UserParam.belongsTo(User, { foreignKey: 'userId', as: 'user' }); 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' }); UserParamValue.belongsTo(UserParamType, { foreignKey: 'userParamTypeId', as: 'user_param_value_type' });
UserParamType.hasMany(UserParamValue, { foreignKey: 'userParamTypeId', as: 'user_param_type_value' }); UserParamType.hasMany(UserParamValue, { foreignKey: 'userParamTypeId', as: 'user_param_type_value' });
@@ -284,6 +309,21 @@ export default function setupAssociations() {
RegionData.belongsTo(RegionType, { foreignKey: 'regionTypeId', as: 'regionType' }); RegionData.belongsTo(RegionType, { foreignKey: 'regionTypeId', as: 'regionType' });
RegionType.hasMany(RegionData, { foreignKey: 'regionTypeId', as: 'regions' }); RegionType.hasMany(RegionData, { foreignKey: 'regionTypeId', as: 'regions' });
Weather.belongsTo(RegionData, { foreignKey: 'regionId', as: 'region' });
RegionData.hasOne(Weather, { foreignKey: 'regionId', as: 'weather' });
Weather.belongsTo(WeatherType, { foreignKey: 'weatherTypeId', as: 'weatherType' });
WeatherType.hasMany(Weather, { foreignKey: 'weatherTypeId', as: 'weathers' });
ProductWeatherEffect.belongsTo(ProductType, { foreignKey: 'productId', as: 'product' });
ProductType.hasMany(ProductWeatherEffect, { foreignKey: 'productId', as: 'weatherEffects' });
ProductWeatherEffect.belongsTo(WeatherType, { foreignKey: 'weatherTypeId', as: 'weatherType' });
WeatherType.hasMany(ProductWeatherEffect, { foreignKey: 'weatherTypeId', as: 'productEffects' });
Production.belongsTo(WeatherType, { foreignKey: 'weatherTypeId', as: 'weatherType' });
WeatherType.hasMany(Production, { foreignKey: 'weatherTypeId', as: 'productions' });
FalukantUser.belongsTo(RegionData, { foreignKey: 'mainBranchRegionId', as: 'mainBranchRegion' }); FalukantUser.belongsTo(RegionData, { foreignKey: 'mainBranchRegionId', as: 'mainBranchRegion' });
RegionData.hasMany(FalukantUser, { foreignKey: 'mainBranchRegionId', as: 'users' }); RegionData.hasMany(FalukantUser, { foreignKey: 'mainBranchRegionId', as: 'users' });
@@ -313,6 +353,8 @@ export default function setupAssociations() {
TitleRequirement.belongsTo(TitleOfNobility, { foreignKey: 'titleId', as: 'title' }); TitleRequirement.belongsTo(TitleOfNobility, { foreignKey: 'titleId', as: 'title' });
TitleOfNobility.hasMany(TitleRequirement, { foreignKey: 'titleId', as: 'requirements' }); 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' }); Branch.belongsTo(RegionData, { foreignKey: 'regionId', as: 'region' });
RegionData.hasMany(Branch, { foreignKey: 'regionId', as: 'branches' }); RegionData.hasMany(Branch, { foreignKey: 'regionId', as: 'branches' });
@@ -383,6 +425,13 @@ export default function setupAssociations() {
DaySell.belongsTo(FalukantUser, { foreignKey: 'sellerId', as: 'user' }); DaySell.belongsTo(FalukantUser, { foreignKey: 'sellerId', as: 'user' });
FalukantUser.hasMany(DaySell, { foreignKey: 'sellerId', as: 'daySells' }); 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' }); Notification.belongsTo(FalukantUser, { foreignKey: 'userId', as: 'user' });
FalukantUser.hasMany(Notification, { foreignKey: 'userId', as: 'notifications' }); FalukantUser.hasMany(Notification, { foreignKey: 'userId', as: 'notifications' });
@@ -421,6 +470,89 @@ export default function setupAssociations() {
PromotionalGiftLog.belongsTo(FalukantCharacter, { foreignKey: 'recipientCharacterId', as: 'recipient' }); PromotionalGiftLog.belongsTo(FalukantCharacter, { foreignKey: 'recipientCharacterId', as: 'recipient' });
FalukantCharacter.hasMany(PromotionalGiftLog, { foreignKey: 'recipientCharacterId', as: 'giftlogs' }); FalukantCharacter.hasMany(PromotionalGiftLog, { foreignKey: 'recipientCharacterId', as: 'giftlogs' });
// Vehicles & Transports
VehicleType.hasMany(Vehicle, {
foreignKey: 'vehicleTypeId',
as: 'vehicles',
});
Vehicle.belongsTo(VehicleType, {
foreignKey: 'vehicleTypeId',
as: 'type',
});
FalukantUser.hasMany(Vehicle, {
foreignKey: 'falukantUserId',
as: 'vehicles',
});
Vehicle.belongsTo(FalukantUser, {
foreignKey: 'falukantUserId',
as: 'owner',
});
RegionData.hasMany(Vehicle, {
foreignKey: 'regionId',
as: 'vehicles',
});
Vehicle.belongsTo(RegionData, {
foreignKey: 'regionId',
as: 'region',
});
// Region distances
RegionData.hasMany(RegionDistance, {
foreignKey: 'sourceRegionId',
as: 'distancesFrom',
});
RegionData.hasMany(RegionDistance, {
foreignKey: 'targetRegionId',
as: 'distancesTo',
});
RegionDistance.belongsTo(RegionData, {
foreignKey: 'sourceRegionId',
as: 'sourceRegion',
});
RegionDistance.belongsTo(RegionData, {
foreignKey: 'targetRegionId',
as: 'targetRegion',
});
Transport.belongsTo(RegionData, {
foreignKey: 'sourceRegionId',
as: 'sourceRegion',
});
Transport.belongsTo(RegionData, {
foreignKey: 'targetRegionId',
as: 'targetRegion',
});
RegionData.hasMany(Transport, {
foreignKey: 'sourceRegionId',
as: 'outgoingTransports',
});
RegionData.hasMany(Transport, {
foreignKey: 'targetRegionId',
as: 'incomingTransports',
});
Transport.belongsTo(ProductType, {
foreignKey: 'productId',
as: 'productType',
});
ProductType.hasMany(Transport, {
foreignKey: 'productId',
as: 'transports',
});
Transport.belongsTo(Vehicle, {
foreignKey: 'vehicleId',
as: 'vehicle',
});
Vehicle.hasMany(Transport, {
foreignKey: 'vehicleId',
as: 'transports',
});
PromotionalGift.hasMany(PromotionalGiftCharacterTrait, { foreignKey: 'gift_id', as: 'characterTraits' }); PromotionalGift.hasMany(PromotionalGiftCharacterTrait, { foreignKey: 'gift_id', as: 'characterTraits' });
PromotionalGift.hasMany(PromotionalGiftMood, { foreignKey: 'gift_id', as: 'promotionalgiftmoods' }); PromotionalGift.hasMany(PromotionalGiftMood, { foreignKey: 'gift_id', as: 'promotionalgiftmoods' });
@@ -453,14 +585,14 @@ export default function setupAssociations() {
Party.belongsToMany(TitleOfNobility, { Party.belongsToMany(TitleOfNobility, {
through: PartyInvitedNobility, through: PartyInvitedNobility,
foreignKey: 'party_id', foreignKey: 'partyId',
otherKey: 'title_of_nobility_id', otherKey: 'titleOfNobilityId',
as: 'invitedNobilities', as: 'invitedNobilities',
}); });
TitleOfNobility.belongsToMany(Party, { TitleOfNobility.belongsToMany(Party, {
through: PartyInvitedNobility, through: PartyInvitedNobility,
foreignKey: 'title_of_nobility_id', foreignKey: 'titleOfNobilityId',
otherKey: 'party_id', otherKey: 'partyId',
as: 'partiesInvitedTo', as: 'partiesInvitedTo',
}); });
@@ -493,44 +625,52 @@ export default function setupAssociations() {
Learning.belongsTo(LearnRecipient, { Learning.belongsTo(LearnRecipient, {
foreignKey: 'learningRecipientId', foreignKey: 'learningRecipientId',
as: 'recipient' as: 'recipient',
constraints: false
} }
); );
LearnRecipient.hasMany(Learning, { LearnRecipient.hasMany(Learning, {
foreignKey: 'learningRecipientId', foreignKey: 'learningRecipientId',
as: 'learnings' as: 'learnings',
constraints: false
}); });
Learning.belongsTo(FalukantUser, { Learning.belongsTo(FalukantUser, {
foreignKey: 'associatedFalukantUserId', foreignKey: 'associatedFalukantUserId',
as: 'learner' as: 'learner',
constraints: false
} }
); );
FalukantUser.hasMany(Learning, { FalukantUser.hasMany(Learning, {
foreignKey: 'associatedFalukantUserId', foreignKey: 'associatedFalukantUserId',
as: 'learnings' as: 'learnings',
constraints: false
}); });
Learning.belongsTo(ProductType, { Learning.belongsTo(ProductType, {
foreignKey: 'productId', foreignKey: 'productId',
as: 'productType' as: 'productType',
constraints: false
}); });
ProductType.hasMany(Learning, { ProductType.hasMany(Learning, {
foreignKey: 'productId', foreignKey: 'productId',
as: 'learnings' as: 'learnings',
constraints: false
}); });
Learning.belongsTo(FalukantCharacter, { Learning.belongsTo(FalukantCharacter, {
foreignKey: 'associatedLearningCharacterId', foreignKey: 'associatedLearningCharacterId',
as: 'learningCharacter' as: 'learningCharacter',
constraints: false
}); });
FalukantCharacter.hasMany(Learning, { FalukantCharacter.hasMany(Learning, {
foreignKey: 'associatedLearningCharacterId', foreignKey: 'associatedLearningCharacterId',
as: 'learningsCharacter' as: 'learningsCharacter',
constraints: false
}); });
FalukantUser.hasMany(Credit, { FalukantUser.hasMany(Credit, {
@@ -746,6 +886,96 @@ 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, { Underground.belongsTo(UndergroundType, {
foreignKey: 'undergroundTypeId', foreignKey: 'undergroundTypeId',
as: 'undergroundType' as: 'undergroundType'
@@ -828,5 +1058,41 @@ export default function setupAssociations() {
TaxiHighscore.belongsTo(TaxiMap, { foreignKey: 'mapId', as: 'map' }); TaxiHighscore.belongsTo(TaxiMap, { foreignKey: 'mapId', as: 'map' });
TaxiMap.hasMany(TaxiHighscore, { foreignKey: 'mapId', as: 'highscores' }); 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' });
} }

View File

@@ -0,0 +1,86 @@
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;

View File

@@ -8,16 +8,12 @@ const Folder = sequelize.define('folder', {
allowNull: false}, allowNull: false},
parentId: { parentId: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: true, allowNull: true
references: { },
model: 'folder',
key: 'id'}},
userId: { userId: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: false, allowNull: false
references: { }}, {
model: 'user',
key: 'id'}}}, {
tableName: 'folder', tableName: 'folder',
schema: 'community', schema: 'community',
underscored: true, underscored: true,

View File

@@ -10,22 +10,11 @@ const FolderImageVisibility = sequelize.define('folder_image_visibility', {
}, },
folderId: { folderId: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: false, allowNull: false
references: {
model: 'folder',
key: 'id'
}
}, },
visibilityTypeId: { visibilityTypeId: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: false, allowNull: false
references: {
model: {
schema: 'type',
tableName: 'image_visibility_type'
},
key: 'id'
}
} }
}, { }, {
tableName: 'folder_image_visibility', tableName: 'folder_image_visibility',

View File

@@ -10,19 +10,11 @@ const FolderVisibilityUser = sequelize.define('folder_visibility_user', {
}, },
folderId: { folderId: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: false, allowNull: false
references: {
model: 'folder',
key: 'id'
}
}, },
visibilityUserId: { visibilityUserId: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: false, allowNull: false
references: {
model: 'image_visibility_user',
key: 'id'
}
} }
}, { }, {
tableName: 'folder_visibility_user', tableName: 'folder_visibility_user',

View File

@@ -10,19 +10,11 @@ const GuestbookEntry = sequelize.define('guestbook_entry', {
allowNull: false}, allowNull: false},
recipientId: { recipientId: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: false, allowNull: false
references: {
model: User,
key: 'id'
}
}, },
senderId: { senderId: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: true, allowNull: true
references: {
model: User,
key: 'id'
}
}, },
senderUsername: { senderUsername: {
type: DataTypes.STRING, type: DataTypes.STRING,

View File

@@ -18,16 +18,12 @@ const Image = sequelize.define('image', {
unique: true}, unique: true},
folderId: { folderId: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: false, allowNull: false
references: { },
model: 'folder',
key: 'id'}},
userId: { userId: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: false, allowNull: false
references: { }}, {
model: 'user',
key: 'id'}}}, {
tableName: 'image', tableName: 'image',
schema: 'community', schema: 'community',
underscored: true, underscored: true,

View File

@@ -10,22 +10,11 @@ const ImageImageVisibility = sequelize.define('image_image_visibility', {
}, },
imageId: { imageId: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: false, allowNull: false
references: {
model: 'image',
key: 'id'
}
}, },
visibilityTypeId: { visibilityTypeId: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: false, allowNull: false
references: {
model: {
schema: 'type',
tableName: 'image_visibility_type'
},
key: 'id'
}
} }
}, { }, {
tableName: 'image_image_visibility', tableName: 'image_image_visibility',

View File

@@ -0,0 +1,24 @@
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;

View File

@@ -7,19 +7,11 @@ import { encrypt, decrypt } from '../../utils/encryption.js';
const UserParam = sequelize.define('user_param', { const UserParam = sequelize.define('user_param', {
userId: { userId: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: false, allowNull: false
references: {
model: User,
key: 'id',
},
}, },
paramTypeId: { paramTypeId: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: false, allowNull: false
references: {
model: UserParamType,
key: 'id',
},
}, },
value: { value: {
type: DataTypes.STRING, type: DataTypes.STRING,

View File

@@ -6,19 +6,11 @@ import UserRightType from '../type/user_right.js';
const UserRight = sequelize.define('user_right', { const UserRight = sequelize.define('user_right', {
userId: { userId: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: false, allowNull: false
references: {
model: User,
key: 'id'
}
}, },
rightTypeId: { rightTypeId: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: false, allowNull: false
references: {
model: UserRightType,
key: 'id'
}
}}, { }}, {
tableName: 'user_right', tableName: 'user_right',
schema: 'community', schema: 'community',

View File

@@ -0,0 +1,75 @@
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;

View File

@@ -0,0 +1,37 @@
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;

View File

@@ -0,0 +1,93 @@
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'
},
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;

View File

@@ -0,0 +1,56 @@
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;

View File

@@ -0,0 +1,69 @@
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;

View File

@@ -0,0 +1,57 @@
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;

View File

@@ -0,0 +1,36 @@
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;

View File

@@ -34,6 +34,18 @@ FalukantCharacter.init(
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: false, allowNull: false,
defaultValue: 1} defaultValue: 1}
,
reputation: {
type: DataTypes.INTEGER,
allowNull: false,
// Initialisierung: zufällig 20..80 (Prozent)
// DB-seitig per DEFAULT umgesetzt, damit es auch ohne App-Logic gilt.
defaultValue: sequelize.literal('(floor(random()*61)+20)'),
validate: {
min: 0,
max: 100
}
}
}, },
{ {
sequelize, sequelize,

View File

@@ -0,0 +1,47 @@
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;

View File

@@ -0,0 +1,38 @@
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;

View File

@@ -29,6 +29,10 @@ Director.init({
type: DataTypes.BOOLEAN, type: DataTypes.BOOLEAN,
allowNull: false, allowNull: false,
defaultValue: true}, defaultValue: true},
mayRepairVehicles: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: true},
lastSalaryPayout: { lastSalaryPayout: {
type: DataTypes.DATE, type: DataTypes.DATE,
allowNull: false, allowNull: false,

View File

@@ -1,5 +1,6 @@
import { Model, DataTypes } from 'sequelize'; import { Model, DataTypes } from 'sequelize';
import { sequelize } from '../../../utils/sequelize.js'; import { sequelize } from '../../../utils/sequelize.js';
import WeatherType from '../type/weather.js';
class Production extends Model { } class Production extends Model { }
@@ -13,10 +14,20 @@ Production.init({
quantity: { quantity: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: false}, allowNull: false},
weatherTypeId: {
type: DataTypes.INTEGER,
allowNull: true,
comment: 'Wetter zum Zeitpunkt der Produktionserstellung'
},
startTimestamp: { startTimestamp: {
type: DataTypes.DATE, type: DataTypes.DATE,
allowNull: false, allowNull: false,
defaultValue: sequelize.literal('CURRENT_TIMESTAMP')} defaultValue: sequelize.literal('CURRENT_TIMESTAMP')},
sleep: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
comment: 'Produktion ist zurückgestellt'}
}, { }, {
sequelize, sequelize,
modelName: 'Production', modelName: 'Production',

View File

@@ -30,6 +30,13 @@ RegionData.init({
allowNull: true, allowNull: true,
defaultValue: {} defaultValue: {}
} }
,
taxPercent: {
type: DataTypes.DECIMAL,
allowNull: false,
defaultValue: 7,
field: 'tax_percent'
}
}, { }, {
sequelize, sequelize,
modelName: 'RegionData', modelName: 'RegionData',

View File

@@ -0,0 +1,41 @@
import { Model, DataTypes } from 'sequelize';
import { sequelize } from '../../../utils/sequelize.js';
import RegionData from './region.js';
class RegionDistance extends Model {}
RegionDistance.init(
{
sourceRegionId: {
type: DataTypes.INTEGER,
allowNull: false
},
targetRegionId: {
type: DataTypes.INTEGER,
allowNull: false
},
transportMode: {
// e.g. 'land', 'water', 'air' should match VehicleType.transportMode
type: DataTypes.STRING,
allowNull: false,
},
distance: {
// distance between regions (e.g. in abstract units, used for travel time etc.)
type: DataTypes.DOUBLE,
allowNull: false,
},
},
{
sequelize,
modelName: 'RegionDistance',
tableName: 'region_distance',
schema: 'falukant_data',
timestamps: false,
underscored: true,
}
);
export default RegionDistance;

View File

@@ -8,18 +8,10 @@ Relationship.init(
{ {
character1Id: { character1Id: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: false, allowNull: false},
references: {
model: FalukantCharacter,
key: 'id'},
onDelete: 'CASCADE'},
character2Id: { character2Id: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: false, allowNull: false},
references: {
model: FalukantCharacter,
key: 'id'},
onDelete: 'CASCADE'},
relationshipTypeId: { relationshipTypeId: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: false, allowNull: false,

View File

@@ -14,7 +14,13 @@ FalukantStock.init({
allowNull: false}, allowNull: false},
quantity: { quantity: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: false}}, { allowNull: false},
productQuality: {
type: DataTypes.INTEGER,
allowNull: true,
comment: 'Quality of the stored product (0-100)'
}
}, {
sequelize, sequelize,
modelName: 'StockData', modelName: 'StockData',
tableName: 'stock', tableName: 'stock',

View File

@@ -0,0 +1,41 @@
import { Model, DataTypes } from 'sequelize';
import { sequelize } from '../../../utils/sequelize.js';
class Transport extends Model {}
Transport.init(
{
sourceRegionId: {
type: DataTypes.INTEGER,
allowNull: false,
},
targetRegionId: {
type: DataTypes.INTEGER,
allowNull: false,
},
productId: {
type: DataTypes.INTEGER,
allowNull: true, // Nullable für leere Transporte (nur Fahrzeuge bewegen)
},
size: {
type: DataTypes.INTEGER,
allowNull: true, // Nullable für leere Transporte (nur Fahrzeuge bewegen)
},
vehicleId: {
type: DataTypes.INTEGER,
allowNull: false,
},
},
{
sequelize,
modelName: 'Transport',
tableName: 'transport',
schema: 'falukant_data',
timestamps: true,
underscored: true,
}
);
export default Transport;

View File

@@ -8,13 +8,6 @@ FalukantUser.init({
userId: { userId: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: false, allowNull: false,
references: {
model: {
tableName: 'user',
schema: 'community'
},
key: 'id'
},
unique: true}, unique: true},
money: { money: {
type: DataTypes.DECIMAL(10, 2), type: DataTypes.DECIMAL(10, 2),
@@ -38,12 +31,11 @@ FalukantUser.init({
defaultValue: 1}, defaultValue: 1},
mainBranchRegionId: { mainBranchRegionId: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: true, allowNull: true
references: { },
model: RegionData, lastNobilityAdvanceAt: {
key: 'id', type: DataTypes.DATE,
schema: 'falukant_data' allowNull: true
}
} }
}, { }, {
sequelize, sequelize,

View File

@@ -26,13 +26,11 @@ UserHouse.init({
}, },
houseTypeId: { houseTypeId: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: false, allowNull: false
defaultValue: 1
}, },
userId: { userId: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: false, allowNull: false
defaultValue: 1
} }
}, { }, {
sequelize, sequelize,

View File

@@ -0,0 +1,45 @@
import { Model, DataTypes } from 'sequelize';
import { sequelize } from '../../../utils/sequelize.js';
class Vehicle extends Model {}
Vehicle.init(
{
vehicleTypeId: {
type: DataTypes.INTEGER,
allowNull: false,
},
falukantUserId: {
type: DataTypes.INTEGER,
allowNull: false,
},
regionId: {
type: DataTypes.INTEGER,
allowNull: false,
},
condition: {
// current condition of the vehicle (0100)
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 100,
},
availableFrom: {
// timestamp when the vehicle becomes available for use
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
},
},
{
sequelize,
modelName: 'Vehicle',
tableName: 'vehicle',
schema: 'falukant_data',
timestamps: true,
underscored: true,
}
);
export default Vehicle;

View File

@@ -0,0 +1,30 @@
import { Model, DataTypes } from 'sequelize';
import { sequelize } from '../../../utils/sequelize.js';
import RegionData from './region.js';
import WeatherType from '../type/weather.js';
class Weather extends Model {}
Weather.init(
{
regionId: {
type: DataTypes.INTEGER,
primaryKey: true,
allowNull: false
},
weatherTypeId: {
type: DataTypes.INTEGER,
allowNull: false
}
},
{
sequelize,
modelName: 'Weather',
tableName: 'weather',
schema: 'falukant_data',
timestamps: false,
underscored: true}
);
export default Weather;

View File

@@ -1,7 +1,12 @@
import { Model, DataTypes } from 'sequelize'; import { Model, DataTypes } from 'sequelize';
import { sequelize } from '../../../utils/sequelize.js'; import { sequelize } from '../../../utils/sequelize.js';
class Notification extends Model { } class Notification extends Model {
// Getter für characterName - wird nicht synchronisiert, da es kein Datenbankfeld ist
get characterName() {
return this.getDataValue('character_name') || null;
}
}
Notification.init({ Notification.init({
userId: { userId: {
@@ -10,6 +15,11 @@ Notification.init({
tr: { tr: {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: false}, allowNull: false},
character_name: {
type: DataTypes.STRING,
allowNull: true,
field: 'character_name'
},
shown: { shown: {
type: DataTypes.BOOLEAN, type: DataTypes.BOOLEAN,
allowNull: false, allowNull: false,

View File

@@ -0,0 +1,44 @@
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;

View File

@@ -0,0 +1,49 @@
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;

View File

@@ -0,0 +1,59 @@
import { Model, DataTypes } from 'sequelize';
import { sequelize } from '../../../utils/sequelize.js';
class ReputationActionLog extends Model {}
ReputationActionLog.init(
{
falukantUserId: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'falukant_user_id',
},
actionTypeId: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'action_type_id',
},
cost: {
type: DataTypes.INTEGER,
allowNull: false,
},
baseGain: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'base_gain',
},
gain: {
type: DataTypes.INTEGER,
allowNull: false,
},
timesUsedBefore: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'times_used_before',
},
actionTimestamp: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: sequelize.literal('CURRENT_TIMESTAMP'),
field: 'action_timestamp',
},
},
{
sequelize,
modelName: 'ReputationActionLog',
tableName: 'reputation_action',
schema: 'falukant_log',
timestamps: false,
underscored: true,
indexes: [
{ fields: ['falukant_user_id', 'action_type_id'] },
{ fields: ['action_timestamp'] },
],
}
);
export default ReputationActionLog;

View File

@@ -0,0 +1,35 @@
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;

View File

@@ -10,14 +10,14 @@ PromotionalGiftCharacterTrait.init(
giftId: { giftId: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
field: 'gift_id', field: 'gift_id',
references: { model: PromotionalGift, key: 'id' }, allowNull: false,
allowNull: false primaryKey: true
}, },
traitId: { traitId: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
field: 'trait_id', field: 'trait_id',
references: { model: CharacterTrait, key: 'id' }, allowNull: false,
allowNull: false primaryKey: true
}, },
suitability: { suitability: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,

View File

@@ -10,20 +10,14 @@ PromotionalGiftMood.init(
giftId: { giftId: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
field: 'gift_id', field: 'gift_id',
references: { allowNull: false,
model: PromotionalGift, primaryKey: true
key: 'id'
},
allowNull: false
}, },
moodId: { moodId: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
field: 'mood_id', field: 'mood_id',
references: { allowNull: false,
model: Mood, primaryKey: true
key: 'id'
},
allowNull: false
}, },
suitability: { suitability: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,

View File

@@ -0,0 +1,38 @@
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;

View File

@@ -15,7 +15,8 @@ ProductType.init({
allowNull: false}, allowNull: false},
sellCost: { sellCost: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: false} allowNull: false
}
}, { }, {
sequelize, sequelize,
modelName: 'ProductType', modelName: 'ProductType',

View File

@@ -0,0 +1,41 @@
import { Model, DataTypes } from 'sequelize';
import { sequelize } from '../../../utils/sequelize.js';
import ProductType from './product.js';
import WeatherType from './weather.js';
class ProductWeatherEffect extends Model {}
ProductWeatherEffect.init(
{
productId: {
type: DataTypes.INTEGER,
allowNull: false
},
weatherTypeId: {
type: DataTypes.INTEGER,
allowNull: false
},
qualityEffect: {
type: DataTypes.INTEGER,
allowNull: false,
comment: 'Effekt auf Qualität: -2 (sehr negativ), -1 (negativ), 0 (neutral), 1 (positiv), 2 (sehr positiv)'
}
},
{
sequelize,
modelName: 'ProductWeatherEffect',
tableName: 'product_weather_effect',
schema: 'falukant_type',
timestamps: false,
underscored: true,
indexes: [
{
unique: true,
fields: ['product_id', 'weather_type_id']
}
]
}
);
export default ProductWeatherEffect;

View File

@@ -9,11 +9,7 @@ RegionType.init({
allowNull: false}, allowNull: false},
parentId: { parentId: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: true, allowNull: true
references: {
model: 'region',
key: 'id',
schema: 'falukant_type'}
} }
}, { }, {
sequelize, sequelize,

Some files were not shown because too many files have changed in this diff Show More