Replace Location table with packed 64-bit int (#679)

* Replace Location table with packed 64 bit int

* Remove double Include and fix Slot documentation

* Fix compilation errors from merge

* Fix namespaces and add expected values to unit tests
This commit is contained in:
Josh 2023-02-21 14:53:38 -06:00 committed by GitHub
parent 575d2b7be7
commit 35ea2682b9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 996 additions and 930 deletions

View file

@ -3,7 +3,7 @@
"isRoot": true, "isRoot": true,
"tools": { "tools": {
"dotnet-ef": { "dotnet-ef": {
"version": "7.0.2", "version": "7.0.3",
"commands": [ "commands": [
"dotnet-ef" "dotnet-ef"
] ]

View file

@ -3,7 +3,7 @@ using System.Diagnostics.CodeAnalysis;
using LBPUnion.ProjectLighthouse.Configuration; using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Database; using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Extensions; using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Types; using LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Users;
using LBPUnion.ProjectLighthouse.Types.Entities.Profile; using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Entities.Token; using LBPUnion.ProjectLighthouse.Types.Entities.Token;
using LBPUnion.ProjectLighthouse.Types.Users; using LBPUnion.ProjectLighthouse.Types.Users;

View file

@ -3,7 +3,7 @@ using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Extensions; using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Types; using LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Users;
using LBPUnion.ProjectLighthouse.StorableLists.Stores; using LBPUnion.ProjectLighthouse.StorableLists.Stores;
using LBPUnion.ProjectLighthouse.Types.Entities.Profile; using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Entities.Token; using LBPUnion.ProjectLighthouse.Types.Entities.Token;
@ -79,7 +79,7 @@ public class FriendsController : ControllerBase
string friends = ""; string friends = "";
foreach (int friendId in friendStore.FriendIds) foreach (int friendId in friendStore.FriendIds)
{ {
User? friend = await this.database.Users.Include(u => u.Location).FirstOrDefaultAsync(u => u.UserId == friendId); User? friend = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == friendId);
if (friend == null) continue; if (friend == null) continue;
friends += friend.Serialize(token.GameVersion); friends += friend.Serialize(token.GameVersion);

View file

@ -36,7 +36,6 @@ public class CollectionController : ControllerBase
if (targetPlaylist == null) return this.BadRequest(); if (targetPlaylist == null) return this.BadRequest();
IQueryable<Slot> slots = this.database.Slots.Include(s => s.Creator) IQueryable<Slot> slots = this.database.Slots.Include(s => s.Creator)
.Include(s => s.Location)
.Where(s => targetPlaylist.SlotIds.Contains(s.SlotId)); .Where(s => targetPlaylist.SlotIds.Contains(s.SlotId));
string response = Enumerable.Aggregate(slots, string.Empty, (current, slot) => current + slot.Serialize()); string response = Enumerable.Aggregate(slots, string.Empty, (current, slot) => current + slot.Serialize());

View file

@ -267,7 +267,6 @@ public class ListController : ControllerBase
(q => q.HeartedUser) (q => q.HeartedUser)
.OrderBy(q => q.HeartedProfileId) .OrderBy(q => q.HeartedProfileId)
.Where(q => q.UserId == targetUser.UserId) .Where(q => q.UserId == targetUser.UserId)
.Include(q => q.HeartedUser.Location)
.Select(q => q.HeartedUser) .Select(q => q.HeartedUser)
.Skip(Math.Max(0, pageStart - 1)) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30)) .Take(Math.Min(pageSize, 30))
@ -369,7 +368,7 @@ public class ListController : ControllerBase
whereQueuedLevels = this.database.QueuedLevels.Where(q => q.User.Username == username) whereQueuedLevels = this.database.QueuedLevels.Where(q => q.User.Username == username)
.Where(q => q.Slot.Type == SlotType.User && !q.Slot.Hidden && q.Slot.GameVersion == gameVersion && q.Slot.FirstUploaded >= oldestTime); .Where(q => q.Slot.Type == SlotType.User && !q.Slot.Hidden && q.Slot.GameVersion == gameVersion && q.Slot.FirstUploaded >= oldestTime);
return whereQueuedLevels.OrderByDescending(q => q.QueuedLevelId).Include(q => q.Slot.Creator).Include(q => q.Slot.Location).Select(q => q.Slot).ByGameVersion(gameVersion, false, false, true); return whereQueuedLevels.OrderByDescending(q => q.QueuedLevelId).Include(q => q.Slot.Creator).Select(q => q.Slot).ByGameVersion(gameVersion, false, false, true);
} else } else
{ {
IQueryable<HeartedLevel> whereHeartedLevels; IQueryable<HeartedLevel> whereHeartedLevels;
@ -385,7 +384,7 @@ public class ListController : ControllerBase
whereHeartedLevels = this.database.HeartedLevels.Where(h => h.User.Username == username) whereHeartedLevels = this.database.HeartedLevels.Where(h => h.User.Username == username)
.Where(h => (h.Slot.Type == SlotType.User || h.Slot.Type == SlotType.Developer) && !h.Slot.Hidden && h.Slot.GameVersion == gameVersion && h.Slot.FirstUploaded >= oldestTime); .Where(h => (h.Slot.Type == SlotType.User || h.Slot.Type == SlotType.Developer) && !h.Slot.Hidden && h.Slot.GameVersion == gameVersion && h.Slot.FirstUploaded >= oldestTime);
return whereHeartedLevels.OrderByDescending(h => h.HeartedLevelId).Include(h => h.Slot.Creator).Include(h => h.Slot.Location).Select(h => h.Slot).ByGameVersion(gameVersion, false, false, true); return whereHeartedLevels.OrderByDescending(h => h.HeartedLevelId).Include(h => h.Slot.Creator).Select(h => h.Slot).ByGameVersion(gameVersion, false, false, true);
} }
} }
#endregion Filtering #endregion Filtering

View file

@ -10,7 +10,6 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Level;
using LBPUnion.ProjectLighthouse.Types.Entities.Profile; using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Entities.Token; using LBPUnion.ProjectLighthouse.Types.Entities.Token;
using LBPUnion.ProjectLighthouse.Types.Logging; using LBPUnion.ProjectLighthouse.Types.Logging;
using LBPUnion.ProjectLighthouse.Types.Misc;
using LBPUnion.ProjectLighthouse.Types.Resources; using LBPUnion.ProjectLighthouse.Types.Resources;
using LBPUnion.ProjectLighthouse.Types.Users; using LBPUnion.ProjectLighthouse.Types.Users;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@ -58,6 +57,12 @@ public class PublishController : ControllerBase
if (string.IsNullOrEmpty(slot.ResourceCollection)) slot.ResourceCollection = slot.RootLevel; if (string.IsNullOrEmpty(slot.ResourceCollection)) slot.ResourceCollection = slot.RootLevel;
if (slot.Resources == null)
{
Logger.Warn("Rejecting level upload, resource list is null", LogArea.Publish);
return this.BadRequest();
}
// Republish logic // Republish logic
if (slot.SlotId != 0) if (slot.SlotId != 0)
{ {
@ -106,12 +111,6 @@ public class PublishController : ControllerBase
return this.BadRequest(); return this.BadRequest();
} }
if (slot.Location == null)
{
Logger.Warn("Rejecting level upload, slot location is null", LogArea.Publish);
return this.BadRequest();
}
slot.Description = CensorHelper.FilterMessage(slot.Description); slot.Description = CensorHelper.FilterMessage(slot.Description);
if (slot.Description.Length > 512) if (slot.Description.Length > 512)
@ -128,7 +127,7 @@ public class PublishController : ControllerBase
return this.BadRequest(); return this.BadRequest();
} }
if (slot.Resources.Any(resource => !FileHelper.ResourceExists(resource))) if (slot.Resources != null && slot.Resources.Any(resource => !FileHelper.ResourceExists(resource)))
{ {
Logger.Warn("Rejecting level upload, missing resource(s)", LogArea.Publish); Logger.Warn("Rejecting level upload, missing resource(s)", LogArea.Publish);
return this.BadRequest(); return this.BadRequest();
@ -169,15 +168,13 @@ public class PublishController : ControllerBase
// Republish logic // Republish logic
if (slot.SlotId != 0) if (slot.SlotId != 0)
{ {
Slot? oldSlot = await this.database.Slots.Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == slot.SlotId); Slot? oldSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slot.SlotId);
if (oldSlot == null) if (oldSlot == null)
{ {
Logger.Warn("Rejecting level republish, wasn't able to find old slot", LogArea.Publish); Logger.Warn("Rejecting level republish, wasn't able to find old slot", LogArea.Publish);
return this.NotFound(); return this.NotFound();
} }
if (oldSlot.Location == null) throw new ArgumentNullException();
if (oldSlot.CreatorId != user.UserId) if (oldSlot.CreatorId != user.UserId)
{ {
Logger.Warn("Rejecting level republish, old level not owned by current user", LogArea.Publish); Logger.Warn("Rejecting level republish, old level not owned by current user", LogArea.Publish);
@ -200,11 +197,7 @@ public class PublishController : ControllerBase
} }
} }
oldSlot.Location.X = slot.Location.X;
oldSlot.Location.Y = slot.Location.Y;
slot.CreatorId = oldSlot.CreatorId; slot.CreatorId = oldSlot.CreatorId;
slot.LocationId = oldSlot.LocationId;
slot.SlotId = oldSlot.SlotId; slot.SlotId = oldSlot.SlotId;
#region Set plays #region Set plays
@ -248,15 +241,6 @@ public class PublishController : ControllerBase
return this.BadRequest(); return this.BadRequest();
} }
//TODO: parse location in body
Location l = new()
{
X = slot.Location.X,
Y = slot.Location.Y,
};
this.database.Locations.Add(l);
await this.database.SaveChangesAsync();
slot.LocationId = l.Id;
slot.CreatorId = user.UserId; slot.CreatorId = user.UserId;
slot.FirstUploaded = TimeHelper.TimestampMillis; slot.FirstUploaded = TimeHelper.TimestampMillis;
slot.LastUpdated = TimeHelper.TimestampMillis; slot.LastUpdated = TimeHelper.TimestampMillis;
@ -289,14 +273,11 @@ public class PublishController : ControllerBase
{ {
GameToken token = this.GetToken(); GameToken token = this.GetToken();
Slot? slot = await this.database.Slots.Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == id); Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound(); if (slot == null) return this.NotFound();
if (slot.Location == null) throw new ArgumentNullException(nameof(id));
if (slot.CreatorId != token.UserId) return this.StatusCode(403, ""); if (slot.CreatorId != token.UserId) return this.StatusCode(403, "");
this.database.Locations.Remove(slot.Location);
this.database.Slots.Remove(slot); this.database.Slots.Remove(slot);
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();

View file

@ -32,7 +32,7 @@ public class ReviewController : ControllerBase
{ {
GameToken token = this.GetToken(); GameToken token = this.GetToken();
Slot? slot = await this.database.Slots.Include(s => s.Creator).Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == slotId); Slot? slot = await this.database.Slots.Include(s => s.Creator).FirstOrDefaultAsync(s => s.SlotId == slotId);
if (slot == null) return this.StatusCode(403, ""); if (slot == null) return this.StatusCode(403, "");
RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == slotId && r.UserId == token.UserId); RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == slotId && r.UserId == token.UserId);
@ -61,7 +61,7 @@ public class ReviewController : ControllerBase
{ {
GameToken token = this.GetToken(); GameToken token = this.GetToken();
Slot? slot = await this.database.Slots.Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == slotId); Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slotId);
if (slot == null) return this.StatusCode(403, ""); if (slot == null) return this.StatusCode(403, "");
RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == slotId && r.UserId == token.UserId); RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == slotId && r.UserId == token.UserId);

View file

@ -75,7 +75,7 @@ public class SlotsController : ControllerBase
List<string?> serializedSlots = new(); List<string?> serializedSlots = new();
foreach (int slotId in s) foreach (int slotId in s)
{ {
Slot? slot = await this.database.Slots.Include(t => t.Creator).Include(t => t.Location).Where(t => t.SlotId == slotId && t.Type == SlotType.User).FirstOrDefaultAsync(); Slot? slot = await this.database.Slots.Include(t => t.Creator).Where(t => t.SlotId == slotId && t.Type == SlotType.User).FirstOrDefaultAsync();
if (slot == null) if (slot == null)
{ {
slot = await this.database.Slots.Where(t => t.InternalSlotId == slotId && t.Type == SlotType.Developer).FirstOrDefaultAsync(); slot = await this.database.Slots.Where(t => t.InternalSlotId == slotId && t.Type == SlotType.Developer).FirstOrDefaultAsync();
@ -511,6 +511,6 @@ public class SlotsController : ControllerBase
// Get game versions exactly equal to gamefiltertype // Get game versions exactly equal to gamefiltertype
whereSlots = this.database.Slots.Where(s => s.Type == SlotType.User && !s.Hidden && s.GameVersion == gameVersion && s.FirstUploaded >= oldestTime); whereSlots = this.database.Slots.Where(s => s.Type == SlotType.User && !s.Hidden && s.GameVersion == gameVersion && s.FirstUploaded >= oldestTime);
return whereSlots.Include(s => s.Creator).Include(s => s.Location); return whereSlots.Include(s => s.Creator);
} }
} }

View file

@ -5,12 +5,11 @@ using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Files; using LBPUnion.ProjectLighthouse.Files;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Types; using LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Users;
using LBPUnion.ProjectLighthouse.Types.Entities.Level; using LBPUnion.ProjectLighthouse.Types.Entities.Level;
using LBPUnion.ProjectLighthouse.Types.Entities.Profile; using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Entities.Token; using LBPUnion.ProjectLighthouse.Types.Entities.Token;
using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Levels;
using LBPUnion.ProjectLighthouse.Types.Misc;
using LBPUnion.ProjectLighthouse.Types.Users; using LBPUnion.ProjectLighthouse.Types.Users;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -33,7 +32,7 @@ public class UserController : ControllerBase
private async Task<string?> getSerializedUser(string username, GameVersion gameVersion = GameVersion.LittleBigPlanet1) private async Task<string?> getSerializedUser(string username, GameVersion gameVersion = GameVersion.LittleBigPlanet1)
{ {
User? user = await this.database.Users.Include(u => u.Location).FirstOrDefaultAsync(u => u.Username == username); User? user = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
return user?.Serialize(gameVersion); return user?.Serialize(gameVersion);
} }
@ -96,6 +95,8 @@ public class UserController : ControllerBase
user.Biography = update.Biography; user.Biography = update.Biography;
} }
if (update.Location != null) user.Location = update.Location;
// ReSharper disable once LoopCanBeConvertedToQuery // ReSharper disable once LoopCanBeConvertedToQuery
foreach (string? resource in new[]{update.IconHash, update.YayHash, update.MehHash, update.BooHash, update.PlanetHash,}) foreach (string? resource in new[]{update.IconHash, update.YayHash, update.MehHash, update.BooHash, update.PlanetHash,})
{ {
@ -127,12 +128,7 @@ public class UserController : ControllerBase
if (slot.CreatorId != token.UserId) continue; if (slot.CreatorId != token.UserId) continue;
Location? loc = await this.database.Locations.FirstOrDefaultAsync(l => l.Id == slot.LocationId); slot.Location = updateSlot.Location;
if (loc == null) throw new ArgumentNullException();
loc.X = updateSlot.Location.X;
loc.Y = updateSlot.Location.Y;
} }
} }
@ -168,16 +164,8 @@ public class UserController : ControllerBase
} }
} }
if (update.Location != null) await this.database.SaveChangesAsync();
{
Location? loc = await this.database.Locations.FirstOrDefaultAsync(l => l.Id == user.LocationId);
if (loc == null) throw new Exception("User loc is null, this should never happen.");
loc.X = update.Location.X;
loc.Y = update.Location.Y;
}
if (this.database.ChangeTracker.HasChanges()) await this.database.SaveChangesAsync();
return this.Ok(); return this.Ok();
} }

View file

@ -20,7 +20,6 @@ public class HeartedCategory : CategoryWithUser
.Where(h => h.Slot.Type == SlotType.User && !h.Slot.Hidden && h.Slot.GameVersion <= GameVersion.LittleBigPlanet3) .Where(h => h.Slot.Type == SlotType.User && !h.Slot.Hidden && h.Slot.GameVersion <= GameVersion.LittleBigPlanet3)
.OrderByDescending(h => h.HeartedLevelId) .OrderByDescending(h => h.HeartedLevelId)
.Include(h => h.Slot.Creator) .Include(h => h.Slot.Creator)
.Include(h => h.Slot.Location)
.Select(h => h.Slot) .Select(h => h.Slot)
.ByGameVersion(GameVersion.LittleBigPlanet3, false, false, true) .ByGameVersion(GameVersion.LittleBigPlanet3, false, false, true)
.FirstOrDefault(); .FirstOrDefault();
@ -30,7 +29,6 @@ public class HeartedCategory : CategoryWithUser
.Where(h => h.Slot.Type == SlotType.User && !h.Slot.Hidden && h.Slot.GameVersion <= GameVersion.LittleBigPlanet3) .Where(h => h.Slot.Type == SlotType.User && !h.Slot.Hidden && h.Slot.GameVersion <= GameVersion.LittleBigPlanet3)
.OrderByDescending(h => h.HeartedLevelId) .OrderByDescending(h => h.HeartedLevelId)
.Include(h => h.Slot.Creator) .Include(h => h.Slot.Creator)
.Include(h => h.Slot.Location)
.Select(h => h.Slot) .Select(h => h.Slot)
.ByGameVersion(GameVersion.LittleBigPlanet3, false, false, true) .ByGameVersion(GameVersion.LittleBigPlanet3, false, false, true)
.Skip(Math.Max(0, pageStart)) .Skip(Math.Max(0, pageStart))

View file

@ -20,7 +20,6 @@ public class QueueCategory : CategoryWithUser
.Where(q => q.Slot.Type == SlotType.User && !q.Slot.Hidden && q.Slot.GameVersion <= GameVersion.LittleBigPlanet3) .Where(q => q.Slot.Type == SlotType.User && !q.Slot.Hidden && q.Slot.GameVersion <= GameVersion.LittleBigPlanet3)
.OrderByDescending(q => q.QueuedLevelId) .OrderByDescending(q => q.QueuedLevelId)
.Include(q => q.Slot.Creator) .Include(q => q.Slot.Creator)
.Include(q => q.Slot.Location)
.Select(q => q.Slot) .Select(q => q.Slot)
.ByGameVersion(GameVersion.LittleBigPlanet3, false, false, true) .ByGameVersion(GameVersion.LittleBigPlanet3, false, false, true)
.FirstOrDefault(); .FirstOrDefault();
@ -30,7 +29,6 @@ public class QueueCategory : CategoryWithUser
.Where(q => q.Slot.Type == SlotType.User && !q.Slot.Hidden && q.Slot.GameVersion <= GameVersion.LittleBigPlanet3) .Where(q => q.Slot.Type == SlotType.User && !q.Slot.Hidden && q.Slot.GameVersion <= GameVersion.LittleBigPlanet3)
.OrderByDescending(q => q.QueuedLevelId) .OrderByDescending(q => q.QueuedLevelId)
.Include(q => q.Slot.Creator) .Include(q => q.Slot.Creator)
.Include(q => q.Slot.Location)
.Select(q => q.Slot) .Select(q => q.Slot)
.ByGameVersion(GameVersion.LittleBigPlanet3, false, false, true) .ByGameVersion(GameVersion.LittleBigPlanet3, false, false, true)
.Skip(Math.Max(0, pageStart - 1)) .Skip(Math.Max(0, pageStart - 1))

View file

@ -1,7 +1,7 @@
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Types; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Users;
[Keyless] [Keyless]
public class ClientsConnected public class ClientsConnected

View file

@ -1,6 +1,6 @@
using System.Xml.Serialization; using System.Xml.Serialization;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Types; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Users;
/// <summary> /// <summary>
/// Sent by the game client to inform the server /// Sent by the game client to inform the server

View file

@ -1,6 +1,6 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Types; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Users;
public class Pins public class Pins
{ {

View file

@ -2,7 +2,7 @@
using System.Xml.Serialization; using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Types; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Users;
[XmlRoot("privacySettings")] [XmlRoot("privacySettings")]
[XmlType("privacySettings")] [XmlType("privacySettings")]

View file

@ -3,7 +3,7 @@ using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Levels;
using LBPUnion.ProjectLighthouse.Types.Misc; using LBPUnion.ProjectLighthouse.Types.Misc;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Types; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Users;
/// <summary> /// <summary>
/// Used by the games to update details about a user's profile /// Used by the games to update details about a user's profile
/// LBP1 only uses Location and IconHash /// LBP1 only uses Location and IconHash

View file

@ -33,14 +33,11 @@ public class SlotPageController : ControllerBase
WebToken? token = this.database.WebTokenFromRequest(this.Request); WebToken? token = this.database.WebTokenFromRequest(this.Request);
if (token == null) return this.Redirect("~/login"); if (token == null) return this.Redirect("~/login");
Slot? targetSlot = await this.database.Slots.Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == id); Slot? targetSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (targetSlot == null) return this.Redirect("~/slots/0"); if (targetSlot == null) return this.Redirect("~/slots/0");
if (targetSlot.Location == null) throw new ArgumentNullException(nameof(id));
if (targetSlot.CreatorId != token.UserId) return this.Redirect("~/slot/" + id); if (targetSlot.CreatorId != token.UserId) return this.Redirect("~/slot/" + id);
this.database.Locations.Remove(targetSlot.Location);
this.database.Slots.Remove(targetSlot); this.database.Slots.Remove(targetSlot);
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();

View file

@ -25,21 +25,11 @@ public class SlotTests : LighthouseServerTest<GameServerTestStartup>
User userA = await database.CreateUser($"unitTestUser{r.Next()}", CryptoHelper.GenerateAuthToken()); User userA = await database.CreateUser($"unitTestUser{r.Next()}", CryptoHelper.GenerateAuthToken());
User userB = await database.CreateUser($"unitTestUser{r.Next()}", CryptoHelper.GenerateAuthToken()); User userB = await database.CreateUser($"unitTestUser{r.Next()}", CryptoHelper.GenerateAuthToken());
Location l = new()
{
X = 0,
Y = 0,
};
database.Locations.Add(l);
await database.SaveChangesAsync();
Slot slotA = new() Slot slotA = new()
{ {
Creator = userA, Creator = userA,
CreatorId = userA.UserId, CreatorId = userA.UserId,
Name = "slotA", Name = "slotA",
Location = l,
LocationId = l.Id,
ResourceCollection = "", ResourceCollection = "",
}; };
@ -48,8 +38,6 @@ public class SlotTests : LighthouseServerTest<GameServerTestStartup>
Creator = userB, Creator = userB,
CreatorId = userB.UserId, CreatorId = userB.UserId,
Name = "slotB", Name = "slotB",
Location = l,
LocationId = l.Id,
ResourceCollection = "", ResourceCollection = "",
}; };

View file

@ -0,0 +1,81 @@
using System.IO;
using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Misc;
using Xunit;
namespace LBPUnion.ProjectLighthouse.Tests;
public class LocationTests
{
[Fact]
public void ShouldSetAndReadUserLocation()
{
Location expected = new()
{
X = 1000,
Y = 5000,
};
User user = new()
{
Location = new Location
{
X = expected.X,
Y = expected.Y,
},
};
Assert.True(user.Location.X == expected.X);
Assert.True(user.Location.Y == expected.Y);
Assert.True(user.LocationPacked == 4_294_967_301_000);
}
[Fact]
public void ShouldSetAndReadSlotLocation()
{
Location expected = new()
{
X = 1000,
Y = 5000,
};
Slot slot = new()
{
Location = new Location
{
X = expected.X,
Y = expected.Y,
},
};
Assert.True(slot.Location.X == expected.X);
Assert.True(slot.Location.Y == expected.Y);
Assert.True(slot.LocationPacked == 4_294_967_301_000);
}
[Fact]
public void ShouldReadLocationAfterDeserialization()
{
XmlSerializer deserializer = new(typeof(Slot));
const string slotData = "<slot><name>test</name><resource>test</resource><location><x>4000</x><y>9000</y></location></slot>";
Slot? deserialized = (Slot?)deserializer.Deserialize(new StringReader(slotData));
Assert.True(deserialized != null);
Assert.True(deserialized.Name == "test");
Assert.True(deserialized.Location.X == 4000);
Assert.True(deserialized.Location.Y == 9000);
Assert.True(deserialized.LocationPacked == 17_179_869_193_000);
}
[Fact]
public void ShouldDeserializeEmptyLocation()
{
XmlSerializer deserializer = new(typeof(Slot));
const string slotData = "<slot><name>test</name></slot>";
Slot? deserialized = (Slot?)deserializer.Deserialize(new StringReader(slotData));
Assert.True(deserialized != null);
Assert.True(deserialized.Name == "test");
Assert.True(deserialized.Location.X == 0);
Assert.True(deserialized.Location.Y == 0);
Assert.True(deserialized.LocationPacked == 0);
}
}

View file

@ -1,34 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Types.Maintenance;
using LBPUnion.ProjectLighthouse.Types.Misc;
namespace LBPUnion.ProjectLighthouse.Administration.Maintenance.MaintenanceJobs;
public class CleanupUnusedLocationsMaintenanceJob : IMaintenanceJob
{
private readonly DatabaseContext database = new();
public string Name() => "Cleanup Unused Locations";
public string Description() => "Cleanup unused locations in the database.";
public async Task Run()
{
List<int> usedLocationIds = new();
usedLocationIds.AddRange(this.database.Slots.Select(slot => slot.LocationId));
usedLocationIds.AddRange(this.database.Users.Select(user => user.LocationId));
IQueryable<Location> locationsToRemove = this.database.Locations.Where(l => !usedLocationIds.Contains(l.Id));
foreach (Location location in locationsToRemove)
{
Console.WriteLine("Removing location " + location.Id);
this.database.Locations.Remove(location);
}
await this.database.SaveChangesAsync();
}
}

View file

@ -28,7 +28,6 @@ public partial class DatabaseContext : DbContext
#region Users #region Users
public DbSet<Comment> Comments { get; set; } public DbSet<Comment> Comments { get; set; }
public DbSet<LastContact> LastContacts { get; set; } public DbSet<LastContact> LastContacts { get; set; }
public DbSet<Location> Locations { get; set; }
public DbSet<Photo> Photos { get; set; } public DbSet<Photo> Photos { get; set; }
public DbSet<PhotoSubject> PhotoSubjects { get; set; } public DbSet<PhotoSubject> PhotoSubjects { get; set; }
public DbSet<PlatformLinkAttempt> PlatformLinkAttempts { get; set; } public DbSet<PlatformLinkAttempt> PlatformLinkAttempts { get; set; }

View file

@ -10,8 +10,6 @@ public partial class DatabaseContext
{ {
public async Task RemoveSlot(Slot slot, bool saveChanges = true) public async Task RemoveSlot(Slot slot, bool saveChanges = true)
{ {
if (slot.Location != null) this.Locations.Remove(slot.Location);
this.Slots.Remove(slot); this.Slots.Remove(slot);
if (saveChanges) await this.SaveChangesAsync(); if (saveChanges) await this.SaveChangesAsync();
@ -22,7 +20,7 @@ public partial class DatabaseContext
HeartedPlaylist? heartedList = await this.HeartedPlaylists.FirstOrDefaultAsync(p => p.UserId == userId && p.PlaylistId == heartedPlaylist.PlaylistId); HeartedPlaylist? heartedList = await this.HeartedPlaylists.FirstOrDefaultAsync(p => p.UserId == userId && p.PlaylistId == heartedPlaylist.PlaylistId);
if (heartedList != null) return; if (heartedList != null) return;
this.HeartedPlaylists.Add(new HeartedPlaylist() this.HeartedPlaylists.Add(new HeartedPlaylist
{ {
PlaylistId = heartedPlaylist.PlaylistId, PlaylistId = heartedPlaylist.PlaylistId,
UserId = userId, UserId = userId,

View file

@ -39,15 +39,10 @@ public partial class DatabaseContext
User? user = await this.Users.Where(u => u.Username == username).FirstOrDefaultAsync(); User? user = await this.Users.Where(u => u.Username == username).FirstOrDefaultAsync();
if (user != null) return user; if (user != null) return user;
Location l = new(); // store to get id after submitting
this.Locations.Add(l); // add to table
await this.SaveChangesAsync(); // saving to the database returns the id and sets it on this entity
user = new User user = new User
{ {
Username = username, Username = username,
Password = password, Password = password,
LocationId = l.Id,
Biography = "", Biography = "",
EmailAddress = emailAddress, EmailAddress = emailAddress,
}; };
@ -99,8 +94,6 @@ public partial class DatabaseContext
if (user == null) return; if (user == null) return;
if (user.Username.Length == 0) return; // don't delete the placeholder user if (user.Username.Length == 0) return; // don't delete the placeholder user
if (user.Location != null) this.Locations.Remove(user.Location);
LastContact? lastContact = await this.LastContacts.FirstOrDefaultAsync(l => l.UserId == user.UserId); LastContact? lastContact = await this.LastContacts.FirstOrDefaultAsync(l => l.UserId == user.UserId);
if (lastContact != null) this.LastContacts.Remove(lastContact); if (lastContact != null) this.LastContacts.Remove(lastContact);

View file

@ -12,17 +12,17 @@ namespace LBPUnion.ProjectLighthouse.Extensions;
public static class DatabaseExtensions public static class DatabaseExtensions
{ {
public static IQueryable<Slot> ByGameVersion public static IQueryable<Slot> ByGameVersion
(this DbSet<Slot> set, GameVersion gameVersion, bool includeSublevels = false, bool includeCreatorAndLocation = false) (this DbSet<Slot> set, GameVersion gameVersion, bool includeSublevels = false, bool includeCreator = false)
=> set.AsQueryable().ByGameVersion(gameVersion, includeSublevels, includeCreatorAndLocation); => set.AsQueryable().ByGameVersion(gameVersion, includeSublevels, includeCreator);
public static IQueryable<Slot> ByGameVersion public static IQueryable<Slot> ByGameVersion
(this IQueryable<Slot> query, GameVersion gameVersion, bool includeSublevels = false, bool includeCreatorAndLocation = false, bool includeDeveloperLevels = false) (this IQueryable<Slot> query, GameVersion gameVersion, bool includeSublevels = false, bool includeCreator = false, bool includeDeveloperLevels = false)
{ {
query = query.Where(s => (s.Type == SlotType.User) || (s.Type == SlotType.Developer && includeDeveloperLevels)); query = query.Where(s => s.Type == SlotType.User || (s.Type == SlotType.Developer && includeDeveloperLevels));
if (includeCreatorAndLocation) if (includeCreator)
{ {
query = query.Include(s => s.Creator).Include(s => s.Location); query = query.Include(s => s.Creator);
} }
if (gameVersion == GameVersion.LittleBigPlanetVita || gameVersion == GameVersion.LittleBigPlanetPSP || gameVersion == GameVersion.Unknown) if (gameVersion == GameVersion.LittleBigPlanetVita || gameVersion == GameVersion.LittleBigPlanetPSP || gameVersion == GameVersion.Unknown)
@ -41,7 +41,7 @@ public static class DatabaseExtensions
public static IQueryable<Review> ByGameVersion(this IQueryable<Review> queryable, GameVersion gameVersion, bool includeSublevels = false) public static IQueryable<Review> ByGameVersion(this IQueryable<Review> queryable, GameVersion gameVersion, bool includeSublevels = false)
{ {
IQueryable<Review> query = queryable.Include(r => r.Slot).Include(r => r.Slot.Creator).Include(r => r.Slot.Location); IQueryable<Review> query = queryable.Include(r => r.Slot).Include(r => r.Slot.Creator);
if (gameVersion == GameVersion.LittleBigPlanetVita || gameVersion == GameVersion.LittleBigPlanetPSP || gameVersion == GameVersion.Unknown) if (gameVersion == GameVersion.LittleBigPlanetVita || gameVersion == GameVersion.LittleBigPlanetPSP || gameVersion == GameVersion.Unknown)
{ {

View file

@ -7,7 +7,6 @@ using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Types.Entities.Level; using LBPUnion.ProjectLighthouse.Types.Entities.Level;
using LBPUnion.ProjectLighthouse.Types.Entities.Profile; using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Levels;
using LBPUnion.ProjectLighthouse.Types.Misc;
using LBPUnion.ProjectLighthouse.Types.Users; using LBPUnion.ProjectLighthouse.Types.Users;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -44,28 +43,6 @@ public static class SlotHelper
private static readonly SemaphoreSlim semaphore = new(1, 1); private static readonly SemaphoreSlim semaphore = new(1, 1);
private static async Task<int> GetPlaceholderLocationId(DatabaseContext database)
{
Location? devLocation = await database.Locations.FirstOrDefaultAsync(l => l.Id == 1);
if (devLocation != null) return devLocation.Id;
await semaphore.WaitAsync(TimeSpan.FromSeconds(5));
try
{
devLocation = new Location
{
Id = 1,
};
database.Locations.Add(devLocation);
return devLocation.Id;
}
finally
{
semaphore.Release();
}
}
public static async Task<int> GetPlaceholderUserId(DatabaseContext database) public static async Task<int> GetPlaceholderUserId(DatabaseContext database)
{ {
int devCreatorId = await database.Users.Where(u => u.Username.Length == 0) int devCreatorId = await database.Users.Where(u => u.Username.Length == 0)
@ -82,7 +59,6 @@ public static class SlotHelper
PermissionLevel = PermissionLevel.Banned, PermissionLevel = PermissionLevel.Banned,
Biography = "Placeholder author of story levels", Biography = "Placeholder author of story levels",
BannedReason = "Banned to not show in users list", BannedReason = "Banned to not show in users list",
LocationId = await GetPlaceholderLocationId(database),
}; };
database.Users.Add(devCreator); database.Users.Add(devCreator);
await database.SaveChangesAsync(); await database.SaveChangesAsync();
@ -119,7 +95,6 @@ public static class SlotHelper
Description = $"Placeholder for {slotType} type level", Description = $"Placeholder for {slotType} type level",
CreatorId = devCreatorId, CreatorId = devCreatorId,
InternalSlotId = guid, InternalSlotId = guid,
LocationId = await GetPlaceholderLocationId(database),
Type = slotType, Type = slotType,
}; };

View file

@ -0,0 +1,129 @@
using LBPUnion.ProjectLighthouse;
using LBPUnion.ProjectLighthouse.Database;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ProjectLighthouse.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20230215195324_ChangeLocationStorage")]
public partial class ChangeLocationStorage : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<ulong>(name: "LocationPacked",
table: "Users",
type: "bigint unsigned",
nullable: false,
defaultValue: 0ul);
migrationBuilder.AddColumn<ulong>(name: "LocationPacked",
table: "Slots",
type: "bigint unsigned",
nullable: false,
defaultValue: 0ul);
migrationBuilder.Sql("UPDATE Users as u inner join Locations as l on u.LocationId = l.Id " +
"SET u.LocationPacked = (l.X << 32 | l.Y)");
migrationBuilder.Sql("UPDATE Slots as s inner join Locations as l on s.LocationId = l.Id " +
"SET s.LocationPacked = (l.X << 32 | l.Y)");
migrationBuilder.DropForeignKey(
name: "FK_Slots_Locations_LocationId",
table: "Slots");
migrationBuilder.DropForeignKey(
name: "FK_Users_Locations_LocationId",
table: "Users");
migrationBuilder.DropTable(
name: "Locations");
migrationBuilder.DropIndex(
name: "IX_Users_LocationId",
table: "Users");
migrationBuilder.DropIndex(
name: "IX_Slots_LocationId",
table: "Slots");
migrationBuilder.DropColumn(
name: "LocationId",
table: "Users");
migrationBuilder.DropColumn(
name: "LocationId",
table: "Slots");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "LocationPacked",
table: "Users");
migrationBuilder.DropColumn(
name: "LocationPacked",
table: "Slots");
migrationBuilder.AddColumn<int>(
name: "LocationId",
table: "Users",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "LocationId",
table: "Slots",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.CreateTable(
name: "Locations",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
X = table.Column<int>(type: "int", nullable: false),
Y = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Locations", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_Users_LocationId",
table: "Users",
column: "LocationId");
migrationBuilder.CreateIndex(
name: "IX_Slots_LocationId",
table: "Slots",
column: "LocationId");
migrationBuilder.AddForeignKey(
name: "FK_Slots_Locations_LocationId",
table: "Slots",
column: "LocationId",
principalTable: "Locations",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_Users_Locations_LocationId",
table: "Users",
column: "LocationId",
principalTable: "Locations",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
}
}

View file

@ -73,16 +73,33 @@ public class Slot
public string ResourceCollection { get; set; } = ""; public string ResourceCollection { get; set; } = "";
[NotMapped] [NotMapped]
[XmlElement("resource")]
[JsonIgnore] [JsonIgnore]
public string[] Resources { [XmlElement("resource")]
get => this.ResourceCollection.Split(","); public string[]? Resources {
set => this.ResourceCollection = string.Join(',', value); get => this.ResourceCollection.Split(",", StringSplitOptions.RemoveEmptyEntries);
set => this.ResourceCollection = string.Join(',', value ?? Array.Empty<string>());
} }
[XmlIgnore] /// <summary>
/// The location of the level on the user's earth
/// Stored as a single 64 bit unsigned integer but split into
/// 2 unsigned 32 bit integers
/// </summary>
[JsonIgnore] [JsonIgnore]
public int LocationId { get; set; } public ulong LocationPacked { get; set; }
[NotMapped]
[XmlElement("location")]
public Location Location
{
get =>
new()
{
X = (int)(this.LocationPacked >> 32),
Y = (int)this.LocationPacked,
};
set => this.LocationPacked = (ulong)value.X << 32 | (uint)value.Y;
}
[XmlIgnore] [XmlIgnore]
public int CreatorId { get; set; } public int CreatorId { get; set; }
@ -91,14 +108,6 @@ public class Slot
[JsonIgnore] [JsonIgnore]
public User? Creator { get; set; } public User? Creator { get; set; }
/// <summary>
/// The location of the level on the creator's earth
/// </summary>
[XmlElement("location")]
[ForeignKey(nameof(LocationId))]
[JsonIgnore]
public Location? Location { get; set; }
[XmlElement("initiallyLocked")] [XmlElement("initiallyLocked")]
public bool InitiallyLocked { get; set; } public bool InitiallyLocked { get; set; }
@ -297,7 +306,7 @@ public class Slot
string slotData = LbpSerializer.StringElement("id", this.SlotId) + string slotData = LbpSerializer.StringElement("id", this.SlotId) +
LbpSerializer.StringElement("npHandle", this.Creator?.Username) + LbpSerializer.StringElement("npHandle", this.Creator?.Username) +
LbpSerializer.StringElement("location", this.Location?.Serialize()) + LbpSerializer.StringElement("location", this.Location.Serialize()) +
LbpSerializer.StringElement("game", (int)this.GameVersion) + LbpSerializer.StringElement("game", (int)this.GameVersion) +
LbpSerializer.StringElement("name", this.Name) + LbpSerializer.StringElement("name", this.Name) +
LbpSerializer.StringElement("description", this.Description) + LbpSerializer.StringElement("description", this.Description) +
@ -356,7 +365,7 @@ public class Slot
LbpSerializer.StringElement("lbp3CompletionCount", this.PlaysLBP3Complete) + LbpSerializer.StringElement("lbp3CompletionCount", this.PlaysLBP3Complete) +
LbpSerializer.StringElement("lbp3UniquePlayCount", this.PlaysLBP3Unique) + LbpSerializer.StringElement("lbp3UniquePlayCount", this.PlaysLBP3Unique) +
(gameVersion == GameVersion.LittleBigPlanetVita ? (gameVersion == GameVersion.LittleBigPlanetVita ?
LbpSerializer.StringElement("sizeOfResources", this.Resources.Sum(FileHelper.ResourceSize)) LbpSerializer.StringElement("sizeOfResources", this.Resources!.Sum(FileHelper.ResourceSize))
: ""); : "");

View file

@ -98,15 +98,26 @@ public class User
select id).Count(); select id).Count();
} }
[JsonIgnore]
public int LocationId { get; set; }
/// <summary> /// <summary>
/// The location of the profile card on the user's earth /// The location of the profile card on the user's earth
/// Stored as a single 64 bit unsigned integer but split into
/// 2 unsigned 32 bit integers
/// </summary> /// </summary>
[ForeignKey("LocationId")]
[JsonIgnore] [JsonIgnore]
public Location Location { get; set; } public ulong LocationPacked { get; set; }
[NotMapped]
[XmlElement("location")]
public Location Location
{
get =>
new()
{
X = (int)(this.LocationPacked >> 32),
Y = (int)this.LocationPacked,
};
set => this.LocationPacked = (ulong)value.X << 32 | (uint)value.Y;
}
[NotMapped] [NotMapped]
[JsonIgnore] [JsonIgnore]

View file

@ -10,9 +10,6 @@ namespace LBPUnion.ProjectLighthouse.Types.Misc;
[XmlType("location")] [XmlType("location")]
public class Location public class Location
{ {
[XmlIgnore]
public int Id { get; set; }
[XmlElement("x")] [XmlElement("x")]
public int X { get; set; } public int X { get; set; }