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:
Josh 2023-03-27 19:39:54 -05:00 committed by GitHub
parent 307b2135a3
commit 329ab66043
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
248 changed files with 4993 additions and 2896 deletions

View file

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

View file

@ -28,15 +28,18 @@ public class SlotEndpoints : ApiEndpointController
/// <returns>The slot</returns> /// <returns>The slot</returns>
/// <response code="200">The slot list, if successful.</response> /// <response code="200">The slot list, if successful.</response>
[HttpGet("slots")] [HttpGet("slots")]
[ProducesResponseType(typeof(List<MinimalSlot>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(List<ApiSlot>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetSlots([FromQuery] int limit = 20, [FromQuery] int skip = 0) public async Task<IActionResult> GetSlots([FromQuery] int limit = 20, [FromQuery] int skip = 0)
{ {
if (skip < 0) skip = 0; if (skip < 0) skip = 0;
if (limit < 0) limit = 0; if (limit < 0) limit = 0;
limit = Math.Min(ServerStatics.PageSize, limit); limit = Math.Min(ServerStatics.PageSize, limit);
IEnumerable<MinimalSlot> minimalSlots = (await this.database.Slots.OrderByDescending(s => s.FirstUploaded).Skip(skip).Take(limit).ToListAsync()).Select IEnumerable<ApiSlot> minimalSlots = await this.database.Slots.OrderByDescending(s => s.FirstUploaded)
(MinimalSlot.FromSlot); .Skip(skip)
.Take(limit)
.Select(s => ApiSlot.CreateFromEntity(s))
.ToListAsync();
return this.Ok(minimalSlots); return this.Ok(minimalSlots);
} }
@ -49,13 +52,13 @@ public class SlotEndpoints : ApiEndpointController
/// <response code="200">The slot, if successful.</response> /// <response code="200">The slot, if successful.</response>
/// <response code="404">The slot could not be found.</response> /// <response code="404">The slot could not be found.</response>
[HttpGet("slot/{id:int}")] [HttpGet("slot/{id:int}")]
[ProducesResponseType(typeof(Slot), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ApiSlot), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetSlot(int id) public async Task<IActionResult> GetSlot(int id)
{ {
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(u => u.SlotId == id); SlotEntity? slot = await this.database.Slots.FirstOrDefaultAsync(u => u.SlotId == id);
if (slot == null) return this.NotFound(); if (slot == null) return this.NotFound();
return this.Ok(slot); return this.Ok(ApiSlot.CreateFromEntity(slot));
} }
} }

View file

@ -2,10 +2,7 @@ using Microsoft.AspNetCore.Mvc;
namespace LBPUnion.ProjectLighthouse.Servers.API.Controllers; namespace LBPUnion.ProjectLighthouse.Servers.API.Controllers;
[ApiController] public class StatusController : ApiEndpointController
[Route("/api/v1")]
[Produces("application/json")]
public class StatusController : ControllerBase
{ {
[AcceptVerbs("GET", "HEAD", Route = "status")] [AcceptVerbs("GET", "HEAD", Route = "status")]
public IActionResult GetStatus() => this.Ok(); public IActionResult GetStatus() => this.Ok();

View file

@ -1,6 +1,7 @@
#nullable enable #nullable enable
using LBPUnion.ProjectLighthouse.Database; using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Servers.API.Responses;
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;
@ -29,25 +30,25 @@ public class UserEndpoints : ApiEndpointController
/// <response code="200">The user, if successful.</response> /// <response code="200">The user, if successful.</response>
/// <response code="404">The user could not be found.</response> /// <response code="404">The user could not be found.</response>
[HttpGet("user/{id:int}")] [HttpGet("user/{id:int}")]
[ProducesResponseType(typeof(User), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ApiUser), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetUser(int id) public async Task<IActionResult> GetUser(int id)
{ {
User? user = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id); UserEntity? user = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id);
if (user == null) return this.NotFound(); if (user == null) return this.NotFound();
return this.Ok(user); return this.Ok(ApiUser.CreateFromEntity(user));
} }
[HttpGet("username/{username}")] [HttpGet("username/{username}")]
[ProducesResponseType(typeof(User), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ApiUser), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetUser(string username) public async Task<IActionResult> GetUser(string username)
{ {
User? user = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username); UserEntity? user = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
if (user == null) return this.NotFound(); if (user == null) return this.NotFound();
return this.Ok(user); return this.Ok(ApiUser.CreateFromEntity(user));
} }
/// <summary> /// <summary>
@ -58,15 +59,16 @@ public class UserEndpoints : ApiEndpointController
/// <response code="200">The list of users, if any were found</response> /// <response code="200">The list of users, if any were found</response>
/// <response code="404">No users matched the query</response> /// <response code="404">No users matched the query</response>
[HttpGet("search/user")] [HttpGet("search/user")]
[ProducesResponseType(typeof(User), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ApiUser), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> SearchUsers(string query) public async Task<IActionResult> SearchUsers(string query)
{ {
List<User> users = await this.database.Users List<ApiUser> users = await this.database.Users
.Where(u => u.PermissionLevel != PermissionLevel.Banned && u.Username.Contains(query)) .Where(u => u.PermissionLevel != PermissionLevel.Banned && u.Username.Contains(query))
.Where(u => u.ProfileVisibility == PrivacyType.All) // TODO: change check for when user is logged in .Where(u => u.ProfileVisibility == PrivacyType.All) // TODO: change check for when user is logged in
.OrderByDescending(b => b.UserId) .OrderByDescending(b => b.UserId)
.Take(20) .Take(20)
.Select(u => ApiUser.CreateFromEntity(u))
.ToListAsync(); .ToListAsync();
if (!users.Any()) return this.NotFound(); if (!users.Any()) return this.NotFound();
@ -81,7 +83,7 @@ public class UserEndpoints : ApiEndpointController
/// <response code="200">The user's status, if successful.</response> /// <response code="200">The user's status, if successful.</response>
/// <response code="404">The user could not be found.</response> /// <response code="404">The user could not be found.</response>
[HttpGet("user/{id:int}/status")] [HttpGet("user/{id:int}/status")]
[ProducesResponseType(typeof(UserStatus), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ApiUser), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetUserStatus(int id) public IActionResult GetUserStatus(int id)
{ {
@ -102,8 +104,8 @@ public class UserEndpoints : ApiEndpointController
string authToken = authHeader[(authHeader.IndexOf(' ') + 1)..]; string authToken = authHeader[(authHeader.IndexOf(' ') + 1)..];
ApiKey? apiKey = await this.database.APIKeys.FirstOrDefaultAsync(k => k.Key == authToken); ApiKeyEntity? apiKey = await this.database.APIKeys.FirstOrDefaultAsync(k => k.Key == authToken);
if (apiKey == null) return this.StatusCode(403, null); if (apiKey == null) return this.Forbid();
if (!string.IsNullOrWhiteSpace(username)) if (!string.IsNullOrWhiteSpace(username))
{ {
@ -111,7 +113,7 @@ public class UserEndpoints : ApiEndpointController
if (userExists) return this.BadRequest(); if (userExists) return this.BadRequest();
} }
RegistrationToken token = new() RegistrationTokenEntity token = new()
{ {
Created = DateTime.Now, Created = DateTime.Now,
Token = CryptoHelper.GenerateAuthToken(), Token = CryptoHelper.GenerateAuthToken(),

View file

@ -0,0 +1,40 @@
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
using LBPUnion.ProjectLighthouse.Types.Misc;
using LBPUnion.ProjectLighthouse.Types.Users;
namespace LBPUnion.ProjectLighthouse.Servers.API.Responses;
public struct ApiSlot
{
public int SlotId { get; set; }
public string Name { get; set; }
public string IconHash { get; set; }
public bool TeamPick { get; set; }
public bool IsAdventure { get; set; }
public Location Location { get; set; }
public GameVersion GameVersion { get; set; }
public long FirstUploaded { get; set; }
public long LastUpdated { get; set; }
public int Plays { get; set; }
public int PlaysUnique { get; set; }
public int PlaysComplete { get; set; }
public bool CommentsEnabled { get; set; }
public static ApiSlot CreateFromEntity(SlotEntity slot) =>
new()
{
SlotId = slot.SlotId,
Name = slot.Name,
IconHash = slot.IconHash,
TeamPick = slot.TeamPick,
IsAdventure = slot.IsAdventurePlanet,
Location = slot.Location,
GameVersion = slot.GameVersion,
FirstUploaded = slot.FirstUploaded,
LastUpdated = slot.LastUpdated,
Plays = slot.Plays,
PlaysUnique = slot.PlaysUnique,
PlaysComplete = slot.PlaysComplete,
CommentsEnabled = slot.CommentsEnabled,
};
}

View file

@ -0,0 +1,42 @@
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Misc;
using LBPUnion.ProjectLighthouse.Types.Users;
namespace LBPUnion.ProjectLighthouse.Servers.API.Responses;
public struct ApiUser
{
public int UserId { get; set; }
public string Username { get; set; }
public bool EmailAddressVerified { get; set; }
public string IconHash { get; set; }
public string Biography { get; set; }
public Location Location { get; set; }
public string YayHash { get; set; }
public string MehHash { get; set; }
public string BooHash { get; set; }
public long LastLogin { get; set; }
public long LastLogout { get; set; }
public PrivacyType LevelVisibility { get; set; }
public PrivacyType ProfileVisibility { get; set; }
public bool CommentsEnabled { get; set; }
public static ApiUser CreateFromEntity(UserEntity entity) =>
new()
{
UserId = entity.UserId,
Username = entity.Username,
EmailAddressVerified = entity.EmailAddressVerified,
IconHash = entity.IconHash,
Biography = entity.Biography,
Location = entity.Location,
YayHash = entity.YayHash,
MehHash = entity.MehHash,
BooHash = entity.BooHash,
LastLogin = entity.LastLogin,
LastLogout = entity.LastLogin,
LevelVisibility = entity.LevelVisibility,
ProfileVisibility = entity.ProfileVisibility,
CommentsEnabled = entity.CommentsEnabled,
};
}

View file

@ -1,31 +0,0 @@
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
using LBPUnion.ProjectLighthouse.Types.Users;
namespace LBPUnion.ProjectLighthouse.Servers.API.Responses;
public struct MinimalSlot
{
public int SlotId { get; set; }
public string Name { get; set; }
public string IconHash { get; set; }
public bool TeamPick { get; set; }
public bool IsAdventure { get; set; }
public GameVersion GameVersion { get; set; }
#if DEBUG
public long FirstUploaded { get; set; }
#endif
public static MinimalSlot FromSlot(Slot slot)
=> new()
{
SlotId = slot.SlotId,
Name = slot.Name,
IconHash = slot.IconHash,
TeamPick = slot.TeamPick,
IsAdventure = slot.IsAdventurePlanet,
GameVersion = slot.GameVersion,
#if DEBUG
FirstUploaded = slot.FirstUploaded,
#endif
};
}

View file

@ -5,7 +5,7 @@ using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Extensions; using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Users; 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.Serialization;
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;
@ -41,7 +41,7 @@ public class ClientConfigurationController : ControllerBase
[HttpGet("t_conf")] [HttpGet("t_conf")]
[Produces("text/xml")] [Produces("text/xml")]
public IActionResult Conf() => this.Ok("<t_enable>false</t_enable>"); public IActionResult Conf() => this.Ok(new TelemetryConfigResponse());
[HttpGet("ChallengeConfig.xml")] [HttpGet("ChallengeConfig.xml")]
[Produces("text/xml")] [Produces("text/xml")]
@ -54,8 +54,8 @@ public class ClientConfigurationController : ControllerBase
[Produces("text/xml")] [Produces("text/xml")]
public async Task<IActionResult> GetPrivacySettings() public async Task<IActionResult> GetPrivacySettings()
{ {
User? user = await this.database.UserFromGameToken(this.GetToken()); UserEntity? user = await this.database.UserFromGameToken(this.GetToken());
if (user == null) return this.StatusCode(403, ""); if (user == null) return this.Forbid();
PrivacySettings ps = new() PrivacySettings ps = new()
{ {
@ -63,15 +63,15 @@ public class ClientConfigurationController : ControllerBase
ProfileVisibility = user.ProfileVisibility.ToSerializedString(), ProfileVisibility = user.ProfileVisibility.ToSerializedString(),
}; };
return this.Ok(ps.Serialize()); return this.Ok(ps);
} }
[HttpPost("privacySettings")] [HttpPost("privacySettings")]
[Produces("text/xml")] [Produces("text/xml")]
public async Task<IActionResult> SetPrivacySetting() public async Task<IActionResult> SetPrivacySetting()
{ {
User? user = await this.database.UserFromGameToken(this.GetToken()); UserEntity? user = await this.database.UserFromGameToken(this.GetToken());
if (user == null) return this.StatusCode(403, ""); if (user == null) return this.Forbid();
PrivacySettings? settings = await this.DeserializeBody<PrivacySettings>(); PrivacySettings? settings = await this.DeserializeBody<PrivacySettings>();
if (settings == null) return this.BadRequest(); if (settings == null) return this.BadRequest();
@ -100,6 +100,6 @@ public class ClientConfigurationController : ControllerBase
ProfileVisibility = user.ProfileVisibility.ToSerializedString(), ProfileVisibility = user.ProfileVisibility.ToSerializedString(),
}; };
return this.Ok(ps.Serialize()); return this.Ok(ps);
} }
} }

View file

@ -2,10 +2,10 @@
using LBPUnion.ProjectLighthouse.Database; 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.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.Serialization;
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;
@ -29,7 +29,7 @@ public class CommentController : ControllerBase
[HttpPost("rateComment/{slotType}/{slotId:int}")] [HttpPost("rateComment/{slotType}/{slotId:int}")]
public async Task<IActionResult> RateComment([FromQuery] int commentId, [FromQuery] int rating, string? username, string? slotType, int slotId) public async Task<IActionResult> RateComment([FromQuery] int commentId, [FromQuery] int rating, string? username, string? slotType, int slotId)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
// Return bad request if both are true or both are false // Return bad request if both are true or both are false
if ((slotId == 0 || SlotHelper.IsTypeInvalid(slotType)) == (username == null)) return this.BadRequest(); if ((slotId == 0 || SlotHelper.IsTypeInvalid(slotType)) == (username == null)) return this.BadRequest();
@ -44,7 +44,7 @@ public class CommentController : ControllerBase
[HttpGet("userComments/{username}")] [HttpGet("userComments/{username}")]
public async Task<IActionResult> GetComments([FromQuery] int pageStart, [FromQuery] int pageSize, string? username, string? slotType, int slotId) public async Task<IActionResult> GetComments([FromQuery] int pageStart, [FromQuery] int pageSize, string? username, string? slotType, int slotId)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
if (pageSize <= 0 || pageStart < 0) return this.BadRequest(); if (pageSize <= 0 || pageStart < 0) return this.BadRequest();
@ -77,45 +77,37 @@ public class CommentController : ControllerBase
where blockedProfile.UserId == token.UserId where blockedProfile.UserId == token.UserId
select blockedProfile.BlockedUserId).ToListAsync(); select blockedProfile.BlockedUserId).ToListAsync();
List<Comment> comments = await this.database.Comments.Where(p => p.TargetId == targetId && p.Type == type) List<GameComment> comments = await this.database.Comments.Where(p => p.TargetId == targetId && p.Type == type)
.OrderByDescending(p => p.Timestamp) .OrderByDescending(p => p.Timestamp)
.Where(p => !blockedUsers.Contains(p.PosterUserId)) .Where(p => !blockedUsers.Contains(p.PosterUserId))
.Include(c => c.Poster) .Include(c => c.Poster)
.Where(p => p.Poster.PermissionLevel != PermissionLevel.Banned) .Where(p => p.Poster.PermissionLevel != PermissionLevel.Banned)
.Skip(Math.Max(0, pageStart - 1)) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30)) .Take(Math.Min(pageSize, 30))
.Select(c => GameComment.CreateFromEntity(c, token.UserId))
.ToListAsync(); .ToListAsync();
string outputXml = comments.Aggregate return this.Ok(new CommentListResponse(comments));
(string.Empty, (current, comment) => current + comment.Serialize(this.getReaction(token.UserId, comment.CommentId).Result));
return this.Ok(LbpSerializer.StringElement("comments", outputXml));
}
private async Task<int> getReaction(int userId, int commentId)
{
return await this.database.Reactions.Where(r => r.UserId == userId)
.Where(r => r.TargetId == commentId)
.Select(r => r.Rating)
.FirstOrDefaultAsync();
} }
[HttpPost("postUserComment/{username}")] [HttpPost("postUserComment/{username}")]
[HttpPost("postComment/{slotType}/{slotId:int}")] [HttpPost("postComment/{slotType}/{slotId:int}")]
public async Task<IActionResult> PostComment(string? username, string? slotType, int slotId) public async Task<IActionResult> PostComment(string? username, string? slotType, int slotId)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
Comment? comment = await this.DeserializeBody<Comment>(); GameComment? comment = await this.DeserializeBody<GameComment>();
if (comment == null) return this.BadRequest(); if (comment == null) return this.BadRequest();
if ((slotId == 0 || SlotHelper.IsTypeInvalid(slotType)) == (username == null)) return this.BadRequest(); if ((slotId == 0 || SlotHelper.IsTypeInvalid(slotType)) == (username == null)) return this.BadRequest();
if (slotType == "developer") slotId = await SlotHelper.GetPlaceholderSlotId(this.database, slotId, SlotType.Developer);
CommentType type = username == null ? CommentType.Level : CommentType.Profile; CommentType type = username == null ? CommentType.Level : CommentType.Profile;
int targetId; int targetId;
if (type == CommentType.Level) if (type == CommentType.Level)
{ {
slotId = await SlotHelper.GetPlaceholderSlotId(this.database, slotId, SlotType.Developer);
targetId = await this.database.Slots.Where(s => s.SlotId == slotId) targetId = await this.database.Slots.Where(s => s.SlotId == slotId)
.Where(s => s.CommentsEnabled && !s.Hidden) .Where(s => s.CommentsEnabled && !s.Hidden)
.Select(s => s.SlotId) .Select(s => s.SlotId)
@ -138,11 +130,11 @@ public class CommentController : ControllerBase
[HttpPost("deleteComment/{slotType}/{slotId:int}")] [HttpPost("deleteComment/{slotType}/{slotId:int}")]
public async Task<IActionResult> DeleteComment([FromQuery] int commentId, string? username, string? slotType, int slotId) public async Task<IActionResult> DeleteComment([FromQuery] int commentId, string? username, string? slotType, int slotId)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
if ((slotId == 0 || SlotHelper.IsTypeInvalid(slotType)) == (username == null)) return this.BadRequest(); if ((slotId == 0 || SlotHelper.IsTypeInvalid(slotType)) == (username == null)) return this.BadRequest();
Comment? comment = await this.database.Comments.FirstOrDefaultAsync(c => c.CommentId == commentId); CommentEntity? comment = await this.database.Comments.FirstOrDefaultAsync(c => c.CommentId == commentId);
if (comment == null) return this.NotFound(); if (comment == null) return this.NotFound();
if (comment.Deleted) return this.Ok(); if (comment.Deleted) return this.Ok();
@ -169,7 +161,7 @@ public class CommentController : ControllerBase
canDelete = comment.PosterUserId == token.UserId || slotCreator == token.UserId; canDelete = comment.PosterUserId == token.UserId || slotCreator == token.UserId;
} }
if (!canDelete) return this.StatusCode(403, ""); if (!canDelete) return this.Forbid();
comment.Deleted = true; comment.Deleted = true;
comment.DeletedBy = await this.database.UsernameFromGameToken(token); comment.DeletedBy = await this.database.UsernameFromGameToken(token);

View file

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Authorization; using LBPUnion.ProjectLighthouse.Types.Serialization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
@ -10,5 +11,5 @@ namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
public class DeveloperController : Controller public class DeveloperController : Controller
{ {
[HttpGet("developer_videos")] [HttpGet("developer_videos")]
public IActionResult DeveloperVideos() => this.Ok("<videos></videos>"); public IActionResult DeveloperVideos() => this.Ok(new GameDeveloperVideos());
} }

View file

@ -2,11 +2,11 @@
using LBPUnion.ProjectLighthouse.Database; 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.Servers.GameServer.Types.Users; 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;
using LBPUnion.ProjectLighthouse.Types.Serialization;
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;
@ -29,17 +29,17 @@ public class FriendsController : ControllerBase
[HttpPost("npdata")] [HttpPost("npdata")]
public async Task<IActionResult> NPData() public async Task<IActionResult> NPData()
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
NPData? npData = await this.DeserializeBody<NPData>(); NPData? npData = await this.DeserializeBody<NPData>();
if (npData == null) return this.BadRequest(); if (npData == null) return this.BadRequest();
SanitizationHelper.SanitizeStringsInClass(npData); SanitizationHelper.SanitizeStringsInClass(npData);
List<User> friends = new(); List<UserEntity> friends = new();
foreach (string friendName in npData.Friends ?? new List<string>()) foreach (string friendName in npData.Friends ?? new List<string>())
{ {
User? friend = await this.database.Users.FirstOrDefaultAsync(u => u.Username == friendName); UserEntity? friend = await this.database.Users.FirstOrDefaultAsync(u => u.Username == friendName);
if (friend == null) continue; if (friend == null) continue;
friends.Add(friend); friends.Add(friend);
@ -48,7 +48,7 @@ public class FriendsController : ControllerBase
List<int> blockedUsers = new(); List<int> blockedUsers = new();
foreach (string blockedUserName in npData.BlockedUsers ?? new List<string>()) foreach (string blockedUserName in npData.BlockedUsers ?? new List<string>())
{ {
User? blockedUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == blockedUserName); UserEntity? blockedUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == blockedUserName);
if (blockedUser == null) continue; if (blockedUser == null) continue;
blockedUsers.Add(blockedUser.UserId); blockedUsers.Add(blockedUser.UserId);
@ -61,30 +61,35 @@ public class FriendsController : ControllerBase
UserFriendStore.UpdateFriendData(friendStore); UserFriendStore.UpdateFriendData(friendStore);
string friendsSerialized = friends.Aggregate(string.Empty, (current, user1) => current + LbpSerializer.StringElement("npHandle", user1.Username)); List<MinimalUserProfile> minimalFriends =
friends.Select(u => new MinimalUserProfile
{
UserHandle = new NpHandle(u.Username, ""),
}).ToList();
return this.Ok(LbpSerializer.StringElement("npdata", LbpSerializer.StringElement("friends", friendsSerialized))); return this.Ok(new FriendResponse(minimalFriends));
} }
[HttpGet("myFriends")] [HttpGet("myFriends")]
public async Task<IActionResult> MyFriends() public async Task<IActionResult> MyFriends()
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
UserFriendData? friendStore = UserFriendStore.GetUserFriendData(token.UserId); UserFriendData? friendStore = UserFriendStore.GetUserFriendData(token.UserId);
if (friendStore == null) GenericUserResponse<GameUser> response = new("myFriends", new List<GameUser>());
return this.Ok(LbpSerializer.BlankElement("myFriends"));
if (friendStore == null)
return this.Ok(response);
string friends = "";
foreach (int friendId in friendStore.FriendIds) foreach (int friendId in friendStore.FriendIds)
{ {
User? friend = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == friendId); UserEntity? friend = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == friendId);
if (friend == null) continue; if (friend == null) continue;
friends += friend.Serialize(token.GameVersion); response.Users.Add(GameUser.CreateFromEntity(friend, token.GameVersion));
} }
return this.Ok(LbpSerializer.StringElement("myFriends", friends)); return this.Ok(response);
} }
} }

View file

@ -64,12 +64,12 @@ public class LoginController : ControllerBase
if (username == null) if (username == null)
{ {
Logger.Warn("Unable to determine username, rejecting login", LogArea.Login); Logger.Warn("Unable to determine username, rejecting login", LogArea.Login);
return this.StatusCode(403, ""); return this.Forbid();
} }
await this.database.RemoveExpiredTokens(); await this.database.RemoveExpiredTokens();
User? user; UserEntity? user;
switch (npTicket.Platform) switch (npTicket.Platform)
{ {
@ -91,7 +91,7 @@ public class LoginController : ControllerBase
if (user == null) if (user == null)
{ {
// Check if there is an account with that username already // Check if there is an account with that username already
User? targetUsername = await this.database.Users.FirstOrDefaultAsync(u => u.Username == npTicket.Username); UserEntity? targetUsername = await this.database.Users.FirstOrDefaultAsync(u => u.Username == npTicket.Username);
if (targetUsername != null) if (targetUsername != null)
{ {
ulong targetPlatform = npTicket.Platform == Platform.RPCS3 ulong targetPlatform = npTicket.Platform == Platform.RPCS3
@ -102,7 +102,7 @@ public class LoginController : ControllerBase
if (targetPlatform != 0) if (targetPlatform != 0)
{ {
Logger.Warn($"New user tried to login but their name is already taken, username={username}", LogArea.Login); Logger.Warn($"New user tried to login but their name is already taken, username={username}", LogArea.Login);
return this.StatusCode(403, ""); return this.Forbid();
} }
// if there is already a pending link request don't create another // if there is already a pending link request don't create another
@ -111,9 +111,9 @@ public class LoginController : ControllerBase
p.PlatformId == npTicket.UserId && p.PlatformId == npTicket.UserId &&
p.UserId == targetUsername.UserId); p.UserId == targetUsername.UserId);
if (linkAttemptExists) return this.StatusCode(403, ""); if (linkAttemptExists) return this.Forbid();
PlatformLinkAttempt linkAttempt = new() PlatformLinkAttemptEntity linkAttempt = new()
{ {
Platform = npTicket.Platform, Platform = npTicket.Platform,
UserId = targetUsername.UserId, UserId = targetUsername.UserId,
@ -124,13 +124,13 @@ public class LoginController : ControllerBase
this.database.PlatformLinkAttempts.Add(linkAttempt); this.database.PlatformLinkAttempts.Add(linkAttempt);
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
Logger.Success($"User '{npTicket.Username}' tried to login but platform isn't linked, platform={npTicket.Platform}", LogArea.Login); Logger.Success($"User '{npTicket.Username}' tried to login but platform isn't linked, platform={npTicket.Platform}", LogArea.Login);
return this.StatusCode(403, ""); return this.Forbid();
} }
if (!ServerConfiguration.Instance.Authentication.AutomaticAccountCreation) if (!ServerConfiguration.Instance.Authentication.AutomaticAccountCreation)
{ {
Logger.Warn($"Unknown user tried to connect username={username}", LogArea.Login); Logger.Warn($"Unknown user tried to connect username={username}", LogArea.Login);
return this.StatusCode(403, ""); return this.Forbid();
} }
// create account for user if they don't exist // create account for user if they don't exist
user = await this.database.CreateUser(username, "$"); user = await this.database.CreateUser(username, "$");
@ -162,7 +162,7 @@ public class LoginController : ControllerBase
{ {
Logger.Warn($"{npTicket.Platform} user changed their name to a name that is already taken," + Logger.Warn($"{npTicket.Platform} user changed their name to a name that is already taken," +
$" oldName='{user.Username}', newName='{npTicket.Username}'", LogArea.Login); $" oldName='{user.Username}', newName='{npTicket.Username}'", LogArea.Login);
return this.StatusCode(403, ""); return this.Forbid();
} }
Logger.Info($"User's username has changed, old='{user.Username}', new='{npTicket.Username}', platform={npTicket.Platform}", LogArea.Login); Logger.Info($"User's username has changed, old='{user.Username}', new='{npTicket.Username}', platform={npTicket.Platform}", LogArea.Login);
user.Username = username; user.Username = username;
@ -176,26 +176,26 @@ public class LoginController : ControllerBase
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
} }
GameToken? token = await this.database.GameTokens.Include(t => t.User) GameTokenEntity? token = await this.database.GameTokens.Include(t => t.User)
.FirstOrDefaultAsync(t => t.UserLocation == ipAddress && t.User.Username == npTicket.Username && t.TicketHash == npTicket.TicketHash); .FirstOrDefaultAsync(t => t.UserLocation == ipAddress && t.User.Username == npTicket.Username && t.TicketHash == npTicket.TicketHash);
if (token != null) if (token != null)
{ {
Logger.Warn($"Rejecting duplicate ticket from {username}", LogArea.Login); Logger.Warn($"Rejecting duplicate ticket from {username}", LogArea.Login);
return this.StatusCode(403, ""); return this.Forbid();
} }
token = await this.database.AuthenticateUser(user, npTicket, ipAddress); token = await this.database.AuthenticateUser(user, npTicket, ipAddress);
if (token == null) if (token == null)
{ {
Logger.Warn($"Unable to find/generate a token for username {npTicket.Username}", LogArea.Login); Logger.Warn($"Unable to find/generate a token for username {npTicket.Username}", LogArea.Login);
return this.StatusCode(403, ""); return this.Forbid();
} }
if (user.IsBanned) if (user.IsBanned)
{ {
Logger.Error($"User {npTicket.Username} tried to login but is banned", LogArea.Login); Logger.Error($"User {npTicket.Username} tried to login but is banned", LogArea.Login);
return this.StatusCode(403, ""); return this.Forbid();
} }
Logger.Success($"Successfully logged in user {user.Username} as {token.GameVersion} client", LogArea.Login); Logger.Success($"Successfully logged in user {user.Username} as {token.GameVersion} client", LogArea.Login);
@ -214,7 +214,7 @@ public class LoginController : ControllerBase
AuthTicket = "MM_AUTH=" + token.UserToken, AuthTicket = "MM_AUTH=" + token.UserToken,
ServerBrand = VersionHelper.EnvVer, ServerBrand = VersionHelper.EnvVer,
TitleStorageUrl = ServerConfiguration.Instance.GameApiExternalUrl, TitleStorageUrl = ServerConfiguration.Instance.GameApiExternalUrl,
}.Serialize() }
); );
} }
} }

View file

@ -25,10 +25,10 @@ public class LogoutController : ControllerBase
[HttpPost] [HttpPost]
public async Task<IActionResult> OnPost() public async Task<IActionResult> OnPost()
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
User? user = await this.database.UserFromGameToken(token); UserEntity? user = await this.database.UserFromGameToken(token);
if (user == null) return this.StatusCode(403, ""); if (user == null) return this.Forbid();
user.LastLogout = TimeHelper.TimestampMillis; user.LastLogout = TimeHelper.TimestampMillis;

View file

@ -28,18 +28,18 @@ public class EnterLevelController : ControllerBase
[HttpPost("play/{slotType}/{slotId:int}")] [HttpPost("play/{slotType}/{slotId:int}")]
public async Task<IActionResult> PlayLevel(string slotType, int slotId) public async Task<IActionResult> PlayLevel(string slotType, int slotId)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest(); if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest();
// don't count plays for developer slots // don't count plays for developer slots
if (slotType == "developer") return this.Ok(); if (slotType == "developer") return this.Ok();
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slotId); SlotEntity? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slotId);
if (slot == null) return this.StatusCode(403, ""); if (slot == null) return this.BadRequest();
IQueryable<VisitedLevel> visited = this.database.VisitedLevels.Where(s => s.SlotId == slotId && s.UserId == token.UserId); IQueryable<VisitedLevelEntity> visited = this.database.VisitedLevels.Where(s => s.SlotId == slotId && s.UserId == token.UserId);
VisitedLevel? v; VisitedLevelEntity? v;
if (!visited.Any()) if (!visited.Any())
{ {
switch (token.GameVersion) switch (token.GameVersion)
@ -57,7 +57,7 @@ public class EnterLevelController : ControllerBase
default: return this.BadRequest(); default: return this.BadRequest();
} }
v = new VisitedLevel v = new VisitedLevelEntity
{ {
SlotId = slotId, SlotId = slotId,
UserId = token.UserId, UserId = token.UserId,
@ -98,22 +98,22 @@ public class EnterLevelController : ControllerBase
[HttpPost("enterLevel/{slotType}/{slotId:int}")] [HttpPost("enterLevel/{slotType}/{slotId:int}")]
public async Task<IActionResult> EnterLevel(string slotType, int slotId) public async Task<IActionResult> EnterLevel(string slotType, int slotId)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest(); if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest();
if (slotType == "developer") return this.Ok(); if (slotType == "developer") return this.Ok();
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slotId); SlotEntity? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slotId);
if (slot == null) return this.NotFound(); if (slot == null) return this.NotFound();
IQueryable<VisitedLevel> visited = this.database.VisitedLevels.Where(s => s.SlotId == slotId && s.UserId == token.UserId); IQueryable<VisitedLevelEntity> visited = this.database.VisitedLevels.Where(s => s.SlotId == slotId && s.UserId == token.UserId);
VisitedLevel? v; VisitedLevelEntity? v;
if (!visited.Any()) if (!visited.Any())
{ {
slot.PlaysLBP1Unique++; slot.PlaysLBP1Unique++;
v = new VisitedLevel v = new VisitedLevelEntity
{ {
SlotId = slotId, SlotId = slotId,
UserId = token.UserId, UserId = token.UserId,

View file

@ -37,10 +37,10 @@ public class MatchController : ControllerBase
[Produces("text/plain")] [Produces("text/plain")]
public async Task<IActionResult> Match() public async Task<IActionResult> Match()
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
User? user = await this.database.UserFromGameToken(token); UserEntity? user = await this.database.UserFromGameToken(token);
if (user == null) return this.StatusCode(403, ""); if (user == null) return this.Forbid();
#region Parse match data #region Parse match data
@ -113,7 +113,7 @@ public class MatchController : ControllerBase
List<int> users = new(); List<int> users = new();
foreach (string playerUsername in createRoom.Players) foreach (string playerUsername in createRoom.Players)
{ {
User? player = await this.database.Users.FirstOrDefaultAsync(u => u.Username == playerUsername); UserEntity? player = await this.database.Users.FirstOrDefaultAsync(u => u.Username == playerUsername);
// ReSharper disable once ConditionIsAlwaysTrueOrFalse // ReSharper disable once ConditionIsAlwaysTrueOrFalse
if (player != null) users.Add(player.UserId); if (player != null) users.Add(player.UserId);
else return this.BadRequest(); else return this.BadRequest();
@ -129,10 +129,10 @@ public class MatchController : ControllerBase
if (room != null) if (room != null)
{ {
List<User> users = new(); List<UserEntity> users = new();
foreach (string playerUsername in updatePlayersInRoom.Players) foreach (string playerUsername in updatePlayersInRoom.Players)
{ {
User? player = await this.database.Users.FirstOrDefaultAsync(u => u.Username == playerUsername); UserEntity? player = await this.database.Users.FirstOrDefaultAsync(u => u.Username == playerUsername);
// ReSharper disable once ConditionIsAlwaysTrueOrFalse // ReSharper disable once ConditionIsAlwaysTrueOrFalse
if (player != null) users.Add(player); if (player != null) users.Add(player);
else return this.BadRequest(); else return this.BadRequest();

View file

@ -46,7 +46,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.";
[HttpGet("announce")] [HttpGet("announce")]
public async Task<IActionResult> Announce() public async Task<IActionResult> Announce()
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
string username = await this.database.UsernameFromGameToken(token); string username = await this.database.UsernameFromGameToken(token);
@ -81,7 +81,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.";
[HttpPost("filter")] [HttpPost("filter")]
public async Task<IActionResult> Filter() public async Task<IActionResult> Filter()
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
string message = await this.ReadBodyAsync(); string message = await this.ReadBodyAsync();
@ -92,7 +92,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.";
if (await this.database.Users.AnyAsync(u => u.EmailAddress == email)) return this.Ok(); if (await this.database.Users.AnyAsync(u => u.EmailAddress == email)) return this.Ok();
User? user = await this.database.UserFromGameToken(token); UserEntity? user = await this.database.UserFromGameToken(token);
if (user == null || user.EmailAddressVerified) return this.Ok(); if (user == null || user.EmailAddressVerified) return this.Ok();
user.EmailAddress = email; user.EmailAddress = email;

View file

@ -8,6 +8,7 @@ using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Types.Entities.Moderation; using LBPUnion.ProjectLighthouse.Types.Entities.Moderation;
using LBPUnion.ProjectLighthouse.Types.Entities.Token; using LBPUnion.ProjectLighthouse.Types.Entities.Token;
using LBPUnion.ProjectLighthouse.Types.Moderation.Reports; using LBPUnion.ProjectLighthouse.Types.Moderation.Reports;
using LBPUnion.ProjectLighthouse.Types.Serialization;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -29,11 +30,11 @@ public class ReportController : ControllerBase
[HttpPost("grief")] [HttpPost("grief")]
public async Task<IActionResult> Report() public async Task<IActionResult> Report()
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
string username = await this.database.UsernameFromGameToken(token); string username = await this.database.UsernameFromGameToken(token);
GriefReport? report = await this.DeserializeBody<GriefReport>(); GameGriefReport? report = await this.DeserializeBody<GameGriefReport>();
if (report == null) return this.BadRequest(); if (report == null) return this.BadRequest();
SanitizationHelper.SanitizeStringsInClass(report); SanitizationHelper.SanitizeStringsInClass(report);
@ -46,18 +47,20 @@ public class ReportController : ControllerBase
if (report.XmlPlayers.Any(p => !this.database.IsUsernameValid(p.Name))) return this.BadRequest(); if (report.XmlPlayers.Any(p => !this.database.IsUsernameValid(p.Name))) return this.BadRequest();
report.Bounds = JsonSerializer.Serialize(report.XmlBounds.Rect, typeof(Rectangle)); GriefReportEntity reportEntity = GameGriefReport.ConvertToEntity(report);
report.Players = JsonSerializer.Serialize(report.XmlPlayers, typeof(ReportPlayer[]));
report.Timestamp = TimeHelper.TimestampMillis;
report.ReportingPlayerId = token.UserId;
this.database.Reports.Add(report); reportEntity.Bounds = JsonSerializer.Serialize(report.XmlBounds.Rect, typeof(Rectangle));
reportEntity.Players = JsonSerializer.Serialize(report.XmlPlayers, typeof(ReportPlayer[]));
reportEntity.Timestamp = TimeHelper.TimestampMillis;
reportEntity.ReportingPlayerId = token.UserId;
this.database.Reports.Add(reportEntity);
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
await WebhookHelper.SendWebhook( await WebhookHelper.SendWebhook(
title: "New grief report", title: "New grief report",
description: $"Submitted by {username}\n" + description: $"Submitted by {username}\n" +
$"To view it, click [here]({ServerConfiguration.Instance.ExternalUrl}/moderation/report/{report.ReportId}).", $"To view it, click [here]({ServerConfiguration.Instance.ExternalUrl}/moderation/report/{reportEntity.ReportId}).",
dest: WebhookHelper.WebhookDestination.Moderation dest: WebhookHelper.WebhookDestination.Moderation
); );

View file

@ -6,12 +6,12 @@ using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Files; using LBPUnion.ProjectLighthouse.Files;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Serialization;
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.Logging; using LBPUnion.ProjectLighthouse.Types.Logging;
using LBPUnion.ProjectLighthouse.Types.Serialization;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -34,17 +34,17 @@ public class PhotosController : ControllerBase
[HttpPost("uploadPhoto")] [HttpPost("uploadPhoto")]
public async Task<IActionResult> UploadPhoto() public async Task<IActionResult> UploadPhoto()
{ {
User? user = await this.database.UserFromGameToken(this.GetToken()); UserEntity? user = await this.database.UserFromGameToken(this.GetToken());
if (user == null) return this.StatusCode(403, ""); 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(); if (photo == null) return this.BadRequest();
SanitizationHelper.SanitizeStringsInClass(photo); 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.LargeHash == photo.LargeHash) return this.Ok(); // photo already uplaoded
if (p.MediumHash == photo.MediumHash) return this.Ok(); if (p.MediumHash == photo.MediumHash) return this.Ok();
@ -52,20 +52,28 @@ public class PhotosController : ControllerBase
if (p.PlanHash == photo.PlanHash) return this.Ok(); if (p.PlanHash == photo.PlanHash) return this.Ok();
} }
photo.CreatorId = user.UserId; PhotoEntity photoEntity = new()
photo.Creator = user; {
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; bool validLevel = false;
PhotoSlot photoSlot = photo.XmlLevelInfo; PhotoSlot photoSlot = photo.LevelInfo;
if (photoSlot.SlotType is SlotType.Pod or SlotType.Local) photoSlot.SlotId = 0; if (photoSlot.SlotType is SlotType.Pod or SlotType.Local) photoSlot.SlotId = 0;
switch (photoSlot.SlotType) switch (photoSlot.SlotType)
{ {
case SlotType.User: case SlotType.User:
{ {
// We'll grab the slot by the RootLevel and see what happens from here. // 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 (slot == null) break;
if (!string.IsNullOrEmpty(slot.RootLevel)) validLevel = true; if (!string.IsNullOrEmpty(slot.RootLevel)) validLevel = true;
@ -76,7 +84,7 @@ public class PhotosController : ControllerBase
case SlotType.Local: case SlotType.Local:
case SlotType.Developer: 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) if (slot != null)
photoSlot.SlotId = slot.SlotId; photoSlot.SlotId = slot.SlotId;
else else
@ -92,23 +100,23 @@ public class PhotosController : ControllerBase
break; 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; 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 // Save to get photo ID for the PhotoSubject foreign keys
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
if (photo.XmlSubjects != null) if (photo.Subjects != null)
{ {
// Check for duplicate photo subjects // Check for duplicate photo subjects
List<string> subjectUserIds = new(4); 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)) if (subjectUserIds.Contains(subject.Username) && !string.IsNullOrEmpty(subject.Username))
return this.BadRequest(); return this.BadRequest();
@ -116,17 +124,23 @@ public class PhotosController : ControllerBase
subjectUserIds.Add(subject.Username); 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); 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); if (slotType == "developer") id = await SlotHelper.GetPlaceholderSlotId(this.database, id, SlotType.Developer);
List<Photo> photos = await this.database.Photos.Include(p => p.Creator) List<GamePhoto> photos = await this.database.Photos.Include(p => p.PhotoSubjects)
.Include(p => p.PhotoSubjects)
.ThenInclude(ps => ps.User)
.Where(p => p.SlotId == id) .Where(p => p.SlotId == id)
.OrderByDescending(s => s.Timestamp) .OrderByDescending(s => s.Timestamp)
.Skip(Math.Max(0, pageStart - 1)) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30)) .Take(Math.Min(pageSize, 30))
.Select(p => GamePhoto.CreateFromEntity(p))
.ToListAsync(); .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")] [HttpGet("photos/by")]
@ -175,16 +188,14 @@ public class PhotosController : ControllerBase
int targetUserId = await this.database.UserIdFromUsername(user); int targetUserId = await this.database.UserIdFromUsername(user);
if (targetUserId == 0) return this.NotFound(); if (targetUserId == 0) return this.NotFound();
List<Photo> photos = await this.database.Photos.Include(p => p.Creator) List<GamePhoto> photos = await this.database.Photos.Include(p => p.PhotoSubjects)
.Include(p => p.PhotoSubjects)
.ThenInclude(ps => ps.User)
.Where(p => p.CreatorId == targetUserId) .Where(p => p.CreatorId == targetUserId)
.OrderByDescending(s => s.Timestamp) .OrderByDescending(s => s.Timestamp)
.Skip(Math.Max(0, pageStart - 1)) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30)) .Take(Math.Min(pageSize, 30))
.Select(p => GamePhoto.CreateFromEntity(p))
.ToListAsync(); .ToListAsync();
string response = photos.Aggregate(string.Empty, (s, photo) => s + photo.Serialize()); return this.Ok(new PhotoListResponse(photos));
return this.Ok(LbpSerializer.StringElement("photos", response));
} }
[HttpGet("photos/with")] [HttpGet("photos/with")]
@ -195,32 +206,30 @@ public class PhotosController : ControllerBase
int targetUserId = await this.database.UserIdFromUsername(user); int targetUserId = await this.database.UserIdFromUsername(user);
if (targetUserId == 0) return this.NotFound(); if (targetUserId == 0) return this.NotFound();
List<Photo> photos = await this.database.Photos.Include(p => p.Creator) List<GamePhoto> photos = await this.database.Photos.Include(p => p.PhotoSubjects)
.Include(p => p.PhotoSubjects)
.ThenInclude(ps => ps.User)
.Where(p => p.PhotoSubjects.Any(ps => ps.UserId == targetUserId)) .Where(p => p.PhotoSubjects.Any(ps => ps.UserId == targetUserId))
.OrderByDescending(s => s.Timestamp) .OrderByDescending(s => s.Timestamp)
.Skip(Math.Max(0, pageStart - 1)) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30)) .Take(Math.Min(pageSize, 30))
.Select(p => GamePhoto.CreateFromEntity(p))
.ToListAsync(); .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}")] [HttpPost("deletePhoto/{id:int}")]
public async Task<IActionResult> DeletePhoto(int id) 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 (photo == null) return this.NotFound();
// If user isn't photo creator then check if they own the level // If user isn't photo creator then check if they own the level
if (photo.CreatorId != token.UserId) if (photo.CreatorId != token.UserId)
{ {
Slot? photoSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == photo.SlotId && s.Type == SlotType.User); 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.StatusCode(401, ""); if (photoSlot == null || photoSlot.CreatorId != token.UserId) return this.Unauthorized();
} }
HashSet<string> photoResources = new(){photo.LargeHash, photo.SmallHash, photo.MediumHash, photo.PlanHash,}; HashSet<string> photoResources = new(){photo.LargeHash, photo.SmallHash, photo.MediumHash, photo.PlanHash,};

View file

@ -4,7 +4,6 @@ using System.IO.Pipelines;
using LBPUnion.ProjectLighthouse.Extensions; using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Files; using LBPUnion.ProjectLighthouse.Files;
using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Misc; using LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Misc;
using LBPUnion.ProjectLighthouse.Types.Logging; using LBPUnion.ProjectLighthouse.Types.Logging;
using LBPUnion.ProjectLighthouse.Types.Resources; using LBPUnion.ProjectLighthouse.Types.Resources;
@ -22,7 +21,7 @@ public class ResourcesController : ControllerBase
{ {
[HttpPost("showModerated")] [HttpPost("showModerated")]
public IActionResult ShowModerated() => this.Ok(LbpSerializer.BlankElement("resources")); public IActionResult ShowModerated() => this.Ok(new ResourceList());
[HttpPost("filterResources")] [HttpPost("filterResources")]
[HttpPost("showNotUploaded")] [HttpPost("showNotUploaded")]
@ -31,11 +30,9 @@ public class ResourcesController : ControllerBase
ResourceList? resourceList = await this.DeserializeBody<ResourceList>(); ResourceList? resourceList = await this.DeserializeBody<ResourceList>();
if (resourceList?.Resources == null) return this.BadRequest(); if (resourceList?.Resources == null) return this.BadRequest();
string resources = resourceList.Resources.Where resourceList.Resources = resourceList.Resources.Where(r => !FileHelper.ResourceExists(r)).ToArray();
(s => !FileHelper.ResourceExists(s))
.Aggregate("", (current, hash) => current + LbpSerializer.StringElement("resource", hash));
return this.Ok(LbpSerializer.StringElement("resources", resources)); return this.Ok(resourceList);
} }
[HttpGet("r/{hash}")] [HttpGet("r/{hash}")]

View file

@ -3,13 +3,13 @@ using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Database; using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Extensions; using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Categories; using LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Categories;
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.Logging; using LBPUnion.ProjectLighthouse.Types.Logging;
using LBPUnion.ProjectLighthouse.Types.Serialization;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -32,16 +32,18 @@ public class CollectionController : ControllerBase
[HttpGet("playlists/{playlistId:int}/slots")] [HttpGet("playlists/{playlistId:int}/slots")]
public async Task<IActionResult> GetPlaylistSlots(int playlistId) public async Task<IActionResult> GetPlaylistSlots(int playlistId)
{ {
Playlist? targetPlaylist = await this.database.Playlists.FirstOrDefaultAsync(p => p.PlaylistId == playlistId); PlaylistEntity? targetPlaylist = await this.database.Playlists.FirstOrDefaultAsync(p => p.PlaylistId == playlistId);
if (targetPlaylist == null) return this.BadRequest(); if (targetPlaylist == null) return this.BadRequest();
IQueryable<Slot> slots = this.database.Slots.Include(s => s.Creator) GameTokenEntity token = this.GetToken();
.Where(s => targetPlaylist.SlotIds.Contains(s.SlotId));
List<SlotBase> slots = await this.database.Slots.Where(s => targetPlaylist.SlotIds.Contains(s.SlotId))
.Select(s => SlotBase.CreateFromEntity(s, token))
.ToListAsync();
string response = Enumerable.Aggregate(slots, string.Empty, (current, slot) => current + slot.Serialize());
int total = targetPlaylist.SlotIds.Length; int total = targetPlaylist.SlotIds.Length;
return this.Ok(LbpSerializer.TaggedStringElement("slots", response, "total", total)); return this.Ok(new GenericSlotResponse(slots, total, 0));
} }
[HttpPost("playlists/{playlistId:int}")] [HttpPost("playlists/{playlistId:int}")]
@ -50,9 +52,9 @@ public class CollectionController : ControllerBase
[HttpPost("playlists/{playlistId:int}/order_slots")] [HttpPost("playlists/{playlistId:int}/order_slots")]
public async Task<IActionResult> UpdatePlaylist(int playlistId, int slotId) public async Task<IActionResult> UpdatePlaylist(int playlistId, int slotId)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
Playlist? targetPlaylist = await this.database.Playlists.FirstOrDefaultAsync(p => p.PlaylistId == playlistId); PlaylistEntity? targetPlaylist = await this.database.Playlists.FirstOrDefaultAsync(p => p.PlaylistId == playlistId);
if (targetPlaylist == null) return this.BadRequest(); if (targetPlaylist == null) return this.BadRequest();
if (token.UserId != targetPlaylist.CreatorId) return this.BadRequest(); if (token.UserId != targetPlaylist.CreatorId) return this.BadRequest();
@ -65,7 +67,7 @@ public class CollectionController : ControllerBase
return this.Ok(this.GetUserPlaylists(token.UserId)); return this.Ok(this.GetUserPlaylists(token.UserId));
} }
Playlist? newPlaylist = await this.DeserializeBody<Playlist>("playlist", "levels"); GamePlaylist? newPlaylist = await this.DeserializeBody<GamePlaylist>("playlist", "levels");
if (newPlaylist == null) return this.BadRequest(); if (newPlaylist == null) return this.BadRequest();
@ -97,41 +99,48 @@ public class CollectionController : ControllerBase
return this.Ok(this.GetUserPlaylists(token.UserId)); return this.Ok(this.GetUserPlaylists(token.UserId));
} }
private string GetUserPlaylists(int userId) private async Task<PlaylistResponse> GetUserPlaylists(int userId)
{ {
string response = Enumerable.Aggregate( List<GamePlaylist> playlists = await this.database.Playlists.Include(p => p.Creator)
this.database.Playlists.Include(p => p.Creator).Where(p => p.CreatorId == userId), .Where(p => p.CreatorId == userId)
string.Empty, .Select(p => GamePlaylist.CreateFromEntity(p))
(current, slot) => current + slot.Serialize()); .ToListAsync();
int total = this.database.Playlists.Count(p => p.CreatorId == userId); int total = this.database.Playlists.Count(p => p.CreatorId == userId);
return LbpSerializer.TaggedStringElement("playlists", response, new Dictionary<string, object> return new PlaylistResponse
{ {
{"total", total}, Playlists = playlists,
{"hint_start", total+1}, Total = total,
}); HintStart = total+1,
};
} }
[HttpPost("playlists")] [HttpPost("playlists")]
public async Task<IActionResult> CreatePlaylist() public async Task<IActionResult> CreatePlaylist()
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
int playlistCount = await this.database.Playlists.CountAsync(p => p.CreatorId == token.UserId); int playlistCount = await this.database.Playlists.CountAsync(p => p.CreatorId == token.UserId);
if (playlistCount > ServerConfiguration.Instance.UserGeneratedContentLimits.ListsQuota) return this.BadRequest(); if (playlistCount > ServerConfiguration.Instance.UserGeneratedContentLimits.ListsQuota) return this.BadRequest();
Playlist? playlist = await this.DeserializeBody<Playlist>("playlist"); GamePlaylist? playlist = await this.DeserializeBody<GamePlaylist>("playlist");
if (playlist == null) return this.BadRequest(); if (playlist == null) return this.BadRequest();
playlist.CreatorId = token.UserId; PlaylistEntity playlistEntity = new()
{
CreatorId = token.UserId,
Description = playlist.Description,
Name = playlist.Name,
SlotIds = playlist.SlotIds,
};
this.database.Playlists.Add(playlist); this.database.Playlists.Add(playlistEntity);
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
return this.Ok(this.GetUserPlaylists(token.UserId)); return this.Ok(await this.GetUserPlaylists(token.UserId));
} }
[HttpGet("user/{username}/playlists")] [HttpGet("user/{username}/playlists")]
@ -140,101 +149,60 @@ public class CollectionController : ControllerBase
int targetUserId = await this.database.UserIdFromUsername(username); int targetUserId = await this.database.UserIdFromUsername(username);
if (targetUserId == 0) return this.BadRequest(); if (targetUserId == 0) return this.BadRequest();
return this.Ok(this.GetUserPlaylists(targetUserId)); return this.Ok(await this.GetUserPlaylists(targetUserId));
} }
[HttpGet("searches")] [HttpGet("searches")]
[HttpGet("genres")] [HttpGet("genres")]
public async Task<IActionResult> GenresAndSearches() public async Task<IActionResult> GenresAndSearches()
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
User? user = await this.database.UserFromGameToken(token); UserEntity? user = await this.database.UserFromGameToken(token);
if (user == null) return this.StatusCode(403, ""); if (user == null) return this.Forbid();
string categoriesSerialized = CategoryHelper.Categories.Aggregate List<GameCategory> categories = new();
(
string.Empty,
(current, category) =>
{
string serialized;
if (category is CategoryWithUser categoryWithUser) serialized = categoryWithUser.Serialize(this.database, user); foreach (Category category in CategoryHelper.Categories.ToList())
else serialized = category.Serialize(this.database); {
if(category is CategoryWithUser categoryWithUser) categories.Add(categoryWithUser.Serialize(this.database, user));
else categories.Add(category.Serialize(this.database));
}
return current + serialized; return this.Ok(new CategoryListResponse(categories, CategoryHelper.Categories.Count, 0, 1));
}
);
categoriesSerialized += LbpSerializer.StringElement("text_search", LbpSerializer.StringElement("url", "/slots/searchLBP3"));
return this.Ok
(
LbpSerializer.TaggedStringElement
(
"categories",
categoriesSerialized,
new Dictionary<string, object>
{
{
"hint", ""
},
{
"hint_start", 1
},
{
"total", CategoryHelper.Categories.Count
},
}
)
);
} }
[HttpGet("searches/{endpointName}")] [HttpGet("searches/{endpointName}")]
public async Task<IActionResult> GetCategorySlots(string endpointName, [FromQuery] int pageStart, [FromQuery] int pageSize) public async Task<IActionResult> GetCategorySlots(string endpointName, [FromQuery] int pageStart, [FromQuery] int pageSize)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
User? user = await this.database.UserFromGameToken(token); UserEntity? user = await this.database.UserFromGameToken(token);
if (user == null) return this.StatusCode(403, ""); if (user == null) return this.Forbid();
Category? category = CategoryHelper.Categories.FirstOrDefault(c => c.Endpoint == endpointName); Category? category = CategoryHelper.Categories.FirstOrDefault(c => c.Endpoint == endpointName);
if (category == null) return this.NotFound(); if (category == null) return this.NotFound();
Logger.Debug("Found category " + category, LogArea.Category); Logger.Debug("Found category " + category, LogArea.Category);
List<Slot> slots; List<SlotBase> slots;
int totalSlots; int totalSlots;
if (category is CategoryWithUser categoryWithUser) if (category is CategoryWithUser categoryWithUser)
{ {
slots = categoryWithUser.GetSlots(this.database, user, pageStart, pageSize).ToList(); slots = categoryWithUser.GetSlots(this.database, user, pageStart, pageSize)
.Select(s => SlotBase.CreateFromEntity(s, token))
.ToList();
totalSlots = categoryWithUser.GetTotalSlots(this.database, user); totalSlots = categoryWithUser.GetTotalSlots(this.database, user);
} }
else else
{ {
slots = category.GetSlots(this.database, pageStart, pageSize).ToList(); slots = category.GetSlots(this.database, pageStart, pageSize)
.Select(s => SlotBase.CreateFromEntity(s, token))
.ToList();
totalSlots = category.GetTotalSlots(this.database); totalSlots = category.GetTotalSlots(this.database);
} }
string slotsSerialized = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize(token.GameVersion)); return this.Ok(new GenericSlotResponse("results", slots, totalSlots, pageStart + pageSize));
return this.Ok
(
LbpSerializer.TaggedStringElement
(
"results",
slotsSerialized,
new Dictionary<string, object>
{
{
"total", totalSlots
},
{
"hint_start", pageStart + pageSize
},
}
)
);
} }
} }

View file

@ -43,9 +43,9 @@ public class LevelTagsController : ControllerBase
[HttpPost("tag/{slotType}/{id:int}")] [HttpPost("tag/{slotType}/{id:int}")]
public async Task<IActionResult> PostTag([FromForm(Name = "t")] string tagName, [FromRoute] string slotType, [FromRoute] int id) public async Task<IActionResult> PostTag([FromForm(Name = "t")] string tagName, [FromRoute] string slotType, [FromRoute] int id)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
Slot? slot = await this.database.Slots.Where(s => s.SlotId == id).FirstOrDefaultAsync(); SlotEntity? slot = await this.database.Slots.Where(s => s.SlotId == id).FirstOrDefaultAsync();
if (slot == null) return this.BadRequest(); if (slot == null) return this.BadRequest();
if (!LabelHelper.IsValidTag(tagName)) return this.BadRequest(); if (!LabelHelper.IsValidTag(tagName)) return this.BadRequest();
@ -56,7 +56,7 @@ public class LevelTagsController : ControllerBase
if (slotType != "user") return this.BadRequest(); if (slotType != "user") return this.BadRequest();
RatedLevel? rating = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.UserId == token.UserId && r.SlotId == slot.SlotId); RatedLevelEntity? rating = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.UserId == token.UserId && r.SlotId == slot.SlotId);
if (rating == null) return this.BadRequest(); if (rating == null) return this.BadRequest();
rating.TagLBP1 = tagName; rating.TagLBP1 = tagName;

View file

