diff --git a/.dockerignore b/.dockerignore index d755b6c..f90ce74 100644 --- a/.dockerignore +++ b/.dockerignore @@ -20,5 +20,4 @@ **/secrets.dev.yaml **/values.dev.yaml LICENSE -README.md -static-pages/* +README.md \ No newline at end of file diff --git a/Foxnouns.Backend/Controllers/MetaController.cs b/Foxnouns.Backend/Controllers/MetaController.cs index e22fbc1..8552164 100644 --- a/Foxnouns.Backend/Controllers/MetaController.cs +++ b/Foxnouns.Backend/Controllers/MetaController.cs @@ -12,7 +12,6 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -using System.Text.RegularExpressions; using Foxnouns.Backend.Dto; using Foxnouns.Backend.Utils; using Microsoft.AspNetCore.Mvc; @@ -20,7 +19,7 @@ using Microsoft.AspNetCore.Mvc; namespace Foxnouns.Backend.Controllers; [Route("/api/v2/meta")] -public partial class MetaController : ApiControllerBase +public class MetaController : ApiControllerBase { private const string Repository = "https://codeberg.org/pronounscc/pronouns.cc"; @@ -49,23 +48,7 @@ public partial class MetaController : ApiControllerBase ) ); - [HttpGet("page/{page}")] - public async Task GetStaticPageAsync(string page, CancellationToken ct = default) - { - if (!PageRegex().IsMatch(page)) - { - throw new ApiError.BadRequest("Invalid page name"); - } - - string path = Path.Join(Directory.GetCurrentDirectory(), "static-pages", $"{page}.md"); - string text = await System.IO.File.ReadAllTextAsync(path, ct); - return Ok(text); - } - [HttpGet("/api/v2/coffee")] public IActionResult BrewCoffee() => Problem("Sorry, I'm a teapot!", statusCode: StatusCodes.Status418ImATeapot); - - [GeneratedRegex(@"^[a-z\-_]+$")] - private static partial Regex PageRegex(); } diff --git a/Foxnouns.Backend/Controllers/Moderation/AuditLogController.cs b/Foxnouns.Backend/Controllers/Moderation/AuditLogController.cs index b2d0581..8b556de 100644 --- a/Foxnouns.Backend/Controllers/Moderation/AuditLogController.cs +++ b/Foxnouns.Backend/Controllers/Moderation/AuditLogController.cs @@ -30,9 +30,7 @@ public class AuditLogController(DatabaseContext db, ModerationRendererService mo public async Task GetAuditLogAsync( [FromQuery] AuditLogEntryType? type = null, [FromQuery] int? limit = null, - [FromQuery] Snowflake? before = null, - [FromQuery] Snowflake? after = null, - [FromQuery(Name = "by-moderator")] Snowflake? byModerator = null + [FromQuery] Snowflake? before = null ) { limit = limit switch @@ -47,30 +45,11 @@ public class AuditLogController(DatabaseContext db, ModerationRendererService mo if (before != null) query = query.Where(e => e.Id < before.Value); - else if (after != null) - query = query.Where(e => e.Id > after.Value); - if (type != null) query = query.Where(e => e.Type == type); - if (byModerator != null) - query = query.Where(e => e.ModeratorId == byModerator.Value); List entries = await query.Take(limit!.Value).ToListAsync(); return Ok(entries.Select(moderationRenderer.RenderAuditLogEntry)); } - - [HttpGet("moderators")] - public async Task GetModeratorsAsync(CancellationToken ct = default) - { - var moderators = await db - .Users.Where(u => - !u.Deleted && (u.Role == UserRole.Admin || u.Role == UserRole.Moderator) - ) - .Select(u => new { u.Id, u.Username }) - .OrderBy(u => u.Id) - .ToListAsync(ct); - - return Ok(moderators); - } } diff --git a/Foxnouns.Backend/Dto/Moderation.cs b/Foxnouns.Backend/Dto/Moderation.cs index c9489ed..f9e6ab7 100644 --- a/Foxnouns.Backend/Dto/Moderation.cs +++ b/Foxnouns.Backend/Dto/Moderation.cs @@ -29,7 +29,6 @@ public record ReportResponse( PartialMember? TargetMember, ReportStatus Status, ReportReason Reason, - string? Context, ReportTargetType TargetType, JObject? Snapshot ); diff --git a/Foxnouns.Backend/Services/ModerationRendererService.cs b/Foxnouns.Backend/Services/ModerationRendererService.cs index 04ef46b..deed9c5 100644 --- a/Foxnouns.Backend/Services/ModerationRendererService.cs +++ b/Foxnouns.Backend/Services/ModerationRendererService.cs @@ -36,7 +36,6 @@ public class ModerationRendererService( : null, report.Status, report.Reason, - report.Context, report.TargetType, report.TargetSnapshot != null ? JsonConvert.DeserializeObject(report.TargetSnapshot) diff --git a/Foxnouns.Backend/static-pages/.gitignore b/Foxnouns.Backend/static-pages/.gitignore deleted file mode 100644 index d6b7ef3..0000000 --- a/Foxnouns.Backend/static-pages/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/Foxnouns.Frontend/src/lib/api/models/moderation.ts b/Foxnouns.Frontend/src/lib/api/models/moderation.ts index f0e112b..b95da5c 100644 --- a/Foxnouns.Frontend/src/lib/api/models/moderation.ts +++ b/Foxnouns.Frontend/src/lib/api/models/moderation.ts @@ -1,6 +1,3 @@ -import type { Member } from "./member"; -import type { PartialMember, PartialUser, User } from "./user"; - export type CreateReportRequest = { reason: ReportReason; context: string | null; @@ -27,35 +24,3 @@ export enum ReportReason { Advertisement = "ADVERTISEMENT", CopyrightViolation = "COPYRIGHT_VIOLATION", } - -export type Report = { - id: string; - reporter: PartialUser; - target_user: PartialUser; - target_member?: PartialMember; - status: "OPEN" | "CLOSED"; - reason: ReportReason; - context: string | null; - target_type: "USER" | "MEMBER"; - snapshot: User | Member | null; -}; - -export type AuditLogEntry = { - id: string; - moderator: AuditLogEntity; - target_user?: AuditLogEntity; - target_member?: AuditLogEntity; - report_id?: string; - type: AuditLogEntryType; - reason: string | null; - cleared_fields?: string[]; -}; - -export type AuditLogEntity = { id: string; username: string }; - -export enum AuditLogEntryType { - IgnoreReport = "IGNORE_REPORT", - WarnUser = "WARN_USER", - WarnUserAndClearProfile = "WARN_USER_AND_CLEAR_PROFILE", - SuspendUser = "SUSPEND_USER", -} diff --git a/Foxnouns.Frontend/src/lib/components/Navbar.svelte b/Foxnouns.Frontend/src/lib/components/Navbar.svelte index edfbd1a..967d688 100644 --- a/Foxnouns.Frontend/src/lib/components/Navbar.svelte +++ b/Foxnouns.Frontend/src/lib/components/Navbar.svelte @@ -58,13 +58,6 @@ @{user.username} - {#if user.role === "ADMIN" || user.role === "MODERATOR"} - - - Administration - - - {/if} {$t("nav.settings")} diff --git a/Foxnouns.Frontend/src/lib/components/admin/AuditLogEntity.svelte b/Foxnouns.Frontend/src/lib/components/admin/AuditLogEntity.svelte deleted file mode 100644 index 1f3645f..0000000 --- a/Foxnouns.Frontend/src/lib/components/admin/AuditLogEntity.svelte +++ /dev/null @@ -1,8 +0,0 @@ - - -{entity.username} ({entity.id}) diff --git a/Foxnouns.Frontend/src/lib/components/admin/AuditLogEntryCard.svelte b/Foxnouns.Frontend/src/lib/components/admin/AuditLogEntryCard.svelte deleted file mode 100644 index 2391b57..0000000 --- a/Foxnouns.Frontend/src/lib/components/admin/AuditLogEntryCard.svelte +++ /dev/null @@ -1,50 +0,0 @@ - - - - Audit log - - -
-
- - - {#if entry.type === "IGNORE_REPORT"} - ignored a report - {:else if entry.type === "WARN_USER" || entry.type === "WARN_USER_AND_CLEAR_PROFILE"} - warned - {:else if entry.type === "SUSPEND_USER"} - suspended - {:else} - (unknown action {entry.type}) - {/if} - {#if entry.target_user} - - {/if} - {#if entry.target_member} - for member - {/if} - - - {date} -
- {#if reason} -
- Reason - {@html reason} -
- {:else} - (no reason given) - {/if} -
diff --git a/Foxnouns.Frontend/src/lib/components/admin/DashboardCard.svelte b/Foxnouns.Frontend/src/lib/components/admin/DashboardCard.svelte deleted file mode 100644 index a6d04e4..0000000 --- a/Foxnouns.Frontend/src/lib/components/admin/DashboardCard.svelte +++ /dev/null @@ -1,17 +0,0 @@ - - -
-
-
-
{title}
-

- {@render children()} -

-
-
-
diff --git a/Foxnouns.Frontend/src/lib/markdown.ts b/Foxnouns.Frontend/src/lib/markdown.ts index 9c4ff35..94a1a05 100644 --- a/Foxnouns.Frontend/src/lib/markdown.ts +++ b/Foxnouns.Frontend/src/lib/markdown.ts @@ -7,7 +7,11 @@ const md = new MarkdownIt({ linkify: true, }).disable(["heading", "lheading", "link", "table", "blockquote"]); -const unsafeMd = new MarkdownIt(); +const unsafeMd = new MarkdownIt({ + html: false, + breaks: true, + linkify: true, +}); export const renderMarkdown = (src: string | null) => (src ? sanitize(md.render(src)) : null); diff --git a/Foxnouns.Frontend/src/lib/pageUtils.svelte.ts b/Foxnouns.Frontend/src/lib/pageUtils.svelte.ts deleted file mode 100644 index 5f45815..0000000 --- a/Foxnouns.Frontend/src/lib/pageUtils.svelte.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { page } from "$app/state"; - -export const isActive = (path: string | string[], prefix: boolean = false) => - typeof path === "string" - ? prefix - ? page.url.pathname.startsWith(path) - : page.url.pathname === path - : prefix - ? path.some((p) => page.url.pathname.startsWith(p)) - : path.some((p) => page.url.pathname === p); diff --git a/Foxnouns.Frontend/src/routes/admin/+layout.server.ts b/Foxnouns.Frontend/src/routes/admin/+layout.server.ts deleted file mode 100644 index 7da4d36..0000000 --- a/Foxnouns.Frontend/src/routes/admin/+layout.server.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { apiRequest } from "$api"; -import ApiError, { ErrorCode } from "$api/error"; -import type { Report } from "$api/models/moderation"; -import { idTimestamp } from "$lib"; -import { redirect } from "@sveltejs/kit"; - -export const load = async ({ parent, fetch, cookies }) => { - const { meUser } = await parent(); - if (!meUser) redirect(303, "/"); - - if (meUser.role !== "ADMIN" && meUser.role !== "MODERATOR") { - throw new ApiError({ - status: 403, - code: ErrorCode.Forbidden, - message: "Only admins and moderators can use this page.", - }); - } - - const reports = await apiRequest("GET", "/moderation/reports", { fetch, cookies }); - const staleReportCount = reports.filter( - (r) => idTimestamp(r.id).diffNow(["days"]).days >= 7, - ).length; - - return { - user: meUser, - isAdmin: meUser.role === "ADMIN", - reportCount: reports.length, - staleReportCount, - }; -}; diff --git a/Foxnouns.Frontend/src/routes/admin/+layout.svelte b/Foxnouns.Frontend/src/routes/admin/+layout.svelte deleted file mode 100644 index 0c0b247..0000000 --- a/Foxnouns.Frontend/src/routes/admin/+layout.svelte +++ /dev/null @@ -1,50 +0,0 @@ - - - diff --git a/Foxnouns.Frontend/src/routes/admin/+page.svelte b/Foxnouns.Frontend/src/routes/admin/+page.svelte deleted file mode 100644 index 79df014..0000000 --- a/Foxnouns.Frontend/src/routes/admin/+page.svelte +++ /dev/null @@ -1,23 +0,0 @@ - - -

Dashboard

- -
- - {data.meta.users.total.toLocaleString("en")} -
- ({data.meta.users.active_month.toLocaleString("en")} active in the last month) -
- {data.meta.members.toLocaleString("en")} - - {data.reportCount.toLocaleString("en")} -
- ({data.staleReportCount} older than 1 week) -
-
diff --git a/Foxnouns.Frontend/src/routes/admin/audit-log/+page.server.ts b/Foxnouns.Frontend/src/routes/admin/audit-log/+page.server.ts deleted file mode 100644 index f48e334..0000000 --- a/Foxnouns.Frontend/src/routes/admin/audit-log/+page.server.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { apiRequest } from "$api"; -import { type AuditLogEntity, type AuditLogEntry } from "$api/models/moderation.js"; - -export const load = async ({ url, fetch, cookies }) => { - const type = url.searchParams.get("type"); - const before = url.searchParams.get("before"); - const after = url.searchParams.get("after"); - const byModerator = url.searchParams.get("by-moderator"); - let limit: number = 100; - if (url.searchParams.has("limit")) limit = parseInt(url.searchParams.get("limit")!); - - const params = new URLSearchParams(); - params.set("limit", limit.toString()); - if (type) params.set("type", type); - if (before) params.set("before", before); - if (after) params.set("after", after); - if (byModerator) params.set("by-moderator", byModerator); - - const entries = await apiRequest( - "GET", - `/moderation/audit-log?${params.toString()}`, - { - fetch, - cookies, - }, - ); - - const moderators = await apiRequest("GET", "/moderation/audit-log/moderators", { - fetch, - cookies, - }); - - let modFilter: AuditLogEntity | null = null; - if (byModerator) - modFilter = entries.find((e) => e.moderator.id === byModerator)?.moderator || null; - - return { entries, type, before, after, modFilter, url: url.toString(), moderators }; -}; diff --git a/Foxnouns.Frontend/src/routes/admin/audit-log/+page.svelte b/Foxnouns.Frontend/src/routes/admin/audit-log/+page.svelte deleted file mode 100644 index a0e182d..0000000 --- a/Foxnouns.Frontend/src/routes/admin/audit-log/+page.svelte +++ /dev/null @@ -1,105 +0,0 @@ - - -

Audit log

- -
- - - Filter by type - - - - Ignore report - - - Warn user - - - Warn user and clear profile - - - Suspend user - - {#if data.type} - Remove filter - {/if} - - - - - Filter by moderator - - - {#each data.moderators as mod (mod.id)} - - {mod.username} - - {/each} - {#if data.modFilter} - Remove filter - {/if} - - -
- -{#if data.before} - Show newer entries -{/if} - -{#each data.entries as entry (entry.id)} - -{:else} -

There are no entries matching your filter

-{/each} - -{#if data.entries.length === 100} - Show older entries -{/if} diff --git a/Foxnouns.Frontend/src/routes/page/[page]/+page.server.ts b/Foxnouns.Frontend/src/routes/page/[page]/+page.server.ts deleted file mode 100644 index 1d9e8fc..0000000 --- a/Foxnouns.Frontend/src/routes/page/[page]/+page.server.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { baseRequest } from "$api"; -import ApiError from "$api/error"; - -export const load = async ({ fetch, params }) => { - const resp = await baseRequest("GET", `/meta/page/${params.page}`, { fetch }); - if (resp.status < 200 || resp.status > 299) { - const err = await resp.json(); - if ("code" in err) throw new ApiError(err); - else throw new ApiError(); - } - - const pageText = await resp.text(); - return { page: params.page, text: pageText }; -}; diff --git a/Foxnouns.Frontend/src/routes/page/[page]/+page.svelte b/Foxnouns.Frontend/src/routes/page/[page]/+page.svelte deleted file mode 100644 index a156d0a..0000000 --- a/Foxnouns.Frontend/src/routes/page/[page]/+page.svelte +++ /dev/null @@ -1,22 +0,0 @@ - - - - {title} • pronouns.cc - - -
- {@html md} -
diff --git a/Foxnouns.Frontend/src/routes/settings/+layout.svelte b/Foxnouns.Frontend/src/routes/settings/+layout.svelte index 8f18c8e..11801cf 100644 --- a/Foxnouns.Frontend/src/routes/settings/+layout.svelte +++ b/Foxnouns.Frontend/src/routes/settings/+layout.svelte @@ -1,11 +1,20 @@ diff --git a/Foxnouns.Frontend/src/routes/settings/members/[id]/+layout@.svelte b/Foxnouns.Frontend/src/routes/settings/members/[id]/+layout@.svelte index f3f4301..faae426 100644 --- a/Foxnouns.Frontend/src/routes/settings/members/[id]/+layout@.svelte +++ b/Foxnouns.Frontend/src/routes/settings/members/[id]/+layout@.svelte @@ -1,12 +1,14 @@ diff --git a/docker-compose.yml b/docker-compose.yml index 751d919..4fc94bb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,7 +16,6 @@ services: - "5007:5001" volumes: - ./docker/config.ini:/app/config.ini - - ./docker/static-pages:/app/static-pages frontend: image: frontend diff --git a/docker/static-pages/.gitignore b/docker/static-pages/.gitignore deleted file mode 100644 index d6b7ef3..0000000 --- a/docker/static-pages/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore