fix: move /notifications and /admin routes before /:slug wildcard
Hono matches routes in definition order, so /:slug was catching "notifications" and "admin" as slug params and returning 404. Static routes must be defined before parameterized routes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
1db8341fb2
commit
f8c51fad0b
121
server/spaces.ts
121
server/spaces.ts
|
|
@ -201,8 +201,68 @@ spaces.post("/", async (c) => {
|
|||
}, 201);
|
||||
});
|
||||
|
||||
// ── Static routes must be defined BEFORE /:slug to avoid matching as a slug ──
|
||||
|
||||
// ── Admin: list ALL spaces with detailed stats ──
|
||||
|
||||
spaces.get("/admin", async (c) => {
|
||||
const STORAGE_DIR = process.env.STORAGE_DIR || "./data/communities";
|
||||
const slugs = await listCommunities();
|
||||
|
||||
const spacesList = [];
|
||||
for (const slug of slugs) {
|
||||
await loadCommunity(slug);
|
||||
const data = getDocumentData(slug);
|
||||
if (!data?.meta) continue;
|
||||
|
||||
const shapes = data.shapes || {};
|
||||
const members = data.members || {};
|
||||
const shapeCount = Object.keys(shapes).length;
|
||||
const memberCount = Object.keys(members).length;
|
||||
|
||||
// Get file size on disk
|
||||
let fileSizeBytes = 0;
|
||||
try {
|
||||
const s = await stat(`${STORAGE_DIR}/${slug}.automerge`);
|
||||
fileSizeBytes = s.size;
|
||||
} catch {
|
||||
try {
|
||||
const s = await stat(`${STORAGE_DIR}/${slug}.json`);
|
||||
fileSizeBytes = s.size;
|
||||
} catch { /* not on disk yet */ }
|
||||
}
|
||||
|
||||
// Count shapes by type
|
||||
const shapeTypes: Record<string, number> = {};
|
||||
for (const shape of Object.values(shapes)) {
|
||||
const t = (shape as Record<string, unknown>).type as string || "unknown";
|
||||
shapeTypes[t] = (shapeTypes[t] || 0) + 1;
|
||||
}
|
||||
|
||||
spacesList.push({
|
||||
slug: data.meta.slug,
|
||||
name: data.meta.name,
|
||||
visibility: data.meta.visibility || "public_read",
|
||||
createdAt: data.meta.createdAt,
|
||||
ownerDID: data.meta.ownerDID,
|
||||
shapeCount,
|
||||
memberCount,
|
||||
fileSizeBytes,
|
||||
shapeTypes,
|
||||
});
|
||||
}
|
||||
|
||||
// Sort by creation date descending (newest first)
|
||||
spacesList.sort((a, b) => {
|
||||
const da = a.createdAt ? new Date(a.createdAt).getTime() : 0;
|
||||
const db = b.createdAt ? new Date(b.createdAt).getTime() : 0;
|
||||
return db - da;
|
||||
});
|
||||
|
||||
return c.json({ spaces: spacesList, total: spacesList.length });
|
||||
});
|
||||
|
||||
// ── Get pending access requests for spaces the user owns ──
|
||||
// NOTE: Must be defined BEFORE /:slug to avoid "notifications" matching as a slug
|
||||
|
||||
spaces.get("/notifications", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
|
|
@ -456,65 +516,6 @@ spaces.get("/:slug/access-requests", async (c) => {
|
|||
return c.json({ requests });
|
||||
});
|
||||
|
||||
// ── Admin: list ALL spaces with detailed stats ──
|
||||
|
||||
spaces.get("/admin", async (c) => {
|
||||
const STORAGE_DIR = process.env.STORAGE_DIR || "./data/communities";
|
||||
const slugs = await listCommunities();
|
||||
|
||||
const spacesList = [];
|
||||
for (const slug of slugs) {
|
||||
await loadCommunity(slug);
|
||||
const data = getDocumentData(slug);
|
||||
if (!data?.meta) continue;
|
||||
|
||||
const shapes = data.shapes || {};
|
||||
const members = data.members || {};
|
||||
const shapeCount = Object.keys(shapes).length;
|
||||
const memberCount = Object.keys(members).length;
|
||||
|
||||
// Get file size on disk
|
||||
let fileSizeBytes = 0;
|
||||
try {
|
||||
const s = await stat(`${STORAGE_DIR}/${slug}.automerge`);
|
||||
fileSizeBytes = s.size;
|
||||
} catch {
|
||||
try {
|
||||
const s = await stat(`${STORAGE_DIR}/${slug}.json`);
|
||||
fileSizeBytes = s.size;
|
||||
} catch { /* not on disk yet */ }
|
||||
}
|
||||
|
||||
// Count shapes by type
|
||||
const shapeTypes: Record<string, number> = {};
|
||||
for (const shape of Object.values(shapes)) {
|
||||
const t = (shape as Record<string, unknown>).type as string || "unknown";
|
||||
shapeTypes[t] = (shapeTypes[t] || 0) + 1;
|
||||
}
|
||||
|
||||
spacesList.push({
|
||||
slug: data.meta.slug,
|
||||
name: data.meta.name,
|
||||
visibility: data.meta.visibility || "public_read",
|
||||
createdAt: data.meta.createdAt,
|
||||
ownerDID: data.meta.ownerDID,
|
||||
shapeCount,
|
||||
memberCount,
|
||||
fileSizeBytes,
|
||||
shapeTypes,
|
||||
});
|
||||
}
|
||||
|
||||
// Sort by creation date descending (newest first)
|
||||
spacesList.sort((a, b) => {
|
||||
const da = a.createdAt ? new Date(a.createdAt).getTime() : 0;
|
||||
const db = b.createdAt ? new Date(b.createdAt).getTime() : 0;
|
||||
return db - da;
|
||||
});
|
||||
|
||||
return c.json({ spaces: spacesList, total: spacesList.length });
|
||||
});
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
// NESTING API
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
|
|
|
|||
Loading…
Reference in New Issue