mirror of
https://github.com/LBPUnion/ProjectLighthouse.git
synced 2025-08-02 18:18:39 +00:00
Implement read-only mode
This commit is contained in:
parent
e1cc214f44
commit
3da6bc20b4
16 changed files with 125 additions and 16 deletions
|
@ -87,4 +87,10 @@
|
|||
<data name="license_warn_3" xml:space="preserve">
|
||||
<value>If not, please publish the source code somewhere accessible to your users.</value>
|
||||
</data>
|
||||
<data name="read_only_warn_title" xml:space="preserve">
|
||||
<value>Read-Only Mode</value>
|
||||
</data>
|
||||
<data name="read_only_warn" xml:space="preserve">
|
||||
<value>This instance is currently in read-only mode. Level and photo uploads, comments, reviews, and certain profile changes will be restricted until read-only mode is disabled.</value>
|
||||
</data>
|
||||
</root>
|
|
@ -23,5 +23,8 @@ public static class BaseLayoutStrings
|
|||
public static readonly TranslatableString LicenseWarn2 = create("license_warn_2");
|
||||
public static readonly TranslatableString LicenseWarn3 = create("license_warn_3");
|
||||
|
||||
public static readonly TranslatableString ReadOnlyWarnTitle = create("read_only_warn_title");
|
||||
public static readonly TranslatableString ReadOnlyWarn = create("read_only_warn");
|
||||
|
||||
private static TranslatableString create(string key) => new(TranslationAreas.BaseLayout, key);
|
||||
}
|
|
@ -119,6 +119,9 @@ public class CommentController : ControllerBase
|
|||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
// Deny request if in read-only mode
|
||||
if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest();
|
||||
|
||||
GameComment? comment = await this.DeserializeBody<GameComment>();
|
||||
if (comment?.Message == null) return this.BadRequest();
|
||||
|
||||
|
@ -159,6 +162,9 @@ public class CommentController : ControllerBase
|
|||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
// Deny request if in read-only mode
|
||||
if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest();
|
||||
|
||||
if ((slotId == 0 || SlotHelper.IsTypeInvalid(slotType)) == (username == null)) return this.BadRequest();
|
||||
|
||||
CommentEntity? comment = await this.database.Comments.FirstOrDefaultAsync(c => c.CommentId == commentId);
|
||||
|
|
|
@ -59,6 +59,13 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.";
|
|||
announceText.Replace("%user", username);
|
||||
announceText.Replace("%id", token.UserId.ToString());
|
||||
|
||||
if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode)
|
||||
{
|
||||
announceText.Insert(0, "This instance is currently in read-only mode. Level and photo uploads, comments, " +
|
||||
"reviews, and certain profile changes will be restricted until read-only mode is " +
|
||||
"disabled.");
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
announceText.Append("\n\n---DEBUG INFO---\n" +
|
||||
$"user.UserId: {token.UserId}\n" +
|
||||
|
|
|
@ -37,6 +37,9 @@ public class PhotosController : ControllerBase
|
|||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
// Deny request if in read-only mode
|
||||
if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest();
|
||||
|
||||
int photoCount = await this.database.Photos.CountAsync(p => p.CreatorId == token.UserId);
|
||||
if (photoCount >= ServerConfiguration.Instance.UserGeneratedContentLimits.PhotosQuota) return this.BadRequest();
|
||||
|
||||
|
@ -90,7 +93,7 @@ public class PhotosController : ControllerBase
|
|||
case SlotType.Developer:
|
||||
{
|
||||
SlotEntity? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.Type == photoSlot.SlotType && s.InternalSlotId == photoSlot.SlotId);
|
||||
if (slot != null)
|
||||
if (slot != null)
|
||||
photoSlot.SlotId = slot.SlotId;
|
||||
else
|
||||
photoSlot.SlotId = await SlotHelper.GetPlaceholderSlotId(this.database, photoSlot.SlotId, photoSlot.SlotType);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#nullable enable
|
||||
using System.Text;
|
||||
using LBPUnion.ProjectLighthouse.Configuration;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Files;
|
||||
using LBPUnion.ProjectLighthouse.Logging;
|
||||
|
@ -58,10 +59,14 @@ public class ResourcesController : ControllerBase
|
|||
string fullPath = Path.GetFullPath(path);
|
||||
|
||||
FileHelper.EnsureDirectoryCreated(assetsDirectory);
|
||||
// lbp treats code 409 as success and as an indicator that the file is already present
|
||||
|
||||
// Deny request if in read-only mode
|
||||
if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest();
|
||||
|
||||
// LBP treats code 409 as success and as an indicator that the file is already present
|
||||
if (FileHelper.ResourceExists(hash)) return this.Conflict();
|
||||
|
||||
// theoretically shouldn't be possible because of hash check but handle anyways
|
||||
// Theoretically shouldn't be possible because of hash check but handle anyways
|
||||
if (!fullPath.StartsWith(FileHelper.FullResourcePath)) return this.BadRequest();
|
||||
|
||||
Logger.Info($"Processing resource upload (hash: {hash})", LogArea.Resources);
|
||||
|
|
|
@ -43,6 +43,9 @@ public class PublishController : ControllerBase
|
|||
UserEntity? user = await this.database.UserFromGameToken(token);
|
||||
if (user == null) return this.Forbid();
|
||||
|
||||
// Deny request if in read-only mode
|
||||
if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest();
|
||||
|
||||
GameUserSlot? slot = await this.DeserializeBody<GameUserSlot>();
|
||||
if (slot == null)
|
||||
{
|
||||
|
@ -116,6 +119,9 @@ public class PublishController : ControllerBase
|
|||
UserEntity? user = await this.database.UserFromGameToken(token);
|
||||
if (user == null) return this.Forbid();
|
||||
|
||||
// Deny request if in read-only mode
|
||||
if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest();
|
||||
|
||||
GameUserSlot? slot = await this.DeserializeBody<GameUserSlot>();
|
||||
|
||||
if (slot == null)
|
||||
|
@ -335,6 +341,9 @@ public class PublishController : ControllerBase
|
|||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
// Deny request if in read-only mode
|
||||
if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest();
|
||||
|
||||
SlotEntity? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
|
||||
if (slot == null) return this.NotFound();
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.Configuration;
|
||||
using LBPUnion.ProjectLighthouse.Database;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
|
@ -92,6 +93,9 @@ public class ReviewController : ControllerBase
|
|||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
// Deny request if in read-only mode
|
||||
if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest();
|
||||
|
||||
GameReview? newReview = await this.DeserializeBody<GameReview>();
|
||||
if (newReview == null) return this.BadRequest();
|
||||
|
||||
|
@ -115,7 +119,7 @@ public class ReviewController : ControllerBase
|
|||
}
|
||||
review.Thumb = Math.Clamp(newReview.Thumb, -1, 1);
|
||||
review.LabelCollection = LabelHelper.RemoveInvalidLabels(newReview.LabelCollection);
|
||||
|
||||
|
||||
review.Text = newReview.Text;
|
||||
review.Deleted = false;
|
||||
review.Timestamp = TimeHelper.TimestampMillis;
|
||||
|
@ -239,6 +243,9 @@ public class ReviewController : ControllerBase
|
|||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
// Deny request if in read-only mode
|
||||
if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest();
|
||||
|
||||
int creatorId = await this.database.Slots.Where(s => s.SlotId == slotId).Select(s => s.CreatorId).FirstOrDefaultAsync();
|
||||
if (creatorId == 0) return this.BadRequest();
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Text.Json;
|
||||
using LBPUnion.ProjectLighthouse.Configuration;
|
||||
using LBPUnion.ProjectLighthouse.Database;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Files;
|
||||
|
@ -73,6 +74,9 @@ public class UserController : ControllerBase
|
|||
|
||||
if (update.Biography != null)
|
||||
{
|
||||
// Deny request if in read-only mode
|
||||
if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest();
|
||||
|
||||
if (update.Biography.Length > 512) return this.BadRequest();
|
||||
|
||||
user.Biography = update.Biography;
|
||||
|
@ -85,6 +89,9 @@ public class UserController : ControllerBase
|
|||
{
|
||||
if (string.IsNullOrWhiteSpace(resource)) continue;
|
||||
|
||||
// Deny request if in read-only mode
|
||||
if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest();
|
||||
|
||||
if (!FileHelper.ResourceExists(resource) && !resource.StartsWith('g')) return this.BadRequest();
|
||||
|
||||
if (!GameResourceHelper.IsValidTexture(resource)) return this.BadRequest();
|
||||
|
|
|
@ -39,6 +39,9 @@ public class UserPageController : ControllerBase
|
|||
WebTokenEntity? token = this.database.WebTokenFromRequest(this.Request);
|
||||
if (token == null) return this.Redirect("~/login");
|
||||
|
||||
// Deny request if in read-only mode
|
||||
if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest();
|
||||
|
||||
if (msg == null)
|
||||
{
|
||||
Logger.Error($"Refusing to post comment from {token.UserId} on user {id}, {nameof(msg)} is null", LogArea.Comments);
|
||||
|
|
|
@ -178,6 +178,18 @@
|
|||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode)
|
||||
{
|
||||
<div class="ui bottom attached red message large">
|
||||
<div class="ui container">
|
||||
<i class="warning icon"></i>
|
||||
<span style="font-size: 1.2rem;">@Model.Translate(BaseLayoutStrings.ReadOnlyWarnTitle)</span>
|
||||
<p>
|
||||
@Html.Raw(Model.Translate(BaseLayoutStrings.ReadOnlyWarn))
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</header>
|
||||
<div class="main">
|
||||
<div class="ui container">
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@using System.Web
|
||||
@using LBPUnion.ProjectLighthouse.Configuration
|
||||
@using LBPUnion.ProjectLighthouse.Database
|
||||
@using LBPUnion.ProjectLighthouse.Localization
|
||||
@using LBPUnion.ProjectLighthouse.Servers.Website.Extensions
|
||||
|
@ -31,18 +32,32 @@
|
|||
@if (Model.CommentsEnabled && Model.User != null)
|
||||
{
|
||||
<div class="ui divider"></div>
|
||||
<form class="ui reply form" action="postComment" method="post">
|
||||
<div class="field">
|
||||
<textarea style="min-height: 70px; height: 70px; max-height:120px" maxlength="100" name="msg"></textarea>
|
||||
@if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode)
|
||||
{
|
||||
<div class="ui red segment">
|
||||
<p>
|
||||
<i>
|
||||
@ServerConfiguration.Instance.Customization.ServerName is currently in read-only mode.
|
||||
You will not be able to post comments until read-only mode is disabled.
|
||||
</i>
|
||||
</p>
|
||||
</div>
|
||||
<input type="submit" class="ui blue button">
|
||||
</form>
|
||||
}
|
||||
else
|
||||
{
|
||||
<form class="ui reply form" action="postComment" method="post">
|
||||
<div class="field">
|
||||
<textarea style="min-height: 70px; height: 70px; max-height:120px" maxlength="100" name="msg"></textarea>
|
||||
</div>
|
||||
<input type="submit" class="ui blue button">
|
||||
</form>
|
||||
}
|
||||
@if (Model.Comments.Count > 0)
|
||||
{
|
||||
<div class="ui divider"></div>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@{
|
||||
int i = 0;
|
||||
foreach (KeyValuePair<CommentEntity, RatedCommentEntity?> commentAndReaction in Model.Comments)
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
@using LBPUnion.ProjectLighthouse.Helpers
|
||||
@using LBPUnion.ProjectLighthouse.Types.Entities.Level
|
||||
@using LBPUnion.ProjectLighthouse.Types.Serialization
|
||||
|
||||
@{
|
||||
bool isMobile = (bool?)ViewData["IsMobile"] ?? false;
|
||||
bool canDelete = (bool?)ViewData["CanDelete"] ?? false;
|
||||
|
@ -29,6 +28,18 @@
|
|||
<div class="ui divider"></div>
|
||||
}
|
||||
|
||||
@if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode)
|
||||
{
|
||||
<div class="ui red segment">
|
||||
<p>
|
||||
<i>
|
||||
@ServerConfiguration.Instance.Customization.ServerName is currently in read-only mode.
|
||||
You will not be able to post reviews in-game until read-only mode is disabled.
|
||||
</i>
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
@for(int i = 0; i < Model.Reviews.Count; i++)
|
||||
{
|
||||
ReviewEntity review = Model.Reviews[i];
|
||||
|
@ -36,7 +47,7 @@
|
|||
-1 => review.Reviewer?.BooHash,
|
||||
0 => review.Reviewer?.MehHash,
|
||||
1 => review.Reviewer?.YayHash,
|
||||
|
||||
|
||||
_ => throw new ArgumentOutOfRangeException(),
|
||||
}) ?? "";
|
||||
|
||||
|
@ -49,7 +60,7 @@
|
|||
-1 => "Boo!",
|
||||
0 => "Meh.",
|
||||
1 => "Yay!",
|
||||
|
||||
|
||||
_ => throw new ArgumentOutOfRangeException(),
|
||||
};
|
||||
|
||||
|
@ -114,7 +125,7 @@
|
|||
if (window.confirm("Are you sure you want to delete this?\nThis action cannot be undone.")){
|
||||
window.location.hash = "reviews";
|
||||
window.location.href = "/moderation/deleteReview/" + reviewId + "?callbackUrl=" + this.window.location;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
|
|
|
@ -41,12 +41,21 @@ public class UserSettingsPage : BaseLayout
|
|||
|
||||
string? avatarHash = await FileHelper.ParseBase64Image(avatar);
|
||||
|
||||
if (avatarHash != null) this.ProfileUser.IconHash = avatarHash;
|
||||
if (avatarHash != null)
|
||||
{
|
||||
// Deny request if in read-only mode
|
||||
if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest();
|
||||
|
||||
this.ProfileUser.IconHash = avatarHash;
|
||||
}
|
||||
|
||||
if (this.User.IsAdmin) this.ProfileUser.ProfileTag = profileTag;
|
||||
|
||||
if (biography != null)
|
||||
{
|
||||
// Deny request if in read-only mode
|
||||
if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest();
|
||||
|
||||
biography = CensorHelper.FilterMessage(biography);
|
||||
if (this.ProfileUser.Biography != biography && biography.Length <= 512)
|
||||
this.ProfileUser.Biography = biography;
|
||||
|
|
|
@ -11,6 +11,12 @@ public class UserGeneratedContentLimitConfiguration
|
|||
|
||||
public int PhotosQuota { get; set; } = 500;
|
||||
|
||||
/// <summary>
|
||||
/// When enabled, all UGC uploads are disabled. This includes levels, photos, reviews,
|
||||
/// comments, and certain profile settings.
|
||||
/// </summary>
|
||||
public bool ReadOnlyMode { get; set; } = false;
|
||||
|
||||
public bool ProfileCommentsEnabled { get; set; } = true;
|
||||
|
||||
public bool LevelCommentsEnabled { get; set; } = true;
|
||||
|
|
|
@ -11,7 +11,7 @@ public class ServerConfiguration : ConfigurationBase<ServerConfiguration>
|
|||
// This is so Lighthouse can properly identify outdated configurations and update them with newer settings accordingly.
|
||||
// If you are modifying anything here, this value MUST be incremented.
|
||||
// Thanks for listening~
|
||||
public override int ConfigVersion { get; set; } = 25;
|
||||
public override int ConfigVersion { get; set; } = 26;
|
||||
|
||||
public override string ConfigName { get; set; } = "lighthouse.yml";
|
||||
public string WebsiteListenUrl { get; set; } = "http://localhost:10060";
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue