diff --git a/src/back/routes/index.ts b/src/back/routes/index.ts
index 1135d9e..3642812 100644
--- a/src/back/routes/index.ts
+++ b/src/back/routes/index.ts
@@ -8,6 +8,7 @@ import { imageRouter, RESPONSIVE_IMAGES_URL_PATH } from "../image-router.js";
import { customUrlView } from "./middlewares/customUrlView.js";
import mountAutoRoutes from "./routes.js";
import _locreq from "locreq";
+import sitemapPage from "../sitemap.xml.js";
const locreq = _locreq(new URL("./", import.meta.url).pathname);
export const mainRouter = (app: TheApp, koa_app: Koa, router: Router): void => {
@@ -41,6 +42,11 @@ export const mainRouter = (app: TheApp, koa_app: Koa, router: Router): void => {
ctx.body = { status: ctx.$app.status, started_at };
});
+ router.get("/sitemap.xml", async (ctx) => {
+ ctx.set("Content-Type", "application/xml");
+ ctx.body = await sitemapPage.render(ctx);
+ });
+
router.use(RESPONSIVE_IMAGES_URL_PATH, imageRouter.getRoutes());
mountAutoRoutes(router);
diff --git a/src/back/sitemap.xml.tsx b/src/back/sitemap.xml.tsx
new file mode 100644
index 0000000..482982c
--- /dev/null
+++ b/src/back/sitemap.xml.tsx
@@ -0,0 +1,29 @@
+import type { Context } from "koa";
+import { Page } from "@sealcode/sealgen";
+
+export const actionName = "Sitemap";
+
+async function generateSitemap(ctx: Context) {
+ const pages = await ctx.$app.collections.pages.list(ctx.$context).fetch();
+
+ let xml = `\n`;
+ xml += `\n`;
+
+ for (const page of pages.items) {
+ xml += ` \n ${ctx.origin.replace(/\/$/, "")}${page.get("url")}\n \n`;
+ }
+
+ xml += `\n`;
+ return xml;
+}
+
+export default new (class SitemapPage extends Page {
+ async canAccess() {
+ return { canAccess: true, message: "" };
+ }
+
+ async render(ctx: Context) {
+ ctx.set("Content-Type", "application/xml");
+ return generateSitemap(ctx);
+ }
+})();
diff --git a/tests/sitemap.test.ts b/tests/sitemap.test.ts
new file mode 100644
index 0000000..114ca72
--- /dev/null
+++ b/tests/sitemap.test.ts
@@ -0,0 +1,54 @@
+import * as assert from "node:assert";
+import { test, expect } from "./backend-fixture";
+
+test("sitemap returns expected XML for empty DB", async ({ backend, request }) => {
+ const response = await request.get(`${backend.url}/sitemap.xml`);
+ assert.strictEqual(response.status(), 200);
+
+ const actual = await response.text();
+ const expected = `
+
+
+ ${backend.url}/
+
+
+`;
+ assert.strictEqual(actual, expected);
+});
+
+test("sitemap returns expected XML after creating a page via UI", async ({
+ page,
+ backend,
+ request,
+}) => {
+ await page.goto(backend.url);
+ await page.getByRole("link", { name: "Go to Admin" }).click();
+ await page.getByPlaceholder("text").fill("admin");
+ await page.getByPlaceholder("text").press("Tab");
+ await page.getByPlaceholder("password").fill("adminadmin");
+ await page.getByRole("button", { name: "Wyślij" }).click();
+
+ await page.getByRole("link", { name: "Edit Pages" }).click();
+ await page.getByRole("link", { name: "Create" }).click();
+ await page.getByLabel("url").fill("/my-page/");
+ await page.getByLabel("title").fill("My Page");
+ await page.getByLabel("heading").fill("My Heading");
+ await page.getByLabel("description").fill("My Description");
+ await page.getByRole("button", { name: "Wyślij" }).click();
+
+ const response = await request.get(`${backend.url}/sitemap.xml`);
+ assert.strictEqual(response.status(), 200);
+
+ const actual = await response.text();
+ const expected = `
+
+
+ ${backend.url}/
+
+
+ ${backend.url}/my-page/
+
+
+`;
+ assert.strictEqual(actual, expected);
+});