parent
4663f32102
commit
2c99a323b9
File diff suppressed because it is too large
Load Diff
@ -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 |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -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() |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -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 |
||||
// |
||||
// Created by Razmig Sarkissian on 18/04/2024. |
||||
// Created by Laurent Morvillier on 15/04/2025. |
||||
// |
||||
|
||||
import Foundation |
||||
import SwiftUI |
||||
import LeStorage |
||||
import PadelClubData |
||||
|
||||
@Observable |
||||
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) |
||||
} |
||||
extension MonthData { |
||||
|
||||
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 |
||||
} |
||||
} |
||||
@ -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() |
||||
} |
||||
} |
||||
@ -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) |
||||
} |
||||
} |
||||
} |
||||
@ -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 |
||||
} |
||||
} |
||||
} |
||||
@ -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…
Reference in new issue