Add admin Articles (CRUD) + Bans resources
- Articles: list / new / edit / delete (websiteArticles), unique-slug generation via slugify() (pure, unit-tested); staff-gated server actions. - Bans: list active bans (ban_expire > now), create ban (writes the bans row + RCON disconnect) with type/duration/reason, lift ban; staff-gated actions. - Admin nav extended (Articles, Bans). Verified: tsc exit 0, vitest 48/48, next build exit 0 (7 /admin routes).
This commit is contained in:
1 parent
9f81096f05
commit
5861d9f1f5
9 files changed
+310
-1
No files matched your search
+13
-1
@@ -1,5 +1,17 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { avatarImageUrl, excerpt } from "./format";
|
||||
import { avatarImageUrl, excerpt, slugify } from "./format";
|
||||
|
||||
describe("slugify", () => {
|
||||
it("lowercases and hyphenates", () => {
|
||||
expect(slugify("Hello World!")).toBe("hello-world");
|
||||
});
|
||||
it("strips accents via NFKD", () => {
|
||||
expect(slugify("Café del Mar")).toBe("cafe-del-mar");
|
||||
});
|
||||
it("falls back to 'article' for empty input", () => {
|
||||
expect(slugify(" *** ")).toBe("article");
|
||||
});
|
||||
});
|
||||
|
||||
describe("avatarImageUrl", () => {
|
||||
const base = "https://www.habbo.com/habbo-imaging/avatarimage";
|
||||
|
||||
@@ -12,6 +12,19 @@ export function avatarImageUrl(
|
||||
return `${base}${sep}${params.toString()}`;
|
||||
}
|
||||
|
||||
/** URL-safe slug from a title (lowercase, ascii, hyphenated). */
|
||||
export function slugify(input: string): string {
|
||||
// NFKD splits accented letters into base + combining mark; the combining
|
||||
// marks (and any other non-alphanumerics) are then collapsed to hyphens.
|
||||
const slug = input
|
||||
.toLowerCase()
|
||||
.normalize("NFKD")
|
||||
.replace(/[^a-z0-9]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "")
|
||||
.slice(0, 255);
|
||||
return slug || "article";
|
||||
}
|
||||
|
||||
/** Trim text to `max` chars on a word boundary, adding an ellipsis. */
|
||||
export function excerpt(text: string, max = 160): string {
|
||||
if (text.length <= max) return text;
|
||||
|
||||
Reference in new issue
Block a user