diff --git a/Sources/App/Commands/RecalulateRecords.swift b/Sources/App/Commands/RecalulateRecords.swift new file mode 100644 index 0000000..02859f1 --- /dev/null +++ b/Sources/App/Commands/RecalulateRecords.swift @@ -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) + } + } +} diff --git a/Sources/App/Content/AllStats.swift b/Sources/App/Content/AllStats.swift index 1350865..c5efcb8 100644 --- a/Sources/App/Content/AllStats.swift +++ b/Sources/App/Content/AllStats.swift @@ -41,26 +41,35 @@ final class AllStats: Content { } } -final class OverallStats: Content { - var overall:StatsWithMostRecentDailyRecord - var mwStats:StatsWithMostRecentDailyRecord - var bocwStats:StatsWithMostRecentDailyRecord - var mostRecentRecord:String +final class DashboardStats: Content { + var dashboardItems:[DashboardItem] = [] + + + init(dashboardItems:[DashboardItem] ){ + self.dashboardItems = dashboardItems + } +} - var statsWithHyder:Stats - var statsWithoutHyder:Stats +final class OverallStats: Content { +// var overall:StatsWithMostRecentDailyRecord + // var mwStats:StatsWithMostRecentDailyRecord + // var bocwStats:StatsWithMostRecentDailyRecord +// var mostRecentRecord:String + + // var statsWithHyder:Stats + // var statsWithoutHyder:Stats 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.mwStats = mwStats; - self.bocwStats = bocwStats; - self.mostRecentRecord = mostRecentRecord + // self.overall = overall + // self.mwStats = mwStats; + // self.bocwStats = bocwStats; + // self.mostRecentRecord = mostRecentRecord - self.statsWithHyder = statsWithHyder - self.statsWithoutHyder = statsWithoutHyder + // self.statsWithHyder = statsWithHyder + // self.statsWithoutHyder = statsWithoutHyder self.dashboardItems = dashboardItems } diff --git a/Sources/App/Content/DashboardItem.swift b/Sources/App/Content/DashboardItem.swift index 9f938dc..6fb97d8 100644 --- a/Sources/App/Content/DashboardItem.swift +++ b/Sources/App/Content/DashboardItem.swift @@ -10,10 +10,13 @@ import Fluent import Vapor struct DashboardItem: Content { + + var codTrackerId: String var title:String var content:String var title2:String? = nil var content2:String? = nil + var sortOrder: Int } diff --git a/Sources/App/Controllers/MatchController.swift b/Sources/App/Controllers/MatchController.swift index f236b05..740c1c6 100644 --- a/Sources/App/Controllers/MatchController.swift +++ b/Sources/App/Controllers/MatchController.swift @@ -17,7 +17,6 @@ struct MatchController: RouteCollection { matchRoute.delete("delete", "id",":id", use: deleteMatch) matchRoute.post("update", "id",":id", use: updateMatch) matchRoute.post("add", use: logMatch) - } @@ -28,20 +27,21 @@ struct MatchController: RouteCollection { let newMatch = try req.content.decode(Match.self) + return Match.find(id, on: req.db) .unwrap(or: Abort(.notFound)) .flatMap { - - $0.update(newMatch: newMatch) - 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 { @@ -51,14 +51,36 @@ struct MatchController: RouteCollection { return Match.find(id, on: req.db) .unwrap(or: Abort(.notFound)) .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 { 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 + + } + } diff --git a/Sources/App/Controllers/StatsController.swift b/Sources/App/Controllers/StatsController.swift index 4009d44..bf3996c 100644 --- a/Sources/App/Controllers/StatsController.swift +++ b/Sources/App/Controllers/StatsController.swift @@ -24,9 +24,140 @@ struct StatsController: RouteCollection { statsRoute.get("history","page",":page", use: history) statsRoute.get("history", use: history) statsRoute.get("maps", use: mapRecords) + statsRoute.get("dashboard", use: dashboard) } + func dashboard(db: Database) throws -> EventLoopFuture { + + 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 { + + return try dashboard(db: req.db) + } func history(req: Request) throws -> EventLoopFuture { @@ -85,9 +216,7 @@ struct StatsController: RouteCollection { } func getAllMatches(matches:[Match]) -> Stats{ - - - + let totals = matches.reduce([0,0]) { (totals, match) -> [Int] in if match.win == true { return [totals[0] + 1, totals[1]] @@ -184,12 +313,10 @@ struct StatsController: RouteCollection { } } - - } - func getStatsForYear(year:Int, req: Request) -> EventLoopFuture{ + func getStatsForYear(year:Int, db: Database) -> EventLoopFuture{ // Specify date components var dateComponents = DateComponents() @@ -218,7 +345,7 @@ struct StatsController: RouteCollection { 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) } } @@ -237,10 +364,6 @@ struct StatsController: RouteCollection { 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) } - - //print ("MDP to dates \(Date().timeIntervalSince(startTime))") - - return dates.reduce([CODDate]()) { (datesPlayed, codDate) -> [CODDate] in if datesPlayed.contains(where: { (existingDate) -> Bool in @@ -257,8 +380,6 @@ struct StatsController: RouteCollection { } } - - func getCumulativeWinLossRatios(matches:[Match]) -> [DataPoint] { let daysPlayed = getDaysPlayed(sortedMatches: matches) @@ -483,34 +604,29 @@ struct StatsController: RouteCollection { } } - func overall(req: Request) throws -> EventLoopFuture { - - - - let startTime = Date() - - let statsWithHyder = statsWithPlayer(req: req, playerId: 5) - - let statsWithoutHyder = statsWithoutPlayer(req: req, playerId: 5) - - let statsFor2020 = getStatsForYear(year: 2020, req: req) - let statsFor2021 = getStatsForYear(year: 2021, req: req) +// func cachedStats(db: Database) -> EventLoopFuture<[String:Stats]> { +// +// } + + + func forceCalculatedStats(db: Database) -> EventLoopFuture<[String:Stats]> { + + let statsWithHyder = statsWithPlayer(db: db, playerId: 5) + let statsWithoutHyder = statsWithoutPlayer(db: db, playerId: 5) + let statsFor2020 = getStatsForYear(year: 2020, db: db) + let statsFor2021 = getStatsForYear(year: 2021, db: db) - - let hyderFuture = statsWithHyder.and(statsWithoutHyder) let hyderStats = hyderFuture.map { (withHyder, withoutHyder) -> [Stats] in 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 @@ -527,11 +643,11 @@ struct StatsController: RouteCollection { var mwSixPlayers:Stats? var mwFivePlayers:Stats? var overallFourPlayers:Stats? - - var mapStats:[Int:Stats]? - var worstMap:Int? - var bestMap:Int? - +// +// var mapStats:[Int:Stats]? +// var worstMap:Int? +// var bestMap:Int? +// group.enter() queue.async { overallStats = self.getStatsWithMostRecentDailyRecord(sortedMatches: matches) @@ -559,42 +675,13 @@ struct StatsController: RouteCollection { return match.codGame == "bocw" && self.shouldCountMatch(match: match ) })) - //print ("cw done \(Date().timeIntervalSince(startTime))") group.leave() } 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) - //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 + overallFourPlayers = self.getAllMatchesByPlayerCount(matches: matches, playerCount: 4) //Feb group.leave() } @@ -615,27 +702,111 @@ struct StatsController: RouteCollection { 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 { + + + 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 { + + return try overall(db: req.db) + + } @@ -722,8 +893,8 @@ struct StatsController: RouteCollection { } - func statsWithPlayer(req: Request, playerId:Int) -> EventLoopFuture { - return Match.query(on: req.db) + func statsWithPlayer(db: Database, playerId:Int) -> EventLoopFuture { + return Match.query(on: db) .filter(\.$players ~~ "\(playerId)") .all().map { (matches) -> (Stats) in return self.getCountedMatches(matches: matches) @@ -731,8 +902,8 @@ struct StatsController: RouteCollection { } - func statsWithoutPlayer (req: Request, playerId:Int) -> EventLoopFuture { - return Match.query(on: req.db) + func statsWithoutPlayer (db: Database, playerId:Int) -> EventLoopFuture { + return Match.query(on: db) .filter(\.$players !~ "\(playerId)") .all().map { (matches) -> (Stats) in return self.getCountedMatches(matches: matches) diff --git a/Sources/App/Migrations/CreateStatistics.swift b/Sources/App/Migrations/CreateStatistics.swift index f71d895..abb8b69 100644 --- a/Sources/App/Migrations/CreateStatistics.swift +++ b/Sources/App/Migrations/CreateStatistics.swift @@ -9,6 +9,7 @@ struct CreateStatistics: Migration { .field("title", .string) .field("wins", .int) .field("losses", .int) + .create() } @@ -19,6 +20,22 @@ struct CreateStatistics: Migration { } +struct AddCodTrackerId: Migration { + // Prepares the database for storing Galaxy models. + func prepare(on database: Database) -> EventLoopFuture { + database.schema("statistics") + + .field("cod_tracker_id", .string) + .update() + } + + // Optionally reverts the changes made in the prepare method. + func revert(on database: Database) -> EventLoopFuture { + database.schema("statistics").delete() + } +} + + //struct CreateMatch: Migration { // func prepare(on database: Database) -> EventLoopFuture { diff --git a/Sources/App/Models/Match.swift b/Sources/App/Models/Match.swift index 6fa4026..43a2c15 100644 --- a/Sources/App/Models/Match.swift +++ b/Sources/App/Models/Match.swift @@ -100,8 +100,8 @@ final class Match: Model, Content { } -extension Match { - struct Create: Content { - } -} +//extension Match { +// struct Create: Content { +// } +//} diff --git a/Sources/App/Models/Statistics.swift b/Sources/App/Models/Statistics.swift index 1482313..6e9eef0 100644 --- a/Sources/App/Models/Statistics.swift +++ b/Sources/App/Models/Statistics.swift @@ -1,5 +1,5 @@ // -// Statistics.swift +// WinLossRecords.swift // Statistics // // Created by Michael Simard on 8/2/21. @@ -8,32 +8,55 @@ import Fluent import Vapor -final class Statistics: Model { +final class WinLossRecords: Model, Content { // 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? - @Field(key: "title") + @Field(key: "title") var title: String - @Field(key: "Wins") + @Field(key: "wins") var wins: Int @Field(key: "losses") var losses: Int + + @Field(key: "cod_tracker_id") + var codTrackerId: String // Creates a new, empty . init() { } // 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.title = title self.wins = wins 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 + + } + + } diff --git a/Sources/App/Utils.swift b/Sources/App/Utils.swift new file mode 100644 index 0000000..e3742f6 --- /dev/null +++ b/Sources/App/Utils.swift @@ -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) + } + +} diff --git a/Sources/App/configure.swift b/Sources/App/configure.swift index 8edba55..84d6108 100644 --- a/Sources/App/configure.swift +++ b/Sources/App/configure.swift @@ -16,7 +16,9 @@ public func configure(_ app: Application) throws { app.http.server.configuration.hostname = "0.0.0.0" app.http.server.configuration.port = 8080 - + app.commands.use(RecalulateRecords(), as: "recalc") + + app.databases.use(.postgres( hostname: Environment.get("DATABASE_HOST") ?? "localhost", username: Environment.get("DATABASE_USERNAME") ?? "cod", @@ -26,7 +28,8 @@ public func configure(_ app: Application) throws { //app.migrations.add(CreateMatch()) app.migrations.add(CreateStatistics()) - + app.migrations.add(AddCodTrackerId()) + // 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.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) - - // override the global encoder used for the `.json` media type ContentConfiguration.global.use(decoder: decoder, for: .json) - // ContentConfiguration.global.use(encoder: encoder, for: .json) // register routes try routes(app) diff --git a/Sources/App/db/DBHelpers.swift b/Sources/App/db/DBHelpers.swift new file mode 100644 index 0000000..564f80f --- /dev/null +++ b/Sources/App/db/DBHelpers.swift @@ -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 = 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") + + + } + + +}