speed update, new table for cached stats
This commit is contained in:
25
Sources/App/Commands/RecalulateRecords.swift
Normal file
25
Sources/App/Commands/RecalulateRecords.swift
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
//
|
||||||
|
// RecalculateStatistics.swift
|
||||||
|
// RecalculateStatistics
|
||||||
|
//
|
||||||
|
// Created by Michael Simard on 8/3/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Vapor
|
||||||
|
import Fluent
|
||||||
|
|
||||||
|
struct RecalulateRecords: Command {
|
||||||
|
struct Signature: CommandSignature {
|
||||||
|
}
|
||||||
|
|
||||||
|
var help: String {
|
||||||
|
"Recaluclates all stats with a w/l ratio"
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(using context: CommandContext, signature: Signature) throws {
|
||||||
|
|
||||||
|
DBHelpers.relcalulateRecords(db: context.application.db) { message in
|
||||||
|
context.console.print(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,26 +41,35 @@ final class AllStats: Content {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class OverallStats: Content {
|
final class DashboardStats: Content {
|
||||||
var overall:StatsWithMostRecentDailyRecord
|
var dashboardItems:[DashboardItem] = []
|
||||||
var mwStats:StatsWithMostRecentDailyRecord
|
|
||||||
var bocwStats:StatsWithMostRecentDailyRecord
|
|
||||||
var mostRecentRecord:String
|
init(dashboardItems:[DashboardItem] ){
|
||||||
|
self.dashboardItems = dashboardItems
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var statsWithHyder:Stats
|
final class OverallStats: Content {
|
||||||
var statsWithoutHyder:Stats
|
// var overall:StatsWithMostRecentDailyRecord
|
||||||
|
// var mwStats:StatsWithMostRecentDailyRecord
|
||||||
|
// var bocwStats:StatsWithMostRecentDailyRecord
|
||||||
|
// var mostRecentRecord:String
|
||||||
|
|
||||||
|
// var statsWithHyder:Stats
|
||||||
|
// var statsWithoutHyder:Stats
|
||||||
|
|
||||||
var dashboardItems:[DashboardItem] = []
|
var dashboardItems:[DashboardItem] = []
|
||||||
|
|
||||||
init( overall:StatsWithMostRecentDailyRecord, mwStats:StatsWithMostRecentDailyRecord, bocwStats:StatsWithMostRecentDailyRecord, mostRecentRecord:String, statsWithHyder:Stats, statsWithoutHyder:Stats, dashboardItems:[DashboardItem]){
|
init(dashboardItems:[DashboardItem]){
|
||||||
|
|
||||||
self.overall = overall
|
// self.overall = overall
|
||||||
self.mwStats = mwStats;
|
// self.mwStats = mwStats;
|
||||||
self.bocwStats = bocwStats;
|
// self.bocwStats = bocwStats;
|
||||||
self.mostRecentRecord = mostRecentRecord
|
// self.mostRecentRecord = mostRecentRecord
|
||||||
|
|
||||||
self.statsWithHyder = statsWithHyder
|
// self.statsWithHyder = statsWithHyder
|
||||||
self.statsWithoutHyder = statsWithoutHyder
|
// self.statsWithoutHyder = statsWithoutHyder
|
||||||
|
|
||||||
self.dashboardItems = dashboardItems
|
self.dashboardItems = dashboardItems
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,10 +10,13 @@ import Fluent
|
|||||||
import Vapor
|
import Vapor
|
||||||
|
|
||||||
struct DashboardItem: Content {
|
struct DashboardItem: Content {
|
||||||
|
|
||||||
|
var codTrackerId: String
|
||||||
var title:String
|
var title:String
|
||||||
var content:String
|
var content:String
|
||||||
var title2:String? = nil
|
var title2:String? = nil
|
||||||
var content2:String? = nil
|
var content2:String? = nil
|
||||||
|
var sortOrder: Int
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ struct MatchController: RouteCollection {
|
|||||||
matchRoute.delete("delete", "id",":id", use: deleteMatch)
|
matchRoute.delete("delete", "id",":id", use: deleteMatch)
|
||||||
matchRoute.post("update", "id",":id", use: updateMatch)
|
matchRoute.post("update", "id",":id", use: updateMatch)
|
||||||
matchRoute.post("add", use: logMatch)
|
matchRoute.post("add", use: logMatch)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -28,20 +27,21 @@ struct MatchController: RouteCollection {
|
|||||||
|
|
||||||
let newMatch = try req.content.decode(Match.self)
|
let newMatch = try req.content.decode(Match.self)
|
||||||
|
|
||||||
|
|
||||||
return Match.find(id, on: req.db)
|
return Match.find(id, on: req.db)
|
||||||
.unwrap(or: Abort(.notFound))
|
.unwrap(or: Abort(.notFound))
|
||||||
.flatMap {
|
.flatMap {
|
||||||
|
|
||||||
|
|
||||||
$0.update(newMatch: newMatch)
|
$0.update(newMatch: newMatch)
|
||||||
|
|
||||||
return $0.save(on: req.db)
|
return $0.save(on: req.db)
|
||||||
|
|
||||||
}
|
}
|
||||||
.map { .ok }
|
.map {
|
||||||
|
|
||||||
|
req.application.threadPool.runIfActive(eventLoop: req.eventLoop.next()) {
|
||||||
|
DBHelpers.relcalulateRecords(db: req.db) { MessagePort in}
|
||||||
|
}
|
||||||
|
return .ok
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteMatch(req: Request) throws -> EventLoopFuture<HTTPStatus> {
|
func deleteMatch(req: Request) throws -> EventLoopFuture<HTTPStatus> {
|
||||||
@@ -51,14 +51,36 @@ struct MatchController: RouteCollection {
|
|||||||
return Match.find(id, on: req.db)
|
return Match.find(id, on: req.db)
|
||||||
.unwrap(or: Abort(.notFound))
|
.unwrap(or: Abort(.notFound))
|
||||||
.flatMap { $0.delete(on: req.db) }
|
.flatMap { $0.delete(on: req.db) }
|
||||||
.map { .ok }
|
.map {
|
||||||
|
|
||||||
|
|
||||||
|
req.application.threadPool.runIfActive(eventLoop: req.eventLoop.next()) {
|
||||||
|
DBHelpers.relcalulateRecords(db: req.db) { MessagePort in}
|
||||||
|
}
|
||||||
|
|
||||||
|
return .ok
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func logMatch(req: Request) throws -> EventLoopFuture<Match> {
|
func logMatch(req: Request) throws -> EventLoopFuture<Match> {
|
||||||
|
|
||||||
let newMatch = try req.content.decode(Match.self)
|
let newMatch = try req.content.decode(Match.self)
|
||||||
return newMatch.save(on: req.db).map { newMatch}
|
return newMatch.save(on: req.db).map {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
req.application.threadPool.runIfActive(eventLoop: req.eventLoop.next()) {
|
||||||
|
DBHelpers.relcalulateRecords(db: req.db) { MessagePort in}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return newMatch
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -24,9 +24,140 @@ struct StatsController: RouteCollection {
|
|||||||
statsRoute.get("history","page",":page", use: history)
|
statsRoute.get("history","page",":page", use: history)
|
||||||
statsRoute.get("history", use: history)
|
statsRoute.get("history", use: history)
|
||||||
statsRoute.get("maps", use: mapRecords)
|
statsRoute.get("maps", use: mapRecords)
|
||||||
|
statsRoute.get("dashboard", use: dashboard)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dashboard(db: Database) throws -> EventLoopFuture<DashboardStats> {
|
||||||
|
|
||||||
|
let statistics = WinLossRecords.query(on: db).all()
|
||||||
|
let adamAffectedMatches = Match.query(on: db).filter(\.$finalKillRuinedPlayerId == 6).count()
|
||||||
|
let totalMWGames = Match.query(on: db).filter(\.$codGame == "mw").count()
|
||||||
|
|
||||||
|
return (statistics.and(adamAffectedMatches).and(totalMWGames)).map { arg -> (DashboardStats) in
|
||||||
|
|
||||||
|
let (((statistics, adamAffectedMatches), totalMWGames)) = arg
|
||||||
|
|
||||||
|
|
||||||
|
// return statistics.map { statistics in
|
||||||
|
return DashboardStats(dashboardItems: statistics.map({ statisticItem in
|
||||||
|
|
||||||
|
|
||||||
|
let title = statisticItem.title
|
||||||
|
var title2:String? = ""
|
||||||
|
var content1 = ""
|
||||||
|
var content2 = ""
|
||||||
|
var sortOrder = 0
|
||||||
|
|
||||||
|
if statisticItem.codTrackerId == "no_hyder_overall"{
|
||||||
|
title2 = "Ratio"
|
||||||
|
content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
|
||||||
|
content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
|
||||||
|
sortOrder = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
else if statisticItem.codTrackerId == "mw_overall"{
|
||||||
|
title2 = "Ratio"
|
||||||
|
content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
|
||||||
|
content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
|
||||||
|
sortOrder = 2
|
||||||
|
|
||||||
|
}
|
||||||
|
else if statisticItem.codTrackerId == "mw_six_players"{
|
||||||
|
title2 = "Ratio"
|
||||||
|
content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
|
||||||
|
content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
|
||||||
|
sortOrder = 6
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
else if statisticItem.codTrackerId == "2021_overall"{
|
||||||
|
title2 = "Ratio"
|
||||||
|
content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
|
||||||
|
content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
|
||||||
|
sortOrder = 5
|
||||||
|
|
||||||
|
}
|
||||||
|
// else if statisticItem.codTrackerId == "best_map_overall"{
|
||||||
|
// title2 = "Ratio"
|
||||||
|
// content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
|
||||||
|
// content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
else if statisticItem.codTrackerId == "bocw_overall"{
|
||||||
|
title2 = "Ratio"
|
||||||
|
content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
|
||||||
|
content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
|
||||||
|
sortOrder = 3
|
||||||
|
|
||||||
|
}
|
||||||
|
// else if statisticItem.codTrackerId == "worst_map_overall"{
|
||||||
|
// title2 = "Ratio"
|
||||||
|
// content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
|
||||||
|
// content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
else if statisticItem.codTrackerId == "with_hyder_overall"{
|
||||||
|
title2 = "Ratio"
|
||||||
|
content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
|
||||||
|
content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
|
||||||
|
sortOrder = 11
|
||||||
|
}
|
||||||
|
else if statisticItem.codTrackerId == "mw_five_players"{
|
||||||
|
title2 = "Ratio"
|
||||||
|
content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
|
||||||
|
content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
|
||||||
|
sortOrder = 7
|
||||||
|
|
||||||
|
}
|
||||||
|
else if statisticItem.codTrackerId == "casual_overall"{
|
||||||
|
title2 = "Ratio"
|
||||||
|
content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
|
||||||
|
content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
|
||||||
|
sortOrder = 4
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
else if statisticItem.codTrackerId == "overall_four_players"{
|
||||||
|
title2 = "Ratio"
|
||||||
|
content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
|
||||||
|
content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
|
||||||
|
sortOrder = 8
|
||||||
|
}
|
||||||
|
else if statisticItem.codTrackerId == "overall"{
|
||||||
|
title2 = "Ratio"
|
||||||
|
content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
|
||||||
|
content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
|
||||||
|
sortOrder = 1
|
||||||
|
}
|
||||||
|
else if statisticItem.codTrackerId == "2020_overall"{
|
||||||
|
title2 = "Ratio"
|
||||||
|
content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
|
||||||
|
content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
|
||||||
|
sortOrder = 9
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return DashboardItem(codTrackerId:statisticItem.codTrackerId, title: title, content: content1, title2: title2, content2: content2, sortOrder: sortOrder)
|
||||||
|
} ) +
|
||||||
|
[
|
||||||
|
DashboardItem(codTrackerId: "adam_ruined_final_kills", title: "Final Kills Ruined by Adam", content: "\(adamAffectedMatches + 7)", title2: "", content2: "",sortOrder: 100),
|
||||||
|
DashboardItem(codTrackerId:"total_mw_games", title: "Total MW Games", content: "\(totalMWGames)", title2: "", content2: "", sortOrder: 0)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func dashboard(req: Request) throws -> EventLoopFuture<DashboardStats> {
|
||||||
|
|
||||||
|
return try dashboard(db: req.db)
|
||||||
|
}
|
||||||
|
|
||||||
func history(req: Request) throws -> EventLoopFuture<MatchHistory> {
|
func history(req: Request) throws -> EventLoopFuture<MatchHistory> {
|
||||||
|
|
||||||
@@ -85,9 +216,7 @@ struct StatsController: RouteCollection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getAllMatches(matches:[Match]) -> Stats{
|
func getAllMatches(matches:[Match]) -> Stats{
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let totals = matches.reduce([0,0]) { (totals, match) -> [Int] in
|
let totals = matches.reduce([0,0]) { (totals, match) -> [Int] in
|
||||||
if match.win == true {
|
if match.win == true {
|
||||||
return [totals[0] + 1, totals[1]]
|
return [totals[0] + 1, totals[1]]
|
||||||
@@ -184,12 +313,10 @@ struct StatsController: RouteCollection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func getStatsForYear(year:Int, req: Request) -> EventLoopFuture<Stats>{
|
func getStatsForYear(year:Int, db: Database) -> EventLoopFuture<Stats>{
|
||||||
|
|
||||||
// Specify date components
|
// Specify date components
|
||||||
var dateComponents = DateComponents()
|
var dateComponents = DateComponents()
|
||||||
@@ -218,7 +345,7 @@ struct StatsController: RouteCollection {
|
|||||||
let endDate = userCalendar.date(from: endDateComponenents)
|
let endDate = userCalendar.date(from: endDateComponenents)
|
||||||
|
|
||||||
|
|
||||||
return Match.query(on: req.db).filter(\.$date > startDate!).filter(\.$date < endDate!).all().map { (matches) -> (Stats) in
|
return Match.query(on: db).filter(\.$date > startDate!).filter(\.$date < endDate!).all().map { (matches) -> (Stats) in
|
||||||
return self.getCountedMatches(matches: matches)
|
return self.getCountedMatches(matches: matches)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -237,10 +364,6 @@ struct StatsController: RouteCollection {
|
|||||||
let dates = sortedMatches.suffix(30).map { (match) -> CODDate in
|
let dates = sortedMatches.suffix(30).map { (match) -> CODDate in
|
||||||
return CODDate(month: match.date.month, year: match.date.year, day: match.date.day, hour: match.date.hour, minute: match.date.minute)
|
return CODDate(month: match.date.month, year: match.date.year, day: match.date.day, hour: match.date.hour, minute: match.date.minute)
|
||||||
}
|
}
|
||||||
|
|
||||||
//print ("MDP to dates \(Date().timeIntervalSince(startTime))")
|
|
||||||
|
|
||||||
|
|
||||||
return dates.reduce([CODDate]()) { (datesPlayed, codDate) -> [CODDate] in
|
return dates.reduce([CODDate]()) { (datesPlayed, codDate) -> [CODDate] in
|
||||||
|
|
||||||
if datesPlayed.contains(where: { (existingDate) -> Bool in
|
if datesPlayed.contains(where: { (existingDate) -> Bool in
|
||||||
@@ -257,8 +380,6 @@ struct StatsController: RouteCollection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func getCumulativeWinLossRatios(matches:[Match]) -> [DataPoint] {
|
func getCumulativeWinLossRatios(matches:[Match]) -> [DataPoint] {
|
||||||
|
|
||||||
let daysPlayed = getDaysPlayed(sortedMatches: matches)
|
let daysPlayed = getDaysPlayed(sortedMatches: matches)
|
||||||
@@ -483,34 +604,29 @@ struct StatsController: RouteCollection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func overall(req: Request) throws -> EventLoopFuture<OverallStats> {
|
// func cachedStats(db: Database) -> EventLoopFuture<[String:Stats]> {
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
let startTime = Date()
|
|
||||||
|
func forceCalculatedStats(db: Database) -> EventLoopFuture<[String:Stats]> {
|
||||||
let statsWithHyder = statsWithPlayer(req: req, playerId: 5)
|
|
||||||
|
let statsWithHyder = statsWithPlayer(db: db, playerId: 5)
|
||||||
let statsWithoutHyder = statsWithoutPlayer(req: req, playerId: 5)
|
let statsWithoutHyder = statsWithoutPlayer(db: db, playerId: 5)
|
||||||
|
let statsFor2020 = getStatsForYear(year: 2020, db: db)
|
||||||
let statsFor2020 = getStatsForYear(year: 2020, req: req)
|
let statsFor2021 = getStatsForYear(year: 2021, db: db)
|
||||||
let statsFor2021 = getStatsForYear(year: 2021, req: req)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let hyderFuture = statsWithHyder.and(statsWithoutHyder)
|
let hyderFuture = statsWithHyder.and(statsWithoutHyder)
|
||||||
|
|
||||||
let hyderStats = hyderFuture.map { (withHyder, withoutHyder) -> [Stats] in
|
let hyderStats = hyderFuture.map { (withHyder, withoutHyder) -> [Stats] in
|
||||||
return [withHyder, withoutHyder]
|
return [withHyder, withoutHyder]
|
||||||
|
|
||||||
//print ("Hyder done \(Date().timeIntervalSince(startTime))")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let matches = Match.query(on: req.db).sort( \.$date).all()
|
let matches = Match.query(on: db).sort( \.$date).all()
|
||||||
|
|
||||||
return matches.and(hyderStats).and(statsFor2020).and(statsFor2021).map { arg -> (OverallStats) in
|
return matches.and(hyderStats).and(statsFor2020).and(statsFor2021).map { arg -> ([String:Stats]) in
|
||||||
|
|
||||||
let (((matches, hyderStats), statsFor2020), statsFor2021) = arg
|
let (((matches, hyderStats), statsFor2020), statsFor2021) = arg
|
||||||
|
|
||||||
@@ -527,11 +643,11 @@ struct StatsController: RouteCollection {
|
|||||||
var mwSixPlayers:Stats?
|
var mwSixPlayers:Stats?
|
||||||
var mwFivePlayers:Stats?
|
var mwFivePlayers:Stats?
|
||||||
var overallFourPlayers:Stats?
|
var overallFourPlayers:Stats?
|
||||||
|
//
|
||||||
var mapStats:[Int:Stats]?
|
// var mapStats:[Int:Stats]?
|
||||||
var worstMap:Int?
|
// var worstMap:Int?
|
||||||
var bestMap:Int?
|
// var bestMap:Int?
|
||||||
|
//
|
||||||
group.enter()
|
group.enter()
|
||||||
queue.async {
|
queue.async {
|
||||||
overallStats = self.getStatsWithMostRecentDailyRecord(sortedMatches: matches)
|
overallStats = self.getStatsWithMostRecentDailyRecord(sortedMatches: matches)
|
||||||
@@ -559,42 +675,13 @@ struct StatsController: RouteCollection {
|
|||||||
return match.codGame == "bocw" && self.shouldCountMatch(match: match )
|
return match.codGame == "bocw" && self.shouldCountMatch(match: match )
|
||||||
}))
|
}))
|
||||||
|
|
||||||
//print ("cw done \(Date().timeIntervalSince(startTime))")
|
|
||||||
|
|
||||||
group.leave()
|
group.leave()
|
||||||
}
|
}
|
||||||
|
|
||||||
group.enter()
|
group.enter()
|
||||||
queue.async {
|
queue.async {
|
||||||
mapStats = self.getMapStats(matches: matches)
|
overallFourPlayers = self.getAllMatchesByPlayerCount(matches: matches, playerCount: 4) //Feb
|
||||||
//print ("maps done \(Date().timeIntervalSince(startTime))")
|
|
||||||
group.leave()
|
|
||||||
|
|
||||||
}
|
|
||||||
//
|
|
||||||
group.enter()
|
|
||||||
queue.async {
|
|
||||||
let mapStats = self.getMapStats(matches: matches)
|
|
||||||
|
|
||||||
bestMap = self.getBestMap(records: mapStats)
|
|
||||||
//print ("best done \(Date().timeIntervalSince(startTime))")
|
|
||||||
group.leave()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
group.enter()
|
|
||||||
queue.async {
|
|
||||||
let mapStats = self.getMapStats(matches: matches)
|
|
||||||
|
|
||||||
worstMap = self.getWorstMap(records: mapStats)
|
|
||||||
//print ("worst done \(Date().timeIntervalSince(startTime))")
|
|
||||||
group.leave()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
group.enter()
|
|
||||||
queue.async {
|
|
||||||
overallFourPlayers = self.getAllMatchesByPlayerCount(matches: matches, playerCount: 4) //Feb 1 21
|
|
||||||
group.leave()
|
group.leave()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -615,27 +702,111 @@ struct StatsController: RouteCollection {
|
|||||||
|
|
||||||
group.wait()
|
group.wait()
|
||||||
|
|
||||||
let dashboardItems = [
|
// let dashboardItems:[DashboardItem] = [
|
||||||
|
//
|
||||||
|
// // DashboardItem(codTrackerId:"total_mw_games", title: "Total MW Games", content: "\(mwStats!.totalWins + mwStats!.totalLosses)" , title2:"", content2:""),
|
||||||
|
// DashboardItem(codTrackerId:"mw_overall",title: "MW Overall", content: mwStats!.record, title2: "Ratio", content2: mwStats!.winLossRatio),
|
||||||
|
// DashboardItem(codTrackerId:"mw_six_players", title: "MW 6 Players ", content: mwSixPlayers!.record, title2: "Ratio", content2: mwSixPlayers!.winLossRatio),
|
||||||
|
// DashboardItem(codTrackerId:"mw_five_players", title: "MW 5 Players ", content: mwFivePlayers!.record, title2: "Ratio", content2: mwFivePlayers!.winLossRatio),
|
||||||
|
// DashboardItem(codTrackerId:"overall_four_players", title: "4 Player Crew", content: overallFourPlayers!.record, title2: "Ratio", content2: overallFourPlayers!.winLossRatio),
|
||||||
|
// DashboardItem(codTrackerId:"overall", title: "Overall", content: overallStats!.record, title2: "Ratio", content2: overallStats!.winLossRatio),
|
||||||
|
// DashboardItem(codTrackerId:"2021_overall", title: "2021 Overall", content: statsFor2021.record, title2: "Ratio", content2: statsFor2021.winLossRatio),
|
||||||
|
// DashboardItem(codTrackerId:"2020_overall", title: "2020 Overall", content: statsFor2020.record, title2: "Ratio", content2: statsFor2020.winLossRatio),
|
||||||
|
//// DashboardItem(codTrackerId:"win_rate_overall", title: "Win Rate", content: "\(overallStats!.winRate)", title2: "", content2: ""),
|
||||||
|
// DashboardItem(codTrackerId:"bocw_overall", title: "Cold War Overall", content: bocwStats!.record, title2: "Ratio", content2: bocwStats!.winLossRatio),
|
||||||
|
// DashboardItem(codTrackerId:"with_hyder_overall", title: "With Hyder", content: hyderStats[0].record, title2: "Ratio", content2: hyderStats[0].winLossRatio),
|
||||||
|
// DashboardItem(codTrackerId:"no_hyder_overall", title: "No Hyder", content: hyderStats[1].record, title2: "Ratio", content2: hyderStats[1].winLossRatio),
|
||||||
|
//// DashboardItem(codTrackerId:"best_map_overall", title: "Best Map", content: MapData.allMaps[bestMap!]?.name ?? "error", title2: "Ratio", content2: "\(mapStats![bestMap!]!.winLossRatio) \(mapStats![bestMap!]!.record)"),
|
||||||
|
//// DashboardItem(codTrackerId:"worst_map_overall", title: "Worst Map", content: MapData.allMaps[worstMap!]?.name ?? "error", title2: "Ratio", content2: "\(mapStats![worstMap!]!.winLossRatio) \(mapStats![worstMap!]!.record)"),
|
||||||
|
//// DashboardItem(codTrackerId:"adam_ruined_final_kills", title: "Final Kills Ruined by Adam", content: "\(matches.filter{$0.finalKillRuinedPlayerId == 6}.count + 7)", title2: "", content2: ""),
|
||||||
|
////
|
||||||
|
// ]
|
||||||
|
//
|
||||||
|
return [
|
||||||
|
"mw_overall":Stats(totalWins: mwStats!.totalWins, totalLosses: mwStats!.totalLosses),
|
||||||
|
"no_hyder_overall":Stats(totalWins: hyderStats[1].totalWins, totalLosses: hyderStats[1].totalLosses),
|
||||||
|
"with_hyder_overall":Stats(totalWins:hyderStats[0].totalWins, totalLosses: hyderStats[0].totalWins),
|
||||||
|
"bocw_overall":Stats(totalWins: bocwStats!.totalWins, totalLosses: bocwStats!.totalLosses),
|
||||||
|
"2020_overall":Stats(totalWins: statsFor2020.totalWins, totalLosses: statsFor2020.totalLosses),
|
||||||
|
"2021_overall":Stats(totalWins: statsFor2021.totalWins, totalLosses: statsFor2021.totalLosses),
|
||||||
|
"mw_six_players":Stats(totalWins: mwSixPlayers!.totalWins, totalLosses: mwSixPlayers!.totalLosses),
|
||||||
|
"overall_four_players":Stats(totalWins: overallFourPlayers!.totalWins, totalLosses: overallFourPlayers!.totalLosses),
|
||||||
|
"mw_five_players":Stats(totalWins: mwFivePlayers!.totalWins, totalLosses: mwFivePlayers!.totalLosses),
|
||||||
|
"overall":Stats(totalWins: overallStats!.totalWins, totalLosses: overallStats!.totalLosses),
|
||||||
|
|
||||||
DashboardItem(title: "Total MW Games", content: "\(mwStats!.totalWins + mwStats!.totalLosses)" , title2:"", content2:""),
|
|
||||||
DashboardItem(title: "MW Overall", content: mwStats!.record, title2: "Ratio", content2: mwStats!.winLossRatio),
|
|
||||||
DashboardItem(title: "MW 6 Players ", content: mwSixPlayers!.record, title2: "Ratio", content2: mwSixPlayers!.winLossRatio),
|
|
||||||
DashboardItem(title: "MW 5 Players ", content: mwFivePlayers!.record, title2: "Ratio", content2: mwFivePlayers!.winLossRatio),
|
|
||||||
DashboardItem(title: "4 Player Crew", content: overallFourPlayers!.record, title2: "Ratio", content2: overallFourPlayers!.winLossRatio),
|
|
||||||
DashboardItem(title: "Overall", content: overallStats!.record, title2: "Ratio", content2: overallStats!.winLossRatio),
|
|
||||||
DashboardItem(title: "2021 Overall", content: statsFor2021.record, title2: "Ratio", content2: statsFor2021.winLossRatio),
|
|
||||||
DashboardItem(title: "2020 Overall", content: statsFor2020.record, title2: "Ratio", content2: statsFor2020.winLossRatio),
|
|
||||||
DashboardItem(title: "Win Rate", content: "\(overallStats!.winRate)", title2: "", content2: ""),
|
|
||||||
DashboardItem(title: "Cold War Overall", content: bocwStats!.record, title2: "Ratio", content2: bocwStats!.winLossRatio),
|
|
||||||
DashboardItem(title: "With Hyder", content: hyderStats[0].record, title2: "Ratio", content2: hyderStats[0].winLossRatio),
|
|
||||||
DashboardItem(title: "No Hyder", content: hyderStats[1].record, title2: "Ratio", content2: hyderStats[1].winLossRatio),
|
|
||||||
DashboardItem(title: "Best Map", content: MapData.allMaps[bestMap!]?.name ?? "error", title2: "Ratio", content2: "\(mapStats![bestMap!]!.winLossRatio) \(mapStats![bestMap!]!.record)"),
|
|
||||||
DashboardItem(title: "Worst Map", content: MapData.allMaps[worstMap!]?.name ?? "error", title2: "Ratio", content2: "\(mapStats![worstMap!]!.winLossRatio) \(mapStats![worstMap!]!.record)"),
|
|
||||||
DashboardItem(title: "Final Kills Ruined by Adam", content: "\(matches.filter{$0.finalKillRuinedPlayerId == 6}.count + 7)", title2: "", content2: ""),
|
|
||||||
|
|
||||||
]
|
]
|
||||||
return OverallStats(overall: overallStats!, mwStats: mwStats!, bocwStats: bocwStats!, mostRecentRecord: "Temporarily Unavailable", statsWithHyder:hyderStats[0], statsWithoutHyder: hyderStats[1], dashboardItems:dashboardItems)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func overall(db: Database) throws -> EventLoopFuture<OverallStats> {
|
||||||
|
|
||||||
|
|
||||||
|
let dashboardStats = try dashboard(db:db)
|
||||||
|
|
||||||
|
let matches = Match.query(on: db).sort( \.$date).all()
|
||||||
|
|
||||||
|
return matches.and(dashboardStats).map { arg -> (OverallStats) in
|
||||||
|
|
||||||
|
let (matches, dashboardStats) = arg
|
||||||
|
|
||||||
|
|
||||||
|
let queue = DispatchQueue(label: "com.sledsoft.cod-tracker.queue", attributes: .concurrent)
|
||||||
|
let group = DispatchGroup()
|
||||||
|
|
||||||
|
var mapStats:[Int:Stats]?
|
||||||
|
var worstMap:Int?
|
||||||
|
var bestMap:Int?
|
||||||
|
|
||||||
|
//
|
||||||
|
group.enter()
|
||||||
|
queue.async {
|
||||||
|
mapStats = self.getMapStats(matches: matches)
|
||||||
|
//print ("maps done \(Date().timeIntervalSince(startTime))")
|
||||||
|
group.leave()
|
||||||
|
|
||||||
|
}
|
||||||
|
//
|
||||||
|
group.enter()
|
||||||
|
queue.async {
|
||||||
|
let mapStats = self.getMapStats(matches: matches)
|
||||||
|
|
||||||
|
bestMap = self.getBestMap(records: mapStats)
|
||||||
|
group.leave()
|
||||||
|
}
|
||||||
|
|
||||||
|
group.enter()
|
||||||
|
queue.async {
|
||||||
|
let mapStats = self.getMapStats(matches: matches)
|
||||||
|
|
||||||
|
worstMap = self.getWorstMap(records: mapStats)
|
||||||
|
group.leave()
|
||||||
|
}
|
||||||
|
|
||||||
|
group.wait()
|
||||||
|
|
||||||
|
let dashboardItems:[DashboardItem] =
|
||||||
|
|
||||||
|
(dashboardStats.dashboardItems +
|
||||||
|
[
|
||||||
|
DashboardItem(codTrackerId:"best_map_overall", title: "Best Map", content: MapData.allMaps[bestMap!]?.name ?? "error", title2: "Ratio", content2: "\(mapStats![bestMap!]!.winLossRatio) \(mapStats![bestMap!]!.record)", sortOrder: 12),
|
||||||
|
DashboardItem(codTrackerId:"worst_map_overall", title: "Worst Map", content: MapData.allMaps[worstMap!]?.name ?? "error", title2: "Ratio", content2: "\(mapStats![worstMap!]!.winLossRatio) \(mapStats![worstMap!]!.record)",sortOrder: 13),
|
||||||
|
DashboardItem(codTrackerId:"adam_ruined_final_kills", title: "Final Kills Ruined by Adam", content: "\(matches.filter{$0.finalKillRuinedPlayerId == 6}.count + 7)", title2: "", content2: "",sortOrder: 14),
|
||||||
|
|
||||||
|
]).sorted{
|
||||||
|
$0.sortOrder < $1.sortOrder
|
||||||
|
}
|
||||||
|
return OverallStats(dashboardItems: dashboardItems)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func overall(req: Request) throws -> EventLoopFuture<OverallStats> {
|
||||||
|
|
||||||
|
return try overall(db: req.db)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -722,8 +893,8 @@ struct StatsController: RouteCollection {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func statsWithPlayer(req: Request, playerId:Int) -> EventLoopFuture<Stats> {
|
func statsWithPlayer(db: Database, playerId:Int) -> EventLoopFuture<Stats> {
|
||||||
return Match.query(on: req.db)
|
return Match.query(on: db)
|
||||||
.filter(\.$players ~~ "\(playerId)")
|
.filter(\.$players ~~ "\(playerId)")
|
||||||
.all().map { (matches) -> (Stats) in
|
.all().map { (matches) -> (Stats) in
|
||||||
return self.getCountedMatches(matches: matches)
|
return self.getCountedMatches(matches: matches)
|
||||||
@@ -731,8 +902,8 @@ struct StatsController: RouteCollection {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func statsWithoutPlayer (req: Request, playerId:Int) -> EventLoopFuture<Stats> {
|
func statsWithoutPlayer (db: Database, playerId:Int) -> EventLoopFuture<Stats> {
|
||||||
return Match.query(on: req.db)
|
return Match.query(on: db)
|
||||||
.filter(\.$players !~ "\(playerId)")
|
.filter(\.$players !~ "\(playerId)")
|
||||||
.all().map { (matches) -> (Stats) in
|
.all().map { (matches) -> (Stats) in
|
||||||
return self.getCountedMatches(matches: matches)
|
return self.getCountedMatches(matches: matches)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ struct CreateStatistics: Migration {
|
|||||||
.field("title", .string)
|
.field("title", .string)
|
||||||
.field("wins", .int)
|
.field("wins", .int)
|
||||||
.field("losses", .int)
|
.field("losses", .int)
|
||||||
|
|
||||||
.create()
|
.create()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,6 +20,22 @@ struct CreateStatistics: Migration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct AddCodTrackerId: Migration {
|
||||||
|
// Prepares the database for storing Galaxy models.
|
||||||
|
func prepare(on database: Database) -> EventLoopFuture<Void> {
|
||||||
|
database.schema("statistics")
|
||||||
|
|
||||||
|
.field("cod_tracker_id", .string)
|
||||||
|
.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optionally reverts the changes made in the prepare method.
|
||||||
|
func revert(on database: Database) -> EventLoopFuture<Void> {
|
||||||
|
database.schema("statistics").delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//struct CreateMatch: Migration {
|
//struct CreateMatch: Migration {
|
||||||
// func prepare(on database: Database) -> EventLoopFuture<Void> {
|
// func prepare(on database: Database) -> EventLoopFuture<Void> {
|
||||||
|
|||||||
@@ -100,8 +100,8 @@ final class Match: Model, Content {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Match {
|
//extension Match {
|
||||||
struct Create: Content {
|
// struct Create: Content {
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Statistics.swift
|
// WinLossRecords.swift
|
||||||
// Statistics
|
// Statistics
|
||||||
//
|
//
|
||||||
// Created by Michael Simard on 8/2/21.
|
// Created by Michael Simard on 8/2/21.
|
||||||
@@ -8,32 +8,55 @@
|
|||||||
import Fluent
|
import Fluent
|
||||||
import Vapor
|
import Vapor
|
||||||
|
|
||||||
final class Statistics: Model {
|
final class WinLossRecords: Model, Content {
|
||||||
// Name of the table or collection.
|
// Name of the table or collection.
|
||||||
static let schema = "statistics"
|
static let schema = "win_loss_records"
|
||||||
|
|
||||||
@ID(key: .id)
|
@ID(key: .id)
|
||||||
var id: UUID?
|
var id: UUID?
|
||||||
|
|
||||||
@Field(key: "title")
|
@Field(key: "title")
|
||||||
var title: String
|
var title: String
|
||||||
|
|
||||||
@Field(key: "Wins")
|
@Field(key: "wins")
|
||||||
var wins: Int
|
var wins: Int
|
||||||
|
|
||||||
@Field(key: "losses")
|
@Field(key: "losses")
|
||||||
var losses: Int
|
var losses: Int
|
||||||
|
|
||||||
|
@Field(key: "cod_tracker_id")
|
||||||
|
var codTrackerId: String
|
||||||
|
|
||||||
|
|
||||||
// Creates a new, empty .
|
// Creates a new, empty .
|
||||||
init() { }
|
init() { }
|
||||||
|
|
||||||
// Creates a new with all properties set.
|
// Creates a new with all properties set.
|
||||||
init(id: UUID? = nil, title: String, wins: Int, losses: Int) {
|
init(id: UUID? = nil, title: String, wins: Int, losses: Int, codTrackerId:String) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.title = title
|
self.title = title
|
||||||
self.wins = wins
|
self.wins = wins
|
||||||
self.losses = losses
|
self.losses = losses
|
||||||
|
self.codTrackerId = codTrackerId
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func update( statistics:WinLossRecords) {
|
||||||
|
|
||||||
|
print ("saving: \(statistics.codTrackerId)")
|
||||||
|
guard let _ = statistics.id else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
print ("saving: \(statistics)")
|
||||||
|
|
||||||
|
|
||||||
|
self.id = statistics.id
|
||||||
|
self.title = statistics.title
|
||||||
|
self.wins = statistics.wins
|
||||||
|
self.losses = statistics.losses
|
||||||
|
self.codTrackerId = statistics.codTrackerId
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
29
Sources/App/Utils.swift
Normal file
29
Sources/App/Utils.swift
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
//
|
||||||
|
// Utils.swift
|
||||||
|
// Utils
|
||||||
|
//
|
||||||
|
// Created by Michael Simard on 8/2/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
class Utils {
|
||||||
|
|
||||||
|
|
||||||
|
static func getRatio(wins:Int, losses:Int) -> String {
|
||||||
|
var returnString = ""
|
||||||
|
let deno = (losses != 0) ? losses : 1
|
||||||
|
returnString = String(Utils.getRatioDouble(wins: wins, losses: losses))
|
||||||
|
if deno == 0 {
|
||||||
|
returnString = returnString + "+"
|
||||||
|
}
|
||||||
|
return returnString
|
||||||
|
}
|
||||||
|
|
||||||
|
static func getRatioDouble(wins:Int, losses:Int) -> Double {
|
||||||
|
let deno = (losses != 0) ? losses : 1
|
||||||
|
|
||||||
|
return (Double(wins) / Double(deno)).truncate(places: 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -16,7 +16,9 @@ public func configure(_ app: Application) throws {
|
|||||||
app.http.server.configuration.hostname = "0.0.0.0"
|
app.http.server.configuration.hostname = "0.0.0.0"
|
||||||
app.http.server.configuration.port = 8080
|
app.http.server.configuration.port = 8080
|
||||||
|
|
||||||
|
app.commands.use(RecalulateRecords(), as: "recalc")
|
||||||
|
|
||||||
|
|
||||||
app.databases.use(.postgres(
|
app.databases.use(.postgres(
|
||||||
hostname: Environment.get("DATABASE_HOST") ?? "localhost",
|
hostname: Environment.get("DATABASE_HOST") ?? "localhost",
|
||||||
username: Environment.get("DATABASE_USERNAME") ?? "cod",
|
username: Environment.get("DATABASE_USERNAME") ?? "cod",
|
||||||
@@ -26,7 +28,8 @@ public func configure(_ app: Application) throws {
|
|||||||
|
|
||||||
//app.migrations.add(CreateMatch())
|
//app.migrations.add(CreateMatch())
|
||||||
app.migrations.add(CreateStatistics())
|
app.migrations.add(CreateStatistics())
|
||||||
|
app.migrations.add(AddCodTrackerId())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// create a new JSON encoder that uses unix-timestamp dates
|
// create a new JSON encoder that uses unix-timestamp dates
|
||||||
@@ -45,23 +48,11 @@ formatter.timeZone = TimeZone(secondsFromGMT: 0)
|
|||||||
encodeFormatter.calendar = Calendar(identifier: .iso8601)
|
encodeFormatter.calendar = Calendar(identifier: .iso8601)
|
||||||
encodeFormatter.locale = Locale(identifier: "en_US_POSIX")
|
encodeFormatter.locale = Locale(identifier: "en_US_POSIX")
|
||||||
|
|
||||||
let date = Date()
|
|
||||||
let tz = TimeZone.current
|
|
||||||
// if tz.isDaylightSavingTime(for: date) {
|
|
||||||
// encodeFormatter.timeZone = TimeZone(abbreviation: "EST")
|
|
||||||
// }
|
|
||||||
// else{
|
|
||||||
// encodeFormatter.timeZone = TimeZone(abbreviation: "EDT")
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
|
|
||||||
encodeFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
encodeFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
||||||
encoder.dateEncodingStrategy = .formatted(encodeFormatter)
|
encoder.dateEncodingStrategy = .formatted(encodeFormatter)
|
||||||
|
|
||||||
|
|
||||||
// override the global encoder used for the `.json` media type
|
|
||||||
ContentConfiguration.global.use(decoder: decoder, for: .json)
|
ContentConfiguration.global.use(decoder: decoder, for: .json)
|
||||||
// ContentConfiguration.global.use(encoder: encoder, for: .json)
|
|
||||||
|
|
||||||
// register routes
|
// register routes
|
||||||
try routes(app)
|
try routes(app)
|
||||||
|
|||||||
56
Sources/App/db/DBHelpers.swift
Normal file
56
Sources/App/db/DBHelpers.swift
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
//
|
||||||
|
// DBHelpers.swift
|
||||||
|
// App
|
||||||
|
//
|
||||||
|
// Created by Michael Simard on 8/4/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Fluent
|
||||||
|
import Vapor
|
||||||
|
|
||||||
|
class DBHelpers {
|
||||||
|
|
||||||
|
static func relcalulateRecords(db:Database, completion:(_ message:String)->()){
|
||||||
|
|
||||||
|
let group = DispatchGroup()
|
||||||
|
_ = try? StatsController().forceCalculatedStats(db: db).map({ statsDict in
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
for key in statsDict.keys {
|
||||||
|
print (key)
|
||||||
|
group.enter()
|
||||||
|
}
|
||||||
|
|
||||||
|
for key in statsDict.keys {
|
||||||
|
|
||||||
|
let s:EventLoopFuture<Void> = WinLossRecords.query(on: db).filter(\.$codTrackerId == key).first()
|
||||||
|
.unwrap(or: Abort(.notFound))
|
||||||
|
.flatMap {
|
||||||
|
|
||||||
|
$0.update(statistics: WinLossRecords(id: $0.id, title: $0.title, wins: statsDict[key]?.totalWins ?? 0, losses: statsDict[key]?.totalLosses ?? 0, codTrackerId: $0.codTrackerId))
|
||||||
|
|
||||||
|
return $0.save(on: db)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
s.whenComplete { result in
|
||||||
|
group.leave()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.wait()
|
||||||
|
|
||||||
|
|
||||||
|
group.wait()
|
||||||
|
|
||||||
|
completion("complete")
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user