diff --git a/gaseous-server/.DS_Store b/gaseous-server/.DS_Store index fc2bf7c..1f84f25 100644 Binary files a/gaseous-server/.DS_Store and b/gaseous-server/.DS_Store differ 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 e145abe..f620c5d 100644 Binary files a/gaseous-server/wwwroot/.DS_Store and b/gaseous-server/wwwroot/.DS_Store differ 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 { }