248 Commits

Author SHA1 Message Date
Torsten Schulz (local)
b59526b20d feat(Localization, User Management): add missingUserId translations and enhance user ID handling
All checks were successful
Deploy to production / deploy (push) Successful in 2m2s
- Added translations for the "missingUserId" key in multiple languages to improve user feedback when user ID is not available.
- Updated the user ID handling in the store and WorkerSchedulesView component to ensure proper user identification when sending data to the daemon.
- Enhanced error handling to notify users when the user ID is missing, improving overall user experience.
2026-05-08 11:09:34 +02:00
Torsten Schulz (local)
008cd7ae86 feat(Navigation, UserRights, Localization): add worker schedules feature and enhance access control
All checks were successful
Deploy to production / deploy (push) Successful in 1m52s
- Updated navigation structure to include a new section for worker schedules, accessible to specific user roles.
- Introduced a new user right type for 'worker_schedule_read' to manage access permissions effectively.
- Added localization entries for worker schedules in multiple languages, ensuring consistent user experience across the application.
- Created a new route and component for managing worker schedules in the admin panel.
2026-05-08 08:54:17 +02:00
Torsten Schulz (local)
0f7220d0b1 feat(FalukantService): update election filtering to include future dates and enhance eligibility checks
All checks were successful
Deploy to production / deploy (push) Successful in 1m55s
- Modified the election query to filter for upcoming election dates instead of past ones, ensuring only future positions are considered.
- Added checks for prerequisites in election eligibility, allowing positions without prerequisites to be selectable, improving user experience in the application.
2026-05-08 08:30:03 +02:00
Torsten Schulz (local)
0e572f8cbe feat(FalukantService, MessagesDialog): enhance character data handling in notifications
All checks were successful
Deploy to production / deploy (push) Successful in 2m3s
- Added mappings for character titles and genders in the FalukantService to enrich notifications with additional character information.
- Updated the MessagesDialog component to utilize the new title and gender data, improving the display of director names based on their titles and genders.
- Ensured that character names are displayed correctly, enhancing the overall user experience in notifications.
2026-05-08 08:25:36 +02:00
Torsten Schulz (local)
cc89fd4bef feat(VocabPracticeDialog, VocabCourseView): implement event dispatch for hard vocabulary changes
All checks were successful
Deploy to production / deploy (push) Successful in 1m55s
- Added event dispatching for 'yourpart:hardvocab:changed' in both VocabPracticeDialog and VocabCourseView components to notify changes in hard vocabulary items.
- Implemented event handling in VocabCourseView to refresh the hard vocabulary list when the event is triggered, ensuring UI consistency across components.
- Included error handling for environments that do not support CustomEvent, enhancing robustness.
2026-05-07 13:52:49 +02:00
Torsten Schulz (local)
42d0652e48 feat(Director): add autoAdjustIncome feature and enhance director data handling
All checks were successful
Deploy to production / deploy (push) Successful in 1m54s
- Introduced a new boolean field `autoAdjustIncome` in the Director model to manage income adjustments automatically.
- Updated the FalukantService to include `autoAdjustIncome` in director data responses and settings management.
- Enhanced the DirectorInfo component to allow users to toggle the `autoAdjustIncome` setting.
- Updated internationalization files to include translations for the new feature across multiple languages.
2026-05-07 13:29:09 +02:00
Torsten Schulz (local)
ddd8ca49d8 feat(FamilyView): enhance character display and avatar handling
All checks were successful
Deploy to production / deploy (push) Successful in 1m54s
- Refactored character display for spouses, children, and lovers to improve UI consistency and visual appeal.
- Introduced a new method for calculating character avatar styles based on age and gender, enhancing the representation of characters.
- Updated the layout to include compact character media for children and lovers, improving space utilization and readability.
2026-05-07 11:15:23 +02:00
Torsten Schulz (local)
8aeefccc3b feat(FalukantService, FamilyView): enhance marriage satisfaction handling
All checks were successful
Deploy to production / deploy (push) Successful in 1m56s
- Added logic to initialize marriage satisfaction to 100 for newly married couples in the FalukantService.
- Updated the FamilyView component to conditionally display marriage satisfaction only for married relationships, improving clarity in the UI.
- Ensured default values are used when marriage satisfaction is not explicitly set, enhancing user experience.
2026-05-07 09:40:58 +02:00
Torsten Schulz (local)
2c453a4a6b feat(VocabService): enhance logging and error handling in lesson assistant message flow
All checks were successful
Deploy to production / deploy (push) Successful in 2m18s
- Introduced detailed logging throughout the sendLessonAssistantMessage method to track request lifecycle, including start, abort conditions, and response handling.
- Improved error handling for various scenarios such as disabled assistant, unconfigured settings, empty messages, and fetch failures, providing clearer feedback to users.
- Added logging for response parsing and upstream errors to facilitate debugging and improve overall service reliability.
2026-05-07 08:58:22 +02:00
Torsten Schulz (local)
cfab56f63d feat(VocabPracticeDialog): improve SRS handling for incorrect answers
All checks were successful
Deploy to production / deploy (push) Successful in 2m8s
- Introduced logic to reinsert incorrectly answered items into the SRS queue, allowing them to be reviewed again before the session ends.
- Added a constant for the reinsert offset to control the position of requeued items.
- Updated session management to ensure incorrect answers are not marked as completed, enhancing the learning experience.
2026-05-07 08:46:33 +02:00
Torsten Schulz (local)
ab3e8d14e5 feat(AppHeader): improve ad display logic and responsiveness
All checks were successful
Deploy to production / deploy (push) Successful in 2m58s
- Enhanced the AppHeader component to support separate ad slots for mobile and desktop views, improving ad display based on viewport width.
- Introduced a responsive design that adjusts ad dimensions and formats for better visibility across different screen sizes.
- Updated methods to dynamically determine the active ad slot and handle viewport resizing events for optimal ad rendering.
2026-05-06 15:53:58 +02:00
Torsten Schulz (local)
5993f79e7a feat(VocabService, AppHeader): enhance timeout configuration and update ad display settings
All checks were successful
Deploy to production / deploy (push) Successful in 2m21s
- Updated the VocabService to allow configurable timeout for responses, defaulting to 300 seconds if not set or invalid.
- Modified AppHeader component to change ad format to horizontal and adjusted responsive settings for better display consistency.
- Ensured proper height settings for ad elements to maintain layout integrity across different screen sizes.
2026-05-06 15:50:14 +02:00
Torsten Schulz (local)
b1365dccbb feat(MoneyHistoryView): load branch names for enhanced activity display
All checks were successful
Deploy to production / deploy (push) Successful in 2m12s
- Added a new method to load branch names from the API and map them by ID for better display in money history activities.
- Updated the component's data structure to include a mapping of branch names, improving the clarity of tax-related activity translations.
2026-05-06 13:52:44 +02:00
Torsten Schulz (local)
511146da74 feat(FalukantService): add numeric value conversion for money checks
All checks were successful
Deploy to production / deploy (push) Successful in 2m12s
- Introduced a new private method `_toNumericValue` to handle conversion of various input types to numeric values, ensuring proper validation of user money and requirement values.
- Updated `checkMoneyRequirement` method to utilize the new conversion logic, improving accuracy in money requirement checks by handling edge cases for non-finite values.
2026-05-05 08:03:05 +02:00
Torsten Schulz (local)
3365f1dd2a feat(MoneyHistoryView): enhance activity translation for tax and reputation actions
All checks were successful
Deploy to production / deploy (push) Successful in 2m7s
- Added translation logic for activities related to tax from sales branches and reputation actions in the MoneyHistoryView component.
- Updated internationalization files to include new strings for these activities in multiple languages.
2026-04-30 15:41:00 +02:00
Torsten Schulz (local)
c6ffdd10f7 feat(update-frontend): enhance environment file handling and add Google AdSense script
All checks were successful
Deploy to production / deploy (push) Successful in 2m3s
- Updated the script to prioritize the root environment file for builds, falling back to the frontend environment file if necessary.
- Added a Google AdSense script to the frontend HTML for improved monetization capabilities.
2026-04-27 16:36:38 +02:00
Torsten Schulz (local)
530855e26e feat(Moderation): enhance moderation reporting and user feedback
All checks were successful
Deploy to production / deploy (push) Successful in 1m55s
- Added user blocking checks in authentication and reporting processes, returning appropriate error responses.
- Expanded moderation report functionality to include new target types and optional fields for reports.
- Implemented a new API endpoint to retrieve the count of open moderation reports.
- Enhanced frontend components to allow users to report profiles, images, and guestbook entries, with corresponding UI updates.
- Updated internationalization files to include new strings for reporting features in both German and English.
2026-04-27 15:57:02 +02:00
Torsten Schulz (local)
e94ae4350d feat(RandomChatDialog): enhance chat reporting functionality
All checks were successful
Deploy to production / deploy (push) Successful in 2m1s
- Added logic to enable the reporting button based on chat state and message presence.
- Introduced a computed property to determine if the chat can be reported.
- Updated the reporting method to accommodate the last partner in case the current partner is unavailable.
- Improved handling of partner details in the reporting payload for better accuracy.
2026-04-27 15:23:48 +02:00
Torsten Schulz (local)
ff68fb72c4 feat(Chat): implement chat incident reporting feature
All checks were successful
Deploy to production / deploy (push) Successful in 1m57s
- Added reportChatIncident method in ChatController to handle reporting of chat incidents.
- Introduced a new API route for reporting incidents in chatRouter.
- Implemented chatService methods to ensure the chat report table is created and to handle incident data storage.
- Enhanced frontend components to allow users to report incidents in both multi and random chat dialogs.
- Updated internationalization files to include new strings for reporting functionality in multiple languages.
2026-04-27 15:00:52 +02:00
Torsten Schulz (local)
90e1c0496a feat(ModerationReports): enhance moderation reports functionality and UI
All checks were successful
Deploy to production / deploy (push) Successful in 2m3s
- Added topic and forum IDs to moderation report queries for better context.
- Updated admin interface to include links to open reported forum messages.
- Implemented methods to handle opening target messages directly from the moderation reports view.
- Enhanced internationalization support for new UI elements in both German and English.
- Improved scrolling behavior to focus on specific messages when navigating to them.
2026-04-27 14:55:23 +02:00
Torsten Schulz (local)
a02fe1f008 feat(Moderation): implement moderation reports feature
All checks were successful
Deploy to production / deploy (push) Successful in 2m1s
- Added moderationRouter to handle moderation-related API routes.
- Introduced new methods in AdminController for fetching all regions, region types, and creating regions.
- Enhanced adminRouter with routes for moderation reports and status updates.
- Updated navigationController to include moderation reports in the admin menu.
- Implemented frontend components for reporting messages in the forum and managing moderation reports.
- Added internationalization support for moderation-related texts in multiple languages.
2026-04-27 14:52:19 +02:00
Torsten Schulz (local)
7fc9b55b59 feat(FalukantService, CreateBranchDialog): implement dynamic branch cost calculation
All checks were successful
Deploy to production / deploy (push) Successful in 2m8s
- Added new constants and methods in FalukantService to calculate branch action costs based on owned branch types, enhancing the cost management logic.
- Updated the CreateBranchDialog to utilize the new cost calculation, ensuring accurate display of branch creation costs based on user-owned branches.
- Improved the handling of branch type costs, allowing for a more flexible and responsive user experience when creating and upgrading branches.
2026-04-27 14:35:45 +02:00
Torsten Schulz (local)
d854200708 feat(VocabPracticeDialog, VocabCourseView): enhance hard vocabulary management and UI
All checks were successful
Deploy to production / deploy (push) Successful in 2m6s
- Refactored methods in VocabPracticeDialog to improve the handling of hard vocabulary items, including the addition of `findMatchingHardKey` and `removeHardEntriesForItem` for better management of hard vocabulary entries.
- Updated the VocabCourseView to display a new section for hard vocabulary items, allowing users to view and remove difficult words easily.
- Enhanced the UI with new styles for the hard vocabulary list, improving user engagement and accessibility to challenging vocabulary practice.
2026-04-26 23:32:18 +02:00
Torsten Schulz (local)
7fd8e4dda8 fix(VocabPracticeDialog): adjust dialog dimensions for improved layout
All checks were successful
Deploy to production / deploy (push) Successful in 1m54s
- Increased the width from 55em to 64em and height from 32em to 40em to enhance the visual layout of the VocabPracticeDialog component, providing a better user experience.
2026-04-23 14:25:24 +02:00
Torsten Schulz (local)
0e39ca9a0f feat(VocabPracticeDialog): improve vocabulary item normalization and expansion
All checks were successful
Deploy to production / deploy (push) Successful in 1m48s
- Enhanced the normalization process for vocabulary items to better accommodate multiple valid translations, improving the overall accuracy of vocabulary practice.
- Updated methods for expanding vocabulary item alternatives, ensuring a more flexible and effective learning experience for users.
2026-04-23 13:47:21 +02:00
Torsten Schulz (local)
79fe05c630 feat(VocabPracticeDialog): enhance vocabulary item expansion logic
All checks were successful
Deploy to production / deploy (push) Successful in 1m48s
- Added methods to split phrase alternatives and expand pool item alternatives, allowing for better handling of multiple valid translations and maintaining alignment between learning and reference phrases.
- Updated the normalization process to incorporate these new methods, improving the accuracy and flexibility of vocabulary practice sessions.
2026-04-23 13:44:31 +02:00
Torsten Schulz (local)
e3c024d5af feat(VocabPracticeDialog): enhance answer variant expansion logic
All checks were successful
Deploy to production / deploy (push) Successful in 1m51s
- Renamed `expandAnswerVariants` to `expandSingleAnswerVariants` for clarity and added a new `expandAnswerVariants` method to handle full-answer alternatives like "A / B".
- Improved the logic to ensure that phrase alternatives are expanded correctly, enhancing the accuracy of answer variant generation during vocabulary practice.
2026-04-23 13:40:08 +02:00
Torsten Schulz (local)
783dd175e8 feat(VocabCourseView): add hard vocabulary practice button and refresh logic
All checks were successful
Deploy to production / deploy (push) Successful in 1m57s
- Introduced a button to start hard vocabulary practice when applicable, enhancing user engagement with challenging vocabulary.
- Implemented methods to manage and refresh the hard vocabulary list from local storage, ensuring users have access to their difficult words.
- Added event listeners to refresh the hard vocabulary list on window focus, improving the overall user experience.
2026-04-23 13:10:05 +02:00
Torsten Schulz (local)
cab5428d0b feat(VocabLessonView): enhance vocab trainer visibility for cross-chapter hard vocabulary
All checks were successful
Deploy to production / deploy (push) Successful in 1m58s
- Updated the VocabLessonView to display the vocab trainer section when there are cross-chapter hard vocabulary items, improving user engagement and accessibility to training options.
- Adjusted the button rendering logic to ensure that the hard vocabulary trainer is available when applicable, enhancing the overall learning experience.
2026-04-23 10:04:46 +02:00
Torsten Schulz (local)
478a7ffc96 fix(VocabPracticeDialog): adjust success tracking for hard-phase drills
All checks were successful
Deploy to production / deploy (push) Successful in 1m56s
- Updated the success tracking logic to prevent inflating normal success counters during hard-phase drills, ensuring accurate representation of user progress.
- Introduced a condition to separate tracking for hard-phase activities, enhancing the integrity of vocabulary rotation and user experience.
2026-04-22 11:55:40 +02:00
Torsten Schulz (local)
677e4c674e refactor(VocabPracticeDialog): replace hard-coded consecutive correct threshold with constant
All checks were successful
Deploy to production / deploy (push) Successful in 1m56s
- Introduced a constant `HARD_REQUIRED_CONSECUTIVE_CORRECT` to define the required number of consecutive correct answers for hard vocabulary items, improving code maintainability and readability.
- Updated relevant methods to utilize the new constant, ensuring consistent behavior across the component.
2026-04-22 11:51:49 +02:00
Torsten Schulz (local)
d3aad6e7ef feat(localization): add difficult vocabulary marking features in multiple languages
All checks were successful
Deploy to production / deploy (push) Successful in 1m58s
- Introduced new localization strings for marking vocabulary as difficult, including options to save and remove difficult marks.
- Enhanced user experience by providing feedback on the status of difficult vocabulary in Cebuano, Spanish, and French.
- Updated existing localization files to ensure consistency across languages, supporting the recent vocabulary management features.
2026-04-22 11:12:26 +02:00
Torsten Schulz (local)
44b40d5a46 feat(VocabPracticeDialog, localization): add hard vocabulary marking feature
All checks were successful
Deploy to production / deploy (push) Successful in 1m53s
- Introduced functionality to mark vocabulary items as difficult, allowing users to track and manage challenging words during practice sessions.
- Added buttons for marking and unmarking current vocabulary as hard, enhancing user engagement and learning effectiveness.
- Updated the UI to display the count of hard-marked vocabulary items, providing users with better insights into their learning progress.
- Enhanced localization files for German and English to support new vocabulary marking features, ensuring a consistent user experience across languages.
2026-04-21 15:57:21 +02:00
Torsten Schulz (local)
4cc2aace6b feat(DayProduction, FalukantService, VocabLessonView): enhance vocabulary training and production tracking
All checks were successful
Deploy to production / deploy (push) Successful in 1m51s
- Added a new `completionCount` field to the DayProduction model to track the number of completed productions.
- Updated the FalukantService to aggregate completed productions using the new `completionCount` field, improving accuracy in production statistics.
- Introduced new vocabulary training features in VocabLessonView, including options to mark vocabulary as difficult and track remaining hard vocabulary, enhancing user engagement and learning effectiveness.
- Updated localization files for German and English to support new vocabulary training features, ensuring a consistent user experience across languages.
2026-04-21 15:44:44 +02:00
Torsten Schulz (local)
27d42c0a3a fix(AdminService, AdminController): enhance error handling and character deletion logic
All checks were successful
Deploy to production / deploy (push) Successful in 1m44s
- Updated error handling in AdminController to include 'targetnotdead' status for improved response accuracy.
- Modified character deletion logic in AdminService to ensure correct target character is identified, especially when dealing with living characters, enhancing data integrity during cleanup operations.
- Adjusted attributes fetched for characters to include 'health', facilitating better decision-making in deletion processes.
2026-04-20 16:27:08 +02:00
Torsten Schulz (local)
7417736daf refactor(AdminService, EditUserView): streamline character data deletion and enhance confirmation dialog
All checks were successful
Deploy to production / deploy (push) Successful in 1m46s
- Introduced a reusable `deleteIfTableExists` function in AdminService to simplify the deletion of character-related data across multiple tables, improving code maintainability.
- Updated the deletion logic to check for table existence before executing delete queries, enhancing robustness.
- Enhanced the EditUserView by integrating a `ChooseDialog` for user confirmation before proceeding with character death cleanup, improving user experience and interaction clarity.
2026-04-20 16:16:48 +02:00
Torsten Schulz (local)
267711fca6 feat(AdminController, AdminService, AdminRouter, localization): add character death cleanup feature
All checks were successful
Deploy to production / deploy (push) Successful in 1m52s
- Implemented `adminCleanupCharacterDeathArtifacts` method in AdminService to remove stale character-bound data after death/inheritance, including knowledge, debtors prism, and political offices.
- Added corresponding route in AdminRouter for triggering the cleanup process via an API endpoint.
- Enhanced AdminController to handle requests for the new cleanup feature, ensuring proper error handling and response formatting.
- Updated frontend components to include a user interface for initiating the cleanup, with localization support in both English and German for improved user experience.
2026-04-20 15:43:44 +02:00
Torsten Schulz (local)
8ce15441bf feat(VocabPracticeDialog): implement immediate retry feature for incorrect answers
All checks were successful
Deploy to production / deploy (push) Successful in 1m47s
- Added a `pendingRetry` property to manage immediate retries after incorrect answers, enhancing user experience during vocabulary practice.
- Updated logic to handle retries, allowing users to quickly attempt the last question again with the correct direction.
- Ensured that the retry state is reset appropriately after correct answers or when skipping questions, maintaining clarity in the training flow.
2026-04-20 10:02:32 +02:00
Torsten Schulz (local)
1500f01875 feat(VocabPracticeDialog, localization): implement last wrong answer review feature
All checks were successful
Deploy to production / deploy (push) Successful in 1m49s
- Added a new section in VocabPracticeDialog to display the last incorrect answer, including the asked vocabulary, user answer, and correct solutions.
- Updated localization strings in Cebuano, German, English, Spanish, and French to support the new review feature, enhancing user experience across multiple languages.
- Improved UI layout for the review section to provide clearer feedback during vocabulary training sessions.
2026-04-20 09:52:26 +02:00
Torsten Schulz (local)
5f13583e41 feat(VocabLessonView, localization): add vocabulary review feature and update translations
All checks were successful
Deploy to production / deploy (push) Successful in 1m55s
- Implemented a new vocabulary review section in VocabLessonView to display the last incorrect answer, including the asked vocabulary, user answer, and correct answer.
- Added localization strings for the new review feature in Cebuano, German, English, Spanish, and French, enhancing user experience across multiple languages.
- Updated the UI to visually differentiate the review section, improving clarity and usability during vocabulary training sessions.
2026-04-20 09:39:48 +02:00
Torsten Schulz (local)
dee4991be7 feat(VocabLessonView): implement vocab trainer continue timer management
All checks were successful
Deploy to production / deploy (push) Successful in 2m2s
- Added a new timer management feature to the vocab trainer, allowing for better control over the continuation of questions after user answers.
- Introduced `clearVocabTrainerContinueTimer` method to prevent multiple timers from running simultaneously, enhancing the stability of the training session.
- Updated various methods to utilize the new timer management, ensuring a smoother user experience during vocabulary training.
2026-04-20 09:26:02 +02:00
Torsten Schulz (local)
d7fa5f925e feat(VocabService, localization, VocabLessonView): extend assistant wait feedback and timeout duration
All checks were successful
Deploy to production / deploy (push) Successful in 2m2s
- Increased the timeout duration for the language assistant from 30 seconds to 120 seconds to accommodate longer processing times.
- Added new localization strings across multiple languages (Cebuano, German, English, Spanish, French) to provide user feedback during the assistant's wait period, enhancing clarity and user experience.
- Implemented a visual waiting indicator in VocabLessonView to inform users that the assistant is still processing their request, improving overall interaction quality.
2026-04-20 08:58:51 +02:00
Torsten Schulz (local)
e28ed7bdb5 feat(VocabService, VocabPracticeDialog, VocabLessonView): enhance vocabulary training logic and UI feedback
All checks were successful
Deploy to production / deploy (push) Successful in 2m17s
- Introduced methods for improved text analysis and validation in VocabService, including `_wordCount` and `_looksLikeFragmentMismatch`, to better assess learning and reference pairs.
- Updated VocabPracticeDialog to display submitted answers and correct solutions, enhancing user feedback during practice sessions.
- Enhanced VocabLessonView to ensure only trainable vocabulary pairs are processed, improving the quality of vocabulary training.
- Added localization entries for new UI elements in both English and German, ensuring clarity in user interactions.
2026-04-20 08:48:39 +02:00
Torsten Schulz (local)
553f132184 feat(VocabPracticeDialog): add answer variant expansion for improved vocabulary training
All checks were successful
Deploy to production / deploy (push) Successful in 1m55s
- Introduced a new method `expandAnswerVariants` to generate multiple answer variants from a given input, enhancing the flexibility of acceptable answers.
- Improved handling of words with slashes to create diverse answer options, ensuring a richer vocabulary training experience.
- Updated the answer collection logic to utilize the new method, allowing for a broader range of valid responses during practice sessions.
2026-04-20 08:36:11 +02:00
Torsten Schulz (local)
f64c923db6 feat(VocabService, VocabPracticeDialog): enhance text analysis and SRS validation
All checks were successful
Deploy to production / deploy (push) Successful in 1m50s
- Improved text analysis methods in VocabService to better identify instructional-like texts and task-related prompts, enhancing vocabulary training quality.
- Updated VocabPracticeDialog to include a new method for checking instruction-like text, ensuring more accurate filtering of trainable pairs.
- Incremented SRS session storage version to reflect changes in session management and data handling.
- Refactored normalization and validation logic to maintain consistency across vocabulary entries and improve user experience.
2026-04-20 08:30:58 +02:00
Torsten Schulz (local)
71f2ee7c97 feat(seo, routing): add Bisaya learning and German for Bisaya courses
All checks were successful
Deploy to production / deploy (push) Successful in 2m0s
- Introduced new routes for Bisaya learning and German for Bisaya courses, enhancing the marketing section of the application.
- Updated sitemap.xml to include new course URLs for better SEO visibility.
- Added localization entries for both courses in English and German, improving content accessibility for users.
- Enhanced SEO metadata generation for the new courses, ensuring proper indexing and visibility in search engines.
- Updated VocabLandingView to feature links to the new courses, improving user navigation and engagement.
2026-04-20 08:28:00 +02:00
Torsten Schulz (local)
e6c90c219b feat(VocabService): improve SRS pair validation and introduce new text analysis methods
All checks were successful
Deploy to production / deploy (push) Successful in 2m13s
- Refactored SRS pair validation logic by introducing the `_isTrainableSrsPair` method to enhance the criteria for acceptable learning and reference pairs.
- Added `_isInstructionLikeText` method to filter out instructional texts from SRS pairs, improving the quality of vocabulary training.
- Updated various methods to utilize the new validation logic, ensuring consistent handling of vocabulary entries across the service.
- Enhanced the `_courseHasDueSrsItems` and `_extractTrainerVocabsFromLessonDidactics` methods to improve data retrieval and filtering based on the new criteria.
2026-04-20 08:21:18 +02:00
Torsten Schulz (local)
b6d749f781 feat(VocabLessonView): enhance vocab trainer logic and answer normalization
All checks were successful
Deploy to production / deploy (push) Successful in 1m51s
- Refactored vocab trainer logic to improve handling of acceptable answers, allowing for multiple translations and synonyms.
- Introduced a new method to retrieve equivalent vocabulary answers, ensuring mixed pools from different lessons provide valid alternatives.
- Enhanced answer normalization process to prevent duplicates and improve the quality of acceptable answers presented to users.
2026-04-17 17:30:03 +02:00
Torsten Schulz (local)
39dcdd7fb3 feat(FalukantService, OverviewView): enhance church office information retrieval and UI display
All checks were successful
Deploy to production / deploy (push) Successful in 1m51s
- Refactored the `getHighestChurchOfficeInfo` method to utilize a new approach for retrieving the highest church office, improving accuracy and performance.
- Updated the `OverviewView` to format and display the highest political and church office names instead of raw rank values, enhancing user experience and clarity.
- Introduced a new method `formatCertificateHighestOffice` for better localization and presentation of office titles in the UI.
2026-04-17 17:17:17 +02:00
Torsten Schulz (local)
afbea926a2 feat(FalukantService, i18n): enhance household tension handling and localization updates
All checks were successful
Deploy to production / deploy (push) Successful in 1m55s
- Added functionality to refresh and update household tension scores and reasons in the FalukantService, improving user experience by reflecting real-time household dynamics.
- Updated localization strings across multiple languages (Cebuano, German, English, Spanish, French) to include new office rank terminology and improved marriage crisis descriptions, enhancing clarity and context for users.
- Introduced a new office rank tier structure in localization files, providing better categorization and understanding of office ranks in the game.
2026-04-17 17:07:53 +02:00
Torsten Schulz (local)
1dd4d18927 fix(FalukantService): update userHouse query attributes and transaction handling
All checks were successful
Deploy to production / deploy (push) Successful in 1m52s
- Modified the userHouse query to include 'id' in the attributes, improving data retrieval.
- Enhanced transaction handling in state and userHouse updates to ensure atomic operations, preventing potential data inconsistencies.
2026-04-17 16:58:41 +02:00
Torsten Schulz (local)
d2eebf1f94 feat(VocabService, VocabCourseView): enhance SRS item retrieval and UI integration
All checks were successful
Deploy to production / deploy (push) Successful in 1m52s
- Updated VocabService to calculate total due count for spaced repetition system (SRS) items, improving data handling for user queries.
- Modified VocabCourseView to incorporate total due count in the UI, ensuring accurate display of SRS item statistics.
- Enhanced error handling and data validation for SRS due items, improving overall reliability and user experience.
2026-04-17 16:53:56 +02:00
Torsten Schulz (local)
7732498875 feat(MiniCalendarWidget): enhance localization and refactor calendar logic
All checks were successful
Deploy to production / deploy (push) Successful in 1m55s
- Updated the MiniCalendarWidget to utilize localized strings for month names, weekdays, and loading messages, improving user experience across multiple languages.
- Refactored the calendar logic to use constants for month keys and weekday order, ensuring consistency with backend configurations.
- Added new translation entries for the mini widget in Cebuano, German, English, Spanish, and French, enhancing accessibility for users.
2026-04-17 16:44:27 +02:00
Torsten Schulz (local)
14881803df feat(i18n): enhance localization for chat and minigames
All checks were successful
Deploy to production / deploy (push) Successful in 1m51s
- Updated localization strings for chat and minigames across multiple languages, including Cebuano, German, English, Spanish, and French, to improve user experience and clarity.
- Added new translations for participant counts, user selection prompts, and game over messages, ensuring consistency and better engagement in the UI.
- Enhanced existing translations for accuracy and context, particularly in the TaxiGame component and chat functionalities.
2026-04-17 16:38:39 +02:00
Torsten Schulz (local)
3232e42251 feat(VocabPracticeDialog): implement SRS session management and enhance UI feedback
All checks were successful
Deploy to production / deploy (push) Successful in 1m53s
- Added support for spaced repetition system (SRS) session management, allowing users to resume practice sessions with due items.
- Introduced new UI elements to display SRS session status, including total due, completed, and remaining items.
- Enhanced localization for SRS-related messages in multiple languages, improving user experience and clarity.
- Updated methods for saving and loading SRS session data to local storage, ensuring persistence across sessions.
2026-04-17 16:28:19 +02:00
Torsten Schulz (local)
9c121d2dc2 feat(i18n): update Cebuano localization for minigames and enhance UI text
All checks were successful
Deploy to production / deploy (push) Successful in 1m56s
- Added new localized strings for minigames in Cebuano, including loading hints, objectives, and play focus descriptions to improve user experience.
- Updated existing translations for clarity and consistency across various game elements.
- Enhanced the Match3Game component to utilize localized strings, ensuring dynamic text rendering based on user language preferences.
- Included a new entry in .gitignore for the locale audit report to maintain a clean repository.
2026-04-17 16:20:20 +02:00
Torsten Schulz (local)
71d5922409 feat(bisaya-course): refine phase 4 didactics and enhance course content generation
All checks were successful
Deploy to production / deploy (push) Successful in 5m19s
- Corrected grammatical errors and improved the phrasing in the BISAYA_PHASE4_DIDACTICS, ensuring clarity and accuracy in the learning materials.
- Updated the course content generation script to include lessons from phase 5, enhancing the overall structure and flow of the course.
- Introduced a new vocabulary course content synchronization process, improving the integration of vocabulary resources across different modules.
- Enhanced the VocabService to dynamically adjust temperature settings based on the mode, optimizing response generation for different contexts.
- Added new localized titles and vocabulary entries in multiple languages, enriching the learning experience for users.
2026-04-17 16:00:41 +02:00
Torsten Schulz (local)
5c315c477f feat(i18n): add language trainer SEO content and update home page metadata
All checks were successful
Deploy to production / deploy (push) Successful in 5m2s
- Introduced a new section for language trainers in multiple languages (Cebuano, German, English, Spanish, French) to enhance user guidance on beginner courses.
- Updated home page SEO metadata to reflect the addition of language courses, improving search visibility and user engagement.
- Enhanced the NoLoginView component to display the new language trainer information, providing users with clear insights into available resources.
2026-04-17 15:30:12 +02:00
Torsten Schulz (local)
776dea2584 feat(LanguageAssistantView): add preset options for Ollama and OpenAI
All checks were successful
Deploy to production / deploy (push) Successful in 4m31s
- Introduced a new section for language assistant presets, allowing users to quickly apply configurations for Ollama and OpenAI.
- Implemented methods to set the base URL and model for each preset, enhancing user experience with one-click setup.
- Added styling for the preset buttons and hints to improve UI clarity and usability.
2026-04-17 15:24:22 +02:00
Torsten Schulz (local)
4205639de3 feat(bisaya-course): enhance phase 4 didactics with new learning goals and speaking prompts
All checks were successful
Deploy to production / deploy (push) Successful in 1m31s
- Expanded the BISAYA_PHASE4_DIDACTICS by adding new learning goals, core patterns, speaking prompts, and practical tasks to improve language acquisition.
- Updated the course content generation scripts to incorporate the new lesson structures and ensure alignment with the latest didactic updates.
- Enhanced the logic for generating exercises based on lesson types, introducing additional situational and speaking exercises for advanced lessons.
2026-04-17 15:12:21 +02:00
Torsten Schulz (local)
4df8f97a41 feat(bisaya-course): add new 'Ort & Richtung' section with learning goals and practical tasks
All checks were successful
Deploy to production / deploy (push) Successful in 4m46s
- Introduced a new section for 'Ort & Richtung' in the Bisaya didactics, including learning goals, core patterns, grammar focus, speaking prompts, and practical tasks to enhance language learning.
- Updated the lesson didactics to include the new section, ensuring comprehensive coverage of location and direction vocabulary.
2026-04-17 14:51:09 +02:00
Torsten Schulz (local)
f93a0f8b35 feat(VocabLessonView): enhance review lesson logic and vocabulary handling
All checks were successful
Deploy to production / deploy (push) Successful in 4m28s
- Added logic to adjust vocabulary blending and switching conditions based on lesson type and didactic mode, improving the learning experience during review lessons.
- Implemented dynamic thresholds for switching between multiple choice and typing modes based on success rates and lesson context.
- Enhanced vocabulary source selection to incorporate a review share, optimizing the question pool for better engagement during typing sessions.
2026-04-17 14:37:40 +02:00
Torsten Schulz (local)
b3346d4cac fix(update-backend): adjust file permissions and handle environment variables safely
All checks were successful
Deploy to production / deploy (push) Successful in 4m25s
- Updated the permission settings for the environment file to 640, ensuring it is readable only by the owner and the deploy group.
- Modified the sequelize configuration to safely handle missing environment variables, converting them to strings or setting them to undefined to prevent runtime errors.
[force-deploy]
2026-04-17 13:53:13 +02:00
Torsten Schulz (local)
1b9d9664b2 refactor(update-backend): reorder permission settings and update migration message
Some checks failed
Deploy to production / deploy (push) Failing after 3m20s
- Moved permission setting commands to the end of the script for better clarity and execution flow.
- Changed the log message from "Running database synchronization..." to "Running database migrations..." to accurately reflect the operation being performed.
- Ensured that permission settings for backend and data directories are consistently applied after the migration process.
[force-deploy]
2026-04-17 13:45:53 +02:00
Torsten Schulz (local)
9a92940dab chore(package): update version to 3.0.0-beta-1.0.0 in package.json
Some checks failed
Deploy to production / deploy (push) Failing after 3m22s
[force-deploy]
2026-04-17 13:41:00 +02:00
Torsten Schulz (local)
5070785a50 feat(deploy): update deployment workflow and migration paths
Some checks failed
Deploy to production / deploy (push) Has been cancelled
- Modified the deployment workflow to include new migration paths for the backend, ensuring that migrations are correctly referenced in the deployment process.
- Updated the `db:migrate` script in package.json to point to the `migrations-active` directory, enhancing clarity and organization of migration files.
- Adjusted the deployment conditions to account for changes in migration file locations, improving the accuracy of change detection during deployments.
- Removed obsolete migration files to streamline the migration process and prevent confusion.
2026-04-17 13:39:38 +02:00
Torsten Schulz (local)
70c381114b feat(migration): enhance contact_message migration to check for existing columns before adding/removing
Some checks failed
Deploy to production / deploy (push) Failing after 3m14s
- Updated the migration script for the contact_message table to conditionally add or remove columns based on their existence, improving migration safety and preventing errors during deployment.
- This change ensures that the migration can be run multiple times without causing issues if the columns already exist or are missing.
[force-deploy]
2026-04-17 12:01:25 +02:00
Torsten Schulz (local)
de9f2c853d feat(deploy): add force deploy detection to deployment workflow
Some checks failed
Deploy to production / deploy (push) Failing after 3m17s
- Implemented logic to check for a `[force-deploy]` tag in the latest commit message, allowing for a forced deployment regardless of detected changes.
- Updated deployment conditions to include force deploy status, enabling more flexible deployment options based on commit messages.
- Enhanced sync step to trigger on force deploy, ensuring content updates are applied even without file changes.
2026-04-17 11:53:00 +02:00
Torsten Schulz (local)
1e9e247dec feat(update-backend): streamline dependency management and migration process
All checks were successful
Deploy to production / deploy (push) Successful in 1m14s
- Updated the npm install command to remove the production flag, allowing for the installation of all dependencies.
- Added a step to prune development dependencies after running database migrations, optimizing the deployment environment.
2026-04-17 11:42:41 +02:00
Torsten Schulz (local)
7b907ea683 feat(VocabLessonView): improve lesson reset behavior and normalize vocabulary handling
Some checks failed
Deploy to production / deploy (push) Has been cancelled
- Updated lesson reset functionality to ensure users start in the 'learn' tab after resetting progress.
- Enhanced vocabulary normalization by adding a method to strip trailing parenthetical notes, improving text consistency.
- Adjusted normalization logic to conditionally ignore trailing notes based on user input mode, enhancing user experience during vocabulary training.
2026-04-17 11:41:46 +02:00
Torsten Schulz (local)
e96c37aac5 feat(database): add migration and sync-db scripts to deployment process
Some checks failed
Deploy to production / deploy (push) Failing after 3m20s
- Introduced a new npm script for database migrations in package.json.
- Updated update-backend.sh to execute migrations and conditionally run legacy sync-db based on environment variable.
- Enhanced syncDatabase.js to skip initialization routines if APP_INIT_ON_START is not set, promoting best practices for schema changes during deployment.
2026-04-17 11:34:59 +02:00
Torsten Schulz (local)
1f10e7c519 feat(deploy): enhance deployment scripts with skip options for backend and frontend
All checks were successful
Deploy to production / deploy (push) Successful in 1m31s
- Updated `deploy-yourpart-bluegreen.sh` to pass additional arguments for skipping backend or frontend updates.
- Enhanced `update.sh` to handle `--skip-backend` and `--skip-frontend` flags, allowing for more flexible deployment based on changes detected.
- Modified deployment workflow to conditionally execute based on changes in frontend or backend files, improving deployment efficiency.
2026-04-17 11:33:02 +02:00
Torsten Schulz (local)
2461e98fb0 feat(bisaya-course): enhance script execution with direct run detection
All checks were successful
Deploy to production / deploy (push) Successful in 4m25s
- Added functionality to detect if the script is run directly, allowing for conditional execution of the main function.
- Imported `pathToFileURL` from the 'url' module to facilitate the direct run check, improving script usability and flexibility.
2026-04-17 11:23:40 +02:00
Torsten Schulz (local)
3f1b474fdd feat(deploy): add vocab course change detection and sync step
Some checks failed
Deploy to production / deploy (push) Failing after 4m12s
- Implemented a new workflow step to detect changes in vocab course files and conditionally sync content.
- Added a script to check for specific changes in the repository and trigger the sync process if necessary.
- Introduced a new npm script for syncing vocab course content in the backend package.json.
2026-04-17 11:17:59 +02:00
Torsten Schulz (local)
f8f5017436 feat(bisaya-course): enhance lesson structure and didactics integration
All checks were successful
Deploy to production / deploy (push) Successful in 2m52s
- Added an auto-incrementing primary key to the UserHouse model for improved database management.
- Integrated new didactics fragments from the Bisaya course plan, including relationship anchor didactics and lessons for enhanced curriculum depth.
- Updated lesson retrieval logic to utilize planned lesson titles, improving the accuracy of didactics resolution.
- Refactored course content generation scripts to incorporate new didactics, ensuring a comprehensive learning experience for users.
2026-04-17 11:08:12 +02:00
Torsten Schulz (local)
a022b8c174 feat(bisaya-course): add new didactics fragments for everyday conversations
All checks were successful
Deploy to production / deploy (push) Successful in 2m49s
- Introduced new didactics fragments for "Alltagsgespräche - Teil 1" and "Alltagsgespräche - Teil 2," including learning goals, core patterns, speaking prompts, and practical tasks to enhance conversational skills in Bisaya.
- Updated the lesson structure to incorporate these new fragments, improving the overall curriculum and providing learners with more comprehensive resources for everyday interactions.
2026-04-17 10:52:39 +02:00
Torsten Schulz (local)
09a10ff830 feat(bisaya-course, vocabService): expand numerical didactics and enhance lesson progress serialization
All checks were successful
Deploy to production / deploy (push) Successful in 2m54s
- Added new glosses for the numbers sixty, seventy, and eighty in the Bisaya didactics, enriching the numerical curriculum.
- Updated the _serializeLessonProgress method to include an options parameter for suppressing lesson review due notifications based on SRS item status.
- Introduced a new method to check for due SRS items, improving the handling of lesson progress and review scheduling.
2026-04-17 09:30:36 +02:00
Torsten Schulz (local)
8be215761d feat(VocabPracticeDialog, VocabCourseView): implement SRS rating feature and enhance user feedback
All checks were successful
Deploy to production / deploy (push) Successful in 2m48s
- Added SRS rating buttons in VocabPracticeDialog to allow users to rate their confidence after answering vocabulary questions.
- Updated methods to handle SRS ratings and integrated them into the review process, improving spaced repetition feedback.
- Enhanced UI with new styles for SRS rating buttons and updated translations for SRS-related terms in multiple languages.
- Modified VocabCourseView to display appropriate introductory text based on SRS due items, improving user guidance.
2026-04-17 09:27:29 +02:00
Torsten Schulz (local)
e2c1147d75 feat(vocab): implement SRS features and enhance vocabulary management
All checks were successful
Deploy to production / deploy (push) Successful in 2m49s
- Added new endpoints in vocabController for retrieving SRS due items and reviewing SRS items, improving spaced repetition support.
- Updated vocabService to handle SRS item creation and scheduling, ensuring effective tracking of vocabulary exposure.
- Enhanced vocabRouter with new routes for SRS functionalities, facilitating user interaction with spaced repetition features.
- Modified VocabPracticeDialog and VocabCourseView to integrate SRS due items, providing users with timely review opportunities.
- Updated translations and UI elements to reflect new SRS features, enhancing user experience and accessibility.
2026-04-17 09:14:30 +02:00
Torsten Schulz (local)
54a77c2e08 feat(vocabService, VocabPracticeDialog, VocabLessonView): enhance vocabulary handling and exposure tracking
All checks were successful
Deploy to production / deploy (push) Successful in 2m51s
- Updated vocabService to merge extracted vocabularies and improve handling of learning and reference pairs.
- Introduced normalization and exposure tracking in VocabPracticeDialog to ensure diverse and underexposed vocabulary practice.
- Enhanced VocabLessonView with methods to identify underexposed vocabularies and adjust selection logic for improved learning outcomes.
- Implemented new constants for minimum exposure requirements to optimize vocabulary training sessions.
2026-04-17 08:58:50 +02:00
Torsten Schulz (local)
d119869750 feat(bisaya-course): expand numerical didactics with additional glosses
All checks were successful
Deploy to production / deploy (push) Successful in 2m45s
- Added new glosses for numbers in Bisaya, enhancing the didactics for numerical lessons.
- Included translations for numbers 4 to 19, improving the comprehensiveness of the numerical curriculum.
- Updated the lesson structure to support the expanded vocabulary, ensuring a richer learning experience.
2026-04-17 08:21:02 +02:00
Torsten Schulz (local)
712370cad3 feat(bisaya-course): add didactics fragments for "Haus & Familie" lessons
All checks were successful
Deploy to production / deploy (push) Successful in 2m56s
- Introduced comprehensive didactics snippets for the "Haus & Familie" lesson, including learning goals, core patterns, grammar focus, speaking prompts, and practical tasks.
- Updated the lesson retrieval logic to incorporate these new fragments as a fallback for core patterns, enhancing the robustness of lesson content delivery.
- Modified the vocab service to utilize the new didactics fragments, ensuring a seamless integration into the existing lesson structure.
2026-04-16 22:44:57 +02:00
Torsten Schulz (local)
229bdc96bf feat(bisaya-course): update lesson retrieval and didactics processing
All checks were successful
Deploy to production / deploy (push) Successful in 2m56s
- Refactored the lesson retrieval logic to include lesson number and title in the query, improving data handling.
- Updated the didactics processing to utilize a lesson-like object instead of fetching lessons by primary key, enhancing performance.
- Improved error handling and ensured proper closure of the database connection after execution.
2026-04-16 22:36:15 +02:00
Torsten Schulz (local)
c1421db72c feat(bisaya-course): add new exercises for situational role plays and enhance lesson normalization
All checks were successful
Deploy to production / deploy (push) Successful in 3m3s
- Introduced a new section titled "Rollenspiele - echte Situationen" with multiple exercises focusing on real-life scenarios in Bisaya, including multiple-choice, gap-fill, and transformation tasks.
- Implemented a normalization function for lesson titles to improve matching accuracy and facilitate the rebuilding of placeholder lessons.
- Updated the course content generation logic to replace outdated exercises with new ones based on normalized titles, ensuring a more relevant learning experience.
2026-04-16 22:28:10 +02:00
Torsten Schulz (local)
68ac5ec281 feat(bisaya-course): add new lesson and update course creation logic
All checks were successful
Deploy to production / deploy (push) Successful in 2m51s
- Introduced a new lesson titled "Zahlen & Preise" to enhance the numerical curriculum in Bisaya.
- Updated the course creation logic to check for existing "Familien"-Bisaya courses, ensuring idempotency in course creation.
- Enhanced the lesson didactics by mapping legacy titles to current lesson keys, improving data consistency.
- Adjusted the course generation script to limit Bisaya courses to German-speaking learners only, streamlining course offerings.
2026-04-16 22:16:23 +02:00
Torsten Schulz (local)
6dce418728 feat(bisaya-course): enhance numerical lessons with expanded content
All checks were successful
Deploy to production / deploy (push) Successful in 2m51s
- Updated lessons on numbers to include detailed sections for "Zahlen 1–20," "Zahlen: Zehner," "Zahlen: Hunderter," and "Zahlen: Tausender," focusing on foundational numerical concepts in Bisaya.
- Introduced various exercise types such as multiple-choice, gap-fill, and transformation tasks to improve learner engagement and comprehension of numerical expressions.
- Enhanced didactics with clear learning goals, core patterns, grammar focus, speaking prompts, and practical tasks to support effective learning.
- Added review lessons to reinforce key concepts and ensure continuity in learning progression.
2026-04-16 21:55:05 +02:00
Torsten Schulz (local)
b2942c1c9d feat(vocabService): enhance answer validation with comparable variants
All checks were successful
Deploy to production / deploy (push) Successful in 2m45s
- Introduced methods to expand comparable answer variants and improve answer equivalence checks, allowing for more flexible user responses.
- Updated the answer validation logic to utilize the new methods, enhancing the accuracy of response evaluation during exercises.
2026-04-16 16:46:37 +02:00
Torsten Schulz (local)
41b07a8951 fix(bisaya-course): update alternatives for transformation exercise
All checks were successful
Deploy to production / deploy (push) Successful in 2m57s
- Modified the alternatives for a transformation exercise to include additional variations, enhancing the response options for learners.
- Updated the vocabulary service to incorporate both primary and alternative answers, improving the accuracy of answer validation during exercises.
2026-04-16 16:42:48 +02:00
Torsten Schulz (local)
9bc6f32b96 feat(bisaya-course): expand lessons on shopping, prices, and numbers
All checks were successful
Deploy to production / deploy (push) Successful in 2m43s
- Added new lessons titled "Einkaufen & Preise" and "Zahlen & Preise" with various exercises focusing on shopping vocabulary and numerical expressions in Bisaya.
- Introduced multiple exercise types including multiple-choice, gap-fill, and transformation tasks to enhance learner engagement and understanding of practical language use in market contexts.
- Updated didactics to include learning goals, core patterns, grammar focus, speaking prompts, and practical tasks related to shopping dialogues and numerical comprehension.
- Included a review lesson "Woche 2 - Wiederholung" to reinforce key concepts from previous lessons, ensuring comprehensive learning continuity.
- Enhanced vocabulary testing with a new "Woche 2 - Vokabeltest" to assess retention of key terms and phrases.
2026-04-16 16:25:47 +02:00
Torsten Schulz (local)
8e29953a95 feat(bisaya-course): add new lesson on time and date concepts
All checks were successful
Deploy to production / deploy (push) Successful in 2m52s
- Introduced a new lesson titled "Zeit & Datum" with multiple exercises focusing on recognizing and using key time-related vocabulary in Bisaya.
- Added various exercise types including multiple-choice, gap-fill, and transformation tasks to enhance learner engagement and understanding of temporal expressions.
- Updated didactics to include learning goals, core patterns, grammar focus, speaking prompts, and practical tasks related to time markers and simple date questions.
2026-04-16 16:09:10 +02:00
Torsten Schulz (local)
e26a3333c4 fix(vocabLessonReview): improve input normalization by trimming punctuation
All checks were successful
Deploy to production / deploy (push) Successful in 2m47s
- Enhanced the `normalize` method to trim trailing punctuation marks (.,!?;:) from input strings, ensuring cleaner and more consistent user input.
- Added logic to replace multiple spaces with a single space, improving the overall formatting of normalized strings.
2026-04-16 15:23:00 +02:00
Torsten Schulz (local)
44850d5913 feat(bisaya-course): update gap-fill exercise structure and instructions
All checks were successful
Deploy to production / deploy (push) Successful in 2m42s
- Changed exercise type from 'pattern_drill' to 'gap_fill' for improved clarity and engagement.
- Revised instructions to specify the use of the verb "adto" across different tenses, enhancing learner guidance.
- Updated question data format to include gaps for each tense, streamlining the exercise layout.
- Adjusted answer data to reflect the new gap-fill structure, ensuring consistency in exercise responses.
- Enhanced explanation to clarify the focus on tense variations with the verb "adto," improving educational value.
2026-04-16 15:06:17 +02:00
Torsten Schulz (local)
a294a94075 feat(vocabLesson): enhance input handling for gap-fill exercises
All checks were successful
Deploy to production / deploy (push) Successful in 2m45s
- Added functionality to manage Enter key events for gap-fill and single input fields, improving user experience during exercises.
- Introduced methods `onGapInputEnter` and `onSingleInputEnter` to streamline answer checking and navigation between input fields.
- Updated input elements with refs for better focus management, ensuring a smoother interaction flow for users.
2026-04-16 14:55:43 +02:00
Torsten Schulz (local)
cb0e1eb2b1 feat(falukant): refactor character ID resolution for political office checks
All checks were successful
Deploy to production / deploy (push) Successful in 2m46s
- Introduced a new method `resolveCharacterIdForOfficeChecks` to robustly resolve character IDs based on user references, improving the reliability of political office queries.
- Updated `getHighestPoliticalOfficeInfo` and `getHighestOfficeAnyInfo` methods to utilize the new character ID resolution logic, enhancing code clarity and reducing redundancy.
- Ensured that character ID retrieval handles both direct and fallback user references, streamlining the process of fetching political office information.
2026-04-15 15:47:31 +02:00
Torsten Schulz (local)
95ea6336b7 feat(falukant): add political office catalog feature and localization updates
All checks were successful
Deploy to production / deploy (push) Successful in 2m52s
- Implemented the `getPoliticalOfficeCatalog` method in FalukantService to retrieve available political offices and their prerequisites based on user eligibility.
- Updated FalukantController and FalukantRouter to include a new endpoint for accessing the political office catalog.
- Enhanced PoliticsView component to display the list of political offices, including their details and application eligibility.
- Added localization entries for the new offices tab in both German and English, improving user experience and accessibility.
2026-04-15 15:30:24 +02:00
Torsten Schulz (local)
7b4c9a0b1c feat(vocabLesson): add detailed review section for failed chapter exams
All checks were successful
Deploy to production / deploy (push) Successful in 2m53s
- Introduced a new review section that displays questions, user answers, and correct answers for failed chapter exams.
- Added localization entries for the review section in multiple languages, enhancing user experience and understanding of mistakes.
- Implemented logic to format and present failed exam details, improving feedback for learners.
2026-04-15 14:02:34 +02:00
Torsten Schulz (local)
cc791501c9 feat(bisaya-course): enhance lesson content for temporal grammar and exercises
All checks were successful
Deploy to production / deploy (push) Successful in 2m52s
- Updated learning goals to emphasize distinguishing between past, present, and future in everyday sentences.
- Revised core patterns to include specific examples for each tense, improving clarity and context for learners.
- Expanded grammar focus sections to provide detailed explanations of time markers and their usage.
- Introduced new exercises for practicing grammar focus, including multiple-choice questions and transformation tasks, enhancing student engagement with temporal aspects of the language.
- Updated speaking prompts and practical tasks to reflect the new content structure, ensuring a comprehensive learning experience.
2026-04-15 13:25:15 +02:00
Torsten Schulz (local)
feda7d90f2 feat(bisaya-course): enhance grammar focus and drills for lesson content
All checks were successful
Deploy to production / deploy (push) Successful in 2m57s
- Added functions to normalize and build grammar curriculum focus entries, improving the structure and clarity of grammar lessons.
- Implemented logic to merge existing grammar focus with newly defined curriculum focus, ensuring comprehensive coverage of key grammar points.
- Introduced new drills for practicing past and future tense constructions, enhancing student engagement and understanding of temporal aspects in Bisaya.
- Updated the `createBisayaCourseContent` function to incorporate these enhancements, ensuring lessons are enriched with relevant grammar exercises.
2026-04-15 11:52:53 +02:00
Torsten Schulz (local)
ff4fbbfab1 feat(bisaya-course): update exercise content for market-related vocabulary
All checks were successful
Deploy to production / deploy (push) Successful in 2m55s
- Changed the exercise question and answer to focus on market-related scenarios, replacing previous church-related content.
- Updated the explanation to align with the new vocabulary introduced in this lesson, enhancing contextual learning for students.
2026-04-15 11:49:50 +02:00
Torsten Schulz (local)
5d16021557 feat(bisaya-course): implement core pattern extraction and merging for exercises
All checks were successful
Deploy to production / deploy (push) Successful in 2m55s
- Added functions to derive core patterns from various exercise types, including gap-fill, transformation, and multiple-choice.
- Implemented logic to merge derived core patterns with existing lesson patterns, ensuring a minimum count for effective lesson content.
- Enhanced the `createBisayaCourseContent` function to update lessons with merged core patterns and provide detailed logging based on the VOCAB_STRICT_AUDIT environment variable.
2026-04-15 11:12:18 +02:00
Torsten Schulz (local)
3500940d1c feat(bisaya-course): add new lessons for everyday conversations
All checks were successful
Deploy to production / deploy (push) Successful in 3m1s
- Introduced two new lessons: 'Alltagsgespräche - Teil 1' and 'Alltagsgespräche - Teil 2'.
- Each lesson includes learning goals, core patterns, speaking prompts, and practical tasks to enhance conversational skills in everyday contexts.
- Updated the lesson structure to support these additions, ensuring a comprehensive learning experience.
2026-04-15 11:09:14 +02:00
Torsten Schulz (local)
f841a35501 feat(bisaya-course): enhance gap-fill exercise processing and validation
All checks were successful
Deploy to production / deploy (push) Successful in 2m54s
- Added `buildCorePatternGlossLookup` to create a mapping of core patterns for improved hint sanitization.
- Implemented `sanitizeGapFillHintText` to validate and replace hints based on answer word count and gloss availability, generating fixes and warnings as needed.
- Updated `sanitizeExerciseForConsistency` to incorporate new sanitization logic for gap-fill exercises, ensuring consistent exercise data handling.
- Enhanced logging in `createBisayaCourseContent` to provide detailed feedback on exercise processing and validation outcomes.
2026-04-14 15:33:00 +02:00
Torsten Schulz (local)
78da376c5b feat(bisaya-course): add word count validation and exercise audit warnings
Some checks failed
Deploy to production / deploy (push) Has been cancelled
- Introduced a `countWords` function to calculate the number of words in a given text.
- Added `collectExerciseAuditWarnings` function to generate warnings for gap-fill exercises with potentially misleading hints.
- Updated the `createBisayaCourseContent` function to log audit warnings when the VOCAB_STRICT_AUDIT environment variable is set, enhancing the validation process for exercise content.
2026-04-14 15:30:58 +02:00
Torsten Schulz (local)
7e5a46a9bf fix(vocabLesson): improve answer validation logic for gap fill exercises
All checks were successful
Deploy to production / deploy (push) Successful in 2m56s
- Enhanced the answer validation process by introducing checks for native word and answer text, ensuring they are trimmed and compared correctly.
- Added logic to identify likely fragment mismatches based on sentence structure and word count, preventing incorrect answer submissions.
- Updated debug logging to provide clearer insights into the validation process, improving maintainability and debugging capabilities.
2026-04-14 14:57:54 +02:00
Torsten Schulz (local)
deb6f5f36c feat(falukant): add improve lover affection feature and localization updates
All checks were successful
Deploy to production / deploy (push) Successful in 2m49s
- Introduced the `improveLoverAffection` method in FalukantService to enhance relationship dynamics by allowing users to boost affection at a cost.
- Updated FalukantController and FalukantRouter to include the new endpoint for improving lover affection.
- Enhanced FamilyView component to provide a button for users to trigger the affection improvement action.
- Added localization entries for the new feature in multiple languages, ensuring clarity in user interactions regarding affection improvements.
2026-04-14 11:39:42 +02:00
Torsten Schulz (local)
26daf5fed5 feat(falukant): enhance notification handling and localization updates
All checks were successful
Deploy to production / deploy (push) Successful in 2m52s
- Updated the `enrichNotificationsWithCharacterNames` method in FalukantService to include region name enrichment and handle additional character IDs.
- Introduced a new `serializeNotificationForClient` function to format notifications for the client, ensuring all relevant data is included.
- Enhanced the MessagesDialog component to merge notification payloads and extract parameters more effectively, improving the clarity of displayed messages.
- Added new localization entries for director resignation risk and regional festival effects in multiple languages, ensuring comprehensive user notifications.
2026-04-14 08:06:56 +02:00
Torsten Schulz (local)
9deda3147e feat(falukant): add transport cost option to product price calculations
All checks were successful
Deploy to production / deploy (push) Successful in 2m49s
- Updated the `getProductPricesInCitiesBatch` method in FalukantService to accept an options parameter for including transport costs in price calculations.
- Modified the tax calculation logic to conditionally apply transport costs based on the new parameter, enhancing the accuracy of pricing based on regional differences.
- Adjusted the FalukantController to pass the includeTransportCosts option from the request body, improving flexibility in pricing queries.
2026-04-13 16:11:53 +02:00
Torsten Schulz (local)
b50d2a9a93 feat(falukant): refine tax calculation logic to handle political office exemptions
All checks were successful
Deploy to production / deploy (push) Successful in 2m41s
- Updated the tax calculation in FalukantService to ensure no exemptions are applied for political offices, streamlining the cumulative tax query.
- Enhanced the SQL query to accurately compute cumulative tax while excluding exempted ancestor levels based on office, improving overall accuracy in tax assessments.
2026-04-13 16:05:41 +02:00
Torsten Schulz (local)
f92b62e55b feat(falukant): enhance tax calculation and update messaging parameters
All checks were successful
Deploy to production / deploy (push) Successful in 2m52s
- Updated the tax calculation logic in FalukantService to include character-based checks for political offices, improving accuracy in tax assessments.
- Enhanced the MessagesDialog component to incorporate additional parameters such as satisfaction, threshold_percent, director_id, and director_character_id, providing more detailed notifications.
- Added new localization entries for director resignation risk notifications in multiple languages, ensuring users receive clear and contextual information regarding director dynamics.
2026-04-13 15:58:30 +02:00
Torsten Schulz (local)
86e14a875d feat(falukant): add scandalExtraDailyPct field and update related components
All checks were successful
Deploy to production / deploy (push) Successful in 2m59s
- Introduced a new field `scandalExtraDailyPct` in the relationship state model to track additional scandal risk per day, with validation constraints.
- Updated the FalukantService to include the new field in relevant calculations and data handling.
- Enhanced the frontend components, including RevenueSection and FamilyView, to display the scandal risk information and updated price calculations.
- Added localization entries for the new field in multiple languages to ensure clarity for users.
2026-04-13 15:31:47 +02:00
Torsten Schulz (local)
b0624422b8 feat(falukant): update production counting logic and enhance localization
All checks were successful
Deploy to production / deploy (push) Successful in 2m55s
- Modified the production counting logic in FalukantService to count each completed production directly from the falukant_log.production table, simplifying the query structure.
- Added a new localization entry for "scoreHowToRaise" in Cebuano, German, English, Spanish, and French to provide users with clear guidance on how to improve their score through various factors.
- Updated the OverviewView component to display the new score improvement information, enhancing user experience and understanding of scoring dynamics.
2026-04-13 13:59:37 +02:00
Torsten Schulz (local)
162e908c1c feat(falukant): add 'lover_breakup_risk_high' event handling and localization updates
All checks were successful
Deploy to production / deploy (push) Successful in 2m52s
- Introduced a new event type for high breakup risk in relationships, including a sample payload for integration.
- Updated the MessagesDialog component to handle new parameters related to breakup risk.
- Added translations for the new event in Cebuano, German, English, Spanish, and French to enhance user understanding of relationship dynamics.
- Enhanced documentation to reflect the new notification structure for high breakup risk events.
2026-04-13 12:21:24 +02:00
Torsten Schulz (local)
1bce855b3a feat(falukant): enhance marriage actions with production delay handling and UI updates
All checks were successful
Deploy to production / deploy (push) Successful in 2m58s
- Implemented logic in FalukantService to delay running productions when spending time with a spouse, updating the start timestamp accordingly.
- Enhanced the response from the spend time API to include details about delayed productions and household order changes.
- Updated FamilyView component to display the cost of marriage actions and added a help section detailing the effects of actions, including production delays.
- Added new translations for action effects in multiple languages to improve user understanding of the marriage actions.
2026-04-13 12:09:17 +02:00
Torsten Schulz (local)
ba72d4fb74 feat(i18n): update 'acknowledgeHelp' translations and enhance UI interaction
All checks were successful
Deploy to production / deploy (push) Successful in 2m56s
- Added new translations for 'maintenance', 'affection', and 'breakup' in Cebuano, German, English, and Spanish to provide users with comprehensive context regarding relationship dynamics.
- Refactored the FamilyView component to utilize a details element for the 'acknowledgeHelp' section, improving user interaction and readability.
- Enhanced styling for the summary element to improve visibility and user engagement.
2026-04-13 11:41:17 +02:00
Torsten Schulz (local)
e2f4b255ff removed double question
All checks were successful
Deploy to production / deploy (push) Successful in 2m59s
2026-04-13 11:27:06 +02:00
Torsten Schulz (local)
60ef98283f fix(falukant): validate office term length and adjust term end calculation
All checks were successful
Deploy to production / deploy (push) Successful in 2m51s
- Added validation for office term length to ensure it is a positive finite number, throwing an error for invalid values.
- Updated the calculation of the term end date to use the term length in days instead of years, aligning with the expected data format.
2026-04-10 16:08:50 +02:00
Torsten Schulz (local)
c6419c6c34 feat(vocab): implement pagination and localization for vocabulary dictionaries
All checks were successful
Deploy to production / deploy (push) Successful in 3m2s
- Added pagination functionality to the vocabulary dictionary views, allowing users to navigate through results efficiently.
- Introduced a new method `_parseDictionaryPaging` in `VocabService` to handle pagination parameters.
- Updated `getLanguageDictionary` and `getCourseDictionary` methods to return pagination details alongside results.
- Enhanced the `VocabDictionaryView` component with pagination controls and updated UI for better user experience.
- Added localization entries for pagination in Cebuano, German, English, Spanish, and French, ensuring a consistent user experience across languages.
2026-04-10 14:35:50 +02:00
Torsten Schulz (local)
f46c864bbc feat(vocab-lesson): implement pagination for vocabulary overview in lesson view
All checks were successful
Deploy to production / deploy (push) Successful in 2m59s
- Added pagination functionality to the vocabulary overview in the VocabLessonView component, allowing users to navigate through vocabulary items more efficiently.
- Introduced new localization entries for pagination controls in Cebuano, German, English, Spanish, and French, ensuring a consistent user experience across languages.
- Enhanced the UI with buttons for previous and next navigation, improving accessibility and usability of the vocabulary list.
2026-04-10 14:26:01 +02:00
Torsten Schulz (local)
545314e905 feat(i18n, frontend): enhance course planning with optional steps and localization updates
All checks were successful
Deploy to production / deploy (push) Successful in 2m52s
- Added a new section for optional learning steps in the course planning UI, allowing users to engage with additional content when no mandatory tasks are due.
- Updated localization files for Cebuano, German, English, Spanish, and French to reflect changes in course planning instructions and titles, ensuring clarity and consistency across languages.
- Improved pedagogical logic for lesson recommendations, focusing on cognitive load and spaced repetition principles to enhance user learning experience.
2026-04-10 13:33:25 +02:00
Torsten Schulz (local)
d17c8a341d feat(vocab): add language and course dictionary endpoints and UI components
All checks were successful
Deploy to production / deploy (push) Successful in 2m53s
- Implemented `getLanguageDictionary` and `getCourseDictionary` methods in the VocabService to retrieve vocabulary entries filtered by search terms.
- Updated VocabController and vocabRouter to include new routes for accessing language and course dictionaries.
- Enhanced frontend components to navigate to the new dictionary views, including buttons in VocabCourseView and VocabLanguageView.
- Added localization entries for the dictionary feature in multiple languages, ensuring a consistent user experience across the platform.
2026-04-10 13:08:18 +02:00
Torsten Schulz (local)
9582e7b900 feat(i18n, frontend): add 'acknowledgeHelp' translations and UI enhancements
All checks were successful
Deploy to production / deploy (push) Successful in 2m56s
- Introduced 'acknowledgeHelp' translations in Cebuano, German, English, and Spanish to provide users with context about the implications of acknowledging relationships.
- Updated the FamilyView component to display the new 'acknowledgeHelp' information, enhancing user understanding of relationship dynamics.
- Improved styling for the acknowledgment help section to ensure clarity and visibility within the user interface.
2026-04-10 10:29:20 +02:00
Torsten Schulz (local)
7e0821c96b feat(falukant): enhance reputation handling in character checks
All checks were successful
Deploy to production / deploy (push) Successful in 2m52s
- Added the 'reputation' attribute to the character query in FalukantService, improving data retrieval for nobility requirements.
- Refactored the `checkReputationRequirement` method to streamline reputation checks, ensuring it handles undefined or null values more effectively, enhancing reliability in user character evaluations.
2026-04-10 08:45:54 +02:00
Torsten Schulz (local)
3450bac99c feat(falukant): enhance notification handling and parameter extraction in MessagesDialog
All checks were successful
Deploy to production / deploy (push) Successful in 2m51s
- Introduced a new method `mergeNotificationPayload` to consolidate top-level fields with embedded JSON from notifications, improving data handling.
- Updated `formatBody` and `extractParams` methods to utilize the merged payload, ensuring accurate extraction of parameters from notifications.
- Enhanced parameter extraction logic to handle various notification structures, improving clarity and consistency in displayed information.
2026-04-10 08:41:53 +02:00
Torsten Schulz (local)
1dfa49191f feat(vocab-lesson): enhance exercise navigation and randomization logic
All checks were successful
Deploy to production / deploy (push) Successful in 2m57s
- Improved the exercise progression logic to automatically advance only on correct answers, ensuring better user feedback during learning.
- Added a new method to shuffle multiple-choice options display order when distractor replacement is not applicable, enhancing the exercise experience.
- Updated the build process for randomized options to include both randomization from a pool and display order shuffling, improving overall engagement and learning outcomes.
2026-04-09 17:57:51 +02:00
Torsten Schulz (local)
5cbd6d06b1 feat(bisaya-course): refine exercises and instructions for improved learning experience
All checks were successful
Deploy to production / deploy (push) Successful in 2m54s
- Updated exercise titles and instructions to enhance clarity and focus on specific learning objectives.
- Modified question data and answer options to better align with the lesson's vocabulary and context.
- Improved explanations to provide clearer guidance on language usage and learning goals, fostering better comprehension for learners.
2026-04-09 17:49:50 +02:00
Torsten Schulz (local)
2b66f76dd9 feat(bisaya-course): add new exercises to enhance Bisaya language learning
All checks were successful
Deploy to production / deploy (push) Successful in 2m52s
- Introduced multiple-choice and transformation exercises focused on common phrases and vocabulary, including translations and contextual usage.
- Expanded the curriculum with practical tasks that encourage conversational skills and comprehension in various scenarios.
- Enhanced existing exercises with clear instructions and explanations to improve user engagement and learning outcomes.
2026-04-09 17:23:20 +02:00
Torsten Schulz (local)
204b7aed04 feat(bisaya-course): expand 'Haus & Familie' lesson with new vocabulary and exercises
All checks were successful
Deploy to production / deploy (push) Successful in 2m52s
- Added comprehensive learning goals, core patterns, grammar focus, speaking prompts, and practical tasks for the 'Haus & Familie' lesson, enhancing the curriculum for better language acquisition.
- Updated exercise content to include multiple-choice questions and gap-fill tasks related to household vocabulary, improving user engagement and learning outcomes.
- Enhanced localization for the lesson description and vocabulary, ensuring clarity and consistency across the learning platform.
2026-04-09 17:19:02 +02:00
Torsten Schulz (local)
f5a5639e97 feat(seo, i18n): update SEO metadata and enhance localization across multiple languages
All checks were successful
Deploy to production / deploy (push) Successful in 2m47s
- Revised SEO titles and descriptions for improved clarity and relevance, emphasizing the platform's features such as community, chat, forums, and browser games.
- Updated localization files for Cebuano, German, English, Spanish, and French to include new translations and enhance existing content, ensuring a consistent user experience across languages.
- Added a new story teaser in the home view to better highlight the platform's offerings without requiring downloads.
2026-04-09 09:44:53 +02:00
Torsten Schulz (local)
641a2134cb fix(falukant): improve product pricing logic and update localization for office notifications
All checks were successful
Deploy to production / deploy (push) Successful in 2m44s
- Refactored the product pricing logic in `falukantService.js` to ensure accurate regional pricing calculations based on client values and server data.
- Added new notification translations for "office filled" in multiple languages (Cebuano, German, English, Spanish, French) to enhance user experience and clarity in notifications.
2026-04-09 09:21:47 +02:00
Torsten Schulz (local)
1118a691b9 feat(falukant): enhance product pricing and nobility advancement features
All checks were successful
Deploy to production / deploy (push) Successful in 2m52s
- Updated the `getAllProductPricesInRegion` method in `FalukantService` to accept additional parameters for network worth and branch ID, improving pricing calculations based on user branches.
- Enhanced the nobility advancement logic in `NobilityView` to display unmet requirements clearly, providing users with better feedback on advancement conditions.
- Refactored the revenue calculation in `ProductionSection` to utilize a cached product prices object, optimizing performance and reducing redundant API calls.
- Updated localization files to include new translations for attack sources across multiple languages, enhancing the user experience for diverse audiences.
- Removed obsolete C++ worker references and streamlined the retention logic for production logs, ensuring efficient data management.
2026-04-09 09:08:32 +02:00
Torsten Schulz (local)
360bb59a4e feat(user): add certificate production tracking and update localization
All checks were successful
Deploy to production / deploy (push) Successful in 2m50s
- Introduced a new field `certificateProductionsCountSince` in the `FalukantUser` model to track the date from which production logs are counted for certificate requirements.
- Updated the `FalukantService` to utilize the new field for calculating completed productions since the specified date.
- Enhanced the UI to display the count of productions since the last promotion, with corresponding translations added for multiple languages including Cebuano, German, English, Spanish, and French.
- Implemented a method to delete old production logs, ensuring efficient data management while maintaining necessary historical records for certificate calculations.
2026-04-09 08:19:19 +02:00
Torsten Schulz (local)
f7030bbabe feat(i18n): add French language support and enhance localization
All checks were successful
Deploy to production / deploy (push) Successful in 2m48s
- Introduced French as a supported language across the application, updating locale files and adding translations for various components.
- Enhanced language handling logic to accommodate French, ensuring proper detection and fallback mechanisms.
- Updated UI elements to include French language options, improving accessibility for French-speaking users.
- Refactored SEO handling to include French in hreflang links, enhancing search engine indexing for multilingual content.
- Added new scripts for managing French translations and ensuring consistency across language files.
2026-04-07 18:04:03 +02:00
Torsten Schulz (local)
f715c6125d feat(i18n): implement deep merging for locale chunks and enhance Cebuano translations
All checks were successful
Deploy to production / deploy (push) Successful in 2m48s
- Introduced a deep merge function to combine locale chunks, improving the handling of language data for Cebuano.
- Updated Cebuano locale files with comprehensive translations, including new sections for admin, social network, and settings.
- Enhanced existing translations for clarity and consistency across various components, ensuring a better user experience.
- Added new fields in the settings and profile sections to capture more user attributes, improving personalization options.
2026-04-07 16:23:11 +02:00
Torsten Schulz (local)
e9f0f6c133 fix(i18n): update Cebuano language label in AppHeader.vue
All checks were successful
Deploy to production / deploy (push) Successful in 2m53s
- Changed the native label for Cebuano from 'Sinugboanon' to 'Binisaya' in the language options of AppHeader.vue for improved accuracy and user recognition.
2026-04-07 15:59:07 +02:00
Torsten Schulz (local)
ae635b9c16 refactor(i18n): improve language handling and UI updates
All checks were successful
Deploy to production / deploy (push) Successful in 2m49s
- Enhanced language setting logic in App.vue and AppHeader.vue to support reactive updates without page reloads.
- Updated language options in AppHeader.vue to use native labels for improved clarity.
- Introduced a utility function in i18n/index.js to streamline locale updates, ensuring consistent language handling across the application.
2026-04-07 15:52:16 +02:00
Torsten Schulz (local)
c5b8860605 feat(seo): enhance multilingual support and SEO handling
All checks were successful
Deploy to production / deploy (push) Successful in 2m46s
- Added support for multiple languages in the frontend, including English, Spanish, and Cebuano, improving accessibility for a broader audience.
- Implemented hreflang links for better SEO performance, ensuring search engines can correctly index language-specific content.
- Updated SEO metadata handling to utilize internationalization keys, enhancing the clarity and relevance of page titles and descriptions.
- Refactored SEO utility functions to streamline the management of OpenGraph and hreflang attributes, improving maintainability and performance.
2026-04-07 15:43:16 +02:00
Torsten Schulz (local)
ebb2283646 refactor(exercises): standardize answer language handling across exercise scripts
All checks were successful
Deploy to production / deploy (push) Successful in 2m48s
- Introduced a mechanism to infer answer language based on question phrasing in multiple exercise scripts, enhancing consistency in exercise data.
- Updated question formats to clarify the intent of exercises, improving user understanding and engagement.
- Streamlined the code for better maintainability and clarity in exercise generation processes.
2026-04-07 14:32:44 +02:00
Torsten Schulz (local)
160c9dafb2 refactor(vocab): enhance token weight handling in VocabLessonView
All checks were successful
Deploy to production / deploy (push) Successful in 2m45s
- Introduced a new mechanism for calculating token weights based on core patterns and important vocabulary, improving the accuracy of target-gloss pair orientation.
- Replaced the previous hint logic with a more robust token-based scoring system, enhancing the quality of vocabulary item representation.
- Streamlined the code for better maintainability and clarity in vocabulary preparation processes.
2026-04-07 11:32:14 +02:00
Torsten Schulz (local)
ee338c0e49 refactor(vocab): update vocabulary label handling in VocabLessonView
All checks were successful
Deploy to production / deploy (push) Successful in 2m44s
- Replaced computed properties for current vocabulary preparation labels with direct references to new properties, enhancing clarity and maintainability.
- Improved the logic for populating native and target hints, ensuring accurate representation of vocabulary items.
- Removed outdated label swapping logic, streamlining the code and focusing on course-specific language names for better user experience.
2026-04-07 11:22:22 +02:00
Torsten Schulz (local)
fe9322c098 feat(vocab): attach language names to course data in VocabService
All checks were successful
Deploy to production / deploy (push) Successful in 2m49s
- Added a method to attach language names to course rows, enhancing the clarity and localization of vocabulary lessons.
- This update improves the overall user experience by ensuring accurate representation of language names in course data.
2026-04-07 11:13:38 +02:00
Torsten Schulz (local)
3a1d83f20c feat(vocab): add course language name handling in VocabLessonView
All checks were successful
Deploy to production / deploy (push) Successful in 2m48s
- Introduced new properties for course language names to enhance clarity in vocabulary preparation.
- Implemented a method to load course language names from the API, improving localization and user experience.
- Updated label logic to utilize course-specific language names, ensuring accurate representation in vocabulary lessons.
2026-04-07 10:29:28 +02:00
Torsten Schulz (local)
e649236e39 feat(vocab): refine vocabulary label logic in VocabLessonView
All checks were successful
Deploy to production / deploy (push) Successful in 2m48s
- Introduced computed properties for current vocabulary preparation labels, enhancing clarity and localization.
- Updated label handling to dynamically adjust based on the context of target and gloss items, improving user experience.
- Enhanced the logic for determining when to swap labels, ensuring accurate representation of vocabulary items for better learning outcomes.
2026-04-07 10:22:36 +02:00
Torsten Schulz (local)
8f6f06caf0 feat(vocab): enhance vocabulary label handling in VocabLessonView
All checks were successful
Deploy to production / deploy (push) Successful in 2m50s
- Refactored VocabLessonView to utilize computed properties for vocabulary preparation labels, improving localization and maintainability.
- Updated the logic for target and gloss labels to prioritize course-specific language names, enhancing user experience and clarity in vocabulary presentation.
- Improved the orientation logic for target-gloss pairs to ensure accurate representation of vocabulary items, contributing to better learning outcomes.
2026-04-07 10:07:54 +02:00
Torsten Schulz (local)
62503191d4 feat(vocab): improve vocabulary preparation logic in VocabLessonView
All checks were successful
Deploy to production / deploy (push) Successful in 2m44s
- Refactored prepItems method to ensure only valid target-gloss pairs are included, enhancing the quality of vocabulary preparation.
- Introduced a mechanism to limit the output to a maximum of 30 unique pairs, improving the compactness of the preparation round.
- Enhanced handling of core patterns and extracted vocabulary to prioritize high-quality learning materials.
2026-04-07 09:58:33 +02:00
Torsten Schulz (local)
86dfb0d859 feat(vocab): update course today plan localization and UI logic
All checks were successful
Deploy to production / deploy (push) Successful in 2m49s
- Added new localization strings for scenarios with no due reviews in German, English, and Spanish, enhancing user guidance.
- Updated VocabCourseView to conditionally display the appropriate introduction message based on the presence of due reviews, improving clarity in the course flow.
2026-04-07 09:55:01 +02:00
Torsten Schulz (local)
f504a5d597 feat(vocab): enhance feedback acknowledgment and localization in vocabulary lessons
All checks were successful
Deploy to production / deploy (push) Successful in 2m41s
- Added acknowledgment messages for exercise reinforcement in German, English, and Spanish localization files to improve user guidance.
- Updated VocabLessonView to include a feedback acknowledgment button, enhancing user interaction after answering questions.
- Implemented logic to track feedback acknowledgment state, improving the flow of lesson reviews and user experience.
2026-04-07 09:47:45 +02:00
Torsten Schulz (local)
3cc5f63610 feat(vocab): add lesson review functionality and update navigation
All checks were successful
Deploy to production / deploy (push) Successful in 2m46s
- Introduced VocabLessonReviewView for reviewing lessons within the vocabulary course structure.
- Updated routing in socialRoutes.js to include a new path for lesson reviews, ensuring authenticated access.
- Modified VocabCourseView to change the lesson button click behavior to navigate to the review view, enhancing user flow.
- Added a new method to handle lesson review navigation, improving the overall user experience in the vocabulary section.
2026-04-07 09:38:00 +02:00
Torsten Schulz (local)
e17f0cdce0 feat(vocab): enhance vocabulary exercises and localization support
All checks were successful
Deploy to production / deploy (push) Successful in 2m51s
- Updated core patterns in BISAYA_PHASE5_DIDACTICS to include gloss translations for better understanding.
- Refactored vocabulary exercise generation in update-food-care-exercises.js to improve randomization and user engagement.
- Added new exercise types and improved question structures for vocabulary lessons, enhancing the learning experience.
- Enhanced localization files for German, English, and Spanish to support new exercise features and improve user guidance.
- Updated VocabLessonView to incorporate sequential navigation for exercises, providing a more structured learning flow.
2026-04-07 09:09:43 +02:00
Torsten Schulz (local)
d192bcae2d refactor(vocabService, VocabLessonView): unify answer language classification for multiple-choice questions
All checks were successful
Deploy to production / deploy (push) Successful in 2m51s
- Renamed and refactored the method for classifying answer languages in multiple-choice questions from _classifyMcQuestionSide to _resolveMcAnswerSide for clarity and consistency.
- Updated the logic to classify answer languages based on questionData.answerLanguage and answerLanguageId, improving accuracy in determining the correct language context.
- Adjusted VocabLessonView to utilize the new classification method, ensuring alignment with backend changes and enhancing the handling of distractor options based on answer language.
- Enhanced documentation to clarify the expected input and output for the new method, improving maintainability and understanding of the codebase.
2026-04-07 08:34:19 +02:00
Torsten Schulz (local)
07ab648143 feat(political-office): enhance political office benefits and salary computation
All checks were successful
Deploy to production / deploy (push) Successful in 3m6s
- Added a new hierarchyLevel field to PoliticalOfficeType for better categorization of political roles.
- Updated computePoliticalDailySalaryPayout function to incorporate hierarchy level in salary calculations, allowing for more dynamic salary adjustments based on office rank.
- Modified SQL scripts to reflect changes in political office benefits, ensuring compatibility with the new salary structure.
- Enhanced localization files to support updated benefit descriptions and salary formats across multiple languages.
- Improved UI components to display the new salary calculations and benefits accurately in the PoliticsView.
2026-04-02 16:49:18 +02:00
Torsten Schulz (local)
e063df5cbe refactor(PoliticsView): enhance UI layout and styling for political information display
All checks were successful
Deploy to production / deploy (push) Successful in 2m52s
- Improved the layout of the political information section by introducing a structured row format for better readability.
- Updated CSS styles to enhance the visual hierarchy and alignment of labels and values, ensuring a more user-friendly interface.
- Adjusted the display of benefits and term end information to provide clearer context and organization within the PoliticsView component.
2026-04-02 16:09:52 +02:00
Torsten Schulz (local)
56be4b76c0 feat(political-benefits): implement political powers and benefits system
All checks were successful
Deploy to production / deploy (push) Successful in 3m3s
- Added new political powers and benefits functionalities, including reputation ticks, tax jurisdiction management, and appointment capabilities.
- Introduced a new job for periodic reputation updates and created necessary database tables for tracking political benefits.
- Enhanced the FalukantController and services to support new endpoints for managing political powers and appointments.
- Updated localization files to reflect new features and improve user experience across multiple languages.
- Modified the UI to display new political powers and benefits, ensuring accurate representation in the PoliticsView.
2026-04-02 16:00:29 +02:00
Torsten Schulz (local)
5d06d97737 feat(politics): add new political office benefits and enhance database migration
All checks were successful
Deploy to production / deploy (push) Successful in 2m54s
- Introduced new benefits including 'reputation_periodic', 'appoint_politicians', 'set_regional_tax', 'free_lover_slots', 'guard_protection', and 'court_immunity' to the political office system.
- Updated database migration to add and remove the 'last_political_daily_salary_on' column using SQL queries for better performance.
- Enhanced localization files for multiple languages to support new benefits, improving user experience across the application.
- Updated UI components to display new benefits correctly in the PoliticsView, ensuring accurate representation of political office functionalities.
2026-04-02 15:27:05 +02:00
Torsten Schulz (local)
49713d957d feat(dashboard): enhance widget availability and initialization
All checks were successful
Deploy to production / deploy (push) Successful in 3m2s
- Updated `getAvailableWidgets` method in `DashboardService` to merge default widget types with database entries, ensuring immediate visibility of new widgets post-deployment.
- Introduced `DASHBOARD_WIDGET_TYPE_DEFAULTS` in `initializeWidgetTypes` for canonical widget types, facilitating API merging when database entries are absent.
- Modified `StatusBar.vue` to utilize a computed property for quick access children, improving menu item handling and visibility based on user context.
- Enhanced `LoggedInView.vue` to dynamically return localized widget labels, improving user experience with accurate translations.
2026-04-02 15:19:08 +02:00
Torsten Schulz (local)
5fcd55be43 feat(vocab): add dashboard learning summary and related endpoints
All checks were successful
Deploy to production / deploy (push) Successful in 2m52s
- Introduced `getDashboardLearningSummary` method in `VocabService` to provide a compact overview of enrolled courses and current lessons for users.
- Updated `vocabController` to include a new route for the dashboard widget, allowing users to access their learning summary.
- Enhanced `vocabRouter` to route requests for the new dashboard widget endpoint.
- Added localization support for the new dashboard features across multiple languages, improving user engagement and accessibility.
- Updated UI components to integrate the new dashboard widget, ensuring a seamless user experience.
2026-04-02 15:06:50 +02:00
Torsten Schulz (local)
77e6f8d3e8 feat(vocab): enhance vocabService and VocabLessonView for improved exercise handling
All checks were successful
Deploy to production / deploy (push) Successful in 2m58s
- Updated VocabService to include validation for synthetic exercise IDs and added methods for generating multiple-choice exercises based on chapter lexemes.
- Implemented a seeded shuffle function to randomize distractor options in multiple-choice questions, ensuring varied user experiences.
- Modified VocabLessonView to adjust target calculations for lesson goals and unlock attempts, increasing the maximum limits for better flexibility in user progress tracking.
2026-04-02 14:23:21 +02:00
Torsten Schulz (local)
3d2ccd620a feat(localization): enhance course progress and review messaging across multiple languages
All checks were successful
Deploy to production / deploy (push) Successful in 2m52s
- Added new confirmation titles and messages for resetting and marking lessons as complete in admin and user interfaces.
- Expanded course flow and review scheduling messages to improve clarity and user guidance in Cebuano, German, Spanish, and English.
- Introduced a new section in the VocabCourseView to display today's recommended steps for users, enhancing the learning experience.
- Updated localization files to ensure consistent messaging and improved user engagement across all supported languages.
2026-04-02 13:49:59 +02:00
Torsten Schulz (local)
edbf22ac5b extended admin tool for finished lessons
All checks were successful
Deploy to production / deploy (push) Successful in 2m54s
2026-04-02 13:32:13 +02:00
Torsten Schulz (local)
9d663e4f2b feat(vocab): enhance lesson progress tracking and review scheduling
All checks were successful
Deploy to production / deploy (push) Successful in 2m48s
- Updated VocabService to include lessonId and lessonNumber in progress data, improving tracking accuracy.
- Modified getLessonProgress and lastProgressTouch methods to accept lesson parameters, enhancing flexibility in progress retrieval.
- Implemented formatReviewBadgeSchedule method to manage review scheduling notifications, providing clearer user feedback.
- Updated VocabCourseView to reflect changes in lesson progress handling, ensuring accurate display of review statuses and due dates.
- Expanded localization for review scheduling messages across multiple languages, enhancing user experience.
2026-04-02 13:27:24 +02:00
Torsten Schulz (local)
153914d5d2 feat(bisaya-course): add family vocabulary and exercises for relatives
All checks were successful
Deploy to production / deploy (push) Successful in 2m57s
- Introduced new content for the 'Familie - Verwandte & Stieffamilie' lesson, including learning goals, core patterns, grammar focus, speaking prompts, and practical tasks.
- Added multiple-choice and gap-fill exercises related to family vocabulary, enhancing the learning experience for users.
- Updated course content to reflect the new lesson and exercises, ensuring comprehensive coverage of family-related terms in Bisaya.
2026-04-02 09:31:46 +02:00
Torsten Schulz (local)
2272db7f91 feat(admin): add user vocab course management functionality
All checks were successful
Deploy to production / deploy (push) Successful in 2m59s
- Implemented `getUserVocabCourses` and `getVocabCourseForAdmin` methods in `AdminController` to allow admins to retrieve enrolled vocab courses for users and specific course details, respectively.
- Updated `adminRouter` to include new routes for accessing user vocab courses and course details.
- Enhanced `AdminService` with methods to list user-enrolled vocab courses and retrieve course information with lessons, ensuring proper access control.
- Improved `VocabService` to support the new functionalities, including attaching language names to course data.
- Updated UI components in `UsersView` to reflect changes, including error handling and loading states for course retrieval, along with localization updates for new features.
2026-04-02 09:21:52 +02:00
Torsten Schulz (local)
b3c8e8e210 refactor(ui): enhance AppContent layout for improved routing and styling
All checks were successful
Deploy to production / deploy (push) Successful in 3m0s
- Wrapped the router-view in a new div to manage layout and styling more effectively.
- Updated CSS to ensure proper height management and scrolling behavior, allowing for better content display.
- Added comments to clarify the purpose of layout adjustments and ensure maintainability.
2026-04-02 08:56:24 +02:00
Torsten Schulz (local)
02f3e82987 fix(ui): update styles and localization in UsersView and AppShell
All checks were successful
Deploy to production / deploy (push) Successful in 3m1s
- Added styles to ensure the app shell content occupies only the remaining space, preventing overflow issues.
- Updated the header in UsersView to reflect accurate localization for user administration.
2026-04-02 08:48:06 +02:00
Torsten Schulz (local)
c3b2c60362 feat(vocab): implement user vocab lesson progress reset functionality
All checks were successful
Deploy to production / deploy (push) Successful in 2m59s
- Added `resetUserVocabLessonProgress` method in `AdminController` to allow admins to reset a user's progress for a specific vocab lesson.
- Introduced corresponding route in `adminRouter` for the new reset functionality.
- Enhanced `VocabService` with methods to purge lesson progress for users, ensuring that only the specified lesson's progress is affected.
- Updated UI components in `UsersView` to facilitate the selection of courses and lessons for resetting progress, including confirmation dialogs and loading states.
- Added localization support for the new reset functionality across multiple languages.
- Implemented reset functionality in `VocabLessonView` for users to reset their own lesson progress.
2026-04-02 08:25:56 +02:00
Torsten Schulz (local)
13534498fa feat(user): enhance email handling with validation and normalization functions
All checks were successful
Deploy to production / deploy (push) Successful in 2m56s
- Introduced `looksLikePlausibleEmail` to validate email format, ensuring only plausible addresses are processed.
- Added `normalizeEmailCandidate` to standardize email input, returning null for invalid formats.
- Updated `decodeEncryptedBlob` to utilize normalization functions for both UTF-8 and hex formats, improving email decryption reliability.
- Adjusted `SettingsService` to ensure email is set after merging adult access state, maintaining data integrity.
2026-04-02 08:07:25 +02:00
Torsten Schulz (local)
3fb4fb92c6 refactor(ui): update layout styles for AppContent and AccountView components
Some checks failed
Deploy to production / deploy (push) Has been cancelled
- Changed layout from grid to flex in AccountView for improved responsiveness and alignment.
- Adjusted flex properties in AppContent to ensure proper scrolling behavior and height management.
- Enhanced styling for account settings panel to support better content overflow handling and visual consistency.
2026-04-02 08:05:32 +02:00
Torsten Schulz (local)
6d9d69dc10 feat(localization): expand language support and enhance UI for user settings
All checks were successful
Deploy to production / deploy (push) Successful in 3m0s
- Added support for additional UI locales including Cebuano and Spanish, improving accessibility for a broader user base.
- Updated language selection components in the AppHeader and SettingsWidget to reflect new language options, enhancing user experience.
- Enhanced localization of various UI elements across components, ensuring consistent language representation and improved user engagement.
- Implemented logic to synchronize user language preferences with backend settings, providing a seamless experience when changing languages.
2026-04-02 07:54:44 +02:00
Torsten Schulz (local)
ac5d436a36 feat(vocab): implement scheduled review management in VocabService and UI updates
All checks were successful
Deploy to production / deploy (push) Successful in 2m56s
- Added methods in VocabService to handle scheduled review states, including normalization of review dates and management of review stages.
- Enhanced lesson state management to support review scheduling, improving the learning process for users.
- Updated VocabCourseView and VocabLessonView to display review statuses and due dates, providing clearer feedback on lesson progress and review requirements.
- Introduced new UI elements to indicate review status, enhancing user engagement and understanding of lesson timelines.
2026-04-01 16:03:42 +02:00
Torsten Schulz (local)
3ff8e4fc40 refactor(bisaya-course): enhance pedagogy logic for German lessons
All checks were successful
Deploy to production / deploy (push) Successful in 2m57s
- Updated the `getGermanForBisayaLessonPedagogy` function to include lesson titles for improved didactic mode determination, specifically adding support for 'contrast_training'.
- Modified multiple scripts to utilize the updated pedagogy function, ensuring consistent application of the new logic across various course phases.
- Enhanced the VocabService to recognize and handle 'contrast_training' as a didactic mode, improving lesson management and user experience.
- Updated UI components to reflect the new didactic mode, ensuring clarity in lesson presentation.
2026-04-01 15:52:53 +02:00
Torsten Schulz (local)
10fc78e81d feat(falukant): implement score threshold logic and enhance UI feedback for certificate progression
All checks were successful
Deploy to production / deploy (push) Successful in 3m3s
- Added a new function to calculate score thresholds based on certificate levels, improving the logic for determining promotion eligibility.
- Updated the FalukantService to include new properties for score and requirement checks, enhancing the decision-making process for certificate readiness.
- Enhanced the OverviewView component to display detailed hints and states regarding certificate progression, providing users with clearer feedback on their status.
- Localized new strings in multiple languages to support the updated UI elements and hints, improving user experience across different languages.
2026-04-01 15:47:11 +02:00
Torsten Schulz (local)
d39cea2c01 refactor(vocab): unify text normalization across VocabService and VocabPracticeDialog
All checks were successful
Deploy to production / deploy (push) Successful in 3m3s
- Enhanced text normalization methods in VocabService and VocabPracticeDialog to include normalization and improved regex for punctuation and symbols, ensuring consistent text handling.
- This update streamlines the normalization process, contributing to better user input processing and overall application reliability.
2026-04-01 15:24:22 +02:00
Torsten Schulz (local)
d38231b52c refactor(vocab): improve text normalization consistency in VocabService and VocabLessonView
All checks were successful
Deploy to production / deploy (push) Successful in 2m56s
- Enhanced text normalization methods in both VocabService and VocabLessonView to include an additional trim operation, ensuring leading and trailing spaces are removed.
- This change improves the consistency and accuracy of text handling across the application, contributing to better user input processing.
2026-04-01 15:09:15 +02:00
Torsten Schulz (local)
7fee9e12d4 refactor(vocab): standardize text normalization methods in VocabService and VocabLessonView
All checks were successful
Deploy to production / deploy (push) Successful in 3m1s
- Updated text normalization logic in VocabService to use a regex that replaces punctuation with spaces, improving text handling consistency.
- Refactored VocabLessonView to utilize the new normalization method for comparable text, enhancing the accuracy of user input processing.
- Ensured both components now share a unified approach to text normalization, streamlining code and improving maintainability.
2026-04-01 14:56:39 +02:00
Torsten Schulz (local)
0421b2bc00 feat(bisaya-course): expand lesson didactics with new topics and detailed grammar focus
All checks were successful
Deploy to production / deploy (push) Successful in 2m59s
- Added new lesson topics including 'Überlebenssätze - Teil 1', 'Familien-Gespräche', 'Gefühle & Zuneigung', and 'Überlebenssätze - Teil 2' to enhance the curriculum.
- Updated existing lessons with more detailed learning goals, core patterns, grammar focus, speaking prompts, and practical tasks to improve learner engagement and comprehension.
- Refactored core patterns to include glosses for better understanding of vocabulary context.
- Enhanced the structure of lesson didactics to provide a more comprehensive learning experience for users.
2026-04-01 14:30:31 +02:00
Torsten Schulz (local)
09015b4244 feat(vocab): implement repeat queue management in VocabService and VocabLessonView
All checks were successful
Deploy to production / deploy (push) Successful in 2m59s
- Added a new method in VocabService to sanitize and manage a repeat queue for vocabulary items, enhancing the learning process.
- Updated VocabLessonView to incorporate repeat queue functionality, allowing for better tracking of vocabulary that needs review.
- Refactored existing logic to ensure seamless integration of repeat queue features, improving user experience during vocabulary lessons.
2026-04-01 14:06:34 +02:00
Torsten Schulz (local)
a3b820cea0 feat(vocab): enhance lesson state management and persistence in VocabLessonView
All checks were successful
Deploy to production / deploy (push) Successful in 3m20s
- Added a new JSONB field `lessonState` to the VocabCourseProgress model to store detailed lesson state information.
- Implemented methods in VocabService for sanitizing and serializing lesson state, ensuring robust data handling.
- Updated VocabLessonView to manage lesson state persistence, including local storage and server synchronization, improving user experience during vocabulary lessons.
- Introduced mechanisms for exporting and normalizing exercise answers, enhancing the accuracy of saved progress.
2026-04-01 11:16:56 +02:00
Torsten Schulz (local)
84adfeafb4 feat(vocab): add grammar explanations to VocabLessonView and enhance localization
All checks were successful
Deploy to production / deploy (push) Successful in 2m53s
- Introduced new sections for grammar explanations in VocabLessonView, providing users with contextual insights during vocabulary lessons.
- Added localization keys in German, English, and Spanish for the new grammar explanation titles and introductions, improving user guidance across languages.
- Updated styles for the grammar explanation section to enhance visual clarity and user experience.
2026-04-01 10:22:53 +02:00
Torsten Schulz (local)
b8e3732ef8 feat(i18n, vocab): enhance localization and vocabulary preparation in VocabLessonView
Some checks failed
Deploy to production / deploy (push) Has been cancelled
- Added new localization keys in German, English, and Spanish for exercise flow, progress, and learning path instructions, improving user guidance across languages.
- Updated VocabLessonView to incorporate these new keys, enhancing the clarity of vocabulary preparation steps and overall user experience during lessons.
- Refactored the layout to better present the learning path and vocabulary preparation stages, ensuring a more cohesive and informative interface.
2026-04-01 10:21:13 +02:00
Torsten Schulz (local)
8bbfd46ada feat(i18n): add new localization keys for lesson details and deepening sections
All checks were successful
Deploy to production / deploy (push) Successful in 2m46s
- Introduced new localization strings in German, English, and Spanish for lesson detail toggles and deepening sections, enhancing user experience across multiple languages.
- Updated VocabLessonView to incorporate these new localization keys, improving the clarity and accessibility of lesson information for users.
2026-04-01 10:05:37 +02:00
Torsten Schulz (local)
6d13965c76 feat(vocab): integrate fallback core patterns and enhance vocabulary display in VocabLessonView
All checks were successful
Deploy to production / deploy (push) Successful in 2m44s
- Added a new method in VocabService to merge core pattern glosses with fallback patterns, improving vocabulary clarity and consistency.
- Updated VocabLessonView to utilize the merged core patterns, ensuring a comprehensive vocabulary overview for users.
- Refactored vocabulary handling logic to enhance user experience during vocabulary lessons, including improved display of lesson vocabulary.
2026-04-01 09:58:32 +02:00
Torsten Schulz (local)
1328e4983e feat(vocab): enrich core patterns with glosses in VocabService and VocabLessonView
All checks were successful
Deploy to production / deploy (push) Successful in 2m54s
- Implemented a new method in VocabService to enhance core patterns with glosses derived from extracted vocabulary, improving the clarity of vocabulary entries.
- Updated the VocabLessonView to utilize the enriched core patterns, ensuring that glosses are displayed alongside vocabulary targets for better user comprehension.
- Refactored vocabulary preparation logic to integrate glosses seamlessly, enhancing the overall user experience during vocabulary lessons.
2026-04-01 09:40:33 +02:00
Torsten Schulz (local)
02b3636e10 feat(vocab-prep): implement enhanced vocabulary preparation steps in VocabLessonView
All checks were successful
Deploy to production / deploy (push) Successful in 2m58s
- Introduced a structured vocabulary preparation process with two review stages before engaging with the vocabulary trainer.
- Added localization support for new vocabulary preparation messages in German, English, and Spanish, improving accessibility for users.
- Updated the VocabLessonView component to display current progress and next steps during vocabulary preparation, enhancing user guidance.
- Refactored related logic to manage preparation stages and item navigation effectively.
2026-04-01 08:58:36 +02:00
Torsten Schulz (local)
0c89c48e68 feat(bisaya-course): restructure core patterns and enhance vocabulary preparation
All checks were successful
Deploy to production / deploy (push) Successful in 2m48s
- Updated core patterns in various scripts to use an object format with target phrases and glosses, improving clarity and usability for learners.
- Enhanced the VocabService to normalize core pattern entries, ensuring consistent handling of vocabulary data.
- Introduced new vocabulary preparation steps in the VocabLessonView, guiding users through active review processes before engaging with the vocabulary trainer.
- Added localization support for new vocabulary preparation hints and instructions in multiple languages, enhancing user experience across the application.
2026-04-01 08:12:57 +02:00
Torsten Schulz (local)
7e45049e94 refactor(VocabLessonView): combine lesson description and learning goals into a single component
All checks were successful
Deploy to production / deploy (push) Successful in 2m56s
- Merged the lesson description and learning goals sections into a unified block for improved layout and readability.
- Updated styles to enhance the visual presentation of the combined content, ensuring a more cohesive user experience.
2026-04-01 08:06:06 +02:00
Torsten Schulz (local)
1e801b33a5 refactor(AppSectionBar): update localization keys for section labels and titles
All checks were successful
Deploy to production / deploy (push) Successful in 2m58s
- Changed localization keys in AppSectionBar.vue to simplify and standardize the structure by removing the 'general' prefix.
- Updated section labels and title mappings to reflect the new key structure, enhancing consistency across the application.
2026-03-31 18:10:18 +02:00
Torsten Schulz (local)
c6caeefb5f feat(bisaya-course): enhance German course content and localization support
All checks were successful
Deploy to production / deploy (push) Successful in 2m47s
- Updated the create-german-for-bisaya-course-content.js script to improve lesson pattern retrieval by introducing a new function for generating a lesson pattern pool.
- Added new exercises for various topics including 'Wohnung & Nachbarn', 'Besuch empfangen', 'Arzt, Apotheke, Termin', and 'Amt, Dokumente, Anmeldung', enhancing practical language skills for learners.
- Improved localization by integrating translation keys for various UI elements and error messages across multiple components, ensuring a consistent user experience in both German and Bisaya.
- Enhanced the main.js file to recognize Bisaya language preferences in browser settings, improving accessibility for users.
2026-03-31 17:40:03 +02:00
Torsten Schulz (local)
b1990334b9 feat(falukant): enhance relationship state handling and director updates
All checks were successful
Deploy to production / deploy (push) Successful in 2m58s
- Updated FalukantService to return user information alongside relationship state for improved context in relationship management.
- Modified DirectorInfo component to trigger a refresh when a new director is hired, ensuring up-to-date information.
- Added socket event handling for 'directorchanged' in BranchView to manage director updates effectively, enhancing real-time responsiveness in the application.
2026-03-31 12:09:55 +02:00
Torsten Schulz (local)
3187a6e7b0 feat(vocab): add lesson and completed lesson vocab pool endpoints
All checks were successful
Deploy to production / deploy (push) Successful in 3m5s
- Implemented new endpoints in VocabController for retrieving vocab pools based on lessons and completed lessons.
- Updated vocabRouter to include routes for accessing lesson vocab pools and completed lesson vocab pools.
- Enhanced VocabService with methods to extract vocab from exercises and lesson didactics, improving vocabulary retrieval for users.
- Modified VocabPracticeDialog and VocabCourseView components to support new vocab pool functionalities, enhancing user experience in vocabulary practice.
2026-03-31 11:59:48 +02:00
Torsten Schulz (local)
01293b0102 feat(bisaya-course): update German course creation script to support default owner retrieval
All checks were successful
Deploy to production / deploy (push) Successful in 3m12s
- Modified the create-german-for-bisaya-course.js script to allow optional ownerHashedId parameter.
- Implemented a new function to find a default owner ('system' or 'admin') if no ownerHashedId is provided.
- Updated documentation to reflect the change in usage for the script.
2026-03-31 11:42:23 +02:00
Torsten Schulz (local)
0d625f1727 feat(falukant): add age information to lovers in family view
All checks were successful
Deploy to production / deploy (push) Successful in 3m7s
- Updated FalukantService to include age details for partners in relationships.
- Added translations for 'age' in English, German, and Spanish localization files.
- Enhanced FamilyView component to display age information for lovers and candidates, improving user experience.
2026-03-31 11:36:12 +02:00
Torsten Schulz (local)
db0e80a559 feat(falukant): enhance child details with other parent information and birth context
All checks were successful
Deploy to production / deploy (push) Successful in 3m2s
- Updated FalukantService to include father and mother character IDs in child relationships.
- Added logic to retrieve and display other parent details in ChildDetailsDialog and FamilyView components.
- Introduced new translations for 'other parent' and 'birth context' in English, German, and Spanish localization files.
- Enhanced UI to show other parent information and birth context in child detail views.
2026-03-31 10:29:22 +02:00
Torsten Schulz (local)
9b3898e43c refactor(deploy): update deployment workflow for production
All checks were successful
Deploy to production / deploy (push) Successful in 3m1s
- Renamed workflow from "Deploy yourpart (blue-green)" to "Deploy to production" for clarity.
- Removed environment variables for SSH configuration and replaced them with direct references to secrets for improved security.
- Updated SSH connection and deployment script steps to utilize secrets for host, port, and user, enhancing the deployment process.
2026-03-31 10:14:09 +02:00
Torsten Schulz (local)
dba9eb8692 feat(bisaya-course): enhance evening and sleep routines in course content
Some checks failed
Deploy yourpart (blue-green) / deploy (push) Failing after 0s
- Updated the Bisaya course to include new evening greetings and sleep-related phrases in core patterns.
- Added multiple exercises focusing on recognizing and using these phrases, improving practical language skills for learners.
- Expanded lesson didactics to incorporate prompts for evening and sleep routines, enriching the overall learning experience.
2026-03-31 10:13:12 +02:00
Torsten Schulz (local)
734b8f9463 feat(bisaya-course): enhance evening greetings and sleep-related exercises
All checks were successful
Deploy to production / deploy (push) Successful in 2m56s
- Updated core patterns to include evening greetings and sleep phrases in the Bisaya course content.
- Added new exercises focusing on recognizing and using evening greetings and sleep wishes, enhancing practical language use for learners.
- Expanded lesson didactics to incorporate evening and sleep-related conversational prompts, improving the overall learning experience.
2026-03-31 09:39:46 +02:00
Torsten Schulz (local)
6b3aee458a feat(bisaya-course): expand exercises for shopping, neighborhood visits, conflict resolution, and free speaking
All checks were successful
Deploy to production / deploy (push) Successful in 2m57s
- Added new exercises in multiple-choice, gap-fill, and situational response formats for categories including 'Einkaufen vertiefen', 'Nachbarschaft & Besuche', 'Rollenspiel - Konflikt und Hilfe', and 'Freies Sprechen - Alltag ohne Stütze'.
- Each exercise includes detailed instructions, question data, answer data, and explanations to enhance the learning experience for Bisaya language learners.
- Focused on practical scenarios to improve conversational skills and vocabulary retention.
2026-03-31 09:04:24 +02:00
Torsten Schulz (local)
53f0745faf feat(bisaya-course): add new exercises for various everyday scenarios
All checks were successful
Deploy to production / deploy (push) Successful in 2m44s
- Introduced multiple-choice, gap-fill, and situational response exercises across several categories including 'Besuch & Gastfreundschaft', 'Gesundheit im Alltag', 'Unterwegs & Transport', 'Kinder im Alltag', and 'Arzt & Termin'.
- Each category includes detailed instructions, question data, answer data, and explanations to enhance learning experiences.
- Expanded the vocabulary and situational context for learners to practice real-life interactions in Bisaya.
2026-03-31 08:56:36 +02:00
Torsten Schulz (local)
9a78bc7c4b feat(admin): add potential fathers retrieval for character management
All checks were successful
Deploy to production / deploy (push) Successful in 2m47s
- Implemented a new method in AdminService to fetch potential fathers for a given character based on existing relationships.
- Updated AdminController to expose this functionality via a new API endpoint.
- Enhanced adminRouter to include the route for retrieving potential fathers.
- Modified frontend components to allow selection of potential fathers during pregnancy and birth management.
- Updated internationalization files to include new translation keys related to father selection.
2026-03-31 08:50:56 +02:00
Torsten Schulz (local)
ee11a989a0 refactor(update-backend): remove temporary environment directory cleanup
All checks were successful
Deploy to production / deploy (push) Successful in 2m43s
- Eliminated the removal of the temporary environment directory in the update-backend.sh script to retain the environment for potential debugging or further use.
2026-03-31 07:50:19 +02:00
Torsten Schulz (local)
9aad42655e refactor(update-backend): clean up script by removing unnecessary newline
Some checks failed
Deploy to production / deploy (push) Failing after 1m56s
- Removed an extra newline in the update-backend.sh script to improve readability and maintainability.
2026-03-31 07:35:47 +02:00
Torsten Schulz (local)
ca33a29317 refactor(update-backend): streamline .env file handling process
Some checks failed
Deploy to production / deploy (push) Failing after 1m58s
- Replaced the previous method of copying .env files with a direct installation from a specified source path.
- Improved permissions and ownership settings for the .env file to enhance security.
- Added user feedback for successful file transfer and verification steps.
2026-03-30 16:19:00 +02:00
Torsten Schulz (local)
4ebff4dc17 fix(i18n): correct translation directions in socialnetwork.json files
All checks were successful
Deploy to production / deploy (push) Successful in 3m24s
- Updated translation keys in German, English, and Spanish localization files to accurately reflect the intended language direction for vocabulary translation.
- Adjusted the `translateTo` and `translateFrom` keys to ensure consistency across all language files.
2026-03-30 16:03:23 +02:00
Torsten Schulz (local)
3d9bca099c feat(vocab): add vocab distractor pool functionality
All checks were successful
Deploy to production / deploy (push) Successful in 3m9s
- Implemented a new endpoint to retrieve a pool of distractors for vocabulary exercises based on prior lessons.
- Updated the VocabController and VocabRouter to include the new getVocabDistractorPool method.
- Enhanced VocabService to classify questions and gather distractors from previous lessons.
- Modified VocabLessonView to fetch and utilize the distractor pool for multiple-choice exercises, improving the learning experience.
2026-03-30 15:13:10 +02:00
Torsten Schulz (local)
2b83c45e97 feat(family): enhance family view and character pregnancy handling
All checks were successful
Deploy to production / deploy (push) Successful in 2m48s
- Updated the FalukantCharacter model to include a default scope that excludes pregnancy-related fields for compatibility with older databases.
- Implemented a new method in FalukantService to conditionally retrieve pregnancy information based on database schema.
- Enhanced the FamilyView component to display a summary navigation for family relationships, including partners, children, and lovers.
- Updated internationalization files to include new translations for family-related terms and summaries.
2026-03-30 14:36:02 +02:00
Torsten Schulz (local)
f35db4b1a1 feat(update-backend): add .env file handling from live system
All checks were successful
Deploy to production / deploy (push) Successful in 5m35s
- Implemented functionality to copy the .env file from the live system to the backend directory if it exists.
- Added ownership change for the copied .env file to ensure proper permissions.
- Included user feedback for successful or failed .env file transfer.
2026-03-30 14:16:15 +02:00
Torsten Schulz (local)
c52d4b60f9 feat(admin): implement pregnancy and birth management features
Some checks failed
Deploy to production / deploy (push) Failing after 2m6s
- Added new admin functionalities to force pregnancy, clear pregnancy, and trigger birth for characters.
- Introduced corresponding routes and controller methods in adminRouter and adminController.
- Enhanced the FalukantCharacter model to include pregnancy-related fields.
- Created database migration for adding pregnancy columns to the character table.
- Updated frontend views and internationalization files to support new pregnancy and birth management features.
- Improved user feedback and error handling for these new actions.
2026-03-30 13:44:43 +02:00
Torsten Schulz (local)
b2591da428 refactor(update scripts): enhance directory handling and error management
Some checks failed
Deploy to production / deploy (push) Failing after 1m46s
- Updated update-backend.sh and update-frontend.sh to accept a target directory as a parameter, improving flexibility.
- Refactored paths to use the target directory for all operations, ensuring consistency and reducing hardcoded values.
- Added error handling with 'set -euo pipefail' for better script reliability.
- Improved user feedback by displaying the target directory during updates.
2026-03-30 13:19:11 +02:00
Torsten Schulz (local)
5001292616 fix(deploy.yml): update deployment script path for blue-green deployment
Some checks failed
Deploy to production / deploy (push) Failing after 1m55s
- Changed the deployment script path from 'actualize-yourpart.sh' to 'deploy-yourpart-bluegreen.sh' to reflect the new deployment strategy.
2026-03-30 11:48:49 +02:00
Torsten Schulz (local)
8f461d4dba fix(deploy.yml): enhance SSH connection testing for deployment
All checks were successful
Deploy to production / deploy (push) Successful in 1m8s
- Added options to disable strict host key checking and enable batch mode for improved SSH connection reliability during deployment.
2026-03-30 11:40:00 +02:00
Torsten Schulz (local)
ed4c4e1b40 refactor(deploy.yml): improve SSH deployment process
Some checks failed
Deploy to production / deploy (push) Has been cancelled
- Added steps to prepare SSH keys and known hosts for secure connections.
- Implemented a test for the SSH connection to ensure successful deployment.
- Updated the deployment step to use SSH for executing the deployment script on the production server.
2026-03-30 11:35:34 +02:00
Torsten Schulz (local)
4ed9ea39a0 refactor(update.sh): enhance script functionality and user feedback
Some checks failed
Deploy to production / deploy (push) Failing after 1s
- Improved error handling to ensure the script exits on failure.
- Added a check for the current branch to ensure it is 'main' before proceeding.
- Updated git commands for better state management and clarity in user feedback messages.
2026-03-30 11:32:51 +02:00
Torsten Schulz (local)
0749c733a4 refactor(update.sh): improve update script reliability and clarity
Some checks failed
Deploy to production / deploy (push) Failing after 1s
- Changed shebang to use env for better portability.
- Added error handling to ensure the script exits on failure.
- Included a check to confirm the current branch is 'main' before proceeding.
- Updated git commands to fetch and reset to origin/main, ensuring a clean state.
- Enhanced user feedback messages for clarity.
2026-03-30 10:51:57 +02:00
Torsten Schulz (local)
966c73dda9 style(AppSectionBar): enhance layout and styling for improved visual presentation
- Updated .app-section-bar__copy to use flexbox for better alignment and spacing.
- Added gap and flex-wrap properties to improve layout responsiveness.
- Adjusted styles in .app-section-bar__eyebrow for a more cohesive design.
2026-03-30 10:45:49 +02:00
Torsten Schulz (local)
34f22229bb refactor(StatusBar): improve layout and styling for better user experience
- Removed unnecessary label from character name display for a cleaner look.
- Adjusted grid layout and spacing in the StatusBar for enhanced alignment and responsiveness.
- Refined padding and gap settings to optimize visual presentation of status elements.
2026-03-30 10:40:57 +02:00
Torsten Schulz (local)
05b91284fa refactor(StatusBar, PoliticsView): update UI elements and improve layout
- Modified StatusBar component to enhance character name display with a label suffix for clarity.
- Refactored styles in StatusBar for better alignment and spacing, improving overall visual presentation.
- Removed the PoliticsView hero section to streamline the layout and focus on tab content, enhancing user navigation.
2026-03-30 10:36:55 +02:00
Torsten Schulz (local)
c531ae2caf feat(StatusBar, falukantService): enhance character information display and UI structure
- Updated falukantService to include predefined first and last names in character data retrieval.
- Refactored StatusBar component layout to improve organization and visual clarity, introducing sections for identity and stats.
- Enhanced character name handling to prioritize display names, ensuring a more dynamic user experience.
2026-03-30 10:31:59 +02:00
Torsten Schulz (local)
028da5a9f0 feat(MessagesDialog, StatusBar, i18n): enhance character death notifications and UI updates
- Added support for displaying character death notifications with detailed information including age, region, and relationships in both German and English.
- Updated MessagesDialog to format additional parameters such as region, spouses, children, and lovers based on the locale.
- Enhanced StatusBar to show the character's name dynamically, improving user experience.
- Modified i18n files to include new notification messages for character deaths, ensuring accurate translations for both languages.
2026-03-30 10:25:54 +02:00
Torsten Schulz (local)
10d8ee015c feat(VocabLessonView): improve vocab question generation and answer validation
- Introduced a new method to retrieve equivalent vocabulary answers based on user prompts and direction, enhancing the accuracy of answer options.
- Updated the buildChoiceOptions method to handle multiple correct answers and ensure the prompt is excluded from choices.
- Enhanced the current vocab question structure to support multiple acceptable answers, improving user feedback and engagement during exercises.
2026-03-30 08:45:59 +02:00
Torsten Schulz (local)
c9a7619737 feat(VocabLessonView): refine vocab trainer experience and improve user guidance
- Updated vocab trainer descriptions and button labels to better reflect user progress and encourage engagement.
- Enhanced visual indicators for exercise availability and review priorities, clarifying the learning path for users.
- Introduced new methods to calculate trainer targets and review blending, optimizing the learning experience based on user performance.
2026-03-28 23:41:25 +01:00
Torsten Schulz (local)
a2c86247b6 feat(VocabLessonView): enhance exercise access and vocab trainer experience
- Added conditional access to exercises based on user progress and previous vocab completion.
- Introduced visual indicators for exercise availability and review priorities to guide users.
- Updated vocab trainer descriptions and button labels to reflect the user's current state and encourage engagement.
- Implemented methods to manage exercise unlock conditions and improve user feedback on progress.
2026-03-28 13:11:05 +01:00
Torsten Schulz (local)
8a96951b50 feat(vocabService): enhance answer validation for multiple choice exercises
- Improved answer validation logic by expanding multiple choice answer data based on lesson context.
- Added methods to extract indices of correct answers and normalize question prompts for better accuracy.
- Refactored existing answer checking to accommodate new data handling, ensuring robust evaluation of user responses.
2026-03-28 12:46:34 +01:00
Torsten Schulz (local)
71e120bf20 feat(socialnetwork): enhance folder and video management with user visibility options
- Added functionality to manage selected users for adult folders and erotic videos, allowing for more granular visibility control.
- Introduced new endpoints and methods in the SocialNetworkController and SocialNetworkService to handle selected users.
- Updated the frontend components to include input fields for selected users in CreateFolderDialog, EditImageDialog, and EroticPicturesView.
- Enhanced the routing to support fetching erotic folders and videos by username, improving user experience in profile views.
2026-03-27 16:56:45 +01:00
Torsten Schulz (local)
39032570e3 fix(i18n): correct German translations for video upload hints and descriptions
- Updated the German translations in socialnetwork.json for improved accuracy and clarity.
- Enhanced the text in EroticPicturesView.vue and EroticVideosView.vue to ensure proper spelling and grammar in user-facing messages.
2026-03-27 16:29:18 +01:00
Torsten Schulz (local)
9e8f8e8077 refactor(i18n): update video management texts for clarity and consistency
- Revised the introductory texts and hints for video uploads in German, English, and Spanish to enhance user understanding.
- Improved descriptions to better reflect the functionality of the video management features.
- Removed outdated statistics display from the EroticVideosView component to streamline the user interface.
2026-03-27 15:43:15 +01:00
Torsten Schulz (local)
e76be33743 refactor(EroticVideosView): restructure layout and enhance video statistics display
- Updated the layout of the EroticVideosView component to improve organization and user experience.
- Introduced a sidebar for video upload and management, separating it from the video list.
- Added statistics for total, visible, and hidden videos to provide users with better insights.
- Enhanced form elements and labels for clarity and usability during video uploads.
2026-03-27 15:36:00 +01:00
Torsten Schulz (local)
6cbcf9d95f style(DialogWidget): update dialog overlay class for improved modal handling
- Changed the class binding for the dialog overlay to differentiate between modal and non-modal states, enhancing visual clarity.
- Introduced a new class for non-modal dialogs to ensure proper positioning and z-index management.
2026-03-27 15:21:57 +01:00
Torsten Schulz (local)
31a96aaf60 style(DialogWidget): remove backdrop filter for non-modal overlay
- Updated the non-modal dialog overlay to have no backdrop filter, enhancing visual clarity.
- Set the modal property to false in MessagesDialog to ensure proper dialog behavior.
2026-03-27 15:17:18 +01:00
Torsten Schulz (local)
8a1ff52a61 feat(chat): enhance room entry announcement logic in MultiChatDialog.vue
- Introduced a new property `lastAnnouncedRoomName` to track the last announced room, preventing redundant announcements.
- Updated the room entry announcement logic to ensure messages are sent only when entering a new room, improving clarity for users.
2026-03-27 15:10:38 +01:00
Torsten Schulz (local)
84c598bf52 feat(chat): initialize current room name in MultiChatDialog.vue
- Added a new property `currentRoomName` to track the name of the currently selected room.
- Updated the initialization logic to reset `currentRoomName` when rooms are loaded and when the chat is disconnected, ensuring accurate room state management.
2026-03-27 15:03:32 +01:00
Torsten Schulz (local)
291e79c41f fix(chat): improve room announcement logic in MultiChatDialog.vue
- Enhanced the logic for announcing room entries, particularly in adult-only mode, to ensure accurate messaging based on room selection.
- Added checks to prevent redundant announcements and ensure proper synchronization with the selected room.
2026-03-27 14:46:08 +01:00
Torsten Schulz (local)
3b823420e6 feat(chat): implement room synchronization for adult-only mode in MultiChatDialog.vue
- Added logic to handle room synchronization when entering a chat in adult-only mode.
- Introduced a flag to manage ongoing room sync requests, improving user experience during room transitions.
2026-03-27 14:37:49 +01:00
Torsten Schulz (local)
674c4d0b69 refactor(chat): simplify adult-only mode checks in MultiChatDialog.vue
- Removed redundant checks for adult-only mode when opening the chat dialog and connecting the chat socket.
- Streamlined the logic to enhance readability and maintainability of the component.
2026-03-27 14:29:00 +01:00
Torsten Schulz (local)
9f3facbb3f chore(dependencies): update package-lock.json and package.json for dependency versions
- Upgraded @emnapi/runtime to version 1.9.1 and @img/colour to version 1.1.0 in package-lock.json.
- Added new dependencies for ansi-regex and strip-ansi with updated versions in multiple locations.
- Updated lodash types to version 4.17.24 and ansi-escapes to version 7.3.0.
- Introduced overrides for minimatch and tmp in package.json to ensure consistent behavior across environments.
- Refactored MultiChatDialog.vue to improve room selection logic and handle adult-only mode more effectively.
2026-03-27 14:05:29 +01:00
Torsten Schulz (local)
07604cc9fa feat(navigation): enhance adult verification handling and notifications
- Updated navigationController to simplify the eroticChat menu structure.
- Enhanced adminService to notify users of adult verification status changes, including previous status.
- Improved AppNavigation and related components to register and unregister socket listeners for adult verification updates.
- Added localized messages for adult verification notifications in English, German, and Spanish.
- Introduced a verification hint in the EroticAccessView to guide users on document submission.
2026-03-27 13:23:44 +01:00
Torsten Schulz (local)
82223676a6 style(admin): update button styles in AdultVerificationView and EroticModerationView
- Added new styles for buttons in the adult verification filters to enhance appearance and usability.
- Set minimum height, padding, and font size for consistency across both views.
2026-03-27 11:25:56 +01:00
Torsten Schulz (local)
207ef6266a feat(deploy): add adult verification directory creation and permissions setup
- Enhanced deploy-backend.sh and update-backend.sh to create the adult verification directory under /opt/yourpart-data.
- Updated permissions for the new directory to ensure proper access control.
- Refactored file path handling in AdminService and SettingsService to utilize the new directory structure for adult verification files.
2026-03-27 11:24:21 +01:00
Torsten Schulz (local)
02837c7b73 refactor(admin): restructure adult verification and erotic moderation views for improved layout
- Updated the AdultVerificationView and EroticModerationView components to utilize a new layout structure with content scrolling and hidden overflow for better user experience.
- Adjusted styles in styles.scss to support the new layout, ensuring proper height and overflow handling for content sections.
2026-03-27 11:18:42 +01:00
Torsten Schulz (local)
25b658acce feat(admin): enhance adult verification file handling and localization
- Added a new method in AdminService to resolve adult verification file paths, improving file retrieval logic.
- Updated the AdminVerificationView to display a message when the verification document is missing.
- Localized the missing document message in German, English, and Spanish for better user experience.
2026-03-27 11:08:52 +01:00
Torsten Schulz (local)
0f0c102ded fix(admin): add 'adult_verification_request' to user parameter type filtering
- Updated the filtering criteria in the AdminService to include 'adult_verification_request' in the user parameter type query, enhancing data retrieval for adult verification processes.
2026-03-27 10:57:16 +01:00
Torsten Schulz (local)
26eb7b8ce7 feat(admin): add document preview functionality to adult verification
- Implemented a preview section for adult verification documents, allowing users to view images and PDFs inline.
- Added localization support for preview titles and messages in German, English, and Spanish.
- Enhanced the component's state management to handle preview visibility and cleanup.
2026-03-27 10:50:28 +01:00
Torsten Schulz (local)
0dd2bce5d1 fix(settings): streamline settings type creation in settingsService and initialization
- Refactored settingsService to use findOrCreate for settings type, improving efficiency and error handling.
- Added initialization for 'account' settings type in initializeSettings, ensuring all necessary settings are created during setup.
2026-03-27 10:43:44 +01:00
Torsten Schulz (local)
cf6d72385e fix(settings): enhance user parameter handling and add special user parameter types
- Introduced a new method to ensure special user parameter types for adult verification settings, improving data integrity and handling.
- Updated the upsertUserParam method to utilize the new special parameter type handling, ensuring robust user parameter management.
- Updated package dependencies in package.json and package-lock.json for consistency and to address potential vulnerabilities.
2026-03-27 10:38:42 +01:00
Torsten Schulz (local)
1a86061680 fix(dependencies): update @gltf-transform packages and sequelize-cli version
- Upgraded @gltf-transform/cli, core, extensions, and functions to version 4.3.0 for improved functionality and compatibility.
- Updated sequelize-cli to version 6.6.5 to address potential vulnerabilities.
- Added an overrides section in package.json for minimatch to ensure consistent behavior across environments.
- Refactored the EroticAccessView.vue component for improved structure and readability.
2026-03-27 10:23:25 +01:00
Torsten Schulz (local)
e13deb0720 fix(api): improve 404 handling for unknown API routes
- Refactored the 404 response for API routes to ensure it only triggers for paths starting with '/api/', enhancing clarity in error handling.
- Updated package dependencies in package.json and package-lock.json to maintain version consistency and address potential vulnerabilities.
2026-03-27 09:52:24 +01:00
Torsten Schulz (local)
21072139f7 fix(cors): refine CORS handling for OPTIONS requests
- Updated CORS middleware to explicitly handle OPTIONS requests, ensuring proper preflight response and improving API request handling.
2026-03-27 09:49:54 +01:00
Torsten Schulz (local)
1878b2a8c7 fix(router): refine stock and inventory routes for improved clarity
- Updated stock and inventory routes to explicitly define branchId as a required parameter, enhancing API usability and consistency in request handling.
2026-03-27 09:39:51 +01:00
Torsten Schulz (local)
6563ca23c7 fix(router): update stock and inventory routes to support optional branchId parameter
- Modified the stock and inventory routes to allow an optional branchId parameter, improving flexibility in API requests.
2026-03-27 09:24:05 +01:00
Torsten Schulz (local)
085333db29 fix(deploy): update package-lock.json for consistent npm ci deployments
- Removed package-lock.json from .gitignore to ensure it is tracked.
- Added locks for backend, frontend, and repo root to maintain version consistency during deployments with npm ci.
- Updated backend with a new script for lockfile synchronization and added a description note.
2026-03-27 09:20:37 +01:00
Torsten Schulz (local)
17325a5263 fix(deploy): package-lock.json versionieren für npm ci
- Zeile **/package-lock.json aus .gitignore entfernt; Locks zu backend,
  frontend und Repo-Root hinzugefügt, damit Deploy-Skripte mit npm ci
  dieselben Versionen wie package.json installieren.
- backend: Script lockfile:sync und Hinweis in description.

Made-with: Cursor
2026-03-27 09:16:17 +01:00
393 changed files with 67980 additions and 2917 deletions

0
.codex Normal file
View File

View File

@@ -0,0 +1,9 @@
---
description: C++-Worker unter src/ sind obsolet — nicht erweitern oder als Quelle für Spiellogik nutzen
alwaysApply: true
---
# Legacy C++ (`src/`)
- Verzeichnis **`src/`** (C++-Worker, WebSocket-Server): **obsolet**. Keine neuen Features, keine fachlichen Fixes dort planen oder umsetzen, sofern der Nutzer nicht ausdrücklich etwas anderes verlangt.
- Falukant-Hintergrundlogik: **Backend** (`backend/`), **externer Daemon**, **Frontend** — siehe `docs/LEGACY_CPP_WORKERS.md`.

121
.gitea/workflows/deploy.yml Normal file
View File

@@ -0,0 +1,121 @@
name: Deploy to production
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Detect vocab course changes
id: vocab_course_changes
shell: bash
run: |
set -euo pipefail
BASE="${{ gitea.event.before }}"
HEAD="${{ gitea.sha }}"
if [ -z "$BASE" ] || [[ "$BASE" =~ ^0+$ ]] || ! git cat-file -e "$BASE^{commit}" 2>/dev/null; then
BASE="HEAD~1"
fi
git diff --name-only "$BASE" "$HEAD" > changed-files.txt
cat changed-files.txt
COMMIT_MESSAGE="$(git log -1 --pretty=%B "$HEAD" || true)"
if echo "$COMMIT_MESSAGE" | grep -qi '\[force-deploy\]'; then
echo "force_deploy=true" >> "$GITHUB_OUTPUT"
else
echo "force_deploy=false" >> "$GITHUB_OUTPUT"
fi
if grep -E '^(backend/scripts/.*(bisaya|course|didactics|vocab)|backend/sql/.*vocab|backend/(migrations-active|migrations-archive)/.*vocab|docs/.*(COURSE|VOCAB|BISAYA|GERMAN_FOR_BISAYA))' changed-files.txt; then
echo "changed=true" >> "$GITHUB_OUTPUT"
else
echo "changed=false" >> "$GITHUB_OUTPUT"
fi
if grep -E '^frontend/' changed-files.txt >/dev/null; then
echo "frontend_changed=true" >> "$GITHUB_OUTPUT"
else
echo "frontend_changed=false" >> "$GITHUB_OUTPUT"
fi
if grep -E '^backend/' changed-files.txt \
| grep -Ev '^(backend/scripts/.*(bisaya|course|didactics|vocab)|backend/sql/.*vocab|backend/(migrations-active|migrations-archive)/.*vocab)$' >/dev/null; then
echo "backend_app_changed=true" >> "$GITHUB_OUTPUT"
else
echo "backend_app_changed=false" >> "$GITHUB_OUTPUT"
fi
# App-Code-Änderungen, die einen echten Deploy benötigen
# (Frontend oder Backend außerhalb reiner Kurs-/Dokument-Sync-Dateien)
if grep -E '^(frontend/|backend/)' changed-files.txt \
| grep -Ev '^(backend/scripts/.*(bisaya|course|didactics|vocab)|backend/sql/.*vocab|backend/(migrations-active|migrations-archive)/.*vocab|docs/.*(COURSE|VOCAB|BISAYA|GERMAN_FOR_BISAYA))'; then
echo "app_changed=true" >> "$GITHUB_OUTPUT"
else
echo "app_changed=false" >> "$GITHUB_OUTPUT"
fi
- name: Prepare SSH
run: |
mkdir -p ~/.ssh
printf "%s" "${{ secrets.PROD_SSH_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -p "${{ secrets.PROD_PORT }}" "${{ secrets.PROD_HOST }}" >> ~/.ssh/known_hosts
- name: Test SSH connection
run: |
ssh -i ~/.ssh/id_ed25519 \
-o StrictHostKeyChecking=no \
-o BatchMode=yes \
-p "${{ secrets.PROD_PORT }}" \
"${{ secrets.PROD_USER }}@${{ secrets.PROD_HOST }}" \
"echo SSH OK"
- name: Run deployment script
if: steps.vocab_course_changes.outputs.app_changed == 'true' || steps.vocab_course_changes.outputs.force_deploy == 'true'
run: |
DEPLOY_FLAGS=""
if [ "${{ steps.vocab_course_changes.outputs.force_deploy }}" = "true" ]; then
DEPLOY_FLAGS=""
elif [ "${{ steps.vocab_course_changes.outputs.backend_app_changed }}" = "true" ] && [ "${{ steps.vocab_course_changes.outputs.frontend_changed }}" != "true" ]; then
DEPLOY_FLAGS="--skip-frontend"
elif [ "${{ steps.vocab_course_changes.outputs.frontend_changed }}" = "true" ] && [ "${{ steps.vocab_course_changes.outputs.backend_app_changed }}" != "true" ]; then
DEPLOY_FLAGS="--skip-backend"
fi
DEPLOY_TARGET="${{ secrets.PROD_DEPLOY_TARGET }}"
if [ -z "$DEPLOY_TARGET" ]; then
DEPLOY_TARGET="/opt/yourpart-green"
fi
echo "Deploy-Flags: ${DEPLOY_FLAGS:-<none>}"
echo "Deploy-Target: $DEPLOY_TARGET"
ssh -i ~/.ssh/id_ed25519 \
-p "${{ secrets.PROD_PORT }}" \
"${{ secrets.PROD_USER }}@${{ secrets.PROD_HOST }}" \
"/home/tsschulz/deploy-yourpart-bluegreen.sh ${DEPLOY_TARGET} ${DEPLOY_FLAGS}"
- name: Skip full deployment (no app changes)
if: steps.vocab_course_changes.outputs.app_changed != 'true' && steps.vocab_course_changes.outputs.force_deploy != 'true'
run: |
echo "Kein Full-Deploy: Es wurden keine Frontend/Backend-App-Dateien geändert."
- name: Sync vocab course content
if: steps.vocab_course_changes.outputs.changed == 'true' || steps.vocab_course_changes.outputs.force_deploy == 'true'
run: |
ssh -i ~/.ssh/id_ed25519 \
-p "${{ secrets.PROD_PORT }}" \
"${{ secrets.PROD_USER }}@${{ secrets.PROD_HOST }}" \
"cd /opt/yourpart && npm --prefix backend run sync:vocab-courses"

5
.gitignore vendored
View File

@@ -5,7 +5,7 @@
.depbe.sh
node_modules
node_modules/*
**/package-lock.json
# package-lock.json wird versioniert (npm ci im Deploy braucht konsistente Locks zu package.json)
backend/.env
backend/.env.local
backend/images
@@ -17,6 +17,9 @@ frontend/node_modules
frontend/node_modules/*
frontend/dist
frontend/dist/*
frontend/scripts/.i18n-de-fr-cache.json
frontend/scripts/.falukant-fr-smooth-cache.json
frontend/ceb-locale-audit-report.json
frontedtree.txt
backend/dist/
backend/data/model-cache

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
## zum testen des push
Hinweis: Das Verzeichnis **`src/`** (C++-Worker) ist veraltet; siehe [`docs/LEGACY_CPP_WORKERS.md`](docs/LEGACY_CPP_WORKERS.md).

View File

@@ -3,7 +3,7 @@
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`.
- A SQL migration was added: `backend/migrations-archive/20260101000000-add-tax-percent-to-region.cjs`.
- It adds `tax_percent` numeric NOT NULL DEFAULT 7 to `falukant_data.region`.
Runtime configuration

View File

@@ -23,6 +23,7 @@ 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 moderationRouter from './routers/moderationRouter.js';
import cors from 'cors';
import './jobs/sessionCleanup.js';
@@ -83,7 +84,12 @@ const corsOptions = {
};
app.use(cors(corsOptions));
app.options('*', cors(corsOptions));
app.use((req, res, next) => {
if (req.method === 'OPTIONS') {
return cors(corsOptions)(req, res, next);
}
return next();
});
app.use(express.json()); // To handle JSON request bodies
app.use('/api/chat', chatRouter);
@@ -100,6 +106,7 @@ app.use('/api/contact', contactRouter);
app.use('/api/socialnetwork', socialnetworkRouter);
app.use('/api/vocab', vocabRouter);
app.use('/api/forum', forumRouter);
app.use('/api/moderation', moderationRouter);
app.use('/api/falukant', falukantRouter);
app.use('/api/friendships', friendshipRouter);
app.use('/api/models', modelsProxyRouter);
@@ -124,6 +131,11 @@ app.get(/^\/(?!api\/).*/, (req, res) => {
});
// Fallback 404 for unknown API routes
app.use('/api/*', (req, res) => res.status(404).send('404 Not Found'));
app.use((req, res, next) => {
if (req.path.startsWith('/api/')) {
return res.status(404).send('404 Not Found');
}
return next();
});
export default app;

View File

@@ -0,0 +1,33 @@
const path = require('path');
const dotenv = require('dotenv');
const envPath = process.env.SEQUELIZE_ENV_FILE
? path.resolve(process.cwd(), process.env.SEQUELIZE_ENV_FILE)
: path.resolve(process.cwd(), '.env');
dotenv.config({ path: envPath });
const dialectOptions = {};
if (process.env.DB_SSL === '1' || process.env.PGSSLMODE === 'require') {
dialectOptions.ssl = process.env.DB_SSL_REJECT_UNAUTHORIZED === '0'
? { rejectUnauthorized: false }
: true;
}
// pg/SCRAM: password muss ein String sein; bei fehlender .env sonst undefined-Fallen vermeiden
const shared = {
username: process.env.DB_USER != null ? String(process.env.DB_USER) : undefined,
password: process.env.DB_PASS != null ? String(process.env.DB_PASS) : '',
database: process.env.DB_NAME != null ? String(process.env.DB_NAME) : undefined,
host: process.env.DB_HOST || '127.0.0.1',
port: Number.parseInt(process.env.DB_PORT || '5432', 10),
dialect: 'postgres',
logging: false,
dialectOptions
};
module.exports = {
development: shared,
test: shared,
production: shared
};

View File

@@ -13,6 +13,11 @@ class AdminController {
this.searchUser = this.searchUser.bind(this);
this.getFalukantUserById = this.getFalukantUserById.bind(this);
this.changeFalukantUser = this.changeFalukantUser.bind(this);
this.adminForceFalukantPregnancy = this.adminForceFalukantPregnancy.bind(this);
this.adminClearFalukantPregnancy = this.adminClearFalukantPregnancy.bind(this);
this.adminForceFalukantBirth = this.adminForceFalukantBirth.bind(this);
this.adminCleanupCharacterDeathArtifacts = this.adminCleanupCharacterDeathArtifacts.bind(this);
this.adminGetPotentialFathersForCharacter = this.adminGetPotentialFathersForCharacter.bind(this);
this.getFalukantUserBranches = this.getFalukantUserBranches.bind(this);
this.updateFalukantStock = this.updateFalukantStock.bind(this);
this.addFalukantStock = this.addFalukantStock.bind(this);
@@ -29,6 +34,10 @@ class AdminController {
this.getUser = this.getUser.bind(this);
this.getUsers = this.getUsers.bind(this);
this.updateUser = this.updateUser.bind(this);
this.resetUserVocabLessonProgress = this.resetUserVocabLessonProgress.bind(this);
this.markUserVocabLessonsCompleteThrough = this.markUserVocabLessonsCompleteThrough.bind(this);
this.getUserVocabCourses = this.getUserVocabCourses.bind(this);
this.getVocabCourseForAdmin = this.getVocabCourseForAdmin.bind(this);
this.getAdultVerificationRequests = this.getAdultVerificationRequests.bind(this);
this.setAdultVerificationStatus = this.setAdultVerificationStatus.bind(this);
this.getAdultVerificationDocument = this.getAdultVerificationDocument.bind(this);
@@ -45,6 +54,9 @@ class AdminController {
// Statistics
this.getUserStatistics = this.getUserStatistics.bind(this);
this.getFalukantRegions = this.getFalukantRegions.bind(this);
this.getFalukantAllRegions = this.getFalukantAllRegions.bind(this);
this.getFalukantRegionTypes = this.getFalukantRegionTypes.bind(this);
this.createFalukantRegion = this.createFalukantRegion.bind(this);
this.updateFalukantRegionMap = this.updateFalukantRegionMap.bind(this);
this.getRegionDistances = this.getRegionDistances.bind(this);
this.upsertRegionDistance = this.upsertRegionDistance.bind(this);
@@ -125,6 +137,77 @@ class AdminController {
}
}
async resetUserVocabLessonProgress(req, res) {
const schema = Joi.object({
lessonId: Joi.number().integer().positive().required()
});
const { error, value } = schema.validate(req.body || {});
if (error) {
return res.status(400).json({ error: error.details[0].message });
}
try {
const { userid: requester } = req.headers;
const { id } = req.params;
const result = await AdminService.adminResetUserVocabLessonProgress(requester, id, value.lessonId);
res.status(200).json(result);
} catch (err) {
const status = err.message === 'noaccess' ? 403 : (err.message === 'lessonnotfound' ? 404 : 500);
res.status(status).json({ error: err.message });
}
}
async markUserVocabLessonsCompleteThrough(req, res) {
const schema = Joi.object({
courseId: Joi.number().integer().positive().required(),
throughLessonNumber: Joi.number().integer().positive().required()
});
const { error, value } = schema.validate(req.body || {});
if (error) {
return res.status(400).json({ error: error.details[0].message });
}
try {
const { userid: requester } = req.headers;
const { id } = req.params;
const result = await AdminService.adminMarkUserVocabLessonsCompleteThrough(
requester,
id,
value.courseId,
value.throughLessonNumber
);
res.status(200).json(result);
} catch (err) {
let status = 500;
if (err.message === 'noaccess') status = 403;
else if (err.message === 'notenrolled') status = 403;
else if (err.message === 'badrequest') status = 400;
res.status(status).json({ error: err.message });
}
}
async getUserVocabCourses(req, res) {
try {
const { userid: requester } = req.headers;
const { id } = req.params;
const result = await AdminService.adminListUserEnrolledVocabCourses(requester, id);
res.status(200).json(result);
} catch (err) {
const status = err.message === 'noaccess' ? 403 : (err.message === 'notfound' ? 404 : 500);
res.status(status).json({ error: err.message });
}
}
async getVocabCourseForAdmin(req, res) {
try {
const { userid: requester } = req.headers;
const { courseId } = req.params;
const result = await AdminService.adminGetVocabCourseWithLessons(requester, courseId);
res.status(200).json(result);
} catch (err) {
const status = err.message === 'noaccess' ? 403 : (err.message === 'coursenotfound' ? 404 : 500);
res.status(status).json({ error: err.message });
}
}
async getAdultVerificationRequests(req, res) {
try {
const { userid: requester } = req.headers;
@@ -372,6 +455,77 @@ class AdminController {
}
}
async adminForceFalukantPregnancy(req, res) {
try {
const { userid: userId } = req.headers;
const { characterId, fatherCharacterId, dueInDays } = req.body;
const response = await AdminService.adminForceFalukantPregnancy(userId, characterId, {
fatherCharacterId,
dueInDays,
});
res.status(200).json(response);
} catch (error) {
console.log(error);
res.status(400).json({ error: error.message });
}
}
async adminGetPotentialFathersForCharacter(req, res) {
try {
const { userid: userId } = req.headers;
const { characterId } = req.params;
const response = await AdminService.adminGetPotentialFathersForCharacter(userId, characterId);
res.status(200).json(response);
} catch (error) {
console.log(error);
res.status(400).json({ error: error.message });
}
}
async adminClearFalukantPregnancy(req, res) {
try {
const { userid: userId } = req.headers;
const { characterId } = req.body;
const response = await AdminService.adminClearFalukantPregnancy(userId, characterId);
res.status(200).json(response);
} catch (error) {
console.log(error);
res.status(400).json({ error: error.message });
}
}
async adminForceFalukantBirth(req, res) {
try {
const { userid: userId } = req.headers;
const { motherCharacterId, fatherCharacterId, birthContext, legitimacy, gender } = req.body;
const response = await AdminService.adminForceFalukantBirth(userId, motherCharacterId, {
fatherCharacterId,
birthContext,
legitimacy,
gender,
});
res.status(200).json(response);
} catch (error) {
console.log(error);
res.status(400).json({ error: error.message });
}
}
async adminCleanupCharacterDeathArtifacts(req, res) {
try {
const { userid: userId } = req.headers;
const { characterId } = req.params;
const response = await AdminService.adminCleanupCharacterDeathArtifacts(userId, characterId);
res.status(200).json(response);
} catch (error) {
console.log(error);
const status = error.message === 'noaccess'
? 403
: (['invalidCharacter', 'notfound', 'targetnotdead'].includes(error.message) ? 400 : 500);
res.status(status).json({ error: error.message });
}
}
async getFalukantUserBranches(req, res) {
try {
const { userid: userId } = req.headers;
@@ -432,6 +586,42 @@ class AdminController {
}
}
async getFalukantAllRegions(req, res) {
try {
const { userid: userId } = req.headers;
const regions = await AdminService.getFalukantAllRegions(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 getFalukantRegionTypes(req, res) {
try {
const { userid: userId } = req.headers;
const types = await AdminService.getFalukantRegionTypes(userId);
res.status(200).json(types);
} catch (error) {
console.log(error);
const status = error.message === 'noaccess' ? 403 : 500;
res.status(status).json({ error: error.message });
}
}
async createFalukantRegion(req, res) {
try {
const { userid: userId } = req.headers;
const created = await AdminService.createFalukantRegion(userId, req.body || {});
res.status(200).json(created);
} catch (error) {
console.log(error);
const status = error.message === 'noaccess' ? 403 : 400;
res.status(status).json({ error: error.message });
}
}
async updateFalukantRegionMap(req, res) {
try {
const { userid: userId } = req.headers;

View File

@@ -29,6 +29,8 @@ class AuthController {
} catch (error) {
if (error.message === 'credentialsinvalid') {
res.status(404).json({ error: error.message });
} else if (error.message === 'userblocked') {
res.status(403).json({ error: error.message });
} else {
res.status(500).json({ error: error.message });
}

View File

@@ -16,6 +16,7 @@ class ChatController {
this.getRoomCreateOptions = this.getRoomCreateOptions.bind(this);
this.getOwnRooms = this.getOwnRooms.bind(this);
this.deleteOwnRoom = this.deleteOwnRoom.bind(this);
this.reportChatIncident = this.reportChatIncident.bind(this);
}
async getMessages(req, res) {
@@ -215,6 +216,32 @@ class ChatController {
res.status(status).json({ error: error.message });
}
}
async reportChatIncident(req, res) {
const schema = Joi.object({
context: Joi.string().valid('random_chat', 'multi_chat', 'one_to_one').required(),
reporterHashedId: Joi.string().allow('', null),
reporterRandomId: Joi.string().allow('', null),
reporterUsername: Joi.string().allow('', null),
offenderHashedId: Joi.string().allow('', null),
offenderRandomId: Joi.string().allow('', null),
offenderUsername: Joi.string().allow('', null),
incidentAt: Joi.date().iso().required(),
chatHistory: Joi.array().min(1).required(),
metadata: Joi.object().unknown(true).optional()
});
const { error, value } = schema.validate(req.body || {});
if (error) {
return res.status(400).json({ error: error.details[0].message });
}
try {
const result = await chatService.reportChatIncident(value);
return res.status(201).json(result);
} catch (err) {
console.error('Error in reportChatIncident:', err);
return res.status(400).json({ error: err.message });
}
}
}
export default ChatController;

View File

@@ -1,4 +1,5 @@
import FalukantService from '../services/falukantService.js';
import politicalPowersService from '../services/falukantPoliticalPowersService.js';
function extractHashedUserId(req) {
return req.headers?.userid;
@@ -118,6 +119,8 @@ class FalukantController {
});
this.setLoverMaintenance = this._wrapWithUser((userId, req) =>
this.service.setLoverMaintenance(userId, req.params.relationshipId, req.body?.maintenanceLevel), { blockInDebtorsPrison: true });
this.improveLoverAffection = this._wrapWithUser((userId, req) =>
this.service.improveLoverAffection(userId, req.params.relationshipId), { blockInDebtorsPrison: true });
this.createLoverRelationship = this._wrapWithUser((userId, req) =>
this.service.createLoverRelationship(userId, req.body?.targetCharacterId, req.body?.loverRole), { successStatus: 201, blockInDebtorsPrison: true });
this.spendTimeWithSpouse = this._wrapWithUser((userId) =>
@@ -207,11 +210,29 @@ class FalukantController {
}, { blockInDebtorsPrison: true });
this.getPoliticsOverview = this._wrapWithUser((userId) => this.service.getPoliticsOverview(userId));
this.getPoliticalOfficeCatalog = this._wrapWithUser((userId) => this.service.getPoliticalOfficeCatalog(userId));
this.getOpenPolitics = this._wrapWithUser((userId) => this.service.getOpenPolitics(userId));
this.getElections = this._wrapWithUser((userId) => this.service.getElections(userId));
this.vote = this._wrapWithUser((userId, req) => this.service.vote(userId, req.body.votes), { blockInDebtorsPrison: true });
this.applyForElections = this._wrapWithUser((userId, req) => this.service.applyForElections(userId, req.body.electionIds), { blockInDebtorsPrison: true });
this.getPoliticalMyPowers = this._wrapWithUser((userId) => politicalPowersService.getMyPowers(userId));
this.getPoliticalTaxJurisdiction = this._wrapWithUser((userId) => politicalPowersService.getTaxJurisdiction(userId));
this.setPoliticalRegionTax = this._wrapWithUser((userId, req) =>
politicalPowersService.setRegionTax(userId, parseInt(req.params.regionId, 10), req.body?.percent), { blockInDebtorsPrison: true });
this.getPoliticalRegionTaxHistory = this._wrapWithUser((userId, req) =>
politicalPowersService.getRegionTaxHistory(userId, parseInt(req.params.regionId, 10), parseInt(req.query.limit || '5', 10)));
this.getPoliticalAppointableOffices = this._wrapWithUser((userId) => politicalPowersService.getAppointableOffices(userId));
this.createPoliticalAppointment = this._wrapWithUser(
(userId, req) =>
politicalPowersService.createAppointment(userId, {
targetCharacterId: req.body?.targetCharacterId,
officeTypeId: req.body?.officeTypeId,
regionId: req.body?.regionId
}),
{ successStatus: 201, blockInDebtorsPrison: true }
);
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) => {
@@ -227,7 +248,9 @@ class FalukantController {
if (Number.isNaN(regionId)) {
throw new Error('regionId is required');
}
return this.service.getAllProductPricesInRegion(userId, regionId);
const networkWorth = req.query.networkWorth === '1' || req.query.networkWorth === 'true';
const branchId = req.query.branchId != null ? parseInt(req.query.branchId, 10) : null;
return this.service.getAllProductPricesInRegion(userId, regionId, { networkWorth, branchId });
});
this.getProductPricesInCities = this._wrapWithUser((userId, req) => {
const productId = parseInt(req.query.productId, 10);
@@ -242,11 +265,17 @@ class FalukantController {
const body = req.body || {};
const items = Array.isArray(body.items) ? body.items : [];
const currentRegionId = body.currentRegionId != null ? parseInt(body.currentRegionId, 10) : null;
const includeTransportCosts = body.includeTransportCosts === true || body.includeTransportCosts === 'true';
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);
return this.service.getProductPricesInCitiesBatch(
userId,
valid,
Number.isNaN(currentRegionId) ? null : currentRegionId,
{ includeTransportCosts }
);
});
this.renovate = this._wrapWithUser((userId, req) => this.service.renovate(userId, req.body.element), { blockInDebtorsPrison: true });
this.renovateAll = this._wrapWithUser((userId) => this.service.renovateAll(userId), { blockInDebtorsPrison: true });

View File

@@ -0,0 +1,79 @@
import Joi from 'joi';
import moderationService from '../services/moderationService.js';
const moderationController = {
async createReport(req, res) {
const allowedTargetTypes = [
'forum_message',
'gallery_image',
'guestbook_entry',
'one_to_one_message',
'diary_entry',
'user_profile',
'blog',
'blog_post'
];
const schema = Joi.object({
targetType: Joi.string().valid(...allowedTargetTypes).required(),
targetId: Joi.number().integer().min(1).optional(),
targetRef: Joi.string().trim().max(255).allow('').optional(),
reason: Joi.string().trim().min(3).max(120).required(),
details: Joi.string().allow('').max(2000).optional()
}).or('targetId', 'targetRef');
const { error, value } = schema.validate(req.body || {});
if (error) {
return res.status(400).json({ error: error.details[0].message });
}
try {
const { userid: userId } = req.headers;
const result = await moderationService.createReport(userId, value);
return res.status(201).json(result);
} catch (err) {
console.error('Error in createReport:', err);
return res.status(400).json({ error: err.message });
}
},
async listReports(req, res) {
try {
const { userid: userId } = req.headers;
const result = await moderationService.listReports(userId, req.query || {});
return res.status(200).json(result);
} catch (err) {
console.error('Error in listReports:', err);
return res.status(400).json({ error: err.message });
}
},
async updateReportStatus(req, res) {
const schema = Joi.object({
status: Joi.string().valid('open', 'in_review', 'resolved', 'rejected').required(),
reviewerNote: Joi.string().allow('').max(2000).optional()
});
const { error, value } = schema.validate(req.body || {});
if (error) {
return res.status(400).json({ error: error.details[0].message });
}
try {
const { userid: userId } = req.headers;
const result = await moderationService.updateReportStatus(userId, req.params.reportId, value);
return res.status(200).json(result);
} catch (err) {
console.error('Error in updateReportStatus:', err);
return res.status(400).json({ error: err.message });
}
},
async getOpenReportCount(req, res) {
try {
const { userid: userId } = req.headers;
const result = await moderationService.getOpenReportCount(userId);
return res.status(200).json(result);
} catch (err) {
console.error('Error in getOpenReportCount:', err);
return res.status(400).json({ error: err.message });
}
}
};
export default moderationController;

View File

@@ -96,9 +96,7 @@ const menuStructure = {
},
eroticChat: {
visible: ["over18"],
action: "openEroticChat",
view: "window",
class: "eroticChatWindow"
action: "openEroticChat"
}
}
},
@@ -280,6 +278,10 @@ const menuStructure = {
visible: ["mainadmin", "forum"],
path: "/admin/forum"
},
moderationReports: {
visible: ["mainadmin", "forum"],
path: "/admin/moderation/reports"
},
chatrooms: {
visible: ["mainadmin", "chatrooms"],
path: "/admin/chatrooms"
@@ -293,7 +295,7 @@ const menuStructure = {
path: "/admin/interests"
},
falukant: {
visible: ["mainadmin", "falukant"],
visible: ["mainadmin", "falukant", "worker_schedule_read"],
children: {
logentries: {
visible: ["mainadmin", "falukant"],
@@ -315,6 +317,10 @@ const menuStructure = {
visible: ["mainadmin", "falukant"],
path: "/admin/falukant/create-npc"
},
workerSchedules: {
visible: ["mainadmin", "worker_schedule_read"],
path: "/admin/falukant/worker-schedules"
},
}
},
minigames: {

View File

@@ -16,13 +16,16 @@ class SocialNetworkController {
this.getFoldersByUsername = this.getFoldersByUsername.bind(this);
this.deleteFolder = this.deleteFolder.bind(this);
this.getAdultFolders = this.getAdultFolders.bind(this);
this.getAdultFoldersByUsername = this.getAdultFoldersByUsername.bind(this);
this.createAdultFolder = this.createAdultFolder.bind(this);
this.getAdultFolderImageList = this.getAdultFolderImageList.bind(this);
this.uploadAdultImage = this.uploadAdultImage.bind(this);
this.getAdultImageByHash = this.getAdultImageByHash.bind(this);
this.changeAdultImage = this.changeAdultImage.bind(this);
this.listEroticVideos = this.listEroticVideos.bind(this);
this.getEroticVideosByUsername = this.getEroticVideosByUsername.bind(this);
this.uploadEroticVideo = this.uploadEroticVideo.bind(this);
this.changeEroticVideo = this.changeEroticVideo.bind(this);
this.getEroticVideoByHash = this.getEroticVideoByHash.bind(this);
this.reportEroticContent = this.reportEroticContent.bind(this);
this.createGuestbookEntry = this.createGuestbookEntry.bind(this);
@@ -157,8 +160,8 @@ class SocialNetworkController {
try {
const userId = req.headers.userid;
const { imageId } = req.params;
const { title, visibilities } = req.body;
const folderId = await this.socialNetworkService.changeImage(userId, imageId, title, visibilities);
const { title, visibilities, selectedUsers } = req.body;
const folderId = await this.socialNetworkService.changeImage(userId, imageId, title, visibilities, selectedUsers);
console.log('--->', folderId);
res.status(201).json(await this.socialNetworkService.getFolderImageList(userId, folderId));
} catch (error) {
@@ -208,6 +211,21 @@ class SocialNetworkController {
}
}
async getAdultFoldersByUsername(req, res) {
try {
const requestingUserId = req.headers.userid;
const { username } = req.params;
const folders = await this.socialNetworkService.getAdultFoldersByUsername(username, requestingUserId);
if (!folders) {
return res.status(404).json({ error: 'No folders found or access denied.' });
}
res.status(200).json(folders);
} catch (error) {
console.error('Error in getAdultFoldersByUsername:', error);
res.status(error.status || 500).json({ error: error.message });
}
}
async createAdultFolder(req, res) {
try {
const userId = req.headers.userid;
@@ -267,8 +285,8 @@ class SocialNetworkController {
try {
const userId = req.headers.userid;
const { imageId } = req.params;
const { title, visibilities } = req.body;
const folderId = await this.socialNetworkService.changeAdultImage(userId, imageId, title, visibilities);
const { title, visibilities, selectedUsers } = req.body;
const folderId = await this.socialNetworkService.changeAdultImage(userId, imageId, title, visibilities, selectedUsers);
res.status(201).json(await this.socialNetworkService.getAdultFolderImageList(userId, folderId));
} catch (error) {
console.error('Error in changeAdultImage:', error);
@@ -287,6 +305,18 @@ class SocialNetworkController {
}
}
async getEroticVideosByUsername(req, res) {
try {
const userId = req.headers.userid;
const { username } = req.params;
const videos = await this.socialNetworkService.getEroticVideosByUsername(username, userId);
res.status(200).json(videos);
} catch (error) {
console.error('Error in getEroticVideosByUsername:', error);
res.status(error.status || 500).json({ error: error.message });
}
}
async uploadEroticVideo(req, res) {
try {
const userId = req.headers.userid;
@@ -300,6 +330,18 @@ class SocialNetworkController {
}
}
async changeEroticVideo(req, res) {
try {
const userId = req.headers.userid;
const { videoId } = req.params;
const updatedVideo = await this.socialNetworkService.changeEroticVideo(userId, videoId, req.body);
res.status(200).json(updatedVideo);
} catch (error) {
console.error('Error in changeEroticVideo:', error);
res.status(error.status || 500).json({ error: error.message });
}
}
async getEroticVideoByHash(req, res) {
try {
const userId = req.headers.userid;

View File

@@ -18,15 +18,35 @@ class VocabController {
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.getLanguageDictionary = this._wrapWithUser((userId, req) =>
this.service.getLanguageDictionary(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 });
this.getLessonVocabPool = this._wrapWithUser((userId, req) => this.service.getLessonVocabPool(userId, req.params.lessonId));
// 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.getCompletedLessonVocabPool = this._wrapWithUser((userId, req) =>
this.service.getCompletedLessonVocabPool(userId, req.params.courseId, req.query.untilLessonId)
);
this.getCourseDictionary = this._wrapWithUser((userId, req) =>
this.service.getCourseDictionary(userId, req.params.courseId, req.query)
);
this.getVocabDistractorPool = this._wrapWithUser((userId, req) =>
this.service.getVocabDistractorPool(userId, req.params.courseId, req.query.beforeLessonId)
);
this.getCourseSrsDue = this._wrapWithUser((userId, req) =>
this.service.getCourseSrsDue(userId, req.params.courseId, req.query)
);
this.reviewSrsItem = this._wrapWithUser((userId, req) =>
this.service.reviewSrsItem(userId, req.body),
{ successStatus: 201 }
);
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));
@@ -41,10 +61,12 @@ class VocabController {
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));
this.getDashboardLearningSummary = this._wrapWithUser((userId) => this.service.getDashboardLearningSummary(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));
this.resetLessonProgress = this._wrapWithUser((userId, req) => this.service.resetMyLessonProgress(userId, req.params.lessonId));
// Grammar Exercises
this.getExerciseTypes = this._wrapWithUser((userId) => this.service.getExerciseTypes());
@@ -77,4 +99,3 @@ class VocabController {
}
export default VocabController;

View File

@@ -0,0 +1,94 @@
/**
* Periodischer Job: reputation_periodic für politische Amtsinhaber.
* Aufruf: systemd-Timer oder FALUKANT_POLITICAL_REPUTATION_JOB=1 (siehe server.js).
*/
import PoliticalOffice from '../models/falukant/data/political_office.js';
import PoliticalOfficeType from '../models/falukant/type/political_office_type.js';
import PoliticalOfficeBenefit from '../models/falukant/predefine/political_office_benefit.js';
import PoliticalOfficeBenefitType from '../models/falukant/type/political_office_benefit_type.js';
import PoliticalBenefitLastTick from '../models/falukant/data/political_benefit_last_tick.js';
import FalukantCharacter from '../models/falukant/data/character.js';
import FalukantUser from '../models/falukant/data/user.js';
import User from '../models/community/user.js';
import { sequelize } from '../utils/sequelize.js';
import { notifyUser } from '../utils/socket.js';
export async function runPoliticalReputationTicks() {
const offices = await PoliticalOffice.findAll({
include: [{ model: PoliticalOfficeType, as: 'type', attributes: ['id', 'name'] }]
});
const toNotify = new Set();
let ticks = 0;
for (const po of offices) {
const characterId = po.characterId;
const benefitRows = await PoliticalOfficeBenefit.findAll({
where: { officeTypeId: po.officeTypeId },
include: [
{
model: PoliticalOfficeBenefitType,
as: 'benefitDefinition',
attributes: ['tr'],
required: true,
where: { tr: 'reputation_periodic' }
}
]
});
for (const br of benefitRows) {
const v = br.value && typeof br.value === 'object' ? br.value : {};
const intervalDays = Math.max(1, Number(v.intervalDays ?? v.everyDays ?? 7));
const gain = Math.max(1, Number(v.gain ?? 1));
const [tickRow, created] = await PoliticalBenefitLastTick.findOrCreate({
where: {
characterId,
politicalOfficeBenefitId: br.id
},
defaults: {
characterId,
politicalOfficeBenefitId: br.id,
lastTickAt: new Date(po.createdAt),
ticksCount: 0
}
});
const baseMs = new Date(tickRow.lastTickAt).getTime();
const daysSince = Math.floor((Date.now() - baseMs) / 86400000);
if (daysSince < intervalDays) continue;
await sequelize.transaction(async (t) => {
const ch = await FalukantCharacter.findByPk(characterId, { transaction: t });
if (!ch) return;
const nextRep = Math.min(100, (ch.reputation ?? 0) + gain);
await ch.update({ reputation: nextRep }, { transaction: t });
await PoliticalBenefitLastTick.update(
{
lastTickAt: new Date(),
ticksCount: (tickRow.ticksCount || 0) + 1
},
{ where: { id: tickRow.id }, transaction: t }
);
});
ticks += 1;
toNotify.add(characterId);
console.info(
`[PoliticalBenefits] reputation_tick characterId=${characterId} benefitId=${br.id} gain=${gain}`
);
}
}
for (const characterId of toNotify) {
const ch = await FalukantCharacter.findByPk(characterId, { attributes: ['userId'] });
if (!ch?.userId) continue;
const fu = await FalukantUser.findOne({
where: { id: ch.userId },
include: [{ model: User, as: 'user', attributes: ['hashedId'] }]
});
const hid = fu?.user?.hashedId;
if (hid) notifyUser(hid, 'falukantUpdateStatus', {});
}
return { processedOffices: offices.length, ticksApplied: ticks };
}

View File

@@ -10,6 +10,9 @@ export const authenticate = async (req, res, next) => {
if (!user) {
return res.status(401).json({ error: 'Unauthorized: Invalid credentials' });
}
if (!user.active) {
return res.status(403).json({ error: 'Unauthorized: User blocked' });
}
try {
await updateUserTimestamp(user.id);
} catch (error) {

View File

View File

@@ -0,0 +1,45 @@
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
const table = { tableName: 'contact_message', schema: 'service' };
const columns = await queryInterface.describeTable(table);
if (!columns.answer) {
await queryInterface.addColumn(table, 'answer', {
type: Sequelize.TEXT,
allowNull: true
});
}
if (!columns.answered_at) {
await queryInterface.addColumn(table, 'answered_at', {
type: Sequelize.DATE,
allowNull: true
});
}
if (!columns.is_answered) {
await queryInterface.addColumn(table, 'is_answered', {
type: Sequelize.BOOLEAN,
allowNull: false,
defaultValue: false
});
}
},
down: async (queryInterface, Sequelize) => {
const table = { tableName: 'contact_message', schema: 'service' };
const columns = await queryInterface.describeTable(table);
if (columns.answer) {
await queryInterface.removeColumn(table, 'answer');
}
if (columns.answered_at) {
await queryInterface.removeColumn(table, 'answered_at');
}
if (columns.is_answered) {
await queryInterface.removeColumn(table, 'is_answered');
}
}
};

View File

@@ -0,0 +1,89 @@
'use strict';
/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.createTable(
{ schema: 'community', tableName: 'erotic_video_image_visibility' },
{
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
},
erotic_video_id: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: { schema: 'community', tableName: 'erotic_video' },
key: 'id',
},
onUpdate: 'CASCADE',
onDelete: 'CASCADE',
},
visibility_type_id: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: { schema: 'type', tableName: 'image_visibility' },
key: 'id',
},
onUpdate: 'CASCADE',
onDelete: 'CASCADE',
},
}
);
await queryInterface.createTable(
{ schema: 'community', tableName: 'erotic_video_visibility_user' },
{
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
},
erotic_video_id: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: { schema: 'community', tableName: 'erotic_video' },
key: 'id',
},
onUpdate: 'CASCADE',
onDelete: 'CASCADE',
},
user_id: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: { schema: 'community', tableName: 'user' },
key: 'id',
},
onUpdate: 'CASCADE',
onDelete: 'CASCADE',
},
}
);
await queryInterface.sequelize.query(`
INSERT INTO community.erotic_video_image_visibility (erotic_video_id, visibility_type_id)
SELECT ev.id, iv.id
FROM community.erotic_video ev
CROSS JOIN type.image_visibility iv
WHERE iv.description = 'adults'
AND NOT EXISTS (
SELECT 1
FROM community.erotic_video_image_visibility eviv
WHERE eviv.erotic_video_id = ev.id
AND eviv.visibility_type_id = iv.id
)
`);
},
async down(queryInterface) {
await queryInterface.dropTable({ schema: 'community', tableName: 'erotic_video_visibility_user' });
await queryInterface.dropTable({ schema: 'community', tableName: 'erotic_video_image_visibility' });
},
};

View File

@@ -0,0 +1,36 @@
"use strict";
/** Schwangerschaft (Admin / Spiel): erwarteter Geburtstermin + optionaler Vater-Charakter */
module.exports = {
async up(queryInterface) {
await queryInterface.sequelize.query(`
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = 'falukant_data' AND table_name = 'character' AND column_name = 'pregnancy_due_at'
) THEN
ALTER TABLE falukant_data."character"
ADD COLUMN pregnancy_due_at TIMESTAMPTZ NULL;
END IF;
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = 'falukant_data' AND table_name = 'character' AND column_name = 'pregnancy_father_character_id'
) THEN
ALTER TABLE falukant_data."character"
ADD COLUMN pregnancy_father_character_id INTEGER NULL
REFERENCES falukant_data."character"(id) ON DELETE SET NULL;
END IF;
END$$;
`);
},
async down(queryInterface) {
await queryInterface.sequelize.query(`
ALTER TABLE falukant_data."character" DROP COLUMN IF EXISTS pregnancy_father_character_id;
`);
await queryInterface.sequelize.query(`
ALTER TABLE falukant_data."character" DROP COLUMN IF EXISTS pregnancy_due_at;
`);
},
};

View File

@@ -0,0 +1,44 @@
'use strict';
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.sequelize.query(`
ALTER TABLE community.vocab_course_lesson
ADD COLUMN IF NOT EXISTS didactic_mode TEXT,
ADD COLUMN IF NOT EXISTS phase_label TEXT,
ADD COLUMN IF NOT EXISTS block_number INTEGER,
ADD COLUMN IF NOT EXISTS difficulty_weight INTEGER,
ADD COLUMN IF NOT EXISTS new_unit_target INTEGER,
ADD COLUMN IF NOT EXISTS review_weight INTEGER,
ADD COLUMN IF NOT EXISTS is_intensive_review BOOLEAN NOT NULL DEFAULT FALSE;
COMMENT ON COLUMN community.vocab_course_lesson.didactic_mode IS
'Didaktischer Modus der Lektion, z.B. core_input, guided_dialogue, intensive_review oder checkpoint.';
COMMENT ON COLUMN community.vocab_course_lesson.phase_label IS
'Übergeordnete Lernphase, z.B. quickstart, daily_life oder stabilization.';
COMMENT ON COLUMN community.vocab_course_lesson.block_number IS
'Inhaltlicher Block für Konsolidierungs- und Wiederholungswellen.';
COMMENT ON COLUMN community.vocab_course_lesson.difficulty_weight IS
'Grobe relative Schwierigkeit der Lektion von leicht bis schwer.';
COMMENT ON COLUMN community.vocab_course_lesson.new_unit_target IS
'Empfohlene Zahl neuer Spracheinheiten in dieser Lektion.';
COMMENT ON COLUMN community.vocab_course_lesson.review_weight IS
'Wie stark Wiederholung in dieser Lektion dominieren soll, typischerweise 0 bis 100.';
COMMENT ON COLUMN community.vocab_course_lesson.is_intensive_review IS
'Markiert Lektionen, die als intensive Wiederholungsphase gedacht sind.';
`);
},
async down(queryInterface, Sequelize) {
await queryInterface.sequelize.query(`
ALTER TABLE community.vocab_course_lesson
DROP COLUMN IF EXISTS is_intensive_review,
DROP COLUMN IF EXISTS review_weight,
DROP COLUMN IF EXISTS new_unit_target,
DROP COLUMN IF EXISTS difficulty_weight,
DROP COLUMN IF EXISTS block_number,
DROP COLUMN IF EXISTS phase_label,
DROP COLUMN IF EXISTS didactic_mode;
`);
}
};

View File

@@ -0,0 +1,20 @@
'use strict';
module.exports = {
async up(queryInterface) {
await queryInterface.sequelize.query(`
ALTER TABLE community.vocab_course_progress
ADD COLUMN IF NOT EXISTS lesson_state JSONB NOT NULL DEFAULT '{}'::jsonb;
COMMENT ON COLUMN community.vocab_course_progress.lesson_state IS
'Persistierter UI- und Lernzustand pro Nutzer und Lektion fuer Resume im Sprachkurs.';
`);
},
async down(queryInterface) {
await queryInterface.sequelize.query(`
ALTER TABLE community.vocab_course_progress
DROP COLUMN IF EXISTS lesson_state;
`);
}
};

View File

@@ -0,0 +1,72 @@
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.sequelize.query(`
ALTER TABLE falukant_data.falukant_user
ADD COLUMN IF NOT EXISTS last_political_daily_salary_on date NULL;
`);
await queryInterface.sequelize.query(`
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = 'falukant_predefine'
AND table_name = 'political_office_benefit'
AND column_name = 'political_office_id'
) AND NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = 'falukant_predefine'
AND table_name = 'political_office_benefit'
AND column_name = 'office_type_id'
) THEN
ALTER TABLE falukant_predefine.political_office_benefit
RENAME COLUMN political_office_id TO office_type_id;
ELSIF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = 'falukant_predefine'
AND table_name = 'political_office_benefit'
AND column_name = 'political_office_id'
) AND EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = 'falukant_predefine'
AND table_name = 'political_office_benefit'
AND column_name = 'office_type_id'
) THEN
UPDATE falukant_predefine.political_office_benefit
SET office_type_id = COALESCE(office_type_id, political_office_id);
ALTER TABLE falukant_predefine.political_office_benefit
DROP COLUMN political_office_id;
END IF;
END $$;
`);
},
down: async (queryInterface, Sequelize) => {
await queryInterface.sequelize.query(`
ALTER TABLE falukant_data.falukant_user
DROP COLUMN IF EXISTS last_political_daily_salary_on;
`);
await queryInterface.sequelize.query(`
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = 'falukant_predefine'
AND table_name = 'political_office_benefit'
AND column_name = 'office_type_id'
) AND NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = 'falukant_predefine'
AND table_name = 'political_office_benefit'
AND column_name = 'political_office_id'
) THEN
ALTER TABLE falukant_predefine.political_office_benefit
RENAME COLUMN office_type_id TO political_office_id;
END IF;
END $$;
`);
}
};

View File

@@ -0,0 +1,30 @@
'use strict';
/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up(queryInterface) {
await queryInterface.sequelize.query(`
INSERT INTO type.user_param_value (user_param_type_id, value, order_id)
SELECT upt.id, 'fr', COALESCE(
(SELECT MAX(v.order_id) FROM type.user_param_value v WHERE v.user_param_type_id = upt.id),
0
) + 1
FROM type.user_param upt
WHERE upt.description = 'language'
AND NOT EXISTS (
SELECT 1 FROM type.user_param_value x
WHERE x.user_param_type_id = upt.id AND x.value = 'fr'
);
`);
},
async down(queryInterface) {
await queryInterface.sequelize.query(`
DELETE FROM type.user_param_value v
USING type.user_param upt
WHERE v.user_param_type_id = upt.id
AND upt.description = 'language'
AND v.value = 'fr';
`);
},
};

View File

@@ -0,0 +1,22 @@
/* eslint-disable */
'use strict';
module.exports = {
async up(queryInterface) {
await queryInterface.sequelize.query(`
ALTER TABLE falukant_data.falukant_user
ADD COLUMN IF NOT EXISTS certificate_productions_count_since TIMESTAMPTZ;
`);
await queryInterface.sequelize.query(`
COMMENT ON COLUMN falukant_data.falukant_user.certificate_productions_count_since IS
'Daemon/UI: Zählt nur falukant_log.production-Zeilen mit COALESCE(production_timestamp, production_date::timestamp) >= diesem Wert; bei Stufenänderung (Aufstieg/Bankrott/Erbfolge) auf NOW() (YpDaemon QUERY_UPDATE_FALUKANT_USER_CERTIFICATE). NULL = alle passenden Log-Zeilen bis zur ersten Stufenänderung nach Migration. Kein Löschen der Logs zum Reset.';
`);
},
async down(queryInterface) {
await queryInterface.sequelize.query(`
ALTER TABLE falukant_data.falukant_user
DROP COLUMN IF EXISTS certificate_productions_count_since;
`);
}
};

View File

@@ -0,0 +1,61 @@
'use strict';
/** @param {import('sequelize').QueryInterface} queryInterface */
module.exports = {
async up(queryInterface) {
await queryInterface.sequelize.query(`
CREATE TABLE IF NOT EXISTS falukant_data.political_benefit_last_tick (
id serial PRIMARY KEY,
character_id integer NOT NULL
REFERENCES falukant_data."character"(id) ON DELETE CASCADE,
political_office_benefit_id integer NOT NULL
REFERENCES falukant_predefine.political_office_benefit(id) ON DELETE CASCADE,
last_tick_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
ticks_count integer NOT NULL DEFAULT 0,
CONSTRAINT political_benefit_last_tick_unique UNIQUE (character_id, political_office_benefit_id)
);
CREATE INDEX IF NOT EXISTS political_benefit_last_tick_character_idx
ON falukant_data.political_benefit_last_tick (character_id);
`);
await queryInterface.sequelize.query(`
CREATE TABLE IF NOT EXISTS falukant_data.region_tax_history (
id serial PRIMARY KEY,
region_id integer NOT NULL REFERENCES falukant_data.region(id) ON DELETE CASCADE,
old_tax_percent numeric(12,4) NOT NULL,
new_tax_percent numeric(12,4) NOT NULL,
setter_character_id integer NOT NULL REFERENCES falukant_data."character"(id) ON DELETE CASCADE,
political_office_id integer NULL REFERENCES falukant_data.political_office(id) ON DELETE SET NULL,
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS region_tax_history_region_idx
ON falukant_data.region_tax_history (region_id, created_at DESC);
`);
await queryInterface.sequelize.query(`
CREATE TABLE IF NOT EXISTS falukant_data.political_appointment (
id serial PRIMARY KEY,
appointer_character_id integer NOT NULL REFERENCES falukant_data."character"(id) ON DELETE CASCADE,
target_character_id integer NOT NULL REFERENCES falukant_data."character"(id) ON DELETE CASCADE,
office_type_id integer NOT NULL REFERENCES falukant_type.political_office_type(id) ON DELETE CASCADE,
region_id integer NOT NULL REFERENCES falukant_data.region(id) ON DELETE CASCADE,
status varchar(32) NOT NULL DEFAULT 'completed',
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
expires_at timestamptz NULL,
completed_political_office_id integer NULL REFERENCES falukant_data.political_office(id) ON DELETE SET NULL
);
CREATE INDEX IF NOT EXISTS political_appointment_appointer_idx
ON falukant_data.political_appointment (appointer_character_id, created_at DESC);
CREATE INDEX IF NOT EXISTS political_appointment_target_idx
ON falukant_data.political_appointment (target_character_id);
`);
},
async down(queryInterface) {
await queryInterface.sequelize.query(`
DROP TABLE IF EXISTS falukant_data.political_appointment;
DROP TABLE IF EXISTS falukant_data.region_tax_history;
DROP TABLE IF EXISTS falukant_data.political_benefit_last_tick;
`);
}
};

View File

@@ -0,0 +1,50 @@
'use strict';
/** Stufe pro politischem Amt (Tageshonorar: base + perRank × hierarchy_level). */
module.exports = {
async up(queryInterface) {
await queryInterface.sequelize.query(`
ALTER TABLE falukant_type.political_office_type
ADD COLUMN IF NOT EXISTS hierarchy_level INTEGER NOT NULL DEFAULT 1;
`);
await queryInterface.sequelize.query(`
UPDATE falukant_type.political_office_type AS pot
SET hierarchy_level = sub.lvl
FROM (VALUES
('assessor', 1),
('councillor', 1),
('council', 2),
('beadle', 2),
('town-clerk', 2),
('mayor', 3),
('master-builder', 2),
('village-major', 2),
('judge', 3),
('bailif', 3),
('taxman', 2),
('sheriff', 3),
('consultant', 3),
('treasurer', 4),
('hangman', 2),
('territorial-council', 3),
('territorial-council-speaker', 4),
('ruler-consultant', 4),
('state-administrator', 4),
('super-state-administrator', 5),
('governor', 5),
('ministry-helper', 4),
('minister', 5),
('chancellor', 6)
) AS sub(name, lvl)
WHERE pot.name = sub.name;
`);
},
async down(queryInterface) {
await queryInterface.sequelize.query(`
ALTER TABLE falukant_type.political_office_type
DROP COLUMN IF EXISTS hierarchy_level;
`);
}
};

View File

@@ -0,0 +1,32 @@
/* eslint-disable */
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.sequelize.query(`
ALTER TABLE falukant_data.relationship_state
ADD COLUMN IF NOT EXISTS scandal_extra_daily_pct double precision NOT NULL DEFAULT 0;
`);
await queryInterface.sequelize.query(`
ALTER TABLE falukant_data.relationship_state
DROP CONSTRAINT IF EXISTS relationship_state_scandal_extra_daily_pct_chk;
`);
await queryInterface.sequelize.query(`
ALTER TABLE falukant_data.relationship_state
ADD CONSTRAINT relationship_state_scandal_extra_daily_pct_chk
CHECK (scandal_extra_daily_pct >= 0 AND scandal_extra_daily_pct <= 100);
`);
},
async down(queryInterface, Sequelize) {
await queryInterface.sequelize.query(`
ALTER TABLE falukant_data.relationship_state
DROP CONSTRAINT IF EXISTS relationship_state_scandal_extra_daily_pct_chk;
`);
await queryInterface.sequelize.query(`
ALTER TABLE falukant_data.relationship_state
DROP COLUMN IF EXISTS scandal_extra_daily_pct;
`);
},
};

View File

@@ -0,0 +1,54 @@
'use strict';
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.sequelize.query(`
ALTER TABLE falukant_data.relationship_state
ADD COLUMN IF NOT EXISTS marriage_satisfaction integer;
`);
await queryInterface.sequelize.query(`
UPDATE falukant_data.relationship_state
SET marriage_satisfaction = 55
WHERE marriage_satisfaction IS NULL;
`);
await queryInterface.sequelize.query(`
ALTER TABLE falukant_data.relationship_state
ALTER COLUMN marriage_satisfaction SET DEFAULT 55;
`);
await queryInterface.sequelize.query(`
ALTER TABLE falukant_data.relationship_state
ALTER COLUMN marriage_satisfaction SET NOT NULL;
`);
await queryInterface.sequelize.query(`
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1
FROM pg_constraint
WHERE conname = 'relationship_state_marriage_satisfaction_check'
AND connamespace = 'falukant_data'::regnamespace
) THEN
ALTER TABLE falukant_data.relationship_state
ADD CONSTRAINT relationship_state_marriage_satisfaction_check
CHECK (marriage_satisfaction >= 0 AND marriage_satisfaction <= 100);
END IF;
END $$;
`);
},
async down(queryInterface, Sequelize) {
await queryInterface.sequelize.query(`
ALTER TABLE falukant_data.relationship_state
DROP CONSTRAINT IF EXISTS relationship_state_marriage_satisfaction_check;
`);
await queryInterface.sequelize.query(`
ALTER TABLE falukant_data.relationship_state
DROP COLUMN IF EXISTS marriage_satisfaction;
`);
}
};

View File

@@ -0,0 +1,49 @@
'use strict';
module.exports = {
async up(queryInterface) {
await queryInterface.sequelize.query(`
CREATE TABLE IF NOT EXISTS community.vocab_srs_item (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES community."user"(id) ON DELETE CASCADE,
course_id INTEGER NOT NULL REFERENCES community.vocab_course(id) ON DELETE CASCADE,
lesson_id INTEGER NULL REFERENCES community.vocab_course_lesson(id) ON DELETE SET NULL,
item_key VARCHAR(80) NOT NULL,
learning TEXT NOT NULL,
reference TEXT NOT NULL,
direction VARCHAR(8) NOT NULL DEFAULT 'BOTH',
stage INTEGER NOT NULL DEFAULT 0,
interval_days INTEGER NOT NULL DEFAULT 0,
last_reviewed_at TIMESTAMP WITH TIME ZONE NULL,
next_due_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
correct_count INTEGER NOT NULL DEFAULT 0,
wrong_count INTEGER NOT NULL DEFAULT 0,
lapse_count INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
CONSTRAINT vocab_srs_item_user_key_unique UNIQUE (user_id, item_key)
);
CREATE INDEX IF NOT EXISTS idx_vocab_srs_item_due
ON community.vocab_srs_item (user_id, course_id, next_due_at);
CREATE INDEX IF NOT EXISTS idx_vocab_srs_item_lesson
ON community.vocab_srs_item (user_id, course_id, lesson_id);
COMMENT ON TABLE community.vocab_srs_item IS
'Nutzerbezogener SRS-Fortschritt pro Vokabel/Phrase aus Sprachkursen.';
COMMENT ON COLUMN community.vocab_srs_item.item_key IS
'Stabiler deterministischer Schlüssel aus Kurs, Lektion und normalisiertem Begriffspaar.';
COMMENT ON COLUMN community.vocab_srs_item.stage IS
'SRS-Stufe. Höhere Stufen bedeuten längere Wiederholungsintervalle.';
COMMENT ON COLUMN community.vocab_srs_item.next_due_at IS
'Zeitpunkt, zu dem das Item wieder fällig ist.';
`);
},
async down(queryInterface) {
await queryInterface.sequelize.query(`
DROP TABLE IF EXISTS community.vocab_srs_item;
`);
}
};

View File

@@ -1,43 +0,0 @@
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.addColumn({
tableName: 'contact_message',
schema: 'service'
}, 'answer', {
type: Sequelize.TEXT,
allowNull: true
});
await queryInterface.addColumn({
tableName: 'contact_message',
schema: 'service'
}, 'answered_at', {
type: Sequelize.DATE,
allowNull: true
});
await queryInterface.addColumn({
tableName: 'contact_message',
schema: 'service'
}, 'is_answered', {
type: Sequelize.BOOLEAN,
allowNull: false,
defaultValue: false
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.removeColumn({
tableName: 'contact_message',
schema: 'service'
}, 'answer');
await queryInterface.removeColumn({
tableName: 'contact_message',
schema: 'service'
}, 'answered_at');
await queryInterface.removeColumn({
tableName: 'contact_message',
schema: 'service'
}, 'is_answered');
}
};

View File

@@ -0,0 +1,27 @@
# Backend-Migrationen (Sequelize)
## Aktive Migrationen
Neue Schema-Änderungen: nur noch Dateien unter **`migrations-active/`**. Ausführung z.B.:
`npm run db:migrate` (siehe `backend/package.json`, Pfad `migrations-active`).
## Archiv
Ältere, bereits auf den Umgebungen ausgerollte Migrationen liegen unter **`migrations-archive/`** und werden vom Sequelize-CLI **nicht** mehr ausgeführt.
Vor dem ersten Deploy nach dieser Aufteilung: fehlende Einträge in `"SequelizeMeta"` für die archivierten Dateinamen per SQL ergänzen, siehe **`sql/baseline-sequelize-meta-migrations-archive.sql`** (idempotent).
## Sonstiges in diesem Ordner
SQL-Hilfsdateien und ältere Notizen verbleiben hier (werden nicht vom CLI geladen).
## Falukant: Zertifikat und Produktionszählung
| Datei (Archiv) | Inhalt |
|--------|--------|
| `migrations-archive/20260402140000-add-certificate-productions-count-since.cjs` | Spalte `falukant_data.falukant_user.certificate_productions_count_since` (`TIMESTAMPTZ`, nullable) inkl. Kommentar. Setzt die DB-Grundlage dafür, dass Daemon, Backend und UI dieselbe Periode für „abgeschlossene Produktionen“ nutzen (Filter mit `COALESCE(production_timestamp, production_date::timestamp)` ab diesem Zeitpunkt; `NULL` = bisherige Historie). |
Eine parallele SQL-Migration im Daemon-Repository (z.B. `014_falukant_certificate_productions_count_since.sql`) kann dieselbe Spalte anlegen, wenn das Deployment dort getrennt ist Schema doppelt anlegen vermeiden.
Details zur Zähl- und Retention-Logik: `docs/FALUKANT_PRODUCTION_CERTIFICATE.md`.

View File

@@ -25,6 +25,8 @@ import ImageVisibilityUser from './community/image_visibility_user.js';
import FolderImageVisibility from './community/folder_image_visibility.js';
import ImageImageVisibility from './community/image_image_visibility.js';
import FolderVisibilityUser from './community/folder_visibility_user.js';
import EroticVideoImageVisibility from './community/erotic_video_image_visibility.js';
import EroticVideoVisibilityUser from './community/erotic_video_visibility_user.js';
import GuestbookEntry from './community/guestbook.js';
import Forum from './forum/forum.js';
import Title from './forum/title.js';
@@ -92,6 +94,9 @@ import Candidate from './falukant/data/candidate.js';
import Vote from './falukant/data/vote.js';
import PoliticalOfficeType from './falukant/type/political_office_type.js';
import PoliticalOffice from './falukant/data/political_office.js';
import PoliticalBenefitLastTick from './falukant/data/political_benefit_last_tick.js';
import RegionTaxHistory from './falukant/data/region_tax_history.js';
import PoliticalAppointment from './falukant/data/political_appointment.js';
import PoliticalOfficeBenefit from './falukant/predefine/political_office_benefit.js';
import PoliticalOfficeBenefitType from './falukant/type/political_office_benefit_type.js';
import PoliticalOfficeRequirement from './falukant/predefine/political_office_prerequisite.js';
@@ -121,6 +126,7 @@ 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 VocabSrsItem from './community/vocab_srs_item.js';
import CalendarEvent from './community/calendar_event.js';
import Campaign from './match3/campaign.js';
import Match3Level from './match3/level.js';
@@ -242,6 +248,17 @@ export default function setupAssociations() {
otherKey: 'imageId'
});
EroticVideo.belongsToMany(ImageVisibilityType, {
through: EroticVideoImageVisibility,
foreignKey: 'eroticVideoId',
otherKey: 'visibilityTypeId'
});
ImageVisibilityType.belongsToMany(EroticVideo, {
through: EroticVideoImageVisibility,
foreignKey: 'visibilityTypeId',
otherKey: 'eroticVideoId'
});
Folder.belongsToMany(ImageVisibilityUser, {
through: FolderVisibilityUser,
foreignKey: 'folderId',
@@ -253,6 +270,19 @@ export default function setupAssociations() {
otherKey: 'folderId'
});
EroticVideo.belongsToMany(User, {
through: EroticVideoVisibilityUser,
foreignKey: 'eroticVideoId',
otherKey: 'userId',
as: 'selectedVisibilityUsers'
});
User.belongsToMany(EroticVideo, {
through: EroticVideoVisibilityUser,
foreignKey: 'userId',
otherKey: 'eroticVideoId',
as: 'visibleEroticVideos'
});
// Guestbook related associations
User.hasMany(GuestbookEntry, { foreignKey: 'recipientId', as: 'receivedEntries' });
User.hasMany(GuestbookEntry, { foreignKey: 'senderId', as: 'sentEntries' });
@@ -353,6 +383,8 @@ export default function setupAssociations() {
FalukantCharacter.belongsTo(RegionData, { foreignKey: 'regionId', as: 'region' });
RegionData.hasMany(FalukantCharacter, { foreignKey: 'regionId', as: 'charactersInRegion' });
FalukantCharacter.belongsTo(FalukantCharacter, { foreignKey: 'pregnancyFatherCharacterId', as: 'pregnancyFather' });
FalukantStock.belongsTo(FalukantStockType, { foreignKey: 'stockTypeId', as: 'stockType' });
FalukantStockType.hasMany(FalukantStock, { foreignKey: 'stockTypeId', as: 'stocks' });
@@ -764,6 +796,48 @@ export default function setupAssociations() {
as: 'heldOffice'
});
PoliticalBenefitLastTick.belongsTo(FalukantCharacter, {
foreignKey: 'characterId',
as: 'character'
});
FalukantCharacter.hasMany(PoliticalBenefitLastTick, {
foreignKey: 'characterId',
as: 'politicalBenefitTicks'
});
PoliticalBenefitLastTick.belongsTo(PoliticalOfficeBenefit, {
foreignKey: 'politicalOfficeBenefitId',
as: 'officeBenefit'
});
RegionTaxHistory.belongsTo(RegionData, { foreignKey: 'regionId', as: 'region' });
RegionData.hasMany(RegionTaxHistory, { foreignKey: 'regionId', as: 'taxHistory' });
RegionTaxHistory.belongsTo(FalukantCharacter, {
foreignKey: 'setterCharacterId',
as: 'setterCharacter'
});
RegionTaxHistory.belongsTo(PoliticalOffice, {
foreignKey: 'politicalOfficeId',
as: 'sourceOffice'
});
PoliticalAppointment.belongsTo(FalukantCharacter, {
foreignKey: 'appointerCharacterId',
as: 'appointer'
});
PoliticalAppointment.belongsTo(FalukantCharacter, {
foreignKey: 'targetCharacterId',
as: 'targetCharacter'
});
PoliticalAppointment.belongsTo(PoliticalOfficeType, {
foreignKey: 'officeTypeId',
as: 'officeType'
});
PoliticalAppointment.belongsTo(RegionData, { foreignKey: 'regionId', as: 'region' });
PoliticalAppointment.belongsTo(PoliticalOffice, {
foreignKey: 'completedPoliticalOfficeId',
as: 'completedOffice'
});
// elections
Election.belongsTo(PoliticalOfficeType, {
foreignKey: 'officeTypeId',
@@ -1104,6 +1178,13 @@ export default function setupAssociations() {
VocabGrammarExerciseProgress.belongsTo(VocabGrammarExercise, { foreignKey: 'exerciseId', as: 'exercise' });
VocabGrammarExercise.hasMany(VocabGrammarExerciseProgress, { foreignKey: 'exerciseId', as: 'progress' });
VocabSrsItem.belongsTo(User, { foreignKey: 'userId', as: 'user' });
User.hasMany(VocabSrsItem, { foreignKey: 'userId', as: 'vocabSrsItems' });
VocabSrsItem.belongsTo(VocabCourse, { foreignKey: 'courseId', as: 'course' });
VocabCourse.hasMany(VocabSrsItem, { foreignKey: 'courseId', as: 'srsItems' });
VocabSrsItem.belongsTo(VocabCourseLesson, { foreignKey: 'lessonId', as: 'lesson' });
VocabCourseLesson.hasMany(VocabSrsItem, { foreignKey: 'lessonId', as: 'srsItems' });
// Calendar associations
CalendarEvent.belongsTo(User, { foreignKey: 'userId', as: 'user' });
User.hasMany(CalendarEvent, { foreignKey: 'userId', as: 'calendarEvents' });

View File

@@ -0,0 +1,26 @@
import { sequelize } from '../../utils/sequelize.js';
import { DataTypes } from 'sequelize';
const EroticVideoImageVisibility = sequelize.define('erotic_video_image_visibility', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
allowNull: false
},
eroticVideoId: {
type: DataTypes.INTEGER,
allowNull: false
},
visibilityTypeId: {
type: DataTypes.INTEGER,
allowNull: false
}
}, {
tableName: 'erotic_video_image_visibility',
timestamps: false,
underscored: true,
schema: 'community'
});
export default EroticVideoImageVisibility;

View File

@@ -0,0 +1,26 @@
import { sequelize } from '../../utils/sequelize.js';
import { DataTypes } from 'sequelize';
const EroticVideoVisibilityUser = sequelize.define('erotic_video_visibility_user', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
allowNull: false
},
eroticVideoId: {
type: DataTypes.INTEGER,
allowNull: false
},
userId: {
type: DataTypes.INTEGER,
allowNull: false
}
}, {
tableName: 'erotic_video_visibility_user',
timestamps: false,
underscored: true,
schema: 'community'
});
export default EroticVideoVisibilityUser;

View File

@@ -8,6 +8,26 @@ function encodeEncryptedValueToBlob(value) {
return Buffer.from(encrypted, 'utf8');
}
/** Nur echte Adressen zurückgeben — verhindert Anzeige von Base64-/Key-artigem Müll bei fehlender Entschlüsselung. */
function looksLikePlausibleEmail(s) {
if (typeof s !== 'string') {
return false;
}
const t = s.trim();
if (!t || t.length > 254) {
return false;
}
return /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/i.test(t);
}
function normalizeEmailCandidate(s) {
if (!s || typeof s !== 'string') {
return null;
}
const t = s.trim();
return looksLikePlausibleEmail(t) ? t : null;
}
function decodeEncryptedBlob(value) {
if (!value) {
return null;
@@ -16,8 +36,9 @@ function decodeEncryptedBlob(value) {
try {
const encryptedUtf8 = value.toString('utf8');
const decryptedUtf8 = decrypt(encryptedUtf8);
if (decryptedUtf8) {
return decryptedUtf8;
const fromUtf8 = normalizeEmailCandidate(decryptedUtf8);
if (fromUtf8) {
return fromUtf8;
}
} catch (error) {
console.warn('Email utf8 decryption failed, trying legacy hex format:', error.message);
@@ -26,15 +47,16 @@ function decodeEncryptedBlob(value) {
try {
const encryptedHex = value.toString('hex');
const decryptedHex = decrypt(encryptedHex);
if (decryptedHex) {
return decryptedHex;
const fromHex = normalizeEmailCandidate(decryptedHex);
if (fromHex) {
return fromHex;
}
} catch (error) {
console.warn('Email legacy hex decryption failed:', error.message);
}
try {
return value.toString('utf8');
return normalizeEmailCandidate(value.toString('utf8'));
} catch (error) {
console.warn('Email could not be read as plain text:', error.message);
return null;

View File

@@ -48,6 +48,42 @@ VocabCourseLesson.init({
defaultValue: 'vocab',
field: 'lesson_type'
},
didacticMode: {
type: DataTypes.TEXT,
allowNull: true,
field: 'didactic_mode'
},
phaseLabel: {
type: DataTypes.TEXT,
allowNull: true,
field: 'phase_label'
},
blockNumber: {
type: DataTypes.INTEGER,
allowNull: true,
field: 'block_number'
},
difficultyWeight: {
type: DataTypes.INTEGER,
allowNull: true,
field: 'difficulty_weight'
},
newUnitTarget: {
type: DataTypes.INTEGER,
allowNull: true,
field: 'new_unit_target'
},
reviewWeight: {
type: DataTypes.INTEGER,
allowNull: true,
field: 'review_weight'
},
isIntensiveReview: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
field: 'is_intensive_review'
},
audioUrl: {
type: DataTypes.TEXT,
allowNull: true,

View File

@@ -34,6 +34,12 @@ VocabCourseProgress.init({
allowNull: false,
defaultValue: 0
},
lessonState: {
type: DataTypes.JSONB,
allowNull: false,
defaultValue: {},
field: 'lesson_state'
},
lastAccessedAt: {
type: DataTypes.DATE,
allowNull: true,

View File

@@ -0,0 +1,94 @@
import { Model, DataTypes } from 'sequelize';
import { sequelize } from '../../utils/sequelize.js';
class VocabSrsItem extends Model {}
VocabSrsItem.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: true,
field: 'lesson_id'
},
itemKey: {
type: DataTypes.STRING(80),
allowNull: false,
field: 'item_key'
},
learning: {
type: DataTypes.TEXT,
allowNull: false
},
reference: {
type: DataTypes.TEXT,
allowNull: false
},
direction: {
type: DataTypes.STRING(8),
allowNull: false,
defaultValue: 'BOTH'
},
stage: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0
},
intervalDays: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
field: 'interval_days'
},
lastReviewedAt: {
type: DataTypes.DATE,
allowNull: true,
field: 'last_reviewed_at'
},
nextDueAt: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
field: 'next_due_at'
},
correctCount: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
field: 'correct_count'
},
wrongCount: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
field: 'wrong_count'
},
lapseCount: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
field: 'lapse_count'
}
}, {
sequelize,
modelName: 'VocabSrsItem',
tableName: 'vocab_srs_item',
schema: 'community',
timestamps: true,
underscored: true
});
export default VocabSrsItem;

View File

@@ -45,6 +45,14 @@ FalukantCharacter.init(
min: 0,
max: 100
}
},
pregnancyDueAt: {
type: DataTypes.DATE,
allowNull: true,
},
pregnancyFatherCharacterId: {
type: DataTypes.INTEGER,
allowNull: true,
}
},
{
@@ -53,7 +61,12 @@ FalukantCharacter.init(
tableName: 'character',
schema: 'falukant_data',
timestamps: true,
underscored: true}
underscored: true,
// Spalten erst nach Migration 20260330000000; ohne Exclude würde SELECT/INSERT auf alten DBs fehlschlagen
defaultScope: {
attributes: { exclude: ['pregnancyDueAt', 'pregnancyFatherCharacterId'] },
},
}
);
export default FalukantCharacter;

View File

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

View File

@@ -0,0 +1,56 @@
import { Model, DataTypes } from 'sequelize';
import { sequelize } from '../../../utils/sequelize.js';
class PoliticalAppointment extends Model {}
PoliticalAppointment.init(
{
appointerCharacterId: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'appointer_character_id'
},
targetCharacterId: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'target_character_id'
},
officeTypeId: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'office_type_id'
},
regionId: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'region_id'
},
status: {
type: DataTypes.STRING(32),
allowNull: false,
defaultValue: 'completed'
},
expiresAt: {
type: DataTypes.DATE,
allowNull: true,
field: 'expires_at'
},
completedPoliticalOfficeId: {
type: DataTypes.INTEGER,
allowNull: true,
field: 'completed_political_office_id'
},
},
{
sequelize,
modelName: 'PoliticalAppointment',
tableName: 'political_appointment',
schema: 'falukant_data',
timestamps: true,
createdAt: 'created_at',
updatedAt: false,
underscored: true
}
);
export default PoliticalAppointment;

View File

@@ -0,0 +1,40 @@
import { Model, DataTypes } from 'sequelize';
import { sequelize } from '../../../utils/sequelize.js';
class PoliticalBenefitLastTick extends Model {}
PoliticalBenefitLastTick.init(
{
characterId: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'character_id'
},
politicalOfficeBenefitId: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'political_office_benefit_id'
},
lastTickAt: {
type: DataTypes.DATE,
allowNull: false,
field: 'last_tick_at'
},
ticksCount: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
field: 'ticks_count'
}
},
{
sequelize,
modelName: 'PoliticalBenefitLastTick',
tableName: 'political_benefit_last_tick',
schema: 'falukant_data',
timestamps: false,
underscored: true
}
);
export default PoliticalBenefitLastTick;

View File

@@ -0,0 +1,46 @@
import { Model, DataTypes } from 'sequelize';
import { sequelize } from '../../../utils/sequelize.js';
class RegionTaxHistory extends Model {}
RegionTaxHistory.init(
{
regionId: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'region_id'
},
oldTaxPercent: {
type: DataTypes.DECIMAL(12, 4),
allowNull: false,
field: 'old_tax_percent'
},
newTaxPercent: {
type: DataTypes.DECIMAL(12, 4),
allowNull: false,
field: 'new_tax_percent'
},
setterCharacterId: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'setter_character_id'
},
politicalOfficeId: {
type: DataTypes.INTEGER,
allowNull: true,
field: 'political_office_id'
}
},
{
sequelize,
modelName: 'RegionTaxHistory',
tableName: 'region_tax_history',
schema: 'falukant_data',
timestamps: true,
createdAt: 'created_at',
updatedAt: false,
underscored: true
}
);
export default RegionTaxHistory;

View File

@@ -88,6 +88,16 @@ RelationshipState.init(
min: 0,
},
},
scandalExtraDailyPct: {
type: DataTypes.FLOAT,
allowNull: false,
defaultValue: 0,
validate: {
min: 0,
max: 100,
},
field: 'scandal_extra_daily_pct',
},
monthsUnderfunded: {
type: DataTypes.INTEGER,
allowNull: false,

View File

@@ -29,6 +29,10 @@ FalukantUser.init({
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 1},
certificateProductionsCountSince: {
type: DataTypes.DATE,
allowNull: true
},
mainBranchRegionId: {
type: DataTypes.INTEGER,
allowNull: true
@@ -36,6 +40,11 @@ FalukantUser.init({
lastNobilityAdvanceAt: {
type: DataTypes.DATE,
allowNull: true
},
lastPoliticalDailySalaryOn: {
type: DataTypes.DATEONLY,
allowNull: true,
field: 'last_political_daily_salary_on'
}
}, {
sequelize,

View File

@@ -4,6 +4,11 @@ import { sequelize } from '../../../utils/sequelize.js';
class UserHouse extends Model { }
UserHouse.init({
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
roofCondition: {
type: DataTypes.INTEGER,
allowNull: false,

View File

@@ -23,7 +23,11 @@ DayProduction.init({
productionDate: {
type: DataTypes.DATEONLY,
allowNull: false,
defaultValue: sequelize.literal('CURRENT_DATE')}
defaultValue: sequelize.literal('CURRENT_DATE')},
completionCount: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 1}
}, {
sequelize,
modelName: 'DayProduction',

View File

@@ -1,7 +1,6 @@
// falukant/predefine/political_office_benefit.js
import { Model, DataTypes } from 'sequelize';
import { sequelize } from '../../../utils/sequelize.js';
import PoliticalOfficeBenefitType from '../type/political_office_benefit_type.js';
class PoliticalOfficeBenefit extends Model {}
@@ -9,31 +8,29 @@ PoliticalOfficeBenefit.init({
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true},
politicalOfficeId: {
autoIncrement: true
},
officeTypeId: {
type: DataTypes.INTEGER,
allowNull: false},
allowNull: false,
field: 'office_type_id'
},
benefitTypeId: {
type: DataTypes.INTEGER,
allowNull: false},
allowNull: false,
field: 'benefit_type_id'
},
value: {
type: DataTypes.JSONB,
allowNull: false}}, {
allowNull: false
}
}, {
sequelize,
modelName: 'PoliticalOfficeBenefit',
tableName: 'political_office_benefit',
schema: 'falukant_predefine',
timestamps: false,
underscored: true});
// Association
PoliticalOfficeBenefit.belongsTo(PoliticalOfficeBenefitType, {
foreignKey: 'benefit_type_id',
as: 'benefitType'
});
PoliticalOfficeBenefitType.hasMany(PoliticalOfficeBenefit, {
foreignKey: 'benefit_type_id',
as: 'benefits'
underscored: true
});
export default PoliticalOfficeBenefit;

View File

@@ -20,7 +20,15 @@ PoliticalOfficeType.init({
termLength: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0}}, {
defaultValue: 0},
/** Stufe für Tageshonorar (base + perRank × level) und Sortierung; 1 = niedrigstes politisches Level */
hierarchyLevel: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 1,
field: 'hierarchy_level'
}
}, {
sequelize,
modelName: 'PoliticalOfficeType',
tableName: 'political_office_type',

View File

@@ -25,6 +25,8 @@ import ImageVisibilityUser from './community/image_visibility_user.js';
import FolderImageVisibility from './community/folder_image_visibility.js';
import ImageImageVisibility from './community/image_image_visibility.js';
import FolderVisibilityUser from './community/folder_visibility_user.js';
import EroticVideoImageVisibility from './community/erotic_video_image_visibility.js';
import EroticVideoVisibilityUser from './community/erotic_video_visibility_user.js';
import GuestbookEntry from './community/guestbook.js';
import DiaryHistory from './community/diary_history.js';
import Diary from './community/diary.js';
@@ -114,6 +116,9 @@ import PoliticalOfficeRequirement from './falukant/predefine/political_office_pr
import PoliticalOfficeBenefitType from './falukant/type/political_office_benefit_type.js';
import PoliticalOfficeBenefit from './falukant/predefine/political_office_benefit.js';
import PoliticalOffice from './falukant/data/political_office.js';
import PoliticalBenefitLastTick from './falukant/data/political_benefit_last_tick.js';
import RegionTaxHistory from './falukant/data/region_tax_history.js';
import PoliticalAppointment from './falukant/data/political_appointment.js';
import Election from './falukant/data/election.js';
import Candidate from './falukant/data/candidate.js';
import Vote from './falukant/data/vote.js';
@@ -151,6 +156,7 @@ 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 VocabSrsItem from './community/vocab_srs_item.js';
import CalendarEvent from './community/calendar_event.js';
const models = {
@@ -179,6 +185,8 @@ const models = {
FolderImageVisibility,
ImageImageVisibility,
FolderVisibilityUser,
EroticVideoImageVisibility,
EroticVideoVisibilityUser,
GuestbookEntry,
DiaryHistory,
Diary,
@@ -258,6 +266,9 @@ const models = {
PoliticalOfficeBenefitType,
PoliticalOfficeBenefit,
PoliticalOffice,
PoliticalBenefitLastTick,
RegionTaxHistory,
PoliticalAppointment,
Election,
Candidate,
Vote,
@@ -308,6 +319,7 @@ const models = {
VocabGrammarExerciseType,
VocabGrammarExercise,
VocabGrammarExerciseProgress,
VocabSrsItem,
// Calendar
CalendarEvent,

View File

@@ -129,24 +129,27 @@ export async function createTriggers() {
p_falukant_user_id integer,
p_money_change numeric,
p_activity text,
p_changed_by integer DEFAULT NULL
p_changed_by integer DEFAULT NULL::integer
)
RETURNS void
LANGUAGE plpgsql
AS $function$
DECLARE
v_money_before numeric(10,2);
v_money_after numeric(10,2);
v_money_before numeric(14,2);
v_money_after numeric(14,2);
v_moneyflow_id bigint;
BEGIN
SELECT money
INTO v_money_before
FROM falukant_data.falukant_user
WHERE id = p_falukant_user_id;
IF NOT FOUND THEN
RAISE EXCEPTION 'FalukantUser mit ID % nicht gefunden', p_falukant_user_id;
END IF;
v_money_after := v_money_before + p_money_change;
INSERT INTO falukant_log.moneyflow (
falukant_user_id,
activity,
@@ -160,15 +163,17 @@ export async function createTriggers() {
p_falukant_user_id,
p_activity,
v_money_before,
NULL, -- Wird gleich aktualisiert
NULL, -- wird gleich aktualisiert
p_money_change,
p_changed_by,
NOW()
)
RETURNING id INTO v_moneyflow_id;
UPDATE falukant_data.falukant_user
SET money = v_money_after
WHERE id = p_falukant_user_id;
UPDATE falukant_log.moneyflow
SET money_after = (
SELECT money

5965
backend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "backend",
"version": "1.0.0",
"description": "",
"description": "Nach Änderungen an dependencies: npm install ausführen und package-lock.json committen (npm ci im Deploy).",
"type": "module",
"main": "index.js",
"scripts": {
@@ -9,17 +9,21 @@
"dev": "NODE_ENV=development node server.js",
"start-daemon": "node daemonServer.js",
"sync-db": "node sync-database.js",
"db:migrate": "sequelize-cli db:migrate --config config/sequelize-cli.cjs --migrations-path migrations-active --env production",
"sync-tables": "node sync-tables-only.js",
"check-connections": "node check-connections.js",
"cleanup-connections": "node cleanup-connections.js",
"diag:town-worth": "QUIET_ENV_LOGS=1 DOTENV_CONFIG_QUIET=1 node scripts/falukant-town-product-worth-stats.mjs",
"diag:moneyflow": "QUIET_ENV_LOGS=1 DOTENV_CONFIG_QUIET=1 node scripts/falukant-moneyflow-report.mjs",
"sync:vocab-courses": "node scripts/sync-vocab-course-content.js",
"lockfile:sync": "npm install --package-lock-only",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@gltf-transform/cli": "^4.3.0",
"amqplib": "^0.10.9",
"bcryptjs": "^3.0.3",
"connect-redis": "^9.0.0",
@@ -42,10 +46,12 @@
"sharp": "^0.34.5",
"socket.io": "^4.8.3",
"uuid": "^13.0.0",
"ws": "^8.20.0",
"@gltf-transform/cli": "^4.3.0"
"ws": "^8.20.0"
},
"devDependencies": {
"sequelize-cli": "^6.6.5"
},
"overrides": {
"minimatch": "10.2.4"
}
}

View File

@@ -2,6 +2,7 @@
import { Router } from 'express';
import { authenticate } from '../middleware/authMiddleware.js';
import AdminController from '../controllers/adminController.js';
import moderationController from '../controllers/moderationController.js';
const router = Router();
const adminController = new AdminController();
@@ -25,6 +26,14 @@ router.put('/users/:id/adult-verification', authenticate, adminController.setAdu
router.get('/users/erotic-moderation', authenticate, adminController.getEroticModerationReports);
router.get('/users/erotic-moderation/preview/:type/:targetId', authenticate, adminController.getEroticModerationPreview);
router.put('/users/erotic-moderation/:id', authenticate, adminController.applyEroticModerationAction);
router.get('/users/:id/vocab-courses', authenticate, adminController.getUserVocabCourses);
router.post('/users/:id/vocab-lesson-progress/reset', authenticate, adminController.resetUserVocabLessonProgress);
router.post(
'/users/:id/vocab-lesson-progress/mark-complete-through',
authenticate,
adminController.markUserVocabLessonsCompleteThrough
);
router.get('/vocab/courses/:courseId', authenticate, adminController.getVocabCourseForAdmin);
router.get('/users/:id', authenticate, adminController.getUser);
router.put('/users/:id', authenticate, adminController.updateUser);
@@ -43,15 +52,26 @@ router.post('/contacts/answer', authenticate, adminController.answerContact);
router.post('/falukant/searchuser', authenticate, adminController.searchUser);
router.get('/falukant/getuser/:id', authenticate, adminController.getFalukantUserById);
router.post('/falukant/edituser', authenticate, adminController.changeFalukantUser);
router.get('/falukant/character/:characterId/potential-fathers', authenticate, adminController.adminGetPotentialFathersForCharacter);
router.post('/falukant/character/force-pregnancy', authenticate, adminController.adminForceFalukantPregnancy);
router.post('/falukant/character/clear-pregnancy', authenticate, adminController.adminClearFalukantPregnancy);
router.post('/falukant/character/force-birth', authenticate, adminController.adminForceFalukantBirth);
router.post('/falukant/character/:characterId/death-cleanup', authenticate, adminController.adminCleanupCharacterDeathArtifacts);
router.get('/falukant/branches/:falukantUserId', authenticate, adminController.getFalukantUserBranches);
router.put('/falukant/stock/:stockId', authenticate, adminController.updateFalukantStock);
router.post('/falukant/stock', authenticate, adminController.addFalukantStock);
router.get('/falukant/stock-types', authenticate, adminController.getFalukantStockTypes);
router.get('/falukant/region-types', authenticate, adminController.getFalukantRegionTypes);
router.get('/falukant/regions', authenticate, adminController.getFalukantRegions);
router.get('/falukant/regions/all', authenticate, adminController.getFalukantAllRegions);
router.post('/falukant/regions', authenticate, adminController.createFalukantRegion);
router.put('/falukant/regions/:id/map', authenticate, adminController.updateFalukantRegionMap);
router.get('/falukant/region-distances', authenticate, adminController.getRegionDistances);
router.post('/falukant/region-distances', authenticate, adminController.upsertRegionDistance);
router.delete('/falukant/region-distances/:id', authenticate, adminController.deleteRegionDistance);
router.get('/moderation/reports', authenticate, moderationController.listReports);
router.get('/moderation/reports/open-count', authenticate, moderationController.getOpenReportCount);
router.post('/moderation/reports/:reportId/status', authenticate, moderationController.updateReportStatus);
router.post('/falukant/npcs/create', authenticate, adminController.createNPCs);
router.get('/falukant/npcs/status/:jobId', authenticate, adminController.getNPCsCreationStatus);
router.get('/falukant/titles', authenticate, adminController.getTitlesOfNobility);

View File

@@ -18,5 +18,6 @@ router.get('/rooms', authenticate, chatController.getRoomList);
router.get('/room-create-options', authenticate, chatController.getRoomCreateOptions);
router.get('/my-rooms', authenticate, chatController.getOwnRooms);
router.delete('/my-rooms/:id', authenticate, chatController.deleteOwnRoom);
router.post('/report', chatController.reportChatIncident);
export default router;

View File

@@ -22,10 +22,12 @@ router.post('/production', falukantController.createProduction);
router.get('/production/:branchId', falukantController.getProduction);
router.get('/stocktypes', falukantController.getStockTypes);
router.get('/stockoverview', falukantController.getStockOverview);
router.get('/stock/?:branchId', falukantController.getStock);
router.get('/stock', falukantController.getStock);
router.get('/stock/:branchId', falukantController.getStock);
router.post('/stock', falukantController.createStock);
router.get('/products', falukantController.getProducts);
router.get('/inventory/?:branchId', falukantController.getInventory);
router.get('/inventory', falukantController.getInventory);
router.get('/inventory/:branchId', falukantController.getInventory);
router.post('/sell/all', falukantController.sellAllProducts);
router.post('/sell', falukantController.sellProduct);
router.post('/moneyhistory', falukantController.moneyHistory);
@@ -52,6 +54,7 @@ router.post('/family/marriage/spend-time', falukantController.spendTimeWithSpous
router.post('/family/marriage/gift', falukantController.giftToSpouse);
router.post('/family/marriage/reconcile', falukantController.reconcileMarriage);
router.post('/family/lover/:relationshipId/maintenance', falukantController.setLoverMaintenance);
router.post('/family/lover/:relationshipId/improve-affection', falukantController.improveLoverAffection);
router.post('/family/lover/:relationshipId/acknowledge', falukantController.acknowledgeLover);
router.post('/family/lover/:relationshipId/end', falukantController.endLoverRelationship);
router.get('/heirs/potential', falukantController.getPotentialHeirs);
@@ -93,6 +96,13 @@ router.post('/nobility', falukantController.advanceNobility);
router.get('/health', falukantController.getHealth);
router.post('/health', falukantController.healthActivity);
router.get('/politics/overview', falukantController.getPoliticsOverview);
router.get('/politics/offices', falukantController.getPoliticalOfficeCatalog);
router.get('/politics/my-powers', falukantController.getPoliticalMyPowers);
router.get('/politics/tax-jurisdiction', falukantController.getPoliticalTaxJurisdiction);
router.put('/politics/region/:regionId/tax', falukantController.setPoliticalRegionTax);
router.get('/politics/region/:regionId/tax-history', falukantController.getPoliticalRegionTaxHistory);
router.get('/politics/appointable-offices', falukantController.getPoliticalAppointableOffices);
router.post('/politics/appointments', falukantController.createPoliticalAppointment);
router.get('/politics/open', falukantController.getOpenPolitics);
router.post('/politics/open', falukantController.applyForElections);
router.get('/politics/elections', falukantController.getElections);

View File

@@ -0,0 +1,10 @@
import express from 'express';
import { authenticate } from '../middleware/authMiddleware.js';
import moderationController from '../controllers/moderationController.js';
const router = express.Router();
router.use(authenticate);
router.post('/reports', moderationController.createReport);
export default router;

View File

@@ -17,12 +17,15 @@ router.get('/folder/:folderId', socialNetworkController.getFolderImageList);
router.post('/images', upload.single('image'), socialNetworkController.uploadImage);
router.post('/erotic/folders/:folderId', socialNetworkController.createAdultFolder);
router.get('/erotic/folders', socialNetworkController.getAdultFolders);
router.get('/profile/erotic/folders/:username', socialNetworkController.getAdultFoldersByUsername);
router.get('/profile/erotic/videos/:username', socialNetworkController.getEroticVideosByUsername);
router.get('/erotic/folder/:folderId', socialNetworkController.getAdultFolderImageList);
router.post('/erotic/images', upload.single('image'), socialNetworkController.uploadAdultImage);
router.put('/erotic/images/:imageId', socialNetworkController.changeAdultImage);
router.get('/erotic/image/:hash', socialNetworkController.getAdultImageByHash);
router.get('/erotic/videos', socialNetworkController.listEroticVideos);
router.post('/erotic/videos', upload.single('video'), socialNetworkController.uploadEroticVideo);
router.put('/erotic/videos/:videoId', socialNetworkController.changeEroticVideo);
router.get('/erotic/video/:hash', socialNetworkController.getEroticVideoByHash);
router.post('/erotic/report', socialNetworkController.reportEroticContent);
router.get('/images/:imageId', socialNetworkController.getImage);

View File

@@ -7,6 +7,8 @@ const vocabController = new VocabController();
router.use(authenticate);
router.get('/dashboard-widget', vocabController.getDashboardLearningSummary);
router.get('/languages', vocabController.listLanguages);
router.get('/languages/all', vocabController.listAllLanguages);
router.post('/languages', vocabController.createLanguage);
@@ -18,19 +20,26 @@ router.get('/languages/:languageId/chapters', vocabController.listChapters);
router.post('/languages/:languageId/chapters', vocabController.createChapter);
router.get('/languages/:languageId/vocabs', vocabController.listLanguageVocabs);
router.get('/languages/:languageId/search', vocabController.searchVocabs);
router.get('/languages/:languageId/dictionary', vocabController.getLanguageDictionary);
router.get('/chapters/:chapterId', vocabController.getChapter);
router.get('/chapters/:chapterId/vocabs', vocabController.listChapterVocabs);
router.post('/chapters/:chapterId/vocabs', vocabController.addVocabToChapter);
router.get('/lessons/:lessonId/vocab-pool', vocabController.getLessonVocabPool);
// Courses
router.post('/courses', vocabController.createCourse);
router.get('/courses', vocabController.getCourses);
router.get('/courses/my', vocabController.getMyCourses);
router.post('/courses/find-by-code', vocabController.getCourseByShareCode);
router.get('/courses/:courseId/completed-lesson-vocabs', vocabController.getCompletedLessonVocabPool);
router.get('/courses/:courseId/dictionary', vocabController.getCourseDictionary);
router.get('/courses/:courseId/distractor-pool', vocabController.getVocabDistractorPool);
router.get('/courses/:courseId/srs/due', vocabController.getCourseSrsDue);
router.get('/courses/:courseId', vocabController.getCourse);
router.put('/courses/:courseId', vocabController.updateCourse);
router.delete('/courses/:courseId', vocabController.deleteCourse);
router.post('/srs/review', vocabController.reviewSrsItem);
// Lessons
router.post('/courses/:courseId/lessons', vocabController.addLessonToCourse);
@@ -45,6 +54,7 @@ router.delete('/courses/:courseId/enroll', vocabController.unenrollFromCourse);
router.get('/courses/:courseId/progress', vocabController.getCourseProgress);
router.get('/lessons/:lessonId', vocabController.getLesson);
router.put('/lessons/:lessonId/progress', vocabController.updateLessonProgress);
router.delete('/lessons/:lessonId/progress', vocabController.resetLessonProgress);
// Grammar Exercises
router.get('/grammar/exercise-types', vocabController.getExerciseTypes);
@@ -58,4 +68,3 @@ router.put('/grammar-exercises/:exerciseId', vocabController.updateGrammarExerci
router.delete('/grammar-exercises/:exerciseId', vocabController.deleteGrammarExercise);
export default router;

View File

@@ -9,6 +9,7 @@
import { sequelize } from '../utils/sequelize.js';
import VocabCourseLesson from '../models/community/vocab_course_lesson.js';
import { getBisayaLessonPedagogy } from './bisaya-course-phase2-pedagogy.js';
const LESSONS_TO_ADD = [
{
@@ -98,6 +99,8 @@ async function addBisayaWeek1Lessons() {
continue;
}
const pedagogy = getBisayaLessonPedagogy(lessonData.lessonNumber) || {};
await VocabCourseLesson.create({
courseId: course.id,
chapterId: null,
@@ -113,7 +116,14 @@ async function addBisayaWeek1Lessons() {
speakingPrompts: lessonData.speakingPrompts || [],
targetMinutes: lessonData.targetMinutes,
targetScorePercent: lessonData.targetScorePercent,
requiresReview: lessonData.requiresReview
requiresReview: lessonData.requiresReview,
didacticMode: pedagogy.didacticMode || null,
phaseLabel: pedagogy.phaseLabel || null,
blockNumber: pedagogy.blockNumber ?? null,
difficultyWeight: pedagogy.difficultyWeight ?? null,
newUnitTarget: pedagogy.newUnitTarget ?? null,
reviewWeight: pedagogy.reviewWeight ?? null,
isIntensiveReview: Boolean(pedagogy.isIntensiveReview)
});
console.log(` ✅ Lektion ${lessonData.lessonNumber}: "${lessonData.title}" hinzugefügt`);

View File

@@ -12,6 +12,12 @@ import VocabGrammarExercise from '../models/community/vocab_grammar_exercise.js'
import VocabCourseProgress from '../models/community/vocab_course_progress.js';
import VocabGrammarExerciseProgress from '../models/community/vocab_grammar_exercise_progress.js';
import { Op } from 'sequelize';
import { getBisayaLessonPedagogy } from './bisaya-course-phase2-pedagogy.js';
import {
BISAYA_DIDACTICS_24_43,
BISAYA_LESSONS_24_43_BY_NUMBER,
BISAYA_RELATIONSHIP_ANCHOR_DIDACTICS
} from './bisaya-course-plan-24-43.js';
const LESSON_DIDACTICS = {
'Begrüßungen & Höflichkeit': {
@@ -21,31 +27,158 @@ const LESSON_DIDACTICS = {
'Höfliche Reaktionen wie Danke und Bitte passend einsetzen.',
'Ein kurzes Begrüßungs-Mini-Gespräch laut üben.'
],
corePatterns: ['Kumusta ka?', 'Maayong buntag.', 'Maayong adlaw.', 'Amping.', 'Babay.', 'Maayo ko.', 'Salamat.', 'Palihug.'],
corePatterns: [
{ target: 'Kumusta ka?', gloss: 'Wie geht es dir?' },
{ target: 'Maayong buntag.', gloss: 'Guten Morgen.' },
{ target: 'Maayong adlaw.', gloss: 'Guten Tag.' },
{ target: 'Maayong gabii.', gloss: 'Guten Abend.' },
{ target: 'Maayong gabii, matulog na ta.', gloss: 'Guten Abend, wir legen uns schlafen.' },
{ target: 'Katulog og maayo.', gloss: 'Schlaf gut.' },
{ target: 'Kapoy na ka?', gloss: 'Bist du müde?' },
{ target: 'Matulog na ta.', gloss: 'Lass uns schlafen gehen.' },
{ target: 'Inom sa og tubig.', gloss: 'Trink Wasser.' },
{ target: 'Patya ang suga.', gloss: 'Mach das Licht aus.' },
{ target: 'Tabuni ang imong kaugalingon.', gloss: 'Deck dich zu.' },
{ target: 'Ugma nasad.', gloss: 'Bis morgen wieder.' },
{ target: 'Damgo og nindot.', gloss: 'Träum schön.' },
{ target: 'Amping.', gloss: 'Pass auf dich auf.' },
{ target: 'Babay.', gloss: 'Tschüss.' },
{ target: 'Maayo ko.', gloss: 'Mir geht es gut.' },
{ target: 'Salamat.', gloss: 'Danke.' },
{ target: 'Palihug.', gloss: 'Bitte.' }
],
grammarFocus: [
{ title: 'Kurzantworten mit ko', text: 'Mit "ko" sprichst du über dich selbst: "Maayo ko."', example: 'Maayo ko. = Mir geht es gut.' },
{ title: 'Maayong + Tageszeit', text: 'Mit "Maayong" kannst du Grüße für verschiedene Tageszeiten bilden.', example: 'Maayong buntag. / Maayong gabii.' }
{ title: 'Maayong + Tageszeit', text: 'Mit "Maayong" kannst du Grüße für verschiedene Tageszeiten bilden.', example: 'Maayong buntag. / Maayong gabii.' },
{ title: 'Abend und Schlafen', text: 'Im Familienalltag folgen auf einen Abendgruß oft direkte Schlafens- oder Fürsorgeformeln.', example: 'Maayong gabii. / Katulog og maayo.' },
{ title: 'Sanfte Schlafensroutine', text: 'Kurze Fragen nach Müdigkeit und kleine Aufforderungen klingen im Familienalltag natürlicher als lange Sätze.', example: 'Kapoy na ka? Matulog na ta. Inom sa og tubig.' },
{ title: 'Familiäre Abendformeln', text: 'Am Abend folgen oft sehr kurze Handlungsformeln wie Licht aus, zudecken oder ein Schlafwunsch.', example: 'Patya ang suga. Tabuni ang imong kaugalingon. Damgo og nindot.' }
],
speakingPrompts: [
{ title: 'Mini-Gespräch', prompt: 'Begrüße eine Person, frage nach dem Befinden und reagiere höflich.', cue: 'Kumusta ka? Maayo ko. Salamat.' },
{ title: 'Verabschiedung', prompt: 'Verabschiede dich kurz und wünsche, dass die andere Person auf sich aufpasst.', cue: 'Babay. Amping.' }
{ title: 'Verabschiedung', prompt: 'Verabschiede dich kurz und wünsche, dass die andere Person auf sich aufpasst.', cue: 'Babay. Amping.' },
{ title: 'Abend und Schlaf', prompt: 'Wünsche einen guten Abend, eine gute Nacht und dass die Person gut schlafen soll.', cue: 'Maayong gabii. Katulog og maayo.' },
{ title: 'Schlafensroutine', prompt: 'Frage, ob die Person müde ist, und leite dann sanft zum Schlafengehen über.', cue: 'Kapoy na ka? Matulog na ta. Inom sa og tubig.' },
{ title: 'Vor dem Schlafen', prompt: 'Bitte darum, das Licht auszumachen, sich zuzudecken, und wünsche eine gute Nacht bis morgen.', cue: 'Patya ang suga. Tabuni ang imong kaugalingon. Ugma nasad. Damgo og nindot.' }
],
practicalTasks: [{ title: 'Alltag', text: 'Sprich die Begrüßung dreimal laut und variiere die Antwort.' }]
},
'Familienwörter': {
learningGoals: [
'Die wichtigsten Familienbezeichnungen sicher erkennen.',
'Familienmitglieder mit respektvollen Wörtern ansprechen.',
'Familienmitglieder und Großeltern mit respektvollen Wörtern ansprechen.',
'Kurze Sätze über die eigene Familie bilden.'
],
corePatterns: ['Si Nanay', 'Si Tatay', 'Kuya nako', 'Ate nako'],
corePatterns: [
{ target: 'Si Nanay.', gloss: 'Das ist Mama.' },
{ target: 'Si Tatay.', gloss: 'Das ist Papa.' },
{ target: 'Si Kuya nako.', gloss: 'Das ist mein älterer Bruder.' },
{ target: 'Si Ate nako.', gloss: 'Das ist meine ältere Schwester.' },
{ target: 'Si Dodong nako.', gloss: 'Das ist mein jüngerer Bruder.' },
{ target: 'Si Inday nako.', gloss: 'Das ist meine jüngere Schwester.' },
{ target: 'Si Lola nako.', gloss: 'Das ist meine Großmutter.' },
{ target: 'Si Lolo nako.', gloss: 'Das ist mein Großvater.' }
],
grammarFocus: [
{ title: 'Respekt in Familienanreden', text: 'Kuya und Ate werden nicht nur in der Familie, sondern auch respektvoll für ältere Personen benutzt.', example: 'Kuya, palihug.' }
{ title: 'Respekt in Familienanreden', text: 'Kuya und Ate richtest du an ältere Geschwister (oder respektvoll an andere). Dodong und Inday nutzt du für jüngere Brüder bzw. Schwestern; „Ading“ ist eine weiche Anrede an jüngere Geschwister.', example: 'Kuya, palihug. / Si Dodong nako.' },
{ title: 'si als Personenmarker', text: 'Mit "si" markierst du im einfachen Satz eine konkrete Person.', example: 'Si Nanay. Si Tatay.' }
],
speakingPrompts: [
{ title: 'Meine Familie', prompt: 'Stelle zwei Familienmitglieder mit einem kurzen Satz vor.', cue: 'Si Nanay. Si Kuya.' }
{ title: 'Meine Familie', prompt: 'Stelle vier Familienmitglieder mit kurzen Sätzen vor.', cue: 'Si Nanay. Si Tatay. Si Kuya nako. Si Dodong nako.' }
],
practicalTasks: [{ title: 'Familienpraxis', text: 'Nenne laut fünf Familienwörter und bilde danach zwei Mini-Sätze.' }]
practicalTasks: [{ title: 'Familienpraxis', text: 'Nenne laut die acht Kern-Familienwörter und bilde danach drei Mini-Sätze über deine Familie.' }]
},
'Überlebenssätze - Teil 1': {
learningGoals: [
'Zentrale Notfall- und Verständnisfragen schnell abrufen.',
'Höflich um Wiederholung, Hilfe und langsamere Sprache bitten.',
'Drei Überlebenssätze hintereinander sicher sprechen.'
],
corePatterns: [
{ target: 'Wala ko kasabot.', gloss: 'Ich verstehe nicht.' },
{ target: 'Palihug ka mubalik?', gloss: 'Kannst du das bitte wiederholen?' },
{ target: 'Asa ang CR?', gloss: 'Wo ist die Toilette?' },
{ target: 'Hinay-hinay lang.', gloss: 'Bitte langsam.' },
{ target: 'Tabangi ko, palihug.', gloss: 'Hilf mir bitte.' },
{ target: 'Unsay pasabot ani?', gloss: 'Was bedeutet das?' }
],
grammarFocus: [
{ title: 'Bitte-Formeln mit palihug', text: '"Palihug" macht Bitten höflich und taucht in vielen Überlebenssätzen auf.', example: 'Palihug ka mubalik? / Tabangi ko, palihug.' },
{ title: 'Kurze Verständnisfragen', text: 'Sehr kurze Fragen helfen dir im Alltag oft mehr als lange Sätze.', example: 'Unsay pasabot ani? Asa ang CR?' }
],
speakingPrompts: [
{ title: 'Wenn du etwas nicht verstehst', prompt: 'Sage, dass du etwas nicht verstehst, und bitte um Wiederholung.', cue: 'Wala ko kasabot. Palihug ka mubalik?' },
{ title: 'Soforthilfe', prompt: 'Bitte um Hilfe und frage dann nach der Toilette oder nach der Bedeutung eines Wortes.', cue: 'Tabangi ko, palihug. Asa ang CR?' }
],
practicalTasks: [{ title: 'Alltagsanker', text: 'Sprich alle sechs Überlebenssätze laut durch und ordne sie drei Alltagssituationen zu.' }]
},
'Familien-Gespräche': {
learningGoals: [
'Kurze Familiengespräche sicher verstehen.',
'Nach Familienmitgliedern fragen und einfache Antworten geben.',
'Ein Mini-Gespräch über Hunger und Zuhause nachsprechen.'
],
corePatterns: [
{ target: 'Kumusta ka, Nanay?', gloss: 'Wie geht es dir, Mama?' },
{ target: 'Asa si Tatay?', gloss: 'Wo ist Papa?' },
{ target: 'Naa siya sa balay.', gloss: 'Er ist zu Hause.' },
{ target: 'Kumusta na ang Kuya?', gloss: 'Wie geht es dem älteren Bruder?' },
{ target: 'Kumusta na ang Dodong?', gloss: 'Wie geht es dem jüngeren Bruder?' },
{ target: 'Kumusta na ang Inday?', gloss: 'Wie geht es der jüngeren Schwester?' },
{ target: 'Gutom na ko, Nanay.', gloss: 'Ich habe Hunger, Mama.' },
{ target: 'Hapit na ang pagkaon.', gloss: 'Das Essen ist fast fertig.' }
],
grammarFocus: [
{ title: 'naa für Ort und Vorhandensein', text: '"Naa" hilft dir, über Orte und Vorhandensein zu sprechen.', example: 'Naa siya sa balay.' }
],
speakingPrompts: [
{ title: 'Familien-Mini-Dialog', prompt: 'Frage nach einem Familienmitglied und reagiere dann mit einer kurzen Antwort.', cue: 'Asa si Tatay? Naa siya sa balay.' }
],
practicalTasks: [{ title: 'Gesprächspraxis', text: 'Spiele einen kurzen Familienaustausch mit Frage, Antwort und Fürsorge nach.' }]
},
'Gefühle & Zuneigung': {
learningGoals: [
'Wichtige Gefühle und Zuneigungsformeln sicher unterscheiden.',
'Freundliche Nähe und Vermissen sprachlich ausdrücken.',
'Zwischen positiven und negativen Gefühlen wechseln.'
],
corePatterns: [
{ target: 'Palangga taka.', gloss: 'Ich hab dich lieb.' },
{ target: 'Ganahan ko nimo.', gloss: 'Ich mag dich.' },
{ target: 'Gimingaw ko nimo.', gloss: 'Ich vermisse dich.' },
{ target: 'Nalipay ko.', gloss: 'Ich bin glücklich.' },
{ target: 'Nasubo ko.', gloss: 'Ich bin traurig.' },
{ target: 'Nalipay ko nga nakita ka.', gloss: 'Ich freue mich, dich zu sehen.' }
],
grammarFocus: [
{ title: 'ko für eigene Gefühle', text: 'Viele Gefühlsaussagen bauen direkt auf dem Muster "Gefühl + ko" auf.', example: 'Nalipay ko. Nasubo ko.' }
],
speakingPrompts: [
{ title: 'Gefühl ausdrücken', prompt: 'Sage, dass du jemanden magst oder vermisst, und ergänze danach ein einfaches Gefühl.', cue: 'Ganahan ko nimo. Nalipay ko.' }
],
practicalTasks: [{ title: 'Herzsprache', text: 'Lies drei Zuneigungsformeln laut und entscheide danach: liebhaben, mögen oder vermissen?' }]
},
'Überlebenssätze - Teil 2': {
learningGoals: [
'Weitere zentrale Alltagsfragen sicher sprechen.',
'Höflich Entschuldigung, Nachfrage und Hilfesprache verbinden.',
'Im Alltag Preise, Dinge und Sprache klar ansprechen.'
],
corePatterns: [
{ target: 'Tagpila ni?', gloss: 'Wie viel kostet das?' },
{ target: 'Unsa ni?', gloss: 'Was ist das?' },
{ target: 'Pasensya.', gloss: 'Entschuldigung.' },
{ target: 'Dili ko mag-Bisaya.', gloss: 'Ich spreche kein Bisaya.' },
{ target: 'Palihug isulat ni.', gloss: 'Bitte schreib das auf.' },
{ target: 'Nawala ko.', gloss: 'Ich habe mich verlaufen.' }
],
grammarFocus: [
{ title: 'Kurze Markt- und Orientierungssprache', text: 'Kurze Fragewörter plus ein Nomen reichen oft, um im Alltag voranzukommen.', example: 'Tagpila ni? Unsa ni?' }
],
speakingPrompts: [
{ title: 'Auf dem Markt', prompt: 'Frage nach Preis und Bedeutung eines Gegenstands und bitte dann darum, etwas aufzuschreiben.', cue: 'Tagpila ni? Unsa ni? Palihug isulat ni.' }
],
practicalTasks: [{ title: 'Unterwegs', text: 'Sprich drei Sätze für Einkauf, Nachfrage und Orientierung laut hintereinander.' }]
},
'Essen & Fürsorge': {
learningGoals: [
@@ -53,29 +186,174 @@ const LESSON_DIDACTICS = {
'Einladungen zum Essen passend beantworten.',
'Kurze Essens-Dialoge laut üben.'
],
corePatterns: ['Nikaon na ka?', 'Kaon ta.', 'Gusto ka mokaon?', 'Lami kaayo.'],
corePatterns: [
{ target: 'Nikaon na ka?', gloss: 'Hast du schon gegessen?' },
{ target: 'Kaon ta.', gloss: 'Lass uns essen.' },
{ target: 'Gusto ka mokaon?', gloss: 'Möchtest du essen?' },
{ target: 'Gutom na ko.', gloss: 'Ich habe Hunger.' },
{ target: 'Palihug, hatagi ko ug tubig.', gloss: 'Bitte gib mir Wasser.' },
{ target: 'Salamat sa pagkaon.', gloss: 'Danke für das Essen.' },
{ target: 'Busog na ko.', gloss: 'Ich bin satt.' },
{ target: 'Lami kaayo.', gloss: 'Sehr lecker.' }
],
grammarFocus: [
{ title: 'na als Zustandsmarker', text: '"na" markiert oft etwas, das bereits eingetreten ist oder jetzt gilt.', example: 'Nikaon na ka?' }
{ title: 'na als Zustandsmarker', text: '"na" markiert oft etwas, das bereits eingetreten ist oder jetzt gilt.', example: 'Nikaon na ka?' },
{ title: 'Bitten mit hatagi ko', text: 'Mit "hatagi ko" bittest du konkret darum, dass dir etwas gegeben wird.', example: 'Palihug, hatagi ko ug tubig.' }
],
speakingPrompts: [
{ title: 'Fürsorge-Dialog', prompt: 'Frage, ob jemand schon gegessen hat, und biete Essen oder Wasser an.', cue: 'Nikaon na ka? Gusto ka mokaon?' }
{ title: 'Fürsorge-Dialog', prompt: 'Frage, ob jemand schon gegessen hat, und biete Essen oder Wasser an.', cue: 'Nikaon na ka? Gusto ka mokaon?' },
{ title: 'Beim Essen reagieren', prompt: 'Sage, dass du Hunger hast, bitte um Wasser und reagiere danach auf das Essen.', cue: 'Gutom na ko. Palihug, hatagi ko ug tubig. Lami kaayo.' }
],
practicalTasks: [{ title: 'Rollenspiel', text: 'Spiele ein kurzes Gespräch zwischen Gastgeber und Gast beim Essen.' }]
},
'Zeitformen - Grundlagen': {
'Essen & Trinken': {
learningGoals: [
'Ni- und Mo- als einfache Zeitmarker unterscheiden.',
'Kurze Sätze in Vergangenheit und Zukunft bilden.',
'Das Muster laut mit mehreren Verben wiederholen.'
'Wichtige Essens- und Trinkwörter schnell erkennen.',
'Zwischen Grundnahrungsmitteln, Getränken und Beilagen unterscheiden.',
'Mit den neuen Wörtern kurze Einkaufs- oder Tischsätze bauen.'
],
corePatterns: [
{ target: 'Kan-on', gloss: 'gekochter Reis' },
{ target: 'Tubig', gloss: 'Wasser' },
{ target: 'Isda', gloss: 'Fisch' },
{ target: 'Manok', gloss: 'Huhn' },
{ target: 'Gulay', gloss: 'Gemüse' },
{ target: 'Prutas', gloss: 'Obst' },
{ target: 'Gatas', gloss: 'Milch' }
],
corePatterns: ['Ni-kaon ko.', 'Mo-kaon ko.', 'Ni-adto ko.', 'Mo-adto ko.'],
grammarFocus: [
{ title: 'Zeitpräfixe', text: 'Ni- verweist auf Vergangenes, Mo- auf Zukünftiges oder Bevorstehendes.', example: 'Ni-kaon ko. / Mo-kaon ko.' }
{ title: 'Wortschatz statt ganzer Sätze', text: 'In dieser Lektion sammelst du bewusst Grundwörter, damit du später kurze Essenssätze daraus bauen kannst.', example: 'Kan-on. Tubig. Isda.' }
],
speakingPrompts: [
{ title: 'Vorher und nachher', prompt: 'Sage einen Satz über etwas, das du getan hast, und einen Satz über etwas, das du tun wirst.', cue: 'Ni-kaon ko. Mo-adto ko.' }
{ title: 'Auf dem Tisch', prompt: 'Nenne drei Dinge, die auf dem Tisch stehen oder die du essen und trinken möchtest.', cue: 'Kan-on, isda ug tubig.' }
],
practicalTasks: [{ title: 'Mustertraining', text: 'Nimm ein Verb und sprich es einmal mit Ni- und einmal mit Mo-.' }]
practicalTasks: [{ title: 'Küchenrunde', text: 'Zeige nacheinander auf sieben Lebensmittel oder stelle sie dir vor und sprich jedes Wort laut aus.' }]
},
'Alltagsgespräche - Teil 1': {
learningGoals: [
'Alltagsaktivitäten in ganzen Sätzen beschreiben.',
'Nach Tagesplan, Aufgaben und Rückkehr fragen.',
'Kurze Familienabsprachen für den Tag sicher führen.'
],
corePatterns: [
{ target: 'Unsa imong buhat karon?', gloss: 'Was machst du heute?' },
{ target: 'Nagluto ko para sa panihapon.', gloss: 'Ich koche für das Abendessen.' },
{ target: 'Naglimpyo ko sa balay.', gloss: 'Ich putze das Haus.' },
{ target: 'Human na ka sa trabaho?', gloss: 'Bist du mit der Arbeit fertig?' },
{ target: 'Dali lang ko mubalik.', gloss: 'Ich komme gleich wieder.' },
{ target: 'Naa koy lakaw karong hapon.', gloss: 'Ich habe heute Nachmittag etwas zu erledigen.' },
{ target: 'Magpahuway ko gamay unya.', gloss: 'Ich ruhe mich später kurz aus.' },
{ target: 'Tawagi ko kung mahuman ka.', gloss: 'Ruf mich an, wenn du fertig bist.' }
],
speakingPrompts: [
{ title: 'Tagesablauf abstimmen', prompt: 'Frage nach dem Plan und sage, was du heute erledigst.', cue: 'Unsa imong buhat karon? Naglimpyo ko sa balay.' }
],
practicalTasks: [{ title: 'Alltagscheck', text: 'Sprich vier Sätze zu deinem heutigen Ablauf: Aufgabe, Erledigung, Rückkehr und Pause.' }]
},
'Haus & Familie': {
learningGoals: [
'Wichtige Wörter für Haus, Räume und Familie zuordnen und aussprechen.',
'Mit „Naa … sa …“ sagen, wo sich jemand oder etwas im Haus befindet.',
'Kurze Sätze über Zuhause und Familie verstehen und nachsprechen.'
],
corePatterns: [
{ target: 'Balay', gloss: 'Haus' },
{ target: 'Kwarto', gloss: 'Zimmer' },
{ target: 'Kusina', gloss: 'Küche' },
{ target: 'Sala', gloss: 'Wohnzimmer' },
{ target: 'Banyo', gloss: 'Badezimmer' },
{ target: 'Pultahan', gloss: 'Tür' },
{ target: 'Bintana', gloss: 'Fenster' },
{ target: 'Atop', gloss: 'Dach' },
{ target: 'Pamilya', gloss: 'Familie' },
{ target: 'Among pamilya', gloss: 'unsere Familie' },
{ target: 'Naa ko sa balay.', gloss: 'Ich bin zu Hause.' },
{ target: 'Naa sila sa kusina.', gloss: 'Sie sind in der Küche.' },
{ target: 'Asa ang kusina?', gloss: 'Wo ist die Küche?' },
{ target: 'Ang among balay.', gloss: 'Unser Haus.' }
],
grammarFocus: [
{
title: 'Naa … sa … für Ort',
text: '„Naa“ drückt aus, dass jemand oder etwas irgendwo ist; „sa“ verbindet mit dem Ort.',
example: 'Naa ko sa balay. Naa sila sa kusina.'
},
{
title: 'among = unser (Plural inklusiv)',
text: '„Among“ passt zu „wir/unsere“ im Sinne von Familie oder Gruppe.',
example: 'Among pamilya. Ang among balay.'
}
],
speakingPrompts: [
{
title: 'Räume benennen',
prompt: 'Nenne Küche, Wohnzimmer und Badezimmer auf Bisaya.',
cue: 'Kusina, sala, banyo.'
},
{
title: 'Wer ist wo?',
prompt: 'Sage, dass du zu Hause bist, und frage, wo die Küche ist.',
cue: 'Naa ko sa balay. Asa ang kusina?'
}
],
practicalTasks: [
{
title: 'Rundgang',
text: 'Geh in Gedanken durch dein Zuhause und benenne jeden Raum laut auf Bisaya.'
}
]
},
'Zeitformen - Grundlagen': {
learningGoals: [
'Vergangenheit, laufende Handlung und Zukunft in einfachen Alltagssätzen unterscheiden.',
'Zeitmarker (ni-, nag-/ga-, mo-) mit Zeitwörtern sinnvoll kombinieren.',
'Zwischen denselben Verben in drei Zeitbezügen sicher wechseln.'
],
corePatterns: [
{ target: 'Ni-kaon ko ganiha.', gloss: 'Ich habe vorhin gegessen.' },
{ target: 'Nagkaon ko karon.', gloss: 'Ich esse gerade jetzt.' },
{ target: 'Mo-kaon ko unya.', gloss: 'Ich werde später essen.' },
{ target: 'Ni-adto ko sa merkado ganiha.', gloss: 'Ich bin vorhin zum Markt gegangen.' },
{ target: 'Naa ko sa merkado karon.', gloss: 'Ich bin jetzt auf dem Markt.' },
{ target: 'Mo-adto ko sa merkado ugma.', gloss: 'Ich werde morgen zum Markt gehen.' },
{ target: 'Nipalit ko og isda ganiha.', gloss: 'Ich habe vorhin Fisch gekauft.' },
{ target: 'Mupalit ko og isda ugma.', gloss: 'Ich werde morgen Fisch kaufen.' }
],
grammarFocus: [
{ title: 'Vergangenheit mit ni-', text: 'ni- markiert im Grundkurs häufig abgeschlossene Handlungen in der Vergangenheit.', example: 'Ni-kaon ko ganiha.' },
{ title: 'Laufende Handlung mit nag-/ga-', text: 'Für gerade laufende oder aktuelle Handlungen wird oft nag-/ga- genutzt, häufig zusammen mit karon.', example: 'Nagkaon ko karon.' },
{ title: 'Zukunft/Absicht mit mo-', text: 'mo- markiert im Kurs Zukünftiges oder Vorhaben und wird oft mit unya/ugma kombiniert.', example: 'Mo-adto ko ugma.' },
{ title: 'Zeitwörter als Klarsteller', text: 'Wörter wie ganiha, karon und ugma helfen, den Zeitbezug eindeutig zu machen.', example: 'ganiha (vorhin), karon (jetzt), ugma (morgen)' }
],
speakingPrompts: [
{ title: 'Dreierschritt Zeit', prompt: 'Formuliere denselben Inhalt nacheinander für Vergangenheit, Gegenwart und Zukunft.', cue: 'Ni-adto ko ganiha. Naa ko diri karon. Mo-adto ko ugma.' },
{ title: 'Tagesplanung mit Zeiten', prompt: 'Sage, was du vorhin getan hast, was du jetzt machst und was du später tun wirst.', cue: 'Nipalit ko ganiha. Nagluto ko karon. Mo-kaon ko unya.' }
],
practicalTasks: [
{ title: 'Verb-Staffel', text: 'Nimm drei Verben (z. B. kaon, adto, palit) und bilde jeweils Vergangenheit, Gegenwart und Zukunft laut.' },
{ title: 'Zeitkarten', text: 'Ziehe zufällig ein Zeitwort (ganiha/karon/unya/ugma) und bilde sofort einen passenden Satz.' }
]
},
'Alltagsgespräche - Teil 2': {
learningGoals: [
'Ziele, Wege und Zeitpunkte im Alltag genauer angeben.',
'Verabredungen und Rückkehrzeiten in ganzen Sätzen formulieren.',
'Einkaufs- und Familienwege natürlich besprechen.'
],
corePatterns: [
{ target: 'Asa ka moadto unya?', gloss: 'Wohin gehst du später?' },
{ target: 'Moadto ko sa merkado unya.', gloss: 'Ich gehe später zum Markt.' },
{ target: 'Unsa imong plano karong gabii?', gloss: 'Was ist dein Plan heute Abend?' },
{ target: 'Magkita mi sa silingan karon.', gloss: 'Wir treffen jetzt die Nachbarn.' },
{ target: 'Mupalit ko ug pagkaon para sa balay.', gloss: 'Ich kaufe Essen für zu Hause.' },
{ target: 'Mahimo ba ta magkita ugma buntag?', gloss: 'Können wir uns morgen früh treffen?' },
{ target: 'Unsa orasa ka mouli?', gloss: 'Um wie viel Uhr kommst du nach Hause?' },
{ target: 'Mouli ko mga alas sais.', gloss: 'Ich komme gegen sechs nach Hause.' }
],
speakingPrompts: [
{ title: 'Weg und Zeit planen', prompt: 'Frage nach Ziel und Uhrzeit und gib eine konkrete Antwort.', cue: 'Asa ka moadto unya? Mouli ko mga alas sais.' }
],
practicalTasks: [{ title: 'Planungsdialog', text: 'Baue einen Mini-Dialog mit Zielort, Uhrzeit und Rückkehr in mindestens vier Sätzen.' }]
},
'Woche 1 - Wiederholung': {
learningGoals: [
@@ -95,9 +373,19 @@ const LESSON_DIDACTICS = {
'Von einzelnen Wörtern zu kurzen Sätzen übergehen.'
],
corePatterns: ['Kumusta', 'Salamat', 'Lami', 'Mingaw ko nimo']
}
},
...BISAYA_RELATIONSHIP_ANCHOR_DIDACTICS,
...BISAYA_DIDACTICS_24_43
};
function resolveDidacticsForLesson(lesson) {
const direct = LESSON_DIDACTICS[lesson.title];
if (direct) return direct;
const plannedTitle = BISAYA_LESSONS_24_43_BY_NUMBER[Number(lesson.lessonNumber)]?.title;
if (plannedTitle && LESSON_DIDACTICS[plannedTitle]) return LESSON_DIDACTICS[plannedTitle];
return null;
}
async function resetBisayaProgress(courseIds) {
if (courseIds.length === 0) return { lessonProgress: 0, exerciseProgress: 0 };
@@ -170,15 +458,36 @@ async function applyBisayaCourseRefresh() {
});
for (const lesson of lessons) {
const didactics = LESSON_DIDACTICS[lesson.title];
if (!didactics) continue;
const didactics = resolveDidacticsForLesson(lesson);
const plannedLesson = BISAYA_LESSONS_24_43_BY_NUMBER[Number(lesson.lessonNumber)];
const pedagogy = getBisayaLessonPedagogy(lesson.lessonNumber);
if (!didactics && !pedagogy) continue;
const normalizedDidactics = didactics || {};
await lesson.update({
learningGoals: didactics.learningGoals || [],
corePatterns: didactics.corePatterns || [],
grammarFocus: didactics.grammarFocus || [],
speakingPrompts: didactics.speakingPrompts || [],
practicalTasks: didactics.practicalTasks || []
...(plannedLesson ? {
title: plannedLesson.title,
description: plannedLesson.desc,
weekNumber: plannedLesson.week,
dayNumber: plannedLesson.day,
lessonType: plannedLesson.type,
culturalNotes: plannedLesson.cultural,
targetMinutes: plannedLesson.targetMin,
targetScorePercent: plannedLesson.targetScore,
requiresReview: plannedLesson.review
} : {}),
learningGoals: normalizedDidactics.learningGoals || [],
corePatterns: normalizedDidactics.corePatterns || [],
grammarFocus: normalizedDidactics.grammarFocus || [],
speakingPrompts: normalizedDidactics.speakingPrompts || [],
practicalTasks: normalizedDidactics.practicalTasks || [],
didacticMode: pedagogy?.didacticMode || null,
phaseLabel: pedagogy?.phaseLabel || null,
blockNumber: pedagogy?.blockNumber ?? null,
difficultyWeight: pedagogy?.difficultyWeight ?? null,
newUnitTarget: pedagogy?.newUnitTarget ?? null,
reviewWeight: pedagogy?.reviewWeight ?? null,
isIntensiveReview: Boolean(pedagogy?.isIntensiveReview)
});
updatedLessons++;
}

View File

@@ -0,0 +1,188 @@
export const BISAYA_PHASE1_DIDACTICS = {
'Begrüßungen & Höflichkeit': {
corePatterns: [
{ target: 'Kumusta ka?', gloss: 'Wie geht es dir?' },
{ target: 'Maayong buntag.', gloss: 'Guten Morgen.' },
{ target: 'Maayong adlaw.', gloss: 'Guten Tag.' },
{ target: 'Maayong gabii.', gloss: 'Guten Abend.' },
{ target: 'Maayong gabii, matulog na ta.', gloss: 'Guten Abend, wir legen uns schlafen.' },
{ target: 'Katulog og maayo.', gloss: 'Schlaf gut.' },
{ target: 'Kapoy na ka?', gloss: 'Bist du müde?' },
{ target: 'Matulog na ta.', gloss: 'Lass uns schlafen gehen.' },
{ target: 'Inom sa og tubig.', gloss: 'Trink Wasser.' },
{ target: 'Patya ang suga.', gloss: 'Mach das Licht aus.' },
{ target: 'Tabuni ang imong kaugalingon.', gloss: 'Deck dich zu.' },
{ target: 'Ugma nasad.', gloss: 'Bis morgen wieder.' },
{ target: 'Damgo og nindot.', gloss: 'Träum schön.' },
{ target: 'Amping.', gloss: 'Pass auf dich auf.' },
{ target: 'Babay.', gloss: 'Tschüss.' },
{ target: 'Maayo ko.', gloss: 'Mir geht es gut.' },
{ target: 'Salamat.', gloss: 'Danke.' },
{ target: 'Palihug.', gloss: 'Bitte.' }
]
}
};
/**
* Volle Didaktik-Snippets für Lektionen, die in update-bisaya-didactics.js gepflegt werden sollen
* und als API-Fallback dienen, wenn core_patterns in der DB fehlen (z. B. Kurz-Wiederholung).
*/
export const BISAYA_DIDACTICS_FRAGMENTS = {
'Alltagsgespräche - Teil 1': {
learningGoals: [
'Alltagsaktivitäten in ganzen Sätzen beschreiben.',
'Nach Tagesplan, Aufgaben und Rückkehr fragen.',
'Kurze Familienabsprachen für den Tag sicher führen.'
],
corePatterns: [
{ target: 'Unsa imong buhat karon?', gloss: 'Was machst du heute?' },
{ target: 'Nagluto ko para sa panihapon.', gloss: 'Ich koche für das Abendessen.' },
{ target: 'Naglimpyo ko sa balay.', gloss: 'Ich putze das Haus.' },
{ target: 'Human na ka sa trabaho?', gloss: 'Bist du mit der Arbeit fertig?' },
{ target: 'Dali lang ko mubalik.', gloss: 'Ich komme gleich wieder.' },
{ target: 'Naa koy lakaw karong hapon.', gloss: 'Ich habe heute Nachmittag etwas zu erledigen.' },
{ target: 'Magpahuway ko gamay unya.', gloss: 'Ich ruhe mich später kurz aus.' },
{ target: 'Tawagi ko kung mahuman ka.', gloss: 'Ruf mich an, wenn du fertig bist.' }
],
speakingPrompts: [
{
title: 'Tagesablauf abstimmen',
prompt: 'Frage nach dem Plan und sage, was du heute erledigst.',
cue: 'Unsa imong buhat karon? Naglimpyo ko sa balay.'
}
],
practicalTasks: [
{
title: 'Alltagscheck',
text: 'Sprich vier Sätze zu deinem heutigen Ablauf: Aufgabe, Erledigung, Rückkehr und Pause.'
}
]
},
'Haus & Familie': {
learningGoals: [
'Wichtige Wörter für Haus, Räume und Familie zuordnen und aussprechen.',
'Mit „Naa … sa …“ sagen, wo sich jemand oder etwas im Haus befindet.',
'Kurze Sätze über Zuhause und Familie verstehen und nachsprechen.'
],
corePatterns: [
{ target: 'Balay', gloss: 'Haus' },
{ target: 'Kwarto', gloss: 'Zimmer' },
{ target: 'Kusina', gloss: 'Küche' },
{ target: 'Sala', gloss: 'Wohnzimmer' },
{ target: 'Banyo', gloss: 'Badezimmer' },
{ target: 'Pultahan', gloss: 'Tür' },
{ target: 'Bintana', gloss: 'Fenster' },
{ target: 'Atop', gloss: 'Dach' },
{ target: 'Pamilya', gloss: 'Familie' },
{ target: 'Among pamilya', gloss: 'unsere Familie' },
{ target: 'Naa ko sa balay.', gloss: 'Ich bin zu Hause.' },
{ target: 'Naa sila sa kusina.', gloss: 'Sie sind in der Küche.' },
{ target: 'Asa ang kusina?', gloss: 'Wo ist die Küche?' },
{ target: 'Ang among balay.', gloss: 'Unser Haus.' }
],
grammarFocus: [
{
title: 'Naa … sa … für Ort',
text: '„Naa“ drückt aus, dass jemand oder etwas irgendwo ist; „sa“ verbindet mit dem Ort.',
example: 'Naa ko sa balay. Naa sila sa kusina.'
},
{
title: 'among = unser (Plural inklusiv)',
text: '„Among“ passt zu „wir/unsere“ im Sinne von Familie oder Gruppe.',
example: 'Among pamilya. Ang among balay.'
}
],
speakingPrompts: [
{
title: 'Räume benennen',
prompt: 'Nenne Küche, Wohnzimmer und Badezimmer auf Bisaya.',
cue: 'Kusina, sala, banyo.'
},
{
title: 'Wer ist wo?',
prompt: 'Sage, dass du zu Hause bist, und frage, wo die Küche ist.',
cue: 'Naa ko sa balay. Asa ang kusina?'
}
],
practicalTasks: [
{
title: 'Rundgang',
text: 'Geh in Gedanken durch dein Zuhause und benenne jeden Raum laut auf Bisaya.'
}
]
},
'Alltagsgespräche - Teil 2': {
learningGoals: [
'Ziele, Wege und Zeitpunkte im Alltag genauer angeben.',
'Verabredungen und Rückkehrzeiten in ganzen Sätzen formulieren.',
'Einkaufs- und Familienwege natürlich besprechen.'
],
corePatterns: [
{ target: 'Asa ka moadto unya?', gloss: 'Wohin gehst du später?' },
{ target: 'Moadto ko sa merkado unya.', gloss: 'Ich gehe später zum Markt.' },
{ target: 'Unsa imong plano karong gabii?', gloss: 'Was ist dein Plan heute Abend?' },
{ target: 'Magkita mi sa silingan karon.', gloss: 'Wir treffen jetzt die Nachbarn.' },
{ target: 'Mupalit ko ug pagkaon para sa balay.', gloss: 'Ich kaufe Essen für zu Hause.' },
{ target: 'Mahimo ba ta magkita ugma buntag?', gloss: 'Können wir uns morgen früh treffen?' },
{ target: 'Unsa orasa ka mouli?', gloss: 'Um wie viel Uhr kommst du nach Hause?' },
{ target: 'Mouli ko mga alas sais.', gloss: 'Ich komme gegen sechs nach Hause.' }
],
speakingPrompts: [
{
title: 'Weg und Zeit planen',
prompt: 'Frage nach Ziel und Uhrzeit und gib eine konkrete Antwort.',
cue: 'Asa ka moadto unya? Mouli ko mga alas sais.'
}
],
practicalTasks: [
{
title: 'Planungsdialog',
text: 'Baue einen Mini-Dialog mit Zielort, Uhrzeit und Rückkehr in mindestens vier Sätzen.'
}
]
},
'Ort & Richtung': {
learningGoals: [
'Fragen nach Ort und Richtung sicher stellen und beantworten.',
'„hier“, „dort“ und „unterwegs nach …“ im Alltag unterscheiden.',
'Kurze Weg- und Zielangaben mit bekannten Ortswörtern verbinden.'
],
corePatterns: [
{ target: 'Asa ka?', gloss: 'Wo bist du? / Wohin gehst du?' },
{ target: 'Asa ang merkado?', gloss: 'Wo ist der Markt?' },
{ target: 'Asa ang simbahan?', gloss: 'Wo ist die Kirche?' },
{ target: 'Dinhi ko.', gloss: 'Ich bin hier.' },
{ target: 'Naa ko dinhi.', gloss: 'Ich bin hier.' },
{ target: 'Didto siya.', gloss: 'Er/sie ist dort.' },
{ target: 'Adto ta didto.', gloss: 'Lass uns dorthin gehen.' },
{ target: 'Padulong ko sa merkado.', gloss: 'Ich bin auf dem Weg zum Markt.' },
{ target: 'Padulong ta didto.', gloss: 'Lass uns dorthin aufbrechen.' },
{ target: 'Naa ko sa balay.', gloss: 'Ich bin zu Hause.' }
],
grammarFocus: [
{
title: 'Asa = Wo / Wohin',
text: '„Asa“ fragt nach Ort oder Ziel. Mit „ang“ kannst du nach einem bestimmten Ort fragen.',
example: 'Asa ka? Asa ang merkado?'
},
{
title: 'dinhi / didto',
text: '„dinhi“ = hier, „didto“ = dort (weg vom Sprecher).',
example: 'Naa ko dinhi. Didto ang simbahan.'
}
],
speakingPrompts: [
{
title: 'Weg fragen',
prompt: 'Frage, wo der Markt ist, und sage dann, dass du dorthin unterwegs bist.',
cue: 'Asa ang merkado? Padulong ko sa merkado.'
}
],
practicalTasks: [
{
title: 'Mini-Route',
text: 'Bilde drei Sätze: wo du bist, wohin du willst, und dass du unterwegs bist.'
}
]
}
};

View File

@@ -0,0 +1,160 @@
export const BISAYA_LESSON_PEDAGOGY = {
1: { phaseLabel: 'quickstart', blockNumber: 1, didacticMode: 'guided_dialogue', difficultyWeight: 2, newUnitTarget: 5, reviewWeight: 15, isIntensiveReview: false },
2: { phaseLabel: 'quickstart', blockNumber: 1, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 8, reviewWeight: 20, isIntensiveReview: false },
3: { phaseLabel: 'quickstart', blockNumber: 1, didacticMode: 'core_input', difficultyWeight: 2, newUnitTarget: 7, reviewWeight: 20, isIntensiveReview: false },
4: { phaseLabel: 'quickstart', blockNumber: 1, didacticMode: 'guided_dialogue', difficultyWeight: 3, newUnitTarget: 5, reviewWeight: 25, isIntensiveReview: false },
5: { phaseLabel: 'quickstart', blockNumber: 1, didacticMode: 'guided_dialogue', difficultyWeight: 3, newUnitTarget: 4, reviewWeight: 25, isIntensiveReview: false },
6: { phaseLabel: 'quickstart', blockNumber: 1, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 7, reviewWeight: 25, isIntensiveReview: false },
7: { phaseLabel: 'quickstart', blockNumber: 1, didacticMode: 'guided_dialogue', difficultyWeight: 3, newUnitTarget: 5, reviewWeight: 30, isIntensiveReview: false },
8: { phaseLabel: 'quickstart', blockNumber: 1, didacticMode: 'core_input', difficultyWeight: 2, newUnitTarget: 6, reviewWeight: 30, isIntensiveReview: false },
9: { phaseLabel: 'quickstart', blockNumber: 1, didacticMode: 'intensive_review', difficultyWeight: 3, newUnitTarget: 0, reviewWeight: 90, isIntensiveReview: true },
10: { phaseLabel: 'quickstart', blockNumber: 1, didacticMode: 'checkpoint', difficultyWeight: 3, newUnitTarget: 0, reviewWeight: 95, isIntensiveReview: false },
11: { phaseLabel: 'quickstart', blockNumber: 2, didacticMode: 'guided_dialogue', difficultyWeight: 3, newUnitTarget: 5, reviewWeight: 25, isIntensiveReview: false },
12: { phaseLabel: 'quickstart', blockNumber: 2, didacticMode: 'core_input', difficultyWeight: 2, newUnitTarget: 7, reviewWeight: 25, isIntensiveReview: false },
13: { phaseLabel: 'quickstart', blockNumber: 2, didacticMode: 'guided_dialogue', difficultyWeight: 3, newUnitTarget: 5, reviewWeight: 30, isIntensiveReview: false },
14: { phaseLabel: 'quickstart', blockNumber: 2, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 30, isIntensiveReview: false },
15: { phaseLabel: 'quickstart', blockNumber: 2, didacticMode: 'pattern_drill', difficultyWeight: 4, newUnitTarget: 4, reviewWeight: 40, isIntensiveReview: false },
16: { phaseLabel: 'quickstart', blockNumber: 2, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 35, isIntensiveReview: false },
17: { phaseLabel: 'quickstart', blockNumber: 2, didacticMode: 'guided_dialogue', difficultyWeight: 4, newUnitTarget: 5, reviewWeight: 35, isIntensiveReview: false },
18: { phaseLabel: 'quickstart', blockNumber: 2, didacticMode: 'core_input', difficultyWeight: 4, newUnitTarget: 7, reviewWeight: 35, isIntensiveReview: false },
19: { phaseLabel: 'quickstart', blockNumber: 2, didacticMode: 'core_input', difficultyWeight: 4, newUnitTarget: 8, reviewWeight: 36, isIntensiveReview: false },
20: { phaseLabel: 'quickstart', blockNumber: 2, didacticMode: 'core_input', difficultyWeight: 4, newUnitTarget: 7, reviewWeight: 36, isIntensiveReview: false },
21: { phaseLabel: 'quickstart', blockNumber: 2, didacticMode: 'core_input', difficultyWeight: 4, newUnitTarget: 6, reviewWeight: 36, isIntensiveReview: false },
22: { phaseLabel: 'quickstart', blockNumber: 2, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 92, isIntensiveReview: true },
23: { phaseLabel: 'quickstart', blockNumber: 2, didacticMode: 'checkpoint', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 96, isIntensiveReview: false },
24: { phaseLabel: 'daily_life', blockNumber: 3, didacticMode: 'guided_dialogue', difficultyWeight: 3, newUnitTarget: 5, reviewWeight: 35, isIntensiveReview: false },
25: { phaseLabel: 'daily_life', blockNumber: 3, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 7, reviewWeight: 35, isIntensiveReview: false },
26: { phaseLabel: 'daily_life', blockNumber: 3, didacticMode: 'guided_dialogue', difficultyWeight: 4, newUnitTarget: 5, reviewWeight: 40, isIntensiveReview: false },
27: { phaseLabel: 'daily_life', blockNumber: 3, didacticMode: 'core_input', difficultyWeight: 4, newUnitTarget: 6, reviewWeight: 40, isIntensiveReview: false },
28: { phaseLabel: 'daily_life', blockNumber: 3, didacticMode: 'pattern_drill', difficultyWeight: 4, newUnitTarget: 4, reviewWeight: 45, isIntensiveReview: false },
29: { phaseLabel: 'daily_life', blockNumber: 3, didacticMode: 'guided_dialogue', difficultyWeight: 4, newUnitTarget: 5, reviewWeight: 45, isIntensiveReview: false },
30: { phaseLabel: 'daily_life', blockNumber: 3, didacticMode: 'guided_dialogue', difficultyWeight: 3, newUnitTarget: 5, reviewWeight: 40, isIntensiveReview: false },
31: { phaseLabel: 'daily_life', blockNumber: 3, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 40, isIntensiveReview: false },
32: { phaseLabel: 'daily_life', blockNumber: 3, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 94, isIntensiveReview: true },
33: { phaseLabel: 'daily_life', blockNumber: 3, didacticMode: 'checkpoint', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 97, isIntensiveReview: false },
34: { phaseLabel: 'stabilization', blockNumber: 4, didacticMode: 'real_life_scenario', difficultyWeight: 4, newUnitTarget: 4, reviewWeight: 55, isIntensiveReview: false },
35: { phaseLabel: 'stabilization', blockNumber: 4, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 95, isIntensiveReview: true },
36: { phaseLabel: 'stabilization', blockNumber: 4, didacticMode: 'real_life_scenario', difficultyWeight: 4, newUnitTarget: 4, reviewWeight: 60, isIntensiveReview: false },
37: { phaseLabel: 'stabilization', blockNumber: 4, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 95, isIntensiveReview: true },
38: { phaseLabel: 'stabilization', blockNumber: 4, didacticMode: 'real_life_scenario', difficultyWeight: 5, newUnitTarget: 3, reviewWeight: 65, isIntensiveReview: false },
39: { phaseLabel: 'stabilization', blockNumber: 4, didacticMode: 'intensive_review', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 98, isIntensiveReview: true },
40: { phaseLabel: 'stabilization', blockNumber: 4, didacticMode: 'real_life_scenario', difficultyWeight: 5, newUnitTarget: 3, reviewWeight: 70, isIntensiveReview: false },
41: { phaseLabel: 'stabilization', blockNumber: 4, didacticMode: 'checkpoint', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 99, isIntensiveReview: false },
42: { phaseLabel: 'stabilization', blockNumber: 4, didacticMode: 'checkpoint', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 100, isIntensiveReview: false },
43: { phaseLabel: 'stabilization', blockNumber: 4, didacticMode: 'real_life_scenario', difficultyWeight: 2, newUnitTarget: 2, reviewWeight: 50, isIntensiveReview: false },
44: { phaseLabel: 'daily_life', blockNumber: 5, didacticMode: 'guided_dialogue', difficultyWeight: 3, newUnitTarget: 5, reviewWeight: 40, isIntensiveReview: false },
45: { phaseLabel: 'daily_life', blockNumber: 5, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 40, isIntensiveReview: false },
46: { phaseLabel: 'daily_life', blockNumber: 5, didacticMode: 'pattern_drill', difficultyWeight: 4, newUnitTarget: 4, reviewWeight: 45, isIntensiveReview: false },
47: { phaseLabel: 'daily_life', blockNumber: 5, didacticMode: 'guided_dialogue', difficultyWeight: 3, newUnitTarget: 5, reviewWeight: 45, isIntensiveReview: false },
48: { phaseLabel: 'daily_life', blockNumber: 5, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 95, isIntensiveReview: true },
49: { phaseLabel: 'daily_life', blockNumber: 5, didacticMode: 'intensive_review', difficultyWeight: 3, newUnitTarget: 0, reviewWeight: 90, isIntensiveReview: true },
50: { phaseLabel: 'daily_life', blockNumber: 5, didacticMode: 'guided_dialogue', difficultyWeight: 4, newUnitTarget: 5, reviewWeight: 45, isIntensiveReview: false },
51: { phaseLabel: 'daily_life', blockNumber: 5, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 45, isIntensiveReview: false },
52: { phaseLabel: 'daily_life', blockNumber: 5, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 96, isIntensiveReview: true },
53: { phaseLabel: 'daily_life', blockNumber: 5, didacticMode: 'checkpoint', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 97, isIntensiveReview: false },
54: { phaseLabel: 'stabilization', blockNumber: 6, didacticMode: 'guided_dialogue', difficultyWeight: 4, newUnitTarget: 5, reviewWeight: 50, isIntensiveReview: false },
55: { phaseLabel: 'stabilization', blockNumber: 6, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 50, isIntensiveReview: false },
56: { phaseLabel: 'stabilization', blockNumber: 6, didacticMode: 'guided_dialogue', difficultyWeight: 4, newUnitTarget: 5, reviewWeight: 55, isIntensiveReview: false },
57: { phaseLabel: 'stabilization', blockNumber: 6, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 55, isIntensiveReview: false },
58: { phaseLabel: 'stabilization', blockNumber: 6, didacticMode: 'real_life_scenario', difficultyWeight: 5, newUnitTarget: 3, reviewWeight: 65, isIntensiveReview: false },
59: { phaseLabel: 'stabilization', blockNumber: 6, didacticMode: 'intensive_review', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 98, isIntensiveReview: true },
60: { phaseLabel: 'stabilization', blockNumber: 6, didacticMode: 'real_life_scenario', difficultyWeight: 5, newUnitTarget: 3, reviewWeight: 70, isIntensiveReview: false },
61: { phaseLabel: 'stabilization', blockNumber: 6, didacticMode: 'checkpoint', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 99, isIntensiveReview: false },
62: { phaseLabel: 'stabilization', blockNumber: 6, didacticMode: 'checkpoint', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 100, isIntensiveReview: false },
63: { phaseLabel: 'stabilization', blockNumber: 6, didacticMode: 'real_life_scenario', difficultyWeight: 2, newUnitTarget: 2, reviewWeight: 60, isIntensiveReview: false },
64: { phaseLabel: 'daily_life', blockNumber: 7, didacticMode: 'guided_dialogue', difficultyWeight: 3, newUnitTarget: 5, reviewWeight: 45, isIntensiveReview: false },
65: { phaseLabel: 'daily_life', blockNumber: 7, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 45, isIntensiveReview: false },
66: { phaseLabel: 'daily_life', blockNumber: 7, didacticMode: 'pattern_drill', difficultyWeight: 4, newUnitTarget: 4, reviewWeight: 50, isIntensiveReview: false },
67: { phaseLabel: 'daily_life', blockNumber: 7, didacticMode: 'guided_dialogue', difficultyWeight: 3, newUnitTarget: 5, reviewWeight: 50, isIntensiveReview: false },
68: { phaseLabel: 'daily_life', blockNumber: 7, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 95, isIntensiveReview: true },
69: { phaseLabel: 'daily_life', blockNumber: 7, didacticMode: 'intensive_review', difficultyWeight: 3, newUnitTarget: 0, reviewWeight: 92, isIntensiveReview: true },
70: { phaseLabel: 'daily_life', blockNumber: 7, didacticMode: 'guided_dialogue', difficultyWeight: 3, newUnitTarget: 5, reviewWeight: 50, isIntensiveReview: false },
71: { phaseLabel: 'daily_life', blockNumber: 7, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 50, isIntensiveReview: false },
72: { phaseLabel: 'daily_life', blockNumber: 7, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 96, isIntensiveReview: true },
73: { phaseLabel: 'daily_life', blockNumber: 7, didacticMode: 'checkpoint', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 97, isIntensiveReview: false },
74: { phaseLabel: 'daily_life', blockNumber: 8, didacticMode: 'guided_dialogue', difficultyWeight: 4, newUnitTarget: 5, reviewWeight: 50, isIntensiveReview: false },
75: { phaseLabel: 'daily_life', blockNumber: 8, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 50, isIntensiveReview: false },
76: { phaseLabel: 'daily_life', blockNumber: 8, didacticMode: 'pattern_drill', difficultyWeight: 4, newUnitTarget: 4, reviewWeight: 55, isIntensiveReview: false },
77: { phaseLabel: 'daily_life', blockNumber: 8, didacticMode: 'guided_dialogue', difficultyWeight: 4, newUnitTarget: 5, reviewWeight: 55, isIntensiveReview: false },
78: { phaseLabel: 'daily_life', blockNumber: 8, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 95, isIntensiveReview: true },
79: { phaseLabel: 'daily_life', blockNumber: 8, didacticMode: 'intensive_review', difficultyWeight: 3, newUnitTarget: 0, reviewWeight: 92, isIntensiveReview: true },
80: { phaseLabel: 'daily_life', blockNumber: 8, didacticMode: 'guided_dialogue', difficultyWeight: 3, newUnitTarget: 5, reviewWeight: 55, isIntensiveReview: false },
81: { phaseLabel: 'daily_life', blockNumber: 8, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 55, isIntensiveReview: false },
82: { phaseLabel: 'daily_life', blockNumber: 8, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 96, isIntensiveReview: true },
83: { phaseLabel: 'daily_life', blockNumber: 8, didacticMode: 'checkpoint', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 97, isIntensiveReview: false },
84: { phaseLabel: 'daily_life', blockNumber: 9, didacticMode: 'guided_dialogue', difficultyWeight: 4, newUnitTarget: 5, reviewWeight: 50, isIntensiveReview: false },
85: { phaseLabel: 'daily_life', blockNumber: 9, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 50, isIntensiveReview: false },
86: { phaseLabel: 'daily_life', blockNumber: 9, didacticMode: 'pattern_drill', difficultyWeight: 4, newUnitTarget: 4, reviewWeight: 55, isIntensiveReview: false },
87: { phaseLabel: 'daily_life', blockNumber: 9, didacticMode: 'guided_dialogue', difficultyWeight: 4, newUnitTarget: 5, reviewWeight: 55, isIntensiveReview: false },
88: { phaseLabel: 'daily_life', blockNumber: 9, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 95, isIntensiveReview: true },
89: { phaseLabel: 'daily_life', blockNumber: 9, didacticMode: 'intensive_review', difficultyWeight: 3, newUnitTarget: 0, reviewWeight: 92, isIntensiveReview: true },
90: { phaseLabel: 'daily_life', blockNumber: 9, didacticMode: 'guided_dialogue', difficultyWeight: 4, newUnitTarget: 5, reviewWeight: 55, isIntensiveReview: false },
91: { phaseLabel: 'daily_life', blockNumber: 9, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 55, isIntensiveReview: false },
92: { phaseLabel: 'daily_life', blockNumber: 9, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 96, isIntensiveReview: true },
93: { phaseLabel: 'daily_life', blockNumber: 9, didacticMode: 'checkpoint', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 97, isIntensiveReview: false },
94: { phaseLabel: 'daily_life', blockNumber: 10, didacticMode: 'guided_dialogue', difficultyWeight: 3, newUnitTarget: 5, reviewWeight: 50, isIntensiveReview: false },
95: { phaseLabel: 'daily_life', blockNumber: 10, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 50, isIntensiveReview: false },
96: { phaseLabel: 'daily_life', blockNumber: 10, didacticMode: 'pattern_drill', difficultyWeight: 4, newUnitTarget: 4, reviewWeight: 55, isIntensiveReview: false },
97: { phaseLabel: 'daily_life', blockNumber: 10, didacticMode: 'guided_dialogue', difficultyWeight: 3, newUnitTarget: 5, reviewWeight: 55, isIntensiveReview: false },
98: { phaseLabel: 'daily_life', blockNumber: 10, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 95, isIntensiveReview: true },
99: { phaseLabel: 'daily_life', blockNumber: 10, didacticMode: 'intensive_review', difficultyWeight: 3, newUnitTarget: 0, reviewWeight: 92, isIntensiveReview: true },
100: { phaseLabel: 'daily_life', blockNumber: 10, didacticMode: 'guided_dialogue', difficultyWeight: 4, newUnitTarget: 5, reviewWeight: 55, isIntensiveReview: false },
101: { phaseLabel: 'daily_life', blockNumber: 10, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 55, isIntensiveReview: false },
102: { phaseLabel: 'daily_life', blockNumber: 10, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 96, isIntensiveReview: true },
103: { phaseLabel: 'daily_life', blockNumber: 10, didacticMode: 'checkpoint', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 97, isIntensiveReview: false },
104: { phaseLabel: 'stabilization', blockNumber: 11, didacticMode: 'guided_dialogue', difficultyWeight: 4, newUnitTarget: 5, reviewWeight: 60, isIntensiveReview: false },
105: { phaseLabel: 'stabilization', blockNumber: 11, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 60, isIntensiveReview: false },
106: { phaseLabel: 'stabilization', blockNumber: 11, didacticMode: 'pattern_drill', difficultyWeight: 4, newUnitTarget: 4, reviewWeight: 65, isIntensiveReview: false },
107: { phaseLabel: 'stabilization', blockNumber: 11, didacticMode: 'guided_dialogue', difficultyWeight: 4, newUnitTarget: 5, reviewWeight: 65, isIntensiveReview: false },
108: { phaseLabel: 'stabilization', blockNumber: 11, didacticMode: 'intensive_review', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 97, isIntensiveReview: true },
109: { phaseLabel: 'stabilization', blockNumber: 11, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 94, isIntensiveReview: true },
110: { phaseLabel: 'stabilization', blockNumber: 11, didacticMode: 'guided_dialogue', difficultyWeight: 4, newUnitTarget: 5, reviewWeight: 65, isIntensiveReview: false },
111: { phaseLabel: 'stabilization', blockNumber: 11, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 65, isIntensiveReview: false },
112: { phaseLabel: 'stabilization', blockNumber: 11, didacticMode: 'intensive_review', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 98, isIntensiveReview: true },
113: { phaseLabel: 'stabilization', blockNumber: 11, didacticMode: 'checkpoint', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 99, isIntensiveReview: false },
114: { phaseLabel: 'stabilization', blockNumber: 12, didacticMode: 'real_life_scenario', difficultyWeight: 5, newUnitTarget: 3, reviewWeight: 70, isIntensiveReview: false },
115: { phaseLabel: 'stabilization', blockNumber: 12, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 95, isIntensiveReview: true },
116: { phaseLabel: 'stabilization', blockNumber: 12, didacticMode: 'real_life_scenario', difficultyWeight: 5, newUnitTarget: 3, reviewWeight: 72, isIntensiveReview: false },
117: { phaseLabel: 'stabilization', blockNumber: 12, didacticMode: 'pattern_drill', difficultyWeight: 5, newUnitTarget: 2, reviewWeight: 80, isIntensiveReview: false },
118: { phaseLabel: 'stabilization', blockNumber: 12, didacticMode: 'intensive_review', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 99, isIntensiveReview: true },
119: { phaseLabel: 'stabilization', blockNumber: 12, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 96, isIntensiveReview: true },
120: { phaseLabel: 'stabilization', blockNumber: 12, didacticMode: 'real_life_scenario', difficultyWeight: 5, newUnitTarget: 3, reviewWeight: 75, isIntensiveReview: false },
121: { phaseLabel: 'stabilization', blockNumber: 12, didacticMode: 'checkpoint', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 99, isIntensiveReview: false },
122: { phaseLabel: 'stabilization', blockNumber: 12, didacticMode: 'checkpoint', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 100, isIntensiveReview: false },
123: { phaseLabel: 'stabilization', blockNumber: 12, didacticMode: 'real_life_scenario', difficultyWeight: 2, newUnitTarget: 2, reviewWeight: 65, isIntensiveReview: false },
124: { phaseLabel: 'stabilization', blockNumber: 13, didacticMode: 'real_life_scenario', difficultyWeight: 5, newUnitTarget: 3, reviewWeight: 72, isIntensiveReview: false },
125: { phaseLabel: 'stabilization', blockNumber: 13, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 94, isIntensiveReview: true },
126: { phaseLabel: 'stabilization', blockNumber: 13, didacticMode: 'pattern_drill', difficultyWeight: 5, newUnitTarget: 2, reviewWeight: 82, isIntensiveReview: false },
127: { phaseLabel: 'stabilization', blockNumber: 13, didacticMode: 'real_life_scenario', difficultyWeight: 5, newUnitTarget: 3, reviewWeight: 74, isIntensiveReview: false },
128: { phaseLabel: 'stabilization', blockNumber: 13, didacticMode: 'intensive_review', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 98, isIntensiveReview: true },
129: { phaseLabel: 'stabilization', blockNumber: 13, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 95, isIntensiveReview: true },
130: { phaseLabel: 'stabilization', blockNumber: 13, didacticMode: 'real_life_scenario', difficultyWeight: 5, newUnitTarget: 3, reviewWeight: 75, isIntensiveReview: false },
131: { phaseLabel: 'stabilization', blockNumber: 13, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 95, isIntensiveReview: true },
132: { phaseLabel: 'stabilization', blockNumber: 13, didacticMode: 'intensive_review', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 99, isIntensiveReview: true },
133: { phaseLabel: 'stabilization', blockNumber: 13, didacticMode: 'checkpoint', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 99, isIntensiveReview: false },
134: { phaseLabel: 'stabilization', blockNumber: 14, didacticMode: 'real_life_scenario', difficultyWeight: 5, newUnitTarget: 3, reviewWeight: 74, isIntensiveReview: false },
135: { phaseLabel: 'stabilization', blockNumber: 14, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 94, isIntensiveReview: true },
136: { phaseLabel: 'stabilization', blockNumber: 14, didacticMode: 'pattern_drill', difficultyWeight: 5, newUnitTarget: 2, reviewWeight: 84, isIntensiveReview: false },
137: { phaseLabel: 'stabilization', blockNumber: 14, didacticMode: 'real_life_scenario', difficultyWeight: 5, newUnitTarget: 3, reviewWeight: 76, isIntensiveReview: false },
138: { phaseLabel: 'stabilization', blockNumber: 14, didacticMode: 'intensive_review', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 98, isIntensiveReview: true },
139: { phaseLabel: 'stabilization', blockNumber: 14, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 95, isIntensiveReview: true },
140: { phaseLabel: 'stabilization', blockNumber: 14, didacticMode: 'real_life_scenario', difficultyWeight: 5, newUnitTarget: 3, reviewWeight: 78, isIntensiveReview: false },
141: { phaseLabel: 'stabilization', blockNumber: 14, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 95, isIntensiveReview: true },
142: { phaseLabel: 'stabilization', blockNumber: 14, didacticMode: 'intensive_review', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 99, isIntensiveReview: true },
143: { phaseLabel: 'stabilization', blockNumber: 14, didacticMode: 'checkpoint', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 99, isIntensiveReview: false },
144: { phaseLabel: 'stabilization', blockNumber: 15, didacticMode: 'intensive_review', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 99, isIntensiveReview: true },
145: { phaseLabel: 'stabilization', blockNumber: 15, didacticMode: 'intensive_review', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 99, isIntensiveReview: true },
146: { phaseLabel: 'stabilization', blockNumber: 15, didacticMode: 'pattern_drill', difficultyWeight: 5, newUnitTarget: 1, reviewWeight: 88, isIntensiveReview: false },
147: { phaseLabel: 'stabilization', blockNumber: 15, didacticMode: 'real_life_scenario', difficultyWeight: 5, newUnitTarget: 2, reviewWeight: 80, isIntensiveReview: false },
148: { phaseLabel: 'stabilization', blockNumber: 15, didacticMode: 'intensive_review', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 100, isIntensiveReview: true },
149: { phaseLabel: 'stabilization', blockNumber: 15, didacticMode: 'intensive_review', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 100, isIntensiveReview: true },
150: { phaseLabel: 'stabilization', blockNumber: 15, didacticMode: 'real_life_scenario', difficultyWeight: 5, newUnitTarget: 2, reviewWeight: 82, isIntensiveReview: false },
151: { phaseLabel: 'stabilization', blockNumber: 15, didacticMode: 'checkpoint', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 100, isIntensiveReview: false },
152: { phaseLabel: 'stabilization', blockNumber: 15, didacticMode: 'checkpoint', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 100, isIntensiveReview: false },
153: { phaseLabel: 'stabilization', blockNumber: 15, didacticMode: 'real_life_scenario', difficultyWeight: 2, newUnitTarget: 1, reviewWeight: 70, isIntensiveReview: false },
154: { phaseLabel: 'stabilization', blockNumber: 15, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 10, reviewWeight: 76, isIntensiveReview: false }
};
export function getBisayaLessonPedagogy(lessonNumber) {
return BISAYA_LESSON_PEDAGOGY[Number(lessonNumber)] || null;
}

View File

@@ -0,0 +1,558 @@
export const BISAYA_PHASE3_DIDACTICS = {
'Besuch & Gastfreundschaft': {
learningGoals: [
'Besuchssituationen sicher eröffnen und freundlich strukturieren.',
'Hereinbitten, Sitzplatz anbieten, Dank ausdrücken und einfache Rückfragen stellen.',
'Bekannte Familien- und Fürsorgemuster in Besuchsgespräche übertragen.',
'Eine kurze Besuchsszene mit mindestens vier eigenen Sätzen sprechen.'
],
corePatterns: [
{ target: 'Sulod lang.', gloss: 'Komm ruhig herein.' },
{ target: 'Lingkod sa.', gloss: 'Setz dich erst einmal.' },
{ target: 'Maayong pag-abot.', gloss: 'Willkommen.' },
{ target: 'Salamat sa pag-anhi.', gloss: 'Danke fürs Kommen.' },
{ target: 'Gimingaw mi nimo.', gloss: 'Wir haben dich vermisst.' },
{ target: 'Kumusta ang biyahe?', gloss: 'Wie war die Reise?' },
{ target: 'Ganahan ka og tubig?', gloss: 'Möchtest du Wasser?' },
{ target: 'Naa ra mi diri.', gloss: 'Wir sind hier.' }
],
grammarFocus: [
{ title: 'lang und sa als weiche Partikel', text: 'lang und sa machen Aufforderungen oft weniger hart und alltagstauglicher.', example: 'Sulod lang. Lingkod sa.' },
{ title: 'mi statt ko', text: 'mi bedeutet wir und passt, wenn du für deine Familie oder Gruppe sprichst.', example: 'Gimingaw mi nimo.' }
],
speakingPrompts: [
{ title: 'Gast empfangen', prompt: 'Empfange einen Gast, bitte ihn herein, biete einen Sitzplatz an und frage nach der Reise.', cue: 'Maayong pag-abot. Sulod lang. Lingkod sa. Kumusta ang biyahe?' },
{ title: 'Familiäre Wärme', prompt: 'Sage, dass ihr die Person vermisst habt und biete Wasser an.', cue: 'Gimingaw mi nimo. Ganahan ka og tubig?' }
],
practicalTasks: [
{ title: 'Besuchsszene', text: 'Spiele eine Besuchsszene mit Begrüßung, Einladung, Angebot und Rückfrage.' }
]
},
'Besuch & Haushalt': {
learningGoals: [
'Wichtige Haus- und Besuchswörter sicher erkennen.',
'Dinge im Haus zeigen, benennen und in kurzen Sätzen verwenden.',
'Besuchswortschatz mit bereits bekannten Fragen verbinden.',
'Mindestens sechs Haushaltswörter aktiv abrufen.'
],
corePatterns: [
{ target: 'bisita', gloss: 'Besuch / Gast' },
{ target: 'balay', gloss: 'Haus / Zuhause' },
{ target: 'kwarto', gloss: 'Zimmer' },
{ target: 'kusina', gloss: 'Küche' },
{ target: 'lamisa', gloss: 'Tisch' },
{ target: 'lingkuranan', gloss: 'Stuhl' },
{ target: 'pultahan', gloss: 'Tür' },
{ target: 'Naa sa kusina.', gloss: 'Es ist in der Küche.' },
{ target: 'Asa ang lamisa?', gloss: 'Wo ist der Tisch?' }
],
grammarFocus: [
{ title: 'Naa sa ...', text: 'Mit naa sa sagst du, wo etwas ist.', example: 'Naa sa kusina. Naa sa kwarto.' }
],
speakingPrompts: [
{ title: 'Im Haus zeigen', prompt: 'Sage, wo Tisch, Stuhl und Küche sind.', cue: 'Naa sa kusina. Naa ang lingkuranan diri.' }
],
practicalTasks: [
{ title: 'Hausrunde', text: 'Gehe gedanklich durch ein Haus und nenne sechs Dinge mit Ort.' }
]
},
'Fragen im Alltag vertiefen': {
learningGoals: [
'Mehrstufige Frageketten bilden, ohne in lange deutsche Satzstrukturen zu fallen.',
'Ziel, Vorhaben, Zeit und Bedeutung höflich erfragen.',
'Bei Unsicherheit nachhaken und eine Frage reparieren.',
'Drei kurze Folgefragen flüssig hintereinander sprechen.'
],
corePatterns: [
{ target: 'Asa ka padulong?', gloss: 'Wohin gehst du?' },
{ target: 'Unsa imong buhaton?', gloss: 'Was wirst du machen?' },
{ target: 'Kanus-a ka moadto?', gloss: 'Wann gehst du hin?' },
{ target: 'Kinsa imong kuyog?', gloss: 'Mit wem bist du unterwegs?' },
{ target: 'Pwede ko mangutana?', gloss: 'Darf ich fragen?' },
{ target: 'Unsay pasabot ani?', gloss: 'Was bedeutet das?' },
{ target: 'Palihug ka mubalik?', gloss: 'Kannst du das bitte wiederholen?' },
{ target: 'Hinay-hinay lang.', gloss: 'Bitte langsam.' }
],
grammarFocus: [
{ title: 'Frageketten', text: 'Bisaya-Alltagsgespräche nutzen oft mehrere kurze Fragen statt einer langen Konstruktion.', example: 'Asa ka padulong? Unsa imong buhaton didto?' },
{ title: 'ka und imong', text: 'ka bezieht sich auf du als Person, imong auf dein/deine.', example: 'Asa ka padulong? Unsa imong buhaton?' }
],
speakingPrompts: [
{ title: 'Drei-Fragen-Kette', prompt: 'Frage nach Ziel, Zeit und Begleitung.', cue: 'Asa ka padulong? Kanus-a ka moadto? Kinsa imong kuyog?' },
{ title: 'Reparaturfrage', prompt: 'Sage, dass du fragen möchtest, bitte um Wiederholung und frage nach Bedeutung.', cue: 'Pwede ko mangutana? Palihug ka mubalik? Unsay pasabot ani?' }
],
practicalTasks: [
{ title: 'Frageleiter', text: 'Baue aus drei kurzen Fragen eine natürliche Alltagsszene.' }
]
},
'Termine & Verabredungen': {
learningGoals: [
'Einfache Treffen verabreden und bestätigen.',
'Zeitangaben mit morgen, später, heute und Uhrzeit verbinden.',
'Zusage, Absage und Verschieben weich ausdrücken.',
'Einen kurzen Termin-Dialog mit Uhrzeit sprechen.'
],
corePatterns: [
{ target: 'Magkita ta ugma.', gloss: 'Wir treffen uns morgen.' },
{ target: 'Unsa orasa?', gloss: 'Um wie viel Uhr?' },
{ target: 'Pwede ko karon.', gloss: 'Ich kann jetzt / heute.' },
{ target: 'Dili ko pwede karon.', gloss: 'Ich kann jetzt / heute nicht.' },
{ target: 'Pwede ugma?', gloss: 'Geht morgen?' },
{ target: 'Sige, kita ta.', gloss: 'Okay, wir sehen uns.' },
{ target: 'Human sa trabaho.', gloss: 'Nach der Arbeit.' },
{ target: 'Texti lang ko.', gloss: 'Schreib mir einfach.' }
],
grammarFocus: [
{ title: 'ta für wir beide / lass uns', text: 'ta verbindet dich und die andere Person in gemeinsamen Handlungen.', example: 'Magkita ta ugma. Kita ta.' },
{ title: 'pwede für Möglichkeit', text: 'pwede hilft bei Zusage, Frage und Absage.', example: 'Pwede ko karon. Pwede ugma?' }
],
speakingPrompts: [
{ title: 'Treffen planen', prompt: 'Vereinbare ein Treffen für morgen, frage nach der Uhrzeit und bestätige.', cue: 'Magkita ta ugma. Unsa orasa? Sige, kita ta.' },
{ title: 'Termin verschieben', prompt: 'Sage, dass du heute nicht kannst, und schlage morgen vor.', cue: 'Dili ko pwede karon. Pwede ugma?' }
],
practicalTasks: [
{ title: 'Kalenderdialog', text: 'Sprich zwei Varianten: eine Zusage und eine weiche Verschiebung.' }
]
},
'Woche 5 - Intensivwiederholung I': {
learningGoals: [
'Besuch, Familie, Fürsorge und Terminplanung unter Abrufdruck mischen.',
'Neue Muster mit alten Grundlagen aus Begrüßung, Essen, Zeit und Beziehung verbinden.',
'Schnell zwischen Rollen wechseln: Gastgeber, Gast, Familienmitglied.',
'Mindestens acht Situationsantworten ohne deutsche Stütze geben.'
],
corePatterns: [
{ target: 'Kumusta ka?', gloss: 'Wie geht es dir?' },
{ target: 'Nikaon na ka?', gloss: 'Hast du schon gegessen?' },
{ target: 'Sulod lang.', gloss: 'Komm ruhig herein.' },
{ target: 'Lingkod sa.', gloss: 'Setz dich erst einmal.' },
{ target: 'Magkita ta ugma.', gloss: 'Wir treffen uns morgen.' },
{ target: 'Unsa orasa?', gloss: 'Um wie viel Uhr?' },
{ target: 'Ganahan ka og tubig?', gloss: 'Möchtest du Wasser?' },
{ target: 'Palangga taka.', gloss: 'Ich hab dich lieb / Ich liebe dich.' }
],
speakingPrompts: [
{ title: 'Rollenwechsel', prompt: 'Wechsle zwischen Gast empfangen, nach Essen fragen und Termin vereinbaren.', cue: 'Sulod lang. Nikaon na ka? Magkita ta ugma.' }
],
practicalTasks: [
{ title: 'Acht schnelle Antworten', text: 'Beantworte acht kurze Situationen aus Besuch, Familie und Terminplanung laut.' }
]
},
'Spiralwiederholung - Familie & Fürsorge': {
learningGoals: [
'Frühe Familien- und Fürsorgemuster aktiv festigen.',
'Familienwörter nicht isoliert, sondern in kurzen Handlungen verwenden.',
'Nähe, Essen, Müdigkeit und Hilfe in einem Mini-Dialog verbinden.',
'Alte Kernmuster mit neuen Besuchssätzen kombinieren.'
],
corePatterns: [
{ target: 'Nanay', gloss: 'Mutter' },
{ target: 'Tatay', gloss: 'Vater' },
{ target: 'anak', gloss: 'Kind' },
{ target: 'pamilya', gloss: 'Familie' },
{ target: 'Nikaon na ka?', gloss: 'Hast du schon gegessen?' },
{ target: 'Kapoy na ka?', gloss: 'Bist du schon müde?' },
{ target: 'Tabangi ko, palihug.', gloss: 'Hilf mir bitte.' },
{ target: 'Palangga taka.', gloss: 'Ich hab dich lieb / Ich liebe dich.' }
],
speakingPrompts: [
{ title: 'Familien-Mini-Dialog', prompt: 'Sprich mit einem Familienmitglied über Essen, Müdigkeit und Hilfe.', cue: 'Nikaon na ka? Kapoy na ka? Tabangi ko, palihug.' }
],
practicalTasks: [
{ title: 'Spiralabruf', text: 'Nimm vier alte Familienwörter und bilde mit jedem einen kurzen Alltagssatz.' }
]
},
'Gesundheit im Alltag': {
learningGoals: [
'Einfache Beschwerden erfragen, beantworten und fürsorglich reagieren.',
'Körper, Medizin, Ruhe und Wasser in einem Pflegegespräch verbinden.',
'Zwischen Frage, Rat und Rückmeldung unterscheiden.',
'Ein Gesundheitsgespräch mit mindestens fünf Sätzen sprechen.'
],
corePatterns: [
{ target: 'Sakit imong ulo?', gloss: 'Tut dein Kopf weh?' },
{ target: 'Sakit akong tiyan.', gloss: 'Mein Bauch tut weh.' },
{ target: 'Niinom ka og tambal?', gloss: 'Hast du Medizin genommen?' },
{ target: 'Magpahuway sa.', gloss: 'Ruh dich erst einmal aus.' },
{ target: 'Uminom og tubig.', gloss: 'Trink Wasser.' },
{ target: 'Mas maayo na ka?', gloss: 'Geht es dir schon besser?' },
{ target: 'Maayo ra ko.', gloss: 'Mir geht es okay.' },
{ target: 'Tawag ta og doktor?', gloss: 'Sollen wir einen Arzt rufen?' }
],
grammarFocus: [
{ title: 'akong und imong', text: 'akong bedeutet mein, imong bedeutet dein. Beides steht direkt vor dem Körperteil.', example: 'akong tiyan, imong ulo' },
{ title: 'Frage mit na', text: 'na kann schon/jetzt markieren und kommt in Gesundheitsfragen häufig vor.', example: 'Mas maayo na ka?' }
],
speakingPrompts: [
{ title: 'Pflegegespräch', prompt: 'Frage nach Schmerzen, biete Ruhe und Wasser an und frage nach Besserung.', cue: 'Sakit imong ulo? Magpahuway sa. Uminom og tubig. Mas maayo na ka?' }
],
practicalTasks: [
{ title: 'Fünf-Satz-Szene', text: 'Sprich eine Szene: Beschwerde, Nachfrage, Medizin, Ruhe, Besserung.' }
]
},
'Medikamente & Beschwerden': {
learningGoals: [
'Gesundheitswortschatz zu Symptomen, Körper und Hilfe sicher abrufen.',
'Symptomwörter in kurze Fürsorgesätze einbauen.',
'Zwischen Wortabruf und situativer Reaktion wechseln.',
'Mindestens acht Gesundheitsbegriffe aktiv verwenden.'
],
corePatterns: [
{ target: 'tambal', gloss: 'Medizin' },
{ target: 'ubo', gloss: 'Husten' },
{ target: 'hilanat', gloss: 'Fieber' },
{ target: 'kasakit', gloss: 'Schmerz' },
{ target: 'ulo', gloss: 'Kopf' },
{ target: 'tiyan', gloss: 'Bauch' },
{ target: 'doktor', gloss: 'Arzt / Ärztin' },
{ target: 'tubig', gloss: 'Wasser' },
{ target: 'Aduna kay hilanat?', gloss: 'Hast du Fieber?' }
],
speakingPrompts: [
{ title: 'Symptom zu Hilfe', prompt: 'Ordne Husten, Fieber und Schmerz je einer passenden Hilfe zu.', cue: 'Aduna kay hilanat? Magpahuway sa. Uminom og tubig.' }
],
practicalTasks: [
{ title: 'Gesundheitskarten', text: 'Ziehe gedanklich acht Gesundheitswörter und bilde daraus vier kurze Hilfesätze.' }
]
},
'Woche 5 - Intensivwiederholung II': {
learningGoals: [
'Gesundheit, Besuch, Fragen und Terminplanung gemischt reaktivieren.',
'Ähnliche Muster gezielt unterscheiden: Frage, Bitte, Angebot, Rat.',
'Unter leichtem Zeitdruck verständlich reagieren.',
'Fehlercluster aus Woche 5 für SRS und Wiederholung sichtbar machen.'
],
corePatterns: [
{ target: 'Pwede ko mangutana?', gloss: 'Darf ich fragen?' },
{ target: 'Palihug ka mubalik?', gloss: 'Kannst du das bitte wiederholen?' },
{ target: 'Magkita ta ugma.', gloss: 'Wir treffen uns morgen.' },
{ target: 'Salamat sa pag-anhi.', gloss: 'Danke fürs Kommen.' },
{ target: 'Niinom ka og tambal?', gloss: 'Hast du Medizin genommen?' },
{ target: 'Mas maayo na ka?', gloss: 'Geht es dir schon besser?' },
{ target: 'Dili ko pwede karon.', gloss: 'Ich kann heute nicht.' },
{ target: 'Ganahan ka og tubig?', gloss: 'Möchtest du Wasser?' }
],
speakingPrompts: [
{ title: 'Mischrunde', prompt: 'Reagiere nacheinander auf Besuch, Krankheit, Termin und Verständnisproblem.', cue: 'Sulod lang. Niinom ka og tambal? Pwede ugma? Palihug ka mubalik?' }
],
practicalTasks: [
{ title: 'Fehlercluster', text: 'Notiere nach der Übung drei Sätze, bei denen du gezögert hast, und wiederhole sie laut.' }
]
},
'Woche 5 - Checkpoint': {
learningGoals: [
'Die Inhalte von Woche 5 diagnostisch sichern.',
'Besuch, Termine, Fragen und Gesundheit in freien Antworten kombinieren.',
'Schwache Muster für die nächste Wiederholungsphase markieren.',
'Eine kurze Besuchs- oder Pflegesituation ohne Vorlage lösen.'
],
corePatterns: [
{ target: 'Sulod lang.', gloss: 'Komm ruhig herein.' },
{ target: 'Unsa orasa?', gloss: 'Um wie viel Uhr?' },
{ target: 'Dili ko pwede karon.', gloss: 'Ich kann heute nicht.' },
{ target: 'Magpahuway sa.', gloss: 'Ruh dich erst einmal aus.' },
{ target: 'Sakit imong ulo?', gloss: 'Tut dein Kopf weh?' },
{ target: 'Kumusta ang biyahe?', gloss: 'Wie war die Reise?' },
{ target: 'Pwede ko mangutana?', gloss: 'Darf ich fragen?' },
{ target: 'Hinay-hinay lang.', gloss: 'Bitte langsam.' }
],
speakingPrompts: [
{ title: 'Checkpoint-Szene', prompt: 'Löse eine Szene mit Besuch, Termin und Gesundheitsfrage.', cue: 'Sulod lang. Magkita ta ugma? Sakit imong ulo?' }
],
practicalTasks: [
{ title: 'Diagnose', text: 'Beantworte fünf freie Situationen und markiere alle Sätze, die nicht sofort kamen.' }
]
},
'Unterwegs & Transport': {
learningGoals: [
'Nach Weg, Haltestelle, Fahrpreis und Ziel fragen.',
'Sagen, wohin man fährt und wo man aussteigen möchte.',
'Transportfragen mit Orts- und Preiswissen aus früheren Wochen verbinden.',
'Eine einfache Fahrt sprachlich organisieren.'
],
corePatterns: [
{ target: 'Asa ang sakayan?', gloss: 'Wo ist die Haltestelle / Mitfahrstelle?' },
{ target: 'Moadto ko sa lungsod.', gloss: 'Ich fahre/gehe in die Stadt.' },
{ target: 'Hunong lang dinhi.', gloss: 'Bitte hier anhalten.' },
{ target: 'Pila ang plite?', gloss: 'Wie viel kostet die Fahrt?' },
{ target: 'Asa ko manaog?', gloss: 'Wo soll ich aussteigen?' },
{ target: 'Duol ra ba?', gloss: 'Ist es nah?' },
{ target: 'Layo pa ba?', gloss: 'Ist es noch weit?' },
{ target: 'Padulong ko sa balay.', gloss: 'Ich bin auf dem Weg nach Hause.' }
],
grammarFocus: [
{ title: 'moadto und padulong', text: 'moadto betont das Hingehen/Fahren, padulong beschreibt die Richtung.', example: 'Moadto ko sa lungsod. Padulong ko sa balay.' },
{ title: 'dinhi', text: 'dinhi bedeutet hier und ist beim Aussteigen oder Anhalten praktisch.', example: 'Hunong lang dinhi.' }
],
speakingPrompts: [
{ title: 'Weg erfragen', prompt: 'Frage nach Haltestelle, Preis und Ausstieg.', cue: 'Asa ang sakayan? Pila ang plite? Asa ko manaog?' },
{ title: 'Fahrt beschreiben', prompt: 'Sage, dass du in die Stadt und später nach Hause fährst.', cue: 'Moadto ko sa lungsod. Padulong ko sa balay.' }
],
practicalTasks: [
{ title: 'Transportdialog', text: 'Sprich eine kurze Fahrt: Ziel, Preis, Ausstieg, Dank.' }
]
},
'Wege & Verkehr': {
learningGoals: [
'Verkehrs- und Wegwörter aktiv abrufen.',
'Route, Fahrzeug und Halt in einfachen Sätzen beschreiben.',
'Frühere Richtungswörter mit Transport verbinden.',
'Eine kurze Route mit mindestens vier Stationen erklären.'
],
corePatterns: [
{ target: 'sakyanan', gloss: 'Fahrzeug / Auto' },
{ target: 'jeepney', gloss: 'Jeepney' },
{ target: 'dalan', gloss: 'Straße / Weg' },
{ target: 'hunong', gloss: 'Halt / anhalten' },
{ target: 'biyahe', gloss: 'Reise / Fahrt' },
{ target: 'tuo', gloss: 'rechts' },
{ target: 'wala', gloss: 'links' },
{ target: 'diretso', gloss: 'geradeaus' },
{ target: 'Duol ra.', gloss: 'Es ist nur nah.' }
],
speakingPrompts: [
{ title: 'Route beschreiben', prompt: 'Beschreibe eine einfache Route mit geradeaus, rechts und links.', cue: 'Diretso. Tuo. Wala. Duol ra.' }
],
practicalTasks: [
{ title: 'Wegskizze', text: 'Erkläre den Weg von Zuhause zur Haltestelle in vier kurzen Sätzen.' }
]
},
'Arbeit & Aufgaben': {
learningGoals: [
'Über Arbeit, Haushaltspflichten und To-dos sprechen.',
'Sagen, was man noch erledigen muss und ob man fertig ist.',
'Hilfe anbieten oder erbitten.',
'Eine Tagesaufgabe mit Zeit und Grund erklären.'
],
corePatterns: [
{ target: 'Naa koy trabaho karon.', gloss: 'Ich habe heute Arbeit.' },
{ target: 'Aduna koy buhaton.', gloss: 'Ich habe etwas zu erledigen.' },
{ target: 'Human na ko.', gloss: 'Ich bin fertig.' },
{ target: 'Wala pa ko nahuman.', gloss: 'Ich bin noch nicht fertig.' },
{ target: 'Tabang ta.', gloss: 'Lass uns helfen.' },
{ target: 'Tabangi ko, palihug.', gloss: 'Hilf mir bitte.' },
{ target: 'Unahon nako ni.', gloss: 'Das mache ich zuerst.' },
{ target: 'Human sa paniudto.', gloss: 'Nach dem Mittagessen.' }
],
grammarFocus: [
{ title: 'naa koy / aduna koy', text: 'Beide Muster drücken aus, dass du etwas hast oder erledigen musst.', example: 'Naa koy trabaho. Aduna koy buhaton.' },
{ title: 'human na vs. wala pa', text: 'human na zeigt fertig, wala pa zeigt noch nicht.', example: 'Human na ko. Wala pa ko nahuman.' }
],
speakingPrompts: [
{ title: 'Tagesaufgabe', prompt: 'Erkläre, was du heute erledigen musst, was zuerst kommt und wann du fertig bist.', cue: 'Aduna koy buhaton. Unahon nako ni. Human na ko unya.' }
],
practicalTasks: [
{ title: 'To-do laut', text: 'Sprich drei Aufgaben: eine erledigt, eine offen, eine mit Hilfe.' }
]
},
'Tätigkeiten & Organisation': {
learningGoals: [
'Organisationswortschatz für Alltag und Haushalt erweitern.',
'Listen, Aufgaben und Hilfe in kurzen Sätzen verwenden.',
'Zwischen Tätigkeit, Person und Reihenfolge unterscheiden.',
'Eine einfache To-do-Liste auf Bisaya sprechen.'
],
corePatterns: [
{ target: 'trabaho', gloss: 'Arbeit' },
{ target: 'buluhaton', gloss: 'Aufgabe / Tätigkeit' },
{ target: 'lista', gloss: 'Liste' },
{ target: 'tabang', gloss: 'Hilfe' },
{ target: 'una', gloss: 'zuerst' },
{ target: 'sunod', gloss: 'als Nächstes' },
{ target: 'human', gloss: 'fertig / danach' },
{ target: 'Kinsa ang motabang?', gloss: 'Wer hilft?' },
{ target: 'Unsa ang sunod?', gloss: 'Was kommt als Nächstes?' }
],
speakingPrompts: [
{ title: 'Organisation', prompt: 'Sage, was zuerst kommt, was danach kommt und wer hilft.', cue: 'Una kini. Sunod kana. Kinsa ang motabang?' }
],
practicalTasks: [
{ title: 'Mini-Liste', text: 'Erstelle mündlich eine Liste mit drei Aufgaben und einer Bitte um Hilfe.' }
]
},
'Freies Gespräch - Familie & Alltag': {
learningGoals: [
'Frühere Themen ohne enge Führung kombinieren.',
'Längere Alltagsantworten mit Familie, Zuhause, Befinden und Plan aufbauen.',
'Bei Unsicherheit Reparaturstrategien verwenden statt abzubrechen.',
'Mindestens fünf zusammenhängende Sätze frei sprechen.'
],
corePatterns: [
{ target: 'Maayo ra ko karon.', gloss: 'Mir geht es heute okay.' },
{ target: 'Naa mi sa balay.', gloss: 'Wir sind zuhause.' },
{ target: 'Nikaon na mi.', gloss: 'Wir haben schon gegessen.' },
{ target: 'Aduna koy buhaton unya.', gloss: 'Ich habe später etwas zu erledigen.' },
{ target: 'Magkita mi unya.', gloss: 'Wir sehen uns später.' },
{ target: 'Gimingaw ko nimo.', gloss: 'Ich vermisse dich.' },
{ target: 'Wala ko kasabot, palihug balik.', gloss: 'Ich verstehe nicht, bitte wiederhole es.' },
{ target: 'Amping kanunay.', gloss: 'Pass immer auf dich auf.' }
],
grammarFocus: [
{ title: 'mi für wir', text: 'mi nutzt du, wenn du über wir sprichst und die angesprochene Person nicht einschließt.', example: 'Naa mi sa balay. Nikaon na mi.' },
{ title: 'Freies Sprechen mit Ankern', text: 'Nutze bekannte Satzanker und füge nur kleine neue Teile hinzu.', example: 'Maayo ra ko karon. Aduna koy buhaton unya.' }
],
speakingPrompts: [
{ title: 'Fünf Sätze frei', prompt: 'Erzähle von deinem Tag mit Befinden, Zuhause, Essen, Plan und Nähe.', cue: 'Maayo ra ko karon. Naa mi sa balay. Nikaon na mi. Aduna koy buhaton unya. Gimingaw ko nimo.' }
],
practicalTasks: [
{ title: 'Freie Aufnahme', text: 'Sprich 30 Sekunden frei und verwende mindestens einen Reparatursatz, wenn du stockst.' }
]
},
'Spiralwiederholung - Wochen 1 bis 4': {
learningGoals: [
'Grundlagen aus den ersten vier Wochen breit reaktivieren.',
'Begrüßung, Essen, Zeit, Preise, Gefühle und Gesundheit mischen.',
'Alte Muster in neuen Kontexten wiederfinden.',
'Langzeitabruf stärker gewichten als neues Material.'
],
corePatterns: [
{ target: 'Kumusta ka?', gloss: 'Wie geht es dir?' },
{ target: 'Maayong gabii.', gloss: 'Guten Abend.' },
{ target: 'Nikaon na ka?', gloss: 'Hast du schon gegessen?' },
{ target: 'Tagpila ni?', gloss: 'Wie viel kostet das?' },
{ target: 'Karon', gloss: 'jetzt / heute' },
{ target: 'Ugma', gloss: 'morgen' },
{ target: 'Nalipay ko.', gloss: 'Ich freue mich / bin glücklich.' },
{ target: 'Sakit akong ulo.', gloss: 'Mein Kopf tut weh.' },
{ target: 'Palihug ka mubalik?', gloss: 'Kannst du das bitte wiederholen?' }
],
speakingPrompts: [
{ title: 'Blockmix', prompt: 'Baue aus Begrüßung, Essen, Preis und Gesundheit eine Alltagsszene.', cue: 'Kumusta ka? Nikaon na ka? Tagpila ni? Sakit akong ulo.' }
],
practicalTasks: [
{ title: 'Langzeitabruf', text: 'Ziehe zehn alte Wörter/Sätze zufällig und bilde daraus fünf kurze Dialogantworten.' }
]
},
'Konflikte & Missverständnisse': {
learningGoals: [
'Missverständnisse höflich ansprechen und entschärfen.',
'Um Wiederholung, langsameres Sprechen oder Erklärung bitten.',
'Eigene Unsicherheit ausdrücken, ohne das Gespräch abzubrechen.',
'Eine kleine heikle Situation ruhig reparieren.'
],
corePatterns: [
{ target: 'Wala ko kasabot.', gloss: 'Ich verstehe nicht.' },
{ target: 'Hinay-hinay lang.', gloss: 'Bitte langsam.' },
{ target: 'Palihug ka mubalik?', gloss: 'Kannst du das bitte wiederholen?' },
{ target: 'Pwede nato istoryahan?', gloss: 'Können wir darüber sprechen?' },
{ target: 'Pasayloa ko.', gloss: 'Entschuldige mich.' },
{ target: 'Dili mao akong pasabot.', gloss: 'Das meinte ich nicht.' },
{ target: 'Sige lang.', gloss: 'Schon okay.' },
{ target: 'Salamat sa pagsabot.', gloss: 'Danke fürs Verständnis.' }
],
grammarFocus: [
{ title: 'Reparieren statt abbrechen', text: 'Ein Reparatursatz hält das Gespräch offen, auch wenn du etwas nicht verstanden hast.', example: 'Wala ko kasabot. Palihug ka mubalik?' },
{ title: 'Weich entschärfen', text: 'Sige lang und Salamat sa pagsabot helfen, Spannung aus dem Gespräch zu nehmen.', example: 'Sige lang. Salamat sa pagsabot.' }
],
speakingPrompts: [
{ title: 'Missverständnis lösen', prompt: 'Sage, dass du nicht verstanden hast, bitte um Wiederholung und entschuldige dich.', cue: 'Wala ko kasabot. Palihug ka mubalik? Pasayloa ko.' },
{ title: 'Heikle Korrektur', prompt: 'Sage, dass du etwas nicht so gemeint hast, und danke fürs Verständnis.', cue: 'Dili mao akong pasabot. Salamat sa pagsabot.' }
],
practicalTasks: [
{ title: 'Konfliktanker', text: 'Übe fünf Reparatursätze, bis du sie ohne Lesen sagen kannst.' }
]
},
'Abschlusstest - Schnellstart': {
learningGoals: [
'Aktiven Grundwortschatz aus dem gesamten 6-Wochen-Schnellstart prüfen.',
'Wörter und kurze Sätze in beide Richtungen abrufen.',
'Ähnliche Muster sicher unterscheiden.',
'Schwächen für die nächste SRS-Wiederholung markieren.'
],
corePatterns: [
{ target: 'Kumusta', gloss: 'Wie geht es / Hallo' },
{ target: 'bisita', gloss: 'Besuch / Gast' },
{ target: 'tambal', gloss: 'Medizin' },
{ target: 'plite', gloss: 'Fahrpreis' },
{ target: 'trabaho', gloss: 'Arbeit' },
{ target: 'buluhaton', gloss: 'Aufgabe' },
{ target: 'dalan', gloss: 'Straße / Weg' },
{ target: 'pamilya', gloss: 'Familie' },
{ target: 'Gimingaw ko nimo.', gloss: 'Ich vermisse dich.' },
{ target: 'Palihug ka mubalik?', gloss: 'Kannst du das bitte wiederholen?' }
],
speakingPrompts: [
{ title: 'Aktiver Wortschatz', prompt: 'Wähle acht Wörter und bilde vier Alltagssätze daraus.', cue: 'bisita -> Sulod lang. tambal -> Niinom ka og tambal?' }
],
practicalTasks: [
{ title: 'Beide Richtungen', text: 'Übersetze zehn Einheiten Deutsch -> Bisaya und Bisaya -> Deutsch.' }
]
},
'Abschlussprüfung - Schnellstart': {
learningGoals: [
'Den 6-Wochen-Schnellstart in Dialog, Abruf und freier Anwendung prüfen.',
'Besuch, Gesundheit, Transport, Arbeit und Familie zusammenführen.',
'Mehrere Sätze ohne starre Vorlage produzieren.',
'Vor dem Übergang in die Alltagsphase stabile Grundfähigkeit zeigen.'
],
corePatterns: [
{ target: 'Sulod lang. Lingkod sa.', gloss: 'Komm herein. Setz dich erst einmal.' },
{ target: 'Magkita ta ugma. Unsa orasa?', gloss: 'Wir treffen uns morgen. Um wie viel Uhr?' },
{ target: 'Niinom ka og tambal?', gloss: 'Hast du Medizin genommen?' },
{ target: 'Asa ang sakayan?', gloss: 'Wo ist die Haltestelle?' },
{ target: 'Pila ang plite?', gloss: 'Wie viel kostet die Fahrt?' },
{ target: 'Aduna koy buhaton karon.', gloss: 'Ich habe heute etwas zu erledigen.' },
{ target: 'Wala ko kasabot. Hinay-hinay lang.', gloss: 'Ich verstehe nicht. Bitte langsam.' },
{ target: 'Amping kanunay.', gloss: 'Pass immer auf dich auf.' }
],
speakingPrompts: [
{ title: 'Große Alltagsszene', prompt: 'Verbinde Besuch, Termin, Gesundheit und Transport in einem Mini-Dialog.', cue: 'Sulod lang. Magkita ta ugma? Niinom ka og tambal? Asa ang sakayan?' },
{ title: 'Freie Antwort', prompt: 'Antworte in mindestens fünf Sätzen auf: Was machst du heute und wen triffst du?', cue: 'Aduna koy buhaton karon. Magkita ta ugma. Amping kanunay.' }
],
practicalTasks: [
{ title: 'Abschlussaufnahme', text: 'Sprich eine 45-Sekunden-Szene mit mindestens sechs Kernmustern aus Woche 1-6.' }
]
},
'Kulturelle Vertiefung im Familienalltag': {
learningGoals: [
'Sprache, Familiennähe und Respekt kulturell einordnen.',
'Indirekte Kommunikation als soziale Strategie verstehen.',
'Nähe, Hilfe und Ablehnung passend formulieren.',
'Kulturelles Verständnis in konkrete Antwortwahl übertragen.'
],
corePatterns: [
{ target: 'pakikisama', gloss: 'soziale Harmonie / gutes Miteinander' },
{ target: 'respeto', gloss: 'Respekt' },
{ target: 'amping', gloss: 'Pass auf dich auf' },
{ target: 'palihug', gloss: 'bitte' },
{ target: 'Dili lang sa karon.', gloss: 'Jetzt lieber nicht.' },
{ target: 'Sige lang.', gloss: 'Schon okay.' },
{ target: 'Tabang ta.', gloss: 'Lass uns helfen.' },
{ target: 'Salamat sa pagsabot.', gloss: 'Danke fürs Verständnis.' }
],
grammarFocus: [
{ title: 'Indirekt ist oft höflicher', text: 'In Familien- und Besuchskontexten kann eine weiche Formulierung natürlicher sein als ein direktes Nein.', example: 'Dili lang sa karon. Sunod na lang.' },
{ title: 'Sprache als Beziehungspflege', text: 'Viele Formeln transportieren nicht nur Inhalt, sondern Fürsorge und Nähe.', example: 'Amping kanunay. Tabang ta.' }
],
speakingPrompts: [
{ title: 'Kulturell passend reagieren', prompt: 'Lehne eine Einladung weich ab und bedanke dich fürs Verständnis.', cue: 'Dili lang sa karon. Salamat sa pagsabot.' },
{ title: 'Hilfe anbieten', prompt: 'Biete Hilfe an, ohne aufdringlich zu klingen.', cue: 'Tabang ta. Sige lang.' }
],
practicalTasks: [
{ title: 'Antwortwahl', text: 'Vergleiche drei direkte deutsche Antworten und formuliere sie weicher auf Bisaya.' }
]
}
};
export const BISAYA_PHASE3_LESSONS = [
{ week: 5, day: 1, num: 44, type: 'conversation', title: 'Besuch & Gastfreundschaft', desc: 'Besuch empfangen, hereinbitten, etwas anbieten und nach der Reise fragen', targetMin: 24, targetScore: 80, review: false, cultural: 'Gastfreundschaft ist im philippinischen Familienalltag zentral und wird oft durch konkrete Fürsorge gezeigt.' },
{ week: 5, day: 1, num: 45, type: 'vocab', title: 'Besuch & Haushalt', desc: 'Haus-, Besuchs- und Haushaltswörter in kurzen Ortssätzen verwenden', targetMin: 22, targetScore: 85, review: true, cultural: null },
{ week: 5, day: 2, num: 46, type: 'grammar', title: 'Fragen im Alltag vertiefen', desc: 'Rückfragen, Folgefragen, Bedeutung und höfliches Nachhaken sicher kombinieren', targetMin: 26, targetScore: 78, review: true, cultural: null },
{ week: 5, day: 2, num: 47, type: 'conversation', title: 'Termine & Verabredungen', desc: 'Treffen planen, Uhrzeiten absprechen, zusagen und weich verschieben', targetMin: 24, targetScore: 80, review: false, cultural: null },
{ week: 5, day: 3, num: 48, type: 'review', title: 'Woche 5 - Intensivwiederholung I', desc: 'Besuch, Familie, Fürsorge und Terminplanung in schnellen Rollenwechseln mischen', targetMin: 34, targetScore: 82, review: false, cultural: null },
{ week: 5, day: 3, num: 49, type: 'vocab', title: 'Spiralwiederholung - Familie & Fürsorge', desc: 'Alte Familien- und Fürsorgemuster aktiv reaktivieren und in neue Szenen übertragen', targetMin: 24, targetScore: 85, review: true, cultural: null },
{ week: 5, day: 4, num: 50, type: 'conversation', title: 'Gesundheit im Alltag', desc: 'Beschwerden erfragen, Ruhe/Wasser/Medizin anbieten und nach Besserung fragen', targetMin: 26, targetScore: 80, review: false, cultural: null },
{ week: 5, day: 4, num: 51, type: 'vocab', title: 'Medikamente & Beschwerden', desc: 'Symptome, Körperwörter und Hilfewortschatz in Fürsorgesätzen verwenden', targetMin: 24, targetScore: 85, review: true, cultural: null },
{ week: 5, day: 5, num: 52, type: 'review', title: 'Woche 5 - Intensivwiederholung II', desc: 'Gesundheit, Besuch, Fragen und Terminplanung unter Abrufdruck kontrastieren', targetMin: 34, targetScore: 82, review: false, cultural: null },
{ week: 5, day: 5, num: 53, type: 'vocab', title: 'Woche 5 - Checkpoint', desc: 'Diagnostischer Checkpoint zu Besuch, Termin, Frageketten und Gesundheit', targetMin: 24, targetScore: 84, review: true, cultural: null },
{ week: 6, day: 1, num: 54, type: 'conversation', title: 'Unterwegs & Transport', desc: 'Nach Weg, Haltestelle, Fahrpreis, Ziel und Ausstieg fragen', targetMin: 26, targetScore: 80, review: false, cultural: null },
{ week: 6, day: 1, num: 55, type: 'vocab', title: 'Wege & Verkehr', desc: 'Verkehrs-, Richtungs- und Bewegungswortschatz in einfachen Routen verwenden', targetMin: 24, targetScore: 85, review: true, cultural: null },
{ week: 6, day: 2, num: 56, type: 'conversation', title: 'Arbeit & Aufgaben', desc: 'Über Arbeit, Pflichten, Reihenfolge, Fertigsein und Hilfe sprechen', targetMin: 26, targetScore: 80, review: false, cultural: null },
{ week: 6, day: 2, num: 57, type: 'vocab', title: 'Tätigkeiten & Organisation', desc: 'Aufgaben, Listen, Reihenfolge und Hilfe als To-do-Sprache festigen', targetMin: 24, targetScore: 85, review: true, cultural: null },
{ week: 6, day: 3, num: 58, type: 'conversation', title: 'Freies Gespräch - Familie & Alltag', desc: 'Fünf zusammenhängende Sätze zu Familie, Zuhause, Befinden und Tagesplan sprechen', targetMin: 30, targetScore: 78, review: false, cultural: null },
{ week: 6, day: 3, num: 59, type: 'review', title: 'Spiralwiederholung - Wochen 1 bis 4', desc: 'Begrüßung, Essen, Zeit, Preise, Gefühle und Gesundheit als Langzeitabruf mischen', targetMin: 34, targetScore: 84, review: false, cultural: null },
{ week: 6, day: 4, num: 60, type: 'conversation', title: 'Konflikte & Missverständnisse', desc: 'Missverständnisse höflich reparieren, um Wiederholung bitten und Spannung entschärfen', targetMin: 26, targetScore: 80, review: false, cultural: 'Ruhiger, indirekter Umgang hilft in heiklen Gesprächen oft mehr als direkte Korrektur.' },
{ week: 6, day: 4, num: 61, type: 'vocab', title: 'Abschlusstest - Schnellstart', desc: 'Aktiver Wortschatztest über den 6-Wochen-Schnellstart in beide Richtungen', targetMin: 24, targetScore: 84, review: true, cultural: null },
{ week: 6, day: 5, num: 62, type: 'review', title: 'Abschlussprüfung - Schnellstart', desc: 'Große Abschlussprüfung mit Besuch, Gesundheit, Transport, Arbeit und freier Anwendung', targetMin: 38, targetScore: 84, review: false, cultural: 'Die Abschlussphase prüft Verständlichkeit und Alltagsfähigkeit, nicht perfekte Grammatik.' },
{ week: 6, day: 5, num: 63, type: 'culture', title: 'Kulturelle Vertiefung im Familienalltag', desc: 'Respekt, Nähe, indirekte Kommunikation und Hilfe im Familienalltag einordnen', targetMin: 22, targetScore: 0, review: false, cultural: 'Sprache, Respekt und Familienrollen sind eng miteinander verbunden.' }
];

File diff suppressed because it is too large Load Diff

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