Compare commits

..

3 commits

Author SHA1 Message Date
041689392b fix: use api URL instead of frontend in sitemap-index.xml
i'm stoopid
2026-08-30 12:22:42 +01:00
253edc0d1a feat: split sitemap 2026-08-30 12:16:47 +01:00
152576e524 chore: update packages 2026-08-30 12:16:14 +01:00
9 changed files with 981 additions and 452 deletions

View file

@ -2,7 +2,7 @@
"name": "@tomodachi-share/backend", "name": "@tomodachi-share/backend",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"packageManager": "pnpm@11.18.0", "packageManager": "pnpm@11.24.0",
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
@ -17,30 +17,30 @@
"@tomodachi-share/shared": "workspace:*", "@tomodachi-share/shared": "workspace:*",
"bit-buffer": "^0.3.0", "bit-buffer": "^0.3.0",
"charinfo-ex": "^0.0.5", "charinfo-ex": "^0.0.5",
"dayjs": "^1.11.21", "dayjs": "^1.11.23",
"downshift": "^9.4.0", "downshift": "^9.4.0",
"file-type": "^22.0.1", "file-type": "^22.0.2",
"next": "16.2.12", "next": "16.3.3",
"next-auth": "5.0.0-beta.32", "next-auth": "5.0.0-beta.32",
"qrcode-generator": "^2.0.4", "qrcode-generator": "^2.0.4",
"react": "^19.2.8", "react": "^19.2.8",
"react-dom": "^19.2.8", "react-dom": "^19.2.8",
"redis": "^6.2.0", "redis": "^6.2.1",
"satori": "^0.29.0", "satori": "^0.33.4",
"sharp": "^0.34.5", "sharp": "^0.34.5",
"sjcl-with-all": "1.0.8", "sjcl-with-all": "1.0.8",
"zod": "^4.4.3" "zod": "^4.5.4"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.3.6", "@eslint/eslintrc": "^3.3.6",
"@iconify/react": "^6.0.2", "@iconify/react": "^6.0.2",
"@tailwindcss/postcss": "^4.3.3", "@tailwindcss/postcss": "^4.3.3",
"@types/node": "^26.1.2", "@types/node": "^26.4.0",
"@types/react": "^19.2.18", "@types/react": "^19.2.18",
"@types/react-dom": "^19.2.4", "@types/react-dom": "^19.2.5",
"@types/sjcl": "^1.0.34", "@types/sjcl": "^1.0.34",
"eslint": "^10.8.0", "eslint": "^10.9.1",
"eslint-config-next": "16.2.12", "eslint-config-next": "16.3.3",
"prisma": "^6.19.3", "prisma": "^6.19.3",
"schema-dts": "^2.0.0", "schema-dts": "^2.0.0",
"tailwindcss": "^4.3.3", "tailwindcss": "^4.3.3",

View file

@ -0,0 +1,26 @@
import { prisma } from "@/lib/prisma";
import { CHUNK_SIZE } from "../sitemap";
import { NextResponse } from "next/server";
export const revalidate = 43200; // update every 12 hours
export async function GET() {
const apiUrl = process.env.NEXT_PUBLIC_BASE_URL!;
const total = await prisma.mii.count({
where: { in_queue: false, quarantined: false },
});
const miiChunks = Math.ceil(total / CHUNK_SIZE);
const totalSitemaps = miiChunks + 1; // +1 for the static-routes sitemap
const sitemapEntries = Array.from({ length: totalSitemaps }, (_, i) => `<sitemap><loc>${apiUrl}/sitemap/${i}.xml</loc></sitemap>`).join("");
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${sitemapEntries}
</sitemapindex>`;
return new NextResponse(xml, {
headers: { "Content-Type": "application/xml" },
});
}

View file

@ -4,11 +4,60 @@ import type { MetadataRoute } from "next";
type SitemapRoute = MetadataRoute.Sitemap[0]; type SitemapRoute = MetadataRoute.Sitemap[0];
export const revalidate = 43200; // update every 12 hours export const revalidate = 43200; // update every 12 hours
export const CHUNK_SIZE = 45000; // stay under 50,000 limit
export default async function sitemap(): Promise<MetadataRoute.Sitemap> { export async function generateSitemaps() {
const total = await prisma.mii.count({
where: {
in_queue: false,
quarantined: false,
},
});
const miiChunks = Math.ceil(total / CHUNK_SIZE);
// id 0 = static routes, ids 1..miiChunks = mii chunks
return Array.from({ length: miiChunks + 1 }, (_, id) => ({ id }));
}
export default async function sitemap(props: { id: Promise<string> }): Promise<MetadataRoute.Sitemap> {
const baseUrl = process.env.NEXT_PUBLIC_FRONTEND_URL!; const baseUrl = process.env.NEXT_PUBLIC_FRONTEND_URL!;
const apiUrl = process.env.NEXT_PUBLIC_BASE_URL!; const apiUrl = process.env.NEXT_PUBLIC_BASE_URL!;
// id 0: static routes only
if ((await props.id) === "0") {
const lastModified = new Date();
return [
{
url: baseUrl,
lastModified,
changeFrequency: "always",
priority: 1,
},
{
url: `${baseUrl}/login`,
lastModified,
changeFrequency: "monthly",
priority: 0.6,
},
{
url: `${baseUrl}/privacy`,
lastModified,
changeFrequency: "yearly",
priority: 0.4,
},
{
url: `${baseUrl}/terms-of-service`,
lastModified,
changeFrequency: "yearly",
priority: 0.4,
},
];
}
// id 1+: paginated mii chunks
const chunkIndex = Number(await props.id) - 1;
const miis = await prisma.mii.findMany({ const miis = await prisma.mii.findMany({
select: { select: {
id: true, id: true,
@ -18,64 +67,19 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
in_queue: false, in_queue: false,
quarantined: false, quarantined: false,
}, },
orderBy: { id: "asc" },
skip: chunkIndex * CHUNK_SIZE,
take: CHUNK_SIZE,
}); });
// const users = await prisma.user.findMany({ return miis.map(
// select: { (mii) =>
// id: true, ({
// updatedAt: true, url: `${baseUrl}/mii/${mii.id}`,
// }, lastModified: mii.createdAt,
// }); changeFrequency: "weekly",
priority: 0.7,
const dynamicRoutes: MetadataRoute.Sitemap = [ images: [`${apiUrl}/mii/${mii.id}/image?type=metadata`],
...miis.map( }) as SitemapRoute,
(mii) => );
({
url: `${baseUrl}/mii/${mii.id}`,
lastModified: mii.createdAt,
changeFrequency: "weekly",
priority: 0.7,
images: [`${apiUrl}/mii/${mii.id}/image?type=metadata`],
}) as SitemapRoute,
),
// ...users.map(
// (user) =>
// ({
// url: `${baseUrl}/profile/${user.id}`,
// lastModified: user.updatedAt,
// changeFrequency: "weekly",
// priority: 0.2,
// }) as SitemapRoute,
// ),
];
const lastModified = new Date();
return [
{
url: baseUrl,
lastModified,
changeFrequency: "always",
priority: 1,
},
{
url: `${baseUrl}/login`,
lastModified,
changeFrequency: "monthly",
priority: 0.6,
},
{
url: `${baseUrl}/privacy`,
lastModified,
changeFrequency: "yearly",
priority: 0.4,
},
{
url: `${baseUrl}/terms-of-service`,
lastModified,
changeFrequency: "yearly",
priority: 0.4,
},
...dynamicRoutes,
];
} }

View file

@ -17,37 +17,37 @@
"@tailwindcss/vite": "^4.3.3", "@tailwindcss/vite": "^4.3.3",
"@tomodachi-share/shared": "workspace:*", "@tomodachi-share/shared": "workspace:*",
"@types/react": "^19.2.18", "@types/react": "^19.2.18",
"@types/react-dom": "^19.2.4", "@types/react-dom": "^19.2.5",
"canvas-confetti": "^1.9.4", "canvas-confetti": "^1.9.4",
"dayjs": "^1.11.21", "dayjs": "^1.11.23",
"downshift": "^9.4.0", "downshift": "^9.4.0",
"embla-carousel-react": "^8.6.0", "embla-carousel-react": "^8.6.0",
"jsqr": "^1.4.0", "jsqr": "^1.4.0",
"nanostores": "^1.4.2", "nanostores": "^1.5.2",
"qrcode-generator": "^2.0.4", "qrcode-generator": "^2.0.4",
"react": "^19.2.8", "react": "^19.2.8",
"react-dom": "^19.2.8", "react-dom": "^19.2.8",
"react-dropzone": "^19.1.1", "react-dropzone": "^20.1.1",
"react-image-crop": "^11.1.2", "react-image-crop": "^11.1.2",
"react-router": "^8.3.0", "react-router": "^8.3.1",
"tailwindcss": "^4.3.3", "tailwindcss": "^4.3.3",
"vite-plugin-node-polyfills": "^0.26.0", "vite-plugin-node-polyfills": "^0.28.0",
"zod": "^4.4.3" "zod": "^4.5.4"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^10.0.1", "@eslint/js": "^10.0.1",
"@iconify/react": "^6.0.2", "@iconify/react": "^6.0.2",
"@types/canvas-confetti": "^1.9.0", "@types/canvas-confetti": "^1.9.0",
"@types/node": "^26.1.2", "@types/node": "^26.4.0",
"@types/react": "^19.2.14", "@types/react": "^19.2.18",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.5",
"@vitejs/plugin-react": "^6.0.5", "@vitejs/plugin-react": "^6.1.1",
"eslint": "^10.8.0", "eslint": "^10.9.1",
"eslint-plugin-react-hooks": "^7.1.1", "eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.3", "eslint-plugin-react-refresh": "^0.5.5",
"globals": "^17.8.0", "globals": "^17.11.0",
"typescript": "~7.0.2", "typescript": "~7.0.2",
"typescript-eslint": "^8.65.0", "typescript-eslint": "^8.68.0",
"vite": "^8.2.0" "vite": "^8.2.2"
} }
} }

View file

@ -10,4 +10,4 @@ Disallow: /report/mii/*
Disallow: /report/user/* Disallow: /report/user/*
Disallow: /admin Disallow: /admin
Sitemap: https://api.tomodachishare.com/sitemap.xml Sitemap: https://api.tomodachishare.com/sitemap-index.xml

View file

@ -9,5 +9,5 @@
"keywords": [], "keywords": [],
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"packageManager": "pnpm@11.18.0" "packageManager": "pnpm@11.24.0"
} }

1219
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -17,3 +17,4 @@ minimumReleaseAgeExclude:
- '@redis/search@6.2.0' - '@redis/search@6.2.0'
- '@redis/time-series@6.2.0' - '@redis/time-series@6.2.0'
- redis@6.2.0 - redis@6.2.0
- zod@4.5.4

View file

@ -8,7 +8,7 @@
"fzstd": "^0.1.1", "fzstd": "^0.1.1",
"sharp": "^0.34.5", "sharp": "^0.34.5",
"sjcl-with-all": "1.0.8", "sjcl-with-all": "1.0.8",
"zod": "^4.4.3" "zod": "^4.5.4"
}, },
"exports": { "exports": {
".": "./src/index.ts", ".": "./src/index.ts",
@ -17,7 +17,8 @@
}, },
"types": "./src/index.ts", "types": "./src/index.ts",
"devDependencies": { "devDependencies": {
"@types/node": "^26.1.2", "@types/node": "^26.4.0",
"@types/sjcl": "^1.0.34" "@types/sjcl": "^1.0.34",
"vite-plugin-node-polyfills": "^0.28.0"
} }
} }