dineway 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- package/dist/{apply-CAPvMfoU.mjs → apply-iVSqz2qs.mjs} +132 -39
- package/dist/astro/index.d.mts +18 -9
- package/dist/astro/index.mjs +238 -16
- package/dist/astro/middleware/auth.d.mts +16 -5
- package/dist/astro/middleware/auth.mjs +74 -37
- package/dist/astro/middleware/redirect.mjs +24 -8
- package/dist/astro/middleware/request-context.mjs +18 -5
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.mjs +411 -169
- package/dist/astro/types.d.mts +25 -8
- package/dist/{byline-DeWCMU_i.mjs → byline-OhH2dlRu.mjs} +6 -21
- package/dist/{bylines-DyqBV9EQ.mjs → bylines-BGpD9_hy.mjs} +16 -6
- package/dist/cache-BdSY-gQN.mjs +42 -0
- package/dist/chunks--4F8ddV4.mjs +18 -0
- package/dist/cli/index.mjs +935 -15
- package/dist/client/external-auth-headers.d.mts +1 -1
- package/dist/client/index.d.mts +11 -3
- package/dist/client/index.mjs +4 -3
- package/dist/{connection-C9pxzuag.mjs → connection-BCNICDWN.mjs} +22 -5
- package/dist/{content-zSgdNmnt.mjs → content-DWi4d0rT.mjs} +41 -2
- package/dist/database/instrumentation.d.mts +34 -0
- package/dist/database/instrumentation.mjs +53 -0
- package/dist/db/index.d.mts +3 -3
- package/dist/db/index.mjs +2 -2
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/libsql.mjs +11 -5
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/db/sqlite.mjs +7 -1
- package/dist/db-errors-CEqD7qH9.mjs +23 -0
- package/dist/{default-WYlzADZL.mjs → default-VjJyuuG9.mjs} +2 -0
- package/dist/{dialect-helpers-B9uSp2GJ.mjs → dialect-helpers-DhTzaUxP.mjs} +3 -0
- package/dist/{error-DrxtnGPg.mjs → error-BmL6QipT.mjs} +7 -3
- package/dist/{index-C-jx21qs.d.mts → index-yvc6E_17.d.mts} +157 -30
- package/dist/index.d.mts +11 -11
- package/dist/index.mjs +24 -22
- package/dist/{loader-qKmo0wAY.mjs → loader-sMG4TZ-u.mjs} +9 -3
- package/dist/media/index.d.mts +1 -1
- package/dist/media/index.mjs +1 -1
- package/dist/media/local-runtime.d.mts +7 -7
- package/dist/page/index.d.mts +10 -2
- package/dist/page/index.mjs +22 -1
- package/dist/patterns-CrCYkMBb.mjs +92 -0
- package/dist/{placeholder-bOx1xCTY.d.mts → placeholder--wOi4TbO.d.mts} +1 -1
- package/dist/{placeholder-B3knXwNc.mjs → placeholder-Cp8g5Emj.mjs} +1 -1
- package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
- package/dist/plugins/adapt-sandbox-entry.mjs +1 -1
- package/dist/{query-BiaPl_g2.mjs → query-kDmwCsHh.mjs} +118 -50
- package/dist/{redirect-JPqLAbxa.mjs → redirect-DnEWAkVg.mjs} +43 -99
- package/dist/{registry-DSd1GWB8.mjs → registry-C0zjeB9P.mjs} +191 -123
- package/dist/request-cache-Dk5qPSOx.mjs +66 -0
- package/dist/request-context.d.mts +4 -16
- package/dist/{runner-B5l1JfOj.d.mts → runner-CFI6B6J2.d.mts} +1 -1
- package/dist/{runner-BGUGywgG.mjs → runner-DWZm2KQm.mjs} +589 -137
- package/dist/runtime.d.mts +6 -6
- package/dist/runtime.mjs +2 -2
- package/dist/{search-BNruJHDL.mjs → search-BApX1xhM.mjs} +570 -424
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +11 -10
- package/dist/seo/index.d.mts +1 -1
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/local.mjs +1 -1
- package/dist/storage/s3.d.mts +11 -3
- package/dist/storage/s3.mjs +78 -15
- package/dist/taxonomies-1s5PaS_8.mjs +266 -0
- package/dist/transaction-Cn2rjY78.mjs +27 -0
- package/dist/{types-BgQeVaPj.d.mts → types-BuMDPy5C.d.mts} +52 -3
- package/dist/{types-DuNbGKjF.mjs → types-COeOq9nK.mjs} +6 -1
- package/dist/{types-ju-_ORz7.d.mts → types-CWbdtiux.d.mts} +13 -5
- package/dist/{types-D38djUXv.d.mts → types-Cj0KMIZV.d.mts} +16 -3
- package/dist/{types-DkvMXalq.d.mts → types-DOrVigru.d.mts} +159 -0
- package/dist/{validate-CXnRKfJK.mjs → validate-BZ5wnLLp.mjs} +2 -1
- package/dist/{validate-DVKJJ-M_.d.mts → validate-IPf8n4Fj.d.mts} +4 -51
- package/dist/{validate-CqRJb_xU.mjs → validate-VPnKoIzW.mjs} +10 -10
- package/dist/version-hmtC3Cmv.mjs +6 -0
- package/package.json +49 -38
- package/src/astro/routes/admin.astro +25 -9
- package/src/astro/routes/api/admin/api-tokens/[id].ts +4 -0
- package/src/astro/routes/api/admin/api-tokens/index.ts +24 -2
- package/src/astro/routes/api/admin/briefing.ts +76 -0
- package/src/astro/routes/api/admin/bylines/[id]/index.ts +3 -0
- package/src/astro/routes/api/admin/bylines/index.ts +2 -0
- package/src/astro/routes/api/admin/context/[id]/history.ts +35 -0
- package/src/astro/routes/api/admin/context/[id]/index.ts +35 -0
- package/src/astro/routes/api/admin/context/[id]/review.ts +57 -0
- package/src/astro/routes/api/admin/context/[id]/supersede.ts +58 -0
- package/src/astro/routes/api/admin/context/diff.ts +35 -0
- package/src/astro/routes/api/admin/context/index.ts +69 -0
- package/src/astro/routes/api/admin/context/stale.ts +35 -0
- package/src/astro/routes/api/admin/hitl-requests/[id]/index.ts +38 -0
- package/src/astro/routes/api/admin/hitl-requests/[id]/resolve.ts +54 -0
- package/src/astro/routes/api/admin/hitl-requests/index.ts +38 -0
- package/src/astro/routes/api/admin/hooks/exclusive/[hookName].ts +58 -17
- package/src/astro/routes/api/admin/oauth-clients/[id].ts +28 -1
- package/src/astro/routes/api/admin/oauth-clients/index.ts +25 -1
- package/src/astro/routes/api/admin/plugins/[id]/disable.ts +54 -2
- package/src/astro/routes/api/admin/plugins/[id]/enable.ts +54 -2
- package/src/astro/routes/api/admin/plugins/[id]/uninstall.ts +51 -1
- package/src/astro/routes/api/admin/plugins/[id]/update.ts +98 -3
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +72 -1
- package/src/astro/routes/api/admin/review-requests/[id]/index.ts +35 -0
- package/src/astro/routes/api/admin/review-requests/[id]/resolve.ts +52 -0
- package/src/astro/routes/api/admin/review-requests/index.ts +35 -0
- package/src/astro/routes/api/admin/users/[id]/disable.ts +26 -23
- package/src/astro/routes/api/admin/users/[id]/index.ts +41 -21
- package/src/astro/routes/api/auth/invite/register-options.ts +73 -0
- package/src/astro/routes/api/auth/magic-link/send.ts +2 -1
- package/src/astro/routes/api/auth/passkey/options.ts +2 -1
- package/src/astro/routes/api/auth/passkey/verify.ts +5 -1
- package/src/astro/routes/api/auth/signup/request.ts +20 -8
- package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +3 -4
- package/src/astro/routes/api/content/[collection]/[id]/compare.ts +1 -1
- package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +16 -2
- package/src/astro/routes/api/content/[collection]/[id]/duplicate.ts +16 -0
- package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +9 -0
- package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +1 -1
- package/src/astro/routes/api/content/[collection]/[id]/publish.ts +45 -1
- package/src/astro/routes/api/content/[collection]/[id]/restore.ts +12 -2
- package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +1 -1
- package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +24 -0
- package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +3 -0
- package/src/astro/routes/api/content/[collection]/[id]/translations.ts +20 -0
- package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +13 -0
- package/src/astro/routes/api/content/[collection]/[id].ts +36 -0
- package/src/astro/routes/api/content/[collection]/index.ts +48 -4
- package/src/astro/routes/api/content/[collection]/trash.ts +1 -1
- package/src/astro/routes/api/health.ts +54 -0
- package/src/astro/routes/api/import/wordpress/analyze.ts +2 -10
- package/src/astro/routes/api/import/wordpress/execute.ts +40 -6
- package/src/astro/routes/api/import/wordpress/prepare.ts +36 -5
- package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +33 -1
- package/src/astro/routes/api/import/wordpress-plugin/analyze.ts +3 -3
- package/src/astro/routes/api/import/wordpress-plugin/execute.ts +57 -15
- package/src/astro/routes/api/manifest.ts +13 -1
- package/src/astro/routes/api/mcp.ts +1 -0
- package/src/astro/routes/api/media/providers/[providerId]/[itemId].ts +7 -2
- package/src/astro/routes/api/media/upload-url.ts +11 -2
- package/src/astro/routes/api/media.ts +9 -7
- package/src/astro/routes/api/menus/[name]/items.ts +124 -5
- package/src/astro/routes/api/menus/[name]/reorder.ts +47 -1
- package/src/astro/routes/api/menus/[name].ts +84 -4
- package/src/astro/routes/api/menus/index.ts +46 -2
- package/src/astro/routes/api/oauth/authorize.ts +21 -8
- package/src/astro/routes/api/oauth/device/code.ts +2 -1
- package/src/astro/routes/api/oauth/device/token.ts +2 -1
- package/src/astro/routes/api/oauth/register.ts +182 -0
- package/src/astro/routes/api/oauth/token.ts +18 -7
- package/src/astro/routes/api/openapi.json.ts +3 -2
- package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +21 -4
- package/src/astro/routes/api/redirects/[id].ts +103 -4
- package/src/astro/routes/api/redirects/index.ts +50 -2
- package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +28 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +15 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +13 -0
- package/src/astro/routes/api/schema/collections/[slug]/index.ts +27 -0
- package/src/astro/routes/api/schema/collections/index.ts +14 -0
- package/src/astro/routes/api/search/index.ts +1 -0
- package/src/astro/routes/api/search/suggest.ts +1 -0
- package/src/astro/routes/api/sections/[slug].ts +123 -4
- package/src/astro/routes/api/sections/index.ts +57 -2
- package/src/astro/routes/api/settings.ts +51 -2
- package/src/astro/routes/api/setup/admin-verify.ts +25 -5
- package/src/astro/routes/api/setup/admin.ts +16 -8
- package/src/astro/routes/api/setup/index.ts +3 -2
- package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +141 -4
- package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +64 -2
- package/src/astro/routes/api/taxonomies/index.ts +57 -2
- package/src/astro/routes/api/well-known/auth.ts +3 -1
- package/src/astro/routes/api/well-known/oauth-authorization-server.ts +8 -5
- package/src/astro/routes/api/well-known/oauth-protected-resource.ts +3 -2
- package/src/astro/routes/api/widget-areas/[name]/reorder.ts +58 -16
- package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +124 -38
- package/src/astro/routes/api/widget-areas/[name]/widgets.ts +66 -20
- package/src/astro/routes/api/widget-areas/[name].ts +55 -7
- package/src/astro/routes/api/widget-areas/index.ts +56 -6
- package/src/components/DinewayHead.astro +15 -7
- package/src/components/DinewayMedia.astro +1 -1
- package/src/components/InlinePortableTextEditor.tsx +1 -1
- package/src/components/Table.astro +68 -41
- package/src/components/index.ts +2 -12
- package/src/components/marks.ts +19 -0
- package/LICENSE +0 -9
- /package/dist/{adapters-BlzWJG82.d.mts → adapters-C2ypTrZZ.d.mts} +0 -0
- /package/dist/{config-Cq8H0SfX.mjs → config-BXwuX8Bx.mjs} +0 -0
- /package/dist/{load-C6FCD1FU.mjs → load-Coc9HpHH.mjs} +0 -0
- /package/dist/{manifest-schema-CTSEyIJ3.mjs → manifest-schema-D1MSVnoI.mjs} +0 -0
- /package/dist/{mode-BlyYtIFO.mjs → mode-47goXBBK.mjs} +0 -0
- /package/dist/{tokens-4vgYuXsZ.mjs → tokens-CJz9ubV6.mjs} +0 -0
- /package/dist/{transport-C5FYnid7.mjs → transport-DB5eDN4x.mjs} +0 -0
- /package/dist/{transport-gIL-e43D.d.mts → transport-Wge_IzKl.d.mts} +0 -0
- /package/dist/{types-CLLdsG3g.d.mts → types-BzcUjoqg.d.mts} +0 -0
- /package/dist/{types-DShnjzb6.mjs → types-griIBQOQ.mjs} +0 -0
|
@@ -11,6 +11,11 @@ import { requireOwnerPerm } from "#api/authorize.js";
|
|
|
11
11
|
import { apiError, mapErrorStatus, unwrapResult } from "#api/error.js";
|
|
12
12
|
import { parseBody, isParseError } from "#api/parse.js";
|
|
13
13
|
import { contentScheduleBody } from "#api/schemas.js";
|
|
14
|
+
import {
|
|
15
|
+
contentApiRouteSource,
|
|
16
|
+
extractActivityItemId,
|
|
17
|
+
logContentActivity,
|
|
18
|
+
} from "#site-context/activity-events.js";
|
|
14
19
|
|
|
15
20
|
export const prerender = false;
|
|
16
21
|
|
|
@@ -69,6 +74,17 @@ export const POST: APIRoute = async ({ params, request, locals, cache }) => {
|
|
|
69
74
|
|
|
70
75
|
if (cache.enabled) await cache.invalidate({ tags: [collection, resolvedId ?? id] });
|
|
71
76
|
|
|
77
|
+
await logContentActivity(dineway.db, locals, {
|
|
78
|
+
action: "scheduled",
|
|
79
|
+
collection,
|
|
80
|
+
entryId: extractActivityItemId(result.data, resolvedId ?? id),
|
|
81
|
+
...contentApiRouteSource("scheduled"),
|
|
82
|
+
summary: `Scheduled content in ${collection}`,
|
|
83
|
+
detail: {
|
|
84
|
+
scheduledAt: body.scheduledAt,
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
|
|
72
88
|
return unwrapResult(result);
|
|
73
89
|
};
|
|
74
90
|
|
|
@@ -101,5 +117,13 @@ export const DELETE: APIRoute = async ({ params, locals, cache }) => {
|
|
|
101
117
|
|
|
102
118
|
if (cache.enabled) await cache.invalidate({ tags: [collection, resolvedId ?? id] });
|
|
103
119
|
|
|
120
|
+
await logContentActivity(dineway.db, locals, {
|
|
121
|
+
action: "unscheduled",
|
|
122
|
+
collection,
|
|
123
|
+
entryId: extractActivityItemId(result.data, resolvedId ?? id),
|
|
124
|
+
...contentApiRouteSource("unscheduled"),
|
|
125
|
+
summary: `Unscheduled content in ${collection}`,
|
|
126
|
+
});
|
|
127
|
+
|
|
104
128
|
return unwrapResult(result);
|
|
105
129
|
};
|
|
@@ -12,6 +12,7 @@ import { apiError, apiSuccess, handleError, requireDb } from "#api/error.js";
|
|
|
12
12
|
import { parseBody, isParseError } from "#api/parse.js";
|
|
13
13
|
import { contentTermsBody } from "#api/schemas.js";
|
|
14
14
|
import { TaxonomyRepository } from "#db/repositories/taxonomy.js";
|
|
15
|
+
import { invalidateTermCache } from "#taxonomies/index.js";
|
|
15
16
|
|
|
16
17
|
export const prerender = false;
|
|
17
18
|
|
|
@@ -122,6 +123,8 @@ export const POST: APIRoute = async ({ params, request, locals }) => {
|
|
|
122
123
|
// Set the terms (replaces existing)
|
|
123
124
|
await repo.setTermsForEntry(collection, id, taxonomy, termIds);
|
|
124
125
|
|
|
126
|
+
invalidateTermCache();
|
|
127
|
+
|
|
125
128
|
// Get the updated terms
|
|
126
129
|
const terms = await repo.getTermsForEntry(collection, id, taxonomy);
|
|
127
130
|
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* Returns all locale variants linked to the same translation group.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { hasPermission } from "@dineway-ai/auth";
|
|
9
10
|
import type { APIRoute } from "astro";
|
|
10
11
|
|
|
11
12
|
import { requirePerm } from "#api/authorize.js";
|
|
@@ -13,6 +14,12 @@ import { apiError, unwrapResult } from "#api/error.js";
|
|
|
13
14
|
|
|
14
15
|
export const prerender = false;
|
|
15
16
|
|
|
17
|
+
function isPublishedTranslation(value: unknown): boolean {
|
|
18
|
+
return (
|
|
19
|
+
typeof value === "object" && value !== null && "status" in value && value.status === "published"
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
16
23
|
export const GET: APIRoute = async ({ params, locals }) => {
|
|
17
24
|
const { dineway, user } = locals;
|
|
18
25
|
const denied = requirePerm(user, "content:read");
|
|
@@ -26,5 +33,18 @@ export const GET: APIRoute = async ({ params, locals }) => {
|
|
|
26
33
|
|
|
27
34
|
const result = await dineway.handleContentTranslations(collection, id);
|
|
28
35
|
|
|
36
|
+
if (result.success && !hasPermission(user, "content:read_drafts")) {
|
|
37
|
+
const data = result.data && typeof result.data === "object" ? result.data : {};
|
|
38
|
+
const translations =
|
|
39
|
+
"translations" in data && Array.isArray(data.translations) ? data.translations : [];
|
|
40
|
+
return unwrapResult({
|
|
41
|
+
success: true,
|
|
42
|
+
data: {
|
|
43
|
+
...data,
|
|
44
|
+
translations: translations.filter(isPublishedTranslation),
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
29
49
|
return unwrapResult(result);
|
|
30
50
|
};
|
|
@@ -8,6 +8,11 @@ import type { APIRoute } from "astro";
|
|
|
8
8
|
|
|
9
9
|
import { requireOwnerPerm } from "#api/authorize.js";
|
|
10
10
|
import { apiError, mapErrorStatus, unwrapResult } from "#api/error.js";
|
|
11
|
+
import {
|
|
12
|
+
contentApiRouteSource,
|
|
13
|
+
extractActivityItemId,
|
|
14
|
+
logContentActivity,
|
|
15
|
+
} from "#site-context/activity-events.js";
|
|
11
16
|
|
|
12
17
|
export const prerender = false;
|
|
13
18
|
|
|
@@ -52,5 +57,13 @@ export const POST: APIRoute = async ({ params, locals, cache }) => {
|
|
|
52
57
|
|
|
53
58
|
if (cache.enabled) await cache.invalidate({ tags: [collection, resolvedId] });
|
|
54
59
|
|
|
60
|
+
await logContentActivity(dineway.db, locals, {
|
|
61
|
+
action: "unpublished",
|
|
62
|
+
collection,
|
|
63
|
+
entryId: extractActivityItemId(result.data, resolvedId),
|
|
64
|
+
...contentApiRouteSource("unpublished"),
|
|
65
|
+
summary: `Unpublished content in ${collection}`,
|
|
66
|
+
});
|
|
67
|
+
|
|
55
68
|
return unwrapResult(result);
|
|
56
69
|
};
|
|
@@ -13,6 +13,12 @@ import { requirePerm, requireOwnerPerm } from "#api/authorize.js";
|
|
|
13
13
|
import { apiError, mapErrorStatus, unwrapResult } from "#api/error.js";
|
|
14
14
|
import { parseBody, isParseError } from "#api/parse.js";
|
|
15
15
|
import { contentUpdateBody } from "#api/schemas.js";
|
|
16
|
+
import {
|
|
17
|
+
activityChangedKeys,
|
|
18
|
+
contentApiRouteSource,
|
|
19
|
+
extractActivityItemId,
|
|
20
|
+
logContentActivity,
|
|
21
|
+
} from "#site-context/activity-events.js";
|
|
16
22
|
|
|
17
23
|
export const prerender = false;
|
|
18
24
|
|
|
@@ -30,6 +36,14 @@ export const GET: APIRoute = async ({ params, url, locals }) => {
|
|
|
30
36
|
|
|
31
37
|
const result = await dineway.handleContentGet(collection, id, locale);
|
|
32
38
|
|
|
39
|
+
if (result.success && !hasPermission(user, "content:read_drafts")) {
|
|
40
|
+
const item = result.data?.item;
|
|
41
|
+
const status = item && typeof item === "object" && "status" in item ? item.status : undefined;
|
|
42
|
+
if (status !== "published") {
|
|
43
|
+
return apiError("NOT_FOUND", "Content not found", 404);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
33
47
|
return unwrapResult(result);
|
|
34
48
|
};
|
|
35
49
|
|
|
@@ -87,6 +101,20 @@ export const PUT: APIRoute = async ({ params, request, locals, cache }) => {
|
|
|
87
101
|
|
|
88
102
|
if (cache.enabled) await cache.invalidate({ tags: [collection, resolvedId] });
|
|
89
103
|
|
|
104
|
+
await logContentActivity(dineway.db, locals, {
|
|
105
|
+
action: "updated",
|
|
106
|
+
collection,
|
|
107
|
+
entryId: extractActivityItemId(result.data, resolvedId),
|
|
108
|
+
...contentApiRouteSource("updated"),
|
|
109
|
+
summary: `Updated content in ${collection}`,
|
|
110
|
+
detail: {
|
|
111
|
+
changedFields: activityChangedKeys(body.data),
|
|
112
|
+
status: body.status ?? null,
|
|
113
|
+
slugChanged: body.slug !== undefined,
|
|
114
|
+
authorChanged: canChangeAuthor,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
|
|
90
118
|
return unwrapResult(result);
|
|
91
119
|
};
|
|
92
120
|
|
|
@@ -133,5 +161,13 @@ export const DELETE: APIRoute = async ({ params, locals, cache }) => {
|
|
|
133
161
|
|
|
134
162
|
if (cache.enabled) await cache.invalidate({ tags: [collection, resolvedId] });
|
|
135
163
|
|
|
164
|
+
await logContentActivity(dineway.db, locals, {
|
|
165
|
+
action: "deleted",
|
|
166
|
+
collection,
|
|
167
|
+
entryId: resolvedId,
|
|
168
|
+
...contentApiRouteSource("deleted"),
|
|
169
|
+
summary: `Deleted content in ${collection}`,
|
|
170
|
+
});
|
|
171
|
+
|
|
136
172
|
return unwrapResult(result);
|
|
137
173
|
};
|
|
@@ -5,12 +5,18 @@
|
|
|
5
5
|
* POST /_dineway/api/content/{collection} - Create content
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { hasPermission } from "@dineway-ai/auth";
|
|
8
9
|
import type { APIRoute } from "astro";
|
|
9
10
|
|
|
10
|
-
import { requirePerm } from "#api/authorize.js";
|
|
11
|
-
import { apiError, unwrapResult } from "#api/error.js";
|
|
11
|
+
import { requirePerm, requireOwnerPerm } from "#api/authorize.js";
|
|
12
|
+
import { apiError, mapErrorStatus, unwrapResult } from "#api/error.js";
|
|
12
13
|
import { parseBody, parseQuery, isParseError } from "#api/parse.js";
|
|
13
14
|
import { contentListQuery, contentCreateBody } from "#api/schemas.js";
|
|
15
|
+
import {
|
|
16
|
+
contentApiRouteSource,
|
|
17
|
+
extractActivityItemId,
|
|
18
|
+
logContentActivity,
|
|
19
|
+
} from "#site-context/activity-events.js";
|
|
14
20
|
|
|
15
21
|
export const prerender = false;
|
|
16
22
|
|
|
@@ -26,7 +32,11 @@ export const GET: APIRoute = async ({ params, url, locals }) => {
|
|
|
26
32
|
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
27
33
|
}
|
|
28
34
|
|
|
29
|
-
const
|
|
35
|
+
const listQuery = hasPermission(user, "content:read_drafts")
|
|
36
|
+
? query
|
|
37
|
+
: { ...query, status: "published" };
|
|
38
|
+
|
|
39
|
+
const result = await dineway.handleContentList(collection, listQuery);
|
|
30
40
|
|
|
31
41
|
return unwrapResult(result);
|
|
32
42
|
};
|
|
@@ -39,10 +49,31 @@ export const POST: APIRoute = async ({ params, request, locals, cache }) => {
|
|
|
39
49
|
const body = await parseBody(request, contentCreateBody);
|
|
40
50
|
if (isParseError(body)) return body;
|
|
41
51
|
|
|
42
|
-
if (!dineway?.handleContentCreate) {
|
|
52
|
+
if (!dineway?.handleContentCreate || (body.translationOf && !dineway?.handleContentGet)) {
|
|
43
53
|
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
44
54
|
}
|
|
45
55
|
|
|
56
|
+
if (body.translationOf && dineway.handleContentGet) {
|
|
57
|
+
const source = await dineway.handleContentGet(collection, body.translationOf);
|
|
58
|
+
if (!source.success) {
|
|
59
|
+
return apiError(
|
|
60
|
+
source.error?.code ?? "NOT_FOUND",
|
|
61
|
+
source.error?.message ?? "Translation source not found",
|
|
62
|
+
mapErrorStatus(source.error?.code),
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const sourceAuthorId =
|
|
67
|
+
typeof source.data?.item.authorId === "string" ? source.data.item.authorId : "";
|
|
68
|
+
const translationDenied = requireOwnerPerm(
|
|
69
|
+
user,
|
|
70
|
+
sourceAuthorId,
|
|
71
|
+
"content:edit_own",
|
|
72
|
+
"content:edit_any",
|
|
73
|
+
);
|
|
74
|
+
if (translationDenied) return translationDenied;
|
|
75
|
+
}
|
|
76
|
+
|
|
46
77
|
// Auto-set authorId to current user when creating content
|
|
47
78
|
const result = await dineway.handleContentCreate(collection, {
|
|
48
79
|
...body,
|
|
@@ -55,5 +86,18 @@ export const POST: APIRoute = async ({ params, request, locals, cache }) => {
|
|
|
55
86
|
|
|
56
87
|
if (cache.enabled) await cache.invalidate({ tags: [collection] });
|
|
57
88
|
|
|
89
|
+
await logContentActivity(dineway.db, locals, {
|
|
90
|
+
action: "created",
|
|
91
|
+
collection,
|
|
92
|
+
entryId: extractActivityItemId(result.data),
|
|
93
|
+
...contentApiRouteSource("created"),
|
|
94
|
+
summary: `Created content in ${collection}`,
|
|
95
|
+
detail: {
|
|
96
|
+
status: body.status ?? "draft",
|
|
97
|
+
locale: body.locale ?? null,
|
|
98
|
+
translationOf: body.translationOf ?? null,
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
58
102
|
return unwrapResult(result, 201);
|
|
59
103
|
};
|
|
@@ -17,7 +17,7 @@ export const GET: APIRoute = async ({ params, url, locals }) => {
|
|
|
17
17
|
const { dineway, user } = locals;
|
|
18
18
|
const collection = params.collection!;
|
|
19
19
|
|
|
20
|
-
const denied = requirePerm(user, "content:
|
|
20
|
+
const denied = requirePerm(user, "content:read_drafts");
|
|
21
21
|
if (denied) return denied;
|
|
22
22
|
|
|
23
23
|
if (!dineway?.handleContentListTrashed) {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { APIRoute } from "astro";
|
|
2
|
+
import { sql } from "kysely";
|
|
3
|
+
|
|
4
|
+
import { VERSION } from "../../../version.js";
|
|
5
|
+
|
|
6
|
+
export const prerender = false;
|
|
7
|
+
|
|
8
|
+
interface HealthResponse {
|
|
9
|
+
status: "ok" | "error";
|
|
10
|
+
version: string;
|
|
11
|
+
database?: "connected" | "disconnected";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const GET: APIRoute = async ({ locals, url }) => {
|
|
15
|
+
const check = url.searchParams.get("check");
|
|
16
|
+
|
|
17
|
+
if (check !== "ready") {
|
|
18
|
+
return Response.json({
|
|
19
|
+
status: "ok",
|
|
20
|
+
version: VERSION,
|
|
21
|
+
} satisfies HealthResponse);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const { dineway } = locals;
|
|
26
|
+
if (!dineway?.db) {
|
|
27
|
+
return Response.json(
|
|
28
|
+
{
|
|
29
|
+
status: "error",
|
|
30
|
+
version: VERSION,
|
|
31
|
+
database: "disconnected",
|
|
32
|
+
} satisfies HealthResponse,
|
|
33
|
+
{ status: 503 },
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
await sql`select 1`.execute(dineway.db);
|
|
38
|
+
|
|
39
|
+
return Response.json({
|
|
40
|
+
status: "ok",
|
|
41
|
+
version: VERSION,
|
|
42
|
+
database: "connected",
|
|
43
|
+
} satisfies HealthResponse);
|
|
44
|
+
} catch {
|
|
45
|
+
return Response.json(
|
|
46
|
+
{
|
|
47
|
+
status: "error",
|
|
48
|
+
version: VERSION,
|
|
49
|
+
database: "disconnected",
|
|
50
|
+
} satisfies HealthResponse,
|
|
51
|
+
{ status: 503 },
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
@@ -13,14 +13,12 @@ import mime from "mime/lite";
|
|
|
13
13
|
|
|
14
14
|
import { requirePerm } from "#api/authorize.js";
|
|
15
15
|
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
16
|
-
import {
|
|
16
|
+
import { sanitizeWordPressImportSlug } from "#import/wordpress-slugs.js";
|
|
17
17
|
import type { DinewayHandlers } from "#types";
|
|
18
18
|
|
|
19
19
|
export const prerender = false;
|
|
20
20
|
|
|
21
21
|
const NUMERIC_PATTERN = /^-?\d+(\.\d+)?$/;
|
|
22
|
-
const INVALID_SLUG_CHARS = /[^a-z0-9_]/g;
|
|
23
|
-
const LEADING_NON_ALPHA = /^[^a-z]+/;
|
|
24
22
|
|
|
25
23
|
/** Field compatibility status */
|
|
26
24
|
export type FieldCompatibility =
|
|
@@ -457,13 +455,7 @@ function isInternalMetaKey(key: string): boolean {
|
|
|
457
455
|
}
|
|
458
456
|
|
|
459
457
|
function sanitizeSlug(slug: string): string {
|
|
460
|
-
|
|
461
|
-
.toLowerCase()
|
|
462
|
-
.replace(INVALID_SLUG_CHARS, "_")
|
|
463
|
-
.replace(LEADING_NON_ALPHA, "");
|
|
464
|
-
if (!sanitized) return "imported";
|
|
465
|
-
if (RESERVED_COLLECTION_SLUGS.includes(sanitized)) return `wp_${sanitized}`;
|
|
466
|
-
return sanitized;
|
|
458
|
+
return sanitizeWordPressImportSlug(slug);
|
|
467
459
|
}
|
|
468
460
|
|
|
469
461
|
function mapPostTypeToCollection(postType: string): string {
|
|
@@ -18,13 +18,18 @@ import {
|
|
|
18
18
|
|
|
19
19
|
import { requirePerm } from "#api/authorize.js";
|
|
20
20
|
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
21
|
+
import {
|
|
22
|
+
ensureWorkflowHitlRouteRequest,
|
|
23
|
+
hitlRequiredRouteError,
|
|
24
|
+
resolveHitlRouteActor,
|
|
25
|
+
} from "#api/hitl-route-helpers.js";
|
|
21
26
|
import { BylineRepository } from "#db/repositories/byline.js";
|
|
22
27
|
import { resolveImportByline } from "#import/utils.js";
|
|
28
|
+
import { sanitizeWordPressImportSlug } from "#import/wordpress-slugs.js";
|
|
29
|
+
import { RiskPolicyEvaluator, WordPressImportHitlPayloadBuilder } from "#site-context/index.js";
|
|
23
30
|
import type { DinewayHandlers, DinewayManifest } from "#types";
|
|
24
31
|
import { slugify } from "#utils/slugify.js";
|
|
25
32
|
|
|
26
|
-
import { sanitizeSlug } from "./analyze.js";
|
|
27
|
-
|
|
28
33
|
export const prerender = false;
|
|
29
34
|
|
|
30
35
|
export interface ImportConfig {
|
|
@@ -75,6 +80,11 @@ export const POST: APIRoute = async ({ request, locals }) => {
|
|
|
75
80
|
const file = fileEntry instanceof File ? fileEntry : null;
|
|
76
81
|
const configEntry = formData.get("config");
|
|
77
82
|
const configJson = typeof configEntry === "string" ? configEntry : null;
|
|
83
|
+
const hitlRequestEntry = formData.get("hitlRequestId");
|
|
84
|
+
const hitlRequestId =
|
|
85
|
+
typeof hitlRequestEntry === "string" && hitlRequestEntry.length > 0
|
|
86
|
+
? hitlRequestEntry
|
|
87
|
+
: undefined;
|
|
78
88
|
|
|
79
89
|
if (!file) {
|
|
80
90
|
return apiError("VALIDATION_ERROR", "No file provided", 400);
|
|
@@ -85,9 +95,33 @@ export const POST: APIRoute = async ({ request, locals }) => {
|
|
|
85
95
|
}
|
|
86
96
|
|
|
87
97
|
const config: ImportConfig = JSON.parse(configJson);
|
|
98
|
+
const actor = resolveHitlRouteActor(locals);
|
|
99
|
+
const evaluator = new RiskPolicyEvaluator({
|
|
100
|
+
db: dineway.db,
|
|
101
|
+
handlers: dineway,
|
|
102
|
+
});
|
|
103
|
+
let text: string;
|
|
104
|
+
if (evaluator.requiresWorkflowHitl(actor.identity)) {
|
|
105
|
+
const fileBuffer = await file.arrayBuffer();
|
|
106
|
+
const action = await new WordPressImportHitlPayloadBuilder().buildExecuteRequest({
|
|
107
|
+
fileName: file.name,
|
|
108
|
+
fileBuffer,
|
|
109
|
+
config,
|
|
110
|
+
});
|
|
111
|
+
const decision = await evaluator.evaluateWorkflowHitl({
|
|
112
|
+
actor: actor.identity,
|
|
113
|
+
hitlRequestId,
|
|
114
|
+
action,
|
|
115
|
+
});
|
|
116
|
+
if (!decision.allowed) {
|
|
117
|
+
const ensured = await ensureWorkflowHitlRouteRequest(dineway.db, locals, decision.action);
|
|
118
|
+
return hitlRequiredRouteError(decision, ensured);
|
|
119
|
+
}
|
|
120
|
+
text = new TextDecoder().decode(fileBuffer);
|
|
121
|
+
} else {
|
|
122
|
+
text = await file.text();
|
|
123
|
+
}
|
|
88
124
|
|
|
89
|
-
// Parse WXR
|
|
90
|
-
const text = await file.text();
|
|
91
125
|
const wxr = await parseWxrString(text);
|
|
92
126
|
|
|
93
127
|
// Build attachment ID -> URL map for featured images
|
|
@@ -170,7 +204,7 @@ async function importContent(
|
|
|
170
204
|
|
|
171
205
|
// Defensive: mapping.collection is already sanitized by prepare, but the user
|
|
172
206
|
// could manually edit the import config between prepare and execute.
|
|
173
|
-
const collection =
|
|
207
|
+
const collection = sanitizeWordPressImportSlug(mapping.collection);
|
|
174
208
|
|
|
175
209
|
// Check if collection exists in manifest
|
|
176
210
|
if (!manifest?.collections[collection]) {
|
|
@@ -271,7 +305,7 @@ async function importContent(
|
|
|
271
305
|
console.error(`Import error for "${post.title || "Untitled"}":`, error);
|
|
272
306
|
result.errors.push({
|
|
273
307
|
title: post.title || "Untitled",
|
|
274
|
-
error: "Failed to import item",
|
|
308
|
+
error: error instanceof Error && error.message ? error.message : "Failed to import item",
|
|
275
309
|
});
|
|
276
310
|
}
|
|
277
311
|
}
|
|
@@ -8,15 +8,23 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { APIRoute } from "astro";
|
|
11
|
+
import { z } from "zod";
|
|
11
12
|
|
|
12
13
|
import { requirePerm } from "#api/authorize.js";
|
|
13
14
|
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
15
|
+
import {
|
|
16
|
+
ensureWorkflowHitlRouteRequest,
|
|
17
|
+
hitlRequiredRouteError,
|
|
18
|
+
resolveHitlRouteActor,
|
|
19
|
+
} from "#api/hitl-route-helpers.js";
|
|
14
20
|
import { isParseError, parseBody } from "#api/parse.js";
|
|
15
21
|
import { wpPrepareBody } from "#api/schemas.js";
|
|
22
|
+
import { sanitizeWordPressImportSlug } from "#import/wordpress-slugs.js";
|
|
16
23
|
import { FIELD_TYPES, type FieldType } from "#schema/types.js";
|
|
24
|
+
import { RiskPolicyEvaluator, WordPressImportHitlPayloadBuilder } from "#site-context/index.js";
|
|
17
25
|
import type { DinewayHandlers } from "#types";
|
|
18
26
|
|
|
19
|
-
import { capitalize,
|
|
27
|
+
import { capitalize, singularize, type ImportFieldDef } from "./analyze.js";
|
|
20
28
|
|
|
21
29
|
/** Validate that a string is a known FieldType, returning undefined if not */
|
|
22
30
|
function asFieldType(value: string): FieldType | undefined {
|
|
@@ -26,6 +34,10 @@ function asFieldType(value: string): FieldType | undefined {
|
|
|
26
34
|
|
|
27
35
|
export const prerender = false;
|
|
28
36
|
|
|
37
|
+
const prepareBodySchema = wpPrepareBody.extend({
|
|
38
|
+
hitlRequestId: z.string().min(1).optional(),
|
|
39
|
+
});
|
|
40
|
+
|
|
29
41
|
interface PrepareRequest {
|
|
30
42
|
postTypes: Array<{
|
|
31
43
|
name: string;
|
|
@@ -52,11 +64,30 @@ export const POST: APIRoute = async ({ request, locals }) => {
|
|
|
52
64
|
if (denied) return denied;
|
|
53
65
|
|
|
54
66
|
try {
|
|
55
|
-
const body = await parseBody(request,
|
|
67
|
+
const body = await parseBody(request, prepareBodySchema);
|
|
56
68
|
if (isParseError(body)) return body;
|
|
57
69
|
|
|
58
|
-
|
|
59
|
-
const
|
|
70
|
+
const actor = resolveHitlRouteActor(locals);
|
|
71
|
+
const evaluator = new RiskPolicyEvaluator({
|
|
72
|
+
db: dineway.db,
|
|
73
|
+
handlers: dineway,
|
|
74
|
+
});
|
|
75
|
+
const action = await new WordPressImportHitlPayloadBuilder().buildPrepareRequest({
|
|
76
|
+
postTypes: body.postTypes,
|
|
77
|
+
});
|
|
78
|
+
const decision = await evaluator.evaluateWorkflowHitl({
|
|
79
|
+
actor: actor.identity,
|
|
80
|
+
hitlRequestId: body.hitlRequestId,
|
|
81
|
+
action,
|
|
82
|
+
});
|
|
83
|
+
if (!decision.allowed) {
|
|
84
|
+
const ensured = await ensureWorkflowHitlRouteRequest(dineway.db, locals, decision.action);
|
|
85
|
+
return hitlRequiredRouteError(decision, ensured);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const result = await prepareImport(dineway.db, {
|
|
89
|
+
postTypes: body.postTypes,
|
|
90
|
+
});
|
|
60
91
|
|
|
61
92
|
return apiSuccess(result, result.success ? 200 : 400);
|
|
62
93
|
} catch (error) {
|
|
@@ -79,7 +110,7 @@ async function prepareImport(
|
|
|
79
110
|
};
|
|
80
111
|
|
|
81
112
|
for (const postType of request.postTypes) {
|
|
82
|
-
const collectionSlug =
|
|
113
|
+
const collectionSlug = sanitizeWordPressImportSlug(postType.collection);
|
|
83
114
|
|
|
84
115
|
try {
|
|
85
116
|
// Check if collection exists
|
|
@@ -12,17 +12,29 @@
|
|
|
12
12
|
|
|
13
13
|
import type { APIRoute } from "astro";
|
|
14
14
|
import { sql } from "kysely";
|
|
15
|
+
import { z } from "zod";
|
|
15
16
|
|
|
16
17
|
import { requirePerm } from "#api/authorize.js";
|
|
17
18
|
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
19
|
+
import {
|
|
20
|
+
ensureWorkflowHitlRouteRequest,
|
|
21
|
+
hitlRequiredRouteError,
|
|
22
|
+
resolveHitlRouteActor,
|
|
23
|
+
} from "#api/hitl-route-helpers.js";
|
|
18
24
|
import { isParseError, parseBody } from "#api/parse.js";
|
|
19
25
|
import { wpRewriteUrlsBody } from "#api/schemas.js";
|
|
26
|
+
import { validateIdentifier } from "#db/validate.js";
|
|
20
27
|
import { normalizeMediaValue } from "#media/normalize.js";
|
|
21
28
|
import type { MediaProvider } from "#media/types.js";
|
|
29
|
+
import { RiskPolicyEvaluator, WordPressImportHitlPayloadBuilder } from "#site-context/index.js";
|
|
22
30
|
import type { DinewayHandlers } from "#types";
|
|
23
31
|
|
|
24
32
|
export const prerender = false;
|
|
25
33
|
|
|
34
|
+
const rewriteUrlsBodySchema = wpRewriteUrlsBody.extend({
|
|
35
|
+
hitlRequestId: z.string().min(1).optional(),
|
|
36
|
+
});
|
|
37
|
+
|
|
26
38
|
const REGEX_SPECIAL_CHARS = /[.*+?^${}()|[\]\\]/g;
|
|
27
39
|
|
|
28
40
|
export interface RewriteUrlsResult {
|
|
@@ -47,7 +59,7 @@ export const POST: APIRoute = async ({ request, locals }) => {
|
|
|
47
59
|
if (denied) return denied;
|
|
48
60
|
|
|
49
61
|
try {
|
|
50
|
-
const body = await parseBody(request,
|
|
62
|
+
const body = await parseBody(request, rewriteUrlsBodySchema);
|
|
51
63
|
if (isParseError(body)) return body;
|
|
52
64
|
|
|
53
65
|
const urlEntries = Object.entries(body.urlMap);
|
|
@@ -60,6 +72,25 @@ export const POST: APIRoute = async ({ request, locals }) => {
|
|
|
60
72
|
});
|
|
61
73
|
}
|
|
62
74
|
|
|
75
|
+
const actor = resolveHitlRouteActor(locals);
|
|
76
|
+
const evaluator = new RiskPolicyEvaluator({
|
|
77
|
+
db: dineway.db,
|
|
78
|
+
handlers: dineway,
|
|
79
|
+
});
|
|
80
|
+
const action = await new WordPressImportHitlPayloadBuilder().buildRewriteUrlsRequest({
|
|
81
|
+
urlMap: body.urlMap,
|
|
82
|
+
collections: body.collections,
|
|
83
|
+
});
|
|
84
|
+
const decision = await evaluator.evaluateWorkflowHitl({
|
|
85
|
+
actor: actor.identity,
|
|
86
|
+
hitlRequestId: body.hitlRequestId,
|
|
87
|
+
action,
|
|
88
|
+
});
|
|
89
|
+
if (!decision.allowed) {
|
|
90
|
+
const ensured = await ensureWorkflowHitlRouteRequest(dineway.db, locals, decision.action);
|
|
91
|
+
return hitlRequiredRouteError(decision, ensured);
|
|
92
|
+
}
|
|
93
|
+
|
|
63
94
|
const getProvider = (id: string) => dineway.getMediaProvider(id);
|
|
64
95
|
const result = await rewriteUrls(dineway.db, body.urlMap, getProvider, body.collections);
|
|
65
96
|
|
|
@@ -280,6 +311,7 @@ async function rewriteUrls(
|
|
|
280
311
|
continue;
|
|
281
312
|
|
|
282
313
|
// Get table name
|
|
314
|
+
validateIdentifier(collection.slug, "collection slug");
|
|
283
315
|
const tableName = `ec_${collection.slug}`;
|
|
284
316
|
|
|
285
317
|
try {
|
|
@@ -15,7 +15,7 @@ import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
|
15
15
|
import { isParseError, parseBody } from "#api/parse.js";
|
|
16
16
|
import { wpPluginAnalyzeBody } from "#api/schemas.js";
|
|
17
17
|
import { getSource } from "#import/index.js";
|
|
18
|
-
import {
|
|
18
|
+
import { resolveAndValidateExternalUrl, SsrfError } from "#import/ssrf.js";
|
|
19
19
|
import type { ImportAnalysis } from "#import/types.js";
|
|
20
20
|
import type { DinewayHandlers } from "#types";
|
|
21
21
|
|
|
@@ -37,9 +37,9 @@ export const POST: APIRoute = async ({ request, locals }) => {
|
|
|
37
37
|
const body = await parseBody(request, wpPluginAnalyzeBody);
|
|
38
38
|
if (isParseError(body)) return body;
|
|
39
39
|
|
|
40
|
-
// SSRF: reject internal/private network targets
|
|
40
|
+
// SSRF: reject internal/private network targets before import work starts.
|
|
41
41
|
try {
|
|
42
|
-
|
|
42
|
+
await resolveAndValidateExternalUrl(body.url);
|
|
43
43
|
} catch (e) {
|
|
44
44
|
const msg = e instanceof SsrfError ? e.message : "Invalid URL";
|
|
45
45
|
return apiError("SSRF_BLOCKED", msg, 400);
|