Skip to content

Commit

Permalink
feat: updateOrCreate on API import (#71)
Browse files Browse the repository at this point in the history
  • Loading branch information
Gustl22 committed Oct 23, 2024
1 parent 50ccdcd commit f194dd7
Show file tree
Hide file tree
Showing 13 changed files with 5,892 additions and 179 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,8 @@ class CompetitionController extends ShelfController<Competition> with ImportCont
}

@override
Future<Response> import(Request request, User? user, String entityId) async {
updateLastImportUtcDateTime(entityId);
return Response.notFound('This operation is not supported yet!');
Future<void> import(int entityId, {String? message, bool obfuscate = true, bool useMock = false}) async {
throw UnimplementedError('This operation is not supported yet!');
}

@override
Expand Down
43 changes: 40 additions & 3 deletions wrestling_scoreboard_server/lib/controllers/entity_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,26 @@ mixin ImportController {
return Response.ok(lastImportUtcDateTime[int.parse(entityId)]?.toIso8601String());
}

void updateLastImportUtcDateTime(String id) {
lastImportUtcDateTime[int.parse(id)] = DateTime.now().toUtc();
void updateLastImportUtcDateTime(int id) {
lastImportUtcDateTime[id] = DateTime.now().toUtc();
}

Future<Response> import(Request request, User? user, String entityId);
Future<Response> postImport(Request request, User? user, String entityIdStr) async {
final bool obfuscate = user?.obfuscate ?? true;
final entityId = int.parse(entityIdStr);
try {
final message = await request.readAsString();
await import(entityId, message: message, obfuscate: obfuscate);
updateLastImportUtcDateTime(entityId);
return Response.ok('{"status": "success"}');
} on HttpException catch (err, stackTrace) {
return Response.badRequest(body: '{"err": "$err", "stackTrace": "$stackTrace"}');
} catch (err, stackTrace) {
return Response.internalServerError(body: '{"err": "$err", "stackTrace": "$stackTrace"}');
}
}

Future<void> import(int entityId, {String? message, bool obfuscate, bool useMock});
}

abstract class ShelfController<T extends DataObject> extends EntityController<T> {
Expand Down Expand Up @@ -342,6 +357,28 @@ abstract class EntityController<T extends DataObject> {
}
}

Future<T> updateOnDiffSingle(T dataObject, {required T? previous}) async {
if (previous == null) {
return await createSingleReturn(dataObject);
}
dataObject = dataObject.copyWithId(previous.id) as T;
if (dataObject != previous) {
await updateSingle(dataObject);
}
return dataObject;
}

Future<List<T>> updateOnDiffMany(List<T> dataObjects, {required List<T> previous}) async {
if (dataObjects.length != previous.length) {
await Future.wait(previous.map((prev) => deleteSingle(prev.id!)));
return await createManyReturn(dataObjects);
} else {
return await Future.wait(Map.fromIterables(dataObjects, previous)
.entries
.map((element) => updateOnDiffSingle(element.key, previous: element.value)));
}
}

Map<String, dynamic> _prepareBindingData(Map<String, dynamic> data) {
final postgresTypes = getPostgresDataTypes();
return data.map((key, value) {
Expand Down
81 changes: 40 additions & 41 deletions wrestling_scoreboard_server/lib/controllers/league_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,63 +49,62 @@ class LeagueController extends OrganizationalController<League> with ImportContr
}

@override
Future<Response> import(Request request, User? user, String entityId) async {
try {
final bool obfuscate = user?.obfuscate ?? true;
final league = await LeagueController().getSingle(int.parse(entityId), obfuscate: false);
Future<void> import(int entityId, {String? message, bool obfuscate = true, bool useMock = false}) async {
final league = await LeagueController().getSingle(entityId, obfuscate: obfuscate);

final organizationId = league.organization?.id;
if (organizationId == null) {
throw Exception('No organization found for league $entityId.');
}
final organizationId = league.organization?.id;
if (organizationId == null) {
throw Exception('No organization found for league $entityId.');
}

final apiProvider = await OrganizationController().initApiProvider(request, organizationId);
if (apiProvider == null) {
throw Exception('No API provider selected for the organization $organizationId.');
}
// apiProvider.isMock = true;
final apiProvider = await OrganizationController().initApiProvider(message, organizationId);
if (apiProvider == null) {
throw Exception('No API provider selected for the organization $organizationId.');
}
apiProvider.isMock = useMock;

final teamMatchs = await apiProvider.importTeamMatches(league: league);
final teamMatchs = await apiProvider.importTeamMatches(league: league);

await Future.forEach(teamMatchs, (teamMatch) async {
await TeamMatchController().getOrCreateSingleOfOrg(teamMatch, obfuscate: obfuscate, onCreate: () async {
await Future.forEach(teamMatchs, (teamMatch) async {
await TeamMatchController().updateOrCreateSingleOfOrg(
teamMatch,
obfuscate: obfuscate,
onUpdateOrCreate: (prevTeamMatch) async {
return teamMatch.copyWith(
home: await LineupController().createSingleReturn(teamMatch.home),
guest: await LineupController().createSingleReturn(teamMatch.guest),
home: await LineupController().updateOnDiffSingle(teamMatch.home, previous: prevTeamMatch?.home),
guest: await LineupController().updateOnDiffSingle(teamMatch.guest, previous: prevTeamMatch?.guest),
referee: teamMatch.referee == null
? null
: await PersonController().getOrCreateSingleOfOrg(teamMatch.referee!, obfuscate: obfuscate),
: await PersonController().updateOrCreateSingleOfOrg(teamMatch.referee!, obfuscate: obfuscate),
judge: teamMatch.judge == null
? null
: await PersonController().getOrCreateSingleOfOrg(teamMatch.judge!, obfuscate: obfuscate),
: await PersonController().updateOrCreateSingleOfOrg(teamMatch.judge!, obfuscate: obfuscate),
matChairman: teamMatch.matChairman == null
? null
: await PersonController().getOrCreateSingleOfOrg(teamMatch.matChairman!, obfuscate: obfuscate),
: await PersonController().updateOrCreateSingleOfOrg(teamMatch.matChairman!, obfuscate: obfuscate),
transcriptWriter: teamMatch.transcriptWriter == null
? null
: await PersonController().getOrCreateSingleOfOrg(teamMatch.transcriptWriter!, obfuscate: obfuscate),
: await PersonController().updateOrCreateSingleOfOrg(teamMatch.transcriptWriter!, obfuscate: obfuscate),
timeKeeper: teamMatch.timeKeeper == null
? null
: await PersonController().getOrCreateSingleOfOrg(teamMatch.timeKeeper!, obfuscate: obfuscate),
: await PersonController().updateOrCreateSingleOfOrg(teamMatch.timeKeeper!, obfuscate: obfuscate),
);
});
},
);

try {
await LeagueTeamParticipationController()
.createSingle(LeagueTeamParticipation(league: league, team: teamMatch.home.team));
await LeagueTeamParticipationController()
.createSingle(LeagueTeamParticipation(league: league, team: teamMatch.guest.team));
} on InvalidParameterException catch (_) {
// Do not add teams multiple times.
}
});

updateLastImportUtcDateTime(entityId);
return Response.ok('{"status": "success"}');
} on HttpException catch (err, stackTrace) {
return Response.badRequest(body: '{"err": "$err", "stackTrace": "$stackTrace"}');
} catch (err, stackTrace) {
return Response.internalServerError(body: '{"err": "$err", "stackTrace": "$stackTrace"}');
}
// Do not add teams to a league multiple times.
final previousHomeTeamParticipation = await LeagueTeamParticipationController()
.getByLeagueAndTeamId(teamId: teamMatch.home.team.id!, leagueId: league.id!, obfuscate: obfuscate);
if (previousHomeTeamParticipation == null) {
await LeagueTeamParticipationController()
.createSingle(LeagueTeamParticipation(league: league, team: teamMatch.home.team));
}
final previousGuestTeamParticipation = await LeagueTeamParticipationController()
.getByLeagueAndTeamId(teamId: teamMatch.guest.team.id!, leagueId: league.id!, obfuscate: obfuscate);
if (previousGuestTeamParticipation == null) {
await LeagueTeamParticipationController()
.createSingle(LeagueTeamParticipation(league: league, team: teamMatch.guest.team));
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,16 @@ class LeagueTeamParticipationController extends ShelfController<LeagueTeamPartic
}

LeagueTeamParticipationController._internal() : super(tableName: 'league_team_participation');

Future<LeagueTeamParticipation?> getByLeagueAndTeamId({
required int teamId,
required int leagueId,
required obfuscate,
}) async {
final many = await getMany(
conditions: ['team_id = @teamId', 'league_id = @leagueId'],
substitutionValues: {'teamId': teamId, 'leagueId': leagueId},
obfuscate: obfuscate);
return many.singleOrNull;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,9 @@ class OrganizationController extends ShelfController<Organization> with ImportCo
);
}

Future<WrestlingApi?> initApiProvider(Request request, int organizationId) async {
Future<WrestlingApi?> initApiProvider(String? message, int organizationId) async {
AuthService? authService;
final message = await request.readAsString();
if (message.isNotEmpty) {
if (message != null && message.isNotEmpty) {
final jsonDecodedMessage = jsonDecode(message);
final authType = getTypeFromTableName(jsonDecodedMessage['tableName']);
if (authType == BasicAuthService) {
Expand All @@ -82,62 +81,61 @@ class OrganizationController extends ShelfController<Organization> with ImportCo
}

@override
Future<Response> import(Request request, User? user, String entityId) async {
try {
final bool obfuscate = user?.obfuscate ?? true;
final apiProvider = await initApiProvider(request, int.parse(entityId));
if (apiProvider == null) {
throw Exception('No API provider selected for the organization $entityId.');
Future<void> import(int entityId, {String? message, bool obfuscate = true, bool useMock = false}) async {
final apiProvider = await initApiProvider(message, entityId);
if (apiProvider == null) {
throw Exception('No API provider selected for the organization $entityId.');
}
apiProvider.isMock = useMock;

final teamClubAffiliations = await apiProvider.importTeamClubAffiliations();
await Future.forEach(teamClubAffiliations, (teamClubAffiliation) async {
final club = await ClubController().updateOrCreateSingleOfOrg(teamClubAffiliation.club, obfuscate: obfuscate);
final team = await TeamController().updateOrCreateSingleOfOrg(teamClubAffiliation.team, obfuscate: obfuscate);
teamClubAffiliation = teamClubAffiliation.copyWith(club: club, team: team);

// Do not add team club affiliations multiple times.
final previousTeamClubAffiliation = await TeamClubAffiliationController()
.getByTeamAndClubId(teamId: team.id!, clubId: club.id!, obfuscate: obfuscate);
if (previousTeamClubAffiliation == null) {
await TeamClubAffiliationController().createSingle(TeamClubAffiliation(team: team, club: club));
}
// apiProvider.isMock = true;

final teamClubAffiliations = await apiProvider.importTeamClubAffiliations();
await Future.forEach(teamClubAffiliations, (teamClubAffiliation) async {
final club = await ClubController().getOrCreateSingleOfOrg(teamClubAffiliation.club, obfuscate: obfuscate);
final team = await TeamController().getOrCreateSingleOfOrg(teamClubAffiliation.team, obfuscate: obfuscate);
teamClubAffiliation = teamClubAffiliation.copyWith(club: club, team: team);

try {
await TeamClubAffiliationController().createSingle(TeamClubAffiliation(team: team, club: club));
} on InvalidParameterException catch (_) {
// Do not add team club affiliations multiple times.
}
});

final divisionBoutResultRuleMap = await apiProvider.importDivisions(minDate: DateTime(DateTime.now().year - 1));
await Future.forEach(divisionBoutResultRuleMap.entries, (divisionBoutResultEntry) async {
Division division = divisionBoutResultEntry.key;
final boutResultRules = divisionBoutResultEntry.value;
division =
await DivisionController().getOrCreateSingleOfOrg(division, obfuscate: obfuscate, onCreate: () async {
final boutConfig = await BoutConfigController().createSingleReturn(division.boutConfig);
await Future.forEach(boutResultRules, (rule) async {
rule = rule.copyWith(boutConfig: boutConfig);
rule = await BoutResultRuleController().createSingleReturn(rule);
});
});

final divisionBoutResultRuleMap = await apiProvider.importDivisions(minDate: DateTime(DateTime.now().year - 1));
await Future.forEach(divisionBoutResultRuleMap.entries, (divisionBoutResultEntry) async {
Division division = divisionBoutResultEntry.key;
var boutResultRules = divisionBoutResultEntry.value;
division = await DivisionController().updateOrCreateSingleOfOrg(
division,
obfuscate: obfuscate,
onUpdateOrCreate: (previousDivision) async {
final boutConfig = await BoutConfigController()
.updateOnDiffSingle(division.boutConfig, previous: previousDivision?.boutConfig);
boutResultRules = boutResultRules.map((rule) => rule.copyWith(boutConfig: boutConfig));
final prevRules = await BoutResultRuleController().getMany(
conditions: ['bout_config_id = @id'], substitutionValues: {'id': boutConfig.id}, obfuscate: obfuscate);
await BoutResultRuleController().updateOnDiffMany(boutResultRules.toList(), previous: prevRules);
return division.copyWith(boutConfig: boutConfig);
});

final divisionWeightClasses = await apiProvider.importDivisionWeightClasses(division: division);
await Future.forEach(divisionWeightClasses, (divisionWeightClass) async {
await DivisionWeightClassController().getOrCreateSingleOfOrg(divisionWeightClass, obfuscate: obfuscate,
onCreate: () async {
final weightClass = await WeightClassController().createSingleReturn(divisionWeightClass.weightClass);
},
);

final divisionWeightClasses = await apiProvider.importDivisionWeightClasses(division: division);
await Future.forEach(divisionWeightClasses, (divisionWeightClass) async {
await DivisionWeightClassController().updateOrCreateSingleOfOrg(
divisionWeightClass,
obfuscate: obfuscate,
onUpdateOrCreate: (previousWeightClass) async {
final weightClass = await WeightClassController()
.updateOnDiffSingle(divisionWeightClass.weightClass, previous: previousWeightClass?.weightClass);
return divisionWeightClass.copyWith(weightClass: weightClass);
});
});

var leagues = await apiProvider.importLeagues(division: division);
leagues = await LeagueController().getOrCreateManyOfOrg(leagues.toList(), obfuscate: obfuscate);
},
);
});

updateLastImportUtcDateTime(entityId);
return Response.ok('{"status": "success"}');
} on HttpException catch (err, stackTrace) {
return Response.badRequest(body: '{"err": "$err", "stackTrace": "$stackTrace"}');
} catch (err, stackTrace) {
return Response.internalServerError(body: '{"err": "$err", "stackTrace": "$stackTrace"}');
}
var leagues = await apiProvider.importLeagues(division: division);
leagues = await LeagueController().updateOrCreateManyOfOrg(leagues.toList(), obfuscate: obfuscate);
});
}

Future<List<DataObject>> search(
Expand All @@ -146,7 +144,8 @@ class OrganizationController extends ShelfController<Organization> with ImportCo
required String searchStr,
required Type searchType,
}) async {
final apiProvider = await initApiProvider(request, id);
final message = await request.readAsString();
final apiProvider = await initApiProvider(message, id);
if (apiProvider == null) {
throw Exception('No API provider selected for the organization $id.');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,12 @@ abstract class OrganizationalController<T extends Organizational> extends ShelfC
return many.first;
}

Future<T> getOrCreateSingleOfOrg(T dataObject, {required bool obfuscate, Future<T> Function()? onCreate}) async {
/// [onUpdateOrCreate] takes a [T] as previous input value.
Future<T> updateOrCreateSingleOfOrg(
T dataObject, {
required bool obfuscate,
Future<T> Function(T? previous)? onUpdateOrCreate,
}) async {
if (dataObject.id != null) {
throw Exception('Data object already has an id: $dataObject');
}
Expand All @@ -45,19 +50,22 @@ abstract class OrganizationalController<T extends Organizational> extends ShelfC
throw Exception('Organization id and sync id must not be null: $dataObject');
}
try {
final single = await getSingleOfOrg(organizational.orgSyncId!,
final previous = await getSingleOfOrg(organizational.orgSyncId!,
orgId: organizational.organization!.id!, obfuscate: obfuscate);
return single;
if (onUpdateOrCreate != null) {
dataObject = await onUpdateOrCreate(previous);
}
return updateOnDiffSingle(dataObject, previous: previous);
} on InvalidParameterException catch (_) {
if (onCreate != null) {
dataObject = await onCreate();
if (onUpdateOrCreate != null) {
dataObject = await onUpdateOrCreate(null);
}
return createSingleReturn(dataObject);
}
}

Future<List<T>> getOrCreateManyOfOrg(List<T> dataObjects, {required bool obfuscate}) async {
return await Future.wait(dataObjects.map((element) => getOrCreateSingleOfOrg(element, obfuscate: obfuscate)));
Future<List<T>> updateOrCreateManyOfOrg(List<T> dataObjects, {required bool obfuscate}) async {
return await Future.wait(dataObjects.map((element) => updateOrCreateSingleOfOrg(element, obfuscate: obfuscate)));
}

static Future<T> getSingleFromDataTypeOfOrg<T extends Organizational>(String orgSyncId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,17 @@ class TeamClubAffiliationController extends ShelfController<TeamClubAffiliation>
return _singleton;
}

Future<TeamClubAffiliation?> getByTeamAndClubId({
required int teamId,
required int clubId,
required obfuscate,
}) async {
final many = await getMany(
conditions: ['team_id = @teamId', 'club_id = @clubId'],
substitutionValues: {'teamId': teamId, 'clubId': clubId},
obfuscate: obfuscate);
return many.singleOrNull;
}

TeamClubAffiliationController._internal() : super(tableName: 'team_club_affiliation');
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@ class TeamController extends OrganizationalController<Team> with ImportControlle
}

@override
Future<Response> import(Request request, User? user, String entityId) async {
updateLastImportUtcDateTime(entityId);
return Response.notFound('This operation is not supported yet!');
Future<void> import(int entityId, {String? message, bool obfuscate = true, bool useMock = false}) async {
throw UnimplementedError('This operation is not supported yet!');
}
}
Loading

0 comments on commit f194dd7

Please sign in to comment.