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,
"tools": {
"dotnet-ef": {
"version": "7.0.3",
"version": "7.0.4",
"commands": [
"dotnet-ef"
]

View file

@ -28,15 +28,18 @@ public class SlotEndpoints : ApiEndpointController
/// <returns>The slot</returns>
/// <response code="200">The slot list, if successful.</response>
[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)
{
if (skip < 0) skip = 0;
if (limit < 0) limit = 0;
limit = Math.Min(ServerStatics.PageSize, limit);
IEnumerable<MinimalSlot> minimalSlots = (await this.database.Slots.OrderByDescending(s => s.FirstUploaded).Skip(skip).Take(limit).ToListAsync()).Select
(MinimalSlot.FromSlot);
IEnumerable<ApiSlot> minimalSlots = await this.database.Slots.OrderByDescending(s => s.FirstUploaded)
.Skip(skip)
.Take(limit)
.Select(s => ApiSlot.CreateFromEntity(s))
.ToListAsync();
return this.Ok(minimalSlots);
}
@ -49,13 +52,13 @@ public class SlotEndpoints : ApiEndpointController
/// <response code="200">The slot, if successful.</response>
/// <response code="404">The slot could not be found.</response>
[HttpGet("slot/{id:int}")]
[ProducesResponseType(typeof(Slot), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiSlot), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
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();
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;
[ApiController]
[Route("/api/v1")]
[Produces("application/json")]
public class StatusController : ControllerBase
public class StatusController : ApiEndpointController
{
[AcceptVerbs("GET", "HEAD", Route = "status")]
public IActionResult GetStatus() => this.Ok();

View file

@ -1,6 +1,7 @@
#nullable enable
using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Servers.API.Responses;
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
using LBPUnion.ProjectLighthouse.Types.Users;
@ -29,25 +30,25 @@ public class UserEndpoints : ApiEndpointController
/// <response code="200">The user, if successful.</response>
/// <response code="404">The user could not be found.</response>
[HttpGet("user/{id:int}")]
[ProducesResponseType(typeof(User), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiUser), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
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();
return this.Ok(user);
return this.Ok(ApiUser.CreateFromEntity(user));
}
[HttpGet("username/{username}")]
[ProducesResponseType(typeof(User), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiUser), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
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();
return this.Ok(user);
return this.Ok(ApiUser.CreateFromEntity(user));
}
/// <summary>
@ -58,15 +59,16 @@ public class UserEndpoints : ApiEndpointController
/// <response code="200">The list of users, if any were found</response>
/// <response code="404">No users matched the query</response>
[HttpGet("search/user")]
[ProducesResponseType(typeof(User), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiUser), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
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.ProfileVisibility == PrivacyType.All) // TODO: change check for when user is logged in
.OrderByDescending(b => b.UserId)
.Take(20)
.Select(u => ApiUser.CreateFromEntity(u))
.ToListAsync();
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="404">The user could not be found.</response>
[HttpGet("user/{id:int}/status")]
[ProducesResponseType(typeof(UserStatus), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiUser), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetUserStatus(int id)
{
@ -102,8 +104,8 @@ public class UserEndpoints : ApiEndpointController
string authToken = authHeader[(authHeader.IndexOf(' ') + 1)..];
ApiKey? apiKey = await this.database.APIKeys.FirstOrDefaultAsync(k => k.Key == authToken);
if (apiKey == null) return this.StatusCode(403, null);
ApiKeyEntity? apiKey = await this.database.APIKeys.FirstOrDefaultAsync(k => k.Key == authToken);
if (apiKey == null) return this.Forbid();
if (!string.IsNullOrWhiteSpace(username))
{
@ -111,7 +113,7 @@ public class UserEndpoints : ApiEndpointController
if (userExists) return this.BadRequest();
}
RegistrationToken token = new()
RegistrationTokenEntity token = new()
{
Created = DateTime.Now,
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.Servers.GameServer.Types.Users;
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
using LBPUnion.ProjectLighthouse.Types.Serialization;
using LBPUnion.ProjectLighthouse.Types.Users;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@ -41,7 +41,7 @@ public class ClientConfigurationController : ControllerBase
[HttpGet("t_conf")]
[Produces("text/xml")]
public IActionResult Conf() => this.Ok("<t_enable>false</t_enable>");
public IActionResult Conf() => this.Ok(new TelemetryConfigResponse());
[HttpGet("ChallengeConfig.xml")]
[Produces("text/xml")]
@ -54,8 +54,8 @@ public class ClientConfigurationController : ControllerBase
[Produces("text/xml")]
public async Task<IActionResult> GetPrivacySettings()
{
User? user = await this.database.UserFromGameToken(this.GetToken());
if (user == null) return this.StatusCode(403, "");
UserEntity? user = await this.database.UserFromGameToken(this.GetToken());
if (user == null) return this.Forbid();
PrivacySettings ps = new()
{
@ -63,15 +63,15 @@ public class ClientConfigurationController : ControllerBase
ProfileVisibility = user.ProfileVisibility.ToSerializedString(),
};
return this.Ok(ps.Serialize());
return this.Ok(ps);
}
[HttpPost("privacySettings")]
[Produces("text/xml")]
public async Task<IActionResult> SetPrivacySetting()
{
User? user = await this.database.UserFromGameToken(this.GetToken());
if (user == null) return this.StatusCode(403, "");
UserEntity? user = await this.database.UserFromGameToken(this.GetToken());
if (user == null) return this.Forbid();
PrivacySettings? settings = await this.DeserializeBody<PrivacySettings>();
if (settings == null) return this.BadRequest();
@ -100,6 +100,6 @@ public class ClientConfigurationController : ControllerBase
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.Extensions;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
using LBPUnion.ProjectLighthouse.Types.Levels;
using LBPUnion.ProjectLighthouse.Types.Serialization;
using LBPUnion.ProjectLighthouse.Types.Users;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@ -29,7 +29,7 @@ public class CommentController : ControllerBase
[HttpPost("rateComment/{slotType}/{slotId:int}")]
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
if ((slotId == 0 || SlotHelper.IsTypeInvalid(slotType)) == (username == null)) return this.BadRequest();
@ -44,7 +44,7 @@ public class CommentController : ControllerBase
[HttpGet("userComments/{username}")]
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();
@ -77,45 +77,37 @@ public class CommentController : ControllerBase
where blockedProfile.UserId == token.UserId
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)
.Where(p => !blockedUsers.Contains(p.PosterUserId))
.Include(c => c.Poster)
.Where(p => p.Poster.PermissionLevel != PermissionLevel.Banned)
.Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30))
.Select(c => GameComment.CreateFromEntity(c, token.UserId))
.ToListAsync();
string outputXml = comments.Aggregate
(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();
return this.Ok(new CommentListResponse(comments));
}
[HttpPost("postUserComment/{username}")]
[HttpPost("postComment/{slotType}/{slotId:int}")]
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 ((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;
int targetId;
if (type == CommentType.Level)
{
slotId = await SlotHelper.GetPlaceholderSlotId(this.database, slotId, SlotType.Developer);
targetId = await this.database.Slots.Where(s => s.SlotId == slotId)
.Where(s => s.CommentsEnabled && !s.Hidden)
.Select(s => s.SlotId)
@ -138,11 +130,11 @@ public class CommentController : ControllerBase
[HttpPost("deleteComment/{slotType}/{slotId:int}")]
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();
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.Deleted) return this.Ok();
@ -169,7 +161,7 @@ public class CommentController : ControllerBase
canDelete = comment.PosterUserId == token.UserId || slotCreator == token.UserId;
}
if (!canDelete) return this.StatusCode(403, "");
if (!canDelete) return this.Forbid();
comment.Deleted = true;
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;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
@ -10,5 +11,5 @@ namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
public class DeveloperController : Controller
{
[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.Extensions;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Users;
using LBPUnion.ProjectLighthouse.StorableLists.Stores;
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
using LBPUnion.ProjectLighthouse.Types.Serialization;
using LBPUnion.ProjectLighthouse.Types.Users;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@ -29,17 +29,17 @@ public class FriendsController : ControllerBase
[HttpPost("npdata")]
public async Task<IActionResult> NPData()
{
GameToken token = this.GetToken();
GameTokenEntity token = this.GetToken();
NPData? npData = await this.DeserializeBody<NPData>();
if (npData == null) return this.BadRequest();
SanitizationHelper.SanitizeStringsInClass(npData);
List<User> friends = new();
List<UserEntity> friends = new();
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;
friends.Add(friend);
@ -48,7 +48,7 @@ public class FriendsController : ControllerBase
List<int> blockedUsers = new();
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;
blockedUsers.Add(blockedUser.UserId);
@ -61,30 +61,35 @@ public class FriendsController : ControllerBase
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")]
public async Task<IActionResult> MyFriends()
{
GameToken token = this.GetToken();
GameTokenEntity token = this.GetToken();
UserFriendData? friendStore = UserFriendStore.GetUserFriendData(token.UserId);
if (friendStore == null)
return this.Ok(LbpSerializer.BlankElement("myFriends"));
GenericUserResponse<GameUser> response = new("myFriends", new List<GameUser>());
if (friendStore == null)
return this.Ok(response);
string friends = "";
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;
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)
{
Logger.Warn("Unable to determine username, rejecting login", LogArea.Login);
return this.StatusCode(403, "");
return this.Forbid();
}
await this.database.RemoveExpiredTokens();
User? user;
UserEntity? user;
switch (npTicket.Platform)
{
@ -91,7 +91,7 @@ public class LoginController : ControllerBase
if (user == null)
{
// 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)
{
ulong targetPlatform = npTicket.Platform == Platform.RPCS3
@ -102,7 +102,7 @@ public class LoginController : ControllerBase
if (targetPlatform != 0)
{
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
@ -111,9 +111,9 @@ public class LoginController : ControllerBase
p.PlatformId == npTicket.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,
UserId = targetUsername.UserId,
@ -124,13 +124,13 @@ public class LoginController : ControllerBase
this.database.PlatformLinkAttempts.Add(linkAttempt);
await this.database.SaveChangesAsync();
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)
{
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
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," +
$" 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);
user.Username = username;
@ -176,26 +176,26 @@ public class LoginController : ControllerBase
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);
if (token != null)
{
Logger.Warn($"Rejecting duplicate ticket from {username}", LogArea.Login);
return this.StatusCode(403, "");
return this.Forbid();
}
token = await this.database.AuthenticateUser(user, npTicket, ipAddress);
if (token == null)
{
Logger.Warn($"Unable to find/generate a token for username {npTicket.Username}", LogArea.Login);
return this.StatusCode(403, "");
return this.Forbid();
}
if (user.IsBanned)
{
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);
@ -214,7 +214,7 @@ public class LoginController : ControllerBase
AuthTicket = "MM_AUTH=" + token.UserToken,
ServerBrand = VersionHelper.EnvVer,
TitleStorageUrl = ServerConfiguration.Instance.GameApiExternalUrl,
}.Serialize()
}
);
}
}

View file

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

View file

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

View file

@ -37,10 +37,10 @@ public class MatchController : ControllerBase
[Produces("text/plain")]
public async Task<IActionResult> Match()
{
GameToken token = this.GetToken();
GameTokenEntity token = this.GetToken();
User? user = await this.database.UserFromGameToken(token);
if (user == null) return this.StatusCode(403, "");
UserEntity? user = await this.database.UserFromGameToken(token);
if (user == null) return this.Forbid();
#region Parse match data
@ -113,7 +113,7 @@ public class MatchController : ControllerBase
List<int> users = new();
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
if (player != null) users.Add(player.UserId);
else return this.BadRequest();
@ -129,10 +129,10 @@ public class MatchController : ControllerBase
if (room != null)
{
List<User> users = new();
List<UserEntity> users = new();
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
if (player != null) users.Add(player);
else return this.BadRequest();

View file

@ -46,7 +46,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.";
[HttpGet("announce")]
public async Task<IActionResult> Announce()
{
GameToken token = this.GetToken();
GameTokenEntity token = this.GetToken();
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")]
public async Task<IActionResult> Filter()
{
GameToken token = this.GetToken();
GameTokenEntity token = this.GetToken();
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();
User? user = await this.database.UserFromGameToken(token);
UserEntity? user = await this.database.UserFromGameToken(token);
if (user == null || user.EmailAddressVerified) return this.Ok();
user.EmailAddress = email;

View file

@ -8,6 +8,7 @@ using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Types.Entities.Moderation;
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
using LBPUnion.ProjectLighthouse.Types.Moderation.Reports;
using LBPUnion.ProjectLighthouse.Types.Serialization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@ -29,11 +30,11 @@ public class ReportController : ControllerBase
[HttpPost("grief")]
public async Task<IActionResult> Report()
{
GameToken token = this.GetToken();
GameTokenEntity token = this.GetToken();
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();
SanitizationHelper.SanitizeStringsInClass(report);
@ -46,18 +47,20 @@ public class ReportController : ControllerBase
if (report.XmlPlayers.Any(p => !this.database.IsUsernameValid(p.Name))) return this.BadRequest();
report.Bounds = JsonSerializer.Serialize(report.XmlBounds.Rect, typeof(Rectangle));
report.Players = JsonSerializer.Serialize(report.XmlPlayers, typeof(ReportPlayer[]));
report.Timestamp = TimeHelper.TimestampMillis;
report.ReportingPlayerId = token.UserId;
GriefReportEntity reportEntity = GameGriefReport.ConvertToEntity(report);
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 WebhookHelper.SendWebhook(
title: "New grief report",
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
);

View file

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

View file

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

View file

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

View file

@ -43,9 +43,9 @@ public class LevelTagsController : ControllerBase
[HttpPost("tag/{slotType}/{id:int}")]
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 (!LabelHelper.IsValidTag(tagName)) return this.BadRequest();
@ -56,7 +56,7 @@ public class LevelTagsController : ControllerBase
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();
rating.TagLBP1 = tagName;

View file

@ -2,12 +2,12 @@
using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Extensions;
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.Profile;
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
using LBPUnion.ProjectLighthouse.Types.Levels;
using LBPUnion.ProjectLighthouse.Types.Serialization;
using LBPUnion.ProjectLighthouse.Types.Users;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@ -43,35 +43,27 @@ public class ListController : ControllerBase
[FromQuery] string? dateFilterType = null
)
{
GameToken token = this.GetToken();
GameTokenEntity token = this.GetToken();
if (pageSize <= 0) return this.BadRequest();
GameVersion gameVersion = token.GameVersion;
IEnumerable<Slot> queuedLevels = this.filterListByRequest(gameFilterType, dateFilterType, token.GameVersion, username, ListFilterType.Queue)
List<SlotBase> queuedLevels = await this.filterListByRequest(gameFilterType, dateFilterType, token.GameVersion, username, ListFilterType.Queue)
.Skip(Math.Max(0, pageStart - 1))
.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
(
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) },
})
);
return this.Ok(new GenericSlotResponse(queuedLevels, total, start));
}
[HttpPost("lolcatftw/add/user/{id:int}")]
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();
await this.database.QueueLevel(token.UserId, slot);
@ -82,9 +74,9 @@ public class ListController : ControllerBase
[HttpPost("lolcatftw/remove/user/{id:int}")]
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();
await this.database.UnqueueLevel(token.UserId, slot);
@ -95,7 +87,7 @@ public class ListController : ControllerBase
[HttpPost("lolcatftw/clear")]
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));
@ -120,30 +112,23 @@ public class ListController : ControllerBase
[FromQuery] string? dateFilterType = null
)
{
GameToken token = this.GetToken();
GameTokenEntity token = this.GetToken();
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);
if (targetUser == null) return this.StatusCode(403, "");
IEnumerable<Slot> heartedLevels = this.filterListByRequest(gameFilterType, dateFilterType, token.GameVersion, username, ListFilterType.FavouriteSlots)
List<SlotBase> heartedLevels = await this.filterListByRequest(gameFilterType, dateFilterType, token.GameVersion, username, ListFilterType.FavouriteSlots)
.Skip(Math.Max(0, pageStart - 1))
.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
(
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) },
})
);
return this.Ok(new GenericSlotResponse("favouriteSlots", heartedLevels, total, start));
}
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}")]
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 (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 (slotType == "developer")
@ -174,13 +159,13 @@ public class ListController : ControllerBase
[HttpPost("unfavourite/slot/{slotType}/{id:int}")]
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 (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 (slotType == "developer")
@ -204,29 +189,31 @@ public class ListController : ControllerBase
if (pageSize <= 0) return this.BadRequest();
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)
.Include(p => p.Playlist).Include(p => p.Playlist.Creator).OrderByDescending(p => p.HeartedPlaylistId).Select(p => p.Playlist);
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)
.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
(
LbpSerializer.TaggedStringElement("favouritePlaylists", response, new Dictionary<string, object>
{
{ "total", this.database.HeartedPlaylists.Count(p => p.UserId == targetUserId) },
{ "hint_start", pageStart + Math.Min(pageSize, 30) },
})
);
return this.Ok(new GenericPlaylistResponse<GamePlaylist>("favouritePlaylists", heartedPlaylists)
{
Total = total,
HintStart = pageStart + Math.Min(pageSize, 30),
});
}
[HttpPost("favourite/playlist/{playlistId:int}")]
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();
await this.database.HeartPlaylist(token.UserId, playlist);
@ -237,9 +224,9 @@ public class ListController : ControllerBase
[HttpPost("unfavourite/playlist/{playlistId:int}")]
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();
await this.database.UnheartPlaylist(token.UserId, playlist);
@ -256,40 +243,34 @@ public class ListController : ControllerBase
[HttpGet("favouriteUsers/{username}")]
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);
if (targetUser == null) return this.StatusCode(403, "");
UserEntity? targetUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
if (targetUser == null) return this.Forbid();
if (pageSize <= 0) return this.BadRequest();
IEnumerable<User> heartedProfiles = this.database.HeartedProfiles.Include
(q => q.HeartedUser)
.OrderBy(q => q.HeartedProfileId)
.Where(q => q.UserId == targetUser.UserId)
.Select(q => q.HeartedUser)
List<GameUser> heartedProfiles = await this.database.HeartedProfiles.Include
(h => h.HeartedUser)
.OrderBy(h => h.HeartedProfileId)
.Where(h => h.UserId == targetUser.UserId)
.Select(h => h.HeartedUser)
.Skip(Math.Max(0, pageStart - 1))
.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
(
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) },
})
);
return this.Ok(new GenericUserResponse<GameUser>("favouriteUsers", heartedProfiles, total, pageStart + Math.Min(pageSize, 30)));
}
[HttpPost("favourite/user/{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();
await this.database.HeartUser(token.UserId, heartedUser);
@ -300,9 +281,9 @@ public class ListController : ControllerBase
[HttpPost("unfavourite/user/{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();
await this.database.UnheartUser(token.UserId, heartedUser);
@ -313,7 +294,7 @@ public class ListController : ControllerBase
#endregion
#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,
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)
{
@ -358,7 +339,7 @@ public class ListController : ControllerBase
if (filterType == ListFilterType.Queue)
{
IQueryable<QueuedLevel> whereQueuedLevels;
IQueryable<QueuedLevelEntity> whereQueuedLevels;
// ReSharper disable once ConvertIfStatementToConditionalTernaryExpression
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);
}
IQueryable<HeartedLevel> whereHeartedLevels;
IQueryable<HeartedLevelEntity> whereHeartedLevels;
// ReSharper disable once ConvertIfStatementToConditionalTernaryExpression
if (gameFilterType == "both")

View file

@ -5,12 +5,12 @@ using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Files;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
using LBPUnion.ProjectLighthouse.Types.Logging;
using LBPUnion.ProjectLighthouse.Types.Resources;
using LBPUnion.ProjectLighthouse.Types.Serialization;
using LBPUnion.ProjectLighthouse.Types.Users;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@ -37,12 +37,12 @@ public class PublishController : ControllerBase
[HttpPost("startPublish")]
public async Task<IActionResult> StartPublish()
{
GameToken token = this.GetToken();
GameTokenEntity token = this.GetToken();
User? user = await this.database.UserFromGameToken(token);
if (user == null) return this.StatusCode(403, "");
UserEntity? user = await this.database.UserFromGameToken(token);
if (user == null) return this.Forbid();
Slot? slot = await this.DeserializeBody<Slot>();
GameUserSlot? slot = await this.DeserializeBody<GameUserSlot>();
if (slot == null)
{
Logger.Warn("Rejecting level upload, slot is null", LogArea.Publish);
@ -55,7 +55,7 @@ public class PublishController : ControllerBase
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)
{
@ -63,10 +63,12 @@ public class PublishController : ControllerBase
return this.BadRequest();
}
int usedSlots = await this.database.Slots.CountAsync(s => s.CreatorId == token.UserId && s.GameVersion == token.GameVersion);
// Republish logic
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)
{
Logger.Warn("Rejecting level republish, could not find old slot", LogArea.Publish);
@ -78,18 +80,18 @@ public class PublishController : ControllerBase
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
(hash => !FileHelper.ResourceExists(hash))
.Aggregate("", (current, hash) => current + LbpSerializer.StringElement("resource", hash));
return this.Ok(LbpSerializer.TaggedStringElement("slot", resources, "type", "user"));
return this.Ok(new SlotResourceResponse(resources.ToList()));
}
/// <summary>
@ -98,12 +100,12 @@ public class PublishController : ControllerBase
[HttpPost("publish")]
public async Task<IActionResult> Publish([FromQuery] string? game)
{
GameToken token = this.GetToken();
GameTokenEntity token = this.GetToken();
User? user = await this.database.UserFromGameToken(token);
if (user == null) return this.StatusCode(403, "");
UserEntity? user = await this.database.UserFromGameToken(token);
if (user == null) return this.Forbid();
Slot? slot = await this.DeserializeBody<Slot>();
GameUserSlot? slot = await this.DeserializeBody<GameUserSlot>();
if (slot == null)
{
@ -111,6 +113,12 @@ public class PublishController : ControllerBase
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);
if (slot.Description.Length > 512)
@ -168,7 +176,7 @@ public class PublishController : ControllerBase
// Republish logic
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)
{
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
if (slotVersion == GameVersion.LittleBigPlanet3)
FileHelper.DeleteResource(slot.RootLevel);
slot.GameVersion = oldSlot.GameVersion;
slot.RootLevel = oldSlot.RootLevel;
slot.ResourceCollection = oldSlot.ResourceCollection;
else
{
oldSlot.GameVersion = slot.GameVersion;
oldSlot.RootLevel = slot.RootLevel;
oldSlot.Resources = slot.Resources;
}
}
}
slot.CreatorId = oldSlot.CreatorId;
slot.SlotId = oldSlot.SlotId;
oldSlot.Name = slot.Name;
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
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;
oldSlot.LastUpdated = TimeHelper.TimestampMillis;
if (slot.MinimumPlayers == 0 || slot.MaximumPlayers == 0)
{
@ -227,20 +231,23 @@ public class PublishController : ControllerBase
slot.MaximumPlayers = 4;
}
slot.MinimumPlayers = Math.Clamp(slot.MinimumPlayers, 1, 4);
slot.MaximumPlayers = Math.Clamp(slot.MaximumPlayers, 1, 4);
oldSlot.MinimumPlayers = Math.Clamp(slot.MinimumPlayers, 1, 4);
oldSlot.MaximumPlayers = Math.Clamp(slot.MaximumPlayers, 1, 4);
this.database.Entry(oldSlot).CurrentValues.SetValues(slot);
await this.database.SaveChangesAsync();
return this.Ok(oldSlot.Serialize(token.GameVersion));
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);
return this.BadRequest();
}
SlotEntity slotEntity = SlotBase.ConvertToEntity(slot);
slot.CreatorId = user.UserId;
slot.FirstUploaded = TimeHelper.TimestampMillis;
slot.LastUpdated = TimeHelper.TimestampMillis;
@ -254,7 +261,7 @@ public class PublishController : ControllerBase
slot.MinimumPlayers = Math.Clamp(slot.MinimumPlayers, 1, 4);
slot.MaximumPlayers = Math.Clamp(slot.MaximumPlayers, 1, 4);
this.database.Slots.Add(slot);
this.database.Slots.Add(slotEntity);
await this.database.SaveChangesAsync();
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);
return this.Ok(slot.Serialize(token.GameVersion));
return this.Ok(SlotBase.CreateFromEntity(slotEntity, this.GetToken()));
}
[HttpPost("unpublish/{id:int}")]
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.CreatorId != token.UserId) return this.StatusCode(403, "");
if (slot.CreatorId != token.UserId) return this.Forbid();
this.database.Slots.Remove(slot);

