From 0db4c0f80d8096749fcbfcbaaaad3828bbfddc4d Mon Sep 17 00:00:00 2001 From: stopflock Date: Fri, 24 Oct 2025 17:31:04 -0500 Subject: [PATCH] all profiles as lists, better handling, stop using a fallback profile for broken submissions without one --- lib/models/node_profile.dart | 294 +++++++++++++-------------- lib/models/operator_profile.dart | 9 - lib/models/pending_upload.dart | 2 +- lib/state/profile_state.dart | 14 +- test/models/pending_upload_test.dart | 83 -------- 5 files changed, 143 insertions(+), 259 deletions(-) delete mode 100644 test/models/pending_upload_test.dart diff --git a/lib/models/node_profile.dart b/lib/models/node_profile.dart index 92caa7f..c00e197 100644 --- a/lib/models/node_profile.dart +++ b/lib/models/node_profile.dart @@ -20,161 +20,149 @@ class NodeProfile { this.editable = true, }); - /// Built‑in default: Generic ALPR camera (customizable template, not submittable) - factory NodeProfile.genericAlpr() => NodeProfile( - id: 'builtin-generic-alpr', - name: 'Generic ALPR', - tags: const { - 'man_made': 'surveillance', - 'surveillance:type': 'ALPR', - }, - builtin: true, - requiresDirection: true, - submittable: false, - editable: false, - ); + /// Get all built-in default node profiles + static List getDefaults() => [ + NodeProfile( + id: 'builtin-generic-alpr', + name: 'Generic ALPR', + tags: const { + 'man_made': 'surveillance', + 'surveillance:type': 'ALPR', + }, + builtin: true, + requiresDirection: true, + submittable: false, + editable: false, + ), + NodeProfile( + id: 'builtin-flock', + name: 'Flock', + tags: const { + 'man_made': 'surveillance', + 'surveillance': 'public', + 'surveillance:type': 'ALPR', + 'surveillance:zone': 'traffic', + 'camera:type': 'fixed', + 'manufacturer': 'Flock Safety', + 'manufacturer:wikidata': 'Q108485435', + }, + builtin: true, + requiresDirection: true, + submittable: true, + editable: true, + ), + NodeProfile( + id: 'builtin-motorola', + name: 'Motorola/Vigilant', + tags: const { + 'man_made': 'surveillance', + 'surveillance': 'public', + 'surveillance:type': 'ALPR', + 'surveillance:zone': 'traffic', + 'camera:type': 'fixed', + 'manufacturer': 'Motorola Solutions', + 'manufacturer:wikidata': 'Q634815', + }, + builtin: true, + requiresDirection: true, + submittable: true, + editable: true, + ), + NodeProfile( + id: 'builtin-genetec', + name: 'Genetec', + tags: const { + 'man_made': 'surveillance', + 'surveillance': 'public', + 'surveillance:type': 'ALPR', + 'surveillance:zone': 'traffic', + 'camera:type': 'fixed', + 'manufacturer': 'Genetec', + 'manufacturer:wikidata': 'Q30295174', + }, + builtin: true, + requiresDirection: true, + submittable: true, + editable: true, + ), + NodeProfile( + id: 'builtin-leonardo', + name: 'Leonardo/ELSAG', + tags: const { + 'man_made': 'surveillance', + 'surveillance': 'public', + 'surveillance:type': 'ALPR', + 'surveillance:zone': 'traffic', + 'camera:type': 'fixed', + 'manufacturer': 'Leonardo', + 'manufacturer:wikidata': 'Q910379', + }, + builtin: true, + requiresDirection: true, + submittable: true, + editable: true, + ), + NodeProfile( + id: 'builtin-neology', + name: 'Neology', + tags: const { + 'man_made': 'surveillance', + 'surveillance': 'public', + 'surveillance:type': 'ALPR', + 'surveillance:zone': 'traffic', + 'camera:type': 'fixed', + 'manufacturer': 'Neology, Inc.', + }, + builtin: true, + requiresDirection: true, + submittable: true, + editable: true, + ), + NodeProfile( + id: 'builtin-generic-gunshot', + name: 'Generic Gunshot Detector', + tags: const { + 'man_made': 'surveillance', + 'surveillance:type': 'gunshot_detector', + }, + builtin: true, + requiresDirection: false, + submittable: false, + editable: false, + ), + NodeProfile( + id: 'builtin-shotspotter', + name: 'ShotSpotter', + tags: const { + 'man_made': 'surveillance', + 'surveillance': 'public', + 'surveillance:type': 'gunshot_detector', + 'surveillance:brand': 'ShotSpotter', + 'surveillance:brand:wikidata': 'Q107740188', + }, + builtin: true, + requiresDirection: false, + submittable: true, + editable: true, + ), + NodeProfile( + id: 'builtin-flock-raven', + name: 'Flock Raven', + tags: const { + 'man_made': 'surveillance', + 'surveillance': 'public', + 'surveillance:type': 'gunshot_detector', + 'brand': 'Flock Safety', + 'brand:wikidata': 'Q108485435', + }, + builtin: true, + requiresDirection: false, + submittable: true, + editable: true, + ), + ]; - /// Built‑in: Flock Safety ALPR camera - factory NodeProfile.flock() => NodeProfile( - id: 'builtin-flock', - name: 'Flock', - tags: const { - 'man_made': 'surveillance', - 'surveillance': 'public', - 'surveillance:type': 'ALPR', - 'surveillance:zone': 'traffic', - 'camera:type': 'fixed', - 'manufacturer': 'Flock Safety', - 'manufacturer:wikidata': 'Q108485435', - }, - builtin: true, - requiresDirection: true, - submittable: true, - editable: true, - ); - /// Built‑in: Motorola Solutions/Vigilant ALPR camera - factory NodeProfile.motorola() => NodeProfile( - id: 'builtin-motorola', - name: 'Motorola/Vigilant', - tags: const { - 'man_made': 'surveillance', - 'surveillance': 'public', - 'surveillance:type': 'ALPR', - 'surveillance:zone': 'traffic', - 'camera:type': 'fixed', - 'manufacturer': 'Motorola Solutions', - 'manufacturer:wikidata': 'Q634815', - }, - builtin: true, - requiresDirection: true, - submittable: true, - editable: true, - ); - - /// Built‑in: Genetec ALPR camera - factory NodeProfile.genetec() => NodeProfile( - id: 'builtin-genetec', - name: 'Genetec', - tags: const { - 'man_made': 'surveillance', - 'surveillance': 'public', - 'surveillance:type': 'ALPR', - 'surveillance:zone': 'traffic', - 'camera:type': 'fixed', - 'manufacturer': 'Genetec', - 'manufacturer:wikidata': 'Q30295174', - }, - builtin: true, - requiresDirection: true, - submittable: true, - editable: true, - ); - - /// Built‑in: Leonardo/ELSAG ALPR camera - factory NodeProfile.leonardo() => NodeProfile( - id: 'builtin-leonardo', - name: 'Leonardo/ELSAG', - tags: const { - 'man_made': 'surveillance', - 'surveillance': 'public', - 'surveillance:type': 'ALPR', - 'surveillance:zone': 'traffic', - 'camera:type': 'fixed', - 'manufacturer': 'Leonardo', - 'manufacturer:wikidata': 'Q910379', - }, - builtin: true, - requiresDirection: true, - submittable: true, - editable: true, - ); - - /// Built‑in: Neology ALPR camera - factory NodeProfile.neology() => NodeProfile( - id: 'builtin-neology', - name: 'Neology', - tags: const { - 'man_made': 'surveillance', - 'surveillance': 'public', - 'surveillance:type': 'ALPR', - 'surveillance:zone': 'traffic', - 'camera:type': 'fixed', - 'manufacturer': 'Neology, Inc.', - }, - builtin: true, - requiresDirection: true, - submittable: true, - editable: true, - ); - - /// Built‑in: Generic gunshot detector (customizable template, not submittable) - factory NodeProfile.genericGunshotDetector() => NodeProfile( - id: 'builtin-generic-gunshot', - name: 'Generic Gunshot Detector', - tags: const { - 'man_made': 'surveillance', - 'surveillance:type': 'gunshot_detector', - }, - builtin: true, - requiresDirection: false, - submittable: false, - editable: false, - ); - - /// Built‑in: ShotSpotter gunshot detector - factory NodeProfile.shotspotter() => NodeProfile( - id: 'builtin-shotspotter', - name: 'ShotSpotter', - tags: const { - 'man_made': 'surveillance', - 'surveillance': 'public', - 'surveillance:type': 'gunshot_detector', - 'surveillance:brand': 'ShotSpotter', - 'surveillance:brand:wikidata': 'Q107740188', - }, - builtin: true, - requiresDirection: false, - submittable: true, - editable: true, - ); - - /// Built‑in: Flock Raven gunshot detector - factory NodeProfile.flockRaven() => NodeProfile( - id: 'builtin-flock-raven', - name: 'Flock Raven', - tags: const { - 'man_made': 'surveillance', - 'surveillance': 'public', - 'surveillance:type': 'gunshot_detector', - 'brand': 'Flock Safety', - 'brand:wikidata': 'Q108485435', - }, - builtin: true, - requiresDirection: false, - submittable: true, - editable: true, - ); /// Returns true if this profile can be used for submissions bool get isSubmittable => submittable; diff --git a/lib/models/operator_profile.dart b/lib/models/operator_profile.dart index 0ed463f..96763d0 100644 --- a/lib/models/operator_profile.dart +++ b/lib/models/operator_profile.dart @@ -44,15 +44,6 @@ class OperatorProfile { ), ]; - /// Built-in default: Lowe's operator profile - factory OperatorProfile.lowes() => getDefaults()[0]; - - /// Built-in default: The Home Depot operator profile - factory OperatorProfile.homeDepot() => getDefaults()[1]; - - /// Built-in default: Simon Property Group operator profile - factory OperatorProfile.simonPropertyGroup() => getDefaults()[2]; - OperatorProfile copyWith({ String? id, String? name, diff --git a/lib/models/pending_upload.dart b/lib/models/pending_upload.dart index 776f05e..e99be93 100644 --- a/lib/models/pending_upload.dart +++ b/lib/models/pending_upload.dart @@ -91,7 +91,7 @@ class PendingUpload { direction: j['dir'], profile: j['profile'] is Map ? NodeProfile.fromJson(j['profile']) - : NodeProfile.genericAlpr(), + : throw Exception('PendingUpload missing required profile data - this should never happen'), operatorProfile: j['operatorProfile'] != null ? OperatorProfile.fromJson(j['operatorProfile']) : null, diff --git a/lib/state/profile_state.dart b/lib/state/profile_state.dart index 30ceccd..8cbdc13 100644 --- a/lib/state/profile_state.dart +++ b/lib/state/profile_state.dart @@ -23,19 +23,7 @@ class ProfileState extends ChangeNotifier { // Add built-in profiles if this is first launch if (addDefaults) { - final builtinProfiles = [ - NodeProfile.genericAlpr(), - NodeProfile.flock(), - NodeProfile.motorola(), - NodeProfile.genetec(), - NodeProfile.leonardo(), - NodeProfile.neology(), - NodeProfile.genericGunshotDetector(), - NodeProfile.shotspotter(), - NodeProfile.flockRaven(), - ]; - - _profiles.addAll(builtinProfiles); + _profiles.addAll(NodeProfile.getDefaults()); await ProfileService().save(_profiles); } diff --git a/test/models/pending_upload_test.dart b/test/models/pending_upload_test.dart deleted file mode 100644 index 16e3a24..0000000 --- a/test/models/pending_upload_test.dart +++ /dev/null @@ -1,83 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:latlong2/latlong.dart'; -import 'package:deflockapp/models/pending_upload.dart'; -import 'package:deflockapp/models/node_profile.dart'; -import 'package:deflockapp/state/settings_state.dart'; - -void main() { - group('PendingUpload', () { - test('should serialize and deserialize upload mode correctly', () { - // Test each upload mode - final testModes = [ - UploadMode.production, - UploadMode.sandbox, - UploadMode.simulate, - ]; - - for (final mode in testModes) { - final original = PendingUpload( - coord: LatLng(37.7749, -122.4194), - direction: 90.0, - profile: NodeProfile.flock(), - uploadMode: mode, - ); - - // Serialize to JSON - final json = original.toJson(); - - // Deserialize from JSON - final restored = PendingUpload.fromJson(json); - - // Verify upload mode is preserved - expect(restored.uploadMode, equals(mode)); - expect(restored.uploadModeDisplayName, equals(original.uploadModeDisplayName)); - - // Verify other fields too - expect(restored.coord.latitude, equals(original.coord.latitude)); - expect(restored.coord.longitude, equals(original.coord.longitude)); - expect(restored.direction, equals(original.direction)); - expect(restored.profile.id, equals(original.profile.id)); - } - }); - - test('should handle legacy JSON without uploadMode', () { - // Simulate old JSON format without uploadMode field - final legacyJson = { - 'lat': 37.7749, - 'lon': -122.4194, - 'dir': 90.0, - 'profile': NodeProfile.flock().toJson(), - 'originalNodeId': null, - 'attempts': 0, - 'error': false, - // Note: no 'uploadMode' field - }; - - final upload = PendingUpload.fromJson(legacyJson); - - // Should default to production mode for legacy entries - expect(upload.uploadMode, equals(UploadMode.production)); - expect(upload.uploadModeDisplayName, equals('Production')); - }); - - test('should correctly identify edits vs new cameras', () { - final newCamera = PendingUpload( - coord: LatLng(37.7749, -122.4194), - direction: 90.0, - profile: NodeProfile.flock(), - uploadMode: UploadMode.production, - ); - - final editCamera = PendingUpload( - coord: LatLng(37.7749, -122.4194), - direction: 90.0, - profile: CameraProfile.flock(), - uploadMode: UploadMode.production, - originalNodeId: 12345, - ); - - expect(newCamera.isEdit, isFalse); - expect(editCamera.isEdit, isTrue); - }); - }); -} \ No newline at end of file