Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement read-only mode #1001

Merged
merged 13 commits into from
Mar 30, 2024
6 changes: 6 additions & 0 deletions ProjectLighthouse.Localization/BaseLayout.resx
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ public async Task<IActionResult> PostComment(string? username, string? slotType,
{
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();

Expand Down Expand Up @@ -159,6 +162,9 @@ public async Task<IActionResult> DeleteComment([FromQuery] int commentId, string
{
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Localization;
using LBPUnion.ProjectLighthouse.Localization.StringLists;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Types.Entities.Notifications;
Expand Down Expand Up @@ -59,6 +61,11 @@ public async Task<IActionResult> Announce()
announceText.Replace("%user", username);
announceText.Replace("%id", token.UserId.ToString());

if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode)
{
announceText.Insert(0, BaseLayoutStrings.ReadOnlyWarn.Translate(LocalizationManager.DefaultLang));
}

#if DEBUG
announceText.Append("\n\n---DEBUG INFO---\n" +
$"user.UserId: {token.UserId}\n" +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ public async Task<IActionResult> UploadPhoto()
{
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();

Expand Down Expand Up @@ -90,7 +93,7 @@ public async Task<IActionResult> UploadPhoto()
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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -58,10 +59,14 @@ public async Task<IActionResult> UploadResource(string hash)
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ public async Task<IActionResult> StartPublish()
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)
{
Expand Down Expand Up @@ -116,6 +119,9 @@ public async Task<IActionResult> Publish([FromQuery] string? game)
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)
Expand Down Expand Up @@ -335,6 +341,9 @@ public async Task<IActionResult> Unpublish(int id)
{
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();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#nullable enable
using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Helpers;
Expand Down Expand Up @@ -92,6 +93,9 @@ public async Task<IActionResult> PostReview(int slotId)
{
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();

Expand All @@ -115,7 +119,7 @@ public async Task<IActionResult> PostReview(int slotId)
}
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;
Expand Down Expand Up @@ -239,6 +243,9 @@ public async Task<IActionResult> DeleteReview(int slotId, string username)
{
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();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.Json;
using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Files;
Expand Down Expand Up @@ -73,6 +74,9 @@ public async Task<IActionResult> UpdateUser()

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;
Expand All @@ -85,6 +89,9 @@ public async Task<IActionResult> UpdateUser()
{
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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

// I would like to apologize in advance for anyone dealing with this file.
// I would like to apologize in advance for anyone dealing with this file.
// Theres probably a better way to do this with delegates but I'm tired.
// TODO: Clean up this file
// - jvyden
Expand Down Expand Up @@ -63,6 +63,9 @@ public async Task<IActionResult> PostComment([FromRoute] int id, [FromForm] stri
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.Redirect("~/slot/" + id);

if (msg == null)
{
Logger.Error($"Refusing to post comment from {token.UserId} on level {id}, {nameof(msg)} is null", LogArea.Comments);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ public async Task<IActionResult> PostComment([FromRoute] int id, [FromForm] stri
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.Redirect("~/user/" + id);

if (msg == null)
{
Logger.Error($"Refusing to post comment from {token.UserId} on user {id}, {nameof(msg)} is null", LogArea.Comments);
Expand Down
37 changes: 36 additions & 1 deletion ProjectLighthouse.Servers.Website/Pages/LandingPage.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,42 @@
}
}

<br><br>
@if (Model.LatestAnnouncement != null)
{
<div class="ui blue segment" style="position: relative;">
<div>
<h3>@Model.LatestAnnouncement.Title</h3>
<div style="padding-bottom: 2em;">
@if (Model.LatestAnnouncement.Content.Length > 250)
{
<span style="white-space: pre-line">
@Model.LatestAnnouncement.Content[..250]...
<a href="@ServerConfiguration.Instance.ExternalUrl/notifications">read more</a>
</span>
}
else
{
<span style="white-space: pre-line">
@Model.LatestAnnouncement.Content
</span>
}
</div>
@if (Model.LatestAnnouncement.Publisher != null)
{
<div class="ui tiny bottom left attached label">
Posted by
<a style="color: black" href="~/user/@Model.LatestAnnouncement.Publisher.UserId">
@Model.LatestAnnouncement.Publisher.Username
</a>
</div>
}
</div>
</div>
}
else
{
<br /><br />
}

<div class="@(isMobile ? "" : "ui center aligned grid")">
<div class="eight wide column">
Expand Down
7 changes: 7 additions & 0 deletions ProjectLighthouse.Servers.Website/Pages/LandingPage.cshtml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Entities.Website;
using LBPUnion.ProjectLighthouse.Types.Levels;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
Expand All @@ -19,6 +20,8 @@ public class LandingPage : BaseLayout
public int PendingAuthAttempts;
public List<UserEntity> PlayersOnline = new();

public WebsiteAnnouncementEntity? LatestAnnouncement;

public LandingPage(DatabaseContext database) : base(database)
{ }

Expand Down Expand Up @@ -54,6 +57,10 @@ public async Task<IActionResult> OnGet()
.Include(s => s.Creator)
.ToListAsync();

this.LatestAnnouncement = await this.Database.WebsiteAnnouncements.Include(a => a.Publisher)
.OrderByDescending(a => a.AnnouncementId)
.FirstOrDefaultAsync();

return this.Page();
}
}
12 changes: 12 additions & 0 deletions ProjectLighthouse.Servers.Website/Pages/Layouts/BaseLayout.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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)
Expand Down
Loading
Loading