Merge branch 'main' into recent-activity

# Conflicts:
#	.config/dotnet-tools.json
#	ProjectLighthouse.Servers.Website/Pages/Partials/ReviewPartial.cshtml
This commit is contained in:
Slendy 2024-04-01 14:45:59 -05:00
commit 8f91875f7c
No known key found for this signature in database
GPG key ID: 7288D68361B91428
54 changed files with 459 additions and 48 deletions

View file

@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "8.0.0",
"version": "8.0.3",
"commands": [
"dotnet-ef"
]

89
.github/workflows/docker-publish.yml vendored Normal file
View file

@ -0,0 +1,89 @@
name: Build Docker Image
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}
jobs:
build-publish:
name: Build and Publish
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
# This is used to complete the identity challenge
# with sigstore/fulcio when running outside of PRs.
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Install the cosign tool except on PR
# https://github.com/sigstore/cosign-installer
- name: Install cosign
if: github.event_name != 'pull_request'
uses: sigstore/cosign-installer@e1523de7571e31dbe865fd2e80c5c7c23ae71eb4 #v3.4.0
with:
cosign-release: 'v2.2.3'
# Set up BuildKit Docker container builder to be able to build
# multi-platform images and export cache
# https://github.com/docker/setup-buildx-action
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
# Login against a Docker registry except on PR
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
# Sign the resulting Docker image digest except on PRs.
# This will only write to the public Rekor transparency log when the Docker
# repository is public to avoid leaking data. If you would like to publish
# transparency data even for private images, pass --force to cosign below.
# https://github.com/sigstore/cosign
- name: Sign the published Docker image
if: ${{ github.event_name != 'pull_request' }}
env:
# https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable
TAGS: ${{ steps.meta.outputs.tags }}
DIGEST: ${{ steps.build-and-push.outputs.digest }}
# This step uses the identity token to provision an ephemeral certificate
# against the sigstore community Fulcio instance.
run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST}

View file

@ -80,4 +80,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>

View file

@ -80,4 +80,10 @@
<data name="license_warn_3" xml:space="preserve">
<value>Hvis ikke, bedes du publicere kildekoden et eller andet sted, der er tilgængeligt for dine brugere.</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>

View file

@ -80,4 +80,10 @@
<data name="license_warn_3" xml:space="preserve">
<value>Falls nicht, veröffentliche den Quellcode irgendwo, wo die Nutzer deiner Instanz darauf zugreifen können.</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>

View file

@ -80,4 +80,10 @@
<data name="license_warn_3" xml:space="preserve">
<value>If not, please publish the source code somewhere accessible to yer 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>

View file

@ -80,4 +80,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>

View file

@ -80,4 +80,10 @@
<data name="license_warn_3" xml:space="preserve">
<value>Si no es así, por favor publique el código fuente en algún lugar accesible para sus usuarios.</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>

View file

@ -80,4 +80,10 @@
<data name="license_warn_3" xml:space="preserve">
<value>De lo contrario, publique el código fuente en algún lugar accesible para sus usuarios.</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>

View file

@ -80,4 +80,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>

View file

@ -80,4 +80,10 @@
<data name="license_warn_3" xml:space="preserve">
<value>Jos ei, julkaise lähdekoodisi jonnekin, jossa se on käyttäjillesi saatavissa.</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>

View file

@ -80,4 +80,10 @@
<data name="license_warn_3" xml:space="preserve">
<value>Kung hindi, mangyaring ilathala ang source code sa isang lugar na maaring ma-access ng iyong mga user.</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>

View file

@ -80,4 +80,10 @@
<data name="license_warn_3" xml:space="preserve">
<value>Dans le cas contraire, veuillez publier le code source dans un endroit accessible à vos utilisateurs.</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>

View file

@ -80,4 +80,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>

View file

@ -80,4 +80,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>

View file

@ -80,4 +80,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>

View file

@ -80,4 +80,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>

View file

@ -80,4 +80,10 @@
<data name="license_warn_3" xml:space="preserve">
<value>In caso contrario, si prega di pubblicare il codice sorgente da qualche parte accessibile ai propri utenti.</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>

View file

@ -80,4 +80,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>

View file

@ -80,4 +80,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>

View file

@ -80,4 +80,10 @@
<data name="license_warn_3" xml:space="preserve">
<value>Hvis ikke, vennligst publiser kildekoden et sted som er tilgjengelig for brukerne dine</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>

View file

@ -80,4 +80,10 @@
<data name="license_warn_3" xml:space="preserve">
<value>Jeśli nie, proszę opublikować kod źródłowy dostępny dla użytkowników.</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>

View file

@ -80,4 +80,10 @@
<data name="license_warn_3" xml:space="preserve">
<value>Se não for o caso, publique o código-fonte em um local acessível aos seus utilizadores.</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>

View file

@ -80,4 +80,10 @@
<data name="license_warn_3" xml:space="preserve">
<value>Если нет, пожалуйста, опубликуйте исходный код в доступном для ваших пользователей месте.</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>

View file

@ -80,4 +80,10 @@
<data name="license_warn_3" xml:space="preserve">
<value>Om inte, vänligen publicera källkoden någonstans tillgängligt för dina användare.</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>

View file

@ -80,4 +80,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>

View file

@ -80,4 +80,10 @@
<data name="license_warn_3" xml:space="preserve">
<value>Eğer değilse, lütfen kaynak kodunu kullanıcılarınızın erişebileceği bir yerde yayınlayın.</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>

View file

@ -80,4 +80,10 @@
<data name="license_warn_3" xml:space="preserve">
<value>Якщо ні, будь ласка, опублікуйте вихідний код у місці, доступному для ваших користувачів.</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>

View file

@ -80,4 +80,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>

View file

@ -80,4 +80,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>

View file

@ -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>

View file

@ -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);
}

View file

@ -133,6 +133,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();
@ -173,6 +176,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);

View file

@ -3,6 +3,8 @@ using LBPUnion.ProjectLighthouse.Configuration;
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;
@ -59,6 +61,11 @@ 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, BaseLayoutStrings.ReadOnlyWarn.Translate(LocalizationManager.DefaultLang) + "\n\n");
}
#if DEBUG
announceText.Append("\n\n---DEBUG INFO---\n" +
$"user.UserId: {token.UserId}\n" +

View file

@ -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);

View file

@ -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);

View file

@ -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();

View file

@ -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;
@ -255,6 +259,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();

View file

@ -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();

View file

@ -10,7 +10,7 @@ using LBPUnion.ProjectLighthouse.Types.Logging;
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
@ -63,6 +63,9 @@ public class SlotPageController : 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.Redirect("~/slot/" + id);
if (msg == null)
{
Logger.Error($"Refusing to post comment from {token.UserId} on level {id}, {nameof(msg)} is null", LogArea.Comments);

View file

@ -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.Redirect("~/user/" + id);
if (msg == null)
{
Logger.Error($"Refusing to post comment from {token.UserId} on user {id}, {nameof(msg)} is null", LogArea.Comments);

View file

@ -60,7 +60,37 @@
}
}
<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">

View file

@ -5,6 +5,7 @@ using LBPUnion.ProjectLighthouse.Helpers;
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;
@ -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)
{ }
@ -54,6 +57,10 @@ public class LandingPage : BaseLayout
.Include(s => s.Creator)
.ToListAsync();
this.LatestAnnouncement = await this.Database.WebsiteAnnouncements.Include(a => a.Publisher)
.OrderByDescending(a => a.AnnouncementId)
.FirstOrDefaultAsync();
return this.Page();
}
}

View file

@ -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">

View file

@ -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)

View file

