Skip to content

Commit 3da6bc2

Browse files
committed
Implement read-only mode
1 parent e1cc214 commit 3da6bc2

File tree

16 files changed

+125
-16
lines changed

16 files changed

+125
-16
lines changed

ProjectLighthouse.Localization/BaseLayout.resx

+6
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,10 @@
8787
<data name="license_warn_3" xml:space="preserve">
8888
<value>If not, please publish the source code somewhere accessible to your users.</value>
8989
</data>
90+
<data name="read_only_warn_title" xml:space="preserve">
91+
<value>Read-Only Mode</value>
92+
</data>
93+
<data name="read_only_warn" xml:space="preserve">
94+
<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>
95+
</data>
9096
</root>

ProjectLighthouse.Localization/StringLists/BaseLayoutStrings.cs

+3
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,8 @@ public static class BaseLayoutStrings
2323
public static readonly TranslatableString LicenseWarn2 = create("license_warn_2");
2424
public static readonly TranslatableString LicenseWarn3 = create("license_warn_3");
2525

26+
public static readonly TranslatableString ReadOnlyWarnTitle = create("read_only_warn_title");
27+
public static readonly TranslatableString ReadOnlyWarn = create("read_only_warn");
28+
2629
private static TranslatableString create(string key) => new(TranslationAreas.BaseLayout, key);
2730
}

ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs

+6
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@ public async Task<IActionResult> PostComment(string? username, string? slotType,
119119
{
120120
GameTokenEntity token = this.GetToken();
121121

122+
// Deny request if in read-only mode
123+
if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest();
124+
122125
GameComment? comment = await this.DeserializeBody<GameComment>();
123126
if (comment?.Message == null) return this.BadRequest();
124127

@@ -159,6 +162,9 @@ public async Task<IActionResult> DeleteComment([FromQuery] int commentId, string
159162
{
160163
GameTokenEntity token = this.GetToken();
161164

165+
// Deny request if in read-only mode
166+
if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest();
167+
162168
if ((slotId == 0 || SlotHelper.IsTypeInvalid(slotType)) == (username == null)) return this.BadRequest();
163169

164170
CommentEntity? comment = await this.database.Comments.FirstOrDefaultAsync(c => c.CommentId == commentId);

ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs

+7
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,13 @@ public async Task<IActionResult> Announce()
5959
announceText.Replace("%user", username);
6060
announceText.Replace("%id", token.UserId.ToString());
6161

62+
if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode)
63+
{
64+
announceText.Insert(0, "This instance is currently in read-only mode. Level and photo uploads, comments, " +
65+
"reviews, and certain profile changes will be restricted until read-only mode is " +
66+
"disabled.");
67+
}
68+
6269
#if DEBUG
6370
announceText.Append("\n\n---DEBUG INFO---\n" +
6471
$"user.UserId: {token.UserId}\n" +

ProjectLighthouse.Servers.GameServer/Controllers/Resources/PhotosController.cs

+4-1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ public async Task<IActionResult> UploadPhoto()
3737
{
3838
GameTokenEntity token = this.GetToken();
3939

40+
// Deny request if in read-only mode
41+
if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest();
42+
4043
int photoCount = await this.database.Photos.CountAsync(p => p.CreatorId == token.UserId);
4144
if (photoCount >= ServerConfiguration.Instance.UserGeneratedContentLimits.PhotosQuota) return this.BadRequest();
4245

@@ -90,7 +93,7 @@ public async Task<IActionResult> UploadPhoto()
9093
case SlotType.Developer:
9194
{
9295
SlotEntity? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.Type == photoSlot.SlotType && s.InternalSlotId == photoSlot.SlotId);
93-
if (slot != null)
96+
if (slot != null)
9497
photoSlot.SlotId = slot.SlotId;
9598
else
9699
photoSlot.SlotId = await SlotHelper.GetPlaceholderSlotId(this.database, photoSlot.SlotId, photoSlot.SlotType);

ProjectLighthouse.Servers.GameServer/Controllers/Resources/ResourcesController.cs

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#nullable enable
22
using System.Text;
3+
using LBPUnion.ProjectLighthouse.Configuration;
34
using LBPUnion.ProjectLighthouse.Extensions;
45
using LBPUnion.ProjectLighthouse.Files;
56
using LBPUnion.ProjectLighthouse.Logging;
@@ -58,10 +59,14 @@ public async Task<IActionResult> UploadResource(string hash)
5859
string fullPath = Path.GetFullPath(path);
5960

6061
FileHelper.EnsureDirectoryCreated(assetsDirectory);
61-
// lbp treats code 409 as success and as an indicator that the file is already present
62+
63+
// Deny request if in read-only mode
64+
if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest();
65+
66+
// LBP treats code 409 as success and as an indicator that the file is already present
6267
if (FileHelper.ResourceExists(hash)) return this.Conflict();
6368

64-
// theoretically shouldn't be possible because of hash check but handle anyways
69+
// Theoretically shouldn't be possible because of hash check but handle anyways
6570
if (!fullPath.StartsWith(FileHelper.FullResourcePath)) return this.BadRequest();
6671

6772
Logger.Info($"Processing resource upload (hash: {hash})", LogArea.Resources);

ProjectLighthouse.Servers.GameServer/Controllers/Slots/PublishController.cs

+9
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ public async Task<IActionResult> StartPublish()
4343
UserEntity? user = await this.database.UserFromGameToken(token);
4444
if (user == null) return this.Forbid();
4545

46+
// Deny request if in read-only mode
47+
if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest();
48+
4649
GameUserSlot? slot = await this.DeserializeBody<GameUserSlot>();
4750
if (slot == null)
4851
{
@@ -116,6 +119,9 @@ public async Task<IActionResult> Publish([FromQuery] string? game)
116119
UserEntity? user = await this.database.UserFromGameToken(token);
117120
if (user == null) return this.Forbid();
118121

122+
// Deny request if in read-only mode
123+
if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest();
124+
119125
GameUserSlot? slot = await this.DeserializeBody<GameUserSlot>();
120126

121127
if (slot == null)
@@ -335,6 +341,9 @@ public async Task<IActionResult> Unpublish(int id)
335341
{
336342
GameTokenEntity token = this.GetToken();
337343

344+
// Deny request if in read-only mode
345+
if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest();
346+
338347
SlotEntity? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
339348
if (slot == null) return this.NotFound();
340349

ProjectLighthouse.Servers.GameServer/Controllers/Slots/ReviewController.cs

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#nullable enable
2+
using LBPUnion.ProjectLighthouse.Configuration;
23
using LBPUnion.ProjectLighthouse.Database;
34
using LBPUnion.ProjectLighthouse.Extensions;
45
using LBPUnion.ProjectLighthouse.Helpers;
@@ -92,6 +93,9 @@ public async Task<IActionResult> PostReview(int slotId)
9293
{
9394
GameTokenEntity token = this.GetToken();
9495

96+
// Deny request if in read-only mode
97+
if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest();
98+
9599
GameReview? newReview = await this.DeserializeBody<GameReview>();
96100
if (newReview == null) return this.BadRequest();
97101

@@ -115,7 +119,7 @@ public async Task<IActionResult> PostReview(int slotId)
115119
}
116120
review.Thumb = Math.Clamp(newReview.Thumb, -1, 1);
117121
review.LabelCollection = LabelHelper.RemoveInvalidLabels(newReview.LabelCollection);
118-
122+
119123
review.Text = newReview.Text;
120124
review.Deleted = false;
121125
review.Timestamp = TimeHelper.TimestampMillis;
@@ -239,6 +243,9 @@ public async Task<IActionResult> DeleteReview(int slotId, string username)
239243
{
240244
GameTokenEntity token = this.GetToken();
241245

246+
// Deny request if in read-only mode
247+
if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest();
248+
242249
int creatorId = await this.database.Slots.Where(s => s.SlotId == slotId).Select(s => s.CreatorId).FirstOrDefaultAsync();
243250
if (creatorId == 0) return this.BadRequest();
244251

ProjectLighthouse.Servers.GameServer/Controllers/UserController.cs

+7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Text.Json;
2+
using LBPUnion.ProjectLighthouse.Configuration;
23
using LBPUnion.ProjectLighthouse.Database;
34
using LBPUnion.ProjectLighthouse.Extensions;
45
using LBPUnion.ProjectLighthouse.Files;
@@ -73,6 +74,9 @@ public async Task<IActionResult> UpdateUser()
7374

7475
if (update.Biography != null)
7576
{
77+
// Deny request if in read-only mode
78+
if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest();
79+
7680
if (update.Biography.Length > 512) return this.BadRequest();
7781

7882
user.Biography = update.Biography;
@@ -85,6 +89,9 @@ public async Task<IActionResult> UpdateUser()
8589
{
8690
if (string.IsNullOrWhiteSpace(resource)) continue;
8791

92+
// Deny request if in read-only mode
93+
if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest();
94+
8895
if (!FileHelper.ResourceExists(resource) && !resource.StartsWith('g')) return this.BadRequest();
8996

9097
if (!GameResourceHelper.IsValidTexture(resource)) return this.BadRequest();

ProjectLighthouse.Servers.Website/Controllers/UserPageController.cs

+3
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ public async Task<IActionResult> PostComment([FromRoute] int id, [FromForm] stri
3939
WebTokenEntity? token = this.database.WebTokenFromRequest(this.Request);
4040
if (token == null) return this.Redirect("~/login");
4141

42+
// Deny request if in read-only mode
43+
if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest();
44+
4245
if (msg == null)
4346
{
4447
Logger.Error($"Refusing to post comment from {token.UserId} on user {id}, {nameof(msg)} is null", LogArea.Comments);

ProjectLighthouse.Servers.Website/Pages/Layouts/BaseLayout.cshtml

+12
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,18 @@
178178
</div>
179179
</div>
180180
}
181+
@if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode)
182+
{
183+
<div class="ui bottom attached red message large">
184+
<div class="ui container">
185+
<i class="warning icon"></i>
186+
<span style="font-size: 1.2rem;">@Model.Translate(BaseLayoutStrings.ReadOnlyWarnTitle)</span>
187+
<p>
188+
@Html.Raw(Model.Translate(BaseLayoutStrings.ReadOnlyWarn))
189+
</p>
190+
</div>
191+
</div>
192+
}
181193
</header>
182194
<div class="main">
183195
<div class="ui container">

ProjectLighthouse.Servers.Website/Pages/Partials/CommentsPartial.cshtml

+21-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
@using System.Web
2+
@using LBPUnion.ProjectLighthouse.Configuration
23
@using LBPUnion.ProjectLighthouse.Database
34
@using LBPUnion.ProjectLighthouse.Localization
45
@using LBPUnion.ProjectLighthouse.Servers.Website.Extensions
@@ -31,18 +32,32 @@
3132
@if (Model.CommentsEnabled && Model.User != null)
3233
{
3334
<div class="ui divider"></div>
34-
<form class="ui reply form" action="postComment" method="post">
35-
<div class="field">
36-
<textarea style="min-height: 70px; height: 70px; max-height:120px" maxlength="100" name="msg"></textarea>
35+
@if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode)
36+
{
37+
<div class="ui red segment">
38+
<p>
39+
<i>
40+
@ServerConfiguration.Instance.Customization.ServerName is currently in read-only mode.
41+
You will not be able to post comments until read-only mode is disabled.
42+
</i>
43+
</p>
3744
</div>
38-
<input type="submit" class="ui blue button">
39-
</form>
45+
}
46+
else
47+
{
48+
<form class="ui reply form" action="postComment" method="post">
49+
<div class="field">
50+
<textarea style="min-height: 70px; height: 70px; max-height:120px" maxlength="100" name="msg"></textarea>
51+
</div>
52+
<input type="submit" class="ui blue button">
53+
</form>
54+
}
4055
@if (Model.Comments.Count > 0)
4156
{
4257
<div class="ui divider"></div>
4358
}
4459
}
45-
60+
4661
@{
4762
int i = 0;
4863
foreach (KeyValuePair<CommentEntity, RatedCommentEntity?> commentAndReaction in Model.Comments)

ProjectLighthouse.Servers.Website/Pages/Partials/ReviewPartial.cshtml

+15-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
@using LBPUnion.ProjectLighthouse.Helpers
55
@using LBPUnion.ProjectLighthouse.Types.Entities.Level
66
@using LBPUnion.ProjectLighthouse.Types.Serialization
7-
87
@{
98
bool isMobile = (bool?)ViewData["IsMobile"] ?? false;
109
bool canDelete = (bool?)ViewData["CanDelete"] ?? false;
@@ -29,14 +28,26 @@
2928
<div class="ui divider"></div>
3029
}
3130

31+
@if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode)
32+
{
33+
<div class="ui red segment">
34+
<p>
35+
<i>
36+
@ServerConfiguration.Instance.Customization.ServerName is currently in read-only mode.
37+
You will not be able to post reviews in-game until read-only mode is disabled.
38+
</i>
39+
</p>
40+
</div>
41+
}
42+
3243
@for(int i = 0; i < Model.Reviews.Count; i++)
3344
{
3445
ReviewEntity review = Model.Reviews[i];
3546
string faceHash = (review.Thumb switch {
3647
-1 => review.Reviewer?.BooHash,
3748
0 => review.Reviewer?.MehHash,
3849
1 => review.Reviewer?.YayHash,
39-
50+
4051
_ => throw new ArgumentOutOfRangeException(),
4152
}) ?? "";
4253

@@ -49,7 +60,7 @@
4960
-1 => "Boo!",
5061
0 => "Meh.",
5162
1 => "Yay!",
52-
63+
5364
_ => throw new ArgumentOutOfRangeException(),
5465
};
5566

@@ -114,7 +125,7 @@
114125
if (window.confirm("Are you sure you want to delete this?\nThis action cannot be undone.")){
115126
window.location.hash = "reviews";
116127
window.location.href = "/moderation/deleteReview/" + reviewId + "?callbackUrl=" + this.window.location;
117-
}
128+
}
118129
}
119130
</script>
120131
</div>

ProjectLighthouse.Servers.Website/Pages/UserSettingsPage.cshtml.cs

+10-1
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,21 @@ [FromForm] string? language
4141

4242
string? avatarHash = await FileHelper.ParseBase64Image(avatar);
4343

44-
if (avatarHash != null) this.ProfileUser.IconHash = avatarHash;
44+
if (avatarHash != null)
45+
{
46+
// Deny request if in read-only mode
47+
if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest();
48+
49+
this.ProfileUser.IconHash = avatarHash;
50+
}
4551

4652
if (this.User.IsAdmin) this.ProfileUser.ProfileTag = profileTag;
4753

4854
if (biography != null)
4955
{
56+
// Deny request if in read-only mode
57+
if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest();
58+
5059
biography = CensorHelper.FilterMessage(biography);
5160
if (this.ProfileUser.Biography != biography && biography.Length <= 512)
5261
this.ProfileUser.Biography = biography;

ProjectLighthouse/Configuration/ConfigurationCategories/UserGeneratedContentLimitConfiguration.cs

+6
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ public class UserGeneratedContentLimitConfiguration
1111

1212
public int PhotosQuota { get; set; } = 500;
1313

14+
/// <summary>
15+
/// When enabled, all UGC uploads are disabled. This includes levels, photos, reviews,
16+
/// comments, and certain profile settings.
17+
/// </summary>
18+
public bool ReadOnlyMode { get; set; } = false;
19+
1420
public bool ProfileCommentsEnabled { get; set; } = true;
1521

1622
public bool LevelCommentsEnabled { get; set; } = true;

ProjectLighthouse/Configuration/ServerConfiguration.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public class ServerConfiguration : ConfigurationBase<ServerConfiguration>
1111
// This is so Lighthouse can properly identify outdated configurations and update them with newer settings accordingly.
1212
// If you are modifying anything here, this value MUST be incremented.
1313
// Thanks for listening~
14-
public override int ConfigVersion { get; set; } = 25;
14+
public override int ConfigVersion { get; set; } = 26;
1515

1616
public override string ConfigName { get; set; } = "lighthouse.yml";
1717
public string WebsiteListenUrl { get; set; } = "http://localhost:10060";

0 commit comments

Comments
 (0)