{
  "name": "Szybkie Kursiki - AI Course Generator",
  "nodes": [
    {
      "parameters": {},
      "id": "manual-trigger",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        250,
        300
      ]
    },
    {
      "parameters": {
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "anthropicApi",
        "resource": "message",
        "model": "claude-sonnet-4-5-20250929",
        "text": "={{ $json.coursePrompt }}",
        "options": {
          "systemMessage": "Jeste\u015b ekspertem w tworzeniu kurs\u00f3w edukacyjnych online. Twoim zadaniem jest wygenerowanie kompletnej struktury kursu w formacie JSON.\n\nKurs musi zawiera\u0107:\n1. Podstawowe informacje o kursie (tytu\u0142, kr\u00f3tki opis, pe\u0142ny opis, ikona Font Awesome, tagi)\n2. Seri\u0119 lekcji (minimum 3-5 lekcji)\n3. Ka\u017cda lekcja MUSI zawiera\u0107:\n   - Tytu\u0142\n   - Kolejno\u015b\u0107 (order)\n   - Tre\u015b\u0107 w formacie Markdown (content_markdown) - szczeg\u00f3\u0142owa, merytoryczna tre\u015b\u0107 lekcji z przyk\u0142adami kodu\n   - Quiz z 3-5 pytaniami wielokrotnego wyboru\n   - Zadanie praktyczne (practical_task) z przyk\u0142adami i rozwi\u0105zaniem\n\nFormat JSON:\n```json\n{\n  \"course\": {\n    \"title\": \"Nazwa kursu\",\n    \"short_description\": \"Kr\u00f3tki opis kursu (max 200 znak\u00f3w)\",\n    \"description\": \"Pe\u0142ny opis kursu - czego si\u0119 nauczysz, dla kogo jest kurs\",\n    \"icon\": \"fas fa-code\",\n    \"tags\": [\"Python\", \"Backend\", \"API\"]\n  },\n  \"lessons\": [\n    {\n      \"title\": \"Tytu\u0142 lekcji\",\n      \"order\": 0,\n      \"content_markdown\": \"# Wprowadzenie\\n\\nTre\u015b\u0107 lekcji w markdown...\\n\\n```python\\nprint('Hello World')\\n```\\n\\n## Kolejna sekcja\\n\\nWi\u0119cej tre\u015bci...\",\n      \"quiz\": {\n        \"title\": \"Quiz - Tytu\u0142 lekcji\",\n        \"description\": \"Sprawd\u017a swoj\u0105 wiedz\u0119\",\n        \"questions\": [\n          {\n            \"text\": \"Tre\u015b\u0107 pytania?\",\n            \"order\": 0,\n            \"explanation\": \"Wyja\u015bnienie poprawnej odpowiedzi\",\n            \"answers\": [\n              {\n                \"text\": \"Odpowied\u017a 1\",\n                \"is_correct\": true,\n                \"order\": 0\n              },\n              {\n                \"text\": \"Odpowied\u017a 2\",\n                \"is_correct\": false,\n                \"order\": 1\n              }\n            ]\n          }\n        ]\n      },\n      \"practical_task\": {\n        \"title\": \"Zadanie praktyczne - Tytu\u0142\",\n        \"content_markdown\": \"# Zadanie\\n\\nOpis zadania do wykonania...\",\n        \"instructions_markdown\": \"## Instrukcje\\n\\n1. Krok 1\\n2. Krok 2\",\n        \"example_markdown\": \"## Przyk\u0142ad\\n\\n```python\\nprzyk\u0142adowy_kod()\\n```\",\n        \"hints_markdown\": \"## Wskaz\u00f3wki\\n\\n- Wskaz\u00f3wka 1\\n- Wskaz\u00f3wka 2\",\n        \"solution_markdown\": \"## Rozwi\u0105zanie\\n\\n```python\\nrozwiazanie_zadania()\\n```\\n\\nWyja\u015bnienie...\"\n      }\n    }\n  ]\n}\n```\n\nWA\u017bNE:\n- Tre\u015b\u0107 lekcji (content_markdown) powinna by\u0107 szczeg\u00f3\u0142owa (min 500 s\u0142\u00f3w)\n- U\u017cywaj prawid\u0142owego formatowania Markdown\n- Bloki kodu musz\u0105 mie\u0107 okre\u015blony j\u0119zyk (```python, ```javascript, itp.)\n- Ka\u017cde pytanie w quizie musi mie\u0107 co najmniej 4 odpowiedzi\n- Tylko jedna odpowied\u017a mo\u017ce by\u0107 poprawna (is_correct: true)\n- Zadania praktyczne musz\u0105 by\u0107 konkretne i wykonalne\n- U\u017cywaj polskich znak\u00f3w (\u0105, \u0107, \u0119, \u0142, \u0144, \u00f3, \u015b, \u017a, \u017c)\n\nODPOWIED\u0179 WY\u0141\u0104CZNIE POPRAWNYM JSON-em, bez dodatkowych komentarzy ani wyja\u015bnie\u0144."
        }
      },
      "id": "anthropic-claude",
      "name": "Generate Course with Claude",
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "typeVersion": 1,
      "position": [
        450,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "// Extract JSON from Claude's response\nconst response = $input.first().json.response;\n\n// Try to parse the response\nlet courseData;\n\ntry {\n  // Check if response contains markdown code blocks\n  const jsonMatch = response.match(/```(?:json)?\\s*([\\s\\S]*?)```/);\n  \n  if (jsonMatch) {\n    // Extract JSON from code block\n    courseData = JSON.parse(jsonMatch[1].trim());\n  } else {\n    // Try to parse directly\n    courseData = JSON.parse(response);\n  }\n  \n  // Validate required fields\n  if (!courseData.course || !courseData.course.title) {\n    throw new Error('Missing course title');\n  }\n  \n  if (!courseData.lessons || courseData.lessons.length === 0) {\n    throw new Error('No lessons provided');\n  }\n  \n  // Ensure all lessons have required fields\n  courseData.lessons.forEach((lesson, index) => {\n    if (!lesson.title) {\n      throw new Error(`Lesson ${index} missing title`);\n    }\n    if (lesson.order === undefined) {\n      lesson.order = index;\n    }\n    if (!lesson.content_markdown) {\n      throw new Error(`Lesson ${index} missing content_markdown`);\n    }\n  });\n  \n  return {\n    json: courseData\n  };\n  \n} catch (error) {\n  throw new Error(`Failed to parse Claude response: ${error.message}\\n\\nResponse: ${response}`);\n}"
      },
      "id": "code-parser",
      "name": "Parse and Validate JSON",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        650,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $env.DJANGO_APP_URL }}/api/import-course/",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Import-Token",
              "value": "={{ $env.COURSE_IMPORT_TOKEN }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "bodyParameters": {
          "parameters": []
        },
        "body": "={{ JSON.stringify($json) }}",
        "options": {}
      },
      "id": "http-import",
      "name": "Import Course to Django",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.1,
      "position": [
        850,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $json.status }}",
              "operation": "equals",
              "value2": "ok"
            }
          ]
        }
      },
      "id": "check-success",
      "name": "Check Import Success",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        1050,
        300
      ]
    },
    {
      "parameters": {
        "values": {
          "string": [
            {
              "name": "status",
              "value": "success"
            },
            {
              "name": "message",
              "value": "Kurs zosta\u0142 pomy\u015blnie utworzony!"
            },
            {
              "name": "course_title",
              "value": "={{ $json.course_title }}"
            },
            {
              "name": "course_slug",
              "value": "={{ $json.course_slug }}"
            },
            {
              "name": "lessons_count",
              "value": "={{ $json.lessons_count }}"
            },
            {
              "name": "admin_url",
              "value": "={{ $env.DJANGO_APP_URL }}{{ $json.admin_url }}"
            },
            {
              "name": "preview_url",
              "value": "={{ $env.DJANGO_APP_URL }}/course/{{ $json.course_slug }}/"
            }
          ]
        },
        "options": {}
      },
      "id": "success-output",
      "name": "Success Output",
      "type": "n8n-nodes-base.set",
      "typeVersion": 2,
      "position": [
        1250,
        200
      ]
    },
    {
      "parameters": {
        "values": {
          "string": [
            {
              "name": "status",
              "value": "error"
            },
            {
              "name": "message",
              "value": "Nie uda\u0142o si\u0119 zaimportowa\u0107 kursu"
            },
            {
              "name": "error",
              "value": "={{ $json.error || 'Unknown error' }}"
            }
          ]
        },
        "options": {}
      },
      "id": "error-output",
      "name": "Error Output",
      "type": "n8n-nodes-base.set",
      "typeVersion": 2,
      "position": [
        1250,
        400
      ]
    }
  ],
  "connections": {
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Generate Course with Claude",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Course with Claude": {
      "main": [
        [
          {
            "node": "Parse and Validate JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse and Validate JSON": {
      "main": [
        [
          {
            "node": "Import Course to Django",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Import Course to Django": {
      "main": [
        [
          {
            "node": "Check Import Success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Import Success": {
      "main": [
        [
          {
            "node": "Success Output",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Error Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "staticData": null,
  "tags": [],
  "triggerCount": 1,
  "updatedAt": "2026-02-02T00:00:00.000Z",
  "versionId": "1"
}