@ -2,12 +2,12 @@
using LBPUnion.ProjectLighthouse.Database; 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.Types.Entities.Interaction; using LBPUnion.ProjectLighthouse.Types.Entities.Interaction;
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.Serialization;
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;
@ -43,35 +43,27 @@ public class ListController : ControllerBase
[FromQuery] string? dateFilterType = null [FromQuery] string? dateFilterType = null
) )
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
GameVersion gameVersion = token.GameVersion; List<SlotBase> queuedLevels = await this.filterListByRequest(gameFilterType, dateFilterType, token.GameVersion, username, ListFilterType.Queue)
IEnumerable<Slot> queuedLevels = this.filterListByRequest(gameFilterType, dateFilterType, token.GameVersion, username, ListFilterType.Queue)
.Skip(Math.Max(0, pageStart - 1)) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30)) .Take(Math.Min(pageSize, 30))
.AsEnumerable(); .Select(s => SlotBase.CreateFromEntity(s, token)).ToListAsync();
string response = queuedLevels.Aggregate(string.Empty, (current, q) => current + q.Serialize(gameVersion)); int total = await this.database.QueuedLevels.CountAsync(q => q.UserId == token.UserId);
int start = pageStart + Math.Min(pageSize, 30);
return this.Ok return this.Ok(new GenericSlotResponse(queuedLevels, total, start));
(
LbpSerializer.TaggedStringElement("slots", response, new Dictionary<string, object>
{
{ "total", await this.database.QueuedLevels.CountAsync(q => q.UserId == token.UserId) },
{ "hint_start", pageStart + Math.Min(pageSize, 30) },
})
);
} }
[HttpPost("lolcatftw/add/user/{id:int}")] [HttpPost("lolcatftw/add/user/{id:int}")]
public async Task<IActionResult> AddQueuedLevel(int id) public async Task<IActionResult> AddQueuedLevel(int id)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); SlotEntity? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound(); if (slot == null) return this.NotFound();
await this.database.QueueLevel(token.UserId, slot); await this.database.QueueLevel(token.UserId, slot);
@ -82,9 +74,9 @@ public class ListController : ControllerBase
[HttpPost("lolcatftw/remove/user/{id:int}")] [HttpPost("lolcatftw/remove/user/{id:int}")]
public async Task<IActionResult> RemoveQueuedLevel(int id) public async Task<IActionResult> RemoveQueuedLevel(int id)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); SlotEntity? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound(); if (slot == null) return this.NotFound();
await this.database.UnqueueLevel(token.UserId, slot); await this.database.UnqueueLevel(token.UserId, slot);
@ -95,7 +87,7 @@ public class ListController : ControllerBase
[HttpPost("lolcatftw/clear")] [HttpPost("lolcatftw/clear")]
public async Task<IActionResult> ClearQueuedLevels() public async Task<IActionResult> ClearQueuedLevels()
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
this.database.QueuedLevels.RemoveRange(this.database.QueuedLevels.Where(q => q.UserId == token.UserId)); this.database.QueuedLevels.RemoveRange(this.database.QueuedLevels.Where(q => q.UserId == token.UserId));
@ -120,30 +112,23 @@ public class ListController : ControllerBase
[FromQuery] string? dateFilterType = null [FromQuery] string? dateFilterType = null
) )
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
GameVersion gameVersion = token.GameVersion; UserEntity? targetUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
if (targetUser == null) return this.Forbid();
User? targetUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username); List<SlotBase> heartedLevels = await this.filterListByRequest(gameFilterType, dateFilterType, token.GameVersion, username, ListFilterType.FavouriteSlots)
if (targetUser == null) return this.StatusCode(403, "");
IEnumerable<Slot> heartedLevels = this.filterListByRequest(gameFilterType, dateFilterType, token.GameVersion, username, ListFilterType.FavouriteSlots)
.Skip(Math.Max(0, pageStart - 1)) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30)) .Take(Math.Min(pageSize, 30))
.AsEnumerable(); .Select(s => SlotBase.CreateFromEntity(s, token))
.ToListAsync();
string response = heartedLevels.Aggregate(string.Empty, (current, q) => current + q.Serialize(gameVersion)); int total = await this.database.HeartedLevels.CountAsync(q => q.UserId == targetUser.UserId);
int start = pageStart + Math.Min(pageSize, 30);
return this.Ok return this.Ok(new GenericSlotResponse("favouriteSlots", heartedLevels, total, start));
(
LbpSerializer.TaggedStringElement("favouriteSlots", response, new Dictionary<string, object>
{
{ "total", await this.database.HeartedLevels.CountAsync(q => q.UserId == targetUser.UserId) },
{ "hint_start", pageStart + Math.Min(pageSize, 30) },
})
);
} }
private const int FirstLbp2DeveloperSlotId = 124806; // This is the first known level slot GUID in LBP2. Feel free to change it if a lower one is found. private const int FirstLbp2DeveloperSlotId = 124806; // This is the first known level slot GUID in LBP2. Feel free to change it if a lower one is found.
@ -151,13 +136,13 @@ public class ListController : ControllerBase
[HttpPost("favourite/slot/{slotType}/{id:int}")] [HttpPost("favourite/slot/{slotType}/{id:int}")]
public async Task<IActionResult> AddFavouriteSlot(string slotType, int id) public async Task<IActionResult> AddFavouriteSlot(string slotType, int id)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest(); if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest();
if (slotType == "developer") id = await SlotHelper.GetPlaceholderSlotId(this.database, id, SlotType.Developer); if (slotType == "developer") id = await SlotHelper.GetPlaceholderSlotId(this.database, id, SlotType.Developer);
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); SlotEntity? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound(); if (slot == null) return this.NotFound();
if (slotType == "developer") if (slotType == "developer")
@ -174,13 +159,13 @@ public class ListController : ControllerBase
[HttpPost("unfavourite/slot/{slotType}/{id:int}")] [HttpPost("unfavourite/slot/{slotType}/{id:int}")]
public async Task<IActionResult> RemoveFavouriteSlot(string slotType, int id) public async Task<IActionResult> RemoveFavouriteSlot(string slotType, int id)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest(); if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest();
if (slotType == "developer") id = await SlotHelper.GetPlaceholderSlotId(this.database, id, SlotType.Developer); if (slotType == "developer") id = await SlotHelper.GetPlaceholderSlotId(this.database, id, SlotType.Developer);
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); SlotEntity? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound(); if (slot == null) return this.NotFound();
if (slotType == "developer") if (slotType == "developer")
@ -204,29 +189,31 @@ public class ListController : ControllerBase
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
int targetUserId = await this.database.UserIdFromUsername(username); int targetUserId = await this.database.UserIdFromUsername(username);
if (targetUserId == 0) return this.StatusCode(403, ""); if (targetUserId == 0) return this.Forbid();
IEnumerable<Playlist> heartedPlaylists = this.database.HeartedPlaylists.Where(p => p.UserId == targetUserId) List<GamePlaylist> heartedPlaylists = await this.database.HeartedPlaylists.Where(p => p.UserId == targetUserId)
.Include(p => p.Playlist).Include(p => p.Playlist.Creator).OrderByDescending(p => p.HeartedPlaylistId).Select(p => p.Playlist); .Include(p => p.Playlist)
.Include(p => p.Playlist.Creator)
.OrderByDescending(p => p.HeartedPlaylistId)
.Select(p => p.Playlist)
.Select(p => GamePlaylist.CreateFromEntity(p))
.ToListAsync();
string response = heartedPlaylists.Aggregate(string.Empty, (current, p) => current + p.Serialize()); int total = await this.database.HeartedPlaylists.CountAsync(p => p.UserId == targetUserId);
return this.Ok return this.Ok(new GenericPlaylistResponse<GamePlaylist>("favouritePlaylists", heartedPlaylists)
( {
LbpSerializer.TaggedStringElement("favouritePlaylists", response, new Dictionary<string, object> Total = total,
{ HintStart = pageStart + Math.Min(pageSize, 30),
{ "total", this.database.HeartedPlaylists.Count(p => p.UserId == targetUserId) }, });
{ "hint_start", pageStart + Math.Min(pageSize, 30) },
})
);
} }
[HttpPost("favourite/playlist/{playlistId:int}")] [HttpPost("favourite/playlist/{playlistId:int}")]
public async Task<IActionResult> AddFavouritePlaylist(int playlistId) public async Task<IActionResult> AddFavouritePlaylist(int playlistId)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
Playlist? playlist = await this.database.Playlists.FirstOrDefaultAsync(s => s.PlaylistId == playlistId); PlaylistEntity? playlist = await this.database.Playlists.FirstOrDefaultAsync(s => s.PlaylistId == playlistId);
if (playlist == null) return this.NotFound(); if (playlist == null) return this.NotFound();
await this.database.HeartPlaylist(token.UserId, playlist); await this.database.HeartPlaylist(token.UserId, playlist);
@ -237,9 +224,9 @@ public class ListController : ControllerBase
[HttpPost("unfavourite/playlist/{playlistId:int}")] [HttpPost("unfavourite/playlist/{playlistId:int}")]
public async Task<IActionResult> RemoveFavouritePlaylist(int playlistId) public async Task<IActionResult> RemoveFavouritePlaylist(int playlistId)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
Playlist? playlist = await this.database.Playlists.FirstOrDefaultAsync(s => s.PlaylistId == playlistId); PlaylistEntity? playlist = await this.database.Playlists.FirstOrDefaultAsync(s => s.PlaylistId == playlistId);
if (playlist == null) return this.NotFound(); if (playlist == null) return this.NotFound();
await this.database.UnheartPlaylist(token.UserId, playlist); await this.database.UnheartPlaylist(token.UserId, playlist);
@ -256,40 +243,34 @@ public class ListController : ControllerBase
[HttpGet("favouriteUsers/{username}")] [HttpGet("favouriteUsers/{username}")]
public async Task<IActionResult> GetFavouriteUsers(string username, [FromQuery] int pageSize, [FromQuery] int pageStart) public async Task<IActionResult> GetFavouriteUsers(string username, [FromQuery] int pageSize, [FromQuery] int pageStart)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
User? targetUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username); UserEntity? targetUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
if (targetUser == null) return this.StatusCode(403, ""); if (targetUser == null) return this.Forbid();
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
IEnumerable<User> heartedProfiles = this.database.HeartedProfiles.Include List<GameUser> heartedProfiles = await this.database.HeartedProfiles.Include
(q => q.HeartedUser) (h => h.HeartedUser)
.OrderBy(q => q.HeartedProfileId) .OrderBy(h => h.HeartedProfileId)
.Where(q => q.UserId == targetUser.UserId) .Where(h => h.UserId == targetUser.UserId)
.Select(q => q.HeartedUser) .Select(h => h.HeartedUser)
.Skip(Math.Max(0, pageStart - 1)) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30)) .Take(Math.Min(pageSize, 30))
.AsEnumerable(); .Select(h => GameUser.CreateFromEntity(h, token.GameVersion))
.ToListAsync();
string response = heartedProfiles.Aggregate(string.Empty, (current, u) => current + u.Serialize(token.GameVersion)); int total = await this.database.HeartedProfiles.CountAsync(h => h.UserId == targetUser.UserId);
return this.Ok return this.Ok(new GenericUserResponse<GameUser>("favouriteUsers", heartedProfiles, total, pageStart + Math.Min(pageSize, 30)));
(
LbpSerializer.TaggedStringElement("favouriteUsers", response, new Dictionary<string, object>
{
{ "total", await this.database.HeartedProfiles.CountAsync(q => q.UserId == targetUser.UserId) },
{ "hint_start", pageStart + Math.Min(pageSize, 30) },
})
);
} }
[HttpPost("favourite/user/{username}")] [HttpPost("favourite/user/{username}")]
public async Task<IActionResult> AddFavouriteUser(string username) public async Task<IActionResult> AddFavouriteUser(string username)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username); UserEntity? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
if (heartedUser == null) return this.NotFound(); if (heartedUser == null) return this.NotFound();
await this.database.HeartUser(token.UserId, heartedUser); await this.database.HeartUser(token.UserId, heartedUser);
@ -300,9 +281,9 @@ public class ListController : ControllerBase
[HttpPost("unfavourite/user/{username}")] [HttpPost("unfavourite/user/{username}")]
public async Task<IActionResult> RemoveFavouriteUser(string username) public async Task<IActionResult> RemoveFavouriteUser(string username)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username); UserEntity? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
if (heartedUser == null) return this.NotFound(); if (heartedUser == null) return this.NotFound();
await this.database.UnheartUser(token.UserId, heartedUser); await this.database.UnheartUser(token.UserId, heartedUser);
@ -313,7 +294,7 @@ public class ListController : ControllerBase
#endregion #endregion
#region Filtering #region Filtering
enum ListFilterType // used to collapse code that would otherwise be two separate functions internal enum ListFilterType // used to collapse code that would otherwise be two separate functions
{ {
Queue, Queue,
FavouriteSlots, FavouriteSlots,
@ -337,7 +318,7 @@ public class ListController : ControllerBase
}; };
} }
private IQueryable<Slot> filterListByRequest(string? gameFilterType, string? dateFilterType, GameVersion version, string username, ListFilterType filterType) private IQueryable<SlotEntity> filterListByRequest(string? gameFilterType, string? dateFilterType, GameVersion version, string username, ListFilterType filterType)
{ {
if (version is GameVersion.LittleBigPlanetPSP or GameVersion.Unknown) if (version is GameVersion.LittleBigPlanetPSP or GameVersion.Unknown)
{ {
@ -358,7 +339,7 @@ public class ListController : ControllerBase
if (filterType == ListFilterType.Queue) if (filterType == ListFilterType.Queue)
{ {
IQueryable<QueuedLevel> whereQueuedLevels; IQueryable<QueuedLevelEntity> whereQueuedLevels;
// ReSharper disable once ConvertIfStatementToConditionalTernaryExpression // ReSharper disable once ConvertIfStatementToConditionalTernaryExpression
if (gameFilterType == "both") if (gameFilterType == "both")
@ -374,7 +355,7 @@ public class ListController : ControllerBase
return whereQueuedLevels.OrderByDescending(q => q.QueuedLevelId).Include(q => q.Slot.Creator).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);
} }
IQueryable<HeartedLevel> whereHeartedLevels; IQueryable<HeartedLevelEntity> whereHeartedLevels;
// ReSharper disable once ConvertIfStatementToConditionalTernaryExpression // ReSharper disable once ConvertIfStatementToConditionalTernaryExpression
if (gameFilterType == "both") if (gameFilterType == "both")

View file

@ -5,12 +5,12 @@ using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Files; using LBPUnion.ProjectLighthouse.Files;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Serialization;
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.Logging; using LBPUnion.ProjectLighthouse.Types.Logging;
using LBPUnion.ProjectLighthouse.Types.Resources; using LBPUnion.ProjectLighthouse.Types.Resources;
using LBPUnion.ProjectLighthouse.Types.Serialization;
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;
@ -37,12 +37,12 @@ public class PublishController : ControllerBase
[HttpPost("startPublish")] [HttpPost("startPublish")]
public async Task<IActionResult> StartPublish() public async Task<IActionResult> StartPublish()
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
User? user = await this.database.UserFromGameToken(token); UserEntity? user = await this.database.UserFromGameToken(token);
if (user == null) return this.StatusCode(403, ""); if (user == null) return this.Forbid();
Slot? slot = await this.DeserializeBody<Slot>(); GameUserSlot? slot = await this.DeserializeBody<GameUserSlot>();
if (slot == null) if (slot == null)
{ {
Logger.Warn("Rejecting level upload, slot is null", LogArea.Publish); Logger.Warn("Rejecting level upload, slot is null", LogArea.Publish);
@ -55,7 +55,7 @@ public class PublishController : ControllerBase
return this.BadRequest(); return this.BadRequest();
} }
if (string.IsNullOrEmpty(slot.ResourceCollection)) slot.ResourceCollection = slot.RootLevel; if (slot.Resources?.Length == 0) slot.Resources = new[]{slot.RootLevel,};
if (slot.Resources == null) if (slot.Resources == null)
{ {
@ -63,10 +63,12 @@ public class PublishController : ControllerBase
return this.BadRequest(); return this.BadRequest();
} }
int usedSlots = await this.database.Slots.CountAsync(s => s.CreatorId == token.UserId && s.GameVersion == token.GameVersion);
// Republish logic // Republish logic
if (slot.SlotId != 0) if (slot.SlotId != 0)
{ {
Slot? oldSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slot.SlotId); SlotEntity? oldSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slot.SlotId);
if (oldSlot == null) if (oldSlot == null)
{ {
Logger.Warn("Rejecting level republish, could not find old slot", LogArea.Publish); Logger.Warn("Rejecting level republish, could not find old slot", LogArea.Publish);
@ -78,18 +80,18 @@ public class PublishController : ControllerBase
return this.BadRequest(); return this.BadRequest();
} }
} }
else if (user.GetUsedSlotsForGame(token.GameVersion) > user.EntitledSlots) else if (usedSlots > user.EntitledSlots)
{ {
return this.StatusCode(403, ""); return this.Forbid();
} }
slot.ResourceCollection += "," + slot.IconHash; // tells LBP to upload icon after we process resources here HashSet<string> resources = new(slot.Resources)
{
slot.IconHash,
};
resources = resources.Where(hash => !FileHelper.ResourceExists(hash)).ToHashSet();
string resources = slot.Resources.Where return this.Ok(new SlotResourceResponse(resources.ToList()));
(hash => !FileHelper.ResourceExists(hash))
.Aggregate("", (current, hash) => current + LbpSerializer.StringElement("resource", hash));
return this.Ok(LbpSerializer.TaggedStringElement("slot", resources, "type", "user"));
} }
/// <summary> /// <summary>
@ -98,12 +100,12 @@ public class PublishController : ControllerBase
[HttpPost("publish")] [HttpPost("publish")]
public async Task<IActionResult> Publish([FromQuery] string? game) public async Task<IActionResult> Publish([FromQuery] string? game)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
User? user = await this.database.UserFromGameToken(token); UserEntity? user = await this.database.UserFromGameToken(token);
if (user == null) return this.StatusCode(403, ""); if (user == null) return this.Forbid();
Slot? slot = await this.DeserializeBody<Slot>(); GameUserSlot? slot = await this.DeserializeBody<GameUserSlot>();
if (slot == null) if (slot == null)
{ {
@ -111,6 +113,12 @@ public class PublishController : ControllerBase
return this.BadRequest(); return this.BadRequest();
} }
if (slot.Resources == null)
{
Logger.Warn("Rejecting level upload, resource list 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)
@ -168,7 +176,7 @@ public class PublishController : ControllerBase
// Republish logic // Republish logic
if (slot.SlotId != 0) if (slot.SlotId != 0)
{ {
Slot? oldSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slot.SlotId); SlotEntity? 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);
@ -190,36 +198,32 @@ public class PublishController : ControllerBase
// Delete the useless rootLevel that lbp3 just uploaded // Delete the useless rootLevel that lbp3 just uploaded
if (slotVersion == GameVersion.LittleBigPlanet3) if (slotVersion == GameVersion.LittleBigPlanet3)
FileHelper.DeleteResource(slot.RootLevel); FileHelper.DeleteResource(slot.RootLevel);
else
slot.GameVersion = oldSlot.GameVersion; {
slot.RootLevel = oldSlot.RootLevel; oldSlot.GameVersion = slot.GameVersion;
slot.ResourceCollection = oldSlot.ResourceCollection; oldSlot.RootLevel = slot.RootLevel;
oldSlot.Resources = slot.Resources;
}
} }
} }
slot.CreatorId = oldSlot.CreatorId; oldSlot.Name = slot.Name;
slot.SlotId = oldSlot.SlotId; oldSlot.Description = slot.Description;
oldSlot.Location = slot.Location;
oldSlot.IconHash = slot.IconHash;
oldSlot.BackgroundHash = slot.BackgroundHash;
oldSlot.AuthorLabels = slot.AuthorLabels;
oldSlot.Shareable = slot.IsShareable;
oldSlot.Resources = slot.Resources;
oldSlot.InitiallyLocked = slot.InitiallyLocked;
oldSlot.Lbp1Only = slot.IsLbp1Only;
oldSlot.IsAdventurePlanet = slot.IsAdventurePlanet;
oldSlot.LevelType = slot.LevelType;
oldSlot.SubLevel = slot.IsSubLevel;
oldSlot.MoveRequired = slot.IsMoveRequired;
oldSlot.CrossControllerRequired = slot.IsCrossControlRequired;
#region Set plays oldSlot.LastUpdated = TimeHelper.TimestampMillis;
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) if (slot.MinimumPlayers == 0 || slot.MaximumPlayers == 0)
{ {
@ -227,20 +231,23 @@ public class PublishController : ControllerBase
slot.MaximumPlayers = 4; slot.MaximumPlayers = 4;
} }
slot.MinimumPlayers = Math.Clamp(slot.MinimumPlayers, 1, 4); oldSlot.MinimumPlayers = Math.Clamp(slot.MinimumPlayers, 1, 4);
slot.MaximumPlayers = Math.Clamp(slot.MaximumPlayers, 1, 4); oldSlot.MaximumPlayers = Math.Clamp(slot.MaximumPlayers, 1, 4);
this.database.Entry(oldSlot).CurrentValues.SetValues(slot);
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
return this.Ok(oldSlot.Serialize(token.GameVersion)); return this.Ok(SlotBase.CreateFromEntity(oldSlot, this.GetToken()));
} }
if (user.GetUsedSlotsForGame(slotVersion) > user.EntitledSlots) int usedSlots = await this.database.Slots.CountAsync(s => s.CreatorId == token.UserId && s.GameVersion == slotVersion);
if (usedSlots > user.EntitledSlots)
{ {
Logger.Warn("Rejecting level upload, too many published slots", LogArea.Publish); Logger.Warn("Rejecting level upload, too many published slots", LogArea.Publish);
return this.BadRequest(); return this.BadRequest();
} }
SlotEntity slotEntity = SlotBase.ConvertToEntity(slot);
slot.CreatorId = user.UserId; slot.CreatorId = user.UserId;
slot.FirstUploaded = TimeHelper.TimestampMillis; slot.FirstUploaded = TimeHelper.TimestampMillis;
slot.LastUpdated = TimeHelper.TimestampMillis; slot.LastUpdated = TimeHelper.TimestampMillis;
@ -254,7 +261,7 @@ public class PublishController : ControllerBase
slot.MinimumPlayers = Math.Clamp(slot.MinimumPlayers, 1, 4); slot.MinimumPlayers = Math.Clamp(slot.MinimumPlayers, 1, 4);
slot.MaximumPlayers = Math.Clamp(slot.MaximumPlayers, 1, 4); slot.MaximumPlayers = Math.Clamp(slot.MaximumPlayers, 1, 4);
this.database.Slots.Add(slot); this.database.Slots.Add(slotEntity);
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
if (user.LevelVisibility == PrivacyType.All) if (user.LevelVisibility == PrivacyType.All)
@ -265,18 +272,18 @@ public class PublishController : ControllerBase
Logger.Success($"Successfully published level {slot.Name} (id: {slot.SlotId}) by {user.Username} (id: {user.UserId})", LogArea.Publish); 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)); return this.Ok(SlotBase.CreateFromEntity(slotEntity, this.GetToken()));
} }
[HttpPost("unpublish/{id:int}")] [HttpPost("unpublish/{id:int}")]
public async Task<IActionResult> Unpublish(int id) public async Task<IActionResult> Unpublish(int id)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); SlotEntity? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound(); if (slot == null) return this.NotFound();
if (slot.CreatorId != token.UserId) return this.StatusCode(403, ""); if (slot.CreatorId != token.UserId) return this.Forbid();
this.database.Slots.Remove(slot); this.database.Slots.Remove(slot);

View file

@ -2,10 +2,10 @@
using LBPUnion.ProjectLighthouse.Database; 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.Types.Entities.Interaction; using LBPUnion.ProjectLighthouse.Types.Entities.Interaction;
using LBPUnion.ProjectLighthouse.Types.Entities.Level; using LBPUnion.ProjectLighthouse.Types.Entities.Level;
using LBPUnion.ProjectLighthouse.Types.Entities.Token; using LBPUnion.ProjectLighthouse.Types.Entities.Token;
using LBPUnion.ProjectLighthouse.Types.Serialization;
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;
@ -30,15 +30,15 @@ public class ReviewController : ControllerBase
[HttpPost("rate/user/{slotId:int}")] [HttpPost("rate/user/{slotId:int}")]
public async Task<IActionResult> Rate(int slotId, [FromQuery] int rating) public async Task<IActionResult> Rate(int slotId, [FromQuery] int rating)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
Slot? slot = await this.database.Slots.Include(s => s.Creator).FirstOrDefaultAsync(s => s.SlotId == slotId); SlotEntity? 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.Forbid();
RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == slotId && r.UserId == token.UserId); RatedLevelEntity? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == slotId && r.UserId == token.UserId);
if (ratedLevel == null) if (ratedLevel == null)
{ {
ratedLevel = new RatedLevel ratedLevel = new RatedLevelEntity
{ {
SlotId = slotId, SlotId = slotId,
UserId = token.UserId, UserId = token.UserId,
@ -59,15 +59,15 @@ public class ReviewController : ControllerBase
[HttpPost("dpadrate/user/{slotId:int}")] [HttpPost("dpadrate/user/{slotId:int}")]
public async Task<IActionResult> DPadRate(int slotId, [FromQuery] int rating) public async Task<IActionResult> DPadRate(int slotId, [FromQuery] int rating)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slotId); SlotEntity? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slotId);
if (slot == null) return this.StatusCode(403, ""); if (slot == null) return this.Forbid();
RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == slotId && r.UserId == token.UserId); RatedLevelEntity? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == slotId && r.UserId == token.UserId);
if (ratedLevel == null) if (ratedLevel == null)
{ {
ratedLevel = new RatedLevel ratedLevel = new RatedLevelEntity
{ {
SlotId = slotId, SlotId = slotId,
UserId = token.UserId, UserId = token.UserId,
@ -79,7 +79,7 @@ public class ReviewController : ControllerBase
ratedLevel.Rating = Math.Clamp(rating, -1, 1); ratedLevel.Rating = Math.Clamp(rating, -1, 1);
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == token.UserId); ReviewEntity? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == token.UserId);
if (review != null) review.Thumb = ratedLevel.Rating; if (review != null) review.Thumb = ratedLevel.Rating;
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
@ -90,20 +90,20 @@ public class ReviewController : ControllerBase
[HttpPost("postReview/user/{slotId:int}")] [HttpPost("postReview/user/{slotId:int}")]
public async Task<IActionResult> PostReview(int slotId) public async Task<IActionResult> PostReview(int slotId)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
Review? newReview = await this.DeserializeBody<Review>(); GameReview? newReview = await this.DeserializeBody<GameReview>();
if (newReview == null) return this.BadRequest(); if (newReview == null) return this.BadRequest();
newReview.Text = CensorHelper.FilterMessage(newReview.Text); newReview.Text = CensorHelper.FilterMessage(newReview.Text);
if (newReview.Text.Length > 512) return this.BadRequest(); if (newReview.Text.Length > 512) return this.BadRequest();
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == token.UserId); ReviewEntity? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == token.UserId);
if (review == null) if (review == null)
{ {
review = new Review review = new ReviewEntity
{ {
SlotId = slotId, SlotId = slotId,
ReviewerId = token.UserId, ReviewerId = token.UserId,
@ -121,10 +121,10 @@ public class ReviewController : ControllerBase
review.Timestamp = TimeHelper.TimestampMillis; review.Timestamp = TimeHelper.TimestampMillis;
// sometimes the game posts/updates a review rating without also calling dpadrate/user/etc (why??) // 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 == token.UserId); RatedLevelEntity? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == slotId && r.UserId == token.UserId);
if (ratedLevel == null) if (ratedLevel == null)
{ {
ratedLevel = new RatedLevel ratedLevel = new RatedLevelEntity
{ {
SlotId = slotId, SlotId = slotId,
UserId = token.UserId, UserId = token.UserId,
@ -144,58 +144,32 @@ public class ReviewController : ControllerBase
[HttpGet("reviewsFor/user/{slotId:int}")] [HttpGet("reviewsFor/user/{slotId:int}")]
public async Task<IActionResult> ReviewsFor(int slotId, [FromQuery] int pageStart = 1, [FromQuery] int pageSize = 10) public async Task<IActionResult> ReviewsFor(int slotId, [FromQuery] int pageStart = 1, [FromQuery] int pageSize = 10)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
GameVersion gameVersion = token.GameVersion; GameVersion gameVersion = token.GameVersion;
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slotId); SlotEntity? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slotId);
if (slot == null) return this.BadRequest(); if (slot == null) return this.BadRequest();
IQueryable<Review?> reviews = this.database.Reviews.ByGameVersion(gameVersion, true) List<GameReview> reviews = await this.database.Reviews.ByGameVersion(gameVersion, true)
.Where(r => r.SlotId == slotId) .Where(r => r.SlotId == slotId)
.Include(r => r.Reviewer)
.Include(r => r.Slot)
.OrderByDescending(r => r.ThumbsUp - r.ThumbsDown) .OrderByDescending(r => r.ThumbsUp - r.ThumbsDown)
.ThenByDescending(r => r.Timestamp) .ThenByDescending(r => r.Timestamp)
.Skip(Math.Max(0, pageStart - 1)) .Skip(Math.Max(0, pageStart - 1))
.Take(pageSize); .Take(Math.Min(pageSize, 30))
.Select(r => GameReview.CreateFromEntity(r, token))
.ToListAsync();
List<Review?> reviewList = reviews.ToList();
string inner = reviewList.Aggregate return this.Ok(new ReviewResponse(reviews, reviews.LastOrDefault()?.Timestamp ?? TimeHelper.TimestampMillis, pageStart + Math.Min(pageSize, 30)));
(
string.Empty,
(current, review) =>
{
if (review == null) return current;
RatedReview? yourThumb = this.database.RatedReviews.FirstOrDefault(r => r.ReviewId == review.ReviewId && r.UserId == token.UserId);
return current + review.Serialize(yourThumb);
}
);
string response = LbpSerializer.TaggedStringElement
(
"reviews",
inner,
new Dictionary<string, object>
{
{
"hint_start", pageStart + pageSize
},
{
"hint", reviewList.LastOrDefault()?.Timestamp ?? 0
},
}
);
return this.Ok(response);
} }
[HttpGet("reviewsBy/{username}")] [HttpGet("reviewsBy/{username}")]
public async Task<IActionResult> ReviewsBy(string username, [FromQuery] int pageStart = 1, [FromQuery] int pageSize = 10) public async Task<IActionResult> ReviewsBy(string username, [FromQuery] int pageStart = 1, [FromQuery] int pageSize = 10)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
@ -205,61 +179,32 @@ public class ReviewController : ControllerBase
if (targetUserId == 0) return this.BadRequest(); if (targetUserId == 0) return this.BadRequest();
IEnumerable<Review?> reviews = this.database.Reviews.ByGameVersion(gameVersion, true) List<GameReview> reviews = await this.database.Reviews.ByGameVersion(gameVersion, true)
.Include(r => r.Reviewer)
.Include(r => r.Slot)
.Where(r => r.ReviewerId == targetUserId) .Where(r => r.ReviewerId == targetUserId)
.OrderByDescending(r => r.Timestamp) .OrderByDescending(r => r.Timestamp)
.Skip(Math.Max(0, pageStart - 1)) .Skip(Math.Max(0, pageStart - 1))
.Take(pageSize); .Take(Math.Min(pageSize, 30))
.Select(r => GameReview.CreateFromEntity(r, token))
.ToListAsync();
List<Review?> reviewList = reviews.ToList(); return this.Ok(new ReviewResponse(reviews, reviews.LastOrDefault()?.Timestamp ?? TimeHelper.TimestampMillis, pageStart));
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 == token.UserId);
return current + review.Serialize(ratedReview);
}
);
string response = LbpSerializer.TaggedStringElement
(
"reviews",
inner,
new Dictionary<string, object>
{
{
"hint_start", pageStart
},
{
"hint", reviewList.LastOrDefault()?.Timestamp ?? 0 // Seems to be the timestamp of oldest
},
}
);
return this.Ok(response);
} }
[HttpPost("rateReview/user/{slotId:int}/{username}")] [HttpPost("rateReview/user/{slotId:int}/{username}")]
public async Task<IActionResult> RateReview(int slotId, string username, [FromQuery] int rating = 0) public async Task<IActionResult> RateReview(int slotId, string username, [FromQuery] int rating = 0)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
int reviewerId = await this.database.UserIdFromUsername(username); int reviewerId = await this.database.UserIdFromUsername(username);
if (reviewerId == 0) return this.StatusCode(400, ""); if (reviewerId == 0) return this.BadRequest();
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == reviewerId); ReviewEntity? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == reviewerId);
if (review == null) return this.StatusCode(400, ""); if (review == null) return this.BadRequest();
RatedReview? ratedReview = await this.database.RatedReviews.FirstOrDefaultAsync(r => r.ReviewId == review.ReviewId && r.UserId == token.UserId); RatedReviewEntity? ratedReview = await this.database.RatedReviews.FirstOrDefaultAsync(r => r.ReviewId == review.ReviewId && r.UserId == token.UserId);
if (ratedReview == null) if (ratedReview == null)
{ {
ratedReview = new RatedReview ratedReview = new RatedReviewEntity
{ {
ReviewId = review.ReviewId, ReviewId = review.ReviewId,
UserId = token.UserId, UserId = token.UserId,
@ -301,18 +246,18 @@ public class ReviewController : ControllerBase
[HttpPost("deleteReview/user/{slotId:int}/{username}")] [HttpPost("deleteReview/user/{slotId:int}/{username}")]
public async Task<IActionResult> DeleteReview(int slotId, string username) public async Task<IActionResult> DeleteReview(int slotId, string username)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
int creatorId = await this.database.Slots.Where(s => s.SlotId == slotId).Select(s => s.CreatorId).FirstOrDefaultAsync(); int creatorId = await this.database.Slots.Where(s => s.SlotId == slotId).Select(s => s.CreatorId).FirstOrDefaultAsync();
if (creatorId == 0) return this.StatusCode(400, ""); if (creatorId == 0) return this.BadRequest();
if (token.UserId != creatorId) return this.StatusCode(403, ""); if (token.UserId != creatorId) return this.Unauthorized();
int reviewerId = await this.database.UserIdFromUsername(username); int reviewerId = await this.database.UserIdFromUsername(username);
if (reviewerId == 0) return this.StatusCode(400, ""); if (reviewerId == 0) return this.BadRequest();
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == reviewerId); ReviewEntity? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == reviewerId);
if (review == null) return this.StatusCode(400, ""); if (review == null) return this.BadRequest();
review.Deleted = true; review.Deleted = true;
review.DeletedBy = DeletedBy.LevelAuthor; review.DeletedBy = DeletedBy.LevelAuthor;

View file

@ -4,12 +4,12 @@ using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Extensions; using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.StorableLists.Stores; using LBPUnion.ProjectLighthouse.StorableLists.Stores;
using LBPUnion.ProjectLighthouse.Types.Entities.Level; using LBPUnion.ProjectLighthouse.Types.Entities.Level;
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.Logging; using LBPUnion.ProjectLighthouse.Types.Logging;
using LBPUnion.ProjectLighthouse.Types.Serialization;
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;
@ -34,7 +34,7 @@ public class ScoreController : ControllerBase
[HttpPost("scoreboard/{slotType}/{id:int}/{childId:int}")] [HttpPost("scoreboard/{slotType}/{id:int}/{childId:int}")]
public async Task<IActionResult> SubmitScore(string slotType, int id, int childId) public async Task<IActionResult> SubmitScore(string slotType, int id, int childId)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
string username = await this.database.UsernameFromGameToken(token); string username = await this.database.UsernameFromGameToken(token);
@ -44,16 +44,15 @@ public class ScoreController : ControllerBase
return this.BadRequest(); return this.BadRequest();
} }
Score? score = await this.DeserializeBody<Score>(); GameScore? score = await this.DeserializeBody<GameScore>();
if (score == null) if (score == null)
{ {
Logger.Warn($"Rejecting score upload, score is null (slotType={slotType}, slotId={id}, user={username})", LogArea.Score); Logger.Warn($"Rejecting score upload, score is null (slotType={slotType}, slotId={id}, user={username})", LogArea.Score);
return this.BadRequest(); return this.BadRequest();
} }
// This only seems to happens on lbp2 versus levels, not sure why // Workaround for parsing player ids of versus levels
if (score.PlayerIdCollection.Contains(':')) if (score.PlayerIds.Length == 1 && score.PlayerIds[0].Contains(':')) score.PlayerIds = score.PlayerIds[0].Split(":");
score.PlayerIdCollection = score.PlayerIdCollection.Replace(':', ',');
if (score.PlayerIds.Length == 0) if (score.PlayerIds.Length == 0)
{ {
@ -89,15 +88,14 @@ public class ScoreController : ControllerBase
SanitizationHelper.SanitizeStringsInClass(score); SanitizationHelper.SanitizeStringsInClass(score);
if (slotType == "developer") id = await SlotHelper.GetPlaceholderSlotId(this.database, id, SlotType.Developer); int slotId = id;
score.SlotId = id; if (slotType == "developer") slotId = await SlotHelper.GetPlaceholderSlotId(this.database, slotId, SlotType.Developer);
score.ChildSlotId = childId;
Slot? slot = this.database.Slots.FirstOrDefault(s => s.SlotId == score.SlotId); SlotEntity? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) if (slot == null)
{ {
Logger.Warn($"Rejecting score upload, slot is null (slotId={score.SlotId}, slotType={slotType}, reqId={id}, user={username})", LogArea.Score); Logger.Warn($"Rejecting score upload, slot is null (slotId={slotId}, slotType={slotType}, reqId={id}, user={username})", LogArea.Score);
return this.BadRequest(); return this.BadRequest();
} }
@ -115,46 +113,56 @@ public class ScoreController : ControllerBase
break; break;
case GameVersion.LittleBigPlanetPSP: case GameVersion.LittleBigPlanetPSP:
case GameVersion.Unknown: case GameVersion.Unknown:
default: throw new ArgumentOutOfRangeException(); default:
return this.BadRequest();
} }
Score playerScore = new() await this.database.SaveChangesAsync();
{
PlayerIdCollection = string.Join(',', score.PlayerIds),
Type = score.Type,
Points = score.Points,
SlotId = score.SlotId,
ChildSlotId = score.ChildSlotId,
};
IQueryable<Score> existingScore = this.database.Scores.Where(s => s.SlotId == playerScore.SlotId) string playerIdCollection = string.Join(',', score.PlayerIds);
ScoreEntity? existingScore = await this.database.Scores.Where(s => s.SlotId == slot.SlotId)
.Where(s => s.ChildSlotId == 0 || s.ChildSlotId == childId) .Where(s => s.ChildSlotId == 0 || s.ChildSlotId == childId)
.Where(s => s.PlayerIdCollection == playerScore.PlayerIdCollection) .Where(s => s.PlayerIdCollection == playerIdCollection)
.Where(s => s.Type == playerScore.Type); .Where(s => s.Type == score.Type)
if (existingScore.Any()) .FirstOrDefaultAsync();
if (existingScore != null)
{ {
Score first = existingScore.First(s => s.SlotId == playerScore.SlotId); existingScore.Points = Math.Max(existingScore.Points, score.Points);
playerScore.ScoreId = first.ScoreId;
playerScore.Points = Math.Max(first.Points, playerScore.Points);
this.database.Entry(first).CurrentValues.SetValues(playerScore);
} }
else else
{ {
ScoreEntity playerScore = new()
{
PlayerIdCollection = playerIdCollection,
Type = score.Type,
Points = score.Points,
SlotId = slotId,
ChildSlotId = childId,
};
this.database.Scores.Add(playerScore); this.database.Scores.Add(playerScore);
} }
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
string myRanking = this.getScores(score.SlotId, score.Type, username, -1, 5, "scoreboardSegment", childId: score.ChildSlotId); return this.Ok(this.getScores(new LeaderboardOptions
{
return this.Ok(myRanking); RootName = "scoreboardSegment",
PageSize = 5,
PageStart = -1,
SlotId = slotId,
ChildSlotId = childId,
ScoreType = score.Type,
TargetUsername = username,
TargetPlayerIds = null,
}));
} }
[HttpGet("friendscores/{slotType}/{slotId:int}/{type:int}")] [HttpGet("friendscores/{slotType}/{slotId:int}/{type:int}")]
[HttpGet("friendscores/{slotType}/{slotId:int}/{childId:int}/{type:int}")] [HttpGet("friendscores/{slotType}/{slotId:int}/{childId:int}/{type:int}")]
public async Task<IActionResult> FriendScores(string slotType, int slotId, int? childId, int type, [FromQuery] int pageStart = -1, [FromQuery] int pageSize = 5) public async Task<IActionResult> FriendScores(string slotType, int slotId, int? childId, int type, [FromQuery] int pageStart = -1, [FromQuery] int pageSize = 5)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
@ -180,7 +188,17 @@ public class ScoreController : ControllerBase
if (friendUsername != null) friendNames.Add(friendUsername); if (friendUsername != null) friendNames.Add(friendUsername);
} }
return this.Ok(this.getScores(slotId, type, username, pageStart, pageSize, "scores", friendNames.ToArray(), childId)); return this.Ok(this.getScores(new LeaderboardOptions
{
RootName = "scores",
PageSize = pageSize,
PageStart = pageStart,
SlotId = slotId,
ChildSlotId = childId,
ScoreType = type,
TargetUsername = username,
TargetPlayerIds = friendNames.ToArray(),
}));
} }
[HttpGet("topscores/{slotType}/{slotId:int}/{type:int}")] [HttpGet("topscores/{slotType}/{slotId:int}/{type:int}")]
@ -188,7 +206,7 @@ public class ScoreController : ControllerBase
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
public async Task<IActionResult> TopScores(string slotType, int slotId, int? childId, int type, [FromQuery] int pageStart = -1, [FromQuery] int pageSize = 5) public async Task<IActionResult> TopScores(string slotType, int slotId, int? childId, int type, [FromQuery] int pageStart = -1, [FromQuery] int pageSize = 5)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
@ -198,79 +216,60 @@ public class ScoreController : ControllerBase
if (slotType == "developer") slotId = await SlotHelper.GetPlaceholderSlotId(this.database, slotId, SlotType.Developer); if (slotType == "developer") slotId = await SlotHelper.GetPlaceholderSlotId(this.database, slotId, SlotType.Developer);
return this.Ok(this.getScores(slotId, type, username, pageStart, pageSize, childId: childId)); return this.Ok(this.getScores(new LeaderboardOptions
{
RootName = "scores",
PageSize = pageSize,
PageStart = pageStart,
SlotId = slotId,
ChildSlotId = childId,
ScoreType = type,
TargetUsername = username,
TargetPlayerIds = null,
}));
} }
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] private class LeaderboardOptions
private string getScores {
( public int SlotId { get; set; }
int slotId, public int ScoreType { get; set; }
int type, public string TargetUsername { get; set; } = "";
string username, public int PageStart { get; set; } = -1;
int pageStart = -1, public int PageSize { get; set; } = 5;
int pageSize = 5, public string RootName { get; set; } = "scores";
string rootName = "scores", public string[]? TargetPlayerIds;
string[]? playerIds = null, public int? ChildSlotId;
int? childId = 0 }
)
private ScoreboardResponse getScores(LeaderboardOptions options)
{ {
// This is hella ugly but it technically assigns the proper rank to a score // This is hella ugly but it technically assigns the proper rank to a score
// var needed for Anonymous type returned from SELECT // var needed for Anonymous type returned from SELECT
var rankedScores = this.database.Scores var rankedScores = this.database.Scores.Where(s => s.SlotId == options.SlotId && s.Type == options.ScoreType)
.Where(s => s.SlotId == slotId && s.Type == type) .Where(s => s.ChildSlotId == 0 || s.ChildSlotId == options.ChildSlotId)
.Where(s => s.ChildSlotId == 0 || s.ChildSlotId == childId)
.AsEnumerable() .AsEnumerable()
.Where(s => playerIds == null || playerIds.Any(id => s.PlayerIdCollection.Split(",").Contains(id))) .Where(s => options.TargetPlayerIds == null ||
options.TargetPlayerIds.Any(id => s.PlayerIdCollection.Split(",").Contains(id)))
.OrderByDescending(s => s.Points) .OrderByDescending(s => s.Points)
.ThenBy(s => s.ScoreId) .ThenBy(s => s.ScoreId)
.ToList() .ToList()
.Select .Select((s, rank) => new
( {
(s, rank) => new Score = s,
{ Rank = rank + 1,
Score = s, })
Rank = rank + 1, .ToList();
}
);
// Find your score, since even if you aren't in the top list your score is pinned // 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.Split(",").Contains(username)).MaxBy(rs => rs.Score.Points); var myScore = rankedScores.Where(rs => rs.Score.PlayerIdCollection.Split(",").Contains(options.TargetUsername)).MaxBy(rs => rs.Score.Points);
// Paginated viewing: if not requesting pageStart, get results around user // 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)); var pagedScores = rankedScores.Skip(options.PageStart != -1 || myScore == null ? options.PageStart - 1 : myScore.Rank - 3).Take(Math.Min(options.PageSize, 30));
string serializedScores = pagedScores.Aggregate List<GameScore> gameScores = pagedScores.Select(ps => GameScore.CreateFromEntity(ps.Score, ps.Rank)).ToList();
(
string.Empty,
(current, rs) =>
{
rs.Score.Rank = rs.Rank;
return current + rs.Score.Serialize();
}
);
string res; return new ScoreboardResponse(options.RootName, gameScores, myScore?.Score.Points ?? 0, myScore?.Rank ?? 0, rankedScores.Count);
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;
} }
} }

View file

@ -1,10 +1,10 @@
#nullable enable #nullable enable
using LBPUnion.ProjectLighthouse.Database; using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Extensions; using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Types.Entities.Level; using LBPUnion.ProjectLighthouse.Types.Entities.Level;
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.Serialization;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -35,7 +35,7 @@ public class SearchController : ControllerBase
string? keyName = "slots" string? keyName = "slots"
) )
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
@ -45,7 +45,7 @@ public class SearchController : ControllerBase
string[] keywords = query.Split(" "); string[] keywords = query.Split(" ");
IQueryable<Slot> dbQuery = this.database.Slots.ByGameVersion(token.GameVersion, false, true) IQueryable<SlotEntity> dbQuery = this.database.Slots.ByGameVersion(token.GameVersion, false, true)
.Where(s => s.Type == SlotType.User) .Where(s => s.Type == SlotType.User)
.OrderBy(s => !s.TeamPick) .OrderBy(s => !s.TeamPick)
.ThenByDescending(s => s.FirstUploaded) .ThenByDescending(s => s.FirstUploaded)
@ -61,11 +61,12 @@ public class SearchController : ControllerBase
s.SlotId.ToString().Equals(keyword) s.SlotId.ToString().Equals(keyword)
); );
List<Slot> slots = await dbQuery.Skip(Math.Max(0, pageStart - 1)).Take(Math.Min(pageSize, 30)).ToListAsync(); List<SlotBase> slots = await dbQuery.Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30))
.Select(s => SlotBase.CreateFromEntity(s, this.GetToken()))
.ToListAsync();
string response = slots.Aggregate("", (current, slot) => current + slot.Serialize(token.GameVersion)); return this.Ok(new GenericSlotResponse(keyName, slots, await dbQuery.CountAsync(), 0));
return this.Ok(LbpSerializer.TaggedStringElement(keyName, response, "total", dbQuery.Count()));
} }
// /LITTLEBIGPLANETPS3_XML?pageStart=1&pageSize=10&resultTypes[]=slot&resultTypes[]=playlist&resultTypes[]=user&adventure=dontCare&textFilter=qwer // /LITTLEBIGPLANETPS3_XML?pageStart=1&pageSize=10&resultTypes[]=slot&resultTypes[]=playlist&resultTypes[]=user&adventure=dontCare&textFilter=qwer

View file

@ -1,14 +1,14 @@
#nullable enable #nullable enable
using System.Security.Cryptography;
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.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Types.Entities.Interaction;
using LBPUnion.ProjectLighthouse.Types.Entities.Level; using LBPUnion.ProjectLighthouse.Types.Entities.Level;
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.Matchmaking.Rooms; using LBPUnion.ProjectLighthouse.Types.Matchmaking.Rooms;
using LBPUnion.ProjectLighthouse.Types.Serialization;
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;
@ -28,113 +28,102 @@ public class SlotsController : ControllerBase
this.database = database; this.database = database;
} }
private static string generateSlotsResponse(string slotAggregate, int start, int total) =>
LbpSerializer.TaggedStringElement("slots",
slotAggregate,
new Dictionary<string, object>
{
{
"hint_start", start
},
{
"total", total
},
});
[HttpGet("slots/by")] [HttpGet("slots/by")]
public async Task<IActionResult> SlotsBy([FromQuery(Name="u")] string username, [FromQuery] int pageStart, [FromQuery] int pageSize) public async Task<IActionResult> SlotsBy([FromQuery(Name = "u")] string username, [FromQuery] int pageStart, [FromQuery] int pageSize)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
GameVersion gameVersion = token.GameVersion;
int targetUserId = await this.database.UserIdFromUsername(username); int targetUserId = await this.database.UserIdFromUsername(username);
if (targetUserId == 0) return this.NotFound(); if (targetUserId == 0) return this.NotFound();
int usedSlots = this.database.Slots.Count(s => s.CreatorId == targetUserId); int usedSlots = this.database.Slots.Count(s => s.CreatorId == targetUserId);
string response = Enumerable.Aggregate List<SlotBase> slots = await this.database.Slots.Where(s => s.CreatorId == targetUserId)
( .ByGameVersion(token.GameVersion, token.UserId == targetUserId)
this.database.Slots.Where(s => s.CreatorId == targetUserId) .Skip(Math.Max(0, pageStart - 1))
.ByGameVersion(gameVersion, token.UserId == targetUserId, true) .Take(Math.Min(pageSize, usedSlots))
.Skip(Math.Max(0, pageStart - 1)) .Select(s => SlotBase.CreateFromEntity(s, token))
.Take(Math.Min(pageSize, usedSlots)), .ToListAsync();
string.Empty,
(current, slot) => current + slot.Serialize(token.GameVersion)
);
int start = pageStart + Math.Min(pageSize, usedSlots); int start = pageStart + Math.Min(pageSize, usedSlots);
int total = await this.database.Slots.CountAsync(s => s.CreatorId == targetUserId); int total = await this.database.Slots.CountAsync(s => s.CreatorId == targetUserId);
return this.Ok(generateSlotsResponse(response, start, total));
return this.Ok(new GenericSlotResponse("slots", slots, total, start));
} }
[HttpGet("slotList")] [HttpGet("slotList")]
public async Task<IActionResult> GetSlotListAlt([FromQuery] int[] s) public async Task<IActionResult> GetSlotListAlt([FromQuery(Name = "s")] int[] slotIds)
{ {
List<string?> serializedSlots = new(); GameTokenEntity token = this.GetToken();
foreach (int slotId in s)
List<SlotBase> slots = new();
foreach (int slotId in slotIds)
{ {
Slot? slot = await this.database.Slots.Include(t => t.Creator).Where(t => t.SlotId == slotId && t.Type == SlotType.User).FirstOrDefaultAsync(); SlotEntity? 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();
if (slot == null) if (slot == null)
{ {
serializedSlots.Add($"<slot type=\"developer\"><id>{slotId}</id></slot>"); slots.Add(new GameDeveloperSlot
{
SlotId = slotId,
});
continue; continue;
} }
} }
serializedSlots.Add(slot.Serialize());
slots.Add(SlotBase.CreateFromEntity(slot, token));
} }
string serialized = serializedSlots.Aggregate(string.Empty, (current, slot) => slot == null ? current : current + slot);
return this.Ok(LbpSerializer.TaggedStringElement("slots", serialized, "total", serializedSlots.Count)); return this.Ok(new GenericSlotResponse(slots, slots.Count, 0));
} }
[HttpGet("slots/developer")] [HttpGet("slots/developer")]
public async Task<IActionResult> StoryPlayers() public async Task<IActionResult> StoryPlayers()
{ {
GameTokenEntity token = this.GetToken();
List<int> activeSlotIds = RoomHelper.Rooms.Where(r => r.Slot.SlotType == SlotType.Developer).Select(r => r.Slot.SlotId).ToList(); List<int> activeSlotIds = RoomHelper.Rooms.Where(r => r.Slot.SlotType == SlotType.Developer).Select(r => r.Slot.SlotId).ToList();
List<string> serializedSlots = new(); List<SlotBase> slots = new();
foreach (int id in activeSlotIds) foreach (int id in activeSlotIds)
{ {
int placeholderSlotId = await SlotHelper.GetPlaceholderSlotId(this.database, id, SlotType.Developer); int placeholderSlotId = await SlotHelper.GetPlaceholderSlotId(this.database, id, SlotType.Developer);
Slot slot = await this.database.Slots.FirstAsync(s => s.SlotId == placeholderSlotId); SlotEntity slot = await this.database.Slots.FirstAsync(s => s.SlotId == placeholderSlotId);
serializedSlots.Add(slot.SerializeDevSlot());
slots.Add(SlotBase.CreateFromEntity(slot, token));
} }
string serialized = serializedSlots.Aggregate(string.Empty, (current, slot) => current + slot); return this.Ok(new GenericSlotResponse(slots));
return this.Ok(LbpSerializer.StringElement("slots", serialized));
} }
[HttpGet("s/developer/{id:int}")] [HttpGet("s/developer/{id:int}")]
public async Task<IActionResult> SDev(int id) public async Task<IActionResult> SDev(int id)
{ {
int slotId = await SlotHelper.GetPlaceholderSlotId(this.database, id, SlotType.Developer); GameTokenEntity token = this.GetToken();
Slot slot = await this.database.Slots.FirstAsync(s => s.SlotId == slotId);
return this.Ok(slot.SerializeDevSlot()); int slotId = await SlotHelper.GetPlaceholderSlotId(this.database, id, SlotType.Developer);
SlotEntity slot = await this.database.Slots.FirstAsync(s => s.SlotId == slotId);
return this.Ok(SlotBase.CreateFromEntity(slot, token));
} }
[HttpGet("s/user/{id:int}")] [HttpGet("s/user/{id:int}")]
public async Task<IActionResult> SUser(int id) public async Task<IActionResult> SUser(int id)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
GameVersion gameVersion = token.GameVersion; GameVersion gameVersion = token.GameVersion;
Slot? slot = await this.database.Slots.ByGameVersion(gameVersion, true, true).FirstOrDefaultAsync(s => s.SlotId == id); SlotEntity? slot = await this.database.Slots.ByGameVersion(gameVersion, true, true).FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound(); if (slot == null) return this.NotFound();
RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == id && r.UserId == token.UserId); return this.Ok(SlotBase.CreateFromEntity(slot, token, SerializationMode.Full));
VisitedLevel? visitedLevel = await this.database.VisitedLevels.FirstOrDefaultAsync(r => r.SlotId == id && r.UserId == token.UserId);
Review? review = await this.database.Reviews.Include(r => r.Slot).FirstOrDefaultAsync(r => r.SlotId == id && r.ReviewerId == token.UserId);
return this.Ok(slot.Serialize(gameVersion, ratedLevel, visitedLevel, review, true));
} }
[HttpGet("slots/cool")] [HttpGet("slots/cool")]
@ -163,27 +152,28 @@ public class SlotsController : ControllerBase
[HttpGet("slots")] [HttpGet("slots")]
public async Task<IActionResult> NewestSlots([FromQuery] int pageStart, [FromQuery] int pageSize) public async Task<IActionResult> NewestSlots([FromQuery] int pageStart, [FromQuery] int pageSize)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
GameVersion gameVersion = token.GameVersion; GameVersion gameVersion = token.GameVersion;
IQueryable<Slot> slots = this.database.Slots.ByGameVersion(gameVersion, false, true) List<SlotBase> slots = await this.database.Slots.ByGameVersion(gameVersion, false, true)
.OrderByDescending(s => s.FirstUploaded) .OrderByDescending(s => s.FirstUploaded)
.Skip(Math.Max(0, pageStart - 1)) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30)); .Take(Math.Min(pageSize, 30))
.Select(s => SlotBase.CreateFromEntity(s, token))
.ToListAsync();
string response = Enumerable.Aggregate(slots, string.Empty, (current, slot) => current + slot.Serialize(gameVersion));
int start = pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots); int start = pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots);
int total = await StatisticsHelper.SlotCountForGame(this.database, token.GameVersion); int total = await StatisticsHelper.SlotCountForGame(this.database, token.GameVersion);
return this.Ok(generateSlotsResponse(response, start, total)); return this.Ok(new GenericSlotResponse(slots, total, start));
} }
[HttpGet("slots/like/{slotType}/{slotId:int}")] [HttpGet("slots/like/{slotType}/{slotId:int}")]
public async Task<IActionResult> SimilarSlots([FromRoute] string slotType, [FromRoute] int slotId, [FromQuery] int pageStart, [FromQuery] int pageSize) public async Task<IActionResult> SimilarSlots([FromRoute] string slotType, [FromRoute] int slotId, [FromQuery] int pageStart, [FromQuery] int pageSize)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
@ -191,10 +181,10 @@ public class SlotsController : ControllerBase
GameVersion gameVersion = token.GameVersion; GameVersion gameVersion = token.GameVersion;
Slot? targetSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slotId); SlotEntity? targetSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slotId);
if (targetSlot == null) return this.BadRequest(); if (targetSlot == null) return this.BadRequest();
string[] tags = targetSlot.LevelTags; string[] tags = targetSlot.LevelTags(this.database);
List<int> slotIdsWithTag = this.database.RatedLevels List<int> slotIdsWithTag = this.database.RatedLevels
.Where(r => r.TagLBP1.Length > 0) .Where(r => r.TagLBP1.Length > 0)
@ -202,105 +192,108 @@ public class SlotsController : ControllerBase
.Select(r => r.SlotId) .Select(r => r.SlotId)
.ToList(); .ToList();
IQueryable<Slot> slots = this.database.Slots.ByGameVersion(gameVersion, false, true) List<SlotBase> slots = await this.database.Slots.ByGameVersion(gameVersion, false, true)
.Where(s => slotIdsWithTag.Contains(s.SlotId)) .Where(s => slotIdsWithTag.Contains(s.SlotId))
.OrderByDescending(s => s.PlaysLBP1) .OrderByDescending(s => s.PlaysLBP1)
.Skip(Math.Max(0, pageStart - 1)) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30)); .Take(Math.Min(pageSize, 30))
.Select(s => SlotBase.CreateFromEntity(s, token))
.ToListAsync();
string response = Enumerable.Aggregate(slots, string.Empty, (current, slot) => current + slot.Serialize(gameVersion));
int start = pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots); int start = pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots);
int total = slotIdsWithTag.Count; int total = slotIdsWithTag.Count;
return this.Ok(generateSlotsResponse(response, start, total)); return this.Ok(new GenericSlotResponse(slots, total, start));
} }
[HttpGet("slots/highestRated")] [HttpGet("slots/highestRated")]
public async Task<IActionResult> HighestRatedSlots([FromQuery] int pageStart, [FromQuery] int pageSize) public async Task<IActionResult> HighestRatedSlots([FromQuery] int pageStart, [FromQuery] int pageSize)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
GameVersion gameVersion = token.GameVersion; GameVersion gameVersion = token.GameVersion;
IEnumerable<Slot> slots = this.database.Slots.ByGameVersion(gameVersion, false, true) List<SlotBase> slots = await this.database.Slots.ByGameVersion(gameVersion, false, true)
.AsEnumerable() .ToAsyncEnumerable()
.OrderByDescending(s => s.RatingLBP1) .OrderByDescending(s => s.RatingLBP1)
.Skip(Math.Max(0, pageStart - 1)) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30)); .Take(Math.Min(pageSize, 30))
.Select(s => SlotBase.CreateFromEntity(s, token))
.ToListAsync();
string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize(gameVersion));
int start = pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots); int start = pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots);
int total = await StatisticsHelper.SlotCount(this.database); int total = await StatisticsHelper.SlotCount(this.database);
return this.Ok(generateSlotsResponse(response, start, total)); return this.Ok(new GenericSlotResponse(slots, total, start));
} }
[HttpGet("slots/tag")] [HttpGet("slots/tag")]
public async Task<IActionResult> SimilarSlots([FromQuery] string tag, [FromQuery] int pageStart, [FromQuery] int pageSize) public async Task<IActionResult> SimilarSlots([FromQuery] string tag, [FromQuery] int pageStart, [FromQuery] int pageSize)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
GameVersion gameVersion = token.GameVersion;
List<int> slotIdsWithTag = await this.database.RatedLevels.Where(r => r.TagLBP1.Length > 0) List<int> slotIdsWithTag = await this.database.RatedLevels.Where(r => r.TagLBP1.Length > 0)
.Where(r => r.TagLBP1 == tag) .Where(r => r.TagLBP1 == tag)
.Select(s => s.SlotId) .Select(s => s.SlotId)
.ToListAsync(); .ToListAsync();
IQueryable<Slot> slots = this.database.Slots.Where(s => slotIdsWithTag.Contains(s.SlotId)) List<SlotBase> slots = await this.database.Slots.Where(s => slotIdsWithTag.Contains(s.SlotId))
.ByGameVersion(gameVersion, false, true) .ByGameVersion(token.GameVersion, false, true)
.OrderByDescending(s => s.PlaysLBP1) .OrderByDescending(s => s.PlaysLBP1)
.Skip(Math.Max(0, pageStart - 1)) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30)); .Take(Math.Min(pageSize, 30))
.Select(s => SlotBase.CreateFromEntity(s, token))
.ToListAsync();
string response = Enumerable.Aggregate(slots, string.Empty, (current, slot) => current + slot.Serialize(gameVersion));
int start = pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots); int start = pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots);
int total = slotIdsWithTag.Count; int total = slotIdsWithTag.Count;
return this.Ok(generateSlotsResponse(response, start, total)); return this.Ok(new GenericSlotResponse(slots, total, start));
} }
[HttpGet("slots/mmpicks")] [HttpGet("slots/mmpicks")]
public async Task<IActionResult> TeamPickedSlots([FromQuery] int pageStart, [FromQuery] int pageSize) public async Task<IActionResult> TeamPickedSlots([FromQuery] int pageStart, [FromQuery] int pageSize)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
GameVersion gameVersion = token.GameVersion; List<SlotBase> slots = await this.database.Slots.Where(s => s.TeamPick)
.ByGameVersion(token.GameVersion, false, true)
IQueryable<Slot> slots = this.database.Slots.Where(s => s.TeamPick)
.ByGameVersion(gameVersion, false, true)
.OrderByDescending(s => s.LastUpdated) .OrderByDescending(s => s.LastUpdated)
.Skip(Math.Max(0, pageStart - 1)) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30)); .Take(Math.Min(pageSize, 30))
string response = Enumerable.Aggregate(slots, string.Empty, (current, slot) => current + slot.Serialize(gameVersion)); .Select(s => SlotBase.CreateFromEntity(s, token))
.ToListAsync();
int start = pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots); int start = pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots);
int total = await StatisticsHelper.TeamPickCountForGame(this.database, token.GameVersion); int total = await StatisticsHelper.TeamPickCountForGame(this.database, token.GameVersion);
return this.Ok(generateSlotsResponse(response, start, total)); return this.Ok(new GenericSlotResponse(slots, total, start));
} }
[HttpGet("slots/lbp2luckydip")] [HttpGet("slots/lbp2luckydip")]
public async Task<IActionResult> LuckyDipSlots([FromQuery] int pageStart, [FromQuery] int pageSize, [FromQuery] int seed) public async Task<IActionResult> LuckyDipSlots([FromQuery] int pageStart, [FromQuery] int pageSize, [FromQuery] int seed)
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
GameVersion gameVersion = token.GameVersion; GameVersion gameVersion = token.GameVersion;
IEnumerable<Slot> slots = this.database.Slots.ByGameVersion(gameVersion, false, true).OrderBy(_ => EF.Functions.Random()).Take(Math.Min(pageSize, 30)); List<SlotBase> slots = await this.database.Slots.ByGameVersion(gameVersion, false, true)
.OrderBy(_ => EF.Functions.Random())
.Take(Math.Min(pageSize, 30))
.Select(s => SlotBase.CreateFromEntity(s, token))
.ToListAsync();
string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize(gameVersion));
int start = pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots); int start = pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots);
int total = await StatisticsHelper.SlotCountForGame(this.database, token.GameVersion); int total = await StatisticsHelper.SlotCountForGame(this.database, token.GameVersion);
return this.Ok(generateSlotsResponse(response, start, total)); return this.Ok(new GenericSlotResponse(slots, total, start));
} }
[HttpGet("slots/thumbs")] [HttpGet("slots/thumbs")]
@ -314,24 +307,25 @@ public class SlotsController : ControllerBase
[FromQuery] string? dateFilterType = null [FromQuery] string? dateFilterType = null
) )
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
Random rand = new(); Random rand = new();
IEnumerable<Slot> slots = this.filterByRequest(gameFilterType, dateFilterType, token.GameVersion) List<SlotBase> slots = await this.filterByRequest(gameFilterType, dateFilterType, token.GameVersion)
.AsEnumerable() .AsAsyncEnumerable()
.OrderByDescending(s => s.Thumbsup) .OrderByDescending(s => s.Thumbsup)
.ThenBy(_ => rand.Next()) .ThenBy(_ => rand.Next())
.Skip(Math.Max(0, pageStart - 1)) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30)); .Take(Math.Min(pageSize, 30))
.Select(s => SlotBase.CreateFromEntity(s, token))
.ToListAsync();
string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize(token.GameVersion));
int start = pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots); int start = pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots);
int total = await StatisticsHelper.SlotCountForGame(this.database, token.GameVersion); int total = await StatisticsHelper.SlotCountForGame(this.database, token.GameVersion);
return this.Ok(generateSlotsResponse(response, start, total)); return this.Ok(new GenericSlotResponse(slots, total, start));
} }
[HttpGet("slots/mostUniquePlays")] [HttpGet("slots/mostUniquePlays")]
@ -345,20 +339,19 @@ public class SlotsController : ControllerBase
[FromQuery] string? dateFilterType = null [FromQuery] string? dateFilterType = null
) )
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
Random rand = new(); Random rand = new();
IEnumerable<Slot> slots = this.filterByRequest(gameFilterType, dateFilterType, token.GameVersion) List<SlotBase> slots = await this.filterByRequest(gameFilterType, dateFilterType, token.GameVersion)
.AsEnumerable() .AsAsyncEnumerable()
.OrderByDescending .OrderByDescending(
(
// probably not the best way to do this? // probably not the best way to do this?
s => s =>
{ {
return this.getGameFilter(gameFilterType, token.GameVersion) switch return getGameFilter(gameFilterType, token.GameVersion) switch
{ {
GameVersion.LittleBigPlanet1 => s.PlaysLBP1Unique, GameVersion.LittleBigPlanet1 => s.PlaysLBP1Unique,
GameVersion.LittleBigPlanet2 => s.PlaysLBP2Unique, GameVersion.LittleBigPlanet2 => s.PlaysLBP2Unique,
@ -366,17 +359,17 @@ public class SlotsController : ControllerBase
GameVersion.LittleBigPlanetVita => s.PlaysLBP2Unique, GameVersion.LittleBigPlanetVita => s.PlaysLBP2Unique,
_ => s.PlaysUnique, _ => s.PlaysUnique,
}; };
} })
)
.ThenBy(_ => rand.Next()) .ThenBy(_ => rand.Next())
.Skip(Math.Max(0, pageStart - 1)) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30)); .Take(Math.Min(pageSize, 30))
.Select(s => SlotBase.CreateFromEntity(s, token))
.ToListAsync();
string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize(token.GameVersion));
int start = pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots); int start = pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots);
int total = await StatisticsHelper.SlotCountForGame(this.database, token.GameVersion); int total = await StatisticsHelper.SlotCountForGame(this.database, token.GameVersion);
return this.Ok(generateSlotsResponse(response, start, total)); return this.Ok(new GenericSlotResponse(slots, total, start));
} }
[HttpGet("slots/mostHearted")] [HttpGet("slots/mostHearted")]
@ -390,24 +383,23 @@ public class SlotsController : ControllerBase
[FromQuery] string? dateFilterType = null [FromQuery] string? dateFilterType = null
) )
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
Random rand = new(); List<SlotBase> slots = await this.filterByRequest(gameFilterType, dateFilterType, token.GameVersion)
.AsAsyncEnumerable()
IEnumerable<Slot> slots = this.filterByRequest(gameFilterType, dateFilterType, token.GameVersion)
.AsEnumerable()
.OrderByDescending(s => s.Hearts) .OrderByDescending(s => s.Hearts)
.ThenBy(_ => rand.Next()) .ThenBy(_ => RandomNumberGenerator.GetInt32(int.MaxValue))
.Skip(Math.Max(0, pageStart - 1)) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30)); .Take(Math.Min(pageSize, 30))
.Select(s => SlotBase.CreateFromEntity(s, token))
.ToListAsync();
string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize(token.GameVersion));
int start = pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots); int start = pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots);
int total = await StatisticsHelper.SlotCountForGame(this.database, token.GameVersion); int total = await StatisticsHelper.SlotCountForGame(this.database, token.GameVersion);
return this.Ok(generateSlotsResponse(response, start, total)); return this.Ok(new GenericSlotResponse(slots, total, start));
} }
// /slots/busiest?pageStart=1&pageSize=30&gameFilterType=both&players=1&move=true // /slots/busiest?pageStart=1&pageSize=30&gameFilterType=both&players=1&move=true
@ -421,7 +413,7 @@ public class SlotsController : ControllerBase
[FromQuery] bool? move = null [FromQuery] bool? move = null
) )
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
@ -447,42 +439,45 @@ public class SlotsController : ControllerBase
.OrderByDescending(kvp => kvp.Value) .OrderByDescending(kvp => kvp.Value)
.Select(kvp => kvp.Key); .Select(kvp => kvp.Key);
List<Slot> slots = new(); List<SlotBase> slots = new();
foreach (int slotId in orderedPlayersBySlotId) foreach (int slotId in orderedPlayersBySlotId)
{ {
Slot? slot = await this.database.Slots.ByGameVersion(token.GameVersion, false, true) SlotBase? slot = await this.database.Slots.ByGameVersion(token.GameVersion, false, true)
.FirstOrDefaultAsync(s => s.SlotId == slotId); .Where(s => s.SlotId == slotId)
.Select(s => SlotBase.CreateFromEntity(s, token))
.FirstOrDefaultAsync();
if (slot == null) continue; // shouldn't happen ever unless the room is borked if (slot == null) continue; // shouldn't happen ever unless the room is borked
slots.Add(slot); slots.Add(slot);
} }
string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize(token.GameVersion));
int start = pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots); int start = pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots);
int total = playersBySlotId.Count; int total = playersBySlotId.Count;
return this.Ok(generateSlotsResponse(response, start, total)); return this.Ok(new GenericSlotResponse(slots, total, start));
} }
private GameVersion getGameFilter(string? gameFilterType, GameVersion version) private static GameVersion getGameFilter(string? gameFilterType, GameVersion version)
{ {
if (version == GameVersion.LittleBigPlanetVita) return GameVersion.LittleBigPlanetVita; return version switch
if (version == GameVersion.LittleBigPlanetPSP) return GameVersion.LittleBigPlanetPSP;
return gameFilterType switch
{ {
"lbp1" => GameVersion.LittleBigPlanet1, GameVersion.LittleBigPlanetVita => GameVersion.LittleBigPlanetVita,
"lbp2" => GameVersion.LittleBigPlanet2, GameVersion.LittleBigPlanetPSP => GameVersion.LittleBigPlanetPSP,
"lbp3" => GameVersion.LittleBigPlanet3, _ => gameFilterType switch
"both" => GameVersion.LittleBigPlanet2, // LBP2 default option {
null => GameVersion.LittleBigPlanet1, "lbp1" => GameVersion.LittleBigPlanet1,
_ => GameVersion.Unknown, "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) private IQueryable<SlotEntity> filterByRequest(string? gameFilterType, string? dateFilterType, GameVersion version)
{ {
if (version == GameVersion.LittleBigPlanetVita || version == GameVersion.LittleBigPlanetPSP || version == GameVersion.Unknown) if (version == GameVersion.LittleBigPlanetVita || version == GameVersion.LittleBigPlanetPSP || version == GameVersion.Unknown)
{ {
@ -498,9 +493,9 @@ public class SlotsController : ControllerBase
_ => 0, _ => 0,
}; };
GameVersion gameVersion = this.getGameFilter(gameFilterType, version); GameVersion gameVersion = getGameFilter(gameFilterType, version);
IQueryable<Slot> whereSlots; IQueryable<SlotEntity> whereSlots;
// ReSharper disable once ConvertIfStatementToConditionalTernaryExpression // ReSharper disable once ConvertIfStatementToConditionalTernaryExpression
if (gameFilterType == "both") if (gameFilterType == "both")

View file

@ -1,9 +1,9 @@
using LBPUnion.ProjectLighthouse.Database; using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Serialization;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using LBPUnion.ProjectLighthouse.Extensions; using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Types.Serialization;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
@ -33,11 +33,7 @@ public class StatisticsController : ControllerBase
int totalSlotCount = await StatisticsHelper.SlotCountForGame(this.database, this.GetToken().GameVersion); int totalSlotCount = await StatisticsHelper.SlotCountForGame(this.database, this.GetToken().GameVersion);
int mmPicksCount = await StatisticsHelper.TeamPickCountForGame(this.database, this.GetToken().GameVersion); int mmPicksCount = await StatisticsHelper.TeamPickCountForGame(this.database, this.GetToken().GameVersion);
return this.Ok return this.Ok(new PlanetStatsResponse(totalSlotCount, mmPicksCount));
(
LbpSerializer.StringElement
("planetStats", LbpSerializer.StringElement("totalSlotCount", totalSlotCount) + LbpSerializer.StringElement("mmPicksCount", mmPicksCount))
);
} }
[HttpGet("planetStats/totalLevelCount")] [HttpGet("planetStats/totalLevelCount")]

View file

@ -4,12 +4,14 @@ using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Extensions; 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.Logging;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Users; 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.Logging;
using LBPUnion.ProjectLighthouse.Types.Serialization;
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;
@ -30,57 +32,41 @@ public class UserController : ControllerBase
this.database = database; this.database = database;
} }
private async Task<string?> getSerializedUser(string username, GameVersion gameVersion = GameVersion.LittleBigPlanet1)
{
User? user = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
return user?.Serialize(gameVersion);
}
private async Task<string?> getSerializedUserPicture(string username)
{
// use an anonymous type to only fetch certain columns
var partialUser = await this.database.Users.Where(u => u.Username == username)
.Select(u => new
{
u.Username,
u.IconHash,
})
.FirstOrDefaultAsync();
if (partialUser == null) return null;
string user = LbpSerializer.TaggedStringElement("npHandle", partialUser.Username, "icon", partialUser.IconHash);
return LbpSerializer.TaggedStringElement("user", user, "type", "user");
}
[HttpGet("user/{username}")] [HttpGet("user/{username}")]
public async Task<IActionResult> GetUser(string username) public async Task<IActionResult> GetUser(string username)
{ {
GameToken token = this.GetToken(); UserEntity? user = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
string? user = await this.getSerializedUser(username, token.GameVersion);
if (user == null) return this.NotFound(); if (user == null) return this.NotFound();
return this.Ok(user); return this.Ok(GameUser.CreateFromEntity(user, this.GetToken().GameVersion));
} }
[HttpGet("users")] [HttpGet("users")]
public async Task<IActionResult> GetUserAlt([FromQuery] string[] u) public async Task<IActionResult> GetUserAlt([FromQuery(Name = "u")] string[] userList)
{ {
List<string?> serializedUsers = new(); List<MinimalUserProfile> minimalUserList = new();
foreach (string userId in u) serializedUsers.Add(await this.getSerializedUserPicture(userId)); foreach (string username in userList)
{
MinimalUserProfile? profile = await this.database.Users.Where(u => u.Username == username)
.Select(u => new MinimalUserProfile
{
UserHandle = new NpHandle(u.Username, u.IconHash),
})
.FirstOrDefaultAsync();
if (profile == null) continue;
minimalUserList.Add(profile);
}
string serialized = serializedUsers.Aggregate(string.Empty, (current, user) => user == null ? current : current + user); return this.Ok(new MinimalUserListResponse(minimalUserList));
return this.Ok(LbpSerializer.StringElement("users", serialized));
} }
[HttpPost("updateUser")] [HttpPost("updateUser")]
public async Task<IActionResult> UpdateUser() public async Task<IActionResult> UpdateUser()
{ {
GameToken token = this.GetToken(); GameTokenEntity token = this.GetToken();
User? user = await this.database.UserFromGameToken(token); UserEntity? user = await this.database.UserFromGameToken(token);
if (user == null) return this.StatusCode(403, ""); if (user == null) return this.Forbid();
UserUpdate? update = await this.DeserializeBody<UserUpdate>("updateUser", "user"); UserUpdate? update = await this.DeserializeBody<UserUpdate>("updateUser", "user");
@ -118,17 +104,17 @@ public class UserController : ControllerBase
if (update.Slots != null) if (update.Slots != null)
{ {
update.Slots = update.Slots.Where(s => s.Type == SlotType.User)
.Where(s => s.Location != null)
.Where(s => s.SlotId != 0).ToList();
foreach (UserUpdateSlot? updateSlot in update.Slots) foreach (UserUpdateSlot? updateSlot in update.Slots)
{ {
// ReSharper disable once MergeIntoNegatedPattern SlotEntity? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == updateSlot.SlotId);
if (updateSlot.Type != SlotType.User || updateSlot.Location == null || updateSlot.SlotId == 0) continue;
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == updateSlot.SlotId);
if (slot == null) continue; if (slot == null) continue;
if (slot.CreatorId != token.UserId) continue; if (slot.CreatorId != token.UserId) continue;
slot.Location = updateSlot.Location; slot.Location = updateSlot.Location!;
} }
} }
@ -159,7 +145,11 @@ public class UserController : ControllerBase
case GameVersion.Unknown: case GameVersion.Unknown:
default: // The rest do not support custom earths. default: // The rest do not support custom earths.
{ {
throw new ArgumentException($"invalid gameVersion {token.GameVersion} for setting earth"); string bodyString = await this.ReadBodyAsync();
Logger.Warn($"User with invalid gameVersion '{token.GameVersion}' tried to set earth hash: \n" +
$"body: '{bodyString}'",
LogArea.Resources);
break;
} }
} }
} }
@ -170,10 +160,11 @@ public class UserController : ControllerBase
} }
[HttpPost("update_my_pins")] [HttpPost("update_my_pins")]
[Produces("text/json")]
public async Task<IActionResult> UpdateMyPins() public async Task<IActionResult> UpdateMyPins()
{ {
User? user = await this.database.UserFromGameToken(this.GetToken()); UserEntity? user = await this.database.UserFromGameToken(this.GetToken());
if (user == null) return this.StatusCode(403, ""); if (user == null) return this.Forbid();
string bodyString = await this.ReadBodyAsync(); string bodyString = await this.ReadBodyAsync();

View file

@ -18,7 +18,7 @@ public class SetLastContactMiddleware : MiddlewareDBContext
if (context.Request.Path.ToString().StartsWith("/LITTLEBIGPLANETPS3_XML")) if (context.Request.Path.ToString().StartsWith("/LITTLEBIGPLANETPS3_XML"))
{ {
// We begin by grabbing a token from the request, if this is a LBPPS3_XML request of course. // We begin by grabbing a token from the request, if this is a LBPPS3_XML request of course.
GameToken? gameToken = await database.GameTokenFromRequest(context.Request); GameTokenEntity? gameToken = await database.GameTokenFromRequest(context.Request);
if (gameToken?.GameVersion == GameVersion.LittleBigPlanet1) if (gameToken?.GameVersion == GameVersion.LittleBigPlanet1)
// Ignore UserFromGameToken null because user must exist for a token to exist // Ignore UserFromGameToken null because user must exist for a token to exist

View file

@ -42,7 +42,7 @@ public class GameServerStartup
( (
options => options =>
{ {
options.OutputFormatters.Add(new XmlOutputFormatter()); options.OutputFormatters.Add(new LbpOutputFormatter());
options.OutputFormatters.Add(new JsonOutputFormatter()); options.OutputFormatters.Add(new JsonOutputFormatter());
} }
); );

View file

@ -35,7 +35,7 @@ public class TokenAuthHandler : AuthenticationHandler<AuthenticationSchemeOption
{ {
if (!this.Context.Request.Cookies.ContainsKey(cookie)) return AuthenticateResult.Fail("No auth cookie"); if (!this.Context.Request.Cookies.ContainsKey(cookie)) return AuthenticateResult.Fail("No auth cookie");
GameToken? gameToken = await this.database.GameTokenFromRequest(this.Request); GameTokenEntity? gameToken = await this.database.GameTokenFromRequest(this.Request);
if (gameToken == null) return AuthenticateResult.Fail("No game token"); if (gameToken == null) return AuthenticateResult.Fail("No game token");
this.Context.Items["Token"] = gameToken; this.Context.Items["Token"] = gameToken;

View file

@ -20,6 +20,6 @@ public static class CategoryHelper
Categories.Add(new LuckyDipCategory()); Categories.Add(new LuckyDipCategory());
using DatabaseContext database = new(); using DatabaseContext database = new();
foreach (DatabaseCategory category in database.CustomCategories) Categories.Add(new CustomCategory(category)); foreach (DatabaseCategoryEntity category in database.CustomCategories) Categories.Add(new CustomCategory(category));
} }
} }

View file

@ -2,18 +2,19 @@
using System.Diagnostics; using System.Diagnostics;
using LBPUnion.ProjectLighthouse.Database; using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Serialization;
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.Logging; using LBPUnion.ProjectLighthouse.Types.Logging;
using LBPUnion.ProjectLighthouse.Types.Serialization;
using LBPUnion.ProjectLighthouse.Types.Users;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Categories; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Categories;
public abstract class CategoryWithUser : Category public abstract class CategoryWithUser : Category
{ {
public abstract Slot? GetPreviewSlot(DatabaseContext database, User user); public abstract SlotEntity? GetPreviewSlot(DatabaseContext database, UserEntity user);
public override Slot? GetPreviewSlot(DatabaseContext database) public override SlotEntity? GetPreviewSlot(DatabaseContext database)
{ {
#if DEBUG #if DEBUG
Logger.Error("tried to get preview slot without user on CategoryWithUser", LogArea.Category); Logger.Error("tried to get preview slot without user on CategoryWithUser", LogArea.Category);
@ -22,7 +23,7 @@ public abstract class CategoryWithUser : Category
return null; return null;
} }
public abstract int GetTotalSlots(DatabaseContext database, User user); public abstract int GetTotalSlots(DatabaseContext database, UserEntity user);
public override int GetTotalSlots(DatabaseContext database) public override int GetTotalSlots(DatabaseContext database)
{ {
#if DEBUG #if DEBUG
@ -32,14 +33,14 @@ public abstract class CategoryWithUser : Category
return -1; return -1;
} }
public abstract IQueryable<Slot> GetSlots(DatabaseContext database, User user, int pageStart, int pageSize); public abstract IQueryable<SlotEntity> GetSlots(DatabaseContext database, UserEntity user, int pageStart, int pageSize);
public override IList<Slot> GetSlots(DatabaseContext database, int pageStart, int pageSize) public override IList<SlotEntity> GetSlots(DatabaseContext database, int pageStart, int pageSize)
{ {
#if DEBUG #if DEBUG
Logger.Error("tried to get slots without user on CategoryWithUser", LogArea.Category); Logger.Error("tried to get slots without user on CategoryWithUser", LogArea.Category);
if (Debugger.IsAttached) Debugger.Break(); if (Debugger.IsAttached) Debugger.Break();
#endif #endif
return new List<Slot>(); return new List<SlotEntity>();
} }
public new string Serialize(DatabaseContext database) public new string Serialize(DatabaseContext database)
@ -48,35 +49,13 @@ public abstract class CategoryWithUser : Category
return string.Empty; return string.Empty;
} }
public string Serialize(DatabaseContext database, User user) public GameCategory Serialize(DatabaseContext database, UserEntity user)
{ {
Slot? previewSlot = this.GetPreviewSlot(database, user); List<SlotBase> slots = new()
{
string previewResults = ""; SlotBase.CreateFromEntity(this.GetPreviewSlot(database, user), GameVersion.LittleBigPlanet3, user.UserId),
if (previewSlot != null) };
previewResults = LbpSerializer.TaggedStringElement int totalSlots = this.GetTotalSlots(database, user);
( return GameCategory.CreateFromEntity(this, new GenericSlotResponse(slots, totalSlots, 2));
"results",
previewSlot.Serialize(),
new Dictionary<string, object>
{
{
"total", this.GetTotalSlots(database, user)
},
{
"hint_start", "2"
},
}
);
return LbpSerializer.StringElement
(
"category",
LbpSerializer.StringElement("name", this.Name) +
LbpSerializer.StringElement("description", this.Description) +
LbpSerializer.StringElement("url", this.IngameEndpoint) +
(previewSlot == null ? "" : previewResults) +
LbpSerializer.StringElement("icon", this.IconHash)
);
} }
} }

View file

@ -21,7 +21,7 @@ public class CustomCategory : Category
this.SlotIds = slotIds.ToList(); this.SlotIds = slotIds.ToList();
} }
public CustomCategory(DatabaseCategory category) public CustomCategory(DatabaseCategoryEntity category)
{ {
this.Name = category.Name; this.Name = category.Name;
this.Description = category.Description; this.Description = category.Description;
@ -35,8 +35,8 @@ public class CustomCategory : Category
public sealed override string Description { get; set; } public sealed override string Description { get; set; }
public sealed override string IconHash { get; set; } public sealed override string IconHash { get; set; }
public sealed override string Endpoint { get; set; } public sealed override string Endpoint { get; set; }
public override Slot? GetPreviewSlot(DatabaseContext database) => database.Slots.FirstOrDefault(s => s.SlotId == this.SlotIds[0]); public override SlotEntity? GetPreviewSlot(DatabaseContext database) => database.Slots.FirstOrDefault(s => s.SlotId == this.SlotIds[0]);
public override IQueryable<Slot> GetSlots public override IQueryable<SlotEntity> GetSlots
(DatabaseContext database, int pageStart, int pageSize) (DatabaseContext database, int pageStart, int pageSize)
=> database.Slots.ByGameVersion(GameVersion.LittleBigPlanet3).Where(s => this.SlotIds.Contains(s.SlotId)); => database.Slots.ByGameVersion(GameVersion.LittleBigPlanet3).Where(s => this.SlotIds.Contains(s.SlotId));
public override int GetTotalSlots(DatabaseContext database) => this.SlotIds.Count; public override int GetTotalSlots(DatabaseContext database) => this.SlotIds.Count;

View file

@ -15,7 +15,7 @@ public class HeartedCategory : CategoryWithUser
public override string Description { get; set; } = "Content you've hearted"; public override string Description { get; set; } = "Content you've hearted";
public override string IconHash { get; set; } = "g820611"; public override string IconHash { get; set; } = "g820611";
public override string Endpoint { get; set; } = "hearted"; public override string Endpoint { get; set; } = "hearted";
public override Slot? GetPreviewSlot(DatabaseContext database, User user) // note: developer slots act up in LBP3 when listed here, so I omitted it public override SlotEntity? GetPreviewSlot(DatabaseContext database, UserEntity user) // note: developer slots act up in LBP3 when listed here, so I omitted it
=> database.HeartedLevels.Where(h => h.UserId == user.UserId) => database.HeartedLevels.Where(h => h.UserId == user.UserId)
.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)
@ -24,7 +24,7 @@ public class HeartedCategory : CategoryWithUser
.ByGameVersion(GameVersion.LittleBigPlanet3, false, false, true) .ByGameVersion(GameVersion.LittleBigPlanet3, false, false, true)
.FirstOrDefault(); .FirstOrDefault();
public override IQueryable<Slot> GetSlots(DatabaseContext database, User user, int pageStart, int pageSize) public override IQueryable<SlotEntity> GetSlots(DatabaseContext database, UserEntity user, int pageStart, int pageSize)
=> database.HeartedLevels.Where(h => h.UserId == user.UserId) => database.HeartedLevels.Where(h => h.UserId == user.UserId)
.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)
@ -34,5 +34,5 @@ public class HeartedCategory : CategoryWithUser
.Skip(Math.Max(0, pageStart)) .Skip(Math.Max(0, pageStart))
.Take(Math.Min(pageSize, 20)); .Take(Math.Min(pageSize, 20));
public override int GetTotalSlots(DatabaseContext database, User user) => database.HeartedLevels.Count(h => h.UserId == user.UserId); public override int GetTotalSlots(DatabaseContext database, UserEntity user) => database.HeartedLevels.Count(h => h.UserId == user.UserId);
} }

