speed update, new table for cached stats

This commit is contained in:
Michael Simard
2021-08-04 16:32:17 -05:00
parent c38b621bdd
commit bc0dc93e9e
11 changed files with 485 additions and 139 deletions

View 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)
}
}
}

View File

@@ -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] = []
var statsWithHyder:Stats
var statsWithoutHyder:Stats
init(dashboardItems:[DashboardItem] ){
self.dashboardItems = dashboardItems
}
}
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
}

View File

@@ -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
}

View File

@@ -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 {
req.application.threadPool.runIfActive(eventLoop: req.eventLoop.next()) {
DBHelpers.relcalulateRecords(db: req.db) { MessagePort in}
}
return .ok
}
.map { .ok }
}
func deleteMatch(req: Request) throws -> EventLoopFuture<HTTPStatus> {
@@ -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<Match> {
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
}
}

View File

@@ -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<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> {
@@ -86,8 +217,6 @@ 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<Stats>{
func getStatsForYear(year:Int, db: Database) -> EventLoopFuture<Stats>{
// 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<OverallStats> {
// func cachedStats(db: Database) -> EventLoopFuture<[String:Stats]> {
//
// }
func forceCalculatedStats(db: Database) -> EventLoopFuture<[String:Stats]> {
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)
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 = [
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: ""),
// 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),
]
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> {
return Match.query(on: req.db)
func statsWithPlayer(db: Database, playerId:Int) -> EventLoopFuture<Stats> {
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<Stats> {
return Match.query(on: req.db)
func statsWithoutPlayer (db: Database, playerId:Int) -> EventLoopFuture<Stats> {
return Match.query(on: db)
.filter(\.$players !~ "\(playerId)")
.all().map { (matches) -> (Stats) in
return self.getCountedMatches(matches: matches)

View File

@@ -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<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 {
// func prepare(on database: Database) -> EventLoopFuture<Void> {

View File

@@ -100,8 +100,8 @@ final class Match: Model, Content {
}
extension Match {
struct Create: Content {
}
}
//extension Match {
// struct Create: Content {
// }
//}

View File

@@ -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
}
}

29
Sources/App/Utils.swift Normal file
View 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)
}
}

View File

@@ -16,6 +16,8 @@ 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",
@@ -26,6 +28,7 @@ public func configure(_ app: Application) throws {
//app.migrations.add(CreateMatch())
app.migrations.add(CreateStatistics())
app.migrations.add(AddCodTrackerId())
@@ -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)

View 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")
}
}