diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index ed6fa3c..5a809bf 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -17,12 +17,119 @@ C425D4122B6D249E002A7B48 /* PadelClubTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C425D4112B6D249E002A7B48 /* PadelClubTests.swift */; }; C425D41C2B6D249E002A7B48 /* PadelClubUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C425D41B2B6D249E002A7B48 /* PadelClubUITests.swift */; }; C425D41E2B6D249E002A7B48 /* PadelClubUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C425D41D2B6D249E002A7B48 /* PadelClubUITestsLaunchTests.swift */; }; + C4339BFB2CFF7D68004E5F09 /* ShareModelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4339BFA2CFF7D64004E5F09 /* ShareModelView.swift */; }; + C4339BFC2CFF7D68004E5F09 /* ShareModelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4339BFA2CFF7D64004E5F09 /* ShareModelView.swift */; }; + C4339BFD2CFF7D68004E5F09 /* ShareModelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4339BFA2CFF7D64004E5F09 /* ShareModelView.swift */; }; C4489BE22C05BF5000043F3D /* DebugSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4489BE12C05BF5000043F3D /* DebugSettingsView.swift */; }; C44B79112BBDA63A00906534 /* Locale+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44B79102BBDA63A00906534 /* Locale+Extensions.swift */; }; C45BAE3B2BC6DF10002EEC8A /* SyncedProducts.storekit in Resources */ = {isa = PBXBuildFile; fileRef = C45BAE3A2BC6DF10002EEC8A /* SyncedProducts.storekit */; }; C45BAE442BCA753E002EEC8A /* Purchase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45BAE432BCA753E002EEC8A /* Purchase.swift */; }; C4607A7D2C04DDE2004CB781 /* APICallsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4607A7C2C04DDE2004CB781 /* APICallsListView.swift */; }; + C471D1542D0C8FED0068091F /* Drawlog.json in Resources */ = {isa = PBXBuildFile; fileRef = C471D1532D0C8FE80068091F /* Drawlog.json */; }; + C471D1552D0C8FED0068091F /* Drawlog.json in Resources */ = {isa = PBXBuildFile; fileRef = C471D1532D0C8FE80068091F /* Drawlog.json */; }; + C471D1562D0C8FED0068091F /* Drawlog.json in Resources */ = {isa = PBXBuildFile; fileRef = C471D1532D0C8FE80068091F /* Drawlog.json */; }; + C471D1582D0C91FE0068091F /* BaseDrawLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = C471D1572D0C91FE0068091F /* BaseDrawLog.swift */; }; + C471D1592D0C91FE0068091F /* BaseDrawLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = C471D1572D0C91FE0068091F /* BaseDrawLog.swift */; }; + C471D15A2D0C91FF0068091F /* BaseDrawLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = C471D1572D0C91FE0068091F /* BaseDrawLog.swift */; }; + C488C7E92CC7D16F0082001F /* generator.py in Resources */ = {isa = PBXBuildFile; fileRef = C488C7E52CC7D1660082001F /* generator.py */; }; + C488C7EA2CC7D16F0082001F /* generator.py in Resources */ = {isa = PBXBuildFile; fileRef = C488C7E52CC7D1660082001F /* generator.py */; }; + C488C7EB2CC7D16F0082001F /* generator.py in Resources */ = {isa = PBXBuildFile; fileRef = C488C7E52CC7D1660082001F /* generator.py */; }; + C488C7F12CC7D22D0082001F /* Club.json in Sources */ = {isa = PBXBuildFile; fileRef = C488C7EC2CC7D2290082001F /* Club.json */; }; + C488C7F22CC7D22D0082001F /* Club.json in Sources */ = {isa = PBXBuildFile; fileRef = C488C7EC2CC7D2290082001F /* Club.json */; }; + C488C7FF2CC7DCB80082001F /* BaseClub.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C7FE2CC7DCB80082001F /* BaseClub.swift */; }; + C488C8002CC7DCB80082001F /* BaseClub.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C7FE2CC7DCB80082001F /* BaseClub.swift */; }; + C488C8012CC7DCB80082001F /* BaseClub.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C7FE2CC7DCB80082001F /* BaseClub.swift */; }; + C488C8032CC7E1E40082001F /* BaseCourt.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C8022CC7E1E40082001F /* BaseCourt.swift */; }; + C488C8042CC7E1E40082001F /* BaseCourt.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C8022CC7E1E40082001F /* BaseCourt.swift */; }; + C488C8052CC7E1E40082001F /* BaseCourt.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C8022CC7E1E40082001F /* BaseCourt.swift */; }; + C488C8202CC7E4240082001F /* Event.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C8162CC7E4240082001F /* Event.json */; }; + C488C8212CC7E4240082001F /* Court.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C8132CC7E4240082001F /* Court.json */; }; + C488C8222CC7E4240082001F /* Tournament.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C81F2CC7E4240082001F /* Tournament.json */; }; + C488C8232CC7E4240082001F /* CustomUser.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C8142CC7E4240082001F /* CustomUser.json */; }; + C488C8242CC7E4240082001F /* Round.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C81C2CC7E4240082001F /* Round.json */; }; + C488C8252CC7E4240082001F /* MatchScheduler.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C8192CC7E4240082001F /* MatchScheduler.json */; }; + C488C8262CC7E4240082001F /* DateInterval.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C8152CC7E4240082001F /* DateInterval.json */; }; + C488C8272CC7E4240082001F /* TeamRegistration.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C81D2CC7E4240082001F /* TeamRegistration.json */; }; + C488C8282CC7E4240082001F /* GroupStage.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C8172CC7E4240082001F /* GroupStage.json */; }; + C488C8292CC7E4240082001F /* MonthData.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C81A2CC7E4240082001F /* MonthData.json */; }; + C488C82A2CC7E4240082001F /* TeamScore.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C81E2CC7E4240082001F /* TeamScore.json */; }; + C488C82B2CC7E4240082001F /* Match.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C8182CC7E4240082001F /* Match.json */; }; + C488C82C2CC7E4240082001F /* PlayerRegistration.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C81B2CC7E4240082001F /* PlayerRegistration.json */; }; + C488C82D2CC7E4240082001F /* BaseDateInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C8072CC7E4240082001F /* BaseDateInterval.swift */; }; + C488C82E2CC7E4240082001F /* BaseMonthData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C80C2CC7E4240082001F /* BaseMonthData.swift */; }; + C488C82F2CC7E4240082001F /* BaseTeamRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C80F2CC7E4240082001F /* BaseTeamRegistration.swift */; }; + C488C8302CC7E4240082001F /* BaseGroupStage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C8092CC7E4240082001F /* BaseGroupStage.swift */; }; + C488C8312CC7E4240082001F /* BaseCustomUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C8062CC7E4240082001F /* BaseCustomUser.swift */; }; + C488C8322CC7E4240082001F /* BaseMatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C80A2CC7E4240082001F /* BaseMatch.swift */; }; + C488C8332CC7E4240082001F /* BaseEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C8082CC7E4240082001F /* BaseEvent.swift */; }; + C488C8342CC7E4240082001F /* BaseRound.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C80E2CC7E4240082001F /* BaseRound.swift */; }; + C488C8352CC7E4240082001F /* BaseMatchScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C80B2CC7E4240082001F /* BaseMatchScheduler.swift */; }; + C488C8372CC7E4240082001F /* BasePlayerRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C80D2CC7E4240082001F /* BasePlayerRegistration.swift */; }; + C488C8382CC7E4240082001F /* BaseTeamScore.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C8102CC7E4240082001F /* BaseTeamScore.swift */; }; + C488C8392CC7E4240082001F /* BaseTournament.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C8112CC7E4240082001F /* BaseTournament.swift */; }; + C488C83A2CC7E4240082001F /* Event.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C8162CC7E4240082001F /* Event.json */; }; + C488C83B2CC7E4240082001F /* Court.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C8132CC7E4240082001F /* Court.json */; }; + C488C83C2CC7E4240082001F /* Tournament.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C81F2CC7E4240082001F /* Tournament.json */; }; + C488C83D2CC7E4240082001F /* CustomUser.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C8142CC7E4240082001F /* CustomUser.json */; }; + C488C83E2CC7E4240082001F /* Round.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C81C2CC7E4240082001F /* Round.json */; }; + C488C83F2CC7E4240082001F /* MatchScheduler.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C8192CC7E4240082001F /* MatchScheduler.json */; }; + C488C8402CC7E4240082001F /* DateInterval.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C8152CC7E4240082001F /* DateInterval.json */; }; + C488C8412CC7E4240082001F /* TeamRegistration.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C81D2CC7E4240082001F /* TeamRegistration.json */; }; + C488C8422CC7E4240082001F /* GroupStage.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C8172CC7E4240082001F /* GroupStage.json */; }; + C488C8432CC7E4240082001F /* MonthData.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C81A2CC7E4240082001F /* MonthData.json */; }; + C488C8442CC7E4240082001F /* TeamScore.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C81E2CC7E4240082001F /* TeamScore.json */; }; + C488C8452CC7E4240082001F /* Match.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C8182CC7E4240082001F /* Match.json */; }; + C488C8462CC7E4240082001F /* PlayerRegistration.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C81B2CC7E4240082001F /* PlayerRegistration.json */; }; + C488C8472CC7E4240082001F /* BaseDateInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C8072CC7E4240082001F /* BaseDateInterval.swift */; }; + C488C8482CC7E4240082001F /* BaseMonthData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C80C2CC7E4240082001F /* BaseMonthData.swift */; }; + C488C8492CC7E4240082001F /* BaseTeamRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C80F2CC7E4240082001F /* BaseTeamRegistration.swift */; }; + C488C84A2CC7E4240082001F /* BaseGroupStage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C8092CC7E4240082001F /* BaseGroupStage.swift */; }; + C488C84B2CC7E4240082001F /* BaseCustomUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C8062CC7E4240082001F /* BaseCustomUser.swift */; }; + C488C84C2CC7E4240082001F /* BaseMatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C80A2CC7E4240082001F /* BaseMatch.swift */; }; + C488C84D2CC7E4240082001F /* BaseEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C8082CC7E4240082001F /* BaseEvent.swift */; }; + C488C84E2CC7E4240082001F /* BaseRound.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C80E2CC7E4240082001F /* BaseRound.swift */; }; + C488C84F2CC7E4240082001F /* BaseMatchScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C80B2CC7E4240082001F /* BaseMatchScheduler.swift */; }; + C488C8512CC7E4240082001F /* BasePlayerRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C80D2CC7E4240082001F /* BasePlayerRegistration.swift */; }; + C488C8522CC7E4240082001F /* BaseTeamScore.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C8102CC7E4240082001F /* BaseTeamScore.swift */; }; + C488C8532CC7E4240082001F /* BaseTournament.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C8112CC7E4240082001F /* BaseTournament.swift */; }; + C488C8542CC7E4240082001F /* BaseDateInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C8072CC7E4240082001F /* BaseDateInterval.swift */; }; + C488C8552CC7E4240082001F /* BaseMonthData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C80C2CC7E4240082001F /* BaseMonthData.swift */; }; + C488C8562CC7E4240082001F /* BaseTeamRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C80F2CC7E4240082001F /* BaseTeamRegistration.swift */; }; + C488C8572CC7E4240082001F /* BaseGroupStage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C8092CC7E4240082001F /* BaseGroupStage.swift */; }; + C488C8582CC7E4240082001F /* BaseCustomUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C8062CC7E4240082001F /* BaseCustomUser.swift */; }; + C488C8592CC7E4240082001F /* BaseMatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C80A2CC7E4240082001F /* BaseMatch.swift */; }; + C488C85A2CC7E4240082001F /* BaseEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C8082CC7E4240082001F /* BaseEvent.swift */; }; + C488C85B2CC7E4240082001F /* BaseRound.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C80E2CC7E4240082001F /* BaseRound.swift */; }; + C488C85C2CC7E4240082001F /* BaseMatchScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C80B2CC7E4240082001F /* BaseMatchScheduler.swift */; }; + C488C85E2CC7E4240082001F /* BasePlayerRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C80D2CC7E4240082001F /* BasePlayerRegistration.swift */; }; + C488C85F2CC7E4240082001F /* BaseTeamScore.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C8102CC7E4240082001F /* BaseTeamScore.swift */; }; + C488C8602CC7E4240082001F /* BaseTournament.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C8112CC7E4240082001F /* BaseTournament.swift */; }; + C488C8612CC7E4240082001F /* Event.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C8162CC7E4240082001F /* Event.json */; }; + C488C8622CC7E4240082001F /* Court.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C8132CC7E4240082001F /* Court.json */; }; + C488C8632CC7E4240082001F /* Tournament.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C81F2CC7E4240082001F /* Tournament.json */; }; + C488C8642CC7E4240082001F /* CustomUser.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C8142CC7E4240082001F /* CustomUser.json */; }; + C488C8652CC7E4240082001F /* Round.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C81C2CC7E4240082001F /* Round.json */; }; + C488C8662CC7E4240082001F /* MatchScheduler.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C8192CC7E4240082001F /* MatchScheduler.json */; }; + C488C8672CC7E4240082001F /* DateInterval.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C8152CC7E4240082001F /* DateInterval.json */; }; + C488C8682CC7E4240082001F /* TeamRegistration.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C81D2CC7E4240082001F /* TeamRegistration.json */; }; + C488C8692CC7E4240082001F /* GroupStage.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C8172CC7E4240082001F /* GroupStage.json */; }; + C488C86A2CC7E4240082001F /* MonthData.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C81A2CC7E4240082001F /* MonthData.json */; }; + C488C86B2CC7E4240082001F /* TeamScore.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C81E2CC7E4240082001F /* TeamScore.json */; }; + C488C86C2CC7E4240082001F /* Match.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C8182CC7E4240082001F /* Match.json */; }; + C488C86D2CC7E4240082001F /* PlayerRegistration.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C81B2CC7E4240082001F /* PlayerRegistration.json */; }; + C488C8712CC816410082001F /* Purchase.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C8702CC816410082001F /* Purchase.json */; }; + C488C8722CC816410082001F /* BasePurchase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C86F2CC816410082001F /* BasePurchase.swift */; }; + C488C8732CC816410082001F /* BasePurchase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C86F2CC816410082001F /* BasePurchase.swift */; }; + C488C8742CC816410082001F /* Purchase.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C8702CC816410082001F /* Purchase.json */; }; + C488C8752CC816410082001F /* Purchase.json in Resources */ = {isa = PBXBuildFile; fileRef = C488C8702CC816410082001F /* Purchase.json */; }; + C488C8762CC816410082001F /* BasePurchase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C86F2CC816410082001F /* BasePurchase.swift */; }; + C488C8822CCBE8FC0082001F /* NetworkStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C8812CCBE8FC0082001F /* NetworkStatusView.swift */; }; + C488C8832CCBE8FC0082001F /* NetworkStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C8812CCBE8FC0082001F /* NetworkStatusView.swift */; }; + C488C8842CCBE8FC0082001F /* NetworkStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C8812CCBE8FC0082001F /* NetworkStatusView.swift */; }; C493B37E2C10AD3600862481 /* LoadingViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = C493B37D2C10AD3600862481 /* LoadingViewModifier.swift */; }; + C49C73142D5B98D8008DD299 /* PlayerPaymentType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49C73132D5B98D7008DD299 /* PlayerPaymentType.swift */; }; + C49C73152D5B98D8008DD299 /* PlayerPaymentType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49C73132D5B98D7008DD299 /* PlayerPaymentType.swift */; }; + C49C73162D5B98D8008DD299 /* PlayerPaymentType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49C73132D5B98D7008DD299 /* PlayerPaymentType.swift */; }; C49C731E2D5E3BE8008DD299 /* VersionComparator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49C731D2D5E3BE4008DD299 /* VersionComparator.swift */; }; C49C731F2D5E3BE8008DD299 /* VersionComparator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49C731D2D5E3BE4008DD299 /* VersionComparator.swift */; }; C49C73202D5E3BE8008DD299 /* VersionComparator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49C731D2D5E3BE4008DD299 /* VersionComparator.swift */; }; @@ -34,6 +141,9 @@ C49EF03C2BE15AF80077B5AA /* String+Crypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF03B2BE15AF80077B5AA /* String+Crypto.swift */; }; C49EF0422BE23BF50077B5AA /* PaymentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF0412BE23BF50077B5AA /* PaymentTests.swift */; }; C49EF0442BE286780077B5AA /* CryptoKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF0432BE286780077B5AA /* CryptoKey.swift */; }; + C4A36F582CE2626A003738C6 /* TournamentLibrary.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A36F572CE2626A003738C6 /* TournamentLibrary.swift */; }; + C4A36F592CE2626A003738C6 /* TournamentLibrary.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A36F572CE2626A003738C6 /* TournamentLibrary.swift */; }; + C4A36F5A2CE2626A003738C6 /* TournamentLibrary.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A36F572CE2626A003738C6 /* TournamentLibrary.swift */; }; C4A47D5A2B6D383C00ADC637 /* Tournament.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D592B6D383C00ADC637 /* Tournament.swift */; }; C4A47D5E2B6D38EC00ADC637 /* DataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D5D2B6D38EC00ADC637 /* DataStore.swift */; }; C4A47D632B6D3D6500ADC637 /* Club.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D622B6D3D6500ADC637 /* Club.swift */; }; @@ -45,12 +155,13 @@ C4A47D9F2B7D0BCE00ADC637 /* StepperView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D9E2B7D0BCE00ADC637 /* StepperView.swift */; }; C4A47DA62B83948E00ADC637 /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47DA52B83948E00ADC637 /* LoginView.swift */; }; C4A47DA92B85F82100ADC637 /* ChangePasswordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47DA82B85F82100ADC637 /* ChangePasswordView.swift */; }; - C4A47DAD2B85FCCD00ADC637 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47DAC2B85FCCD00ADC637 /* User.swift */; }; + C4A47DAD2B85FCCD00ADC637 /* CustomUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47DAC2B85FCCD00ADC637 /* CustomUser.swift */; }; C4A47DB32B86387500ADC637 /* AccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47DB22B86387500ADC637 /* AccountView.swift */; }; C4B3A1552C2581DA0078EAA8 /* Patcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B3A1542C2581DA0078EAA8 /* Patcher.swift */; }; C4C01D982C481C0C0059087C /* CapsuleViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C01D972C481C0C0059087C /* CapsuleViewModifier.swift */; }; C4C33F762C9B1ED4006316DE /* CodingContainer+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C33F752C9B1EC5006316DE /* CodingContainer+Extensions.swift */; }; C4C33F772C9B1ED4006316DE /* CodingContainer+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C33F752C9B1EC5006316DE /* CodingContainer+Extensions.swift */; }; + C4D477992CB6704C0077713D /* SynchronizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D477982CB6704C0077713D /* SynchronizationTests.swift */; }; C4EC6F572BE92CAC000CEAB4 /* local.plist in Resources */ = {isa = PBXBuildFile; fileRef = C4EC6F562BE92CAC000CEAB4 /* local.plist */; }; C4EC6F592BE92D88000CEAB4 /* PListReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC6F582BE92D88000CEAB4 /* PListReader.swift */; }; C4FC2E272C2AABC90021F3BF /* PasswordField.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FC2E262C2AABC90021F3BF /* PasswordField.swift */; }; @@ -332,7 +443,7 @@ FF4CBFFA2C996C0600151637 /* TournamentScheduleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF0E0B6C2BC254C6005F00A9 /* TournamentScheduleView.swift */; }; FF4CBFFB2C996C0600151637 /* MatchFormatStorageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AF02BD1AEBD00A86CF8 /* MatchFormatStorageView.swift */; }; FF4CBFFC2C996C0600151637 /* UmpireView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3F74F52B919E45004CFE0E /* UmpireView.swift */; }; - FF4CBFFD2C996C0600151637 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47DAC2B85FCCD00ADC637 /* User.swift */; }; + FF4CBFFD2C996C0600151637 /* CustomUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47DAC2B85FCCD00ADC637 /* CustomUser.swift */; }; FF4CBFFE2C996C0600151637 /* MatchSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D002BAEF0B400A9A3BD /* MatchSummaryView.swift */; }; FF4CBFFF2C996C0600151637 /* TournamentDurationManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26442BAE0A3400650388 /* TournamentDurationManagerView.swift */; }; FF4CC0002C996C0600151637 /* MockData.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC5522BAB354A00FD8220 /* MockData.swift */; }; @@ -644,7 +755,7 @@ FF70FB792C90584900129CC2 /* TournamentScheduleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF0E0B6C2BC254C6005F00A9 /* TournamentScheduleView.swift */; }; FF70FB7A2C90584900129CC2 /* MatchFormatStorageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AF02BD1AEBD00A86CF8 /* MatchFormatStorageView.swift */; }; FF70FB7B2C90584900129CC2 /* UmpireView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3F74F52B919E45004CFE0E /* UmpireView.swift */; }; - FF70FB7C2C90584900129CC2 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47DAC2B85FCCD00ADC637 /* User.swift */; }; + FF70FB7C2C90584900129CC2 /* CustomUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47DAC2B85FCCD00ADC637 /* CustomUser.swift */; }; FF70FB7D2C90584900129CC2 /* MatchSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D002BAEF0B400A9A3BD /* MatchSummaryView.swift */; }; FF70FB7E2C90584900129CC2 /* TournamentDurationManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26442BAE0A3400650388 /* TournamentDurationManagerView.swift */; }; FF70FB7F2C90584900129CC2 /* MockData.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC5522BAB354A00FD8220 /* MockData.swift */; }; @@ -945,12 +1056,48 @@ C425D4172B6D249E002A7B48 /* PadelClubUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PadelClubUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; C425D41B2B6D249E002A7B48 /* PadelClubUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadelClubUITests.swift; sourceTree = ""; }; C425D41D2B6D249E002A7B48 /* PadelClubUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadelClubUITestsLaunchTests.swift; sourceTree = ""; }; + C4339BFA2CFF7D64004E5F09 /* ShareModelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareModelView.swift; sourceTree = ""; }; C4489BE12C05BF5000043F3D /* DebugSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugSettingsView.swift; sourceTree = ""; }; C44B79102BBDA63A00906534 /* Locale+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Locale+Extensions.swift"; sourceTree = ""; }; C45BAE3A2BC6DF10002EEC8A /* SyncedProducts.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = SyncedProducts.storekit; sourceTree = ""; }; C45BAE432BCA753E002EEC8A /* Purchase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Purchase.swift; sourceTree = ""; }; C4607A7C2C04DDE2004CB781 /* APICallsListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APICallsListView.swift; sourceTree = ""; }; + C471D1532D0C8FE80068091F /* Drawlog.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = Drawlog.json; sourceTree = ""; }; + C471D1572D0C91FE0068091F /* BaseDrawLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseDrawLog.swift; sourceTree = ""; }; + C488C7E52CC7D1660082001F /* generator.py */ = {isa = PBXFileReference; lastKnownFileType = text.script.python; path = generator.py; sourceTree = ""; }; + C488C7EC2CC7D2290082001F /* Club.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = Club.json; sourceTree = ""; }; + C488C7FE2CC7DCB80082001F /* BaseClub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseClub.swift; sourceTree = ""; }; + C488C8022CC7E1E40082001F /* BaseCourt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseCourt.swift; sourceTree = ""; }; + C488C8062CC7E4240082001F /* BaseCustomUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseCustomUser.swift; sourceTree = ""; }; + C488C8072CC7E4240082001F /* BaseDateInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseDateInterval.swift; sourceTree = ""; }; + C488C8082CC7E4240082001F /* BaseEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseEvent.swift; sourceTree = ""; }; + C488C8092CC7E4240082001F /* BaseGroupStage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseGroupStage.swift; sourceTree = ""; }; + C488C80A2CC7E4240082001F /* BaseMatch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseMatch.swift; sourceTree = ""; }; + C488C80B2CC7E4240082001F /* BaseMatchScheduler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseMatchScheduler.swift; sourceTree = ""; }; + C488C80C2CC7E4240082001F /* BaseMonthData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseMonthData.swift; sourceTree = ""; }; + C488C80D2CC7E4240082001F /* BasePlayerRegistration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasePlayerRegistration.swift; sourceTree = ""; }; + C488C80E2CC7E4240082001F /* BaseRound.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseRound.swift; sourceTree = ""; }; + C488C80F2CC7E4240082001F /* BaseTeamRegistration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseTeamRegistration.swift; sourceTree = ""; }; + C488C8102CC7E4240082001F /* BaseTeamScore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseTeamScore.swift; sourceTree = ""; }; + C488C8112CC7E4240082001F /* BaseTournament.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseTournament.swift; sourceTree = ""; }; + C488C8132CC7E4240082001F /* Court.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = Court.json; sourceTree = ""; }; + C488C8142CC7E4240082001F /* CustomUser.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = CustomUser.json; sourceTree = ""; }; + C488C8152CC7E4240082001F /* DateInterval.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = DateInterval.json; sourceTree = ""; }; + C488C8162CC7E4240082001F /* Event.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = Event.json; sourceTree = ""; }; + C488C8172CC7E4240082001F /* GroupStage.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = GroupStage.json; sourceTree = ""; }; + C488C8182CC7E4240082001F /* Match.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = Match.json; sourceTree = ""; }; + C488C8192CC7E4240082001F /* MatchScheduler.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = MatchScheduler.json; sourceTree = ""; }; + C488C81A2CC7E4240082001F /* MonthData.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = MonthData.json; sourceTree = ""; }; + C488C81B2CC7E4240082001F /* PlayerRegistration.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = PlayerRegistration.json; sourceTree = ""; }; + C488C81C2CC7E4240082001F /* Round.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = Round.json; sourceTree = ""; }; + C488C81D2CC7E4240082001F /* TeamRegistration.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = TeamRegistration.json; sourceTree = ""; }; + C488C81E2CC7E4240082001F /* TeamScore.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = TeamScore.json; sourceTree = ""; }; + C488C81F2CC7E4240082001F /* Tournament.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = Tournament.json; sourceTree = ""; }; + C488C86F2CC816410082001F /* BasePurchase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasePurchase.swift; sourceTree = ""; }; + C488C8702CC816410082001F /* Purchase.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = Purchase.json; sourceTree = ""; }; + C488C8812CCBE8FC0082001F /* NetworkStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkStatusView.swift; sourceTree = ""; }; C493B37D2C10AD3600862481 /* LoadingViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingViewModifier.swift; sourceTree = ""; }; + C49C73132D5B98D7008DD299 /* PlayerPaymentType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerPaymentType.swift; sourceTree = ""; }; C49C731D2D5E3BE4008DD299 /* VersionComparator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionComparator.swift; sourceTree = ""; }; C49EF0182BD694290077B5AA /* PurchaseListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchaseListView.swift; sourceTree = ""; }; C49EF01A2BD6A1E80077B5AA /* URLs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLs.swift; sourceTree = ""; }; @@ -959,6 +1106,7 @@ C49EF03B2BE15AF80077B5AA /* String+Crypto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Crypto.swift"; sourceTree = ""; }; C49EF0412BE23BF50077B5AA /* PaymentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentTests.swift; sourceTree = ""; }; C49EF0432BE286780077B5AA /* CryptoKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CryptoKey.swift; sourceTree = ""; }; + C4A36F572CE2626A003738C6 /* TournamentLibrary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentLibrary.swift; sourceTree = ""; }; C4A47D592B6D383C00ADC637 /* Tournament.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tournament.swift; sourceTree = ""; }; C4A47D5D2B6D38EC00ADC637 /* DataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStore.swift; sourceTree = ""; }; C4A47D622B6D3D6500ADC637 /* Club.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Club.swift; sourceTree = ""; }; @@ -970,11 +1118,12 @@ C4A47D9E2B7D0BCE00ADC637 /* StepperView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepperView.swift; sourceTree = ""; }; C4A47DA52B83948E00ADC637 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = ""; }; C4A47DA82B85F82100ADC637 /* ChangePasswordView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangePasswordView.swift; sourceTree = ""; }; - C4A47DAC2B85FCCD00ADC637 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; + C4A47DAC2B85FCCD00ADC637 /* CustomUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomUser.swift; sourceTree = ""; }; C4A47DB22B86387500ADC637 /* AccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountView.swift; sourceTree = ""; }; C4B3A1542C2581DA0078EAA8 /* Patcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Patcher.swift; sourceTree = ""; }; C4C01D972C481C0C0059087C /* CapsuleViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapsuleViewModifier.swift; sourceTree = ""; }; C4C33F752C9B1EC5006316DE /* CodingContainer+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CodingContainer+Extensions.swift"; sourceTree = ""; }; + C4D477982CB6704C0077713D /* SynchronizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SynchronizationTests.swift; sourceTree = ""; }; C4EC6F562BE92CAC000CEAB4 /* local.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = local.plist; sourceTree = ""; }; C4EC6F582BE92D88000CEAB4 /* PListReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PListReader.swift; sourceTree = ""; }; C4FC2E262C2AABC90021F3BF /* PasswordField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordField.swift; sourceTree = ""; }; @@ -1391,6 +1540,7 @@ C411C9C22BEBA453003017AD /* ServerDataTests.swift */, C411C9C82BF219CB003017AD /* UserDataTests.swift */, C411C9CF2BF38F41003017AD /* TokenExemptionTests.swift */, + C4D477982CB6704C0077713D /* SynchronizationTests.swift */, ); path = PadelClubTests; sourceTree = ""; @@ -1412,18 +1562,61 @@ name = Frameworks; sourceTree = ""; }; + C488C7FD2CC7D6CD0082001F /* Gen */ = { + isa = PBXGroup; + children = ( + C488C7E52CC7D1660082001F /* generator.py */, + C488C7EC2CC7D2290082001F /* Club.json */, + C488C8132CC7E4240082001F /* Court.json */, + C488C8142CC7E4240082001F /* CustomUser.json */, + C488C8152CC7E4240082001F /* DateInterval.json */, + C471D1532D0C8FE80068091F /* Drawlog.json */, + C488C8162CC7E4240082001F /* Event.json */, + C488C8172CC7E4240082001F /* GroupStage.json */, + C488C8182CC7E4240082001F /* Match.json */, + C488C8192CC7E4240082001F /* MatchScheduler.json */, + C488C81A2CC7E4240082001F /* MonthData.json */, + C488C81B2CC7E4240082001F /* PlayerRegistration.json */, + C488C8702CC816410082001F /* Purchase.json */, + C488C81C2CC7E4240082001F /* Round.json */, + C488C81D2CC7E4240082001F /* TeamRegistration.json */, + C488C81E2CC7E4240082001F /* TeamScore.json */, + C488C81F2CC7E4240082001F /* Tournament.json */, + C488C7FE2CC7DCB80082001F /* BaseClub.swift */, + C488C8022CC7E1E40082001F /* BaseCourt.swift */, + C488C8062CC7E4240082001F /* BaseCustomUser.swift */, + C488C8072CC7E4240082001F /* BaseDateInterval.swift */, + C471D1572D0C91FE0068091F /* BaseDrawLog.swift */, + C488C8082CC7E4240082001F /* BaseEvent.swift */, + C488C8092CC7E4240082001F /* BaseGroupStage.swift */, + C488C80A2CC7E4240082001F /* BaseMatch.swift */, + C488C80B2CC7E4240082001F /* BaseMatchScheduler.swift */, + C488C80C2CC7E4240082001F /* BaseMonthData.swift */, + C488C80D2CC7E4240082001F /* BasePlayerRegistration.swift */, + C488C86F2CC816410082001F /* BasePurchase.swift */, + C488C80E2CC7E4240082001F /* BaseRound.swift */, + C488C80F2CC7E4240082001F /* BaseTeamRegistration.swift */, + C488C8102CC7E4240082001F /* BaseTeamScore.swift */, + C488C8112CC7E4240082001F /* BaseTournament.swift */, + ); + path = Gen; + sourceTree = ""; + }; C4A47D5F2B6D3B2D00ADC637 /* Data */ = { isa = PBXGroup; children = ( + C488C7FD2CC7D6CD0082001F /* Gen */, C411C9CC2BF21DAF003017AD /* README.md */, C4A47D5D2B6D38EC00ADC637 /* DataStore.swift */, C4FC2E2A2C2C0E4D0021F3BF /* TournamentStore.swift */, - C4A47DAC2B85FCCD00ADC637 /* User.swift */, + C4A36F572CE2626A003738C6 /* TournamentLibrary.swift */, + C4A47DAC2B85FCCD00ADC637 /* CustomUser.swift */, C4A47D592B6D383C00ADC637 /* Tournament.swift */, FF967CE72BAEC70100A9A3BD /* GroupStage.swift */, FF967CED2BAECBD700A9A3BD /* Round.swift */, FF967CEB2BAECB9900A9A3BD /* Match.swift */, FF967CF12BAECC0B00A9A3BD /* PlayerRegistration.swift */, + C49C73132D5B98D7008DD299 /* PlayerPaymentType.swift */, FF967CF02BAECC0B00A9A3BD /* TeamRegistration.swift */, FF967CEF2BAECC0A00A9A3BD /* TeamScore.swift */, C4A47D622B6D3D6500ADC637 /* Club.swift */, @@ -1468,6 +1661,7 @@ C4A47D852B7BA33F00ADC637 /* User */ = { isa = PBXGroup; children = ( + C4339BFA2CFF7D64004E5F09 /* ShareModelView.swift */, C4A47DB22B86387500ADC637 /* AccountView.swift */, C4A47DA82B85F82100ADC637 /* ChangePasswordView.swift */, C4A47DA52B83948E00ADC637 /* LoginView.swift */, @@ -1751,6 +1945,7 @@ FF3F74F52B919E45004CFE0E /* UmpireView.swift */, FFA252AC2CDB734A0074E63F /* UmpireStatisticView.swift */, FFD783FE2B91BA42000F62A6 /* PadelClubView.swift */, + C488C8812CCBE8FC0082001F /* NetworkStatusView.swift */, ); path = Umpire; sourceTree = ""; @@ -2255,8 +2450,24 @@ FF1F4B842BFA02A4000B4573 /* groupstagescore-template.html in Resources */, FF1F4B852BFA02A4000B4573 /* player-template.html in Resources */, FF1F4B862BFA02A4000B4573 /* groupstagerow-template.html in Resources */, + C488C8712CC816410082001F /* Purchase.json in Resources */, FF1F4B872BFA02A4000B4573 /* hiddenplayer-template.html in Resources */, + C488C7EB2CC7D16F0082001F /* generator.py in Resources */, FF1F4B882BFA02A4000B4573 /* bracket-template.html in Resources */, + C488C8202CC7E4240082001F /* Event.json in Resources */, + C488C8212CC7E4240082001F /* Court.json in Resources */, + C488C8222CC7E4240082001F /* Tournament.json in Resources */, + C471D1552D0C8FED0068091F /* Drawlog.json in Resources */, + C488C8232CC7E4240082001F /* CustomUser.json in Resources */, + C488C8242CC7E4240082001F /* Round.json in Resources */, + C488C8252CC7E4240082001F /* MatchScheduler.json in Resources */, + C488C8262CC7E4240082001F /* DateInterval.json in Resources */, + C488C8272CC7E4240082001F /* TeamRegistration.json in Resources */, + C488C8282CC7E4240082001F /* GroupStage.json in Resources */, + C488C8292CC7E4240082001F /* MonthData.json in Resources */, + C488C82A2CC7E4240082001F /* TeamScore.json in Resources */, + C488C82B2CC7E4240082001F /* Match.json in Resources */, + C488C82C2CC7E4240082001F /* PlayerRegistration.json in Resources */, FF1F4B892BFA02A4000B4573 /* groupstagecol-template.html in Resources */, FF1F4B8A2BFA02A4000B4573 /* groupstage-template.html in Resources */, FF1F4B8B2BFA02A4000B4573 /* groupstageentrant-template.html in Resources */, @@ -2294,8 +2505,24 @@ FF4CC03D2C996C0600151637 /* groupstagescore-template.html in Resources */, FF4CC03E2C996C0600151637 /* player-template.html in Resources */, FF4CC03F2C996C0600151637 /* groupstagerow-template.html in Resources */, + C488C8752CC816410082001F /* Purchase.json in Resources */, FF4CC0402C996C0600151637 /* hiddenplayer-template.html in Resources */, + C488C7E92CC7D16F0082001F /* generator.py in Resources */, FF4CC0412C996C0600151637 /* bracket-template.html in Resources */, + C488C83A2CC7E4240082001F /* Event.json in Resources */, + C488C83B2CC7E4240082001F /* Court.json in Resources */, + C488C83C2CC7E4240082001F /* Tournament.json in Resources */, + C471D1562D0C8FED0068091F /* Drawlog.json in Resources */, + C488C83D2CC7E4240082001F /* CustomUser.json in Resources */, + C488C83E2CC7E4240082001F /* Round.json in Resources */, + C488C83F2CC7E4240082001F /* MatchScheduler.json in Resources */, + C488C8402CC7E4240082001F /* DateInterval.json in Resources */, + C488C8412CC7E4240082001F /* TeamRegistration.json in Resources */, + C488C8422CC7E4240082001F /* GroupStage.json in Resources */, + C488C8432CC7E4240082001F /* MonthData.json in Resources */, + C488C8442CC7E4240082001F /* TeamScore.json in Resources */, + C488C8452CC7E4240082001F /* Match.json in Resources */, + C488C8462CC7E4240082001F /* PlayerRegistration.json in Resources */, FF4CC0422C996C0600151637 /* groupstagecol-template.html in Resources */, FF4CC0432C996C0600151637 /* groupstage-template.html in Resources */, FF4CC0442C996C0600151637 /* groupstageentrant-template.html in Resources */, @@ -2319,8 +2546,24 @@ FF70FBBC2C90584900129CC2 /* groupstagescore-template.html in Resources */, FF70FBBD2C90584900129CC2 /* player-template.html in Resources */, FF70FBBE2C90584900129CC2 /* groupstagerow-template.html in Resources */, + C488C8742CC816410082001F /* Purchase.json in Resources */, FF70FBBF2C90584900129CC2 /* hiddenplayer-template.html in Resources */, + C488C7EA2CC7D16F0082001F /* generator.py in Resources */, FF70FBC02C90584900129CC2 /* bracket-template.html in Resources */, + C488C8612CC7E4240082001F /* Event.json in Resources */, + C488C8622CC7E4240082001F /* Court.json in Resources */, + C488C8632CC7E4240082001F /* Tournament.json in Resources */, + C471D1542D0C8FED0068091F /* Drawlog.json in Resources */, + C488C8642CC7E4240082001F /* CustomUser.json in Resources */, + C488C8652CC7E4240082001F /* Round.json in Resources */, + C488C8662CC7E4240082001F /* MatchScheduler.json in Resources */, + C488C8672CC7E4240082001F /* DateInterval.json in Resources */, + C488C8682CC7E4240082001F /* TeamRegistration.json in Resources */, + C488C8692CC7E4240082001F /* GroupStage.json in Resources */, + C488C86A2CC7E4240082001F /* MonthData.json in Resources */, + C488C86B2CC7E4240082001F /* TeamScore.json in Resources */, + C488C86C2CC7E4240082001F /* Match.json in Resources */, + C488C86D2CC7E4240082001F /* PlayerRegistration.json in Resources */, FF70FBC12C90584900129CC2 /* groupstagecol-template.html in Resources */, FF70FBC22C90584900129CC2 /* groupstage-template.html in Resources */, FF70FBC32C90584900129CC2 /* groupstageentrant-template.html in Resources */, @@ -2353,6 +2596,7 @@ C4A47D9F2B7D0BCE00ADC637 /* StepperView.swift in Sources */, FFC83D4F2BB807D100750834 /* RoundsView.swift in Sources */, FF1CBC1B2BB53D1F0036DAAB /* FederalTournament.swift in Sources */, + C4339BFB2CFF7D68004E5F09 /* ShareModelView.swift in Sources */, FF8F26412BADFC8700650388 /* TournamentInitView.swift in Sources */, C4A47D8A2B7BBB6500ADC637 /* SubscriptionView.swift in Sources */, FFD655D82C8DE27400E5B35E /* TournamentLookUpView.swift in Sources */, @@ -2377,6 +2621,7 @@ FF7091682B90F79F00AB08DA /* TournamentCellView.swift in Sources */, FF6EC9042B9479F500EA7F5A /* Sequence+Extensions.swift in Sources */, FF9267FA2BCE78EC0080F940 /* CashierDetailView.swift in Sources */, + C488C8722CC816410082001F /* BasePurchase.swift in Sources */, FFE103082C353B7600684FC9 /* EventClubSettingsView.swift in Sources */, C4A47DB32B86387500ADC637 /* AccountView.swift in Sources */, FFCEDA4C2C2C08EA00F8C0F2 /* PlayersWithoutContactView.swift in Sources */, @@ -2399,6 +2644,19 @@ FF8F263B2BAD528600650388 /* EventCreationView.swift in Sources */, FFC1E1082BAC29FC008D6F59 /* LocationManager.swift in Sources */, C4C01D982C481C0C0059087C /* CapsuleViewModifier.swift in Sources */, + C488C82D2CC7E4240082001F /* BaseDateInterval.swift in Sources */, + C488C82E2CC7E4240082001F /* BaseMonthData.swift in Sources */, + C471D1592D0C91FE0068091F /* BaseDrawLog.swift in Sources */, + C488C82F2CC7E4240082001F /* BaseTeamRegistration.swift in Sources */, + C488C8302CC7E4240082001F /* BaseGroupStage.swift in Sources */, + C488C8312CC7E4240082001F /* BaseCustomUser.swift in Sources */, + C488C8322CC7E4240082001F /* BaseMatch.swift in Sources */, + C488C8332CC7E4240082001F /* BaseEvent.swift in Sources */, + C488C8342CC7E4240082001F /* BaseRound.swift in Sources */, + C488C8352CC7E4240082001F /* BaseMatchScheduler.swift in Sources */, + C488C8372CC7E4240082001F /* BasePlayerRegistration.swift in Sources */, + C488C8382CC7E4240082001F /* BaseTeamScore.swift in Sources */, + C488C8392CC7E4240082001F /* BaseTournament.swift in Sources */, FF6087EC2BE26A2F004E1E47 /* BroadcastView.swift in Sources */, FFF964552BC266CF00EEF017 /* SchedulerView.swift in Sources */, FFA1B1292BB71773006CE248 /* PadelClubButtonView.swift in Sources */, @@ -2436,6 +2694,7 @@ FF4AB6BB2B9256D50002987F /* SearchViewModel.swift in Sources */, FF967CF32BAECC0B00A9A3BD /* PlayerRegistration.swift in Sources */, FF4AB6BF2B92577A0002987F /* ImportedPlayerView.swift in Sources */, + C4A36F5A2CE2626A003738C6 /* TournamentLibrary.swift in Sources */, FF1162872BD004AD000C4809 /* EditingTeamView.swift in Sources */, FF3A74332D37DCF2007E3032 /* InscriptionLegendView.swift in Sources */, FF6EC9062B947A1000EA7F5A /* NetworkManagerError.swift in Sources */, @@ -2445,6 +2704,7 @@ FF3795662B9399AA004EA093 /* Persistence.swift in Sources */, FFCF76072C3BE9BC006C8C3D /* CloseDatePicker.swift in Sources */, FF1DF49B2BD8D23900822FA0 /* BarButtonView.swift in Sources */, + C488C8052CC7E1E40082001F /* BaseCourt.swift in Sources */, FFF964502BC25E3700EEF017 /* PlanningView.swift in Sources */, FF967CEC2BAECB9900A9A3BD /* Match.swift in Sources */, FF8F264B2BAE0B4100650388 /* TournamentLevelPickerView.swift in Sources */, @@ -2531,6 +2791,7 @@ FF3A73F32D37C34D007E3032 /* RegistrationInfoSheetView.swift in Sources */, C49EF01B2BD6A1E80077B5AA /* URLs.swift in Sources */, FFCFC0142BBC59FC00B82851 /* MatchDescriptor.swift in Sources */, + C49C73142D5B98D8008DD299 /* PlayerPaymentType.swift in Sources */, FF8F264C2BAE0B4100650388 /* TournamentFormatSelectionView.swift in Sources */, FF17CA572CC02FEA003C7323 /* CoachListView.swift in Sources */, FFBF065E2BBD8040009D6715 /* MatchListView.swift in Sources */, @@ -2543,13 +2804,14 @@ FF0E0B6D2BC254C6005F00A9 /* TournamentScheduleView.swift in Sources */, FF025AF12BD1AEBD00A86CF8 /* MatchFormatStorageView.swift in Sources */, FF3F74F62B919E45004CFE0E /* UmpireView.swift in Sources */, - C4A47DAD2B85FCCD00ADC637 /* User.swift in Sources */, + C4A47DAD2B85FCCD00ADC637 /* CustomUser.swift in Sources */, C4C33F762C9B1ED4006316DE /* CodingContainer+Extensions.swift in Sources */, FF967D012BAEF0B400A9A3BD /* MatchSummaryView.swift in Sources */, FFA252B62CDD2C6C0074E63F /* OngoingDestination.swift in Sources */, FF8F26452BAE0A3400650388 /* TournamentDurationManagerView.swift in Sources */, FF1DC5532BAB354A00FD8220 /* MockData.swift in Sources */, FF967D092BAF3D4000A9A3BD /* TeamDetailView.swift in Sources */, + C488C8822CCBE8FC0082001F /* NetworkStatusView.swift in Sources */, FFA252A92CDB70520074E63F /* PlayerStatisticView.swift in Sources */, FF5DA18F2BB9268800A33061 /* GroupStagesSettingsView.swift in Sources */, FF663FBE2BE019EC0031AE83 /* TournamentFilterView.swift in Sources */, @@ -2571,6 +2833,7 @@ FF089EBD2BB0287D00F0AEC7 /* PlayerView.swift in Sources */, FF967D032BAEF0C000A9A3BD /* MatchDetailView.swift in Sources */, FFF1D2CB2C4A22B200C8D33D /* ExportFormat.swift in Sources */, + C488C8012CC7DCB80082001F /* BaseClub.swift in Sources */, FF967D0F2BAF63B000A9A3BD /* PlayerBlockView.swift in Sources */, C4A47D922B7BBBEC00ADC637 /* StoreItem.swift in Sources */, FFB9C8712BBADDE200A0EF4F /* Selectable.swift in Sources */, @@ -2614,6 +2877,7 @@ files = ( C411C9D02BF38F41003017AD /* TokenExemptionTests.swift in Sources */, C49EF0422BE23BF50077B5AA /* PaymentTests.swift in Sources */, + C4D477992CB6704C0077713D /* SynchronizationTests.swift in Sources */, C425D4122B6D249E002A7B48 /* PadelClubTests.swift in Sources */, C411C9C92BF219CB003017AD /* UserDataTests.swift in Sources */, C411C9C32BEBA453003017AD /* ServerDataTests.swift in Sources */, @@ -2678,6 +2942,7 @@ FF4CBF6B2C996C0600151637 /* Calendar+Extensions.swift in Sources */, FF4CBF6C2C996C0600151637 /* TeamScore.swift in Sources */, FF4CBF6D2C996C0600151637 /* EditablePlayerView.swift in Sources */, + C488C7F12CC7D22D0082001F /* Club.json in Sources */, FF4CBF6E2C996C0600151637 /* PlayerDetailView.swift in Sources */, FFA252AF2CDB734A0074E63F /* UmpireStatisticView.swift in Sources */, FF4CBF6F2C996C0600151637 /* ListRowViewModifier.swift in Sources */, @@ -2692,6 +2957,20 @@ FF4CBF782C996C0600151637 /* EventCreationView.swift in Sources */, FF4CBF792C996C0600151637 /* LocationManager.swift in Sources */, FF4CBF7A2C996C0600151637 /* CapsuleViewModifier.swift in Sources */, + C488C8472CC7E4240082001F /* BaseDateInterval.swift in Sources */, + C488C8832CCBE8FC0082001F /* NetworkStatusView.swift in Sources */, + C488C8482CC7E4240082001F /* BaseMonthData.swift in Sources */, + C488C8492CC7E4240082001F /* BaseTeamRegistration.swift in Sources */, + C488C84A2CC7E4240082001F /* BaseGroupStage.swift in Sources */, + C488C84B2CC7E4240082001F /* BaseCustomUser.swift in Sources */, + C488C84C2CC7E4240082001F /* BaseMatch.swift in Sources */, + C488C84D2CC7E4240082001F /* BaseEvent.swift in Sources */, + C488C84E2CC7E4240082001F /* BaseRound.swift in Sources */, + C488C84F2CC7E4240082001F /* BaseMatchScheduler.swift in Sources */, + C49C73152D5B98D8008DD299 /* PlayerPaymentType.swift in Sources */, + C488C8512CC7E4240082001F /* BasePlayerRegistration.swift in Sources */, + C488C8522CC7E4240082001F /* BaseTeamScore.swift in Sources */, + C488C8532CC7E4240082001F /* BaseTournament.swift in Sources */, FF4CBF7B2C996C0600151637 /* BroadcastView.swift in Sources */, FF4CBF7C2C996C0600151637 /* SchedulerView.swift in Sources */, FF4CBF7D2C996C0600151637 /* PadelClubButtonView.swift in Sources */, @@ -2710,6 +2989,7 @@ FF4CBF882C996C0600151637 /* TeamRowView.swift in Sources */, FF4CBF892C996C0600151637 /* ContactManager.swift in Sources */, FF4CBF8A2C996C0600151637 /* AppDelegate.swift in Sources */, + C488C8762CC816410082001F /* BasePurchase.swift in Sources */, FF4CBF8B2C996C0600151637 /* SubscriptionInfoView.swift in Sources */, FF4CBF8C2C996C0600151637 /* EditScoreView.swift in Sources */, FF4CBF8D2C996C0600151637 /* TournamentOrganizerView.swift in Sources */, @@ -2728,6 +3008,7 @@ FF4CBF992C996C0600151637 /* StoreManager.swift in Sources */, FF4CBF9A2C996C0600151637 /* SearchViewModel.swift in Sources */, FF4CBF9B2C996C0600151637 /* PlayerRegistration.swift in Sources */, + C4339BFD2CFF7D68004E5F09 /* ShareModelView.swift in Sources */, FF4CBF9C2C996C0600151637 /* ImportedPlayerView.swift in Sources */, FF4CBF9D2C996C0600151637 /* EditingTeamView.swift in Sources */, FF3A74322D37DCF2007E3032 /* InscriptionLegendView.swift in Sources */, @@ -2738,6 +3019,7 @@ FF4CBFA22C996C0600151637 /* Persistence.swift in Sources */, FF4CBFA32C996C0600151637 /* CloseDatePicker.swift in Sources */, FF4CBFA42C996C0600151637 /* BarButtonView.swift in Sources */, + C488C8042CC7E1E40082001F /* BaseCourt.swift in Sources */, FF4CBFA52C996C0600151637 /* PlanningView.swift in Sources */, FF4CBFA62C996C0600151637 /* Match.swift in Sources */, FF4CBFA72C996C0600151637 /* TournamentLevelPickerView.swift in Sources */, @@ -2766,6 +3048,7 @@ FF4CBFBD2C996C0600151637 /* AgendaDestination.swift in Sources */, FF4CBFBE2C996C0600151637 /* PadelClubApp.xcdatamodeld in Sources */, FF4CBFBF2C996C0600151637 /* SetInputView.swift in Sources */, + C471D15A2D0C91FF0068091F /* BaseDrawLog.swift in Sources */, FF4CBFC02C996C0600151637 /* ButtonValidateView.swift in Sources */, FF4CBFC12C996C0600151637 /* ClubRowView.swift in Sources */, FF4CBFC22C996C0600151637 /* ClubDetailView.swift in Sources */, @@ -2829,6 +3112,7 @@ FF4CBFF32C996C0600151637 /* MatchListView.swift in Sources */, FF4CBFF42C996C0600151637 /* PadelClubApp.swift in Sources */, FF4CBFF52C996C0600151637 /* TournamentSettingsView.swift in Sources */, + C4A36F582CE2626A003738C6 /* TournamentLibrary.swift in Sources */, FF4CBFF62C996C0600151637 /* String+Crypto.swift in Sources */, FF4CBFF72C996C0600151637 /* GroupStageTeamReplacementView.swift in Sources */, FF4CBFF82C996C0600151637 /* TabItemModifier.swift in Sources */, @@ -2837,7 +3121,7 @@ FFBA2D2D2CA2CE9E00D5BBDD /* CodingContainer+Extensions.swift in Sources */, FF4CBFFB2C996C0600151637 /* MatchFormatStorageView.swift in Sources */, FF4CBFFC2C996C0600151637 /* UmpireView.swift in Sources */, - FF4CBFFD2C996C0600151637 /* User.swift in Sources */, + FF4CBFFD2C996C0600151637 /* CustomUser.swift in Sources */, FF4CBFFE2C996C0600151637 /* MatchSummaryView.swift in Sources */, FFA252B52CDD2C6C0074E63F /* OngoingDestination.swift in Sources */, FF4CBFFF2C996C0600151637 /* TournamentDurationManagerView.swift in Sources */, @@ -2864,6 +3148,7 @@ FF4CC0112C996C0600151637 /* PlayerView.swift in Sources */, FF4CC0122C996C0600151637 /* MatchDetailView.swift in Sources */, FF4CC0132C996C0600151637 /* ExportFormat.swift in Sources */, + C488C7FF2CC7DCB80082001F /* BaseClub.swift in Sources */, FF4CC0142C996C0600151637 /* PlayerBlockView.swift in Sources */, FF4CC0152C996C0600151637 /* StoreItem.swift in Sources */, FF4CC0162C996C0600151637 /* Selectable.swift in Sources */, @@ -2950,6 +3235,7 @@ FF70FAEA2C90584900129CC2 /* Calendar+Extensions.swift in Sources */, FF70FAEB2C90584900129CC2 /* TeamScore.swift in Sources */, FF70FAEC2C90584900129CC2 /* EditablePlayerView.swift in Sources */, + C488C7F22CC7D22D0082001F /* Club.json in Sources */, FF70FAED2C90584900129CC2 /* PlayerDetailView.swift in Sources */, FFA252AD2CDB734A0074E63F /* UmpireStatisticView.swift in Sources */, FF70FAEE2C90584900129CC2 /* ListRowViewModifier.swift in Sources */, @@ -2964,6 +3250,20 @@ FF70FAF72C90584900129CC2 /* EventCreationView.swift in Sources */, FF70FAF82C90584900129CC2 /* LocationManager.swift in Sources */, FF70FAF92C90584900129CC2 /* CapsuleViewModifier.swift in Sources */, + C488C8542CC7E4240082001F /* BaseDateInterval.swift in Sources */, + C488C8842CCBE8FC0082001F /* NetworkStatusView.swift in Sources */, + C488C8552CC7E4240082001F /* BaseMonthData.swift in Sources */, + C488C8562CC7E4240082001F /* BaseTeamRegistration.swift in Sources */, + C488C8572CC7E4240082001F /* BaseGroupStage.swift in Sources */, + C488C8582CC7E4240082001F /* BaseCustomUser.swift in Sources */, + C488C8592CC7E4240082001F /* BaseMatch.swift in Sources */, + C488C85A2CC7E4240082001F /* BaseEvent.swift in Sources */, + C488C85B2CC7E4240082001F /* BaseRound.swift in Sources */, + C488C85C2CC7E4240082001F /* BaseMatchScheduler.swift in Sources */, + C49C73162D5B98D8008DD299 /* PlayerPaymentType.swift in Sources */, + C488C85E2CC7E4240082001F /* BasePlayerRegistration.swift in Sources */, + C488C85F2CC7E4240082001F /* BaseTeamScore.swift in Sources */, + C488C8602CC7E4240082001F /* BaseTournament.swift in Sources */, FF70FAFA2C90584900129CC2 /* BroadcastView.swift in Sources */, FF70FAFB2C90584900129CC2 /* SchedulerView.swift in Sources */, FF70FAFC2C90584900129CC2 /* PadelClubButtonView.swift in Sources */, @@ -2982,6 +3282,7 @@ FF70FB072C90584900129CC2 /* TeamRowView.swift in Sources */, FF70FB082C90584900129CC2 /* ContactManager.swift in Sources */, FF70FB092C90584900129CC2 /* AppDelegate.swift in Sources */, + C488C8732CC816410082001F /* BasePurchase.swift in Sources */, FF70FB0A2C90584900129CC2 /* SubscriptionInfoView.swift in Sources */, FF70FB0B2C90584900129CC2 /* EditScoreView.swift in Sources */, FF70FB0C2C90584900129CC2 /* TournamentOrganizerView.swift in Sources */, @@ -3000,6 +3301,7 @@ FF70FB182C90584900129CC2 /* StoreManager.swift in Sources */, FF70FB192C90584900129CC2 /* SearchViewModel.swift in Sources */, FF70FB1A2C90584900129CC2 /* PlayerRegistration.swift in Sources */, + C4339BFC2CFF7D68004E5F09 /* ShareModelView.swift in Sources */, FF70FB1B2C90584900129CC2 /* ImportedPlayerView.swift in Sources */, FF70FB1C2C90584900129CC2 /* EditingTeamView.swift in Sources */, FF3A74342D37DCF2007E3032 /* InscriptionLegendView.swift in Sources */, @@ -3010,6 +3312,7 @@ FF70FB212C90584900129CC2 /* Persistence.swift in Sources */, FF70FB222C90584900129CC2 /* CloseDatePicker.swift in Sources */, FF70FB232C90584900129CC2 /* BarButtonView.swift in Sources */, + C488C8032CC7E1E40082001F /* BaseCourt.swift in Sources */, FF70FB242C90584900129CC2 /* PlanningView.swift in Sources */, FF70FB252C90584900129CC2 /* Match.swift in Sources */, FF70FB262C90584900129CC2 /* TournamentLevelPickerView.swift in Sources */, @@ -3038,6 +3341,7 @@ FF70FB3C2C90584900129CC2 /* AgendaDestination.swift in Sources */, FF70FB3D2C90584900129CC2 /* PadelClubApp.xcdatamodeld in Sources */, FF70FB3E2C90584900129CC2 /* SetInputView.swift in Sources */, + C471D1582D0C91FE0068091F /* BaseDrawLog.swift in Sources */, FF70FB3F2C90584900129CC2 /* ButtonValidateView.swift in Sources */, FF70FB402C90584900129CC2 /* ClubRowView.swift in Sources */, FF70FB412C90584900129CC2 /* ClubDetailView.swift in Sources */, @@ -3101,6 +3405,7 @@ FF70FB722C90584900129CC2 /* MatchListView.swift in Sources */, FF70FB732C90584900129CC2 /* PadelClubApp.swift in Sources */, FF70FB742C90584900129CC2 /* TournamentSettingsView.swift in Sources */, + C4A36F592CE2626A003738C6 /* TournamentLibrary.swift in Sources */, FF70FB752C90584900129CC2 /* String+Crypto.swift in Sources */, FF70FB762C90584900129CC2 /* GroupStageTeamReplacementView.swift in Sources */, FF70FB772C90584900129CC2 /* TabItemModifier.swift in Sources */, @@ -3108,7 +3413,7 @@ FF70FB792C90584900129CC2 /* TournamentScheduleView.swift in Sources */, FF70FB7A2C90584900129CC2 /* MatchFormatStorageView.swift in Sources */, FF70FB7B2C90584900129CC2 /* UmpireView.swift in Sources */, - FF70FB7C2C90584900129CC2 /* User.swift in Sources */, + FF70FB7C2C90584900129CC2 /* CustomUser.swift in Sources */, C4C33F772C9B1ED4006316DE /* CodingContainer+Extensions.swift in Sources */, FF70FB7D2C90584900129CC2 /* MatchSummaryView.swift in Sources */, FFA252B72CDD2C6C0074E63F /* OngoingDestination.swift in Sources */, @@ -3136,6 +3441,7 @@ FF70FB902C90584900129CC2 /* PlayerView.swift in Sources */, FF70FB912C90584900129CC2 /* MatchDetailView.swift in Sources */, FF70FB922C90584900129CC2 /* ExportFormat.swift in Sources */, + C488C8002CC7DCB80082001F /* BaseClub.swift in Sources */, FF70FB932C90584900129CC2 /* PlayerBlockView.swift in Sources */, FF70FB942C90584900129CC2 /* StoreItem.swift in Sources */, FF70FB952C90584900129CC2 /* Selectable.swift in Sources */, @@ -3352,7 +3658,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1.26; + MARKETING_VERSION = 1.2; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3397,7 +3703,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1.26; + MARKETING_VERSION = 1.2; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3604,7 +3910,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1.1; + MARKETING_VERSION = 1.2; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3646,7 +3952,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1.1; + MARKETING_VERSION = 1.2; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/PadelClub/AppDelegate.swift b/PadelClub/AppDelegate.swift index 22383c0..a678151 100644 --- a/PadelClub/AppDelegate.swift +++ b/PadelClub/AppDelegate.swift @@ -19,8 +19,7 @@ class AppDelegate : NSObject, UIApplicationDelegate, UNUserNotificationCenterDel UIApplication.shared.registerForRemoteNotifications() UNUserNotificationCenter.current().delegate = self - - Logger.log("didFinishLaunchingWithOptions") + return true } @@ -28,7 +27,7 @@ class AppDelegate : NSObject, UIApplicationDelegate, UNUserNotificationCenterDel func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { - if StoreCenter.main.hasToken() { + if StoreCenter.main.isAuthenticated { Task { do { let services = try StoreCenter.main.service() diff --git a/PadelClub/Data/Club.swift b/PadelClub/Data/Club.swift index c124f28..f52bc18 100644 --- a/PadelClub/Data/Club.swift +++ b/PadelClub/Data/Club.swift @@ -10,61 +10,10 @@ import SwiftUI import LeStorage @Observable -final class Club : ModelObject, Storable, Hashable { +final class Club: BaseClub { - static func resourceName() -> String { return "clubs" } - static func tokenExemptedMethods() -> [HTTPMethod] { return [.get] } - static func filterByStoreIdentifier() -> Bool { return false } - static var relationshipNames: [String] = [] - - static func == (lhs: Club, rhs: Club) -> Bool { - lhs.id == rhs.id - } - - func hash(into hasher: inout Hasher) { - return hasher.combine(id) - } - - var id: String = Store.randomId() - var creator: String? - var name: String - var acronym: String - var phone: String? - var code: String? - //var federalClubData: Data? - var address: String? - var city: String? - var zipCode: String? - var latitude: Double? - var longitude: Double? - var courtCount: Int = 2 - var broadcastCode: String? - var timezone: String? -// var alphabeticalName: Bool = false - - internal init(creator: String? = nil, name: String, acronym: String? = nil, phone: String? = nil, code: String? = nil, address: String? = nil, city: String? = nil, zipCode: String? = nil, latitude: Double? = nil, longitude: Double? = nil, courtCount: Int = 2, broadcastCode: String? = nil, timezone: String? = nil) { - self.name = name - self.creator = creator - self.acronym = acronym ?? name.acronym() - self.phone = phone - self.code = code - self.address = address - self.city = city - self.zipCode = zipCode - self.latitude = latitude - self.longitude = longitude - self.courtCount = courtCount - self.broadcastCode = broadcastCode - self.timezone = TimeZone.current.identifier - } + static var copyServerResponse: Bool { return true } - override func copyFromServerInstance(_ instance: any Storable) -> Bool { - guard let copy = instance as? Club else { return false } - self.broadcastCode = copy.broadcastCode -// Logger.log("write code: \(self.broadcastCode)") - return true - } - func clubTitle(_ displayStyle: DisplayStyle = .wide) -> String { switch displayStyle { case .wide, .title: @@ -82,50 +31,13 @@ final class Club : ModelObject, Storable, Hashable { DataStore.shared.courts.filter { $0.club == self.id }.sorted(by: \.index) } - override func deleteDependencies() throws { + override func deleteDependencies() { let customizedCourts = self.customizedCourts for customizedCourt in customizedCourts { - try customizedCourt.deleteDependencies() + customizedCourt.deleteDependencies() } DataStore.shared.courts.deleteDependencies(customizedCourts) } - - enum CodingKeys: String, CodingKey { - case _id = "id" - case _creator = "creator" - case _name = "name" - case _acronym = "acronym" - case _phone = "phone" - case _code = "code" - case _address = "address" - case _city = "city" - case _zipCode = "zipCode" - case _latitude = "latitude" - case _longitude = "longitude" - case _courtCount = "courtCount" - case _broadcastCode = "broadcastCode" - case _timezone = "timezone" -// case _alphabeticalName = "alphabeticalName" - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(id, forKey: ._id) - try container.encode(creator, forKey: ._creator) - try container.encode(name, forKey: ._name) - try container.encode(acronym, forKey: ._acronym) - try container.encode(phone, forKey: ._phone) - try container.encode(code, forKey: ._code) - try container.encode(address, forKey: ._address) - try container.encode(city, forKey: ._city) - try container.encode(zipCode, forKey: ._zipCode) - try container.encode(latitude, forKey: ._latitude) - try container.encode(longitude, forKey: ._longitude) - try container.encode(courtCount, forKey: ._courtCount) - try container.encode(broadcastCode, forKey: ._broadcastCode) - try container.encode(timezone, forKey: ._timezone) - } } @@ -177,7 +89,7 @@ extension Club { } func hasBeenCreated(by creatorId: String?) -> Bool { - return creatorId == creator || creator == nil + return creatorId == creator || creator == nil || self.relatedUser == creatorId } func isFavorite() -> Bool { @@ -196,7 +108,9 @@ extension Club { if let club { return club } else { - return Club(creator: StoreCenter.main.userId, name: name, code: code, city: city, zipCode: zipCode) + let club = Club(creator: StoreCenter.main.userId, name: name, code: code, city: city, zipCode: zipCode) + club.relatedUser = StoreCenter.main.userId + return club } } diff --git a/PadelClub/Data/Court.swift b/PadelClub/Data/Court.swift index e7751bc..925b9d9 100644 --- a/PadelClub/Data/Court.swift +++ b/PadelClub/Data/Court.swift @@ -10,40 +10,27 @@ import SwiftUI import LeStorage @Observable -final class Court : ModelObject, Storable, Hashable { - static func resourceName() -> String { return "courts" } - static func tokenExemptedMethods() -> [HTTPMethod] { return [] } - static func filterByStoreIdentifier() -> Bool { return false } - static var relationshipNames: [String] = [] - +final class Court: BaseCourt { + static func == (lhs: Court, rhs: Court) -> Bool { lhs.id == rhs.id } - func hash(into hasher: inout Hasher) { - return hasher.combine(id) - } - - var id: String = Store.randomId() - var index: Int - var club: String - var name: String? - var exitAllowed: Bool = false - var indoor: Bool = false - init(index: Int, club: String, name: String? = nil, exitAllowed: Bool = false, indoor: Bool = false) { + + super.init() + self.index = index + self.lastUpdate = Date() self.club = club self.name = name self.exitAllowed = exitAllowed self.indoor = indoor } -// internal init(club: String, name: String? = nil, index: Int) { -// self.club = club -// self.name = name -// self.index = index -// } + required init(from decoder: Decoder) throws { + try super.init(from: decoder) + } func courtTitle() -> String { self.name ?? courtIndexTitle() @@ -61,27 +48,7 @@ final class Court : ModelObject, Storable, Hashable { Store.main.findById(club) } - override func deleteDependencies() throws { - } - - enum CodingKeys: String, CodingKey { - case _id = "id" - case _index = "index" - case _club = "club" - case _name = "name" - case _exitAllowed = "exitAllowed" - case _indoor = "indoor" - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(id, forKey: ._id) - try container.encode(index, forKey: ._index) - try container.encode(club, forKey: ._club) - try container.encode(name, forKey: ._name) - try container.encode(exitAllowed, forKey: ._exitAllowed) - try container.encode(indoor, forKey: ._indoor) + override func deleteDependencies() { } } diff --git a/PadelClub/Data/CustomUser.swift b/PadelClub/Data/CustomUser.swift new file mode 100644 index 0000000..0624736 --- /dev/null +++ b/PadelClub/Data/CustomUser.swift @@ -0,0 +1,262 @@ +// +// User.swift +// PadelClub +// +// Created by Laurent Morvillier on 21/02/2024. +// + +import Foundation +import LeStorage + +enum UserRight: Int, Codable { + case none = 0 + case edition = 1 + case creation = 2 +} + +@Observable +class CustomUser: BaseCustomUser, UserBase { + +// static func resourceName() -> String { "users" } +// static func tokenExemptedMethods() -> [HTTPMethod] { return [.post] } +// static func filterByStoreIdentifier() -> Bool { return false } +// static var relationshipNames: [String] = [] +// +// public var id: String = Store.randomId() +// var lastUpdate: Date +// public var username: String +// public var email: String +// var clubs: [String] = [] +// var umpireCode: String? +// var licenceId: String? +// var firstName: String +// var lastName: String +// var phone: String? +// var country: String? +// +// var summonsMessageBody : String? = nil +// var summonsMessageSignature: String? = nil +// var summonsAvailablePaymentMethods: String? = nil +// var summonsDisplayFormat: Bool = false +// var summonsDisplayEntryFee: Bool = false +// var summonsUseFullCustomMessage: Bool = false +// var matchFormatsDefaultDuration: [MatchFormat: Int]? = nil +// var bracketMatchFormatPreference: MatchFormat? +// var groupStageMatchFormatPreference: MatchFormat? +// var loserBracketMatchFormatPreference: MatchFormat? +// var loserBracketMode: LoserBracketMode = .automatic +// +// var deviceId: String? + + init(username: String, email: String, firstName: String, lastName: String, phone: String?, country: String?, loserBracketMode: LoserBracketMode = .automatic) { + super.init(username: username, email: email, firstName: firstName, lastName: lastName, phone: phone, country: country, loserBracketMode: loserBracketMode) + + +// self.lastUpdate = Date() +// self.username = username +// self.firstName = firstName +// self.lastName = lastName +// self.email = email +// self.phone = phone +// self.country = country +// self.loserBracketMode = loserBracketMode + } + + required init(from decoder: Decoder) throws { + try super.init(from: decoder) + } + + public func uuid() throws -> UUID { + if let uuid = UUID(uuidString: self.id) { + return uuid + } + throw UUIDError.cantConvertString(string: self.id) + } + + func currentPlayerData() -> ImportedPlayer? { + guard let licenceId else { return nil } + let federalContext = PersistenceController.shared.localContainer.viewContext + let fetchRequest = ImportedPlayer.fetchRequest() + let predicate = NSPredicate(format: "license == %@", licenceId) + fetchRequest.predicate = predicate + return try? federalContext.fetch(fetchRequest).first + } + + func defaultSignature() -> String { + return "Sportivement,\n\(firstName) \(lastName), votre JAP." + } + + func fullName() -> String? { + guard firstName.isEmpty == false && lastName.isEmpty == false else { + return nil + } + return "\(firstName) \(lastName)" + } + + func hasTenupClubs() -> Bool { + self.clubsObjects().filter({ $0.code != nil }).isEmpty == false + } + + func hasFavoriteClubsAndCreatedClubs() -> Bool { + clubsObjects(includeCreated: true).isEmpty == false + } + + func setUserClub(_ userClub: Club) { + self.clubs.insert(userClub.id, at: 0) + } + + func clubsObjects(includeCreated: Bool = false) -> [Club] { + return DataStore.shared.clubs.filter({ (includeCreated && $0.creator == id) || clubs.contains($0.id) }) + } + + func createdClubsObjectsNotFavorite() -> [Club] { + return DataStore.shared.clubs.filter({ ($0.creator == id) && clubs.contains($0.id) == false }) + } + + func saveMatchFormatsDefaultDuration(_ matchFormat: MatchFormat, estimatedDuration: Int) { + if estimatedDuration == matchFormat.defaultEstimatedDuration { + matchFormatsDefaultDuration?.removeValue(forKey: matchFormat) + } else { + matchFormatsDefaultDuration = matchFormatsDefaultDuration ?? [MatchFormat: Int]() + matchFormatsDefaultDuration?[matchFormat] = estimatedDuration + } + } + + func addClub(_ club: Club) { + if !self.clubs.contains(where: { $0.id == club.id }) { + self.clubs.append(club.id) + } + } + +// enum CodingKeys: String, CodingKey { +// case _id = "id" +// case _lastUpdate = "lastUpdate" +// case _username = "username" +// case _email = "email" +// case _clubs = "clubs" +// case _umpireCode = "umpireCode" +// case _licenceId = "licenceId" +// case _firstName = "firstName" +// case _lastName = "lastName" +// case _phone = "phone" +// case _country = "country" +// case _summonsMessageBody = "summonsMessageBody" +// case _summonsMessageSignature = "summonsMessageSignature" +// case _summonsAvailablePaymentMethods = "summonsAvailablePaymentMethods" +// case _summonsDisplayFormat = "summonsDisplayFormat" +// case _summonsDisplayEntryFee = "summonsDisplayEntryFee" +// case _summonsUseFullCustomMessage = "summonsUseFullCustomMessage" +// case _matchFormatsDefaultDuration = "matchFormatsDefaultDuration" +// case _bracketMatchFormatPreference = "bracketMatchFormatPreference" +// case _groupStageMatchFormatPreference = "groupStageMatchFormatPreference" +// case _loserBracketMatchFormatPreference = "loserBracketMatchFormatPreference" +// case _deviceId = "deviceId" +// case _loserBracketMode = "loserBracketMode" +// } +// +// public required init(from decoder: Decoder) throws { +// let container = try decoder.container(keyedBy: CodingKeys.self) +// +// // Required properties +// id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() +// lastUpdate = try container.decodeIfPresent(Date.self, forKey: ._lastUpdate) ?? Date() +// username = try container.decode(String.self, forKey: ._username) +// email = try container.decode(String.self, forKey: ._email) +// firstName = try container.decode(String.self, forKey: ._firstName) +// lastName = try container.decode(String.self, forKey: ._lastName) +// +// // Optional properties +// clubs = try container.decodeIfPresent([String].self, forKey: ._clubs) ?? [] +// umpireCode = try container.decodeIfPresent(String.self, forKey: ._umpireCode) +// licenceId = try container.decodeIfPresent(String.self, forKey: ._licenceId) +// phone = try container.decodeIfPresent(String.self, forKey: ._phone) +// country = try container.decodeIfPresent(String.self, forKey: ._country) +// +// // Summons-related properties +// summonsMessageBody = try container.decodeIfPresent(String.self, forKey: ._summonsMessageBody) +// summonsMessageSignature = try container.decodeIfPresent(String.self, forKey: ._summonsMessageSignature) +// summonsAvailablePaymentMethods = try container.decodeIfPresent(String.self, forKey: ._summonsAvailablePaymentMethods) +// summonsDisplayFormat = try container.decodeIfPresent(Bool.self, forKey: ._summonsDisplayFormat) ?? false +// summonsDisplayEntryFee = try container.decodeIfPresent(Bool.self, forKey: ._summonsDisplayEntryFee) ?? false +// summonsUseFullCustomMessage = try container.decodeIfPresent(Bool.self, forKey: ._summonsUseFullCustomMessage) ?? false +// +// // Match-related properties +// matchFormatsDefaultDuration = try container.decodeIfPresent([MatchFormat: Int].self, forKey: ._matchFormatsDefaultDuration) +// bracketMatchFormatPreference = try container.decodeIfPresent(MatchFormat.self, forKey: ._bracketMatchFormatPreference) +// groupStageMatchFormatPreference = try container.decodeIfPresent(MatchFormat.self, forKey: ._groupStageMatchFormatPreference) +// loserBracketMatchFormatPreference = try container.decodeIfPresent(MatchFormat.self, forKey: ._loserBracketMatchFormatPreference) +// loserBracketMode = try container.decodeIfPresent(LoserBracketMode.self, forKey: ._loserBracketMode) ?? .automatic +// } +// +// func encode(to encoder: Encoder) throws { +// var container = encoder.container(keyedBy: CodingKeys.self) +// +// try container.encode(id, forKey: ._id) +// try container.encode(lastUpdate, forKey: ._lastUpdate) +// try container.encode(username, forKey: ._username) +// try container.encode(email, forKey: ._email) +// try container.encode(clubs, forKey: ._clubs) +// +// try container.encode(umpireCode, forKey: ._umpireCode) +// try container.encode(licenceId, forKey: ._licenceId) +// try container.encode(firstName, forKey: ._firstName) +// try container.encode(lastName, forKey: ._lastName) +// try container.encode(phone, forKey: ._phone) +// try container.encode(country, forKey: ._country) +// try container.encode(summonsMessageBody, forKey: ._summonsMessageBody) +// try container.encode(summonsMessageSignature, forKey: ._summonsMessageSignature) +// try container.encode(summonsAvailablePaymentMethods, forKey: ._summonsAvailablePaymentMethods) +// try container.encode(summonsDisplayFormat, forKey: ._summonsDisplayFormat) +// try container.encode(summonsDisplayEntryFee, forKey: ._summonsDisplayEntryFee) +// try container.encode(summonsUseFullCustomMessage, forKey: ._summonsUseFullCustomMessage) +// +// try container.encode(matchFormatsDefaultDuration, forKey: ._matchFormatsDefaultDuration) +// try container.encode(bracketMatchFormatPreference, forKey: ._bracketMatchFormatPreference) +// try container.encode(groupStageMatchFormatPreference, forKey: ._groupStageMatchFormatPreference) +// try container.encode(loserBracketMatchFormatPreference, forKey: ._loserBracketMatchFormatPreference) +// try container.encode(deviceId, forKey: ._deviceId) +// +// try container.encode(loserBracketMode, forKey: ._loserBracketMode) +// } + + static func placeHolder() -> CustomUser { + return CustomUser(username: "", email: "", firstName: "", lastName: "", phone: nil, country: nil) + } + +} + +class UserCreationForm: CustomUser, UserPasswordBase { + + init(user: CustomUser, username: String, password: String, firstName: String, lastName: String, email: String, phone: String?, country: String?) { + self.password = password + super.init(username: username, email: email, firstName: firstName, lastName: lastName, phone: phone, country: country) + + self.summonsMessageBody = user.summonsMessageBody + self.summonsMessageSignature = user.summonsMessageSignature + self.summonsAvailablePaymentMethods = user.summonsAvailablePaymentMethods + self.summonsDisplayFormat = user.summonsDisplayFormat + self.summonsDisplayEntryFee = user.summonsDisplayEntryFee + self.summonsUseFullCustomMessage = user.summonsUseFullCustomMessage + self.matchFormatsDefaultDuration = user.matchFormatsDefaultDuration + self.bracketMatchFormatPreference = user.bracketMatchFormatPreference + self.groupStageMatchFormatPreference = user.groupStageMatchFormatPreference + self.loserBracketMatchFormatPreference = user.loserBracketMatchFormatPreference + + } + + required init(from decoder: Decoder) throws { + fatalError("init(from:) has not been implemented") + } + + public var password: String + + private enum CodingKeys: String, CodingKey { + case password + } + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.password, forKey: .password) + } +} diff --git a/PadelClub/Data/DataStore.swift b/PadelClub/Data/DataStore.swift index 7886d6d..d98db75 100644 --- a/PadelClub/Data/DataStore.swift +++ b/PadelClub/Data/DataStore.swift @@ -13,7 +13,7 @@ class DataStore: ObservableObject { static let shared = DataStore() - @Published var user: User = User.placeHolder() { + @Published var user: CustomUser = CustomUser.placeHolder() { didSet { let loggedUser = StoreCenter.main.userId != nil StoreCenter.main.collectionsCanSynchronize = loggedUser @@ -22,7 +22,7 @@ class DataStore: ObservableObject { if self.user.id != self.userStorage.item()?.id { self.userStorage.setItemNoSync(self.user) if StoreCenter.main.collectionsCanSynchronize { - Store.main.loadCollectionsFromServer() + StoreCenter.main.initialSynchronization() self._fixMissingClubCreatorIfNecessary(self.clubs) self._fixMissingEventCreatorIfNecessary(self.events) } @@ -41,9 +41,9 @@ class DataStore: ObservableObject { fileprivate(set) var dateIntervals: StoredCollection fileprivate(set) var purchases: StoredCollection - fileprivate var userStorage: StoredSingleton + fileprivate var userStorage: StoredSingleton - fileprivate var _temporaryLocalUser: OptionalStorage = OptionalStorage(fileName: "tmp_local_user.json") + fileprivate var _temporaryLocalUser: OptionalStorage = OptionalStorage(fileName: "tmp_local_user.json") fileprivate(set) var appSettingsStorage: MicroStorage = MicroStorage(fileName: "appsettings.json") var appSettings: AppSettings { @@ -52,17 +52,20 @@ class DataStore: ObservableObject { init() { let store = Store.main - let serverURL: String = URLs.api.rawValue StoreCenter.main.blackListUserName("apple-test") +// let secureScheme = true + let domain: String = URLs.activationHost.rawValue + #if DEBUG - if let server = PListReader.readString(plist: "local", key: "server") { - StoreCenter.main.synchronizationApiURL = server + if let secure = PListReader.readBool(plist: "local", key: "secure_server"), + let domain = PListReader.readString(plist: "local", key: "server_domain") { + StoreCenter.main.configureURLs(secureScheme: secure, domain: domain) } else { - StoreCenter.main.synchronizationApiURL = serverURL + StoreCenter.main.configureURLs(secureScheme: true, domain: domain) } #else - StoreCenter.main.synchronizationApiURL = serverURL + StoreCenter.main.configureURLs(secureScheme: true, domain: domain) #endif StoreCenter.main.logsFailedAPICalls() @@ -75,18 +78,19 @@ class DataStore: ObservableObject { } #endif - Logger.log("Sync URL: \(StoreCenter.main.synchronizationApiURL ?? "none"), sync: \(synchronized) ") + StoreCenter.main.forceNoSynchronization = !synchronized + let indexed: Bool = true - self.clubs = store.registerCollection(synchronized: synchronized, indexed: indexed) - self.courts = store.registerCollection(synchronized: synchronized, indexed: indexed) - self.tournaments = store.registerCollection(synchronized: synchronized, indexed: indexed) - self.events = store.registerCollection(synchronized: synchronized, indexed: indexed) - self.monthData = store.registerCollection(synchronized: false, indexed: indexed) - self.dateIntervals = store.registerCollection(synchronized: synchronized, indexed: indexed) - + self.clubs = store.registerSynchronizedCollection(indexed: indexed) + self.courts = store.registerSynchronizedCollection(indexed: indexed) + self.tournaments = store.registerSynchronizedCollection(indexed: indexed) + self.events = store.registerSynchronizedCollection(indexed: indexed) + self.dateIntervals = store.registerSynchronizedCollection(indexed: indexed) self.userStorage = store.registerObject(synchronized: synchronized) - self.purchases = Store.main.registerCollection(synchronized: true, inMemory: true) + self.purchases = Store.main.registerSynchronizedCollection(inMemory: true) + + self.monthData = store.registerCollection(indexed: indexed) // Load ApiCallCollection, making them restart at launch and deletable on disconnect StoreCenter.main.loadApiCallCollection(type: GroupStage.self) @@ -95,6 +99,7 @@ class DataStore: ObservableObject { StoreCenter.main.loadApiCallCollection(type: TeamRegistration.self) StoreCenter.main.loadApiCallCollection(type: Match.self) StoreCenter.main.loadApiCallCollection(type: TeamScore.self) + StoreCenter.main.loadApiCallCollection(type: DrawLog.self) NotificationCenter.default.addObserver(self, selector: #selector(collectionDidLoad), name: NSNotification.Name.CollectionDidLoad, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(collectionDidUpdate), name: NSNotification.Name.CollectionDidChange, object: nil) @@ -111,45 +116,37 @@ class DataStore: ObservableObject { } func saveUser() { - do { - if user.username.count > 0 { - try self.userStorage.update() - } else { - self._temporaryLocalUser.item = self.user - } - } catch { - Logger.error(error) + if user.username.count > 0 { + self.userStorage.update() + } else { + self._temporaryLocalUser.item = self.user } } @objc func collectionDidLoad(notification: Notification) { - - if let userSingleton: StoredSingleton = notification.object as? StoredSingleton { - self.user = userSingleton.item() ?? self._temporaryLocalUser.item ?? User.placeHolder() + + if let userSingleton: StoredSingleton = notification.object as? StoredSingleton { + self.user = userSingleton.item() ?? self._temporaryLocalUser.item ?? CustomUser.placeHolder() } else if let clubsCollection: StoredCollection = notification.object as? StoredCollection { self._fixMissingClubCreatorIfNecessary(clubsCollection) } else if let eventsCollection: StoredCollection = notification.object as? StoredCollection { self._fixMissingEventCreatorIfNecessary(eventsCollection) } - if Store.main.collectionsAllLoaded() { + if Store.main.fileCollectionsAllLoaded() { Patcher.applyAllWhenApplicable() } } fileprivate func _fixMissingClubCreatorIfNecessary(_ clubsCollection: StoredCollection) { - do { - for club in clubsCollection { - if let userId = StoreCenter.main.userId, club.creator == nil { - club.creator = userId - self.userStorage.item()?.addClub(club) - try self.userStorage.update() - clubsCollection.writeChangeAndInsertOnServer(instance: club) - } + for club in clubsCollection { + if let userId = StoreCenter.main.userId, club.creator == nil { + club.creator = userId + self.userStorage.item()?.addClub(club) + self.userStorage.update() + clubsCollection.writeChangeAndInsertOnServer(instance: club) } - } catch { - Logger.error(error) } } @@ -215,12 +212,24 @@ class DataStore: ObservableObject { } + func deleteTournament(_ tournament: Tournament) { + let event = tournament.eventObject() + let isLastTournament = event?.tournaments.count == 1 + self.tournaments.delete(instance: tournament) + if let event, isLastTournament { + self.events.delete(instance: event) + } + StoreCenter.main.destroyStore(identifier: tournament.id) + } + fileprivate func _localDisconnect() { StoreCenter.main.collectionsCanSynchronize = false let tournamendIds: [String] = self.tournaments.map { $0.id } + TournamentLibrary.shared.reset() + self.tournaments.reset() self.clubs.reset() self.courts.reset() @@ -232,76 +241,76 @@ class DataStore: ObservableObject { Guard.main.disconnect() StoreCenter.main.disconnect() - self.user = self._temporaryLocalUser.item ?? User.placeHolder() - self.user.clubs.removeAll() - // done after because otherwise folders remain for tournament in tournamendIds { StoreCenter.main.destroyStore(identifier: tournament.id) } - - } - - func copyToLocalServer(tournament: Tournament) { - - Task { - do { - - if let url = PListReader.readString(plist: "local", key: "local_server"), - let login = PListReader.readString(plist: "local", key: "username"), - let pass = PListReader.readString(plist: "local", key: "password") { - let service = Services(url: url) - let _: User = try await service.login(username: login, password: pass) - - tournament.event = nil - _ = try await service.post(tournament) - - for groupStage in tournament.groupStages() { - _ = try await service.post(groupStage) - } - for round in tournament.rounds() { - try await self._insertRoundAndChildren(round: round, service: service) - } - for teamRegistration in tournament.unsortedTeams() { - _ = try await service.post(teamRegistration) - for playerRegistration in teamRegistration.unsortedPlayers() { - _ = try await service.post(playerRegistration) - } - } - for groupStage in tournament.groupStages() { - for match in groupStage._matches() { - try await self._insertMatch(match: match, service: service) - } - } - for round in tournament.allRounds() { - for match in round._matches() { - try await self._insertMatch(match: match, service: service) - } - } - } - } catch { - Logger.error(error) - } - } - - } - - fileprivate func _insertRoundAndChildren(round: Round, service: Services) async throws { - _ = try await service.post(round) - for loserRound in round.loserRounds() { - try await self._insertRoundAndChildren(round: loserRound, service: service) - } - } - - fileprivate func _insertMatch(match: Match, service: Services) async throws { - _ = try await service.post(match) - for teamScore in match.teamScores { - _ = try await service.post(teamScore) - } + self.user = self._temporaryLocalUser.item ?? CustomUser.placeHolder() + self.user.clubs.removeAll() } +// func copyToLocalServer(tournament: Tournament) { +// +// Task { +// do { +// +// if let url = PListReader.readString(plist: "local", key: "local_server"), +// let login = PListReader.readString(plist: "local", key: "username"), +// let pass = PListReader.readString(plist: "local", key: "password") { +// let service = Services(url: url) +// let _: CustomUser = try await service.login(username: login, password: pass) +// +// tournament.event = nil +// _ = try await service.post(tournament) +// +// for groupStage in tournament.groupStages() { +// _ = try await service.post(groupStage) +// } +// for round in tournament.rounds() { +// try await self._insertRoundAndChildren(round: round, service: service) +// } +// for teamRegistration in tournament.unsortedTeams() { +// _ = try await service.post(teamRegistration) +// for playerRegistration in teamRegistration.unsortedPlayers() { +// _ = try await service.post(playerRegistration) +// } +// } +// for groupStage in tournament.groupStages() { +// for match in groupStage._matches() { +// try await self._insertMatch(match: match, service: service) +// } +// } +// for round in tournament.allRounds() { +// for match in round._matches() { +// try await self._insertMatch(match: match, service: service) +// } +// } +// +// } +// } catch { +// Logger.error(error) +// } +// } +// +// } +// +// fileprivate func _insertRoundAndChildren(round: Round, service: Services) async throws { +// _ = try await service.post(round) +// for loserRound in round.loserRounds() { +// try await self._insertRoundAndChildren(round: loserRound, service: service) +// } +// } +// +// fileprivate func _insertMatch(match: Match, service: Services) async throws { +// _ = try await service.post(match) +// for teamScore in match.teamScores { +// _ = try await service.post(teamScore) +// } +// +// } + // MARK: - Convenience func runningMatches() -> [Match] { @@ -310,24 +319,27 @@ class DataStore: ObservableObject { var runningMatches: [Match] = [] for tournament in lastTournaments { - let matches = tournament.tournamentStore.matches.filter { match in - match.disabled == false && match.isRunning() + if let store = tournament.tournamentStore { + let matches = store.matches.filter { match in + match.disabled == false && match.isRunning() + } + runningMatches.append(contentsOf: matches) } - runningMatches.append(contentsOf: matches) } return runningMatches } - func runningAndNextMatches() -> [Match] { let dateNow : Date = Date() let lastTournaments = self.tournaments.filter { $0.isDeleted == false && $0.startDate <= dateNow && $0.hasEnded() == false }.sorted(by: \Tournament.startDate, order: .descending).prefix(10) var runningMatches: [Match] = [] for tournament in lastTournaments { - let matches = tournament.tournamentStore.matches.filter { match in - match.disabled == false && match.startDate != nil && match.endDate == nil } - runningMatches.append(contentsOf: matches) + if let store = tournament.tournamentStore { + let matches = store.matches.filter { match in + match.disabled == false && match.startDate != nil && match.endDate == nil } + runningMatches.append(contentsOf: matches) + } } return runningMatches } @@ -338,9 +350,11 @@ class DataStore: ObservableObject { var runningMatches: [Match] = [] for tournament in lastTournaments { - let matches = tournament.tournamentStore.matches.filter { match in - match.disabled == false && match.hasEnded() } - runningMatches.append(contentsOf: matches) + if let store = tournament.tournamentStore { + let matches = store.matches.filter { match in + match.disabled == false && match.hasEnded() } + runningMatches.append(contentsOf: matches) + } } return runningMatches.sorted(by: \.endDate!, order: .descending) } diff --git a/PadelClub/Data/DateInterval.swift b/PadelClub/Data/DateInterval.swift index 23438bd..864d9b3 100644 --- a/PadelClub/Data/DateInterval.swift +++ b/PadelClub/Data/DateInterval.swift @@ -10,23 +10,31 @@ import SwiftUI import LeStorage @Observable -final class DateInterval: ModelObject, Storable { - static func resourceName() -> String { return "date-intervals" } - static func tokenExemptedMethods() -> [HTTPMethod] { return [] } - static func filterByStoreIdentifier() -> Bool { return false } - static var relationshipNames: [String] = [] - - var id: String = Store.randomId() - var event: String - var courtIndex: Int - var startDate: Date - var endDate: Date +final class DateInterval: BaseDateInterval { + +// static func resourceName() -> String { return "date-intervals" } +// static func tokenExemptedMethods() -> [HTTPMethod] { return [] } +// static func filterByStoreIdentifier() -> Bool { return false } +// static var relationshipNames: [String] = [] +// +// var id: String = Store.randomId() +// var lastUpdate: Date +// var event: String +// var courtIndex: Int +// var startDate: Date +// var endDate: Date internal init(event: String, courtIndex: Int, startDate: Date, endDate: Date) { - self.event = event - self.courtIndex = courtIndex - self.startDate = startDate - self.endDate = endDate + super.init(event: event, courtIndex: courtIndex, startDate: startDate, endDate: endDate) +// self.lastUpdate = Date() +// self.event = event +// self.courtIndex = courtIndex +// self.startDate = startDate +// self.endDate = endDate + } + + required init(from decoder: any Decoder) throws { + try super.init(from: decoder) } var range: Range { @@ -45,19 +53,20 @@ final class DateInterval: ModelObject, Storable { date <= startDate && date <= endDate && date >= startDate && date >= endDate } - override func deleteDependencies() throws { + override func deleteDependencies() { } - enum CodingKeys: String, CodingKey { - case _id = "id" - case _event = "event" - case _courtIndex = "courtIndex" - case _startDate = "startDate" - case _endDate = "endDate" - } +// enum CodingKeys: String, CodingKey { +// case _id = "id" +// case _lastUpdate = "lastUpdate" +// case _event = "event" +// case _courtIndex = "courtIndex" +// case _startDate = "startDate" +// case _endDate = "endDate" +// } func insertOnServer() throws { - try DataStore.shared.dateIntervals.writeChangeAndInsertOnServer(instance: self) + DataStore.shared.dateIntervals.writeChangeAndInsertOnServer(instance: self) } } diff --git a/PadelClub/Data/DrawLog.swift b/PadelClub/Data/DrawLog.swift index 8673aaf..a0be6e3 100644 --- a/PadelClub/Data/DrawLog.swift +++ b/PadelClub/Data/DrawLog.swift @@ -10,29 +10,7 @@ import SwiftUI import LeStorage @Observable -final class DrawLog: ModelObject, Storable { - static func resourceName() -> String { return "draw-logs" } - static func tokenExemptedMethods() -> [HTTPMethod] { return [] } - static func filterByStoreIdentifier() -> Bool { return true } - static var relationshipNames: [String] = [] - - var id: String = Store.randomId() - var tournament: String - var drawDate: Date = Date() - var drawSeed: Int - var drawMatchIndex: Int - var drawTeamPosition: TeamPosition - var drawType: DrawType - - internal init(id: String = Store.randomId(), tournament: String, drawDate: Date = Date(), drawSeed: Int, drawMatchIndex: Int, drawTeamPosition: TeamPosition, drawType: DrawType) { - self.id = id - self.tournament = tournament - self.drawDate = drawDate - self.drawSeed = drawSeed - self.drawMatchIndex = drawMatchIndex - self.drawTeamPosition = drawTeamPosition - self.drawType = drawType - } +final class DrawLog: BaseDrawLog, SideStorable { func tournamentObject() -> Tournament? { Store.main.findById(self.tournament) @@ -74,7 +52,7 @@ final class DrawLog: ModelObject, Storable { switch drawType { case .seed: let roundIndex = RoundRule.roundIndex(fromMatchIndex: drawMatchIndex) - return tournamentStore.rounds.first(where: { $0.parent == nil && $0.index == roundIndex })?._matches().first(where: { $0.index == drawMatchIndex }) + return tournamentStore?.rounds.first(where: { $0.parent == nil && $0.index == roundIndex })?._matches().first(where: { $0.index == drawMatchIndex }) default: return nil } @@ -92,50 +70,11 @@ final class DrawLog: ModelObject, Storable { return drawMatch()?.matchTitle() ?? "" } - var tournamentStore: TournamentStore { - return TournamentStore.instance(tournamentId: self.tournament) - } - - override func deleteDependencies() throws { - } - - enum CodingKeys: String, CodingKey { - case _id = "id" - case _tournament = "tournament" - case _drawDate = "drawDate" - case _drawSeed = "drawSeed" - case _drawMatchIndex = "drawMatchIndex" - case _drawTeamPosition = "drawTeamPosition" - case _drawType = "drawType" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - id = try container.decode(String.self, forKey: ._id) - tournament = try container.decode(String.self, forKey: ._tournament) - drawDate = try container.decode(Date.self, forKey: ._drawDate) - drawSeed = try container.decode(Int.self, forKey: ._drawSeed) - drawMatchIndex = try container.decode(Int.self, forKey: ._drawMatchIndex) - drawTeamPosition = try container.decode(TeamPosition.self, forKey: ._drawTeamPosition) - drawType = try container.decodeIfPresent(DrawType.self, forKey: ._drawType) ?? .seed + var tournamentStore: TournamentStore? { + return TournamentLibrary.shared.store(tournamentId: self.tournament) } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(id, forKey: ._id) - try container.encode(tournament, forKey: ._tournament) - try container.encode(drawDate, forKey: ._drawDate) - try container.encode(drawSeed, forKey: ._drawSeed) - try container.encode(drawMatchIndex, forKey: ._drawMatchIndex) - try container.encode(drawTeamPosition, forKey: ._drawTeamPosition) - try container.encode(drawType, forKey: ._drawType) - } - - func insertOnServer() throws { - self.tournamentStore.drawLogs.writeChangeAndInsertOnServer(instance: self) + override func deleteDependencies() { } } diff --git a/PadelClub/Data/Event.swift b/PadelClub/Data/Event.swift index 0517c72..c2e9709 100644 --- a/PadelClub/Data/Event.swift +++ b/PadelClub/Data/Event.swift @@ -10,38 +10,28 @@ import LeStorage import SwiftUI @Observable -final class Event: ModelObject, Storable { - - static func resourceName() -> String { return "events" } - static func tokenExemptedMethods() -> [HTTPMethod] { return [] } - static func filterByStoreIdentifier() -> Bool { return false } - static var relationshipNames: [String] = [] - - var id: String = Store.randomId() - var creator: String? - var club: String? - var creationDate: Date = Date() - var name: String? - var tenupId: String? +final class Event: BaseEvent { internal init(creator: String? = nil, club: String? = nil, name: String? = nil, tenupId: String? = nil) { - self.creator = creator - self.club = club - self.name = name - self.tenupId = tenupId + super.init(creator: creator, club: club, name: name, tenupId: tenupId) + self.relatedUser = creator + } + + required init(from decoder: Decoder) throws { + try super.init(from: decoder) } - override func deleteDependencies() throws { + override func deleteDependencies() { let tournaments = self.tournaments for tournament in tournaments { - try tournament.deleteDependencies() + tournament.deleteDependencies() } DataStore.shared.tournaments.deleteDependencies(tournaments) let courtsUnavailabilities = self.courtsUnavailability for courtsUnavailability in courtsUnavailabilities { - try courtsUnavailability.deleteDependencies() + courtsUnavailability.deleteDependencies() } DataStore.shared.dateIntervals.deleteDependencies(courtsUnavailabilities) } @@ -94,7 +84,7 @@ final class Event: ModelObject, Storable { } func insertOnServer() throws { - try DataStore.shared.events.writeChangeAndInsertOnServer(instance: self) + DataStore.shared.events.writeChangeAndInsertOnServer(instance: self) for tournament in self.tournaments { try tournament.insertOnServer() } @@ -104,27 +94,3 @@ final class Event: ModelObject, Storable { } } - - -extension Event { - enum CodingKeys: String, CodingKey { - case _id = "id" - case _creator = "creator" - case _club = "club" - case _creationDate = "creationDate" - case _name = "name" - case _tenupId = "tenupId" - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(id, forKey: ._id) - try container.encode(creator, forKey: ._creator) - try container.encode(club, forKey: ._club) - try container.encode(creationDate, forKey: ._creationDate) - try container.encode(name, forKey: ._name) - try container.encode(tenupId, forKey: ._tenupId) - } - -} diff --git a/PadelClub/Data/Gen/BaseClub.swift b/PadelClub/Data/Gen/BaseClub.swift new file mode 100644 index 0000000..df595de --- /dev/null +++ b/PadelClub/Data/Gen/BaseClub.swift @@ -0,0 +1,146 @@ +// Generated by SwiftModelGenerator +// Do not modify this file manually + +import Foundation +import LeStorage +import SwiftUI + +@Observable +class BaseClub: SyncedModelObject, SyncedStorable { + + static func resourceName() -> String { return "clubs" } + static func tokenExemptedMethods() -> [HTTPMethod] { return [] } + + var id: String = Store.randomId() + var creator: String? = nil + var name: String = "" + var acronym: String = "" + var phone: String? = nil + var code: String? = nil + var address: String? = nil + var city: String? = nil + var zipCode: String? = nil + var latitude: Double? = nil + var longitude: Double? = nil + var courtCount: Int = 2 + var broadcastCode: String? = nil + var timezone: String? = TimeZone.current.identifier + + init( + id: String = Store.randomId(), + creator: String? = nil, + name: String = "", + acronym: String = "", + phone: String? = nil, + code: String? = nil, + address: String? = nil, + city: String? = nil, + zipCode: String? = nil, + latitude: Double? = nil, + longitude: Double? = nil, + courtCount: Int = 2, + broadcastCode: String? = nil, + timezone: String? = TimeZone.current.identifier + ) { + super.init() + self.id = id + self.creator = creator + self.name = name + self.acronym = acronym + self.phone = phone + self.code = code + self.address = address + self.city = city + self.zipCode = zipCode + self.latitude = latitude + self.longitude = longitude + self.courtCount = courtCount + self.broadcastCode = broadcastCode + self.timezone = timezone + } + + enum CodingKeys: String, CodingKey { + case _id = "id" + case _creator = "creator" + case _name = "name" + case _acronym = "acronym" + case _phone = "phone" + case _code = "code" + case _address = "address" + case _city = "city" + case _zipCode = "zipCode" + case _latitude = "latitude" + case _longitude = "longitude" + case _courtCount = "courtCount" + case _broadcastCode = "broadcastCode" + case _timezone = "timezone" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() + self.creator = try container.decodeIfPresent(String.self, forKey: ._creator) ?? nil + self.name = try container.decodeIfPresent(String.self, forKey: ._name) ?? "" + self.acronym = try container.decodeIfPresent(String.self, forKey: ._acronym) ?? "" + self.phone = try container.decodeIfPresent(String.self, forKey: ._phone) ?? nil + self.code = try container.decodeIfPresent(String.self, forKey: ._code) ?? nil + self.address = try container.decodeIfPresent(String.self, forKey: ._address) ?? nil + self.city = try container.decodeIfPresent(String.self, forKey: ._city) ?? nil + self.zipCode = try container.decodeIfPresent(String.self, forKey: ._zipCode) ?? nil + self.latitude = try container.decodeIfPresent(Double.self, forKey: ._latitude) ?? nil + self.longitude = try container.decodeIfPresent(Double.self, forKey: ._longitude) ?? nil + self.courtCount = try container.decodeIfPresent(Int.self, forKey: ._courtCount) ?? 2 + self.broadcastCode = try container.decodeIfPresent(String.self, forKey: ._broadcastCode) ?? nil + self.timezone = try container.decodeIfPresent(String.self, forKey: ._timezone) ?? TimeZone.current.identifier + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.id, forKey: ._id) + try container.encode(self.creator, forKey: ._creator) + try container.encode(self.name, forKey: ._name) + try container.encode(self.acronym, forKey: ._acronym) + try container.encode(self.phone, forKey: ._phone) + try container.encode(self.code, forKey: ._code) + try container.encode(self.address, forKey: ._address) + try container.encode(self.city, forKey: ._city) + try container.encode(self.zipCode, forKey: ._zipCode) + try container.encode(self.latitude, forKey: ._latitude) + try container.encode(self.longitude, forKey: ._longitude) + try container.encode(self.courtCount, forKey: ._courtCount) + try container.encode(self.broadcastCode, forKey: ._broadcastCode) + try container.encode(self.timezone, forKey: ._timezone) + try super.encode(to: encoder) + } + + func creatorValue() -> CustomUser? { + guard let creator = self.creator else { return nil } + return Store.main.findById(creator) + } + + func copy(from other: any Storable) { + guard let club = other as? BaseClub else { return } + self.id = club.id + self.creator = club.creator + self.name = club.name + self.acronym = club.acronym + self.phone = club.phone + self.code = club.code + self.address = club.address + self.city = club.city + self.zipCode = club.zipCode + self.latitude = club.latitude + self.longitude = club.longitude + self.courtCount = club.courtCount + self.broadcastCode = club.broadcastCode + self.timezone = club.timezone + } + + static func relationships() -> [Relationship] { + return [ + Relationship(type: CustomUser.self, keyPath: \BaseClub.creator), + ] + } + +} \ No newline at end of file diff --git a/PadelClub/Data/Gen/BaseCourt.swift b/PadelClub/Data/Gen/BaseCourt.swift new file mode 100644 index 0000000..10099bd --- /dev/null +++ b/PadelClub/Data/Gen/BaseCourt.swift @@ -0,0 +1,89 @@ +// Generated by SwiftModelGenerator +// Do not modify this file manually + +import Foundation +import LeStorage +import SwiftUI + +@Observable +class BaseCourt: SyncedModelObject, SyncedStorable { + + static func resourceName() -> String { return "courts" } + static func tokenExemptedMethods() -> [HTTPMethod] { return [] } + + var id: String = Store.randomId() + var index: Int = 0 + var club: String = "" + var name: String? = nil + var exitAllowed: Bool = false + var indoor: Bool = false + + init( + id: String = Store.randomId(), + index: Int = 0, + club: String = "", + name: String? = nil, + exitAllowed: Bool = false, + indoor: Bool = false + ) { + super.init() + self.id = id + self.index = index + self.club = club + self.name = name + self.exitAllowed = exitAllowed + self.indoor = indoor + } + + enum CodingKeys: String, CodingKey { + case _id = "id" + case _index = "index" + case _club = "club" + case _name = "name" + case _exitAllowed = "exitAllowed" + case _indoor = "indoor" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() + self.index = try container.decodeIfPresent(Int.self, forKey: ._index) ?? 0 + self.club = try container.decodeIfPresent(String.self, forKey: ._club) ?? "" + self.name = try container.decodeIfPresent(String.self, forKey: ._name) ?? nil + self.exitAllowed = try container.decodeIfPresent(Bool.self, forKey: ._exitAllowed) ?? false + self.indoor = try container.decodeIfPresent(Bool.self, forKey: ._indoor) ?? false + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.id, forKey: ._id) + try container.encode(self.index, forKey: ._index) + try container.encode(self.club, forKey: ._club) + try container.encode(self.name, forKey: ._name) + try container.encode(self.exitAllowed, forKey: ._exitAllowed) + try container.encode(self.indoor, forKey: ._indoor) + try super.encode(to: encoder) + } + + func clubValue() -> Club? { + return Store.main.findById(club) + } + + func copy(from other: any Storable) { + guard let court = other as? BaseCourt else { return } + self.id = court.id + self.index = court.index + self.club = court.club + self.name = court.name + self.exitAllowed = court.exitAllowed + self.indoor = court.indoor + } + + static func relationships() -> [Relationship] { + return [ + Relationship(type: Club.self, keyPath: \BaseCourt.club), + ] + } + +} \ No newline at end of file diff --git a/PadelClub/Data/Gen/BaseCustomUser.swift b/PadelClub/Data/Gen/BaseCustomUser.swift new file mode 100644 index 0000000..1b61d70 --- /dev/null +++ b/PadelClub/Data/Gen/BaseCustomUser.swift @@ -0,0 +1,202 @@ +// Generated by SwiftModelGenerator +// Do not modify this file manually + +import Foundation +import LeStorage +import SwiftUI + +@Observable +class BaseCustomUser: SyncedModelObject, SyncedStorable { + + static func resourceName() -> String { return "users" } + static func tokenExemptedMethods() -> [HTTPMethod] { return [.post] } + + var id: String = Store.randomId() + var username: String = "" + var email: String = "" + var clubs: [String] = [] + var umpireCode: String? = nil + var licenceId: String? = nil + var firstName: String = "" + var lastName: String = "" + var phone: String? = nil + var country: String? = nil + var summonsMessageBody: String? = nil + var summonsMessageSignature: String? = nil + var summonsAvailablePaymentMethods: String? = nil + var summonsDisplayFormat: Bool = false + var summonsDisplayEntryFee: Bool = false + var summonsUseFullCustomMessage: Bool = false + var matchFormatsDefaultDuration: [MatchFormat: Int]? = nil + var bracketMatchFormatPreference: MatchFormat? = nil + var groupStageMatchFormatPreference: MatchFormat? = nil + var loserBracketMatchFormatPreference: MatchFormat? = nil + var loserBracketMode: LoserBracketMode = .automatic + var deviceId: String? = nil + var agents: [String] = [] + + init( + id: String = Store.randomId(), + username: String = "", + email: String = "", + clubs: [String] = [], + umpireCode: String? = nil, + licenceId: String? = nil, + firstName: String = "", + lastName: String = "", + phone: String? = nil, + country: String? = nil, + summonsMessageBody: String? = nil, + summonsMessageSignature: String? = nil, + summonsAvailablePaymentMethods: String? = nil, + summonsDisplayFormat: Bool = false, + summonsDisplayEntryFee: Bool = false, + summonsUseFullCustomMessage: Bool = false, + matchFormatsDefaultDuration: [MatchFormat: Int]? = nil, + bracketMatchFormatPreference: MatchFormat? = nil, + groupStageMatchFormatPreference: MatchFormat? = nil, + loserBracketMatchFormatPreference: MatchFormat? = nil, + loserBracketMode: LoserBracketMode = .automatic, + deviceId: String? = nil, + agents: [String] = [] + ) { + super.init() + self.id = id + self.username = username + self.email = email + self.clubs = clubs + self.umpireCode = umpireCode + self.licenceId = licenceId + self.firstName = firstName + self.lastName = lastName + self.phone = phone + self.country = country + self.summonsMessageBody = summonsMessageBody + self.summonsMessageSignature = summonsMessageSignature + self.summonsAvailablePaymentMethods = summonsAvailablePaymentMethods + self.summonsDisplayFormat = summonsDisplayFormat + self.summonsDisplayEntryFee = summonsDisplayEntryFee + self.summonsUseFullCustomMessage = summonsUseFullCustomMessage + self.matchFormatsDefaultDuration = matchFormatsDefaultDuration + self.bracketMatchFormatPreference = bracketMatchFormatPreference + self.groupStageMatchFormatPreference = groupStageMatchFormatPreference + self.loserBracketMatchFormatPreference = loserBracketMatchFormatPreference + self.loserBracketMode = loserBracketMode + self.deviceId = deviceId + self.agents = agents + } + + enum CodingKeys: String, CodingKey { + case _id = "id" + case _username = "username" + case _email = "email" + case _clubs = "clubs" + case _umpireCode = "umpireCode" + case _licenceId = "licenceId" + case _firstName = "firstName" + case _lastName = "lastName" + case _phone = "phone" + case _country = "country" + case _summonsMessageBody = "summonsMessageBody" + case _summonsMessageSignature = "summonsMessageSignature" + case _summonsAvailablePaymentMethods = "summonsAvailablePaymentMethods" + case _summonsDisplayFormat = "summonsDisplayFormat" + case _summonsDisplayEntryFee = "summonsDisplayEntryFee" + case _summonsUseFullCustomMessage = "summonsUseFullCustomMessage" + case _matchFormatsDefaultDuration = "matchFormatsDefaultDuration" + case _bracketMatchFormatPreference = "bracketMatchFormatPreference" + case _groupStageMatchFormatPreference = "groupStageMatchFormatPreference" + case _loserBracketMatchFormatPreference = "loserBracketMatchFormatPreference" + case _loserBracketMode = "loserBracketMode" + case _deviceId = "deviceId" + case _agents = "agents" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() + self.username = try container.decodeIfPresent(String.self, forKey: ._username) ?? "" + self.email = try container.decodeIfPresent(String.self, forKey: ._email) ?? "" + self.clubs = try container.decodeIfPresent([String].self, forKey: ._clubs) ?? [] + self.umpireCode = try container.decodeIfPresent(String.self, forKey: ._umpireCode) ?? nil + self.licenceId = try container.decodeIfPresent(String.self, forKey: ._licenceId) ?? nil + self.firstName = try container.decodeIfPresent(String.self, forKey: ._firstName) ?? "" + self.lastName = try container.decodeIfPresent(String.self, forKey: ._lastName) ?? "" + self.phone = try container.decodeIfPresent(String.self, forKey: ._phone) ?? nil + self.country = try container.decodeIfPresent(String.self, forKey: ._country) ?? nil + self.summonsMessageBody = try container.decodeIfPresent(String.self, forKey: ._summonsMessageBody) ?? nil + self.summonsMessageSignature = try container.decodeIfPresent(String.self, forKey: ._summonsMessageSignature) ?? nil + self.summonsAvailablePaymentMethods = try container.decodeIfPresent(String.self, forKey: ._summonsAvailablePaymentMethods) ?? nil + self.summonsDisplayFormat = try container.decodeIfPresent(Bool.self, forKey: ._summonsDisplayFormat) ?? false + self.summonsDisplayEntryFee = try container.decodeIfPresent(Bool.self, forKey: ._summonsDisplayEntryFee) ?? false + self.summonsUseFullCustomMessage = try container.decodeIfPresent(Bool.self, forKey: ._summonsUseFullCustomMessage) ?? false + self.matchFormatsDefaultDuration = try container.decodeIfPresent([MatchFormat: Int].self, forKey: ._matchFormatsDefaultDuration) ?? nil + self.bracketMatchFormatPreference = try container.decodeIfPresent(MatchFormat.self, forKey: ._bracketMatchFormatPreference) ?? nil + self.groupStageMatchFormatPreference = try container.decodeIfPresent(MatchFormat.self, forKey: ._groupStageMatchFormatPreference) ?? nil + self.loserBracketMatchFormatPreference = try container.decodeIfPresent(MatchFormat.self, forKey: ._loserBracketMatchFormatPreference) ?? nil + self.loserBracketMode = try container.decodeIfPresent(LoserBracketMode.self, forKey: ._loserBracketMode) ?? .automatic + self.deviceId = try container.decodeIfPresent(String.self, forKey: ._deviceId) ?? nil + self.agents = try container.decodeIfPresent([String].self, forKey: ._agents) ?? [] + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.id, forKey: ._id) + try container.encode(self.username, forKey: ._username) + try container.encode(self.email, forKey: ._email) + try container.encode(self.clubs, forKey: ._clubs) + try container.encode(self.umpireCode, forKey: ._umpireCode) + try container.encode(self.licenceId, forKey: ._licenceId) + try container.encode(self.firstName, forKey: ._firstName) + try container.encode(self.lastName, forKey: ._lastName) + try container.encode(self.phone, forKey: ._phone) + try container.encode(self.country, forKey: ._country) + try container.encode(self.summonsMessageBody, forKey: ._summonsMessageBody) + try container.encode(self.summonsMessageSignature, forKey: ._summonsMessageSignature) + try container.encode(self.summonsAvailablePaymentMethods, forKey: ._summonsAvailablePaymentMethods) + try container.encode(self.summonsDisplayFormat, forKey: ._summonsDisplayFormat) + try container.encode(self.summonsDisplayEntryFee, forKey: ._summonsDisplayEntryFee) + try container.encode(self.summonsUseFullCustomMessage, forKey: ._summonsUseFullCustomMessage) + try container.encode(self.matchFormatsDefaultDuration, forKey: ._matchFormatsDefaultDuration) + try container.encode(self.bracketMatchFormatPreference, forKey: ._bracketMatchFormatPreference) + try container.encode(self.groupStageMatchFormatPreference, forKey: ._groupStageMatchFormatPreference) + try container.encode(self.loserBracketMatchFormatPreference, forKey: ._loserBracketMatchFormatPreference) + try container.encode(self.loserBracketMode, forKey: ._loserBracketMode) + try container.encode(self.deviceId, forKey: ._deviceId) + try container.encode(self.agents, forKey: ._agents) + try super.encode(to: encoder) + } + + func copy(from other: any Storable) { + guard let customuser = other as? BaseCustomUser else { return } + self.id = customuser.id + self.username = customuser.username + self.email = customuser.email + self.clubs = customuser.clubs + self.umpireCode = customuser.umpireCode + self.licenceId = customuser.licenceId + self.firstName = customuser.firstName + self.lastName = customuser.lastName + self.phone = customuser.phone + self.country = customuser.country + self.summonsMessageBody = customuser.summonsMessageBody + self.summonsMessageSignature = customuser.summonsMessageSignature + self.summonsAvailablePaymentMethods = customuser.summonsAvailablePaymentMethods + self.summonsDisplayFormat = customuser.summonsDisplayFormat + self.summonsDisplayEntryFee = customuser.summonsDisplayEntryFee + self.summonsUseFullCustomMessage = customuser.summonsUseFullCustomMessage + self.matchFormatsDefaultDuration = customuser.matchFormatsDefaultDuration + self.bracketMatchFormatPreference = customuser.bracketMatchFormatPreference + self.groupStageMatchFormatPreference = customuser.groupStageMatchFormatPreference + self.loserBracketMatchFormatPreference = customuser.loserBracketMatchFormatPreference + self.loserBracketMode = customuser.loserBracketMode + self.deviceId = customuser.deviceId + self.agents = customuser.agents + } + + static func relationships() -> [Relationship] { + return [] + } + +} \ No newline at end of file diff --git a/PadelClub/Data/Gen/BaseDateInterval.swift b/PadelClub/Data/Gen/BaseDateInterval.swift new file mode 100644 index 0000000..a429f9d --- /dev/null +++ b/PadelClub/Data/Gen/BaseDateInterval.swift @@ -0,0 +1,76 @@ +// Generated by SwiftModelGenerator +// Do not modify this file manually + +import Foundation +import LeStorage +import SwiftUI + +@Observable +class BaseDateInterval: SyncedModelObject, SyncedStorable { + + static func resourceName() -> String { return "date-intervals" } + static func tokenExemptedMethods() -> [HTTPMethod] { return [] } + + var id: String = Store.randomId() + var event: String = "" + var courtIndex: Int = 0 + var startDate: Date = Date() + var endDate: Date = Date() + + init( + id: String = Store.randomId(), + event: String = "", + courtIndex: Int = 0, + startDate: Date = Date(), + endDate: Date = Date() + ) { + super.init() + self.id = id + self.event = event + self.courtIndex = courtIndex + self.startDate = startDate + self.endDate = endDate + } + + enum CodingKeys: String, CodingKey { + case _id = "id" + case _event = "event" + case _courtIndex = "courtIndex" + case _startDate = "startDate" + case _endDate = "endDate" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() + self.event = try container.decodeIfPresent(String.self, forKey: ._event) ?? "" + self.courtIndex = try container.decodeIfPresent(Int.self, forKey: ._courtIndex) ?? 0 + self.startDate = try container.decodeIfPresent(Date.self, forKey: ._startDate) ?? Date() + self.endDate = try container.decodeIfPresent(Date.self, forKey: ._endDate) ?? Date() + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.id, forKey: ._id) + try container.encode(self.event, forKey: ._event) + try container.encode(self.courtIndex, forKey: ._courtIndex) + try container.encode(self.startDate, forKey: ._startDate) + try container.encode(self.endDate, forKey: ._endDate) + try super.encode(to: encoder) + } + + func copy(from other: any Storable) { + guard let dateinterval = other as? BaseDateInterval else { return } + self.id = dateinterval.id + self.event = dateinterval.event + self.courtIndex = dateinterval.courtIndex + self.startDate = dateinterval.startDate + self.endDate = dateinterval.endDate + } + + static func relationships() -> [Relationship] { + return [] + } + +} \ No newline at end of file diff --git a/PadelClub/Data/Gen/BaseDrawLog.swift b/PadelClub/Data/Gen/BaseDrawLog.swift new file mode 100644 index 0000000..b339153 --- /dev/null +++ b/PadelClub/Data/Gen/BaseDrawLog.swift @@ -0,0 +1,96 @@ +// Generated by SwiftModelGenerator +// Do not modify this file manually + +import Foundation +import LeStorage +import SwiftUI + +@Observable +class BaseDrawLog: SyncedModelObject, SyncedStorable { + + static func resourceName() -> String { return "draw-logs" } + static func tokenExemptedMethods() -> [HTTPMethod] { return [] } + + var id: String = Store.randomId() + var tournament: String = "" + var drawDate: Date = Date() + var drawSeed: Int = 0 + var drawMatchIndex: Int = 0 + var drawTeamPosition: TeamPosition = TeamPosition.one + var drawType: DrawType = DrawType.seed + + init( + id: String = Store.randomId(), + tournament: String = "", + drawDate: Date = Date(), + drawSeed: Int = 0, + drawMatchIndex: Int = 0, + drawTeamPosition: TeamPosition = TeamPosition.one, + drawType: DrawType = DrawType.seed + ) { + super.init() + self.id = id + self.tournament = tournament + self.drawDate = drawDate + self.drawSeed = drawSeed + self.drawMatchIndex = drawMatchIndex + self.drawTeamPosition = drawTeamPosition + self.drawType = drawType + } + + enum CodingKeys: String, CodingKey { + case _id = "id" + case _tournament = "tournament" + case _drawDate = "drawDate" + case _drawSeed = "drawSeed" + case _drawMatchIndex = "drawMatchIndex" + case _drawTeamPosition = "drawTeamPosition" + case _drawType = "drawType" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() + self.tournament = try container.decodeIfPresent(String.self, forKey: ._tournament) ?? "" + self.drawDate = try container.decodeIfPresent(Date.self, forKey: ._drawDate) ?? Date() + self.drawSeed = try container.decodeIfPresent(Int.self, forKey: ._drawSeed) ?? 0 + self.drawMatchIndex = try container.decodeIfPresent(Int.self, forKey: ._drawMatchIndex) ?? 0 + self.drawTeamPosition = try container.decodeIfPresent(TeamPosition.self, forKey: ._drawTeamPosition) ?? TeamPosition.one + self.drawType = try container.decodeIfPresent(DrawType.self, forKey: ._drawType) ?? DrawType.seed + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.id, forKey: ._id) + try container.encode(self.tournament, forKey: ._tournament) + try container.encode(self.drawDate, forKey: ._drawDate) + try container.encode(self.drawSeed, forKey: ._drawSeed) + try container.encode(self.drawMatchIndex, forKey: ._drawMatchIndex) + try container.encode(self.drawTeamPosition, forKey: ._drawTeamPosition) + try container.encode(self.drawType, forKey: ._drawType) + try super.encode(to: encoder) + } + + func tournamentValue() -> Tournament? { + return Store.main.findById(tournament) + } + + func copy(from other: any Storable) { + guard let drawlog = other as? BaseDrawLog else { return } + self.id = drawlog.id + self.tournament = drawlog.tournament + self.drawDate = drawlog.drawDate + self.drawSeed = drawlog.drawSeed + self.drawMatchIndex = drawlog.drawMatchIndex + self.drawTeamPosition = drawlog.drawTeamPosition + self.drawType = drawlog.drawType + } + + static func relationships() -> [Relationship] { + return [ + Relationship(type: Tournament.self, keyPath: \BaseDrawLog.tournament), + ] + } + +} \ No newline at end of file diff --git a/PadelClub/Data/Gen/BaseEvent.swift b/PadelClub/Data/Gen/BaseEvent.swift new file mode 100644 index 0000000..4dcb703 --- /dev/null +++ b/PadelClub/Data/Gen/BaseEvent.swift @@ -0,0 +1,96 @@ +// Generated by SwiftModelGenerator +// Do not modify this file manually + +import Foundation +import LeStorage +import SwiftUI + +@Observable +class BaseEvent: SyncedModelObject, SyncedStorable { + + static func resourceName() -> String { return "events" } + static func tokenExemptedMethods() -> [HTTPMethod] { return [] } + + var id: String = Store.randomId() + var creator: String? = nil + var club: String? = nil + var creationDate: Date = Date() + var name: String? = nil + var tenupId: String? = nil + + init( + id: String = Store.randomId(), + creator: String? = nil, + club: String? = nil, + creationDate: Date = Date(), + name: String? = nil, + tenupId: String? = nil + ) { + super.init() + self.id = id + self.creator = creator + self.club = club + self.creationDate = creationDate + self.name = name + self.tenupId = tenupId + } + + enum CodingKeys: String, CodingKey { + case _id = "id" + case _creator = "creator" + case _club = "club" + case _creationDate = "creationDate" + case _name = "name" + case _tenupId = "tenupId" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() + self.creator = try container.decodeIfPresent(String.self, forKey: ._creator) ?? nil + self.club = try container.decodeIfPresent(String.self, forKey: ._club) ?? nil + self.creationDate = try container.decodeIfPresent(Date.self, forKey: ._creationDate) ?? Date() + self.name = try container.decodeIfPresent(String.self, forKey: ._name) ?? nil + self.tenupId = try container.decodeIfPresent(String.self, forKey: ._tenupId) ?? nil + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.id, forKey: ._id) + try container.encode(self.creator, forKey: ._creator) + try container.encode(self.club, forKey: ._club) + try container.encode(self.creationDate, forKey: ._creationDate) + try container.encode(self.name, forKey: ._name) + try container.encode(self.tenupId, forKey: ._tenupId) + try super.encode(to: encoder) + } + + func creatorValue() -> CustomUser? { + guard let creator = self.creator else { return nil } + return Store.main.findById(creator) + } + + func clubValue() -> Club? { + guard let club = self.club else { return nil } + return Store.main.findById(club) + } + + func copy(from other: any Storable) { + guard let event = other as? BaseEvent else { return } + self.id = event.id + self.creator = event.creator + self.club = event.club + self.creationDate = event.creationDate + self.name = event.name + self.tenupId = event.tenupId + } + + static func relationships() -> [Relationship] { + return [ + Relationship(type: CustomUser.self, keyPath: \BaseEvent.creator), + Relationship(type: Club.self, keyPath: \BaseEvent.club), + ] + } + +} \ No newline at end of file diff --git a/PadelClub/Data/Gen/BaseGroupStage.swift b/PadelClub/Data/Gen/BaseGroupStage.swift new file mode 100644 index 0000000..c5c9e1d --- /dev/null +++ b/PadelClub/Data/Gen/BaseGroupStage.swift @@ -0,0 +1,103 @@ +// Generated by SwiftModelGenerator +// Do not modify this file manually + +import Foundation +import LeStorage +import SwiftUI + +@Observable +class BaseGroupStage: SyncedModelObject, SyncedStorable { + + static func resourceName() -> String { return "group-stages" } + static func tokenExemptedMethods() -> [HTTPMethod] { return [] } + + var id: String = Store.randomId() + var tournament: String = "" + var index: Int = 0 + var size: Int = 0 + var format: MatchFormat? = nil + var startDate: Date? = nil + var name: String? = nil + var step: Int = 0 + + init( + id: String = Store.randomId(), + tournament: String = "", + index: Int = 0, + size: Int = 0, + format: MatchFormat? = nil, + startDate: Date? = nil, + name: String? = nil, + step: Int = 0 + ) { + super.init() + self.id = id + self.tournament = tournament + self.index = index + self.size = size + self.format = format + self.startDate = startDate + self.name = name + self.step = step + } + + enum CodingKeys: String, CodingKey { + case _id = "id" + case _tournament = "tournament" + case _index = "index" + case _size = "size" + case _format = "format" + case _startDate = "startDate" + case _name = "name" + case _step = "step" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() + self.tournament = try container.decodeIfPresent(String.self, forKey: ._tournament) ?? "" + self.index = try container.decodeIfPresent(Int.self, forKey: ._index) ?? 0 + self.size = try container.decodeIfPresent(Int.self, forKey: ._size) ?? 0 + self.format = try container.decodeIfPresent(MatchFormat.self, forKey: ._format) ?? nil + self.startDate = try container.decodeIfPresent(Date.self, forKey: ._startDate) ?? nil + self.name = try container.decodeIfPresent(String.self, forKey: ._name) ?? nil + self.step = try container.decodeIfPresent(Int.self, forKey: ._step) ?? 0 + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.id, forKey: ._id) + try container.encode(self.tournament, forKey: ._tournament) + try container.encode(self.index, forKey: ._index) + try container.encode(self.size, forKey: ._size) + try container.encode(self.format, forKey: ._format) + try container.encode(self.startDate, forKey: ._startDate) + try container.encode(self.name, forKey: ._name) + try container.encode(self.step, forKey: ._step) + try super.encode(to: encoder) + } + + func tournamentValue() -> Tournament? { + return Store.main.findById(tournament) + } + + func copy(from other: any Storable) { + guard let groupstage = other as? BaseGroupStage else { return } + self.id = groupstage.id + self.tournament = groupstage.tournament + self.index = groupstage.index + self.size = groupstage.size + self.format = groupstage.format + self.startDate = groupstage.startDate + self.name = groupstage.name + self.step = groupstage.step + } + + static func relationships() -> [Relationship] { + return [ + Relationship(type: Tournament.self, keyPath: \BaseGroupStage.tournament), + ] + } + +} \ No newline at end of file diff --git a/PadelClub/Data/Gen/BaseMatch.swift b/PadelClub/Data/Gen/BaseMatch.swift new file mode 100644 index 0000000..f66ab28 --- /dev/null +++ b/PadelClub/Data/Gen/BaseMatch.swift @@ -0,0 +1,152 @@ +// Generated by SwiftModelGenerator +// Do not modify this file manually + +import Foundation +import LeStorage +import SwiftUI + +@Observable +class BaseMatch: SyncedModelObject, SyncedStorable { + + static func resourceName() -> String { return "matches" } + static func tokenExemptedMethods() -> [HTTPMethod] { return [] } + + var id: String = Store.randomId() + var round: String? = nil + var groupStage: String? = nil + var startDate: Date? = nil + var endDate: Date? = nil + var index: Int = 0 + var format: MatchFormat? = nil + var servingTeamId: String? = nil + var winningTeamId: String? = nil + var losingTeamId: String? = nil + var name: String? = nil + var disabled: Bool = false + var courtIndex: Int? = nil + var confirmed: Bool = false + + init( + id: String = Store.randomId(), + round: String? = nil, + groupStage: String? = nil, + startDate: Date? = nil, + endDate: Date? = nil, + index: Int = 0, + format: MatchFormat? = nil, + servingTeamId: String? = nil, + winningTeamId: String? = nil, + losingTeamId: String? = nil, + name: String? = nil, + disabled: Bool = false, + courtIndex: Int? = nil, + confirmed: Bool = false + ) { + super.init() + self.id = id + self.round = round + self.groupStage = groupStage + self.startDate = startDate + self.endDate = endDate + self.index = index + self.format = format + self.servingTeamId = servingTeamId + self.winningTeamId = winningTeamId + self.losingTeamId = losingTeamId + self.name = name + self.disabled = disabled + self.courtIndex = courtIndex + self.confirmed = confirmed + } + + enum CodingKeys: String, CodingKey { + case _id = "id" + case _round = "round" + case _groupStage = "groupStage" + case _startDate = "startDate" + case _endDate = "endDate" + case _index = "index" + case _format = "format" + case _servingTeamId = "servingTeamId" + case _winningTeamId = "winningTeamId" + case _losingTeamId = "losingTeamId" + case _name = "name" + case _disabled = "disabled" + case _courtIndex = "courtIndex" + case _confirmed = "confirmed" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() + self.round = try container.decodeIfPresent(String.self, forKey: ._round) ?? nil + self.groupStage = try container.decodeIfPresent(String.self, forKey: ._groupStage) ?? nil + self.startDate = try container.decodeIfPresent(Date.self, forKey: ._startDate) ?? nil + self.endDate = try container.decodeIfPresent(Date.self, forKey: ._endDate) ?? nil + self.index = try container.decodeIfPresent(Int.self, forKey: ._index) ?? 0 + self.format = try container.decodeIfPresent(MatchFormat.self, forKey: ._format) ?? nil + self.servingTeamId = try container.decodeIfPresent(String.self, forKey: ._servingTeamId) ?? nil + self.winningTeamId = try container.decodeIfPresent(String.self, forKey: ._winningTeamId) ?? nil + self.losingTeamId = try container.decodeIfPresent(String.self, forKey: ._losingTeamId) ?? nil + self.name = try container.decodeIfPresent(String.self, forKey: ._name) ?? nil + self.disabled = try container.decodeIfPresent(Bool.self, forKey: ._disabled) ?? false + self.courtIndex = try container.decodeIfPresent(Int.self, forKey: ._courtIndex) ?? nil + self.confirmed = try container.decodeIfPresent(Bool.self, forKey: ._confirmed) ?? false + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.id, forKey: ._id) + try container.encode(self.round, forKey: ._round) + try container.encode(self.groupStage, forKey: ._groupStage) + try container.encode(self.startDate, forKey: ._startDate) + try container.encode(self.endDate, forKey: ._endDate) + try container.encode(self.index, forKey: ._index) + try container.encode(self.format, forKey: ._format) + try container.encode(self.servingTeamId, forKey: ._servingTeamId) + try container.encode(self.winningTeamId, forKey: ._winningTeamId) + try container.encode(self.losingTeamId, forKey: ._losingTeamId) + try container.encode(self.name, forKey: ._name) + try container.encode(self.disabled, forKey: ._disabled) + try container.encode(self.courtIndex, forKey: ._courtIndex) + try container.encode(self.confirmed, forKey: ._confirmed) + try super.encode(to: encoder) + } + + func roundValue() -> Round? { + guard let round = self.round else { return nil } + return self.store?.findById(round) + } + + func groupStageValue() -> GroupStage? { + guard let groupStage = self.groupStage else { return nil } + return self.store?.findById(groupStage) + } + + func copy(from other: any Storable) { + guard let match = other as? BaseMatch else { return } + self.id = match.id + self.round = match.round + self.groupStage = match.groupStage + self.startDate = match.startDate + self.endDate = match.endDate + self.index = match.index + self.format = match.format + self.servingTeamId = match.servingTeamId + self.winningTeamId = match.winningTeamId + self.losingTeamId = match.losingTeamId + self.name = match.name + self.disabled = match.disabled + self.courtIndex = match.courtIndex + self.confirmed = match.confirmed + } + + static func relationships() -> [Relationship] { + return [ + Relationship(type: Round.self, keyPath: \BaseMatch.round), + Relationship(type: GroupStage.self, keyPath: \BaseMatch.groupStage), + ] + } + +} \ No newline at end of file diff --git a/PadelClub/Data/Gen/BaseMatchScheduler.swift b/PadelClub/Data/Gen/BaseMatchScheduler.swift new file mode 100644 index 0000000..7c33848 --- /dev/null +++ b/PadelClub/Data/Gen/BaseMatchScheduler.swift @@ -0,0 +1,159 @@ +// Generated by SwiftModelGenerator +// Do not modify this file manually + +import Foundation +import LeStorage +import SwiftUI + +@Observable +class BaseMatchScheduler: BaseModelObject, Storable { + + static func resourceName() -> String { return "match-scheduler" } + static func tokenExemptedMethods() -> [HTTPMethod] { return [] } + + var id: String = Store.randomId() + var tournament: String = "" + var timeDifferenceLimit: Int = 0 + var loserBracketRotationDifference: Int = 0 + var upperBracketRotationDifference: Int = 0 + var accountUpperBracketBreakTime: Bool = false + var accountLoserBracketBreakTime: Bool = false + var randomizeCourts: Bool = false + var rotationDifferenceIsImportant: Bool = false + var shouldHandleUpperRoundSlice: Bool = false + var shouldEndRoundBeforeStartingNext: Bool = false + var groupStageChunkCount: Int? = nil + var overrideCourtsUnavailability: Bool = false + var shouldTryToFillUpCourtsAvailable: Bool = false + var courtsAvailable: Set = Set() + var simultaneousStart: Bool = true + + init( + id: String = Store.randomId(), + tournament: String = "", + timeDifferenceLimit: Int = 0, + loserBracketRotationDifference: Int = 0, + upperBracketRotationDifference: Int = 0, + accountUpperBracketBreakTime: Bool = false, + accountLoserBracketBreakTime: Bool = false, + randomizeCourts: Bool = false, + rotationDifferenceIsImportant: Bool = false, + shouldHandleUpperRoundSlice: Bool = false, + shouldEndRoundBeforeStartingNext: Bool = false, + groupStageChunkCount: Int? = nil, + overrideCourtsUnavailability: Bool = false, + shouldTryToFillUpCourtsAvailable: Bool = false, + courtsAvailable: Set = Set(), + simultaneousStart: Bool = true + ) { + super.init() + self.id = id + self.tournament = tournament + self.timeDifferenceLimit = timeDifferenceLimit + self.loserBracketRotationDifference = loserBracketRotationDifference + self.upperBracketRotationDifference = upperBracketRotationDifference + self.accountUpperBracketBreakTime = accountUpperBracketBreakTime + self.accountLoserBracketBreakTime = accountLoserBracketBreakTime + self.randomizeCourts = randomizeCourts + self.rotationDifferenceIsImportant = rotationDifferenceIsImportant + self.shouldHandleUpperRoundSlice = shouldHandleUpperRoundSlice + self.shouldEndRoundBeforeStartingNext = shouldEndRoundBeforeStartingNext + self.groupStageChunkCount = groupStageChunkCount + self.overrideCourtsUnavailability = overrideCourtsUnavailability + self.shouldTryToFillUpCourtsAvailable = shouldTryToFillUpCourtsAvailable + self.courtsAvailable = courtsAvailable + self.simultaneousStart = simultaneousStart + } + + enum CodingKeys: String, CodingKey { + case _id = "id" + case _tournament = "tournament" + case _timeDifferenceLimit = "timeDifferenceLimit" + case _loserBracketRotationDifference = "loserBracketRotationDifference" + case _upperBracketRotationDifference = "upperBracketRotationDifference" + case _accountUpperBracketBreakTime = "accountUpperBracketBreakTime" + case _accountLoserBracketBreakTime = "accountLoserBracketBreakTime" + case _randomizeCourts = "randomizeCourts" + case _rotationDifferenceIsImportant = "rotationDifferenceIsImportant" + case _shouldHandleUpperRoundSlice = "shouldHandleUpperRoundSlice" + case _shouldEndRoundBeforeStartingNext = "shouldEndRoundBeforeStartingNext" + case _groupStageChunkCount = "groupStageChunkCount" + case _overrideCourtsUnavailability = "overrideCourtsUnavailability" + case _shouldTryToFillUpCourtsAvailable = "shouldTryToFillUpCourtsAvailable" + case _courtsAvailable = "courtsAvailable" + case _simultaneousStart = "simultaneousStart" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() + self.tournament = try container.decodeIfPresent(String.self, forKey: ._tournament) ?? "" + self.timeDifferenceLimit = try container.decodeIfPresent(Int.self, forKey: ._timeDifferenceLimit) ?? 0 + self.loserBracketRotationDifference = try container.decodeIfPresent(Int.self, forKey: ._loserBracketRotationDifference) ?? 0 + self.upperBracketRotationDifference = try container.decodeIfPresent(Int.self, forKey: ._upperBracketRotationDifference) ?? 0 + self.accountUpperBracketBreakTime = try container.decodeIfPresent(Bool.self, forKey: ._accountUpperBracketBreakTime) ?? false + self.accountLoserBracketBreakTime = try container.decodeIfPresent(Bool.self, forKey: ._accountLoserBracketBreakTime) ?? false + self.randomizeCourts = try container.decodeIfPresent(Bool.self, forKey: ._randomizeCourts) ?? false + self.rotationDifferenceIsImportant = try container.decodeIfPresent(Bool.self, forKey: ._rotationDifferenceIsImportant) ?? false + self.shouldHandleUpperRoundSlice = try container.decodeIfPresent(Bool.self, forKey: ._shouldHandleUpperRoundSlice) ?? false + self.shouldEndRoundBeforeStartingNext = try container.decodeIfPresent(Bool.self, forKey: ._shouldEndRoundBeforeStartingNext) ?? false + self.groupStageChunkCount = try container.decodeIfPresent(Int.self, forKey: ._groupStageChunkCount) ?? nil + self.overrideCourtsUnavailability = try container.decodeIfPresent(Bool.self, forKey: ._overrideCourtsUnavailability) ?? false + self.shouldTryToFillUpCourtsAvailable = try container.decodeIfPresent(Bool.self, forKey: ._shouldTryToFillUpCourtsAvailable) ?? false + self.courtsAvailable = try container.decodeIfPresent(Set.self, forKey: ._courtsAvailable) ?? Set() + self.simultaneousStart = try container.decodeIfPresent(Bool.self, forKey: ._simultaneousStart) ?? true + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.id, forKey: ._id) + try container.encode(self.tournament, forKey: ._tournament) + try container.encode(self.timeDifferenceLimit, forKey: ._timeDifferenceLimit) + try container.encode(self.loserBracketRotationDifference, forKey: ._loserBracketRotationDifference) + try container.encode(self.upperBracketRotationDifference, forKey: ._upperBracketRotationDifference) + try container.encode(self.accountUpperBracketBreakTime, forKey: ._accountUpperBracketBreakTime) + try container.encode(self.accountLoserBracketBreakTime, forKey: ._accountLoserBracketBreakTime) + try container.encode(self.randomizeCourts, forKey: ._randomizeCourts) + try container.encode(self.rotationDifferenceIsImportant, forKey: ._rotationDifferenceIsImportant) + try container.encode(self.shouldHandleUpperRoundSlice, forKey: ._shouldHandleUpperRoundSlice) + try container.encode(self.shouldEndRoundBeforeStartingNext, forKey: ._shouldEndRoundBeforeStartingNext) + try container.encode(self.groupStageChunkCount, forKey: ._groupStageChunkCount) + try container.encode(self.overrideCourtsUnavailability, forKey: ._overrideCourtsUnavailability) + try container.encode(self.shouldTryToFillUpCourtsAvailable, forKey: ._shouldTryToFillUpCourtsAvailable) + try container.encode(self.courtsAvailable, forKey: ._courtsAvailable) + try container.encode(self.simultaneousStart, forKey: ._simultaneousStart) + try super.encode(to: encoder) + } + + func tournamentValue() -> Tournament? { + return Store.main.findById(tournament) + } + + func copy(from other: any Storable) { + guard let matchscheduler = other as? BaseMatchScheduler else { return } + self.id = matchscheduler.id + self.tournament = matchscheduler.tournament + self.timeDifferenceLimit = matchscheduler.timeDifferenceLimit + self.loserBracketRotationDifference = matchscheduler.loserBracketRotationDifference + self.upperBracketRotationDifference = matchscheduler.upperBracketRotationDifference + self.accountUpperBracketBreakTime = matchscheduler.accountUpperBracketBreakTime + self.accountLoserBracketBreakTime = matchscheduler.accountLoserBracketBreakTime + self.randomizeCourts = matchscheduler.randomizeCourts + self.rotationDifferenceIsImportant = matchscheduler.rotationDifferenceIsImportant + self.shouldHandleUpperRoundSlice = matchscheduler.shouldHandleUpperRoundSlice + self.shouldEndRoundBeforeStartingNext = matchscheduler.shouldEndRoundBeforeStartingNext + self.groupStageChunkCount = matchscheduler.groupStageChunkCount + self.overrideCourtsUnavailability = matchscheduler.overrideCourtsUnavailability + self.shouldTryToFillUpCourtsAvailable = matchscheduler.shouldTryToFillUpCourtsAvailable + self.courtsAvailable = matchscheduler.courtsAvailable + self.simultaneousStart = matchscheduler.simultaneousStart + } + + static func relationships() -> [Relationship] { + return [ + Relationship(type: Tournament.self, keyPath: \BaseMatchScheduler.tournament), + ] + } + +} \ No newline at end of file diff --git a/PadelClub/Data/Gen/BaseMonthData.swift b/PadelClub/Data/Gen/BaseMonthData.swift new file mode 100644 index 0000000..1f1a12c --- /dev/null +++ b/PadelClub/Data/Gen/BaseMonthData.swift @@ -0,0 +1,118 @@ +// Generated by SwiftModelGenerator +// Do not modify this file manually + +import Foundation +import LeStorage +import SwiftUI + +@Observable +class BaseMonthData: BaseModelObject, Storable { + + static func resourceName() -> String { return "month-data" } + static func tokenExemptedMethods() -> [HTTPMethod] { return [] } + + var id: String = Store.randomId() + var monthKey: String = "" + var creationDate: Date = Date() + var maleUnrankedValue: Int? = nil + var femaleUnrankedValue: Int? = nil + var maleCount: Int? = nil + var femaleCount: Int? = nil + var anonymousCount: Int? = nil + var incompleteMode: Bool = false + var dataModelIdentifier: String? = nil + var fileModelIdentifier: String? = nil + + init( + id: String = Store.randomId(), + monthKey: String = "", + creationDate: Date = Date(), + maleUnrankedValue: Int? = nil, + femaleUnrankedValue: Int? = nil, + maleCount: Int? = nil, + femaleCount: Int? = nil, + anonymousCount: Int? = nil, + incompleteMode: Bool = false, + dataModelIdentifier: String? = nil, + fileModelIdentifier: String? = nil + ) { + super.init() + self.id = id + self.monthKey = monthKey + self.creationDate = creationDate + self.maleUnrankedValue = maleUnrankedValue + self.femaleUnrankedValue = femaleUnrankedValue + self.maleCount = maleCount + self.femaleCount = femaleCount + self.anonymousCount = anonymousCount + self.incompleteMode = incompleteMode + self.dataModelIdentifier = dataModelIdentifier + self.fileModelIdentifier = fileModelIdentifier + } + + enum CodingKeys: String, CodingKey { + case _id = "id" + case _monthKey = "monthKey" + case _creationDate = "creationDate" + case _maleUnrankedValue = "maleUnrankedValue" + case _femaleUnrankedValue = "femaleUnrankedValue" + case _maleCount = "maleCount" + case _femaleCount = "femaleCount" + case _anonymousCount = "anonymousCount" + case _incompleteMode = "incompleteMode" + case _dataModelIdentifier = "dataModelIdentifier" + case _fileModelIdentifier = "fileModelIdentifier" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() + self.monthKey = try container.decodeIfPresent(String.self, forKey: ._monthKey) ?? "" + self.creationDate = try container.decodeIfPresent(Date.self, forKey: ._creationDate) ?? Date() + self.maleUnrankedValue = try container.decodeIfPresent(Int.self, forKey: ._maleUnrankedValue) ?? nil + self.femaleUnrankedValue = try container.decodeIfPresent(Int.self, forKey: ._femaleUnrankedValue) ?? nil + self.maleCount = try container.decodeIfPresent(Int.self, forKey: ._maleCount) ?? nil + self.femaleCount = try container.decodeIfPresent(Int.self, forKey: ._femaleCount) ?? nil + self.anonymousCount = try container.decodeIfPresent(Int.self, forKey: ._anonymousCount) ?? nil + self.incompleteMode = try container.decodeIfPresent(Bool.self, forKey: ._incompleteMode) ?? false + self.dataModelIdentifier = try container.decodeIfPresent(String.self, forKey: ._dataModelIdentifier) ?? nil + self.fileModelIdentifier = try container.decodeIfPresent(String.self, forKey: ._fileModelIdentifier) ?? nil + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.id, forKey: ._id) + try container.encode(self.monthKey, forKey: ._monthKey) + try container.encode(self.creationDate, forKey: ._creationDate) + try container.encode(self.maleUnrankedValue, forKey: ._maleUnrankedValue) + try container.encode(self.femaleUnrankedValue, forKey: ._femaleUnrankedValue) + try container.encode(self.maleCount, forKey: ._maleCount) + try container.encode(self.femaleCount, forKey: ._femaleCount) + try container.encode(self.anonymousCount, forKey: ._anonymousCount) + try container.encode(self.incompleteMode, forKey: ._incompleteMode) + try container.encode(self.dataModelIdentifier, forKey: ._dataModelIdentifier) + try container.encode(self.fileModelIdentifier, forKey: ._fileModelIdentifier) + try super.encode(to: encoder) + } + + func copy(from other: any Storable) { + guard let monthdata = other as? BaseMonthData else { return } + self.id = monthdata.id + self.monthKey = monthdata.monthKey + self.creationDate = monthdata.creationDate + self.maleUnrankedValue = monthdata.maleUnrankedValue + self.femaleUnrankedValue = monthdata.femaleUnrankedValue + self.maleCount = monthdata.maleCount + self.femaleCount = monthdata.femaleCount + self.anonymousCount = monthdata.anonymousCount + self.incompleteMode = monthdata.incompleteMode + self.dataModelIdentifier = monthdata.dataModelIdentifier + self.fileModelIdentifier = monthdata.fileModelIdentifier + } + + static func relationships() -> [Relationship] { + return [] + } + +} \ No newline at end of file diff --git a/PadelClub/Data/Gen/BasePlayerRegistration.swift b/PadelClub/Data/Gen/BasePlayerRegistration.swift new file mode 100644 index 0000000..6775fa1 --- /dev/null +++ b/PadelClub/Data/Gen/BasePlayerRegistration.swift @@ -0,0 +1,202 @@ +// Generated by SwiftModelGenerator +// Do not modify this file manually + +import Foundation +import LeStorage +import SwiftUI + +@Observable +class BasePlayerRegistration: SyncedModelObject, SyncedStorable { + + static func resourceName() -> String { return "player-registrations" } + static func tokenExemptedMethods() -> [HTTPMethod] { return [] } + + var id: String = Store.randomId() + var teamRegistration: String? = nil + var firstName: String = "" + var lastName: String = "" + var licenceId: String? = nil + var rank: Int? = nil + var paymentType: PlayerPaymentType? = nil + var sex: PlayerSexType? = nil + var tournamentPlayed: Int? = nil + var points: Double? = nil + var clubName: String? = nil + var ligueName: String? = nil + var assimilation: String? = nil + var phoneNumber: String? = nil + var email: String? = nil + var birthdate: String? = nil + var computedRank: Int = 0 + var source: PlayerRegistration.PlayerDataSource? = nil + var hasArrived: Bool = false + var coach: Bool = false + var captain: Bool = false + var registeredOnline: Bool = false + + init( + id: String = Store.randomId(), + teamRegistration: String? = nil, + firstName: String = "", + lastName: String = "", + licenceId: String? = nil, + rank: Int? = nil, + paymentType: PlayerPaymentType? = nil, + sex: PlayerSexType? = nil, + tournamentPlayed: Int? = nil, + points: Double? = nil, + clubName: String? = nil, + ligueName: String? = nil, + assimilation: String? = nil, + phoneNumber: String? = nil, + email: String? = nil, + birthdate: String? = nil, + computedRank: Int = 0, + source: PlayerRegistration.PlayerDataSource? = nil, + hasArrived: Bool = false, + coach: Bool = false, + captain: Bool = false, + registeredOnline: Bool = false + ) { + super.init() + self.id = id + self.teamRegistration = teamRegistration + self.firstName = firstName + self.lastName = lastName + self.licenceId = licenceId + self.rank = rank + self.paymentType = paymentType + self.sex = sex + self.tournamentPlayed = tournamentPlayed + self.points = points + self.clubName = clubName + self.ligueName = ligueName + self.assimilation = assimilation + self.phoneNumber = phoneNumber + self.email = email + self.birthdate = birthdate + self.computedRank = computedRank + self.source = source + self.hasArrived = hasArrived + self.coach = coach + self.captain = captain + self.registeredOnline = registeredOnline + } + + enum CodingKeys: String, CodingKey { + case _id = "id" + case _teamRegistration = "teamRegistration" + case _firstName = "firstName" + case _lastName = "lastName" + case _licenceId = "licenceId" + case _rank = "rank" + case _paymentType = "paymentType" + case _sex = "sex" + case _tournamentPlayed = "tournamentPlayed" + case _points = "points" + case _clubName = "clubName" + case _ligueName = "ligueName" + case _assimilation = "assimilation" + case _phoneNumber = "phoneNumber" + case _email = "email" + case _birthdate = "birthdate" + case _computedRank = "computedRank" + case _source = "source" + case _hasArrived = "hasArrived" + case _coach = "coach" + case _captain = "captain" + case _registeredOnline = "registeredOnline" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() + self.teamRegistration = try container.decodeIfPresent(String.self, forKey: ._teamRegistration) ?? nil + self.firstName = try container.decodeIfPresent(String.self, forKey: ._firstName) ?? "" + self.lastName = try container.decodeIfPresent(String.self, forKey: ._lastName) ?? "" + self.licenceId = try container.decodeIfPresent(String.self, forKey: ._licenceId) ?? nil + self.rank = try container.decodeIfPresent(Int.self, forKey: ._rank) ?? nil + self.paymentType = try container.decodeIfPresent(PlayerPaymentType.self, forKey: ._paymentType) ?? nil + self.sex = try container.decodeIfPresent(PlayerSexType.self, forKey: ._sex) ?? nil + self.tournamentPlayed = try container.decodeIfPresent(Int.self, forKey: ._tournamentPlayed) ?? nil + self.points = try container.decodeIfPresent(Double.self, forKey: ._points) ?? nil + self.clubName = try container.decodeIfPresent(String.self, forKey: ._clubName) ?? nil + self.ligueName = try container.decodeIfPresent(String.self, forKey: ._ligueName) ?? nil + self.assimilation = try container.decodeIfPresent(String.self, forKey: ._assimilation) ?? nil + self.phoneNumber = try container.decodeIfPresent(String.self, forKey: ._phoneNumber) ?? nil + self.email = try container.decodeIfPresent(String.self, forKey: ._email) ?? nil + self.birthdate = try container.decodeIfPresent(String.self, forKey: ._birthdate) ?? nil + self.computedRank = try container.decodeIfPresent(Int.self, forKey: ._computedRank) ?? 0 + self.source = try container.decodeIfPresent(PlayerRegistration.PlayerDataSource.self, forKey: ._source) ?? nil + self.hasArrived = try container.decodeIfPresent(Bool.self, forKey: ._hasArrived) ?? false + self.coach = try container.decodeIfPresent(Bool.self, forKey: ._coach) ?? false + self.captain = try container.decodeIfPresent(Bool.self, forKey: ._captain) ?? false + self.registeredOnline = try container.decodeIfPresent(Bool.self, forKey: ._registeredOnline) ?? false + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.id, forKey: ._id) + try container.encode(self.teamRegistration, forKey: ._teamRegistration) + try container.encode(self.firstName, forKey: ._firstName) + try container.encode(self.lastName, forKey: ._lastName) + try container.encode(self.licenceId, forKey: ._licenceId) + try container.encode(self.rank, forKey: ._rank) + try container.encode(self.paymentType, forKey: ._paymentType) + try container.encode(self.sex, forKey: ._sex) + try container.encode(self.tournamentPlayed, forKey: ._tournamentPlayed) + try container.encode(self.points, forKey: ._points) + try container.encode(self.clubName, forKey: ._clubName) + try container.encode(self.ligueName, forKey: ._ligueName) + try container.encode(self.assimilation, forKey: ._assimilation) + try container.encode(self.phoneNumber, forKey: ._phoneNumber) + try container.encode(self.email, forKey: ._email) + try container.encode(self.birthdate, forKey: ._birthdate) + try container.encode(self.computedRank, forKey: ._computedRank) + try container.encode(self.source, forKey: ._source) + try container.encode(self.hasArrived, forKey: ._hasArrived) + try container.encode(self.coach, forKey: ._coach) + try container.encode(self.captain, forKey: ._captain) + try container.encode(self.registeredOnline, forKey: ._registeredOnline) + try super.encode(to: encoder) + } + + func teamRegistrationValue() -> TeamRegistration? { + guard let teamRegistration = self.teamRegistration else { return nil } + return Store.main.findById(teamRegistration) + } + + func copy(from other: any Storable) { + guard let playerregistration = other as? BasePlayerRegistration else { return } + self.id = playerregistration.id + self.teamRegistration = playerregistration.teamRegistration + self.firstName = playerregistration.firstName + self.lastName = playerregistration.lastName + self.licenceId = playerregistration.licenceId + self.rank = playerregistration.rank + self.paymentType = playerregistration.paymentType + self.sex = playerregistration.sex + self.tournamentPlayed = playerregistration.tournamentPlayed + self.points = playerregistration.points + self.clubName = playerregistration.clubName + self.ligueName = playerregistration.ligueName + self.assimilation = playerregistration.assimilation + self.phoneNumber = playerregistration.phoneNumber + self.email = playerregistration.email + self.birthdate = playerregistration.birthdate + self.computedRank = playerregistration.computedRank + self.source = playerregistration.source + self.hasArrived = playerregistration.hasArrived + self.coach = playerregistration.coach + self.captain = playerregistration.captain + self.registeredOnline = playerregistration.registeredOnline + } + + static func relationships() -> [Relationship] { + return [ + Relationship(type: TeamRegistration.self, keyPath: \BasePlayerRegistration.teamRegistration), + ] + } + +} \ No newline at end of file diff --git a/PadelClub/Data/Gen/BasePurchase.swift b/PadelClub/Data/Gen/BasePurchase.swift new file mode 100644 index 0000000..e10f087 --- /dev/null +++ b/PadelClub/Data/Gen/BasePurchase.swift @@ -0,0 +1,95 @@ +// Generated by SwiftModelGenerator +// Do not modify this file manually + +import Foundation +import LeStorage + +class BasePurchase: SyncedModelObject, SyncedStorable { + + static func resourceName() -> String { return "purchases" } + static func tokenExemptedMethods() -> [HTTPMethod] { return [] } + + var id: UInt64 = 0 + var user: String = "" + var purchaseDate: Date = Date() + var productId: String = "" + var quantity: Int? = nil + var revocationDate: Date? = nil + var expirationDate: Date? = nil + + init( + id: UInt64 = 0, + user: String = "", + purchaseDate: Date = Date(), + productId: String = "", + quantity: Int? = nil, + revocationDate: Date? = nil, + expirationDate: Date? = nil + ) { + super.init() + self.id = id + self.user = user + self.purchaseDate = purchaseDate + self.productId = productId + self.quantity = quantity + self.revocationDate = revocationDate + self.expirationDate = expirationDate + } + + enum CodingKeys: String, CodingKey { + case id = "id" + case user = "user" + case purchaseDate = "purchaseDate" + case productId = "productId" + case quantity = "quantity" + case revocationDate = "revocationDate" + case expirationDate = "expirationDate" + } + + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decodeIfPresent(UInt64.self, forKey: .id) ?? 0 + self.user = try container.decodeEncrypted(key: .user) + self.purchaseDate = try container.decodeIfPresent(Date.self, forKey: .purchaseDate) ?? Date() + self.productId = try container.decodeIfPresent(String.self, forKey: .productId) ?? "" + self.quantity = try container.decodeIfPresent(Int.self, forKey: .quantity) ?? nil + self.revocationDate = try container.decodeIfPresent(Date.self, forKey: .revocationDate) ?? nil + self.expirationDate = try container.decodeIfPresent(Date.self, forKey: .expirationDate) ?? nil + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.id, forKey: .id) + try container.encodeAndEncryptIfPresent(self.user.data(using: .utf8), forKey: .user) + try container.encode(self.purchaseDate, forKey: .purchaseDate) + try container.encode(self.productId, forKey: .productId) + try container.encode(self.quantity, forKey: .quantity) + try container.encode(self.revocationDate, forKey: .revocationDate) + try container.encode(self.expirationDate, forKey: .expirationDate) + try super.encode(to: encoder) + } + + func userValue() -> CustomUser? { + return Store.main.findById(user) + } + + func copy(from other: any Storable) { + guard let purchase = other as? BasePurchase else { return } + self.id = purchase.id + self.user = purchase.user + self.purchaseDate = purchase.purchaseDate + self.productId = purchase.productId + self.quantity = purchase.quantity + self.revocationDate = purchase.revocationDate + self.expirationDate = purchase.expirationDate + } + + static func relationships() -> [Relationship] { + return [ + Relationship(type: CustomUser.self, keyPath: \BasePurchase.user), + ] + } + +} \ No newline at end of file diff --git a/PadelClub/Data/Gen/BaseRound.swift b/PadelClub/Data/Gen/BaseRound.swift new file mode 100644 index 0000000..7ea4249 --- /dev/null +++ b/PadelClub/Data/Gen/BaseRound.swift @@ -0,0 +1,103 @@ +// Generated by SwiftModelGenerator +// Do not modify this file manually + +import Foundation +import LeStorage +import SwiftUI + +@Observable +class BaseRound: SyncedModelObject, SyncedStorable { + + static func resourceName() -> String { return "rounds" } + static func tokenExemptedMethods() -> [HTTPMethod] { return [] } + + var id: String = Store.randomId() + var tournament: String = "" + var index: Int = 0 + var parent: String? = nil + var format: MatchFormat? = nil + var startDate: Date? = nil + var groupStageLoserBracket: Bool = false + var loserBracketMode: LoserBracketMode = .automatic + + init( + id: String = Store.randomId(), + tournament: String = "", + index: Int = 0, + parent: String? = nil, + format: MatchFormat? = nil, + startDate: Date? = nil, + groupStageLoserBracket: Bool = false, + loserBracketMode: LoserBracketMode = .automatic + ) { + super.init() + self.id = id + self.tournament = tournament + self.index = index + self.parent = parent + self.format = format + self.startDate = startDate + self.groupStageLoserBracket = groupStageLoserBracket + self.loserBracketMode = loserBracketMode + } + + enum CodingKeys: String, CodingKey { + case _id = "id" + case _tournament = "tournament" + case _index = "index" + case _parent = "parent" + case _format = "format" + case _startDate = "startDate" + case _groupStageLoserBracket = "groupStageLoserBracket" + case _loserBracketMode = "loserBracketMode" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() + self.tournament = try container.decodeIfPresent(String.self, forKey: ._tournament) ?? "" + self.index = try container.decodeIfPresent(Int.self, forKey: ._index) ?? 0 + self.parent = try container.decodeIfPresent(String.self, forKey: ._parent) ?? nil + self.format = try container.decodeIfPresent(MatchFormat.self, forKey: ._format) ?? nil + self.startDate = try container.decodeIfPresent(Date.self, forKey: ._startDate) ?? nil + self.groupStageLoserBracket = try container.decodeIfPresent(Bool.self, forKey: ._groupStageLoserBracket) ?? false + self.loserBracketMode = try container.decodeIfPresent(LoserBracketMode.self, forKey: ._loserBracketMode) ?? .automatic + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.id, forKey: ._id) + try container.encode(self.tournament, forKey: ._tournament) + try container.encode(self.index, forKey: ._index) + try container.encode(self.parent, forKey: ._parent) + try container.encode(self.format, forKey: ._format) + try container.encode(self.startDate, forKey: ._startDate) + try container.encode(self.groupStageLoserBracket, forKey: ._groupStageLoserBracket) + try container.encode(self.loserBracketMode, forKey: ._loserBracketMode) + try super.encode(to: encoder) + } + + func tournamentValue() -> Tournament? { + return Store.main.findById(tournament) + } + + func copy(from other: any Storable) { + guard let round = other as? BaseRound else { return } + self.id = round.id + self.tournament = round.tournament + self.index = round.index + self.parent = round.parent + self.format = round.format + self.startDate = round.startDate + self.groupStageLoserBracket = round.groupStageLoserBracket + self.loserBracketMode = round.loserBracketMode + } + + static func relationships() -> [Relationship] { + return [ + Relationship(type: Tournament.self, keyPath: \BaseRound.tournament), + ] + } + +} \ No newline at end of file diff --git a/PadelClub/Data/Gen/BaseTeamRegistration.swift b/PadelClub/Data/Gen/BaseTeamRegistration.swift new file mode 100644 index 0000000..237d84f --- /dev/null +++ b/PadelClub/Data/Gen/BaseTeamRegistration.swift @@ -0,0 +1,195 @@ +// Generated by SwiftModelGenerator +// Do not modify this file manually + +import Foundation +import LeStorage +import SwiftUI + +@Observable +class BaseTeamRegistration: SyncedModelObject, SyncedStorable { + + static func resourceName() -> String { return "team-registrations" } + static func tokenExemptedMethods() -> [HTTPMethod] { return [] } + + var id: String = Store.randomId() + var tournament: String = "" + var groupStage: String? = nil + var registrationDate: Date? = nil + var callDate: Date? = nil + var bracketPosition: Int? = nil + var groupStagePosition: Int? = nil + var comment: String? = nil + var source: String? = nil + var sourceValue: String? = nil + var logo: String? = nil + var name: String? = nil + var walkOut: Bool = false + var wildCardBracket: Bool = false + var wildCardGroupStage: Bool = false + var weight: Int = 0 + var lockedWeight: Int? = nil + var confirmationDate: Date? = nil + var qualified: Bool = false + var finalRanking: Int? = nil + var pointsEarned: Int? = nil + + init( + id: String = Store.randomId(), + tournament: String = "", + groupStage: String? = nil, + registrationDate: Date? = nil, + callDate: Date? = nil, + bracketPosition: Int? = nil, + groupStagePosition: Int? = nil, + comment: String? = nil, + source: String? = nil, + sourceValue: String? = nil, + logo: String? = nil, + name: String? = nil, + walkOut: Bool = false, + wildCardBracket: Bool = false, + wildCardGroupStage: Bool = false, + weight: Int = 0, + lockedWeight: Int? = nil, + confirmationDate: Date? = nil, + qualified: Bool = false, + finalRanking: Int? = nil, + pointsEarned: Int? = nil + ) { + super.init() + self.id = id + self.tournament = tournament + self.groupStage = groupStage + self.registrationDate = registrationDate + self.callDate = callDate + self.bracketPosition = bracketPosition + self.groupStagePosition = groupStagePosition + self.comment = comment + self.source = source + self.sourceValue = sourceValue + self.logo = logo + self.name = name + self.walkOut = walkOut + self.wildCardBracket = wildCardBracket + self.wildCardGroupStage = wildCardGroupStage + self.weight = weight + self.lockedWeight = lockedWeight + self.confirmationDate = confirmationDate + self.qualified = qualified + self.finalRanking = finalRanking + self.pointsEarned = pointsEarned + } + + enum CodingKeys: String, CodingKey { + case _id = "id" + case _tournament = "tournament" + case _groupStage = "groupStage" + case _registrationDate = "registrationDate" + case _callDate = "callDate" + case _bracketPosition = "bracketPosition" + case _groupStagePosition = "groupStagePosition" + case _comment = "comment" + case _source = "source" + case _sourceValue = "sourceValue" + case _logo = "logo" + case _name = "name" + case _walkOut = "walkOut" + case _wildCardBracket = "wildCardBracket" + case _wildCardGroupStage = "wildCardGroupStage" + case _weight = "weight" + case _lockedWeight = "lockedWeight" + case _confirmationDate = "confirmationDate" + case _qualified = "qualified" + case _finalRanking = "finalRanking" + case _pointsEarned = "pointsEarned" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() + self.tournament = try container.decodeIfPresent(String.self, forKey: ._tournament) ?? "" + self.groupStage = try container.decodeIfPresent(String.self, forKey: ._groupStage) ?? nil + self.registrationDate = try container.decodeIfPresent(Date.self, forKey: ._registrationDate) ?? nil + self.callDate = try container.decodeIfPresent(Date.self, forKey: ._callDate) ?? nil + self.bracketPosition = try container.decodeIfPresent(Int.self, forKey: ._bracketPosition) ?? nil + self.groupStagePosition = try container.decodeIfPresent(Int.self, forKey: ._groupStagePosition) ?? nil + self.comment = try container.decodeIfPresent(String.self, forKey: ._comment) ?? nil + self.source = try container.decodeIfPresent(String.self, forKey: ._source) ?? nil + self.sourceValue = try container.decodeIfPresent(String.self, forKey: ._sourceValue) ?? nil + self.logo = try container.decodeIfPresent(String.self, forKey: ._logo) ?? nil + self.name = try container.decodeIfPresent(String.self, forKey: ._name) ?? nil + self.walkOut = try container.decodeIfPresent(Bool.self, forKey: ._walkOut) ?? false + self.wildCardBracket = try container.decodeIfPresent(Bool.self, forKey: ._wildCardBracket) ?? false + self.wildCardGroupStage = try container.decodeIfPresent(Bool.self, forKey: ._wildCardGroupStage) ?? false + self.weight = try container.decodeIfPresent(Int.self, forKey: ._weight) ?? 0 + self.lockedWeight = try container.decodeIfPresent(Int.self, forKey: ._lockedWeight) ?? nil + self.confirmationDate = try container.decodeIfPresent(Date.self, forKey: ._confirmationDate) ?? nil + self.qualified = try container.decodeIfPresent(Bool.self, forKey: ._qualified) ?? false + self.finalRanking = try container.decodeIfPresent(Int.self, forKey: ._finalRanking) ?? nil + self.pointsEarned = try container.decodeIfPresent(Int.self, forKey: ._pointsEarned) ?? nil + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.id, forKey: ._id) + try container.encode(self.tournament, forKey: ._tournament) + try container.encode(self.groupStage, forKey: ._groupStage) + try container.encode(self.registrationDate, forKey: ._registrationDate) + try container.encode(self.callDate, forKey: ._callDate) + try container.encode(self.bracketPosition, forKey: ._bracketPosition) + try container.encode(self.groupStagePosition, forKey: ._groupStagePosition) + try container.encode(self.comment, forKey: ._comment) + try container.encode(self.source, forKey: ._source) + try container.encode(self.sourceValue, forKey: ._sourceValue) + try container.encode(self.logo, forKey: ._logo) + try container.encode(self.name, forKey: ._name) + try container.encode(self.walkOut, forKey: ._walkOut) + try container.encode(self.wildCardBracket, forKey: ._wildCardBracket) + try container.encode(self.wildCardGroupStage, forKey: ._wildCardGroupStage) + try container.encode(self.weight, forKey: ._weight) + try container.encode(self.lockedWeight, forKey: ._lockedWeight) + try container.encode(self.confirmationDate, forKey: ._confirmationDate) + try container.encode(self.qualified, forKey: ._qualified) + try container.encode(self.finalRanking, forKey: ._finalRanking) + try container.encode(self.pointsEarned, forKey: ._pointsEarned) + try super.encode(to: encoder) + } + + func groupStageValue() -> GroupStage? { + guard let groupStage = self.groupStage else { return nil } + return self.store?.findById(groupStage) + } + + func copy(from other: any Storable) { + guard let teamregistration = other as? BaseTeamRegistration else { return } + self.id = teamregistration.id + self.tournament = teamregistration.tournament + self.groupStage = teamregistration.groupStage + self.registrationDate = teamregistration.registrationDate + self.callDate = teamregistration.callDate + self.bracketPosition = teamregistration.bracketPosition + self.groupStagePosition = teamregistration.groupStagePosition + self.comment = teamregistration.comment + self.source = teamregistration.source + self.sourceValue = teamregistration.sourceValue + self.logo = teamregistration.logo + self.name = teamregistration.name + self.walkOut = teamregistration.walkOut + self.wildCardBracket = teamregistration.wildCardBracket + self.wildCardGroupStage = teamregistration.wildCardGroupStage + self.weight = teamregistration.weight + self.lockedWeight = teamregistration.lockedWeight + self.confirmationDate = teamregistration.confirmationDate + self.qualified = teamregistration.qualified + self.finalRanking = teamregistration.finalRanking + self.pointsEarned = teamregistration.pointsEarned + } + + static func relationships() -> [Relationship] { + return [ + Relationship(type: GroupStage.self, keyPath: \BaseTeamRegistration.groupStage), + ] + } + +} \ No newline at end of file diff --git a/PadelClub/Data/Gen/BaseTeamScore.swift b/PadelClub/Data/Gen/BaseTeamScore.swift new file mode 100644 index 0000000..2407f39 --- /dev/null +++ b/PadelClub/Data/Gen/BaseTeamScore.swift @@ -0,0 +1,95 @@ +// Generated by SwiftModelGenerator +// Do not modify this file manually + +import Foundation +import LeStorage +import SwiftUI + +@Observable +class BaseTeamScore: SyncedModelObject, SyncedStorable { + + static func resourceName() -> String { return "team-scores" } + static func tokenExemptedMethods() -> [HTTPMethod] { return [] } + + var id: String = Store.randomId() + var match: String = "" + var teamRegistration: String? = nil + var score: String? = nil + var walkOut: Int? = nil + var luckyLoser: Int? = nil + + init( + id: String = Store.randomId(), + match: String = "", + teamRegistration: String? = nil, + score: String? = nil, + walkOut: Int? = nil, + luckyLoser: Int? = nil + ) { + super.init() + self.id = id + self.match = match + self.teamRegistration = teamRegistration + self.score = score + self.walkOut = walkOut + self.luckyLoser = luckyLoser + } + + enum CodingKeys: String, CodingKey { + case _id = "id" + case _match = "match" + case _teamRegistration = "teamRegistration" + case _score = "score" + case _walkOut = "walkOut" + case _luckyLoser = "luckyLoser" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() + self.match = try container.decodeIfPresent(String.self, forKey: ._match) ?? "" + self.teamRegistration = try container.decodeIfPresent(String.self, forKey: ._teamRegistration) ?? nil + self.score = try container.decodeIfPresent(String.self, forKey: ._score) ?? nil + self.walkOut = try container.decodeIfPresent(Int.self, forKey: ._walkOut) ?? nil + self.luckyLoser = try container.decodeIfPresent(Int.self, forKey: ._luckyLoser) ?? nil + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.id, forKey: ._id) + try container.encode(self.match, forKey: ._match) + try container.encode(self.teamRegistration, forKey: ._teamRegistration) + try container.encode(self.score, forKey: ._score) + try container.encode(self.walkOut, forKey: ._walkOut) + try container.encode(self.luckyLoser, forKey: ._luckyLoser) + try super.encode(to: encoder) + } + + func matchValue() -> Match? { + return self.store?.findById(match) + } + + func teamRegistrationValue() -> TeamRegistration? { + guard let teamRegistration = self.teamRegistration else { return nil } + return self.store?.findById(teamRegistration) + } + + func copy(from other: any Storable) { + guard let teamscore = other as? BaseTeamScore else { return } + self.id = teamscore.id + self.match = teamscore.match + self.teamRegistration = teamscore.teamRegistration + self.score = teamscore.score + self.walkOut = teamscore.walkOut + self.luckyLoser = teamscore.luckyLoser + } + + static func relationships() -> [Relationship] { + return [ + Relationship(type: Match.self, keyPath: \BaseTeamScore.match), + Relationship(type: TeamRegistration.self, keyPath: \BaseTeamScore.teamRegistration), + ] + } + +} \ No newline at end of file diff --git a/PadelClub/Data/Gen/BaseTournament.swift b/PadelClub/Data/Gen/BaseTournament.swift new file mode 100644 index 0000000..c397195 --- /dev/null +++ b/PadelClub/Data/Gen/BaseTournament.swift @@ -0,0 +1,473 @@ +// Generated by SwiftModelGenerator +// Do not modify this file manually + +import Foundation +import LeStorage +import SwiftUI + +@Observable +class BaseTournament: SyncedModelObject, SyncedStorable { + + static func resourceName() -> String { return "tournaments" } + static func tokenExemptedMethods() -> [HTTPMethod] { return [] } + + var id: String = Store.randomId() + var event: String? = nil + var name: String? = nil + var startDate: Date = Date() + var endDate: Date? = nil + var creationDate: Date = Date() + var isPrivate: Bool = false + var groupStageFormat: MatchFormat? = nil + var roundFormat: MatchFormat? = nil + var loserRoundFormat: MatchFormat? = nil + var groupStageSortMode: GroupStageOrderingMode = GroupStageOrderingMode.snake + var groupStageCount: Int = 0 + var rankSourceDate: Date? = nil + var dayDuration: Int = 0 + var teamCount: Int = 0 + var teamSorting: TeamSortingType = TeamSortingType.inscriptionDate + var federalCategory: TournamentCategory = TournamentCategory.men + var federalLevelCategory: TournamentLevel = TournamentLevel.unlisted + var federalAgeCategory: FederalTournamentAge = FederalTournamentAge.unlisted + var closedRegistrationDate: Date? = nil + var groupStageAdditionalQualified: Int = 0 + var courtCount: Int = 2 + var prioritizeClubMembers: Bool = false + var qualifiedPerGroupStage: Int = 0 + var teamsPerGroupStage: Int = 0 + var entryFee: Double? = nil + var payment: TournamentPayment? = nil + var additionalEstimationDuration: Int = 0 + var isDeleted: Bool = false + var isCanceled: Bool = false + var publishTeams: Bool = false + var publishSummons: Bool = false + var publishGroupStages: Bool = false + var publishBrackets: Bool = false + var shouldVerifyGroupStage: Bool = false + var shouldVerifyBracket: Bool = false + var hideTeamsWeight: Bool = false + var publishTournament: Bool = false + var hidePointsEarned: Bool = false + var publishRankings: Bool = false + var loserBracketMode: LoserBracketMode = .automatic + var initialSeedRound: Int = 0 + var initialSeedCount: Int = 0 + var enableOnlineRegistration: Bool = false + var registrationDateLimit: Date? = nil + var openingRegistrationDate: Date? = nil + var waitingListLimit: Int? = nil + var accountIsRequired: Bool = true + var licenseIsRequired: Bool = true + var minimumPlayerPerTeam: Int = 2 + var maximumPlayerPerTeam: Int = 2 + var information: String? = nil + + init( + id: String = Store.randomId(), + event: String? = nil, + name: String? = nil, + startDate: Date = Date(), + endDate: Date? = nil, + creationDate: Date = Date(), + isPrivate: Bool = false, + groupStageFormat: MatchFormat? = nil, + roundFormat: MatchFormat? = nil, + loserRoundFormat: MatchFormat? = nil, + groupStageSortMode: GroupStageOrderingMode = GroupStageOrderingMode.snake, + groupStageCount: Int = 0, + rankSourceDate: Date? = nil, + dayDuration: Int = 0, + teamCount: Int = 0, + teamSorting: TeamSortingType = TeamSortingType.inscriptionDate, + federalCategory: TournamentCategory = TournamentCategory.men, + federalLevelCategory: TournamentLevel = TournamentLevel.unlisted, + federalAgeCategory: FederalTournamentAge = FederalTournamentAge.unlisted, + closedRegistrationDate: Date? = nil, + groupStageAdditionalQualified: Int = 0, + courtCount: Int = 2, + prioritizeClubMembers: Bool = false, + qualifiedPerGroupStage: Int = 0, + teamsPerGroupStage: Int = 0, + entryFee: Double? = nil, + payment: TournamentPayment? = nil, + additionalEstimationDuration: Int = 0, + isDeleted: Bool = false, + isCanceled: Bool = false, + publishTeams: Bool = false, + publishSummons: Bool = false, + publishGroupStages: Bool = false, + publishBrackets: Bool = false, + shouldVerifyGroupStage: Bool = false, + shouldVerifyBracket: Bool = false, + hideTeamsWeight: Bool = false, + publishTournament: Bool = false, + hidePointsEarned: Bool = false, + publishRankings: Bool = false, + loserBracketMode: LoserBracketMode = .automatic, + initialSeedRound: Int = 0, + initialSeedCount: Int = 0, + enableOnlineRegistration: Bool = false, + registrationDateLimit: Date? = nil, + openingRegistrationDate: Date? = nil, + waitingListLimit: Int? = nil, + accountIsRequired: Bool = true, + licenseIsRequired: Bool = true, + minimumPlayerPerTeam: Int = 2, + maximumPlayerPerTeam: Int = 2, + information: String? = nil + ) { + super.init() + self.id = id + self.event = event + self.name = name + self.startDate = startDate + self.endDate = endDate + self.creationDate = creationDate + self.isPrivate = isPrivate + self.groupStageFormat = groupStageFormat + self.roundFormat = roundFormat + self.loserRoundFormat = loserRoundFormat + self.groupStageSortMode = groupStageSortMode + self.groupStageCount = groupStageCount + self.rankSourceDate = rankSourceDate + self.dayDuration = dayDuration + self.teamCount = teamCount + self.teamSorting = teamSorting + self.federalCategory = federalCategory + self.federalLevelCategory = federalLevelCategory + self.federalAgeCategory = federalAgeCategory + self.closedRegistrationDate = closedRegistrationDate + self.groupStageAdditionalQualified = groupStageAdditionalQualified + self.courtCount = courtCount + self.prioritizeClubMembers = prioritizeClubMembers + self.qualifiedPerGroupStage = qualifiedPerGroupStage + self.teamsPerGroupStage = teamsPerGroupStage + self.entryFee = entryFee + self.payment = payment + self.additionalEstimationDuration = additionalEstimationDuration + self.isDeleted = isDeleted + self.isCanceled = isCanceled + self.publishTeams = publishTeams + self.publishSummons = publishSummons + self.publishGroupStages = publishGroupStages + self.publishBrackets = publishBrackets + self.shouldVerifyGroupStage = shouldVerifyGroupStage + self.shouldVerifyBracket = shouldVerifyBracket + self.hideTeamsWeight = hideTeamsWeight + self.publishTournament = publishTournament + self.hidePointsEarned = hidePointsEarned + self.publishRankings = publishRankings + self.loserBracketMode = loserBracketMode + self.initialSeedRound = initialSeedRound + self.initialSeedCount = initialSeedCount + self.enableOnlineRegistration = enableOnlineRegistration + self.registrationDateLimit = registrationDateLimit + self.openingRegistrationDate = openingRegistrationDate + self.waitingListLimit = waitingListLimit + self.accountIsRequired = accountIsRequired + self.licenseIsRequired = licenseIsRequired + self.minimumPlayerPerTeam = minimumPlayerPerTeam + self.maximumPlayerPerTeam = maximumPlayerPerTeam + self.information = information + } + + enum CodingKeys: String, CodingKey { + case _id = "id" + case _event = "event" + case _name = "name" + case _startDate = "startDate" + case _endDate = "endDate" + case _creationDate = "creationDate" + case _isPrivate = "isPrivate" + case _groupStageFormat = "groupStageFormat" + case _roundFormat = "roundFormat" + case _loserRoundFormat = "loserRoundFormat" + case _groupStageSortMode = "groupStageSortMode" + case _groupStageCount = "groupStageCount" + case _rankSourceDate = "rankSourceDate" + case _dayDuration = "dayDuration" + case _teamCount = "teamCount" + case _teamSorting = "teamSorting" + case _federalCategory = "federalCategory" + case _federalLevelCategory = "federalLevelCategory" + case _federalAgeCategory = "federalAgeCategory" + case _closedRegistrationDate = "closedRegistrationDate" + case _groupStageAdditionalQualified = "groupStageAdditionalQualified" + case _courtCount = "courtCount" + case _prioritizeClubMembers = "prioritizeClubMembers" + case _qualifiedPerGroupStage = "qualifiedPerGroupStage" + case _teamsPerGroupStage = "teamsPerGroupStage" + case _entryFee = "entryFee" + case _payment = "payment" + case _additionalEstimationDuration = "additionalEstimationDuration" + case _isDeleted = "isDeleted" + case _isCanceled = "isCanceled" + case _publishTeams = "publishTeams" + case _publishSummons = "publishSummons" + case _publishGroupStages = "publishGroupStages" + case _publishBrackets = "publishBrackets" + case _shouldVerifyGroupStage = "shouldVerifyGroupStage" + case _shouldVerifyBracket = "shouldVerifyBracket" + case _hideTeamsWeight = "hideTeamsWeight" + case _publishTournament = "publishTournament" + case _hidePointsEarned = "hidePointsEarned" + case _publishRankings = "publishRankings" + case _loserBracketMode = "loserBracketMode" + case _initialSeedRound = "initialSeedRound" + case _initialSeedCount = "initialSeedCount" + case _enableOnlineRegistration = "enableOnlineRegistration" + case _registrationDateLimit = "registrationDateLimit" + case _openingRegistrationDate = "openingRegistrationDate" + case _waitingListLimit = "waitingListLimit" + case _accountIsRequired = "accountIsRequired" + case _licenseIsRequired = "licenseIsRequired" + case _minimumPlayerPerTeam = "minimumPlayerPerTeam" + case _maximumPlayerPerTeam = "maximumPlayerPerTeam" + case _information = "information" + } + + private static func _decodePayment(container: KeyedDecodingContainer) throws -> TournamentPayment? { + let data = try container.decodeIfPresent(Data.self, forKey: ._payment) + + if let data { + do { + let decoded: String = try data.decryptData(pass: CryptoKey.pass.rawValue) + let sequence = decoded.compactMap { NumberFormatter.standard.number(from: String($0))?.intValue } + return TournamentPayment(rawValue: sequence[18]) + } catch { + Logger.error(error) + } + } + return nil + } + + private func _encodePayment(container: inout KeyedEncodingContainer) throws { + guard let payment else { + try container.encodeNil(forKey: ._payment) + return + } + + let max: Int = TournamentPayment.allCases.count + var sequence = (1...18).map { _ in Int.random(in: (0..) throws -> Bool { + let data = try container.decodeIfPresent(Data.self, forKey: ._isCanceled) + if let data { + do { + let decoded: String = try data.decryptData(pass: CryptoKey.pass.rawValue) + let sequence = decoded.compactMap { NumberFormatter.standard.number(from: String($0))?.intValue } + return Bool.decodeInt(sequence[18]) + } catch { + Logger.error(error) + } + } + return false + } + + private func _encodeIscanceled(container: inout KeyedEncodingContainer) throws { + let max: Int = 9 + var sequence = (1...18).map { _ in Int.random(in: (0...max)) } + sequence.append(self.isCanceled.encodedValue) + sequence.append(contentsOf: (1...13).map { _ in Int.random(in: (0...max ))} ) + + let stringCombo: [String] = sequence.map { $0.formatted() } + let joined: String = stringCombo.joined(separator: "") + if let data = joined.data(using: .utf8) { + let encryped: Data = try data.encrypt(pass: CryptoKey.pass.rawValue) + try container.encode(encryped, forKey: ._isCanceled) + } + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() + self.event = try container.decodeIfPresent(String.self, forKey: ._event) ?? nil + self.name = try container.decodeIfPresent(String.self, forKey: ._name) ?? nil + self.startDate = try container.decodeIfPresent(Date.self, forKey: ._startDate) ?? Date() + self.endDate = try container.decodeIfPresent(Date.self, forKey: ._endDate) ?? nil + self.creationDate = try container.decodeIfPresent(Date.self, forKey: ._creationDate) ?? Date() + self.isPrivate = try container.decodeIfPresent(Bool.self, forKey: ._isPrivate) ?? false + self.groupStageFormat = try container.decodeIfPresent(MatchFormat.self, forKey: ._groupStageFormat) ?? nil + self.roundFormat = try container.decodeIfPresent(MatchFormat.self, forKey: ._roundFormat) ?? nil + self.loserRoundFormat = try container.decodeIfPresent(MatchFormat.self, forKey: ._loserRoundFormat) ?? nil + self.groupStageSortMode = try container.decodeIfPresent(GroupStageOrderingMode.self, forKey: ._groupStageSortMode) ?? GroupStageOrderingMode.snake + self.groupStageCount = try container.decodeIfPresent(Int.self, forKey: ._groupStageCount) ?? 0 + self.rankSourceDate = try container.decodeIfPresent(Date.self, forKey: ._rankSourceDate) ?? nil + self.dayDuration = try container.decodeIfPresent(Int.self, forKey: ._dayDuration) ?? 0 + self.teamCount = try container.decodeIfPresent(Int.self, forKey: ._teamCount) ?? 0 + self.teamSorting = try container.decodeIfPresent(TeamSortingType.self, forKey: ._teamSorting) ?? TeamSortingType.inscriptionDate + self.federalCategory = try container.decodeIfPresent(TournamentCategory.self, forKey: ._federalCategory) ?? TournamentCategory.men + self.federalLevelCategory = try container.decodeIfPresent(TournamentLevel.self, forKey: ._federalLevelCategory) ?? TournamentLevel.unlisted + self.federalAgeCategory = try container.decodeIfPresent(FederalTournamentAge.self, forKey: ._federalAgeCategory) ?? FederalTournamentAge.unlisted + self.closedRegistrationDate = try container.decodeIfPresent(Date.self, forKey: ._closedRegistrationDate) ?? nil + self.groupStageAdditionalQualified = try container.decodeIfPresent(Int.self, forKey: ._groupStageAdditionalQualified) ?? 0 + self.courtCount = try container.decodeIfPresent(Int.self, forKey: ._courtCount) ?? 2 + self.prioritizeClubMembers = try container.decodeIfPresent(Bool.self, forKey: ._prioritizeClubMembers) ?? false + self.qualifiedPerGroupStage = try container.decodeIfPresent(Int.self, forKey: ._qualifiedPerGroupStage) ?? 0 + self.teamsPerGroupStage = try container.decodeIfPresent(Int.self, forKey: ._teamsPerGroupStage) ?? 0 + self.entryFee = try container.decodeIfPresent(Double.self, forKey: ._entryFee) ?? nil + self.payment = try Self._decodePayment(container: container) + self.additionalEstimationDuration = try container.decodeIfPresent(Int.self, forKey: ._additionalEstimationDuration) ?? 0 + self.isDeleted = try container.decodeIfPresent(Bool.self, forKey: ._isDeleted) ?? false + self.isCanceled = try Self._decodeIscanceled(container: container) + self.publishTeams = try container.decodeIfPresent(Bool.self, forKey: ._publishTeams) ?? false + self.publishSummons = try container.decodeIfPresent(Bool.self, forKey: ._publishSummons) ?? false + self.publishGroupStages = try container.decodeIfPresent(Bool.self, forKey: ._publishGroupStages) ?? false + self.publishBrackets = try container.decodeIfPresent(Bool.self, forKey: ._publishBrackets) ?? false + self.shouldVerifyGroupStage = try container.decodeIfPresent(Bool.self, forKey: ._shouldVerifyGroupStage) ?? false + self.shouldVerifyBracket = try container.decodeIfPresent(Bool.self, forKey: ._shouldVerifyBracket) ?? false + self.hideTeamsWeight = try container.decodeIfPresent(Bool.self, forKey: ._hideTeamsWeight) ?? false + self.publishTournament = try container.decodeIfPresent(Bool.self, forKey: ._publishTournament) ?? false + self.hidePointsEarned = try container.decodeIfPresent(Bool.self, forKey: ._hidePointsEarned) ?? false + self.publishRankings = try container.decodeIfPresent(Bool.self, forKey: ._publishRankings) ?? false + self.loserBracketMode = try container.decodeIfPresent(LoserBracketMode.self, forKey: ._loserBracketMode) ?? .automatic + self.initialSeedRound = try container.decodeIfPresent(Int.self, forKey: ._initialSeedRound) ?? 0 + self.initialSeedCount = try container.decodeIfPresent(Int.self, forKey: ._initialSeedCount) ?? 0 + self.enableOnlineRegistration = try container.decodeIfPresent(Bool.self, forKey: ._enableOnlineRegistration) ?? false + self.registrationDateLimit = try container.decodeIfPresent(Date.self, forKey: ._registrationDateLimit) ?? nil + self.openingRegistrationDate = try container.decodeIfPresent(Date.self, forKey: ._openingRegistrationDate) ?? nil + self.waitingListLimit = try container.decodeIfPresent(Int.self, forKey: ._waitingListLimit) ?? nil + self.accountIsRequired = try container.decodeIfPresent(Bool.self, forKey: ._accountIsRequired) ?? true + self.licenseIsRequired = try container.decodeIfPresent(Bool.self, forKey: ._licenseIsRequired) ?? true + self.minimumPlayerPerTeam = try container.decodeIfPresent(Int.self, forKey: ._minimumPlayerPerTeam) ?? 2 + self.maximumPlayerPerTeam = try container.decodeIfPresent(Int.self, forKey: ._maximumPlayerPerTeam) ?? 2 + self.information = try container.decodeIfPresent(String.self, forKey: ._information) ?? nil + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.id, forKey: ._id) + try container.encode(self.event, forKey: ._event) + try container.encode(self.name, forKey: ._name) + try container.encode(self.startDate, forKey: ._startDate) + try container.encode(self.endDate, forKey: ._endDate) + try container.encode(self.creationDate, forKey: ._creationDate) + try container.encode(self.isPrivate, forKey: ._isPrivate) + try container.encode(self.groupStageFormat, forKey: ._groupStageFormat) + try container.encode(self.roundFormat, forKey: ._roundFormat) + try container.encode(self.loserRoundFormat, forKey: ._loserRoundFormat) + try container.encode(self.groupStageSortMode, forKey: ._groupStageSortMode) + try container.encode(self.groupStageCount, forKey: ._groupStageCount) + try container.encode(self.rankSourceDate, forKey: ._rankSourceDate) + try container.encode(self.dayDuration, forKey: ._dayDuration) + try container.encode(self.teamCount, forKey: ._teamCount) + try container.encode(self.teamSorting, forKey: ._teamSorting) + try container.encode(self.federalCategory, forKey: ._federalCategory) + try container.encode(self.federalLevelCategory, forKey: ._federalLevelCategory) + try container.encode(self.federalAgeCategory, forKey: ._federalAgeCategory) + try container.encode(self.closedRegistrationDate, forKey: ._closedRegistrationDate) + try container.encode(self.groupStageAdditionalQualified, forKey: ._groupStageAdditionalQualified) + try container.encode(self.courtCount, forKey: ._courtCount) + try container.encode(self.prioritizeClubMembers, forKey: ._prioritizeClubMembers) + try container.encode(self.qualifiedPerGroupStage, forKey: ._qualifiedPerGroupStage) + try container.encode(self.teamsPerGroupStage, forKey: ._teamsPerGroupStage) + try container.encode(self.entryFee, forKey: ._entryFee) + try _encodePayment(container: &container) + try container.encode(self.additionalEstimationDuration, forKey: ._additionalEstimationDuration) + try container.encode(self.isDeleted, forKey: ._isDeleted) + try _encodeIscanceled(container: &container) + try container.encode(self.publishTeams, forKey: ._publishTeams) + try container.encode(self.publishSummons, forKey: ._publishSummons) + try container.encode(self.publishGroupStages, forKey: ._publishGroupStages) + try container.encode(self.publishBrackets, forKey: ._publishBrackets) + try container.encode(self.shouldVerifyGroupStage, forKey: ._shouldVerifyGroupStage) + try container.encode(self.shouldVerifyBracket, forKey: ._shouldVerifyBracket) + try container.encode(self.hideTeamsWeight, forKey: ._hideTeamsWeight) + try container.encode(self.publishTournament, forKey: ._publishTournament) + try container.encode(self.hidePointsEarned, forKey: ._hidePointsEarned) + try container.encode(self.publishRankings, forKey: ._publishRankings) + try container.encode(self.loserBracketMode, forKey: ._loserBracketMode) + try container.encode(self.initialSeedRound, forKey: ._initialSeedRound) + try container.encode(self.initialSeedCount, forKey: ._initialSeedCount) + try container.encode(self.enableOnlineRegistration, forKey: ._enableOnlineRegistration) + try container.encode(self.registrationDateLimit, forKey: ._registrationDateLimit) + try container.encode(self.openingRegistrationDate, forKey: ._openingRegistrationDate) + try container.encode(self.waitingListLimit, forKey: ._waitingListLimit) + try container.encode(self.accountIsRequired, forKey: ._accountIsRequired) + try container.encode(self.licenseIsRequired, forKey: ._licenseIsRequired) + try container.encode(self.minimumPlayerPerTeam, forKey: ._minimumPlayerPerTeam) + try container.encode(self.maximumPlayerPerTeam, forKey: ._maximumPlayerPerTeam) + try container.encode(self.information, forKey: ._information) + try super.encode(to: encoder) + } + + func eventValue() -> Event? { + guard let event = self.event else { return nil } + return Store.main.findById(event) + } + + func copy(from other: any Storable) { + guard let tournament = other as? BaseTournament else { return } + self.id = tournament.id + self.event = tournament.event + self.name = tournament.name + self.startDate = tournament.startDate + self.endDate = tournament.endDate + self.creationDate = tournament.creationDate + self.isPrivate = tournament.isPrivate + self.groupStageFormat = tournament.groupStageFormat + self.roundFormat = tournament.roundFormat + self.loserRoundFormat = tournament.loserRoundFormat + self.groupStageSortMode = tournament.groupStageSortMode + self.groupStageCount = tournament.groupStageCount + self.rankSourceDate = tournament.rankSourceDate + self.dayDuration = tournament.dayDuration + self.teamCount = tournament.teamCount + self.teamSorting = tournament.teamSorting + self.federalCategory = tournament.federalCategory + self.federalLevelCategory = tournament.federalLevelCategory + self.federalAgeCategory = tournament.federalAgeCategory + self.closedRegistrationDate = tournament.closedRegistrationDate + self.groupStageAdditionalQualified = tournament.groupStageAdditionalQualified + self.courtCount = tournament.courtCount + self.prioritizeClubMembers = tournament.prioritizeClubMembers + self.qualifiedPerGroupStage = tournament.qualifiedPerGroupStage + self.teamsPerGroupStage = tournament.teamsPerGroupStage + self.entryFee = tournament.entryFee + self.payment = tournament.payment + self.additionalEstimationDuration = tournament.additionalEstimationDuration + self.isDeleted = tournament.isDeleted + self.isCanceled = tournament.isCanceled + self.publishTeams = tournament.publishTeams + self.publishSummons = tournament.publishSummons + self.publishGroupStages = tournament.publishGroupStages + self.publishBrackets = tournament.publishBrackets + self.shouldVerifyGroupStage = tournament.shouldVerifyGroupStage + self.shouldVerifyBracket = tournament.shouldVerifyBracket + self.hideTeamsWeight = tournament.hideTeamsWeight + self.publishTournament = tournament.publishTournament + self.hidePointsEarned = tournament.hidePointsEarned + self.publishRankings = tournament.publishRankings + self.loserBracketMode = tournament.loserBracketMode + self.initialSeedRound = tournament.initialSeedRound + self.initialSeedCount = tournament.initialSeedCount + self.enableOnlineRegistration = tournament.enableOnlineRegistration + self.registrationDateLimit = tournament.registrationDateLimit + self.openingRegistrationDate = tournament.openingRegistrationDate + self.waitingListLimit = tournament.waitingListLimit + self.accountIsRequired = tournament.accountIsRequired + self.licenseIsRequired = tournament.licenseIsRequired + self.minimumPlayerPerTeam = tournament.minimumPlayerPerTeam + self.maximumPlayerPerTeam = tournament.maximumPlayerPerTeam + self.information = tournament.information + } + + static func relationships() -> [Relationship] { + return [ + Relationship(type: Event.self, keyPath: \BaseTournament.event), + ] + } + +} \ No newline at end of file diff --git a/PadelClub/Data/Gen/Club.json b/PadelClub/Data/Gen/Club.json new file mode 100644 index 0000000..14a9fc7 --- /dev/null +++ b/PadelClub/Data/Gen/Club.json @@ -0,0 +1,92 @@ +{ + "models": [ + { + "name": "Club", + "synchronizable": true, + "observable": true, + "properties": [ + { + "name": "id", + "type": "String", + "defaultValue": "Store.randomId()" + }, + { + "name": "creator", + "type": "String", + "optional": true, + "defaultValue": "nil", + "foreignKey": "CustomUser" + }, + { + "name": "name", + "type": "String", + "defaultValue": "\"\"" + }, + { + "name": "acronym", + "type": "String", + "defaultValue": "\"\"" + }, + { + "name": "phone", + "type": "String", + "optional": true, + "defaultValue": "nil" + }, + { + "name": "code", + "type": "String", + "optional": true, + "defaultValue": "nil" + }, + { + "name": "address", + "type": "String", + "optional": true, + "defaultValue": "nil" + }, + { + "name": "city", + "type": "String", + "optional": true, + "defaultValue": "nil" + }, + { + "name": "zipCode", + "type": "String", + "optional": true, + "defaultValue": "nil" + }, + { + "name": "latitude", + "type": "Double", + "optional": true, + "defaultValue": "nil" + }, + { + "name": "longitude", + "type": "Double", + "optional": true, + "defaultValue": "nil" + }, + { + "name": "courtCount", + "type": "Int", + "defaultValue": "2" + }, + { + "name": "broadcastCode", + "type": "String", + "optional": true, + "defaultValue": "nil" + }, + { + "name": "timezone", + "type": "String", + "optional": true, + "defaultValue": "TimeZone.current.identifier" + } + ] + } + ] +} diff --git a/PadelClub/Data/Gen/Court.json b/PadelClub/Data/Gen/Court.json new file mode 100644 index 0000000..ec2bbe4 --- /dev/null +++ b/PadelClub/Data/Gen/Court.json @@ -0,0 +1,42 @@ +{ + "models": [ + { + "name": "Court", + "synchronizable": true, + "observable": true, + "properties": [ + { + "name": "id", + "type": "String", + "defaultValue": "Store.randomId()" + }, + { + "name": "index", + "type": "Int" + }, + { + "name": "club", + "type": "String", + "foreignKey": "Club" + }, + { + "name": "name", + "type": "String", + "optional": true, + "defaultValue": "nil" + }, + { + "name": "exitAllowed", + "type": "Bool", + "defaultValue": "false" + }, + { + "name": "indoor", + "type": "Bool", + "defaultValue": "false" + } + ], + "tokenExemptedMethods": [] + } + ] +} diff --git a/PadelClub/Data/Gen/CustomUser.json b/PadelClub/Data/Gen/CustomUser.json new file mode 100644 index 0000000..ac32c3c --- /dev/null +++ b/PadelClub/Data/Gen/CustomUser.json @@ -0,0 +1,136 @@ +{ + "models": [ + { + "name": "CustomUser", + "resource_name": "users", + "synchronizable": true, + "observable": true, + "tokenExemptedMethods": ["post"], + "properties": [ + { + "name": "id", + "type": "String", + "defaultValue": "Store.randomId()" + }, + { + "name": "username", + "type": "String" + }, + { + "name": "email", + "type": "String" + }, + { + "name": "clubs", + "type": "[String]", + "defaultValue": "[]" + }, + { + "name": "umpireCode", + "type": "String", + "optional": true, + "defaultValue": "nil" + }, + { + "name": "licenceId", + "type": "String", + "optional": true, + "defaultValue": "nil" + }, + { + "name": "firstName", + "type": "String" + }, + { + "name": "lastName", + "type": "String" + }, + { + "name": "phone", + "type": "String", + "optional": true, + "defaultValue": "nil" + }, + { + "name": "country", + "type": "String", + "optional": true, + "defaultValue": "nil" + }, + { + "name": "summonsMessageBody", + "type": "String", + "optional": true, + "defaultValue": "nil" + }, + { + "name": "summonsMessageSignature", + "type": "String", + "optional": true, + "defaultValue": "nil" + }, + { + "name": "summonsAvailablePaymentMethods", + "type": "String", + "optional": true, + "defaultValue": "nil" + }, + { + "name": "summonsDisplayFormat", + "type": "Bool", + "defaultValue": "false" + }, + { + "name": "summonsDisplayEntryFee", + "type": "Bool", + "defaultValue": "false" + }, + { + "name": "summonsUseFullCustomMessage", + "type": "Bool", + "defaultValue": "false" + }, + { + "name": "matchFormatsDefaultDuration", + "type": "[MatchFormat: Int]", + "optional": true, + "defaultValue": "nil" + }, + { + "name": "bracketMatchFormatPreference", + "type": "MatchFormat", + "optional": true, + "defaultValue": "nil" + }, + { + "name": "groupStageMatchFormatPreference", + "type": "MatchFormat", + "optional": true, + "defaultValue": "nil" + }, + { + "name": "loserBracketMatchFormatPreference", + "type": "MatchFormat", + "optional": true, + "defaultValue": "nil" + }, + { + "name": "loserBracketMode", + "type": "LoserBracketMode", + "defaultValue": ".automatic" + }, + { + "name": "deviceId", + "type": "String", + "optional": true, + "defaultValue": "nil" + }, + { + "name": "agents", + "type": "[String]", + "defaultValue": "[]" + } + ] + } + ] +} diff --git a/PadelClub/Data/Gen/DateInterval.json b/PadelClub/Data/Gen/DateInterval.json new file mode 100644 index 0000000..b7c1532 --- /dev/null +++ b/PadelClub/Data/Gen/DateInterval.json @@ -0,0 +1,33 @@ +{ + "models": [ + { + "name": "DateInterval", + "synchronizable": true, + "observable": true, + "properties": [ + { + "name": "id", + "type": "String", + "defaultValue": "Store.randomId()" + }, + { + "name": "event", + "type": "String" + }, + { + "name": "courtIndex", + "type": "Int" + }, + { + "name": "startDate", + "type": "Date" + }, + { + "name": "endDate", + "type": "Date" + } + ], + "tokenExemptedMethods": [] + } + ] +} diff --git a/PadelClub/Data/Gen/Drawlog.json b/PadelClub/Data/Gen/Drawlog.json new file mode 100644 index 0000000..8f872d9 --- /dev/null +++ b/PadelClub/Data/Gen/Drawlog.json @@ -0,0 +1,47 @@ + +{ + "models": [ + { + "name": "DrawLog", + "synchronizable": true, + "sideStorable": true, + "observable": true, + "relationshipNames": [], + "properties": [ + { + "name": "id", + "type": "String", + "defaultValue": "Store.randomId()" + }, + { + "name": "tournament", + "type": "String", + "foreignKey": "Tournament" + }, + { + "name": "drawDate", + "type": "Date", + "defaultValue": "Date()" + }, + { + "name": "drawSeed", + "type": "Int" + }, + { + "name": "drawMatchIndex", + "type": "Int" + }, + { + "name": "drawTeamPosition", + "type": "TeamPosition", + "defaultValue": "TeamPosition.one" + }, + { + "name": "drawType", + "type": "DrawType", + "defaultValue": "DrawType.seed" + } + ] + } + ] +} diff --git a/PadelClub/Data/Gen/Event.json b/PadelClub/Data/Gen/Event.json new file mode 100644 index 0000000..0fa8d00 --- /dev/null +++ b/PadelClub/Data/Gen/Event.json @@ -0,0 +1,48 @@ +{ + "models": [ + { + "name": "Event", + "synchronizable": true, + "observable": true, + "properties": [ + { + "name": "id", + "type": "String", + "defaultValue": "Store.randomId()" + }, + { + "name": "creator", + "type": "String", + "optional": true, + "defaultValue": "nil", + "foreignKey": "CustomUser" + }, + { + "name": "club", + "type": "String", + "optional": true, + "defaultValue": "nil", + "foreignKey": "Club" + }, + { + "name": "creationDate", + "type": "Date", + "defaultValue": "Date()" + }, + { + "name": "name", + "type": "String", + "optional": true, + "defaultValue": "nil" + }, + { + "name": "tenupId", + "type": "String", + "optional": true, + "defaultValue": "nil" + } + ], + "tokenExemptedMethods": [] + } + ] +} diff --git a/PadelClub/Data/Gen/GroupStage.json b/PadelClub/Data/Gen/GroupStage.json new file mode 100644 index 0000000..ecfdad5 --- /dev/null +++ b/PadelClub/Data/Gen/GroupStage.json @@ -0,0 +1,53 @@ +{ + "models": [ + { + "name": "GroupStage", + "synchronizable": true, + "observable": true, + "properties": [ + { + "name": "id", + "type": "String", + "defaultValue": "Store.randomId()" + }, + { + "name": "tournament", + "type": "String", + "foreignKey": "Tournament" + }, + { + "name": "index", + "type": "Int" + }, + { + "name": "size", + "type": "Int" + }, + { + "name": "format", + "type": "MatchFormat", + "optional": true, + "defaultValue": "nil" + }, + { + "name": "startDate", + "type": "Date", + "optional": true, + "defaultValue": "nil" + }, + { + "name": "name", + "type": "String", + "optional": true, + "defaultValue": "nil" + }, + { + "name": "step", + "type": "Int", + "defaultValue": "0" + } + ], + "tokenExemptedMethods": [] + } + ] +} diff --git a/PadelClub/Data/Gen/Match.json b/PadelClub/Data/Gen/Match.json new file mode 100644 index 0000000..913014c --- /dev/null +++ b/PadelClub/Data/Gen/Match.json @@ -0,0 +1,83 @@ +{ + "models": [ + { + "name": "Match", + "synchronizable": true, + "observable": true, + "tokenExemptedMethods": [], + "properties": [ + { + "name": "id", + "type": "String", + "defaultValue": "Store.randomId()" + }, + { + "name": "round", + "type": "String", + "optional": true, + "foreignKey": "Round*" + }, + { + "name": "groupStage", + "type": "String", + "optional": true, + "foreignKey": "GroupStage*" + }, + { + "name": "startDate", + "type": "Date", + "optional": true + }, + { + "name": "endDate", + "type": "Date", + "optional": true + }, + { + "name": "index", + "type": "Int" + }, + { + "name": "format", + "type": "MatchFormat", + "optional": true + }, + { + "name": "servingTeamId", + "type": "String", + "optional": true + }, + { + "name": "winningTeamId", + "type": "String", + "optional": true + }, + { + "name": "losingTeamId", + "type": "String", + "optional": true + }, + { + "name": "name", + "type": "String", + "optional": true + }, + { + "name": "disabled", + "type": "Bool", + "defaultValue": "false" + }, + { + "name": "courtIndex", + "type": "Int", + "optional": true + }, + { + "name": "confirmed", + "type": "Bool", + "defaultValue": "false" + } + ] + } + ] +} diff --git a/PadelClub/Data/Gen/MatchScheduler.json b/PadelClub/Data/Gen/MatchScheduler.json new file mode 100644 index 0000000..90f09d7 --- /dev/null +++ b/PadelClub/Data/Gen/MatchScheduler.json @@ -0,0 +1,84 @@ +{ + "models": [ + { + "name": "MatchScheduler", + "resource_name": "match-scheduler", + "synchronizable": false, + "observable": true, + "tokenExemptedMethods": [], + "properties": [ + { + "name": "id", + "type": "String", + "defaultValue": "Store.randomId()" + }, + { + "name": "tournament", + "type": "String", + "foreignKey": "Tournament" + }, + { + "name": "timeDifferenceLimit", + "type": "Int" + }, + { + "name": "loserBracketRotationDifference", + "type": "Int" + }, + { + "name": "upperBracketRotationDifference", + "type": "Int" + }, + { + "name": "accountUpperBracketBreakTime", + "type": "Bool" + }, + { + "name": "accountLoserBracketBreakTime", + "type": "Bool" + }, + { + "name": "randomizeCourts", + "type": "Bool" + }, + { + "name": "rotationDifferenceIsImportant", + "type": "Bool" + }, + { + "name": "shouldHandleUpperRoundSlice", + "type": "Bool" + }, + { + "name": "shouldEndRoundBeforeStartingNext", + "type": "Bool" + }, + { + "name": "groupStageChunkCount", + "type": "Int", + "optional": true + }, + { + "name": "overrideCourtsUnavailability", + "type": "Bool", + "defaultValue": "false" + }, + { + "name": "shouldTryToFillUpCourtsAvailable", + "type": "Bool", + "defaultValue": "false" + }, + { + "name": "courtsAvailable", + "type": "Set", + "defaultValue": "Set()" + }, + { + "name": "simultaneousStart", + "type": "Bool", + "defaultValue": "true" + } + ] + } + ] +} diff --git a/PadelClub/Data/Gen/MonthData.json b/PadelClub/Data/Gen/MonthData.json new file mode 100644 index 0000000..896038f --- /dev/null +++ b/PadelClub/Data/Gen/MonthData.json @@ -0,0 +1,71 @@ +{ + "models": [ + { + "name": "MonthData", + "resource_name": "month-data", + "synchronizable": false, + "observable": true, + "tokenExemptedMethods": [], + "properties": [ + { + "name": "id", + "type": "String", + "defaultValue": "Store.randomId()" + }, + { + "name": "monthKey", + "type": "String" + }, + { + "name": "creationDate", + "type": "Date" + }, + { + "name": "maleUnrankedValue", + "type": "Int", + "optional": true, + "defaultValue": "nil" + }, + { + "name": "femaleUnrankedValue", + "type": "Int", + "optional": true, + "defaultValue": "nil" + }, + { + "name": "maleCount", + "type": "Int", + "optional": true, + "defaultValue": "nil" + }, + { + "name": "femaleCount", + "type": "Int", + "optional": true, + "defaultValue": "nil" + }, + { + "name": "anonymousCount", + "type": "Int", + "optional": true, + "defaultValue": "nil" + }, + { + "name": "incompleteMode", + "type": "Bool", + "defaultValue": "false" + }, + { + "name": "dataModelIdentifier", + "type": "String", + "optional": true + }, + { + "name": "fileModelIdentifier", + "type": "String", + "optional": true + } + ] + } + ] +} diff --git a/PadelClub/Data/Gen/PlayerRegistration.json b/PadelClub/Data/Gen/PlayerRegistration.json new file mode 100644 index 0000000..1f5eb98 --- /dev/null +++ b/PadelClub/Data/Gen/PlayerRegistration.json @@ -0,0 +1,122 @@ +{ + "models": [ + { + "name": "PlayerRegistration", + "synchronizable": true, + "sideStorable": true, + "observable": true, + "relationshipNames": ["teamRegistration"], + "properties": [ + { + "name": "id", + "type": "String", + "defaultValue": "Store.randomId()" + }, + { + "name": "teamRegistration", + "type": "String", + "optional": true, + "foreignKey": "TeamRegistration" + }, + { + "name": "firstName", + "type": "String" + }, + { + "name": "lastName", + "type": "String" + }, + { + "name": "licenceId", + "type": "String", + "optional": true + }, + { + "name": "rank", + "type": "Int", + "optional": true + }, + { + "name": "paymentType", + "type": "PlayerPaymentType", + "optional": true + }, + { + "name": "sex", + "type": "PlayerSexType", + "optional": true + }, + { + "name": "tournamentPlayed", + "type": "Int", + "optional": true + }, + { + "name": "points", + "type": "Double", + "optional": true + }, + { + "name": "clubName", + "type": "String", + "optional": true + }, + { + "name": "ligueName", + "type": "String", + "optional": true + }, + { + "name": "assimilation", + "type": "String", + "optional": true + }, + { + "name": "phoneNumber", + "type": "String", + "optional": true + }, + { + "name": "email", + "type": "String", + "optional": true + }, + { + "name": "birthdate", + "type": "String", + "optional": true + }, + { + "name": "computedRank", + "type": "Int", + "defaultValue": "0" + }, + { + "name": "source", + "type": "PlayerRegistration.PlayerDataSource", + "optional": true + }, + { + "name": "hasArrived", + "type": "Bool", + "defaultValue": "false" + }, + { + "name": "coach", + "type": "Bool", + "defaultValue": "false" + }, + { + "name": "captain", + "type": "Bool", + "defaultValue": "false" + }, + { + "name": "registeredOnline", + "type": "Bool", + "defaultValue": "false" + } + ] + } + ] +} diff --git a/PadelClub/Data/Gen/Purchase.json b/PadelClub/Data/Gen/Purchase.json new file mode 100644 index 0000000..a8dd49b --- /dev/null +++ b/PadelClub/Data/Gen/Purchase.json @@ -0,0 +1,51 @@ +{ + "models": [ + { + "name": "Purchase", + "synchronizable": true, + "properties": [ + { + "name": "id", + "type": "UInt64", + "defaultValue": "0" + }, + { + "name": "user", + "type": "String", + "defaultValue": "\"\"", + "encryption": "standard", + "foreignKey": "CustomUser" + }, + { + "name": "purchaseDate", + "type": "Date" + }, + { + "name": "productId", + "type": "String", + "defaultValue": "\"\"" + }, + { + "name": "quantity", + "type": "Int", + "optional": true, + "defaultValue": "nil" + }, + { + "name": "revocationDate", + "type": "Date", + "optional": true, + "defaultValue": "nil" + }, + { + "name": "expirationDate", + "type": "Date", + "optional": true, + "defaultValue": "nil" + } + ], + "tokenExemptedMethods": [], + "relationshipNames": [] + } + ] +} diff --git a/PadelClub/Data/Gen/Round.json b/PadelClub/Data/Gen/Round.json new file mode 100644 index 0000000..48a6cf7 --- /dev/null +++ b/PadelClub/Data/Gen/Round.json @@ -0,0 +1,53 @@ +{ + "models": [ + { + "name": "Round", + "synchronizable": true, + "sideStorable": true, + "observable": true, + "relationshipNames": [], + "properties": [ + { + "name": "id", + "type": "String", + "defaultValue": "Store.randomId()" + }, + { + "name": "tournament", + "type": "String", + "foreignKey": "Tournament" + }, + { + "name": "index", + "type": "Int" + }, + { + "name": "parent", + "type": "String", + "optional": true + }, + { + "name": "format", + "type": "MatchFormat", + "optional": true, + "private": true + }, + { + "name": "startDate", + "type": "Date", + "optional": true + }, + { + "name": "groupStageLoserBracket", + "type": "Bool", + "defaultValue": "false" + }, + { + "name": "loserBracketMode", + "type": "LoserBracketMode", + "defaultValue": ".automatic" + } + ] + } + ] +} diff --git a/PadelClub/Data/Gen/TeamRegistration.json b/PadelClub/Data/Gen/TeamRegistration.json new file mode 100644 index 0000000..995d08e --- /dev/null +++ b/PadelClub/Data/Gen/TeamRegistration.json @@ -0,0 +1,118 @@ +{ + "models": [ + { + "name": "TeamRegistration", + "synchronizable": true, + "sideStorable": true, + "observable": true, + "relationshipNames": [], + "properties": [ + { + "name": "id", + "type": "String", + "defaultValue": "Store.randomId()" + }, + { + "name": "tournament", + "type": "String" + }, + { + "name": "groupStage", + "type": "String", + "optional": true, + "foreignKey": "GroupStage*" + }, + { + "name": "registrationDate", + "type": "Date", + "optional": true + }, + { + "name": "callDate", + "type": "Date", + "optional": true + }, + { + "name": "bracketPosition", + "type": "Int", + "optional": true + }, + { + "name": "groupStagePosition", + "type": "Int", + "optional": true + }, + { + "name": "comment", + "type": "String", + "optional": true + }, + { + "name": "source", + "type": "String", + "optional": true + }, + { + "name": "sourceValue", + "type": "String", + "optional": true + }, + { + "name": "logo", + "type": "String", + "optional": true + }, + { + "name": "name", + "type": "String", + "optional": true + }, + { + "name": "walkOut", + "type": "Bool", + "defaultValue": "false" + }, + { + "name": "wildCardBracket", + "type": "Bool", + "defaultValue": "false" + }, + { + "name": "wildCardGroupStage", + "type": "Bool", + "defaultValue": "false" + }, + { + "name": "weight", + "type": "Int", + "defaultValue": "0" + }, + { + "name": "lockedWeight", + "type": "Int", + "optional": true + }, + { + "name": "confirmationDate", + "type": "Date", + "optional": true + }, + { + "name": "qualified", + "type": "Bool", + "defaultValue": "false" + }, + { + "name": "finalRanking", + "type": "Int", + "optional": true + }, + { + "name": "pointsEarned", + "type": "Int", + "optional": true + } + ] + } + ] +} diff --git a/PadelClub/Data/Gen/TeamScore.json b/PadelClub/Data/Gen/TeamScore.json new file mode 100644 index 0000000..9165c44 --- /dev/null +++ b/PadelClub/Data/Gen/TeamScore.json @@ -0,0 +1,44 @@ +{ + "models": [ + { + "name": "TeamScore", + "synchronizable": true, + "sideStorable": true, + "observable": true, + "relationshipNames": ["match"], + "properties": [ + { + "name": "id", + "type": "String", + "defaultValue": "Store.randomId()" + }, + { + "name": "match", + "type": "String", + "foreignKey": "Match*" + }, + { + "name": "teamRegistration", + "type": "String", + "optional": true, + "foreignKey": "TeamRegistration*" + }, + { + "name": "score", + "type": "String", + "optional": true + }, + { + "name": "walkOut", + "type": "Int", + "optional": true + }, + { + "name": "luckyLoser", + "type": "Int", + "optional": true + } + ] + } + ] +} diff --git a/PadelClub/Data/Gen/Tournament.json b/PadelClub/Data/Gen/Tournament.json new file mode 100644 index 0000000..2c752ea --- /dev/null +++ b/PadelClub/Data/Gen/Tournament.json @@ -0,0 +1,271 @@ +{ + "models": [ + { + "name": "Tournament", + "synchronizable": true, + "copyable": true, + "observable": true, + "relationshipNames": [], + "properties": [ + { + "name": "id", + "type": "String", + "defaultValue": "Store.randomId()" + }, + { + "name": "event", + "type": "String", + "optional": true, + "foreignKey": "Event" + }, + { + "name": "name", + "type": "String", + "optional": true + }, + { + "name": "startDate", + "type": "Date" + }, + { + "name": "endDate", + "type": "Date", + "optional": true + }, + { + "name": "creationDate", + "type": "Date", + "private": true + }, + { + "name": "isPrivate", + "type": "Bool" + }, + { + "name": "groupStageFormat", + "type": "MatchFormat", + "optional": true, + "private": true + }, + { + "name": "roundFormat", + "type": "MatchFormat", + "optional": true, + "private": true + }, + { + "name": "loserRoundFormat", + "type": "MatchFormat", + "optional": true, + "private": true + }, + { + "name": "groupStageSortMode", + "type": "GroupStageOrderingMode", + "defaultValue": "GroupStageOrderingMode.snake" + }, + { + "name": "groupStageCount", + "type": "Int" + }, + { + "name": "rankSourceDate", + "type": "Date", + "optional": true + }, + { + "name": "dayDuration", + "type": "Int" + }, + { + "name": "teamCount", + "type": "Int" + }, + { + "name": "teamSorting", + "type": "TeamSortingType", + "defaultValue": "TeamSortingType.inscriptionDate" + }, + { + "name": "federalCategory", + "type": "TournamentCategory", + "defaultValue": "TournamentCategory.men" + }, + { + "name": "federalLevelCategory", + "type": "TournamentLevel", + "defaultValue": "TournamentLevel.unlisted" + }, + { + "name": "federalAgeCategory", + "type": "FederalTournamentAge", + "defaultValue": "FederalTournamentAge.unlisted" + }, + { + "name": "closedRegistrationDate", + "type": "Date", + "optional": true + }, + { + "name": "groupStageAdditionalQualified", + "type": "Int" + }, + { + "name": "courtCount", + "type": "Int", + "defaultValue": "2" + }, + { + "name": "prioritizeClubMembers", + "type": "Bool" + }, + { + "name": "qualifiedPerGroupStage", + "type": "Int" + }, + { + "name": "teamsPerGroupStage", + "type": "Int" + }, + { + "name": "entryFee", + "type": "Double", + "optional": true + }, + { + "name": "payment", + "type": "TournamentPayment", + "optional": true, + "defaultValue": "nil", + "encryption": "tournament_payment" + }, + { + "name": "additionalEstimationDuration", + "type": "Int", + "defaultValue": "0" + }, + { + "name": "isDeleted", + "type": "Bool", + "defaultValue": "false" + }, + { + "name": "isCanceled", + "type": "Bool", + "defaultValue": "false", + "encryption": "tournament_iscanceled" + }, + { + "name": "publishTeams", + "type": "Bool", + "defaultValue": "false" + }, + { + "name": "publishSummons", + "type": "Bool", + "defaultValue": "false" + }, + { + "name": "publishGroupStages", + "type": "Bool", + "defaultValue": "false" + }, + { + "name": "publishBrackets", + "type": "Bool", + "defaultValue": "false" + }, + { + "name": "shouldVerifyGroupStage", + "type": "Bool", + "defaultValue": "false" + }, + { + "name": "shouldVerifyBracket", + "type": "Bool", + "defaultValue": "false" + }, + { + "name": "hideTeamsWeight", + "type": "Bool", + "defaultValue": "false" + }, + { + "name": "publishTournament", + "type": "Bool", + "defaultValue": "false" + }, + { + "name": "hidePointsEarned", + "type": "Bool", + "defaultValue": "false" + }, + { + "name": "publishRankings", + "type": "Bool", + "defaultValue": "false" + }, + { + "name": "loserBracketMode", + "type": "LoserBracketMode", + "defaultValue": ".automatic" + }, + { + "name": "initialSeedRound", + "type": "Int", + "defaultValue": "0" + }, + { + "name": "initialSeedCount", + "type": "Int", + "defaultValue": "0" + }, + { + "name": "enableOnlineRegistration", + "type": "Bool", + "defaultValue": "false" + }, + { + "name": "registrationDateLimit", + "type": "Date", + "optional": true + }, + { + "name": "openingRegistrationDate", + "type": "Date", + "optional": true + }, + { + "name": "waitingListLimit", + "type": "Int", + "optional": true + }, + { + "name": "accountIsRequired", + "type": "Bool", + "defaultValue": "true" + }, + { + "name": "licenseIsRequired", + "type": "Bool", + "defaultValue": "true" + }, + { + "name": "minimumPlayerPerTeam", + "type": "Int", + "defaultValue": 2 + }, + { + "name": "maximumPlayerPerTeam", + "type": "Int", + "defaultValue": 2 + }, + { + "name": "information", + "type": "String", + "optional": true + } + ] + } + ] +} diff --git a/PadelClub/Data/Gen/generator.py b/PadelClub/Data/Gen/generator.py new file mode 100644 index 0000000..a8ece00 --- /dev/null +++ b/PadelClub/Data/Gen/generator.py @@ -0,0 +1,566 @@ +import json +import re +import os +from pathlib import Path +from typing import Dict, List, Any +import argparse +import sys +import logging +from datetime import datetime +import inflect + +class SwiftModelGenerator: + def __init__(self, json_data: Dict[str, Any]): + self.data = json_data + self.pluralizer = inflect.engine() + + def generate_model(self, model_data: Dict[str, Any]) -> str: + model_name = model_data["name"] + is_sync = model_data.get("synchronizable", False) + is_observable = model_data.get("observable", False) + properties = model_data["properties"] + + # Get protocol specific configurations + resource = self.make_resource_name(model_name) + resource_name = model_data.get("resource_name", resource) + token_exempted = model_data.get("tokenExemptedMethods", []) + + lines = ["// Generated by SwiftModelGenerator", "// Do not modify this file manually", ""] + + # Import statement + lines.append("import Foundation") + lines.append("import LeStorage") + if is_observable: + lines.append("import SwiftUI") + lines.append("") + + # Class declaration + if is_observable: + lines.append("@Observable") + protocol = "SyncedStorable" if is_sync else "Storable" + base_class = "SyncedModelObject" if is_sync else "BaseModelObject" + lines.append(f"class Base{model_name}: {base_class}, {protocol} {{") + lines.append("") + + # Add SyncedStorable protocol requirements + lines.extend(self._generate_protocol_requirements(resource_name, token_exempted)) + lines.append("") + + # Properties + for prop in properties: + swift_type = prop["type"] + if prop.get("optional", False): + swift_type += "?" + default_value = prop.get("defaultValue", "nil" if prop.get("optional", False) else self._get_default_value(swift_type)) + lines.append(f" var {prop['name']}: {swift_type} = {default_value}") + + lines.append("") + + # Add constructor + lines.extend(self._generate_constructor(model_name, properties)) + lines.append("") + + # CodingKeys + lines.extend(self._generate_coding_keys(properties, is_observable, is_sync)) + lines.append("") + + # Encryption methods + encrypted_props = [p for p in properties if "encryption" in p] + if encrypted_props: + lines.extend(self._generate_encryption_methods(properties)) + lines.append("") + + # Codable implementation + lines.extend(self._generate_decoder(model_name, properties, is_observable, is_sync)) + lines.append("") + lines.extend(self._generate_encoder(properties, is_observable, is_sync)) + lines.append("") + + # Foreign Key convenience + foreign_key_methods = self._generate_foreign_key_methods(properties) + if foreign_key_methods: + lines.extend(foreign_key_methods) + # lines.append("") + + # Copy method + lines.extend(self._generate_copy_method(model_name, properties)) + lines.append("") + + # Add relationships function + lines.extend(self._generate_relationships(model_name, properties)) + lines.append("") + + lines.append("}") + return "\n".join(lines) + + def _generate_constructor(self, model_name: str, properties: List[Dict[str, Any]]) -> List[str]: + """Generate a constructor with all properties as parameters with default values.""" + lines = [" init("] + + # Generate parameter list + params = [] + for prop in properties: + name = prop['name'] + type_name = prop['type'] + is_optional = prop.get("optional", False) + + # Always include a default value + default_value = prop.get("defaultValue") + if default_value is None: + if is_optional: + default_value = "nil" + else: + default_value = self._get_default_value(type_name) + + # Format the parameter with its type and default value + param = f" {name}: {type_name}" + if is_optional: + param += "?" + param += f" = {default_value}" + params.append(param) + + # Join parameters with commas + lines.extend([f"{param}," for param in params[:-1]]) + lines.append(f"{params[-1]}") # Last parameter without comma + + # Constructor body + lines.extend([ + " ) {", + " super.init()", + ]) + + # Property assignments + for prop in properties: + name = prop['name'] + lines.append(f" self.{name} = {name}") + + lines.append(" }") + return lines + + def _generate_foreign_key_methods(self, properties: List[Dict[str, Any]]) -> List[str]: + lines = [] + for prop in properties: + if "foreignKey" in prop: + foreign_key = prop["foreignKey"] + prop_name = prop["name"] + method_name = f"{prop_name}Value" + is_optional = prop.get("optional", False) + + lines.extend([f" func {method_name}() -> {foreign_key.rstrip('*')}? {{"]) + + if is_optional: + lines.extend([ + f" guard let {prop_name} = self.{prop_name} else {{ return nil }}" + ]) + + if foreign_key.endswith("*"): + foreign_key = foreign_key[:-1] # Remove the asterisk + lines.extend([ + f" return self.store?.findById({prop_name})" + ]) + else: + lines.extend([ + f" return Store.main.findById({prop_name})" + ]) + + lines.extend([" }", ""]) # Close the method and add a blank line + return lines + + def _generate_coding_keys(self, properties: List[Dict[str, Any]], is_observable: bool, is_sync: bool = False) -> List[str]: + lines = [" enum CodingKeys: String, CodingKey {"] + for prop in properties: + name = prop['name'] + # Add underscore prefix to case name if observable, but keep the string value without underscore + case_name = f"_{name}" if is_observable else name + lines.append(f" case {case_name} = \"{name}\"") + + lines.append(" }") + return lines + + def _generate_encryption_methods(self, properties: List[Dict[str, Any]]) -> List[str]: + lines = [] + for prop in properties: + if "encryption" in prop: + name = prop['name'] + enc_type = prop['encryption'] + if enc_type == "tournament_payment": + lines.extend([ + f" private static func _decode{name.capitalize()}(container: KeyedDecodingContainer) throws -> TournamentPayment? {{", + f" let data = try container.decodeIfPresent(Data.self, forKey: ._{name})", + " ", + " if let data {", + " do {", + " let decoded: String = try data.decryptData(pass: CryptoKey.pass.rawValue)", + " let sequence = decoded.compactMap { NumberFormatter.standard.number(from: String($0))?.intValue }", + " return TournamentPayment(rawValue: sequence[18])", + " } catch {", + " Logger.error(error)", + " }", + " }", + " return nil", + " }", + "", + f" private func _encode{name.capitalize()}(container: inout KeyedEncodingContainer) throws {{", + f" guard let {name} else {{", + f" try container.encodeNil(forKey: ._{name})", + " return", + " }", + " ", + " let max: Int = TournamentPayment.allCases.count", + " var sequence = (1...18).map { _ in Int.random(in: (0..) throws -> Bool {{", + f" let data = try container.decodeIfPresent(Data.self, forKey: ._{name})", + " if let data {", + " do {", + " let decoded: String = try data.decryptData(pass: CryptoKey.pass.rawValue)", + " let sequence = decoded.compactMap { NumberFormatter.standard.number(from: String($0))?.intValue }", + " return Bool.decodeInt(sequence[18])", + " } catch {", + " Logger.error(error)", + " }", + " }", + " return false", + " }", + "", + f" private func _encode{name.capitalize()}(container: inout KeyedEncodingContainer) throws {{", + " let max: Int = 9", + " var sequence = (1...18).map { _ in Int.random(in: (0...max)) }", + f" sequence.append(self.{name}.encodedValue)", + " sequence.append(contentsOf: (1...13).map { _ in Int.random(in: (0...max ))} )", + " ", + " let stringCombo: [String] = sequence.map { $0.formatted() }", + " let joined: String = stringCombo.joined(separator: \"\")", + " if let data = joined.data(using: .utf8) {", + " let encryped: Data = try data.encrypt(pass: CryptoKey.pass.rawValue)", + f" try container.encode(encryped, forKey: ._{name})", + " }", + " }" + ]) + return lines + + def _generate_decoder(self, model_name: str, properties: List[Dict[str, Any]], is_observable: bool, is_sync: bool = False) -> List[str]: + lines = [" required init(from decoder: Decoder) throws {", + " let container = try decoder.container(keyedBy: CodingKeys.self)"] + + for prop in properties: + name = prop['name'] + type_name = prop['type'] + is_optional = prop.get("optional", False) + default_value = prop.get("defaultValue", "nil" if is_optional else self._get_default_value(type_name)) + option = prop.get("option") + + # Use the correct case reference based on observable flag + case_ref = f"_{name}" if is_observable else name + + if "encryption" in prop: + enc_type = prop['encryption'] + if enc_type == "standard": + if is_optional: + lines.append(f" self.{name} = try container.decodeEncryptedIfPresent(key: .{case_ref})") + else: + lines.append(f" self.{name} = try container.decodeEncrypted(key: .{case_ref})") + elif enc_type in ["tournament_payment", "tournament_iscanceled"]: + lines.append(f" self.{name} = try Self._decode{name.capitalize()}(container: container)") + else: + # Handle Date with fractional option + if type_name == "Date" and option == "fractional": + if is_optional: + lines.append(f" if let dateString = try container.decodeIfPresent(String.self, forKey: .{case_ref}) {{") + lines.append(f" self.{name} = Date.iso8601FractionalFormatter.date(from: dateString)") + lines.append(" } else {") + lines.append(f" self.{name} = {default_value}") + lines.append(" }") + else: + lines.append(f" let dateString = try container.decode(String.self, forKey: .{case_ref})") + lines.append(f" self.{name} = Date.iso8601FractionalFormatter.date(from: dateString) ?? {default_value}") + else: + lines.append(f" self.{name} = try container.decodeIfPresent({type_name}.self, forKey: .{case_ref}) ?? {default_value}") + + lines.append(" try super.init(from: decoder)") + + lines.append(" }") + return lines + + def _generate_encoder(self, properties: List[Dict[str, Any]], is_observable: bool, is_sync: bool = False) -> List[str]: + lines = [" override func encode(to encoder: Encoder) throws {", + " var container = encoder.container(keyedBy: CodingKeys.self)"] + + for prop in properties: + name = prop['name'] + type_name = prop['type'] + is_optional = prop.get('optional', False) + option = prop.get("option") + + # Use the correct case reference based on observable flag + case_ref = f"_{name}" if is_observable else name + + if "encryption" in prop: + enc_type = prop['encryption'] + if enc_type == "standard": + if is_optional: + lines.append(f" try container.encodeAndEncryptIfPresent(self.{name}?.data(using: .utf8), forKey: .{case_ref})") + else: + lines.append(f" try container.encodeAndEncryptIfPresent(self.{name}.data(using: .utf8), forKey: .{case_ref})") + elif enc_type in ["tournament_payment", "tournament_iscanceled"]: + lines.append(f" try _encode{name.capitalize()}(container: &container)") + else: + if type_name == "Date" and option == "fractional": + if is_optional: + lines.append(f" if let date = self.{name} {{") + lines.append(f" try container.encode(Date.iso8601FractionalFormatter.string(from: date), forKey: .{case_ref})") + lines.append(" } else {") + lines.append(f" try container.encodeNil(forKey: .{case_ref})") + lines.append(" }") + else: + lines.append(f" try container.encode(Date.iso8601FractionalFormatter.string(from: self.{name}), forKey: .{case_ref})") + else: + lines.append(f" try container.encode(self.{name}, forKey: .{case_ref})") + + lines.append(" try super.encode(to: encoder)") + lines.append(" }") + return lines + + def _generate_copy_method(self, model_name: str, properties: List[Dict[str, Any]]) -> List[str]: + + model_variable = model_name.lower() + lines = [f" func copy(from other: any Storable) {{"] + lines.append(f" guard let {model_variable} = other as? Base{model_name} else {{ return }}") + + for prop in properties: + name = prop['name'] + lines.append(f" self.{name} = {model_variable}.{name}") + + lines.append(" }") + return lines + + def _generate_protocol_requirements(self, resource_name: str, token_exempted: List[str]) -> List[str]: + """Generate the static functions required by SyncedStorable protocol.""" + # Convert HTTP methods to proper format + formatted_methods = [f".{method.lower()}" for method in token_exempted] + methods_str = ", ".join(formatted_methods) if formatted_methods else "" + + return [ + f" static func resourceName() -> String {{ return \"{resource_name}\" }}", + f" static func tokenExemptedMethods() -> [HTTPMethod] {{ return [{methods_str}] }}", + ] + + def _generate_relationships(self, model_name, properties: List[Dict[str, Any]]) -> List[str]: + # Find all properties with foreign keys + foreign_key_props = [p for p in properties if "foreignKey" in p] + + if not foreign_key_props: + # If no foreign keys, return empty array + return [ + " static func relationships() -> [Relationship] {", + " return []", + " }" + ] + + lines = [ + " static func relationships() -> [Relationship] {", + " return [" + ] + + # Generate relationship entries + for prop in foreign_key_props: + name = prop["name"] + foreign_key = prop["foreignKey"].rstrip('*') # Remove asterisk if present + lines.append(f" Relationship(type: {foreign_key}.self, keyPath: \\Base{model_name}.{name}),") + + # Close the array and function + lines.extend([ + " ]", + " }" + ]) + + return lines + + def _get_default_value(self, type_name: str) -> str: + """Get default value for non-optional types""" + if "String" in type_name: + return "\"\"" + elif "Int" in type_name: + return "0" + elif "Double" in type_name: + return "0.0" + elif "Bool" in type_name: + return "false" + elif "Date" in type_name: + return "Date()" + else: + return "nil" # For any other type + + def get_output_filename(self, model_name: str) -> str: + """Generate the output filename for a model.""" + return f"Base{model_name}.swift" + + def make_resource_name(self, text): + p = inflect.engine() + # Split camelCase into words + words = re.findall('[A-Z][^A-Z]*', text) + + if not words: + words = [text] + + words = [word.lower() for word in words] + words[-1] = p.plural(words[-1]) + return '-'.join(words) + +def process_directory(input_dir: str, output_dir: str, logger: logging.Logger, dry_run: bool = False) -> int: + """Process all JSON files in the input directory and generate Swift models.""" + try: + input_path = validate_directory(input_dir) + if not dry_run: + output_path = validate_directory(output_dir, create=True) + + json_files = list(input_path.glob("*.json")) + if not json_files: + logger.warning(f"No JSON files found in '{input_dir}'") + return 0 + + logger.info(f"Found {len(json_files)} JSON files to process") + successful_files = 0 + + for json_file in json_files: + try: + with open(json_file, 'r') as f: + json_data = json.load(f) + + generator = SwiftModelGenerator(json_data) + + # Generate each model in the JSON file + for model in json_data["models"]: + model_name = model["name"] + swift_code = generator.generate_model(model) + + if dry_run: + logger.info(f"Would generate Base{model_name}.swift") + continue + + # Write to output file with Base prefix + output_file = output_path / generator.get_output_filename(model_name) + with open(output_file, 'w') as f: + f.write(swift_code) + + logger.info(f"Generated Base{model_name}.swift") + + successful_files += 1 + + except json.JSONDecodeError as e: + logger.error(f"Error parsing {json_file.name}: {e}") + except KeyError as e: + logger.error(f"Missing required key in {json_file.name}: {e}") + except Exception as e: + logger.error(f"Error processing {json_file.name}: {e}") + + return successful_files + + except Exception as e: + logger.error(f"Fatal error: {e}") + return 0 + + +def setup_logging(verbose: bool) -> logging.Logger: + """Configure logging based on verbosity level.""" + logger = logging.getLogger('SwiftModelGenerator') + handler = logging.StreamHandler() + + if verbose: + logger.setLevel(logging.DEBUG) + handler.setFormatter(logging.Formatter( + '%(asctime)s - %(levelname)s - %(message)s' + )) + else: + logger.setLevel(logging.INFO) + handler.setFormatter(logging.Formatter('%(message)s')) + + logger.addHandler(handler) + return logger + +def validate_directory(path: str, create: bool = False) -> Path: + """Validate and optionally create a directory.""" + dir_path = Path(path) + if dir_path.exists(): + if not dir_path.is_dir(): + raise ValueError(f"'{path}' exists but is not a directory") + elif create: + dir_path.mkdir(parents=True) + else: + raise ValueError(f"Directory '{path}' does not exist") + return dir_path + +def main(): + parser = argparse.ArgumentParser( + description="Generate Swift model classes from JSON definitions", + epilog="Example: %(prog)s -i ./model_definitions -o ../MyProject/Models" + ) + + parser.add_argument( + "-i", "--input-dir", + required=True, + help="Directory containing JSON model definitions" + ) + + parser.add_argument( + "-o", "--output-dir", + required=True, + help="Directory where Swift files will be generated" + ) + + parser.add_argument( + "-v", "--verbose", + action="store_true", + help="Enable verbose output" + ) + + parser.add_argument( + "--dry-run", + action="store_true", + help="Show what would be generated without actually creating files" + ) + + parser.add_argument( + "--version", + action="version", + version="%(prog)s 1.0.0" + ) + + args = parser.parse_args() + + # Setup logging + logger = setup_logging(args.verbose) + + # Process the directories + start_time = datetime.now() + logger.info("Starting model generation...") + + successful_files = process_directory( + args.input_dir, + args.output_dir, + logger, + args.dry_run + ) + + # Report results + duration = datetime.now() - start_time + logger.info(f"\nGeneration completed in {duration.total_seconds():.2f} seconds") + logger.info(f"Successfully processed {successful_files} files") + + # Return appropriate exit code + sys.exit(0 if successful_files > 0 else 1) + +if __name__ == "__main__": + main() diff --git a/PadelClub/Data/GroupStage.swift b/PadelClub/Data/GroupStage.swift index b60f139..cd2d346 100644 --- a/PadelClub/Data/GroupStage.swift +++ b/PadelClub/Data/GroupStage.swift @@ -11,20 +11,7 @@ import Algorithms import SwiftUI @Observable -final class GroupStage: ModelObject, Storable { - static func resourceName() -> String { "group-stages" } - static func tokenExemptedMethods() -> [HTTPMethod] { return [] } - static func filterByStoreIdentifier() -> Bool { return true } - static var relationshipNames: [String] = [] - - var id: String = Store.randomId() - var tournament: String - var index: Int - var size: Int - private var format: MatchFormat? - var startDate: Date? - var name: String? - var step: Int = 0 +final class GroupStage: BaseGroupStage, SideStorable { var matchFormat: MatchFormat { get { @@ -35,24 +22,15 @@ final class GroupStage: ModelObject, Storable { } } - internal init(tournament: String, index: Int, size: Int, matchFormat: MatchFormat? = nil, startDate: Date? = nil, name: String? = nil, step: Int = 0) { - self.tournament = tournament - self.index = index - self.size = size - self.format = matchFormat - self.startDate = startDate - self.name = name - self.step = step - } - - var tournamentStore: TournamentStore { - return TournamentStore.instance(tournamentId: self.tournament) + var tournamentStore: TournamentStore? { + return TournamentLibrary.shared.store(tournamentId: self.tournament) } // MARK: - Computed dependencies func _matches() -> [Match] { - return self.tournamentStore.matches.filter { $0.groupStage == self.id }.sorted(by: \.index) + guard let tournamentStore = self.tournamentStore else { return [] } + return tournamentStore.matches.filter { $0.groupStage == self.id }.sorted(by: \.index) // Store.main.filter { $0.groupStage == self.id } } @@ -107,12 +85,12 @@ final class GroupStage: ModelObject, Storable { } fileprivate func _createMatch(index: Int) -> Match { - let match: Match = Match(groupStage: self.id, + let match: Match = Match(groupStage: self.id, index: index, - matchFormat: self.matchFormat, + format: self.matchFormat, name: self.localizedMatchUpLabel(for: index)) match.store = self.store - print("_createMatch(index)", index) +// print("_createMatch(index)", index) return match } @@ -124,7 +102,7 @@ final class GroupStage: ModelObject, Storable { returnMatches = returnMatches.filter({ $0.index >= matchCount * matchPhaseCount }) } do { - try self.tournamentStore.matches.delete(contentOfs: returnMatches) + try self.tournamentStore?.matches.delete(contentOfs: returnMatches) } catch { Logger.error(error) } @@ -150,12 +128,8 @@ final class GroupStage: ModelObject, Storable { matches.append(newMatch) } - do { - try self.tournamentStore.matches.addOrUpdate(contentOfs: matches) - try self.tournamentStore.teamScores.addOrUpdate(contentOfs: teamScores) - } catch { - Logger.error(error) - } + self.tournamentStore?.matches.addOrUpdate(contentOfs: matches) + self.tournamentStore?.teamScores.addOrUpdate(contentOfs: teamScores) } func buildMatches(keepExistingMatches: Bool = false) { @@ -180,8 +154,8 @@ final class GroupStage: ModelObject, Storable { } do { - try self.tournamentStore.matches.addOrUpdate(contentOfs: matches) - try self.tournamentStore.teamScores.addOrUpdate(contentOfs: teamScores) + try self.tournamentStore?.matches.addOrUpdate(contentOfs: matches) + try self.tournamentStore?.teamScores.addOrUpdate(contentOfs: teamScores) } catch { Logger.error(error) } @@ -211,21 +185,19 @@ final class GroupStage: ModelObject, Storable { clearScoreCache() if hasEnded(), let tournament = tournamentObject() { - do { - let teams = teams(true) - for (index, team) in teams.enumerated() { - team.qualified = index < tournament.qualifiedPerGroupStage - if team.bracketPosition != nil && team.qualified == false { - tournamentObject()?.shouldVerifyBracket = true - } - } - try self.tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teams) - if let tournamentObject = tournamentObject() { - try DataStore.shared.tournaments.addOrUpdate(instance: tournamentObject) + + let teams = teams(true) + for (index, team) in teams.enumerated() { + team.qualified = index < tournament.qualifiedPerGroupStage + if team.bracketPosition != nil && team.qualified == false { + tournamentObject()?.shouldVerifyBracket = true } - } catch { - Logger.error(error) } + try self.tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: teams) + if let tournamentObject = tournamentObject() { + try DataStore.shared.tournaments.addOrUpdate(instance: tournamentObject) + } + self.tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: teams) let groupStagesAreOverAtFirstStep = tournament.groupStagesAreOver(atStep: 0) let nextStepGroupStages = tournament.groupStages(atStep: 1) @@ -233,11 +205,7 @@ final class GroupStage: ModelObject, Storable { if groupStagesAreOverAtFirstStep, nextStepGroupStages.isEmpty || groupStagesAreOverAtSecondStep == true, tournament.groupStageLoserBracketAreOver(), tournament.rounds().isEmpty { tournament.endDate = Date() - do { - try DataStore.shared.tournaments.addOrUpdate(instance: tournament) - } catch { - Logger.error(error) - } + DataStore.shared.tournaments.addOrUpdate(instance: tournament) } tournament.updateTournamentState() @@ -423,11 +391,7 @@ final class GroupStage: ModelObject, Storable { } private func _removeMatches() { - do { - try self.tournamentStore.matches.delete(contentOfs: _matches()) - } catch { - Logger.error(error) - } + self.tournamentStore?.matches.delete(contentOfs: _matches()) } private func _numberOfMatchesToBuild() -> Int { @@ -483,10 +447,11 @@ final class GroupStage: ModelObject, Storable { } func unsortedTeams() -> [TeamRegistration] { + guard let tournamentStore = self.tournamentStore else { return [] } if step > 0 { - return self.tournamentStore.groupStages.filter({ $0.step == step - 1 }).compactMap({ $0.teams(true)[safe: index] }) + return tournamentStore.groupStages.filter({ $0.step == step - 1 }).compactMap({ $0.teams(true)[safe: index] }) } - return self.tournamentStore.teamRegistrations.filter { $0.groupStage == self.id && $0.groupStagePosition != nil } + return tournamentStore.teamRegistrations.filter { $0.groupStage == self.id && $0.groupStagePosition != nil } } @@ -613,11 +578,7 @@ final class GroupStage: ModelObject, Storable { playedMatches.forEach { match in match.matchFormat = matchFormat } - do { - try self.tournamentStore.matches.addOrUpdate(contentOfs: playedMatches) - } catch { - Logger.error(error) - } + self.tournamentStore?.matches.addOrUpdate(contentOfs: playedMatches) } func pasteData() -> String { @@ -635,42 +596,46 @@ final class GroupStage: ModelObject, Storable { return teams(true).firstIndex(of: team) } - override func deleteDependencies() throws { + override func deleteDependencies() { let matches = self._matches() for match in matches { - try match.deleteDependencies() - } - self.tournamentStore.matches.deleteDependencies(matches) - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - id = try container.decode(String.self, forKey: ._id) - tournament = try container.decode(String.self, forKey: ._tournament) - index = try container.decode(Int.self, forKey: ._index) - size = try container.decode(Int.self, forKey: ._size) - format = try container.decodeIfPresent(MatchFormat.self, forKey: ._format) - startDate = try container.decodeIfPresent(Date.self, forKey: ._startDate) - name = try container.decodeIfPresent(String.self, forKey: ._name) - step = try container.decodeIfPresent(Int.self, forKey: ._step) ?? 0 - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(id, forKey: ._id) - try container.encode(tournament, forKey: ._tournament) - try container.encode(index, forKey: ._index) - try container.encode(size, forKey: ._size) - try container.encode(format, forKey: ._format) - try container.encode(startDate, forKey: ._startDate) - try container.encode(name, forKey: ._name) - try container.encode(step, forKey: ._step) - } + match.deleteDependencies() + } + self.tournamentStore?.matches.deleteDependencies(matches) + } + +// init(from decoder: Decoder) throws { +// let container = try decoder.container(keyedBy: CodingKeys.self) +// +// id = try container.decode(String.self, forKey: ._id) +// storeId = try container.decode(String.self, forKey: ._storeId) +// lastUpdate = try container.decode(Date.self, forKey: ._lastUpdate) +// tournament = try container.decode(String.self, forKey: ._tournament) +// index = try container.decode(Int.self, forKey: ._index) +// size = try container.decode(Int.self, forKey: ._size) +// format = try container.decodeIfPresent(MatchFormat.self, forKey: ._format) +// startDate = try container.decodeIfPresent(Date.self, forKey: ._startDate) +// name = try container.decodeIfPresent(String.self, forKey: ._name) +// step = try container.decodeIfPresent(Int.self, forKey: ._step) ?? 0 +// } +// +// func encode(to encoder: Encoder) throws { +// var container = encoder.container(keyedBy: CodingKeys.self) +// +// try container.encode(id, forKey: ._id) +// try container.encode(storeId, forKey: ._storeId) +// try container.encode(lastUpdate, forKey: ._lastUpdate) +// try container.encode(tournament, forKey: ._tournament) +// try container.encode(index, forKey: ._index) +// try container.encode(size, forKey: ._size) +// try container.encode(format, forKey: ._format) +// try container.encode(startDate, forKey: ._startDate) +// try container.encode(name, forKey: ._name) +// try container.encode(step, forKey: ._step) +// } func insertOnServer() { - self.tournamentStore.groupStages.writeChangeAndInsertOnServer(instance: self) + self.tournamentStore?.groupStages.writeChangeAndInsertOnServer(instance: self) for match in self._matches() { match.insertOnServer() } @@ -681,6 +646,8 @@ final class GroupStage: ModelObject, Storable { extension GroupStage { enum CodingKeys: String, CodingKey { case _id = "id" + case _storeId = "storeId" + case _lastUpdate = "lastUpdate" case _tournament = "tournament" case _index = "index" case _size = "size" diff --git a/PadelClub/Data/Match.swift b/PadelClub/Data/Match.swift index f08ef76..5149a60 100644 --- a/PadelClub/Data/Match.swift +++ b/PadelClub/Data/Match.swift @@ -9,15 +9,11 @@ import Foundation import LeStorage @Observable -final class Match: ModelObject, Storable, Equatable { +final class Match: BaseMatch, SideStorable { + static func == (lhs: Match, rhs: Match) -> Bool { lhs.id == rhs.id && lhs.startDate == rhs.startDate } - - static func resourceName() -> String { "matches" } - static func tokenExemptedMethods() -> [HTTPMethod] { return [] } - static func filterByStoreIdentifier() -> Bool { return true } - static var relationshipNames: [String] = ["round", "groupStage"] static func setServerTitle(upperRound: Round, matchIndex: Int) -> String { if upperRound.index == 0 { return upperRound.roundTitle() } @@ -26,41 +22,14 @@ final class Match: ModelObject, Storable, Equatable { var byeState: Bool = false - var id: String = Store.randomId() - var round: String? - var groupStage: String? - var startDate: Date? - var endDate: Date? - var index: Int - private var format: MatchFormat? - //var court: String? - var servingTeamId: String? - var winningTeamId: String? - var losingTeamId: String? - //var broadcasted: Bool - var name: String? - //var order: Int - var disabled: Bool = false - private(set) var courtIndex: Int? - var confirmed: Bool = false - - init(round: String? = nil, groupStage: String? = nil, startDate: Date? = nil, endDate: Date? = nil, index: Int, matchFormat: MatchFormat? = nil, servingTeamId: String? = nil, winningTeamId: String? = nil, losingTeamId: String? = nil, name: String? = nil, disabled: Bool = false, courtIndex: Int? = nil, confirmed: Bool = false) { - self.round = round - self.groupStage = groupStage - self.startDate = startDate - self.endDate = endDate - self.index = index - self.format = matchFormat - //self.court = court - self.servingTeamId = servingTeamId - self.winningTeamId = winningTeamId - self.losingTeamId = losingTeamId - self.disabled = disabled - self.name = name - self.courtIndex = courtIndex - self.confirmed = confirmed -// self.broadcasted = broadcasted -// self.order = order + init(round: String? = nil, groupStage: String? = nil, startDate: Date? = nil, endDate: Date? = nil, index: Int, format: MatchFormat? = nil, servingTeamId: String? = nil, winningTeamId: String? = nil, losingTeamId: String? = nil, name: String? = nil, disabled: Bool = false, courtIndex: Int? = nil, confirmed: Bool = false) { + + super.init(round: round, groupStage: groupStage, startDate: startDate, endDate: endDate, index: index, format: format, servingTeamId: servingTeamId, winningTeamId: winningTeamId, losingTeamId: losingTeamId, name: name, disabled: disabled, courtIndex: courtIndex, confirmed: confirmed) + + } + + required init(from decoder: Decoder) throws { + try super.init(from: decoder) } func setMatchName(_ serverName: String?) { @@ -77,9 +46,9 @@ final class Match: ModelObject, Storable, Equatable { } } - var tournamentStore: TournamentStore { - if let store = self.store as? TournamentStore { - return store + var tournamentStore: TournamentStore? { + if let id = self.store?.identifier { + return TournamentLibrary.shared.store(tournamentId: id) } fatalError("missing store for \(String(describing: type(of: self)))") } @@ -91,20 +60,18 @@ final class Match: ModelObject, Storable, Equatable { // MARK: - Computed dependencies var teamScores: [TeamScore] { - return self.tournamentStore.teamScores.filter { $0.match == self.id } + guard let tournamentStore = self.tournamentStore else { return [] } + return tournamentStore.teamScores.filter { $0.match == self.id } } // MARK: - - override func deleteDependencies() throws { - guard let tournament = self.currentTournament() else { - return - } + override func deleteDependencies() { let teamScores = self.teamScores for teamScore in teamScores { - try teamScore.deleteDependencies() + teamScore.deleteDependencies() } - tournament.tournamentStore.teamScores.deleteDependencies(teamScores) + self.tournamentStore?.teamScores.deleteDependencies(teamScores) } func indexInRound(in matches: [Match]? = nil) -> Int { @@ -183,12 +150,12 @@ defer { func winner() -> TeamRegistration? { guard let winningTeamId else { return nil } - return self.tournamentStore.teamRegistrations.findById(winningTeamId) + return self.tournamentStore?.teamRegistrations.findById(winningTeamId) } func loser() -> TeamRegistration? { guard let losingTeamId else { return nil } - return self.tournamentStore.teamRegistrations.findById(losingTeamId) + return self.tournamentStore?.teamRegistrations.findById(losingTeamId) } @@ -217,11 +184,7 @@ defer { endDate = nil followingMatch()?.cleanScheduleAndSave(nil) _loserMatch()?.cleanScheduleAndSave(nil) - do { - try self.tournamentStore.matches.addOrUpdate(instance: self) - } catch { - Logger.error(error) - } + self.tournamentStore?.matches.addOrUpdate(instance: self) } func resetMatch() { @@ -237,22 +200,14 @@ defer { func resetScores() { teamScores.forEach({ $0.score = nil }) - do { - try self.tournamentStore.teamScores.addOrUpdate(contentOfs: teamScores) - } catch { - Logger.error(error) - } + self.tournamentStore?.teamScores.addOrUpdate(contentOfs: teamScores) } func teamWillBeWalkOut(_ team: TeamRegistration) { resetMatch() let existingTeamScore = teamScore(ofTeam: team) ?? TeamScore(match: id, team: team) existingTeamScore.walkOut = 1 - do { - try self.tournamentStore.teamScores.addOrUpdate(instance: existingTeamScore) - } catch { - Logger.error(error) - } + self.tournamentStore?.teamScores.addOrUpdate(instance: existingTeamScore) } func luckyLosers() -> [TeamRegistration] { @@ -270,19 +225,11 @@ defer { let position = matchIndex * 2 + teamPosition.rawValue let previousScores = teamScores.filter({ $0.luckyLoser == position }) - do { - try self.tournamentStore.teamScores.delete(contentOfs: previousScores) - } catch { - Logger.error(error) - } + self.tournamentStore?.teamScores.delete(contentOfs: previousScores) let teamScoreLuckyLoser = teamScore(ofTeam: team) ?? TeamScore(match: id, team: team) teamScoreLuckyLoser.luckyLoser = position - do { - try self.tournamentStore.teamScores.addOrUpdate(instance: teamScoreLuckyLoser) - } catch { - Logger.error(error) - } + self.tournamentStore?.teamScores.addOrUpdate(instance: teamScoreLuckyLoser) } func disableMatch() { @@ -317,14 +264,14 @@ defer { let isTopMatch = topMatchIndex + 1 == index let lookingForIndex = isTopMatch ? topMatchIndex : bottomMatchIndex - return self.tournamentStore.matches.first(where: { $0.round == round && $0.index == lookingForIndex }) + return self.tournamentStore?.matches.first(where: { $0.round == round && $0.index == lookingForIndex }) } private func _forwardMatch(inRound round: Round) -> Match? { guard let roundObjectNextRound = round.nextRound() else { return nil } let nextIndex = (index - 1) / 2 - return self.tournamentStore.matches.first(where: { $0.round == roundObjectNextRound.id && $0.index == nextIndex }) + return self.tournamentStore?.matches.first(where: { $0.round == roundObjectNextRound.id && $0.index == nextIndex }) } func _toggleForwardMatchDisableState(_ state: Bool) { @@ -389,9 +336,10 @@ defer { let currentState = disabled disabled = state + if disabled != currentState { do { - try self.tournamentStore.teamScores.delete(contentOfs: teamScores) + try self.tournamentStore?.teamScores.delete(contentOfs: teamScores) } catch { Logger.error(error) } @@ -401,11 +349,7 @@ defer { for team in teams { if isSeededBy(team: team) { team.bracketPosition = nil - do { - try self.tournamentStore.teamRegistrations.addOrUpdate(instance: team) - } catch { - Logger.error(error) - } + self.tournamentStore?.teamRegistrations.addOrUpdate(instance: team) } } } @@ -415,7 +359,7 @@ defer { roundObject?._cachedSeedInterval = nil name = nil do { - try self.tournamentStore.matches.addOrUpdate(instance: self) + try self.tournamentStore?.matches.addOrUpdate(instance: self) } catch { Logger.error(error) } @@ -433,7 +377,8 @@ defer { } func next() -> Match? { - let matches: [Match] = self.tournamentStore.matches.filter { $0.round == round && $0.index > index && $0.disabled == false } + guard let tournamentStore = self.tournamentStore else { return nil } + let matches: [Match] = tournamentStore.matches.filter { $0.round == round && $0.index > index && $0.disabled == false } return matches.sorted(by: \.index).first } @@ -443,7 +388,7 @@ defer { } func getFollowingMatch(fromNextRoundId nextRoundId: String) -> Match? { - return self.tournamentStore.matches.first(where: { $0.round == nextRoundId && $0.index == (index - 1) / 2 }) + return self.tournamentStore?.matches.first(where: { $0.round == nextRoundId && $0.index == (index - 1) / 2 }) } func getDuration() -> Int { @@ -476,7 +421,7 @@ defer { guard let roundObject else { return nil } let topPreviousRoundMatchIndex = topPreviousRoundMatchIndex() let roundObjectPreviousRoundId = roundObject.previousRound()?.id - return self.tournamentStore.matches.first(where: { match in + return self.tournamentStore?.matches.first(where: { match in match.round != nil && match.round == roundObjectPreviousRoundId && match.index == topPreviousRoundMatchIndex }) } @@ -485,7 +430,7 @@ defer { guard let roundObject else { return nil } let bottomPreviousRoundMatchIndex = bottomPreviousRoundMatchIndex() let roundObjectPreviousRoundId = roundObject.previousRound()?.id - return self.tournamentStore.matches.first(where: { match in + return self.tournamentStore?.matches.first(where: { match in match.round != nil && match.round == roundObjectPreviousRoundId && match.index == bottomPreviousRoundMatchIndex }) } @@ -522,8 +467,10 @@ defer { func previousMatches() -> [Match] { guard let roundObject else { return [] } + guard let tournamentStore = self.tournamentStore else { return [] } + let roundObjectPreviousRoundId = roundObject.previousRound()?.id - return self.tournamentStore.matches.filter { match in + return tournamentStore.matches.filter { match in match.round == roundObjectPreviousRoundId && (match.index == topPreviousRoundMatchIndex() || match.index == bottomPreviousRoundMatchIndex()) }.sorted(by: \.index) } @@ -545,7 +492,7 @@ defer { teamScoreWinning.walkOut = nil teamScoreWinning.score = matchFormat.defaultWalkOutScore(false).compactMap({ String($0) }).joined(separator: ",") do { - try self.tournamentStore.teamScores.addOrUpdate(contentOfs: [teamScoreWalkout, teamScoreWinning]) + try self.tournamentStore?.teamScores.addOrUpdate(contentOfs: [teamScoreWalkout, teamScoreWinning]) } catch { Logger.error(error) } @@ -613,7 +560,7 @@ defer { } do { - try self.tournamentStore.teamScores.addOrUpdate(contentOfs: [teamScoreOne, teamScoreTwo]) + try self.tournamentStore?.teamScores.addOrUpdate(contentOfs: [teamScoreOne, teamScoreTwo]) } catch { Logger.error(error) } @@ -629,11 +576,7 @@ defer { let ids = newTeamScores.map { $0.id } let teamScores = teamScores.filter({ ids.contains($0.id) == false }) if teamScores.isEmpty == false { - do { - try self.tournamentStore.teamScores.delete(contentOfs: teamScores) - } catch { - Logger.error(error) - } + self.tournamentStore?.teamScores.delete(contentOfs: teamScores) followingMatch()?.resetTeamScores(outsideOf: []) _loserMatch()?.resetTeamScores(outsideOf: []) } @@ -655,11 +598,8 @@ defer { func updateTeamScores() { let teams = getOrCreateTeamScores() - do { - try self.tournamentStore.teamScores.addOrUpdate(contentOfs: teams) - } catch { - Logger.error(error) - } + + self.tournamentStore?.teamScores.addOrUpdate(contentOfs: teams) resetTeamScores(outsideOf: teams) if teams.isEmpty == false { updateFollowingMatchTeamScore() @@ -813,7 +753,8 @@ defer { } func scores() -> [TeamScore] { - return self.tournamentStore.teamScores.filter { $0.match == id } + guard let tournamentStore = self.tournamentStore else { return [] } + return tournamentStore.teamScores.filter { $0.match == id } } func teams() -> [TeamRegistration] { @@ -945,12 +886,12 @@ defer { var roundObject: Round? { guard let round else { return nil } - return self.tournamentStore.rounds.findById(round) + return self.tournamentStore?.rounds.findById(round) } var groupStageObject: GroupStage? { guard let groupStage else { return nil } - return self.tournamentStore.groupStages.findById(groupStage) + return self.tournamentStore?.groupStages.findById(groupStage) } var isLoserBracket: Bool { @@ -1077,54 +1018,15 @@ defer { func ancestors() -> [Match] { previousMatches() + loserMatches() } - + func matchSpots() -> [MatchSpot] { [MatchSpot(match: self, teamPosition: .one), MatchSpot(match: self, teamPosition: .two)] } - - enum CodingKeys: String, CodingKey { - case _id = "id" - case _round = "round" - case _groupStage = "groupStage" - case _startDate = "startDate" - case _endDate = "endDate" - case _index = "index" - case _format = "format" -// case _court = "court" - case _courtIndex = "courtIndex" - case _servingTeamId = "servingTeamId" - case _winningTeamId = "winningTeamId" - case _losingTeamId = "losingTeamId" -// case _broadcasted = "broadcasted" - case _name = "name" -// case _order = "order" - case _disabled = "disabled" - case _confirmed = "confirmed" - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(id, forKey: ._id) - try container.encode(round, forKey: ._round) - try container.encode(groupStage, forKey: ._groupStage) - try container.encode(startDate, forKey: ._startDate) - try container.encode(endDate, forKey: ._endDate) - try container.encode(format, forKey: ._format) - try container.encode(servingTeamId, forKey: ._servingTeamId) - try container.encode(index, forKey: ._index) - try container.encode(winningTeamId, forKey: ._winningTeamId) - try container.encode(losingTeamId, forKey: ._losingTeamId) - try container.encode(name, forKey: ._name) - try container.encode(disabled, forKey: ._disabled) - try container.encode(courtIndex, forKey: ._courtIndex) - try container.encode(confirmed, forKey: ._confirmed) - } - + func insertOnServer() { - self.tournamentStore.matches.writeChangeAndInsertOnServer(instance: self) + self.tournamentStore?.matches.writeChangeAndInsertOnServer(instance: self) for teamScore in self.teamScores { - try teamScore.insertOnServer() + teamScore.insertOnServer() } } diff --git a/PadelClub/Data/MatchScheduler.swift b/PadelClub/Data/MatchScheduler.swift index 11865c2..11e4705 100644 --- a/PadelClub/Data/MatchScheduler.swift +++ b/PadelClub/Data/MatchScheduler.swift @@ -10,81 +10,45 @@ import LeStorage import SwiftUI @Observable -final class MatchScheduler : ModelObject, Storable { +final class MatchScheduler: BaseMatchScheduler, SideStorable { +// +// init(tournament: String, +// timeDifferenceLimit: Int = 5, +// loserBracketRotationDifference: Int = 0, +// upperBracketRotationDifference: Int = 1, +// accountUpperBracketBreakTime: Bool = true, +// accountLoserBracketBreakTime: Bool = false, +// randomizeCourts: Bool = true, +// rotationDifferenceIsImportant: Bool = false, +// shouldHandleUpperRoundSlice: Bool = false, +// shouldEndRoundBeforeStartingNext: Bool = true, +//<<<<<<< HEAD +// groupStageChunkCount: Int? = nil, overrideCourtsUnavailability: Bool = false, shouldTryToFillUpCourtsAvailable: Bool = false) { +// super.init() +//======= +// groupStageChunkCount: Int? = nil, +// overrideCourtsUnavailability: Bool = false, +// shouldTryToFillUpCourtsAvailable: Bool = true, +// courtsAvailable: Set = Set(), +// simultaneousStart: Bool = true) { +//>>>>>>> main +// self.tournament = tournament +// self.timeDifferenceLimit = timeDifferenceLimit +// self.loserBracketRotationDifference = loserBracketRotationDifference +// self.upperBracketRotationDifference = upperBracketRotationDifference +// self.accountUpperBracketBreakTime = accountUpperBracketBreakTime +// self.accountLoserBracketBreakTime = accountLoserBracketBreakTime +// self.randomizeCourts = randomizeCourts +// self.rotationDifferenceIsImportant = rotationDifferenceIsImportant +// self.shouldHandleUpperRoundSlice = shouldHandleUpperRoundSlice +// self.shouldEndRoundBeforeStartingNext = shouldEndRoundBeforeStartingNext +// self.groupStageChunkCount = groupStageChunkCount +// self.overrideCourtsUnavailability = overrideCourtsUnavailability +// self.shouldTryToFillUpCourtsAvailable = shouldTryToFillUpCourtsAvailable +// self.courtsAvailable = courtsAvailable +// self.simultaneousStart = simultaneousStart +// } - static func resourceName() -> String { return "match-scheduler" } - static func tokenExemptedMethods() -> [HTTPMethod] { return [] } - static func filterByStoreIdentifier() -> Bool { return false } - static var relationshipNames: [String] = [] - - private(set) var id: String = Store.randomId() - var tournament: String - var timeDifferenceLimit: Int - var loserBracketRotationDifference: Int - var upperBracketRotationDifference: Int - var accountUpperBracketBreakTime: Bool - var accountLoserBracketBreakTime: Bool - var randomizeCourts: Bool - var rotationDifferenceIsImportant: Bool - var shouldHandleUpperRoundSlice: Bool - var shouldEndRoundBeforeStartingNext: Bool - var groupStageChunkCount: Int? - var overrideCourtsUnavailability: Bool = false - var shouldTryToFillUpCourtsAvailable: Bool = true - var courtsAvailable: Set = Set() - var simultaneousStart: Bool = true - - init(tournament: String, - timeDifferenceLimit: Int = 5, - loserBracketRotationDifference: Int = 0, - upperBracketRotationDifference: Int = 1, - accountUpperBracketBreakTime: Bool = true, - accountLoserBracketBreakTime: Bool = false, - randomizeCourts: Bool = true, - rotationDifferenceIsImportant: Bool = false, - shouldHandleUpperRoundSlice: Bool = false, - shouldEndRoundBeforeStartingNext: Bool = true, - groupStageChunkCount: Int? = nil, - overrideCourtsUnavailability: Bool = false, - shouldTryToFillUpCourtsAvailable: Bool = true, - courtsAvailable: Set = Set(), - simultaneousStart: Bool = true) { - self.tournament = tournament - self.timeDifferenceLimit = timeDifferenceLimit - self.loserBracketRotationDifference = loserBracketRotationDifference - self.upperBracketRotationDifference = upperBracketRotationDifference - self.accountUpperBracketBreakTime = accountUpperBracketBreakTime - self.accountLoserBracketBreakTime = accountLoserBracketBreakTime - self.randomizeCourts = randomizeCourts - self.rotationDifferenceIsImportant = rotationDifferenceIsImportant - self.shouldHandleUpperRoundSlice = shouldHandleUpperRoundSlice - self.shouldEndRoundBeforeStartingNext = shouldEndRoundBeforeStartingNext - self.groupStageChunkCount = groupStageChunkCount - self.overrideCourtsUnavailability = overrideCourtsUnavailability - self.shouldTryToFillUpCourtsAvailable = shouldTryToFillUpCourtsAvailable - self.courtsAvailable = courtsAvailable - self.simultaneousStart = simultaneousStart - } - - enum CodingKeys: String, CodingKey { - case _id = "id" - case _tournament = "tournament" - case _timeDifferenceLimit = "timeDifferenceLimit" - case _loserBracketRotationDifference = "loserBracketRotationDifference" - case _upperBracketRotationDifference = "upperBracketRotationDifference" - case _accountUpperBracketBreakTime = "accountUpperBracketBreakTime" - case _accountLoserBracketBreakTime = "accountLoserBracketBreakTime" - case _randomizeCourts = "randomizeCourts" - case _rotationDifferenceIsImportant = "rotationDifferenceIsImportant" - case _shouldHandleUpperRoundSlice = "shouldHandleUpperRoundSlice" - case _shouldEndRoundBeforeStartingNext = "shouldEndRoundBeforeStartingNext" - case _groupStageChunkCount = "groupStageChunkCount" - case _overrideCourtsUnavailability = "overrideCourtsUnavailability" - case _shouldTryToFillUpCourtsAvailable = "shouldTryToFillUpCourtsAvailable" - case _courtsAvailable = "courtsAvailable" - case _simultaneousStart = "simultaneousStart" - } - var courtsUnavailability: [DateInterval]? { guard let event = tournamentObject()?.eventObject() else { return nil } return event.courtsUnavailability + (overrideCourtsUnavailability ? [] : event.tournamentsCourtsUsed(exluding: tournament)) @@ -94,8 +58,9 @@ final class MatchScheduler : ModelObject, Storable { return tournamentObject()?.additionalEstimationDuration ?? 0 } - var tournamentStore: TournamentStore { - return TournamentStore.instance(tournamentId: self.tournament) + var tournamentStore: TournamentStore? { + return TournamentLibrary.shared.store(tournamentId: self.tournament) +// TournamentStore.instance(tournamentId: self.tournament) } func tournamentObject() -> Tournament? { @@ -155,7 +120,7 @@ final class MatchScheduler : ModelObject, Storable { groupStages.filter({ $0.startDate == nil || times.contains($0.startDate!) == false }).chunked(into: computedGroupStageChunkCount).forEach { groups in groups.forEach({ $0.startDate = lastDate }) do { - try self.tournamentStore.groupStages.addOrUpdate(contentOfs: groups) + try self.tournamentStore?.groupStages.addOrUpdate(contentOfs: groups) } catch { Logger.error(error) } @@ -176,7 +141,7 @@ final class MatchScheduler : ModelObject, Storable { } } do { - try self.tournamentStore.matches.addOrUpdate(contentOfs: matches) + try self.tournamentStore?.matches.addOrUpdate(contentOfs: matches) } catch { Logger.error(error) } @@ -770,7 +735,7 @@ final class MatchScheduler : ModelObject, Storable { } do { - try self.tournamentStore.matches.addOrUpdate(contentOfs: allMatches) + try self.tournamentStore?.matches.addOrUpdate(contentOfs: allMatches) } catch { Logger.error(error) } diff --git a/PadelClub/Data/MonthData.swift b/PadelClub/Data/MonthData.swift index 676ccd6..78bed5a 100644 --- a/PadelClub/Data/MonthData.swift +++ b/PadelClub/Data/MonthData.swift @@ -10,44 +10,16 @@ import SwiftUI import LeStorage @Observable -final class MonthData : ModelObject, Storable { - - static func resourceName() -> String { return "month-data" } - static func tokenExemptedMethods() -> [HTTPMethod] { return [] } - static func filterByStoreIdentifier() -> Bool { return false } - static var relationshipNames: [String] = [] - - private(set) var id: String = Store.randomId() - private(set) var monthKey: String - private(set) var creationDate: Date - var maleUnrankedValue: Int? = nil - var femaleUnrankedValue: Int? = nil - var maleCount: Int? = nil - var femaleCount: Int? = nil - var anonymousCount: Int? = nil - var incompleteMode: Bool = false - var dataModelIdentifier: String? - var fileModelIdentifier: String? +final class MonthData: BaseMonthData { init(monthKey: String) { + super.init() self.monthKey = monthKey self.creationDate = Date() } required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - id = try container.decode(String.self, forKey: ._id) - monthKey = try container.decode(String.self, forKey: ._monthKey) - creationDate = try container.decode(Date.self, forKey: ._creationDate) - maleUnrankedValue = try container.decodeIfPresent(Int.self, forKey: ._maleUnrankedValue) - femaleUnrankedValue = try container.decodeIfPresent(Int.self, forKey: ._femaleUnrankedValue) - maleCount = try container.decodeIfPresent(Int.self, forKey: ._maleCount) - femaleCount = try container.decodeIfPresent(Int.self, forKey: ._femaleCount) - anonymousCount = try container.decodeIfPresent(Int.self, forKey: ._anonymousCount) - incompleteMode = try container.decodeIfPresent(Bool.self, forKey: ._incompleteMode) ?? false - dataModelIdentifier = try container.decodeIfPresent(String.self, forKey: ._dataModelIdentifier) ?? nil - fileModelIdentifier = try container.decodeIfPresent(String.self, forKey: ._fileModelIdentifier) ?? nil - + try super.init(from: decoder) } func total() -> Int { @@ -77,28 +49,9 @@ final class MonthData : ModelObject, Storable { currentMonthData.femaleUnrankedValue = lastDataSourceFemaleUnranked?.0 currentMonthData.femaleCount = lastDataSourceFemaleUnranked?.1 currentMonthData.anonymousCount = anonymousCount - do { - try DataStore.shared.monthData.addOrUpdate(instance: currentMonthData) - } catch { - Logger.error(error) - } + DataStore.shared.monthData.addOrUpdate(instance: currentMonthData) + } } - - override func deleteDependencies() throws { - } - enum CodingKeys: String, CodingKey { - case _id = "id" - case _monthKey = "monthKey" - case _creationDate = "creationDate" - case _maleUnrankedValue = "maleUnrankedValue" - case _femaleUnrankedValue = "femaleUnrankedValue" - case _maleCount = "maleCount" - case _femaleCount = "femaleCount" - case _anonymousCount = "anonymousCount" - case _incompleteMode = "incompleteMode" - case _dataModelIdentifier = "dataModelIdentifier" - case _fileModelIdentifier = "fileModelIdentifier" - } } diff --git a/PadelClub/Data/PlayerPaymentType.swift b/PadelClub/Data/PlayerPaymentType.swift new file mode 100644 index 0000000..4ce8325 --- /dev/null +++ b/PadelClub/Data/PlayerPaymentType.swift @@ -0,0 +1,53 @@ +// +// PlayerPaymentType.swift +// PadelClub +// +// Created by Laurent Morvillier on 11/02/2025. +// + +import Foundation + +enum PlayerPaymentType: Int, CaseIterable, Identifiable, Codable { + + init?(rawValue: Int?) { + guard let value = rawValue else { return nil } + self.init(rawValue: value) + } + + var id: Self { + self + } + + case cash = 0 + case lydia = 1 + case gift = 2 + case check = 3 + case paylib = 4 + case bankTransfer = 5 + case clubHouse = 6 + case creditCard = 7 + case forfeit = 8 + + func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { + switch self { + case .check: + return "Chèque" + case .cash: + return "Cash" + case .lydia: + return "Lydia" + case .paylib: + return "Paylib" + case .bankTransfer: + return "Virement" + case .clubHouse: + return "Clubhouse" + case .creditCard: + return "CB" + case .forfeit: + return "Forfait" + case .gift: + return "Offert" + } + } +} diff --git a/PadelClub/Data/PlayerRegistration.swift b/PadelClub/Data/PlayerRegistration.swift index 31ed7a1..3717437 100644 --- a/PadelClub/Data/PlayerRegistration.swift +++ b/PadelClub/Data/PlayerRegistration.swift @@ -9,39 +9,8 @@ import Foundation import LeStorage @Observable -final class PlayerRegistration: ModelObject, Storable { - static func resourceName() -> String { "player-registrations" } - static func tokenExemptedMethods() -> [HTTPMethod] { return [] } - static func filterByStoreIdentifier() -> Bool { return true } - static var relationshipNames: [String] = ["teamRegistration"] +final class PlayerRegistration: BasePlayerRegistration, SideStorable { - var id: String = Store.randomId() - var teamRegistration: String? - var firstName: String - var lastName: String - var licenceId: String? - var rank: Int? - var paymentType: PlayerPaymentType? - var sex: PlayerSexType? - - var tournamentPlayed: Int? - var points: Double? - var clubName: String? - var ligueName: String? - var assimilation: String? - - var phoneNumber: String? - var email: String? - var birthdate: String? - - var computedRank: Int = 0 - var source: PlayerDataSource? - - var hasArrived: Bool = false - var coach: Bool = false - var captain: Bool = false - var registeredOnline: Bool = false - func localizedSourceLabel() -> String { switch source { case .frenchFederation: @@ -57,8 +26,8 @@ final class PlayerRegistration: ModelObject, Storable { } } - - init(teamRegistration: String? = nil, firstName: String, lastName: String, licenceId: String? = nil, rank: Int? = nil, paymentType: PlayerPaymentType? = nil, sex: PlayerSexType? = nil, tournamentPlayed: Int? = nil, points: Double? = nil, clubName: String? = nil, ligueName: String? = nil, assimilation: String? = nil, phoneNumber: String? = nil, email: String? = nil, birthdate: String? = nil, computedRank: Int = 0, source: PlayerDataSource? = nil, hasArrived: Bool = false, coach: Bool = false, captain: Bool = false, registeredOnline: Bool = false) { + init(teamRegistration: String? = nil, firstName: String, lastName: String, licenceId: String? = nil, rank: Int? = nil, paymentType: PlayerPaymentType? = nil, sex: PlayerSexType? = nil, tournamentPlayed: Int? = nil, points: Double? = nil, clubName: String? = nil, ligueName: String? = nil, assimilation: String? = nil, phoneNumber: String? = nil, email: String? = nil, birthdate: String? = nil, computedRank: Int = 0, source: PlayerRegistration.PlayerDataSource? = nil, hasArrived: Bool = false) { + super.init() self.teamRegistration = teamRegistration self.firstName = firstName self.lastName = lastName @@ -77,12 +46,10 @@ final class PlayerRegistration: ModelObject, Storable { self.computedRank = computedRank self.source = source self.hasArrived = hasArrived - self.captain = captain - self.coach = coach - self.registeredOnline = registeredOnline } internal init(importedPlayer: ImportedPlayer) { + super.init() self.teamRegistration = "" self.firstName = (importedPlayer.firstName ?? "").prefixTrimmed(50).capitalized self.lastName = (importedPlayer.lastName ?? "").prefixTrimmed(50).uppercased() @@ -99,6 +66,7 @@ final class PlayerRegistration: ModelObject, Storable { } internal init?(federalData: [String], sex: Int, sexUnknown: Bool) { + super.init() let _lastName = federalData[0].trimmed.uppercased() let _firstName = federalData[1].trimmed.capitalized if _lastName.isEmpty && _firstName.isEmpty { return nil } @@ -136,11 +104,18 @@ final class PlayerRegistration: ModelObject, Storable { } } - var tournamentStore: TournamentStore { - if let store = self.store as? TournamentStore { - return store + required init(from decoder: any Decoder) throws { + try super.init(from: decoder) + } + + var tournamentStore: TournamentStore? { + guard let storeId else { + fatalError("missing store id for \(String(describing: type(of: self)))") } - fatalError("missing store for \(String(describing: type(of: self)))") + return TournamentLibrary.shared.store(tournamentId: storeId) +// if let store = self.store as? TournamentStore { +// return store +// } } var computedAge: Int? { @@ -207,7 +182,7 @@ final class PlayerRegistration: ModelObject, Storable { func team() -> TeamRegistration? { guard let teamRegistration else { return nil } - return self.tournamentStore.teamRegistrations.findById(teamRegistration) + return self.tournamentStore?.teamRegistrations.findById(teamRegistration) } func isHere() -> Bool { @@ -393,154 +368,11 @@ final class PlayerRegistration: ModelObject, Storable { return false } - enum CodingKeys: String, CodingKey { - case _id = "id" - case _teamRegistration = "teamRegistration" - case _firstName = "firstName" - case _lastName = "lastName" - case _licenceId = "licenceId" - case _rank = "rank" - case _paymentType = "paymentType" - case _sex = "sex" - case _tournamentPlayed = "tournamentPlayed" - case _points = "points" - case _clubName = "clubName" - case _ligueName = "ligueName" - case _assimilation = "assimilation" - case _birthdate = "birthdate" - case _phoneNumber = "phoneNumber" - case _email = "email" - case _computedRank = "computedRank" - case _source = "source" - case _hasArrived = "hasArrived" - case _coach = "coach" - case _captain = "captain" - case _registeredOnline = "registeredOnline" - - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - // Non-optional properties - id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() - firstName = try container.decode(String.self, forKey: ._firstName) - lastName = try container.decode(String.self, forKey: ._lastName) - computedRank = try container.decodeIfPresent(Int.self, forKey: ._computedRank) ?? 0 - hasArrived = try container.decodeIfPresent(Bool.self, forKey: ._hasArrived) ?? false - coach = try container.decodeIfPresent(Bool.self, forKey: ._coach) ?? false - captain = try container.decodeIfPresent(Bool.self, forKey: ._captain) ?? false - - // Optional properties - teamRegistration = try container.decodeIfPresent(String.self, forKey: ._teamRegistration) - licenceId = try container.decodeIfPresent(String.self, forKey: ._licenceId) - rank = try container.decodeIfPresent(Int.self, forKey: ._rank) - paymentType = try container.decodeIfPresent(PlayerPaymentType.self, forKey: ._paymentType) - sex = try container.decodeIfPresent(PlayerSexType.self, forKey: ._sex) - tournamentPlayed = try container.decodeIfPresent(Int.self, forKey: ._tournamentPlayed) - points = try container.decodeIfPresent(Double.self, forKey: ._points) - clubName = try container.decodeIfPresent(String.self, forKey: ._clubName) - ligueName = try container.decodeIfPresent(String.self, forKey: ._ligueName) - assimilation = try container.decodeIfPresent(String.self, forKey: ._assimilation) - phoneNumber = try container.decodeIfPresent(String.self, forKey: ._phoneNumber) - email = try container.decodeIfPresent(String.self, forKey: ._email) - birthdate = try container.decodeIfPresent(String.self, forKey: ._birthdate) - source = try container.decodeIfPresent(PlayerDataSource.self, forKey: ._source) - registeredOnline = try container.decodeIfPresent(Bool.self, forKey: ._registeredOnline) ?? false - - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(id, forKey: ._id) - try container.encode(teamRegistration, forKey: ._teamRegistration) - - try container.encode(firstName, forKey: ._firstName) - try container.encode(lastName, forKey: ._lastName) - try container.encode(licenceId, forKey: ._licenceId) - try container.encode(rank, forKey: ._rank) - try container.encode(paymentType, forKey: ._paymentType) - try container.encode(sex, forKey: ._sex) - try container.encode(tournamentPlayed, forKey: ._tournamentPlayed) - try container.encode(points, forKey: ._points) - try container.encode(clubName, forKey: ._clubName) - try container.encode(ligueName, forKey: ._ligueName) - try container.encode(assimilation, forKey: ._assimilation) - try container.encode(phoneNumber, forKey: ._phoneNumber) - try container.encode(email, forKey: ._email) - try container.encode(birthdate, forKey: ._birthdate) - try container.encode(computedRank, forKey: ._computedRank) - try container.encode(source, forKey: ._source) - try container.encode(hasArrived, forKey: ._hasArrived) - try container.encode(captain, forKey: ._captain) - try container.encode(coach, forKey: ._coach) - try container.encode(registeredOnline, forKey: ._registeredOnline) - } - enum PlayerDataSource: Int, Codable { case frenchFederation = 0 case beachPadel = 1 } - enum PlayerSexType: Int, Hashable, CaseIterable, Identifiable, Codable { - init?(rawValue: Int?) { - guard let value = rawValue else { return nil } - self.init(rawValue: value) - } - - var id: Self { - self - } - - case female = 0 - case male = 1 - } - - enum PlayerPaymentType: Int, CaseIterable, Identifiable, Codable { - init?(rawValue: Int?) { - guard let value = rawValue else { return nil } - self.init(rawValue: value) - } - - var id: Self { - self - } - - case cash = 0 - case lydia = 1 - case gift = 2 - case check = 3 - case paylib = 4 - case bankTransfer = 5 - case clubHouse = 6 - case creditCard = 7 - case forfeit = 8 - - func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { - switch self { - case .check: - return "Chèque" - case .cash: - return "Cash" - case .lydia: - return "Lydia" - case .paylib: - return "Paylib" - case .bankTransfer: - return "Virement" - case .clubHouse: - return "Clubhouse" - case .creditCard: - return "CB" - case .forfeit: - return "Forfait" - case .gift: - return "Offert" - } - } - } - static func addon(for playerRank: Int, manMax: Int, womanMax: Int) -> Int { switch playerRank { case 0: return 0 @@ -552,21 +384,11 @@ final class PlayerRegistration: ModelObject, Storable { } func insertOnServer() { - self.tournamentStore.playerRegistrations.writeChangeAndInsertOnServer(instance: self) + self.tournamentStore?.playerRegistrations.writeChangeAndInsertOnServer(instance: self) } } -extension PlayerRegistration: Hashable { - static func == (lhs: PlayerRegistration, rhs: PlayerRegistration) -> Bool { - lhs.id == rhs.id - } - - func hash(into hasher: inout Hasher) { - hasher.combine(id) - } -} - extension PlayerRegistration: PlayerHolder { func getAssimilatedAsMaleRank() -> Int? { nil @@ -617,3 +439,22 @@ extension PlayerRegistration: PlayerHolder { computedRank } } + +enum PlayerDataSource: Int, Codable { + case frenchFederation = 0 + case beachPadel = 1 +} + +enum PlayerSexType: Int, Hashable, CaseIterable, Identifiable, Codable { + init?(rawValue: Int?) { + guard let value = rawValue else { return nil } + self.init(rawValue: value) + } + + var id: Self { + self + } + + case female = 0 + case male = 1 +} diff --git a/PadelClub/Data/README.md b/PadelClub/Data/README.md index d4a670e..c61a0b2 100644 --- a/PadelClub/Data/README.md +++ b/PadelClub/Data/README.md @@ -1,15 +1,28 @@ -# Procédure d'ajout de champ dans une classe +# Procédure de création de classe +Dans Swift: +- Dans Data > Gen > créer un nouveau fichier json pour la classe + - Le paramètre "foreignKey" permet de générer une méthode qui récupère l'objet parent. Ajouter une étoile permet d'indiquer que l'on cherche l'objet dans le Store de l'objet et non le Store.main. +- Pour générer les fichiers, on se place dans le répertoire de generator.py et on lance la commande : python generator.py -i . -o . + +Dans Django: +- Les modèles de base doivent étendre BaseModel +- Les modèles stockés dans des répertoires doivent étendre SideStoreModel +- Les classes d'admin doivent étendre SyncedObjectAdmin +- Les ForeignKey doivent toujours avoir on_delete=models.SET_NULL + - Pour se faciliter la vie dans l'admin, on veut que les delete effacent les enfants malgré tout. Il faut donc implémenter la méthode delete_dependencies(self) de la classe + + +# Procédure d'ajout de champ dans une classe Dans Swift: -- Ajouter le champ dans classe -- Ajouter le champ dans le constructeur si possible -- Ajouter la codingKey correspondante -- Ajouter le champ dans l'encoding/decoding +- Ouvrir le fichier .json correspondant à la classe +- Regénérer la classe, voir ci-dessus pour la commande - Ouvrir **ServerDataTests** et ajouter un test sur le champ - Pour que les tests sur les dates fonctionnent, on peut tester date.formatted() par exemple Dans Django: - Ajouter le champ dans la classe +- Si c'est une ForeignKey, *toujours* mettre un related_name sinon la synchro casse - Si c'est un champ dans **CustomUser**: - Ajouter le champ à la méthode fields_for_update - Ajouter le champ dans UserSerializer > create > create_user dans serializers.py @@ -18,4 +31,3 @@ Dans Django: Enfin, revenir dans Xcode, ouvrir ServerDataTests et lancer le test mis à jour - diff --git a/PadelClub/Data/Round.swift b/PadelClub/Data/Round.swift index de2cb34..452d992 100644 --- a/PadelClub/Data/Round.swift +++ b/PadelClub/Data/Round.swift @@ -10,36 +10,32 @@ import LeStorage import SwiftUI @Observable -final class Round: ModelObject, Storable { - static func resourceName() -> String { "rounds" } - static func tokenExemptedMethods() -> [HTTPMethod] { return [] } - static func filterByStoreIdentifier() -> Bool { return true } - static var relationshipNames: [String] = [] - - var id: String = Store.randomId() - var tournament: String - var index: Int - var parent: String? - private(set) var format: MatchFormat? - var startDate: Date? - var groupStageLoserBracket: Bool = false - var loserBracketMode: LoserBracketMode = .automatic +final class Round: BaseRound, SideStorable { + var _cachedSeedInterval: SeedInterval? - + internal init(tournament: String, index: Int, parent: String? = nil, matchFormat: MatchFormat? = nil, startDate: Date? = nil, groupStageLoserBracket: Bool = false, loserBracketMode: LoserBracketMode = .automatic) { - self.tournament = tournament - self.index = index - self.parent = parent - self.format = matchFormat - self.startDate = startDate - self.groupStageLoserBracket = groupStageLoserBracket - self.loserBracketMode = loserBracketMode + + super.init(tournament: tournament, index: index, parent: parent, format: matchFormat, startDate: startDate, groupStageLoserBracket: groupStageLoserBracket, loserBracketMode: loserBracketMode) + +// self.lastUpdate = Date() +// self.tournament = tournament +// self.index = index +// self.parent = parent +// self.format = matchFormat +// self.startDate = startDate +// self.groupStageLoserBracket = groupStageLoserBracket +// self.loserBracketMode = loserBracketMode + } + + required init(from decoder: any Decoder) throws { + try super.init(from: decoder) } // MARK: - Computed dependencies - var tournamentStore: TournamentStore { - return TournamentStore.instance(tournamentId: self.tournament) + var tournamentStore: TournamentStore? { + return TournamentLibrary.shared.store(tournamentId: self.tournament) } func tournamentObject() -> Tournament? { @@ -47,13 +43,13 @@ final class Round: ModelObject, Storable { } func _matches() -> [Match] { - return self.tournamentStore.matches.filter { $0.round == self.id }.sorted(by: \.index) -// return Store.main.filter { $0.round == self.id } + guard let tournamentStore = self.tournamentStore else { return [] } + return tournamentStore.matches.filter { $0.round == self.id }.sorted(by: \.index) } func getDisabledMatches() -> [Match] { - return self.tournamentStore.matches.filter { $0.round == self.id && $0.disabled == true } -// return Store.main.filter { $0.round == self.id && $0.disabled == true } + guard let tournamentStore = self.tournamentStore else { return [] } + return tournamentStore.matches.filter { $0.round == self.id && $0.disabled == true } } // MARK: - @@ -90,8 +86,9 @@ final class Round: ModelObject, Storable { func previousMatches(ofMatch match: Match) -> [Match] { guard let previousRound = previousRound() else { return [] } + guard let tournamentStore = self.tournamentStore else { return [] } - return self.tournamentStore.matches.filter { + return tournamentStore.matches.filter { $0.round == previousRound.id && ($0.index == match.topPreviousRoundMatchIndex() || $0.index == match.bottomPreviousRoundMatchIndex()) } @@ -118,7 +115,7 @@ final class Round: ModelObject, Storable { } func seed(_ team: TeamPosition, inMatchIndex matchIndex: Int) -> TeamRegistration? { - return self.tournamentStore.teamRegistrations.first(where: { + return self.tournamentStore?.teamRegistrations.first(where: { $0.bracketPosition != nil && ($0.bracketPosition! / 2) == matchIndex && ($0.bracketPosition! % 2) == team.rawValue @@ -126,7 +123,8 @@ final class Round: ModelObject, Storable { } func seeds(inMatchIndex matchIndex: Int) -> [TeamRegistration] { - return self.tournamentStore.teamRegistrations.filter { + guard let tournamentStore = self.tournamentStore else { return [] } + return tournamentStore.teamRegistrations.filter { $0.tournament == tournament && $0.bracketPosition != nil @@ -141,9 +139,10 @@ final class Round: ModelObject, Storable { } func seeds() -> [TeamRegistration] { + guard let tournamentStore = self.tournamentStore else { return [] } let initialMatchIndex = RoundRule.matchIndex(fromRoundIndex: index) let numberOfMatches = RoundRule.numberOfMatches(forRoundIndex: index) - return self.tournamentStore.teamRegistrations.filter { + return tournamentStore.teamRegistrations.filter { $0.bracketPosition != nil && ($0.bracketPosition! / 2) >= initialMatchIndex && ($0.bracketPosition! / 2) < initialMatchIndex + numberOfMatches @@ -162,12 +161,12 @@ final class Round: ModelObject, Storable { func losers() -> [TeamRegistration] { let teamIds: [String] = self._matches().compactMap { $0.losingTeamId } - return teamIds.compactMap { self.tournamentStore.teamRegistrations.findById($0) } + return teamIds.compactMap { self.tournamentStore?.teamRegistrations.findById($0) } } func winners() -> [TeamRegistration] { let teamIds: [String] = self._matches().compactMap { $0.winningTeamId } - return teamIds.compactMap { self.tournamentStore.teamRegistrations.findById($0) } + return teamIds.compactMap { self.tournamentStore?.teamRegistrations.findById($0) } } func teams() -> [TeamRegistration] { @@ -192,24 +191,24 @@ defer { return luckyLoser.team } else if let previousMatch = topPreviousRoundMatch(ofMatch: match, previousRound: previousRound) { if let teamId = previousMatch.winningTeamId { - return self.tournamentStore.teamRegistrations.findById(teamId) + return self.tournamentStore?.teamRegistrations.findById(teamId) } else if previousMatch.disabled { return previousMatch.teams().first } } else if let parent = upperBracketTopMatch(ofMatchIndex: match.index, previousRound: previousRound)?.losingTeamId { - return tournamentStore.findById(parent) + return self.store?.findById(parent) } case .two: if let luckyLoser = match.teamScores.first(where: { $0.luckyLoser == match.index * 2 + 1 }) { return luckyLoser.team } else if let previousMatch = bottomPreviousRoundMatch(ofMatch: match, previousRound: previousRound) { if let teamId = previousMatch.winningTeamId { - return self.tournamentStore.teamRegistrations.findById(teamId) + return self.tournamentStore?.teamRegistrations.findById(teamId) } else if previousMatch.disabled { return previousMatch.teams().first } } else if let parent = upperBracketBottomMatch(ofMatchIndex: match.index, previousRound: previousRound)?.losingTeamId { - return tournamentStore.findById(parent) + return self.store?.findById(parent) } } @@ -269,7 +268,7 @@ defer { guard let previousRound else { return nil } let topPreviousRoundMatchIndex = match.topPreviousRoundMatchIndex() - return self.tournamentStore.matches.first(where: { + return self.tournamentStore?.matches.first(where: { $0.round == previousRound.id && $0.index == topPreviousRoundMatchIndex }) } @@ -285,20 +284,21 @@ defer { guard let previousRound else { return nil } let bottomPreviousRoundMatchIndex = match.bottomPreviousRoundMatchIndex() - return self.tournamentStore.matches.first(where: { + return self.tournamentStore?.matches.first(where: { $0.round == previousRound.id && $0.index == bottomPreviousRoundMatchIndex }) } func getMatch(atMatchIndexInRound matchIndexInRound: Int) -> Match? { - self.tournamentStore.matches.first(where: { + self.tournamentStore?.matches.first(where: { let index = RoundRule.matchIndexWithinRound(fromMatchIndex: $0.index) return $0.round == id && index == matchIndexInRound }) } func enabledMatches() -> [Match] { - return self.tournamentStore.matches.filter { $0.round == self.id && $0.disabled == false }.sorted(by: \.index) + guard let tournamentStore = self.tournamentStore else { return [] } + return tournamentStore.matches.filter { $0.round == self.id && $0.disabled == false }.sorted(by: \.index) } // func displayableMatches() -> [Match] { @@ -335,11 +335,11 @@ defer { print("func previousRound of: ", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) } #endif - return self.tournamentStore.rounds.first(where: { $0.parent == parent && $0.index == index + 1 }) + return self.tournamentStore?.rounds.first(where: { $0.parent == parent && $0.index == index + 1 }) } func nextRound() -> Round? { - return self.tournamentStore.rounds.first(where: { $0.parent == parent && $0.index == index - 1 }) + return self.tournamentStore?.rounds.first(where: { $0.parent == parent && $0.index == index - 1 }) } func loserRounds(forRoundIndex roundIndex: Int) -> [Round] { @@ -406,11 +406,7 @@ defer { // Logger.error(error) // } } - do { - try self.tournamentStore.matches.addOrUpdate(contentOfs: _matches) - } catch { - Logger.error(error) - } + self.tournamentStore?.matches.addOrUpdate(contentOfs: _matches) } var cumulativeMatchCount: Int { @@ -470,9 +466,12 @@ defer { } #endif let initialMatchIndexFromRoundIndex = RoundRule.matchIndex(fromRoundIndex: index) - let seedsAfterThisRound: [TeamRegistration] = self.tournamentStore.teamRegistrations.filter { - $0.bracketPosition != nil - && ($0.bracketPosition! / 2) < initialMatchIndexFromRoundIndex + var seedsAfterThisRound: [TeamRegistration] = [] + if let tournamentStore = tournamentStore { + seedsAfterThisRound = tournamentStore.teamRegistrations.filter { + $0.bracketPosition != nil + && ($0.bracketPosition! / 2) < initialMatchIndexFromRoundIndex + } } // let seedsAfterThisRound : [TeamRegistration] = Store.main.filter(isIncluded: { @@ -531,9 +530,12 @@ defer { //print(seedInterval.localizedLabel()) return seedInterval } else { - let seedsAfterThisRound : [TeamRegistration] = self.tournamentStore.teamRegistrations.filter { - $0.bracketPosition != nil - && ($0.bracketPosition! / 2) < initialMatchIndexFromRoundIndex + var seedsAfterThisRound : [TeamRegistration] = [] + if let tournamentStore = self.tournamentStore { + seedsAfterThisRound = tournamentStore.teamRegistrations.filter { + $0.bracketPosition != nil + && ($0.bracketPosition! / 2) < initialMatchIndexFromRoundIndex + } } var seedsCount = seedsAfterThisRound.count @@ -577,7 +579,7 @@ defer { if let seedInterval = seedInterval(initialMode: initialMode) { return seedInterval.localizedLabel(displayStyle) } - print("Round pas trouvé", id, parent, index) +// print("Round pas trouvé", id, parent, index) return "Match de classement" } return RoundRule.roundName(fromRoundIndex: index, displayStyle: displayStyle) @@ -587,11 +589,7 @@ defer { let tournamentObject = tournamentObject() if let tournamentObject, index == 0, isUpperBracket(), hasEnded() { tournamentObject.endDate = Date() - do { - try DataStore.shared.tournaments.addOrUpdate(instance: tournamentObject) - } catch { - Logger.error(error) - } + DataStore.shared.tournaments.addOrUpdate(instance: tournamentObject) } tournamentObject?.updateTournamentState() @@ -609,7 +607,7 @@ defer { } func loserRounds() -> [Round] { - + guard let tournamentStore = self.tournamentStore else { return [] } #if _DEBUG_TIME //DEBUGING TIME let start = Date() defer { @@ -618,7 +616,7 @@ defer { } #endif - return self.tournamentStore.rounds.filter( { $0.parent == id }).sorted(by: \.index).reversed() + return tournamentStore.rounds.filter( { $0.parent == id }).sorted(by: \.index).reversed() } func loserRoundsAndChildren() -> [Round] { @@ -635,12 +633,8 @@ defer { } func deleteLoserBracket() { - do { - let loserRounds = loserRounds() - try self.tournamentStore.rounds.delete(contentOfs: loserRounds) - } catch { - Logger.error(error) - } + let loserRounds = loserRounds() + self.tournamentStore?.rounds.delete(contentOfs: loserRounds) } func buildLoserBracket() { @@ -659,26 +653,17 @@ defer { round.parent = id //parent return round } - - do { - try self.tournamentStore.rounds.addOrUpdate(contentOfs: rounds) - } catch { - Logger.error(error) - } + self.tournamentStore?.rounds.addOrUpdate(contentOfs: rounds) let matchCount = RoundRule.numberOfMatches(forTeams: currentRoundMatchCount) let matches = (0.. Int { @@ -719,69 +704,71 @@ defer { playedMatches.forEach { match in match.matchFormat = updatedMatchFormat } - do { - try self.tournamentStore.matches.addOrUpdate(contentOfs: playedMatches) - } catch { - Logger.error(error) - } + self.tournamentStore?.matches.addOrUpdate(contentOfs: playedMatches) } - override func deleteDependencies() throws { + override func deleteDependencies() { let matches = self._matches() for match in matches { - try match.deleteDependencies() + match.deleteDependencies() } - self.tournamentStore.matches.deleteDependencies(matches) + self.tournamentStore?.matches.deleteDependencies(matches) let loserRounds = self.loserRounds() for round in loserRounds { - try round.deleteDependencies() + round.deleteDependencies() } - self.tournamentStore.rounds.deleteDependencies(loserRounds) - } - - enum CodingKeys: String, CodingKey { - case _id = "id" - case _tournament = "tournament" - case _index = "index" - case _parent = "parent" - case _format = "format" - case _startDate = "startDate" - case _groupStageLoserBracket = "groupStageLoserBracket" - case _loserBracketMode = "loserBracketMode" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - id = try container.decode(String.self, forKey: ._id) - tournament = try container.decode(String.self, forKey: ._tournament) - index = try container.decode(Int.self, forKey: ._index) - parent = try container.decodeIfPresent(String.self, forKey: ._parent) - format = try container.decodeIfPresent(MatchFormat.self, forKey: ._format) - startDate = try container.decodeIfPresent(Date.self, forKey: ._startDate) - groupStageLoserBracket = try container.decodeIfPresent(Bool.self, forKey: ._groupStageLoserBracket) ?? false - loserBracketMode = try container.decodeIfPresent(LoserBracketMode.self, forKey: ._loserBracketMode) ?? .automatic - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(id, forKey: ._id) - try container.encode(tournament, forKey: ._tournament) - try container.encode(index, forKey: ._index) - try container.encode(groupStageLoserBracket, forKey: ._groupStageLoserBracket) - try container.encode(loserBracketMode, forKey: ._loserBracketMode) - - try container.encode(parent, forKey: ._parent) - try container.encode(format, forKey: ._format) - try container.encode(startDate, forKey: ._startDate) - - } + self.tournamentStore?.rounds.deleteDependencies(loserRounds) + } + +// enum CodingKeys: String, CodingKey { +// case _id = "id" +// case _storeId = "storeId" +// case _lastUpdate = "lastUpdate" +// case _tournament = "tournament" +// case _index = "index" +// case _parent = "parent" +// case _format = "format" +// case _startDate = "startDate" +// case _groupStageLoserBracket = "groupStageLoserBracket" +// case _loserBracketMode = "loserBracketMode" +// } +// +// required init(from decoder: Decoder) throws { +// let container = try decoder.container(keyedBy: CodingKeys.self) +// id = try container.decode(String.self, forKey: ._id) +// storeId = try container.decode(String.self, forKey: ._storeId) +// lastUpdate = try container.decodeIfPresent(Date.self, forKey: ._lastUpdate) ?? Date() +// tournament = try container.decode(String.self, forKey: ._tournament) +// index = try container.decode(Int.self, forKey: ._index) +// parent = try container.decodeIfPresent(String.self, forKey: ._parent) +// format = try container.decodeIfPresent(MatchFormat.self, forKey: ._format) +// startDate = try container.decodeIfPresent(Date.self, forKey: ._startDate) +// groupStageLoserBracket = try container.decodeIfPresent(Bool.self, forKey: ._groupStageLoserBracket) ?? false +// loserBracketMode = try container.decodeIfPresent(LoserBracketMode.self, forKey: ._loserBracketMode) ?? .automatic +// } +// +// func encode(to encoder: Encoder) throws { +// var container = encoder.container(keyedBy: CodingKeys.self) +// +// try container.encode(id, forKey: ._id) +// try container.encode(lastUpdate, forKey: ._lastUpdate) +// try container.encode(storeId, forKey: ._storeId) +// try container.encode(tournament, forKey: ._tournament) +// try container.encode(index, forKey: ._index) +// try container.encode(groupStageLoserBracket, forKey: ._groupStageLoserBracket) +// try container.encode(loserBracketMode, forKey: ._loserBracketMode) +// +// try container.encode(parent, forKey: ._parent) +// try container.encode(format, forKey: ._format) +// try container.encode(startDate, forKey: ._startDate) +// +// } func insertOnServer() { - self.tournamentStore.rounds.writeChangeAndInsertOnServer(instance: self) + self.tournamentStore?.rounds.writeChangeAndInsertOnServer(instance: self) for match in self._matches() { match.insertOnServer() } @@ -789,11 +776,7 @@ defer { } -extension Round: Selectable, Equatable { - static func == (lhs: Round, rhs: Round) -> Bool { - lhs.id == rhs.id - } - +extension Round: Selectable { func selectionLabel(index: Int) -> String { if let parentRound { diff --git a/PadelClub/Data/TeamRegistration.swift b/PadelClub/Data/TeamRegistration.swift index 7019524..4be73e5 100644 --- a/PadelClub/Data/TeamRegistration.swift +++ b/PadelClub/Data/TeamRegistration.swift @@ -10,49 +10,51 @@ import LeStorage import SwiftUI @Observable -final class TeamRegistration: ModelObject, Storable { - static func resourceName() -> String { "team-registrations" } - static func tokenExemptedMethods() -> [HTTPMethod] { return [] } - static func filterByStoreIdentifier() -> Bool { return true } - static var relationshipNames: [String] = [] - - var id: String = Store.randomId() - var tournament: String - var groupStage: String? - var registrationDate: Date? - var callDate: Date? - var bracketPosition: Int? - var groupStagePosition: Int? - var comment: String? - var source: String? - var sourceValue: String? - var logo: String? - var name: String? - - var walkOut: Bool = false - var wildCardBracket: Bool = false - var wildCardGroupStage: Bool = false - var weight: Int = 0 - var lockedWeight: Int? - var confirmationDate: Date? - var qualified: Bool = false - var finalRanking: Int? - var pointsEarned: Int? - - func hasRegisteredOnline() -> Bool { - players().anySatisfy({ $0.registeredOnline }) - } - - func unrankedOrUnknown() -> Bool { - players().anySatisfy({ $0.source == nil }) - } +final class TeamRegistration: BaseTeamRegistration, SideStorable { - - func isOutOfTournament() -> Bool { - walkOut - } - - init(tournament: String, groupStage: String? = nil, registrationDate: Date? = nil, callDate: Date? = nil, bracketPosition: Int? = nil, groupStagePosition: Int? = nil, comment: String? = nil, source: String? = nil, sourceValue: String? = nil, logo: String? = nil, name: String? = nil, walkOut: Bool = false, wildCardBracket: Bool = false, wildCardGroupStage: Bool = false, weight: Int = 0, lockedWeight: Int? = nil, confirmationDate: Date? = nil, qualified: Bool = false, finalRanking: Int? = nil, pointsEarned: Int? = nil) { + // static func resourceName() -> String { "team-registrations" } + // static func tokenExemptedMethods() -> [HTTPMethod] { return [] } + // static func filterByStoreIdentifier() -> Bool { return true } + // static var relationshipNames: [String] = [] + // + // var id: String = Store.randomId() + // var lastUpdate: Date + // var tournament: String + // var groupStage: String? + // var registrationDate: Date? + // var callDate: Date? + // var bracketPosition: Int? + // var groupStagePosition: Int? + // var comment: String? + // var source: String? + // var sourceValue: String? + // var logo: String? + // var name: String? + // + // var walkOut: Bool = false + // var wildCardBracket: Bool = false + // var wildCardGroupStage: Bool = false + // var weight: Int = 0 + // var lockedWeight: Int? + // var confirmationDate: Date? + // var qualified: Bool = false + // var finalRanking: Int? + // var pointsEarned: Int? + // + // var storeId: String? = nil + + init( + tournament: String, groupStage: String? = nil, registrationDate: Date? = nil, + callDate: Date? = nil, bracketPosition: Int? = nil, groupStagePosition: Int? = nil, + comment: String? = nil, source: String? = nil, sourceValue: String? = nil, + logo: String? = nil, name: String? = nil, walkOut: Bool = false, + wildCardBracket: Bool = false, wildCardGroupStage: Bool = false, weight: Int = 0, + lockedWeight: Int? = nil, confirmationDate: Date? = nil, qualified: Bool = false + ) { + + super.init() + + // self.storeId = tournament self.tournament = tournament self.groupStage = groupStage self.registrationDate = registrationDate ?? Date() @@ -71,75 +73,86 @@ final class TeamRegistration: ModelObject, Storable { self.lockedWeight = lockedWeight self.confirmationDate = confirmationDate self.qualified = qualified - self.finalRanking = finalRanking - self.pointsEarned = pointsEarned } - - var tournamentStore: TournamentStore { - return TournamentStore.instance(tournamentId: self.tournament) + + func hasRegisteredOnline() -> Bool { + players().anySatisfy({ $0.registeredOnline }) + } + + func unrankedOrUnknown() -> Bool { + players().anySatisfy({ $0.source == nil }) + } + + func isOutOfTournament() -> Bool { + walkOut + } + + required init(from decoder: any Decoder) throws { + try super.init(from: decoder) + } + + var tournamentStore: TournamentStore? { + return TournamentLibrary.shared.store(tournamentId: self.tournament) } // MARK: - Computed dependencies - + func unsortedPlayers() -> [PlayerRegistration] { - return self.tournamentStore.playerRegistrations.filter { $0.teamRegistration == self.id && $0.coach == false } + guard let tournamentStore = self.tournamentStore else { return [] } + return tournamentStore.playerRegistrations.filter { + $0.teamRegistration == self.id && $0.coach == false + } } - + // MARK: - - + func deleteTeamScores() { - let ts = self.tournamentStore.teamScores.filter({ $0.teamRegistration == id }) - do { - try self.tournamentStore.teamScores.delete(contentOfs: ts) - } catch { - Logger.error(error) - } + guard let tournamentStore = self.tournamentStore else { return } + let ts = tournamentStore.teamScores.filter({ $0.teamRegistration == id }) + tournamentStore.teamScores.delete(contentOfs: ts) } - - override func deleteDependencies() throws { + + override func deleteDependencies() { let unsortedPlayers = unsortedPlayers() for player in unsortedPlayers { - try player.deleteDependencies() + player.deleteDependencies() } - self.tournamentStore.playerRegistrations.deleteDependencies(unsortedPlayers) - + self.tournamentStore?.playerRegistrations.deleteDependencies(unsortedPlayers) + let teamScores = teamScores() for teamScore in teamScores { - try teamScore.deleteDependencies() + teamScore.deleteDependencies() } - self.tournamentStore.teamScores.deleteDependencies(teamScores) + self.tournamentStore?.teamScores.deleteDependencies(teamScores) } - + func hasArrived(isHere: Bool = false) { let unsortedPlayers = unsortedPlayers() unsortedPlayers.forEach({ $0.hasArrived = !isHere }) - do { - try self.tournamentStore.playerRegistrations.addOrUpdate(contentOfs: unsortedPlayers) - } catch { - Logger.error(error) - } + self.tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: unsortedPlayers) } - + func isHere() -> Bool { let unsortedPlayers = unsortedPlayers() if unsortedPlayers.isEmpty { return false } return unsortedPlayers.allSatisfy({ $0.hasArrived }) } - func isSeedable() -> Bool { bracketPosition == nil && groupStage == nil } - + func setSeedPosition(inSpot match: Match, slot: TeamPosition?, opposingSeeding: Bool) { - var teamPosition : TeamPosition { + var teamPosition: TeamPosition { if let slot { return slot } else { let matchIndex = match.index let seedRound = RoundRule.roundIndex(fromMatchIndex: matchIndex) let numberOfMatches = RoundRule.numberOfMatches(forRoundIndex: seedRound) - let isUpper = RoundRule.matchIndexWithinRound(fromMatchIndex: matchIndex) < (numberOfMatches / 2) + let isUpper = + RoundRule.matchIndexWithinRound(fromMatchIndex: matchIndex) + < (numberOfMatches / 2) var teamPosition = slot ?? (isUpper ? .one : .two) if opposingSeeding { teamPosition = slot ?? (isUpper ? .two : .one) @@ -156,9 +169,11 @@ final class TeamRegistration: ModelObject, Storable { } if let tournament = tournamentObject() { if let index = index(in: tournament.selectedSortedTeams()) { - let drawLog = DrawLog(tournament: tournament.id, drawSeed: index, drawMatchIndex: match.index, drawTeamPosition: teamPosition, drawType: .seed) + let drawLog = DrawLog( + tournament: tournament.id, drawSeed: index, drawMatchIndex: match.index, + drawTeamPosition: teamPosition, drawType: .seed) do { - try tournamentStore.drawLogs.addOrUpdate(instance: drawLog) + try tournamentStore?.drawLogs.addOrUpdate(instance: drawLog) } catch { Logger.error(error) } @@ -166,7 +181,7 @@ final class TeamRegistration: ModelObject, Storable { tournament.updateTeamScores(in: bracketPosition) } } - + func expectedSummonDate() -> Date? { if let groupStageStartDate = groupStageObject()?.startDate { return groupStageStartDate @@ -175,11 +190,11 @@ final class TeamRegistration: ModelObject, Storable { } return nil } - + var initialWeight: Int { return lockedWeight ?? weight } - + func called() -> Bool { return callDate != nil } @@ -191,82 +206,93 @@ final class TeamRegistration: ModelObject, Storable { func getPhoneNumbers() -> [String] { return players().compactMap { $0.phoneNumber }.filter({ $0.isEmpty == false }) } - + func getMail() -> [String] { let mails = players().compactMap({ $0.email }) return mails } - + func isImported() -> Bool { let unsortedPlayers = unsortedPlayers() if unsortedPlayers.isEmpty { return false } return unsortedPlayers.allSatisfy({ $0.isImported() }) } - + func isWildCard() -> Bool { return wildCardBracket || wildCardGroupStage } - + func isPlaying() -> Bool { return currentMatch() != nil } - + func currentMatch() -> Match? { return teamScores().compactMap { $0.matchObject() }.first(where: { $0.isRunning() }) } - + func teamScores() -> [TeamScore] { - return self.tournamentStore.teamScores.filter({ $0.teamRegistration == id }) + guard let tournamentStore = self.tournamentStore else { return [] } + return tournamentStore.teamScores.filter({ $0.teamRegistration == id }) } - + func wins() -> [Match] { - return self.tournamentStore.matches.filter({ $0.winningTeamId == id }) + guard let tournamentStore = self.tournamentStore else { return [] } + return tournamentStore.matches.filter({ $0.winningTeamId == id }) } func loses() -> [Match] { - return self.tournamentStore.matches.filter({ $0.losingTeamId == id }) + guard let tournamentStore = self.tournamentStore else { return [] } + return tournamentStore.matches.filter({ $0.losingTeamId == id }) } - + func matches() -> [Match] { - return self.tournamentStore.matches.filter({ $0.losingTeamId == id || $0.winningTeamId == id }) + guard let tournamentStore = self.tournamentStore else { return [] } + return tournamentStore.matches.filter({ $0.losingTeamId == id || $0.winningTeamId == id }) } var tournamentCategory: TournamentCategory { tournamentObject()?.tournamentCategory ?? .men } - + @objc var canonicalName: String { players().map { $0.canonicalName }.joined(separator: " ") } - + func hasMemberOfClub(_ codeClubOrClubName: String?) -> Bool { guard let codeClubOrClubName else { return true } return unsortedPlayers().anySatisfy({ - $0.clubName?.contains(codeClubOrClubName) == true || $0.clubName?.contains(codeClubOrClubName) == true + $0.clubName?.contains(codeClubOrClubName) == true + || $0.clubName?.contains(codeClubOrClubName) == true }) } - + func updateWeight(inTournamentCategory tournamentCategory: TournamentCategory) { self.setWeight(from: self.players(), inTournamentCategory: tournamentCategory) } - func teamLabel(_ displayStyle: DisplayStyle = .wide, twoLines: Bool = false, separator: String = "&") -> String { + func teamLabel( + _ displayStyle: DisplayStyle = .wide, twoLines: Bool = false, separator: String = "&" + ) -> String { if let name { return name } - return players().map { $0.playerLabel(displayStyle) }.joined(separator: twoLines ? "\n" : " \(separator) ") + return players().map { $0.playerLabel(displayStyle) }.joined( + separator: twoLines ? "\n" : " \(separator) ") } - + func teamLabelRanked(displayRank: Bool, displayTeamName: Bool) -> String { - [displayTeamName ? name : nil, displayRank ? seedIndex() : nil, displayTeamName ? (name == nil ? teamLabel() : name) : teamLabel()].compactMap({ $0 }).joined(separator: " ") + [ + displayTeamName ? name : nil, displayRank ? seedIndex() : nil, + displayTeamName ? (name == nil ? teamLabel() : name) : teamLabel(), + ].compactMap({ $0 }).joined(separator: " ") } - + func seedIndex() -> String? { guard let tournament = tournamentObject() else { return nil } guard let index = index(in: tournament.selectedSortedTeams()) else { return nil } return "(\(index + 1))" } - + func index(in teams: [TeamRegistration]) -> Int? { return teams.firstIndex(where: { $0.id == id }) } @@ -279,19 +305,23 @@ final class TeamRegistration: ModelObject, Storable { return "###" } } - + func contains(_ searchField: String) -> Bool { - return unsortedPlayers().anySatisfy({ $0.contains(searchField) }) || self.name?.localizedCaseInsensitiveContains(searchField) == true + return unsortedPlayers().anySatisfy({ $0.contains(searchField) }) + || self.name?.localizedCaseInsensitiveContains(searchField) == true } func containsExactlyPlayerLicenses(_ playerLicenses: [String?]) -> Bool { - let arrayOfIds : [String] = unsortedPlayers().compactMap({ $0.licenceId?.strippedLicense?.canonicalVersion }) - let ids : Set = Set(arrayOfIds.sorted()) - let searchedIds = Set(playerLicenses.compactMap({ $0?.strippedLicense?.canonicalVersion }).sorted()) + let arrayOfIds: [String] = unsortedPlayers().compactMap({ + $0.licenceId?.strippedLicense?.canonicalVersion + }) + let ids: Set = Set(arrayOfIds.sorted()) + let searchedIds = Set( + playerLicenses.compactMap({ $0?.strippedLicense?.canonicalVersion }).sorted()) if ids.isEmpty || searchedIds.isEmpty { return false } return ids.hashValue == searchedIds.hashValue } - + func includes(players: [PlayerRegistration]) -> Bool { let unsortedPlayers = unsortedPlayers() guard players.count == unsortedPlayers.count else { return false } @@ -301,32 +331,33 @@ final class TeamRegistration: ModelObject, Storable { } } } - + func includes(player: PlayerRegistration) -> Bool { return unsortedPlayers().anySatisfy { _player in _player.isSameAs(player) } } - + func canPlay() -> Bool { let unsortedPlayers = unsortedPlayers() if unsortedPlayers.isEmpty { return false } - return matches().isEmpty == false || unsortedPlayers.allSatisfy({ $0.hasPaid() || $0.hasArrived }) + return matches().isEmpty == false + || unsortedPlayers.allSatisfy({ $0.hasPaid() || $0.hasArrived }) } - + func availableForSeedPick() -> Bool { return groupStage == nil && bracketPosition == nil } - + func inGroupStage() -> Bool { return groupStagePosition != nil } - + func inRound() -> Bool { return bracketPosition != nil } - + func positionLabel() -> String? { if groupStagePosition != nil { return "Poule" } if let initialRound = initialRound() { @@ -335,7 +366,7 @@ final class TeamRegistration: ModelObject, Storable { return nil } } - + func initialRoundColor() -> Color? { if walkOut { return Color.logoRed } if groupStagePosition != nil || wildCardGroupStage { return Color.blue } @@ -347,58 +378,63 @@ final class TeamRegistration: ModelObject, Storable { return nil } } - + func resetGroupeStagePosition() { + guard let tournamentStore = self.tournamentStore else { return } if let groupStage { - let matches = self.tournamentStore.matches.filter({ $0.groupStage == groupStage }).map { $0.id } - let teamScores = self.tournamentStore.teamScores.filter({ $0.teamRegistration == id && matches.contains($0.match) }) - do { - try tournamentStore.teamScores.delete(contentOfs: teamScores) - } catch { - Logger.error(error) + let matches = tournamentStore.matches.filter({ $0.groupStage == groupStage }).map { + $0.id } + let teamScores = tournamentStore.teamScores.filter({ + $0.teamRegistration == id && matches.contains($0.match) + }) + tournamentStore.teamScores.delete(contentOfs: teamScores) } //groupStageObject()?._matches().forEach({ $0.updateTeamScores() }) groupStage = nil groupStagePosition = nil } - + func resetBracketPosition() { - let matches = self.tournamentStore.matches.filter({ $0.groupStage == nil }).map { $0.id } - let teamScores = self.tournamentStore.teamScores.filter({ $0.teamRegistration == id && matches.contains($0.match) }) - do { - try tournamentStore.teamScores.delete(contentOfs: teamScores) - } catch { - Logger.error(error) - } + guard let tournamentStore = self.tournamentStore else { return } + let matches = tournamentStore.matches.filter({ $0.groupStage == nil }).map { $0.id } + let teamScores = tournamentStore.teamScores.filter({ + $0.teamRegistration == id && matches.contains($0.match) + }) + tournamentStore.teamScores.delete(contentOfs: teamScores) self.bracketPosition = nil } - + func resetPositions() { resetGroupeStagePosition() - resetBracketPosition() + resetBracketPosition() } - + func pasteData(_ exportFormat: ExportFormat = .rawText, _ index: Int = 0) -> String { switch exportFormat { case .rawText: - return [playersPasteData(exportFormat), formattedInscriptionDate(exportFormat), name].compactMap({ $0 }).joined(separator: exportFormat.newLineSeparator()) + return [playersPasteData(exportFormat), formattedInscriptionDate(exportFormat), name] + .compactMap({ $0 }).joined(separator: exportFormat.newLineSeparator()) case .csv: - return [index.formatted(), playersPasteData(exportFormat), isWildCard() ? "WC" : weight.formatted()].joined(separator: exportFormat.separator()) + return [ + index.formatted(), playersPasteData(exportFormat), + isWildCard() ? "WC" : weight.formatted(), + ].joined(separator: exportFormat.separator()) } } - + var computedRegistrationDate: Date { return registrationDate ?? .distantFuture } - + func formattedInscriptionDate(_ exportFormat: ExportFormat = .rawText) -> String? { guard let registrationDate else { return nil } - - let formattedDate = registrationDate.formatted(.dateTime.weekday().day().month().hour().minute()) + + let formattedDate = registrationDate.formatted( + .dateTime.weekday().day().month().hour().minute()) let onlineSuffix = hasRegisteredOnline() ? " en ligne" : "" - + switch exportFormat { case .rawText: return "Inscrit\(onlineSuffix) le \(formattedDate)" @@ -406,13 +442,14 @@ final class TeamRegistration: ModelObject, Storable { return formattedDate } } - + func formattedSummonDate(_ exportFormat: ExportFormat = .rawText) -> String? { - + switch exportFormat { case .rawText: if let callDate { - return "Convoqué le " + callDate.formatted(.dateTime.weekday().day().month().hour().minute()) + return "Convoqué le " + + callDate.formatted(.dateTime.weekday().day().month().hour().minute()) } else { return nil } @@ -424,22 +461,31 @@ final class TeamRegistration: ModelObject, Storable { } } } - + func playersPasteData(_ exportFormat: ExportFormat = .rawText) -> String { switch exportFormat { case .rawText: - return players().map { $0.pasteData(exportFormat) }.joined(separator: exportFormat.newLineSeparator()) + return players().map { $0.pasteData(exportFormat) }.joined( + separator: exportFormat.newLineSeparator()) case .csv: - return players().map { [$0.pasteData(exportFormat), isWildCard() ? "WC" : $0.computedRank.formatted() ].joined(separator: exportFormat.separator()) }.joined(separator: exportFormat.separator()) + return players().map { + [$0.pasteData(exportFormat), isWildCard() ? "WC" : $0.computedRank.formatted()] + .joined(separator: exportFormat.separator()) + }.joined(separator: exportFormat.separator()) } } - - func updatePlayers(_ players: Set, inTournamentCategory tournamentCategory: TournamentCategory) { + + func updatePlayers( + _ players: Set, + inTournamentCategory tournamentCategory: TournamentCategory + ) { let previousPlayers = Set(unsortedPlayers()) - + players.forEach { player in previousPlayers.forEach { oldPlayer in - if player.licenceId?.strippedLicense == oldPlayer.licenceId?.strippedLicense, player.licenceId?.strippedLicense != nil { + if player.licenceId?.strippedLicense == oldPlayer.licenceId?.strippedLicense, + player.licenceId?.strippedLicense != nil + { player.registeredOnline = oldPlayer.registeredOnline player.coach = oldPlayer.coach player.tournamentPlayed = oldPlayer.tournamentPlayed @@ -450,29 +496,24 @@ final class TeamRegistration: ModelObject, Storable { } } } - - + let playersToRemove = previousPlayers.subtracting(players) - do { - try self.tournamentStore.playerRegistrations.delete(contentOfs: playersToRemove) - } catch { - Logger.error(error) - } + self.tournamentStore?.playerRegistrations.delete(contentOfs: Array(playersToRemove)) setWeight(from: Array(players), inTournamentCategory: tournamentCategory) players.forEach { player in player.teamRegistration = id } - -// do { -// try self.tournamentStore.playerRegistrations.addOrUpdate(contentOfs: players) -// } catch { -// Logger.error(error) -// } + + // do { + // try self.tournamentStore.playerRegistrations.addOrUpdate(contentOfs: players) + // } catch { + // Logger.error(error) + // } } - + typealias TeamRange = (left: TeamRegistration?, right: TeamRegistration?) - + func replacementRange() -> TeamRange? { guard let tournamentObject = tournamentObject() else { return nil } guard let index = tournamentObject.indexOf(team: self) else { return nil } @@ -481,7 +522,7 @@ final class TeamRegistration: ModelObject, Storable { let right = selectedSortedTeams[safe: index + 1] return (left: left, right: right) } - + func replacementRangeExtended() -> TeamRange? { guard let tournamentObject = tournamentObject() else { return nil } guard let groupStagePosition else { return nil } @@ -490,19 +531,23 @@ final class TeamRegistration: ModelObject, Storable { if groupStagePosition == 0 { left = tournamentObject.seeds().last } else { - let previousHat = selectedSortedTeams.filter({ $0.groupStagePosition == groupStagePosition - 1 }).sorted(by: \.weight) + let previousHat = selectedSortedTeams.filter({ + $0.groupStagePosition == groupStagePosition - 1 + }).sorted(by: \.weight) left = previousHat.last } var right: TeamRegistration? = nil if groupStagePosition == tournamentObject.teamsPerGroupStage - 1 { right = nil } else { - let previousHat = selectedSortedTeams.filter({ $0.groupStagePosition == groupStagePosition + 1 }).sorted(by: \.weight) + let previousHat = selectedSortedTeams.filter({ + $0.groupStagePosition == groupStagePosition + 1 + }).sorted(by: \.weight) right = previousHat.first } return (left: left, right: right) } - + typealias AreInIncreasingOrder = (PlayerRegistration, PlayerRegistration) -> Bool func players() -> [PlayerRegistration] { @@ -511,36 +556,40 @@ final class TeamRegistration: ModelObject, Storable { let predicates: [AreInIncreasingOrder] = [ { $0.sex?.rawValue ?? 0 < $1.sex?.rawValue ?? 0 }, { $0.rank ?? Int.max < $1.rank ?? Int.max }, - { $0.lastName < $1.lastName}, - { $0.firstName < $1.firstName } + { $0.lastName < $1.lastName }, + { $0.firstName < $1.firstName }, ] - + for predicate in predicates { if !predicate(lhs, rhs) && !predicate(rhs, lhs) { continue } - + return predicate(lhs, rhs) } - + return false } } - + func coaches() -> [PlayerRegistration] { - tournamentStore.playerRegistrations.filter({ $0.coach }) + guard let store = self.tournamentStore else { return [] } + return store.playerRegistrations.filter { $0.coach } } - - func setWeight(from players: [PlayerRegistration], inTournamentCategory tournamentCategory: TournamentCategory) { + + func setWeight( + from players: [PlayerRegistration], + inTournamentCategory tournamentCategory: TournamentCategory + ) { let significantPlayerCount = significantPlayerCount() let sortedPlayers = players.sorted(by: \.computedRank, order: .ascending) weight = (sortedPlayers.prefix(significantPlayerCount).map { $0.computedRank } + missingPlayerType(inTournamentCategory: tournamentCategory).map { unrankValue(for: $0 == 1 ? true : false ) }).prefix(significantPlayerCount).reduce(0,+) } - + func significantPlayerCount() -> Int { return tournamentObject()?.significantPlayerCount() ?? 2 } - + func missingPlayerType(inTournamentCategory tournamentCategory: TournamentCategory) -> [Int] { let players = unsortedPlayers() if players.count >= 2 { return [] } @@ -553,41 +602,42 @@ final class TeamRegistration: ModelObject, Storable { } return missing } - + func unrankValue(for malePlayer: Bool) -> Int { return tournamentObject()?.unrankValue(for: malePlayer) ?? 90_000 } - + func groupStageObject() -> GroupStage? { guard let groupStage else { return nil } - return self.tournamentStore.groupStages.findById(groupStage) + return self.tournamentStore?.groupStages.findById(groupStage) } - + func initialRound() -> Round? { guard let bracketPosition else { return nil } let roundIndex = RoundRule.roundIndex(fromMatchIndex: bracketPosition / 2) - return self.tournamentStore.rounds.first(where: { $0.index == roundIndex }) + return self.tournamentStore?.rounds.first(where: { $0.index == roundIndex }) } func initialMatch() -> Match? { guard let bracketPosition else { return nil } guard let initialRoundObject = initialRound() else { return nil } - return self.tournamentStore.matches.first(where: { $0.round == initialRoundObject.id && $0.index == bracketPosition / 2 }) + return self.tournamentStore?.matches.first(where: { + $0.round == initialRoundObject.id && $0.index == bracketPosition / 2 + }) } func toggleSummonConfirmation() { - if confirmationDate == nil { confirmationDate = Date() } - else { confirmationDate = nil } + if confirmationDate == nil { confirmationDate = Date() } else { confirmationDate = nil } } - + func didConfirmSummon() -> Bool { confirmationDate != nil } - + func tournamentObject() -> Tournament? { return Store.main.findById(tournament) } - + func groupStagePositionAtStep(_ step: Int) -> Int? { guard let groupStagePosition else { return nil } if step == 0 { @@ -597,7 +647,7 @@ final class TeamRegistration: ModelObject, Storable { } return nil } - + func wildcardLabel() -> String? { if isWildCard() { let wildcardLabel: String = ["Wildcard", (wildCardBracket ? "Tableau" : "Poule")].joined(separator: " ") @@ -606,24 +656,26 @@ final class TeamRegistration: ModelObject, Storable { return nil } } - + var _cachedRestingTime: (Bool, Date?)? - + func restingTime() -> Date? { if let _cachedRestingTime { return _cachedRestingTime.1 } - let restingTime = matches().filter({ $0.hasEnded() }).sorted(by: \.computedEndDateForSorting).last?.endDate + let restingTime = matches().filter({ $0.hasEnded() }).sorted( + by: \.computedEndDateForSorting + ).last?.endDate _cachedRestingTime = (true, restingTime) return restingTime } - + func resetRestingTime() { _cachedRestingTime = nil } - + var restingTimeForSorting: Date { restingTime()! } - + func teamNameLabel() -> String { if let name, name.isEmpty == false { return name @@ -631,7 +683,7 @@ final class TeamRegistration: ModelObject, Storable { return "Toute l'équipe" } } - + func isDifferentPosition(_ drawMatchIndex: Int?) -> Bool { if let bracketPosition, let drawMatchIndex { return drawMatchIndex != bracketPosition @@ -642,7 +694,7 @@ final class TeamRegistration: ModelObject, Storable { } return false } - + func shouldDisplayRankAndWeight() -> Bool { unsortedPlayers().count > 0 } @@ -690,102 +742,14 @@ final class TeamRegistration: ModelObject, Storable { return nil } - - enum CodingKeys: String, CodingKey { - case _id = "id" - case _tournament = "tournament" - case _groupStage = "groupStage" - case _registrationDate = "registrationDate" - case _callDate = "callDate" - case _bracketPosition = "bracketPosition" - case _groupStagePosition = "groupStagePosition" - case _comment = "comment" - case _source = "source" - case _sourceValue = "sourceValue" - case _logo = "logo" - case _name = "name" - case _wildCardBracket = "wildCardBracket" - case _wildCardGroupStage = "wildCardGroupStage" - case _weight = "weight" - case _walkOut = "walkOut" - case _lockedWeight = "lockedWeight" - case _confirmationDate = "confirmationDate" - case _qualified = "qualified" - case _finalRanking = "finalRanking" - case _pointsEarned = "pointsEarned" - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - // Non-optional properties - id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() - tournament = try container.decode(String.self, forKey: ._tournament) - walkOut = try container.decodeIfPresent(Bool.self, forKey: ._walkOut) ?? false - wildCardBracket = try container.decodeIfPresent(Bool.self, forKey: ._wildCardBracket) ?? false - wildCardGroupStage = try container.decodeIfPresent(Bool.self, forKey: ._wildCardGroupStage) ?? false - weight = try container.decodeIfPresent(Int.self, forKey: ._weight) ?? 0 - qualified = try container.decodeIfPresent(Bool.self, forKey: ._qualified) ?? false - - // Optional properties - groupStage = try container.decodeIfPresent(String.self, forKey: ._groupStage) - registrationDate = try container.decodeIfPresent(Date.self, forKey: ._registrationDate) - callDate = try container.decodeIfPresent(Date.self, forKey: ._callDate) - bracketPosition = try container.decodeIfPresent(Int.self, forKey: ._bracketPosition) - groupStagePosition = try container.decodeIfPresent(Int.self, forKey: ._groupStagePosition) - comment = try container.decodeIfPresent(String.self, forKey: ._comment) - source = try container.decodeIfPresent(String.self, forKey: ._source) - sourceValue = try container.decodeIfPresent(String.self, forKey: ._sourceValue) - logo = try container.decodeIfPresent(String.self, forKey: ._logo) - name = try container.decodeIfPresent(String.self, forKey: ._name) - lockedWeight = try container.decodeIfPresent(Int.self, forKey: ._lockedWeight) - confirmationDate = try container.decodeIfPresent(Date.self, forKey: ._confirmationDate) - finalRanking = try container.decodeIfPresent(Int.self, forKey: ._finalRanking) - pointsEarned = try container.decodeIfPresent(Int.self, forKey: ._pointsEarned) - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(id, forKey: ._id) - try container.encode(tournament, forKey: ._tournament) - try container.encode(groupStage, forKey: ._groupStage) - try container.encode(registrationDate, forKey: ._registrationDate) - try container.encode(callDate, forKey: ._callDate) - try container.encode(bracketPosition, forKey: ._bracketPosition) - try container.encode(groupStagePosition, forKey: ._groupStagePosition) - try container.encode(comment, forKey: ._comment) - try container.encode(source, forKey: ._source) - try container.encode(sourceValue, forKey: ._sourceValue) - try container.encode(logo, forKey: ._logo) - try container.encode(name, forKey: ._name) - try container.encode(walkOut, forKey: ._walkOut) - try container.encode(wildCardBracket, forKey: ._wildCardBracket) - try container.encode(wildCardGroupStage, forKey: ._wildCardGroupStage) - try container.encode(weight, forKey: ._weight) - try container.encode(lockedWeight, forKey: ._lockedWeight) - try container.encode(confirmationDate, forKey: ._confirmationDate) - try container.encode(qualified, forKey: ._qualified) - try container.encode(finalRanking, forKey: ._finalRanking) - try container.encode(pointsEarned, forKey: ._pointsEarned) - } - + func insertOnServer() { - self.tournamentStore.teamRegistrations.writeChangeAndInsertOnServer(instance: self) + self.tournamentStore?.teamRegistrations.writeChangeAndInsertOnServer(instance: self) for playerRegistration in self.unsortedPlayers() { playerRegistration.insertOnServer() } } - -} -extension TeamRegistration: Hashable { - static func == (lhs: TeamRegistration, rhs: TeamRegistration) -> Bool { - lhs.id == rhs.id - } - - func hash(into hasher: inout Hasher) { - hasher.combine(id) - } } enum TeamDataSource: Int, Codable { diff --git a/PadelClub/Data/TeamScore.swift b/PadelClub/Data/TeamScore.swift index 9a7c94b..cc54a41 100644 --- a/PadelClub/Data/TeamScore.swift +++ b/PadelClub/Data/TeamScore.swift @@ -9,59 +9,70 @@ import Foundation import LeStorage @Observable -final class TeamScore: ModelObject, Storable { - - static func resourceName() -> String { "team-scores" } - static func tokenExemptedMethods() -> [HTTPMethod] { return [] } - static func filterByStoreIdentifier() -> Bool { return true } - static var relationshipNames: [String] = ["match"] - - var id: String = Store.randomId() - var match: String - var teamRegistration: String? - //var playerRegistrations: [String] = [] - var score: String? - var walkOut: Int? - var luckyLoser: Int? +final class TeamScore: BaseTeamScore, SideStorable { + + +// static func resourceName() -> String { "team-scores" } +// static func tokenExemptedMethods() -> [HTTPMethod] { return [] } +// static func filterByStoreIdentifier() -> Bool { return true } +// static var relationshipNames: [String] = ["match"] +// +// var id: String = Store.randomId() +// var lastUpdate: Date +// var match: String +// var teamRegistration: String? +// //var playerRegistrations: [String] = [] +// var score: String? +// var walkOut: Int? +// var luckyLoser: Int? +// +// var storeId: String? = nil init(match: String, teamRegistration: String? = nil, score: String? = nil, walkOut: Int? = nil, luckyLoser: Int? = nil) { - self.match = match - self.teamRegistration = teamRegistration -// self.playerRegistrations = playerRegistrations - self.score = score - self.walkOut = walkOut - self.luckyLoser = luckyLoser + super.init(match: match, teamRegistration: teamRegistration, score: score, walkOut: walkOut, luckyLoser: luckyLoser) + +// self.match = match +// self.teamRegistration = teamRegistration +//// self.playerRegistrations = playerRegistrations +// self.score = score +// self.walkOut = walkOut +// self.luckyLoser = luckyLoser } init(match: String, team: TeamRegistration?) { - self.match = match + super.init(match: match) if let team { self.teamRegistration = team.id - //self.playerRegistrations = team.players().map { $0.id } } - self.score = nil - self.walkOut = nil - self.luckyLoser = nil } - var tournamentStore: TournamentStore { - if let store = self.store as? TournamentStore { - return store + required init(from decoder: Decoder) throws { + try super.init(from: decoder) + } + + var tournamentStore: TournamentStore? { + guard let storeId else { + fatalError("missing store id for \(String(describing: type(of: self)))") } - fatalError("missing store for \(String(describing: type(of: self)))") + return TournamentLibrary.shared.store(tournamentId: storeId) +// +// if let store = self.store as? TournamentStore { +// return store +// } +// fatalError("missing store for \(String(describing: type(of: self)))") } // MARK: - Computed dependencies func matchObject() -> Match? { - return self.tournamentStore.matches.findById(self.match) + return self.tournamentStore?.matches.findById(self.match) } var team: TeamRegistration? { guard let teamRegistration else { return nil } - return self.tournamentStore.teamRegistrations.findById(teamRegistration) + return self.tournamentStore?.teamRegistrations.findById(teamRegistration) } // MARK: - @@ -70,29 +81,33 @@ final class TeamScore: ModelObject, Storable { return walkOut != nil } - enum CodingKeys: String, CodingKey { - case _id = "id" - case _match = "match" - case _teamRegistration = "teamRegistration" - //case _playerRegistrations = "playerRegistrations" - case _score = "score" - case _walkOut = "walkOut" - case _luckyLoser = "luckyLoser" - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(id, forKey: ._id) - try container.encode(match, forKey: ._match) - try container.encode(teamRegistration, forKey: ._teamRegistration) - try container.encode(score, forKey: ._score) - try container.encode(walkOut, forKey: ._walkOut) - try container.encode(luckyLoser, forKey: ._luckyLoser) - } +// enum CodingKeys: String, CodingKey { +// case _id = "id" +// case _storeId = "storeId" +// case _lastUpdate = "lastUpdate" +// case _match = "match" +// case _teamRegistration = "teamRegistration" +// //case _playerRegistrations = "playerRegistrations" +// case _score = "score" +// case _walkOut = "walkOut" +// case _luckyLoser = "luckyLoser" +// } +// +// func encode(to encoder: Encoder) throws { +// var container = encoder.container(keyedBy: CodingKeys.self) +// +// try container.encode(id, forKey: ._id) +// try container.encode(storeId, forKey: ._storeId) +// try container.encode(lastUpdate, forKey: ._lastUpdate) +// try container.encode(match, forKey: ._match) +// try container.encode(teamRegistration, forKey: ._teamRegistration) +// try container.encode(score, forKey: ._score) +// try container.encode(walkOut, forKey: ._walkOut) +// try container.encode(luckyLoser, forKey: ._luckyLoser) +// } func insertOnServer() { - self.tournamentStore.teamScores.writeChangeAndInsertOnServer(instance: self) + self.tournamentStore?.teamScores.writeChangeAndInsertOnServer(instance: self) } } diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index fe0ef7b..cd458bf 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -10,66 +10,8 @@ import LeStorage import SwiftUI @Observable -final class Tournament : ModelObject, Storable { - static func resourceName() -> String { "tournaments" } - static func tokenExemptedMethods() -> [HTTPMethod] { return [] } - static func filterByStoreIdentifier() -> Bool { return false } - static var relationshipNames: [String] = [] - - var id: String = Store.randomId() - var event: String? - var name: String? - var startDate: Date - var endDate: Date? - private(set) var creationDate: Date - var isPrivate: Bool - private(set) var groupStageFormat: MatchFormat? - private(set) var roundFormat: MatchFormat? - private(set) var loserRoundFormat: MatchFormat? - var groupStageSortMode: GroupStageOrderingMode - var groupStageCount: Int - var rankSourceDate: Date? - var dayDuration: Int - var teamCount: Int - var teamSorting: TeamSortingType - var federalCategory: TournamentCategory - var federalLevelCategory: TournamentLevel - var federalAgeCategory: FederalTournamentAge - var closedRegistrationDate: Date? - var groupStageAdditionalQualified: Int - var courtCount: Int = 2 - var prioritizeClubMembers: Bool - var qualifiedPerGroupStage: Int - var teamsPerGroupStage: Int - var entryFee: Double? - var payment: TournamentPayment? = nil - var additionalEstimationDuration: Int = 0 - var isDeleted: Bool = false - var isCanceled: Bool = false - var publishTeams: Bool = false - //var publishWaitingList: Bool = false - var publishSummons: Bool = false - var publishGroupStages: Bool = false - var publishBrackets: Bool = false - var shouldVerifyGroupStage: Bool = false - var shouldVerifyBracket: Bool = false - var hideTeamsWeight: Bool = false - var publishTournament: Bool = false - var hidePointsEarned: Bool = false - var publishRankings: Bool = false - var loserBracketMode: LoserBracketMode = .automatic - var initialSeedRound: Int = 0 - var initialSeedCount: Int = 0 - var enableOnlineRegistration: Bool = false - var registrationDateLimit: Date? = nil - var openingRegistrationDate: Date? = nil - var waitingListLimit: Int? = nil - var accountIsRequired: Bool = true - var licenseIsRequired: Bool = true - var minimumPlayerPerTeam: Int = 2 - var maximumPlayerPerTeam: Int = 2 - var information: String? = nil - +final class Tournament: BaseTournament { + //local variable var refreshInProgress: Bool = false var lastTeamRefresh: Date? @@ -82,67 +24,14 @@ final class Tournament : ModelObject, Storable { @ObservationIgnored var navigationPath: [Screen] = [] - - enum CodingKeys: String, CodingKey { - case _id = "id" - case _event = "event" - case _creator = "creator" - case _name = "name" - case _startDate = "startDate" - case _endDate = "endDate" - case _creationDate = "creationDate" - case _isPrivate = "isPrivate" - case _groupStageFormat = "groupStageFormat" - case _roundFormat = "roundFormat" - case _loserRoundFormat = "loserRoundFormat" - case _groupStageSortMode = "groupStageSortMode" - case _groupStageCount = "groupStageCount" - case _rankSourceDate = "rankSourceDate" - case _dayDuration = "dayDuration" - case _teamCount = "teamCount" - case _teamSorting = "teamSorting" - case _federalCategory = "federalCategory" - case _federalLevelCategory = "federalLevelCategory" - case _federalAgeCategory = "federalAgeCategory" - case _seedCount = "seedCount" - case _closedRegistrationDate = "closedRegistrationDate" - case _groupStageAdditionalQualified = "groupStageAdditionalQualified" - case _courtCount = "courtCount" - case _prioritizeClubMembers = "prioritizeClubMembers" - case _qualifiedPerGroupStage = "qualifiedPerGroupStage" - case _teamsPerGroupStage = "teamsPerGroupStage" - case _entryFee = "entryFee" - case _additionalEstimationDuration = "additionalEstimationDuration" - case _isDeleted = "isDeleted" - case _isCanceled = "localId" - case _payment = "globalId" - case _publishTeams = "publishTeams" - //case _publishWaitingList = "publishWaitingList" - case _publishSummons = "publishSummons" - case _publishGroupStages = "publishGroupStages" - case _publishBrackets = "publishBrackets" - case _shouldVerifyGroupStage = "shouldVerifyGroupStage" - case _shouldVerifyBracket = "shouldVerifyBracket" - case _hideTeamsWeight = "hideTeamsWeight" - case _publishTournament = "publishTournament" - case _hidePointsEarned = "hidePointsEarned" - case _publishRankings = "publishRankings" - case _loserBracketMode = "loserBracketMode" - case _initialSeedRound = "initialSeedRound" - case _initialSeedCount = "initialSeedCount" - case _enableOnlineRegistration = "enableOnlineRegistration" - case _registrationDateLimit = "registrationDateLimit" - case _openingRegistrationDate = "openingRegistrationDate" - case _waitingListLimit = "waitingListLimit" - case _accountIsRequired = "accountIsRequired" - case _licenseIsRequired = "licenseIsRequired" - case _minimumPlayerPerTeam = "minimumPlayerPerTeam" - case _maximumPlayerPerTeam = "maximumPlayerPerTeam" - case _information = "information" - } +// internal init(event: String? = nil, name: String? = nil, startDate: Date = Date(), endDate: Date? = nil, creationDate: Date = Date(), isPrivate: Bool = false, groupStageFormat: MatchFormat? = nil, roundFormat: MatchFormat? = nil, loserRoundFormat: MatchFormat? = nil, groupStageSortMode: GroupStageOrderingMode, groupStageCount: Int = 4, rankSourceDate: Date? = nil, dayDuration: Int = 1, teamCount: Int = 24, teamSorting: TeamSortingType? = nil, federalCategory: TournamentCategory, federalLevelCategory: TournamentLevel, federalAgeCategory: FederalTournamentAge, closedRegistrationDate: Date? = nil, groupStageAdditionalQualified: Int = 0, courtCount: Int = 2, prioritizeClubMembers: Bool = false, qualifiedPerGroupStage: Int = 1, teamsPerGroupStage: Int = 4, entryFee: Double? = nil, additionalEstimationDuration: Int = 0, isDeleted: Bool = false, publishTeams: Bool = false, publishSummons: Bool = false, publishGroupStages: Bool = false, publishBrackets: Bool = false, shouldVerifyBracket: Bool = false, shouldVerifyGroupStage: Bool = false, hideTeamsWeight: Bool = false, publishTournament: Bool = false, hidePointsEarned: Bool = false, publishRankings: Bool = false, loserBracketMode: LoserBracketMode = .automatic, initialSeedRound: Int = 0, initialSeedCount: Int = 0) { +// super.init() +// } + internal init(event: String? = nil, name: String? = nil, startDate: Date = Date(), endDate: Date? = nil, creationDate: Date = Date(), isPrivate: Bool = true, groupStageFormat: MatchFormat? = nil, roundFormat: MatchFormat? = nil, loserRoundFormat: MatchFormat? = nil, groupStageSortMode: GroupStageOrderingMode, groupStageCount: Int = 4, rankSourceDate: Date? = nil, dayDuration: Int = 1, teamCount: Int = 24, teamSorting: TeamSortingType? = nil, federalCategory: TournamentCategory, federalLevelCategory: TournamentLevel, federalAgeCategory: FederalTournamentAge, closedRegistrationDate: Date? = nil, groupStageAdditionalQualified: Int = 0, courtCount: Int = 2, prioritizeClubMembers: Bool = false, qualifiedPerGroupStage: Int = 1, teamsPerGroupStage: Int = 4, entryFee: Double? = nil, additionalEstimationDuration: Int = 0, isDeleted: Bool = false, publishTeams: Bool = false, publishSummons: Bool = false, publishGroupStages: Bool = false, publishBrackets: Bool = false, shouldVerifyBracket: Bool = false, shouldVerifyGroupStage: Bool = false, hideTeamsWeight: Bool = false, publishTournament: Bool = false, hidePointsEarned: Bool = false, publishRankings: Bool = false, loserBracketMode: LoserBracketMode = .automatic, initialSeedRound: Int = 0, initialSeedCount: Int = 0, enableOnlineRegistration: Bool = false, registrationDateLimit: Date? = nil, openingRegistrationDate: Date? = nil, waitingListLimit: Int? = nil, accountIsRequired: Bool = true, licenseIsRequired: Bool = true, minimumPlayerPerTeam: Int = 2, maximumPlayerPerTeam: Int = 2, information: String? = nil) { + super.init() self.event = event self.name = name self.startDate = startDate @@ -207,272 +96,75 @@ final class Tournament : ModelObject, Storable { self.maximumPlayerPerTeam = maximumPlayerPerTeam self.information = information } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - id = try container.decode(String.self, forKey: ._id) - event = try container.decodeIfPresent(String.self, forKey: ._event) - name = try container.decodeIfPresent(String.self, forKey: ._name) - startDate = try container.decode(Date.self, forKey: ._startDate) - endDate = try container.decodeIfPresent(Date.self, forKey: ._endDate) - creationDate = try container.decode(Date.self, forKey: ._creationDate) - isPrivate = try container.decode(Bool.self, forKey: ._isPrivate) - groupStageFormat = try container.decodeIfPresent(MatchFormat.self, forKey: ._groupStageFormat) - roundFormat = try container.decodeIfPresent(MatchFormat.self, forKey: ._roundFormat) - loserRoundFormat = try container.decodeIfPresent(MatchFormat.self, forKey: ._loserRoundFormat) - groupStageSortMode = try container.decode(GroupStageOrderingMode.self, forKey: ._groupStageSortMode) - groupStageCount = try container.decode(Int.self, forKey: ._groupStageCount) - rankSourceDate = try container.decodeIfPresent(Date.self, forKey: ._rankSourceDate) - dayDuration = try container.decode(Int.self, forKey: ._dayDuration) - teamCount = try container.decode(Int.self, forKey: ._teamCount) - teamSorting = try container.decode(TeamSortingType.self, forKey: ._teamSorting) - federalCategory = try container.decode(TournamentCategory.self, forKey: ._federalCategory) - federalLevelCategory = try container.decode(TournamentLevel.self, forKey: ._federalLevelCategory) - federalAgeCategory = try container.decode(FederalTournamentAge.self, forKey: ._federalAgeCategory) - closedRegistrationDate = try container.decodeIfPresent(Date.self, forKey: ._closedRegistrationDate) - groupStageAdditionalQualified = try container.decode(Int.self, forKey: ._groupStageAdditionalQualified) - courtCount = try container.decode(Int.self, forKey: ._courtCount) - prioritizeClubMembers = try container.decode(Bool.self, forKey: ._prioritizeClubMembers) - qualifiedPerGroupStage = try container.decode(Int.self, forKey: ._qualifiedPerGroupStage) - teamsPerGroupStage = try container.decode(Int.self, forKey: ._teamsPerGroupStage) - entryFee = try container.decodeIfPresent(Double.self, forKey: ._entryFee) - payment = try Tournament._decodePayment(container: container) - additionalEstimationDuration = try container.decode(Int.self, forKey: ._additionalEstimationDuration) - isDeleted = try container.decode(Bool.self, forKey: ._isDeleted) - isCanceled = try Tournament._decodeCanceled(container: container) - publishTeams = try container.decodeIfPresent(Bool.self, forKey: ._publishTeams) ?? false - publishSummons = try container.decodeIfPresent(Bool.self, forKey: ._publishSummons) ?? false - publishGroupStages = try container.decodeIfPresent(Bool.self, forKey: ._publishGroupStages) ?? false - publishBrackets = try container.decodeIfPresent(Bool.self, forKey: ._publishBrackets) ?? false - shouldVerifyBracket = try container.decodeIfPresent(Bool.self, forKey: ._shouldVerifyBracket) ?? false - shouldVerifyGroupStage = try container.decodeIfPresent(Bool.self, forKey: ._shouldVerifyGroupStage) ?? false - hideTeamsWeight = try container.decodeIfPresent(Bool.self, forKey: ._hideTeamsWeight) ?? false - publishTournament = try container.decodeIfPresent(Bool.self, forKey: ._publishTournament) ?? false - hidePointsEarned = try container.decodeIfPresent(Bool.self, forKey: ._hidePointsEarned) ?? false - publishRankings = try container.decodeIfPresent(Bool.self, forKey: ._publishRankings) ?? false - loserBracketMode = try container.decodeIfPresent(LoserBracketMode.self, forKey: ._loserBracketMode) ?? .automatic - initialSeedRound = try container.decodeIfPresent(Int.self, forKey: ._initialSeedRound) ?? 0 - initialSeedCount = try container.decodeIfPresent(Int.self, forKey: ._initialSeedCount) ?? 0 - enableOnlineRegistration = try container.decodeIfPresent(Bool.self, forKey: ._enableOnlineRegistration) ?? false - registrationDateLimit = try container.decodeIfPresent(Date.self, forKey: ._registrationDateLimit) - openingRegistrationDate = try container.decodeIfPresent(Date.self, forKey: ._openingRegistrationDate) - waitingListLimit = try container.decodeIfPresent(Int.self, forKey: ._waitingListLimit) - accountIsRequired = try container.decodeIfPresent(Bool.self, forKey: ._accountIsRequired) ?? true - licenseIsRequired = try container.decodeIfPresent(Bool.self, forKey: ._licenseIsRequired) ?? true - minimumPlayerPerTeam = try container.decodeIfPresent(Int.self, forKey: ._minimumPlayerPerTeam) ?? 2 - maximumPlayerPerTeam = try container.decodeIfPresent(Int.self, forKey: ._maximumPlayerPerTeam) ?? 2 - - information = try container.decodeIfPresent(String.self, forKey: ._information) - } - - fileprivate static let _numberFormatter: NumberFormatter = NumberFormatter() - fileprivate static func _decodePayment(container: KeyedDecodingContainer) throws -> TournamentPayment? { - let data = try container.decodeIfPresent(Data.self, forKey: ._payment) - - if let data { - do { - let decoded: String = try data.decryptData(pass: CryptoKey.pass.rawValue) - let sequence = decoded.compactMap { _numberFormatter.number(from: String($0))?.intValue } - return TournamentPayment(rawValue: sequence[18]) - } catch { - Logger.error(error) - } - } - return nil - } - - fileprivate static func _decodeCanceled(container: KeyedDecodingContainer) throws -> Bool { - let data = try container.decodeIfPresent(Data.self, forKey: ._isCanceled) - if let data { - do { - let decoded: String = try data.decryptData(pass: CryptoKey.pass.rawValue) - let sequence = decoded.compactMap { _numberFormatter.number(from: String($0))?.intValue } - return Bool.decodeInt(sequence[18]) - } catch { - Logger.error(error) - } - } - return false + required init(from decoder: Decoder) throws { + try super.init(from: decoder) } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(id, forKey: ._id) - try container.encode(event, forKey: ._event) - try container.encode(name, forKey: ._name) - - try container.encode(startDate, forKey: ._startDate) - try container.encode(endDate, forKey: ._endDate) - - try container.encode(creationDate, forKey: ._creationDate) - try container.encode(isPrivate, forKey: ._isPrivate) - - try container.encode(groupStageFormat, forKey: ._groupStageFormat) - try container.encode(roundFormat, forKey: ._roundFormat) - try container.encode(loserRoundFormat, forKey: ._loserRoundFormat) - try container.encode(groupStageSortMode, forKey: ._groupStageSortMode) - try container.encode(groupStageCount, forKey: ._groupStageCount) - - try container.encode(rankSourceDate, forKey: ._rankSourceDate) - - try container.encode(dayDuration, forKey: ._dayDuration) - try container.encode(teamCount, forKey: ._teamCount) - try container.encode(teamSorting, forKey: ._teamSorting) - try container.encode(federalCategory, forKey: ._federalCategory) - try container.encode(federalLevelCategory, forKey: ._federalLevelCategory) - try container.encode(federalAgeCategory, forKey: ._federalAgeCategory) - - try container.encode(closedRegistrationDate, forKey: ._closedRegistrationDate) - - try container.encode(groupStageAdditionalQualified, forKey: ._groupStageAdditionalQualified) - try container.encode(courtCount, forKey: ._courtCount) - try container.encode(prioritizeClubMembers, forKey: ._prioritizeClubMembers) - try container.encode(qualifiedPerGroupStage, forKey: ._qualifiedPerGroupStage) - try container.encode(teamsPerGroupStage, forKey: ._teamsPerGroupStage) - try container.encode(entryFee, forKey: ._entryFee) - - try self._encodePayment(container: &container) - try container.encode(additionalEstimationDuration, forKey: ._additionalEstimationDuration) - try container.encode(isDeleted, forKey: ._isDeleted) - try self._encodeIsCanceled(container: &container) - try container.encode(publishTeams, forKey: ._publishTeams) - try container.encode(publishSummons, forKey: ._publishSummons) - try container.encode(publishBrackets, forKey: ._publishBrackets) - try container.encode(publishGroupStages, forKey: ._publishGroupStages) - try container.encode(shouldVerifyBracket, forKey: ._shouldVerifyBracket) - try container.encode(shouldVerifyGroupStage, forKey: ._shouldVerifyGroupStage) - try container.encode(hideTeamsWeight, forKey: ._hideTeamsWeight) - try container.encode(publishTournament, forKey: ._publishTournament) - try container.encode(hidePointsEarned, forKey: ._hidePointsEarned) - try container.encode(publishRankings, forKey: ._publishRankings) - try container.encode(loserBracketMode, forKey: ._loserBracketMode) - try container.encode(initialSeedRound, forKey: ._initialSeedRound) - try container.encode(initialSeedCount, forKey: ._initialSeedCount) - try container.encode(enableOnlineRegistration, forKey: ._enableOnlineRegistration) - try container.encode(registrationDateLimit, forKey: ._registrationDateLimit) - try container.encode(openingRegistrationDate, forKey: ._openingRegistrationDate) - try container.encode(waitingListLimit, forKey: ._waitingListLimit) - - try container.encode(accountIsRequired, forKey: ._accountIsRequired) - try container.encode(licenseIsRequired, forKey: ._licenseIsRequired) - try container.encode(minimumPlayerPerTeam, forKey: ._minimumPlayerPerTeam) - try container.encode(maximumPlayerPerTeam, forKey: ._maximumPlayerPerTeam) - try container.encode(information, forKey: ._information) + var tournamentStore: TournamentStore? { + return TournamentLibrary.shared.store(tournamentId: self.id) } - - fileprivate func _encodePayment(container: inout KeyedEncodingContainer) throws { - - guard let payment else { - try container.encodeNil(forKey: ._payment) - return - } - - let max: Int = TournamentPayment.allCases.count - var sequence = (1...18).map { _ in Int.random(in: (0..) throws { - - let max: Int = 9 - var sequence = (1...18).map { _ in Int.random(in: (0...max)) } - sequence.append(self.isCanceled.encodedValue) - sequence.append(contentsOf: (1...13).map { _ in Int.random(in: (0...max ))} ) - - let stringCombo: [String] = sequence.map { $0.formatted() } - let joined: String = stringCombo.joined(separator: "") - if let data = joined.data(using: .utf8) { - let encryped: Data = try data.encrypt(pass: CryptoKey.pass.rawValue) - try container.encode(encryped, forKey: ._isCanceled) - } - } + override func deleteDependencies() { + guard let store = self.tournamentStore else { return } - var tournamentStore: TournamentStore { - return TournamentStore.instance(tournamentId: self.id) - } - - override func deleteDependencies() throws { - let store = self.tournamentStore - let drawLogs = self.tournamentStore.drawLogs + let drawLogs = Array(store.drawLogs) for drawLog in drawLogs { - try drawLog.deleteDependencies() + drawLog.deleteDependencies() } store.drawLogs.deleteDependencies(drawLogs) - let teams = self.tournamentStore.teamRegistrations + let teams = Array(store.teamRegistrations) for team in teams { - try team.deleteDependencies() + team.deleteDependencies() } store.teamRegistrations.deleteDependencies(teams) - - let groups = self.tournamentStore.groupStages + + let groups = Array(store.groupStages) for group in groups { - try group.deleteDependencies() + group.deleteDependencies() } store.groupStages.deleteDependencies(groups) - - let rounds = Array(self.tournamentStore.rounds) + + let rounds = Array(store.rounds) for round in rounds { - try round.deleteDependencies() + round.deleteDependencies() } store.rounds.deleteDependencies(rounds) store.matchSchedulers.deleteDependencies(self._matchSchedulers()) + } // MARK: - Computed Dependencies - + func unsortedTeams() -> [TeamRegistration] { - return Array(self.tournamentStore.teamRegistrations) + guard let tournamentStore = self.tournamentStore else { return [] } + return Array(tournamentStore.teamRegistrations) } - + func unsortedTeamsCount() -> Int { - return self.tournamentStore.teamRegistrations.count + return self.tournamentStore?.teamRegistrations.count ?? 0 } - func groupStages(atStep step: Int = 0) -> [GroupStage] { - let groupStages: [GroupStage] = self.tournamentStore.groupStages.filter { $0.tournament == self.id && $0.step == step } + guard let tournamentStore = self.tournamentStore else { return [] } + let groupStages: [GroupStage] = tournamentStore.groupStages.filter { $0.tournament == self.id && $0.step == step } return groupStages.sorted(by: \.index) } - + func allGroupStages() -> [GroupStage] { - return self.tournamentStore.groupStages.sorted(by: \GroupStage.computedOrder) + guard let tournamentStore = self.tournamentStore else { return [] } + return tournamentStore.groupStages.sorted(by: \GroupStage.computedOrder) } - + func allRounds() -> [Round] { - return Array(self.tournamentStore.rounds) + guard let tournamentStore = self.tournamentStore else { return [] } + return Array(tournamentStore.rounds) } - + // MARK: - - - /// Warning: if the enum has more than 10 cases, the payment algo is broken - enum TournamentPayment: Int, CaseIterable { - case free, unit, subscriptionUnit, unlimited - - var isSubscription: Bool { - switch self { - case .subscriptionUnit, .unlimited: - return true - default: - return false - } - } - - } - + enum State { case initial case build @@ -480,7 +172,7 @@ final class Tournament : ModelObject, Storable { case canceled case finished } - + func eventLabel() -> String { if let event = eventObject(), let name = event.name { return name @@ -488,15 +180,15 @@ final class Tournament : ModelObject, Storable { return "" } } - + func publishedTournamentDate() -> Date { return min(creationDate.tomorrowAtNine, startDate) } - + func publishedTeamsDate() -> Date { return self.startDate } - + func canBePublished() -> Bool { switch state() { case .build, .finished, .running: @@ -505,11 +197,11 @@ final class Tournament : ModelObject, Storable { return false } } - + func isTournamentPublished() -> Bool { return (Date() >= publishedTournamentDate()) || publishTournament } - + func areTeamsPublished() -> Bool { return Date() >= startDate || publishTeams } @@ -517,7 +209,7 @@ final class Tournament : ModelObject, Storable { func areSummonsPublished() -> Bool { return Date() >= startDate || publishSummons } - + fileprivate func _publishedDateFromMatches(_ matches: [Match]) -> Date? { let startDates: [Date] = matches.compactMap { $0.startDate } let sortedDates: [Date] = startDates.sorted() @@ -532,12 +224,12 @@ final class Tournament : ModelObject, Storable { return startDate } } - + func publishedGroupStagesDate() -> Date? { let matches: [Match] = self.groupStages().flatMap { $0.playedMatches() } return self._publishedDateFromMatches(matches) } - + func areGroupStagesPublished() -> Bool { if publishGroupStages { return true } if let publishedGroupStagesDate = publishedGroupStagesDate() { @@ -546,12 +238,12 @@ final class Tournament : ModelObject, Storable { return false } } - + func publishedBracketsDate() -> Date? { let matches: [Match] = self.rounds().flatMap { $0.playedMatches() } return self._publishedDateFromMatches(matches) } - + func areBracketsPublished() -> Bool { if publishBrackets { return true } if let publishedBracketsDate = publishedBracketsDate() { @@ -560,12 +252,12 @@ final class Tournament : ModelObject, Storable { return false } } - + func shareURL(_ pageLink: PageLink = .matches) -> URL? { if pageLink == .clubBroadcast { let club = club() - print("club", club) - print("club broadcast code", club?.broadcastCode) +// print("club", club) +// print("club broadcast code", club?.broadcastCode) if let club, let broadcastCode = club.broadcastCode { return URLs.main.url.appending(path: "c/\(broadcastCode)") } else { @@ -585,16 +277,16 @@ defer { #endif return Set(runningMatches.compactMap { $0.courtIndex }).sorted() } - + func hasStarted() -> Bool { return startDate <= Date() } - + func eventObject() -> Event? { guard let event else { return nil } return Store.main.findById(event) } - + func pasteDataForImporting(_ exportFormat: ExportFormat = .rawText) -> String { let _selectedSortedTeams = selectedSortedTeams() let selectedSortedTeams = _selectedSortedTeams + waitingListSortedTeams(selectedSortedTeams: _selectedSortedTeams) @@ -610,11 +302,11 @@ defer { return teamPaste.joined(separator: exportFormat.newLineSeparator()) } } - + func club() -> Club? { return eventObject()?.clubObject() } - + func locationLabel(_ displayStyle: DisplayStyle = .wide) -> String { if let club = club() { switch displayStyle { @@ -627,29 +319,29 @@ defer { return "" } } - + func hasEnded() -> Bool { return endDate != nil } - + func state() -> State { if self.isCanceled == true { return .canceled } - + if self.hasEnded() { return .finished } - + let isBuild = (groupStageCount > 0 && groupStages().isEmpty == false) || rounds().isEmpty == false - + if isBuild && startDate <= Date() { return .running } - + if isBuild { return .build } return .initial } - + func seededTeams() -> [TeamRegistration] { return selectedSortedTeams().filter({ $0.bracketPosition != nil && $0.groupStagePosition == nil }) } @@ -667,7 +359,7 @@ defer { let seeds = max(selectedSortedTeams.count - groupStageSpots() , 0) return Array(selectedSortedTeams.prefix(seeds)) } - + func availableSeeds() -> [TeamRegistration] { #if _DEBUG_TIME //DEBUGING TIME let start = Date() @@ -680,7 +372,7 @@ defer { return seeds().filter { $0.isSeedable() } } - + func lastSeedRound() -> Int { if let last = seeds().filter({ $0.bracketPosition != nil }).last { return RoundRule.roundIndex(fromMatchIndex: last.bracketPosition! / 2) @@ -688,12 +380,12 @@ defer { return 0 } } - + func getRound(atRoundIndex roundIndex: Int) -> Round? { - return self.tournamentStore.rounds.first(where: { $0.index == roundIndex }) + return self.tournamentStore?.rounds.first(where: { $0.index == roundIndex }) // return Store.main.filter(isIncluded: { $0.tournament == id && $0.index == roundIndex }).first } - + func availableSeedSpot(inRoundIndex roundIndex: Int) -> [Match] { return getRound(atRoundIndex: roundIndex)?.playedMatches().filter { $0.isEmpty() } ?? [] } @@ -701,7 +393,6 @@ defer { func availableSeedOpponentSpot(inRoundIndex roundIndex: Int) -> [Match] { return getRound(atRoundIndex: roundIndex)?.playedMatches().filter { $0.hasSpaceLeft() } ?? [] } - func availableSeedGroups(includeAll: Bool = false) -> [SeedInterval] { let seeds = seeds() var availableSeedGroup = Set() @@ -746,7 +437,7 @@ defer { return nil } } - + func availableSeedGroup() -> SeedInterval? { let seeds = seeds() if let firstIndex = seeds.firstIndex(where: { $0.isSeedable() }) { @@ -755,18 +446,18 @@ defer { } return nil } - + func randomSeed(fromSeedGroup seedGroup: SeedInterval) -> TeamRegistration? { let availableSeeds = seeds(inSeedGroup: seedGroup) return availableSeeds.randomElement() } - + func seeds(inSeedGroup seedGroup: SeedInterval) -> [TeamRegistration] { let availableSeedInSeedGroup = (seedGroup.last - seedGroup.first) + 1 let availableSeeds = seeds().dropFirst(seedGroup.first - 1).prefix(availableSeedInSeedGroup).filter({ $0.isSeedable() }) return availableSeeds } - + func seedGroupAvailable(atRoundIndex roundIndex: Int) -> SeedInterval? { if let availableSeedGroup = availableSeedGroup() { return seedGroupAvailable(atRoundIndex: roundIndex, availableSeedGroup: availableSeedGroup) @@ -805,11 +496,11 @@ defer { } } } - + return nil } - - func setSeeds(inRoundIndex roundIndex: Int, inSeedGroup seedGroup: SeedInterval) { + + func setSeeds(inRoundIndex roundIndex: Int, inSeedGroup seedGroup: SeedInterval) { if seedGroup == SeedInterval(first: 1, last: 2) { let seeds = seeds() if let matches = getRound(atRoundIndex: roundIndex)?.playedMatches() { @@ -821,11 +512,11 @@ defer { } } } else { - + let availableSeedSpot = availableSeedSpot(inRoundIndex: roundIndex) let availableSeedOpponentSpot = availableSeedOpponentSpot(inRoundIndex: roundIndex) let availableSeeds = seeds(inSeedGroup: seedGroup) - + if seedGroup == SeedInterval(first: 3, last: 4), availableSeedSpot.count == 6 { var spots = [Match]() spots.append(availableSeedSpot[1]) @@ -849,7 +540,7 @@ defer { seed.setSeedPosition(inSpot: spots[index], slot: nil, opposingSeeding: false) } } else if (availableSeeds.count <= availableSeedOpponentSpot.count && availableSeeds.count <= self.availableSeeds().count) { - + let spots = availableSeedOpponentSpot.shuffled() for (index, seed) in availableSeeds.enumerated() { seed.setSeedPosition(inSpot: spots[index], slot: nil, opposingSeeding: true) @@ -864,8 +555,8 @@ defer { } } } - - + + func inscriptionClosed() -> Bool { closedRegistrationDate != nil } @@ -878,14 +569,14 @@ defer { func matchesWithSpace() -> [Match] { getActiveRound()?.playedMatches().filter({ $0.hasSpaceLeft() }) ?? [] } - + func getActiveRound(withSeeds: Bool = false) -> Round? { let rounds: [Round] = self.rounds() let unfinishedRounds: [Round] = rounds.filter { $0.hasStarted() && $0.hasEnded() == false } let sortedRounds: [Round] = unfinishedRounds.sorted(by: \.index).reversed() - + let round = sortedRounds.first ?? rounds.last(where: { $0.hasEnded() }) ?? rounds.first - + if withSeeds { if round?.seeds().isEmpty == false { return round @@ -903,35 +594,38 @@ defer { DateInterval(event: event.id, courtIndex: match.courtIndex!, startDate: match.startDate!, endDate: match.estimatedEndDate(additionalEstimationDuration)!) } } - + func allRoundMatches() -> [Match] { return allRounds().flatMap { $0._matches() } } - + func allMatches() -> [Match] { - return self.tournamentStore.matches.filter { $0.disabled == false } + guard let tournamentStore = self.tournamentStore else { return [] } + return tournamentStore.matches.filter { $0.disabled == false } } func _allMatchesIncludingDisabled() -> [Match] { - return Array(self.tournamentStore.matches) + guard let tournamentStore = self.tournamentStore else { return [] } + return Array(tournamentStore.matches) } func rounds() -> [Round] { - let rounds: [Round] = self.tournamentStore.rounds.filter { $0.isUpperBracket() } + guard let tournamentStore = self.tournamentStore else { return [] } + let rounds: [Round] = tournamentStore.rounds.filter { $0.isUpperBracket() } return rounds.sorted(by: \.index).reversed() } - + func sortedTeams(selectedSortedTeams: [TeamRegistration]) -> [TeamRegistration] { let teams = selectedSortedTeams return teams + waitingListTeams(in: teams, includingWalkOuts: true) } - + func waitingListSortedTeams(selectedSortedTeams: [TeamRegistration]) -> [TeamRegistration] { let teams = selectedSortedTeams return waitingListTeams(in: teams, includingWalkOuts: false) } - + func selectedSortedTeams() -> [TeamRegistration] { #if _DEBUG_TIME //DEBUGING TIME let start = Date() @@ -942,7 +636,7 @@ defer { #endif var _sortedTeams : [TeamRegistration] = [] var _teams = unsortedTeams().filter({ $0.isOutOfTournament() == false }) - + if let closedRegistrationDate { _teams = _teams.filter({ team in if let registrationDate = team.registrationDate { @@ -966,26 +660,26 @@ defer { var groupStageTeamCount: Int = groupStageSpots - wcGroupStage.count if groupStageTeamCount < 0 { groupStageTeamCount = 0 } if bracketSeeds < 0 { bracketSeeds = 0 } - + if prioritizeClubMembers { - + var bracketTeams: [TeamRegistration] = [] bracketTeams.append(contentsOf: _completeTeams.filter { $0.hasMemberOfClub(clubName) }) - + let others: [TeamRegistration] = _completeTeams.filter { $0.hasMemberOfClub(clubName) == false } let sortedOthers: [TeamRegistration] = others.sorted(using: defaultSorting, order: .ascending) bracketTeams.append(contentsOf: sortedOthers) - + bracketTeams = bracketTeams .prefix(bracketSeeds) .sorted(using: _currentSelectionSorting, order: .ascending) bracketTeams.append(contentsOf: wcBracket) - + // let bracketTeams: [TeamRegistration] = (_completeTeams.filter { $0.hasMemberOfClub(clubName) } + _completeTeams.filter { $0.hasMemberOfClub(clubName) == false }.sorted(using: defaultSorting, order: .ascending)).prefix(bracketSeeds).sorted(using: _currentSelectionSorting, order: .ascending) + wcBracket - + let groupStageTeamsNoFiltering = Set(_completeTeams).subtracting(bracketTeams) let groupStageTeams = (groupStageTeamsNoFiltering.filter { $0.hasMemberOfClub(clubName) } + groupStageTeamsNoFiltering.filter { $0.hasMemberOfClub(clubName) == false }.sorted(using: defaultSorting, order: .ascending)).prefix(groupStageTeamCount).sorted(using: _currentSelectionSorting, order: .ascending) + wcGroupStage - + _sortedTeams = bracketTeams.sorted(using: _currentSelectionSorting, order: .ascending) + groupStageTeams.sorted(using: _currentSelectionSorting, order: .ascending) } else { let bracketTeams = _completeTeams.prefix(bracketSeeds).sorted(using: _currentSelectionSorting, order: .ascending) + wcBracket @@ -994,7 +688,7 @@ defer { } return _sortedTeams } - + func waitingListTeams(in teams: [TeamRegistration], includingWalkOuts: Bool) -> [TeamRegistration] { let waitingList = Set(unsortedTeams()).subtracting(teams) let waitings = waitingList.filter { $0.isOutOfTournament() == false }.sorted(using: _defaultSorting(), order: .ascending) @@ -1009,11 +703,11 @@ defer { func bracketCut(teamCount: Int, groupStageCut: Int) -> Int { return self.teamCount - groupStageCut } - + func groupStageCut() -> Int { return groupStageSpots() } - + func cutLabel(index: Int, teamCount: Int?) -> String { let _teamCount = teamCount ?? selectedSortedTeams().count let groupStageCut = groupStageCut() @@ -1043,15 +737,16 @@ defer { } func unsortedTeamsWithoutWO() -> [TeamRegistration] { - return self.tournamentStore.teamRegistrations.filter { $0.isOutOfTournament() == false } -// return Store.main.filter { $0.tournament == self.id && $0.walkOut == false } + guard let tournamentStore = self.tournamentStore else { return [] } + return tournamentStore.teamRegistrations.filter { $0.isOutOfTournament() == false } } func walkoutTeams() -> [TeamRegistration] { - return self.tournamentStore.teamRegistrations.filter { $0.walkOut == true } + guard let tournamentStore = self.tournamentStore else { return [] } + return tournamentStore.teamRegistrations.filter { $0.walkOut == true } // return Store.main.filter { $0.tournament == self.id && $0.walkOut == true } } - + func duplicates(in players: [PlayerRegistration]) -> [PlayerRegistration] { var duplicates = [PlayerRegistration]() Set(players.compactMap({ $0.licenceId })).forEach { licenceId in @@ -1062,29 +757,33 @@ defer { } return duplicates } - + func homonyms(in players: [PlayerRegistration]) -> [PlayerRegistration] { players.filter({ $0.hasHomonym() }) } - + func unsortedPlayers() -> [PlayerRegistration] { - return Array(self.tournamentStore.playerRegistrations) + guard let tournamentStore = self.tournamentStore else { return [] } + return Array(tournamentStore.playerRegistrations) } func selectedPlayers() -> [PlayerRegistration] { return self.selectedSortedTeams().flatMap { $0.unsortedPlayers() }.sorted(by: \.computedRank) } - func paidSelectedPlayers(type: PlayerRegistration.PlayerPaymentType) -> Double? { + func paidSelectedPlayers(type: PlayerPaymentType) -> Double? { if let entryFee { - return Double(self.selectedSortedTeams().flatMap { $0.unsortedPlayers() }.filter { $0.paymentType == type }.count) * entryFee + let flat = self.selectedSortedTeams().flatMap { $0.unsortedPlayers() } + let count = flat.filter { $0.paymentType == type }.count + return Double(count) * entryFee } else { return nil } } func players() -> [PlayerRegistration] { - return self.tournamentStore.playerRegistrations.sorted(by: \.computedRank) + guard let tournamentStore = self.tournamentStore else { return [] } + return tournamentStore.playerRegistrations.sorted(by: \.computedRank) } func unrankValue(for malePlayer: Bool) -> Int? { @@ -1104,12 +803,12 @@ defer { var clubName: String? { return self.eventObject()?.clubObject()?.name } - + //todo func significantPlayerCount() -> Int { return minimumPlayerPerTeam } - + func inadequatePlayers(in players: [PlayerRegistration]) -> [PlayerRegistration] { if startDate.isInCurrentYear() == false { return [] @@ -1118,7 +817,7 @@ defer { return isPlayerRankInadequate(player: player) } } - + func isPlayerRankInadequate(player: PlayerHolder) -> Bool { guard let rank = player.getRank() else { return false } let _rank = player.male ? rank : rank + PlayerRegistration.addon(for: rank, manMax: maleUnrankedValue ?? 0, womanMax: femaleUnrankedValue ?? 0) @@ -1128,7 +827,7 @@ defer { return false } } - + func ageInadequatePlayers(in players: [PlayerRegistration]) -> [PlayerRegistration] { if startDate.isInCurrentYear() == false { return [] @@ -1137,7 +836,7 @@ defer { return isPlayerAgeInadequate(player: player) } } - + func isPlayerAgeInadequate(player: PlayerHolder) -> Bool { guard let computedAge = player.computedAge else { return false } if federalTournamentAge.isAgeValid(age: computedAge) == false { @@ -1146,7 +845,7 @@ defer { return false } } - + func licenseYearValidity() -> Int { if startDate.get(.month) > 8 { return startDate.get(.year) + 1 @@ -1154,7 +853,7 @@ defer { return startDate.get(.year) } } - + func playersWithoutValidLicense(in players: [PlayerRegistration], isImported: Bool) -> [PlayerRegistration] { let licenseYearValidity = self.licenseYearValidity() return players.filter({ player in @@ -1165,20 +864,20 @@ defer { // Player is not imported: validate license and handle `isImported` flag for non-imported players let noLicenseId = player.licenceId == nil || player.licenceId?.isEmpty == true let invalidFormattedLicense = player.formattedLicense().isLicenseNumber == false - + // If global `isImported` is true, check license number as well let invalidLicenseForImportedFlag = isImported && !player.isValidLicenseNumber(year: licenseYearValidity) - + return noLicenseId || invalidFormattedLicense || invalidLicenseForImportedFlag } }) } - + func getStartDate(ofSeedIndex seedIndex: Int?) -> Date? { guard let seedIndex else { return nil } return selectedSortedTeams()[safe: seedIndex]?.callDate } - + func importTeams(_ teams: [FileImportManager.TeamHolder]) { var teamsToImport = [TeamRegistration]() let players = players().filter { $0.licenceId != nil } @@ -1203,24 +902,17 @@ defer { teamsToImport.append(newTeam) } } - - do { - try self.tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teamsToImport) - } catch { - Logger.error(error) - } - do { - try self.tournamentStore.playerRegistrations.addOrUpdate(contentOfs: teams.flatMap { $0.players }) - } catch { - Logger.error(error) + + if let tournamentStore = self.tournamentStore { + tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teamsToImport) + tournamentStore.playerRegistrations.addOrUpdate(contentOfs: teams.flatMap { $0.players }) } - if state() == .build && groupStageCount > 0 && groupStageTeams().isEmpty { setGroupStage(randomize: groupStageSortMode == .random) } } - + func maximumCourtsPerGroupSage() -> Int { if teamsPerGroupStage > 1 { return min(teamsPerGroupStage / 2, courtCount) @@ -1246,19 +938,19 @@ defer { return callDateIssue.count + duplicates.count + problematicPlayers.count + inadequatePlayers.count + playersWithoutValidLicense.count + playersMissing.count + waitingListInBracket.count + waitingListInGroupStage.count + ageInadequatePlayers.count + homonyms.count } - + func isStartDateIsDifferentThanCallDate(_ team: TeamRegistration, expectedSummonDate: Date? = nil) -> Bool { guard let summonDate = team.callDate else { return true } let expectedSummonDate : Date? = team.expectedSummonDate() ?? expectedSummonDate guard let expectedSummonDate else { return true } return Calendar.current.compare(summonDate, to: expectedSummonDate, toGranularity: .minute) != ComparisonResult.orderedSame } - + func groupStagesMatches(atStep step: Int = 0) -> [Match] { return groupStages(atStep: step).flatMap({ $0._matches() }) // return Store.main.filter(isIncluded: { $0.groupStage != nil && groupStageIds.contains($0.groupStage!) }) } - + static let defaultSorting : [MySortDescriptor] = [.keyPath(\Match.computedStartDateForSorting), .keyPath(\Match.computedOrder)] static func availableToStart(_ allMatches: [Match], in runningMatches: [Match], checkCanPlay: Bool = true) -> [Match] { @@ -1271,7 +963,7 @@ defer { #endif return allMatches.filter({ $0.isRunning() == false && $0.canBeStarted(inMatches: runningMatches, checkCanPlay: checkCanPlay) }).sorted(using: defaultSorting, order: .ascending) } - + static func runningMatches(_ allMatches: [Match]) -> [Match] { #if _DEBUG_TIME //DEBUGING TIME let start = Date() @@ -1282,7 +974,7 @@ defer { #endif return allMatches.filter({ $0.isRunning() && $0.isReady() }).sorted(using: defaultSorting, order: .ascending) } - + static func readyMatches(_ allMatches: [Match]) -> [Match] { #if _DEBUG_TIME //DEBUGING TIME let start = Date() @@ -1293,7 +985,7 @@ defer { #endif return allMatches.filter({ $0.isReady() && $0.isRunning() == false && $0.hasEnded() == false }).sorted(using: defaultSorting, order: .ascending) } - + static func matchesLeft(_ allMatches: [Match]) -> [Match] { #if _DEBUG_TIME //DEBUGING TIME let start = Date() @@ -1320,12 +1012,12 @@ defer { return Array(allMatches.filter({ $0.hasEnded() }).sorted(by: \.computedEndDateForSorting).reversed()) } } - + func teamsRanked() -> [TeamRegistration] { let selected = selectedSortedTeams().filter({ $0.finalRanking != nil }) return selected.sorted(by: \.finalRanking!, order: .ascending) } - + private func _removeStrings(from dictionary: inout [Int: [String]], stringsToRemove: [String]) { for key in dictionary.keys { if var stringArray = dictionary[key] { @@ -1344,7 +1036,7 @@ defer { let lastStep = lastStep() if rounds.isEmpty, lastStep > 0 { let groupStages = groupStages(atStep: lastStep) - + for groupStage in groupStages { let groupStageTeams = groupStage.teams(true) for teamIndex in 0.. qualifiedPerGroupStage ? groupStageAdditionalQualified : 0) print("finalRanking", team.teamLabel() , _index, baseRank, groupStageWidth) if let existingTeams = teams[_index] { @@ -1470,16 +1162,18 @@ defer { } } } - + return teams } - + func setRankings(finalRanks: [Int: [String]]) async -> [Int: [TeamRegistration]] { + guard let tournamentStore = self.tournamentStore else { return [:] } + var rankings: [Int: [TeamRegistration]] = [:] - + finalRanks.keys.sorted().forEach { rank in if let rankedTeamIds = finalRanks[rank] { - let teams: [TeamRegistration] = rankedTeamIds.compactMap { self.tournamentStore.teamRegistrations.findById($0) } + let teams: [TeamRegistration] = rankedTeamIds.compactMap { tournamentStore.teamRegistrations.findById($0) } rankings[rank] = teams } } @@ -1492,9 +1186,9 @@ defer { } } } - + do { - try self.tournamentStore.teamRegistrations.addOrUpdate(contentOfs: unsortedTeams()) + try self.tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: unsortedTeams()) } catch { Logger.error(error) } @@ -1507,10 +1201,10 @@ defer { Logger.error(error) } } - + return rankings } - + func lockRegistration() { closedRegistrationDate = Date() let count = selectedSortedTeams().count @@ -1521,26 +1215,17 @@ defer { teams.forEach { team in team.lockedWeight = team.weight } - do { - try self.tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teams) - } catch { - Logger.error(error) - } + self.tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: teams) } - + func unlockRegistration() { closedRegistrationDate = nil let teams = unsortedTeams() teams.forEach { team in team.lockedWeight = nil } - do { - try self.tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teams) - } catch { - Logger.error(error) - } + self.tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: teams) } - func updateWeights() { let teams = self.unsortedTeams() @@ -1548,17 +1233,9 @@ defer { let players = team.unsortedPlayers() players.forEach { $0.setComputedRank(in: self) } team.setWeight(from: players, inTournamentCategory: tournamentCategory) - do { - try self.tournamentStore.playerRegistrations.addOrUpdate(contentOfs: players) - } catch { - Logger.error(error) - } - } - do { - try self.tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teams) - } catch { - Logger.error(error) + self.tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: players) } + self.tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: teams) } func updateRank(to newDate: Date?, forceRefreshLockWeight: Bool, providedSources: [CSVParser]?) async throws { @@ -1580,13 +1257,13 @@ defer { if monthData == nil { async let lastRankWoman = SourceFileManager.shared.getUnrankValue(forMale: false, rankSourceDate: rankSourceDate) async let lastRankMan = SourceFileManager.shared.getUnrankValue(forMale: true, rankSourceDate: rankSourceDate) - + let formatted = URL.importDateFormatter.string(from: newDate) let newMonthData = MonthData(monthKey: formatted) - + newMonthData.maleUnrankedValue = await lastRankMan newMonthData.femaleUnrankedValue = await lastRankWoman - + do { try DataStore.shared.monthData.addOrUpdate(instance: newMonthData) } catch { @@ -1624,7 +1301,7 @@ defer { } } - try tournamentStore.playerRegistrations.addOrUpdate(contentOfs: players) + try tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: players) let unsortedTeams = unsortedTeams() unsortedTeams.forEach { team in @@ -1633,7 +1310,7 @@ defer { team.lockedWeight = team.weight } } - try tournamentStore.teamRegistrations.addOrUpdate(contentOfs: unsortedTeams) + try tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: unsortedTeams) refreshRanking = false } @@ -1641,11 +1318,11 @@ defer { func missingUnrankedValue() -> Bool { return maleUnrankedValue == nil || femaleUnrankedValue == nil } - + func findTeam(_ players: [PlayerRegistration]) -> TeamRegistration? { return unsortedTeams().first(where: { $0.includes(players: players) }) } - + func tournamentTitle(_ displayStyle: DisplayStyle = .wide, hideSenior: Bool = false) -> String { if tournamentLevel == .unlisted, displayStyle == .title { if let name { @@ -1667,7 +1344,7 @@ defer { return title } } - + func localizedTournamentType() -> String { switch tournamentLevel { case .unlisted: @@ -1676,19 +1353,19 @@ defer { return tournamentLevel.localizedLevelLabel(.short) + tournamentCategory.localizedLabel(.short) } } - + func hideWeight() -> Bool { return hideTeamsWeight } - + func isAnimation() -> Bool { federalLevelCategory == .unlisted } - + func subtitle(_ displayStyle: DisplayStyle = .wide) -> String { return name ?? "" } - + func formattedDate(_ displayStyle: DisplayStyle = .wide) -> String { switch displayStyle { case .title: @@ -1699,11 +1376,11 @@ defer { startDate.formatted(date: .numeric, time: .omitted) } } - + func qualifiedFromGroupStage() -> Int { return groupStageCount * qualifiedPerGroupStage } - + func availableQualifiedTeams() -> [TeamRegistration] { #if _DEBUG_TIME //DEBUGING TIME @@ -1716,11 +1393,11 @@ defer { return unsortedTeams().filter({ $0.qualified && $0.bracketPosition == nil }) } - + func qualifiedTeams() -> [TeamRegistration] { return unsortedTeams().filter({ $0.qualified }) } - + func moreQualifiedToDraw() -> Int { return max((qualifiedFromGroupStage() + groupStageAdditionalQualified) - qualifiedTeams().count, 0) } @@ -1744,18 +1421,18 @@ defer { return groupStages.allSatisfy({ $0.hasEnded() }) //return qualifiedTeams().count == qualifiedFromGroupStage() + groupStageAdditionalQualified } - + func groupStageLoserBracketAreOver() -> Bool { guard let groupStageLoserBracket = groupStageLoserBracket() else { return true } return groupStageLoserBracket.hasEnded() } - + fileprivate func _paymentMethodMessage() -> String? { return DataStore.shared.user.summonsAvailablePaymentMethods ?? ContactType.defaultAvailablePaymentMethods } - + var entryFeeMessage: String { if let entryFee { let message: String = "Inscription : \(entryFee.formatted(.currency(code: Locale.defaultCurrency()))) par joueur." @@ -1764,7 +1441,7 @@ defer { return "Inscription : gratuite." } } - + func umpireMail() -> [String]? { return [DataStore.shared.user.email] } @@ -1776,7 +1453,7 @@ defer { return 0.0 } } - + func paidCompletion() -> Double { let selectedPlayers = selectedPlayers() if selectedPlayers.isEmpty { return 0 } @@ -1807,7 +1484,7 @@ defer { let completionLabel = completion.isNaN ? "" : completion.formatted(.percent.precision(.fractionLength(0))) return TournamentStatus(label: label, completion: completionLabel) } - + func scheduleStatus() async -> TournamentStatus { let allMatches = allMatches() let ready = allMatches.filter({ $0.startDate != nil }) @@ -1817,7 +1494,7 @@ defer { let completionLabel = completion.isNaN ? "" : completion.formatted(.percent.precision(.fractionLength(0))) return TournamentStatus(label: label, completion: completionLabel) } - + func callStatus() async -> TournamentStatus { let selectedSortedTeams = selectedSortedTeams() let called = selectedSortedTeams.filter { isStartDateIsDifferentThanCallDate($0) == false } @@ -1828,7 +1505,7 @@ defer { let completionLabel = completion.isNaN ? "" : completion.formatted(.percent.precision(.fractionLength(0))) return TournamentStatus(label: label, completion: completionLabel) } - + func confirmedSummonStatus() async -> TournamentStatus { let selectedSortedTeams = selectedSortedTeams() let called = selectedSortedTeams.filter { $0.confirmationDate != nil } @@ -1837,7 +1514,7 @@ defer { let completionLabel = completion.isNaN ? "" : completion.formatted(.percent.precision(.fractionLength(0))) return TournamentStatus(label: label, completion: completionLabel) } - + func bracketStatus() async -> (status: String, description: String?, cut: TeamRegistration.TeamRange?) { let availableSeeds = availableSeeds() var description: String? = nil @@ -1850,32 +1527,32 @@ defer { description = "placer \(availableQualifiedTeams.count) qualifié" + availableQualifiedTeams.count.pluralSuffix } } - + var cut: TeamRegistration.TeamRange? = nil if description == nil && isAnimation() == false { cut = TeamRegistration.TeamRange(availableSeeds.first, availableSeeds.last) } - + if let round = getActiveRound() { return ([round.roundTitle(.short), round.roundStatus()].joined(separator: " ").lowercased(), description, cut) } else { return ("", description, nil) } } - + func groupStageStatus() async -> (status: String, cut: TeamRegistration.TeamRange?) { let groupStageTeams = groupStageTeams() let groupStageTeamsCount = groupStageTeams.count if groupStageTeamsCount == 0 || groupStageTeamsCount != groupStageSpots() { return ("à compléter", nil) } - + let cut : TeamRegistration.TeamRange? = isAnimation() ? nil : TeamRegistration.TeamRange(groupStageTeams.first, groupStageTeams.last) - + let runningGroupStages = groupStages().filter({ $0.isRunning() }) if groupStagesAreOver() { return ("terminées", cut) } if runningGroupStages.isEmpty { - + let ongoingGroupStages = runningGroupStages.filter({ $0.hasStarted() && $0.hasEnded() == false }) if ongoingGroupStages.isEmpty == false { return ("Poule" + ongoingGroupStages.count.pluralSuffix + " " + ongoingGroupStages.map { ($0.index + 1).formatted() }.joined(separator: ", ") + " en cours", cut) @@ -1889,7 +1566,7 @@ defer { func settingsDescriptionLocalizedLabel() -> String { [courtCount.formatted() + " terrain\(courtCount.pluralSuffix)", entryFeeMessage].joined(separator: ", ") } - + func structureDescriptionLocalizedLabel() -> String { let groupStageLabel: String? = groupStageCount > 0 ? groupStageCount.formatted() + " poule\(groupStageCount.pluralSuffix)" : nil return [teamCount.formatted() + " équipes", groupStageLabel].compactMap({ $0 }).joined(separator: ", ") @@ -1899,7 +1576,7 @@ defer { resetBracketPosition() deleteStructure() deleteGroupStages() - + switch preset { case .doubleGroupStage: buildGroupStages() @@ -1911,7 +1588,7 @@ defer { buildBracket() } } - + func addWildCardIfNeeded(_ count: Int, _ type: MatchType) { let currentCount = selectedSortedTeams().filter({ if type == .bracket { @@ -1920,13 +1597,13 @@ defer { return $0.wildCardGroupStage } }).count - + if currentCount < count { let _diff = count - currentCount addWildCard(_diff, type) } } - + func addWildCard(_ count: Int, _ type: MatchType) { let wcs = (0.. Int { let bracketTeamCount = teamCount - (teamsPerGroupStage - qualifiedPerGroupStage) * groupStageCount + (groupStageAdditionalQualified * (groupStageCount > 0 ? 1 : 0)) return bracketTeamCount @@ -1992,17 +1667,16 @@ defer { let roundCount = RoundRule.numberOfRounds(forTeams: minimalBracketTeamCount ?? bracketTeamCount()) let matchCount = RoundRule.numberOfMatches(forTeams: minimalBracketTeamCount ?? bracketTeamCount()) - let rounds = (0.. Bool { return entryFee == nil || entryFee == 0 } - + func indexOf(team: TeamRegistration) -> Int? { return selectedSortedTeams().firstIndex(where: { $0.id == team.id }) } - + func labelIndexOf(team: TeamRegistration) -> String? { if let teamIndex = indexOf(team: team) { return "Tête de série #" + (teamIndex + 1).formatted() @@ -2151,7 +1809,7 @@ defer { return nil } } - + func addTeam(_ players: Set, registrationDate: Date? = nil, name: String? = nil) -> TeamRegistration { let team = TeamRegistration(tournament: id, registrationDate: registrationDate, name: name) team.setWeight(from: Array(players), inTournamentCategory: tournamentCategory) @@ -2174,7 +1832,7 @@ defer { roundFormat = newValue } } - + var groupStageMatchFormat: MatchFormat { get { groupStageFormat ?? .defaultFormatForMatchType(.groupStage) @@ -2183,7 +1841,7 @@ defer { groupStageFormat = newValue } } - + var loserBracketMatchFormat: MatchFormat { get { loserRoundFormat ?? .defaultFormatForMatchType(.loserBracket) @@ -2192,7 +1850,7 @@ defer { loserRoundFormat = newValue } } - + var groupStageOrderingMode: GroupStageOrderingMode { get { groupStageSortMode @@ -2201,7 +1859,7 @@ defer { groupStageSortMode = newValue } } - + var tournamentCategory: TournamentCategory { get { federalCategory @@ -2215,7 +1873,7 @@ defer { } } } - + var tournamentLevel: TournamentLevel { get { federalLevelCategory @@ -2228,7 +1886,7 @@ defer { matchFormat = roundSmartMatchFormat(5) } } - + var federalTournamentAge: FederalTournamentAge { get { federalAgeCategory @@ -2237,7 +1895,7 @@ defer { federalAgeCategory = newValue } } - + func loserBracketSmartMatchFormat(_ roundIndex: Int) -> MatchFormat { let format = tournamentLevel.federalFormatForLoserBracketRound(roundIndex) if tournamentLevel == .p25 { return .superTie } @@ -2247,7 +1905,7 @@ defer { return loserBracketMatchFormat } } - + func groupStageSmartMatchFormat() -> MatchFormat { let format = tournamentLevel.federalFormatForGroupStage() if tournamentLevel == .p25 { return .superTie } @@ -2268,10 +1926,10 @@ defer { //enableOnlineRegistration = true registrationDateLimit = deadline(for: .inscription) } - + //self.customizeUsingPreferences() } - + func customizeUsingPreferences() { guard let lastTournamentWithSameBuild = DataStore.shared.tournaments.filter({ tournament in tournament.tournamentLevel == self.tournamentLevel @@ -2288,11 +1946,11 @@ defer { self.teamCount = (lastTournamentWithSameBuild.teamCount / 2) * 2 self.enableOnlineRegistration = lastTournamentWithSameBuild.enableOnlineRegistration } - + func onlineRegistrationCanBeEnabled() -> Bool { isAnimation() == false } - + func roundSmartMatchFormat(_ roundIndex: Int) -> MatchFormat { let format = tournamentLevel.federalFormatForBracketRound(roundIndex) if tournamentLevel == .p25 { return .superTie } @@ -2302,7 +1960,7 @@ defer { return matchFormat } } - + private func _defaultSorting() -> [MySortDescriptor] { switch teamSorting { case .rank: @@ -2311,7 +1969,7 @@ defer { [.keyPath(\TeamRegistration.registrationDate!), .keyPath(\TeamRegistration.initialWeight), .keyPath(\TeamRegistration.id)] } } - + func isSameBuild(_ build: any TournamentBuildHolder) -> Bool { tournamentLevel == build.level && tournamentCategory == build.category @@ -2321,28 +1979,29 @@ defer { private let _currentSelectionSorting : [MySortDescriptor] = [.keyPath(\.weight), .keyPath(\.id)] private func _matchSchedulers() -> [MatchScheduler] { - return self.tournamentStore.matchSchedulers.filter { $0.tournament == self.id } + guard let tournamentStore = self.tournamentStore else { return [] } + return tournamentStore.matchSchedulers.filter { $0.tournament == self.id } // DataStore.shared.matchSchedulers.filter(isIncluded: { $0.tournament == self.id }) } func matchScheduler() -> MatchScheduler? { return self._matchSchedulers().first } - + func courtsAvailable() -> [Int] { (0.. MonthData? { guard let rankSourceDate else { return nil } let dateString = URL.importDateFormatter.string(from: rankSourceDate) return DataStore.shared.monthData.first(where: { $0.monthKey == dateString }) } - + var maleUnrankedValue: Int? { return currentMonthData()?.maleUnrankedValue } - + var femaleUnrankedValue: Int? { return currentMonthData()?.femaleUnrankedValue } @@ -2350,16 +2009,16 @@ defer { func courtNameIfAvailable(atIndex courtIndex: Int) -> String? { return club()?.customizedCourts.first(where: { $0.index == courtIndex })?.name } - + func courtName(atIndex courtIndex: Int) -> String { return courtNameIfAvailable(atIndex: courtIndex) ?? Court.courtIndexedTitle(atIndex: courtIndex) } - + func tournamentWinner() -> TeamRegistration? { - let finals: Round? = self.tournamentStore.rounds.first(where: { $0.index == 0 && $0.isUpperBracket() }) + let finals: Round? = self.tournamentStore?.rounds.first(where: { $0.index == 0 && $0.isUpperBracket() }) return finals?.playedMatches().first?.winner() } - + func getGroupStageChunkValue() -> Int { if groupStageCount > 0 && teamsPerGroupStage >= 2 { let result = courtCount / (teamsPerGroupStage / 2) @@ -2370,7 +2029,7 @@ defer { return 1 } } - + func replacementRangeExtended(groupStagePosition: Int) -> TeamRegistration.TeamRange? { let selectedSortedTeams = selectedSortedTeams() var left: TeamRegistration? = nil @@ -2390,7 +2049,7 @@ defer { return (left: left, right: right) } - + typealias TeamPlacementIssue = (shouldBeInIt: [String], shouldNotBeInIt: [String]) func groupStageTeamPlacementIssue() -> TeamPlacementIssue { let selected = selectedSortedTeams() @@ -2403,7 +2062,7 @@ defer { let shouldNotBeInIt = Set(groupIds).subtracting(selectedIds) return (Array(shouldBeInIt), Array(shouldNotBeInIt)) } - + func bracketTeamPlacementIssue() -> TeamPlacementIssue { let selected = selectedSortedTeams() let allTeams = unsortedTeams() @@ -2417,50 +2076,46 @@ defer { let shouldNotBeInIt = Set(groupIds).subtracting(selectedIds) return (Array(shouldBeInIt), Array(shouldNotBeInIt)) } - + func groupStageLoserBracket() -> Round? { - tournamentStore.rounds.first(where: { $0.groupStageLoserBracket }) + self.tournamentStore?.rounds.first(where: { $0.groupStageLoserBracket }) } - + func groupStageLoserBracketsInitialPlace() -> Int { return teamCount - teamsPerGroupStage * groupStageCount + qualifiedPerGroupStage * groupStageCount + groupStageAdditionalQualified + 1 } - + func addNewGroupStageStep() { let lastStep = lastStep() + 1 for i in 0.. Int { - self.tournamentStore.groupStages.sorted(by: \.step).last?.step ?? 0 + self.tournamentStore?.groupStages.sorted(by: \.step).last?.step ?? 0 } - + func generateSmartLoserGroupStageBracket() { guard let groupStageLoserBracket = groupStageLoserBracket() else { return } for i in qualifiedPerGroupStage.. [Match] { rounds().flatMap { $0.loserRoundsAndChildren().flatMap({ $0._matches() }) } } @@ -2483,23 +2138,24 @@ defer { func seedsCount() -> Int { selectedSortedTeams().count - groupStageSpots() } - + func lastDrawnDate() -> Date? { drawLogs().last?.drawDate } - + func drawLogs() -> [DrawLog] { - self.tournamentStore.drawLogs.sorted(by: \.drawDate) + guard let tournamentStore = self.tournamentStore else { return [] } + return tournamentStore.drawLogs.sorted(by: \.drawDate) } - + func seedSpotsLeft() -> Bool { let alreadySeededRounds = rounds().filter({ $0.seeds().isEmpty == false }) if alreadySeededRounds.isEmpty { return true } let spotsLeft = alreadySeededRounds.flatMap({ $0.playedMatches() }).filter { $0.isEmpty() || $0.isValidSpot() } - + return spotsLeft.isEmpty == false } - + func isRoundValidForSeeding(roundIndex: Int) -> Bool { if let lastRoundWithSeeds = rounds().last(where: { $0.seeds().isEmpty == false }) { return roundIndex >= lastRoundWithSeeds.index @@ -2508,7 +2164,7 @@ defer { } } - + func updateSeedsBracketPosition() async { await removeAllSeeds(saveTeamsAtTheEnd: false) let drawLogs = drawLogs().reversed() @@ -2521,9 +2177,9 @@ defer { } } } - + do { - try tournamentStore.teamRegistrations.addOrUpdate(contentOfs: seeds) + try tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: seeds) } catch { Logger.error(error) } @@ -2551,26 +2207,20 @@ defer { } do { - try tournamentStore.teamScores.delete(contentOfs: ts) - } catch { - Logger.error(error) - } - - do { - try tournamentStore.matches.addOrUpdate(contentOfs: allMatches) + try tournamentStore?.teamScores.delete(contentOfs: ts) } catch { Logger.error(error) } if saveTeamsAtTheEnd { do { - try tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teams) + try tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: teams) } catch { Logger.error(error) } } } - + func addNewRound(_ roundIndex: Int) async { let round = Round(tournament: id, index: roundIndex, matchFormat: matchFormat) let matchCount = RoundRule.numberOfMatches(forRoundIndex: roundIndex) @@ -2579,8 +2229,8 @@ defer { var currentIndex = 0 let matches = (0.. String { var logs : [String] = ["Journal des tirages\n\n"] logs.append(drawLogs().map { $0.exportedDrawLog() }.joined(separator: "\n\n")) return logs.joined() } - - + + func courtUnavailable(courtIndex: Int, from startDate: Date, to endDate: Date) -> Bool { guard let source = eventObject()?.courtsUnavailability else { return false } let courtLockedSchedule = source.filter({ $0.courtIndex == courtIndex }) @@ -2660,7 +2310,7 @@ defer { } let currentTeamCount = unsortedTeamsWithoutWO().count - + if currentTeamCount >= teamCount { if let waitingListLimit = waitingListLimit { let waitingListCount = currentTeamCount - teamCount @@ -2673,9 +2323,8 @@ defer { return .open } - + // MARK: - Status - func shouldTournamentBeOver() async -> Bool { #if _DEBUGING_TIME //DEBUGING TIME let start = Date() @@ -2687,16 +2336,16 @@ defer { if isDeleted == false && hasEnded() == false && hasStarted() { let allMatches = allMatches() let remainingMatches = allMatches.filter({ $0.hasEnded() == false && $0.startDate != nil }) - + let calendar = Calendar.current let anyTomorrow = remainingMatches.anySatisfy({ calendar.isDateInTomorrow($0.startDate!) }) - - + + if anyTomorrow == false, let endDate = allMatches.filter({ $0.hasEnded() }).sorted(by: \.endDate!, order: .ascending).last?.endDate, endDate.timeIntervalSinceNow <= -2 * 3600 { return true } } - + return false } @@ -2717,15 +2366,15 @@ defer { } func refreshTeamList() async { - guard StoreCenter.main.hasToken() else { return } + guard StoreCenter.main.isAuthenticated else { return } guard shouldRefreshTeams(), refreshInProgress == false, enableOnlineRegistration, hasEnded() == false else { return } await MainActor.run { refreshInProgress = true } do { - try await self.tournamentStore.playerRegistrations.loadDataFromServerIfAllowed(clear: true) - try await self.tournamentStore.teamScores.loadDataFromServerIfAllowed(clear: true) - try await self.tournamentStore.teamRegistrations.loadDataFromServerIfAllowed(clear: true) + try await self.tournamentStore?.playerRegistrations.loadDataFromServerIfAllowed(clear: true) + try await self.tournamentStore?.teamScores.loadDataFromServerIfAllowed(clear: true) + try await self.tournamentStore?.teamRegistrations.loadDataFromServerIfAllowed(clear: true) await MainActor.run { refreshInProgress = false lastTeamRefresh = Date() @@ -2740,42 +2389,42 @@ defer { } // MARK: - - + func insertOnServer() throws { - - DataStore.shared.tournaments.writeChangeAndInsertOnServer(instance: self) - for teamRegistration in self.tournamentStore.teamRegistrations { - teamRegistration.insertOnServer() - } - for groupStage in self.tournamentStore.groupStages { - groupStage.insertOnServer() - } - for round in self.tournamentStore.rounds { - round.insertOnServer() - } +// DataStore.shared.tournaments.writeChangeAndInsertOnServer(instance: self) +// +// for teamRegistration in self.tournamentStore?.teamRegistrations { +// teamRegistration.insertOnServer() +// } +// for groupStage in self.tournamentStore?.groupStages { +// groupStage.insertOnServer() +// } +// for round in self.tournamentStore.rounds { +// round.insertOnServer() +// } } - + // MARK: - Payments & Crypto - + enum PaymentError: Error { case cantPayTournament } - + func payIfNecessary() throws { if self.payment != nil { return } if let payment = Guard.main.paymentForNewTournament() { self.payment = payment - try DataStore.shared.tournaments.addOrUpdate(instance: self) + DataStore.shared.tournaments.addOrUpdate(instance: self) return } throw PaymentError.cantPayTournament } - + } -fileprivate extension Bool { +extension Bool { var encodedValue: Int { switch self { case true: @@ -2830,16 +2479,6 @@ fileprivate extension Bool { // } //} -extension Tournament: Hashable { - static func == (lhs: Tournament, rhs: Tournament) -> Bool { - lhs.id == rhs.id - } - - func hash(into hasher: inout Hasher) { - hasher.combine(id) - } -} - extension Tournament: FederalTournamentHolder { func tournamentTitle(_ displayStyle: DisplayStyle, forBuild build: any TournamentBuildHolder) -> String { if isAnimation() { @@ -2857,13 +2496,13 @@ extension Tournament: FederalTournamentHolder { var codeClub: String? { club()?.code } - + var holderId: String { id } func clubLabel() -> String { locationLabel() } - + func subtitleLabel(forBuild build: any TournamentBuildHolder) -> String { if isAnimation() { if displayAgeAndCategory(forBuild: build) == false { @@ -2883,7 +2522,7 @@ extension Tournament: FederalTournamentHolder { self ] } - + var dayPeriod: DayPeriod { let day = startDate.get(.weekday) switch day { @@ -2893,7 +2532,7 @@ extension Tournament: FederalTournamentHolder { return .weekend } } - + func displayAgeAndCategory(forBuild build: any TournamentBuildHolder) -> Bool { if isAnimation() { if let name, name.count < DeviceHelper.maxCharacter() { @@ -2912,15 +2551,15 @@ extension Tournament: TournamentBuildHolder { func buildHolderTitle(_ displayStyle: DisplayStyle) -> String { tournamentTitle(.short) } - + var category: TournamentCategory { tournamentCategory } - + var level: TournamentLevel { tournamentLevel } - + var age: FederalTournamentAge { federalTournamentAge } @@ -2936,9 +2575,9 @@ extension Tournament { let rankSourceDate = _mostRecentDateAvailable let tournaments : [Tournament] = DataStore.shared.tournaments.filter { $0.endDate != nil && $0.isDeleted == false }.sorted(by: \.endDate!, order: .descending) - + var shouldBePrivate = tournaments.first?.isPrivate ?? true - + if Guard.main.currentPlan == .monthlyUnlimited { shouldBePrivate = false } else if Guard.main.purchasedTransactions.isEmpty == false { @@ -2955,7 +2594,7 @@ extension Tournament { static func fake() -> Tournament { return Tournament(event: "Roland Garros", name: "Magic P100", startDate: Date(), endDate: Date(), creationDate: Date(), isPrivate: false, groupStageFormat: .nineGames, roundFormat: nil, loserRoundFormat: nil, groupStageSortMode: .snake, groupStageCount: 4, rankSourceDate: nil, dayDuration: 2, teamCount: 24, teamSorting: .rank, federalCategory: .men, federalLevelCategory: .p100, federalAgeCategory: .a45, closedRegistrationDate: nil, groupStageAdditionalQualified: 0, courtCount: 4, prioritizeClubMembers: false, qualifiedPerGroupStage: 2, teamsPerGroupStage: 4, entryFee: nil) } - + } extension Tournament { @@ -2970,3 +2609,18 @@ extension Tournament { return nil } } + +/// Warning: if the enum has more than 10 cases, the payment algo is broken +enum TournamentPayment: Int, CaseIterable { + case free, unit, subscriptionUnit, unlimited + + var isSubscription: Bool { + switch self { + case .subscriptionUnit, .unlimited: + return true + default: + return false + } + } + +} diff --git a/PadelClub/Data/TournamentLibrary.swift b/PadelClub/Data/TournamentLibrary.swift new file mode 100644 index 0000000..062083f --- /dev/null +++ b/PadelClub/Data/TournamentLibrary.swift @@ -0,0 +1,33 @@ +// +// TournamentLibrary.swift +// PadelClub +// +// Created by Laurent Morvillier on 11/11/2024. +// + +import Foundation +import LeStorage + +class TournamentLibrary { + + static let shared: TournamentLibrary = TournamentLibrary() + + fileprivate var _stores: [String : TournamentStore] = [:] + + func store(tournamentId: String) -> TournamentStore? { + guard let tournament = DataStore.shared.tournaments.first(where: { $0.id == tournamentId }) else { return nil } + + if let store = self._stores[tournamentId] { + return store + } + let store = StoreCenter.main.store(identifier: tournamentId) + let tournamentStore = TournamentStore(store: store) + self._stores[tournamentId] = tournamentStore + return tournamentStore + } + + func reset() { + self._stores.removeAll() + } + +} diff --git a/PadelClub/Data/TournamentStore.swift b/PadelClub/Data/TournamentStore.swift index 78e593b..3168ccd 100644 --- a/PadelClub/Data/TournamentStore.swift +++ b/PadelClub/Data/TournamentStore.swift @@ -9,14 +9,9 @@ import Foundation import LeStorage import SwiftUI -class TournamentStore: Store, ObservableObject { +class TournamentStore: ObservableObject { - static func instance(tournamentId: String) -> TournamentStore { -// if StoreCenter.main.userId == nil { -// fatalError("cant request store without id") -// } - return StoreCenter.main.store(identifier: tournamentId, parameter: "tournament") - } + var store: Store fileprivate(set) var groupStages: StoredCollection = StoredCollection.placeholder() fileprivate(set) var matches: StoredCollection = StoredCollection.placeholder() @@ -28,13 +23,18 @@ class TournamentStore: Store, ObservableObject { fileprivate(set) var matchSchedulers: StoredCollection = StoredCollection.placeholder() fileprivate(set) var drawLogs: StoredCollection = StoredCollection.placeholder() - convenience init(tournament: Tournament) { - self.init(identifier: tournament.id, parameter: "tournament") +// convenience init(tournament: Tournament) { +// let store = StoreCenter.main.store(identifier: tournament.id) +// self.init(store: store) +// self._initialize() +// } + + init(store: Store) { + self.store = store + self._initialize() } - required init(identifier: String, parameter: String) { - - super.init(identifier: identifier, parameter: parameter) + fileprivate func _initialize() { var synchronized: Bool = true let indexed: Bool = true @@ -45,17 +45,31 @@ class TournamentStore: Store, ObservableObject { } #endif - self.groupStages = self.registerCollection(synchronized: synchronized, indexed: indexed) - self.rounds = self.registerCollection(synchronized: synchronized, indexed: indexed) - self.teamRegistrations = self.registerCollection(synchronized: synchronized, indexed: indexed) - self.playerRegistrations = self.registerCollection(synchronized: synchronized, indexed: indexed) - self.matches = self.registerCollection(synchronized: synchronized, indexed: indexed) - self.teamScores = self.registerCollection(synchronized: synchronized, indexed: indexed) - self.matchSchedulers = self.registerCollection(synchronized: false, indexed: indexed) - self.drawLogs = self.registerCollection(synchronized: synchronized, indexed: indexed) - - self.loadCollectionsFromServerIfNoFile() + self.groupStages = self.store.registerSynchronizedCollection(indexed: indexed) + self.rounds = self.store.registerSynchronizedCollection(indexed: indexed) + self.teamRegistrations = self.store.registerSynchronizedCollection(indexed: indexed) + self.playerRegistrations = self.store.registerSynchronizedCollection(indexed: indexed) + self.matches = self.store.registerSynchronizedCollection(indexed: indexed) + self.teamScores = self.store.registerSynchronizedCollection(indexed: indexed) + self.matchSchedulers = self.store.registerCollection(indexed: indexed) + self.drawLogs = self.store.registerCollection(indexed: indexed) + + self.store.loadCollectionsFromServerIfNoFile() + NotificationCenter.default.addObserver( + self, + selector: #selector(_leStorageDidSynchronize), + name: NSNotification.Name.LeStorageDidSynchronize, + object: nil) + + } + + @objc func _leStorageDidSynchronize(notification: Notification) { +// Logger.log("SYNCED > teamRegistrations count = \(self.teamRegistrations.count)") + } + + deinit { + NotificationCenter.default.removeObserver(self) } } diff --git a/PadelClub/Data/User.swift b/PadelClub/Data/User.swift deleted file mode 100644 index 06ff5bd..0000000 --- a/PadelClub/Data/User.swift +++ /dev/null @@ -1,250 +0,0 @@ -// -// User.swift -// PadelClub -// -// Created by Laurent Morvillier on 21/02/2024. -// - -import Foundation -import LeStorage - -enum UserRight: Int, Codable { - case none = 0 - case edition = 1 - case creation = 2 -} - -@Observable -class User: ModelObject, UserBase, Storable { - - static func resourceName() -> String { "users" } - static func tokenExemptedMethods() -> [HTTPMethod] { return [.post] } - static func filterByStoreIdentifier() -> Bool { return false } - static var relationshipNames: [String] = [] - - public var id: String = Store.randomId() - public var username: String - public var email: String - var clubs: [String] = [] - var umpireCode: String? - var licenceId: String? - var firstName: String - var lastName: String - var phone: String? - var country: String? - - var summonsMessageBody : String? = nil - var summonsMessageSignature: String? = nil - var summonsAvailablePaymentMethods: String? = nil - var summonsDisplayFormat: Bool = false - var summonsDisplayEntryFee: Bool = false - var summonsUseFullCustomMessage: Bool = false - var matchFormatsDefaultDuration: [MatchFormat: Int]? = nil - var bracketMatchFormatPreference: MatchFormat? - var groupStageMatchFormatPreference: MatchFormat? - var loserBracketMatchFormatPreference: MatchFormat? - var loserBracketMode: LoserBracketMode = .automatic - - var deviceId: String? - - init(username: String, email: String, firstName: String, lastName: String, phone: String?, country: String?, loserBracketMode: LoserBracketMode = .automatic) { - self.username = username - self.firstName = firstName - self.lastName = lastName - self.email = email - self.phone = phone - self.country = country - self.loserBracketMode = loserBracketMode - } - - public func uuid() throws -> UUID { - if let uuid = UUID(uuidString: self.id) { - return uuid - } - throw UUIDError.cantConvertString(string: self.id) - } - - func currentPlayerData() -> ImportedPlayer? { - guard let licenceId else { return nil } - let federalContext = PersistenceController.shared.localContainer.viewContext - let fetchRequest = ImportedPlayer.fetchRequest() - let predicate = NSPredicate(format: "license == %@", licenceId) - fetchRequest.predicate = predicate - return try? federalContext.fetch(fetchRequest).first - } - - func defaultSignature() -> String { - return "Sportivement,\n\(firstName) \(lastName), votre JAP." - } - - func fullName() -> String? { - guard firstName.isEmpty == false && lastName.isEmpty == false else { - return nil - } - return "\(firstName) \(lastName)" - } - - func hasTenupClubs() -> Bool { - self.clubsObjects().filter({ $0.code != nil }).isEmpty == false - } - - func hasFavoriteClubsAndCreatedClubs() -> Bool { - clubsObjects(includeCreated: true).isEmpty == false - } - - func setUserClub(_ userClub: Club) { - self.clubs.insert(userClub.id, at: 0) - } - - func clubsObjects(includeCreated: Bool = false) -> [Club] { - return DataStore.shared.clubs.filter({ (includeCreated && $0.creator == id) || clubs.contains($0.id) }) - } - - func createdClubsObjectsNotFavorite() -> [Club] { - return DataStore.shared.clubs.filter({ ($0.creator == id) && clubs.contains($0.id) == false }) - } - - func saveMatchFormatsDefaultDuration(_ matchFormat: MatchFormat, estimatedDuration: Int) { - if estimatedDuration == matchFormat.defaultEstimatedDuration { - matchFormatsDefaultDuration?.removeValue(forKey: matchFormat) - } else { - matchFormatsDefaultDuration = matchFormatsDefaultDuration ?? [MatchFormat: Int]() - matchFormatsDefaultDuration?[matchFormat] = estimatedDuration - } - } - - func addClub(_ club: Club) { - if !self.clubs.contains(where: { $0.id == club.id }) { - self.clubs.append(club.id) - } - } - - enum CodingKeys: String, CodingKey { - case _id = "id" - case _username = "username" - case _email = "email" - case _clubs = "clubs" - case _umpireCode = "umpireCode" - case _licenceId = "licenceId" - case _firstName = "firstName" - case _lastName = "lastName" - case _phone = "phone" - case _country = "country" - case _summonsMessageBody = "summonsMessageBody" - case _summonsMessageSignature = "summonsMessageSignature" - case _summonsAvailablePaymentMethods = "summonsAvailablePaymentMethods" - case _summonsDisplayFormat = "summonsDisplayFormat" - case _summonsDisplayEntryFee = "summonsDisplayEntryFee" - case _summonsUseFullCustomMessage = "summonsUseFullCustomMessage" - case _matchFormatsDefaultDuration = "matchFormatsDefaultDuration" - case _bracketMatchFormatPreference = "bracketMatchFormatPreference" - case _groupStageMatchFormatPreference = "groupStageMatchFormatPreference" - case _loserBracketMatchFormatPreference = "loserBracketMatchFormatPreference" - case _deviceId = "deviceId" - case _loserBracketMode = "loserBracketMode" - } - - public required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - // Required properties - id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() - username = try container.decode(String.self, forKey: ._username) - email = try container.decode(String.self, forKey: ._email) - firstName = try container.decode(String.self, forKey: ._firstName) - lastName = try container.decode(String.self, forKey: ._lastName) - - // Optional properties - clubs = try container.decodeIfPresent([String].self, forKey: ._clubs) ?? [] - umpireCode = try container.decodeIfPresent(String.self, forKey: ._umpireCode) - licenceId = try container.decodeIfPresent(String.self, forKey: ._licenceId) - phone = try container.decodeIfPresent(String.self, forKey: ._phone) - country = try container.decodeIfPresent(String.self, forKey: ._country) - - // Summons-related properties - summonsMessageBody = try container.decodeIfPresent(String.self, forKey: ._summonsMessageBody) - summonsMessageSignature = try container.decodeIfPresent(String.self, forKey: ._summonsMessageSignature) - summonsAvailablePaymentMethods = try container.decodeIfPresent(String.self, forKey: ._summonsAvailablePaymentMethods) - summonsDisplayFormat = try container.decodeIfPresent(Bool.self, forKey: ._summonsDisplayFormat) ?? false - summonsDisplayEntryFee = try container.decodeIfPresent(Bool.self, forKey: ._summonsDisplayEntryFee) ?? false - summonsUseFullCustomMessage = try container.decodeIfPresent(Bool.self, forKey: ._summonsUseFullCustomMessage) ?? false - - // Match-related properties - matchFormatsDefaultDuration = try container.decodeIfPresent([MatchFormat: Int].self, forKey: ._matchFormatsDefaultDuration) - bracketMatchFormatPreference = try container.decodeIfPresent(MatchFormat.self, forKey: ._bracketMatchFormatPreference) - groupStageMatchFormatPreference = try container.decodeIfPresent(MatchFormat.self, forKey: ._groupStageMatchFormatPreference) - loserBracketMatchFormatPreference = try container.decodeIfPresent(MatchFormat.self, forKey: ._loserBracketMatchFormatPreference) - loserBracketMode = try container.decodeIfPresent(LoserBracketMode.self, forKey: ._loserBracketMode) ?? .automatic - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(id, forKey: ._id) - try container.encode(username, forKey: ._username) - try container.encode(email, forKey: ._email) - try container.encode(clubs, forKey: ._clubs) - - try container.encode(umpireCode, forKey: ._umpireCode) - try container.encode(licenceId, forKey: ._licenceId) - try container.encode(firstName, forKey: ._firstName) - try container.encode(lastName, forKey: ._lastName) - try container.encode(phone, forKey: ._phone) - try container.encode(country, forKey: ._country) - try container.encode(summonsMessageBody, forKey: ._summonsMessageBody) - try container.encode(summonsMessageSignature, forKey: ._summonsMessageSignature) - try container.encode(summonsAvailablePaymentMethods, forKey: ._summonsAvailablePaymentMethods) - try container.encode(summonsDisplayFormat, forKey: ._summonsDisplayFormat) - try container.encode(summonsDisplayEntryFee, forKey: ._summonsDisplayEntryFee) - try container.encode(summonsUseFullCustomMessage, forKey: ._summonsUseFullCustomMessage) - - try container.encode(matchFormatsDefaultDuration, forKey: ._matchFormatsDefaultDuration) - try container.encode(bracketMatchFormatPreference, forKey: ._bracketMatchFormatPreference) - try container.encode(groupStageMatchFormatPreference, forKey: ._groupStageMatchFormatPreference) - try container.encode(loserBracketMatchFormatPreference, forKey: ._loserBracketMatchFormatPreference) - try container.encode(deviceId, forKey: ._deviceId) - - try container.encode(loserBracketMode, forKey: ._loserBracketMode) - } - - static func placeHolder() -> User { - return User(username: "", email: "", firstName: "", lastName: "", phone: nil, country: nil) - } - -} - -class UserCreationForm: User, UserPasswordBase { - - init(user: User, username: String, password: String, firstName: String, lastName: String, email: String, phone: String?, country: String?) { - self.password = password - super.init(username: username, email: email, firstName: firstName, lastName: lastName, phone: phone, country: country) - - self.summonsMessageBody = user.summonsMessageBody - self.summonsMessageSignature = user.summonsMessageSignature - self.summonsAvailablePaymentMethods = user.summonsAvailablePaymentMethods - self.summonsDisplayFormat = user.summonsDisplayFormat - self.summonsDisplayEntryFee = user.summonsDisplayEntryFee - self.summonsUseFullCustomMessage = user.summonsUseFullCustomMessage - self.matchFormatsDefaultDuration = user.matchFormatsDefaultDuration - self.bracketMatchFormatPreference = user.bracketMatchFormatPreference - self.groupStageMatchFormatPreference = user.groupStageMatchFormatPreference - self.loserBracketMatchFormatPreference = user.loserBracketMatchFormatPreference - - } - - required init(from decoder: Decoder) throws { - fatalError("init(from:) has not been implemented") - } - - public var password: String - - private enum CodingKeys: String, CodingKey { - case password - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(self.password, forKey: .password) - } -} diff --git a/PadelClub/Extensions/CodingContainer+Extensions.swift b/PadelClub/Extensions/CodingContainer+Extensions.swift index 3f05814..f0b63e6 100644 --- a/PadelClub/Extensions/CodingContainer+Extensions.swift +++ b/PadelClub/Extensions/CodingContainer+Extensions.swift @@ -26,14 +26,18 @@ extension KeyedDecodingContainer { } extension KeyedEncodingContainer { + + mutating func encodeAndEncrypt(_ value: Data, forKey key: Key) throws { + let encryped: Data = try value.encrypt(pass: CryptoKey.pass.rawValue) + try self.encode(encryped, forKey: key) + } mutating func encodeAndEncryptIfPresent(_ value: Data?, forKey key: Key) throws { guard let value else { try encodeNil(forKey: key) return } - let encryped: Data = try value.encrypt(pass: CryptoKey.pass.rawValue) - try self.encode(encryped, forKey: key) + try self.encodeAndEncrypt(value, forKey: key) } } diff --git a/PadelClub/Extensions/NumberFormatter+Extensions.swift b/PadelClub/Extensions/NumberFormatter+Extensions.swift index e7f2262..691a7f5 100644 --- a/PadelClub/Extensions/NumberFormatter+Extensions.swift +++ b/PadelClub/Extensions/NumberFormatter+Extensions.swift @@ -13,4 +13,8 @@ extension NumberFormatter { formatter.numberStyle = .ordinal return formatter } + + static var standard: NumberFormatter { + return NumberFormatter() + } } diff --git a/PadelClub/Utils/FileImportManager.swift b/PadelClub/Utils/FileImportManager.swift index f1c83a5..bc87eb6 100644 --- a/PadelClub/Utils/FileImportManager.swift +++ b/PadelClub/Utils/FileImportManager.swift @@ -450,7 +450,7 @@ class FileImportManager { let data = player.components(separatedBy: separator) let lastName : String = data[safe: 2]?.prefixTrimmed(50) ?? "" let firstName : String = data[safe: 3]?.prefixTrimmed(50) ?? "" - let sex: PlayerRegistration.PlayerSexType = data[safe: 0] == "f" ? PlayerRegistration.PlayerSexType.female : PlayerRegistration.PlayerSexType.male + let sex: PlayerSexType = data[safe: 0] == "f" ? PlayerSexType.female : PlayerSexType.male if data[safe: 1]?.trimmed != nil { teamName = data[safe: 1]?.trimmed } diff --git a/PadelClub/Utils/Patcher.swift b/PadelClub/Utils/Patcher.swift index 6841bf4..4b76f79 100644 --- a/PadelClub/Utils/Patcher.swift +++ b/PadelClub/Utils/Patcher.swift @@ -14,6 +14,7 @@ enum PatchError: Error { enum Patch: String, CaseIterable { case cleanLogs + case syncUpgrade var id: String { return "padelclub.app.patch.\(self.rawValue)" @@ -43,121 +44,50 @@ class Patcher { fileprivate static func _applyPatch(_ patch: Patch) throws { switch patch { case .cleanLogs: self._cleanLogs() + case .syncUpgrade: self._syncUpgrade() } } -// -// fileprivate static func _patchAlexisLeDu() { -// guard StoreCenter.main.userId == "94f45ed2-8938-4c32-a4b6-e4525073dd33" else { return } -// -// let clubs = DataStore.shared.clubs -// StoreCenter.main.resetApiCalls(collection: clubs) -//// clubs.resetApiCalls() -// -// for club in clubs.filter({ $0.creator == "d5060b89-e979-4c19-bf78-e459a6ed5318"}) { -// club.creator = StoreCenter.main.userId -// clubs.writeChangeAndInsertOnServer(instance: club) -// } -// -// } -// -// fileprivate static func _importDataFromDev() throws { -// -// let devServices = Services(url: "https://xlr.alwaysdata.net/roads/") -// guard devServices.hasToken() else { -// return -// } -// guard StoreCenter.main.synchronizationApiURL == "https://padelclub.app/roads/" else { -// return -// } -// -// guard let userId = StoreCenter.main.userId else { -// return -// } -// -// try StoreCenter.main.migrateToken(devServices) -// -// -// let myClubs: [Club] = DataStore.shared.clubs.filter { $0.creator == userId } -// let clubIds: [String] = myClubs.map { $0.id } -// -// myClubs.forEach { club in -// DataStore.shared.clubs.insertIntoCurrentService(item: club) -// -// let courts = DataStore.shared.courts.filter { clubIds.contains($0.club) } -// for court in courts { -// DataStore.shared.courts.insertIntoCurrentService(item: court) -// } -// } -// -// DataStore.shared.user.clubs = Array(clubIds) -// DataStore.shared.saveUser() -// -// DataStore.shared.events.insertAllIntoCurrentService() -// DataStore.shared.tournaments.insertAllIntoCurrentService() -// DataStore.shared.dateIntervals.insertAllIntoCurrentService() -// -// for tournament in DataStore.shared.tournaments { -// let store = tournament.tournamentStore -// -// Task { // need to wait for the collections to load -// try await Task.sleep(until: .now + .seconds(2)) -// -// store.teamRegistrations.insertAllIntoCurrentService() -// store.rounds.insertAllIntoCurrentService() -// store.groupStages.insertAllIntoCurrentService() -// store.matches.insertAllIntoCurrentService() -// store.playerRegistrations.insertAllIntoCurrentService() -// store.teamScores.insertAllIntoCurrentService() -// -// } -// } -// -// } -// -// fileprivate static func _patchMissingMatches() { -// -// guard let url = StoreCenter.main.synchronizationApiURL else { -// return -// } -// guard url == "https://padelclub.app/roads/" else { -// return -// } -// let services = Services(url: url) -// -// for tournament in DataStore.shared.tournaments { -// -// let store = tournament.tournamentStore -// let identifier = StoreIdentifier(value: tournament.id, parameterName: "tournament") -// -// Task { -// -// do { -// // if nothing is online we upload the data -// let matches: [Match] = try await services.get(identifier: identifier) -// if matches.isEmpty { -// store.matches.insertAllIntoCurrentService() -// } -// -// let playerRegistrations: [PlayerRegistration] = try await services.get(identifier: identifier) -// if playerRegistrations.isEmpty { -// store.playerRegistrations.insertAllIntoCurrentService() -// } -// -// let teamScores: [TeamScore] = try await services.get(identifier: identifier) -// if teamScores.isEmpty { -// store.teamScores.insertAllIntoCurrentService() -// } -// -// } catch { -// Logger.error(error) -// } -// -// } -// } -// -// } fileprivate static func _cleanLogs() { StoreCenter.main.resetLoggingCollections() } + + fileprivate static func _syncUpgrade() { + for tournament in DataStore.shared.tournaments { + let id = tournament.id + + guard let store = TournamentLibrary.shared.store(tournamentId: tournament.id) else { continue } + + for round in store.rounds { + round.storeId = id + } + store.rounds.addOrUpdate(contentOfs: store.rounds) + for groupStage in store.groupStages { + groupStage.storeId = id + } + store.groupStages.addOrUpdate(contentOfs: store.groupStages) + for teamRegistration in store.teamRegistrations { + teamRegistration.storeId = id + } + store.teamRegistrations.addOrUpdate(contentOfs: store.teamRegistrations) + for pr in store.playerRegistrations { + pr.storeId = id + } + store.playerRegistrations.addOrUpdate(contentOfs: store.playerRegistrations) + for match in store.matches { + match.storeId = id + } + store.matches.addOrUpdate(contentOfs: store.matches) + for ts in store.teamScores { + ts.storeId = id + } + store.teamScores.addOrUpdate(contentOfs: store.teamScores) + for ms in store.matchSchedulers { + ms.storeId = id + } + store.matchSchedulers.addOrUpdate(contentOfs: store.matchSchedulers) + + } + } + } diff --git a/PadelClub/Utils/SourceFileManager.swift b/PadelClub/Utils/SourceFileManager.swift index 41fef12..ffa4731 100644 --- a/PadelClub/Utils/SourceFileManager.swift +++ b/PadelClub/Utils/SourceFileManager.swift @@ -27,9 +27,9 @@ class SourceFileManager { if !fileManager.fileExists(atPath: directoryURL.path) { // Directory does not exist, create it try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil) - print("Directory created at: \(directoryURL)") +// print("Directory created at: \(directoryURL)") } else { - print("Directory already exists at: \(directoryURL)") +// print("Directory already exists at: \(directoryURL)") } } catch { print("Error: \(error)") diff --git a/PadelClub/Utils/URLs.swift b/PadelClub/Utils/URLs.swift index 452e35d..eddbe65 100644 --- a/PadelClub/Utils/URLs.swift +++ b/PadelClub/Utils/URLs.swift @@ -8,23 +8,23 @@ import Foundation enum URLs: String, Identifiable { - +// case httpScheme = "https://" #if DEBUG case activationHost = "xlr.alwaysdata.net" case main = "https://xlr.alwaysdata.net/" - case api = "https://xlr.alwaysdata.net/roads/" +// case api = "https://xlr.alwaysdata.net/roads/" #elseif TESTFLIGHT case activationHost = "xlr.alwaysdata.net" case main = "https://xlr.alwaysdata.net/" - case api = "https://xlr.alwaysdata.net/roads/" +// case api = "https://xlr.alwaysdata.net/roads/" #elseif PRODTEST case activationHost = "padelclub.app" case main = "https://padelclub.app/" - case api = "https://padelclub.app/roads/" +// case api = "https://padelclub.app/roads/" #else case activationHost = "padelclub.app" case main = "https://padelclub.app/" - case api = "https://padelclub.app/roads/" +// case api = "https://padelclub.app/roads/" #endif case subscriptions = "https://apple.co/2Th4vqI" diff --git a/PadelClub/ViewModel/Screen.swift b/PadelClub/ViewModel/Screen.swift index fec745f..38a02b0 100644 --- a/PadelClub/ViewModel/Screen.swift +++ b/PadelClub/ViewModel/Screen.swift @@ -20,6 +20,7 @@ enum Screen: String, Codable { case broadcast case event case print + case share case restingTime case stateSettings } diff --git a/PadelClub/Views/Calling/CallSettingsView.swift b/PadelClub/Views/Calling/CallSettingsView.swift index 54fbcb6..49db7c5 100644 --- a/PadelClub/Views/Calling/CallSettingsView.swift +++ b/PadelClub/Views/Calling/CallSettingsView.swift @@ -15,7 +15,7 @@ struct CallSettingsView: View { @State private var showSendToAllView: Bool = false @State private var addLink: Bool = false - var tournamentStore: TournamentStore { + var tournamentStore: TournamentStore? { return self.tournament.tournamentStore } @@ -66,7 +66,7 @@ struct CallSettingsView: View { team.callDate = nil } do { - try tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teams) + try tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: teams) } catch { Logger.error(error) } @@ -84,7 +84,7 @@ struct CallSettingsView: View { } } do { - try tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teams) + try tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: teams) } catch { Logger.error(error) } diff --git a/PadelClub/Views/Calling/CallView.swift b/PadelClub/Views/Calling/CallView.swift index 8f8f8a7..0baae88 100644 --- a/PadelClub/Views/Calling/CallView.swift +++ b/PadelClub/Views/Calling/CallView.swift @@ -104,7 +104,7 @@ struct CallView: View { } } - var tournamentStore: TournamentStore { + var tournamentStore: TournamentStore? { return self.tournament.tournamentStore } @@ -130,7 +130,7 @@ struct CallView: View { } } do { - try self.tournamentStore.teamRegistrations.addOrUpdate(contentOfs: calledTeams) + try self.tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: calledTeams) } catch { Logger.error(error) } diff --git a/PadelClub/Views/Calling/SendToAllView.swift b/PadelClub/Views/Calling/SendToAllView.swift index 8a2adc2..a0515e0 100644 --- a/PadelClub/Views/Calling/SendToAllView.swift +++ b/PadelClub/Views/Calling/SendToAllView.swift @@ -32,7 +32,7 @@ struct SendToAllView: View { @State var summonParamByMessage: Bool = false @State var summonParamReSummon: Bool = false - var tournamentStore: TournamentStore { + var tournamentStore: TournamentStore? { return self.tournament.tournamentStore } @@ -234,12 +234,14 @@ struct SendToAllView: View { } func _roundTeams() -> [TeamRegistration] { - let rounds: [Round] = contactRecipients.compactMap { self.tournamentStore.rounds.findById($0) } + guard let tournamentStore = self.tournamentStore else { return [] } + let rounds: [Round] = contactRecipients.compactMap { tournamentStore.rounds.findById($0) } return rounds.flatMap { $0.teams() } } func _groupStagesTeams() -> [TeamRegistration] { - let groupStages : [GroupStage] = contactRecipients.compactMap { self.tournamentStore.groupStages.findById($0) } + guard let tournamentStore = self.tournamentStore else { return [] } + let groupStages : [GroupStage] = contactRecipients.compactMap { tournamentStore.groupStages.findById($0) } return groupStages.flatMap { $0.teams() } } diff --git a/PadelClub/Views/Calling/TeamsCallingView.swift b/PadelClub/Views/Calling/TeamsCallingView.swift index b2473a9..017c5fa 100644 --- a/PadelClub/Views/Calling/TeamsCallingView.swift +++ b/PadelClub/Views/Calling/TeamsCallingView.swift @@ -135,7 +135,7 @@ struct CallMenuOptionsView: View { } set: { _ in team.toggleSummonConfirmation() do { - try self.tournament.tournamentStore.teamRegistrations.addOrUpdate(instance: team) + try self.tournament.tournamentStore?.teamRegistrations.addOrUpdate(instance: team) } catch { Logger.error(error) } @@ -170,7 +170,7 @@ struct CallMenuOptionsView: View { RowButtonView("Effacer la date de convocation", role: .destructive) { team.callDate = nil do { - try self.tournament.tournamentStore.teamRegistrations.addOrUpdate(instance: team) + try self.tournament.tournamentStore?.teamRegistrations.addOrUpdate(instance: team) } catch { Logger.error(error) } @@ -183,7 +183,7 @@ struct CallMenuOptionsView: View { RowButtonView("Indiquer comme convoquée", role: .destructive) { team.callDate = team.initialMatch()?.startDate ?? tournament.startDate do { - try self.tournament.tournamentStore.teamRegistrations.addOrUpdate(instance: team) + try self.tournament.tournamentStore?.teamRegistrations.addOrUpdate(instance: team) } catch { Logger.error(error) } diff --git a/PadelClub/Views/Cashier/CashierDetailView.swift b/PadelClub/Views/Cashier/CashierDetailView.swift index 068d17f..0f1c8a1 100644 --- a/PadelClub/Views/Cashier/CashierDetailView.swift +++ b/PadelClub/Views/Cashier/CashierDetailView.swift @@ -76,7 +76,7 @@ struct CashierDetailView: View { private func _tournamentsCashierDetailView(_ tournaments: [Tournament]) -> some View { DisclosureGroup { - ForEach(PlayerRegistration.PlayerPaymentType.allCases) { type in + ForEach(PlayerPaymentType.allCases) { type in PaymentTypeCashierRowView(tournaments: tournaments, type: type) } } label: { @@ -137,7 +137,7 @@ struct CashierDetailView: View { struct PaymentTypeCashierRowView: View { let tournaments: [Tournament] - let type: PlayerRegistration.PlayerPaymentType + let type: PlayerPaymentType @State private var value: Double? @@ -167,7 +167,7 @@ struct CashierDetailView: View { var body: some View { DisclosureGroup { let selectedPlayers = tournament.selectedPlayers() - ForEach(PlayerRegistration.PlayerPaymentType.allCases) { type in + ForEach(PlayerPaymentType.allCases) { type in let count = selectedPlayers.filter({ $0.paymentType == type }).count if count > 0 { LabeledContent { diff --git a/PadelClub/Views/Cashier/CashierSettingsView.swift b/PadelClub/Views/Cashier/CashierSettingsView.swift index 098c78b..2090e0d 100644 --- a/PadelClub/Views/Cashier/CashierSettingsView.swift +++ b/PadelClub/Views/Cashier/CashierSettingsView.swift @@ -35,7 +35,6 @@ struct CashierSettingsView: View { Text("Si vous souhaitez que Padel Club vous aide à suivre les encaissements, indiquer un prix d'inscription. Sinon Padel Club vous aidera à suivre simplement l'arrivée et la présence des joueurs.") } - let players = tournament.selectedPlayers() if players.anySatisfy({ $0.hasArrived == false }) { @@ -138,19 +137,11 @@ struct CashierSettingsView: View { } private func _save(players: [PlayerRegistration]) { - do { - try tournament.tournamentStore.playerRegistrations.addOrUpdate(contentOfs: players) - } catch { - Logger.error(error) - } + tournament.tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: players) } private func _save() { - do { - try dataStore.tournaments.addOrUpdate(instance: tournament) - } catch { - Logger.error(error) - } + dataStore.tournaments.addOrUpdate(instance: tournament) } } diff --git a/PadelClub/Views/Club/ClubDetailView.swift b/PadelClub/Views/Club/ClubDetailView.swift index a7748ea..f74d143 100644 --- a/PadelClub/Views/Club/ClubDetailView.swift +++ b/PadelClub/Views/Club/ClubDetailView.swift @@ -251,11 +251,7 @@ struct ClubDetailView: View { } .onDisappear { if displayContext == .edition && clubDeleted == false { - do { - try dataStore.clubs.addOrUpdate(instance: club) - } catch { - Logger.error(error) - } + dataStore.clubs.addOrUpdate(instance: club) } } .onAppear { @@ -295,15 +291,11 @@ struct ClubDetailView: View { } private func _deleteClub() { - do { - clubDeleted = true - dataStore.user.clubs.removeAll(where: { $0 == club.id }) - self.dataStore.saveUser() - try dataStore.clubs.deleteById(club.id) - dismiss() - } catch { - Logger.error(error) - } + clubDeleted = true + dataStore.user.clubs.removeAll(where: { $0 == club.id }) + self.dataStore.saveUser() + dataStore.clubs.delete(instance: club) + dismiss() } } diff --git a/PadelClub/Views/Club/ClubSearchView.swift b/PadelClub/Views/Club/ClubSearchView.swift index 9c5378c..492e8b3 100644 --- a/PadelClub/Views/Club/ClubSearchView.swift +++ b/PadelClub/Views/Club/ClubSearchView.swift @@ -324,6 +324,8 @@ struct ClubSearchView: View { if clubToEdit.hasBeenCreated(by: StoreCenter.main.userId) { if clubToEdit.name.isEmpty { clubToEdit.name = clubMarker.nom.capitalized + } + if clubToEdit.acronym.isEmpty { clubToEdit.acronym = clubToEdit.automaticShortName().uppercased() } clubToEdit.code = clubMarker.clubID diff --git a/PadelClub/Views/Components/Labels.swift b/PadelClub/Views/Components/Labels.swift index 7049334..a87e34a 100644 --- a/PadelClub/Views/Components/Labels.swift +++ b/PadelClub/Views/Components/Labels.swift @@ -31,6 +31,12 @@ struct LabelDelete: View { } } +struct ShareLabel: View { + var body: some View { + Label("Partager", systemImage: "square.and.arrow.up.fill") + } +} + struct LabelFilter: View { var body: some View { Label("Filtrer", systemImage: "line.3.horizontal.decrease.circle") diff --git a/PadelClub/Views/Components/RowButtonView.swift b/PadelClub/Views/Components/RowButtonView.swift index 0dce2d5..d3aa97e 100644 --- a/PadelClub/Views/Components/RowButtonView.swift +++ b/PadelClub/Views/Components/RowButtonView.swift @@ -97,7 +97,7 @@ struct RowButtonView: View { .confirmationDialog("Confirmation", isPresented: $askConfirmation, titleVisibility: .visible) { - Button("OK") { + Button("OK", role: self.role) { if let action { action() } else if let asyncAction { diff --git a/PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift b/PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift index 521b328..50e5155 100644 --- a/PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift +++ b/PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift @@ -29,7 +29,7 @@ struct GroupStageSettingsView: View { _courtIndex = .init(wrappedValue: groupStage._matches().first?.courtIndex ?? 0) } - var tournamentStore: TournamentStore { + var tournamentStore: TournamentStore? { return self.tournament.tournamentStore } @@ -70,7 +70,7 @@ struct GroupStageSettingsView: View { } do { - try tournamentStore.matches.addOrUpdate(contentOfs: groupStage._matches()) + try tournamentStore?.matches.addOrUpdate(contentOfs: groupStage._matches()) } catch { Logger.error(error) } @@ -93,7 +93,7 @@ struct GroupStageSettingsView: View { groupStage._matches().forEach({ $0.updateTeamScores() }) } do { - try tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teams) + try tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: teams) } catch { Logger.error(error) } @@ -116,7 +116,7 @@ struct GroupStageSettingsView: View { } do { - try tournamentStore.matches.addOrUpdate(contentOfs: groupStage._matches()) + try tournamentStore?.matches.addOrUpdate(contentOfs: groupStage._matches()) } catch { Logger.error(error) } @@ -132,7 +132,7 @@ struct GroupStageSettingsView: View { groupStage._matches().forEach({ $0.updateTeamScores() }) } do { - try tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teams) + try tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: teams) } catch { Logger.error(error) } @@ -243,7 +243,7 @@ struct GroupStageSettingsView: View { private func _save() { do { - try tournamentStore.groupStages.addOrUpdate(instance: groupStage) + try tournamentStore?.groupStages.addOrUpdate(instance: groupStage) } catch { Logger.error(error) } diff --git a/PadelClub/Views/GroupStage/Components/GroupStageTeamView.swift b/PadelClub/Views/GroupStage/Components/GroupStageTeamView.swift index 18a3786..74a181f 100644 --- a/PadelClub/Views/GroupStage/Components/GroupStageTeamView.swift +++ b/PadelClub/Views/GroupStage/Components/GroupStageTeamView.swift @@ -23,7 +23,7 @@ struct GroupStageTeamView: View { let groupStage: GroupStage var team: TeamRegistration - var tournamentStore: TournamentStore { + var tournamentStore: TournamentStore? { return self.tournament.tournamentStore } @@ -189,7 +189,7 @@ struct GroupStageTeamView: View { private func _save() { do { - try tournamentStore.teamRegistrations.addOrUpdate(instance: team) + try tournamentStore?.teamRegistrations.addOrUpdate(instance: team) } catch { Logger.error(error) } diff --git a/PadelClub/Views/GroupStage/GroupStageView.swift b/PadelClub/Views/GroupStage/GroupStageView.swift index adbb27f..6baf09c 100644 --- a/PadelClub/Views/GroupStage/GroupStageView.swift +++ b/PadelClub/Views/GroupStage/GroupStageView.swift @@ -22,7 +22,7 @@ struct GroupStageView: View { _sortingMode = .init(wrappedValue: groupStage.hasEnded() ? .score : .weight) } - var tournamentStore: TournamentStore { + var tournamentStore: TournamentStore? { return self.tournament.tournamentStore } @@ -118,7 +118,7 @@ struct GroupStageView: View { let scores: [GroupStage.TeamGroupStageScore]? let teams: [TeamRegistration] - var tournamentStore: TournamentStore { + var tournamentStore: TournamentStore? { return self.tournament.tournamentStore } @@ -217,7 +217,7 @@ struct GroupStageView: View { Button { player.hasArrived.toggle() do { - try self.tournamentStore.playerRegistrations.addOrUpdate(instance: player) + try self.tournamentStore?.playerRegistrations.addOrUpdate(instance: player) } catch { Logger.error(error) } @@ -246,7 +246,7 @@ struct GroupStageView: View { team.groupStagePosition = index groupStage._matches().forEach({ $0.updateTeamScores() }) do { - try tournamentStore.teamRegistrations.addOrUpdate(instance: team) + try tournamentStore?.teamRegistrations.addOrUpdate(instance: team) } catch { Logger.error(error) } @@ -276,7 +276,7 @@ struct GroupStageView: View { private func _save() { do { - try tournamentStore.groupStages.addOrUpdate(instance: groupStage) + try tournamentStore?.groupStages.addOrUpdate(instance: groupStage) } catch { Logger.error(error) } diff --git a/PadelClub/Views/GroupStage/GroupStagesSettingsView.swift b/PadelClub/Views/GroupStage/GroupStagesSettingsView.swift index ed98683..ae854f7 100644 --- a/PadelClub/Views/GroupStage/GroupStagesSettingsView.swift +++ b/PadelClub/Views/GroupStage/GroupStagesSettingsView.swift @@ -15,7 +15,7 @@ struct GroupStagesSettingsView: View { @State private var generationDoneMessage: String? let step: Int - var tournamentStore: TournamentStore { + var tournamentStore: TournamentStore? { return self.tournament.tournamentStore } @@ -26,7 +26,7 @@ struct GroupStagesSettingsView: View { let issues = tournament.groupStageTeamPlacementIssue() DisclosureGroup { ForEach(issues.shouldBeInIt, id: \.self) { id in - if let team: TeamRegistration = self.tournamentStore.teamRegistrations.findById(id) { + if let team: TeamRegistration = self.tournamentStore?.teamRegistrations.findById(id) { TeamRowView(team: team) } } @@ -39,12 +39,12 @@ struct GroupStagesSettingsView: View { } DisclosureGroup { ForEach(issues.shouldNotBeInIt, id: \.self) { id in - if let team = self.tournamentStore.teamRegistrations.findById(id) { + if let team = self.tournamentStore?.teamRegistrations.findById(id) { Menu { Button("Retirer de sa poule") { team.resetGroupeStagePosition() do { - try self.tournamentStore.teamRegistrations.addOrUpdate(instance: team) + try self.tournamentStore?.teamRegistrations.addOrUpdate(instance: team) } catch { Logger.error(error) } @@ -80,7 +80,7 @@ struct GroupStagesSettingsView: View { let round = Round(tournament: tournament.id, index: 0, matchFormat: tournament.loserRoundFormat, groupStageLoserBracket: true) do { - try tournamentStore.rounds.addOrUpdate(instance: round) + try tournamentStore?.rounds.addOrUpdate(instance: round) } catch { Logger.error(error) } @@ -89,7 +89,7 @@ struct GroupStagesSettingsView: View { } else if let groupStageLoserBracket = tournament.groupStageLoserBracket() { RowButtonView("Supprimer les matchs de classements", role: .destructive) { do { - try tournamentStore.rounds.delete(instance: groupStageLoserBracket) + try tournamentStore?.rounds.delete(instance: groupStageLoserBracket) } catch { Logger.error(error) } @@ -110,7 +110,7 @@ struct GroupStagesSettingsView: View { RowButtonView("Supprimer cette phase de poule", role: .destructive) { let groupStages = tournament.groupStages(atStep: tournament.lastStep()) do { - try tournament.tournamentStore.groupStages.delete(contentOfs: groupStages) + try self.tournamentStore?.groupStages.delete(contentOfs: groupStages) } catch { Logger.error(error) } @@ -150,7 +150,7 @@ struct GroupStagesSettingsView: View { } do { - try tournamentStore.matches.addOrUpdate(contentOfs: matches) + try tournamentStore?.matches.addOrUpdate(contentOfs: matches) } catch { Logger.error(error) } @@ -210,7 +210,7 @@ struct GroupStagesSettingsView: View { } } do { - try self.tournament.tournamentStore.groupStages.addOrUpdate(contentOfs: groupStages) + try self.tournament.tournamentStore?.groupStages.addOrUpdate(contentOfs: groupStages) } catch { Logger.error(error) } @@ -224,7 +224,7 @@ struct GroupStagesSettingsView: View { groupStage.name = nil } do { - try self.tournament.tournamentStore.groupStages.addOrUpdate(contentOfs: groupStages) + try self.tournament.tournamentStore?.groupStages.addOrUpdate(contentOfs: groupStages) } catch { Logger.error(error) } @@ -241,7 +241,7 @@ struct GroupStagesSettingsView: View { groupStage._matches().forEach({ $0.updateTeamScores() }) } do { - try tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teams) + try tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: teams) } catch { Logger.error(error) } diff --git a/PadelClub/Views/GroupStage/GroupStagesView.swift b/PadelClub/Views/GroupStage/GroupStagesView.swift index a8f9f33..c89f7bc 100644 --- a/PadelClub/Views/GroupStage/GroupStagesView.swift +++ b/PadelClub/Views/GroupStage/GroupStagesView.swift @@ -197,7 +197,7 @@ struct GroupStagesView: View { results.forEach { drawResult in missingQualifiedFromGroupStages[drawResult.drawIndex].qualified = true do { - try self.tournament.tournamentStore.teamRegistrations.addOrUpdate(instance: missingQualifiedFromGroupStages[drawResult.drawIndex]) + try self.tournament.tournamentStore?.teamRegistrations.addOrUpdate(instance: missingQualifiedFromGroupStages[drawResult.drawIndex]) } catch { Logger.error(error) } diff --git a/PadelClub/Views/GroupStage/LoserBracketFromGroupStageView.swift b/PadelClub/Views/GroupStage/LoserBracketFromGroupStageView.swift index bd0fe30..b5e2096 100644 --- a/PadelClub/Views/GroupStage/LoserBracketFromGroupStageView.swift +++ b/PadelClub/Views/GroupStage/LoserBracketFromGroupStageView.swift @@ -21,7 +21,7 @@ struct LoserBracketFromGroupStageView: View { _isEditingLoserBracketGroupStage = .init(wrappedValue: loserBracket._matches().isEmpty) } - var tournamentStore: TournamentStore { + var tournamentStore: TournamentStore? { return self.tournament.tournamentStore } @@ -107,23 +107,16 @@ struct LoserBracketFromGroupStageView: View { private func _addNewMatch() { let currentGroupStageLoserBracketsInitialPlace = tournament.groupStageLoserBracketsInitialPlace() let placeCount = displayableMatches.isEmpty ? currentGroupStageLoserBracketsInitialPlace : max(currentGroupStageLoserBracketsInitialPlace, displayableMatches.map({ $0.index }).max()! + 2) - let match = Match(round: loserBracket.id, index: placeCount, matchFormat: loserBracket.matchFormat) + + let match = Match(round: loserBracket.id, index: placeCount, format: loserBracket.matchFormat) match.setMatchName("\(placeCount)\(placeCount.ordinalFormattedSuffix()) place") - do { - try tournamentStore.matches.addOrUpdate(instance: match) - } catch { - Logger.error(error) - } + tournamentStore?.matches.addOrUpdate(instance: match) } private func _deleteAllMatches() { let displayableMatches = loserBracket.playedMatches().sorted(by: \.index) - do { - try tournamentStore.matches.delete(contentOfs: displayableMatches) - } catch { - Logger.error(error) - } + tournamentStore?.matches.delete(contentOfs: displayableMatches) } @@ -171,7 +164,7 @@ struct GroupStageLoserBracketMatchFooterView: View { Spacer() FooterButtonView("Effacer", role: .destructive) { do { - try match.tournamentStore.matches.delete(instance: match) + try match.tournamentStore?.matches.delete(instance: match) } catch { Logger.error(error) } @@ -206,15 +199,7 @@ struct GroupStageLoserBracketMatchFooterView: View { match.setMatchName("\(newIndexValidated)\(newIndexValidated.ordinalFormattedSuffix()) place") - do { - try match.tournamentStore.teamScores.addOrUpdate(contentOfs: teamScores) - } catch { - Logger.error(error) - } - do { - try match.tournamentStore.matches.addOrUpdate(instance: match) - } catch { - Logger.error(error) - } - } + match.tournamentStore?.teamScores.addOrUpdate(contentOfs: teamScores) + match.tournamentStore?.matches.addOrUpdate(instance: match) + } } diff --git a/PadelClub/Views/Match/Components/MatchDateView.swift b/PadelClub/Views/Match/Components/MatchDateView.swift index 5dd2187..c1095e3 100644 --- a/PadelClub/Views/Match/Components/MatchDateView.swift +++ b/PadelClub/Views/Match/Components/MatchDateView.swift @@ -191,7 +191,7 @@ struct MatchDateView: View { } do { - try self.match.tournamentStore.matches.addOrUpdate(instance: match) + try self.match.tournamentStore?.matches.addOrUpdate(instance: match) } catch { Logger.error(error) } diff --git a/PadelClub/Views/Match/MatchDetailView.swift b/PadelClub/Views/Match/MatchDetailView.swift index 9e19053..5dd2485 100644 --- a/PadelClub/Views/Match/MatchDetailView.swift +++ b/PadelClub/Views/Match/MatchDetailView.swift @@ -38,7 +38,7 @@ struct MatchDetailView: View { @State private var presentRanking: Bool = false @State private var confirmScoreEdition: Bool = false - var tournamentStore: TournamentStore { + var tournamentStore: TournamentStore? { return match.tournamentStore } @@ -121,8 +121,10 @@ struct MatchDetailView: View { DisclosureGroup { ForEach(unpaid) { player in LabeledContent { - PlayerPayView(player: player) - .environmentObject(tournamentStore) + if let store = self.tournamentStore { + PlayerPayView(player: player) + .environmentObject(store) + } } label: { Text(player.playerLabel()) } @@ -140,8 +142,12 @@ struct MatchDetailView: View { menuView } .sheet(isPresented: $showDetails) { - MatchTeamDetailView(match: match).tint(.master) - .environmentObject(tournamentStore) + if let store = self.tournamentStore { + MatchTeamDetailView(match: match).tint(.master) + .environmentObject(store) + } else { + Text("no store") + } } .sheet(isPresented: self.$showSubscriptionView, content: { NavigationStack { @@ -364,7 +370,7 @@ struct MatchDetailView: View { ForEach(match.teamScores) { teamScore in Button(role: .destructive) { do { - try tournamentStore.teamScores.delete(instance: teamScore) + try tournamentStore?.teamScores.delete(instance: teamScore) } catch { Logger.error(error) } @@ -566,7 +572,7 @@ struct MatchDetailView: View { if match.isReady() == false && match.teams().count == 2 { let teamsScores = match.getOrCreateTeamScores() do { - try tournamentStore.teamScores.addOrUpdate(contentOfs: teamsScores) + try tournamentStore?.teamScores.addOrUpdate(contentOfs: teamsScores) } catch { Logger.error(error) } @@ -615,7 +621,7 @@ struct MatchDetailView: View { private func _saveMatch() { do { - try tournamentStore.matches.addOrUpdate(instance: match) + try tournamentStore?.matches.addOrUpdate(instance: match) } catch { Logger.error(error) } diff --git a/PadelClub/Views/Match/MatchRowView.swift b/PadelClub/Views/Match/MatchRowView.swift index 643078e..e74bd44 100644 --- a/PadelClub/Views/Match/MatchRowView.swift +++ b/PadelClub/Views/Match/MatchRowView.swift @@ -69,7 +69,7 @@ struct MatchRowView: View { Button { player.hasArrived.toggle() do { - try player.tournamentStore.playerRegistrations.addOrUpdate(instance: player) + try player.tournamentStore?.playerRegistrations.addOrUpdate(instance: player) } catch { Logger.error(error) } diff --git a/PadelClub/Views/Match/MatchSetupView.swift b/PadelClub/Views/Match/MatchSetupView.swift index 72b5b19..8170ae0 100644 --- a/PadelClub/Views/Match/MatchSetupView.swift +++ b/PadelClub/Views/Match/MatchSetupView.swift @@ -16,7 +16,7 @@ struct MatchSetupView: View { @State var match: Match @State private var seedGroup: SeedInterval? - var tournamentStore: TournamentStore { + var tournamentStore: TournamentStore? { return match.tournamentStore } @@ -68,19 +68,19 @@ struct MatchSetupView: View { if walkOutSpot || team.bracketPosition != nil || matchTypeContext == .loserBracket { match.setLuckyLoser(team: team, teamPosition: teamPosition) do { - try tournamentStore.matches.addOrUpdate(instance: match) + try tournamentStore?.matches.addOrUpdate(instance: match) } catch { Logger.error(error) } } else if team.bracketPosition == nil { team.setSeedPosition(inSpot: match, slot: teamPosition, opposingSeeding: false) do { - try tournamentStore.matches.addOrUpdate(instance: match) + try tournamentStore?.matches.addOrUpdate(instance: match) } catch { Logger.error(error) } do { - try tournamentStore.teamRegistrations.addOrUpdate(instance: team) + try tournamentStore?.teamRegistrations.addOrUpdate(instance: team) } catch { Logger.error(error) } @@ -97,7 +97,7 @@ struct MatchSetupView: View { if let randomTeam = luckyLosers.randomElement() { match.setLuckyLoser(team: randomTeam, teamPosition: teamPosition) do { - try tournamentStore.matches.addOrUpdate(instance: match) + try tournamentStore?.matches.addOrUpdate(instance: match) } catch { Logger.error(error) } @@ -112,12 +112,12 @@ struct MatchSetupView: View { if let randomTeam = availableQualifiedTeams.randomElement() { randomTeam.setSeedPosition(inSpot: match, slot: teamPosition, opposingSeeding: false) do { - try tournamentStore.matches.addOrUpdate(instance: match) + try tournamentStore?.matches.addOrUpdate(instance: match) } catch { Logger.error(error) } do { - try tournamentStore.teamRegistrations.addOrUpdate(instance: randomTeam) + try tournamentStore?.teamRegistrations.addOrUpdate(instance: randomTeam) } catch { Logger.error(error) } @@ -132,12 +132,12 @@ struct MatchSetupView: View { if let randomTeam = tournament.randomSeed(fromSeedGroup: seedGroup) { randomTeam.setSeedPosition(inSpot: match, slot: teamPosition, opposingSeeding: false) do { - try tournamentStore.matches.addOrUpdate(instance: match) + try tournamentStore?.matches.addOrUpdate(instance: match) } catch { Logger.error(error) } do { - try tournamentStore.teamRegistrations.addOrUpdate(instance: randomTeam) + try tournamentStore?.teamRegistrations.addOrUpdate(instance: randomTeam) } catch { Logger.error(error) } @@ -155,12 +155,12 @@ struct MatchSetupView: View { if let randomTeam = tournament.randomSeed(fromSeedGroup: seedGroup) { randomTeam.setSeedPosition(inSpot: match, slot: teamPosition, opposingSeeding: false) do { - try tournamentStore.matches.addOrUpdate(instance: match) + try tournamentStore?.matches.addOrUpdate(instance: match) } catch { Logger.error(error) } do { - try tournamentStore.teamRegistrations.addOrUpdate(instance: randomTeam) + try tournamentStore?.teamRegistrations.addOrUpdate(instance: randomTeam) } catch { Logger.error(error) } @@ -185,7 +185,7 @@ struct MatchSetupView: View { Button { match.unlockSeedPosition(atTeamPosition: teamPosition) do { - try tournamentStore.matches.addOrUpdate(instance: match) + try tournamentStore?.matches.addOrUpdate(instance: match) } catch { Logger.error(error) } @@ -197,7 +197,7 @@ struct MatchSetupView: View { ConfirmButtonView(shouldConfirm: shouldConfirm, message: MatchSetupView.confirmationMessage) { _ = match.lockAndGetSeedPosition(atTeamPosition: teamPosition) do { - try tournamentStore.matches.addOrUpdate(instance: match) + try tournamentStore?.matches.addOrUpdate(instance: match) } catch { Logger.error(error) } @@ -220,7 +220,7 @@ struct MatchSetupView: View { if match.isSeededBy(team: team, inTeamPosition: teamPosition) { if let score = match.teamScore(ofTeam: team) { do { - try tournamentStore.teamScores.delete(instance: score) + try tournamentStore?.teamScores.delete(instance: score) } catch { Logger.error(error) } @@ -228,7 +228,7 @@ struct MatchSetupView: View { team.bracketPosition = nil do { - try tournamentStore.teamRegistrations.addOrUpdate(instance: team) + try tournamentStore?.teamRegistrations.addOrUpdate(instance: team) } catch { Logger.error(error) } @@ -236,7 +236,7 @@ struct MatchSetupView: View { if previousMatch.disabled { previousMatch.enableMatch() do { - try tournamentStore.matches.addOrUpdate(instance: previousMatch) + try tournamentStore?.matches.addOrUpdate(instance: previousMatch) } catch { Logger.error(error) } @@ -244,14 +244,14 @@ struct MatchSetupView: View { } do { - try tournamentStore.matches.addOrUpdate(instance: match) + try tournamentStore?.matches.addOrUpdate(instance: match) } catch { Logger.error(error) } } else if match.isLoserBracket { if let score = match.teamScore(ofTeam: team) { do { - try tournamentStore.teamScores.delete(instance: score) + try tournamentStore?.teamScores.delete(instance: score) } catch { Logger.error(error) } @@ -259,7 +259,7 @@ struct MatchSetupView: View { } else { match.teamWillBeWalkOut(team) do { - try tournamentStore.matches.addOrUpdate(instance: match) + try tournamentStore?.matches.addOrUpdate(instance: match) } catch { Logger.error(error) } diff --git a/PadelClub/Views/Navigation/Agenda/EventListView.swift b/PadelClub/Views/Navigation/Agenda/EventListView.swift index adc4c01..1b75e2e 100644 --- a/PadelClub/Views/Navigation/Agenda/EventListView.swift +++ b/PadelClub/Views/Navigation/Agenda/EventListView.swift @@ -16,6 +16,8 @@ struct EventListView: View { let tournaments: [FederalTournamentHolder] let sortAscending: Bool + + @State var showUserSearch: Bool = false var lastDataSource: Date? { guard let _lastDataSource = dataStore.appSettings.lastDataSource else { return nil } @@ -343,19 +345,16 @@ struct EventListView: View { #if DEBUG .swipeActions(edge: .trailing, allowsFullSwipe: true) { Button(role: .destructive) { - do { - let event = tournament.eventObject() - let isLastTournament = event?.tournaments.count == 1 - try dataStore.tournaments.delete(instance: tournament) - if let event, isLastTournament { - try dataStore.events.delete(instance: event) - } - } catch { - Logger.error(error) - } + dataStore.deleteTournament(tournament) } label: { LabelDelete() } +// Button() { +// self.showUserSearch = true +// } label: { +// ShareLabel().tint(.orange) +// } + } #endif } diff --git a/PadelClub/Views/Navigation/Agenda/TournamentSubscriptionView.swift b/PadelClub/Views/Navigation/Agenda/TournamentSubscriptionView.swift index b9050ed..51fe95e 100644 --- a/PadelClub/Views/Navigation/Agenda/TournamentSubscriptionView.swift +++ b/PadelClub/Views/Navigation/Agenda/TournamentSubscriptionView.swift @@ -13,7 +13,7 @@ struct TournamentSubscriptionView: View { let federalTournament: FederalTournament let build: any TournamentBuildHolder - let user: User + let user: CustomUser @State private var selectedPlayers: [ImportedPlayer] @State private var contactType: ContactType? = nil @@ -21,7 +21,7 @@ struct TournamentSubscriptionView: View { @State private var didSendMessage: Bool = false @State private var didSaveInCalendar: Bool = false - init(federalTournament: FederalTournament, build: any TournamentBuildHolder, user: User) { + init(federalTournament: FederalTournament, build: any TournamentBuildHolder, user: CustomUser) { self.federalTournament = federalTournament self.build = build self.user = user diff --git a/PadelClub/Views/Navigation/MainView.swift b/PadelClub/Views/Navigation/MainView.swift index afd626c..b306c5c 100644 --- a/PadelClub/Views/Navigation/MainView.swift +++ b/PadelClub/Views/Navigation/MainView.swift @@ -47,12 +47,8 @@ struct MainView: View { } )} - private func _isConnected() -> Bool { - return StoreCenter.main.hasToken() && StoreCenter.main.userId != nil - } - var badgeText: Text? { - return (dataStore.appSettings.didCreateAccount && _isConnected() == false) ? Text("!").font(.headline) : nil + return (dataStore.appSettings.didCreateAccount && StoreCenter.main.isAuthenticated == false) ? Text("!").font(.headline) : nil } var body: some View { @@ -95,8 +91,8 @@ struct MainView: View { } .id(mainViewId) .onChange(of: dataStore.user.id) { - print("dataStore.user.id", dataStore.user.id) - print("StoreCenter.main.userId", StoreCenter.main.userId) + print("dataStore.user.id = ", dataStore.user.id) + print("StoreCenter.main.userId = ", StoreCenter.main.userId ?? "") if StoreCenter.main.userId == nil { // user disconnected navigation.path.removeLast(navigation.path.count) mainViewId = UUID() @@ -105,7 +101,7 @@ struct MainView: View { .environmentObject(dataStore) .task { //await self._checkSourceFileAvailability() - if StoreCenter.main.hasToken() { + if StoreCenter.main.isAuthenticated { do { try await dataStore.clubs.loadDataFromServerIfAllowed() } catch { diff --git a/PadelClub/Views/Navigation/Toolbox/APICallsListView.swift b/PadelClub/Views/Navigation/Toolbox/APICallsListView.swift index 467b105..6706d47 100644 --- a/PadelClub/Views/Navigation/Toolbox/APICallsListView.swift +++ b/PadelClub/Views/Navigation/Toolbox/APICallsListView.swift @@ -10,32 +10,96 @@ import LeStorage struct APICallsListView: View { - @State var collections: [String] = [] + @State var descriptors: [CollectionDescriptor] = [] var body: some View { List { - ForEach(self.collections) { name in - NavigationLink(name) { - APICallsView(name: name) + ForEach(self.descriptors) { descriptor in + + NavigationLink { + if let syncedType = descriptor.type as? any SyncedStorable.Type { + APICallsView(name: descriptor.name, type: syncedType) + } + } label: { + LabeledContent(descriptor.name, value: descriptor.count.string) } + } }.onAppear { - self.collections = Store.main.collectionNames() + self.load() } - } + + func load() { + Task { + self.descriptors = await StoreCenter.main.apiCollectionDescriptors().map { + CollectionDescriptor(name: $0.0, type: $0.1) + }.sorted(by: \.name, order: .ascending) + + for descriptor in self.descriptors { + if let type = descriptor.type as? any SyncedStorable.Type { + await self.loadCount(type, descriptor) + } + } + } + } + + func loadCount(_ type: T.Type, _ descriptor: CollectionDescriptor) async { + let calls = await StoreCenter.main.apiCalls(type: type) + descriptor.count = calls.count +// Logger.log("\(descriptor.name), count = \(calls.count)") + } + +} + +@Observable +class CollectionDescriptor: Identifiable, Hashable { + + var id: UUID = UUID() + var name: String + var type: any Storable.Type + var count: Int = 0 + + init(name: String, type: any Storable.Type) { + self.name = name + self.type = type + } + + static func == (lhs: CollectionDescriptor, rhs: CollectionDescriptor) -> Bool { + return lhs.id == rhs.id + } + func hash(into hasher: inout Hasher) { + hasher.combine(self.id) + } + } struct APICallsView: View { var name: String - @State var text: String = "" + var type: any SyncedStorable.Type + +// @State var text: String = "" + @State var apiCalls: [any SomeCall] = [] var body: some View { List { - Text(self.text).lineLimit(nil) - }.onAppear { + Section(header: + Text("\(apiCalls.count) API calls") + .font(.headline) + .foregroundColor(.primary) + ) { + ForEach(self.apiCalls, id: \.id) { apiCall in + NavigationLink { + APICallView(apiCall: apiCall, type: self.type) + } label: { + LabeledContent(apiCall.method.rawValue, value: apiCall.attemptsCount.string) + } + } + } + }.navigationTitle(self.name) + .onAppear { self._load() } } @@ -43,10 +107,126 @@ struct APICallsView: View { @MainActor fileprivate func _load() { Task { - self.text = await StoreCenter.main.apiCallsFileContent(resourceName: self.name) + await self.load(self.type) +// self.text = await StoreCenter.main.apiCallsFileContent(resourceName: self.name) } } + func load(_ type: T.Type) async { + let calls: [ApiCall] = await StoreCenter.main.apiCalls(type: type) + self.apiCalls = calls + } + +} + +struct APICallView: View { + + var apiCall: any SomeCall + var type: any SyncedStorable.Type + @State private var errorMessage: String? = nil + @State private var isLoading: Bool = false + @State private var showSuccessPopover: Bool = false + + var body: some View { + Form { + Section { + LabeledContent("Method", value: apiCall.method.rawValue) + LabeledContent("Data id", value: apiCall.stringId) + LabeledContent("Attempts", value: apiCall.attemptsCount.string) + } + + Section { + if isLoading { + ProgressView() + .padding() + } else { + + Button("Execute") { + self.execute() + // Clear previous error when attempting again + errorMessage = nil + } + .disabled(isLoading) + } + if let errorMessage = errorMessage { + LabeledContent("Error", value: errorMessage) + // VStack(alignment: .leading) { + // Text("Error:") + // .fontWeight(.bold) + // .foregroundColor(.red) + // Text(errorMessage) + // .foregroundColor(.red) + // } + } + if showSuccessPopover { + VStack { + Image(systemName: "checkmark.circle.fill") + .foregroundColor(.green) + .font(.title) + Text("Success") + .foregroundColor(.green) + } + .padding() + .background( + RoundedRectangle(cornerRadius: 8) + .fill(Color.white) + .shadow(radius: 2) + ) + .transition(.scale.combined(with: .opacity)) + } + + } + Section { + Text(apiCall.dataContent ?? "none").monospaced().font(.caption) + } + + } + .animation(.easeInOut, value: showSuccessPopover) + .navigationTitle(String(describing: self.type)) + } + + func execute() { + self.execute(type: self.type) + } + + func execute(type: T.Type) { + if let call = self.apiCall as? ApiCall { + Task { + await MainActor.run { + isLoading = true + } + + do { + let results = try await StoreCenter.main.execute(apiCalls: [call]) + + await MainActor.run { + isLoading = false + if let result = results.first { + errorMessage = result.message + showSuccessPopover = (200..<300).contains(result.status) + } else { + errorMessage = "no results" + } + } + + // Update UI on the main thread +// await MainActor.run { +// errorMessage = nil +// isLoading = false +// +// // Show success popover +// showSuccessPopover = true +// } + } catch { + // Update UI on the main thread + await MainActor.run { + errorMessage = error.localizedDescription + isLoading = false + } + } + } + } + } } //#Preview { diff --git a/PadelClub/Views/Navigation/Toolbox/DebugSettingsView.swift b/PadelClub/Views/Navigation/Toolbox/DebugSettingsView.swift index 84786f0..76716fb 100644 --- a/PadelClub/Views/Navigation/Toolbox/DebugSettingsView.swift +++ b/PadelClub/Views/Navigation/Toolbox/DebugSettingsView.swift @@ -11,12 +11,22 @@ import LeStorage struct DebugSettingsView: View { var body: some View { List { - LabeledContent("UUID", value: self._userId) - LabeledContent("User Name", value: self._userName) - LabeledContent("Token", value: self._token) - LabeledContent("Server", value: self._server) - LabeledContent("Synchronized", value: self._synchronized) - LabeledContent("CollectionsCanSynchronize", value: self._canSynchronize) + + Section("Status") { + LabeledContent("Has Websocket Manager", value: self._hasWebSocketManager) + LabeledContent("Websocket ping", value: self._wsPingStatus) + LabeledContent("Websocket failure", value: self._wsFailure) + LabeledContent("Last sync date", value: self._lastSyncDate) + } + + Section("Settings") { + LabeledContent("UUID", value: self._userId) + LabeledContent("User Name", value: self._userName) + LabeledContent("Token", value: self._token) + LabeledContent("Server", value: self._apiURL) + LabeledContent("Synchronized", value: self._synchronized) + LabeledContent("CollectionsCanSynchronize", value: self._canSynchronize) + } } } @@ -25,15 +35,16 @@ struct DebugSettingsView: View { } fileprivate var _userName: String { - return StoreCenter.main.userName() ?? "" + return StoreCenter.main.userName ?? "" } fileprivate var _token: String { - return StoreCenter.main.token() ?? "" + let token = try? StoreCenter.main.token() + return token ?? "" } - fileprivate var _server: String { - return PListReader.readString(plist: "local", key: "server") ?? "" + fileprivate var _apiURL: String { + return StoreCenter.main.apiURL ?? "not configured" } fileprivate var _synchronized: String { @@ -47,6 +58,19 @@ struct DebugSettingsView: View { fileprivate var _canSynchronize: String { return "\(StoreCenter.main.collectionsCanSynchronize)" } + + fileprivate var _wsPingStatus: String { + return "\(StoreCenter.main.websocketPingStatus)" + } + fileprivate var _wsFailure: String { + return "\(StoreCenter.main.websocketFailure)" + } + fileprivate var _hasWebSocketManager: String { + return "\(StoreCenter.main.hasWebSocketManager)" + } + fileprivate var _lastSyncDate: String { + return "\(StoreCenter.main.lastSyncDate)" + } } //#Preview { diff --git a/PadelClub/Views/Navigation/Toolbox/ToolboxView.swift b/PadelClub/Views/Navigation/Toolbox/ToolboxView.swift index c6b2177..1b16842 100644 --- a/PadelClub/Views/Navigation/Toolbox/ToolboxView.swift +++ b/PadelClub/Views/Navigation/Toolbox/ToolboxView.swift @@ -15,6 +15,12 @@ struct ToolboxView: View { @Environment(NavigationViewModel.self) private var navigation: NavigationViewModel @State private var didResetApiCalls: Bool = false + @State var showDebugViews: Bool = false + + @State private var tapCount = 0 + @State private var lastTapTime: Date? = nil + private let tapTimeThreshold: TimeInterval = 1.0 + var lastDataSource: String? { dataStore.appSettings.lastDataSource } @@ -39,93 +45,21 @@ struct ToolboxView: View { .contextMenu { ShareLink(item: URLs.main.url) } - + SupportButtonView(contentIsUnavailable: false) Link(destination: URLs.appReview.url) { Text("Partagez vos impressions !") } - + Link(destination: URLs.instagram.url) { Text("Compte Instagram PadelClub.app") } } - #if DEBUG - - Section { - NavigationLink("Settings") { - DebugSettingsView() - } - NavigationLink("API calls") { - APICallsListView() - } - } - - Section { - RowButtonView("Reset ALL API Calls") { - StoreCenter.main.resetApiCalls() - Logger.log("Api calls reset") - } - } - Section { - RowButtonView("Fix Names") { - - for tournament in dataStore.tournaments { - - let store = tournament.tournamentStore - let playerRegistrations = store.playerRegistrations - playerRegistrations.forEach { player in - player.firstName = player.firstName.trimmed.capitalized - player.lastName = player.lastName.trimmed.uppercased() - } - - do { - try store.playerRegistrations.addOrUpdate(contentOfs: playerRegistrations) - } catch { - Logger.error(error) - } - } - - } - } - - Section { - RowButtonView("Delete teams") { - - for tournament in DataStore.shared.tournaments { - - let store: TournamentStore = tournament.tournamentStore - - let teamRegistrations = store.teamRegistrations.filter({ $0.tournamentObject() == nil }) - do { - try store.teamRegistrations.delete(contentOfs: teamRegistrations) - } catch { - Logger.error(error) - } - } - } - } - - Section { - // TODO - RowButtonView("Delete players") { - - for tournament in DataStore.shared.tournaments { - let store: TournamentStore = tournament.tournamentStore - - let playersRegistrations = store.playerRegistrations.filter({ $0.team() == nil }) - do { - try store.playerRegistrations.delete(contentOfs: playersRegistrations) - } catch { - Logger.error(error) - } - } - - } - + if self.showDebugViews { + DebugView() } - #endif Section { NavigationLink { @@ -133,7 +67,7 @@ struct ToolboxView: View { } label: { Label("Rechercher un joueur", systemImage: "person.fill.viewfinder") } - + NavigationLink { RankCalculatorView() } label: { @@ -182,13 +116,13 @@ struct ToolboxView: View { didResetApiCalls = true } } - + Section { Link(destination: URLs.appDescription.url) { Text("Page de présentation de Padel Club") } } - + Section { Link(destination: URLs.privacy.url) { Text("Politique de confidentialité") @@ -205,6 +139,16 @@ struct ToolboxView: View { } } } + .onAppear { +#if DEBUG + self.showDebugViews = true +#endif + +#if TESTFLIGHT + self.showDebugViews = true +#endif + + } .overlay(alignment: .bottom) { if didResetApiCalls { Label("logs effacés", systemImage: "checkmark") @@ -217,8 +161,16 @@ struct ToolboxView: View { } } } - .navigationTitle(TabDestination.toolbox.title) +// .navigationBarTitleDisplayMode(.large) + // .navigationTitle(TabDestination.toolbox.title) .toolbar { + ToolbarItem(placement: .principal) { + Text(TabDestination.toolbox.title) + .font(.headline) + .onTapGesture { + _handleTitleTap() + } + } ToolbarItem(placement: .topBarLeading) { Link(destination: URLs.appStore.url) { Text("v\(PadelClubApp.appVersion)") @@ -240,12 +192,119 @@ struct ToolboxView: View { } } } + + private func _handleTitleTap() { + // Reset counter if too much time elapsed since last tap + if let lastTime = lastTapTime, Date().timeIntervalSince(lastTime) > tapTimeThreshold { + tapCount = 0 + } + + // Update tap count and time + tapCount += 1 + lastTapTime = Date() + + print("Tap count: \(tapCount)") + + // Check if we've reached 4 taps + if tapCount == 4 { + _showDebugView() + tapCount = 0 + } + } + + private func _showDebugView() { + self.showDebugViews = true + } + } //#Preview { // ToolboxView() //} +struct DebugView: View { + + @EnvironmentObject var dataStore: DataStore + + var body: some View { + + Section { + NavigationLink("Settings") { + DebugSettingsView() + } + NavigationLink("API calls") { + APICallsListView() + } + } + + Section { + RowButtonView("Reset ALL API Calls") { + StoreCenter.main.resetApiCalls() + Logger.log("Api calls reset") + } + } + Section { + RowButtonView("Fix Names") { + + for tournament in dataStore.tournaments { + + if let store = tournament.tournamentStore { + let playerRegistrations = store.playerRegistrations + playerRegistrations.forEach { player in + player.firstName = player.firstName.trimmed.capitalized + player.lastName = player.lastName.trimmed.uppercased() + } + + do { + try store.playerRegistrations.addOrUpdate(contentOfs: playerRegistrations) + } catch { + Logger.error(error) + } + } + } + + } + } + + Section { + RowButtonView("Delete teams") { + + for tournament in DataStore.shared.tournaments { + + if let store: TournamentStore = tournament.tournamentStore { + + let teamRegistrations = store.teamRegistrations.filter({ $0.tournamentObject() == nil }) + do { + try store.teamRegistrations.delete(contentOfs: teamRegistrations) + } catch { + Logger.error(error) + } + } + } + } + } + + Section { + // TODO + RowButtonView("Delete players") { + + for tournament in DataStore.shared.tournaments { + if let store: TournamentStore = tournament.tournamentStore { + + let playersRegistrations = store.playerRegistrations.filter({ $0.team() == nil }) + do { + try store.playerRegistrations.delete(contentOfs: playersRegistrations) + } catch { + Logger.error(error) + } + } + } + + } + + } + } +} struct ZipLog: Transferable { private func _getZip() -> URL? { diff --git a/PadelClub/Views/Navigation/Umpire/NetworkStatusView.swift b/PadelClub/Views/Navigation/Umpire/NetworkStatusView.swift new file mode 100644 index 0000000..07f8ca1 --- /dev/null +++ b/PadelClub/Views/Navigation/Umpire/NetworkStatusView.swift @@ -0,0 +1,45 @@ +// +// NetworkStatusView.swift +// PadelClub +// +// Created by Laurent Morvillier on 25/10/2024. +// + +import SwiftUI +import LeStorage + +struct NetworkStatusView: View { + + @State private var isConnected = false + @State private var timer: Timer? + + var body: some View { + Image(systemName: self.isConnected ? "network" : "network.slash") + .resizable() + .scaledToFit() + + .onAppear { + self._defineStatus() + // Start the timer when the view appears + timer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: true) { _ in + withAnimation { + self._defineStatus() + } + } + } + .onDisappear { + // Clean up timer when view disappears + timer?.invalidate() + timer = nil + } + } + + fileprivate func _defineStatus() { + self.isConnected = LeStorage.NetworkMonitor.shared.isConnected + } + +} + +#Preview { + NetworkStatusView() +} diff --git a/PadelClub/Views/Navigation/Umpire/PadelClubView.swift b/PadelClub/Views/Navigation/Umpire/PadelClubView.swift index 5263260..5c66ff9 100644 --- a/PadelClub/Views/Navigation/Umpire/PadelClubView.swift +++ b/PadelClub/Views/Navigation/Umpire/PadelClubView.swift @@ -40,10 +40,10 @@ struct PadelClubView: View { if let currentMonth = monthData.first, currentMonth.incompleteMode { Section { - Text("Attention, depuis Août 2024, les données fédérales publiques des joueurs (messieurs) récupérables sont incomplètes car limité au 80.000 premiers joueurs.") + Text("Attention, depuis Août 2024, les données fédérales publiques des joueurs (messieurs) récupérables sont incomplètes car limitées aux 80 000 premiers joueurs.") if currentMonth.maleUnrankedValue == nil { - Text("Le rang d'un joueur non-classé n'est donc pas calculable pour le moment, Padel Club utilisera une valeur par défaut de de 90.000.") + Text("Le rang d'un joueur non-classé n'est donc pas calculable pour le moment, Padel Club utilisera une valeur par défaut de 90 000.") } Text("Un classement souligné comme ci-dessous indiquera que l'information provient d'un mois précédent.") diff --git a/PadelClub/Views/Navigation/Umpire/UmpireView.swift b/PadelClub/Views/Navigation/Umpire/UmpireView.swift index 7fb1421..78adfb1 100644 --- a/PadelClub/Views/Navigation/Umpire/UmpireView.swift +++ b/PadelClub/Views/Navigation/Umpire/UmpireView.swift @@ -55,7 +55,7 @@ struct UmpireView: View { } } - if self._isConnected() { + if StoreCenter.main.isAuthenticated { NavigationLink { AccountView(user: dataStore.user) { } } label: { @@ -186,11 +186,12 @@ struct UmpireView: View { .toolbar { #if DEBUG ToolbarItem(placement: .topBarTrailing) { - if StoreCenter.main.collectionsCanSynchronize { - Image(systemName: "checkmark.icloud") - } else { - Image(systemName: "icloud.slash") - } + NetworkStatusView() +// if StoreCenter.main.collectionsCanSynchronize { +// Image(systemName: "checkmark.icloud") +// } else { +// Image(systemName: "icloud.slash") +// } } #endif } @@ -241,10 +242,6 @@ struct UmpireView: View { } } - fileprivate func _isConnected() -> Bool { - return dataStore.user.username.count > 0 && StoreCenter.main.hasToken() - } - } struct AccountRowView: View { @@ -253,17 +250,17 @@ struct AccountRowView: View { var userName: String var body: some View { - let hasToken = StoreCenter.main.hasToken() + let isAuthenticated = StoreCenter.main.isAuthenticated LabeledContent { - if hasToken { + if isAuthenticated { Text(self.userName) - } else if StoreCenter.main.userName() != nil { + } else if StoreCenter.main.userName != nil { Image(systemName: "xmark.circle.fill") .foregroundStyle(.logoRed) } } label: { Label("Mon compte", systemImage: "person.fill") - if hasToken && dataStore.user.email.isEmpty == false { + if isAuthenticated && dataStore.user.email.isEmpty == false { Text(dataStore.user.email) } } diff --git a/PadelClub/Views/Planning/GroupStageScheduleEditorView.swift b/PadelClub/Views/Planning/GroupStageScheduleEditorView.swift index ec29423..a0e6866 100644 --- a/PadelClub/Views/Planning/GroupStageScheduleEditorView.swift +++ b/PadelClub/Views/Planning/GroupStageScheduleEditorView.swift @@ -17,7 +17,7 @@ struct GroupStageScheduleEditorView: View { @State private var startDate: Date @State private var currentDate: Date? - var tournamentStore: TournamentStore { + var tournamentStore: TournamentStore? { return self.tournament.tournamentStore } @@ -42,7 +42,7 @@ struct GroupStageScheduleEditorView: View { private func _save() { do { - try tournamentStore.groupStages.addOrUpdate(instance: groupStage) + try tournamentStore?.groupStages.addOrUpdate(instance: groupStage) } catch { Logger.error(error) } diff --git a/PadelClub/Views/Planning/LoserRoundScheduleEditorView.swift b/PadelClub/Views/Planning/LoserRoundScheduleEditorView.swift index e14ad4b..0d5eafb 100644 --- a/PadelClub/Views/Planning/LoserRoundScheduleEditorView.swift +++ b/PadelClub/Views/Planning/LoserRoundScheduleEditorView.swift @@ -19,7 +19,7 @@ struct LoserRoundScheduleEditorView: View { @State private var matchFormat: MatchFormat @State private var currentDate: Date? - var tournamentStore: TournamentStore { + var tournamentStore: TournamentStore? { return self.tournament.tournamentStore } @@ -92,7 +92,7 @@ struct LoserRoundScheduleEditorView: View { private func _save() { do { - try self.tournamentStore.rounds.addOrUpdate(contentOfs: upperRound.loserRounds()) + try self.tournamentStore?.rounds.addOrUpdate(contentOfs: upperRound.loserRounds()) } catch { Logger.error(error) } diff --git a/PadelClub/Views/Planning/LoserRoundStepScheduleEditorView.swift b/PadelClub/Views/Planning/LoserRoundStepScheduleEditorView.swift index 6ee09ec..567c182 100644 --- a/PadelClub/Views/Planning/LoserRoundStepScheduleEditorView.swift +++ b/PadelClub/Views/Planning/LoserRoundStepScheduleEditorView.swift @@ -20,7 +20,7 @@ struct LoserRoundStepScheduleEditorView: View { @State private var startDate: Date //@State private var matchFormat: MatchFormat - var tournamentStore: TournamentStore { + var tournamentStore: TournamentStore? { return self.tournament.tournamentStore } @@ -79,7 +79,7 @@ struct LoserRoundStepScheduleEditorView: View { private func _save() { do { - try tournamentStore.rounds.addOrUpdate(contentOfs: upperRound.loserRounds(forRoundIndex: round.index)) + try tournamentStore?.rounds.addOrUpdate(contentOfs: upperRound.loserRounds(forRoundIndex: round.index)) } catch { Logger.error(error) } diff --git a/PadelClub/Views/Planning/MatchScheduleEditorView.swift b/PadelClub/Views/Planning/MatchScheduleEditorView.swift index 592a4ac..24f534a 100644 --- a/PadelClub/Views/Planning/MatchScheduleEditorView.swift +++ b/PadelClub/Views/Planning/MatchScheduleEditorView.swift @@ -14,7 +14,7 @@ struct MatchScheduleEditorView: View { @State private var startDate: Date @State private var currentDate: Date? - var tournamentStore: TournamentStore { + var tournamentStore: TournamentStore? { return self.tournament.tournamentStore } @@ -54,7 +54,7 @@ struct MatchScheduleEditorView: View { private func _save() { do { - try tournamentStore.matches.addOrUpdate(instance: match) + try tournamentStore?.matches.addOrUpdate(instance: match) } catch { Logger.error(error) } diff --git a/PadelClub/Views/Planning/PlanningSettingsView.swift b/PadelClub/Views/Planning/PlanningSettingsView.swift index 8a1d024..8b1ab94 100644 --- a/PadelClub/Views/Planning/PlanningSettingsView.swift +++ b/PadelClub/Views/Planning/PlanningSettingsView.swift @@ -14,7 +14,7 @@ struct PlanningSettingsView: View { @Bindable var tournament: Tournament @Bindable var matchScheduler: MatchScheduler - + @State private var groupStageChunkCount: Int @State private var isScheduling: Bool = false @State private var schedulingDone: Bool = false @@ -24,11 +24,11 @@ struct PlanningSettingsView: View { @State private var deletingDateMatchesDone: Bool = false @State private var deletingDone: Bool = false @State private var presentFormatHelperView: Bool = false - - var tournamentStore: TournamentStore { + + var tournamentStore: TournamentStore? { return self.tournament.tournamentStore } - + init(tournament: Tournament) { self.tournament = tournament if let matchScheduler = tournament.matchScheduler() { @@ -44,7 +44,7 @@ struct PlanningSettingsView: View { self._groupStageChunkCount = State(wrappedValue: tournament.getGroupStageChunkValue()) } } - + var body: some View { List { if tournament.payment == nil { @@ -63,7 +63,7 @@ struct PlanningSettingsView: View { } TournamentFieldsManagerView(localizedStringKey: "Terrains maximum", count: $tournament.courtCount) - + if let event = tournament.eventObject() { NavigationLink { CourtAvailabilitySettingsView(event: event) @@ -76,7 +76,7 @@ struct PlanningSettingsView: View { } } } - + NavigationLink { MultiCourtPickerView(matchScheduler: matchScheduler) .environment(tournament) @@ -119,7 +119,7 @@ struct PlanningSettingsView: View { } } } - + if issueFound { Text("Padel Club n'a pas réussi à définir un horaire pour tous les matchs de ce tournoi, à cause de la programmation d'autres tournois ou de l'indisponibilité des terrains.") .foregroundStyle(.logoRed) @@ -132,7 +132,7 @@ struct PlanningSettingsView: View { } label: { Text("Voir plus d'options intelligentes") } - + if let event, event.tournaments.count > 1 { Toggle(isOn: $matchScheduler.overrideCourtsUnavailability) { Text("Ne pas tenir compte des autres tournois") @@ -143,7 +143,7 @@ struct PlanningSettingsView: View { Text("Cette option fait en sorte qu'un terrain pris par un match d'un autre tournoi de cet événement soit toujours considéré comme libre.") } } - + _smartView() } .navigationTitle("Réglages") @@ -171,7 +171,7 @@ struct PlanningSettingsView: View { .headerProminence(.increased) .onAppear { do { - try self.tournamentStore.matchSchedulers.addOrUpdate(instance: matchScheduler) + try self.tournamentStore?.matchSchedulers.addOrUpdate(instance: matchScheduler) } catch { Logger.error(error) } @@ -188,13 +188,13 @@ struct PlanningSettingsView: View { .deferredRendering(for: .seconds(2)) } } - + if deletingDone { Label("Tous les horaires ont été supprimés", systemImage: "clock.badge.xmark") .toastFormatted() .deferredRendering(for: .seconds(2)) } - + if deletingDateMatchesDone { Label("Horaires des matchs supprimés", systemImage: "clock.badge.xmark") .toastFormatted() @@ -212,7 +212,7 @@ struct PlanningSettingsView: View { _save() } } - + private func _localizedFooterMessage(groupStagesWithDateIsEmpty: Bool, roundsWithDateIsEmpty: Bool) -> String { let base = "Supprime les horaires des matchs restants non démarrés." let extend = " Garde les horaires définis pour les " @@ -226,20 +226,20 @@ struct PlanningSettingsView: View { return base + extend + "poules et les manches du tableau." } } - + @ViewBuilder private func _smartView() -> some View { let allMatches = tournament.allMatches().filter({ $0.hasEnded() == false && $0.hasStarted() == false }) let allGroupStages = tournament.allGroupStages() let allRounds = tournament.allRounds() let matchesWithDate = allMatches.filter({ $0.startDate != nil }) - + let groupMatchesByDay = _groupMatchesByDay(matches: matchesWithDate) - + let countedSet = _matchCountPerDay(matchesByDay: groupMatchesByDay, tournament: tournament) - + _formatPerDayView(matchCountPerDay: countedSet) - + let groupStagesWithDate = allGroupStages.filter({ $0.startDate != nil }) let roundsWithDate = allRounds.filter({ $0.startDate != nil }) if matchesWithDate.isEmpty == false { @@ -251,7 +251,7 @@ struct PlanningSettingsView: View { $0.startDate = nil $0.confirmed = false }) - try self.tournamentStore.matches.addOrUpdate(contentOfs: allMatches) + try self.tournamentStore?.matches.addOrUpdate(contentOfs: allMatches) deletingDateMatchesDone = true } catch { Logger.error(error) @@ -261,15 +261,15 @@ struct PlanningSettingsView: View { Text(_localizedFooterMessage(groupStagesWithDateIsEmpty: groupStagesWithDate.isEmpty, roundsWithDateIsEmpty: roundsWithDate.isEmpty)) } } - + if groupStagesWithDate.isEmpty == false { Section { RowButtonView("Supprimer les horaires des poules", role: .destructive) { do { deletingDone = false allGroupStages.forEach({ $0.startDate = nil }) - try self.tournamentStore.groupStages.addOrUpdate(contentOfs: allGroupStages) - + try self.tournamentStore?.groupStages.addOrUpdate(contentOfs: allGroupStages) + deletingDone = true } catch { Logger.error(error) @@ -277,14 +277,14 @@ struct PlanningSettingsView: View { } } } - + if roundsWithDate.isEmpty == false { Section { RowButtonView("Supprimer les horaires du tableau", role: .destructive) { do { deletingDone = false allRounds.forEach({ $0.startDate = nil }) - try self.tournamentStore.rounds.addOrUpdate(contentOfs: allRounds) + try self.tournamentStore?.rounds.addOrUpdate(contentOfs: allRounds) deletingDone = true } catch { Logger.error(error) @@ -294,7 +294,7 @@ struct PlanningSettingsView: View { Text("Supprime les horaires définis pour les manches du tableau.") } } - + if matchesWithDate.isEmpty == false && groupStagesWithDate.isEmpty == false && roundsWithDate.isEmpty == false { Section { RowButtonView("Supprimer tous les horaires", role: .destructive) { @@ -304,13 +304,13 @@ struct PlanningSettingsView: View { $0.startDate = nil $0.confirmed = false }) - try self.tournamentStore.matches.addOrUpdate(contentOfs: allMatches) - + try self.tournamentStore?.matches.addOrUpdate(contentOfs: allMatches) + allGroupStages.forEach({ $0.startDate = nil }) - try self.tournamentStore.groupStages.addOrUpdate(contentOfs: allGroupStages) - + try self.tournamentStore?.groupStages.addOrUpdate(contentOfs: allGroupStages) + allRounds.forEach({ $0.startDate = nil }) - try self.tournamentStore.rounds.addOrUpdate(contentOfs: allRounds) + try self.tournamentStore?.rounds.addOrUpdate(contentOfs: allRounds) deletingDone = true } catch { Logger.error(error) @@ -320,7 +320,7 @@ struct PlanningSettingsView: View { Text("Supprime les horaires des matchs restants non démarrés, les horaires définis pour les poules et les manches du tableau.") } } - + #if DEBUG Section { RowButtonView("Debug delete all dates", role: .destructive) { @@ -331,13 +331,13 @@ struct PlanningSettingsView: View { $0.endDate = nil $0.confirmed = false }) - try self.tournamentStore.matches.addOrUpdate(contentOfs: tournament.allMatches()) - + try self.tournamentStore?.matches.addOrUpdate(contentOfs: tournament.allMatches()) + allGroupStages.forEach({ $0.startDate = nil }) - try self.tournamentStore.groupStages.addOrUpdate(contentOfs: allGroupStages) - + try self.tournamentStore?.groupStages.addOrUpdate(contentOfs: allGroupStages) + allRounds.forEach({ $0.startDate = nil }) - try self.tournamentStore.rounds.addOrUpdate(contentOfs: allRounds) + try self.tournamentStore?.rounds.addOrUpdate(contentOfs: allRounds) deletingDone = true } catch { Logger.error(error) @@ -348,7 +348,7 @@ struct PlanningSettingsView: View { } #endif - + Section { if groupStagesWithDate.isEmpty == false { Text("Des dates de démarrages ont été indiqué pour les poules et seront prises en compte.") @@ -371,7 +371,7 @@ struct PlanningSettingsView: View { Text("Padel Club programmera tous les matchs de votre tournoi en fonction de différents paramètres, ") + Text("tout en tenant compte des horaires que vous avez fixé.").underline() } } - + @ViewBuilder private func _optionsView() -> some View { List { @@ -398,7 +398,7 @@ struct PlanningSettingsView: View { matchScheduler.groupStageChunkCount = nil } } - + if parallelType { TournamentFieldsManagerView(localizedStringKey: "Poule en parallèle", count: $groupStageChunkCount, max: tournament.groupStageCount) .onChange(of: groupStageChunkCount) { @@ -408,7 +408,7 @@ struct PlanningSettingsView: View { } footer: { Text("Vous pouvez indiquer le nombre de poule démarrant en même temps.") } - + Section { Toggle(isOn: $matchScheduler.simultaneousStart) { Text("Démarrage simultané") @@ -417,7 +417,7 @@ struct PlanningSettingsView: View { Text("En simultané, un match de chaque poule d'un groupe de poule sera joué avant de passer à la suite de la programmation. Si l'option est désactivée, un maximum de matchs simultanés d'une poule sera programmé avant de passer à la poule suivante.") } } - + Section { Toggle(isOn: $matchScheduler.randomizeCourts) { Text("Distribuer les terrains au hasard") @@ -431,7 +431,7 @@ struct PlanningSettingsView: View { } footer: { Text("Tout en tenant compte de l'option ci-dessous, Padel Club essaiera de remplir les créneaux à chaque rotation.") } - + Section { Toggle(isOn: $matchScheduler.shouldHandleUpperRoundSlice) { Text("Équilibrer les matchs d'une manche") @@ -439,13 +439,13 @@ struct PlanningSettingsView: View { } footer: { Text("Cette option permet de programmer une manche sur plusieurs rotation de manière équilibrée dans le cas où il y a plus de matchs à jouer dans cette manche que de terrains.") } - + Section { Toggle(isOn: $matchScheduler.shouldEndRoundBeforeStartingNext) { Text("Finir une manche, classement inclus avant de continuer") } } - + Section { Toggle(isOn: $matchScheduler.accountUpperBracketBreakTime) { Text("Tenir compte des temps de pause réglementaires") @@ -453,7 +453,7 @@ struct PlanningSettingsView: View { } header: { Text("Tableau") } - + Section { Toggle(isOn: $matchScheduler.accountLoserBracketBreakTime) { Text("Tenir compte des temps de pause réglementaires") @@ -461,19 +461,19 @@ struct PlanningSettingsView: View { } header: { Text("Classement") } - + Section { Toggle(isOn: $matchScheduler.rotationDifferenceIsImportant) { Text("Forcer une rotation d'attente supplémentaire entre 2 phases") } - + LabeledContent { StepperView(count: $matchScheduler.upperBracketRotationDifference, minimum: 0, maximum: 2) } label: { Text("Tableau") } .disabled(matchScheduler.rotationDifferenceIsImportant == false) - + LabeledContent { StepperView(count: $matchScheduler.loserBracketRotationDifference, minimum: 0, maximum: 2) } label: { @@ -483,7 +483,7 @@ struct PlanningSettingsView: View { } footer: { Text("Cette option ajoute du temps entre 2 rotations, permettant ainsi de mieux configurer plusieurs tournois se déroulant en même temps.") } - + Section { LabeledContent { StepperView(count: $matchScheduler.timeDifferenceLimit, step: 5) @@ -501,34 +501,34 @@ struct PlanningSettingsView: View { .navigationBarTitleDisplayMode(.inline) .toolbarBackground(.visible, for: .navigationBar) } - + private func _setupSchedule() async -> Bool { return matchScheduler.updateSchedule(tournament: tournament) } - + private func _save() { do { - try self.tournamentStore.matchSchedulers.addOrUpdate(instance: matchScheduler) + try self.tournamentStore?.matchSchedulers.addOrUpdate(instance: matchScheduler) try dataStore.tournaments.addOrUpdate(instance: tournament) } catch { Logger.error(error) } } - + private func _groupMatchesByDay(matches: [Match]) -> [Date: [Match]] { var matchesByDay = [Date: [Match]]() let calendar = Calendar.current - + for match in matches { // Extract day/month/year and create a date with only these components let components = calendar.dateComponents([.year, .month, .day], from: match.computedStartDateForSorting) let strippedDate = calendar.date(from: components)! - + // Group matches by the strippedDate (only day/month/year) if matchesByDay[strippedDate] == nil { matchesByDay[strippedDate] = [] } - + let shouldIncludeMatch: Bool switch match.matchType { case .groupStage: @@ -538,24 +538,24 @@ struct PlanningSettingsView: View { case .loserBracket: shouldIncludeMatch = true } - + if shouldIncludeMatch { matchesByDay[strippedDate]!.append(match) } } - + return matchesByDay } - + private func _matchCountPerDay(matchesByDay: [Date: [Match]], tournament: Tournament) -> [Date: NSCountedSet] { let days = matchesByDay.keys var matchCountPerDay = [Date: NSCountedSet]() - + for day in days { if let matches = matchesByDay[day] { var groupStageCount = 0 let countedSet = NSCountedSet() - + for match in matches { switch match.matchType { case .groupStage: @@ -570,15 +570,15 @@ struct PlanningSettingsView: View { break } } - + if groupStageCount > 0 { for _ in 0.. totalForThisFormat @@ -628,7 +628,7 @@ struct PlanningSettingsView: View { } } } - + // Helper function to format date to string (you can customize the format) private func _formattedDate(_ date: Date) -> String { let formatter = DateFormatter() diff --git a/PadelClub/Views/Planning/PlanningView.swift b/PadelClub/Views/Planning/PlanningView.swift index 0f3193c..7e28e6a 100644 --- a/PadelClub/Views/Planning/PlanningView.swift +++ b/PadelClub/Views/Planning/PlanningView.swift @@ -97,7 +97,7 @@ struct PlanningView: View { ToolbarItem(placement: .topBarTrailing) { Button("Sauver") { do { - try self.tournament.tournamentStore.matches.addOrUpdate(contentOfs: allMatches) + try self.tournament.tournamentStore?.matches.addOrUpdate(contentOfs: allMatches) } catch { Logger.error(error) } @@ -503,7 +503,7 @@ struct PlanningView: View { // } // } // -// try? self.tournament.tournamentStore.matches.addOrUpdate(contentOfs: matches) +// try? self.tournament.tournamentStore?.matches.addOrUpdate(contentOfs: matches) // } // // diff --git a/PadelClub/Views/Planning/RoundScheduleEditorView.swift b/PadelClub/Views/Planning/RoundScheduleEditorView.swift index 679b306..2c42ac7 100644 --- a/PadelClub/Views/Planning/RoundScheduleEditorView.swift +++ b/PadelClub/Views/Planning/RoundScheduleEditorView.swift @@ -16,7 +16,7 @@ struct RoundScheduleEditorView: View { @State private var startDate: Date @State private var currentDate: Date? - var tournamentStore: TournamentStore { + var tournamentStore: TournamentStore? { return self.tournament.tournamentStore } @@ -71,7 +71,7 @@ struct RoundScheduleEditorView: View { private func _save() { do { - try tournamentStore.rounds.addOrUpdate(instance: round) + try tournamentStore?.rounds.addOrUpdate(instance: round) } catch { Logger.error(error) } diff --git a/PadelClub/Views/Planning/SchedulerView.swift b/PadelClub/Views/Planning/SchedulerView.swift index 333357e..a4e83b6 100644 --- a/PadelClub/Views/Planning/SchedulerView.swift +++ b/PadelClub/Views/Planning/SchedulerView.swift @@ -26,7 +26,7 @@ struct SchedulerView: View { var tournament: Tournament var destination: ScheduleDestination - var tournamentStore: TournamentStore { + var tournamentStore: TournamentStore? { return self.tournament.tournamentStore } @@ -49,7 +49,7 @@ struct SchedulerView: View { } do { try dataStore.tournaments.addOrUpdate(instance: tournament) - try self.tournamentStore.groupStages.addOrUpdate(contentOfs: groupStages) + try self.tournamentStore?.groupStages.addOrUpdate(contentOfs: groupStages) } catch { Logger.error(error) } @@ -90,7 +90,7 @@ struct SchedulerView: View { do { try dataStore.tournaments.addOrUpdate(instance: tournament) - try self.tournamentStore.groupStages.addOrUpdate(contentOfs: groupStages) + try self.tournamentStore?.groupStages.addOrUpdate(contentOfs: groupStages) } catch { Logger.error(error) } @@ -131,7 +131,7 @@ struct SchedulerView: View { Button { round.updateMatchFormatAndAllMatches(federalFormat) do { - try self.tournamentStore.rounds.addOrUpdate(instance: round) + try self.tournamentStore?.rounds.addOrUpdate(instance: round) } catch { Logger.error(error) } @@ -176,7 +176,7 @@ struct SchedulerView: View { Button { round.updateMatchFormatAndAllMatches(federalFormat) do { - try self.tournamentStore.rounds.addOrUpdate(instance: round) + try self.tournamentStore?.rounds.addOrUpdate(instance: round) } catch { Logger.error(error) } diff --git a/PadelClub/Views/Player/Components/PlayerPayView.swift b/PadelClub/Views/Player/Components/PlayerPayView.swift index 4382f14..e4e8b85 100644 --- a/PadelClub/Views/Player/Components/PlayerPayView.swift +++ b/PadelClub/Views/Player/Components/PlayerPayView.swift @@ -15,9 +15,9 @@ struct PlayerPayView: View { var body: some View { Picker(selection: $player.paymentType) { - Text("Non réglé").tag(nil as PlayerRegistration.PlayerPaymentType?) - ForEach(PlayerRegistration.PlayerPaymentType.allCases) { type in - Text(type.localizedLabel()).tag(type as PlayerRegistration.PlayerPaymentType?) + Text("Non réglé").tag(nil as PlayerPaymentType?) + ForEach(PlayerPaymentType.allCases) { type in + Text(type.localizedLabel()).tag(type as PlayerPaymentType?) } } label: { } @@ -29,10 +29,6 @@ struct PlayerPayView: View { } private func _save() { - do { - try tournamentStore.playerRegistrations.addOrUpdate(instance: player) - } catch { - Logger.error(error) - } + tournamentStore.playerRegistrations.addOrUpdate(instance: player) } } diff --git a/PadelClub/Views/Player/Components/PlayerPopoverView.swift b/PadelClub/Views/Player/Components/PlayerPopoverView.swift index 9bd893b..f51de04 100644 --- a/PadelClub/Views/Player/Components/PlayerPopoverView.swift +++ b/PadelClub/Views/Player/Components/PlayerPopoverView.swift @@ -251,7 +251,13 @@ struct PlayerPopoverView: View { } func createManualPlayer() { - let playerRegistration = PlayerRegistration(firstName: firstName.trimmedMultiline, lastName: lastName.trimmedMultiline, licenceId: license.trimmedMultiline.isEmpty ? nil : license, rank: rank, sex: PlayerRegistration.PlayerSexType(rawValue: sex)) + + let playerRegistration = PlayerRegistration( + firstName: firstName.trimmedMultiline, + lastName: lastName.trimmedMultiline, + licenceId: license.trimmedMultiline.isEmpty ? nil : license, + rank: rank, + sex: PlayerSexType(rawValue: sex)) self.creationCompletionHandler(playerRegistration) } diff --git a/PadelClub/Views/Player/Components/PlayerSexPickerView.swift b/PadelClub/Views/Player/Components/PlayerSexPickerView.swift index 91e7527..022e037 100644 --- a/PadelClub/Views/Player/Components/PlayerSexPickerView.swift +++ b/PadelClub/Views/Player/Components/PlayerSexPickerView.swift @@ -14,7 +14,7 @@ struct PlayerSexPickerView: View { @Environment(Tournament.self) var tournament: Tournament @Bindable var player: PlayerRegistration - var tournamentStore: TournamentStore { + var tournamentStore: TournamentStore? { return self.tournament.tournamentStore } @@ -23,8 +23,8 @@ struct PlayerSexPickerView: View { Text(player.playerLabel()) Spacer() Picker(selection: $player.sex) { - Text("Homme").tag(PlayerRegistration.PlayerSexType.male as PlayerRegistration.PlayerSexType?) - Text("Femme").tag(PlayerRegistration.PlayerSexType.female as PlayerRegistration.PlayerSexType?) + Text("Homme").tag(PlayerSexType.male as PlayerSexType?) + Text("Femme").tag(PlayerSexType.female as PlayerSexType?) } label: { } @@ -37,19 +37,11 @@ struct PlayerSexPickerView: View { } private func _save() { - do { - player.setComputedRank(in: tournament) - try tournamentStore.playerRegistrations.addOrUpdate(instance: player) - if let team = player.team() { - team.updateWeight(inTournamentCategory: tournament.tournamentCategory) - try tournamentStore.teamRegistrations.addOrUpdate(instance: team) - } - - } catch { - // Replace this implementation with code to handle the error appropriately. - // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. - let nsError = error as NSError - fatalError("Unresolved error \(nsError), \(nsError.userInfo)") + player.setComputedRank(in: tournament) + tournamentStore?.playerRegistrations.addOrUpdate(instance: player) + if let team = player.team() { + team.updateWeight(inTournamentCategory: tournament.tournamentCategory) + tournamentStore?.teamRegistrations.addOrUpdate(instance: team) } } diff --git a/PadelClub/Views/Player/PlayerDetailView.swift b/PadelClub/Views/Player/PlayerDetailView.swift index 52ebb05..52036ae 100644 --- a/PadelClub/Views/Player/PlayerDetailView.swift +++ b/PadelClub/Views/Player/PlayerDetailView.swift @@ -19,7 +19,7 @@ struct PlayerDetailView: View { @State private var email: String @FocusState var focusedField: PlayerRegistration.CodingKeys? - var tournamentStore: TournamentStore { + var tournamentStore: TournamentStore? { return self.tournament.tournamentStore } @@ -277,13 +277,13 @@ struct PlayerDetailView: View { private func _save() { do { - try self.tournamentStore.playerRegistrations.addOrUpdate(instance: player) + try self.tournamentStore?.playerRegistrations.addOrUpdate(instance: player) } catch { Logger.error(error) } if let team = player.team() { do { - try self.tournamentStore.teamRegistrations.addOrUpdate(instance: team) + try self.tournamentStore?.teamRegistrations.addOrUpdate(instance: team) } catch { Logger.error(error) } diff --git a/PadelClub/Views/Player/PlayerView.swift b/PadelClub/Views/Player/PlayerView.swift index db0a81d..7dbda7d 100644 --- a/PadelClub/Views/Player/PlayerView.swift +++ b/PadelClub/Views/Player/PlayerView.swift @@ -15,7 +15,7 @@ struct PlayerView: View { let player: PlayerRegistration - var tournamentStore: TournamentStore { + var tournamentStore: TournamentStore? { return self.tournament.tournamentStore } @@ -24,7 +24,7 @@ struct PlayerView: View { .swipeActions(edge: .trailing, allowsFullSwipe: true) { Button(role: .destructive) { do { - try self.tournamentStore.playerRegistrations.delete(instance: player) + try self.tournamentStore?.playerRegistrations.delete(instance: player) } catch { Logger.error(error) } diff --git a/PadelClub/Views/Round/DrawLogsView.swift b/PadelClub/Views/Round/DrawLogsView.swift index 6459955..cd16e96 100644 --- a/PadelClub/Views/Round/DrawLogsView.swift +++ b/PadelClub/Views/Round/DrawLogsView.swift @@ -52,7 +52,7 @@ struct DrawLogsView: View { Button("Tout effacer", role: .destructive) { do { - try tournament.tournamentStore.drawLogs.deleteAll() + try tournament.tournamentStore?.drawLogs.deleteAll() } catch { Logger.error(error) } diff --git a/PadelClub/Views/Round/LoserRoundSettingsView.swift b/PadelClub/Views/Round/LoserRoundSettingsView.swift index f39455f..5026317 100644 --- a/PadelClub/Views/Round/LoserRoundSettingsView.swift +++ b/PadelClub/Views/Round/LoserRoundSettingsView.swift @@ -64,7 +64,7 @@ struct LoserRoundSettingsView: View { }) allRoundMatches.forEach({ $0.setMatchName($0.roundTitle()) }) do { - try self.tournament.tournamentStore.matches.addOrUpdate(contentOfs: allRoundMatches) + try self.tournament.tournamentStore?.matches.addOrUpdate(contentOfs: allRoundMatches) } catch { Logger.error(error) } @@ -121,13 +121,13 @@ struct LoserRoundSettingsView: View { } do { - try self.tournament.tournamentStore.matches.addOrUpdate(contentOfs: matches) + try self.tournament.tournamentStore?.matches.addOrUpdate(contentOfs: matches) } catch { Logger.error(error) } do { - try self.tournament.tournamentStore.rounds.addOrUpdate(instance: upperBracketRound.round) + try self.tournament.tournamentStore?.rounds.addOrUpdate(instance: upperBracketRound.round) } catch { Logger.error(error) } @@ -145,7 +145,7 @@ struct LoserRoundSettingsView: View { match._toggleLoserMatchDisableState(true) } do { - try self.tournament.tournamentStore.matches.addOrUpdate(contentOfs: upperBracketRound.round.allLoserRoundMatches()) + try self.tournament.tournamentStore?.matches.addOrUpdate(contentOfs: upperBracketRound.round.allLoserRoundMatches()) } catch { Logger.error(error) } diff --git a/PadelClub/Views/Round/LoserRoundView.swift b/PadelClub/Views/Round/LoserRoundView.swift index fba3563..dd4cd71 100644 --- a/PadelClub/Views/Round/LoserRoundView.swift +++ b/PadelClub/Views/Round/LoserRoundView.swift @@ -133,7 +133,7 @@ struct LoserRoundView: View { let allRoundMatches = loserBracket.allMatches.filter({ $0.name == nil }) allRoundMatches.forEach({ $0.setMatchName($0.roundTitle()) }) do { - try self.tournament.tournamentStore.matches.addOrUpdate(contentOfs: allRoundMatches) + try self.tournament.tournamentStore?.matches.addOrUpdate(contentOfs: allRoundMatches) } catch { Logger.error(error) } diff --git a/PadelClub/Views/Round/RoundSettingsView.swift b/PadelClub/Views/Round/RoundSettingsView.swift index b785fec..ed2ca61 100644 --- a/PadelClub/Views/Round/RoundSettingsView.swift +++ b/PadelClub/Views/Round/RoundSettingsView.swift @@ -14,7 +14,7 @@ struct RoundSettingsView: View { @Environment(\.isEditingTournamentSeed) private var isEditingTournamentSeed @Environment(Tournament.self) var tournament: Tournament - var tournamentStore: TournamentStore { + var tournamentStore: TournamentStore? { return self.tournament.tournamentStore } @@ -25,7 +25,7 @@ struct RoundSettingsView: View { let issues = tournament.bracketTeamPlacementIssue() DisclosureGroup { ForEach(issues.shouldBeInIt, id: \.self) { id in - if let team: TeamRegistration = self.tournamentStore.teamRegistrations.findById(id) { + if let team: TeamRegistration = self.tournamentStore?.teamRegistrations.findById(id) { TeamRowView(team: team) } } @@ -38,15 +38,11 @@ struct RoundSettingsView: View { } DisclosureGroup { ForEach(issues.shouldNotBeInIt, id: \.self) { id in - if let team = self.tournamentStore.teamRegistrations.findById(id) { + if let team = self.tournamentStore?.teamRegistrations.findById(id) { Menu { Button("Retirer du tableau") { team.resetBracketPosition() - do { - try self.tournamentStore.teamRegistrations.addOrUpdate(instance: team) - } catch { - Logger.error(error) - } + self.tournamentStore?.teamRegistrations.addOrUpdate(instance: team) } } label: { TeamRowView(team: team) @@ -63,11 +59,7 @@ struct RoundSettingsView: View { RowButtonView("Valider l'état du tableau", role: .destructive) { tournament.shouldVerifyBracket = false - do { - try dataStore.tournaments.addOrUpdate(instance: tournament) - } catch { - Logger.error(error) - } + dataStore.tournaments.addOrUpdate(instance: tournament) } } footer: { Text("Suite à un changement dans votre liste d'inscrits, veuillez vérifier l'intégrité de votre tableau et valider que tout est ok.") @@ -136,7 +128,7 @@ struct RoundSettingsView: View { let allRoundMatches = tournament.allLoserRoundMatches() allRoundMatches.forEach({ $0.setMatchName($0.roundTitle()) }) do { - try self.tournament.tournamentStore.matches.addOrUpdate(contentOfs: allRoundMatches) + try self.tournament.tournamentStore?.matches.addOrUpdate(contentOfs: allRoundMatches) } catch { Logger.error(error) } @@ -172,8 +164,8 @@ struct RoundSettingsView: View { teams.forEach { team in team.resetBracketPosition() } - try tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teams) - try tournamentStore.rounds.delete(instance: lastRound) + try tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: teams) + try tournamentStore?.rounds.delete(instance: lastRound) } catch { Logger.error(error) } diff --git a/PadelClub/Views/Round/RoundView.swift b/PadelClub/Views/Round/RoundView.swift index 3e94096..1371a9e 100644 --- a/PadelClub/Views/Round/RoundView.swift +++ b/PadelClub/Views/Round/RoundView.swift @@ -30,7 +30,7 @@ struct RoundView: View { BracketEditTip.matchesHidden = upperRound.round.getDisabledMatches().count } - var tournamentStore: TournamentStore { + var tournamentStore: TournamentStore? { return self.tournament.tournamentStore } @@ -215,7 +215,7 @@ struct RoundView: View { FooterButtonView("Désactiver", role: .destructive) { match.disableMatch() do { - try tournamentStore.matches.addOrUpdate(instance: match) + try tournamentStore?.matches.addOrUpdate(instance: match) } catch { Logger.error(error) } @@ -306,7 +306,7 @@ struct RoundView: View { private func _save(seeds: [TeamRegistration]) { do { - try self.tournamentStore.teamRegistrations.addOrUpdate(contentOfs: seeds) + try self.tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: seeds) } catch { Logger.error(error) } @@ -344,7 +344,7 @@ struct RoundView: View { } do { - try tournament.tournamentStore.matches.addOrUpdate(contentOfs: matchesToUpdate + loserMatches) + try tournament.tournamentStore?.matches.addOrUpdate(contentOfs: matchesToUpdate + loserMatches) } catch { Logger.error(error) } diff --git a/PadelClub/Views/Score/EditScoreView.swift b/PadelClub/Views/Score/EditScoreView.swift index 52e3225..d04bf4a 100644 --- a/PadelClub/Views/Score/EditScoreView.swift +++ b/PadelClub/Views/Score/EditScoreView.swift @@ -207,7 +207,7 @@ struct EditScoreView: View { self.confirmScoreEdition = true if let match = matchDescriptor.match { do { - try match.tournamentStore.matches.addOrUpdate(instance: match) + try match.tournamentStore?.matches.addOrUpdate(instance: match) } catch { Logger.error(error) } diff --git a/PadelClub/Views/Shared/SupportButtonView.swift b/PadelClub/Views/Shared/SupportButtonView.swift index 0586c88..b715356 100644 --- a/PadelClub/Views/Shared/SupportButtonView.swift +++ b/PadelClub/Views/Shared/SupportButtonView.swift @@ -79,7 +79,8 @@ struct SupportButtonView: View { private func _getBody() -> String { let separator = "---------------------------------------------" - return ["Décrivez votre problème", "\n\n\n", separator, "token", StoreCenter.main.token(), separator, "userId", StoreCenter.main.userId, separator, "dataStore userId", DataStore.shared.user.id].compacted().joined(separator: "\n") + let token = try? StoreCenter.main.token() + return ["Décrivez votre problème", "\n\n\n", separator, "token", token ?? "", separator, "userId", StoreCenter.main.userId, separator, "dataStore userId", DataStore.shared.user.id].compacted().joined(separator: "\n") } private func _getDeviceIdentifier() -> String { diff --git a/PadelClub/Views/Team/CoachListView.swift b/PadelClub/Views/Team/CoachListView.swift index 7a980d6..f5a771e 100644 --- a/PadelClub/Views/Team/CoachListView.swift +++ b/PadelClub/Views/Team/CoachListView.swift @@ -50,7 +50,7 @@ struct CoachListView: View { private func _save() { do { - try self.tournament.tournamentStore.teamRegistrations.addOrUpdate(instance: team) + try self.tournament.tournamentStore?.teamRegistrations.addOrUpdate(instance: team) } catch { Logger.error(error) } diff --git a/PadelClub/Views/Team/EditingTeamView.swift b/PadelClub/Views/Team/EditingTeamView.swift index 6f930ca..d5fa22c 100644 --- a/PadelClub/Views/Team/EditingTeamView.swift +++ b/PadelClub/Views/Team/EditingTeamView.swift @@ -52,7 +52,7 @@ struct EditingTeamView: View { } } - var tournamentStore: TournamentStore { + var tournamentStore: TournamentStore? { return self.tournament.tournamentStore } @@ -199,7 +199,7 @@ struct EditingTeamView: View { _resetTeam() team.deleteTeamScores() do { - try tournamentStore.teamRegistrations.delete(instance: team) + try tournamentStore?.teamRegistrations.delete(instance: team) } catch { Logger.error(error) } @@ -345,7 +345,7 @@ struct EditingTeamView: View { } set: { confirmed in team.confirmationDate = confirmed ? Date() : nil do { - try tournamentStore.teamRegistrations.addOrUpdate(instance: team) + try tournamentStore?.teamRegistrations.addOrUpdate(instance: team) } catch { Logger.error(error) } @@ -360,7 +360,7 @@ struct EditingTeamView: View { $0.hasArrived = hasArrived } do { - try tournamentStore.playerRegistrations.addOrUpdate(contentOfs: team.unsortedPlayers()) + try tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: team.unsortedPlayers()) } catch { Logger.error(error) } @@ -369,7 +369,7 @@ struct EditingTeamView: View { private func _save() { do { - try tournamentStore.teamRegistrations.addOrUpdate(instance: team) + try tournamentStore?.teamRegistrations.addOrUpdate(instance: team) } catch { Logger.error(error) } diff --git a/PadelClub/Views/Tournament/FileImportView.swift b/PadelClub/Views/Tournament/FileImportView.swift index 635cd78..4e2353d 100644 --- a/PadelClub/Views/Tournament/FileImportView.swift +++ b/PadelClub/Views/Tournament/FileImportView.swift @@ -103,7 +103,7 @@ struct FileImportView: View { _fileProvider = .init(wrappedValue: defaultFileProvider) } - var tournamentStore: TournamentStore { + var tournamentStore: TournamentStore? { return self.tournament.tournamentStore } @@ -121,7 +121,7 @@ struct FileImportView: View { private func _deleteTeams(teams: [TeamRegistration]) async { await MainActor.run { do { - try tournamentStore.teamRegistrations.delete(contentOfs: teams) + try tournamentStore?.teamRegistrations.delete(contentOfs: teams) } catch { Logger.error(error) } diff --git a/PadelClub/Views/Tournament/Screen/AddTeamView.swift b/PadelClub/Views/Tournament/Screen/AddTeamView.swift index 62c265c..e96ebe5 100644 --- a/PadelClub/Views/Tournament/Screen/AddTeamView.swift +++ b/PadelClub/Views/Tournament/Screen/AddTeamView.swift @@ -46,7 +46,7 @@ struct AddTeamView: View { @State private var testMessageIndex: Int = 0 @State private var presentLocalMultiplayerSearch: Bool = false - var tournamentStore: TournamentStore { + var tournamentStore: TournamentStore? { return self.tournament.tournamentStore } @@ -365,12 +365,12 @@ struct AddTeamView: View { return false } - private func _createTeam(checkDuplicates: Bool, checkHomonym: Bool) { + private func _createTeam(checkDuplicates: Bool, checkHomonym: Bool) { if checkDuplicates && _isDuplicate() { confirmDuplicate = true return } - + let players = _currentSelection() if checkHomonym { @@ -382,31 +382,23 @@ struct AddTeamView: View { } let team = tournament.addTeam(players) - do { - try self.tournamentStore.teamRegistrations.addOrUpdate(instance: team) - } catch { - Logger.error(error) - } - do { - try self.tournamentStore.playerRegistrations.addOrUpdate(contentOfs: players) - } catch { - Logger.error(error) + self.tournamentStore?.teamRegistrations.addOrUpdate(instance: team) + self.tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: players) + + pasteString = nil + editableTextField = "" + createdPlayers.removeAll() + createdPlayerIds.removeAll() + + if team.players().count > 1 { + dismiss() + } else { + editedTeam = team + team.unsortedPlayers().forEach { player in + createdPlayers.insert(player) + createdPlayerIds.insert(player.id) + } } - - pasteString = nil - editableTextField = "" - createdPlayers.removeAll() - createdPlayerIds.removeAll() - - if team.players().count > 1 { - dismiss() - } else { - editedTeam = team - team.unsortedPlayers().forEach { player in - createdPlayers.insert(player) - createdPlayerIds.insert(player.id) - } - } } private func _updateTeam(checkDuplicates: Bool) { @@ -418,16 +410,8 @@ struct AddTeamView: View { let players = _currentSelection() editedTeam.updatePlayers(players, inTournamentCategory: tournament.tournamentCategory) - do { - try self.tournamentStore.teamRegistrations.addOrUpdate(instance: editedTeam) - } catch { - Logger.error(error) - } - do { - try self.tournamentStore.playerRegistrations.addOrUpdate(contentOfs: players) - } catch { - Logger.error(error) - } + self.tournamentStore?.teamRegistrations.addOrUpdate(instance: editedTeam) + self.tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: players) pasteString = nil editableTextField = "" @@ -614,7 +598,7 @@ struct AddTeamView: View { let _searchForHit = pasteString.hashValue if searchForHit != _searchForHit { - DispatchQueue.main.async { + DispatchQueue.main.async { searchForHit = _searchForHit hitsForSearch = [:] } diff --git a/PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift b/PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift index 97f605c..b83cc68 100644 --- a/PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift +++ b/PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift @@ -225,13 +225,15 @@ struct InscriptionInfoView: View { Section { DisclosureGroup { ForEach(playersWithoutValidLicense) { - EditablePlayerView(player: $0, editingOptions: [.licenceId]) - .environmentObject(tournament.tournamentStore) - .onChange(of: $0.licenceId) { - players = tournament.unsortedPlayers() - let isImported = players.anySatisfy({ $0.isImported() }) - playersWithoutValidLicense = tournament.playersWithoutValidLicense(in: players, isImported: isImported) - } + if let store = tournament.tournamentStore { + EditablePlayerView(player: $0, editingOptions: [.licenceId]) + .environmentObject(store) + .onChange(of: $0.licenceId) { + players = tournament.unsortedPlayers() + let isImported = players.anySatisfy({ $0.isImported() }) + playersWithoutValidLicense = tournament.playersWithoutValidLicense(in: players, isImported: isImported) + } + } } } label: { LabeledContent { diff --git a/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift b/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift index f2c65e8..635d458 100644 --- a/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift +++ b/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift @@ -262,14 +262,14 @@ struct TournamentGeneralSettingsView: View { } do { - try self.tournament.tournamentStore.matches.addOrUpdate(contentOfs: matches) + try self.tournament.tournamentStore?.matches.addOrUpdate(contentOfs: matches) } catch { Logger.error(error) } } do { - try self.tournament.tournamentStore.rounds.addOrUpdate(contentOfs: rounds) + try self.tournament.tournamentStore?.rounds.addOrUpdate(contentOfs: rounds) } catch { Logger.error(error) } diff --git a/PadelClub/Views/Tournament/Screen/Components/TournamentMatchFormatsSettingsView.swift b/PadelClub/Views/Tournament/Screen/Components/TournamentMatchFormatsSettingsView.swift index 2546787..06b57d8 100644 --- a/PadelClub/Views/Tournament/Screen/Components/TournamentMatchFormatsSettingsView.swift +++ b/PadelClub/Views/Tournament/Screen/Components/TournamentMatchFormatsSettingsView.swift @@ -17,7 +17,7 @@ struct TournamentMatchFormatsSettingsView: View { @State private var confirmUpdate: Bool = false @State private var updateCompleted: Bool = false - var tournamentStore: TournamentStore { + var tournamentStore: TournamentStore? { return self.tournament.tournamentStore } @@ -99,12 +99,12 @@ struct TournamentMatchFormatsSettingsView: View { } } do { - try tournamentStore.groupStages.addOrUpdate(contentOfs: groupStages) + try tournamentStore?.groupStages.addOrUpdate(contentOfs: groupStages) } catch { Logger.error(error) } do { - try tournamentStore.rounds.addOrUpdate(contentOfs: allRounds) + try tournamentStore?.rounds.addOrUpdate(contentOfs: allRounds) } catch { Logger.error(error) } diff --git a/PadelClub/Views/Tournament/Screen/Components/UpdateSourceRankDateView.swift b/PadelClub/Views/Tournament/Screen/Components/UpdateSourceRankDateView.swift index f85eb6e..0b8cfb0 100644 --- a/PadelClub/Views/Tournament/Screen/Components/UpdateSourceRankDateView.swift +++ b/PadelClub/Views/Tournament/Screen/Components/UpdateSourceRankDateView.swift @@ -17,7 +17,7 @@ struct UpdateSourceRankDateView: View { @State private var updatingRank = false var tournament: Tournament - var tournamentStore: TournamentStore { + var tournamentStore: TournamentStore? { return self.tournament.tournamentStore } @@ -42,6 +42,7 @@ struct UpdateSourceRankDateView: View { updatingRank = true Task { do { + try await tournament.updateRank(to: currentRankSourceDate, forceRefreshLockWeight: forceRefreshLockWeight, providedSources: nil) try dataStore.tournaments.addOrUpdate(instance: tournament) } catch { diff --git a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift index b7608f9..74f7667 100644 --- a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift +++ b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift @@ -54,7 +54,7 @@ struct InscriptionManagerView: View { @State private var refreshStatus: Bool? @State private var showLegendView: Bool = false - var tournamentStore: TournamentStore { + var tournamentStore: TournamentStore? { return self.tournament.tournamentStore } @@ -213,7 +213,7 @@ struct InscriptionManagerView: View { } do { - try tournamentStore.teamRegistrations.addOrUpdate(contentOfs: waitingList) + try tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: waitingList) try dataStore.tournaments.addOrUpdate(instance: tournament) } catch { Logger.error(error) @@ -559,7 +559,6 @@ struct InscriptionManagerView: View { refreshResult = nil refreshStatus = nil do { - await self.tournament.refreshTeamList() _setHash() @@ -1105,7 +1104,7 @@ struct InscriptionManagerView: View { Button(role: .destructive) { team.deleteTeamScores() do { - try tournamentStore.teamRegistrations.delete(instance: team) + try tournamentStore?.teamRegistrations.delete(instance: team) } catch { Logger.error(error) } diff --git a/PadelClub/Views/Tournament/Screen/TableStructureView.swift b/PadelClub/Views/Tournament/Screen/TableStructureView.swift index 58f7145..027c00f 100644 --- a/PadelClub/Views/Tournament/Screen/TableStructureView.swift +++ b/PadelClub/Views/Tournament/Screen/TableStructureView.swift @@ -437,7 +437,7 @@ struct TableStructureView: View { team.groupStage = nil } do { - try tournament.tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teams) + try tournament.tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: teams) } catch { Logger.error(error) } diff --git a/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift b/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift index 438ade9..c4c79ef 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift @@ -137,33 +137,37 @@ struct TournamentCashierView: View { } var body: some View { - VStack(spacing: 0) { - GenericDestinationPickerView(selectedDestination: $selectedDestination, destinations: allDestinations, nilDestinationIsValid: true) - switch selectedDestination { - case .none: - CashierSettingsView(tournament: tournament) - case .some(let selectedCall): - switch selectedCall { - case .summary: - CashierDetailView(tournament: tournament).id(selectedCall.id) - case .groupStage(let groupStage): - CashierView(tournament: tournament, teams: groupStage.teams()).id(selectedCall.id) - .searchable(text: $cashierViewModel.searchText, isPresented: $cashierViewModel.isSearching, placement: .navigationBarDrawer(displayMode: .always), prompt: Text("Chercher un joueur")) - case .bracket(let round): - CashierView(tournament: tournament, teams: round.seeds()).id(selectedCall.id) - .searchable(text: $cashierViewModel.searchText, isPresented: $cashierViewModel.isSearching, placement: .navigationBarDrawer(displayMode: .always), prompt: Text("Chercher un joueur")) - case .all(let tournament): - CashierView(tournament: tournament, teams: tournament.selectedSortedTeams()).id(selectedCall.id) - .searchable(text: $cashierViewModel.searchText, isPresented: $cashierViewModel.isSearching, placement: .navigationBarDrawer(displayMode: .always), prompt: Text("Chercher un joueur")) + if let store = self.tournament.tournamentStore { + VStack(spacing: 0) { + GenericDestinationPickerView(selectedDestination: $selectedDestination, destinations: allDestinations, nilDestinationIsValid: true) + switch selectedDestination { + case .none: + CashierSettingsView(tournament: tournament) + case .some(let selectedCall): + switch selectedCall { + case .summary: + CashierDetailView(tournament: tournament).id(selectedCall.id) + case .groupStage(let groupStage): + CashierView(tournament: tournament, teams: groupStage.teams()).id(selectedCall.id) + .searchable(text: $cashierViewModel.searchText, isPresented: $cashierViewModel.isSearching, placement: .navigationBarDrawer(displayMode: .always), prompt: Text("Chercher un joueur")) + case .bracket(let round): + CashierView(tournament: tournament, teams: round.seeds()).id(selectedCall.id) + .searchable(text: $cashierViewModel.searchText, isPresented: $cashierViewModel.isSearching, placement: .navigationBarDrawer(displayMode: .always), prompt: Text("Chercher un joueur")) + case .all(let tournament): + CashierView(tournament: tournament, teams: tournament.selectedSortedTeams()).id(selectedCall.id) + .searchable(text: $cashierViewModel.searchText, isPresented: $cashierViewModel.isSearching, placement: .navigationBarDrawer(displayMode: .always), prompt: Text("Chercher un joueur")) + } } } + .environment(tournament) + .environmentObject(store) + .environmentObject(cashierViewModel) + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(.visible, for: .navigationBar) + .navigationTitle(tournament.isFree() ? "Présence" : "Encaissement") + } else { + Text("no store") } - .environment(tournament) - .environmentObject(tournament.tournamentStore) - .environmentObject(cashierViewModel) - .navigationBarTitleDisplayMode(.inline) - .toolbarBackground(.visible, for: .navigationBar) - .navigationTitle(tournament.isFree() ? "Présence" : "Encaissement") } } diff --git a/PadelClub/Views/Tournament/Screen/TournamentRankView.swift b/PadelClub/Views/Tournament/Screen/TournamentRankView.swift index 17c0061..be0dff0 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentRankView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentRankView.swift @@ -17,7 +17,7 @@ struct TournamentRankView: View { @State private var calculating = false @State private var selectedTeam: TeamRegistration? - var tournamentStore: TournamentStore { + var tournamentStore: TournamentStore? { return self.tournament.tournamentStore } @@ -134,7 +134,7 @@ struct TournamentRankView: View { Button("Valider") { selectedTeam.pointsEarned = tournament.isAnimation() ? nil : tournament.tournamentLevel.points(for: selectedTeam.finalRanking! - 1, count: tournament.teamCount) do { - try self.tournamentStore.teamRegistrations.addOrUpdate(instance: selectedTeam) + try self.tournamentStore?.teamRegistrations.addOrUpdate(instance: selectedTeam) } catch { Logger.error(error) } @@ -183,7 +183,7 @@ struct TournamentRankView: View { team.finalRanking = key team.pointsEarned = tournament.isAnimation() ? nil : tournament.tournamentLevel.points(for: key - 1, count: tournament.teamCount) do { - try self.tournament.tournamentStore.teamRegistrations.addOrUpdate(instance: team) + try self.tournament.tournamentStore?.teamRegistrations.addOrUpdate(instance: team) } catch { Logger.error(error) } @@ -285,7 +285,7 @@ struct TournamentRankView: View { team.pointsEarned = tournament.isAnimation() ? nil : tournament.tournamentLevel.points(for: key - 1, count: tournament.teamCount) do { - try self.tournament.tournamentStore.teamRegistrations.addOrUpdate(instance: team) + try self.tournament.tournamentStore?.teamRegistrations.addOrUpdate(instance: team) } catch { Logger.error(error) } @@ -301,7 +301,7 @@ struct TournamentRankView: View { Button("Valider") { team.pointsEarned = tournament.isAnimation() ? nil : tournament.tournamentLevel.points(for: key - 1, count: tournament.teamCount) do { - try self.tournament.tournamentStore.teamRegistrations.addOrUpdate(instance: team) + try self.tournament.tournamentStore?.teamRegistrations.addOrUpdate(instance: team) } catch { Logger.error(error) } @@ -329,7 +329,7 @@ struct TournamentRankView: View { private func _save() { do { - try self.tournamentStore.teamRegistrations.addOrUpdate(contentOfs: tournament.unsortedTeams()) + try self.tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: tournament.unsortedTeams()) } catch { Logger.error(error) } diff --git a/PadelClub/Views/Tournament/Shared/TournamentCellView.swift b/PadelClub/Views/Tournament/Shared/TournamentCellView.swift index 67924a9..3b6f6d7 100644 --- a/PadelClub/Views/Tournament/Shared/TournamentCellView.swift +++ b/PadelClub/Views/Tournament/Shared/TournamentCellView.swift @@ -44,13 +44,13 @@ struct TournamentCellView: View { } var teamCount: Int? { - if let tournament = tournament as? Tournament { - let hasStarted = tournament.inscriptionClosed() || tournament.hasStarted() - let count = hasStarted ? tournament.selectedSortedTeams().count : tournament.unsortedTeamsWithoutWO().count - return count - } else { + guard let tournament = tournament as? Tournament, DataStore.shared.tournaments.contains(where: {$0.id == self.tournament.holderId }) else { // we make sure the tournament is in the DataStore otherwise we keep instantiating TournamentStore after disconnections return nil } + + let hasStarted = tournament.inscriptionClosed() || tournament.hasStarted() + let count = hasStarted ? tournament.selectedSortedTeams().count : tournament.unsortedTeamsWithoutWO().count + return count } fileprivate func _spacing() -> CGFloat { diff --git a/PadelClub/Views/Tournament/Subscription/Guard.swift b/PadelClub/Views/Tournament/Subscription/Guard.swift index 61a83c8..d701e9c 100644 --- a/PadelClub/Views/Tournament/Subscription/Guard.swift +++ b/PadelClub/Views/Tournament/Subscription/Guard.swift @@ -254,16 +254,16 @@ import LeStorage // return units.reduce(0) { $0 + $1.purchasedQuantity } } - func paymentForNewTournament() -> Tournament.TournamentPayment? { + func paymentForNewTournament() -> TournamentPayment? { switch self.currentPlan { case .monthlyUnlimited: - return Tournament.TournamentPayment.unlimited + return TournamentPayment.unlimited case .fivePerMonth: if let purchaseDate = self.currentBestPurchase?.purchaseDate { let tournaments = DataStore.shared.tournaments.filter { $0.creationDate > purchaseDate && $0.payment == .subscriptionUnit && $0.isCanceled == false } if tournaments.count < StoreItem.five { - return Tournament.TournamentPayment.subscriptionUnit + return TournamentPayment.subscriptionUnit } } return self._paymentWithoutSubscription() @@ -273,21 +273,21 @@ import LeStorage } - fileprivate func _paymentWithoutSubscription() -> Tournament.TournamentPayment? { + fileprivate func _paymentWithoutSubscription() -> TournamentPayment? { let freelyPayed: Int = DataStore.shared.tournaments.filter { $0.payment == .free && $0.isCanceled == false }.count if freelyPayed < self._freeTournaments { - return Tournament.TournamentPayment.free + return TournamentPayment.free } let tournamentCreditCount: Int = self._purchasedTournamentCount() let unitlyPayed = DataStore.shared.tournaments.filter { $0.payment == .unit && $0.isCanceled == false }.count if tournamentCreditCount > unitlyPayed { - return Tournament.TournamentPayment.unit + return TournamentPayment.unit } return nil } var remainingTournaments: Int { - let unitlyPayed = DataStore.shared.tournaments.filter { $0.payment == Tournament.TournamentPayment.unit }.count + let unitlyPayed = DataStore.shared.tournaments.filter { $0.payment == TournamentPayment.unit }.count let tournamentCreditCount = self._purchasedTournamentCount() // let notDeletedTournamentCount = DataStore.shared.tournaments.filter { $0.isDeleted == false }.count diff --git a/PadelClub/Views/Tournament/Subscription/Purchase.swift b/PadelClub/Views/Tournament/Subscription/Purchase.swift index 103b5e4..14cb860 100644 --- a/PadelClub/Views/Tournament/Subscription/Purchase.swift +++ b/PadelClub/Views/Tournament/Subscription/Purchase.swift @@ -8,39 +8,49 @@ import Foundation import LeStorage -class Purchase: ModelObject, Storable { - static func resourceName() -> String { return "purchases" } - static func tokenExemptedMethods() -> [HTTPMethod] { return [] } - static func filterByStoreIdentifier() -> Bool { return false } - static var relationshipNames: [String] = [] - - var id: UInt64 - var user: String - var purchaseDate: Date - var productId: String - var quantity: Int? - var revocationDate: Date? = nil - var expirationDate: Date? = nil +class Purchase: BasePurchase { + +// static func resourceName() -> String { return "purchases" } +// static func tokenExemptedMethods() -> [HTTPMethod] { return [] } +// static func filterByStoreIdentifier() -> Bool { return false } +// static var relationshipNames: [String] = [] +// +// var id: UInt64 +// var lastUpdate: Date +// var user: String +// var purchaseDate: Date +// var productId: String +// var quantity: Int? +// var revocationDate: Date? = nil +// var expirationDate: Date? = nil init(user: String, transactionId: UInt64, purchaseDate: Date, productId: String, quantity: Int? = nil, revocationDate: Date? = nil, expirationDate: Date? = nil) { - self.id = transactionId - self.user = user - self.purchaseDate = purchaseDate - self.productId = productId - self.quantity = quantity - self.revocationDate = revocationDate - self.expirationDate = expirationDate + super.init(id: transactionId, user: user, purchaseDate: purchaseDate, productId: productId, quantity: quantity, revocationDate: revocationDate, expirationDate: expirationDate) + +// self.id = transactionId +// self.lastUpdate = Date() +// self.user = user +// self.purchaseDate = purchaseDate +// self.productId = productId +// self.quantity = quantity +// self.revocationDate = revocationDate +// self.expirationDate = expirationDate } - enum CodingKeys: String, CodingKey, CaseIterable { - case id - case user - case purchaseDate - case productId - case quantity - case revocationDate - case expirationDate + required init(from decoder: Decoder) throws { + try super.init(from: decoder) } + +// enum CodingKeys: String, CodingKey, CaseIterable { +// case id +// case lastUpdate +// case user +// case purchaseDate +// case productId +// case quantity +// case revocationDate +// case expirationDate +// } func isValid() -> Bool { guard self.revocationDate == nil else { @@ -52,28 +62,30 @@ class Purchase: ModelObject, Storable { return expiration > Date() } - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(self.id, forKey: .id) - try container.encodeAndEncryptIfPresent(self.user.data(using: .utf8), forKey: .user) - try container.encode(self.purchaseDate, forKey: .purchaseDate) - try container.encode(self.productId, forKey: .productId) - try container.encode(self.quantity, forKey: .quantity) - try container.encode(self.revocationDate, forKey: .revocationDate) - try container.encode(self.expirationDate, forKey: .expirationDate) - } - - required init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - self.id = try container.decode(UInt64.self, forKey: .id) - self.user = try container.decodeEncrypted(key: .user) - self.purchaseDate = try container.decode(Date.self, forKey: .purchaseDate) - self.productId = try container.decode(String.self, forKey: .productId) - self.quantity = try container.decodeIfPresent(Int.self, forKey: .quantity) - self.revocationDate = try container.decodeIfPresent(Date.self, forKey: .revocationDate) - self.expirationDate = try container.decodeIfPresent(Date.self, forKey: .expirationDate) - } +// func encode(to encoder: Encoder) throws { +// var container = encoder.container(keyedBy: CodingKeys.self) +// +// try container.encode(self.id, forKey: .id) +// try container.encode(self.lastUpdate, forKey: .lastUpdate) +// try container.encodeAndEncryptIfPresent(self.user.data(using: .utf8), forKey: .user) +// try container.encode(self.purchaseDate, forKey: .purchaseDate) +// try container.encode(self.productId, forKey: .productId) +// try container.encode(self.quantity, forKey: .quantity) +// try container.encode(self.revocationDate, forKey: .revocationDate) +// try container.encode(self.expirationDate, forKey: .expirationDate) +// } +// +// required init(from decoder: any Decoder) throws { +// let container = try decoder.container(keyedBy: CodingKeys.self) +// +// self.id = try container.decode(UInt64.self, forKey: .id) +// self.lastUpdate = try container.decodeIfPresent(Date.self, forKey: .lastUpdate) ?? Date() +// self.user = try container.decodeEncrypted(key: .user) +// self.purchaseDate = try container.decode(Date.self, forKey: .purchaseDate) +// self.productId = try container.decode(String.self, forKey: .productId) +// self.quantity = try container.decodeIfPresent(Int.self, forKey: .quantity) +// self.revocationDate = try container.decodeIfPresent(Date.self, forKey: .revocationDate) +// self.expirationDate = try container.decodeIfPresent(Date.self, forKey: .expirationDate) +// } } diff --git a/PadelClub/Views/Tournament/Subscription/SubscriptionInfoView.swift b/PadelClub/Views/Tournament/Subscription/SubscriptionInfoView.swift index e5b86ca..2354c0e 100644 --- a/PadelClub/Views/Tournament/Subscription/SubscriptionInfoView.swift +++ b/PadelClub/Views/Tournament/Subscription/SubscriptionInfoView.swift @@ -10,7 +10,7 @@ import TipKit struct SubscriptionInfoView: View { - @State var payment: Tournament.TournamentPayment? = .free + @State var payment: TournamentPayment? = .free var body: some View { diff --git a/PadelClub/Views/Tournament/TournamentView.swift b/PadelClub/Views/Tournament/TournamentView.swift index e9f9b5d..9aa834c 100644 --- a/PadelClub/Views/Tournament/TournamentView.swift +++ b/PadelClub/Views/Tournament/TournamentView.swift @@ -162,6 +162,8 @@ struct TournamentView: View { } case .print: PrintSettingsView(tournament: tournament) + case .share: + ShareModelView(instance: tournament) case .restingTime: TeamRestingView() case .stateSettings: @@ -265,6 +267,10 @@ struct TournamentView: View { NavigationLink(value: Screen.print) { Label("Imprimer", systemImage: "printer") } + +// NavigationLink(value: Screen.share) { +// Label("Partager", systemImage: "square.and.arrow.up") +// } Divider() @@ -281,16 +287,12 @@ struct TournamentView: View { } .onAppear { TournamentRunningTip.isRunning = tournament.state() == .running - Logger.log("Payment = \(String(describing: self.tournament.payment)), canceled = \(self.tournament.isCanceled)") + Logger.log("Tournament Id = \(self.tournament.id), Payment = \(String(describing: self.tournament.payment))") } } private func _save() { - do { - try dataStore.tournaments.addOrUpdate(instance: tournament) - } catch { - Logger.error(error) - } + dataStore.tournaments.addOrUpdate(instance: tournament) } } diff --git a/PadelClub/Views/User/AccountView.swift b/PadelClub/Views/User/AccountView.swift index 340e162..539a2d4 100644 --- a/PadelClub/Views/User/AccountView.swift +++ b/PadelClub/Views/User/AccountView.swift @@ -9,8 +9,10 @@ import SwiftUI import LeStorage struct AccountView: View { - - var user: User + + @Environment(\.dismiss) private var dismiss + + var user: CustomUser var handler: () -> () var body: some View { @@ -28,13 +30,14 @@ struct AccountView: View { Section { RowButtonView("Déconnexion", role: .destructive) { DataStore.shared.disconnect() - handler() + dismiss() +// handler() } } Section { RowButtonView("Supprimer mon compte", role: .destructive, confirmationMessage: "Voulez-vous vraiment supprimer définitivement votre compte et ses données associées ?") { DataStore.shared.deleteAccount() - handler() +// handler() } } }.navigationTitle(user.username) diff --git a/PadelClub/Views/User/LoginView.swift b/PadelClub/Views/User/LoginView.swift index d66f8d7..57e36d4 100644 --- a/PadelClub/Views/User/LoginView.swift +++ b/PadelClub/Views/User/LoginView.swift @@ -74,7 +74,7 @@ struct LoginView: View { } } - var handler: (User) -> () + var handler: (CustomUser) -> () var body: some View { @@ -195,7 +195,7 @@ struct LoginView: View { self.isLoading = true do { let service = try StoreCenter.main.service() - let user: User = try await service.login( + let user: CustomUser = try await service.login( username: self.username, password: self.password) self.dataStore.user = user diff --git a/PadelClub/Views/User/ShareModelView.swift b/PadelClub/Views/User/ShareModelView.swift new file mode 100644 index 0000000..c281ccd --- /dev/null +++ b/PadelClub/Views/User/ShareModelView.swift @@ -0,0 +1,113 @@ +// +// UserSearchView.swift +// PadelClub +// +// Created by Laurent Morvillier on 03/12/2024. +// + +import Combine +import LeStorage +import SwiftUI + +class UserSearchViewModel: ObservableObject { + + @Published var searchText = "" + @Published var userNames: [ShortUser] = [] + + @Published var users: [String] = [] + @Published var availableUsers: [ShortUser] = [] + @Published var selectedUsers: [String] = [] + + init() { + Task { + do { + let service = try StoreCenter.main.service() + let userNames = try await service.getUserNames() + DispatchQueue.main.async { + self.userNames = userNames + self.availableUsers = self.users.compactMap { userId in + self.userNames.first(where: { $0.id == userId }) + } + } + } catch { + Logger.error(error) + } + } + } + + func userTapped(_ user: String) { + if let index = self.selectedUsers.firstIndex(of: user) { + self.selectedUsers.remove(at: index) + } else { + self.selectedUsers.append(user) + } + } + + func contains(_ user: String) -> Bool { + return self.selectedUsers.firstIndex(of: user) != nil + } +} + +struct ShareModelView : View { + @StateObject private var viewModel = UserSearchViewModel() + + let instance: T + + var body: some View { + NavigationView { + if !self.viewModel.availableUsers.isEmpty { + List { + ForEach(self.viewModel.availableUsers, id: \.id) { user in + let isSelected = viewModel.contains(user.id) + UserRow(user: user, isSelected: isSelected) + .contentShape(Rectangle()) + .onTapGesture { + self.viewModel.userTapped(user.id) + self._modifyAuthorizedUsersList() + } + } + } + .listStyle(PlainListStyle()) + .navigationTitle("Partage") + } else { + ContentUnavailableView("Si vous souhaitez partager votre tournoi avec d'autres utilisateurs, veuillez contacter notre support", image: "person.crop.circle.badge.xmark") + } + + }.onAppear { + self.viewModel.selectedUsers = StoreCenter.main.authorizedUsers(for: self.instance.stringId) + self.viewModel.users = DataStore.shared.user.agents + } + } + + fileprivate func _modifyAuthorizedUsersList() { + do { + try StoreCenter.main.setAuthorizedUsers(for: self.instance, users: self.viewModel.selectedUsers) + } catch { + Logger.error(error) + } + + } +} + +struct UserRow: View { + let user: ShortUser + let isSelected: Bool + + var body: some View { + HStack { + Text("\(user.firstName) \(user.lastName)") + Spacer() + if self.isSelected { + Image(systemName: "checkmark").foregroundStyle(.logoOrange) + } + } + .padding(.vertical, 4) + } +} + +// Preview provider +struct ShareModelView_Previews: PreviewProvider { + static var previews: some View { + ShareModelView(instance: Tournament.fake()) + } +} diff --git a/PadelClub/Views/User/UserCreationView.swift b/PadelClub/Views/User/UserCreationView.swift index a20ebb4..60a5a31 100644 --- a/PadelClub/Views/User/UserCreationView.swift +++ b/PadelClub/Views/User/UserCreationView.swift @@ -232,7 +232,7 @@ struct UserCreationFormView: View { country: country) let service: Services = try StoreCenter.main.service() - let _: User = try await service.createAccount(user: userCreationForm) + let _: CustomUser = try await service.createAccount(user: userCreationForm) DispatchQueue.main.async { self.isLoading = false diff --git a/PadelClubTests/ServerDataTests.swift b/PadelClubTests/ServerDataTests.swift index 15e26b6..0ef72d8 100644 --- a/PadelClubTests/ServerDataTests.swift +++ b/PadelClubTests/ServerDataTests.swift @@ -15,7 +15,8 @@ final class ServerDataTests: XCTestCase { let password: String = "MyPass1234--" override func setUpWithError() throws { - StoreCenter.main.synchronizationApiURL = "http://127.0.0.1:8000/roads/" +// StoreCenter.main.synchronizationApiURL = "http://127.0.0.1:8000/roads/" + StoreCenter.main.configureURLs(secureScheme: false, domain: "127.0.0.1:8000") Task { do { try await self.login() @@ -31,7 +32,7 @@ final class ServerDataTests: XCTestCase { func login() async throws { // print("LOGIN!") - let _: User = try await StoreCenter.main.service().login(username: self.username, password: self.password) + let _: CustomUser = try await StoreCenter.main.service().login(username: self.username, password: self.password) } func testClub() async throws { @@ -47,27 +48,36 @@ final class ServerDataTests: XCTestCase { club.phone = "061234567890" club.courtCount = 3 - let inserted_club: Club = try await StoreCenter.main.service().post(club) - assert(inserted_club.name == club.name) - assert(inserted_club.acronym == club.acronym) - assert(inserted_club.zipCode == club.zipCode) - assert(inserted_club.address == club.address) - assert(inserted_club.latitude == club.latitude) - assert(inserted_club.longitude == club.longitude) - assert(inserted_club.phone == club.phone) - assert(inserted_club.courtCount == club.courtCount) - assert(inserted_club.broadcastCode != nil) - assert(inserted_club.timezone != club.timezone) - - inserted_club.phone = "123456" - - let updated_club: Club = try await StoreCenter.main.service().put(inserted_club) - assert(updated_club.phone == inserted_club.phone) - + if let inserted_club: Club = try await StoreCenter.main.service().post(club) { + assert(inserted_club.lastUpdate == club.lastUpdate) + assert(inserted_club.name == club.name) + assert(inserted_club.acronym == club.acronym) + assert(inserted_club.zipCode == club.zipCode) + assert(inserted_club.address == club.address) + assert(inserted_club.latitude == club.latitude) + assert(inserted_club.longitude == club.longitude) + assert(inserted_club.phone == club.phone) + assert(inserted_club.courtCount == club.courtCount) + assert(inserted_club.broadcastCode != nil) + assert(inserted_club.timezone != club.timezone) + + inserted_club.phone = "123456" + inserted_club.lastUpdate = Date() + + if let updated_club: Club = try await StoreCenter.main.service().put(inserted_club) { + assert(updated_club.phone == inserted_club.phone) + } else { + XCTFail("missing data") + } + + } else { + XCTFail("missing data") + } } func testLogin() async throws { - let user: User = try await StoreCenter.main.service().login(username: self.username, password: self.password) + let service = try StoreCenter.main.service() + let user: CustomUser = try await service.login(username: self.username, password: self.password) assert(user.username == self.username) } @@ -85,11 +95,14 @@ final class ServerDataTests: XCTestCase { } let event = Event(creator: userId, club: clubId, name: "Roland Garros", tenupId: "abc") - let e = try await StoreCenter.main.service().post(event) - - assert(e.name == event.name) - assert(e.tenupId == event.tenupId) - + if let e = try await StoreCenter.main.service().post(event){ + + assert(e.name == event.name) + assert(e.lastUpdate == event.lastUpdate) + assert(e.tenupId == event.tenupId) + } else { + XCTFail("missing data") + } } func testTournament() async throws { @@ -101,53 +114,59 @@ final class ServerDataTests: XCTestCase { } let tournament = Tournament(event: eventId, name: "RG Homme", startDate: Date(), endDate: nil, creationDate: Date(), isPrivate: false, groupStageFormat: MatchFormat.megaTie, roundFormat: MatchFormat.nineGames, loserRoundFormat: MatchFormat.nineGamesDecisivePoint, groupStageSortMode: GroupStageOrderingMode.snake, groupStageCount: 2, rankSourceDate: Date(), dayDuration: 5, teamCount: 3, teamSorting: TeamSortingType.rank, federalCategory: TournamentCategory.mix, federalLevelCategory: TournamentLevel.p1000, federalAgeCategory: FederalTournamentAge.a45, closedRegistrationDate: Date(), groupStageAdditionalQualified: 4, courtCount: 9, prioritizeClubMembers: true, qualifiedPerGroupStage: 1, teamsPerGroupStage: 2, entryFee: 30.0, additionalEstimationDuration: 5, isDeleted: true, publishTeams: true, publishSummons: true, publishGroupStages: true, publishBrackets: true, shouldVerifyBracket: true, shouldVerifyGroupStage: true, hideTeamsWeight: true, publishTournament: true, hidePointsEarned: true, publishRankings: true, loserBracketMode: .manual, initialSeedRound: 8, initialSeedCount: 4, accountIsRequired: false, licenseIsRequired: false, minimumPlayerPerTeam: 3, maximumPlayerPerTeam: 5, information: "Super") - let t = try await StoreCenter.main.service().post(tournament) - assert(t.event == tournament.event) - assert(t.name == tournament.name) - assert(t.startDate.formatted() == tournament.startDate.formatted()) - assert(t.endDate?.formatted() == tournament.endDate?.formatted()) - assert(t.creationDate.formatted() == tournament.creationDate.formatted()) - assert(t.isPrivate == tournament.isPrivate) - assert(t.groupStageFormat == tournament.groupStageFormat) - assert(t.roundFormat == tournament.roundFormat) - assert(t.loserRoundFormat == tournament.loserRoundFormat) - assert(t.groupStageSortMode == tournament.groupStageSortMode) - assert(t.groupStageCount == tournament.groupStageCount) - assert(t.rankSourceDate?.formatted() == tournament.rankSourceDate?.formatted()) - assert(t.dayDuration == tournament.dayDuration) - assert(t.teamCount == tournament.teamCount) - assert(t.teamSorting == tournament.teamSorting) - assert(t.federalCategory == tournament.federalCategory) - assert(t.federalLevelCategory == tournament.federalLevelCategory) - assert(t.federalAgeCategory == tournament.federalAgeCategory) - assert(t.closedRegistrationDate?.formatted() == tournament.closedRegistrationDate?.formatted()) - assert(t.groupStageAdditionalQualified == tournament.groupStageAdditionalQualified) - assert(t.courtCount == tournament.courtCount) - assert(t.prioritizeClubMembers == tournament.prioritizeClubMembers) - assert(t.qualifiedPerGroupStage == tournament.qualifiedPerGroupStage) - assert(t.teamsPerGroupStage == tournament.teamsPerGroupStage) - assert(t.entryFee == tournament.entryFee) - assert(t.additionalEstimationDuration == tournament.additionalEstimationDuration) - assert(t.isDeleted == tournament.isDeleted) - assert(t.publishTeams == tournament.publishTeams) - assert(t.publishSummons == tournament.publishSummons) - assert(t.publishGroupStages == tournament.publishGroupStages) - assert(t.publishBrackets == tournament.publishBrackets) - assert(t.shouldVerifyBracket == tournament.shouldVerifyBracket) - assert(t.shouldVerifyGroupStage == tournament.shouldVerifyGroupStage) - assert(t.hideTeamsWeight == tournament.hideTeamsWeight) - assert(t.publishTournament == tournament.publishTournament) - assert(t.hidePointsEarned == tournament.hidePointsEarned) - assert(t.publishRankings == tournament.publishRankings) - assert(t.loserBracketMode == tournament.loserBracketMode) - assert(t.initialSeedCount == tournament.initialSeedCount) - assert(t.initialSeedRound == tournament.initialSeedRound) - assert(t.accountIsRequired == tournament.accountIsRequired) - assert(t.licenseIsRequired == tournament.licenseIsRequired) - assert(t.minimumPlayerPerTeam == tournament.minimumPlayerPerTeam) - assert(t.maximumPlayerPerTeam == tournament.maximumPlayerPerTeam) - assert(t.information == tournament.information) + if let t = try await StoreCenter.main.service().post(tournament) { + + assert(t.lastUpdate.formatted() == tournament.lastUpdate.formatted()) + assert(t.event == tournament.event) + assert(t.name == tournament.name) + assert(t.startDate.formatted() == tournament.startDate.formatted()) + assert(t.endDate?.formatted() == tournament.endDate?.formatted()) + assert(t.creationDate.formatted() == tournament.creationDate.formatted()) + assert(t.isPrivate == tournament.isPrivate) + assert(t.groupStageFormat == tournament.groupStageFormat) + assert(t.roundFormat == tournament.roundFormat) + assert(t.loserRoundFormat == tournament.loserRoundFormat) + assert(t.groupStageSortMode == tournament.groupStageSortMode) + assert(t.groupStageCount == tournament.groupStageCount) + assert(t.rankSourceDate?.formatted() == tournament.rankSourceDate?.formatted()) + assert(t.dayDuration == tournament.dayDuration) + assert(t.teamCount == tournament.teamCount) + assert(t.teamSorting == tournament.teamSorting) + assert(t.federalCategory == tournament.federalCategory) + assert(t.federalLevelCategory == tournament.federalLevelCategory) + assert(t.federalAgeCategory == tournament.federalAgeCategory) + assert(t.closedRegistrationDate?.formatted() == tournament.closedRegistrationDate?.formatted()) + assert(t.groupStageAdditionalQualified == tournament.groupStageAdditionalQualified) + assert(t.courtCount == tournament.courtCount) + assert(t.prioritizeClubMembers == tournament.prioritizeClubMembers) + assert(t.qualifiedPerGroupStage == tournament.qualifiedPerGroupStage) + assert(t.teamsPerGroupStage == tournament.teamsPerGroupStage) + assert(t.entryFee == tournament.entryFee) + assert(t.additionalEstimationDuration == tournament.additionalEstimationDuration) + assert(t.isDeleted == tournament.isDeleted) + assert(t.publishTeams == tournament.publishTeams) + assert(t.publishSummons == tournament.publishSummons) + assert(t.publishGroupStages == tournament.publishGroupStages) + assert(t.publishBrackets == tournament.publishBrackets) + assert(t.shouldVerifyBracket == tournament.shouldVerifyBracket) + assert(t.shouldVerifyGroupStage == tournament.shouldVerifyGroupStage) + assert(t.hideTeamsWeight == tournament.hideTeamsWeight) + assert(t.publishTournament == tournament.publishTournament) + assert(t.hidePointsEarned == tournament.hidePointsEarned) + assert(t.publishRankings == tournament.publishRankings) + assert(t.loserBracketMode == tournament.loserBracketMode) + assert(t.initialSeedCount == tournament.initialSeedCount) + assert(t.initialSeedRound == tournament.initialSeedRound) + assert(t.accountIsRequired == tournament.accountIsRequired) + assert(t.licenseIsRequired == tournament.licenseIsRequired) + assert(t.minimumPlayerPerTeam == tournament.minimumPlayerPerTeam) + assert(t.maximumPlayerPerTeam == tournament.maximumPlayerPerTeam) + assert(t.information == tournament.information) + + } else { + XCTFail("missing data") + } } func testGroupStage() async throws { @@ -158,17 +177,22 @@ final class ServerDataTests: XCTestCase { return } - let groupStage = GroupStage(tournament: tournamentId, index: 2, size: 3, matchFormat: MatchFormat.nineGames, startDate: Date(), name: "Yeah!", step: 1) - let gs: GroupStage = try await StoreCenter.main.service().post(groupStage) - - assert(gs.tournament == groupStage.tournament) - assert(gs.name == groupStage.name) - assert(gs.index == groupStage.index) - assert(gs.size == groupStage.size) - assert(gs.matchFormat == groupStage.matchFormat) - assert(gs.startDate != nil) - assert(gs.step == groupStage.step) - + let groupStage = GroupStage(tournament: tournamentId, index: 2, size: 3, format: MatchFormat.nineGames, startDate: Date(), name: "Yeah!", step: 1) + groupStage.storeId = "123" + if let gs: GroupStage = try await StoreCenter.main.service().post(groupStage) { + + assert(gs.tournament == groupStage.tournament) + assert(gs.storeId == groupStage.storeId) + assert(gs.lastUpdate == groupStage.lastUpdate) + assert(gs.name == groupStage.name) + assert(gs.index == groupStage.index) + assert(gs.size == groupStage.size) + assert(gs.matchFormat == groupStage.matchFormat) + assert(gs.startDate != nil) + assert(gs.step == groupStage.step) + } else { + XCTFail("missing data") + } } @@ -183,16 +207,21 @@ final class ServerDataTests: XCTestCase { let parentRoundId = rounds.first?.id let round = Round(tournament: tournamentId, index: 1, parent: parentRoundId, matchFormat: MatchFormat.nineGames, startDate: Date(), groupStageLoserBracket: false, loserBracketMode: .manual) - let r: Round = try await StoreCenter.main.service().post(round) - - assert(r.tournament == round.tournament) - assert(r.index == round.index) - assert(r.parent == round.parent) - assert(r.matchFormat == round.matchFormat) - assert(r.startDate != nil) - assert(r.groupStageLoserBracket == round.groupStageLoserBracket) - assert(r.loserBracketMode == round.loserBracketMode) - + round.storeId = "abc" + if let r: Round = try await StoreCenter.main.service().post(round) { + + assert(r.storeId == round.storeId) + assert(r.tournament == round.tournament) + assert(r.lastUpdate == round.lastUpdate) + assert(r.index == round.index) + assert(r.parent == round.parent) + assert(r.matchFormat == round.matchFormat) + assert(r.startDate != nil) + assert(r.groupStageLoserBracket == round.groupStageLoserBracket) + assert(r.loserBracketMode == round.loserBracketMode) + } else { + XCTFail("missing data") + } } func testTeamRegistration() async throws { @@ -208,30 +237,36 @@ final class ServerDataTests: XCTestCase { return } - let teamRegistration = TeamRegistration(tournament: tournamentId, groupStage: groupStageId, registrationDate: Date(), callDate: Date(), bracketPosition: 1, groupStagePosition: 2, comment: "comment", source: "source", sourceValue: "source V", logo: "logo", name: "Stax", walkOut: true, wildCardBracket: true, wildCardGroupStage: true, weight: 1, lockedWeight: 11, confirmationDate: Date(), qualified: true, finalRanking: 4, pointsEarned: 200) + let teamRegistration = TeamRegistration(tournament: tournamentId, groupStage: groupStageId, registrationDate: Date(), callDate: Date(), bracketPosition: 1, groupStagePosition: 2, comment: "comment", source: "source", sourceValue: "source V", logo: "logo", name: "Stax", walkOut: true, wildCardBracket: true, wildCardGroupStage: true, weight: 1, lockedWeight: 11, confirmationDate: Date(), qualified: true) + teamRegistration.storeId = "123" - let tr: TeamRegistration = try await StoreCenter.main.service().post(teamRegistration) - - assert(tr.tournament == teamRegistration.tournament) - assert(tr.groupStage == teamRegistration.groupStage) - assert(tr.registrationDate != nil) - assert(tr.callDate != nil) - assert(tr.bracketPosition == teamRegistration.bracketPosition) - assert(tr.groupStagePosition == teamRegistration.groupStagePosition) - assert(tr.comment == teamRegistration.comment) - assert(tr.source == teamRegistration.source) - assert(tr.sourceValue == teamRegistration.sourceValue) - assert(tr.logo == teamRegistration.logo) - assert(tr.name == teamRegistration.name) - assert(tr.walkOut == teamRegistration.walkOut) - assert(tr.wildCardBracket == teamRegistration.wildCardBracket) - assert(tr.wildCardGroupStage == teamRegistration.wildCardGroupStage) - assert(tr.weight == teamRegistration.weight) - assert(tr.lockedWeight == teamRegistration.lockedWeight) - assert(tr.confirmationDate?.formatted() == teamRegistration.confirmationDate?.formatted()) - assert(tr.qualified == teamRegistration.qualified) - assert(tr.finalRanking == teamRegistration.finalRanking) - assert(tr.pointsEarned == teamRegistration.pointsEarned) + if let tr: TeamRegistration = try await StoreCenter.main.service().post(teamRegistration) { + + assert(tr.storeId == teamRegistration.storeId) + assert(tr.tournament == teamRegistration.tournament) + assert(tr.lastUpdate == teamRegistration.lastUpdate) + assert(tr.groupStage == teamRegistration.groupStage) + assert(tr.registrationDate != nil) + assert(tr.callDate != nil) + assert(tr.bracketPosition == teamRegistration.bracketPosition) + assert(tr.groupStagePosition == teamRegistration.groupStagePosition) + assert(tr.comment == teamRegistration.comment) + assert(tr.source == teamRegistration.source) + assert(tr.sourceValue == teamRegistration.sourceValue) + assert(tr.logo == teamRegistration.logo) + assert(tr.name == teamRegistration.name) + assert(tr.walkOut == teamRegistration.walkOut) + assert(tr.wildCardBracket == teamRegistration.wildCardBracket) + assert(tr.wildCardGroupStage == teamRegistration.wildCardGroupStage) + assert(tr.weight == teamRegistration.weight) + assert(tr.lockedWeight == teamRegistration.lockedWeight) + assert(tr.confirmationDate?.formatted() == teamRegistration.confirmationDate?.formatted()) + assert(tr.qualified == teamRegistration.qualified) + assert(tr.finalRanking == teamRegistration.finalRanking) + assert(tr.pointsEarned == teamRegistration.pointsEarned) + } else { + XCTFail("missing data") + } } func testPlayerRegistration() async throws { @@ -242,30 +277,36 @@ final class ServerDataTests: XCTestCase { return } - let playerRegistration = PlayerRegistration(teamRegistration: teamRegistrationId, firstName: "juan", lastName: "lebron", licenceId: "123", rank: 11, paymentType: PlayerRegistration.PlayerPaymentType.cash, sex: PlayerRegistration.PlayerSexType.male, tournamentPlayed: 2, points: 33, clubName: "le club", ligueName: "la league", assimilation: "ass", phoneNumber: "123123", email: "email@email.com", birthdate: nil, computedRank: 222, source: PlayerRegistration.PlayerDataSource.frenchFederation, hasArrived: true, coach: true, captain: true, registeredOnline: true) - let pr: PlayerRegistration = try await StoreCenter.main.service().post(playerRegistration) - - assert(pr.teamRegistration == playerRegistration.teamRegistration) - assert(pr.firstName == playerRegistration.firstName) - assert(pr.lastName == playerRegistration.lastName) - assert(pr.licenceId == playerRegistration.licenceId) - assert(pr.rank == playerRegistration.rank) - assert(pr.paymentType == playerRegistration.paymentType) - assert(pr.sex == playerRegistration.sex) - assert(pr.tournamentPlayed == playerRegistration.tournamentPlayed) - assert(pr.points == playerRegistration.points) - assert(pr.clubName == playerRegistration.clubName) - assert(pr.ligueName == playerRegistration.ligueName) - assert(pr.assimilation == playerRegistration.assimilation) - assert(pr.phoneNumber == playerRegistration.phoneNumber) - assert(pr.birthdate == playerRegistration.birthdate) - assert(pr.computedRank == playerRegistration.computedRank) - assert(pr.source == playerRegistration.source) - assert(pr.hasArrived == playerRegistration.hasArrived) - assert(pr.captain == playerRegistration.captain) - assert(pr.coach == playerRegistration.coach) - assert(pr.registeredOnline == playerRegistration.registeredOnline) - + let playerRegistration = PlayerRegistration(teamRegistration: teamRegistrationId, firstName: "juan", lastName: "lebron", licenceId: "123", rank: 11, paymentType: PlayerPaymentType.cash, sex: PlayerSexType.male, tournamentPlayed: 2, points: 33, clubName: "le club", ligueName: "la league", assimilation: "ass", phoneNumber: "123123", email: "email@email.com", birthdate: nil, computedRank: 222, source: PlayerRegistration.PlayerDataSource.frenchFederation, hasArrived: true) + playerRegistration.storeId = "123" + + if let pr: PlayerRegistration = try await StoreCenter.main.service().post(playerRegistration) { + + assert(pr.storeId == playerRegistration.storeId) + assert(pr.lastName == playerRegistration.lastName) + assert(pr.teamRegistration == playerRegistration.teamRegistration) + assert(pr.firstName == playerRegistration.firstName) + assert(pr.lastName == playerRegistration.lastName) + assert(pr.licenceId == playerRegistration.licenceId) + assert(pr.rank == playerRegistration.rank) + assert(pr.paymentType == playerRegistration.paymentType) + assert(pr.sex == playerRegistration.sex) + assert(pr.tournamentPlayed == playerRegistration.tournamentPlayed) + assert(pr.points == playerRegistration.points) + assert(pr.clubName == playerRegistration.clubName) + assert(pr.ligueName == playerRegistration.ligueName) + assert(pr.assimilation == playerRegistration.assimilation) + assert(pr.phoneNumber == playerRegistration.phoneNumber) + assert(pr.birthdate == playerRegistration.birthdate) + assert(pr.computedRank == playerRegistration.computedRank) + assert(pr.source == playerRegistration.source) + assert(pr.hasArrived == playerRegistration.hasArrived) + assert(pr.captain == playerRegistration.captain) + assert(pr.coach == playerRegistration.coach) + assert(pr.registeredOnline == playerRegistration.registeredOnline) + } else { + XCTFail("missing data") + } } func testMatch() async throws { @@ -278,22 +319,27 @@ final class ServerDataTests: XCTestCase { let rounds: [Round] = try await StoreCenter.main.service().get() let parentRoundId = rounds.first?.id - let match: Match = Match(round: parentRoundId, groupStage: nil, startDate: Date(), endDate: Date(), index: 2, matchFormat: MatchFormat.twoSets, servingTeamId: teamRegistrationId, winningTeamId: teamRegistrationId, losingTeamId: teamRegistrationId, disabled: true, courtIndex: 1, confirmed: true) - let m: Match = try await StoreCenter.main.service().post(match) - - assert(m.round == match.round) - assert(m.groupStage == match.groupStage) - assert(m.startDate != nil) - assert(m.endDate != nil) - assert(m.index == match.index) - assert(m.matchFormat == match.matchFormat) - assert(m.servingTeamId == match.servingTeamId) - assert(m.winningTeamId == match.winningTeamId) - assert(m.losingTeamId == match.losingTeamId) - assert(m.disabled == match.disabled) - assert(m.courtIndex == match.courtIndex) - assert(m.confirmed == match.confirmed) - + let match: Match = Match(round: parentRoundId, groupStage: nil, startDate: Date(), endDate: Date(), index: 2, format: MatchFormat.twoSets, servingTeamId: teamRegistrationId, winningTeamId: teamRegistrationId, losingTeamId: teamRegistrationId, disabled: true, courtIndex: 1, confirmed: true) + match.storeId = "123" + if let m: Match = try await StoreCenter.main.service().post(match) { + + assert(m.storeId == match.storeId) + assert(m.lastUpdate == match.lastUpdate) + assert(m.round == match.round) + assert(m.groupStage == match.groupStage) + assert(m.startDate != nil) + assert(m.endDate != nil) + assert(m.index == match.index) + assert(m.matchFormat == match.matchFormat) + assert(m.servingTeamId == match.servingTeamId) + assert(m.winningTeamId == match.winningTeamId) + assert(m.losingTeamId == match.losingTeamId) + assert(m.disabled == match.disabled) + assert(m.courtIndex == match.courtIndex) + assert(m.confirmed == match.confirmed) + } else { + XCTFail("missing data") + } } func testTeamScore() async throws { @@ -309,14 +355,19 @@ final class ServerDataTests: XCTestCase { return } let teamScore = TeamScore(match: matchId, teamRegistration: teamRegistrationId, score: "6/6", walkOut: 1, luckyLoser: 1) - let ts: TeamScore = try await StoreCenter.main.service().post(teamScore) - - assert(ts.match == teamScore.match) - assert(ts.teamRegistration == teamScore.teamRegistration) - assert(ts.score == teamScore.score) - assert(ts.walkOut == teamScore.walkOut) - assert(ts.luckyLoser == teamScore.luckyLoser) - + teamScore.storeId = "!23" + if let ts: TeamScore = try await StoreCenter.main.service().post(teamScore) { + + assert(ts.storeId == teamScore.storeId) + assert(ts.lastUpdate == teamScore.lastUpdate) + assert(ts.match == teamScore.match) + assert(ts.teamRegistration == teamScore.teamRegistration) + assert(ts.score == teamScore.score) + assert(ts.walkOut == teamScore.walkOut) + assert(ts.luckyLoser == teamScore.luckyLoser) + } else { + XCTFail("missing data") + } } func testCourt() async throws { @@ -328,14 +379,17 @@ final class ServerDataTests: XCTestCase { } let court = Court(index: 1, club: clubId, name: "Philippe Chatrier", exitAllowed: true, indoor: true) - let c: Court = try await StoreCenter.main.service().post(court) - - assert(c.club == court.club) - assert(c.name == court.name) - assert(c.index == court.index) - assert(c.exitAllowed == court.exitAllowed) - assert(c.indoor == court.indoor) - + if let c: Court = try await StoreCenter.main.service().post(court) { + + assert(c.lastUpdate == court.lastUpdate) + assert(c.club == court.club) + assert(c.name == court.name) + assert(c.index == court.index) + assert(c.exitAllowed == court.exitAllowed) + assert(c.indoor == court.indoor) + } else { + XCTFail("missing data") + } } func testDateInterval() async throws { @@ -347,13 +401,16 @@ final class ServerDataTests: XCTestCase { } let dateInterval = DateInterval(event: eventId, courtIndex: 1, startDate: Date(), endDate: Date()) - let di: PadelClub.DateInterval = try await StoreCenter.main.service().post(dateInterval) - - assert(di.event == dateInterval.event) - assert(di.courtIndex == dateInterval.courtIndex) - assert(di.startDate.formatted() == dateInterval.startDate.formatted()) - assert(di.endDate.formatted() == dateInterval.endDate.formatted()) - + if let di: PadelClub.DateInterval = try await StoreCenter.main.service().post(dateInterval) { + + assert(di.lastUpdate == dateInterval.lastUpdate) + assert(di.event == dateInterval.event) + assert(di.courtIndex == dateInterval.courtIndex) + assert(di.startDate.formatted() == dateInterval.startDate.formatted()) + assert(di.endDate.formatted() == dateInterval.endDate.formatted()) + } else { + XCTFail("missing data") + } } func testPurchase() async throws { @@ -367,16 +424,20 @@ final class ServerDataTests: XCTestCase { let quantity = Int.random(in: 0...10) let purchase: Purchase = Purchase(user: userId, transactionId: transactionId, purchaseDate: Date(), productId: "app.padelclub.productId", quantity: quantity, revocationDate: Date(), expirationDate: Date()) - let p: Purchase = try await StoreCenter.main.service().post(purchase) - - assert(p.id == purchase.id) - assert(p.user == purchase.user) - assert(p.productId == purchase.productId) - assert(p.purchaseDate.formatted() == purchase.purchaseDate.formatted()) - assert(p.quantity == purchase.quantity) - assert(p.revocationDate?.formatted() == purchase.revocationDate?.formatted()) - assert(p.expirationDate?.formatted() == purchase.expirationDate?.formatted()) + if let p: Purchase = try await StoreCenter.main.service().post(purchase) { + + assert(p.id == purchase.id) + assert(p.lastUpdate == purchase.lastUpdate) + assert(p.user == purchase.user) + assert(p.productId == purchase.productId) + assert(p.purchaseDate.formatted() == purchase.purchaseDate.formatted()) + assert(p.quantity == purchase.quantity) + assert(p.revocationDate?.formatted() == purchase.revocationDate?.formatted()) + assert(p.expirationDate?.formatted() == purchase.expirationDate?.formatted()) + } else { + XCTFail("missing data") + } } func testDrawLog() async throws { @@ -388,14 +449,19 @@ final class ServerDataTests: XCTestCase { } let drawLog = DrawLog(tournament: tournamentId, drawSeed: 1, drawMatchIndex: 1, drawTeamPosition: .two, drawType: .court) - let d: DrawLog = try await StoreCenter.main.service().post(drawLog) - - assert(d.tournament == drawLog.tournament) - assert(d.drawDate.formatted() == drawLog.drawDate.formatted()) - assert(d.drawSeed == drawLog.drawSeed) - assert(d.drawTeamPosition == drawLog.drawTeamPosition) - assert(d.drawMatchIndex == drawLog.drawMatchIndex) - assert(d.drawType == drawLog.drawType) + drawLog.storeId = "!23" + + if let d: DrawLog = try await StoreCenter.main.service().post(drawLog) { + + assert(d.tournament == drawLog.tournament) + assert(d.drawDate.formatted() == drawLog.drawDate.formatted()) + assert(d.drawSeed == drawLog.drawSeed) + assert(d.drawTeamPosition == drawLog.drawTeamPosition) + assert(d.drawMatchIndex == drawLog.drawMatchIndex) + assert(d.drawType == drawLog.drawType) + } else { + XCTFail("missing data") + } } } diff --git a/PadelClubTests/SynchronizationTests.swift b/PadelClubTests/SynchronizationTests.swift new file mode 100644 index 0000000..8d35757 --- /dev/null +++ b/PadelClubTests/SynchronizationTests.swift @@ -0,0 +1,33 @@ +// +// SynchronizationTests.swift +// PadelClubTests +// +// Created by Laurent Morvillier on 09/10/2024. +// + +import Testing +import LeStorage +@testable import PadelClub + +struct SynchronizationTests { + + let username: String = "laurent" + let password: String = "StaxKikoo12" + + init() { + StoreCenter.main.configureURLs(secureScheme: false, domain: "127.0.0.1:8000") + } + + @Test func synchronizationTest() async throws { + + _ = try await self.login() + try await StoreCenter.main.synchronizeLastUpdates() + + } + + func login() async throws -> CustomUser { + let user: CustomUser = try await StoreCenter.main.service().login(username: self.username, password: self.password) + return user + } + +} diff --git a/PadelClubTests/TokenExemptionTests.swift b/PadelClubTests/TokenExemptionTests.swift index 923be80..e257ddf 100644 --- a/PadelClubTests/TokenExemptionTests.swift +++ b/PadelClubTests/TokenExemptionTests.swift @@ -16,7 +16,7 @@ final class TokenExemptionTests: XCTestCase { let password: String = "MyPass1234--" override func setUpWithError() throws { - StoreCenter.main.synchronizationApiURL = "http://127.0.0.1:8000/roads/" + StoreCenter.main.configureURLs(secureScheme: false, domain: "127.0.0.1:8000") StoreCenter.main.disconnect() } @@ -31,8 +31,11 @@ final class TokenExemptionTests: XCTestCase { let club: Club = Club(name: "mon club 2", acronym: "MC", phone: "132", code: "456", address: "l'adresse", city: "la ville", zipCode: "13131", latitude: 13.11111, longitude: 1.121212) - let c = try await StoreCenter.main.service().post(club) - assert(c.id == club.id) + if let c = try await StoreCenter.main.service().post(club) { + assert(c.id == club.id) + } else { + XCTFail("missing data") + } do { _ = try await StoreCenter.main.service().put(club) @@ -44,13 +47,15 @@ final class TokenExemptionTests: XCTestCase { let _ = try await self.login() club.creator = user.id - let uc = try await StoreCenter.main.service().put(club) - assert(uc.creator == user.id) - + if let uc = try await StoreCenter.main.service().put(club) { + assert(uc.creator == user.id) + } else { + XCTFail("missing data") + } } - func login() async throws -> User { - let user: User = try await StoreCenter.main.service().login(username: self.username, password: self.password) + func login() async throws -> CustomUser { + let user: CustomUser = try await StoreCenter.main.service().login(username: self.username, password: self.password) return user } diff --git a/PadelClubTests/UserDataTests.swift b/PadelClubTests/UserDataTests.swift index c7f7fd6..c5a9f63 100644 --- a/PadelClubTests/UserDataTests.swift +++ b/PadelClubTests/UserDataTests.swift @@ -15,7 +15,7 @@ final class UserDataTests: XCTestCase { let password: String = "MyPass1234--" override func setUpWithError() throws { - StoreCenter.main.synchronizationApiURL = "http://127.0.0.1:8000/roads/" + StoreCenter.main.configureURLs(secureScheme: false, domain: "127.0.0.1:8000") } override func tearDownWithError() throws { @@ -24,8 +24,12 @@ final class UserDataTests: XCTestCase { func testUserCreation() async throws { - let userCreationForm = UserCreationForm(user: User.placeHolder(), username: self.username, password: self.password, firstName: "jean", lastName: "coco", email: "UserDataTests@lolomo.net", phone: "0123", country: "France") - let user: User = try await StoreCenter.main.service().createAccount(user: userCreationForm) +//<<<<<<< HEAD +// let userCreationForm = UserCreationForm(user: CustomUser.placeHolder(), username: self.username, password: self.password, firstName: "jean", lastName: "coco", email: "test@lolomo.com", phone: "0123", country: "France") +// let user: CustomUser = try await StoreCenter.main.service().createAccount(user: userCreationForm) +//======= + let userCreationForm = UserCreationForm(user: CustomUser.placeHolder(), username: self.username, password: self.password, firstName: "jean", lastName: "coco", email: "UserDataTests@lolomo.net", phone: "0123", country: "France") + let user: CustomUser = try await StoreCenter.main.service().createAccount(user: userCreationForm) assert(user.username == userCreationForm.username) assert(user.firstName == userCreationForm.firstName) @@ -36,8 +40,8 @@ final class UserDataTests: XCTestCase { } - func login() async throws -> User { - let user: User = try await StoreCenter.main.service().login(username: self.username, password: self.password) + func login() async throws -> CustomUser { + let user: CustomUser = try await StoreCenter.main.service().login(username: self.username, password: self.password) return user } @@ -61,20 +65,21 @@ final class UserDataTests: XCTestCase { user.loserBracketMatchFormatPreference = MatchFormat.twoSetsOfFourGames user.loserBracketMode = .manual - let uu = try await StoreCenter.main.service().put(user) - - assert(uu.summonsMessageBody == user.summonsMessageBody) - assert(uu.summonsMessageSignature == user.summonsMessageSignature) - assert(uu.summonsAvailablePaymentMethods == user.summonsAvailablePaymentMethods) - assert(uu.summonsDisplayFormat == user.summonsDisplayFormat) - assert(uu.summonsDisplayEntryFee == user.summonsDisplayEntryFee) - assert(uu.summonsUseFullCustomMessage == user.summonsUseFullCustomMessage) - assert(uu.matchFormatsDefaultDuration == user.matchFormatsDefaultDuration) - assert(uu.bracketMatchFormatPreference == user.bracketMatchFormatPreference) - assert(uu.groupStageMatchFormatPreference == user.groupStageMatchFormatPreference) - assert(uu.loserBracketMatchFormatPreference == user.loserBracketMatchFormatPreference) - assert(uu.loserBracketMode == user.loserBracketMode) - + if let uu = try await StoreCenter.main.service().put(user) { + assert(uu.summonsMessageBody == user.summonsMessageBody) + assert(uu.summonsMessageSignature == user.summonsMessageSignature) + assert(uu.summonsAvailablePaymentMethods == user.summonsAvailablePaymentMethods) + assert(uu.summonsDisplayFormat == user.summonsDisplayFormat) + assert(uu.summonsDisplayEntryFee == user.summonsDisplayEntryFee) + assert(uu.summonsUseFullCustomMessage == user.summonsUseFullCustomMessage) + assert(uu.matchFormatsDefaultDuration == user.matchFormatsDefaultDuration) + assert(uu.bracketMatchFormatPreference == user.bracketMatchFormatPreference) + assert(uu.groupStageMatchFormatPreference == user.groupStageMatchFormatPreference) + assert(uu.loserBracketMatchFormatPreference == user.loserBracketMatchFormatPreference) + assert(uu.loserBracketMode == user.loserBracketMode) + } else { + XCTFail("missing data") + } } }