From e5b9bb1b693f03e84db6cfde5c88f57f8d1ccc70 Mon Sep 17 00:00:00 2001 From: Nico Date: Thu, 6 Nov 2025 21:22:28 +0100 Subject: [PATCH] added examples --- examples/README.md | 175 ++++++++++ examples/advanced_path.json | 151 ++++++++ examples/example_path.json | 149 ++++++++ examples/simple_path.json | 67 ++++ examples/test_repository_functions.rs | 486 ++++++++++++++++++++++++++ 5 files changed, 1028 insertions(+) create mode 100644 examples/README.md create mode 100644 examples/advanced_path.json create mode 100644 examples/example_path.json create mode 100644 examples/simple_path.json create mode 100644 examples/test_repository_functions.rs diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..a830863 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,175 @@ +# Flalingo Path JSON Structure Documentation + +This directory contains example JSON files that demonstrate the structure of learning paths in the Flalingo language learning application. + +## Overview + +A learning path in Flalingo is a structured sequence of educational content organized into nodes, where each node contains multiple exercises. The JSON structure mirrors the Rust data models used in the application. + +## File Examples + +- **`example_path.json`** - Comprehensive example showing a complete German family vocabulary path +- **`simple_path.json`** - Basic example for beginners (German greetings) +- **`advanced_path.json`** - Complex business German communication path + +## JSON Structure + +### Root Path Object + +```json +{ + "id": "string", // Unique identifier for the path + "title": "string", // Human-readable path title + "description": "string", // Detailed description of the path content + "metadata": [...], // Array of metadata objects + "nodes": [...] // Array of node objects +} +``` + +### Metadata Object + +```json +{ + "path_id": "string", // Reference to parent path ID + "version": "string", // Semantic version (e.g., "1.2.0") + "created_at": "string", // ISO 8601 UTC timestamp + "updated_at": "string" // ISO 8601 UTC timestamp +} +``` + +### Node Object + +```json +{ + "id": number, // Unique numeric identifier + "title": "string", // Node title/name + "description": "string", // Node description + "path_id": "string", // Reference to parent path ID + "exercises": [...] // Array of exercise objects +} +``` + +### Exercise Object + +```json +{ + "id": number, // Unique numeric identifier + "ex_type": "string", // Exercise type (see types below) + "content": "string", // JSON-encoded exercise content + "node_id": number // Reference to parent node ID +} +``` + +## Exercise Types + +The `ex_type` field defines the type of exercise. Common types include: + +### Basic Types +- **`vocabulary`** - Single word/phrase learning +- **`multiple_choice`** - Question with multiple answer options +- **`fill_blank`** - Complete sentences with missing words +- **`translation`** - Translate between languages +- **`listening`** - Audio comprehension exercises + +### Interactive Types +- **`drag_drop`** - Match items by dragging and dropping +- **`conversation`** - Simulated dialogue practice +- **`speaking`** - Voice recording and pronunciation +- **`role_play`** - Interactive scenario-based exercises + +### Advanced Types +- **`grammar_explanation`** - Detailed grammar lessons +- **`story_completion`** - Complete narrative texts +- **`comprehensive_quiz`** - Multi-format assessment +- **`case_study_comprehensive`** - Complex real-world scenarios + +## Exercise Content Structure + +The `content` field contains a JSON-encoded string with exercise-specific data: + +### Vocabulary Exercise +```json +{ + "word": "der Vater", + "translation": "father", + "audio": "/audio/vater.mp3", + "example": "Mein Vater ist Arzt." +} +``` + +### Multiple Choice Exercise +```json +{ + "question": "How do you say 'sister' in German?", + "options": ["die Schwester", "der Schwester", "das Schwester", "die Schwestern"], + "correct": 0, + "explanation": "'Die Schwester' is feminine, so it uses the article 'die'." +} +``` + +### Conversation Exercise +```json +{ + "scenario": "Family introduction at a party", + "dialogue": [ + {"speaker": "A", "text": "Ist das deine Familie?"}, + {"speaker": "B", "text": "Ja, das sind meine Eltern und mein Bruder."} + ], + "vocabulary_focus": ["Familie", "Eltern", "Alter", "Beruf"] +} +``` + +### Listening Exercise +```json +{ + "audio_file": "/audio/family_description.mp3", + "transcript": "Hallo, ich heiße Anna...", + "questions": [ + {"question": "Wie heißt die Frau?", "answer": "Anna"}, + {"question": "Ist sie verheiratet?", "answer": "Ja"} + ] +} +``` + +## Design Principles + +### Progressive Difficulty +Paths are structured with increasing complexity: +1. **Simple vocabulary introduction** +2. **Basic grammar concepts** +3. **Practical application** +4. **Comprehensive review** + +### Content Organization +- **Logical grouping**: Related concepts are grouped within nodes +- **Sequential learning**: Nodes build upon previous knowledge +- **Mixed exercise types**: Various formats maintain engagement +- **Real-world context**: Practical scenarios and authentic language use + +### Metadata Usage +- **Version control**: Track content updates and revisions +- **Timestamps**: Monitor content freshness and usage patterns +- **Path relationships**: Enable content dependencies and prerequisites + +## File Naming Convention + +- `simple_*.json` - Beginner level (A1-A2) +- `example_*.json` - Intermediate level (B1-B2) +- `advanced_*.json` - Advanced level (C1-C2) +- `specialized_*.json` - Domain-specific content (business, academic, etc.) + +## Integration Notes + +These JSON files can be: +- **Imported** into the SQLite database using migration scripts +- **Exported** from the database for backup or sharing +- **Used as templates** for creating new learning paths +- **Validated** against the Rust type system for consistency + +## Validation + +All JSON files should be validated for: +- **Structure compliance** with the documented schema +- **Content consistency** (valid references, proper formatting) +- **Educational quality** (appropriate difficulty progression, clear instructions) +- **Technical accuracy** (valid audio paths, properly encoded JSON strings) \ No newline at end of file diff --git a/examples/advanced_path.json b/examples/advanced_path.json new file mode 100644 index 0000000..f2468cc --- /dev/null +++ b/examples/advanced_path.json @@ -0,0 +1,151 @@ +{ + "id": "path_advanced_001", + "title": "German Business Communication - Geschäftskommunikation", + "description": "Master advanced German communication skills for professional environments. Learn formal language, business etiquette, and complex grammatical structures used in corporate settings.", + "metadata": [ + { + "path_id": "path_advanced_001", + "version": "2.1.0", + "created_at": "2024-02-01T08:15:00Z", + "updated_at": "2024-03-15T16:45:22Z" + } + ], + "nodes": [ + { + "id": 1, + "title": "Formal Correspondence - Formelle Korrespondenz", + "description": "Learn to write professional emails, letters, and formal documents in German business context.", + "path_id": "path_advanced_001", + "exercises": [ + { + "id": 101, + "ex_type": "formal_writing", + "content": "{\"task\": \"Write a formal email requesting a meeting\", \"scenario\": \"You need to schedule a quarterly review with your German business partner\", \"required_elements\": [\"formal salutation\", \"purpose statement\", \"specific time request\", \"polite closing\"], \"vocabulary_bank\": [\"Sehr geehrte Damen und Herren\", \"bezüglich\", \"vereinbaren\", \"Mit freundlichen Grüßen\"]}", + "node_id": 1 + }, + { + "id": 102, + "ex_type": "grammar_complex", + "content": "{\"topic\": \"Subjunctive II in formal requests\", \"explanation\": \"Use Konjunktiv II for polite requests in business: 'Könnten Sie...', 'Wären Sie so freundlich...', 'Hätten Sie Zeit...'\", \"examples\": [\"Könnten Sie mir bitte den Bericht zusenden?\", \"Wären Sie so freundlich, das zu überprüfen?\"], \"exercise\": \"Transform direct requests into polite subjunctive forms\"}", + "node_id": 1 + }, + { + "id": 103, + "ex_type": "vocabulary_advanced", + "content": "{\"category\": \"Business correspondence\", \"terms\": [{\"word\": \"die Anlage\", \"translation\": \"attachment/enclosure\", \"context\": \"formal letters\"}, {\"word\": \"bezüglich\", \"translation\": \"regarding/concerning\", \"context\": \"subject lines\"}, {\"word\": \"unverzüglich\", \"translation\": \"immediately/without delay\", \"context\": \"urgent requests\"}]}", + "node_id": 1 + } + ] + }, + { + "id": 2, + "title": "Meeting and Presentation Language - Besprechungs- und Präsentationssprache", + "description": "Develop skills for participating in and leading business meetings, giving presentations, and facilitating discussions.", + "path_id": "path_advanced_001", + "exercises": [ + { + "id": 201, + "ex_type": "presentation_skills", + "content": "{\"scenario\": \"Quarterly sales presentation\", \"structure\": [\"Eröffnung\", \"Agenda\", \"Hauptpunkte\", \"Schlussfolgerung\"], \"phrases\": [\"Darf ich Ihre Aufmerksamkeit haben?\", \"Lassen Sie mich mit... beginnen\", \"Das bringt mich zu meinem nächsten Punkt\", \"Zusammenfassend kann man sagen...\"], \"task\": \"Present Q3 results using formal presentation language\"}", + "node_id": 2 + }, + { + "id": 202, + "ex_type": "debate_simulation", + "content": "{\"topic\": \"Remote work policies\", \"positions\": [\"Pro remote work\", \"Pro office work\"], \"required_skills\": [\"expressing opinions formally\", \"countering arguments\", \"finding compromises\"], \"vocabulary\": [\"meiner Ansicht nach\", \"hingegen\", \"allerdings\", \"andererseits\", \"einen Kompromiss finden\"]}", + "node_id": 2 + }, + { + "id": 203, + "ex_type": "listening_complex", + "content": "{\"audio_file\": \"/audio/board_meeting.mp3\", \"duration\": 300, \"complexity\": \"high\", \"accents\": [\"Standard German\", \"Austrian\", \"Swiss German\"], \"task\": \"Extract key decisions and action items from board meeting\", \"questions\": [{\"type\": \"inference\", \"question\": \"What is the underlying concern about the merger?\"}, {\"type\": \"detail\", \"question\": \"When is the deadline for the feasibility study?\"}]}", + "node_id": 2 + } + ] + }, + { + "id": 3, + "title": "Negotiation and Conflict Resolution - Verhandlung und Konfliktlösung", + "description": "Master the art of negotiating and resolving conflicts in German business environments using diplomatic language and cultural awareness.", + "path_id": "path_advanced_001", + "exercises": [ + { + "id": 301, + "ex_type": "negotiation_simulation", + "content": "{\"scenario\": \"Contract renewal negotiation\", \"your_role\": \"Supplier representative\", \"partner_role\": \"Corporate buyer\", \"objectives\": [\"Maintain current pricing\", \"Extend contract duration\", \"Add performance bonuses\"], \"constraints\": [\"Maximum 5% price increase acceptable\", \"Must maintain quality standards\"], \"diplomatic_phrases\": [\"Ich verstehe Ihre Position, jedoch...\", \"Könnten wir einen Mittelweg finden?\", \"Was wäre, wenn wir...\", \"Das ist durchaus verhandelbar\"]}", + "node_id": 3 + }, + { + "id": 302, + "ex_type": "cultural_competence", + "content": "{\"situation\": \"German business hierarchy and decision-making\", \"cultural_notes\": [\"Germans value directness but maintain formality\", \"Decision-making can be slow and consensus-based\", \"Punctuality is crucial\", \"Small talk is minimal in business settings\"], \"scenarios\": [{\"context\": \"Disagreeing with a senior colleague\", \"appropriate_response\": \"Mit Verlaub, ich sehe das etwas anders...\"}, {\"context\": \"Requesting urgent action\", \"appropriate_response\": \"Es wäre wichtig, dass wir das zeitnah klären...\"}]}", + "node_id": 3 + }, + { + "id": 303, + "ex_type": "conflict_mediation", + "content": "{\"scenario\": \"Mediating between two departments with conflicting priorities\", \"techniques\": [\"Active listening\", \"Reframing issues\", \"Finding common ground\", \"Proposing win-win solutions\"], \"language_tools\": [\"Wenn ich Sie richtig verstehe...\", \"Beide Seiten haben berechtigte Anliegen...\", \"Könnten wir das Problem von einer anderen Seite betrachten?\", \"Was wäre für alle Beteiligten akzeptabel?\"]}", + "node_id": 3 + } + ] + }, + { + "id": 4, + "title": "Advanced Grammar in Context - Fortgeschrittene Grammatik im Kontext", + "description": "Master complex grammatical structures essential for sophisticated business communication, including advanced subordinate clauses and modal constructions.", + "path_id": "path_advanced_001", + "exercises": [ + { + "id": 401, + "ex_type": "complex_grammar", + "content": "{\"topic\": \"Extended participial constructions\", \"explanation\": \"Partizipialkonstruktionen allow complex ideas to be expressed concisely in formal German\", \"examples\": [\"Die im letzten Quartal erzielten Ergebnisse übertreffen unsere Erwartungen.\", \"Der von unserem Team entwickelte Vorschlag wurde angenommen.\"], \"practice\": \"Transform full relative clauses into participial constructions\"}", + "node_id": 4 + }, + { + "id": 402, + "ex_type": "modal_constructions", + "content": "{\"focus\": \"haben/sein + zu + infinitive vs modal verbs\", \"rules\": [\"haben + zu = müssen (active)\", \"sein + zu = können/müssen (passive)\"], \"examples\": [\"Das ist zu bedenken. (Das muss bedacht werden.)\", \"Wir haben das zu berücksichtigen. (Wir müssen das berücksichtigen.)\"], \"business_context\": \"Formal instructions and obligations\"}", + "node_id": 4 + }, + { + "id": 403, + "ex_type": "register_analysis", + "content": "{\"task\": \"Identify and correct register mismatches\", \"text_samples\": [{\"text\": \"Hi, könnten Sie mal eben den Vertrag checken?\", \"issue\": \"Mixed informal/formal register\", \"correction\": \"Sehr geehrte/r..., könnten Sie bitte den Vertrag überprüfen?\"}, {\"text\": \"Das ist total wichtig für unser Meeting.\", \"issue\": \"Colloquial intensifier in formal context\", \"correction\": \"Das ist von größter Bedeutung für unsere Besprechung.\"}]}", + "node_id": 4 + } + ] + }, + { + "id": 5, + "title": "Comprehensive Business Case Study - Umfassende Geschäftsfallstudie", + "description": "Apply all learned skills in a complex, multi-faceted business scenario requiring advanced German communication across various professional contexts.", + "path_id": "path_advanced_001", + "exercises": [ + { + "id": 501, + "ex_type": "case_study_comprehensive", + "content": "{\"scenario\": \"German manufacturing company considering expansion into renewable energy sector\", \"your_role\": \"External consultant\", \"deliverables\": [\"Market analysis presentation\", \"Risk assessment report\", \"Stakeholder negotiation\", \"Board recommendation\"], \"timeline\": \"4 weeks\", \"complications\": [\"Regulatory changes\", \"Competitor actions\", \"Internal resistance\", \"Budget constraints\"]}", + "node_id": 5 + }, + { + "id": 502, + "ex_type": "multi_stakeholder_communication", + "content": "{\"stakeholders\": [{\"role\": \"CEO\", \"communication_style\": \"Direct, results-focused\", \"priorities\": [\"ROI\", \"Timeline\", \"Risk mitigation\"]}, {\"role\": \"HR Director\", \"communication_style\": \"Collaborative, people-focused\", \"priorities\": [\"Employee impact\", \"Training needs\", \"Change management\"]}, {\"role\": \"Technical Lead\", \"communication_style\": \"Detail-oriented, analytical\", \"priorities\": [\"Technical feasibility\", \"Quality standards\", \"Implementation challenges\"]}], \"task\": \"Adapt your communication style and content for each stakeholder\"}", + "node_id": 5 + }, + { + "id": 503, + "ex_type": "crisis_communication", + "content": "{\"crisis\": \"Major supplier bankruptcy affects production schedule\", \"immediate_actions\": [\"Inform key stakeholders\", \"Assess impact\", \"Develop contingency plan\", \"Manage media relations\"], \"communication_channels\": [\"Internal memo\", \"Client notification\", \"Press statement\", \"Investor update\"], \"tone_requirements\": [\"Transparent but reassuring\", \"Professional under pressure\", \"Solution-focused\"]}", + "node_id": 5 + }, + { + "id": 504, + "ex_type": "final_assessment", + "content": "{\"format\": \"Comprehensive evaluation\", \"components\": [{\"skill\": \"Written communication\", \"task\": \"Draft complete business proposal\", \"criteria\": [\"Formal register\", \"Complex grammar\", \"Persuasive argumentation\", \"Cultural appropriateness\"]}, {\"skill\": \"Oral communication\", \"task\": \"Deliver presentation and handle Q&A\", \"criteria\": [\"Clear articulation\", \"Professional demeanor\", \"Spontaneous responses\", \"Cultural sensitivity\"]}, {\"skill\": \"Interactive communication\", \"task\": \"Lead negotiation simulation\", \"criteria\": [\"Diplomatic language\", \"Conflict resolution\", \"Win-win solutions\", \"Cultural awareness\"]}]}", + "node_id": 5 + } + ] + } + ] +} diff --git a/examples/example_path.json b/examples/example_path.json new file mode 100644 index 0000000..2fdcca8 --- /dev/null +++ b/examples/example_path.json @@ -0,0 +1,149 @@ +{ + "id": "path_001", + "title": "German Basics - Family & Relationships", + "description": "Learn essential German vocabulary and grammar related to family members, relationships, and basic social interactions. Perfect for beginners who want to talk about their personal life in German.", + "metadata": [ + { + "path_id": "path_001", + "version": "1.2.0", + "created_at": "2024-01-15T10:30:00Z", + "updated_at": "2024-03-10T14:22:33Z" + } + ], + "nodes": [ + { + "id": 1, + "title": "Family Members - Die Familie", + "description": "Learn the basic vocabulary for family members and how to introduce your family in German.", + "path_id": "path_001", + "exercises": [ + { + "id": 101, + "ex_type": "vocabulary", + "content": "{\"word\": \"der Vater\", \"translation\": \"father\", \"audio\": \"/audio/vater.mp3\", \"example\": \"Mein Vater ist Arzt.\"}", + "node_id": 1 + }, + { + "id": 102, + "ex_type": "vocabulary", + "content": "{\"word\": \"die Mutter\", \"translation\": \"mother\", \"audio\": \"/audio/mutter.mp3\", \"example\": \"Meine Mutter kocht gern.\"}", + "node_id": 1 + }, + { + "id": 103, + "ex_type": "vocabulary", + "content": "{\"word\": \"der Bruder\", \"translation\": \"brother\", \"audio\": \"/audio/bruder.mp3\", \"example\": \"Ich habe einen Bruder.\"}", + "node_id": 1 + }, + { + "id": 104, + "ex_type": "multiple_choice", + "content": "{\"question\": \"How do you say 'sister' in German?\", \"options\": [\"die Schwester\", \"der Schwester\", \"das Schwester\", \"die Schwestern\"], \"correct\": 0, \"explanation\": \"'Die Schwester' is feminine, so it uses the article 'die'.\"}", + "node_id": 1 + }, + { + "id": 105, + "ex_type": "fill_blank", + "content": "{\"sentence\": \"Meine ____ ist sehr nett.\", \"answer\": \"Schwester\", \"hint\": \"female sibling\", \"translation\": \"My sister is very nice.\"}", + "node_id": 1 + } + ] + }, + { + "id": 2, + "title": "Possessive Pronouns - Possessivpronomen", + "description": "Master the use of possessive pronouns (mein, dein, sein, ihr) when talking about family and relationships.", + "path_id": "path_001", + "exercises": [ + { + "id": 201, + "ex_type": "grammar_explanation", + "content": "{\"topic\": \"Possessive Pronouns\", \"explanation\": \"German possessive pronouns change based on the gender and case of the noun they modify. 'Mein' (my), 'dein' (your), 'sein' (his), 'ihr' (her).\", \"examples\": [\"mein Vater\", \"meine Mutter\", \"dein Bruder\", \"ihre Schwester\"]}", + "node_id": 2 + }, + { + "id": 202, + "ex_type": "drag_drop", + "content": "{\"instruction\": \"Match the possessive pronoun with the correct family member\", \"pairs\": [{\"left\": \"mein\", \"right\": \"Vater\"}, {\"left\": \"meine\", \"right\": \"Mutter\"}, {\"left\": \"sein\", \"right\": \"Bruder\"}, {\"left\": \"ihre\", \"right\": \"Schwester\"}]}", + "node_id": 2 + }, + { + "id": 203, + "ex_type": "translation", + "content": "{\"english\": \"My father works in Berlin.\", \"german\": \"Mein Vater arbeitet in Berlin.\", \"hints\": [\"possessive pronoun\", \"verb conjugation\", \"preposition\"]}", + "node_id": 2 + }, + { + "id": 204, + "ex_type": "multiple_choice", + "content": "{\"question\": \"Complete: '_____ Tochter ist 5 Jahre alt.' (His daughter is 5 years old)\", \"options\": [\"Sein\", \"Seine\", \"Ihrer\", \"Ihre\"], \"correct\": 1, \"explanation\": \"'Tochter' is feminine, so 'sein' becomes 'seine'.\"}", + "node_id": 2 + } + ] + }, + { + "id": 3, + "title": "Describing Relationships - Beziehungen beschreiben", + "description": "Learn how to describe family relationships, marital status, and social connections in German.", + "path_id": "path_001", + "exercises": [ + { + "id": 301, + "ex_type": "vocabulary", + "content": "{\"word\": \"verheiratet\", \"translation\": \"married\", \"audio\": \"/audio/verheiratet.mp3\", \"example\": \"Sie ist verheiratet.\"}", + "node_id": 3 + }, + { + "id": 302, + "ex_type": "vocabulary", + "content": "{\"word\": \"ledig\", \"translation\": \"single/unmarried\", \"audio\": \"/audio/ledig.mp3\", \"example\": \"Er ist noch ledig.\"}", + "node_id": 3 + }, + { + "id": 303, + "ex_type": "conversation", + "content": "{\"scenario\": \"Introducing your family at a party\", \"dialogue\": [{\"speaker\": \"A\", \"text\": \"Ist das deine Familie?\"}, {\"speaker\": \"B\", \"text\": \"Ja, das sind meine Eltern und mein Bruder.\"}, {\"speaker\": \"A\", \"text\": \"Wie alt ist dein Bruder?\"}, {\"speaker\": \"B\", \"text\": \"Er ist 25 Jahre alt und arbeitet als Lehrer.\"}], \"vocabulary_focus\": [\"Familie\", \"Eltern\", \"Alter\", \"Beruf\"]}", + "node_id": 3 + }, + { + "id": 304, + "ex_type": "listening", + "content": "{\"audio_file\": \"/audio/family_description.mp3\", \"transcript\": \"Hallo, ich heiße Anna. Ich bin verheiratet und habe zwei Kinder. Mein Mann arbeitet als Ingenieur und meine Tochter geht noch zur Schule.\", \"questions\": [{\"question\": \"Wie heißt die Frau?\", \"answer\": \"Anna\"}, {\"question\": \"Ist sie verheiratet?\", \"answer\": \"Ja\"}, {\"question\": \"Was ist ihr Mann von Beruf?\", \"answer\": \"Ingenieur\"}]}", + "node_id": 3 + } + ] + }, + { + "id": 4, + "title": "Practice & Review - Übung und Wiederholung", + "description": "Comprehensive review of all concepts learned in this path through mixed exercises and real-world scenarios.", + "path_id": "path_001", + "exercises": [ + { + "id": 401, + "ex_type": "story_completion", + "content": "{\"story\": \"Maria stellt ihre Familie vor. Sie sagt: 'Das ist ___ Familie. ___ Vater ist Arzt und ___ Mutter ist Lehrerin. Ich habe auch einen ___ und eine ___. Wir sind eine große und glückliche Familie.'\", \"blanks\": [\"meine\", \"Mein\", \"meine\", \"Bruder\", \"Schwester\"], \"context\": \"Family introduction story\"}", + "node_id": 4 + }, + { + "id": 402, + "ex_type": "speaking", + "content": "{\"prompt\": \"Describe your family in German. Include at least 3 family members and use possessive pronouns.\", \"expected_elements\": [\"possessive pronouns\", \"family vocabulary\", \"complete sentences\"], \"time_limit\": 60}", + "node_id": 4 + }, + { + "id": 403, + "ex_type": "comprehensive_quiz", + "content": "{\"questions\": [{\"type\": \"multiple_choice\", \"question\": \"How do you say 'my parents' in German?\", \"options\": [\"meine Eltern\", \"mein Eltern\", \"meinen Eltern\", \"meiner Eltern\"], \"correct\": 0}, {\"type\": \"translation\", \"english\": \"Her husband is very kind.\", \"german\": \"Ihr Mann ist sehr nett.\"}, {\"type\": \"vocabulary\", \"definition\": \"A female parent\", \"answer\": \"die Mutter\"}]}", + "node_id": 4 + }, + { + "id": 404, + "ex_type": "role_play", + "content": "{\"scenario\": \"You're at a family gathering. Practice introducing different family members to a friend.\", \"roles\": [\"You\", \"Friend\"], \"objectives\": [\"Use correct possessive pronouns\", \"Introduce at least 4 family members\", \"Ask questions about the friend's family\"], \"vocabulary_bank\": [\"Großmutter\", \"Großvater\", \"Onkel\", \"Tante\", \"Cousin\", \"Cousine\"]}", + "node_id": 4 + } + ] + } + ] +} diff --git a/examples/simple_path.json b/examples/simple_path.json new file mode 100644 index 0000000..36c1b5c --- /dev/null +++ b/examples/simple_path.json @@ -0,0 +1,67 @@ +{ + "id": "path_beginner_001", + "title": "German Greetings - Erste Begrüßungen", + "description": "Learn basic German greetings and polite expressions. Your first steps into the German language!", + "metadata": [ + { + "path_id": "path_beginner_001", + "version": "1.0.0", + "created_at": "2024-01-10T09:00:00Z", + "updated_at": "2024-01-10T09:00:00Z" + } + ], + "nodes": [ + { + "id": 1, + "title": "Hello & Goodbye - Hallo & Tschüss", + "description": "Learn the most common ways to say hello and goodbye in German.", + "path_id": "path_beginner_001", + "exercises": [ + { + "id": 101, + "ex_type": "vocabulary", + "content": "{\"word\": \"Hallo\", \"translation\": \"Hello\", \"audio\": \"/audio/hallo.mp3\", \"example\": \"Hallo, wie geht's?\"}", + "node_id": 1 + }, + { + "id": 102, + "ex_type": "vocabulary", + "content": "{\"word\": \"Tschüss\", \"translation\": \"Bye\", \"audio\": \"/audio/tschuess.mp3\", \"example\": \"Tschüss, bis bald!\"}", + "node_id": 1 + }, + { + "id": 103, + "ex_type": "multiple_choice", + "content": "{\"question\": \"How do you say 'Hello' in German?\", \"options\": [\"Hallo\", \"Danke\", \"Bitte\", \"Tschüss\"], \"correct\": 0, \"explanation\": \"'Hallo' is the most common way to say hello in German.\"}", + "node_id": 1 + } + ] + }, + { + "id": 2, + "title": "Please & Thank You - Bitte & Danke", + "description": "Master the magic words of politeness in German.", + "path_id": "path_beginner_001", + "exercises": [ + { + "id": 201, + "ex_type": "vocabulary", + "content": "{\"word\": \"Danke\", \"translation\": \"Thank you\", \"audio\": \"/audio/danke.mp3\", \"example\": \"Danke schön!\"}", + "node_id": 2 + }, + { + "id": 202, + "ex_type": "vocabulary", + "content": "{\"word\": \"Bitte\", \"translation\": \"Please/You're welcome\", \"audio\": \"/audio/bitte.mp3\", \"example\": \"Bitte schön!\"}", + "node_id": 2 + }, + { + "id": 203, + "ex_type": "conversation", + "content": "{\"scenario\": \"Simple polite exchange\", \"dialogue\": [{\"speaker\": \"A\", \"text\": \"Danke!\"}, {\"speaker\": \"B\", \"text\": \"Bitte schön!\"}], \"vocabulary_focus\": [\"Danke\", \"Bitte\"]}", + "node_id": 2 + } + ] + } + ] +} diff --git a/examples/test_repository_functions.rs b/examples/test_repository_functions.rs new file mode 100644 index 0000000..3fb36f3 --- /dev/null +++ b/examples/test_repository_functions.rs @@ -0,0 +1,486 @@ +use std::path::Path; + +// This is a test/example file demonstrating how to use the new repository functions +// Note: This requires the database to be set up and proper imports + +#[cfg(test)] +mod tests { + use chrono::Utc; + use sqlx::SqlitePool; + + use crate::models::{ + exercise::Exercise, + node::Node, + path::{Metadata, Path}, + }; + use crate::repositories::{ + path_json_utils::PathJsonUtils, + repository_manager::RepositoryManager, + }; + + async fn setup_test_database() -> SqlitePool { + // This would normally connect to a test database + // For demonstration purposes only + todo!("Setup test database connection") + } + + #[tokio::test] + async fn test_save_and_retrieve_path() { + let pool = setup_test_database().await; + let repo_manager = RepositoryManager::new(&pool); + + // Create a test path + let test_path = create_sample_path(); + + // Save the path + let saved_path_id = repo_manager + .paths() + .save_path(test_path.clone()) + .await + .expect("Failed to save path"); + + println!("Saved path with ID: {}", saved_path_id); + + // Retrieve the path + let path_id_int = saved_path_id.parse::().expect("Invalid path ID"); + let retrieved_path = repo_manager + .paths() + .get_path_by_id(path_id_int) + .await + .expect("Failed to retrieve path"); + + // Verify the data + assert_eq!(retrieved_path.id, test_path.id); + assert_eq!(retrieved_path.title, test_path.title); + assert_eq!(retrieved_path.nodes.len(), test_path.nodes.len()); + + println!("✅ Successfully saved and retrieved path!"); + } + + #[tokio::test] + async fn test_update_path() { + let pool = setup_test_database().await; + let repo_manager = RepositoryManager::new(&pool); + + // Create and save initial path + let mut test_path = create_sample_path(); + let path_id = repo_manager + .paths() + .save_path(test_path.clone()) + .await + .expect("Failed to save path"); + + // Modify the path + test_path.title = "Updated Path Title".to_string(); + test_path.description = "Updated description with new content".to_string(); + + // Add a new node + let new_node = Node { + id: 999, // This will be auto-assigned + title: "New Node".to_string(), + description: "A newly added node".to_string(), + path_id: test_path.id.clone(), + exercises: vec![Exercise { + id: 999, + ex_type: "vocabulary".to_string(), + content: r#"{"word": "neu", "translation": "new", "example": "Das ist neu."}"# + .to_string(), + node_id: 999, + }], + }; + test_path.nodes.push(new_node); + + // Update the path + repo_manager + .paths() + .update_path(test_path.clone()) + .await + .expect("Failed to update path"); + + // Retrieve and verify + let path_id_int = path_id.parse::().expect("Invalid path ID"); + let updated_path = repo_manager + .paths() + .get_path_by_id(path_id_int) + .await + .expect("Failed to retrieve updated path"); + + assert_eq!(updated_path.title, "Updated Path Title"); + assert_eq!(updated_path.nodes.len(), 3); // Original 2 + 1 new + + println!("✅ Successfully updated path!"); + } + + #[tokio::test] + async fn test_clone_path() { + let pool = setup_test_database().await; + let repo_manager = RepositoryManager::new(&pool); + + // Create and save original path + let original_path = create_sample_path(); + let original_path_id = repo_manager + .paths() + .save_path(original_path.clone()) + .await + .expect("Failed to save original path"); + + // Clone the path + let original_id_int = original_path_id.parse::().expect("Invalid path ID"); + let cloned_path_id = repo_manager + .clone_path_complete( + original_id_int, + "cloned_path_001", + "Cloned German Basics", + ) + .await + .expect("Failed to clone path"); + + // Retrieve the cloned path + let cloned_id_int = cloned_path_id.parse::().unwrap_or(0); + let cloned_path = repo_manager + .paths() + .get_path_by_id(cloned_id_int) + .await + .expect("Failed to retrieve cloned path"); + + // Verify clone + assert_eq!(cloned_path.id, "cloned_path_001"); + assert_eq!(cloned_path.title, "Cloned German Basics"); + assert_eq!(cloned_path.nodes.len(), original_path.nodes.len()); + + println!("✅ Successfully cloned path!"); + } + + #[tokio::test] + async fn test_json_import_export() { + let pool = setup_test_database().await; + let repo_manager = RepositoryManager::new(&pool); + + // Create sample JSON + let json_content = r#" + { + "id": "test_json_path", + "title": "JSON Test Path", + "description": "Testing JSON import/export functionality", + "metadata": [ + { + "path_id": "test_json_path", + "version": "1.0.0", + "created_at": "2024-01-01T12:00:00Z", + "updated_at": "2024-01-01T12:00:00Z" + } + ], + "nodes": [ + { + "id": 1, + "title": "JSON Test Node", + "description": "Testing node from JSON", + "path_id": "test_json_path", + "exercises": [ + { + "id": 1, + "ex_type": "vocabulary", + "content": "{\"word\": \"Test\", \"translation\": \"Test\", \"example\": \"This is a test.\"}", + "node_id": 1 + } + ] + } + ] + } + "#; + + // Import from JSON + let imported_path_id = repo_manager + .import_path_from_json(json_content) + .await + .expect("Failed to import path from JSON"); + + println!("Imported path ID: {}", imported_path_id); + + // Export back to JSON + let path_id_int = imported_path_id.parse::().expect("Invalid path ID"); + let exported_json = repo_manager + .export_path_to_json(path_id_int) + .await + .expect("Failed to export path to JSON"); + + println!("Exported JSON length: {} characters", exported_json.len()); + + // Verify the exported JSON contains expected content + assert!(exported_json.contains("JSON Test Path")); + assert!(exported_json.contains("test_json_path")); + + println!("✅ Successfully imported and exported JSON!"); + } + + #[tokio::test] + async fn test_path_statistics() { + let pool = setup_test_database().await; + let repo_manager = RepositoryManager::new(&pool); + + // Create and save path + let test_path = create_sample_path(); + let path_id = repo_manager + .paths() + .save_path(test_path) + .await + .expect("Failed to save path"); + + // Get statistics + let path_id_int = path_id.parse::().expect("Invalid path ID"); + let stats = repo_manager + .get_path_statistics(path_id_int) + .await + .expect("Failed to get path statistics"); + + // Print statistics + stats.print_detailed_summary(); + + // Verify statistics + assert_eq!(stats.node_count, 2); + assert_eq!(stats.total_exercises, 3); + assert!(stats.exercise_types.contains_key("vocabulary")); + assert!(stats.exercise_types.contains_key("multiple_choice")); + + println!("✅ Successfully generated path statistics!"); + } + + #[tokio::test] + async fn test_path_validation() { + let pool = setup_test_database().await; + let repo_manager = RepositoryManager::new(&pool); + + // Create and save path + let test_path = create_sample_path(); + let path_id = repo_manager + .paths() + .save_path(test_path) + .await + .expect("Failed to save path"); + + // Validate path integrity + let path_id_int = path_id.parse::().expect("Invalid path ID"); + let issues = repo_manager + .validate_path_integrity(path_id_int) + .await + .expect("Failed to validate path"); + + if issues.is_empty() { + println!("✅ Path validation passed - no issues found!"); + } else { + println!("⚠️ Path validation found issues:"); + for issue in &issues { + println!(" - {}", issue); + } + } + } + + #[tokio::test] + async fn test_search_functionality() { + let pool = setup_test_database().await; + let repo_manager = RepositoryManager::new(&pool); + + // Create and save multiple paths for searching + let path1 = create_sample_path(); + let mut path2 = create_sample_path(); + path2.id = "search_test_002".to_string(); + path2.title = "Advanced German Grammar".to_string(); + path2.description = "Complex grammatical structures and advanced vocabulary".to_string(); + + repo_manager + .paths() + .save_path(path1) + .await + .expect("Failed to save path1"); + repo_manager + .paths() + .save_path(path2) + .await + .expect("Failed to save path2"); + + // Search for paths + let search_results = repo_manager + .search_paths("German") + .await + .expect("Failed to search paths"); + + println!("Search results for 'German':"); + for result in &search_results { + result.print_summary(); + println!(); + } + + assert!(!search_results.is_empty()); + println!("✅ Search functionality working correctly!"); + } + + #[tokio::test] + async fn test_delete_operations() { + let pool = setup_test_database().await; + let repo_manager = RepositoryManager::new(&pool); + + // Create and save path + let test_path = create_sample_path(); + let path_id = repo_manager + .paths() + .save_path(test_path) + .await + .expect("Failed to save path"); + + // Verify path exists + let path_id_int = path_id.parse::().expect("Invalid path ID"); + let path_exists = repo_manager + .paths() + .path_exists(path_id_int) + .await + .expect("Failed to check path existence"); + assert!(path_exists); + + // Delete the path + repo_manager + .paths() + .delete_path(path_id_int) + .await + .expect("Failed to delete path"); + + // Verify path no longer exists + let path_still_exists = repo_manager + .paths() + .path_exists(path_id_int) + .await + .expect("Failed to check path existence after deletion"); + assert!(!path_still_exists); + + println!("✅ Successfully deleted path and verified removal!"); + } + + #[tokio::test] + async fn test_database_statistics() { + let pool = setup_test_database().await; + let repo_manager = RepositoryManager::new(&pool); + + // Get database statistics + let stats = repo_manager + .get_stats() + .await + .expect("Failed to get database statistics"); + + println!("=== Database Statistics ==="); + println!("Total paths: {}", stats.path_count); + println!("Total nodes: {}", stats.node_count); + println!("Total exercises: {}", stats.exercise_count); + println!("Total metadata records: {}", stats.metadata_count); + println!("Total records: {}", stats.total_records()); + println!("Database empty: {}", stats.is_empty()); + + println!("✅ Successfully retrieved database statistics!"); + } + + // Helper function to create a sample path for testing + fn create_sample_path() -> Path { + let now = Utc::now(); + + let metadata = vec![Metadata { + path_id: "test_path_001".to_string(), + version: "1.0.0".to_string(), + created_at: now, + updated_at: now, + }]; + + let exercises1 = vec![ + Exercise { + id: 1, + ex_type: "vocabulary".to_string(), + content: r#"{"word": "Hallo", "translation": "Hello", "audio": "/audio/hallo.mp3", "example": "Hallo, wie geht's?"}"#.to_string(), + node_id: 1, + }, + Exercise { + id: 2, + ex_type: "multiple_choice".to_string(), + content: r#"{"question": "How do you say 'goodbye' in German?", "options": ["Tschüss", "Hallo", "Bitte", "Danke"], "correct": 0, "explanation": "Tschüss is the informal way to say goodbye."}"#.to_string(), + node_id: 1, + } + ]; + + let exercises2 = vec![Exercise { + id: 3, + ex_type: "vocabulary".to_string(), + content: r#"{"word": "Danke", "translation": "Thank you", "audio": "/audio/danke.mp3", "example": "Danke schön!"}"#.to_string(), + node_id: 2, + }]; + + let nodes = vec![ + Node { + id: 1, + title: "Basic Greetings".to_string(), + description: "Learn essential German greetings".to_string(), + path_id: "test_path_001".to_string(), + exercises: exercises1, + }, + Node { + id: 2, + title: "Politeness".to_string(), + description: "Learn polite expressions".to_string(), + path_id: "test_path_001".to_string(), + exercises: exercises2, + }, + ]; + + Path { + id: "test_path_001".to_string(), + title: "German Basics Test".to_string(), + description: "A test path for demonstrating repository functionality".to_string(), + metadata, + nodes, + } + } +} + +// Example usage functions (not tests) +pub mod examples { + use super::*; + + /// Example: How to use the repository manager in your application + pub async fn example_basic_usage() { + println!("=== Basic Repository Usage Example ==="); + + // This would normally use your actual database connection + // let pool = get_database_pool().await; + // let repo_manager = RepositoryManager::new(&pool); + + // Example operations: + println!("1. Create repository manager"); + println!("2. Save a new path"); + println!("3. Retrieve and display path"); + println!("4. Update path content"); + println!("5. Search for paths"); + println!("6. Generate statistics"); + println!("7. Export to JSON"); + println!("8. Cleanup/delete if needed"); + } + + /// Example: How to work with JSON imports + pub async fn example_json_workflow() { + println!("=== JSON Import/Export Workflow ==="); + + // Steps for JSON workflow: + println!("1. Validate JSON file structure"); + println!("2. Import path from JSON"); + println!("3. Verify import success"); + println!("4. Make modifications if needed"); + println!("5. Export updated version"); + println!("6. Backup all paths to JSON files"); + } + + /// Example: How to perform bulk operations + pub async fn example_bulk_operations() { + println!("=== Bulk Operations Example ==="); + + // Bulk operation examples: + println!("1. Import multiple paths from directory"); + println!("2. Validate all paths in database"); + println!("3. Generate statistics for all paths"); + println!("4. Search across all content"); + println!("5. Export all paths to backup directory"); + } +}