Lots of bug fixes and performance improvements (#410)

* Many bug fixes and performance enhancements

* Fix warnings and speed up photos with me

* Finish refactoring user serialization

* Finish refactoring user serialization
Use GameTokens instead of User when possible
Prevent negative page sizes

* Fix debug compilation

* Add gzip compression to example nginx config

* Remove deflate changes

* Add UsernameFromWebToken

Co-authored-by: Jayden <jvyden@jvyden.xyz>
This commit is contained in:
Josh 2022-08-12 19:56:17 -05:00 committed by GitHub
parent 8dbd0e63ff
commit d23a264b8a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 625 additions and 505 deletions

View file

@ -31,6 +31,8 @@ public class SlotEndpoints : ApiEndpointController
[ProducesResponseType(typeof(List<MinimalSlot>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(List<MinimalSlot>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetSlots([FromQuery] int limit = 20, [FromQuery] int skip = 0) public async Task<IActionResult> GetSlots([FromQuery] int limit = 20, [FromQuery] int skip = 0)
{ {
if (skip < 0) skip = 0;
if (limit < 0) limit = 0;
limit = Math.Min(ServerStatics.PageSize, limit); limit = Math.Min(ServerStatics.PageSize, limit);
IEnumerable<MinimalSlot> minimalSlots = (await this.database.Slots.OrderByDescending(s => s.FirstUploaded).Skip(skip).Take(limit).ToListAsync()).Select IEnumerable<MinimalSlot> minimalSlots = (await this.database.Slots.OrderByDescending(s => s.FirstUploaded).Skip(skip).Take(limit).ToListAsync()).Select

View file

@ -4,7 +4,6 @@ using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Configuration; using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.PlayerData; using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles; using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;

View file

@ -26,12 +26,12 @@ public class CommentController : ControllerBase
[HttpPost("rateComment/{slotType}/{slotId:int}")] [HttpPost("rateComment/{slotType}/{slotId:int}")]
public async Task<IActionResult> RateComment([FromQuery] int commentId, [FromQuery] int rating, string? username, string? slotType, int slotId) public async Task<IActionResult> RateComment([FromQuery] int commentId, [FromQuery] int rating, string? username, string? slotType, int slotId)
{ {
User? user = await this.database.UserFromGameRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (user == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest(); if (username == null && (SlotHelper.IsTypeInvalid(slotType) || slotId == 0)) return this.BadRequest();
bool success = await this.database.RateComment(user, commentId, rating); bool success = await this.database.RateComment(token.UserId, commentId, rating);
if (!success) return this.BadRequest(); if (!success) return this.BadRequest();
return this.Ok(); return this.Ok();
@ -41,8 +41,10 @@ public class CommentController : ControllerBase
[HttpGet("userComments/{username}")] [HttpGet("userComments/{username}")]
public async Task<IActionResult> GetComments([FromQuery] int pageStart, [FromQuery] int pageSize, string? username, string? slotType, int slotId) public async Task<IActionResult> GetComments([FromQuery] int pageStart, [FromQuery] int pageSize, string? username, string? slotType, int slotId)
{ {
User? user = await this.database.UserFromGameRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (user == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest();
int targetId = slotId; int targetId = slotId;
CommentType type = CommentType.Level; CommentType type = CommentType.Level;
@ -75,29 +77,28 @@ public class CommentController : ControllerBase
(c => c.Poster) (c => c.Poster)
.Where(c => c.TargetId == targetId && c.Type == type) .Where(c => c.TargetId == targetId && c.Type == type)
.OrderByDescending(c => c.Timestamp) .OrderByDescending(c => c.Timestamp)
.Skip(pageStart - 1) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30)) .Take(Math.Min(pageSize, 30))
.ToListAsync(); .ToListAsync();
string outputXml = comments.Aggregate string outputXml = comments.Aggregate
(string.Empty, (current, comment) => current + comment.Serialize(this.getReaction(user.UserId, comment.CommentId).Result)); (string.Empty, (current, comment) => current + comment.Serialize(this.getReaction(token.UserId, comment.CommentId).Result));
return this.Ok(LbpSerializer.StringElement("comments", outputXml)); return this.Ok(LbpSerializer.StringElement("comments", outputXml));
} }
private async Task<int> getReaction(int userId, int commentId) private async Task<int> getReaction(int userId, int commentId)
{ {
Reaction? reaction = await this.database.Reactions.FirstOrDefaultAsync(r => r.UserId == userId && r.TargetId == commentId); return await this.database.Reactions.Where(r => r.UserId == userId && r.TargetId == commentId)
if (reaction == null) return 0; .Select(r => r.Rating)
.FirstOrDefaultAsync();
return reaction.Rating;
} }
[HttpPost("postUserComment/{username}")] [HttpPost("postUserComment/{username}")]
[HttpPost("postComment/{slotType}/{slotId:int}")] [HttpPost("postComment/{slotType}/{slotId:int}")]
public async Task<IActionResult> PostComment(string? username, string? slotType, int slotId) public async Task<IActionResult> PostComment(string? username, string? slotType, int slotId)
{ {
User? poster = await this.database.UserFromGameRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (poster == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
this.Request.Body.Position = 0; this.Request.Body.Position = 0;
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync(); string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
@ -119,7 +120,7 @@ public class CommentController : ControllerBase
if (slotType == "developer") targetId = await SlotHelper.GetPlaceholderSlotId(this.database, targetId, SlotType.Developer); if (slotType == "developer") targetId = await SlotHelper.GetPlaceholderSlotId(this.database, targetId, SlotType.Developer);
bool success = await this.database.PostComment(poster, targetId, type, comment.Message); bool success = await this.database.PostComment(token.UserId, targetId, type, comment.Message);
if (success) return this.Ok(); if (success) return this.Ok();
return this.BadRequest(); return this.BadRequest();
@ -129,8 +130,8 @@ public class CommentController : ControllerBase
[HttpPost("deleteComment/{slotType}/{slotId:int}")] [HttpPost("deleteComment/{slotType}/{slotId:int}")]
public async Task<IActionResult> DeleteComment([FromQuery] int commentId, string? username, string? slotType, int slotId) public async Task<IActionResult> DeleteComment([FromQuery] int commentId, string? username, string? slotType, int slotId)
{ {
User? user = await this.database.UserFromGameRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (user == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
Comment? comment = await this.database.Comments.FirstOrDefaultAsync(c => c.CommentId == commentId); Comment? comment = await this.database.Comments.FirstOrDefaultAsync(c => c.CommentId == commentId);
if (comment == null) return this.NotFound(); if (comment == null) return this.NotFound();
@ -140,12 +141,12 @@ public class CommentController : ControllerBase
if (slotType == "developer") slotId = await SlotHelper.GetPlaceholderSlotId(this.database, slotId, SlotType.Developer); if (slotType == "developer") slotId = await SlotHelper.GetPlaceholderSlotId(this.database, slotId, SlotType.Developer);
// if you are not the poster // if you are not the poster
if (comment.PosterUserId != user.UserId) if (comment.PosterUserId != token.UserId)
{ {
if (comment.Type == CommentType.Profile) if (comment.Type == CommentType.Profile)
{ {
// if you aren't the poster and aren't the profile owner // if you aren't the poster and aren't the profile owner
if (comment.TargetId != user.UserId) if (comment.TargetId != token.UserId)
{ {
return this.StatusCode(403, ""); return this.StatusCode(403, "");
} }
@ -154,7 +155,7 @@ public class CommentController : ControllerBase
{ {
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == comment.TargetId); Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == comment.TargetId);
// if you aren't the creator of the level // if you aren't the creator of the level
if (slot == null || slot.CreatorId != user.UserId || slotId != slot.SlotId) if (slot == null || slot.CreatorId != token.UserId || slotId != slot.SlotId)
{ {
return this.StatusCode(403, ""); return this.StatusCode(403, "");
} }
@ -162,7 +163,7 @@ public class CommentController : ControllerBase
} }
comment.Deleted = true; comment.Deleted = true;
comment.DeletedBy = user.Username; comment.DeletedBy = await this.database.UsernameFromGameToken(token);
comment.DeletedType = "user"; comment.DeletedType = "user";
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();

View file

@ -26,8 +26,8 @@ public class FriendsController : ControllerBase
[HttpPost("npdata")] [HttpPost("npdata")]
public async Task<IActionResult> NPData() public async Task<IActionResult> NPData()
{ {
User? user = await this.database.UserFromGameRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (user == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
this.Request.Body.Position = 0; this.Request.Body.Position = 0;
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync(); string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
@ -56,8 +56,8 @@ public class FriendsController : ControllerBase
blockedUsers.Add(blockedUser.UserId); blockedUsers.Add(blockedUser.UserId);
} }
UserFriendData? friendStore = UserFriendStore.GetUserFriendData(user.UserId); UserFriendData? friendStore = UserFriendStore.GetUserFriendData(token.UserId);
if (friendStore == null) friendStore = UserFriendStore.CreateUserFriendData(user.UserId); if (friendStore == null) friendStore = UserFriendStore.CreateUserFriendData(token.UserId);
friendStore.FriendIds = friends.Select(u => u.UserId).ToList(); friendStore.FriendIds = friends.Select(u => u.UserId).ToList();
friendStore.BlockedIds = blockedUsers; friendStore.BlockedIds = blockedUsers;

View file

@ -2,7 +2,6 @@
using LBPUnion.ProjectLighthouse.Levels; using LBPUnion.ProjectLighthouse.Levels;
using LBPUnion.ProjectLighthouse.PlayerData; using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles; using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -23,38 +22,35 @@ public class EnterLevelController : ControllerBase
[HttpPost("play/user/{slotId}")] [HttpPost("play/user/{slotId}")]
public async Task<IActionResult> PlayLevel(int slotId) public async Task<IActionResult> PlayLevel(int slotId)
{ {
User? user = await this.database.UserFromGameRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (user == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slotId); Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slotId);
if (slot == null) return this.StatusCode(403, ""); if (slot == null) return this.StatusCode(403, "");
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
GameVersion gameVersion = token.GameVersion; GameVersion gameVersion = token.GameVersion;
IQueryable<VisitedLevel> visited = this.database.VisitedLevels.Where(s => s.SlotId == slotId && s.UserId == user.UserId); IQueryable<VisitedLevel> visited = this.database.VisitedLevels.Where(s => s.SlotId == slotId && s.UserId == token.UserId);
VisitedLevel? v; VisitedLevel? v;
if (!visited.Any()) if (!visited.Any())
{ {
switch (gameVersion) switch (gameVersion)
{ {
case GameVersion.LittleBigPlanet2: case GameVersion.LittleBigPlanet2:
case GameVersion.LittleBigPlanetVita:
slot.PlaysLBP2Unique++; slot.PlaysLBP2Unique++;
break; break;
case GameVersion.LittleBigPlanet3: case GameVersion.LittleBigPlanet3:
slot.PlaysLBP3Unique++; slot.PlaysLBP3Unique++;
break; break;
case GameVersion.LittleBigPlanetVita:
slot.PlaysLBPVitaUnique++;
break;
default: return this.BadRequest(); default: return this.BadRequest();
} }
v = new VisitedLevel(); v = new VisitedLevel
v.SlotId = slotId; {
v.UserId = user.UserId; SlotId = slotId,
UserId = token.UserId,
};
this.database.VisitedLevels.Add(v); this.database.VisitedLevels.Add(v);
} }
else else
@ -67,6 +63,7 @@ public class EnterLevelController : ControllerBase
switch (gameVersion) switch (gameVersion)
{ {
case GameVersion.LittleBigPlanet2: case GameVersion.LittleBigPlanet2:
case GameVersion.LittleBigPlanetVita:
slot.PlaysLBP2++; slot.PlaysLBP2++;
v.PlaysLBP2++; v.PlaysLBP2++;
break; break;
@ -74,12 +71,9 @@ public class EnterLevelController : ControllerBase
slot.PlaysLBP3++; slot.PlaysLBP3++;
v.PlaysLBP3++; v.PlaysLBP3++;
break; break;
case GameVersion.LittleBigPlanetVita:
slot.PlaysLBPVita++;
v.PlaysLBPVita++;
break;
case GameVersion.LittleBigPlanetPSP: throw new NotImplementedException(); case GameVersion.LittleBigPlanetPSP: throw new NotImplementedException();
case GameVersion.Unknown: case GameVersion.Unknown:
case GameVersion.LittleBigPlanet1:
default: default:
return this.BadRequest(); return this.BadRequest();
} }
@ -93,21 +87,23 @@ public class EnterLevelController : ControllerBase
[HttpGet("enterLevel/{id:int}")] [HttpGet("enterLevel/{id:int}")]
public async Task<IActionResult> EnterLevel(int id) public async Task<IActionResult> EnterLevel(int id)
{ {
User? user = await this.database.UserFromGameRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (user == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound(); if (slot == null) return this.NotFound();
IQueryable<VisitedLevel> visited = this.database.VisitedLevels.Where(s => s.SlotId == id && s.UserId == user.UserId); IQueryable<VisitedLevel> visited = this.database.VisitedLevels.Where(s => s.SlotId == id && s.UserId == token.UserId);
VisitedLevel? v; VisitedLevel? v;
if (!visited.Any()) if (!visited.Any())
{ {
slot.PlaysLBP1Unique++; slot.PlaysLBP1Unique++;
v = new VisitedLevel(); v = new VisitedLevel
v.SlotId = id; {
v.UserId = user.UserId; SlotId = id,
UserId = token.UserId,
};
this.database.VisitedLevels.Add(v); this.database.VisitedLevels.Add(v);
} }
else else

View file

@ -86,9 +86,9 @@ public class MatchController : ControllerBase
// Check how many people are online in release builds, disabled for debug for ..well debugging. // Check how many people are online in release builds, disabled for debug for ..well debugging.
#if DEBUG #if DEBUG
if (matchData is FindBestRoom diveInData) else if (matchData is FindBestRoom diveInData)
#else #else
if (matchData is FindBestRoom diveInData && MatchHelper.UserLocations.Count > 1) else if (matchData is FindBestRoom diveInData && MatchHelper.UserLocations.Count > 1)
#endif #endif
{ {
FindBestRoomResponse? response = RoomHelper.FindBestRoom FindBestRoomResponse? response = RoomHelper.FindBestRoom
@ -102,7 +102,7 @@ public class MatchController : ControllerBase
return this.Ok($"[{{\"StatusCode\":200}},{serialized}]"); return this.Ok($"[{{\"StatusCode\":200}},{serialized}]");
} }
if (matchData is CreateRoom createRoom && MatchHelper.UserLocations.Count >= 1) else if (matchData is CreateRoom createRoom && MatchHelper.UserLocations.Count >= 1)
{ {
List<int> users = new(); List<int> users = new();
foreach (string playerUsername in createRoom.Players) foreach (string playerUsername in createRoom.Players)
@ -117,7 +117,7 @@ public class MatchController : ControllerBase
RoomHelper.CreateRoom(users, gameToken.GameVersion, gameToken.Platform, createRoom.RoomSlot); RoomHelper.CreateRoom(users, gameToken.GameVersion, gameToken.Platform, createRoom.RoomSlot);
} }
if (matchData is UpdatePlayersInRoom updatePlayersInRoom) else if (matchData is UpdatePlayersInRoom updatePlayersInRoom)
{ {
Room? room = RoomHelper.Rooms.FirstOrDefault(r => r.HostId == user.UserId); Room? room = RoomHelper.Rooms.FirstOrDefault(r => r.HostId == user.UserId);
@ -133,7 +133,7 @@ public class MatchController : ControllerBase
} }
room.PlayerIds = users.Select(u => u.UserId).ToList(); room.PlayerIds = users.Select(u => u.UserId).ToList();
RoomHelper.CleanupRooms(null, room); await RoomHelper.CleanupRooms(null, room);
} }
} }

View file

@ -4,8 +4,6 @@ using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.PlayerData; using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
@ -39,8 +37,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.";
[HttpGet("eula")] [HttpGet("eula")]
public async Task<IActionResult> Eula() public async Task<IActionResult> Eula()
{ {
User? user = await this.database.UserFromGameRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (user == null) return this.StatusCode(403, ""); // ReSharper disable once ConvertIfStatementToReturnStatement
if (token == null) return this.StatusCode(403, "");
return this.Ok($"{license}\n{ServerConfiguration.Instance.EulaText}"); return this.Ok($"{license}\n{ServerConfiguration.Instance.EulaText}");
} }
@ -48,38 +47,30 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.";
[HttpGet("announce")] [HttpGet("announce")]
public async Task<IActionResult> Announce() public async Task<IActionResult> Announce()
{ {
#if !DEBUG GameToken? token = await this.database.GameTokenFromRequest(this.Request);
User? user = await this.database.UserFromGameRequest(this.Request); if (token == null) return this.StatusCode(403, "");
if (user == null) return this.StatusCode(403, "");
#else
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request);
if (userAndToken == null) return this.StatusCode(403, ""); string username = await this.database.UsernameFromGameToken(token);
// ReSharper disable once PossibleInvalidOperationException
User user = userAndToken.Value.Item1;
GameToken gameToken = userAndToken.Value.Item2;
#endif
string announceText = ServerConfiguration.Instance.AnnounceText; string announceText = ServerConfiguration.Instance.AnnounceText;
announceText = announceText.Replace("%user", user.Username); announceText = announceText.Replace("%user", username);
announceText = announceText.Replace("%id", user.UserId.ToString()); announceText = announceText.Replace("%id", token.UserId.ToString());
return this.Ok return this.Ok
( (
announceText + announceText +
#if DEBUG #if DEBUG
"\n\n---DEBUG INFO---\n" + "\n\n---DEBUG INFO---\n" +
$"user.UserId: {user.UserId}\n" + $"user.UserId: {token.UserId}\n" +
$"token.Approved: {gameToken.Approved}\n" + $"token.Approved: {token.Approved}\n" +
$"token.Used: {gameToken.Used}\n" + $"token.Used: {token.Used}\n" +
$"token.UserLocation: {gameToken.UserLocation}\n" + $"token.UserLocation: {token.UserLocation}\n" +
$"token.GameVersion: {gameToken.GameVersion}\n" + $"token.GameVersion: {token.GameVersion}\n" +
$"token.ExpiresAt: {gameToken.ExpiresAt.ToString(CultureInfo.CurrentCulture)}\n" + $"token.ExpiresAt: {token.ExpiresAt.ToString(CultureInfo.CurrentCulture)}\n" +
"---DEBUG INFO---" + "---DEBUG INFO---" +
#endif #endif
(announceText != "" ? "\n" : "") (string.IsNullOrWhiteSpace(announceText) ? "" : "\n")
); );
} }
@ -92,15 +83,17 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.";
[HttpPost("filter")] [HttpPost("filter")]
public async Task<IActionResult> Filter() public async Task<IActionResult> Filter()
{ {
User? user = await this.database.UserFromGameRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (user == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
string response = await new StreamReader(this.Request.Body).ReadToEndAsync(); string response = await new StreamReader(this.Request.Body).ReadToEndAsync();
string scannedText = CensorHelper.ScanMessage(response); string scannedText = CensorHelper.ScanMessage(response);
Logger.Info($"{user.Username}: {response} / {scannedText}", LogArea.Filter); string username = await this.database.UsernameFromGameToken(token);
Logger.Info($"{username}: {response} / {scannedText}", LogArea.Filter);
return this.Ok(scannedText); return this.Ok(scannedText);
} }

View file

@ -2,6 +2,7 @@
using System.Xml.Serialization; using System.Xml.Serialization;
using Discord; using Discord;
using LBPUnion.ProjectLighthouse.Configuration; using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Levels; using LBPUnion.ProjectLighthouse.Levels;
using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Logging;
@ -63,7 +64,7 @@ public class PhotosController : ControllerBase
case SlotType.User: case SlotType.User:
{ {
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.Type == SlotType.User && s.SlotId == photoSlot.SlotId); Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.Type == SlotType.User && s.SlotId == photoSlot.SlotId);
if (slot != null) validLevel = slot.RootLevel == photoSlot.RootLevel; if (slot != null && !string.IsNullOrEmpty(slot.RootLevel)) validLevel = true;
break; break;
} }
case SlotType.Pod: case SlotType.Pod:
@ -89,8 +90,19 @@ public class PhotosController : ControllerBase
if (photo.Timestamp > TimeHelper.Timestamp) photo.Timestamp = TimeHelper.Timestamp; if (photo.Timestamp > TimeHelper.Timestamp) photo.Timestamp = TimeHelper.Timestamp;
// Check for duplicate photo subjects
List<string> subjectUserIds = new(4);
foreach (PhotoSubject subject in photo.Subjects) foreach (PhotoSubject subject in photo.Subjects)
{ {
if (subjectUserIds.Contains(subject.Username) && !string.IsNullOrEmpty(subject.Username)) return this.BadRequest();
subjectUserIds.Add(subject.Username);
}
foreach (PhotoSubject subject in photo.Subjects)
{
if (string.IsNullOrEmpty(subject.Username)) continue;
subject.User = await this.database.Users.FirstOrDefaultAsync(u => u.Username == subject.Username); subject.User = await this.database.Users.FirstOrDefaultAsync(u => u.Username == subject.Username);
if (subject.User == null) continue; if (subject.User == null) continue;
@ -103,18 +115,7 @@ public class PhotosController : ControllerBase
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
// Check for duplicate photo subjects photo.PhotoSubjectIds = photo.Subjects.Where(s => s.UserId != 0).Select(subject => subject.PhotoSubjectId.ToString()).ToArray();
List<int> subjectUserIds = new(4);
foreach (PhotoSubject subject in photo.Subjects)
{
if (subjectUserIds.Contains(subject.UserId)) return this.BadRequest();
subjectUserIds.Add(subject.UserId);
}
photo.PhotoSubjectIds = photo.Subjects.Select(subject => subject.PhotoSubjectId.ToString()).ToArray();
// photo.Slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == photo.SlotId);
Logger.Debug($"Adding PhotoSubjectCollection ({photo.PhotoSubjectCollection}) to photo", LogArea.Photos); Logger.Debug($"Adding PhotoSubjectCollection ({photo.PhotoSubjectCollection}) to photo", LogArea.Photos);
@ -139,8 +140,10 @@ public class PhotosController : ControllerBase
[HttpGet("photos/{slotType}/{id:int}")] [HttpGet("photos/{slotType}/{id:int}")]
public async Task<IActionResult> SlotPhotos([FromQuery] int pageStart, [FromQuery] int pageSize, string slotType, int id) public async Task<IActionResult> SlotPhotos([FromQuery] int pageStart, [FromQuery] int pageSize, string slotType, int id)
{ {
User? user = await this.database.UserFromGameRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (user == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest();
if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest(); if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest();
@ -149,7 +152,7 @@ public class PhotosController : ControllerBase
List<Photo> photos = await this.database.Photos.Include(p => p.Creator) List<Photo> photos = await this.database.Photos.Include(p => p.Creator)
.Where(p => p.SlotId == id) .Where(p => p.SlotId == id)
.OrderByDescending(s => s.Timestamp) .OrderByDescending(s => s.Timestamp)
.Skip(pageStart - 1) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30)) .Take(Math.Min(pageSize, 30))
.ToListAsync(); .ToListAsync();
string response = photos.Aggregate(string.Empty, (s, photo) => s + photo.Serialize(id, SlotHelper.ParseType(slotType))); string response = photos.Aggregate(string.Empty, (s, photo) => s + photo.Serialize(id, SlotHelper.ParseType(slotType)));
@ -159,15 +162,19 @@ public class PhotosController : ControllerBase
[HttpGet("photos/by")] [HttpGet("photos/by")]
public async Task<IActionResult> UserPhotosBy([FromQuery] string user, [FromQuery] int pageStart, [FromQuery] int pageSize) public async Task<IActionResult> UserPhotosBy([FromQuery] string user, [FromQuery] int pageStart, [FromQuery] int pageSize)
{ {
User? userFromQuery = await this.database.Users.FirstOrDefaultAsync(u => u.Username == user); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
// ReSharper disable once ConditionIsAlwaysTrueOrFalse if (token == null) return this.StatusCode(403, "");
if (userFromQuery == null) return this.NotFound();
if (pageSize <= 0) return this.BadRequest();
int targetUserId = await this.database.Users.Where(u => u.Username == user).Select(u => u.UserId).FirstOrDefaultAsync();
if (targetUserId == 0) return this.NotFound();
List<Photo> photos = await this.database.Photos.Include List<Photo> photos = await this.database.Photos.Include
(p => p.Creator) (p => p.Creator)
.Where(p => p.CreatorId == userFromQuery.UserId) .Where(p => p.CreatorId == targetUserId)
.OrderByDescending(s => s.Timestamp) .OrderByDescending(s => s.Timestamp)
.Skip(pageStart - 1) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30)) .Take(Math.Min(pageSize, 30))
.ToListAsync(); .ToListAsync();
string response = photos.Aggregate(string.Empty, (s, photo) => s + photo.Serialize()); string response = photos.Aggregate(string.Empty, (s, photo) => s + photo.Serialize());
@ -177,19 +184,31 @@ public class PhotosController : ControllerBase
[HttpGet("photos/with")] [HttpGet("photos/with")]
public async Task<IActionResult> UserPhotosWith([FromQuery] string user, [FromQuery] int pageStart, [FromQuery] int pageSize) public async Task<IActionResult> UserPhotosWith([FromQuery] string user, [FromQuery] int pageStart, [FromQuery] int pageSize)
{ {
User? userFromQuery = await this.database.Users.FirstOrDefaultAsync(u => u.Username == user); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
// ReSharper disable once ConditionIsAlwaysTrueOrFalse if (token == null) return this.StatusCode(403, "");
if (userFromQuery == null) return this.NotFound();
List<Photo> photos = new(); if (pageSize <= 0) return this.BadRequest();
foreach (Photo photo in this.database.Photos.Include
(p => p.Creator)) photos.AddRange(photo.Subjects.Where(subject => subject.User.UserId == userFromQuery.UserId).Select(_ => photo));
string response = photos.OrderByDescending int targetUserId = await this.database.Users.Where(u => u.Username == user).Select(u => u.UserId).FirstOrDefaultAsync();
(s => s.Timestamp) if (targetUserId == 0) return this.NotFound();
.Skip(pageStart - 1)
.Take(Math.Min(pageSize, 30)) List<int> photoSubjectIds = new();
.Aggregate(string.Empty, (s, photo) => s + photo.Serialize()); photoSubjectIds.AddRange(this.database.PhotoSubjects.Where(p => p.UserId == targetUserId).Select(p => p.PhotoSubjectId));
var list = this.database.Photos.Select(p => new
{
p.PhotoId,
p.PhotoSubjectCollection,
}).ToList();
List<int> photoIds = (from v in list where photoSubjectIds.Any(ps => v.PhotoSubjectCollection.Contains(ps.ToString())) select v.PhotoId).ToList();
string response = Enumerable.Aggregate(
this.database.Photos.Where(p => photoIds.Any(id => p.PhotoId == id) && p.CreatorId != targetUserId)
.OrderByDescending(s => s.Timestamp)
.Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30)),
string.Empty,
(current, photo) => current + photo.Serialize());
return this.Ok(LbpSerializer.StringElement("photos", response)); return this.Ok(LbpSerializer.StringElement("photos", response));
} }
@ -197,13 +216,18 @@ public class PhotosController : ControllerBase
[HttpPost("deletePhoto/{id:int}")] [HttpPost("deletePhoto/{id:int}")]
public async Task<IActionResult> DeletePhoto(int id) public async Task<IActionResult> DeletePhoto(int id)
{ {
User? user = await this.database.UserFromGameRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (user == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
Photo? photo = await this.database.Photos.FirstOrDefaultAsync(p => p.PhotoId == id); Photo? photo = await this.database.Photos.FirstOrDefaultAsync(p => p.PhotoId == id);
if (photo == null) return this.NotFound(); if (photo == null) return this.NotFound();
if (photo.CreatorId != user.UserId) return this.StatusCode(401, ""); if (photo.CreatorId != token.UserId) return this.StatusCode(401, "");
foreach (string idStr in photo.PhotoSubjectIds)
{
if (!int.TryParse(idStr, out int subjectId)) throw new InvalidCastException(idStr + " is not a valid number.");
this.database.PhotoSubjects.RemoveWhere(p => p.PhotoSubjectId == subjectId);
}
this.database.Photos.Remove(photo); this.database.Photos.Remove(photo);
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
return this.Ok(); return this.Ok();

View file

@ -3,9 +3,8 @@ using System.Buffers;
using System.IO.Pipelines; using System.IO.Pipelines;
using System.Xml.Serialization; using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Files; using LBPUnion.ProjectLighthouse.Files;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles; using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -32,8 +31,8 @@ public class ResourcesController : ControllerBase
[HttpPost("showNotUploaded")] [HttpPost("showNotUploaded")]
public async Task<IActionResult> FilterResources() public async Task<IActionResult> FilterResources()
{ {
User? user = await this.database.UserFromGameRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (user == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync(); string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
@ -52,8 +51,8 @@ public class ResourcesController : ControllerBase
[HttpGet("r/{hash}")] [HttpGet("r/{hash}")]
public async Task<IActionResult> GetResource(string hash) public async Task<IActionResult> GetResource(string hash)
{ {
User? user = await this.database.UserFromGameRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (user == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
string path = FileHelper.GetResourcePath(hash); string path = FileHelper.GetResourcePath(hash);
@ -67,8 +66,8 @@ public class ResourcesController : ControllerBase
[HttpPost("upload/{hash}")] [HttpPost("upload/{hash}")]
public async Task<IActionResult> UploadResource(string hash) public async Task<IActionResult> UploadResource(string hash)
{ {
User? user = await this.database.UserFromGameRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (user == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
string assetsDirectory = FileHelper.ResourcePath; string assetsDirectory = FileHelper.ResourcePath;
string path = FileHelper.GetResourcePath(hash); string path = FileHelper.GetResourcePath(hash);

View file

@ -1,5 +1,4 @@
#nullable enable #nullable enable
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Extensions; using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Levels; using LBPUnion.ProjectLighthouse.Levels;
using LBPUnion.ProjectLighthouse.PlayerData; using LBPUnion.ProjectLighthouse.PlayerData;
@ -31,6 +30,8 @@ public class ListController : ControllerBase
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest();
GameVersion gameVersion = token.GameVersion; GameVersion gameVersion = token.GameVersion;
IEnumerable<Slot> queuedLevels = this.database.QueuedLevels.Where(q => q.User.Username == username) IEnumerable<Slot> queuedLevels = this.database.QueuedLevels.Where(q => q.User.Username == username)
@ -38,7 +39,7 @@ public class ListController : ControllerBase
.Include(q => q.Slot.Location) .Include(q => q.Slot.Location)
.Select(q => q.Slot) .Select(q => q.Slot)
.ByGameVersion(gameVersion) .ByGameVersion(gameVersion)
.Skip(pageStart - 1) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30)) .Take(Math.Min(pageSize, 30))
.AsEnumerable(); .AsEnumerable();
@ -54,13 +55,13 @@ public class ListController : ControllerBase
[HttpPost("lolcatftw/add/user/{id:int}")] [HttpPost("lolcatftw/add/user/{id:int}")]
public async Task<IActionResult> AddQueuedLevel(int id) public async Task<IActionResult> AddQueuedLevel(int id)
{ {
User? user = await this.database.UserFromGameRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (user == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound(); if (slot == null) return this.NotFound();
await this.database.QueueLevel(user, slot); await this.database.QueueLevel(token.UserId, slot);
return this.Ok(); return this.Ok();
} }
@ -68,13 +69,13 @@ public class ListController : ControllerBase
[HttpPost("lolcatftw/remove/user/{id:int}")] [HttpPost("lolcatftw/remove/user/{id:int}")]
public async Task<IActionResult> RemoveQueuedLevel(int id) public async Task<IActionResult> RemoveQueuedLevel(int id)
{ {
User? user = await this.database.UserFromGameRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (user == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound(); if (slot == null) return this.NotFound();
await this.database.UnqueueLevel(user, slot); await this.database.UnqueueLevel(token.UserId, slot);
return this.Ok(); return this.Ok();
} }
@ -82,10 +83,10 @@ public class ListController : ControllerBase
[HttpPost("lolcatftw/clear")] [HttpPost("lolcatftw/clear")]
public async Task<IActionResult> ClearQueuedLevels() public async Task<IActionResult> ClearQueuedLevels()
{ {
User? user = await this.database.UserFromGameRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (user == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
this.database.QueuedLevels.RemoveRange(this.database.QueuedLevels.Where(q => q.UserId == user.UserId)); this.database.QueuedLevels.RemoveRange(this.database.QueuedLevels.Where(q => q.UserId == token.UserId));
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
@ -102,14 +103,19 @@ public class ListController : ControllerBase
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest();
GameVersion gameVersion = token.GameVersion; GameVersion gameVersion = token.GameVersion;
IEnumerable<Slot> heartedLevels = this.database.HeartedLevels.Where(q => q.User.Username == username) User? targetUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
if (targetUser == null) return this.StatusCode(403, "");
IEnumerable<Slot> heartedLevels = this.database.HeartedLevels.Where(q => q.UserId == targetUser.UserId)
.Include(q => q.Slot.Creator) .Include(q => q.Slot.Creator)
.Include(q => q.Slot.Location) .Include(q => q.Slot.Location)
.Select(q => q.Slot) .Select(q => q.Slot)
.ByGameVersion(gameVersion) .ByGameVersion(gameVersion)
.Skip(pageStart - 1) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30)) .Take(Math.Min(pageSize, 30))
.AsEnumerable(); .AsEnumerable();
@ -119,7 +125,7 @@ public class ListController : ControllerBase
( (
LbpSerializer.TaggedStringElement("favouriteSlots", response, new Dictionary<string, object> LbpSerializer.TaggedStringElement("favouriteSlots", response, new Dictionary<string, object>
{ {
{ "total", this.database.HeartedLevels.Include(q => q.User).Count(q => q.User.Username == username) }, { "total", this.database.HeartedLevels.Count(q => q.UserId == targetUser.UserId) },
{ "hint_start", pageStart + Math.Min(pageSize, 30) }, { "hint_start", pageStart + Math.Min(pageSize, 30) },
}) })
); );
@ -128,13 +134,13 @@ public class ListController : ControllerBase
[HttpPost("favourite/slot/user/{id:int}")] [HttpPost("favourite/slot/user/{id:int}")]
public async Task<IActionResult> AddFavouriteSlot(int id) public async Task<IActionResult> AddFavouriteSlot(int id)
{ {
User? user = await this.database.UserFromGameRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (user == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound(); if (slot == null) return this.NotFound();
await this.database.HeartLevel(user, slot); await this.database.HeartLevel(token.UserId, slot);
return this.Ok(); return this.Ok();
} }
@ -142,13 +148,13 @@ public class ListController : ControllerBase
[HttpPost("unfavourite/slot/user/{id:int}")] [HttpPost("unfavourite/slot/user/{id:int}")]
public async Task<IActionResult> RemoveFavouriteSlot(int id) public async Task<IActionResult> RemoveFavouriteSlot(int id)
{ {
User? user = await this.database.UserFromGameRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (user == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound(); if (slot == null) return this.NotFound();
await this.database.UnheartLevel(user, slot); await this.database.UnheartLevel(token.UserId, slot);
return this.Ok(); return this.Ok();
} }
@ -165,22 +171,27 @@ public class ListController : ControllerBase
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
IEnumerable<HeartedProfile> heartedProfiles = this.database.HeartedProfiles.Include User? targetUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
(q => q.User) if (targetUser == null) return this.StatusCode(403, "");
.Include(q => q.HeartedUser)
if (pageSize <= 0) return this.BadRequest();
IEnumerable<User> heartedProfiles = this.database.HeartedProfiles.Include
(q => q.HeartedUser)
.Include(q => q.HeartedUser.Location) .Include(q => q.HeartedUser.Location)
.Where(q => q.User.Username == username) .Select(q => q.HeartedUser)
.Skip(pageStart - 1) .Where(q => q.UserId == targetUser.UserId)
.Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30)) .Take(Math.Min(pageSize, 30))
.AsEnumerable(); .AsEnumerable();
string response = heartedProfiles.Aggregate(string.Empty, (current, q) => current + q.HeartedUser.Serialize(token.GameVersion)); string response = heartedProfiles.Aggregate(string.Empty, (current, u) => current + u.Serialize(token.GameVersion));
return this.Ok return this.Ok
( (
LbpSerializer.TaggedStringElement("favouriteUsers", response, new Dictionary<string, object> LbpSerializer.TaggedStringElement("favouriteUsers", response, new Dictionary<string, object>
{ {
{ "total", this.database.HeartedProfiles.Include(q => q.User).Count(q => q.User.Username == username) }, { "total", this.database.HeartedProfiles.Count(q => q.UserId == targetUser.UserId) },
{ "hint_start", pageStart + Math.Min(pageSize, 30) }, { "hint_start", pageStart + Math.Min(pageSize, 30) },
}) })
); );
@ -189,13 +200,13 @@ public class ListController : ControllerBase
[HttpPost("favourite/user/{username}")] [HttpPost("favourite/user/{username}")]
public async Task<IActionResult> AddFavouriteUser(string username) public async Task<IActionResult> AddFavouriteUser(string username)
{ {
User? user = await this.database.UserFromGameRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (user == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username); User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
if (heartedUser == null) return this.NotFound(); if (heartedUser == null) return this.NotFound();
await this.database.HeartUser(user, heartedUser); await this.database.HeartUser(token.UserId, heartedUser);
return this.Ok(); return this.Ok();
} }
@ -203,13 +214,13 @@ public class ListController : ControllerBase
[HttpPost("unfavourite/user/{username}")] [HttpPost("unfavourite/user/{username}")]
public async Task<IActionResult> RemoveFavouriteUser(string username) public async Task<IActionResult> RemoveFavouriteUser(string username)
{ {
User? user = await this.database.UserFromGameRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (user == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username); User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
if (heartedUser == null) return this.NotFound(); if (heartedUser == null) return this.NotFound();
await this.database.UnheartUser(user, heartedUser); await this.database.UnheartUser(token.UserId, heartedUser);
return this.Ok(); return this.Ok();
} }

View file

@ -185,10 +185,6 @@ public class PublishController : ControllerBase
slot.PlaysLBP3Complete = oldSlot.PlaysLBP3Complete; slot.PlaysLBP3Complete = oldSlot.PlaysLBP3Complete;
slot.PlaysLBP3Unique = oldSlot.PlaysLBP3Unique; slot.PlaysLBP3Unique = oldSlot.PlaysLBP3Unique;
slot.PlaysLBPVita = oldSlot.PlaysLBPVita;
slot.PlaysLBPVitaComplete = oldSlot.PlaysLBPVitaComplete;
slot.PlaysLBPVitaUnique = oldSlot.PlaysLBPVitaUnique;
#endregion #endregion
slot.FirstUploaded = oldSlot.FirstUploaded; slot.FirstUploaded = oldSlot.FirstUploaded;
@ -249,15 +245,15 @@ public class PublishController : ControllerBase
[HttpPost("unpublish/{id:int}")] [HttpPost("unpublish/{id:int}")]
public async Task<IActionResult> Unpublish(int id) public async Task<IActionResult> Unpublish(int id)
{ {
User? user = await this.database.UserFromGameRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (user == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
Slot? slot = await this.database.Slots.Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == id); Slot? slot = await this.database.Slots.Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound(); if (slot == null) return this.NotFound();
if (slot.Location == null) throw new ArgumentNullException(); if (slot.Location == null) throw new ArgumentNullException();
if (slot.CreatorId != user.UserId) return this.StatusCode(403, ""); if (slot.CreatorId != token.UserId) return this.StatusCode(403, "");
this.database.Locations.Remove(slot.Location); this.database.Locations.Remove(slot.Location);
this.database.Slots.Remove(slot); this.database.Slots.Remove(slot);

View file

@ -30,19 +30,19 @@ public class ReviewController : ControllerBase
[HttpPost("rate/user/{slotId}")] [HttpPost("rate/user/{slotId}")]
public async Task<IActionResult> Rate(int slotId, [FromQuery] int rating) public async Task<IActionResult> Rate(int slotId, [FromQuery] int rating)
{ {
User? user = await this.database.UserFromGameRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (user == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
Slot? slot = await this.database.Slots.Include(s => s.Creator).Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == slotId); Slot? slot = await this.database.Slots.Include(s => s.Creator).Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == slotId);
if (slot == null) return this.StatusCode(403, ""); if (slot == null) return this.StatusCode(403, "");
RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == slotId && r.UserId == user.UserId); RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == slotId && r.UserId == token.UserId);
if (ratedLevel == null) if (ratedLevel == null)
{ {
ratedLevel = new RatedLevel ratedLevel = new RatedLevel
{ {
SlotId = slotId, SlotId = slotId,
UserId = user.UserId, UserId = token.UserId,
Rating = 0, Rating = 0,
}; };
this.database.RatedLevels.Add(ratedLevel); this.database.RatedLevels.Add(ratedLevel);
@ -59,19 +59,19 @@ public class ReviewController : ControllerBase
[HttpPost("dpadrate/user/{slotId:int}")] [HttpPost("dpadrate/user/{slotId:int}")]
public async Task<IActionResult> DPadRate(int slotId, [FromQuery] int rating) public async Task<IActionResult> DPadRate(int slotId, [FromQuery] int rating)
{ {
User? user = await this.database.UserFromGameRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (user == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
Slot? slot = await this.database.Slots.Include(s => s.Creator).Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == slotId); Slot? slot = await this.database.Slots.Include(s => s.Creator).Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == slotId);
if (slot == null) return this.StatusCode(403, ""); if (slot == null) return this.StatusCode(403, "");
RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == slotId && r.UserId == user.UserId); RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == slotId && r.UserId == token.UserId);
if (ratedLevel == null) if (ratedLevel == null)
{ {
ratedLevel = new RatedLevel ratedLevel = new RatedLevel
{ {
SlotId = slotId, SlotId = slotId,
UserId = user.UserId, UserId = token.UserId,
RatingLBP1 = 0, RatingLBP1 = 0,
}; };
this.database.RatedLevels.Add(ratedLevel); this.database.RatedLevels.Add(ratedLevel);
@ -79,7 +79,7 @@ public class ReviewController : ControllerBase
ratedLevel.Rating = Math.Clamp(rating, -1, 1); ratedLevel.Rating = Math.Clamp(rating, -1, 1);
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == user.UserId); Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == token.UserId);
if (review != null) review.Thumb = ratedLevel.Rating; if (review != null) review.Thumb = ratedLevel.Rating;
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
@ -90,22 +90,22 @@ public class ReviewController : ControllerBase
[HttpPost("postReview/user/{slotId:int}")] [HttpPost("postReview/user/{slotId:int}")]
public async Task<IActionResult> PostReview(int slotId) public async Task<IActionResult> PostReview(int slotId)
{ {
User? user = await this.database.UserFromGameRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (user == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
Review? newReview = await this.getReviewFromBody(); Review? newReview = await this.getReviewFromBody();
if (newReview == null) return this.BadRequest(); if (newReview == null) return this.BadRequest();
if (newReview.Text.Length > 512) return this.BadRequest(); if (newReview.Text.Length > 512) return this.BadRequest();
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == user.UserId); Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == token.UserId);
if (review == null) if (review == null)
{ {
review = new Review review = new Review
{ {
SlotId = slotId, SlotId = slotId,
ReviewerId = user.UserId, ReviewerId = token.UserId,
DeletedBy = DeletedBy.None, DeletedBy = DeletedBy.None,
ThumbsUp = 0, ThumbsUp = 0,
ThumbsDown = 0, ThumbsDown = 0,
@ -119,13 +119,13 @@ public class ReviewController : ControllerBase
review.Timestamp = TimeHelper.UnixTimeMilliseconds(); review.Timestamp = TimeHelper.UnixTimeMilliseconds();
// sometimes the game posts/updates a review rating without also calling dpadrate/user/etc (why??) // sometimes the game posts/updates a review rating without also calling dpadrate/user/etc (why??)
RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == slotId && r.UserId == user.UserId); RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == slotId && r.UserId == token.UserId);
if (ratedLevel == null) if (ratedLevel == null)
{ {
ratedLevel = new RatedLevel ratedLevel = new RatedLevel
{ {
SlotId = slotId, SlotId = slotId,
UserId = user.UserId, UserId = token.UserId,
RatingLBP1 = 0, RatingLBP1 = 0,
}; };
this.database.RatedLevels.Add(ratedLevel); this.database.RatedLevels.Add(ratedLevel);
@ -141,15 +141,12 @@ public class ReviewController : ControllerBase
[HttpGet("reviewsFor/user/{slotId:int}")] [HttpGet("reviewsFor/user/{slotId:int}")]
public async Task<IActionResult> ReviewsFor(int slotId, [FromQuery] int pageStart = 1, [FromQuery] int pageSize = 10) public async Task<IActionResult> ReviewsFor(int slotId, [FromQuery] int pageStart = 1, [FromQuery] int pageSize = 10)
{ {
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
if (userAndToken == null) return this.StatusCode(403, ""); if (pageSize <= 0) return this.BadRequest();
// ReSharper disable once PossibleInvalidOperationException GameVersion gameVersion = token.GameVersion;
User user = userAndToken.Value.Item1;
GameToken gameToken = userAndToken.Value.Item2;
GameVersion gameVersion = gameToken.GameVersion;
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slotId); Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slotId);
if (slot == null) return this.BadRequest(); if (slot == null) return this.BadRequest();
@ -160,7 +157,7 @@ public class ReviewController : ControllerBase
.Include(r => r.Slot) .Include(r => r.Slot)
.OrderByDescending(r => r.ThumbsUp - r.ThumbsDown) .OrderByDescending(r => r.ThumbsUp - r.ThumbsDown)
.ThenByDescending(r => r.Timestamp) .ThenByDescending(r => r.Timestamp)
.Skip(pageStart - 1) .Skip(Math.Max(0, pageStart - 1))
.Take(pageSize); .Take(pageSize);
List<Review?> reviewList = reviews.ToList(); List<Review?> reviewList = reviews.ToList();
@ -172,7 +169,7 @@ public class ReviewController : ControllerBase
{ {
if (review == null) return current; if (review == null) return current;
RatedReview? yourThumb = this.database.RatedReviews.FirstOrDefault(r => r.ReviewId == review.ReviewId && r.UserId == user.UserId); RatedReview? yourThumb = this.database.RatedReviews.FirstOrDefault(r => r.ReviewId == review.ReviewId && r.UserId == token.UserId);
return current + review.Serialize(null, yourThumb); return current + review.Serialize(null, yourThumb);
} }
); );
@ -196,22 +193,23 @@ public class ReviewController : ControllerBase
[HttpGet("reviewsBy/{username}")] [HttpGet("reviewsBy/{username}")]
public async Task<IActionResult> ReviewsBy(string username, [FromQuery] int pageStart = 1, [FromQuery] int pageSize = 10) public async Task<IActionResult> ReviewsBy(string username, [FromQuery] int pageStart = 1, [FromQuery] int pageSize = 10)
{ {
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
if (userAndToken == null) return this.StatusCode(403, ""); if (pageSize <= 0) return this.BadRequest();
// ReSharper disable once PossibleInvalidOperationException GameVersion gameVersion = token.GameVersion;
User user = userAndToken.Value.Item1;
GameToken gameToken = userAndToken.Value.Item2;
GameVersion gameVersion = gameToken.GameVersion; int targetUserId = await this.database.Users.Where(u => u.Username == username).Select(u => u.UserId).FirstOrDefaultAsync();
if (targetUserId == 0) return this.BadRequest();
IEnumerable<Review?> reviews = this.database.Reviews.ByGameVersion(gameVersion, true) IEnumerable<Review?> reviews = this.database.Reviews.ByGameVersion(gameVersion, true)
.Include(r => r.Reviewer) .Include(r => r.Reviewer)
.Include(r => r.Slot) .Include(r => r.Slot)
.Where(r => r.Reviewer!.Username == username) .Where(r => r.ReviewerId == targetUserId)
.OrderByDescending(r => r.Timestamp) .OrderByDescending(r => r.Timestamp)
.Skip(pageStart - 1) .Skip(Math.Max(0, pageStart - 1))
.Take(pageSize); .Take(pageSize);
List<Review?> reviewList = reviews.ToList(); List<Review?> reviewList = reviews.ToList();
@ -223,7 +221,7 @@ public class ReviewController : ControllerBase
{ {
if (review == null) return current; if (review == null) return current;
RatedReview? ratedReview = this.database.RatedReviews.FirstOrDefault(r => r.ReviewId == review.ReviewId && r.UserId == user.UserId); RatedReview? ratedReview = this.database.RatedReviews.FirstOrDefault(r => r.ReviewId == review.ReviewId && r.UserId == token.UserId);
return current + review.Serialize(null, ratedReview); return current + review.Serialize(null, ratedReview);
} }
); );
@ -249,22 +247,22 @@ public class ReviewController : ControllerBase
[HttpPost("rateReview/user/{slotId:int}/{username}")] [HttpPost("rateReview/user/{slotId:int}/{username}")]
public async Task<IActionResult> RateReview(int slotId, string username, [FromQuery] int rating = 0) public async Task<IActionResult> RateReview(int slotId, string username, [FromQuery] int rating = 0)
{ {
User? user = await this.database.UserFromGameRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (user == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
User? reviewer = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username); int reviewerId = await this.database.Users.Where(u => u.Username == username).Select(u => u.UserId).FirstOrDefaultAsync();
if (reviewer == null) return this.StatusCode(400, ""); if (reviewerId == 0) return this.StatusCode(400, "");
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == reviewer.UserId); Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == reviewerId);
if (review == null) return this.StatusCode(400, ""); if (review == null) return this.StatusCode(400, "");
RatedReview? ratedReview = await this.database.RatedReviews.FirstOrDefaultAsync(r => r.ReviewId == review.ReviewId && r.UserId == user.UserId); RatedReview? ratedReview = await this.database.RatedReviews.FirstOrDefaultAsync(r => r.ReviewId == review.ReviewId && r.UserId == token.UserId);
if (ratedReview == null) if (ratedReview == null)
{ {
ratedReview = new RatedReview ratedReview = new RatedReview
{ {
ReviewId = review.ReviewId, ReviewId = review.ReviewId,
UserId = user.UserId, UserId = token.UserId,
Thumb = 0, Thumb = 0,
}; };
this.database.RatedReviews.Add(ratedReview); this.database.RatedReviews.Add(ratedReview);
@ -276,12 +274,12 @@ public class ReviewController : ControllerBase
if (oldRating == ratedReview.Thumb) return this.Ok(); if (oldRating == ratedReview.Thumb) return this.Ok();
// if the user's rating changed then we recount the review's ratings to ensure accuracy // if the user's rating changed then we recount the review's ratings to ensure accuracy
List<RatedReview> reactions = await this.database.RatedReviews.Where(r => r.ReviewId == review.ReviewId).ToListAsync(); List<int> reactions = await this.database.RatedReviews.Where(r => r.ReviewId == reviewerId).Select(r => r.Thumb).ToListAsync();
int yay = 0; int yay = 0;
int boo = 0; int boo = 0;
foreach (RatedReview r in reactions) foreach (int r in reactions)
{ {
switch (r.Thumb) switch (r)
{ {
case -1: case -1:
boo++; boo++;
@ -303,11 +301,19 @@ public class ReviewController : ControllerBase
[HttpPost("deleteReview/user/{slotId:int}/{username}")] [HttpPost("deleteReview/user/{slotId:int}/{username}")]
public async Task<IActionResult> DeleteReview(int slotId, string username) public async Task<IActionResult> DeleteReview(int slotId, string username)
{ {
User? reviewer = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (reviewer == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == reviewer.UserId); int creatorId = await this.database.Slots.Where(s => s.SlotId == slotId).Select(s => s.CreatorId).FirstOrDefaultAsync();
if (review == null) return this.StatusCode(403, ""); if (creatorId == 0) return this.StatusCode(400, "");
if (token.UserId != creatorId) return this.StatusCode(403, "");
int reviewerId = await this.database.Users.Where(u => u.Username == username).Select(u => u.UserId).FirstOrDefaultAsync();
if (reviewerId == 0) return this.StatusCode(400, "");
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == reviewerId);
if (review == null) return this.StatusCode(400, "");
review.Deleted = true; review.Deleted = true;
review.DeletedBy = DeletedBy.LevelAuthor; review.DeletedBy = DeletedBy.LevelAuthor;

View file

@ -1,13 +1,11 @@
#nullable enable #nullable enable
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Xml.Serialization; using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Levels; using LBPUnion.ProjectLighthouse.Levels;
using LBPUnion.ProjectLighthouse.PlayerData; using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles; using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
@ -27,16 +25,13 @@ public class ScoreController : ControllerBase
[HttpPost("scoreboard/{slotType}/{id:int}")] [HttpPost("scoreboard/{slotType}/{id:int}")]
public async Task<IActionResult> SubmitScore(string slotType, int id, [FromQuery] bool lbp1 = false, [FromQuery] bool lbp2 = false, [FromQuery] bool lbp3 = false) public async Task<IActionResult> SubmitScore(string slotType, int id, [FromQuery] bool lbp1 = false, [FromQuery] bool lbp2 = false, [FromQuery] bool lbp3 = false)
{ {
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
if (userAndToken == null) return this.StatusCode(403, ""); string username = await this.database.UsernameFromGameToken(token);
if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest(); if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest();
// ReSharper disable once PossibleInvalidOperationException
User user = userAndToken.Value.Item1;
GameToken gameToken = userAndToken.Value.Item2;
this.Request.Body.Position = 0; this.Request.Body.Position = 0;
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync(); string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
@ -53,20 +48,18 @@ public class ScoreController : ControllerBase
Slot? slot = this.database.Slots.FirstOrDefault(s => s.SlotId == score.SlotId); Slot? slot = this.database.Slots.FirstOrDefault(s => s.SlotId == score.SlotId);
if (slot == null) return this.BadRequest(); if (slot == null) return this.BadRequest();
switch (gameToken.GameVersion) switch (token.GameVersion)
{ {
case GameVersion.LittleBigPlanet1: case GameVersion.LittleBigPlanet1:
slot.PlaysLBP1Complete++; slot.PlaysLBP1Complete++;
break; break;
case GameVersion.LittleBigPlanet2: case GameVersion.LittleBigPlanet2:
case GameVersion.LittleBigPlanetVita:
slot.PlaysLBP2Complete++; slot.PlaysLBP2Complete++;
break; break;
case GameVersion.LittleBigPlanet3: case GameVersion.LittleBigPlanet3:
slot.PlaysLBP3Complete++; slot.PlaysLBP3Complete++;
break; break;
case GameVersion.LittleBigPlanetVita:
slot.PlaysLBPVitaComplete++;
break;
} }
IQueryable<Score> existingScore = this.database.Scores.Where(s => s.SlotId == score.SlotId) IQueryable<Score> existingScore = this.database.Scores.Where(s => s.SlotId == score.SlotId)
@ -87,7 +80,7 @@ public class ScoreController : ControllerBase
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
string myRanking = this.getScores(score.SlotId, score.Type, user, -1, 5, "scoreboardSegment"); string myRanking = this.getScores(score.SlotId, score.Type, username, -1, 5, "scoreboardSegment");
return this.Ok(myRanking); return this.Ok(myRanking);
} }
@ -102,15 +95,18 @@ public class ScoreController : ControllerBase
public async Task<IActionResult> TopScores(string slotType, int slotId, int type, [FromQuery] int pageStart = -1, [FromQuery] int pageSize = 5) public async Task<IActionResult> TopScores(string slotType, int slotId, int type, [FromQuery] int pageStart = -1, [FromQuery] int pageSize = 5)
{ {
// Get username // Get username
User? user = await this.database.UserFromGameRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
if (user == null) return this.StatusCode(403, ""); if (pageSize <= 0) return this.BadRequest();
string username = await this.database.UsernameFromGameToken(token);
if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest(); if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest();
if (slotType == "developer") slotId = await SlotHelper.GetPlaceholderSlotId(this.database, slotId, SlotType.Developer); if (slotType == "developer") slotId = await SlotHelper.GetPlaceholderSlotId(this.database, slotId, SlotType.Developer);
return this.Ok(this.getScores(slotId, type, user, pageStart, pageSize)); return this.Ok(this.getScores(slotId, type, username, pageStart, pageSize));
} }
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
@ -118,12 +114,13 @@ public class ScoreController : ControllerBase
( (
int slotId, int slotId,
int type, int type,
User user, string username,
int pageStart = -1, int pageStart = -1,
int pageSize = 5, int pageSize = 5,
string rootName = "scores" string rootName = "scores"
) )
{ {
// This is hella ugly but it technically assigns the proper rank to a score // This is hella ugly but it technically assigns the proper rank to a score
// var needed for Anonymous type returned from SELECT // var needed for Anonymous type returned from SELECT
var rankedScores = this.database.Scores.Where(s => s.SlotId == slotId && s.Type == type) var rankedScores = this.database.Scores.Where(s => s.SlotId == slotId && s.Type == type)
@ -139,7 +136,7 @@ public class ScoreController : ControllerBase
); );
// Find your score, since even if you aren't in the top list your score is pinned // Find your score, since even if you aren't in the top list your score is pinned
var myScore = rankedScores.Where(rs => rs.Score.PlayerIdCollection.Contains(user.Username)).OrderByDescending(rs => rs.Score.Points).FirstOrDefault(); var myScore = rankedScores.Where(rs => rs.Score.PlayerIdCollection.Contains(username)).MaxBy(rs => rs.Score.Points);
// Paginated viewing: if not requesting pageStart, get results around user // Paginated viewing: if not requesting pageStart, get results around user
var pagedScores = rankedScores.Skip(pageStart != -1 || myScore == null ? pageStart - 1 : myScore.Rank - 3).Take(Math.Min(pageSize, 30)); var pagedScores = rankedScores.Skip(pageStart != -1 || myScore == null ? pageStart - 1 : myScore.Rank - 3).Take(Math.Min(pageSize, 30));

View file

@ -2,7 +2,6 @@
using LBPUnion.ProjectLighthouse.Levels; using LBPUnion.ProjectLighthouse.Levels;
using LBPUnion.ProjectLighthouse.PlayerData; using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -34,6 +33,8 @@ public class SearchController : ControllerBase
GameToken? gameToken = await this.database.GameTokenFromRequest(this.Request); GameToken? gameToken = await this.database.GameTokenFromRequest(this.Request);
if (gameToken == null) return this.StatusCode(403, ""); if (gameToken == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest();
if (string.IsNullOrWhiteSpace(query)) return this.BadRequest(); if (string.IsNullOrWhiteSpace(query)) return this.BadRequest();
query = query.ToLower(); query = query.ToLower();
@ -56,7 +57,7 @@ public class SearchController : ControllerBase
s.SlotId.ToString().Equals(keyword) s.SlotId.ToString().Equals(keyword)
); );
List<Slot> slots = await dbQuery.Skip(pageStart - 1).Take(Math.Min(pageSize, 30)).ToListAsync(); List<Slot> slots = await dbQuery.Skip(Math.Max(0, pageStart - 1)).Take(Math.Min(pageSize, 30)).ToListAsync();
string response = slots.Aggregate("", (current, slot) => current + slot.Serialize(gameToken.GameVersion)); string response = slots.Aggregate("", (current, slot) => current + slot.Serialize(gameToken.GameVersion));

View file

@ -30,16 +30,18 @@ public class SlotsController : ControllerBase
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest();
GameVersion gameVersion = token.GameVersion; GameVersion gameVersion = token.GameVersion;
User? user = await this.database.Users.FirstOrDefaultAsync(dbUser => dbUser.Username == u); int targetUserId = await this.database.Users.Where(dbUser => dbUser.Username == u).Select(dbUser => dbUser.UserId).FirstOrDefaultAsync();
if (user == null) return this.NotFound(); if (targetUserId == 0) return this.NotFound();
string response = Enumerable.Aggregate string response = Enumerable.Aggregate
( (
this.database.Slots.ByGameVersion(gameVersion, token.UserId == user.UserId, true) this.database.Slots.ByGameVersion(gameVersion, token.UserId == targetUserId, true)
.Where(s => s.Creator!.Username == user.Username) .Where(s => s.CreatorId == targetUserId)
.Skip(pageStart - 1) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots)), .Take(Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots)),
string.Empty, string.Empty,
(current, slot) => current + slot.Serialize(token.GameVersion) (current, slot) => current + slot.Serialize(token.GameVersion)
@ -57,7 +59,7 @@ public class SlotsController : ControllerBase
"hint_start", pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots) "hint_start", pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots)
}, },
{ {
"total", user.UsedSlots "total", await this.database.Slots.CountAsync(s => s.CreatorId == targetUserId)
}, },
} }
) )
@ -93,9 +95,6 @@ public class SlotsController : ControllerBase
[HttpGet("slots/developer")] [HttpGet("slots/developer")]
public async Task<IActionResult> StoryPlayers() public async Task<IActionResult> StoryPlayers()
{ {
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
@ -107,7 +106,7 @@ public class SlotsController : ControllerBase
{ {
int placeholderSlotId = await SlotHelper.GetPlaceholderSlotId(this.database, id, SlotType.Developer); int placeholderSlotId = await SlotHelper.GetPlaceholderSlotId(this.database, id, SlotType.Developer);
Slot slot = await this.database.Slots.FirstAsync(s => s.SlotId == placeholderSlotId); Slot slot = await this.database.Slots.FirstAsync(s => s.SlotId == placeholderSlotId);
serializedSlots.Add(slot.SerializeDevSlot(false)); serializedSlots.Add(slot.SerializeDevSlot());
} }
string serialized = serializedSlots.Aggregate(string.Empty, (current, slot) => current + slot); string serialized = serializedSlots.Aggregate(string.Empty, (current, slot) => current + slot);
@ -118,9 +117,6 @@ public class SlotsController : ControllerBase
[HttpGet("s/developer/{id:int}")] [HttpGet("s/developer/{id:int}")]
public async Task<IActionResult> SDev(int id) public async Task<IActionResult> SDev(int id)
{ {
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
@ -133,9 +129,6 @@ public class SlotsController : ControllerBase
[HttpGet("s/user/{id:int}")] [HttpGet("s/user/{id:int}")]
public async Task<IActionResult> SUser(int id) public async Task<IActionResult> SUser(int id)
{ {
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
@ -145,10 +138,10 @@ public class SlotsController : ControllerBase
if (slot == null) return this.NotFound(); if (slot == null) return this.NotFound();
RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == id && r.UserId == user.UserId); 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 == user.UserId); VisitedLevel? visitedLevel = await this.database.VisitedLevels.FirstOrDefaultAsync(r => r.SlotId == id && r.UserId == token.UserId);
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == id && r.ReviewerId == user.UserId); Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == id && r.ReviewerId == token.UserId);
return this.Ok(slot.Serialize(gameVersion, ratedLevel, visitedLevel, review)); return this.Ok(slot.Serialize(gameVersion, ratedLevel, visitedLevel, review, true));
} }
[HttpGet("slots/cool")] [HttpGet("slots/cool")]
@ -181,11 +174,13 @@ public class SlotsController : ControllerBase
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest();
GameVersion gameVersion = token.GameVersion; GameVersion gameVersion = token.GameVersion;
IQueryable<Slot> slots = this.database.Slots.ByGameVersion(gameVersion, false, true) IQueryable<Slot> slots = this.database.Slots.ByGameVersion(gameVersion, false, true)
.OrderByDescending(s => s.FirstUploaded) .OrderByDescending(s => s.FirstUploaded)
.Skip(pageStart - 1) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30)); .Take(Math.Min(pageSize, 30));
string response = Enumerable.Aggregate(slots, string.Empty, (current, slot) => current + slot.Serialize(gameVersion)); string response = Enumerable.Aggregate(slots, string.Empty, (current, slot) => current + slot.Serialize(gameVersion));
@ -215,12 +210,14 @@ public class SlotsController : ControllerBase
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest();
GameVersion gameVersion = token.GameVersion; GameVersion gameVersion = token.GameVersion;
IQueryable<Slot> slots = this.database.Slots.ByGameVersion(gameVersion, false, true) IQueryable<Slot> slots = this.database.Slots.ByGameVersion(gameVersion, false, true)
.Where(s => s.TeamPick) .Where(s => s.TeamPick)
.OrderByDescending(s => s.LastUpdated) .OrderByDescending(s => s.LastUpdated)
.Skip(pageStart - 1) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30)); .Take(Math.Min(pageSize, 30));
string response = Enumerable.Aggregate(slots, string.Empty, (current, slot) => current + slot.Serialize(gameVersion)); string response = Enumerable.Aggregate(slots, string.Empty, (current, slot) => current + slot.Serialize(gameVersion));
@ -249,6 +246,8 @@ public class SlotsController : ControllerBase
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest();
GameVersion gameVersion = token.GameVersion; GameVersion gameVersion = token.GameVersion;
IEnumerable<Slot> slots = this.database.Slots.ByGameVersion(gameVersion, false, true).OrderBy(_ => EF.Functions.Random()).Take(Math.Min(pageSize, 30)); IEnumerable<Slot> slots = this.database.Slots.ByGameVersion(gameVersion, false, true).OrderBy(_ => EF.Functions.Random()).Take(Math.Min(pageSize, 30));
@ -288,13 +287,15 @@ public class SlotsController : ControllerBase
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest();
Random rand = new(); Random rand = new();
IEnumerable<Slot> slots = this.filterByRequest(gameFilterType, dateFilterType, token.GameVersion) IEnumerable<Slot> slots = this.filterByRequest(gameFilterType, dateFilterType, token.GameVersion)
.AsEnumerable() .AsEnumerable()
.OrderByDescending(s => s.Thumbsup) .OrderByDescending(s => s.Thumbsup)
.ThenBy(_ => rand.Next()) .ThenBy(_ => rand.Next())
.Skip(pageStart - 1) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30)); .Take(Math.Min(pageSize, 30));
string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize(token.GameVersion)); string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize(token.GameVersion));
@ -332,6 +333,8 @@ public class SlotsController : ControllerBase
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest();
Random rand = new(); Random rand = new();
IEnumerable<Slot> slots = this.filterByRequest(gameFilterType, dateFilterType, token.GameVersion) IEnumerable<Slot> slots = this.filterByRequest(gameFilterType, dateFilterType, token.GameVersion)
@ -346,13 +349,13 @@ public class SlotsController : ControllerBase
GameVersion.LittleBigPlanet1 => s.PlaysLBP1Unique, GameVersion.LittleBigPlanet1 => s.PlaysLBP1Unique,
GameVersion.LittleBigPlanet2 => s.PlaysLBP2Unique, GameVersion.LittleBigPlanet2 => s.PlaysLBP2Unique,
GameVersion.LittleBigPlanet3 => s.PlaysLBP3Unique, GameVersion.LittleBigPlanet3 => s.PlaysLBP3Unique,
GameVersion.LittleBigPlanetVita => s.PlaysLBPVitaUnique, GameVersion.LittleBigPlanetVita => s.PlaysLBP2Unique,
_ => s.PlaysUnique, _ => s.PlaysUnique,
}; };
} }
) )
.ThenBy(_ => rand.Next()) .ThenBy(_ => rand.Next())
.Skip(pageStart - 1) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30)); .Take(Math.Min(pageSize, 30));
string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize(token.GameVersion)); string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize(token.GameVersion));
@ -390,13 +393,15 @@ public class SlotsController : ControllerBase
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest();
Random rand = new(); Random rand = new();
IEnumerable<Slot> slots = this.filterByRequest(gameFilterType, dateFilterType, token.GameVersion) IEnumerable<Slot> slots = this.filterByRequest(gameFilterType, dateFilterType, token.GameVersion)
.AsEnumerable() .AsEnumerable()
.OrderByDescending(s => s.Hearts) .OrderByDescending(s => s.Hearts)
.ThenBy(_ => rand.Next()) .ThenBy(_ => rand.Next())
.Skip(pageStart - 1) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30)); .Take(Math.Min(pageSize, 30));
string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize(token.GameVersion)); string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize(token.GameVersion));
@ -434,6 +439,8 @@ public class SlotsController : ControllerBase
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest();
Dictionary<int, int> playersBySlotId = new(); Dictionary<int, int> playersBySlotId = new();
foreach (Room room in RoomHelper.Rooms) foreach (Room room in RoomHelper.Rooms)
@ -451,7 +458,7 @@ public class SlotsController : ControllerBase
} }
IEnumerable<int> orderedPlayersBySlotId = playersBySlotId IEnumerable<int> orderedPlayersBySlotId = playersBySlotId
.Skip(pageStart - 1) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30)) .Take(Math.Min(pageSize, 30))
.OrderByDescending(kvp => kvp.Value) .OrderByDescending(kvp => kvp.Value)
.Select(kvp => kvp.Key); .Select(kvp => kvp.Key);

View file

@ -1,10 +1,10 @@
using System.IO.Compression;
using LBPUnion.ProjectLighthouse.Configuration; using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Middlewares; using LBPUnion.ProjectLighthouse.Middlewares;
using LBPUnion.ProjectLighthouse.PlayerData; using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
@ -124,6 +124,7 @@ public class GameServerStartup
context.Response.Body = responseBuffer; context.Response.Body = responseBuffer;
await next(context); // Handle the request so we can get the server digest hash await next(context); // Handle the request so we can get the server digest hash
responseBuffer.Position = 0;
// Compute the server digest hash. // Compute the server digest hash.
if (computeDigests) if (computeDigests)
@ -139,10 +140,7 @@ public class GameServerStartup
context.Response.Headers.Add("X-Digest-A", serverDigest); context.Response.Headers.Add("X-Digest-A", serverDigest);
} }
// Set the X-Original-Content-Length header to the length of the response buffer. // Copy the buffered response to the actual response stream.
context.Response.Headers.Add("X-Original-Content-Length", responseBuffer.Length.ToString());
// Copy the buffered response to the actual respose stream.
responseBuffer.Position = 0; responseBuffer.Position = 0;
await responseBuffer.CopyToAsync(oldResponseStream); await responseBuffer.CopyToAsync(oldResponseStream);
context.Response.Body = oldResponseStream; context.Response.Body = oldResponseStream;

View file

@ -23,6 +23,7 @@ public class RoomVisualizerController : ControllerBase
public async Task<IActionResult> CreateFakeRoom() public async Task<IActionResult> CreateFakeRoom()
{ {
#if !DEBUG #if !DEBUG
await Task.FromResult(0);
return this.NotFound(); return this.NotFound();
#else #else
List<int> users = await this.database.Users.OrderByDescending(_ => EF.Functions.Random()).Take(2).Select(u => u.UserId).ToListAsync(); List<int> users = await this.database.Users.OrderByDescending(_ => EF.Functions.Random()).Take(2).Select(u => u.UserId).ToListAsync();
@ -51,6 +52,7 @@ public class RoomVisualizerController : ControllerBase
public async Task<IActionResult> CreateRoomsWithDuplicatePlayers() public async Task<IActionResult> CreateRoomsWithDuplicatePlayers()
{ {
#if !DEBUG #if !DEBUG
await Task.FromResult(0);
return this.NotFound(); return this.NotFound();
#else #else
List<int> users = await this.database.Users.OrderByDescending(_ => EF.Functions.Random()).Take(1).Select(u => u.UserId).ToListAsync(); List<int> users = await this.database.Users.OrderByDescending(_ => EF.Functions.Random()).Take(1).Select(u => u.UserId).ToListAsync();

View file

@ -2,7 +2,7 @@
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Levels; using LBPUnion.ProjectLighthouse.Levels;
using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles; using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -28,10 +28,10 @@ public class SlotPageController : ControllerBase
[HttpGet("rateComment")] [HttpGet("rateComment")]
public async Task<IActionResult> RateComment([FromRoute] int id, [FromQuery] int commentId, [FromQuery] int rating) public async Task<IActionResult> RateComment([FromRoute] int id, [FromQuery] int commentId, [FromQuery] int rating)
{ {
User? user = this.database.UserFromWebRequest(this.Request); WebToken? token = this.database.WebTokenFromRequest(this.Request);
if (user == null) return this.Redirect("~/login"); if (token == null) return this.Redirect("~/login");
await this.database.RateComment(user, commentId, rating); await this.database.RateComment(token.UserId, commentId, rating);
return this.Redirect($"~/slot/{id}#{commentId}"); return this.Redirect($"~/slot/{id}#{commentId}");
} }
@ -39,19 +39,19 @@ public class SlotPageController : ControllerBase
[HttpPost("postComment")] [HttpPost("postComment")]
public async Task<IActionResult> PostComment([FromRoute] int id, [FromForm] string? msg) public async Task<IActionResult> PostComment([FromRoute] int id, [FromForm] string? msg)
{ {
User? user = this.database.UserFromWebRequest(this.Request); WebToken? token = this.database.WebTokenFromRequest(this.Request);
if (user == null) return this.Redirect("~/login"); if (token == null) return this.Redirect("~/login");
if (msg == null) if (msg == null)
{ {
Logger.Error($"Refusing to post comment from {user.UserId} on user {id}, {nameof(msg)} is null", LogArea.Comments); Logger.Error($"Refusing to post comment from {token.UserId} on user {id}, {nameof(msg)} is null", LogArea.Comments);
return this.Redirect("~/slot/" + id); return this.Redirect("~/slot/" + id);
} }
msg = SanitizationHelper.SanitizeString(msg); msg = SanitizationHelper.SanitizeString(msg);
await this.database.PostComment(user, id, CommentType.Level, msg); await this.database.PostComment(token.UserId, id, CommentType.Level, msg);
Logger.Success($"Posted comment from {user.UserId}: \"{msg}\" on user {id}", LogArea.Comments); Logger.Success($"Posted comment from {token.UserId}: \"{msg}\" on user {id}", LogArea.Comments);
return this.Redirect("~/slot/" + id); return this.Redirect("~/slot/" + id);
} }
@ -61,13 +61,13 @@ public class SlotPageController : ControllerBase
{ {
if (string.IsNullOrEmpty(callbackUrl)) callbackUrl = "~/slot/" + id; if (string.IsNullOrEmpty(callbackUrl)) callbackUrl = "~/slot/" + id;
User? user = this.database.UserFromWebRequest(this.Request); WebToken? token = this.database.WebTokenFromRequest(this.Request);
if (user == null) return this.Redirect("~/login"); if (token == null) return this.Redirect("~/login");
Slot? heartedSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); Slot? heartedSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (heartedSlot == null) return this.NotFound(); if (heartedSlot == null) return this.NotFound();
await this.database.HeartLevel(user, heartedSlot); await this.database.HeartLevel(token.UserId, heartedSlot);
return this.Redirect(callbackUrl); return this.Redirect(callbackUrl);
} }
@ -77,13 +77,13 @@ public class SlotPageController : ControllerBase
{ {
if (string.IsNullOrEmpty(callbackUrl)) callbackUrl = "~/slot/" + id; if (string.IsNullOrEmpty(callbackUrl)) callbackUrl = "~/slot/" + id;
User? user = this.database.UserFromWebRequest(this.Request); WebToken? token = this.database.WebTokenFromRequest(this.Request);
if (user == null) return this.Redirect("~/login"); if (token == null) return this.Redirect("~/login");
Slot? heartedSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); Slot? heartedSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (heartedSlot == null) return this.NotFound(); if (heartedSlot == null) return this.NotFound();
await this.database.UnheartLevel(user, heartedSlot); await this.database.UnheartLevel(token.UserId, heartedSlot);
return this.Redirect(callbackUrl); return this.Redirect(callbackUrl);
} }
@ -93,13 +93,13 @@ public class SlotPageController : ControllerBase
{ {
if (string.IsNullOrEmpty(callbackUrl)) callbackUrl = "~/slot/" + id; if (string.IsNullOrEmpty(callbackUrl)) callbackUrl = "~/slot/" + id;
User? user = this.database.UserFromWebRequest(this.Request); WebToken? token = this.database.WebTokenFromRequest(this.Request);
if (user == null) return this.Redirect("~/login"); if (token == null) return this.Redirect("~/login");
Slot? queuedSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); Slot? queuedSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (queuedSlot == null) return this.NotFound(); if (queuedSlot == null) return this.NotFound();
await this.database.QueueLevel(user, queuedSlot); await this.database.QueueLevel(token.UserId, queuedSlot);
return this.Redirect(callbackUrl); return this.Redirect(callbackUrl);
} }
@ -109,13 +109,13 @@ public class SlotPageController : ControllerBase
{ {
if (string.IsNullOrEmpty(callbackUrl)) callbackUrl = "~/slot/" + id; if (string.IsNullOrEmpty(callbackUrl)) callbackUrl = "~/slot/" + id;
User? user = this.database.UserFromWebRequest(this.Request); WebToken? token = this.database.WebTokenFromRequest(this.Request);
if (user == null) return this.Redirect("~/login"); if (token == null) return this.Redirect("~/login");
Slot? queuedSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); Slot? queuedSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (queuedSlot == null) return this.NotFound(); if (queuedSlot == null) return this.NotFound();
await this.database.UnqueueLevel(user, queuedSlot); await this.database.UnqueueLevel(token.UserId, queuedSlot);
return this.Redirect(callbackUrl); return this.Redirect(callbackUrl);
} }

View file

@ -1,6 +1,7 @@
#nullable enable #nullable enable
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles; using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -22,10 +23,10 @@ public class UserPageController : ControllerBase
[HttpGet("rateComment")] [HttpGet("rateComment")]
public async Task<IActionResult> RateComment([FromRoute] int id, [FromQuery] int? commentId, [FromQuery] int? rating) public async Task<IActionResult> RateComment([FromRoute] int id, [FromQuery] int? commentId, [FromQuery] int? rating)
{ {
User? user = this.database.UserFromWebRequest(this.Request); WebToken? token = this.database.WebTokenFromRequest(this.Request);
if (user == null) return this.Redirect("~/login"); if (token == null) return this.Redirect("~/login");
await this.database.RateComment(user, commentId.GetValueOrDefault(), rating.GetValueOrDefault()); await this.database.RateComment(token.UserId, commentId.GetValueOrDefault(), rating.GetValueOrDefault());
return this.Redirect($"~/user/{id}#{commentId}"); return this.Redirect($"~/user/{id}#{commentId}");
} }
@ -33,19 +34,19 @@ public class UserPageController : ControllerBase
[HttpPost("postComment")] [HttpPost("postComment")]
public async Task<IActionResult> PostComment([FromRoute] int id, [FromForm] string? msg) public async Task<IActionResult> PostComment([FromRoute] int id, [FromForm] string? msg)
{ {
User? user = this.database.UserFromWebRequest(this.Request); WebToken? token = this.database.WebTokenFromRequest(this.Request);
if (user == null) return this.Redirect("~/login"); if (token == null) return this.Redirect("~/login");
if (msg == null) if (msg == null)
{ {
Logger.Error($"Refusing to post comment from {user.UserId} on user {id}, {nameof(msg)} is null", LogArea.Comments); Logger.Error($"Refusing to post comment from {token.UserId} on user {id}, {nameof(msg)} is null", LogArea.Comments);
return this.Redirect("~/user/" + id); return this.Redirect("~/user/" + id);
} }
msg = SanitizationHelper.SanitizeString(msg); msg = SanitizationHelper.SanitizeString(msg);
await this.database.PostComment(user, id, CommentType.Profile, msg); await this.database.PostComment(token.UserId, id, CommentType.Profile, msg);
Logger.Success($"Posted comment from {user.UserId}: \"{msg}\" on user {id}", LogArea.Comments); Logger.Success($"Posted comment from {token.UserId}: \"{msg}\" on user {id}", LogArea.Comments);
return this.Redirect("~/user/" + id); return this.Redirect("~/user/" + id);
} }
@ -53,13 +54,13 @@ public class UserPageController : ControllerBase
[HttpGet("heart")] [HttpGet("heart")]
public async Task<IActionResult> HeartUser([FromRoute] int id) public async Task<IActionResult> HeartUser([FromRoute] int id)
{ {
User? user = this.database.UserFromWebRequest(this.Request); WebToken? token = this.database.WebTokenFromRequest(this.Request);
if (user == null) return this.Redirect("~/login"); if (token == null) return this.Redirect("~/login");
User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id); User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id);
if (heartedUser == null) return this.NotFound(); if (heartedUser == null) return this.NotFound();
await this.database.HeartUser(user, heartedUser); await this.database.HeartUser(token.UserId, heartedUser);
return this.Redirect("~/user/" + id); return this.Redirect("~/user/" + id);
} }
@ -67,13 +68,13 @@ public class UserPageController : ControllerBase
[HttpGet("unheart")] [HttpGet("unheart")]
public async Task<IActionResult> UnheartUser([FromRoute] int id) public async Task<IActionResult> UnheartUser([FromRoute] int id)
{ {
User? user = this.database.UserFromWebRequest(this.Request); WebToken? token = this.database.WebTokenFromRequest(this.Request);
if (user == null) return this.Redirect("~/login"); if (token == null) return this.Redirect("~/login");
User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id); User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id);
if (heartedUser == null) return this.NotFound(); if (heartedUser == null) return this.NotFound();
await this.database.UnheartUser(user, heartedUser); await this.database.UnheartUser(token.UserId, heartedUser);
return this.Redirect("~/user/" + id); return this.Redirect("~/user/" + id);
} }

View file

@ -13,7 +13,7 @@ public class CasePage : BaseLayout
public CasePage(Database database) : base(database) public CasePage(Database database) : base(database)
{} {}
public List<ModerationCase> Cases; public List<ModerationCase> Cases = new();
public int CaseCount; public int CaseCount;
public int DismissedCaseCount; public int DismissedCaseCount;

View file

@ -12,16 +12,13 @@ public class FilterTestPage : BaseLayout
public string? FilteredText; public string? FilteredText;
public string? Text; public string? Text;
#if DEBUG
public IActionResult OnGet(string? text = null) public IActionResult OnGet(string? text = null)
{ {
#if !DEBUG
return this.NotFound();
#endif
if (text != null) this.FilteredText = CensorHelper.ScanMessage(text); if (text != null) this.FilteredText = CensorHelper.ScanMessage(text);
this.Text = text; this.Text = text;
return this.Page(); return this.Page();
} }
#endif
} }

View file

@ -42,14 +42,14 @@ public class LandingPage : BaseLayout
const int maxShownLevels = 5; const int maxShownLevels = 5;
this.LatestTeamPicks = await this.Database.Slots.Where(s => s.Type == SlotType.User && s.Type == SlotType.User) this.LatestTeamPicks = await this.Database.Slots.Where(s => s.Type == SlotType.User && !s.SubLevel)
.Where(s => s.TeamPick) .Where(s => s.TeamPick)
.OrderByDescending(s => s.FirstUploaded) .OrderByDescending(s => s.FirstUploaded)
.Take(maxShownLevels) .Take(maxShownLevels)
.Include(s => s.Creator) .Include(s => s.Creator)
.ToListAsync(); .ToListAsync();
this.NewestLevels = await this.Database.Slots.Where(s => s.Type == SlotType.User && s.Type == SlotType.User) this.NewestLevels = await this.Database.Slots.Where(s => s.Type == SlotType.User && !s.SubLevel)
.OrderByDescending(s => s.FirstUploaded) .OrderByDescending(s => s.FirstUploaded)
.Take(maxShownLevels) .Take(maxShownLevels)
.Include(s => s.Creator) .Include(s => s.Creator)

View file

@ -1,4 +1,5 @@
@using System.Globalization @using System.Globalization
@using System.Web
@using LBPUnion.ProjectLighthouse.Levels @using LBPUnion.ProjectLighthouse.Levels
@using LBPUnion.ProjectLighthouse.PlayerData @using LBPUnion.ProjectLighthouse.PlayerData
@model LBPUnion.ProjectLighthouse.PlayerData.Photo @model LBPUnion.ProjectLighthouse.PlayerData.Photo
@ -25,7 +26,7 @@
{ {
case SlotType.User: case SlotType.User:
<span> <span>
in level <b><a href="/slot/@Model.SlotId">@Model.Slot.Name</a></b> in level <b><a href="/slot/@Model.SlotId">@HttpUtility.HtmlDecode(Model.Slot.Name)</a></b>
</span> </span>
break; break;
case SlotType.Developer: case SlotType.Developer:

View file

@ -11,7 +11,7 @@ public class PirateSignupPage : BaseLayout
public PirateSignupPage(Database database) : base(database) public PirateSignupPage(Database database) : base(database)
{} {}
public async Task<IActionResult> OnGet() public IActionResult OnGet()
{ {
User? user = this.Database.UserFromWebRequest(this.Request); User? user = this.Database.UserFromWebRequest(this.Request);
if (user == null) return this.RedirectToPage("/login"); if (user == null) return this.RedirectToPage("/login");

View file

@ -17,7 +17,7 @@
bool isMobile = this.Request.IsMobile(); bool isMobile = this.Request.IsMobile();
} }
@if (Model.Slot.Hidden) @if (Model.Slot!.Hidden)
{ {
<div class="ui inverted red segment"> <div class="ui inverted red segment">
<h2>This level is currently hidden.</h2> <h2>This level is currently hidden.</h2>

View file

@ -54,7 +54,8 @@ public class SlotPage : BaseLayout
} }
} }
if (slot.Hidden && (this.User != slot.Creator && !(bool)this.User?.IsModerator)) return this.NotFound(); if (slot.Hidden || slot.SubLevel && this.User == null && this.User != slot.Creator || !this.User!.IsModerator)
return this.NotFound();
this.Slot = slot; this.Slot = slot;

View file

@ -5,7 +5,6 @@ using LBPUnion.ProjectLighthouse.Levels;
using LBPUnion.ProjectLighthouse.PlayerData; using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles; using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts; using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -49,7 +48,7 @@ public class SlotsPage : BaseLayout
} }
else else
{ {
finalSearch.Append(part); finalSearch.Append(part).Append(' ');
} }
} }
@ -59,6 +58,7 @@ public class SlotsPage : BaseLayout
.Where(p => p.Type == SlotType.User && !p.Hidden) .Where(p => p.Type == SlotType.User && !p.Hidden)
.Where(p => p.Name.Contains(finalSearch.ToString())) .Where(p => p.Name.Contains(finalSearch.ToString()))
.Where(p => p.Creator != null && (targetAuthor == null || string.Equals(p.Creator.Username.ToLower(), targetAuthor.ToLower()))) .Where(p => p.Creator != null && (targetAuthor == null || string.Equals(p.Creator.Username.ToLower(), targetAuthor.ToLower())))
.Where(p => p.Creator != null && (!p.SubLevel || p.Creator == this.User))
.Where(p => targetGame == null || p.GameVersion == targetGame) .Where(p => targetGame == null || p.GameVersion == targetGame)
.CountAsync(); .CountAsync();
@ -71,6 +71,7 @@ public class SlotsPage : BaseLayout
.Where(p => p.Type == SlotType.User && !p.Hidden) .Where(p => p.Type == SlotType.User && !p.Hidden)
.Where(p => p.Name.Contains(finalSearch.ToString())) .Where(p => p.Name.Contains(finalSearch.ToString()))
.Where(p => p.Creator != null && (targetAuthor == null || string.Equals(p.Creator.Username.ToLower(), targetAuthor.ToLower()))) .Where(p => p.Creator != null && (targetAuthor == null || string.Equals(p.Creator.Username.ToLower(), targetAuthor.ToLower())))
.Where(p => p.Creator != null && (!p.SubLevel || p.Creator == this.User))
.Where(p => p.Creator!.LevelVisibility == PrivacyType.All) // TODO: change check for when user is logged in .Where(p => p.Creator!.LevelVisibility == PrivacyType.All) // TODO: change check for when user is logged in
.Where(p => targetGame == null || p.GameVersion == targetGame) .Where(p => targetGame == null || p.GameVersion == targetGame)
.OrderByDescending(p => p.FirstUploaded) .OrderByDescending(p => p.FirstUploaded)

View file

@ -151,6 +151,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=PCSF/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=PCSF/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Sublevels/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Sublevels/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Swingy/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Swingy/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=thumbsdown/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=thumbsup/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=thumbsup/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=topscores/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=topscores/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=UCAS/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=UCAS/@EntryIndexedValue">True</s:Boolean>

View file

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.PlayerData;
namespace LBPUnion.ProjectLighthouse.Administration.Maintenance.MaintenanceJobs;
public class CleanupUnusedPhotoSubjects : IMaintenanceJob
{
private readonly Database database = new();
public string Name() => "Cleanup Unused PhotoSubjects";
public string Description() => "Cleanup unused photo subjects in the database.";
public async Task Run()
{
List<string> subjectCollections = new();
List<int> usedPhotoSubjectIds = new();
subjectCollections.AddRange(this.database.Photos.Select(p => p.PhotoSubjectCollection));
foreach (string idCollection in subjectCollections)
{
usedPhotoSubjectIds.AddRange(idCollection.Split(",").Where(x => int.TryParse(x, out _)).Select(int.Parse));
}
IQueryable<PhotoSubject> subjectsToRemove = this.database.PhotoSubjects.Where(p => !usedPhotoSubjectIds.Contains(p.PhotoSubjectId));
foreach (PhotoSubject subject in subjectsToRemove)
{
Console.WriteLine(@"Removing subject " + subject.PhotoSubjectId);
this.database.PhotoSubjects.Remove(subject);
}
await this.database.SaveChangesAsync();
}
}

View file

@ -9,5 +9,5 @@ public class CleanupRoomsTask : IRepeatingTask
public string Name => "Cleanup Rooms"; public string Name => "Cleanup Rooms";
public TimeSpan RepeatInterval => TimeSpan.FromSeconds(10); public TimeSpan RepeatInterval => TimeSpan.FromSeconds(10);
public DateTime LastRan { get; set; } public DateTime LastRan { get; set; }
public async Task Run(Database database) => RoomHelper.CleanupRooms(); public Task Run(Database database) => RoomHelper.CleanupRooms();
} }

View file

@ -125,20 +125,20 @@ public class Database : DbContext
#region Hearts & Queues #region Hearts & Queues
public async Task<bool> RateComment(User user, int commentId, int rating) public async Task<bool> RateComment(int userId, int commentId, int rating)
{ {
Comment? comment = await this.Comments.FirstOrDefaultAsync(c => commentId == c.CommentId); Comment? comment = await this.Comments.FirstOrDefaultAsync(c => commentId == c.CommentId);
if (comment == null) return false; if (comment == null) return false;
if (comment.PosterUserId == user.UserId) return false; if (comment.PosterUserId == userId) return false;
Reaction? reaction = await this.Reactions.FirstOrDefaultAsync(r => r.UserId == user.UserId && r.TargetId == commentId); Reaction? reaction = await this.Reactions.FirstOrDefaultAsync(r => r.UserId == userId && r.TargetId == commentId);
if (reaction == null) if (reaction == null)
{ {
Reaction newReaction = new() Reaction newReaction = new()
{ {
UserId = user.UserId, UserId = userId,
TargetId = commentId, TargetId = commentId,
Rating = 0, Rating = 0,
}; };
@ -176,7 +176,7 @@ public class Database : DbContext
return true; return true;
} }
public async Task<bool> PostComment(User user, int targetId, CommentType type, string message) public async Task<bool> PostComment(int userId, int targetId, CommentType type, string message)
{ {
if (message.Length > 100) return false; if (message.Length > 100) return false;
@ -195,7 +195,7 @@ public class Database : DbContext
( (
new Comment new Comment
{ {
PosterUserId = user.UserId, PosterUserId = userId,
TargetId = targetId, TargetId = targetId,
Type = type, Type = type,
Message = message, Message = message,
@ -206,9 +206,9 @@ public class Database : DbContext
return true; return true;
} }
public async Task HeartUser(User user, User heartedUser) public async Task HeartUser(int userId, User heartedUser)
{ {
HeartedProfile? heartedProfile = await this.HeartedProfiles.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.HeartedUserId == heartedUser.UserId); HeartedProfile? heartedProfile = await this.HeartedProfiles.FirstOrDefaultAsync(q => q.UserId == userId && q.HeartedUserId == heartedUser.UserId);
if (heartedProfile != null) return; if (heartedProfile != null) return;
this.HeartedProfiles.Add this.HeartedProfiles.Add
@ -216,24 +216,24 @@ public class Database : DbContext
new HeartedProfile new HeartedProfile
{ {
HeartedUserId = heartedUser.UserId, HeartedUserId = heartedUser.UserId,
UserId = user.UserId, UserId = userId,
} }
); );
await this.SaveChangesAsync(); await this.SaveChangesAsync();
} }
public async Task UnheartUser(User user, User heartedUser) public async Task UnheartUser(int userId, User heartedUser)
{ {
HeartedProfile? heartedProfile = await this.HeartedProfiles.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.HeartedUserId == heartedUser.UserId); HeartedProfile? heartedProfile = await this.HeartedProfiles.FirstOrDefaultAsync(q => q.UserId == userId && q.HeartedUserId == heartedUser.UserId);
if (heartedProfile != null) this.HeartedProfiles.Remove(heartedProfile); if (heartedProfile != null) this.HeartedProfiles.Remove(heartedProfile);
await this.SaveChangesAsync(); await this.SaveChangesAsync();
} }
public async Task HeartLevel(User user, Slot heartedSlot) public async Task HeartLevel(int userId, Slot heartedSlot)
{ {
HeartedLevel? heartedLevel = await this.HeartedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == heartedSlot.SlotId); HeartedLevel? heartedLevel = await this.HeartedLevels.FirstOrDefaultAsync(q => q.UserId == userId && q.SlotId == heartedSlot.SlotId);
if (heartedLevel != null) return; if (heartedLevel != null) return;
this.HeartedLevels.Add this.HeartedLevels.Add
@ -241,24 +241,24 @@ public class Database : DbContext
new HeartedLevel new HeartedLevel
{ {
SlotId = heartedSlot.SlotId, SlotId = heartedSlot.SlotId,
UserId = user.UserId, UserId = userId,
} }
); );
await this.SaveChangesAsync(); await this.SaveChangesAsync();
} }
public async Task UnheartLevel(User user, Slot heartedSlot) public async Task UnheartLevel(int userId, Slot heartedSlot)
{ {
HeartedLevel? heartedLevel = await this.HeartedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == heartedSlot.SlotId); HeartedLevel? heartedLevel = await this.HeartedLevels.FirstOrDefaultAsync(q => q.UserId == userId && q.SlotId == heartedSlot.SlotId);
if (heartedLevel != null) this.HeartedLevels.Remove(heartedLevel); if (heartedLevel != null) this.HeartedLevels.Remove(heartedLevel);
await this.SaveChangesAsync(); await this.SaveChangesAsync();
} }
public async Task QueueLevel(User user, Slot queuedSlot) public async Task QueueLevel(int userId, Slot queuedSlot)
{ {
QueuedLevel? queuedLevel = await this.QueuedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == queuedSlot.SlotId); QueuedLevel? queuedLevel = await this.QueuedLevels.FirstOrDefaultAsync(q => q.UserId == userId && q.SlotId == queuedSlot.SlotId);
if (queuedLevel != null) return; if (queuedLevel != null) return;
this.QueuedLevels.Add this.QueuedLevels.Add
@ -266,16 +266,16 @@ public class Database : DbContext
new QueuedLevel new QueuedLevel
{ {
SlotId = queuedSlot.SlotId, SlotId = queuedSlot.SlotId,
UserId = user.UserId, UserId = userId,
} }
); );
await this.SaveChangesAsync(); await this.SaveChangesAsync();
} }
public async Task UnqueueLevel(User user, Slot queuedSlot) public async Task UnqueueLevel(int userId, Slot queuedSlot)
{ {
QueuedLevel? queuedLevel = await this.QueuedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == queuedSlot.SlotId); QueuedLevel? queuedLevel = await this.QueuedLevels.FirstOrDefaultAsync(q => q.UserId == userId && q.SlotId == queuedSlot.SlotId);
if (queuedLevel != null) this.QueuedLevels.Remove(queuedLevel); if (queuedLevel != null) this.QueuedLevels.Remove(queuedLevel);
await this.SaveChangesAsync(); await this.SaveChangesAsync();
@ -285,6 +285,13 @@ public class Database : DbContext
#region Game Token Shenanigans #region Game Token Shenanigans
public async Task<string> UsernameFromGameToken(GameToken? token)
{
if (token == null) return "";
return await this.Users.Where(u => u.UserId == token.UserId).Select(u => u.Username).FirstAsync();
}
public async Task<User?> UserFromMMAuth(string authToken, bool allowUnapproved = false) public async Task<User?> UserFromMMAuth(string authToken, bool allowUnapproved = false)
{ {
if (ServerStatics.IsUnitTesting) allowUnapproved = true; if (ServerStatics.IsUnitTesting) allowUnapproved = true;
@ -300,7 +307,7 @@ public class Database : DbContext
return null; return null;
} }
return await this.Users.Include(u => u.Location).FirstOrDefaultAsync(u => u.UserId == token.UserId); return await this.Users.FirstOrDefaultAsync(u => u.UserId == token.UserId);
} }
public async Task<User?> UserFromGameToken public async Task<User?> UserFromGameToken
@ -351,7 +358,7 @@ public class Database : DbContext
return null; return null;
} }
User? user = await this.UserFromGameToken(token); User? user = await this.Users.FirstOrDefaultAsync(u => u.UserId == token.UserId);
if (user == null) return null; if (user == null) return null;
@ -362,6 +369,13 @@ public class Database : DbContext
#region Web Token Shenanigans #region Web Token Shenanigans
public async Task<string> UsernameFromWebToken(WebToken? token)
{
if (token == null) return "";
return await this.Users.Where(u => u.UserId == token.UserId).Select(u => u.Username).FirstAsync();
}
public User? UserFromLighthouseToken(string lighthouseToken) public User? UserFromLighthouseToken(string lighthouseToken)
{ {
WebToken? token = this.WebTokens.FirstOrDefault(t => t.UserToken == lighthouseToken); WebToken? token = this.WebTokens.FirstOrDefault(t => t.UserToken == lighthouseToken);

View file

@ -22,6 +22,6 @@ public class HeartedCategory : CategoryWithUser
.Include(h => h.Slot) .Include(h => h.Slot)
.Select(h => h.Slot) .Select(h => h.Slot)
.ByGameVersion(GameVersion.LittleBigPlanet3) .ByGameVersion(GameVersion.LittleBigPlanet3)
.Skip(pageStart) .Skip(Math.Max(0, pageStart))
.Take(Math.Min(pageSize, 20)); .Take(Math.Min(pageSize, 20));
} }

View file

@ -18,7 +18,7 @@ public class NewestLevelsCategory : Category
(Database database, int pageStart, int pageSize) (Database database, int pageStart, int pageSize)
=> database.Slots.ByGameVersion(GameVersion.LittleBigPlanet3, false, true) => database.Slots.ByGameVersion(GameVersion.LittleBigPlanet3, false, true)
.OrderByDescending(s => s.FirstUploaded) .OrderByDescending(s => s.FirstUploaded)
.Skip(pageStart - 1) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 20)); .Take(Math.Min(pageSize, 20));
public override int GetTotalSlots(Database database) => database.Slots.Count(s => s.Type == SlotType.User); public override int GetTotalSlots(Database database) => database.Slots.Count(s => s.Type == SlotType.User);
} }

View file

@ -25,7 +25,7 @@ public class QueueCategory : CategoryWithUser
.Include(q => q.Slot.Location) .Include(q => q.Slot.Location)
.Select(q => q.Slot) .Select(q => q.Slot)
.ByGameVersion(GameVersion.LittleBigPlanet3) .ByGameVersion(GameVersion.LittleBigPlanet3)
.Skip(pageStart - 1) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 20)); .Take(Math.Min(pageSize, 20));
public override int GetTotalSlots(Database database, User user) => database.QueuedLevels.Count(q => q.UserId == user.UserId); public override int GetTotalSlots(Database database, User user) => database.QueuedLevels.Count(q => q.UserId == user.UserId);

View file

@ -20,7 +20,7 @@ public class TeamPicksCategory : Category
(GameVersion.LittleBigPlanet3, false, true) (GameVersion.LittleBigPlanet3, false, true)
.OrderByDescending(s => s.FirstUploaded) .OrderByDescending(s => s.FirstUploaded)
.Where(s => s.TeamPick) .Where(s => s.TeamPick)
.Skip(pageStart - 1) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 20)); .Take(Math.Min(pageSize, 20));
public override int GetTotalSlots(Database database) => database.Slots.Count(s => s.TeamPick); public override int GetTotalSlots(Database database) => database.Slots.Count(s => s.TeamPick);
} }

View file

@ -1,5 +1,4 @@
#nullable enable #nullable enable
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Linq; using System.Linq;
@ -155,15 +154,15 @@ public class Slot
[XmlIgnore] [XmlIgnore]
[NotMapped] [NotMapped]
public int Plays => this.PlaysLBP1 + this.PlaysLBP2 + this.PlaysLBP3 + this.PlaysLBPVita; public int Plays => this.PlaysLBP1 + this.PlaysLBP2 + this.PlaysLBP3;
[XmlIgnore] [XmlIgnore]
[NotMapped] [NotMapped]
public int PlaysUnique => this.PlaysLBP1Unique + this.PlaysLBP2Unique + this.PlaysLBP3Unique + this.PlaysLBPVitaUnique; public int PlaysUnique => this.PlaysLBP1Unique + this.PlaysLBP2Unique + this.PlaysLBP3Unique;
[XmlIgnore] [XmlIgnore]
[NotMapped] [NotMapped]
public int PlaysComplete => this.PlaysLBP1Complete + this.PlaysLBP2Complete + this.PlaysLBP3Complete + this.PlaysLBPVitaComplete; public int PlaysComplete => this.PlaysLBP1Complete + this.PlaysLBP2Complete + this.PlaysLBP3Complete;
[XmlIgnore] [XmlIgnore]
[JsonIgnore] [JsonIgnore]
@ -201,18 +200,6 @@ public class Slot
[JsonIgnore] [JsonIgnore]
public int PlaysLBP3Unique { get; set; } public int PlaysLBP3Unique { get; set; }
[XmlIgnore]
[JsonIgnore]
public int PlaysLBPVita { get; set; }
[XmlIgnore]
[JsonIgnore]
public int PlaysLBPVitaComplete { get; set; }
[XmlIgnore]
[JsonIgnore]
public int PlaysLBPVitaUnique { get; set; }
[NotMapped] [NotMapped]
[JsonIgnore] [JsonIgnore]
[XmlElement("thumbsup")] [XmlElement("thumbsup")]
@ -252,33 +239,18 @@ public class Slot
[JsonIgnore] [JsonIgnore]
public string HiddenReason { get; set; } = ""; public string HiddenReason { get; set; } = "";
public string SerializeResources() public string SerializeDevSlot()
{ {
return this.Resources.Aggregate("", (current, resource) => current + LbpSerializer.StringElement("resource", resource)) + int comments = this.Comments;
LbpSerializer.StringElement("sizeOfResources", this.Resources.Sum(FileHelper.ResourceSize)); int photos = this.Photos;
}
public string SerializeDevSlot(bool includeExtras = true)
{
int comments = 0, photos = 0;
if (includeExtras)
{
comments = this.database.Comments.Count(c => c.Type == CommentType.Level && c.TargetId == this.SlotId);
photos = this.database.Photos.Count(c => c.SlotId == this.SlotId);
}
int players = RoomHelper.Rooms int players = RoomHelper.Rooms
.Where(r => r.Slot.SlotType == SlotType.Developer && r.Slot.SlotId == this.InternalSlotId) .Where(r => r.Slot.SlotType == SlotType.Developer && r.Slot.SlotId == this.InternalSlotId)
.Sum(r => r.PlayerIds.Count); .Sum(r => r.PlayerIds.Count);
string slotData = LbpSerializer.StringElement("id", this.InternalSlotId) + string slotData = LbpSerializer.StringElement("id", this.InternalSlotId) +
LbpSerializer.StringElement("playerCount", players); LbpSerializer.StringElement("playerCount", players) +
LbpSerializer.StringElement("commentCount", comments) +
if(includeExtras) LbpSerializer.StringElement("photoCount", photos);
slotData += LbpSerializer.StringElement("commentCount", comments) +
LbpSerializer.StringElement("photoCount", photos);
return LbpSerializer.TaggedStringElement("slot", slotData, "type", "developer"); return LbpSerializer.TaggedStringElement("slot", slotData, "type", "developer");
} }
@ -291,89 +263,76 @@ public class Slot
GameVersion gameVersion = GameVersion.LittleBigPlanet1, GameVersion gameVersion = GameVersion.LittleBigPlanet1,
RatedLevel? yourRatingStats = null, RatedLevel? yourRatingStats = null,
VisitedLevel? yourVisitedStats = null, VisitedLevel? yourVisitedStats = null,
Review? yourReview = null Review? yourReview = null,
bool fullSerialization = false
) )
{ {
if (this.Type == SlotType.Developer) return this.SerializeDevSlot(); if (this.Type == SlotType.Developer) return this.SerializeDevSlot();
int playerCount = RoomHelper.Rooms.Count(r => r.Slot.SlotType == SlotType.User && r.Slot.SlotId == this.SlotId); int playerCount = RoomHelper.Rooms.Count(r => r.Slot.SlotType == SlotType.User && r.Slot.SlotId == this.SlotId);
string slotData = LbpSerializer.StringElement("name", this.Name) + string slotData = LbpSerializer.StringElement("id", this.SlotId) +
LbpSerializer.StringElement("id", this.SlotId) +
LbpSerializer.StringElement("game", (int)this.GameVersion) +
LbpSerializer.StringElement("npHandle", this.Creator?.Username) + LbpSerializer.StringElement("npHandle", this.Creator?.Username) +
LbpSerializer.StringElement("description", this.Description) +
LbpSerializer.StringElement("icon", this.IconHash) +
LbpSerializer.StringElement("rootLevel", this.RootLevel) +
LbpSerializer.StringElement("authorLabels", this.AuthorLabels) +
LbpSerializer.StringElement("labels", this.AuthorLabels) +
this.SerializeResources() +
LbpSerializer.StringElement("location", this.Location?.Serialize()) + LbpSerializer.StringElement("location", this.Location?.Serialize()) +
LbpSerializer.StringElement("game", (int)this.GameVersion) +
LbpSerializer.StringElement("name", this.Name) +
LbpSerializer.StringElement("description", this.Description) +
LbpSerializer.StringElement("rootLevel", this.RootLevel) +
LbpSerializer.StringElement("icon", this.IconHash) +
LbpSerializer.StringElement("initiallyLocked", this.InitiallyLocked) + LbpSerializer.StringElement("initiallyLocked", this.InitiallyLocked) +
LbpSerializer.StringElement("isSubLevel", this.SubLevel) + LbpSerializer.StringElement("isSubLevel", this.SubLevel) +
LbpSerializer.StringElement("isLBP1Only", this.Lbp1Only) + LbpSerializer.StringElement("isLBP1Only", this.Lbp1Only) +
LbpSerializer.StringElement("shareable", this.Shareable) +
LbpSerializer.StringElement("background", this.BackgroundHash) + LbpSerializer.StringElement("background", this.BackgroundHash) +
LbpSerializer.StringElement("shareable", this.Shareable) +
LbpSerializer.StringElement("authorLabels", this.AuthorLabels) +
LbpSerializer.StringElement("leveltype", this.LevelType) +
LbpSerializer.StringElement("minPlayers", this.MinimumPlayers) + LbpSerializer.StringElement("minPlayers", this.MinimumPlayers) +
LbpSerializer.StringElement("maxPlayers", this.MaximumPlayers) + LbpSerializer.StringElement("maxPlayers", this.MaximumPlayers) +
LbpSerializer.StringElement("moveRequired", this.MoveRequired) +
LbpSerializer.StringElement("firstPublished", this.FirstUploaded) +
LbpSerializer.StringElement("lastUpdated", this.LastUpdated) +
LbpSerializer.StringElement("mmpick", this.TeamPick) +
LbpSerializer.StringElement("heartCount", this.Hearts) + LbpSerializer.StringElement("heartCount", this.Hearts) +
LbpSerializer.StringElement("playCount", this.Plays) + LbpSerializer.StringElement("thumbsup", this.Thumbsup) +
LbpSerializer.StringElement("thumbsdown", this.Thumbsdown) +
LbpSerializer.StringElement("averageRating", this.RatingLBP1) +
LbpSerializer.StringElement("playerCount", playerCount) +
LbpSerializer.StringElement("mmpick", this.TeamPick) +
(fullSerialization ? LbpSerializer.StringElement("moveRequired", this.MoveRequired) : "") +
(fullSerialization ? LbpSerializer.StringElement("crossControlRequired", this.CrossControllerRequired) : "") +
(yourRatingStats != null ?
LbpSerializer.StringElement("yourRating", yourRatingStats.RatingLBP1) +
LbpSerializer.StringElement("yourDPadRating", yourRatingStats.Rating)
: "") +
(yourVisitedStats != null ?
LbpSerializer.StringElement("yourlbp1PlayCount", yourVisitedStats.PlaysLBP1) +
LbpSerializer.StringElement("yourlbp2PlayCount", yourVisitedStats.PlaysLBP2) +
LbpSerializer.StringElement("yourlbp3PlayCount", yourVisitedStats.PlaysLBP3)
: "") +
LbpSerializer.StringElement("reviewCount", this.ReviewCount) +
LbpSerializer.StringElement("commentCount", this.Comments) +
LbpSerializer.StringElement("photoCount", this.Photos) + LbpSerializer.StringElement("photoCount", this.Photos) +
LbpSerializer.StringElement("authorPhotoCount", this.PhotosWithAuthor) + LbpSerializer.StringElement("authorPhotoCount", this.PhotosWithAuthor) +
LbpSerializer.StringElement("commentCount", this.Comments) + (fullSerialization ? LbpSerializer.StringElement("labels", this.AuthorLabels) : "") +
LbpSerializer.StringElement("uniquePlayCount", this.PlaysLBP2Unique) + // ??? good naming scheme lol LbpSerializer.StringElement("firstPublished", this.FirstUploaded) +
LbpSerializer.StringElement("lastUpdated", this.LastUpdated) +
(fullSerialization ?
yourReview?.Serialize() +
LbpSerializer.StringElement("reviewsEnabled", ServerConfiguration.Instance.UserGeneratedContentLimits.LevelReviewsEnabled) +
LbpSerializer.StringElement("commentsEnabled", ServerConfiguration.Instance.UserGeneratedContentLimits.LevelCommentsEnabled && this.CommentsEnabled)
: "") +
LbpSerializer.StringElement("playCount", this.Plays) +
LbpSerializer.StringElement("completionCount", this.PlaysComplete) + LbpSerializer.StringElement("completionCount", this.PlaysComplete) +
LbpSerializer.StringElement("lbp1PlayCount", this.PlaysLBP1) + LbpSerializer.StringElement("lbp1PlayCount", this.PlaysLBP1) +
LbpSerializer.StringElement("lbp1CompletionCount", this.PlaysLBP1Complete) + LbpSerializer.StringElement("lbp1CompletionCount", this.PlaysLBP1Complete) +
LbpSerializer.StringElement("lbp1UniquePlayCount", this.PlaysLBP1Unique) + LbpSerializer.StringElement("lbp1UniquePlayCount", this.PlaysLBP1Unique) +
LbpSerializer.StringElement("lbp2PlayCount", this.PlaysLBP2) +
LbpSerializer.StringElement("lbp2CompletionCount", this.PlaysLBP2Complete) +
LbpSerializer.StringElement("uniquePlayCount", this.PlaysLBP2Unique) + // ??? good naming scheme lol
LbpSerializer.StringElement("lbp3PlayCount", this.PlaysLBP3) + LbpSerializer.StringElement("lbp3PlayCount", this.PlaysLBP3) +
LbpSerializer.StringElement("lbp3CompletionCount", this.PlaysLBP3Complete) + LbpSerializer.StringElement("lbp3CompletionCount", this.PlaysLBP3Complete) +
LbpSerializer.StringElement("lbp3UniquePlayCount", this.PlaysLBP3Unique) + LbpSerializer.StringElement("lbp3UniquePlayCount", this.PlaysLBP3Unique) +
LbpSerializer.StringElement("thumbsup", this.Thumbsup) + (gameVersion == GameVersion.LittleBigPlanetVita ?
LbpSerializer.StringElement("thumbsdown", this.Thumbsdown) + LbpSerializer.StringElement("sizeOfResources", this.Resources.Sum(FileHelper.ResourceSize))
LbpSerializer.StringElement("averageRating", this.RatingLBP1) + : "");
LbpSerializer.StringElement("leveltype", this.LevelType) +
LbpSerializer.StringElement("yourRating", yourRatingStats?.RatingLBP1) +
LbpSerializer.StringElement("yourDPadRating", yourRatingStats?.Rating) +
LbpSerializer.StringElement("yourlbpPlayCount", yourVisitedStats?.PlaysLBP1) +
LbpSerializer.StringElement("yourlbp3PlayCount", yourVisitedStats?.PlaysLBP3) +
yourReview?.Serialize("yourReview") +
LbpSerializer.StringElement("reviewsEnabled", ServerConfiguration.Instance.UserGeneratedContentLimits.LevelReviewsEnabled) +
LbpSerializer.StringElement("commentsEnabled", ServerConfiguration.Instance.UserGeneratedContentLimits.LevelCommentsEnabled && CommentsEnabled) +
LbpSerializer.StringElement("playerCount", playerCount) +
LbpSerializer.StringElement("reviewCount", this.ReviewCount);
int yourPlays;
int plays;
int playsComplete;
int playsUnique;
if (gameVersion == GameVersion.LittleBigPlanetVita)
{
yourPlays = yourVisitedStats?.PlaysLBPVita ?? 0;
plays = this.PlaysLBPVita;
playsComplete = this.PlaysLBPVitaComplete;
playsUnique = this.PlaysLBPVitaUnique;
}
else
{
yourPlays = yourVisitedStats?.PlaysLBP2 ?? 0;
plays = this.PlaysLBP2;
playsComplete = this.PlaysLBP2Complete;
playsUnique = this.PlaysLBP2Unique;
}
slotData += LbpSerializer.StringElement("yourlbp2PlayCount", yourPlays) +
LbpSerializer.StringElement("lbp2PlayCount", plays) +
LbpSerializer.StringElement("playCount", plays) +
LbpSerializer.StringElement("lbp2CompletionCount", playsComplete) +
LbpSerializer.StringElement("completionCount", playsComplete) +
LbpSerializer.StringElement("lbp2UniquePlayCount", playsUnique) + // not actually used ingame, as per above comment
LbpSerializer.StringElement("uniquePlayCount", playsUnique);
return LbpSerializer.TaggedStringElement("slot", slotData, "type", "user"); return LbpSerializer.TaggedStringElement("slot", slotData, "type", "user");
} }

