coregen-cli 3.1.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- coregen.py +1585 -0
- coregen_cli-3.1.6.dist-info/METADATA +1211 -0
- coregen_cli-3.1.6.dist-info/RECORD +172 -0
- coregen_cli-3.1.6.dist-info/WHEEL +5 -0
- coregen_cli-3.1.6.dist-info/entry_points.txt +2 -0
- coregen_cli-3.1.6.dist-info/top_level.txt +2 -0
- generators/__init__.py +1 -0
- generators/assets/__init__.py +1 -0
- generators/assets/assets.py +30 -0
- generators/assets/main.py +4 -0
- generators/config/__init__.py +1 -0
- generators/config/analisy_options.py +42 -0
- generators/config/main.py +11 -0
- generators/config/pubspec.py +139 -0
- generators/cursor/__init__.py +3 -0
- generators/cursor/setup.py +87 -0
- generators/helpers/__init__.py +56 -0
- generators/helpers/component.py +1030 -0
- generators/helpers/config.py +255 -0
- generators/helpers/data_source.py +180 -0
- generators/helpers/domain.py +469 -0
- generators/helpers/feature.py +654 -0
- generators/helpers/navigation.py +274 -0
- generators/helpers/page.py +63 -0
- generators/helpers/project.py +33 -0
- generators/helpers/utils.py +338 -0
- generators/helpers/validation.py +364 -0
- generators/initializator.py +68 -0
- generators/main.py +81 -0
- generators/static/assets/logo.png +0 -0
- generators/static/assets/mock/auth_user.json +5 -0
- generators/static/assets/svgs/apple.svg +4 -0
- generators/static/assets/svgs/google.svg +2 -0
- generators/static/cursor/AGENTS.md.jinja +37 -0
- generators/static/cursor/agents/doc-writer.md +20 -0
- generators/static/cursor/agents/epic-orchestrator.md +28 -0
- generators/static/cursor/agents/feature-implementer.md +29 -0
- generators/static/cursor/agents/integration-wiring.md +30 -0
- generators/static/cursor/agents/layer-application.md +21 -0
- generators/static/cursor/agents/layer-guardian.md +21 -0
- generators/static/cursor/agents/layer-infrastructure.md +31 -0
- generators/static/cursor/agents/layer-model.md +24 -0
- generators/static/cursor/agents/layer-presentation.md +33 -0
- generators/static/cursor/docs/architecture/APIS_AND_INTEGRATION.md.jinja +42 -0
- generators/static/cursor/docs/architecture/CARAVAGGIO_COMPONENTS.md.jinja +317 -0
- generators/static/cursor/docs/architecture/DDD_LAYERS.md.jinja +32 -0
- generators/static/cursor/docs/architecture/FILE_TEMPLATES.md.jinja +76 -0
- generators/static/cursor/docs/architecture/MOCK_AND_REMOTE_DATA.md.jinja +87 -0
- generators/static/cursor/docs/architecture/REFERENCE_IMPLEMENTATIONS.md.jinja +48 -0
- generators/static/cursor/docs/architecture/WIDGETS_AND_CARAVAGGIO.md.jinja +76 -0
- generators/static/cursor/docs/epics/README.md +17 -0
- generators/static/cursor/lib/domain/AGENTS.md +23 -0
- generators/static/cursor/lib/features/AGENTS.md +23 -0
- generators/static/cursor/lib/widgets/AGENTS.md +21 -0
- generators/static/cursor/rules/apis-layer.mdc.jinja +21 -0
- generators/static/cursor/rules/application-layer.mdc.jinja +12 -0
- generators/static/cursor/rules/architecture-core.mdc.jinja +43 -0
- generators/static/cursor/rules/dart-conventions.mdc.jinja +14 -0
- generators/static/cursor/rules/domain-layer.mdc.jinja +24 -0
- generators/static/cursor/rules/login-context.mdc.jinja +16 -0
- generators/static/cursor/rules/presentation-layer.mdc.jinja +15 -0
- generators/static/cursor/rules/quality-gate.mdc.jinja +14 -0
- generators/static/cursor/rules/ui-caravaggio.mdc.jinja +22 -0
- generators/static/cursor/skills/caravaggio-ui/SKILL.md +138 -0
- generators/static/cursor/skills/epic-delivery/SKILL.md +74 -0
- generators/static/cursor/skills/epic-delivery/templates/epic-status.md +14 -0
- generators/static/cursor/skills/epic-delivery/templates/epic.md +36 -0
- generators/static/templates/apis/common/constants_template.jinja +48 -0
- generators/static/templates/apis/common/data_source_config_template.jinja +47 -0
- generators/static/templates/apis/core/api_injectable_module_template.jinja +14 -0
- generators/static/templates/apis/interceptors/api_logger_template.jinja +135 -0
- generators/static/templates/apis/interceptors/auth_interceptor_template.jinja +207 -0
- generators/static/templates/auth/application/auth_bloc_template.jinja +36 -0
- generators/static/templates/auth/application/auth_event_template.jinja +7 -0
- generators/static/templates/auth/application/auth_state_template.jinja +8 -0
- generators/static/templates/auth/infrastructure/auth_facade_module_template.jinja +16 -0
- generators/static/templates/auth/infrastructure/auth_facade_template.jinja +288 -0
- generators/static/templates/auth/infrastructure/mock_auth_facade_template.jinja +106 -0
- generators/static/templates/auth/model/auth_failure_template.jinja +11 -0
- generators/static/templates/auth/model/i_auth_facade_template.jinja +24 -0
- generators/static/templates/auth/model/user_template.jinja +34 -0
- generators/static/templates/auth/model/value_objects_template.jinja +54 -0
- generators/static/templates/auth/presentation/login_screen_template.jinja +24 -0
- generators/static/templates/auth/sign_in_form/application/sign_in_form_bloc_template.jinja +139 -0
- generators/static/templates/auth/sign_in_form/application/sign_in_form_event_template.jinja +15 -0
- generators/static/templates/auth/sign_in_form/application/sign_in_form_state_template.jinja +26 -0
- generators/static/templates/auth/sign_in_form/presentation/apple_form_template.jinja +16 -0
- generators/static/templates/auth/sign_in_form/presentation/email_password_form_template_INACTIVE.jinja +94 -0
- generators/static/templates/auth/sign_in_form/presentation/google_form_template.jinja +15 -0
- generators/static/templates/auth/sign_in_form/presentation/sign_in_form_template.jinja +381 -0
- generators/static/templates/component/component_bloc_template.jinja +49 -0
- generators/static/templates/component/component_event_template.jinja +7 -0
- generators/static/templates/component/component_form_bloc_template.jinja +56 -0
- generators/static/templates/component/component_form_event_template.jinja +7 -0
- generators/static/templates/component/component_form_state_template.jinja +18 -0
- generators/static/templates/component/component_form_widget_template.jinja +130 -0
- generators/static/templates/component/component_list_widget_template.jinja +95 -0
- generators/static/templates/component/component_state_template.jinja +9 -0
- generators/static/templates/component/component_widget_template.jinja +113 -0
- generators/static/templates/core/bloc/base_form_bloc_template.jinja +32 -0
- generators/static/templates/core/errors/error_localizer_template.jinja +61 -0
- generators/static/templates/core/infrastructure/base_mapper_template.jinja +21 -0
- generators/static/templates/core/infrastructure/base_repository_mixin_template.jinja +78 -0
- generators/static/templates/core/infrastructure/firebase_injectable_module_template.jinja +11 -0
- generators/static/templates/core/infrastructure/firestore_helpers_template.jinja +22 -0
- generators/static/templates/core/infrastructure/repository_error_handler_template.jinja +44 -0
- generators/static/templates/core/model/common_interfaces_template.jinja +3 -0
- generators/static/templates/core/model/entity_template.jinja +5 -0
- generators/static/templates/core/model/errors_template.jinja +15 -0
- generators/static/templates/core/model/failures_template.jinja +97 -0
- generators/static/templates/core/model/value_objects_template.jinja +135 -0
- generators/static/templates/core/model/value_validators_template.jinja +105 -0
- generators/static/templates/core/presentation/app_drawer_template.jinja +52 -0
- generators/static/templates/core/presentation/app_widget_template.jinja +41 -0
- generators/static/templates/core/presentation/bottom_nav_bar_template.jinja +32 -0
- generators/static/templates/domain/domain_mapper_template.jinja +39 -0
- generators/static/templates/domain/domain_remote_service_template.jinja +46 -0
- generators/static/templates/domain/domain_repository_template.jinja +90 -0
- generators/static/templates/domain/domain_service_module_template.jinja +23 -0
- generators/static/templates/domain/enum_template.jinja +3 -0
- generators/static/templates/domain/i_domain_service_template.jinja +18 -0
- generators/static/templates/domain/mock_domain_service_template.jinja +73 -0
- generators/static/templates/feature/feature_bloc_template.jinja +104 -0
- generators/static/templates/feature/feature_dto_template.jinja +26 -0
- generators/static/templates/feature/feature_entity_template.jinja +23 -0
- generators/static/templates/feature/feature_event_template.jinja +20 -0
- generators/static/templates/feature/feature_extensions_template.jinja +25 -0
- generators/static/templates/feature/feature_failure_template.jinja +11 -0
- generators/static/templates/feature/feature_page_template.jinja +113 -0
- generators/static/templates/feature/feature_repository_template.jinja +112 -0
- generators/static/templates/feature/feature_state_template.jinja +20 -0
- generators/static/templates/feature/i_feature_repository_template.jinja +21 -0
- generators/static/templates/feature/value_object_field_template.jinja +0 -0
- generators/static/templates/feature/value_object_template.jinja +0 -0
- generators/static/templates/feature/value_validators_template.jinja +57 -0
- generators/static/templates/home/home_screen_template.jinja +68 -0
- generators/static/templates/home/presentation/home_screen_template.jinja +52 -0
- generators/static/templates/home/screen_template.jinja +12 -0
- generators/static/templates/injection_template.jinja +8 -0
- generators/static/templates/logging/analytics_logging_template.jinja +42 -0
- generators/static/templates/logging/console_template.jinja +50 -0
- generators/static/templates/logging/logger_injectable_module_template.jinja +24 -0
- generators/static/templates/logging/logger_template.jinja +118 -0
- generators/static/templates/main_template.jinja +43 -0
- generators/static/templates/page_template.jinja +29 -0
- generators/static/templates/router_template.jinja +26 -0
- generators/static/templates/splash/presentation/splash_screen_auth_template.jinja +97 -0
- generators/static/templates/splash/presentation/splash_screen_template.jinja +51 -0
- generators/static/templates/storage/storage_repository_template.jinja +80 -0
- generators/static/templates/user_profile/infrastructure/user_profile_dto_template.jinja +27 -0
- generators/static/templates/user_profile/infrastructure/user_profile_mapper_template.jinja +43 -0
- generators/static/templates/user_profile/infrastructure/user_profile_repository_template.jinja +82 -0
- generators/static/templates/user_profile/infrastructure/user_profile_service_template.jinja +27 -0
- generators/static/templates/user_profile/model/i_user_profile_repository_template.jinja +34 -0
- generators/static/templates/user_profile/model/user_profile_failure_template.jinja +11 -0
- generators/static/templates/user_profile/model/user_profile_template.jinja +31 -0
- generators/static/templates/widgets/common/custom_scaffold_template.jinja +52 -0
- generators/static/templates/widgets/common/error_widget_template.jinja +22 -0
- generators/static/templates/widgets/common/loading_widget_template.jinja +14 -0
- generators/static/templates/widgets/common/unknown_state_widget_template.jinja +23 -0
- generators/templates/__init__.py +1 -0
- generators/templates/_core/core_generator.py +121 -0
- generators/templates/apis/apis_generator.py +50 -0
- generators/templates/auth/auth_generator.py +40 -0
- generators/templates/auth/sign_in_form_generator.py +22 -0
- generators/templates/copier.py +51 -0
- generators/templates/home/home_generator.py +26 -0
- generators/templates/lib/lib_generator.py +40 -0
- generators/templates/logging/logging_generator.py +28 -0
- generators/templates/main.py +75 -0
- generators/templates/splash/splash_generator.py +20 -0
- generators/templates/storage/storage_generator.py +5 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Core value objects for domain-driven design
|
|
3
|
+
* Provides base classes for type-safe value objects:
|
|
4
|
+
* - ValueObject<T>: Abstract base for all value objects
|
|
5
|
+
* - Concrete implementations for common types (strings, emails, etc.)
|
|
6
|
+
* - Validation integration with Either types
|
|
7
|
+
* - Immutability and equality built-in
|
|
8
|
+
*
|
|
9
|
+
* Value objects ensure data integrity and type safety
|
|
10
|
+
* throughout the domain layer by encapsulating validation logic
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import 'package:dartz/dartz.dart';
|
|
14
|
+
import 'package:meta/meta.dart';
|
|
15
|
+
import 'package:uuid/uuid.dart';
|
|
16
|
+
import 'package:[[project_name]]/core/model/common_interfaces.dart';
|
|
17
|
+
import 'package:[[project_name]]/core/model/errors.dart';
|
|
18
|
+
import 'package:[[project_name]]/core/model/failures.dart';
|
|
19
|
+
import 'package:[[project_name]]/core/model/value_validators.dart';
|
|
20
|
+
|
|
21
|
+
// Used for attributes within the domain. Each attribute is a ValueObject<T>
|
|
22
|
+
// Example:
|
|
23
|
+
//
|
|
24
|
+
// Note({required Description description})
|
|
25
|
+
//
|
|
26
|
+
// Description extends a ValueObject<String> and has a validator that ensures, for example, that the string is not empty
|
|
27
|
+
//
|
|
28
|
+
// NOTE: All domain ValueObjects extend ValueObject<T>
|
|
29
|
+
// Each domain ValueObject has a validator specified in core/value_validators.dart
|
|
30
|
+
// Validators return an Either<ValueFailure<T>, T> representing the result of the validation
|
|
31
|
+
// If validation fails, a ValueFailure<T> is returned with the reason for the failure
|
|
32
|
+
// If validation succeeds, the valid value of type T is returned
|
|
33
|
+
//
|
|
34
|
+
// The Failures defined in the domain folder (e.g. note_failure.dart) are instead used to represent specific errors
|
|
35
|
+
// that can occur during the execution of operations in the domain (e.g. error while saving a note)
|
|
36
|
+
// These Failures are used to handle errors at the application level and not for data validation
|
|
37
|
+
|
|
38
|
+
@immutable
|
|
39
|
+
abstract class ValueObject<T> implements IValidatable {
|
|
40
|
+
const ValueObject();
|
|
41
|
+
Either<ValueFailure<T>, T> get value;
|
|
42
|
+
|
|
43
|
+
/// Throws [UnexpectedValueError] containing the [ValueFailure]
|
|
44
|
+
T getOrCrash() {
|
|
45
|
+
// id = identity - same as writing (right) => right
|
|
46
|
+
return value.fold((ValueFailure<T> f) => throw UnexpectedValueError(f), id);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
T getOrElse(T dflt) {
|
|
50
|
+
return value.getOrElse(() => dflt);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/// Returns the [ValueFailure] if the value is invalid, otherwise returns [Unit]
|
|
54
|
+
///
|
|
55
|
+
/// Example:
|
|
56
|
+
///
|
|
57
|
+
/// extension NoteX on Note {
|
|
58
|
+
/// Option<ValueFailure<dynamic>> get failureOption {
|
|
59
|
+
/// return description.failureOrUnit
|
|
60
|
+
/// .andThen(color.failureOrUnit)
|
|
61
|
+
/// .andThen(todos.failureOrUnit)
|
|
62
|
+
/// .andThen(
|
|
63
|
+
/// todos
|
|
64
|
+
/// .getOrCrash()
|
|
65
|
+
/// .map((todoItem) => todoItem.failureOption)
|
|
66
|
+
/// .filter((o) => o.isSome())
|
|
67
|
+
/// .getOrElse(0, (_) => none())
|
|
68
|
+
/// .fold(() => right(unit), (f) => left(f)),
|
|
69
|
+
/// )
|
|
70
|
+
/// .fold((f) => some(f), (_) => none());
|
|
71
|
+
/// }
|
|
72
|
+
/// }
|
|
73
|
+
///
|
|
74
|
+
/// Usage in a Widget:
|
|
75
|
+
/// if (note.failureOption.isSome()) {
|
|
76
|
+
/// return ErrorNoteCard(note: note);
|
|
77
|
+
/// }
|
|
78
|
+
Either<ValueFailure<dynamic>, Unit> get failureOrUnit {
|
|
79
|
+
return value.fold(
|
|
80
|
+
(ValueFailure<T> l) => left(l),
|
|
81
|
+
(T r) => right(unit),
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@override
|
|
86
|
+
bool isValid() {
|
|
87
|
+
return value.isRight();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@override
|
|
91
|
+
bool operator ==(Object o) {
|
|
92
|
+
if (identical(this, o)) return true;
|
|
93
|
+
return o is ValueObject<T> && o.value == value;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@override
|
|
97
|
+
int get hashCode => value.hashCode;
|
|
98
|
+
|
|
99
|
+
@override
|
|
100
|
+
String toString() => 'Value([[value]])';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
class UniqueId extends ValueObject<String> {
|
|
104
|
+
@override
|
|
105
|
+
final Either<ValueFailure<String>, String> value;
|
|
106
|
+
|
|
107
|
+
// We cannot let a simple String be passed in. This would allow for possible non-unique IDs.
|
|
108
|
+
factory UniqueId() {
|
|
109
|
+
return UniqueId._(
|
|
110
|
+
right(const Uuid().v1()),
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/// Used with strings we trust are unique, such as database IDs.
|
|
115
|
+
factory UniqueId.fromUniqueString(String uniqueIdStr) {
|
|
116
|
+
return UniqueId._(
|
|
117
|
+
right(uniqueIdStr),
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const UniqueId._(this.value);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
class StringSingleLine extends ValueObject<String> {
|
|
125
|
+
@override
|
|
126
|
+
final Either<ValueFailure<String>, String> value;
|
|
127
|
+
|
|
128
|
+
factory StringSingleLine(String input) {
|
|
129
|
+
return StringSingleLine._(
|
|
130
|
+
validateSingleLine(input),
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const StringSingleLine._(this.value);
|
|
135
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import 'package:dartz/dartz.dart';
|
|
2
|
+
import 'package:[[project_name]]/core/model/failures.dart';
|
|
3
|
+
|
|
4
|
+
// Used to validate ValueObjects within the domain
|
|
5
|
+
// Example:
|
|
6
|
+
//
|
|
7
|
+
// Note({required Description description})
|
|
8
|
+
//
|
|
9
|
+
// Description extends a ValueObject<String> and has a validator that ensures, for example, that the string is not empty
|
|
10
|
+
//
|
|
11
|
+
//
|
|
12
|
+
// class Description extends ValueObject<String> {
|
|
13
|
+
// @override
|
|
14
|
+
// final Either<ValueFailure<String>, String> value;
|
|
15
|
+
// static const maxLength = 1000;
|
|
16
|
+
// factory Description(String input) {
|
|
17
|
+
// assert(input != null);
|
|
18
|
+
// return Description._(
|
|
19
|
+
// validateMaxStringLength(input, maxLength).flatMap(validateStringNotEmpty),
|
|
20
|
+
// );
|
|
21
|
+
// }
|
|
22
|
+
// const Description._(this.value);
|
|
23
|
+
// }
|
|
24
|
+
//
|
|
25
|
+
//
|
|
26
|
+
// NOTE: All domain ValueObjects extend ValueObject<T>
|
|
27
|
+
// Each domain ValueObject has a validator specified in core/value_validators.dart
|
|
28
|
+
// Validators return an Either<ValueFailure<T>, T> representing the result of the validation
|
|
29
|
+
// If validation fails, a ValueFailure<T> is returned with the reason for the failure
|
|
30
|
+
// If validation succeeds, the valid value of type T is returned
|
|
31
|
+
//
|
|
32
|
+
// The Failures defined in the domain folder (e.g. note_failure.dart) are instead used to represent specific errors
|
|
33
|
+
// that can occur during the execution of operations in the domain (e.g. error while saving a note)
|
|
34
|
+
// These Failures are used to handle errors at the application level and not for data validation
|
|
35
|
+
|
|
36
|
+
Either<ValueFailure<String>, String> validateMaxStringLength(
|
|
37
|
+
String input,
|
|
38
|
+
int maxLength,
|
|
39
|
+
) {
|
|
40
|
+
if (input.length <= maxLength) {
|
|
41
|
+
return right(input);
|
|
42
|
+
} else {
|
|
43
|
+
return left(ValueFailure<String>.exceedingLength(
|
|
44
|
+
failedValue: input,
|
|
45
|
+
max: maxLength,
|
|
46
|
+
));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
Either<ValueFailure<String>, String> validateStringNotEmpty(String input) {
|
|
51
|
+
if (input.isEmpty) {
|
|
52
|
+
return left(ValueFailure<String>.empty(failedValue: input));
|
|
53
|
+
} else {
|
|
54
|
+
return right(input);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
Either<ValueFailure<String>, String> validateSingleLine(String input) {
|
|
59
|
+
if (input.contains('\n')) {
|
|
60
|
+
return left(ValueFailure<String>.multiline(failedValue: input));
|
|
61
|
+
} else {
|
|
62
|
+
return right(input);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
Either<ValueFailure<List<T>>, List<T>> validateMaxListLength<T>(List<T> input, int maxLength) {
|
|
67
|
+
if (input.length <= maxLength) {
|
|
68
|
+
return right(input);
|
|
69
|
+
} else {
|
|
70
|
+
return left(ValueFailure<List<T>>.listTooLong(
|
|
71
|
+
failedValue: input,
|
|
72
|
+
max: maxLength,
|
|
73
|
+
));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
Either<ValueFailure<String>, String> validateEmailAddress(String input) {
|
|
78
|
+
// Maybe not the most robust way of email validation but it__STRING_3__*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+__STRING_4__";
|
|
79
|
+
const String emailRegex = r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?)*$";
|
|
80
|
+
if (RegExp(emailRegex).hasMatch(input)) {
|
|
81
|
+
return right(input);
|
|
82
|
+
} else {
|
|
83
|
+
return left(ValueFailure<String>.invalidEmail(failedValue: input));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
Either<ValueFailure<String>, String> validatePassword(String input) {
|
|
88
|
+
// You can also add some advanced password checks (uppercase/lowercase, at least 1 number, ...)
|
|
89
|
+
if (input.length >= 6) {
|
|
90
|
+
return right(input);
|
|
91
|
+
} else {
|
|
92
|
+
return left(ValueFailure<String>.shortPassword(failedValue: input));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
Either<ValueFailure<String>, String> validateConfirmPassword(
|
|
97
|
+
String password,
|
|
98
|
+
String confirmPassword,
|
|
99
|
+
) {
|
|
100
|
+
if (password == confirmPassword) {
|
|
101
|
+
return right(confirmPassword);
|
|
102
|
+
} else {
|
|
103
|
+
return left(ValueFailure<String>.passwordsDoNotMatch(failedValue: confirmPassword));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import 'package:caravaggio_ui/caravaggio_ui.dart';
|
|
2
|
+
import 'package:flutter/material.dart';
|
|
3
|
+
import 'package:go_router/go_router.dart';
|
|
4
|
+
import 'package:[[project_name]]/features/home/home_page.dart';
|
|
5
|
+
{% for drawer_item in drawer_items %}
|
|
6
|
+
import 'package:[[project_name]]/[[drawer_item.name]]/presentation/[[drawer_item.name]]_page.dart';
|
|
7
|
+
{% endfor %}
|
|
8
|
+
|
|
9
|
+
class AppDrawer extends StatelessWidget {
|
|
10
|
+
const AppDrawer({super.key});
|
|
11
|
+
|
|
12
|
+
@override
|
|
13
|
+
Widget build(BuildContext context) {
|
|
14
|
+
return Drawer(
|
|
15
|
+
child: ListView(
|
|
16
|
+
padding: EdgeInsets.zero,
|
|
17
|
+
children: <Widget>[
|
|
18
|
+
const DrawerHeader(
|
|
19
|
+
decoration: BoxDecoration(
|
|
20
|
+
color: Colors.blue,
|
|
21
|
+
),
|
|
22
|
+
child: Text(
|
|
23
|
+
'App Navigation',
|
|
24
|
+
style: TextStyle(
|
|
25
|
+
color: Colors.white,
|
|
26
|
+
fontSize: 24,
|
|
27
|
+
),
|
|
28
|
+
),
|
|
29
|
+
),
|
|
30
|
+
CTile.simple(
|
|
31
|
+
title: 'Home',
|
|
32
|
+
icon: Icons.home,
|
|
33
|
+
onTap: () {
|
|
34
|
+
Navigator.of(context).pop();
|
|
35
|
+
context.go(HomePage.routeName);
|
|
36
|
+
},
|
|
37
|
+
),
|
|
38
|
+
{% for drawer_item in drawer_items %}
|
|
39
|
+
CTile.simple(
|
|
40
|
+
title: '[[drawer_item.name|replace("_", " ")|title]]',
|
|
41
|
+
icon: Icons.star,
|
|
42
|
+
onTap: () {
|
|
43
|
+
Navigator.of(context).pop();
|
|
44
|
+
context.go([[drawer_item.name|title]]Page.routeName);
|
|
45
|
+
},
|
|
46
|
+
),
|
|
47
|
+
{% endfor %}
|
|
48
|
+
],
|
|
49
|
+
),
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
import 'package:caravaggio_ui/caravaggio_ui.dart';
|
|
3
|
+
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
4
|
+
import 'package:provider/single_child_widget.dart' show SingleChildWidget;
|
|
5
|
+
{% if has_login %}
|
|
6
|
+
import 'package:[[project_name]]/features/auth/application/auth_bloc.dart';
|
|
7
|
+
import 'package:[[project_name]]/injection.dart';
|
|
8
|
+
import 'package:[[project_name]]/domain/auth/model/i_auth_facade.dart';
|
|
9
|
+
{% endif %}
|
|
10
|
+
import 'package:[[project_name]]/router.dart';
|
|
11
|
+
|
|
12
|
+
class AppWidget extends StatelessWidget {
|
|
13
|
+
const AppWidget({super.key});
|
|
14
|
+
|
|
15
|
+
@override
|
|
16
|
+
Widget build(BuildContext context) {
|
|
17
|
+
return MultiBlocProvider(
|
|
18
|
+
providers: <SingleChildWidget>[
|
|
19
|
+
{% if has_login %}
|
|
20
|
+
BlocProvider<AuthBloc>(create: (BuildContext context) => AuthBloc(getIt<IAuthFacade>())..add(const AuthCheckRequested())),
|
|
21
|
+
{% endif %}
|
|
22
|
+
// Add blocs here if needed
|
|
23
|
+
],
|
|
24
|
+
child: const _App(),
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
class _App extends StatelessWidget {
|
|
30
|
+
const _App();
|
|
31
|
+
|
|
32
|
+
@override
|
|
33
|
+
Widget build(BuildContext context) {
|
|
34
|
+
return MaterialApp.router(
|
|
35
|
+
title: '[[project_name]]',
|
|
36
|
+
theme: CUI.themeData,
|
|
37
|
+
routerConfig: router,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
|
|
3
|
+
class BottomNavBar extends StatelessWidget {
|
|
4
|
+
final int currentIndex;
|
|
5
|
+
final Function(int) onTap;
|
|
6
|
+
|
|
7
|
+
const BottomNavBar({
|
|
8
|
+
super.key,
|
|
9
|
+
required this.currentIndex,
|
|
10
|
+
required this.onTap,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
@override
|
|
14
|
+
Widget build(BuildContext context) {
|
|
15
|
+
return BottomNavigationBar(
|
|
16
|
+
currentIndex: currentIndex,
|
|
17
|
+
onTap: onTap,
|
|
18
|
+
items: const [
|
|
19
|
+
BottomNavigationBarItem(
|
|
20
|
+
icon: Icon(Icons.home),
|
|
21
|
+
label: 'Home',
|
|
22
|
+
),
|
|
23
|
+
{% for nav_item in nav_items %}
|
|
24
|
+
BottomNavigationBarItem(
|
|
25
|
+
icon: Icon(Icons.star),
|
|
26
|
+
label: '[[nav_item.name|replace("_", " ")|title]]',
|
|
27
|
+
),
|
|
28
|
+
{% endfor %}
|
|
29
|
+
],
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Mapper template for DTO-Domain conversions
|
|
3
|
+
* Maps between DTO (API layer) and Domain Entity (business layer)
|
|
4
|
+
*
|
|
5
|
+
* This mapper ensures proper conversion between:
|
|
6
|
+
* - DTO (from/to API)
|
|
7
|
+
* - Domain Entity (business logic)
|
|
8
|
+
*
|
|
9
|
+
* Extends BaseMapper which provides default toDomainList/toDtoList implementations.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import 'package:injectable/injectable.dart';
|
|
13
|
+
import 'package:[[project_name]]/core/model/value_objects.dart';
|
|
14
|
+
import 'package:[[project_name]]/core/infrastructure/base_mapper.dart';
|
|
15
|
+
import 'package:[[project_name]]/[[import_prefix]]/model/[[file_name]].dart';
|
|
16
|
+
import 'package:[[project_name]]/[[import_prefix]]/model/value_objects.dart';
|
|
17
|
+
import 'package:[[project_name]]/[[import_prefix]]/infrastructure/[[file_name]]_dto.dart';
|
|
18
|
+
[[mapper_imports]]
|
|
19
|
+
|
|
20
|
+
@lazySingleton
|
|
21
|
+
class [[entity_name]]Mapper extends BaseMapper<[[entity_name]], [[entity_name]]Dto> {
|
|
22
|
+
[[mapper_fields]]
|
|
23
|
+
|
|
24
|
+
[[entity_name]]Mapper([[mapper_constructor_params]]);
|
|
25
|
+
|
|
26
|
+
@override
|
|
27
|
+
[[entity_name]] toDomain([[entity_name]]Dto dto) {
|
|
28
|
+
return [[entity_name]](
|
|
29
|
+
[[from_dto_fields]]
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@override
|
|
34
|
+
[[entity_name]]Dto toDto([[entity_name]] entity) {
|
|
35
|
+
return [[entity_name]]Dto(
|
|
36
|
+
[[to_dto_fields]]
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Remote [[entity_name]] service — Retrofit REST client.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import 'package:dio/dio.dart';
|
|
6
|
+
import 'package:injectable/injectable.dart';
|
|
7
|
+
import 'package:retrofit/retrofit.dart';
|
|
8
|
+
import 'package:[[project_name]]/apis/common/constants.dart';
|
|
9
|
+
import 'package:[[project_name]]/[[import_prefix]]/infrastructure/[[file_name]]_dto.dart';
|
|
10
|
+
import 'package:[[project_name]]/[[import_prefix]]/infrastructure/i_[[file_name]]_service.dart';
|
|
11
|
+
|
|
12
|
+
part '[[file_name]]_remote_service.g.dart';
|
|
13
|
+
|
|
14
|
+
@module
|
|
15
|
+
abstract class [[entity_name]]RemoteModule {
|
|
16
|
+
@lazySingleton
|
|
17
|
+
[[entity_name]]RemoteService [[entity_name_camel]]RemoteService(Dio dio) {
|
|
18
|
+
return [[entity_name]]RemoteService(dio, baseUrl: Constants.dioBaseUrl);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@RestApi()
|
|
23
|
+
abstract class [[entity_name]]RemoteService implements I[[entity_name]]Service {
|
|
24
|
+
@factoryMethod
|
|
25
|
+
factory [[entity_name]]RemoteService(Dio dio, {String baseUrl}) = _[[entity_name]]RemoteService;
|
|
26
|
+
|
|
27
|
+
@override
|
|
28
|
+
@GET('/[[kebab_name]]s')
|
|
29
|
+
Future<List<[[entity_name]]Dto>> getAll();
|
|
30
|
+
|
|
31
|
+
@override
|
|
32
|
+
@GET('/[[kebab_name]]s/{id}')
|
|
33
|
+
Future<[[entity_name]]Dto> getById(@Path('id') String id);
|
|
34
|
+
|
|
35
|
+
@override
|
|
36
|
+
@POST('/[[kebab_name]]s')
|
|
37
|
+
Future<[[entity_name]]Dto> create(@Body() [[entity_name]]Dto [[entity_name_camel]]Dto);
|
|
38
|
+
|
|
39
|
+
@override
|
|
40
|
+
@PUT('/[[kebab_name]]s/{id}')
|
|
41
|
+
Future<[[entity_name]]Dto> update(@Path('id') String id, @Body() [[entity_name]]Dto [[entity_name_camel]]Dto);
|
|
42
|
+
|
|
43
|
+
@override
|
|
44
|
+
@DELETE('/[[kebab_name]]s/{id}')
|
|
45
|
+
Future<void> delete(@Path('id') String id);
|
|
46
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Repository implementation template
|
|
3
|
+
* Provides concrete implementation of repository interface with:
|
|
4
|
+
* - BaseRepositoryMixin for centralized error handling
|
|
5
|
+
* - Service for API calls
|
|
6
|
+
* - Mapper for DTO-Domain conversions
|
|
7
|
+
* - Injectable dependency registration
|
|
8
|
+
*
|
|
9
|
+
* This repository acts as a bridge between the domain layer and the API layer
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import 'package:dartz/dartz.dart';
|
|
13
|
+
import 'package:injectable/injectable.dart';
|
|
14
|
+
import 'package:[[project_name]]/core/infrastructure/base_repository_mixin.dart';
|
|
15
|
+
import 'package:[[project_name]]/[[import_prefix]]/model/[[file_name]].dart';
|
|
16
|
+
import 'package:[[project_name]]/[[import_prefix]]/model/[[file_name]]_failure.dart';
|
|
17
|
+
import 'package:[[project_name]]/[[import_prefix]]/model/i_[[file_name]]_repository.dart';
|
|
18
|
+
import 'package:[[project_name]]/[[import_prefix]]/infrastructure/[[file_name]]_dto.dart';
|
|
19
|
+
import 'package:[[project_name]]/[[import_prefix]]/infrastructure/i_[[file_name]]_service.dart';
|
|
20
|
+
import 'package:[[project_name]]/[[import_prefix]]/infrastructure/[[file_name]]_mapper.dart';
|
|
21
|
+
|
|
22
|
+
@Injectable(as: I[[entity_name]]Repository)
|
|
23
|
+
class [[entity_name]]Repository with BaseRepositoryMixin<[[entity_name]]Failure> implements I[[entity_name]]Repository {
|
|
24
|
+
final I[[entity_name]]Service _service;
|
|
25
|
+
final [[entity_name]]Mapper _mapper;
|
|
26
|
+
|
|
27
|
+
[[entity_name]]Repository(
|
|
28
|
+
this._service,
|
|
29
|
+
this._mapper,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
@override
|
|
33
|
+
Future<Either<[[entity_name]]Failure, List<[[entity_name]]>>> getAll() {
|
|
34
|
+
return handleListCall(
|
|
35
|
+
serviceCall: () => _service.getAll(),
|
|
36
|
+
mapToDomainList: (List<[[entity_name]]Dto> dtos) => _mapper.toDomainList(dtos),
|
|
37
|
+
onUnexpected: () => const [[entity_name]]Failure.unexpected(),
|
|
38
|
+
onNotFound: () => const [[entity_name]]Failure.notFound(),
|
|
39
|
+
onInsufficientPermission: () => const [[entity_name]]Failure.insufficientPermission(),
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@override
|
|
44
|
+
Future<Either<[[entity_name]]Failure, [[entity_name]]>> getById(String id) {
|
|
45
|
+
return handleSimpleCall(
|
|
46
|
+
serviceCall: () => _service.getById(id),
|
|
47
|
+
mapToDomain: ([[entity_name]]Dto dto) => _mapper.toDomain(dto),
|
|
48
|
+
onUnexpected: () => const [[entity_name]]Failure.unexpected(),
|
|
49
|
+
onNotFound: () => const [[entity_name]]Failure.notFound(),
|
|
50
|
+
onInsufficientPermission: () => const [[entity_name]]Failure.insufficientPermission(),
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@override
|
|
55
|
+
Future<Either<[[entity_name]]Failure, Unit>> create([[entity_name]] item) {
|
|
56
|
+
return handleUnitCall(
|
|
57
|
+
serviceCall: () async {
|
|
58
|
+
final [[entity_name]]Dto dto = _mapper.toDto(item);
|
|
59
|
+
await _service.create(dto);
|
|
60
|
+
},
|
|
61
|
+
onUnexpected: () => const [[entity_name]]Failure.unexpected(),
|
|
62
|
+
onNotFound: () => const [[entity_name]]Failure.notFound(),
|
|
63
|
+
onInsufficientPermission: () => const [[entity_name]]Failure.insufficientPermission(),
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@override
|
|
68
|
+
Future<Either<[[entity_name]]Failure, Unit>> update([[entity_name]] item) {
|
|
69
|
+
return handleUnitCall(
|
|
70
|
+
serviceCall: () async {
|
|
71
|
+
final [[entity_name]]Dto dto = _mapper.toDto(item);
|
|
72
|
+
await _service.update(item.id.getOrCrash(), dto);
|
|
73
|
+
},
|
|
74
|
+
onUnexpected: () => const [[entity_name]]Failure.unexpected(),
|
|
75
|
+
onNotFound: () => const [[entity_name]]Failure.notFound(),
|
|
76
|
+
onInsufficientPermission: () => const [[entity_name]]Failure.insufficientPermission(),
|
|
77
|
+
onUnableToUpdate: () => const [[entity_name]]Failure.unableToUpdate(),
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@override
|
|
82
|
+
Future<Either<[[entity_name]]Failure, Unit>> delete(String id) {
|
|
83
|
+
return handleUnitCall(
|
|
84
|
+
serviceCall: () => _service.delete(id),
|
|
85
|
+
onUnexpected: () => const [[entity_name]]Failure.unexpected(),
|
|
86
|
+
onNotFound: () => const [[entity_name]]Failure.notFound(),
|
|
87
|
+
onInsufficientPermission: () => const [[entity_name]]Failure.insufficientPermission(),
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Binds I[[entity_name]]Service to mock or remote implementation per DataSourceConfig.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import 'package:injectable/injectable.dart';
|
|
6
|
+
import 'package:[[project_name]]/apis/common/data_source_config.dart';
|
|
7
|
+
import 'package:[[project_name]]/[[import_prefix]]/infrastructure/i_[[file_name]]_service.dart';
|
|
8
|
+
import 'package:[[project_name]]/[[import_prefix]]/infrastructure/[[file_name]]_remote_service.dart';
|
|
9
|
+
import 'package:[[project_name]]/[[import_prefix]]/infrastructure/mock_[[file_name]]_service.dart';
|
|
10
|
+
|
|
11
|
+
@module
|
|
12
|
+
abstract class [[entity_name]]ServiceModule {
|
|
13
|
+
@lazySingleton
|
|
14
|
+
I[[entity_name]]Service [[entity_name_camel]]Service(
|
|
15
|
+
[[entity_name]]RemoteService remote,
|
|
16
|
+
Mock[[entity_name]]Service mock,
|
|
17
|
+
) {
|
|
18
|
+
if (DataSourceConfig.useMock('[[file_name]]')) {
|
|
19
|
+
return mock;
|
|
20
|
+
}
|
|
21
|
+
return remote;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Service interface for [[entity_name]] data access.
|
|
3
|
+
* Implemented by mock (JSON) and remote (Retrofit) services.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import 'package:[[project_name]]/[[import_prefix]]/infrastructure/[[file_name]]_dto.dart';
|
|
7
|
+
|
|
8
|
+
abstract class I[[entity_name]]Service {
|
|
9
|
+
Future<List<[[entity_name]]Dto>> getAll();
|
|
10
|
+
|
|
11
|
+
Future<[[entity_name]]Dto> getById(String id);
|
|
12
|
+
|
|
13
|
+
Future<[[entity_name]]Dto> create([[entity_name]]Dto [[entity_name_camel]]Dto);
|
|
14
|
+
|
|
15
|
+
Future<[[entity_name]]Dto> update(String id, [[entity_name]]Dto [[entity_name_camel]]Dto);
|
|
16
|
+
|
|
17
|
+
Future<void> delete(String id);
|
|
18
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Mock [[entity_name]] service — loads seed data from assets/mock/[[file_name]].json.
|
|
3
|
+
* CRUD mutations are kept in memory for the current app session.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import 'dart:convert';
|
|
7
|
+
|
|
8
|
+
import 'package:flutter/services.dart';
|
|
9
|
+
import 'package:injectable/injectable.dart';
|
|
10
|
+
import 'package:[[project_name]]/[[import_prefix]]/infrastructure/[[file_name]]_dto.dart';
|
|
11
|
+
import 'package:[[project_name]]/[[import_prefix]]/infrastructure/i_[[file_name]]_service.dart';
|
|
12
|
+
|
|
13
|
+
@lazySingleton
|
|
14
|
+
class Mock[[entity_name]]Service implements I[[entity_name]]Service {
|
|
15
|
+
static const String _assetPath = 'assets/mock/[[file_name]].json';
|
|
16
|
+
|
|
17
|
+
List<[[entity_name]]Dto>? _items;
|
|
18
|
+
|
|
19
|
+
Future<List<[[entity_name]]Dto>> _loadItems() async {
|
|
20
|
+
if (_items != null) {
|
|
21
|
+
return _items!;
|
|
22
|
+
}
|
|
23
|
+
final String raw = await rootBundle.loadString(_assetPath);
|
|
24
|
+
final Map<String, dynamic> decoded = jsonDecode(raw) as Map<String, dynamic>;
|
|
25
|
+
final List<dynamic> list = decoded['items'] as List<dynamic>? ?? <dynamic>[];
|
|
26
|
+
_items = list
|
|
27
|
+
.map((dynamic e) => [[entity_name]]Dto.fromJson(Map<String, dynamic>.from(e as Map)))
|
|
28
|
+
.toList();
|
|
29
|
+
return _items!;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@override
|
|
33
|
+
Future<List<[[entity_name]]Dto>> getAll() => _loadItems();
|
|
34
|
+
|
|
35
|
+
@override
|
|
36
|
+
Future<[[entity_name]]Dto> getById(String id) async {
|
|
37
|
+
final List<[[entity_name]]Dto> items = await _loadItems();
|
|
38
|
+
for (final [[entity_name]]Dto item in items) {
|
|
39
|
+
if (item.id == id) {
|
|
40
|
+
return item;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
throw StateError('[[entity_name]] not found: $id');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@override
|
|
47
|
+
Future<[[entity_name]]Dto> create([[entity_name]]Dto [[entity_name_camel]]Dto) async {
|
|
48
|
+
final List<[[entity_name]]Dto> items = await _loadItems();
|
|
49
|
+
items.add([[entity_name_camel]]Dto);
|
|
50
|
+
return [[entity_name_camel]]Dto;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@override
|
|
54
|
+
Future<[[entity_name]]Dto> update(String id, [[entity_name]]Dto [[entity_name_camel]]Dto) async {
|
|
55
|
+
final List<[[entity_name]]Dto> items = await _loadItems();
|
|
56
|
+
final int index = items.indexWhere(([[entity_name]]Dto e) => e.id == id);
|
|
57
|
+
if (index < 0) {
|
|
58
|
+
throw StateError('[[entity_name]] not found: $id');
|
|
59
|
+
}
|
|
60
|
+
items[index] = [[entity_name_camel]]Dto;
|
|
61
|
+
return [[entity_name_camel]]Dto;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@override
|
|
65
|
+
Future<void> delete(String id) async {
|
|
66
|
+
final List<[[entity_name]]Dto> items = await _loadItems();
|
|
67
|
+
final int before = items.length;
|
|
68
|
+
items.removeWhere(([[entity_name]]Dto e) => e.id == id);
|
|
69
|
+
if (items.length == before) {
|
|
70
|
+
throw StateError('[[entity_name]] not found: $id');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|