mirror of
https://github.com/LBPUnion/ProjectLighthouse.git
synced 2025-05-11 12:42:26 +00:00
Split GameAPI and Website into their own projects
This commit is contained in:
parent
bb03a01246
commit
14154faaf8
116 changed files with 484 additions and 287 deletions
|
@ -0,0 +1,120 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Logging;
|
||||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Categories;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.GameAPI.Controllers.Slots;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
[Produces("text/xml")]
|
||||
public class CollectionController : ControllerBase
|
||||
{
|
||||
private readonly Database database;
|
||||
|
||||
public CollectionController(Database database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
[HttpGet("user/{username}/playlists")]
|
||||
public IActionResult GetUserPlaylists(string username) => this.Ok();
|
||||
|
||||
[HttpGet("searches")]
|
||||
[HttpGet("genres")]
|
||||
public async Task<IActionResult> GenresAndSearches()
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
string categoriesSerialized = CollectionHelper.Categories.Aggregate
|
||||
(
|
||||
string.Empty,
|
||||
(current, category) =>
|
||||
{
|
||||
string serialized;
|
||||
|
||||
if (category is CategoryWithUser categoryWithUser) serialized = categoryWithUser.Serialize(this.database, user);
|
||||
else serialized = category.Serialize(this.database);
|
||||
|
||||
return current + serialized;
|
||||
}
|
||||
);
|
||||
|
||||
return this.Ok
|
||||
(
|
||||
LbpSerializer.TaggedStringElement
|
||||
(
|
||||
"categories",
|
||||
categoriesSerialized,
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
"hint", ""
|
||||
},
|
||||
{
|
||||
"hint_start", 1
|
||||
},
|
||||
{
|
||||
"total", CollectionHelper.Categories.Count
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
[HttpGet("searches/{endpointName}")]
|
||||
public async Task<IActionResult> GetCategorySlots(string endpointName, [FromQuery] int pageStart, [FromQuery] int pageSize)
|
||||
{
|
||||
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request);
|
||||
|
||||
if (userAndToken == null) return this.StatusCode(403, "");
|
||||
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
User user = userAndToken.Value.Item1;
|
||||
GameToken gameToken = userAndToken.Value.Item2;
|
||||
|
||||
Category? category = CollectionHelper.Categories.FirstOrDefault(c => c.Endpoint == endpointName);
|
||||
if (category == null) return this.NotFound();
|
||||
|
||||
Logger.LogDebug("Found category " + category, LogArea.Category);
|
||||
|
||||
List<Slot> slots;
|
||||
int totalSlots;
|
||||
|
||||
if (category is CategoryWithUser categoryWithUser)
|
||||
{
|
||||
slots = categoryWithUser.GetSlots(this.database, user, pageStart, pageSize).ToList();
|
||||
totalSlots = categoryWithUser.GetTotalSlots(this.database, user);
|
||||
}
|
||||
else
|
||||
{
|
||||
slots = category.GetSlots(this.database, pageStart, pageSize).ToList();
|
||||
totalSlots = category.GetTotalSlots(this.database);
|
||||
}
|
||||
|
||||
string slotsSerialized = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize(gameToken.GameVersion));
|
||||
|
||||
return this.Ok
|
||||
(
|
||||
LbpSerializer.TaggedStringElement
|
||||
(
|
||||
"results",
|
||||
slotsSerialized,
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
"total", totalSlots
|
||||
},
|
||||
{
|
||||
"hint_start", pageStart + pageSize
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.GameAPI.Controllers.Slots;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/tags")]
|
||||
[Produces("text/plain")]
|
||||
public class LevelTagsController : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public IActionResult Get()
|
||||
{
|
||||
string[] tags = Enum.GetNames(typeof(LevelTags));
|
||||
|
||||
int i = 0;
|
||||
foreach (string tag in tags)
|
||||
{
|
||||
tags[i] = $"TAG_{tag.Replace("_", "-")}";
|
||||
i++;
|
||||
}
|
||||
|
||||
return this.Ok(string.Join(",", tags));
|
||||
}
|
||||
}
|
212
ProjectLighthouse.GameAPI/Controllers/Slots/ListController.cs
Normal file
212
ProjectLighthouse.GameAPI/Controllers/Slots/ListController.cs
Normal file
|
@ -0,0 +1,212 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.GameAPI.Controllers.Slots;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
[Produces("text/xml")]
|
||||
public class ListController : ControllerBase
|
||||
{
|
||||
private readonly Database database;
|
||||
public ListController(Database database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
#region Levels
|
||||
|
||||
#region Level Queue (lolcatftw)
|
||||
|
||||
[HttpGet("slots/lolcatftw/{username}")]
|
||||
public async Task<IActionResult> GetLevelQueue(string username, [FromQuery] int pageSize, [FromQuery] int pageStart)
|
||||
{
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
GameVersion gameVersion = token.GameVersion;
|
||||
|
||||
IEnumerable<QueuedLevel> queuedLevels = this.database.QueuedLevels.Include(q => q.User)
|
||||
.Include(q => q.Slot)
|
||||
.Include(q => q.Slot.Location)
|
||||
.Include(q => q.Slot.Creator)
|
||||
.Where(q => q.Slot.GameVersion <= gameVersion)
|
||||
.Where(q => q.User.Username == username)
|
||||
.Skip(pageStart - 1)
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.AsEnumerable();
|
||||
|
||||
string response = queuedLevels.Aggregate(string.Empty, (current, q) => current + q.Slot.Serialize(gameVersion));
|
||||
|
||||
return this.Ok
|
||||
(
|
||||
LbpSerializer.TaggedStringElement
|
||||
("slots", response, "total", this.database.QueuedLevels.Include(q => q.User).Count(q => q.User.Username == username))
|
||||
);
|
||||
}
|
||||
|
||||
[HttpPost("lolcatftw/add/user/{id:int}")]
|
||||
public async Task<IActionResult> AddQueuedLevel(int id)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
|
||||
if (slot == null) return this.NotFound();
|
||||
|
||||
await this.database.QueueLevel(user, slot);
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
[HttpPost("lolcatftw/remove/user/{id:int}")]
|
||||
public async Task<IActionResult> RemoveQueuedLevel(int id)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
|
||||
if (slot == null) return this.NotFound();
|
||||
|
||||
await this.database.UnqueueLevel(user, slot);
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
[HttpPost("lolcatftw/clear")]
|
||||
public async Task<IActionResult> ClearQueuedLevels()
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
this.database.QueuedLevels.RemoveRange(this.database.QueuedLevels.Where(q => q.UserId == user.UserId));
|
||||
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Hearted Levels
|
||||
|
||||
[HttpGet("favouriteSlots/{username}")]
|
||||
public async Task<IActionResult> GetFavouriteSlots(string username, [FromQuery] int pageSize, [FromQuery] int pageStart)
|
||||
{
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
GameVersion gameVersion = token.GameVersion;
|
||||
|
||||
IEnumerable<HeartedLevel> heartedLevels = this.database.HeartedLevels.Include(q => q.User)
|
||||
.Include(q => q.Slot)
|
||||
.Include(q => q.Slot.Location)
|
||||
.Include(q => q.Slot.Creator)
|
||||
.Where(q => q.Slot.GameVersion <= gameVersion)
|
||||
.Where(q => q.User.Username == username)
|
||||
.Skip(pageStart - 1)
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.AsEnumerable();
|
||||
|
||||
string response = heartedLevels.Aggregate(string.Empty, (current, q) => current + q.Slot.Serialize(gameVersion));
|
||||
|
||||
return this.Ok
|
||||
(
|
||||
LbpSerializer.TaggedStringElement
|
||||
("favouriteSlots", response, "total", this.database.HeartedLevels.Include(q => q.User).Count(q => q.User.Username == username))
|
||||
);
|
||||
}
|
||||
|
||||
[HttpPost("favourite/slot/user/{id:int}")]
|
||||
public async Task<IActionResult> AddFavouriteSlot(int id)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
|
||||
if (slot == null) return this.NotFound();
|
||||
|
||||
await this.database.HeartLevel(user, slot);
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
[HttpPost("unfavourite/slot/user/{id:int}")]
|
||||
public async Task<IActionResult> RemoveFavouriteSlot(int id)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
|
||||
if (slot == null) return this.NotFound();
|
||||
|
||||
await this.database.UnheartLevel(user, slot);
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion Levels
|
||||
|
||||
#region Users
|
||||
|
||||
[HttpGet("favouriteUsers/{username}")]
|
||||
public async Task<IActionResult> GetFavouriteUsers(string username, [FromQuery] int pageSize, [FromQuery] int pageStart)
|
||||
{
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
IEnumerable<HeartedProfile> heartedProfiles = this.database.HeartedProfiles.Include
|
||||
(q => q.User)
|
||||
.Include(q => q.HeartedUser)
|
||||
.Include(q => q.HeartedUser.Location)
|
||||
.Where(q => q.User.Username == username)
|
||||
.Skip(pageStart - 1)
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.AsEnumerable();
|
||||
|
||||
string response = heartedProfiles.Aggregate(string.Empty, (current, q) => current + q.HeartedUser.Serialize(token.GameVersion));
|
||||
|
||||
return this.Ok
|
||||
(
|
||||
LbpSerializer.TaggedStringElement
|
||||
("favouriteUsers", response, "total", this.database.HeartedProfiles.Include(q => q.User).Count(q => q.User.Username == username))
|
||||
);
|
||||
}
|
||||
|
||||
[HttpPost("favourite/user/{username}")]
|
||||
public async Task<IActionResult> AddFavouriteUser(string username)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
|
||||
if (heartedUser == null) return this.NotFound();
|
||||
|
||||
await this.database.HeartUser(user, heartedUser);
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
[HttpPost("unfavourite/user/{username}")]
|
||||
public async Task<IActionResult> RemoveFavouriteUser(string username)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
|
||||
if (heartedUser == null) return this.NotFound();
|
||||
|
||||
await this.database.UnheartUser(user, heartedUser);
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
236
ProjectLighthouse.GameAPI/Controllers/Slots/PublishController.cs
Normal file
236
ProjectLighthouse.GameAPI/Controllers/Slots/PublishController.cs
Normal file
|
@ -0,0 +1,236 @@
|
|||
#nullable enable
|
||||
using System.Xml.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Files;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Types.Profiles;
|
||||
using LBPUnion.ProjectLighthouse.Types.Settings;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.GameAPI.Controllers.Slots;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
[Produces("text/xml")]
|
||||
public class PublishController : ControllerBase
|
||||
{
|
||||
private readonly Database database;
|
||||
|
||||
public PublishController(Database 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()
|
||||
{
|
||||
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request);
|
||||
|
||||
if (userAndToken == null) return this.StatusCode(403, "");
|
||||
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
User user = userAndToken.Value.Item1;
|
||||
GameToken gameToken = userAndToken.Value.Item2;
|
||||
|
||||
Slot? slot = await this.getSlotFromBody();
|
||||
if (slot == null) return this.BadRequest(); // if the level cant be parsed then it obviously cant be uploaded
|
||||
|
||||
if (string.IsNullOrEmpty(slot.RootLevel)) 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) return this.NotFound();
|
||||
if (oldSlot.CreatorId != user.UserId) return this.BadRequest();
|
||||
}
|
||||
else if (user.GetUsedSlotsForGame(gameToken.GameVersion) > ServerConfiguration.Instance.UserGeneratedContentLimits.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()
|
||||
{
|
||||
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request);
|
||||
|
||||
if (userAndToken == null) return this.StatusCode(403, "");
|
||||
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
User user = userAndToken.Value.Item1;
|
||||
GameToken gameToken = userAndToken.Value.Item2;
|
||||
Slot? slot = await this.getSlotFromBody();
|
||||
|
||||
if (slot == null) return this.BadRequest();
|
||||
|
||||
if (slot.Location == null) return this.BadRequest();
|
||||
|
||||
if (slot.Description.Length > 200) return this.BadRequest();
|
||||
|
||||
if (slot.Name.Length > 100) return this.BadRequest();
|
||||
|
||||
if (slot.Resources.Any(resource => !FileHelper.ResourceExists(resource)))
|
||||
{
|
||||
return this.BadRequest();
|
||||
}
|
||||
|
||||
LbpFile? rootLevel = LbpFile.FromHash(slot.RootLevel);
|
||||
|
||||
if (rootLevel == null) return this.BadRequest();
|
||||
|
||||
if (rootLevel.FileType != LbpFileType.Level) return this.BadRequest();
|
||||
|
||||
// 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) return this.NotFound();
|
||||
|
||||
if (oldSlot.Location == null) throw new ArgumentNullException();
|
||||
|
||||
if (oldSlot.CreatorId != user.UserId) return this.BadRequest();
|
||||
|
||||
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;
|
||||
|
||||
slot.PlaysLBPVita = oldSlot.PlaysLBPVita;
|
||||
slot.PlaysLBPVitaComplete = oldSlot.PlaysLBPVitaComplete;
|
||||
slot.PlaysLBPVitaUnique = oldSlot.PlaysLBPVitaUnique;
|
||||
|
||||
#endregion
|
||||
|
||||
slot.FirstUploaded = oldSlot.FirstUploaded;
|
||||
slot.LastUpdated = TimeHelper.UnixTimeMilliseconds();
|
||||
|
||||
slot.TeamPick = oldSlot.TeamPick;
|
||||
|
||||
// Only update a slot's gameVersion if the level was actually change
|
||||
if (oldSlot.RootLevel != slot.RootLevel)
|
||||
{
|
||||
slot.GameVersion = gameToken.GameVersion;
|
||||
}
|
||||
else
|
||||
{
|
||||
slot.GameVersion = oldSlot.GameVersion;
|
||||
}
|
||||
|
||||
if (slot.MinimumPlayers == 0 || slot.MaximumPlayers == 0)
|
||||
{
|
||||
slot.MinimumPlayers = 1;
|
||||
slot.MaximumPlayers = 4;
|
||||
}
|
||||
|
||||
this.database.Entry(oldSlot).CurrentValues.SetValues(slot);
|
||||
await this.database.SaveChangesAsync();
|
||||
return this.Ok(oldSlot.Serialize(gameToken.GameVersion));
|
||||
}
|
||||
|
||||
if (user.GetUsedSlotsForGame(gameToken.GameVersion) > ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots)
|
||||
{
|
||||
return this.StatusCode(403, "");
|
||||
}
|
||||
|
||||
//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.UnixTimeMilliseconds();
|
||||
slot.LastUpdated = TimeHelper.UnixTimeMilliseconds();
|
||||
slot.GameVersion = gameToken.GameVersion;
|
||||
|
||||
if (slot.MinimumPlayers == 0 || slot.MaximumPlayers == 0)
|
||||
{
|
||||
slot.MinimumPlayers = 1;
|
||||
slot.MaximumPlayers = 4;
|
||||
}
|
||||
|
||||
this.database.Slots.Add(slot);
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
await WebhookHelper.SendWebhook
|
||||
(
|
||||
"New level published!",
|
||||
$"**{user.Username}** just published a new level: [**{slot.Name}**]({ServerConfiguration.Instance.ExternalUrl}/slot/{slot.SlotId})\n{slot.Description}"
|
||||
);
|
||||
|
||||
return this.Ok(slot.Serialize(gameToken.GameVersion));
|
||||
}
|
||||
|
||||
[HttpPost("unpublish/{id:int}")]
|
||||
public async Task<IActionResult> Unpublish(int id)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
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();
|
||||
|
||||
if (slot.CreatorId != user.UserId) return this.StatusCode(403, "");
|
||||
|
||||
this.database.Locations.Remove(slot.Location);
|
||||
this.database.Slots.Remove(slot);
|
||||
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
private async Task<Slot?> getSlotFromBody()
|
||||
{
|
||||
this.Request.Body.Position = 0;
|
||||
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
|
||||
|
||||
XmlSerializer serializer = new(typeof(Slot));
|
||||
Slot? slot = (Slot?)serializer.Deserialize(new StringReader(bodyString));
|
||||
|
||||
SanitizationHelper.SanitizeStringsInClass(slot);
|
||||
|
||||
return slot;
|
||||
}
|
||||
}
|
326
ProjectLighthouse.GameAPI/Controllers/Slots/ReviewController.cs
Normal file
326
ProjectLighthouse.GameAPI/Controllers/Slots/ReviewController.cs
Normal file
|
@ -0,0 +1,326 @@
|
|||
#nullable enable
|
||||
using System.Xml.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Helpers.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Types.Reviews;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.GameAPI.Controllers.Slots;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
[Produces("text/xml")]
|
||||
public class ReviewController : ControllerBase
|
||||
{
|
||||
private readonly Database database;
|
||||
|
||||
public ReviewController(Database database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
// LBP1 rating
|
||||
[HttpPost("rate/user/{slotId}")]
|
||||
public async Task<IActionResult> Rate(int slotId, [FromQuery] int rating)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
Slot? slot = await this.database.Slots.Include(s => s.Creator).Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == slotId);
|
||||
if (slot == null) return this.StatusCode(403, "");
|
||||
|
||||
RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == slotId && r.UserId == user.UserId);
|
||||
if (ratedLevel == null)
|
||||
{
|
||||
ratedLevel = new RatedLevel
|
||||
{
|
||||
SlotId = slotId,
|
||||
UserId = user.UserId,
|
||||
Rating = 0,
|
||||
};
|
||||
this.database.RatedLevels.Add(ratedLevel);
|
||||
}
|
||||
|
||||
ratedLevel.RatingLBP1 = Math.Max(Math.Min(5, rating), 0);
|
||||
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
// LBP2 and beyond rating
|
||||
[HttpPost("dpadrate/user/{slotId:int}")]
|
||||
public async Task<IActionResult> DPadRate(int slotId, [FromQuery] int rating)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
Slot? slot = await this.database.Slots.Include(s => s.Creator).Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == slotId);
|
||||
if (slot == null) return this.StatusCode(403, "");
|
||||
|
||||
RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == slotId && r.UserId == user.UserId);
|
||||
if (ratedLevel == null)
|
||||
{
|
||||
ratedLevel = new RatedLevel
|
||||
{
|
||||
SlotId = slotId,
|
||||
UserId = user.UserId,
|
||||
RatingLBP1 = 0,
|
||||
};
|
||||
this.database.RatedLevels.Add(ratedLevel);
|
||||
}
|
||||
|
||||
ratedLevel.Rating = Math.Clamp(rating, -1, 1);
|
||||
|
||||
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == user.UserId);
|
||||
if (review != null) review.Thumb = ratedLevel.Rating;
|
||||
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
[HttpPost("postReview/user/{slotId:int}")]
|
||||
public async Task<IActionResult> PostReview(int slotId)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
Review? newReview = await this.getReviewFromBody();
|
||||
if (newReview == null) return this.BadRequest();
|
||||
|
||||
if (newReview.Text.Length > 100) return this.BadRequest();
|
||||
|
||||
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == user.UserId);
|
||||
|
||||
if (review == null)
|
||||
{
|
||||
review = new Review
|
||||
{
|
||||
SlotId = slotId,
|
||||
ReviewerId = user.UserId,
|
||||
DeletedBy = DeletedBy.None,
|
||||
ThumbsUp = 0,
|
||||
ThumbsDown = 0,
|
||||
};
|
||||
this.database.Reviews.Add(review);
|
||||
}
|
||||
review.Thumb = newReview.Thumb;
|
||||
review.LabelCollection = newReview.LabelCollection;
|
||||
review.Text = newReview.Text;
|
||||
review.Deleted = false;
|
||||
review.Timestamp = TimeHelper.UnixTimeMilliseconds();
|
||||
|
||||
// sometimes the game posts/updates a review rating without also calling dpadrate/user/etc (why??)
|
||||
RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == slotId && r.UserId == user.UserId);
|
||||
if (ratedLevel == null)
|
||||
{
|
||||
ratedLevel = new RatedLevel
|
||||
{
|
||||
SlotId = slotId,
|
||||
UserId = user.UserId,
|
||||
RatingLBP1 = 0,
|
||||
};
|
||||
this.database.RatedLevels.Add(ratedLevel);
|
||||
}
|
||||
|
||||
ratedLevel.Rating = newReview.Thumb;
|
||||
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
[HttpGet("reviewsFor/user/{slotId:int}")]
|
||||
public async Task<IActionResult> ReviewsFor(int slotId, [FromQuery] int pageStart = 1, [FromQuery] int pageSize = 10)
|
||||
{
|
||||
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request);
|
||||
|
||||
if (userAndToken == null) return this.StatusCode(403, "");
|
||||
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
User user = userAndToken.Value.Item1;
|
||||
GameToken gameToken = userAndToken.Value.Item2;
|
||||
|
||||
GameVersion gameVersion = gameToken.GameVersion;
|
||||
|
||||
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slotId);
|
||||
if (slot == null) return this.BadRequest();
|
||||
|
||||
IQueryable<Review?> reviews = this.database.Reviews.ByGameVersion(gameVersion, true)
|
||||
.Where(r => r.SlotId == slotId)
|
||||
.Include(r => r.Reviewer)
|
||||
.Include(r => r.Slot)
|
||||
.OrderByDescending(r => r.ThumbsUp - r.ThumbsDown)
|
||||
.ThenByDescending(r => r.Timestamp)
|
||||
.Skip(pageStart - 1)
|
||||
.Take(pageSize);
|
||||
|
||||
List<Review?> reviewList = reviews.ToList();
|
||||
|
||||
string inner = reviewList.Aggregate
|
||||
(
|
||||
string.Empty,
|
||||
(current, review) =>
|
||||
{
|
||||
if (review == null) return current;
|
||||
|
||||
RatedReview? yourThumb = this.database.RatedReviews.FirstOrDefault(r => r.ReviewId == review.ReviewId && r.UserId == user.UserId);
|
||||
return current + review.Serialize(null, yourThumb);
|
||||
}
|
||||
);
|
||||
string response = LbpSerializer.TaggedStringElement
|
||||
(
|
||||
"reviews",
|
||||
inner,
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
"hint_start", pageStart + pageSize
|
||||
},
|
||||
{
|
||||
"hint", reviewList.LastOrDefault()!.Timestamp // not sure
|
||||
},
|
||||
}
|
||||
);
|
||||
return this.Ok(response);
|
||||
}
|
||||
|
||||
[HttpGet("reviewsBy/{username}")]
|
||||
public async Task<IActionResult> ReviewsBy(string username, [FromQuery] int pageStart = 1, [FromQuery] int pageSize = 10)
|
||||
{
|
||||
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request);
|
||||
|
||||
if (userAndToken == null) return this.StatusCode(403, "");
|
||||
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
User user = userAndToken.Value.Item1;
|
||||
GameToken gameToken = userAndToken.Value.Item2;
|
||||
|
||||
GameVersion gameVersion = gameToken.GameVersion;
|
||||
|
||||
IEnumerable<Review?> reviews = this.database.Reviews.ByGameVersion(gameVersion, true)
|
||||
.Include(r => r.Reviewer)
|
||||
.Include(r => r.Slot)
|
||||
.Where(r => r.Reviewer!.Username == username)
|
||||
.OrderByDescending(r => r.Timestamp)
|
||||
.Skip(pageStart - 1)
|
||||
.Take(pageSize);
|
||||
|
||||
List<Review?> reviewList = reviews.ToList();
|
||||
|
||||
string inner = reviewList.Aggregate
|
||||
(
|
||||
string.Empty,
|
||||
(current, review) =>
|
||||
{
|
||||
if (review == null) return current;
|
||||
|
||||
RatedReview? ratedReview = this.database.RatedReviews.FirstOrDefault(r => r.ReviewId == review.ReviewId && r.UserId == user.UserId);
|
||||
return current + review.Serialize(null, ratedReview);
|
||||
}
|
||||
);
|
||||
|
||||
string response = LbpSerializer.TaggedStringElement
|
||||
(
|
||||
"reviews",
|
||||
inner,
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
"hint_start", pageStart
|
||||
},
|
||||
{
|
||||
"hint", reviewList.LastOrDefault()!.Timestamp // Seems to be the timestamp of oldest
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return this.Ok(response);
|
||||
}
|
||||
|
||||
[HttpPost("rateReview/user/{slotId:int}/{username}")]
|
||||
public async Task<IActionResult> RateReview(int slotId, string username, [FromQuery] int rating = 0)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
User? reviewer = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
|
||||
if (reviewer == null) return this.StatusCode(400, "");
|
||||
|
||||
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == reviewer.UserId);
|
||||
if (review == null) return this.StatusCode(400, "");
|
||||
|
||||
RatedReview? ratedReview = await this.database.RatedReviews.FirstOrDefaultAsync(r => r.ReviewId == review.ReviewId && r.UserId == user.UserId);
|
||||
if (ratedReview == null)
|
||||
{
|
||||
ratedReview = new RatedReview
|
||||
{
|
||||
ReviewId = review.ReviewId,
|
||||
UserId = user.UserId,
|
||||
Thumb = 0,
|
||||
};
|
||||
this.database.RatedReviews.Add(ratedReview);
|
||||
await this.database.SaveChangesAsync();
|
||||
}
|
||||
|
||||
int oldRating = ratedReview.Thumb;
|
||||
ratedReview.Thumb = Math.Clamp(rating, -1, 1);
|
||||
if (oldRating == ratedReview.Thumb) return this.Ok();
|
||||
|
||||
// if the user's rating changed then we recount the review's ratings to ensure accuracy
|
||||
List<RatedReview> reactions = await this.database.RatedReviews.Where(r => r.ReviewId == review.ReviewId).ToListAsync();
|
||||
int yay = 0;
|
||||
int boo = 0;
|
||||
foreach (RatedReview r in reactions)
|
||||
{
|
||||
switch (r.Thumb)
|
||||
{
|
||||
case -1:
|
||||
boo++;
|
||||
break;
|
||||
case 1:
|
||||
yay++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
review.ThumbsDown = boo;
|
||||
review.ThumbsUp = yay;
|
||||
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
[HttpPost("deleteReview/user/{slotId:int}/{username}")]
|
||||
public async Task<IActionResult> DeleteReview(int slotId, string username)
|
||||
{
|
||||
User? reviewer = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
|
||||
if (reviewer == null) return this.StatusCode(403, "");
|
||||
|
||||
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == reviewer.UserId);
|
||||
if (review == null) return this.StatusCode(403, "");
|
||||
|
||||
review.Deleted = true;
|
||||
review.DeletedBy = DeletedBy.LevelAuthor;
|
||||
|
||||
await this.database.SaveChangesAsync();
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
private async Task<Review?> getReviewFromBody()
|
||||
{
|
||||
this.Request.Body.Position = 0;
|
||||
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
|
||||
|
||||
XmlSerializer serializer = new(typeof(Review));
|
||||
Review? review = (Review?)serializer.Deserialize(new StringReader(bodyString));
|
||||
SanitizationHelper.SanitizeStringsInClass(review);
|
||||
return review;
|
||||
}
|
||||
}
|
169
ProjectLighthouse.GameAPI/Controllers/Slots/ScoreController.cs
Normal file
169
ProjectLighthouse.GameAPI/Controllers/Slots/ScoreController.cs
Normal file
|
@ -0,0 +1,169 @@
|
|||
#nullable enable
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Xml.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.GameAPI.Controllers.Slots;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
[Produces("text/xml")]
|
||||
public class ScoreController : ControllerBase
|
||||
{
|
||||
private readonly Database database;
|
||||
|
||||
public ScoreController(Database database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
[HttpPost("scoreboard/user/{id:int}")]
|
||||
public async Task<IActionResult> SubmitScore(int id, [FromQuery] bool lbp1 = false, [FromQuery] bool lbp2 = false, [FromQuery] bool lbp3 = false)
|
||||
{
|
||||
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request);
|
||||
|
||||
if (userAndToken == null) return this.StatusCode(403, "");
|
||||
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
User user = userAndToken.Value.Item1;
|
||||
GameToken gameToken = userAndToken.Value.Item2;
|
||||
|
||||
this.Request.Body.Position = 0;
|
||||
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
|
||||
|
||||
XmlSerializer serializer = new(typeof(Score));
|
||||
Score? score = (Score?)serializer.Deserialize(new StringReader(bodyString));
|
||||
if (score == null) return this.BadRequest();
|
||||
|
||||
SanitizationHelper.SanitizeStringsInClass(score);
|
||||
|
||||
score.SlotId = id;
|
||||
|
||||
Slot? slot = this.database.Slots.FirstOrDefault(s => s.SlotId == score.SlotId);
|
||||
if (slot == null) return this.BadRequest();
|
||||
|
||||
switch (gameToken.GameVersion)
|
||||
{
|
||||
case GameVersion.LittleBigPlanet1:
|
||||
slot.PlaysLBP1Complete++;
|
||||
break;
|
||||
case GameVersion.LittleBigPlanet2:
|
||||
slot.PlaysLBP2Complete++;
|
||||
break;
|
||||
case GameVersion.LittleBigPlanet3:
|
||||
slot.PlaysLBP3Complete++;
|
||||
break;
|
||||
case GameVersion.LittleBigPlanetVita:
|
||||
slot.PlaysLBPVitaComplete++;
|
||||
break;
|
||||
}
|
||||
|
||||
IQueryable<Score> existingScore = this.database.Scores.Where(s => s.SlotId == score.SlotId)
|
||||
.Where(s => s.PlayerIdCollection == score.PlayerIdCollection)
|
||||
.Where(s => s.Type == score.Type);
|
||||
|
||||
if (existingScore.Any())
|
||||
{
|
||||
Score first = existingScore.First(s => s.SlotId == score.SlotId);
|
||||
score.ScoreId = first.ScoreId;
|
||||
score.Points = Math.Max(first.Points, score.Points);
|
||||
this.database.Entry(first).CurrentValues.SetValues(score);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.database.Scores.Add(score);
|
||||
}
|
||||
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
string myRanking = this.getScores(score.SlotId, score.Type, user, -1, 5, "scoreboardSegment");
|
||||
|
||||
return this.Ok(myRanking);
|
||||
}
|
||||
|
||||
[HttpGet("friendscores/user/{slotId:int}/{type:int}")]
|
||||
public IActionResult FriendScores(int slotId, int type)
|
||||
//=> await TopScores(slotId, type);
|
||||
=> this.Ok(LbpSerializer.BlankElement("scores"));
|
||||
|
||||
[HttpGet("topscores/user/{slotId:int}/{type:int}")]
|
||||
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
|
||||
public async Task<IActionResult> TopScores(int slotId, int type, [FromQuery] int pageStart = -1, [FromQuery] int pageSize = 5)
|
||||
{
|
||||
// Get username
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
return this.Ok(this.getScores(slotId, type, user, pageStart, pageSize));
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
|
||||
private string getScores
|
||||
(
|
||||
int slotId,
|
||||
int type,
|
||||
User user,
|
||||
int pageStart = -1,
|
||||
int pageSize = 5,
|
||||
string rootName = "scores"
|
||||
)
|
||||
{
|
||||
// This is hella ugly but it technically assigns the proper rank to a score
|
||||
// var needed for Anonymous type returned from SELECT
|
||||
var rankedScores = this.database.Scores.Where(s => s.SlotId == slotId && s.Type == type)
|
||||
.OrderByDescending(s => s.Points)
|
||||
.ToList()
|
||||
.Select
|
||||
(
|
||||
(s, rank) => new
|
||||
{
|
||||
Score = s,
|
||||
Rank = rank + 1,
|
||||
}
|
||||
);
|
||||
|
||||
// Find your score, since even if you aren't in the top list your score is pinned
|
||||
var myScore = rankedScores.Where(rs => rs.Score.PlayerIdCollection.Contains(user.Username)).OrderByDescending(rs => rs.Score.Points).FirstOrDefault();
|
||||
|
||||
// Paginated viewing: if not requesting pageStart, get results around user
|
||||
var pagedScores = rankedScores.Skip(pageStart != -1 || myScore == null ? pageStart - 1 : myScore.Rank - 3).Take(Math.Min(pageSize, 30));
|
||||
|
||||
string serializedScores = pagedScores.Aggregate
|
||||
(
|
||||
string.Empty,
|
||||
(current, rs) =>
|
||||
{
|
||||
rs.Score.Rank = rs.Rank;
|
||||
return current + rs.Score.Serialize();
|
||||
}
|
||||
);
|
||||
|
||||
string res;
|
||||
if (myScore == null) res = LbpSerializer.StringElement(rootName, serializedScores);
|
||||
else
|
||||
res = LbpSerializer.TaggedStringElement
|
||||
(
|
||||
rootName,
|
||||
serializedScores,
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
"yourScore", myScore.Score.Points
|
||||
},
|
||||
{
|
||||
"yourRank", myScore.Rank
|
||||
}, //This is the numerator of your position globally in the side menu.
|
||||
{
|
||||
"totalNumScores", rankedScores.Count()
|
||||
}, // This is the denominator of your position globally in the side menu.
|
||||
}
|
||||
);
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.GameAPI.Controllers.Slots;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
[Produces("text/xml")]
|
||||
public class SearchController : ControllerBase
|
||||
{
|
||||
private readonly Database database;
|
||||
public SearchController(Database database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
[HttpGet("slots/search")]
|
||||
public async Task<IActionResult> SearchSlots([FromQuery] string query, [FromQuery] int pageSize, [FromQuery] int pageStart)
|
||||
{
|
||||
GameToken? gameToken = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (gameToken == null) return this.StatusCode(403, "");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(query)) return this.BadRequest();
|
||||
|
||||
query = query.ToLower();
|
||||
|
||||
string[] keywords = query.Split(" ");
|
||||
|
||||
IQueryable<Slot> dbQuery = this.database.Slots.Include
|
||||
(s => s.Creator)
|
||||
.Include(s => s.Location)
|
||||
.OrderBy(s => !s.TeamPick)
|
||||
.ThenByDescending(s => s.FirstUploaded)
|
||||
.Where(s => s.SlotId >= 0); // dumb query to conv into IQueryable
|
||||
|
||||
// ReSharper disable once LoopCanBeConvertedToQuery
|
||||
foreach (string keyword in keywords)
|
||||
dbQuery = dbQuery.Where
|
||||
(
|
||||
s => s.Name.ToLower().Contains(keyword) ||
|
||||
s.Description.ToLower().Contains(keyword) ||
|
||||
s.Creator!.Username.ToLower().Contains(keyword) ||
|
||||
s.SlotId.ToString().Equals(keyword)
|
||||
);
|
||||
|
||||
List<Slot> slots = await dbQuery.Skip(pageStart - 1).Take(Math.Min(pageSize, 30)).ToListAsync();
|
||||
|
||||
string response = slots.Aggregate("", (current, slot) => current + slot.Serialize(gameToken.GameVersion));
|
||||
|
||||
return this.Ok(LbpSerializer.TaggedStringElement("slots", response, "total", dbQuery.Count()));
|
||||
}
|
||||
}
|
402
ProjectLighthouse.GameAPI/Controllers/Slots/SlotsController.cs
Normal file
402
ProjectLighthouse.GameAPI/Controllers/Slots/SlotsController.cs
Normal file
|
@ -0,0 +1,402 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Helpers.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Types.Reviews;
|
||||
using LBPUnion.ProjectLighthouse.Types.Settings;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.GameAPI.Controllers.Slots;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
[Produces("text/xml")]
|
||||
public class SlotsController : ControllerBase
|
||||
{
|
||||
private readonly Database database;
|
||||
public SlotsController(Database database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
[HttpGet("slots/by")]
|
||||
public async Task<IActionResult> SlotsBy([FromQuery] string u, [FromQuery] int pageStart, [FromQuery] int pageSize)
|
||||
{
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
GameVersion gameVersion = token.GameVersion;
|
||||
|
||||
User? user = await this.database.Users.FirstOrDefaultAsync(dbUser => dbUser.Username == u);
|
||||
if (user == null) return this.NotFound();
|
||||
|
||||
string response = Enumerable.Aggregate
|
||||
(
|
||||
this.database.Slots.ByGameVersion(gameVersion, token.UserId == user.UserId, true)
|
||||
.Where(s => s.Creator!.Username == user.Username)
|
||||
.Skip(pageStart - 1)
|
||||
.Take(Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots)),
|
||||
string.Empty,
|
||||
(current, slot) => current + slot.Serialize(token.GameVersion)
|
||||
);
|
||||
|
||||
return this.Ok
|
||||
(
|
||||
LbpSerializer.TaggedStringElement
|
||||
(
|
||||
"slots",
|
||||
response,
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
"hint_start", pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots)
|
||||
},
|
||||
{
|
||||
"total", user.UsedSlots
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
[HttpGet("s/user/{id:int}")]
|
||||
public async Task<IActionResult> SUser(int id)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
GameVersion gameVersion = token.GameVersion;
|
||||
|
||||
Slot? slot = await this.database.Slots.ByGameVersion(gameVersion, true, true).FirstOrDefaultAsync(s => s.SlotId == id);
|
||||
|
||||
if (slot == null) return this.NotFound();
|
||||
|
||||
RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == id && r.UserId == user.UserId);
|
||||
VisitedLevel? visitedLevel = await this.database.VisitedLevels.FirstOrDefaultAsync(r => r.SlotId == id && r.UserId == user.UserId);
|
||||
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == id && r.ReviewerId == user.UserId);
|
||||
return this.Ok(slot.Serialize(gameVersion, ratedLevel, visitedLevel, review));
|
||||
}
|
||||
|
||||
[HttpGet("slots/cool")]
|
||||
public async Task<IActionResult> Lbp1CoolSlots([FromQuery] int page)
|
||||
{
|
||||
const int pageSize = 30;
|
||||
return await this.CoolSlots((page - 1) * pageSize, pageSize);
|
||||
}
|
||||
|
||||
[HttpGet("slots/lbp2cool")]
|
||||
public async Task<IActionResult> CoolSlots
|
||||
(
|
||||
[FromQuery] int pageStart,
|
||||
[FromQuery] int pageSize,
|
||||
[FromQuery] string? gameFilterType = null,
|
||||
[FromQuery] int? players = null,
|
||||
[FromQuery] bool? move = null,
|
||||
[FromQuery] int? page = null
|
||||
)
|
||||
{
|
||||
int _pageStart = pageStart;
|
||||
if (page != null) _pageStart = (int)page * 30;
|
||||
// bit of a better placeholder until we can track average user interaction with /stream endpoint
|
||||
return await this.ThumbsSlots(_pageStart, Math.Min(pageSize, 30), gameFilterType, players, move, "thisWeek");
|
||||
}
|
||||
|
||||
[HttpGet("slots")]
|
||||
public async Task<IActionResult> NewestSlots([FromQuery] int pageStart, [FromQuery] int pageSize)
|
||||
{
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
GameVersion gameVersion = token.GameVersion;
|
||||
|
||||
IQueryable<Slot> slots = this.database.Slots.ByGameVersion(gameVersion, false, true)
|
||||
.OrderByDescending(s => s.FirstUploaded)
|
||||
.Skip(pageStart - 1)
|
||||
.Take(Math.Min(pageSize, 30));
|
||||
|
||||
string response = Enumerable.Aggregate(slots, string.Empty, (current, slot) => current + slot.Serialize(gameVersion));
|
||||
|
||||
return this.Ok
|
||||
(
|
||||
LbpSerializer.TaggedStringElement
|
||||
(
|
||||
"slots",
|
||||
response,
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
"hint_start", pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots)
|
||||
},
|
||||
{
|
||||
"total", await StatisticsHelper.SlotCount()
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
[HttpGet("slots/mmpicks")]
|
||||
public async Task<IActionResult> TeamPickedSlots([FromQuery] int pageStart, [FromQuery] int pageSize)
|
||||
{
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
GameVersion gameVersion = token.GameVersion;
|
||||
|
||||
IQueryable<Slot> slots = this.database.Slots.ByGameVersion(gameVersion, false, true)
|
||||
.Where(s => s.TeamPick)
|
||||
.OrderByDescending(s => s.LastUpdated)
|
||||
.Skip(pageStart - 1)
|
||||
.Take(Math.Min(pageSize, 30));
|
||||
string response = Enumerable.Aggregate(slots, string.Empty, (current, slot) => current + slot.Serialize(gameVersion));
|
||||
|
||||
return this.Ok
|
||||
(
|
||||
LbpSerializer.TaggedStringElement
|
||||
(
|
||||
"slots",
|
||||
response,
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
"hint_start", pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots)
|
||||
},
|
||||
{
|
||||
"total", await StatisticsHelper.TeamPickCount()
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
[HttpGet("slots/lbp2luckydip")]
|
||||
public async Task<IActionResult> LuckyDipSlots([FromQuery] int pageStart, [FromQuery] int pageSize, [FromQuery] int seed)
|
||||
{
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
GameVersion gameVersion = token.GameVersion;
|
||||
|
||||
IEnumerable<Slot> slots = this.database.Slots.ByGameVersion(gameVersion, false, true).OrderBy(_ => EF.Functions.Random()).Take(Math.Min(pageSize, 30));
|
||||
|
||||
string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize(gameVersion));
|
||||
|
||||
return this.Ok
|
||||
(
|
||||
LbpSerializer.TaggedStringElement
|
||||
(
|
||||
"slots",
|
||||
response,
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
"hint_start", pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots)
|
||||
},
|
||||
{
|
||||
"total", await StatisticsHelper.SlotCount()
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
[HttpGet("slots/thumbs")]
|
||||
public async Task<IActionResult> ThumbsSlots
|
||||
(
|
||||
[FromQuery] int pageStart,
|
||||
[FromQuery] int pageSize,
|
||||
[FromQuery] string? gameFilterType = null,
|
||||
[FromQuery] int? players = null,
|
||||
[FromQuery] bool? move = null,
|
||||
[FromQuery] string? dateFilterType = null
|
||||
)
|
||||
{
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
Random rand = new();
|
||||
|
||||
IEnumerable<Slot> slots = this.filterByRequest(gameFilterType, dateFilterType, token.GameVersion)
|
||||
.AsEnumerable()
|
||||
.OrderByDescending(s => s.Thumbsup)
|
||||
.ThenBy(_ => rand.Next())
|
||||
.Skip(pageStart - 1)
|
||||
.Take(Math.Min(pageSize, 30));
|
||||
|
||||
string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize(token.GameVersion));
|
||||
|
||||
return this.Ok
|
||||
(
|
||||
LbpSerializer.TaggedStringElement
|
||||
(
|
||||
"slots",
|
||||
response,
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
"hint_start", pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots)
|
||||
},
|
||||
{
|
||||
"total", await StatisticsHelper.SlotCount()
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
[HttpGet("slots/mostUniquePlays")]
|
||||
public async Task<IActionResult> MostUniquePlaysSlots
|
||||
(
|
||||
[FromQuery] int pageStart,
|
||||
[FromQuery] int pageSize,
|
||||
[FromQuery] string? gameFilterType = null,
|
||||
[FromQuery] int? players = null,
|
||||
[FromQuery] bool? move = null,
|
||||
[FromQuery] string? dateFilterType = null
|
||||
)
|
||||
{
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
Random rand = new();
|
||||
|
||||
IEnumerable<Slot> slots = this.filterByRequest(gameFilterType, dateFilterType, token.GameVersion)
|
||||
.AsEnumerable()
|
||||
.OrderByDescending
|
||||
(
|
||||
// probably not the best way to do this?
|
||||
s =>
|
||||
{
|
||||
return this.getGameFilter(gameFilterType, token.GameVersion) switch
|
||||
{
|
||||
GameVersion.LittleBigPlanet1 => s.PlaysLBP1Unique,
|
||||
GameVersion.LittleBigPlanet2 => s.PlaysLBP2Unique,
|
||||
GameVersion.LittleBigPlanet3 => s.PlaysLBP3Unique,
|
||||
GameVersion.LittleBigPlanetVita => s.PlaysLBPVitaUnique,
|
||||
_ => s.PlaysUnique,
|
||||
};
|
||||
}
|
||||
)
|
||||
.ThenBy(_ => rand.Next())
|
||||
.Skip(pageStart - 1)
|
||||
.Take(Math.Min(pageSize, 30));
|
||||
|
||||
string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize(token.GameVersion));
|
||||
|
||||
return this.Ok
|
||||
(
|
||||
LbpSerializer.TaggedStringElement
|
||||
(
|
||||
"slots",
|
||||
response,
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
"hint_start", pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots)
|
||||
},
|
||||
{
|
||||
"total", await StatisticsHelper.SlotCount()
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
[HttpGet("slots/mostHearted")]
|
||||
public async Task<IActionResult> MostHeartedSlots
|
||||
(
|
||||
[FromQuery] int pageStart,
|
||||
[FromQuery] int pageSize,
|
||||
[FromQuery] string? gameFilterType = null,
|
||||
[FromQuery] int? players = null,
|
||||
[FromQuery] bool? move = null,
|
||||
[FromQuery] string? dateFilterType = null
|
||||
)
|
||||
{
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
Random rand = new();
|
||||
|
||||
IEnumerable<Slot> slots = this.filterByRequest(gameFilterType, dateFilterType, token.GameVersion)
|
||||
.AsEnumerable()
|
||||
.OrderByDescending(s => s.Hearts)
|
||||
.ThenBy(_ => rand.Next())
|
||||
.Skip(pageStart - 1)
|
||||
.Take(Math.Min(pageSize, 30));
|
||||
|
||||
string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize(token.GameVersion));
|
||||
|
||||
return this.Ok
|
||||
(
|
||||
LbpSerializer.TaggedStringElement
|
||||
(
|
||||
"slots",
|
||||
response,
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
"hint_start", pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots)
|
||||
},
|
||||
{
|
||||
"total", await StatisticsHelper.SlotCount()
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private GameVersion getGameFilter(string? gameFilterType, GameVersion version)
|
||||
{
|
||||
if (version == GameVersion.LittleBigPlanetVita) return GameVersion.LittleBigPlanetVita;
|
||||
if (version == GameVersion.LittleBigPlanetPSP) return GameVersion.LittleBigPlanetPSP;
|
||||
|
||||
return gameFilterType switch
|
||||
{
|
||||
"lbp1" => GameVersion.LittleBigPlanet1,
|
||||
"lbp2" => GameVersion.LittleBigPlanet2,
|
||||
"lbp3" => GameVersion.LittleBigPlanet3,
|
||||
"both" => GameVersion.LittleBigPlanet2, // LBP2 default option
|
||||
null => GameVersion.LittleBigPlanet1,
|
||||
_ => GameVersion.Unknown,
|
||||
};
|
||||
}
|
||||
|
||||
private IQueryable<Slot> filterByRequest(string? gameFilterType, string? dateFilterType, GameVersion version)
|
||||
{
|
||||
if (version == GameVersion.LittleBigPlanetVita || version == GameVersion.LittleBigPlanetPSP || version == GameVersion.Unknown)
|
||||
{
|
||||
return this.database.Slots.ByGameVersion(version, false, true);
|
||||
}
|
||||
|
||||
string _dateFilterType = dateFilterType ?? "";
|
||||
|
||||
long oldestTime = _dateFilterType switch
|
||||
{
|
||||
"thisWeek" => DateTimeOffset.Now.AddDays(-7).ToUnixTimeMilliseconds(),
|
||||
"thisMonth" => DateTimeOffset.Now.AddDays(-31).ToUnixTimeMilliseconds(),
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
GameVersion gameVersion = this.getGameFilter(gameFilterType, version);
|
||||
|
||||
IQueryable<Slot> whereSlots;
|
||||
|
||||
// ReSharper disable once ConvertIfStatementToConditionalTernaryExpression
|
||||
if (gameFilterType == "both")
|
||||
// Get game versions less than the current version
|
||||
// Needs support for LBP3 ("both" = LBP1+2)
|
||||
whereSlots = this.database.Slots.Where(s => s.GameVersion <= gameVersion && s.FirstUploaded >= oldestTime);
|
||||
else
|
||||
// Get game versions exactly equal to gamefiltertype
|
||||
whereSlots = this.database.Slots.Where(s => s.GameVersion == gameVersion && s.FirstUploaded >= oldestTime);
|
||||
|
||||
return whereSlots.Include(s => s.Creator).Include(s => s.Location);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue