dineway 0.1.4 → 0.1.5
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-ByRGV2pq.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-BKXPsfmJ.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
package/dist/page/index.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { A as PageMetadataContribution, D as PageFragmentContribution, N as PageMetadataLinkRel, P as PagePlacement, q as PublicPageContext, t as BreadcrumbItem } from "../types-Cj0KMIZV.mjs";
|
|
2
|
+
import { n as SeoSettings } from "../types-BuMDPy5C.mjs";
|
|
2
3
|
|
|
3
4
|
//#region src/page/context.d.ts
|
|
4
5
|
/** Fields shared by both input forms */
|
|
@@ -112,6 +113,13 @@ declare function renderFragments(contributions: PageFragmentContribution[], plac
|
|
|
112
113
|
* Returns an empty array if no SEO-relevant data is present.
|
|
113
114
|
*/
|
|
114
115
|
declare function generateBaseSeoContributions(page: PublicPageContext): PageMetadataContribution[];
|
|
116
|
+
/**
|
|
117
|
+
* Generate site-level SEO metadata contributions from SiteSettings.seo.
|
|
118
|
+
*
|
|
119
|
+
* These tags apply to every page, so they're sourced from global site settings
|
|
120
|
+
* rather than page-specific context.
|
|
121
|
+
*/
|
|
122
|
+
declare function generateSiteSeoContributions(seoSettings: SeoSettings | undefined): PageMetadataContribution[];
|
|
115
123
|
//#endregion
|
|
116
124
|
//#region src/page/jsonld.d.ts
|
|
117
125
|
/**
|
|
@@ -145,4 +153,4 @@ interface DinewayPageRuntime {
|
|
|
145
153
|
*/
|
|
146
154
|
declare function getPageRuntime(locals: Record<string, unknown>): DinewayPageRuntime | undefined;
|
|
147
155
|
//#endregion
|
|
148
|
-
export { type CreatePublicPageContextInput, DinewayPageRuntime, type ResolvedPageMetadata, buildBlogPostingJsonLd, buildWebSiteJsonLd, cleanJsonLd, createPublicPageContext, escapeHtmlAttr, generateBaseSeoContributions, getPageRuntime, renderFragments, renderPageMetadata, resolveFragments, resolvePageMetadata, safeJsonLdSerialize };
|
|
156
|
+
export { type CreatePublicPageContextInput, DinewayPageRuntime, type ResolvedPageMetadata, buildBlogPostingJsonLd, buildWebSiteJsonLd, cleanJsonLd, createPublicPageContext, escapeHtmlAttr, generateBaseSeoContributions, generateSiteSeoContributions, getPageRuntime, renderFragments, renderPageMetadata, resolveFragments, resolvePageMetadata, safeJsonLdSerialize };
|
package/dist/page/index.mjs
CHANGED
|
@@ -403,6 +403,27 @@ function generateBaseSeoContributions(page) {
|
|
|
403
403
|
}
|
|
404
404
|
return contributions;
|
|
405
405
|
}
|
|
406
|
+
/**
|
|
407
|
+
* Generate site-level SEO metadata contributions from SiteSettings.seo.
|
|
408
|
+
*
|
|
409
|
+
* These tags apply to every page, so they're sourced from global site settings
|
|
410
|
+
* rather than page-specific context.
|
|
411
|
+
*/
|
|
412
|
+
function generateSiteSeoContributions(seoSettings) {
|
|
413
|
+
const contributions = [];
|
|
414
|
+
if (!seoSettings) return contributions;
|
|
415
|
+
if (seoSettings.googleVerification) contributions.push({
|
|
416
|
+
kind: "meta",
|
|
417
|
+
name: "google-site-verification",
|
|
418
|
+
content: seoSettings.googleVerification
|
|
419
|
+
});
|
|
420
|
+
if (seoSettings.bingVerification) contributions.push({
|
|
421
|
+
kind: "meta",
|
|
422
|
+
name: "msvalidate.01",
|
|
423
|
+
content: seoSettings.bingVerification
|
|
424
|
+
});
|
|
425
|
+
return contributions;
|
|
426
|
+
}
|
|
406
427
|
|
|
407
428
|
//#endregion
|
|
408
429
|
//#region src/page/index.ts
|
|
@@ -416,4 +437,4 @@ function getPageRuntime(locals) {
|
|
|
416
437
|
}
|
|
417
438
|
|
|
418
439
|
//#endregion
|
|
419
|
-
export { buildBlogPostingJsonLd, buildWebSiteJsonLd, cleanJsonLd, createPublicPageContext, escapeHtmlAttr, generateBaseSeoContributions, getPageRuntime, renderFragments, renderPageMetadata, resolveFragments, resolvePageMetadata, safeJsonLdSerialize };
|
|
440
|
+
export { buildBlogPostingJsonLd, buildWebSiteJsonLd, cleanJsonLd, createPublicPageContext, escapeHtmlAttr, generateBaseSeoContributions, generateSiteSeoContributions, getPageRuntime, renderFragments, renderPageMetadata, resolveFragments, resolvePageMetadata, safeJsonLdSerialize };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
//#region src/redirects/patterns.ts
|
|
2
|
+
/**
|
|
3
|
+
* URL pattern matching for redirects.
|
|
4
|
+
*
|
|
5
|
+
* Uses Astro's route syntax: [param] for named segments, [...rest] for catch-all.
|
|
6
|
+
* Compiles patterns to safe regexes -- no user-supplied regex, no ReDoS risk.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* const compiled = compilePattern("/old-blog/[...path]");
|
|
11
|
+
* const match = matchPattern(compiled, "/old-blog/2024/01/post");
|
|
12
|
+
* // match = { path: "2024/01/post" }
|
|
13
|
+
*
|
|
14
|
+
* interpolateDestination("/blog/[...path]", match);
|
|
15
|
+
* // "/blog/2024/01/post"
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
/** Matches [paramName] placeholders */
|
|
19
|
+
const PARAM_PATTERN = /\[(\w+)\]/g;
|
|
20
|
+
/** Matches [...splatName] placeholders */
|
|
21
|
+
const SPLAT_PATTERN = /\[\.\.\.(\w+)\]/g;
|
|
22
|
+
/** Combined pattern for validation: matches both [param] and [...splat] */
|
|
23
|
+
const ANY_PLACEHOLDER = /\[(?:\.\.\.)?(\w+)\]/g;
|
|
24
|
+
/** Split on capture groups in compiled regex string */
|
|
25
|
+
const CAPTURE_GROUP_SPLIT = /(\([^)]+\))/;
|
|
26
|
+
/** Escape regex-special characters in literal parts */
|
|
27
|
+
const REGEX_SPECIAL_CHARS = /[.*+?^${}|\\]/g;
|
|
28
|
+
/**
|
|
29
|
+
* Returns true if a source string contains [param] or [...splat] placeholders.
|
|
30
|
+
*/
|
|
31
|
+
function isPattern(source) {
|
|
32
|
+
return source.match(ANY_PLACEHOLDER) !== null;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Compile a URL pattern into a regex for matching.
|
|
36
|
+
*
|
|
37
|
+
* - `[param]` matches a single path segment (`[^/]+`)
|
|
38
|
+
* - `[...rest]` matches one or more remaining segments (`.+`)
|
|
39
|
+
*/
|
|
40
|
+
function compilePattern(source) {
|
|
41
|
+
const paramNames = [];
|
|
42
|
+
let regexStr = source.replace(SPLAT_PATTERN, (_match, name) => {
|
|
43
|
+
paramNames.push(name);
|
|
44
|
+
return "(.+)";
|
|
45
|
+
});
|
|
46
|
+
regexStr = regexStr.replace(PARAM_PATTERN, (_match, name) => {
|
|
47
|
+
paramNames.push(name);
|
|
48
|
+
return "([^/]+)";
|
|
49
|
+
});
|
|
50
|
+
const escaped = regexStr.split(CAPTURE_GROUP_SPLIT).map((part, i) => {
|
|
51
|
+
if (i % 2 === 1) return part;
|
|
52
|
+
return part.replace(REGEX_SPECIAL_CHARS, "\\$&");
|
|
53
|
+
}).join("");
|
|
54
|
+
return {
|
|
55
|
+
regex: new RegExp(`^${escaped}$`),
|
|
56
|
+
paramNames,
|
|
57
|
+
source
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Match a path against a compiled pattern.
|
|
62
|
+
* Returns captured params or null if no match.
|
|
63
|
+
*/
|
|
64
|
+
function matchPattern(compiled, path) {
|
|
65
|
+
const match = path.match(compiled.regex);
|
|
66
|
+
if (!match) return null;
|
|
67
|
+
const params = {};
|
|
68
|
+
for (let i = 0; i < compiled.paramNames.length; i++) {
|
|
69
|
+
const value = match[i + 1];
|
|
70
|
+
if (value !== void 0) params[compiled.paramNames[i]] = value;
|
|
71
|
+
}
|
|
72
|
+
return params;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Interpolate captured params into a destination pattern.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* interpolateDestination("/blog/[...path]", { path: "2024/01/post" })
|
|
79
|
+
* // "/blog/2024/01/post"
|
|
80
|
+
*/
|
|
81
|
+
function interpolateDestination(destination, params) {
|
|
82
|
+
let result = destination.replace(SPLAT_PATTERN, (_match, name) => {
|
|
83
|
+
return params[name] ?? "";
|
|
84
|
+
});
|
|
85
|
+
result = result.replace(PARAM_PATTERN, (_match, name) => {
|
|
86
|
+
return params[name] ?? "";
|
|
87
|
+
});
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
//#endregion
|
|
92
|
+
export { matchPattern as i, interpolateDestination as n, isPattern as r, compilePattern as t };
|
|
@@ -260,7 +260,7 @@ declare function normalizeMediaValue(value: unknown, getProvider: (id: string) =
|
|
|
260
260
|
*
|
|
261
261
|
* Generates blurhash and dominant color from image buffers for LQIP support.
|
|
262
262
|
* Decodes images via jpeg-js (pure JS) and upng-js (pure JS, uses pako for
|
|
263
|
-
* deflate). No
|
|
263
|
+
* deflate). No native image bindings required, so it works in server runtimes.
|
|
264
264
|
*/
|
|
265
265
|
interface PlaceholderData {
|
|
266
266
|
blurhash: string;
|
|
@@ -132,7 +132,7 @@ function recordToMediaValue(obj) {
|
|
|
132
132
|
*
|
|
133
133
|
* Generates blurhash and dominant color from image buffers for LQIP support.
|
|
134
134
|
* Decodes images via jpeg-js (pure JS) and upng-js (pure JS, uses pako for
|
|
135
|
-
* deflate). No
|
|
135
|
+
* deflate). No native image bindings required, so it works in server runtimes.
|
|
136
136
|
*/
|
|
137
137
|
const SUPPORTED_TYPES = {
|
|
138
138
|
"image/jpeg": "jpeg",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import "../types-
|
|
2
|
-
import { wn as PluginDescriptor } from "../index-
|
|
3
|
-
import "../runner-
|
|
4
|
-
import {
|
|
5
|
-
import "../validate-
|
|
1
|
+
import "../types-DOrVigru.mjs";
|
|
2
|
+
import { wn as PluginDescriptor } from "../index-yvc6E_17.mjs";
|
|
3
|
+
import "../runner-CFI6B6J2.mjs";
|
|
4
|
+
import { X as ResolvedPlugin, tt as StandardPluginDefinition } from "../types-Cj0KMIZV.mjs";
|
|
5
|
+
import "../validate-IPf8n4Fj.mjs";
|
|
6
6
|
|
|
7
7
|
//#region src/plugins/adapt-sandbox-entry.d.ts
|
|
8
8
|
/**
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-ClPoSABd.mjs";
|
|
2
|
-
import { n as getI18nConfig, r as isI18nEnabled, t as getFallbackChain } from "./config-Cq8H0SfX.mjs";
|
|
3
2
|
import { getRequestContext } from "./request-context.mjs";
|
|
3
|
+
import { n as getI18nConfig, r as isI18nEnabled, t as getFallbackChain } from "./config-BXwuX8Bx.mjs";
|
|
4
|
+
import { n as requestCached } from "./request-cache-Dk5qPSOx.mjs";
|
|
5
|
+
import { t as isMissingTableError } from "./db-errors-CEqD7qH9.mjs";
|
|
4
6
|
|
|
5
7
|
//#region src/visual-editing/editable.ts
|
|
6
8
|
/**
|
|
@@ -76,6 +78,7 @@ var query_exports = /* @__PURE__ */ __exportAll({
|
|
|
76
78
|
getDinewayEntry: () => getDinewayEntry,
|
|
77
79
|
getEditMeta: () => getEditMeta,
|
|
78
80
|
getTranslations: () => getTranslations,
|
|
81
|
+
invalidateUrlPatternCache: () => invalidateUrlPatternCache,
|
|
79
82
|
resolveDinewayPath: () => resolveDinewayPath
|
|
80
83
|
});
|
|
81
84
|
const COLLECTION_NAME = "_dineway";
|
|
@@ -166,42 +169,59 @@ function entryEditOptions(entry) {
|
|
|
166
169
|
* ```
|
|
167
170
|
*/
|
|
168
171
|
async function getDinewayCollection(type, filter) {
|
|
169
|
-
const { getLiveCollection } = await import("astro:content");
|
|
170
172
|
const ctx = getRequestContext();
|
|
171
173
|
const i18nConfig = getI18nConfig();
|
|
172
174
|
const resolvedLocale = filter?.locale ?? ctx?.locale ?? (isI18nEnabled() ? i18nConfig.defaultLocale : void 0);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
error
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const
|
|
193
|
-
|
|
175
|
+
return requestCached(collectionCacheKey(type, filter, resolvedLocale), async () => {
|
|
176
|
+
const { getLiveCollection } = await import("astro:content");
|
|
177
|
+
const result = await getLiveCollection(COLLECTION_NAME, {
|
|
178
|
+
type,
|
|
179
|
+
status: filter?.status,
|
|
180
|
+
limit: filter?.limit,
|
|
181
|
+
cursor: filter?.cursor,
|
|
182
|
+
where: filter?.where,
|
|
183
|
+
orderBy: filter?.orderBy,
|
|
184
|
+
locale: resolvedLocale
|
|
185
|
+
});
|
|
186
|
+
const { entries, error, cacheHint } = result;
|
|
187
|
+
const rawCursor = Object.getOwnPropertyDescriptor(result, "nextCursor")?.value;
|
|
188
|
+
const nextCursor = typeof rawCursor === "string" ? rawCursor : void 0;
|
|
189
|
+
if (error) return {
|
|
190
|
+
entries: [],
|
|
191
|
+
error,
|
|
192
|
+
cacheHint: {}
|
|
193
|
+
};
|
|
194
|
+
const isEditMode = ctx?.editMode ?? false;
|
|
195
|
+
const entriesWithEdit = entries.map((entry) => {
|
|
196
|
+
const dbId = entryDatabaseId(entry);
|
|
197
|
+
if (isEditMode) tagEditableFields(entryData(entry), type, dbId);
|
|
198
|
+
return {
|
|
199
|
+
...entry,
|
|
200
|
+
edit: isEditMode ? createEditable(type, dbId, entryEditOptions(entry)) : createNoop()
|
|
201
|
+
};
|
|
202
|
+
});
|
|
203
|
+
await Promise.all([hydrateEntryBylines(type, entriesWithEdit), hydrateEntryTerms(type, entriesWithEdit)]);
|
|
194
204
|
return {
|
|
195
|
-
|
|
196
|
-
|
|
205
|
+
entries: entriesWithEdit,
|
|
206
|
+
nextCursor,
|
|
207
|
+
cacheHint: cacheHint ?? {}
|
|
197
208
|
};
|
|
198
209
|
});
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
210
|
+
}
|
|
211
|
+
function collectionCacheKey(type, filter, locale) {
|
|
212
|
+
return `collection:${type}:${[
|
|
213
|
+
filter?.status ?? "",
|
|
214
|
+
filter?.limit ?? "",
|
|
215
|
+
filter?.cursor ?? "",
|
|
216
|
+
filter?.where ? stableStringify(filter.where) : "",
|
|
217
|
+
filter?.orderBy ? JSON.stringify(filter.orderBy) : "",
|
|
218
|
+
locale ?? ""
|
|
219
|
+
].join("|")}`;
|
|
220
|
+
}
|
|
221
|
+
function stableStringify(value) {
|
|
222
|
+
const ordered = {};
|
|
223
|
+
for (const key of Object.keys(value).toSorted()) ordered[key] = value[key];
|
|
224
|
+
return JSON.stringify(ordered);
|
|
205
225
|
}
|
|
206
226
|
/**
|
|
207
227
|
* Get a single entry by type and ID/slug
|
|
@@ -228,7 +248,8 @@ async function getDinewayEntry(type, id, options) {
|
|
|
228
248
|
const ctx = getRequestContext();
|
|
229
249
|
const preview = ctx?.preview;
|
|
230
250
|
const isEditMode = ctx?.editMode ?? false;
|
|
231
|
-
const
|
|
251
|
+
const isPreviewMode = !!preview && preview.collection === type;
|
|
252
|
+
const serveDrafts = isPreviewMode || isEditMode;
|
|
232
253
|
const requestedLocale = options?.locale ?? ctx?.locale;
|
|
233
254
|
/** Wrap a raw Astro entry with edit proxy, tagging editable fields if needed */
|
|
234
255
|
function wrapEntry(raw) {
|
|
@@ -247,9 +268,9 @@ async function getDinewayEntry(type, id, options) {
|
|
|
247
268
|
return status === "published" || !!(status === "scheduled" && scheduledAt && new Date(scheduledAt) <= /* @__PURE__ */ new Date());
|
|
248
269
|
}
|
|
249
270
|
const localeChain = requestedLocale && isI18nEnabled() ? getFallbackChain(requestedLocale) : [requestedLocale];
|
|
250
|
-
/** Return a successful EntryResult with
|
|
271
|
+
/** Return a successful EntryResult with runtime data hydrated */
|
|
251
272
|
async function successResult(wrapped, opts) {
|
|
252
|
-
await hydrateEntryBylines(type, [wrapped]);
|
|
273
|
+
await Promise.all([hydrateEntryBylines(type, [wrapped]), hydrateEntryTerms(type, [wrapped])]);
|
|
253
274
|
return {
|
|
254
275
|
entry: wrapped,
|
|
255
276
|
isPreview: opts.isPreview,
|
|
@@ -273,6 +294,17 @@ async function getDinewayEntry(type, id, options) {
|
|
|
273
294
|
cacheHint: {}
|
|
274
295
|
};
|
|
275
296
|
if (!baseEntry) continue;
|
|
297
|
+
if (isPreviewMode && !isEditMode) {
|
|
298
|
+
const dbId = entryDatabaseId(baseEntry);
|
|
299
|
+
if (preview.id !== dbId && preview.id !== id) {
|
|
300
|
+
if (isVisible(baseEntry)) return successResult(wrapEntry(baseEntry), {
|
|
301
|
+
isPreview: false,
|
|
302
|
+
fallbackLocale,
|
|
303
|
+
cacheHint: cacheHint ?? {}
|
|
304
|
+
});
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
276
308
|
const draftRevisionId = dataStr(entryData(baseEntry), "draftRevisionId") || void 0;
|
|
277
309
|
if (draftRevisionId) {
|
|
278
310
|
const { entry: draftEntry, error: draftError } = await getLiveEntry(COLLECTION_NAME, {
|
|
@@ -337,7 +369,7 @@ async function getDinewayEntry(type, id, options) {
|
|
|
337
369
|
async function hydrateEntryBylines(type, entries) {
|
|
338
370
|
if (entries.length === 0) return;
|
|
339
371
|
try {
|
|
340
|
-
const { getBylinesForEntries } = await import("./bylines-
|
|
372
|
+
const { getBylinesForEntries } = await import("./bylines-BGpD9_hy.mjs").then((n) => n.t);
|
|
341
373
|
const ids = entries.map((e) => dataStr(entryData(e), "id")).filter(Boolean);
|
|
342
374
|
if (ids.length === 0) return;
|
|
343
375
|
const bylinesMap = await getBylinesForEntries(type, ids);
|
|
@@ -350,8 +382,30 @@ async function hydrateEntryBylines(type, entries) {
|
|
|
350
382
|
data.byline = credits[0]?.byline ?? null;
|
|
351
383
|
}
|
|
352
384
|
} catch (err) {
|
|
353
|
-
|
|
354
|
-
|
|
385
|
+
if (!isMissingTableError(err)) {
|
|
386
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
387
|
+
console.warn("[dineway] Failed to hydrate bylines:", msg);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
async function hydrateEntryTerms(type, entries) {
|
|
392
|
+
if (entries.length === 0) return;
|
|
393
|
+
try {
|
|
394
|
+
const { getAllTermsForEntries } = await import("./taxonomies-1s5PaS_8.mjs").then((n) => n.c);
|
|
395
|
+
const ids = entries.map((entry) => dataStr(entryData(entry), "id")).filter(Boolean);
|
|
396
|
+
if (ids.length === 0) return;
|
|
397
|
+
const termsMap = await getAllTermsForEntries(type, ids);
|
|
398
|
+
for (const entry of entries) {
|
|
399
|
+
const data = entryData(entry);
|
|
400
|
+
const dbId = dataStr(data, "id");
|
|
401
|
+
if (!dbId) continue;
|
|
402
|
+
data.terms = termsMap.get(dbId) ?? {};
|
|
403
|
+
}
|
|
404
|
+
} catch (err) {
|
|
405
|
+
if (!isMissingTableError(err)) {
|
|
406
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
407
|
+
console.warn("[dineway] Failed to hydrate terms:", msg);
|
|
408
|
+
}
|
|
355
409
|
}
|
|
356
410
|
}
|
|
357
411
|
/**
|
|
@@ -371,9 +425,9 @@ async function hydrateEntryBylines(type, entries) {
|
|
|
371
425
|
*/
|
|
372
426
|
async function getTranslations(type, id) {
|
|
373
427
|
try {
|
|
374
|
-
const db = (await import("./loader-
|
|
428
|
+
const db = (await import("./loader-sMG4TZ-u.mjs").then((n) => n.r)).getDb;
|
|
375
429
|
const dbInstance = await db();
|
|
376
|
-
const { ContentRepository } = await import("./content-
|
|
430
|
+
const { ContentRepository } = await import("./content-DWi4d0rT.mjs").then((n) => n.n);
|
|
377
431
|
const repo = new ContentRepository(dbInstance);
|
|
378
432
|
const item = await repo.findByIdOrSlug(type, id);
|
|
379
433
|
if (!item) return {
|
|
@@ -413,6 +467,10 @@ function patternToRegex(pattern) {
|
|
|
413
467
|
paramNames
|
|
414
468
|
};
|
|
415
469
|
}
|
|
470
|
+
let cachedUrlPatterns = null;
|
|
471
|
+
function invalidateUrlPatternCache() {
|
|
472
|
+
cachedUrlPatterns = null;
|
|
473
|
+
}
|
|
416
474
|
/**
|
|
417
475
|
* Resolve a URL path to a content entry by matching against collection URL patterns.
|
|
418
476
|
*
|
|
@@ -433,22 +491,32 @@ function patternToRegex(pattern) {
|
|
|
433
491
|
* ```
|
|
434
492
|
*/
|
|
435
493
|
async function resolveDinewayPath(path) {
|
|
436
|
-
const
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
494
|
+
const hasDbOverride = !!getRequestContext()?.db;
|
|
495
|
+
let patterns = !hasDbOverride ? cachedUrlPatterns : null;
|
|
496
|
+
if (!patterns) {
|
|
497
|
+
const { getDb } = await import("./loader-sMG4TZ-u.mjs").then((n) => n.r);
|
|
498
|
+
const { SchemaRegistry } = await import("./registry-C0zjeB9P.mjs").then((n) => n.r);
|
|
499
|
+
patterns = (await new SchemaRegistry(await getDb()).listCollections()).filter((collection) => collection.urlPattern).map((collection) => {
|
|
500
|
+
const { regex, paramNames } = patternToRegex(collection.urlPattern);
|
|
501
|
+
return {
|
|
502
|
+
slug: collection.slug,
|
|
503
|
+
regex,
|
|
504
|
+
paramNames
|
|
505
|
+
};
|
|
506
|
+
});
|
|
507
|
+
if (!hasDbOverride) cachedUrlPatterns = patterns;
|
|
508
|
+
}
|
|
509
|
+
for (const pattern of patterns) {
|
|
510
|
+
const match = path.match(pattern.regex);
|
|
443
511
|
if (!match) continue;
|
|
444
512
|
const params = {};
|
|
445
|
-
for (let i = 0; i < paramNames.length; i++) params[paramNames[i]] = match[i + 1];
|
|
513
|
+
for (let i = 0; i < pattern.paramNames.length; i++) params[pattern.paramNames[i]] = match[i + 1];
|
|
446
514
|
const slug = params.slug;
|
|
447
515
|
if (!slug) continue;
|
|
448
|
-
const { entry } = await getDinewayEntry(
|
|
516
|
+
const { entry } = await getDinewayEntry(pattern.slug, slug);
|
|
449
517
|
if (entry) return {
|
|
450
518
|
entry,
|
|
451
|
-
collection:
|
|
519
|
+
collection: pattern.slug,
|
|
452
520
|
params
|
|
453
521
|
};
|
|
454
522
|
}
|
|
@@ -456,4 +524,4 @@ async function resolveDinewayPath(path) {
|
|
|
456
524
|
}
|
|
457
525
|
|
|
458
526
|
//#endregion
|
|
459
|
-
export {
|
|
527
|
+
export { invalidateUrlPatternCache as a, createEditable as c, getTranslations as i, createNoop as l, getDinewayEntry as n, query_exports as o, getEditMeta as r, resolveDinewayPath as s, getDinewayCollection as t };
|
|
@@ -1,100 +1,23 @@
|
|
|
1
|
-
import { r as currentTimestampValue } from "./dialect-helpers-
|
|
1
|
+
import { r as currentTimestampValue } from "./dialect-helpers-DhTzaUxP.mjs";
|
|
2
2
|
import { n as decodeCursor, r as encodeCursor } from "./types-BawVha09.mjs";
|
|
3
|
+
import { i as matchPattern, n as interpolateDestination, r as isPattern, t as compilePattern } from "./patterns-CrCYkMBb.mjs";
|
|
3
4
|
import { sql } from "kysely";
|
|
4
5
|
import { ulid } from "ulidx";
|
|
5
6
|
|
|
6
|
-
//#region src/
|
|
7
|
-
/**
|
|
8
|
-
* URL pattern matching for redirects.
|
|
9
|
-
*
|
|
10
|
-
* Uses Astro's route syntax: [param] for named segments, [...rest] for catch-all.
|
|
11
|
-
* Compiles patterns to safe regexes -- no user-supplied regex, no ReDoS risk.
|
|
12
|
-
*
|
|
13
|
-
* @example
|
|
14
|
-
* ```ts
|
|
15
|
-
* const compiled = compilePattern("/old-blog/[...path]");
|
|
16
|
-
* const match = matchPattern(compiled, "/old-blog/2024/01/post");
|
|
17
|
-
* // match = { path: "2024/01/post" }
|
|
18
|
-
*
|
|
19
|
-
* interpolateDestination("/blog/[...path]", match);
|
|
20
|
-
* // "/blog/2024/01/post"
|
|
21
|
-
* ```
|
|
22
|
-
*/
|
|
23
|
-
/** Matches [paramName] placeholders */
|
|
24
|
-
const PARAM_PATTERN = /\[(\w+)\]/g;
|
|
25
|
-
/** Matches [...splatName] placeholders */
|
|
26
|
-
const SPLAT_PATTERN = /\[\.\.\.(\w+)\]/g;
|
|
27
|
-
/** Combined pattern for validation: matches both [param] and [...splat] */
|
|
28
|
-
const ANY_PLACEHOLDER = /\[(?:\.\.\.)?(\w+)\]/g;
|
|
29
|
-
/** Split on capture groups in compiled regex string */
|
|
30
|
-
const CAPTURE_GROUP_SPLIT = /(\([^)]+\))/;
|
|
31
|
-
/** Escape regex-special characters in literal parts */
|
|
32
|
-
const REGEX_SPECIAL_CHARS = /[.*+?^${}|\\]/g;
|
|
33
|
-
/**
|
|
34
|
-
* Returns true if a source string contains [param] or [...splat] placeholders.
|
|
35
|
-
*/
|
|
36
|
-
function isPattern(source) {
|
|
37
|
-
return source.match(ANY_PLACEHOLDER) !== null;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Compile a URL pattern into a regex for matching.
|
|
41
|
-
*
|
|
42
|
-
* - `[param]` matches a single path segment (`[^/]+`)
|
|
43
|
-
* - `[...rest]` matches one or more remaining segments (`.+`)
|
|
44
|
-
*/
|
|
45
|
-
function compilePattern(source) {
|
|
46
|
-
const paramNames = [];
|
|
47
|
-
let regexStr = source.replace(SPLAT_PATTERN, (_match, name) => {
|
|
48
|
-
paramNames.push(name);
|
|
49
|
-
return "(.+)";
|
|
50
|
-
});
|
|
51
|
-
regexStr = regexStr.replace(PARAM_PATTERN, (_match, name) => {
|
|
52
|
-
paramNames.push(name);
|
|
53
|
-
return "([^/]+)";
|
|
54
|
-
});
|
|
55
|
-
const escaped = regexStr.split(CAPTURE_GROUP_SPLIT).map((part, i) => {
|
|
56
|
-
if (i % 2 === 1) return part;
|
|
57
|
-
return part.replace(REGEX_SPECIAL_CHARS, "\\$&");
|
|
58
|
-
}).join("");
|
|
59
|
-
return {
|
|
60
|
-
regex: new RegExp(`^${escaped}$`),
|
|
61
|
-
paramNames,
|
|
62
|
-
source
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Match a path against a compiled pattern.
|
|
67
|
-
* Returns captured params or null if no match.
|
|
68
|
-
*/
|
|
69
|
-
function matchPattern(compiled, path) {
|
|
70
|
-
const match = path.match(compiled.regex);
|
|
71
|
-
if (!match) return null;
|
|
72
|
-
const params = {};
|
|
73
|
-
for (let i = 0; i < compiled.paramNames.length; i++) {
|
|
74
|
-
const value = match[i + 1];
|
|
75
|
-
if (value !== void 0) params[compiled.paramNames[i]] = value;
|
|
76
|
-
}
|
|
77
|
-
return params;
|
|
78
|
-
}
|
|
7
|
+
//#region src/database/repositories/redirect.ts
|
|
79
8
|
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* @example
|
|
83
|
-
* interpolateDestination("/blog/[...path]", { path: "2024/01/post" })
|
|
84
|
-
* // "/blog/2024/01/post"
|
|
9
|
+
* Hard cap on rows stored in `_dineway_404_log`. When exceeded, the oldest
|
|
10
|
+
* rows by `last_seen_at` are evicted on insert.
|
|
85
11
|
*/
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return
|
|
12
|
+
const MAX_404_LOG_ROWS = 1e4;
|
|
13
|
+
/** Max stored length for the `Referer` header. */
|
|
14
|
+
const REFERRER_MAX_LENGTH = 512;
|
|
15
|
+
/** Max stored length for the `User-Agent` header. */
|
|
16
|
+
const USER_AGENT_MAX_LENGTH = 256;
|
|
17
|
+
function truncateOrNull(value, max) {
|
|
18
|
+
if (value === null || value === void 0) return null;
|
|
19
|
+
return value.length > max ? value.slice(0, max) : value;
|
|
94
20
|
}
|
|
95
|
-
|
|
96
|
-
//#endregion
|
|
97
|
-
//#region src/database/repositories/redirect.ts
|
|
98
21
|
function rowToRedirect(row) {
|
|
99
22
|
return {
|
|
100
23
|
id: row.id,
|
|
@@ -184,6 +107,9 @@ var RedirectRepository = class {
|
|
|
184
107
|
const result = await this.db.deleteFrom("_dineway_redirects").where("id", "=", id).executeTakeFirst();
|
|
185
108
|
return BigInt(result.numDeletedRows) > 0n;
|
|
186
109
|
}
|
|
110
|
+
async findAllEnabled() {
|
|
111
|
+
return (await this.db.selectFrom("_dineway_redirects").selectAll().where("enabled", "=", 1).execute()).map(rowToRedirect);
|
|
112
|
+
}
|
|
187
113
|
async findExactMatch(path) {
|
|
188
114
|
const row = await this.db.selectFrom("_dineway_redirects").selectAll().where("source", "=", path).where("enabled", "=", 1).where("is_pattern", "=", 0).executeTakeFirst();
|
|
189
115
|
return row ? rowToRedirect(row) : null;
|
|
@@ -252,14 +178,34 @@ var RedirectRepository = class {
|
|
|
252
178
|
return Number(result.numUpdatedRows);
|
|
253
179
|
}
|
|
254
180
|
async log404(entry) {
|
|
181
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
182
|
+
const referrer = truncateOrNull(entry.referrer, REFERRER_MAX_LENGTH);
|
|
183
|
+
const userAgent = truncateOrNull(entry.userAgent, USER_AGENT_MAX_LENGTH);
|
|
184
|
+
const ip = entry.ip ?? null;
|
|
255
185
|
await this.db.insertInto("_dineway_404_log").values({
|
|
256
186
|
id: ulid(),
|
|
257
187
|
path: entry.path,
|
|
258
|
-
referrer
|
|
259
|
-
user_agent:
|
|
260
|
-
ip
|
|
261
|
-
|
|
262
|
-
|
|
188
|
+
referrer,
|
|
189
|
+
user_agent: userAgent,
|
|
190
|
+
ip,
|
|
191
|
+
hits: 1,
|
|
192
|
+
last_seen_at: now,
|
|
193
|
+
created_at: now
|
|
194
|
+
}).onConflict((oc) => oc.column("path").doUpdateSet({
|
|
195
|
+
hits: sql`hits + 1`,
|
|
196
|
+
last_seen_at: now,
|
|
197
|
+
referrer,
|
|
198
|
+
user_agent: userAgent,
|
|
199
|
+
ip
|
|
200
|
+
})).execute();
|
|
201
|
+
await this.enforce404Cap();
|
|
202
|
+
}
|
|
203
|
+
async enforce404Cap() {
|
|
204
|
+
const countRow = await this.db.selectFrom("_dineway_404_log").select((eb) => eb.fn.countAll().as("c")).executeTakeFirst();
|
|
205
|
+
const count = Number(countRow?.c ?? 0);
|
|
206
|
+
if (count <= MAX_404_LOG_ROWS) return;
|
|
207
|
+
const excess = count - MAX_404_LOG_ROWS;
|
|
208
|
+
await this.db.deleteFrom("_dineway_404_log").where("id", "in", this.db.selectFrom("_dineway_404_log").select("id").orderBy("last_seen_at", "asc").orderBy("id", "asc").limit(excess)).execute();
|
|
263
209
|
}
|
|
264
210
|
async find404s(opts) {
|
|
265
211
|
const limit = Math.min(Math.max(opts.limit ?? 50, 1), 100);
|
|
@@ -289,14 +235,12 @@ var RedirectRepository = class {
|
|
|
289
235
|
return (await sql`
|
|
290
236
|
SELECT
|
|
291
237
|
path,
|
|
292
|
-
|
|
293
|
-
MAX(
|
|
238
|
+
SUM(hits) as count,
|
|
239
|
+
MAX(last_seen_at) as last_seen,
|
|
294
240
|
(
|
|
295
241
|
SELECT referrer FROM _dineway_404_log AS inner_log
|
|
296
242
|
WHERE inner_log.path = _dineway_404_log.path
|
|
297
243
|
AND referrer IS NOT NULL AND referrer != ''
|
|
298
|
-
GROUP BY referrer
|
|
299
|
-
ORDER BY COUNT(*) DESC
|
|
300
244
|
LIMIT 1
|
|
301
245
|
) as top_referrer
|
|
302
246
|
FROM _dineway_404_log
|