View file

@ -2,10 +2,10 @@
using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Extensions;
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.Token;
using LBPUnion.ProjectLighthouse.Types.Serialization;
using LBPUnion.ProjectLighthouse.Types.Users;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@ -30,15 +30,15 @@ public class ReviewController : ControllerBase
[HttpPost("rate/user/{slotId:int}")]
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);
if (slot == null) return this.StatusCode(403, "");
SlotEntity? slot = await this.database.Slots.Include(s => s.Creator).FirstOrDefaultAsync(s => s.SlotId == slotId);
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)
{
ratedLevel = new RatedLevel
ratedLevel = new RatedLevelEntity
{
SlotId = slotId,
UserId = token.UserId,
@ -59,15 +59,15 @@ public class ReviewController : ControllerBase
[HttpPost("dpadrate/user/{slotId:int}")]
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);
if (slot == null) return this.StatusCode(403, "");
SlotEntity? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slotId);
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)
{
ratedLevel = new RatedLevel
ratedLevel = new RatedLevelEntity
{
SlotId = slotId,
UserId = token.UserId,
@ -79,7 +79,7 @@ public class ReviewController : ControllerBase
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;
await this.database.SaveChangesAsync();
@ -90,20 +90,20 @@ public class ReviewController : ControllerBase
[HttpPost("postReview/user/{slotId:int}")]
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();
newReview.Text = CensorHelper.FilterMessage(newReview.Text);
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)
{
review = new Review
review = new ReviewEntity
{
SlotId = slotId,
ReviewerId = token.UserId,
@ -121,10 +121,10 @@ public class ReviewController : ControllerBase
review.Timestamp = TimeHelper.TimestampMillis;
// 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)
{
ratedLevel = new RatedLevel
ratedLevel = new RatedLevelEntity
{
SlotId = slotId,
UserId = token.UserId,
@ -144,58 +144,32 @@ public class ReviewController : ControllerBase
[HttpGet("reviewsFor/user/{slotId:int}")]
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();
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();
IQueryable<Review?> reviews = this.database.Reviews.ByGameVersion(gameVersion, true)
List<GameReview> reviews = await this.database.Reviews.ByGameVersion(gameVersion, true)
.Where(r => r.SlotId == slotId)
.Include(r => r.Reviewer)
.Include(r => r.Slot)
.OrderByDescending(r => r.ThumbsUp - r.ThumbsDown)
.ThenByDescending(r => r.Timestamp)
.Skip(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
(
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);
return this.Ok(new ReviewResponse(reviews, reviews.LastOrDefault()?.Timestamp ?? TimeHelper.TimestampMillis, pageStart + Math.Min(pageSize, 30)));
}
[HttpGet("reviewsBy/{username}")]
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();
@ -205,61 +179,32 @@ public class ReviewController : ControllerBase
if (targetUserId == 0) return this.BadRequest();
IEnumerable<Review?> reviews = this.database.Reviews.ByGameVersion(gameVersion, true)
.Include(r => r.Reviewer)
.Include(r => r.Slot)
List<GameReview> reviews = await this.database.Reviews.ByGameVersion(gameVersion, true)
.Where(r => r.ReviewerId == targetUserId)
.OrderByDescending(r => r.Timestamp)
.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
(
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);
return this.Ok(new ReviewResponse(reviews, reviews.LastOrDefault()?.Timestamp ?? TimeHelper.TimestampMillis, pageStart));
}
[HttpPost("rateReview/user/{slotId:int}/{username}")]
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);
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);
if (review == null) return this.StatusCode(400, "");
ReviewEntity? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == reviewerId);
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)
{
ratedReview = new RatedReview
ratedReview = new RatedReviewEntity
{
ReviewId = review.ReviewId,
UserId = token.UserId,
@ -301,18 +246,18 @@ public class ReviewController : ControllerBase
[HttpPost("deleteReview/user/{slotId:int}/{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();
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);
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);
if (review == null) return this.StatusCode(400, "");
ReviewEntity? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == reviewerId);
if (review == null) return this.BadRequest();
review.Deleted = true;
review.DeletedBy = DeletedBy.LevelAuthor;

View file

@ -4,12 +4,12 @@ using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.StorableLists.Stores;
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
using LBPUnion.ProjectLighthouse.Types.Levels;
using LBPUnion.ProjectLighthouse.Types.Logging;
using LBPUnion.ProjectLighthouse.Types.Serialization;
using LBPUnion.ProjectLighthouse.Types.Users;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@ -34,7 +34,7 @@ public class ScoreController : ControllerBase
[HttpPost("scoreboard/{slotType}/{id:int}/{childId:int}")]
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);
@ -44,16 +44,15 @@ public class ScoreController : ControllerBase
return this.BadRequest();
}
Score? score = await this.DeserializeBody<Score>();
GameScore? score = await this.DeserializeBody<GameScore>();
if (score == null)
{
Logger.Warn($"Rejecting score upload, score is null (slotType={slotType}, slotId={id}, user={username})", LogArea.Score);
return this.BadRequest();
}
// This only seems to happens on lbp2 versus levels, not sure why
if (score.PlayerIdCollection.Contains(':'))
score.PlayerIdCollection = score.PlayerIdCollection.Replace(':', ',');
// Workaround for parsing player ids of versus levels
if (score.PlayerIds.Length == 1 && score.PlayerIds[0].Contains(':')) score.PlayerIds = score.PlayerIds[0].Split(":");
if (score.PlayerIds.Length == 0)
{
@ -89,15 +88,14 @@ public class ScoreController : ControllerBase
SanitizationHelper.SanitizeStringsInClass(score);
if (slotType == "developer") id = await SlotHelper.GetPlaceholderSlotId(this.database, id, SlotType.Developer);
int slotId = id;
score.SlotId = id;
score.ChildSlotId = childId;
if (slotType == "developer") slotId = await SlotHelper.GetPlaceholderSlotId(this.database, slotId, SlotType.Developer);
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)
{
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();
}
@ -115,46 +113,56 @@ public class ScoreController : ControllerBase
break;
case GameVersion.LittleBigPlanetPSP:
case GameVersion.Unknown:
default: throw new ArgumentOutOfRangeException();
default:
return this.BadRequest();
}
Score playerScore = new()
{
PlayerIdCollection = string.Join(',', score.PlayerIds),
Type = score.Type,
Points = score.Points,
SlotId = score.SlotId,
ChildSlotId = score.ChildSlotId,
};
await this.database.SaveChangesAsync();
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.PlayerIdCollection == playerScore.PlayerIdCollection)
.Where(s => s.Type == playerScore.Type);
if (existingScore.Any())
.Where(s => s.PlayerIdCollection == playerIdCollection)
.Where(s => s.Type == score.Type)
.FirstOrDefaultAsync();
if (existingScore != null)
{
Score first = existingScore.First(s => s.SlotId == playerScore.SlotId);
playerScore.ScoreId = first.ScoreId;
playerScore.Points = Math.Max(first.Points, playerScore.Points);
this.database.Entry(first).CurrentValues.SetValues(playerScore);
existingScore.Points = Math.Max(existingScore.Points, score.Points);
}
else
{
ScoreEntity playerScore = new()
{
PlayerIdCollection = playerIdCollection,
Type = score.Type,
Points = score.Points,
SlotId = slotId,
ChildSlotId = childId,
};
this.database.Scores.Add(playerScore);
}
await this.database.SaveChangesAsync();
string myRanking = this.getScores(score.SlotId, score.Type, username, -1, 5, "scoreboardSegment", childId: score.ChildSlotId);
return this.Ok(myRanking);
return this.Ok(this.getScores(new LeaderboardOptions
{
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}/{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)
{
GameToken token = this.GetToken();
GameTokenEntity token = this.GetToken();
if (pageSize <= 0) return this.BadRequest();
@ -180,7 +188,17 @@ public class ScoreController : ControllerBase
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}")]
@ -188,7 +206,7 @@ public class ScoreController : ControllerBase
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
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();
@ -198,79 +216,60 @@ public class ScoreController : ControllerBase
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 string getScores
(
int slotId,
int type,
string username,
int pageStart = -1,
int pageSize = 5,
string rootName = "scores",
string[]? playerIds = null,
int? childId = 0
)
private class LeaderboardOptions
{
public int SlotId { get; set; }
public int ScoreType { get; set; }
public string TargetUsername { get; set; } = "";
public int PageStart { get; set; } = -1;
public int PageSize { get; set; } = 5;
public string RootName { get; set; } = "scores";
public string[]? TargetPlayerIds;
public int? ChildSlotId;
}
private ScoreboardResponse getScores(LeaderboardOptions options)
{
// This is hella ugly but it technically assigns the proper rank to a score
// var needed for Anonymous type returned from SELECT
var rankedScores = this.database.Scores
.Where(s => s.SlotId == slotId && s.Type == type)
.Where(s => s.ChildSlotId == 0 || s.ChildSlotId == childId)
var rankedScores = this.database.Scores.Where(s => s.SlotId == options.SlotId && s.Type == options.ScoreType)
.Where(s => s.ChildSlotId == 0 || s.ChildSlotId == options.ChildSlotId)
.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)
.ThenBy(s => s.ScoreId)
.ToList()
.Select
(
(s, rank) => new
{
Score = s,
Rank = rank + 1,
}
);
.Select((s, rank) => new
{
Score = s,
Rank = rank + 1,
})
.ToList();
// 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
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
(
string.Empty,
(current, rs) =>
{
rs.Score.Rank = rs.Rank;
return current + rs.Score.Serialize();
}
);
List<GameScore> gameScores = pagedScores.Select(ps => GameScore.CreateFromEntity(ps.Score, ps.Rank)).ToList();
string res;
if (myScore == null) res = LbpSerializer.StringElement(rootName, serializedScores);
else
res = LbpSerializer.TaggedStringElement
(
rootName,
serializedScores,
new Dictionary<string, object>
{
{
"yourScore", myScore.Score.Points
},
{
"yourRank", myScore.Rank
}, //This is the numerator of your position globally in the side menu.
{
"totalNumScores", rankedScores.Count()
}, // This is the denominator of your position globally in the side menu.
}
);
return res;
return new ScoreboardResponse(options.RootName, gameScores, myScore?.Score.Points ?? 0, myScore?.Rank ?? 0, rankedScores.Count);
}
}

