mirror of
https://github.com/LBPUnion/ProjectLighthouse.git
synced 2025-05-13 13:22:28 +00:00
Refactor Database into DatabaseContext Moved into separate folder so it actually has a namespace instead sitting in the root
319 lines
No EOL
11 KiB
C#
319 lines
No EOL
11 KiB
C#
#nullable enable
|
|
using LBPUnion.ProjectLighthouse.Configuration;
|
|
using LBPUnion.ProjectLighthouse.Database;
|
|
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.Logging;
|
|
using LBPUnion.ProjectLighthouse.Types.Misc;
|
|
using LBPUnion.ProjectLighthouse.Types.Resources;
|
|
using LBPUnion.ProjectLighthouse.Types.Users;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
|
|
|
|
[ApiController]
|
|
[Authorize]
|
|
[Route("LITTLEBIGPLANETPS3_XML/")]
|
|
[Produces("text/xml")]
|
|
public class PublishController : ControllerBase
|
|
{
|
|
private readonly DatabaseContext database;
|
|
|
|
public PublishController(DatabaseContext database)
|
|
{
|
|
this.database = database;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Endpoint the game uses to check what resources need to be uploaded and if the level can be uploaded
|
|
/// </summary>
|
|
[HttpPost("startPublish")]
|
|
public async Task<IActionResult> StartPublish()
|
|
{
|
|
GameToken token = this.GetToken();
|
|
|
|
User? user = await this.database.UserFromGameToken(token);
|
|
if (user == null) return this.StatusCode(403, "");
|
|
|
|
Slot? slot = await this.DeserializeBody<Slot>();
|
|
if (slot == null)
|
|
{
|
|
Logger.Warn("Rejecting level upload, slot is null", LogArea.Publish);
|
|
return this.BadRequest(); // if the level cant be parsed then it obviously cant be uploaded
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(slot.RootLevel))
|
|
{
|
|
Logger.Warn("Rejecting level upload, slot does not include rootLevel", LogArea.Publish);
|
|
return this.BadRequest();
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(slot.ResourceCollection)) slot.ResourceCollection = slot.RootLevel;
|
|
|
|
// Republish logic
|
|
if (slot.SlotId != 0)
|
|
{
|
|
Slot? oldSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slot.SlotId);
|
|
if (oldSlot == null)
|
|
{
|
|
Logger.Warn("Rejecting level republish, could not find old slot", LogArea.Publish);
|
|
return this.NotFound();
|
|
}
|
|
if (oldSlot.CreatorId != user.UserId)
|
|
{
|
|
Logger.Warn("Rejecting level republish, old slot's creator is not publishing user", LogArea.Publish);
|
|
return this.BadRequest();
|
|
}
|
|
}
|
|
else if (user.GetUsedSlotsForGame(token.GameVersion) > user.EntitledSlots)
|
|
{
|
|
return this.StatusCode(403, "");
|
|
}
|
|
|
|
slot.ResourceCollection += "," + slot.IconHash; // tells LBP to upload icon after we process resources here
|
|
|
|
string resources = slot.Resources.Where
|
|
(hash => !FileHelper.ResourceExists(hash))
|
|
.Aggregate("", (current, hash) => current + LbpSerializer.StringElement("resource", hash));
|
|
|
|
return this.Ok(LbpSerializer.TaggedStringElement("slot", resources, "type", "user"));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Endpoint actually used to publish a level
|
|
/// </summary>
|
|
[HttpPost("publish")]
|
|
public async Task<IActionResult> Publish([FromQuery] string? game)
|
|
{
|
|
GameToken token = this.GetToken();
|
|
|
|
User? user = await this.database.UserFromGameToken(token);
|
|
if (user == null) return this.StatusCode(403, "");
|
|
|
|
Slot? slot = await this.DeserializeBody<Slot>();
|
|
|
|
if (slot == null)
|
|
{
|
|
Logger.Warn("Rejecting level upload, slot is null", LogArea.Publish);
|
|
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);
|
|
|
|
if (slot.Description.Length > 512)
|
|
{
|
|
Logger.Warn($"Rejecting level upload, description too long ({slot.Description.Length} characters)", LogArea.Publish);
|
|
return this.BadRequest();
|
|
}
|
|
|
|
slot.Name = CensorHelper.FilterMessage(slot.Name);
|
|
|
|
if (slot.Name.Length > 64)
|
|
{
|
|
Logger.Warn($"Rejecting level upload, title too long ({slot.Name.Length} characters)", LogArea.Publish);
|
|
return this.BadRequest();
|
|
}
|
|
|
|
if (slot.Resources.Any(resource => !FileHelper.ResourceExists(resource)))
|
|
{
|
|
Logger.Warn("Rejecting level upload, missing resource(s)", LogArea.Publish);
|
|
return this.BadRequest();
|
|
}
|
|
|
|
LbpFile? rootLevel = LbpFile.FromHash(slot.RootLevel);
|
|
|
|
if (rootLevel == null)
|
|
{
|
|
Logger.Warn("Rejecting level upload, unable to find rootLevel", LogArea.Publish);
|
|
return this.BadRequest();
|
|
}
|
|
|
|
if (!slot.IsAdventurePlanet)
|
|
{
|
|
if (rootLevel.FileType != LbpFileType.Level)
|
|
{
|
|
Logger.Warn("Rejecting level upload, rootLevel is not a level", LogArea.Publish);
|
|
return this.BadRequest();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (rootLevel.FileType != LbpFileType.Adventure)
|
|
{
|
|
Logger.Warn("Rejecting level upload, rootLevel is not a LBP 3 Adventure", LogArea.Publish);
|
|
return this.BadRequest();
|
|
}
|
|
}
|
|
|
|
GameVersion slotVersion = FileHelper.ParseLevelVersion(rootLevel);
|
|
|
|
slot.GameVersion = slotVersion;
|
|
if (slotVersion == GameVersion.Unknown) slot.GameVersion = token.GameVersion;
|
|
|
|
slot.AuthorLabels = LabelHelper.RemoveInvalidLabels(slot.AuthorLabels);
|
|
|
|
// Republish logic
|
|
if (slot.SlotId != 0)
|
|
{
|
|
Slot? oldSlot = await this.database.Slots.Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == slot.SlotId);
|
|
if (oldSlot == null)
|
|
{
|
|
Logger.Warn("Rejecting level republish, wasn't able to find old slot", LogArea.Publish);
|
|
return this.NotFound();
|
|
}
|
|
|
|
if (oldSlot.Location == null) throw new ArgumentNullException();
|
|
|
|
if (oldSlot.CreatorId != user.UserId)
|
|
{
|
|
Logger.Warn("Rejecting level republish, old level not owned by current user", LogArea.Publish);
|
|
return this.BadRequest();
|
|
}
|
|
|
|
// I hate lbp3
|
|
if (game != null)
|
|
{
|
|
GameVersion intendedVersion = FromAbbreviation(game);
|
|
if (intendedVersion != GameVersion.Unknown && intendedVersion != slotVersion)
|
|
{
|
|
// Delete the useless rootLevel that lbp3 just uploaded
|
|
if (slotVersion == GameVersion.LittleBigPlanet3)
|
|
FileHelper.DeleteResource(slot.RootLevel);
|
|
|
|
slot.GameVersion = oldSlot.GameVersion;
|
|
slot.RootLevel = oldSlot.RootLevel;
|
|
slot.ResourceCollection = oldSlot.ResourceCollection;
|
|
}
|
|
}
|
|
|
|
oldSlot.Location.X = slot.Location.X;
|
|
oldSlot.Location.Y = slot.Location.Y;
|
|
|
|
slot.CreatorId = oldSlot.CreatorId;
|
|
slot.LocationId = oldSlot.LocationId;
|
|
slot.SlotId = oldSlot.SlotId;
|
|
|
|
#region Set plays
|
|
|
|
slot.PlaysLBP1 = oldSlot.PlaysLBP1;
|
|
slot.PlaysLBP1Complete = oldSlot.PlaysLBP1Complete;
|
|
slot.PlaysLBP1Unique = oldSlot.PlaysLBP1Unique;
|
|
|
|
slot.PlaysLBP2 = oldSlot.PlaysLBP2;
|
|
slot.PlaysLBP2Complete = oldSlot.PlaysLBP2Complete;
|
|
slot.PlaysLBP2Unique = oldSlot.PlaysLBP2Unique;
|
|
|
|
slot.PlaysLBP3 = oldSlot.PlaysLBP3;
|
|
slot.PlaysLBP3Complete = oldSlot.PlaysLBP3Complete;
|
|
slot.PlaysLBP3Unique = oldSlot.PlaysLBP3Unique;
|
|
|
|
#endregion
|
|
|
|
slot.FirstUploaded = oldSlot.FirstUploaded;
|
|
slot.LastUpdated = TimeHelper.TimestampMillis;
|
|
|
|
slot.TeamPick = oldSlot.TeamPick;
|
|
|
|
if (slot.MinimumPlayers == 0 || slot.MaximumPlayers == 0)
|
|
{
|
|
slot.MinimumPlayers = 1;
|
|
slot.MaximumPlayers = 4;
|
|
}
|
|
|
|
slot.MinimumPlayers = Math.Clamp(slot.MinimumPlayers, 1, 4);
|
|
slot.MaximumPlayers = Math.Clamp(slot.MaximumPlayers, 1, 4);
|
|
|
|
this.database.Entry(oldSlot).CurrentValues.SetValues(slot);
|
|
await this.database.SaveChangesAsync();
|
|
return this.Ok(oldSlot.Serialize(token.GameVersion));
|
|
}
|
|
|
|
if (user.GetUsedSlotsForGame(slotVersion) > user.EntitledSlots)
|
|
{
|
|
Logger.Warn("Rejecting level upload, too many published slots", LogArea.Publish);
|
|
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.FirstUploaded = TimeHelper.TimestampMillis;
|
|
slot.LastUpdated = TimeHelper.TimestampMillis;
|
|
|
|
if (slot.MinimumPlayers == 0 || slot.MaximumPlayers == 0)
|
|
{
|
|
slot.MinimumPlayers = 1;
|
|
slot.MaximumPlayers = 4;
|
|
}
|
|
|
|
slot.MinimumPlayers = Math.Clamp(slot.MinimumPlayers, 1, 4);
|
|
slot.MaximumPlayers = Math.Clamp(slot.MaximumPlayers, 1, 4);
|
|
|
|
this.database.Slots.Add(slot);
|
|
await this.database.SaveChangesAsync();
|
|
|
|
if (user.LevelVisibility == PrivacyType.All)
|
|
{
|
|
await WebhookHelper.SendWebhook("New level published!",
|
|
$"**{user.Username}** just published a new level: [**{slot.Name}**]({ServerConfiguration.Instance.ExternalUrl}/slot/{slot.SlotId})\n{slot.Description}");
|
|
}
|
|
|
|
Logger.Success($"Successfully published level {slot.Name} (id: {slot.SlotId}) by {user.Username} (id: {user.UserId})", LogArea.Publish);
|
|
|
|
return this.Ok(slot.Serialize(token.GameVersion));
|
|
}
|
|
|
|
[HttpPost("unpublish/{id:int}")]
|
|
public async Task<IActionResult> Unpublish(int id)
|
|
{
|
|
GameToken token = this.GetToken();
|
|
|
|
Slot? slot = await this.database.Slots.Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == id);
|
|
if (slot == null) return this.NotFound();
|
|
|
|
if (slot.Location == null) throw new ArgumentNullException(nameof(id));
|
|
|
|
if (slot.CreatorId != token.UserId) return this.StatusCode(403, "");
|
|
|
|
this.database.Locations.Remove(slot.Location);
|
|
this.database.Slots.Remove(slot);
|
|
|
|
await this.database.SaveChangesAsync();
|
|
|
|
return this.Ok();
|
|
}
|
|
|
|
private static GameVersion FromAbbreviation(string abbr)
|
|
{
|
|
return abbr switch
|
|
{
|
|
"lbp1" => GameVersion.LittleBigPlanet1,
|
|
"lbp2" => GameVersion.LittleBigPlanet2,
|
|
"lbp3" => GameVersion.LittleBigPlanet3,
|
|
"lbpv" => GameVersion.LittleBigPlanetVita,
|
|
"lbppsp" => GameVersion.LittleBigPlanetPSP,
|
|
_ => GameVersion.Unknown,
|
|
};
|
|
}
|
|
} |