diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 760f6172..7217a983 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "dotnet-ef": { - "version": "7.0.13", + "version": "8.0.6", "commands": [ "dotnet-ef" ] diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..5f14b721 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Normalize shell scripts to have LF line endings +*.sh text eol=lf \ No newline at end of file diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 00000000..f0536400 --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -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 / + 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} diff --git a/ProjectLighthouse.Localization/BaseLayout.lang-ar-SA.resx b/ProjectLighthouse.Localization/BaseLayout.lang-ar-SA.resx index 93d2af66..840e788a 100644 --- a/ProjectLighthouse.Localization/BaseLayout.lang-ar-SA.resx +++ b/ProjectLighthouse.Localization/BaseLayout.lang-ar-SA.resx @@ -80,4 +80,10 @@ If not, please publish the source code somewhere accessible to your users. + + Read-Only Mode + + + 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. + \ No newline at end of file diff --git a/ProjectLighthouse.Localization/BaseLayout.lang-da-DK.resx b/ProjectLighthouse.Localization/BaseLayout.lang-da-DK.resx index e09dd9af..288b7fb7 100644 --- a/ProjectLighthouse.Localization/BaseLayout.lang-da-DK.resx +++ b/ProjectLighthouse.Localization/BaseLayout.lang-da-DK.resx @@ -80,4 +80,10 @@ Hvis ikke, bedes du publicere kildekoden et eller andet sted, der er tilgængeligt for dine brugere. + + Read-Only Mode + + + 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. + \ No newline at end of file diff --git a/ProjectLighthouse.Localization/BaseLayout.lang-de-DE.resx b/ProjectLighthouse.Localization/BaseLayout.lang-de-DE.resx index 23a346fc..c440b48b 100644 --- a/ProjectLighthouse.Localization/BaseLayout.lang-de-DE.resx +++ b/ProjectLighthouse.Localization/BaseLayout.lang-de-DE.resx @@ -80,4 +80,10 @@ Falls nicht, veröffentliche den Quellcode irgendwo, wo die Nutzer deiner Instanz darauf zugreifen können. + + Read-Only Mode + + + 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. + \ No newline at end of file diff --git a/ProjectLighthouse.Localization/BaseLayout.lang-en-PT.resx b/ProjectLighthouse.Localization/BaseLayout.lang-en-PT.resx index 5e95e70d..4b1ed6f2 100644 --- a/ProjectLighthouse.Localization/BaseLayout.lang-en-PT.resx +++ b/ProjectLighthouse.Localization/BaseLayout.lang-en-PT.resx @@ -80,4 +80,10 @@ If not, please publish the source code somewhere accessible to yer users. + + Read-Only Mode + + + 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. + \ No newline at end of file diff --git a/ProjectLighthouse.Localization/BaseLayout.lang-eo-UY.resx b/ProjectLighthouse.Localization/BaseLayout.lang-eo-UY.resx index 7f915877..33a44d35 100644 --- a/ProjectLighthouse.Localization/BaseLayout.lang-eo-UY.resx +++ b/ProjectLighthouse.Localization/BaseLayout.lang-eo-UY.resx @@ -80,4 +80,10 @@ If not, please publish the source code somewhere accessible to your users. + + Read-Only Mode + + + 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. + \ No newline at end of file diff --git a/ProjectLighthouse.Localization/BaseLayout.lang-es-ES.resx b/ProjectLighthouse.Localization/BaseLayout.lang-es-ES.resx index 605e6c1c..9e8054d3 100644 --- a/ProjectLighthouse.Localization/BaseLayout.lang-es-ES.resx +++ b/ProjectLighthouse.Localization/BaseLayout.lang-es-ES.resx @@ -80,4 +80,10 @@ Si no es así, por favor publique el código fuente en algún lugar accesible para sus usuarios. + + Read-Only Mode + + + 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. + \ No newline at end of file diff --git a/ProjectLighthouse.Localization/BaseLayout.lang-es-MX.resx b/ProjectLighthouse.Localization/BaseLayout.lang-es-MX.resx index 4a9f0504..e7ab8b50 100644 --- a/ProjectLighthouse.Localization/BaseLayout.lang-es-MX.resx +++ b/ProjectLighthouse.Localization/BaseLayout.lang-es-MX.resx @@ -80,4 +80,10 @@ De lo contrario, publique el código fuente en algún lugar accesible para sus usuarios. + + Read-Only Mode + + + 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. + \ No newline at end of file diff --git a/ProjectLighthouse.Localization/BaseLayout.lang-et-EE.resx b/ProjectLighthouse.Localization/BaseLayout.lang-et-EE.resx index e8afb535..018d7688 100644 --- a/ProjectLighthouse.Localization/BaseLayout.lang-et-EE.resx +++ b/ProjectLighthouse.Localization/BaseLayout.lang-et-EE.resx @@ -80,4 +80,10 @@ If not, please publish the source code somewhere accessible to your users. + + Read-Only Mode + + + 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. + \ No newline at end of file diff --git a/ProjectLighthouse.Localization/BaseLayout.lang-fi-FI.resx b/ProjectLighthouse.Localization/BaseLayout.lang-fi-FI.resx index 3f0769c7..010bee82 100644 --- a/ProjectLighthouse.Localization/BaseLayout.lang-fi-FI.resx +++ b/ProjectLighthouse.Localization/BaseLayout.lang-fi-FI.resx @@ -80,4 +80,10 @@ Jos ei, julkaise lähdekoodisi jonnekin, jossa se on käyttäjillesi saatavissa. + + Read-Only Mode + + + 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. + \ No newline at end of file diff --git a/ProjectLighthouse.Localization/BaseLayout.lang-fil-PH.resx b/ProjectLighthouse.Localization/BaseLayout.lang-fil-PH.resx index eeca3734..1f6d2f80 100644 --- a/ProjectLighthouse.Localization/BaseLayout.lang-fil-PH.resx +++ b/ProjectLighthouse.Localization/BaseLayout.lang-fil-PH.resx @@ -80,4 +80,10 @@ Kung hindi, mangyaring ilathala ang source code sa isang lugar na maaring ma-access ng iyong mga user. + + Read-Only Mode + + + 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. + \ No newline at end of file diff --git a/ProjectLighthouse.Localization/BaseLayout.lang-fr-FR.resx b/ProjectLighthouse.Localization/BaseLayout.lang-fr-FR.resx index 5799bbab..b6a13ce4 100644 --- a/ProjectLighthouse.Localization/BaseLayout.lang-fr-FR.resx +++ b/ProjectLighthouse.Localization/BaseLayout.lang-fr-FR.resx @@ -80,4 +80,10 @@ Dans le cas contraire, veuillez publier le code source dans un endroit accessible à vos utilisateurs. + + Read-Only Mode + + + 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. + \ No newline at end of file diff --git a/ProjectLighthouse.Localization/BaseLayout.lang-ga-IE.resx b/ProjectLighthouse.Localization/BaseLayout.lang-ga-IE.resx index e9c979f1..8d3faadf 100644 --- a/ProjectLighthouse.Localization/BaseLayout.lang-ga-IE.resx +++ b/ProjectLighthouse.Localization/BaseLayout.lang-ga-IE.resx @@ -80,4 +80,10 @@ If not, please publish the source code somewhere accessible to your users. + + Read-Only Mode + + + 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. + \ No newline at end of file diff --git a/ProjectLighthouse.Localization/BaseLayout.lang-he-IL.resx b/ProjectLighthouse.Localization/BaseLayout.lang-he-IL.resx index b8181267..4f227b17 100644 --- a/ProjectLighthouse.Localization/BaseLayout.lang-he-IL.resx +++ b/ProjectLighthouse.Localization/BaseLayout.lang-he-IL.resx @@ -80,4 +80,10 @@ If not, please publish the source code somewhere accessible to your users. + + Read-Only Mode + + + 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. + \ No newline at end of file diff --git a/ProjectLighthouse.Localization/BaseLayout.lang-hi-IN.resx b/ProjectLighthouse.Localization/BaseLayout.lang-hi-IN.resx index e8afb535..018d7688 100644 --- a/ProjectLighthouse.Localization/BaseLayout.lang-hi-IN.resx +++ b/ProjectLighthouse.Localization/BaseLayout.lang-hi-IN.resx @@ -80,4 +80,10 @@ If not, please publish the source code somewhere accessible to your users. + + Read-Only Mode + + + 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. + \ No newline at end of file diff --git a/ProjectLighthouse.Localization/BaseLayout.lang-id-ID.resx b/ProjectLighthouse.Localization/BaseLayout.lang-id-ID.resx index e8afb535..018d7688 100644 --- a/ProjectLighthouse.Localization/BaseLayout.lang-id-ID.resx +++ b/ProjectLighthouse.Localization/BaseLayout.lang-id-ID.resx @@ -80,4 +80,10 @@ If not, please publish the source code somewhere accessible to your users. + + Read-Only Mode + + + 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. + \ No newline at end of file diff --git a/ProjectLighthouse.Localization/BaseLayout.lang-it-IT.resx b/ProjectLighthouse.Localization/BaseLayout.lang-it-IT.resx index d0e7dc78..1bf4d272 100644 --- a/ProjectLighthouse.Localization/BaseLayout.lang-it-IT.resx +++ b/ProjectLighthouse.Localization/BaseLayout.lang-it-IT.resx @@ -80,4 +80,10 @@ In caso contrario, si prega di pubblicare il codice sorgente da qualche parte accessibile ai propri utenti. + + Read-Only Mode + + + 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. + \ No newline at end of file diff --git a/ProjectLighthouse.Localization/BaseLayout.lang-ja-JP.resx b/ProjectLighthouse.Localization/BaseLayout.lang-ja-JP.resx index 87a8f042..bd34f75a 100644 --- a/ProjectLighthouse.Localization/BaseLayout.lang-ja-JP.resx +++ b/ProjectLighthouse.Localization/BaseLayout.lang-ja-JP.resx @@ -80,4 +80,10 @@ If not, please publish the source code somewhere accessible to your users. + + Read-Only Mode + + + 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. + \ No newline at end of file diff --git a/ProjectLighthouse.Localization/BaseLayout.lang-nl-NL.resx b/ProjectLighthouse.Localization/BaseLayout.lang-nl-NL.resx index e8afb535..018d7688 100644 --- a/ProjectLighthouse.Localization/BaseLayout.lang-nl-NL.resx +++ b/ProjectLighthouse.Localization/BaseLayout.lang-nl-NL.resx @@ -80,4 +80,10 @@ If not, please publish the source code somewhere accessible to your users. + + Read-Only Mode + + + 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. + \ No newline at end of file diff --git a/ProjectLighthouse.Localization/BaseLayout.lang-no-NO.resx b/ProjectLighthouse.Localization/BaseLayout.lang-no-NO.resx index 2667ba27..7498eea4 100644 --- a/ProjectLighthouse.Localization/BaseLayout.lang-no-NO.resx +++ b/ProjectLighthouse.Localization/BaseLayout.lang-no-NO.resx @@ -80,4 +80,10 @@ Hvis ikke, vennligst publiser kildekoden et sted som er tilgjengelig for brukerne dine + + Read-Only Mode + + + 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. + \ No newline at end of file diff --git a/ProjectLighthouse.Localization/BaseLayout.lang-pl-PL.resx b/ProjectLighthouse.Localization/BaseLayout.lang-pl-PL.resx index 3f3c3967..165ad1ea 100644 --- a/ProjectLighthouse.Localization/BaseLayout.lang-pl-PL.resx +++ b/ProjectLighthouse.Localization/BaseLayout.lang-pl-PL.resx @@ -80,4 +80,10 @@ Jeśli nie, proszę opublikować kod źródłowy dostępny dla użytkowników. + + Read-Only Mode + + + 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. + \ No newline at end of file diff --git a/ProjectLighthouse.Localization/BaseLayout.lang-pt-PT.resx b/ProjectLighthouse.Localization/BaseLayout.lang-pt-PT.resx index 542aaefe..acd98ae6 100644 --- a/ProjectLighthouse.Localization/BaseLayout.lang-pt-PT.resx +++ b/ProjectLighthouse.Localization/BaseLayout.lang-pt-PT.resx @@ -80,4 +80,10 @@ Se não for o caso, publique o código-fonte em um local acessível aos seus utilizadores. + + Read-Only Mode + + + 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. + \ No newline at end of file diff --git a/ProjectLighthouse.Localization/BaseLayout.lang-ru-RU.resx b/ProjectLighthouse.Localization/BaseLayout.lang-ru-RU.resx index 6885eef7..af911178 100644 --- a/ProjectLighthouse.Localization/BaseLayout.lang-ru-RU.resx +++ b/ProjectLighthouse.Localization/BaseLayout.lang-ru-RU.resx @@ -80,4 +80,10 @@ Если нет, пожалуйста, опубликуйте исходный код в доступном для ваших пользователей месте. + + Read-Only Mode + + + 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. + \ No newline at end of file diff --git a/ProjectLighthouse.Localization/BaseLayout.lang-sv-SE.resx b/ProjectLighthouse.Localization/BaseLayout.lang-sv-SE.resx index 5ea5b420..335728b0 100644 --- a/ProjectLighthouse.Localization/BaseLayout.lang-sv-SE.resx +++ b/ProjectLighthouse.Localization/BaseLayout.lang-sv-SE.resx @@ -80,4 +80,10 @@ Om inte, vänligen publicera källkoden någonstans tillgängligt för dina användare. + + Read-Only Mode + + + 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. + \ No newline at end of file diff --git a/ProjectLighthouse.Localization/BaseLayout.lang-th-TH.resx b/ProjectLighthouse.Localization/BaseLayout.lang-th-TH.resx index e8afb535..018d7688 100644 --- a/ProjectLighthouse.Localization/BaseLayout.lang-th-TH.resx +++ b/ProjectLighthouse.Localization/BaseLayout.lang-th-TH.resx @@ -80,4 +80,10 @@ If not, please publish the source code somewhere accessible to your users. + + Read-Only Mode + + + 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. + \ No newline at end of file diff --git a/ProjectLighthouse.Localization/BaseLayout.lang-tr-TR.resx b/ProjectLighthouse.Localization/BaseLayout.lang-tr-TR.resx index dad9985b..6fcea799 100644 --- a/ProjectLighthouse.Localization/BaseLayout.lang-tr-TR.resx +++ b/ProjectLighthouse.Localization/BaseLayout.lang-tr-TR.resx @@ -80,4 +80,10 @@ Eğer değilse, lütfen kaynak kodunu kullanıcılarınızın erişebileceği bir yerde yayınlayın. + + Read-Only Mode + + + 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. + \ No newline at end of file diff --git a/ProjectLighthouse.Localization/BaseLayout.lang-uk-UA.resx b/ProjectLighthouse.Localization/BaseLayout.lang-uk-UA.resx index 393240d3..02d46642 100644 --- a/ProjectLighthouse.Localization/BaseLayout.lang-uk-UA.resx +++ b/ProjectLighthouse.Localization/BaseLayout.lang-uk-UA.resx @@ -80,4 +80,10 @@ Якщо ні, будь ласка, опублікуйте вихідний код у місці, доступному для ваших користувачів. + + Read-Only Mode + + + 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. + \ No newline at end of file diff --git a/ProjectLighthouse.Localization/BaseLayout.lang-zh-CN.resx b/ProjectLighthouse.Localization/BaseLayout.lang-zh-CN.resx index b93490ef..5911daf5 100644 --- a/ProjectLighthouse.Localization/BaseLayout.lang-zh-CN.resx +++ b/ProjectLighthouse.Localization/BaseLayout.lang-zh-CN.resx @@ -80,4 +80,10 @@ If not, please publish the source code somewhere accessible to your users. + + Read-Only Mode + + + 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. + \ No newline at end of file diff --git a/ProjectLighthouse.Localization/BaseLayout.lang-zh-TW.resx b/ProjectLighthouse.Localization/BaseLayout.lang-zh-TW.resx index 39f48810..865dd50a 100644 --- a/ProjectLighthouse.Localization/BaseLayout.lang-zh-TW.resx +++ b/ProjectLighthouse.Localization/BaseLayout.lang-zh-TW.resx @@ -80,4 +80,10 @@ If not, please publish the source code somewhere accessible to your users. + + Read-Only Mode + + + 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. + \ No newline at end of file diff --git a/ProjectLighthouse.Localization/BaseLayout.resx b/ProjectLighthouse.Localization/BaseLayout.resx index 6cd9c681..65d1229f 100644 --- a/ProjectLighthouse.Localization/BaseLayout.resx +++ b/ProjectLighthouse.Localization/BaseLayout.resx @@ -87,4 +87,10 @@ If not, please publish the source code somewhere accessible to your users. + + Read-Only Mode + + + 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. + \ No newline at end of file diff --git a/ProjectLighthouse.Localization/StringLists/BaseLayoutStrings.cs b/ProjectLighthouse.Localization/StringLists/BaseLayoutStrings.cs index 64f39021..f46cb4e6 100644 --- a/ProjectLighthouse.Localization/StringLists/BaseLayoutStrings.cs +++ b/ProjectLighthouse.Localization/StringLists/BaseLayoutStrings.cs @@ -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); } \ No newline at end of file diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs index dc334fd4..1f57fa29 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs @@ -114,6 +114,9 @@ public class CommentController : GameController { GameTokenEntity token = this.GetToken(); + // Deny request if in read-only mode + if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest(); + GameComment? comment = await this.DeserializeBody(); if (comment?.Message == null) return this.BadRequest(); @@ -154,6 +157,9 @@ public class CommentController : GameController { 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); diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Login/LoginController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Login/LoginController.cs index c775a169..dc893062 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Login/LoginController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Login/LoginController.cs @@ -71,7 +71,7 @@ public class LoginController : ControllerBase switch (npTicket.Platform) { case Platform.RPCS3: - user = await this.database.Users.FirstOrDefaultAsync(u => u.LinkedRpcnId == npTicket.UserId); + user = await this.database.Users.FirstOrDefaultAsync(u => u.LinkedRpcnId == npTicket.UserId); break; case Platform.PS3: case Platform.Vita: @@ -87,7 +87,7 @@ public class LoginController : ControllerBase // If this user id hasn't been linked to any accounts if (user == null) { - // Check if there is an account with that username already + // Check if there is an account with that username already UserEntity? targetUsername = await this.database.Users.FirstOrDefaultAsync(u => u.Username == npTicket.Username); if (targetUsername != null) { @@ -174,7 +174,7 @@ public class LoginController : ControllerBase } GameTokenEntity? token = await this.database.GameTokens.Include(t => t.User) - .FirstOrDefaultAsync(t => t.UserLocation == ipAddress && t.User.Username == npTicket.Username && t.TicketHash == npTicket.TicketHash); + .FirstOrDefaultAsync(t => t.User.Username == npTicket.Username && t.TicketHash == npTicket.TicketHash); if (token != null) { diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Matching/MatchController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Matching/MatchController.cs index 7b6b8e0b..3bedbfbf 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Matching/MatchController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Matching/MatchController.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using LBPUnion.ProjectLighthouse.Configuration; using LBPUnion.ProjectLighthouse.Database; using LBPUnion.ProjectLighthouse.Extensions; using LBPUnion.ProjectLighthouse.Helpers; @@ -37,6 +38,11 @@ public class MatchController : GameController UserEntity? user = await this.database.UserFromGameToken(token); if (user == null) return this.Forbid(); + await LastContactHelper.SetLastContact(this.database, user, token.GameVersion, token.Platform); + + // Do not allow matchmaking if it has been disabled + if (!ServerConfiguration.Instance.Matchmaking.MatchmakingEnabled) return this.BadRequest(); + #region Parse match data // Example POST /match: [UpdateMyPlayerData,["Player":"FireGamer9872"]] @@ -70,15 +76,12 @@ public class MatchController : GameController #endregion - await LastContactHelper.SetLastContact(this.database, user, token.GameVersion, token.Platform); - #region Process match data switch (matchData) { case UpdateMyPlayerData playerData: { - MatchHelper.SetUserLocation(user.UserId, token.UserLocation); Room? room = RoomHelper.FindRoomByUser(user.UserId, token.GameVersion, token.Platform, true); if (playerData.RoomState != null) @@ -86,19 +89,13 @@ public class MatchController : GameController room.State = (RoomState)playerData.RoomState; break; } - // Check how many people are online in release builds, disabled for debug for ..well debugging. - #if DEBUG case FindBestRoom diveInData: - #else - case FindBestRoom diveInData when MatchHelper.UserLocations.Count > 1: - #endif { FindBestRoomResponse? response = RoomHelper.FindBestRoom(this.database, user, token.GameVersion, diveInData.RoomSlot, - token.Platform, - token.UserLocation); + token.Platform); if (response == null) return this.NotFound(); @@ -108,7 +105,7 @@ public class MatchController : GameController return this.Ok($"[{{\"StatusCode\":200}},{serialized}]"); } - case CreateRoom createRoom when !MatchHelper.UserLocations.IsEmpty: + case CreateRoom createRoom: { List users = new(); foreach (string playerUsername in createRoom.Players) diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs index d8526345..c0f680f2 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs @@ -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.Servers.GameServer.Types; @@ -58,10 +60,14 @@ along with this program. If not, see ."; 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" + - $"token.UserLocation: {token.UserLocation}\n" + $"token.GameVersion: {token.GameVersion}\n" + $"token.TicketHash: {token.TicketHash}\n" + $"token.ExpiresAt: {token.ExpiresAt.ToString()}\n" + diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Resources/PhotosController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Resources/PhotosController.cs index a1ce7f92..59cabb00 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Resources/PhotosController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Resources/PhotosController.cs @@ -32,6 +32,9 @@ public class PhotosController : GameController { 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(); @@ -85,7 +88,7 @@ public class PhotosController : GameController 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); diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Resources/ResourcesController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Resources/ResourcesController.cs index e024ffeb..da4748bd 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Resources/ResourcesController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Resources/ResourcesController.cs @@ -1,4 +1,5 @@ using System.Text; +using LBPUnion.ProjectLighthouse.Configuration; using LBPUnion.ProjectLighthouse.Extensions; using LBPUnion.ProjectLighthouse.Files; using LBPUnion.ProjectLighthouse.Logging; @@ -54,10 +55,14 @@ public class ResourcesController : GameController 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); diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/PublishController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/PublishController.cs index 27f06ae0..41d93c53 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/PublishController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/PublishController.cs @@ -44,6 +44,9 @@ public class PublishController : GameController 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(); if (slot == null) { @@ -117,6 +120,9 @@ public class PublishController : GameController 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(); if (slot == null) @@ -336,6 +342,9 @@ public class PublishController : GameController { 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(); diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ReviewController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ReviewController.cs index 857e2acd..7f2e14a1 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ReviewController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ReviewController.cs @@ -1,3 +1,5 @@ +#nullable enable +using LBPUnion.ProjectLighthouse.Configuration; using LBPUnion.ProjectLighthouse.Database; using LBPUnion.ProjectLighthouse.Extensions; using LBPUnion.ProjectLighthouse.Helpers; @@ -87,6 +89,9 @@ public class ReviewController : GameController { GameTokenEntity token = this.GetToken(); + // Deny request if in read-only mode + if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest(); + GameReview? newReview = await this.DeserializeBody(); if (newReview == null) return this.BadRequest(); @@ -110,7 +115,7 @@ public class ReviewController : GameController } 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; @@ -148,6 +153,13 @@ public class ReviewController : GameController List reviews = (await this.database.Reviews .Where(r => r.SlotId == slotId) + .Select(r => new + { + Review = r, + SlotVersion = r.Slot!.GameVersion, + }) + .Where(a => a.SlotVersion <= token.GameVersion) + .Select(a => a.Review) .OrderByDescending(r => r.ThumbsUp - r.ThumbsDown) .ThenByDescending(r => r.Timestamp) .ApplyPagination(pageData) @@ -169,6 +181,13 @@ public class ReviewController : GameController List reviews = (await this.database.Reviews .Where(r => r.ReviewerId == targetUserId) + .Select(r => new + { + Review = r, + SlotVersion = r.Slot!.GameVersion, + }) + .Where(a => a.SlotVersion <= token.GameVersion) + .Select(a => a.Review) .OrderByDescending(r => r.Timestamp) .ApplyPagination(pageData) .ToListAsync()).ToSerializableList(r => GameReview.CreateFromEntity(r, token)); @@ -234,6 +253,9 @@ public class ReviewController : GameController { 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(); diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/UserController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/UserController.cs index 0861a38d..f7ecd94a 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/UserController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/UserController.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using LBPUnion.ProjectLighthouse.Configuration; using LBPUnion.ProjectLighthouse.Database; using LBPUnion.ProjectLighthouse.Extensions; using LBPUnion.ProjectLighthouse.Files; @@ -69,6 +70,9 @@ public class UserController : GameController 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; @@ -81,6 +85,9 @@ public class UserController : GameController { 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(); @@ -169,7 +176,7 @@ public class UserController : GameController // 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); + string newPins = string.Join(",", pinJson.ProfilePins.Distinct()); if (string.Equals(currentPins, newPins)) return this.Ok("[{\"StatusCode\":200}]"); diff --git a/ProjectLighthouse.Servers.GameServer/Startup/TokenAuthHandler.cs b/ProjectLighthouse.Servers.GameServer/Startup/TokenAuthHandler.cs index 02760bce..cc97b80f 100644 --- a/ProjectLighthouse.Servers.GameServer/Startup/TokenAuthHandler.cs +++ b/ProjectLighthouse.Servers.GameServer/Startup/TokenAuthHandler.cs @@ -1,6 +1,8 @@ -using System.Security.Claims; +using System.Net; +using System.Security.Claims; using System.Text.Encodings.Web; using LBPUnion.ProjectLighthouse.Database; +using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Types.Entities.Token; using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Logging.Abstractions; @@ -36,10 +38,17 @@ public class TokenAuthHandler : AuthenticationHandler [HttpGet("wipePlanets")] - public async Task WipePlanets([FromRoute] int id) { + public async Task WipePlanets([FromRoute] int id) + { UserEntity? user = this.database.UserFromWebRequest(this.Request); if (user == null || !user.IsModerator) return this.NotFound(); @@ -90,6 +94,78 @@ public class AdminUserController : ControllerBase return this.Redirect($"/user/{targetedUser.UserId}"); } + + /// + /// Deletes every comment by the user. Useful in case of mass spam + /// + [HttpGet("wipeComments")] + public async Task WipeComments([FromRoute] int id) + { + UserEntity? user = this.database.UserFromWebRequest(this.Request); + if (user == null || !user.IsModerator) return this.NotFound(); + + UserEntity? targetedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id); + if (targetedUser == null) return this.NotFound(); + + // Find every comment by the user, then set the deletion info on them + await this.database.Comments.Where(c => c.PosterUserId == targetedUser.UserId) + .ExecuteUpdateAsync(s => + s.SetProperty(c => c.Deleted, true) + .SetProperty(c => c.DeletedBy, user.Username) + .SetProperty(c => c.DeletedType, "moderator")); + Logger.Success($"Deleted comments for {targetedUser.Username} (id:{targetedUser.UserId})", LogArea.Admin); + + await this.database.SendNotification(targetedUser.UserId, + "Your comments have been deleted by a moderator."); + + return this.Redirect($"/user/{targetedUser.UserId}"); + } + + /// + /// Deletes every score from the user. Useful in the case where a user cheated a ton of scores + /// + [HttpGet("wipeScores")] + public async Task WipeScores([FromRoute] int id) + { + UserEntity? user = this.database.UserFromWebRequest(this.Request); + if (user == null || !user.IsModerator) return this.NotFound(); + + UserEntity? targetedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id); + if (targetedUser == null) return this.NotFound(); + + // Find and delete every score uploaded by the target user + await this.database.Scores.Where(c => c.UserId == targetedUser.UserId).ExecuteDeleteAsync(); + Logger.Success($"Deleted scores for {targetedUser.Username} (id:{targetedUser.UserId})", LogArea.Admin); + + await this.database.SendNotification(targetedUser.UserId, "Your scores have been deleted by a moderator."); + + return this.Redirect($"/user/{targetedUser.UserId}"); + } + + /// + /// Forces the email verification of a user. + /// + [HttpGet("forceVerifyEmail")] + public async Task ForceVerifyEmail([FromRoute] int id) + { + UserEntity? user = this.database.UserFromWebRequest(this.Request); + if (user == null || !user.IsModerator) return this.NotFound(); + + UserEntity? targetedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id); + if (targetedUser == null) return this.NotFound(); + if (targetedUser.EmailAddress == null || targetedUser.EmailAddressVerified) return this.NotFound(); + + List tokens = await this.database.EmailVerificationTokens + .Where(t => t.UserId == targetedUser.UserId) + .ToListAsync(); + this.database.EmailVerificationTokens.RemoveRange(tokens); + + targetedUser.EmailAddressVerified = true; + + await this.database.SaveChangesAsync(); + + return this.Redirect($"/user/{targetedUser.UserId}"); + } [HttpPost("/admin/user/{id:int}/setPermissionLevel")] public async Task SetUserPermissionLevel([FromRoute] int id, [FromForm] PermissionLevel role) diff --git a/ProjectLighthouse.Servers.Website/Controllers/Debug/RoomVisualizerController.cs b/ProjectLighthouse.Servers.Website/Controllers/Debug/RoomVisualizerController.cs index 547e38ca..905a1609 100644 --- a/ProjectLighthouse.Servers.Website/Controllers/Debug/RoomVisualizerController.cs +++ b/ProjectLighthouse.Servers.Website/Controllers/Debug/RoomVisualizerController.cs @@ -1,8 +1,10 @@ using LBPUnion.ProjectLighthouse.Database; +using Microsoft.AspNetCore.Mvc; +#if DEBUG using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Types.Users; -using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +#endif namespace LBPUnion.ProjectLighthouse.Servers.Website.Controllers.Debug; @@ -27,10 +29,6 @@ public class RoomVisualizerController : ControllerBase List users = await this.database.Users.OrderByDescending(_ => EF.Functions.Random()).Take(2).Select(u => u.UserId).ToListAsync(); RoomHelper.CreateRoom(users, GameVersion.LittleBigPlanet2, Platform.PS3); - foreach (int user in users) - { - MatchHelper.SetUserLocation(user, "127.0.0.1"); - } return this.Redirect("/debug/roomVisualizer"); #endif } diff --git a/ProjectLighthouse.Servers.Website/Controllers/SlotPageController.cs b/ProjectLighthouse.Servers.Website/Controllers/SlotPageController.cs index 66eafbc0..7f22b0b6 100644 --- a/ProjectLighthouse.Servers.Website/Controllers/SlotPageController.cs +++ b/ProjectLighthouse.Servers.Website/Controllers/SlotPageController.cs @@ -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); diff --git a/ProjectLighthouse.Servers.Website/Controllers/UserPageController.cs b/ProjectLighthouse.Servers.Website/Controllers/UserPageController.cs index 161bb1a8..afd23458 100644 --- a/ProjectLighthouse.Servers.Website/Controllers/UserPageController.cs +++ b/ProjectLighthouse.Servers.Website/Controllers/UserPageController.cs @@ -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); diff --git a/ProjectLighthouse.Servers.Website/Pages/Debug/RoomVisualizerPage.cshtml b/ProjectLighthouse.Servers.Website/Pages/Debug/RoomVisualizerPage.cshtml index af71ec13..f922d3e8 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Debug/RoomVisualizerPage.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/Debug/RoomVisualizerPage.cshtml @@ -17,15 +17,15 @@ diff --git a/ProjectLighthouse.Servers.Website/Pages/SlotSettingsPage.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/SlotSettingsPage.cshtml.cs index 310386e7..b461d982 100644 --- a/ProjectLighthouse.Servers.Website/Pages/SlotSettingsPage.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/SlotSettingsPage.cshtml.cs @@ -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; } diff --git a/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml b/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml index d675e8c1..98c23fcd 100644 --- a/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml @@ -324,6 +324,18 @@ else Wipe Earth Decorations + + + + Wipe User's Comments + + + + + Wipe User's Scores + @if (!Model.CommentsDisabledByModerator) { @@ -333,6 +345,14 @@ else } + @if (Model.ProfileUser.EmailAddress != null && !Model.ProfileUser.EmailAddressVerified) + { + + + Forcibly Verify Email + + } + @if (Model.User.IsAdmin) {
diff --git a/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml.cs index 64bfb501..05cb5671 100644 --- a/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml.cs @@ -45,7 +45,8 @@ public class UserPage : BaseLayout if (this.ProfileUser == null) return this.NotFound(); string userSlug = this.ProfileUser.GenerateSlug(); - if (slug == null || userSlug != slug) + // Only redirect if there is a valid slug for this user and the current slug doesn't match + if (!string.IsNullOrWhiteSpace(userSlug) && (slug == null || userSlug != slug)) { return this.Redirect($"~/user/{userId}/{userSlug}"); } diff --git a/ProjectLighthouse.Servers.Website/Pages/UserSettingsPage.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/UserSettingsPage.cshtml.cs index 066ca973..85b0c242 100644 --- a/ProjectLighthouse.Servers.Website/Pages/UserSettingsPage.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/UserSettingsPage.cshtml.cs @@ -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; diff --git a/ProjectLighthouse.Servers.Website/ProjectLighthouse.Servers.Website.csproj b/ProjectLighthouse.Servers.Website/ProjectLighthouse.Servers.Website.csproj index 9749d85c..7768663f 100644 --- a/ProjectLighthouse.Servers.Website/ProjectLighthouse.Servers.Website.csproj +++ b/ProjectLighthouse.Servers.Website/ProjectLighthouse.Servers.Website.csproj @@ -11,6 +11,6 @@ - + diff --git a/ProjectLighthouse.Tests.GameApiTests/Integration/DatabaseTests.cs b/ProjectLighthouse.Tests.GameApiTests/Integration/DatabaseTests.cs index cc8ddc27..fb87b618 100644 --- a/ProjectLighthouse.Tests.GameApiTests/Integration/DatabaseTests.cs +++ b/ProjectLighthouse.Tests.GameApiTests/Integration/DatabaseTests.cs @@ -20,8 +20,8 @@ public class DatabaseTests : LighthouseServerTest int rand = new Random().Next(); - UserEntity userA = await database.CreateUser("unitTestUser" + rand, CryptoHelper.GenerateAuthToken()); - UserEntity userB = await database.CreateUser("unitTestUser" + rand, CryptoHelper.GenerateAuthToken()); + UserEntity userA = await database.CreateUser("unitTestUser" + rand, CryptoHelper.BCryptHash(CryptoHelper.GenerateAuthToken())); + UserEntity userB = await database.CreateUser("unitTestUser" + rand, CryptoHelper.BCryptHash(CryptoHelper.GenerateAuthToken())); Assert.NotNull(userA); Assert.NotNull(userB); diff --git a/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj b/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj index 1e6cabdb..ef720919 100644 --- a/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj +++ b/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj @@ -9,18 +9,18 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/ReviewControllerTests.cs b/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/ReviewControllerTests.cs new file mode 100644 index 00000000..d6eb94f8 --- /dev/null +++ b/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/ReviewControllerTests.cs @@ -0,0 +1,100 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse.Database; +using LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots; +using LBPUnion.ProjectLighthouse.Tests.Helpers; +using LBPUnion.ProjectLighthouse.Types.Entities.Level; +using LBPUnion.ProjectLighthouse.Types.Entities.Token; +using LBPUnion.ProjectLighthouse.Types.Serialization; +using LBPUnion.ProjectLighthouse.Types.Users; +using Microsoft.AspNetCore.Mvc; +using Xunit; + +namespace ProjectLighthouse.Tests.GameApiTests.Unit.Controllers; + +[Trait("Category", "Unit")] +public class ReviewControllerTests +{ + private static async Task InsertTestData(DatabaseContext database) + { + database.Slots.Add(new SlotEntity + { + SlotId = 1, + CreatorId = 1, + GameVersion = GameVersion.LittleBigPlanet3, + }); + + database.Slots.Add(new SlotEntity + { + SlotId = 2, + CreatorId = 1, + GameVersion = GameVersion.LittleBigPlanet2, + }); + + database.Reviews.Add(new ReviewEntity + { + ReviewId = 1, + ReviewerId = 1, + SlotId = 1, + }); + + database.Reviews.Add(new ReviewEntity + { + ReviewId = 2, + ReviewerId = 1, + SlotId = 2, + }); + await database.SaveChangesAsync(); + } + + [Theory] + [InlineData(GameVersion.LittleBigPlanet2, 1)] + [InlineData(GameVersion.LittleBigPlanet3, 2)] + public async Task ReviewsBy_ShouldNotList_HigherGameVersions(GameVersion version, int expected) + { + GameTokenEntity token = MockHelper.GetUnitTestToken(); + token.GameVersion = version; + DatabaseContext database = await MockHelper.GetTestDatabase(new List + { + token, + }); + + await InsertTestData(database); + + ReviewController controller = new(database); + controller.SetupTestController(token); + + IActionResult response = await controller.ReviewsBy("unittest"); + ReviewResponse review = response.CastTo(); + + Assert.Equal(expected, review.Reviews.Count); + Assert.True(review.Reviews.All(r => database.Slots.FirstOrDefault(s => s.SlotId == r.Slot.SlotId)?.GameVersion <= version)); + } + + [Theory] + [InlineData(GameVersion.LittleBigPlanet2, 2, 1)] + [InlineData(GameVersion.LittleBigPlanet2, 1, 0)] + [InlineData(GameVersion.LittleBigPlanet3, 2, 1)] + [InlineData(GameVersion.LittleBigPlanet3, 1, 1)] + public async Task ReviewsFor_ShouldNotList_HigherGameVersions(GameVersion version, int slotId, int expected) + { + GameTokenEntity token = MockHelper.GetUnitTestToken(); + token.GameVersion = version; + DatabaseContext database = await MockHelper.GetTestDatabase(new List + { + token, + }); + + await InsertTestData(database); + + ReviewController controller = new(database); + controller.SetupTestController(token); + + IActionResult response = await controller.ReviewsFor(slotId); + ReviewResponse review = response.CastTo(); + + Assert.Equal(expected, review.Reviews.Count); + Assert.True(review.Reviews.All(r => database.Slots.FirstOrDefault(s => s.SlotId == r.Slot.SlotId)?.GameVersion <= version)); + } +} \ No newline at end of file diff --git a/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/UserControllerTests.cs b/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/UserControllerTests.cs index e7228b2c..3347db1e 100644 --- a/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/UserControllerTests.cs +++ b/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/UserControllerTests.cs @@ -177,4 +177,29 @@ public class UserControllerTests Assert.Equal(expectedPins, dbMock.Users.First().Pins); Assert.Equal(expectedResponse, pinsResponse); } + + [Fact] + public async Task UpdateMyPins_ShouldRemove_DuplicatePins() + { + UserEntity entity = MockHelper.GetUnitTestUser(); + entity.Pins = "1234"; + List users = new() + { + entity, + }; + await using DatabaseContext dbMock = await MockHelper.GetTestDatabase(users); + + UserController userController = new(dbMock); + userController.SetupTestController("{\"profile_pins\": [1234, 1234]}"); + + const string expectedPins = "1234"; + const string expectedResponse = "[{\"StatusCode\":200}]"; + + IActionResult result = await userController.UpdateMyPins(); + + string pinsResponse = result.CastTo(); + + Assert.Equal(expectedPins, dbMock.Users.First().Pins); + Assert.Equal(expectedResponse, pinsResponse); + } } \ No newline at end of file diff --git a/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj b/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj index 69bd3e2b..c5b336f5 100644 --- a/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj +++ b/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj @@ -9,20 +9,20 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/ProjectLighthouse.Tests/Helpers/MockHelper.cs b/ProjectLighthouse.Tests/Helpers/MockHelper.cs index 063bc552..64dc10c3 100644 --- a/ProjectLighthouse.Tests/Helpers/MockHelper.cs +++ b/ProjectLighthouse.Tests/Helpers/MockHelper.cs @@ -37,7 +37,6 @@ public static class MockHelper UserId = 1, ExpiresAt = DateTime.MaxValue, TokenId = 1, - UserLocation = "127.0.0.1", UserToken = "unittest", }; diff --git a/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj b/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj index 4205a75a..4c4199a9 100644 --- a/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj +++ b/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj @@ -14,23 +14,23 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + diff --git a/ProjectLighthouse.sln.DotSettings b/ProjectLighthouse.sln.DotSettings index d3818928..174ff7a3 100644 --- a/ProjectLighthouse.sln.DotSettings +++ b/ProjectLighthouse.sln.DotSettings @@ -1,4 +1,6 @@ - + True False False @@ -96,10 +98,14 @@ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="" Suffix="" Style="aaBb" /></Policy> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Private" Description="Constant fields (private)"><ElementKinds><Kind Name="CONSTANT_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Methods"><ElementKinds><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="" Suffix="" Style="aaBb" /></Policy></Policy> True True True True + True True True True diff --git a/ProjectLighthouse/Configuration/ConfigurationCategories/MatchmakingConfiguration.cs b/ProjectLighthouse/Configuration/ConfigurationCategories/MatchmakingConfiguration.cs new file mode 100644 index 00000000..4e9debac --- /dev/null +++ b/ProjectLighthouse/Configuration/ConfigurationCategories/MatchmakingConfiguration.cs @@ -0,0 +1,6 @@ +namespace LBPUnion.ProjectLighthouse.Configuration.ConfigurationCategories; + +public class MatchmakingConfiguration +{ + public bool MatchmakingEnabled { get; set; } = true; +} \ No newline at end of file diff --git a/ProjectLighthouse/Configuration/ConfigurationCategories/UserGeneratedContentLimitConfiguration.cs b/ProjectLighthouse/Configuration/ConfigurationCategories/UserGeneratedContentLimitConfiguration.cs index 6430b754..f725c278 100644 --- a/ProjectLighthouse/Configuration/ConfigurationCategories/UserGeneratedContentLimitConfiguration.cs +++ b/ProjectLighthouse/Configuration/ConfigurationCategories/UserGeneratedContentLimitConfiguration.cs @@ -11,6 +11,12 @@ public class UserGeneratedContentLimitConfiguration public int PhotosQuota { get; set; } = 500; + /// + /// When enabled, all UGC uploads are disabled. This includes levels, photos, reviews, + /// comments, and certain profile settings. + /// + public bool ReadOnlyMode { get; set; } = false; + public bool ProfileCommentsEnabled { get; set; } = true; public bool LevelCommentsEnabled { get; set; } = true; diff --git a/ProjectLighthouse/Configuration/ServerConfiguration.cs b/ProjectLighthouse/Configuration/ServerConfiguration.cs index d51d4ca2..30c7c89b 100644 --- a/ProjectLighthouse/Configuration/ServerConfiguration.cs +++ b/ProjectLighthouse/Configuration/ServerConfiguration.cs @@ -11,7 +11,7 @@ public class ServerConfiguration : ConfigurationBase // 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; } = 24; + public override int ConfigVersion { get; set; } = 26; public override string ConfigName { get; set; } = "lighthouse.yml"; public string WebsiteListenUrl { get; set; } = "http://localhost:10060"; @@ -35,6 +35,7 @@ public class ServerConfiguration : ConfigurationBase public AuthenticationConfiguration Authentication { get; set; } = new(); public CaptchaConfiguration Captcha { get; set; } = new(); public DigestKeyConfiguration DigestKey { get; set; } = new(); + public MatchmakingConfiguration Matchmaking { get; set; } = new(); public GoogleAnalyticsConfiguration GoogleAnalytics { get; set; } = new(); public MailConfiguration Mail { get; set; } = new(); public UserGeneratedContentLimitConfiguration UserGeneratedContentLimits { get; set; } = new(); diff --git a/ProjectLighthouse/Database/DatabaseContext.Utils.cs b/ProjectLighthouse/Database/DatabaseContext.Utils.cs index 61f1487e..a4a22e96 100644 --- a/ProjectLighthouse/Database/DatabaseContext.Utils.cs +++ b/ProjectLighthouse/Database/DatabaseContext.Utils.cs @@ -67,10 +67,10 @@ public partial class DatabaseContext UserToken = CryptoHelper.GenerateAuthToken(), User = user, UserId = user.UserId, - UserLocation = userLocation, GameVersion = npTicket.GameVersion, Platform = npTicket.Platform, TicketHash = npTicket.TicketHash, + LocationHash = CryptoHelper.Sha256Hash(userLocation), // we can get away with a low expiry here since LBP will just get a new token everytime it gets 403'd ExpiresAt = DateTime.UtcNow + TimeSpan.FromHours(1), }; diff --git a/ProjectLighthouse/Helpers/CryptoHelper.cs b/ProjectLighthouse/Helpers/CryptoHelper.cs index cfa896a2..dc719bf4 100644 --- a/ProjectLighthouse/Helpers/CryptoHelper.cs +++ b/ProjectLighthouse/Helpers/CryptoHelper.cs @@ -9,9 +9,6 @@ namespace LBPUnion.ProjectLighthouse.Helpers; public static class CryptoHelper { - - private static readonly SHA256 sha256 = SHA256.Create(); - /// /// Generates a random SHA256 and BCrypted token /// @@ -19,10 +16,15 @@ public static class CryptoHelper public static string GenerateAuthToken() { byte[] bytes = (byte[])GenerateRandomBytes(256); - return BCryptHash(Sha256Hash(bytes)); } + public static string GenerateUrlToken() + { + byte[] bytes = (byte[])GenerateRandomBytes(256); + return Convert.ToBase64String(Encoding.UTF8.GetBytes(BCryptHash(Sha256Hash(bytes)))); + } + public static string ComputeDigest(string path, string authCookie, byte[] body, string digestKey, bool excludeBody = false) { @@ -156,7 +158,7 @@ public static class CryptoHelper public static string Sha256Hash(string str) => Sha256Hash(Encoding.UTF8.GetBytes(str)); - public static string Sha256Hash(byte[] bytes) => BitConverter.ToString(sha256.ComputeHash(bytes)).Replace("-", "").ToLower(); + public static string Sha256Hash(byte[] bytes) => BitConverter.ToString(SHA256.HashData(bytes)).Replace("-", "").ToLower(); public static string Sha1Hash(byte[] bytes) => BitConverter.ToString(SHA1.HashData(bytes)).Replace("-", ""); diff --git a/ProjectLighthouse/Helpers/EmailHelper.cs b/ProjectLighthouse/Helpers/EmailHelper.cs index 3750f41c..52c69f9d 100644 --- a/ProjectLighthouse/Helpers/EmailHelper.cs +++ b/ProjectLighthouse/Helpers/EmailHelper.cs @@ -52,7 +52,7 @@ public static class SMTPHelper { Created = DateTime.UtcNow, UserId = user.UserId, - ResetToken = CryptoHelper.GenerateAuthToken(), + ResetToken = CryptoHelper.GenerateUrlToken(), }; database.PasswordResetTokens.Add(token); @@ -92,7 +92,7 @@ public static class SMTPHelper { UserId = user.UserId, User = user, - EmailToken = CryptoHelper.GenerateAuthToken(), + EmailToken = CryptoHelper.GenerateUrlToken(), ExpiresAt = DateTime.UtcNow.AddHours(6), }; diff --git a/ProjectLighthouse/Helpers/MatchHelper.cs b/ProjectLighthouse/Helpers/MatchHelper.cs index 9566a744..d6a06977 100644 --- a/ProjectLighthouse/Helpers/MatchHelper.cs +++ b/ProjectLighthouse/Helpers/MatchHelper.cs @@ -11,15 +11,8 @@ namespace LBPUnion.ProjectLighthouse.Helpers; public static partial class MatchHelper { - public static readonly ConcurrentDictionary UserLocations = new(); public static readonly ConcurrentDictionary?> UserRecentlyDivedIn = new(); - public static void SetUserLocation(int userId, string location) - { - if (UserLocations.TryGetValue(userId, out string? _)) UserLocations.TryRemove(userId, out _); - UserLocations.TryAdd(userId, location); - } - public static void AddUserRecentlyDivedIn(int userId, int otherUserId) { if (!UserRecentlyDivedIn.TryGetValue(userId, out List? recentlyDivedIn)) UserRecentlyDivedIn.TryAdd(userId, recentlyDivedIn = new List()); diff --git a/ProjectLighthouse/Helpers/RoomHelper.cs b/ProjectLighthouse/Helpers/RoomHelper.cs index 021e86ec..47d0771c 100644 --- a/ProjectLighthouse/Helpers/RoomHelper.cs +++ b/ProjectLighthouse/Helpers/RoomHelper.cs @@ -1,7 +1,6 @@ #nullable enable using System; using System.Collections.Generic; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; @@ -16,6 +15,9 @@ using LBPUnion.ProjectLighthouse.Types.Logging; using LBPUnion.ProjectLighthouse.Types.Matchmaking; using LBPUnion.ProjectLighthouse.Types.Matchmaking.Rooms; using LBPUnion.ProjectLighthouse.Types.Users; +#if DEBUG +using System.Diagnostics; +#endif namespace LBPUnion.ProjectLighthouse.Helpers; @@ -27,7 +29,7 @@ public static class RoomHelper private static int roomIdIncrement; internal static int RoomIdIncrement => roomIdIncrement++; - public static FindBestRoomResponse? FindBestRoom(DatabaseContext database, UserEntity? user, GameVersion roomVersion, RoomSlot? slot, Platform? platform, string? location) + public static FindBestRoomResponse? FindBestRoom(DatabaseContext database, UserEntity? user, GameVersion roomVersion, RoomSlot? slot, Platform? platform) { if (roomVersion == GameVersion.LittleBigPlanet1 || roomVersion == GameVersion.LittleBigPlanetPSP) { @@ -61,30 +63,10 @@ public static class RoomHelper { if (user != null && MatchHelper.DidUserRecentlyDiveInWith(user.UserId, room.HostId)) continue; - Dictionary relevantUserLocations = new(); - - // Determine if all players in a room have UserLocations stored, also store the relevant userlocations while we're at it - bool allPlayersHaveLocations = room.PlayerIds.All - ( - p => - { - bool gotValue = MatchHelper.UserLocations.TryGetValue(p, out string? value); - - if (gotValue && value != null) relevantUserLocations.TryAdd(p, value); - return gotValue; - } - ); - - // If we don't have all locations then the game won't know how to communicate. Thus, it's not a valid room. - if (!allPlayersHaveLocations) continue; - - // If we got here then it should be a valid room. - FindBestRoomResponse response = new() { RoomId = room.RoomId, Players = new List(), - Locations = new List(), }; foreach (UserEntity player in room.GetPlayers(database)) @@ -97,8 +79,6 @@ public static class RoomHelper User = player, } ); - - response.Locations.Add(relevantUserLocations.GetValueOrDefault(player.UserId)); // Already validated to exist } if (user != null) @@ -111,8 +91,6 @@ public static class RoomHelper } ); - if (location == null) response.Locations.Add(location); - response.Slots = new List> { new() @@ -189,7 +167,7 @@ public static class RoomHelper { StorableList rooms = Rooms; // cache rooms so we dont gen a new one every time List roomsToUpdate = new(); - + #if DEBUG Logger.Debug($"Cleaning up rooms... (took {stopwatch.ElapsedMilliseconds}ms to get lock on {nameof(RoomLock)})", LogArea.Match); #endif @@ -205,7 +183,7 @@ public static class RoomHelper .ToList(); foreach (int player in playersToRemove) room.PlayerIds.Remove(player); - + roomsToUpdate.Add(room); } @@ -248,7 +226,7 @@ public static class RoomHelper int roomCountAfterCleanup = rooms.Count(); // Log the amount of rooms cleaned up. - // If we didnt clean any rooms, it's not useful to log in a + // If we didnt clean any rooms, it's not useful to log in a // production environment but it's still quite useful for debugging. // // So, we handle that case here: diff --git a/ProjectLighthouse/Helpers/VersionHelper.cs b/ProjectLighthouse/Helpers/VersionHelper.cs index 895c36f7..f6c07003 100644 --- a/ProjectLighthouse/Helpers/VersionHelper.cs +++ b/ProjectLighthouse/Helpers/VersionHelper.cs @@ -19,7 +19,6 @@ public static class VersionHelper public static string EnvVer => $"{ServerConfiguration.Instance.Customization.EnvironmentName} {FullRevision}"; public static string FullVersion => $"Project Lighthouse {ServerConfiguration.Instance.Customization.EnvironmentName} {Branch}@{CommitHash} {Build}"; - public static bool IsDirty => ThisAssembly.Git.IsDirty; public static string RepositoryUrl => ThisAssembly.Git.RepositoryUrl; public const string Build = diff --git a/ProjectLighthouse/Migrations/20240317231145_RemoveUserLocationFromGameTokens.cs b/ProjectLighthouse/Migrations/20240317231145_RemoveUserLocationFromGameTokens.cs new file mode 100644 index 00000000..cba74161 --- /dev/null +++ b/ProjectLighthouse/Migrations/20240317231145_RemoveUserLocationFromGameTokens.cs @@ -0,0 +1,30 @@ +using LBPUnion.ProjectLighthouse.Database; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace LBPUnion.ProjectLighthouse.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20240317231145_RemoveUserLocationFromGameTokens")] + public partial class RemoveUserLocationFromGameTokens : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "UserLocation", + table: "GameTokens"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "UserLocation", + table: "GameTokens", + type: "longtext", + nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"); + } + } +} diff --git a/ProjectLighthouse/Migrations/20240625004457_AddLocationHashToGameToken.cs b/ProjectLighthouse/Migrations/20240625004457_AddLocationHashToGameToken.cs new file mode 100644 index 00000000..ff8858ee --- /dev/null +++ b/ProjectLighthouse/Migrations/20240625004457_AddLocationHashToGameToken.cs @@ -0,0 +1,598 @@ +using LBPUnion.ProjectLighthouse.Database; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace LBPUnion.ProjectLighthouse.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20240625004457_AddLocationHashToGameToken")] + public partial class AddLocationHashToGameToken : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "TokenId", + table: "WebTokens", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "AnnouncementId", + table: "WebsiteAnnouncements", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "VisitedLevelId", + table: "VisitedLevels", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "UserId", + table: "Users", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "SlotId", + table: "Slots", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "ScoreId", + table: "Scores", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "ReviewId", + table: "Reviews", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "ReportId", + table: "Reports", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "TokenId", + table: "RegistrationTokens", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "RatedReviewId", + table: "RatedReviews", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "RatedLevelId", + table: "RatedLevels", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "RatingId", + table: "RatedComments", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "QueuedLevelId", + table: "QueuedLevels", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "PlaylistId", + table: "Playlists", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "PlatformLinkAttemptId", + table: "PlatformLinkAttempts", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "PhotoSubjectId", + table: "PhotoSubjects", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "PhotoId", + table: "Photos", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "TokenId", + table: "PasswordResetTokens", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "Notifications", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "HeartedProfileId", + table: "HeartedProfiles", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "HeartedPlaylistId", + table: "HeartedPlaylists", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "HeartedLevelId", + table: "HeartedLevels", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "TicketHash", + table: "GameTokens", + type: "varchar(64)", + maxLength: 64, + nullable: true, + oldClrType: typeof(string), + oldType: "longtext", + oldNullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AlterColumn( + name: "TokenId", + table: "GameTokens", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AddColumn( + name: "LocationHash", + table: "GameTokens", + type: "varchar(64)", + maxLength: 64, + nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AlterColumn( + name: "EmailVerificationTokenId", + table: "EmailVerificationTokens", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "EmailSetTokenId", + table: "EmailSetTokens", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "CategoryId", + table: "CustomCategories", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "CommentId", + table: "Comments", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "CaseId", + table: "Cases", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "BlockedProfileId", + table: "BlockedProfiles", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "APIKeys", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "LocationHash", + table: "GameTokens"); + + migrationBuilder.AlterColumn( + name: "TokenId", + table: "WebTokens", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "AnnouncementId", + table: "WebsiteAnnouncements", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "VisitedLevelId", + table: "VisitedLevels", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "UserId", + table: "Users", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "SlotId", + table: "Slots", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "ScoreId", + table: "Scores", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "ReviewId", + table: "Reviews", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "ReportId", + table: "Reports", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "TokenId", + table: "RegistrationTokens", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "RatedReviewId", + table: "RatedReviews", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "RatedLevelId", + table: "RatedLevels", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "RatingId", + table: "RatedComments", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "QueuedLevelId", + table: "QueuedLevels", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "PlaylistId", + table: "Playlists", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "PlatformLinkAttemptId", + table: "PlatformLinkAttempts", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "PhotoSubjectId", + table: "PhotoSubjects", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "PhotoId", + table: "Photos", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "TokenId", + table: "PasswordResetTokens", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "Notifications", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "HeartedProfileId", + table: "HeartedProfiles", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "HeartedPlaylistId", + table: "HeartedPlaylists", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "HeartedLevelId", + table: "HeartedLevels", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "TicketHash", + table: "GameTokens", + type: "longtext", + nullable: true, + oldClrType: typeof(string), + oldType: "varchar(64)", + oldMaxLength: 64, + oldNullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AlterColumn( + name: "TokenId", + table: "GameTokens", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "EmailVerificationTokenId", + table: "EmailVerificationTokens", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "EmailSetTokenId", + table: "EmailSetTokens", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "CategoryId", + table: "CustomCategories", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "CommentId", + table: "Comments", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "CaseId", + table: "Cases", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "BlockedProfileId", + table: "BlockedProfiles", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "APIKeys", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + } + } +} diff --git a/ProjectLighthouse/Migrations/DatabaseContextModelSnapshot.cs b/ProjectLighthouse/Migrations/DatabaseContextModelSnapshot.cs index 5da0015e..6c801706 100644 --- a/ProjectLighthouse/Migrations/DatabaseContextModelSnapshot.cs +++ b/ProjectLighthouse/Migrations/DatabaseContextModelSnapshot.cs @@ -3,6 +3,7 @@ using System; using LBPUnion.ProjectLighthouse.Database; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; #nullable disable @@ -16,15 +17,19 @@ namespace ProjectLighthouse.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "7.0.13") + .HasAnnotation("ProductVersion", "8.0.6") .HasAnnotation("Relational:MaxIdentifierLength", 64); + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Interaction.HeartedLevelEntity", b => { b.Property("HeartedLevelId") .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("HeartedLevelId")); + b.Property("SlotId") .HasColumnType("int"); @@ -46,6 +51,8 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("HeartedPlaylistId")); + b.Property("PlaylistId") .HasColumnType("int"); @@ -67,6 +74,8 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("HeartedProfileId")); + b.Property("HeartedUserId") .HasColumnType("int"); @@ -88,6 +97,8 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("QueuedLevelId")); + b.Property("SlotId") .HasColumnType("int"); @@ -109,6 +120,8 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RatingId")); + b.Property("CommentId") .HasColumnType("int"); @@ -133,6 +146,8 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RatedLevelId")); + b.Property("Rating") .HasColumnType("int"); @@ -163,6 +178,8 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RatedReviewId")); + b.Property("ReviewId") .HasColumnType("int"); @@ -187,6 +204,8 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("VisitedLevelId")); + b.Property("PlaysLBP1") .HasColumnType("int"); @@ -217,6 +236,8 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CategoryId")); + b.Property("Description") .HasColumnType("longtext"); @@ -243,6 +264,8 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PlaylistId")); + b.Property("CreatorId") .HasColumnType("int"); @@ -271,6 +294,8 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReviewId")); + b.Property("Deleted") .HasColumnType("tinyint(1)"); @@ -318,6 +343,8 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ScoreId")); + b.Property("ChildSlotId") .HasColumnType("int"); @@ -351,6 +378,8 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SlotId")); + b.Property("AuthorLabels") .IsRequired() .HasColumnType("longtext"); @@ -504,6 +533,8 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReportId")); + b.Property("Bounds") .HasColumnType("longtext"); @@ -550,6 +581,8 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CaseId")); + b.Property("AffectedId") .HasColumnType("int"); @@ -604,6 +637,8 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("IsDismissed") .HasColumnType("tinyint(1)"); @@ -629,6 +664,8 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BlockedProfileId")); + b.Property("BlockedUserId") .HasColumnType("int"); @@ -650,6 +687,8 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CommentId")); + b.Property("Deleted") .HasColumnType("tinyint(1)"); @@ -719,6 +758,8 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PhotoId")); + b.Property("CreatorId") .HasColumnType("int"); @@ -759,6 +800,8 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PhotoSubjectId")); + b.Property("Bounds") .HasColumnType("longtext"); @@ -783,6 +826,8 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PlatformLinkAttemptId")); + b.Property("IPAddress") .HasColumnType("longtext"); @@ -811,6 +856,8 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("UserId")); + b.Property("AdminGrantedSlots") .HasColumnType("int"); @@ -916,6 +963,8 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("Created") .HasColumnType("datetime(6)"); @@ -936,6 +985,8 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmailSetTokenId")); + b.Property("EmailToken") .HasColumnType("longtext"); @@ -958,6 +1009,8 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmailVerificationTokenId")); + b.Property("EmailToken") .HasColumnType("longtext"); @@ -980,24 +1033,28 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TokenId")); + b.Property("ExpiresAt") .HasColumnType("datetime(6)"); b.Property("GameVersion") .HasColumnType("int"); + b.Property("LocationHash") + .HasMaxLength(64) + .HasColumnType("varchar(64)"); + b.Property("Platform") .HasColumnType("int"); b.Property("TicketHash") - .HasColumnType("longtext"); + .HasMaxLength(64) + .HasColumnType("varchar(64)"); b.Property("UserId") .HasColumnType("int"); - b.Property("UserLocation") - .HasColumnType("longtext"); - b.Property("UserToken") .HasColumnType("longtext"); @@ -1014,6 +1071,8 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TokenId")); + b.Property("Created") .HasColumnType("datetime(6)"); @@ -1034,6 +1093,8 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TokenId")); + b.Property("Created") .HasColumnType("datetime(6)"); @@ -1054,6 +1115,8 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TokenId")); + b.Property("ExpiresAt") .HasColumnType("datetime(6)"); @@ -1077,6 +1140,8 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AnnouncementId")); + b.Property("Content") .HasColumnType("longtext"); diff --git a/ProjectLighthouse/ProjectLighthouse.csproj b/ProjectLighthouse/ProjectLighthouse.csproj index e56b134a..c538b220 100644 --- a/ProjectLighthouse/ProjectLighthouse.csproj +++ b/ProjectLighthouse/ProjectLighthouse.csproj @@ -10,31 +10,31 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - - - - + + + + - + diff --git a/ProjectLighthouse/StartupTasks.cs b/ProjectLighthouse/StartupTasks.cs index 6777b510..ec1730eb 100644 --- a/ProjectLighthouse/StartupTasks.cs +++ b/ProjectLighthouse/StartupTasks.cs @@ -53,12 +53,6 @@ public static class StartupTasks // Version info depends on ServerConfig Logger.Info($"You are running version {VersionHelper.FullVersion}", LogArea.Startup); - if (VersionHelper.IsDirty) - { - Logger.Warn("This is a modified version of Project Lighthouse. " + - "Please make sure you are properly disclosing the source code to any users who may be using this instance.", - LogArea.Startup); - } Logger.Info("Connecting to the database...", LogArea.Startup); diff --git a/ProjectLighthouse/Tickets/NPTicket.cs b/ProjectLighthouse/Tickets/NPTicket.cs index bb11a93e..69173d7c 100644 --- a/ProjectLighthouse/Tickets/NPTicket.cs +++ b/ProjectLighthouse/Tickets/NPTicket.cs @@ -185,7 +185,7 @@ public class NPTicket } // Used to identify duplicate tickets - npTicket.TicketHash = CryptoHelper.Sha1Hash(data); + npTicket.TicketHash = CryptoHelper.Sha256Hash(data); #if DEBUG Logger.Debug("npTicket data:", LogArea.Login); diff --git a/ProjectLighthouse/Tickets/TicketReader.cs b/ProjectLighthouse/Tickets/TicketReader.cs index ee6b1a70..697a72a1 100644 --- a/ProjectLighthouse/Tickets/TicketReader.cs +++ b/ProjectLighthouse/Tickets/TicketReader.cs @@ -33,6 +33,9 @@ public struct SectionHeader public SectionType Type; public ushort Length; public int Position; + + public override string ToString() => + $"SectionHeader(Type='{this.Type}', Length='{this.Length}', Position='{this.Position}')"; } public class TicketReader : BinaryReader diff --git a/ProjectLighthouse/Types/Entities/Token/GameTokenEntity.cs b/ProjectLighthouse/Types/Entities/Token/GameTokenEntity.cs index 4a5f648d..ab7d3a16 100644 --- a/ProjectLighthouse/Types/Entities/Token/GameTokenEntity.cs +++ b/ProjectLighthouse/Types/Entities/Token/GameTokenEntity.cs @@ -19,13 +19,15 @@ public class GameTokenEntity public string UserToken { get; set; } - public string UserLocation { get; set; } - public GameVersion GameVersion { get; set; } public Platform Platform { get; set; } + [StringLength(64)] public string TicketHash { get; set; } + [StringLength(64)] + public string LocationHash { get; set; } + public DateTime ExpiresAt { get; set; } } \ No newline at end of file diff --git a/ProjectLighthouse/Types/Matchmaking/Rooms/FindBestRoomResponse.cs b/ProjectLighthouse/Types/Matchmaking/Rooms/FindBestRoomResponse.cs index 856a7666..5c90a64a 100644 --- a/ProjectLighthouse/Types/Matchmaking/Rooms/FindBestRoomResponse.cs +++ b/ProjectLighthouse/Types/Matchmaking/Rooms/FindBestRoomResponse.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; namespace LBPUnion.ProjectLighthouse.Types.Matchmaking.Rooms; @@ -14,8 +13,4 @@ public class FindBestRoomResponse [JsonIgnore] public IEnumerable FirstSlot => this.Slots[0]; - - [JsonPropertyName("Location")] - [SuppressMessage("ReSharper", "CollectionNeverQueried.Global")] - public List Locations { get; set; } } \ No newline at end of file diff --git a/ProjectLighthouse/Types/Serialization/GameDeveloperSlot.cs b/ProjectLighthouse/Types/Serialization/GameDeveloperSlot.cs index 3de55ee4..93f8436c 100644 --- a/ProjectLighthouse/Types/Serialization/GameDeveloperSlot.cs +++ b/ProjectLighthouse/Types/Serialization/GameDeveloperSlot.cs @@ -1,6 +1,8 @@ -using System.Linq; +using System; +using System.Linq; using System.Threading.Tasks; using System.Xml.Serialization; +using LBPUnion.ProjectLighthouse.Configuration; using LBPUnion.ProjectLighthouse.Database; using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Types.Entities.Profile; @@ -30,6 +32,13 @@ public class GameDeveloperSlot : SlotBase, INeedsPreparationForSerialization [XmlElement("photoCount")] public int PhotoCount { get; set; } + [XmlElement("commentsEnabled")] + public bool CommentsEnabled + { + get => ServerConfiguration.Instance.UserGeneratedContentLimits.LevelCommentsEnabled; + set => throw new NotSupportedException(); + } + public async Task PrepareSerialization(DatabaseContext database) { if (this.SlotId == 0 || this.InternalSlotId == 0) return;