From a2d863dc7a9b3097e1c23ec60e22f3cb34948b61 Mon Sep 17 00:00:00 2001 From: Michael Green <84688932+michael-j-green@users.noreply.github.com> Date: Wed, 7 Feb 2024 16:19:30 +1100 Subject: [PATCH] Added game last played, and total play time stats * Added game last played, and total play time stats --- gaseous-server/.DS_Store | Bin 10244 -> 8196 bytes gaseous-server/Classes/Statistics.cs | 99 +++++++++++++++++ .../Controllers/V1.1/StatisticsController.cs | 104 ++++++++++++++++++ gaseous-server/Models/StatisticsModel.cs | 17 +++ .../Support/Database/MySQL/gaseous-1020.sql | 13 +++ gaseous-server/gaseous-server.csproj | 4 + gaseous-server/wwwroot/.DS_Store | Bin 10244 -> 10244 bytes gaseous-server/wwwroot/pages/emulator.html | 26 +++++ gaseous-server/wwwroot/pages/game.html | 34 ++++++ gaseous-server/wwwroot/styles/style.css | 25 +++++ 10 files changed, 322 insertions(+) create mode 100644 gaseous-server/Classes/Statistics.cs create mode 100644 gaseous-server/Controllers/V1.1/StatisticsController.cs create mode 100644 gaseous-server/Models/StatisticsModel.cs create mode 100644 gaseous-server/Support/Database/MySQL/gaseous-1020.sql diff --git a/gaseous-server/.DS_Store b/gaseous-server/.DS_Store index fc2bf7c94e049171ee93dbdec98e5cf6a8c203fe..1f84f25e84a335080b104cbb33e107c25364aecd 100644 GIT binary patch delta 153 zcmZn(XmOBWU|?W$DortDU;r^WfEYvza8E20o2aKKq7IS=@)?Q?l5+BsfV>?^8xxnX zZ)WFU;b2tXY#_LndGbb4(alAo%a}M!EzNZlj0}u6D@b%R3Niy#1AzoLkZ=W=xv}s& d^JIRNKn|dZAe$H_$Ma0xyily15m{#kBLJxC9xVU> delta 677 zcmZp1XbF&DU|?W$DortDU{C-uIe-{M3-C-V6q~50C~N}cFar4u48=L=hQZ1Cxdj^w zr?F3L;NQ&7!NO4wl4oUbWGG@tWk^J*0gA!^l5B3ii%U{YeiBfMBXs-fn^($?Is(}! zDpCljD9C`g0%CkUm|_Dupcn{K8A{Ncz&hUur~*YE*%7;#^p*cxf*paT2H^}`YQPRr z2Zjg42@gONiXmmeMR_^-dFeoL#?3qeYnkg=83KTTln!(!JTO7Jpa4l0#f>Rj-oOJ2 zu0J;)$%R-|pavAkj>!*%#WtT4UdF^>WMH79U}9)Gd7gm%=G|gGj2jzb*aVp&4pHC+ x(ypL1u(9wv^JIRRKoKTL%FqBQ0Hy{55Iwm-rZ*Do7h1S>@;zY*pj#&~0RUvbolpP( diff --git a/gaseous-server/Classes/Statistics.cs b/gaseous-server/Classes/Statistics.cs new file mode 100644 index 0000000..495d1ac --- /dev/null +++ b/gaseous-server/Classes/Statistics.cs @@ -0,0 +1,99 @@ +using System.Data; +using gaseous_server.Models; + +namespace gaseous_server.Classes +{ + public class Statistics + { + public StatisticsModel RecordSession(Guid SessionId, long GameId, string UserId) + { + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql; + Dictionary dbDict; + + if (SessionId == Guid.Empty) + { + // new session required + SessionId = Guid.NewGuid(); + + sql = "INSERT INTO UserTimeTracking (GameId, UserId, SessionId, SessionTime, SessionLength) VALUES (@gameid, @userid, @sessionid, @sessiontime, @sessionlength);"; + dbDict = new Dictionary{ + { "gameid", GameId }, + { "userid", UserId }, + { "sessionid", SessionId }, + { "sessiontime", DateTime.UtcNow }, + { "sessionlength", 1 } + }; + + db.ExecuteNonQuery(sql, dbDict); + + return new StatisticsModel{ + GameId = GameId, + SessionId = SessionId, + SessionStart = (DateTime)dbDict["sessiontime"], + SessionLength = (int)dbDict["sessionlength"] + }; + } + else + { + // update existing session + sql = "UPDATE UserTimeTracking SET SessionLength = SessionLength + @sessionlength WHERE GameId = @gameid AND UserId = @userid AND SessionId = @sessionid;"; + dbDict = new Dictionary{ + { "gameid", GameId }, + { "userid", UserId }, + { "sessionid", SessionId }, + { "sessionlength", 1 } + }; + + db.ExecuteNonQuery(sql, dbDict); + + sql = "SELECT * FROM UserTimeTracking WHERE GameId = @gameid AND UserId = @userid AND SessionId = @sessionid;"; + DataTable data = db.ExecuteCMD(sql, dbDict); + + return new StatisticsModel{ + GameId = (long)data.Rows[0]["GameId"], + SessionId = Guid.Parse(data.Rows[0]["SessionId"].ToString()), + SessionStart = (DateTime)data.Rows[0]["SessionTime"], + SessionLength = (int)data.Rows[0]["SessionLength"] + }; + } + } + + public StatisticsModel? GetSession(long GameId, string UserId) + { + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "SELECT SUM(SessionLength) AS TotalLength FROM UserTimeTracking WHERE GameId = @gameid AND UserId = @userid;"; + Dictionary dbDict = new Dictionary{ + { "gameid", GameId }, + { "userid", UserId } + }; + + DataTable data = db.ExecuteCMD(sql, dbDict); + + if (data.Rows.Count == 0) + { + return null; + } + else + { + if (data.Rows[0]["TotalLength"] == DBNull.Value) + { + return null; + } + else + { + int TotalTime = int.Parse(data.Rows[0]["TotalLength"].ToString()); + + sql = "SELECT * FROM UserTimeTracking WHERE GameId = @gameid AND UserId = @userid ORDER BY SessionTime DESC LIMIT 1;"; + data = db.ExecuteCMD(sql, dbDict); + + return new StatisticsModel{ + GameId = GameId, + SessionLength = TotalTime, + SessionStart = (DateTime)data.Rows[0]["SessionTime"] + }; + } + } + } + } +} \ No newline at end of file diff --git a/gaseous-server/Controllers/V1.1/StatisticsController.cs b/gaseous-server/Controllers/V1.1/StatisticsController.cs new file mode 100644 index 0000000..22ac5a2 --- /dev/null +++ b/gaseous-server/Controllers/V1.1/StatisticsController.cs @@ -0,0 +1,104 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; +using gaseous_server.Models; +using gaseous_server.Classes; +using Authentication; +using Microsoft.AspNetCore.Identity; +using Asp.Versioning; + +namespace gaseous_server.Controllers.v1_1 +{ + [Route("api/v{version:apiVersion}/[controller]")] + [ApiVersion("1.0")] + [ApiVersion("1.1")] + [ApiController] + public class StatisticsController: ControllerBase + { + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + + public StatisticsController( + UserManager userManager, + SignInManager signInManager + ) + { + _userManager = userManager; + _signInManager = signInManager; + } + + [MapToApiVersion("1.0")] + [MapToApiVersion("1.1")] + [HttpPost] + [Authorize] + [ProducesResponseType(typeof(Models.StatisticsModel), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [Route("Games/{GameId}/")] + public async Task NewRecordStatistics(long GameId) + { + var user = await _userManager.GetUserAsync(User); + + if (user != null) + { + Statistics statistics = new Statistics(); + return Ok(statistics.RecordSession(Guid.Empty, GameId, user.Id)); + } + else + { + return Unauthorized(); + } + } + + [MapToApiVersion("1.0")] + [MapToApiVersion("1.1")] + [HttpPut] + [Authorize] + [ProducesResponseType(typeof(Models.StatisticsModel), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [Route("Games/{GameId}/{SessionId}")] + public async Task SubsequentRecordStatistics(long GameId, Guid SessionId) + { + var user = await _userManager.GetUserAsync(User); + + if (user != null) + { + Statistics statistics = new Statistics(); + return Ok(statistics.RecordSession(SessionId, GameId, user.Id)); + } + else + { + return Unauthorized(); + } + } + + [MapToApiVersion("1.0")] + [MapToApiVersion("1.1")] + [HttpGet] + [Authorize] + [ProducesResponseType(typeof(Models.StatisticsModel), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [Route("Games/{GameId}")] + public async Task GetStatistics(long GameId) + { + var user = await _userManager.GetUserAsync(User); + + if (user != null) + { + Statistics statistics = new Statistics(); + StatisticsModel? model = statistics.GetSession(GameId, user.Id); + if (model == null) + { + return NoContent(); + } + else + { + return Ok(model); + } + } + else + { + return Unauthorized(); + } + } + } +} \ No newline at end of file diff --git a/gaseous-server/Models/StatisticsModel.cs b/gaseous-server/Models/StatisticsModel.cs new file mode 100644 index 0000000..a399924 --- /dev/null +++ b/gaseous-server/Models/StatisticsModel.cs @@ -0,0 +1,17 @@ +namespace gaseous_server.Models +{ + public class StatisticsModel + { + public Guid SessionId { get; set; } = Guid.Empty; + public long GameId { get; set; } + public DateTime SessionStart { get; set; } + public int SessionLength { get; set; } + public DateTime SessionEnd + { + get + { + return SessionStart.AddMinutes(SessionLength); + } + } + } +} \ No newline at end of file diff --git a/gaseous-server/Support/Database/MySQL/gaseous-1020.sql b/gaseous-server/Support/Database/MySQL/gaseous-1020.sql new file mode 100644 index 0000000..4e5dcfe --- /dev/null +++ b/gaseous-server/Support/Database/MySQL/gaseous-1020.sql @@ -0,0 +1,13 @@ +CREATE TABLE `UserTimeTracking` ( + `GameId` BIGINT NULL, + `UserId` VARCHAR(45) NULL, + `SessionId` VARCHAR(45) NULL, + `SessionTime` DATETIME NULL, + `SessionLength` INT NULL, + INDEX `UserId_idx` (`UserId` ASC) VISIBLE, + INDEX `SessionId_idx` (`SessionId` ASC) VISIBLE, + CONSTRAINT `UserId` + FOREIGN KEY (`UserId`) + REFERENCES `Users` (`Id`) + ON DELETE CASCADE + ON UPDATE NO ACTION); diff --git a/gaseous-server/gaseous-server.csproj b/gaseous-server/gaseous-server.csproj index 249d011..032b733 100644 --- a/gaseous-server/gaseous-server.csproj +++ b/gaseous-server/gaseous-server.csproj @@ -61,6 +61,8 @@ + + @@ -102,5 +104,7 @@ + + \ No newline at end of file diff --git a/gaseous-server/wwwroot/.DS_Store b/gaseous-server/wwwroot/.DS_Store index e145abe9a73514c3a5aa4ebe17eb93dae507d671..f620c5da2981dcfc9b18f5642241f354c74ac368 100644 GIT binary patch delta 59 zcmV-B0L1@99unyd Rvj!mh1e4qmFS9Wf)B-Mn643wv delta 46 zcmZn(XbIThFF5&^SSg2*fq{;KiJ|G_(}L2Ic|=tvw+IPumKA!*yO~|#7t1D5W@Z3r CTn=mi diff --git a/gaseous-server/wwwroot/pages/emulator.html b/gaseous-server/wwwroot/pages/emulator.html index 27182d2..3f2c65c 100644 --- a/gaseous-server/wwwroot/pages/emulator.html +++ b/gaseous-server/wwwroot/pages/emulator.html @@ -70,4 +70,30 @@ bg.setAttribute('style', 'background-image: url("/api/v1.1/Games/' + gameId + '/artwork/' + artworks[artworksPosition] + '/image/original/' + artworks[artworksPosition] + '.jpg"); background-position: center; background-repeat: no-repeat; background-size: cover; filter: blur(10px); -webkit-filter: blur(10px);'); } } + + // statistics + var SessionId = undefined; + + function SaveStatistics() { + var model; + if (SessionId == undefined) { + ajaxCall( + '/api/v1.1/Statistics/Games/' + gameId, + 'POST', + function (success) { + SessionId = success.sessionId; + } + ); + } else { + ajaxCall( + '/api/v1.1/Statistics/Games/' + gameId + '/' + SessionId, + 'PUT', + function (success) { + + } + ); + } + } + + setInterval(SaveStatistics, 60000); diff --git a/gaseous-server/wwwroot/pages/game.html b/gaseous-server/wwwroot/pages/game.html index 31feafb..867f33c 100644 --- a/gaseous-server/wwwroot/pages/game.html +++ b/gaseous-server/wwwroot/pages/game.html @@ -63,6 +63,16 @@ +
+
+ Last Played + - +
+
+ Total Play Time + - +
+
@@ -256,6 +266,30 @@ gamePublisherLabel.setAttribute('style', 'display: none;'); } + // load statistics + ajaxCall('/api/v1.1/Statistics/Games/' + gameId, 'GET', function (result) { + var gameStat_lastPlayed = document.getElementById('gamestatistics_lastplayed_value'); + var gameStat_timePlayed = document.getElementById('gamestatistics_timeplayed_value'); + if (result) { + // gameStat_lastPlayed.innerHTML = moment(result.sessionEnd).format("YYYY-MM-DD h:mm:ss a"); + const dateOptions = { + //weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric' + }; + gameStat_lastPlayed.innerHTML = new Date(result.sessionEnd).toLocaleDateString(undefined, dateOptions); + if (result.sessionLength >= 60) { + gameStat_timePlayed.innerHTML = Number(result.sessionLength / 60) + " hours"; + } else { + gameStat_timePlayed.innerHTML = Number(result.sessionLength) + " minutes"; + } + } else { + gameStat_lastPlayed.innerHTML = '-'; + gameStat_timePlayed.innerHTML = '-'; + } + }); + // load release date var gameSummaryRelease = document.getElementById('gamesummary_firstrelease'); if (result.firstReleaseDate) { diff --git a/gaseous-server/wwwroot/styles/style.css b/gaseous-server/wwwroot/styles/style.css index da776bc..778bedf 100644 --- a/gaseous-server/wwwroot/styles/style.css +++ b/gaseous-server/wwwroot/styles/style.css @@ -905,6 +905,31 @@ iframe { border-color: white; } +#gamestatistics { + display: block; + width: 100%; + margin-bottom: 10px; + background-color: rgba(56, 56, 56, 0.3); +} + +.gamestatistics_box { + display: inline-block; + padding: 10px; +} + +.gamestatistics_label { + display: block; + font-weight: bold; + font-size: 14px; + text-transform: uppercase; + padding: 3px; +} + +.gamestatistics_value { + display: block; + padding: 3px; +} + #gamesummarytext_label { }