diff --git a/.config/dictionaries/project.dic b/.config/dictionaries/project.dic index 9889668eac6..897ff8e3100 100644 --- a/.config/dictionaries/project.dic +++ b/.config/dictionaries/project.dic @@ -52,12 +52,14 @@ chrono ciphertext ciphertexts CIPs +CNFT COCOAPODS codegen codepoints collabs commitlog concatcp +coproposers COSE coti coverallsapp @@ -112,6 +114,7 @@ gethostname Gitbook gmtime gradlew +Hackathons headful headlessui HIDPI diff --git a/catalyst-gateway/bin/Cargo.toml b/catalyst-gateway/bin/Cargo.toml index c33d2750bd9..75c578f6d92 100644 --- a/catalyst-gateway/bin/Cargo.toml +++ b/catalyst-gateway/bin/Cargo.toml @@ -15,7 +15,7 @@ repository.workspace = true workspace = true [dependencies] -cardano-chain-follower = { version = "0.0.6", git = "https://github.com/input-output-hk/catalyst-libs.git", tag="v0.0.10" } +cardano-chain-follower = { version = "0.0.6", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "v0.0.10" } c509-certificate = { version = "0.0.3", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "v0.0.3" } rbac-registration = { version = "0.0.2", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "v0.0.8" } diff --git a/catalyst_voices/apps/voices/lib/widgets/document_builder/document_property.dart b/catalyst_voices/apps/voices/lib/widgets/document_builder/document_property.dart new file mode 100644 index 00000000000..672c9f2ab97 --- /dev/null +++ b/catalyst_voices/apps/voices/lib/widgets/document_builder/document_property.dart @@ -0,0 +1,34 @@ +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:flutter/material.dart'; + +class DocumentPropertyWidget extends StatelessWidget { + final BaseDocumentDefinition definition; + const DocumentPropertyWidget({super.key, required this.definition}); + + @override + Widget build(BuildContext context) { + return switch (definition) { + SegmentDefinition() => throw UnimplementedError(), + SectionDefinition() => throw UnimplementedError(), + SingleLineTextEntryDefinition() => throw UnimplementedError(), + SingleLineHttpsURLEntryDefinition() => throw UnimplementedError(), + MultiLineTextEntryDefinition() => throw UnimplementedError(), + MultiLineTextEntryMarkdownDefinition() => throw UnimplementedError(), + DropDownSingleSelectDefinition() => throw UnimplementedError(), + MultiSelectDefinition() => throw UnimplementedError(), + SingleLineTextEntryListDefinition() => throw UnimplementedError(), + MultiLineTextEntryListMarkdownDefinition() => throw UnimplementedError(), + SingleLineHttpsURLEntryListDefinition() => throw UnimplementedError(), + NestedQuestionsListDefinition() => throw UnimplementedError(), + NestedQuestionsDefinition() => throw UnimplementedError(), + SingleGroupedTagSelectorDefinition() => throw UnimplementedError(), + TagGroupDefinition() => throw UnimplementedError(), + TagSelectionDefinition() => throw UnimplementedError(), + TokenValueCardanoADADefinition() => throw UnimplementedError(), + DurationInMonthsDefinition() => throw UnimplementedError(), + YesNoChoiceDefinition() => throw UnimplementedError(), + AgreementConfirmationDefinition() => throw UnimplementedError(), + SPDXLicenceOrUrlDefinition() => throw UnimplementedError(), + }; + } +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/catalyst_voices_models.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/catalyst_voices_models.dart index f17f3498d2a..91852ca68f3 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/catalyst_voices_models.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/catalyst_voices_models.dart @@ -7,6 +7,9 @@ export 'campaign/campaign_category.dart'; export 'campaign/campaign_publish.dart'; export 'campaign/campaign_section.dart'; export 'crypto/lock_factor.dart'; +export 'document_builder/document_builder.dart'; +export 'document_builder/document_definitions.dart'; +export 'document_builder/document_schema.dart'; export 'errors/errors.dart'; export 'file/voices_file.dart'; export 'markdown_data.dart'; diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document_builder/document_builder.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document_builder/document_builder.dart new file mode 100644 index 00000000000..54ec501db8f --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document_builder/document_builder.dart @@ -0,0 +1,82 @@ +import 'package:catalyst_voices_models/src/document_builder/document_schema.dart'; +import 'package:equatable/equatable.dart'; + +class DocumentBuilder extends Equatable { + final String schema; + final List segments; + + const DocumentBuilder({ + required this.schema, + required this.segments, + }); + + factory DocumentBuilder.build(DocumentSchema schema) { + return DocumentBuilder( + schema: schema.propertiesSchema, + segments: schema.segments + .map( + (element) => DocumentBuilderSegment( + id: element.id, + sections: element.sections + .map( + (element) => DocumentBuilderSection( + id: element.id, + elements: element.elements + .map( + (e) => DocumentBuilderElement( + id: e.id, + value: e.ref.type.defaultValue, + ), + ) + .toList(), + ), + ) + .toList(), + ), + ) + .toList(), + ); + } + + @override + List get props => [schema, segments]; +} + +class DocumentBuilderSegment extends Equatable { + final String id; + final List sections; + + const DocumentBuilderSegment({ + required this.id, + required this.sections, + }); + + @override + List get props => [id, sections]; +} + +class DocumentBuilderSection extends Equatable { + final String id; + final List elements; + + const DocumentBuilderSection({ + required this.id, + required this.elements, + }); + + @override + List get props => [id, elements]; +} + +class DocumentBuilderElement extends Equatable { + final String id; + final dynamic value; + + const DocumentBuilderElement({ + required this.id, + required this.value, + }); + + @override + List get props => [id, value]; +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document_builder/document_definitions.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document_builder/document_definitions.dart new file mode 100644 index 00000000000..6ac7baa15ab --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document_builder/document_definitions.dart @@ -0,0 +1,567 @@ +import 'package:collection/collection.dart'; +import 'package:equatable/equatable.dart'; +import 'package:meta/meta.dart'; + +enum DocumentDefinitionsObjectType { + string, + object, + integer, + boolean, + array, + unknown; + + const DocumentDefinitionsObjectType(); + + static DocumentDefinitionsObjectType fromString(String value) { + return DocumentDefinitionsObjectType.values.asNameMap()[value] ?? + DocumentDefinitionsObjectType.unknown; + } + + dynamic get defaultValue => switch (this) { + string => '', + integer => 0, + boolean => true, + array => [], + object => {}, + unknown => 'unknown', + }; +} + +enum DocumentDefinitionsContentMediaType { + textPlain('text/plain'), + markdown('text/markdown'), + unknown('unknown'); + + final String schemaValue; + + const DocumentDefinitionsContentMediaType(this.schemaValue); + + static DocumentDefinitionsContentMediaType fromString(String value) { + return DocumentDefinitionsContentMediaType.values + .firstWhereOrNull((e) => e.schemaValue.toLowerCase() == value) ?? + DocumentDefinitionsContentMediaType.unknown; + } +} + +enum DocumentDefinitionsFormat { + path('path'), + uri('uri'), + dropDownSingleSelect('dropDownSingleSelect'), + multiSelect('multiSelect'), + singleLineTextEntryList('singleLineTextEntryList'), + singleLineTextEntryListMarkdown('singleLineTextEntryListMarkdown'), + singleLineHttpsURLEntryList('singleLineHttpsURLEntryList'), + nestedQuestionsList('nestedQuestionsList'), + nestedQuestions('nestedQuestions'), + singleGroupedTagSelector('singleGroupedTagSelector'), + tagGroup('tagGroup'), + tagSelection('tagSelection'), + tokenCardanoADA('token:cardano:ada'), + durationInMonths('datetime:duration:months'), + yesNoChoice('yesNoChoice'), + agreementConfirmation('agreementConfirmation'), + spdxLicenseOrURL('spdxLicenseOrURL'), + unknown('unknown'); + + final String value; + + const DocumentDefinitionsFormat(this.value); + + static DocumentDefinitionsFormat fromString(String value) { + return DocumentDefinitionsFormat.values + .firstWhereOrNull((e) => e.value.toLowerCase() == value) ?? + DocumentDefinitionsFormat.unknown; + } +} + +sealed class BaseDocumentDefinition extends Equatable { + final DocumentDefinitionsObjectType type; + final String note; + + const BaseDocumentDefinition({ + required this.type, + required this.note, + }); + + @visibleForTesting + static final Map refPathToDefinitionType = { + 'segment': SegmentDefinition, + 'section': SectionDefinition, + 'singleLineTextEntry': SingleLineTextEntryDefinition, + 'singleLineHttpsURLEntry': SingleLineHttpsURLEntryDefinition, + 'multiLineTextEntry': MultiLineTextEntryDefinition, + 'multiLineTextEntryMarkdown': MultiLineTextEntryMarkdownDefinition, + 'dropDownSingleSelect': DropDownSingleSelectDefinition, + 'multiSelect': MultiSelectDefinition, + 'singleLineTextEntryList': SingleLineTextEntryListDefinition, + 'multiLineTextEntryListMarkdown': MultiLineTextEntryListMarkdownDefinition, + 'singleLineHttpsURLEntryList': SingleLineHttpsURLEntryListDefinition, + 'nestedQuestionsList': NestedQuestionsListDefinition, + 'nestedQuestions': NestedQuestionsDefinition, + 'singleGroupedTagSelector': SingleGroupedTagSelectorDefinition, + 'tagGroup': TagGroupDefinition, + 'tagSelection': TagSelectionDefinition, + 'tokenValueCardanoADA': TokenValueCardanoADADefinition, + 'durationInMonths': DurationInMonthsDefinition, + 'yesNoChoice': YesNoChoiceDefinition, + 'agreementConfirmation': AgreementConfirmationDefinition, + 'spdxLicenseOrURL': SPDXLicenceOrUrlDefinition, + }; + + static Type typeFromRefPath(String refPath) { + final ref = refPath.split('/').last; + return refPathToDefinitionType[ref] ?? + (throw ArgumentError('Unknown refPath: $refPath')); + } + + static bool isKnownType(String refPath) { + final ref = refPath.split('/').last; + return refPathToDefinitionType[ref] != null; + } +} + +extension BaseDocumentDefinitionListExt on List { + BaseDocumentDefinition getDefinition(String refPath) { + final definitionType = BaseDocumentDefinition.typeFromRefPath(refPath); + final classType = definitionType; + + return firstWhere((e) => e.runtimeType == classType); + } +} + +class SegmentDefinition extends BaseDocumentDefinition { + final bool additionalProperties; + + const SegmentDefinition({ + required super.type, + required super.note, + required this.additionalProperties, + }); + + @override + List get props => [ + type, + note, + additionalProperties, + ]; +} + +class SectionDefinition extends BaseDocumentDefinition { + final bool additionalProperties; + + const SectionDefinition({ + required super.type, + required super.note, + required this.additionalProperties, + }); + + @override + List get props => [ + additionalProperties, + type, + note, + ]; +} + +class SingleLineTextEntryDefinition extends BaseDocumentDefinition { + final DocumentDefinitionsContentMediaType contentMediaType; + final String pattern; + + const SingleLineTextEntryDefinition({ + required super.type, + required super.note, + required this.contentMediaType, + required this.pattern, + }); + + @override + List get props => [ + contentMediaType, + pattern, + type, + note, + ]; +} + +class SingleLineHttpsURLEntryDefinition extends BaseDocumentDefinition { + final DocumentDefinitionsFormat format; + final String pattern; + + const SingleLineHttpsURLEntryDefinition({ + required super.type, + required super.note, + required this.format, + required this.pattern, + }); + + @override + List get props => [ + format, + pattern, + type, + note, + ]; +} + +class MultiLineTextEntryDefinition extends BaseDocumentDefinition { + final DocumentDefinitionsContentMediaType contentMediaType; + final String pattern; + + const MultiLineTextEntryDefinition({ + required super.type, + required super.note, + required this.contentMediaType, + required this.pattern, + }); + + @override + List get props => [ + contentMediaType, + pattern, + type, + note, + ]; +} + +class MultiLineTextEntryMarkdownDefinition extends BaseDocumentDefinition { + final DocumentDefinitionsContentMediaType contentMediaType; + final String pattern; + + const MultiLineTextEntryMarkdownDefinition({ + required super.type, + required super.note, + required this.contentMediaType, + required this.pattern, + }); + + @override + List get props => [ + contentMediaType, + pattern, + type, + note, + ]; +} + +class DropDownSingleSelectDefinition extends BaseDocumentDefinition { + final DocumentDefinitionsFormat format; + final DocumentDefinitionsContentMediaType contentMediaType; + final String pattern; + + const DropDownSingleSelectDefinition({ + required super.type, + required super.note, + required this.format, + required this.contentMediaType, + required this.pattern, + }); + + @override + List get props => [ + format, + contentMediaType, + pattern, + type, + note, + ]; +} + +class MultiSelectDefinition extends BaseDocumentDefinition { + final DocumentDefinitionsFormat format; + final bool uniqueItems; + + const MultiSelectDefinition({ + required super.type, + required super.note, + required this.format, + required this.uniqueItems, + }); + + @override + List get props => [ + format, + uniqueItems, + type, + note, + ]; +} + +class SingleLineTextEntryListDefinition extends BaseDocumentDefinition { + final DocumentDefinitionsFormat format; + final bool uniqueItems; + final List defaultValues; + final Map items; + + const SingleLineTextEntryListDefinition({ + required super.type, + required super.note, + required this.format, + required this.uniqueItems, + required this.defaultValues, + required this.items, + }); + + @override + List get props => [ + format, + uniqueItems, + type, + note, + defaultValues, + items, + ]; +} + +class MultiLineTextEntryListMarkdownDefinition extends BaseDocumentDefinition { + final DocumentDefinitionsFormat format; + final bool uniqueItems; + final List defaultValue; + final Map items; + + const MultiLineTextEntryListMarkdownDefinition({ + required super.type, + required super.note, + required this.format, + required this.uniqueItems, + required this.defaultValue, + required this.items, + }); + + @override + List get props => [ + format, + uniqueItems, + type, + note, + defaultValue, + items, + ]; +} + +class SingleLineHttpsURLEntryListDefinition extends BaseDocumentDefinition { + final DocumentDefinitionsFormat format; + final bool uniqueItems; + final List defaultValue; + final Map items; + + const SingleLineHttpsURLEntryListDefinition({ + required super.type, + required super.note, + required this.format, + required this.uniqueItems, + required this.defaultValue, + required this.items, + }); + + @override + List get props => [ + format, + uniqueItems, + type, + note, + defaultValue, + items, + ]; +} + +class NestedQuestionsListDefinition extends BaseDocumentDefinition { + final DocumentDefinitionsFormat format; + final bool uniqueItems; + final List defaultValue; + + const NestedQuestionsListDefinition({ + required super.type, + required super.note, + required this.format, + required this.uniqueItems, + required this.defaultValue, + }); + + @override + List get props => [ + format, + uniqueItems, + type, + note, + defaultValue, + ]; +} + +class NestedQuestionsDefinition extends BaseDocumentDefinition { + final DocumentDefinitionsFormat format; + final bool additionalProperties; + + const NestedQuestionsDefinition({ + required super.type, + required super.note, + required this.format, + required this.additionalProperties, + }); + + @override + List get props => [ + format, + additionalProperties, + type, + note, + ]; +} + +class SingleGroupedTagSelectorDefinition extends BaseDocumentDefinition { + final DocumentDefinitionsFormat format; + final bool additionalProperties; + + const SingleGroupedTagSelectorDefinition({ + required super.type, + required super.note, + required this.format, + required this.additionalProperties, + }); + + @override + List get props => [ + format, + additionalProperties, + type, + note, + ]; +} + +class TagGroupDefinition extends BaseDocumentDefinition { + final DocumentDefinitionsFormat format; + final String pattern; + + const TagGroupDefinition({ + required super.type, + required super.note, + required this.format, + required this.pattern, + }); + + @override + List get props => [ + format, + pattern, + type, + note, + ]; +} + +class TagSelectionDefinition extends BaseDocumentDefinition { + final DocumentDefinitionsFormat format; + final String pattern; + + const TagSelectionDefinition({ + required super.type, + required super.note, + required this.format, + required this.pattern, + }); + + @override + List get props => [ + format, + pattern, + type, + note, + ]; +} + +class TokenValueCardanoADADefinition extends BaseDocumentDefinition { + final DocumentDefinitionsFormat format; + + const TokenValueCardanoADADefinition({ + required super.type, + required super.note, + required this.format, + }); + + @override + List get props => [ + format, + type, + note, + ]; +} + +class DurationInMonthsDefinition extends BaseDocumentDefinition { + final DocumentDefinitionsFormat format; + + const DurationInMonthsDefinition({ + required super.type, + required super.note, + required this.format, + }); + + @override + List get props => [ + type, + note, + format, + ]; +} + +class YesNoChoiceDefinition extends BaseDocumentDefinition { + final DocumentDefinitionsFormat format; + final bool defaultValue; + + const YesNoChoiceDefinition({ + required super.type, + required super.note, + required this.format, + required this.defaultValue, + }); + + @override + List get props => [ + format, + defaultValue, + type, + note, + ]; +} + +class AgreementConfirmationDefinition extends BaseDocumentDefinition { + final DocumentDefinitionsFormat format; + final bool defaultValue; + final bool constValue; + + const AgreementConfirmationDefinition({ + required super.type, + required super.note, + required this.format, + required this.defaultValue, + required this.constValue, + }); + + @override + List get props => [ + format, + defaultValue, + constValue, + type, + note, + ]; +} + +class SPDXLicenceOrUrlDefinition extends BaseDocumentDefinition { + final DocumentDefinitionsFormat format; + final String pattern; + final DocumentDefinitionsContentMediaType contentMediaType; + + const SPDXLicenceOrUrlDefinition({ + required super.type, + required super.note, + required this.format, + required this.pattern, + required this.contentMediaType, + }); + + @override + List get props => [ + format, + pattern, + type, + note, + ]; +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document_builder/document_schema.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document_builder/document_schema.dart new file mode 100644 index 00000000000..53e64c7d217 --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document_builder/document_schema.dart @@ -0,0 +1,121 @@ +import 'package:catalyst_voices_models/src/document_builder/document_definitions.dart'; +import 'package:catalyst_voices_shared/catalyst_voices_shared.dart'; +import 'package:equatable/equatable.dart'; + +class DocumentSchema extends Equatable { + final String schema; + final String title; + final String description; + final List segments; + final List order; + final String propertiesSchema; + + const DocumentSchema({ + required this.schema, + required this.title, + required this.description, + required this.segments, + required this.order, + required this.propertiesSchema, + }); + + @override + List get props => [ + schema, + title, + description, + segments, + order, + propertiesSchema, + ]; +} + +class DocumentSchemaSegment extends Equatable { + final BaseDocumentDefinition ref; + final String id; + final String title; + final String description; + final List sections; + + const DocumentSchemaSegment({ + required this.ref, + required this.id, + required this.title, + required this.description, + required this.sections, + }); + + @override + List get props => [ + id, + title, + description, + sections, + ]; +} + +class DocumentSchemaSection extends Equatable { + final BaseDocumentDefinition ref; + final String id; + final String title; + final String description; + final List elements; + final bool isRequired; + + const DocumentSchemaSection({ + required this.ref, + required this.id, + required this.title, + required this.description, + required this.elements, + required this.isRequired, + }); + + @override + List get props => [ + ref, + id, + title, + description, + elements, + isRequired, + ]; +} + +class DocumentSchemaElement extends Equatable { + final BaseDocumentDefinition ref; + final String id; + final String title; + final String description; + + final String? defaultValue; + final String guidance; + final List enumValues; + final Range? range; + final Range? itemsRange; + + const DocumentSchemaElement({ + required this.ref, + required this.id, + required this.title, + required this.description, + required this.defaultValue, + required this.guidance, + this.enumValues = const [], + required this.range, + required this.itemsRange, + }); + + @override + List get props => [ + ref, + id, + title, + description, + defaultValue, + guidance, + enumValues, + range, + itemsRange, + ]; +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/pubspec.yaml b/catalyst_voices/packages/internal/catalyst_voices_models/pubspec.yaml index 2103e4b4976..6cd4eb2eac8 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_models/pubspec.yaml +++ b/catalyst_voices/packages/internal/catalyst_voices_models/pubspec.yaml @@ -16,9 +16,12 @@ dependencies: collection: ^1.18.0 convert: ^3.1.1 equatable: ^2.0.7 + json_annotation: ^4.9.0 meta: ^1.10.0 password_strength: ^0.2.0 dev_dependencies: + build_runner: ^2.4.12 catalyst_analysis: ^2.0.0 + json_serializable: ^6.9.0 test: ^1.24.9 diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document_builder_dto.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document_builder_dto.dart new file mode 100644 index 00000000000..0c3fe59f955 --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document_builder_dto.dart @@ -0,0 +1,227 @@ +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:catalyst_voices_shared/catalyst_voices_shared.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'document_builder_dto.g.dart'; + +@JsonSerializable() +class DocumentBuilderDto extends Equatable { + @JsonKey(name: r'$schema') + final String schema; + @JsonKey(fromJson: _fromJsonSegments, toJson: _toJsonSegments) + final List segments; + + const DocumentBuilderDto({ + required this.schema, + required this.segments, + }); + + factory DocumentBuilderDto.fromJson(Map json) { + json['segments'] = Map.from(json)..remove(r'$schema'); + return _$DocumentBuilderDtoFromJson(json); + } + + factory DocumentBuilderDto.fromModel(DocumentBuilder model) { + return DocumentBuilderDto( + schema: model.schema, + segments: + model.segments.map(DocumentBuilderSegmentDto.fromModel).toList(), + ); + } + + Map toJson() { + final segments = {}..addAll({r'$schema': schema}); + for (final segment in this.segments) { + segments.addAll(segment.toJson()); + } + return segments; + } + + DocumentBuilder toModel() { + return DocumentBuilder( + schema: schema, + segments: segments.map((e) => e.toModel()).toList(), + ); + } + + static Map _toJsonSegments( + List segments, + ) { + final map = {}; + for (final segment in segments) { + map[segment.id] = segment.toJson(); + } + return map; + } + + static List _fromJsonSegments( + Map json, + ) { + final listOfSegments = json.convertMapToListWithIds(); + return listOfSegments.map(DocumentBuilderSegmentDto.fromJson).toList(); + } + + @override + List get props => [schema, segments]; +} + +@JsonSerializable() +class DocumentBuilderSegmentDto extends Equatable { + final String id; + @JsonKey(fromJson: _fromJsonSections, toJson: _toJsonSections) + final List sections; + + const DocumentBuilderSegmentDto({ + required this.id, + required this.sections, + }); + + factory DocumentBuilderSegmentDto.fromJson(Map json) { + json['sections'] = Map.from(json)..remove('id'); + return _$DocumentBuilderSegmentDtoFromJson(json); + } + + factory DocumentBuilderSegmentDto.fromModel(DocumentBuilderSegment model) { + return DocumentBuilderSegmentDto( + id: model.id, + sections: + model.sections.map(DocumentBuilderSectionDto.fromModel).toList(), + ); + } + + Map toJson() { + final sections = {}; + for (final section in this.sections) { + sections.addAll(section.toJson()); + } + return { + id: sections, + }; + } + + DocumentBuilderSegment toModel() { + return DocumentBuilderSegment( + id: id, + sections: sections.map((e) => e.toModel()).toList(), + ); + } + + static Map _toJsonSections( + List sections, + ) { + final map = {}; + for (final section in sections) { + map[section.id] = section.toJson(); + } + return map; + } + + static List _fromJsonSections( + Map json, + ) { + final listOfSections = json.convertMapToListWithIds(); + return listOfSections.map(DocumentBuilderSectionDto.fromJson).toList(); + } + + @override + List get props => [id, sections]; +} + +@JsonSerializable() +class DocumentBuilderSectionDto extends Equatable { + final String id; + @JsonKey(fromJson: _fromJsonElements, toJson: _toJsonElements) + final List elements; + + const DocumentBuilderSectionDto({ + required this.id, + required this.elements, + }); + + factory DocumentBuilderSectionDto.fromJson(Map json) { + json['elements'] = Map.from(json)..remove('id'); + return _$DocumentBuilderSectionDtoFromJson(json); + } + + factory DocumentBuilderSectionDto.fromModel(DocumentBuilderSection model) { + return DocumentBuilderSectionDto( + id: model.id, + elements: + model.elements.map(DocumentBuilderElementDto.fromModel).toList(), + ); + } + + Map toJson() { + final map = {}; + for (final element in elements) { + map.addAll(element.toJson()); + } + return { + id: map, + }; + } + + DocumentBuilderSection toModel() { + return DocumentBuilderSection( + id: id, + elements: elements.map((e) => e.toModel()).toList(), + ); + } + + static Map _toJsonElements( + List elements, + ) { + final map = {}; + for (final element in elements) { + map[element.id] = element.value; + } + return map; + } + + static List _fromJsonElements( + Map json, + ) { + final listOfElements = json.convertMapToListWithIdsAndValues(); + return listOfElements.map(DocumentBuilderElementDto.fromJson).toList(); + } + + @override + List get props => [id, elements]; +} + +@JsonSerializable() +class DocumentBuilderElementDto extends Equatable { + final String id; + final dynamic value; + + const DocumentBuilderElementDto({ + required this.id, + required this.value, + }); + + factory DocumentBuilderElementDto.fromJson(Map json) { + return _$DocumentBuilderElementDtoFromJson(json); + } + + factory DocumentBuilderElementDto.fromModel(DocumentBuilderElement model) { + return DocumentBuilderElementDto( + id: model.id, + value: model.value, + ); + } + + Map toJson() => { + id: value, + }; + + DocumentBuilderElement toModel() { + return DocumentBuilderElement( + id: id, + value: value, + ); + } + + @override + List get props => [id, value]; +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document_definitions_dto.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document_definitions_dto.dart new file mode 100644 index 00000000000..9b8d064108a --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document_definitions_dto.dart @@ -0,0 +1,928 @@ +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'document_definitions_dto.g.dart'; + +@JsonSerializable() +class DocumentDefinitionsDto extends Equatable { + final SegmentDto segment; + final SectionDto section; + final SingleLineTextEntryDto singleLineTextEntry; + final SingleLineHttpsURLEntryDto singleLineHttpsURLEntry; + final MultiLineTextEntryDto multiLineTextEntry; + final MultiLineTextEntryMarkdownDto multiLineTextEntryMarkdown; + final DropDownSingleSelectDto dropDownSingleSelect; + final MultiSelectDto multiSelect; + final SingleLineTextEntryListDto singleLineTextEntryList; + final MultiLineTextEntryListMarkdownDto multiLineTextEntryListMarkdown; + final SingleLineHttpsURLEntryListDto singleLineHttpsURLEntryList; + final NestedQuestionsListDto nestedQuestionsList; + final NestedQuestionsDto nestedQuestions; + final SingleGroupedTagSelectorDto singleGroupedTagSelector; + final TagGroupDto tagGroup; + final TagSelectionDto tagSelection; + @JsonKey(name: 'tokenValueCardanoADA') + final TokenValueCardanoAdaDto tokenValueCardanoAda; + final DurationInMonthsDto durationInMonths; + final YesNoChoiceDto yesNoChoice; + final AgreementConfirmationDto agreementConfirmation; + @JsonKey(name: 'spdxLicenseOrURL') + final SPDXLicenceOrUrlDto spdxLicenceOrUrl; + + const DocumentDefinitionsDto({ + required this.segment, + required this.section, + required this.singleLineTextEntry, + required this.singleLineHttpsURLEntry, + required this.multiLineTextEntry, + required this.multiLineTextEntryMarkdown, + required this.dropDownSingleSelect, + required this.multiSelect, + required this.singleLineTextEntryList, + required this.multiLineTextEntryListMarkdown, + required this.singleLineHttpsURLEntryList, + required this.nestedQuestionsList, + required this.nestedQuestions, + required this.singleGroupedTagSelector, + required this.tagGroup, + required this.tagSelection, + required this.tokenValueCardanoAda, + required this.durationInMonths, + required this.yesNoChoice, + required this.agreementConfirmation, + required this.spdxLicenceOrUrl, + }); + + factory DocumentDefinitionsDto.fromJson(Map json) => + _$DocumentDefinitionsDtoFromJson(json); + + Map toJson() => _$DocumentDefinitionsDtoToJson(this); + + List get definitionsModels => [ + segment.toModel(), + section.toModel(), + singleLineTextEntry.toModel(), + multiLineTextEntry.toModel(), + multiLineTextEntryMarkdown.toModel(), + dropDownSingleSelect.toModel(), + multiSelect.toModel(), + singleLineTextEntryList.toModel(), + multiLineTextEntryListMarkdown.toModel(), + singleLineHttpsURLEntry.toModel(), + singleLineHttpsURLEntryList.toModel(), + nestedQuestionsList.toModel(), + nestedQuestions.toModel(), + singleGroupedTagSelector.toModel(), + tagGroup.toModel(), + tagSelection.toModel(), + tokenValueCardanoAda.toModel(), + durationInMonths.toModel(), + yesNoChoice.toModel(), + agreementConfirmation.toModel(), + spdxLicenceOrUrl.toModel(), + ]; + + @override + List get props => [ + section, + segment, + singleLineTextEntry, + singleLineHttpsURLEntry, + multiLineTextEntry, + multiLineTextEntryMarkdown, + dropDownSingleSelect, + multiSelect, + singleLineTextEntryList, + multiLineTextEntryListMarkdown, + singleLineHttpsURLEntryList, + nestedQuestionsList, + nestedQuestions, + singleGroupedTagSelector, + tagGroup, + tagSelection, + tokenValueCardanoAda, + durationInMonths, + yesNoChoice, + agreementConfirmation, + spdxLicenceOrUrl, + ]; +} + +@JsonSerializable() +class SegmentDto extends Equatable { + final String type; + final bool additionalProperties; + @JsonKey(name: 'x-note') + final String note; + + const SegmentDto({ + required this.type, + required this.additionalProperties, + required this.note, + }); + + factory SegmentDto.fromJson(Map json) => + _$SegmentDtoFromJson(json); + + Map toJson() => _$SegmentDtoToJson(this); + + SegmentDefinition toModel() => SegmentDefinition( + type: DocumentDefinitionsObjectType.fromString(type), + note: note, + additionalProperties: additionalProperties, + ); + + @override + List get props => [ + type, + additionalProperties, + note, + ]; +} + +@JsonSerializable() +class SectionDto extends Equatable { + final String type; + final bool additionalProperties; + @JsonKey(name: 'x-note') + final String note; + + const SectionDto({ + required this.type, + required this.additionalProperties, + required this.note, + }); + + factory SectionDto.fromJson(Map json) => + _$SectionDtoFromJson(json); + + Map toJson() => _$SectionDtoToJson(this); + + SectionDefinition toModel() => SectionDefinition( + type: DocumentDefinitionsObjectType.fromString(type), + note: note, + additionalProperties: additionalProperties, + ); + + @override + List get props => [ + type, + additionalProperties, + note, + ]; +} + +@JsonSerializable() +class SingleLineTextEntryDto extends Equatable { + final String type; + final String contentMediaType; + final String pattern; + @JsonKey(name: 'x-note') + final String note; + + const SingleLineTextEntryDto({ + required this.type, + required this.contentMediaType, + required this.pattern, + required this.note, + }); + + factory SingleLineTextEntryDto.fromJson(Map json) => + _$SingleLineTextEntryDtoFromJson(json); + + Map toJson() => _$SingleLineTextEntryDtoToJson(this); + + SingleLineTextEntryDefinition toModel() => SingleLineTextEntryDefinition( + type: DocumentDefinitionsObjectType.fromString(type), + note: note, + contentMediaType: + DocumentDefinitionsContentMediaType.fromString(contentMediaType), + pattern: pattern, + ); + + @override + List get props => [ + type, + contentMediaType, + pattern, + note, + ]; +} + +@JsonSerializable() +class SingleLineHttpsURLEntryDto extends Equatable { + final String type; + final String format; + final String pattern; + @JsonKey(name: 'x-note') + final String note; + + const SingleLineHttpsURLEntryDto({ + required this.type, + required this.format, + required this.pattern, + required this.note, + }); + + factory SingleLineHttpsURLEntryDto.fromJson(Map json) => + _$SingleLineHttpsURLEntryDtoFromJson(json); + + Map toJson() => _$SingleLineHttpsURLEntryDtoToJson(this); + + SingleLineHttpsURLEntryDefinition toModel() { + return SingleLineHttpsURLEntryDefinition( + type: DocumentDefinitionsObjectType.fromString(type), + note: note, + format: DocumentDefinitionsFormat.fromString(format), + pattern: pattern, + ); + } + + @override + List get props => [ + type, + format, + pattern, + note, + ]; +} + +@JsonSerializable() +class MultiLineTextEntryDto extends Equatable { + final String type; + final String contentMediaType; + final String pattern; + @JsonKey(name: 'x-note') + final String note; + + const MultiLineTextEntryDto({ + required this.type, + required this.contentMediaType, + required this.pattern, + required this.note, + }); + + factory MultiLineTextEntryDto.fromJson(Map json) => + _$MultiLineTextEntryDtoFromJson(json); + + Map toJson() => _$MultiLineTextEntryDtoToJson(this); + + MultiLineTextEntryDefinition toModel() { + return MultiLineTextEntryDefinition( + type: DocumentDefinitionsObjectType.fromString(type), + note: note, + contentMediaType: + DocumentDefinitionsContentMediaType.fromString(contentMediaType), + pattern: pattern, + ); + } + + @override + List get props => [ + type, + contentMediaType, + pattern, + note, + ]; +} + +@JsonSerializable() +class MultiLineTextEntryMarkdownDto extends Equatable { + final String type; + final String contentMediaType; + final String pattern; + @JsonKey(name: 'x-note') + final String note; + + const MultiLineTextEntryMarkdownDto({ + required this.type, + required this.contentMediaType, + required this.pattern, + required this.note, + }); + + factory MultiLineTextEntryMarkdownDto.fromJson(Map json) => + _$MultiLineTextEntryMarkdownDtoFromJson(json); + + Map toJson() => _$MultiLineTextEntryMarkdownDtoToJson(this); + + MultiLineTextEntryMarkdownDefinition toModel() { + return MultiLineTextEntryMarkdownDefinition( + type: DocumentDefinitionsObjectType.fromString(type), + note: note, + contentMediaType: + DocumentDefinitionsContentMediaType.fromString(contentMediaType), + pattern: pattern, + ); + } + + @override + List get props => [ + type, + contentMediaType, + pattern, + note, + ]; +} + +@JsonSerializable() +class DropDownSingleSelectDto extends Equatable { + final String type; + final String contentMediaType; + final String pattern; + final String format; + @JsonKey(name: 'x-note') + final String note; + + const DropDownSingleSelectDto({ + required this.type, + required this.contentMediaType, + required this.pattern, + required this.format, + required this.note, + }); + + factory DropDownSingleSelectDto.fromJson(Map json) => + _$DropDownSingleSelectDtoFromJson(json); + + Map toJson() => _$DropDownSingleSelectDtoToJson(this); + + DropDownSingleSelectDefinition toModel() { + return DropDownSingleSelectDefinition( + type: DocumentDefinitionsObjectType.fromString(type), + note: note, + contentMediaType: + DocumentDefinitionsContentMediaType.fromString(contentMediaType), + pattern: pattern, + format: DocumentDefinitionsFormat.fromString(format), + ); + } + + @override + List get props => [ + type, + contentMediaType, + pattern, + format, + note, + ]; +} + +@JsonSerializable() +class MultiSelectDto extends Equatable { + final String type; + final bool uniqueItems; + final String format; + @JsonKey(name: 'x-note') + final String note; + + const MultiSelectDto({ + required this.type, + required this.uniqueItems, + required this.format, + required this.note, + }); + + factory MultiSelectDto.fromJson(Map json) => + _$MultiSelectDtoFromJson(json); + + Map toJson() => _$MultiSelectDtoToJson(this); + + MultiSelectDefinition toModel() { + return MultiSelectDefinition( + type: DocumentDefinitionsObjectType.fromString(type), + note: note, + format: DocumentDefinitionsFormat.fromString(format), + uniqueItems: uniqueItems, + ); + } + + @override + List get props => [ + type, + uniqueItems, + note, + format, + ]; +} + +@JsonSerializable() +class SingleLineTextEntryListDto extends Equatable { + final String type; + final String format; + final bool uniqueItems; + @JsonKey(name: 'default') + final List defaultValue; + final Map items; + @JsonKey(name: 'x-note') + final String note; + + const SingleLineTextEntryListDto({ + required this.type, + required this.format, + required this.uniqueItems, + required this.defaultValue, + required this.items, + required this.note, + }); + + factory SingleLineTextEntryListDto.fromJson(Map json) => + _$SingleLineTextEntryListDtoFromJson(json); + + Map toJson() => _$SingleLineTextEntryListDtoToJson(this); + + SingleLineTextEntryListDefinition toModel() => + SingleLineTextEntryListDefinition( + type: DocumentDefinitionsObjectType.fromString(type), + note: note, + format: DocumentDefinitionsFormat.fromString(format), + uniqueItems: uniqueItems, + defaultValues: defaultValue, + items: items, + ); + + @override + List get props => [ + type, + note, + format, + uniqueItems, + defaultValue, + items, + ]; +} + +@JsonSerializable() +class MultiLineTextEntryListMarkdownDto extends Equatable { + final String type; + final String format; + final bool uniqueItems; + @JsonKey(name: 'default') + final List defaultValue; + final Map items; + @JsonKey(name: 'x-note') + final String note; + + const MultiLineTextEntryListMarkdownDto({ + required this.type, + required this.format, + required this.uniqueItems, + required this.defaultValue, + required this.items, + required this.note, + }); + + factory MultiLineTextEntryListMarkdownDto.fromJson( + Map json, + ) => + _$MultiLineTextEntryListMarkdownDtoFromJson(json); + + Map toJson() => + _$MultiLineTextEntryListMarkdownDtoToJson(this); + + MultiLineTextEntryListMarkdownDefinition toModel() => + MultiLineTextEntryListMarkdownDefinition( + type: DocumentDefinitionsObjectType.fromString(type), + note: note, + format: DocumentDefinitionsFormat.fromString(format), + uniqueItems: uniqueItems, + defaultValue: defaultValue, + items: items, + ); + + @override + List get props => [ + type, + note, + format, + uniqueItems, + defaultValue, + items, + ]; +} + +@JsonSerializable() +class SingleLineHttpsURLEntryListDto extends Equatable { + final String type; + final String format; + final bool uniqueItems; + @JsonKey(name: 'default') + final List defaultValue; + final Map items; + @JsonKey(name: 'x-note') + final String note; + + const SingleLineHttpsURLEntryListDto({ + required this.type, + required this.format, + required this.uniqueItems, + required this.defaultValue, + required this.items, + required this.note, + }); + + factory SingleLineHttpsURLEntryListDto.fromJson(Map json) => + _$SingleLineHttpsURLEntryListDtoFromJson(json); + + Map toJson() => _$SingleLineHttpsURLEntryListDtoToJson(this); + + SingleLineHttpsURLEntryListDefinition toModel() => + SingleLineHttpsURLEntryListDefinition( + type: DocumentDefinitionsObjectType.fromString(type), + note: note, + format: DocumentDefinitionsFormat.fromString(format), + uniqueItems: uniqueItems, + defaultValue: defaultValue, + items: items, + ); + + @override + List get props => [ + type, + note, + format, + uniqueItems, + defaultValue, + items, + ]; +} + +@JsonSerializable() +class NestedQuestionsListDto extends Equatable { + final String type; + final String format; + final bool uniqueItems; + @JsonKey(name: 'default') + final List defaultValue; + @JsonKey(name: 'x-note') + final String note; + + const NestedQuestionsListDto({ + required this.type, + required this.format, + required this.uniqueItems, + required this.defaultValue, + required this.note, + }); + + factory NestedQuestionsListDto.fromJson(Map json) => + _$NestedQuestionsListDtoFromJson(json); + + Map toJson() => _$NestedQuestionsListDtoToJson(this); + + NestedQuestionsListDefinition toModel() => NestedQuestionsListDefinition( + type: DocumentDefinitionsObjectType.fromString(type), + note: note, + format: DocumentDefinitionsFormat.fromString(format), + uniqueItems: uniqueItems, + defaultValue: defaultValue, + ); + + @override + List get props => [ + type, + note, + format, + uniqueItems, + defaultValue, + ]; +} + +@JsonSerializable() +class NestedQuestionsDto extends Equatable { + final String type; + final String format; + final bool additionalProperties; + @JsonKey(name: 'x-note') + final String note; + + const NestedQuestionsDto({ + required this.type, + required this.format, + required this.additionalProperties, + required this.note, + }); + + factory NestedQuestionsDto.fromJson(Map json) => + _$NestedQuestionsDtoFromJson(json); + + Map toJson() => _$NestedQuestionsDtoToJson(this); + + NestedQuestionsDefinition toModel() { + return NestedQuestionsDefinition( + type: DocumentDefinitionsObjectType.fromString(type), + note: note, + format: DocumentDefinitionsFormat.fromString(format), + additionalProperties: additionalProperties, + ); + } + + @override + List get props => [ + type, + format, + note, + additionalProperties, + ]; +} + +@JsonSerializable() +class SingleGroupedTagSelectorDto extends Equatable { + final String type; + final String format; + final bool additionalProperties; + @JsonKey(name: 'x-note') + final String note; + + const SingleGroupedTagSelectorDto({ + required this.type, + required this.format, + required this.additionalProperties, + required this.note, + }); + + factory SingleGroupedTagSelectorDto.fromJson(Map json) => + _$SingleGroupedTagSelectorDtoFromJson(json); + + Map toJson() => _$SingleGroupedTagSelectorDtoToJson(this); + + SingleGroupedTagSelectorDefinition toModel() { + return SingleGroupedTagSelectorDefinition( + type: DocumentDefinitionsObjectType.fromString(type), + note: note, + format: DocumentDefinitionsFormat.fromString(format), + additionalProperties: additionalProperties, + ); + } + + @override + List get props => [ + type, + format, + note, + additionalProperties, + ]; +} + +@JsonSerializable() +class TagGroupDto extends Equatable { + final String type; + final String format; + final String pattern; + @JsonKey(name: 'x-note') + final String note; + + const TagGroupDto({ + required this.type, + required this.format, + required this.pattern, + required this.note, + }); + + factory TagGroupDto.fromJson(Map json) => + _$TagGroupDtoFromJson(json); + + Map toJson() => _$TagGroupDtoToJson(this); + + TagGroupDefinition toModel() { + return TagGroupDefinition( + type: DocumentDefinitionsObjectType.fromString(type), + note: note, + format: DocumentDefinitionsFormat.fromString(format), + pattern: pattern, + ); + } + + @override + List get props => [ + type, + format, + pattern, + note, + ]; +} + +@JsonSerializable() +class TagSelectionDto extends Equatable { + final String type; + final String format; + final String pattern; + @JsonKey(name: 'x-note') + final String note; + + const TagSelectionDto({ + required this.type, + required this.format, + required this.pattern, + required this.note, + }); + + factory TagSelectionDto.fromJson(Map json) => + _$TagSelectionDtoFromJson(json); + + Map toJson() => _$TagSelectionDtoToJson(this); + + TagSelectionDefinition toModel() { + return TagSelectionDefinition( + type: DocumentDefinitionsObjectType.fromString(type), + note: note, + format: DocumentDefinitionsFormat.fromString(format), + pattern: pattern, + ); + } + + @override + List get props => [ + type, + format, + pattern, + note, + ]; +} + +@JsonSerializable() +class TokenValueCardanoAdaDto extends Equatable { + final String type; + final String format; + @JsonKey(name: 'x-note') + final String note; + + const TokenValueCardanoAdaDto({ + required this.type, + required this.format, + required this.note, + }); + + factory TokenValueCardanoAdaDto.fromJson(Map json) => + _$TokenValueCardanoAdaDtoFromJson(json); + + Map toJson() => _$TokenValueCardanoAdaDtoToJson(this); + + TokenValueCardanoADADefinition toModel() => TokenValueCardanoADADefinition( + type: DocumentDefinitionsObjectType.fromString(type), + note: note, + format: DocumentDefinitionsFormat.fromString(format), + ); + + @override + List get props => [ + type, + note, + format, + ]; +} + +@JsonSerializable() +class DurationInMonthsDto extends Equatable { + final String type; + final String format; + @JsonKey(name: 'x-note') + final String note; + + const DurationInMonthsDto({ + required this.type, + required this.format, + required this.note, + }); + + factory DurationInMonthsDto.fromJson(Map json) => + _$DurationInMonthsDtoFromJson(json); + + Map toJson() => _$DurationInMonthsDtoToJson(this); + + DurationInMonthsDefinition toModel() { + return DurationInMonthsDefinition( + type: DocumentDefinitionsObjectType.fromString(type), + note: note, + format: DocumentDefinitionsFormat.fromString(format), + ); + } + + @override + List get props => [ + type, + format, + note, + ]; +} + +@JsonSerializable() +class YesNoChoiceDto extends Equatable { + final String type; + final String format; + @JsonKey(name: 'default') + final bool defaultValue; + @JsonKey(name: 'x-note') + final String note; + + const YesNoChoiceDto({ + required this.type, + required this.format, + required this.defaultValue, + required this.note, + }); + + factory YesNoChoiceDto.fromJson(Map json) => + _$YesNoChoiceDtoFromJson(json); + + Map toJson() => _$YesNoChoiceDtoToJson(this); + + YesNoChoiceDefinition toModel() => YesNoChoiceDefinition( + type: DocumentDefinitionsObjectType.fromString(type), + note: note, + format: DocumentDefinitionsFormat.fromString(format), + defaultValue: defaultValue, + ); + + @override + List get props => [ + type, + format, + note, + defaultValue, + ]; +} + +@JsonSerializable() +class AgreementConfirmationDto extends Equatable { + final String type; + final String format; + @JsonKey(name: 'default') + final bool defaultValue; + @JsonKey(name: 'const') + final bool constValue; + @JsonKey(name: 'x-note') + final String note; + + const AgreementConfirmationDto({ + required this.type, + required this.format, + required this.defaultValue, + required this.constValue, + required this.note, + }); + + factory AgreementConfirmationDto.fromJson(Map json) => + _$AgreementConfirmationDtoFromJson(json); + + Map toJson() => _$AgreementConfirmationDtoToJson(this); + + AgreementConfirmationDefinition toModel() => AgreementConfirmationDefinition( + type: DocumentDefinitionsObjectType.fromString(type), + note: note, + format: DocumentDefinitionsFormat.fromString(format), + defaultValue: defaultValue, + constValue: constValue, + ); + + @override + List get props => [ + type, + format, + note, + defaultValue, + constValue, + ]; +} + +@JsonSerializable() +class SPDXLicenceOrUrlDto extends Equatable { + final String type; + final String contentMediaType; + final String pattern; + final String format; + @JsonKey(name: 'x-note') + final String note; + + const SPDXLicenceOrUrlDto({ + required this.type, + required this.contentMediaType, + required this.pattern, + required this.format, + required this.note, + }); + + factory SPDXLicenceOrUrlDto.fromJson(Map json) => + _$SPDXLicenceOrUrlDtoFromJson(json); + + Map toJson() => _$SPDXLicenceOrUrlDtoToJson(this); + + SPDXLicenceOrUrlDefinition toModel() => SPDXLicenceOrUrlDefinition( + type: DocumentDefinitionsObjectType.fromString(type), + note: note, + format: DocumentDefinitionsFormat.fromString(format), + pattern: pattern, + contentMediaType: + DocumentDefinitionsContentMediaType.fromString(contentMediaType), + ); + + @override + List get props => [ + type, + format, + note, + pattern, + contentMediaType, + ]; +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document_schema_dto.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document_schema_dto.dart new file mode 100644 index 00000000000..6ad7a9d6542 --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/dto/document_schema_dto.dart @@ -0,0 +1,353 @@ +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:catalyst_voices_repositories/src/dto/document_definitions_dto.dart'; +import 'package:catalyst_voices_shared/catalyst_voices_shared.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'document_schema_dto.g.dart'; + +@JsonSerializable() +class DocumentSchemaDto extends Equatable implements Identifiable { + @JsonKey(name: r'$schema') + final String schema; + @override + @JsonKey(name: r'$id') + final String id; + final String title; + final String description; + final DocumentDefinitionsDto definitions; + final String type; + final bool additionalProperties; + @JsonKey( + toJson: _toJsonProperties, + fromJson: _fromJsonProperties, + name: 'properties', + ) + final List segments; + @JsonKey(name: 'x-order') + final List order; + @JsonKey(includeToJson: false) + final String propertiesSchema; + + const DocumentSchemaDto({ + required this.schema, + required this.id, + required this.title, + required this.description, + required this.definitions, + this.type = 'object', + this.additionalProperties = false, + required this.segments, + required this.order, + required this.propertiesSchema, + }); + factory DocumentSchemaDto.fromJson(Map json) { + final segmentsMap = json['properties'] as Map; + json['propertiesSchema'] = + (segmentsMap[r'$schema'] as Map)['const']; + + return _$DocumentSchemaDtoFromJson(json); + } + + Map toJson() => _$DocumentSchemaDtoToJson(this); + + DocumentSchema toModel() { + final sortedProperties = List.from(this.segments) + ..sortByOrder(order); + final segments = sortedProperties + .where((e) => e.ref.contains('segment')) + .map((e) => e.toModel(definitions.definitionsModels)) + .toList(); + return DocumentSchema( + schema: schema, + title: title, + description: description, + segments: segments, + order: order, + propertiesSchema: propertiesSchema, + ); + } + + @override + List get props => [ + schema, + title, + description, + definitions, + type, + additionalProperties, + segments, + order, + ]; + + static Map _toJsonProperties( + List segments, + ) { + final map = {}; + for (final property in segments) { + map[property.id] = property.toJson(); + } + return map; + } + + static List _fromJsonProperties( + Map json, + ) { + final listOfSegments = json.convertMapToListWithIds(); + return listOfSegments.map(DocumentSchemaSegmentDto.fromJson).toList(); + } +} + +@JsonSerializable() +class DocumentSchemaSegmentDto extends Equatable implements Identifiable { + @JsonKey(name: r'$ref') + final String ref; + @override + final String id; + final String title; + final String description; + @JsonKey( + toJson: _toJsonProperties, + fromJson: _fromJsonProperties, + name: 'properties', + ) + final List sections; + final List required; + @JsonKey(name: 'x-order') + final List order; + + const DocumentSchemaSegmentDto({ + required this.ref, + required this.id, + this.title = '', + this.description = '', + required this.sections, + this.required = const [], + this.order = const [], + }); + + factory DocumentSchemaSegmentDto.fromJson(Map json) => + _$DocumentSchemaSegmentDtoFromJson(json); + + Map toJson() => _$DocumentSchemaSegmentDtoToJson(this); + + DocumentSchemaSegment toModel(List definitions) { + final sortedProperties = List.from(this.sections) + ..sortByOrder(order); + + final sections = sortedProperties + .where((element) => element.ref.contains('section')) + .map((e) => e.toModel(definitions, isRequired: required.contains(e.id))) + .toList(); + return DocumentSchemaSegment( + ref: definitions.getDefinition(ref), + id: id, + title: title, + description: description, + sections: sections, + ); + } + + @override + List get props => [ + ref, + id, + title, + description, + sections, + required, + order, + ]; + + static Map _toJsonProperties( + List sections, + ) { + final map = {}; + for (final property in sections) { + map[property.id] = property.toJson(); + } + return map; + } + + static List _fromJsonProperties( + Map json, + ) { + final listOfSections = json.convertMapToListWithIds(); + return listOfSections.map(DocumentSchemaSectionDto.fromJson).toList(); + } +} + +@JsonSerializable() +class DocumentSchemaSectionDto extends Equatable implements Identifiable { + @JsonKey(name: r'$ref') + final String ref; + @override + final String id; + final String title; + final String description; + @JsonKey( + toJson: _toJsonProperties, + fromJson: _fromJsonProperties, + name: 'properties', + ) + final List elements; + final List required; + @JsonKey(name: 'x-order') + final List order; + @JsonKey(name: 'if') + final Map ifs; + final Map then; // Return to this + @JsonKey(name: 'open_source') + final Map openSource; // Return to this + + const DocumentSchemaSectionDto({ + required this.ref, + required this.id, + this.title = '', + this.description = '', + required this.elements, + this.required = const [], + this.order = const [], + this.ifs = const {}, + this.then = const {}, + this.openSource = const {}, + }); + + factory DocumentSchemaSectionDto.fromJson(Map json) => + _$DocumentSchemaSectionDtoFromJson(json); + + Map toJson() => _$DocumentSchemaSectionDtoToJson(this); + + DocumentSchemaSection toModel( + List definitions, { + required bool isRequired, + }) { + final sortedElements = List.from(this.elements) + ..sortByOrder(order); + final elements = sortedElements + .where((element) => BaseDocumentDefinition.isKnownType(element.ref)) + .map((e) => e.toModel(definitions)) + .toList(); + return DocumentSchemaSection( + ref: definitions.getDefinition(ref), + id: id, + title: title, + description: description, + elements: elements, + isRequired: isRequired, + ); + } + + @override + List get props => [ + ref, + id, + title, + description, + elements, + required, + order, + ifs, + then, + ]; + + static Map _toJsonProperties( + List properties, + ) { + final map = {}; + for (final property in properties) { + map[property.id] = property.toJson(); + } + return map; + } + + static List _fromJsonProperties( + Map json, + ) { + final listOfProperties = json.convertMapToListWithIds(); + return listOfProperties.map(DocumentSchemaElementDto.fromJson).toList(); + } +} + +@JsonSerializable() +class DocumentSchemaElementDto extends Equatable implements Identifiable { + @JsonKey(name: r'$ref') + final String ref; + @override + final String id; + final String title; + final String description; + @JsonKey(includeIfNull: false) + final int? minLength; + @JsonKey(includeIfNull: false) + final int? maxLength; + @JsonKey(name: 'default') + final String defaultValue; + @JsonKey(name: 'x-guidance') + final String guidance; + @JsonKey(name: 'enum') + final List enumValues; + @JsonKey(includeIfNull: false) + final int? maxItems; + @JsonKey(includeIfNull: false) + final int? minItems; + @JsonKey(includeIfNull: false) + final int? minimum; + @JsonKey(includeIfNull: false) + final int? maximum; + // TODO(ryszard-schossler): return to this + final Map items; + + const DocumentSchemaElementDto({ + this.ref = '', + required this.id, + this.title = '', + this.description = '', + required this.minLength, + required this.maxLength, + this.defaultValue = '', + this.guidance = '', + this.enumValues = const [], + required this.maxItems, + required this.minItems, + required this.minimum, + required this.maximum, + this.items = const {}, + }); + + factory DocumentSchemaElementDto.fromJson(Map json) => + _$DocumentSchemaElementDtoFromJson(json); + + Map toJson() => _$DocumentSchemaElementDtoToJson(this); + + DocumentSchemaElement toModel(List definitions) { + return DocumentSchemaElement( + ref: definitions.getDefinition(ref), + id: id, + title: title, + description: description, + defaultValue: defaultValue, + guidance: guidance, + enumValues: enumValues, + range: Range.optionalRangeOf(min: minimum, max: maximum), + itemsRange: Range.optionalRangeOf(min: minItems, max: maxItems), + ); + } + + @override + List get props => [ + ref, + id, + title, + description, + minLength, + maxLength, + defaultValue, + guidance, + enumValues, + maxItems, + minItems, + minimum, + maximum, + ]; +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/pubspec.yaml b/catalyst_voices/packages/internal/catalyst_voices_repositories/pubspec.yaml index 937dbe004fe..60fe81f45ca 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_repositories/pubspec.yaml +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/pubspec.yaml @@ -14,6 +14,7 @@ dependencies: catalyst_voices_shared: path: ../catalyst_voices_shared chopper: ^8.0.3 + equatable: ^2.0.7 flutter: sdk: flutter http: ^1.2.2 diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/test/assets/0ce8ab38-9258-4fbc-a62e-7faa6e58318f.schema.json b/catalyst_voices/packages/internal/catalyst_voices_repositories/test/assets/0ce8ab38-9258-4fbc-a62e-7faa6e58318f.schema.json new file mode 100644 index 00000000000..fbf5d568942 --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/test/assets/0ce8ab38-9258-4fbc-a62e-7faa6e58318f.schema.json @@ -0,0 +1,1067 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://cardano.org/schemas/catalyst/f14/proposal", + "title": "F14 Submission Form", + "description": "Schema for the F14 Catalyst Proposal Submission Form", + "definitions": { + "schemaReferenceNonUI": { + "$comment": "NOT UI: used to identify the kind of template document used.", + "type": "string", + "format": "path", + "readOnly": true + }, + "segment": { + "$comment": "UI - Logical Document Section Break.", + "type": "object", + "additionalProperties": false, + "x-note": "Major sections of the proposal. Each segment contains sections of information grouped together." + }, + "section": { + "$comment": "UI - Logical Document Sub-Section Break.", + "type": "object", + "additionalProperties": false, + "x-note": "Subsections containing specific details about the proposal." + }, + "singleLineTextEntry": { + "$comment": "UI - Single Line text entry without any markup or rich text capability.", + "type": "string", + "contentMediaType": "text/plain", + "pattern": "^.*$", + "x-note": "Enter a single line of text. No formatting, line breaks, or special characters are allowed." + }, + "singleLineHttpsURLEntry": { + "$comment": "UI - Single Line text entry for HTTPS Urls.", + "type": "string", + "format": "uri", + "pattern": "^https:.*", + "x-note": "Enter a valid HTTPS URL. Must start with 'https://' and be a complete, working web address." + }, + "multiLineTextEntry": { + "$comment": "UI - Multiline text entry without any markup or rich text capability.", + "type": "string", + "contentMediaType": "text/plain", + "pattern": "^[\\S\\s]*$", + "x-note": "Enter multiple lines of plain text. You can use line breaks but no special formatting." + }, + "multiLineTextEntryMarkdown": { + "$comment": "UI - Multiline text entry with Markdown content.", + "type": "string", + "contentMediaType": "text/markdown", + "pattern": "^[\\S\\s]*$", + "x-note": "Use Markdown formatting for rich text. Available formatting:\n- Headers: # for h1, ## for h2, etc.\n- Lists: * or - for bullets, 1. for numbered\n- Emphasis: *italic* or **bold**\n- Links: [text](url)\n- Code: `inline` or ```block```" + }, + "dropDownSingleSelect": { + "$comment": "UI - Drop Down Selection of a single entry from the defined enum.", + "type": "string", + "contentMediaType": "text/plain", + "pattern": "^.*$", + "format": "dropDownSingleSelect", + "x-note": "Select one option from the dropdown menu. Only one choice is allowed." + }, + "multiSelect": { + "$comment": "UI - Multiselect from the given items.", + "type": "array", + "uniqueItems": true, + "format": "multiSelect", + "x-note": "Select multiple options from the dropdown menu. Multiple choices are allowed." + }, + "singleLineTextEntryList": { + "$comment": "UI - A Growable List of single line text (no markup or richtext).", + "type": "array", + "format": "singleLineTextEntryList", + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/singleLineTextEntry", + "maxLength": 1024 + }, + "x-note": "Add multiple single-line text entries. Each entry should be unique and under 1024 characters." + }, + "multiLineTextEntryListMarkdown": { + "$comment": "UI - A Growable List of markdown formatted text fields.", + "type": "array", + "format": "multiLineTextEntryListMarkdown", + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/multiLineTextEntryMarkdown", + "maxLength": 10240 + }, + "x-note": "Add multiple markdown-formatted text entries. Each entry can include rich formatting and should be unique." + }, + "singleLineHttpsURLEntryList": { + "$comment": "UI - A Growable List of HTTPS URLs.", + "type": "array", + "format": "singleLineHttpsURLEntryList", + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/singleLineHttpsURLEntry", + "maxLength": 1024 + }, + "x-note": "Enter multiple HTTPS URLs. Each URL should be unique and under 1024 characters." + }, + "nestedQuestionsList": { + "$comment": "UI - A Growable List of Questions. The contents are an object, that can have any UI elements within.", + "type": "array", + "format": "nestedQuestionsList", + "uniqueItems": true, + "default": [], + "x-note": "Add multiple questions. Each question should be unique." + }, + "nestedQuestions": { + "$comment": "UI - The container for a nested question set.", + "type": "object", + "format": "nestedQuestions", + "additionalProperties": false, + "x-note": "Add multiple questions. Each question should be unique." + }, + "singleGroupedTagSelector": { + "$comment": "UI - A selector where a top level selection, gives a single choice from a list of tags.", + "type": "object", + "format": "singleGroupedTagSelector", + "additionalProperties": true, + "x-note": "Select one option from the dropdown menu. Only one choice is allowed." + }, + "tagGroup": { + "$comment": "UI - An individual group within a singleGroupedTagSelector.", + "type": "string", + "format": "tagGroup", + "pattern": "^.*$", + "x-note": "Select one option from the dropdown menu. Only one choice is allowed." + }, + "tagSelection": { + "$comment": "UI - An individual tag within the group of a singleGroupedTagSelector.", + "type": "string", + "format": "tagSelection", + "pattern": "^.*$", + "x-note": "Select one option from the dropdown menu. Only one choice is allowed." + }, + "tokenValueCardanoADA": { + "$comment": "UI - A Token Value denominated in Cardano ADA.", + "type": "integer", + "format": "token:cardano:ada", + "x-note": "Enter the amount of Cardano ADA to be used in the proposal." + }, + "durationInMonths": { + "$comment": "UI - A Duration represented in total months.", + "type": "integer", + "format": "datetime:duration:months", + "x-note": "Enter the duration of the proposal in months." + }, + "yesNoChoice": { + "$comment": "UI - A Boolean choice, represented as a Yes/No selection. Yes = true.", + "type": "boolean", + "format": "yesNoChoice", + "default": false, + "x-note": "Select Yes or No." + }, + "agreementConfirmation": { + "$comment": "UI - A Boolean choice, defaults to `false` but its invalid if its not set to `true`.", + "type": "boolean", + "format": "agreementConfirmation", + "default": false, + "const": true, + "x-note": "Select Yes or No." + }, + "spdxLicenseOrURL": { + "$comment": "UI - Drop Down Selection of any valid SPDX Identifier. This is a complex type, it should let the user select one of the valid SPDX licenses, or enter a URL of the license if its proprietary. In the form its just a string.", + "type": "string", + "contentMediaType": "text/plain", + "pattern": "^.*$", + "format": "spdxLicenseOrURL", + "x-note": "Select one option from the dropdown menu. Only one choice is allowed." + } + }, + "type": "object", + "additionalProperties": false, + "properties": { + "$schema": { + "$ref": "#/definitions/schemaReferenceNonUI", + "default": "./0ce8ab38-9258-4fbc-a62e-7faa6e58318f.schema.json", + "const": "./0ce8ab38-9258-4fbc-a62e-7faa6e58318f.schema.json" + }, + "setup": { + "$ref": "#/definitions/segment", + "title": "proposal setup", + "description": "Proposal title", + "properties": { + "title": { + "$ref": "#/definitions/section", + "title": "proposal setup", + "description": "Proposal title", + "properties": { + "title": { + "$ref": "#/definitions/singleLineTextEntry", + "title": "Proposal Title", + "description": "

