Compare commits

..

No commits in common. 'main' and 'newoffer2025' have entirely different histories.

  1. BIN
      .DS_Store
  2. 4
      .gitignore
  3. 13
      PadelClubData/Data/Club.swift
  4. 2
      PadelClubData/Data/Court.swift
  5. 28
      PadelClubData/Data/DataStore.swift
  6. 2
      PadelClubData/Data/DateInterval.swift
  7. 2
      PadelClubData/Data/DrawLog.swift
  8. 52
      PadelClubData/Data/Event.swift
  9. 23
      PadelClubData/Data/Gen/BaseClub.swift
  10. 20
      PadelClubData/Data/Gen/BaseCourt.swift
  11. 20
      PadelClubData/Data/Gen/BaseCustomUser.swift
  12. 11
      PadelClubData/Data/Gen/BaseDateInterval.swift
  13. 20
      PadelClubData/Data/Gen/BaseDrawLog.swift
  14. 26
      PadelClubData/Data/Gen/BaseEvent.swift
  15. 23
      PadelClubData/Data/Gen/BaseGroupStage.swift
  16. 22
      PadelClubData/Data/Gen/BaseMatch.swift
  17. 20
      PadelClubData/Data/Gen/BaseMatchScheduler.swift
  18. 11
      PadelClubData/Data/Gen/BaseMonthData.swift
  19. 20
      PadelClubData/Data/Gen/BasePlayerRegistration.swift
  20. 20
      PadelClubData/Data/Gen/BasePurchase.swift
  21. 22
      PadelClubData/Data/Gen/BaseRound.swift
  22. 26
      PadelClubData/Data/Gen/BaseTeamRegistration.swift
  23. 20
      PadelClubData/Data/Gen/BaseTeamScore.swift
  24. 35
      PadelClubData/Data/Gen/BaseTournament.swift
  25. 3
      PadelClubData/Data/Gen/Drawlog.json
  26. 2
      PadelClubData/Data/Gen/GroupStage.json
  27. 4
      PadelClubData/Data/Gen/Match.json
  28. 2
      PadelClubData/Data/Gen/MatchScheduler.json
  29. 1
      PadelClubData/Data/Gen/PlayerRegistration.json
  30. 3
      PadelClubData/Data/Gen/Purchase.json
  31. 3
      PadelClubData/Data/Gen/Round.json
  32. 6
      PadelClubData/Data/Gen/TeamRegistration.json
  33. 5
      PadelClubData/Data/Gen/TeamScore.json
  34. 8
      PadelClubData/Data/Gen/Tournament.json
  35. 263
      PadelClubData/Data/Gen/generator.py
  36. 64
      PadelClubData/Data/GroupStage.swift
  37. 56
      PadelClubData/Data/Match.swift
  38. 17
      PadelClubData/Data/MatchScheduler.swift
  39. 42
      PadelClubData/Data/PlayerRegistration.swift
  40. 55
      PadelClubData/Data/Round.swift
  41. 96
      PadelClubData/Data/TeamRegistration.swift
  42. 8
      PadelClubData/Data/TeamScore.swift
  43. 187
      PadelClubData/Data/Tournament.swift
  44. 13
      PadelClubData/Data/TournamentLibrary.swift
  45. 12
      PadelClubData/Data/TournamentStore.swift
  46. 47
      PadelClubData/Extensions/Date+Extensions.swift
  47. 93
      PadelClubData/Extensions/String+Extensions.swift
  48. 70
      PadelClubData/Subscriptions/Guard.swift
  49. 4
      PadelClubData/Subscriptions/StoreManager.swift
  50. 95
      PadelClubData/Utils/ContactManager.swift
  51. 20
      PadelClubData/Utils/ExportFormat.swift
  52. 16
      PadelClubData/Utils/PListReader.swift
  53. 73
      PadelClubData/ViewModel/PadelRule.swift
  54. 34
      PadelClubDataTests/Config.swift
  55. 31
      PadelClubDataTests/DeletionTests.swift
  56. 101
      PadelClubDataTests/PadelClubDataTests.swift
  57. 1154
      PadelClubDataTests/SyncDataAccessTests.swift
  58. 73
      PadelClubDataTests/SynchronizationTests.swift

BIN
.DS_Store vendored

Binary file not shown.

4
.gitignore vendored

@ -3,12 +3,8 @@
# #
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
PadelClubDataTests/config.plist
## User settings ## User settings
xcuserdata/ xcuserdata/
.DS_Store
config.plist
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint *.xcscmblueprint

@ -29,12 +29,15 @@ final public class Club: BaseClub {
DataStore.shared.courts.filter { $0.club == self.id }.sorted(by: \.index) DataStore.shared.courts.filter { $0.club == self.id }.sorted(by: \.index)
} }
public override func deleteDependencies(store: Store, actionOption: ActionOption) { public override func deleteDependencies(store: Store, shouldBeSynchronized: Bool) {
store.deleteDependencies(type: Court.self, actionOption: actionOption) { $0.club == self.id }
} store.deleteDependencies(type: Court.self, shouldBeSynchronized: shouldBeSynchronized) { $0.club == self.id }
public override func deleteUnusedSharedDependencies(store: Store) { // let customizedCourts = self.customizedCourts
store.deleteUnusedSharedDependencies(type: Court.self) { $0.club == self.id } // for customizedCourt in customizedCourts {
// customizedCourt.deleteDependencies(store: store, shouldBeSynchronized: shouldBeSynchronized)
// }
// DataStore.shared.courts.deleteDependencies(customizedCourts, shouldBeSynchronized: shouldBeSynchronized)
} }
} }

@ -32,7 +32,7 @@ final public class Court: BaseCourt {
Store.main.findById(club) Store.main.findById(club)
} }
public override func deleteDependencies(store: Store, actionOption: ActionOption) { public override func deleteDependencies(store: Store, shouldBeSynchronized: Bool) {
} }
} }

