File size: 4,203 Bytes
450b0f4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import { BadRequestException } from '@nestjs/common';

const SUPPORTED_COMPONENT_TYPES = new Set([
  'dialogue',
  'media',
  'markdown',
  'target_matching',
  'category_sorting',
  'mindmap_reveal',
  'mcq',
  'matching_columns',
  'true_false',
  'sequence_sorting',
  'final_summary',
]);

function requireArray(value: any, label: string) {
  if (!Array.isArray(value)) {
    throw new BadRequestException(`${label} must be an array`);
  }
}

function requireString(value: any, label: string) {
  if (typeof value !== 'string' || value.trim() === '') {
    throw new BadRequestException(`${label} must be a non-empty string`);
  }
}

function requireObject(value: any, label: string) {
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
    throw new BadRequestException(`${label} must be a JSON object`);
  }
}

export class NodeSchemaValidator {
  static validateLessonFlow(data: any) {
    requireArray(data, 'lessonFlow');

    const ids = new Set<string>();
    data.forEach((component: any, idx: number) => {
      requireObject(component, `lessonFlow[${idx}]`);
      requireString(component.id, `lessonFlow[${idx}].id`);
      requireString(component.type, `lessonFlow[${idx}].type`);

      if (ids.has(component.id)) {
        throw new BadRequestException(`lessonFlow[${idx}].id duplicates "${component.id}"`);
      }
      ids.add(component.id);

      if (!SUPPORTED_COMPONENT_TYPES.has(component.type)) {
        throw new BadRequestException(`lessonFlow[${idx}].type "${component.type}" is not supported`);
      }

      requireObject(component.config, `lessonFlow[${idx}].config`);
      this.validateComponentConfig(component, idx);

      if (component.completionRule) {
        requireObject(component.completionRule, `lessonFlow[${idx}].completionRule`);
        requireString(component.completionRule.type, `lessonFlow[${idx}].completionRule.type`);
      }
    });
  }

  private static validateComponentConfig(component: any, idx: number) {
    const config = component.config;
    const label = `lessonFlow[${idx}].config`;

    if (component.type === 'dialogue') {
      requireArray(config.lines, `${label}.lines`);
      config.lines.forEach((line: any, lineIdx: number) => {
        requireString(line.text, `${label}.lines[${lineIdx}].text`);
      });
    }

    if (component.type === 'media') {
      requireString(config.url, `${label}.url`);
    }

    if (component.type === 'markdown') {
      requireString(config.content, `${label}.content`);
    }

    if (component.type === 'target_matching') {
      requireArray(config.targets, `${label}.targets`);
      requireArray(config.items, `${label}.items`);
    }

    if (component.type === 'category_sorting') {
      requireArray(config.categories, `${label}.categories`);
      requireArray(config.cards, `${label}.cards`);
    }

    if (component.type === 'mindmap_reveal') {
      requireArray(config.nodes, `${label}.nodes`);
    }

    if (component.type === 'mcq') {
      requireString(config.question, `${label}.question`);
      requireArray(config.options, `${label}.options`);
      if (!config.options.some((option: any) => option.isCorrect === true || option.correct === true)) {
        throw new BadRequestException(`${label}.options must contain at least one correct option`);
      }
    }

    if (component.type === 'matching_columns') {
      requireArray(config.leftColumn, `${label}.leftColumn`);
      requireArray(config.rightColumn, `${label}.rightColumn`);
      requireArray(config.correctPairs, `${label}.correctPairs`);
    }

    if (component.type === 'true_false') {
      requireString(config.statement, `${label}.statement`);
      if (typeof config.correctAnswer !== 'boolean') {
        throw new BadRequestException(`${label}.correctAnswer must be a boolean`);
      }
    }

    if (component.type === 'sequence_sorting') {
      requireArray(config.items, `${label}.items`);
    }

    if (component.type === 'final_summary') {
      if (config.keyTakeaways !== undefined) {
        requireArray(config.keyTakeaways, `${label}.keyTakeaways`);
      }
    }
  }

  static validateNode(lessonFlow: any) {
    this.validateLessonFlow(lessonFlow);
  }
}