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
This commit is contained in:
Robert Paciorek 2025-07-28 09:43:36 +00:00 committed by Spirtix
parent 74b24d8ff5
commit ea75d182a6
10 changed files with 188 additions and 22 deletions

View File

@ -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<object>().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<AvatarData>(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<int, Guid> 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<int, int> 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<int> 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<StableData>(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");
}
}

View File

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

View File

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

View File

@ -5,7 +5,7 @@ using sodoff.Schema;
namespace sodoff.Model;
[Index(nameof(Uid))]
[Index(nameof(Uid), IsUnique = true)]
public class Viking {
[Key]
[JsonIgnore]

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" ?>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta charset="utf-8" />
<title>Export SoDOff account</title>
</head>
<body>
<form action="/Export" method="post">
<label for="username">Username:</label><br />
<input type="text" id="username" name="username" /><br />
<label for="password">Password:</label><br />
<input type="password" id="password" name="password" /><br /><br />
<input type="submit" value="Submit" />
</form>
</body>
</html>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8" ?>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta charset="utf-8" />
<title>Import SoDOff account</title>
</head>
<body>
<form action="/Import" method="post" enctype="multipart/form-data">
<label for="username">Username:</label><br />
<input type="text" id="username" name="username" /><br />
<label for="password">Password:</label><br />
<input type="password" id="password" name="password" /><br /><br />
<label for="vikingName">Viking name (in imported file):</label><br />
<input type="text" id="vikingName" name="vikingName" /><br />
<label for="newVikingName">New viking name:</label><br />
<input type="text" id="newVikingName" name="newVikingName" /><br />
<label for="dataFile">Account data:</label><br />
<input type="file" id="dataFile" name="dataFile" /><br /><br />
<input type="submit" value="Submit" />
</form>
</body>
</html>

13
src/Schema/NestData.cs Normal file
View File

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

22
src/Schema/StableData.cs Normal file
View File

@ -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<NestData> NestList;
}

View File

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

View File

@ -155,5 +155,11 @@
<EmbeddedResource Include="Resources\missions\badge_wojs_al.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
<EmbeddedResource Include="Resources\html\export.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
<EmbeddedResource Include="Resources\html\import.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
</Project>