diff --git a/MareSynchronosServer/MareSynchronos.API/CharacterCacheDto.cs b/MareSynchronosServer/MareSynchronos.API/CharacterCacheDto.cs new file mode 100644 index 0000000..69e5ff2 --- /dev/null +++ b/MareSynchronosServer/MareSynchronos.API/CharacterCacheDto.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace MareSynchronos.API +{ + public class CharacterCacheDto + { + public List FileReplacements { get; set; } = new(); + public string GlamourerData { get; set; } + public string Hash { get; set; } + public int JobId { get; set; } + } + + public class FileReplacementDto + { + public string[] GamePaths { get; set; } = Array.Empty(); + public string Hash { get; set; } + public string ImcData { get; set; } + } +} diff --git a/MareSynchronosServer/MareSynchronos.API/MareSynchronos.API.csproj b/MareSynchronosServer/MareSynchronos.API/MareSynchronos.API.csproj index f208d30..47c5a70 100644 --- a/MareSynchronosServer/MareSynchronos.API/MareSynchronos.API.csproj +++ b/MareSynchronosServer/MareSynchronos.API/MareSynchronos.API.csproj @@ -4,4 +4,8 @@ net5.0 + + + + diff --git a/MareSynchronosServer/MareSynchronosServer/Data/MareDbContext.cs b/MareSynchronosServer/MareSynchronosServer/Data/MareDbContext.cs index ad00f9d..be42f73 100644 --- a/MareSynchronosServer/MareSynchronosServer/Data/MareDbContext.cs +++ b/MareSynchronosServer/MareSynchronosServer/Data/MareDbContext.cs @@ -1,5 +1,8 @@ -using MareSynchronosServer.Models; +using System.Collections.Generic; +using MareSynchronos.API; +using MareSynchronosServer.Models; using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; namespace MareSynchronosServer.Data { @@ -12,12 +15,24 @@ namespace MareSynchronosServer.Data public DbSet Users { get; set; } public DbSet Files { get; set; } public DbSet Whitelists { get; set; } + public DbSet Visibilities { get; set; } + public DbSet CharacterData { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity().ToTable("Users"); modelBuilder.Entity().ToTable("FileCaches"); modelBuilder.Entity().ToTable("Whitelists"); + modelBuilder.Entity().ToTable("Visibility") + .HasKey(k => new { k.CID, k.OtherCID }); + modelBuilder.Entity() + .Property(b => b.EquipmentData) + .HasConversion( + v => JsonConvert.SerializeObject(v), + v => JsonConvert.DeserializeObject>(v)); + modelBuilder.Entity() + .ToTable("CharacterData") + .HasKey(k => new { k.UserId, k.JobId }); } } } diff --git a/MareSynchronosServer/MareSynchronosServer/Hubs/BaseHub.cs b/MareSynchronosServer/MareSynchronosServer/Hubs/BaseHub.cs new file mode 100644 index 0000000..c0874de --- /dev/null +++ b/MareSynchronosServer/MareSynchronosServer/Hubs/BaseHub.cs @@ -0,0 +1,61 @@ +using System.Linq; +using System.Security.Claims; +using System.Security.Cryptography; +using MareSynchronosServer.Data; +using Microsoft.AspNetCore.SignalR; + +namespace MareSynchronosServer.Hubs +{ + public abstract class BaseHub : Hub + { + protected MareDbContext DbContext { get; init; } + + protected BaseHub(MareDbContext context) + { + DbContext = context; + } + + protected string AuthenticatedUserId => Context.User?.Claims?.SingleOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value ?? "Unknown"; + + protected Models.User? GetAuthenticatedUser() + { + return DbContext.Users.Single(u => u.UID == AuthenticatedUserId); + } + + protected Models.User? GetUserFromCID(string cid) + { + return DbContext.Users.SingleOrDefault(c => c.CharacterIdentification == cid); + } + + protected Models.User? GetUserFromUID(string uid) + { + return DbContext.Users.SingleOrDefault(c => c.UID == uid); + } + + protected bool IsUserOnline(string uid) + { + return DbContext.Users.Any(c => c.UID == uid && !string.IsNullOrEmpty(c.CharacterIdentification)); + } + + public static string GenerateRandomString(int length, string allowableChars = null) + { + if (string.IsNullOrEmpty(allowableChars)) + allowableChars = @"ABCDEFGHJKLMNPQRSTUVWXYZ0123456789"; + + // Generate random data + var rnd = new byte[length]; + using (var rng = new RNGCryptoServiceProvider()) + rng.GetBytes(rnd); + + // Generate the output string + var allowable = allowableChars.ToCharArray(); + var l = allowable.Length; + var chars = new char[length]; + for (var i = 0; i < length; i++) + chars[i] = allowable[rnd[i] % l]; + + return new string(chars); + } + + } +} \ No newline at end of file diff --git a/MareSynchronosServer/MareSynchronosServer/Hubs/Files.cs b/MareSynchronosServer/MareSynchronosServer/Hubs/Files.cs index d84b5e1..5b2ae84 100644 --- a/MareSynchronosServer/MareSynchronosServer/Hubs/Files.cs +++ b/MareSynchronosServer/MareSynchronosServer/Hubs/Files.cs @@ -6,47 +6,53 @@ using System.Linq; using System.Security.Claims; using System.Security.Cryptography; using System.Threading.Tasks; +using MareSynchronos.API; using MareSynchronosServer.Authentication; using MareSynchronosServer.Data; using MareSynchronosServer.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.SignalR; +using Microsoft.EntityFrameworkCore; namespace MareSynchronosServer.Hubs { - [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AUTH_SCHEME)] - public class Files : Hub + public class Files : BaseHub { - private readonly MareDbContext _dbContext; - - public Files(MareDbContext dbContext) + public Files(MareDbContext dbContext) : base(dbContext) { - _dbContext = dbContext; } [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AUTH_SCHEME)] - public async Task SendFiles(List fileList) + public async Task SendFiles(List fileList) { - var existingFiles = _dbContext.Files.Where(f => fileList.Contains(f.Hash)).ToList(); - foreach (var file in fileList.Where(f => existingFiles.All(e => e.Hash != f))) + var fileListHashes = fileList.Select(x => x.Hash).ToList(); + var existingFiles = DbContext.Files.Where(f => fileListHashes.Contains(f.Hash)).ToList(); + foreach (var file in fileListHashes.Where(f => existingFiles.All(e => e.Hash != f))) { - var userId = Context.User!.Claims.SingleOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value ?? "Unknown"; - await _dbContext.Files.AddAsync(new FileCache() + var userId = AuthenticatedUserId; + await DbContext.Files.AddAsync(new FileCache() { Hash = file, LastAccessTime = DateTime.Now, Uploaded = false, - Uploader = _dbContext.Users.Single(u => u.UID == userId) + Uploader = DbContext.Users.Single(u => u.UID == userId) }); - await _dbContext.SaveChangesAsync(); + await DbContext.SaveChangesAsync(); await Clients.Caller!.SendAsync("FileRequest", file); } } + [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AUTH_SCHEME)] + public async Task IsUploadFinished() + { + var userUid = AuthenticatedUserId; + return await DbContext.Files.AnyAsync(f => f.Uploader.UID == userUid && !f.Uploaded); + } + [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AUTH_SCHEME)] public async Task UploadFile(string hash, byte[] file) { - var relatedFile = _dbContext.Files.SingleOrDefault(f => f.Hash == hash); + var relatedFile = DbContext.Files.SingleOrDefault(f => f.Hash == hash); if (relatedFile == null) return false; var decodedFile = LZ4.LZ4Codec.Unwrap(file); using var sha1 = new SHA1CryptoServiceProvider(); @@ -54,21 +60,31 @@ namespace MareSynchronosServer.Hubs var computedHashString = BitConverter.ToString(computedHash).Replace("-", ""); if (hash != computedHashString) { + DbContext.Remove(relatedFile); + await DbContext.SaveChangesAsync(); return false; } await File.WriteAllBytesAsync(@"G:\ServerTest\" + hash, file); relatedFile.Uploaded = true; relatedFile.LastAccessTime = DateTime.Now; - await _dbContext.SaveChangesAsync(); + await DbContext.SaveChangesAsync(); return true; } + [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AUTH_SCHEME)] + public async Task DownloadFile(string hash) + { + var file = DbContext.Files.SingleOrDefault(f => f.Hash == hash); + if (file == null) return Array.Empty(); + return await File.ReadAllBytesAsync(@"G:\ServerTest\" + hash); + } + public override Task OnDisconnectedAsync(Exception exception) { - var userId = Context.User!.Claims.SingleOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value; - var notUploadedFiles = _dbContext.Files.Where(f => !f.Uploaded && f.Uploader.UID == userId).ToList(); - _dbContext.RemoveRange(notUploadedFiles); - _dbContext.SaveChanges(); + var userId = AuthenticatedUserId; + var notUploadedFiles = DbContext.Files.Where(f => !f.Uploaded && f.Uploader.UID == userId).ToList(); + DbContext.RemoveRange(notUploadedFiles); + DbContext.SaveChanges(); return base.OnDisconnectedAsync(exception); } } diff --git a/MareSynchronosServer/MareSynchronosServer/Hubs/User.cs b/MareSynchronosServer/MareSynchronosServer/Hubs/User.cs index 48f762e..4fc6c81 100644 --- a/MareSynchronosServer/MareSynchronosServer/Hubs/User.cs +++ b/MareSynchronosServer/MareSynchronosServer/Hubs/User.cs @@ -1,8 +1,7 @@ using System; using System.Collections.Generic; -using System.Globalization; +using System.Diagnostics; using System.Linq; -using System.Security.Claims; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; @@ -16,13 +15,10 @@ using Microsoft.EntityFrameworkCore; namespace MareSynchronosServer.Hubs { - public class User : Hub + public class User : BaseHub { - private readonly MareDbContext _dbContext; - - public User(MareDbContext dbContext) + public User(MareDbContext dbContext) : base(dbContext) { - _dbContext = dbContext; } public async Task Register() @@ -39,119 +35,269 @@ namespace MareSynchronosServer.Hubs while (!hasValidUid) { var uid = GenerateRandomString(10); - if (_dbContext.Users.Any(u => u.UID == uid)) continue; + if (DbContext.Users.Any(u => u.UID == uid)) continue; user.UID = uid; hasValidUid = true; } - _dbContext.Users.Add(user); + DbContext.Users.Add(user); - await _dbContext.SaveChangesAsync(); + await DbContext.SaveChangesAsync(); return computedHash; } + private Whitelist OppositeEntry(string otherUID) => + DbContext.Whitelists.SingleOrDefault(w => w.User.UID == otherUID && w.OtherUser.UID == AuthenticatedUserId); + [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AUTH_SCHEME)] public string GetUID() { - return Context.User!.Claims.Single(c => c.Type == ClaimTypes.NameIdentifier).Value; + return AuthenticatedUserId; } [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AUTH_SCHEME)] - public async Task SendWhitelist(List whiteListEntries) + public async Task SendVisibilityList(List currentVisibilityList) { - var currentUserId = Context.User!.Claims.Single(c => c.Type == ClaimTypes.NameIdentifier).Value; - var user = _dbContext.Users.Single(u => u.UID == currentUserId); - var userWhitelists = _dbContext.Whitelists + Stopwatch st = Stopwatch.StartNew(); + var cid = DbContext.Users.Single(u => u.UID == AuthenticatedUserId).CharacterIdentification; + var visibilities = DbContext.Visibilities.Where(v => v.CID == cid).ToList(); + foreach (var visibility in currentVisibilityList.Where(visibility => visibilities.All(v => v.OtherCID != visibility))) + { + await DbContext.Visibilities.AddAsync(new Visibility { CID = cid, OtherCID = visibility }); + } + + foreach (var visibility in visibilities.Where(v => currentVisibilityList.Contains(v.OtherCID))) + { + DbContext.Visibilities.Remove(visibility); + } + + await DbContext.SaveChangesAsync(); + st.Stop(); + Debug.WriteLine("Visibility update took " + st.Elapsed); + } + + [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AUTH_SCHEME)] + public async Task GetCharacterData(Dictionary visibleCharacterWithJobs) + { + var uid = AuthenticatedUserId; + Dictionary ret = new(); + var whitelistEntriesHavingThisUser = DbContext.Whitelists .Include(w => w.User) .Include(w => w.OtherUser) - .Where(w => w.User.UID == currentUserId) + .Where(w => w.OtherUser.UID == uid && !w.IsPaused + && visibleCharacterWithJobs.Keys.Contains(w.User.CharacterIdentification)) .ToList(); - foreach (var whitelist in whiteListEntries) + foreach (var whiteListEntry in whitelistEntriesHavingThisUser) { - var otherEntry = _dbContext.Whitelists.SingleOrDefault(w => - w.User.UID == whitelist.OtherUID && w.OtherUser.UID == user.UID); + bool isNotPaused = await DbContext.Whitelists.AnyAsync(w => + !w.IsPaused && w.User.UID == uid && w.OtherUser.UID == whiteListEntry.User.UID); + if (!isNotPaused) continue; + var dictEntry = visibleCharacterWithJobs[whiteListEntry.User.CharacterIdentification]; - var prevEntry = userWhitelists.SingleOrDefault(w => w.OtherUser.UID == whitelist.OtherUID); - if (prevEntry != null) + var cachedChar = await + DbContext.CharacterData.SingleOrDefaultAsync(c => c.UserId == whiteListEntry.User.UID && c.JobId == dictEntry); + if (cachedChar != null) { - prevEntry.IsPaused = whitelist.IsPaused; + await Clients.User(uid).SendAsync("ReceiveCharacterData", cachedChar, + whiteListEntry.User.CharacterIdentification); } - else + } + } + + [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AUTH_SCHEME)] + public async Task PushCharacterData(CharacterCacheDto characterCache, List visibleCharacterIds) + { + var uid = AuthenticatedUserId; + var whitelistEntriesHavingThisUser = DbContext.Whitelists + .Include(w => w.User) + .Include(w => w.OtherUser) + .Where(w => w.OtherUser.UID == uid && !w.IsPaused + && visibleCharacterIds.Contains(w.User.CharacterIdentification)).ToList(); + var existingCharacterData = + await DbContext.CharacterData.SingleOrDefaultAsync(s => + s.UserId == uid && s.JobId == characterCache.JobId); + if (existingCharacterData != null && existingCharacterData.Hash != characterCache.Hash) + { + existingCharacterData.EquipmentData = + characterCache.FileReplacements; + existingCharacterData.Hash = characterCache.Hash; + existingCharacterData.GlamourerData = characterCache.GlamourerData; + DbContext.CharacterData.Update(existingCharacterData); + } + else if (existingCharacterData == null) + { + CharacterData data = new CharacterData { - var otherUser = _dbContext.Users.SingleOrDefault(u => u.UID == whitelist.OtherUID); - if (otherUser != null) + UserId = AuthenticatedUserId, + EquipmentData = characterCache.FileReplacements, + Hash = characterCache.Hash, + GlamourerData = characterCache.GlamourerData, + JobId = characterCache.JobId + }; + await DbContext.CharacterData.AddAsync(data); + } + + await DbContext.SaveChangesAsync(); + + foreach (var whitelistEntry in whitelistEntriesHavingThisUser) + { + var ownEntry = DbContext.Whitelists.SingleOrDefault(w => + w.User.UID == uid && w.OtherUser.UID == whitelistEntry.User.UID); + if (ownEntry == null || ownEntry.IsPaused) continue; + await Clients.User(whitelistEntry.User.UID).SendAsync("ReceiveCharacterData", characterCache, whitelistEntry.OtherUser.CharacterIdentification); + } + } + + [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AUTH_SCHEME)] + public async Task SendCharacterNameHash(string characterNameHash) + { + DbContext.Users.Single(u => u.UID == AuthenticatedUserId).CharacterIdentification = characterNameHash; + await DbContext.SaveChangesAsync(); + } + + [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AUTH_SCHEME)] + public async Task SendWhitelistAddition(string uid) + { + var user = await DbContext.Users.SingleAsync(u => u.UID == AuthenticatedUserId); + var otherUser = await DbContext.Users.SingleOrDefaultAsync(u => u.UID == uid); + if (otherUser == null) return; + Whitelist wl = new Whitelist() + { + IsPaused = false, + OtherUser = otherUser, + User = user + }; + await DbContext.Whitelists.AddAsync(wl); + await DbContext.SaveChangesAsync(); + var otherEntry = OppositeEntry(uid); + await Clients.User(user.UID) + .SendAsync("UpdateWhitelist", new WhitelistDto() + { + OtherUID = otherUser.UID, + IsPaused = false, + IsPausedFromOthers = otherEntry?.IsPaused ?? false, + IsSynced = otherEntry != null + }, otherUser.CharacterIdentification); + if (otherEntry != null) + { + await Clients.User(uid).SendAsync("UpdateWhitelist", + new WhitelistDto() { - Whitelist wl = new Whitelist - { - User = user, - OtherUser = otherUser, - IsPaused = whitelist.IsPaused - }; - otherEntry = wl; - await _dbContext.Whitelists.AddAsync(wl); - } - } - - if (otherEntry != null) - { - await Clients.User(whitelist.OtherUID).SendAsync("UpdateWhitelist", currentUserId, true, - whitelist.IsPaused); - } - - await _dbContext.SaveChangesAsync(); + OtherUID = user.UID, + IsPaused = otherEntry.IsPaused, + IsPausedFromOthers = false, + IsSynced = true + }, user.CharacterIdentification); } + } - foreach (var deletedEntry in userWhitelists.Where(u => whiteListEntries.All(e => e.OtherUID != u.OtherUser.UID)).ToList()) + [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AUTH_SCHEME)] + public async Task SendWhitelistRemoval(string uid) + { + var user = await DbContext.Users.SingleAsync(u => u.UID == AuthenticatedUserId); + var otherUser = await DbContext.Users.SingleOrDefaultAsync(u => u.UID == uid); + if (otherUser == null) return; + Whitelist wl = + await DbContext.Whitelists.SingleOrDefaultAsync(w => w.User == user && w.OtherUser == otherUser); + DbContext.Whitelists.Remove(wl); + await DbContext.SaveChangesAsync(); + var otherEntry = OppositeEntry(uid); + await Clients.User(user.UID) + .SendAsync("UpdateWhitelist", new WhitelistDto() + { + OtherUID = otherUser.UID, + IsPaused = false, + IsPausedFromOthers = otherEntry?.IsPaused ?? false, + IsSynced = otherEntry != null + }, otherUser.CharacterIdentification); + if (otherEntry != null) { - var otherEntry = _dbContext.Whitelists.SingleOrDefault(w => - w.User.UID == deletedEntry.OtherUser.UID && w.OtherUser.UID == user.UID); - if (otherEntry != null) + await Clients.User(uid).SendAsync("UpdateWhitelist", new WhitelistDto() { - await Clients.User(otherEntry.User.UID).SendAsync("UpdateWhitelist", currentUserId, false, false); - } - - _dbContext.Whitelists.Remove(deletedEntry); + OtherUID = user.UID, + IsPaused = otherEntry.IsPaused, + IsPausedFromOthers = false, + IsSynced = false + }, user.CharacterIdentification); + } + } + + [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AUTH_SCHEME)] + public async Task SendWhitelistPauseChange(string uid, bool isPaused) + { + var user = DbContext.Users.Single(u => u.UID == AuthenticatedUserId); + var otherUser = await DbContext.Users.SingleOrDefaultAsync(u => u.UID == uid); + if (otherUser == null) return; + Whitelist wl = + await DbContext.Whitelists.SingleOrDefaultAsync(w => w.User == user && w.OtherUser == otherUser); + wl.IsPaused = isPaused; + DbContext.Update(wl); + await DbContext.SaveChangesAsync(); + var otherEntry = OppositeEntry(uid); + await Clients.User(user.UID) + .SendAsync("UpdateWhitelist", new WhitelistDto() + { + OtherUID = otherUser.UID, + IsPaused = isPaused, + IsPausedFromOthers = otherEntry?.IsPaused ?? false, + IsSynced = otherEntry != null + }, otherUser.CharacterIdentification); + if (otherEntry != null) + { + await Clients.User(uid).SendAsync("UpdateWhitelist", new WhitelistDto() + { + OtherUID = user.UID, + IsPaused = otherEntry.IsPaused, + IsPausedFromOthers = isPaused, + IsSynced = true + }, user.CharacterIdentification); } - _dbContext.Whitelists.RemoveRange(); - await _dbContext.SaveChangesAsync(); } [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AUTH_SCHEME)] public async Task> GetWhitelist() { - string userid = Context.User!.Claims.Single(c => c.Type == ClaimTypes.NameIdentifier).Value; - return _dbContext.Whitelists.Include(u => u.OtherUser).Include(u => u.User).Where(w => w.User.UID == userid) + string userid = AuthenticatedUserId; + var user = GetAuthenticatedUser(); + return DbContext.Whitelists + .Include(u => u.OtherUser) + .Include(u => u.User) + .Where(w => w.User.UID == userid) .ToList() .Select(w => { - var otherEntry = _dbContext.Whitelists.SingleOrDefault(a => a.User.UID == w.OtherUser.UID && a.OtherUser.UID == userid); + var otherEntry = OppositeEntry(w.OtherUser.UID); + var otherUser = GetUserFromUID(w.OtherUser.UID); + var seesYou = false; + if (otherEntry != null) + { + seesYou = DbContext.Visibilities.Any(v => + v.CID == otherUser.CharacterIdentification && v.OtherCID == user.CharacterIdentification); + } return new WhitelistDto { IsPaused = w.IsPaused, OtherUID = w.OtherUser.UID, IsSynced = otherEntry != null, - IsPausedFromOthers = otherEntry?.IsPaused ?? false + IsPausedFromOthers = otherEntry?.IsPaused ?? false, }; }).ToList(); } - public static string GenerateRandomString(int length, string allowableChars = null) + + public override Task OnDisconnectedAsync(Exception exception) { - if (string.IsNullOrEmpty(allowableChars)) - allowableChars = @"ABCDEFGHJKLMNPQRSTUVWXYZ0123456789"; + var user = DbContext.Users.SingleOrDefault(u => u.UID == AuthenticatedUserId); + if (user != null) + { + var outdatedVisibilities = DbContext.Visibilities.Where(v => v.CID == user.CharacterIdentification); + DbContext.RemoveRange(outdatedVisibilities); + var outdatedCharacterData = DbContext.CharacterData.Where(v => v.UserId == user.UID); + DbContext.RemoveRange(outdatedCharacterData); + user.CharacterIdentification = null; + DbContext.SaveChanges(); + } - // Generate random data - var rnd = new byte[length]; - using (var rng = new RNGCryptoServiceProvider()) - rng.GetBytes(rnd); - - // Generate the output string - var allowable = allowableChars.ToCharArray(); - var l = allowable.Length; - var chars = new char[length]; - for (var i = 0; i < length; i++) - chars[i] = allowable[rnd[i] % l]; - - return new string(chars); + return base.OnDisconnectedAsync(exception); } } } diff --git a/MareSynchronosServer/MareSynchronosServer/Models/CharacterData.cs b/MareSynchronosServer/MareSynchronosServer/Models/CharacterData.cs new file mode 100644 index 0000000..e6fba8e --- /dev/null +++ b/MareSynchronosServer/MareSynchronosServer/Models/CharacterData.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using MareSynchronos.API; + +namespace MareSynchronosServer.Models +{ + public class CharacterData + { + public string UserId { get; set; } + public int JobId { get; set; } + public List EquipmentData { get; set; } + public string GlamourerData { get; set; } + public string Hash { get; set; } + } +} diff --git a/MareSynchronosServer/MareSynchronosServer/Models/Visibility.cs b/MareSynchronosServer/MareSynchronosServer/Models/Visibility.cs new file mode 100644 index 0000000..f672f20 --- /dev/null +++ b/MareSynchronosServer/MareSynchronosServer/Models/Visibility.cs @@ -0,0 +1,8 @@ +namespace MareSynchronosServer.Models +{ + public class Visibility + { + public string CID { get; set; } + public string OtherCID { get; set; } + } +}