@ -29,6 +29,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 +48,7 @@
-1 => review.Reviewer?.BooHash,
0 => review.Reviewer?.MehHash,
1 => review.Reviewer?.YayHash,
_ => throw new ArgumentOutOfRangeException(),
}) ?? "";
@ -49,7 +61,7 @@
-1 => "Boo!",
0 => "Meh.",
1 => "Yay!",
_ => throw new ArgumentOutOfRangeException(),
};
@ -114,7 +126,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>

View file

@ -1,4 +1,5 @@
#nullable enable
using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Files;
using LBPUnion.ProjectLighthouse.Helpers;
@ -25,6 +26,10 @@ public class SlotSettingsPage : BaseLayout
if (!this.User.IsModerator && this.User != this.Slot.Creator) return this.Redirect("~/slot/" + slotId);
// Deny request if in read-only mode
if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode)
return this.Redirect($"~/slot/{slotId}");
string? avatarHash = await FileHelper.ParseBase64Image(avatar);
if (avatarHash != null) this.Slot.IconHash = avatarHash;
@ -46,7 +51,7 @@ public class SlotSettingsPage : BaseLayout
if (labels != null)
{
labels = LabelHelper.RemoveInvalidLabels(labels);
if (this.Slot.AuthorLabels != labels)
if (this.Slot.AuthorLabels != labels)
this.Slot.AuthorLabels = labels;
}

View file

@ -39,6 +39,10 @@ public class UserSettingsPage : BaseLayout
if (!this.User.IsModerator && this.User != this.ProfileUser) return this.Redirect("~/user/" + userId);
// Deny request if in read-only mode
if (avatar != null && ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode)
return this.Redirect($"~/user/{userId}");
string? avatarHash = await FileHelper.ParseBase64Image(avatar);
if (avatarHash != null) this.ProfileUser.IconHash = avatarHash;
@ -47,6 +51,10 @@ public class UserSettingsPage : BaseLayout
if (biography != null)
{
// Deny request if in read-only mode
if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode)
return this.Redirect($"~/user/{userId}");
biography = CensorHelper.FilterMessage(biography);
if (this.ProfileUser.Biography != biography && biography.Length <= 512)
this.ProfileUser.Biography = biography;

View file

@ -9,18 +9,18 @@
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.2">
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.6.6" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="xunit" Version="2.7.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.7">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PackageReference Include="coverlet.collector" Version="6.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>

View file

@ -9,20 +9,20 @@
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.2">
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="Selenium.WebDriver" Version="4.17.0" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="121.0.6167.8500" />
<PackageReference Include="xunit" Version="2.6.6" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="Selenium.WebDriver" Version="4.19.0" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="123.0.6312.8600" />
<PackageReference Include="xunit" Version="2.7.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.7">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PackageReference Include="coverlet.collector" Version="6.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>

View file

@ -14,23 +14,23 @@
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.2">
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.6.6" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="xunit" Version="2.7.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.7">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PackageReference Include="coverlet.collector" Version="6.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.3" />
</ItemGroup>
<ItemGroup>

View file

@ -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;

View file

@ -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";

View file

@ -10,27 +10,27 @@
<ItemGroup>
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="GitInfo" Version="3.3.3">
<PackageReference Include="GitInfo" Version="3.3.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Pfim" Version="0.11.2" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.3" />
<PackageReference Include="Discord.Net.Webhook" Version="3.13.0" />
<PackageReference Include="Discord.Net.Webhook" Version="3.14.1" />
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="8.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.2">
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="8.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.0" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
<PackageReference Include="Redis.OM" Version="0.6.1" />
<PackageReference Include="SharpZipLib" Version="1.4.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="YamlDotNet" Version="15.1.0" />
<PackageReference Include="BouncyCastle.Cryptography" Version="2.2.1" />
<PackageReference Include="DistributedLock.MySql" Version="1.0.1" />
<PackageReference Include="YamlDotNet" Version="15.1.2" />
<PackageReference Include="BouncyCastle.Cryptography" Version="2.3.0" />
<PackageReference Include="DistributedLock.MySql" Version="1.0.2" />
</ItemGroup>
<ItemGroup>