View file

@ -23,5 +23,4 @@ public class VisitedLevel
public int PlaysLBP1 { get; set; } public int PlaysLBP1 { get; set; }
public int PlaysLBP2 { get; set; } public int PlaysLBP2 { get; set; }
public int PlaysLBP3 { get; set; } public int PlaysLBP3 { get; set; }
public int PlaysLBPVita { get; set; }
} }

View file

@ -184,7 +184,7 @@ public class RoomHelper
} }
[SuppressMessage("ReSharper", "InvertIf")] [SuppressMessage("ReSharper", "InvertIf")]
public static void CleanupRooms(int? hostId = null, Room? newRoom = null, Database? database = null) public static Task CleanupRooms(int? hostId = null, Room? newRoom = null, Database? database = null)
{ {
#if DEBUG #if DEBUG
Stopwatch stopwatch = new(); Stopwatch stopwatch = new();
@ -276,5 +276,7 @@ public class RoomHelper
Logger.Info(logText, LogArea.Match); Logger.Info(logText, LogArea.Match);
} }
} }
return Task.FromResult(0);
} }
} }

View file

@ -0,0 +1,69 @@
using LBPUnion.ProjectLighthouse;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ProjectLighthouse.Migrations
{
[DbContext(typeof(Database))]
[Migration("20220808053519_RemoveVitaPlayCount")]
public partial class RemoveVitaPlayCount : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("UPDATE Slots SET PlaysLbp2 = PlaysLBP2 + PlaysLBPVita");
migrationBuilder.Sql("UPDATE Slots SET PlaysLBP2Complete = PlaysLBP2Complete + PlaysLBPVitaComplete");
migrationBuilder.Sql("UPDATE Slots SET PlaysLBP2Unique = PlaysLBP2Unique + PlaysLBPVitaUnique");
migrationBuilder.Sql("UPDATE VisitedLevels SET PlaysLBP2 = PlaysLBP2 + PlaysLBPVita");
migrationBuilder.DropColumn(
name: "PlaysLBPVita",
table: "Slots");
migrationBuilder.DropColumn(
name: "PlaysLBPVitaComplete",
table: "Slots");
migrationBuilder.DropColumn(
name: "PlaysLBPVitaUnique",
table: "Slots");
migrationBuilder.DropColumn(
name: "PlaysLBPVita",
table: "VisitedLevels");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "PlaysLBPVita",
table: "Slots",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "PlaysLBPVitaComplete",
table: "Slots",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "PlaysLBPVitaUnique",
table: "Slots",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "PlaysLBPVita",
table: "VisitedLevels",
type: "int",
nullable: false,
defaultValue: 0);
}
}
}

View file

@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
@ -29,14 +30,14 @@ public class User
} }
public int UserId { get; set; } public int UserId { get; set; }
public string Username { get; set; } public string Username { get; set; } = "";
#nullable enable #nullable enable
[JsonIgnore] [JsonIgnore]
public string? EmailAddress { get; set; } = null; public string? EmailAddress { get; set; }
#nullable disable #nullable disable
public bool EmailAddressVerified { get; set; } = false; public bool EmailAddressVerified { get; set; }
[JsonIgnore] [JsonIgnore]
public string Password { get; set; } public string Password { get; set; }
@ -82,9 +83,19 @@ public class User
[JsonIgnore] [JsonIgnore]
public int PhotosByMe => this.database.Photos.Count(p => p.CreatorId == this.UserId); public int PhotosByMe => this.database.Photos.Count(p => p.CreatorId == this.UserId);
[NotMapped] private int PhotosWithMe()
[JsonIgnore] {
public int PhotosWithMe => Enumerable.Sum(this.database.Photos, photo => photo.Subjects.Count(subject => subject.User.UserId == this.UserId)); List<int> photoSubjectIds = new();
photoSubjectIds.AddRange(this.database.PhotoSubjects.Where(p => p.UserId == this.UserId).Select(p => p.PhotoSubjectId));
var list = this.database.Photos.Select(p => new
{
p.PhotoId,
p.PhotoSubjectCollection,
}).ToList();
List<int> photoIds = (from v in list where photoSubjectIds.Any(ps => v.PhotoSubjectCollection.Contains(ps.ToString())) select v.PhotoId).ToList();
return this.database.Photos.Count(p => photoIds.Any(pId => p.PhotoId == pId) && p.CreatorId != this.UserId);
}
[JsonIgnore] [JsonIgnore]
public int LocationId { get; set; } public int LocationId { get; set; }
@ -156,8 +167,10 @@ public class User
[JsonIgnore] [JsonIgnore]
public PermissionLevel PermissionLevel { get; set; } = PermissionLevel.Default; public PermissionLevel PermissionLevel { get; set; } = PermissionLevel.Default;
#nullable enable
[JsonIgnore] [JsonIgnore]
public string? BannedReason { get; set; } public string? BannedReason { get; set; }
#nullable disable
#nullable enable #nullable enable
[JsonIgnore] [JsonIgnore]
@ -181,36 +194,36 @@ public class User
string user = LbpSerializer.TaggedStringElement("npHandle", this.Username, "icon", this.IconHash) + string user = LbpSerializer.TaggedStringElement("npHandle", this.Username, "icon", this.IconHash) +
LbpSerializer.StringElement("game", (int)gameVersion) + LbpSerializer.StringElement("game", (int)gameVersion) +
this.serializeSlots(gameVersion) + this.serializeSlots(gameVersion) +
LbpSerializer.StringElement("lists", this.Lists) + LbpSerializer.StringElement<string>("lists", this.Lists, true) +
LbpSerializer.StringElement LbpSerializer.StringElement<string>
( (
"lists_quota", "lists_quota",
ServerConfiguration.Instance.UserGeneratedContentLimits.ListsQuota ServerConfiguration.Instance.UserGeneratedContentLimits.ListsQuota,
true
) + // technically not a part of the user but LBP expects it ) + // technically not a part of the user but LBP expects it
LbpSerializer.StringElement("biography", this.Biography) + LbpSerializer.StringElement<string>("heartCount", this.Hearts, true) +
LbpSerializer.StringElement("reviewCount", this.Reviews) +
LbpSerializer.StringElement("commentCount", this.Comments) +
LbpSerializer.StringElement("photosByMeCount", this.PhotosByMe) +
LbpSerializer.StringElement("photosWithMeCount", this.PhotosWithMe) +
LbpSerializer.StringElement("commentsEnabled", ServerConfiguration.Instance.UserGeneratedContentLimits.ProfileCommentsEnabled && CommentsEnabled) +
LbpSerializer.StringElement("location", this.Location.Serialize()) +
LbpSerializer.StringElement("favouriteSlotCount", this.HeartedLevels) +
LbpSerializer.StringElement("favouriteUserCount", this.HeartedUsers) +
LbpSerializer.StringElement("lolcatftwCount", this.QueuedLevels) +
LbpSerializer.StringElement("pins", this.Pins) +
this.serializeEarth(gameVersion) + this.serializeEarth(gameVersion) +
LbpSerializer.BlankElement("photos") + LbpSerializer.StringElement<string>("yay2", this.YayHash, true) +
LbpSerializer.StringElement("heartCount", this.Hearts) + LbpSerializer.StringElement<string>("boo2", this.BooHash, true) +
LbpSerializer.StringElement("yay2", this.YayHash) + LbpSerializer.StringElement<string>("meh2", this.MehHash, true) +
LbpSerializer.StringElement("boo2", this.BooHash) + LbpSerializer.StringElement<string>("biography", this.Biography, true) +
LbpSerializer.StringElement("meh2", this.MehHash); LbpSerializer.StringElement<int>("reviewCount", this.Reviews, true) +
LbpSerializer.StringElement<int>("commentCount", this.Comments, true) +
LbpSerializer.StringElement<int>("photosByMeCount", this.PhotosByMe, true) +
LbpSerializer.StringElement<int>("photosWithMeCount", this.PhotosWithMe(), true) +
LbpSerializer.StringElement("commentsEnabled", ServerConfiguration.Instance.UserGeneratedContentLimits.ProfileCommentsEnabled && this.CommentsEnabled) +
LbpSerializer.StringElement("location", this.Location.Serialize()) +
LbpSerializer.StringElement<int>("favouriteSlotCount", this.HeartedLevels, true) +
LbpSerializer.StringElement<int>("favouriteUserCount", this.HeartedUsers, true) +
LbpSerializer.StringElement<int>("lolcatftwCount", this.QueuedLevels, true) +
LbpSerializer.StringElement<string>("pins", this.Pins, true);
return LbpSerializer.TaggedStringElement("user", user, "type", "user"); return LbpSerializer.TaggedStringElement("user", user, "type", "user");
} }
private string serializeEarth(GameVersion gameVersion) private string serializeEarth(GameVersion gameVersion)
{ {
return LbpSerializer.StringElement return LbpSerializer.StringElement<string>
( (
"planets", "planets",
gameVersion switch gameVersion switch
@ -219,7 +232,8 @@ public class User
GameVersion.LittleBigPlanet3 => this.PlanetHashLBP3, GameVersion.LittleBigPlanet3 => this.PlanetHashLBP3,
GameVersion.LittleBigPlanetVita => this.PlanetHashLBPVita, GameVersion.LittleBigPlanetVita => this.PlanetHashLBPVita,
_ => "", // other versions do not have custom planets _ => "", // other versions do not have custom planets
} },
true
); );
} }
@ -243,52 +257,45 @@ public class User
[XmlIgnore] [XmlIgnore]
public int EntitledSlots => ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots + this.AdminGrantedSlots; public int EntitledSlots => ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots + this.AdminGrantedSlots;
/// <summary>
/// The number of slots remaining on the earth
/// </summary>
[JsonIgnore] [JsonIgnore]
public int FreeSlots => this.EntitledSlots - this.UsedSlots; [XmlIgnore]
private int CrossControlSlots => this.database.Slots.Count(s => s.CreatorId == this.UserId && s.CrossControllerRequired);
[JsonIgnore] [JsonIgnore]
[XmlIgnore] [XmlIgnore]
public int AdminGrantedSlots { get; set; } public int AdminGrantedSlots { get; set; }
private static readonly string[] slotTypes =
{
"lbp2", "lbp3", "crossControl",
};
private string serializeSlots(GameVersion gameVersion) private string serializeSlots(GameVersion gameVersion)
{ {
string slots = string.Empty; string slots = string.Empty;
int entitledSlots = this.EntitledSlots;
string[] slotTypesLocal; Dictionary<string, int> usedSlots = new();
if (gameVersion == GameVersion.LittleBigPlanetVita) if (gameVersion == GameVersion.LittleBigPlanetVita)
{ {
slots += LbpSerializer.StringElement("lbp2UsedSlots", this.GetUsedSlotsForGame(GameVersion.LittleBigPlanetVita)); usedSlots.Add("lbp2", this.GetUsedSlotsForGame(GameVersion.LittleBigPlanetVita));
slotTypesLocal = new[]
{
"lbp2",
};
} }
else else
{ {
slots += LbpSerializer.StringElement("lbp1UsedSlots", this.GetUsedSlotsForGame(GameVersion.LittleBigPlanet1)); int lbp1Used = this.GetUsedSlotsForGame(GameVersion.LittleBigPlanet1);
slots += LbpSerializer.StringElement("lbp2UsedSlots", this.GetUsedSlotsForGame(GameVersion.LittleBigPlanet2)); int lbp2Used = this.GetUsedSlotsForGame(GameVersion.LittleBigPlanet2);
slots += LbpSerializer.StringElement("lbp3UsedSlots", this.GetUsedSlotsForGame(GameVersion.LittleBigPlanet3)); int lbp3Used = this.GetUsedSlotsForGame(GameVersion.LittleBigPlanet3);
slotTypesLocal = slotTypes; int crossControlUsed = this.CrossControlSlots;
usedSlots.Add("crossControl", crossControlUsed);
usedSlots.Add("lbp2", lbp2Used);
usedSlots.Add("lbp3", lbp3Used);
// these 3 actually correspond to lbp1 only despite the name
slots += LbpSerializer.StringElement("lbp1UsedSlots", lbp1Used);
slots += LbpSerializer.StringElement("entitledSlots", entitledSlots);
slots += LbpSerializer.StringElement("freeSlots", entitledSlots - lbp1Used);
} }
slots += LbpSerializer.StringElement("entitledSlots", this.EntitledSlots); foreach (KeyValuePair<string, int> entry in usedSlots)
slots += LbpSerializer.StringElement("freeSlots", this.FreeSlots);
foreach (string slotType in slotTypesLocal)
{ {
slots += LbpSerializer.StringElement(slotType + "EntitledSlots", this.EntitledSlots); slots += LbpSerializer.StringElement(entry.Key + "UsedSlots", entry.Value);
// ReSharper disable once StringLiteralTypo slots += LbpSerializer.StringElement(entry.Key + "EntitledSlots", entitledSlots);
slots += LbpSerializer.StringElement(slotType + slotType == "crossControl" ? "PurchsedSlots" : "PurchasedSlots", 0); slots += LbpSerializer.StringElement(entry.Key + (entry.Key == "crossControl" ? "PurchsedSlots" : "PurchasedSlots"), 0);
slots += LbpSerializer.StringElement(slotType + "FreeSlots", this.FreeSlots); slots += LbpSerializer.StringElement(entry.Key + "FreeSlots", entitledSlots - entry.Value);
} }
return slots; return slots;

View file

@ -323,15 +323,6 @@ namespace ProjectLighthouse.Migrations
b.Property<int>("PlaysLBP3Unique") b.Property<int>("PlaysLBP3Unique")
.HasColumnType("int"); .HasColumnType("int");
b.Property<int>("PlaysLBPVita")
.HasColumnType("int");
b.Property<int>("PlaysLBPVitaComplete")
.HasColumnType("int");
b.Property<int>("PlaysLBPVitaUnique")
.HasColumnType("int");
b.Property<string>("ResourceCollection") b.Property<string>("ResourceCollection")
.IsRequired() .IsRequired()
.HasColumnType("longtext"); .HasColumnType("longtext");
@ -376,9 +367,6 @@ namespace ProjectLighthouse.Migrations
b.Property<int>("PlaysLBP3") b.Property<int>("PlaysLBP3")
.HasColumnType("int"); .HasColumnType("int");
b.Property<int>("PlaysLBPVita")
.HasColumnType("int");
b.Property<int>("SlotId") b.Property<int>("SlotId")
.HasColumnType("int"); .HasColumnType("int");

View file

@ -1,6 +1,8 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using YamlDotNet.Core.Tokens;
namespace LBPUnion.ProjectLighthouse.Serialization; namespace LBPUnion.ProjectLighthouse.Serialization;
@ -20,6 +22,8 @@ public static class LbpSerializer
public static string StringElement(string key, object value) => $"<{key}>{value}</{key}>"; public static string StringElement(string key, object value) => $"<{key}>{value}</{key}>";
public static string StringElement<T>(string key, object value, bool omitIfDefault) => !omitIfDefault || value != null && !Equals(value, default(T)) && (!value.GetType().IsValueType || !Activator.CreateInstance(value.GetType())!.Equals(value)) ? $"<{key}>{value}</{key}>" : "";
public static string TaggedStringElement public static string TaggedStringElement
(KeyValuePair<string, object> pair, KeyValuePair<string, object> tagPair) (KeyValuePair<string, object> pair, KeyValuePair<string, object> tagPair)
=> $"<{pair.Key} {tagPair.Key}=\"{tagPair.Value}\">{pair.Value}</{pair.Key}>"; => $"<{pair.Key} {tagPair.Key}=\"{tagPair.Value}\">{pair.Value}</{pair.Key}>";

View file

@ -9,6 +9,12 @@ server {
ssl_ciphers ALL; ssl_ciphers ALL;
ssl_prefer_server_ciphers off; ssl_prefer_server_ciphers off;
# Enable gzip Compression
gzip on;
gzip_types *;
gzip_http_version 1.1;
gzip_min_length 100;
# Server locations # Server locations
# Technically, the ports dont follow standards, # Technically, the ports dont follow standards,
# but they're bad standards so who cares. # but they're bad standards so who cares.