View file

@ -1,10 +1,10 @@
#nullable enable
using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
using LBPUnion.ProjectLighthouse.Types.Levels;
using LBPUnion.ProjectLighthouse.Types.Serialization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
@ -35,7 +35,7 @@ public class SearchController : ControllerBase
string? keyName = "slots"
)
{
GameToken token = this.GetToken();
GameTokenEntity token = this.GetToken();
if (pageSize <= 0) return this.BadRequest();
@ -45,7 +45,7 @@ public class SearchController : ControllerBase
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)
.OrderBy(s => !s.TeamPick)
.ThenByDescending(s => s.FirstUploaded)
@ -61,11 +61,12 @@ public class SearchController : ControllerBase
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(LbpSerializer.TaggedStringElement(keyName, response, "total", dbQuery.Count()));
return this.Ok(new GenericSlotResponse(keyName, slots, await dbQuery.CountAsync(), 0));
}
// /LITTLEBIGPLANETPS3_XML?pageStart=1&pageSize=10&resultTypes[]=slot&resultTypes[]=playlist&resultTypes[]=user&adventure=dontCare&textFilter=qwer

View file

@ -1,14 +1,14 @@
#nullable enable
using System.Security.Cryptography;
using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Extensions;
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.Token;
using LBPUnion.ProjectLighthouse.Types.Levels;
using LBPUnion.ProjectLighthouse.Types.Matchmaking.Rooms;
using LBPUnion.ProjectLighthouse.Types.Serialization;
using LBPUnion.ProjectLighthouse.Types.Users;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@ -28,113 +28,102 @@ public class SlotsController : ControllerBase
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")]
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();
GameVersion gameVersion = token.GameVersion;
int targetUserId = await this.database.UserIdFromUsername(username);
if (targetUserId == 0) return this.NotFound();
int usedSlots = this.database.Slots.Count(s => s.CreatorId == targetUserId);
string response = Enumerable.Aggregate
(
this.database.Slots.Where(s => s.CreatorId == targetUserId)
.ByGameVersion(gameVersion, token.UserId == targetUserId, true)
.Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, usedSlots)),
string.Empty,
(current, slot) => current + slot.Serialize(token.GameVersion)
);
List<SlotBase> slots = await this.database.Slots.Where(s => s.CreatorId == targetUserId)
.ByGameVersion(token.GameVersion, token.UserId == targetUserId)
.Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, usedSlots))
.Select(s => SlotBase.CreateFromEntity(s, token))
.ToListAsync();
int start = pageStart + Math.Min(pageSize, usedSlots);
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")]
public async Task<IActionResult> GetSlotListAlt([FromQuery] int[] s)
public async Task<IActionResult> GetSlotListAlt([FromQuery(Name = "s")] int[] slotIds)
{
List<string?> serializedSlots = new();
foreach (int slotId in s)
GameTokenEntity token = this.GetToken();
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)
{
slot = await this.database.Slots.Where(t => t.InternalSlotId == slotId && t.Type == SlotType.Developer).FirstOrDefaultAsync();
if (slot == null)
{
serializedSlots.Add($"<slot type=\"developer\"><id>{slotId}</id></slot>");
slots.Add(new GameDeveloperSlot
{
SlotId = slotId,
});
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")]
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<string> serializedSlots = new();
List<SlotBase> slots = new();
foreach (int id in activeSlotIds)
{
int placeholderSlotId = await SlotHelper.GetPlaceholderSlotId(this.database, id, SlotType.Developer);
Slot slot = await this.database.Slots.FirstAsync(s => s.SlotId == placeholderSlotId);
serializedSlots.Add(slot.SerializeDevSlot());
SlotEntity slot = await this.database.Slots.FirstAsync(s => s.SlotId == placeholderSlotId);
slots.Add(SlotBase.CreateFromEntity(slot, token));
}
string serialized = serializedSlots.Aggregate(string.Empty, (current, slot) => current + slot);
return this.Ok(LbpSerializer.StringElement("slots", serialized));
return this.Ok(new GenericSlotResponse(slots));
}
[HttpGet("s/developer/{id:int}")]
public async Task<IActionResult> SDev(int id)
{
int slotId = await SlotHelper.GetPlaceholderSlotId(this.database, id, SlotType.Developer);
Slot slot = await this.database.Slots.FirstAsync(s => s.SlotId == slotId);
GameTokenEntity token = this.GetToken();
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}")]
public async Task<IActionResult> SUser(int id)
{
GameToken token = this.GetToken();
GameTokenEntity token = this.GetToken();
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();
RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == id && r.UserId == token.UserId);
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));
return this.Ok(SlotBase.CreateFromEntity(slot, token, SerializationMode.Full));
}
[HttpGet("slots/cool")]
@ -163,27 +152,28 @@ public class SlotsController : ControllerBase
[HttpGet("slots")]
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();
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)
.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 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}")]
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();
@ -191,10 +181,10 @@ public class SlotsController : ControllerBase
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();
string[] tags = targetSlot.LevelTags;
string[] tags = targetSlot.LevelTags(this.database);
List<int> slotIdsWithTag = this.database.RatedLevels
.Where(r => r.TagLBP1.Length > 0)
@ -202,105 +192,108 @@ public class SlotsController : ControllerBase
.Select(r => r.SlotId)
.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))
.OrderByDescending(s => s.PlaysLBP1)
.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 total = slotIdsWithTag.Count;
return this.Ok(generateSlotsResponse(response, start, total));
return this.Ok(new GenericSlotResponse(slots, total, start));
}
[HttpGet("slots/highestRated")]
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();
GameVersion gameVersion = token.GameVersion;
IEnumerable<Slot> slots = this.database.Slots.ByGameVersion(gameVersion, false, true)
.AsEnumerable()
List<SlotBase> slots = await this.database.Slots.ByGameVersion(gameVersion, false, true)
.ToAsyncEnumerable()
.OrderByDescending(s => s.RatingLBP1)
.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 total = await StatisticsHelper.SlotCount(this.database);
return this.Ok(generateSlotsResponse(response, start, total));
return this.Ok(new GenericSlotResponse(slots, total, start));
}
[HttpGet("slots/tag")]
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();
GameVersion gameVersion = token.GameVersion;
List<int> slotIdsWithTag = await this.database.RatedLevels.Where(r => r.TagLBP1.Length > 0)
.Where(r => r.TagLBP1 == tag)
.Select(s => s.SlotId)
.ToListAsync();
IQueryable<Slot> slots = this.database.Slots.Where(s => slotIdsWithTag.Contains(s.SlotId))
.ByGameVersion(gameVersion, false, true)
List<SlotBase> slots = await this.database.Slots.Where(s => slotIdsWithTag.Contains(s.SlotId))
.ByGameVersion(token.GameVersion, false, true)
.OrderByDescending(s => s.PlaysLBP1)
.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 total = slotIdsWithTag.Count;
return this.Ok(generateSlotsResponse(response, start, total));
return this.Ok(new GenericSlotResponse(slots, total, start));
}
[HttpGet("slots/mmpicks")]
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();
GameVersion gameVersion = token.GameVersion;
IQueryable<Slot> slots = this.database.Slots.Where(s => s.TeamPick)
.ByGameVersion(gameVersion, false, true)
List<SlotBase> slots = await this.database.Slots.Where(s => s.TeamPick)
.ByGameVersion(token.GameVersion, false, true)
.OrderByDescending(s => s.LastUpdated)
.Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30));
string response = Enumerable.Aggregate(slots, string.Empty, (current, slot) => current + slot.Serialize(gameVersion));
.Take(Math.Min(pageSize, 30))
.Select(s => SlotBase.CreateFromEntity(s, token))
.ToListAsync();
int start = pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots);
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")]
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();
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 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")]
@ -314,24 +307,25 @@ public class SlotsController : ControllerBase
[FromQuery] string? dateFilterType = null
)
{
GameToken token = this.GetToken();
GameTokenEntity token = this.GetToken();
if (pageSize <= 0) return this.BadRequest();
Random rand = new();
IEnumerable<Slot> slots = this.filterByRequest(gameFilterType, dateFilterType, token.GameVersion)
.AsEnumerable()
List<SlotBase> slots = await this.filterByRequest(gameFilterType, dateFilterType, token.GameVersion)
.AsAsyncEnumerable()
.OrderByDescending(s => s.Thumbsup)
.ThenBy(_ => rand.Next())
.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 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")]
@ -345,20 +339,19 @@ public class SlotsController : ControllerBase
[FromQuery] string? dateFilterType = null
)
{
GameToken token = this.GetToken();
GameTokenEntity token = this.GetToken();
if (pageSize <= 0) return this.BadRequest();
Random rand = new();
IEnumerable<Slot> slots = this.filterByRequest(gameFilterType, dateFilterType, token.GameVersion)
.AsEnumerable()
.OrderByDescending
(
List<SlotBase> slots = await this.filterByRequest(gameFilterType, dateFilterType, token.GameVersion)
.AsAsyncEnumerable()
.OrderByDescending(
// probably not the best way to do this?
s =>
{
return this.getGameFilter(gameFilterType, token.GameVersion) switch
return getGameFilter(gameFilterType, token.GameVersion) switch
{
GameVersion.LittleBigPlanet1 => s.PlaysLBP1Unique,
GameVersion.LittleBigPlanet2 => s.PlaysLBP2Unique,
@ -366,17 +359,17 @@ public class SlotsController : ControllerBase
GameVersion.LittleBigPlanetVita => s.PlaysLBP2Unique,
_ => s.PlaysUnique,
};
}
)
})
.ThenBy(_ => rand.Next())
.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 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")]
@ -390,24 +383,23 @@ public class SlotsController : ControllerBase
[FromQuery] string? dateFilterType = null
)
{
GameToken token = this.GetToken();
GameTokenEntity token = this.GetToken();
if (pageSize <= 0) return this.BadRequest();
Random rand = new();
IEnumerable<Slot> slots = this.filterByRequest(gameFilterType, dateFilterType, token.GameVersion)
.AsEnumerable()
List<SlotBase> slots = await this.filterByRequest(gameFilterType, dateFilterType, token.GameVersion)
.AsAsyncEnumerable()
.OrderByDescending(s => s.Hearts)
.ThenBy(_ => rand.Next())
.ThenBy(_ => RandomNumberGenerator.GetInt32(int.MaxValue))
.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 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
@ -421,7 +413,7 @@ public class SlotsController : ControllerBase
[FromQuery] bool? move = null
)
{
GameToken token = this.GetToken();
GameTokenEntity token = this.GetToken();
if (pageSize <= 0) return this.BadRequest();
@ -447,42 +439,45 @@ public class SlotsController : ControllerBase
.OrderByDescending(kvp => kvp.Value)
.Select(kvp => kvp.Key);
List<Slot> slots = new();
List<SlotBase> slots = new();
foreach (int slotId in orderedPlayersBySlotId)
{
Slot? slot = await this.database.Slots.ByGameVersion(token.GameVersion, false, true)
.FirstOrDefaultAsync(s => s.SlotId == slotId);
SlotBase? slot = await this.database.Slots.ByGameVersion(token.GameVersion, false, true)
.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
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 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;
if (version == GameVersion.LittleBigPlanetPSP) return GameVersion.LittleBigPlanetPSP;
return gameFilterType switch
return version switch
{
"lbp1" => GameVersion.LittleBigPlanet1,
"lbp2" => GameVersion.LittleBigPlanet2,
"lbp3" => GameVersion.LittleBigPlanet3,
"both" => GameVersion.LittleBigPlanet2, // LBP2 default option
null => GameVersion.LittleBigPlanet1,
_ => GameVersion.Unknown,
GameVersion.LittleBigPlanetVita => GameVersion.LittleBigPlanetVita,
GameVersion.LittleBigPlanetPSP => GameVersion.LittleBigPlanetPSP,
_ => gameFilterType switch
{
"lbp1" => GameVersion.LittleBigPlanet1,
"lbp2" => GameVersion.LittleBigPlanet2,
"lbp3" => GameVersion.LittleBigPlanet3,
"both" => GameVersion.LittleBigPlanet2, // LBP2 default option
null => GameVersion.LittleBigPlanet1,
_ => GameVersion.Unknown,
},
};
}
private IQueryable<Slot> filterByRequest(string? gameFilterType, string? dateFilterType, GameVersion version)
private IQueryable<SlotEntity> filterByRequest(string? gameFilterType, string? dateFilterType, GameVersion version)
{
if (version == GameVersion.LittleBigPlanetVita || version == GameVersion.LittleBigPlanetPSP || version == GameVersion.Unknown)
{
@ -498,9 +493,9 @@ public class SlotsController : ControllerBase
_ => 0,
};
GameVersion gameVersion = this.getGameFilter(gameFilterType, version);
GameVersion gameVersion = getGameFilter(gameFilterType, version);
IQueryable<Slot> whereSlots;
IQueryable<SlotEntity> whereSlots;
// ReSharper disable once ConvertIfStatementToConditionalTernaryExpression
if (gameFilterType == "both")

View file

@ -1,9 +1,9 @@
using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Serialization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Types.Serialization;
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 mmPicksCount = await StatisticsHelper.TeamPickCountForGame(this.database, this.GetToken().GameVersion);
return this.Ok
(
LbpSerializer.StringElement
("planetStats", LbpSerializer.StringElement("totalSlotCount", totalSlotCount) + LbpSerializer.StringElement("mmPicksCount", mmPicksCount))
);
return this.Ok(new PlanetStatsResponse(totalSlotCount, mmPicksCount));
}
[HttpGet("planetStats/totalLevelCount")]

View file

@ -4,12 +4,14 @@ using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Files;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Users;
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
using LBPUnion.ProjectLighthouse.Types.Levels;
using LBPUnion.ProjectLighthouse.Types.Logging;
using LBPUnion.ProjectLighthouse.Types.Serialization;
using LBPUnion.ProjectLighthouse.Types.Users;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@ -30,57 +32,41 @@ public class UserController : ControllerBase
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}")]
public async Task<IActionResult> GetUser(string username)
{
GameToken token = this.GetToken();
string? user = await this.getSerializedUser(username, token.GameVersion);
UserEntity? user = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
if (user == null) return this.NotFound();
return this.Ok(user);
return this.Ok(GameUser.CreateFromEntity(user, this.GetToken().GameVersion));
}
[HttpGet("users")]
public async Task<IActionResult> GetUserAlt([FromQuery] string[] u)
public async Task<IActionResult> GetUserAlt([FromQuery(Name = "u")] string[] userList)
{
List<string?> serializedUsers = new();
foreach (string userId in u) serializedUsers.Add(await this.getSerializedUserPicture(userId));
List<MinimalUserProfile> minimalUserList = new();
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(LbpSerializer.StringElement("users", serialized));
return this.Ok(new MinimalUserListResponse(minimalUserList));
}
[HttpPost("updateUser")]
public async Task<IActionResult> UpdateUser()
{
GameToken token = this.GetToken();
GameTokenEntity token = this.GetToken();
User? user = await this.database.UserFromGameToken(token);
if (user == null) return this.StatusCode(403, "");
UserEntity? user = await this.database.UserFromGameToken(token);
if (user == null) return this.Forbid();
UserUpdate? update = await this.DeserializeBody<UserUpdate>("updateUser", "user");
@ -118,17 +104,17 @@ public class UserController : ControllerBase
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)
{
// ReSharper disable once MergeIntoNegatedPattern
if (updateSlot.Type != SlotType.User || updateSlot.Location == null || updateSlot.SlotId == 0) continue;
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == updateSlot.SlotId);
SlotEntity? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == updateSlot.SlotId);
if (slot == null) 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:
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")]
[Produces("text/json")]
public async Task<IActionResult> UpdateMyPins()
{
User? user = await this.database.UserFromGameToken(this.GetToken());
if (user == null) return this.StatusCode(403, "");
UserEntity? user = await this.database.UserFromGameToken(this.GetToken());
if (user == null) return this.Forbid();
string bodyString = await this.ReadBodyAsync();

View file

@ -18,7 +18,7 @@ public class SetLastContactMiddleware : MiddlewareDBContext
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.
GameToken? gameToken = await database.GameTokenFromRequest(context.Request);
GameTokenEntity? gameToken = await database.GameTokenFromRequest(context.Request);
if (gameToken?.GameVersion == GameVersion.LittleBigPlanet1)
// Ignore UserFromGameToken null because user must exist for a token to exist

View file

@ -42,7 +42,7 @@ public class GameServerStartup
(
options =>
{
options.OutputFormatters.Add(new XmlOutputFormatter());
options.OutputFormatters.Add(new LbpOutputFormatter());
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");
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");
this.Context.Items["Token"] = gameToken;

View file

@ -20,6 +20,6 @@ public static class CategoryHelper
Categories.Add(new LuckyDipCategory());
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 LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Levels;
using LBPUnion.ProjectLighthouse.Types.Logging;
using LBPUnion.ProjectLighthouse.Types.Serialization;
using LBPUnion.ProjectLighthouse.Types.Users;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Categories;
public abstract class CategoryWithUser : Category
{
public abstract Slot? GetPreviewSlot(DatabaseContext database, User user);
public override Slot? GetPreviewSlot(DatabaseContext database)
public abstract SlotEntity? GetPreviewSlot(DatabaseContext database, UserEntity user);
public override SlotEntity? GetPreviewSlot(DatabaseContext database)
{
#if DEBUG
Logger.Error("tried to get preview slot without user on CategoryWithUser", LogArea.Category);
@ -22,7 +23,7 @@ public abstract class CategoryWithUser : Category
return null;
}
public abstract int GetTotalSlots(DatabaseContext database, User user);
public abstract int GetTotalSlots(DatabaseContext database, UserEntity user);
public override int GetTotalSlots(DatabaseContext database)
{
#if DEBUG
@ -32,14 +33,14 @@ public abstract class CategoryWithUser : Category
return -1;
}
public abstract IQueryable<Slot> GetSlots(DatabaseContext database, User user, int pageStart, int pageSize);
public override IList<Slot> GetSlots(DatabaseContext database, int pageStart, int pageSize)
public abstract IQueryable<SlotEntity> GetSlots(DatabaseContext database, UserEntity user, int pageStart, int pageSize);
public override IList<SlotEntity> GetSlots(DatabaseContext database, int pageStart, int pageSize)
{
#if DEBUG
Logger.Error("tried to get slots without user on CategoryWithUser", LogArea.Category);
if (Debugger.IsAttached) Debugger.Break();
#endif
return new List<Slot>();
return new List<SlotEntity>();
}
public new string Serialize(DatabaseContext database)
@ -48,35 +49,13 @@ public abstract class CategoryWithUser : Category
return string.Empty;
}
public string Serialize(DatabaseContext database, User user)
public GameCategory Serialize(DatabaseContext database, UserEntity user)
{
Slot? previewSlot = this.GetPreviewSlot(database, user);
string previewResults = "";
if (previewSlot != null)
previewResults = LbpSerializer.TaggedStringElement
(
"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)
);
List<SlotBase> slots = new()
{
SlotBase.CreateFromEntity(this.GetPreviewSlot(database, user), GameVersion.LittleBigPlanet3, user.UserId),
};
int totalSlots = this.GetTotalSlots(database, user);
return GameCategory.CreateFromEntity(this, new GenericSlotResponse(slots, totalSlots, 2));
}
}

View file

@ -21,7 +21,7 @@ public class CustomCategory : Category
this.SlotIds = slotIds.ToList();
}
public CustomCategory(DatabaseCategory category)
public CustomCategory(DatabaseCategoryEntity category)
{
this.Name = category.Name;
this.Description = category.Description;
@ -35,8 +35,8 @@ public class CustomCategory : Category
public sealed override string Description { get; set; }
public sealed override string IconHash { 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 IQueryable<Slot> GetSlots
public override SlotEntity? GetPreviewSlot(DatabaseContext database) => database.Slots.FirstOrDefault(s => s.SlotId == this.SlotIds[0]);
public override IQueryable<SlotEntity> GetSlots
(DatabaseContext database, int pageStart, int pageSize)
=> database.Slots.ByGameVersion(GameVersion.LittleBigPlanet3).Where(s => this.SlotIds.Contains(s.SlotId));
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 IconHash { get; set; } = "g820611";
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)
.Where(h => h.Slot.Type == SlotType.User && !h.Slot.Hidden && h.Slot.GameVersion <= GameVersion.LittleBigPlanet3)
.OrderByDescending(h => h.HeartedLevelId)
@ -24,7 +24,7 @@ public class HeartedCategory : CategoryWithUser
.ByGameVersion(GameVersion.LittleBigPlanet3, false, false, true)
.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)
.Where(h => h.Slot.Type == SlotType.User && !h.Slot.Hidden && h.Slot.GameVersion <= GameVersion.LittleBigPlanet3)
.OrderByDescending(h => h.HeartedLevelId)
@ -34,5 +34,5 @@ public class HeartedCategory : CategoryWithUser
.Skip(Math.Max(0, pageStart))
.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 IconHash { get; set; } = "g820603";
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 IEnumerable<Slot> GetSlots
public override SlotEntity? GetPreviewSlot(DatabaseContext database) => database.Slots.Where(s => s.Type == SlotType.User).AsEnumerable().MaxBy(s => s.Thumbsup);
public override IEnumerable<SlotEntity> GetSlots
(DatabaseContext database, int pageStart, int pageSize)
=> database.Slots.ByGameVersion(GameVersion.LittleBigPlanet3, false, true)
.AsEnumerable()

View file

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

View file

@ -13,12 +13,12 @@ public class MostPlayedCategory : Category
public override string Description { get; set; } = "The most played content";
public override string IconHash { get; set; } = "g820608";
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)
.OrderByDescending(s => s.PlaysLBP1Unique + s.PlaysLBP2Unique + s.PlaysLBP3Unique)
.ThenByDescending(s => s.PlaysLBP1 + s.PlaysLBP2 + s.PlaysLBP3)
.FirstOrDefault();
public override IQueryable<Slot> GetSlots
public override IQueryable<SlotEntity> GetSlots
(DatabaseContext database, int pageStart, int pageSize)
=> database.Slots.ByGameVersion(GameVersion.LittleBigPlanet3, false, true)
.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 IconHash { get; set; } = "g820623";
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 IQueryable<Slot> GetSlots
public override SlotEntity? GetPreviewSlot(DatabaseContext database) => database.Slots.Where(s => s.Type == SlotType.User).OrderByDescending(s => s.FirstUploaded).FirstOrDefault();
public override IQueryable<SlotEntity> GetSlots
(DatabaseContext database, int pageStart, int pageSize)
=> database.Slots.ByGameVersion(GameVersion.LittleBigPlanet3, false, true)
.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 IconHash { get; set; } = "g820614";
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)
.Where(q => q.Slot.Type == SlotType.User && !q.Slot.Hidden && q.Slot.GameVersion <= GameVersion.LittleBigPlanet3)
.OrderByDescending(q => q.QueuedLevelId)
@ -24,7 +24,7 @@ public class QueueCategory : CategoryWithUser
.ByGameVersion(GameVersion.LittleBigPlanet3, false, false, true)
.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)
.Where(q => q.Slot.Type == SlotType.User && !q.Slot.Hidden && q.Slot.GameVersion <= GameVersion.LittleBigPlanet3)
.OrderByDescending(q => q.QueuedLevelId)
@ -34,5 +34,5 @@ public class QueueCategory : CategoryWithUser
.Skip(Math.Max(0, pageStart - 1))
.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 IconHash { get; set; } = "g820626";
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 IQueryable<Slot> GetSlots
public override SlotEntity? GetPreviewSlot(DatabaseContext database) => database.Slots.OrderByDescending(s => s.FirstUploaded).FirstOrDefault(s => s.TeamPick);
public override IQueryable<SlotEntity> GetSlots
(DatabaseContext database, int pageStart, int pageSize)
=> database.Slots.ByGameVersion(GameVersion.LittleBigPlanet3, false, true)
.OrderByDescending(s => s.FirstUploaded)

View file

@ -1,4 +1,5 @@
using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Types.Serialization;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Misc;
@ -9,7 +10,7 @@ namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Misc;
/// </summary>
[XmlRoot("resources")]
[XmlType("resources")]
public class ResourceList
public class ResourceList : ILbpSerializable
{
[XmlElement("resource")]
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
using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Types.Serialization;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Users;
[XmlRoot("privacySettings")]
[XmlType("privacySettings")]
public class PrivacySettings
public class PrivacySettings : ILbpSerializable
{
[XmlElement("levelVisiblity")]
public string? LevelVisibility { get; set; }
[XmlElement("profileVisiblity")]
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")]
public async Task<IActionResult> DeleteReport([FromRoute] int id)
{
User? user = this.database.UserFromWebRequest(this.Request);
if (user == null || !user.IsAdmin) return this.StatusCode(403, "");
UserEntity? user = this.database.UserFromWebRequest(this.Request);
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();
List<string> hashes = new()
@ -49,10 +49,10 @@ public class AdminReportController : ControllerBase
[HttpGet("dismiss")]
public async Task<IActionResult> DismissReport([FromRoute] int id)
{
User? user = this.database.UserFromWebRequest(this.Request);
if (user == null || !user.IsModerator) return this.StatusCode(403, "");
UserEntity? user = this.database.UserFromWebRequest(this.Request);
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();
FileHelper.DeleteResource(report.JpegHash);

View file

@ -28,10 +28,10 @@ public class AdminUserController : ControllerBase
/// </summary>
[HttpGet("wipePlanets")]
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();
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();
string[] hashes = {
@ -47,7 +47,7 @@ public class AdminUserController : ControllerBase
if (string.IsNullOrWhiteSpace(hash)) continue;
// 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 ||
u.PlanetHashLBP3 == hash ||
u.PlanetHashLBPVita == hash)
@ -57,7 +57,7 @@ public class AdminUserController : ControllerBase
System.Diagnostics.Debug.Assert(users.Count != 0);
// Reset each users' hash.
foreach (User userWithPlanet in users)
foreach (UserEntity userWithPlanet in users)
{
userWithPlanet.PlanetHashLBP2 = "";
userWithPlanet.PlanetHashLBP3 = "";
@ -92,10 +92,10 @@ public class AdminUserController : ControllerBase
[HttpPost("/admin/user/{id:int}/setPermissionLevel")]
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();
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 (role != PermissionLevel.Banned)

View file

@ -22,7 +22,7 @@ public class AuthenticationController : ControllerBase
[HttpGet("unlink/{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");
Platform[] invalidTokens;
@ -48,10 +48,10 @@ public class AuthenticationController : ControllerBase
[HttpGet("approve/{id:int}")]
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");
PlatformLinkAttempt? linkAttempt = await this.database.PlatformLinkAttempts
PlatformLinkAttemptEntity? linkAttempt = await this.database.PlatformLinkAttempts
.FirstOrDefaultAsync(l => l.PlatformLinkAttemptId == id);
if (linkAttempt == null) return this.NotFound();
@ -76,10 +76,10 @@ public class AuthenticationController : ControllerBase
[HttpGet("deny/{id:int}")]
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");
PlatformLinkAttempt? linkAttempt = await this.database.PlatformLinkAttempts
PlatformLinkAttemptEntity? linkAttempt = await this.database.PlatformLinkAttempts
.FirstOrDefaultAsync(l => l.PlatformLinkAttemptId == id);
if (linkAttempt == null) return this.NotFound();

View file

@ -20,10 +20,10 @@ public class ModerationCaseController : ControllerBase
[HttpGet("dismiss")]
public async Task<IActionResult> DismissCase([FromRoute] int id)
{
User? user = this.database.UserFromWebRequest(this.Request);
if (user == null || !user.IsModerator) return this.StatusCode(403, "");
UserEntity? user = this.database.UserFromWebRequest(this.Request);
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();
@case.DismissedAt = DateTime.Now;

View file

@ -1,6 +1,7 @@
using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Serialization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
@ -17,9 +18,9 @@ public class ModerationRemovalController : ControllerBase
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");
T? item = await getHandler(user, id);
@ -34,9 +35,9 @@ public class ModerationRemovalController : ControllerBase
[HttpGet("deleteScore/{scoreId:int}")]
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;
return user.IsModerator ? score : null;
@ -46,10 +47,10 @@ public class ModerationRemovalController : ControllerBase
[HttpGet("deleteComment/{commentId:int}")]
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");
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.Deleted) return this.Redirect(callbackUrl ?? "~/");
@ -82,10 +83,10 @@ public class ModerationRemovalController : ControllerBase
[HttpGet("deleteReview/{reviewId:int}")]
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");
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.Deleted) return this.Redirect(callbackUrl ?? "~/");
@ -103,9 +104,9 @@ public class ModerationRemovalController : ControllerBase
[HttpGet("deletePhoto/{photoId:int}")]
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 (!user.IsModerator && photo.CreatorId != user.UserId) return null;

View file

@ -23,10 +23,10 @@ public class ModerationSlotController : ControllerBase
[HttpGet("teamPick")]
public async Task<IActionResult> TeamPick([FromRoute] int id)
{
User? user = this.database.UserFromWebRequest(this.Request);
if (user == null || !user.IsModerator) return this.StatusCode(403, "");
UserEntity? user = this.database.UserFromWebRequest(this.Request);
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();
slot.TeamPick = true;
@ -42,10 +42,10 @@ public class ModerationSlotController : ControllerBase
[HttpGet("removeTeamPick")]
public async Task<IActionResult> RemoveTeamPick([FromRoute] int id)
{
User? user = this.database.UserFromWebRequest(this.Request);
if (user == null || !user.IsModerator) return this.StatusCode(403, "");
UserEntity? user = this.database.UserFromWebRequest(this.Request);
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();
slot.TeamPick = false;
@ -58,10 +58,10 @@ public class ModerationSlotController : ControllerBase
[HttpGet("delete")]
public async Task<IActionResult> DeleteLevel([FromRoute] int id)
{
User? user = this.database.UserFromWebRequest(this.Request);
if (user == null || !user.IsModerator) return this.StatusCode(403, "");
UserEntity? user = this.database.UserFromWebRequest(this.Request);
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();
await this.database.RemoveSlot(slot);

View file

@ -30,10 +30,10 @@ public class SlotPageController : ControllerBase
[HttpGet("unpublish")]
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");
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.CreatorId != token.UserId) return this.Redirect("~/slot/" + id);
@ -48,7 +48,7 @@ public class SlotPageController : ControllerBase
[HttpGet("rateComment")]
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");
await this.database.RateComment(token.UserId, commentId, rating);
@ -59,7 +59,7 @@ public class SlotPageController : ControllerBase
[HttpPost("postComment")]
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 (msg == null)
@ -90,10 +90,10 @@ public class SlotPageController : ControllerBase
{
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");
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();
await this.database.HeartLevel(token.UserId, heartedSlot);
@ -106,10 +106,10 @@ public class SlotPageController : ControllerBase
{
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");
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();
await this.database.UnheartLevel(token.UserId, heartedSlot);
@ -122,10 +122,10 @@ public class SlotPageController : ControllerBase
{
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");
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();
await this.database.QueueLevel(token.UserId, queuedSlot);
@ -138,10 +138,10 @@ public class SlotPageController : ControllerBase
{
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");
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();
await this.database.UnqueueLevel(token.UserId, queuedSlot);

View file

@ -24,7 +24,7 @@ public class UserPageController : ControllerBase
[HttpGet("rateComment")]
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");
await this.database.RateComment(token.UserId, commentId.GetValueOrDefault(), rating.GetValueOrDefault());
@ -35,7 +35,7 @@ public class UserPageController : ControllerBase
[HttpPost("postComment")]
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 (msg == null)
@ -64,10 +64,10 @@ public class UserPageController : ControllerBase
[HttpGet("heart")]
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");
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();
await this.database.HeartUser(token.UserId, heartedUser);
@ -78,10 +78,10 @@ public class UserPageController : ControllerBase
[HttpGet("unheart")]
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");
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();
await this.database.UnheartUser(token.UserId, heartedUser);
@ -92,10 +92,10 @@ public class UserPageController : ControllerBase
[HttpGet("block")]
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");
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();
await this.database.BlockUser(token.UserId, blockedUser);
@ -106,10 +106,10 @@ public class UserPageController : ControllerBase
[HttpGet("unblock")]
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");
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();
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));
public static Task<IHtmlContent> ToHtml<T>
(
this Slot slot,
this SlotEntity slot,
IHtmlHelper<T> helper,
ViewDataDictionary<T> viewData,
User? user,
UserEntity? user,
string callbackUrl,
string language = "",
string timeZone = "",
@ -55,6 +55,6 @@ public static class PartialExtensions
.WithKeyValue("IsMini", isMini)
.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));
}

View file

@ -14,14 +14,14 @@ public class UserRequiredRedirectMiddleware : MiddlewareDBContext
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"))
{
await this.next(ctx);
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)
{
await this.next(ctx);

View file

@ -38,7 +38,7 @@
}
<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 class="ui blue segment">

View file

@ -9,7 +9,7 @@ namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages.Admin;
public class AdminApiKeyPageModel : BaseLayout
{
public List<ApiKey> ApiKeys = new();
public List<ApiKeyEntity> ApiKeys = new();
public int KeyCount;
public AdminApiKeyPageModel(DatabaseContext database) : base(database)
@ -17,7 +17,7 @@ public class AdminApiKeyPageModel : BaseLayout
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.IsAdmin) return this.NotFound();
@ -29,10 +29,10 @@ public class AdminApiKeyPageModel : BaseLayout
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();
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();
this.Database.APIKeys.Remove(apiKey);
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)
{
User? user = this.Database.UserFromWebRequest(this.Request);
UserEntity? user = this.Database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("~/login");
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>
<div class="ui grid">
@foreach (User user in Model.Users)
@foreach (UserEntity user in Model.Users)
{
string color;
string subtitle;

View file

@ -11,13 +11,13 @@ public class AdminPanelUsersPage : BaseLayout
{
public int UserCount;
public List<User> Users = new();
public List<UserEntity> Users = new();
public AdminPanelUsersPage(DatabaseContext database) : base(database)
{}
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.IsAdmin) return this.NotFound();

View file

@ -12,11 +12,11 @@ public class AdminSetGrantedSlotsPage : BaseLayout
public AdminSetGrantedSlotsPage(DatabaseContext database) : base(database)
{}
public User? TargetedUser;
public UserEntity? TargetedUser;
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();
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)
{
User? user = this.Database.UserFromWebRequest(this.Request);
UserEntity? user = this.Database.UserFromWebRequest(this.Request);
if (user == null || !user.IsAdmin) return this.NotFound();
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>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>
}

View file

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

View file

@ -21,14 +21,14 @@ public class CompleteEmailVerificationPage : BaseLayout
{
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)
{
this.Error = "Invalid verification token";
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)
{
@ -50,7 +50,7 @@ public class CompleteEmailVerificationPage : BaseLayout
if (user.Password != null) return this.Page();
// if user's account was created automatically
WebToken webToken = new()
WebTokenEntity webToken = new()
{
ExpiresAt = DateTime.Now.AddDays(7),
Verified = true,

View file

@ -19,7 +19,7 @@ public class SendVerificationEmailPage : BaseLayout
{
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.EmailAddressVerified) return this.Redirect("/");

View file

@ -22,7 +22,7 @@ public class SetEmailForm : BaseLayout
public IActionResult OnGet()
{
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");
return this.Page();
@ -33,10 +33,10 @@ public class SetEmailForm : BaseLayout
{
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");
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 (!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);
<div class="ui red segment">

View file

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

View file

@ -46,7 +46,7 @@
@{
int i = 0;
foreach (User user in Model.PlayersOnline)
foreach (UserEntity user in Model.PlayersOnline)
{
i++;
@await user.ToLink(Html, ViewData, language, timeZone, true)
@ -66,7 +66,7 @@
<h1><i class="star icon"></i>@Model.Translate(LandingPageStrings.LatestTeamPicks)</h1>
<div class="ui divider"></div>
<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)
<br>
@ -83,7 +83,7 @@
<h1><i class="globe americas icon"></i>@Model.Translate(LandingPageStrings.NewestLevels)</h1>
<div class="ui divider"></div>
<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)
<br>

View file

@ -17,15 +17,15 @@ public class LandingPage : BaseLayout
{}
public int PendingAuthAttempts;
public List<User> PlayersOnline = new();
public List<UserEntity> PlayersOnline = new();
public List<Slot>? LatestTeamPicks;
public List<Slot>? NewestLevels;
public List<SlotEntity>? LatestTeamPicks;
public List<SlotEntity>? NewestLevels;
[UsedImplicitly]
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)

View file

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

View file

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

View file

@ -12,7 +12,7 @@ public class LogoutPage : BaseLayout
{}
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("~/");
this.Database.WebTokens.Remove(token);

View file

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

View file

@ -42,7 +42,7 @@ public class PasswordResetRequestForm : BaseLayout
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)
{
@ -51,7 +51,7 @@ public class PasswordResetRequestForm : BaseLayout
return this.Page();
}
PasswordResetToken token = new()
PasswordResetTokenEntity token = new()
{
Created = DateTime.Now,
UserId = user.UserId,

View file

@ -15,7 +15,7 @@ public class PasswordResetRequiredPage : BaseLayout
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.PasswordResetRequired) return this.Redirect("~/passwordReset");

View file

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

View file

@ -54,7 +54,7 @@ public class RegisterForm : BaseLayout
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)
{
this.Error = this.Translate(ErrorStrings.UsernameTaken);
@ -74,9 +74,9 @@ public class RegisterForm : BaseLayout
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,
UserToken = CryptoHelper.GenerateAuthToken(),

View file

@ -12,7 +12,7 @@
<p>There are @Model.UserCount banned users.</p>
@foreach (User user in Model.Users)
@foreach (UserEntity user in Model.Users)
{
<div class="ui segment">
@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 List<User> Users = new();
public List<UserEntity> Users = new();
public int PageAmount;
@ -23,7 +23,7 @@ public class BannedUsersPage : BaseLayout
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");
this.Users = await this.Database.Users

View file

@ -21,7 +21,7 @@
<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)))
}

View file

@ -13,7 +13,7 @@ public class CasePage : BaseLayout
public CasePage(DatabaseContext database) : base(database)
{}
public List<ModerationCase> Cases = new();
public List<ModerationCaseEntity> Cases = new();
public int CaseCount;
public int DismissedCaseCount;
@ -23,7 +23,7 @@ public class CasePage : BaseLayout
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.IsModerator) return this.NotFound();

View file

@ -15,7 +15,7 @@
<p>There are @Model.SlotCount hidden levels.</p>
@foreach (Slot slot in Model.Slots)
@foreach (SlotEntity slot in Model.Slots)
{
<div class="ui segment">
@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 List<Slot> Slots = new();
public List<SlotEntity> Slots = new();
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");
this.Slots = await this.Database.Slots

View file

@ -16,7 +16,7 @@ public class ModPanelPage : BaseLayout
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.IsModerator) return this.NotFound();

View file

@ -17,7 +17,7 @@ public class NewCasePage : BaseLayout
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 (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)
{
User? user = this.Database.UserFromWebRequest(this.Request);
UserEntity? user = this.Database.UserFromWebRequest(this.Request);
if (user == null || !user.IsModerator) return this.Redirect("/login");
if (type == null) return this.BadRequest();
@ -43,7 +43,7 @@ public class NewCasePage : BaseLayout
// if id is invalid then return bad request
if (!await type.Value.IsIdValid((int)affectedId, this.Database)) return this.BadRequest();
ModerationCase @case = new()
ModerationCaseEntity @case = new()
{
Type = type.Value,
Reason = reason,

View file

@ -14,15 +14,15 @@ public class ReportPage : BaseLayout
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)
{
User? user = this.Database.UserFromWebRequest(this.Request);
UserEntity? user = this.Database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("~/login");
if (!user.IsAdmin) return this.NotFound();
GriefReport? report = await this.Database.Reports
GriefReportEntity? report = await this.Database.Reports
.Include(r => r.ReportingPlayer)
.FirstOrDefaultAsync(r => r.ReportId == reportId);
if (report == null) return this.NotFound();

View file

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

View file

@ -19,7 +19,7 @@ public class ReportsPage : BaseLayout
public int ReportCount;
public List<GriefReport> Reports = new();
public List<GriefReportEntity> Reports = new();
public string SearchValue = "";
@ -28,7 +28,7 @@ public class ReportsPage : BaseLayout
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.IsModerator) return this.NotFound();
@ -51,7 +51,7 @@ public class ReportsPage : BaseLayout
.Take(ServerStatics.PageSize)
.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>();

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">
@Html.AntiForgeryToken()

View file

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

View file

@ -23,7 +23,7 @@
<div class="ui list">
@for(int i = 0; i < Model.Scores.Count; i++)
{
Score score = Model.Scores[i];
ScoreEntity score = Model.Scores[i];
string[] playerIds = score.PlayerIds;
DatabaseContext database = Model.Database;
<div class="item">
@ -41,7 +41,7 @@
<div class="list" style="padding-top: 0">
@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">
<i class="minus icon" style="padding-top: 9px"></i>
<div class="content" style="padding-left: 0">

View file

@ -1,12 +1,14 @@
@using LBPUnion.ProjectLighthouse.Database
@using LBPUnion.ProjectLighthouse.Localization
@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 timeZone = (string?)ViewData["TimeZone"] ?? TimeZoneInfo.Local.Id;
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">

View file

@ -3,7 +3,7 @@
@using LBPUnion.ProjectLighthouse.Types.Entities.Level
@using LBPUnion.ProjectLighthouse.Types.Entities.Profile
@using LBPUnion.ProjectLighthouse.Types.Moderation.Cases
@model LBPUnion.ProjectLighthouse.Types.Entities.Moderation.ModerationCase
@model LBPUnion.ProjectLighthouse.Types.Entities.Moderation.ModerationCaseEntity
@{
DatabaseContext database = new();
@ -62,7 +62,7 @@
@if (Model.Type.AffectsLevel())
{
Slot? slot = await Model.GetSlotAsync(database);
SlotEntity? slot = await Model.GetSlotAsync(database);
if (slot != null)
{
<p><strong>Affected level:</strong> <a href="/slot/@slot.SlotId">@slot.Name</a></p>
@ -70,7 +70,7 @@
}
else if (Model.Type.AffectsUser())
{
User? user = await Model.GetUserAsync(database);
UserEntity? user = await Model.GetUserAsync(database);
if (user != null)
{
<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.Types.Entities.Profile
@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;
@ -83,15 +84,17 @@
</p>
}
<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)
}
</div>
@{
PhotoSubject[] subjects = Model.PhotoSubjects.ToArray();
foreach (PhotoSubject subject in subjects) subject.Username = subject.User.Username;
GamePhotoSubject[] subjects = Model.PhotoSubjects.Select(GamePhotoSubject.CreateFromEntity).ToArray();
foreach (GamePhotoSubject subject in subjects)
{
subject.Username = Model.PhotoSubjects.Where(ps => ps.UserId == subject.UserId).Select(ps => ps.User.Username).First();
}
}
<script>

View file

@ -1,5 +1,5 @@
@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;

View file

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

View file

@ -6,10 +6,10 @@
@using LBPUnion.ProjectLighthouse.Types.Entities.Profile
@using LBPUnion.ProjectLighthouse.Types.Users
@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();

View file

@ -1,6 +1,7 @@
@using LBPUnion.ProjectLighthouse.Database
@using LBPUnion.ProjectLighthouse.Localization
@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;
@ -40,14 +41,22 @@
}
</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>
<i>@Model.Status.ToTranslatedString(language, timeZone)</i>
<i>@Model.GetStatus(context).ToTranslatedString(language, timeZone)</i>
</span>
<div class="cardStatsUnderTitle">
<i class="pink heart icon" title="Hearts"></i> <span>@Model.Hearts</span>
<i class="blue comment icon" title="Comments"></i> <span>@Model.Comments</span>
<i class="green upload icon" title="Uploaded Levels"></i><span>@Model.UsedSlots</span>
<i class="purple camera icon" title="Uploaded Photos"></i><span>@Model.PhotosByMe</span>
<i class="pink heart icon" title="Hearts"></i> <span>@hearts</span>
<i class="blue comment icon" title="Comments"></i> <span>@comments</span>
<i class="green upload icon" title="Uploaded Levels"></i><span>@levels</span>
<i class="purple camera icon" title="Uploaded Photos"></i><span>@photos</span>
</div>
</div>
</div>

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