View file

@ -14,8 +14,8 @@ public class HighestRatedCategory : Category
public override string Description { get; set; } = "Community Highest Rated content"; public override string Description { get; set; } = "Community Highest Rated content";
public override string IconHash { get; set; } = "g820603"; public override string IconHash { get; set; } = "g820603";
public override string Endpoint { get; set; } = "thumbs"; public override string Endpoint { get; set; } = "thumbs";
public override Slot? GetPreviewSlot(DatabaseContext database) => database.Slots.Where(s => s.Type == SlotType.User).AsEnumerable().MaxBy(s => s.Thumbsup); public override SlotEntity? GetPreviewSlot(DatabaseContext database) => database.Slots.Where(s => s.Type == SlotType.User).AsEnumerable().MaxBy(s => s.Thumbsup);
public override IEnumerable<Slot> GetSlots public override IEnumerable<SlotEntity> GetSlots
(DatabaseContext database, int pageStart, int pageSize) (DatabaseContext database, int pageStart, int pageSize)
=> database.Slots.ByGameVersion(GameVersion.LittleBigPlanet3, false, true) => database.Slots.ByGameVersion(GameVersion.LittleBigPlanet3, false, true)
.AsEnumerable() .AsEnumerable()

View file

@ -14,8 +14,8 @@ public class LuckyDipCategory : Category
public override string Description { get; set; } = "Randomized uploaded content"; public override string Description { get; set; } = "Randomized uploaded content";
public override string IconHash { get; set; } = "g820605"; public override string IconHash { get; set; } = "g820605";
public override string Endpoint { get; set; } = "lbp2luckydip"; public override string Endpoint { get; set; } = "lbp2luckydip";
public override Slot? GetPreviewSlot(DatabaseContext database) => database.Slots.Where(s => s.Type == SlotType.User).OrderByDescending(_ => EF.Functions.Random()).FirstOrDefault(); public override SlotEntity? GetPreviewSlot(DatabaseContext database) => database.Slots.Where(s => s.Type == SlotType.User).OrderByDescending(_ => EF.Functions.Random()).FirstOrDefault();
public override IQueryable<Slot> GetSlots public override IQueryable<SlotEntity> GetSlots
(DatabaseContext database, int pageStart, int pageSize) (DatabaseContext database, int pageStart, int pageSize)
=> database.Slots.ByGameVersion(GameVersion.LittleBigPlanet3, false, true) => database.Slots.ByGameVersion(GameVersion.LittleBigPlanet3, false, true)
.OrderByDescending(_ => EF.Functions.Random()) .OrderByDescending(_ => EF.Functions.Random())

View file

@ -14,8 +14,8 @@ public class MostHeartedCategory : Category
public override string Description { get; set; } = "The Most Hearted Content"; public override string Description { get; set; } = "The Most Hearted Content";
public override string IconHash { get; set; } = "g820607"; public override string IconHash { get; set; } = "g820607";
public override string Endpoint { get; set; } = "mostHearted"; public override string Endpoint { get; set; } = "mostHearted";
public override Slot? GetPreviewSlot(DatabaseContext database) => database.Slots.Where(s => s.Type == SlotType.User).AsEnumerable().MaxBy(s => s.Hearts); public override SlotEntity? GetPreviewSlot(DatabaseContext database) => database.Slots.Where(s => s.Type == SlotType.User).AsEnumerable().MaxBy(s => s.Hearts);
public override IEnumerable<Slot> GetSlots public override IEnumerable<SlotEntity> GetSlots
(DatabaseContext database, int pageStart, int pageSize) (DatabaseContext database, int pageStart, int pageSize)
=> database.Slots.ByGameVersion(GameVersion.LittleBigPlanet3, false, true) => database.Slots.ByGameVersion(GameVersion.LittleBigPlanet3, false, true)
.AsEnumerable() .AsEnumerable()

View file

@ -13,12 +13,12 @@ public class MostPlayedCategory : Category
public override string Description { get; set; } = "The most played content"; public override string Description { get; set; } = "The most played content";
public override string IconHash { get; set; } = "g820608"; public override string IconHash { get; set; } = "g820608";
public override string Endpoint { get; set; } = "mostUniquePlays"; public override string Endpoint { get; set; } = "mostUniquePlays";
public override Slot? GetPreviewSlot(DatabaseContext database) => database.Slots public override SlotEntity? GetPreviewSlot(DatabaseContext database) => database.Slots
.Where(s => s.Type == SlotType.User) .Where(s => s.Type == SlotType.User)
.OrderByDescending(s => s.PlaysLBP1Unique + s.PlaysLBP2Unique + s.PlaysLBP3Unique) .OrderByDescending(s => s.PlaysLBP1Unique + s.PlaysLBP2Unique + s.PlaysLBP3Unique)
.ThenByDescending(s => s.PlaysLBP1 + s.PlaysLBP2 + s.PlaysLBP3) .ThenByDescending(s => s.PlaysLBP1 + s.PlaysLBP2 + s.PlaysLBP3)
.FirstOrDefault(); .FirstOrDefault();
public override IQueryable<Slot> GetSlots public override IQueryable<SlotEntity> GetSlots
(DatabaseContext database, int pageStart, int pageSize) (DatabaseContext database, int pageStart, int pageSize)
=> database.Slots.ByGameVersion(GameVersion.LittleBigPlanet3, false, true) => database.Slots.ByGameVersion(GameVersion.LittleBigPlanet3, false, true)
.OrderByDescending(s => s.PlaysLBP1Unique + s.PlaysLBP2Unique + s.PlaysLBP3Unique) .OrderByDescending(s => s.PlaysLBP1Unique + s.PlaysLBP2Unique + s.PlaysLBP3Unique)

View file

@ -13,8 +13,8 @@ public class NewestLevelsCategory : Category
public override string Description { get; set; } = "The most recently published content"; public override string Description { get; set; } = "The most recently published content";
public override string IconHash { get; set; } = "g820623"; public override string IconHash { get; set; } = "g820623";
public override string Endpoint { get; set; } = "newest"; public override string Endpoint { get; set; } = "newest";
public override Slot? GetPreviewSlot(DatabaseContext database) => database.Slots.Where(s => s.Type == SlotType.User).OrderByDescending(s => s.FirstUploaded).FirstOrDefault(); public override SlotEntity? GetPreviewSlot(DatabaseContext database) => database.Slots.Where(s => s.Type == SlotType.User).OrderByDescending(s => s.FirstUploaded).FirstOrDefault();
public override IQueryable<Slot> GetSlots public override IQueryable<SlotEntity> GetSlots
(DatabaseContext database, int pageStart, int pageSize) (DatabaseContext database, int pageStart, int pageSize)
=> database.Slots.ByGameVersion(GameVersion.LittleBigPlanet3, false, true) => database.Slots.ByGameVersion(GameVersion.LittleBigPlanet3, false, true)
.OrderByDescending(s => s.FirstUploaded) .OrderByDescending(s => s.FirstUploaded)

View file

@ -15,7 +15,7 @@ public class QueueCategory : CategoryWithUser
public override string Description { get; set; } = "Your queued content"; public override string Description { get; set; } = "Your queued content";
public override string IconHash { get; set; } = "g820614"; public override string IconHash { get; set; } = "g820614";
public override string Endpoint { get; set; } = "queue"; public override string Endpoint { get; set; } = "queue";
public override Slot? GetPreviewSlot(DatabaseContext database, User user) public override SlotEntity? GetPreviewSlot(DatabaseContext database, UserEntity user)
=> database.QueuedLevels.Where(q => q.UserId == user.UserId) => database.QueuedLevels.Where(q => q.UserId == user.UserId)
.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)
@ -24,7 +24,7 @@ public class QueueCategory : CategoryWithUser
.ByGameVersion(GameVersion.LittleBigPlanet3, false, false, true) .ByGameVersion(GameVersion.LittleBigPlanet3, false, false, true)
.FirstOrDefault(); .FirstOrDefault();
public override IQueryable<Slot> GetSlots(DatabaseContext database, User user, int pageStart, int pageSize) public override IQueryable<SlotEntity> GetSlots(DatabaseContext database, UserEntity user, int pageStart, int pageSize)
=> database.QueuedLevels.Where(q => q.UserId == user.UserId) => database.QueuedLevels.Where(q => q.UserId == user.UserId)
.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)
@ -34,5 +34,5 @@ public class QueueCategory : CategoryWithUser
.Skip(Math.Max(0, pageStart - 1)) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 20)); .Take(Math.Min(pageSize, 20));
public override int GetTotalSlots(DatabaseContext database, User user) => database.QueuedLevels.Count(q => q.UserId == user.UserId); public override int GetTotalSlots(DatabaseContext database, UserEntity user) => database.QueuedLevels.Count(q => q.UserId == user.UserId);
} }

View file

@ -13,8 +13,8 @@ public class TeamPicksCategory : Category
public override string Description { get; set; } = "Community Team Picks"; public override string Description { get; set; } = "Community Team Picks";
public override string IconHash { get; set; } = "g820626"; public override string IconHash { get; set; } = "g820626";
public override string Endpoint { get; set; } = "team_picks"; public override string Endpoint { get; set; } = "team_picks";
public override Slot? GetPreviewSlot(DatabaseContext database) => database.Slots.OrderByDescending(s => s.FirstUploaded).FirstOrDefault(s => s.TeamPick); public override SlotEntity? GetPreviewSlot(DatabaseContext database) => database.Slots.OrderByDescending(s => s.FirstUploaded).FirstOrDefault(s => s.TeamPick);
public override IQueryable<Slot> GetSlots public override IQueryable<SlotEntity> GetSlots
(DatabaseContext database, int pageStart, int pageSize) (DatabaseContext database, int pageStart, int pageSize)
=> database.Slots.ByGameVersion(GameVersion.LittleBigPlanet3, false, true) => database.Slots.ByGameVersion(GameVersion.LittleBigPlanet3, false, true)
.OrderByDescending(s => s.FirstUploaded) .OrderByDescending(s => s.FirstUploaded)

View file

