mirror of
https://github.com/LBPUnion/ProjectLighthouse.git
synced 2025-05-17 15:22:26 +00:00
Refactor serialization system (#702)
* Initial work for serialization refactor * Experiment with new naming conventions * Mostly implement user and slot serialization. Still needs to be fine tuned to match original implementation Many things are left in a broken state like website features/api endpoints/lbp3 categories * Fix release building * Migrate scores, reviews, and more to new serialization system. Many things are still broken but progress is steadily being made * Fix Api responses and migrate serialization for most types * Make serialization better and fix bugs Fix recursive PrepareSerialization when recursive item is set during root item's PrepareSerialization, items, should be properly indexed in order but it's only tested to 1 level of recursion * Fix review serialization * Fix user serialization producing malformed SQL query * Remove DefaultIfEmpty query * MariaDB doesn't like double nested queries * Fix LBP1 tag counter * Implement lbp3 categories and add better deserialization handling * Implement expression tree caching to speed up reflection and write new serializer tests * Remove Game column from UserEntity and rename DatabaseContextModelSnapshot.cs back to DatabaseModelSnapshot.cs * Make UserEntity username not required * Fix recursive serialization of lists and add relevant unit tests * Actually commit the migration * Fix LocationTests to use new deserialization class * Fix comments not serializing the right author username * Replace all occurrences of StatusCode with their respective ASP.NET named result instead of StatusCode(403) everything is now in the form of Forbid() * Fix SlotBase.ConvertToEntity and LocationTests * Fix compilation error * Give Location a default value in GameUserSlot and GameUser * Reimplement stubbed website functions * Convert grief reports to new serialization system * Update DatabaseModelSnapshot and bump dotnet tool version * Remove unused directives * Fix broken type reference * Fix rated comments on website * Don't include banned users in website comments * Optimize score submission * Fix slot id calculating in in-game comment posting * Move serialization interfaces to types folder and add more documentation * Allow uploading of versus scores
This commit is contained in:
parent
307b2135a3
commit
329ab66043
248 changed files with 4993 additions and 2896 deletions
|
@ -6,12 +6,12 @@ using LBPUnion.ProjectLighthouse.Extensions;
|
|||
using LBPUnion.ProjectLighthouse.Files;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Logging;
|
||||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Types.Logging;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
@ -34,17 +34,17 @@ public class PhotosController : ControllerBase
|
|||
[HttpPost("uploadPhoto")]
|
||||
public async Task<IActionResult> UploadPhoto()
|
||||
{
|
||||
User? user = await this.database.UserFromGameToken(this.GetToken());
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
UserEntity? user = await this.database.UserFromGameToken(this.GetToken());
|
||||
if (user == null) return this.Forbid();
|
||||
|
||||
if (user.PhotosByMe >= ServerConfiguration.Instance.UserGeneratedContentLimits.PhotosQuota) return this.BadRequest();
|
||||
if (user.GetUploadedPhotoCount(this.database) >= ServerConfiguration.Instance.UserGeneratedContentLimits.PhotosQuota) return this.BadRequest();
|
||||
|
||||
Photo? photo = await this.DeserializeBody<Photo>();
|
||||
GamePhoto? photo = await this.DeserializeBody<GamePhoto>();
|
||||
if (photo == null) return this.BadRequest();
|
||||
|
||||
SanitizationHelper.SanitizeStringsInClass(photo);
|
||||
|
||||
foreach (Photo p in this.database.Photos.Where(p => p.CreatorId == user.UserId))
|
||||
foreach (PhotoEntity p in this.database.Photos.Where(p => p.CreatorId == user.UserId))
|
||||
{
|
||||
if (p.LargeHash == photo.LargeHash) return this.Ok(); // photo already uplaoded
|
||||
if (p.MediumHash == photo.MediumHash) return this.Ok();
|
||||
|
@ -52,20 +52,28 @@ public class PhotosController : ControllerBase
|
|||
if (p.PlanHash == photo.PlanHash) return this.Ok();
|
||||
}
|
||||
|
||||
photo.CreatorId = user.UserId;
|
||||
photo.Creator = user;
|
||||
PhotoEntity photoEntity = new()
|
||||
{
|
||||
CreatorId = user.UserId,
|
||||
Creator = user,
|
||||
SmallHash = photo.SmallHash,
|
||||
MediumHash = photo.MediumHash,
|
||||
LargeHash = photo.LargeHash,
|
||||
PlanHash = photo.PlanHash,
|
||||
Timestamp = photo.Timestamp,
|
||||
};
|
||||
|
||||
if (photo.XmlLevelInfo?.RootLevel != null)
|
||||
if (photo.LevelInfo?.RootLevel != null)
|
||||
{
|
||||
bool validLevel = false;
|
||||
PhotoSlot photoSlot = photo.XmlLevelInfo;
|
||||
PhotoSlot photoSlot = photo.LevelInfo;
|
||||
if (photoSlot.SlotType is SlotType.Pod or SlotType.Local) photoSlot.SlotId = 0;
|
||||
switch (photoSlot.SlotType)
|
||||
{
|
||||
case SlotType.User:
|
||||
{
|
||||
// We'll grab the slot by the RootLevel and see what happens from here.
|
||||
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.Type == SlotType.User && s.ResourceCollection.Contains(photoSlot.RootLevel));
|
||||
SlotEntity? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.Type == SlotType.User && s.ResourceCollection.Contains(photoSlot.RootLevel));
|
||||
if (slot == null) break;
|
||||
|
||||
if (!string.IsNullOrEmpty(slot.RootLevel)) validLevel = true;
|
||||
|
@ -76,7 +84,7 @@ public class PhotosController : ControllerBase
|
|||
case SlotType.Local:
|
||||
case SlotType.Developer:
|
||||
{
|
||||
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.Type == photoSlot.SlotType && s.InternalSlotId == photoSlot.SlotId);
|
||||
SlotEntity? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.Type == photoSlot.SlotType && s.InternalSlotId == photoSlot.SlotId);
|
||||
if (slot != null)
|
||||
photoSlot.SlotId = slot.SlotId;
|
||||
else
|
||||
|
@ -92,23 +100,23 @@ public class PhotosController : ControllerBase
|
|||
break;
|
||||
}
|
||||
|
||||
if (validLevel) photo.SlotId = photo.XmlLevelInfo.SlotId;
|
||||
if (validLevel) photoEntity.SlotId = photoSlot.SlotId;
|
||||
}
|
||||
|
||||
if (photo.XmlSubjects?.Count > 4) return this.BadRequest();
|
||||
if (photo.Subjects?.Count > 4) return this.BadRequest();
|
||||
|
||||
if (photo.Timestamp > TimeHelper.Timestamp) photo.Timestamp = TimeHelper.Timestamp;
|
||||
|
||||
this.database.Photos.Add(photo);
|
||||
this.database.Photos.Add(photoEntity);
|
||||
|
||||
// Save to get photo ID for the PhotoSubject foreign keys
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
if (photo.XmlSubjects != null)
|
||||
if (photo.Subjects != null)
|
||||
{
|
||||
// Check for duplicate photo subjects
|
||||
List<string> subjectUserIds = new(4);
|
||||
foreach (PhotoSubject subject in photo.PhotoSubjects)
|
||||
foreach (GamePhotoSubject subject in photo.Subjects)
|
||||
{
|
||||
if (subjectUserIds.Contains(subject.Username) && !string.IsNullOrEmpty(subject.Username))
|
||||
return this.BadRequest();
|
||||
|
@ -116,17 +124,23 @@ public class PhotosController : ControllerBase
|
|||
subjectUserIds.Add(subject.Username);
|
||||
}
|
||||
|
||||
foreach (PhotoSubject subject in photo.XmlSubjects.Where(subject => !string.IsNullOrEmpty(subject.Username)))
|
||||
foreach (GamePhotoSubject subject in photo.Subjects.Where(subject => !string.IsNullOrEmpty(subject.Username)))
|
||||
{
|
||||
subject.User = await this.database.Users.FirstOrDefaultAsync(u => u.Username == subject.Username);
|
||||
subject.UserId = await this.database.Users.Where(u => u.Username == subject.Username)
|
||||
.Select(u => u.UserId)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (subject.User == null) continue;
|
||||
if (subject.UserId == 0) continue;
|
||||
|
||||
PhotoSubjectEntity subjectEntity = new()
|
||||
{
|
||||
PhotoId = photoEntity.PhotoId,
|
||||
UserId = subject.UserId,
|
||||
};
|
||||
|
||||
subject.UserId = subject.User.UserId;
|
||||
subject.PhotoId = photo.PhotoId;
|
||||
Logger.Debug($"Adding PhotoSubject (userid {subject.UserId}) to db", LogArea.Photos);
|
||||
|
||||
this.database.PhotoSubjects.Add(subject);
|
||||
this.database.PhotoSubjects.Add(subjectEntity);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -155,16 +169,15 @@ public class PhotosController : ControllerBase
|
|||
|
||||
if (slotType == "developer") id = await SlotHelper.GetPlaceholderSlotId(this.database, id, SlotType.Developer);
|
||||
|
||||
List<Photo> photos = await this.database.Photos.Include(p => p.Creator)
|
||||
.Include(p => p.PhotoSubjects)
|
||||
.ThenInclude(ps => ps.User)
|
||||
List<GamePhoto> photos = await this.database.Photos.Include(p => p.PhotoSubjects)
|
||||
.Where(p => p.SlotId == id)
|
||||
.OrderByDescending(s => s.Timestamp)
|
||||
.Skip(Math.Max(0, pageStart - 1))
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.Select(p => GamePhoto.CreateFromEntity(p))
|
||||
.ToListAsync();
|
||||
string response = photos.Aggregate(string.Empty, (s, photo) => s + photo.Serialize(id, SlotHelper.ParseType(slotType)));
|
||||
return this.Ok(LbpSerializer.StringElement("photos", response));
|
||||
|
||||
return this.Ok(new PhotoListResponse(photos));
|
||||
}
|
||||
|
||||
[HttpGet("photos/by")]
|
||||
|
@ -175,16 +188,14 @@ public class PhotosController : ControllerBase
|
|||
int targetUserId = await this.database.UserIdFromUsername(user);
|
||||
if (targetUserId == 0) return this.NotFound();
|
||||
|
||||
List<Photo> photos = await this.database.Photos.Include(p => p.Creator)
|
||||
.Include(p => p.PhotoSubjects)
|
||||
.ThenInclude(ps => ps.User)
|
||||
List<GamePhoto> photos = await this.database.Photos.Include(p => p.PhotoSubjects)
|
||||
.Where(p => p.CreatorId == targetUserId)
|
||||
.OrderByDescending(s => s.Timestamp)
|
||||
.Skip(Math.Max(0, pageStart - 1))
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.Select(p => GamePhoto.CreateFromEntity(p))
|
||||
.ToListAsync();
|
||||
string response = photos.Aggregate(string.Empty, (s, photo) => s + photo.Serialize());
|
||||
return this.Ok(LbpSerializer.StringElement("photos", response));
|
||||
return this.Ok(new PhotoListResponse(photos));
|
||||
}
|
||||
|
||||
[HttpGet("photos/with")]
|
||||
|
@ -195,32 +206,30 @@ public class PhotosController : ControllerBase
|
|||
int targetUserId = await this.database.UserIdFromUsername(user);
|
||||
if (targetUserId == 0) return this.NotFound();
|
||||
|
||||
List<Photo> photos = await this.database.Photos.Include(p => p.Creator)
|
||||
.Include(p => p.PhotoSubjects)
|
||||
.ThenInclude(ps => ps.User)
|
||||
List<GamePhoto> photos = await this.database.Photos.Include(p => p.PhotoSubjects)
|
||||
.Where(p => p.PhotoSubjects.Any(ps => ps.UserId == targetUserId))
|
||||
.OrderByDescending(s => s.Timestamp)
|
||||
.Skip(Math.Max(0, pageStart - 1))
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.Select(p => GamePhoto.CreateFromEntity(p))
|
||||
.ToListAsync();
|
||||
string response = photos.Aggregate(string.Empty, (current, photo) => current + photo.Serialize());
|
||||
|
||||
return this.Ok(LbpSerializer.StringElement("photos", response));
|
||||
return this.Ok(new PhotoListResponse(photos));
|
||||
}
|
||||
|
||||
[HttpPost("deletePhoto/{id:int}")]
|
||||
public async Task<IActionResult> DeletePhoto(int id)
|
||||
{
|
||||
GameToken token = this.GetToken();
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
Photo? photo = await this.database.Photos.FirstOrDefaultAsync(p => p.PhotoId == id);
|
||||
PhotoEntity? photo = await this.database.Photos.FirstOrDefaultAsync(p => p.PhotoId == id);
|
||||
if (photo == null) return this.NotFound();
|
||||
|
||||
// If user isn't photo creator then check if they own the level
|
||||
if (photo.CreatorId != token.UserId)
|
||||
{
|
||||
Slot? photoSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == photo.SlotId && s.Type == SlotType.User);
|
||||
if (photoSlot == null || photoSlot.CreatorId != token.UserId) return this.StatusCode(401, "");
|
||||
SlotEntity? photoSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == photo.SlotId && s.Type == SlotType.User);
|
||||
if (photoSlot == null || photoSlot.CreatorId != token.UserId) return this.Unauthorized();
|
||||
}
|
||||
|
||||
HashSet<string> photoResources = new(){photo.LargeHash, photo.SmallHash, photo.MediumHash, photo.PlanHash,};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue