Split project with PadelClubData

sync3
Laurent 6 months ago
parent 4663f32102
commit 2c99a323b9
  1. 820
      PadelClub.xcodeproj/project.pbxproj
  2. 3
      PadelClub.xcworkspace/contents.xcworkspacedata
  3. 32
      PadelClub/AppDelegate.swift
  4. 111
      PadelClub/Data/AppSettings.swift
  5. 115
      PadelClub/Data/Club.swift
  6. 1
      PadelClub/Data/Coredata/ImportedPlayer+Extensions.swift
  7. 58
      PadelClub/Data/Court.swift
  8. 316
      PadelClub/Data/CustomUser.swift
  9. 403
      PadelClub/Data/DataStore.swift
  10. 76
      PadelClub/Data/DateInterval.swift
  11. 97
      PadelClub/Data/DrawLog.swift
  12. 100
      PadelClub/Data/Event.swift
  13. 19
      PadelClub/Data/Federal/FederalTournament.swift
  14. 1
      PadelClub/Data/Federal/FederalTournamentHolder.swift
  15. 150
      PadelClub/Data/Gen/BaseClub.swift
  16. 93
      PadelClub/Data/Gen/BaseCourt.swift
  17. 262
      PadelClub/Data/Gen/BaseCustomUser.swift
  18. 80
      PadelClub/Data/Gen/BaseDateInterval.swift
  19. 100
      PadelClub/Data/Gen/BaseDrawLog.swift
  20. 100
      PadelClub/Data/Gen/BaseEvent.swift
  21. 107
      PadelClub/Data/Gen/BaseGroupStage.swift
  22. 156
      PadelClub/Data/Gen/BaseMatch.swift
  23. 163
      PadelClub/Data/Gen/BaseMatchScheduler.swift
  24. 122
      PadelClub/Data/Gen/BaseMonthData.swift
  25. 227
      PadelClub/Data/Gen/BasePlayerRegistration.swift
  26. 99
      PadelClub/Data/Gen/BasePurchase.swift
  27. 107
      PadelClub/Data/Gen/BaseRound.swift
  28. 199
      PadelClub/Data/Gen/BaseTeamRegistration.swift
  29. 99
      PadelClub/Data/Gen/BaseTeamScore.swift
  30. 590
      PadelClub/Data/Gen/BaseTournament.swift
  31. 93
      PadelClub/Data/Gen/Club.json
  32. 42
      PadelClub/Data/Gen/Court.json
  33. 177
      PadelClub/Data/Gen/CustomUser.json
  34. 33
      PadelClub/Data/Gen/DateInterval.json
  35. 47
      PadelClub/Data/Gen/Drawlog.json
  36. 48
      PadelClub/Data/Gen/Event.json
  37. 53
      PadelClub/Data/Gen/GroupStage.json
  38. 83
      PadelClub/Data/Gen/Match.json
  39. 91
      PadelClub/Data/Gen/MatchScheduler.json
  40. 71
      PadelClub/Data/Gen/MonthData.json
  41. 138
      PadelClub/Data/Gen/PlayerRegistration.json
  42. 51
      PadelClub/Data/Gen/Purchase.json
  43. 53
      PadelClub/Data/Gen/Round.json
  44. 118
      PadelClub/Data/Gen/TeamRegistration.json
  45. 44
      PadelClub/Data/Gen/TeamScore.json
  46. 355
      PadelClub/Data/Gen/Tournament.json
  47. 593
      PadelClub/Data/Gen/generator.py
  48. 684
      PadelClub/Data/GroupStage.swift
  49. 1088
      PadelClub/Data/Match.swift
  50. 888
      PadelClub/Data/MatchScheduler.swift
  51. 66
      PadelClub/Data/MockData.swift
  52. 53
      PadelClub/Data/PlayerPaymentType.swift
  53. 510
      PadelClub/Data/PlayerRegistration.swift
  54. 35
      PadelClub/Data/README.md
  55. 1049
      PadelClub/Data/Round.swift
  56. 779
      PadelClub/Data/TeamRegistration.swift
  57. 117
      PadelClub/Data/TeamScore.swift
  58. 2688
      PadelClub/Data/Tournament.swift
  59. 33
      PadelClub/Data/TournamentLibrary.swift
  60. 68
      PadelClub/Data/TournamentStore.swift
  61. 96
      PadelClub/Extensions/Array+Extensions.swift
  62. 25
      PadelClub/Extensions/Badge+Extensions.swift
  63. 71
      PadelClub/Extensions/Calendar+Extensions.swift
  64. 45
      PadelClub/Extensions/CodingContainer+Extensions.swift
  65. 22
      PadelClub/Extensions/CustomUser+Extensions.swift
  66. 266
      PadelClub/Extensions/Date+Extensions.swift
  67. 43
      PadelClub/Extensions/FixedWidthInteger+Extensions.swift
  68. 28
      PadelClub/Extensions/Locale+Extensions.swift
  69. 28
      PadelClub/Extensions/MonthData+Extensions.swift
  70. 27
      PadelClub/Extensions/MySortDescriptor.swift
  71. 20
      PadelClub/Extensions/NumberFormatter+Extensions.swift
  72. 229
      PadelClub/Extensions/PlayerRegistration+Extensions.swift
  73. 33
      PadelClub/Extensions/Round+Extensions.swift
  74. 103
      PadelClub/Extensions/Sequence+Extensions.swift
  75. 34
      PadelClub/Extensions/SourceFileManager+Extensions.swift
  76. 42
      PadelClub/Extensions/SpinDrawable+Extensions.swift
  77. 47
      PadelClub/Extensions/String+Crypto.swift
  78. 313
      PadelClub/Extensions/String+Extensions.swift
  79. 67
      PadelClub/Extensions/TeamRegistration+Extensions.swift
  80. 445
      PadelClub/Extensions/Tournament+Extensions.swift
  81. 182
      PadelClub/Extensions/URL+Extensions.swift
  82. 1
      PadelClub/PadelClubApp.swift
  83. 254
      PadelClub/Utils/ContactManager.swift
  84. 12
      PadelClub/Utils/CryptoKey.swift
  85. 64
      PadelClub/Utils/DisplayContext.swift
  86. 37
      PadelClub/Utils/ExportFormat.swift
  87. 1
      PadelClub/Utils/FileImportManager.swift
  88. 1
      PadelClub/Utils/HtmlGenerator.swift
  89. 1
      PadelClub/Utils/HtmlService.swift
  90. 1
      PadelClub/Utils/Network/NetworkFederalService.swift
  91. 1
      PadelClub/Utils/Network/NetworkManager.swift
  92. 28
      PadelClub/Utils/Network/NetworkManagerError.swift
  93. 1
      PadelClub/Utils/Network/RefundService.swift
  94. 47
      PadelClub/Utils/PListReader.swift
  95. 2116
      PadelClub/Utils/PadelRule.swift
  96. 143
      PadelClub/Utils/Patcher.swift
  97. 270
      PadelClub/Utils/SourceFileManager.swift
  98. 1
      PadelClub/Utils/SwiftParser.swift
  99. 1
      PadelClub/Utils/Tips.swift
  100. 95
      PadelClub/Utils/URLs.swift
  101. Some files were not shown because too many files have changed in this diff Show More

File diff suppressed because it is too large Load Diff

@ -4,6 +4,9 @@
<FileRef <FileRef
location = "group:../LeStorage/LeStorage.xcodeproj"> location = "group:../LeStorage/LeStorage.xcodeproj">
</FileRef> </FileRef>
<FileRef
location = "container:../PadelClubData/PadelClubData.xcodeproj">
</FileRef>
<FileRef <FileRef
location = "group:PadelClub.xcodeproj"> location = "group:PadelClub.xcodeproj">
</FileRef> </FileRef>

@ -9,6 +9,7 @@ import Foundation
import UIKit import UIKit
import LeStorage import LeStorage
import UserNotifications import UserNotifications
import PadelClubData
class AppDelegate : NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate { class AppDelegate : NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {
@ -17,12 +18,43 @@ class AppDelegate : NSObject, UIApplicationDelegate, UNUserNotificationCenterDel
_ = Guard.main // init guard _ = Guard.main // init guard
self._configureLeStorage()
UIApplication.shared.registerForRemoteNotifications() UIApplication.shared.registerForRemoteNotifications()
UNUserNotificationCenter.current().delegate = self UNUserNotificationCenter.current().delegate = self
return true return true
} }
fileprivate func _configureLeStorage() {
StoreCenter.main.blackListUserName("apple-test")
// let secureScheme = true
let domain: String = URLs.activationHost.rawValue
#if DEBUG
if let secure = PListReader.readBool(plist: "local", key: "secure_server"),
let domain = PListReader.readString(plist: "local", key: "server_domain") {
StoreCenter.main.configureURLs(secureScheme: secure, domain: domain)
} else {
StoreCenter.main.configureURLs(secureScheme: true, domain: domain)
}
#else
StoreCenter.main.configureURLs(secureScheme: true, domain: domain)
#endif
StoreCenter.main.logsFailedAPICalls()
var synchronized: Bool = true
#if DEBUG
if let sync = PListReader.readBool(plist: "local", key: "synchronized") {
synchronized = sync
}
#endif
StoreCenter.main.forceNoSynchronization = !synchronized
}
func applicationWillEnterForeground(_ application: UIApplication) { func applicationWillEnterForeground(_ application: UIApplication) {
Task { Task {
try await Guard.main.refreshPurchasedAppleProducts() try await Guard.main.refreshPurchasedAppleProducts()

@ -1,111 +0,0 @@
//
// AppSettings.swift
// PadelClub
//
// Created by Razmig Sarkissian on 26/03/2024.
//
import Foundation
import LeStorage
import SwiftUI
@Observable
final class AppSettings: MicroStorable {
var lastDataSource: String? = nil
var didCreateAccount: Bool = false
var didRegisterAccount: Bool = false
//search tournament stuff
var tournamentCategories: Set<TournamentCategory.ID>
var tournamentLevels: Set<TournamentLevel.ID>
var tournamentAges: Set<FederalTournamentAge.ID>
var tournamentTypes: Set<FederalTournamentType.ID>
var startDate: Date
var endDate: Date
var city: String
var distance: Double
var sortingOption: String
var nationalCup: Bool
var dayDuration: Int?
var dayPeriod: DayPeriod
func lastDataSourceDate() -> Date? {
guard let lastDataSource else { return nil }
return URL.importDateFormatter.date(from: lastDataSource)
}
func localizedLastDataSource() -> String? {
guard let lastDataSource else { return nil }
guard let date = URL.importDateFormatter.date(from: lastDataSource) else { return nil }
return date.monthYearFormatted
}
func resetSearch() {
tournamentAges = Set()
tournamentTypes = Set()
tournamentLevels = Set()
tournamentCategories = Set()
city = ""
distance = 30
startDate = Date()
endDate = Calendar.current.date(byAdding: .month, value: 3, to: Date())!
sortingOption = "dateDebut+asc"
nationalCup = false
dayDuration = nil
dayPeriod = .all
}
required init() {
tournamentAges = Set()
tournamentTypes = Set()
tournamentLevels = Set()
tournamentCategories = Set()
city = ""
distance = 30
startDate = Date()
endDate = Calendar.current.date(byAdding: .month, value: 3, to: Date())!
sortingOption = "dateDebut+asc"
nationalCup = false
dayDuration = nil
dayPeriod = .all
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
lastDataSource = try container.decodeIfPresent(String.self, forKey: ._lastDataSource)
didCreateAccount = try container.decodeIfPresent(Bool.self, forKey: ._didCreateAccount) ?? false
didRegisterAccount = try container.decodeIfPresent(Bool.self, forKey: ._didRegisterAccount) ?? false
tournamentCategories = try container.decodeIfPresent(Set<TournamentCategory.ID>.self, forKey: ._tournamentCategories) ?? Set()
tournamentLevels = try container.decodeIfPresent(Set<TournamentLevel.ID>.self, forKey: ._tournamentLevels) ?? Set()
tournamentAges = try container.decodeIfPresent(Set<FederalTournamentAge.ID>.self, forKey: ._tournamentAges) ?? Set()
tournamentTypes = try container.decodeIfPresent(Set<FederalTournamentType.ID>.self, forKey: ._tournamentTypes) ?? Set()
startDate = try container.decodeIfPresent(Date.self, forKey: ._startDate) ?? Date()
endDate = try container.decodeIfPresent(Date.self, forKey: ._endDate) ?? Calendar.current.date(byAdding: .month, value: 3, to: Date())!
city = try container.decodeIfPresent(String.self, forKey: ._city) ?? ""
distance = try container.decodeIfPresent(Double.self, forKey: ._distance) ?? 30
sortingOption = try container.decodeIfPresent(String.self, forKey: ._sortingOption) ?? "dateDebut+asc"
nationalCup = try container.decodeIfPresent(Bool.self, forKey: ._nationalCup) ?? false
dayDuration = try container.decodeIfPresent(Int.self, forKey: ._dayDuration)
dayPeriod = try container.decodeIfPresent(DayPeriod.self, forKey: ._dayPeriod) ?? .all
}
enum CodingKeys: String, CodingKey {
case _lastDataSource = "lastDataSource"
case _didCreateAccount = "didCreateAccount"
case _didRegisterAccount = "didRegisterAccount"
case _tournamentCategories = "tournamentCategories"
case _tournamentLevels = "tournamentLevels"
case _tournamentAges = "tournamentAges"
case _tournamentTypes = "tournamentTypes"
case _startDate = "startDate"
case _endDate = "endDate"
case _city = "city"
case _distance = "distance"
case _sortingOption = "sortingOption"
case _nationalCup = "nationalCup"
case _dayDuration = "dayDuration"
case _dayPeriod = "dayPeriod"
}
}

@ -1,115 +0,0 @@
//
// Club.swift
// PadelClub
//
// Created by Laurent Morvillier on 02/02/2024.
//
import Foundation
import SwiftUI
import LeStorage
@Observable
final class Club: BaseClub {
func clubTitle(_ displayStyle: DisplayStyle = .wide) -> String {
switch displayStyle {
case .wide, .title:
return name
case .short:
return acronym
}
}
func shareURL() -> URL? {
return URL(string: URLs.main.url.appending(path: "?club=\(id)").absoluteString.removingPercentEncoding!)
}
var customizedCourts: [Court] {
DataStore.shared.courts.filter { $0.club == self.id }.sorted(by: \.index)
}
override func deleteDependencies() {
let customizedCourts = self.customizedCourts
for customizedCourt in customizedCourts {
customizedCourt.deleteDependencies()
}
DataStore.shared.courts.deleteDependencies(customizedCourts)
}
}
extension Club {
var isValid: Bool {
name.isEmpty == false && name.count > 3
}
func automaticShortName() -> String {
name.acronym()
}
enum AcronymMode: String, CaseIterable {
case automatic = "Automatique"
case custom = "Personalisée"
}
func shortNameMode() -> AcronymMode {
(acronym.isEmpty || acronym == automaticShortName()) ? .automatic : .custom
}
func hasTenupId() -> Bool {
code != nil
}
func federalLink() -> URL? {
guard let code else { return nil }
return URL(string: "https://tenup.fft.fr/club/\(code)")
}
func courtName(atIndex courtIndex: Int) -> String {
courtNameIfAvailable(atIndex: courtIndex) ?? Court.courtIndexedTitle(atIndex: courtIndex)
}
func courtNameIfAvailable(atIndex courtIndex: Int) -> String? {
customizedCourts.first(where: { $0.index == courtIndex })?.name
}
func update(fromClub club: Club) {
self.acronym = club.acronym
self.name = club.name
self.phone = club.phone
self.code = club.code
self.address = club.address
self.city = club.city
self.zipCode = club.zipCode
self.latitude = club.latitude
self.longitude = club.longitude
}
func hasBeenCreated(by creatorId: String?) -> Bool {
return creatorId == creator || creator == nil || self.relatedUser == creatorId
}
func isFavorite() -> Bool {
return DataStore.shared.user.clubs.contains(where: { $0 == self.id })
}
static func findOrCreate(name: String, code: String?, city: String? = nil, zipCode: String? = nil) -> Club {
/*
identify a club : code, name, ??
*/
let club: Club? = DataStore.shared.clubs.first(where: { (code == nil && $0.name == name && $0.city == city && $0.zipCode == zipCode) || code != nil && $0.code == code })
if let club {
return club
} else {
let club = Club(creator: StoreCenter.main.userId, name: name, acronym: name.acronym(), code: code, city: city, zipCode: zipCode)
club.relatedUser = StoreCenter.main.userId
return club
}
}
}

@ -6,6 +6,7 @@
// //
import Foundation import Foundation
import PadelClubData
extension ImportedPlayer: PlayerHolder { extension ImportedPlayer: PlayerHolder {
func getAssimilatedAsMaleRank() -> Int? { func getAssimilatedAsMaleRank() -> Int? {

@ -1,58 +0,0 @@
//
// Court.swift
// PadelClub
//
// Created by Razmig Sarkissian on 23/04/2024.
//
import Foundation
import SwiftUI
import LeStorage
@Observable
final class Court: BaseCourt {
static func == (lhs: Court, rhs: Court) -> Bool {
lhs.id == rhs.id
}
init(index: Int, club: String, name: String? = nil, exitAllowed: Bool = false, indoor: Bool = false) {
super.init()
self.index = index
self.lastUpdate = Date()
self.club = club
self.name = name
self.exitAllowed = exitAllowed
self.indoor = indoor
}
required init(from decoder: Decoder) throws {
try super.init(from: decoder)
}
required public init() {
super.init()
}
func courtTitle() -> String {
self.name ?? courtIndexTitle()
}
func courtIndexTitle() -> String {
Self.courtIndexedTitle(atIndex: index)
}
static func courtIndexedTitle(atIndex index: Int) -> String {
("Terrain #" + (index + 1).formatted())
}
func clubObject() -> Club? {
Store.main.findById(club)
}
override func deleteDependencies() {
}
}

@ -1,316 +0,0 @@
//
// User.swift
// PadelClub
//
// Created by Laurent Morvillier on 21/02/2024.
//
import Foundation
import LeStorage
enum UserRight: Int, Codable {
case none = 0
case edition = 1
case creation = 2
}
enum RegistrationPaymentMode: Int, Codable {
case disabled = 0
case corporate = 1
case noFee = 2
case stripe = 3
static let stripeFixedFee = 0.25 // Fixed fee in euros
static let stripePercentageFee = 0.014 // 1.4%
func canEnableOnlinePayment() -> Bool {
switch self {
case .disabled:
return false
case .corporate:
return true
case .noFee:
return true
case .stripe:
return true
}
}
func requiresStripe() -> Bool {
switch self {
case .disabled:
return false
case .corporate:
return true
case .noFee:
return true
case .stripe:
return true
}
}
}
@Observable
class CustomUser: BaseCustomUser, UserBase {
// static func resourceName() -> String { "users" }
// static func tokenExemptedMethods() -> [HTTPMethod] { return [.post] }
// static func filterByStoreIdentifier() -> Bool { return false }
// static var relationshipNames: [String] = []
//
// public var id: String = Store.randomId()
// var lastUpdate: Date
// public var username: String
// public var email: String
// var clubs: [String] = []
// var umpireCode: String?
// var licenceId: String?
// var firstName: String
// var lastName: String
// var phone: String?
// var country: String?
//
// var summonsMessageBody : String? = nil
// var summonsMessageSignature: String? = nil
// var summonsAvailablePaymentMethods: String? = nil
// var summonsDisplayFormat: Bool = false
// var summonsDisplayEntryFee: Bool = false
// var summonsUseFullCustomMessage: Bool = false
// var matchFormatsDefaultDuration: [MatchFormat: Int]? = nil
// var bracketMatchFormatPreference: MatchFormat?
// var groupStageMatchFormatPreference: MatchFormat?
// var loserBracketMatchFormatPreference: MatchFormat?
// var loserBracketMode: LoserBracketMode = .automatic
//
// var deviceId: String?
init(username: String, email: String, firstName: String, lastName: String, phone: String?, country: String?, loserBracketMode: LoserBracketMode = .automatic) {
super.init(username: username, email: email, firstName: firstName, lastName: lastName, phone: phone, country: country, loserBracketMode: loserBracketMode)
// self.lastUpdate = Date()
// self.username = username
// self.firstName = firstName
// self.lastName = lastName
// self.email = email
// self.phone = phone
// self.country = country
// self.loserBracketMode = loserBracketMode
}
required init(from decoder: Decoder) throws {
try super.init(from: decoder)
}
required public init() {
super.init()
}
public func uuid() throws -> UUID {
if let uuid = UUID(uuidString: self.id) {
return uuid
}
throw UUIDError.cantConvertString(string: self.id)
}
func currentPlayerData() -> ImportedPlayer? {
guard let licenceId = self.licenceId?.strippedLicense else { return nil }
let federalContext = PersistenceController.shared.localContainer.viewContext
let fetchRequest = ImportedPlayer.fetchRequest()
let predicate = NSPredicate(format: "license == %@", licenceId)
fetchRequest.predicate = predicate
return try? federalContext.fetch(fetchRequest).first
}
func defaultSignature(_ tournament: Tournament?) -> String {
let fullName = tournament?.umpireCustomContact ?? fullName()
return "Sportivement,\n\(fullName), votre JAP."
}
func fullName() -> String {
[firstName, lastName].joined(separator: " ")
}
func hasTenupClubs() -> Bool {
self.clubsObjects().filter({ $0.code != nil }).isEmpty == false
}
func hasFavoriteClubsAndCreatedClubs() -> Bool {
clubsObjects(includeCreated: true).isEmpty == false
}
func setUserClub(_ userClub: Club) {
self.clubs.insert(userClub.id, at: 0)
}
func clubsObjects(includeCreated: Bool = false) -> [Club] {
return DataStore.shared.clubs.filter({ (includeCreated && $0.creator == id) || clubs.contains($0.id) })
}
func createdClubsObjectsNotFavorite() -> [Club] {
return DataStore.shared.clubs.filter({ ($0.creator == id) && clubs.contains($0.id) == false })
}
func saveMatchFormatsDefaultDuration(_ matchFormat: MatchFormat, estimatedDuration: Int) {
if estimatedDuration == matchFormat.defaultEstimatedDuration {
matchFormatsDefaultDuration?.removeValue(forKey: matchFormat)
} else {
matchFormatsDefaultDuration = matchFormatsDefaultDuration ?? [MatchFormat: Int]()
matchFormatsDefaultDuration?[matchFormat] = estimatedDuration
}
}
func addClub(_ club: Club) {
if !self.clubs.contains(where: { $0.id == club.id }) {
self.clubs.append(club.id)
}
}
func getSummonsMessageSignature() -> String? {
if let summonsMessageSignature, summonsMessageSignature.isEmpty == false {
return summonsMessageSignature
} else {
return nil
}
}
func canEnableOnlinePayment() -> Bool {
registrationPaymentMode.canEnableOnlinePayment()
}
// enum CodingKeys: String, CodingKey {
// case _id = "id"
// case _lastUpdate = "lastUpdate"
// case _username = "username"
// case _email = "email"
// case _clubs = "clubs"
// case _umpireCode = "umpireCode"
// case _licenceId = "licenceId"
// case _firstName = "firstName"
// case _lastName = "lastName"
// case _phone = "phone"
// case _country = "country"
// case _summonsMessageBody = "summonsMessageBody"
// case _summonsMessageSignature = "summonsMessageSignature"
// case _summonsAvailablePaymentMethods = "summonsAvailablePaymentMethods"
// case _summonsDisplayFormat = "summonsDisplayFormat"
// case _summonsDisplayEntryFee = "summonsDisplayEntryFee"
// case _summonsUseFullCustomMessage = "summonsUseFullCustomMessage"
// case _matchFormatsDefaultDuration = "matchFormatsDefaultDuration"
// case _bracketMatchFormatPreference = "bracketMatchFormatPreference"
// case _groupStageMatchFormatPreference = "groupStageMatchFormatPreference"
// case _loserBracketMatchFormatPreference = "loserBracketMatchFormatPreference"
// case _deviceId = "deviceId"
// case _loserBracketMode = "loserBracketMode"
// }
//
// public required init(from decoder: Decoder) throws {
// let container = try decoder.container(keyedBy: CodingKeys.self)
//
// // Required properties
// id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId()
// lastUpdate = try container.decodeIfPresent(Date.self, forKey: ._lastUpdate) ?? Date()
// username = try container.decode(String.self, forKey: ._username)
// email = try container.decode(String.self, forKey: ._email)
// firstName = try container.decode(String.self, forKey: ._firstName)
// lastName = try container.decode(String.self, forKey: ._lastName)
//
// // Optional properties
// clubs = try container.decodeIfPresent([String].self, forKey: ._clubs) ?? []
// umpireCode = try container.decodeIfPresent(String.self, forKey: ._umpireCode)
// licenceId = try container.decodeIfPresent(String.self, forKey: ._licenceId)
// phone = try container.decodeIfPresent(String.self, forKey: ._phone)
// country = try container.decodeIfPresent(String.self, forKey: ._country)
//
// // Summons-related properties
// summonsMessageBody = try container.decodeIfPresent(String.self, forKey: ._summonsMessageBody)
// summonsMessageSignature = try container.decodeIfPresent(String.self, forKey: ._summonsMessageSignature)
// summonsAvailablePaymentMethods = try container.decodeIfPresent(String.self, forKey: ._summonsAvailablePaymentMethods)
// summonsDisplayFormat = try container.decodeIfPresent(Bool.self, forKey: ._summonsDisplayFormat) ?? false
// summonsDisplayEntryFee = try container.decodeIfPresent(Bool.self, forKey: ._summonsDisplayEntryFee) ?? false
// summonsUseFullCustomMessage = try container.decodeIfPresent(Bool.self, forKey: ._summonsUseFullCustomMessage) ?? false
//
// // Match-related properties
// matchFormatsDefaultDuration = try container.decodeIfPresent([MatchFormat: Int].self, forKey: ._matchFormatsDefaultDuration)
// bracketMatchFormatPreference = try container.decodeIfPresent(MatchFormat.self, forKey: ._bracketMatchFormatPreference)
// groupStageMatchFormatPreference = try container.decodeIfPresent(MatchFormat.self, forKey: ._groupStageMatchFormatPreference)
// loserBracketMatchFormatPreference = try container.decodeIfPresent(MatchFormat.self, forKey: ._loserBracketMatchFormatPreference)
// loserBracketMode = try container.decodeIfPresent(LoserBracketMode.self, forKey: ._loserBracketMode) ?? .automatic
// }
//
// func encode(to encoder: Encoder) throws {
// var container = encoder.container(keyedBy: CodingKeys.self)
//
// try container.encode(id, forKey: ._id)
// try container.encode(lastUpdate, forKey: ._lastUpdate)
// try container.encode(username, forKey: ._username)
// try container.encode(email, forKey: ._email)
// try container.encode(clubs, forKey: ._clubs)
//
// try container.encode(umpireCode, forKey: ._umpireCode)
// try container.encode(licenceId, forKey: ._licenceId)
// try container.encode(firstName, forKey: ._firstName)
// try container.encode(lastName, forKey: ._lastName)
// try container.encode(phone, forKey: ._phone)
// try container.encode(country, forKey: ._country)
// try container.encode(summonsMessageBody, forKey: ._summonsMessageBody)
// try container.encode(summonsMessageSignature, forKey: ._summonsMessageSignature)
// try container.encode(summonsAvailablePaymentMethods, forKey: ._summonsAvailablePaymentMethods)
// try container.encode(summonsDisplayFormat, forKey: ._summonsDisplayFormat)
// try container.encode(summonsDisplayEntryFee, forKey: ._summonsDisplayEntryFee)
// try container.encode(summonsUseFullCustomMessage, forKey: ._summonsUseFullCustomMessage)
//
// try container.encode(matchFormatsDefaultDuration, forKey: ._matchFormatsDefaultDuration)
// try container.encode(bracketMatchFormatPreference, forKey: ._bracketMatchFormatPreference)
// try container.encode(groupStageMatchFormatPreference, forKey: ._groupStageMatchFormatPreference)
// try container.encode(loserBracketMatchFormatPreference, forKey: ._loserBracketMatchFormatPreference)
// try container.encode(deviceId, forKey: ._deviceId)
//
// try container.encode(loserBracketMode, forKey: ._loserBracketMode)
// }
static func placeHolder() -> CustomUser {
return CustomUser(username: "", email: "", firstName: "", lastName: "", phone: nil, country: nil)
}
}
class UserCreationForm: CustomUser, UserPasswordBase {
init(user: CustomUser, username: String, password: String, firstName: String, lastName: String, email: String, phone: String?, country: String?) {
self.password = password
super.init(username: username, email: email, firstName: firstName, lastName: lastName, phone: phone, country: country)
self.summonsMessageBody = user.summonsMessageBody
self.summonsMessageSignature = user.summonsMessageSignature
self.summonsAvailablePaymentMethods = user.summonsAvailablePaymentMethods
self.summonsDisplayFormat = user.summonsDisplayFormat
self.summonsDisplayEntryFee = user.summonsDisplayEntryFee
self.summonsUseFullCustomMessage = user.summonsUseFullCustomMessage
self.matchFormatsDefaultDuration = user.matchFormatsDefaultDuration
self.bracketMatchFormatPreference = user.bracketMatchFormatPreference
self.groupStageMatchFormatPreference = user.groupStageMatchFormatPreference
self.loserBracketMatchFormatPreference = user.loserBracketMatchFormatPreference
}
required init(from decoder: Decoder) throws {
fatalError("init(from:) has not been implemented")
}
required public init() {
self.password = ""
super.init()
}
public var password: String
private enum CodingKeys: String, CodingKey {
case password
}
override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.password, forKey: .password)
}
}

@ -1,403 +0,0 @@
//
// DataStore.swift
// PadelClub
//
// Created by Laurent Morvillier on 02/02/2024.
//
import Foundation
import LeStorage
import SwiftUI
class DataStore: ObservableObject {
static let shared = DataStore()
@Published var user: CustomUser = CustomUser.placeHolder() {
didSet {
let loggedUser = StoreCenter.main.isAuthenticated
if loggedUser {
if self.user.id != self.userStorage.item()?.id {
self.userStorage.setItemNoSync(self.user)
StoreCenter.main.initialSynchronization(clear: false)
self._fixMissingClubCreatorIfNecessary(self.clubs)
self._fixMissingEventCreatorIfNecessary(self.events)
}
} else {
self._temporaryLocalUser.item = self.user
}
}
}
fileprivate(set) var tournaments: SyncedCollection<Tournament>
fileprivate(set) var clubs: SyncedCollection<Club>
fileprivate(set) var courts: SyncedCollection<Court>
fileprivate(set) var events: SyncedCollection<Event>
fileprivate(set) var monthData: StoredCollection<MonthData>
fileprivate(set) var dateIntervals: SyncedCollection<DateInterval>
fileprivate(set) var purchases: SyncedCollection<Purchase>
var userStorage: StoredSingleton<CustomUser>
fileprivate var _temporaryLocalUser: OptionalStorage<CustomUser> = OptionalStorage(fileName: "tmp_local_user.json")
fileprivate(set) var appSettingsStorage: MicroStorage<AppSettings> = MicroStorage(fileName: "appsettings.json")
var appSettings: AppSettings {
appSettingsStorage.item
}
init() {
let store = Store.main
StoreCenter.main.blackListUserName("apple-test")
// let secureScheme = true
let domain: String = URLs.activationHost.rawValue
#if DEBUG
if let secure = PListReader.readBool(plist: "local", key: "secure_server"),
let domain = PListReader.readString(plist: "local", key: "server_domain") {
StoreCenter.main.configureURLs(secureScheme: secure, domain: domain)
} else {
StoreCenter.main.configureURLs(secureScheme: true, domain: domain)
}
#else
StoreCenter.main.configureURLs(secureScheme: true, domain: domain)
#endif
StoreCenter.main.logsFailedAPICalls()
var synchronized: Bool = true
#if DEBUG
if let sync = PListReader.readBool(plist: "local", key: "synchronized") {
synchronized = sync
}
#endif
StoreCenter.main.forceNoSynchronization = !synchronized
let indexed: Bool = true
self.clubs = store.registerSynchronizedCollection(indexed: indexed)
self.courts = store.registerSynchronizedCollection(indexed: indexed)
self.tournaments = store.registerSynchronizedCollection(indexed: indexed)
self.events = store.registerSynchronizedCollection(indexed: indexed)
self.dateIntervals = store.registerSynchronizedCollection(indexed: indexed)
self.userStorage = store.registerObject(synchronized: synchronized)
self.purchases = Store.main.registerSynchronizedCollection(inMemory: true)
self.monthData = store.registerCollection(indexed: indexed)
// Load ApiCallCollection, making them restart at launch and deletable on disconnect
StoreCenter.main.loadApiCallCollection(type: GroupStage.self)
StoreCenter.main.loadApiCallCollection(type: Round.self)
StoreCenter.main.loadApiCallCollection(type: PlayerRegistration.self)
StoreCenter.main.loadApiCallCollection(type: TeamRegistration.self)
StoreCenter.main.loadApiCallCollection(type: Match.self)
StoreCenter.main.loadApiCallCollection(type: TeamScore.self)
StoreCenter.main.loadApiCallCollection(type: DrawLog.self)
NotificationCenter.default.addObserver(self, selector: #selector(collectionDidLoad), name: NSNotification.Name.CollectionDidLoad, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(collectionDidUpdate), name: NSNotification.Name.CollectionDidChange, object: nil)
NotificationCenter.default.addObserver(
self,
selector: #selector(_willEnterForegroundNotification),
name: UIScene.willEnterForegroundNotification,
object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
func saveUser() {
if user.username.count > 0 {
self.userStorage.update()
} else {
self._temporaryLocalUser.item = self.user
}
}
@objc func collectionDidLoad(notification: Notification) {
if let userSingleton: StoredSingleton<CustomUser> = notification.object as? StoredSingleton<CustomUser> {
self.user = userSingleton.item() ?? self._temporaryLocalUser.item ?? CustomUser.placeHolder()
} else if let clubsCollection: SyncedCollection<Club> = notification.object as? SyncedCollection<Club> {
self._fixMissingClubCreatorIfNecessary(clubsCollection)
} else if let eventsCollection: SyncedCollection<Event> = notification.object as? SyncedCollection<Event> {
self._fixMissingEventCreatorIfNecessary(eventsCollection)
}
if Store.main.fileCollectionsAllLoaded() {
AutomaticPatcher.applyAllWhenApplicable()
}
}
fileprivate func _fixMissingClubCreatorIfNecessary(_ clubsCollection: SyncedCollection<Club>) {
for club in clubsCollection {
if let userId = StoreCenter.main.userId, club.creator == nil {
club.creator = userId
self.userStorage.item()?.addClub(club)
self.userStorage.update()
clubsCollection.writeChangeAndInsertOnServer(instance: club)
}
}
}
fileprivate func _fixMissingEventCreatorIfNecessary(_ eventsCollection: SyncedCollection<Event>) {
for event in eventsCollection {
if let userId = StoreCenter.main.userId, event.creator == nil {
event.creator = userId
do {
try event.insertOnServer()
} catch {
Logger.error(error)
}
}
}
}
@objc func collectionDidUpdate(notification: Notification) {
self.objectWillChange.send()
}
@objc func _willEnterForegroundNotification() {
Task {
try await self.purchases.loadDataFromServerIfAllowed(clear: true)
}
}
func disconnect() {
Task {
if await StoreCenter.main.hasPendingAPICalls() {
// todo qu'est ce qu'on fait des API Call ?
}
do {
let services = try StoreCenter.main.service()
try await services.logout()
} catch {
Logger.error(error)
}
DispatchQueue.main.async {
self._localDisconnect()
}
}
}
func deleteAccount() {
Task {
do {
let services = try StoreCenter.main.service()
try await services.deleteAccount()
} catch {
Logger.error(error)
}
DispatchQueue.main.async {
self._localDisconnect()
}
}
}
func deleteTournament(_ tournament: Tournament) {
let event = tournament.eventObject()
let isLastTournament = event?.tournaments.count == 1
self.tournaments.delete(instance: tournament)
if let event, isLastTournament {
self.events.delete(instance: event)
}
StoreCenter.main.destroyStore(identifier: tournament.id)
}
fileprivate func _localDisconnect() {
let tournamendIds: [String] = self.tournaments.map { $0.id }
TournamentLibrary.shared.reset()
self.tournaments.reset()
self.clubs.reset()
self.courts.reset()
self.events.reset()
self.dateIntervals.reset()
self.userStorage.reset()
self.purchases.reset()
Guard.main.disconnect()
StoreCenter.main.disconnect()
for tournament in tournamendIds {
StoreCenter.main.destroyStore(identifier: tournament.id)
}
self.user = self._temporaryLocalUser.item ?? CustomUser.placeHolder()
self.user.clubs.removeAll()
}
func tryUserUpdate(userCopy: CustomUser) async throws {
try await self.userStorage.tryPutBeforeUpdating(userCopy)
}
// func copyToLocalServer(tournament: Tournament) {
//
// Task {
// do {
//
// if let url = PListReader.readString(plist: "local", key: "local_server"),
// let login = PListReader.readString(plist: "local", key: "username"),
// let pass = PListReader.readString(plist: "local", key: "password") {
// let service = Services(url: url)
// let _: CustomUser = try await service.login(username: login, password: pass)
//
// tournament.event = nil
// _ = try await service.post(tournament)
//
// for groupStage in tournament.groupStages() {
// _ = try await service.post(groupStage)
// }
// for round in tournament.rounds() {
// try await self._insertRoundAndChildren(round: round, service: service)
// }
// for teamRegistration in tournament.unsortedTeams() {
// _ = try await service.post(teamRegistration)
// for playerRegistration in teamRegistration.unsortedPlayers() {
// _ = try await service.post(playerRegistration)
// }
// }
// for groupStage in tournament.groupStages() {
// for match in groupStage._matches() {
// try await self._insertMatch(match: match, service: service)
// }
// }
// for round in tournament.allRounds() {
// for match in round._matches() {
// try await self._insertMatch(match: match, service: service)
// }
// }
//
// }
// } catch {
// Logger.error(error)
// }
// }
//
// }
//
// fileprivate func _insertRoundAndChildren(round: Round, service: Services) async throws {
// _ = try await service.post(round)
// for loserRound in round.loserRounds() {
// try await self._insertRoundAndChildren(round: loserRound, service: service)
// }
// }
//
// fileprivate func _insertMatch(match: Match, service: Services) async throws {
// _ = try await service.post(match)
// for teamScore in match.teamScores {
// _ = try await service.post(teamScore)
// }
//
// }
// MARK: - Convenience
private var _lastRunningCheckDate: Date? = nil
private var _cachedRunningMatches: [Match]? = nil
func runningMatches() -> [Match] {
let dateNow : Date = Date()
if let lastCheck = _lastRunningCheckDate,
let cachedMatches = _cachedRunningMatches,
dateNow.timeIntervalSince(lastCheck) < 5 {
return cachedMatches
}
let lastTournaments = self.tournaments.filter { $0.isDeleted == false && $0.startDate <= dateNow && $0.hasEnded() == false }.sorted(by: \Tournament.startDate, order: .descending).prefix(10)
var runningMatches: [Match] = []
for tournament in lastTournaments {
if let store = tournament.tournamentStore {
let matches = store.matches.filter { match in
match.disabled == false && match.isRunning()
}
runningMatches.append(contentsOf: matches)
}
}
_lastRunningCheckDate = dateNow
_cachedRunningMatches = runningMatches
return _cachedRunningMatches!
}
private var _lastRunningAndNextCheckDate: Date? = nil
private var _cachedRunningAndNextMatches: [Match]? = nil
func runningAndNextMatches() -> [Match] {
let dateNow : Date = Date()
if let lastCheck = _lastRunningAndNextCheckDate,
let cachedMatches = _cachedRunningAndNextMatches,
dateNow.timeIntervalSince(lastCheck) < 5 {
return cachedMatches
}
let lastTournaments = self.tournaments.filter { $0.isDeleted == false && $0.startDate <= dateNow && $0.hasEnded() == false }.sorted(by: \Tournament.startDate, order: .descending).prefix(10)
var runningMatches: [Match] = []
for tournament in lastTournaments {
if let store = tournament.tournamentStore {
let matches = store.matches.filter { match in
match.disabled == false && match.startDate != nil && match.endDate == nil }
runningMatches.append(contentsOf: matches)
}
}
_lastRunningAndNextCheckDate = dateNow
_cachedRunningAndNextMatches = runningMatches
return _cachedRunningAndNextMatches!
}
private var _lastEndCheckDate: Date? = nil
private var _cachedEndMatches: [Match]? = nil
func endMatches() -> [Match] {
let dateNow : Date = Date()
if let lastCheck = _lastEndCheckDate,
let cachedMatches = _cachedEndMatches,
dateNow.timeIntervalSince(lastCheck) < 5 {
return cachedMatches
}
let lastTournaments = self.tournaments.filter { $0.isDeleted == false && $0.startDate <= dateNow && $0.hasEnded() == false }.sorted(by: \Tournament.startDate, order: .descending).prefix(10)
var runningMatches: [Match] = []
for tournament in lastTournaments {
if let store = tournament.tournamentStore {
let matches = store.matches.filter { match in
match.disabled == false && match.hasEnded() }
runningMatches.append(contentsOf: matches)
}
}
_lastEndCheckDate = dateNow
_cachedEndMatches = runningMatches.sorted(by: \.endDate!, order: .descending)
return _cachedEndMatches!
}
func subscriptionUnitlyPayedTournaments(after date: Date) -> Int {
return DataStore.shared.tournaments.count(where: { tournament in
tournament.creationDate > date &&
tournament.payment == .subscriptionUnit &&
tournament.isCanceled == false &&
tournament.isDeleted == false })
}
}

@ -1,76 +0,0 @@
//
// DateInterval.swift
// PadelClub
//
// Created by Razmig Sarkissian on 19/04/2024.
//
import Foundation
import SwiftUI
import LeStorage
@Observable
final class DateInterval: BaseDateInterval {
// static func resourceName() -> String { return "date-intervals" }
// static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
// static func filterByStoreIdentifier() -> Bool { return false }
// static var relationshipNames: [String] = []
//
// var id: String = Store.randomId()
// var lastUpdate: Date
// var event: String
// var courtIndex: Int
// var startDate: Date
// var endDate: Date
internal init(event: String, courtIndex: Int, startDate: Date, endDate: Date) {
super.init(event: event, courtIndex: courtIndex, startDate: startDate, endDate: endDate)
// self.lastUpdate = Date()
// self.event = event
// self.courtIndex = courtIndex
// self.startDate = startDate
// self.endDate = endDate
}
required init(from decoder: any Decoder) throws {
try super.init(from: decoder)
}
required public init() {
super.init()
}
var range: Range<Date> {
startDate..<endDate
}
func isSingleDay() -> Bool {
Calendar.current.isDate(startDate, inSameDayAs: endDate)
}
func isDateInside(_ date: Date) -> Bool {
date >= startDate && date <= endDate
}
func isDateOutside(_ date: Date) -> Bool {
date <= startDate && date <= endDate && date >= startDate && date >= endDate
}
override func deleteDependencies() {
}
// enum CodingKeys: String, CodingKey {
// case _id = "id"
// case _lastUpdate = "lastUpdate"
// case _event = "event"
// case _courtIndex = "courtIndex"
// case _startDate = "startDate"
// case _endDate = "endDate"
// }
func insertOnServer() throws {
DataStore.shared.dateIntervals.writeChangeAndInsertOnServer(instance: self)
}
}

@ -1,97 +0,0 @@
//
// DrawLog.swift
// PadelClub
//
// Created by razmig on 22/10/2024.
//
import Foundation
import SwiftUI
import LeStorage
@Observable
final class DrawLog: BaseDrawLog, SideStorable {
func tournamentObject() -> Tournament? {
Store.main.findById(self.tournament)
}
func computedBracketPosition() -> Int {
drawMatchIndex * 2 + drawTeamPosition.rawValue
}
func updateTeamBracketPosition(_ team: TeamRegistration) {
guard let match = drawMatch() else { return }
let seedPosition: Int = match.lockAndGetSeedPosition(atTeamPosition: drawTeamPosition)
team.bracketPosition = seedPosition
tournamentObject()?.updateTeamScores(in: seedPosition)
}
func exportedDrawLog() -> String {
[drawType.localizedDrawType(), drawDate.localizedDate(), localizedDrawLogLabel(), localizedDrawBranch()].filter({ $0.isEmpty == false }).joined(separator: " ")
}
func localizedDrawSeedLabel() -> String {
return "\(drawType.localizedDrawType()) #\(drawSeed + 1)"
}
func localizedDrawLogLabel() -> String {
return [localizedDrawSeedLabel(), positionLabel()].filter({ $0.isEmpty == false }).joined(separator: " -> ")
}
func localizedDrawBranch() -> String {
switch drawType {
case .seed:
return drawTeamPosition.localizedBranchLabel()
default:
return ""
}
}
func drawMatch() -> Match? {
switch drawType {
case .seed:
let roundIndex = RoundRule.roundIndex(fromMatchIndex: drawMatchIndex)
return tournamentStore?.rounds.first(where: { $0.parent == nil && $0.index == roundIndex })?._matches().first(where: { $0.index == drawMatchIndex })
default:
return nil
}
}
func positionLabel() -> String {
return drawMatch()?.roundAndMatchTitle() ?? ""
}
func roundLabel() -> String {
return drawMatch()?.roundTitle() ?? ""
}
func matchLabel() -> String {
return drawMatch()?.matchTitle() ?? ""
}
var tournamentStore: TournamentStore? {
return TournamentLibrary.shared.store(tournamentId: self.tournament)
}
override func deleteDependencies() {
}
}
enum DrawType: Int, Codable {
case seed
case groupStage
case court
func localizedDrawType() -> String {
switch self {
case .seed:
return "Tête de série"
case .groupStage:
return "Poule"
case .court:
return "Terrain"
}
}
}

@ -1,100 +0,0 @@
//
// Event_v2.swift
// Padel Tournament
//
// Created by razmig on 10/03/2024.
//
import Foundation
import LeStorage
import SwiftUI
@Observable
final class Event: BaseEvent {
internal init(creator: String? = nil, club: String? = nil, name: String? = nil, tenupId: String? = nil) {
super.init(creator: creator, club: club, name: name, tenupId: tenupId)
self.relatedUser = creator
}
required init(from decoder: Decoder) throws {
try super.init(from: decoder)
}
required public init() {
super.init()
}
override func deleteDependencies() {
let tournaments = self.tournaments
for tournament in tournaments {
tournament.deleteDependencies()
}
DataStore.shared.tournaments.deleteDependencies(tournaments)
let courtsUnavailabilities = self.courtsUnavailability
for courtsUnavailability in courtsUnavailabilities {
courtsUnavailability.deleteDependencies()
}
DataStore.shared.dateIntervals.deleteDependencies(courtsUnavailabilities)
}
// MARK: - Computed dependencies
var tournaments: [Tournament] {
DataStore.shared.tournaments.filter { $0.event == self.id && $0.isDeleted == false }
}
func clubObject() -> Club? {
guard let club else { return nil }
return Store.main.findById(club)
}
var courtsUnavailability: [DateInterval] {
DataStore.shared.dateIntervals.filter({ $0.event == id })
}
// MARK: -
func eventCourtCount() -> Int {
tournaments.map { $0.courtCount }.max() ?? 2
}
func eventStartDate() -> Date {
tournaments.map { $0.startDate }.min() ?? Date()
}
func eventDayDuration() -> Int {
tournaments.map { $0.dayDuration }.max() ?? 1
}
func eventTitle() -> String {
if let name, name.isEmpty == false {
return name
} else {
return "Événement"
}
}
func existingBuild(_ build: any TournamentBuildHolder) -> Tournament? {
tournaments.first(where: { $0.isSameBuild(build) })
}
func tournamentsCourtsUsed(exluding tournamentId: String) -> [DateInterval] {
tournaments.filter { $0.id != tournamentId }.flatMap({ tournament in
tournament.getPlayedMatchDateIntervals(in: self)
})
}
func insertOnServer() throws {
DataStore.shared.events.writeChangeAndInsertOnServer(instance: self)
for tournament in self.tournaments {
try tournament.insertOnServer()
}
for dataInterval in self.courtsUnavailability {
try dataInterval.insertOnServer()
}
}
}

@ -6,25 +6,8 @@
import Foundation import Foundation
import CoreLocation import CoreLocation
import LeStorage import LeStorage
import PadelClubData
enum DayPeriod: Int, CaseIterable, Identifiable, Codable {
var id: Int { self.rawValue }
case all
case weekend
case week
func localizedDayPeriodLabel() -> String {
switch self {
case .all:
return "n'importe"
case .week:
return "la semaine"
case .weekend:
return "le week-end"
}
}
}
// MARK: - FederalTournament // MARK: - FederalTournament
struct FederalTournament: Identifiable, Codable { struct FederalTournament: Identifiable, Codable {

@ -6,6 +6,7 @@
// //
import Foundation import Foundation
import PadelClubData
protocol FederalTournamentHolder { protocol FederalTournamentHolder {
var holderId: String { get } var holderId: String { get }

@ -1,150 +0,0 @@
// Generated by SwiftModelGenerator
// Do not modify this file manually
import Foundation
import LeStorage
import SwiftUI
@Observable
class BaseClub: SyncedModelObject, SyncedStorable {
static func resourceName() -> String { return "clubs" }
static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
static var copyServerResponse: Bool = true
var id: String = Store.randomId()
var creator: String? = nil
var name: String = ""
var acronym: String = ""
var phone: String? = nil
var code: String? = nil
var address: String? = nil
var city: String? = nil
var zipCode: String? = nil
var latitude: Double? = nil
var longitude: Double? = nil
var courtCount: Int = 2
var broadcastCode: String? = nil
var timezone: String? = TimeZone.current.identifier
init(
id: String = Store.randomId(),
creator: String? = nil,
name: String = "",
acronym: String = "",
phone: String? = nil,
code: String? = nil,
address: String? = nil,
city: String? = nil,
zipCode: String? = nil,
latitude: Double? = nil,
longitude: Double? = nil,
courtCount: Int = 2,
broadcastCode: String? = nil,
timezone: String? = TimeZone.current.identifier
) {
super.init()
self.id = id
self.creator = creator
self.name = name
self.acronym = acronym
self.phone = phone
self.code = code
self.address = address
self.city = city
self.zipCode = zipCode
self.latitude = latitude
self.longitude = longitude
self.courtCount = courtCount
self.broadcastCode = broadcastCode
self.timezone = timezone
}
required public override init() {
super.init()
}
enum CodingKeys: String, CodingKey {
case _id = "id"
case _creator = "creator"
case _name = "name"
case _acronym = "acronym"
case _phone = "phone"
case _code = "code"
case _address = "address"
case _city = "city"
case _zipCode = "zipCode"
case _latitude = "latitude"
case _longitude = "longitude"
case _courtCount = "courtCount"
case _broadcastCode = "broadcastCode"
case _timezone = "timezone"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId()
self.creator = try container.decodeIfPresent(String.self, forKey: ._creator) ?? nil
self.name = try container.decodeIfPresent(String.self, forKey: ._name) ?? ""
self.acronym = try container.decodeIfPresent(String.self, forKey: ._acronym) ?? ""
self.phone = try container.decodeIfPresent(String.self, forKey: ._phone) ?? nil
self.code = try container.decodeIfPresent(String.self, forKey: ._code) ?? nil
self.address = try container.decodeIfPresent(String.self, forKey: ._address) ?? nil
self.city = try container.decodeIfPresent(String.self, forKey: ._city) ?? nil
self.zipCode = try container.decodeIfPresent(String.self, forKey: ._zipCode) ?? nil
self.latitude = try container.decodeIfPresent(Double.self, forKey: ._latitude) ?? nil
self.longitude = try container.decodeIfPresent(Double.self, forKey: ._longitude) ?? nil
self.courtCount = try container.decodeIfPresent(Int.self, forKey: ._courtCount) ?? 2
self.broadcastCode = try container.decodeIfPresent(String.self, forKey: ._broadcastCode) ?? nil
self.timezone = try container.decodeIfPresent(String.self, forKey: ._timezone) ?? TimeZone.current.identifier
try super.init(from: decoder)
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.id, forKey: ._id)
try container.encode(self.creator, forKey: ._creator)
try container.encode(self.name, forKey: ._name)
try container.encode(self.acronym, forKey: ._acronym)
try container.encode(self.phone, forKey: ._phone)
try container.encode(self.code, forKey: ._code)
try container.encode(self.address, forKey: ._address)
try container.encode(self.city, forKey: ._city)
try container.encode(self.zipCode, forKey: ._zipCode)
try container.encode(self.latitude, forKey: ._latitude)
try container.encode(self.longitude, forKey: ._longitude)
try container.encode(self.courtCount, forKey: ._courtCount)
try container.encode(self.broadcastCode, forKey: ._broadcastCode)
try container.encode(self.timezone, forKey: ._timezone)
try super.encode(to: encoder)
}
func creatorValue() -> CustomUser? {
guard let creator = self.creator else { return nil }
return Store.main.findById(creator)
}
func copy(from other: any Storable) {
guard let club = other as? BaseClub else { return }
self.id = club.id
self.creator = club.creator
self.name = club.name
self.acronym = club.acronym
self.phone = club.phone
self.code = club.code
self.address = club.address
self.city = club.city
self.zipCode = club.zipCode
self.latitude = club.latitude
self.longitude = club.longitude
self.courtCount = club.courtCount
self.broadcastCode = club.broadcastCode
self.timezone = club.timezone
}
static func relationships() -> [Relationship] {
return [
Relationship(type: CustomUser.self, keyPath: \BaseClub.creator),
]
}
}

@ -1,93 +0,0 @@
// Generated by SwiftModelGenerator
// Do not modify this file manually
import Foundation
import LeStorage
import SwiftUI
@Observable
class BaseCourt: SyncedModelObject, SyncedStorable {
static func resourceName() -> String { return "courts" }
static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
static var copyServerResponse: Bool = false
var id: String = Store.randomId()
var index: Int = 0
var club: String = ""
var name: String? = nil
var exitAllowed: Bool = false
var indoor: Bool = false
init(
id: String = Store.randomId(),
index: Int = 0,
club: String = "",
name: String? = nil,
exitAllowed: Bool = false,
indoor: Bool = false
) {
super.init()
self.id = id
self.index = index
self.club = club
self.name = name
self.exitAllowed = exitAllowed
self.indoor = indoor
}
required public override init() {
super.init()
}
enum CodingKeys: String, CodingKey {
case _id = "id"
case _index = "index"
case _club = "club"
case _name = "name"
case _exitAllowed = "exitAllowed"
case _indoor = "indoor"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId()
self.index = try container.decodeIfPresent(Int.self, forKey: ._index) ?? 0
self.club = try container.decodeIfPresent(String.self, forKey: ._club) ?? ""
self.name = try container.decodeIfPresent(String.self, forKey: ._name) ?? nil
self.exitAllowed = try container.decodeIfPresent(Bool.self, forKey: ._exitAllowed) ?? false
self.indoor = try container.decodeIfPresent(Bool.self, forKey: ._indoor) ?? false
try super.init(from: decoder)
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.id, forKey: ._id)
try container.encode(self.index, forKey: ._index)
try container.encode(self.club, forKey: ._club)
try container.encode(self.name, forKey: ._name)
try container.encode(self.exitAllowed, forKey: ._exitAllowed)
try container.encode(self.indoor, forKey: ._indoor)
try super.encode(to: encoder)
}
func clubValue() -> Club? {
return Store.main.findById(club)
}
func copy(from other: any Storable) {
guard let court = other as? BaseCourt else { return }
self.id = court.id
self.index = court.index
self.club = court.club
self.name = court.name
self.exitAllowed = court.exitAllowed
self.indoor = court.indoor
}
static func relationships() -> [Relationship] {
return [
Relationship(type: Club.self, keyPath: \BaseCourt.club),
]
}
}

@ -1,262 +0,0 @@
// Generated by SwiftModelGenerator
// Do not modify this file manually
import Foundation
import LeStorage
import SwiftUI
@Observable
class BaseCustomUser: SyncedModelObject, SyncedStorable {
static func resourceName() -> String { return "users" }
static func tokenExemptedMethods() -> [HTTPMethod] { return [.post] }
static var copyServerResponse: Bool = false
var id: String = Store.randomId()
var username: String = ""
var email: String = ""
var clubs: [String] = []
var umpireCode: String? = nil
var licenceId: String? = nil
var firstName: String = ""
var lastName: String = ""
var phone: String? = nil
var country: String? = nil
var summonsMessageBody: String? = nil
var summonsMessageSignature: String? = nil
var summonsAvailablePaymentMethods: String? = nil
var summonsDisplayFormat: Bool = false
var summonsDisplayEntryFee: Bool = false
var summonsUseFullCustomMessage: Bool = false
var matchFormatsDefaultDuration: [MatchFormat: Int]? = nil
var bracketMatchFormatPreference: MatchFormat? = nil
var groupStageMatchFormatPreference: MatchFormat? = nil
var loserBracketMatchFormatPreference: MatchFormat? = nil
var loserBracketMode: LoserBracketMode = .automatic
var disableRankingFederalRuling: Bool = false
var deviceId: String? = nil
var agents: [String] = []
var userRole: Int? = nil
var registrationPaymentMode: RegistrationPaymentMode = RegistrationPaymentMode.disabled
var umpireCustomMail: String? = nil
var umpireCustomContact: String? = nil
var umpireCustomPhone: String? = nil
var hideUmpireMail: Bool = false
var hideUmpirePhone: Bool = true
init(
id: String = Store.randomId(),
username: String = "",
email: String = "",
clubs: [String] = [],
umpireCode: String? = nil,
licenceId: String? = nil,
firstName: String = "",
lastName: String = "",
phone: String? = nil,
country: String? = nil,
summonsMessageBody: String? = nil,
summonsMessageSignature: String? = nil,
summonsAvailablePaymentMethods: String? = nil,
summonsDisplayFormat: Bool = false,
summonsDisplayEntryFee: Bool = false,
summonsUseFullCustomMessage: Bool = false,
matchFormatsDefaultDuration: [MatchFormat: Int]? = nil,
bracketMatchFormatPreference: MatchFormat? = nil,
groupStageMatchFormatPreference: MatchFormat? = nil,
loserBracketMatchFormatPreference: MatchFormat? = nil,
loserBracketMode: LoserBracketMode = .automatic,
disableRankingFederalRuling: Bool = false,
deviceId: String? = nil,
agents: [String] = [],
userRole: Int? = nil,
registrationPaymentMode: RegistrationPaymentMode = RegistrationPaymentMode.disabled,
umpireCustomMail: String? = nil,
umpireCustomContact: String? = nil,
umpireCustomPhone: String? = nil,
hideUmpireMail: Bool = false,
hideUmpirePhone: Bool = true
) {
super.init()
self.id = id
self.username = username
self.email = email
self.clubs = clubs
self.umpireCode = umpireCode
self.licenceId = licenceId
self.firstName = firstName
self.lastName = lastName
self.phone = phone
self.country = country
self.summonsMessageBody = summonsMessageBody
self.summonsMessageSignature = summonsMessageSignature
self.summonsAvailablePaymentMethods = summonsAvailablePaymentMethods
self.summonsDisplayFormat = summonsDisplayFormat
self.summonsDisplayEntryFee = summonsDisplayEntryFee
self.summonsUseFullCustomMessage = summonsUseFullCustomMessage
self.matchFormatsDefaultDuration = matchFormatsDefaultDuration
self.bracketMatchFormatPreference = bracketMatchFormatPreference
self.groupStageMatchFormatPreference = groupStageMatchFormatPreference
self.loserBracketMatchFormatPreference = loserBracketMatchFormatPreference
self.loserBracketMode = loserBracketMode
self.disableRankingFederalRuling = disableRankingFederalRuling
self.deviceId = deviceId
self.agents = agents
self.userRole = userRole
self.registrationPaymentMode = registrationPaymentMode
self.umpireCustomMail = umpireCustomMail
self.umpireCustomContact = umpireCustomContact
self.umpireCustomPhone = umpireCustomPhone
self.hideUmpireMail = hideUmpireMail
self.hideUmpirePhone = hideUmpirePhone
}
required public override init() {
super.init()
}
enum CodingKeys: String, CodingKey {
case _id = "id"
case _username = "username"
case _email = "email"
case _clubs = "clubs"
case _umpireCode = "umpireCode"
case _licenceId = "licenceId"
case _firstName = "firstName"
case _lastName = "lastName"
case _phone = "phone"
case _country = "country"
case _summonsMessageBody = "summonsMessageBody"
case _summonsMessageSignature = "summonsMessageSignature"
case _summonsAvailablePaymentMethods = "summonsAvailablePaymentMethods"
case _summonsDisplayFormat = "summonsDisplayFormat"
case _summonsDisplayEntryFee = "summonsDisplayEntryFee"
case _summonsUseFullCustomMessage = "summonsUseFullCustomMessage"
case _matchFormatsDefaultDuration = "matchFormatsDefaultDuration"
case _bracketMatchFormatPreference = "bracketMatchFormatPreference"
case _groupStageMatchFormatPreference = "groupStageMatchFormatPreference"
case _loserBracketMatchFormatPreference = "loserBracketMatchFormatPreference"
case _loserBracketMode = "loserBracketMode"
case _disableRankingFederalRuling = "disableRankingFederalRuling"
case _deviceId = "deviceId"
case _agents = "agents"
case _userRole = "userRole"
case _registrationPaymentMode = "registrationPaymentMode"
case _umpireCustomMail = "umpireCustomMail"
case _umpireCustomContact = "umpireCustomContact"
case _umpireCustomPhone = "umpireCustomPhone"
case _hideUmpireMail = "hideUmpireMail"
case _hideUmpirePhone = "hideUmpirePhone"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId()
self.username = try container.decodeIfPresent(String.self, forKey: ._username) ?? ""
self.email = try container.decodeIfPresent(String.self, forKey: ._email) ?? ""
self.clubs = try container.decodeIfPresent([String].self, forKey: ._clubs) ?? []
self.umpireCode = try container.decodeIfPresent(String.self, forKey: ._umpireCode) ?? nil
self.licenceId = try container.decodeIfPresent(String.self, forKey: ._licenceId) ?? nil
self.firstName = try container.decodeIfPresent(String.self, forKey: ._firstName) ?? ""
self.lastName = try container.decodeIfPresent(String.self, forKey: ._lastName) ?? ""
self.phone = try container.decodeIfPresent(String.self, forKey: ._phone) ?? nil
self.country = try container.decodeIfPresent(String.self, forKey: ._country) ?? nil
self.summonsMessageBody = try container.decodeIfPresent(String.self, forKey: ._summonsMessageBody) ?? nil
self.summonsMessageSignature = try container.decodeIfPresent(String.self, forKey: ._summonsMessageSignature) ?? nil
self.summonsAvailablePaymentMethods = try container.decodeIfPresent(String.self, forKey: ._summonsAvailablePaymentMethods) ?? nil
self.summonsDisplayFormat = try container.decodeIfPresent(Bool.self, forKey: ._summonsDisplayFormat) ?? false
self.summonsDisplayEntryFee = try container.decodeIfPresent(Bool.self, forKey: ._summonsDisplayEntryFee) ?? false
self.summonsUseFullCustomMessage = try container.decodeIfPresent(Bool.self, forKey: ._summonsUseFullCustomMessage) ?? false
self.matchFormatsDefaultDuration = try container.decodeIfPresent([MatchFormat: Int].self, forKey: ._matchFormatsDefaultDuration) ?? nil
self.bracketMatchFormatPreference = try container.decodeIfPresent(MatchFormat.self, forKey: ._bracketMatchFormatPreference) ?? nil
self.groupStageMatchFormatPreference = try container.decodeIfPresent(MatchFormat.self, forKey: ._groupStageMatchFormatPreference) ?? nil
self.loserBracketMatchFormatPreference = try container.decodeIfPresent(MatchFormat.self, forKey: ._loserBracketMatchFormatPreference) ?? nil
self.loserBracketMode = try container.decodeIfPresent(LoserBracketMode.self, forKey: ._loserBracketMode) ?? .automatic
self.disableRankingFederalRuling = try container.decodeIfPresent(Bool.self, forKey: ._disableRankingFederalRuling) ?? false
self.deviceId = try container.decodeIfPresent(String.self, forKey: ._deviceId) ?? nil
self.agents = try container.decodeIfPresent([String].self, forKey: ._agents) ?? []
self.userRole = try container.decodeIfPresent(Int.self, forKey: ._userRole) ?? nil
self.registrationPaymentMode = try container.decodeIfPresent(RegistrationPaymentMode.self, forKey: ._registrationPaymentMode) ?? RegistrationPaymentMode.disabled
self.umpireCustomMail = try container.decodeIfPresent(String.self, forKey: ._umpireCustomMail) ?? nil
self.umpireCustomContact = try container.decodeIfPresent(String.self, forKey: ._umpireCustomContact) ?? nil
self.umpireCustomPhone = try container.decodeIfPresent(String.self, forKey: ._umpireCustomPhone) ?? nil
self.hideUmpireMail = try container.decodeIfPresent(Bool.self, forKey: ._hideUmpireMail) ?? false
self.hideUmpirePhone = try container.decodeIfPresent(Bool.self, forKey: ._hideUmpirePhone) ?? true
try super.init(from: decoder)
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.id, forKey: ._id)
try container.encode(self.username, forKey: ._username)
try container.encode(self.email, forKey: ._email)
try container.encode(self.clubs, forKey: ._clubs)
try container.encode(self.umpireCode, forKey: ._umpireCode)
try container.encode(self.licenceId, forKey: ._licenceId)
try container.encode(self.firstName, forKey: ._firstName)
try container.encode(self.lastName, forKey: ._lastName)
try container.encode(self.phone, forKey: ._phone)
try container.encode(self.country, forKey: ._country)
try container.encode(self.summonsMessageBody, forKey: ._summonsMessageBody)
try container.encode(self.summonsMessageSignature, forKey: ._summonsMessageSignature)
try container.encode(self.summonsAvailablePaymentMethods, forKey: ._summonsAvailablePaymentMethods)
try container.encode(self.summonsDisplayFormat, forKey: ._summonsDisplayFormat)
try container.encode(self.summonsDisplayEntryFee, forKey: ._summonsDisplayEntryFee)
try container.encode(self.summonsUseFullCustomMessage, forKey: ._summonsUseFullCustomMessage)
try container.encode(self.matchFormatsDefaultDuration, forKey: ._matchFormatsDefaultDuration)
try container.encode(self.bracketMatchFormatPreference, forKey: ._bracketMatchFormatPreference)
try container.encode(self.groupStageMatchFormatPreference, forKey: ._groupStageMatchFormatPreference)
try container.encode(self.loserBracketMatchFormatPreference, forKey: ._loserBracketMatchFormatPreference)
try container.encode(self.loserBracketMode, forKey: ._loserBracketMode)
try container.encode(self.disableRankingFederalRuling, forKey: ._disableRankingFederalRuling)
try container.encode(self.deviceId, forKey: ._deviceId)
try container.encode(self.agents, forKey: ._agents)
try container.encode(self.userRole, forKey: ._userRole)
try container.encode(self.registrationPaymentMode, forKey: ._registrationPaymentMode)
try container.encode(self.umpireCustomMail, forKey: ._umpireCustomMail)
try container.encode(self.umpireCustomContact, forKey: ._umpireCustomContact)
try container.encode(self.umpireCustomPhone, forKey: ._umpireCustomPhone)
try container.encode(self.hideUmpireMail, forKey: ._hideUmpireMail)
try container.encode(self.hideUmpirePhone, forKey: ._hideUmpirePhone)
try super.encode(to: encoder)
}
func copy(from other: any Storable) {
guard let customuser = other as? BaseCustomUser else { return }
self.id = customuser.id
self.username = customuser.username
self.email = customuser.email
self.clubs = customuser.clubs
self.umpireCode = customuser.umpireCode
self.licenceId = customuser.licenceId
self.firstName = customuser.firstName
self.lastName = customuser.lastName
self.phone = customuser.phone
self.country = customuser.country
self.summonsMessageBody = customuser.summonsMessageBody
self.summonsMessageSignature = customuser.summonsMessageSignature
self.summonsAvailablePaymentMethods = customuser.summonsAvailablePaymentMethods
self.summonsDisplayFormat = customuser.summonsDisplayFormat
self.summonsDisplayEntryFee = customuser.summonsDisplayEntryFee
self.summonsUseFullCustomMessage = customuser.summonsUseFullCustomMessage
self.matchFormatsDefaultDuration = customuser.matchFormatsDefaultDuration
self.bracketMatchFormatPreference = customuser.bracketMatchFormatPreference
self.groupStageMatchFormatPreference = customuser.groupStageMatchFormatPreference
self.loserBracketMatchFormatPreference = customuser.loserBracketMatchFormatPreference
self.loserBracketMode = customuser.loserBracketMode
self.disableRankingFederalRuling = customuser.disableRankingFederalRuling
self.deviceId = customuser.deviceId
self.agents = customuser.agents
self.userRole = customuser.userRole
self.registrationPaymentMode = customuser.registrationPaymentMode
self.umpireCustomMail = customuser.umpireCustomMail
self.umpireCustomContact = customuser.umpireCustomContact
self.umpireCustomPhone = customuser.umpireCustomPhone
self.hideUmpireMail = customuser.hideUmpireMail
self.hideUmpirePhone = customuser.hideUmpirePhone
}
static func relationships() -> [Relationship] {
return []
}
}

@ -1,80 +0,0 @@
// Generated by SwiftModelGenerator
// Do not modify this file manually
import Foundation
import LeStorage
import SwiftUI
@Observable
class BaseDateInterval: SyncedModelObject, SyncedStorable {
static func resourceName() -> String { return "date-intervals" }
static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
static var copyServerResponse: Bool = false
var id: String = Store.randomId()
var event: String = ""
var courtIndex: Int = 0
var startDate: Date = Date()
var endDate: Date = Date()
init(
id: String = Store.randomId(),
event: String = "",
courtIndex: Int = 0,
startDate: Date = Date(),
endDate: Date = Date()
) {
super.init()
self.id = id
self.event = event
self.courtIndex = courtIndex
self.startDate = startDate
self.endDate = endDate
}
required public override init() {
super.init()
}
enum CodingKeys: String, CodingKey {
case _id = "id"
case _event = "event"
case _courtIndex = "courtIndex"
case _startDate = "startDate"
case _endDate = "endDate"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId()
self.event = try container.decodeIfPresent(String.self, forKey: ._event) ?? ""
self.courtIndex = try container.decodeIfPresent(Int.self, forKey: ._courtIndex) ?? 0
self.startDate = try container.decodeIfPresent(Date.self, forKey: ._startDate) ?? Date()
self.endDate = try container.decodeIfPresent(Date.self, forKey: ._endDate) ?? Date()
try super.init(from: decoder)
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.id, forKey: ._id)
try container.encode(self.event, forKey: ._event)
try container.encode(self.courtIndex, forKey: ._courtIndex)
try container.encode(self.startDate, forKey: ._startDate)
try container.encode(self.endDate, forKey: ._endDate)
try super.encode(to: encoder)
}
func copy(from other: any Storable) {
guard let dateinterval = other as? BaseDateInterval else { return }
self.id = dateinterval.id
self.event = dateinterval.event
self.courtIndex = dateinterval.courtIndex
self.startDate = dateinterval.startDate
self.endDate = dateinterval.endDate
}
static func relationships() -> [Relationship] {
return []
}
}

@ -1,100 +0,0 @@
// Generated by SwiftModelGenerator
// Do not modify this file manually
import Foundation
import LeStorage
import SwiftUI
@Observable
class BaseDrawLog: SyncedModelObject, SyncedStorable {
static func resourceName() -> String { return "draw-logs" }
static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
static var copyServerResponse: Bool = false
var id: String = Store.randomId()
var tournament: String = ""
var drawDate: Date = Date()
var drawSeed: Int = 0
var drawMatchIndex: Int = 0
var drawTeamPosition: TeamPosition = TeamPosition.one
var drawType: DrawType = DrawType.seed
init(
id: String = Store.randomId(),
tournament: String = "",
drawDate: Date = Date(),
drawSeed: Int = 0,
drawMatchIndex: Int = 0,
drawTeamPosition: TeamPosition = TeamPosition.one,
drawType: DrawType = DrawType.seed
) {
super.init()
self.id = id
self.tournament = tournament
self.drawDate = drawDate
self.drawSeed = drawSeed
self.drawMatchIndex = drawMatchIndex
self.drawTeamPosition = drawTeamPosition
self.drawType = drawType
}
required public override init() {
super.init()
}
enum CodingKeys: String, CodingKey {
case _id = "id"
case _tournament = "tournament"
case _drawDate = "drawDate"
case _drawSeed = "drawSeed"
case _drawMatchIndex = "drawMatchIndex"
case _drawTeamPosition = "drawTeamPosition"
case _drawType = "drawType"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId()
self.tournament = try container.decodeIfPresent(String.self, forKey: ._tournament) ?? ""
self.drawDate = try container.decodeIfPresent(Date.self, forKey: ._drawDate) ?? Date()
self.drawSeed = try container.decodeIfPresent(Int.self, forKey: ._drawSeed) ?? 0
self.drawMatchIndex = try container.decodeIfPresent(Int.self, forKey: ._drawMatchIndex) ?? 0
self.drawTeamPosition = try container.decodeIfPresent(TeamPosition.self, forKey: ._drawTeamPosition) ?? TeamPosition.one
self.drawType = try container.decodeIfPresent(DrawType.self, forKey: ._drawType) ?? DrawType.seed
try super.init(from: decoder)
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.id, forKey: ._id)
try container.encode(self.tournament, forKey: ._tournament)
try container.encode(self.drawDate, forKey: ._drawDate)
try container.encode(self.drawSeed, forKey: ._drawSeed)
try container.encode(self.drawMatchIndex, forKey: ._drawMatchIndex)
try container.encode(self.drawTeamPosition, forKey: ._drawTeamPosition)
try container.encode(self.drawType, forKey: ._drawType)
try super.encode(to: encoder)
}
func tournamentValue() -> Tournament? {
return Store.main.findById(tournament)
}
func copy(from other: any Storable) {
guard let drawlog = other as? BaseDrawLog else { return }
self.id = drawlog.id
self.tournament = drawlog.tournament
self.drawDate = drawlog.drawDate
self.drawSeed = drawlog.drawSeed
self.drawMatchIndex = drawlog.drawMatchIndex
self.drawTeamPosition = drawlog.drawTeamPosition
self.drawType = drawlog.drawType
}
static func relationships() -> [Relationship] {
return [
Relationship(type: Tournament.self, keyPath: \BaseDrawLog.tournament),
]
}
}

@ -1,100 +0,0 @@
// Generated by SwiftModelGenerator
// Do not modify this file manually
import Foundation
import LeStorage
import SwiftUI
@Observable
class BaseEvent: SyncedModelObject, SyncedStorable {
static func resourceName() -> String { return "events" }
static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
static var copyServerResponse: Bool = false
var id: String = Store.randomId()
var creator: String? = nil
var club: String? = nil
var creationDate: Date = Date()
var name: String? = nil
var tenupId: String? = nil
init(
id: String = Store.randomId(),
creator: String? = nil,
club: String? = nil,
creationDate: Date = Date(),
name: String? = nil,
tenupId: String? = nil
) {
super.init()
self.id = id
self.creator = creator
self.club = club
self.creationDate = creationDate
self.name = name
self.tenupId = tenupId
}
required public override init() {
super.init()
}
enum CodingKeys: String, CodingKey {
case _id = "id"
case _creator = "creator"
case _club = "club"
case _creationDate = "creationDate"
case _name = "name"
case _tenupId = "tenupId"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId()
self.creator = try container.decodeIfPresent(String.self, forKey: ._creator) ?? nil
self.club = try container.decodeIfPresent(String.self, forKey: ._club) ?? nil
self.creationDate = try container.decodeIfPresent(Date.self, forKey: ._creationDate) ?? Date()
self.name = try container.decodeIfPresent(String.self, forKey: ._name) ?? nil
self.tenupId = try container.decodeIfPresent(String.self, forKey: ._tenupId) ?? nil
try super.init(from: decoder)
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.id, forKey: ._id)
try container.encode(self.creator, forKey: ._creator)
try container.encode(self.club, forKey: ._club)
try container.encode(self.creationDate, forKey: ._creationDate)
try container.encode(self.name, forKey: ._name)
try container.encode(self.tenupId, forKey: ._tenupId)
try super.encode(to: encoder)
}
func creatorValue() -> CustomUser? {
guard let creator = self.creator else { return nil }
return Store.main.findById(creator)
}
func clubValue() -> Club? {
guard let club = self.club else { return nil }
return Store.main.findById(club)
}
func copy(from other: any Storable) {
guard let event = other as? BaseEvent else { return }
self.id = event.id
self.creator = event.creator
self.club = event.club
self.creationDate = event.creationDate
self.name = event.name
self.tenupId = event.tenupId
}
static func relationships() -> [Relationship] {
return [
Relationship(type: CustomUser.self, keyPath: \BaseEvent.creator),
Relationship(type: Club.self, keyPath: \BaseEvent.club),
]
}
}

@ -1,107 +0,0 @@
// Generated by SwiftModelGenerator
// Do not modify this file manually
import Foundation
import LeStorage
import SwiftUI
@Observable
class BaseGroupStage: SyncedModelObject, SyncedStorable {
static func resourceName() -> String { return "group-stages" }
static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
static var copyServerResponse: Bool = false
var id: String = Store.randomId()
var tournament: String = ""
var index: Int = 0
var size: Int = 0
var format: MatchFormat? = nil
var startDate: Date? = nil
var name: String? = nil
var step: Int = 0
init(
id: String = Store.randomId(),
tournament: String = "",
index: Int = 0,
size: Int = 0,
format: MatchFormat? = nil,
startDate: Date? = nil,
name: String? = nil,
step: Int = 0
) {
super.init()
self.id = id
self.tournament = tournament
self.index = index
self.size = size
self.format = format
self.startDate = startDate
self.name = name
self.step = step
}
required public override init() {
super.init()
}
enum CodingKeys: String, CodingKey {
case _id = "id"
case _tournament = "tournament"
case _index = "index"
case _size = "size"
case _format = "format"
case _startDate = "startDate"
case _name = "name"
case _step = "step"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId()
self.tournament = try container.decodeIfPresent(String.self, forKey: ._tournament) ?? ""
self.index = try container.decodeIfPresent(Int.self, forKey: ._index) ?? 0
self.size = try container.decodeIfPresent(Int.self, forKey: ._size) ?? 0
self.format = try container.decodeIfPresent(MatchFormat.self, forKey: ._format) ?? nil
self.startDate = try container.decodeIfPresent(Date.self, forKey: ._startDate) ?? nil
self.name = try container.decodeIfPresent(String.self, forKey: ._name) ?? nil
self.step = try container.decodeIfPresent(Int.self, forKey: ._step) ?? 0
try super.init(from: decoder)
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.id, forKey: ._id)
try container.encode(self.tournament, forKey: ._tournament)
try container.encode(self.index, forKey: ._index)
try container.encode(self.size, forKey: ._size)
try container.encode(self.format, forKey: ._format)
try container.encode(self.startDate, forKey: ._startDate)
try container.encode(self.name, forKey: ._name)
try container.encode(self.step, forKey: ._step)
try super.encode(to: encoder)
}
func tournamentValue() -> Tournament? {
return Store.main.findById(tournament)
}
func copy(from other: any Storable) {
guard let groupstage = other as? BaseGroupStage else { return }
self.id = groupstage.id
self.tournament = groupstage.tournament
self.index = groupstage.index
self.size = groupstage.size
self.format = groupstage.format
self.startDate = groupstage.startDate
self.name = groupstage.name
self.step = groupstage.step
}
static func relationships() -> [Relationship] {
return [
Relationship(type: Tournament.self, keyPath: \BaseGroupStage.tournament),
]
}
}

@ -1,156 +0,0 @@
// Generated by SwiftModelGenerator
// Do not modify this file manually
import Foundation
import LeStorage
import SwiftUI
@Observable
class BaseMatch: SyncedModelObject, SyncedStorable {
static func resourceName() -> String { return "matches" }
static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
static var copyServerResponse: Bool = false
var id: String = Store.randomId()
var round: String? = nil
var groupStage: String? = nil
var startDate: Date? = nil
var endDate: Date? = nil
var index: Int = 0
var format: MatchFormat? = nil
var servingTeamId: String? = nil
var winningTeamId: String? = nil
var losingTeamId: String? = nil
var name: String? = nil
var disabled: Bool = false
var courtIndex: Int? = nil
var confirmed: Bool = false
init(
id: String = Store.randomId(),
round: String? = nil,
groupStage: String? = nil,
startDate: Date? = nil,
endDate: Date? = nil,
index: Int = 0,
format: MatchFormat? = nil,
servingTeamId: String? = nil,
winningTeamId: String? = nil,
losingTeamId: String? = nil,
name: String? = nil,
disabled: Bool = false,
courtIndex: Int? = nil,
confirmed: Bool = false
) {
super.init()
self.id = id
self.round = round
self.groupStage = groupStage
self.startDate = startDate
self.endDate = endDate
self.index = index
self.format = format
self.servingTeamId = servingTeamId
self.winningTeamId = winningTeamId
self.losingTeamId = losingTeamId
self.name = name
self.disabled = disabled
self.courtIndex = courtIndex
self.confirmed = confirmed
}
required public override init() {
super.init()
}
enum CodingKeys: String, CodingKey {
case _id = "id"
case _round = "round"
case _groupStage = "groupStage"
case _startDate = "startDate"
case _endDate = "endDate"
case _index = "index"
case _format = "format"
case _servingTeamId = "servingTeamId"
case _winningTeamId = "winningTeamId"
case _losingTeamId = "losingTeamId"
case _name = "name"
case _disabled = "disabled"
case _courtIndex = "courtIndex"
case _confirmed = "confirmed"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId()
self.round = try container.decodeIfPresent(String.self, forKey: ._round) ?? nil
self.groupStage = try container.decodeIfPresent(String.self, forKey: ._groupStage) ?? nil
self.startDate = try container.decodeIfPresent(Date.self, forKey: ._startDate) ?? nil
self.endDate = try container.decodeIfPresent(Date.self, forKey: ._endDate) ?? nil
self.index = try container.decodeIfPresent(Int.self, forKey: ._index) ?? 0
self.format = try container.decodeIfPresent(MatchFormat.self, forKey: ._format) ?? nil
self.servingTeamId = try container.decodeIfPresent(String.self, forKey: ._servingTeamId) ?? nil
self.winningTeamId = try container.decodeIfPresent(String.self, forKey: ._winningTeamId) ?? nil
self.losingTeamId = try container.decodeIfPresent(String.self, forKey: ._losingTeamId) ?? nil
self.name = try container.decodeIfPresent(String.self, forKey: ._name) ?? nil
self.disabled = try container.decodeIfPresent(Bool.self, forKey: ._disabled) ?? false
self.courtIndex = try container.decodeIfPresent(Int.self, forKey: ._courtIndex) ?? nil
self.confirmed = try container.decodeIfPresent(Bool.self, forKey: ._confirmed) ?? false
try super.init(from: decoder)
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.id, forKey: ._id)
try container.encode(self.round, forKey: ._round)
try container.encode(self.groupStage, forKey: ._groupStage)
try container.encode(self.startDate, forKey: ._startDate)
try container.encode(self.endDate, forKey: ._endDate)
try container.encode(self.index, forKey: ._index)
try container.encode(self.format, forKey: ._format)
try container.encode(self.servingTeamId, forKey: ._servingTeamId)
try container.encode(self.winningTeamId, forKey: ._winningTeamId)
try container.encode(self.losingTeamId, forKey: ._losingTeamId)
try container.encode(self.name, forKey: ._name)
try container.encode(self.disabled, forKey: ._disabled)
try container.encode(self.courtIndex, forKey: ._courtIndex)
try container.encode(self.confirmed, forKey: ._confirmed)
try super.encode(to: encoder)
}
func roundValue() -> Round? {
guard let round = self.round else { return nil }
return self.store?.findById(round)
}
func groupStageValue() -> GroupStage? {
guard let groupStage = self.groupStage else { return nil }
return self.store?.findById(groupStage)
}
func copy(from other: any Storable) {
guard let match = other as? BaseMatch else { return }
self.id = match.id
self.round = match.round
self.groupStage = match.groupStage
self.startDate = match.startDate
self.endDate = match.endDate
self.index = match.index
self.format = match.format
self.servingTeamId = match.servingTeamId
self.winningTeamId = match.winningTeamId
self.losingTeamId = match.losingTeamId
self.name = match.name
self.disabled = match.disabled
self.courtIndex = match.courtIndex
self.confirmed = match.confirmed
}
static func relationships() -> [Relationship] {
return [
Relationship(type: Round.self, keyPath: \BaseMatch.round),
Relationship(type: GroupStage.self, keyPath: \BaseMatch.groupStage),
]
}
}

@ -1,163 +0,0 @@
// Generated by SwiftModelGenerator
// Do not modify this file manually
import Foundation
import LeStorage
import SwiftUI
@Observable
class BaseMatchScheduler: BaseModelObject, Storable {
static func resourceName() -> String { return "match-scheduler" }
static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
static var copyServerResponse: Bool = false
var id: String = Store.randomId()
var tournament: String = ""
var timeDifferenceLimit: Int = 5
var loserBracketRotationDifference: Int = 0
var upperBracketRotationDifference: Int = 1
var accountUpperBracketBreakTime: Bool = true
var accountLoserBracketBreakTime: Bool = false
var randomizeCourts: Bool = true
var rotationDifferenceIsImportant: Bool = false
var shouldHandleUpperRoundSlice: Bool = false
var shouldEndRoundBeforeStartingNext: Bool = true
var groupStageChunkCount: Int? = nil
var overrideCourtsUnavailability: Bool = false
var shouldTryToFillUpCourtsAvailable: Bool = false
var courtsAvailable: Set<Int> = Set<Int>()
var simultaneousStart: Bool = true
init(
id: String = Store.randomId(),
tournament: String = "",
timeDifferenceLimit: Int = 5,
loserBracketRotationDifference: Int = 0,
upperBracketRotationDifference: Int = 1,
accountUpperBracketBreakTime: Bool = true,
accountLoserBracketBreakTime: Bool = false,
randomizeCourts: Bool = true,
rotationDifferenceIsImportant: Bool = false,
shouldHandleUpperRoundSlice: Bool = false,
shouldEndRoundBeforeStartingNext: Bool = true,
groupStageChunkCount: Int? = nil,
overrideCourtsUnavailability: Bool = false,
shouldTryToFillUpCourtsAvailable: Bool = false,
courtsAvailable: Set<Int> = Set<Int>(),
simultaneousStart: Bool = true
) {
super.init()
self.id = id
self.tournament = tournament
self.timeDifferenceLimit = timeDifferenceLimit
self.loserBracketRotationDifference = loserBracketRotationDifference
self.upperBracketRotationDifference = upperBracketRotationDifference
self.accountUpperBracketBreakTime = accountUpperBracketBreakTime
self.accountLoserBracketBreakTime = accountLoserBracketBreakTime
self.randomizeCourts = randomizeCourts
self.rotationDifferenceIsImportant = rotationDifferenceIsImportant
self.shouldHandleUpperRoundSlice = shouldHandleUpperRoundSlice
self.shouldEndRoundBeforeStartingNext = shouldEndRoundBeforeStartingNext
self.groupStageChunkCount = groupStageChunkCount
self.overrideCourtsUnavailability = overrideCourtsUnavailability
self.shouldTryToFillUpCourtsAvailable = shouldTryToFillUpCourtsAvailable
self.courtsAvailable = courtsAvailable
self.simultaneousStart = simultaneousStart
}
required public override init() {
super.init()
}
enum CodingKeys: String, CodingKey {
case _id = "id"
case _tournament = "tournament"
case _timeDifferenceLimit = "timeDifferenceLimit"
case _loserBracketRotationDifference = "loserBracketRotationDifference"
case _upperBracketRotationDifference = "upperBracketRotationDifference"
case _accountUpperBracketBreakTime = "accountUpperBracketBreakTime"
case _accountLoserBracketBreakTime = "accountLoserBracketBreakTime"
case _randomizeCourts = "randomizeCourts"
case _rotationDifferenceIsImportant = "rotationDifferenceIsImportant"
case _shouldHandleUpperRoundSlice = "shouldHandleUpperRoundSlice"
case _shouldEndRoundBeforeStartingNext = "shouldEndRoundBeforeStartingNext"
case _groupStageChunkCount = "groupStageChunkCount"
case _overrideCourtsUnavailability = "overrideCourtsUnavailability"
case _shouldTryToFillUpCourtsAvailable = "shouldTryToFillUpCourtsAvailable"
case _courtsAvailable = "courtsAvailable"
case _simultaneousStart = "simultaneousStart"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId()
self.tournament = try container.decodeIfPresent(String.self, forKey: ._tournament) ?? ""
self.timeDifferenceLimit = try container.decodeIfPresent(Int.self, forKey: ._timeDifferenceLimit) ?? 5
self.loserBracketRotationDifference = try container.decodeIfPresent(Int.self, forKey: ._loserBracketRotationDifference) ?? 0
self.upperBracketRotationDifference = try container.decodeIfPresent(Int.self, forKey: ._upperBracketRotationDifference) ?? 1
self.accountUpperBracketBreakTime = try container.decodeIfPresent(Bool.self, forKey: ._accountUpperBracketBreakTime) ?? true
self.accountLoserBracketBreakTime = try container.decodeIfPresent(Bool.self, forKey: ._accountLoserBracketBreakTime) ?? false
self.randomizeCourts = try container.decodeIfPresent(Bool.self, forKey: ._randomizeCourts) ?? true
self.rotationDifferenceIsImportant = try container.decodeIfPresent(Bool.self, forKey: ._rotationDifferenceIsImportant) ?? false
self.shouldHandleUpperRoundSlice = try container.decodeIfPresent(Bool.self, forKey: ._shouldHandleUpperRoundSlice) ?? false
self.shouldEndRoundBeforeStartingNext = try container.decodeIfPresent(Bool.self, forKey: ._shouldEndRoundBeforeStartingNext) ?? true
self.groupStageChunkCount = try container.decodeIfPresent(Int.self, forKey: ._groupStageChunkCount) ?? nil
self.overrideCourtsUnavailability = try container.decodeIfPresent(Bool.self, forKey: ._overrideCourtsUnavailability) ?? false
self.shouldTryToFillUpCourtsAvailable = try container.decodeIfPresent(Bool.self, forKey: ._shouldTryToFillUpCourtsAvailable) ?? false
self.courtsAvailable = try container.decodeIfPresent(Set<Int>.self, forKey: ._courtsAvailable) ?? Set<Int>()
self.simultaneousStart = try container.decodeIfPresent(Bool.self, forKey: ._simultaneousStart) ?? true
try super.init(from: decoder)
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.id, forKey: ._id)
try container.encode(self.tournament, forKey: ._tournament)
try container.encode(self.timeDifferenceLimit, forKey: ._timeDifferenceLimit)
try container.encode(self.loserBracketRotationDifference, forKey: ._loserBracketRotationDifference)
try container.encode(self.upperBracketRotationDifference, forKey: ._upperBracketRotationDifference)
try container.encode(self.accountUpperBracketBreakTime, forKey: ._accountUpperBracketBreakTime)
try container.encode(self.accountLoserBracketBreakTime, forKey: ._accountLoserBracketBreakTime)
try container.encode(self.randomizeCourts, forKey: ._randomizeCourts)
try container.encode(self.rotationDifferenceIsImportant, forKey: ._rotationDifferenceIsImportant)
try container.encode(self.shouldHandleUpperRoundSlice, forKey: ._shouldHandleUpperRoundSlice)
try container.encode(self.shouldEndRoundBeforeStartingNext, forKey: ._shouldEndRoundBeforeStartingNext)
try container.encode(self.groupStageChunkCount, forKey: ._groupStageChunkCount)
try container.encode(self.overrideCourtsUnavailability, forKey: ._overrideCourtsUnavailability)
try container.encode(self.shouldTryToFillUpCourtsAvailable, forKey: ._shouldTryToFillUpCourtsAvailable)
try container.encode(self.courtsAvailable, forKey: ._courtsAvailable)
try container.encode(self.simultaneousStart, forKey: ._simultaneousStart)
try super.encode(to: encoder)
}
func tournamentValue() -> Tournament? {
return Store.main.findById(tournament)
}
func copy(from other: any Storable) {
guard let matchscheduler = other as? BaseMatchScheduler else { return }
self.id = matchscheduler.id
self.tournament = matchscheduler.tournament
self.timeDifferenceLimit = matchscheduler.timeDifferenceLimit
self.loserBracketRotationDifference = matchscheduler.loserBracketRotationDifference
self.upperBracketRotationDifference = matchscheduler.upperBracketRotationDifference
self.accountUpperBracketBreakTime = matchscheduler.accountUpperBracketBreakTime
self.accountLoserBracketBreakTime = matchscheduler.accountLoserBracketBreakTime
self.randomizeCourts = matchscheduler.randomizeCourts
self.rotationDifferenceIsImportant = matchscheduler.rotationDifferenceIsImportant
self.shouldHandleUpperRoundSlice = matchscheduler.shouldHandleUpperRoundSlice
self.shouldEndRoundBeforeStartingNext = matchscheduler.shouldEndRoundBeforeStartingNext
self.groupStageChunkCount = matchscheduler.groupStageChunkCount
self.overrideCourtsUnavailability = matchscheduler.overrideCourtsUnavailability
self.shouldTryToFillUpCourtsAvailable = matchscheduler.shouldTryToFillUpCourtsAvailable
self.courtsAvailable = matchscheduler.courtsAvailable
self.simultaneousStart = matchscheduler.simultaneousStart
}
static func relationships() -> [Relationship] {
return [
Relationship(type: Tournament.self, keyPath: \BaseMatchScheduler.tournament),
]
}
}

@ -1,122 +0,0 @@
// Generated by SwiftModelGenerator
// Do not modify this file manually
import Foundation
import LeStorage
import SwiftUI
@Observable
class BaseMonthData: BaseModelObject, Storable {
static func resourceName() -> String { return "month-data" }
static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
static var copyServerResponse: Bool = false
var id: String = Store.randomId()
var monthKey: String = ""
var creationDate: Date = Date()
var maleUnrankedValue: Int? = nil
var femaleUnrankedValue: Int? = nil
var maleCount: Int? = nil
var femaleCount: Int? = nil
var anonymousCount: Int? = nil
var incompleteMode: Bool = false
var dataModelIdentifier: String? = nil
var fileModelIdentifier: String? = nil
init(
id: String = Store.randomId(),
monthKey: String = "",
creationDate: Date = Date(),
maleUnrankedValue: Int? = nil,
femaleUnrankedValue: Int? = nil,
maleCount: Int? = nil,
femaleCount: Int? = nil,
anonymousCount: Int? = nil,
incompleteMode: Bool = false,
dataModelIdentifier: String? = nil,
fileModelIdentifier: String? = nil
) {
super.init()
self.id = id
self.monthKey = monthKey
self.creationDate = creationDate
self.maleUnrankedValue = maleUnrankedValue
self.femaleUnrankedValue = femaleUnrankedValue
self.maleCount = maleCount
self.femaleCount = femaleCount
self.anonymousCount = anonymousCount
self.incompleteMode = incompleteMode
self.dataModelIdentifier = dataModelIdentifier
self.fileModelIdentifier = fileModelIdentifier
}
required public override init() {
super.init()
}
enum CodingKeys: String, CodingKey {
case _id = "id"
case _monthKey = "monthKey"
case _creationDate = "creationDate"
case _maleUnrankedValue = "maleUnrankedValue"
case _femaleUnrankedValue = "femaleUnrankedValue"
case _maleCount = "maleCount"
case _femaleCount = "femaleCount"
case _anonymousCount = "anonymousCount"
case _incompleteMode = "incompleteMode"
case _dataModelIdentifier = "dataModelIdentifier"
case _fileModelIdentifier = "fileModelIdentifier"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId()
self.monthKey = try container.decodeIfPresent(String.self, forKey: ._monthKey) ?? ""
self.creationDate = try container.decodeIfPresent(Date.self, forKey: ._creationDate) ?? Date()
self.maleUnrankedValue = try container.decodeIfPresent(Int.self, forKey: ._maleUnrankedValue) ?? nil
self.femaleUnrankedValue = try container.decodeIfPresent(Int.self, forKey: ._femaleUnrankedValue) ?? nil
self.maleCount = try container.decodeIfPresent(Int.self, forKey: ._maleCount) ?? nil
self.femaleCount = try container.decodeIfPresent(Int.self, forKey: ._femaleCount) ?? nil
self.anonymousCount = try container.decodeIfPresent(Int.self, forKey: ._anonymousCount) ?? nil
self.incompleteMode = try container.decodeIfPresent(Bool.self, forKey: ._incompleteMode) ?? false
self.dataModelIdentifier = try container.decodeIfPresent(String.self, forKey: ._dataModelIdentifier) ?? nil
self.fileModelIdentifier = try container.decodeIfPresent(String.self, forKey: ._fileModelIdentifier) ?? nil
try super.init(from: decoder)
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.id, forKey: ._id)
try container.encode(self.monthKey, forKey: ._monthKey)
try container.encode(self.creationDate, forKey: ._creationDate)
try container.encode(self.maleUnrankedValue, forKey: ._maleUnrankedValue)
try container.encode(self.femaleUnrankedValue, forKey: ._femaleUnrankedValue)
try container.encode(self.maleCount, forKey: ._maleCount)
try container.encode(self.femaleCount, forKey: ._femaleCount)
try container.encode(self.anonymousCount, forKey: ._anonymousCount)
try container.encode(self.incompleteMode, forKey: ._incompleteMode)
try container.encode(self.dataModelIdentifier, forKey: ._dataModelIdentifier)
try container.encode(self.fileModelIdentifier, forKey: ._fileModelIdentifier)
try super.encode(to: encoder)
}
func copy(from other: any Storable) {
guard let monthdata = other as? BaseMonthData else { return }
self.id = monthdata.id
self.monthKey = monthdata.monthKey
self.creationDate = monthdata.creationDate
self.maleUnrankedValue = monthdata.maleUnrankedValue
self.femaleUnrankedValue = monthdata.femaleUnrankedValue
self.maleCount = monthdata.maleCount
self.femaleCount = monthdata.femaleCount
self.anonymousCount = monthdata.anonymousCount
self.incompleteMode = monthdata.incompleteMode
self.dataModelIdentifier = monthdata.dataModelIdentifier
self.fileModelIdentifier = monthdata.fileModelIdentifier
}
static func relationships() -> [Relationship] {
return []
}
}

@ -1,227 +0,0 @@
// Generated by SwiftModelGenerator
// Do not modify this file manually
import Foundation
import LeStorage
import SwiftUI
@Observable
class BasePlayerRegistration: SyncedModelObject, SyncedStorable {
static func resourceName() -> String { return "player-registrations" }
static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
static var copyServerResponse: Bool = false
var id: String = Store.randomId()
var teamRegistration: String? = nil
var firstName: String = ""
var lastName: String = ""
var licenceId: String? = nil
var rank: Int? = nil
var paymentType: PlayerPaymentType? = nil
var sex: PlayerSexType? = nil
var tournamentPlayed: Int? = nil
var points: Double? = nil
var clubName: String? = nil
var ligueName: String? = nil
var assimilation: String? = nil
var phoneNumber: String? = nil
var email: String? = nil
var birthdate: String? = nil
var computedRank: Int = 0
var source: PlayerRegistration.PlayerDataSource? = nil
var hasArrived: Bool = false
var coach: Bool = false
var captain: Bool = false
var registeredOnline: Bool = false
var timeToConfirm: Date? = nil
var registrationStatus: PlayerRegistration.RegistrationStatus = PlayerRegistration.RegistrationStatus.waiting
var paymentId: String? = nil
init(
id: String = Store.randomId(),
teamRegistration: String? = nil,
firstName: String = "",
lastName: String = "",
licenceId: String? = nil,
rank: Int? = nil,
paymentType: PlayerPaymentType? = nil,
sex: PlayerSexType? = nil,
tournamentPlayed: Int? = nil,
points: Double? = nil,
clubName: String? = nil,
ligueName: String? = nil,
assimilation: String? = nil,
phoneNumber: String? = nil,
email: String? = nil,
birthdate: String? = nil,
computedRank: Int = 0,
source: PlayerRegistration.PlayerDataSource? = nil,
hasArrived: Bool = false,
coach: Bool = false,
captain: Bool = false,
registeredOnline: Bool = false,
timeToConfirm: Date? = nil,
registrationStatus: PlayerRegistration.RegistrationStatus = PlayerRegistration.RegistrationStatus.waiting,
paymentId: String? = nil
) {
super.init()
self.id = id
self.teamRegistration = teamRegistration
self.firstName = firstName
self.lastName = lastName
self.licenceId = licenceId
self.rank = rank
self.paymentType = paymentType
self.sex = sex
self.tournamentPlayed = tournamentPlayed
self.points = points
self.clubName = clubName
self.ligueName = ligueName
self.assimilation = assimilation
self.phoneNumber = phoneNumber
self.email = email
self.birthdate = birthdate
self.computedRank = computedRank
self.source = source
self.hasArrived = hasArrived
self.coach = coach
self.captain = captain
self.registeredOnline = registeredOnline
self.timeToConfirm = timeToConfirm
self.registrationStatus = registrationStatus
self.paymentId = paymentId
}
required public override init() {
super.init()
}
enum CodingKeys: String, CodingKey {
case _id = "id"
case _teamRegistration = "teamRegistration"
case _firstName = "firstName"
case _lastName = "lastName"
case _licenceId = "licenceId"
case _rank = "rank"
case _paymentType = "paymentType"
case _sex = "sex"
case _tournamentPlayed = "tournamentPlayed"
case _points = "points"
case _clubName = "clubName"
case _ligueName = "ligueName"
case _assimilation = "assimilation"
case _phoneNumber = "phoneNumber"
case _email = "email"
case _birthdate = "birthdate"
case _computedRank = "computedRank"
case _source = "source"
case _hasArrived = "hasArrived"
case _coach = "coach"
case _captain = "captain"
case _registeredOnline = "registeredOnline"
case _timeToConfirm = "timeToConfirm"
case _registrationStatus = "registrationStatus"
case _paymentId = "paymentId"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId()
self.teamRegistration = try container.decodeIfPresent(String.self, forKey: ._teamRegistration) ?? nil
self.firstName = try container.decodeIfPresent(String.self, forKey: ._firstName) ?? ""
self.lastName = try container.decodeIfPresent(String.self, forKey: ._lastName) ?? ""
self.licenceId = try container.decodeIfPresent(String.self, forKey: ._licenceId) ?? nil
self.rank = try container.decodeIfPresent(Int.self, forKey: ._rank) ?? nil
self.paymentType = try container.decodeIfPresent(PlayerPaymentType.self, forKey: ._paymentType) ?? nil
self.sex = try container.decodeIfPresent(PlayerSexType.self, forKey: ._sex) ?? nil
self.tournamentPlayed = try container.decodeIfPresent(Int.self, forKey: ._tournamentPlayed) ?? nil
self.points = try container.decodeIfPresent(Double.self, forKey: ._points) ?? nil
self.clubName = try container.decodeIfPresent(String.self, forKey: ._clubName) ?? nil
self.ligueName = try container.decodeIfPresent(String.self, forKey: ._ligueName) ?? nil
self.assimilation = try container.decodeIfPresent(String.self, forKey: ._assimilation) ?? nil
self.phoneNumber = try container.decodeIfPresent(String.self, forKey: ._phoneNumber) ?? nil
self.email = try container.decodeIfPresent(String.self, forKey: ._email) ?? nil
self.birthdate = try container.decodeIfPresent(String.self, forKey: ._birthdate) ?? nil
self.computedRank = try container.decodeIfPresent(Int.self, forKey: ._computedRank) ?? 0
self.source = try container.decodeIfPresent(PlayerRegistration.PlayerDataSource.self, forKey: ._source) ?? nil
self.hasArrived = try container.decodeIfPresent(Bool.self, forKey: ._hasArrived) ?? false
self.coach = try container.decodeIfPresent(Bool.self, forKey: ._coach) ?? false
self.captain = try container.decodeIfPresent(Bool.self, forKey: ._captain) ?? false
self.registeredOnline = try container.decodeIfPresent(Bool.self, forKey: ._registeredOnline) ?? false
self.timeToConfirm = try container.decodeIfPresent(Date.self, forKey: ._timeToConfirm) ?? nil
self.registrationStatus = try container.decodeIfPresent(PlayerRegistration.RegistrationStatus.self, forKey: ._registrationStatus) ?? PlayerRegistration.RegistrationStatus.waiting
self.paymentId = try container.decodeIfPresent(String.self, forKey: ._paymentId) ?? nil
try super.init(from: decoder)
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.id, forKey: ._id)
try container.encode(self.teamRegistration, forKey: ._teamRegistration)
try container.encode(self.firstName, forKey: ._firstName)
try container.encode(self.lastName, forKey: ._lastName)
try container.encode(self.licenceId, forKey: ._licenceId)
try container.encode(self.rank, forKey: ._rank)
try container.encode(self.paymentType, forKey: ._paymentType)
try container.encode(self.sex, forKey: ._sex)
try container.encode(self.tournamentPlayed, forKey: ._tournamentPlayed)
try container.encode(self.points, forKey: ._points)
try container.encode(self.clubName, forKey: ._clubName)
try container.encode(self.ligueName, forKey: ._ligueName)
try container.encode(self.assimilation, forKey: ._assimilation)
try container.encode(self.phoneNumber, forKey: ._phoneNumber)
try container.encode(self.email, forKey: ._email)
try container.encode(self.birthdate, forKey: ._birthdate)
try container.encode(self.computedRank, forKey: ._computedRank)
try container.encode(self.source, forKey: ._source)
try container.encode(self.hasArrived, forKey: ._hasArrived)
try container.encode(self.coach, forKey: ._coach)
try container.encode(self.captain, forKey: ._captain)
try container.encode(self.registeredOnline, forKey: ._registeredOnline)
try container.encode(self.timeToConfirm, forKey: ._timeToConfirm)
try container.encode(self.registrationStatus, forKey: ._registrationStatus)
try container.encode(self.paymentId, forKey: ._paymentId)
try super.encode(to: encoder)
}
func teamRegistrationValue() -> TeamRegistration? {
guard let teamRegistration = self.teamRegistration else { return nil }
return Store.main.findById(teamRegistration)
}
func copy(from other: any Storable) {
guard let playerregistration = other as? BasePlayerRegistration else { return }
self.id = playerregistration.id
self.teamRegistration = playerregistration.teamRegistration
self.firstName = playerregistration.firstName
self.lastName = playerregistration.lastName
self.licenceId = playerregistration.licenceId
self.rank = playerregistration.rank
self.paymentType = playerregistration.paymentType
self.sex = playerregistration.sex
self.tournamentPlayed = playerregistration.tournamentPlayed
self.points = playerregistration.points
self.clubName = playerregistration.clubName
self.ligueName = playerregistration.ligueName
self.assimilation = playerregistration.assimilation
self.phoneNumber = playerregistration.phoneNumber
self.email = playerregistration.email
self.birthdate = playerregistration.birthdate
self.computedRank = playerregistration.computedRank
self.source = playerregistration.source
self.hasArrived = playerregistration.hasArrived
self.coach = playerregistration.coach
self.captain = playerregistration.captain
self.registeredOnline = playerregistration.registeredOnline
self.timeToConfirm = playerregistration.timeToConfirm
self.registrationStatus = playerregistration.registrationStatus
self.paymentId = playerregistration.paymentId
}
static func relationships() -> [Relationship] {
return [
Relationship(type: TeamRegistration.self, keyPath: \BasePlayerRegistration.teamRegistration),
]
}
}

@ -1,99 +0,0 @@
// Generated by SwiftModelGenerator
// Do not modify this file manually
import Foundation
import LeStorage
class BasePurchase: SyncedModelObject, SyncedStorable {
static func resourceName() -> String { return "purchases" }
static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
static var copyServerResponse: Bool = false
var id: UInt64 = 0
var user: String = ""
var purchaseDate: Date = Date()
var productId: String = ""
var quantity: Int? = nil
var revocationDate: Date? = nil
var expirationDate: Date? = nil
init(
id: UInt64 = 0,
user: String = "",
purchaseDate: Date = Date(),
productId: String = "",
quantity: Int? = nil,
revocationDate: Date? = nil,
expirationDate: Date? = nil
) {
super.init()
self.id = id
self.user = user
self.purchaseDate = purchaseDate
self.productId = productId
self.quantity = quantity
self.revocationDate = revocationDate
self.expirationDate = expirationDate
}
required public override init() {
super.init()
}
enum CodingKeys: String, CodingKey {
case id = "id"
case user = "user"
case purchaseDate = "purchaseDate"
case productId = "productId"
case quantity = "quantity"
case revocationDate = "revocationDate"
case expirationDate = "expirationDate"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decodeIfPresent(UInt64.self, forKey: .id) ?? 0
self.user = try container.decodeEncrypted(key: .user)
self.purchaseDate = try container.decodeIfPresent(Date.self, forKey: .purchaseDate) ?? Date()
self.productId = try container.decodeIfPresent(String.self, forKey: .productId) ?? ""
self.quantity = try container.decodeIfPresent(Int.self, forKey: .quantity) ?? nil
self.revocationDate = try container.decodeIfPresent(Date.self, forKey: .revocationDate) ?? nil
self.expirationDate = try container.decodeIfPresent(Date.self, forKey: .expirationDate) ?? nil
try super.init(from: decoder)
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.id, forKey: .id)
try container.encodeAndEncryptIfPresent(self.user.data(using: .utf8), forKey: .user)
try container.encode(self.purchaseDate, forKey: .purchaseDate)
try container.encode(self.productId, forKey: .productId)
try container.encode(self.quantity, forKey: .quantity)
try container.encode(self.revocationDate, forKey: .revocationDate)
try container.encode(self.expirationDate, forKey: .expirationDate)
try super.encode(to: encoder)
}
func userValue() -> CustomUser? {
return Store.main.findById(user)
}
func copy(from other: any Storable) {
guard let purchase = other as? BasePurchase else { return }
self.id = purchase.id
self.user = purchase.user
self.purchaseDate = purchase.purchaseDate
self.productId = purchase.productId
self.quantity = purchase.quantity
self.revocationDate = purchase.revocationDate
self.expirationDate = purchase.expirationDate
}
static func relationships() -> [Relationship] {
return [
Relationship(type: CustomUser.self, keyPath: \BasePurchase.user),
]
}
}

@ -1,107 +0,0 @@
// Generated by SwiftModelGenerator
// Do not modify this file manually
import Foundation
import LeStorage
import SwiftUI
@Observable
class BaseRound: SyncedModelObject, SyncedStorable {
static func resourceName() -> String { return "rounds" }
static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
static var copyServerResponse: Bool = false
var id: String = Store.randomId()
var tournament: String = ""
var index: Int = 0
var parent: String? = nil
var format: MatchFormat? = nil
var startDate: Date? = nil
var groupStageLoserBracket: Bool = false
var loserBracketMode: LoserBracketMode = .automatic
init(
id: String = Store.randomId(),
tournament: String = "",
index: Int = 0,
parent: String? = nil,
format: MatchFormat? = nil,
startDate: Date? = nil,
groupStageLoserBracket: Bool = false,
loserBracketMode: LoserBracketMode = .automatic
) {
super.init()
self.id = id
self.tournament = tournament
self.index = index
self.parent = parent
self.format = format
self.startDate = startDate
self.groupStageLoserBracket = groupStageLoserBracket
self.loserBracketMode = loserBracketMode
}
required public override init() {
super.init()
}
enum CodingKeys: String, CodingKey {
case _id = "id"
case _tournament = "tournament"
case _index = "index"
case _parent = "parent"
case _format = "format"
case _startDate = "startDate"
case _groupStageLoserBracket = "groupStageLoserBracket"
case _loserBracketMode = "loserBracketMode"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId()
self.tournament = try container.decodeIfPresent(String.self, forKey: ._tournament) ?? ""
self.index = try container.decodeIfPresent(Int.self, forKey: ._index) ?? 0
self.parent = try container.decodeIfPresent(String.self, forKey: ._parent) ?? nil
self.format = try container.decodeIfPresent(MatchFormat.self, forKey: ._format) ?? nil
self.startDate = try container.decodeIfPresent(Date.self, forKey: ._startDate) ?? nil
self.groupStageLoserBracket = try container.decodeIfPresent(Bool.self, forKey: ._groupStageLoserBracket) ?? false
self.loserBracketMode = try container.decodeIfPresent(LoserBracketMode.self, forKey: ._loserBracketMode) ?? .automatic
try super.init(from: decoder)
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.id, forKey: ._id)
try container.encode(self.tournament, forKey: ._tournament)
try container.encode(self.index, forKey: ._index)
try container.encode(self.parent, forKey: ._parent)
try container.encode(self.format, forKey: ._format)
try container.encode(self.startDate, forKey: ._startDate)
try container.encode(self.groupStageLoserBracket, forKey: ._groupStageLoserBracket)
try container.encode(self.loserBracketMode, forKey: ._loserBracketMode)
try super.encode(to: encoder)
}
func tournamentValue() -> Tournament? {
return Store.main.findById(tournament)
}
func copy(from other: any Storable) {
guard let round = other as? BaseRound else { return }
self.id = round.id
self.tournament = round.tournament
self.index = round.index
self.parent = round.parent
self.format = round.format
self.startDate = round.startDate
self.groupStageLoserBracket = round.groupStageLoserBracket
self.loserBracketMode = round.loserBracketMode
}
static func relationships() -> [Relationship] {
return [
Relationship(type: Tournament.self, keyPath: \BaseRound.tournament),
]
}
}

@ -1,199 +0,0 @@
// Generated by SwiftModelGenerator
// Do not modify this file manually
import Foundation
import LeStorage
import SwiftUI
@Observable
class BaseTeamRegistration: SyncedModelObject, SyncedStorable {
static func resourceName() -> String { return "team-registrations" }
static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
static var copyServerResponse: Bool = false
var id: String = Store.randomId()
var tournament: String = ""
var groupStage: String? = nil
var registrationDate: Date? = nil
var callDate: Date? = nil
var bracketPosition: Int? = nil
var groupStagePosition: Int? = nil
var comment: String? = nil
var source: String? = nil
var sourceValue: String? = nil
var logo: String? = nil
var name: String? = nil
var walkOut: Bool = false
var wildCardBracket: Bool = false
var wildCardGroupStage: Bool = false
var weight: Int = 0
var lockedWeight: Int? = nil
var confirmationDate: Date? = nil
var qualified: Bool = false
var finalRanking: Int? = nil
var pointsEarned: Int? = nil
init(
id: String = Store.randomId(),
tournament: String = "",
groupStage: String? = nil,
registrationDate: Date? = nil,
callDate: Date? = nil,
bracketPosition: Int? = nil,
groupStagePosition: Int? = nil,
comment: String? = nil,
source: String? = nil,
sourceValue: String? = nil,
logo: String? = nil,
name: String? = nil,
walkOut: Bool = false,
wildCardBracket: Bool = false,
wildCardGroupStage: Bool = false,
weight: Int = 0,
lockedWeight: Int? = nil,
confirmationDate: Date? = nil,
qualified: Bool = false,
finalRanking: Int? = nil,
pointsEarned: Int? = nil
) {
super.init()
self.id = id
self.tournament = tournament
self.groupStage = groupStage
self.registrationDate = registrationDate
self.callDate = callDate
self.bracketPosition = bracketPosition
self.groupStagePosition = groupStagePosition
self.comment = comment
self.source = source
self.sourceValue = sourceValue
self.logo = logo
self.name = name
self.walkOut = walkOut
self.wildCardBracket = wildCardBracket
self.wildCardGroupStage = wildCardGroupStage
self.weight = weight
self.lockedWeight = lockedWeight
self.confirmationDate = confirmationDate
self.qualified = qualified
self.finalRanking = finalRanking
self.pointsEarned = pointsEarned
}
required public override init() {
super.init()
}
enum CodingKeys: String, CodingKey {
case _id = "id"
case _tournament = "tournament"
case _groupStage = "groupStage"
case _registrationDate = "registrationDate"
case _callDate = "callDate"
case _bracketPosition = "bracketPosition"
case _groupStagePosition = "groupStagePosition"
case _comment = "comment"
case _source = "source"
case _sourceValue = "sourceValue"
case _logo = "logo"
case _name = "name"
case _walkOut = "walkOut"
case _wildCardBracket = "wildCardBracket"
case _wildCardGroupStage = "wildCardGroupStage"
case _weight = "weight"
case _lockedWeight = "lockedWeight"
case _confirmationDate = "confirmationDate"
case _qualified = "qualified"
case _finalRanking = "finalRanking"
case _pointsEarned = "pointsEarned"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId()
self.tournament = try container.decodeIfPresent(String.self, forKey: ._tournament) ?? ""
self.groupStage = try container.decodeIfPresent(String.self, forKey: ._groupStage) ?? nil
self.registrationDate = try container.decodeIfPresent(Date.self, forKey: ._registrationDate) ?? nil
self.callDate = try container.decodeIfPresent(Date.self, forKey: ._callDate) ?? nil
self.bracketPosition = try container.decodeIfPresent(Int.self, forKey: ._bracketPosition) ?? nil
self.groupStagePosition = try container.decodeIfPresent(Int.self, forKey: ._groupStagePosition) ?? nil
self.comment = try container.decodeIfPresent(String.self, forKey: ._comment) ?? nil
self.source = try container.decodeIfPresent(String.self, forKey: ._source) ?? nil
self.sourceValue = try container.decodeIfPresent(String.self, forKey: ._sourceValue) ?? nil
self.logo = try container.decodeIfPresent(String.self, forKey: ._logo) ?? nil
self.name = try container.decodeIfPresent(String.self, forKey: ._name) ?? nil
self.walkOut = try container.decodeIfPresent(Bool.self, forKey: ._walkOut) ?? false
self.wildCardBracket = try container.decodeIfPresent(Bool.self, forKey: ._wildCardBracket) ?? false
self.wildCardGroupStage = try container.decodeIfPresent(Bool.self, forKey: ._wildCardGroupStage) ?? false
self.weight = try container.decodeIfPresent(Int.self, forKey: ._weight) ?? 0
self.lockedWeight = try container.decodeIfPresent(Int.self, forKey: ._lockedWeight) ?? nil
self.confirmationDate = try container.decodeIfPresent(Date.self, forKey: ._confirmationDate) ?? nil
self.qualified = try container.decodeIfPresent(Bool.self, forKey: ._qualified) ?? false
self.finalRanking = try container.decodeIfPresent(Int.self, forKey: ._finalRanking) ?? nil
self.pointsEarned = try container.decodeIfPresent(Int.self, forKey: ._pointsEarned) ?? nil
try super.init(from: decoder)
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.id, forKey: ._id)
try container.encode(self.tournament, forKey: ._tournament)
try container.encode(self.groupStage, forKey: ._groupStage)
try container.encode(self.registrationDate, forKey: ._registrationDate)
try container.encode(self.callDate, forKey: ._callDate)
try container.encode(self.bracketPosition, forKey: ._bracketPosition)
try container.encode(self.groupStagePosition, forKey: ._groupStagePosition)
try container.encode(self.comment, forKey: ._comment)
try container.encode(self.source, forKey: ._source)
try container.encode(self.sourceValue, forKey: ._sourceValue)
try container.encode(self.logo, forKey: ._logo)
try container.encode(self.name, forKey: ._name)
try container.encode(self.walkOut, forKey: ._walkOut)
try container.encode(self.wildCardBracket, forKey: ._wildCardBracket)
try container.encode(self.wildCardGroupStage, forKey: ._wildCardGroupStage)
try container.encode(self.weight, forKey: ._weight)
try container.encode(self.lockedWeight, forKey: ._lockedWeight)
try container.encode(self.confirmationDate, forKey: ._confirmationDate)
try container.encode(self.qualified, forKey: ._qualified)
try container.encode(self.finalRanking, forKey: ._finalRanking)
try container.encode(self.pointsEarned, forKey: ._pointsEarned)
try super.encode(to: encoder)
}
func groupStageValue() -> GroupStage? {
guard let groupStage = self.groupStage else { return nil }
return self.store?.findById(groupStage)
}
func copy(from other: any Storable) {
guard let teamregistration = other as? BaseTeamRegistration else { return }
self.id = teamregistration.id
self.tournament = teamregistration.tournament
self.groupStage = teamregistration.groupStage
self.registrationDate = teamregistration.registrationDate
self.callDate = teamregistration.callDate
self.bracketPosition = teamregistration.bracketPosition
self.groupStagePosition = teamregistration.groupStagePosition
self.comment = teamregistration.comment
self.source = teamregistration.source
self.sourceValue = teamregistration.sourceValue
self.logo = teamregistration.logo
self.name = teamregistration.name
self.walkOut = teamregistration.walkOut
self.wildCardBracket = teamregistration.wildCardBracket
self.wildCardGroupStage = teamregistration.wildCardGroupStage
self.weight = teamregistration.weight
self.lockedWeight = teamregistration.lockedWeight
self.confirmationDate = teamregistration.confirmationDate
self.qualified = teamregistration.qualified
self.finalRanking = teamregistration.finalRanking
self.pointsEarned = teamregistration.pointsEarned
}
static func relationships() -> [Relationship] {
return [
Relationship(type: GroupStage.self, keyPath: \BaseTeamRegistration.groupStage),
]
}
}

@ -1,99 +0,0 @@
// Generated by SwiftModelGenerator
// Do not modify this file manually
import Foundation
import LeStorage
import SwiftUI
@Observable
class BaseTeamScore: SyncedModelObject, SyncedStorable {
static func resourceName() -> String { return "team-scores" }
static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
static var copyServerResponse: Bool = false
var id: String = Store.randomId()
var match: String = ""
var teamRegistration: String? = nil
var score: String? = nil
var walkOut: Int? = nil
var luckyLoser: Int? = nil
init(
id: String = Store.randomId(),
match: String = "",
teamRegistration: String? = nil,
score: String? = nil,
walkOut: Int? = nil,
luckyLoser: Int? = nil
) {
super.init()
self.id = id
self.match = match
self.teamRegistration = teamRegistration
self.score = score
self.walkOut = walkOut
self.luckyLoser = luckyLoser
}
required public override init() {
super.init()
}
enum CodingKeys: String, CodingKey {
case _id = "id"
case _match = "match"
case _teamRegistration = "teamRegistration"
case _score = "score"
case _walkOut = "walkOut"
case _luckyLoser = "luckyLoser"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId()
self.match = try container.decodeIfPresent(String.self, forKey: ._match) ?? ""
self.teamRegistration = try container.decodeIfPresent(String.self, forKey: ._teamRegistration) ?? nil
self.score = try container.decodeIfPresent(String.self, forKey: ._score) ?? nil
self.walkOut = try container.decodeIfPresent(Int.self, forKey: ._walkOut) ?? nil
self.luckyLoser = try container.decodeIfPresent(Int.self, forKey: ._luckyLoser) ?? nil
try super.init(from: decoder)
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.id, forKey: ._id)
try container.encode(self.match, forKey: ._match)
try container.encode(self.teamRegistration, forKey: ._teamRegistration)
try container.encode(self.score, forKey: ._score)
try container.encode(self.walkOut, forKey: ._walkOut)
try container.encode(self.luckyLoser, forKey: ._luckyLoser)
try super.encode(to: encoder)
}
func matchValue() -> Match? {
return self.store?.findById(match)
}
func teamRegistrationValue() -> TeamRegistration? {
guard let teamRegistration = self.teamRegistration else { return nil }
return self.store?.findById(teamRegistration)
}
func copy(from other: any Storable) {
guard let teamscore = other as? BaseTeamScore else { return }
self.id = teamscore.id
self.match = teamscore.match
self.teamRegistration = teamscore.teamRegistration
self.score = teamscore.score
self.walkOut = teamscore.walkOut
self.luckyLoser = teamscore.luckyLoser
}
static func relationships() -> [Relationship] {
return [
Relationship(type: Match.self, keyPath: \BaseTeamScore.match),
Relationship(type: TeamRegistration.self, keyPath: \BaseTeamScore.teamRegistration),
]
}
}

@ -1,590 +0,0 @@
// Generated by SwiftModelGenerator
// Do not modify this file manually
import Foundation
import LeStorage
import SwiftUI
@Observable
class BaseTournament: SyncedModelObject, SyncedStorable {
static func resourceName() -> String { return "tournaments" }
static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
static var copyServerResponse: Bool = false
var id: String = Store.randomId()
var event: String? = nil
var name: String? = nil
var startDate: Date = Date()
var endDate: Date? = nil
var creationDate: Date = Date()
var isPrivate: Bool = false
var groupStageFormat: MatchFormat? = nil
var roundFormat: MatchFormat? = nil
var loserRoundFormat: MatchFormat? = nil
var groupStageSortMode: GroupStageOrderingMode = GroupStageOrderingMode.snake
var groupStageCount: Int = 4
var rankSourceDate: Date? = nil
var dayDuration: Int = 1
var teamCount: Int = 24
var teamSorting: TeamSortingType = TeamSortingType.inscriptionDate
var federalCategory: TournamentCategory = TournamentCategory.men
var federalLevelCategory: TournamentLevel = TournamentLevel.p100
var federalAgeCategory: FederalTournamentAge = FederalTournamentAge.senior
var closedRegistrationDate: Date? = nil
var groupStageAdditionalQualified: Int = 0
var courtCount: Int = 2
var prioritizeClubMembers: Bool = false
var qualifiedPerGroupStage: Int = 1
var teamsPerGroupStage: Int = 4
var entryFee: Double? = nil
var payment: TournamentPayment? = nil
var additionalEstimationDuration: Int = 0
var isDeleted: Bool = false
var isCanceled: Bool = false
var publishTeams: Bool = false
var publishSummons: Bool = false
var publishGroupStages: Bool = false
var publishBrackets: Bool = false
var shouldVerifyGroupStage: Bool = false
var shouldVerifyBracket: Bool = false
var hideTeamsWeight: Bool = false
var publishTournament: Bool = false
var hidePointsEarned: Bool = false
var publishRankings: Bool = false
var loserBracketMode: LoserBracketMode = .automatic
var initialSeedRound: Int = 0
var initialSeedCount: Int = 0
var enableOnlineRegistration: Bool = false
var registrationDateLimit: Date? = nil
var openingRegistrationDate: Date? = nil
var waitingListLimit: Int? = nil
var accountIsRequired: Bool = true
var licenseIsRequired: Bool = true
var minimumPlayerPerTeam: Int = 2
var maximumPlayerPerTeam: Int = 2
var information: String? = nil
var umpireCustomMail: String? = nil
var umpireCustomContact: String? = nil
var umpireCustomPhone: String? = nil
var hideUmpireMail: Bool = false
var hideUmpirePhone: Bool = true
var disableRankingFederalRuling: Bool = false
var teamCountLimit: Bool = true
var enableOnlinePayment: Bool = false
var onlinePaymentIsMandatory: Bool = false
var enableOnlinePaymentRefund: Bool = false
var refundDateLimit: Date? = nil
var stripeAccountId: String? = nil
var enableTimeToConfirm: Bool = false
var isCorporateTournament: Bool = false
var isTemplate: Bool = false
init(
id: String = Store.randomId(),
event: String? = nil,
name: String? = nil,
startDate: Date = Date(),
endDate: Date? = nil,
creationDate: Date = Date(),
isPrivate: Bool = false,
groupStageFormat: MatchFormat? = nil,
roundFormat: MatchFormat? = nil,
loserRoundFormat: MatchFormat? = nil,
groupStageSortMode: GroupStageOrderingMode = GroupStageOrderingMode.snake,
groupStageCount: Int = 4,
rankSourceDate: Date? = nil,
dayDuration: Int = 1,
teamCount: Int = 24,
teamSorting: TeamSortingType = TeamSortingType.inscriptionDate,
federalCategory: TournamentCategory = TournamentCategory.men,
federalLevelCategory: TournamentLevel = TournamentLevel.p100,
federalAgeCategory: FederalTournamentAge = FederalTournamentAge.senior,
closedRegistrationDate: Date? = nil,
groupStageAdditionalQualified: Int = 0,
courtCount: Int = 2,
prioritizeClubMembers: Bool = false,
qualifiedPerGroupStage: Int = 1,
teamsPerGroupStage: Int = 4,
entryFee: Double? = nil,
payment: TournamentPayment? = nil,
additionalEstimationDuration: Int = 0,
isDeleted: Bool = false,
isCanceled: Bool = false,
publishTeams: Bool = false,
publishSummons: Bool = false,
publishGroupStages: Bool = false,
publishBrackets: Bool = false,
shouldVerifyGroupStage: Bool = false,
shouldVerifyBracket: Bool = false,
hideTeamsWeight: Bool = false,
publishTournament: Bool = false,
hidePointsEarned: Bool = false,
publishRankings: Bool = false,
loserBracketMode: LoserBracketMode = .automatic,
initialSeedRound: Int = 0,
initialSeedCount: Int = 0,
enableOnlineRegistration: Bool = false,
registrationDateLimit: Date? = nil,
openingRegistrationDate: Date? = nil,
waitingListLimit: Int? = nil,
accountIsRequired: Bool = true,
licenseIsRequired: Bool = true,
minimumPlayerPerTeam: Int = 2,
maximumPlayerPerTeam: Int = 2,
information: String? = nil,
umpireCustomMail: String? = nil,
umpireCustomContact: String? = nil,
umpireCustomPhone: String? = nil,
hideUmpireMail: Bool = false,
hideUmpirePhone: Bool = true,
disableRankingFederalRuling: Bool = false,
teamCountLimit: Bool = true,
enableOnlinePayment: Bool = false,
onlinePaymentIsMandatory: Bool = false,
enableOnlinePaymentRefund: Bool = false,
refundDateLimit: Date? = nil,
stripeAccountId: String? = nil,
enableTimeToConfirm: Bool = false,
isCorporateTournament: Bool = false,
isTemplate: Bool = false
) {
super.init()
self.id = id
self.event = event
self.name = name
self.startDate = startDate
self.endDate = endDate
self.creationDate = creationDate
self.isPrivate = isPrivate
self.groupStageFormat = groupStageFormat
self.roundFormat = roundFormat
self.loserRoundFormat = loserRoundFormat
self.groupStageSortMode = groupStageSortMode
self.groupStageCount = groupStageCount
self.rankSourceDate = rankSourceDate
self.dayDuration = dayDuration
self.teamCount = teamCount
self.teamSorting = teamSorting
self.federalCategory = federalCategory
self.federalLevelCategory = federalLevelCategory
self.federalAgeCategory = federalAgeCategory
self.closedRegistrationDate = closedRegistrationDate
self.groupStageAdditionalQualified = groupStageAdditionalQualified
self.courtCount = courtCount
self.prioritizeClubMembers = prioritizeClubMembers
self.qualifiedPerGroupStage = qualifiedPerGroupStage
self.teamsPerGroupStage = teamsPerGroupStage
self.entryFee = entryFee
self.payment = payment
self.additionalEstimationDuration = additionalEstimationDuration
self.isDeleted = isDeleted
self.isCanceled = isCanceled
self.publishTeams = publishTeams
self.publishSummons = publishSummons
self.publishGroupStages = publishGroupStages
self.publishBrackets = publishBrackets
self.shouldVerifyGroupStage = shouldVerifyGroupStage
self.shouldVerifyBracket = shouldVerifyBracket
self.hideTeamsWeight = hideTeamsWeight
self.publishTournament = publishTournament
self.hidePointsEarned = hidePointsEarned
self.publishRankings = publishRankings
self.loserBracketMode = loserBracketMode
self.initialSeedRound = initialSeedRound
self.initialSeedCount = initialSeedCount
self.enableOnlineRegistration = enableOnlineRegistration
self.registrationDateLimit = registrationDateLimit
self.openingRegistrationDate = openingRegistrationDate
self.waitingListLimit = waitingListLimit
self.accountIsRequired = accountIsRequired
self.licenseIsRequired = licenseIsRequired
self.minimumPlayerPerTeam = minimumPlayerPerTeam
self.maximumPlayerPerTeam = maximumPlayerPerTeam
self.information = information
self.umpireCustomMail = umpireCustomMail
self.umpireCustomContact = umpireCustomContact
self.umpireCustomPhone = umpireCustomPhone
self.hideUmpireMail = hideUmpireMail
self.hideUmpirePhone = hideUmpirePhone
self.disableRankingFederalRuling = disableRankingFederalRuling
self.teamCountLimit = teamCountLimit
self.enableOnlinePayment = enableOnlinePayment
self.onlinePaymentIsMandatory = onlinePaymentIsMandatory
self.enableOnlinePaymentRefund = enableOnlinePaymentRefund
self.refundDateLimit = refundDateLimit
self.stripeAccountId = stripeAccountId
self.enableTimeToConfirm = enableTimeToConfirm
self.isCorporateTournament = isCorporateTournament
self.isTemplate = isTemplate
}
required public override init() {
super.init()
}
enum CodingKeys: String, CodingKey {
case isCanceled = "isCanceled"
case payment = "payment"
case _id = "id"
case _event = "event"
case _name = "name"
case _startDate = "startDate"
case _endDate = "endDate"
case _creationDate = "creationDate"
case _isPrivate = "isPrivate"
case _groupStageFormat = "groupStageFormat"
case _roundFormat = "roundFormat"
case _loserRoundFormat = "loserRoundFormat"
case _groupStageSortMode = "groupStageSortMode"
case _groupStageCount = "groupStageCount"
case _rankSourceDate = "rankSourceDate"
case _dayDuration = "dayDuration"
case _teamCount = "teamCount"
case _teamSorting = "teamSorting"
case _federalCategory = "federalCategory"
case _federalLevelCategory = "federalLevelCategory"
case _federalAgeCategory = "federalAgeCategory"
case _closedRegistrationDate = "closedRegistrationDate"
case _groupStageAdditionalQualified = "groupStageAdditionalQualified"
case _courtCount = "courtCount"
case _prioritizeClubMembers = "prioritizeClubMembers"
case _qualifiedPerGroupStage = "qualifiedPerGroupStage"
case _teamsPerGroupStage = "teamsPerGroupStage"
case _entryFee = "entryFee"
case _payment = "globalId"
case _additionalEstimationDuration = "additionalEstimationDuration"
case _isDeleted = "isDeleted"
case _isCanceled = "localId"
case _publishTeams = "publishTeams"
case _publishSummons = "publishSummons"
case _publishGroupStages = "publishGroupStages"
case _publishBrackets = "publishBrackets"
case _shouldVerifyGroupStage = "shouldVerifyGroupStage"
case _shouldVerifyBracket = "shouldVerifyBracket"
case _hideTeamsWeight = "hideTeamsWeight"
case _publishTournament = "publishTournament"
case _hidePointsEarned = "hidePointsEarned"
case _publishRankings = "publishRankings"
case _loserBracketMode = "loserBracketMode"
case _initialSeedRound = "initialSeedRound"
case _initialSeedCount = "initialSeedCount"
case _enableOnlineRegistration = "enableOnlineRegistration"
case _registrationDateLimit = "registrationDateLimit"
case _openingRegistrationDate = "openingRegistrationDate"
case _waitingListLimit = "waitingListLimit"
case _accountIsRequired = "accountIsRequired"
case _licenseIsRequired = "licenseIsRequired"
case _minimumPlayerPerTeam = "minimumPlayerPerTeam"
case _maximumPlayerPerTeam = "maximumPlayerPerTeam"
case _information = "information"
case _umpireCustomMail = "umpireCustomMail"
case _umpireCustomContact = "umpireCustomContact"
case _umpireCustomPhone = "umpireCustomPhone"
case _hideUmpireMail = "hideUmpireMail"
case _hideUmpirePhone = "hideUmpirePhone"
case _disableRankingFederalRuling = "disableRankingFederalRuling"
case _teamCountLimit = "teamCountLimit"
case _enableOnlinePayment = "enableOnlinePayment"
case _onlinePaymentIsMandatory = "onlinePaymentIsMandatory"
case _enableOnlinePaymentRefund = "enableOnlinePaymentRefund"
case _refundDateLimit = "refundDateLimit"
case _stripeAccountId = "stripeAccountId"
case _enableTimeToConfirm = "enableTimeToConfirm"
case _isCorporateTournament = "isCorporateTournament"
case _isTemplate = "isTemplate"
}
private static func _decodePayment(container: KeyedDecodingContainer<CodingKeys>) throws -> TournamentPayment? {
var data = try container.decodeIfPresent(Data.self, forKey: ._payment)
if data == nil {
data = try container.decodeIfPresent(Data.self, forKey: .payment)
}
if let data {
do {
let decoded: String = try data.decryptData(pass: CryptoKey.pass.rawValue)
let sequence = decoded.compactMap { NumberFormatter.standard.number(from: String($0))?.intValue }
return TournamentPayment(rawValue: sequence[18])
} catch {
Logger.error(error)
}
}
return nil
}
private func _encodePayment(container: inout KeyedEncodingContainer<CodingKeys>) throws {
guard let payment else {
try container.encodeNil(forKey: ._payment)
return
}
let max: Int = TournamentPayment.allCases.count
var sequence = (1...18).map { _ in Int.random(in: (0..<max)) }
sequence.append(payment.rawValue)
sequence.append(contentsOf: (1...13).map { _ in Int.random(in: (0..<max ))} )
let stringCombo: [String] = sequence.map { $0.formatted() }
let joined: String = stringCombo.joined(separator: "")
if let data = joined.data(using: .utf8) {
let encryped: Data = try data.encrypt(pass: CryptoKey.pass.rawValue)
try container.encodeIfPresent(encryped, forKey: ._payment)
}
}
private static func _decodeIscanceled(container: KeyedDecodingContainer<CodingKeys>) throws -> Bool {
var data = try container.decodeIfPresent(Data.self, forKey: ._isCanceled)
if data == nil {
data = try container.decodeIfPresent(Data.self, forKey: .isCanceled)
}
if let data {
do {
let decoded: String = try data.decryptData(pass: CryptoKey.pass.rawValue)
let sequence = decoded.compactMap { NumberFormatter.standard.number(from: String($0))?.intValue }
return Bool.decodeInt(sequence[18])
} catch {
Logger.error(error)
}
}
return false
}
private func _encodeIscanceled(container: inout KeyedEncodingContainer<CodingKeys>) throws {
let max: Int = 9
var sequence = (1...18).map { _ in Int.random(in: (0...max)) }
sequence.append(self.isCanceled.encodedValue)
sequence.append(contentsOf: (1...13).map { _ in Int.random(in: (0...max ))} )
let stringCombo: [String] = sequence.map { $0.formatted() }
let joined: String = stringCombo.joined(separator: "")
if let data = joined.data(using: .utf8) {
let encryped: Data = try data.encrypt(pass: CryptoKey.pass.rawValue)
try container.encode(encryped, forKey: ._isCanceled)
}
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId()
self.event = try container.decodeIfPresent(String.self, forKey: ._event) ?? nil
self.name = try container.decodeIfPresent(String.self, forKey: ._name) ?? nil
self.startDate = try container.decodeIfPresent(Date.self, forKey: ._startDate) ?? Date()
self.endDate = try container.decodeIfPresent(Date.self, forKey: ._endDate) ?? nil
self.creationDate = try container.decodeIfPresent(Date.self, forKey: ._creationDate) ?? Date()
self.isPrivate = try container.decodeIfPresent(Bool.self, forKey: ._isPrivate) ?? false
self.groupStageFormat = try container.decodeIfPresent(MatchFormat.self, forKey: ._groupStageFormat) ?? nil
self.roundFormat = try container.decodeIfPresent(MatchFormat.self, forKey: ._roundFormat) ?? nil
self.loserRoundFormat = try container.decodeIfPresent(MatchFormat.self, forKey: ._loserRoundFormat) ?? nil
self.groupStageSortMode = try container.decodeIfPresent(GroupStageOrderingMode.self, forKey: ._groupStageSortMode) ?? GroupStageOrderingMode.snake
self.groupStageCount = try container.decodeIfPresent(Int.self, forKey: ._groupStageCount) ?? 4
self.rankSourceDate = try container.decodeIfPresent(Date.self, forKey: ._rankSourceDate) ?? nil
self.dayDuration = try container.decodeIfPresent(Int.self, forKey: ._dayDuration) ?? 1
self.teamCount = try container.decodeIfPresent(Int.self, forKey: ._teamCount) ?? 24
self.teamSorting = try container.decodeIfPresent(TeamSortingType.self, forKey: ._teamSorting) ?? TeamSortingType.inscriptionDate
self.federalCategory = try container.decodeIfPresent(TournamentCategory.self, forKey: ._federalCategory) ?? TournamentCategory.men
self.federalLevelCategory = try container.decodeIfPresent(TournamentLevel.self, forKey: ._federalLevelCategory) ?? TournamentLevel.p100
self.federalAgeCategory = try container.decodeIfPresent(FederalTournamentAge.self, forKey: ._federalAgeCategory) ?? FederalTournamentAge.senior
self.closedRegistrationDate = try container.decodeIfPresent(Date.self, forKey: ._closedRegistrationDate) ?? nil
self.groupStageAdditionalQualified = try container.decodeIfPresent(Int.self, forKey: ._groupStageAdditionalQualified) ?? 0
self.courtCount = try container.decodeIfPresent(Int.self, forKey: ._courtCount) ?? 2
self.prioritizeClubMembers = try container.decodeIfPresent(Bool.self, forKey: ._prioritizeClubMembers) ?? false
self.qualifiedPerGroupStage = try container.decodeIfPresent(Int.self, forKey: ._qualifiedPerGroupStage) ?? 1
self.teamsPerGroupStage = try container.decodeIfPresent(Int.self, forKey: ._teamsPerGroupStage) ?? 4
self.entryFee = try container.decodeIfPresent(Double.self, forKey: ._entryFee) ?? nil
self.payment = try Self._decodePayment(container: container)
self.additionalEstimationDuration = try container.decodeIfPresent(Int.self, forKey: ._additionalEstimationDuration) ?? 0
self.isDeleted = try container.decodeIfPresent(Bool.self, forKey: ._isDeleted) ?? false
self.isCanceled = try Self._decodeIscanceled(container: container)
self.publishTeams = try container.decodeIfPresent(Bool.self, forKey: ._publishTeams) ?? false
self.publishSummons = try container.decodeIfPresent(Bool.self, forKey: ._publishSummons) ?? false
self.publishGroupStages = try container.decodeIfPresent(Bool.self, forKey: ._publishGroupStages) ?? false
self.publishBrackets = try container.decodeIfPresent(Bool.self, forKey: ._publishBrackets) ?? false
self.shouldVerifyGroupStage = try container.decodeIfPresent(Bool.self, forKey: ._shouldVerifyGroupStage) ?? false
self.shouldVerifyBracket = try container.decodeIfPresent(Bool.self, forKey: ._shouldVerifyBracket) ?? false
self.hideTeamsWeight = try container.decodeIfPresent(Bool.self, forKey: ._hideTeamsWeight) ?? false
self.publishTournament = try container.decodeIfPresent(Bool.self, forKey: ._publishTournament) ?? false
self.hidePointsEarned = try container.decodeIfPresent(Bool.self, forKey: ._hidePointsEarned) ?? false
self.publishRankings = try container.decodeIfPresent(Bool.self, forKey: ._publishRankings) ?? false
self.loserBracketMode = try container.decodeIfPresent(LoserBracketMode.self, forKey: ._loserBracketMode) ?? .automatic
self.initialSeedRound = try container.decodeIfPresent(Int.self, forKey: ._initialSeedRound) ?? 0
self.initialSeedCount = try container.decodeIfPresent(Int.self, forKey: ._initialSeedCount) ?? 0
self.enableOnlineRegistration = try container.decodeIfPresent(Bool.self, forKey: ._enableOnlineRegistration) ?? false
self.registrationDateLimit = try container.decodeIfPresent(Date.self, forKey: ._registrationDateLimit) ?? nil
self.openingRegistrationDate = try container.decodeIfPresent(Date.self, forKey: ._openingRegistrationDate) ?? nil
self.waitingListLimit = try container.decodeIfPresent(Int.self, forKey: ._waitingListLimit) ?? nil
self.accountIsRequired = try container.decodeIfPresent(Bool.self, forKey: ._accountIsRequired) ?? true
self.licenseIsRequired = try container.decodeIfPresent(Bool.self, forKey: ._licenseIsRequired) ?? true
self.minimumPlayerPerTeam = try container.decodeIfPresent(Int.self, forKey: ._minimumPlayerPerTeam) ?? 2
self.maximumPlayerPerTeam = try container.decodeIfPresent(Int.self, forKey: ._maximumPlayerPerTeam) ?? 2
self.information = try container.decodeIfPresent(String.self, forKey: ._information) ?? nil
self.umpireCustomMail = try container.decodeIfPresent(String.self, forKey: ._umpireCustomMail) ?? nil
self.umpireCustomContact = try container.decodeIfPresent(String.self, forKey: ._umpireCustomContact) ?? nil
self.umpireCustomPhone = try container.decodeIfPresent(String.self, forKey: ._umpireCustomPhone) ?? nil
self.hideUmpireMail = try container.decodeIfPresent(Bool.self, forKey: ._hideUmpireMail) ?? false
self.hideUmpirePhone = try container.decodeIfPresent(Bool.self, forKey: ._hideUmpirePhone) ?? true
self.disableRankingFederalRuling = try container.decodeIfPresent(Bool.self, forKey: ._disableRankingFederalRuling) ?? false
self.teamCountLimit = try container.decodeIfPresent(Bool.self, forKey: ._teamCountLimit) ?? true
self.enableOnlinePayment = try container.decodeIfPresent(Bool.self, forKey: ._enableOnlinePayment) ?? false
self.onlinePaymentIsMandatory = try container.decodeIfPresent(Bool.self, forKey: ._onlinePaymentIsMandatory) ?? false
self.enableOnlinePaymentRefund = try container.decodeIfPresent(Bool.self, forKey: ._enableOnlinePaymentRefund) ?? false
self.refundDateLimit = try container.decodeIfPresent(Date.self, forKey: ._refundDateLimit) ?? nil
self.stripeAccountId = try container.decodeIfPresent(String.self, forKey: ._stripeAccountId) ?? nil
self.enableTimeToConfirm = try container.decodeIfPresent(Bool.self, forKey: ._enableTimeToConfirm) ?? false
self.isCorporateTournament = try container.decodeIfPresent(Bool.self, forKey: ._isCorporateTournament) ?? false
self.isTemplate = try container.decodeIfPresent(Bool.self, forKey: ._isTemplate) ?? false
try super.init(from: decoder)
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.id, forKey: ._id)
try container.encode(self.event, forKey: ._event)
try container.encode(self.name, forKey: ._name)
try container.encode(self.startDate, forKey: ._startDate)
try container.encode(self.endDate, forKey: ._endDate)
try container.encode(self.creationDate, forKey: ._creationDate)
try container.encode(self.isPrivate, forKey: ._isPrivate)
try container.encode(self.groupStageFormat, forKey: ._groupStageFormat)
try container.encode(self.roundFormat, forKey: ._roundFormat)
try container.encode(self.loserRoundFormat, forKey: ._loserRoundFormat)
try container.encode(self.groupStageSortMode, forKey: ._groupStageSortMode)
try container.encode(self.groupStageCount, forKey: ._groupStageCount)
try container.encode(self.rankSourceDate, forKey: ._rankSourceDate)
try container.encode(self.dayDuration, forKey: ._dayDuration)
try container.encode(self.teamCount, forKey: ._teamCount)
try container.encode(self.teamSorting, forKey: ._teamSorting)
try container.encode(self.federalCategory, forKey: ._federalCategory)
try container.encode(self.federalLevelCategory, forKey: ._federalLevelCategory)
try container.encode(self.federalAgeCategory, forKey: ._federalAgeCategory)
try container.encode(self.closedRegistrationDate, forKey: ._closedRegistrationDate)
try container.encode(self.groupStageAdditionalQualified, forKey: ._groupStageAdditionalQualified)
try container.encode(self.courtCount, forKey: ._courtCount)
try container.encode(self.prioritizeClubMembers, forKey: ._prioritizeClubMembers)
try container.encode(self.qualifiedPerGroupStage, forKey: ._qualifiedPerGroupStage)
try container.encode(self.teamsPerGroupStage, forKey: ._teamsPerGroupStage)
try container.encode(self.entryFee, forKey: ._entryFee)
try _encodePayment(container: &container)
try container.encode(self.additionalEstimationDuration, forKey: ._additionalEstimationDuration)
try container.encode(self.isDeleted, forKey: ._isDeleted)
try _encodeIscanceled(container: &container)
try container.encode(self.publishTeams, forKey: ._publishTeams)
try container.encode(self.publishSummons, forKey: ._publishSummons)
try container.encode(self.publishGroupStages, forKey: ._publishGroupStages)
try container.encode(self.publishBrackets, forKey: ._publishBrackets)
try container.encode(self.shouldVerifyGroupStage, forKey: ._shouldVerifyGroupStage)
try container.encode(self.shouldVerifyBracket, forKey: ._shouldVerifyBracket)
try container.encode(self.hideTeamsWeight, forKey: ._hideTeamsWeight)
try container.encode(self.publishTournament, forKey: ._publishTournament)
try container.encode(self.hidePointsEarned, forKey: ._hidePointsEarned)
try container.encode(self.publishRankings, forKey: ._publishRankings)
try container.encode(self.loserBracketMode, forKey: ._loserBracketMode)
try container.encode(self.initialSeedRound, forKey: ._initialSeedRound)
try container.encode(self.initialSeedCount, forKey: ._initialSeedCount)
try container.encode(self.enableOnlineRegistration, forKey: ._enableOnlineRegistration)
try container.encode(self.registrationDateLimit, forKey: ._registrationDateLimit)
try container.encode(self.openingRegistrationDate, forKey: ._openingRegistrationDate)
try container.encode(self.waitingListLimit, forKey: ._waitingListLimit)
try container.encode(self.accountIsRequired, forKey: ._accountIsRequired)
try container.encode(self.licenseIsRequired, forKey: ._licenseIsRequired)
try container.encode(self.minimumPlayerPerTeam, forKey: ._minimumPlayerPerTeam)
try container.encode(self.maximumPlayerPerTeam, forKey: ._maximumPlayerPerTeam)
try container.encode(self.information, forKey: ._information)
try container.encode(self.umpireCustomMail, forKey: ._umpireCustomMail)
try container.encode(self.umpireCustomContact, forKey: ._umpireCustomContact)
try container.encode(self.umpireCustomPhone, forKey: ._umpireCustomPhone)
try container.encode(self.hideUmpireMail, forKey: ._hideUmpireMail)
try container.encode(self.hideUmpirePhone, forKey: ._hideUmpirePhone)
try container.encode(self.disableRankingFederalRuling, forKey: ._disableRankingFederalRuling)
try container.encode(self.teamCountLimit, forKey: ._teamCountLimit)
try container.encode(self.enableOnlinePayment, forKey: ._enableOnlinePayment)
try container.encode(self.onlinePaymentIsMandatory, forKey: ._onlinePaymentIsMandatory)
try container.encode(self.enableOnlinePaymentRefund, forKey: ._enableOnlinePaymentRefund)
try container.encode(self.refundDateLimit, forKey: ._refundDateLimit)
try container.encode(self.stripeAccountId, forKey: ._stripeAccountId)
try container.encode(self.enableTimeToConfirm, forKey: ._enableTimeToConfirm)
try container.encode(self.isCorporateTournament, forKey: ._isCorporateTournament)
try container.encode(self.isTemplate, forKey: ._isTemplate)
try super.encode(to: encoder)
}
func eventValue() -> Event? {
guard let event = self.event else { return nil }
return Store.main.findById(event)
}
func copy(from other: any Storable) {
guard let tournament = other as? BaseTournament else { return }
self.id = tournament.id
self.event = tournament.event
self.name = tournament.name
self.startDate = tournament.startDate
self.endDate = tournament.endDate
self.creationDate = tournament.creationDate
self.isPrivate = tournament.isPrivate
self.groupStageFormat = tournament.groupStageFormat
self.roundFormat = tournament.roundFormat
self.loserRoundFormat = tournament.loserRoundFormat
self.groupStageSortMode = tournament.groupStageSortMode
self.groupStageCount = tournament.groupStageCount
self.rankSourceDate = tournament.rankSourceDate
self.dayDuration = tournament.dayDuration
self.teamCount = tournament.teamCount
self.teamSorting = tournament.teamSorting
self.federalCategory = tournament.federalCategory
self.federalLevelCategory = tournament.federalLevelCategory
self.federalAgeCategory = tournament.federalAgeCategory
self.closedRegistrationDate = tournament.closedRegistrationDate
self.groupStageAdditionalQualified = tournament.groupStageAdditionalQualified
self.courtCount = tournament.courtCount
self.prioritizeClubMembers = tournament.prioritizeClubMembers
self.qualifiedPerGroupStage = tournament.qualifiedPerGroupStage
self.teamsPerGroupStage = tournament.teamsPerGroupStage
self.entryFee = tournament.entryFee
self.payment = tournament.payment
self.additionalEstimationDuration = tournament.additionalEstimationDuration
self.isDeleted = tournament.isDeleted
self.isCanceled = tournament.isCanceled
self.publishTeams = tournament.publishTeams
self.publishSummons = tournament.publishSummons
self.publishGroupStages = tournament.publishGroupStages
self.publishBrackets = tournament.publishBrackets
self.shouldVerifyGroupStage = tournament.shouldVerifyGroupStage
self.shouldVerifyBracket = tournament.shouldVerifyBracket
self.hideTeamsWeight = tournament.hideTeamsWeight
self.publishTournament = tournament.publishTournament
self.hidePointsEarned = tournament.hidePointsEarned
self.publishRankings = tournament.publishRankings
self.loserBracketMode = tournament.loserBracketMode
self.initialSeedRound = tournament.initialSeedRound
self.initialSeedCount = tournament.initialSeedCount
self.enableOnlineRegistration = tournament.enableOnlineRegistration
self.registrationDateLimit = tournament.registrationDateLimit
self.openingRegistrationDate = tournament.openingRegistrationDate
self.waitingListLimit = tournament.waitingListLimit
self.accountIsRequired = tournament.accountIsRequired
self.licenseIsRequired = tournament.licenseIsRequired
self.minimumPlayerPerTeam = tournament.minimumPlayerPerTeam
self.maximumPlayerPerTeam = tournament.maximumPlayerPerTeam
self.information = tournament.information
self.umpireCustomMail = tournament.umpireCustomMail
self.umpireCustomContact = tournament.umpireCustomContact
self.umpireCustomPhone = tournament.umpireCustomPhone
self.hideUmpireMail = tournament.hideUmpireMail
self.hideUmpirePhone = tournament.hideUmpirePhone
self.disableRankingFederalRuling = tournament.disableRankingFederalRuling
self.teamCountLimit = tournament.teamCountLimit
self.enableOnlinePayment = tournament.enableOnlinePayment
self.onlinePaymentIsMandatory = tournament.onlinePaymentIsMandatory
self.enableOnlinePaymentRefund = tournament.enableOnlinePaymentRefund
self.refundDateLimit = tournament.refundDateLimit
self.stripeAccountId = tournament.stripeAccountId
self.enableTimeToConfirm = tournament.enableTimeToConfirm
self.isCorporateTournament = tournament.isCorporateTournament
self.isTemplate = tournament.isTemplate
}
static func relationships() -> [Relationship] {
return [
Relationship(type: Event.self, keyPath: \BaseTournament.event),
]
}
}

@ -1,93 +0,0 @@
{
"models": [
{
"name": "Club",
"synchronizable": true,
"observable": true,
"copy_server_response": "true",
"properties": [
{
"name": "id",
"type": "String",
"defaultValue": "Store.randomId()"
},
{
"name": "creator",
"type": "String",
"optional": true,
"defaultValue": "nil",
"foreignKey": "CustomUser"
},
{
"name": "name",
"type": "String",
"defaultValue": "\"\""
},
{
"name": "acronym",
"type": "String",
"defaultValue": "\"\""
},
{
"name": "phone",
"type": "String",
"optional": true,
"defaultValue": "nil"
},
{
"name": "code",
"type": "String",
"optional": true,
"defaultValue": "nil"
},
{
"name": "address",
"type": "String",
"optional": true,
"defaultValue": "nil"
},
{
"name": "city",
"type": "String",
"optional": true,
"defaultValue": "nil"
},
{
"name": "zipCode",
"type": "String",
"optional": true,
"defaultValue": "nil"
},
{
"name": "latitude",
"type": "Double",
"optional": true,
"defaultValue": "nil"
},
{
"name": "longitude",
"type": "Double",
"optional": true,
"defaultValue": "nil"
},
{
"name": "courtCount",
"type": "Int",
"defaultValue": "2"
},
{
"name": "broadcastCode",
"type": "String",
"optional": true,
"defaultValue": "nil"
},
{
"name": "timezone",
"type": "String",
"optional": true,
"defaultValue": "TimeZone.current.identifier"
}
]
}
]
}

@ -1,42 +0,0 @@
{
"models": [
{
"name": "Court",
"synchronizable": true,
"observable": true,
"properties": [
{
"name": "id",
"type": "String",
"defaultValue": "Store.randomId()"
},
{
"name": "index",
"type": "Int"
},
{
"name": "club",
"type": "String",
"foreignKey": "Club"
},
{
"name": "name",
"type": "String",
"optional": true,
"defaultValue": "nil"
},
{
"name": "exitAllowed",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "indoor",
"type": "Bool",
"defaultValue": "false"
}
],
"tokenExemptedMethods": []
}
]
}

@ -1,177 +0,0 @@
{
"models": [
{
"name": "CustomUser",
"resource_name": "users",
"synchronizable": true,
"observable": true,
"tokenExemptedMethods": ["post"],
"properties": [
{
"name": "id",
"type": "String",
"defaultValue": "Store.randomId()"
},
{
"name": "username",
"type": "String"
},
{
"name": "email",
"type": "String"
},
{
"name": "clubs",
"type": "[String]",
"defaultValue": "[]"
},
{
"name": "umpireCode",
"type": "String",
"optional": true,
"defaultValue": "nil"
},
{
"name": "licenceId",
"type": "String",
"optional": true,
"defaultValue": "nil"
},
{
"name": "firstName",
"type": "String"
},
{
"name": "lastName",
"type": "String"
},
{
"name": "phone",
"type": "String",
"optional": true,
"defaultValue": "nil"
},
{
"name": "country",
"type": "String",
"optional": true,
"defaultValue": "nil"
},
{
"name": "summonsMessageBody",
"type": "String",
"optional": true,
"defaultValue": "nil"
},
{
"name": "summonsMessageSignature",
"type": "String",
"optional": true,
"defaultValue": "nil"
},
{
"name": "summonsAvailablePaymentMethods",
"type": "String",
"optional": true,
"defaultValue": "nil"
},
{
"name": "summonsDisplayFormat",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "summonsDisplayEntryFee",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "summonsUseFullCustomMessage",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "matchFormatsDefaultDuration",
"type": "[MatchFormat: Int]",
"optional": true,
"defaultValue": "nil"
},
{
"name": "bracketMatchFormatPreference",
"type": "MatchFormat",
"optional": true,
"defaultValue": "nil"
},
{
"name": "groupStageMatchFormatPreference",
"type": "MatchFormat",
"optional": true,
"defaultValue": "nil"
},
{
"name": "loserBracketMatchFormatPreference",
"type": "MatchFormat",
"optional": true,
"defaultValue": "nil"
},
{
"name": "loserBracketMode",
"type": "LoserBracketMode",
"defaultValue": ".automatic"
},
{
"name": "disableRankingFederalRuling",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "deviceId",
"type": "String",
"optional": true,
"defaultValue": "nil"
},
{
"name": "agents",
"type": "[String]",
"defaultValue": "[]"
},
{
"name": "userRole",
"type": "Int",
"optional": true,
"defaultValue": "nil"
},
{
"name": "registrationPaymentMode",
"type": "RegistrationPaymentMode",
"defaultValue": "RegistrationPaymentMode.disabled"
},
{
"name": "umpireCustomMail",
"type": "String",
"optional": true
},
{
"name": "umpireCustomContact",
"type": "String",
"optional": true
},
{
"name": "umpireCustomPhone",
"type": "String",
"optional": true
},
{
"name": "hideUmpireMail",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "hideUmpirePhone",
"type": "Bool",
"defaultValue": "true"
}
]
}
]
}

@ -1,33 +0,0 @@
{
"models": [
{
"name": "DateInterval",
"synchronizable": true,
"observable": true,
"properties": [
{
"name": "id",
"type": "String",
"defaultValue": "Store.randomId()"
},
{
"name": "event",
"type": "String"
},
{
"name": "courtIndex",
"type": "Int"
},
{
"name": "startDate",
"type": "Date"
},
{
"name": "endDate",
"type": "Date"
}
],
"tokenExemptedMethods": []
}
]
}

@ -1,47 +0,0 @@
{
"models": [
{
"name": "DrawLog",
"synchronizable": true,
"sideStorable": true,
"observable": true,
"relationshipNames": [],
"properties": [
{
"name": "id",
"type": "String",
"defaultValue": "Store.randomId()"
},
{
"name": "tournament",
"type": "String",
"foreignKey": "Tournament"
},
{
"name": "drawDate",
"type": "Date",
"defaultValue": "Date()"
},
{
"name": "drawSeed",
"type": "Int"
},
{
"name": "drawMatchIndex",
"type": "Int"
},
{
"name": "drawTeamPosition",
"type": "TeamPosition",
"defaultValue": "TeamPosition.one"
},
{
"name": "drawType",
"type": "DrawType",
"defaultValue": "DrawType.seed"
}
]
}
]
}

@ -1,48 +0,0 @@
{
"models": [
{
"name": "Event",
"synchronizable": true,
"observable": true,
"properties": [
{
"name": "id",
"type": "String",
"defaultValue": "Store.randomId()"
},
{
"name": "creator",
"type": "String",
"optional": true,
"defaultValue": "nil",
"foreignKey": "CustomUser"
},
{
"name": "club",
"type": "String",
"optional": true,
"defaultValue": "nil",
"foreignKey": "Club"
},
{
"name": "creationDate",
"type": "Date",
"defaultValue": "Date()"
},
{
"name": "name",
"type": "String",
"optional": true,
"defaultValue": "nil"
},
{
"name": "tenupId",
"type": "String",
"optional": true,
"defaultValue": "nil"
}
],
"tokenExemptedMethods": []
}
]
}

@ -1,53 +0,0 @@
{
"models": [
{
"name": "GroupStage",
"synchronizable": true,
"observable": true,
"properties": [
{
"name": "id",
"type": "String",
"defaultValue": "Store.randomId()"
},
{
"name": "tournament",
"type": "String",
"foreignKey": "Tournament"
},
{
"name": "index",
"type": "Int"
},
{
"name": "size",
"type": "Int"
},
{
"name": "format",
"type": "MatchFormat",
"optional": true,
"defaultValue": "nil"
},
{
"name": "startDate",
"type": "Date",
"optional": true,
"defaultValue": "nil"
},
{
"name": "name",
"type": "String",
"optional": true,
"defaultValue": "nil"
},
{
"name": "step",
"type": "Int",
"defaultValue": "0"
}
],
"tokenExemptedMethods": []
}
]
}

@ -1,83 +0,0 @@
{
"models": [
{
"name": "Match",
"synchronizable": true,
"observable": true,
"tokenExemptedMethods": [],
"properties": [
{
"name": "id",
"type": "String",
"defaultValue": "Store.randomId()"
},
{
"name": "round",
"type": "String",
"optional": true,
"foreignKey": "Round*"
},
{
"name": "groupStage",
"type": "String",
"optional": true,
"foreignKey": "GroupStage*"
},
{
"name": "startDate",
"type": "Date",
"optional": true
},
{
"name": "endDate",
"type": "Date",
"optional": true
},
{
"name": "index",
"type": "Int"
},
{
"name": "format",
"type": "MatchFormat",
"optional": true
},
{
"name": "servingTeamId",
"type": "String",
"optional": true
},
{
"name": "winningTeamId",
"type": "String",
"optional": true
},
{
"name": "losingTeamId",
"type": "String",
"optional": true
},
{
"name": "name",
"type": "String",
"optional": true
},
{
"name": "disabled",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "courtIndex",
"type": "Int",
"optional": true
},
{
"name": "confirmed",
"type": "Bool",
"defaultValue": "false"
}
]
}
]
}

@ -1,91 +0,0 @@
{
"models": [
{
"name": "MatchScheduler",
"resource_name": "match-scheduler",
"synchronizable": false,
"observable": true,
"tokenExemptedMethods": [],
"properties": [
{
"name": "id",
"type": "String",
"defaultValue": "Store.randomId()"
},
{
"name": "tournament",
"type": "String",
"foreignKey": "Tournament"
},
{
"name": "timeDifferenceLimit",
"type": "Int",
"defaultValue": "5"
},
{
"name": "loserBracketRotationDifference",
"type": "Int",
"defaultValue": "0"
},
{
"name": "upperBracketRotationDifference",
"type": "Int",
"defaultValue": "1"
},
{
"name": "accountUpperBracketBreakTime",
"type": "Bool",
"defaultValue": "true"
},
{
"name": "accountLoserBracketBreakTime",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "randomizeCourts",
"type": "Bool",
"defaultValue": "true"
},
{
"name": "rotationDifferenceIsImportant",
"type": "Bool"
},
{
"name": "shouldHandleUpperRoundSlice",
"type": "Bool"
},
{
"name": "shouldEndRoundBeforeStartingNext",
"type": "Bool",
"defaultValue": "true"
},
{
"name": "groupStageChunkCount",
"type": "Int",
"optional": true
},
{
"name": "overrideCourtsUnavailability",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "shouldTryToFillUpCourtsAvailable",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "courtsAvailable",
"type": "Set<Int>",
"defaultValue": "Set<Int>()"
},
{
"name": "simultaneousStart",
"type": "Bool",
"defaultValue": "true"
}
]
}
]
}

@ -1,71 +0,0 @@
{
"models": [
{
"name": "MonthData",
"resource_name": "month-data",
"synchronizable": false,
"observable": true,
"tokenExemptedMethods": [],
"properties": [
{
"name": "id",
"type": "String",
"defaultValue": "Store.randomId()"
},
{
"name": "monthKey",
"type": "String"
},
{
"name": "creationDate",
"type": "Date"
},
{
"name": "maleUnrankedValue",
"type": "Int",
"optional": true,
"defaultValue": "nil"
},
{
"name": "femaleUnrankedValue",
"type": "Int",
"optional": true,
"defaultValue": "nil"
},
{
"name": "maleCount",
"type": "Int",
"optional": true,
"defaultValue": "nil"
},
{
"name": "femaleCount",
"type": "Int",
"optional": true,
"defaultValue": "nil"
},
{
"name": "anonymousCount",
"type": "Int",
"optional": true,
"defaultValue": "nil"
},
{
"name": "incompleteMode",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "dataModelIdentifier",
"type": "String",
"optional": true
},
{
"name": "fileModelIdentifier",
"type": "String",
"optional": true
}
]
}
]
}

@ -1,138 +0,0 @@
{
"models": [
{
"name": "PlayerRegistration",
"synchronizable": true,
"sideStorable": true,
"observable": true,
"relationshipNames": ["teamRegistration"],
"properties": [
{
"name": "id",
"type": "String",
"defaultValue": "Store.randomId()"
},
{
"name": "teamRegistration",
"type": "String",
"optional": true,
"foreignKey": "TeamRegistration"
},
{
"name": "firstName",
"type": "String"
},
{
"name": "lastName",
"type": "String"
},
{
"name": "licenceId",
"type": "String",
"optional": true
},
{
"name": "rank",
"type": "Int",
"optional": true
},
{
"name": "paymentType",
"type": "PlayerPaymentType",
"optional": true
},
{
"name": "sex",
"type": "PlayerSexType",
"optional": true
},
{
"name": "tournamentPlayed",
"type": "Int",
"optional": true
},
{
"name": "points",
"type": "Double",
"optional": true
},
{
"name": "clubName",
"type": "String",
"optional": true
},
{
"name": "ligueName",
"type": "String",
"optional": true
},
{
"name": "assimilation",
"type": "String",
"optional": true
},
{
"name": "phoneNumber",
"type": "String",
"optional": true
},
{
"name": "email",
"type": "String",
"optional": true
},
{
"name": "birthdate",
"type": "String",
"optional": true
},
{
"name": "computedRank",
"type": "Int",
"defaultValue": "0"
},
{
"name": "source",
"type": "PlayerRegistration.PlayerDataSource",
"optional": true
},
{
"name": "hasArrived",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "coach",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "captain",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "registeredOnline",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "timeToConfirm",
"type": "Date",
"optional": true
},
{
"name": "registrationStatus",
"type": "PlayerRegistration.RegistrationStatus",
"choices": "PlayerRegistration.RegistrationStatus",
"defaultValue": "PlayerRegistration.RegistrationStatus.waiting"
},
{
"name": "paymentId",
"type": "String",
"optional": true
}
]
}
]
}

@ -1,51 +0,0 @@
{
"models": [
{
"name": "Purchase",
"synchronizable": true,
"properties": [
{
"name": "id",
"type": "UInt64",
"defaultValue": "0"
},
{
"name": "user",
"type": "String",
"defaultValue": "\"\"",
"encryption": "standard",
"foreignKey": "CustomUser"
},
{
"name": "purchaseDate",
"type": "Date"
},
{
"name": "productId",
"type": "String",
"defaultValue": "\"\""
},
{
"name": "quantity",
"type": "Int",
"optional": true,
"defaultValue": "nil"
},
{
"name": "revocationDate",
"type": "Date",
"optional": true,
"defaultValue": "nil"
},
{
"name": "expirationDate",
"type": "Date",
"optional": true,
"defaultValue": "nil"
}
],
"tokenExemptedMethods": [],
"relationshipNames": []
}
]
}

@ -1,53 +0,0 @@
{
"models": [
{
"name": "Round",
"synchronizable": true,
"sideStorable": true,
"observable": true,
"relationshipNames": [],
"properties": [
{
"name": "id",
"type": "String",
"defaultValue": "Store.randomId()"
},
{
"name": "tournament",
"type": "String",
"foreignKey": "Tournament"
},
{
"name": "index",
"type": "Int"
},
{
"name": "parent",
"type": "String",
"optional": true
},
{
"name": "format",
"type": "MatchFormat",
"optional": true,
"private": true
},
{
"name": "startDate",
"type": "Date",
"optional": true
},
{
"name": "groupStageLoserBracket",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "loserBracketMode",
"type": "LoserBracketMode",
"defaultValue": ".automatic"
}
]
}
]
}

@ -1,118 +0,0 @@
{
"models": [
{
"name": "TeamRegistration",
"synchronizable": true,
"sideStorable": true,
"observable": true,
"relationshipNames": [],
"properties": [
{
"name": "id",
"type": "String",
"defaultValue": "Store.randomId()"
},
{
"name": "tournament",
"type": "String"
},
{
"name": "groupStage",
"type": "String",
"optional": true,
"foreignKey": "GroupStage*"
},
{
"name": "registrationDate",
"type": "Date",
"optional": true
},
{
"name": "callDate",
"type": "Date",
"optional": true
},
{
"name": "bracketPosition",
"type": "Int",
"optional": true
},
{
"name": "groupStagePosition",
"type": "Int",
"optional": true
},
{
"name": "comment",
"type": "String",
"optional": true
},
{
"name": "source",
"type": "String",
"optional": true
},
{
"name": "sourceValue",
"type": "String",
"optional": true
},
{
"name": "logo",
"type": "String",
"optional": true
},
{
"name": "name",
"type": "String",
"optional": true
},
{
"name": "walkOut",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "wildCardBracket",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "wildCardGroupStage",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "weight",
"type": "Int",
"defaultValue": "0"
},
{
"name": "lockedWeight",
"type": "Int",
"optional": true
},
{
"name": "confirmationDate",
"type": "Date",
"optional": true
},
{
"name": "qualified",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "finalRanking",
"type": "Int",
"optional": true
},
{
"name": "pointsEarned",
"type": "Int",
"optional": true
}
]
}
]
}

@ -1,44 +0,0 @@
{
"models": [
{
"name": "TeamScore",
"synchronizable": true,
"sideStorable": true,
"observable": true,
"relationshipNames": ["match"],
"properties": [
{
"name": "id",
"type": "String",
"defaultValue": "Store.randomId()"
},
{
"name": "match",
"type": "String",
"foreignKey": "Match*"
},
{
"name": "teamRegistration",
"type": "String",
"optional": true,
"foreignKey": "TeamRegistration*"
},
{
"name": "score",
"type": "String",
"optional": true
},
{
"name": "walkOut",
"type": "Int",
"optional": true
},
{
"name": "luckyLoser",
"type": "Int",
"optional": true
}
]
}
]
}

@ -1,355 +0,0 @@
{
"models": [
{
"name": "Tournament",
"synchronizable": true,
"copyable": true,
"observable": true,
"relationshipNames": [],
"properties": [
{
"name": "id",
"type": "String",
"defaultValue": "Store.randomId()"
},
{
"name": "event",
"type": "String",
"optional": true,
"foreignKey": "Event"
},
{
"name": "name",
"type": "String",
"optional": true
},
{
"name": "startDate",
"type": "Date"
},
{
"name": "endDate",
"type": "Date",
"optional": true
},
{
"name": "creationDate",
"type": "Date",
"private": true
},
{
"name": "isPrivate",
"type": "Bool"
},
{
"name": "groupStageFormat",
"type": "MatchFormat",
"optional": true,
"private": true
},
{
"name": "roundFormat",
"type": "MatchFormat",
"optional": true,
"private": true
},
{
"name": "loserRoundFormat",
"type": "MatchFormat",
"optional": true,
"private": true
},
{
"name": "groupStageSortMode",
"type": "GroupStageOrderingMode",
"defaultValue": "GroupStageOrderingMode.snake"
},
{
"name": "groupStageCount",
"type": "Int",
"defaultValue": "4"
},
{
"name": "rankSourceDate",
"type": "Date",
"optional": true
},
{
"name": "dayDuration",
"type": "Int",
"defaultValue": "1"
},
{
"name": "teamCount",
"type": "Int",
"defaultValue": "24"
},
{
"name": "teamSorting",
"type": "TeamSortingType",
"defaultValue": "TeamSortingType.inscriptionDate"
},
{
"name": "federalCategory",
"type": "TournamentCategory",
"defaultValue": "TournamentCategory.men"
},
{
"name": "federalLevelCategory",
"type": "TournamentLevel",
"defaultValue": "TournamentLevel.p100"
},
{
"name": "federalAgeCategory",
"type": "FederalTournamentAge",
"defaultValue": "FederalTournamentAge.senior"
},
{
"name": "closedRegistrationDate",
"type": "Date",
"optional": true
},
{
"name": "groupStageAdditionalQualified",
"type": "Int",
"defaultValue": "0"
},
{
"name": "courtCount",
"type": "Int",
"defaultValue": "2"
},
{
"name": "prioritizeClubMembers",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "qualifiedPerGroupStage",
"type": "Int",
"defaultValue": "1"
},
{
"name": "teamsPerGroupStage",
"type": "Int",
"defaultValue": "4"
},
{
"name": "entryFee",
"type": "Double",
"optional": true
},
{
"name": "payment",
"type": "TournamentPayment",
"optional": true,
"defaultValue": "nil",
"encryption": "tournament_payment",
"codingKey": "globalId"
},
{
"name": "additionalEstimationDuration",
"type": "Int",
"defaultValue": "0"
},
{
"name": "isDeleted",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "isCanceled",
"type": "Bool",
"defaultValue": "false",
"encryption": "tournament_iscanceled",
"codingKey": "localId"
},
{
"name": "publishTeams",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "publishSummons",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "publishGroupStages",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "publishBrackets",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "shouldVerifyGroupStage",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "shouldVerifyBracket",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "hideTeamsWeight",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "publishTournament",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "hidePointsEarned",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "publishRankings",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "loserBracketMode",
"type": "LoserBracketMode",
"defaultValue": ".automatic"
},
{
"name": "initialSeedRound",
"type": "Int",
"defaultValue": "0"
},
{
"name": "initialSeedCount",
"type": "Int",
"defaultValue": "0"
},
{
"name": "enableOnlineRegistration",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "registrationDateLimit",
"type": "Date",
"optional": true
},
{
"name": "openingRegistrationDate",
"type": "Date",
"optional": true
},
{
"name": "waitingListLimit",
"type": "Int",
"optional": true
},
{
"name": "accountIsRequired",
"type": "Bool",
"defaultValue": "true"
},
{
"name": "licenseIsRequired",
"type": "Bool",
"defaultValue": "true"
},
{
"name": "minimumPlayerPerTeam",
"type": "Int",
"defaultValue": "2"
},
{
"name": "maximumPlayerPerTeam",
"type": "Int",
"defaultValue": "2"
},
{
"name": "information",
"type": "String",
"optional": true
},
{
"name": "umpireCustomMail",
"type": "String",
"optional": true
},
{
"name": "umpireCustomContact",
"type": "String",
"optional": true
},
{
"name": "umpireCustomPhone",
"type": "String",
"optional": true
},
{
"name": "hideUmpireMail",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "hideUmpirePhone",
"type": "Bool",
"defaultValue": "true"
},
{
"name": "disableRankingFederalRuling",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "teamCountLimit",
"type": "Bool",
"defaultValue": "true"
},
{
"name": "enableOnlinePayment",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "onlinePaymentIsMandatory",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "enableOnlinePaymentRefund",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "refundDateLimit",
"type": "Date",
"optional": true
},
{
"name": "stripeAccountId",
"type": "String",
"optional": true
},
{
"name": "enableTimeToConfirm",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "isCorporateTournament",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "isTemplate",
"type": "Bool",
"defaultValue": "false"
}
]
}
]
}

@ -1,593 +0,0 @@
import json
import re
import os
from pathlib import Path
from typing import Dict, List, Any
import argparse
import sys
import logging
from datetime import datetime
import inflect
class SwiftModelGenerator:
def __init__(self, json_data: Dict[str, Any]):
self.data = json_data
self.pluralizer = inflect.engine()
def generate_model(self, model_data: Dict[str, Any]) -> str:
model_name = model_data["name"]
is_sync = model_data.get("synchronizable", False)
is_observable = model_data.get("observable", False)
properties = model_data["properties"]
# Get protocol specific configurations
resource = self.make_resource_name(model_name)
resource_name = model_data.get("resource_name", resource)
token_exempted = model_data.get("tokenExemptedMethods", [])
copy_server_response = model_data.get("copy_server_response", "false")
lines = ["// Generated by SwiftModelGenerator", "// Do not modify this file manually", ""]
# Import statement
lines.append("import Foundation")
lines.append("import LeStorage")
if is_observable:
lines.append("import SwiftUI")
lines.append("")
# Class declaration
if is_observable:
lines.append("@Observable")
protocol = "SyncedStorable" if is_sync else "Storable"
base_class = "SyncedModelObject" if is_sync else "BaseModelObject"
lines.append(f"class Base{model_name}: {base_class}, {protocol} {{")
lines.append("")
# Add SyncedStorable protocol requirements
lines.extend(self._generate_protocol_requirements(resource_name, token_exempted, copy_server_response))
lines.append("")
# Properties
for prop in properties:
swift_type = prop["type"]
if prop.get("optional", False):
swift_type += "?"
default_value = prop.get("defaultValue", "nil" if prop.get("optional", False) else self._get_default_value(swift_type))
lines.append(f" var {prop['name']}: {swift_type} = {default_value}")
lines.append("")
# Add constructor
lines.extend(self._generate_constructor(model_name, properties))
lines.append("")
# CodingKeys
lines.extend(self._generate_coding_keys(model_name, properties, is_observable))
lines.append("")
# Encryption methods
encrypted_props = [p for p in properties if "encryption" in p]
if encrypted_props:
lines.extend(self._generate_encryption_methods(properties))
lines.append("")
# Codable implementation
lines.extend(self._generate_decoder(model_name, properties, is_observable, is_sync))
lines.append("")
lines.extend(self._generate_encoder(properties, is_observable, is_sync))
lines.append("")
# Foreign Key convenience
foreign_key_methods = self._generate_foreign_key_methods(properties)
if foreign_key_methods:
lines.extend(foreign_key_methods)
# lines.append("")
# Copy method
lines.extend(self._generate_copy_method(model_name, properties))
lines.append("")
# Add relationships function
lines.extend(self._generate_relationships(model_name, properties))
lines.append("")
lines.append("}")
return "\n".join(lines)
def _generate_constructor(self, model_name: str, properties: List[Dict[str, Any]]) -> List[str]:
"""Generate a constructor with all properties as parameters with default values."""
lines = [" init("]
# Generate parameter list
params = []
for prop in properties:
name = prop['name']
type_name = prop['type']
is_optional = prop.get("optional", False)
# Always include a default value
default_value = prop.get("defaultValue")
if default_value is None:
if is_optional:
default_value = "nil"
else:
default_value = self._get_default_value(type_name)
# Format the parameter with its type and default value
param = f" {name}: {type_name}"
if is_optional:
param += "?"
param += f" = {default_value}"
params.append(param)
# Join parameters with commas
lines.extend([f"{param}," for param in params[:-1]])
lines.append(f"{params[-1]}") # Last parameter without comma
# Constructor body
lines.extend([
" ) {",
" super.init()",
])
# Property assignments
for prop in properties:
name = prop['name']
lines.append(f" self.{name} = {name}")
lines.append(" }")
lines.extend([
" required public override init() {",
" super.init()",
" }",
])
return lines
def _generate_foreign_key_methods(self, properties: List[Dict[str, Any]]) -> List[str]:
lines = []
for prop in properties:
if "foreignKey" in prop:
foreign_key = prop["foreignKey"]
prop_name = prop["name"]
method_name = f"{prop_name}Value"
is_optional = prop.get("optional", False)
lines.extend([f" func {method_name}() -> {foreign_key.rstrip('*')}? {{"])
if is_optional:
lines.extend([
f" guard let {prop_name} = self.{prop_name} else {{ return nil }}"
])
if foreign_key.endswith("*"):
foreign_key = foreign_key[:-1] # Remove the asterisk
lines.extend([
f" return self.store?.findById({prop_name})"
])
else:
lines.extend([
f" return Store.main.findById({prop_name})"
])
lines.extend([" }", ""]) # Close the method and add a blank line
return lines
def _generate_coding_keys(self, model_name: str, properties: List[Dict[str, Any]], is_observable: bool) -> List[str]:
lines = [" enum CodingKeys: String, CodingKey {"]
if model_name == 'Tournament':
lines.append(" case isCanceled = \"isCanceled\"")
lines.append(" case payment = \"payment\"")
for prop in properties:
name = prop['name']
# Add underscore prefix to case name if observable
case_name = f"_{name}" if is_observable else name
# Use custom codingKey if provided, otherwise use the property name
coding_key_value = prop.get("codingKey", name)
lines.append(f" case {case_name} = \"{coding_key_value}\"")
lines.append(" }")
return lines
def _generate_encryption_methods(self, properties: List[Dict[str, Any]]) -> List[str]:
lines = []
for prop in properties:
if "encryption" in prop:
name = prop['name']
enc_type = prop['encryption']
if enc_type == "tournament_payment":
lines.extend([
f" private static func _decode{name.capitalize()}(container: KeyedDecodingContainer<CodingKeys>) throws -> TournamentPayment? {{",
f" var data = try container.decodeIfPresent(Data.self, forKey: ._{name})",
" if data == nil {",
" data = try container.decodeIfPresent(Data.self, forKey: .payment)",
" }",
" ",
" if let data {",
" do {",
" let decoded: String = try data.decryptData(pass: CryptoKey.pass.rawValue)",
" let sequence = decoded.compactMap { NumberFormatter.standard.number(from: String($0))?.intValue }",
" return TournamentPayment(rawValue: sequence[18])",
" } catch {",
" Logger.error(error)",
" }",
" }",
" return nil",
" }",
"",
f" private func _encode{name.capitalize()}(container: inout KeyedEncodingContainer<CodingKeys>) throws {{",
f" guard let {name} else {{",
f" try container.encodeNil(forKey: ._{name})",
" return",
" }",
" ",
" let max: Int = TournamentPayment.allCases.count",
" var sequence = (1...18).map { _ in Int.random(in: (0..<max)) }",
f" sequence.append({name}.rawValue)",
" sequence.append(contentsOf: (1...13).map { _ in Int.random(in: (0..<max ))} )",
"",
" let stringCombo: [String] = sequence.map { $0.formatted() }",
" let joined: String = stringCombo.joined(separator: \"\")",
" if let data = joined.data(using: .utf8) {",
" let encryped: Data = try data.encrypt(pass: CryptoKey.pass.rawValue)",
f" try container.encodeIfPresent(encryped, forKey: ._{name})",
" }",
" }"
])
elif enc_type == "tournament_iscanceled":
lines.extend([
f" private static func _decode{name.capitalize()}(container: KeyedDecodingContainer<CodingKeys>) throws -> Bool {{",
f" var data = try container.decodeIfPresent(Data.self, forKey: ._{name})",
" if data == nil {",
" data = try container.decodeIfPresent(Data.self, forKey: .isCanceled)",
" }",
" if let data {",
" do {",
" let decoded: String = try data.decryptData(pass: CryptoKey.pass.rawValue)",
" let sequence = decoded.compactMap { NumberFormatter.standard.number(from: String($0))?.intValue }",
" return Bool.decodeInt(sequence[18])",
" } catch {",
" Logger.error(error)",
" }",
" }",
" return false",
" }",
"",
f" private func _encode{name.capitalize()}(container: inout KeyedEncodingContainer<CodingKeys>) throws {{",
" let max: Int = 9",
" var sequence = (1...18).map { _ in Int.random(in: (0...max)) }",
f" sequence.append(self.{name}.encodedValue)",
" sequence.append(contentsOf: (1...13).map { _ in Int.random(in: (0...max ))} )",
" ",
" let stringCombo: [String] = sequence.map { $0.formatted() }",
" let joined: String = stringCombo.joined(separator: \"\")",
" if let data = joined.data(using: .utf8) {",
" let encryped: Data = try data.encrypt(pass: CryptoKey.pass.rawValue)",
f" try container.encode(encryped, forKey: ._{name})",
" }",
" }"
])
return lines
def _generate_decoder(self, model_name: str, properties: List[Dict[str, Any]], is_observable: bool, is_sync: bool = False) -> List[str]:
lines = [" required init(from decoder: Decoder) throws {",
" let container = try decoder.container(keyedBy: CodingKeys.self)"]
for prop in properties:
name = prop['name']
type_name = prop['type']
is_optional = prop.get("optional", False)
default_value = prop.get("defaultValue", "nil" if is_optional else self._get_default_value(type_name))
option = prop.get("option")
# Use the correct case reference based on observable flag
case_ref = f"_{name}" if is_observable else name
if "encryption" in prop:
enc_type = prop['encryption']
if enc_type == "standard":
if is_optional:
lines.append(f" self.{name} = try container.decodeEncryptedIfPresent(key: .{case_ref})")
else:
lines.append(f" self.{name} = try container.decodeEncrypted(key: .{case_ref})")
elif enc_type in ["tournament_payment", "tournament_iscanceled"]:
lines.append(f" self.{name} = try Self._decode{name.capitalize()}(container: container)")
else:
# Handle Date with fractional option
if type_name == "Date" and option == "fractional":
if is_optional:
lines.append(f" if let dateString = try container.decodeIfPresent(String.self, forKey: .{case_ref}) {{")
lines.append(f" self.{name} = Date.iso8601FractionalFormatter.date(from: dateString)")
lines.append(" } else {")
lines.append(f" self.{name} = {default_value}")
lines.append(" }")
else:
lines.append(f" let dateString = try container.decode(String.self, forKey: .{case_ref})")
lines.append(f" self.{name} = Date.iso8601FractionalFormatter.date(from: dateString) ?? {default_value}")
else:
lines.append(f" self.{name} = try container.decodeIfPresent({type_name}.self, forKey: .{case_ref}) ?? {default_value}")
lines.append(" try super.init(from: decoder)")
lines.append(" }")
return lines
def _generate_encoder(self, properties: List[Dict[str, Any]], is_observable: bool, is_sync: bool = False) -> List[str]:
lines = [" override func encode(to encoder: Encoder) throws {",
" var container = encoder.container(keyedBy: CodingKeys.self)"]
for prop in properties:
name = prop['name']
type_name = prop['type']
is_optional = prop.get('optional', False)
option = prop.get("option")
# Use the correct case reference based on observable flag
case_ref = f"_{name}" if is_observable else name
if "encryption" in prop:
enc_type = prop['encryption']
if enc_type == "standard":
if is_optional:
lines.append(f" try container.encodeAndEncryptIfPresent(self.{name}?.data(using: .utf8), forKey: .{case_ref})")
else:
lines.append(f" try container.encodeAndEncryptIfPresent(self.{name}.data(using: .utf8), forKey: .{case_ref})")
elif enc_type in ["tournament_payment", "tournament_iscanceled"]:
lines.append(f" try _encode{name.capitalize()}(container: &container)")
else:
if type_name == "Date" and option == "fractional":
if is_optional:
lines.append(f" if let date = self.{name} {{")
lines.append(f" try container.encode(Date.iso8601FractionalFormatter.string(from: date), forKey: .{case_ref})")
lines.append(" } else {")
lines.append(f" try container.encodeNil(forKey: .{case_ref})")
lines.append(" }")
else:
lines.append(f" try container.encode(Date.iso8601FractionalFormatter.string(from: self.{name}), forKey: .{case_ref})")
else:
lines.append(f" try container.encode(self.{name}, forKey: .{case_ref})")
lines.append(" try super.encode(to: encoder)")
lines.append(" }")
return lines
def _generate_copy_method(self, model_name: str, properties: List[Dict[str, Any]]) -> List[str]:
model_variable = model_name.lower()
lines = [f" func copy(from other: any Storable) {{"]
lines.append(f" guard let {model_variable} = other as? Base{model_name} else {{ return }}")
for prop in properties:
name = prop['name']
lines.append(f" self.{name} = {model_variable}.{name}")
lines.append(" }")
return lines
def _generate_protocol_requirements(self, resource_name: str, token_exempted: List[str], copy_server_response: str) -> List[str]:
"""Generate the static functions required by SyncedStorable protocol."""
# Convert HTTP methods to proper format
formatted_methods = [f".{method.lower()}" for method in token_exempted]
methods_str = ", ".join(formatted_methods) if formatted_methods else ""
return [
f" static func resourceName() -> String {{ return \"{resource_name}\" }}",
f" static func tokenExemptedMethods() -> [HTTPMethod] {{ return [{methods_str}] }}",
f" static var copyServerResponse: Bool = {copy_server_response}",
]
def _generate_relationships(self, model_name, properties: List[Dict[str, Any]]) -> List[str]:
# Find all properties with foreign keys
foreign_key_props = [p for p in properties if "foreignKey" in p]
if not foreign_key_props:
# If no foreign keys, return empty array
return [
" static func relationships() -> [Relationship] {",
" return []",
" }"
]
lines = [
" static func relationships() -> [Relationship] {",
" return ["
]
# Generate relationship entries
for prop in foreign_key_props:
name = prop["name"]
foreign_key = prop["foreignKey"].rstrip('*') # Remove asterisk if present
lines.append(f" Relationship(type: {foreign_key}.self, keyPath: \\Base{model_name}.{name}),")
# Close the array and function
lines.extend([
" ]",
" }"
])
return lines
def _get_default_value(self, type_name: str) -> str:
"""Get default value for non-optional types"""
if "String" in type_name:
return "\"\""
elif "Int" in type_name:
return "0"
elif "Double" in type_name:
return "0.0"
elif "Bool" in type_name:
return "false"
elif "Date" in type_name:
return "Date()"
else:
return "nil" # For any other type
def get_output_filename(self, model_name: str) -> str:
"""Generate the output filename for a model."""
return f"Base{model_name}.swift"
def make_resource_name(self, text):
p = inflect.engine()
# Split camelCase into words
words = re.findall('[A-Z][^A-Z]*', text)
if not words:
words = [text]
words = [word.lower() for word in words]
words[-1] = p.plural(words[-1])
return '-'.join(words)
def process_directory(input_dir: str, output_dir: str, logger: logging.Logger, dry_run: bool = False) -> int:
"""Process all JSON files in the input directory and generate Swift models."""
try:
input_path = validate_directory(input_dir)
if not dry_run:
output_path = validate_directory(output_dir, create=True)
json_files = list(input_path.glob("*.json"))
if not json_files:
logger.warning(f"No JSON files found in '{input_dir}'")
return 0
logger.info(f"Found {len(json_files)} JSON files to process")
successful_files = 0
for json_file in json_files:
try:
with open(json_file, 'r') as f:
json_data = json.load(f)
generator = SwiftModelGenerator(json_data)
# Generate each model in the JSON file
for model in json_data["models"]:
model_name = model["name"]
swift_code = generator.generate_model(model)
if dry_run:
logger.info(f"Would generate Base{model_name}.swift")
continue
# Write to output file with Base prefix
output_file = output_path / generator.get_output_filename(model_name)
with open(output_file, 'w') as f:
f.write(swift_code)
logger.info(f"Generated Base{model_name}.swift")
successful_files += 1
except json.JSONDecodeError as e:
logger.error(f"Error parsing {json_file.name}: {e}")
except KeyError as e:
logger.error(f"Missing required key in {json_file.name}: {e}")
except Exception as e:
logger.error(f"Error processing {json_file.name}: {e}")
return successful_files
except Exception as e:
logger.error(f"Fatal error: {e}")
return 0
def setup_logging(verbose: bool) -> logging.Logger:
"""Configure logging based on verbosity level."""
logger = logging.getLogger('SwiftModelGenerator')
handler = logging.StreamHandler()
if verbose:
logger.setLevel(logging.DEBUG)
handler.setFormatter(logging.Formatter(
'%(asctime)s - %(levelname)s - %(message)s'
))
else:
logger.setLevel(logging.INFO)
handler.setFormatter(logging.Formatter('%(message)s'))
logger.addHandler(handler)
return logger
def validate_directory(path: str, create: bool = False) -> Path:
"""Validate and optionally create a directory."""
dir_path = Path(path)
if dir_path.exists():
if not dir_path.is_dir():
raise ValueError(f"'{path}' exists but is not a directory")
elif create:
dir_path.mkdir(parents=True)
else:
raise ValueError(f"Directory '{path}' does not exist")
return dir_path
def main():
parser = argparse.ArgumentParser(
description="Generate Swift model classes from JSON definitions",
epilog="Example: %(prog)s -i ./model_definitions -o ../MyProject/Models"
)
parser.add_argument(
"-i", "--input-dir",
required=True,
help="Directory containing JSON model definitions"
)
parser.add_argument(
"-o", "--output-dir",
required=True,
help="Directory where Swift files will be generated"
)
parser.add_argument(
"-v", "--verbose",
action="store_true",
help="Enable verbose output"
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Show what would be generated without actually creating files"
)
parser.add_argument(
"--version",
action="version",
version="%(prog)s 1.0.0"
)
args = parser.parse_args()
# Setup logging
logger = setup_logging(args.verbose)
# Process the directories
start_time = datetime.now()
logger.info("Starting model generation...")
successful_files = process_directory(
args.input_dir,
args.output_dir,
logger,
args.dry_run
)
# Report results
duration = datetime.now() - start_time
logger.info(f"\nGeneration completed in {duration.total_seconds():.2f} seconds")
logger.info(f"Successfully processed {successful_files} files")
# Return appropriate exit code
sys.exit(0 if successful_files > 0 else 1)
if __name__ == "__main__":
main()

@ -1,684 +0,0 @@
//
// GroupStage.swift
// Padel Tournament
//
// Created by razmig on 10/03/2024.
//
import Foundation
import LeStorage
import Algorithms
import SwiftUI
@Observable
final class GroupStage: BaseGroupStage, SideStorable {
var matchFormat: MatchFormat {
get {
format ?? .defaultFormatForMatchType(.groupStage)
}
set {
format = newValue
}
}
var tournamentStore: TournamentStore? {
return TournamentLibrary.shared.store(tournamentId: self.tournament)
}
// MARK: - Computed dependencies
func _matches() -> [Match] {
guard let tournamentStore = self.tournamentStore else { return [] }
return tournamentStore.matches.filter { $0.groupStage == self.id }.sorted(by: \.index)
// Store.main.filter { $0.groupStage == self.id }
}
func tournamentObject() -> Tournament? {
Store.main.findById(self.tournament)
}
// MARK: -
func teamAt(groupStagePosition: Int) -> TeamRegistration? {
if step > 0 {
return teams().first(where: { $0.groupStagePositionAtStep(step) == groupStagePosition })
}
return teams().first(where: { $0.groupStagePosition == groupStagePosition })
}
func groupStageTitle(_ displayStyle: DisplayStyle = .wide) -> String {
if let name { return name }
var stepLabel = ""
if step > 0 {
stepLabel = " (" + (step + 1).ordinalFormatted(feminine: true) + " phase)"
}
switch displayStyle {
case .title:
return "Poule \(index + 1)" + stepLabel
case .wide:
return "Poule \(index + 1)"
case .short:
return "#\(index + 1)"
}
}
var computedOrder: Int {
index + step * 100
}
func isRunning() -> Bool { // at least a match has started
_matches().anySatisfy({ $0.isRunning() })
}
func hasStarted() -> Bool { // meaning at least one match is over
_matches().filter { $0.hasEnded() }.isEmpty == false
}
func hasEnded() -> Bool {
let _matches = _matches()
if _matches.isEmpty { return false }
//guard teams().count == size else { return false }
return _matches.anySatisfy { $0.hasEnded() == false } == false
}
fileprivate func _createMatch(index: Int) -> Match {
let match: Match = Match(groupStage: self.id,
index: index,
format: self.matchFormat,
name: self.localizedMatchUpLabel(for: index))
match.store = self.store
// print("_createMatch(index)", index)
return match
}
func removeReturnMatches(onlyLast: Bool = false) {
var returnMatches = _matches().filter({ $0.index >= matchCount })
if onlyLast {
let matchPhaseCount = matchPhaseCount - 1
returnMatches = returnMatches.filter({ $0.index >= matchCount * matchPhaseCount })
}
do {
try self.tournamentStore?.matches.delete(contentOfs: returnMatches)
} catch {
Logger.error(error)
}
}
var matchPhaseCount: Int {
let count = _matches().count
if matchCount > 0 {
return count / matchCount
} else {
return 0
}
}
func addReturnMatches() {
var teamScores = [TeamScore]()
var matches = [Match]()
let matchPhaseCount = matchPhaseCount
for i in 0..<_numberOfMatchesToBuild() {
let newMatch = self._createMatch(index: i + matchCount * matchPhaseCount)
// let newMatch = Match(groupStage: self.id, index: i, matchFormat: self.matchFormat, name: localizedMatchUpLabel(for: i))
teamScores.append(contentsOf: newMatch.createTeamScores())
matches.append(newMatch)
}
self.tournamentStore?.matches.addOrUpdate(contentOfs: matches)
self.tournamentStore?.teamScores.addOrUpdate(contentOfs: teamScores)
}
func buildMatches(keepExistingMatches: Bool = false) {
var teamScores = [TeamScore]()
var matches = [Match]()
clearScoreCache()
if keepExistingMatches == false {
_removeMatches()
for i in 0..<_numberOfMatchesToBuild() {
let newMatch = self._createMatch(index: i)
// let newMatch = Match(groupStage: self.id, index: i, matchFormat: self.matchFormat, name: localizedMatchUpLabel(for: i))
teamScores.append(contentsOf: newMatch.createTeamScores())
matches.append(newMatch)
}
} else {
for match in _matches() {
match.resetTeamScores(outsideOf: [])
teamScores.append(contentsOf: match.createTeamScores())
}
}
do {
try self.tournamentStore?.matches.addOrUpdate(contentOfs: matches)
try self.tournamentStore?.teamScores.addOrUpdate(contentOfs: teamScores)
} catch {
Logger.error(error)
}
}
func playedMatches() -> [Match] {
let ordered = _matches()
let order = _matchOrder()
let matchCount = max(1, matchCount)
let count = ordered.count / matchCount
if ordered.isEmpty == false && ordered.count % order.count == 0 {
let repeatedArray = (0..<count).flatMap { i in
order.map { $0 + i * order.count }
}
let result = repeatedArray.map { ordered[$0] }
return result
} else {
return ordered
}
}
func orderedIndexOfMatch(_ match: Match) -> Int {
_matchOrder()[safe: match.index] ?? match.index
}
func updateGroupStageState() {
clearScoreCache()
if hasEnded(), let tournament = tournamentObject() {
let teams = teams(true)
for (index, team) in teams.enumerated() {
team.qualified = index < tournament.qualifiedPerGroupStage
if team.bracketPosition != nil && team.qualified == false {
tournamentObject()?.shouldVerifyBracket = true
}
}
try self.tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: teams)
if let tournamentObject = tournamentObject() {
try DataStore.shared.tournaments.addOrUpdate(instance: tournamentObject)
}
self.tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: teams)
let groupStagesAreOverAtFirstStep = tournament.groupStagesAreOver(atStep: 0)
let nextStepGroupStages = tournament.groupStages(atStep: 1)
let groupStagesAreOverAtSecondStep = tournament.groupStagesAreOver(atStep: 1)
if groupStagesAreOverAtFirstStep, nextStepGroupStages.isEmpty || groupStagesAreOverAtSecondStep == true, tournament.groupStageLoserBracketAreOver(), tournament.rounds().isEmpty {
tournament.endDate = Date()
DataStore.shared.tournaments.addOrUpdate(instance: tournament)
}
tournament.updateTournamentState()
}
}
func scoreLabel(forGroupStagePosition groupStagePosition: Int, score: TeamGroupStageScore? = nil) -> (wins: String, losses: String, setsDifference: String?, gamesDifference: String?)? {
if let scoreData = (score ?? _score(forGroupStagePosition: groupStagePosition, nilIfEmpty: true)) {
let hideSetDifference = matchFormat.setsToWin == 1
let setDifference = scoreData.setDifference.formatted(.number.sign(strategy: .always(includingZero: true))) + " set" + scoreData.setDifference.pluralSuffix
let gameDifference = scoreData.gameDifference.formatted(.number.sign(strategy: .always(includingZero: true))) + " jeu" + scoreData.gameDifference.localizedPluralSuffix("x")
return (wins: scoreData.wins.formatted(), losses: scoreData.loses.formatted(), setsDifference: hideSetDifference ? nil : setDifference, gamesDifference: gameDifference)
// return "\(scoreData.wins)/\(scoreData.loses) " + differenceAsString
} else {
return nil
}
}
// func _score(forGroupStagePosition groupStagePosition: Int, nilIfEmpty: Bool = false) -> TeamGroupStageScore? {
// guard let team = teamAt(groupStagePosition: groupStagePosition) else { return nil }
// let matches = matches(forGroupStagePosition: groupStagePosition).filter({ $0.hasEnded() })
// if matches.isEmpty && nilIfEmpty { return nil }
// let wins = matches.filter { $0.winningTeamId == team.id }.count
// let loses = matches.filter { $0.losingTeamId == team.id }.count
// let differences = matches.compactMap { $0.scoreDifference(groupStagePosition, atStep: step) }
// let setDifference = differences.map { $0.set }.reduce(0,+)
// let gameDifference = differences.map { $0.game }.reduce(0,+)
// return (team, wins, loses, setDifference, gameDifference)
// /*
// 2 points par rencontre gagnée
// 1 point par rencontre perdue
// -1 point en cas de rencontre perdue par disqualification (scores de 6/0 6/0 attribués aux trois matchs)
// -2 points en cas de rencontre perdu par WO (scores de 6/0 6/0 attribués aux trois matchs)
// */
// }
//
func matches(forGroupStagePosition groupStagePosition: Int) -> [Match] {
let combos = Array((0..<size).combinations(ofCount: 2))
var matchIndexes = [Int]()
for (index, combo) in combos.enumerated() {
if combo.contains(groupStagePosition) { //team is playing
matchIndexes.append(index)
}
}
return _matches().filter { matchIndexes.contains($0.index%matchCount) }
}
func initialStartDate(forTeam team: TeamRegistration) -> Date? {
guard let groupStagePosition = team.groupStagePositionAtStep(step) else { return nil }
return matches(forGroupStagePosition: groupStagePosition).compactMap({ $0.startDate }).sorted().first ?? startDate
}
func matchPlayed(by groupStagePosition: Int, againstPosition: Int) -> Match? {
if groupStagePosition == againstPosition { return nil }
let combos = Array((0..<size).combinations(ofCount: 2))
var matchIndexes = [Int]()
for (index, combo) in combos.enumerated() {
if combo.contains(groupStagePosition) && combo.contains(againstPosition) { //teams are playing
matchIndexes.append(index)
}
}
return _matches().first(where: { matchIndexes.contains($0.index) })
}
func availableToStart(playedMatches: [Match], in runningMatches: [Match], checkCanPlay: Bool = true) -> [Match] {
#if _DEBUG_TIME //DEBUGING TIME
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func group stage availableToStart", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif
return playedMatches.filter({ $0.isRunning() == false && $0.canBeStarted(inMatches: runningMatches, checkCanPlay: checkCanPlay) }).sorted(by: \.computedStartDateForSorting)
}
func runningMatches(playedMatches: [Match]) -> [Match] {
#if _DEBUG_TIME //DEBUGING TIME
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func group stage runningMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif
return playedMatches.filter({ $0.isRunning() }).sorted(by: \.computedStartDateForSorting)
}
func readyMatches(playedMatches: [Match]) -> [Match] {
#if _DEBUG_TIME //DEBUGING TIME
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func group stage readyMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif
return playedMatches.filter({ $0.isReady() && $0.isRunning() == false && $0.hasEnded() == false })
}
func finishedMatches(playedMatches: [Match]) -> [Match] {
#if _DEBUG_TIME //DEBUGING TIME
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func group stage finishedMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif
return playedMatches.filter({ $0.hasEnded() }).sorted(by: \.computedEndDateForSorting).reversed()
}
func isReturnMatchEnabled() -> Bool {
_matches().count > matchCount
}
private func _matchOrder() -> [Int] {
var order: [Int]
switch size {
case 3:
order = [1, 2, 0]
case 4:
order = [2, 3, 1, 4, 5, 0]
case 5:
order = [3, 5, 8, 2, 6, 1, 9, 4, 7, 0]
case 6:
order = [4, 7, 9, 3, 6, 11, 2, 8, 10, 1, 13, 5, 12, 14, 0]
default:
order = []
}
return order
}
func indexOf(_ matchIndex: Int) -> Int {
_matchOrder().firstIndex(of: matchIndex) ?? matchIndex
}
func _matchUp(for matchIndex: Int) -> [Int] {
let combinations = Array((0..<size).combinations(ofCount: 2))
return combinations[safe: matchIndex%matchCount] ?? []
}
func returnMatchesSuffix(for matchIndex: Int) -> String {
if matchCount > 0 {
let count = _matches().count
if count > matchCount * 2 {
return " - vague \((matchIndex / matchCount) + 1)"
}
if matchIndex >= matchCount {
return " - retour"
}
}
return ""
}
func localizedMatchUpLabel(for matchIndex: Int) -> String {
let matchUp = _matchUp(for: matchIndex)
if let index = matchUp.first, let index2 = matchUp.last {
return "#\(index + 1) vs #\(index2 + 1)" + returnMatchesSuffix(for: matchIndex)
} else {
return "--"
}
}
var matchCount: Int {
(size * (size - 1)) / 2
}
func team(teamPosition team: TeamPosition, inMatchIndex matchIndex: Int) -> TeamRegistration? {
let _teams = _teams(for: matchIndex)
switch team {
case .one:
return _teams.first ?? nil
case .two:
return _teams.last ?? nil
}
}
private func _teams(for matchIndex: Int) -> [TeamRegistration?] {
let combinations = Array(0..<size).combinations(ofCount: 2).map {$0}
return combinations[safe: matchIndex%matchCount]?.map { teamAt(groupStagePosition: $0) } ?? []
}
private func _removeMatches() {
self.tournamentStore?.matches.delete(contentOfs: _matches())
}
private func _numberOfMatchesToBuild() -> Int {
(size * (size - 1)) / 2
}
func unsortedPlayers() -> [PlayerRegistration] {
unsortedTeams().flatMap({ $0.unsortedPlayers() })
}
typealias TeamScoreAreInIncreasingOrder = (TeamGroupStageScore, TeamGroupStageScore) -> Bool
typealias TeamGroupStageScore = (team: TeamRegistration, wins: Int, loses: Int, setDifference: Int, gameDifference: Int)
fileprivate func _headToHead(_ teamPosition: TeamRegistration, _ otherTeam: TeamRegistration) -> Bool {
let indexes = [teamPosition, otherTeam].compactMap({ $0.groupStagePosition }).sorted()
let combos = Array((0..<size).combinations(ofCount: 2))
let matchIndexes = combos.enumerated().compactMap { $0.element == indexes ? $0.offset : nil }
let matches = _matches().filter { matchIndexes.contains($0.index) }
if matches.count > 1 {
let scoreA = calculateScore(for: teamPosition, matches: matches, groupStagePosition: teamPosition.groupStagePosition!)
let scoreB = calculateScore(for: otherTeam, matches: matches, groupStagePosition: otherTeam.groupStagePosition!)
let teamsSorted = [scoreA, scoreB].sorted { (lhs, rhs) in
let predicates: [TeamScoreAreInIncreasingOrder] = [
{ $0.wins < $1.wins },
{ $0.setDifference < $1.setDifference },
{ $0.gameDifference < $1.gameDifference},
{ [self] in $0.team.groupStagePositionAtStep(self.step)! > $1.team.groupStagePositionAtStep(self.step)! }
]
for predicate in predicates {
if !predicate(lhs, rhs) && !predicate(rhs, lhs) {
continue
}
return predicate(lhs, rhs)
}
return false
}.map({ $0.team })
return teamsSorted.first == teamPosition
} else {
if let matchIndex = combos.firstIndex(of: indexes), let match = _matches().first(where: { $0.index == matchIndex }) {
return teamPosition.id == match.losingTeamId
} else {
return false
}
}
}
func unsortedTeams() -> [TeamRegistration] {
guard let tournamentStore = self.tournamentStore else { return [] }
if step > 0 {
return tournamentStore.groupStages.filter({ $0.step == step - 1 }).compactMap({ $0.teams(true)[safe: index] })
}
return tournamentStore.teamRegistrations.filter { $0.groupStage == self.id && $0.groupStagePosition != nil }
}
var scoreCache: [Int: TeamGroupStageScore] = [:]
func computedScore(forTeam team: TeamRegistration, step: Int = 0) -> TeamGroupStageScore? {
guard let groupStagePositionAtStep = team.groupStagePositionAtStep(step) else {
return nil
}
if let cachedScore = scoreCache[groupStagePositionAtStep] {
return cachedScore
} else {
let score = _score(forGroupStagePosition: groupStagePositionAtStep)
if let score = score {
scoreCache[groupStagePositionAtStep] = score
}
return score
}
}
func teams(_ sortedByScore: Bool = false, scores: [TeamGroupStageScore]? = nil) -> [TeamRegistration] {
if sortedByScore {
return unsortedTeams().compactMap({ team in
// Check cache or use provided scores, otherwise calculate and store in cache
scores?.first(where: { $0.team.id == team.id }) ?? {
return computedScore(forTeam: team, step: step)
}()
}).sorted { (lhs, rhs) in
let predicates: [TeamScoreAreInIncreasingOrder] = [
{ $0.wins < $1.wins },
{ $0.setDifference < $1.setDifference },
{ $0.gameDifference < $1.gameDifference},
{ self._headToHead($0.team, $1.team) },
{ [self] in $0.team.groupStagePositionAtStep(self.step)! > $1.team.groupStagePositionAtStep(self.step)! }
]
for predicate in predicates {
if !predicate(lhs, rhs) && !predicate(rhs, lhs) {
continue
}
return predicate(lhs, rhs)
}
return false
}.map({ $0.team }).reversed()
} else {
return unsortedTeams().sorted(by: \TeamRegistration.groupStagePosition!)
}
}
func _score(forGroupStagePosition groupStagePosition: Int, nilIfEmpty: Bool = false) -> TeamGroupStageScore? {
// Check if the score for this position is already cached
if let cachedScore = scoreCache[groupStagePosition] {
return cachedScore
}
guard let team = teamAt(groupStagePosition: groupStagePosition) else { return nil }
let matches = matches(forGroupStagePosition: groupStagePosition).filter({ $0.hasEnded() })
if matches.isEmpty && nilIfEmpty { return nil }
let score = calculateScore(for: team, matches: matches, groupStagePosition: groupStagePosition)
scoreCache[groupStagePosition] = score
return score
}
private func calculateScore(for team: TeamRegistration, matches: [Match], groupStagePosition: Int) -> TeamGroupStageScore {
let wins = matches.filter { $0.winningTeamId == team.id }.count
let loses = matches.filter { $0.losingTeamId == team.id }.count
let differences = matches.compactMap { $0.scoreDifference(groupStagePosition, atStep: step) }
let setDifference = differences.map { $0.set }.reduce(0,+)
let gameDifference = differences.map { $0.game }.reduce(0,+)
return (team, wins, loses, setDifference, gameDifference)
}
// Clear the cache if necessary, for example when starting a new step or when matches update
func clearScoreCache() {
scoreCache.removeAll()
}
// func teams(_ sortedByScore: Bool = false, scores: [TeamGroupStageScore]? = nil) -> [TeamRegistration] {
// if sortedByScore {
// return unsortedTeams().compactMap({ team in
// scores?.first(where: { $0.team.id == team.id }) ?? _score(forGroupStagePosition: team.groupStagePositionAtStep(step)!)
// }).sorted { (lhs, rhs) in
// // Calculate intermediate values once and reuse them
// let lhsWins = lhs.wins
// let rhsWins = rhs.wins
// let lhsSetDifference = lhs.setDifference
// let rhsSetDifference = rhs.setDifference
// let lhsGameDifference = lhs.gameDifference
// let rhsGameDifference = rhs.gameDifference
// let lhsHeadToHead = self._headToHead(lhs.team, rhs.team)
// let rhsHeadToHead = self._headToHead(rhs.team, lhs.team)
// let lhsGroupStagePosition = lhs.team.groupStagePositionAtStep(self.step)!
// let rhsGroupStagePosition = rhs.team.groupStagePositionAtStep(self.step)!
//
// // Define comparison predicates in the same order
// let predicates: [(Bool, Bool)] = [
// (lhsWins < rhsWins, lhsWins > rhsWins),
// (lhsSetDifference < rhsSetDifference, lhsSetDifference > rhsSetDifference),
// (lhsGameDifference < rhsGameDifference, lhsGameDifference > rhsGameDifference),
// (lhsHeadToHead, rhsHeadToHead),
// (lhsGroupStagePosition > rhsGroupStagePosition, lhsGroupStagePosition < rhsGroupStagePosition)
// ]
//
// // Iterate over predicates and return as soon as a valid comparison is found
// for (lhsPredicate, rhsPredicate) in predicates {
// if lhsPredicate { return true }
// if rhsPredicate { return false }
// }
//
// return false
// }.map({ $0.team }).reversed()
// } else {
// return unsortedTeams().sorted(by: \TeamRegistration.groupStagePosition!)
// }
// }
func updateMatchFormat(_ updatedMatchFormat: MatchFormat) {
self.matchFormat = updatedMatchFormat
self.updateAllMatchesFormat()
}
func updateAllMatchesFormat() {
let playedMatches = playedMatches()
playedMatches.forEach { match in
match.matchFormat = matchFormat
}
self.tournamentStore?.matches.addOrUpdate(contentOfs: playedMatches)
}
func pasteData() -> String {
var data: [String] = []
data.append(self.groupStageTitle())
teams().forEach { team in
data.append(team.teamLabelRanked(displayRank: true, displayTeamName: true))
}
return data.joined(separator: "\n")
}
func finalPosition(ofTeam team: TeamRegistration) -> Int? {
guard hasEnded() else { return nil }
return teams(true).firstIndex(of: team)
}
override func deleteDependencies() {
let matches = self._matches()
for match in matches {
match.deleteDependencies()
}
self.tournamentStore?.matches.deleteDependencies(matches)
}
// init(from decoder: Decoder) throws {
// let container = try decoder.container(keyedBy: CodingKeys.self)
//
// id = try container.decode(String.self, forKey: ._id)
// storeId = try container.decode(String.self, forKey: ._storeId)
// lastUpdate = try container.decode(Date.self, forKey: ._lastUpdate)
// tournament = try container.decode(String.self, forKey: ._tournament)
// index = try container.decode(Int.self, forKey: ._index)
// size = try container.decode(Int.self, forKey: ._size)
// format = try container.decodeIfPresent(MatchFormat.self, forKey: ._format)
// startDate = try container.decodeIfPresent(Date.self, forKey: ._startDate)
// name = try container.decodeIfPresent(String.self, forKey: ._name)
// step = try container.decodeIfPresent(Int.self, forKey: ._step) ?? 0
// }
//
// func encode(to encoder: Encoder) throws {
// var container = encoder.container(keyedBy: CodingKeys.self)
//
// try container.encode(id, forKey: ._id)
// try container.encode(storeId, forKey: ._storeId)
// try container.encode(lastUpdate, forKey: ._lastUpdate)
// try container.encode(tournament, forKey: ._tournament)
// try container.encode(index, forKey: ._index)
// try container.encode(size, forKey: ._size)
// try container.encode(format, forKey: ._format)
// try container.encode(startDate, forKey: ._startDate)
// try container.encode(name, forKey: ._name)
// try container.encode(step, forKey: ._step)
// }
func insertOnServer() {
self.tournamentStore?.groupStages.writeChangeAndInsertOnServer(instance: self)
for match in self._matches() {
match.insertOnServer()
}
}
}
extension GroupStage {
enum CodingKeys: String, CodingKey {
case _id = "id"
case _storeId = "storeId"
case _lastUpdate = "lastUpdate"
case _tournament = "tournament"
case _index = "index"
case _size = "size"
case _format = "format"
case _startDate = "startDate"
case _name = "name"
case _step = "step"
}
}
extension GroupStage: Selectable {
func selectionLabel(index: Int) -> String {
groupStageTitle()
}
func badgeValue() -> Int? {
return runningMatches(playedMatches: _matches()).count
}
func badgeValueColor() -> Color? {
return nil
}
func badgeImage() -> Badge? {
if teams().count < size {
return .xmark
} else {
return hasEnded() ? .checkmark : nil
}
}
}

File diff suppressed because it is too large Load Diff

@ -1,888 +0,0 @@
//
// MatchScheduler.swift
// PadelClub
//
// Created by Razmig Sarkissian on 08/04/2024.
//
import Foundation
import LeStorage
import SwiftUI
@Observable
final class MatchScheduler: BaseMatchScheduler, SideStorable {
//
// init(tournament: String,
// timeDifferenceLimit: Int = 5,
// loserBracketRotationDifference: Int = 0,
// upperBracketRotationDifference: Int = 1,
// accountUpperBracketBreakTime: Bool = true,
// accountLoserBracketBreakTime: Bool = false,
// randomizeCourts: Bool = true,
// rotationDifferenceIsImportant: Bool = false,
// shouldHandleUpperRoundSlice: Bool = false,
// shouldEndRoundBeforeStartingNext: Bool = true,
//<<<<<<< HEAD
// groupStageChunkCount: Int? = nil, overrideCourtsUnavailability: Bool = false, shouldTryToFillUpCourtsAvailable: Bool = false) {
// super.init()
//=======
// groupStageChunkCount: Int? = nil,
// overrideCourtsUnavailability: Bool = false,
// shouldTryToFillUpCourtsAvailable: Bool = true,
// courtsAvailable: Set<Int> = Set<Int>(),
// simultaneousStart: Bool = true) {
//>>>>>>> main
// self.tournament = tournament
// self.timeDifferenceLimit = timeDifferenceLimit
// self.loserBracketRotationDifference = loserBracketRotationDifference
// self.upperBracketRotationDifference = upperBracketRotationDifference
// self.accountUpperBracketBreakTime = accountUpperBracketBreakTime
// self.accountLoserBracketBreakTime = accountLoserBracketBreakTime
// self.randomizeCourts = randomizeCourts
// self.rotationDifferenceIsImportant = rotationDifferenceIsImportant
// self.shouldHandleUpperRoundSlice = shouldHandleUpperRoundSlice
// self.shouldEndRoundBeforeStartingNext = shouldEndRoundBeforeStartingNext
// self.groupStageChunkCount = groupStageChunkCount
// self.overrideCourtsUnavailability = overrideCourtsUnavailability
// self.shouldTryToFillUpCourtsAvailable = shouldTryToFillUpCourtsAvailable
// self.courtsAvailable = courtsAvailable
// self.simultaneousStart = simultaneousStart
// }
var courtsUnavailability: [DateInterval]? {
guard let event = tournamentObject()?.eventObject() else { return nil }
return event.courtsUnavailability + (overrideCourtsUnavailability ? [] : event.tournamentsCourtsUsed(exluding: tournament))
}
var additionalEstimationDuration : Int {
return tournamentObject()?.additionalEstimationDuration ?? 0
}
var tournamentStore: TournamentStore? {
return TournamentLibrary.shared.store(tournamentId: self.tournament)
// TournamentStore.instance(tournamentId: self.tournament)
}
func tournamentObject() -> Tournament? {
return Store.main.findById(tournament)
}
@discardableResult
func updateGroupStageSchedule(tournament: Tournament, specificGroupStage: GroupStage? = nil, atStep step: Int = 0, startDate: Date? = nil) -> Date {
let computedGroupStageChunkCount = groupStageChunkCount ?? tournament.getGroupStageChunkValue()
var groupStages: [GroupStage] = tournament.groupStages(atStep: step)
if let specificGroupStage {
groupStages = [specificGroupStage]
}
let matches = groupStages.flatMap { $0._matches() }
matches.forEach({
$0.removeCourt()
$0.startDate = nil
$0.confirmed = false
})
var lastDate : Date = startDate ?? tournament.startDate
let times = Set(groupStages.compactMap { $0.startDate }).sorted()
if let first = times.first {
if first.isEarlierThan(tournament.startDate) {
tournament.startDate = first
do {
try DataStore.shared.tournaments.addOrUpdate(instance: tournament)
} catch {
Logger.error(error)
}
}
}
times.forEach({ time in
if lastDate.isEarlierThan(time) {
lastDate = time
}
let groups = groupStages.filter({ $0.startDate == time })
let dispatch = groupStageDispatcher(groupStages: groups, startingDate: lastDate)
dispatch.timedMatches.forEach { matchSchedule in
if let match = matches.first(where: { $0.id == matchSchedule.matchID }) {
let estimatedDuration = match.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)
let timeIntervalToAdd = (Double(matchSchedule.rotationIndex)) * Double(estimatedDuration) * 60
if let startDate = match.groupStageObject?.startDate {
let matchStartDate = startDate.addingTimeInterval(timeIntervalToAdd)
match.startDate = matchStartDate
lastDate = matchStartDate.addingTimeInterval(Double(estimatedDuration) * 60)
}
match.setCourt(matchSchedule.courtIndex)
}
}
})
groupStages.filter({ $0.startDate == nil || times.contains($0.startDate!) == false }).chunked(into: computedGroupStageChunkCount).forEach { groups in
groups.forEach({ $0.startDate = lastDate })
do {
try self.tournamentStore?.groupStages.addOrUpdate(contentOfs: groups)
} catch {
Logger.error(error)
}
let dispatch = groupStageDispatcher(groupStages: groups, startingDate: lastDate)
dispatch.timedMatches.forEach { matchSchedule in
if let match = matches.first(where: { $0.id == matchSchedule.matchID }) {
let estimatedDuration = match.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)
let timeIntervalToAdd = (Double(matchSchedule.rotationIndex)) * Double(estimatedDuration) * 60
if let startDate = match.groupStageObject?.startDate {
let matchStartDate = startDate.addingTimeInterval(timeIntervalToAdd)
match.startDate = matchStartDate
lastDate = matchStartDate.addingTimeInterval(Double(estimatedDuration) * 60)
}
match.setCourt(matchSchedule.courtIndex)
}
}
}
do {
try self.tournamentStore?.matches.addOrUpdate(contentOfs: matches)
} catch {
Logger.error(error)
}
return lastDate
}
func groupStageDispatcher(groupStages: [GroupStage], startingDate: Date) -> GroupStageMatchDispatcher {
let _groupStages = groupStages
// Get the maximum count of matches in any group
let maxMatchesCount = _groupStages.map { $0._matches().count }.max() ?? 0
var flattenedMatches = [Match]()
if simultaneousStart {
// Flatten matches in a round-robin order by cycling through each group
flattenedMatches = (0..<maxMatchesCount).flatMap { index in
_groupStages.compactMap { group in
// Safely access matches, return nil if index is out of bounds
let playedMatches = group.playedMatches()
return playedMatches.indices.contains(index) ? playedMatches[index] : nil
}
}
} else {
flattenedMatches = _groupStages.flatMap({ $0.playedMatches() })
}
var slots = [GroupStageTimeMatch]()
var availableMatches = flattenedMatches
var rotationIndex = 0
var teamsPerRotation = [Int: [String]]() // Tracks teams assigned to each rotation
var freeCourtPerRotation = [Int: [Int]]() // Tracks free courts per rotation
var groupLastRotation = [Int: Int]() // Tracks the last rotation each group was involved in
let courtsUnavailability = courtsUnavailability
while slots.count < flattenedMatches.count {
print("Starting rotation \(rotationIndex) with \(availableMatches.count) matches left")
teamsPerRotation[rotationIndex] = []
freeCourtPerRotation[rotationIndex] = []
let previousRotationBracketIndexes = slots.filter { $0.rotationIndex == rotationIndex - 1 }
.map { ($0.groupIndex, 1) }
let counts = Dictionary(previousRotationBracketIndexes, uniquingKeysWith: +)
var rotationMatches = Array(availableMatches.filter({ match in
// Check if all teams from the match are not already scheduled in the current rotation
let teamsAvailable = teamsPerRotation[rotationIndex]!.allSatisfy({ !match.containsTeamIndex($0) })
if !teamsAvailable {
print("Match \(match.roundAndMatchTitle()) has teams already scheduled in rotation \(rotationIndex)")
}
return teamsAvailable
}))
if rotationIndex > 0, simultaneousStart == false {
rotationMatches = rotationMatches.sorted(by: {
if counts[$0.groupStageObject!.index] ?? 0 == counts[$1.groupStageObject!.index] ?? 0 {
return $0.groupStageObject!.index < $1.groupStageObject!.index
} else {
return counts[$0.groupStageObject!.index] ?? 0 < counts[$1.groupStageObject!.index] ?? 0
}
})
}
courtsAvailable.forEach { courtIndex in
print("Checking availability for court \(courtIndex) in rotation \(rotationIndex)")
if let first = rotationMatches.first(where: { match in
let estimatedDuration = match.matchFormat.getEstimatedDuration(additionalEstimationDuration)
let timeIntervalToAdd = Double(rotationIndex) * Double(estimatedDuration) * 60
let rotationStartDate: Date = startingDate.addingTimeInterval(timeIntervalToAdd)
let courtsUnavailable = courtsUnavailable(startDate: rotationStartDate, duration: match.matchFormat.getEstimatedDuration(additionalEstimationDuration), courtsUnavailability: courtsUnavailability)
if courtsUnavailable.contains(courtIndex) {
print("Court \(courtIndex) is unavailable at \(rotationStartDate)")
return false
}
let teamsAvailable = teamsPerRotation[rotationIndex]!.allSatisfy({ !match.containsTeamIndex($0) })
if !teamsAvailable {
print("Teams from match \(match.roundAndMatchTitle()) are already scheduled in this rotation")
return false
}
print("Match \(match.roundAndMatchTitle()) is available for court \(courtIndex) at \(rotationStartDate)")
return true
}) {
let timeMatch = GroupStageTimeMatch(matchID: first.id, rotationIndex: rotationIndex, courtIndex: courtIndex, groupIndex: first.groupStageObject!.index)
print("Scheduled match: \(first.roundAndMatchTitle()) on court \(courtIndex) at rotation \(rotationIndex)")
slots.append(timeMatch)
teamsPerRotation[rotationIndex]!.append(contentsOf: first.matchUp())
rotationMatches.removeAll(where: { $0.id == first.id })
availableMatches.removeAll(where: { $0.id == first.id })
if let index = first.groupStageObject?.index {
groupLastRotation[index] = rotationIndex
}
} else {
print("No available matches for court \(courtIndex) in rotation \(rotationIndex), adding to free court list")
freeCourtPerRotation[rotationIndex]!.append(courtIndex)
}
}
rotationIndex += 1
}
print("All matches scheduled. Total rotations: \(rotationIndex)")
// Organize slots and ensure courts are randomized or sorted
var organizedSlots = [GroupStageTimeMatch]()
for i in 0..<rotationIndex {
let courtsSorted: [Int] = slots.filter({ $0.rotationIndex == i }).map { $0.courtIndex }.sorted()
let courts: [Int] = randomizeCourts ? courtsSorted.shuffled() : courtsSorted
var matches = slots.filter({ $0.rotationIndex == i }).sorted(using: .keyPath(\.groupIndex), .keyPath(\.courtIndex))
for j in 0..<matches.count {
matches[j].courtIndex = courts[j]
organizedSlots.append(matches[j])
}
}
return GroupStageMatchDispatcher(
timedMatches: organizedSlots,
freeCourtPerRotation: freeCourtPerRotation,
rotationCount: rotationIndex,
groupLastRotation: groupLastRotation
)
}
func rotationDifference(loserBracket: Bool) -> Int {
if loserBracket {
return loserBracketRotationDifference
} else {
return upperBracketRotationDifference
}
}
func roundMatchCanBePlayed(_ match: Match, roundObject: Round, slots: [TimeMatch], rotationIndex: Int, targetedStartDate: Date, minimumTargetedEndDate: inout Date) -> Bool {
print("Evaluating match: \(match.roundAndMatchTitle()) in round: \(roundObject.roundTitle()) with index: \(match.index)")
if let roundStartDate = roundObject.startDate, targetedStartDate < roundStartDate {
print("Cannot start at \(targetedStartDate), earlier than round start date \(roundStartDate)")
if targetedStartDate == minimumTargetedEndDate {
print("Updating minimumTargetedEndDate to roundStartDate: \(roundStartDate)")
minimumTargetedEndDate = roundStartDate
} else {
print("Setting minimumTargetedEndDate to the earlier of \(roundStartDate) and \(minimumTargetedEndDate)")
minimumTargetedEndDate = min(roundStartDate, minimumTargetedEndDate)
}
print("Returning false: Match cannot start earlier than the round start date.")
return false
}
let previousMatches = roundObject.precedentMatches(ofMatch: match)
if previousMatches.isEmpty {
print("No ancestors matches for this match, returning true. (eg beginning of tournament 1st bracket")
return true
}
let previousMatchSlots = slots.filter { previousMatches.map { $0.id }.contains($0.matchID) }
if previousMatchSlots.isEmpty {
if previousMatches.filter({ !$0.disabled }).allSatisfy({ $0.startDate != nil }) {
print("All previous matches have start dates, returning true.")
return true
}
print("Some previous matches are pending, returning false.")
return false
}
if previousMatches.filter({ !$0.disabled }).count > previousMatchSlots.count {
if previousMatches.filter({ !$0.disabled }).anySatisfy({ $0.startDate != nil }) {
print("Some previous matches started, returning true.")
return true
}
print("Not enough previous matches have started, returning false.")
return false
}
var includeBreakTime = false
if accountLoserBracketBreakTime && roundObject.isLoserBracket() {
includeBreakTime = true
print("Including break time for loser bracket.")
}
if accountUpperBracketBreakTime && !roundObject.isLoserBracket() {
includeBreakTime = true
print("Including break time for upper bracket.")
}
let previousMatchIsInPreviousRotation = previousMatchSlots.allSatisfy {
$0.rotationIndex + rotationDifference(loserBracket: roundObject.isLoserBracket()) < rotationIndex
}
if previousMatchIsInPreviousRotation {
print("All previous matches are from earlier rotations, returning true.")
} else {
print("Some previous matches are from the current rotation.")
}
guard let minimumPossibleEndDate = previousMatchSlots.map({
$0.estimatedEndDate(includeBreakTime: includeBreakTime)
}).max() else {
print("No valid previous match end date, returning \(previousMatchIsInPreviousRotation).")
return previousMatchIsInPreviousRotation
}
if targetedStartDate >= minimumPossibleEndDate {
if rotationDifferenceIsImportant {
print("Targeted start date is after the minimum possible end date and rotation difference is important, returning \(previousMatchIsInPreviousRotation).")
return previousMatchIsInPreviousRotation
} else {
print("Targeted start date is after the minimum possible end date, returning true.")
return true
}
} else {
if targetedStartDate == minimumTargetedEndDate {
print("Updating minimumTargetedEndDate to minimumPossibleEndDate: \(minimumPossibleEndDate)")
minimumTargetedEndDate = minimumPossibleEndDate
} else {
print("Setting minimumTargetedEndDate to the earlier of \(minimumPossibleEndDate) and \(minimumTargetedEndDate)")
minimumTargetedEndDate = min(minimumPossibleEndDate, minimumTargetedEndDate)
}
print("Targeted start date \(targetedStartDate) is before the minimum possible end date, returning false. \(minimumTargetedEndDate)")
return false
}
}
func getNextStartDate(fromPreviousRotationSlots slots: [TimeMatch], includeBreakTime: Bool) -> Date? {
slots.map { $0.estimatedEndDate(includeBreakTime: includeBreakTime) }.min()
}
func getNextEarliestAvailableDate(from slots: [TimeMatch]) -> [(Int, Date)] {
let byCourt = Dictionary(grouping: slots, by: { $0.courtIndex })
return (byCourt.keys.flatMap { courtIndex in
let matchesByCourt = byCourt[courtIndex]?.sorted(by: \.startDate)
let lastMatch = matchesByCourt?.last
var results = [(Int, Date)]()
if let courtFreeDate = lastMatch?.estimatedEndDate(includeBreakTime: false) {
results.append((courtIndex, courtFreeDate))
}
return results
}
)
}
func getAvailableCourts(from matches: [Match]) -> [(Int, Date)] {
let validMatches = matches.filter({ $0.courtIndex != nil && $0.startDate != nil })
let byCourt = Dictionary(grouping: validMatches, by: { $0.courtIndex! })
return (byCourt.keys.flatMap { court in
let matchesByCourt = byCourt[court]?.sorted(by: \.startDate!)
let lastMatch = matchesByCourt?.last
var results = [(Int, Date)]()
if let courtFreeDate = lastMatch?.estimatedEndDate(additionalEstimationDuration) {
results.append((court, courtFreeDate))
}
return results
}
)
}
func roundDispatcher(flattenedMatches: [Match], dispatcherStartDate: Date, initialCourts: [Int]?) -> MatchDispatcher {
var slots = [TimeMatch]()
var _startDate: Date?
var rotationIndex = 0
var availableMatchs = flattenedMatches.filter({ $0.startDate == nil })
let courtsUnavailability = courtsUnavailability
var issueFound: Bool = false
// Log start of the function
print("Starting roundDispatcher with \(availableMatchs.count) matches and \(courtsAvailable) courts available")
flattenedMatches.filter { $0.startDate != nil }.sorted(by: \.startDate!).forEach { match in
if _startDate == nil {
_startDate = match.startDate
} else if match.startDate! > _startDate! {
_startDate = match.startDate
rotationIndex += 1
}
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)
}
if !slots.isEmpty {
rotationIndex += 1
}
var freeCourtPerRotation = [Int: [Int]]()
var courts = initialCourts ?? Array(courtsAvailable)
var shouldStartAtDispatcherDate = rotationIndex > 0
var suitableDate: Date?
while !availableMatchs.isEmpty && !issueFound && rotationIndex < 50 {
freeCourtPerRotation[rotationIndex] = []
let previousRotationSlots = slots.filter({ $0.rotationIndex == rotationIndex - 1 })
var rotationStartDate: Date
if previousRotationSlots.isEmpty && rotationIndex > 0 {
let computedSuitableDate = slots.sorted(by: \.computedEndDateForSorting).last?.computedEndDateForSorting
print("Previous rotation was empty, find a suitable rotationStartDate \(suitableDate)")
rotationStartDate = suitableDate ?? computedSuitableDate ?? dispatcherStartDate
} else {
rotationStartDate = getNextStartDate(fromPreviousRotationSlots: previousRotationSlots, includeBreakTime: false) ?? dispatcherStartDate
}
if shouldStartAtDispatcherDate {
rotationStartDate = dispatcherStartDate
shouldStartAtDispatcherDate = false
} else {
courts = rotationIndex == 0 ? courts : Array(courtsAvailable)
}
courts.sort()
// Log courts availability and start date
print("Courts available at rotation \(rotationIndex): \(courts)")
print("Rotation start date: \(rotationStartDate)")
// Check for court availability and break time conflicts
if rotationIndex > 0, let freeCourtPreviousRotation = freeCourtPerRotation[rotationIndex - 1], !freeCourtPreviousRotation.isEmpty {
print("Handling break time conflicts or waiting for free courts")
let previousPreviousRotationSlots = slots.filter { $0.rotationIndex == rotationIndex - 2 && freeCourtPreviousRotation.contains($0.courtIndex) }
var previousEndDate = getNextStartDate(fromPreviousRotationSlots: previousPreviousRotationSlots, includeBreakTime: accountUpperBracketBreakTime)
var previousEndDateNoBreak = getNextStartDate(fromPreviousRotationSlots: previousPreviousRotationSlots, includeBreakTime: false)
if let courtsUnavailability, previousEndDate != nil {
previousEndDate = getFirstFreeCourt(startDate: previousEndDate!, duration: 0, courts: courts, courtsUnavailability: courtsUnavailability).earliestFreeDate
}
if let courtsUnavailability, previousEndDateNoBreak != nil {
previousEndDateNoBreak = getFirstFreeCourt(startDate: previousEndDateNoBreak!, duration: 0, courts: courts, courtsUnavailability: courtsUnavailability).earliestFreeDate
}
let noBreakAlreadyTested = previousRotationSlots.anySatisfy { $0.startDate == previousEndDateNoBreak }
if let previousEndDate, let previousEndDateNoBreak {
let differenceWithBreak = rotationStartDate.timeIntervalSince(previousEndDate)
let differenceWithoutBreak = rotationStartDate.timeIntervalSince(previousEndDateNoBreak)
print("Difference with break: \(differenceWithBreak), without break: \(differenceWithoutBreak)")
let timeDifferenceLimitInSeconds = Double(timeDifferenceLimit * 60)
var difference = differenceWithBreak
if differenceWithBreak <= 0, accountUpperBracketBreakTime == false {
difference = differenceWithoutBreak
} else if differenceWithBreak > timeDifferenceLimitInSeconds && differenceWithoutBreak > timeDifferenceLimitInSeconds {
difference = noBreakAlreadyTested ? differenceWithBreak : max(differenceWithBreak, differenceWithoutBreak)
}
print("Final difference to evaluate: \(difference)")
if (difference > timeDifferenceLimitInSeconds && rotationStartDate.addingTimeInterval(-difference) != previousEndDate) || difference < 0 {
print("""
Adjusting rotation start:
- Initial rotationStartDate: \(rotationStartDate)
- Adjusted by difference: \(difference)
- Adjusted rotationStartDate: \(rotationStartDate.addingTimeInterval(-difference))
- PreviousEndDate: \(previousEndDate)
""")
courts.removeAll(where: { freeCourtPreviousRotation.contains($0) })
freeCourtPerRotation[rotationIndex] = courts
courts = freeCourtPreviousRotation
rotationStartDate = rotationStartDate.addingTimeInterval(-difference)
}
}
} else if let firstMatch = availableMatchs.first {
let duration = firstMatch.matchFormat.getEstimatedDuration(additionalEstimationDuration)
let courtsUnavailable = courtsUnavailable(startDate: rotationStartDate, duration: duration, courtsUnavailability: courtsUnavailability)
if Array(Set(courtsAvailable).subtracting(Set(courtsUnavailable))).isEmpty {
print("Issue: All courts unavailable in this rotation")
if let courtsUnavailability {
let computedStartDateAndCourts = getFirstFreeCourt(startDate: rotationStartDate, duration: duration, courts: courts, courtsUnavailability: courtsUnavailability)
rotationStartDate = computedStartDateAndCourts.earliestFreeDate
courts = computedStartDateAndCourts.availableCourts
} else {
issueFound = true
}
} else {
courts = Array(Set(courtsAvailable).subtracting(Set(courtsUnavailable)))
}
}
// Dispatch courts and schedule matches
suitableDate = dispatchCourts(courts: courts, availableMatchs: &availableMatchs, slots: &slots, rotationIndex: rotationIndex, rotationStartDate: rotationStartDate, freeCourtPerRotation: &freeCourtPerRotation, courtsUnavailability: courtsUnavailability)
rotationIndex += 1
}
// Organize matches in slots
var organizedSlots = [TimeMatch]()
for i in 0..<rotationIndex {
let courtsSorted = slots.filter { $0.rotationIndex == i }.map { $0.courtIndex }.sorted()
let courts = randomizeCourts ? courtsSorted.shuffled() : courtsSorted
var matches = slots.filter { $0.rotationIndex == i }.sorted(using: .keyPath(\.courtIndex))
for j in 0..<matches.count {
matches[j].courtIndex = courts[j]
organizedSlots.append(matches[j])
}
}
print("Finished roundDispatcher with \(organizedSlots.count) scheduled matches")
return MatchDispatcher(timedMatches: organizedSlots, freeCourtPerRotation: freeCourtPerRotation, rotationCount: rotationIndex, issueFound: issueFound)
}
func dispatchCourts(courts: [Int], availableMatchs: inout [Match], slots: inout [TimeMatch], rotationIndex: Int, rotationStartDate: Date, freeCourtPerRotation: inout [Int: [Int]], courtsUnavailability: [DateInterval]?) -> Date {
var matchPerRound = [String: Int]()
var minimumTargetedEndDate = rotationStartDate
// Log dispatch attempt
print("Dispatching courts for rotation \(rotationIndex) with start date \(rotationStartDate) and available courts \(courts.sorted())")
for (courtPosition, courtIndex) in courts.sorted().enumerated() {
if let firstMatch = availableMatchs.first(where: { match in
print("Trying to find a match for court \(courtIndex) in rotation \(rotationIndex)")
let roundObject = match.roundObject!
let duration = match.matchFormat.getEstimatedDuration(additionalEstimationDuration)
let courtsUnavailable = courtsUnavailable(startDate: rotationStartDate, duration: duration, courtsUnavailability: courtsUnavailability)
if courtsUnavailable.contains(courtIndex) {
print("Returning false: Court \(courtIndex) unavailable due to schedule conflicts during \(rotationStartDate).")
return false
}
let canBePlayed = roundMatchCanBePlayed(match, roundObject: roundObject, slots: slots, rotationIndex: rotationIndex, targetedStartDate: rotationStartDate, minimumTargetedEndDate: &minimumTargetedEndDate)
if !canBePlayed {
print("Returning false: Match \(match.roundAndMatchTitle()) can't be played due to constraints.")
return false
}
let currentRotationSameRoundMatches = matchPerRound[roundObject.id] ?? 0
let roundMatchesCount = roundObject.playedMatches().count
if shouldHandleUpperRoundSlice {
if roundObject.parent == nil, roundObject.index > 1, currentRotationSameRoundMatches == 0, courts.count - matchPerRound.count < 2 {
return false
}
else if roundObject.parent == nil, roundObject.index == 0, matchPerRound.isEmpty == false {
return false
}
else if roundObject.parent == nil, roundObject.index == 1, currentRotationSameRoundMatches >= 0, courtsAvailable.count > 0 {
return true
}
else if roundObject.parent == nil && roundMatchesCount > courts.count && currentRotationSameRoundMatches >= min(roundMatchesCount / 2, courts.count) {
print("Returning false: Too many matches already played in the current rotation for round \(roundObject.roundTitle()).")
return false
}
}
let indexInRound = match.indexInRound()
if shouldTryToFillUpCourtsAvailable == false {
if roundObject.parent == nil && roundObject.index > 1 && indexInRound == 0, let nextMatch = match.next() {
var nextMinimumTargetedEndDate = minimumTargetedEndDate
if courtPosition < courts.count - 1 && canBePlayed && roundMatchCanBePlayed(nextMatch, roundObject: roundObject, slots: slots, rotationIndex: rotationIndex, targetedStartDate: rotationStartDate, minimumTargetedEndDate: &nextMinimumTargetedEndDate) {
print("Returning true: Both current \(match.index) and next match \(nextMatch.index) can be played in rotation \(rotationIndex).")
return true
} else {
print("Returning false: Either current match or next match cannot be played in rotation \(rotationIndex).")
return false
}
}
}
print("Returning true: Match \(match.roundAndMatchTitle()) can be played on court \(courtIndex).")
return canBePlayed
}) {
print("Found match: \(firstMatch.roundAndMatchTitle()) for court \(courtIndex) at \(rotationStartDate)")
matchPerRound[firstMatch.roundObject!.id, default: 0] += 1
let timeMatch = TimeMatch(
matchID: firstMatch.id,
rotationIndex: rotationIndex,
courtIndex: courtIndex,
startDate: rotationStartDate,
durationLeft: firstMatch.matchFormat.getEstimatedDuration(additionalEstimationDuration),
minimumBreakTime: firstMatch.matchFormat.breakTime.breakTime
)
slots.append(timeMatch)
availableMatchs.removeAll(where: { $0.id == firstMatch.id })
} else {
print("No suitable match found for court \(courtIndex) in rotation \(rotationIndex). Adding court to freeCourtPerRotation.")
freeCourtPerRotation[rotationIndex]?.append(courtIndex)
}
}
if freeCourtPerRotation[rotationIndex]?.count == courtsAvailable.count {
print("All courts in rotation \(rotationIndex) are free, minimumTargetedEndDate : \(minimumTargetedEndDate)")
}
if let courtsUnavailability {
let computedStartDateAndCourts = getFirstFreeCourt(startDate: minimumTargetedEndDate, duration: 0, courts: courts, courtsUnavailability: courtsUnavailability)
return computedStartDateAndCourts.earliestFreeDate
}
return minimumTargetedEndDate
}
@discardableResult func updateBracketSchedule(tournament: Tournament, fromRoundId roundId: String?, fromMatchId matchId: String?, startDate: Date) -> Bool {
let upperRounds: [Round] = tournament.rounds()
let allMatches: [Match] = tournament.allMatches().filter({ $0.hasEnded() == false && $0.hasStarted() == false })
var rounds = [Round]()
if let groupStageLoserBracketRound = tournament.groupStageLoserBracket() {
rounds.append(groupStageLoserBracketRound)
}
if shouldEndRoundBeforeStartingNext {
rounds.append(contentsOf: upperRounds.flatMap {
[$0] + $0.loserRoundsAndChildren()
})
} else {
rounds.append(contentsOf: upperRounds.map {
$0
} + upperRounds.flatMap {
$0.loserRoundsAndChildren()
})
}
let flattenedMatches = rounds.flatMap { round in
round._matches().filter({ $0.disabled == false && $0.hasEnded() == false && $0.hasStarted() == false }).sorted(by: \.index)
}
flattenedMatches.forEach({
if (roundId == nil && matchId == nil) || $0.startDate?.isEarlierThan(startDate) == false {
$0.startDate = nil
$0.removeCourt()
$0.confirmed = false
}
})
// if let roundId {
// if let round : Round = Store.main.findById(roundId) {
// let matches = round._matches().filter({ $0.disabled == false }).sorted(by: \.index)
// round.resetFromRoundAllMatchesStartDate()
// flattenedMatches = matches + flattenedMatches
// }
//
// } else if let matchId {
// if let match : Match = Store.main.findById(matchId) {
// if let round = match.roundObject {
// round.resetFromRoundAllMatchesStartDate(from: match)
// }
// flattenedMatches = [match] + flattenedMatches
// }
// }
if let roundId, let matchId {
//todo
if let index = flattenedMatches.firstIndex(where: { $0.round == roundId && $0.id == matchId }) {
flattenedMatches[index...].forEach {
$0.startDate = nil
$0.removeCourt()
$0.confirmed = false
}
}
} else if let roundId {
//todo
if let index = flattenedMatches.firstIndex(where: { $0.round == roundId }) {
flattenedMatches[index...].forEach {
$0.startDate = nil
$0.removeCourt()
$0.confirmed = false
}
}
}
let matches: [Match] = allMatches.filter { $0.startDate?.isEarlierThan(startDate) == true && $0.startDate?.dayInt == startDate.dayInt }
let usedCourts = getAvailableCourts(from: matches)
let initialCourts: [Int] = usedCourts.filter { (court, availableDate) in
availableDate <= startDate
}.sorted(by: \.1).compactMap { $0.0 }
let courts : [Int]? = initialCourts.isEmpty ? nil : initialCourts
print("initial available courts at beginning: \(courts ?? [])")
let roundDispatch = self.roundDispatcher(flattenedMatches: flattenedMatches, dispatcherStartDate: startDate, initialCourts: courts)
roundDispatch.timedMatches.forEach { matchSchedule in
if let match = flattenedMatches.first(where: { $0.id == matchSchedule.matchID }) {
match.startDate = matchSchedule.startDate
match.setCourt(matchSchedule.courtIndex)
}
}
do {
try self.tournamentStore?.matches.addOrUpdate(contentOfs: allMatches)
} catch {
Logger.error(error)
}
return roundDispatch.issueFound
}
func courtsUnavailable(startDate: Date, duration: Int, courtsUnavailability: [DateInterval]?) -> [Int] {
let endDate = startDate.addingTimeInterval(Double(duration) * 60)
guard let courtsUnavailability else { return [] }
let groupedBy = Dictionary(grouping: courtsUnavailability, by: { $0.courtIndex })
let courts = groupedBy.keys
return courts.filter {
courtUnavailable(courtIndex: $0, from: startDate, to: endDate, source: courtsUnavailability)
}
}
func courtUnavailable(courtIndex: Int, from startDate: Date, to endDate: Date, source: [DateInterval]) -> Bool {
let courtLockedSchedule = source.filter({ $0.courtIndex == courtIndex })
return courtLockedSchedule.anySatisfy({ dateInterval in
let range = startDate..<endDate
return dateInterval.range.overlaps(range)
})
}
func getFirstFreeCourt(startDate: Date, duration: Int, courts: [Int], courtsUnavailability: [DateInterval]) -> (earliestFreeDate: Date, availableCourts: [Int]) {
var earliestEndDate: Date?
var availableCourtsAtEarliest: [Int] = []
// Iterate through each court and find the earliest time it becomes free
for courtIndex in courts {
let unavailabilityForCourt = courtsUnavailability.filter { $0.courtIndex == courtIndex }
var isAvailable = true
for interval in unavailabilityForCourt {
if interval.startDate <= startDate && interval.endDate > startDate {
isAvailable = false
if let currentEarliest = earliestEndDate {
earliestEndDate = min(currentEarliest, interval.endDate)
} else {
earliestEndDate = interval.endDate
}
}
}
// If the court is available at the start date, add it to the list of available courts
if isAvailable {
availableCourtsAtEarliest.append(courtIndex)
}
}
// If there are no unavailable courts, return the original start date and all courts
if let earliestEndDate = earliestEndDate {
// Find which courts will be available at the earliest free date
let courtsAvailableAtEarliest = courts.filter { courtIndex in
let unavailabilityForCourt = courtsUnavailability.filter { $0.courtIndex == courtIndex }
return unavailabilityForCourt.allSatisfy { $0.endDate <= earliestEndDate }
}
return (earliestFreeDate: earliestEndDate, availableCourts: courtsAvailableAtEarliest)
} else {
// If no courts were unavailable, all courts are available at the start date
return (earliestFreeDate: startDate.addingTimeInterval(Double(duration) * 60), availableCourts: courts)
}
}
func updateSchedule(tournament: Tournament) -> Bool {
if tournament.courtCount < courtsAvailable.count {
courtsAvailable = Set(tournament.courtsAvailable())
}
var lastDate = tournament.startDate
if tournament.groupStageCount > 0 {
lastDate = updateGroupStageSchedule(tournament: tournament)
}
if tournament.groupStages(atStep: 1).isEmpty == false {
lastDate = updateGroupStageSchedule(tournament: tournament, atStep: 1, startDate: lastDate)
}
return updateBracketSchedule(tournament: tournament, fromRoundId: nil, fromMatchId: nil, startDate: lastDate)
}
}
struct GroupStageTimeMatch {
let matchID: String
let rotationIndex: Int
var courtIndex: Int
let groupIndex: Int
}
struct TimeMatch {
let matchID: String
let rotationIndex: Int
var courtIndex: Int
var startDate: Date
var durationLeft: Int //in minutes
var minimumBreakTime: Int //in minutes
func estimatedEndDate(includeBreakTime: Bool) -> Date {
let minutesToAdd = Double(durationLeft + (includeBreakTime ? minimumBreakTime : 0))
return startDate.addingTimeInterval(minutesToAdd * 60.0)
}
var computedEndDateForSorting: Date {
estimatedEndDate(includeBreakTime: false)
}
}
struct GroupStageMatchDispatcher {
let timedMatches: [GroupStageTimeMatch]
let freeCourtPerRotation: [Int: [Int]]
let rotationCount: Int
let groupLastRotation: [Int: Int]
}
struct MatchDispatcher {
let timedMatches: [TimeMatch]
let freeCourtPerRotation: [Int: [Int]]
let rotationCount: Int
let issueFound: Bool
}
extension Match {
func teamIds() -> [String] {
return teams().map { $0.id }
}
func containsTeamId(_ id: String) -> Bool {
return teamIds().contains(id)
}
func containsTeamIndex(_ id: String) -> Bool {
matchUp().contains(id)
}
func matchUp() -> [String] {
guard let groupStageObject else {
return []
}
return groupStageObject._matchUp(for: index).map { groupStageObject.id + "_\($0)" }
}
}

@ -1,66 +0,0 @@
//
// MockData.swift
// PadelClub
//
// Created by Razmig Sarkissian on 20/03/2024.
//
import Foundation
extension Court {
static func mock() -> Court {
Court(index: 0, club: "", name: "Test")
}
}
extension Event {
static func mock() -> Event {
Event()
}
}
extension Club {
static func mock() -> Club {
Club(name: "AUC", acronym: "AUC")
}
static func newEmptyInstance() -> Club {
Club(name: "", acronym: "")
}
}
extension GroupStage {
static func mock() -> GroupStage {
GroupStage(tournament: "", index: 0, size: 4)
}
}
extension Round {
static func mock() -> Round {
Round(tournament: "", index: 0)
}
}
extension Tournament {
static func mock() -> Tournament {
return Tournament(groupStageSortMode: .snake, teamSorting: .inscriptionDate, federalCategory: .men, federalLevelCategory: .p100, federalAgeCategory: .senior)
}
}
extension Match {
static func mock() -> Match {
return Match(index: 0)
}
}
extension TeamRegistration {
static func mock() -> TeamRegistration {
return TeamRegistration(tournament: "")
}
}
extension PlayerRegistration {
static func mock() -> PlayerRegistration {
return PlayerRegistration(firstName: "Raz", lastName: "Shark", sex: .male)
}
}

@ -1,53 +0,0 @@
//
// PlayerPaymentType.swift
// PadelClub
//
// Created by Laurent Morvillier on 11/02/2025.
//
import Foundation
enum PlayerPaymentType: Int, CaseIterable, Identifiable, Codable {
init?(rawValue: Int?) {
guard let value = rawValue else { return nil }
self.init(rawValue: value)
}
var id: Self {
self
}
case cash = 0
case lydia = 1
case gift = 2
case check = 3
case paylib = 4
case bankTransfer = 5
case clubHouse = 6
case creditCard = 7
case forfeit = 8
func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
switch self {
case .check:
return "Chèque"
case .cash:
return "Cash"
case .lydia:
return "Lydia"
case .paylib:
return "Paylib"
case .bankTransfer:
return "Virement"
case .clubHouse:
return "Clubhouse"
case .creditCard:
return "CB"
case .forfeit:
return "Forfait"
case .gift:
return "Offert"
}
}
}

@ -1,510 +0,0 @@
//
// PlayerRegistration.swift
// Padel Tournament
//
// Created by razmig on 10/03/2024.
//
import Foundation
import LeStorage
@Observable
final class PlayerRegistration: BasePlayerRegistration, SideStorable {
func localizedSourceLabel() -> String {
switch source {
case .frenchFederation:
return "base fédérale"
case .beachPadel:
return "beach-padel"
case nil:
if registeredOnline {
return "à vérifier vous-même"
} else {
return "créé par vous-même"
}
}
}
init(teamRegistration: String? = nil, firstName: String, lastName: String, licenceId: String? = nil, rank: Int? = nil, paymentType: PlayerPaymentType? = nil, sex: PlayerSexType? = nil, tournamentPlayed: Int? = nil, points: Double? = nil, clubName: String? = nil, ligueName: String? = nil, assimilation: String? = nil, phoneNumber: String? = nil, email: String? = nil, birthdate: String? = nil, computedRank: Int = 0, source: PlayerRegistration.PlayerDataSource? = nil, hasArrived: Bool = false, coach: Bool = false, captain: Bool = false, registeredOnline: Bool = false, timeToConfirm: Date? = nil, registrationStatus: PlayerRegistration.RegistrationStatus = PlayerRegistration.RegistrationStatus.waiting, paymentId: String? = nil) {
super.init()
self.teamRegistration = teamRegistration
self.firstName = firstName
self.lastName = lastName
self.licenceId = licenceId
self.rank = rank
self.paymentType = paymentType
self.sex = sex
self.tournamentPlayed = tournamentPlayed
self.points = points
self.clubName = clubName
self.ligueName = ligueName
self.assimilation = assimilation
self.phoneNumber = phoneNumber
self.email = email
self.birthdate = birthdate
self.computedRank = computedRank
self.source = source
self.hasArrived = hasArrived
self.coach = coach
self.captain = captain
self.registeredOnline = registeredOnline
self.timeToConfirm = timeToConfirm
self.registrationStatus = registrationStatus
self.paymentId = paymentId
}
internal init(importedPlayer: ImportedPlayer) {
super.init()
self.teamRegistration = ""
self.firstName = (importedPlayer.firstName ?? "").prefixTrimmed(50).capitalized
self.lastName = (importedPlayer.lastName ?? "").prefixTrimmed(50).uppercased()
self.licenceId = importedPlayer.license?.prefixTrimmed(50) ?? nil
self.rank = Int(importedPlayer.rank)
self.sex = importedPlayer.male ? .male : .female
self.tournamentPlayed = importedPlayer.tournamentPlayed
self.points = importedPlayer.getPoints()
self.clubName = importedPlayer.clubName?.prefixTrimmed(200)
self.ligueName = importedPlayer.ligueName?.prefixTrimmed(200)
self.assimilation = importedPlayer.assimilation?.prefixTrimmed(50)
self.source = .frenchFederation
self.birthdate = importedPlayer.birthYear?.prefixTrimmed(50)
}
internal init?(federalData: [String], sex: Int, sexUnknown: Bool) {
super.init()
let _lastName = federalData[0].trimmed.uppercased()
let _firstName = federalData[1].trimmed.capitalized
if _lastName.isEmpty && _firstName.isEmpty { return nil }
lastName = _lastName.prefixTrimmed(50)
firstName = _firstName.prefixTrimmed(50)
birthdate = federalData[2].formattedAsBirthdate().prefixTrimmed(50)
licenceId = federalData[3].prefixTrimmed(50)
clubName = federalData[4].prefixTrimmed(200)
let stringRank = federalData[5]
if stringRank.isEmpty {
rank = nil
} else {
rank = Int(stringRank)
}
let _email = federalData[6]
if _email.isEmpty == false {
self.email = _email.prefixTrimmed(50)
}
let _phoneNumber = federalData[7]
if _phoneNumber.isEmpty == false {
self.phoneNumber = _phoneNumber.prefixTrimmed(50)
}
source = .beachPadel
if sexUnknown {
if sex == 1 && FileImportManager.shared.foundInWomenData(license: federalData[3]) {
self.sex = .female
} else if FileImportManager.shared.foundInMenData(license: federalData[3]) {
self.sex = .male
} else {
self.sex = nil
}
} else {
self.sex = PlayerSexType(rawValue: sex)
}
}
required init(from decoder: any Decoder) throws {
try super.init(from: decoder)
}
required public init() {
super.init()
}
var tournamentStore: TournamentStore? {
guard let storeId else {
fatalError("missing store id for \(String(describing: type(of: self)))")
}
return TournamentLibrary.shared.store(tournamentId: storeId)
// if let store = self.store as? TournamentStore {
// return store
// }
}
var computedAge: Int? {
if let birthdate {
let components = birthdate.components(separatedBy: "/")
if let age = components.last, let ageInt = Int(age) {
let year = Calendar.current.getSportAge()
if age.count == 2 { //si l'année est sur 2 chiffres dans le fichier
if ageInt < 23 {
return year - 2000 - ageInt
} else {
return year - 2000 + 100 - ageInt
}
} else { //si l'année est représenté sur 4 chiffres
return year - ageInt
}
}
}
return nil
}
func pasteData(_ exportFormat: ExportFormat = .rawText) -> String {
switch exportFormat {
case .rawText:
return [firstName.capitalized, lastName.capitalized, licenceId?.computedLicense].compactMap({ $0 }).joined(separator: exportFormat.separator())
case .csv:
return [lastName.uppercased() + " " + firstName.capitalized].joined(separator: exportFormat.separator())
}
}
func isPlaying() -> Bool {
team()?.isPlaying() == true
}
func contains(_ searchField: String) -> Bool {
let nameComponents = searchField.canonicalVersion.split(separator: " ")
if nameComponents.count > 1 {
let pairs = nameComponents.pairs()
return pairs.contains(where: {
(firstName.canonicalVersion.localizedCaseInsensitiveContains(String($0)) &&
lastName.canonicalVersion.localizedCaseInsensitiveContains(String($1))) ||
(firstName.canonicalVersion.localizedCaseInsensitiveContains(String($1)) &&
lastName.canonicalVersion.localizedCaseInsensitiveContains(String($0)))
})
} else {
return nameComponents.contains { component in
firstName.canonicalVersion.localizedCaseInsensitiveContains(component) ||
lastName.canonicalVersion.localizedCaseInsensitiveContains(component)
}
}
}
func isSameAs(_ player: PlayerRegistration) -> Bool {
firstName.trimmedMultiline.canonicalVersion.localizedCaseInsensitiveCompare(player.firstName.trimmedMultiline.canonicalVersion) == .orderedSame &&
lastName.trimmedMultiline.canonicalVersion.localizedCaseInsensitiveCompare(player.lastName.trimmedMultiline.canonicalVersion) == .orderedSame
}
func tournament() -> Tournament? {
guard let tournament = team()?.tournament else { return nil }
return Store.main.findById(tournament)
}
func team() -> TeamRegistration? {
guard let teamRegistration else { return nil }
return self.tournamentStore?.teamRegistrations.findById(teamRegistration)
}
func isHere() -> Bool {
hasArrived
}
func hasPaid() -> Bool {
paymentType != nil
}
func playerLabel(_ displayStyle: DisplayStyle = .wide) -> String {
switch displayStyle {
case .wide, .title:
return lastName.trimmed.capitalized + " " + firstName.trimmed.capitalized
case .short:
let names = lastName.components(separatedBy: .whitespaces)
if lastName.components(separatedBy: .whitespaces).count > 1 {
if let firstLongWord = names.first(where: { $0.count > 3 }) {
return firstLongWord.trimmed.capitalized.trunc(length: 10) + " " + firstName.trimmed.prefix(1).capitalized + "."
}
}
return lastName.trimmed.capitalized.trunc(length: 10) + " " + firstName.trimmed.prefix(1).capitalized + "."
}
}
func isImported() -> Bool {
source == .beachPadel
}
func unrankedOrUnknown() -> Bool {
source == nil
}
func isValidLicenseNumber(year: Int) -> Bool {
guard let licenceId else { return false }
guard licenceId.isLicenseNumber else { return false }
guard licenceId.suffix(6) == "(\(year))" else { return false }
return true
}
@objc
var canonicalName: String {
playerLabel().folding(options: .diacriticInsensitive, locale: .current).lowercased()
}
func rankLabel(_ displayStyle: DisplayStyle = .wide) -> String {
if let rank, rank > 0 {
if rank != computedRank {
return computedRank.formatted() + " (" + rank.formatted() + ")"
} else {
return rank.formatted()
}
} else {
return "non classé" + (isMalePlayer() ? "" : "e")
}
}
func updateRank(from sources: [CSVParser], lastRank: Int?) async throws {
#if DEBUG_TIME
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func updateRank()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif
if let dataFound = try await history(from: sources) {
await MainActor.run {
rank = dataFound.rankValue?.toInt()
points = dataFound.points
tournamentPlayed = dataFound.tournamentCountValue?.toInt()
}
} else if let dataFound = try await historyFromName(from: sources) {
await MainActor.run {
rank = dataFound.rankValue?.toInt()
points = dataFound.points
tournamentPlayed = dataFound.tournamentCountValue?.toInt()
}
} else {
await MainActor.run {
rank = lastRank
}
}
}
func history(from sources: [CSVParser]) async throws -> Line? {
#if DEBUG_TIME
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func history()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif
guard let license = licenceId?.strippedLicense else {
return nil // Do NOT call historyFromName here, let updateRank handle it
}
let filteredSources = sources.filter { $0.maleData == isMalePlayer() }
return await withTaskGroup(of: Line?.self) { group in
for source in filteredSources {
group.addTask {
guard !Task.isCancelled else { return nil }
return try? await source.first { $0.rawValue.contains(";\(license);") }
}
}
for await result in group {
if let result {
group.cancelAll() // Stop other tasks as soon as we find a match
return result
}
}
return nil
}
}
func historyFromName(from sources: [CSVParser]) async throws -> Line? {
#if DEBUG
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func historyFromName()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif
let filteredSources = sources.filter { $0.maleData == isMalePlayer() }
let normalizedLastName = lastName.canonicalVersionWithPunctuation
let normalizedFirstName = firstName.canonicalVersionWithPunctuation
return await withTaskGroup(of: Line?.self) { group in
for source in filteredSources {
group.addTask {
guard !Task.isCancelled else { print("Cancelled"); return nil }
return try? await source.first {
let lineValue = $0.rawValue.canonicalVersionWithPunctuation
return lineValue.contains(";\(normalizedLastName);\(normalizedFirstName);")
}
}
}
for await result in group {
if let result {
group.cancelAll() // Stop other tasks as soon as we find a match
return result
}
}
return nil
}
}
func setComputedRank(in tournament: Tournament) {
let currentRank = rank ?? tournament.unrankValue(for: isMalePlayer()) ?? 90_000
switch tournament.tournamentCategory {
case .men:
computedRank = isMalePlayer() ? currentRank : currentRank + PlayerRegistration.addon(for: currentRank, manMax: tournament.maleUnrankedValue ?? 0, womanMax: tournament.femaleUnrankedValue ?? 0)
default:
computedRank = currentRank
}
}
func isMalePlayer() -> Bool {
sex == .male
}
func validateLicenceId(_ year: Int) {
if let currentLicenceId = licenceId {
if currentLicenceId.trimmed.hasSuffix("(\(year-1))") {
self.licenceId = currentLicenceId.replacingOccurrences(of: "\(year-1)", with: "\(year)")
} else if let computedLicense = currentLicenceId.strippedLicense?.computedLicense {
self.licenceId = computedLicense + " (\(year))"
}
}
}
func hasHomonym() -> Bool {
let federalContext = PersistenceController.shared.localContainer.viewContext
let fetchRequest = ImportedPlayer.fetchRequest()
let predicate = NSPredicate(format: "firstName == %@ && lastName == %@", firstName, lastName)
fetchRequest.predicate = predicate
do {
let count = try federalContext.count(for: fetchRequest)
return count > 1
} catch {
}
return false
}
func hasPaidOnline() -> Bool {
registrationStatus == .confirmed && paymentId != nil && paymentType == .creditCard
}
func hasConfirmed() -> Bool {
registrationStatus == .confirmed
}
func confirmRegistration() {
registrationStatus = .confirmed
}
enum PlayerDataSource: Int, Codable {
case frenchFederation = 0
case beachPadel = 1
}
enum RegistrationStatus: Int, Codable, CaseIterable, Identifiable {
case waiting = 0
case pending = 1
case confirmed = 2
case canceled = 3
var id: Int { self.rawValue }
func localizedRegistrationStatus() -> String {
switch self {
case .waiting:
return "En attente"
case .pending:
return "En cours"
case .confirmed:
return "Confirmé"
case .canceled:
return "Annulé"
}
}
}
static func addon(for playerRank: Int, manMax: Int, womanMax: Int) -> Int {
switch playerRank {
case 0: return 0
case womanMax: return manMax - womanMax
case manMax: return 0
default:
return TournamentCategory.femaleInMaleAssimilationAddition(playerRank)
}
}
func insertOnServer() {
self.tournamentStore?.playerRegistrations.writeChangeAndInsertOnServer(instance: self)
}
}
extension PlayerRegistration: PlayerHolder {
func getAssimilatedAsMaleRank() -> Int? {
nil
}
func getFirstName() -> String {
firstName
}
func getLastName() -> String {
lastName
}
func getPoints() -> Double? {
self.points
}
func getRank() -> Int? {
rank
}
func isUnranked() -> Bool {
rank == nil
}
func formattedRank() -> String {
self.rankLabel()
}
func formattedLicense() -> String {
if let licenceId { return licenceId.computedLicense }
return "aucune licence"
}
var male: Bool {
isMalePlayer()
}
func getBirthYear() -> Int? {
nil
}
func getProgression() -> Int {
0
}
func getComputedRank() -> Int? {
computedRank
}
}
enum PlayerDataSource: Int, Codable {
case frenchFederation = 0
case beachPadel = 1
}
enum PlayerSexType: Int, Hashable, CaseIterable, Identifiable, Codable {
init?(rawValue: Int?) {
guard let value = rawValue else { return nil }
self.init(rawValue: value)
}
var id: Self {
self
}
case female = 0
case male = 1
}

@ -1,35 +0,0 @@
# Procédure de création de classe
Dans Swift:
- Dans Data > Gen > créer un nouveau fichier json pour la classe
- Le paramètre "foreignKey" permet de générer une méthode qui récupère l'objet parent. Ajouter une étoile permet d'indiquer que l'on cherche l'objet dans le Store de l'objet et non le Store.main.
- Pour générer les fichiers, on se place dans le répertoire de generator.py et on lance la commande : python generator.py -i . -o .
- il faut avoir inflect: pip install inflect
Dans Django:
- Les modèles de base doivent étendre BaseModel
- Les modèles stockés dans des répertoires doivent étendre SideStoreModel
- Les classes d'admin doivent étendre SyncedObjectAdmin
- Les ForeignKey doivent toujours avoir on_delete=models.SET_NULL
- Pour se faciliter la vie dans l'admin, on veut que les delete effacent les enfants malgré tout. Il faut donc implémenter la méthode delete_dependencies(self) de la classe
# Procédure d'ajout de champ dans une classe
Dans Swift:
- Ouvrir le fichier .json correspondant à la classe
- Regénérer la classe, voir ci-dessus pour la commande
- Ouvrir **ServerDataTests** et ajouter un test sur le champ
- Pour que les tests sur les dates fonctionnent, on peut tester date.formatted() par exemple
Dans Django:
- Ajouter le champ dans la classe
- Si c'est une ForeignKey, *toujours* mettre un related_name sinon la synchro casse
- Si c'est un champ dans **CustomUser**:
- Ajouter le champ à la méthode fields_for_update
- Ajouter le champ dans UserSerializer > create > create_user dans serializers.py
- L'ajouter aussi dans admin.py si nécéssaire
- Faire le *makemigrations* + *migrate*
Note: Les nouvelles classes de model doivent étendre BaseModel ou SideStoreModel
Enfin, revenir dans Xcode, ouvrir ServerDataTests et lancer le test mis à jour

File diff suppressed because it is too large Load Diff

@ -1,779 +0,0 @@
//
// TeamRegistration.swift
// Padel Tournament
//
// Created by razmig on 10/03/2024.
//
import Foundation
import LeStorage
import SwiftUI
@Observable
final class TeamRegistration: BaseTeamRegistration, SideStorable {
// static func resourceName() -> String { "team-registrations" }
// static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
// static func filterByStoreIdentifier() -> Bool { return true }
// static var relationshipNames: [String] = []
//
// var id: String = Store.randomId()
// var lastUpdate: Date
// var tournament: String
// var groupStage: String?
// var registrationDate: Date?
// var callDate: Date?
// var bracketPosition: Int?
// var groupStagePosition: Int?
// var comment: String?
// var source: String?
// var sourceValue: String?
// var logo: String?
// var name: String?
//
// var walkOut: Bool = false
// var wildCardBracket: Bool = false
// var wildCardGroupStage: Bool = false
// var weight: Int = 0
// var lockedWeight: Int?
// var confirmationDate: Date?
// var qualified: Bool = false
// var finalRanking: Int?
// var pointsEarned: Int?
//
// var storeId: String? = nil
init(
tournament: String, groupStage: String? = nil, registrationDate: Date? = nil,
callDate: Date? = nil, bracketPosition: Int? = nil, groupStagePosition: Int? = nil,
comment: String? = nil, source: String? = nil, sourceValue: String? = nil,
logo: String? = nil, name: String? = nil, walkOut: Bool = false,
wildCardBracket: Bool = false, wildCardGroupStage: Bool = false, weight: Int = 0,
lockedWeight: Int? = nil, confirmationDate: Date? = nil, qualified: Bool = false
) {
super.init()
// self.storeId = tournament
self.tournament = tournament
self.groupStage = groupStage
self.registrationDate = registrationDate ?? Date()
self.callDate = callDate
self.bracketPosition = bracketPosition
self.groupStagePosition = groupStagePosition
self.comment = comment
self.source = source
self.sourceValue = sourceValue
self.logo = logo
self.name = name
self.walkOut = walkOut
self.wildCardBracket = wildCardBracket
self.wildCardGroupStage = wildCardGroupStage
self.weight = weight
self.lockedWeight = lockedWeight
self.confirmationDate = confirmationDate
self.qualified = qualified
}
func hasRegisteredOnline() -> Bool {
players().anySatisfy({ $0.registeredOnline })
}
func hasPaidOnline() -> Bool {
players().anySatisfy({ $0.hasPaidOnline() })
}
func hasConfirmed() -> Bool {
players().allSatisfy({ $0.hasConfirmed() })
}
func confirmRegistration() {
let players = players()
players.forEach({ $0.confirmRegistration() })
tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: players)
}
func unrankedOrUnknown() -> Bool {
players().anySatisfy({ $0.source == nil })
}
func isOutOfTournament() -> Bool {
walkOut
}
required init(from decoder: any Decoder) throws {
try super.init(from: decoder)
}
required public init() {
super.init()
}
var tournamentStore: TournamentStore? {
return TournamentLibrary.shared.store(tournamentId: self.tournament)
}
// MARK: - Computed dependencies
func unsortedPlayers() -> [PlayerRegistration] {
guard let tournamentStore = self.tournamentStore else { return [] }
return tournamentStore.playerRegistrations.filter {
$0.teamRegistration == self.id && $0.coach == false
}
}
// MARK: -
func deleteTeamScores() {
guard let tournamentStore = self.tournamentStore else { return }
let ts = tournamentStore.teamScores.filter({ $0.teamRegistration == id })
tournamentStore.teamScores.delete(contentOfs: ts)
}
override func deleteDependencies() {
let unsortedPlayers = unsortedPlayers()
for player in unsortedPlayers {
player.deleteDependencies()
}
self.tournamentStore?.playerRegistrations.deleteDependencies(unsortedPlayers)
let teamScores = teamScores()
for teamScore in teamScores {
teamScore.deleteDependencies()
}
self.tournamentStore?.teamScores.deleteDependencies(teamScores)
}
func hasArrived(isHere: Bool = false) {
let unsortedPlayers = unsortedPlayers()
unsortedPlayers.forEach({ $0.hasArrived = !isHere })
self.tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: unsortedPlayers)
}
func isHere() -> Bool {
let unsortedPlayers = unsortedPlayers()
if unsortedPlayers.isEmpty { return false }
return unsortedPlayers.allSatisfy({ $0.hasArrived })
}
func isSeedable() -> Bool {
bracketPosition == nil && groupStage == nil
}
func setSeedPosition(inSpot match: Match, slot: TeamPosition?, opposingSeeding: Bool) {
var teamPosition: TeamPosition {
if let slot {
return slot
} else {
let matchIndex = match.index
let seedRound = RoundRule.roundIndex(fromMatchIndex: matchIndex)
let numberOfMatches = RoundRule.numberOfMatches(forRoundIndex: seedRound)
let isUpper =
RoundRule.matchIndexWithinRound(fromMatchIndex: matchIndex)
< (numberOfMatches / 2)
var teamPosition = slot ?? (isUpper ? .one : .two)
if opposingSeeding {
teamPosition = slot ?? (isUpper ? .two : .one)
}
return teamPosition
}
}
let seedPosition: Int = match.lockAndGetSeedPosition(atTeamPosition: teamPosition)
tournamentObject()?.resetTeamScores(in: bracketPosition)
self.bracketPosition = seedPosition
if groupStagePosition != nil && qualified == false {
qualified = true
}
if let tournament = tournamentObject() {
if let index = index(in: tournament.selectedSortedTeams()) {
let drawLog = DrawLog(
tournament: tournament.id, drawSeed: index, drawMatchIndex: match.index,
drawTeamPosition: teamPosition, drawType: .seed)
do {
try tournamentStore?.drawLogs.addOrUpdate(instance: drawLog)
} catch {
Logger.error(error)
}
}
tournament.updateTeamScores(in: bracketPosition)
}
}
func expectedSummonDate() -> Date? {
if let groupStageStartDate = groupStageObject()?.startDate {
return groupStageStartDate
} else if let roundMatchStartDate = initialMatch()?.startDate {
return roundMatchStartDate
}
return nil
}
var initialWeight: Int {
return lockedWeight ?? weight
}
func called() -> Bool {
return callDate != nil
}
func confirmed() -> Bool {
return confirmationDate != nil
}
func getPhoneNumbers() -> [String] {
return players().compactMap { $0.phoneNumber }.filter({ $0.isEmpty == false })
}
func getMail() -> [String] {
let mails = players().compactMap({ $0.email })
return mails
}
func isImported() -> Bool {
let unsortedPlayers = unsortedPlayers()
if unsortedPlayers.isEmpty { return false }
return unsortedPlayers.allSatisfy({ $0.isImported() })
}
func isWildCard() -> Bool {
return wildCardBracket || wildCardGroupStage
}
func isPlaying() -> Bool {
return currentMatch() != nil
}
func currentMatch() -> Match? {
return teamScores().compactMap { $0.matchObject() }.first(where: { $0.isRunning() })
}
func teamScores() -> [TeamScore] {
guard let tournamentStore = self.tournamentStore else { return [] }
return tournamentStore.teamScores.filter({ $0.teamRegistration == id })
}
func wins() -> [Match] {
guard let tournamentStore = self.tournamentStore else { return [] }
return tournamentStore.matches.filter({ $0.winningTeamId == id })
}
func loses() -> [Match] {
guard let tournamentStore = self.tournamentStore else { return [] }
return tournamentStore.matches.filter({ $0.losingTeamId == id })
}
func matches() -> [Match] {
guard let tournamentStore = self.tournamentStore else { return [] }
return tournamentStore.matches.filter({ $0.losingTeamId == id || $0.winningTeamId == id })
}
var tournamentCategory: TournamentCategory {
tournamentObject()?.tournamentCategory ?? .men
}
@objc
var canonicalName: String {
players().map { $0.canonicalName }.joined(separator: " ")
}
func hasMemberOfClub(_ codeClubOrClubName: String?) -> Bool {
guard let codeClubOrClubName else { return true }
return unsortedPlayers().anySatisfy({
$0.clubName?.contains(codeClubOrClubName) == true
|| $0.clubName?.contains(codeClubOrClubName) == true
})
}
func updateWeight(inTournamentCategory tournamentCategory: TournamentCategory) {
self.setWeight(from: self.players(), inTournamentCategory: tournamentCategory)
}
func teamLabel(
_ displayStyle: DisplayStyle = .wide, twoLines: Bool = false, separator: String = "&"
) -> String {
if let name { return name }
return players().map { $0.playerLabel(displayStyle) }.joined(
separator: twoLines ? "\n" : " \(separator) ")
}
func teamLabelRanked(displayRank: Bool, displayTeamName: Bool) -> String {
[
displayTeamName ? name : nil, displayRank ? seedIndex() : nil,
displayTeamName ? (name == nil ? teamLabel() : name) : teamLabel(),
].compactMap({ $0 }).joined(separator: " ")
}
func seedIndex() -> String? {
guard let tournament = tournamentObject() else { return nil }
guard let index = index(in: tournament.selectedSortedTeams()) else { return nil }
return "(\(index + 1))"
}
func index(in teams: [TeamRegistration]) -> Int? {
return teams.firstIndex(where: { $0.id == id })
}
func formattedSeed(in teams: [TeamRegistration]? = nil) -> String {
let selectedSortedTeams = teams ?? tournamentObject()?.selectedSortedTeams() ?? []
if let index = index(in: selectedSortedTeams) {
return "#\(index + 1)"
} else {
return "###"
}
}
func contains(_ searchField: String) -> Bool {
return unsortedPlayers().anySatisfy({ $0.contains(searchField) })
|| self.name?.localizedCaseInsensitiveContains(searchField) == true
}
func containsExactlyPlayerLicenses(_ playerLicenses: [String?]) -> Bool {
let arrayOfIds: [String] = unsortedPlayers().compactMap({
$0.licenceId?.strippedLicense?.canonicalVersion
})
let ids: Set<String> = Set<String>(arrayOfIds.sorted())
let searchedIds = Set<String>(
playerLicenses.compactMap({ $0?.strippedLicense?.canonicalVersion }).sorted())
if ids.isEmpty || searchedIds.isEmpty { return false }
return ids.hashValue == searchedIds.hashValue
}
func includes(players: [PlayerRegistration]) -> Bool {
let unsortedPlayers = unsortedPlayers()
guard players.count == unsortedPlayers.count else { return false }
return players.allSatisfy { player in
unsortedPlayers.anySatisfy { _player in
_player.isSameAs(player)
}
}
}
func includes(player: PlayerRegistration) -> Bool {
return unsortedPlayers().anySatisfy { _player in
_player.isSameAs(player)
}
}
func canPlay() -> Bool {
let unsortedPlayers = unsortedPlayers()
if unsortedPlayers.isEmpty { return false }
return matches().isEmpty == false
|| unsortedPlayers.allSatisfy({ $0.hasPaid() || $0.hasArrived })
}
func availableForSeedPick() -> Bool {
return groupStage == nil && bracketPosition == nil
}
func inGroupStage() -> Bool {
return groupStagePosition != nil
}
func inRound() -> Bool {
return bracketPosition != nil
}
func positionLabel() -> String? {
if groupStagePosition != nil { return "Poule" }
if let initialRound = initialRound() {
return initialRound.roundTitle()
} else {
return nil
}
}
func initialRoundColor() -> Color? {
if walkOut { return Color.logoRed }
if groupStagePosition != nil || wildCardGroupStage { return Color.blue }
if let initialRound = initialRound(), let colorHex = RoundRule.colors[safe: initialRound.index] {
return Color(uiColor: .init(fromHex: colorHex))
} else if wildCardBracket {
return Color.mint
} else {
return nil
}
}
func resetGroupeStagePosition() {
guard let tournamentStore = self.tournamentStore else { return }
if let groupStage {
let matches = tournamentStore.matches.filter({ $0.groupStage == groupStage }).map {
$0.id
}
let teamScores = tournamentStore.teamScores.filter({
$0.teamRegistration == id && matches.contains($0.match)
})
tournamentStore.teamScores.delete(contentOfs: teamScores)
}
//groupStageObject()?._matches().forEach({ $0.updateTeamScores() })
groupStage = nil
groupStagePosition = nil
}
func resetBracketPosition() {
guard let tournamentStore = self.tournamentStore else { return }
let matches = tournamentStore.matches.filter({ $0.groupStage == nil }).map { $0.id }
let teamScores = tournamentStore.teamScores.filter({
$0.teamRegistration == id && matches.contains($0.match)
})
tournamentStore.teamScores.delete(contentOfs: teamScores)
self.bracketPosition = nil
}
func resetPositions() {
resetGroupeStagePosition()
resetBracketPosition()
}
func pasteData(_ exportFormat: ExportFormat = .rawText, _ index: Int = 0) -> String {
switch exportFormat {
case .rawText:
return [playersPasteData(exportFormat), formattedInscriptionDate(exportFormat), name]
.compactMap({ $0 }).joined(separator: exportFormat.newLineSeparator())
case .csv:
return [
index.formatted(), playersPasteData(exportFormat),
isWildCard() ? "WC" : weight.formatted(),
].joined(separator: exportFormat.separator())
}
}
var computedRegistrationDate: Date {
return registrationDate ?? .distantFuture
}
func formattedInscriptionDate(_ exportFormat: ExportFormat = .rawText) -> String? {
guard let registrationDate else { return nil }
let formattedDate = registrationDate.formatted(
.dateTime.weekday().day().month().hour().minute())
let onlineSuffix = hasRegisteredOnline() ? " en ligne" : ""
switch exportFormat {
case .rawText:
return "Inscrit\(onlineSuffix) le \(formattedDate)"
case .csv:
return formattedDate
}
}
func formattedSummonDate(_ exportFormat: ExportFormat = .rawText) -> String? {
switch exportFormat {
case .rawText:
if let callDate {
return "Convoqué le "
+ callDate.formatted(.dateTime.weekday().day().month().hour().minute())
} else {
return nil
}
case .csv:
if let callDate {
return callDate.formatted(.dateTime.weekday().day().month().hour().minute())
} else {
return nil
}
}
}
func playersPasteData(_ exportFormat: ExportFormat = .rawText) -> String {
switch exportFormat {
case .rawText:
return players().map { $0.pasteData(exportFormat) }.joined(
separator: exportFormat.newLineSeparator())
case .csv:
return players().map {
[$0.pasteData(exportFormat), isWildCard() ? "WC" : $0.computedRank.formatted()]
.joined(separator: exportFormat.separator())
}.joined(separator: exportFormat.separator())
}
}
func updatePlayers(
_ players: Set<PlayerRegistration>,
inTournamentCategory tournamentCategory: TournamentCategory
) {
let previousPlayers = Set(unsortedPlayers())
players.forEach { player in
previousPlayers.forEach { oldPlayer in
if player.licenceId?.strippedLicense == oldPlayer.licenceId?.strippedLicense,
player.licenceId?.strippedLicense != nil
{
player.registeredOnline = oldPlayer.registeredOnline
player.paymentType = oldPlayer.paymentType
player.paymentId = oldPlayer.paymentId
player.registrationStatus = oldPlayer.registrationStatus
player.timeToConfirm = oldPlayer.timeToConfirm
player.coach = oldPlayer.coach
player.tournamentPlayed = oldPlayer.tournamentPlayed
player.points = oldPlayer.points
player.captain = oldPlayer.captain
player.assimilation = oldPlayer.assimilation
player.ligueName = oldPlayer.ligueName
}
}
}
let playersToRemove = previousPlayers.subtracting(players)
self.tournamentStore?.playerRegistrations.delete(contentOfs: Array(playersToRemove))
setWeight(from: Array(players), inTournamentCategory: tournamentCategory)
players.forEach { player in
player.teamRegistration = id
}
// do {
// try self.tournamentStore.playerRegistrations.addOrUpdate(contentOfs: players)
// } catch {
// Logger.error(error)
// }
}
typealias TeamRange = (left: TeamRegistration?, right: TeamRegistration?)
func replacementRange() -> TeamRange? {
guard let tournamentObject = tournamentObject() else { return nil }
guard let index = tournamentObject.indexOf(team: self) else { return nil }
let selectedSortedTeams = tournamentObject.selectedSortedTeams()
let left = selectedSortedTeams[safe: index - 1]
let right = selectedSortedTeams[safe: index + 1]
return (left: left, right: right)
}
func replacementRangeExtended() -> TeamRange? {
guard let tournamentObject = tournamentObject() else { return nil }
guard let groupStagePosition else { return nil }
let selectedSortedTeams = tournamentObject.selectedSortedTeams()
var left: TeamRegistration? = nil
if groupStagePosition == 0 {
left = tournamentObject.seeds().last
} else {
let previousHat = selectedSortedTeams.filter({
$0.groupStagePosition == groupStagePosition - 1
}).sorted(by: \.weight)
left = previousHat.last
}
var right: TeamRegistration? = nil
if groupStagePosition == tournamentObject.teamsPerGroupStage - 1 {
right = nil
} else {
let previousHat = selectedSortedTeams.filter({
$0.groupStagePosition == groupStagePosition + 1
}).sorted(by: \.weight)
right = previousHat.first
}
return (left: left, right: right)
}
typealias AreInIncreasingOrder = (PlayerRegistration, PlayerRegistration) -> Bool
func players() -> [PlayerRegistration] {
self.unsortedPlayers().sorted { (lhs, rhs) in
let predicates: [AreInIncreasingOrder] = [
{ $0.sex?.rawValue ?? 0 < $1.sex?.rawValue ?? 0 },
{ $0.rank ?? Int.max < $1.rank ?? Int.max },
{ $0.lastName < $1.lastName },
{ $0.firstName < $1.firstName },
]
for predicate in predicates {
if !predicate(lhs, rhs) && !predicate(rhs, lhs) {
continue
}
return predicate(lhs, rhs)
}
return false
}
}
func coaches() -> [PlayerRegistration] {
guard let store = self.tournamentStore else { return [] }
return store.playerRegistrations.filter { $0.coach }
}
func setWeight(
from players: [PlayerRegistration],
inTournamentCategory tournamentCategory: TournamentCategory
) {
let significantPlayerCount = significantPlayerCount()
let sortedPlayers = players.sorted(by: \.computedRank, order: .ascending)
weight = (sortedPlayers.prefix(significantPlayerCount).map { $0.computedRank } + missingPlayerType(inTournamentCategory: tournamentCategory).map { unrankValue(for: $0 == 1 ? true : false ) }).prefix(significantPlayerCount).reduce(0,+)
}
func significantPlayerCount() -> Int {
return tournamentObject()?.significantPlayerCount() ?? 2
}
func missingPlayerType(inTournamentCategory tournamentCategory: TournamentCategory) -> [Int] {
let players = unsortedPlayers()
if players.count >= 2 { return [] }
let s = players.compactMap { $0.sex?.rawValue }
var missing = tournamentCategory.mandatoryPlayerType()
s.forEach { i in
if let index = missing.firstIndex(of: i) {
missing.remove(at: index)
}
}
return missing
}
func unrankValue(for malePlayer: Bool) -> Int {
return tournamentObject()?.unrankValue(for: malePlayer) ?? 90_000
}
func groupStageObject() -> GroupStage? {
guard let groupStage else { return nil }
return self.tournamentStore?.groupStages.findById(groupStage)
}
func initialRound() -> Round? {
guard let bracketPosition else { return nil }
let roundIndex = RoundRule.roundIndex(fromMatchIndex: bracketPosition / 2)
return self.tournamentStore?.rounds.first(where: { $0.index == roundIndex })
}
func initialMatch() -> Match? {
guard let bracketPosition else { return nil }
guard let initialRoundObject = initialRound() else { return nil }
return self.tournamentStore?.matches.first(where: {
$0.round == initialRoundObject.id && $0.index == bracketPosition / 2
})
}
func toggleSummonConfirmation() {
if confirmationDate == nil { confirmationDate = Date() } else { confirmationDate = nil }
}
func didConfirmSummon() -> Bool {
confirmationDate != nil
}
func tournamentObject() -> Tournament? {
return Store.main.findById(tournament)
}
func groupStagePositionAtStep(_ step: Int) -> Int? {
guard let groupStagePosition else { return nil }
if step == 0 {
return groupStagePosition
} else if let groupStageObject = groupStageObject(), groupStageObject.hasEnded() {
return groupStageObject.index
}
return nil
}
func wildcardLabel() -> String? {
if isWildCard() {
let wildcardLabel: String = ["Wildcard", (wildCardBracket ? "Tableau" : "Poule")].joined(separator: " ")
return wildcardLabel
} else {
return nil
}
}
var _cachedRestingTime: (Bool, Date?)?
func restingTime() -> Date? {
if let _cachedRestingTime { return _cachedRestingTime.1 }
let restingTime = matches().filter({ $0.hasEnded() }).sorted(
by: \.computedEndDateForSorting
).last?.endDate
_cachedRestingTime = (true, restingTime)
return restingTime
}
func resetRestingTime() {
_cachedRestingTime = nil
}
var restingTimeForSorting: Date {
restingTime()!
}
func teamNameLabel() -> String {
if let name, name.isEmpty == false {
return name
} else {
return "Toute l'équipe"
}
}
func isDifferentPosition(_ drawMatchIndex: Int?) -> Bool {
if let bracketPosition, let drawMatchIndex {
return drawMatchIndex != bracketPosition
} else if bracketPosition != nil {
return true
} else if drawMatchIndex != nil {
return true
}
return false
}
func shouldDisplayRankAndWeight() -> Bool {
unsortedPlayers().count > 0
}
func teamInitialPositionBracket() -> String? {
let round = initialMatch()?.roundAndMatchTitle()
if let round {
return round
}
return nil
}
func teamInitialPositionGroupStage() -> String? {
let groupStage = self.groupStageObject()
let group = groupStage?.groupStageTitle(.title)
var groupPositionLabel: String? = nil
if let finalPosition = groupStage?.finalPosition(ofTeam: self) {
groupPositionLabel = (finalPosition + 1).ordinalFormatted()
} else if let groupStagePosition {
groupPositionLabel = "\(groupStagePosition + 1)"
}
if let group {
if let groupPositionLabel {
return [group, "#\(groupPositionLabel)"].joined(separator: " ")
} else {
return group
}
}
return nil
}
func qualifiedStatus(hideBracketStatus: Bool = false) -> String? {
let teamInitialPositionBracket = teamInitialPositionBracket()
let groupStageTitle = teamInitialPositionGroupStage()
let base: String? = qualified ? "Qualifié" : nil
if let groupStageTitle, let teamInitialPositionBracket, hideBracketStatus == false {
return [base, groupStageTitle, ">", teamInitialPositionBracket].compactMap({ $0 }).joined(separator: " ")
} else if let groupStageTitle {
return [base, groupStageTitle].compactMap({ $0 }).joined(separator: " ")
} else if hideBracketStatus == false {
return teamInitialPositionBracket
}
return nil
}
func insertOnServer() {
self.tournamentStore?.teamRegistrations.writeChangeAndInsertOnServer(instance: self)
for playerRegistration in self.unsortedPlayers() {
playerRegistration.insertOnServer()
}
}
}
enum TeamDataSource: Int, Codable {
case beachPadel
}

@ -1,117 +0,0 @@
//
// TeamScore.swift
// Padel Tournament
//
// Created by razmig on 10/03/2024.
//
import Foundation
import LeStorage
@Observable
final class TeamScore: BaseTeamScore, SideStorable {
// static func resourceName() -> String { "team-scores" }
// static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
// static func filterByStoreIdentifier() -> Bool { return true }
// static var relationshipNames: [String] = ["match"]
//
// var id: String = Store.randomId()
// var lastUpdate: Date
// var match: String
// var teamRegistration: String?
// //var playerRegistrations: [String] = []
// var score: String?
// var walkOut: Int?
// var luckyLoser: Int?
//
// var storeId: String? = nil
init(match: String, teamRegistration: String? = nil, score: String? = nil, walkOut: Int? = nil, luckyLoser: Int? = nil) {
super.init(match: match, teamRegistration: teamRegistration, score: score, walkOut: walkOut, luckyLoser: luckyLoser)
// self.match = match
// self.teamRegistration = teamRegistration
//// self.playerRegistrations = playerRegistrations
// self.score = score
// self.walkOut = walkOut
// self.luckyLoser = luckyLoser
}
init(match: String, team: TeamRegistration?) {
super.init(match: match)
if let team {
self.teamRegistration = team.id
}
}
required init(from decoder: Decoder) throws {
try super.init(from: decoder)
}
required public init() {
super.init()
}
var tournamentStore: TournamentStore? {
guard let storeId else {
fatalError("missing store id for \(String(describing: type(of: self)))")
}
return TournamentLibrary.shared.store(tournamentId: storeId)
//
// if let store = self.store as? TournamentStore {
// return store
// }
// fatalError("missing store for \(String(describing: type(of: self)))")
}
// MARK: - Computed dependencies
func matchObject() -> Match? {
return self.tournamentStore?.matches.findById(self.match)
}
var team: TeamRegistration? {
guard let teamRegistration else {
return nil
}
return self.tournamentStore?.teamRegistrations.findById(teamRegistration)
}
// MARK: -
func isWalkOut() -> Bool {
return walkOut != nil
}
// enum CodingKeys: String, CodingKey {
// case _id = "id"
// case _storeId = "storeId"
// case _lastUpdate = "lastUpdate"
// case _match = "match"
// case _teamRegistration = "teamRegistration"
// //case _playerRegistrations = "playerRegistrations"
// case _score = "score"
// case _walkOut = "walkOut"
// case _luckyLoser = "luckyLoser"
// }
//
// func encode(to encoder: Encoder) throws {
// var container = encoder.container(keyedBy: CodingKeys.self)
//
// try container.encode(id, forKey: ._id)
// try container.encode(storeId, forKey: ._storeId)
// try container.encode(lastUpdate, forKey: ._lastUpdate)
// try container.encode(match, forKey: ._match)
// try container.encode(teamRegistration, forKey: ._teamRegistration)
// try container.encode(score, forKey: ._score)
// try container.encode(walkOut, forKey: ._walkOut)
// try container.encode(luckyLoser, forKey: ._luckyLoser)
// }
func insertOnServer() {
self.tournamentStore?.teamScores.writeChangeAndInsertOnServer(instance: self)
}
}

File diff suppressed because it is too large Load Diff

@ -1,33 +0,0 @@
//
// TournamentLibrary.swift
// PadelClub
//
// Created by Laurent Morvillier on 11/11/2024.
//
import Foundation
import LeStorage
class TournamentLibrary {
static let shared: TournamentLibrary = TournamentLibrary()
fileprivate var _stores: [String : TournamentStore] = [:]
func store(tournamentId: String) -> TournamentStore? {
guard let tournament = DataStore.shared.tournaments.first(where: { $0.id == tournamentId }) else { return nil }
if let store = self._stores[tournamentId] {
return store
}
let store = StoreCenter.main.store(identifier: tournamentId)
let tournamentStore = TournamentStore(store: store)
self._stores[tournamentId] = tournamentStore
return tournamentStore
}
func reset() {
self._stores.removeAll()
}
}

@ -1,68 +0,0 @@
//
// TournamentStore.swift
// PadelClub
//
// Created by Laurent Morvillier on 26/06/2024.
//
import Foundation
import LeStorage
import SwiftUI
class TournamentStore: ObservableObject {
var store: Store
fileprivate(set) var groupStages: SyncedCollection<GroupStage> = SyncedCollection.placeholder()
fileprivate(set) var matches: SyncedCollection<Match> = SyncedCollection.placeholder()
fileprivate(set) var teamRegistrations: SyncedCollection<TeamRegistration> = SyncedCollection.placeholder()
fileprivate(set) var playerRegistrations: SyncedCollection<PlayerRegistration> = SyncedCollection.placeholder()
fileprivate(set) var rounds: SyncedCollection<Round> = SyncedCollection.placeholder()
fileprivate(set) var teamScores: SyncedCollection<TeamScore> = SyncedCollection.placeholder()
fileprivate(set) var matchSchedulers: StoredCollection<MatchScheduler> = StoredCollection.placeholder()
fileprivate(set) var drawLogs: SyncedCollection<DrawLog> = SyncedCollection.placeholder()
// convenience init(tournament: Tournament) {
// let store = StoreCenter.main.store(identifier: tournament.id)
// self.init(store: store)
// self._initialize()
// }
init(store: Store) {
self.store = store
self._initialize()
}
fileprivate func _initialize() {
let indexed: Bool = true
self.groupStages = self.store.registerSynchronizedCollection(indexed: indexed)
self.rounds = self.store.registerSynchronizedCollection(indexed: indexed)
self.teamRegistrations = self.store.registerSynchronizedCollection(indexed: indexed)
self.playerRegistrations = self.store.registerSynchronizedCollection(indexed: indexed)
self.matches = self.store.registerSynchronizedCollection(indexed: indexed)
self.teamScores = self.store.registerSynchronizedCollection(indexed: indexed)
self.matchSchedulers = self.store.registerCollection(indexed: indexed)
self.drawLogs = self.store.registerSynchronizedCollection(indexed: indexed)
self.store.loadCollectionsFromServerIfNoFile()
NotificationCenter.default.addObserver(
self,
selector: #selector(_leStorageDidSynchronize),
name: NSNotification.Name.LeStorageDidSynchronize,
object: nil)
}
@objc func _leStorageDidSynchronize(notification: Notification) {
// Logger.log("SYNCED > teamRegistrations count = \(self.teamRegistrations.count)")
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}

@ -1,96 +0,0 @@
//
// Array+Extensions.swift
// PadelClub
//
// Created by Razmig Sarkissian on 03/03/2024.
//
import Foundation
extension Array {
func chunked(into size: Int) -> [[Element]] {
return stride(from: 0, to: count, by: size).map {
Array(self[$0 ..< Swift.min($0 + size, count)])
}
}
func anySatisfy(_ p: (Element) -> Bool) -> Bool {
return first(where: { p($0) }) != nil
//return !self.allSatisfy { !p($0) }
}
// Check if the number of elements in the sequence is even
var isEven: Bool {
return self.count % 2 == 0
}
// Check if the number of elements in the sequence is odd
var isOdd: Bool {
return self.count % 2 != 0
}
}
extension Array where Element: Equatable {
/// Remove first collection element that is equal to the given `object` or `element`:
mutating func remove(elements: [Element]) {
elements.forEach {
if let index = firstIndex(of: $0) {
remove(at: index)
}
}
}
}
extension Array where Element: CustomStringConvertible {
func customJoined(separator: String, lastSeparator: String) -> String {
switch count {
case 0:
return ""
case 1:
return "\(self[0])"
case 2:
return "\(self[0]) \(lastSeparator) \(self[1])"
default:
let firstPart = dropLast().map { "\($0)" }.joined(separator: ", ")
let lastPart = "\(lastSeparator) \(last!)"
return "\(firstPart) \(lastPart)"
}
}
}
extension Dictionary where Key == Int, Value == [String] {
mutating func setOrAppend(_ element: String?, at key: Int) {
// Check if the element is nil; do nothing if it is
guard let element = element else {
return
}
// Check if the key exists in the dictionary
if var array = self[key] {
// If it exists, append the element to the array
array.append(element)
self[key] = array
} else {
// If it doesn't exist, create a new array with the element
self[key] = [element]
}
}
}
extension Array where Element == String {
func formatList(maxDisplay: Int = 2) -> [String] {
// Check if the array has fewer or equal elements than the maximum display limit
if self.count <= maxDisplay {
// Join all elements with commas
return self
} else {
// Join only the first `maxDisplay` elements and add "et plus"
let displayedItems = self.prefix(maxDisplay)
let remainingCount = self.count - maxDisplay
return displayedItems.dropLast() + [displayedItems.last! + " et \(remainingCount) de plus"]
}
}
}

@ -0,0 +1,25 @@
//
// Badge+Extensions.swift
// PadelClub
//
// Created by Laurent Morvillier on 15/04/2025.
//
import Foundation
import SwiftUI
import PadelClubData
extension Badge {
func color() -> Color {
switch self {
case .checkmark:
.green
case .xmark:
.logoRed
case .custom(_, let color):
color
}
}
}

@ -1,71 +0,0 @@
//
// Calendar+Extensions.swift
// PadelClub
//
// Created by Razmig Sarkissian on 28/03/2024.
//
import Foundation
extension Calendar {
func numberOfDaysBetween(_ from: Date?, and to: Date?) -> Int {
guard let from, let to else { return 0 }
let fromDate = startOfDay(for: from)
let toDate = startOfDay(for: to)
let numberOfDays = dateComponents([.day], from: fromDate, to: toDate)
return numberOfDays.day! // <1>
}
func isSameDay(date1: Date?, date2: Date?) -> Bool {
guard let date1, let date2 else { return false }
return numberOfDaysBetween(date1, and: date2) == 0
}
func getSportAge() -> Int {
let currentDate = Date()
// Get the current year
let currentYear = component(.year, from: currentDate)
// Define the date components for 1st September and 31st December of the current year
let septemberFirstComponents = DateComponents(year: currentYear, month: 9, day: 1)
let decemberThirtyFirstComponents = DateComponents(year: currentYear, month: 12, day: 31)
// Get the actual dates for 1st September and 31st December
let septemberFirst = date(from: septemberFirstComponents)!
let decemberThirtyFirst = date(from: decemberThirtyFirstComponents)!
// Determine the sport year
let sportYear: Int
if currentDate >= septemberFirst && currentDate <= decemberThirtyFirst {
// If after 1st September and before 31st December, use current year + 1
sportYear = currentYear + 1
} else {
// Otherwise, use the current year
sportYear = currentYear
}
return sportYear
}
}
extension Calendar {
// Add or subtract months from a date
func addMonths(_ months: Int, to date: Date) -> Date {
return self.date(byAdding: .month, value: months, to: date)!
}
// Generate a list of month start dates between two dates
func generateMonthRange(startDate: Date, endDate: Date) -> [Date] {
var dates: [Date] = []
var currentDate = startDate
while currentDate <= endDate {
dates.append(currentDate)
currentDate = self.addMonths(1, to: currentDate)
}
return dates
}
}

@ -1,45 +0,0 @@
//
// KeyedEncodingContainer+Extensions.swift
// PadelClub
//
// Created by Laurent Morvillier on 18/09/2024.
//
import Foundation
import LeStorage
extension KeyedDecodingContainer {
func decodeEncrypted(key: Key) throws -> String {
let data = try self.decode(Data.self, forKey: key)
return try data.decryptData(pass: CryptoKey.pass.rawValue)
}
func decodeEncryptedIfPresent(key: Key) throws -> String? {
let data = try self.decodeIfPresent(Data.self, forKey: key)
if let data {
return try data.decryptData(pass: CryptoKey.pass.rawValue)
}
return nil
}
}
extension KeyedEncodingContainer {
mutating func encodeAndEncrypt(_ value: Data, forKey key: Key) throws {
let encryped: Data = try value.encrypt(pass: CryptoKey.pass.rawValue)
try self.encode(encryped, forKey: key)
}
mutating func encodeAndEncryptIfPresent(_ value: Data?, forKey key: Key) throws {
guard let value else {
try encodeNil(forKey: key)
return
}
try self.encodeAndEncrypt(value, forKey: key)
}
}

@ -0,0 +1,22 @@
//
// CustomUser+Extensions.swift
// PadelClub
//
// Created by Laurent Morvillier on 15/04/2025.
//
import Foundation
import PadelClubData
extension CustomUser {
func currentPlayerData() -> ImportedPlayer? {
guard let licenceId = self.licenceId?.strippedLicense else { return nil }
let federalContext = PersistenceController.shared.localContainer.viewContext
let fetchRequest = ImportedPlayer.fetchRequest()
let predicate = NSPredicate(format: "license == %@", licenceId)
fetchRequest.predicate = predicate
return try? federalContext.fetch(fetchRequest).first
}
}

@ -1,266 +0,0 @@
//
// Date+Extensions.swift
// PadelClub
//
// Created by Razmig Sarkissian on 01/03/2024.
//
import Foundation
enum TimeOfDay {
case morning
case noon
case afternoon
case evening
case night
var hello: String {
switch self {
case .morning, .noon, .afternoon:
return "Bonjour"
case .evening, .night:
return "Bonsoir"
}
}
var goodbye: String {
switch self {
case .morning, .noon, .afternoon:
return "Bonne journée"
case .evening, .night:
return "Bonne soirée"
}
}
}
extension Date {
func withoutSeconds() -> Date {
let calendar = Calendar.current
return calendar.date(bySettingHour: calendar.component(.hour, from: self),
minute: calendar.component(.minute, from: self),
second: 0,
of: self)!
}
func localizedDate() -> String {
self.formatted(.dateTime.weekday().day().month()) + " à " + self.formattedAsHourMinute()
}
func formattedAsHourMinute() -> String {
formatted(.dateTime.hour().minute())
}
func formattedAsDate() -> String {
formatted(.dateTime.weekday().day(.twoDigits).month().year())
}
var dateFormatted: String {
formatted(.dateTime.day(.twoDigits).month(.twoDigits).year(.twoDigits))
}
var monthYearFormatted: String {
formatted(.dateTime.month(.wide).year(.defaultDigits))
}
var twoDigitsYearFormatted: String {
formatted(Date.FormatStyle(date: .numeric, time: .omitted).locale(Locale(identifier: "fr_FR")).year(.twoDigits))
}
var timeOfDay: TimeOfDay {
let hour = Calendar.current.component(.hour, from: self)
switch hour {
case 6..<12 : return .morning
case 12 : return .noon
case 13..<17 : return .afternoon
case 17..<22 : return .evening
default: return .night
}
}
}
extension Date {
func isInCurrentYear() -> Bool {
let calendar = Calendar.current
let currentYear = calendar.component(.year, from: Date())
let yearOfDate = calendar.component(.year, from: self)
return currentYear == yearOfDate
}
func get(_ components: Calendar.Component..., calendar: Calendar = Calendar.current) -> DateComponents {
return calendar.dateComponents(Set(components), from: self)
}
func get(_ component: Calendar.Component, calendar: Calendar = Calendar.current) -> Int {
return calendar.component(component, from: self)
}
var tomorrowAtNine: Date {
let currentHour = Calendar.current.component(.hour, from: self)
let startOfDay = Calendar.current.startOfDay(for: self)
if currentHour < 8 {
return Calendar.current.date(byAdding: .hour, value: 9, to: startOfDay)!
} else {
let date = Calendar.current.date(byAdding: .day, value: 1, to: startOfDay)
return Calendar.current.date(byAdding: .hour, value: 9, to: date!)!
}
}
func atBeginningOfDay(hourInt: Int = 9) -> Date {
Calendar.current.date(byAdding: .hour, value: hourInt, to: self.startOfDay)!
}
static var firstDayOfWeek = Calendar.current.firstWeekday
static var capitalizedFirstLettersOfWeekdays: [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.veryShortStandaloneWeekdaySymbols
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] = {
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale.current
return (1...12).compactMap { month in
dateFormatter.setLocalizedDateFormatFromTemplate("MMMM")
let date = Calendar.current.date(from: DateComponents(year: 2000, month: month, day: 1))
return date.map { dateFormatter.string(from: $0) }
}
}()
var startOfMonth: Date {
Calendar.current.dateInterval(of: .month, for: self)!.start
}
var endOfMonth: Date {
let lastDay = Calendar.current.dateInterval(of: .month, for: self)!.end
return Calendar.current.date(byAdding: .day, value: -1, to: lastDay)!
}
var startOfPreviousMonth: Date {
let dayInPreviousMonth = Calendar.current.date(byAdding: .month, value: -1, to: self)!
return dayInPreviousMonth.startOfMonth
}
var numberOfDaysInMonth: Int {
Calendar.current.component(.day, from: endOfMonth)
}
// var sundayBeforeStart: Date {
// let startOfMonthWeekday = Calendar.current.component(.weekday, from: startOfMonth)
// let numberFromPreviousMonth = startOfMonthWeekday - 1
// return Calendar.current.date(byAdding: .day, value: -numberFromPreviousMonth, to: startOfMonth)!
// }
// New to accomodate for different start of week days
var firstWeekDayBeforeStart: Date {
let startOfMonthWeekday = Calendar.current.component(.weekday, from: startOfMonth)
let numberFromPreviousMonth = startOfMonthWeekday - Self.firstDayOfWeek
return Calendar.current.date(byAdding: .day, value: -numberFromPreviousMonth, to: startOfMonth)!
}
var calendarDisplayDays: [Date] {
var days: [Date] = []
// Current month days
for dayOffset in 0..<numberOfDaysInMonth {
let newDay = Calendar.current.date(byAdding: .day, value: dayOffset, to: startOfMonth)
days.append(newDay!)
}
// previous month days
for dayOffset in 0..<startOfPreviousMonth.numberOfDaysInMonth {
let newDay = Calendar.current.date(byAdding: .day, value: dayOffset, to: startOfPreviousMonth)
days.append(newDay!)
}
// Fixed to accomodate different weekday starts
return days.filter { $0 >= firstWeekDayBeforeStart && $0 <= endOfMonth }.sorted(by: <)
}
var monthInt: Int {
Calendar.current.component(.month, from: self)
}
var yearInt: Int {
Calendar.current.component(.year, from: self)
}
var dayInt: Int {
Calendar.current.component(.day, from: self)
}
var startOfDay: Date {
Calendar.current.startOfDay(for: self)
}
func endOfDay() -> Date {
let calendar = Calendar.current
return calendar.date(bySettingHour: 23, minute: 59, second: 59, of: self)!
}
func atNine() -> Date {
let calendar = Calendar.current
return calendar.date(bySettingHour: 9, minute: 0, second: 0, of: self)!
}
func atEightAM() -> Date {
let calendar = Calendar.current
return calendar.date(bySettingHour: 8, minute: 0, second: 0, of: self)!
}
}
extension Date {
func isEarlierThan(_ date: Date) -> Bool {
Calendar.current.compare(self, to: date, toGranularity: .minute) == .orderedAscending
}
}
extension Date {
func localizedTime() -> String {
self.formattedAsHourMinute()
}
func localizedDay() -> String {
self.formatted(.dateTime.weekday(.wide).day())
}
func localizedWeekDay() -> String {
self.formatted(.dateTime.weekday(.wide))
}
func timeElapsedString() -> String {
let timeInterval = abs(Date().timeIntervalSince(self))
let duration = Duration.seconds(timeInterval)
let formatStyle = Duration.UnitsFormatStyle(allowedUnits: [.hours, .minutes], width: .narrow)
return formatStyle.format(duration)
}
static var hourMinuteFormatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.hour, .minute] // Customize units
formatter.unitsStyle = .abbreviated // You can choose .abbreviated or .short
return formatter
}()
func truncateMinutesAndSeconds() -> Date {
let calendar = Calendar.current
return calendar.date(bySetting: .minute, value: 0, of: self)!.withoutSeconds()
}
}

@ -1,43 +0,0 @@
//
// FixedWidthInteger+Extensions.swift
// PadelClub
//
// Created by Razmig Sarkissian on 03/03/2024.
//
import Foundation
public extension FixedWidthInteger {
func ordinalFormattedSuffix(feminine: Bool = false) -> String {
switch self {
case 1: return feminine ? "ère" : "er"
default: return "ème"
}
}
func ordinalFormatted(feminine: Bool = false) -> String {
return self.formatted() + self.ordinalFormattedSuffix(feminine: feminine)
}
private var isMany: Bool {
self > 1 || self < -1
}
var pluralSuffix: String {
return isMany ? "s" : ""
}
func localizedPluralSuffix(_ plural: String = "s") -> String {
return isMany ? plural : ""
}
func formattedAsRawString() -> String {
String(self)
}
func durationInHourMinutes() -> String {
let duration = Duration.seconds(self*60)
let formatStyle = Duration.UnitsFormatStyle(allowedUnits: [.hours, .minutes], width: .narrow)
return formatStyle.format(duration)
}
}

@ -1,28 +0,0 @@
//
// Locale+Extensions.swift
// PadelClub
//
// Created by Laurent Morvillier on 03/04/2024.
//
import Foundation
extension Locale {
static func countries() -> [String] {
var countries: [String] = []
for countryCode in Locale.Region.isoRegions {
if let countryName = Locale.current.localizedString(forRegionCode: countryCode.identifier) {
countries.append(countryName)
}
}
return countries.sorted()
}
static func defaultCurrency() -> String {
// return "EUR"
Locale.current.currency?.identifier ?? "EUR"
}
}

@ -1,34 +1,14 @@
// //
// MonthData.swift // MonthData+Extensions.swift
// PadelClub // PadelClub
// //
// Created by Razmig Sarkissian on 18/04/2024. // Created by Laurent Morvillier on 15/04/2025.
// //
import Foundation import Foundation
import SwiftUI import PadelClubData
import LeStorage
@Observable extension MonthData {
final class MonthData: BaseMonthData {
init(monthKey: String) {
super.init()
self.monthKey = monthKey
self.creationDate = Date()
}
required init(from decoder: Decoder) throws {
try super.init(from: decoder)
}
required public init() {
super.init()
}
func total() -> Int {
return (maleCount ?? 0) + (femaleCount ?? 0)
}
static func calculateCurrentUnrankedValues(fromDate: Date) async { static func calculateCurrentUnrankedValues(fromDate: Date) async {

@ -1,27 +0,0 @@
//
// MySortDescriptor.swift
// PadelClub
//
// Created by Razmig Sarkissian on 26/03/2024.
//
import Foundation
struct MySortDescriptor<Value> {
var comparator: (Value, Value) -> ComparisonResult
}
extension MySortDescriptor {
static func keyPath<T: Comparable>(_ keyPath: KeyPath<Value, T>) -> Self {
Self { rootA, rootB in
let valueA = rootA[keyPath: keyPath]
let valueB = rootB[keyPath: keyPath]
guard valueA != valueB else {
return .orderedSame
}
return valueA < valueB ? .orderedAscending : .orderedDescending
}
}
}

@ -1,20 +0,0 @@
//
// NumberFormatter+Extensions.swift
// PadelClub
//
// Created by Razmig Sarkissian on 27/03/2024.
//
import Foundation
extension NumberFormatter {
static var ordinal: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .ordinal
return formatter
}()
static var standard: NumberFormatter = {
return NumberFormatter()
}()
}

@ -0,0 +1,229 @@
//
// PlayerRegistration+Extensions.swift
// PadelClub
//
// Created by Laurent Morvillier on 15/04/2025.
//
import Foundation
import PadelClubData
extension PlayerRegistration {
convenience init(importedPlayer: ImportedPlayer) {
self.init()
self.teamRegistration = ""
self.firstName = (importedPlayer.firstName ?? "").prefixTrimmed(50).capitalized
self.lastName = (importedPlayer.lastName ?? "").prefixTrimmed(50).uppercased()
self.licenceId = importedPlayer.license?.prefixTrimmed(50) ?? nil
self.rank = Int(importedPlayer.rank)
self.sex = importedPlayer.male ? .male : .female
self.tournamentPlayed = importedPlayer.tournamentPlayed
self.points = importedPlayer.getPoints()
self.clubName = importedPlayer.clubName?.prefixTrimmed(200)
self.ligueName = importedPlayer.ligueName?.prefixTrimmed(200)
self.assimilation = importedPlayer.assimilation?.prefixTrimmed(50)
self.source = .frenchFederation
self.birthdate = importedPlayer.birthYear?.prefixTrimmed(50)
}
convenience init?(federalData: [String], sex: Int, sexUnknown: Bool) {
self.init()
let _lastName = federalData[0].trimmed.uppercased()
let _firstName = federalData[1].trimmed.capitalized
if _lastName.isEmpty && _firstName.isEmpty { return nil }
lastName = _lastName.prefixTrimmed(50)
firstName = _firstName.prefixTrimmed(50)
birthdate = federalData[2].formattedAsBirthdate().prefixTrimmed(50)
licenceId = federalData[3].prefixTrimmed(50)
clubName = federalData[4].prefixTrimmed(200)
let stringRank = federalData[5]
if stringRank.isEmpty {
rank = nil
} else {
rank = Int(stringRank)
}
let _email = federalData[6]
if _email.isEmpty == false {
self.email = _email.prefixTrimmed(50)
}
let _phoneNumber = federalData[7]
if _phoneNumber.isEmpty == false {
self.phoneNumber = _phoneNumber.prefixTrimmed(50)
}
source = .beachPadel
if sexUnknown {
if sex == 1 && FileImportManager.shared.foundInWomenData(license: federalData[3]) {
self.sex = .female
} else if FileImportManager.shared.foundInMenData(license: federalData[3]) {
self.sex = .male
} else {
self.sex = nil
}
} else {
self.sex = PlayerSexType(rawValue: sex)
}
}
}
extension PlayerRegistration {
func hasHomonym() -> Bool {
let federalContext = PersistenceController.shared.localContainer.viewContext
let fetchRequest = ImportedPlayer.fetchRequest()
let predicate = NSPredicate(format: "firstName == %@ && lastName == %@", firstName, lastName)
fetchRequest.predicate = predicate
do {
let count = try federalContext.count(for: fetchRequest)
return count > 1
} catch {
}
return false
}
func updateRank(from sources: [CSVParser], lastRank: Int?) async throws {
#if DEBUG_TIME
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func updateRank()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif
if let dataFound = try await history(from: sources) {
rank = dataFound.rankValue?.toInt()
points = dataFound.points
tournamentPlayed = dataFound.tournamentCountValue?.toInt()
} else if let dataFound = try await historyFromName(from: sources) {
rank = dataFound.rankValue?.toInt()
points = dataFound.points
tournamentPlayed = dataFound.tournamentCountValue?.toInt()
} else {
rank = lastRank
}
}
func history(from sources: [CSVParser]) async throws -> Line? {
#if DEBUG_TIME
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func history()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif
guard let license = licenceId?.strippedLicense else {
return nil // Do NOT call historyFromName here, let updateRank handle it
}
let filteredSources = sources.filter { $0.maleData == isMalePlayer() }
return await withTaskGroup(of: Line?.self) { group in
for source in filteredSources {
group.addTask {
guard !Task.isCancelled else { return nil }
return try? await source.first { $0.rawValue.contains(";\(license);") }
}
}
for await result in group {
if let result {
group.cancelAll() // Stop other tasks as soon as we find a match
return result
}
}
return nil
}
}
func historyFromName(from sources: [CSVParser]) async throws -> Line? {
#if DEBUG
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func historyFromName()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif
let filteredSources = sources.filter { $0.maleData == isMalePlayer() }
let normalizedLastName = lastName.canonicalVersionWithPunctuation
let normalizedFirstName = firstName.canonicalVersionWithPunctuation
return await withTaskGroup(of: Line?.self) { group in
for source in filteredSources {
group.addTask {
guard !Task.isCancelled else { print("Cancelled"); return nil }
return try? await source.first {
let lineValue = $0.rawValue.canonicalVersionWithPunctuation
return lineValue.contains(";\(normalizedLastName);\(normalizedFirstName);")
}
}
}
for await result in group {
if let result {
group.cancelAll() // Stop other tasks as soon as we find a match
return result
}
}
return nil
}
}
}
extension PlayerRegistration: PlayerHolder {
func getAssimilatedAsMaleRank() -> Int? {
nil
}
func getFirstName() -> String {
firstName
}
func getLastName() -> String {
lastName
}
func getPoints() -> Double? {
self.points
}
func getRank() -> Int? {
rank
}
func isUnranked() -> Bool {
rank == nil
}
func formattedRank() -> String {
self.rankLabel()
}
func formattedLicense() -> String {
if let licenceId { return licenceId.computedLicense }
return "aucune licence"
}
var male: Bool {
isMalePlayer()
}
func getBirthYear() -> Int? {
nil
}
func getProgression() -> Int {
0
}
func getComputedRank() -> Int? {
computedRank
}
}

@ -0,0 +1,33 @@
//
// Round+Extensions.swift
// PadelClub
//
// Created by Laurent Morvillier on 30/04/2025.
//
import Foundation
import PadelClubData
extension Round {
func loserBracketTurns() -> [LoserRound] {
#if _DEBUG_TIME //DEBUGING TIME
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func loserBracketTurns()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif
var rounds = [LoserRound]()
let currentRoundMatchCount = RoundRule.numberOfMatches(forRoundIndex: index)
let roundCount = RoundRule.numberOfRounds(forTeams: currentRoundMatchCount)
for index in 0..<roundCount {
let lr = LoserRound(roundIndex: roundCount - index - 1, turnIndex: index, upperBracketRound: self)
rounds.append(lr)
}
return rounds
}
}

@ -1,103 +0,0 @@
//
// Sequence+Extensions.swift
// PadelClub
//
// Created by Razmig Sarkissian on 03/03/2024.
//
import Foundation
extension Collection {
/// Returns the element at the specified index if it is within bounds, otherwise nil.
subscript (safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
extension Sequence {
func sorted<T: Comparable>(by keyPath: KeyPath<Element, T>) -> [Element] {
return sorted { a, b in
return a[keyPath: keyPath] < b[keyPath: keyPath]
}
}
}
extension Sequence {
func pairs() -> AnySequence<(Element, Element)> {
AnySequence(zip(self, self.dropFirst()))
}
}
extension Sequence {
func concurrentForEach(
_ operation: @escaping (Element) async throws -> Void
) async throws {
try await withThrowingTaskGroup(of: Void.self) { group in
// First, create all tasks
for element in self {
group.addTask {
try await operation(element)
}
}
// Then wait for all tasks to complete
for try await _ in group {}
}
}
func concurrentForEach(
_ operation: @escaping (Element) async -> Void
) async {
await withTaskGroup(of: Void.self) { group in
// First, add all tasks
for element in self {
group.addTask {
await operation(element)
}
}
// Then wait for all tasks to complete
for await _ in group {}
}
}
}
enum SortOrder {
case ascending
case descending
}
extension Sequence {
func sorted(using descriptors: [MySortDescriptor<Element>],
order: SortOrder) -> [Element] {
sorted { valueA, valueB in
for descriptor in descriptors {
let result = descriptor.comparator(valueA, valueB)
switch result {
case .orderedSame:
// Keep iterating if the two elements are equal,
// since that'll let the next descriptor determine
// the sort order:
break
case .orderedAscending:
return order == .ascending
case .orderedDescending:
return order == .descending
}
}
// If no descriptor was able to determine the sort
// order, we'll default to false (similar to when
// using the '<' operator with the built-in API):
return false
}
}
}
extension Sequence {
func sorted(using descriptors: MySortDescriptor<Element>...) -> [Element] {
sorted(using: descriptors, order: .ascending)
}
}

@ -0,0 +1,34 @@
//
// SourceFileManager+Extensions.swift
// PadelClub
//
// Created by Laurent Morvillier on 15/04/2025.
//
import Foundation
import LeStorage
import PadelClubData
extension SourceFileManager {
func exportToCSV(_ prefix: String = "", players: [FederalPlayer], sourceFileType: SourceFile, date: Date) {
let lastDateString = URL.importDateFormatter.string(from: date)
let dateString = [prefix, "CLASSEMENT-PADEL", sourceFileType.rawValue, lastDateString].filter({ $0.isEmpty == false }).joined(separator: "-") + "." + "csv"
let documentsUrl:URL = (FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first as URL?)!
let destinationFileUrl = documentsUrl.appendingPathComponent("\(dateString)")
var csvText : String = ""
for player in players {
csvText.append(player.exportToCSV() + "\n")
}
do {
try csvText.write(to: destinationFileUrl, atomically: true, encoding: .utf8)
print("CSV file exported successfully.")
} catch {
print("Error writing CSV file:", error)
Logger.error(error)
}
}
}

@ -0,0 +1,42 @@
//
// SpinDrawable+Extensions.swift
// PadelClub
//
// Created by Laurent Morvillier on 15/04/2025.
//
import Foundation
import PadelClubData
extension String: SpinDrawable {
public func segmentLabel(_ displayStyle: DisplayStyle, hideNames: Bool) -> [String] {
[self]
}
}
extension Match: SpinDrawable {
public func segmentLabel(_ displayStyle: DisplayStyle, hideNames: Bool) -> [String] {
let teams = teams()
if teams.count == 1, hideNames == false {
return teams.first!.segmentLabel(displayStyle, hideNames: hideNames)
} else {
return [roundTitle(), matchTitle(displayStyle)].compactMap { $0 }
}
}
}
extension TeamRegistration: SpinDrawable {
public func segmentLabel(_ displayStyle: DisplayStyle, hideNames: Bool) -> [String] {
var strings: [String] = []
let indexLabel = tournamentObject()?.labelIndexOf(team: self)
if let indexLabel {
strings.append(indexLabel)
if hideNames {
return strings
}
}
strings.append(contentsOf: self.players().map { $0.playerLabel(displayStyle) })
return strings
}
}

@ -1,47 +0,0 @@
//
// String+Crypto.swift
// PadelClub
//
// Created by Laurent Morvillier on 30/04/2024.
//
import Foundation
import CryptoKit
enum CryptoError: Error {
case invalidUTF8
case cantConvertUTF8
case invalidBase64String
case nilSeal
}
extension Data {
func encrypt(pass: String) throws -> Data {
let key = try self._createSymmetricKey(fromString: pass)
let sealedBox = try AES.GCM.seal(self, using: key)
if let combined = sealedBox.combined {
return combined
}
throw CryptoError.nilSeal
}
func decryptData(pass: String) throws -> String {
let key = try self._createSymmetricKey(fromString: pass)
let sealedBox = try AES.GCM.SealedBox(combined: self)
let decryptedData = try AES.GCM.open(sealedBox, using: key)
guard let decryptedMessage = String(data: decryptedData, encoding: .utf8) else {
throw CryptoError.invalidUTF8
}
return decryptedMessage
}
fileprivate func _createSymmetricKey(fromString keyString: String) throws -> SymmetricKey {
guard let keyData = Data(base64Encoded: keyString) else {
throw CryptoError.invalidBase64String
}
return SymmetricKey(data: keyData)
}
}

@ -1,313 +0,0 @@
//
// String+Extensions.swift
// PadelClub
//
// Created by Razmig Sarkissian on 01/03/2024.
//
import Foundation
// MARK: - Trimming and stuff
extension String {
func trunc(length: Int, trailing: String = "") -> String {
if length <= 0 { return self }
return (self.count > length) ? self.prefix(length) + trailing : self
}
func prefixTrimmed(_ length: Int) -> String {
String(trimmed.prefix(length))
}
func prefixMultilineTrimmed(_ length: Int) -> String {
String(trimmedMultiline.prefix(length))
}
var trimmed: String {
replaceCharactersFromSet(characterSet: .newlines, replacementString: " ").trimmingCharacters(in: .whitespacesAndNewlines)
}
var trimmedMultiline: String {
self.trimmingCharacters(in: .whitespacesAndNewlines)
}
func replaceCharactersFromSet(characterSet: CharacterSet, replacementString: String = "") -> String {
components(separatedBy: characterSet).joined(separator:replacementString)
}
var canonicalVersion: String {
trimmed.replaceCharactersFromSet(characterSet: .punctuationCharacters, replacementString: " ").folding(options: .diacriticInsensitive, locale: .current).lowercased()
}
var canonicalVersionWithPunctuation: String {
trimmed.folding(options: .diacriticInsensitive, locale: .current).lowercased()
}
var removingFirstCharacter: String {
String(dropFirst())
}
func isValidEmail() -> Bool {
let emailRegEx = "^[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}$"
let emailPredicate = NSPredicate(format:"SELF MATCHES %@", emailRegEx)
return emailPredicate.evaluate(with: self)
}
}
// MARK: - Club Name
extension String {
func acronym() -> String {
let acronym = canonicalVersion.replaceCharactersFromSet(characterSet: .whitespacesAndNewlines)
if acronym.count > 10 {
return concatenateFirstLetters().uppercased()
} else {
return acronym.uppercased()
}
}
func concatenateFirstLetters() -> String {
// Split the input into sentences
let sentences = self.components(separatedBy: .whitespacesAndNewlines)
if sentences.count == 1 {
return String(self.prefix(10))
}
// Extract the first character of each sentence
let firstLetters = sentences.compactMap { sentence -> Character? in
let trimmedSentence = sentence.trimmingCharacters(in: .whitespacesAndNewlines)
if trimmedSentence.count > 2 {
if let firstCharacter = trimmedSentence.first {
return firstCharacter
}
}
return nil
}
// Join the first letters together into a string
let result = String(firstLetters)
return String(result.prefix(10))
}
}
// MARK: - FFT License
extension String {
var computedLicense: String {
if let licenseKey {
return self + licenseKey
} else {
return self
}
}
var strippedLicense: String? {
var dropFirst = 0
if hasPrefix("0") {
dropFirst = 1
}
if let match = self.dropFirst(dropFirst).firstMatch(of: /[0-9]{6,8}/) {
let lic = String(self.dropFirst(dropFirst)[match.range.lowerBound..<match.range.upperBound])
return lic
} else {
return nil
}
}
var isLicenseNumber: Bool {
if let match = self.firstMatch(of: /[0-9]{6,8}[A-Z]/) {
let lic = String(self[match.range.lowerBound..<match.range.upperBound].dropLast(1))
let lastLetter = String(self[match.range.lowerBound..<match.range.upperBound].suffix(1))
if let lkey = lic.licenseKey {
return lkey == lastLetter
}
}
return false
}
var licenseKey: String? {
if let intValue = Int(self) {
var value = intValue
value -= 1
value = value % 23
let v = UnicodeScalar("A").value
let i = Int(v)
if let s = UnicodeScalar(i + value) {
var c = Character(s)
if c >= "I" {
value += 1
if let newS = UnicodeScalar(i + value) {
c = Character(newS)
}
}
if c >= "O" {
value += 1
if let newS = UnicodeScalar(i + value) {
c = Character(newS)
}
}
if c >= "Q" {
value += 1
if let newS = UnicodeScalar(i + value) {
c = Character(newS)
}
}
return String(c)
}
}
return nil
}
func licencesFound() -> [String] {
// First try to find licenses with format: 5-8 digits followed by optional letter
let precisePattern = /[1-9][0-9]{5,7}[ ]?[A-Za-z]?/
let preciseMatches = self.matches(of: precisePattern)
let preciseResults = preciseMatches.map { String(self[$0.range]).trimmingCharacters(in: .whitespaces) }
// If we find potential licenses with the precise pattern
if !preciseResults.isEmpty {
// Filter to only include those with trailing letters
let licensesWithLetters = preciseResults.filter {
let lastChar = $0.last
return lastChar != nil && lastChar!.isLetter
}
print("🎫 Found \(preciseResults.count) potential licenses, filtering to \(licensesWithLetters.count) with trailing letters")
// If we have licenses with letters, validate them
if !licensesWithLetters.isEmpty {
let validLicenses = licensesWithLetters.filter { $0.isLicenseNumber }
// If we have valid licenses, return the numeric part of each
if !validLicenses.isEmpty {
let numericLicenses = validLicenses.map { license -> String in
// Extract just the numeric part (all characters except the last letter)
if let lastChar = license.last, lastChar.isLetter {
return String(license.dropLast())
}
return license
}
if numericLicenses.isEmpty == false {
print("🎫 Found valid licenses: \(validLicenses), returning numeric parts: \(numericLicenses)")
return numericLicenses
}
}
}
}
// Fallback to just number pattern if we didn't find good matches
let numberPattern = /[1-9][0-9]{5,7}/
let numberMatches = self.matches(of: numberPattern)
let numberResults = numberMatches.map { String(self[$0.range]) }
print("🎫 Falling back to number-only pattern, found: \(numberResults)")
return numberResults
}
}
// MARK: - FFT Source Importing
extension String {
enum RegexStatic {
static let mobileNumber = /^(?:\+33|0033|0)[6-7](?:[ .-]?[0-9]{2}){4}$/
static let phoneNumber = /^(?:\+33|0033|0)[1-9](?:[ .-]?[0-9]{2}){4}$/
}
func isMobileNumber() -> Bool {
firstMatch(of: RegexStatic.mobileNumber) != nil
}
func isPhoneNumber() -> Bool {
firstMatch(of: RegexStatic.phoneNumber) != nil
}
func cleanSearchText() -> String {
// Create a character set of all punctuation except slashes and hyphens
var punctuationToRemove = CharacterSet.punctuationCharacters
punctuationToRemove.remove(charactersIn: "/-")
// Remove the unwanted punctuation
return self.components(separatedBy: punctuationToRemove)
.joined(separator: " ")
.trimmingCharacters(in: .whitespacesAndNewlines)
}
//april 04-2024 bug with accent characters / adobe / fft
mutating func replace(characters: [(Character, Character)]) {
for (targetChar, replacementChar) in characters {
self = String(self.map { $0 == targetChar ? replacementChar : $0 })
}
}
}
// MARK: - Player Names
extension StringProtocol {
var firstUppercased: String { prefix(1).uppercased() + dropFirst() }
var firstCapitalized: String { prefix(1).capitalized + dropFirst() }
}
// MARK: - todo clean up ??
extension LosslessStringConvertible {
var string: String { .init(self) }
}
extension String {
func createFile(_ withName: String = "temp", _ exportedFormat: ExportFormat = .rawText) -> URL {
let url = FileManager.default.temporaryDirectory
.appendingPathComponent(withName)
.appendingPathExtension(exportedFormat.suffix)
let string = self
try? FileManager.default.removeItem(at: url)
try? string.write(to: url, atomically: true, encoding: .utf8)
return url
}
}
extension String {
func toInt() -> Int? {
Int(self)
}
}
extension String : @retroactive Identifiable {
public var id: String { self }
}
extension String {
/// Parses the birthdate string into a `Date` based on multiple formats.
/// - Returns: A `Date` object if parsing is successful, or `nil` if the format is unrecognized.
func parseAsBirthdate() -> Date? {
let dateFormats = [
"yyyy-MM-dd", // Format for "1993-01-31"
"dd/MM/yyyy", // Format for "27/07/1992"
"dd/MM/yy" // Format for "27/07/92"
]
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX") // Ensure consistent parsing
for format in dateFormats {
dateFormatter.dateFormat = format
if let date = dateFormatter.date(from: self) {
return date // Return the parsed date if successful
}
}
return nil // Return nil if no format matches
}
/// Formats the birthdate string into "DD/MM/YYYY".
/// - Returns: A formatted birthdate string, or the original string if parsing fails.
func formattedAsBirthdate() -> String {
if let parsedDate = self.parseAsBirthdate() {
let outputFormatter = DateFormatter()
outputFormatter.dateFormat = "dd/MM/yyyy" // Desired output format
return outputFormatter.string(from: parsedDate)
}
return self // Return the original string if parsing fails
}
}

@ -0,0 +1,67 @@
//
// TeamRegistration+Extensions.swift
// PadelClub
//
// Created by Laurent Morvillier on 15/04/2025.
//
import Foundation
import SwiftUI
import PadelClubData
extension TeamRegistration {
func initialRoundColor() -> Color? {
if walkOut { return Color.logoRed }
if groupStagePosition != nil || wildCardGroupStage { return Color.blue }
if let initialRound = initialRound(), let colorHex = RoundRule.colors[safe: initialRound.index] {
return Color(uiColor: .init(fromHex: colorHex))
} else if wildCardBracket {
return Color.mint
} else {
return nil
}
}
func updateWeight(inTournamentCategory tournamentCategory: TournamentCategory) {
self.setWeight(from: self.players(), inTournamentCategory: tournamentCategory)
}
func updatePlayers(
_ players: Set<PlayerRegistration>,
inTournamentCategory tournamentCategory: TournamentCategory
) {
let previousPlayers = Set(unsortedPlayers())
players.forEach { player in
previousPlayers.forEach { oldPlayer in
if player.licenceId?.strippedLicense == oldPlayer.licenceId?.strippedLicense,
player.licenceId?.strippedLicense != nil
{
player.registeredOnline = oldPlayer.registeredOnline
player.coach = oldPlayer.coach
player.tournamentPlayed = oldPlayer.tournamentPlayed
player.points = oldPlayer.points
player.captain = oldPlayer.captain
player.assimilation = oldPlayer.assimilation
player.ligueName = oldPlayer.ligueName
}
}
}
let playersToRemove = previousPlayers.subtracting(players)
self.tournamentStore?.playerRegistrations.delete(contentOfs: Array(playersToRemove))
setWeight(from: Array(players), inTournamentCategory: tournamentCategory)
players.forEach { player in
player.teamRegistration = id
}
// do {
// try self.tournamentStore.playerRegistrations.addOrUpdate(contentOfs: players)
// } catch {
// Logger.error(error)
// }
}
}

@ -0,0 +1,445 @@
//
// Tournament+Extensions.swift
// PadelClub
//
// Created by Laurent Morvillier on 15/04/2025.
//
import Foundation
import SwiftUI
import PadelClubData
import LeStorage
extension Tournament {
func setupFederalSettings() {
teamSorting = tournamentLevel.defaultTeamSortingType
groupStageMatchFormat = groupStageSmartMatchFormat()
loserBracketMatchFormat = loserBracketSmartMatchFormat(5)
matchFormat = roundSmartMatchFormat(5)
entryFee = tournamentLevel.entryFee
registrationDateLimit = deadline(for: .inscription)
if enableOnlineRegistration, isAnimation() == false {
accountIsRequired = true
licenseIsRequired = true
}
}
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.dayDuration = lastTournamentWithSameBuild.dayDuration
self.teamCount = (lastTournamentWithSameBuild.teamCount / 2) * 2
self.enableOnlineRegistration = lastTournamentWithSameBuild.enableOnlineRegistration
}
func addTeam(_ players: Set<PlayerRegistration>, registrationDate: Date? = nil, name: String? = nil) -> TeamRegistration {
let team = TeamRegistration(tournament: id, registrationDate: registrationDate, name: name)
team.setWeight(from: Array(players), inTournamentCategory: tournamentCategory)
players.forEach { player in
player.teamRegistration = team.id
}
if isAnimation() {
if team.weight == 0 {
team.weight = unsortedTeams().count
}
}
return team
}
func addWildCardIfNeeded(_ count: Int, _ type: MatchType) {
let currentCount = selectedSortedTeams().filter({
if type == .bracket {
return $0.wildCardBracket
} else {
return $0.wildCardGroupStage
}
}).count
if currentCount < count {
let _diff = count - currentCount
addWildCard(_diff, type)
}
}
func addEmptyTeamRegistration(_ count: Int) {
guard let tournamentStore = self.tournamentStore else { return }
let teams = (0..<count).map { _ in
let team = TeamRegistration(tournament: id)
team.setWeight(from: [], inTournamentCategory: self.tournamentCategory)
return team
}
do {
try tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teams)
} catch {
Logger.error(error)
}
}
func addWildCard(_ count: Int, _ type: MatchType) {
let wcs = (0..<count).map { _ in
let team = TeamRegistration(tournament: id)
if type == .bracket {
team.wildCardBracket = true
} else {
team.wildCardGroupStage = true
}
team.setWeight(from: [], inTournamentCategory: self.tournamentCategory)
team.weight += 200_000
return team
}
do {
try self.tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: wcs)
} catch {
Logger.error(error)
}
}
func teamsRanked() -> [TeamRegistration] {
let selected = selectedSortedTeams().filter({ $0.finalRanking != nil })
return selected.sorted(by: \.finalRanking!, order: .ascending)
}
func playersWithoutValidLicense(in players: [PlayerRegistration], isImported: Bool) -> [PlayerRegistration] {
let licenseYearValidity = self.licenseYearValidity()
return players.filter({ player in
if player.isImported() {
// Player is marked as imported: check if the license is valid
return !player.isValidLicenseNumber(year: licenseYearValidity)
} else {
// Player is not imported: validate license and handle `isImported` flag for non-imported players
let noLicenseId = player.licenceId == nil || player.licenceId?.isEmpty == true
let invalidFormattedLicense = player.formattedLicense().isLicenseNumber == false
// If global `isImported` is true, check license number as well
let invalidLicenseForImportedFlag = isImported && !player.isValidLicenseNumber(year: licenseYearValidity)
return noLicenseId || invalidFormattedLicense || invalidLicenseForImportedFlag
}
})
}
func homonyms(in players: [PlayerRegistration]) -> [PlayerRegistration] {
players.filter({ $0.hasHomonym() })
}
func payIfNecessary() throws {
if self.payment != nil { return }
if let payment = Guard.main.paymentForNewTournament() {
self.payment = payment
DataStore.shared.tournaments.addOrUpdate(instance: self)
return
}
throw PaymentError.cantPayTournament
}
func cutLabelColor(index: Int?, teamCount: Int?) -> Color {
guard let index else { return Color.grayNotUniversal }
let _teamCount = teamCount ?? selectedSortedTeams().count
let groupStageCut = groupStageCut()
let bracketCut = bracketCut(teamCount: _teamCount, groupStageCut: groupStageCut)
if index < bracketCut {
return Color.mint
} else if index - bracketCut < groupStageCut && _teamCount > 0 {
return Color.indigo
} else {
return Color.grayNotUniversal
}
}
func isPlayerAgeInadequate(player: PlayerHolder) -> Bool {
guard let computedAge = player.computedAge else { return false }
if federalTournamentAge.isAgeValid(age: computedAge) == false {
return true
} else {
return false
}
}
func isPlayerRankInadequate(player: PlayerHolder) -> Bool {
guard let rank = player.getRank() else { return false }
let _rank = player.male ? rank : rank + PlayerRegistration.addon(for: rank, manMax: maleUnrankedValue ?? 0, womanMax: femaleUnrankedValue ?? 0)
if _rank <= tournamentLevel.minimumPlayerRank(category: tournamentCategory, ageCategory: federalTournamentAge) {
return true
} else {
return false
}
}
func inadequatePlayers(in players: [PlayerRegistration]) -> [PlayerRegistration] {
if startDate.isInCurrentYear() == false {
return []
}
return players.filter { player in
return isPlayerRankInadequate(player: player)
}
}
func ageInadequatePlayers(in players: [PlayerRegistration]) -> [PlayerRegistration] {
if startDate.isInCurrentYear() == false {
return []
}
return players.filter { player in
return isPlayerAgeInadequate(player: player)
}
}
func importTeams(_ teams: [FileImportManager.TeamHolder]) {
var teamsToImport = [TeamRegistration]()
let players = players().filter { $0.licenceId != nil }
teams.forEach { team in
if let previousTeam = team.previousTeam {
previousTeam.updatePlayers(team.players, inTournamentCategory: team.tournamentCategory)
teamsToImport.append(previousTeam)
} else {
var registrationDate = team.registrationDate
if let previousPlayer = players.first(where: { player in
let ids = team.players.compactMap({ $0.licenceId })
return ids.contains(player.licenceId!)
}), let previousTeamRegistrationDate = previousPlayer.team()?.registrationDate {
registrationDate = previousTeamRegistrationDate
}
let newTeam = addTeam(team.players, registrationDate: registrationDate, name: team.name)
if isAnimation() {
if newTeam.weight == 0 {
newTeam.weight = team.index(in: teams) ?? 0
}
}
teamsToImport.append(newTeam)
}
}
if let tournamentStore = self.tournamentStore {
tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teamsToImport)
tournamentStore.playerRegistrations.addOrUpdate(contentOfs: teams.flatMap { $0.players })
}
if state() == .build && groupStageCount > 0 && groupStageTeams().isEmpty {
setGroupStage(randomize: groupStageSortMode == .random)
}
}
func registrationIssues(selectedTeams: [TeamRegistration]) async -> Int {
let players : [PlayerRegistration] = unsortedPlayers()
let callDateIssue : [TeamRegistration] = selectedTeams.filter { $0.callDate != nil && isStartDateIsDifferentThanCallDate($0) }
let duplicates : [PlayerRegistration] = duplicates(in: players)
let problematicPlayers : [PlayerRegistration] = players.filter({ $0.sex == nil })
let inadequatePlayers : [PlayerRegistration] = inadequatePlayers(in: players)
let homonyms = homonyms(in: players)
let ageInadequatePlayers = ageInadequatePlayers(in: players)
let isImported = players.anySatisfy({ $0.isImported() })
let playersWithoutValidLicense : [PlayerRegistration] = playersWithoutValidLicense(in: players, isImported: isImported)
let playersMissing : [TeamRegistration] = selectedTeams.filter({ $0.unsortedPlayers().count < 2 })
let waitingList : [TeamRegistration] = waitingListTeams(in: selectedTeams, includingWalkOuts: true)
let waitingListInBracket = waitingList.filter({ $0.bracketPosition != nil })
let waitingListInGroupStage = waitingList.filter({ $0.groupStage != nil })
return callDateIssue.count + duplicates.count + problematicPlayers.count + inadequatePlayers.count + playersWithoutValidLicense.count + playersMissing.count + waitingListInBracket.count + waitingListInGroupStage.count + ageInadequatePlayers.count + homonyms.count
}
func updateRank(to newDate: Date?, forceRefreshLockWeight: Bool, providedSources: [CSVParser]?) async throws {
refreshRanking = true
#if DEBUG_TIME
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func updateRank()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif
guard let newDate else { return }
rankSourceDate = newDate
// Fetch current month data only once
var monthData = currentMonthData()
if monthData == nil {
async let lastRankWoman = SourceFileManager.shared.getUnrankValue(forMale: false, rankSourceDate: rankSourceDate)
async let lastRankMan = SourceFileManager.shared.getUnrankValue(forMale: true, rankSourceDate: rankSourceDate)
let formatted = URL.importDateFormatter.string(from: newDate)
let newMonthData = MonthData(monthKey: formatted)
newMonthData.maleUnrankedValue = await lastRankMan
newMonthData.femaleUnrankedValue = await lastRankWoman
do {
try DataStore.shared.monthData.addOrUpdate(instance: newMonthData)
} catch {
Logger.error(error)
}
monthData = newMonthData
}
let lastRankMan = monthData?.maleUnrankedValue
let lastRankWoman = monthData?.femaleUnrankedValue
var chunkedParsers: [CSVParser] = []
if let providedSources {
chunkedParsers = providedSources
} else {
// Fetch only the required files
let dataURLs = SourceFileManager.shared.allFiles.filter { $0.dateFromPath == newDate }
guard !dataURLs.isEmpty else { return } // Early return if no files found
let sources = dataURLs.map { CSVParser(url: $0) }
chunkedParsers = try await chunkAllSources(sources: sources, size: 10000)
}
let players = unsortedPlayers()
try await players.concurrentForEach { player in
let lastRank = (player.sex == .female) ? lastRankWoman : lastRankMan
try await player.updateRank(from: chunkedParsers, lastRank: lastRank)
player.setComputedRank(in: self)
}
if providedSources == nil {
try chunkedParsers.forEach { chunk in
try FileManager.default.removeItem(at: chunk.url)
}
}
try tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: players)
let unsortedTeams = unsortedTeams()
unsortedTeams.forEach { team in
team.setWeight(from: team.players(), inTournamentCategory: tournamentCategory)
if forceRefreshLockWeight {
team.lockedWeight = team.weight
}
}
try tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: unsortedTeams)
refreshRanking = false
}
}
extension Tournament {
static func newEmptyInstance() -> Tournament {
let lastDataSource: String? = DataStore.shared.appSettings.lastDataSource
var _mostRecentDateAvailable: Date? {
guard let lastDataSource else { return nil }
return URL.importDateFormatter.date(from: lastDataSource)
}
let rankSourceDate = _mostRecentDateAvailable
return Tournament(rankSourceDate: rankSourceDate)
}
}
extension Tournament: FederalTournamentHolder {
func tournamentTitle(_ displayStyle: DisplayStyle, forBuild build: any TournamentBuildHolder) -> String {
if isAnimation() {
if let name {
return name.trunc(length: DeviceHelper.charLength())
} else if build.age == .unlisted, build.category == .unlisted {
return build.level.localizedLevelLabel(.title)
} else {
return build.level.localizedLevelLabel(displayStyle)
}
}
return build.level.localizedLevelLabel(displayStyle)
}
var codeClub: String? {
club()?.code
}
var holderId: String { id }
func clubLabel() -> String {
locationLabel()
}
func subtitleLabel(forBuild build: any TournamentBuildHolder) -> String {
if isAnimation() {
if displayAgeAndCategory(forBuild: build) == false {
return [build.category.localizedCategoryLabel(ageCategory: build.age), build.age.localizedFederalAgeLabel()].filter({ $0.isEmpty == false }).joined(separator: " ")
} else if name != nil {
return build.level.localizedLevelLabel(.title)
} else {
return ""
}
} else {
return subtitle()
}
}
var tournaments: [any TournamentBuildHolder] {
[
self
]
}
var dayPeriod: DayPeriod {
let day = startDate.get(.weekday)
switch day {
case 2...6:
return .week
default:
return .weekend
}
}
func displayAgeAndCategory(forBuild build: any TournamentBuildHolder) -> Bool {
if isAnimation() {
if let name, name.count < DeviceHelper.maxCharacter() {
return true
} else if build.age == .unlisted, build.category == .unlisted {
return true
} else {
return DeviceHelper.isBigScreen()
}
}
return true
}
}
extension Tournament: TournamentBuildHolder {
public func buildHolderTitle(_ displayStyle: DisplayStyle) -> String {
tournamentTitle(.short)
}
public var category: TournamentCategory {
tournamentCategory
}
public var level: TournamentLevel {
tournamentLevel
}
public var age: FederalTournamentAge {
federalTournamentAge
}
}
//extension Tournament {
// func deadline(for type: TournamentDeadlineType) -> Date? {
// guard [.p500, .p1000, .p1500, .p2000].contains(tournamentLevel) else { return nil }
//
// let daysOffset = type.daysOffset(level: tournamentLevel)
// if let date = Calendar.current.date(byAdding: .day, value: daysOffset, to: startDate) {
// let startOfDay = Calendar.current.startOfDay(for: date)
// return Calendar.current.date(byAdding: type.timeOffset, to: startOfDay)
// }
// return nil
// }
//}

@ -1,182 +0,0 @@
//
// URL+Extensions.swift
// PadelClub
//
// Created by Razmig Sarkissian on 01/03/2024.
//
import Foundation
extension URL {
static var savedDateFormatter: DateFormatter = {
let df = DateFormatter()
df.dateFormat = "DD/MM/yyyy"
return df
}()
static var importDateFormatter: DateFormatter = {
let df = DateFormatter()
df.dateFormat = "MM-yyyy"
return df
}()
var dateFromPath: Date {
let found = deletingPathExtension().path().components(separatedBy: "-").suffix(2).joined(separator: "-")
if let date = URL.importDateFormatter.date(from: found) {
return date
} else {
return Date()
}
}
var index: Int {
if let i = path().dropLast(12).last?.wholeNumberValue {
return i
}
return 0
}
var manData: Bool {
path().contains("MESSIEURS")
}
var womanData: Bool {
path().contains("DAMES")
}
static var seed: URL? {
Bundle.main.url(forResource: "SeedData", withExtension: nil)
}
}
extension URL {
func creationDate() -> Date? {
// Use FileManager to retrieve the file attributes
do {
let fileAttributes = try FileManager.default.attributesOfItem(atPath: self.path())
// Access the creationDate from the file attributes
if let creationDate = fileAttributes[.creationDate] as? Date {
print("File creationDate: \(creationDate)")
return creationDate
} else {
print("creationDate not found.")
}
} catch {
print("Error retrieving file attributes: \(error.localizedDescription)")
}
return nil
}
func fftImportingStatus() -> Int? {
// Read the contents of the file
guard let fileContents = try? String(contentsOfFile: path(), encoding: .utf8) else {
return nil
}
// Split the contents by newline characters
let lines = fileContents.components(separatedBy: .newlines)
//0 means no need to reimport, just recalc
//1 or missing means re-import
if let line = lines.first(where: {
$0.hasPrefix("import-status:")
}) {
return Int(line.replacingOccurrences(of: "import-status:", with: ""))
}
return nil
}
func fftImportingMaleUnrankValue() -> Int? {
// Read the contents of the file
guard let fileContents = try? String(contentsOfFile: path(), encoding: .utf8) else {
return nil
}
// Split the contents by newline characters
let lines = fileContents.components(separatedBy: .newlines)
if let line = lines.first(where: {
$0.hasPrefix("unrank-male-value:")
}) {
return Int(line.replacingOccurrences(of: "unrank-male-value:", with: ""))
}
return nil
}
func fileModelIdentifier() -> String? {
// Read the contents of the file
guard let fileContents = try? String(contentsOfFile: path(), encoding: .utf8) else {
return nil
}
// Split the contents by newline characters
let lines = fileContents.components(separatedBy: .newlines)
if let line = lines.first(where: {
$0.hasPrefix("file-model-version:")
}) {
return line.replacingOccurrences(of: "file-model-version:", with: "")
}
return nil
}
func fftImportingUncomplete() -> Int? {
// Read the contents of the file
guard let fileContents = try? String(contentsOfFile: path(), encoding: .utf8) else {
return nil
}
// Split the contents by newline characters
let lines = fileContents.components(separatedBy: .newlines)
if let line = lines.first(where: {
$0.hasPrefix("max-players:")
}) {
return Int(line.replacingOccurrences(of: "max-players:", with: ""))
}
return nil
}
func getUnrankedValue() -> Int? {
// Read the contents of the file
guard let fileContents = try? String(contentsOfFile: path(), encoding: .utf8) else {
return nil
}
// Split the contents by newline characters
let lines = fileContents.components(separatedBy: .newlines)
// Get the last non-empty line
var lastLine: String?
for line in lines.reversed() {
let trimmedLine = line.trimmingCharacters(in: .whitespacesAndNewlines)
if !trimmedLine.isEmpty {
lastLine = trimmedLine
break
}
}
guard let rankString = lastLine?.components(separatedBy: ";").dropFirst().first, let rank = Int(rankString) else {
return nil
}
// Define the regular expression pattern
let pattern = "\\b\(NSRegularExpression.escapedPattern(for: rankString))\\b"
// Create the regular expression object
guard let regex = try? NSRegularExpression(pattern: pattern) else {
return nil
}
// Get the matches
let matches = regex.matches(in: fileContents, range: NSRange(fileContents.startIndex..., in: fileContents))
// Return the count of matches
return matches.count + rank - 1
}
}

@ -8,6 +8,7 @@
import SwiftUI import SwiftUI
import LeStorage import LeStorage
import TipKit import TipKit
import PadelClubData
@main @main
struct PadelClubApp: App { struct PadelClubApp: App {

@ -1,254 +0,0 @@
//
// ContactManager.swift
// Padel Tournament
//
// Created by Razmig Sarkissian on 19/09/2023.
//
import Foundation
import SwiftUI
import MessageUI
import LeStorage
enum ContactManagerError: LocalizedError {
case mailFailed
case mailNotSent //no network no error
case messageFailed
case messageNotSent //no network no error
case calendarAccessDenied
case calendarEventSaveFailed
case noCalendarAvailable
case uncalledTeams([TeamRegistration])
var localizedDescription: String {
switch self {
case .mailFailed:
return "Le mail n'a pas été envoyé"
case .mailNotSent:
return "Le mail est dans la boîte d'envoi de l'app Mail. Vérifiez son état dans l'app Mail avant d'essayer de le renvoyer."
case .messageFailed:
return "Le SMS n'a pas été envoyé"
case .messageNotSent:
return "Le SMS n'a pas été envoyé"
case .uncalledTeams(let array):
let verb = array.count > 1 ? "peuvent" : "peut"
return "Attention, \(array.count) équipe\(array.count.pluralSuffix) ne \(verb) pas être contacté par la méthode choisie"
case .calendarAccessDenied:
return "Padel Club n'a pas accès à votre calendrier"
case .calendarEventSaveFailed:
return "Padel Club n'a pas réussi à sauver ce tournoi dans votre calendrier"
case .noCalendarAvailable:
return "Padel Club n'a pas réussi à trouver un calendrier pour y inscrire ce tournoi"
}
}
static func getNetworkErrorMessage(sentError: ContactManagerError?, networkMonitorConnected: Bool) -> String {
var errors: [String] = []
if networkMonitorConnected == false {
errors.append("L'appareil n'est pas connecté à internet.")
}
if let sentError {
errors.append(sentError.localizedDescription)
}
return errors.joined(separator: "\n")
}
}
enum ContactType: Identifiable {
case mail(date: Date?, recipients: [String]?, bccRecipients: [String]?, body: String?, subject: String?, tournamentBuild: TournamentBuild?)
case message(date: Date?, recipients: [String]?, body: String?, tournamentBuild: TournamentBuild?)
var id: Int {
switch self {
case .message: return 0
case .mail: return 1
}
}
}
extension ContactType {
static let defaultCustomMessage: String =
"""
Il est conseillé de vous présenter 10 minutes avant de jouer.\n\nMerci de me confirmer votre présence avec votre nom et de prévenir votre partenaire.
"""
static let defaultAvailablePaymentMethods: String = "Règlement possible par chèque ou espèces."
static func callingCustomMessage(source: String? = nil, tournament: Tournament?, startDate: Date?, roundLabel: String) -> String {
let tournamentCustomMessage = source ?? DataStore.shared.user.summonsMessageBody ?? defaultCustomMessage
let clubName = tournament?.clubName ?? ""
var text = tournamentCustomMessage
let date = startDate ?? tournament?.startDate ?? Date()
if let tournament {
text = text.replacingOccurrences(of: "#titre", with: tournament.tournamentTitle(.title, hideSenior: true))
text = text.replacingOccurrences(of: "#prix", with: tournament.entryFeeMessage)
}
text = text.replacingOccurrences(of: "#club", with: clubName)
text = text.replacingOccurrences(of: "#manche", with: roundLabel.lowercased())
text = text.replacingOccurrences(of: "#jour", with: "\(date.formatted(Date.FormatStyle().weekday(.wide).day().month(.wide)))")
text = text.replacingOccurrences(of: "#horaire", with: "\(date.formatted(Date.FormatStyle().hour().minute()))")
let signature = DataStore.shared.user.getSummonsMessageSignature() ?? DataStore.shared.user.defaultSignature(tournament)
text = text.replacingOccurrences(of: "#signature", with: signature)
return text
}
static func callingMessage(tournament: Tournament?, startDate: Date?, roundLabel: String, matchFormat: MatchFormat?, reSummon: Bool = false) -> String {
let useFullCustomMessage = DataStore.shared.user.summonsUseFullCustomMessage
if useFullCustomMessage {
return callingCustomMessage(tournament: tournament, startDate: startDate, roundLabel: roundLabel)
}
let date = startDate ?? tournament?.startDate ?? Date()
let clubName = tournament?.clubName ?? ""
let message = DataStore.shared.user.summonsMessageBody ?? defaultCustomMessage
let signature = DataStore.shared.user.getSummonsMessageSignature() ?? DataStore.shared.user.defaultSignature(tournament)
let localizedCalled = "convoqué" + (tournament?.tournamentCategory == .women ? "e" : "") + "s"
var entryFeeMessage: String? {
(DataStore.shared.user.summonsDisplayEntryFee) ? tournament?.entryFeeMessage : nil
}
var linkMessage: String? {
if let tournament, tournament.isPrivate == false, let shareLink = tournament.shareURL(.matches)?.absoluteString {
return "Vous pourrez suivre tous les résultats de ce tournoi sur le site :\n\n".appending(shareLink)
} else {
return nil
}
}
var computedMessage: String {
[entryFeeMessage, message, linkMessage].compacted().map { $0.trimmedMultiline }.joined(separator: "\n\n")
}
let intro = reSummon ? "Suite à des forfaits, vous êtes finalement" : "Vous êtes"
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)"
} else {
return "Bonjour,\n\n\(intro) \(localizedCalled) \(roundLabel) au \(clubName) le \(date.formatted(Date.FormatStyle().weekday(.wide).day().month(.wide))) à \(date.formatted(Date.FormatStyle().hour().minute())).\n\nMerci de confirmer en répondant à ce message et de prévenir votre partenaire !\n\n\(signature)"
}
}
}
struct MessageComposeView: UIViewControllerRepresentable {
typealias Completion = (_ result: MessageComposeResult) -> Void
static var canSendText: Bool { MFMessageComposeViewController.canSendText() }
let recipients: [String]?
let body: String?
let completion: Completion?
func makeUIViewController(context: Context) -> UIViewController {
guard Self.canSendText else {
let errorView = ContentUnavailableView("Aucun compte de messagerie", systemImage: "xmark", description: Text("Aucun compte de messagerie n'est configuré sur cet appareil."))
return UIHostingController(rootView: errorView)
}
let controller = MFMessageComposeViewController()
controller.messageComposeDelegate = context.coordinator
controller.recipients = recipients
controller.body = body
return controller
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(completion: self.completion)
}
class Coordinator: NSObject, MFMessageComposeViewControllerDelegate {
private let completion: Completion?
public init(completion: Completion?) {
self.completion = completion
}
public func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) {
controller.dismiss(animated: true, completion: {
self.completion?(result)
})
}
}
}
struct MailComposeView: UIViewControllerRepresentable {
typealias Completion = (_ result: MFMailComposeResult) -> Void
static var canSendMail: Bool {
if let mailURL = URL(string: "mailto:?to=jap@padelclub.com") {
let mailConfigured = UIApplication.shared.canOpenURL(mailURL)
return mailConfigured && MFMailComposeViewController.canSendMail()
} else {
return MFMailComposeViewController.canSendMail()
}
}
let recipients: [String]?
let bccRecipients: [String]?
let body: String?
let subject: String?
var attachmentURL: URL?
let completion: Completion?
func makeUIViewController(context: Context) -> UIViewController {
guard Self.canSendMail else {
let errorView = ContentUnavailableView("Aucun compte mail", systemImage: "xmark", description: Text("Aucun compte mail n'est configuré sur cet appareil."))
return UIHostingController(rootView: errorView)
}
let controller = MFMailComposeViewController()
controller.mailComposeDelegate = context.coordinator
controller.setToRecipients(recipients)
controller.setBccRecipients(bccRecipients)
if let attachmentURL {
do {
let attachmentData = try Data(contentsOf: attachmentURL)
controller.addAttachmentData(attachmentData, mimeType: "application/zip", fileName: "backup.zip")
} catch {
print("Could not attach file: \(error)")
}
}
if let body {
controller.setMessageBody(body, isHTML: false)
}
if let subject {
controller.setSubject(subject)
}
return controller
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(completion: self.completion)
}
class Coordinator: NSObject, MFMailComposeViewControllerDelegate {
private let completion: Completion?
public init(completion: Completion?) {
self.completion = completion
}
public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true, completion: {
self.completion?(result)
})
}
}
}

@ -1,12 +0,0 @@
//
// Key.swift
// PadelClub
//
// Created by Laurent Morvillier on 30/04/2024.
//
import Foundation
enum CryptoKey: String {
case pass = "Aa9QDV1G5MP9ijF2FTFasibNbS/Zun4qXrubIL2P+Ik="
}

@ -1,64 +0,0 @@
//
// DisplayContext.swift
// PadelClub
//
// Created by Razmig Sarkissian on 20/03/2024.
//
import Foundation
import UIKit
enum DisplayContext {
case addition
case edition
case lockedForEditing
case selection
}
enum DisplayStyle {
case title
case wide
case short
}
enum SummoningDisplayContext {
case footer
case menu
}
struct DeviceHelper {
static func isBigScreen() -> Bool {
switch UIDevice.current.userInterfaceIdiom {
case .pad: // iPads
return true
case .phone: // iPhones (you can add more cases here for large vs small phones)
if UIScreen.main.bounds.size.width > 375 { // iPhone X, 11, 12, 13 Pro Max etc.
return true // large phones
} else {
return false // smaller phones
}
default:
return false // Other devices (Apple Watch, TV, etc.)
}
}
static func maxCharacter() -> Int {
switch UIDevice.current.userInterfaceIdiom {
case .pad: // iPads
return 30
case .phone: // iPhones (you can add more cases here for large vs small phones)
if UIScreen.main.bounds.size.width > 375 { // iPhone X, 11, 12, 13 Pro Max etc.
return 15 // large phones
} else {
return 9 // smaller phones
}
default:
return 9 // Other devices (Apple Watch, TV, etc.)
}
}
static func charLength() -> Int {
isBigScreen() ? 0 : 15
}
}

@ -1,37 +0,0 @@
//
// ExportFormat.swift
// PadelClub
//
// Created by Razmig Sarkissian on 19/07/2024.
//
import Foundation
enum ExportFormat: Int, Identifiable, CaseIterable {
var id: Int { self.rawValue }
case rawText
case csv
var suffix: String {
switch self {
case .rawText:
return "txt"
case .csv:
return "csv"
}
}
func separator() -> String {
switch self {
case .rawText:
return " "
case .csv:
return ";"
}
}
func newLineSeparator(_ count: Int = 1) -> String {
return Array(repeating: "\n", count: count).joined()
}
}

@ -8,6 +8,7 @@
import Foundation import Foundation
import LeStorage import LeStorage
import SwiftUI import SwiftUI
import PadelClubData
enum FileImportManagerError: LocalizedError { enum FileImportManagerError: LocalizedError {
case unknownFormat case unknownFormat

@ -9,6 +9,7 @@ import Foundation
import UIKit import UIKit
import WebKit import WebKit
import PDFKit import PDFKit
import PadelClubData
class HtmlGenerator: ObservableObject { class HtmlGenerator: ObservableObject {

@ -6,6 +6,7 @@
// //
import Foundation import Foundation
import PadelClubData
enum HtmlService { enum HtmlService {

@ -7,6 +7,7 @@
import Foundation import Foundation
import CoreLocation import CoreLocation
import PadelClubData
class NetworkFederalService { class NetworkFederalService {
struct HttpCommand: Decodable { struct HttpCommand: Decodable {

@ -6,6 +6,7 @@
// //
import Foundation import Foundation
import PadelClubData
class NetworkManager { class NetworkManager {
static let shared: NetworkManager = NetworkManager() static let shared: NetworkManager = NetworkManager()

@ -1,28 +0,0 @@
//
// NetworkManagerError.swift
// PadelClub
//
// Created by Razmig Sarkissian on 03/03/2024.
//
import Foundation
enum NetworkManagerError: LocalizedError {
case maintenance
case fileNotYetAvailable
case mailFailed
case mailNotSent //no network no error
case messageFailed
case messageNotSent //no network no error
case fileNotModified
case fileNotDownloaded(Int)
var errorDescription: String? {
switch self {
case .maintenance:
return "Le site de la FFT est en maintenance"
default:
return String(describing: self)
}
}
}

@ -7,6 +7,7 @@
import Foundation import Foundation
import LeStorage import LeStorage
import PadelClubData
class RefundService { class RefundService {
static func processRefund(teamRegistrationId: String) async throws -> RefundResponse { static func processRefund(teamRegistrationId: String) async throws -> RefundResponse {

@ -1,47 +0,0 @@
//
// PListReader.swift
// PadelClub
//
// Created by Laurent Morvillier on 06/05/2024.
//
import Foundation
class PListReader {
static func dictionary(plist: String) -> [String: Any]? {
if let plistPath = Bundle.main.path(forResource: plist, ofType: "plist") {
// Read plist file into Data
if let plistData = FileManager.default.contents(atPath: plistPath) {
do {
// Deserialize plist data into a dictionary
if let plistDictionary = try PropertyListSerialization.propertyList(from: plistData, options: [], format: nil) as? [String: Any] {
return plistDictionary
}
} catch {
print("Error reading plist data: \(error)")
}
} else {
print("Failed to read plist file at path: \(plistPath)")
}
} else {
print("Plist file 'Data.plist' not found in bundle")
}
return nil
}
static func readString(plist: String, key: String) -> String? {
if let dictionary = self.dictionary(plist: plist) {
return dictionary[key] as? String
}
return nil
}
static func readBool(plist: String, key: String) -> Bool? {
if let dictionary = self.dictionary(plist: plist) {
return dictionary[key] as? Bool
}
return nil
}
}

File diff suppressed because it is too large Load Diff

@ -1,143 +0,0 @@
//
// Patcher.swift
// PadelClub
//
// Created by Laurent Morvillier on 21/06/2024.
//
import Foundation
import LeStorage
enum ManualPatch: String {
case disconnect
var id: String {
return "padelclub.app.manual.patch.\(self.rawValue)"
}
}
class ManualPatcher {
static func patchIfPossible(_ patch: ManualPatch) -> Bool {
if UserDefaults.standard.value(forKey: patch.id) == nil {
do {
Logger.log(">>> Patches \(patch.rawValue)...")
let result = try self._applyPatch(patch)
UserDefaults.standard.setValue(true, forKey: patch.id)
return result
} catch {
Logger.error(error)
}
}
return false
}
fileprivate static func _applyPatch(_ patch: ManualPatch) throws -> Bool {
switch patch {
case .disconnect:
let rawToken = try? StoreCenter.main.rawTokenShouldNotBeUsed()
if StoreCenter.main.userName != nil || StoreCenter.main.userId != nil || rawToken != nil {
DataStore.shared.disconnect()
return true
} else {
return false
}
}
}
}
enum PatchError: Error {
case patchError(message: String)
}
enum Patch: String, CaseIterable {
case cleanLogs
case syncUpgrade
case updateTournaments
case cleanPurchaseApiCalls = "cleanPurchaseApiCalls_2"
var id: String {
return "padelclub.app.patch.\(self.rawValue)"
}
}
class AutomaticPatcher {
static func applyAllWhenApplicable() {
for patch in Patch.allCases {
self.patchIfPossible(patch)
}
}
static func patchIfPossible(_ patch: Patch) {
if UserDefaults.standard.value(forKey: patch.id) == nil {
do {
Logger.log(">>> Patches \(patch.rawValue)...")
try self._applyPatch(patch)
UserDefaults.standard.setValue(true, forKey: patch.id)
} catch {
Logger.error(error)
}
}
}
fileprivate static func _applyPatch(_ patch: Patch) throws {
switch patch {
case .cleanLogs: self._cleanLogs()
case .syncUpgrade: self._syncUpgrade()
case .updateTournaments: self._updateTournaments()
case .cleanPurchaseApiCalls: self._cleanPurchaseApiCalls()
}
}
fileprivate static func _cleanLogs() {
StoreCenter.main.resetLoggingCollections()
}
fileprivate static func _syncUpgrade() {
for tournament in DataStore.shared.tournaments {
let id = tournament.id
guard let store = TournamentLibrary.shared.store(tournamentId: tournament.id) else { continue }
for round in store.rounds {
round.storeId = id
}
store.rounds.addOrUpdate(contentOfs: store.rounds)
for groupStage in store.groupStages {
groupStage.storeId = id
}
store.groupStages.addOrUpdate(contentOfs: store.groupStages)
for teamRegistration in store.teamRegistrations {
teamRegistration.storeId = id
}
store.teamRegistrations.addOrUpdate(contentOfs: store.teamRegistrations)
for pr in store.playerRegistrations {
pr.storeId = id
}
store.playerRegistrations.addOrUpdate(contentOfs: store.playerRegistrations)
for match in store.matches {
match.storeId = id
}
store.matches.addOrUpdate(contentOfs: store.matches)
for ts in store.teamScores {
ts.storeId = id
}
store.teamScores.addOrUpdate(contentOfs: store.teamScores)
for ms in store.matchSchedulers {
ms.storeId = id
}
store.matchSchedulers.addOrUpdate(contentOfs: store.matchSchedulers)
}
}
fileprivate static func _updateTournaments() {
DataStore.shared.tournaments.addOrUpdate(contentOfs: DataStore.shared.tournaments)
}
fileprivate static func _cleanPurchaseApiCalls() {
StoreCenter.main.resetApiCalls(type: Purchase.self)
}
}

@ -1,270 +0,0 @@
//
// SourceFileManager.swift
// PadelClub
//
// Created by Razmig Sarkissian on 01/03/2024.
//
import Foundation
import LeStorage
class SourceFileManager {
static let shared = SourceFileManager()
init() {
createDirectoryIfNeeded(directoryURL: rankingSourceDirectory)
#if targetEnvironment(simulator)
createDirectoryIfNeeded(directoryURL: anonymousSourceDirectory)
#endif
}
let rankingSourceDirectory : URL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appending(path: "rankings")
let anonymousSourceDirectory : URL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appending(path: "anonymous")
func createDirectoryIfNeeded(directoryURL: URL) {
let fileManager = FileManager.default
do {
// Check if the directory exists
if !fileManager.fileExists(atPath: directoryURL.path) {
// Directory does not exist, create it
try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil)
// print("Directory created at: \(directoryURL)")
} else {
// print("Directory already exists at: \(directoryURL)")
}
} catch {
print("Error: \(error)")
}
}
var lastDataSource: String? {
DataStore.shared.appSettings.lastDataSource
}
func lastDataSourceDate() -> Date? {
guard let lastDataSource else { return nil }
return URL.importDateFormatter.date(from: lastDataSource)
}
func fetchData() async {
await fetchData(fromDate: Date())
// if let mostRecent = mostRecentDateAvailable, let current = Calendar.current.date(byAdding: .month, value: 1, to: mostRecent), current > mostRecent {
// await fetchData(fromDate: current)
// } else {
// }
}
func _removeAllData(fromDate current: Date) {
let lastStringDate = URL.importDateFormatter.string(from: current)
let files = ["MESSIEURS", "MESSIEURS-2", "MESSIEURS-3", "MESSIEURS-4", "DAMES"]
files.forEach { fileName in
NetworkManager.shared.removeRankingData(lastDateString: lastStringDate, fileName: fileName)
}
}
func exportToCSV(_ prefix: String = "", players: [FederalPlayer], sourceFileType: SourceFile, date: Date) {
let lastDateString = URL.importDateFormatter.string(from: date)
let dateString = [prefix, "CLASSEMENT-PADEL", sourceFileType.rawValue, lastDateString].filter({ $0.isEmpty == false }).joined(separator: "-") + "." + "csv"
let documentsUrl:URL = (FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first as URL?)!
let destinationFileUrl = documentsUrl.appendingPathComponent("\(dateString)")
var csvText : String = ""
for player in players {
csvText.append(player.exportToCSV() + "\n")
}
do {
try csvText.write(to: destinationFileUrl, atomically: true, encoding: .utf8)
print("CSV file exported successfully.")
} catch {
print("Error writing CSV file:", error)
Logger.error(error)
}
}
actor SourceFileDownloadTracker {
var _downloadedFileStatus : Int? = nil
func updateIfNecessary(with successState: Int?) {
if successState != nil && (_downloadedFileStatus == nil || _downloadedFileStatus == 0) {
_downloadedFileStatus = successState
}
}
func getDownloadedFileStatus() -> Int? {
return _downloadedFileStatus
}
}
//return nil if no new files
//return 1 if new file to import
//return 0 if new file just to re-calc static data, no need to re-import
@discardableResult
func fetchData(fromDate current: Date) async -> Int? {
let lastStringDate = URL.importDateFormatter.string(from: current)
let files = ["MESSIEURS", "MESSIEURS-2", "MESSIEURS-3", "MESSIEURS-4", "DAMES"]
let sourceFileDownloadTracker = SourceFileDownloadTracker()
do {
try await withThrowingTaskGroup(of: Void.self) { group in // Mark 1
for file in files {
group.addTask { [sourceFileDownloadTracker] in
let success = try await NetworkManager.shared.downloadRankingData(lastDateString: lastStringDate, fileName: file)
await sourceFileDownloadTracker.updateIfNecessary(with: success)
}
}
try await group.waitForAll()
}
// if current < Date() {
// if let nextCurrent = Calendar.current.date(byAdding: .month, value: 1, to: current) {
// await fetchData(fromDate: nextCurrent)
// }
// }
} catch {
print("downloadRankingData", error)
if mostRecentDateAvailable == nil {
if let previousDate = Calendar.current.date(byAdding: .month, value: -1, to: current) {
await fetchData(fromDate: previousDate)
}
}
}
let downloadedFileStatus = await sourceFileDownloadTracker.getDownloadedFileStatus()
return downloadedFileStatus
}
func getAllFiles(initialDate: String = "08-2022") async {
let dates = monthsBetweenDates(startDateString: initialDate, endDateString: Date().monthYearFormatted)
.compactMap {
URL.importDateFormatter.date(from: $0)
}
.filter { date in
allFiles.contains(where: { $0.dateFromPath == date }) == false
}
try? await dates.concurrentForEach { date in
await self.fetchData(fromDate: date)
}
}
func monthsBetweenDates(startDateString: String, endDateString: String) -> [String] {
let dateFormatter = URL.importDateFormatter
guard let startDate = dateFormatter.date(from: startDateString),
let endDate = dateFormatter.date(from: endDateString) else {
return []
}
var months: [String] = []
var currentDate = startDate
let calendar = Calendar.current
while currentDate <= endDate {
let monthString = dateFormatter.string(from: currentDate)
months.append(monthString)
guard let nextMonthDate = calendar.date(byAdding: .month, value: 1, to: currentDate) else {
break
}
currentDate = nextMonthDate
}
return months
}
func getUnrankValue(forMale: Bool, rankSourceDate: Date?) -> Int? {
let _rankSourceDate = rankSourceDate ?? mostRecentDateAvailable
let urls = allFiles(forMale).filter { $0.dateFromPath == _rankSourceDate }
return urls.compactMap { $0.getUnrankedValue() }.sorted().last
}
var mostRecentDateAvailable: Date? {
allFiles(false).first?.dateFromPath
}
func removeAllFilesFromServer() {
let allFiles = try! FileManager.default.contentsOfDirectory(at: rankingSourceDirectory, includingPropertiesForKeys: nil)
allFiles.filter { $0.pathExtension == "csv" }.forEach { url in
try? FileManager.default.removeItem(at: url)
}
}
func anonymousFiles() -> [URL] {
let allJSONFiles = try! FileManager.default.contentsOfDirectory(at: anonymousSourceDirectory, includingPropertiesForKeys: nil).filter({ url in
url.pathExtension == "csv"
})
return allJSONFiles
}
func jsonFiles() -> [URL] {
let allJSONFiles = try! FileManager.default.contentsOfDirectory(at: rankingSourceDirectory, includingPropertiesForKeys: nil).filter({ url in
url.pathExtension == "json"
})
return allJSONFiles
}
var allFiles: [URL] {
let allFiles = try! FileManager.default.contentsOfDirectory(at: rankingSourceDirectory, includingPropertiesForKeys: nil).filter({ url in
url.pathExtension == "csv"
})
return (allFiles + (Bundle.main.urls(forResourcesWithExtension: "csv", subdirectory: nil) ?? [])).sorted { $0.dateFromPath == $1.dateFromPath ? $0.index < $1.index : $0.dateFromPath > $1.dateFromPath }
}
func allFiles(_ isManPlayer: Bool) -> [URL] {
allFiles.filter({ url in
url.path().contains(isManPlayer ? SourceFile.messieurs.rawValue : SourceFile.dames.rawValue)
})
}
func allFilesSortedByDate(_ isManPlayer: Bool) -> [URL] {
return allFiles(isManPlayer)
}
static func isDateAfterUrlImportDate(date: Date, dateString: String) -> Bool {
guard let importDate = URL.importDateFormatter.date(from: dateString) else {
return false
}
return importDate.isEarlierThan(date)
}
static func getSortOption() -> [SortOption] {
return SortOption.allCases
}
}
enum SourceFile: String, CaseIterable {
case dames = "DAMES"
case messieurs = "MESSIEURS"
var filesFromServer: [URL] {
let rankingSourceDirectory = SourceFileManager.shared.rankingSourceDirectory
let allFiles = try! FileManager.default.contentsOfDirectory(at: rankingSourceDirectory, includingPropertiesForKeys: nil)
return allFiles.filter{$0.pathExtension == "csv" && $0.path().contains(rawValue)}
}
func currentURLs(importingDate: Date) -> [URL] {
var files = Bundle.main.urls(forResourcesWithExtension: "csv", subdirectory: nil)?.filter({ url in
url.path().contains(rawValue)
}) ?? []
files.append(contentsOf: filesFromServer)
return files.filter({ $0.dateFromPath == importingDate })
}
var isMan: Bool {
switch self {
case .dames:
return false
default:
return true
}
}
}

@ -6,6 +6,7 @@
// //
import Foundation import Foundation
import PadelClubData
typealias LineIterator = AsyncLineSequence<URL.AsyncBytes>.AsyncIterator typealias LineIterator = AsyncLineSequence<URL.AsyncBytes>.AsyncIterator
struct Line: Identifiable { struct Line: Identifiable {

@ -7,6 +7,7 @@
import Foundation import Foundation
import TipKit import TipKit
import PadelClubData
struct PadelBeachExportTip: Tip { struct PadelBeachExportTip: Tip {
var title: Text { var title: Text {

@ -1,95 +0,0 @@
//
// URLs.swift
// PadelClub
//
// Created by Laurent Morvillier on 22/04/2024.
//
import Foundation
enum URLs: String, Identifiable {
// case httpScheme = "https://"
#if DEBUG
case activationHost = "http://127.0.0.1:8000"
case main = "http://127.0.0.1:8000/"
// case api = "https://xlr.alwaysdata.net/roads/"
#elseif TESTFLIGHT
case activationHost = "xlr.alwaysdata.net"
case main = "https://xlr.alwaysdata.net/"
// case api = "https://xlr.alwaysdata.net/roads/"
#elseif PRODTEST
case activationHost = "padelclub.app"
case main = "https://padelclub.app/"
// case api = "https://padelclub.app/roads/"
#else
case activationHost = "padelclub.app"
case main = "https://padelclub.app/"
// case api = "https://padelclub.app/roads/"
#endif
case subscriptions = "https://apple.co/2Th4vqI"
case beachPadel = "https://beach-padel.app.fft.fr/beachja/index/"
//case padelClub = "https://padelclub.app"
case tenup = "https://tenup.fft.fr"
case padelCompetitionGeneralGuide = "https://padelclub.app/static/rules/padel-guide-general.pdf"
case padelCompetitionSpecificGuide = "https://padelclub.app/static/rules/padel-guide-cdc.pdf"
case padelCompetitionRankingGuide = "https://padelclub.app/static/rules/padel-guide-rankings.pdf"
case padelRules = "https://padelclub.app/static/rules/padel-rules.pdf"
case restingDischarge = "https://club.fft.fr/tennisfirmidecazeville/60120370_d/data_1/pdf/fo/formlairededechargederesponsabilitetournoidepadel.pdf"
case appReview = "https://apps.apple.com/app/padel-club/id6484163558?mt=8&action=write-review"
case appDescription = "https://padelclub.app/download/"
case instagram = "https://www.instagram.com/padelclub.app?igsh=bmticnV5YWhpMnBn"
case appStore = "https://apps.apple.com/app/padel-club/id6484163558"
case eula = "https://www.apple.com/legal/internet-services/itunes/dev/stdeula/"
case privacy = "https://padelclub.app/terms-of-use"
var id: String { return self.rawValue }
var url: URL {
return URL(string: self.rawValue)!
}
func extend(path: String) -> URL {
return URL(string: self.rawValue + path)!
}
}
enum PageLink: String, Identifiable, CaseIterable {
case info = "Informations"
case teams = "Équipes"
case summons = "Convocations"
case groupStages = "Poules"
case matches = "Tournoi"
case rankings = "Classement"
case broadcast = "Mode TV (Tournoi)"
case clubBroadcast = "Mode TV (Club)"
var id: String { self.rawValue }
func localizedLabel() -> String {
rawValue
}
var path: String {
switch self {
case .matches:
return ""
case .info:
return "info"
case .teams:
return "teams"
case .summons:
return "summons"
case .rankings:
return "rankings"
case .groupStages:
return "group-stages"
case .broadcast:
return "broadcast"
case .clubBroadcast:
return ""
}
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save