From a60d3610c6fa766cb6b9e5e0b33caa0aacc50e63 Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Sun, 8 Sep 2024 20:40:37 +0200 Subject: [PATCH] fix: Improve database imports --- .../mocks/services/network/data_manager.dart | 6 ++ .../lib/services/network/data_manager.dart | 2 + .../lib/services/network/remote/rest.dart | 15 ++++ .../view/screens/overview/shared/actions.dart | 3 + .../team_match/team_match_overview.dart | 3 + .../lib/src/data/membership.freezed.dart | 6 +- .../src/data/team_match/team_match_bout.dart | 9 +- .../team_match/team_match_bout.freezed.dart | 63 +++++++++++-- .../data/team_match/team_match_bout.g.dart | 5 ++ .../lib/src/services/apis/germany_by.dart | 2 +- .../lib/src/util/transaction.dart | 2 +- .../test/services/apis_test.dart | 2 +- .../PostgreSQL-wrestling_scoreboard-dump.sql | 42 +++++---- .../v0.0.1-beta.14_Initial-migration.sql | 4 +- .../lib/controllers/bout_controller.dart | 4 +- .../division_weight_class_controller.dart | 4 +- .../lib/controllers/league_controller.dart | 90 +++++-------------- .../controllers/organization_controller.dart | 18 ++-- .../organizational_controller.dart | 5 +- .../team_match_bout_controller.dart | 5 +- .../controllers/team_match_controller.dart | 78 ++++++++++++++++ .../lib/routes/api_route.dart | 1 + 22 files changed, 255 insertions(+), 114 deletions(-) diff --git a/wrestling_scoreboard_client/lib/mocks/services/network/data_manager.dart b/wrestling_scoreboard_client/lib/mocks/services/network/data_manager.dart index 7799dfa5..eb5d262d 100644 --- a/wrestling_scoreboard_client/lib/mocks/services/network/data_manager.dart +++ b/wrestling_scoreboard_client/lib/mocks/services/network/data_manager.dart @@ -445,6 +445,12 @@ class MockDataManager extends DataManager { throw UnimplementedError(); } + @override + Future organizationTeamMatchImport(int id, {AuthService? authService}) { + // TODO: implement organizationLeagueImport + throw UnimplementedError(); + } + @override Future organizationCompetitionImport(int id, {AuthService? authService}) { // TODO: implement organizationCompetitionImport diff --git a/wrestling_scoreboard_client/lib/services/network/data_manager.dart b/wrestling_scoreboard_client/lib/services/network/data_manager.dart index edf8a038..2aa20f56 100644 --- a/wrestling_scoreboard_client/lib/services/network/data_manager.dart +++ b/wrestling_scoreboard_client/lib/services/network/data_manager.dart @@ -92,6 +92,8 @@ abstract class DataManager implements AuthManager { Future organizationCompetitionImport(int id, {AuthService? authService}); + Future organizationTeamMatchImport(int id, {AuthService? authService}); + Future>> search({ required String searchTerm, Type? type, diff --git a/wrestling_scoreboard_client/lib/services/network/remote/rest.dart b/wrestling_scoreboard_client/lib/services/network/remote/rest.dart index 90a6b980..793faf09 100644 --- a/wrestling_scoreboard_client/lib/services/network/remote/rest.dart +++ b/wrestling_scoreboard_client/lib/services/network/remote/rest.dart @@ -181,6 +181,21 @@ class RestDataManager extends DataManager { } } + @override + Future organizationTeamMatchImport(int id, {AuthService? authService}) async { + String? body; + if (authService != null) { + if (authService is BasicAuthService) { + body = jsonEncode(singleToJson(authService, BasicAuthService, CRUD.update)); + } + } + final uri = Uri.parse('$_apiUrl/team_match/$id/api/import'); + final response = await http.post(uri, body: body, headers: _headers); + if (response.statusCode != 200) { + throw RestException('Failed to import from team_match $id', response: response); + } + } + @override Future organizationCompetitionImport(int id, {AuthService? authService}) async { String? body; diff --git a/wrestling_scoreboard_client/lib/view/screens/overview/shared/actions.dart b/wrestling_scoreboard_client/lib/view/screens/overview/shared/actions.dart index 6e50bc80..35ec66b1 100644 --- a/wrestling_scoreboard_client/lib/view/screens/overview/shared/actions.dart +++ b/wrestling_scoreboard_client/lib/view/screens/overview/shared/actions.dart @@ -40,6 +40,8 @@ class OrganizationImportAction extends ConsumerWidget { await dataManager.organizationLeagueImport(id, authService: authService); case OrganizationImportType.competition: await dataManager.organizationCompetitionImport(id, authService: authService); + case OrganizationImportType.teamMatch: + await dataManager.organizationTeamMatchImport(id, authService: authService); } if (context.mounted) { await showOkDialog(context: context, child: Text(localizations.actionSuccessful)); @@ -60,4 +62,5 @@ enum OrganizationImportType { team, league, competition, + teamMatch, } diff --git a/wrestling_scoreboard_client/lib/view/screens/overview/team_match/team_match_overview.dart b/wrestling_scoreboard_client/lib/view/screens/overview/team_match/team_match_overview.dart index bc4c5383..96c9f782 100644 --- a/wrestling_scoreboard_client/lib/view/screens/overview/team_match/team_match_overview.dart +++ b/wrestling_scoreboard_client/lib/view/screens/overview/team_match/team_match_overview.dart @@ -16,6 +16,7 @@ import 'package:wrestling_scoreboard_client/view/screens/edit/lineup_edit.dart'; import 'package:wrestling_scoreboard_client/view/screens/edit/team_match/team_match_bout_edit.dart'; import 'package:wrestling_scoreboard_client/view/screens/edit/team_match/team_match_edit.dart'; import 'package:wrestling_scoreboard_client/view/screens/overview/common.dart'; +import 'package:wrestling_scoreboard_client/view/screens/overview/shared/actions.dart'; import 'package:wrestling_scoreboard_client/view/screens/overview/team_match/team_match_bout_overview.dart'; import 'package:wrestling_scoreboard_client/view/widgets/auth.dart'; import 'package:wrestling_scoreboard_client/view/widgets/consumer.dart'; @@ -74,6 +75,8 @@ class TeamMatchOverview extends ConsumerWidget { label: localizations.match, details: '${match.home.team.name} - ${match.guest.team.name}', actions: [ + OrganizationImportAction( + id: id, orgId: match.organization!.id!, importType: OrganizationImportType.teamMatch), // TODO: replace with file_save when https://github.com/flutter/flutter/issues/102560 is merged, also replace in settings. IconButton( onPressed: () async { diff --git a/wrestling_scoreboard_common/lib/src/data/membership.freezed.dart b/wrestling_scoreboard_common/lib/src/data/membership.freezed.dart index 84eb1102..4f9113d7 100644 --- a/wrestling_scoreboard_common/lib/src/data/membership.freezed.dart +++ b/wrestling_scoreboard_common/lib/src/data/membership.freezed.dart @@ -23,7 +23,7 @@ mixin _$Membership { int? get id => throw _privateConstructorUsedError; String? get orgSyncId => throw _privateConstructorUsedError; Organization? get organization => throw _privateConstructorUsedError; - String? get no => throw _privateConstructorUsedError; // Vereinsnummer + String? get no => throw _privateConstructorUsedError; // Mitgliedsnummer Club get club => throw _privateConstructorUsedError; Person get person => throw _privateConstructorUsedError; @@ -211,7 +211,7 @@ class _$MembershipImpl extends _Membership { final Organization? organization; @override final String? no; -// Vereinsnummer +// Mitgliedsnummer @override final Club club; @override @@ -274,7 +274,7 @@ abstract class _Membership extends Membership { @override Organization? get organization; @override - String? get no; // Vereinsnummer + String? get no; // Mitgliedsnummer @override Club get club; @override diff --git a/wrestling_scoreboard_common/lib/src/data/team_match/team_match_bout.dart b/wrestling_scoreboard_common/lib/src/data/team_match/team_match_bout.dart index abcd6d23..e44cbc49 100644 --- a/wrestling_scoreboard_common/lib/src/data/team_match/team_match_bout.dart +++ b/wrestling_scoreboard_common/lib/src/data/team_match/team_match_bout.dart @@ -6,11 +6,13 @@ part 'team_match_bout.freezed.dart'; part 'team_match_bout.g.dart'; @freezed -class TeamMatchBout with _$TeamMatchBout implements DataObject { +class TeamMatchBout with _$TeamMatchBout implements DataObject, Organizational { const TeamMatchBout._(); const factory TeamMatchBout({ int? id, + String? orgSyncId, + Organization? organization, required int pos, required TeamMatch teamMatch, required Bout bout, @@ -21,9 +23,12 @@ class TeamMatchBout with _$TeamMatchBout implements DataObject { static Future fromRaw(Map e, GetSingleOfTypeCallback getSingle) async { final teamMatch = await getSingle(e['team_match_id'] as int); final bout = await getSingle(e['bout_id'] as int); + final organizationId = e['organization_id'] as int?; return TeamMatchBout( id: e['id'] as int?, + orgSyncId: e['org_sync_id'] as String?, + organization: organizationId == null ? null : await getSingle(organizationId), teamMatch: teamMatch, bout: bout, pos: e['pos'], @@ -34,6 +39,8 @@ class TeamMatchBout with _$TeamMatchBout implements DataObject { Map toRaw() { return { if (id != null) 'id': id, + if (orgSyncId != null) 'org_sync_id': orgSyncId, + if (organization != null) 'organization_id': organization?.id!, 'pos': pos, 'team_match_id': teamMatch.id!, 'bout_id': bout.id!, diff --git a/wrestling_scoreboard_common/lib/src/data/team_match/team_match_bout.freezed.dart b/wrestling_scoreboard_common/lib/src/data/team_match/team_match_bout.freezed.dart index 41b393d8..566ec196 100644 --- a/wrestling_scoreboard_common/lib/src/data/team_match/team_match_bout.freezed.dart +++ b/wrestling_scoreboard_common/lib/src/data/team_match/team_match_bout.freezed.dart @@ -21,6 +21,8 @@ TeamMatchBout _$TeamMatchBoutFromJson(Map json) { /// @nodoc mixin _$TeamMatchBout { int? get id => throw _privateConstructorUsedError; + String? get orgSyncId => throw _privateConstructorUsedError; + Organization? get organization => throw _privateConstructorUsedError; int get pos => throw _privateConstructorUsedError; TeamMatch get teamMatch => throw _privateConstructorUsedError; Bout get bout => throw _privateConstructorUsedError; @@ -39,8 +41,9 @@ abstract class $TeamMatchBoutCopyWith<$Res> { factory $TeamMatchBoutCopyWith(TeamMatchBout value, $Res Function(TeamMatchBout) then) = _$TeamMatchBoutCopyWithImpl<$Res, TeamMatchBout>; @useResult - $Res call({int? id, int pos, TeamMatch teamMatch, Bout bout}); + $Res call({int? id, String? orgSyncId, Organization? organization, int pos, TeamMatch teamMatch, Bout bout}); + $OrganizationCopyWith<$Res>? get organization; $TeamMatchCopyWith<$Res> get teamMatch; $BoutCopyWith<$Res> get bout; } @@ -60,6 +63,8 @@ class _$TeamMatchBoutCopyWithImpl<$Res, $Val extends TeamMatchBout> implements $ @override $Res call({ Object? id = freezed, + Object? orgSyncId = freezed, + Object? organization = freezed, Object? pos = null, Object? teamMatch = null, Object? bout = null, @@ -69,6 +74,14 @@ class _$TeamMatchBoutCopyWithImpl<$Res, $Val extends TeamMatchBout> implements $ ? _value.id : id // ignore: cast_nullable_to_non_nullable as int?, + orgSyncId: freezed == orgSyncId + ? _value.orgSyncId + : orgSyncId // ignore: cast_nullable_to_non_nullable + as String?, + organization: freezed == organization + ? _value.organization + : organization // ignore: cast_nullable_to_non_nullable + as Organization?, pos: null == pos ? _value.pos : pos // ignore: cast_nullable_to_non_nullable @@ -84,6 +97,20 @@ class _$TeamMatchBoutCopyWithImpl<$Res, $Val extends TeamMatchBout> implements $ ) as $Val); } + /// Create a copy of TeamMatchBout + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $OrganizationCopyWith<$Res>? get organization { + if (_value.organization == null) { + return null; + } + + return $OrganizationCopyWith<$Res>(_value.organization!, (value) { + return _then(_value.copyWith(organization: value) as $Val); + }); + } + /// Create a copy of TeamMatchBout /// with the given fields replaced by the non-null parameter values. @override @@ -111,8 +138,10 @@ abstract class _$$TeamMatchBoutImplCopyWith<$Res> implements $TeamMatchBoutCopyW __$$TeamMatchBoutImplCopyWithImpl<$Res>; @override @useResult - $Res call({int? id, int pos, TeamMatch teamMatch, Bout bout}); + $Res call({int? id, String? orgSyncId, Organization? organization, int pos, TeamMatch teamMatch, Bout bout}); + @override + $OrganizationCopyWith<$Res>? get organization; @override $TeamMatchCopyWith<$Res> get teamMatch; @override @@ -131,6 +160,8 @@ class __$$TeamMatchBoutImplCopyWithImpl<$Res> extends _$TeamMatchBoutCopyWithImp @override $Res call({ Object? id = freezed, + Object? orgSyncId = freezed, + Object? organization = freezed, Object? pos = null, Object? teamMatch = null, Object? bout = null, @@ -140,6 +171,14 @@ class __$$TeamMatchBoutImplCopyWithImpl<$Res> extends _$TeamMatchBoutCopyWithImp ? _value.id : id // ignore: cast_nullable_to_non_nullable as int?, + orgSyncId: freezed == orgSyncId + ? _value.orgSyncId + : orgSyncId // ignore: cast_nullable_to_non_nullable + as String?, + organization: freezed == organization + ? _value.organization + : organization // ignore: cast_nullable_to_non_nullable + as Organization?, pos: null == pos ? _value.pos : pos // ignore: cast_nullable_to_non_nullable @@ -159,13 +198,19 @@ class __$$TeamMatchBoutImplCopyWithImpl<$Res> extends _$TeamMatchBoutCopyWithImp /// @nodoc @JsonSerializable() class _$TeamMatchBoutImpl extends _TeamMatchBout { - const _$TeamMatchBoutImpl({this.id, required this.pos, required this.teamMatch, required this.bout}) : super._(); + const _$TeamMatchBoutImpl( + {this.id, this.orgSyncId, this.organization, required this.pos, required this.teamMatch, required this.bout}) + : super._(); factory _$TeamMatchBoutImpl.fromJson(Map json) => _$$TeamMatchBoutImplFromJson(json); @override final int? id; @override + final String? orgSyncId; + @override + final Organization? organization; + @override final int pos; @override final TeamMatch teamMatch; @@ -174,7 +219,7 @@ class _$TeamMatchBoutImpl extends _TeamMatchBout { @override String toString() { - return 'TeamMatchBout(id: $id, pos: $pos, teamMatch: $teamMatch, bout: $bout)'; + return 'TeamMatchBout(id: $id, orgSyncId: $orgSyncId, organization: $organization, pos: $pos, teamMatch: $teamMatch, bout: $bout)'; } @override @@ -183,6 +228,8 @@ class _$TeamMatchBoutImpl extends _TeamMatchBout { (other.runtimeType == runtimeType && other is _$TeamMatchBoutImpl && (identical(other.id, id) || other.id == id) && + (identical(other.orgSyncId, orgSyncId) || other.orgSyncId == orgSyncId) && + (identical(other.organization, organization) || other.organization == organization) && (identical(other.pos, pos) || other.pos == pos) && (identical(other.teamMatch, teamMatch) || other.teamMatch == teamMatch) && (identical(other.bout, bout) || other.bout == bout)); @@ -190,7 +237,7 @@ class _$TeamMatchBoutImpl extends _TeamMatchBout { @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash(runtimeType, id, pos, teamMatch, bout); + int get hashCode => Object.hash(runtimeType, id, orgSyncId, organization, pos, teamMatch, bout); /// Create a copy of TeamMatchBout /// with the given fields replaced by the non-null parameter values. @@ -211,6 +258,8 @@ class _$TeamMatchBoutImpl extends _TeamMatchBout { abstract class _TeamMatchBout extends TeamMatchBout { const factory _TeamMatchBout( {final int? id, + final String? orgSyncId, + final Organization? organization, required final int pos, required final TeamMatch teamMatch, required final Bout bout}) = _$TeamMatchBoutImpl; @@ -221,6 +270,10 @@ abstract class _TeamMatchBout extends TeamMatchBout { @override int? get id; @override + String? get orgSyncId; + @override + Organization? get organization; + @override int get pos; @override TeamMatch get teamMatch; diff --git a/wrestling_scoreboard_common/lib/src/data/team_match/team_match_bout.g.dart b/wrestling_scoreboard_common/lib/src/data/team_match/team_match_bout.g.dart index bc717342..549d2586 100644 --- a/wrestling_scoreboard_common/lib/src/data/team_match/team_match_bout.g.dart +++ b/wrestling_scoreboard_common/lib/src/data/team_match/team_match_bout.g.dart @@ -8,6 +8,9 @@ part of 'team_match_bout.dart'; _$TeamMatchBoutImpl _$$TeamMatchBoutImplFromJson(Map json) => _$TeamMatchBoutImpl( id: (json['id'] as num?)?.toInt(), + orgSyncId: json['orgSyncId'] as String?, + organization: + json['organization'] == null ? null : Organization.fromJson(json['organization'] as Map), pos: (json['pos'] as num).toInt(), teamMatch: TeamMatch.fromJson(json['teamMatch'] as Map), bout: Bout.fromJson(json['bout'] as Map), @@ -15,6 +18,8 @@ _$TeamMatchBoutImpl _$$TeamMatchBoutImplFromJson(Map json) => _ Map _$$TeamMatchBoutImplToJson(_$TeamMatchBoutImpl instance) => { 'id': instance.id, + 'orgSyncId': instance.orgSyncId, + 'organization': instance.organization?.toJson(), 'pos': instance.pos, 'teamMatch': instance.teamMatch.toJson(), 'bout': instance.bout.toJson(), diff --git a/wrestling_scoreboard_common/lib/src/services/apis/germany_by.dart b/wrestling_scoreboard_common/lib/src/services/apis/germany_by.dart index 933a0b8c..179759cf 100644 --- a/wrestling_scoreboard_common/lib/src/services/apis/germany_by.dart +++ b/wrestling_scoreboard_common/lib/src/services/apis/germany_by.dart @@ -416,7 +416,7 @@ class ByGermanyWrestlingApi extends WrestlingApi { final boutDuration = boutDurationSeconds == null ? Duration.zero : Duration(seconds: boutDurationSeconds); var bout = Bout( - orgSyncId: '${event.orgSyncId}_${weightClass.name.replaceAll(' ', '_')}', + orgSyncId: '${event.orgSyncId}_${weightClass.name}_${weightClass.style.name}'.replaceAll(' ', '_'), organization: organization, duration: boutDuration, weightClass: weightClass, diff --git a/wrestling_scoreboard_common/lib/src/util/transaction.dart b/wrestling_scoreboard_common/lib/src/util/transaction.dart index 990f3f55..90c64bbd 100644 --- a/wrestling_scoreboard_common/lib/src/util/transaction.dart +++ b/wrestling_scoreboard_common/lib/src/util/transaction.dart @@ -10,7 +10,7 @@ Future runSynchronized({ required String key, bool Function()? canAbort, required Future Function() runAsync, - Duration timeout = const Duration(seconds: 30), + Duration timeout = const Duration(seconds: 60), }) async { Completer? completer; try { diff --git a/wrestling_scoreboard_common/test/services/apis_test.dart b/wrestling_scoreboard_common/test/services/apis_test.dart index 7fd64598..5b5e2741 100644 --- a/wrestling_scoreboard_common/test/services/apis_test.dart +++ b/wrestling_scoreboard_common/test/services/apis_test.dart @@ -551,7 +551,7 @@ void main() { // TODO: Evaluate this bout: https://www.brv-ringen.de/index.php?option=com_rdb&view=rdb&Itemid=512&tk=cs&sid=2023&yid=M&menu=1&op=lc&lid=Bayernliga&cntl=Ergebnisse&from=ll&cid=005029c // Why activity and passivity at different times, or points at different times? final expectedBout = Bout( - orgSyncId: '005029c_61_kg', + orgSyncId: '005029c_61_kg_free', duration: Duration(minutes: 6), result: BoutResult.vpo, weightClass: weightClass, diff --git a/wrestling_scoreboard_server/database/dump/PostgreSQL-wrestling_scoreboard-dump.sql b/wrestling_scoreboard_server/database/dump/PostgreSQL-wrestling_scoreboard-dump.sql index 7e16df1a..b6722e47 100644 --- a/wrestling_scoreboard_server/database/dump/PostgreSQL-wrestling_scoreboard-dump.sql +++ b/wrestling_scoreboard_server/database/dump/PostgreSQL-wrestling_scoreboard-dump.sql @@ -929,7 +929,9 @@ CREATE TABLE public.team_match_bout ( id integer NOT NULL, team_match_id integer NOT NULL, bout_id integer NOT NULL, - pos integer DEFAULT 0 NOT NULL + pos integer DEFAULT 0 NOT NULL, + org_sync_id character varying(127), + organization_id integer ); @@ -1561,21 +1563,21 @@ COPY public.team_match (id, date, location, visitors_count, comment, no, organiz -- Data for Name: team_match_bout; Type: TABLE DATA; Schema: public; Owner: wrestling -- -COPY public.team_match_bout (id, team_match_id, bout_id, pos) FROM stdin; -25 1 27 0 -26 1 28 1 -27 1 29 2 -30 1 32 3 -32 1 34 4 -33 1 35 5 -35 1 37 6 -36 1 38 7 -38 1 40 8 -41 1 43 9 -43 1 45 10 -45 1 47 11 -47 1 49 12 -48 1 50 13 +COPY public.team_match_bout (id, team_match_id, bout_id, pos, org_sync_id, organization_id) FROM stdin; +25 1 27 0 \N \N +26 1 28 1 \N \N +27 1 29 2 \N \N +30 1 32 3 \N \N +32 1 34 4 \N \N +33 1 35 5 \N \N +35 1 37 6 \N \N +36 1 38 7 \N \N +38 1 40 8 \N \N +41 1 43 9 \N \N +43 1 45 10 \N \N +45 1 47 11 \N \N +47 1 49 12 \N \N +48 1 50 13 \N \N \. @@ -2310,6 +2312,14 @@ ALTER TABLE ONLY public.team_match_bout ADD CONSTRAINT team_match_bout_bout_id_fk FOREIGN KEY (bout_id) REFERENCES public.bout(id) ON DELETE CASCADE; +-- +-- Name: team_match_bout team_match_bout_organization_id_fk; Type: FK CONSTRAINT; Schema: public; Owner: wrestling +-- + +ALTER TABLE ONLY public.team_match_bout + ADD CONSTRAINT team_match_bout_organization_id_fk FOREIGN KEY (organization_id) REFERENCES public.organization(id); + + -- -- Name: team_match_bout team_match_bout_team_match_id_fk; Type: FK CONSTRAINT; Schema: public; Owner: wrestling -- diff --git a/wrestling_scoreboard_server/database/migration/v0.0.1-beta.14_Initial-migration.sql b/wrestling_scoreboard_server/database/migration/v0.0.1-beta.14_Initial-migration.sql index 7851bb12..0bac3b3d 100644 --- a/wrestling_scoreboard_server/database/migration/v0.0.1-beta.14_Initial-migration.sql +++ b/wrestling_scoreboard_server/database/migration/v0.0.1-beta.14_Initial-migration.sql @@ -60,4 +60,6 @@ ALTER TABLE ONLY public.secured_user ADD CONSTRAINT user_person_id_fk FOREIGN KE alter table division_weight_class drop constraint division_weight_class_league_id_fk; alter table division_weight_class add constraint division_weight_class_division_id_fk foreign key (division_id) references division on delete cascade; - +alter table team_match_bout add org_sync_id varchar(127); +alter table team_match_bout add organization_id integer; +alter table team_match_bout add constraint team_match_bout_organization_id_fk foreign key (organization_id) references organization; diff --git a/wrestling_scoreboard_server/lib/controllers/bout_controller.dart b/wrestling_scoreboard_server/lib/controllers/bout_controller.dart index d193a77a..df5d6051 100644 --- a/wrestling_scoreboard_server/lib/controllers/bout_controller.dart +++ b/wrestling_scoreboard_server/lib/controllers/bout_controller.dart @@ -2,13 +2,13 @@ import 'package:postgres/postgres.dart' as psql; import 'package:shelf/shelf.dart'; import 'package:wrestling_scoreboard_common/common.dart'; import 'package:wrestling_scoreboard_server/controllers/auth_controller.dart'; +import 'package:wrestling_scoreboard_server/controllers/organizational_controller.dart'; import 'package:wrestling_scoreboard_server/controllers/participant_state_controller.dart'; import 'package:wrestling_scoreboard_server/request.dart'; import 'bout_action_controller.dart'; -import 'entity_controller.dart'; -class BoutController extends ShelfController { +class BoutController extends OrganizationalController { static final BoutController _singleton = BoutController._internal(); factory BoutController() { diff --git a/wrestling_scoreboard_server/lib/controllers/division_weight_class_controller.dart b/wrestling_scoreboard_server/lib/controllers/division_weight_class_controller.dart index b4ab74a5..68999101 100644 --- a/wrestling_scoreboard_server/lib/controllers/division_weight_class_controller.dart +++ b/wrestling_scoreboard_server/lib/controllers/division_weight_class_controller.dart @@ -1,8 +1,8 @@ import 'package:wrestling_scoreboard_common/common.dart'; -import 'entity_controller.dart'; +import 'organizational_controller.dart'; -class DivisionWeightClassController extends ShelfController { +class DivisionWeightClassController extends OrganizationalController { static final DivisionWeightClassController _singleton = DivisionWeightClassController._internal(); factory DivisionWeightClassController() { diff --git a/wrestling_scoreboard_server/lib/controllers/league_controller.dart b/wrestling_scoreboard_server/lib/controllers/league_controller.dart index 338b6ad6..e1b62a5f 100644 --- a/wrestling_scoreboard_server/lib/controllers/league_controller.dart +++ b/wrestling_scoreboard_server/lib/controllers/league_controller.dart @@ -1,23 +1,16 @@ import 'package:shelf/shelf.dart'; import 'package:wrestling_scoreboard_common/common.dart'; import 'package:wrestling_scoreboard_server/controllers/auth_controller.dart'; -import 'package:wrestling_scoreboard_server/controllers/bout_action_controller.dart'; import 'package:wrestling_scoreboard_server/controllers/entity_controller.dart'; import 'package:wrestling_scoreboard_server/controllers/league_team_participation_controller.dart'; import 'package:wrestling_scoreboard_server/controllers/lineup_controller.dart'; -import 'package:wrestling_scoreboard_server/controllers/membership_controller.dart'; import 'package:wrestling_scoreboard_server/controllers/organization_controller.dart'; import 'package:wrestling_scoreboard_server/controllers/organizational_controller.dart'; -import 'package:wrestling_scoreboard_server/controllers/participant_state_controller.dart'; -import 'package:wrestling_scoreboard_server/controllers/participation_controller.dart'; import 'package:wrestling_scoreboard_server/controllers/person_controller.dart'; import 'package:wrestling_scoreboard_server/controllers/team_controller.dart'; -import 'package:wrestling_scoreboard_server/controllers/team_match_bout_controller.dart'; import 'package:wrestling_scoreboard_server/controllers/team_match_controller.dart'; import 'package:wrestling_scoreboard_server/request.dart'; -import 'bout_controller.dart'; - class LeagueController extends OrganizationalController { static final LeagueController _singleton = LeagueController._internal(); @@ -74,29 +67,28 @@ class LeagueController extends OrganizationalController { final teamMatchs = await apiProvider.importTeamMatches(league: league); await Future.forEach(teamMatchs, (teamMatch) async { - teamMatch = teamMatch.copyWith( - // TODO: Don't create lineup or delete old one, if match already exists. - home: await LineupController().createSingleReturn(teamMatch.home), - guest: await LineupController().createSingleReturn(teamMatch.guest), - referee: teamMatch.referee == null - ? null - : await PersonController().getOrCreateSingleOfOrg(teamMatch.referee!, obfuscate: obfuscate), - judge: teamMatch.judge == null - ? null - : await PersonController().getOrCreateSingleOfOrg(teamMatch.judge!, obfuscate: obfuscate), - matChairman: teamMatch.matChairman == null - ? null - : await PersonController().getOrCreateSingleOfOrg(teamMatch.matChairman!, obfuscate: obfuscate), - transcriptWriter: teamMatch.transcriptWriter == null - ? null - : await PersonController().getOrCreateSingleOfOrg(teamMatch.transcriptWriter!, obfuscate: obfuscate), - timeKeeper: teamMatch.timeKeeper == null - ? null - : await PersonController().getOrCreateSingleOfOrg(teamMatch.timeKeeper!, obfuscate: obfuscate), - ); - teamMatch = await TeamMatchController().getOrCreateSingleOfOrg(teamMatch, obfuscate: obfuscate); + await TeamMatchController().getOrCreateSingleOfOrg(teamMatch, obfuscate: obfuscate, onCreate: () async { + return teamMatch.copyWith( + home: await LineupController().createSingleReturn(teamMatch.home), + guest: await LineupController().createSingleReturn(teamMatch.guest), + referee: teamMatch.referee == null + ? null + : await PersonController().getOrCreateSingleOfOrg(teamMatch.referee!, obfuscate: obfuscate), + judge: teamMatch.judge == null + ? null + : await PersonController().getOrCreateSingleOfOrg(teamMatch.judge!, obfuscate: obfuscate), + matChairman: teamMatch.matChairman == null + ? null + : await PersonController().getOrCreateSingleOfOrg(teamMatch.matChairman!, obfuscate: obfuscate), + transcriptWriter: teamMatch.transcriptWriter == null + ? null + : await PersonController().getOrCreateSingleOfOrg(teamMatch.transcriptWriter!, obfuscate: obfuscate), + timeKeeper: teamMatch.timeKeeper == null + ? null + : await PersonController().getOrCreateSingleOfOrg(teamMatch.timeKeeper!, obfuscate: obfuscate), + ); + }); - // TODO: may do this in a separate import call for the participating teams: try { await LeagueTeamParticipationController() .createSingle(LeagueTeamParticipation(league: league, team: teamMatch.home.team)); @@ -105,46 +97,6 @@ class LeagueController extends OrganizationalController { } on InvalidParameterException catch (_) { // Do not add teams multiple times. } - - final boutMap = await apiProvider.importBouts(event: teamMatch); - final bouts = await Future.wait(boutMap.entries.map((boutEntry) async { - var bout = boutEntry.key; - - Future saveDeepParticipantState(ParticipantState? participantState) async { - if (participantState != null) { - final person = await PersonController() - .getOrCreateSingleOfOrg(participantState.participation.membership.person, obfuscate: obfuscate); - - final membership = await MembershipController().getOrCreateSingleOfOrg( - participantState.participation.membership.copyWith(person: person), - obfuscate: obfuscate); - - final participation = await ParticipationController() - .createSingleReturn(participantState.participation.copyWith(membership: membership)); - - return await ParticipantStateController() - .createSingleReturn(participantState.copyWith(participation: participation)); - } - return null; - } - - bout = await BoutController().createSingleReturn(bout.copyWith( - r: await saveDeepParticipantState(bout.r), - b: await saveDeepParticipantState(bout.b), - )); - - // Add missing id to bout of boutActions - final Iterable boutActions = boutEntry.value.map((action) => action.copyWith(bout: bout)); - await BoutActionController().createMany(boutActions.toList()); - return bout; - })); - - await Future.wait(bouts.asMap().entries.map((boutEntry) async { - final index = boutEntry.key; - final bout = boutEntry.value; - await TeamMatchBoutController() - .createSingleReturn(TeamMatchBout(pos: index, teamMatch: teamMatch, bout: bout)); - })); }); return Response.ok('{"status": "success"}'); diff --git a/wrestling_scoreboard_server/lib/controllers/organization_controller.dart b/wrestling_scoreboard_server/lib/controllers/organization_controller.dart index 3f983c5c..a382c0ce 100644 --- a/wrestling_scoreboard_server/lib/controllers/organization_controller.dart +++ b/wrestling_scoreboard_server/lib/controllers/organization_controller.dart @@ -98,17 +98,19 @@ class OrganizationController extends ShelfController { final divisions = await apiProvider.importDivisions(minDate: DateTime(DateTime.now().year - 1)); await Future.forEach(divisions, (division) async { - // TODO: Don't create bout config or delete old one, if division already exists. - final boutConfig = await BoutConfigController().createSingleReturn(division.boutConfig); - division = division.copyWith(boutConfig: boutConfig); - division = await DivisionController().getOrCreateSingleOfOrg(division, obfuscate: obfuscate); + division = + await DivisionController().getOrCreateSingleOfOrg(division, obfuscate: obfuscate, onCreate: () async { + final boutConfig = await BoutConfigController().createSingleReturn(division.boutConfig); + return division.copyWith(boutConfig: boutConfig); + }); - // TODO: Don't create (division) weight classes or delete old ones, if division already exists. final divisionWeightClasses = await apiProvider.importDivisionWeightClasses(division: division); await Future.forEach(divisionWeightClasses, (divisionWeightClass) async { - final weightClass = await WeightClassController().createSingleReturn(divisionWeightClass.weightClass); - divisionWeightClass = divisionWeightClass.copyWith(weightClass: weightClass); - divisionWeightClass = await DivisionWeightClassController().createSingleReturn(divisionWeightClass); + await DivisionWeightClassController().getOrCreateSingleOfOrg(divisionWeightClass, obfuscate: obfuscate, + onCreate: () async { + final weightClass = await WeightClassController().createSingleReturn(divisionWeightClass.weightClass); + return divisionWeightClass.copyWith(weightClass: weightClass); + }); }); var leagues = await apiProvider.importLeagues(division: division); diff --git a/wrestling_scoreboard_server/lib/controllers/organizational_controller.dart b/wrestling_scoreboard_server/lib/controllers/organizational_controller.dart index 2ffbe086..f748472f 100644 --- a/wrestling_scoreboard_server/lib/controllers/organizational_controller.dart +++ b/wrestling_scoreboard_server/lib/controllers/organizational_controller.dart @@ -36,7 +36,7 @@ abstract class OrganizationalController extends ShelfC return many.first; } - Future getOrCreateSingleOfOrg(T dataObject, {required bool obfuscate}) async { + Future getOrCreateSingleOfOrg(T dataObject, {required bool obfuscate, Future Function()? onCreate}) async { if (dataObject.id != null) { throw Exception('Data object already has an id: $dataObject'); } @@ -49,6 +49,9 @@ abstract class OrganizationalController extends ShelfC orgId: organizational.organization!.id!, obfuscate: obfuscate); return single; } on InvalidParameterException catch (_) { + if (onCreate != null) { + dataObject = await onCreate(); + } return createSingleReturn(dataObject); } } diff --git a/wrestling_scoreboard_server/lib/controllers/team_match_bout_controller.dart b/wrestling_scoreboard_server/lib/controllers/team_match_bout_controller.dart index 024f4e4f..7ea2f6f6 100644 --- a/wrestling_scoreboard_server/lib/controllers/team_match_bout_controller.dart +++ b/wrestling_scoreboard_server/lib/controllers/team_match_bout_controller.dart @@ -1,8 +1,7 @@ import 'package:wrestling_scoreboard_common/common.dart'; +import 'package:wrestling_scoreboard_server/controllers/organizational_controller.dart'; -import 'entity_controller.dart'; - -class TeamMatchBoutController extends ShelfController { +class TeamMatchBoutController extends OrganizationalController { static final TeamMatchBoutController _singleton = TeamMatchBoutController._internal(); factory TeamMatchBoutController() { diff --git a/wrestling_scoreboard_server/lib/controllers/team_match_controller.dart b/wrestling_scoreboard_server/lib/controllers/team_match_controller.dart index 18d3b7de..a42d3a1d 100644 --- a/wrestling_scoreboard_server/lib/controllers/team_match_controller.dart +++ b/wrestling_scoreboard_server/lib/controllers/team_match_controller.dart @@ -4,10 +4,14 @@ import 'package:postgres/postgres.dart' as psql; import 'package:shelf/shelf.dart'; import 'package:wrestling_scoreboard_common/common.dart'; import 'package:wrestling_scoreboard_server/controllers/auth_controller.dart'; +import 'package:wrestling_scoreboard_server/controllers/bout_action_controller.dart'; import 'package:wrestling_scoreboard_server/controllers/division_controller.dart'; +import 'package:wrestling_scoreboard_server/controllers/membership_controller.dart'; +import 'package:wrestling_scoreboard_server/controllers/organization_controller.dart'; import 'package:wrestling_scoreboard_server/controllers/organizational_controller.dart'; import 'package:wrestling_scoreboard_server/controllers/participant_state_controller.dart'; import 'package:wrestling_scoreboard_server/controllers/participation_controller.dart'; +import 'package:wrestling_scoreboard_server/controllers/person_controller.dart'; import 'package:wrestling_scoreboard_server/controllers/team_match_bout_controller.dart'; import 'package:wrestling_scoreboard_server/controllers/websocket_handler.dart'; import 'package:wrestling_scoreboard_server/request.dart'; @@ -139,6 +143,80 @@ class TeamMatchController extends OrganizationalController { return {'comment': psql.Type.text}; } + Future import(Request request, User? user, String teamMatchId) async { + try { + final bool obfuscate = user?.obfuscate ?? true; + final teamMatch = await TeamMatchController().getSingle(int.parse(teamMatchId), obfuscate: false); + + final organizationId = teamMatch.organization?.id; + if (organizationId == null) { + throw Exception('No organization found for league $teamMatchId.'); + } + + final apiProvider = await OrganizationController().initApiProvider(request, organizationId); + if (apiProvider == null) { + throw Exception('No API provider selected for the organization $organizationId.'); + } + // apiProvider.isMock = true; + + final boutMap = await apiProvider.importBouts(event: teamMatch); + + // Handle bouts one after one, (NO Future.wait) as it may conflicts creating the same membership as once. + var index = 0; + for (final boutEntry in boutMap.entries) { + var bout = boutEntry.key; + + final teamMatchBout = TeamMatchBout( + pos: index, + teamMatch: teamMatch, + bout: bout, + organization: teamMatch.organization, + orgSyncId: bout.orgSyncId, + ); + await TeamMatchBoutController().getOrCreateSingleOfOrg(teamMatchBout, obfuscate: obfuscate, onCreate: () async { + bout = await BoutController().getOrCreateSingleOfOrg( + bout, + obfuscate: obfuscate, + onCreate: () async { + return bout.copyWith( + r: await _saveDeepParticipantState(bout.r, obfuscate: obfuscate), + b: await _saveDeepParticipantState(bout.b, obfuscate: obfuscate), + ); + }, + ); + + // Add missing id to bout of boutActions + final Iterable boutActions = boutEntry.value.map((action) => action.copyWith(bout: bout)); + await BoutActionController().createMany(boutActions.toList()); + return teamMatchBout.copyWith(bout: bout); + }); + index++; + } + return Response.ok('{"status": "success"}'); + } catch (err, stackTrace) { + return Response.internalServerError(body: '{"err": "$err", "stackTrace": "$stackTrace"}'); + } + } + + Future _saveDeepParticipantState(ParticipantState? participantState, + {required bool obfuscate}) async { + if (participantState != null) { + final person = await PersonController() + .getOrCreateSingleOfOrg(participantState.participation.membership.person, obfuscate: obfuscate); + + final membership = await MembershipController().getOrCreateSingleOfOrg( + participantState.participation.membership.copyWith(person: person), + obfuscate: obfuscate); + + final participation = await ParticipationController() + .createSingleReturn(participantState.participation.copyWith(membership: membership)); + + return await ParticipantStateController() + .createSingleReturn(participantState.copyWith(participation: participation)); + } + return null; + } + @override Set getSearchableAttributes() => {'no', 'location', 'comment'}; } diff --git a/wrestling_scoreboard_server/lib/routes/api_route.dart b/wrestling_scoreboard_server/lib/routes/api_route.dart index 590d4010..f3e2a3ab 100644 --- a/wrestling_scoreboard_server/lib/routes/api_route.dart +++ b/wrestling_scoreboard_server/lib/routes/api_route.dart @@ -149,6 +149,7 @@ class ApiRoute { router.restrictedGetOne('/team//team_matchs', teamController.requestTeamMatches); final matchController = TeamMatchController(); + router.restrictedPostOne('/team_match//api/import', matchController.import); router.restrictedPost('/team_match', matchController.postSingle); router.restrictedGet('/team_matchs', matchController.requestMany); router.restrictedGet('/team_matches', matchController.requestMany);