diff --git a/gaseous-server/Classes/Auth/Classes/IdentityUser.cs b/gaseous-server/Classes/Auth/Classes/IdentityUser.cs index e1ac7a0..8de9b49 100644 --- a/gaseous-server/Classes/Auth/Classes/IdentityUser.cs +++ b/gaseous-server/Classes/Auth/Classes/IdentityUser.cs @@ -12,5 +12,6 @@ namespace Authentication { public SecurityProfileViewModel SecurityProfile { get; set; } public List UserPreferences { get; set; } + public Guid Avatar { get; set; } } } diff --git a/gaseous-server/Classes/Auth/Classes/UserTable.cs b/gaseous-server/Classes/Auth/Classes/UserTable.cs index af02b03..b6f6890 100644 --- a/gaseous-server/Classes/Auth/Classes/UserTable.cs +++ b/gaseous-server/Classes/Auth/Classes/UserTable.cs @@ -75,7 +75,7 @@ namespace Authentication public TUser GetUserById(string userId) { TUser user = null; - string commandText = "Select * from Users where Id = @id"; + string commandText = "Select * from Users LEFT JOIN (SELECT UserId, Id AS AvatarId FROM UserAvatars) UserAvatars ON users.Id = UserAvatars.UserId where Id = @id"; Dictionary parameters = new Dictionary() { { "@id", userId } }; var rows = _database.ExecuteCMDDict(commandText, parameters); @@ -100,6 +100,7 @@ namespace Authentication user.TwoFactorEnabled = row["TwoFactorEnabled"] == "1" ? true:false; user.SecurityProfile = GetSecurityProfile(user); user.UserPreferences = GetPreferences(user); + user.Avatar = string.IsNullOrEmpty((string?)row["AvatarId"]) ? Guid.Empty : Guid.Parse((string?)row["AvatarId"]); } return user; @@ -113,7 +114,7 @@ namespace Authentication public List GetUserByName(string normalizedUserName) { List users = new List(); - string commandText = "Select * from Users where NormalizedEmail = @name"; + string commandText = "Select * from Users LEFT JOIN (SELECT UserId, Id AS AvatarId FROM UserAvatars) UserAvatars ON users.Id = UserAvatars.UserId where NormalizedEmail = @name"; Dictionary parameters = new Dictionary() { { "@name", normalizedUserName } }; var rows = _database.ExecuteCMDDict(commandText, parameters); @@ -137,6 +138,7 @@ namespace Authentication user.TwoFactorEnabled = row["TwoFactorEnabled"] == "1" ? true:false; user.SecurityProfile = GetSecurityProfile(user); user.UserPreferences = GetPreferences(user); + user.Avatar = string.IsNullOrEmpty((string?)row["AvatarId"]) ? Guid.Empty : Guid.Parse((string?)row["AvatarId"]); users.Add(user); } @@ -146,7 +148,7 @@ namespace Authentication public List GetUsers() { List users = new List(); - string commandText = "Select * from Users order by NormalizedUserName"; + string commandText = "Select * from Users LEFT JOIN (SELECT UserId, Id AS AvatarId FROM UserAvatars) UserAvatars ON users.Id = UserAvatars.UserId order by NormalizedUserName"; var rows = _database.ExecuteCMDDict(commandText); foreach(Dictionary row in rows) @@ -169,6 +171,7 @@ namespace Authentication user.TwoFactorEnabled = row["TwoFactorEnabled"] == "1" ? true:false; user.SecurityProfile = GetSecurityProfile(user); user.UserPreferences = GetPreferences(user); + user.Avatar = string.IsNullOrEmpty((string?)row["AvatarId"]) ? Guid.Empty : Guid.Parse((string?)row["AvatarId"]); users.Add(user); } @@ -437,5 +440,30 @@ namespace Authentication return 0; } } + + public Guid SetAvatar(TUser user, byte[] bytes) + { + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql; + Dictionary dbDict = new Dictionary + { + { "userid", user.Id } + }; + + if (bytes.Length == 0) + { + sql = "DELETE FROM UserAvatars WHERE UserId = @userid"; + db.ExecuteNonQuery(sql, dbDict); + return Guid.Empty; + } + else + { + sql = "DELETE FROM UserAvatars WHERE UserId = @userid; INSERT INTO UserAvatars (UserId, Id, Avatar) VALUES (@userid, @id, @avatar);"; + dbDict.Add("id", Guid.NewGuid()); + dbDict.Add("avatar", bytes); + db.ExecuteNonQuery(sql, dbDict); + return (Guid)dbDict["id"]; + } + } } } diff --git a/gaseous-server/Classes/Auth/Models/ProfileViewModel.cs b/gaseous-server/Classes/Auth/Models/ProfileViewModel.cs index a3a9903..ce6e9d1 100644 --- a/gaseous-server/Classes/Auth/Models/ProfileViewModel.cs +++ b/gaseous-server/Classes/Auth/Models/ProfileViewModel.cs @@ -8,6 +8,7 @@ namespace Authentication public List Roles { get; set; } public SecurityProfileViewModel SecurityProfile { get; set; } public List UserPreferences { get; set; } + public Guid Avatar { get; set; } public string HighestRole { get { diff --git a/gaseous-server/Classes/Auth/Models/UserViewModel.cs b/gaseous-server/Classes/Auth/Models/UserViewModel.cs index cedbb8a..e1a6b09 100644 --- a/gaseous-server/Classes/Auth/Models/UserViewModel.cs +++ b/gaseous-server/Classes/Auth/Models/UserViewModel.cs @@ -8,6 +8,7 @@ namespace Authentication public DateTimeOffset? LockoutEnd { get; set; } public List Roles { get; set; } public SecurityProfileViewModel SecurityProfile { get; set; } + public Guid Avatar { get; set; } public string HighestRole { get { diff --git a/gaseous-server/Controllers/V1.0/AccountController.cs b/gaseous-server/Controllers/V1.0/AccountController.cs index e5b3875..92a23f7 100644 --- a/gaseous-server/Controllers/V1.0/AccountController.cs +++ b/gaseous-server/Controllers/V1.0/AccountController.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; using Asp.Versioning; +using IGDB; namespace gaseous_server.Controllers { @@ -98,6 +99,7 @@ namespace gaseous_server.Controllers profile.Roles = new List(await _userManager.GetRolesAsync(user)); profile.SecurityProfile = user.SecurityProfile; profile.UserPreferences = user.UserPreferences; + profile.Avatar = user.Avatar; profile.Roles.Sort(); return Ok(profile); @@ -186,6 +188,7 @@ namespace gaseous_server.Controllers user.LockoutEnabled = rawUser.LockoutEnabled; user.LockoutEnd = rawUser.LockoutEnd; user.SecurityProfile = rawUser.SecurityProfile; + user.Avatar = rawUser.Avatar; // get roles ApplicationUser? aUser = await _userManager.FindByIdAsync(rawUser.Id); @@ -414,5 +417,115 @@ namespace gaseous_server.Controllers return Ok(); } } + + [HttpPost] + [ProducesResponseType(StatusCodes.Status200OK)] + [RequestSizeLimit(long.MaxValue)] + [Consumes("multipart/form-data")] + [DisableRequestSizeLimit, RequestFormLimits(MultipartBodyLengthLimit = long.MaxValue, ValueLengthLimit = int.MaxValue)] + [Route("Avatar")] + public async Task UploadAvatar(IFormFile file) + { + ApplicationUser? user = await _userManager.GetUserAsync(User); + if (user == null) + { + return Unauthorized(); + } + else + { + Guid avatarId = Guid.Empty; + + if (file.Length > 0) + { + using (var ms = new MemoryStream()) + { + file.CopyTo(ms); + byte[] fileBytes = ms.ToArray(); + byte[] targetBytes; + + using (var image = new ImageMagick.MagickImage(fileBytes)) + { + ImageMagick.MagickGeometry size = new ImageMagick.MagickGeometry(256, 256); + + // This will resize the image to a fixed size without maintaining the aspect ratio. + // Normally an image will be resized to fit inside the specified size. + size.IgnoreAspectRatio = true; + + image.Resize(size); + var newMs = new MemoryStream(); + image.Resize(size); + image.Strip(); + image.Write(newMs, ImageMagick.MagickFormat.Jpg); + + targetBytes = newMs.ToArray(); + } + + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + UserTable userTable = new UserTable(db); + avatarId = userTable.SetAvatar(user, targetBytes); + } + } + + return Ok(avatarId); + } + } + + [HttpGet] + [Route("Avatar/{id}.jpg")] + [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult GetAvatar(Guid id) + { + if (id == Guid.Empty) + { + return NotFound(); + } + else + { + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "SELECT * FROM UserAvatars WHERE Id = @id"; + Dictionary dbDict = new Dictionary{ + { "id", id } + }; + + DataTable data = db.ExecuteCMD(sql, dbDict); + + if (data.Rows.Count > 0) + { + string filename = id.ToString() + ".jpg"; + byte[] filedata = (byte[])data.Rows[0]["Avatar"]; + string contentType = "image/jpg"; + + var cd = new System.Net.Mime.ContentDisposition + { + FileName = filename, + Inline = true, + }; + + Response.Headers.Add("Content-Disposition", cd.ToString()); + Response.Headers.Add("Cache-Control", "public, max-age=604800"); + + return File(filedata, contentType); + } + else + { + return NotFound(); + } + } + } + + [HttpDelete] + [Route("Avatar/{id}.jpg")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task DeleteAvatarAsync() + { + ApplicationUser? user = await _userManager.GetUserAsync(User); + + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + UserTable userTable = new UserTable(db); + userTable.SetAvatar(user, new byte[0]); + + return Ok(); + } } } \ No newline at end of file diff --git a/gaseous-server/Support/Database/MySQL/gaseous-1019.sql b/gaseous-server/Support/Database/MySQL/gaseous-1019.sql new file mode 100644 index 0000000..a39eca9 --- /dev/null +++ b/gaseous-server/Support/Database/MySQL/gaseous-1019.sql @@ -0,0 +1,11 @@ +CREATE TABLE `UserAvatars` ( + `UserId` VARCHAR(128) NOT NULL, + `Id` VARCHAR(45) NOT NULL, + `Avatar` LONGBLOB NULL, + PRIMARY KEY (`UserId`), + INDEX `idx_AvatarId` (`Id` ASC) VISIBLE, + CONSTRAINT `ApplicationUser_Avatar` + FOREIGN KEY (`UserId`) + REFERENCES `Users` (`Id`) + ON DELETE CASCADE + ON UPDATE NO ACTION); diff --git a/gaseous-server/Support/PlatformMap.json b/gaseous-server/Support/PlatformMap.json index c5bda53..dbd9bdd 100644 --- a/gaseous-server/Support/PlatformMap.json +++ b/gaseous-server/Support/PlatformMap.json @@ -1590,18 +1590,18 @@ "retroPieDirectoryName": "mastersystem", "webEmulator": { "type": "EmulatorJS", - "core": "segaMS", + "core": "picodrive", "availableWebEmulators": [ { "emulatorType": "EmulatorJS", "availableWebEmulatorCores": [ { - "core": "segaMS", - "alternateCoreName": "picodrive", + "core": "picodrive", "default": true }, { - "core": "genesis_plus_gx" + "core": "segaMS", + "alternateCoreName": "genesis_plus_gx" } ] } diff --git a/gaseous-server/wwwroot/index.html b/gaseous-server/wwwroot/index.html index 4c57e73..6b94d8d 100644 --- a/gaseous-server/wwwroot/index.html +++ b/gaseous-server/wwwroot/index.html @@ -130,6 +130,8 @@ console.log("User is logged in"); userProfile = result; + loadAvatar(userProfile.avatar); + // hide the upload button if it's not permitted var uploadButton = document.getElementById('banner_upload'); if (!userProfile.roles.includes("Admin") && !userProfile.roles.includes("Gamer")) { diff --git a/gaseous-server/wwwroot/pages/dialogs/userprofile.html b/gaseous-server/wwwroot/pages/dialogs/userprofile.html index 0ede6b3..1ca6c0a 100644 --- a/gaseous-server/wwwroot/pages/dialogs/userprofile.html +++ b/gaseous-server/wwwroot/pages/dialogs/userprofile.html @@ -1,5 +1,6 @@
Preferences
+
Avatar
Account
@@ -78,6 +79,17 @@
+