Proposal title

Please note we suggest you use no more than 60 characters for your proposal title so that it can be easily viewed in the voting app.

", + "minLength": 1, + "maxLength": 60, + "x-guidance": "

The title should clearly express what the proposal is about. Voters can see the title in the voting app, even without opening the proposal, so a clear, unambiguous, and concise title is very important.

" + } + }, + "required": [ + "title" + ] + }, + "proposer": { + "$ref": "#/definitions/section", + "properties": { + "applicant": { + "$ref": "#/definitions/singleLineTextEntry", + "title": "Name and surname of main applicant", + "description": "Name and surname of main applicant", + "x-guidance": "

Please provide the name and surname of the main applicant. The main applicant is considered as the individual responsible for the project and the person authorized to act on behalf of other applicants (where applicable).

", + "minLength": 2, + "maxLength": 100 + }, + "type": { + "$ref": "#/definitions/dropDownSingleSelect", + "title": "Are you delivering this project as an individual or as an entity (whether formally incorporated or not)", + "description": "Are you delivering this project as an individual or as an entity (whether formally incorporated or not)", + "x-guidance": "

Please select from one of the following:

  1. Individual
  2. Entity (Incorporated)
  3. Entity (Not Incorporated)
", + "enum": [ + "Individual", + "Entity (Incorporated)", + "Entity (Not Incorporated)" + ], + "default": "Individual" + }, + "coproposers": { + "$ref": "#/definitions/singleLineTextEntryList", + "title": "Co-proposers and additional applicants", + "description": "Co-proposers and additional applicants", + "x-guidance": "

List any persons who are submitting the proposal jointly with the main applicant. Make sure you have confirmed approval/awareness with these individuals/accounts before adding them. If there is more than one proposer, identify the lead person who is authorized to act on behalf of other co-proposers. IMPORTANT A maximum of 6 (six) proposals can be led or co-proposed by the same applicant or enterprise. Please, reference Fund 14 rules for added detail.

", + "maxItems": 5, + "minItems": 0 + } + }, + "required": [ + "applicant", + "type" + ], + "x-order": [ + "applicant", + "type", + "coproposers" + ] + } + }, + "required": [ + "title", + "proposer" + ], + "x-order": [ + "title", + "proposer" + ] + }, + "summary": { + "$ref": "#/definitions/segment", + "title": "Proposal Summary", + "description": "Key information about your proposal", + "properties": { + "budget": { + "$ref": "#/definitions/section", + "title": "Budget Information", + "properties": { + "requestedFunds": { + "$ref": "#/definitions/tokenValueCardanoADA", + "title": "Requested funds in ADA", + "description": "The amount of funding requested for your proposal", + "x-guidance": "

There is a minimum and a maximum amount of funding that can be requested in a single Catalyst proposal. These are outlined below per each category:

Minimum Funding Amount per proposal:

Cardano Open: A15,000