@ -22,8 +22,8 @@ public class DataStore: ObservableObject {
if self.user.id != self.userStorage.item()?.id { if self.user.id != self.userStorage.item()?.id {
self.userStorage.setItemNoSync(self.user) self.userStorage.setItemNoSync(self.user)
StoreCenter.main.initialSynchronization(clear: false) StoreCenter.main.initialSynchronization(clear: false)
self._fixMissingClubCreatorIfNecessary() self._fixMissingClubCreatorIfNecessary(self.clubs)
self._fixMissingEventCreatorIfNecessary() self._fixMissingEventCreatorIfNecessary(self.events)
} }
} else { } else {
self._temporaryLocalUser.item = self.user self._temporaryLocalUser.item = self.user
@ -59,7 +59,7 @@ public class DataStore: ObservableObject {
self.events = store.registerSynchronizedCollection(indexed: indexed) self.events = store.registerSynchronizedCollection(indexed: indexed)
self.dateIntervals = store.registerSynchronizedCollection(indexed: indexed) self.dateIntervals = store.registerSynchronizedCollection(indexed: indexed)
self.userStorage = store.registerObject(synchronized: true, shouldLoadDataFromServer: false) self.userStorage = store.registerObject(synchronized: true, shouldLoadDataFromServer: false)
self.purchases = store.registerSynchronizedCollection(inMemory: true) self.purchases = Store.main.registerSynchronizedCollection(inMemory: true)
self.monthData = store.registerCollection(indexed: indexed) self.monthData = store.registerCollection(indexed: indexed)
@ -96,12 +96,12 @@ public class DataStore: ObservableObject {
@objc func collectionDidLoad(notification: Notification) { @objc func collectionDidLoad(notification: Notification) {
if let userSingleton: StoredCollection<CustomUser> = notification.object as? StoredCollection<CustomUser> { if let userSingleton: StoredSingleton<CustomUser> = notification.object as? StoredSingleton<CustomUser> {
self.user = userSingleton.first ?? self._temporaryLocalUser.item ?? CustomUser.placeHolder() self.user = userSingleton.item() ?? self._temporaryLocalUser.item ?? CustomUser.placeHolder()
} else if notification.object is StoredCollection<Club> { } else if let clubsCollection: SyncedCollection<Club> = notification.object as? SyncedCollection<Club> {
self._fixMissingClubCreatorIfNecessary() self._fixMissingClubCreatorIfNecessary(clubsCollection)
} else if notification.object is StoredCollection<Event> { } else if let eventsCollection: SyncedCollection<Event> = notification.object as? SyncedCollection<Event> {
self._fixMissingEventCreatorIfNecessary() self._fixMissingEventCreatorIfNecessary(eventsCollection)
} }
if Store.main.fileCollectionsAllLoaded() { if Store.main.fileCollectionsAllLoaded() {
@ -111,11 +111,10 @@ public class DataStore: ObservableObject {
} }
fileprivate func _fixMissingClubCreatorIfNecessary() { fileprivate func _fixMissingClubCreatorIfNecessary(_ clubsCollection: SyncedCollection<Club>) {
if self.user.clubs.count > 0 { return } if self.user.clubs.count > 0 { return }
let clubsCollection = DataStore.shared.clubs
for club in clubsCollection { for club in clubsCollection {
if let userId = StoreCenter.main.userId, club.creator == nil { if let userId = StoreCenter.main.userId, club.creator == nil {
club.creator = userId club.creator = userId
@ -126,8 +125,7 @@ public class DataStore: ObservableObject {
} }
} }
fileprivate func _fixMissingEventCreatorIfNecessary() { fileprivate func _fixMissingEventCreatorIfNecessary(_ eventsCollection: SyncedCollection<Event>) {
let eventsCollection = DataStore.shared.events
for event in eventsCollection { for event in eventsCollection {
if let userId = StoreCenter.main.userId, event.creator == nil { if let userId = StoreCenter.main.userId, event.creator == nil {
event.creator = userId event.creator = userId
@ -332,7 +330,7 @@ public class DataStore: ObservableObject {
_lastRunningAndNextCheckDate = nil _lastRunningAndNextCheckDate = nil
} }
public func runningAndNextMatches(_ selectedTournaments: Set<String> = Set()) -> [Match] { public func runningAndNextMatches() -> [Match] {
let dateNow : Date = Date() let dateNow : Date = Date()
if let lastCheck = _lastRunningAndNextCheckDate, if let lastCheck = _lastRunningAndNextCheckDate,
let cachedMatches = _cachedRunningAndNextMatches, let cachedMatches = _cachedRunningAndNextMatches,
@ -340,7 +338,7 @@ public class DataStore: ObservableObject {
return cachedMatches return cachedMatches
} }
let lastTournaments = self.tournaments.filter { (selectedTournaments.isEmpty || selectedTournaments.contains($0.id) == false) && $0.isDeleted == false && $0.startDate <= dateNow.addingTimeInterval(86_400) && $0.hasEnded() == false }.sorted(by: \Tournament.startDate, order: .descending).prefix(10) 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] = [] var runningMatches: [Match] = []
for tournament in lastTournaments { for tournament in lastTournaments {

@ -28,7 +28,7 @@ final public class DateInterval: BaseDateInterval {
date <= startDate && date <= endDate && date >= startDate && date >= endDate date <= startDate && date <= endDate && date >= startDate && date >= endDate
} }
public override func deleteDependencies(store: Store, actionOption: ActionOption) { public override func deleteDependencies(store: Store, shouldBeSynchronized: Bool) {
} }
// enum CodingKeys: String, CodingKey { // enum CodingKeys: String, CodingKey {

@ -74,7 +74,7 @@ final public class DrawLog: BaseDrawLog, SideStorable {
return TournamentLibrary.shared.store(tournamentId: self.tournament) return TournamentLibrary.shared.store(tournamentId: self.tournament)
} }
public override func deleteDependencies(store: Store, actionOption: ActionOption) { public override func deleteDependencies(store: Store, shouldBeSynchronized: Bool) {
} }
} }

@ -25,16 +25,24 @@ final public class Event: BaseEvent {
super.init() super.init()
} }
public override func deleteUnusedSharedDependencies(store: Store) { public override func deleteDependencies(store: Store, shouldBeSynchronized: Bool) {
store.deleteUnusedSharedDependencies(type: Tournament.self) { $0.event == self.id && $0.isDeleted == false }
store.deleteUnusedSharedDependencies(type: DateInterval.self) { $0.event == self.id }
}
public override func deleteDependencies(store: Store, actionOption: ActionOption) {
store.deleteDependencies(type: Tournament.self, actionOption: actionOption) { $0.event == self.id && $0.isDeleted == false } store.deleteDependencies(type: Tournament.self, shouldBeSynchronized: shouldBeSynchronized) { $0.event == self.id && $0.isDeleted == false }
store.deleteDependencies(type: DateInterval.self, actionOption: actionOption) { $0.event == self.id } store.deleteDependencies(type: DateInterval.self, shouldBeSynchronized: shouldBeSynchronized) { $0.event == self.id }
// let tournaments = self.tournaments
// for tournament in tournaments {
// tournament.deleteDependencies(store: store, shouldBeSynchronized: shouldBeSynchronized)
// }
//
// DataStore.shared.tournaments.deleteDependencies(tournaments, shouldBeSynchronized: shouldBeSynchronized)
//
// let courtsUnavailabilities = self.courtsUnavailability
// for courtsUnavailability in courtsUnavailabilities {
// courtsUnavailability.deleteDependencies(store: store, shouldBeSynchronized: shouldBeSynchronized)
// }
// DataStore.shared.dateIntervals.deleteDependencies(courtsUnavailabilities, shouldBeSynchronized: shouldBeSynchronized)
} }
// MARK: - Computed dependencies // MARK: - Computed dependencies
@ -183,35 +191,7 @@ final public class Event: BaseEvent {
return link.compactMap({ $0 }).joined(separator: "\n\n") return link.compactMap({ $0 }).joined(separator: "\n\n")
} }
public func selectedTeams() -> [TeamRegistration] {
confirmedTournaments().flatMap({ $0.selectedSortedTeams() })
}
public func umpireMail() -> [String]? {
confirmedTournaments().first?.umpireMail()
}
public func mailSubject() -> String {
let tournaments = confirmedTournaments()
guard !tournaments.isEmpty else {
return eventTitle()
}
// Get the date range from all confirmed tournaments
let dates = tournaments.compactMap { $0.startDate }
guard let firstDate = dates.min(), let lastDate = dates.max() else {
return eventTitle()
}
let dateRange = firstDate == lastDate
? firstDate.formattedDate(.short)
: "\(firstDate.formatted(.dateTime.day())) au \(lastDate.formatted(.dateTime.day())) \(lastDate.formatted(.dateTime.month(.wide))) \(lastDate.formatted(.dateTime.year()))"
let subject = [eventTitle(), dateRange, clubObject()?.name].compactMap({ $0 }).joined(separator: " | ")
return subject
}
func insertOnServer() throws { func insertOnServer() throws {
DataStore.shared.events.writeChangeAndInsertOnServer(instance: self) DataStore.shared.events.writeChangeAndInsertOnServer(instance: self)

@ -1,4 +1,4 @@
// Generated by LeStorageGenerator // Generated by SwiftModelGenerator
// Do not modify this file manually // Do not modify this file manually
import Foundation import Foundation
@ -11,7 +11,6 @@ public class BaseClub: SyncedModelObject, SyncedStorable {
public static func resourceName() -> String { return "clubs" } public static func resourceName() -> String { return "clubs" }
public static func tokenExemptedMethods() -> [HTTPMethod] { return [] } public static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
public static var copyServerResponse: Bool = true public static var copyServerResponse: Bool = true
public static func storeParent() -> Bool { return false }
public var id: String = Store.randomId() public var id: String = Store.randomId()
public var creator: String? = nil public var creator: String? = nil
@ -121,7 +120,7 @@ public class BaseClub: SyncedModelObject, SyncedStorable {
func creatorValue() -> CustomUser? { func creatorValue() -> CustomUser? {
guard let creator = self.creator else { return nil } guard let creator = self.creator else { return nil }
return self.store?.findById(creator) return Store.main.findById(creator)
} }
public func copy(from other: any Storable) { public func copy(from other: any Storable) {
@ -142,24 +141,10 @@ public class BaseClub: SyncedModelObject, SyncedStorable {
self.timezone = club.timezone self.timezone = club.timezone
} }
public static func parentRelationships() -> [Relationship] { public static func relationships() -> [Relationship] {
return [
Relationship(type: CustomUser.self, keyPath: \BaseClub.creator, storeLookup: .same),
]
}
public static func childrenRelationships() -> [Relationship] {
return [ return [
Relationship(type: Event.self, keyPath: \BaseEvent.club, storeLookup: .same), Relationship(type: CustomUser.self, keyPath: \BaseClub.creator),
Relationship(type: Court.self, keyPath: \BaseCourt.club, storeLookup: .same),
] ]
} }
public static func relationships() -> [Relationship] {
var relationships: [Relationship] = []
relationships.append(contentsOf: parentRelationships())
relationships.append(contentsOf: childrenRelationships())
return relationships
}
} }

@ -1,4 +1,4 @@
// Generated by LeStorageGenerator // Generated by SwiftModelGenerator
// Do not modify this file manually // Do not modify this file manually
import Foundation import Foundation
@ -11,7 +11,6 @@ public class BaseCourt: SyncedModelObject, SyncedStorable {
public static func resourceName() -> String { return "courts" } public static func resourceName() -> String { return "courts" }
public static func tokenExemptedMethods() -> [HTTPMethod] { return [] } public static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
public static var copyServerResponse: Bool = false public static var copyServerResponse: Bool = false
public static func storeParent() -> Bool { return false }
public var id: String = Store.randomId() public var id: String = Store.randomId()
public var index: Int = 0 public var index: Int = 0
@ -72,7 +71,7 @@ public class BaseCourt: SyncedModelObject, SyncedStorable {
} }
func clubValue() -> Club? { func clubValue() -> Club? {
return self.store?.findById(club) return Store.main.findById(club)
} }
public func copy(from other: any Storable) { public func copy(from other: any Storable) {
@ -85,21 +84,10 @@ public class BaseCourt: SyncedModelObject, SyncedStorable {
self.indoor = court.indoor self.indoor = court.indoor
} }
public static func parentRelationships() -> [Relationship] { public static func relationships() -> [Relationship] {
return [ return [
Relationship(type: Club.self, keyPath: \BaseCourt.club, storeLookup: .same), Relationship(type: Club.self, keyPath: \BaseCourt.club),
] ]
} }
public static func childrenRelationships() -> [Relationship] {
return []
}
public static func relationships() -> [Relationship] {
var relationships: [Relationship] = []
relationships.append(contentsOf: parentRelationships())
relationships.append(contentsOf: childrenRelationships())
return relationships
}
} }

@ -1,4 +1,4 @@
// Generated by LeStorageGenerator // Generated by SwiftModelGenerator
// Do not modify this file manually // Do not modify this file manually
import Foundation import Foundation
@ -11,7 +11,6 @@ public class BaseCustomUser: SyncedModelObject, SyncedStorable {
public static func resourceName() -> String { return "users" } public static func resourceName() -> String { return "users" }
public static func tokenExemptedMethods() -> [HTTPMethod] { return [.post] } public static func tokenExemptedMethods() -> [HTTPMethod] { return [.post] }
public static var copyServerResponse: Bool = false public static var copyServerResponse: Bool = false
public static func storeParent() -> Bool { return false }
public var id: String = Store.randomId() public var id: String = Store.randomId()
public var username: String = "" public var username: String = ""
@ -256,23 +255,8 @@ public class BaseCustomUser: SyncedModelObject, SyncedStorable {
self.hideUmpirePhone = customuser.hideUmpirePhone self.hideUmpirePhone = customuser.hideUmpirePhone
} }
public static func parentRelationships() -> [Relationship] {
return []
}
public static func childrenRelationships() -> [Relationship] {
return [
Relationship(type: Club.self, keyPath: \BaseClub.creator, storeLookup: .same),
Relationship(type: Event.self, keyPath: \BaseEvent.creator, storeLookup: .same),
Relationship(type: Purchase.self, keyPath: \BasePurchase.user, storeLookup: .same),
]
}
public static func relationships() -> [Relationship] { public static func relationships() -> [Relationship] {
var relationships: [Relationship] = [] return []
relationships.append(contentsOf: parentRelationships())
relationships.append(contentsOf: childrenRelationships())
return relationships
} }
} }

@ -1,4 +1,4 @@
// Generated by LeStorageGenerator // Generated by SwiftModelGenerator
// Do not modify this file manually // Do not modify this file manually
import Foundation import Foundation
@ -11,7 +11,6 @@ public class BaseDateInterval: SyncedModelObject, SyncedStorable {
public static func resourceName() -> String { return "date-intervals" } public static func resourceName() -> String { return "date-intervals" }
public static func tokenExemptedMethods() -> [HTTPMethod] { return [] } public static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
public static var copyServerResponse: Bool = false public static var copyServerResponse: Bool = false
public static func storeParent() -> Bool { return false }
public var id: String = Store.randomId() public var id: String = Store.randomId()
public var event: String = "" public var event: String = ""
@ -74,14 +73,6 @@ public class BaseDateInterval: SyncedModelObject, SyncedStorable {
self.endDate = dateinterval.endDate self.endDate = dateinterval.endDate
} }
public static func parentRelationships() -> [Relationship] {
return []
}
public static func childrenRelationships() -> [Relationship] {
return []
}
public static func relationships() -> [Relationship] { public static func relationships() -> [Relationship] {
return [] return []
} }

@ -1,4 +1,4 @@
// Generated by LeStorageGenerator // Generated by SwiftModelGenerator
// Do not modify this file manually // Do not modify this file manually
import Foundation import Foundation
@ -11,7 +11,6 @@ public class BaseDrawLog: SyncedModelObject, SyncedStorable {
public static func resourceName() -> String { return "draw-logs" } public static func resourceName() -> String { return "draw-logs" }
public static func tokenExemptedMethods() -> [HTTPMethod] { return [] } public static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
public static var copyServerResponse: Bool = false public static var copyServerResponse: Bool = false
public static func storeParent() -> Bool { return false }
public var id: String = Store.randomId() public var id: String = Store.randomId()
public var tournament: String = "" public var tournament: String = ""
@ -78,7 +77,7 @@ public class BaseDrawLog: SyncedModelObject, SyncedStorable {
} }
func tournamentValue() -> Tournament? { func tournamentValue() -> Tournament? {
return self.store?.storeCenter.mainStore.findById(tournament) return Store.main.findById(tournament)
} }
public func copy(from other: any Storable) { public func copy(from other: any Storable) {
@ -92,21 +91,10 @@ public class BaseDrawLog: SyncedModelObject, SyncedStorable {
self.drawType = drawlog.drawType self.drawType = drawlog.drawType
} }
public static func parentRelationships() -> [Relationship] { public static func relationships() -> [Relationship] {
return [ return [
Relationship(type: Tournament.self, keyPath: \BaseDrawLog.tournament, storeLookup: .main), Relationship(type: Tournament.self, keyPath: \BaseDrawLog.tournament),
] ]
} }
public static func childrenRelationships() -> [Relationship] {
return []
}
public static func relationships() -> [Relationship] {
var relationships: [Relationship] = []
relationships.append(contentsOf: parentRelationships())
relationships.append(contentsOf: childrenRelationships())
return relationships
}
} }

@ -1,4 +1,4 @@
// Generated by LeStorageGenerator // Generated by SwiftModelGenerator
// Do not modify this file manually // Do not modify this file manually
import Foundation import Foundation
@ -11,7 +11,6 @@ public class BaseEvent: SyncedModelObject, SyncedStorable {
public static func resourceName() -> String { return "events" } public static func resourceName() -> String { return "events" }
public static func tokenExemptedMethods() -> [HTTPMethod] { return [] } public static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
public static var copyServerResponse: Bool = false public static var copyServerResponse: Bool = false
public static func storeParent() -> Bool { return false }
public var id: String = Store.randomId() public var id: String = Store.randomId()
public var creator: String? = nil public var creator: String? = nil
@ -73,12 +72,12 @@ public class BaseEvent: SyncedModelObject, SyncedStorable {
func creatorValue() -> CustomUser? { func creatorValue() -> CustomUser? {
guard let creator = self.creator else { return nil } guard let creator = self.creator else { return nil }
return self.store?.findById(creator) return Store.main.findById(creator)
} }
func clubValue() -> Club? { func clubValue() -> Club? {
guard let club = self.club else { return nil } guard let club = self.club else { return nil }
return self.store?.findById(club) return Store.main.findById(club)
} }
public func copy(from other: any Storable) { public func copy(from other: any Storable) {
@ -91,24 +90,11 @@ public class BaseEvent: SyncedModelObject, SyncedStorable {
self.tenupId = event.tenupId self.tenupId = event.tenupId
} }
public static func parentRelationships() -> [Relationship] { public static func relationships() -> [Relationship] {
return [
Relationship(type: CustomUser.self, keyPath: \BaseEvent.creator, storeLookup: .same),
Relationship(type: Club.self, keyPath: \BaseEvent.club, storeLookup: .same),
]
}
public static func childrenRelationships() -> [Relationship] {
return [ return [
Relationship(type: Tournament.self, keyPath: \BaseTournament.event, storeLookup: .same), Relationship(type: CustomUser.self, keyPath: \BaseEvent.creator),
Relationship(type: Club.self, keyPath: \BaseEvent.club),
] ]
} }
public static func relationships() -> [Relationship] {
var relationships: [Relationship] = []
relationships.append(contentsOf: parentRelationships())
relationships.append(contentsOf: childrenRelationships())
return relationships
}
} }

@ -1,4 +1,4 @@
// Generated by LeStorageGenerator // Generated by SwiftModelGenerator
// Do not modify this file manually // Do not modify this file manually
import Foundation import Foundation
@ -11,7 +11,6 @@ public class BaseGroupStage: SyncedModelObject, SyncedStorable {
public static func resourceName() -> String { return "group-stages" } public static func resourceName() -> String { return "group-stages" }
public static func tokenExemptedMethods() -> [HTTPMethod] { return [] } public static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
public static var copyServerResponse: Bool = false public static var copyServerResponse: Bool = false
public static func storeParent() -> Bool { return false }
public var id: String = Store.randomId() public var id: String = Store.randomId()
public var tournament: String = "" public var tournament: String = ""
@ -96,7 +95,7 @@ public class BaseGroupStage: SyncedModelObject, SyncedStorable {
} }
func tournamentValue() -> Tournament? { func tournamentValue() -> Tournament? {
return self.store?.storeCenter.mainStore.findById(tournament) return Store.main.findById(tournament)
} }
public func copy(from other: any Storable) { public func copy(from other: any Storable) {
@ -112,24 +111,10 @@ public class BaseGroupStage: SyncedModelObject, SyncedStorable {
self.plannedStartDate = groupstage.plannedStartDate self.plannedStartDate = groupstage.plannedStartDate
} }
public static func parentRelationships() -> [Relationship] { public static func relationships() -> [Relationship] {
return [
Relationship(type: Tournament.self, keyPath: \BaseGroupStage.tournament, storeLookup: .main),
]
}
public static func childrenRelationships() -> [Relationship] {
return [ return [
Relationship(type: Match.self, keyPath: \BaseMatch.groupStage, storeLookup: .same), Relationship(type: Tournament.self, keyPath: \BaseGroupStage.tournament),
Relationship(type: TeamRegistration.self, keyPath: \BaseTeamRegistration.groupStage, storeLookup: .same),
] ]
} }
public static func relationships() -> [Relationship] {
var relationships: [Relationship] = []
relationships.append(contentsOf: parentRelationships())
relationships.append(contentsOf: childrenRelationships())
return relationships
}
} }

@ -1,4 +1,4 @@
// Generated by LeStorageGenerator // Generated by SwiftModelGenerator
// Do not modify this file manually // Do not modify this file manually
import Foundation import Foundation
@ -11,7 +11,6 @@ public class BaseMatch: SyncedModelObject, SyncedStorable {
public static func resourceName() -> String { return "matches" } public static func resourceName() -> String { return "matches" }
public static func tokenExemptedMethods() -> [HTTPMethod] { return [] } public static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
public static var copyServerResponse: Bool = false public static var copyServerResponse: Bool = false
public static func storeParent() -> Bool { return false }
public var id: String = Store.randomId() public var id: String = Store.randomId()
public var round: String? = nil public var round: String? = nil
@ -160,24 +159,11 @@ public class BaseMatch: SyncedModelObject, SyncedStorable {
self.plannedStartDate = match.plannedStartDate self.plannedStartDate = match.plannedStartDate
} }
public static func parentRelationships() -> [Relationship] { public static func relationships() -> [Relationship] {
return [
Relationship(type: Round.self, keyPath: \BaseMatch.round, storeLookup: .same),
Relationship(type: GroupStage.self, keyPath: \BaseMatch.groupStage, storeLookup: .same),
]
}
public static func childrenRelationships() -> [Relationship] {
return [ return [
Relationship(type: TeamScore.self, keyPath: \BaseTeamScore.match, storeLookup: .same), Relationship(type: Round.self, keyPath: \BaseMatch.round),
Relationship(type: GroupStage.self, keyPath: \BaseMatch.groupStage),
] ]
} }
public static func relationships() -> [Relationship] {
var relationships: [Relationship] = []
relationships.append(contentsOf: parentRelationships())
relationships.append(contentsOf: childrenRelationships())
return relationships
}
} }

@ -1,4 +1,4 @@
// Generated by LeStorageGenerator // Generated by SwiftModelGenerator
// Do not modify this file manually // Do not modify this file manually
import Foundation import Foundation
@ -11,7 +11,6 @@ public class BaseMatchScheduler: BaseModelObject, Storable {
public static func resourceName() -> String { return "match-scheduler" } public static func resourceName() -> String { return "match-scheduler" }
public static func tokenExemptedMethods() -> [HTTPMethod] { return [] } public static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
public static var copyServerResponse: Bool = false public static var copyServerResponse: Bool = false
public static func storeParent() -> Bool { return false }
public var id: String = Store.randomId() public var id: String = Store.randomId()
public var tournament: String = "" public var tournament: String = ""
@ -144,7 +143,7 @@ public class BaseMatchScheduler: BaseModelObject, Storable {
} }
func tournamentValue() -> Tournament? { func tournamentValue() -> Tournament? {
return self.store?.storeCenter.mainStore.findById(tournament) return Store.main.findById(tournament)
} }
public func copy(from other: any Storable) { public func copy(from other: any Storable) {
@ -169,21 +168,10 @@ public class BaseMatchScheduler: BaseModelObject, Storable {
self.accountGroupStageBreakTime = matchscheduler.accountGroupStageBreakTime self.accountGroupStageBreakTime = matchscheduler.accountGroupStageBreakTime
} }
public static func parentRelationships() -> [Relationship] { public static func relationships() -> [Relationship] {
return [ return [
Relationship(type: Tournament.self, keyPath: \BaseMatchScheduler.tournament, storeLookup: .main), Relationship(type: Tournament.self, keyPath: \BaseMatchScheduler.tournament),
] ]
} }
public static func childrenRelationships() -> [Relationship] {
return []
}
public static func relationships() -> [Relationship] {
var relationships: [Relationship] = []
relationships.append(contentsOf: parentRelationships())
relationships.append(contentsOf: childrenRelationships())
return relationships
}
} }

@ -1,4 +1,4 @@
// Generated by LeStorageGenerator // Generated by SwiftModelGenerator
// Do not modify this file manually // Do not modify this file manually
import Foundation import Foundation
@ -11,7 +11,6 @@ public class BaseMonthData: BaseModelObject, Storable {
public static func resourceName() -> String { return "month-data" } public static func resourceName() -> String { return "month-data" }
public static func tokenExemptedMethods() -> [HTTPMethod] { return [] } public static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
public static var copyServerResponse: Bool = false public static var copyServerResponse: Bool = false
public static func storeParent() -> Bool { return false }
public var id: String = Store.randomId() public var id: String = Store.randomId()
public var monthKey: String = "" public var monthKey: String = ""
@ -116,14 +115,6 @@ public class BaseMonthData: BaseModelObject, Storable {
self.fileModelIdentifier = monthdata.fileModelIdentifier self.fileModelIdentifier = monthdata.fileModelIdentifier
} }
public static func parentRelationships() -> [Relationship] {
return []
}
public static func childrenRelationships() -> [Relationship] {
return []
}
public static func relationships() -> [Relationship] { public static func relationships() -> [Relationship] {
return [] return []
} }

@ -1,4 +1,4 @@
// Generated by LeStorageGenerator // Generated by SwiftModelGenerator
// Do not modify this file manually // Do not modify this file manually
import Foundation import Foundation
@ -11,7 +11,6 @@ public class BasePlayerRegistration: SyncedModelObject, SyncedStorable {
public static func resourceName() -> String { return "player-registrations" } public static func resourceName() -> String { return "player-registrations" }
public static func tokenExemptedMethods() -> [HTTPMethod] { return [] } public static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
public static var copyServerResponse: Bool = false public static var copyServerResponse: Bool = false
public static func storeParent() -> Bool { return false }
public var id: String = Store.randomId() public var id: String = Store.randomId()
public var teamRegistration: String? = nil public var teamRegistration: String? = nil
@ -217,7 +216,7 @@ public class BasePlayerRegistration: SyncedModelObject, SyncedStorable {
func teamRegistrationValue() -> TeamRegistration? { func teamRegistrationValue() -> TeamRegistration? {
guard let teamRegistration = self.teamRegistration else { return nil } guard let teamRegistration = self.teamRegistration else { return nil }
return self.store?.findById(teamRegistration) return Store.main.findById(teamRegistration)
} }
public func copy(from other: any Storable) { public func copy(from other: any Storable) {
@ -254,21 +253,10 @@ public class BasePlayerRegistration: SyncedModelObject, SyncedStorable {
self.contactEmail = playerregistration.contactEmail self.contactEmail = playerregistration.contactEmail
} }
public static func parentRelationships() -> [Relationship] { public static func relationships() -> [Relationship] {
return [ return [
Relationship(type: TeamRegistration.self, keyPath: \BasePlayerRegistration.teamRegistration, storeLookup: .same), Relationship(type: TeamRegistration.self, keyPath: \BasePlayerRegistration.teamRegistration),
] ]
} }
public static func childrenRelationships() -> [Relationship] {
return []
}
public static func relationships() -> [Relationship] {
var relationships: [Relationship] = []
relationships.append(contentsOf: parentRelationships())
relationships.append(contentsOf: childrenRelationships())
return relationships
}
} }

@ -1,4 +1,4 @@
// Generated by LeStorageGenerator // Generated by SwiftModelGenerator
// Do not modify this file manually // Do not modify this file manually
import Foundation import Foundation
@ -9,7 +9,6 @@ public class BasePurchase: SyncedModelObject, SyncedStorable {
public static func resourceName() -> String { return "purchases" } public static func resourceName() -> String { return "purchases" }
public static func tokenExemptedMethods() -> [HTTPMethod] { return [] } public static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
public static var copyServerResponse: Bool = false public static var copyServerResponse: Bool = false
public static func storeParent() -> Bool { return false }
public var id: UInt64 = 0 public var id: UInt64 = 0
public var user: String = "" public var user: String = ""
@ -77,7 +76,7 @@ public class BasePurchase: SyncedModelObject, SyncedStorable {
} }
func userValue() -> CustomUser? { func userValue() -> CustomUser? {
return self.store?.findById(user) return Store.main.findById(user)
} }
public func copy(from other: any Storable) { public func copy(from other: any Storable) {
@ -91,21 +90,10 @@ public class BasePurchase: SyncedModelObject, SyncedStorable {
self.expirationDate = purchase.expirationDate self.expirationDate = purchase.expirationDate
} }
public static func parentRelationships() -> [Relationship] { public static func relationships() -> [Relationship] {
return [ return [
Relationship(type: CustomUser.self, keyPath: \BasePurchase.user, storeLookup: .same), Relationship(type: CustomUser.self, keyPath: \BasePurchase.user),
] ]
} }
public static func childrenRelationships() -> [Relationship] {
return []
}
public static func relationships() -> [Relationship] {
var relationships: [Relationship] = []
relationships.append(contentsOf: parentRelationships())
relationships.append(contentsOf: childrenRelationships())
return relationships
}
} }

@ -1,4 +1,4 @@
// Generated by LeStorageGenerator // Generated by SwiftModelGenerator
// Do not modify this file manually // Do not modify this file manually
import Foundation import Foundation
@ -11,7 +11,6 @@ public class BaseRound: SyncedModelObject, SyncedStorable {
public static func resourceName() -> String { return "rounds" } public static func resourceName() -> String { return "rounds" }
public static func tokenExemptedMethods() -> [HTTPMethod] { return [] } public static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
public static var copyServerResponse: Bool = false public static var copyServerResponse: Bool = false
public static func storeParent() -> Bool { return false }
public var id: String = Store.randomId() public var id: String = Store.randomId()
public var tournament: String = "" public var tournament: String = ""
@ -96,7 +95,7 @@ public class BaseRound: SyncedModelObject, SyncedStorable {
} }
func tournamentValue() -> Tournament? { func tournamentValue() -> Tournament? {
return self.store?.storeCenter.mainStore.findById(tournament) return Store.main.findById(tournament)
} }
public func copy(from other: any Storable) { public func copy(from other: any Storable) {
@ -112,23 +111,10 @@ public class BaseRound: SyncedModelObject, SyncedStorable {
self.plannedStartDate = round.plannedStartDate self.plannedStartDate = round.plannedStartDate
} }
public static func parentRelationships() -> [Relationship] { public static func relationships() -> [Relationship] {
return [
Relationship(type: Tournament.self, keyPath: \BaseRound.tournament, storeLookup: .main),
]
}
public static func childrenRelationships() -> [Relationship] {
return [ return [
Relationship(type: Match.self, keyPath: \BaseMatch.round, storeLookup: .same), Relationship(type: Tournament.self, keyPath: \BaseRound.tournament),
] ]
} }
public static func relationships() -> [Relationship] {
var relationships: [Relationship] = []
relationships.append(contentsOf: parentRelationships())
relationships.append(contentsOf: childrenRelationships())
return relationships
}
} }

@ -1,4 +1,4 @@
// Generated by LeStorageGenerator // Generated by SwiftModelGenerator
// Do not modify this file manually // Do not modify this file manually
import Foundation import Foundation
@ -11,7 +11,6 @@ public class BaseTeamRegistration: SyncedModelObject, SyncedStorable {
public static func resourceName() -> String { return "team-registrations" } public static func resourceName() -> String { return "team-registrations" }
public static func tokenExemptedMethods() -> [HTTPMethod] { return [] } public static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
public static var copyServerResponse: Bool = false public static var copyServerResponse: Bool = false
public static func storeParent() -> Bool { return false }
public var id: String = Store.randomId() public var id: String = Store.randomId()
public var tournament: String = "" public var tournament: String = ""
@ -167,10 +166,6 @@ public class BaseTeamRegistration: SyncedModelObject, SyncedStorable {
try super.encode(to: encoder) try super.encode(to: encoder)
} }
func tournamentValue() -> Tournament? {
return self.store?.storeCenter.mainStore.findById(tournament)
}
func groupStageValue() -> GroupStage? { func groupStageValue() -> GroupStage? {
guard let groupStage = self.groupStage else { return nil } guard let groupStage = self.groupStage else { return nil }
return self.store?.findById(groupStage) return self.store?.findById(groupStage)
@ -202,25 +197,10 @@ public class BaseTeamRegistration: SyncedModelObject, SyncedStorable {
self.uniqueRandomIndex = teamregistration.uniqueRandomIndex self.uniqueRandomIndex = teamregistration.uniqueRandomIndex
} }
public static func parentRelationships() -> [Relationship] { public static func relationships() -> [Relationship] {
return [
Relationship(type: Tournament.self, keyPath: \BaseTeamRegistration.tournament, storeLookup: .main),
Relationship(type: GroupStage.self, keyPath: \BaseTeamRegistration.groupStage, storeLookup: .same),
]
}
public static func childrenRelationships() -> [Relationship] {
return [ return [
Relationship(type: TeamScore.self, keyPath: \BaseTeamScore.teamRegistration, storeLookup: .same), Relationship(type: GroupStage.self, keyPath: \BaseTeamRegistration.groupStage),
Relationship(type: PlayerRegistration.self, keyPath: \BasePlayerRegistration.teamRegistration, storeLookup: .same),
] ]
} }
public static func relationships() -> [Relationship] {
var relationships: [Relationship] = []
relationships.append(contentsOf: parentRelationships())
relationships.append(contentsOf: childrenRelationships())
return relationships
}
} }

@ -1,4 +1,4 @@
// Generated by LeStorageGenerator // Generated by SwiftModelGenerator
// Do not modify this file manually // Do not modify this file manually
import Foundation import Foundation
@ -11,7 +11,6 @@ public class BaseTeamScore: SyncedModelObject, SyncedStorable {
public static func resourceName() -> String { return "team-scores" } public static func resourceName() -> String { return "team-scores" }
public static func tokenExemptedMethods() -> [HTTPMethod] { return [] } public static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
public static var copyServerResponse: Bool = false public static var copyServerResponse: Bool = false
public static func storeParent() -> Bool { return false }
public var id: String = Store.randomId() public var id: String = Store.randomId()
public var match: String = "" public var match: String = ""
@ -90,22 +89,11 @@ public class BaseTeamScore: SyncedModelObject, SyncedStorable {
self.luckyLoser = teamscore.luckyLoser self.luckyLoser = teamscore.luckyLoser
} }
public static func parentRelationships() -> [Relationship] { public static func relationships() -> [Relationship] {
return [ return [
Relationship(type: Match.self, keyPath: \BaseTeamScore.match, storeLookup: .same), Relationship(type: Match.self, keyPath: \BaseTeamScore.match),
Relationship(type: TeamRegistration.self, keyPath: \BaseTeamScore.teamRegistration, storeLookup: .same), Relationship(type: TeamRegistration.self, keyPath: \BaseTeamScore.teamRegistration),
] ]
} }
public static func childrenRelationships() -> [Relationship] {
return []
}
public static func relationships() -> [Relationship] {
var relationships: [Relationship] = []
relationships.append(contentsOf: parentRelationships())
relationships.append(contentsOf: childrenRelationships())
return relationships
}
} }

@ -1,4 +1,4 @@
// Generated by LeStorageGenerator // Generated by SwiftModelGenerator
// Do not modify this file manually // Do not modify this file manually
import Foundation import Foundation
@ -11,7 +11,6 @@ public class BaseTournament: SyncedModelObject, SyncedStorable {
public static func resourceName() -> String { return "tournaments" } public static func resourceName() -> String { return "tournaments" }
public static func tokenExemptedMethods() -> [HTTPMethod] { return [] } public static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
public static var copyServerResponse: Bool = false public static var copyServerResponse: Bool = false
public static func storeParent() -> Bool { return true }
public var id: String = Store.randomId() public var id: String = Store.randomId()
public var event: String? = nil public var event: String? = nil
@ -85,7 +84,6 @@ public class BaseTournament: SyncedModelObject, SyncedStorable {
public var clubMemberFeeDeduction: Double? = nil public var clubMemberFeeDeduction: Double? = nil
public var unregisterDeltaInHours: Int = 24 public var unregisterDeltaInHours: Int = 24
public var currencyCode: String? = nil public var currencyCode: String? = nil
public var customClubName: String? = nil
public init( public init(
id: String = Store.randomId(), id: String = Store.randomId(),
@ -159,8 +157,7 @@ public class BaseTournament: SyncedModelObject, SyncedStorable {
showTeamsInProg: Bool = false, showTeamsInProg: Bool = false,
clubMemberFeeDeduction: Double? = nil, clubMemberFeeDeduction: Double? = nil,
unregisterDeltaInHours: Int = 24, unregisterDeltaInHours: Int = 24,
currencyCode: String? = nil, currencyCode: String? = nil
customClubName: String? = nil
) { ) {
super.init() super.init()
self.id = id self.id = id
@ -235,7 +232,6 @@ public class BaseTournament: SyncedModelObject, SyncedStorable {
self.clubMemberFeeDeduction = clubMemberFeeDeduction self.clubMemberFeeDeduction = clubMemberFeeDeduction
self.unregisterDeltaInHours = unregisterDeltaInHours self.unregisterDeltaInHours = unregisterDeltaInHours
self.currencyCode = currencyCode self.currencyCode = currencyCode
self.customClubName = customClubName
} }
required public override init() { required public override init() {
super.init() super.init()
@ -316,7 +312,6 @@ public class BaseTournament: SyncedModelObject, SyncedStorable {
case _clubMemberFeeDeduction = "clubMemberFeeDeduction" case _clubMemberFeeDeduction = "clubMemberFeeDeduction"
case _unregisterDeltaInHours = "unregisterDeltaInHours" case _unregisterDeltaInHours = "unregisterDeltaInHours"
case _currencyCode = "currencyCode" case _currencyCode = "currencyCode"
case _customClubName = "customClubName"
} }
private static func _decodePayment(container: KeyedDecodingContainer<CodingKeys>) throws -> TournamentPayment? { private static func _decodePayment(container: KeyedDecodingContainer<CodingKeys>) throws -> TournamentPayment? {
@ -460,7 +455,6 @@ public class BaseTournament: SyncedModelObject, SyncedStorable {
self.clubMemberFeeDeduction = try container.decodeIfPresent(Double.self, forKey: ._clubMemberFeeDeduction) ?? nil self.clubMemberFeeDeduction = try container.decodeIfPresent(Double.self, forKey: ._clubMemberFeeDeduction) ?? nil
self.unregisterDeltaInHours = try container.decodeIfPresent(Int.self, forKey: ._unregisterDeltaInHours) ?? 24 self.unregisterDeltaInHours = try container.decodeIfPresent(Int.self, forKey: ._unregisterDeltaInHours) ?? 24
self.currencyCode = try container.decodeIfPresent(String.self, forKey: ._currencyCode) ?? nil self.currencyCode = try container.decodeIfPresent(String.self, forKey: ._currencyCode) ?? nil
self.customClubName = try container.decodeIfPresent(String.self, forKey: ._customClubName) ?? nil
try super.init(from: decoder) try super.init(from: decoder)
} }
@ -538,13 +532,12 @@ public class BaseTournament: SyncedModelObject, SyncedStorable {
try container.encode(self.clubMemberFeeDeduction, forKey: ._clubMemberFeeDeduction) try container.encode(self.clubMemberFeeDeduction, forKey: ._clubMemberFeeDeduction)
try container.encode(self.unregisterDeltaInHours, forKey: ._unregisterDeltaInHours) try container.encode(self.unregisterDeltaInHours, forKey: ._unregisterDeltaInHours)
try container.encode(self.currencyCode, forKey: ._currencyCode) try container.encode(self.currencyCode, forKey: ._currencyCode)
try container.encode(self.customClubName, forKey: ._customClubName)
try super.encode(to: encoder) try super.encode(to: encoder)
} }
func eventValue() -> Event? { func eventValue() -> Event? {
guard let event = self.event else { return nil } guard let event = self.event else { return nil }
return self.store?.findById(event) return Store.main.findById(event)
} }
public func copy(from other: any Storable) { public func copy(from other: any Storable) {
@ -621,30 +614,12 @@ public class BaseTournament: SyncedModelObject, SyncedStorable {
self.clubMemberFeeDeduction = tournament.clubMemberFeeDeduction self.clubMemberFeeDeduction = tournament.clubMemberFeeDeduction
self.unregisterDeltaInHours = tournament.unregisterDeltaInHours self.unregisterDeltaInHours = tournament.unregisterDeltaInHours
self.currencyCode = tournament.currencyCode self.currencyCode = tournament.currencyCode
self.customClubName = tournament.customClubName
} }
public static func parentRelationships() -> [Relationship] { public static func relationships() -> [Relationship] {
return [
Relationship(type: Event.self, keyPath: \BaseTournament.event, storeLookup: .same),
]
}
public static func childrenRelationships() -> [Relationship] {
return [ return [
Relationship(type: GroupStage.self, keyPath: \BaseGroupStage.tournament, storeLookup: .child), Relationship(type: Event.self, keyPath: \BaseTournament.event),
Relationship(type: MatchScheduler.self, keyPath: \BaseMatchScheduler.tournament, storeLookup: .child),
Relationship(type: Round.self, keyPath: \BaseRound.tournament, storeLookup: .child),
Relationship(type: TeamRegistration.self, keyPath: \BaseTeamRegistration.tournament, storeLookup: .child),
Relationship(type: DrawLog.self, keyPath: \BaseDrawLog.tournament, storeLookup: .child),
] ]
} }
public static func relationships() -> [Relationship] {
var relationships: [Relationship] = []
relationships.append(contentsOf: parentRelationships())
relationships.append(contentsOf: childrenRelationships())
return relationships
}
} }

@ -6,6 +6,7 @@
"synchronizable": true, "synchronizable": true,
"sideStorable": true, "sideStorable": true,
"observable": true, "observable": true,
"relationshipNames": [],
"properties": [ "properties": [
{ {
"name": "id", "name": "id",
@ -15,7 +16,7 @@
{ {
"name": "tournament", "name": "tournament",
"type": "String", "type": "String",
"foreignKey": "Tournament###" "foreignKey": "Tournament"
}, },
{ {
"name": "drawDate", "name": "drawDate",

@ -13,7 +13,7 @@
{ {
"name": "tournament", "name": "tournament",
"type": "String", "type": "String",
"foreignKey": "Tournament###" "foreignKey": "Tournament"
}, },
{ {
"name": "index", "name": "index",

@ -15,13 +15,13 @@
"name": "round", "name": "round",
"type": "String", "type": "String",
"optional": true, "optional": true,
"foreignKey": "Round" "foreignKey": "Round*"
}, },
{ {
"name": "groupStage", "name": "groupStage",
"type": "String", "type": "String",
"optional": true, "optional": true,
"foreignKey": "GroupStage" "foreignKey": "GroupStage*"
}, },
{ {
"name": "startDate", "name": "startDate",

@ -15,7 +15,7 @@
{ {
"name": "tournament", "name": "tournament",
"type": "String", "type": "String",
"foreignKey": "Tournament###" "foreignKey": "Tournament"
}, },
{ {
"name": "timeDifferenceLimit", "name": "timeDifferenceLimit",

@ -5,6 +5,7 @@
"synchronizable": true, "synchronizable": true,
"sideStorable": true, "sideStorable": true,
"observable": true, "observable": true,
"relationshipNames": ["teamRegistration"],
"properties": [ "properties": [
{ {
"name": "id", "name": "id",

@ -44,7 +44,8 @@
"defaultValue": "nil" "defaultValue": "nil"
} }
], ],
"tokenExemptedMethods": [] "tokenExemptedMethods": [],
"relationshipNames": []
} }
] ]
} }

@ -5,6 +5,7 @@
"synchronizable": true, "synchronizable": true,
"sideStorable": true, "sideStorable": true,
"observable": true, "observable": true,
"relationshipNames": [],
"properties": [ "properties": [
{ {
"name": "id", "name": "id",
@ -14,7 +15,7 @@
{ {
"name": "tournament", "name": "tournament",
"type": "String", "type": "String",
"foreignKey": "Tournament###" "foreignKey": "Tournament"
}, },
{ {
"name": "index", "name": "index",

@ -5,6 +5,7 @@
"synchronizable": true, "synchronizable": true,
"sideStorable": true, "sideStorable": true,
"observable": true, "observable": true,
"relationshipNames": [],
"properties": [ "properties": [
{ {
"name": "id", "name": "id",
@ -13,14 +14,13 @@
}, },
{ {
"name": "tournament", "name": "tournament",
"type": "String", "type": "String"
"foreignKey": "Tournament###"
}, },
{ {
"name": "groupStage", "name": "groupStage",
"type": "String", "type": "String",
"optional": true, "optional": true,
"foreignKey": "GroupStage" "foreignKey": "GroupStage*"
}, },
{ {
"name": "registrationDate", "name": "registrationDate",

@ -5,6 +5,7 @@
"synchronizable": true, "synchronizable": true,
"sideStorable": true, "sideStorable": true,
"observable": true, "observable": true,
"relationshipNames": ["match"],
"properties": [ "properties": [
{ {
"name": "id", "name": "id",
@ -14,13 +15,13 @@
{ {
"name": "match", "name": "match",
"type": "String", "type": "String",
"foreignKey": "Match" "foreignKey": "Match*"
}, },
{ {
"name": "teamRegistration", "name": "teamRegistration",
"type": "String", "type": "String",
"optional": true, "optional": true,
"foreignKey": "TeamRegistration" "foreignKey": "TeamRegistration*"
}, },
{ {
"name": "score", "name": "score",

@ -3,8 +3,9 @@
{ {
"name": "Tournament", "name": "Tournament",
"synchronizable": true, "synchronizable": true,
"copyable": true,
"observable": true, "observable": true,
"storeParent": true, "relationshipNames": [],
"properties": [ "properties": [
{ {
"name": "id", "name": "id",
@ -372,11 +373,6 @@
"name": "currencyCode", "name": "currencyCode",
"type": "String", "type": "String",
"optional": true "optional": true
},
{
"name": "customClubName",
"type": "String",
"optional": true
} }
] ]
} }

@ -3,85 +3,21 @@ import re
import os import os
from pathlib import Path from pathlib import Path
from typing import Dict, List, Any from typing import Dict, List, Any
from collections import defaultdict
import argparse import argparse
import sys import sys
import logging import logging
from datetime import datetime from datetime import datetime
import inflect import inflect
class RelationshipAnalyzer: class SwiftModelGenerator:
def __init__(self): def __init__(self, json_data: Dict[str, Any]):
self.parent_relationships = defaultdict(list) # model_name -> list of parent relationships self.data = json_data
self.children_relationships = defaultdict(list) # model_name -> list of children relationships
def analyze_all_models(self, input_dir: str) -> None:
"""Analyze all JSON files to build complete relationship map."""
input_path = Path(input_dir)
json_files = list(input_path.glob("*.json"))
for json_file in json_files:
with open(json_file, 'r') as f:
json_data = json.load(f)
for model in json_data["models"]:
model_name = model["name"]
properties = model.get("properties", [])
# Find foreign key properties (parents)
for prop in properties:
if "foreignKey" in prop:
foreign_key = prop["foreignKey"].rstrip('###')
located_on_main_store = prop["foreignKey"].endswith('###')
if located_on_main_store:
# Store parent relationship
self.parent_relationships[model_name].append({
"name": prop["name"],
"foreignKey": foreign_key,
"storeLookup": ".main"
})
# Store children relationship (reverse)
self.children_relationships[foreign_key].append({
"name": prop["name"],
"childModel": model_name,
"storeLookup": ".child"
})
else:
# Store parent relationship
self.parent_relationships[model_name].append({
"name": prop["name"],
"foreignKey": foreign_key,
"storeLookup": ".same"
})
# Store children relationship (reverse)
self.children_relationships[foreign_key].append({
"name": prop["name"],
"childModel": model_name,
"storeLookup": ".same"
})
def get_parent_relationships(self, model_name: str) -> List[Dict[str, Any]]:
"""Get parent relationships for a model."""
return self.parent_relationships.get(model_name, [])
def get_children_relationships(self, model_name: str) -> List[Dict[str, Any]]:
"""Get children relationships for a model."""
return self.children_relationships.get(model_name, [])
class LeStorageGenerator:
def __init__(self, json_data: Dict[str, Any], relationship_analyzer: RelationshipAnalyzer = None):
self.json_data = json_data
self.relationship_analyzer = relationship_analyzer
self.pluralizer = inflect.engine() self.pluralizer = inflect.engine()
def generate_model(self, model_data: Dict[str, Any]) -> str: def generate_model(self, model_data: Dict[str, Any]) -> str:
model_name = model_data["name"] model_name = model_data["name"]
is_sync = model_data.get("synchronizable", False) is_sync = model_data.get("synchronizable", False)
is_observable = model_data.get("observable", False) is_observable = model_data.get("observable", False)
store_parent = model_data.get("storeParent", False)
properties = model_data["properties"] properties = model_data["properties"]
did_set_properties = [] did_set_properties = []
@ -91,7 +27,7 @@ class LeStorageGenerator:
token_exempted = model_data.get("tokenExemptedMethods", []) token_exempted = model_data.get("tokenExemptedMethods", [])
copy_server_response = model_data.get("copy_server_response", "false") copy_server_response = model_data.get("copy_server_response", "false")
lines = ["// Generated by LeStorageGenerator", "// Do not modify this file manually", ""] lines = ["// Generated by SwiftModelGenerator", "// Do not modify this file manually", ""]
# Import statement # Import statement
lines.append("import Foundation") lines.append("import Foundation")
@ -109,7 +45,7 @@ class LeStorageGenerator:
lines.append("") lines.append("")
# Add SyncedStorable protocol requirements # Add SyncedStorable protocol requirements
lines.extend(self._generate_protocol_requirements(resource_name, token_exempted, copy_server_response, store_parent)) lines.extend(self._generate_protocol_requirements(resource_name, token_exempted, copy_server_response))
lines.append("") lines.append("")
# Properties # Properties
@ -166,11 +102,6 @@ class LeStorageGenerator:
lines.extend(self._generate_copy_method(model_name, properties)) lines.extend(self._generate_copy_method(model_name, properties))
lines.append("") lines.append("")
# Copy method
# if is_sync:
# lines.extend(self._generate_copy_for_update_method(model_name, properties))
# lines.append("")
# Add relationships function # Add relationships function
lines.extend(self._generate_relationships(model_name, properties)) lines.extend(self._generate_relationships(model_name, properties))
lines.append("") lines.append("")
@ -180,7 +111,7 @@ class LeStorageGenerator:
def _generate_constructor(self, model_name: str, properties: List[Dict[str, Any]]) -> List[str]: 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.""" """Generate a constructor with all properties as parameters with default values."""
lines = [" public init("] lines = [" public init("]
# Generate parameter list # Generate parameter list
@ -221,15 +152,15 @@ class LeStorageGenerator:
lines.append(f" self.{name} = {name}") lines.append(f" self.{name} = {name}")
lines.append(" }") lines.append(" }")
lines.extend([ lines.extend([
" required public override init() {", " required public override init() {",
" super.init()", " super.init()",
" }", " }",
]) ])
return lines return lines
def _generate_didset_methods(self, properties) -> List[str]: def _generate_didset_methods(self, properties) -> List[str]:
@ -247,21 +178,21 @@ class LeStorageGenerator:
method_name = f"{prop_name}Value" method_name = f"{prop_name}Value"
is_optional = prop.get("optional", False) is_optional = prop.get("optional", False)
lines.extend([f" func {method_name}() -> {foreign_key.rstrip('###')}? {{"]) lines.extend([f" func {method_name}() -> {foreign_key.rstrip('*')}? {{"])
if is_optional: if is_optional:
lines.extend([ lines.extend([
f" guard let {prop_name} = self.{prop_name} else {{ return nil }}" f" guard let {prop_name} = self.{prop_name} else {{ return nil }}"
]) ])
if foreign_key.endswith("###"): if foreign_key.endswith("*"):
foreign_key = foreign_key[:-1] # Remove the asterisk foreign_key = foreign_key[:-1] # Remove the asterisk
lines.extend([ lines.extend([
f" return self.store?.storeCenter.mainStore.findById({prop_name})" f" return self.store?.findById({prop_name})"
]) ])
else: else:
lines.extend([ lines.extend([
f" return self.store?.findById({prop_name})" f" return Store.main.findById({prop_name})"
]) ])
lines.extend([" }", ""]) # Close the method and add a blank line lines.extend([" }", ""]) # Close the method and add a blank line
@ -462,114 +393,43 @@ class LeStorageGenerator:
lines.append(" }") lines.append(" }")
return lines return lines
def _generate_copy_for_update_method(self, model_name: str, properties: List[Dict[str, Any]]) -> List[str]: def _generate_protocol_requirements(self, resource_name: str, token_exempted: List[str], copy_server_response: str) -> List[str]:
model_variable = model_name.lower()
lines = [f" public func copyForUpdate(from other: any Storable) {{"]
lines.append(f" guard let {model_variable} = other as? Base{model_name} else {{ return }}")
# First handle foreign key properties with special deletion logic
for prop in properties:
if "foreignKey" in prop and prop["foreignKey"] != "CustomUser":
name = prop["name"]
foreign_key = prop["foreignKey"].rstrip('###') # Remove asterisk if present
foreign_variable = name + "Value"
# Generate the foreign key check and deletion logic
lines.append(f" if {model_variable}.{name} != self.{name}, let {name}Object = self.{foreign_variable}(), {name}Object.shared == true, let store = {name}Object.store {{")
lines.append(f" store.deleteUnusedShared({name}Object)")
lines.append(f" }}")
# Then copy all properties
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], copy_server_response: str, store_parent: bool) -> List[str]:
"""Generate the static functions required by SyncedStorable protocol.""" """Generate the static functions required by SyncedStorable protocol."""
# Convert HTTP methods to proper format # Convert HTTP methods to proper format
formatted_methods = [f".{method.lower()}" for method in token_exempted] formatted_methods = [f".{method.lower()}" for method in token_exempted]
methods_str = ", ".join(formatted_methods) if formatted_methods else "" methods_str = ", ".join(formatted_methods) if formatted_methods else ""
store_parent_swift = "true" if store_parent else "false"
return [ return [
f" public static func resourceName() -> String {{ return \"{resource_name}\" }}", f" public static func resourceName() -> String {{ return \"{resource_name}\" }}",
f" public static func tokenExemptedMethods() -> [HTTPMethod] {{ return [{methods_str}] }}", f" public static func tokenExemptedMethods() -> [HTTPMethod] {{ return [{methods_str}] }}",
f" public static var copyServerResponse: Bool = {copy_server_response}", f" public static var copyServerResponse: Bool = {copy_server_response}",
f" public static func storeParent() -> Bool {{ return {store_parent_swift} }}",
] ]
def _generate_relationships(self, model_name, properties: List[Dict[str, Any]]) -> List[str]: def _generate_relationships(self, model_name, properties: List[Dict[str, Any]]) -> List[str]:
# if not self.relationship_analyzer: # Find all properties with foreign keys
# # Fallback to old behavior if no analyzer provided foreign_key_props = [p for p in properties if "foreignKey" in p]
# return self._generate_legacy_relationships(model_name, properties)
lines = []
# Generate parentRelationships method if not foreign_key_props:
lines.extend(self._generate_parent_relationships(model_name)) # If no foreign keys, return empty array
lines.append("")
# Generate childrenRelationships method
lines.extend(self._generate_children_relationships(model_name))
lines.append("")
# Generate combined relationships method
lines.extend(self._generate_combined_relationships(model_name))
return lines
def _generate_parent_relationships(self, model_name: str) -> List[str]:
"""Generate parentRelationships() method."""
parent_rels = self.relationship_analyzer.get_parent_relationships(model_name)
if not parent_rels:
return [ return [
" public static func parentRelationships() -> [Relationship] {", " public static func relationships() -> [Relationship] {",
" return []",
" }"
]
lines = [
" public static func parentRelationships() -> [Relationship] {",
" return ["
]
for rel in parent_rels:
# main_store = "true" if rel["storeLookup"] else "false"
lines.append(f" Relationship(type: {rel['foreignKey']}.self, keyPath: \\Base{model_name}.{rel['name']}, storeLookup: {rel["storeLookup"]}),")
lines.extend([
" ]",
" }"
])
return lines
def _generate_children_relationships(self, model_name: str) -> List[str]:
"""Generate childrenRelationships() method."""
children_rels = self.relationship_analyzer.get_children_relationships(model_name)
if not children_rels:
return [
" public static func childrenRelationships() -> [Relationship] {",
" return []", " return []",
" }" " }"
] ]
lines = [ lines = [
" public static func childrenRelationships() -> [Relationship] {", " public static func relationships() -> [Relationship] {",
" return [" " return ["
] ]
for rel in children_rels: # Generate relationship entries
# main_store = "true" if rel["storeLookup"] else "false" for prop in foreign_key_props:
lines.append(f" Relationship(type: {rel['childModel']}.self, keyPath: \\Base{rel['childModel']}.{rel['name']}, storeLookup: {rel["storeLookup"]}),") 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([ lines.extend([
" ]", " ]",
" }" " }"
@ -577,66 +437,6 @@ class LeStorageGenerator:
return lines return lines
def _generate_combined_relationships(self, model_name: str) -> List[str]:
"""Generate relationships() method that combines parent and children."""
parent_rels = self.relationship_analyzer.get_parent_relationships(model_name)
children_rels = self.relationship_analyzer.get_children_relationships(model_name)
if not parent_rels and not children_rels:
return [
" public static func relationships() -> [Relationship] {",
" return []",
" }"
]
lines = [
" public static func relationships() -> [Relationship] {",
" var relationships: [Relationship] = []",
" relationships.append(contentsOf: parentRelationships())",
" relationships.append(contentsOf: childrenRelationships())",
" return relationships",
" }"
]
return lines
# def _generate_legacy_relationships(self, model_name, properties: List[Dict[str, Any]]) -> List[str]:
# """Legacy relationship generation for backward compatibility."""
# # 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 [
# " public static func relationships() -> [Relationship] {",
# " return []",
# " }"
# ]
#
# lines = [
# " public static func relationships() -> [Relationship] {",
# " return ["
# ]
#
# # Generate relationship entries
# for prop in foreign_key_props:
# name = prop["name"]
# located_on_main_store = "true" if prop["foreignKey"].endswith('###') else "false"
# foreign_key = prop["foreignKey"].rstrip('###') # Remove asterisk if present
#
# if located_on_main_store:
# lines.append(f" Relationship(type: {foreign_key}.self, keyPath: \\Base{model_name}.{name}, storeLookup: {located_on_main_store}),")
# else:
# lines.append(f" Relationship(type: {foreign_key}.self, keyPath: \\Base{model_name}.{name}, storeLookup: {located_on_main_store}),")
#
# # Close the array and function
# lines.extend([
# " ]",
# " }"
# ])
#
# return lines
def _get_default_value(self, type_name: str) -> str: def _get_default_value(self, type_name: str) -> str:
"""Get default value for non-optional types""" """Get default value for non-optional types"""
if "String" in type_name: if "String" in type_name:
@ -681,21 +481,14 @@ def process_directory(input_dir: str, output_dir: str, logger: logging.Logger, d
return 0 return 0
logger.info(f"Found {len(json_files)} JSON files to process") logger.info(f"Found {len(json_files)} JSON files to process")
# First pass: Analyze all relationships
logger.info("Analyzing relationships across all models...")
relationship_analyzer = RelationshipAnalyzer()
relationship_analyzer.analyze_all_models(input_dir)
successful_files = 0 successful_files = 0
# Second pass: Generate models with complete relationship information
for json_file in json_files: for json_file in json_files:
try: try:
with open(json_file, 'r') as f: with open(json_file, 'r') as f:
json_data = json.load(f) json_data = json.load(f)
generator = LeStorageGenerator(json_data, relationship_analyzer) generator = SwiftModelGenerator(json_data)
# Generate each model in the JSON file # Generate each model in the JSON file
for model in json_data["models"]: for model in json_data["models"]:
@ -731,7 +524,7 @@ def process_directory(input_dir: str, output_dir: str, logger: logging.Logger, d
def setup_logging(verbose: bool) -> logging.Logger: def setup_logging(verbose: bool) -> logging.Logger:
"""Configure logging based on verbosity level.""" """Configure logging based on verbosity level."""
logger = logging.getLogger('LeStorageGenerator') logger = logging.getLogger('SwiftModelGenerator')
handler = logging.StreamHandler() handler = logging.StreamHandler()
if verbose: if verbose:

@ -90,7 +90,7 @@ final public class GroupStage: BaseGroupStage, SideStorable {
return _matches.anySatisfy { $0.hasEnded() == false } == false return _matches.anySatisfy { $0.hasEnded() == false } == false
} }
func createMatch(index: Int) -> Match { fileprivate func _createMatch(index: Int) -> Match {
let match: Match = Match(groupStage: self.id, let match: Match = Match(groupStage: self.id,
index: index, index: index,
format: self.matchFormat, format: self.matchFormat,
@ -128,7 +128,7 @@ final public class GroupStage: BaseGroupStage, SideStorable {
var matches = [Match]() var matches = [Match]()
let matchPhaseCount = matchPhaseCount let matchPhaseCount = matchPhaseCount
for i in 0..<_numberOfMatchesToBuild() { for i in 0..<_numberOfMatchesToBuild() {
let newMatch = self.createMatch(index: i + matchCount * matchPhaseCount) let newMatch = self._createMatch(index: i + matchCount * matchPhaseCount)
// let newMatch = Match(groupStage: self.id, index: i, matchFormat: self.matchFormat, name: localizedMatchUpLabel(for: i)) // let newMatch = Match(groupStage: self.id, index: i, matchFormat: self.matchFormat, name: localizedMatchUpLabel(for: i))
teamScores.append(contentsOf: newMatch.createTeamScores()) teamScores.append(contentsOf: newMatch.createTeamScores())
matches.append(newMatch) matches.append(newMatch)
@ -147,13 +147,13 @@ final public class GroupStage: BaseGroupStage, SideStorable {
_removeMatches() _removeMatches()
for i in 0..<_numberOfMatchesToBuild() { for i in 0..<_numberOfMatchesToBuild() {
let newMatch = self.createMatch(index: i) let newMatch = self._createMatch(index: i)
// let newMatch = Match(groupStage: self.id, index: i, matchFormat: self.matchFormat, name: localizedMatchUpLabel(for: i)) // let newMatch = Match(groupStage: self.id, index: i, matchFormat: self.matchFormat, name: localizedMatchUpLabel(for: i))
teamScores.append(contentsOf: newMatch.createTeamScores()) teamScores.append(contentsOf: newMatch.createTeamScores())
matches.append(newMatch) matches.append(newMatch)
} }
} else { } else {
for match in self._matches() { for match in _matches() {
match.resetTeamScores(outsideOf: []) match.resetTeamScores(outsideOf: [])
teamScores.append(contentsOf: match.createTeamScores()) teamScores.append(contentsOf: match.createTeamScores())
} }
@ -298,7 +298,7 @@ final public class GroupStage: BaseGroupStage, SideStorable {
return playedMatches.filter({ $0.isRunning() }).sorted(by: \.computedStartDateForSorting) return playedMatches.filter({ $0.isRunning() }).sorted(by: \.computedStartDateForSorting)
} }
public func readyMatches(playedMatches: [Match], runningMatches: [Match]) -> [Match] { public func readyMatches(playedMatches: [Match]) -> [Match] {
#if _DEBUG_TIME //DEBUGING TIME #if _DEBUG_TIME //DEBUGING TIME
let start = Date() let start = Date()
defer { defer {
@ -306,9 +306,7 @@ final public class GroupStage: BaseGroupStage, SideStorable {
print("func group stage readyMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) print("func group stage readyMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
} }
#endif #endif
let playingTeams = runningMatches.flatMap({ $0.teams() }).map({ $0.id }) return playedMatches.filter({ $0.isReady() && $0.isRunning() == false && $0.hasEnded() == false })
return playedMatches.filter({ $0.isReady() && $0.isRunning() == false && $0.hasEnded() == false && $0.containsTeamIds(playingTeams) == false })
} }
public func finishedMatches(playedMatches: [Match]) -> [Match] { public func finishedMatches(playedMatches: [Match]) -> [Match] {
@ -337,7 +335,7 @@ final public class GroupStage: BaseGroupStage, SideStorable {
case 5: case 5:
order = [3, 5, 8, 2, 6, 1, 9, 4, 7, 0] order = [3, 5, 8, 2, 6, 1, 9, 4, 7, 0]
case 6: case 6:
order = [4, 6, 10, 1, 8, 12, 2, 7, 11, 3, 5, 13, 14, 9, 0] order = [4, 7, 9, 3, 6, 11, 2, 8, 10, 1, 13, 5, 12, 14, 0]
case 7: case 7:
order = [6, 15, 20, 1, 16, 19, 2, 10, 18, 3, 9, 14, 4, 7, 12, 5, 8, 11, 0, 13, 17] order = [6, 15, 20, 1, 16, 19, 2, 10, 18, 3, 9, 14, 4, 7, 12, 5, 8, 11, 0, 13, 17]
case 8: case 8:
@ -402,11 +400,11 @@ final public class GroupStage: BaseGroupStage, SideStorable {
return combinations[safe: matchIndex%matchCount]?.map { teamAt(groupStagePosition: $0) } ?? [] return combinations[safe: matchIndex%matchCount]?.map { teamAt(groupStagePosition: $0) } ?? []
} }
func _removeMatches() { private func _removeMatches() {
self.tournamentStore?.matches.delete(contentOfs: _matches()) self.tournamentStore?.matches.delete(contentOfs: _matches())
} }
func _numberOfMatchesToBuild() -> Int { private func _numberOfMatchesToBuild() -> Int {
(size * (size - 1)) / 2 (size * (size - 1)) / 2
} }
@ -449,7 +447,7 @@ final public class GroupStage: BaseGroupStage, SideStorable {
return teamsSorted.first == teamPosition return teamsSorted.first == teamPosition
} else { } else {
if let matchIndex = combos.firstIndex(of: indexes), let match = self._matches().first(where: { $0.index == matchIndex }) { if let matchIndex = combos.firstIndex(of: indexes), let match = _matches().first(where: { $0.index == matchIndex }) {
return teamPosition.id == match.losingTeamId return teamPosition.id == match.losingTeamId
} else { } else {
return false return false
@ -616,41 +614,19 @@ final public class GroupStage: BaseGroupStage, SideStorable {
} }
public func computedStartDate() -> Date? { public func computedStartDate() -> Date? {
return self._matches().sorted(by: \.computedStartDateForSorting).first?.startDate return _matches().sorted(by: \.computedStartDateForSorting).first?.startDate
}
public func removeAllTeams() {
let teams = teams()
teams.forEach { team in
team.groupStagePosition = nil
team.groupStage = nil
self._matches().forEach({ $0.updateTeamScores() })
}
tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: teams)
} }
public func setData(from correspondingGroupStage: GroupStage, tournamentStartDate: Date, previousTournamentStartDate: Date) { public override func deleteDependencies(store: Store, shouldBeSynchronized: Bool) {
self.matchFormat = correspondingGroupStage.matchFormat
if let correspondingPlannedStartDate = correspondingGroupStage.plannedStartDate {
let offset = correspondingPlannedStartDate.timeIntervalSince(previousTournamentStartDate)
self.startDate = tournamentStartDate.addingTimeInterval(offset)
}
self.size = correspondingGroupStage.size
self.name = correspondingGroupStage.name
let matches = correspondingGroupStage._matches() store.deleteDependencies(type: Match.self, shouldBeSynchronized: shouldBeSynchronized) { $0.groupStage == self.id }
for (index, match) in self._matches().enumerated() {
match.setData(from: matches[index], tournamentStartDate: tournamentStartDate, previousTournamentStartDate: previousTournamentStartDate)
}
}
public override func deleteDependencies(store: Store, actionOption: ActionOption) { //
store.deleteDependencies(type: Match.self, actionOption: actionOption) { $0.groupStage == self.id } // let matches = self._matches()
} // for match in matches {
// match.deleteDependencies(store: store, shouldBeSynchronized: shouldBeSynchronized)
public override func deleteUnusedSharedDependencies(store: Store) { // }
store.deleteUnusedSharedDependencies(type: Match.self) { $0.groupStage == self.id } // self.tournamentStore?.matches.deleteDependencies(matches, shouldBeSynchronized: shouldBeSynchronized)
} }
func insertOnServer() { func insertOnServer() {

@ -22,25 +22,6 @@ final public class Match: BaseMatch, SideStorable {
public var byeState: Bool = false public var byeState: Bool = false
//<<<<<<< HEAD
// public 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)
// }
//
// required public init() {
// super.init()
//
// let drawLog = DrawLog()
// }
//
//=======
//>>>>>>> main
// MARK: - DidSet // MARK: - DidSet
public override func didSetStartDate() { public override func didSetStartDate() {
@ -95,12 +76,15 @@ final public class Match: BaseMatch, SideStorable {
// MARK: - // MARK: -
public override func deleteDependencies(store: Store, actionOption: ActionOption) { public override func deleteDependencies(store: Store, shouldBeSynchronized: Bool) {
store.deleteDependencies(type: TeamScore.self, actionOption: actionOption) { $0.match == self.id }
} store.deleteDependencies(type: TeamScore.self, shouldBeSynchronized: shouldBeSynchronized) { $0.match == self.id }
public override func deleteUnusedSharedDependencies(store: Store) { // let teamScores = self.teamScores
store.deleteUnusedSharedDependencies(type: TeamScore.self) { $0.match == self.id } // for teamScore in teamScores {
// teamScore.deleteDependencies(store: store, shouldBeSynchronized: shouldBeSynchronized)
// }
// self.tournamentStore?.teamScores.deleteDependencies(teamScores, shouldBeSynchronized: shouldBeSynchronized)
} }
public func indexInRound(in matches: [Match]? = nil) -> Int { public func indexInRound(in matches: [Match]? = nil) -> Int {
@ -494,15 +478,7 @@ defer {
return (groupStageObject.index + 1) * 100 + groupStageObject.indexOf(index) return (groupStageObject.index + 1) * 100 + groupStageObject.indexOf(index)
} }
guard let roundObject else { return index } guard let roundObject else { return index }
return (300 - (roundObject.theoryCumulativeMatchCount * 10 + roundObject.index * 22)) * 10 + RoundRule.matchIndexWithinRound(fromMatchIndex: index)
// primary: theoryCumulativeMatchCount (final = 1, semis = 3, quarters = 7, ...)
// multiply by a factor big enough to separate match indexes
let primary = roundObject.theoryCumulativeMatchCount * 1_000
// tie-breaker: match position inside the round (0..N-1)
let secondary = RoundRule.matchIndexWithinRound(fromMatchIndex: index)
return primary + secondary
} }
public func previousMatches() -> [Match] { public func previousMatches() -> [Match] {
@ -1139,18 +1115,6 @@ defer {
public func initialStartDate() -> Date? { public func initialStartDate() -> Date? {
plannedStartDate ?? startDate plannedStartDate ?? startDate
} }
public func setData(from correspondingMatch: Match, tournamentStartDate: Date, previousTournamentStartDate: Date) {
if let correspondingMatchPlannedStartDate = correspondingMatch.plannedStartDate {
let offset = correspondingMatchPlannedStartDate.timeIntervalSince(previousTournamentStartDate)
self.startDate = tournamentStartDate.addingTimeInterval(offset)
}
self.disabled = correspondingMatch.disabled
self.matchFormat = correspondingMatch.matchFormat
self.courtIndex = correspondingMatch.courtIndex
self.name = correspondingMatch.name
}
func insertOnServer() { func insertOnServer() {
self.tournamentStore?.matches.writeChangeAndInsertOnServer(instance: self) self.tournamentStore?.matches.writeChangeAndInsertOnServer(instance: self)

@ -420,7 +420,7 @@ final public class MatchScheduler: BaseMatchScheduler, SideStorable {
rotationIndex += 1 rotationIndex += 1
} }
let timeMatch = TimeMatch(matchID: match.id, rotationIndex: rotationIndex, matchRank: match.computedOrder, courtIndex: match.courtIndex ?? 0, startDate: match.startDate!, durationLeft: match.matchFormat.getEstimatedDuration(additionalEstimationDuration), minimumBreakTime: match.matchFormat.breakTime.breakTime) let timeMatch = TimeMatch(matchID: match.id, rotationIndex: rotationIndex, courtIndex: match.courtIndex ?? 0, startDate: match.startDate!, durationLeft: match.matchFormat.getEstimatedDuration(additionalEstimationDuration), minimumBreakTime: match.matchFormat.breakTime.breakTime)
slots.append(timeMatch) slots.append(timeMatch)
} }
@ -534,7 +534,7 @@ final public class MatchScheduler: BaseMatchScheduler, SideStorable {
for i in 0..<rotationIndex { for i in 0..<rotationIndex {
let courtsSorted = slots.filter { $0.rotationIndex == i }.map { $0.courtIndex }.sorted() let courtsSorted = slots.filter { $0.rotationIndex == i }.map { $0.courtIndex }.sorted()
let courts = randomizeCourts ? courtsSorted.shuffled() : courtsSorted let courts = randomizeCourts ? courtsSorted.shuffled() : courtsSorted
var matches = slots.filter { $0.rotationIndex == i }.sorted(using: .keyPath(\.matchRank)) var matches = slots.filter { $0.rotationIndex == i }.sorted(using: .keyPath(\.courtIndex))
for j in 0..<matches.count { for j in 0..<matches.count {
matches[j].courtIndex = courts[j] matches[j].courtIndex = courts[j]
@ -621,7 +621,6 @@ final public class MatchScheduler: BaseMatchScheduler, SideStorable {
let timeMatch = TimeMatch( let timeMatch = TimeMatch(
matchID: firstMatch.id, matchID: firstMatch.id,
rotationIndex: rotationIndex, rotationIndex: rotationIndex,
matchRank: firstMatch.computedOrder,
courtIndex: courtIndex, courtIndex: courtIndex,
startDate: rotationStartDate, startDate: rotationStartDate,
durationLeft: firstMatch.matchFormat.getEstimatedDuration(additionalEstimationDuration), durationLeft: firstMatch.matchFormat.getEstimatedDuration(additionalEstimationDuration),
@ -839,10 +838,8 @@ final public class MatchScheduler: BaseMatchScheduler, SideStorable {
} }
if tournament.dayDuration > 1 && (lastDate.timeOfDay == .evening || lastDate.timeOfDay == .night || errorFormat) { if tournament.dayDuration > 1 && (lastDate.timeOfDay == .evening || lastDate.timeOfDay == .night || errorFormat) {
if tournament.groupStageCount > 0 { bracketStartDate = lastDate.tomorrowAtNine
bracketStartDate = lastDate.tomorrowAtNine
}
} }
return updateBracketSchedule(tournament: tournament, fromRoundId: nil, fromMatchId: nil, startDate: bracketStartDate) return updateBracketSchedule(tournament: tournament, fromRoundId: nil, fromMatchId: nil, startDate: bracketStartDate)
@ -859,7 +856,6 @@ struct GroupStageTimeMatch {
public struct TimeMatch { public struct TimeMatch {
let matchID: String let matchID: String
let rotationIndex: Int let rotationIndex: Int
let matchRank: Int
var courtIndex: Int var courtIndex: Int
var startDate: Date var startDate: Date
var durationLeft: Int //in minutes var durationLeft: Int //in minutes
@ -898,11 +894,6 @@ extension Match {
return teamIds().contains(id) return teamIds().contains(id)
} }
public func containsTeamIds(_ ids: [String]) -> Bool {
let teamIds = teamIds()
return !Set(ids).isDisjoint(with: teamIds)
}
public func containsTeamIndex(_ id: String) -> Bool { public func containsTeamIndex(_ id: String) -> Bool {
matchUp().contains(id) matchUp().contains(id)
} }

@ -56,23 +56,12 @@ final public class PlayerRegistration: BasePlayerRegistration, SideStorable {
return nil return nil
} }
public func pasteData(_ exportFormat: ExportFormat = .rawText, type: ExportType) -> String { public func pasteData(_ exportFormat: ExportFormat = .rawText) -> String {
switch type { switch exportFormat {
case .payment: case .rawText:
switch exportFormat { return [firstName.capitalized, lastName.capitalized, licenceId?.computedLicense].compactMap({ $0 }).joined(separator: exportFormat.separator())
case .rawText: case .csv:
return [firstName.capitalized, lastName.capitalized, hasPaidOnline() ? "Payé [X]" : "Payé  [ ]"].compactMap({ $0 }).joined(separator: exportFormat.separator()) return [lastName.uppercased() + " " + firstName.capitalized].joined(separator: exportFormat.separator())
case .csv:
return [lastName.uppercased() + " " + firstName.capitalized].joined(separator: exportFormat.separator())
}
case .sharing:
switch exportFormat {
case .rawText:
return [firstName.capitalized, lastName.capitalized, licenceId?.computedLicense].compactMap({ $0 }).joined(separator: exportFormat.separator())
case .csv:
return [lastName.uppercased() + " " + firstName.capitalized, hasPaid() ? "Payé" : "", hasPaidOnline() ? "En ligne" : ""]
.joined(separator: exportFormat.separator())
}
} }
} }
@ -81,13 +70,6 @@ final public class PlayerRegistration: BasePlayerRegistration, SideStorable {
} }
public func contains(_ searchField: String) -> Bool { public func contains(_ searchField: String) -> Bool {
if let paymentId, paymentId.localizedCaseInsensitiveContains(searchField) { return true }
if let email, email.lowercased().localizedCaseInsensitiveContains(searchField) { return true }
if let contactEmail, contactEmail.localizedCaseInsensitiveContains(searchField) { return true }
if let licenceId, licenceId.localizedCaseInsensitiveContains(searchField) { return true }
if searchField.isPhoneNumber(), let phoneNumber, phoneNumber.isSamePhoneNumber(as: searchField) { return true }
if searchField.isPhoneNumber(), let contactPhoneNumber, contactPhoneNumber.isSamePhoneNumber(as: searchField) { return true }
let nameComponents = searchField.canonicalVersion.split(separator: " ") let nameComponents = searchField.canonicalVersion.split(separator: " ")
if nameComponents.count > 1 { if nameComponents.count > 1 {
@ -185,7 +167,7 @@ final public class PlayerRegistration: BasePlayerRegistration, SideStorable {
} }
public func setComputedRank(in tournament: Tournament) { public func setComputedRank(in tournament: Tournament) {
let maleUnranked = tournament.unrankValue(for: isMalePlayer()) ?? 92_327 let maleUnranked = tournament.unrankValue(for: isMalePlayer()) ?? 90_415
let femaleUnranked = tournament.unrankValue(for: false) ?? 0 let femaleUnranked = tournament.unrankValue(for: false) ?? 0
let currentRank = rank ?? maleUnranked let currentRank = rank ?? maleUnranked
switch tournament.tournamentCategory { switch tournament.tournamentCategory {
@ -230,16 +212,6 @@ final public class PlayerRegistration: BasePlayerRegistration, SideStorable {
registrationStatus = .confirmed registrationStatus = .confirmed
} }
public func hasMail() -> Bool {
let mails = [email, contactEmail].compactMap({ $0 })
return mails.isEmpty == false && mails.anySatisfy({ $0.isValidEmail() })
}
public func hasMobilePhone() -> Bool {
let phones = [phoneNumber, contactPhoneNumber].compactMap({ $0 })
return phones.isEmpty == false && phones.anySatisfy({ $0.isPhoneNumber() })
}
public func paidAmount(_ tournament: Tournament, accountForGiftOrForfeit: Bool = false) -> Double { public func paidAmount(_ tournament: Tournament, accountForGiftOrForfeit: Bool = false) -> Double {
if accountForGiftOrForfeit == false, paymentType == .gift { if accountForGiftOrForfeit == false, paymentType == .gift {
return 0.0 return 0.0

@ -56,30 +56,6 @@ final public class Round: BaseRound, SideStorable {
return tournamentStore.matches.filter { $0.round == self.id && $0.disabled == true } return tournamentStore.matches.filter { $0.round == self.id && $0.disabled == true }
} }
public func setData(from correspondingRound: Round, tournamentStartDate: Date, previousTournamentStartDate: Date) {
let matches = correspondingRound._matches()
for (index, match) in self._matches().enumerated() {
match.setData(from: matches[index], tournamentStartDate: tournamentStartDate, previousTournamentStartDate: previousTournamentStartDate)
}
self.matchFormat = correspondingRound.matchFormat
if let correspondingPlannedStartDate = correspondingRound.plannedStartDate {
let offset = correspondingPlannedStartDate.timeIntervalSince(previousTournamentStartDate)
self.startDate = tournamentStartDate.addingTimeInterval(offset)
}
self.loserBracketMode = correspondingRound.loserBracketMode
self.groupStageLoserBracket = correspondingRound.groupStageLoserBracket
loserRounds().forEach { round in
if let pRound = correspondingRound.loserRounds().first(where: { r in
r.index == round.index
}) {
round.setData(from: pRound, tournamentStartDate: tournamentStartDate, previousTournamentStartDate: previousTournamentStartDate)
}
}
}
// MARK: - // MARK: -
public var matchFormat: MatchFormat { public var matchFormat: MatchFormat {
@ -785,7 +761,10 @@ defer {
guard currentRoundMatchCount > 1 else { return } guard currentRoundMatchCount > 1 else { return }
guard let tournamentStore else { return } guard let tournamentStore else { return }
let roundCount = RoundRule.numberOfRounds(forTeams: currentRoundMatchCount) let roundCount = RoundRule.numberOfRounds(forTeams: currentRoundMatchCount)
let loserBracketMatchFormat = tournamentObject()?.loserBracketSmartMatchFormat() let loserBracketMatchFormat = tournamentObject()?.loserBracketMatchFormat
// if let parentRound {
// loserBracketMatchFormat = tournamentObject()?.loserBracketSmartMatchFormat(parentRound.index)
// }
var titles = [String: String]() var titles = [String: String]()
@ -912,17 +891,27 @@ defer {
groupStageLoserBracket == true groupStageLoserBracket == true
} }
public override func deleteDependencies(store: Store, actionOption: ActionOption) { public override func deleteDependencies(store: Store, shouldBeSynchronized: Bool) {
store.deleteDependencies(type: Match.self, actionOption: actionOption) { $0.round == self.id } store.deleteDependencies(type: Match.self, shouldBeSynchronized: shouldBeSynchronized) { $0.round == self.id }
store.deleteDependencies(type: Round.self, actionOption: actionOption) { $0.parent == self.id } store.deleteDependencies(type: Round.self, shouldBeSynchronized: shouldBeSynchronized) { $0.parent == self.id }
// let matches = self._matches()
// for match in matches {
// match.deleteDependencies(store: store, shouldBeSynchronized: shouldBeSynchronized)
// }
//
// self.tournamentStore?.matches.deleteDependencies(matches, shouldBeSynchronized: shouldBeSynchronized)
//
// let loserRounds = self.loserRounds()
// for round in loserRounds {
// round.deleteDependencies(store: store, shouldBeSynchronized: shouldBeSynchronized)
// }
//
// self.tournamentStore?.rounds.deleteDependencies(loserRounds, shouldBeSynchronized: shouldBeSynchronized)
} }
public override func deleteUnusedSharedDependencies(store: Store) {
store.deleteUnusedSharedDependencies(type: Match.self) { $0.round == self.id }
store.deleteUnusedSharedDependencies(type: Round.self) { $0.parent == self.id }
}
// enum CodingKeys: String, CodingKey { // enum CodingKeys: String, CodingKey {
// case _id = "id" // case _id = "id"

@ -50,10 +50,6 @@ final public class TeamRegistration: BaseTeamRegistration, SideStorable {
players().anySatisfy({ $0.registeredOnline }) players().anySatisfy({ $0.registeredOnline })
} }
public func hasPaid() -> Bool {
players().allSatisfy({ $0.hasPaid() })
}
public func hasPaidOnline() -> Bool { public func hasPaidOnline() -> Bool {
players().anySatisfy({ $0.hasPaidOnline() }) players().anySatisfy({ $0.hasPaidOnline() })
} }
@ -96,17 +92,23 @@ final public class TeamRegistration: BaseTeamRegistration, SideStorable {
let ts = tournamentStore.teamScores.filter({ $0.teamRegistration == id }) let ts = tournamentStore.teamScores.filter({ $0.teamRegistration == id })
tournamentStore.teamScores.delete(contentOfs: ts) tournamentStore.teamScores.delete(contentOfs: ts)
} }
public override func deleteUnusedSharedDependencies(store: Store) {
store.deleteUnusedSharedDependencies(type: TeamScore.self) { $0.teamRegistration == self.id }
store.deleteUnusedSharedDependencies(type: PlayerRegistration.self) { $0.teamRegistration == self.id }
}
public override func deleteDependencies(store: Store, actionOption: ActionOption) {
store.deleteDependencies(type: TeamScore.self, actionOption: actionOption) { $0.teamRegistration == self.id }
store.deleteDependencies(type: PlayerRegistration.self, actionOption: actionOption) { $0.teamRegistration == self.id }
public override func deleteDependencies(store: Store, shouldBeSynchronized: Bool) {
store.deleteDependencies(type: TeamScore.self, shouldBeSynchronized: shouldBeSynchronized) { $0.teamRegistration == self.id }
store.deleteDependencies(type: PlayerRegistration.self, shouldBeSynchronized: shouldBeSynchronized) { $0.teamRegistration == self.id }
// let unsortedPlayers = unsortedPlayers()
// for player in unsortedPlayers {
// player.deleteDependencies(store: store, shouldBeSynchronized: shouldBeSynchronized)
// }
// self.tournamentStore?.playerRegistrations.deleteDependencies(unsortedPlayers, shouldBeSynchronized: shouldBeSynchronized)
//
// let teamScores = teamScores()
// for teamScore in teamScores {
// teamScore.deleteDependencies(store: store, shouldBeSynchronized: shouldBeSynchronized)
// }
// self.tournamentStore?.teamScores.deleteDependencies(teamScores, shouldBeSynchronized: shouldBeSynchronized)
} }
public func hasArrived(isHere: Bool = false) { public func hasArrived(isHere: Bool = false) {
@ -171,7 +173,7 @@ final public class TeamRegistration: BaseTeamRegistration, SideStorable {
} else if let roundMatchStartDate = initialMatch()?.startDate { } else if let roundMatchStartDate = initialMatch()?.startDate {
return roundMatchStartDate return roundMatchStartDate
} }
return callDate return nil
} }
public var initialWeight: Int { public var initialWeight: Int {
@ -211,7 +213,7 @@ final public class TeamRegistration: BaseTeamRegistration, SideStorable {
} }
public func currentMatch() -> Match? { public func currentMatch() -> Match? {
return teamScores().compactMap { $0.matchValue() }.first(where: { $0.isRunning() }) return teamScores().compactMap { $0.matchObject() }.first(where: { $0.isRunning() })
} }
public func teamScores() -> [TeamScore] { public func teamScores() -> [TeamScore] {
@ -254,7 +256,7 @@ final public class TeamRegistration: BaseTeamRegistration, SideStorable {
public func teamLabel( public func teamLabel(
_ displayStyle: DisplayStyle = .wide, twoLines: Bool = false, separator: String = "&" _ displayStyle: DisplayStyle = .wide, twoLines: Bool = false, separator: String = "&"
) -> String { ) -> String {
if let name, name.isEmpty == false { return name } if let name { return name }
return players().map { $0.playerLabel(displayStyle) }.joined( return players().map { $0.playerLabel(displayStyle) }.joined(
separator: twoLines ? "\n" : " \(separator) ") separator: twoLines ? "\n" : " \(separator) ")
} }
@ -378,20 +380,14 @@ final public class TeamRegistration: BaseTeamRegistration, SideStorable {
resetBracketPosition() resetBracketPosition()
} }
public func pasteData(_ exportFormat: ExportFormat = .rawText, type: ExportType, _ index: Int = 0) -> String { public func pasteData(_ exportFormat: ExportFormat = .rawText, _ index: Int = 0) -> String {
switch exportFormat { switch exportFormat {
case .rawText: case .rawText:
switch type { return [playersPasteData(exportFormat), formattedInscriptionDate(exportFormat), name]
case .sharing: .compactMap({ $0 }).joined(separator: exportFormat.newLineSeparator())
return [playersPasteData(exportFormat, type: type), formattedInscriptionDate(exportFormat), name]
.compactMap({ $0 }).joined(separator: exportFormat.newLineSeparator())
case .payment:
return [playersPasteData(exportFormat, type: type), name]
.compactMap({ $0 }).joined(separator: exportFormat.newLineSeparator())
}
case .csv: case .csv:
return [ return [
index.formatted(), playersPasteData(exportFormat, type: type), index.formatted(), playersPasteData(exportFormat),
isWildCard() ? "WC" : weight.formatted(), isWildCard() ? "WC" : weight.formatted(),
].joined(separator: exportFormat.separator()) ].joined(separator: exportFormat.separator())
} }
@ -441,20 +437,15 @@ final public class TeamRegistration: BaseTeamRegistration, SideStorable {
} }
} }
public func playersPasteData(_ exportFormat: ExportFormat = .rawText, type: ExportType) -> String { public func playersPasteData(_ exportFormat: ExportFormat = .rawText) -> String {
switch exportFormat { switch exportFormat {
case .rawText: case .rawText:
return players().map { $0.pasteData(exportFormat, type: type) }.joined( return players().map { $0.pasteData(exportFormat) }.joined(
separator: exportFormat.newLineSeparator()) separator: exportFormat.newLineSeparator())
case .csv: case .csv:
return players().map { return players().map {
switch type { [$0.pasteData(exportFormat), isWildCard() ? "WC" : $0.computedRank.formatted()]
case .sharing: .joined(separator: exportFormat.separator())
[$0.pasteData(exportFormat, type: type), isWildCard() ? "WC" : $0.computedRank.formatted()]
.joined(separator: exportFormat.separator())
case .payment:
$0.pasteData(exportFormat, type: type)
}
}.joined(separator: exportFormat.separator()) }.joined(separator: exportFormat.separator())
} }
} }
@ -551,7 +542,7 @@ final public class TeamRegistration: BaseTeamRegistration, SideStorable {
} }
public func unrankValue(for malePlayer: Bool) -> Int { public func unrankValue(for malePlayer: Bool) -> Int {
return tournamentObject()?.unrankValue(for: malePlayer) ?? 92_327 return tournamentObject()?.unrankValue(for: malePlayer) ?? 90_415
} }
public func groupStageObject() -> GroupStage? { public func groupStageObject() -> GroupStage? {
@ -687,37 +678,6 @@ final public class TeamRegistration: BaseTeamRegistration, SideStorable {
return nil return nil
} }
public func followingMatches() -> [Match] {
guard let tournamentStore else { return [] }
let allTeamScores = tournamentStore.teamScores.filter({ $0.teamRegistration == self.id })
let ids = allTeamScores.compactMap({ $0.match })
let matches = tournamentStore.matches.filter({ match in
ids.contains(match.id)
})
return matches.sorted(by: \.computedStartDateForSorting)
}
public func nextMatch(in followingMatches: [Match]) -> Match? {
return followingMatches.filter({ $0.hasEnded() == false }).first
}
public func lastMatchPlayed(in followingMatches: [Match]) -> Match? {
return followingMatches.first(where: { $0.hasEnded() })
}
public func numberOfRotation(in followingMatches: [Match]) -> (Int, Int)? {
if let nextMatch = nextMatch(in: followingMatches), let nextMatchPlannedStartDate = nextMatch.plannedStartDate, let lastMatchPlayed = lastMatchPlayed(in: followingMatches), let lastMatchPlayedPlannedStartDate = lastMatchPlayed.plannedStartDate {
let courtCount = self.tournamentStore?.matches.filter({ $0.plannedStartDate == nextMatchPlannedStartDate && $0.disabled == false && $0.hasEnded() == false && $0.confirmed == true && $0.id != nextMatch.id && $0.hasStarted() == false }).count ?? 0
let interval = nextMatchPlannedStartDate.timeIntervalSince(lastMatchPlayedPlannedStartDate)
let matchDuration = lastMatchPlayed.matchFormat.defaultEstimatedDuration * 60
let rotation = Int(interval) / matchDuration
print("numberOfRotation", interval, matchDuration, courtCount, rotation)
return (rotation, (rotation - 1) * lastMatchPlayed.courtCount() + courtCount + 1)
}
return nil
}
func insertOnServer() { func insertOnServer() {
self.tournamentStore?.teamRegistrations.writeChangeAndInsertOnServer(instance: self) self.tournamentStore?.teamRegistrations.writeChangeAndInsertOnServer(instance: self)

@ -49,10 +49,10 @@ final public class TeamScore: BaseTeamScore, SideStorable {
// MARK: - Computed dependencies // MARK: - Computed dependencies
// public func matchObject() -> Match? { public func matchObject() -> Match? {
// return self.tournamentStore?.matches.findById(self.match) return self.tournamentStore?.matches.findById(self.match)
// } }
//
public var team: TeamRegistration? { public var team: TeamRegistration? {
guard let teamRegistration else { guard let teamRegistration else {
return nil return nil

@ -32,32 +32,16 @@ final public class Tournament: BaseTournament {
return TournamentLibrary.shared.store(tournamentId: self.id) return TournamentLibrary.shared.store(tournamentId: self.id)
} }
public override func deleteUnusedSharedDependencies(store: Store) { public override func deleteDependencies(store: Store, shouldBeSynchronized: Bool) {
do { do {
let tournamentStore = try store.alternateStore(identifier: self.id) let tournamentStore = try store.alternateStore(identifier: self.id)
tournamentStore.deleteUnusedSharedDependencies(type: DrawLog.self) tournamentStore.deleteAllDependencies(type: DrawLog.self, shouldBeSynchronized: shouldBeSynchronized)
tournamentStore.deleteUnusedSharedDependencies(type: TeamRegistration.self) tournamentStore.deleteAllDependencies(type: TeamRegistration.self, shouldBeSynchronized: shouldBeSynchronized)
tournamentStore.deleteUnusedSharedDependencies(type: GroupStage.self) tournamentStore.deleteAllDependencies(type: GroupStage.self, shouldBeSynchronized: shouldBeSynchronized)
tournamentStore.deleteUnusedSharedDependencies(type: Round.self) tournamentStore.deleteAllDependencies(type: Round.self, shouldBeSynchronized: shouldBeSynchronized)
} catch { tournamentStore.deleteAllDependencies(type: MatchScheduler.self, shouldBeSynchronized: shouldBeSynchronized)
Logger.error(error)
}
store.deleteUnusedSharedDependencies(type: Court.self) { $0.club == self.id }
}
public override func deleteDependencies(store: Store, actionOption: ActionOption) {
do {
let tournamentStore = try store.alternateStore(identifier: self.id)
tournamentStore.deleteAllDependencies(type: DrawLog.self, actionOption: actionOption)
tournamentStore.deleteAllDependencies(type: TeamRegistration.self, actionOption: actionOption)
tournamentStore.deleteAllDependencies(type: GroupStage.self, actionOption: actionOption)
tournamentStore.deleteAllDependencies(type: Round.self, actionOption: actionOption)
tournamentStore.deleteAllDependencies(type: MatchScheduler.self, actionOption: actionOption)
} catch { } catch {
Logger.error(error) Logger.error(error)
} }
@ -104,36 +88,11 @@ final public class Tournament: BaseTournament {
return self.tournamentStore?.teamRegistrations.count ?? 0 return self.tournamentStore?.teamRegistrations.count ?? 0
} }
public func deleteGroupStage(_ groupStage: GroupStage) {
groupStage.removeAllTeams()
let index = groupStage.index
self.tournamentStore?.groupStages.delete(instance: groupStage)
self.groupStageCount -= 1
let groupStages = self.groupStages()
groupStages.filter({ $0.index > index }).forEach { gs in
gs.index -= 1
}
self.tournamentStore?.groupStages.addOrUpdate(contentOfs: groupStages)
}
public func addGroupStage() {
let groupStage = GroupStage(tournament: id, index: groupStageCount, size: teamsPerGroupStage, format: groupStageFormat)
self.tournamentStore?.groupStages.addOrUpdate(instance: groupStage)
groupStage.buildMatches(keepExistingMatches: false)
self.groupStageCount += 1
}
public func groupStages(atStep step: Int = 0) -> [GroupStage] { public func groupStages(atStep step: Int = 0) -> [GroupStage] {
guard let tournamentStore = self.tournamentStore else { return [] } guard let tournamentStore = self.tournamentStore else { return [] }
let groupStages: [GroupStage] = tournamentStore.groupStages.filter { $0.step == step } let groupStages: [GroupStage] = tournamentStore.groupStages.filter { $0.tournament == self.id && $0.step == step }
return groupStages.sorted(by: \.index) return groupStages.sorted(by: \.index)
} }
public func hasGroupeStages() -> Bool {
if groupStageCount > 0 { return true }
guard let tournamentStore = self.tournamentStore else { return false }
return tournamentStore.groupStages.isEmpty == false
}
public func allGroupStages() -> [GroupStage] { public func allGroupStages() -> [GroupStage] {
guard let tournamentStore = self.tournamentStore else { return [] } guard let tournamentStore = self.tournamentStore else { return [] }
@ -277,24 +236,17 @@ defer {
return Store.main.findById(event) return Store.main.findById(event)
} }
public func pasteDataForImporting(_ exportFormat: ExportFormat = .rawText, type: ExportType) -> String { public func pasteDataForImporting(_ exportFormat: ExportFormat = .rawText) -> String {
let _selectedSortedTeams = selectedSortedTeams() let _selectedSortedTeams = selectedSortedTeams()
let selectedSortedTeams = _selectedSortedTeams + waitingListSortedTeams(selectedSortedTeams: _selectedSortedTeams) let selectedSortedTeams = _selectedSortedTeams + waitingListSortedTeams(selectedSortedTeams: _selectedSortedTeams)
switch exportFormat { switch exportFormat {
case .rawText: case .rawText:
let waitingList = waitingListTeams(in: selectedSortedTeams, includingWalkOuts: true) return (selectedSortedTeams.compactMap { $0.pasteData(exportFormat) } + ["Liste d'attente"] + waitingListTeams(in: selectedSortedTeams, includingWalkOuts: true).compactMap { $0.pasteData(exportFormat) }).joined(separator: exportFormat.newLineSeparator(2))
var stats = [String]()
if type == .payment, isAnimation(), minimumPlayerPerTeam == 1 {
stats += ["\(self.selectedPlayers().count.formatted()) personnes"]
} else {
stats += [selectedSortedTeams.count.formatted() + " équipes"]
}
return (stats + selectedSortedTeams.compactMap { $0.pasteData(exportFormat, type: type) } + (waitingList.isEmpty == false ? ["Liste d'attente"] : []) + waitingList.compactMap { $0.pasteData(exportFormat, type: type) }).joined(separator: exportFormat.newLineSeparator(1))
case .csv: case .csv:
let headers = ["", "Nom Prénom", "rang", "Nom Prénom", "rang", "poids", "Paire"].joined(separator: exportFormat.separator()) let headers = ["", "Nom Prénom", "rang", "Nom Prénom", "rang", "poids", "Paire"].joined(separator: exportFormat.separator())
var teamPaste = [headers] var teamPaste = [headers]
for (index, team) in selectedSortedTeams.enumerated() { for (index, team) in selectedSortedTeams.enumerated() {
var teamData = team.pasteData(exportFormat, type: type, index + 1) var teamData = team.pasteData(exportFormat, index + 1)
teamData.append(exportFormat.separator()) teamData.append(exportFormat.separator())
teamData.append(team.teamLastNames().joined(separator: " / ")) teamData.append(team.teamLastNames().joined(separator: " / "))
teamPaste.append(teamData) teamPaste.append(teamData)
@ -393,7 +345,6 @@ defer {
public func availableSeedOpponentSpot(inRoundIndex roundIndex: Int) -> [Match] { public func availableSeedOpponentSpot(inRoundIndex roundIndex: Int) -> [Match] {
return getRound(atRoundIndex: roundIndex)?.playedMatches().filter { $0.hasSpaceLeft() } ?? [] return getRound(atRoundIndex: roundIndex)?.playedMatches().filter { $0.hasSpaceLeft() } ?? []
} }
public func availableSeedGroups(includeAll: Bool = false) -> [SeedInterval] { public func availableSeedGroups(includeAll: Bool = false) -> [SeedInterval] {
let seeds = seeds() let seeds = seeds()
var availableSeedGroup = Set<SeedInterval>() var availableSeedGroup = Set<SeedInterval>()
@ -411,24 +362,6 @@ defer {
return availableSeedGroup.sorted(by: <) return availableSeedGroup.sorted(by: <)
} }
public func generateSeedGroups(base: Int, teamCount: Int) -> [SeedInterval] {
let start = base + 1
let root = SeedInterval(first: start, last: start + teamCount - 1)
var groups: [SeedInterval] = []
func split(interval: SeedInterval) {
groups.append(interval)
if let chunks = interval.chunks() {
for chunk in chunks {
split(interval: chunk)
}
}
}
split(interval: root)
return groups.sorted(by: <)
}
public func chunksBy(in chunks: [SeedInterval], availableSeedGroup: inout Set<SeedInterval>) { public func chunksBy(in chunks: [SeedInterval], availableSeedGroup: inout Set<SeedInterval>) {
chunks.forEach { chunk in chunks.forEach { chunk in
availableSeedGroup.insert(chunk) availableSeedGroup.insert(chunk)
@ -721,7 +654,7 @@ defer {
var groupStageTeamCount: Int = groupStageSpots - wcGroupStage.count var groupStageTeamCount: Int = groupStageSpots - wcGroupStage.count
if groupStageTeamCount < 0 { groupStageTeamCount = 0 } if groupStageTeamCount < 0 { groupStageTeamCount = 0 }
if bracketSeeds < 0 { bracketSeeds = 0 } if bracketSeeds < 0 { bracketSeeds = 0 }
let clubName = self.clubName
if prioritizeClubMembers { if prioritizeClubMembers {
var bracketTeams: [TeamRegistration] = [] var bracketTeams: [TeamRegistration] = []
@ -904,7 +837,7 @@ defer {
return allMatches.filter({ $0.isRunning() && $0.isReady() }).sorted(using: defaultSorting, order: .ascending) return allMatches.filter({ $0.isRunning() && $0.isReady() }).sorted(using: defaultSorting, order: .ascending)
} }
public static func readyMatches(_ allMatches: [Match], runningMatches: [Match]) -> [Match] { public static func readyMatches(_ allMatches: [Match]) -> [Match] {
#if _DEBUG_TIME //DEBUGING TIME #if _DEBUG_TIME //DEBUGING TIME
let start = Date() let start = Date()
defer { defer {
@ -912,10 +845,7 @@ defer {
print("func tournament readyMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) print("func tournament readyMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
} }
#endif #endif
return allMatches.filter({ $0.isReady() && $0.isRunning() == false && $0.hasEnded() == false }).sorted(using: defaultSorting, order: .ascending)
let playingTeams = runningMatches.flatMap({ $0.teams() }).map({ $0.id })
return allMatches.filter({ $0.isReady() && $0.isRunning() == false && $0.hasEnded() == false && $0.containsTeamIds(playingTeams) == false }).sorted(using: defaultSorting, order: .ascending)
} }
public static func matchesLeft(_ allMatches: [Match]) -> [Match] { public static func matchesLeft(_ allMatches: [Match]) -> [Match] {
@ -1179,10 +1109,10 @@ defer {
} }
public func tournamentTitle(_ displayStyle: DisplayStyle = .wide, hideSenior: Bool = false) -> String { public func tournamentTitle(_ displayStyle: DisplayStyle = .wide, hideSenior: Bool = false) -> String {
if tournamentLevel == .unlisted { if tournamentLevel == .unlisted, displayStyle == .title {
if let name { if let name {
return name return name
} else if displayStyle == .title { } else {
return tournamentLevel.localizedLevelLabel(.title) return tournamentLevel.localizedLevelLabel(.title)
} }
} }
@ -1222,7 +1152,14 @@ defer {
} }
public func formattedDate(_ displayStyle: DisplayStyle = .wide) -> String { public func formattedDate(_ displayStyle: DisplayStyle = .wide) -> String {
startDate.formattedDate(displayStyle) switch displayStyle {
case .title:
startDate.formatted(.dateTime.weekday(.abbreviated).day().month(.abbreviated).year())
case .wide:
startDate.formatted(date: Date.FormatStyle.DateStyle.complete, time: Date.FormatStyle.TimeStyle.omitted)
case .short:
startDate.formatted(date: .numeric, time: .omitted)
}
} }
public func qualifiedFromGroupStage() -> Int { public func qualifiedFromGroupStage() -> Int {
@ -1472,7 +1409,7 @@ defer {
var _groupStages = [GroupStage]() var _groupStages = [GroupStage]()
for index in 0..<groupStageCount { for index in 0..<groupStageCount {
let groupStage = GroupStage(tournament: id, index: index, size: teamsPerGroupStage, format: groupStageSmartMatchFormat()) let groupStage = GroupStage(tournament: id, index: index, size: teamsPerGroupStage, format: groupStageFormat)
_groupStages.append(groupStage) _groupStages.append(groupStage)
} }
@ -1491,7 +1428,7 @@ defer {
let matchCount = RoundRule.numberOfMatches(forTeams: minimalBracketTeamCount ?? bracketTeamCount()) let matchCount = RoundRule.numberOfMatches(forTeams: minimalBracketTeamCount ?? bracketTeamCount())
let rounds = (0..<roundCount).map { //index 0 is the final let rounds = (0..<roundCount).map { //index 0 is the final
return Round(tournament: id, index: $0, format: roundSmartMatchFormat($0), loserBracketMode: loserBracketMode) return Round(tournament: id, index: $0, format: matchFormat, loserBracketMode: loserBracketMode)
} }
if rounds.isEmpty { if rounds.isEmpty {
@ -1553,9 +1490,6 @@ defer {
public func deleteGroupStages() { public func deleteGroupStages() {
self.tournamentStore?.groupStages.delete(contentOfs: allGroupStages()) self.tournamentStore?.groupStages.delete(contentOfs: allGroupStages())
if let gs = self.groupStageLoserBracket() {
self.tournamentStore?.rounds.delete(instance: gs)
}
} }
public func refreshGroupStages(keepExistingMatches: Bool = false) { public func refreshGroupStages(keepExistingMatches: Bool = false) {
@ -1693,9 +1627,9 @@ defer {
set { set {
federalLevelCategory = newValue federalLevelCategory = newValue
teamSorting = newValue.defaultTeamSortingType teamSorting = newValue.defaultTeamSortingType
groupStageMatchFormat = DataStore.shared.user.groupStageMatchFormatPreference ?? groupStageSmartMatchFormat() groupStageMatchFormat = groupStageSmartMatchFormat()
loserBracketMatchFormat = DataStore.shared.user.loserBracketMatchFormatPreference ?? loserBracketSmartMatchFormat() loserBracketMatchFormat = loserBracketSmartMatchFormat(1)
matchFormat = DataStore.shared.user.bracketMatchFormatPreference ?? roundSmartMatchFormat(5) matchFormat = roundSmartMatchFormat(5)
} }
} }
@ -1708,8 +1642,8 @@ defer {
} }
} }
public func loserBracketSmartMatchFormat() -> MatchFormat { public func loserBracketSmartMatchFormat(_ roundIndex: Int) -> MatchFormat {
let format = tournamentLevel.federalFormatForLoserBracketRound() let format = tournamentLevel.federalFormatForLoserBracketRound(roundIndex)
if tournamentLevel == .p25 { return .superTie } if tournamentLevel == .p25 { return .superTie }
if format.rank < loserBracketMatchFormat.rank { if format.rank < loserBracketMatchFormat.rank {
return format return format
@ -1720,6 +1654,7 @@ defer {
public func groupStageSmartMatchFormat() -> MatchFormat { public func groupStageSmartMatchFormat() -> MatchFormat {
let format = tournamentLevel.federalFormatForGroupStage() let format = tournamentLevel.federalFormatForGroupStage()
if tournamentLevel == .p25 { return .superTie }
if format.rank < groupStageMatchFormat.rank { if format.rank < groupStageMatchFormat.rank {
return format return format
} else { } else {
@ -1728,21 +1663,19 @@ defer {
} }
public func initSettings(templateTournament: Tournament?, overrideTeamCount: Bool = true) { public func initSettings(templateTournament: Tournament?, overrideTeamCount: Bool = true) {
courtCount = eventObject()?.clubObject()?.courtCount ?? 2
setupDefaultPrivateSettings(templateTournament: templateTournament) setupDefaultPrivateSettings(templateTournament: templateTournament)
setupUmpireSettings(defaultTournament: nil) //default is not template, default is for event sharing settings setupUmpireSettings(defaultTournament: nil) //default is not template, default is for event sharing settings
if let templateTournament { if let templateTournament {
setupRegistrationSettings(templateTournament: templateTournament, overrideTeamCount: overrideTeamCount) setupRegistrationSettings(templateTournament: templateTournament, overrideTeamCount: overrideTeamCount)
} }
setupFederalSettings() setupFederalSettings()
customizeUsingPreferences()
} }
public func setupFederalSettings() { public func setupFederalSettings() {
teamSorting = tournamentLevel.defaultTeamSortingType teamSorting = tournamentLevel.defaultTeamSortingType
groupStageMatchFormat = DataStore.shared.user.groupStageMatchFormatPreference ?? groupStageSmartMatchFormat() groupStageMatchFormat = groupStageSmartMatchFormat()
loserBracketMatchFormat = DataStore.shared.user.loserBracketMatchFormatPreference ?? loserBracketSmartMatchFormat() loserBracketMatchFormat = loserBracketSmartMatchFormat(5)
matchFormat = DataStore.shared.user.bracketMatchFormatPreference ?? roundSmartMatchFormat(5) matchFormat = roundSmartMatchFormat(5)
entryFee = tournamentLevel.entryFee entryFee = tournamentLevel.entryFee
registrationDateLimit = deadline(for: .inscription) registrationDateLimit = deadline(for: .inscription)
if enableOnlineRegistration, isAnimation() == false { if enableOnlineRegistration, isAnimation() == false {
@ -1751,23 +1684,6 @@ defer {
} }
} }
public func customizeUsingPreferences() {
guard let lastTournamentWithSameBuild = DataStore.shared.tournaments.filter({ tournament in
tournament.tournamentLevel == self.tournamentLevel
&& tournament.tournamentCategory == self.tournamentCategory
&& tournament.federalTournamentAge == self.federalTournamentAge
&& tournament.hasEnded() == true
&& tournament.isCanceled == false
&& tournament.isDeleted == false
}).sorted(by: \.endDate!, order: .descending).first else {
return
}
self.entryFee = lastTournamentWithSameBuild.entryFee
self.clubMemberFeeDeduction = lastTournamentWithSameBuild.clubMemberFeeDeduction
}
public func deadline(for type: TournamentDeadlineType) -> Date? { public func deadline(for type: TournamentDeadlineType) -> Date? {
guard [.p500, .p1000, .p1500, .p2000].contains(tournamentLevel) else { return nil } guard [.p500, .p1000, .p1500, .p2000].contains(tournamentLevel) else { return nil }
@ -1857,6 +1773,7 @@ defer {
public func roundSmartMatchFormat(_ roundIndex: Int) -> MatchFormat { public func roundSmartMatchFormat(_ roundIndex: Int) -> MatchFormat {
let format = tournamentLevel.federalFormatForBracketRound(roundIndex) let format = tournamentLevel.federalFormatForBracketRound(roundIndex)
if tournamentLevel == .p25 { return .superTie }
if format.rank < matchFormat.rank { if format.rank < matchFormat.rank {
return format return format
} else { } else {
@ -2123,18 +2040,7 @@ defer {
} }
} }
} }
public func removeRound(_ round: Round) async {
await MainActor.run {
let teams = round.seeds()
teams.forEach { team in
team.resetBracketPosition()
}
tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: teams)
tournamentStore?.rounds.delete(instance: round)
}
}
public func addNewRound(_ roundIndex: Int) async { public func addNewRound(_ roundIndex: Int) async {
await MainActor.run { await MainActor.run {
let round = Round(tournament: id, index: roundIndex, format: matchFormat) let round = Round(tournament: id, index: roundIndex, format: matchFormat)
@ -2320,11 +2226,7 @@ defer {
} }
public func onlineTeams() -> [TeamRegistration] { public func onlineTeams() -> [TeamRegistration] {
// guard let teamRegistrations = tournamentStore?.teamRegistrations else { return [] } unsortedTeams().filter({ $0.hasRegisteredOnline() })
// return teamRegistrations.cached(key: "online") { collection in
// collection.filter { $0.hasRegisteredOnline() }
// }
return unsortedTeams().filter({ $0.hasRegisteredOnline() })
} }
public func paidOnlineTeams() -> [TeamRegistration] { public func paidOnlineTeams() -> [TeamRegistration] {
@ -2359,7 +2261,7 @@ defer {
} }
public func mailSubject() -> String { public func mailSubject() -> String {
let subject = [tournamentTitle(hideSenior: true), formattedDate(.short), customClubName ?? clubName].compactMap({ $0 }).joined(separator: " | ") let subject = [tournamentTitle(hideSenior: true), formattedDate(.short), clubName].compactMap({ $0 }).joined(separator: " | ")
return subject return subject
} }
@ -2464,9 +2366,6 @@ defer {
} }
public func addon(for playerRank: Int, manMax: Int, womanMax: Int) -> Int { public func addon(for playerRank: Int, manMax: Int, womanMax: Int) -> Int {
if tournamentCategory != .men {
return 0
}
switch playerRank { switch playerRank {
case 0: return 0 case 0: return 0
case womanMax: return manMax - womanMax case womanMax: return manMax - womanMax
@ -2506,16 +2405,6 @@ defer {
self.tournamentStore?.rounds.addOrUpdate(contentOfs: allRounds) self.tournamentStore?.rounds.addOrUpdate(contentOfs: allRounds)
} }
public func formatSummary() -> String {
var label = [String]()
if groupStageCount > 0 {
label.append("Poules " + groupStageMatchFormat.format)
}
label.append("Tableau " + matchFormat.format)
label.append("Classement " + loserBracketMatchFormat.format)
return label.joined(separator: ", ")
}
// MARK: - // MARK: -
func insertOnServer() throws { func insertOnServer() throws {

@ -20,15 +20,10 @@ public class TournamentLibrary {
if let store = self._stores[tournamentId] { if let store = self._stores[tournamentId] {
return store return store
} }
do { let store = StoreCenter.main.requestStore(identifier: tournamentId)
let store = try StoreCenter.main.store(identifier: tournamentId) let tournamentStore = TournamentStore(store: store)
let tournamentStore = TournamentStore(store: store, tournamentId: tournamentId) self._stores[tournamentId] = tournamentStore
self._stores[tournamentId] = tournamentStore return tournamentStore
return tournamentStore
} catch {
Logger.error(error)
return nil
}
} }
func reset() { func reset() {

@ -13,7 +13,6 @@ import Combine
public class TournamentStore: ObservableObject { public class TournamentStore: ObservableObject {
var store: Store var store: Store
let tournamentId: String
public fileprivate(set) var groupStages: SyncedCollection<GroupStage> = SyncedCollection.placeholder() public fileprivate(set) var groupStages: SyncedCollection<GroupStage> = SyncedCollection.placeholder()
public fileprivate(set) var matches: SyncedCollection<Match> = SyncedCollection.placeholder() public fileprivate(set) var matches: SyncedCollection<Match> = SyncedCollection.placeholder()
@ -31,18 +30,13 @@ public class TournamentStore: ObservableObject {
// self._initialize() // self._initialize()
// } // }
init(store: Store, tournamentId: String) { init(store: Store) {
self.store = store self.store = store
self.tournamentId = tournamentId
self._initialize() self._initialize()
} }
fileprivate func _initialize() { fileprivate func _initialize() {
guard let tournament = DataStore.shared.tournaments.findById(self.tournamentId) else {
return
}
let indexed: Bool = true let indexed: Bool = true
self.groupStages = self.store.registerSynchronizedCollection(indexed: indexed) self.groupStages = self.store.registerSynchronizedCollection(indexed: indexed)
@ -54,9 +48,7 @@ public class TournamentStore: ObservableObject {
self.matchSchedulers = self.store.registerCollection(indexed: indexed) self.matchSchedulers = self.store.registerCollection(indexed: indexed)
self.drawLogs = self.store.registerSynchronizedCollection(indexed: indexed) self.drawLogs = self.store.registerSynchronizedCollection(indexed: indexed)
if tournament.sharing == nil { self.store.loadCollectionsFromServerIfNoFile()
self.store.loadCollectionsFromServerIfNoFile()
}
NotificationCenter.default.addObserver( NotificationCenter.default.addObserver(
self, self,

@ -118,14 +118,6 @@ public extension Date {
} }
} }
var nextDay: Date {
return Calendar.current.date(byAdding: .day, value: 1, to: self)!
}
var weekDay: Int {
Calendar.current.component(.weekday, from: self)
}
func atBeginningOfDay(hourInt: Int = 9) -> Date { func atBeginningOfDay(hourInt: Int = 9) -> Date {
Calendar.current.date(byAdding: .hour, value: hourInt, to: self.startOfDay)! Calendar.current.date(byAdding: .hour, value: hourInt, to: self.startOfDay)!
} }
@ -152,28 +144,6 @@ public extension Date {
return weekdays.map { $0.capitalized } return weekdays.map { $0.capitalized }
}() }()
static var weekdays: [String] = {
let calendar = Calendar.current
// let weekdays = calendar.shortWeekdaySymbols
// return weekdays.map { weekday in
// guard let firstLetter = weekday.first else { return "" }
// return String(firstLetter).capitalized
// }
// Adjusted for the different weekday starts
var weekdays = calendar.weekdaySymbols
if firstDayOfWeek > 1 {
for _ in 1..<firstDayOfWeek {
if let first = weekdays.first {
weekdays.append(first)
weekdays.removeFirst()
}
}
}
return weekdays.map { $0.capitalized }
}()
static var fullMonthNames: [String] = { static var fullMonthNames: [String] = {
let dateFormatter = DateFormatter() let dateFormatter = DateFormatter()
dateFormatter.locale = Locale.current dateFormatter.locale = Locale.current
@ -194,11 +164,6 @@ public extension Date {
return Calendar.current.date(byAdding: .day, value: -1, to: lastDay)! return Calendar.current.date(byAdding: .day, value: -1, to: lastDay)!
} }
var endOfWeek: Date {
let lastDay = Calendar.current.dateInterval(of: .weekOfMonth, for: self)!.end
return Calendar.current.date(byAdding: .day, value: -1, to: lastDay)!
}
var startOfPreviousMonth: Date { var startOfPreviousMonth: Date {
let dayInPreviousMonth = Calendar.current.date(byAdding: .month, value: -1, to: self)! let dayInPreviousMonth = Calendar.current.date(byAdding: .month, value: -1, to: self)!
return dayInPreviousMonth.startOfMonth return dayInPreviousMonth.startOfMonth
@ -311,18 +276,6 @@ public extension Date {
let calendar = Calendar.current let calendar = Calendar.current
return calendar.date(bySetting: .minute, value: 0, of: self)!.withoutSeconds() return calendar.date(bySetting: .minute, value: 0, of: self)!.withoutSeconds()
} }
func formattedDate(_ displayStyle: DisplayStyle = .wide) -> String {
switch displayStyle {
case .title:
self.formatted(.dateTime.weekday(.abbreviated).day().month(.abbreviated).year())
case .wide:
self.formatted(date: Date.FormatStyle.DateStyle.complete, time: Date.FormatStyle.TimeStyle.omitted)
case .short:
self.formatted(date: .numeric, time: .omitted)
}
}
} }
public extension Date { public extension Date {

@ -213,96 +213,16 @@ public extension String {
// MARK: - FFT Source Importing // MARK: - FFT Source Importing
public extension String { public extension String {
enum RegexStatic { enum RegexStatic {
// Patterns for France only static let mobileNumber = /^(?:\+33|0033|0)[6-7](?:[ .-]?[0-9]{2}){4}$/
static let phoneNumber = /^(\+33|0033|33|0)[1-9][0-9]{8}$/ static let phoneNumber = /^(?:\+33|0033|0)[1-9](?:[ .-]?[0-9]{2}){4}$/
static let phoneNumberWithExtra0 = /^33[0][1-9][0-9]{8}$/
static let mobileNumber = /^(\+33|0033|33|0)[6-7][0-9]{8}$/
static let mobileNumberWithExtra0 = /^33[0][6-7][0-9]{8}$/
}
private func cleanedNumberForValidation() -> String {
// Keep leading '+' if present, remove all other non-digit characters
var cleaned = self.trimmingCharacters(in: .whitespacesAndNewlines)
if cleaned.hasPrefix("+") {
// Preserve '+' at start, remove all other non-digit characters
let digitsOnly = cleaned.dropFirst().components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
cleaned = "+" + digitsOnly
} else {
// Remove all non-digit characters
cleaned = cleaned.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
}
return cleaned
}
// MARK: - Phone Number Validation
/// Validate if the string is a mobile number for the specified locale.
/// - Parameter locale: The locale to validate against. Defaults to `.current`.
/// - Returns: True if the string matches the mobile number pattern for the locale.
func isMobileNumber(locale: Locale = .current) -> Bool {
// TODO: Support additional regions/locales in the future.
switch locale.region?.identifier {
case "FR", "fr", nil:
// French logic for now
let cleaned = cleanedNumberForValidation()
if cleaned.firstMatch(of: RegexStatic.mobileNumber) != nil {
return true
}
if cleaned.firstMatch(of: RegexStatic.mobileNumberWithExtra0) != nil {
return true
}
return false
default:
// For unsupported locales, fallback to checking if the string contains at least 8 digits
// This is a generic minimum length for most countries' phone numbers
let digitsOnly = self.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
return digitsOnly.count >= 8
}
} }
/// Validate if the string is a phone number for the specified locale. func isMobileNumber() -> Bool {
/// - Parameter locale: The locale to validate against. Defaults to `.current`. firstMatch(of: RegexStatic.mobileNumber) != nil
/// - Returns: True if the string matches the phone number pattern for the locale.
func isPhoneNumber(locale: Locale = .current) -> Bool {
// TODO: Support additional regions/locales in the future.
switch locale.region?.identifier {
case "FR", "fr", nil:
// French logic for now
let cleaned = cleanedNumberForValidation()
if cleaned.firstMatch(of: RegexStatic.phoneNumber) != nil {
return true
}
if cleaned.firstMatch(of: RegexStatic.phoneNumberWithExtra0) != nil {
return true
}
return false
default:
// For unsupported locales, fallback to checking if the string contains at least 8 digits
// This is a generic minimum length for most countries' phone numbers
let digitsOnly = self.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
return digitsOnly.count >= 8
}
}
func normalize(_ phone: String) -> String {
var normalized = phone.trimmingCharacters(in: .whitespacesAndNewlines)
// Remove all non-digit characters
normalized = normalized.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
// Remove leading country code for France (33) if present
if normalized.hasPrefix("33") {
if normalized.dropFirst(2).hasPrefix("0") {
// Keep as is, don't strip the zero after 33
} else {
normalized = "0" + normalized.dropFirst(2)
}
} else if normalized.hasPrefix("0033") {
normalized = "0" + normalized.dropFirst(4)
}
return normalized
} }
func isSamePhoneNumber(as other: String) -> Bool { func isPhoneNumber() -> Bool {
return normalize(self) == normalize(other) firstMatch(of: RegexStatic.phoneNumber) != nil
} }
func cleanSearchText() -> String { func cleanSearchText() -> String {
@ -392,4 +312,3 @@ public extension String {
return self // Return the original string if parsing fails return self // Return the original string if parsing fails
} }
} }

@ -30,7 +30,11 @@ import Combine
self.updateListenerTask = self.listenForTransactions() self.updateListenerTask = self.listenForTransactions()
Task { Task {
await self.refreshPurchases() do {
try await self.refreshPurchasedAppleProducts()
} catch {
Logger.error(error)
}
Logger.log("plan = \(String(describing: currentBestPurchase?.productId))") Logger.log("plan = \(String(describing: currentBestPurchase?.productId))")
} }
@ -44,7 +48,7 @@ import Combine
} }
@objc func collectionDidLoad(notification: Notification) { @objc func collectionDidLoad(notification: Notification) {
if let _ = notification.object as? StoredCollection<Purchase> { if let _ = notification.object as? BaseCollection<Purchase> {
self._updateBestPlan() self._updateBestPlan()
} }
} }
@ -62,45 +66,18 @@ import Combine
return productIds return productIds
} }
public func refreshPurchases() async { public func refreshPurchasedAppleProducts() async throws {
await _refreshUnfinishedTransactions()
await _refreshPurchasedAppleProducts()
}
fileprivate func _refreshPurchasedAppleProducts() async {
// Iterate through the user's purchased products. // Iterate through the user's purchased products.
for await verificationResult in Transaction.currentEntitlements { for await verificationResult in Transaction.currentEntitlements {
do { let transaction = try await self.processTransactionResult(verificationResult)
let transaction = try await self.processTransactionResult(verificationResult) print("processs product id = \(transaction.productID)")
print("processs product id = \(transaction.productID)") DispatchQueue.main.async {
DispatchQueue.main.async { NotificationCenter.default.post(name: Notification.Name.StoreEventHappened, object: nil)
NotificationCenter.default.post(name: Notification.Name.StoreEventHappened, object: nil)
}
await transaction.finish()
} catch {
Logger.error(error)
}
}
}
public func _refreshUnfinishedTransactions() async {
// Iterate through the user's purchased products.
for await verificationResult in Transaction.unfinished {
do {
let transaction = try await self.processTransactionResult(verificationResult)
print("processs product id = \(transaction.productID)")
DispatchQueue.main.async {
NotificationCenter.default.post(name: Notification.Name.StoreEventHappened, object: nil)
}
await transaction.finish()
} catch {
Logger.error(error)
} }
await transaction.finish()
} }
} }
func listenForTransactions() -> Task<Void, Never> { func listenForTransactions() -> Task<Void, Never> {
return Task(priority: .background) { return Task(priority: .background) {
@ -242,7 +219,7 @@ import Combine
purchases.append(contentsOf: userPurchases) purchases.append(contentsOf: userPurchases)
let validPurchases = DataStore.shared.purchases.filter { $0.isValid() } let validPurchases = DataStore.shared.purchases.filter { $0.isValid() }
// Logger.log("valid purchases = \(validPurchases.count)") Logger.log("valid purchases = \(validPurchases.count)")
purchases.append(contentsOf: validPurchases) purchases.append(contentsOf: validPurchases)
if let purchase = purchases.first(where: { $0.productId == StoreItem.monthlyUnlimited.rawValue }) { if let purchase = purchases.first(where: { $0.productId == StoreItem.monthlyUnlimited.rawValue }) {
@ -266,26 +243,7 @@ import Combine
// return units.reduce(0) { $0 + $1.purchasedQuantity } // return units.reduce(0) { $0 + $1.purchasedQuantity }
} }
struct CanCreateResponse: Decodable { var canCreate: Bool } public func paymentForNewTournament() -> TournamentPayment? {
public func paymentForNewTournament() async -> TournamentPayment? {
if let payment = self.localPaymentForNewTournament() {
return payment
} else if let services = try? StoreCenter.main.service() {
do {
let response: CanCreateResponse = try await services.run(path: "is_granted_unlimited_access/", method: .get, requiresToken: true)
if response.canCreate {
return .unlimited
}
} catch {
Logger.error(error)
}
}
return nil
}
public func localPaymentForNewTournament() -> TournamentPayment? {
switch self.currentPlan { switch self.currentPlan {
case .monthlyUnlimited: case .monthlyUnlimited:

@ -71,8 +71,10 @@ public class StoreManager {
fileprivate func _productIdentifiers() -> [String] { fileprivate func _productIdentifiers() -> [String] {
var items: [StoreItem] = [] var items: [StoreItem] = []
switch Guard.main.currentPlan { switch Guard.main.currentPlan {
case .fivePerMonth:
items = [StoreItem.unit, StoreItem.unit10Pack, StoreItem.monthlyUnlimited]
case .monthlyUnlimited: case .monthlyUnlimited:
items = [StoreItem.unit, StoreItem.unit10Pack] break
default: default:
items = [StoreItem.unit, StoreItem.unit10Pack, StoreItem.monthlyUnlimited] items = [StoreItem.unit, StoreItem.unit10Pack, StoreItem.monthlyUnlimited]
} }

@ -55,89 +55,6 @@ public enum ContactManagerError: LocalizedError {
} }
} }
public enum SummonType: Int, Identifiable {
case contact
case contactWithoutSignature
case summon
case summonWalkoutFollowUp
case summonErrorFollowUp
public func isRecall() -> Bool {
switch self {
case .contact, .contactWithoutSignature:
return false
case .summon:
return false
case .summonWalkoutFollowUp:
return true
case .summonErrorFollowUp:
return true
}
}
public func mainWord() -> String {
switch self {
case .contact:
return "Contacter"
case .contactWithoutSignature:
return "Contacter"
case .summon:
return "Convoquer"
case .summonWalkoutFollowUp:
return "Reconvoquer"
case .summonErrorFollowUp:
return "Reconvoquer"
}
}
public func caption() -> String? {
switch self {
case .contact:
return nil
case .contactWithoutSignature:
return "Sans texte par défaut"
case .summon:
return nil
case .summonWalkoutFollowUp:
return "Suite à un forfait"
case .summonErrorFollowUp:
return "Suite à une erreur"
}
}
public func shouldConfirm() -> Bool {
switch self {
case .contact, .contactWithoutSignature:
return false
case .summon:
return true
case .summonWalkoutFollowUp:
return true
case .summonErrorFollowUp:
return true
}
}
public func intro() -> String {
switch self {
case .contactWithoutSignature:
return ""
case .contact:
return "Vous êtes"
case .summon:
return "Vous êtes"
case .summonWalkoutFollowUp:
return "Suite à des forfaits, vous êtes finalement"
case .summonErrorFollowUp:
return "Suite à une erreur, vous êtes finalement"
}
}
public var id: Int {
self.rawValue
}
}
public enum ContactType: Identifiable { public enum ContactType: Identifiable {
case mail(date: Date?, recipients: [String]?, bccRecipients: [String]?, body: String?, subject: String?, tournamentBuild: TournamentBuild?) case mail(date: Date?, recipients: [String]?, bccRecipients: [String]?, body: String?, subject: String?, tournamentBuild: TournamentBuild?)
case message(date: Date?, recipients: [String]?, body: String?, tournamentBuild: TournamentBuild?) case message(date: Date?, recipients: [String]?, body: String?, tournamentBuild: TournamentBuild?)
@ -159,7 +76,7 @@ Il est conseillé de vous présenter 10 minutes avant de jouer.\n\nMerci de me c
static func callingCustomMessage(source: String? = nil, tournament: Tournament?, startDate: Date?, roundLabel: String) -> String { static func callingCustomMessage(source: String? = nil, tournament: Tournament?, startDate: Date?, roundLabel: String) -> String {
let tournamentCustomMessage = source ?? DataStore.shared.user.summonsMessageBody ?? defaultCustomMessage let tournamentCustomMessage = source ?? DataStore.shared.user.summonsMessageBody ?? defaultCustomMessage
let clubName = tournament?.customClubName ?? tournament?.clubName ?? "" let clubName = tournament?.clubName ?? ""
var text = tournamentCustomMessage var text = tournamentCustomMessage
let date = startDate ?? tournament?.startDate ?? Date() let date = startDate ?? tournament?.startDate ?? Date()
@ -180,10 +97,8 @@ Il est conseillé de vous présenter 10 minutes avant de jouer.\n\nMerci de me c
return text return text
} }
static func callingMessage(tournament: Tournament?, startDate: Date?, roundLabel: String, matchFormat: MatchFormat?, summonType: SummonType = .summon) -> String { static func callingMessage(tournament: Tournament?, startDate: Date?, roundLabel: String, matchFormat: MatchFormat?, reSummon: Bool = false) -> String {
if summonType == .contactWithoutSignature {
return ""
}
let useFullCustomMessage = DataStore.shared.user.summonsUseFullCustomMessage let useFullCustomMessage = DataStore.shared.user.summonsUseFullCustomMessage
if useFullCustomMessage { if useFullCustomMessage {
@ -192,7 +107,7 @@ Il est conseillé de vous présenter 10 minutes avant de jouer.\n\nMerci de me c
let date = startDate ?? tournament?.startDate ?? Date() let date = startDate ?? tournament?.startDate ?? Date()
let clubName = tournament?.customClubName ?? tournament?.clubName ?? "" let clubName = tournament?.clubName ?? ""
let message = DataStore.shared.user.summonsMessageBody ?? defaultCustomMessage let message = DataStore.shared.user.summonsMessageBody ?? defaultCustomMessage
let signature = DataStore.shared.user.getSummonsMessageSignature() ?? DataStore.shared.user.defaultSignature(tournament) let signature = DataStore.shared.user.getSummonsMessageSignature() ?? DataStore.shared.user.defaultSignature(tournament)
@ -214,7 +129,7 @@ Il est conseillé de vous présenter 10 minutes avant de jouer.\n\nMerci de me c
[entryFeeMessage, message, linkMessage].compacted().map { $0.trimmedMultiline }.joined(separator: "\n\n") [entryFeeMessage, message, linkMessage].compacted().map { $0.trimmedMultiline }.joined(separator: "\n\n")
} }
let intro = summonType.intro() let intro = reSummon ? "Suite à des forfaits, vous êtes finalement" : "Vous êtes"
if let tournament { if let tournament {
return "Bonjour,\n\n\(intro) \(localizedCalled) pour jouer en \(roundLabel.lowercased()) du \(tournament.tournamentTitle(.title, hideSenior: true)) au \(clubName) le \(date.formatted(Date.FormatStyle().weekday(.wide).day().month(.wide))) à \(date.formatted(Date.FormatStyle().hour().minute())).\n\n" + computedMessage + "\n\n\(signature)" return "Bonjour,\n\n\(intro) \(localizedCalled) pour jouer en \(roundLabel.lowercased()) du \(tournament.tournamentTitle(.title, hideSenior: true)) au \(clubName) le \(date.formatted(Date.FormatStyle().weekday(.wide).day().month(.wide))) à \(date.formatted(Date.FormatStyle().hour().minute())).\n\n" + computedMessage + "\n\n\(signature)"

@ -35,23 +35,3 @@ public enum ExportFormat: Int, Identifiable, CaseIterable {
return Array(repeating: "\n", count: count).joined() return Array(repeating: "\n", count: count).joined()
} }
} }
public enum ExportType: Int, Identifiable, CaseIterable {
public var id: Int { self.rawValue }
case sharing
case payment
public func localizedString() -> String {
switch self {
case .sharing:
return "inscriptions"
case .payment:
return "pointage"
}
}
public func newLineSeparator(_ count: Int = 1) -> String {
return Array(repeating: "\n", count: count).joined()
}
}

@ -10,11 +10,7 @@ import Foundation
public class PListReader { public class PListReader {
static func dictionary(plist: String) -> [String: Any]? { static func dictionary(plist: String) -> [String: Any]? {
return self.dictionary(plist: plist, bundle: Bundle.main) if let plistPath = Bundle.main.path(forResource: plist, ofType: "plist") {
}
static func dictionary(plist: String, bundle: Bundle) -> [String: Any]? {
if let plistPath = bundle.path(forResource: plist, ofType: "plist") {
// Read plist file into Data // Read plist file into Data
if let plistData = FileManager.default.contents(atPath: plistPath) { if let plistData = FileManager.default.contents(atPath: plistPath) {
do { do {
@ -29,21 +25,21 @@ public class PListReader {
print("Failed to read plist file at path: \(plistPath)") print("Failed to read plist file at path: \(plistPath)")
} }
} else { } else {
print("Plist file '\(plist)' not found in bundle") print("Plist file 'Data.plist' not found in bundle")
} }
return nil return nil
} }
public static func readString(plist: String, key: String, bundle: Bundle = Bundle.main) -> String? { public static func readString(plist: String, key: String) -> String? {
if let dictionary = self.dictionary(plist: plist, bundle: bundle) { if let dictionary = self.dictionary(plist: plist) {
return dictionary[key] as? String return dictionary[key] as? String
} }
return nil return nil
} }
public static func readBool(plist: String, key: String, bundle: Bundle = Bundle.main) -> Bool? { public static func readBool(plist: String, key: String) -> Bool? {
if let dictionary = self.dictionary(plist: plist, bundle: bundle) { if let dictionary = self.dictionary(plist: plist) {
return dictionary[key] as? Bool return dictionary[key] as? Bool
} }
return nil return nil

@ -25,7 +25,7 @@ enum RankSource: Hashable {
} }
} }
public protocol TournamentBuildHolder: Identifiable, Hashable, Equatable { public protocol TournamentBuildHolder: Identifiable {
var id: String { get } var id: String { get }
var category: TournamentCategory { get } var category: TournamentCategory { get }
var level: TournamentLevel { get } var level: TournamentLevel { get }
@ -349,9 +349,7 @@ public enum FederalTournamentAge: Int, Hashable, Codable, CaseIterable, Identifi
return 4 return 4
} else { } else {
switch level { switch level {
case .p25: case .p25, .p100, .p250:
return 4
case .p100, .p250:
if category == .women { if category == .women {
return 4 return 4
} }
@ -552,7 +550,7 @@ public enum TournamentLevel: Int, Hashable, Codable, CaseIterable, Identifiable
} }
case .p1500, .p2000: case .p1500, .p2000:
if roundIndex <= 3 { //demi / finale / quart / 8eme if roundIndex <= 3 { //demi / finale / quart / 8eme
return .twoSets return .twoSetsDecisivePoint
} else { } else {
return .twoSetsSuperTie return .twoSetsSuperTie
} }
@ -570,7 +568,7 @@ public enum TournamentLevel: Int, Hashable, Codable, CaseIterable, Identifiable
} }
} }
public func federalFormatForLoserBracketRound() -> MatchFormat { public func federalFormatForLoserBracketRound(_ roundIndex: Int) -> MatchFormat {
switch self { switch self {
case .p25: case .p25:
return .superTie return .superTie
@ -712,7 +710,7 @@ public enum TournamentLevel: Int, Hashable, Codable, CaseIterable, Identifiable
case 13...16: case 13...16:
return [175,150,138,113,100,88,75,63,53,45,38,25,13,8,3] return [175,150,138,113,100,88,75,63,53,45,38,25,13,8,3]
case 17...20: case 17...20:
return [188,163,150,138,125,113,100,88,75,63,58,50,45,38,30,25,13,8,3] return [188,163,150,138,123,113,100,88,75,63,58,50,45,38,30,25,13,8,3]
case 21...24: case 21...24:
return [188,175,163,150,138,125,118,108,100,93,83,75,70,63,58,50,45,38,30,25,13,8,3] return [188,175,163,150,138,125,118,108,100,93,83,75,70,63,58,50,45,38,30,25,13,8,3]
case 25...28: case 25...28:
@ -1201,7 +1199,6 @@ public enum TeamPosition: Int, Identifiable, Hashable, Codable, CaseIterable {
public enum SetFormat: Int, Hashable, Codable { public enum SetFormat: Int, Hashable, Codable {
case nine case nine
case four case four
case three
case six case six
case superTieBreak case superTieBreak
case megaTieBreak case megaTieBreak
@ -1228,10 +1225,6 @@ public enum SetFormat: Int, Hashable, Codable {
if teamOne == 5 || teamTwo == 5 { if teamOne == 5 || teamTwo == 5 {
return true return true
} }
case .three:
if teamOne == 4 || teamTwo == 4 {
return true
}
case .six: case .six:
if teamOne == 7 || teamTwo == 7 { if teamOne == 7 || teamTwo == 7 {
return true return true
@ -1250,8 +1243,6 @@ public enum SetFormat: Int, Hashable, Codable {
return 8 return 8
case .four: case .four:
return 4 return 4
case .three:
return 3
case .six: case .six:
return 6 return 6
case .superTieBreak, .megaTieBreak: case .superTieBreak, .megaTieBreak:
@ -1269,10 +1260,6 @@ public enum SetFormat: Int, Hashable, Codable {
if teamOneScore == 4 { if teamOneScore == 4 {
return [] return []
} }
case .three:
if teamOneScore == 3 {
return []
}
case .six: case .six:
if teamOneScore == 6 { if teamOneScore == 6 {
return [] return []
@ -1295,8 +1282,6 @@ public enum SetFormat: Int, Hashable, Codable {
return [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] return [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
case .four: case .four:
return [5, 4, 3, 2, 1, 0] return [5, 4, 3, 2, 1, 0]
case .three:
return [4, 3, 2, 1, 0]
case .six: case .six:
return [7, 6, 5, 4, 3, 2, 1, 0] return [7, 6, 5, 4, 3, 2, 1, 0]
case .superTieBreak: case .superTieBreak:
@ -1312,8 +1297,6 @@ public enum SetFormat: Int, Hashable, Codable {
return 9 return 9
case .four: case .four:
return 4 return 4
case .three:
return 4
case .six: case .six:
return 6 return 6
case .superTieBreak: case .superTieBreak:
@ -1381,15 +1364,15 @@ public enum MatchFormat: Int, Hashable, Codable, CaseIterable, Identifiable {
return 2 return 2
case .nineGames, .nineGamesDecisivePoint: case .nineGames, .nineGamesDecisivePoint:
return 3 return 3
case .singleSetOfFourGames, .singleSetOfFourGamesDecisivePoint:
return 4
case .superTie: case .superTie:
return 5 return 4
case .megaTie: case .megaTie:
return 6 return 5
case .twoSetsOfSuperTie: case .twoSetsOfSuperTie:
return 7 return 6
case .singleSet, .singleSetDecisivePoint: case .singleSet, .singleSetDecisivePoint:
return 7
case .singleSetOfFourGames, .singleSetOfFourGamesDecisivePoint:
return 8 return 8
} }
} }
@ -1412,15 +1395,15 @@ public enum MatchFormat: Int, Hashable, Codable, CaseIterable, Identifiable {
return 3 return 3
case .nineGamesDecisivePoint: case .nineGamesDecisivePoint:
return 3 return 3
case .singleSetOfFourGames, .singleSetOfFourGamesDecisivePoint:
return 4
case .superTie: case .superTie:
return 5 return 4
case .megaTie: case .megaTie:
return 6 return 5
case .twoSetsOfSuperTie: case .twoSetsOfSuperTie:
return 7 return 6
case .singleSet, .singleSetDecisivePoint: case .singleSet, .singleSetDecisivePoint:
return 7
case .singleSetOfFourGames, .singleSetOfFourGamesDecisivePoint:
return 8 return 8
} }
} }
@ -1658,7 +1641,7 @@ public enum MatchFormat: Int, Hashable, Codable, CaseIterable, Identifiable {
case .megaTie: case .megaTie:
return "supertie de 15 points" return "supertie de 15 points"
case .singleSetOfFourGames, .singleSetOfFourGamesDecisivePoint: case .singleSetOfFourGames, .singleSetOfFourGamesDecisivePoint:
return "1 set de 4 jeux, tiebreak à 3/3" return "1 set de 4 jeux, tiebreak à 4/4"
} }
} }
@ -1687,10 +1670,8 @@ public enum MatchFormat: Int, Hashable, Codable, CaseIterable, Identifiable {
switch self { switch self {
case .twoSets, .twoSetsSuperTie, .twoSetsDecisivePoint, .twoSetsDecisivePointSuperTie, .singleSet, .singleSetDecisivePoint: case .twoSets, .twoSetsSuperTie, .twoSetsDecisivePoint, .twoSetsDecisivePointSuperTie, .singleSet, .singleSetDecisivePoint:
return .six return .six
case .twoSetsOfFourGames, .twoSetsOfFourGamesDecisivePoint: case .twoSetsOfFourGames, .twoSetsOfFourGamesDecisivePoint, .singleSetOfFourGames, .singleSetOfFourGamesDecisivePoint:
return .four return .four
case .singleSetOfFourGames, .singleSetOfFourGamesDecisivePoint:
return .three
case .nineGames, .nineGamesDecisivePoint: case .nineGames, .nineGamesDecisivePoint:
return .nine return .nine
case .superTie, .twoSetsOfSuperTie: case .superTie, .twoSetsOfSuperTie:
@ -1869,16 +1850,6 @@ public enum RoundRule {
} }
} }
public static func cumulatedNumberOfMatches(forTeams teams: Int) -> Int {
var i = teams / 2
var loserTeams = teams / 2
while loserTeams > 1 {
i += Self.cumulatedNumberOfMatches(forTeams: loserTeams)
loserTeams = loserTeams / 2
}
return i
}
public static func teamsInFirstRound(forTeams teams: Int) -> Int { public static func teamsInFirstRound(forTeams teams: Int) -> Int {
Int(pow(2.0, ceil(log2(Double(teams))))) Int(pow(2.0, ceil(log2(Double(teams)))))
} }
@ -1909,16 +1880,12 @@ public enum RoundRule {
} }
public static func numberOfMatches(forRoundIndex roundIndex: Int) -> Int { public static func numberOfMatches(forRoundIndex roundIndex: Int) -> Int {
(1 << roundIndex) Int(pow(2.0, Double(roundIndex)))
}
public static func baseIndex(forRoundIndex roundIndex: Int) -> Int {
numberOfMatches(forRoundIndex: roundIndex) - 1
} }
static func matchIndexWithinRound(fromMatchIndex matchIndex: Int) -> Int { static func matchIndexWithinRound(fromMatchIndex matchIndex: Int) -> Int {
let roundIndex = roundIndex(fromMatchIndex: matchIndex) let roundIndex = roundIndex(fromMatchIndex: matchIndex)
let matchIndexWithinRound = matchIndex - baseIndex(forRoundIndex: roundIndex) let matchIndexWithinRound = matchIndex - (Int(pow(2.0, Double(roundIndex))) - 1)
return matchIndexWithinRound return matchIndexWithinRound
} }
@ -1942,7 +1909,7 @@ public enum RoundRule {
} }
return "Quart de finale" return "Quart de finale"
default: default:
return "\((1 << roundIndex))ème" return "\(Int(pow(2.0, Double(roundIndex))))ème"
} }
} }
} }

@ -1,34 +0,0 @@
//
// Config.swift
// PadelClubData
//
// Created by Laurent Morvillier on 21/05/2025.
//
@testable import PadelClubData
class Config {
var secure: Bool
var domain: String
init(secure: Bool, domain: String) {
self.secure = secure
self.domain = domain
}
static var server: Config {
let bundle = Bundle(for: self)
let secure = PListReader.readBool(plist: "config", key: "secure_server", bundle: bundle)
let domain = PListReader.readString(plist: "config", key: "server_domain", bundle: bundle)
if let secure, let domain {
return Config(secure: secure, domain: domain)
}
fatalError("no server configuration")
}
}

@ -18,14 +18,11 @@ struct DeletionTests {
init() async throws { init() async throws {
let conf = Config.server
FileManager.default.deleteDirectoryInDocuments(directoryName: "storage") FileManager.default.deleteDirectoryInDocuments(directoryName: "storage")
FileManager.default.deleteDirectoryInDocuments(directoryName: "storage-2") FileManager.default.deleteDirectoryInDocuments(directoryName: "storage-2")
self.secondStoreCenter = StoreCenter(directoryName: "storage-2") self.secondStoreCenter = StoreCenter(directoryName: "storage-2")
self.secondStoreCenter.configureURLs(secureScheme: false, domain: "127.0.0.1:8000", webSockets: false, useSynchronization: true)
self.secondStoreCenter.configureURLs(secureScheme: conf.secure, domain: conf.domain, webSockets: false, useSynchronization: true)
self.secondStoreCenter.tokenKeychain = MockKeychainStore(fileName: "storage-2/token.json") self.secondStoreCenter.tokenKeychain = MockKeychainStore(fileName: "storage-2/token.json")
self.secondStoreCenter.deviceKeychain = MockKeychainStore(fileName: "storage-2/device.json") self.secondStoreCenter.deviceKeychain = MockKeychainStore(fileName: "storage-2/device.json")
try self.secondStoreCenter.deviceKeychain.add(value: UUID().uuidString) try self.secondStoreCenter.deviceKeychain.add(value: UUID().uuidString)
@ -37,7 +34,7 @@ struct DeletionTests {
try await self.login(storeCenter: self.secondStoreCenter, username: self.username1, password: self.password1) try await self.login(storeCenter: self.secondStoreCenter, username: self.username1, password: self.password1)
} }
StoreCenter.main.configureURLs(secureScheme: conf.secure, domain: conf.domain, webSockets: false, useSynchronization: true) StoreCenter.main.configureURLs(secureScheme: false, domain: "127.0.0.1:8000", webSockets: false, useSynchronization: true)
StoreCenter.main.tokenKeychain = MockKeychainStore(fileName: "storage/token.json") StoreCenter.main.tokenKeychain = MockKeychainStore(fileName: "storage/token.json")
StoreCenter.main.deviceKeychain = MockKeychainStore(fileName: "storage/device.json") StoreCenter.main.deviceKeychain = MockKeychainStore(fileName: "storage/device.json")
try StoreCenter.main.deviceKeychain.add(value: UUID().uuidString) try StoreCenter.main.deviceKeychain.add(value: UUID().uuidString)
@ -250,20 +247,12 @@ struct DeletionTests {
#expect(clubColB.count == 1) #expect(clubColB.count == 1)
#expect(eventColB.count == 0) #expect(eventColB.count == 0)
#expect(tournamentColB.count == 0) #expect(tournamentColB.count == 0)
#expect(groupStageColB.count == 0)
do { #expect(roundColB.count == 0)
let _ = try self.secondStoreCenter.store(identifier: tournament.id) #expect(matchColB.count == 0)
Issue.record("should go in the catch because the store has been destroyed") #expect(teamRegistrationColB.count == 0)
} catch { #expect(teamScoreColB.count == 0)
#expect(1 == 1) #expect(playerRegistrationColB.count == 0)
}
// #expect(groupStageColB.count == 0)
// #expect(roundColB.count == 0)
// #expect(matchColB.count == 0)
// #expect(teamRegistrationColB.count == 0)
// #expect(teamScoreColB.count == 0)
// #expect(playerRegistrationColB.count == 0)
} }
} }
@ -290,10 +279,10 @@ actor BoolChecker {
return return
} }
// print("sleep...") print("sleep...")
// Wait for 100ms before next check // Wait for 100ms before next check
try? await Task.sleep(for: .milliseconds(1000)) try? await Task.sleep(for: .milliseconds(100))
} }
// Throw error if timeout is reached // Throw error if timeout is reached

@ -1,101 +0,0 @@
//
// PadelClubDataTests.swift
// PadelClubDataTests
//
// Created by Laurent Morvillier on 15/04/2025.
//
import Testing
@testable import PadelClubData
@testable import LeStorage
enum TestError: Error {
case notAuthenticated
case sameDeviceId
case missingEvent
case missingTournament
}
struct PadelClubDataTests {
let username: String = "UserDataTests"
let password: String = "MyPass1234--"
init() async throws {
let conf = Config.server
StoreCenter.main.configureURLs(secureScheme: conf.secure, domain: conf.domain)
StoreCenter.main.tokenKeychain = MockKeychainStore(fileName: "token.json")
try await self.login()
}
mutating func login() async throws {
let _: CustomUser = try await StoreCenter.main.service().login(username: self.username, password: self.password)
}
@Test func testAuthentication() {
#expect(StoreCenter.main.isAuthenticated)
}
@Test func createTournament() async throws {
guard let userId = StoreCenter.main.userId else {
throw TestError.notAuthenticated
}
// Cleanup
let eventCol: SyncedCollection<Event> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection()
// let events: [Event] = try await StoreCenter.main.service().get()
try await eventCol.deleteAsync(contentOfs: Array(eventCol))
#expect(eventCol.count == 0)
let tournamentCol: SyncedCollection<Tournament> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection()
// let tournaments: [Tournament] = try await StoreCenter.main.service().get()
try await tournamentCol.deleteAsync(contentOfs: Array(tournamentCol))
#expect(tournamentCol.count == 0)
// Create
let event: Event = Event(creator: userId, club: nil, name: "test_createTournament")
try await eventCol.addOrUpdateAsync(instance: event)
let tournament: Tournament = Tournament.fake()
tournament.event = event.id
try await tournamentCol.addOrUpdateAsync(instance: tournament)
// Test server content
try await eventCol.loadOnceAsync()
#expect(eventCol.count == 1)
try await tournamentCol.loadOnceAsync()
#expect(tournamentCol.count == 1)
}
@Test func dualStoreCenter() async throws {
let conf = Config.server
let secondStoreServer = StoreCenter()
secondStoreServer.configureURLs(secureScheme: conf.secure, domain: conf.domain)
secondStoreServer.tokenKeychain = MockKeychainStore(fileName: "token.json")
let _: CustomUser = try await secondStoreServer.service().login(username: self.username, password: self.password)
#expect(StoreCenter.main.isAuthenticated)
#expect(secondStoreServer.isAuthenticated)
}
@Test func testWebsocketSynchronization() async throws {
let secondStoreServer = StoreCenter()
secondStoreServer.configureURLs(secureScheme: false, domain: "127.0.0.1:8000")
secondStoreServer.tokenKeychain = MockKeychainStore(fileName: "token.json")
let events = DataStore.shared.events
try await DataStore.shared.events.deleteAsync(contentOfs: Array(events))
}
}

File diff suppressed because it is too large Load Diff

@ -23,13 +23,11 @@ struct SynchronizationTests {
init() async throws { init() async throws {
let conf = Config.server
FileManager.default.deleteDirectoryInDocuments(directoryName: "storage") FileManager.default.deleteDirectoryInDocuments(directoryName: "storage")
FileManager.default.deleteDirectoryInDocuments(directoryName: "storage-2") FileManager.default.deleteDirectoryInDocuments(directoryName: "storage-2")
self.secondStoreCenter = StoreCenter(directoryName: "storage-2") self.secondStoreCenter = StoreCenter(directoryName: "storage-2")
self.secondStoreCenter.configureURLs(secureScheme: conf.secure, domain: conf.domain, webSockets: false, useSynchronization: true) self.secondStoreCenter.configureURLs(secureScheme: false, domain: "127.0.0.1:8000", webSockets: false, useSynchronization: true)
self.secondStoreCenter.tokenKeychain = MockKeychainStore(fileName: "storage-2/token.json") self.secondStoreCenter.tokenKeychain = MockKeychainStore(fileName: "storage-2/token.json")
self.secondStoreCenter.deviceKeychain = MockKeychainStore(fileName: "storage-2/device.json") self.secondStoreCenter.deviceKeychain = MockKeychainStore(fileName: "storage-2/device.json")
try self.secondStoreCenter.deviceKeychain.add(value: UUID().uuidString) try self.secondStoreCenter.deviceKeychain.add(value: UUID().uuidString)
@ -41,7 +39,7 @@ struct SynchronizationTests {
try await self.login(storeCenter: self.secondStoreCenter) try await self.login(storeCenter: self.secondStoreCenter)
} }
StoreCenter.main.configureURLs(secureScheme: conf.secure, domain: conf.domain, webSockets: false, useSynchronization: true) StoreCenter.main.configureURLs(secureScheme: false, domain: "127.0.0.1:8000", webSockets: false, useSynchronization: true)
StoreCenter.main.tokenKeychain = MockKeychainStore(fileName: "storage/token.json") StoreCenter.main.tokenKeychain = MockKeychainStore(fileName: "storage/token.json")
StoreCenter.main.deviceKeychain = MockKeychainStore(fileName: "storage/device.json") StoreCenter.main.deviceKeychain = MockKeychainStore(fileName: "storage/device.json")
try StoreCenter.main.deviceKeychain.add(value: UUID().uuidString) try StoreCenter.main.deviceKeychain.add(value: UUID().uuidString)
@ -75,14 +73,13 @@ struct SynchronizationTests {
// Cleanup // Cleanup
let eventCollection1: SyncedCollection<Event> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection() let eventCollection1: SyncedCollection<Event> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection()
try await eventCollection1.loadOnceAsync()
#expect(eventCollection1.hasLoaded == true) #expect(eventCollection1.hasLoaded == true)
try await eventCollection1.deleteAsync(contentOfs: Array(eventCollection1)) try await eventCollection1.deleteAsync(contentOfs: Array(eventCollection1))
let eventCollection2: SyncedCollection<Event> = await self.secondStoreCenter.mainStore.asyncLoadingSynchronizedCollection() let eventCollection2: SyncedCollection<Event> = await self.secondStoreCenter.mainStore.asyncLoadingSynchronizedCollection()
#expect(eventCollection2.hasLoaded == true) #expect(eventCollection2.hasLoaded == true)
eventCollection2.reset() eventCollection2.clear()
// cleanup sync residues // cleanup sync residues
let _ = try await self.secondStoreCenter.testSynchronizeOnceAsync() let _ = try await self.secondStoreCenter.testSynchronizeOnceAsync()
@ -123,13 +120,13 @@ struct SynchronizationTests {
let eventCollectionA: SyncedCollection<Event> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection() let eventCollectionA: SyncedCollection<Event> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection()
try await eventCollectionA.deleteAsync(contentOfs: Array(eventCollectionA)) try await eventCollectionA.deleteAsync(contentOfs: Array(eventCollectionA))
let eventCollectionB: SyncedCollection<Event> = await self.secondStoreCenter.mainStore.asyncLoadingSynchronizedCollection() let eventCollectionB: SyncedCollection<Event> = await self.secondStoreCenter.mainStore.asyncLoadingSynchronizedCollection()
eventCollectionB.reset() eventCollectionB.clear()
// Setup clubs collections // Setup clubs collections
let clubCollectionA: SyncedCollection<Club> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection() let clubCollectionA: SyncedCollection<Club> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection()
try await clubCollectionA.deleteAsync(contentOfs: Array(clubCollectionA)) try await clubCollectionA.deleteAsync(contentOfs: Array(clubCollectionA))
let clubCollectionB: SyncedCollection<Club> = await self.secondStoreCenter.mainStore.asyncLoadingSynchronizedCollection() let clubCollectionB: SyncedCollection<Club> = await self.secondStoreCenter.mainStore.asyncLoadingSynchronizedCollection()
clubCollectionB.reset() clubCollectionB.clear()
// cleanup sync residues // cleanup sync residues
let _ = try await self.secondStoreCenter.testSynchronizeOnceAsync() let _ = try await self.secondStoreCenter.testSynchronizeOnceAsync()
@ -187,7 +184,7 @@ struct SynchronizationTests {
let teamRegColA: SyncedCollection<TeamRegistration> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection() let teamRegColA: SyncedCollection<TeamRegistration> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection()
try await teamRegColA.deleteAsync(contentOfs: Array(teamRegColA)) try await teamRegColA.deleteAsync(contentOfs: Array(teamRegColA))
let teamRegColB: SyncedCollection<TeamRegistration> = await self.secondStoreCenter.mainStore.asyncLoadingSynchronizedCollection() let teamRegColB: SyncedCollection<TeamRegistration> = await self.secondStoreCenter.mainStore.asyncLoadingSynchronizedCollection()
teamRegColB.reset() teamRegColB.clear()
// cleanup sync residues // cleanup sync residues
let _ = try await self.secondStoreCenter.testSynchronizeOnceAsync() let _ = try await self.secondStoreCenter.testSynchronizeOnceAsync()
@ -215,7 +212,7 @@ struct SynchronizationTests {
try await eventCollectionA.deleteAsync(contentOfs: Array(eventCollectionA)) try await eventCollectionA.deleteAsync(contentOfs: Array(eventCollectionA))
let eventCollectionB: SyncedCollection<Event> = await self.secondStoreCenter.mainStore.asyncLoadingSynchronizedCollection() let eventCollectionB: SyncedCollection<Event> = await self.secondStoreCenter.mainStore.asyncLoadingSynchronizedCollection()
eventCollectionB.reset() eventCollectionB.clear()
// cleanup sync residues // cleanup sync residues
let _ = try await self.secondStoreCenter.testSynchronizeOnceAsync() let _ = try await self.secondStoreCenter.testSynchronizeOnceAsync()
@ -311,61 +308,5 @@ struct SynchronizationTests {
} }
// needs to run on a postgreSQL, otherwise fails because of sqlite database locks
@Test func testBuildEverything() async throws {
// Cleanup
let tournamentColA: SyncedCollection<Tournament> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection()
let tournamentColB: SyncedCollection<Tournament> = await self.secondStoreCenter.mainStore.asyncLoadingSynchronizedCollection()
let tournamentsToDelete: [Tournament] = try await StoreCenter.main.service().get()
tournamentColA.delete(contentOfs: tournamentsToDelete)
// Setup tournament + build everything
let tournament = Tournament()
try await tournamentColA.addOrUpdateAsync(instance: tournament)
try await tournament.deleteAndBuildEverythingAsync()
let tourStore = try StoreCenter.main.store(identifier: tournament.id)
let gsColA: SyncedCollection<GroupStage> = try tourStore.syncedCollection()
let roundColA: SyncedCollection<Round> = try tourStore.syncedCollection()
let matchesColA: SyncedCollection<Match> = try tourStore.syncedCollection()
#expect(gsColA.count == 4)
#expect(roundColA.count == 15)
#expect(matchesColA.count == 56)
// Sync with 2nd store
try await secondStoreCenter.testSynchronizeOnceAsync()
#expect(tournamentColB.count == 1)
let tourStoreB = try secondStoreCenter.store(identifier: tournament.id)
let gsColB: SyncedCollection<GroupStage> = try tourStoreB.syncedCollection()
let roundColB: SyncedCollection<Round> = try tourStoreB.syncedCollection()
let matchesColB: SyncedCollection<Match> = try tourStoreB.syncedCollection()
#expect(gsColB.count == 4)
#expect(roundColB.count == 15)
#expect(matchesColB.count == 56)
// change setup + build everything
tournament.groupStageCount = 2
tournament.teamCount = 20
try await tournamentColA.addOrUpdateAsync(instance: tournament)
try await tournament.deleteAndBuildEverythingAsync()
#expect(gsColA.count == 2)
#expect(roundColA.count == 15)
#expect(matchesColA.count == 44)
// Sync with 2nd store
try await secondStoreCenter.testSynchronizeOnceAsync()
#expect(gsColB.count == 2)
#expect(roundColB.count == 15)
#expect(matchesColB.count == 44)
}
} }

Loading…
Cancel
Save