diff --git a/CHANGELOG.md b/CHANGELOG.md index 32a052d6..242bd54d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 3.8.0 + +- Added `gql_builder` module (`GqlQuery`, `GqlMutation`, `GqlFragment`, `GqlField`, `GqlVariable`) for composable, type-safe GraphQL query construction. +- Moved `LayrzConnector` from `lib/src/utils/` into the `api/` module; still accessible via the top-level barrel. +- Added `LayrzConnector.perform(Gql)` replacing the raw-string `perform(query:, variables:)` method. +- Added `GqlVariableType.list` with `listOf` and `nestedRequired` for `[ID]!` and `[ID!]!` list types. +- Added `GqlVariableType.input` with `inputName` for GraphQL input object variables. +- Added `GqlField.args` for rendering field-level arguments (e.g. `charts(apiToken: $apiToken)`). +- Added `Avatar.gqlFragment` reusable fragment. +- Migrated all API callers (`LayrzChart`, `LayrzChartInput`, `Access`, `User`, `Locator`, `LocatorInput`, `MapLayer`, `MapLayerInput`, `Poi`, `PoiInput`, `Token`, `RegisteredApp`) to the `gql_builder`. +- Added `assets`, `assetsIds`, and `enableLttb` fields to `LayrzChart` and `LayrzChartInput`. +- Fragment collection is now automatic — the builder walks the field tree at generation time; no explicit `fragments:` list required. + ## 3.7.10 - Added `StockClosing` entity model diff --git a/lib/src/access/src/access.dart b/lib/src/access/src/access.dart index 5d0d711a..a877273e 100644 --- a/lib/src/access/src/access.dart +++ b/lib/src/access/src/access.dart @@ -20,29 +20,23 @@ abstract class Access with _$Access { factory Access.fromJson(Map json) => _$AccessFromJson(json); - /// [graphqlIdFragment] GraphQL fragment for Access - static const String graphqlIdFragment = ''' - fragment accessFragment on AccessPermission { - id - read - write - manage - objectId - userId - module - } - '''; + /// [graphqlIdFragment] GqlFragment for Access using integer ID + static GqlFragment get graphqlIdFragment => GqlFragment(name: 'accessFragment', onType: 'AccessPermission') + ..add(GqlField(name: 'id')) + ..add(GqlField(name: 'read')) + ..add(GqlField(name: 'write')) + ..add(GqlField(name: 'manage')) + ..add(GqlField(name: 'objectId')) + ..add(GqlField(name: 'userId')) + ..add(GqlField(name: 'module')); - /// [graphqlUuidFragment] GraphQL fragment for Access using UUID - static const String graphqlUuidFragment = ''' - fragment accessUuidFragment on AccessPermissionUuid { - id - read - write - manage - objectId - userId - module - } - '''; + /// [graphqlUuidFragment] GqlFragment for Access using UUID + static GqlFragment get graphqlUuidFragment => GqlFragment(name: 'accessUuidFragment', onType: 'AccessPermissionUuid') + ..add(GqlField(name: 'id')) + ..add(GqlField(name: 'read')) + ..add(GqlField(name: 'write')) + ..add(GqlField(name: 'manage')) + ..add(GqlField(name: 'objectId')) + ..add(GqlField(name: 'userId')) + ..add(GqlField(name: 'module')); } diff --git a/lib/src/access/src/access_input.dart b/lib/src/access/src/access_input.dart index 1a19db51..85231055 100644 --- a/lib/src/access/src/access_input.dart +++ b/lib/src/access/src/access_input.dart @@ -45,12 +45,24 @@ abstract class AccessInput with _$AccessInput { bool useUuid = false, }) async { final connector = LayrzConnector(uri: uri); + final isNew = id == null; + final opName = isNew + ? (useUuid ? 'addAccessPermissionUuid' : 'addAccessPermission') + : (useUuid ? 'editAccessPermissionUuid' : 'editAccessPermission'); + final inputName = useUuid ? 'AccessPermissionUuidInput' : 'AccessPermissionInput'; try { final response = await connector.perform( - query: id == null - ? (useUuid ? AccessInput.addUuidGraphqlMutation : AccessInput.addIdGraphqlMutation) - : (useUuid ? AccessInput.editUuidGraphqlMutation : AccessInput.editIdGraphqlMutation), - variables: {'apiToken': apiToken, 'data': toJson()}, + GqlMutation( + variables: [ + GqlVariable(name: 'apiToken', type: .string, req: true, value: apiToken), + GqlVariable(name: 'data', type: .input, req: true, inputName: inputName, value: toJson()), + ], + name: opName, + )..add( + GqlField(name: opName, args: {'apiToken': 'apiToken', 'data': 'data'}) + ..add(GqlField(name: 'status')) + ..add(GqlField(name: 'errors')), + ), ); final data = response.data; @@ -60,9 +72,7 @@ abstract class AccessInput with _$AccessInput { return false; } - final result = id == null - ? (useUuid ? data['data']['addAccessPermissionUuid'] : data['data']['addAccessPermission']) - : (useUuid ? data['data']['editAccessPermissionUuid'] : data['data']['editAccessPermission']); + final result = data['data'][opName]; if (result == null) { onResponse?.call(ApiStatus.internalError.toJson()); Log.error("layrz_models/AccessInput/save(): No result from server"); @@ -98,11 +108,22 @@ abstract class AccessInput with _$AccessInput { bool useUuid = false, }) async { final connector = LayrzConnector(uri: uri); + final opName = useUuid ? 'deleteAccessPermissionUuid' : 'deleteAccessPermission'; + final inputName = useUuid ? 'AccessPermissionUuidInput' : 'AccessPermissionInput'; try { final response = await connector.perform( - query: useUuid ? AccessInput.deleteUuidGraphqlMutation : AccessInput.deleteIdGraphqlMutation, - variables: {'apiToken': apiToken, 'data': toJson()}, + GqlMutation( + variables: [ + GqlVariable(name: 'apiToken', type: .string, req: true, value: apiToken), + GqlVariable(name: 'data', type: .input, req: true, inputName: inputName, value: toJson()), + ], + name: opName, + )..add( + GqlField(name: opName, args: {'apiToken': 'apiToken', 'data': 'data'}) + ..add(GqlField(name: 'status')) + ..add(GqlField(name: 'errors')), + ), ); final data = response.data; @@ -112,7 +133,7 @@ abstract class AccessInput with _$AccessInput { return false; } - final result = data['data'][useUuid ? 'deleteAccessPermissionUuid' : 'deleteAccessPermission']; + final result = data['data'][opName]; if (result == null) { onResponse?.call(ApiStatus.internalError.toJson()); Log.error("layrz_models/Access/delete(): No result from server"); @@ -131,64 +152,4 @@ abstract class AccessInput with _$AccessInput { return false; } } - - /// [addIdGraphqlMutation] GraphQL mutation for adding an access permission - static String get addIdGraphqlMutation => r''' - mutation($apiToken: String!, $data: AccessPermissionInput!) { - addAccessPermission(apiToken: $apiToken, data: $data) { - status - errors - } - } - '''; - - /// [addUuidGraphqlMutation] GraphQL mutation for adding an access permission - static String get addUuidGraphqlMutation => r''' - mutation($apiToken: String!, $data: AccessPermissionUuidInput!) { - addAccessPermissionUuid(apiToken: $apiToken, data: $data) { - status - errors - } - } - '''; - - /// [editIdGraphqlMutation] GraphQL mutation for updating an access permission - static String get editIdGraphqlMutation => r''' - mutation($apiToken: String!, $data: AccessPermissionInput!) { - editAccessPermission(apiToken: $apiToken, data: $data) { - status - errors - } - } - '''; - - /// [editUuidGraphqlMutation] GraphQL mutation for updating an access permission - static String get editUuidGraphqlMutation => r''' - mutation($apiToken: String!, $data: AccessPermissionUuidInput!) { - editAccessPermissionUuid(apiToken: $apiToken, data: $data) { - status - errors - } - } - '''; - - /// [deleteIdGraphqlMutation] GraphQL mutation for deleting an access permission - static String get deleteIdGraphqlMutation => r''' - mutation($apiToken: String!, $data: AccessPermissionInput!) { - deleteAccessPermission(apiToken: $apiToken, data: $data) { - status - errors - } - } - '''; - - /// [deleteUuidGraphqlMutation] GraphQL mutation for deleting an access permission - static String get deleteUuidGraphqlMutation => r''' - mutation($apiToken: String!, $data: AccessPermissionUuidInput!) { - deleteAccessPermissionUuid(apiToken: $apiToken, data: $data) { - status - errors - } - } - '''; } diff --git a/lib/src/api/api.dart b/lib/src/api/api.dart index 651bbc66..b457bcb6 100644 --- a/lib/src/api/api.dart +++ b/lib/src/api/api.dart @@ -1,9 +1,18 @@ library; +import 'dart:convert'; + +import 'package:dio/dio.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'api.freezed.dart'; part 'api.g.dart'; +part 'src/api_connector.dart'; part 'src/response.dart'; part 'src/status.dart'; + +part 'src/gql_builder/variables.dart'; +part 'src/gql_builder/fragment.dart'; +part 'src/gql_builder/field.dart'; +part 'src/gql_builder/gql.dart'; diff --git a/lib/src/utils/src/api_connector.dart b/lib/src/api/src/api_connector.dart similarity index 68% rename from lib/src/utils/src/api_connector.dart rename to lib/src/api/src/api_connector.dart index 248b4f5a..bde60285 100644 --- a/lib/src/utils/src/api_connector.dart +++ b/lib/src/api/src/api_connector.dart @@ -1,6 +1,4 @@ -import 'dart:convert'; - -import 'package:dio/dio.dart'; +part of '../api.dart'; class LayrzConnector { final Uri uri; @@ -40,13 +38,18 @@ class LayrzConnector { late final Dio _dio; - Future perform({ - required String query, - required Map variables, - }) => - _dio.post('', data: { - 'query': query, - 'variables': variables, - 'operationName': null, - }); + /// [perform] executes a [Gql] object built with the gql_builder. + /// Each [GqlVariable] with a non-null `value` is included in the variables map; + /// variables without a value are omitted from the wire payload. + Future perform(Gql gql) { + final variables = { + for (final v in gql.variables) + if (v.value != null) v.name: v.value, + }; + return _dio.post('', data: { + 'query': gql.generated, + 'variables': variables, + 'operationName': null, + }); + } } diff --git a/lib/src/api/src/gql_builder/field.dart b/lib/src/api/src/gql_builder/field.dart new file mode 100644 index 00000000..8f97af93 --- /dev/null +++ b/lib/src/api/src/gql_builder/field.dart @@ -0,0 +1,39 @@ +part of '../../api.dart'; + +class GqlField { + /// [name] is the GraphQL field name, e.g. `charts`, `addChart`, `id`, `name`. + final String name; + + /// [alias] is the GraphQL field alias, e.g. `myCharts: charts`. Optional. + final String? alias; + + /// [fields] are the child fields to query on this field, e.g. for `charts { id name }` + /// the `charts` field has two child fields `id` and `name`. + late List fields; + + /// [parser] is an optional function to parse the raw JSON value of this field into a Dart type. + final T? Function(Object?)? parser; + + /// [fragment] is an optional fragment to spread on this field, e.g. `...ChartFragment`. + final GqlFragment? fragment; + + /// Key is the GraphQL argument name, value is the variable name (without `$`). + /// Example: `{'apiToken': 'apiToken', 'id': 'id'}` renders as `(apiToken: $apiToken, id: $id)`. + final Map args; + + /// [GqlField] represents a field in a GraphQL query or mutation, with + /// optional child fields, arguments, and fragments. + GqlField({ + required this.name, + this.alias, + this.parser, + List? fields, + this.fragment, + Map? args, + }) : args = args ?? const {} { + this.fields = List.from(fields ?? [], growable: true); + } + + /// Adds a child field to this field, e.g. adding `id` and `name` to `charts` in `charts { id name }`. + void add(GqlField field) => fields.add(field); +} diff --git a/lib/src/api/src/gql_builder/fragment.dart b/lib/src/api/src/gql_builder/fragment.dart new file mode 100644 index 00000000..60c5f090 --- /dev/null +++ b/lib/src/api/src/gql_builder/fragment.dart @@ -0,0 +1,35 @@ +part of '../../api.dart'; + +class GqlFragment { + /// [name] is the name of the fragment, e.g. `ChartFragment`. + final String name; + + /// [onType] is the GraphQL type on which this fragment is defined, e.g. `Chart`. + final String onType; + + /// [fields] are the fields included in this fragment. + late List fields; + + /// [GqlFragment] represents a GraphQL fragment, which is a reusable selection of fields on a specific type. + /// Example usage: `fragment ChartFragment on Chart { id name type }` defines a + /// fragment named `ChartFragment` on the `Chart` type, which includes the fields `id`, `name`, and `type`. + GqlFragment({ + required this.name, + required this.onType, + List? fields, + }) { + this.fields = List.from(fields ?? [], growable: true); + } + + /// Adds a field to this fragment, e.g. adding `id` to `ChartFragment` in `fragment ChartFragment on Chart { id }`. + void add(GqlField field) => fields.add(field); + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + return other is GqlFragment && other.name == name && other.onType == onType; + } + + @override + int get hashCode => name.hashCode ^ onType.hashCode; +} diff --git a/lib/src/api/src/gql_builder/gql.dart b/lib/src/api/src/gql_builder/gql.dart new file mode 100644 index 00000000..8fa7ff69 --- /dev/null +++ b/lib/src/api/src/gql_builder/gql.dart @@ -0,0 +1,161 @@ +part of '../../api.dart'; + +abstract class Gql { + /// [name] is an optional name for the query or mutation, useful for debugging and server-side logging. + final String? name; + + /// [variables] are the variables declared for this query or mutation, e.g. `($apiToken: String!, $id: ID)`. + final List variables; + + /// [fields] are the top-level fields to query or mutate, e.g. `charts { id name }` + /// or `addChart(data: $data) { id name }`. + late List fields; + + /// [includeTypename] indicates whether to automatically include `__typename` in every selection set. + final bool includeTypename; + + /// [Gql] represents a GraphQL query or mutation, with support for variables, nested fields, and fragments. + Gql({ + this.name, + required this.variables, + List? fields, + this.includeTypename = false, + }) { + this.fields = List.from(fields ?? [], growable: true); + } + + /// [generated] returns the GraphQL query or mutation string generated from this [Gql] object, + /// including variable declarations, field selections, and fragment definitions. + String get generated { + final buffer = StringBuffer(); + + // Collect all fragments referenced anywhere in the field tree, in encounter order, + // deduped by name so each fragment block is only rendered once. + final fragments = _collectFragments(fields); + for (final fragment in fragments) { + buffer.write('fragment ${fragment.name} on ${fragment.onType} {\n'); + if (includeTypename) buffer.write(' __typename\n'); + for (final field in fragment.fields) { + buffer.write('${_writeField(field)}\n'); + } + buffer.write('}\n\n'); + } + + buffer.write(this is GqlMutation ? 'mutation' : 'query'); + + if (name != null) { + buffer.write(' $name'); + } + + if (variables.isNotEmpty) { + buffer.write('('); + buffer.write(variables.map((v) => '\$${v.name}: ${_renderType(v)}').join(', ')); + buffer.write(') {\n'); + } + + for (final field in fields) { + buffer.write('${_writeField(field)}\n'); + } + buffer.write('}\n'); + + return buffer.toString(); + } + + /// [add] adds a top-level field to this query or mutation. + void add(GqlField field) => fields.add(field); + + /// Recursively walks [fields] and collects every [GqlFragment] referenced, deduped by name. + List _collectFragments(List fields) { + final seen = {}; + final result = []; + + void visit(List fs) { + for (final f in fs) { + if (f.fragment != null && seen.add(f.fragment!.name)) { + result.add(f.fragment!); + // Also collect any fragments referenced inside the fragment's own fields. + visit(f.fragment!.fields); + } + visit(f.fields); + } + } + + visit(fields); + return result; + } + + String _renderType(GqlVariable v) { + String base; + switch (v.type) { + case GqlVariableType.string: + base = 'String'; + case GqlVariableType.int: + base = 'Int'; + case GqlVariableType.float: + base = 'Float'; + case GqlVariableType.boolean: + base = 'Boolean'; + case GqlVariableType.id: + base = 'ID'; + case GqlVariableType.json: + base = 'Json'; + case GqlVariableType.uuid: + base = 'Uuid'; + case GqlVariableType.input: + base = v.inputName!; + case GqlVariableType.list: + final inner = _renderType( + GqlVariable( + name: v.name, + type: v.listOf!, + req: v.nestedRequired, + inputName: v.inputName, + ), + ); + base = '[$inner]'; + } + return v.req ? '$base!' : base; + } + + String _writeField(GqlField field, {int depth = 0}) { + final buffer = StringBuffer(); + final indent = ' ' * (depth + 1); + + if (field.alias != null) { + buffer.write('$indent${field.alias}: ${field.name}'); + } else { + buffer.write('$indent${field.name}'); + } + + if (field.args.isNotEmpty) { + final argStr = field.args.entries.map((e) => '${e.key}: \$${e.value}').join(', '); + buffer.write('($argStr)'); + } + + if (field.fields.isNotEmpty) { + buffer.write(' {\n'); + if (includeTypename) buffer.write('$indent __typename\n'); + for (final subField in field.fields) { + buffer.write('${_writeField(subField, depth: depth + 1)}\n'); + } + buffer.write('$indent}'); + } else if (field.fragment != null) { + buffer.write(' {\n'); + if (includeTypename) buffer.write('$indent __typename\n'); + buffer.write('$indent ...${field.fragment!.name}\n'); + buffer.write('$indent}'); + } + + return buffer.toString(); + } +} + +/// [GqlQuery] represents a GraphQL query operation, which is used to fetch data from the server. +class GqlQuery extends Gql { + GqlQuery({super.name, required super.variables, super.fields, super.includeTypename}); +} + +/// [GqlMutation] represents a GraphQL mutation operation, which is used to modify data on the server. +class GqlMutation extends Gql { + GqlMutation({super.name, required super.variables, super.fields, super.includeTypename}); +} diff --git a/lib/src/api/src/gql_builder/variables.dart b/lib/src/api/src/gql_builder/variables.dart new file mode 100644 index 00000000..a511c9d8 --- /dev/null +++ b/lib/src/api/src/gql_builder/variables.dart @@ -0,0 +1,70 @@ +part of '../../api.dart'; + +enum GqlVariableType { + string, + int, + float, + boolean, + id, + json, + uuid, + list, + input, +} + +class GqlVariable { + /// [name] is the variable name without the leading `$`, e.g. `apiToken`, `id`, `data`. + final String name; + + /// [type] is the variable type, e.g. string, int, list, input, etc. + final GqlVariableType type; + + /// `!` on the outer type — e.g. `ID!`, `[ID]!`, `[ID!]!`. + final bool req; + + /// `!` on the list element type. Legacy API uses `[ID]!` (nestedRequired: false), + /// modern uses `[ID!]!` (nestedRequired: true). Ignored unless type == list. + final bool nestedRequired; + + /// For type == list: the element scalar type. Cannot itself be list. + final GqlVariableType? listOf; + + /// For type == input: the GraphQL input type name, e.g. 'ChartInput', 'AccessPermissionUuidInput'. + final String? inputName; + + /// Runtime value serialized into the request's variables map. + /// null means the variable is declared but omitted from the wire payload. + final Object? value; + + const GqlVariable({ + required this.name, + required this.type, + this.req = false, + this.nestedRequired = false, + this.listOf, + this.inputName, + this.value, + }) : assert( + type != GqlVariableType.list || listOf != null, + 'GqlVariable.listOf is required when type == list', + ), + assert( + type != GqlVariableType.list || listOf != GqlVariableType.list, + 'Nested lists are not supported', + ), + assert( + type != GqlVariableType.input || inputName != null, + 'GqlVariable.inputName is required when type == input', + ); + + /// Returns a copy with a runtime [value] attached. + GqlVariable withValue(Object? value) => GqlVariable( + name: name, + type: type, + req: req, + nestedRequired: nestedRequired, + listOf: listOf, + inputName: inputName, + value: value, + ); +} diff --git a/lib/src/app/src/registered_app.dart b/lib/src/app/src/registered_app.dart index c6e8fa36..acb8aadd 100644 --- a/lib/src/app/src/registered_app.dart +++ b/lib/src/app/src/registered_app.dart @@ -62,7 +62,18 @@ abstract class RegisteredApp with _$RegisteredApp { }) async { final connector = LayrzConnector(uri: uri); try { - final response = await connector.perform(query: fetchAllGraphqlQuery, variables: {'apiToken': apiToken}); + final response = await connector.perform( + GqlQuery( + variables: [ + GqlVariable(name: 'apiToken', type: .string, req: true, value: apiToken), + ], + name: 'fetchRegisteredApps', + )..add( + GqlField(name: 'registeredApps', args: {'apiToken': 'apiToken'}) + ..add(GqlField(name: 'status')) + ..add(GqlField(name: 'result', fragment: gqlFragment)), + ), + ); final data = response.data; if (data == null) { @@ -94,55 +105,36 @@ abstract class RegisteredApp with _$RegisteredApp { } } - /// [fetchAllGraphqlQuery] is the GraphQL query to fetch all the registered apps. - /// This query requires the `apiToken` parameter. - static String get fetchAllGraphqlQuery => - '$registeredAppFragment' - r''' - query($apiToken: String!) { - registeredApps(apiToken: $apiToken) { - status - result { - ...registeredAppFragment - } - } - } - '''; - - /// [registeredAppFragment] is the GraphQL fragment to fetch a registered app. - static String get registeredAppFragment => r''' - fragment registeredAppFragment on RegisteredApp { - id - name - nickname - isCustomized - technology - sourceId - - instances { - id - appId - platform - host - status - } - - designInformation { - theme - mainColor - - favicons { - normal - white - } - - logos { - normal - white - } - - appicon - } - } - '''; + /// [gqlFragment] is the GqlFragment for a registered app. + static GqlFragment get gqlFragment => GqlFragment(name: 'registeredAppFragment', onType: 'RegisteredApp') + ..add(GqlField(name: 'id')) + ..add(GqlField(name: 'name')) + ..add(GqlField(name: 'nickname')) + ..add(GqlField(name: 'isCustomized')) + ..add(GqlField(name: 'technology')) + ..add(GqlField(name: 'sourceId')) + ..add( + GqlField(name: 'instances') + ..add(GqlField(name: 'id')) + ..add(GqlField(name: 'appId')) + ..add(GqlField(name: 'platform')) + ..add(GqlField(name: 'host')) + ..add(GqlField(name: 'status')), + ) + ..add( + GqlField(name: 'designInformation') + ..add(GqlField(name: 'theme')) + ..add(GqlField(name: 'mainColor')) + ..add( + GqlField(name: 'favicons') + ..add(GqlField(name: 'normal')) + ..add(GqlField(name: 'white')), + ) + ..add( + GqlField(name: 'logos') + ..add(GqlField(name: 'normal')) + ..add(GqlField(name: 'white')), + ) + ..add(GqlField(name: 'appicon')), + ); } diff --git a/lib/src/avatar/avatar.dart b/lib/src/avatar/avatar.dart index 99591165..21cf2b5b 100644 --- a/lib/src/avatar/avatar.dart +++ b/lib/src/avatar/avatar.dart @@ -3,6 +3,7 @@ library; import 'package:collection/collection.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:layrz_icons/layrz_icons.dart'; +import 'package:layrz_models/src/api/api.dart'; import 'package:layrz_models/src/converters/converters.dart'; part 'avatar.freezed.dart'; diff --git a/lib/src/avatar/src/avatar.dart b/lib/src/avatar/src/avatar.dart index c981b021..dd122038 100644 --- a/lib/src/avatar/src/avatar.dart +++ b/lib/src/avatar/src/avatar.dart @@ -28,4 +28,12 @@ abstract class Avatar with _$Avatar { }) = _Avatar; factory Avatar.fromJson(Map json) => _$AvatarFromJson(json); + + /// [gqlFragment] is the GqlFragment for an avatar. + static GqlFragment get gqlFragment => GqlFragment(name: 'avatarFragment', onType: 'Avatar') + ..add(GqlField(name: 'type')) + ..add(GqlField(name: 'url')) + ..add(GqlField(name: 'icon')) + ..add(GqlField(name: 'emoji')) + ..add(GqlField(name: 'base64')); } diff --git a/lib/src/charts/charts.dart b/lib/src/charts/charts.dart index 9308aacd..96c74c1b 100644 --- a/lib/src/charts/charts.dart +++ b/lib/src/charts/charts.dart @@ -3,12 +3,14 @@ library; import 'package:flutter/material.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:collection/collection.dart'; +import 'package:layrz_logging/layrz_logging.dart'; import 'package:layrz_models/layrz_models.dart'; part 'charts.freezed.dart'; part 'charts.g.dart'; part 'src/chart.dart'; +part 'src/chart_input.dart'; part 'src/axis_config.dart'; part 'src/chart_data_serie_type.dart'; part 'src/chart_algorithm.dart'; diff --git a/lib/src/charts/charts.freezed.dart b/lib/src/charts/charts.freezed.dart index 2c0a3489..6f977b42 100644 --- a/lib/src/charts/charts.freezed.dart +++ b/lib/src/charts/charts.freezed.dart @@ -21,10 +21,13 @@ mixin _$LayrzChart { String? get description;/// [formula] is the formula used to calculate the chart. This property is a LCL formula. String? get formula;/// [script] is the script used to calculate the chart. This property is a Python script. String? get script;/// [sensors] is a list of sensors used to calculate the chart. - List? get sensors;/// [type] is the type of the chart. + List? get sensors;/// [assets] is the list of assets associated with the chart. + List? get assets;/// [assetsIds] is the list of asset IDs associated with the chart. + List? get assetsIds;/// [type] is the type of the chart. @JsonKey(unknownEnumValue: ChartType.area) ChartType? get type;/// [algorithm] is the algorithm used to calculate the chart. @JsonKey(unknownEnumValue: ChartAlgorithm.auto) ChartAlgorithm? get algorithm;/// [dataSource] is the data source used to calculate the chart. -@JsonKey(unknownEnumValue: ChartDataSource.messages) ChartDataSource? get dataSource;/// [access] is a list of granted access to this entity. +@JsonKey(unknownEnumValue: ChartDataSource.messages) ChartDataSource? get dataSource;/// [enableLttb] indicates whether the LTTB downsampling algorithm is enabled for this chart. + bool? get enableLttb;/// [access] is a list of granted access to this entity. List? get access; /// Create a copy of LayrzChart /// with the given fields replaced by the non-null parameter values. @@ -38,16 +41,16 @@ $LayrzChartCopyWith get copyWith => _$LayrzChartCopyWithImpl Object.hash(runtimeType,id,name,description,formula,script,const DeepCollectionEquality().hash(sensors),type,algorithm,dataSource,const DeepCollectionEquality().hash(access)); +int get hashCode => Object.hash(runtimeType,id,name,description,formula,script,const DeepCollectionEquality().hash(sensors),const DeepCollectionEquality().hash(assets),const DeepCollectionEquality().hash(assetsIds),type,algorithm,dataSource,enableLttb,const DeepCollectionEquality().hash(access)); @override String toString() { - return 'LayrzChart(id: $id, name: $name, description: $description, formula: $formula, script: $script, sensors: $sensors, type: $type, algorithm: $algorithm, dataSource: $dataSource, access: $access)'; + return 'LayrzChart(id: $id, name: $name, description: $description, formula: $formula, script: $script, sensors: $sensors, assets: $assets, assetsIds: $assetsIds, type: $type, algorithm: $algorithm, dataSource: $dataSource, enableLttb: $enableLttb, access: $access)'; } @@ -58,7 +61,7 @@ abstract mixin class $LayrzChartCopyWith<$Res> { factory $LayrzChartCopyWith(LayrzChart value, $Res Function(LayrzChart) _then) = _$LayrzChartCopyWithImpl; @useResult $Res call({ - String id, String name, String? description, String? formula, String? script, List? sensors,@JsonKey(unknownEnumValue: ChartType.area) ChartType? type,@JsonKey(unknownEnumValue: ChartAlgorithm.auto) ChartAlgorithm? algorithm,@JsonKey(unknownEnumValue: ChartDataSource.messages) ChartDataSource? dataSource, List? access + String id, String name, String? description, String? formula, String? script, List? sensors, List? assets, List? assetsIds,@JsonKey(unknownEnumValue: ChartType.area) ChartType? type,@JsonKey(unknownEnumValue: ChartAlgorithm.auto) ChartAlgorithm? algorithm,@JsonKey(unknownEnumValue: ChartDataSource.messages) ChartDataSource? dataSource, bool? enableLttb, List? access }); @@ -75,7 +78,7 @@ class _$LayrzChartCopyWithImpl<$Res> /// Create a copy of LayrzChart /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? description = freezed,Object? formula = freezed,Object? script = freezed,Object? sensors = freezed,Object? type = freezed,Object? algorithm = freezed,Object? dataSource = freezed,Object? access = freezed,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? description = freezed,Object? formula = freezed,Object? script = freezed,Object? sensors = freezed,Object? assets = freezed,Object? assetsIds = freezed,Object? type = freezed,Object? algorithm = freezed,Object? dataSource = freezed,Object? enableLttb = freezed,Object? access = freezed,}) { return _then(_self.copyWith( id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable @@ -83,10 +86,13 @@ as String,description: freezed == description ? _self.description : description as String?,formula: freezed == formula ? _self.formula : formula // ignore: cast_nullable_to_non_nullable as String?,script: freezed == script ? _self.script : script // ignore: cast_nullable_to_non_nullable as String?,sensors: freezed == sensors ? _self.sensors : sensors // ignore: cast_nullable_to_non_nullable +as List?,assets: freezed == assets ? _self.assets : assets // ignore: cast_nullable_to_non_nullable +as List?,assetsIds: freezed == assetsIds ? _self.assetsIds : assetsIds // ignore: cast_nullable_to_non_nullable as List?,type: freezed == type ? _self.type : type // ignore: cast_nullable_to_non_nullable as ChartType?,algorithm: freezed == algorithm ? _self.algorithm : algorithm // ignore: cast_nullable_to_non_nullable as ChartAlgorithm?,dataSource: freezed == dataSource ? _self.dataSource : dataSource // ignore: cast_nullable_to_non_nullable -as ChartDataSource?,access: freezed == access ? _self.access : access // ignore: cast_nullable_to_non_nullable +as ChartDataSource?,enableLttb: freezed == enableLttb ? _self.enableLttb : enableLttb // ignore: cast_nullable_to_non_nullable +as bool?,access: freezed == access ? _self.access : access // ignore: cast_nullable_to_non_nullable as List?, )); } @@ -172,10 +178,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String name, String? description, String? formula, String? script, List? sensors, @JsonKey(unknownEnumValue: ChartType.area) ChartType? type, @JsonKey(unknownEnumValue: ChartAlgorithm.auto) ChartAlgorithm? algorithm, @JsonKey(unknownEnumValue: ChartDataSource.messages) ChartDataSource? dataSource, List? access)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String name, String? description, String? formula, String? script, List? sensors, List? assets, List? assetsIds, @JsonKey(unknownEnumValue: ChartType.area) ChartType? type, @JsonKey(unknownEnumValue: ChartAlgorithm.auto) ChartAlgorithm? algorithm, @JsonKey(unknownEnumValue: ChartDataSource.messages) ChartDataSource? dataSource, bool? enableLttb, List? access)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _LayrzChart() when $default != null: -return $default(_that.id,_that.name,_that.description,_that.formula,_that.script,_that.sensors,_that.type,_that.algorithm,_that.dataSource,_that.access);case _: +return $default(_that.id,_that.name,_that.description,_that.formula,_that.script,_that.sensors,_that.assets,_that.assetsIds,_that.type,_that.algorithm,_that.dataSource,_that.enableLttb,_that.access);case _: return orElse(); } @@ -193,10 +199,10 @@ return $default(_that.id,_that.name,_that.description,_that.formula,_that.script /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String id, String name, String? description, String? formula, String? script, List? sensors, @JsonKey(unknownEnumValue: ChartType.area) ChartType? type, @JsonKey(unknownEnumValue: ChartAlgorithm.auto) ChartAlgorithm? algorithm, @JsonKey(unknownEnumValue: ChartDataSource.messages) ChartDataSource? dataSource, List? access) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String id, String name, String? description, String? formula, String? script, List? sensors, List? assets, List? assetsIds, @JsonKey(unknownEnumValue: ChartType.area) ChartType? type, @JsonKey(unknownEnumValue: ChartAlgorithm.auto) ChartAlgorithm? algorithm, @JsonKey(unknownEnumValue: ChartDataSource.messages) ChartDataSource? dataSource, bool? enableLttb, List? access) $default,) {final _that = this; switch (_that) { case _LayrzChart(): -return $default(_that.id,_that.name,_that.description,_that.formula,_that.script,_that.sensors,_that.type,_that.algorithm,_that.dataSource,_that.access);case _: +return $default(_that.id,_that.name,_that.description,_that.formula,_that.script,_that.sensors,_that.assets,_that.assetsIds,_that.type,_that.algorithm,_that.dataSource,_that.enableLttb,_that.access);case _: throw StateError('Unexpected subclass'); } @@ -213,10 +219,10 @@ return $default(_that.id,_that.name,_that.description,_that.formula,_that.script /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String name, String? description, String? formula, String? script, List? sensors, @JsonKey(unknownEnumValue: ChartType.area) ChartType? type, @JsonKey(unknownEnumValue: ChartAlgorithm.auto) ChartAlgorithm? algorithm, @JsonKey(unknownEnumValue: ChartDataSource.messages) ChartDataSource? dataSource, List? access)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String name, String? description, String? formula, String? script, List? sensors, List? assets, List? assetsIds, @JsonKey(unknownEnumValue: ChartType.area) ChartType? type, @JsonKey(unknownEnumValue: ChartAlgorithm.auto) ChartAlgorithm? algorithm, @JsonKey(unknownEnumValue: ChartDataSource.messages) ChartDataSource? dataSource, bool? enableLttb, List? access)? $default,) {final _that = this; switch (_that) { case _LayrzChart() when $default != null: -return $default(_that.id,_that.name,_that.description,_that.formula,_that.script,_that.sensors,_that.type,_that.algorithm,_that.dataSource,_that.access);case _: +return $default(_that.id,_that.name,_that.description,_that.formula,_that.script,_that.sensors,_that.assets,_that.assetsIds,_that.type,_that.algorithm,_that.dataSource,_that.enableLttb,_that.access);case _: return null; } @@ -228,7 +234,7 @@ return $default(_that.id,_that.name,_that.description,_that.formula,_that.script @JsonSerializable() class _LayrzChart extends LayrzChart { - const _LayrzChart({required this.id, required this.name, this.description, this.formula, this.script, final List? sensors, @JsonKey(unknownEnumValue: ChartType.area) this.type, @JsonKey(unknownEnumValue: ChartAlgorithm.auto) this.algorithm, @JsonKey(unknownEnumValue: ChartDataSource.messages) this.dataSource, final List? access}): _sensors = sensors,_access = access,super._(); + const _LayrzChart({required this.id, required this.name, this.description, this.formula, this.script, final List? sensors, final List? assets, final List? assetsIds, @JsonKey(unknownEnumValue: ChartType.area) this.type, @JsonKey(unknownEnumValue: ChartAlgorithm.auto) this.algorithm, @JsonKey(unknownEnumValue: ChartDataSource.messages) this.dataSource, this.enableLttb, final List? access}): _sensors = sensors,_assets = assets,_assetsIds = assetsIds,_access = access,super._(); factory _LayrzChart.fromJson(Map json) => _$LayrzChartFromJson(json); /// [id] is a unique identifier for this entity. @@ -252,12 +258,36 @@ class _LayrzChart extends LayrzChart { return EqualUnmodifiableListView(value); } +/// [assets] is the list of assets associated with the chart. + final List? _assets; +/// [assets] is the list of assets associated with the chart. +@override List? get assets { + final value = _assets; + if (value == null) return null; + if (_assets is EqualUnmodifiableListView) return _assets; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); +} + +/// [assetsIds] is the list of asset IDs associated with the chart. + final List? _assetsIds; +/// [assetsIds] is the list of asset IDs associated with the chart. +@override List? get assetsIds { + final value = _assetsIds; + if (value == null) return null; + if (_assetsIds is EqualUnmodifiableListView) return _assetsIds; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); +} + /// [type] is the type of the chart. @override@JsonKey(unknownEnumValue: ChartType.area) final ChartType? type; /// [algorithm] is the algorithm used to calculate the chart. @override@JsonKey(unknownEnumValue: ChartAlgorithm.auto) final ChartAlgorithm? algorithm; /// [dataSource] is the data source used to calculate the chart. @override@JsonKey(unknownEnumValue: ChartDataSource.messages) final ChartDataSource? dataSource; +/// [enableLttb] indicates whether the LTTB downsampling algorithm is enabled for this chart. +@override final bool? enableLttb; /// [access] is a list of granted access to this entity. final List? _access; /// [access] is a list of granted access to this entity. @@ -283,16 +313,16 @@ Map toJson() { @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _LayrzChart&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.formula, formula) || other.formula == formula)&&(identical(other.script, script) || other.script == script)&&const DeepCollectionEquality().equals(other._sensors, _sensors)&&(identical(other.type, type) || other.type == type)&&(identical(other.algorithm, algorithm) || other.algorithm == algorithm)&&(identical(other.dataSource, dataSource) || other.dataSource == dataSource)&&const DeepCollectionEquality().equals(other._access, _access)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _LayrzChart&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.formula, formula) || other.formula == formula)&&(identical(other.script, script) || other.script == script)&&const DeepCollectionEquality().equals(other._sensors, _sensors)&&const DeepCollectionEquality().equals(other._assets, _assets)&&const DeepCollectionEquality().equals(other._assetsIds, _assetsIds)&&(identical(other.type, type) || other.type == type)&&(identical(other.algorithm, algorithm) || other.algorithm == algorithm)&&(identical(other.dataSource, dataSource) || other.dataSource == dataSource)&&(identical(other.enableLttb, enableLttb) || other.enableLttb == enableLttb)&&const DeepCollectionEquality().equals(other._access, _access)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,id,name,description,formula,script,const DeepCollectionEquality().hash(_sensors),type,algorithm,dataSource,const DeepCollectionEquality().hash(_access)); +int get hashCode => Object.hash(runtimeType,id,name,description,formula,script,const DeepCollectionEquality().hash(_sensors),const DeepCollectionEquality().hash(_assets),const DeepCollectionEquality().hash(_assetsIds),type,algorithm,dataSource,enableLttb,const DeepCollectionEquality().hash(_access)); @override String toString() { - return 'LayrzChart(id: $id, name: $name, description: $description, formula: $formula, script: $script, sensors: $sensors, type: $type, algorithm: $algorithm, dataSource: $dataSource, access: $access)'; + return 'LayrzChart(id: $id, name: $name, description: $description, formula: $formula, script: $script, sensors: $sensors, assets: $assets, assetsIds: $assetsIds, type: $type, algorithm: $algorithm, dataSource: $dataSource, enableLttb: $enableLttb, access: $access)'; } @@ -303,7 +333,7 @@ abstract mixin class _$LayrzChartCopyWith<$Res> implements $LayrzChartCopyWith<$ factory _$LayrzChartCopyWith(_LayrzChart value, $Res Function(_LayrzChart) _then) = __$LayrzChartCopyWithImpl; @override @useResult $Res call({ - String id, String name, String? description, String? formula, String? script, List? sensors,@JsonKey(unknownEnumValue: ChartType.area) ChartType? type,@JsonKey(unknownEnumValue: ChartAlgorithm.auto) ChartAlgorithm? algorithm,@JsonKey(unknownEnumValue: ChartDataSource.messages) ChartDataSource? dataSource, List? access + String id, String name, String? description, String? formula, String? script, List? sensors, List? assets, List? assetsIds,@JsonKey(unknownEnumValue: ChartType.area) ChartType? type,@JsonKey(unknownEnumValue: ChartAlgorithm.auto) ChartAlgorithm? algorithm,@JsonKey(unknownEnumValue: ChartDataSource.messages) ChartDataSource? dataSource, bool? enableLttb, List? access }); @@ -320,7 +350,7 @@ class __$LayrzChartCopyWithImpl<$Res> /// Create a copy of LayrzChart /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? description = freezed,Object? formula = freezed,Object? script = freezed,Object? sensors = freezed,Object? type = freezed,Object? algorithm = freezed,Object? dataSource = freezed,Object? access = freezed,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? description = freezed,Object? formula = freezed,Object? script = freezed,Object? sensors = freezed,Object? assets = freezed,Object? assetsIds = freezed,Object? type = freezed,Object? algorithm = freezed,Object? dataSource = freezed,Object? enableLttb = freezed,Object? access = freezed,}) { return _then(_LayrzChart( id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable @@ -328,10 +358,13 @@ as String,description: freezed == description ? _self.description : description as String?,formula: freezed == formula ? _self.formula : formula // ignore: cast_nullable_to_non_nullable as String?,script: freezed == script ? _self.script : script // ignore: cast_nullable_to_non_nullable as String?,sensors: freezed == sensors ? _self._sensors : sensors // ignore: cast_nullable_to_non_nullable +as List?,assets: freezed == assets ? _self._assets : assets // ignore: cast_nullable_to_non_nullable +as List?,assetsIds: freezed == assetsIds ? _self._assetsIds : assetsIds // ignore: cast_nullable_to_non_nullable as List?,type: freezed == type ? _self.type : type // ignore: cast_nullable_to_non_nullable as ChartType?,algorithm: freezed == algorithm ? _self.algorithm : algorithm // ignore: cast_nullable_to_non_nullable as ChartAlgorithm?,dataSource: freezed == dataSource ? _self.dataSource : dataSource // ignore: cast_nullable_to_non_nullable -as ChartDataSource?,access: freezed == access ? _self._access : access // ignore: cast_nullable_to_non_nullable +as ChartDataSource?,enableLttb: freezed == enableLttb ? _self.enableLttb : enableLttb // ignore: cast_nullable_to_non_nullable +as bool?,access: freezed == access ? _self._access : access // ignore: cast_nullable_to_non_nullable as List?, )); } @@ -355,13 +388,17 @@ mixin _$LayrzChartInput { String get script;/// [script] is the script used to calculate the chart. This property is a Python script. set script(String value);/// [sensors] is a list of sensors used to calculate the chart. List get sensors;/// [sensors] is a list of sensors used to calculate the chart. - set sensors(List value);/// [type] is the type of the chart. + set sensors(List value);/// [assetsIds] is the list of asset IDs associated with the chart. + List get assetsIds;/// [assetsIds] is the list of asset IDs associated with the chart. + set assetsIds(List value);/// [type] is the type of the chart. @JsonKey(unknownEnumValue: ChartType.area) ChartType get type;/// [type] is the type of the chart. @JsonKey(unknownEnumValue: ChartType.area) set type(ChartType value);/// [algorithm] is the algorithm used to calculate the chart. @JsonKey(unknownEnumValue: ChartAlgorithm.auto) ChartAlgorithm get algorithm;/// [algorithm] is the algorithm used to calculate the chart. @JsonKey(unknownEnumValue: ChartAlgorithm.auto) set algorithm(ChartAlgorithm value);/// [dataSource] is the data source used to calculate the chart. @JsonKey(unknownEnumValue: ChartDataSource.messages) ChartDataSource get dataSource;/// [dataSource] is the data source used to calculate the chart. -@JsonKey(unknownEnumValue: ChartDataSource.messages) set dataSource(ChartDataSource value); +@JsonKey(unknownEnumValue: ChartDataSource.messages) set dataSource(ChartDataSource value);/// [enableLttb] indicates whether the LTTB downsampling algorithm is enabled for this chart. + bool get enableLttb;/// [enableLttb] indicates whether the LTTB downsampling algorithm is enabled for this chart. + set enableLttb(bool value); /// Create a copy of LayrzChartInput /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -376,7 +413,7 @@ $LayrzChartInputCopyWith get copyWith => _$LayrzChartInputCopyW @override String toString() { - return 'LayrzChartInput(id: $id, name: $name, description: $description, formula: $formula, script: $script, sensors: $sensors, type: $type, algorithm: $algorithm, dataSource: $dataSource)'; + return 'LayrzChartInput(id: $id, name: $name, description: $description, formula: $formula, script: $script, sensors: $sensors, assetsIds: $assetsIds, type: $type, algorithm: $algorithm, dataSource: $dataSource, enableLttb: $enableLttb)'; } @@ -387,7 +424,7 @@ abstract mixin class $LayrzChartInputCopyWith<$Res> { factory $LayrzChartInputCopyWith(LayrzChartInput value, $Res Function(LayrzChartInput) _then) = _$LayrzChartInputCopyWithImpl; @useResult $Res call({ - String? id, String name, String description, String formula, String script, List sensors,@JsonKey(unknownEnumValue: ChartType.area) ChartType type,@JsonKey(unknownEnumValue: ChartAlgorithm.auto) ChartAlgorithm algorithm,@JsonKey(unknownEnumValue: ChartDataSource.messages) ChartDataSource dataSource + String? id, String name, String description, String formula, String script, List sensors, List assetsIds,@JsonKey(unknownEnumValue: ChartType.area) ChartType type,@JsonKey(unknownEnumValue: ChartAlgorithm.auto) ChartAlgorithm algorithm,@JsonKey(unknownEnumValue: ChartDataSource.messages) ChartDataSource dataSource, bool enableLttb }); @@ -404,7 +441,7 @@ class _$LayrzChartInputCopyWithImpl<$Res> /// Create a copy of LayrzChartInput /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? id = freezed,Object? name = null,Object? description = null,Object? formula = null,Object? script = null,Object? sensors = null,Object? type = null,Object? algorithm = null,Object? dataSource = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? id = freezed,Object? name = null,Object? description = null,Object? formula = null,Object? script = null,Object? sensors = null,Object? assetsIds = null,Object? type = null,Object? algorithm = null,Object? dataSource = null,Object? enableLttb = null,}) { return _then(_self.copyWith( id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable as String?,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable @@ -412,10 +449,12 @@ as String,description: null == description ? _self.description : description // as String,formula: null == formula ? _self.formula : formula // ignore: cast_nullable_to_non_nullable as String,script: null == script ? _self.script : script // ignore: cast_nullable_to_non_nullable as String,sensors: null == sensors ? _self.sensors : sensors // ignore: cast_nullable_to_non_nullable +as List,assetsIds: null == assetsIds ? _self.assetsIds : assetsIds // ignore: cast_nullable_to_non_nullable as List,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable as ChartType,algorithm: null == algorithm ? _self.algorithm : algorithm // ignore: cast_nullable_to_non_nullable as ChartAlgorithm,dataSource: null == dataSource ? _self.dataSource : dataSource // ignore: cast_nullable_to_non_nullable -as ChartDataSource, +as ChartDataSource,enableLttb: null == enableLttb ? _self.enableLttb : enableLttb // ignore: cast_nullable_to_non_nullable +as bool, )); } @@ -500,10 +539,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String? id, String name, String description, String formula, String script, List sensors, @JsonKey(unknownEnumValue: ChartType.area) ChartType type, @JsonKey(unknownEnumValue: ChartAlgorithm.auto) ChartAlgorithm algorithm, @JsonKey(unknownEnumValue: ChartDataSource.messages) ChartDataSource dataSource)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String? id, String name, String description, String formula, String script, List sensors, List assetsIds, @JsonKey(unknownEnumValue: ChartType.area) ChartType type, @JsonKey(unknownEnumValue: ChartAlgorithm.auto) ChartAlgorithm algorithm, @JsonKey(unknownEnumValue: ChartDataSource.messages) ChartDataSource dataSource, bool enableLttb)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _LayrzChartInput() when $default != null: -return $default(_that.id,_that.name,_that.description,_that.formula,_that.script,_that.sensors,_that.type,_that.algorithm,_that.dataSource);case _: +return $default(_that.id,_that.name,_that.description,_that.formula,_that.script,_that.sensors,_that.assetsIds,_that.type,_that.algorithm,_that.dataSource,_that.enableLttb);case _: return orElse(); } @@ -521,10 +560,10 @@ return $default(_that.id,_that.name,_that.description,_that.formula,_that.script /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String? id, String name, String description, String formula, String script, List sensors, @JsonKey(unknownEnumValue: ChartType.area) ChartType type, @JsonKey(unknownEnumValue: ChartAlgorithm.auto) ChartAlgorithm algorithm, @JsonKey(unknownEnumValue: ChartDataSource.messages) ChartDataSource dataSource) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String? id, String name, String description, String formula, String script, List sensors, List assetsIds, @JsonKey(unknownEnumValue: ChartType.area) ChartType type, @JsonKey(unknownEnumValue: ChartAlgorithm.auto) ChartAlgorithm algorithm, @JsonKey(unknownEnumValue: ChartDataSource.messages) ChartDataSource dataSource, bool enableLttb) $default,) {final _that = this; switch (_that) { case _LayrzChartInput(): -return $default(_that.id,_that.name,_that.description,_that.formula,_that.script,_that.sensors,_that.type,_that.algorithm,_that.dataSource);case _: +return $default(_that.id,_that.name,_that.description,_that.formula,_that.script,_that.sensors,_that.assetsIds,_that.type,_that.algorithm,_that.dataSource,_that.enableLttb);case _: throw StateError('Unexpected subclass'); } @@ -541,10 +580,10 @@ return $default(_that.id,_that.name,_that.description,_that.formula,_that.script /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String? id, String name, String description, String formula, String script, List sensors, @JsonKey(unknownEnumValue: ChartType.area) ChartType type, @JsonKey(unknownEnumValue: ChartAlgorithm.auto) ChartAlgorithm algorithm, @JsonKey(unknownEnumValue: ChartDataSource.messages) ChartDataSource dataSource)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String? id, String name, String description, String formula, String script, List sensors, List assetsIds, @JsonKey(unknownEnumValue: ChartType.area) ChartType type, @JsonKey(unknownEnumValue: ChartAlgorithm.auto) ChartAlgorithm algorithm, @JsonKey(unknownEnumValue: ChartDataSource.messages) ChartDataSource dataSource, bool enableLttb)? $default,) {final _that = this; switch (_that) { case _LayrzChartInput() when $default != null: -return $default(_that.id,_that.name,_that.description,_that.formula,_that.script,_that.sensors,_that.type,_that.algorithm,_that.dataSource);case _: +return $default(_that.id,_that.name,_that.description,_that.formula,_that.script,_that.sensors,_that.assetsIds,_that.type,_that.algorithm,_that.dataSource,_that.enableLttb);case _: return null; } @@ -556,7 +595,7 @@ return $default(_that.id,_that.name,_that.description,_that.formula,_that.script @JsonSerializable() class _LayrzChartInput extends LayrzChartInput { - _LayrzChartInput({this.id, this.name = '', this.description = '', this.formula = '', this.script = '', this.sensors = const [], @JsonKey(unknownEnumValue: ChartType.area) this.type = ChartType.area, @JsonKey(unknownEnumValue: ChartAlgorithm.auto) this.algorithm = ChartAlgorithm.auto, @JsonKey(unknownEnumValue: ChartDataSource.messages) this.dataSource = ChartDataSource.messages}): super._(); + _LayrzChartInput({this.id, this.name = '', this.description = '', this.formula = '', this.script = '', this.sensors = const [], this.assetsIds = const [], @JsonKey(unknownEnumValue: ChartType.area) this.type = ChartType.area, @JsonKey(unknownEnumValue: ChartAlgorithm.auto) this.algorithm = ChartAlgorithm.auto, @JsonKey(unknownEnumValue: ChartDataSource.messages) this.dataSource = ChartDataSource.messages, this.enableLttb = true}): super._(); factory _LayrzChartInput.fromJson(Map json) => _$LayrzChartInputFromJson(json); /// [id] is a unique identifier for this entity. Keep it null to create a new entity. @@ -571,12 +610,16 @@ class _LayrzChartInput extends LayrzChartInput { @override@JsonKey() String script; /// [sensors] is a list of sensors used to calculate the chart. @override@JsonKey() List sensors; +/// [assetsIds] is the list of asset IDs associated with the chart. +@override@JsonKey() List assetsIds; /// [type] is the type of the chart. @override@JsonKey(unknownEnumValue: ChartType.area) ChartType type; /// [algorithm] is the algorithm used to calculate the chart. @override@JsonKey(unknownEnumValue: ChartAlgorithm.auto) ChartAlgorithm algorithm; /// [dataSource] is the data source used to calculate the chart. @override@JsonKey(unknownEnumValue: ChartDataSource.messages) ChartDataSource dataSource; +/// [enableLttb] indicates whether the LTTB downsampling algorithm is enabled for this chart. +@override@JsonKey() bool enableLttb; /// Create a copy of LayrzChartInput /// with the given fields replaced by the non-null parameter values. @@ -593,7 +636,7 @@ Map toJson() { @override String toString() { - return 'LayrzChartInput(id: $id, name: $name, description: $description, formula: $formula, script: $script, sensors: $sensors, type: $type, algorithm: $algorithm, dataSource: $dataSource)'; + return 'LayrzChartInput(id: $id, name: $name, description: $description, formula: $formula, script: $script, sensors: $sensors, assetsIds: $assetsIds, type: $type, algorithm: $algorithm, dataSource: $dataSource, enableLttb: $enableLttb)'; } @@ -604,7 +647,7 @@ abstract mixin class _$LayrzChartInputCopyWith<$Res> implements $LayrzChartInput factory _$LayrzChartInputCopyWith(_LayrzChartInput value, $Res Function(_LayrzChartInput) _then) = __$LayrzChartInputCopyWithImpl; @override @useResult $Res call({ - String? id, String name, String description, String formula, String script, List sensors,@JsonKey(unknownEnumValue: ChartType.area) ChartType type,@JsonKey(unknownEnumValue: ChartAlgorithm.auto) ChartAlgorithm algorithm,@JsonKey(unknownEnumValue: ChartDataSource.messages) ChartDataSource dataSource + String? id, String name, String description, String formula, String script, List sensors, List assetsIds,@JsonKey(unknownEnumValue: ChartType.area) ChartType type,@JsonKey(unknownEnumValue: ChartAlgorithm.auto) ChartAlgorithm algorithm,@JsonKey(unknownEnumValue: ChartDataSource.messages) ChartDataSource dataSource, bool enableLttb }); @@ -621,7 +664,7 @@ class __$LayrzChartInputCopyWithImpl<$Res> /// Create a copy of LayrzChartInput /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? id = freezed,Object? name = null,Object? description = null,Object? formula = null,Object? script = null,Object? sensors = null,Object? type = null,Object? algorithm = null,Object? dataSource = null,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? id = freezed,Object? name = null,Object? description = null,Object? formula = null,Object? script = null,Object? sensors = null,Object? assetsIds = null,Object? type = null,Object? algorithm = null,Object? dataSource = null,Object? enableLttb = null,}) { return _then(_LayrzChartInput( id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable as String?,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable @@ -629,10 +672,12 @@ as String,description: null == description ? _self.description : description // as String,formula: null == formula ? _self.formula : formula // ignore: cast_nullable_to_non_nullable as String,script: null == script ? _self.script : script // ignore: cast_nullable_to_non_nullable as String,sensors: null == sensors ? _self.sensors : sensors // ignore: cast_nullable_to_non_nullable +as List,assetsIds: null == assetsIds ? _self.assetsIds : assetsIds // ignore: cast_nullable_to_non_nullable as List,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable as ChartType,algorithm: null == algorithm ? _self.algorithm : algorithm // ignore: cast_nullable_to_non_nullable as ChartAlgorithm,dataSource: null == dataSource ? _self.dataSource : dataSource // ignore: cast_nullable_to_non_nullable -as ChartDataSource, +as ChartDataSource,enableLttb: null == enableLttb ? _self.enableLttb : enableLttb // ignore: cast_nullable_to_non_nullable +as bool, )); } diff --git a/lib/src/charts/charts.g.dart b/lib/src/charts/charts.g.dart index 84249eb0..afd6e929 100644 --- a/lib/src/charts/charts.g.dart +++ b/lib/src/charts/charts.g.dart @@ -15,6 +15,12 @@ _LayrzChart _$LayrzChartFromJson(Map json) => _LayrzChart( sensors: (json['sensors'] as List?) ?.map((e) => e as String) .toList(), + assets: (json['assets'] as List?) + ?.map((e) => Asset.fromJson(e as Map)) + .toList(), + assetsIds: (json['assetsIds'] as List?) + ?.map((e) => e as String) + .toList(), type: $enumDecodeNullable( _$ChartTypeEnumMap, json['type'], @@ -30,6 +36,7 @@ _LayrzChart _$LayrzChartFromJson(Map json) => _LayrzChart( json['dataSource'], unknownValue: ChartDataSource.messages, ), + enableLttb: json['enableLttb'] as bool?, access: (json['access'] as List?) ?.map((e) => Access.fromJson(e as Map)) .toList(), @@ -43,9 +50,12 @@ Map _$LayrzChartToJson(_LayrzChart instance) => 'formula': instance.formula, 'script': instance.script, 'sensors': instance.sensors, + 'assets': instance.assets?.map((e) => e.toJson()).toList(), + 'assetsIds': instance.assetsIds, 'type': instance.type?.toJson(), 'algorithm': instance.algorithm?.toJson(), 'dataSource': instance.dataSource?.toJson(), + 'enableLttb': instance.enableLttb, 'access': instance.access?.map((e) => e.toJson()).toList(), }; @@ -81,40 +91,43 @@ const _$ChartDataSourceEnumMap = { ChartDataSource.lastMessages: 'LAST_MESSAGES', }; -_LayrzChartInput _$LayrzChartInputFromJson(Map json) => - _LayrzChartInput( - id: json['id'] as String?, - name: json['name'] as String? ?? '', - description: json['description'] as String? ?? '', - formula: json['formula'] as String? ?? '', - script: json['script'] as String? ?? '', - sensors: - (json['sensors'] as List?) - ?.map((e) => e as String) - .toList() ?? - const [], - type: - $enumDecodeNullable( - _$ChartTypeEnumMap, - json['type'], - unknownValue: ChartType.area, - ) ?? - ChartType.area, - algorithm: - $enumDecodeNullable( - _$ChartAlgorithmEnumMap, - json['algorithm'], - unknownValue: ChartAlgorithm.auto, - ) ?? - ChartAlgorithm.auto, - dataSource: - $enumDecodeNullable( - _$ChartDataSourceEnumMap, - json['dataSource'], - unknownValue: ChartDataSource.messages, - ) ?? - ChartDataSource.messages, - ); +_LayrzChartInput _$LayrzChartInputFromJson( + Map json, +) => _LayrzChartInput( + id: json['id'] as String?, + name: json['name'] as String? ?? '', + description: json['description'] as String? ?? '', + formula: json['formula'] as String? ?? '', + script: json['script'] as String? ?? '', + sensors: + (json['sensors'] as List?)?.map((e) => e as String).toList() ?? + const [], + assetsIds: + (json['assetsIds'] as List?)?.map((e) => e as String).toList() ?? + const [], + type: + $enumDecodeNullable( + _$ChartTypeEnumMap, + json['type'], + unknownValue: ChartType.area, + ) ?? + ChartType.area, + algorithm: + $enumDecodeNullable( + _$ChartAlgorithmEnumMap, + json['algorithm'], + unknownValue: ChartAlgorithm.auto, + ) ?? + ChartAlgorithm.auto, + dataSource: + $enumDecodeNullable( + _$ChartDataSourceEnumMap, + json['dataSource'], + unknownValue: ChartDataSource.messages, + ) ?? + ChartDataSource.messages, + enableLttb: json['enableLttb'] as bool? ?? true, +); Map _$LayrzChartInputToJson(_LayrzChartInput instance) => { @@ -124,9 +137,11 @@ Map _$LayrzChartInputToJson(_LayrzChartInput instance) => 'formula': instance.formula, 'script': instance.script, 'sensors': instance.sensors, + 'assetsIds': instance.assetsIds, 'type': instance.type.toJson(), 'algorithm': instance.algorithm.toJson(), 'dataSource': instance.dataSource.toJson(), + 'enableLttb': instance.enableLttb, }; _AxisConfig _$AxisConfigFromJson(Map json) => _AxisConfig( diff --git a/lib/src/charts/src/chart.dart b/lib/src/charts/src/chart.dart index be8a4e64..d6cb0beb 100644 --- a/lib/src/charts/src/chart.dart +++ b/lib/src/charts/src/chart.dart @@ -22,6 +22,12 @@ abstract class LayrzChart with _$LayrzChart { /// [sensors] is a list of sensors used to calculate the chart. List? sensors, + /// [assets] is the list of assets associated with the chart. + List? assets, + + /// [assetsIds] is the list of asset IDs associated with the chart. + List? assetsIds, + /// [type] is the type of the chart. @JsonKey(unknownEnumValue: ChartType.area) ChartType? type, @@ -31,44 +37,264 @@ abstract class LayrzChart with _$LayrzChart { /// [dataSource] is the data source used to calculate the chart. @JsonKey(unknownEnumValue: ChartDataSource.messages) ChartDataSource? dataSource, + /// [enableLttb] indicates whether the LTTB downsampling algorithm is enabled for this chart. + bool? enableLttb, + /// [access] is a list of granted access to this entity. List? access, }) = _LayrzChart; factory LayrzChart.fromJson(Map json) => _$LayrzChartFromJson(json); -} -@unfreezed -abstract class LayrzChartInput with _$LayrzChartInput { - const LayrzChartInput._(); - factory LayrzChartInput({ - /// [id] is a unique identifier for this entity. Keep it null to create a new entity. - String? id, + /// [fetch] fetches the full chart data from the API using this instance's id. + Future fetch({ + /// [apiToken] is the API token to use for authentication. + required String apiToken, - /// [name] is the name of the chart. - @Default('') String name, + /// [uri] is the GraphQL endpoint to use + required Uri uri, - /// [description] is a description of the chart. Useful for identification or brief explanation. - @Default('') String description, + /// [onResponse] is the callback to call when the response is received + void Function(String statusCode)? onResponse, + }) async { + final connector = LayrzConnector(uri: uri); + try { + final response = await connector.perform( + GqlQuery( + variables: [ + GqlVariable(name: 'apiToken', type: .string, req: true, value: apiToken), + GqlVariable(name: 'id', type: .id, req: true, value: id), + ], + name: 'charts', + )..add( + GqlField(name: 'charts', args: {'apiToken': 'apiToken', 'id': 'id'}) + ..add(GqlField(name: 'status')) + ..add(GqlField(name: 'result', fragment: gqlFragment)), + ), + ); - /// [formula] is the formula used to calculate the chart. This property is a LCL formula. - @Default('') String formula, + final data = response.data; + if (data == null) { + onResponse?.call(ApiStatus.internalError.toJson()); + Log.error("layrz_models/LayrzChart/fetch(): No response from server"); + return null; + } - /// [script] is the script used to calculate the chart. This property is a Python script. - @Default('') String script, + final result = data['data']['charts']; + if (result == null) { + onResponse?.call(ApiStatus.internalError.toJson()); + Log.error("layrz_models/LayrzChart/fetch(): No result from server"); + return null; + } - /// [sensors] is a list of sensors used to calculate the chart. - @Default([]) List sensors, + final status = ApiStatus.fromJson(result['status']); + if (status != ApiStatus.ok) { + onResponse?.call(status.toJson()); + return null; + } + if (result['result'] == null || (result['result'] as List).isEmpty) { + onResponse?.call('NOT_FOUND'); + return null; + } - /// [type] is the type of the chart. - @JsonKey(unknownEnumValue: ChartType.area) @Default(ChartType.area) ChartType type, + return LayrzChart.fromJson(Map.from(result['result'][0] as Map)); + } catch (e, stack) { + Log.critical("layrz_models/LayrzChart/fetch(): General exception => $e\n$stack"); + return null; + } + } - /// [algorithm] is the algorithm used to calculate the chart. - @JsonKey(unknownEnumValue: ChartAlgorithm.auto) @Default(ChartAlgorithm.auto) ChartAlgorithm algorithm, + /// [fetchAll] fetches all charts from the API with a lightweight payload, suitable for listings and pickers. + static Future> fetchAll({ + /// [apiToken] is the API token to use for authentication. + required String apiToken, - /// [dataSource] is the data source used to calculate the chart. - @JsonKey(unknownEnumValue: ChartDataSource.messages) @Default(ChartDataSource.messages) ChartDataSource dataSource, - }) = _LayrzChartInput; + /// [uri] is the GraphQL endpoint to use + required Uri uri, + + /// [onResponse] is the callback to call when the response is received + void Function(String statusCode)? onResponse, + }) async { + final connector = LayrzConnector(uri: uri); + try { + final response = await connector.perform( + GqlQuery( + variables: [ + GqlVariable(name: 'apiToken', type: .string, req: true, value: apiToken), + ], + name: 'charts', + )..add( + GqlField(name: 'charts', args: {'apiToken': 'apiToken'}) + ..add(GqlField(name: 'status')) + ..add( + GqlField(name: 'result') + ..add(GqlField(name: 'id')) + ..add(GqlField(name: 'name')) + ..add(GqlField(name: 'type')), + ), + ), + ); + + final data = response.data; + if (data == null) { + onResponse?.call(ApiStatus.internalError.toJson()); + Log.error("layrz_models/LayrzChart/fetchAll(): No response from server"); + return []; + } + + final result = data['data']['charts']; + if (result == null) { + onResponse?.call(ApiStatus.internalError.toJson()); + Log.error("layrz_models/LayrzChart/fetchAll(): No result from server"); + return []; + } + + final status = ApiStatus.fromJson(result['status']); + if (status != ApiStatus.ok) { + onResponse?.call(status.toJson()); + return []; + } + + return (result['result'] as List?) + ?.map((e) => LayrzChart.fromJson(Map.from(e as Map))) + .toList() ?? + []; + } catch (e, stack) { + Log.critical("layrz_models/LayrzChart/fetchAll(): General exception => $e\n$stack"); + return []; + } + } + + /// [delete] deletes this chart via the API. + Future delete({ + /// [apiToken] is the API token to use for authentication. + required String apiToken, + + /// [uri] is the GraphQL endpoint to use + required Uri uri, + + /// [onResponse] is the callback to call when the response is received + void Function(String statusCode)? onResponse, + }) async { + final connector = LayrzConnector(uri: uri); + try { + final response = await connector.perform( + GqlMutation( + variables: [ + GqlVariable(name: 'apiToken', type: .string, req: true, value: apiToken), + GqlVariable(name: 'ids', type: .list, listOf: .id, req: true, value: [id]), + ], + name: 'deleteChart', + )..add( + GqlField(name: 'deleteChart', args: {'apiToken': 'apiToken', 'ids': 'ids'}) + ..add(GqlField(name: 'status')) + ..add(GqlField(name: 'errors')), + ), + ); + + final data = response.data; + if (data == null) { + onResponse?.call(ApiStatus.internalError.toJson()); + Log.error("layrz_models/LayrzChart/delete(): No response from server"); + return false; + } + + final result = data['data']['deleteCharts']; + if (result == null) { + onResponse?.call(ApiStatus.internalError.toJson()); + Log.error("layrz_models/LayrzChart/delete(): No result from server"); + return false; + } + + final status = ApiStatus.fromJson(result['status']); + if (status != ApiStatus.ok) { + onResponse?.call(status.toJson()); + return false; + } + + return true; + } catch (e, stack) { + Log.critical("layrz_models/LayrzChart/delete(): General exception => $e\n$stack"); + return false; + } + } + + /// [deleteMultiple] deletes a batch of charts by their IDs. + static Future deleteMultiple({ + /// [apiToken] is the API token to use for authentication. + required String apiToken, + + /// [uri] is the GraphQL endpoint to use + required Uri uri, + + /// [ids] is the list of chart IDs to delete + required List ids, + + /// [onResponse] is the callback to call when the response is received + void Function(String statusCode)? onResponse, + }) async { + final connector = LayrzConnector(uri: uri); + try { + final response = await connector.perform( + GqlMutation( + variables: [ + GqlVariable(name: 'apiToken', type: .string, req: true, value: apiToken), + GqlVariable(name: 'ids', type: .list, listOf: .id, req: true, value: ids), + ], + name: 'deleteCharts', + )..add( + GqlField(name: 'deleteCharts', args: {'apiToken': 'apiToken', 'ids': 'ids'}) + ..add(GqlField(name: 'status')) + ..add(GqlField(name: 'errors')), + ), + ); + + final data = response.data; + if (data == null) { + onResponse?.call(ApiStatus.internalError.toJson()); + Log.error("layrz_models/LayrzChart/deleteMultiple(): No response from server"); + return false; + } + + final result = data['data']['deleteCharts']; + if (result == null) { + onResponse?.call(ApiStatus.internalError.toJson()); + Log.error("layrz_models/LayrzChart/deleteMultiple(): No result from server"); + return false; + } + + final status = ApiStatus.fromJson(result['status']); + if (status != ApiStatus.ok) { + onResponse?.call(status.toJson()); + return false; + } + + return true; + } catch (e, stack) { + Log.critical("layrz_models/LayrzChart/deleteMultiple(): General exception => $e\n$stack"); + return false; + } + } - factory LayrzChartInput.fromJson(Map json) => _$LayrzChartInputFromJson(json); + /// [gqlFragment] is the GqlFragment for a chart, including nested access permissions. + static GqlFragment get gqlFragment => GqlFragment(name: 'chartFragment', onType: 'Chart') + ..add(GqlField(name: 'id')) + ..add(GqlField(name: 'name')) + ..add(GqlField(name: 'description')) + ..add(GqlField(name: 'type')) + ..add(GqlField(name: 'algorithm')) + ..add(GqlField(name: 'dataSource')) + ..add(GqlField(name: 'formula')) + ..add(GqlField(name: 'script')) + ..add(GqlField(name: 'sensors')) + ..add( + GqlField(name: 'assets') + ..add(GqlField(name: 'id')) + ..add(GqlField(name: 'name')) + ..add(GqlField(name: 'dynamicIcon', fragment: Avatar.gqlFragment)) + ..add(GqlField(name: 'mode')), + ) + ..add(GqlField(name: 'assetsIds')) + ..add(GqlField(name: 'enableLttb')) + ..add(GqlField(name: 'access', fragment: Access.graphqlIdFragment)); } diff --git a/lib/src/charts/src/chart_input.dart b/lib/src/charts/src/chart_input.dart new file mode 100644 index 00000000..c1255df7 --- /dev/null +++ b/lib/src/charts/src/chart_input.dart @@ -0,0 +1,100 @@ +part of '../charts.dart'; + +@unfreezed +abstract class LayrzChartInput with _$LayrzChartInput { + const LayrzChartInput._(); + factory LayrzChartInput({ + /// [id] is a unique identifier for this entity. Keep it null to create a new entity. + String? id, + + /// [name] is the name of the chart. + @Default('') String name, + + /// [description] is a description of the chart. Useful for identification or brief explanation. + @Default('') String description, + + /// [formula] is the formula used to calculate the chart. This property is a LCL formula. + @Default('') String formula, + + /// [script] is the script used to calculate the chart. This property is a Python script. + @Default('') String script, + + /// [sensors] is a list of sensors used to calculate the chart. + @Default([]) List sensors, + + /// [assetsIds] is the list of asset IDs associated with the chart. + @Default([]) List assetsIds, + + /// [type] is the type of the chart. + @JsonKey(unknownEnumValue: ChartType.area) @Default(ChartType.area) ChartType type, + + /// [algorithm] is the algorithm used to calculate the chart. + @JsonKey(unknownEnumValue: ChartAlgorithm.auto) @Default(ChartAlgorithm.auto) ChartAlgorithm algorithm, + + /// [dataSource] is the data source used to calculate the chart. + @JsonKey(unknownEnumValue: ChartDataSource.messages) @Default(ChartDataSource.messages) ChartDataSource dataSource, + + /// [enableLttb] indicates whether the LTTB downsampling algorithm is enabled for this chart. + @Default(true) bool enableLttb, + }) = _LayrzChartInput; + + factory LayrzChartInput.fromJson(Map json) => _$LayrzChartInputFromJson(json); + + /// [save] saves the chart input to the API, creating a new chart when [id] is null or editing the existing one otherwise. + Future>?> save({ + /// [apiToken] is the API token to use for authentication. + required String apiToken, + + /// [uri] is the GraphQL endpoint to use + required Uri uri, + + /// [onResponse] is the callback to call when the response is received + void Function(String statusCode)? onResponse, + }) async { + final connector = LayrzConnector(uri: uri); + try { + final response = await connector.perform( + GqlMutation( + variables: [ + GqlVariable(name: 'apiToken', type: .string, req: true, value: apiToken), + GqlVariable(name: 'data', type: .input, req: true, inputName: 'ChartInput', value: toJson()), + ], + name: id == null ? 'addChart' : 'editChart', + )..add( + GqlField(name: id == null ? 'addChart' : 'editChart', args: {'apiToken': 'apiToken', 'data': 'data'}) + ..add(GqlField(name: 'status')) + ..add(GqlField(name: 'errors')) + ..add(GqlField(name: 'result', fragment: LayrzChart.gqlFragment)), + ), + ); + + final data = response.data; + if (data == null) { + onResponse?.call(ApiStatus.internalError.toJson()); + Log.error("layrz_models/LayrzChartInput/save(): No response from server"); + return null; + } + + final result = id == null ? data['data']['addChart'] : data['data']['editChart']; + if (result == null) { + onResponse?.call(ApiStatus.internalError.toJson()); + Log.error("layrz_models/LayrzChartInput/save(): No result from server"); + return null; + } + + final status = ApiStatus.fromJson(result['status']); + if (status != ApiStatus.ok) { + onResponse?.call(status.toJson()); + return ApiResponse( + status: status, + errors: Map.from(result['errors'] ?? {}), + ); + } + + return ApiResponse(status: ApiStatus.ok, result: LayrzChart.fromJson(result['result'])); + } catch (e, stack) { + Log.critical("layrz_models/LayrzChartInput/save(): General exception => $e\n$stack"); + return null; + } + } +} diff --git a/lib/src/locator/locator.dart b/lib/src/locator/locator.dart index 4293e022..24e78d38 100644 --- a/lib/src/locator/locator.dart +++ b/lib/src/locator/locator.dart @@ -5,13 +5,13 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:layrz_logging/layrz_logging.dart'; import 'package:layrz_models/src/api/api.dart'; import 'package:layrz_models/src/app/app.dart'; +import 'package:layrz_models/src/avatar/avatar.dart'; import 'package:layrz_models/src/assets/assets.dart'; import 'package:layrz_models/src/converters/converters.dart'; import 'package:layrz_models/src/geofences/geofences.dart'; import 'package:layrz_models/src/map/map.dart'; import 'package:layrz_models/src/triggers/triggers.dart'; import 'package:layrz_models/src/users/users.dart'; -import 'package:layrz_models/src/utils/src/api_connector.dart'; part 'locator.freezed.dart'; part 'locator.g.dart'; diff --git a/lib/src/locator/src/locator.dart b/lib/src/locator/src/locator.dart index d89460b8..2c5c5b10 100644 --- a/lib/src/locator/src/locator.dart +++ b/lib/src/locator/src/locator.dart @@ -126,7 +126,20 @@ abstract class Locator with _$Locator { }) async { final connector = LayrzConnector(uri: uri); try { - final response = await connector.perform(query: fetchSingleQuery, variables: {'apiToken': apiToken, 'id': id}); + final response = await connector.perform( + GqlQuery( + variables: [ + GqlVariable(name: 'apiToken', type: .string, req: true, value: apiToken), + GqlVariable(name: 'id', type: .id, value: id), + ], + name: 'fetchLocators', + )..add( + GqlField(name: 'locators', args: {'apiToken': 'apiToken', 'id': 'id'}) + ..add(GqlField(name: 'status')) + ..add(GqlField(name: 'errors')) + ..add(GqlField(name: 'result', fragment: gqlFragment)), + ), + ); final data = response.data; if (data == null) { @@ -174,7 +187,49 @@ abstract class Locator with _$Locator { }) async { final connector = LayrzConnector(uri: uri); try { - final response = await connector.perform(query: fetchAllGraphqlQuery, variables: {'apiToken': apiToken}); + final response = await connector.perform( + GqlQuery( + variables: [ + GqlVariable(name: 'apiToken', type: .string, req: true, value: apiToken), + ], + name: 'fetchLocators', + )..add( + GqlField(name: 'locators', args: {'apiToken': 'apiToken'}) + ..add(GqlField(name: 'status')) + ..add(GqlField(name: 'errors')) + ..add( + GqlField(name: 'result') + ..add(GqlField(name: 'id')) + ..add(GqlField(name: 'token')) + ..add(GqlField(name: 'isExpired')) + ..add(GqlField(name: 'expiresAt')) + ..add(GqlField(name: 'expiredBy', fragment: basicUserFields)) + ..add(GqlField(name: 'expiredById')) + ..add(GqlField(name: 'createdAt')) + ..add(GqlField(name: 'createdBy', fragment: basicUserFields)) + ..add(GqlField(name: 'createdById')) + ..add(GqlField(name: 'updatedAt')) + ..add(GqlField(name: 'updatedBy', fragment: basicUserFields)) + ..add(GqlField(name: 'updatedById')) + ..add(GqlField(name: 'description')) + ..add( + GqlField(name: 'customization') + ..add(GqlField(name: 'id')) + ..add(GqlField(name: 'name')) + ..add(GqlField(name: 'nickname')) + ..add(GqlField(name: 'technology')) + ..add(GqlField(name: 'sourceId')) + ..add( + GqlField(name: 'instances') + ..add(GqlField(name: 'id')) + ..add(GqlField(name: 'appId')) + ..add(GqlField(name: 'platform')) + ..add(GqlField(name: 'host')), + ), + ), + ), + ), + ); final data = response.data; if (data == null) { @@ -223,11 +278,17 @@ abstract class Locator with _$Locator { try { final response = await connector.perform( - query: expireGraphqlMutation, - variables: { - 'apiToken': apiToken, - 'ids': [id], - }, + GqlMutation( + variables: [ + GqlVariable(name: 'apiToken', type: .string, req: true, value: apiToken), + GqlVariable(name: 'ids', type: .list, listOf: .id, req: true, nestedRequired: true, value: [id]), + ], + name: 'expireLocator', + )..add( + GqlField(name: 'expireLocators', args: {'apiToken': 'apiToken', 'ids': 'ids'}) + ..add(GqlField(name: 'status')) + ..add(GqlField(name: 'errors')), + ), ); final data = response.data; @@ -276,8 +337,17 @@ abstract class Locator with _$Locator { try { final response = await connector.perform( - query: expireGraphqlMutation, - variables: {'apiToken': apiToken, 'ids': ids}, + GqlMutation( + variables: [ + GqlVariable(name: 'apiToken', type: .string, req: true, value: apiToken), + GqlVariable(name: 'ids', type: .list, listOf: .id, req: true, nestedRequired: true, value: ids), + ], + name: 'expireLocators', + )..add( + GqlField(name: 'expireLocators', args: {'apiToken': 'apiToken', 'ids': 'ids'}) + ..add(GqlField(name: 'status')) + ..add(GqlField(name: 'errors')), + ), ); final data = response.data; @@ -307,221 +377,113 @@ abstract class Locator with _$Locator { } } - /// [fetchSingleQuery] is the GraphQL query to fetch a single locator by its ID - /// It uses the [Locator.graphqlFragment] to get the locator data - static String get fetchSingleQuery => - '${Locator.graphqlFragment}' - r''' - query fetchLocators($apiToken: String!, $id: ID) { - locators(apiToken: $apiToken, id: $id) { - status - errors - result { - ...locatorFragment - } - } - } - '''; - - /// [fetchAllGraphqlQuery] is the GraphQL query to fetch all locators - /// It includes the basic user fields fragment [basicUserFields] to get the user data - /// It does not use the [Locator.graphqlFragment] to reduce the amount of data - static String get fetchAllGraphqlQuery => r''' - fragment basicUserFields on User { - id - name - dynamicAvatar { - type - icon - emoji - url - } - } - - query fetchLocators($apiToken: String!) { - locators(apiToken: $apiToken) { - status - errors - result { - id - token - - isExpired - - expiresAt - expiredBy { - ...basicUserFields - } - expiredById - - createdAt - createdBy { - ...basicUserFields - } - createdById - updatedAt - updatedBy { - ...basicUserFields - } - updatedById - - description - - customization { - id - name - nickname - technology - sourceId - - instances { - id - appId - platform - host - } - } - } - } - } - '''; - - /// [graphqlFragment] is the GraphQL fragment to fetch the locator data - /// It includes the basic user fields fragment [basicUserFields] to get the user data - static String get graphqlFragment => - ''' - ${RegisteredApp.registeredAppFragment} - - fragment basicUserFields on User { - id - name - dynamicAvatar { - type - icon - emoji - url - } - } - - fragment locatorFragment on Locator { - id - token - mqttConfig { - host - port - username - password - topic - } - - customization { - id - name - nickname - technology - sourceId - - instances { - id - appId - platform - host - } - } - - assets { - id - name - mode - dynamicIcon { - type - icon - emoji - url - } - } - assetsIds - geofences { - id - name - mode - color - } - geofencesIds - triggers { - id - name - code - color - kind - } - triggersIds - - mapLayerId - mapLayer { - id - name - source - } - - poisIds - pois { - id - name - latitude - longitude - } - - enableSidebar - showAssetsLabels - showGeofencesLabels - showPoisLabels - showAssetsTrails - boundary { - topleft { - latitude - longitude - } - bottomright { - latitude - longitude - } - } - description - - isExpired - - expiresAt - expiredBy { - ...basicUserFields - } - expiredById - - createdAt - createdBy { - ...basicUserFields - } - createdById - updatedAt - updatedBy { - ...basicUserFields - } - updatedById - - customization { - ...registeredAppFragment - } - customizationId - } - '''; - - /// [expireGraphqlMutation] is the GraphQL mutation to expire one or more locators by their IDs - static String get expireGraphqlMutation => r''' - mutation expireLocator($apiToken: String!, $ids: [ID!]!) { - expireLocators(apiToken: $apiToken, ids: $ids) { - status - errors - } - } - '''; + /// [basicUserFields] is a lightweight GqlFragment for user references within locator responses. + static GqlFragment get basicUserFields => GqlFragment(name: 'basicUserFields', onType: 'User') + ..add(GqlField(name: 'id')) + ..add(GqlField(name: 'name')) + ..add(GqlField(name: 'dynamicAvatar', fragment: Avatar.gqlFragment)); + + /// [gqlFragment] is the GqlFragment for a locator, including nested associations. + static GqlFragment get gqlFragment => GqlFragment(name: 'locatorFragment', onType: 'Locator') + ..add(GqlField(name: 'id')) + ..add(GqlField(name: 'token')) + ..add( + GqlField(name: 'mqttConfig') + ..add(GqlField(name: 'host')) + ..add(GqlField(name: 'port')) + ..add(GqlField(name: 'username')) + ..add(GqlField(name: 'password')) + ..add(GqlField(name: 'topic')), + ) + ..add( + GqlField(name: 'customization') + ..add(GqlField(name: 'id')) + ..add(GqlField(name: 'name')) + ..add(GqlField(name: 'nickname')) + ..add(GqlField(name: 'technology')) + ..add(GqlField(name: 'sourceId')) + ..add( + GqlField(name: 'instances') + ..add(GqlField(name: 'id')) + ..add(GqlField(name: 'appId')) + ..add(GqlField(name: 'platform')) + ..add(GqlField(name: 'host')), + ), + ) + ..add( + GqlField(name: 'assets') + ..add(GqlField(name: 'id')) + ..add(GqlField(name: 'name')) + ..add(GqlField(name: 'mode')) + ..add( + GqlField(name: 'dynamicIcon') + ..add(GqlField(name: 'type')) + ..add(GqlField(name: 'icon')) + ..add(GqlField(name: 'emoji')) + ..add(GqlField(name: 'url')), + ), + ) + ..add(GqlField(name: 'assetsIds')) + ..add( + GqlField(name: 'geofences') + ..add(GqlField(name: 'id')) + ..add(GqlField(name: 'name')) + ..add(GqlField(name: 'mode')) + ..add(GqlField(name: 'color')), + ) + ..add(GqlField(name: 'geofencesIds')) + ..add( + GqlField(name: 'triggers') + ..add(GqlField(name: 'id')) + ..add(GqlField(name: 'name')) + ..add(GqlField(name: 'code')) + ..add(GqlField(name: 'color')) + ..add(GqlField(name: 'kind')), + ) + ..add(GqlField(name: 'triggersIds')) + ..add(GqlField(name: 'mapLayerId')) + ..add( + GqlField(name: 'mapLayer') + ..add(GqlField(name: 'id')) + ..add(GqlField(name: 'name')) + ..add(GqlField(name: 'source')), + ) + ..add(GqlField(name: 'poisIds')) + ..add( + GqlField(name: 'pois') + ..add(GqlField(name: 'id')) + ..add(GqlField(name: 'name')) + ..add(GqlField(name: 'latitude')) + ..add(GqlField(name: 'longitude')), + ) + ..add(GqlField(name: 'enableSidebar')) + ..add(GqlField(name: 'showAssetsLabels')) + ..add(GqlField(name: 'showGeofencesLabels')) + ..add(GqlField(name: 'showPoisLabels')) + ..add(GqlField(name: 'showAssetsTrails')) + ..add( + GqlField(name: 'boundary') + ..add( + GqlField(name: 'topleft') + ..add(GqlField(name: 'latitude')) + ..add(GqlField(name: 'longitude')), + ) + ..add( + GqlField(name: 'bottomright') + ..add(GqlField(name: 'latitude')) + ..add(GqlField(name: 'longitude')), + ), + ) + ..add(GqlField(name: 'description')) + ..add(GqlField(name: 'isExpired')) + ..add(GqlField(name: 'expiresAt')) + ..add(GqlField(name: 'expiredBy', fragment: basicUserFields)) + ..add(GqlField(name: 'expiredById')) + ..add(GqlField(name: 'createdAt')) + ..add(GqlField(name: 'createdBy', fragment: basicUserFields)) + ..add(GqlField(name: 'createdById')) + ..add(GqlField(name: 'updatedAt')) + ..add(GqlField(name: 'updatedBy', fragment: basicUserFields)) + ..add(GqlField(name: 'updatedById')) + ..add(GqlField(name: 'customizationId')); } diff --git a/lib/src/locator/src/locator_input.dart b/lib/src/locator/src/locator_input.dart index 57ff2c92..e1a560e3 100644 --- a/lib/src/locator/src/locator_input.dart +++ b/lib/src/locator/src/locator_input.dart @@ -55,7 +55,7 @@ abstract class LocatorInput with _$LocatorInput { /// [simulateLink] simulates the locator link String simulateLink({RegisteredApp? customization}) { - final token = 'SIMULATED_TOKEN'; + const token = 'SIMULATED_TOKEN'; final webInstance = customization?.instances?.firstWhereOrNull((e) => e.platform == AppPlatform.web); final webHost = webInstance?.host ?? ''; if (webHost.isNotEmpty) return 'https://$webHost/#/$token'; @@ -76,10 +76,21 @@ abstract class LocatorInput with _$LocatorInput { void Function(String statusCode)? onResponse, }) async { final connector = LayrzConnector(uri: uri); + final opName = id == null ? 'addLocator' : 'editLocator'; try { final response = await connector.perform( - query: id == null ? addGraphqlMutation : editGraphqlMutation, - variables: {'apiToken': apiToken, 'data': toJson()}, + GqlMutation( + variables: [ + GqlVariable(name: 'apiToken', type: .string, req: true, value: apiToken), + GqlVariable(name: 'data', type: .input, req: true, inputName: 'LocatorInput', value: toJson()), + ], + name: opName, + )..add( + GqlField(name: opName, args: {'apiToken': 'apiToken', 'data': 'data'}) + ..add(GqlField(name: 'status')) + ..add(GqlField(name: 'errors')) + ..add(GqlField(name: 'result', fragment: Locator.gqlFragment)), + ), ); final data = response.data; @@ -89,7 +100,7 @@ abstract class LocatorInput with _$LocatorInput { return null; } - final result = id == null ? data['data']['addLocator'] : data['data']['editLocator']; + final result = data['data'][opName]; if (result == null) { onResponse?.call(ApiStatus.internalError.toJson()); Log.error("layrz_models/LocatorInput/save(): No result from server"); @@ -111,36 +122,4 @@ abstract class LocatorInput with _$LocatorInput { return null; } } - - /// [addGraphqlMutation] is the GraphQL mutation to add a locator - /// It uses the [Locator.graphqlFragment] to get the locator data - static String get addGraphqlMutation => - '${Locator.graphqlFragment}' - r''' - mutation addLocator($apiToken: String!, $data: LocatorInput!) { - addLocator(data: $data, apiToken: $apiToken) { - status - errors - result { - ...locatorFragment - } - } - } - '''; - - /// [editGraphqlMutation] is the GraphQL mutation to edit a locator - /// It uses the [Locator.graphqlFragment] to get the locator data - static String get editGraphqlMutation => - '${Locator.graphqlFragment}' - r''' - mutation editLocator($apiToken: String!, $data: LocatorInput!) { - editLocator(data: $data, apiToken: $apiToken) { - status - errors - result { - ...locatorFragment - } - } - } - '''; } diff --git a/lib/src/map/map.dart b/lib/src/map/map.dart index c8007be7..f0bf2944 100644 --- a/lib/src/map/map.dart +++ b/lib/src/map/map.dart @@ -7,7 +7,6 @@ import 'package:layrz_logging/layrz_logging.dart'; import 'package:layrz_models/src/access/access.dart'; import 'package:layrz_models/src/api/api.dart'; import 'package:layrz_models/src/converters/converters.dart'; -import 'package:layrz_models/src/utils/src/api_connector.dart'; // Freezed part 'map.freezed.dart'; diff --git a/lib/src/map/src/layer.dart b/lib/src/map/src/layer.dart index 92bf19d5..7e6eb1fc 100644 --- a/lib/src/map/src/layer.dart +++ b/lib/src/map/src/layer.dart @@ -90,8 +90,18 @@ abstract class MapLayer with _$MapLayer { final connector = LayrzConnector(uri: uri); try { final response = await connector.perform( - query: fetchSingleQuery, - variables: {'apiToken': apiToken, 'id': id}, + GqlQuery( + variables: [ + GqlVariable(name: 'apiToken', type: .string, req: true, value: apiToken), + GqlVariable(name: 'id', type: .id, value: id), + ], + name: 'mapLayers', + )..add( + GqlField(name: 'mapLayers', args: {'apiToken': 'apiToken', 'id': 'id'}) + ..add(GqlField(name: 'status')) + ..add(GqlField(name: 'errors')) + ..add(GqlField(name: 'result', fragment: gqlFragment)), + ), ); final data = response.data; @@ -140,8 +150,22 @@ abstract class MapLayer with _$MapLayer { final connector = LayrzConnector(uri: uri); try { final response = await connector.perform( - query: fetchAllGraphqlQuery, - variables: {'apiToken': apiToken}, + GqlQuery( + variables: [ + GqlVariable(name: 'apiToken', type: .string, req: true, value: apiToken), + ], + name: 'mapLayers', + )..add( + GqlField(name: 'mapLayers', args: {'apiToken': 'apiToken'}) + ..add(GqlField(name: 'status')) + ..add(GqlField(name: 'errors')) + ..add( + GqlField(name: 'result') + ..add(GqlField(name: 'id')) + ..add(GqlField(name: 'name')) + ..add(GqlField(name: 'source')), + ), + ), ); final data = response.data; @@ -192,8 +216,17 @@ abstract class MapLayer with _$MapLayer { final connector = LayrzConnector(uri: uri); try { final response = await connector.perform( - query: deleteMapLayers, - variables: {'apiToken': apiToken, 'ids': ids}, + GqlMutation( + variables: [ + GqlVariable(name: 'apiToken', type: .string, req: true, value: apiToken), + GqlVariable(name: 'ids', type: .list, listOf: .id, req: true, nestedRequired: true, value: ids), + ], + name: 'deleteMapLayers', + )..add( + GqlField(name: 'deleteMapLayers', args: {'apiToken': 'apiToken', 'ids': 'ids'}) + ..add(GqlField(name: 'status')) + ..add(GqlField(name: 'errors')), + ), ); final data = response.data; @@ -223,69 +256,24 @@ abstract class MapLayer with _$MapLayer { } } - /// [fetchSingleQuery] is the GraphQL query to fetch a single [MapLayer] by its ID - /// It uses the [MapLayer.graphqlFragment] to get the map layer data - static String get fetchSingleQuery => - '${MapLayer.graphqlFragment}' - r''' - query mapLayers($apiToken: String!, $id: ID) { - mapLayers(apiToken: $apiToken, id: $id) { - status - errors - result { - ...mapLayerFragment - } - } - } - '''; - - /// [fetchAllGraphqlQuery] is the GraphQL query to fetch all [MapLayer]s - /// It uses a lighter subset of fields to reduce the amount of data transferred - static String get fetchAllGraphqlQuery => r''' - query mapLayers($apiToken: String!) { - mapLayers(apiToken: $apiToken) { - status - errors - result { - id - name - source - } - } - } - '''; - - /// [graphqlFragment] is the GraphQL fragment to fetch the [MapLayer] data - static String get graphqlFragment => ''' - fragment mapLayerFragment on MapLayer { - id - name - source - rasterServerLight - rasterServerDark - googleToken - googleLayers - mapboxToken - mapboxLayers - mapboxCustomUsername - mapboxCustomStyleId - hereToken - hereLayers - attributionUrl - attributionUrlDark - attributionWidth - attributionHeight - appsIds - } - '''; - - /// [deleteMapLayers] is the GraphQL mutation to delete one or more [MapLayer]s by their IDs - static String get deleteMapLayers => r''' - mutation deleteMapLayers($apiToken: String!, $ids: [ID!]!) { - deleteMapLayers(apiToken: $apiToken, ids: $ids) { - status - errors - } - } - '''; + /// [gqlFragment] is the GqlFragment for a [MapLayer]. + static GqlFragment get gqlFragment => GqlFragment(name: 'mapLayerFragment', onType: 'MapLayer') + ..add(GqlField(name: 'id')) + ..add(GqlField(name: 'name')) + ..add(GqlField(name: 'source')) + ..add(GqlField(name: 'rasterServerLight')) + ..add(GqlField(name: 'rasterServerDark')) + ..add(GqlField(name: 'googleToken')) + ..add(GqlField(name: 'googleLayers')) + ..add(GqlField(name: 'mapboxToken')) + ..add(GqlField(name: 'mapboxLayers')) + ..add(GqlField(name: 'mapboxCustomUsername')) + ..add(GqlField(name: 'mapboxCustomStyleId')) + ..add(GqlField(name: 'hereToken')) + ..add(GqlField(name: 'hereLayers')) + ..add(GqlField(name: 'attributionUrl')) + ..add(GqlField(name: 'attributionUrlDark')) + ..add(GqlField(name: 'attributionWidth')) + ..add(GqlField(name: 'attributionHeight')) + ..add(GqlField(name: 'appsIds')); } diff --git a/lib/src/map/src/layer_input.dart b/lib/src/map/src/layer_input.dart index 0b4277fc..fd272c53 100644 --- a/lib/src/map/src/layer_input.dart +++ b/lib/src/map/src/layer_input.dart @@ -90,10 +90,21 @@ abstract class MapLayerInput with _$MapLayerInput { void Function(String statusCode)? onResponse, }) async { final connector = LayrzConnector(uri: uri); + final opName = id == null ? 'addMapLayer' : 'editMapLayer'; try { final response = await connector.perform( - query: id == null ? addGraphqlMutation : editGraphqlMutation, - variables: {'apiToken': apiToken, 'data': toJson()}, + GqlMutation( + variables: [ + GqlVariable(name: 'apiToken', type: .string, req: true, value: apiToken), + GqlVariable(name: 'data', type: .input, req: true, inputName: 'MapLayerInput', value: toJson()), + ], + name: opName, + )..add( + GqlField(name: opName, args: {'apiToken': 'apiToken', 'data': 'data'}) + ..add(GqlField(name: 'status')) + ..add(GqlField(name: 'errors')) + ..add(GqlField(name: 'result', fragment: MapLayer.gqlFragment)), + ), ); final data = response.data; @@ -103,7 +114,7 @@ abstract class MapLayerInput with _$MapLayerInput { return null; } - final result = id == null ? data['data']['addMapLayer'] : data['data']['editMapLayer']; + final result = data['data'][opName]; if (result == null) { onResponse?.call(ApiStatus.internalError.toJson()); Log.error("layrz_models/MapLayerInput/save(): No result from server"); @@ -125,36 +136,4 @@ abstract class MapLayerInput with _$MapLayerInput { return null; } } - - /// [addGraphqlMutation] is the GraphQL mutation to add a [MapLayer] - /// It uses the [MapLayer.graphqlFragment] to get the map layer data - static String get addGraphqlMutation => - '${MapLayer.graphqlFragment}' - r''' - mutation addMapLayer($apiToken: String!, $data: MapLayerInput!) { - addMapLayer(data: $data, apiToken: $apiToken) { - status - errors - result { - ...mapLayerFragment - } - } - } - '''; - - /// [editGraphqlMutation] is the GraphQL mutation to edit a [MapLayer] - /// It uses the [MapLayer.graphqlFragment] to get the map layer data - static String get editGraphqlMutation => - '${MapLayer.graphqlFragment}' - r''' - mutation editMapLayer($apiToken: String!, $data: MapLayerInput!) { - editMapLayer(data: $data, apiToken: $apiToken) { - status - errors - result { - ...mapLayerFragment - } - } - } - '''; } diff --git a/lib/src/map/src/poi.dart b/lib/src/map/src/poi.dart index b9c1285f..747531fd 100644 --- a/lib/src/map/src/poi.dart +++ b/lib/src/map/src/poi.dart @@ -46,7 +46,20 @@ abstract class Poi with _$Poi { }) async { final connector = LayrzConnector(uri: uri); try { - final response = await connector.perform(query: fetchSingleQuery, variables: {'apiToken': apiToken, 'id': id}); + final response = await connector.perform( + GqlQuery( + variables: [ + GqlVariable(name: 'apiToken', type: .string, req: true, value: apiToken), + GqlVariable(name: 'id', type: .id, value: id), + ], + name: 'pois', + )..add( + GqlField(name: 'pois', args: {'apiToken': 'apiToken', 'id': 'id'}) + ..add(GqlField(name: 'status')) + ..add(GqlField(name: 'errors')) + ..add(GqlField(name: 'result', fragment: gqlFragment)), + ), + ); final data = response.data; if (data == null) { @@ -94,7 +107,24 @@ abstract class Poi with _$Poi { }) async { final connector = LayrzConnector(uri: uri); try { - final response = await connector.perform(query: fetchAllGraphqlQuery, variables: {'apiToken': apiToken}); + final response = await connector.perform( + GqlQuery( + variables: [ + GqlVariable(name: 'apiToken', type: .string, req: true, value: apiToken), + ], + name: 'pois', + )..add( + GqlField(name: 'pois', args: {'apiToken': 'apiToken'}) + ..add(GqlField(name: 'status')) + ..add(GqlField(name: 'errors')) + ..add( + GqlField(name: 'result') + ..add(GqlField(name: 'id')) + ..add(GqlField(name: 'name')) + ..add(GqlField(name: 'icon')), + ), + ), + ); final data = response.data; if (data == null) { @@ -145,8 +175,17 @@ abstract class Poi with _$Poi { try { final response = await connector.perform( - query: deletePois, - variables: {'apiToken': apiToken, 'ids': ids}, + GqlMutation( + variables: [ + GqlVariable(name: 'apiToken', type: .string, req: true, value: apiToken), + GqlVariable(name: 'ids', type: .list, listOf: .id, req: true, nestedRequired: true, value: ids), + ], + name: 'deletePois', + )..add( + GqlField(name: 'deletePois', args: {'apiToken': 'apiToken', 'ids': 'ids'}) + ..add(GqlField(name: 'status')) + ..add(GqlField(name: 'errors')), + ), ); final data = response.data; @@ -176,65 +215,13 @@ abstract class Poi with _$Poi { } } - /// [fetchSingleQuery] is the GraphQL query to fetch a single POI by its ID - /// It uses the [Poi.graphqlFragment] to get the POI data - static String get fetchSingleQuery => - '${Poi.graphqlFragment}' - r''' - query pois($apiToken: String!, $id: ID) { - pois(apiToken: $apiToken, id: $id) { - status - errors - result { - ...poiFragment - } - } - } - '''; - - /// [fetchAllGraphqlQuery] is the GraphQL query to fetch all POIs - /// It includes the basic user fields fragment [basicUserFields] to get the user data - /// It does not use the [Poi.graphqlFragment] to reduce the amount of data - static String get fetchAllGraphqlQuery => r''' - query pois($apiToken: String!) { - pois(apiToken: $apiToken) { - status - errors - result { - id - name - icon - } - } - } - '''; - - /// [graphqlFragment] is the GraphQL fragment to fetch the POI data - /// It includes the basic user fields fragment [basicUserFields] to get the user data - static String get graphqlFragment => - ''' - ${Access.graphqlUuidFragment} - fragment poiFragment on Poi { - id - name - description - icon - latitude - longitude - - access { - ...accessUuidFragment - } - } - '''; - - /// [deletePois] is the GraphQL mutation to delete one or more POIs by their IDs - static String get deletePois => r''' - mutation deletePois($apiToken: String!, $ids: [ID!]!) { - deletePois(apiToken: $apiToken, ids: $ids) { - status - errors - } - } - '''; + /// [gqlFragment] is the GqlFragment for a POI, including access permissions. + static GqlFragment get gqlFragment => GqlFragment(name: 'poiFragment', onType: 'Poi') + ..add(GqlField(name: 'id')) + ..add(GqlField(name: 'name')) + ..add(GqlField(name: 'description')) + ..add(GqlField(name: 'icon')) + ..add(GqlField(name: 'latitude')) + ..add(GqlField(name: 'longitude')) + ..add(GqlField(name: 'access', fragment: Access.graphqlUuidFragment)); } diff --git a/lib/src/map/src/poi_input.dart b/lib/src/map/src/poi_input.dart index 7f54ca05..31e36220 100644 --- a/lib/src/map/src/poi_input.dart +++ b/lib/src/map/src/poi_input.dart @@ -43,10 +43,21 @@ abstract class PoiInput with _$PoiInput { void Function(String statusCode)? onResponse, }) async { final connector = LayrzConnector(uri: uri); + final opName = id == null ? 'addPoi' : 'editPoi'; try { final response = await connector.perform( - query: id == null ? addGraphqlMutation : editGraphqlMutation, - variables: {'apiToken': apiToken, 'data': toJson()}, + GqlMutation( + variables: [ + GqlVariable(name: 'apiToken', type: .string, req: true, value: apiToken), + GqlVariable(name: 'data', type: .input, req: true, inputName: 'PoiInput', value: toJson()), + ], + name: opName, + )..add( + GqlField(name: opName, args: {'apiToken': 'apiToken', 'data': 'data'}) + ..add(GqlField(name: 'status')) + ..add(GqlField(name: 'errors')) + ..add(GqlField(name: 'result', fragment: Poi.gqlFragment)), + ), ); final data = response.data; @@ -56,7 +67,7 @@ abstract class PoiInput with _$PoiInput { return null; } - final result = id == null ? data['data']['addPoi'] : data['data']['editPoi']; + final result = data['data'][opName]; if (result == null) { onResponse?.call(ApiStatus.internalError.toJson()); Log.error("layrz_models/PoiInput/save(): No result from server"); @@ -78,36 +89,4 @@ abstract class PoiInput with _$PoiInput { return null; } } - - /// [addGraphqlMutation] is the GraphQL mutation to add a POI - /// It uses the [Poi.graphqlFragment] to get the POI data - static String get addGraphqlMutation => - '${Poi.graphqlFragment}' - r''' - mutation addPoi($apiToken: String!, $data: PoiInput!) { - addPoi(data: $data, apiToken: $apiToken) { - status - errors - result { - ...poiFragment - } - } - } - '''; - - /// [editGraphqlMutation] is the GraphQL mutation to edit a POI - /// It uses the [Poi.graphqlFragment] to get the POI data - static String get editGraphqlMutation => - '${Poi.graphqlFragment}' - r''' - mutation editPoi($apiToken: String!, $data: PoiInput!) { - editPoi(data: $data, apiToken: $apiToken) { - status - errors - result { - ...poiFragment - } - } - } - '''; } diff --git a/lib/src/token/src/token.dart b/lib/src/token/src/token.dart index 140643a7..f2dae610 100644 --- a/lib/src/token/src/token.dart +++ b/lib/src/token/src/token.dart @@ -39,8 +39,17 @@ abstract class Token with _$Token { final connector = LayrzConnector(uri: uri); try { final response = await connector.perform( - query: fetchAllGraphqlQuery, - variables: {'apiToken': apiToken}, + GqlQuery( + variables: [ + GqlVariable(name: 'apiToken', type: .string, req: true, value: apiToken), + ], + name: 'fetchTokens', + )..add( + GqlField(name: 'tokens', args: {'apiToken': 'apiToken'}) + ..add(GqlField(name: 'status')) + ..add(GqlField(name: 'errors')) + ..add(GqlField(name: 'result', fragment: gqlFragment)), + ), ); final data = response.data; @@ -89,11 +98,17 @@ abstract class Token with _$Token { try { final response = await connector.perform( - query: expireGraphqlMutation, - variables: { - 'apiToken': apiToken, - 'tokenToExpire': token, - }, + GqlMutation( + variables: [ + GqlVariable(name: 'apiToken', type: .string, req: true, value: apiToken), + GqlVariable(name: 'tokenToExpire', type: .string, req: true, value: token), + ], + name: 'expireToken', + )..add( + GqlField(name: 'expireToken', args: {'apiToken': 'apiToken', 'tokenToExpire': 'tokenToExpire'}) + ..add(GqlField(name: 'status')) + ..add(GqlField(name: 'errors')), + ), ); final data = response.data; @@ -144,11 +159,18 @@ abstract class Token with _$Token { try { final response = await connector.perform( - query: createGraphqlMutation, - variables: { - 'apiToken': apiToken, - 'duration': duration?.inSeconds, - }, + GqlMutation( + variables: [ + GqlVariable(name: 'apiToken', type: .string, req: true, value: apiToken), + GqlVariable(name: 'duration', type: .int, value: duration?.inSeconds), + ], + name: 'createToken', + )..add( + GqlField(name: 'createToken', args: {'apiToken': 'apiToken', 'duration': 'duration'}) + ..add(GqlField(name: 'status')) + ..add(GqlField(name: 'errors')) + ..add(GqlField(name: 'result', fragment: gqlFragment)), + ), ); final data = response.data; @@ -178,55 +200,11 @@ abstract class Token with _$Token { } } - /// [fragment] is the GraphQL fragment for the token model, which can be used in - /// GraphQL queries and mutations to specify the fields to retrieve or manipulate. - static String get fragment => r''' - fragment tokenFragment on Token { - id - token - validBefore - issuedAt - audience - } - '''; - - /// [fetchAllGraphqlQuery] is the GraphQL query to fetch all tokens for the authenticated user. - static String get fetchAllGraphqlQuery => - '$fragment' - r''' - query ($apiToken: String!) { - tokens(apiToken: $apiToken) { - status - errors - result { - ...tokenFragment - } - } - } - '''; - - /// [expireGraphqlMutation] is the GraphQL mutation to expire a token immediately. - static String get expireGraphqlMutation => r''' - mutation ($apiToken: String!, $tokenToExpire: String!) { - expireToken(apiToken: $apiToken, tokenToExpire: $tokenToExpire) { - status - errors - } - } - '''; - - /// [createGraphqlMutation] is the GraphQL mutation to create a new token. - static String get createGraphqlMutation => - '$fragment' - r''' - mutation ($apiToken: String!, $duration: Duration) { - createToken(apiToken: $apiToken, duration: $duration) { - status - errors - result { - ...tokenFragment - } - } - } - '''; + /// [gqlFragment] is the GqlFragment for a token. + static GqlFragment get gqlFragment => GqlFragment(name: 'tokenFragment', onType: 'Token') + ..add(GqlField(name: 'id')) + ..add(GqlField(name: 'token')) + ..add(GqlField(name: 'validBefore')) + ..add(GqlField(name: 'issuedAt')) + ..add(GqlField(name: 'audience')); } diff --git a/lib/src/token/token.dart b/lib/src/token/token.dart index b7193fad..79e1e32e 100644 --- a/lib/src/token/token.dart +++ b/lib/src/token/token.dart @@ -5,7 +5,6 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:layrz_logging/layrz_logging.dart'; import 'package:layrz_models/src/api/api.dart'; import 'package:layrz_models/src/converters/converters.dart'; -import 'package:layrz_models/src/utils/src/api_connector.dart'; part 'token.freezed.dart'; part 'token.g.dart'; diff --git a/lib/src/users/src/user.dart b/lib/src/users/src/user.dart index df6ccb7a..4f88b849 100644 --- a/lib/src/users/src/user.dart +++ b/lib/src/users/src/user.dart @@ -156,6 +156,67 @@ abstract class User with _$User { }) = _User; factory User.fromJson(Map json) => _$UserFromJson(json); + + /// [gqlFragment] is the lightweight GqlFragment for a user, suitable for listings and pickers. + static final GqlFragment gqlFragment = GqlFragment(name: 'userFragment', onType: 'User') + ..add(GqlField(name: 'id')) + ..add(GqlField(name: 'name')) + ..add(GqlField(name: 'dynamicAvatar', fragment: Avatar.gqlFragment)); + + /// [buildFetchAllQuery] builds the GqlQuery to fetch all users with listing-level fields. + static GqlQuery buildFetchAllQuery({required String apiToken}) { + return GqlQuery( + name: 'users', + variables: [ + GqlVariable(name: 'apiToken', type: GqlVariableType.string, req: true, value: apiToken), + ], + )..add( + GqlField(name: 'users', args: {'apiToken': 'apiToken'}) + ..add(GqlField(name: 'status')) + ..add(GqlField(name: 'errors')) + ..add(GqlField(name: 'result', fragment: gqlFragment)), + ); + } + + /// [fetchAll] fetches all users from the API with a lightweight payload. + static Future> fetchAll({ + required String apiToken, + required Uri uri, + void Function(String statusCode)? onResponse, + }) async { + final connector = LayrzConnector(uri: uri); + try { + final response = await connector.perform(buildFetchAllQuery(apiToken: apiToken)); + + final data = response.data; + if (data == null) { + onResponse?.call(ApiStatus.internalError.toJson()); + Log.error("layrz_models/User/fetchAll(): No response from server"); + return []; + } + + final result = data['data']['users']; + if (result == null) { + onResponse?.call(ApiStatus.internalError.toJson()); + Log.error("layrz_models/User/fetchAll(): No result from server"); + return []; + } + + final status = ApiStatus.fromJson(result['status']); + if (status != ApiStatus.ok) { + onResponse?.call(status.toJson()); + return []; + } + + return (result['result'] as List?) + ?.map((e) => User.fromJson(Map.from(e as Map))) + .toList() ?? + []; + } catch (e, stack) { + Log.critical("layrz_models/User/fetchAll(): General exception => $e\n$stack"); + return []; + } + } } @unfreezed diff --git a/lib/src/users/users.dart b/lib/src/users/users.dart index 955feb86..ed81cd7b 100644 --- a/lib/src/users/users.dart +++ b/lib/src/users/users.dart @@ -2,6 +2,7 @@ library; import 'package:collection/collection.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:layrz_logging/layrz_logging.dart'; import 'package:layrz_models/layrz_models.dart'; part 'users.freezed.dart'; diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart index 4c3fbd7e..e79fda78 100644 --- a/lib/src/utils/utils.dart +++ b/lib/src/utils/utils.dart @@ -4,7 +4,6 @@ import 'dart:math'; import 'package:flutter/material.dart' show Color, Colors; -export 'src/api_connector.dart'; part 'src/colors.dart'; part 'src/layrz_number.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index fbe3cdc0..27e0dc58 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ description: Layrz API models for Dart/Flutter. This package contains the models used by the Layrz API. name: layrz_models -version: "3.7.10" +version: "3.8.0" repository: https://github.com/goldenm-software/layrz_models environment: