diff --git a/ProjectLighthouse.Localization/General.resx b/ProjectLighthouse.Localization/General.resx index fc10126d..c92e5f04 100644 --- a/ProjectLighthouse.Localization/General.resx +++ b/ProjectLighthouse.Localization/General.resx @@ -57,4 +57,7 @@ Email + + Announcements + \ No newline at end of file diff --git a/ProjectLighthouse.Localization/StringLists/GeneralStrings.cs b/ProjectLighthouse.Localization/StringLists/GeneralStrings.cs index edc3eb5f..9ba858d6 100644 --- a/ProjectLighthouse.Localization/StringLists/GeneralStrings.cs +++ b/ProjectLighthouse.Localization/StringLists/GeneralStrings.cs @@ -15,6 +15,7 @@ public static class GeneralStrings public static readonly TranslatableString RecentPhotos = create("recent_photos"); public static readonly TranslatableString RecentActivity = create("recent_activity"); public static readonly TranslatableString Soon = create("soon"); + public static readonly TranslatableString Announcements = create("announcements"); private static TranslatableString create(string key) => new(TranslationAreas.General, key); } \ No newline at end of file diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs index 18b245c1..544eb7ad 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs @@ -1,4 +1,5 @@ #nullable enable +using System.Text; using LBPUnion.ProjectLighthouse.Configuration; using LBPUnion.ProjectLighthouse.Database; using LBPUnion.ProjectLighthouse.Extensions; @@ -51,25 +52,22 @@ along with this program. If not, see ."; string username = await this.database.UsernameFromGameToken(token); - string announceText = ServerConfiguration.Instance.AnnounceText; + StringBuilder announceText = new(ServerConfiguration.Instance.AnnounceText); - announceText = announceText.Replace("%user", username); - announceText = announceText.Replace("%id", token.UserId.ToString()); + announceText.Replace("%user", username); + announceText.Replace("%id", token.UserId.ToString()); - return this.Ok - ( - announceText + - #if DEBUG - "\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" + - "---DEBUG INFO---" + - #endif - (string.IsNullOrWhiteSpace(announceText) ? "" : "\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" + + "---DEBUG INFO---"); + #endif + + return this.Ok(announceText.ToString()); } [HttpGet("notification")] diff --git a/ProjectLighthouse.Servers.Website/Pages/AnnouncePage.cshtml b/ProjectLighthouse.Servers.Website/Pages/AnnouncePage.cshtml new file mode 100644 index 00000000..06710ad2 --- /dev/null +++ b/ProjectLighthouse.Servers.Website/Pages/AnnouncePage.cshtml @@ -0,0 +1,67 @@ +@page "/announce" +@using LBPUnion.ProjectLighthouse.Localization.StringLists +@using LBPUnion.ProjectLighthouse.Types.Entities.Website +@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.AnnouncePage +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers + +@{ + Layout = "Layouts/BaseLayout"; + Model.Title = Model.Translate(GeneralStrings.Announcements); +} + +@if (Model.User != null && Model.User.IsAdmin) +{ +
+

Post New Announcement