Cardano Uses Cases: A15,000

Cardano Partners: A500,000

Maximum Funding Amount per proposal:

Cardano Open:

  • Developers (technical): A200,000
  • Ecosystem (non-technical): A100,000

Cardano Uses Cases:

  • Concept A150,000
  • Product: A500,000

Cardano Partners:

  • Enterprise R&D A2,000,000
  • Growth & Acceleration: A2,000,000
", + "minimum": 15000, + "maximum": 2000000 + } + }, + "required": [ + "requestedFunds" + ], + "x-order": [ + "requestedFunds" + ] + }, + "time": { + "$ref": "#/definitions/section", + "properties": { + "duration": { + "$ref": "#/definitions/durationInMonths", + "title": "Project Duration in Months", + "description": "Specify the expected duration of your project. Projects must be completable within 2-12 months.", + "x-guidance": "

Minimum 2 months-Maximum 12 months. The scope of your funding request and this project is expected to produce the deliverables you specify in the proposal within 2-12 months If you believe your project will take longer than 12 months, consider reducing the project's scope so that it becomes achievable within 12 months If your project completes earlier than scheduled so long as you have submitted your PoAs and Project Close-out report and video then your project can be closed out.

", + "minimum": 2, + "maximum": 12 + } + }, + "required": [ + "duration" + ] + }, + "translation": { + "$ref": "#/definitions/section", + "title": "Translation Information", + "description": "Information about the proposal's language and translation status", + "properties": { + "isTranslated": { + "$ref": "#/definitions/yesNoChoice", + "title": "Auto-translated Status", + "description": "Indicate if your proposal has been auto-translated into English from another language", + "x-guidance": "

Tick YES if your proposal has been auto-translated into English from another language so readers are reminded that your proposal has been translated, and that they should be tolerant of any language imperfections. Tick NO if your proposal has not been auto-translated into English from another language

" + }, + "originalLanguage": { + "$ref": "#/definitions/singleLineTextEntry", + "title": "Original Language", + "description": "If auto-translated, specify the original language of your proposal", + "enum": [ + "Arabic", + "Chinese", + "French", + "German", + "Indonesian", + "Italian", + "Japanese", + "Korean", + "Portuguese", + "Russian", + "Spanish", + "Turkish", + "Vietnamese", + "Other" + ] + }, + "originalDocumentLink": { + "$ref": "#/definitions/singleLineHttpsURLEntry", + "title": "Original Document Link", + "description": "Provide a link to the original proposal document in its original language" + } + }, + "if": { + "properties": { + "isTranslated": { + "const": true + } + } + }, + "then": { + "required": [ + "originalLanguage", + "originalDocumentLink" + ], + "properties": { + "originalLanguage": { + "description": "Original language is required when the proposal is translated" + }, + "originalDocumentLink": { + "description": "Link to the original document is required when the proposal is translated" + } + } + }, + "else": { + "properties": { + "originalLanguage": { + "not": {} + }, + "originalDocumentLink": { + "not": {} + } + } + }, + "required": [ + "isTranslated" + ] + }, + "problem": { + "$ref": "#/definitions/section", + "title": "Problem Statement", + "description": "Define the problem your proposal aims to solve", + "properties": { + "statement": { + "$ref": "#/definitions/multiLineTextEntry", + "title": "Problem Description", + "description": "Clearly define the problem you aim to solve. This will be visible in the Catalyst voting app.", + "minLength": 10, + "maxLength": 200, + "x-guidance": "

Ensure you present a well-defined problem. What is the core issue that you hope to fix? Remember: the reader might not recognize the problem unless you state it clearly. This answer will be displayed on the Catalyst voting app, so voters will see it even if they don't open your proposal to read it in detail.

" + }, + "impact": { + "$ref": "#/definitions/multiSelect", + "title": "Impact Areas", + "description": "Select the areas that will be most impacted by solving this problem", + "items": { + "$ref": "#/definitions/singleLineTextEntry", + "enum": [ + "Technical Infrastructure", + "User Experience", + "Developer Tooling", + "Community Growth", + "Economic Sustainability", + "Interoperability", + "Security", + "Scalability", + "Education", + "Adoption" + ] + }, + "minItems": 1, + "maxItems": 3 + } + }, + "required": [ + "statement", + "impact" + ] + }, + "solution": { + "$ref": "#/definitions/section", + "title": "Solution Overview", + "description": "Describe your proposed solution to the problem", + "properties": { + "summary": { + "$ref": "#/definitions/multiLineTextEntry", + "title": "Solution Summary", + "description": "Briefly describe your solution. Focus on what you will do or create to solve the problem.", + "minLength": 10, + "maxLength": 200, + "x-guidance": "

Focus on what you are going to do, or make, or change, to solve the problem. So not 'There should be a way to....' but 'We will make a Clearly state how the solution addresses the specific problem you have identified - connect the 'why' and the 'how' This answer will be displayed on the Catalyst voting app, so voters will see it even if they do not open your proposal and read it in detail.

" + }, + "approach": { + "$ref": "#/definitions/multiLineTextEntry", + "title": "Technical Approach", + "description": "Outline the technical approach or methodology you will use", + "maxLength": 500 + }, + "innovationAspects": { + "$ref": "#/definitions/singleLineTextEntryList", + "title": "Innovation Aspects", + "description": "Key innovative aspects of your solution", + "minItems": 1, + "maxItems": 5 + } + }, + "required": [ + "summary", + "approach" + ] + }, + "supportingLinks": { + "$ref": "#/definitions/section", + "title": "Supporting Documentation", + "description": "Additional resources and documentation for your proposal", + "x-guidance": "

Here, provide links to yours or your partner organization's website, repository, or marketing. Alternatively, provide links to any whitepaper or other publication relevant to your proposal. Note however that this is extra information that voters and Community Reviewers might choose not to read. You should not fail to include any of the questions in this form because you feel the answers can be found elsewhere. If any links are specified make sure these are added in good order (first link must be present before specifying second). Also ensure all links include https. Without these steps, the form will not be submittable and show errors

", + "properties": { + "mainRepository": { + "$ref": "#/definitions/singleLineHttpsURLEntry", + "title": "Main Code Repository", + "description": "Primary repository where the project's code will be hosted" + }, + "documentation": { + "$ref": "#/definitions/singleLineHttpsURLEntry", + "title": "Documentation URL", + "description": "Main documentation site or resource for the project" + }, + "other": { + "$ref": "#/definitions/singleLineHttpsURLEntryList", + "title": "Resource Links", + "description": "Links to any other relevant documentation, code repositories, or marketing materials. All links must use HTTPS.", + "minItems": 0, + "maxItems": 5 + } + } + }, + "dependencies": { + "$ref": "#/definitions/section", + "title": "Project Dependencies", + "description": "External dependencies and requirements for project success", + "x-guidance": "

If your project has any dependencies and prerequisites for your project's success, list them here. These are usually external factors (such as third-party suppliers, external resources, third-party software, etc.) that may cause a delay, since a project has less control over them. In case of third party software, indicate whether you have the necessary licenses and permission to use such software.

", + "properties": { + "details": { + "$ref": "#/definitions/nestedQuestionsList", + "title": "Dependency Details", + "description": "List and describe each dependency", + "items": { + "$ref": "#/definitions/nestedQuestions", + "properties": { + "name": { + "$ref": "#/definitions/singleLineTextEntry", + "title": "Dependency Name", + "description": "Name of the organization, technology, or resource", + "maxLength": 100 + }, + "type": { + "$ref": "#/definitions/dropDownSingleSelect", + "title": "Dependency Type", + "description": "Type of dependency", + "enum": [ + "Technical", + "Organizational", + "Legal", + "Financial", + "Other" + ] + }, + "description": { + "$ref": "#/definitions/multiLineTextEntry", + "title": "Description", + "description": "Explain why this dependency is essential and how it affects your project", + "maxLength": 500 + }, + "mitigationPlan": { + "$ref": "#/definitions/multiLineTextEntry", + "title": "Mitigation Plan", + "description": "How will you handle potential issues with this dependency", + "maxLength": 300 + } + }, + "required": [ + "name", + "type", + "description" + ] + }, + "minItems": 0, + "maxItems": 10 + } + } + }, + "open_source": { + "$ref": "#/definitions/section", + "title": "Project Open Source", + "description": "Will your project's output be fully open source? Open source refers to something people can modify and share because its design is publicly accessible.", + "x-guidance": "

Open source software is software with source code that anyone can inspect, modify, and enhance. Conversely, only the original authors of proprietary software can legally copy, inspect, and alter that software

", + "properties": { + "source_code": { + "$ref": "#/definitions/spdxLicenseOrURL" + }, + "documentation": { + "$ref": "#/definitions/spdxLicenseOrURL" + }, + "note": { + "$ref": "#/definitions/multiLineTextEntry", + "title": "More Information", + "description": "Please provide here more information on the open source status of your project outputs", + "maxLength": 500, + "x-guidance": "

If you did not answer PROPRIETARY to the above questions, the project should be open source available throughout the entire lifecycle of the project with a declared open-source repository. Please indicate here the type of license you intend to use for open source and provide any further information you feel is relevant to the open source status of your project outputs If only certain elements of your code will be open source please clarify which elements will be open source here. If you answered NO to the above question, please give further details as to why your projects outputs will not be open source METADATA

