From 278f04d38134650d6f73ef52dcc926c56fedb555 Mon Sep 17 00:00:00 2001 From: Robert Paciorek Date: Sun, 27 Jul 2025 10:30:34 +0000 Subject: [PATCH 1/6] disable null-related warnings --- src/sodoff.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sodoff.csproj b/src/sodoff.csproj index 68159ae..cab8f4d 100644 --- a/src/sodoff.csproj +++ b/src/sodoff.csproj @@ -8,6 +8,8 @@ USE_SQLITE;$(DefineConstants) USE_POSTGRESQL;$(DefineConstants) USE_MYSQL;$(DefineConstants) + + 8600,8601,8602,8603,8604,8618,8625,8629 From fb6c935e7e88403aa9f6e14d1384ce88cbbfbe5d Mon Sep 17 00:00:00 2001 From: Robert Paciorek Date: Sun, 27 Jul 2025 10:34:28 +0000 Subject: [PATCH 2/6] fix upcoming missions in GetUserMissionState * this is bugfix for upcoming missions issue in SoD 2.9 after 60cc00d * also removed TODO because ProductGroupID is not related to mission.GroupID and ProductGroupID filtering is covered by gameVersion --- src/Controllers/Common/ContentController.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Controllers/Common/ContentController.cs b/src/Controllers/Common/ContentController.cs index 6c72812..1c2e6eb 100644 --- a/src/Controllers/Common/ContentController.cs +++ b/src/Controllers/Common/ContentController.cs @@ -1109,14 +1109,17 @@ public class ContentController : Controller { foreach (var m in filterV2.MissionPair) if (m.MissionID != null) result.Missions.Add(missionService.GetMissionWithProgress((int)m.MissionID, viking.Id, gameVersion)); - // TODO: probably should also check for mission based on filterV2.ProductGroupID vs mission.GroupID } else { if (filterV2.GetCompletedMission ?? false) { foreach (var mission in viking.MissionStates.Where(x => x.MissionStatus == MissionStatus.Completed)) result.Missions.Add(missionService.GetMissionWithProgress(mission.MissionId, viking.Id, gameVersion)); } else { - foreach (var mission in viking.MissionStates.Where(x => x.MissionStatus != MissionStatus.Completed)) - result.Missions.Add(missionService.GetMissionWithProgress(mission.MissionId, viking.Id, gameVersion)); + var missionStatesById = viking.MissionStates.Where(x => x.MissionStatus != MissionStatus.Completed).ToDictionary(ms => ms.MissionId); + HashSet upcomingMissionIds = new(missionStore.GetUpcomingMissions(gameVersion)); + var combinedMissionIds = new HashSet(missionStatesById.Keys); + combinedMissionIds.UnionWith(upcomingMissionIds); + foreach (var missionId in combinedMissionIds) + result.Missions.Add(missionService.GetMissionWithProgress(missionId, viking.Id, gameVersion)); } } From 74b24d8ff5f544be0ebc3f00c520c9dc9699cad4 Mon Sep 17 00:00:00 2001 From: Robert Paciorek Date: Thu, 17 Jul 2025 17:39:25 +0000 Subject: [PATCH 3/6] user data export and import interfce (WIP) --- .../Common/ImportExportController.cs | 146 ++++++++++++++++++ src/Model/AchievementPoints.cs | 3 + src/Model/AchievementTaskState.cs | 3 + src/Model/DBContext.cs | 3 + src/Model/Dragon.cs | 7 +- src/Model/GameData.cs | 5 +- src/Model/GameDataPair.cs | 4 + src/Model/Image.cs | 3 + src/Model/InventoryItem.cs | 4 + src/Model/MMORole.cs | 3 + src/Model/MissionState.cs | 6 +- src/Model/Neighborhood.cs | 6 +- src/Model/Pair.cs | 4 + src/Model/PairData.cs | 8 + src/Model/Party.cs | 5 +- src/Model/ProfileAnswer.cs | 4 + src/Model/Rating.cs | 4 + src/Model/RatingRank.cs | 3 + src/Model/Room.cs | 6 +- src/Model/RoomItem.cs | 4 + src/Model/SaveData.cs | 6 +- src/Model/SceneData.cs | 4 + src/Model/TaskStatus.cs | 6 +- src/Model/User.cs | 2 + src/Model/UserBadgeCompleteData.cs | 7 +- src/Model/UserMissionData.cs | 3 + src/Model/Viking.cs | 8 + 27 files changed, 257 insertions(+), 10 deletions(-) create mode 100644 src/Controllers/Common/ImportExportController.cs diff --git a/src/Controllers/Common/ImportExportController.cs b/src/Controllers/Common/ImportExportController.cs new file mode 100644 index 0000000..b429adf --- /dev/null +++ b/src/Controllers/Common/ImportExportController.cs @@ -0,0 +1,146 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.Encodings.Web; +using sodoff.Model; + +namespace sodoff.Controllers.Common; +public class ExportController : ControllerBase { + private readonly DBContext ctx; + + public ExportController(DBContext ctx) { + this.ctx = ctx; + } + + [HttpPost] + [Route("ImportExport.asmx/Export")] + public IActionResult Export([FromForm] string username, [FromForm] string password) { + // Authenticate user by Username + User? user = ctx.Users.FirstOrDefault(e => e.Username == username); + if (user is null || new PasswordHasher().VerifyHashedPassword(null, user.Password, password) == PasswordVerificationResult.Failed) { + return Unauthorized("Invalid username or password."); + } + + // Serialize to JSON + var options = new JsonSerializerOptions + { + ReferenceHandler = ReferenceHandler.IgnoreCycles, + Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + WriteIndented = true + }; + string jsonData = JsonSerializer.Serialize(user, options); + + return Ok(jsonData); + } + + [HttpPost] + [Route("ImportExport.asmx/Import")] + public IActionResult Import([FromForm] string username, [FromForm] string password, [FromForm] string vikingName, [FromForm] IFormFile dataFile) { + User? user = ctx.Users.FirstOrDefault(e => e.Username == username); + if (user is null || new PasswordHasher().VerifyHashedPassword(null, user.Password, password) == PasswordVerificationResult.Failed) { + return Unauthorized("Invalid username or password."); + } + + User user_data; + using (var reader = new StreamReader(dataFile.OpenReadStream())) { + user_data = System.Text.Json.JsonSerializer.Deserialize(reader.ReadToEnd()); + } + + foreach (var v in user_data.Vikings) { + if (v.Name == vikingName) { + Viking viking = new Viking { + Uid = v.Uid, // TODO check for unique or just generate new? + Name = v.Name, // TODO check for unique + User = user, + AvatarSerialized = v.AvatarSerialized, + CreationDate = v.CreationDate, // TODO or use now? + BirthDate = v.BirthDate, + Gender = v.Gender, + GameVersion = v.GameVersion + }; + user.Vikings.Add(viking); + + foreach (var x in v.Dragons) { + x.Viking = viking; + // TODO check EntityId for unique or just generate new? + x.Id = 0; // FIXME map old→new value for dragon id to update (stables) xml's + ctx.Dragons.Add(x); + } + foreach (var x in v.Images) { + x.Viking = viking; + ctx.Images.Add(x); + } + foreach (var x in v.InventoryItems) { + x.Id = 0; // FIXME map old→new value for item id to update xml's and rooms + x.Viking = viking; + ctx.InventoryItems.Add(x); + } + foreach (var x in v.Rooms) { + x.Viking = viking; + ctx.Rooms.Add(x); // FIXME need update room name (if numeric) + } + foreach (var x in v.MissionStates) { + x.Viking = viking; + ctx.MissionStates.Add(x); + } + foreach (var x in v.TaskStatuses) { + x.Viking = viking; + ctx.TaskStatuses.Add(x); + } + foreach (var x in v.AchievementTaskStates) { + x.Viking = viking; + ctx.AchievementTaskState.Add(x); + } + foreach (var x in v.AchievementPoints) { + x.Viking = viking; + ctx.AchievementPoints.Add(x); + } + foreach (var x in v.PairData) { + x.Viking = viking; + ctx.PairData.Add(x); // FIXME need update PetID in stable XML + } + foreach (var x in v.ProfileAnswers) { + x.Viking = viking; + ctx.ProfileAnswers.Add(x); + } + foreach (var x in v.GameData) { + x.Viking = viking; + ctx.GameData.Add(x); + } + foreach (var x in v.SavedData) { + x.Viking = viking; + ctx.SavedData.Add(x); + } + foreach (var x in v.Parties) { + x.Viking = viking; + ctx.Parties.Add(x); + } + foreach (var x in v.UserMissions) { + x.Viking = viking; + ctx.UserMissions.Add(x); + } + foreach (var x in v.UserBadgesCompleted) { + x.Viking = viking; + ctx.UserBadgesCompleted.Add(x); + } + if (v.Ratings.Count > 0) { + viking.Ratings = new List(); + foreach (var x in v.Ratings) { + // TODO (non-SoD) add rating via SetRating(viking, x.Rank.CategoryID, x.Rank.RatedEntityID, x.Rank.RatedUserID, x.Value); + } + } + if (v.Neighborhood != null) { + v.Neighborhood.Viking = viking; + ctx.Neighborhoods.Add(v.Neighborhood); + } + // TODO set viking.SelectedDragon + + ctx.SaveChanges(); + return Ok("OK"); + } + } + return Ok("Viking Not Found"); + } +} diff --git a/src/Model/AchievementPoints.cs b/src/Model/AchievementPoints.cs index 6cfc402..94c5b2b 100644 --- a/src/Model/AchievementPoints.cs +++ b/src/Model/AchievementPoints.cs @@ -1,12 +1,15 @@ using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace sodoff.Model; public class AchievementPoints { + [JsonIgnore] public int VikingId { get; set; } public int Type { get; set; } public int Value { get; set; } + [JsonIgnore] public virtual Viking? Viking { get; set; } } diff --git a/src/Model/AchievementTaskState.cs b/src/Model/AchievementTaskState.cs index ef2edb6..27f4f03 100644 --- a/src/Model/AchievementTaskState.cs +++ b/src/Model/AchievementTaskState.cs @@ -1,15 +1,18 @@ using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; using Microsoft.EntityFrameworkCore; namespace sodoff.Model; [PrimaryKey(nameof(TaskId), nameof(VikingId))] public class AchievementTaskState { + [JsonIgnore] public int VikingId { get; set; } public int TaskId { get; set; } public int Points { get; set; } + [JsonIgnore] public virtual Viking? Viking { get; set; } } diff --git a/src/Model/DBContext.cs b/src/Model/DBContext.cs index 1832763..02b01a9 100644 --- a/src/Model/DBContext.cs +++ b/src/Model/DBContext.cs @@ -23,6 +23,9 @@ public class DBContext : DbContext { public DbSet ProfileAnswers { get; set; } = null!; public DbSet MMORoles { get; set; } = null!; public DbSet Parties { get; set; } = null!; + public DbSet AchievementTaskState { get; set; } = null!; + public DbSet SavedData { get; set; } = null!; + public DbSet UserMissions { get; set; } = null!; public DbSet Neighborhoods { get; set; } = null!; // we had a brief debate on whether it's neighborhoods or neighborheed public DbSet Groups { get; set; } = null!; diff --git a/src/Model/Dragon.cs b/src/Model/Dragon.cs index f8f636a..b3e4ac3 100644 --- a/src/Model/Dragon.cs +++ b/src/Model/Dragon.cs @@ -1,22 +1,27 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; namespace sodoff.Model; public class Dragon { [Key] + // [JsonIgnore] used in serialised xml (stables) [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } [Required] public Guid EntityId { get; set; } + [Required] + [JsonIgnore] public int VikingId { get; set; } public string? RaisedPetData { get; set; } public int? PetXP { get; set; } - + + [JsonIgnore] public virtual Viking Viking { get; set; } = null!; public virtual ICollection PairData { get; set; } = null!; } diff --git a/src/Model/GameData.cs b/src/Model/GameData.cs index 56f801c..7db40d3 100644 --- a/src/Model/GameData.cs +++ b/src/Model/GameData.cs @@ -1,10 +1,13 @@ using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace sodoff.Model; public class GameData { [Key] + [JsonIgnore] public int Id { get; set; } + [JsonIgnore] public int VikingId { get; set; } public int GameId { get; set; } @@ -15,6 +18,6 @@ public class GameData { public bool Win { get; set; } public bool Loss { get; set; } public virtual ICollection GameDataPairs { get; set; } = null!; + [JsonIgnore] public virtual Viking Viking { get; set; } = null!; - } diff --git a/src/Model/GameDataPair.cs b/src/Model/GameDataPair.cs index 9543936..e5d45aa 100644 --- a/src/Model/GameDataPair.cs +++ b/src/Model/GameDataPair.cs @@ -1,11 +1,15 @@ using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace sodoff.Model; public class GameDataPair { [Key] + [JsonIgnore] public int Id { get; set; } + [JsonIgnore] public int GameDataId { get; set; } public string Name { get; set; } = null!; public int Value { get; set; } + [JsonIgnore] public virtual GameData GameData { get; set; } = null!; } diff --git a/src/Model/Image.cs b/src/Model/Image.cs index f12727c..f671c4d 100644 --- a/src/Model/Image.cs +++ b/src/Model/Image.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; using Microsoft.EntityFrameworkCore; namespace sodoff.Model; @@ -12,11 +13,13 @@ public class Image { public int ImageSlot { get; set; } [Required] + [JsonIgnore] public int VikingId { get; set; } public string? ImageData { get; set; } public string? TemplateName { get; set; } + [JsonIgnore] public virtual Viking Viking { get; set; } = null!; } diff --git a/src/Model/InventoryItem.cs b/src/Model/InventoryItem.cs index ea6c5d1..563fba9 100644 --- a/src/Model/InventoryItem.cs +++ b/src/Model/InventoryItem.cs @@ -1,18 +1,22 @@ using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace sodoff.Model { public class InventoryItem { [Key] + // [JsonIgnore] used as room id, used in serialised xml (pairs, ...) public int Id { get; set; } public int ItemId { get; set; } + [JsonIgnore] public int VikingId { get; set; } public string? StatsSerialized { get; set; } public string? AttributesSerialized { get; set; } + [JsonIgnore] public virtual Viking Viking { get; set; } = null!; public int Quantity { get; set; } diff --git a/src/Model/MMORole.cs b/src/Model/MMORole.cs index 8f97762..4a72c1a 100644 --- a/src/Model/MMORole.cs +++ b/src/Model/MMORole.cs @@ -1,15 +1,18 @@ using sodoff.Schema; using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace sodoff.Model; public class MMORole { [Key] + [JsonIgnore] public int Id { get; set; } public int VikingId { get; set; } public Role Role { get; set; } + [JsonIgnore] public virtual Viking? Viking { get; set; } } diff --git a/src/Model/MissionState.cs b/src/Model/MissionState.cs index b82f1b0..c99d738 100644 --- a/src/Model/MissionState.cs +++ b/src/Model/MissionState.cs @@ -1,15 +1,19 @@ using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace sodoff.Model; public class MissionState { [Key] + [JsonIgnore] public int Id { get; set; } public int MissionId { get; set; } + [JsonIgnore] public int VikingId { get; set; } + [JsonIgnore] public virtual Viking? Viking { get; set; } public MissionStatus MissionStatus { get; set; } @@ -19,4 +23,4 @@ public class MissionState { public enum MissionStatus { Upcoming,Active,Completed -} \ No newline at end of file +} diff --git a/src/Model/Neighborhood.cs b/src/Model/Neighborhood.cs index a748c76..67b363c 100644 --- a/src/Model/Neighborhood.cs +++ b/src/Model/Neighborhood.cs @@ -1,18 +1,20 @@ using sodoff.Schema; using System.ComponentModel.DataAnnotations; -using System.Data; -using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Serialization; namespace sodoff.Model { public class Neighborhood { + [JsonIgnore] public virtual Viking? Viking { get; set; } [Key] + [JsonIgnore] public int Id { get; set; } [Required] + [JsonIgnore] public int VikingId { get; set; } [Required] diff --git a/src/Model/Pair.cs b/src/Model/Pair.cs index 5c6aca6..abc55cc 100644 --- a/src/Model/Pair.cs +++ b/src/Model/Pair.cs @@ -1,18 +1,22 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; namespace sodoff.Model; public class Pair { [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [JsonIgnore] public int Id { get; set; } public string Key { get; set; } = null!; public string Value { get; set; } = null!; + [JsonIgnore] public int MasterId { get; set; } + [JsonIgnore] public virtual PairData PairData { get; set; } = null!; } diff --git a/src/Model/PairData.cs b/src/Model/PairData.cs index 9f11ce5..9db929a 100644 --- a/src/Model/PairData.cs +++ b/src/Model/PairData.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; namespace sodoff.Model; @@ -9,21 +10,28 @@ namespace sodoff.Model; public class PairData { [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [JsonIgnore] public int Id { get; set; } public int PairId { get; set; } + [JsonIgnore] public Guid? UserId { get; set; } + [JsonIgnore] public int? VikingId { get; set; } + [JsonIgnore] public int? DragonId { get; set; } public virtual ICollection Pairs { get; set; } + [JsonIgnore] public virtual User? User { get; set; } + [JsonIgnore] public virtual Viking? Viking { get; set; } + [JsonIgnore] public virtual Dragon? Dragon { get; set; } } diff --git a/src/Model/Party.cs b/src/Model/Party.cs index 9667da5..dd4b5ab 100644 --- a/src/Model/Party.cs +++ b/src/Model/Party.cs @@ -1,18 +1,21 @@ using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; namespace sodoff.Model { public class Party { [Key] + [JsonIgnore] public int Id { get; set; } public string Location { get; set; } = null!; + [JsonIgnore] public int VikingId { get; set; } public DateTime ExpirationDate { get; set; } = DateTime.UtcNow; public bool? PrivateParty { get; set; } public string LocationIconAsset { get; set; } = null!; public string AssetBundle { get; set; } = null!; + [JsonIgnore] public virtual Viking? Viking { get; set; } } } diff --git a/src/Model/ProfileAnswer.cs b/src/Model/ProfileAnswer.cs index 4aae801..ff4982b 100644 --- a/src/Model/ProfileAnswer.cs +++ b/src/Model/ProfileAnswer.cs @@ -1,15 +1,19 @@ using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace sodoff.Model { public class ProfileAnswer { [Key] + [JsonIgnore] public int Id { get; set; } + [JsonIgnore] public int VikingId { get; set; } public int QuestionID { get; set; } public int AnswerID { get; set; } + [JsonIgnore] public virtual Viking Viking { get; set; } = null!; } } diff --git a/src/Model/Rating.cs b/src/Model/Rating.cs index 565e736..a195a54 100644 --- a/src/Model/Rating.cs +++ b/src/Model/Rating.cs @@ -1,17 +1,21 @@ using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace sodoff.Model; public class Rating { [Key] + [JsonIgnore] public int Id { get; set; } + [JsonIgnore] public int VikingId { get; set; } public int RankId { get; set; } public int Value { get; set; } public DateTime Date { get; set; } + [JsonIgnore] public virtual Viking Viking { get; set; } public virtual RatingRank Rank { get; set; } } diff --git a/src/Model/RatingRank.cs b/src/Model/RatingRank.cs index 1eebec1..359f9b3 100644 --- a/src/Model/RatingRank.cs +++ b/src/Model/RatingRank.cs @@ -1,9 +1,11 @@ using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace sodoff.Model; public class RatingRank { [Key] + [JsonIgnore] public int Id { get; set; } public int CategoryID { get; set; } @@ -14,5 +16,6 @@ public class RatingRank { public float RatingAverage { get; set; } // On a scale of 1-5 public DateTime UpdateDate { get; set; } + [JsonIgnore] public virtual ICollection Ratings { get; set; } = null!; } diff --git a/src/Model/Room.cs b/src/Model/Room.cs index 6089e1f..6f8fe48 100644 --- a/src/Model/Room.cs +++ b/src/Model/Room.cs @@ -1,18 +1,22 @@ using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace sodoff.Model; public class Room { [Key] + [JsonIgnore] public int Id { get; set; } public string RoomId { get; set; } + [JsonIgnore] public int VikingId { get; set; } public string? Name { get; set; } + [JsonIgnore] public virtual Viking? Viking { get; set; } public virtual ICollection Items { get; set; } = null!; -} \ No newline at end of file +} diff --git a/src/Model/RoomItem.cs b/src/Model/RoomItem.cs index 329b0bd..8701dd9 100644 --- a/src/Model/RoomItem.cs +++ b/src/Model/RoomItem.cs @@ -1,12 +1,16 @@ using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace sodoff.Model; public class RoomItem { [Key] + [JsonIgnore] public int Id { get; set; } + [JsonIgnore] public int RoomId { get; set; } + [JsonIgnore] public virtual Room Room { get; set; } public string RoomItemData { get; set; } diff --git a/src/Model/SaveData.cs b/src/Model/SaveData.cs index 0d411cf..f455241 100644 --- a/src/Model/SaveData.cs +++ b/src/Model/SaveData.cs @@ -1,8 +1,12 @@ -namespace sodoff.Model; +using System.Text.Json.Serialization; + +namespace sodoff.Model; public class SavedData { + [JsonIgnore] public int VikingId { get; set; } public uint SaveId { get; set; } public string? SerializedData { get; set; } + [JsonIgnore] public virtual Viking Viking { get; set; } = null!; } diff --git a/src/Model/SceneData.cs b/src/Model/SceneData.cs index 5e17821..6a39cfc 100644 --- a/src/Model/SceneData.cs +++ b/src/Model/SceneData.cs @@ -1,14 +1,18 @@ using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace sodoff.Model { public class SceneData { [Key] + [JsonIgnore] public int Id { get; set; } public int VikingId { get; set; } public string SceneName { get; set; } = null!; public string XmlData { get; set; } = null!; + + [JsonIgnore] public virtual Viking Viking { get; set; } = null!; } } diff --git a/src/Model/TaskStatus.cs b/src/Model/TaskStatus.cs index 9e19f2b..a83857c 100644 --- a/src/Model/TaskStatus.cs +++ b/src/Model/TaskStatus.cs @@ -1,11 +1,15 @@ -namespace sodoff.Model { +using System.Text.Json.Serialization; + +namespace sodoff.Model { public class TaskStatus { public int Id { get; set; } public int MissionId { get; set; } + [JsonIgnore] public int VikingId { get; set; } + [JsonIgnore] public virtual Viking? Viking { get; set; } public string? Payload { get; set; } diff --git a/src/Model/User.cs b/src/Model/User.cs index 358feb1..2d3d579 100644 --- a/src/Model/User.cs +++ b/src/Model/User.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace sodoff.Model; public class User { @@ -14,6 +15,7 @@ public class User { [Required] public string Password { get; set; } = null!; + [JsonIgnore] public virtual ICollection Sessions { get; set; } = null!; public virtual ICollection Vikings { get; set; } = null!; public virtual ICollection PairData { get; set; } = null!; diff --git a/src/Model/UserBadgeCompleteData.cs b/src/Model/UserBadgeCompleteData.cs index efab4c9..d96eafd 100644 --- a/src/Model/UserBadgeCompleteData.cs +++ b/src/Model/UserBadgeCompleteData.cs @@ -1,11 +1,16 @@ -namespace sodoff.Model +using System.Text.Json.Serialization; + +namespace sodoff.Model { public class UserBadgeCompleteData { + [JsonIgnore] public int Id { get; set; } + [JsonIgnore] public int VikingId { get; set; } public int BadgeId { get; set; } + [JsonIgnore] public virtual Viking? Viking { get; set; } } } diff --git a/src/Model/UserMissionData.cs b/src/Model/UserMissionData.cs index 1a971b9..16a5cf8 100644 --- a/src/Model/UserMissionData.cs +++ b/src/Model/UserMissionData.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; using Microsoft.EntityFrameworkCore; namespace sodoff.Model @@ -6,6 +7,7 @@ namespace sodoff.Model [PrimaryKey(nameof(VikingId), nameof(WorldId), nameof(MissionId))] public class UserMissionData { + [JsonIgnore] public int VikingId { get; set; } public int WorldId { get; set; } public int MissionId { get; set; } @@ -13,6 +15,7 @@ namespace sodoff.Model public int TaskId { get; set; } public bool IsCompleted { get; set; } = false; + [JsonIgnore] public virtual Viking? Viking { get; set; } } } diff --git a/src/Model/Viking.cs b/src/Model/Viking.cs index a787107..e2b8ca4 100644 --- a/src/Model/Viking.cs +++ b/src/Model/Viking.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; using sodoff.Schema; namespace sodoff.Model; @@ -7,6 +8,7 @@ namespace sodoff.Model; [Index(nameof(Uid))] public class Viking { [Key] + [JsonIgnore] public int Id { get; set; } public Guid Uid { get; set; } @@ -15,13 +17,17 @@ public class Viking { public string Name { get; set; } = null!; [Required] + [JsonIgnore] public Guid UserId { get; set; } public string? AvatarSerialized { get; set; } + [JsonIgnore] public int? SelectedDragonId { get; set; } + [JsonIgnore] public virtual ICollection Sessions { get; set; } = null!; + [JsonIgnore] public virtual User User { get; set; } = null!; public virtual ICollection Dragons { get; set; } = null!; public virtual ICollection Images { get; set; } = null!; @@ -37,8 +43,10 @@ public class Viking { public virtual ICollection ProfileAnswers { get; set; } = null!; public virtual ICollection SavedData { get; set; } = null!; public virtual ICollection Parties { get; set; } = null!; + [JsonIgnore] public virtual ICollection MMORoles { get; set; } = null!; public virtual Neighborhood? Neighborhood { get; set; } = null!; + [JsonIgnore] public virtual ICollection Groups { get; set; } = null!; public virtual ICollection Ratings { get; set; } = null!; public virtual Dragon? SelectedDragon { get; set; } From ea75d182a6a76d07927500626364a71285229903 Mon Sep 17 00:00:00 2001 From: Robert Paciorek Date: Mon, 28 Jul 2025 09:43:36 +0000 Subject: [PATCH 4/6] user data export and import interfce (enhance) * update items and dragons id on import * check for viking name unique * add unique constraints in database * add simple import/export html form for localhosted srvers --- .../Common/ImportExportController.cs | 95 +++++++++++++++---- src/Model/Dragon.cs | 4 +- src/Model/User.cs | 5 +- src/Model/Viking.cs | 2 +- src/Resources/html/export.xml | 16 ++++ src/Resources/html/import.xml | 24 +++++ src/Schema/NestData.cs | 13 +++ src/Schema/StableData.cs | 22 +++++ src/Schema/UserRankData.cs | 23 +++++ src/sodoff.csproj | 6 ++ 10 files changed, 188 insertions(+), 22 deletions(-) create mode 100644 src/Resources/html/export.xml create mode 100644 src/Resources/html/import.xml create mode 100644 src/Schema/NestData.cs create mode 100644 src/Schema/StableData.cs create mode 100644 src/Schema/UserRankData.cs diff --git a/src/Controllers/Common/ImportExportController.cs b/src/Controllers/Common/ImportExportController.cs index b429adf..1631bbf 100644 --- a/src/Controllers/Common/ImportExportController.cs +++ b/src/Controllers/Common/ImportExportController.cs @@ -5,6 +5,8 @@ using System.Text.Json; using System.Text.Json.Serialization; using System.Text.Encodings.Web; using sodoff.Model; +using sodoff.Schema; +using sodoff.Util; namespace sodoff.Controllers.Common; public class ExportController : ControllerBase { @@ -15,7 +17,7 @@ public class ExportController : ControllerBase { } [HttpPost] - [Route("ImportExport.asmx/Export")] + [Route("Export")] public IActionResult Export([FromForm] string username, [FromForm] string password) { // Authenticate user by Username User? user = ctx.Users.FirstOrDefault(e => e.Username == username); @@ -36,8 +38,8 @@ public class ExportController : ControllerBase { } [HttpPost] - [Route("ImportExport.asmx/Import")] - public IActionResult Import([FromForm] string username, [FromForm] string password, [FromForm] string vikingName, [FromForm] IFormFile dataFile) { + [Route("Import")] + public IActionResult Import([FromForm] string username, [FromForm] string password, [FromForm] string vikingName, [FromForm] IFormFile dataFile, [FromForm] string? newVikingName) { User? user = ctx.Users.FirstOrDefault(e => e.Username == username); if (user is null || new PasswordHasher().VerifyHashedPassword(null, user.Password, password) == PasswordVerificationResult.Failed) { return Unauthorized("Invalid username or password."); @@ -50,36 +52,81 @@ public class ExportController : ControllerBase { foreach (var v in user_data.Vikings) { if (v.Name == vikingName) { + if (String.IsNullOrEmpty(newVikingName)) + newVikingName = vikingName; + + if (ctx.Vikings.Count(e => e.Name == newVikingName) > 0) { + return Conflict("Viking name already in use"); + } + + if (newVikingName != vikingName) { + AvatarData avatarData = XmlUtil.DeserializeXml(v.AvatarSerialized); + avatarData.DisplayName = newVikingName; + v.AvatarSerialized = XmlUtil.SerializeXml(avatarData); + } + Viking viking = new Viking { - Uid = v.Uid, // TODO check for unique or just generate new? - Name = v.Name, // TODO check for unique + Uid = Guid.NewGuid(), + Name = newVikingName, User = user, AvatarSerialized = v.AvatarSerialized, - CreationDate = v.CreationDate, // TODO or use now? + CreationDate = DateTime.UtcNow, BirthDate = v.BirthDate, Gender = v.Gender, GameVersion = v.GameVersion }; user.Vikings.Add(viking); + Dictionary dragonIds = new(); foreach (var x in v.Dragons) { x.Viking = viking; - // TODO check EntityId for unique or just generate new? - x.Id = 0; // FIXME map old→new value for dragon id to update (stables) xml's + x.EntityId = Guid.NewGuid(); + dragonIds.Add(x.Id, x.EntityId); + x.Id = 0; ctx.Dragons.Add(x); } - foreach (var x in v.Images) { - x.Viking = viking; - ctx.Images.Add(x); - } + Dictionary itemIds = new(); foreach (var x in v.InventoryItems) { - x.Id = 0; // FIXME map old→new value for item id to update xml's and rooms + itemIds.Add(x.Id, x.ItemId); + x.Id = 0; x.Viking = viking; ctx.InventoryItems.Add(x); } + + ctx.SaveChanges(); // need for get new ids of dragons and items + + HashSet usedItemIds = new(); foreach (var x in v.Rooms) { x.Viking = viking; - ctx.Rooms.Add(x); // FIXME need update room name (if numeric) + if (int.TryParse(x.RoomId, out int roomID)) { + // numeric room name is inventory item id + // remap old value to new value based on item id value + roomID = viking.InventoryItems.FirstOrDefault(e => e.ItemId == itemIds[roomID] && !usedItemIds.Contains(e.Id)).Id; + usedItemIds.Add(roomID); + x.RoomId = roomID.ToString(); + } + ctx.Rooms.Add(x); + } + foreach (var x in v.PairData) { + x.Viking = viking; + if (x.PairId == 2014) { // stables data + foreach (var p in x.Pairs.Where(e => e.Key.StartsWith("Stable"))) { + StableData stableData = XmlUtil.DeserializeXml(p.Value); + stableData.InventoryID = viking.InventoryItems.FirstOrDefault(e => e.ItemId == stableData.ItemID && !usedItemIds.Contains(e.Id)).Id; + usedItemIds.Add(stableData.InventoryID); + foreach (var n in stableData.NestList) { + if (n.PetID != 0) + n.PetID = viking.Dragons.FirstOrDefault(d => d.EntityId == dragonIds[n.PetID]).Id; + } + p.Value = XmlUtil.SerializeXml(stableData); + } + } + ctx.PairData.Add(x); + } + + foreach (var x in v.Images) { + x.Viking = viking; + ctx.Images.Add(x); } foreach (var x in v.MissionStates) { x.Viking = viking; @@ -97,10 +144,6 @@ public class ExportController : ControllerBase { x.Viking = viking; ctx.AchievementPoints.Add(x); } - foreach (var x in v.PairData) { - x.Viking = viking; - ctx.PairData.Add(x); // FIXME need update PetID in stable XML - } foreach (var x in v.ProfileAnswers) { x.Viking = viking; ctx.ProfileAnswers.Add(x); @@ -135,7 +178,9 @@ public class ExportController : ControllerBase { v.Neighborhood.Viking = viking; ctx.Neighborhoods.Add(v.Neighborhood); } - // TODO set viking.SelectedDragon + + if (v.SelectedDragon != null) + viking.SelectedDragon = viking.Dragons.FirstOrDefault(d => d.EntityId == dragonIds[v.SelectedDragon.Id]); ctx.SaveChanges(); return Ok("OK"); @@ -143,4 +188,16 @@ public class ExportController : ControllerBase { } return Ok("Viking Not Found"); } + + [HttpGet] + [Route("Export")] + public IActionResult Export() { + return Content(XmlUtil.ReadResourceXmlString("html.export"), "application/xhtml+xml"); + } + + [HttpGet] + [Route("Import")] + public IActionResult Import() { + return Content(XmlUtil.ReadResourceXmlString("html.import"), "application/xhtml+xml"); + } } diff --git a/src/Model/Dragon.cs b/src/Model/Dragon.cs index b3e4ac3..cdfd831 100644 --- a/src/Model/Dragon.cs +++ b/src/Model/Dragon.cs @@ -1,9 +1,11 @@ -using System.ComponentModel.DataAnnotations; +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; namespace sodoff.Model; +[Index(nameof(EntityId), IsUnique = true)] public class Dragon { [Key] // [JsonIgnore] used in serialised xml (stables) diff --git a/src/Model/User.cs b/src/Model/User.cs index 2d3d579..de4beb3 100644 --- a/src/Model/User.cs +++ b/src/Model/User.cs @@ -1,7 +1,10 @@ -using System.ComponentModel.DataAnnotations; +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations; using System.Text.Json.Serialization; namespace sodoff.Model; + +[Index(nameof(Username), IsUnique = true)] public class User { [Key] public Guid Id { get; set; } diff --git a/src/Model/Viking.cs b/src/Model/Viking.cs index e2b8ca4..86a86e9 100644 --- a/src/Model/Viking.cs +++ b/src/Model/Viking.cs @@ -5,7 +5,7 @@ using sodoff.Schema; namespace sodoff.Model; -[Index(nameof(Uid))] +[Index(nameof(Uid), IsUnique = true)] public class Viking { [Key] [JsonIgnore] diff --git a/src/Resources/html/export.xml b/src/Resources/html/export.xml new file mode 100644 index 0000000..d93c359 --- /dev/null +++ b/src/Resources/html/export.xml @@ -0,0 +1,16 @@ + + + + + Export SoDOff account + + +
+
+
+
+

+ +
+ + diff --git a/src/Resources/html/import.xml b/src/Resources/html/import.xml new file mode 100644 index 0000000..3ed291c --- /dev/null +++ b/src/Resources/html/import.xml @@ -0,0 +1,24 @@ + + + + + Import SoDOff account + + +
+
+
+
+

+ +
+
+
+
+
+

+ + +
+ + diff --git a/src/Schema/NestData.cs b/src/Schema/NestData.cs new file mode 100644 index 0000000..c115c54 --- /dev/null +++ b/src/Schema/NestData.cs @@ -0,0 +1,13 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "NestData", Namespace = "")] +[Serializable] +public class NestData { + [XmlElement(ElementName = "PetID")] + public int PetID; + + [XmlElement(ElementName = "ID")] + public int ID; +} diff --git a/src/Schema/StableData.cs b/src/Schema/StableData.cs new file mode 100644 index 0000000..dff5268 --- /dev/null +++ b/src/Schema/StableData.cs @@ -0,0 +1,22 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "StableData", Namespace = "")] +[Serializable] +public class StableData { + [XmlElement(ElementName = "Name")] + public string Name; + + [XmlElement(ElementName = "ID")] + public int ID; + + [XmlElement(ElementName = "ItemID")] + public int ItemID; + + [XmlElement(ElementName = "InventoryID")] + public int InventoryID; + + [XmlElement(ElementName = "Nests")] + public List NestList; +} diff --git a/src/Schema/UserRankData.cs b/src/Schema/UserRankData.cs new file mode 100644 index 0000000..fa9fc48 --- /dev/null +++ b/src/Schema/UserRankData.cs @@ -0,0 +1,23 @@ +using System.Diagnostics; +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "UserRankData", Namespace = "")] +[Serializable] +public class UserRankData { + [XmlElement(ElementName = "UserID")] + public Guid UserID; + + [XmlElement(ElementName = "Points")] + public int Points; + + [XmlElement(ElementName = "CurrentRank")] + public UserRank CurrentRank; + + [XmlElement(ElementName = "MemberRank")] + public UserRank MemberRank; + + [XmlElement(ElementName = "NextRank")] + public UserRank NextRank; +} diff --git a/src/sodoff.csproj b/src/sodoff.csproj index cab8f4d..4e30ed9 100644 --- a/src/sodoff.csproj +++ b/src/sodoff.csproj @@ -155,5 +155,11 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + From 59c722fe65bc0fc9fb5fb8b0ec7d0226c1fe10f7 Mon Sep 17 00:00:00 2001 From: Robert Paciorek Date: Tue, 12 Aug 2025 20:36:44 +0000 Subject: [PATCH 5/6] importer bugfix - error on stable imports stables do not use unique inventory slots --- src/Controllers/Common/ImportExportController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controllers/Common/ImportExportController.cs b/src/Controllers/Common/ImportExportController.cs index 1631bbf..92dfea9 100644 --- a/src/Controllers/Common/ImportExportController.cs +++ b/src/Controllers/Common/ImportExportController.cs @@ -112,7 +112,7 @@ public class ExportController : ControllerBase { if (x.PairId == 2014) { // stables data foreach (var p in x.Pairs.Where(e => e.Key.StartsWith("Stable"))) { StableData stableData = XmlUtil.DeserializeXml(p.Value); - stableData.InventoryID = viking.InventoryItems.FirstOrDefault(e => e.ItemId == stableData.ItemID && !usedItemIds.Contains(e.Id)).Id; + stableData.InventoryID = viking.InventoryItems.FirstOrDefault(e => e.ItemId == stableData.ItemID).Id; usedItemIds.Add(stableData.InventoryID); foreach (var n in stableData.NestList) { if (n.PetID != 0) From f8b26e468b7d6d3bcd69628c11255a4ca2798417 Mon Sep 17 00:00:00 2001 From: Spirtix Date: Thu, 25 Sep 2025 17:44:04 +0200 Subject: [PATCH 6/6] fix dragons with predefined name getting a random name --- src/Controllers/Common/ContentController.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Controllers/Common/ContentController.cs b/src/Controllers/Common/ContentController.cs index 1c2e6eb..bfe965f 100644 --- a/src/Controllers/Common/ContentController.cs +++ b/src/Controllers/Common/ContentController.cs @@ -580,7 +580,8 @@ public class ContentController : Controller { raisedPetRequest.RaisedPetData.IsPetCreated = true; raisedPetRequest.RaisedPetData.RaisedPetID = 0; // Initially make zero, so the db auto-fills raisedPetRequest.RaisedPetData.EntityID = Guid.Parse(dragonId); - raisedPetRequest.RaisedPetData.Name = string.Concat("Dragon-", dragonId.AsSpan(0, 8)); // Start off with a random name + if (string.IsNullOrEmpty(raisedPetRequest.RaisedPetData.Name)) + raisedPetRequest.RaisedPetData.Name = string.Concat("Dragon-", dragonId.AsSpan(0, 8)); // Start off with a random name raisedPetRequest.RaisedPetData.IsSelected = false; // The api returns false, not sure why raisedPetRequest.RaisedPetData.CreateDate = new DateTime(DateTime.Now.Ticks); raisedPetRequest.RaisedPetData.UpdateDate = new DateTime(DateTime.Now.Ticks);