added examples

This commit is contained in:
2025-11-06 21:22:28 +01:00
parent fdf335b346
commit e5b9bb1b69
5 changed files with 1028 additions and 0 deletions

175
examples/README.md Normal file
View File

@@ -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)

151
examples/advanced_path.json Normal file
View File

@@ -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
}
]
}
]
}

149
examples/example_path.json Normal file
View File

@@ -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
}
]
}
]
}

67
examples/simple_path.json Normal file
View File

@@ -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
}
]
}
]
}

View File

@@ -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::<i32>().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::<i32>().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::<i32>().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::<i32>().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::<i32>().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::<i32>().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::<i32>().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::<i32>().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");
}
}