+ @if (!string.IsNullOrWhiteSpace(Model.Error)) + { + @await Html.PartialAsync("Partials/ErrorModalPartial", (Model.Translate(GeneralStrings.Error), Model.Error), ViewData) + } +
+ @Html.AntiForgeryToken() +
+ + +
+
+ + +
+ +
+
+} + +@if (Model.Announcements.Any()) +{ + @foreach (WebsiteAnnouncementEntity announcement in Model.Announcements) + { +
+

@announcement.Title

+

+ @announcement.Content +

+ + @if (Model.User != null && Model.User.IsAdmin) + { +
+ @Html.AntiForgeryToken() + +
+ } +
+ } +} +else +{ +
+

There are no announcements to display.

+
+} \ No newline at end of file diff --git a/ProjectLighthouse.Servers.Website/Pages/AnnouncePage.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/AnnouncePage.cshtml.cs new file mode 100644 index 00000000..bf4175d4 --- /dev/null +++ b/ProjectLighthouse.Servers.Website/Pages/AnnouncePage.cshtml.cs @@ -0,0 +1,82 @@ +#nullable enable + +using LBPUnion.ProjectLighthouse.Configuration; +using LBPUnion.ProjectLighthouse.Database; +using LBPUnion.ProjectLighthouse.Helpers; +using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts; +using LBPUnion.ProjectLighthouse.Types.Entities.Profile; +using LBPUnion.ProjectLighthouse.Types.Entities.Website; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages; + +public class AnnouncePage : BaseLayout +{ + public AnnouncePage(DatabaseContext database) : base(database) + { } + + public List Announcements { get; set; } = new(); + public string Error { get; set; } = ""; + + public async Task OnGet() + { + this.Announcements = await this.Database.WebsiteAnnouncements + .OrderByDescending(a => a.AnnouncementId) + .ToListAsync(); + + return this.Page(); + } + + public async Task OnPost([FromForm] string title, [FromForm] string content) + { + UserEntity? user = this.Database.UserFromWebRequest(this.Request); + if (user == null) return this.BadRequest(); + if (!user.IsAdmin) return this.BadRequest(); + + if (string.IsNullOrWhiteSpace(title) || string.IsNullOrWhiteSpace(content)) + { + this.Error = "Invalid form data, please ensure all fields are filled out."; + return this.Page(); + } + + WebsiteAnnouncementEntity announcement = new() + { + Title = title, + Content = content, + }; + + await this.Database.WebsiteAnnouncements.AddAsync(announcement); + await this.Database.SaveChangesAsync(); + + if (DiscordConfiguration.Instance.DiscordIntegrationEnabled) + { + string truncatedAnnouncement = content.Length > 250 + ? content[..250] + $"... [read more]({ServerConfiguration.Instance.ExternalUrl}/announce)" + : content; + + await WebhookHelper.SendWebhook($":mega: {title}", + truncatedAnnouncement); + } + + return this.RedirectToPage(); + } + + public async Task OnPostDelete(int id) + { + UserEntity? user = this.Database.UserFromWebRequest(this.Request); + if (user == null) return this.BadRequest(); + if (!user.IsAdmin) return this.BadRequest(); + + WebsiteAnnouncementEntity? announcement = await this.Database.WebsiteAnnouncements + .Where(a => a.AnnouncementId == id) + .FirstOrDefaultAsync(); + + if (announcement == null) return this.BadRequest(); + + this.Database.WebsiteAnnouncements.Remove(announcement); + await this.Database.SaveChangesAsync(); + + return this.RedirectToPage(); + } +} \ No newline at end of file diff --git a/ProjectLighthouse.Servers.Website/Pages/Layouts/BaseLayout.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/Layouts/BaseLayout.cshtml.cs index 5c1ce0f8..3d81280b 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Layouts/BaseLayout.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/Layouts/BaseLayout.cshtml.cs @@ -33,6 +33,8 @@ public class BaseLayout : PageModel this.NavigationItems.Add(new PageNavigationItem(BaseLayoutStrings.HeaderUsers, "/users/0", "user friends")); this.NavigationItems.Add(new PageNavigationItem(BaseLayoutStrings.HeaderPhotos, "/photos/0", "camera")); this.NavigationItems.Add(new PageNavigationItem(BaseLayoutStrings.HeaderSlots, "/slots/0", "globe americas")); + + this.NavigationItemsRight.Add(new PageNavigationItem(GeneralStrings.Announcements, "/announce", "bullhorn")); } public new UserEntity? User { diff --git a/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/MessageControllerTests.cs b/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/MessageControllerTests.cs index 8728a613..0c14a9df 100644 --- a/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/MessageControllerTests.cs +++ b/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/MessageControllerTests.cs @@ -82,7 +82,7 @@ along with this program. If not, see ." + "\nuni ServerConfiguration.Instance.AnnounceText = "you are now logged in as %user (id: %id)"; - const string expected = "you are now logged in as unittest (id: 1)\n"; + const string expected = "you are now logged in as unittest (id: 1)"; IActionResult result = await messageController.Announce(); diff --git a/ProjectLighthouse.Tests.WebsiteTests/Integration/AdminTests.cs b/ProjectLighthouse.Tests.WebsiteTests/Integration/AdminTests.cs index ec8d9782..79d15561 100644 --- a/ProjectLighthouse.Tests.WebsiteTests/Integration/AdminTests.cs +++ b/ProjectLighthouse.Tests.WebsiteTests/Integration/AdminTests.cs @@ -14,7 +14,7 @@ namespace ProjectLighthouse.Tests.WebsiteTests.Integration; [Trait("Category", "Integration")] public class AdminTests : LighthouseWebTest { - private const string adminPanelButtonXPath = "/html/body/div/header/div/div/div/a[1]"; + private const string adminPanelButtonXPath = "/html/body/div/header/div/div/div/a[2]"; [Fact] public async Task ShouldShowAdminPanelButtonWhenAdmin() diff --git a/ProjectLighthouse/Database/DatabaseContext.cs b/ProjectLighthouse/Database/DatabaseContext.cs index 830ee726..2bd56296 100644 --- a/ProjectLighthouse/Database/DatabaseContext.cs +++ b/ProjectLighthouse/Database/DatabaseContext.cs @@ -5,6 +5,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Maintenance; using LBPUnion.ProjectLighthouse.Types.Entities.Moderation; using LBPUnion.ProjectLighthouse.Types.Entities.Profile; using LBPUnion.ProjectLighthouse.Types.Entities.Token; +using LBPUnion.ProjectLighthouse.Types.Entities.Website; using Microsoft.EntityFrameworkCore; namespace LBPUnion.ProjectLighthouse.Database; @@ -61,6 +62,10 @@ public partial class DatabaseContext : DbContext #region Misc public DbSet CompletedMigrations { get; set; } #endregion + + #region Website + public DbSet WebsiteAnnouncements { get; set; } + #endregion #endregion diff --git a/ProjectLighthouse/Migrations/20230620211613_AddWebAnnouncementsToDb.cs b/ProjectLighthouse/Migrations/20230620211613_AddWebAnnouncementsToDb.cs new file mode 100644 index 00000000..592f9ee3 --- /dev/null +++ b/ProjectLighthouse/Migrations/20230620211613_AddWebAnnouncementsToDb.cs @@ -0,0 +1,48 @@ +using LBPUnion.ProjectLighthouse.Database; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ProjectLighthouse.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20230620211613_AddWebAnnouncementsToDb")] + public partial class AddWebAnnouncementsToDb : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "WebsiteAnnouncements", + columns: table => new + { + AnnouncementId = table.Column( + type: "int", + nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Title = table.Column( + type: "longtext", + nullable: false, + defaultValue: "") + .Annotation("MySql:CharSet", "utf8mb4"), + Content = table.Column( + type: "longtext", + nullable: false, + defaultValue: "") + .Annotation("MySql:CharSet", "utf8mb4") + }, + constraints: table => + { + table.PrimaryKey("PK_WebsiteAnnouncements", x => x.AnnouncementId); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "WebsiteAnnouncements"); + } + } +} diff --git a/ProjectLighthouse/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs b/ProjectLighthouse/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs index f21666ba..847b390b 100644 --- a/ProjectLighthouse/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs +++ b/ProjectLighthouse/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs @@ -16,7 +16,7 @@ namespace ProjectLighthouse.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "7.0.4") + .HasAnnotation("ProductVersion", "7.0.7") .HasAnnotation("Relational:MaxIdentifierLength", 64); modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Interaction.HeartedLevelEntity", b => @@ -1024,6 +1024,23 @@ namespace ProjectLighthouse.Migrations b.ToTable("WebTokens"); }); + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Website.WebsiteAnnouncementEntity", b => + { + b.Property("AnnouncementId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Content") + .HasColumnType("longtext"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.HasKey("Identifier"); + + b.ToTable("WebsiteAnnouncements"); + }); + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Interaction.HeartedLevelEntity", b => { b.HasOne("LBPUnion.ProjectLighthouse.Types.Entities.Level.SlotEntity", "Slot") diff --git a/ProjectLighthouse/Types/Entities/Website/WebsiteAnnouncementEntity.cs b/ProjectLighthouse/Types/Entities/Website/WebsiteAnnouncementEntity.cs new file mode 100644 index 00000000..26c42488 --- /dev/null +++ b/ProjectLighthouse/Types/Entities/Website/WebsiteAnnouncementEntity.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; + +namespace LBPUnion.ProjectLighthouse.Types.Entities.Website; + +public class WebsiteAnnouncementEntity +{ + [Key] + public int AnnouncementId { get; set; } + + public string Title { get; set; } + + public string Content { get; set; } +} \ No newline at end of file