" + } + }, + "required": [ + "source_code", + "documentation" + ], + "x-order": [ + "source_code", + "documentation", + "note" + ] + } + }, + "x-order": [ + "budget", + "time", + "translation", + "problem", + "solution", + "supportingLinks", + "dependencies", + "open_source" + ] + }, + "horizons": { + "$ref": "#/definitions/segment", + "title": "Horizons", + "properties": { + "theme": { + "$ref": "#/definitions/section", + "title": "Horizons", + "description": "Long-term vision and categorization of your project", + "properties": { + "grouped_tag": { + "$ref": "#/definitions/singleGroupedTagSelector", + "oneOf": [ + { + "properties": { + "group": { + "$ref": "#/definitions/tagGroup", + "const": "Governance" + }, + "tag": { + "$ref": "#/definitions/tagSelection", + "enum": [ + "Governance", + "DAO" + ] + } + } + }, + { + "properties": { + "group": { + "$ref": "#/definitions/tagGroup", + "const": "Education" + }, + "tag": { + "$ref": "#/definitions/tagSelection", + "enum": [ + "Education", + "Learn to Earn", + "Training", + "Translation" + ] + } + } + }, + { + "properties": { + "group": { + "$ref": "#/definitions/tagGroup", + "const": "Community & Outreach" + }, + "tag": { + "$ref": "#/definitions/tagSelection", + "enum": [ + "Connected Community", + "Community", + "Community Outreach", + "Social Media" + ] + } + } + }, + { + "properties": { + "group": { + "$ref": "#/definitions/tagGroup", + "const": "Development & Tools" + }, + "tag": { + "$ref": "#/definitions/tagSelection", + "enum": [ + "Developer Tools", + "L2", + "Infrastructure", + "Analytics", + "AI", + "Research", + "UTXO", + "P2P" + ] + } + } + }, + { + "properties": { + "group": { + "$ref": "#/definitions/tagGroup", + "const": "Identity & Security" + }, + "tag": { + "$ref": "#/definitions/tagSelection", + "enum": [ + "Identity & Verification", + "Cybersecurity", + "Security", + "Authentication", + "Privacy" + ] + } + } + }, + { + "properties": { + "group": { + "$ref": "#/definitions/tagGroup", + "const": "DeFi" + }, + "tag": { + "$ref": "#/definitions/tagSelection", + "enum": [ + "DeFi", + "Payments", + "Stablecoin", + "Risk Management", + "Yield", + "Staking", + "Lending" + ] + } + } + }, + { + "properties": { + "group": { + "$ref": "#/definitions/tagGroup", + "const": "Real World Applications" + }, + "tag": { + "$ref": "#/definitions/tagSelection", + "enum": [ + "Wallet", + "Marketplace", + "Manufacturing", + "IoT", + "Financial Services", + "E-commerce", + "Business Services", + "Supply Chain", + "Real Estate", + "Healthcare", + "Tourism", + "Entertainments", + "RWA", + "Music", + "Tokenization" + ] + } + } + }, + { + "properties": { + "group": { + "$ref": "#/definitions/tagGroup", + "const": "Events & Marketing" + }, + "tag": { + "$ref": "#/definitions/tagSelection", + "enum": [ + "Events", + "Marketing", + "Hackathons", + "Accelerator", + "Incubator" + ] + } + } + }, + { + "properties": { + "group": { + "$ref": "#/definitions/tagGroup", + "const": "Interoperability" + }, + "tag": { + "$ref": "#/definitions/tagSelection", + "enum": [ + "Cross-chain", + "Interoperability", + "Off-chain", + "Legal", + "Policy Advocacy", + "Standards" + ] + } + } + }, + { + "properties": { + "group": { + "$ref": "#/definitions/tagGroup", + "const": "Sustainability" + }, + "tag": { + "$ref": "#/definitions/tagSelection", + "enum": [ + "Sustainability", + "Environment", + "Agriculture" + ] + } + } + }, + { + "properties": { + "group": { + "$ref": "#/definitions/tagGroup", + "const": "Smart Contracts" + }, + "tag": { + "$ref": "#/definitions/tagSelection", + "enum": [ + "Smart Contract", + "Smart Contracts", + "Audit", + "Oracles" + ] + } + } + }, + { + "properties": { + "group": { + "$ref": "#/definitions/tagGroup", + "const": "GameFi" + }, + "tag": { + "$ref": "#/definitions/tagSelection", + "enum": [ + "Gaming", + "Gaming (GameFi)", + "Entertainment", + "Metaverse" + ] + } + } + }, + { + "properties": { + "group": { + "$ref": "#/definitions/tagGroup", + "const": "NFT" + }, + "tag": { + "$ref": "#/definitions/tagSelection", + "enum": [ + "NFT", + "CNFT", + "Collectibles", + "Digital Twin" + ] + } + } + } + ] + } + }, + "x-order": [ + "theme" + ] + } + }, + "x-order": [ + "theme" + ] + }, + "details": { + "$ref": "#/definitions/segment", + "title": "Your Project and Solution", + "properties": { + "solution": { + "$ref": "#/definitions/section", + "title": "Solution", + "description": "

How you write this section will depend on what type of proposal you are writing. You might want to include details on:


  • How do you perceive the problem you are solving?
  • What are your reasons for approaching it in the way that you have?
  • Who will your project engage?
  • How will you demonstrate or prove your impact?


Explain what is unique about your solution, who will benefit, and why this is important to Cardano.

", + "properties": { + "solution": { + "$ref": "#/definitions/multiLineTextEntryMarkdown", + "minLength": 1, + "maxLength": 10240 + } + } + }, + "impact": { + "$ref": "#/definitions/section", + "title": "Impact", + "description": "

Please include here a description of how you intend to measure impact (whether quantitative or qualitative) and how and with whom you will share your outputs:


  • In what way will the success of your project bring value to the Cardano Community? 
  • How will you measure this impact? 
  • How will you share the outputs and opportunities that result from your project?
", + "properties": { + "impact": { + "$ref": "#/definitions/multiLineTextEntryMarkdown", + "minLength": 1, + "maxLength": 10240 + } + } + }, + "feasibility": { + "$ref": "#/definitions/section", + "title": "Capabilities & Feasibility", + "description": "

Please describe your existing capabilities that demonstrate how and why you believe you’re best suited to deliver this project?

Please include the steps or processes that demonstrate that you can be trusted to manage funds properly.

", + "properties": { + "feasibility": { + "$ref": "#/definitions/multiLineTextEntryMarkdown", + "minLength": 1, + "maxLength": 10240 + } + } + } + }, + "x-order": [ + "solution", + "impact", + "feasibility" + ] + }, + "milestones": { + "$ref": "#/definitions/segment", + "title": "Milestones", + "properties": { + "milestones": { + "$ref": "#/definitions/section", + "title": "Project Milestones", + "description": "

Each milestone must declare:

  • A: Milestone outputs
  • B: Acceptance criteria
  • C: Evidence of completion

Requirements:

  • For Grant Amounts up to 75k ada: minimum 3 milestones (2 + final)
  • For Grant Amounts 75k-150k ada: minimum 4 milestones (3 + final)
  • The final milestone must include Project Close-out Report and Video
", + "properties": { + "milestone_list": { + "type": "array", + "title": "Milestones", + "description": "What are the key milestones you need to achieve in order to complete your project successfully?", + "x-guidance": "

Milestone Requirements:

  • For Grant Amounts of up to 75k ada: at least 2 milestones, plus the final one including Project Close-out Report and Video, must be included (3 milestones in total)
  • For Grant Amounts over 75k ada up to 150k ada: at least 3 milestones, plus the final one including Project Close-out Report and Video, must be included (4 milestones in total)
  • For Grant Amounts over 150k ada up to 300k ada: at least 4 milestones, plus the final one including Project Close-out Report and Video, must be included (5 milestones in total)
  • For Grant Amounts exceeding 300k ada: at least 5 milestones, plus the final one including Project Close-out Report and Video, must be included (6 milestones in total)
", + "minItems": 3, + "maxItems": 6, + "items": { + "type": "object", + "required": [ + "title", + "outputs", + "acceptance_criteria", + "evidence", + "delivery_month", + "cost" + ], + "properties": { + "title": { + "$ref": "#/definitions/singleLineTextEntry", + "title": "Milestone Title", + "description": "A clear, concise title for this milestone", + "maxLength": 100 + }, + "outputs": { + "$ref": "#/definitions/multiLineTextEntryMarkdown", + "title": "Milestone Outputs", + "description": "What will be delivered in this milestone", + "maxLength": 1000 + }, + "acceptance_criteria": { + "$ref": "#/definitions/multiLineTextEntryMarkdown", + "title": "Acceptance Criteria", + "description": "Specific conditions that must be met", + "maxLength": 1000 + }, + "evidence": { + "$ref": "#/definitions/multiLineTextEntryMarkdown", + "title": "Evidence of Completion", + "description": "How you will demonstrate achievement", + "maxLength": 1000 + }, + "delivery_month": { + "$ref": "#/definitions/durationInMonths", + "title": "Delivery Month", + "description": "The month when this milestone will be delivered", + "minimum": 1, + "maximum": 12 + }, + "cost": { + "$ref": "#/definitions/tokenValueCardanoADA", + "title": "Cost in ADA", + "description": "The cost of this milestone in ADA" + }, + "progress": { + "$ref": "#/definitions/dropDownSingleSelect", + "title": "Progress Status", + "description": "Current status of the milestone", + "enum": [ + "Not Started", + "In Progress", + "Completed", + "Delayed" + ], + "default": "Not Started" + } + } + } + } + }, + "required": [ + "milestone_list" + ] + } + }, + "x-order": [ + "milestones" + ] + }, + "pitch": { + "$ref": "#/definitions/segment", + "title": "Final Pitch", + "properties": { + "team": { + "$ref": "#/definitions/section", + "title": "Team", + "properties": { + "who": { + "$ref": "#/definitions/multiLineTextEntryMarkdown", + "title": "Who is in the project team and what are their roles?", + "description": "

List your team, their Linkedin profiles (or similar) and state what aspect of the proposal’s work each team member will undertake.


If you are planning to recruit additional team members, please state what specific skills you will be looking for in the people you recruit, so readers can see that you understand what skills will be needed to complete the project.


You are expected to have already engaged the relevant members of the organizations referenced so you understand if they are willing and/or have capacity to support the project. If you have not taken any steps to engage with your team yet, it is likely that the resources will not be available if you are approved for funding, which can jeopardize the project before it has even begun. The Catalyst team cannot help with this, meaning you are expected to have understood the requirements and engaged the necessary people before submitting a proposal.


Have you engaged anyone on any of the technical group channels (eg Discord or Telegram), or do you have a direct line of communications with the people and resources required?


Important: Catalyst funding is not anonymous, and some level of ‘proof of life’ verifications will take place before initial funding is released. Also remember that your proposal will be publicly available, so make sure to obtain any consent required before including confidential or third party information.


All Project Participants must disclose their role and scope of services across any submitted proposals, even if they are not in the lead or co-proposer role, such as an implementer, vendor, service provider, etc. Failure to disclose this information may lead to disqualification from the current grant round.

", + "minLength": 1, + "maxLength": 10240 + } + } + }, + "budget": { + "$ref": "#/definitions/section", + "title": "Budget & Costs", + "properties": { + "costs": { + "$ref": "#/definitions/multiLineTextEntryMarkdown", + "title": "Please provide a cost breakdown of the proposed work and resources", + "description": "

Make sure every element mentioned in your plan reflects its cost. It may be helpful to refer to your plan and timeline, list all the resources you will need at each stage, and what they cost.


Here, provide a clear description of any third party product or service you will be using. This could be hardware, software licenses, professional services (legal, accounting, code auditing, etc) but does not need to include the use of contracted programmers and developers.


The exact budget elements you include will depend on what type of work you are doing, and you might need to give less detail for a small, low-budget proposal. If the cost of the project will exceed the funding request, please provide information about alternative sources of funding.


Consider including budget elements for publicity / marketing / promotion / community engagement; project management; documentation; and reporting back to the community. Most proposals need these, but many proposers forget to include them.


It is the project team’s responsibility to properly manage the funds provided. Make sure to reference Fund Rules to understand eligibility around costs.

", + "minLength": 1, + "maxLength": 10240 + } + } + }, + "value": { + "$ref": "#/definitions/section", + "title": "Value for Money", + "properties": { + "note": { + "$ref": "#/definitions/multiLineTextEntryMarkdown", + "title": "How does the cost of the project represent value for money for the Cardano ecosystem?", + "description": "

Use the response to provide the context about the costs you listed previously, particularly if they are high.


It may be helpful to include some brief information on how you have decided on the costs of the project. 


For instance, can you justify with supporting evidence that costs are proportional to the average wage in your country, or typical freelance rates in your industry? Is there anything else that helps to support how the project represents value for money?

", + "minLength": 1, + "maxLength": 10240 + } + } + } + }, + "x-order": [ + "team", + "budget", + "value" + ] + }, + "agreements": { + "$ref": "#/definitions/segment", + "title": "Acknowledgements", + "properties": { + "mandatory": { + "$ref": "#/definitions/section", + "title": "Mandatory", + "properties": { + "fund_rules": { + "$ref": "#/definitions/agreementConfirmation", + "title": "Fund Rules:", + "description": "

By submitting a proposal to Project Catalyst Fund14, I confirm that I have read and agree to be bound by the Fund Rules.

" + }, + "terms_and_conditions": { + "$ref": "#/definitions/agreementConfirmation", + "title": "Terms and Conditions:", + "description": "

By submitting a proposal to Project Catalyst Fund14, I confirm that I have read and agree to be bound by the Project Catalyst Terms and Conditions.

" + }, + "privacy_policy": { + "$ref": "#/definitions/agreementConfirmation", + "title": "Privacy Policy: ", + "description": "

I acknowledge and agree that any data I share in connection with my participation in Project Catalyst Fund14 will be collected, stored, used and processed in accordance with the Catalyst FC’s Privacy Policy.

" + } + }, + "required": [ + "fund_rules", + "terms_and_conditions", + "privacy_policy" + ], + "x-order": [ + "fund_rules", + "terms_and_conditions", + "privacy_policy" + ] + } + }, + "x-order": [ + "mandatory" + ] + } + }, + "x-order": [ + "setup", + "summary", + "horizons", + "details", + "milestones", + "pitch", + "agreements" + ] +} \ No newline at end of file diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/test/assets/generic_proposal.json b/catalyst_voices/packages/internal/catalyst_voices_repositories/test/assets/generic_proposal.json new file mode 100644 index 00000000000..e0100f00cc8 --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/test/assets/generic_proposal.json @@ -0,0 +1,138 @@ +{ + "$schema": "./0ce8ab38-9258-4fbc-a62e-7faa6e58318f.schema.json", + "setup": { + "title": { + "title": "Example Catalyst Proposal" + }, + "proposer": { + "applicant": "John Smith", + "type": "Individual", + "coproposers": [ + "Jane Doe", + "Bob Wilson" + ] + } + }, + "summary": { + "budget": { + "requestedFunds": 150000 + }, + "time": { + "duration": 6 + }, + "translation": { + "isTranslated": true, + "originalLanguage": "German", + "originalDocumentLink": "https://example.com/original-doc" + }, + "problem": { + "statement": "Current challenge in the Cardano ecosystem...", + "impact": [ + "Technical Infrastructure", + "Developer Tooling", + "Adoption" + ] + }, + "solution": { + "summary": "Our solution provides a comprehensive toolkit...", + "approach": "We will implement this solution using...", + "innovationAspects": [ + "Novel testing framework", + "Automated integration tools" + ] + }, + "supportingLinks": { + "mainRepository": "https://github.com/example/project", + "documentation": "https://docs.example.com", + "other": [ + "https://example.com/whitepaper", + "https://example.com/roadmap" + ] + }, + "dependencies": { + "details": [ + { + "name": "External API Service", + "type": "Technical", + "description": "Integration with third-party API service", + "mitigationPlan": "Build fallback mechanisms and maintain alternative providers" + } + ] + }, + "open_source": { + "source_code": "MIT", + "documentation": "MIT", + "note": "All project outputs will be open source under MIT license" + } + }, + "horizons": { + "theme": { + "grouped_tag": { + "group": "DeFi", + "tag": "Staking" + } + } + }, + "details": { + "solution": { + "solution": "Our solution involves developing a comprehensive toolkit that will enhance the Cardano developer experience..." + }, + "impact": { + "impact": "The project will significantly impact developer productivity by reducing development time and improving code quality..." + }, + "feasibility": { + "feasibility": "Our team has extensive experience in blockchain development and has successfully delivered similar projects..." + } + }, + "milestones": { + "milestones": { + "milestone_list": [ + { + "title": "Initial Setup and Planning", + "outputs": "Project infrastructure setup and detailed planning documents", + "acceptance_criteria": "- Development environment configured\n- Detailed project plan approved", + "evidence": "- GitHub repository setup\n- Documentation of infrastructure\n- Project planning documents", + "delivery_month": 1, + "cost": 30000, + "progress": "Not Started" + }, + { + "title": "Core Development", + "outputs": "Implementation of main features", + "acceptance_criteria": "- Core features implemented\n- Unit tests passing", + "evidence": "- Code repository\n- Test results\n- Technical documentation", + "delivery_month": 3, + "cost": 60000, + "progress": "Not Started" + }, + { + "title": "Final Release and Documentation", + "outputs": "Project completion, documentation, and Project Close-out Report and Video", + "acceptance_criteria": "- All features implemented and tested\n- Documentation complete\n- Close-out report and video delivered", + "evidence": "- Final release\n- Complete documentation\n- Close-out report and video", + "delivery_month": 6, + "cost": 60000, + "progress": "Not Started" + } + ] + } + }, + "pitch": { + "team": { + "who": "Our team consists of experienced blockchain developers with proven track records..." + }, + "budget": { + "costs": "Budget breakdown:\n- Development (70%): 105,000 ADA\n- Testing (15%): 22,500 ADA\n- Documentation (15%): 22,500 ADA" + }, + "value": { + "note": "This project provides excellent value for money by delivering essential developer tools..." + } + }, + "agreements": { + "mandatory": { + "fund_rules": true, + "terms_and_conditions": true, + "privacy_policy": true + } + } +} \ No newline at end of file diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/test/helpers/read_json.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/test/helpers/read_json.dart new file mode 100644 index 00000000000..982db98a6ba --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/test/helpers/read_json.dart @@ -0,0 +1,10 @@ +import 'dart:io'; + +String readJson(String name) { + var dir = Directory.current.path; + + if (dir.endsWith('/test')) { + dir = dir.replaceAll('/test', ''); + } + return File('$dir/$name').readAsStringSync(); +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document_builder/document_builder_test.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document_builder/document_builder_test.dart new file mode 100644 index 00000000000..d07c5b43bad --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document_builder/document_builder_test.dart @@ -0,0 +1,51 @@ +import 'dart:convert'; + +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:catalyst_voices_repositories/src/dto/document_builder_dto.dart'; +import 'package:catalyst_voices_repositories/src/dto/document_schema_dto.dart'; +import 'package:test/test.dart'; + +import '../../helpers/read_json.dart'; + +void main() { + group('DocumentBuilder', () { + const schemaPath = + 'test/assets/0ce8ab38-9258-4fbc-a62e-7faa6e58318f.schema.json'; + + late Map schemaJson; + + setUpAll(() { + schemaJson = json.decode(readJson(schemaPath)) as Map; + }); + + test('Converts segments list into object for JSON', () { + final schemaDto = DocumentSchemaDto.fromJson(schemaJson); + final schema = schemaDto.toModel(); + + final proposalBuilder = DocumentBuilder.build(schema); + final proposalBuilderDto = DocumentBuilderDto.fromModel(proposalBuilder); + final proposalBuilderJson = proposalBuilderDto.toJson(); + + for (final segment in proposalBuilderDto.segments) { + expect(proposalBuilderJson[segment.id], isA>()); + } + }); + + test('Converts object from JSON into List of segments', () { + final schemaDto = DocumentSchemaDto.fromJson(schemaJson); + final schema = schemaDto.toModel(); + + final proposalBuilder = DocumentBuilder.build(schema); + final proposalBuilderDto = DocumentBuilderDto.fromModel(proposalBuilder); + + final proposalBuilderJson = proposalBuilderDto.toJson(); + final proposalBuilderDtoFromJson = + DocumentBuilderDto.fromJson(proposalBuilderJson); + + expect( + proposalBuilderDtoFromJson.segments.length, + proposalBuilderDto.segments.length, + ); + }); + }); +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document_builder/document_definitions_test.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document_builder/document_definitions_test.dart new file mode 100644 index 00000000000..02f357faad5 --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document_builder/document_definitions_test.dart @@ -0,0 +1,54 @@ +import 'dart:convert'; + +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:catalyst_voices_repositories/src/dto/document_schema_dto.dart'; +import 'package:test/test.dart'; + +import '../../helpers/read_json.dart'; + +void main() { + group('DocumentDefinitions', () { + const schemaPath = + 'test/assets/0ce8ab38-9258-4fbc-a62e-7faa6e58318f.schema.json'; + + late Map schemaJson; + + setUpAll(() { + schemaJson = json.decode(readJson(schemaPath)) as Map; + }); + test( + // ignore: lines_longer_than_80_chars + 'Check if all definition are in definition list inside DefinitionDto model', + () async { + final schemaDto = DocumentSchemaDto.fromJson(schemaJson); + final definitions = schemaDto.definitions.definitionsModels; + + for (final value + in BaseDocumentDefinition.refPathToDefinitionType.values) { + final occurrences = definitions + .where((element) => element.runtimeType == value) + .length; + expect( + occurrences, + equals(1), + reason: 'Value $value appears $occurrences times in the list', + ); + } + }, + ); + + test('Check if document definition media type is parse correctly', () { + final schemaDto = DocumentSchemaDto.fromJson(schemaJson); + final definitions = schemaDto.definitions.definitionsModels; + + final singleLineTextEntry = + definitions.getDefinition('#/definitions/singleLineTextEntry') + as SingleLineTextEntryDefinition; + + expect( + singleLineTextEntry.contentMediaType, + DocumentDefinitionsContentMediaType.textPlain, + ); + }); + }); +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document_builder/document_schema_test.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document_builder/document_schema_test.dart new file mode 100644 index 00000000000..a5c7969d46a --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document_builder/document_schema_test.dart @@ -0,0 +1,60 @@ +import 'dart:convert'; + +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:catalyst_voices_repositories/src/dto/document_schema_dto.dart'; +import 'package:test/test.dart'; + +import '../../helpers/read_json.dart'; + +void main() { + group('DocumentSchema', () { + const schemaPath = + 'test/assets/0ce8ab38-9258-4fbc-a62e-7faa6e58318f.schema.json'; + + late Map schemaJson; + + setUpAll(() { + schemaJson = json.decode(readJson(schemaPath)) as Map; + }); + + test('X-order of segments is kept in model class', () async { + final schemaDto = DocumentSchemaDto.fromJson(schemaJson); + + final schema = schemaDto.toModel(); + + if (schemaDto.order.length != schema.segments.length) { + return; + } + for (var i = 0; i < schema.segments.length; i++) { + expect(schema.segments[i].id, schemaDto.order[i]); + } + }); + + test('X-order of section is kept in model class', () { + final schemaDto = DocumentSchemaDto.fromJson(schemaJson); + final schema = schemaDto.toModel(); + + for (var i = 0; i < schema.segments.length; i++) { + if (schemaDto.segments[i].order.length != + schema.segments[i].sections.length) { + continue; + } + for (var j = 0; j < schema.segments[i].sections.length; j++) { + expect( + schema.segments[i].sections[j].id, + schemaDto.segments[i].order[j], + ); + } + } + }); + + test('Check if every segment has a SegmentDefinition as ref', () { + final schemaDto = DocumentSchemaDto.fromJson(schemaJson); + final schema = schemaDto.toModel(); + + for (final segment in schema.segments) { + expect(segment.ref, isA()); + } + }); + }); +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/catalyst_voices_shared.dart b/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/catalyst_voices_shared.dart index a2b7d04c7c3..884defba558 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/catalyst_voices_shared.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/catalyst_voices_shared.dart @@ -7,6 +7,9 @@ export 'crypto/crypto_service.dart'; export 'crypto/key_derivation.dart'; export 'crypto/local_crypto_service.dart'; export 'dependency/dependency_provider.dart'; +export 'document/extension/document_list_sort_ext.dart'; +export 'document/extension/document_map_to_list_ext.dart'; +export 'document/identifiable.dart'; export 'formatter/cryptocurrency_formatter.dart'; export 'formatter/wallet_address_formatter.dart'; export 'keychain/keychain.dart'; @@ -17,6 +20,7 @@ export 'keychain/vault_keychain_provider.dart'; export 'logging/logging_service.dart'; export 'platform/catalyst_platform.dart'; export 'platform_aware_builder/platform_aware_builder.dart'; +export 'range/range.dart'; export 'responsive/responsive_builder.dart'; export 'responsive/responsive_child.dart'; export 'responsive/responsive_padding.dart'; diff --git a/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/document/extension/document_list_sort_ext.dart b/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/document/extension/document_list_sort_ext.dart new file mode 100644 index 00000000000..7ff95db6d34 --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/document/extension/document_list_sort_ext.dart @@ -0,0 +1,13 @@ +import 'package:catalyst_voices_shared/catalyst_voices_shared.dart'; + +extension DocumentListSortExt on List { + void sortByOrder(List order) { + final orderMap = {for (var i = 0; i < order.length; i++) order[i]: i}; + + sort((a, b) { + final aIndex = orderMap[a.id] ?? double.maxFinite.toInt(); + final bIndex = orderMap[b.id] ?? double.maxFinite.toInt(); + return aIndex.compareTo(bIndex); + }); + } +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/document/extension/document_map_to_list_ext.dart b/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/document/extension/document_map_to_list_ext.dart new file mode 100644 index 00000000000..cab157eed80 --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/document/extension/document_map_to_list_ext.dart @@ -0,0 +1,28 @@ +extension DocumentMapToListExt on Map { + List> convertMapToListWithIds() { + final list = >[]; + + for (final entry in entries) { + if (entry.key == r'$schema') continue; + final value = entry.value as Map; + value['id'] = entry.key; + list.add(value); + } + + return list; + } + + List> convertMapToListWithIdsAndValues() { + final list = >[]; + + for (final entry in entries) { + if (entry.key == r'$schema') continue; + final value = {}; + value['id'] = entry.key; + value['value'] = entry.value; + list.add(value); + } + + return list; + } +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/document/identifiable.dart b/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/document/identifiable.dart new file mode 100644 index 00000000000..5d0eb779875 --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/document/identifiable.dart @@ -0,0 +1,3 @@ +abstract class Identifiable { + String get id; +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/range/range.dart b/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/range/range.dart new file mode 100644 index 00000000000..ad17adab392 --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/range/range.dart @@ -0,0 +1,18 @@ +import 'package:equatable/equatable.dart'; + +class Range extends Equatable { + final T min; + final T max; + + const Range({required this.min, required this.max}); + + static Range? optionalRangeOf({T? min, T? max}) { + if (min == null || max == null) { + return null; + } + return Range(min: min, max: max); + } + + @override + List get props => [min, max]; +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_shared/pubspec.yaml b/catalyst_voices/packages/internal/catalyst_voices_shared/pubspec.yaml index 66de050feea..56bdf864fa9 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_shared/pubspec.yaml +++ b/catalyst_voices/packages/internal/catalyst_voices_shared/pubspec.yaml @@ -15,6 +15,7 @@ dependencies: collection: ^1.18.0 convert: ^3.1.1 cryptography: ^2.7.0 + equatable: ^2.0.7 flutter: sdk: flutter flutter_secure_storage: ^9.2.2