mirror of
https://github.com/LBPUnion/ProjectLighthouse.git
synced 2025-06-01 05:32:27 +00:00
Move servers to LBPU.PL.Servers
This commit is contained in:
parent
545b5a0709
commit
b2ec7eae57
116 changed files with 173 additions and 162 deletions
|
@ -0,0 +1,57 @@
|
|||
#nullable enable
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Settings;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
[Produces("text/plain")]
|
||||
public class ClientConfigurationController : ControllerBase
|
||||
{
|
||||
private readonly Database database;
|
||||
|
||||
public ClientConfigurationController(Database database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
[HttpGet("network_settings.nws")]
|
||||
[SuppressMessage("ReSharper", "StringLiteralTypo")]
|
||||
public async Task<IActionResult> NetworkSettings()
|
||||
{
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
HostString hostname = this.Request.Host;
|
||||
return this.Ok
|
||||
(
|
||||
"ProbabilityOfPacketDelay 0.0\nMinPacketDelayFrames 0\nMaxPacketDelayFrames 3\nProbabilityOfPacketDrop 0.0\nEnableFakeConditionsForLoopback true\nNumberOfFramesPredictionAllowedForNonLocalPlayer 1000\nEnablePrediction true\nMinPredictedFrames 0\nMaxPredictedFrames 10\nAllowGameRendCameraSplit true\nFramesBeforeAgressiveCatchup 30\nPredictionPadSides 200\nPredictionPadTop 200\nPredictionPadBottom 200\nShowErrorNumbers true\nAllowModeratedLevels false\nAllowModeratedPoppetItems false\nTIMEOUT_WAIT_FOR_JOIN_RESPONSE_FROM_PREV_PARTY_HOST 50.0\nTIMEOUT_WAIT_FOR_CHANGE_LEVEL_PARTY_HOST 30.0\nTIMEOUT_WAIT_FOR_CHANGE_LEVEL_PARTY_MEMBER 45.0\nTIMEOUT_WAIT_FOR_REQUEST_JOIN_FRIEND 15.0\nTIMEOUT_WAIT_FOR_CONNECTION_FROM_HOST 30.0\nTIMEOUT_WAIT_FOR_ROOM_ID_TO_JOIN 60.0\nTIMEOUT_WAIT_FOR_GET_NUM_PLAYERS_ONLINE 60.0\nTIMEOUT_WAIT_FOR_SIGNALLING_CONNECTIONS 120.0\nTIMEOUT_WAIT_FOR_PARTY_DATA 60.0\nTIME_TO_WAIT_FOR_LEAVE_MESSAGE_TO_COME_BACK 20.0\nTIME_TO_WAIT_FOR_FOLLOWING_REQUESTS_TO_ARRIVE 30.0\nTIMEOUT_WAIT_FOR_FINISHED_MIGRATING_HOST 30.0\nTIMEOUT_WAIT_FOR_PARTY_LEADER_FINISH_JOINING 45.0\nTIMEOUT_WAIT_FOR_QUICKPLAY_LEVEL 60.0\nTIMEOUT_WAIT_FOR_PLAYERS_TO_JOIN 30.0\nTIMEOUT_WAIT_FOR_DIVE_IN_PLAYERS 240.0\nTIMEOUT_WAIT_FOR_FIND_BEST_ROOM 60.0\nTIMEOUT_DIVE_IN_TOTAL 300.0\nTIMEOUT_WAIT_FOR_SOCKET_CONNECTION 120.0\nTIMEOUT_WAIT_FOR_REQUEST_RESOURCE_MESSAGE 120.0\nTIMEOUT_WAIT_FOR_LOCAL_CLIENT_TO_GET_RESOURCE_LIST 120.0\nTIMEOUT_WAIT_FOR_CLIENT_TO_LOAD_RESOURCES 120.0\nTIMEOUT_WAIT_FOR_LOCAL_CLIENT_TO_SAVE_GAME_STATE 30.0\nTIMEOUT_WAIT_FOR_ADD_PLAYERS_TO_TAKE 30.0\nTIMEOUT_WAIT_FOR_UPDATE_FROM_CLIENT 90.0\nTIMEOUT_WAIT_FOR_HOST_TO_GET_RESOURCE_LIST 60.0\nTIMEOUT_WAIT_FOR_HOST_TO_SAVE_GAME_STATE 60.0\nTIMEOUT_WAIT_FOR_HOST_TO_ADD_US 30.0\nTIMEOUT_WAIT_FOR_UPDATE 60.0\nTIMEOUT_WAIT_FOR_REQUEST_JOIN 50.0\nTIMEOUT_WAIT_FOR_AUTOJOIN_PRESENCE 60.0\nTIMEOUT_WAIT_FOR_AUTOJOIN_CONNECTION 120.0\nSECONDS_BETWEEN_PINS_AWARDED_UPLOADS 300.0\nEnableKeepAlive true\nAllowVoIPRecordingPlayback true\nOverheatingThresholdDisallowMidgameJoin 0.95\nMaxCatchupFrames 3\nMaxLagBeforeShowLoading 23\nMinLagBeforeHideLoading 30\nLagImprovementInflectionPoint -1.0\nFlickerThreshold 2.0\nClosedDemo2014Version 1\nClosedDemo2014Expired false\nEnablePlayedFilter true\nEnableCommunityDecorations true\nGameStateUpdateRate 10.0\nGameStateUpdateRateWithConsumers 1.0\nDisableDLCPublishCheck false\nEnableDiveIn true\nEnableHackChecks false\nAllowOnlineCreate true" +
|
||||
$"TelemetryServer {hostname}\n" +
|
||||
$"CDNHostName {hostname}\n" +
|
||||
$"ShowLevelBoos {ServerConfiguration.Instance.UserGeneratedContentLimits.BooingEnabled.ToString().ToLower()}\n"
|
||||
);
|
||||
}
|
||||
|
||||
[HttpGet("t_conf")]
|
||||
[Produces("text/json")]
|
||||
public IActionResult Conf() => this.Ok("[{\"StatusCode\":200}]");
|
||||
|
||||
[HttpGet("farc_hashes")]
|
||||
public IActionResult FarcHashes() => this.Ok();
|
||||
|
||||
[HttpGet("privacySettings")]
|
||||
[Produces("text/xml")]
|
||||
public IActionResult PrivacySettings()
|
||||
{
|
||||
PrivacySettings ps = new()
|
||||
{
|
||||
LevelVisibility = "all",
|
||||
ProfileVisibility = "all",
|
||||
};
|
||||
|
||||
return this.Ok(ps.Serialize());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
#nullable enable
|
||||
using System.Xml.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Types.Profiles;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
[Produces("text/xml")]
|
||||
public class CommentController : ControllerBase
|
||||
{
|
||||
private readonly Database database;
|
||||
public CommentController(Database database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
[HttpPost("rateUserComment/{username}")]
|
||||
[HttpPost("rateComment/user/{slotId:int}")]
|
||||
public async Task<IActionResult> RateComment([FromQuery] int commentId, [FromQuery] int rating, string? username, int? slotId)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
bool success = await this.database.RateComment(user, commentId, rating);
|
||||
if (!success) return this.BadRequest();
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
[HttpGet("comments/user/{slotId:int}")]
|
||||
[HttpGet("userComments/{username}")]
|
||||
public async Task<IActionResult> GetComments([FromQuery] int pageStart, [FromQuery] int pageSize, string? username, int? slotId)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
int targetId = slotId.GetValueOrDefault();
|
||||
CommentType type = CommentType.Level;
|
||||
if (!string.IsNullOrWhiteSpace(username))
|
||||
{
|
||||
targetId = this.database.Users.First(u => u.Username.Equals(username)).UserId;
|
||||
type = CommentType.Profile;
|
||||
}
|
||||
|
||||
List<Comment> comments = await this.database.Comments.Include
|
||||
(c => c.Poster)
|
||||
.Where(c => c.TargetId == targetId && c.Type == type)
|
||||
.OrderByDescending(c => c.Timestamp)
|
||||
.Skip(pageStart - 1)
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.ToListAsync();
|
||||
|
||||
string outputXml = comments.Aggregate
|
||||
(string.Empty, (current, comment) => current + comment.Serialize(this.getReaction(user.UserId, comment.CommentId).Result));
|
||||
return this.Ok(LbpSerializer.StringElement("comments", outputXml));
|
||||
}
|
||||
|
||||
private async Task<int> getReaction(int userId, int commentId)
|
||||
{
|
||||
Reaction? reaction = await this.database.Reactions.FirstOrDefaultAsync(r => r.UserId == userId && r.TargetId == commentId);
|
||||
if (reaction == null) return 0;
|
||||
|
||||
return reaction.Rating;
|
||||
}
|
||||
|
||||
[HttpPost("postUserComment/{username}")]
|
||||
[HttpPost("postComment/user/{slotId:int}")]
|
||||
public async Task<IActionResult> PostComment(string? username, int? slotId)
|
||||
{
|
||||
User? poster = await this.database.UserFromGameRequest(this.Request);
|
||||
if (poster == null) return this.StatusCode(403, "");
|
||||
|
||||
this.Request.Body.Position = 0;
|
||||
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
|
||||
|
||||
XmlSerializer serializer = new(typeof(Comment));
|
||||
Comment? comment = (Comment?)serializer.Deserialize(new StringReader(bodyString));
|
||||
|
||||
SanitizationHelper.SanitizeStringsInClass(comment);
|
||||
|
||||
CommentType type = (slotId.GetValueOrDefault() == 0 ? CommentType.Profile : CommentType.Level);
|
||||
|
||||
if (comment == null) return this.BadRequest();
|
||||
|
||||
int targetId = slotId.GetValueOrDefault();
|
||||
|
||||
if (type == CommentType.Profile) targetId = this.database.Users.First(u => u.Username == username).UserId;
|
||||
|
||||
bool success = await this.database.PostComment(poster, targetId, type, comment.Message);
|
||||
if (success) return this.Ok();
|
||||
|
||||
return this.BadRequest();
|
||||
}
|
||||
|
||||
[HttpPost("deleteUserComment/{username}")]
|
||||
[HttpPost("deleteComment/user/{slotId:int}")]
|
||||
public async Task<IActionResult> DeleteComment([FromQuery] int commentId, string? username, int? slotId)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
Comment? comment = await this.database.Comments.FirstOrDefaultAsync(c => c.CommentId == commentId);
|
||||
if (comment == null) return this.NotFound();
|
||||
|
||||
// if you are not the poster
|
||||
if (comment.PosterUserId != user.UserId)
|
||||
{
|
||||
if (comment.Type == CommentType.Profile)
|
||||
{
|
||||
// if you aren't the poster and aren't the profile owner
|
||||
if (comment.TargetId != user.UserId)
|
||||
{
|
||||
return this.StatusCode(403, "");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == comment.TargetId);
|
||||
// if you aren't the creator of the level
|
||||
if (slot == null || slot.CreatorId != user.UserId || slotId.GetValueOrDefault() != slot.SlotId)
|
||||
{
|
||||
return this.StatusCode(403, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
comment.Deleted = true;
|
||||
comment.DeletedBy = user.Username;
|
||||
comment.DeletedType = "user";
|
||||
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
public class DeveloperController : Controller
|
||||
{
|
||||
[HttpGet("/developer_videos")]
|
||||
public IActionResult DeveloperVideos() => this.Ok();
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
#nullable enable
|
||||
using System.Xml.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Profiles;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
public class FriendsController : ControllerBase
|
||||
{
|
||||
private readonly Database database;
|
||||
|
||||
public FriendsController(Database database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
[HttpPost("npdata")]
|
||||
public async Task<IActionResult> NPData()
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
this.Request.Body.Position = 0;
|
||||
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
|
||||
|
||||
XmlSerializer serializer = new(typeof(NPData));
|
||||
NPData? npData = (NPData?)serializer.Deserialize(new StringReader(bodyString));
|
||||
if (npData == null) return this.BadRequest();
|
||||
|
||||
SanitizationHelper.SanitizeStringsInClass(npData);
|
||||
|
||||
List<User> friends = new();
|
||||
foreach (string friendName in npData.Friends)
|
||||
{
|
||||
User? friend = await this.database.Users.FirstOrDefaultAsync(u => u.Username == friendName);
|
||||
if (friend == null) continue;
|
||||
|
||||
friends.Add(friend);
|
||||
}
|
||||
|
||||
List<int> blockedUsers = new();
|
||||
foreach (string blockedUserName in npData.BlockedUsers)
|
||||
{
|
||||
User? blockedUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == blockedUserName);
|
||||
if (blockedUser == null) continue;
|
||||
|
||||
blockedUsers.Add(blockedUser.UserId);
|
||||
}
|
||||
|
||||
if (FriendHelper.FriendIdsByUserId.ContainsKey(user.UserId))
|
||||
{
|
||||
FriendHelper.FriendIdsByUserId.Remove(user.UserId);
|
||||
FriendHelper.BlockedIdsByUserId.Remove(user.UserId);
|
||||
}
|
||||
|
||||
FriendHelper.FriendIdsByUserId.Add(user.UserId, friends.Select(u => u.UserId).ToArray());
|
||||
FriendHelper.BlockedIdsByUserId.Add(user.UserId, blockedUsers.ToArray());
|
||||
|
||||
string friendsSerialized = friends.Aggregate(string.Empty, (current, user1) => current + LbpSerializer.StringElement("npHandle", user1.Username));
|
||||
|
||||
return this.Ok(LbpSerializer.StringElement("npdata", LbpSerializer.StringElement("friends", friendsSerialized)));
|
||||
}
|
||||
|
||||
[HttpGet("myFriends")]
|
||||
public async Task<IActionResult> MyFriends()
|
||||
{
|
||||
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request);
|
||||
|
||||
if (userAndToken == null) return this.StatusCode(403, "");
|
||||
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
User user = userAndToken.Value.Item1;
|
||||
GameToken gameToken = userAndToken.Value.Item2;
|
||||
|
||||
if (!FriendHelper.FriendIdsByUserId.TryGetValue(user.UserId, out int[]? friendIds) || friendIds == null)
|
||||
return this.Ok(LbpSerializer.BlankElement("myFriends"));
|
||||
|
||||
string friends = "";
|
||||
foreach (int friendId in friendIds)
|
||||
{
|
||||
User? friend = await this.database.Users.Include(u => u.Location).FirstOrDefaultAsync(u => u.UserId == friendId);
|
||||
if (friend == null) continue;
|
||||
|
||||
friends += friend.Serialize(gameToken.GameVersion);
|
||||
}
|
||||
|
||||
return this.Ok(LbpSerializer.StringElement("myFriends", friends));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
#nullable enable
|
||||
using System.Net;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Logging;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Settings;
|
||||
using LBPUnion.ProjectLighthouse.Types.Tickets;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/login")]
|
||||
[Produces("text/xml")]
|
||||
public class LoginController : ControllerBase
|
||||
{
|
||||
private readonly Database database;
|
||||
|
||||
public LoginController(Database database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Login()
|
||||
{
|
||||
MemoryStream ms = new();
|
||||
await this.Request.Body.CopyToAsync(ms);
|
||||
byte[] loginData = ms.ToArray();
|
||||
|
||||
NPTicket? npTicket;
|
||||
try
|
||||
{
|
||||
npTicket = NPTicket.CreateFromBytes(loginData);
|
||||
}
|
||||
catch
|
||||
{
|
||||
npTicket = null;
|
||||
}
|
||||
|
||||
if (npTicket == null)
|
||||
{
|
||||
Logger.LogWarn("npTicket was null, rejecting login", LogArea.Login);
|
||||
return this.BadRequest();
|
||||
}
|
||||
|
||||
IPAddress? remoteIpAddress = this.HttpContext.Connection.RemoteIpAddress;
|
||||
if (remoteIpAddress == null)
|
||||
{
|
||||
Logger.LogWarn("unable to determine ip, rejecting login", LogArea.Login);
|
||||
return this.StatusCode(403, ""); // 403 probably isnt the best status code for this, but whatever
|
||||
}
|
||||
|
||||
string ipAddress = remoteIpAddress.ToString();
|
||||
|
||||
// Get an existing token from the IP & username
|
||||
GameToken? token = await this.database.GameTokens.Include
|
||||
(t => t.User)
|
||||
.FirstOrDefaultAsync(t => t.UserLocation == ipAddress && t.User.Username == npTicket.Username && !t.Used);
|
||||
|
||||
if (token == null) // If we cant find an existing token, try to generate a new one
|
||||
{
|
||||
token = await this.database.AuthenticateUser(npTicket, ipAddress);
|
||||
if (token == null)
|
||||
{
|
||||
Logger.LogWarn($"Unable to find/generate a token for username {npTicket.Username}", LogArea.Login);
|
||||
return this.StatusCode(403, ""); // If not, then 403.
|
||||
}
|
||||
}
|
||||
|
||||
User? user = await this.database.UserFromGameToken(token, true);
|
||||
|
||||
if (user == null || user.Banned)
|
||||
{
|
||||
Logger.LogError($"Unable to find user {npTicket.Username} from token", LogArea.Login);
|
||||
return this.StatusCode(403, "");
|
||||
}
|
||||
|
||||
if (ServerConfiguration.Instance.Authentication.UseExternalAuth)
|
||||
{
|
||||
if (ServerConfiguration.Instance.Authentication.BlockDeniedUsers)
|
||||
{
|
||||
string ipAddressAndName = $"{token.UserLocation}|{user.Username}";
|
||||
if (DeniedAuthenticationHelper.RecentlyDenied(ipAddressAndName) || DeniedAuthenticationHelper.GetAttempts(ipAddressAndName) > 3)
|
||||
{
|
||||
this.database.AuthenticationAttempts.RemoveRange
|
||||
(this.database.AuthenticationAttempts.Include(a => a.GameToken).Where(a => a.GameToken.UserId == user.UserId));
|
||||
|
||||
DeniedAuthenticationHelper.AddAttempt(ipAddressAndName);
|
||||
|
||||
await this.database.SaveChangesAsync();
|
||||
Logger.LogWarn($"Too many recent denied logins from user {user.Username}, rejecting login", LogArea.Login);
|
||||
return this.StatusCode(403, "");
|
||||
}
|
||||
}
|
||||
|
||||
if (this.database.UserApprovedIpAddresses.Where(a => a.UserId == user.UserId).Select(a => a.IpAddress).Contains(ipAddress))
|
||||
{
|
||||
token.Approved = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
AuthenticationAttempt authAttempt = new()
|
||||
{
|
||||
GameToken = token,
|
||||
GameTokenId = token.TokenId,
|
||||
Timestamp = TimestampHelper.Timestamp,
|
||||
IPAddress = ipAddress,
|
||||
Platform = npTicket.Platform,
|
||||
};
|
||||
|
||||
this.database.AuthenticationAttempts.Add(authAttempt);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
token.Approved = true;
|
||||
}
|
||||
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
if (!token.Approved)
|
||||
{
|
||||
Logger.LogWarn($"Token unapproved for user {user.Username}, rejecting login", LogArea.Login);
|
||||
return this.StatusCode(403, "");
|
||||
}
|
||||
|
||||
Logger.LogSuccess($"Successfully logged in user {user.Username} as {token.GameVersion} client", LogArea.Login);
|
||||
// After this point we are now considering this session as logged in.
|
||||
|
||||
// We just logged in with the token. Mark it as used so someone else doesnt try to use it,
|
||||
// and so we don't pick the same token up when logging in later.
|
||||
token.Used = true;
|
||||
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
// Create a new room on LBP2/3/Vita
|
||||
if (token.GameVersion != GameVersion.LittleBigPlanet1) RoomHelper.CreateRoom(user, token.GameVersion, token.Platform);
|
||||
|
||||
return this.Ok
|
||||
(
|
||||
new LoginResult
|
||||
{
|
||||
AuthTicket = "MM_AUTH=" + token.UserToken,
|
||||
LbpEnvVer = ServerStatics.ServerName,
|
||||
}.Serialize()
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Matching;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
// [Produces("text/plain")]
|
||||
public class EnterLevelController : ControllerBase
|
||||
{
|
||||
private readonly Database database;
|
||||
|
||||
public EnterLevelController(Database database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
[HttpPost("play/user/{slotId}")]
|
||||
public async Task<IActionResult> PlayLevel(int slotId)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slotId);
|
||||
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;
|
||||
|
||||
IQueryable<VisitedLevel> visited = this.database.VisitedLevels.Where(s => s.SlotId == slotId && s.UserId == user.UserId);
|
||||
VisitedLevel? v;
|
||||
if (!visited.Any())
|
||||
{
|
||||
switch (gameVersion)
|
||||
{
|
||||
case GameVersion.LittleBigPlanet2:
|
||||
slot.PlaysLBP2Unique++;
|
||||
break;
|
||||
case GameVersion.LittleBigPlanet3:
|
||||
slot.PlaysLBP3Unique++;
|
||||
break;
|
||||
case GameVersion.LittleBigPlanetVita:
|
||||
slot.PlaysLBPVitaUnique++;
|
||||
break;
|
||||
default: return this.BadRequest();
|
||||
}
|
||||
|
||||
v = new VisitedLevel();
|
||||
v.SlotId = slotId;
|
||||
v.UserId = user.UserId;
|
||||
this.database.VisitedLevels.Add(v);
|
||||
}
|
||||
else
|
||||
{
|
||||
v = await visited.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
if (v == null) return this.NotFound();
|
||||
|
||||
switch (gameVersion)
|
||||
{
|
||||
case GameVersion.LittleBigPlanet2:
|
||||
slot.PlaysLBP2++;
|
||||
v.PlaysLBP2++;
|
||||
break;
|
||||
case GameVersion.LittleBigPlanet3:
|
||||
slot.PlaysLBP3++;
|
||||
v.PlaysLBP3++;
|
||||
break;
|
||||
case GameVersion.LittleBigPlanetVita:
|
||||
slot.PlaysLBPVita++;
|
||||
v.PlaysLBPVita++;
|
||||
break;
|
||||
case GameVersion.LittleBigPlanetPSP: throw new NotImplementedException();
|
||||
case GameVersion.Unknown:
|
||||
default:
|
||||
return this.BadRequest();
|
||||
}
|
||||
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
// Only used in LBP1
|
||||
[HttpGet("enterLevel/{id:int}")]
|
||||
public async Task<IActionResult> EnterLevel(int id)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
|
||||
if (slot == null) return this.NotFound();
|
||||
|
||||
IQueryable<VisitedLevel> visited = this.database.VisitedLevels.Where(s => s.SlotId == id && s.UserId == user.UserId);
|
||||
VisitedLevel? v;
|
||||
if (!visited.Any())
|
||||
{
|
||||
slot.PlaysLBP1Unique++;
|
||||
|
||||
v = new VisitedLevel();
|
||||
v.SlotId = id;
|
||||
v.UserId = user.UserId;
|
||||
this.database.VisitedLevels.Add(v);
|
||||
}
|
||||
else
|
||||
{
|
||||
v = await visited.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
if (v == null) return this.NotFound();
|
||||
|
||||
slot.PlaysLBP1++;
|
||||
v.PlaysLBP1++;
|
||||
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
#nullable enable
|
||||
using System.Text.Json;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Helpers.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Logging;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Match;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Matching;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
[Produces("text/xml")]
|
||||
public class MatchController : ControllerBase
|
||||
{
|
||||
private readonly Database database;
|
||||
|
||||
public MatchController(Database database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
[HttpPost("match")]
|
||||
[Produces("text/plain")]
|
||||
public async Task<IActionResult> Match()
|
||||
{
|
||||
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request);
|
||||
|
||||
if (userAndToken == null) return this.StatusCode(403, "");
|
||||
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
User user = userAndToken.Value.Item1;
|
||||
GameToken gameToken = userAndToken.Value.Item2;
|
||||
|
||||
#region Parse match data
|
||||
|
||||
// Example POST /match: [UpdateMyPlayerData,["Player":"FireGamer9872"]]
|
||||
|
||||
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
|
||||
|
||||
if (bodyString.Length == 0 || bodyString[0] != '[') return this.BadRequest();
|
||||
|
||||
Logger.LogInfo("Received match data: " + bodyString, LogArea.Match);
|
||||
|
||||
IMatchData? matchData;
|
||||
try
|
||||
{
|
||||
matchData = MatchHelper.Deserialize(bodyString);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
Logger.LogError("Exception while parsing matchData: ", LogArea.Match);
|
||||
Logger.LogError(e.ToDetailedException(), LogArea.Match);
|
||||
|
||||
return this.BadRequest();
|
||||
}
|
||||
|
||||
if (matchData == null)
|
||||
{
|
||||
Logger.LogError($"Could not parse match data: {nameof(matchData)} is null", LogArea.Match);
|
||||
return this.BadRequest();
|
||||
}
|
||||
|
||||
Logger.LogInfo($"Parsed match from {user.Username} (type: {matchData.GetType()})", LogArea.Match);
|
||||
|
||||
#endregion
|
||||
|
||||
await LastContactHelper.SetLastContact(user, gameToken.GameVersion, gameToken.Platform);
|
||||
|
||||
#region Process match data
|
||||
|
||||
if (matchData is UpdateMyPlayerData playerData)
|
||||
{
|
||||
MatchHelper.SetUserLocation(user.UserId, gameToken.UserLocation);
|
||||
Room? room = RoomHelper.FindRoomByUser(user, gameToken.GameVersion, gameToken.Platform, true);
|
||||
|
||||
if (playerData.RoomState != null)
|
||||
if (room != null && Equals(room.Host, user))
|
||||
room.State = (RoomState)playerData.RoomState;
|
||||
}
|
||||
|
||||
// Check how many people are online in release builds, disabled for debug for ..well debugging.
|
||||
#if DEBUG
|
||||
if (matchData is FindBestRoom diveInData)
|
||||
#else
|
||||
if (matchData is FindBestRoom diveInData && MatchHelper.UserLocations.Count > 1)
|
||||
#endif
|
||||
{
|
||||
FindBestRoomResponse? response = RoomHelper.FindBestRoom
|
||||
(user, gameToken.GameVersion, diveInData.RoomSlot, gameToken.Platform, gameToken.UserLocation);
|
||||
|
||||
if (response == null) return this.NotFound();
|
||||
|
||||
string serialized = JsonSerializer.Serialize(response, typeof(FindBestRoomResponse));
|
||||
foreach (Player player in response.Players) MatchHelper.AddUserRecentlyDivedIn(user.UserId, player.User.UserId);
|
||||
|
||||
return this.Ok($"[{{\"StatusCode\":200}},{serialized}]");
|
||||
}
|
||||
|
||||
if (matchData is CreateRoom createRoom && MatchHelper.UserLocations.Count >= 1)
|
||||
{
|
||||
List<User> users = new();
|
||||
foreach (string playerUsername in createRoom.Players)
|
||||
{
|
||||
User? player = await this.database.Users.FirstOrDefaultAsync(u => u.Username == playerUsername);
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
|
||||
if (player != null) users.Add(player);
|
||||
else return this.BadRequest();
|
||||
}
|
||||
|
||||
// Create a new one as requested
|
||||
RoomHelper.CreateRoom(users, gameToken.GameVersion, gameToken.Platform, createRoom.RoomSlot);
|
||||
}
|
||||
|
||||
if (matchData is UpdatePlayersInRoom updatePlayersInRoom)
|
||||
{
|
||||
Room? room = RoomHelper.Rooms.FirstOrDefault(r => r.Host == user);
|
||||
|
||||
if (room != null)
|
||||
{
|
||||
List<User> users = new();
|
||||
foreach (string playerUsername in updatePlayersInRoom.Players)
|
||||
{
|
||||
User? player = await this.database.Users.FirstOrDefaultAsync(u => u.Username == playerUsername);
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
|
||||
if (player != null) users.Add(player);
|
||||
else return this.BadRequest();
|
||||
}
|
||||
|
||||
room.Players = users;
|
||||
RoomHelper.CleanupRooms(null, room);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return this.Ok("[{\"StatusCode\":200}]");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Logging;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Settings;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
[Produces("text/plain")]
|
||||
public class MessageController : ControllerBase
|
||||
{
|
||||
private readonly Database database;
|
||||
|
||||
private const string license = @"
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.";
|
||||
|
||||
public MessageController(Database database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
[HttpGet("eula")]
|
||||
public async Task<IActionResult> Eula()
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
return this.Ok($"{license}\n{ServerConfiguration.Instance.EulaText}");
|
||||
}
|
||||
|
||||
[HttpGet("announce")]
|
||||
public async Task<IActionResult> Announce()
|
||||
{
|
||||
#if !DEBUG
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
#else
|
||||
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request);
|
||||
|
||||
if (userAndToken == null) return this.StatusCode(403, "");
|
||||
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
User user = userAndToken.Value.Item1;
|
||||
GameToken gameToken = userAndToken.Value.Item2;
|
||||
#endif
|
||||
|
||||
string announceText = ServerConfiguration.Instance.AnnounceText;
|
||||
|
||||
announceText = announceText.Replace("%user", user.Username);
|
||||
announceText = announceText.Replace("%id", user.UserId.ToString());
|
||||
|
||||
return this.Ok
|
||||
(
|
||||
announceText +
|
||||
#if DEBUG
|
||||
"\n\n---DEBUG INFO---\n" +
|
||||
$"user.UserId: {user.UserId}\n" +
|
||||
$"token.Approved: {gameToken.Approved}\n" +
|
||||
$"token.Used: {gameToken.Used}\n" +
|
||||
$"token.UserLocation: {gameToken.UserLocation}\n" +
|
||||
$"token.GameVersion: {gameToken.GameVersion}\n" +
|
||||
"---DEBUG INFO---" +
|
||||
#endif
|
||||
"\n"
|
||||
);
|
||||
}
|
||||
|
||||
[HttpGet("notification")]
|
||||
public IActionResult Notification() => this.Ok();
|
||||
/// <summary>
|
||||
/// Filters chat messages sent by a user.
|
||||
/// The reponse sent is the text that will appear in-game.
|
||||
/// </summary>
|
||||
[HttpPost("filter")]
|
||||
public async Task<IActionResult> Filter()
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
string response = await new StreamReader(this.Request.Body).ReadToEndAsync();
|
||||
|
||||
string scannedText = CensorHelper.ScanMessage(response);
|
||||
|
||||
Logger.LogInfo($"{user.Username}: {response} / {scannedText}", LogArea.Filter);
|
||||
|
||||
return this.Ok(scannedText);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
#nullable enable
|
||||
using System.Text.Json;
|
||||
using System.Xml.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Reports;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
[Produces("text/xml")]
|
||||
public class ReportController : ControllerBase
|
||||
{
|
||||
private readonly Database database;
|
||||
|
||||
public ReportController(Database database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
[HttpPost("grief")]
|
||||
public async Task<IActionResult> Report()
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
this.Request.Body.Position = 0;
|
||||
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
|
||||
|
||||
XmlSerializer serializer = new(typeof(GriefReport));
|
||||
GriefReport? report = (GriefReport?)serializer.Deserialize(new StringReader(bodyString));
|
||||
|
||||
if (report == null) return this.BadRequest();
|
||||
|
||||
SanitizationHelper.SanitizeStringsInClass(report);
|
||||
|
||||
report.Bounds = JsonSerializer.Serialize(report.XmlBounds.Rect, typeof(Rectangle));
|
||||
report.Players = JsonSerializer.Serialize(report.XmlPlayers, typeof(ReportPlayer[]));
|
||||
report.Timestamp = TimeHelper.UnixTimeMilliseconds();
|
||||
report.ReportingPlayerId = user.UserId;
|
||||
|
||||
this.database.Reports.Add(report);
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
#nullable enable
|
||||
using System.Xml.Serialization;
|
||||
using Discord;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Logging;
|
||||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Settings;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Resources;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
[Produces("text/xml")]
|
||||
public class PhotosController : ControllerBase
|
||||
{
|
||||
private readonly Database database;
|
||||
|
||||
public PhotosController(Database database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
[HttpPost("uploadPhoto")]
|
||||
public async Task<IActionResult> UploadPhoto()
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
if (user.PhotosByMe >= ServerConfiguration.Instance.UserGeneratedContentLimits.PhotosQuota) return this.BadRequest();
|
||||
|
||||
this.Request.Body.Position = 0;
|
||||
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
|
||||
|
||||
XmlSerializer serializer = new(typeof(Photo));
|
||||
Photo? photo = (Photo?)serializer.Deserialize(new StringReader(bodyString));
|
||||
if (photo == null) return this.BadRequest();
|
||||
|
||||
SanitizationHelper.SanitizeStringsInClass(photo);
|
||||
|
||||
foreach (Photo p in this.database.Photos.Where(p => p.CreatorId == user.UserId))
|
||||
{
|
||||
if (p.LargeHash == photo.LargeHash) return this.Ok(); // photo already uplaoded
|
||||
if (p.MediumHash == photo.MediumHash) return this.Ok();
|
||||
if (p.SmallHash == photo.SmallHash) return this.Ok();
|
||||
if (p.PlanHash == photo.PlanHash) return this.Ok();
|
||||
}
|
||||
|
||||
photo.CreatorId = user.UserId;
|
||||
photo.Creator = user;
|
||||
|
||||
if (photo.Subjects.Count > 4) return this.BadRequest();
|
||||
|
||||
if (photo.Timestamp > TimestampHelper.Timestamp) photo.Timestamp = TimestampHelper.Timestamp;
|
||||
|
||||
foreach (PhotoSubject subject in photo.Subjects)
|
||||
{
|
||||
subject.User = await this.database.Users.FirstOrDefaultAsync(u => u.Username == subject.Username);
|
||||
|
||||
if (subject.User == null) continue;
|
||||
|
||||
subject.UserId = subject.User.UserId;
|
||||
Logger.LogDebug($"Adding PhotoSubject (userid {subject.UserId}) to db", LogArea.Photos);
|
||||
|
||||
this.database.PhotoSubjects.Add(subject);
|
||||
}
|
||||
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
// Check for duplicate photo subjects
|
||||
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.LogDebug($"Adding PhotoSubjectCollection ({photo.PhotoSubjectCollection}) to photo", LogArea.Photos);
|
||||
|
||||
this.database.Photos.Add(photo);
|
||||
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
await WebhookHelper.SendWebhook
|
||||
(
|
||||
new EmbedBuilder
|
||||
{
|
||||
Title = "New photo uploaded!",
|
||||
Description = $"{user.Username} uploaded a new photo.",
|
||||
ImageUrl = $"{ServerConfiguration.Instance.ExternalUrl}/gameAssets/{photo.LargeHash}",
|
||||
Color = WebhookHelper.UnionColor,
|
||||
}
|
||||
);
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
[HttpGet("photos/user/{id:int}")]
|
||||
public async Task<IActionResult> SlotPhotos(int id)
|
||||
{
|
||||
List<Photo> photos = await this.database.Photos.Include(p => p.Creator).Take(10).ToListAsync();
|
||||
string response = photos.Aggregate(string.Empty, (s, photo) => s + photo.Serialize(id));
|
||||
return this.Ok(LbpSerializer.StringElement("photos", response));
|
||||
}
|
||||
|
||||
[HttpGet("photos/by")]
|
||||
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);
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
|
||||
if (userFromQuery == null) return this.NotFound();
|
||||
|
||||
List<Photo> photos = await this.database.Photos.Include
|
||||
(p => p.Creator)
|
||||
.Where(p => p.CreatorId == userFromQuery.UserId)
|
||||
.OrderByDescending(s => s.Timestamp)
|
||||
.Skip(pageStart - 1)
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.ToListAsync();
|
||||
string response = photos.Aggregate(string.Empty, (s, photo) => s + photo.Serialize(0));
|
||||
return this.Ok(LbpSerializer.StringElement("photos", response));
|
||||
}
|
||||
|
||||
[HttpGet("photos/with")]
|
||||
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);
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
|
||||
if (userFromQuery == null) return this.NotFound();
|
||||
|
||||
List<Photo> photos = new();
|
||||
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
|
||||
(s => s.Timestamp)
|
||||
.Skip(pageStart - 1)
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.Aggregate(string.Empty, (s, photo) => s + photo.Serialize(0));
|
||||
|
||||
return this.Ok(LbpSerializer.StringElement("photos", response));
|
||||
}
|
||||
|
||||
[HttpPost("deletePhoto/{id:int}")]
|
||||
public async Task<IActionResult> DeletePhoto(int id)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
Photo? photo = await this.database.Photos.FirstOrDefaultAsync(p => p.PhotoId == id);
|
||||
if (photo == null) return this.NotFound();
|
||||
if (photo.CreatorId != user.UserId) return this.StatusCode(401, "");
|
||||
|
||||
this.database.Photos.Remove(photo);
|
||||
await this.database.SaveChangesAsync();
|
||||
return this.Ok();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
#nullable enable
|
||||
using System.Xml.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Logging;
|
||||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Files;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using IOFile = System.IO.File;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Resources;
|
||||
|
||||
[ApiController]
|
||||
[Produces("text/xml")]
|
||||
[Route("LITTLEBIGPLANETPS3_XML")]
|
||||
public class ResourcesController : ControllerBase
|
||||
{
|
||||
private readonly Database database;
|
||||
|
||||
public ResourcesController(Database database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
[HttpPost("showModerated")]
|
||||
public IActionResult ShowModerated() => this.Ok(LbpSerializer.BlankElement("resources"));
|
||||
|
||||
[HttpPost("filterResources")]
|
||||
[HttpPost("showNotUploaded")]
|
||||
public async Task<IActionResult> FilterResources()
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
|
||||
|
||||
XmlSerializer serializer = new(typeof(ResourceList));
|
||||
ResourceList? resourceList = (ResourceList?)serializer.Deserialize(new StringReader(bodyString));
|
||||
|
||||
if (resourceList == null) return this.BadRequest();
|
||||
|
||||
string resources = resourceList.Resources.Where
|
||||
(s => !FileHelper.ResourceExists(s))
|
||||
.Aggregate("", (current, hash) => current + LbpSerializer.StringElement("resource", hash));
|
||||
|
||||
return this.Ok(LbpSerializer.StringElement("resources", resources));
|
||||
}
|
||||
|
||||
[HttpGet("r/{hash}")]
|
||||
public async Task<IActionResult> GetResource(string hash)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
string path = FileHelper.GetResourcePath(hash);
|
||||
|
||||
if (FileHelper.ResourceExists(hash)) return this.File(IOFile.OpenRead(path), "application/octet-stream");
|
||||
|
||||
return this.NotFound();
|
||||
}
|
||||
|
||||
// TODO: check if this is a valid hash
|
||||
[HttpPost("upload/{hash}/unattributed")]
|
||||
[HttpPost("upload/{hash}")]
|
||||
public async Task<IActionResult> UploadResource(string hash)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
string assetsDirectory = FileHelper.ResourcePath;
|
||||
string path = FileHelper.GetResourcePath(hash);
|
||||
|
||||
FileHelper.EnsureDirectoryCreated(assetsDirectory);
|
||||
// lbp treats code 409 as success and as an indicator that the file is already present
|
||||
if (FileHelper.ResourceExists(hash)) this.Conflict();
|
||||
|
||||
Logger.LogInfo($"Processing resource upload (hash: {hash})", LogArea.Resources);
|
||||
LbpFile file = new(await BinaryHelper.ReadFromPipeReader(this.Request.BodyReader));
|
||||
|
||||
if (!FileHelper.IsFileSafe(file))
|
||||
{
|
||||
Logger.LogWarn($"File is unsafe (hash: {hash}, type: {file.FileType})", LogArea.Resources);
|
||||
return this.Conflict();
|
||||
}
|
||||
|
||||
string calculatedHash = file.Hash;
|
||||
if (calculatedHash != hash)
|
||||
{
|
||||
Logger.LogWarn
|
||||
($"File hash does not match the uploaded file! (hash: {hash}, calculatedHash: {calculatedHash}, type: {file.FileType})", LogArea.Resources);
|
||||
return this.Conflict();
|
||||
}
|
||||
|
||||
Logger.LogSuccess($"File is OK! (hash: {hash}, type: {file.FileType})", LogArea.Resources);
|
||||
await IOFile.WriteAllBytesAsync(path, file.Data);
|
||||
return this.Ok();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Logging;
|
||||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Categories;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
[Produces("text/xml")]
|
||||
public class CollectionController : ControllerBase
|
||||
{
|
||||
private readonly Database database;
|
||||
|
||||
public CollectionController(Database database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
[HttpGet("user/{username}/playlists")]
|
||||
public IActionResult GetUserPlaylists(string username) => this.Ok();
|
||||
|
||||
[HttpGet("searches")]
|
||||
[HttpGet("genres")]
|
||||
public async Task<IActionResult> GenresAndSearches()
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
string categoriesSerialized = CollectionHelper.Categories.Aggregate
|
||||
(
|
||||
string.Empty,
|
||||
(current, category) =>
|
||||
{
|
||||
string serialized;
|
||||
|
||||
if (category is CategoryWithUser categoryWithUser) serialized = categoryWithUser.Serialize(this.database, user);
|
||||
else serialized = category.Serialize(this.database);
|
||||
|
||||
return current + serialized;
|
||||
}
|
||||
);
|
||||
|
||||
return this.Ok
|
||||
(
|
||||
LbpSerializer.TaggedStringElement
|
||||
(
|
||||
"categories",
|
||||
categoriesSerialized,
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
"hint", ""
|
||||
},
|
||||
{
|
||||
"hint_start", 1
|
||||
},
|
||||
{
|
||||
"total", CollectionHelper.Categories.Count
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
[HttpGet("searches/{endpointName}")]
|
||||
public async Task<IActionResult> GetCategorySlots(string endpointName, [FromQuery] int pageStart, [FromQuery] int pageSize)
|
||||
{
|
||||
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request);
|
||||
|
||||
if (userAndToken == null) return this.StatusCode(403, "");
|
||||
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
User user = userAndToken.Value.Item1;
|
||||
GameToken gameToken = userAndToken.Value.Item2;
|
||||
|
||||
Category? category = CollectionHelper.Categories.FirstOrDefault(c => c.Endpoint == endpointName);
|
||||
if (category == null) return this.NotFound();
|
||||
|
||||
Logger.LogDebug("Found category " + category, LogArea.Category);
|
||||
|
||||
List<Slot> slots;
|
||||
int totalSlots;
|
||||
|
||||
if (category is CategoryWithUser categoryWithUser)
|
||||
{
|
||||
slots = categoryWithUser.GetSlots(this.database, user, pageStart, pageSize).ToList();
|
||||
totalSlots = categoryWithUser.GetTotalSlots(this.database, user);
|
||||
}
|
||||
else
|
||||
{
|
||||
slots = category.GetSlots(this.database, pageStart, pageSize).ToList();
|
||||
totalSlots = category.GetTotalSlots(this.database);
|
||||
}
|
||||
|
||||
string slotsSerialized = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize(gameToken.GameVersion));
|
||||
|
||||
return this.Ok
|
||||
(
|
||||
LbpSerializer.TaggedStringElement
|
||||
(
|
||||
"results",
|
||||
slotsSerialized,
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
"total", totalSlots
|
||||
},
|
||||
{
|
||||
"hint_start", pageStart + pageSize
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/tags")]
|
||||
[Produces("text/plain")]
|
||||
public class LevelTagsController : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public IActionResult Get()
|
||||
{
|
||||
string[] tags = Enum.GetNames(typeof(LevelTags));
|
||||
|
||||
int i = 0;
|
||||
foreach (string tag in tags)
|
||||
{
|
||||
tags[i] = $"TAG_{tag.Replace("_", "-")}";
|
||||
i++;
|
||||
}
|
||||
|
||||
return this.Ok(string.Join(",", tags));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,212 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
[Produces("text/xml")]
|
||||
public class ListController : ControllerBase
|
||||
{
|
||||
private readonly Database database;
|
||||
public ListController(Database database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
#region Levels
|
||||
|
||||
#region Level Queue (lolcatftw)
|
||||
|
||||
[HttpGet("slots/lolcatftw/{username}")]
|
||||
public async Task<IActionResult> GetLevelQueue(string username, [FromQuery] int pageSize, [FromQuery] int pageStart)
|
||||
{
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
GameVersion gameVersion = token.GameVersion;
|
||||
|
||||
IEnumerable<QueuedLevel> queuedLevels = this.database.QueuedLevels.Include(q => q.User)
|
||||
.Include(q => q.Slot)
|
||||
.Include(q => q.Slot.Location)
|
||||
.Include(q => q.Slot.Creator)
|
||||
.Where(q => q.Slot.GameVersion <= gameVersion)
|
||||
.Where(q => q.User.Username == username)
|
||||
.Skip(pageStart - 1)
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.AsEnumerable();
|
||||
|
||||
string response = queuedLevels.Aggregate(string.Empty, (current, q) => current + q.Slot.Serialize(gameVersion));
|
||||
|
||||
return this.Ok
|
||||
(
|
||||
LbpSerializer.TaggedStringElement
|
||||
("slots", response, "total", this.database.QueuedLevels.Include(q => q.User).Count(q => q.User.Username == username))
|
||||
);
|
||||
}
|
||||
|
||||
[HttpPost("lolcatftw/add/user/{id:int}")]
|
||||
public async Task<IActionResult> AddQueuedLevel(int id)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
|
||||
if (slot == null) return this.NotFound();
|
||||
|
||||
await this.database.QueueLevel(user, slot);
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
[HttpPost("lolcatftw/remove/user/{id:int}")]
|
||||
public async Task<IActionResult> RemoveQueuedLevel(int id)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
|
||||
if (slot == null) return this.NotFound();
|
||||
|
||||
await this.database.UnqueueLevel(user, slot);
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
[HttpPost("lolcatftw/clear")]
|
||||
public async Task<IActionResult> ClearQueuedLevels()
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
this.database.QueuedLevels.RemoveRange(this.database.QueuedLevels.Where(q => q.UserId == user.UserId));
|
||||
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Hearted Levels
|
||||
|
||||
[HttpGet("favouriteSlots/{username}")]
|
||||
public async Task<IActionResult> GetFavouriteSlots(string username, [FromQuery] int pageSize, [FromQuery] int pageStart)
|
||||
{
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
GameVersion gameVersion = token.GameVersion;
|
||||
|
||||
IEnumerable<HeartedLevel> heartedLevels = this.database.HeartedLevels.Include(q => q.User)
|
||||
.Include(q => q.Slot)
|
||||
.Include(q => q.Slot.Location)
|
||||
.Include(q => q.Slot.Creator)
|
||||
.Where(q => q.Slot.GameVersion <= gameVersion)
|
||||
.Where(q => q.User.Username == username)
|
||||
.Skip(pageStart - 1)
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.AsEnumerable();
|
||||
|
||||
string response = heartedLevels.Aggregate(string.Empty, (current, q) => current + q.Slot.Serialize(gameVersion));
|
||||
|
||||
return this.Ok
|
||||
(
|
||||
LbpSerializer.TaggedStringElement
|
||||
("favouriteSlots", response, "total", this.database.HeartedLevels.Include(q => q.User).Count(q => q.User.Username == username))
|
||||
);
|
||||
}
|
||||
|
||||
[HttpPost("favourite/slot/user/{id:int}")]
|
||||
public async Task<IActionResult> AddFavouriteSlot(int id)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
|
||||
if (slot == null) return this.NotFound();
|
||||
|
||||
await this.database.HeartLevel(user, slot);
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
[HttpPost("unfavourite/slot/user/{id:int}")]
|
||||
public async Task<IActionResult> RemoveFavouriteSlot(int id)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
|
||||
if (slot == null) return this.NotFound();
|
||||
|
||||
await this.database.UnheartLevel(user, slot);
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion Levels
|
||||
|
||||
#region Users
|
||||
|
||||
[HttpGet("favouriteUsers/{username}")]
|
||||
public async Task<IActionResult> GetFavouriteUsers(string username, [FromQuery] int pageSize, [FromQuery] int pageStart)
|
||||
{
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
IEnumerable<HeartedProfile> heartedProfiles = this.database.HeartedProfiles.Include
|
||||
(q => q.User)
|
||||
.Include(q => q.HeartedUser)
|
||||
.Include(q => q.HeartedUser.Location)
|
||||
.Where(q => q.User.Username == username)
|
||||
.Skip(pageStart - 1)
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.AsEnumerable();
|
||||
|
||||
string response = heartedProfiles.Aggregate(string.Empty, (current, q) => current + q.HeartedUser.Serialize(token.GameVersion));
|
||||
|
||||
return this.Ok
|
||||
(
|
||||
LbpSerializer.TaggedStringElement
|
||||
("favouriteUsers", response, "total", this.database.HeartedProfiles.Include(q => q.User).Count(q => q.User.Username == username))
|
||||
);
|
||||
}
|
||||
|
||||
[HttpPost("favourite/user/{username}")]
|
||||
public async Task<IActionResult> AddFavouriteUser(string username)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
|
||||
if (heartedUser == null) return this.NotFound();
|
||||
|
||||
await this.database.HeartUser(user, heartedUser);
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
[HttpPost("unfavourite/user/{username}")]
|
||||
public async Task<IActionResult> RemoveFavouriteUser(string username)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
|
||||
if (heartedUser == null) return this.NotFound();
|
||||
|
||||
await this.database.UnheartUser(user, heartedUser);
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
|
@ -0,0 +1,236 @@
|
|||
#nullable enable
|
||||
using System.Xml.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Files;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Types.Profiles;
|
||||
using LBPUnion.ProjectLighthouse.Types.Settings;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
[Produces("text/xml")]
|
||||
public class PublishController : ControllerBase
|
||||
{
|
||||
private readonly Database database;
|
||||
|
||||
public PublishController(Database database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Endpoint the game uses to check what resources need to be uploaded and if the level can be uploaded
|
||||
/// </summary>
|
||||
[HttpPost("startPublish")]
|
||||
public async Task<IActionResult> StartPublish()
|
||||
{
|
||||
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request);
|
||||
|
||||
if (userAndToken == null) return this.StatusCode(403, "");
|
||||
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
User user = userAndToken.Value.Item1;
|
||||
GameToken gameToken = userAndToken.Value.Item2;
|
||||
|
||||
Slot? slot = await this.getSlotFromBody();
|
||||
if (slot == null) return this.BadRequest(); // if the level cant be parsed then it obviously cant be uploaded
|
||||
|
||||
if (string.IsNullOrEmpty(slot.RootLevel)) return this.BadRequest();
|
||||
|
||||
if (string.IsNullOrEmpty(slot.ResourceCollection)) slot.ResourceCollection = slot.RootLevel;
|
||||
|
||||
// Republish logic
|
||||
if (slot.SlotId != 0)
|
||||
{
|
||||
Slot? oldSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slot.SlotId);
|
||||
if (oldSlot == null) return this.NotFound();
|
||||
if (oldSlot.CreatorId != user.UserId) return this.BadRequest();
|
||||
}
|
||||
else if (user.GetUsedSlotsForGame(gameToken.GameVersion) > ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots)
|
||||
{
|
||||
return this.StatusCode(403, "");
|
||||
}
|
||||
|
||||
slot.ResourceCollection += "," + slot.IconHash; // tells LBP to upload icon after we process resources here
|
||||
|
||||
string resources = slot.Resources.Where
|
||||
(hash => !FileHelper.ResourceExists(hash))
|
||||
.Aggregate("", (current, hash) => current + LbpSerializer.StringElement("resource", hash));
|
||||
|
||||
return this.Ok(LbpSerializer.TaggedStringElement("slot", resources, "type", "user"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Endpoint actually used to publish a level
|
||||
/// </summary>
|
||||
[HttpPost("publish")]
|
||||
public async Task<IActionResult> Publish()
|
||||
{
|
||||
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request);
|
||||
|
||||
if (userAndToken == null) return this.StatusCode(403, "");
|
||||
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
User user = userAndToken.Value.Item1;
|
||||
GameToken gameToken = userAndToken.Value.Item2;
|
||||
Slot? slot = await this.getSlotFromBody();
|
||||
|
||||
if (slot == null) return this.BadRequest();
|
||||
|
||||
if (slot.Location == null) return this.BadRequest();
|
||||
|
||||
if (slot.Description.Length > 200) return this.BadRequest();
|
||||
|
||||
if (slot.Name.Length > 100) return this.BadRequest();
|
||||
|
||||
if (slot.Resources.Any(resource => !FileHelper.ResourceExists(resource)))
|
||||
{
|
||||
return this.BadRequest();
|
||||
}
|
||||
|
||||
LbpFile? rootLevel = LbpFile.FromHash(slot.RootLevel);
|
||||
|
||||
if (rootLevel == null) return this.BadRequest();
|
||||
|
||||
if (rootLevel.FileType != LbpFileType.Level) return this.BadRequest();
|
||||
|
||||
// Republish logic
|
||||
if (slot.SlotId != 0)
|
||||
{
|
||||
Slot? oldSlot = await this.database.Slots.Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == slot.SlotId);
|
||||
if (oldSlot == null) return this.NotFound();
|
||||
|
||||
if (oldSlot.Location == null) throw new ArgumentNullException();
|
||||
|
||||
if (oldSlot.CreatorId != user.UserId) return this.BadRequest();
|
||||
|
||||
oldSlot.Location.X = slot.Location.X;
|
||||
oldSlot.Location.Y = slot.Location.Y;
|
||||
|
||||
slot.CreatorId = oldSlot.CreatorId;
|
||||
slot.LocationId = oldSlot.LocationId;
|
||||
slot.SlotId = oldSlot.SlotId;
|
||||
|
||||
#region Set plays
|
||||
|
||||
slot.PlaysLBP1 = oldSlot.PlaysLBP1;
|
||||
slot.PlaysLBP1Complete = oldSlot.PlaysLBP1Complete;
|
||||
slot.PlaysLBP1Unique = oldSlot.PlaysLBP1Unique;
|
||||
|
||||
slot.PlaysLBP2 = oldSlot.PlaysLBP2;
|
||||
slot.PlaysLBP2Complete = oldSlot.PlaysLBP2Complete;
|
||||
slot.PlaysLBP2Unique = oldSlot.PlaysLBP2Unique;
|
||||
|
||||
slot.PlaysLBP3 = oldSlot.PlaysLBP3;
|
||||
slot.PlaysLBP3Complete = oldSlot.PlaysLBP3Complete;
|
||||
slot.PlaysLBP3Unique = oldSlot.PlaysLBP3Unique;
|
||||
|
||||
slot.PlaysLBPVita = oldSlot.PlaysLBPVita;
|
||||
slot.PlaysLBPVitaComplete = oldSlot.PlaysLBPVitaComplete;
|
||||
slot.PlaysLBPVitaUnique = oldSlot.PlaysLBPVitaUnique;
|
||||
|
||||
#endregion
|
||||
|
||||
slot.FirstUploaded = oldSlot.FirstUploaded;
|
||||
slot.LastUpdated = TimeHelper.UnixTimeMilliseconds();
|
||||
|
||||
slot.TeamPick = oldSlot.TeamPick;
|
||||
|
||||
// Only update a slot's gameVersion if the level was actually change
|
||||
if (oldSlot.RootLevel != slot.RootLevel)
|
||||
{
|
||||
slot.GameVersion = gameToken.GameVersion;
|
||||
}
|
||||
else
|
||||
{
|
||||
slot.GameVersion = oldSlot.GameVersion;
|
||||
}
|
||||
|
||||
if (slot.MinimumPlayers == 0 || slot.MaximumPlayers == 0)
|
||||
{
|
||||
slot.MinimumPlayers = 1;
|
||||
slot.MaximumPlayers = 4;
|
||||
}
|
||||
|
||||
this.database.Entry(oldSlot).CurrentValues.SetValues(slot);
|
||||
await this.database.SaveChangesAsync();
|
||||
return this.Ok(oldSlot.Serialize(gameToken.GameVersion));
|
||||
}
|
||||
|
||||
if (user.GetUsedSlotsForGame(gameToken.GameVersion) > ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots)
|
||||
{
|
||||
return this.StatusCode(403, "");
|
||||
}
|
||||
|
||||
//TODO: parse location in body
|
||||
Location l = new()
|
||||
{
|
||||
X = slot.Location.X,
|
||||
Y = slot.Location.Y,
|
||||
};
|
||||
this.database.Locations.Add(l);
|
||||
await this.database.SaveChangesAsync();
|
||||
slot.LocationId = l.Id;
|
||||
slot.CreatorId = user.UserId;
|
||||
slot.FirstUploaded = TimeHelper.UnixTimeMilliseconds();
|
||||
slot.LastUpdated = TimeHelper.UnixTimeMilliseconds();
|
||||
slot.GameVersion = gameToken.GameVersion;
|
||||
|
||||
if (slot.MinimumPlayers == 0 || slot.MaximumPlayers == 0)
|
||||
{
|
||||
slot.MinimumPlayers = 1;
|
||||
slot.MaximumPlayers = 4;
|
||||
}
|
||||
|
||||
this.database.Slots.Add(slot);
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
await WebhookHelper.SendWebhook
|
||||
(
|
||||
"New level published!",
|
||||
$"**{user.Username}** just published a new level: [**{slot.Name}**]({ServerConfiguration.Instance.ExternalUrl}/slot/{slot.SlotId})\n{slot.Description}"
|
||||
);
|
||||
|
||||
return this.Ok(slot.Serialize(gameToken.GameVersion));
|
||||
}
|
||||
|
||||
[HttpPost("unpublish/{id:int}")]
|
||||
public async Task<IActionResult> Unpublish(int id)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
Slot? slot = await this.database.Slots.Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == id);
|
||||
if (slot == null) return this.NotFound();
|
||||
|
||||
if (slot.Location == null) throw new ArgumentNullException();
|
||||
|
||||
if (slot.CreatorId != user.UserId) return this.StatusCode(403, "");
|
||||
|
||||
this.database.Locations.Remove(slot.Location);
|
||||
this.database.Slots.Remove(slot);
|
||||
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
private async Task<Slot?> getSlotFromBody()
|
||||
{
|
||||
this.Request.Body.Position = 0;
|
||||
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
|
||||
|
||||
XmlSerializer serializer = new(typeof(Slot));
|
||||
Slot? slot = (Slot?)serializer.Deserialize(new StringReader(bodyString));
|
||||
|
||||
SanitizationHelper.SanitizeStringsInClass(slot);
|
||||
|
||||
return slot;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,326 @@
|
|||
#nullable enable
|
||||
using System.Xml.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Helpers.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Types.Reviews;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
[Produces("text/xml")]
|
||||
public class ReviewController : ControllerBase
|
||||
{
|
||||
private readonly Database database;
|
||||
|
||||
public ReviewController(Database database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
// LBP1 rating
|
||||
[HttpPost("rate/user/{slotId}")]
|
||||
public async Task<IActionResult> Rate(int slotId, [FromQuery] int rating)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
Slot? slot = await this.database.Slots.Include(s => s.Creator).Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == slotId);
|
||||
if (slot == null) return this.StatusCode(403, "");
|
||||
|
||||
RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == slotId && r.UserId == user.UserId);
|
||||
if (ratedLevel == null)
|
||||
{
|
||||
ratedLevel = new RatedLevel
|
||||
{
|
||||
SlotId = slotId,
|
||||
UserId = user.UserId,
|
||||
Rating = 0,
|
||||
};
|
||||
this.database.RatedLevels.Add(ratedLevel);
|
||||
}
|
||||
|
||||
ratedLevel.RatingLBP1 = Math.Max(Math.Min(5, rating), 0);
|
||||
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
// LBP2 and beyond rating
|
||||
[HttpPost("dpadrate/user/{slotId:int}")]
|
||||
public async Task<IActionResult> DPadRate(int slotId, [FromQuery] int rating)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
Slot? slot = await this.database.Slots.Include(s => s.Creator).Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == slotId);
|
||||
if (slot == null) return this.StatusCode(403, "");
|
||||
|
||||
RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == slotId && r.UserId == user.UserId);
|
||||
if (ratedLevel == null)
|
||||
{
|
||||
ratedLevel = new RatedLevel
|
||||
{
|
||||
SlotId = slotId,
|
||||
UserId = user.UserId,
|
||||
RatingLBP1 = 0,
|
||||
};
|
||||
this.database.RatedLevels.Add(ratedLevel);
|
||||
}
|
||||
|
||||
ratedLevel.Rating = Math.Clamp(rating, -1, 1);
|
||||
|
||||
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == user.UserId);
|
||||
if (review != null) review.Thumb = ratedLevel.Rating;
|
||||
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
[HttpPost("postReview/user/{slotId:int}")]
|
||||
public async Task<IActionResult> PostReview(int slotId)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
Review? newReview = await this.getReviewFromBody();
|
||||
if (newReview == null) return this.BadRequest();
|
||||
|
||||
if (newReview.Text.Length > 100) return this.BadRequest();
|
||||
|
||||
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == user.UserId);
|
||||
|
||||
if (review == null)
|
||||
{
|
||||
review = new Review
|
||||
{
|
||||
SlotId = slotId,
|
||||
ReviewerId = user.UserId,
|
||||
DeletedBy = DeletedBy.None,
|
||||
ThumbsUp = 0,
|
||||
ThumbsDown = 0,
|
||||
};
|
||||
this.database.Reviews.Add(review);
|
||||
}
|
||||
review.Thumb = newReview.Thumb;
|
||||
review.LabelCollection = newReview.LabelCollection;
|
||||
review.Text = newReview.Text;
|
||||
review.Deleted = false;
|
||||
review.Timestamp = TimeHelper.UnixTimeMilliseconds();
|
||||
|
||||
// sometimes the game posts/updates a review rating without also calling dpadrate/user/etc (why??)
|
||||
RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == slotId && r.UserId == user.UserId);
|
||||
if (ratedLevel == null)
|
||||
{
|
||||
ratedLevel = new RatedLevel
|
||||
{
|
||||
SlotId = slotId,
|
||||
UserId = user.UserId,
|
||||
RatingLBP1 = 0,
|
||||
};
|
||||
this.database.RatedLevels.Add(ratedLevel);
|
||||
}
|
||||
|
||||
ratedLevel.Rating = newReview.Thumb;
|
||||
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
[HttpGet("reviewsFor/user/{slotId:int}")]
|
||||
public async Task<IActionResult> ReviewsFor(int slotId, [FromQuery] int pageStart = 1, [FromQuery] int pageSize = 10)
|
||||
{
|
||||
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request);
|
||||
|
||||
if (userAndToken == null) return this.StatusCode(403, "");
|
||||
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
User user = userAndToken.Value.Item1;
|
||||
GameToken gameToken = userAndToken.Value.Item2;
|
||||
|
||||
GameVersion gameVersion = gameToken.GameVersion;
|
||||
|
||||
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slotId);
|
||||
if (slot == null) return this.BadRequest();
|
||||
|
||||
IQueryable<Review?> reviews = this.database.Reviews.ByGameVersion(gameVersion, true)
|
||||
.Where(r => r.SlotId == slotId)
|
||||
.Include(r => r.Reviewer)
|
||||
.Include(r => r.Slot)
|
||||
.OrderByDescending(r => r.ThumbsUp - r.ThumbsDown)
|
||||
.ThenByDescending(r => r.Timestamp)
|
||||
.Skip(pageStart - 1)
|
||||
.Take(pageSize);
|
||||
|
||||
List<Review?> reviewList = reviews.ToList();
|
||||
|
||||
string inner = reviewList.Aggregate
|
||||
(
|
||||
string.Empty,
|
||||
(current, review) =>
|
||||
{
|
||||
if (review == null) return current;
|
||||
|
||||
RatedReview? yourThumb = this.database.RatedReviews.FirstOrDefault(r => r.ReviewId == review.ReviewId && r.UserId == user.UserId);
|
||||
return current + review.Serialize(null, yourThumb);
|
||||
}
|
||||
);
|
||||
string response = LbpSerializer.TaggedStringElement
|
||||
(
|
||||
"reviews",
|
||||
inner,
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
"hint_start", pageStart + pageSize
|
||||
},
|
||||
{
|
||||
"hint", reviewList.LastOrDefault()!.Timestamp // not sure
|
||||
},
|
||||
}
|
||||
);
|
||||
return this.Ok(response);
|
||||
}
|
||||
|
||||
[HttpGet("reviewsBy/{username}")]
|
||||
public async Task<IActionResult> ReviewsBy(string username, [FromQuery] int pageStart = 1, [FromQuery] int pageSize = 10)
|
||||
{
|
||||
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request);
|
||||
|
||||
if (userAndToken == null) return this.StatusCode(403, "");
|
||||
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
User user = userAndToken.Value.Item1;
|
||||
GameToken gameToken = userAndToken.Value.Item2;
|
||||
|
||||
GameVersion gameVersion = gameToken.GameVersion;
|
||||
|
||||
IEnumerable<Review?> reviews = this.database.Reviews.ByGameVersion(gameVersion, true)
|
||||
.Include(r => r.Reviewer)
|
||||
.Include(r => r.Slot)
|
||||
.Where(r => r.Reviewer!.Username == username)
|
||||
.OrderByDescending(r => r.Timestamp)
|
||||
.Skip(pageStart - 1)
|
||||
.Take(pageSize);
|
||||
|
||||
List<Review?> reviewList = reviews.ToList();
|
||||
|
||||
string inner = reviewList.Aggregate
|
||||
(
|
||||
string.Empty,
|
||||
(current, review) =>
|
||||
{
|
||||
if (review == null) return current;
|
||||
|
||||
RatedReview? ratedReview = this.database.RatedReviews.FirstOrDefault(r => r.ReviewId == review.ReviewId && r.UserId == user.UserId);
|
||||
return current + review.Serialize(null, ratedReview);
|
||||
}
|
||||
);
|
||||
|
||||
string response = LbpSerializer.TaggedStringElement
|
||||
(
|
||||
"reviews",
|
||||
inner,
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
"hint_start", pageStart
|
||||
},
|
||||
{
|
||||
"hint", reviewList.LastOrDefault()!.Timestamp // Seems to be the timestamp of oldest
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return this.Ok(response);
|
||||
}
|
||||
|
||||
[HttpPost("rateReview/user/{slotId:int}/{username}")]
|
||||
public async Task<IActionResult> RateReview(int slotId, string username, [FromQuery] int rating = 0)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
User? reviewer = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
|
||||
if (reviewer == null) return this.StatusCode(400, "");
|
||||
|
||||
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == reviewer.UserId);
|
||||
if (review == null) return this.StatusCode(400, "");
|
||||
|
||||
RatedReview? ratedReview = await this.database.RatedReviews.FirstOrDefaultAsync(r => r.ReviewId == review.ReviewId && r.UserId == user.UserId);
|
||||
if (ratedReview == null)
|
||||
{
|
||||
ratedReview = new RatedReview
|
||||
{
|
||||
ReviewId = review.ReviewId,
|
||||
UserId = user.UserId,
|
||||
Thumb = 0,
|
||||
};
|
||||
this.database.RatedReviews.Add(ratedReview);
|
||||
await this.database.SaveChangesAsync();
|
||||
}
|
||||
|
||||
int oldRating = ratedReview.Thumb;
|
||||
ratedReview.Thumb = Math.Clamp(rating, -1, 1);
|
||||
if (oldRating == ratedReview.Thumb) return this.Ok();
|
||||
|
||||
// if the user's rating changed then we recount the review's ratings to ensure accuracy
|
||||
List<RatedReview> reactions = await this.database.RatedReviews.Where(r => r.ReviewId == review.ReviewId).ToListAsync();
|
||||
int yay = 0;
|
||||
int boo = 0;
|
||||
foreach (RatedReview r in reactions)
|
||||
{
|
||||
switch (r.Thumb)
|
||||
{
|
||||
case -1:
|
||||
boo++;
|
||||
break;
|
||||
case 1:
|
||||
yay++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
review.ThumbsDown = boo;
|
||||
review.ThumbsUp = yay;
|
||||
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
[HttpPost("deleteReview/user/{slotId:int}/{username}")]
|
||||
public async Task<IActionResult> DeleteReview(int slotId, string username)
|
||||
{
|
||||
User? reviewer = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
|
||||
if (reviewer == null) return this.StatusCode(403, "");
|
||||
|
||||
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == reviewer.UserId);
|
||||
if (review == null) return this.StatusCode(403, "");
|
||||
|
||||
review.Deleted = true;
|
||||
review.DeletedBy = DeletedBy.LevelAuthor;
|
||||
|
||||
await this.database.SaveChangesAsync();
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
private async Task<Review?> getReviewFromBody()
|
||||
{
|
||||
this.Request.Body.Position = 0;
|
||||
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
|
||||
|
||||
XmlSerializer serializer = new(typeof(Review));
|
||||
Review? review = (Review?)serializer.Deserialize(new StringReader(bodyString));
|
||||
SanitizationHelper.SanitizeStringsInClass(review);
|
||||
return review;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
#nullable enable
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Xml.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
[Produces("text/xml")]
|
||||
public class ScoreController : ControllerBase
|
||||
{
|
||||
private readonly Database database;
|
||||
|
||||
public ScoreController(Database database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
[HttpPost("scoreboard/user/{id:int}")]
|
||||
public async Task<IActionResult> SubmitScore(int id, [FromQuery] bool lbp1 = false, [FromQuery] bool lbp2 = false, [FromQuery] bool lbp3 = false)
|
||||
{
|
||||
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request);
|
||||
|
||||
if (userAndToken == null) return this.StatusCode(403, "");
|
||||
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
User user = userAndToken.Value.Item1;
|
||||
GameToken gameToken = userAndToken.Value.Item2;
|
||||
|
||||
this.Request.Body.Position = 0;
|
||||
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
|
||||
|
||||
XmlSerializer serializer = new(typeof(Score));
|
||||
Score? score = (Score?)serializer.Deserialize(new StringReader(bodyString));
|
||||
if (score == null) return this.BadRequest();
|
||||
|
||||
SanitizationHelper.SanitizeStringsInClass(score);
|
||||
|
||||
score.SlotId = id;
|
||||
|
||||
Slot? slot = this.database.Slots.FirstOrDefault(s => s.SlotId == score.SlotId);
|
||||
if (slot == null) return this.BadRequest();
|
||||
|
||||
switch (gameToken.GameVersion)
|
||||
{
|
||||
case GameVersion.LittleBigPlanet1:
|
||||
slot.PlaysLBP1Complete++;
|
||||
break;
|
||||
case GameVersion.LittleBigPlanet2:
|
||||
slot.PlaysLBP2Complete++;
|
||||
break;
|
||||
case GameVersion.LittleBigPlanet3:
|
||||
slot.PlaysLBP3Complete++;
|
||||
break;
|
||||
case GameVersion.LittleBigPlanetVita:
|
||||
slot.PlaysLBPVitaComplete++;
|
||||
break;
|
||||
}
|
||||
|
||||
IQueryable<Score> existingScore = this.database.Scores.Where(s => s.SlotId == score.SlotId)
|
||||
.Where(s => s.PlayerIdCollection == score.PlayerIdCollection)
|
||||
.Where(s => s.Type == score.Type);
|
||||
|
||||
if (existingScore.Any())
|
||||
{
|
||||
Score first = existingScore.First(s => s.SlotId == score.SlotId);
|
||||
score.ScoreId = first.ScoreId;
|
||||
score.Points = Math.Max(first.Points, score.Points);
|
||||
this.database.Entry(first).CurrentValues.SetValues(score);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.database.Scores.Add(score);
|
||||
}
|
||||
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
string myRanking = this.getScores(score.SlotId, score.Type, user, -1, 5, "scoreboardSegment");
|
||||
|
||||
return this.Ok(myRanking);
|
||||
}
|
||||
|
||||
[HttpGet("friendscores/user/{slotId:int}/{type:int}")]
|
||||
public IActionResult FriendScores(int slotId, int type)
|
||||
//=> await TopScores(slotId, type);
|
||||
=> this.Ok(LbpSerializer.BlankElement("scores"));
|
||||
|
||||
[HttpGet("topscores/user/{slotId:int}/{type:int}")]
|
||||
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
|
||||
public async Task<IActionResult> TopScores(int slotId, int type, [FromQuery] int pageStart = -1, [FromQuery] int pageSize = 5)
|
||||
{
|
||||
// Get username
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
return this.Ok(this.getScores(slotId, type, user, pageStart, pageSize));
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
|
||||
private string getScores
|
||||
(
|
||||
int slotId,
|
||||
int type,
|
||||
User user,
|
||||
int pageStart = -1,
|
||||
int pageSize = 5,
|
||||
string rootName = "scores"
|
||||
)
|
||||
{
|
||||
// This is hella ugly but it technically assigns the proper rank to a score
|
||||
// var needed for Anonymous type returned from SELECT
|
||||
var rankedScores = this.database.Scores.Where(s => s.SlotId == slotId && s.Type == type)
|
||||
.OrderByDescending(s => s.Points)
|
||||
.ToList()
|
||||
.Select
|
||||
(
|
||||
(s, rank) => new
|
||||
{
|
||||
Score = s,
|
||||
Rank = rank + 1,
|
||||
}
|
||||
);
|
||||
|
||||
// Find your score, since even if you aren't in the top list your score is pinned
|
||||
var myScore = rankedScores.Where(rs => rs.Score.PlayerIdCollection.Contains(user.Username)).OrderByDescending(rs => rs.Score.Points).FirstOrDefault();
|
||||
|
||||
// Paginated viewing: if not requesting pageStart, get results around user
|
||||
var pagedScores = rankedScores.Skip(pageStart != -1 || myScore == null ? pageStart - 1 : myScore.Rank - 3).Take(Math.Min(pageSize, 30));
|
||||
|
||||
string serializedScores = pagedScores.Aggregate
|
||||
(
|
||||
string.Empty,
|
||||
(current, rs) =>
|
||||
{
|
||||
rs.Score.Rank = rs.Rank;
|
||||
return current + rs.Score.Serialize();
|
||||
}
|
||||
);
|
||||
|
||||
string res;
|
||||
if (myScore == null) res = LbpSerializer.StringElement(rootName, serializedScores);
|
||||
else
|
||||
res = LbpSerializer.TaggedStringElement
|
||||
(
|
||||
rootName,
|
||||
serializedScores,
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
"yourScore", myScore.Score.Points
|
||||
},
|
||||
{
|
||||
"yourRank", myScore.Rank
|
||||
}, //This is the numerator of your position globally in the side menu.
|
||||
{
|
||||
"totalNumScores", rankedScores.Count()
|
||||
}, // This is the denominator of your position globally in the side menu.
|
||||
}
|
||||
);
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
[Produces("text/xml")]
|
||||
public class SearchController : ControllerBase
|
||||
{
|
||||
private readonly Database database;
|
||||
public SearchController(Database database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
[HttpGet("slots/search")]
|
||||
public async Task<IActionResult> SearchSlots([FromQuery] string query, [FromQuery] int pageSize, [FromQuery] int pageStart)
|
||||
{
|
||||
GameToken? gameToken = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (gameToken == null) return this.StatusCode(403, "");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(query)) return this.BadRequest();
|
||||
|
||||
query = query.ToLower();
|
||||
|
||||
string[] keywords = query.Split(" ");
|
||||
|
||||
IQueryable<Slot> dbQuery = this.database.Slots.Include
|
||||
(s => s.Creator)
|
||||
.Include(s => s.Location)
|
||||
.OrderBy(s => !s.TeamPick)
|
||||
.ThenByDescending(s => s.FirstUploaded)
|
||||
.Where(s => s.SlotId >= 0); // dumb query to conv into IQueryable
|
||||
|
||||
// ReSharper disable once LoopCanBeConvertedToQuery
|
||||
foreach (string keyword in keywords)
|
||||
dbQuery = dbQuery.Where
|
||||
(
|
||||
s => s.Name.ToLower().Contains(keyword) ||
|
||||
s.Description.ToLower().Contains(keyword) ||
|
||||
s.Creator!.Username.ToLower().Contains(keyword) ||
|
||||
s.SlotId.ToString().Equals(keyword)
|
||||
);
|
||||
|
||||
List<Slot> slots = await dbQuery.Skip(pageStart - 1).Take(Math.Min(pageSize, 30)).ToListAsync();
|
||||
|
||||
string response = slots.Aggregate("", (current, slot) => current + slot.Serialize(gameToken.GameVersion));
|
||||
|
||||
return this.Ok(LbpSerializer.TaggedStringElement("slots", response, "total", dbQuery.Count()));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,402 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Helpers.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Types.Reviews;
|
||||
using LBPUnion.ProjectLighthouse.Types.Settings;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
[Produces("text/xml")]
|
||||
public class SlotsController : ControllerBase
|
||||
{
|
||||
private readonly Database database;
|
||||
public SlotsController(Database database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
[HttpGet("slots/by")]
|
||||
public async Task<IActionResult> SlotsBy([FromQuery] string u, [FromQuery] int pageStart, [FromQuery] int pageSize)
|
||||
{
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
GameVersion gameVersion = token.GameVersion;
|
||||
|
||||
User? user = await this.database.Users.FirstOrDefaultAsync(dbUser => dbUser.Username == u);
|
||||
if (user == null) return this.NotFound();
|
||||
|
||||
string response = Enumerable.Aggregate
|
||||
(
|
||||
this.database.Slots.ByGameVersion(gameVersion, token.UserId == user.UserId, true)
|
||||
.Where(s => s.Creator!.Username == user.Username)
|
||||
.Skip(pageStart - 1)
|
||||
.Take(Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots)),
|
||||
string.Empty,
|
||||
(current, slot) => current + slot.Serialize(token.GameVersion)
|
||||
);
|
||||
|
||||
return this.Ok
|
||||
(
|
||||
LbpSerializer.TaggedStringElement
|
||||
(
|
||||
"slots",
|
||||
response,
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
"hint_start", pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots)
|
||||
},
|
||||
{
|
||||
"total", user.UsedSlots
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
[HttpGet("s/user/{id:int}")]
|
||||
public async Task<IActionResult> SUser(int id)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
GameVersion gameVersion = token.GameVersion;
|
||||
|
||||
Slot? slot = await this.database.Slots.ByGameVersion(gameVersion, true, true).FirstOrDefaultAsync(s => s.SlotId == id);
|
||||
|
||||
if (slot == null) return this.NotFound();
|
||||
|
||||
RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == id && r.UserId == user.UserId);
|
||||
VisitedLevel? visitedLevel = await this.database.VisitedLevels.FirstOrDefaultAsync(r => r.SlotId == id && r.UserId == user.UserId);
|
||||
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == id && r.ReviewerId == user.UserId);
|
||||
return this.Ok(slot.Serialize(gameVersion, ratedLevel, visitedLevel, review));
|
||||
}
|
||||
|
||||
[HttpGet("slots/cool")]
|
||||
public async Task<IActionResult> Lbp1CoolSlots([FromQuery] int page)
|
||||
{
|
||||
const int pageSize = 30;
|
||||
return await this.CoolSlots((page - 1) * pageSize, pageSize);
|
||||
}
|
||||
|
||||
[HttpGet("slots/lbp2cool")]
|
||||
public async Task<IActionResult> CoolSlots
|
||||
(
|
||||
[FromQuery] int pageStart,
|
||||
[FromQuery] int pageSize,
|
||||
[FromQuery] string? gameFilterType = null,
|
||||
[FromQuery] int? players = null,
|
||||
[FromQuery] bool? move = null,
|
||||
[FromQuery] int? page = null
|
||||
)
|
||||
{
|
||||
int _pageStart = pageStart;
|
||||
if (page != null) _pageStart = (int)page * 30;
|
||||
// bit of a better placeholder until we can track average user interaction with /stream endpoint
|
||||
return await this.ThumbsSlots(_pageStart, Math.Min(pageSize, 30), gameFilterType, players, move, "thisWeek");
|
||||
}
|
||||
|
||||
[HttpGet("slots")]
|
||||
public async Task<IActionResult> NewestSlots([FromQuery] int pageStart, [FromQuery] int pageSize)
|
||||
{
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
GameVersion gameVersion = token.GameVersion;
|
||||
|
||||
IQueryable<Slot> slots = this.database.Slots.ByGameVersion(gameVersion, false, true)
|
||||
.OrderByDescending(s => s.FirstUploaded)
|
||||
.Skip(pageStart - 1)
|
||||
.Take(Math.Min(pageSize, 30));
|
||||
|
||||
string response = Enumerable.Aggregate(slots, string.Empty, (current, slot) => current + slot.Serialize(gameVersion));
|
||||
|
||||
return this.Ok
|
||||
(
|
||||
LbpSerializer.TaggedStringElement
|
||||
(
|
||||
"slots",
|
||||
response,
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
"hint_start", pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots)
|
||||
},
|
||||
{
|
||||
"total", await StatisticsHelper.SlotCount()
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
[HttpGet("slots/mmpicks")]
|
||||
public async Task<IActionResult> TeamPickedSlots([FromQuery] int pageStart, [FromQuery] int pageSize)
|
||||
{
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
GameVersion gameVersion = token.GameVersion;
|
||||
|
||||
IQueryable<Slot> slots = this.database.Slots.ByGameVersion(gameVersion, false, true)
|
||||
.Where(s => s.TeamPick)
|
||||
.OrderByDescending(s => s.LastUpdated)
|
||||
.Skip(pageStart - 1)
|
||||
.Take(Math.Min(pageSize, 30));
|
||||
string response = Enumerable.Aggregate(slots, string.Empty, (current, slot) => current + slot.Serialize(gameVersion));
|
||||
|
||||
return this.Ok
|
||||
(
|
||||
LbpSerializer.TaggedStringElement
|
||||
(
|
||||
"slots",
|
||||
response,
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
"hint_start", pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots)
|
||||
},
|
||||
{
|
||||
"total", await StatisticsHelper.TeamPickCount()
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
[HttpGet("slots/lbp2luckydip")]
|
||||
public async Task<IActionResult> LuckyDipSlots([FromQuery] int pageStart, [FromQuery] int pageSize, [FromQuery] int seed)
|
||||
{
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
GameVersion gameVersion = token.GameVersion;
|
||||
|
||||
IEnumerable<Slot> slots = this.database.Slots.ByGameVersion(gameVersion, false, true).OrderBy(_ => EF.Functions.Random()).Take(Math.Min(pageSize, 30));
|
||||
|
||||
string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize(gameVersion));
|
||||
|
||||
return this.Ok
|
||||
(
|
||||
LbpSerializer.TaggedStringElement
|
||||
(
|
||||
"slots",
|
||||
response,
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
"hint_start", pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots)
|
||||
},
|
||||
{
|
||||
"total", await StatisticsHelper.SlotCount()
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
[HttpGet("slots/thumbs")]
|
||||
public async Task<IActionResult> ThumbsSlots
|
||||
(
|
||||
[FromQuery] int pageStart,
|
||||
[FromQuery] int pageSize,
|
||||
[FromQuery] string? gameFilterType = null,
|
||||
[FromQuery] int? players = null,
|
||||
[FromQuery] bool? move = null,
|
||||
[FromQuery] string? dateFilterType = null
|
||||
)
|
||||
{
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
Random rand = new();
|
||||
|
||||
IEnumerable<Slot> slots = this.filterByRequest(gameFilterType, dateFilterType, token.GameVersion)
|
||||
.AsEnumerable()
|
||||
.OrderByDescending(s => s.Thumbsup)
|
||||
.ThenBy(_ => rand.Next())
|
||||
.Skip(pageStart - 1)
|
||||
.Take(Math.Min(pageSize, 30));
|
||||
|
||||
string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize(token.GameVersion));
|
||||
|
||||
return this.Ok
|
||||
(
|
||||
LbpSerializer.TaggedStringElement
|
||||
(
|
||||
"slots",
|
||||
response,
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
"hint_start", pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots)
|
||||
},
|
||||
{
|
||||
"total", await StatisticsHelper.SlotCount()
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
[HttpGet("slots/mostUniquePlays")]
|
||||
public async Task<IActionResult> MostUniquePlaysSlots
|
||||
(
|
||||
[FromQuery] int pageStart,
|
||||
[FromQuery] int pageSize,
|
||||
[FromQuery] string? gameFilterType = null,
|
||||
[FromQuery] int? players = null,
|
||||
[FromQuery] bool? move = null,
|
||||
[FromQuery] string? dateFilterType = null
|
||||
)
|
||||
{
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
Random rand = new();
|
||||
|
||||
IEnumerable<Slot> slots = this.filterByRequest(gameFilterType, dateFilterType, token.GameVersion)
|
||||
.AsEnumerable()
|
||||
.OrderByDescending
|
||||
(
|
||||
// probably not the best way to do this?
|
||||
s =>
|
||||
{
|
||||
return this.getGameFilter(gameFilterType, token.GameVersion) switch
|
||||
{
|
||||
GameVersion.LittleBigPlanet1 => s.PlaysLBP1Unique,
|
||||
GameVersion.LittleBigPlanet2 => s.PlaysLBP2Unique,
|
||||
GameVersion.LittleBigPlanet3 => s.PlaysLBP3Unique,
|
||||
GameVersion.LittleBigPlanetVita => s.PlaysLBPVitaUnique,
|
||||
_ => s.PlaysUnique,
|
||||
};
|
||||
}
|
||||
)
|
||||
.ThenBy(_ => rand.Next())
|
||||
.Skip(pageStart - 1)
|
||||
.Take(Math.Min(pageSize, 30));
|
||||
|
||||
string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize(token.GameVersion));
|
||||
|
||||
return this.Ok
|
||||
(
|
||||
LbpSerializer.TaggedStringElement
|
||||
(
|
||||
"slots",
|
||||
response,
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
"hint_start", pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots)
|
||||
},
|
||||
{
|
||||
"total", await StatisticsHelper.SlotCount()
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
[HttpGet("slots/mostHearted")]
|
||||
public async Task<IActionResult> MostHeartedSlots
|
||||
(
|
||||
[FromQuery] int pageStart,
|
||||
[FromQuery] int pageSize,
|
||||
[FromQuery] string? gameFilterType = null,
|
||||
[FromQuery] int? players = null,
|
||||
[FromQuery] bool? move = null,
|
||||
[FromQuery] string? dateFilterType = null
|
||||
)
|
||||
{
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
Random rand = new();
|
||||
|
||||
IEnumerable<Slot> slots = this.filterByRequest(gameFilterType, dateFilterType, token.GameVersion)
|
||||
.AsEnumerable()
|
||||
.OrderByDescending(s => s.Hearts)
|
||||
.ThenBy(_ => rand.Next())
|
||||
.Skip(pageStart - 1)
|
||||
.Take(Math.Min(pageSize, 30));
|
||||
|
||||
string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize(token.GameVersion));
|
||||
|
||||
return this.Ok
|
||||
(
|
||||
LbpSerializer.TaggedStringElement
|
||||
(
|
||||
"slots",
|
||||
response,
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
"hint_start", pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots)
|
||||
},
|
||||
{
|
||||
"total", await StatisticsHelper.SlotCount()
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private GameVersion getGameFilter(string? gameFilterType, GameVersion version)
|
||||
{
|
||||
if (version == GameVersion.LittleBigPlanetVita) return GameVersion.LittleBigPlanetVita;
|
||||
if (version == GameVersion.LittleBigPlanetPSP) return GameVersion.LittleBigPlanetPSP;
|
||||
|
||||
return gameFilterType switch
|
||||
{
|
||||
"lbp1" => GameVersion.LittleBigPlanet1,
|
||||
"lbp2" => GameVersion.LittleBigPlanet2,
|
||||
"lbp3" => GameVersion.LittleBigPlanet3,
|
||||
"both" => GameVersion.LittleBigPlanet2, // LBP2 default option
|
||||
null => GameVersion.LittleBigPlanet1,
|
||||
_ => GameVersion.Unknown,
|
||||
};
|
||||
}
|
||||
|
||||
private IQueryable<Slot> filterByRequest(string? gameFilterType, string? dateFilterType, GameVersion version)
|
||||
{
|
||||
if (version == GameVersion.LittleBigPlanetVita || version == GameVersion.LittleBigPlanetPSP || version == GameVersion.Unknown)
|
||||
{
|
||||
return this.database.Slots.ByGameVersion(version, false, true);
|
||||
}
|
||||
|
||||
string _dateFilterType = dateFilterType ?? "";
|
||||
|
||||
long oldestTime = _dateFilterType switch
|
||||
{
|
||||
"thisWeek" => DateTimeOffset.Now.AddDays(-7).ToUnixTimeMilliseconds(),
|
||||
"thisMonth" => DateTimeOffset.Now.AddDays(-31).ToUnixTimeMilliseconds(),
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
GameVersion gameVersion = this.getGameFilter(gameFilterType, version);
|
||||
|
||||
IQueryable<Slot> whereSlots;
|
||||
|
||||
// ReSharper disable once ConvertIfStatementToConditionalTernaryExpression
|
||||
if (gameFilterType == "both")
|
||||
// Get game versions less than the current version
|
||||
// Needs support for LBP3 ("both" = LBP1+2)
|
||||
whereSlots = this.database.Slots.Where(s => s.GameVersion <= gameVersion && s.FirstUploaded >= oldestTime);
|
||||
else
|
||||
// Get game versions exactly equal to gamefiltertype
|
||||
whereSlots = this.database.Slots.Where(s => s.GameVersion == gameVersion && s.FirstUploaded >= oldestTime);
|
||||
|
||||
return whereSlots.Include(s => s.Creator).Include(s => s.Location);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
[Produces("text/plain")]
|
||||
public class StatisticsController : ControllerBase
|
||||
{
|
||||
[HttpGet("playersInPodCount")]
|
||||
[HttpGet("totalPlayerCount")]
|
||||
public async Task<IActionResult> TotalPlayerCount() => this.Ok((await StatisticsHelper.RecentMatches()).ToString()!);
|
||||
|
||||
[HttpGet("planetStats")]
|
||||
public async Task<IActionResult> PlanetStats()
|
||||
{
|
||||
int totalSlotCount = await StatisticsHelper.SlotCount();
|
||||
int mmPicksCount = await StatisticsHelper.TeamPickCount();
|
||||
|
||||
return this.Ok
|
||||
(
|
||||
LbpSerializer.StringElement
|
||||
("planetStats", LbpSerializer.StringElement("totalSlotCount", totalSlotCount) + LbpSerializer.StringElement("mmPicksCount", mmPicksCount))
|
||||
);
|
||||
}
|
||||
|
||||
[HttpGet("planetStats/totalLevelCount")]
|
||||
public async Task<IActionResult> TotalLevelCount() => this.Ok((await StatisticsHelper.SlotCount()).ToString());
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
[Produces("text/xml")]
|
||||
public class StoreController : Controller
|
||||
{
|
||||
[HttpGet("promotions")]
|
||||
public IActionResult Promotions() => this.Ok();
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
#nullable enable
|
||||
using System.Text.Json;
|
||||
using System.Xml.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Profiles;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
[Produces("text/xml")]
|
||||
public class UserController : ControllerBase
|
||||
{
|
||||
private readonly Database database;
|
||||
|
||||
public UserController(Database database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
private async Task<string?> getSerializedUser(string username, GameVersion gameVersion = GameVersion.LittleBigPlanet1)
|
||||
{
|
||||
User? user = await this.database.Users.Include(u => u.Location).FirstOrDefaultAsync(u => u.Username == username);
|
||||
return user?.Serialize(gameVersion);
|
||||
}
|
||||
|
||||
private async Task<string?> getSerializedUserPicture(string username)
|
||||
{
|
||||
// use an anonymous type to only fetch certain columns
|
||||
var partialUser = await this.database.Users.Where(u => u.Username == username)
|
||||
.Select
|
||||
(
|
||||
u => new
|
||||
{
|
||||
u.Username,
|
||||
u.IconHash,
|
||||
}
|
||||
)
|
||||
.FirstOrDefaultAsync();
|
||||
if (partialUser == null) return null;
|
||||
|
||||
string user = LbpSerializer.TaggedStringElement("npHandle", partialUser.Username, "icon", partialUser.IconHash);
|
||||
return LbpSerializer.TaggedStringElement("user", user, "type", "user");
|
||||
}
|
||||
|
||||
[HttpGet("user/{username}")]
|
||||
public async Task<IActionResult> GetUser(string username)
|
||||
{
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
string? user = await this.getSerializedUser(username, token.GameVersion);
|
||||
if (user == null) return this.NotFound();
|
||||
|
||||
return this.Ok(user);
|
||||
}
|
||||
|
||||
[HttpGet("users")]
|
||||
public async Task<IActionResult> GetUserAlt([FromQuery] string[] u)
|
||||
{
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
List<string?> serializedUsers = new();
|
||||
foreach (string userId in u) serializedUsers.Add(await this.getSerializedUserPicture(userId));
|
||||
|
||||
string serialized = serializedUsers.Aggregate(string.Empty, (current, user) => user == null ? current : current + user);
|
||||
|
||||
return this.Ok(LbpSerializer.StringElement("users", serialized));
|
||||
}
|
||||
|
||||
[HttpPost("updateUser")]
|
||||
public async Task<IActionResult> UpdateUser()
|
||||
{
|
||||
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request);
|
||||
|
||||
if (userAndToken == null) return this.StatusCode(403, "");
|
||||
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
User user = userAndToken.Value.Item1;
|
||||
GameToken gameToken = userAndToken.Value.Item2;
|
||||
|
||||
this.Request.Body.Position = 0;
|
||||
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
|
||||
// xml hack so we can use one class to deserialize different root names
|
||||
string rootElement = bodyString.Contains("updateUser") ? "updateUser" : "user";
|
||||
XmlSerializer serializer = new(typeof(UserUpdate), new XmlRootAttribute(rootElement));
|
||||
UserUpdate? update = (UserUpdate?)serializer.Deserialize(new StringReader(bodyString));
|
||||
|
||||
if (update == null) return this.BadRequest();
|
||||
|
||||
SanitizationHelper.SanitizeStringsInClass(update);
|
||||
|
||||
if (update.Biography != null)
|
||||
{
|
||||
if (update.Biography.Length > 100) return this.BadRequest();
|
||||
|
||||
user.Biography = update.Biography;
|
||||
}
|
||||
|
||||
foreach (string? resource in new[]
|
||||
{
|
||||
update.IconHash, update.YayHash, update.MehHash, update.BooHash, update.PlanetHash,
|
||||
})
|
||||
{
|
||||
if (resource != null && !FileHelper.ResourceExists(resource)) return this.BadRequest();
|
||||
}
|
||||
|
||||
if (update.IconHash != null) user.IconHash = update.IconHash;
|
||||
|
||||
if (update.YayHash != null) user.YayHash = update.YayHash;
|
||||
|
||||
if (update.MehHash != null) user.MehHash = update.MehHash;
|
||||
|
||||
if (update.BooHash != null) user.BooHash = update.BooHash;
|
||||
|
||||
if (update.PlanetHash != null)
|
||||
{
|
||||
switch (gameToken.GameVersion)
|
||||
{
|
||||
case GameVersion.LittleBigPlanet2: // LBP2 planets will apply to LBP3
|
||||
{
|
||||
user.PlanetHashLBP2 = update.PlanetHash;
|
||||
user.PlanetHashLBP3 = update.PlanetHash;
|
||||
break;
|
||||
}
|
||||
case GameVersion.LittleBigPlanet3: // LBP3 and vita can only apply to their own games, only set 1 here
|
||||
{
|
||||
user.PlanetHashLBP3 = update.PlanetHash;
|
||||
break;
|
||||
}
|
||||
case GameVersion.LittleBigPlanetVita:
|
||||
{
|
||||
user.PlanetHashLBPVita = update.PlanetHash;
|
||||
break;
|
||||
}
|
||||
case GameVersion.LittleBigPlanet1:
|
||||
case GameVersion.LittleBigPlanetPSP:
|
||||
case GameVersion.Unknown:
|
||||
default: // The rest do not support custom earths.
|
||||
{
|
||||
throw new ArgumentException($"invalid gameVersion {gameToken.GameVersion} for setting earth");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (update.Location != null)
|
||||
{
|
||||
Location? loc = await this.database.Locations.FirstOrDefaultAsync(l => l.Id == user.LocationId);
|
||||
if (loc == null) throw new Exception("User loc is null, this should never happen.");
|
||||
|
||||
loc.X = update.Location.X;
|
||||
loc.Y = update.Location.Y;
|
||||
}
|
||||
|
||||
if (this.database.ChangeTracker.HasChanges()) await this.database.SaveChangesAsync();
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
[HttpPost("update_my_pins")]
|
||||
public async Task<IActionResult> UpdateMyPins()
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
string pinsString = await new StreamReader(this.Request.Body).ReadToEndAsync();
|
||||
Pins? pinJson = JsonSerializer.Deserialize<Pins>(pinsString);
|
||||
if (pinJson == null) return this.BadRequest();
|
||||
|
||||
// Sometimes the update gets called periodically as pin progress updates via playing,
|
||||
// may not affect equipped profile pins however, so check before setting it.
|
||||
string currentPins = user.Pins;
|
||||
string newPins = string.Join(",", pinJson.ProfilePins);
|
||||
|
||||
if (string.Equals(currentPins, newPins)) return this.Ok("[{\"StatusCode\":200}]");
|
||||
|
||||
user.Pins = newPins;
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
return this.Ok("[{\"StatusCode\":200}]");
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue