Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ follows [Semantic Versioning](https://semver.org).
`services.authz.seedAdminTuples()` instead of importing `bootstrapAdminTuples`.
Core builds them once per site under `admin.runtime_dir`; the admin no longer
calls their factories. Requires `@dune/core` 0.36 or newer.
- **Blueprints and data paths come from core's `services`**: `services.blueprints`
(`has` / `resolve` / `validate`, read from the content engine at call time)
replaces importing `resolveBlueprint` / `validateFrontmatter`, and
`services.paths.dataDir` replaces `resolveDataDir()`. Requires `@dune/core`
0.36 or newer.
- **`GET /admin/api/search/analytics` is removed.** It summarised
`search-analytics.jsonl`, which nothing in core or the first-party plugins
ever wrote, so it always returned an empty summary.
- **Security policy, sessions, users and passwords come from core's `services`**
(`MountApi.services`, `@dune/core/plugin-api`) instead of imports from
`@dune/core`: HTML sanitising, SSRF-guarded fetch, body limits, client IP, the
Expand Down
3 changes: 1 addition & 2 deletions mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import { LocalAuthProvider } from "./src/admin/auth/local-provider.ts";
import type { AuthProvider } from "./src/admin/auth/provider.ts";
import type { ScheduledAction } from "@dune/core/workflow";
import { applyWorkflowTransition, SCHEDULED_ACTION_STATUS } from "./src/admin/workflow-actions.ts";
import { resolveDataDir } from "@dune/core/forms";
import { createCommentManager } from "./src/admin/comments.ts";
import { createCollabManager } from "./src/collab/mod.ts";
import type { MachineTranslator } from "@dune/core/mt";
Expand Down Expand Up @@ -146,7 +145,7 @@ export function createAdminPlugin(
};
const runtimeDir = adminCfg.runtime_dir ?? ".dune/admin";
// `system.data_dir` — the same resolution core uses for the submissions it stores.
const dataDir = resolveDataDir(config);
const dataDir = services.paths.dataDir;

// ── Admin services ────────────────────────────────────────────────────────

Expand Down
186 changes: 92 additions & 94 deletions src/admin/manifest.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,53 +51,52 @@ import * as r44 from "./routes/api/preview.ts";
import * as r45 from "./routes/api/registry/plugins.ts";
import * as r46 from "./routes/api/registry/themes.ts";
import * as r47 from "./routes/api/render-markdown.ts";
import * as r48 from "./routes/api/search/analytics.ts";
import * as r49 from "./routes/api/search/engines.ts";
import * as r50 from "./routes/api/sections.ts";
import * as r51 from "./routes/api/staging/[...path]/index.ts";
import * as r52 from "./routes/api/staging/[...path]/publish.ts";
import * as r53 from "./routes/api/theme-preview.ts";
import * as r54 from "./routes/api/themes/install.ts";
import * as r55 from "./routes/api/users/index.ts";
import * as r56 from "./routes/api/users/[id]/index.ts";
import * as r57 from "./routes/api/users/[id]/password.ts";
import * as r58 from "./routes/api/webhooks/deliveries.ts";
import * as r59 from "./routes/api/workflow/schedule/index.ts";
import * as r60 from "./routes/api/workflow/schedule/[id].ts";
import * as r61 from "./routes/api/workflow/scheduled/[...path].ts";
import * as r62 from "./routes/api/workflow/stages.ts";
import * as r63 from "./routes/api/workflow/status/[...path].ts";
import * as r64 from "./routes/api/workflow/transition.ts";
import * as r65 from "./routes/audit/index.tsx";
import * as r66 from "./routes/collab/edit-ws.ts";
import * as r67 from "./routes/collab/ws.ts";
import * as r68 from "./routes/config/index.tsx";
import * as r69 from "./routes/email-preview/index.tsx";
import * as r70 from "./routes/flex/index.tsx";
import * as r71 from "./routes/flex/[type]/index.tsx";
import * as r72 from "./routes/flex/[type]/[id].tsx";
import * as r73 from "./routes/i18n/index.tsx";
import * as r74 from "./routes/i18n/memory.tsx";
import * as r75 from "./routes/jobs/index.tsx";
import * as r76 from "./routes/login.tsx";
import * as r77 from "./routes/login/logout.ts";
import * as r78 from "./routes/marketplace/index.tsx";
import * as r79 from "./routes/media/index.tsx";
import * as r80 from "./routes/metrics/index.tsx";
import * as r81 from "./routes/pages/index.tsx";
import * as r82 from "./routes/pages/builder.tsx";
import * as r83 from "./routes/pages/edit.tsx";
import * as r84 from "./routes/pages/history.tsx";
import * as r85 from "./routes/plugins/index.tsx";
import * as r86 from "./routes/search/index.tsx";
import * as r87 from "./routes/submissions/index.tsx";
import * as r88 from "./routes/submissions/[form]/index.tsx";
import * as r89 from "./routes/submissions/[form]/[id]/index.tsx";
import * as r90 from "./routes/submissions/[form]/[id]/delete.ts";
import * as r91 from "./routes/submissions/[form]/[id]/files/[filename].ts";
import * as r92 from "./routes/submissions/[form]/[id]/status.ts";
import * as r93 from "./routes/themes/index.tsx";
import * as r94 from "./routes/users/index.tsx";
import * as r48 from "./routes/api/search/engines.ts";
import * as r49 from "./routes/api/sections.ts";
import * as r50 from "./routes/api/staging/[...path]/index.ts";
import * as r51 from "./routes/api/staging/[...path]/publish.ts";
import * as r52 from "./routes/api/theme-preview.ts";
import * as r53 from "./routes/api/themes/install.ts";
import * as r54 from "./routes/api/users/index.ts";
import * as r55 from "./routes/api/users/[id]/index.ts";
import * as r56 from "./routes/api/users/[id]/password.ts";
import * as r57 from "./routes/api/webhooks/deliveries.ts";
import * as r58 from "./routes/api/workflow/schedule/index.ts";
import * as r59 from "./routes/api/workflow/schedule/[id].ts";
import * as r60 from "./routes/api/workflow/scheduled/[...path].ts";
import * as r61 from "./routes/api/workflow/stages.ts";
import * as r62 from "./routes/api/workflow/status/[...path].ts";
import * as r63 from "./routes/api/workflow/transition.ts";
import * as r64 from "./routes/audit/index.tsx";
import * as r65 from "./routes/collab/edit-ws.ts";
import * as r66 from "./routes/collab/ws.ts";
import * as r67 from "./routes/config/index.tsx";
import * as r68 from "./routes/email-preview/index.tsx";
import * as r69 from "./routes/flex/index.tsx";
import * as r70 from "./routes/flex/[type]/index.tsx";
import * as r71 from "./routes/flex/[type]/[id].tsx";
import * as r72 from "./routes/i18n/index.tsx";
import * as r73 from "./routes/i18n/memory.tsx";
import * as r74 from "./routes/jobs/index.tsx";
import * as r75 from "./routes/login.tsx";
import * as r76 from "./routes/login/logout.ts";
import * as r77 from "./routes/marketplace/index.tsx";
import * as r78 from "./routes/media/index.tsx";
import * as r79 from "./routes/metrics/index.tsx";
import * as r80 from "./routes/pages/index.tsx";
import * as r81 from "./routes/pages/builder.tsx";
import * as r82 from "./routes/pages/edit.tsx";
import * as r83 from "./routes/pages/history.tsx";
import * as r84 from "./routes/plugins/index.tsx";
import * as r85 from "./routes/search/index.tsx";
import * as r86 from "./routes/submissions/index.tsx";
import * as r87 from "./routes/submissions/[form]/index.tsx";
import * as r88 from "./routes/submissions/[form]/[id]/index.tsx";
import * as r89 from "./routes/submissions/[form]/[id]/delete.ts";
import * as r90 from "./routes/submissions/[form]/[id]/files/[filename].ts";
import * as r91 from "./routes/submissions/[form]/[id]/status.ts";
import * as r92 from "./routes/themes/index.tsx";
import * as r93 from "./routes/users/index.tsx";

/** Shape of an fs-convention route module (subset Fresh cares about). */
export interface AdminRouteModule {
Expand Down Expand Up @@ -165,53 +164,52 @@ export const adminRoutes: AdminRouteEntry[] = [
{ pattern: "/api/registry/plugins", mod: r45 },
{ pattern: "/api/registry/themes", mod: r46 },
{ pattern: "/api/render-markdown", mod: r47 },
{ pattern: "/api/search/analytics", mod: r48 },
{ pattern: "/api/search/engines", mod: r49 },
{ pattern: "/api/sections", mod: r50 },
{ pattern: "/api/staging/:path*", mod: r51 },
{ pattern: "/api/staging/:path*/publish", mod: r52 },
{ pattern: "/api/theme-preview", mod: r53 },
{ pattern: "/api/themes/install", mod: r54 },
{ pattern: "/api/users", mod: r55 },
{ pattern: "/api/users/:id", mod: r56 },
{ pattern: "/api/users/:id/password", mod: r57 },
{ pattern: "/api/webhooks/deliveries", mod: r58 },
{ pattern: "/api/workflow/schedule", mod: r59 },
{ pattern: "/api/workflow/schedule/:id", mod: r60 },
{ pattern: "/api/workflow/scheduled/:path*", mod: r61 },
{ pattern: "/api/workflow/stages", mod: r62 },
{ pattern: "/api/workflow/status/:path*", mod: r63 },
{ pattern: "/api/workflow/transition", mod: r64 },
{ pattern: "/audit", mod: r65 },
{ pattern: "/collab/edit-ws", mod: r66 },
{ pattern: "/collab/ws", mod: r67 },
{ pattern: "/config", mod: r68 },
{ pattern: "/email-preview", mod: r69 },
{ pattern: "/flex", mod: r70 },
{ pattern: "/flex/:type", mod: r71 },
{ pattern: "/flex/:type/:id", mod: r72 },
{ pattern: "/i18n", mod: r73 },
{ pattern: "/i18n/memory", mod: r74 },
{ pattern: "/jobs", mod: r75 },
{ pattern: "/login", mod: r76 },
{ pattern: "/login/logout", mod: r77 },
{ pattern: "/marketplace", mod: r78 },
{ pattern: "/media", mod: r79 },
{ pattern: "/metrics", mod: r80 },
{ pattern: "/pages", mod: r81 },
{ pattern: "/pages/builder", mod: r82 },
{ pattern: "/pages/edit", mod: r83 },
{ pattern: "/pages/history", mod: r84 },
{ pattern: "/plugins", mod: r85 },
{ pattern: "/search", mod: r86 },
{ pattern: "/submissions", mod: r87 },
{ pattern: "/submissions/:form", mod: r88 },
{ pattern: "/submissions/:form/:id", mod: r89 },
{ pattern: "/submissions/:form/:id/delete", mod: r90 },
{ pattern: "/submissions/:form/:id/files/:filename", mod: r91 },
{ pattern: "/submissions/:form/:id/status", mod: r92 },
{ pattern: "/themes", mod: r93 },
{ pattern: "/users", mod: r94 },
{ pattern: "/api/search/engines", mod: r48 },
{ pattern: "/api/sections", mod: r49 },
{ pattern: "/api/staging/:path*", mod: r50 },
{ pattern: "/api/staging/:path*/publish", mod: r51 },
{ pattern: "/api/theme-preview", mod: r52 },
{ pattern: "/api/themes/install", mod: r53 },
{ pattern: "/api/users", mod: r54 },
{ pattern: "/api/users/:id", mod: r55 },
{ pattern: "/api/users/:id/password", mod: r56 },
{ pattern: "/api/webhooks/deliveries", mod: r57 },
{ pattern: "/api/workflow/schedule", mod: r58 },
{ pattern: "/api/workflow/schedule/:id", mod: r59 },
{ pattern: "/api/workflow/scheduled/:path*", mod: r60 },
{ pattern: "/api/workflow/stages", mod: r61 },
{ pattern: "/api/workflow/status/:path*", mod: r62 },
{ pattern: "/api/workflow/transition", mod: r63 },
{ pattern: "/audit", mod: r64 },
{ pattern: "/collab/edit-ws", mod: r65 },
{ pattern: "/collab/ws", mod: r66 },
{ pattern: "/config", mod: r67 },
{ pattern: "/email-preview", mod: r68 },
{ pattern: "/flex", mod: r69 },
{ pattern: "/flex/:type", mod: r70 },
{ pattern: "/flex/:type/:id", mod: r71 },
{ pattern: "/i18n", mod: r72 },
{ pattern: "/i18n/memory", mod: r73 },
{ pattern: "/jobs", mod: r74 },
{ pattern: "/login", mod: r75 },
{ pattern: "/login/logout", mod: r76 },
{ pattern: "/marketplace", mod: r77 },
{ pattern: "/media", mod: r78 },
{ pattern: "/metrics", mod: r79 },
{ pattern: "/pages", mod: r80 },
{ pattern: "/pages/builder", mod: r81 },
{ pattern: "/pages/edit", mod: r82 },
{ pattern: "/pages/history", mod: r83 },
{ pattern: "/plugins", mod: r84 },
{ pattern: "/search", mod: r85 },
{ pattern: "/submissions", mod: r86 },
{ pattern: "/submissions/:form", mod: r87 },
{ pattern: "/submissions/:form/:id", mod: r88 },
{ pattern: "/submissions/:form/:id/delete", mod: r89 },
{ pattern: "/submissions/:form/:id/files/:filename", mod: r90 },
{ pattern: "/submissions/:form/:id/status", mod: r91 },
{ pattern: "/themes", mod: r92 },
{ pattern: "/users", mod: r93 },
];

/** Island files under src/admin/islands/, for Builder.registerIsland(). */
Expand Down
5 changes: 2 additions & 3 deletions src/admin/routes/api/config/theme-config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/** GET + PUT /admin/api/config/theme-config */

import { resolveDataDir } from "@dune/core/forms";
import type { AdminState } from "../../../types.ts";
import { requirePermission, json, serverError, csrfCheck } from "../_utils.ts";
import type { BlueprintField } from "@dune/core/blueprints";
Expand All @@ -25,7 +24,7 @@ export const handler = {
const denied = await requirePermission(ctx, "config.update");
if (denied) return denied;

const { engine, storage, config, hooks } = ctx.state.adminContext;
const { engine, storage, hooks } = ctx.state.adminContext;
try {
const body = await ctx.req.json() as Record<string, unknown>;
const manifest = engine.themes.theme.manifest;
Expand Down Expand Up @@ -59,7 +58,7 @@ export const handler = {
}
}

const dataDir = resolveDataDir(config);
const dataDir = ctx.state.adminContext.services.paths.dataDir;
const themeConfigPath = `${dataDir}/theme-config.json`;
const themeName = engine.config.theme.name;

Expand Down
11 changes: 4 additions & 7 deletions src/admin/routes/api/pages/[...path]/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import type { AdminState } from "../../../../types.ts";
import { requirePermission, requireTsxWrite, requirePageOwner, requireOwnedPage, pageOwnerId, json, serverError, actorFromAuth, getClientIp, csrfCheck, validatePagePath } from "../../_utils.ts";
import { stringify as stringifyYaml } from "@std/yaml";
import { resolveBlueprint, validateFrontmatter } from "@dune/core/blueprints";
import { fireContentWebhooks } from "../../../../../admin/webhooks.ts";
import type { PageFrontmatter } from "@dune/core/content/types";
import type { FreshContext } from "fresh";
Expand Down Expand Up @@ -51,10 +50,7 @@ export const handler = {
// nothing for every site with blueprints configured. Resolve
// inheritance (`extends`) the same way PUT's validateFrontmatter()
// does, rather than sending the raw unresolved definition.
const rawBlueprint = engine.blueprints[page.template];
const blueprint = rawBlueprint
? resolveBlueprint(page.template, rawBlueprint, engine.blueprints, 0)
: null;
const blueprint = ctx.state.adminContext.services.blueprints.resolve(page.template);
return json({
sourcePath: page.sourcePath, route: page.route, format: page.format,
template: page.template, frontmatter: page.frontmatter, rawContent: page.rawContent,
Expand Down Expand Up @@ -113,8 +109,9 @@ export const handler = {
else delete mergedFm.createdBy;

const template = (mergedFm.template as string) ?? page.template;
if (engine.blueprints[template]) {
const errors = validateFrontmatter(mergedFm as PageFrontmatter, template, engine.blueprints);
const { blueprints } = ctx.state.adminContext.services;
if (blueprints.has(template)) {
const errors = blueprints.validate(mergedFm as PageFrontmatter, template);
if (errors.length > 0) {
return json({ error: "Blueprint validation failed", validationErrors: errors }, 422);
}
Expand Down
3 changes: 1 addition & 2 deletions src/admin/routes/api/plugins/[name]/config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/** PUT /admin/api/plugins/:name/config */

import { resolveDataDir } from "@dune/core/forms";
import type { AdminState } from "../../../../types.ts";
import { requirePermission, json, serverError, csrfCheck } from "../../_utils.ts";
import type { BlueprintField } from "@dune/core/blueprints";
Expand Down Expand Up @@ -75,7 +74,7 @@ export const handler = {
if (errors.length > 0) return json({ error: `Missing required fields: ${errors.join(", ")}` }, 422);
}

const dataDir = resolveDataDir(config);
const dataDir = ctx.state.adminContext.services.paths.dataDir;
const filePath = `${dataDir}/plugins/${pluginName}.json`;
await storage.write(filePath, new TextEncoder().encode(JSON.stringify(body, null, 2)));

Expand Down
23 changes: 0 additions & 23 deletions src/admin/routes/api/search/analytics.ts

This file was deleted.

2 changes: 1 addition & 1 deletion tests/admin/pages_edit_blueprint_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ function makeCtx(opts: {
state: {
auth: { authenticated: true, user: { id: "1", username: "admin", roles: ["admin"] } },
adminContext: {
services: makeServices(null),
services: makeServices(null, { getBlueprints: () => engine.blueprints as never }),
engine,
storage: opts.storage,
config: { system: { content: { dir: "content" } }, admin: {} },
Expand Down
2 changes: 2 additions & 0 deletions tests/admin/theme_config_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import { assertEquals } from "https://deno.land/std@0.224.0/assert/mod.ts";
import { handler } from "../../src/admin/routes/api/config/theme-config.ts";
import { makeServices } from "../helpers/services.ts";

// deno-lint-ignore no-explicit-any
function makeStorage(initial: Record<string, string> = {}): any {
Expand Down Expand Up @@ -48,6 +49,7 @@ function makeCtx(opts: {
state: {
auth: { authenticated: true, user: { id: "1", username: "admin" } },
adminContext: {
services: makeServices(opts.storage),
engine,
storage: opts.storage,
config: { admin: { data_dir: "data" } },
Expand Down
5 changes: 3 additions & 2 deletions tests/helpers/services.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { createPluginServices } from "@dune/core/plugin-api";
import type { PluginServices } from "@dune/core/plugin-api";
import type { PluginServices, PluginServicesOptions } from "@dune/core/plugin-api";
import type { DuneConfig } from "@dune/core/config";
import type { StorageAdapter } from "@dune/core/storage";

/** Core's plugin services over a test storage (default config: local sessions). */
export function makeServices(storage: unknown): PluginServices {
export function makeServices(storage: unknown, extra: Partial<PluginServicesOptions> = {}): PluginServices {
return createPluginServices({
config: { system: {}, site: {} } as unknown as DuneConfig,
storage: storage as StorageAdapter,
...extra,
});
}
Loading