@ -1,4 +1,5 @@
using System.Xml.Serialization; using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Types.Serialization;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Misc; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Misc;
@ -9,7 +10,7 @@ namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Misc;
/// </summary> /// </summary>
[XmlRoot("resources")] [XmlRoot("resources")]
[XmlType("resources")] [XmlType("resources")]
public class ResourceList public class ResourceList : ILbpSerializable
{ {
[XmlElement("resource")] [XmlElement("resource")]
public string[]? Resources; public string[]? Resources;

View file

@ -1,25 +0,0 @@
using LBPUnion.ProjectLighthouse.Serialization;
using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Users;
[Keyless]
public class ClientsConnected
{
public bool Lbp1 { get; set; }
public bool Lbp2 { get; set; }
public bool LbpMe { get; set; }
public bool Lbp3Ps3 { get; set; }
public bool Lbp3Ps4 { get; set; }
public string Serialize()
=> LbpSerializer.StringElement
(
"clientsConnected",
LbpSerializer.StringElement("lbp1", this.Lbp1) +
LbpSerializer.StringElement("lbp2", this.Lbp2) +
LbpSerializer.StringElement("lbpme", this.LbpMe) +
LbpSerializer.StringElement("lbp3ps3", this.Lbp3Ps3) +
LbpSerializer.StringElement("lbp3ps4", this.Lbp3Ps4)
);
}

View file

@ -1,24 +1,16 @@
#nullable enable #nullable enable
using System.Xml.Serialization; using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Types.Serialization;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Users; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Users;
[XmlRoot("privacySettings")] [XmlRoot("privacySettings")]
[XmlType("privacySettings")] [XmlType("privacySettings")]
public class PrivacySettings public class PrivacySettings : ILbpSerializable
{ {
[XmlElement("levelVisiblity")] [XmlElement("levelVisiblity")]
public string? LevelVisibility { get; set; } public string? LevelVisibility { get; set; }
[XmlElement("profileVisiblity")] [XmlElement("profileVisiblity")]
public string? ProfileVisibility { get; set; } public string? ProfileVisibility { get; set; }
public string Serialize()
=> LbpSerializer.StringElement
(
"privacySettings",
LbpSerializer.StringElement("levelVisibility", this.LevelVisibility) +
LbpSerializer.StringElement("profileVisibility", this.ProfileVisibility)
);
} }

View file

@ -22,10 +22,10 @@ public class AdminReportController : ControllerBase
[HttpGet("remove")] [HttpGet("remove")]
public async Task<IActionResult> DeleteReport([FromRoute] int id) public async Task<IActionResult> DeleteReport([FromRoute] int id)
{ {
User? user = this.database.UserFromWebRequest(this.Request); UserEntity? user = this.database.UserFromWebRequest(this.Request);
if (user == null || !user.IsAdmin) return this.StatusCode(403, ""); if (user == null || !user.IsAdmin) return this.Forbid();
GriefReport? report = await this.database.Reports.FirstOrDefaultAsync(r => r.ReportId == id); GriefReportEntity? report = await this.database.Reports.FirstOrDefaultAsync(r => r.ReportId == id);
if (report == null) return this.NotFound(); if (report == null) return this.NotFound();
List<string> hashes = new() List<string> hashes = new()
@ -49,10 +49,10 @@ public class AdminReportController : ControllerBase
[HttpGet("dismiss")] [HttpGet("dismiss")]
public async Task<IActionResult> DismissReport([FromRoute] int id) public async Task<IActionResult> DismissReport([FromRoute] int id)
{ {
User? user = this.database.UserFromWebRequest(this.Request); UserEntity? user = this.database.UserFromWebRequest(this.Request);
if (user == null || !user.IsModerator) return this.StatusCode(403, ""); if (user == null || !user.IsModerator) return this.Forbid();
GriefReport? report = await this.database.Reports.FirstOrDefaultAsync(r => r.ReportId == id); GriefReportEntity? report = await this.database.Reports.FirstOrDefaultAsync(r => r.ReportId == id);
if (report == null) return this.NotFound(); if (report == null) return this.NotFound();
FileHelper.DeleteResource(report.JpegHash); FileHelper.DeleteResource(report.JpegHash);

View file

@ -28,10 +28,10 @@ public class AdminUserController : ControllerBase
/// </summary> /// </summary>
[HttpGet("wipePlanets")] [HttpGet("wipePlanets")]
public async Task<IActionResult> WipePlanets([FromRoute] int id) { public async Task<IActionResult> WipePlanets([FromRoute] int id) {
User? user = this.database.UserFromWebRequest(this.Request); UserEntity? user = this.database.UserFromWebRequest(this.Request);
if (user == null || !user.IsModerator) return this.NotFound(); if (user == null || !user.IsModerator) return this.NotFound();
User? targetedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id); UserEntity? targetedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id);
if (targetedUser == null) return this.NotFound(); if (targetedUser == null) return this.NotFound();
string[] hashes = { string[] hashes = {
@ -47,7 +47,7 @@ public class AdminUserController : ControllerBase
if (string.IsNullOrWhiteSpace(hash)) continue; if (string.IsNullOrWhiteSpace(hash)) continue;
// Find users with a matching hash // Find users with a matching hash
List<User> users = await this.database.Users List<UserEntity> users = await this.database.Users
.Where(u => u.PlanetHashLBP2 == hash || .Where(u => u.PlanetHashLBP2 == hash ||
u.PlanetHashLBP3 == hash || u.PlanetHashLBP3 == hash ||
u.PlanetHashLBPVita == hash) u.PlanetHashLBPVita == hash)
@ -57,7 +57,7 @@ public class AdminUserController : ControllerBase
System.Diagnostics.Debug.Assert(users.Count != 0); System.Diagnostics.Debug.Assert(users.Count != 0);
// Reset each users' hash. // Reset each users' hash.
foreach (User userWithPlanet in users) foreach (UserEntity userWithPlanet in users)
{ {
userWithPlanet.PlanetHashLBP2 = ""; userWithPlanet.PlanetHashLBP2 = "";
userWithPlanet.PlanetHashLBP3 = ""; userWithPlanet.PlanetHashLBP3 = "";
@ -92,10 +92,10 @@ public class AdminUserController : ControllerBase
[HttpPost("/admin/user/{id:int}/setPermissionLevel")] [HttpPost("/admin/user/{id:int}/setPermissionLevel")]
public async Task<IActionResult> SetUserPermissionLevel([FromRoute] int id, [FromForm] PermissionLevel role) public async Task<IActionResult> SetUserPermissionLevel([FromRoute] int id, [FromForm] PermissionLevel role)
{ {
User? user = this.database.UserFromWebRequest(this.Request); UserEntity? user = this.database.UserFromWebRequest(this.Request);
if (user == null || !user.IsAdmin) return this.NotFound(); if (user == null || !user.IsAdmin) return this.NotFound();
User? targetedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id); UserEntity? targetedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id);
if (targetedUser == null) return this.NotFound(); if (targetedUser == null) return this.NotFound();
if (role != PermissionLevel.Banned) if (role != PermissionLevel.Banned)

View file

@ -22,7 +22,7 @@ public class AuthenticationController : ControllerBase
[HttpGet("unlink/{platform}")] [HttpGet("unlink/{platform}")]
public async Task<IActionResult> UnlinkPlatform(string platform) public async Task<IActionResult> UnlinkPlatform(string platform)
{ {
User? user = this.database.UserFromWebRequest(this.Request); UserEntity? user = this.database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("~/login"); if (user == null) return this.Redirect("~/login");
Platform[] invalidTokens; Platform[] invalidTokens;
@ -48,10 +48,10 @@ public class AuthenticationController : ControllerBase
[HttpGet("approve/{id:int}")] [HttpGet("approve/{id:int}")]
public async Task<IActionResult> Approve(int id) public async Task<IActionResult> Approve(int id)
{ {
User? user = this.database.UserFromWebRequest(this.Request); UserEntity? user = this.database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("/login"); if (user == null) return this.Redirect("/login");
PlatformLinkAttempt? linkAttempt = await this.database.PlatformLinkAttempts PlatformLinkAttemptEntity? linkAttempt = await this.database.PlatformLinkAttempts
.FirstOrDefaultAsync(l => l.PlatformLinkAttemptId == id); .FirstOrDefaultAsync(l => l.PlatformLinkAttemptId == id);
if (linkAttempt == null) return this.NotFound(); if (linkAttempt == null) return this.NotFound();
@ -76,10 +76,10 @@ public class AuthenticationController : ControllerBase
[HttpGet("deny/{id:int}")] [HttpGet("deny/{id:int}")]
public async Task<IActionResult> Deny(int id) public async Task<IActionResult> Deny(int id)
{ {
User? user = this.database.UserFromWebRequest(this.Request); UserEntity? user = this.database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("/login"); if (user == null) return this.Redirect("/login");
PlatformLinkAttempt? linkAttempt = await this.database.PlatformLinkAttempts PlatformLinkAttemptEntity? linkAttempt = await this.database.PlatformLinkAttempts
.FirstOrDefaultAsync(l => l.PlatformLinkAttemptId == id); .FirstOrDefaultAsync(l => l.PlatformLinkAttemptId == id);
if (linkAttempt == null) return this.NotFound(); if (linkAttempt == null) return this.NotFound();

View file

@ -20,10 +20,10 @@ public class ModerationCaseController : ControllerBase
[HttpGet("dismiss")] [HttpGet("dismiss")]
public async Task<IActionResult> DismissCase([FromRoute] int id) public async Task<IActionResult> DismissCase([FromRoute] int id)
{ {
User? user = this.database.UserFromWebRequest(this.Request); UserEntity? user = this.database.UserFromWebRequest(this.Request);
if (user == null || !user.IsModerator) return this.StatusCode(403, ""); if (user == null || !user.IsModerator) return this.Forbid();
ModerationCase? @case = await this.database.Cases.FirstOrDefaultAsync(c => c.CaseId == id); ModerationCaseEntity? @case = await this.database.Cases.FirstOrDefaultAsync(c => c.CaseId == id);
if (@case == null) return this.NotFound(); if (@case == null) return this.NotFound();
@case.DismissedAt = DateTime.Now; @case.DismissedAt = DateTime.Now;

View file

@ -1,6 +1,7 @@
using LBPUnion.ProjectLighthouse.Database; 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.Serialization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -17,9 +18,9 @@ public class ModerationRemovalController : ControllerBase
this.database = database; this.database = database;
} }
private async Task<IActionResult> Delete<T>(DbSet<T> dbSet, int id, string? callbackUrl, Func<User, int, Task<T?>> getHandler) where T: class private async Task<IActionResult> Delete<T>(DbSet<T> dbSet, int id, string? callbackUrl, Func<UserEntity, int, Task<T?>> getHandler) where T: class
{ {
User? user = this.database.UserFromWebRequest(this.Request); UserEntity? user = this.database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("~/login"); if (user == null) return this.Redirect("~/login");
T? item = await getHandler(user, id); T? item = await getHandler(user, id);
@ -34,9 +35,9 @@ public class ModerationRemovalController : ControllerBase
[HttpGet("deleteScore/{scoreId:int}")] [HttpGet("deleteScore/{scoreId:int}")]
public async Task<IActionResult> DeleteScore(int scoreId, [FromQuery] string? callbackUrl) public async Task<IActionResult> DeleteScore(int scoreId, [FromQuery] string? callbackUrl)
{ {
return await this.Delete<Score>(this.database.Scores, scoreId, callbackUrl, async (user, id) => return await this.Delete<ScoreEntity>(this.database.Scores, scoreId, callbackUrl, async (user, id) =>
{ {
Score? score = await this.database.Scores.Include(s => s.Slot).FirstOrDefaultAsync(s => s.ScoreId == id); ScoreEntity? score = await this.database.Scores.Include(s => s.Slot).FirstOrDefaultAsync(s => s.ScoreId == id);
if (score == null) return null; if (score == null) return null;
return user.IsModerator ? score : null; return user.IsModerator ? score : null;
@ -46,10 +47,10 @@ public class ModerationRemovalController : ControllerBase
[HttpGet("deleteComment/{commentId:int}")] [HttpGet("deleteComment/{commentId:int}")]
public async Task<IActionResult> DeleteComment(int commentId, [FromQuery] string? callbackUrl) public async Task<IActionResult> DeleteComment(int commentId, [FromQuery] string? callbackUrl)
{ {
User? user = this.database.UserFromWebRequest(this.Request); UserEntity? user = this.database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("~/login"); if (user == null) return this.Redirect("~/login");
Comment? comment = await this.database.Comments.FirstOrDefaultAsync(c => c.CommentId == commentId); CommentEntity? comment = await this.database.Comments.FirstOrDefaultAsync(c => c.CommentId == commentId);
if (comment == null) return this.Redirect("~/404"); if (comment == null) return this.Redirect("~/404");
if (comment.Deleted) return this.Redirect(callbackUrl ?? "~/"); if (comment.Deleted) return this.Redirect(callbackUrl ?? "~/");
@ -82,10 +83,10 @@ public class ModerationRemovalController : ControllerBase
[HttpGet("deleteReview/{reviewId:int}")] [HttpGet("deleteReview/{reviewId:int}")]
public async Task<IActionResult> DeleteReview(int reviewId, [FromQuery] string? callbackUrl) public async Task<IActionResult> DeleteReview(int reviewId, [FromQuery] string? callbackUrl)
{ {
User? user = this.database.UserFromWebRequest(this.Request); UserEntity? user = this.database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("~/login"); if (user == null) return this.Redirect("~/login");
Review? review = await this.database.Reviews.Include(r => r.Slot).FirstOrDefaultAsync(c => c.ReviewId == reviewId); ReviewEntity? review = await this.database.Reviews.Include(r => r.Slot).FirstOrDefaultAsync(c => c.ReviewId == reviewId);
if (review == null) return this.Redirect("~/404"); if (review == null) return this.Redirect("~/404");
if (review.Deleted) return this.Redirect(callbackUrl ?? "~/"); if (review.Deleted) return this.Redirect(callbackUrl ?? "~/");
@ -103,9 +104,9 @@ public class ModerationRemovalController : ControllerBase
[HttpGet("deletePhoto/{photoId:int}")] [HttpGet("deletePhoto/{photoId:int}")]
public async Task<IActionResult> DeletePhoto(int photoId, [FromQuery] string? callbackUrl) public async Task<IActionResult> DeletePhoto(int photoId, [FromQuery] string? callbackUrl)
{ {
return await this.Delete<Photo>(this.database.Photos, photoId, callbackUrl, async (user, id) => return await this.Delete<PhotoEntity>(this.database.Photos, photoId, callbackUrl, async (user, id) =>
{ {
Photo? photo = await this.database.Photos.Include(p => p.Slot).FirstOrDefaultAsync(p => p.PhotoId == id); PhotoEntity? photo = await this.database.Photos.Include(p => p.Slot).FirstOrDefaultAsync(p => p.PhotoId == id);
if (photo == null) return null; if (photo == null) return null;
if (!user.IsModerator && photo.CreatorId != user.UserId) return null; if (!user.IsModerator && photo.CreatorId != user.UserId) return null;

View file

@ -23,10 +23,10 @@ public class ModerationSlotController : ControllerBase
[HttpGet("teamPick")] [HttpGet("teamPick")]
public async Task<IActionResult> TeamPick([FromRoute] int id) public async Task<IActionResult> TeamPick([FromRoute] int id)
{ {
User? user = this.database.UserFromWebRequest(this.Request); UserEntity? user = this.database.UserFromWebRequest(this.Request);
if (user == null || !user.IsModerator) return this.StatusCode(403, ""); if (user == null || !user.IsModerator) return this.Forbid();
Slot? slot = await this.database.Slots.Include(s => s.Creator).FirstOrDefaultAsync(s => s.SlotId == id); SlotEntity? slot = await this.database.Slots.Include(s => s.Creator).FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound(); if (slot == null) return this.NotFound();
slot.TeamPick = true; slot.TeamPick = true;
@ -42,10 +42,10 @@ public class ModerationSlotController : ControllerBase
[HttpGet("removeTeamPick")] [HttpGet("removeTeamPick")]
public async Task<IActionResult> RemoveTeamPick([FromRoute] int id) public async Task<IActionResult> RemoveTeamPick([FromRoute] int id)
{ {
User? user = this.database.UserFromWebRequest(this.Request); UserEntity? user = this.database.UserFromWebRequest(this.Request);
if (user == null || !user.IsModerator) return this.StatusCode(403, ""); if (user == null || !user.IsModerator) return this.Forbid();
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); SlotEntity? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound(); if (slot == null) return this.NotFound();
slot.TeamPick = false; slot.TeamPick = false;
@ -58,10 +58,10 @@ public class ModerationSlotController : ControllerBase
[HttpGet("delete")] [HttpGet("delete")]
public async Task<IActionResult> DeleteLevel([FromRoute] int id) public async Task<IActionResult> DeleteLevel([FromRoute] int id)
{ {
User? user = this.database.UserFromWebRequest(this.Request); UserEntity? user = this.database.UserFromWebRequest(this.Request);
if (user == null || !user.IsModerator) return this.StatusCode(403, ""); if (user == null || !user.IsModerator) return this.Forbid();
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); SlotEntity? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.Ok(); if (slot == null) return this.Ok();
await this.database.RemoveSlot(slot); await this.database.RemoveSlot(slot);

View file

@ -30,10 +30,10 @@ public class SlotPageController : ControllerBase
[HttpGet("unpublish")] [HttpGet("unpublish")]
public async Task<IActionResult> UnpublishSlot([FromRoute] int id) public async Task<IActionResult> UnpublishSlot([FromRoute] int id)
{ {
WebToken? token = this.database.WebTokenFromRequest(this.Request); WebTokenEntity? 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.FirstOrDefaultAsync(s => s.SlotId == id); SlotEntity? 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.CreatorId != token.UserId) return this.Redirect("~/slot/" + id); if (targetSlot.CreatorId != token.UserId) return this.Redirect("~/slot/" + id);
@ -48,7 +48,7 @@ public class SlotPageController : ControllerBase
[HttpGet("rateComment")] [HttpGet("rateComment")]
public async Task<IActionResult> RateComment([FromRoute] int id, [FromQuery] int commentId, [FromQuery] int rating) public async Task<IActionResult> RateComment([FromRoute] int id, [FromQuery] int commentId, [FromQuery] int rating)
{ {
WebToken? token = this.database.WebTokenFromRequest(this.Request); WebTokenEntity? token = this.database.WebTokenFromRequest(this.Request);
if (token == null) return this.Redirect("~/login"); if (token == null) return this.Redirect("~/login");
await this.database.RateComment(token.UserId, commentId, rating); await this.database.RateComment(token.UserId, commentId, rating);
@ -59,7 +59,7 @@ public class SlotPageController : ControllerBase
[HttpPost("postComment")] [HttpPost("postComment")]
public async Task<IActionResult> PostComment([FromRoute] int id, [FromForm] string? msg) public async Task<IActionResult> PostComment([FromRoute] int id, [FromForm] string? msg)
{ {
WebToken? token = this.database.WebTokenFromRequest(this.Request); WebTokenEntity? token = this.database.WebTokenFromRequest(this.Request);
if (token == null) return this.Redirect("~/login"); if (token == null) return this.Redirect("~/login");
if (msg == null) if (msg == null)
@ -90,10 +90,10 @@ public class SlotPageController : ControllerBase
{ {
if (string.IsNullOrEmpty(callbackUrl)) callbackUrl = "~/slot/" + id; if (string.IsNullOrEmpty(callbackUrl)) callbackUrl = "~/slot/" + id;
WebToken? token = this.database.WebTokenFromRequest(this.Request); WebTokenEntity? token = this.database.WebTokenFromRequest(this.Request);
if (token == null) return this.Redirect("~/login"); if (token == null) return this.Redirect("~/login");
Slot? heartedSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); SlotEntity? heartedSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (heartedSlot == null) return this.NotFound(); if (heartedSlot == null) return this.NotFound();
await this.database.HeartLevel(token.UserId, heartedSlot); await this.database.HeartLevel(token.UserId, heartedSlot);
@ -106,10 +106,10 @@ public class SlotPageController : ControllerBase
{ {
if (string.IsNullOrEmpty(callbackUrl)) callbackUrl = "~/slot/" + id; if (string.IsNullOrEmpty(callbackUrl)) callbackUrl = "~/slot/" + id;
WebToken? token = this.database.WebTokenFromRequest(this.Request); WebTokenEntity? token = this.database.WebTokenFromRequest(this.Request);
if (token == null) return this.Redirect("~/login"); if (token == null) return this.Redirect("~/login");
Slot? heartedSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); SlotEntity? heartedSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (heartedSlot == null) return this.NotFound(); if (heartedSlot == null) return this.NotFound();
await this.database.UnheartLevel(token.UserId, heartedSlot); await this.database.UnheartLevel(token.UserId, heartedSlot);
@ -122,10 +122,10 @@ public class SlotPageController : ControllerBase
{ {
if (string.IsNullOrEmpty(callbackUrl)) callbackUrl = "~/slot/" + id; if (string.IsNullOrEmpty(callbackUrl)) callbackUrl = "~/slot/" + id;
WebToken? token = this.database.WebTokenFromRequest(this.Request); WebTokenEntity? token = this.database.WebTokenFromRequest(this.Request);
if (token == null) return this.Redirect("~/login"); if (token == null) return this.Redirect("~/login");
Slot? queuedSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); SlotEntity? queuedSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (queuedSlot == null) return this.NotFound(); if (queuedSlot == null) return this.NotFound();
await this.database.QueueLevel(token.UserId, queuedSlot); await this.database.QueueLevel(token.UserId, queuedSlot);
@ -138,10 +138,10 @@ public class SlotPageController : ControllerBase
{ {
if (string.IsNullOrEmpty(callbackUrl)) callbackUrl = "~/slot/" + id; if (string.IsNullOrEmpty(callbackUrl)) callbackUrl = "~/slot/" + id;
WebToken? token = this.database.WebTokenFromRequest(this.Request); WebTokenEntity? token = this.database.WebTokenFromRequest(this.Request);
if (token == null) return this.Redirect("~/login"); if (token == null) return this.Redirect("~/login");
Slot? queuedSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); SlotEntity? queuedSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (queuedSlot == null) return this.NotFound(); if (queuedSlot == null) return this.NotFound();
await this.database.UnqueueLevel(token.UserId, queuedSlot); await this.database.UnqueueLevel(token.UserId, queuedSlot);

View file

@ -24,7 +24,7 @@ public class UserPageController : ControllerBase
[HttpGet("rateComment")] [HttpGet("rateComment")]
public async Task<IActionResult> RateComment([FromRoute] int id, [FromQuery] int? commentId, [FromQuery] int? rating) public async Task<IActionResult> RateComment([FromRoute] int id, [FromQuery] int? commentId, [FromQuery] int? rating)
{ {
WebToken? token = this.database.WebTokenFromRequest(this.Request); WebTokenEntity? token = this.database.WebTokenFromRequest(this.Request);
if (token == null) return this.Redirect("~/login"); if (token == null) return this.Redirect("~/login");
await this.database.RateComment(token.UserId, commentId.GetValueOrDefault(), rating.GetValueOrDefault()); await this.database.RateComment(token.UserId, commentId.GetValueOrDefault(), rating.GetValueOrDefault());
@ -35,7 +35,7 @@ public class UserPageController : ControllerBase
[HttpPost("postComment")] [HttpPost("postComment")]
public async Task<IActionResult> PostComment([FromRoute] int id, [FromForm] string? msg) public async Task<IActionResult> PostComment([FromRoute] int id, [FromForm] string? msg)
{ {
WebToken? token = this.database.WebTokenFromRequest(this.Request); WebTokenEntity? token = this.database.WebTokenFromRequest(this.Request);
if (token == null) return this.Redirect("~/login"); if (token == null) return this.Redirect("~/login");
if (msg == null) if (msg == null)
@ -64,10 +64,10 @@ public class UserPageController : ControllerBase
[HttpGet("heart")] [HttpGet("heart")]
public async Task<IActionResult> HeartUser([FromRoute] int id) public async Task<IActionResult> HeartUser([FromRoute] int id)
{ {
WebToken? token = this.database.WebTokenFromRequest(this.Request); WebTokenEntity? token = this.database.WebTokenFromRequest(this.Request);
if (token == null) return this.Redirect("~/login"); if (token == null) return this.Redirect("~/login");
User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id); UserEntity? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id);
if (heartedUser == null) return this.NotFound(); if (heartedUser == null) return this.NotFound();
await this.database.HeartUser(token.UserId, heartedUser); await this.database.HeartUser(token.UserId, heartedUser);
@ -78,10 +78,10 @@ public class UserPageController : ControllerBase
[HttpGet("unheart")] [HttpGet("unheart")]
public async Task<IActionResult> UnheartUser([FromRoute] int id) public async Task<IActionResult> UnheartUser([FromRoute] int id)
{ {
WebToken? token = this.database.WebTokenFromRequest(this.Request); WebTokenEntity? token = this.database.WebTokenFromRequest(this.Request);
if (token == null) return this.Redirect("~/login"); if (token == null) return this.Redirect("~/login");
User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id); UserEntity? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id);
if (heartedUser == null) return this.NotFound(); if (heartedUser == null) return this.NotFound();
await this.database.UnheartUser(token.UserId, heartedUser); await this.database.UnheartUser(token.UserId, heartedUser);
@ -92,10 +92,10 @@ public class UserPageController : ControllerBase
[HttpGet("block")] [HttpGet("block")]
public async Task<IActionResult> BlockUser([FromRoute] int id) public async Task<IActionResult> BlockUser([FromRoute] int id)
{ {
WebToken? token = this.database.WebTokenFromRequest(this.Request); WebTokenEntity? token = this.database.WebTokenFromRequest(this.Request);
if (token == null) return this.Redirect("~/login"); if (token == null) return this.Redirect("~/login");
User? blockedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id); UserEntity? blockedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id);
if (blockedUser == null) return this.NotFound(); if (blockedUser == null) return this.NotFound();
await this.database.BlockUser(token.UserId, blockedUser); await this.database.BlockUser(token.UserId, blockedUser);
@ -106,10 +106,10 @@ public class UserPageController : ControllerBase
[HttpGet("unblock")] [HttpGet("unblock")]
public async Task<IActionResult> UnblockUser([FromRoute] int id) public async Task<IActionResult> UnblockUser([FromRoute] int id)
{ {
WebToken? token = this.database.WebTokenFromRequest(this.Request); WebTokenEntity? token = this.database.WebTokenFromRequest(this.Request);
if (token == null) return this.Redirect("~/login"); if (token == null) return this.Redirect("~/login");
User? blockedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id); UserEntity? blockedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id);
if (blockedUser == null) return this.NotFound(); if (blockedUser == null) return this.NotFound();
await this.database.UnblockUser(token.UserId, blockedUser); await this.database.UnblockUser(token.UserId, blockedUser);

View file

@ -32,15 +32,15 @@ public static class PartialExtensions
} }
} }
public static Task<IHtmlContent> ToLink<T>(this User user, IHtmlHelper<T> helper, ViewDataDictionary<T> viewData, string language, string timeZone = "", bool includeStatus = false) public static Task<IHtmlContent> ToLink<T>(this UserEntity user, IHtmlHelper<T> helper, ViewDataDictionary<T> viewData, string language, string timeZone = "", bool includeStatus = false)
=> helper.PartialAsync("Partials/Links/UserLinkPartial", user, viewData.WithLang(language).WithTime(timeZone).WithKeyValue("IncludeStatus", includeStatus)); => helper.PartialAsync("Partials/Links/UserLinkPartial", user, viewData.WithLang(language).WithTime(timeZone).WithKeyValue("IncludeStatus", includeStatus));
public static Task<IHtmlContent> ToHtml<T> public static Task<IHtmlContent> ToHtml<T>
( (
this Slot slot, this SlotEntity slot,
IHtmlHelper<T> helper, IHtmlHelper<T> helper,
ViewDataDictionary<T> viewData, ViewDataDictionary<T> viewData,
User? user, UserEntity? user,
string callbackUrl, string callbackUrl,
string language = "", string language = "",
string timeZone = "", string timeZone = "",
@ -55,6 +55,6 @@ public static class PartialExtensions
.WithKeyValue("IsMini", isMini) .WithKeyValue("IsMini", isMini)
.WithKeyValue("IsMobile", isMobile)); .WithKeyValue("IsMobile", isMobile));
public static Task<IHtmlContent> ToHtml<T>(this Photo photo, IHtmlHelper<T> helper, ViewDataDictionary<T> viewData, string language, string timeZone, bool canDelete = false) public static Task<IHtmlContent> ToHtml<T>(this PhotoEntity photo, IHtmlHelper<T> helper, ViewDataDictionary<T> viewData, string language, string timeZone, bool canDelete = false)
=> helper.PartialAsync("Partials/PhotoPartial", photo, viewData.WithLang(language).WithTime(timeZone).CanDelete(canDelete)); => helper.PartialAsync("Partials/PhotoPartial", photo, viewData.WithLang(language).WithTime(timeZone).CanDelete(canDelete));
} }

View file

@ -14,14 +14,14 @@ public class UserRequiredRedirectMiddleware : MiddlewareDBContext
public override async Task InvokeAsync(HttpContext ctx, DatabaseContext database) public override async Task InvokeAsync(HttpContext ctx, DatabaseContext database)
{ {
WebToken? token = database.WebTokenFromRequest(ctx.Request); WebTokenEntity? token = database.WebTokenFromRequest(ctx.Request);
if (token == null || pathContains(ctx, "/logout")) if (token == null || pathContains(ctx, "/logout"))
{ {
await this.next(ctx); await this.next(ctx);
return; return;
} }
User? user = await database.Users.FirstOrDefaultAsync(u => u.UserId == token.UserId); UserEntity? user = await database.Users.FirstOrDefaultAsync(u => u.UserId == token.UserId);
if (user == null) if (user == null)
{ {
await this.next(ctx); await this.next(ctx);

View file

@ -38,7 +38,7 @@
} }
<div class="ui four column grid"> <div class="ui four column grid">
@foreach (ApiKey key in Model.ApiKeys) @foreach (ApiKeyEntity key in Model.ApiKeys)
{ {
<div id="keyitem-@key.Id" class="five wide column"> <div id="keyitem-@key.Id" class="five wide column">
<div class="ui blue segment"> <div class="ui blue segment">

View file

@ -9,7 +9,7 @@ namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages.Admin;
public class AdminApiKeyPageModel : BaseLayout public class AdminApiKeyPageModel : BaseLayout
{ {
public List<ApiKey> ApiKeys = new(); public List<ApiKeyEntity> ApiKeys = new();
public int KeyCount; public int KeyCount;
public AdminApiKeyPageModel(DatabaseContext database) : base(database) public AdminApiKeyPageModel(DatabaseContext database) : base(database)
@ -17,7 +17,7 @@ public class AdminApiKeyPageModel : BaseLayout
public async Task<IActionResult> OnGet() public async Task<IActionResult> OnGet()
{ {
User? user = this.Database.UserFromWebRequest(this.Request); UserEntity? user = this.Database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("~/login"); if (user == null) return this.Redirect("~/login");
if (!user.IsAdmin) return this.NotFound(); if (!user.IsAdmin) return this.NotFound();
@ -29,10 +29,10 @@ public class AdminApiKeyPageModel : BaseLayout
public async Task<IActionResult> OnPost(string keyId) public async Task<IActionResult> OnPost(string keyId)
{ {
User? user = this.Database.UserFromWebRequest(this.Request); UserEntity? user = this.Database.UserFromWebRequest(this.Request);
if (user == null || !user.IsAdmin) return this.NotFound(); if (user == null || !user.IsAdmin) return this.NotFound();
ApiKey? apiKey = await this.Database.APIKeys.FirstOrDefaultAsync(k => k.Id == int.Parse(keyId)); ApiKeyEntity? apiKey = await this.Database.APIKeys.FirstOrDefaultAsync(k => k.Id == int.Parse(keyId));
if (apiKey == null) return this.NotFound(); if (apiKey == null) return this.NotFound();
this.Database.APIKeys.Remove(apiKey); this.Database.APIKeys.Remove(apiKey);
await this.Database.SaveChangesAsync(); await this.Database.SaveChangesAsync();

View file

@ -24,7 +24,7 @@ public class AdminPanelPage : BaseLayout
public async Task<IActionResult> OnGet([FromQuery] string? args, [FromQuery] string? command, [FromQuery] string? maintenanceJob, [FromQuery] string? log) public async Task<IActionResult> OnGet([FromQuery] string? args, [FromQuery] string? command, [FromQuery] string? maintenanceJob, [FromQuery] string? log)
{ {
User? user = this.Database.UserFromWebRequest(this.Request); UserEntity? user = this.Database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("~/login"); if (user == null) return this.Redirect("~/login");
if (!user.IsAdmin) return this.NotFound(); if (!user.IsAdmin) return this.NotFound();

View file

@ -12,7 +12,7 @@
<p><b>Note:</b> Users are ordered by their permissions, then by most-recent-first.</p> <p><b>Note:</b> Users are ordered by their permissions, then by most-recent-first.</p>
<div class="ui grid"> <div class="ui grid">
@foreach (User user in Model.Users) @foreach (UserEntity user in Model.Users)
{ {
string color; string color;
string subtitle; string subtitle;

View file

@ -11,13 +11,13 @@ public class AdminPanelUsersPage : BaseLayout
{ {
public int UserCount; public int UserCount;
public List<User> Users = new(); public List<UserEntity> Users = new();
public AdminPanelUsersPage(DatabaseContext database) : base(database) public AdminPanelUsersPage(DatabaseContext database) : base(database)
{} {}
public async Task<IActionResult> OnGet() public async Task<IActionResult> OnGet()
{ {
User? user = this.Database.UserFromWebRequest(this.Request); UserEntity? user = this.Database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("~/login"); if (user == null) return this.Redirect("~/login");
if (!user.IsAdmin) return this.NotFound(); if (!user.IsAdmin) return this.NotFound();

View file

@ -12,11 +12,11 @@ public class AdminSetGrantedSlotsPage : BaseLayout
public AdminSetGrantedSlotsPage(DatabaseContext database) : base(database) public AdminSetGrantedSlotsPage(DatabaseContext database) : base(database)
{} {}
public User? TargetedUser; public UserEntity? TargetedUser;
public async Task<IActionResult> OnGet([FromRoute] int id) public async Task<IActionResult> OnGet([FromRoute] int id)
{ {
User? user = this.Database.UserFromWebRequest(this.Request); UserEntity? user = this.Database.UserFromWebRequest(this.Request);
if (user == null || !user.IsAdmin) return this.NotFound(); if (user == null || !user.IsAdmin) return this.NotFound();
this.TargetedUser = await this.Database.Users.FirstOrDefaultAsync(u => u.UserId == id); this.TargetedUser = await this.Database.Users.FirstOrDefaultAsync(u => u.UserId == id);
@ -27,7 +27,7 @@ public class AdminSetGrantedSlotsPage : BaseLayout
public async Task<IActionResult> OnPost([FromRoute] int id, int grantedSlotCount) public async Task<IActionResult> OnPost([FromRoute] int id, int grantedSlotCount)
{ {
User? user = this.Database.UserFromWebRequest(this.Request); UserEntity? user = this.Database.UserFromWebRequest(this.Request);
if (user == null || !user.IsAdmin) return this.NotFound(); if (user == null || !user.IsAdmin) return this.NotFound();
this.TargetedUser = await this.Database.Users.FirstOrDefaultAsync(u => u.UserId == id); this.TargetedUser = await this.Database.Users.FirstOrDefaultAsync(u => u.UserId == id);

View file

@ -81,7 +81,7 @@
} }
<p>@room.PlayerIds.Count players, state is @room.State, version is @room.RoomVersion.ToPrettyString() on platform @room.RoomPlatform</p> <p>@room.PlayerIds.Count players, state is @room.State, version is @room.RoomVersion.ToPrettyString() on platform @room.RoomPlatform</p>
<p>Slot type: @room.Slot.SlotType, slot id: @room.Slot.SlotId</p> <p>Slot type: @room.Slot.SlotType, slot id: @room.Slot.SlotId</p>
@foreach (User player in room.GetPlayers(Model.Database)) @foreach (UserEntity player in room.GetPlayers(Model.Database))
{ {
<div class="ui segment">@player.Username</div> <div class="ui segment">@player.Username</div>
} }

View file

@ -17,7 +17,7 @@ public class RoomVisualizerPage : BaseLayout
public IActionResult OnGet() public IActionResult OnGet()
{ {
#if !DEBUG #if !DEBUG
User? user = this.Database.UserFromWebRequest(this.Request); UserEntity? user = this.Database.UserFromWebRequest(this.Request);
if (user == null || !user.IsAdmin) return this.NotFound(); if (user == null || !user.IsAdmin) return this.NotFound();
#endif #endif

View file

@ -21,14 +21,14 @@ public class CompleteEmailVerificationPage : BaseLayout
{ {
if (!ServerConfiguration.Instance.Mail.MailEnabled) return this.NotFound(); if (!ServerConfiguration.Instance.Mail.MailEnabled) return this.NotFound();
EmailVerificationToken? emailVerifyToken = await this.Database.EmailVerificationTokens.FirstOrDefaultAsync(e => e.EmailToken == token); EmailVerificationTokenEntity? emailVerifyToken = await this.Database.EmailVerificationTokens.FirstOrDefaultAsync(e => e.EmailToken == token);
if (emailVerifyToken == null) if (emailVerifyToken == null)
{ {
this.Error = "Invalid verification token"; this.Error = "Invalid verification token";
return this.Page(); return this.Page();
} }
User user = await this.Database.Users.FirstAsync(u => u.UserId == emailVerifyToken.UserId); UserEntity user = await this.Database.Users.FirstAsync(u => u.UserId == emailVerifyToken.UserId);
if (DateTime.Now > emailVerifyToken.ExpiresAt) if (DateTime.Now > emailVerifyToken.ExpiresAt)
{ {
@ -50,7 +50,7 @@ public class CompleteEmailVerificationPage : BaseLayout
if (user.Password != null) return this.Page(); if (user.Password != null) return this.Page();
// if user's account was created automatically // if user's account was created automatically
WebToken webToken = new() WebTokenEntity webToken = new()
{ {
ExpiresAt = DateTime.Now.AddDays(7), ExpiresAt = DateTime.Now.AddDays(7),
Verified = true, Verified = true,

View file

@ -19,7 +19,7 @@ public class SendVerificationEmailPage : BaseLayout
{ {
if (!ServerConfiguration.Instance.Mail.MailEnabled) return this.NotFound(); if (!ServerConfiguration.Instance.Mail.MailEnabled) return this.NotFound();
User? user = this.Database.UserFromWebRequest(this.Request); UserEntity? user = this.Database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("/login"); if (user == null) return this.Redirect("/login");
if (user.EmailAddressVerified) return this.Redirect("/"); if (user.EmailAddressVerified) return this.Redirect("/");

View file

@ -22,7 +22,7 @@ public class SetEmailForm : BaseLayout
public IActionResult OnGet() public IActionResult OnGet()
{ {
if (!ServerConfiguration.Instance.Mail.MailEnabled) return this.NotFound(); if (!ServerConfiguration.Instance.Mail.MailEnabled) return this.NotFound();
WebToken? token = this.Database.WebTokenFromRequest(this.Request); WebTokenEntity? token = this.Database.WebTokenFromRequest(this.Request);
if (token == null) return this.Redirect("/login"); if (token == null) return this.Redirect("/login");
return this.Page(); return this.Page();
@ -33,10 +33,10 @@ public class SetEmailForm : BaseLayout
{ {
if (!ServerConfiguration.Instance.Mail.MailEnabled) return this.NotFound(); if (!ServerConfiguration.Instance.Mail.MailEnabled) return this.NotFound();
WebToken? token = this.Database.WebTokenFromRequest(this.Request); WebTokenEntity? token = this.Database.WebTokenFromRequest(this.Request);
if (token == null) return this.Redirect("~/login"); if (token == null) return this.Redirect("~/login");
User? user = await this.Database.Users.FirstOrDefaultAsync(u => u.UserId == token.UserId); UserEntity? user = await this.Database.Users.FirstOrDefaultAsync(u => u.UserId == token.UserId);
if (user == null) return this.Redirect("~/login"); if (user == null) return this.Redirect("~/login");
if (!SanitizationHelper.IsValidEmail(emailAddress)) if (!SanitizationHelper.IsValidEmail(emailAddress))

View file

@ -22,7 +22,7 @@ else
} }
} }
@foreach (PlatformLinkAttempt authAttempt in Model.LinkAttempts) @foreach (PlatformLinkAttemptEntity authAttempt in Model.LinkAttempts)
{ {
DateTimeOffset timestamp = TimeZoneInfo.ConvertTime(DateTimeOffset.FromUnixTimeMilliseconds(authAttempt.Timestamp), timeZoneInfo); DateTimeOffset timestamp = TimeZoneInfo.ConvertTime(DateTimeOffset.FromUnixTimeMilliseconds(authAttempt.Timestamp), timeZoneInfo);
<div class="ui red segment"> <div class="ui red segment">

View file

@ -10,7 +10,7 @@ namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages.ExternalAuth;
public class AuthenticationPage : BaseLayout public class AuthenticationPage : BaseLayout
{ {
public List<PlatformLinkAttempt> LinkAttempts = new(); public List<PlatformLinkAttemptEntity> LinkAttempts = new();
public IPAddress? IpAddress; public IPAddress? IpAddress;
public AuthenticationPage(DatabaseContext database) : base(database) public AuthenticationPage(DatabaseContext database) : base(database)
@ -18,7 +18,7 @@ public class AuthenticationPage : BaseLayout
public IActionResult OnGet() public IActionResult OnGet()
{ {
if (this.User == null) return this.StatusCode(403, ""); if (this.User == null) return this.Forbid();
this.IpAddress = this.HttpContext.Connection.RemoteIpAddress; this.IpAddress = this.HttpContext.Connection.RemoteIpAddress;

View file

@ -46,7 +46,7 @@
@{ @{
int i = 0; int i = 0;
foreach (User user in Model.PlayersOnline) foreach (UserEntity user in Model.PlayersOnline)
{ {
i++; i++;
@await user.ToLink(Html, ViewData, language, timeZone, true) @await user.ToLink(Html, ViewData, language, timeZone, true)
@ -66,7 +66,7 @@
<h1><i class="star icon"></i>@Model.Translate(LandingPageStrings.LatestTeamPicks)</h1> <h1><i class="star icon"></i>@Model.Translate(LandingPageStrings.LatestTeamPicks)</h1>
<div class="ui divider"></div> <div class="ui divider"></div>
<div class="ui left aligned segment"> <div class="ui left aligned segment">
@foreach (Slot slot in Model.LatestTeamPicks!) @* Can't reach a point where this is null *@ @foreach (SlotEntity slot in Model.LatestTeamPicks!) @* Can't reach a point where this is null *@
{ {
@await slot.ToHtml(Html, ViewData, Model.User, $"~/slot/{slot.SlotId}", language, timeZone, isMobile, true, true) @await slot.ToHtml(Html, ViewData, Model.User, $"~/slot/{slot.SlotId}", language, timeZone, isMobile, true, true)
<br> <br>
@ -83,7 +83,7 @@
<h1><i class="globe americas icon"></i>@Model.Translate(LandingPageStrings.NewestLevels)</h1> <h1><i class="globe americas icon"></i>@Model.Translate(LandingPageStrings.NewestLevels)</h1>
<div class="ui divider"></div> <div class="ui divider"></div>
<div class="ui left aligned segment"> <div class="ui left aligned segment">
@foreach (Slot slot in Model.NewestLevels!) @* Can't reach a point where this is null *@ @foreach (SlotEntity slot in Model.NewestLevels!) @* Can't reach a point where this is null *@
{ {
@await slot.ToHtml(Html, ViewData, Model.User, $"~/slot/{slot.SlotId}", language, timeZone, isMobile, true, true) @await slot.ToHtml(Html, ViewData, Model.User, $"~/slot/{slot.SlotId}", language, timeZone, isMobile, true, true)
<br> <br>

View file

@ -17,15 +17,15 @@ public class LandingPage : BaseLayout
{} {}
public int PendingAuthAttempts; public int PendingAuthAttempts;
public List<User> PlayersOnline = new(); public List<UserEntity> PlayersOnline = new();
public List<Slot>? LatestTeamPicks; public List<SlotEntity>? LatestTeamPicks;
public List<Slot>? NewestLevels; public List<SlotEntity>? NewestLevels;
[UsedImplicitly] [UsedImplicitly]
public async Task<IActionResult> OnGet() public async Task<IActionResult> OnGet()
{ {
User? user = this.Database.UserFromWebRequest(this.Request); UserEntity? user = this.Database.UserFromWebRequest(this.Request);
if (user != null && user.PasswordResetRequired) return this.Redirect("~/passwordResetRequired"); if (user != null && user.PasswordResetRequired) return this.Redirect("~/passwordResetRequired");
if (user != null) if (user != null)

View file

@ -25,7 +25,7 @@ public class BaseLayout : PageModel
public string Title = string.Empty; public string Title = string.Empty;
private User? user; private UserEntity? user;
public BaseLayout(DatabaseContext database) public BaseLayout(DatabaseContext database)
{ {
this.Database = database; this.Database = database;
@ -35,7 +35,7 @@ public class BaseLayout : PageModel
this.NavigationItems.Add(new PageNavigationItem(BaseLayoutStrings.HeaderSlots, "/slots/0", "globe americas")); this.NavigationItems.Add(new PageNavigationItem(BaseLayoutStrings.HeaderSlots, "/slots/0", "globe americas"));
} }
public new User? User { public new UserEntity? User {
get { get {
if (this.user != null) return this.user; if (this.user != null) return this.user;

View file

@ -44,7 +44,7 @@ public class LoginForm : BaseLayout
return this.Page(); return this.Page();
} }
User? user; UserEntity? user;
if (!ServerConfiguration.Instance.Mail.MailEnabled) if (!ServerConfiguration.Instance.Mail.MailEnabled)
{ {
@ -55,7 +55,7 @@ public class LoginForm : BaseLayout
user = await this.Database.Users.FirstOrDefaultAsync(u => u.EmailAddress == username); user = await this.Database.Users.FirstOrDefaultAsync(u => u.EmailAddress == username);
if (user == null) if (user == null)
{ {
User? noEmailUser = await this.Database.Users.FirstOrDefaultAsync(u => u.Username == username); UserEntity? noEmailUser = await this.Database.Users.FirstOrDefaultAsync(u => u.Username == username);
if (noEmailUser != null && noEmailUser.EmailAddress == null) user = noEmailUser; if (noEmailUser != null && noEmailUser.EmailAddress == null) user = noEmailUser;
} }
@ -86,7 +86,7 @@ public class LoginForm : BaseLayout
return this.Page(); return this.Page();
} }
WebToken webToken = new() WebTokenEntity webToken = new()
{ {
UserId = user.UserId, UserId = user.UserId,
UserToken = CryptoHelper.GenerateAuthToken(), UserToken = CryptoHelper.GenerateAuthToken(),

View file

@ -12,7 +12,7 @@ public class LogoutPage : BaseLayout
{} {}
public async Task<IActionResult> OnGet() public async Task<IActionResult> OnGet()
{ {
WebToken? token = this.Database.WebTokenFromRequest(this.Request); WebTokenEntity? token = this.Database.WebTokenFromRequest(this.Request);
if (token == null) return this.Redirect("~/"); if (token == null) return this.Redirect("~/");
this.Database.WebTokens.Remove(token); this.Database.WebTokens.Remove(token);

View file

@ -19,7 +19,7 @@ public class PasswordResetPage : BaseLayout
[UsedImplicitly] [UsedImplicitly]
public async Task<IActionResult> OnPost(string password, string confirmPassword) public async Task<IActionResult> OnPost(string password, string confirmPassword)
{ {
User? user; UserEntity? user;
if (this.Request.Query.ContainsKey("token")) if (this.Request.Query.ContainsKey("token"))
{ {
user = await this.Database.UserFromPasswordResetToken(this.Request.Query["token"][0]); user = await this.Database.UserFromPasswordResetToken(this.Request.Query["token"][0]);
@ -63,7 +63,7 @@ public class PasswordResetPage : BaseLayout
{ {
if (this.Request.Query.ContainsKey("token")) return this.Page(); if (this.Request.Query.ContainsKey("token")) return this.Page();
User? user = this.Database.UserFromWebRequest(this.Request); UserEntity? user = this.Database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("~/login"); if (user == null) return this.Redirect("~/login");
return this.Page(); return this.Page();

View file

@ -42,7 +42,7 @@ public class PasswordResetRequestForm : BaseLayout
return this.Page(); return this.Page();
} }
User? user = await this.Database.Users.FirstOrDefaultAsync(u => u.EmailAddress == email && u.EmailAddressVerified); UserEntity? user = await this.Database.Users.FirstOrDefaultAsync(u => u.EmailAddress == email && u.EmailAddressVerified);
if (user == null) if (user == null)
{ {
@ -51,7 +51,7 @@ public class PasswordResetRequestForm : BaseLayout
return this.Page(); return this.Page();
} }
PasswordResetToken token = new() PasswordResetTokenEntity token = new()
{ {
Created = DateTime.Now, Created = DateTime.Now,
UserId = user.UserId, UserId = user.UserId,

View file

@ -15,7 +15,7 @@ public class PasswordResetRequiredPage : BaseLayout
public IActionResult OnGet() public IActionResult OnGet()
{ {
User? user = this.Database.UserFromWebRequest(this.Request); UserEntity? user = this.Database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("~/login"); if (user == null) return this.Redirect("~/login");
if (!user.PasswordResetRequired) return this.Redirect("~/passwordReset"); if (!user.PasswordResetRequired) return this.Redirect("~/passwordReset");

View file

@ -12,7 +12,7 @@ public class PirateSignupPage : BaseLayout
public IActionResult OnGet() public IActionResult OnGet()
{ {
User? user = this.Database.UserFromWebRequest(this.Request); UserEntity? user = this.Database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("/login"); if (user == null) return this.Redirect("/login");
return this.Page(); return this.Page();
@ -20,7 +20,7 @@ public class PirateSignupPage : BaseLayout
public async Task<IActionResult> OnPost() public async Task<IActionResult> OnPost()
{ {
User? user = this.Database.UserFromWebRequest(this.Request); UserEntity? user = this.Database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("/login"); if (user == null) return this.Redirect("/login");
user.Language = user.Language == "en-PT" ? "en" : "en-PT"; user.Language = user.Language == "en-PT" ? "en" : "en-PT";

View file

@ -54,7 +54,7 @@ public class RegisterForm : BaseLayout
return this.Page(); return this.Page();
} }
User? existingUser = await this.Database.Users.FirstOrDefaultAsync(u => u.Username.ToLower() == username.ToLower()); UserEntity? existingUser = await this.Database.Users.FirstOrDefaultAsync(u => u.Username.ToLower() == username.ToLower());
if (existingUser != null) if (existingUser != null)
{ {
this.Error = this.Translate(ErrorStrings.UsernameTaken); this.Error = this.Translate(ErrorStrings.UsernameTaken);
@ -74,9 +74,9 @@ public class RegisterForm : BaseLayout
return this.Page(); return this.Page();
} }
User user = await this.Database.CreateUser(username, CryptoHelper.BCryptHash(password), emailAddress); UserEntity user = await this.Database.CreateUser(username, CryptoHelper.BCryptHash(password), emailAddress);
WebToken webToken = new() WebTokenEntity webToken = new()
{ {
UserId = user.UserId, UserId = user.UserId,
UserToken = CryptoHelper.GenerateAuthToken(), UserToken = CryptoHelper.GenerateAuthToken(),

View file

@ -12,7 +12,7 @@
<p>There are @Model.UserCount banned users.</p> <p>There are @Model.UserCount banned users.</p>
@foreach (User user in Model.Users) @foreach (UserEntity user in Model.Users)
{ {
<div class="ui segment"> <div class="ui segment">
@await Html.PartialAsync("Partials/UserCardPartial", user, new ViewDataDictionary(ViewData) @await Html.PartialAsync("Partials/UserCardPartial", user, new ViewDataDictionary(ViewData)

View file

@ -13,7 +13,7 @@ public class BannedUsersPage : BaseLayout
public BannedUsersPage(DatabaseContext database) : base(database) public BannedUsersPage(DatabaseContext database) : base(database)
{} {}
public List<User> Users = new(); public List<UserEntity> Users = new();
public int PageAmount; public int PageAmount;
@ -23,7 +23,7 @@ public class BannedUsersPage : BaseLayout
public async Task<IActionResult> OnGet([FromRoute] int pageNumber, [FromQuery] string? name) public async Task<IActionResult> OnGet([FromRoute] int pageNumber, [FromQuery] string? name)
{ {
WebToken? token = this.Database.WebTokenFromRequest(this.Request); WebTokenEntity? token = this.Database.WebTokenFromRequest(this.Request);
if (token == null) return this.Redirect("/login"); if (token == null) return this.Redirect("/login");
this.Users = await this.Database.Users this.Users = await this.Database.Users

View file

@ -21,7 +21,7 @@
<div class="ui divider"></div> <div class="ui divider"></div>
@foreach (ModerationCase @case in Model.Cases) @foreach (ModerationCaseEntity @case in Model.Cases)
{ {
@(await Html.PartialAsync("Partials/ModerationCasePartial", @case, ViewData.WithTime(timeZone))) @(await Html.PartialAsync("Partials/ModerationCasePartial", @case, ViewData.WithTime(timeZone)))
} }

View file

@ -13,7 +13,7 @@ public class CasePage : BaseLayout
public CasePage(DatabaseContext database) : base(database) public CasePage(DatabaseContext database) : base(database)
{} {}
public List<ModerationCase> Cases = new(); public List<ModerationCaseEntity> Cases = new();
public int CaseCount; public int CaseCount;
public int DismissedCaseCount; public int DismissedCaseCount;
@ -23,7 +23,7 @@ public class CasePage : BaseLayout
public async Task<IActionResult> OnGet([FromRoute] int pageNumber, [FromQuery] string? name) public async Task<IActionResult> OnGet([FromRoute] int pageNumber, [FromQuery] string? name)
{ {
User? user = this.Database.UserFromWebRequest(this.Request); UserEntity? user = this.Database.UserFromWebRequest(this.Request);
if (user == null) return this.NotFound(); if (user == null) return this.NotFound();
if (!user.IsModerator) return this.NotFound(); if (!user.IsModerator) return this.NotFound();

View file

@ -15,7 +15,7 @@
<p>There are @Model.SlotCount hidden levels.</p> <p>There are @Model.SlotCount hidden levels.</p>
@foreach (Slot slot in Model.Slots) @foreach (SlotEntity slot in Model.Slots)
{ {
<div class="ui segment"> <div class="ui segment">
@await slot.ToHtml(Html, ViewData, Model.User, $"~/moderation/hiddenLevels/{Model.PageNumber}", language, timeZone, isMobile, true) @await slot.ToHtml(Html, ViewData, Model.User, $"~/moderation/hiddenLevels/{Model.PageNumber}", language, timeZone, isMobile, true)

View file

@ -19,11 +19,11 @@ public class HiddenLevelsPage : BaseLayout
public int SlotCount; public int SlotCount;
public List<Slot> Slots = new(); public List<SlotEntity> Slots = new();
public async Task<IActionResult> OnGet([FromRoute] int pageNumber, [FromQuery] string? name) public async Task<IActionResult> OnGet([FromRoute] int pageNumber, [FromQuery] string? name)
{ {
WebToken? token = this.Database.WebTokenFromRequest(this.Request); WebTokenEntity? token = this.Database.WebTokenFromRequest(this.Request);
if (token == null) return this.Redirect("/login"); if (token == null) return this.Redirect("/login");
this.Slots = await this.Database.Slots this.Slots = await this.Database.Slots

View file

@ -16,7 +16,7 @@ public class ModPanelPage : BaseLayout
public async Task<IActionResult> OnGet() public async Task<IActionResult> OnGet()
{ {
User? user = this.Database.UserFromWebRequest(this.Request); UserEntity? user = this.Database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("~/login"); if (user == null) return this.Redirect("~/login");
if (!user.IsModerator) return this.NotFound(); if (!user.IsModerator) return this.NotFound();

View file

@ -17,7 +17,7 @@ public class NewCasePage : BaseLayout
public IActionResult OnGet([FromQuery] CaseType? type, [FromQuery] int? affectedId) public IActionResult OnGet([FromQuery] CaseType? type, [FromQuery] int? affectedId)
{ {
User? user = this.Database.UserFromWebRequest(this.Request); UserEntity? user = this.Database.UserFromWebRequest(this.Request);
if (user == null || !user.IsModerator) return this.Redirect("/login"); if (user == null || !user.IsModerator) return this.Redirect("/login");
if (type == null) return this.BadRequest(); if (type == null) return this.BadRequest();
@ -31,7 +31,7 @@ public class NewCasePage : BaseLayout
public async Task<IActionResult> OnPost(CaseType? type, string? reason, string? modNotes, DateTime expires, int? affectedId) public async Task<IActionResult> OnPost(CaseType? type, string? reason, string? modNotes, DateTime expires, int? affectedId)
{ {
User? user = this.Database.UserFromWebRequest(this.Request); UserEntity? user = this.Database.UserFromWebRequest(this.Request);
if (user == null || !user.IsModerator) return this.Redirect("/login"); if (user == null || !user.IsModerator) return this.Redirect("/login");
if (type == null) return this.BadRequest(); if (type == null) return this.BadRequest();
@ -43,7 +43,7 @@ public class NewCasePage : BaseLayout
// if id is invalid then return bad request // if id is invalid then return bad request
if (!await type.Value.IsIdValid((int)affectedId, this.Database)) return this.BadRequest(); if (!await type.Value.IsIdValid((int)affectedId, this.Database)) return this.BadRequest();
ModerationCase @case = new() ModerationCaseEntity @case = new()
{ {
Type = type.Value, Type = type.Value,
Reason = reason, Reason = reason,

View file

@ -14,15 +14,15 @@ public class ReportPage : BaseLayout
public ReportPage(DatabaseContext database) : base(database) public ReportPage(DatabaseContext database) : base(database)
{} {}
public GriefReport Report = null!; // Report is not used if it's null in OnGet public GriefReportEntity Report = null!; // Report is not used if it's null in OnGet
public async Task<IActionResult> OnGet([FromRoute] int reportId) public async Task<IActionResult> OnGet([FromRoute] int reportId)
{ {
User? user = this.Database.UserFromWebRequest(this.Request); UserEntity? user = this.Database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("~/login"); if (user == null) return this.Redirect("~/login");
if (!user.IsAdmin) return this.NotFound(); if (!user.IsAdmin) return this.NotFound();
GriefReport? report = await this.Database.Reports GriefReportEntity? report = await this.Database.Reports
.Include(r => r.ReportingPlayer) .Include(r => r.ReportingPlayer)
.FirstOrDefaultAsync(r => r.ReportId == reportId); .FirstOrDefaultAsync(r => r.ReportId == reportId);
if (report == null) return this.NotFound(); if (report == null) return this.NotFound();

View file

@ -28,7 +28,7 @@
let images = []; let images = [];
</script> </script>
@foreach (GriefReport report in Model.Reports) @foreach (GriefReportEntity report in Model.Reports)
{ {
@await Html.PartialAsync("Partials/ReportPartial", report, ViewData.WithTime(timeZone)) @await Html.PartialAsync("Partials/ReportPartial", report, ViewData.WithTime(timeZone))
} }

View file

@ -19,7 +19,7 @@ public class ReportsPage : BaseLayout
public int ReportCount; public int ReportCount;
public List<GriefReport> Reports = new(); public List<GriefReportEntity> Reports = new();
public string SearchValue = ""; public string SearchValue = "";
@ -28,7 +28,7 @@ public class ReportsPage : BaseLayout
public async Task<IActionResult> OnGet([FromRoute] int pageNumber, [FromQuery] string? name) public async Task<IActionResult> OnGet([FromRoute] int pageNumber, [FromQuery] string? name)
{ {
User? user = this.Database.UserFromWebRequest(this.Request); UserEntity? user = this.Database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("~/login"); if (user == null) return this.Redirect("~/login");
if (!user.IsModerator) return this.NotFound(); if (!user.IsModerator) return this.NotFound();
@ -51,7 +51,7 @@ public class ReportsPage : BaseLayout
.Take(ServerStatics.PageSize) .Take(ServerStatics.PageSize)
.ToListAsync(); .ToListAsync();
foreach (GriefReport r in this.Reports) foreach (GriefReportEntity r in this.Reports)
{ {
r.XmlPlayers = (ReportPlayer[]?)JsonSerializer.Deserialize(r.Players, typeof(ReportPlayer[])) ?? Array.Empty<ReportPlayer>(); r.XmlPlayers = (ReportPlayer[]?)JsonSerializer.Deserialize(r.Players, typeof(ReportPlayer[])) ?? Array.Empty<ReportPlayer>();

View file

@ -1,4 +1,4 @@
@model LBPUnion.ProjectLighthouse.Types.Entities.Profile.User @model LBPUnion.ProjectLighthouse.Types.Entities.Profile.UserEntity
<form method="post" action="/admin/user/@Model.UserId/setGrantedSlots"> <form method="post" action="/admin/user/@Model.UserId/setGrantedSlots">
@Html.AntiForgeryToken() @Html.AntiForgeryToken()

View file

@ -2,6 +2,7 @@
@using System.IO @using System.IO
@using LBPUnion.ProjectLighthouse.Localization @using LBPUnion.ProjectLighthouse.Localization
@using LBPUnion.ProjectLighthouse.Servers.Website.Extensions @using LBPUnion.ProjectLighthouse.Servers.Website.Extensions
@using LBPUnion.ProjectLighthouse.Types.Entities.Interaction
@using LBPUnion.ProjectLighthouse.Types.Entities.Profile @using LBPUnion.ProjectLighthouse.Types.Entities.Profile
@{ @{
@ -42,65 +43,70 @@
<div class="ui divider"></div> <div class="ui divider"></div>
} }
} }
@{
int i = 0;
foreach (KeyValuePair<CommentEntity, RatedCommentEntity?> commentAndReaction in Model.Comments)
{
CommentEntity comment = commentAndReaction.Key;
int yourThumb = commentAndReaction.Value?.Rating ?? 0;
DateTimeOffset timestamp = DateTimeOffset.FromUnixTimeSeconds(comment.Timestamp / 1000).ToLocalTime();
StringWriter messageWriter = new();
HttpUtility.HtmlDecode(comment.getComment(), messageWriter);
@for(int i = 0; i < Model.Comments.Count; i++) string decodedMessage = messageWriter.ToString();
{ string? url = Url.RouteUrl(ViewContext.RouteData.Values);
Comment comment = Model.Comments[i]; if (url == null) continue;
DateTimeOffset timestamp = DateTimeOffset.FromUnixTimeSeconds(comment.Timestamp / 1000).ToLocalTime();
StringWriter messageWriter = new();
HttpUtility.HtmlDecode(comment.getComment(), messageWriter);
string decodedMessage = messageWriter.ToString(); int rating = comment.ThumbsUp - comment.ThumbsDown;
string? url = Url.RouteUrl(ViewContext.RouteData.Values);
if (url == null) continue;
int rating = comment.ThumbsUp - comment.ThumbsDown; <div style="display: flex" id="@comment.CommentId">
@{
<div style="display: flex" id="@comment.CommentId"> string style = "";
@{ if (Model.User?.UserId == comment.PosterUserId)
string style = ""; {
if (Model.User?.UserId == comment.PosterUserId) style = "pointer-events: none";
{ }
style = "pointer-events: none";
} }
} <div class="voting" style="@(style)">
<div class="voting" style="@(style)"> <a href="@url/rateComment?commentId=@(comment.CommentId)&rating=@(yourThumb == 1 ? 0 : 1)">
<a href="@url/rateComment?commentId=@(comment.CommentId)&rating=@(comment.YourThumb == 1 ? 0 : 1)"> <i class="fitted @(yourThumb == 1 ? "green" : "grey") arrow up link icon" style="display: block"></i>
<i class="fitted @(comment.YourThumb == 1 ? "green" : "grey") arrow up link icon" style="display: block"></i> </a>
</a> <span style="text-align: center; margin: auto; @(rating < 0 ? "margin-left: -5px" : "")">@(rating)</span>
<span style="text-align: center; margin: auto; @(rating < 0 ? "margin-left: -5px" : "")">@(rating)</span> <a href="@url/rateComment?commentId=@(comment.CommentId)&rating=@(yourThumb == -1 ? 0 : -1)">
<a href="@url/rateComment?commentId=@(comment.CommentId)&rating=@(comment.YourThumb == -1 ? 0 : -1)"> <i class="fitted @(yourThumb == -1 ? "red" : "grey") arrow down link icon" style="display: block"></i>
<i class="fitted @(comment.YourThumb == -1 ? "red" : "grey") arrow down link icon" style="display: block"></i> </a>
</a> </div>
</div>
<div class="comment"> <div class="comment">
<b>@await comment.Poster.ToLink(Html, ViewData, language): </b> <b>@await comment.Poster.ToLink(Html, ViewData, language): </b>
@if (comment.Deleted) @if (comment.Deleted)
{ {
<i> <i>
<span>@decodedMessage</span>
</i>
}
else
{
<span>@decodedMessage</span> <span>@decodedMessage</span>
</i> }
} @if (((Model.User?.IsModerator ?? false) || Model.User?.UserId == comment.PosterUserId || Model.User?.UserId == pageOwnerId) && !comment.Deleted)
else {
{ <button class="ui red icon button" style="display:inline-flex; float: right" onclick="deleteComment(@comment.CommentId)">
<span>@decodedMessage</span> <i class="trash icon"></i>
} </button>
@if (((Model.User?.IsModerator ?? false) || Model.User?.UserId == comment.PosterUserId || Model.User?.UserId == pageOwnerId) && !comment.Deleted) }
{ <p>
<button class="ui red icon button" style="display:inline-flex; float: right" onclick="deleteComment(@comment.CommentId)"> <i>@TimeZoneInfo.ConvertTime(timestamp, timeZoneInfo).ToString("M/d/yyyy @ h:mm:ss tt")</i>
<i class="trash icon"></i> </p>
</button> @if (i != Model.Comments.Count - 1)
} {
<p> <div class="ui divider"></div>
<i>@TimeZoneInfo.ConvertTime(timestamp, timeZoneInfo).ToString("M/d/yyyy @ h:mm:ss tt")</i> }
</p> </div>
@if (i != Model.Comments.Count - 1)
{
<div class="ui divider"></div>
}
</div> </div>
</div> i++;
}
} }
<script> <script>
function deleteComment(commentId){ function deleteComment(commentId){

View file

@ -23,7 +23,7 @@
<div class="ui list"> <div class="ui list">
@for(int i = 0; i < Model.Scores.Count; i++) @for(int i = 0; i < Model.Scores.Count; i++)
{ {
Score score = Model.Scores[i]; ScoreEntity score = Model.Scores[i];
string[] playerIds = score.PlayerIds; string[] playerIds = score.PlayerIds;
DatabaseContext database = Model.Database; DatabaseContext database = Model.Database;
<div class="item"> <div class="item">
@ -41,7 +41,7 @@
<div class="list" style="padding-top: 0"> <div class="list" style="padding-top: 0">
@for (int j = 0; j < playerIds.Length; j++) @for (int j = 0; j < playerIds.Length; j++)
{ {
User? user = await database.Users.FirstOrDefaultAsync(u => u.Username == playerIds[j]); UserEntity? user = await database.Users.FirstOrDefaultAsync(u => u.Username == playerIds[j]);
<div class="item"> <div class="item">
<i class="minus icon" style="padding-top: 9px"></i> <i class="minus icon" style="padding-top: 9px"></i>
<div class="content" style="padding-left: 0"> <div class="content" style="padding-left: 0">

View file

@ -1,12 +1,14 @@
@using LBPUnion.ProjectLighthouse.Database
@using LBPUnion.ProjectLighthouse.Localization @using LBPUnion.ProjectLighthouse.Localization
@using LBPUnion.ProjectLighthouse.Types.Users @using LBPUnion.ProjectLighthouse.Types.Users
@model LBPUnion.ProjectLighthouse.Types.Entities.Profile.User @model LBPUnion.ProjectLighthouse.Types.Entities.Profile.UserEntity
@{ @{
string language = (string?)ViewData["Language"] ?? LocalizationManager.DefaultLang; string language = (string?)ViewData["Language"] ?? LocalizationManager.DefaultLang;
string timeZone = (string?)ViewData["TimeZone"] ?? TimeZoneInfo.Local.Id; string timeZone = (string?)ViewData["TimeZone"] ?? TimeZoneInfo.Local.Id;
bool includeStatus = (bool?)ViewData["IncludeStatus"] ?? false; bool includeStatus = (bool?)ViewData["IncludeStatus"] ?? false;
string userStatus = includeStatus ? Model.Status.ToTranslatedString(language, timeZone) : ""; await using DatabaseContext database = new();
string userStatus = includeStatus ? Model.GetStatus(database).ToTranslatedString(language, timeZone) : "";
} }
<a href="/user/@Model.UserId" title="@userStatus" class="user-link"> <a href="/user/@Model.UserId" title="@userStatus" class="user-link">

View file

@ -3,7 +3,7 @@
@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.Moderation.Cases @using LBPUnion.ProjectLighthouse.Types.Moderation.Cases
@model LBPUnion.ProjectLighthouse.Types.Entities.Moderation.ModerationCase @model LBPUnion.ProjectLighthouse.Types.Entities.Moderation.ModerationCaseEntity
@{ @{
DatabaseContext database = new(); DatabaseContext database = new();
@ -62,7 +62,7 @@
@if (Model.Type.AffectsLevel()) @if (Model.Type.AffectsLevel())
{ {
Slot? slot = await Model.GetSlotAsync(database); SlotEntity? slot = await Model.GetSlotAsync(database);
if (slot != null) if (slot != null)
{ {
<p><strong>Affected level:</strong> <a href="/slot/@slot.SlotId">@slot.Name</a></p> <p><strong>Affected level:</strong> <a href="/slot/@slot.SlotId">@slot.Name</a></p>
@ -70,7 +70,7 @@
} }
else if (Model.Type.AffectsUser()) else if (Model.Type.AffectsUser())
{ {
User? user = await Model.GetUserAsync(database); UserEntity? user = await Model.GetUserAsync(database);
if (user != null) if (user != null)
{ {
<p><strong>Affected user:</strong> <a href="/user/@user.UserId">@user.Username</a></p> <p><strong>Affected user:</strong> <a href="/user/@user.UserId">@user.Username</a></p>

View file

@ -3,7 +3,8 @@
@using LBPUnion.ProjectLighthouse.Servers.Website.Extensions @using LBPUnion.ProjectLighthouse.Servers.Website.Extensions
@using LBPUnion.ProjectLighthouse.Types.Entities.Profile @using LBPUnion.ProjectLighthouse.Types.Entities.Profile
@using LBPUnion.ProjectLighthouse.Types.Levels @using LBPUnion.ProjectLighthouse.Types.Levels
@model LBPUnion.ProjectLighthouse.Types.Entities.Profile.Photo @using LBPUnion.ProjectLighthouse.Types.Serialization
@model LBPUnion.ProjectLighthouse.Types.Entities.Profile.PhotoEntity
@{ @{
string language = (string?)ViewData["Language"] ?? LocalizationManager.DefaultLang; string language = (string?)ViewData["Language"] ?? LocalizationManager.DefaultLang;
@ -83,15 +84,17 @@
</p> </p>
} }
<div id="hover-subjects-@Model.PhotoId"> <div id="hover-subjects-@Model.PhotoId">
@foreach (PhotoSubject subject in Model.PhotoSubjects) @foreach (PhotoSubjectEntity subject in Model.PhotoSubjects)
{ {
@await subject.User.ToLink(Html, ViewData, language, timeZone) @await subject.User.ToLink(Html, ViewData, language, timeZone)
} }
</div> </div>
@{ @{
PhotoSubject[] subjects = Model.PhotoSubjects.ToArray(); GamePhotoSubject[] subjects = Model.PhotoSubjects.Select(GamePhotoSubject.CreateFromEntity).ToArray();
foreach (PhotoSubject subject in subjects) subject.Username = subject.User.Username; foreach (GamePhotoSubject subject in subjects)
{
subject.Username = Model.PhotoSubjects.Where(ps => ps.UserId == subject.UserId).Select(ps => ps.User.Username).First();
}
} }
<script> <script>

View file

@ -1,5 +1,5 @@
@using LBPUnion.ProjectLighthouse.Types.Moderation.Reports @using LBPUnion.ProjectLighthouse.Types.Moderation.Reports
@model LBPUnion.ProjectLighthouse.Types.Entities.Moderation.GriefReport @model LBPUnion.ProjectLighthouse.Types.Entities.Moderation.GriefReportEntity
@{ @{
string timeZone = (string?)ViewData["TimeZone"] ?? TimeZoneInfo.Local.Id; string timeZone = (string?)ViewData["TimeZone"] ?? TimeZoneInfo.Local.Id;

View file

@ -3,6 +3,7 @@
@using LBPUnion.ProjectLighthouse.Files @using LBPUnion.ProjectLighthouse.Files
@using LBPUnion.ProjectLighthouse.Helpers @using LBPUnion.ProjectLighthouse.Helpers
@using LBPUnion.ProjectLighthouse.Types.Entities.Level @using LBPUnion.ProjectLighthouse.Types.Entities.Level
@using LBPUnion.ProjectLighthouse.Types.Serialization
@{ @{
bool isMobile = (bool?)ViewData["IsMobile"] ?? false; bool isMobile = (bool?)ViewData["IsMobile"] ?? false;
@ -30,7 +31,7 @@
@for(int i = 0; i < Model.Reviews.Count; i++) @for(int i = 0; i < Model.Reviews.Count; i++)
{ {
Review review = Model.Reviews[i]; ReviewEntity review = Model.Reviews[i];
string faceHash = (review.Thumb switch { string faceHash = (review.Thumb switch {
-1 => review.Reviewer?.BooHash, -1 => review.Reviewer?.BooHash,
0 => review.Reviewer?.MehHash, 0 => review.Reviewer?.MehHash,

View file

@ -6,10 +6,10 @@
@using LBPUnion.ProjectLighthouse.Types.Entities.Profile @using LBPUnion.ProjectLighthouse.Types.Entities.Profile
@using LBPUnion.ProjectLighthouse.Types.Users @using LBPUnion.ProjectLighthouse.Types.Users
@using Microsoft.EntityFrameworkCore @using Microsoft.EntityFrameworkCore
@model LBPUnion.ProjectLighthouse.Types.Entities.Level.Slot @model LBPUnion.ProjectLighthouse.Types.Entities.Level.SlotEntity
@{ @{
User? user = (User?)ViewData["User"]; UserEntity? user = (UserEntity?)ViewData["User"];
await using DatabaseContext database = new(); await using DatabaseContext database = new();

View file

@ -1,6 +1,7 @@
@using LBPUnion.ProjectLighthouse.Database
@using LBPUnion.ProjectLighthouse.Localization @using LBPUnion.ProjectLighthouse.Localization
@using LBPUnion.ProjectLighthouse.Types.Users @using LBPUnion.ProjectLighthouse.Types.Users
@model LBPUnion.ProjectLighthouse.Types.Entities.Profile.User @model LBPUnion.ProjectLighthouse.Types.Entities.Profile.UserEntity
@{ @{
bool showLink = (bool?)ViewData["ShowLink"] ?? false; bool showLink = (bool?)ViewData["ShowLink"] ?? false;
@ -40,14 +41,22 @@
} }
</h1> </h1>
} }
@{
await using DatabaseContext context = new();
int hearts = Model.GetHeartCount(context);
int comments = Model.GetCommentCount(context);
int levels = Model.GetUsedSlotCount(context);
int photos = Model.GetUploadedPhotoCount(context);
}
<span> <span>
<i>@Model.Status.ToTranslatedString(language, timeZone)</i> <i>@Model.GetStatus(context).ToTranslatedString(language, timeZone)</i>
</span> </span>
<div class="cardStatsUnderTitle"> <div class="cardStatsUnderTitle">
<i class="pink heart icon" title="Hearts"></i> <span>@Model.Hearts</span> <i class="pink heart icon" title="Hearts"></i> <span>@hearts</span>
<i class="blue comment icon" title="Comments"></i> <span>@Model.Comments</span> <i class="blue comment icon" title="Comments"></i> <span>@comments</span>
<i class="green upload icon" title="Uploaded Levels"></i><span>@Model.UsedSlots</span> <i class="green upload icon" title="Uploaded Levels"></i><span>@levels</span>
<i class="purple camera icon" title="Uploaded Photos"></i><span>@Model.PhotosByMe</span> <i class="purple camera icon" title="Uploaded Photos"></i><span>@photos</span>
</div> </div>
</div> </div>
</div> </div>

Some files were not shown because too many files have changed in this diff Show more