@websolutespa/bom-next 3.0.0
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/CHANGELOG.md +7 -0
- package/README.md +11 -0
- package/dist/config.client-B8As5DY0.js +2 -0
- package/dist/config.edge-B8As5DY0.js +2 -0
- package/dist/config.server-B8As5DY0.js +2 -0
- package/dist/exports/client-xcFJNcG7.d.ts +979 -0
- package/dist/exports/client-xcFJNcG7.d.ts.map +1 -0
- package/dist/exports/client.js +5735 -0
- package/dist/exports/client.js.map +1 -0
- package/dist/exports/instrumentation-DBO6QH4d.d.ts +13 -0
- package/dist/exports/instrumentation-DBO6QH4d.d.ts.map +1 -0
- package/dist/exports/instrumentation.js +25 -0
- package/dist/exports/instrumentation.js.map +1 -0
- package/dist/exports/proxy-Cv7QFVci.d.ts +71 -0
- package/dist/exports/proxy-Cv7QFVci.d.ts.map +1 -0
- package/dist/exports/proxy.js +254 -0
- package/dist/exports/proxy.js.map +1 -0
- package/dist/exports/server-BHObJHJ6.d.ts +402 -0
- package/dist/exports/server-BHObJHJ6.d.ts.map +1 -0
- package/dist/exports/server.js +1974 -0
- package/dist/exports/server.js.map +1 -0
- package/dist/http-_2M-UFRx.js +61 -0
- package/dist/http-_2M-UFRx.js.map +1 -0
- package/dist/index-CdYcvudT.d.ts +4 -0
- package/dist/index-CtfAJ0y7.d.ts +499 -0
- package/dist/index-CtfAJ0y7.d.ts.map +1 -0
- package/dist/index.js +201 -0
- package/dist/index.js.map +1 -0
- package/dist/route-DzFwERQP.js +65 -0
- package/dist/route-DzFwERQP.js.map +1 -0
- package/dist/store-CJzqrHsl.js +73 -0
- package/dist/store-CJzqrHsl.js.map +1 -0
- package/dist/types-BatUTdbZ.d.ts +15 -0
- package/dist/types-BatUTdbZ.d.ts.map +1 -0
- package/dist/types-D29_snYY.d.ts +191 -0
- package/dist/types-D29_snYY.d.ts.map +1 -0
- package/dist/utils-Ca7xh5mm.js +78 -0
- package/dist/utils-Ca7xh5mm.js.map +1 -0
- package/dist/utils-YwteTbk7.js +23 -0
- package/dist/utils-YwteTbk7.js.map +1 -0
- package/package.json +156 -0
- package/src/client/client.tsx +18 -0
- package/src/client/extra.tsx +21 -0
- package/src/client/label.tsx +24 -0
- package/src/client/layout.tsx +22 -0
- package/src/client/local.collection.ts +33 -0
- package/src/client/metadata.tsx +217 -0
- package/src/client/page.tsx +22 -0
- package/src/client/useApi.test.tsx +203 -0
- package/src/client/useApi.ts +59 -0
- package/src/client/useCollectionApi.test.tsx +201 -0
- package/src/client/useCollectionApi.ts +66 -0
- package/src/client/useExtra.tsx +12 -0
- package/src/client/useHttp.ts +54 -0
- package/src/client/useLabel.test.tsx +76 -0
- package/src/client/useLabel.tsx +12 -0
- package/src/client/useLayout.tsx +13 -0
- package/src/client/useLink.tsx +13 -0
- package/src/client/useMetaData.tsx +12 -0
- package/src/client/usePage.tsx +12 -0
- package/src/client/useUser.tsx +12 -0
- package/src/client/user.tsx +43 -0
- package/src/exports/client.ts +20 -0
- package/src/exports/instrumentation.ts +1 -0
- package/src/exports/proxy.ts +5 -0
- package/src/exports/server.ts +19 -0
- package/src/global.d.ts +6 -0
- package/src/index.ts +17 -0
- package/src/instrumentation/client.ts +19 -0
- package/src/instrumentation/sentry/capture.requestError.ts +6 -0
- package/src/instrumentation/sentry/capture.routerTransitionStart.ts +4 -0
- package/src/instrumentation/sentry/config.client.ts +9 -0
- package/src/instrumentation/sentry/config.edge.ts +9 -0
- package/src/instrumentation/sentry/config.server.ts +9 -0
- package/src/instrumentation/server.ts +24 -0
- package/src/instrumentation/types.ts +12 -0
- package/src/lib/breadcrumb.ts +21 -0
- package/src/lib/const.ts +3 -0
- package/src/lib/http.test.ts +25 -0
- package/src/lib/http.ts +74 -0
- package/src/lib/label.ts +43 -0
- package/src/lib/layout.defaults.ts +167 -0
- package/src/lib/link.ts +19 -0
- package/src/lib/meta-data.ts +60 -0
- package/src/lib/open-graph.ts +165 -0
- package/src/lib/pagination.ts +78 -0
- package/src/lib/payload.config.ts +12 -0
- package/src/lib/route.ts +83 -0
- package/src/lib/store.ts +63 -0
- package/src/lib/structured-data.ts +24 -0
- package/src/lib/twitter-card.ts +49 -0
- package/src/lib/utils.ts +45 -0
- package/src/mock.ts +264 -0
- package/src/proxy/protected-route.test.ts +75 -0
- package/src/proxy/protected-route.ts +57 -0
- package/src/proxy/proxy.test.ts +48 -0
- package/src/proxy/proxy.ts +64 -0
- package/src/proxy/route-auto-detection.ts +126 -0
- package/src/proxy/route-proxy.ts +187 -0
- package/src/server/context.ts +147 -0
- package/src/server/handlers/auth.handler.ts +27 -0
- package/src/server/handlers/captions.handler.ts +32 -0
- package/src/server/handlers/collection.handler.test.ts +57 -0
- package/src/server/handlers/collection.handler.ts +212 -0
- package/src/server/handlers/error-response.ts +45 -0
- package/src/server/handlers/index.handler.ts +146 -0
- package/src/server/handlers/preview.handler.ts +63 -0
- package/src/server/handlers/registry.handler.test.ts +65 -0
- package/src/server/handlers/registry.handler.ts +259 -0
- package/src/server/handlers/route.handler.ts +161 -0
- package/src/server/handlers/sitemap.handler.ts +374 -0
- package/src/server/handlers/static-params.handler.ts +111 -0
- package/src/server/handlers/vars.handler.ts +167 -0
- package/src/server/layout.ts +120 -0
- package/src/server/lazy.ts +41 -0
- package/src/server/local-api.ts +63 -0
- package/src/server/metadata.ts +87 -0
- package/src/server/mixer-payload.ts +124 -0
- package/src/server/mixer-payload.type-test.ts +27 -0
- package/src/server/mixer.ts +439 -0
- package/src/server/mock/category.ts +34 -0
- package/src/server/mock/collection.ts +21 -0
- package/src/server/mock/page.ts +62 -0
- package/src/server/mock/route.ts +36 -0
- package/src/server/mock/store.ts +159 -0
- package/src/server/page.ts +86 -0
- package/src/server/session/session.ts +101 -0
- package/src/server/utils.ts +23 -0
- package/src/server/utmz.ts +11 -0
- package/src/types.ts +211 -0
|
@@ -0,0 +1,1974 @@
|
|
|
1
|
+
import { t as getRouteLinkCategory } from "../route-DzFwERQP.js";
|
|
2
|
+
import { c as guest, i as buildStrategy, l as buildBreadcrumb } from "../store-CJzqrHsl.js";
|
|
3
|
+
import { o as localApiGet } from "../http-_2M-UFRx.js";
|
|
4
|
+
import { a as sanitizeRedirectTo, c as getLink, i as sanitizeRedirectFrom, o as sanitizeRedirectUrl, r as sanitizeHref, u as getLabelFromLabels } from "../utils-Ca7xh5mm.js";
|
|
5
|
+
import { n as getNextUrl, t as getHrefBeforeRedirect } from "../utils-YwteTbk7.js";
|
|
6
|
+
import { collectRoutes, defaultLocale, defaultMarket, eachMarketLocale, getCategorySegments, getHost, getOrigin, getRootCategory, isDevelopment, isObject, isProduction, qsDeserialize, qsSerialize } from "@websolutespa/bom-core";
|
|
7
|
+
import { cache } from "react";
|
|
8
|
+
import { NextResponse } from "next/server";
|
|
9
|
+
import { InMemoryCache, getRouteSnapshot, getRoutes as getRoutes$1, sortCollection, whereCollection } from "@websolutespa/payload-plugin-bowl";
|
|
10
|
+
import { createPayloadRequest, getPayload as getPayload$1 } from "payload";
|
|
11
|
+
import { draftMode, headers } from "next/headers";
|
|
12
|
+
import { readFile, readdir, stat } from "node:fs/promises";
|
|
13
|
+
import { basename, dirname, extname, join, relative, resolve } from "node:path";
|
|
14
|
+
import { ResponseNotFound, ResponseSuccess } from "@websolutespa/payload-utils/server";
|
|
15
|
+
//#region src/server/mixer.ts
|
|
16
|
+
const USE_CACHE = true;
|
|
17
|
+
const getRouteCache = () => {
|
|
18
|
+
let cache = globalThis._route;
|
|
19
|
+
if (!cache) {
|
|
20
|
+
cache = new InMemoryCache();
|
|
21
|
+
globalThis._route = cache;
|
|
22
|
+
}
|
|
23
|
+
return cache;
|
|
24
|
+
};
|
|
25
|
+
const getMixerCache = () => {
|
|
26
|
+
let cache = globalThis._mixer;
|
|
27
|
+
if (!cache) {
|
|
28
|
+
cache = new InMemoryCache();
|
|
29
|
+
globalThis._mixer = cache;
|
|
30
|
+
}
|
|
31
|
+
return cache;
|
|
32
|
+
};
|
|
33
|
+
function getCacheKey({ market = "all", locale = "all", user = guest, draft = "false" }) {
|
|
34
|
+
const roles = user?.roles || ["guest"];
|
|
35
|
+
const tenants = "tenants" in user && Array.isArray(user.tenants) ? user.tenants.map((x) => isObject(x) ? x.id : x) : [];
|
|
36
|
+
roles.sort();
|
|
37
|
+
tenants.sort();
|
|
38
|
+
return `context-${[
|
|
39
|
+
market,
|
|
40
|
+
locale,
|
|
41
|
+
draft === "false" ? "published" : "draft",
|
|
42
|
+
...roles,
|
|
43
|
+
...tenants
|
|
44
|
+
].join("-")}`;
|
|
45
|
+
}
|
|
46
|
+
async function getContext() {
|
|
47
|
+
let context = globalThis._context;
|
|
48
|
+
if (!context) {
|
|
49
|
+
context = {
|
|
50
|
+
payload: void 0,
|
|
51
|
+
context: void 0
|
|
52
|
+
};
|
|
53
|
+
globalThis._context = context;
|
|
54
|
+
}
|
|
55
|
+
return context;
|
|
56
|
+
}
|
|
57
|
+
async function setContext(data) {
|
|
58
|
+
const context = await getContext();
|
|
59
|
+
if (data.payload) context.payload = data.payload;
|
|
60
|
+
if (data.context) context.context = data.context;
|
|
61
|
+
}
|
|
62
|
+
function getSearchParams(request) {
|
|
63
|
+
const searchParams = request.nextUrl.searchParams;
|
|
64
|
+
const params = Object.fromEntries(searchParams.entries());
|
|
65
|
+
return {
|
|
66
|
+
market: params.market || defaultMarket,
|
|
67
|
+
locale: params.locale || defaultLocale,
|
|
68
|
+
defaultMarket,
|
|
69
|
+
defaultLocale,
|
|
70
|
+
params,
|
|
71
|
+
searchParams: request.nextUrl.searchParams
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const getPayloadInstance = cache(async (props) => await getPayload$1(props));
|
|
75
|
+
function getOptions({ locale = "all", user = guest, draft = false }) {
|
|
76
|
+
return {
|
|
77
|
+
localized: {
|
|
78
|
+
locale,
|
|
79
|
+
fallbackLocale: defaultLocale,
|
|
80
|
+
draft,
|
|
81
|
+
depth: 0,
|
|
82
|
+
pagination: false,
|
|
83
|
+
limit: 1e4,
|
|
84
|
+
user,
|
|
85
|
+
overrideAccess: false
|
|
86
|
+
},
|
|
87
|
+
unlocalized: {
|
|
88
|
+
locale: "all",
|
|
89
|
+
fallbackLocale: defaultLocale,
|
|
90
|
+
draft,
|
|
91
|
+
depth: 0,
|
|
92
|
+
pagination: false,
|
|
93
|
+
limit: 1e4,
|
|
94
|
+
user,
|
|
95
|
+
overrideAccess: false
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
async function getRoutes({ payload, market = "all", locale = "all", user = guest, draft = false }) {
|
|
100
|
+
const cacheKey = getCacheKey({
|
|
101
|
+
market,
|
|
102
|
+
locale,
|
|
103
|
+
user,
|
|
104
|
+
draft: draft ? "true" : "false"
|
|
105
|
+
});
|
|
106
|
+
const routeCache = getRouteCache();
|
|
107
|
+
if (routeCache.has(cacheKey)) {
|
|
108
|
+
console.log("getRoutes from cache", cacheKey);
|
|
109
|
+
return routeCache.get(cacheKey);
|
|
110
|
+
}
|
|
111
|
+
let routes = [];
|
|
112
|
+
if (!user) return routes;
|
|
113
|
+
const { localized, unlocalized } = getOptions({
|
|
114
|
+
locale,
|
|
115
|
+
user,
|
|
116
|
+
draft
|
|
117
|
+
});
|
|
118
|
+
const markets = (await payload.find({
|
|
119
|
+
...localized,
|
|
120
|
+
collection: "market",
|
|
121
|
+
where: { isActive: { equals: true } }
|
|
122
|
+
})).docs;
|
|
123
|
+
const locales = (await payload.findGlobal({
|
|
124
|
+
...localized,
|
|
125
|
+
slug: "locale"
|
|
126
|
+
})).items.filter((x) => x.isActive);
|
|
127
|
+
if (locales.length > 0 && markets.length > 0) {
|
|
128
|
+
const keys = payload.config.collections.filter((x) => {
|
|
129
|
+
return x.fields.find((x) => "name" in x && x.name === "slug");
|
|
130
|
+
}).map((x) => x.slug);
|
|
131
|
+
const pages = Object.fromEntries(keys.map((x) => [x, []]));
|
|
132
|
+
for (const key of keys) pages[key] = (await payload.find({
|
|
133
|
+
...unlocalized,
|
|
134
|
+
collection: key
|
|
135
|
+
})).docs;
|
|
136
|
+
const categories = (await payload.find({
|
|
137
|
+
...unlocalized,
|
|
138
|
+
collection: "category",
|
|
139
|
+
depth: 1
|
|
140
|
+
})).docs;
|
|
141
|
+
const medias = (await payload.find({
|
|
142
|
+
...unlocalized,
|
|
143
|
+
collection: "media",
|
|
144
|
+
depth: 1
|
|
145
|
+
})).docs;
|
|
146
|
+
routes = collectRoutes(pages, categories, markets, locales, medias);
|
|
147
|
+
}
|
|
148
|
+
routeCache.set(cacheKey, routes);
|
|
149
|
+
return routes;
|
|
150
|
+
}
|
|
151
|
+
async function getMixerContext({ payload, market, locale, user = guest, draft = false }) {
|
|
152
|
+
const cacheKey = getCacheKey({
|
|
153
|
+
market,
|
|
154
|
+
locale,
|
|
155
|
+
user,
|
|
156
|
+
draft: draft ? "true" : "false"
|
|
157
|
+
});
|
|
158
|
+
const mixerCache = getMixerCache();
|
|
159
|
+
if (mixerCache.has(cacheKey) && USE_CACHE) {
|
|
160
|
+
console.log("getContext from cache", cacheKey);
|
|
161
|
+
return mixerCache.get(cacheKey);
|
|
162
|
+
}
|
|
163
|
+
const { localized } = getOptions({
|
|
164
|
+
locale,
|
|
165
|
+
user,
|
|
166
|
+
draft
|
|
167
|
+
});
|
|
168
|
+
const locales = (await payload.findGlobal({
|
|
169
|
+
...localized,
|
|
170
|
+
slug: "locale"
|
|
171
|
+
})).items.filter((x) => x.isActive);
|
|
172
|
+
const localeIds = locales.map((x) => x.id);
|
|
173
|
+
const markets = (await payload.find({
|
|
174
|
+
...localized,
|
|
175
|
+
collection: "market"
|
|
176
|
+
})).docs.filter((x) => x.isActive);
|
|
177
|
+
const marketIds = markets.map((x) => x.id);
|
|
178
|
+
const categories = (await payload.find({
|
|
179
|
+
...localized,
|
|
180
|
+
collection: "category",
|
|
181
|
+
depth: 2
|
|
182
|
+
})).docs;
|
|
183
|
+
const routes = await getRoutes({
|
|
184
|
+
payload,
|
|
185
|
+
market,
|
|
186
|
+
locale,
|
|
187
|
+
user,
|
|
188
|
+
draft
|
|
189
|
+
});
|
|
190
|
+
const context = {
|
|
191
|
+
locale,
|
|
192
|
+
localeIds,
|
|
193
|
+
locales,
|
|
194
|
+
market,
|
|
195
|
+
marketIds,
|
|
196
|
+
markets,
|
|
197
|
+
routes,
|
|
198
|
+
currentRoutes: filterRoutes(routes, market, locale),
|
|
199
|
+
categories,
|
|
200
|
+
richText: false
|
|
201
|
+
};
|
|
202
|
+
mixerCache.set(cacheKey, context);
|
|
203
|
+
await setContext({ context });
|
|
204
|
+
return context;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Runtime (request-time) payload accessor.
|
|
208
|
+
*
|
|
209
|
+
* By default, operates as the guest user for static generation. For auth:
|
|
210
|
+
* call getHeaders() directly in your page/route/layout, then pass it here.
|
|
211
|
+
*
|
|
212
|
+
* Example:
|
|
213
|
+
* const headers = await getHeaders();
|
|
214
|
+
* const { payload, user, getContext } = await getPayload({ config, headers });
|
|
215
|
+
*/
|
|
216
|
+
async function getPayload({ config, headers }) {
|
|
217
|
+
const payload = await getPayload$1({ config });
|
|
218
|
+
await setContext({ payload });
|
|
219
|
+
let user = guest;
|
|
220
|
+
if (headers) try {
|
|
221
|
+
const auth = await payload.auth({
|
|
222
|
+
headers,
|
|
223
|
+
canSetHeaders: false
|
|
224
|
+
});
|
|
225
|
+
if (auth.user) user = auth.user;
|
|
226
|
+
} catch (error) {
|
|
227
|
+
console.log("getPayload.error", error);
|
|
228
|
+
}
|
|
229
|
+
const draft = false;
|
|
230
|
+
return {
|
|
231
|
+
payload,
|
|
232
|
+
user,
|
|
233
|
+
getContext: (market, locale) => getMixerContext({
|
|
234
|
+
payload,
|
|
235
|
+
market,
|
|
236
|
+
locale,
|
|
237
|
+
user,
|
|
238
|
+
draft
|
|
239
|
+
})
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function filterRoutes(routes, market, locale) {
|
|
243
|
+
market = market === "all" ? defaultMarket : market;
|
|
244
|
+
locale = locale === "all" ? defaultLocale : locale;
|
|
245
|
+
return routes.filter((x) => x.market === market && x.locale === locale);
|
|
246
|
+
}
|
|
247
|
+
function getAuthHeaders(token) {
|
|
248
|
+
if (token) return new Headers({ Authorization: `JWT ${token.value}` });
|
|
249
|
+
else return new Headers({ Authorization: `users API-Key ${process.env.STORE_API_KEY}` });
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Produces the common option block shared across all payload.find / payload.findGlobal calls.
|
|
253
|
+
* For payload.find, add pagination: false and limit: 10000 at the call site.
|
|
254
|
+
* For payload.findByID, spread directly — extra find-only fields are ignored.
|
|
255
|
+
*/
|
|
256
|
+
function buildBaseOptions(user, locale, draft) {
|
|
257
|
+
return {
|
|
258
|
+
locale,
|
|
259
|
+
fallbackLocale: defaultLocale,
|
|
260
|
+
draft: draft ?? false,
|
|
261
|
+
depth: 0,
|
|
262
|
+
user,
|
|
263
|
+
overrideAccess: false
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Payload proxy example
|
|
268
|
+
*
|
|
269
|
+
*
|
|
270
|
+
* import type { Payload, PayloadRequest } from '../types/index.js'
|
|
271
|
+
|
|
272
|
+
import { commitTransaction } from './commitTransaction.js'
|
|
273
|
+
import { initTransaction } from './initTransaction.js'
|
|
274
|
+
import { killTransaction } from './killTransaction.js'
|
|
275
|
+
|
|
276
|
+
const localOperations = [
|
|
277
|
+
'count',
|
|
278
|
+
'countGlobalVersions',
|
|
279
|
+
'countVersions',
|
|
280
|
+
'create',
|
|
281
|
+
'delete',
|
|
282
|
+
'duplicate',
|
|
283
|
+
'find',
|
|
284
|
+
'findByID',
|
|
285
|
+
'delete',
|
|
286
|
+
'duplicate',
|
|
287
|
+
'find',
|
|
288
|
+
'findByID',
|
|
289
|
+
'findGlobal',
|
|
290
|
+
'findGlobalVersionByID',
|
|
291
|
+
'findGlobalVersions',
|
|
292
|
+
'findVersionByID',
|
|
293
|
+
'findVersionByID',
|
|
294
|
+
'login',
|
|
295
|
+
'update',
|
|
296
|
+
'updateGlobal',
|
|
297
|
+
'restoreVersion',
|
|
298
|
+
'restoreGlobalVersion',
|
|
299
|
+
] satisfies (keyof Payload)[]
|
|
300
|
+
|
|
301
|
+
type LocalOperation = (typeof localOperations)[number]
|
|
302
|
+
|
|
303
|
+
const isLocalOperation = (key: string): key is LocalOperation => {
|
|
304
|
+
return localOperations.includes(key as LocalOperation)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export const withTransaction = async <T>(
|
|
308
|
+
payload: Payload,
|
|
309
|
+
callback: (transaction: Payload) => Promise<T>,
|
|
310
|
+
) => {
|
|
311
|
+
const transactionReq = { payload } as PayloadRequest
|
|
312
|
+
await initTransaction(transactionReq)
|
|
313
|
+
|
|
314
|
+
const proxy = new Proxy(payload, {
|
|
315
|
+
get(target, property: keyof Payload, receiver) {
|
|
316
|
+
if (isLocalOperation(property)) {
|
|
317
|
+
const operation = Reflect.get(target, property, receiver)
|
|
318
|
+
|
|
319
|
+
return function (options) {
|
|
320
|
+
let req: Partial<PayloadRequest> = options.req
|
|
321
|
+
|
|
322
|
+
if (!req) {
|
|
323
|
+
req = {}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (!req.transactionID) {
|
|
327
|
+
req = { ...req, transactionID: transactionReq.transactionID }
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return (operation as (...args: unknown[]) => void)({
|
|
331
|
+
...options,
|
|
332
|
+
req,
|
|
333
|
+
})
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return Reflect.get(target, property, receiver)
|
|
338
|
+
},
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
try {
|
|
342
|
+
const result = await callback(proxy)
|
|
343
|
+
await commitTransaction(transactionReq)
|
|
344
|
+
return result
|
|
345
|
+
} catch (e) {
|
|
346
|
+
await killTransaction(transactionReq)
|
|
347
|
+
throw e
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// -----------------
|
|
352
|
+
|
|
353
|
+
const { one, two } = await withTransaction(payload, async (transaction) => {
|
|
354
|
+
const one = await transaction.create({ collection: 'posts', data: { title: 'one' } })
|
|
355
|
+
const two = await transaction.create({ collection: 'posts', data: { title: 'two' } })
|
|
356
|
+
|
|
357
|
+
return { one, two }
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
*/
|
|
361
|
+
//#endregion
|
|
362
|
+
//#region src/server/layout.ts
|
|
363
|
+
/**
|
|
364
|
+
* Builds the full ILayout object using Payload local API calls.
|
|
365
|
+
*
|
|
366
|
+
* All payload.find / payload.findGlobal calls pass `context: mixerContext` so
|
|
367
|
+
* payload-plugin-bowl can enrich results (hrefs, richText serialisation, etc.).
|
|
368
|
+
*
|
|
369
|
+
* Collection and global slugs use the payload-plugin-bowl defaults:
|
|
370
|
+
* market → 'market' (collection)
|
|
371
|
+
* locale → 'locale' (global)
|
|
372
|
+
* label → 'label' (collection)
|
|
373
|
+
* app → 'app' (global) ← defined in payload-plugin-bowl
|
|
374
|
+
* menu → 'menu' (collection)
|
|
375
|
+
*
|
|
376
|
+
* The route link tree is built from mixerContext.currentRoutes + mixerContext.categories
|
|
377
|
+
* using getRouteLinkCategory (pure fn, no store calls) from bom-mixer-models/server.
|
|
378
|
+
*/
|
|
379
|
+
async function getLayout(market, locale, payload, user, mixerContext, draft) {
|
|
380
|
+
const base = buildBaseOptions(user, locale, draft);
|
|
381
|
+
const findOpts = {
|
|
382
|
+
...base,
|
|
383
|
+
pagination: false,
|
|
384
|
+
limit: 1e4
|
|
385
|
+
};
|
|
386
|
+
const markets = (await payload.find({
|
|
387
|
+
...findOpts,
|
|
388
|
+
collection: "market",
|
|
389
|
+
context: mixerContext
|
|
390
|
+
})).docs;
|
|
391
|
+
const locales = ((await payload.findGlobal({
|
|
392
|
+
...base,
|
|
393
|
+
slug: "locale",
|
|
394
|
+
context: mixerContext
|
|
395
|
+
}))?.items ?? []).filter((x) => x.isActive);
|
|
396
|
+
const labels = (await payload.find({
|
|
397
|
+
...findOpts,
|
|
398
|
+
collection: "label",
|
|
399
|
+
context: mixerContext
|
|
400
|
+
})).docs;
|
|
401
|
+
const app = await payload.findGlobal({
|
|
402
|
+
...base,
|
|
403
|
+
slug: "app",
|
|
404
|
+
context: mixerContext
|
|
405
|
+
});
|
|
406
|
+
const menusResult = await payload.find({
|
|
407
|
+
...findOpts,
|
|
408
|
+
collection: "menu",
|
|
409
|
+
context: mixerContext
|
|
410
|
+
});
|
|
411
|
+
const menu = {};
|
|
412
|
+
menusResult.docs.forEach((x) => {
|
|
413
|
+
menu[String(x.id)] = x;
|
|
414
|
+
});
|
|
415
|
+
const categories = mixerContext.categories ?? [];
|
|
416
|
+
const currentRoutes = mixerContext.currentRoutes ?? [];
|
|
417
|
+
const rootCategory = getRootCategory(categories);
|
|
418
|
+
const treeResult = rootCategory ? getRouteLinkCategory(locale, currentRoutes, categories, rootCategory, rootCategory) : void 0;
|
|
419
|
+
const tree = Array.isArray(treeResult) ? treeResult[0] : treeResult;
|
|
420
|
+
const firstLevelRoutes = tree?.items ?? [];
|
|
421
|
+
const flatTopLevelRoutes = tree ? [tree, ...firstLevelRoutes] : [];
|
|
422
|
+
const topLevelRoutes = flatTopLevelRoutes.reduce((object, routeLink) => {
|
|
423
|
+
if (!object[routeLink.schema] || routeLink.schema === String(routeLink.category)) object[routeLink.schema] = routeLink;
|
|
424
|
+
return object;
|
|
425
|
+
}, {});
|
|
426
|
+
return {
|
|
427
|
+
labels,
|
|
428
|
+
locale,
|
|
429
|
+
locales,
|
|
430
|
+
market,
|
|
431
|
+
markets,
|
|
432
|
+
app,
|
|
433
|
+
menu,
|
|
434
|
+
topLevelHrefs: flatTopLevelRoutes.reduce((object, routeLink) => {
|
|
435
|
+
if (routeLink.href && (!object[routeLink.schema] || routeLink.schema === String(routeLink.category))) object[routeLink.schema] = routeLink.href;
|
|
436
|
+
return object;
|
|
437
|
+
}, {}),
|
|
438
|
+
topLevelRoutes,
|
|
439
|
+
tree
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
//#endregion
|
|
443
|
+
//#region src/server/page.ts
|
|
444
|
+
/**
|
|
445
|
+
* Fetches a single page document by ID using Payload local API and assembles
|
|
446
|
+
* the IPage envelope (routes, alternates, breadcrumb, parentRoute).
|
|
447
|
+
*
|
|
448
|
+
* All data required for the envelope comes from mixerContext — no store calls.
|
|
449
|
+
* - routes/alternates : mixerContext.routes filtered by schema+id
|
|
450
|
+
* - segments : getCategorySegments() from bom-core (pure)
|
|
451
|
+
* - breadcrumb : findRouteOfCategory() from bom-core + mixerContext.currentRoutes
|
|
452
|
+
*/
|
|
453
|
+
async function getPage(slug, id, market, locale, payload, user, mixerContext, draftMode) {
|
|
454
|
+
try {
|
|
455
|
+
const base = buildBaseOptions(user, locale, draftMode);
|
|
456
|
+
const page = await payload.findByID({
|
|
457
|
+
collection: slug,
|
|
458
|
+
id,
|
|
459
|
+
locale: base.locale,
|
|
460
|
+
fallbackLocale: base.fallbackLocale,
|
|
461
|
+
draft: base.draft,
|
|
462
|
+
depth: base.depth,
|
|
463
|
+
user: base.user,
|
|
464
|
+
overrideAccess: base.overrideAccess,
|
|
465
|
+
disableErrors: true,
|
|
466
|
+
context: mixerContext
|
|
467
|
+
});
|
|
468
|
+
if (page) {
|
|
469
|
+
const routes = mixerContext.routes.filter((r) => r.schema === slug && String(r.page) === String(id));
|
|
470
|
+
const currentRoute = routes.find((r) => r.market === market && r.locale === locale);
|
|
471
|
+
const alternates = routes;
|
|
472
|
+
const categories = mixerContext.categories;
|
|
473
|
+
const segments = getCategorySegments(categories, page);
|
|
474
|
+
const currentRoutes = mixerContext.currentRoutes;
|
|
475
|
+
const breadcrumb = currentRoute ? buildBreadcrumb(segments, currentRoute, currentRoutes) : [];
|
|
476
|
+
const parentRoute = breadcrumb.length > 1 ? breadcrumb[breadcrumb.length - 2] : void 0;
|
|
477
|
+
return {
|
|
478
|
+
...page,
|
|
479
|
+
href: currentRoute?.id,
|
|
480
|
+
alternates,
|
|
481
|
+
breadcrumb,
|
|
482
|
+
parentRoute
|
|
483
|
+
};
|
|
484
|
+
} else {
|
|
485
|
+
console.log("PageService.getPage.notfound", slug, id, locale);
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
} catch {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
//#endregion
|
|
493
|
+
//#region src/server/context.ts
|
|
494
|
+
/**
|
|
495
|
+
* Per-request context store (React.cache ensures a fresh Map per RSC render).
|
|
496
|
+
* Safe to use inside Next.js server components — do NOT call this at build time.
|
|
497
|
+
*/
|
|
498
|
+
const PageContext = cache(() => {
|
|
499
|
+
return /* @__PURE__ */ new Map();
|
|
500
|
+
});
|
|
501
|
+
async function getPageContext() {
|
|
502
|
+
return {
|
|
503
|
+
layout: PageContext().get("layout"),
|
|
504
|
+
page: PageContext().get("page"),
|
|
505
|
+
user: PageContext().get("user") || null,
|
|
506
|
+
label: PageContext().get("label"),
|
|
507
|
+
link: PageContext().get("link")
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
async function setPageContext(data) {
|
|
511
|
+
const context = PageContext();
|
|
512
|
+
for (const key in data) context.set(key, data[key]);
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Same as createPageContext but without draft mode — used for error/not-found pages
|
|
516
|
+
* that should always show published content regardless of draft mode state.
|
|
517
|
+
*/
|
|
518
|
+
async function createNotFoundContext(market, locale, slug, id, config, headers) {
|
|
519
|
+
const { payload, user, getContext } = await getPayload({
|
|
520
|
+
config,
|
|
521
|
+
headers
|
|
522
|
+
});
|
|
523
|
+
const context = await getContext(market, locale);
|
|
524
|
+
const layout = await getLayout(market, locale, payload, user, context);
|
|
525
|
+
const page = await getPage(slug, id, market, locale, payload, user, context);
|
|
526
|
+
const label = getLabelFromLabels(layout.labels);
|
|
527
|
+
const link = (category) => getLink(category, layout);
|
|
528
|
+
const pageContext = {
|
|
529
|
+
layout,
|
|
530
|
+
page,
|
|
531
|
+
user,
|
|
532
|
+
label,
|
|
533
|
+
link
|
|
534
|
+
};
|
|
535
|
+
setPageContext(pageContext);
|
|
536
|
+
return pageContext;
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Initialises the full page context using Payload local API.
|
|
540
|
+
*
|
|
541
|
+
* Replaces the @websolutespa/bom-next/server createPageContext that goes through the store
|
|
542
|
+
* abstraction. All data is fetched directly from Payload; the mixer context
|
|
543
|
+
* (routes + categories) is passed to every payload.find call so that
|
|
544
|
+
* payload-plugin-bowl can enrich responses.
|
|
545
|
+
*/
|
|
546
|
+
async function createPageContext(market, locale, slug, id, config, headers) {
|
|
547
|
+
const draft = { isEnabled: false };
|
|
548
|
+
const { payload, user, getContext } = await getPayload({
|
|
549
|
+
config,
|
|
550
|
+
headers
|
|
551
|
+
});
|
|
552
|
+
const context = await getContext(market, locale);
|
|
553
|
+
const layout = await getLayout(market, locale, payload, user, context, draft.isEnabled);
|
|
554
|
+
const page = await getPage(slug, id, market, locale, payload, user, context, draft.isEnabled);
|
|
555
|
+
const label = getLabelFromLabels(layout.labels);
|
|
556
|
+
const link = (category) => getLink(category, layout);
|
|
557
|
+
const pageContext = {
|
|
558
|
+
layout,
|
|
559
|
+
page,
|
|
560
|
+
user,
|
|
561
|
+
label,
|
|
562
|
+
link
|
|
563
|
+
};
|
|
564
|
+
setPageContext(pageContext);
|
|
565
|
+
return pageContext;
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Layout-only context — no page fetch. Used by RootLayout server components.
|
|
569
|
+
*/
|
|
570
|
+
async function createLayoutContext(market, locale, config, headers) {
|
|
571
|
+
const { payload, user, getContext } = await getPayload({
|
|
572
|
+
config,
|
|
573
|
+
headers
|
|
574
|
+
});
|
|
575
|
+
const layout = await getLayout(market, locale, payload, user, await getContext(market, locale));
|
|
576
|
+
const label = getLabelFromLabels(layout.labels);
|
|
577
|
+
const link = (category) => getLink(category, layout);
|
|
578
|
+
const pageContext = {
|
|
579
|
+
layout,
|
|
580
|
+
user,
|
|
581
|
+
label,
|
|
582
|
+
link
|
|
583
|
+
};
|
|
584
|
+
setPageContext(pageContext);
|
|
585
|
+
return pageContext;
|
|
586
|
+
}
|
|
587
|
+
//#endregion
|
|
588
|
+
//#region src/server/handlers/error-response.ts
|
|
589
|
+
const getStatus = (error) => {
|
|
590
|
+
return typeof error.status === "number" && error.status >= 400 && error.status < 600 ? error.status : 500;
|
|
591
|
+
};
|
|
592
|
+
const isPublic = (error, status) => {
|
|
593
|
+
return error.isPublic === true || error.isPublic === void 0 && status >= 400 && status < 500;
|
|
594
|
+
};
|
|
595
|
+
/**
|
|
596
|
+
* Converts errors thrown by Next or Payload handlers into Payload-compatible
|
|
597
|
+
* API responses without exposing messages or data from non-public errors.
|
|
598
|
+
*/
|
|
599
|
+
function getHandlerErrorResponse(error) {
|
|
600
|
+
const statusError = error && typeof error === "object" ? error : {};
|
|
601
|
+
const status = getStatus(statusError);
|
|
602
|
+
const publicError = isPublic(statusError, status);
|
|
603
|
+
const payloadError = { message: publicError && typeof statusError.message === "string" ? statusError.message : publicError && typeof statusError.statusText === "string" ? statusError.statusText : "Something went wrong." };
|
|
604
|
+
if (publicError && typeof statusError.name === "string") payloadError.name = statusError.name;
|
|
605
|
+
if (publicError && statusError.data !== null && statusError.data !== void 0) payloadError.data = statusError.data;
|
|
606
|
+
return Response.json({ errors: [payloadError] }, { status });
|
|
607
|
+
}
|
|
608
|
+
//#endregion
|
|
609
|
+
//#region src/server/handlers/auth.handler.ts
|
|
610
|
+
function getAuthMeHandler(config) {
|
|
611
|
+
return async function(request) {
|
|
612
|
+
try {
|
|
613
|
+
const { user } = await getPayload({
|
|
614
|
+
config,
|
|
615
|
+
headers: await headers()
|
|
616
|
+
});
|
|
617
|
+
if ((user?.roles || []).includes("user") && "firstName" in user) return Response.json({
|
|
618
|
+
id: user.id,
|
|
619
|
+
email: user.email,
|
|
620
|
+
firstName: user.firstName,
|
|
621
|
+
lastName: user.lastName
|
|
622
|
+
}, { status: 200 });
|
|
623
|
+
return Response.json(null, { status: 200 });
|
|
624
|
+
} catch (error) {
|
|
625
|
+
console.log("getAuthMeHandler.error", error);
|
|
626
|
+
return getHandlerErrorResponse(error);
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
//#endregion
|
|
631
|
+
//#region src/server/handlers/captions.handler.ts
|
|
632
|
+
function getCaptionsVTT(config) {
|
|
633
|
+
return async function(request) {
|
|
634
|
+
try {
|
|
635
|
+
const title = request.nextUrl.searchParams.get("title") || "Untitled";
|
|
636
|
+
const body = `WEBVTT
|
|
637
|
+
|
|
638
|
+
00:00:01.000 --> 00:00:03.000
|
|
639
|
+
- This video has no audio.
|
|
640
|
+
|
|
641
|
+
${title ? `
|
|
642
|
+
00:00:04.000 --> 00:00:06.000
|
|
643
|
+
- The title of the video is "${title}".
|
|
644
|
+
` : ""}`;
|
|
645
|
+
return new Response(body, {
|
|
646
|
+
status: 200,
|
|
647
|
+
headers: {
|
|
648
|
+
"Content-Type": "text/plain; charset=UTF-8",
|
|
649
|
+
"X-Frame-Options": "SAMEORIGIN",
|
|
650
|
+
"X-Robots-Tag": "noindex, follow"
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
} catch (error) {
|
|
654
|
+
console.error("getCaptionsVTT.error", error);
|
|
655
|
+
return getHandlerErrorResponse(error);
|
|
656
|
+
}
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
//#endregion
|
|
660
|
+
//#region src/server/local-api.ts
|
|
661
|
+
async function getLocalApi(config, slug) {
|
|
662
|
+
const payload = await getPayload$1({ config });
|
|
663
|
+
const collectionSlugs = payload.config.collections.map((x) => x.slug);
|
|
664
|
+
const globalSlugs = payload.config.globals.map((x) => x.slug);
|
|
665
|
+
[...collectionSlugs, ...globalSlugs].sort();
|
|
666
|
+
if (collectionSlugs.includes(slug)) {
|
|
667
|
+
const collectionSlug = slug;
|
|
668
|
+
return {
|
|
669
|
+
findMany: async (params) => {
|
|
670
|
+
return (await payload.find({
|
|
671
|
+
...params,
|
|
672
|
+
collection: collectionSlug,
|
|
673
|
+
pagination: false,
|
|
674
|
+
limit: 1e4,
|
|
675
|
+
overrideAccess: false
|
|
676
|
+
})).docs;
|
|
677
|
+
},
|
|
678
|
+
findPaged: async (params) => {
|
|
679
|
+
return await payload.find({
|
|
680
|
+
...params,
|
|
681
|
+
collection: collectionSlug,
|
|
682
|
+
overrideAccess: false
|
|
683
|
+
});
|
|
684
|
+
},
|
|
685
|
+
findOne: async (params) => {
|
|
686
|
+
return await payload.findByID({
|
|
687
|
+
id: "",
|
|
688
|
+
...params,
|
|
689
|
+
collection: collectionSlug,
|
|
690
|
+
disableErrors: true,
|
|
691
|
+
overrideAccess: false
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
};
|
|
695
|
+
} else if (globalSlugs.includes(slug)) {
|
|
696
|
+
const globalSlug = slug;
|
|
697
|
+
return {
|
|
698
|
+
findMany: async (params) => {
|
|
699
|
+
return await payload.findGlobal({
|
|
700
|
+
slug: globalSlug,
|
|
701
|
+
...params,
|
|
702
|
+
overrideAccess: false
|
|
703
|
+
});
|
|
704
|
+
},
|
|
705
|
+
findPaged: async (params) => {
|
|
706
|
+
throw "Not implemented";
|
|
707
|
+
},
|
|
708
|
+
findOne: async (params) => {
|
|
709
|
+
throw "Not implemented";
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
//#endregion
|
|
715
|
+
//#region src/server/handlers/collection.handler.ts
|
|
716
|
+
function collectionGetHandler(config) {
|
|
717
|
+
return async function(request, { params: getParams }) {
|
|
718
|
+
try {
|
|
719
|
+
const { payload, user, getContext } = await getPayload({
|
|
720
|
+
config,
|
|
721
|
+
headers: await headers()
|
|
722
|
+
});
|
|
723
|
+
const collectionSlugs = payload.config.collections.map((x) => x.slug);
|
|
724
|
+
const globalSlugs = payload.config.globals.map((x) => x.slug);
|
|
725
|
+
const keys = [...collectionSlugs, ...globalSlugs];
|
|
726
|
+
keys.sort();
|
|
727
|
+
const { collections } = await getParams;
|
|
728
|
+
if (!Array.isArray(collections) || collections?.length === 0) {
|
|
729
|
+
console.error("storeHandler.collectionGet", "method not allowed", collections);
|
|
730
|
+
return new Response("method not allowed", {
|
|
731
|
+
status: 405,
|
|
732
|
+
statusText: "method not allowed"
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
const slug = collections[0];
|
|
736
|
+
const id = collections[1];
|
|
737
|
+
const search = new URL(request.url).search;
|
|
738
|
+
const params = qsDeserialize(search);
|
|
739
|
+
const context = params.market && params.locale ? await getContext(params.market, params.locale) : void 0;
|
|
740
|
+
if (!keys.includes(slug)) {
|
|
741
|
+
console.error("storeHandler.collectionGet", "keys", slug);
|
|
742
|
+
console.error("storeHandler.collectionGet", "method not allowed", slug);
|
|
743
|
+
return new Response("method not allowed", {
|
|
744
|
+
status: 405,
|
|
745
|
+
statusText: "method not allowed"
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
const localApi = await getLocalApi(config, slug);
|
|
749
|
+
if (id) {
|
|
750
|
+
const item = await localApi.findOne({
|
|
751
|
+
...params,
|
|
752
|
+
id,
|
|
753
|
+
context,
|
|
754
|
+
user
|
|
755
|
+
});
|
|
756
|
+
if (item) return Response.json(item);
|
|
757
|
+
else return new Response("not found", {
|
|
758
|
+
status: 404,
|
|
759
|
+
statusText: "not found"
|
|
760
|
+
});
|
|
761
|
+
} else if (String(params.pagination) === "true") {
|
|
762
|
+
const items = await localApi.findPaged({
|
|
763
|
+
...params,
|
|
764
|
+
context,
|
|
765
|
+
user
|
|
766
|
+
});
|
|
767
|
+
return Response.json(items);
|
|
768
|
+
} else {
|
|
769
|
+
const items = await localApi.findMany({
|
|
770
|
+
...params,
|
|
771
|
+
context,
|
|
772
|
+
user
|
|
773
|
+
});
|
|
774
|
+
return Response.json(items);
|
|
775
|
+
}
|
|
776
|
+
} catch (error) {
|
|
777
|
+
const response = getHandlerErrorResponse(error);
|
|
778
|
+
console.error("storeHandler.collectionGet.error", response.status, error);
|
|
779
|
+
return response;
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
function collectionPostHandler(config) {
|
|
784
|
+
return async function(request, { params: getParams }) {
|
|
785
|
+
try {
|
|
786
|
+
const { payload } = await getPayload({ config });
|
|
787
|
+
const keys = [...payload.config.collections.map((x) => x.slug), ...payload.config.globals.map((x) => x.slug)];
|
|
788
|
+
keys.sort();
|
|
789
|
+
const { collections } = await getParams;
|
|
790
|
+
if (!Array.isArray(collections) || collections.length !== 1) {
|
|
791
|
+
console.error("method not allowed", collections);
|
|
792
|
+
return new Response("method not allowed", {
|
|
793
|
+
status: 405,
|
|
794
|
+
statusText: "method not allowed"
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
const slug = collections[0];
|
|
798
|
+
if (!keys.includes(slug)) {
|
|
799
|
+
console.error("storeHandler.collectionGet", "method not allowed", slug);
|
|
800
|
+
return new Response("method not allowed", {
|
|
801
|
+
status: 405,
|
|
802
|
+
statusText: "method not allowed"
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
} catch (error) {
|
|
806
|
+
const response = getHandlerErrorResponse(error);
|
|
807
|
+
console.error("storeHandler.collectionPost.error", response.status, error);
|
|
808
|
+
return response;
|
|
809
|
+
}
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
function collectionPatchHandler(config) {
|
|
813
|
+
return async function(request, { params: getParams }) {
|
|
814
|
+
try {
|
|
815
|
+
const { payload } = await getPayload({ config });
|
|
816
|
+
const keys = [...payload.config.collections.map((x) => x.slug), ...payload.config.globals.map((x) => x.slug)];
|
|
817
|
+
keys.sort();
|
|
818
|
+
const { collections } = await getParams;
|
|
819
|
+
if (!Array.isArray(collections) || collections.length !== 2) {
|
|
820
|
+
console.error("method not allowed", collections);
|
|
821
|
+
return new Response("method not allowed", {
|
|
822
|
+
status: 405,
|
|
823
|
+
statusText: "method not allowed"
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
const slug = collections[0];
|
|
827
|
+
if (!keys.includes(slug)) {
|
|
828
|
+
console.error("storeHandler.collectionGet", "method not allowed", slug);
|
|
829
|
+
return new Response("method not allowed", {
|
|
830
|
+
status: 405,
|
|
831
|
+
statusText: "method not allowed"
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
} catch (error) {
|
|
835
|
+
const response = getHandlerErrorResponse(error);
|
|
836
|
+
console.error("storeHandler.collectionPatch.error", response.status, error);
|
|
837
|
+
return response;
|
|
838
|
+
}
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
function collectionDeleteHandler(config) {
|
|
842
|
+
return async function(request, { params: getParams }) {
|
|
843
|
+
try {
|
|
844
|
+
const { payload } = await getPayload({ config });
|
|
845
|
+
const keys = [...payload.config.collections.map((x) => x.slug), ...payload.config.globals.map((x) => x.slug)];
|
|
846
|
+
keys.sort();
|
|
847
|
+
const { collections } = await getParams;
|
|
848
|
+
if (!Array.isArray(collections) || collections.length !== 2) {
|
|
849
|
+
console.error("method not allowed", collections);
|
|
850
|
+
return new Response("method not allowed", {
|
|
851
|
+
status: 405,
|
|
852
|
+
statusText: "method not allowed"
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
const slug = collections[0];
|
|
856
|
+
if (!keys.includes(slug)) {
|
|
857
|
+
console.error("storeHandler.collectionGet", "method not allowed", slug);
|
|
858
|
+
return new Response("method not allowed", {
|
|
859
|
+
status: 405,
|
|
860
|
+
statusText: "method not allowed"
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
collections[1];
|
|
864
|
+
} catch (error) {
|
|
865
|
+
const response = getHandlerErrorResponse(error);
|
|
866
|
+
console.error("storeHandler.collectionDelete.error", response.status, error);
|
|
867
|
+
return response;
|
|
868
|
+
}
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
//#endregion
|
|
872
|
+
//#region src/server/handlers/index.handler.ts
|
|
873
|
+
function indexGetHandler(config) {
|
|
874
|
+
return async function(request) {
|
|
875
|
+
try {
|
|
876
|
+
const payload = await getPayload$1({ config });
|
|
877
|
+
const keys = [...payload.config.collections.map((x) => x.slug), ...payload.config.globals.map((x) => x.slug)];
|
|
878
|
+
keys.sort();
|
|
879
|
+
const market = defaultMarket;
|
|
880
|
+
const locale = defaultLocale;
|
|
881
|
+
if (!Array.isArray(keys) || keys?.length === 0) {
|
|
882
|
+
console.error("apiHome.collectionGet", "collections not found", keys);
|
|
883
|
+
return new Response("method not allowed", {
|
|
884
|
+
status: 405,
|
|
885
|
+
statusText: "method not allowed"
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
return new Response(`
|
|
889
|
+
<html>
|
|
890
|
+
<head>
|
|
891
|
+
<title>REST Api</title>
|
|
892
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
|
893
|
+
<style type="text/css">
|
|
894
|
+
html {
|
|
895
|
+
padding: 0;
|
|
896
|
+
background: #09090A;
|
|
897
|
+
color: #fefefe;
|
|
898
|
+
}
|
|
899
|
+
body {
|
|
900
|
+
padding: 15px;
|
|
901
|
+
font-family: Inter,-apple-system,'Helvetica Neue','Segoe UI',Roboto,Arial,'Noto Sans',sans-serif;
|
|
902
|
+
font-size: 13px;
|
|
903
|
+
background: #09090A;
|
|
904
|
+
color: #fefefe;
|
|
905
|
+
}
|
|
906
|
+
.container {
|
|
907
|
+
margin: 0 auto;
|
|
908
|
+
max-width: 1280px;
|
|
909
|
+
}
|
|
910
|
+
a {
|
|
911
|
+
color: #fefefe;
|
|
912
|
+
text-decoration: none;
|
|
913
|
+
}
|
|
914
|
+
a:visited {
|
|
915
|
+
color: #9e9e9e;
|
|
916
|
+
}
|
|
917
|
+
a:hover {
|
|
918
|
+
text-decoration: underline;
|
|
919
|
+
}
|
|
920
|
+
table {
|
|
921
|
+
width: 100%;
|
|
922
|
+
margin: 10px 0;
|
|
923
|
+
border: none;
|
|
924
|
+
border-collapse: collapse;
|
|
925
|
+
}
|
|
926
|
+
td,
|
|
927
|
+
th {
|
|
928
|
+
text-align: left;
|
|
929
|
+
font-size: 12px;
|
|
930
|
+
padding: 3px;
|
|
931
|
+
}
|
|
932
|
+
th:not(:last-child),
|
|
933
|
+
td:not(:last-child) {
|
|
934
|
+
padding-right: 30px;
|
|
935
|
+
}
|
|
936
|
+
thead th {
|
|
937
|
+
border-bottom: 1px solid #0e0e0f;
|
|
938
|
+
}
|
|
939
|
+
#table tr:nth-child(odd) td {
|
|
940
|
+
background-color: #0e0e0f;
|
|
941
|
+
}
|
|
942
|
+
#table tbody tr:hover td {
|
|
943
|
+
background-color: #1A1A1B;
|
|
944
|
+
}
|
|
945
|
+
#table tbody tr:hover td,
|
|
946
|
+
#table tbody tr:hover td a {
|
|
947
|
+
color: #fefefe;
|
|
948
|
+
}
|
|
949
|
+
.wysiwyg {
|
|
950
|
+
margin: 0;
|
|
951
|
+
padding: 0 0 10px 0;
|
|
952
|
+
line-height: 1.2em;
|
|
953
|
+
}
|
|
954
|
+
.wysiwyg a {
|
|
955
|
+
color: #00ea4e;
|
|
956
|
+
font-weight: 600;
|
|
957
|
+
}
|
|
958
|
+
.wysiwyg a:visited {
|
|
959
|
+
color: #00ea4e;
|
|
960
|
+
}
|
|
961
|
+
</style>
|
|
962
|
+
</head>
|
|
963
|
+
<body>
|
|
964
|
+
<div class="container">
|
|
965
|
+
<h1>REST API</h1>
|
|
966
|
+
<p class="wysiwyg">
|
|
967
|
+
This is the REST Api generated by <a href="https://github.com/websolutespa/bom" target="_blank" rel="noopener">Mixer</a>.
|
|
968
|
+
</p>
|
|
969
|
+
<p class="wysiwyg">
|
|
970
|
+
It contains a list of all the generic local api CRUD handlers of Mixer app. <br />
|
|
971
|
+
All methods should be called with market and locale parameters.
|
|
972
|
+
</p>
|
|
973
|
+
<table id="table" cellpadding="3">
|
|
974
|
+
<thead>
|
|
975
|
+
<tr>
|
|
976
|
+
<th width="150px">Method</th>
|
|
977
|
+
<th width="150px">Collection</th>
|
|
978
|
+
<th>Endpoint</th>
|
|
979
|
+
</tr>
|
|
980
|
+
</thead>
|
|
981
|
+
<tbody>
|
|
982
|
+
${keys.map((c) => `
|
|
983
|
+
<tr>
|
|
984
|
+
<td>GET, POST, PATCH</td>
|
|
985
|
+
<td>${c}</td>
|
|
986
|
+
<td><a href="/api/${c}?market=${market}&locale=${locale}" target="_blank">/api/${c}</a></td>
|
|
987
|
+
</tr>
|
|
988
|
+
<tr>
|
|
989
|
+
<td>GET, DELETE</td>
|
|
990
|
+
<td>${c}</td>
|
|
991
|
+
<td><a href="/api/${c}/1?market=${market}&locale=${locale}" target="_blank">/api/${c}/{id}</a></td>
|
|
992
|
+
</tr>
|
|
993
|
+
`).join("\n")}
|
|
994
|
+
</tbody>
|
|
995
|
+
</table>
|
|
996
|
+
</div>
|
|
997
|
+
</body>
|
|
998
|
+
`, { headers: {
|
|
999
|
+
"Content-Type": "text/html; charset=UTF-8",
|
|
1000
|
+
"X-Frame-Options": "SAMEORIGIN",
|
|
1001
|
+
"X-Robots-Tag": "noindex, follow"
|
|
1002
|
+
} });
|
|
1003
|
+
} catch (error) {
|
|
1004
|
+
const response = getHandlerErrorResponse(error);
|
|
1005
|
+
console.error("storeHandler.indexGet.error", response.status, error);
|
|
1006
|
+
return response;
|
|
1007
|
+
}
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
//#endregion
|
|
1011
|
+
//#region src/server/handlers/preview.handler.ts
|
|
1012
|
+
async function previewHandler(request) {
|
|
1013
|
+
const { searchParams } = new URL(request.url);
|
|
1014
|
+
const { marketId, localeId, templateId, documentId, _status, _live } = Object.fromEntries(searchParams);
|
|
1015
|
+
const destination = `/${marketId}/${localeId}/${templateId}/${documentId}${_live ? "?_live=true" : ""}`;
|
|
1016
|
+
(await draftMode()).enable();
|
|
1017
|
+
const path = `${process.env.NEXT_PUBLIC_URL}${destination}`;
|
|
1018
|
+
return NextResponse.redirect(path, { status: 303 });
|
|
1019
|
+
}
|
|
1020
|
+
//#endregion
|
|
1021
|
+
//#region src/server/handlers/registry.handler.ts
|
|
1022
|
+
const REGISTRY_SCHEMA = "https://ui.shadcn.com/schema/registry.json";
|
|
1023
|
+
const REGISTRY_ITEM_SCHEMA = "https://ui.shadcn.com/schema/registry-item.json";
|
|
1024
|
+
const SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1025
|
+
".css",
|
|
1026
|
+
".json",
|
|
1027
|
+
".ts",
|
|
1028
|
+
".tsx"
|
|
1029
|
+
]);
|
|
1030
|
+
const PUBLISHED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
1031
|
+
"components",
|
|
1032
|
+
"examples",
|
|
1033
|
+
"hooks",
|
|
1034
|
+
"lazy",
|
|
1035
|
+
"lib",
|
|
1036
|
+
"sections",
|
|
1037
|
+
"theme"
|
|
1038
|
+
]);
|
|
1039
|
+
const SHADCN_COMPONENTS = /* @__PURE__ */ new Set([
|
|
1040
|
+
"accordion",
|
|
1041
|
+
"alert-dialog",
|
|
1042
|
+
"alert",
|
|
1043
|
+
"aspect-ratio",
|
|
1044
|
+
"avatar",
|
|
1045
|
+
"badge",
|
|
1046
|
+
"breadcrumb",
|
|
1047
|
+
"calendar",
|
|
1048
|
+
"card",
|
|
1049
|
+
"carousel",
|
|
1050
|
+
"chart",
|
|
1051
|
+
"checkbox",
|
|
1052
|
+
"collapsible",
|
|
1053
|
+
"combobox",
|
|
1054
|
+
"command",
|
|
1055
|
+
"context-menu",
|
|
1056
|
+
"dialog",
|
|
1057
|
+
"drawer",
|
|
1058
|
+
"dropdown-menu",
|
|
1059
|
+
"form",
|
|
1060
|
+
"hover-card",
|
|
1061
|
+
"input",
|
|
1062
|
+
"input-otp",
|
|
1063
|
+
"label",
|
|
1064
|
+
"menubar",
|
|
1065
|
+
"navigation-menu",
|
|
1066
|
+
"pagination",
|
|
1067
|
+
"popover",
|
|
1068
|
+
"progress",
|
|
1069
|
+
"radio-group",
|
|
1070
|
+
"resizable",
|
|
1071
|
+
"scroll-area",
|
|
1072
|
+
"select",
|
|
1073
|
+
"separator",
|
|
1074
|
+
"sheet",
|
|
1075
|
+
"sidebar",
|
|
1076
|
+
"skeleton",
|
|
1077
|
+
"slider",
|
|
1078
|
+
"sonner",
|
|
1079
|
+
"switch",
|
|
1080
|
+
"table",
|
|
1081
|
+
"tabs",
|
|
1082
|
+
"textarea",
|
|
1083
|
+
"toast",
|
|
1084
|
+
"toggle",
|
|
1085
|
+
"toggle-group",
|
|
1086
|
+
"tooltip"
|
|
1087
|
+
]);
|
|
1088
|
+
function toKebabCase(value) {
|
|
1089
|
+
return value.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[._\s]+/g, "-").toLowerCase();
|
|
1090
|
+
}
|
|
1091
|
+
function titleFromName(name) {
|
|
1092
|
+
return name.replace(/(^|[-_])(\w)/g, (_, __, character) => character.toUpperCase());
|
|
1093
|
+
}
|
|
1094
|
+
function packageName(specifier) {
|
|
1095
|
+
return specifier.startsWith("@") ? specifier.split("/").slice(0, 2).join("/") : specifier.split("/")[0];
|
|
1096
|
+
}
|
|
1097
|
+
function getOrigin$1(request) {
|
|
1098
|
+
return process.env.NEXT_PUBLIC_URL?.replace(/\/$/, "") || request.nextUrl.origin;
|
|
1099
|
+
}
|
|
1100
|
+
async function directoryExists(directory) {
|
|
1101
|
+
try {
|
|
1102
|
+
return (await stat(directory)).isDirectory();
|
|
1103
|
+
} catch {
|
|
1104
|
+
return false;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
async function getUiSourceDirectory() {
|
|
1108
|
+
if (process.env.UI_REGISTRY_SOURCE_DIR) {
|
|
1109
|
+
const configuredPath = resolve(process.env.UI_REGISTRY_SOURCE_DIR);
|
|
1110
|
+
if (await directoryExists(configuredPath)) return configuredPath;
|
|
1111
|
+
throw new Error(`UI_REGISTRY_SOURCE_DIR does not exist: ${configuredPath}`);
|
|
1112
|
+
}
|
|
1113
|
+
let currentDirectory = process.cwd();
|
|
1114
|
+
while (dirname(currentDirectory) !== currentDirectory) {
|
|
1115
|
+
const candidate = join(currentDirectory, "packages", "ui", "src");
|
|
1116
|
+
if (await directoryExists(candidate)) return candidate;
|
|
1117
|
+
currentDirectory = dirname(currentDirectory);
|
|
1118
|
+
}
|
|
1119
|
+
throw new Error("Unable to find packages/ui/src. Set UI_REGISTRY_SOURCE_DIR to its absolute path.");
|
|
1120
|
+
}
|
|
1121
|
+
async function getSourceFiles(directory, sourceDirectory = directory) {
|
|
1122
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
1123
|
+
return (await Promise.all(entries.map(async (entry) => {
|
|
1124
|
+
const absolutePath = join(directory, entry.name);
|
|
1125
|
+
if (entry.isDirectory()) return getSourceFiles(absolutePath, sourceDirectory);
|
|
1126
|
+
if (!SOURCE_EXTENSIONS.has(extname(entry.name)) || entry.name.endsWith(".d.ts") || /\.(test|spec)\.[^.]+$/.test(entry.name)) return [];
|
|
1127
|
+
return [{
|
|
1128
|
+
absolutePath,
|
|
1129
|
+
relativePath: entry.name
|
|
1130
|
+
}];
|
|
1131
|
+
}))).flat().map((file) => ({
|
|
1132
|
+
...file,
|
|
1133
|
+
relativePath: relative(sourceDirectory, file.absolutePath).replaceAll("\\", "/")
|
|
1134
|
+
}));
|
|
1135
|
+
}
|
|
1136
|
+
function getImportSpecifiers(source) {
|
|
1137
|
+
return [...source.matchAll(/(?:import|export)\s+(?:type\s+)?(?:[\s\S]*?\s+from\s+)?['"]([^'"]+)['"]/g)].map((match) => match[1]);
|
|
1138
|
+
}
|
|
1139
|
+
function resolveSourceImport(sourceDirectory, sourceFile, specifier, sourceFiles) {
|
|
1140
|
+
let candidate;
|
|
1141
|
+
if (specifier.startsWith("@app/ui/")) candidate = specifier.slice(8);
|
|
1142
|
+
if (specifier.startsWith(".")) candidate = relative(sourceDirectory, resolve(dirname(sourceFile), specifier)).replaceAll("\\", "/");
|
|
1143
|
+
if (!candidate) return void 0;
|
|
1144
|
+
const normalizedCandidate = candidate.replace(/^\.\//, "");
|
|
1145
|
+
return sourceFiles.get(normalizedCandidate) || sourceFiles.get(`${normalizedCandidate}.ts`) || sourceFiles.get(`${normalizedCandidate}.tsx`) || sourceFiles.get(`${normalizedCandidate}/index.ts`) || sourceFiles.get(`${normalizedCandidate}/index.tsx`);
|
|
1146
|
+
}
|
|
1147
|
+
function rewriteUiImports(source, sourceFile, sourceDirectory, sourceFiles) {
|
|
1148
|
+
return source.replace(/(['"])(@app\/ui\/[^'"]+|\.\.?\/[^'"]+)\1/g, (match, quote, specifier) => {
|
|
1149
|
+
const importedFile = resolveSourceImport(sourceDirectory, sourceFile.absolutePath, specifier, sourceFiles);
|
|
1150
|
+
return importedFile ? `${quote}${getImportPath(importedFile.relativePath)}${quote}` : match;
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
function getImportPath(relativePath) {
|
|
1154
|
+
const extensionlessPath = relativePath.replace(/\.(tsx?|mts|cts)$/, "");
|
|
1155
|
+
if (extensionlessPath.startsWith("components/")) return `@/components/ui/${extensionlessPath.slice(11)}`;
|
|
1156
|
+
if (extensionlessPath.startsWith("hooks/")) return `@/hooks/${extensionlessPath.slice(6)}`;
|
|
1157
|
+
if (extensionlessPath.startsWith("lib/")) return `@/lib/${extensionlessPath.slice(4)}`;
|
|
1158
|
+
return `@/components/${extensionlessPath}`;
|
|
1159
|
+
}
|
|
1160
|
+
function getTargetPath(relativePath) {
|
|
1161
|
+
if (relativePath.startsWith("components/")) return `@ui/${relativePath.slice(11)}`;
|
|
1162
|
+
if (relativePath.startsWith("hooks/")) return `@hooks/${relativePath.slice(6)}`;
|
|
1163
|
+
if (relativePath.startsWith("lib/")) return `@lib/${relativePath.slice(4)}`;
|
|
1164
|
+
return `@components/${relativePath}`;
|
|
1165
|
+
}
|
|
1166
|
+
function getRegistryFileType(relativePath) {
|
|
1167
|
+
if (relativePath.startsWith("components/")) return "registry:ui";
|
|
1168
|
+
if (relativePath.startsWith("hooks/")) return "registry:hook";
|
|
1169
|
+
if (relativePath.startsWith("theme/")) return "registry:style";
|
|
1170
|
+
if (extname(relativePath) === ".json") return "registry:file";
|
|
1171
|
+
return "registry:lib";
|
|
1172
|
+
}
|
|
1173
|
+
function isPublishedFile(file) {
|
|
1174
|
+
const [directory] = file.relativePath.split("/");
|
|
1175
|
+
return PUBLISHED_DIRECTORIES.has(directory) && !/(^|\/)index\.[^.]+$/.test(file.relativePath);
|
|
1176
|
+
}
|
|
1177
|
+
function getRegistryItemName(file) {
|
|
1178
|
+
const [directory, ...path] = file.relativePath.split("/");
|
|
1179
|
+
const name = toKebabCase(path.join("/").replace(extname(file.relativePath), "").replaceAll("/", "-"));
|
|
1180
|
+
return directory === "components" ? name : `${directory}-${name}`;
|
|
1181
|
+
}
|
|
1182
|
+
async function createRegistryCatalog(sourceDirectory, origin) {
|
|
1183
|
+
const files = (await getSourceFiles(sourceDirectory)).filter((file) => PUBLISHED_DIRECTORIES.has(file.relativePath.split("/")[0]));
|
|
1184
|
+
const filesByRelativePath = new Map(files.map((file) => [file.relativePath, file]));
|
|
1185
|
+
const entries = new Map(files.filter(isPublishedFile).map((file) => [getRegistryItemName(file), file]));
|
|
1186
|
+
const entryNamesByPath = new Map([...entries].map(([name, file]) => [file.relativePath, name]));
|
|
1187
|
+
const includedItems = new Set([...entries].filter(([name, file]) => !file.relativePath.startsWith("components/") || name === "button" || !SHADCN_COMPONENTS.has(name)).map(([name]) => name));
|
|
1188
|
+
async function getItem(name) {
|
|
1189
|
+
const entry = entries.get(name);
|
|
1190
|
+
if (!entry || !includedItems.has(name)) return void 0;
|
|
1191
|
+
const filesToInclude = /* @__PURE__ */ new Map();
|
|
1192
|
+
const externalDependencies = /* @__PURE__ */ new Set();
|
|
1193
|
+
const registryDependencies = /* @__PURE__ */ new Set();
|
|
1194
|
+
const pendingFiles = [entry];
|
|
1195
|
+
while (pendingFiles.length) {
|
|
1196
|
+
const file = pendingFiles.pop();
|
|
1197
|
+
if (filesToInclude.has(file.relativePath)) continue;
|
|
1198
|
+
filesToInclude.set(file.relativePath, file);
|
|
1199
|
+
const source = await readFile(file.absolutePath, "utf8");
|
|
1200
|
+
for (const specifier of getImportSpecifiers(source)) {
|
|
1201
|
+
const localFile = resolveSourceImport(sourceDirectory, file.absolutePath, specifier, filesByRelativePath);
|
|
1202
|
+
const dependencyName = localFile && entryNamesByPath.get(localFile.relativePath);
|
|
1203
|
+
if (localFile?.relativePath.startsWith("components/")) {
|
|
1204
|
+
const componentName = toKebabCase(basename(localFile.relativePath, extname(localFile.relativePath)));
|
|
1205
|
+
if (dependencyName !== name) registryDependencies.add(includedItems.has(componentName) ? `${origin}/r/${componentName}.json` : componentName);
|
|
1206
|
+
} else if (localFile && dependencyName && dependencyName !== name && includedItems.has(dependencyName)) registryDependencies.add(`${origin}/r/${dependencyName}.json`);
|
|
1207
|
+
else if (localFile) pendingFiles.push(localFile);
|
|
1208
|
+
else if (!specifier.startsWith(".") && !specifier.startsWith("@app/ui/") && !specifier.startsWith("node:") && specifier !== "react" && specifier !== "react-dom") externalDependencies.add(packageName(specifier));
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
const registryFiles = await Promise.all([...filesToInclude.values()].sort((left, right) => left.relativePath.localeCompare(right.relativePath)).map(async (file) => ({
|
|
1212
|
+
path: file.relativePath,
|
|
1213
|
+
content: rewriteUiImports(await readFile(file.absolutePath, "utf8"), file, sourceDirectory, filesByRelativePath),
|
|
1214
|
+
type: getRegistryFileType(file.relativePath),
|
|
1215
|
+
target: getTargetPath(file.relativePath)
|
|
1216
|
+
})));
|
|
1217
|
+
return {
|
|
1218
|
+
$schema: REGISTRY_ITEM_SCHEMA,
|
|
1219
|
+
name,
|
|
1220
|
+
type: "registry:ui",
|
|
1221
|
+
title: titleFromName(name),
|
|
1222
|
+
...externalDependencies.size && { dependencies: [...externalDependencies].sort() },
|
|
1223
|
+
...registryDependencies.size && { registryDependencies: [...registryDependencies].sort() },
|
|
1224
|
+
files: registryFiles
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
async function getCatalogItem(name) {
|
|
1228
|
+
const item = await getItem(name);
|
|
1229
|
+
if (!item) return void 0;
|
|
1230
|
+
return {
|
|
1231
|
+
...item,
|
|
1232
|
+
files: item.files.map((file) => ({
|
|
1233
|
+
path: file.path,
|
|
1234
|
+
type: file.type,
|
|
1235
|
+
target: file.target
|
|
1236
|
+
}))
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
return {
|
|
1240
|
+
items: includedItems,
|
|
1241
|
+
getCatalogItem,
|
|
1242
|
+
getItem
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
function jsonResponse(body, status = 200) {
|
|
1246
|
+
return Response.json(body, {
|
|
1247
|
+
status,
|
|
1248
|
+
headers: {
|
|
1249
|
+
"X-Frame-Options": "SAMEORIGIN",
|
|
1250
|
+
"X-Robots-Tag": "noindex, follow"
|
|
1251
|
+
}
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
function getRegistry() {
|
|
1255
|
+
return async function(request) {
|
|
1256
|
+
const catalog = await createRegistryCatalog(await getUiSourceDirectory(), getOrigin$1(request));
|
|
1257
|
+
const items = (await Promise.all([...catalog.items].sort().map((name) => catalog.getCatalogItem(name)))).filter(Boolean);
|
|
1258
|
+
return jsonResponse({
|
|
1259
|
+
$schema: REGISTRY_SCHEMA,
|
|
1260
|
+
name: "bom-ui",
|
|
1261
|
+
homepage: getOrigin$1(request),
|
|
1262
|
+
items
|
|
1263
|
+
});
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
function getRegistryItem() {
|
|
1267
|
+
return async function(request, { params: getParams }) {
|
|
1268
|
+
const { slug } = await getParams;
|
|
1269
|
+
const name = slug.at(-1)?.replace(/\.json$/, "");
|
|
1270
|
+
if (!name) return jsonResponse({ error: "Registry item name is required." }, 404);
|
|
1271
|
+
const item = await (await createRegistryCatalog(await getUiSourceDirectory(), getOrigin$1(request))).getItem(name);
|
|
1272
|
+
return item ? jsonResponse(item) : jsonResponse({ error: `Registry item not found: ${name}` }, 404);
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1275
|
+
//#endregion
|
|
1276
|
+
//#region src/server/handlers/route.handler.ts
|
|
1277
|
+
/**
|
|
1278
|
+
* Creates the internal `/-route` GET handler used by middleware to resolve CMS
|
|
1279
|
+
* paths, route schemas, and redirects before a page is rendered.
|
|
1280
|
+
*
|
|
1281
|
+
* One Payload request is created per incoming API request so authentication and
|
|
1282
|
+
* query context are shared by every route operation. The route service then
|
|
1283
|
+
* reuses its snapshot cache, which avoids rebuilding the complete route graph
|
|
1284
|
+
* for repeated market, locale, and access contexts.
|
|
1285
|
+
*/
|
|
1286
|
+
function getRouteHandler(config) {
|
|
1287
|
+
return async function(request) {
|
|
1288
|
+
try {
|
|
1289
|
+
const searchParams = request.nextUrl.searchParams;
|
|
1290
|
+
const pathname = searchParams.get("pathname") || void 0;
|
|
1291
|
+
const href = searchParams.get("href") || void 0;
|
|
1292
|
+
const hrefBeforeRedirect = searchParams.get("hrefBeforeRedirect") || void 0;
|
|
1293
|
+
const schema = searchParams.get("schema") || void 0;
|
|
1294
|
+
const market = searchParams.get("market") || void 0;
|
|
1295
|
+
const locale = searchParams.get("locale") || void 0;
|
|
1296
|
+
const payload = await getPayload$1({ config });
|
|
1297
|
+
const req = await createPayloadRequest({
|
|
1298
|
+
config,
|
|
1299
|
+
request
|
|
1300
|
+
});
|
|
1301
|
+
req.payload = payload;
|
|
1302
|
+
req.user = guest;
|
|
1303
|
+
if (schema && market && locale) {
|
|
1304
|
+
req.query = {
|
|
1305
|
+
locale,
|
|
1306
|
+
market
|
|
1307
|
+
};
|
|
1308
|
+
const route = (await getRouteSnapshot(req)).getSchema(schema, market, locale);
|
|
1309
|
+
return route ? ResponseSuccess({ route }) : ResponseNotFound();
|
|
1310
|
+
}
|
|
1311
|
+
if (pathname && href) {
|
|
1312
|
+
const resolution = (await getRouteSnapshot(req)).resolve(pathname);
|
|
1313
|
+
const route = resolution.route;
|
|
1314
|
+
if (route !== void 0) return ResponseSuccess({
|
|
1315
|
+
parent: resolution.parent,
|
|
1316
|
+
route: {
|
|
1317
|
+
...route,
|
|
1318
|
+
...resolution.splat ? { splat: resolution.splat } : {}
|
|
1319
|
+
}
|
|
1320
|
+
});
|
|
1321
|
+
else {
|
|
1322
|
+
const url = new URL(href);
|
|
1323
|
+
const urlBeforeRedirect = hrefBeforeRedirect ? new URL(hrefBeforeRedirect) : null;
|
|
1324
|
+
const sanitizedHref = sanitizeHref(url, urlBeforeRedirect);
|
|
1325
|
+
const { payload } = req;
|
|
1326
|
+
const payloadResponse = await payload.find({
|
|
1327
|
+
collection: "redirect",
|
|
1328
|
+
where: { isActive: { equals: true } },
|
|
1329
|
+
sort: "order",
|
|
1330
|
+
depth: 0,
|
|
1331
|
+
limit: 1e4,
|
|
1332
|
+
pagination: false,
|
|
1333
|
+
req,
|
|
1334
|
+
overrideAccess: true
|
|
1335
|
+
});
|
|
1336
|
+
let redirects = payloadResponse.docs ? payloadResponse.docs : [];
|
|
1337
|
+
redirects = redirects.map((x) => ({
|
|
1338
|
+
...x,
|
|
1339
|
+
from: sanitizeRedirectFrom(x.from, url, urlBeforeRedirect),
|
|
1340
|
+
to: sanitizeRedirectTo(x.to)
|
|
1341
|
+
}));
|
|
1342
|
+
const exactMatchResolver = (redirect) => {
|
|
1343
|
+
const status = parseInt(redirect.status) || 307;
|
|
1344
|
+
if (status < 400) {
|
|
1345
|
+
const redirectUrl = sanitizeRedirectUrl(redirect.to, url);
|
|
1346
|
+
return Response.json({ redirectUrl }, { status });
|
|
1347
|
+
} else return Response.json("gone", { status });
|
|
1348
|
+
};
|
|
1349
|
+
const exactMatchRedirect = redirects.find((x) => x.from === sanitizedHref);
|
|
1350
|
+
if (exactMatchRedirect !== void 0) return exactMatchResolver(exactMatchRedirect);
|
|
1351
|
+
const patternMatchResolver = (redirect) => {
|
|
1352
|
+
const status = parseInt(redirect.status) || 307;
|
|
1353
|
+
if (status < 400) {
|
|
1354
|
+
const regexp = new RegExp(redirect.from);
|
|
1355
|
+
let i = 0;
|
|
1356
|
+
let redirectUrl = redirect.to.replace(/\*/g, (...rest) => {
|
|
1357
|
+
return `$${++i}`;
|
|
1358
|
+
});
|
|
1359
|
+
redirectUrl = sanitizedHref.replace(regexp, redirectUrl);
|
|
1360
|
+
return Response.json({ redirectUrl }, { status });
|
|
1361
|
+
} else return Response.json("gone", { status });
|
|
1362
|
+
};
|
|
1363
|
+
redirects.forEach((x) => {
|
|
1364
|
+
let expression = x.from;
|
|
1365
|
+
expression = expression.replace(/\*(?!\))/g, "(.*)");
|
|
1366
|
+
expression = `^${expression.replace(/(^\^)|(\$$)/g, "")}$`;
|
|
1367
|
+
x.from = expression;
|
|
1368
|
+
});
|
|
1369
|
+
const firstMatch = redirects.find((x) => sanitizedHref.match(x.from));
|
|
1370
|
+
if (firstMatch) return patternMatchResolver(firstMatch);
|
|
1371
|
+
return ResponseSuccess({ parent: resolution.parent });
|
|
1372
|
+
}
|
|
1373
|
+
} else {
|
|
1374
|
+
const query = qsDeserialize(decodeURIComponent(searchParams.toString()));
|
|
1375
|
+
req.query = query;
|
|
1376
|
+
const routes = await getRoutes$1(req);
|
|
1377
|
+
let items = await whereCollection(routes, query.where);
|
|
1378
|
+
items = await sortCollection(items, query.sort);
|
|
1379
|
+
return ResponseSuccess(items);
|
|
1380
|
+
}
|
|
1381
|
+
} catch (error) {
|
|
1382
|
+
console.error("routeGetHandler.error", error);
|
|
1383
|
+
return getHandlerErrorResponse(error);
|
|
1384
|
+
}
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
//#endregion
|
|
1388
|
+
//#region src/server/handlers/sitemap.handler.ts
|
|
1389
|
+
const origin = process.env.NEXT_PUBLIC_URL || "";
|
|
1390
|
+
function getSiteMapIndex(config) {
|
|
1391
|
+
return async function(request) {
|
|
1392
|
+
try {
|
|
1393
|
+
const { getContext } = await getPayload({ config });
|
|
1394
|
+
const { markets, locales, routes } = await getContext(defaultMarket, defaultLocale);
|
|
1395
|
+
const sitemaps = [];
|
|
1396
|
+
const getTime = (date) => {
|
|
1397
|
+
return typeof date !== "undefined" ? (typeof date === "string" ? new Date(date) : date).getTime() : 0;
|
|
1398
|
+
};
|
|
1399
|
+
eachMarketLocale(markets, locales, (market, locale, markets, locales) => {
|
|
1400
|
+
const sitemapRoutes = routes.filter((x) => x.market === market.id && x.locale === locale.id);
|
|
1401
|
+
sitemapRoutes.sort((a, b) => getTime(a.updatedAt) - getTime(b.updatedAt));
|
|
1402
|
+
sitemaps.push({
|
|
1403
|
+
id: `/${market.id}/${locale.id}/sitemap.xml`,
|
|
1404
|
+
updatedAt: sitemapRoutes.length > 0 ? sitemapRoutes[sitemapRoutes.length - 1].updatedAt : void 0
|
|
1405
|
+
});
|
|
1406
|
+
});
|
|
1407
|
+
const body = `<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type="text/xsl" href="${origin}/sitemap.xsl"?>
|
|
1408
|
+
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
1409
|
+
${sitemaps.map((sitemap) => `
|
|
1410
|
+
<sitemap>
|
|
1411
|
+
<loc>${escapeHtml(encodeURI(`${origin}${sitemap.id}`))}</loc>${sitemap.updatedAt ? `
|
|
1412
|
+
<lastmod>${escapeHtml(String(sitemap.updatedAt))}</lastmod>` : ""}
|
|
1413
|
+
</sitemap>
|
|
1414
|
+
`).join("")}
|
|
1415
|
+
</sitemapindex>`;
|
|
1416
|
+
return new Response(body, {
|
|
1417
|
+
status: 200,
|
|
1418
|
+
headers: {
|
|
1419
|
+
"Content-Type": "application/xml; charset=UTF-8",
|
|
1420
|
+
"X-Frame-Options": "SAMEORIGIN",
|
|
1421
|
+
"X-Robots-Tag": "noindex, follow"
|
|
1422
|
+
}
|
|
1423
|
+
});
|
|
1424
|
+
} catch (error) {
|
|
1425
|
+
const response = getHandlerErrorResponse(error);
|
|
1426
|
+
console.error("storeHandler.sitemapIndex.error", response.status, error);
|
|
1427
|
+
return response;
|
|
1428
|
+
}
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
function getSiteMapXML(config) {
|
|
1432
|
+
return async function(request, { params: getParams }) {
|
|
1433
|
+
try {
|
|
1434
|
+
const params = await getParams;
|
|
1435
|
+
const market = params?.market || defaultMarket;
|
|
1436
|
+
const locale = params?.locale || defaultLocale;
|
|
1437
|
+
const { getContext } = await getPayload({ config });
|
|
1438
|
+
const { markets, locales, routes } = await getContext(market, locale);
|
|
1439
|
+
const selectedMarket = markets.find((x) => market ? x.id === market : x.isDefault) || markets[0];
|
|
1440
|
+
const selectedLocale = locales.find((x) => locale ? x.id === locale : x.isDefault) || locales[0];
|
|
1441
|
+
const selectedMarketLocale = selectedMarket && selectedMarket.defaultLanguage ? selectedMarket.defaultLanguage : selectedLocale.id;
|
|
1442
|
+
const isCanonicalRoute = market && locale ? (route) => {
|
|
1443
|
+
return route.noindex !== true && route.market === selectedMarket.id && route.locale === selectedLocale.id;
|
|
1444
|
+
} : (route) => {
|
|
1445
|
+
return route.noindex !== true && route.market === selectedMarket.id && route.locale === selectedMarketLocale;
|
|
1446
|
+
};
|
|
1447
|
+
const getCanonicalAlternates = market && locale ? (canonical) => {
|
|
1448
|
+
return [];
|
|
1449
|
+
} : (canonical) => {
|
|
1450
|
+
return routes.filter((alternate) => {
|
|
1451
|
+
return alternate !== canonical && alternate.schema === canonical.schema && alternate.page === canonical.page;
|
|
1452
|
+
});
|
|
1453
|
+
};
|
|
1454
|
+
const routeToRouteCanonical = (canonical) => {
|
|
1455
|
+
const alternates = getCanonicalAlternates(canonical);
|
|
1456
|
+
return {
|
|
1457
|
+
...canonical,
|
|
1458
|
+
alternates
|
|
1459
|
+
};
|
|
1460
|
+
};
|
|
1461
|
+
const canonicalRoutes = routes.filter(isCanonicalRoute).map(routeToRouteCanonical);
|
|
1462
|
+
const body = `<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type="text/xsl" href="${origin}/sitemap.xsl"?>
|
|
1463
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
|
1464
|
+
xmlns:xhtml="http://www.w3.org/1999/xhtml"
|
|
1465
|
+
xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">
|
|
1466
|
+
${getSiteMapXMLUrls(origin, canonicalRoutes)}
|
|
1467
|
+
</urlset>
|
|
1468
|
+
`;
|
|
1469
|
+
return new Response(body, {
|
|
1470
|
+
status: 200,
|
|
1471
|
+
headers: {
|
|
1472
|
+
"Content-Type": "application/xml; charset=UTF-8",
|
|
1473
|
+
"X-Frame-Options": "SAMEORIGIN",
|
|
1474
|
+
"X-Robots-Tag": "noindex, follow"
|
|
1475
|
+
}
|
|
1476
|
+
});
|
|
1477
|
+
} catch (error) {
|
|
1478
|
+
const response = getHandlerErrorResponse(error);
|
|
1479
|
+
console.error("storeHandler.sitemapXml.error", response.status, error);
|
|
1480
|
+
return response;
|
|
1481
|
+
}
|
|
1482
|
+
};
|
|
1483
|
+
}
|
|
1484
|
+
function getSiteMapXSL(config) {
|
|
1485
|
+
return async function(request, { params: getParams }) {
|
|
1486
|
+
try {
|
|
1487
|
+
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1488
|
+
<xsl:stylesheet version="2.0"
|
|
1489
|
+
xmlns:html="http://www.w3.org/TR/REC-html40"
|
|
1490
|
+
xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
|
|
1491
|
+
xmlns:sitemap="http://www.sitemaps.org/schemas/sitemap/0.9"
|
|
1492
|
+
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
|
1493
|
+
<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>
|
|
1494
|
+
<xsl:template match="/">
|
|
1495
|
+
<html lang="${(await getParams)?.locale || defaultLocale}" xmlns="http://www.w3.org/1999/xhtml">
|
|
1496
|
+
<head>
|
|
1497
|
+
<title>XML Sitemap</title>
|
|
1498
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
|
1499
|
+
<style type="text/css">
|
|
1500
|
+
html {
|
|
1501
|
+
padding: 0;
|
|
1502
|
+
background: #09090A;
|
|
1503
|
+
color: #fefefe;
|
|
1504
|
+
}
|
|
1505
|
+
body {
|
|
1506
|
+
padding: 15px;
|
|
1507
|
+
font-family: Inter,-apple-system,'Helvetica Neue','Segoe UI',Roboto,Arial,'Noto Sans',sans-serif;
|
|
1508
|
+
font-size: 13px;
|
|
1509
|
+
background: #09090A;
|
|
1510
|
+
color: #fefefe;
|
|
1511
|
+
}
|
|
1512
|
+
.container {
|
|
1513
|
+
margin: 0 auto;
|
|
1514
|
+
max-width: 1280px;
|
|
1515
|
+
}
|
|
1516
|
+
a {
|
|
1517
|
+
color: #fefefe;
|
|
1518
|
+
text-decoration: none;
|
|
1519
|
+
}
|
|
1520
|
+
a:visited {
|
|
1521
|
+
color: #9e9e9e;
|
|
1522
|
+
}
|
|
1523
|
+
a:hover {
|
|
1524
|
+
text-decoration: underline;
|
|
1525
|
+
}
|
|
1526
|
+
table {
|
|
1527
|
+
width: 100%;
|
|
1528
|
+
margin: 10px 0;
|
|
1529
|
+
border: none;
|
|
1530
|
+
border-collapse: collapse;
|
|
1531
|
+
}
|
|
1532
|
+
td,
|
|
1533
|
+
th {
|
|
1534
|
+
text-align: left;
|
|
1535
|
+
font-size: 12px;
|
|
1536
|
+
padding: 3px;
|
|
1537
|
+
}
|
|
1538
|
+
th:not(:last-child),
|
|
1539
|
+
td:not(:last-child) {
|
|
1540
|
+
padding-right: 30px;
|
|
1541
|
+
}
|
|
1542
|
+
thead th {
|
|
1543
|
+
border-bottom: 1px solid #0e0e0f;
|
|
1544
|
+
}
|
|
1545
|
+
#sitemap tr:nth-child(odd) td {
|
|
1546
|
+
background-color: #0e0e0f;
|
|
1547
|
+
}
|
|
1548
|
+
#sitemap tbody tr:hover td {
|
|
1549
|
+
background-color: #1A1A1B;
|
|
1550
|
+
}
|
|
1551
|
+
#sitemap tbody tr:hover td,
|
|
1552
|
+
#sitemap tbody tr:hover td a {
|
|
1553
|
+
color: #fefefe;
|
|
1554
|
+
}
|
|
1555
|
+
.wysiwyg {
|
|
1556
|
+
margin: 0;
|
|
1557
|
+
padding: 0 0 10px 0;
|
|
1558
|
+
line-height: 1.2em;
|
|
1559
|
+
}
|
|
1560
|
+
.wysiwyg a {
|
|
1561
|
+
color: #00ea4e;
|
|
1562
|
+
font-weight: 600;
|
|
1563
|
+
}
|
|
1564
|
+
.wysiwyg a:visited {
|
|
1565
|
+
color: #00ea4e;
|
|
1566
|
+
}
|
|
1567
|
+
</style>
|
|
1568
|
+
</head>
|
|
1569
|
+
<body>
|
|
1570
|
+
<div class="container">
|
|
1571
|
+
<h1>XML Sitemap</h1>
|
|
1572
|
+
<p class="wysiwyg">
|
|
1573
|
+
This is an XML Sitemap meant for consumption by search engines, generated by <a href="https://github.com/websolutespa/bom" target="_blank" rel="noopener">Mixer</a>.
|
|
1574
|
+
</p>
|
|
1575
|
+
<p class="wysiwyg">
|
|
1576
|
+
You can find more information about XML sitemaps on <a href="https://sitemaps.org" target="_blank" rel="noopener">sitemaps.org</a>.
|
|
1577
|
+
</p>
|
|
1578
|
+
<xsl:if test="count(sitemap:sitemapindex/sitemap:sitemap) > 0">
|
|
1579
|
+
<p class="wysiwyg">
|
|
1580
|
+
This sitemap index file contains <xsl:value-of select="count(sitemap:sitemapindex/sitemap:sitemap)"/> sitemaps.
|
|
1581
|
+
</p>
|
|
1582
|
+
<table id="sitemap" cellpadding="3">
|
|
1583
|
+
<thead>
|
|
1584
|
+
<tr>
|
|
1585
|
+
<th width="75%">Sitemap</th>
|
|
1586
|
+
<th width="25%" title="Last Modification Time">Updated At</th>
|
|
1587
|
+
</tr>
|
|
1588
|
+
</thead>
|
|
1589
|
+
<tbody>
|
|
1590
|
+
<xsl:for-each select="sitemap:sitemapindex/sitemap:sitemap">
|
|
1591
|
+
<xsl:variable name="sitemapURL">
|
|
1592
|
+
<xsl:value-of select="sitemap:loc"/>
|
|
1593
|
+
</xsl:variable>
|
|
1594
|
+
<tr>
|
|
1595
|
+
<td>
|
|
1596
|
+
<a href="{$sitemapURL}"><xsl:value-of select="sitemap:loc"/></a>
|
|
1597
|
+
</td>
|
|
1598
|
+
<td>
|
|
1599
|
+
<xsl:value-of select="concat(substring(sitemap:lastmod,0,11),concat(' ', substring(sitemap:lastmod,12,5)),concat(' ', substring(sitemap:lastmod,20,6)))"/>
|
|
1600
|
+
</td>
|
|
1601
|
+
</tr>
|
|
1602
|
+
</xsl:for-each>
|
|
1603
|
+
</tbody>
|
|
1604
|
+
</table>
|
|
1605
|
+
</xsl:if>
|
|
1606
|
+
<xsl:if test="count(sitemap:sitemapindex/sitemap:sitemap) < 1">
|
|
1607
|
+
<p class="wysiwyg">
|
|
1608
|
+
This sitemap contains <xsl:value-of select="count(sitemap:urlset/sitemap:url)"/> URLs.
|
|
1609
|
+
</p>
|
|
1610
|
+
<table id="sitemap" cellpadding="3">
|
|
1611
|
+
<thead>
|
|
1612
|
+
<tr>
|
|
1613
|
+
<th width="75%">URL</th>
|
|
1614
|
+
<th width="15%" title="Last Modification Time">Updated At</th>
|
|
1615
|
+
<th width="10%">Images</th>
|
|
1616
|
+
</tr>
|
|
1617
|
+
</thead>
|
|
1618
|
+
<tbody>
|
|
1619
|
+
<xsl:variable name="lower" select="'abcdefghijklmnopqrstuvwxyz'"/>
|
|
1620
|
+
<xsl:variable name="upper" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>
|
|
1621
|
+
<xsl:for-each select="sitemap:urlset/sitemap:url">
|
|
1622
|
+
<tr>
|
|
1623
|
+
<td>
|
|
1624
|
+
<xsl:variable name="itemURL">
|
|
1625
|
+
<xsl:value-of select="sitemap:loc"/>
|
|
1626
|
+
</xsl:variable>
|
|
1627
|
+
<a href="{$itemURL}">
|
|
1628
|
+
<xsl:value-of select="sitemap:loc"/>
|
|
1629
|
+
</a>
|
|
1630
|
+
</td>
|
|
1631
|
+
<td>
|
|
1632
|
+
<xsl:value-of select="concat(substring(sitemap:lastmod,0,11),concat(' ', substring(sitemap:lastmod,12,5)),concat(' ', substring(sitemap:lastmod,20,6)))"/>
|
|
1633
|
+
</td>
|
|
1634
|
+
<td>
|
|
1635
|
+
<xsl:value-of select="count(image:image)"/>
|
|
1636
|
+
</td>
|
|
1637
|
+
</tr>
|
|
1638
|
+
</xsl:for-each>
|
|
1639
|
+
</tbody>
|
|
1640
|
+
</table>
|
|
1641
|
+
</xsl:if>
|
|
1642
|
+
</div>
|
|
1643
|
+
</body>
|
|
1644
|
+
</html>
|
|
1645
|
+
</xsl:template>
|
|
1646
|
+
</xsl:stylesheet>`;
|
|
1647
|
+
return new Response(body, {
|
|
1648
|
+
status: 200,
|
|
1649
|
+
headers: {
|
|
1650
|
+
"Content-Type": "application/xml; charset=UTF-8",
|
|
1651
|
+
"X-Frame-Options": "SAMEORIGIN",
|
|
1652
|
+
"X-Robots-Tag": "noindex, follow"
|
|
1653
|
+
}
|
|
1654
|
+
});
|
|
1655
|
+
} catch (error) {
|
|
1656
|
+
const response = getHandlerErrorResponse(error);
|
|
1657
|
+
console.error("storeHandler.sitemapXsl.error", response.status, error);
|
|
1658
|
+
return response;
|
|
1659
|
+
}
|
|
1660
|
+
};
|
|
1661
|
+
}
|
|
1662
|
+
function getSiteMapXMLLastMod(canonical) {
|
|
1663
|
+
return canonical.updatedAt ? `
|
|
1664
|
+
<lastmod>${escapeHtml(String(canonical.updatedAt))}</lastmod>` : "";
|
|
1665
|
+
}
|
|
1666
|
+
function getSiteMapXMLImage(canonical) {
|
|
1667
|
+
return canonical.media ? `
|
|
1668
|
+
<image:image>
|
|
1669
|
+
<image:loc>${escapeHtml(encodeURI(`${canonical.media.src || canonical.media.url}`))}</image:loc>
|
|
1670
|
+
</image:image>` : "";
|
|
1671
|
+
}
|
|
1672
|
+
function getSiteMapXMLAlternates(origin, canonical) {
|
|
1673
|
+
return canonical.alternates.map((alternate) => `
|
|
1674
|
+
<xhtml:link href="${escapeHtml(encodeURI(`${origin}${alternate.id}`))}" hreflang="${alternate.locale}-${alternate.market}" rel="alternate" />`).join("");
|
|
1675
|
+
}
|
|
1676
|
+
function getSiteMapXMLUrl(origin, canonical) {
|
|
1677
|
+
return `
|
|
1678
|
+
<url>
|
|
1679
|
+
<loc>${escapeHtml(encodeURI(`${origin}${canonical.id}`))}</loc>${getSiteMapXMLLastMod(canonical)}${getSiteMapXMLAlternates(origin, canonical)}${getSiteMapXMLImage(canonical)}
|
|
1680
|
+
</url>`;
|
|
1681
|
+
}
|
|
1682
|
+
function escapeHtml(string) {
|
|
1683
|
+
const matchHtmlRegExp = /["'&<>]/;
|
|
1684
|
+
const str = "" + string;
|
|
1685
|
+
const match = matchHtmlRegExp.exec(str);
|
|
1686
|
+
if (!match) return str;
|
|
1687
|
+
let escape;
|
|
1688
|
+
let html = "";
|
|
1689
|
+
let index = 0;
|
|
1690
|
+
let lastIndex = 0;
|
|
1691
|
+
for (index = match.index; index < str.length; index++) {
|
|
1692
|
+
switch (str.charCodeAt(index)) {
|
|
1693
|
+
case 34:
|
|
1694
|
+
escape = """;
|
|
1695
|
+
break;
|
|
1696
|
+
case 38:
|
|
1697
|
+
escape = "&";
|
|
1698
|
+
break;
|
|
1699
|
+
case 39:
|
|
1700
|
+
escape = "'";
|
|
1701
|
+
break;
|
|
1702
|
+
case 60:
|
|
1703
|
+
escape = "<";
|
|
1704
|
+
break;
|
|
1705
|
+
case 62:
|
|
1706
|
+
escape = ">";
|
|
1707
|
+
break;
|
|
1708
|
+
default: continue;
|
|
1709
|
+
}
|
|
1710
|
+
if (lastIndex !== index) html += str.substring(lastIndex, index);
|
|
1711
|
+
lastIndex = index + 1;
|
|
1712
|
+
html += escape;
|
|
1713
|
+
}
|
|
1714
|
+
return lastIndex !== index ? html + str.substring(lastIndex, index) : html;
|
|
1715
|
+
}
|
|
1716
|
+
function getSiteMapXMLUrls(origin, canonicalRoutes) {
|
|
1717
|
+
return canonicalRoutes.map((canonicalRoute) => getSiteMapXMLUrl(origin, canonicalRoute)).join("");
|
|
1718
|
+
}
|
|
1719
|
+
//#endregion
|
|
1720
|
+
//#region src/server/handlers/static-params.handler.ts
|
|
1721
|
+
/**
|
|
1722
|
+
* Returns a generateStaticParams function for a given page collection slug.
|
|
1723
|
+
*/
|
|
1724
|
+
function getStaticParams(slug, config) {
|
|
1725
|
+
return async function generateStaticParams() {
|
|
1726
|
+
if (isDevelopment || buildStrategy === "runtime") return [];
|
|
1727
|
+
const { getContext } = await getPayload({ config });
|
|
1728
|
+
const { routes } = await getContext("all", "all");
|
|
1729
|
+
return routes.filter((r) => r.schema === slug).map((route) => ({
|
|
1730
|
+
market: route.market,
|
|
1731
|
+
locale: route.locale,
|
|
1732
|
+
id: String(route.page)
|
|
1733
|
+
}));
|
|
1734
|
+
};
|
|
1735
|
+
}
|
|
1736
|
+
function getStaticParams_(schema) {
|
|
1737
|
+
return async function generateStaticParams() {
|
|
1738
|
+
if (isDevelopment || buildStrategy === "runtime") return [];
|
|
1739
|
+
const query = qsSerialize({ where: { or: [{
|
|
1740
|
+
schema: { equals: schema },
|
|
1741
|
+
template: { equals: schema }
|
|
1742
|
+
}] } });
|
|
1743
|
+
return (await localApiGet(`/-route?${query}`)).map((route) => ({
|
|
1744
|
+
market: route.market,
|
|
1745
|
+
locale: route.locale,
|
|
1746
|
+
id: String(route.page)
|
|
1747
|
+
}));
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
function getStaticParamsFromPayload(schema, config) {
|
|
1751
|
+
return async function generateStaticParams() {
|
|
1752
|
+
if (isDevelopment || buildStrategy === "runtime") return [];
|
|
1753
|
+
return (await getRoutes({
|
|
1754
|
+
payload: await getPayloadInstance({ config }),
|
|
1755
|
+
market: "all",
|
|
1756
|
+
locale: "all",
|
|
1757
|
+
user: guest,
|
|
1758
|
+
draft: false
|
|
1759
|
+
})).filter((route) => route.schema === schema || route.template === schema).map((route) => ({
|
|
1760
|
+
market: route.market,
|
|
1761
|
+
locale: route.locale,
|
|
1762
|
+
id: String(route.page)
|
|
1763
|
+
}));
|
|
1764
|
+
};
|
|
1765
|
+
}
|
|
1766
|
+
//#endregion
|
|
1767
|
+
//#region src/server/handlers/vars.handler.ts
|
|
1768
|
+
const DISALLOWED_KEYS = ["__NEXT_PRIVATE_STANDALONE_CONFIG"];
|
|
1769
|
+
const getFilteredEnv = (env) => {
|
|
1770
|
+
const entries = Object.entries(env).filter(([k, v]) => !DISALLOWED_KEYS.includes(k) && typeof v === "string");
|
|
1771
|
+
entries.sort();
|
|
1772
|
+
return Object.fromEntries(entries);
|
|
1773
|
+
};
|
|
1774
|
+
const sortKeys = (object) => {
|
|
1775
|
+
const entries = Object.entries(object || {});
|
|
1776
|
+
entries.sort();
|
|
1777
|
+
return Object.fromEntries(entries);
|
|
1778
|
+
};
|
|
1779
|
+
function getVarsHandler(config) {
|
|
1780
|
+
return async function(request) {
|
|
1781
|
+
try {
|
|
1782
|
+
const searchParams = request.nextUrl.searchParams;
|
|
1783
|
+
const { market, locale, defaultMarket, defaultLocale, params } = getSearchParams(request);
|
|
1784
|
+
const headers$1 = await headers();
|
|
1785
|
+
const { payload, user, getContext } = await getPayload({
|
|
1786
|
+
config,
|
|
1787
|
+
headers: headers$1
|
|
1788
|
+
});
|
|
1789
|
+
if (!(user?.roles || []).includes("admin")) throw {
|
|
1790
|
+
status: 401,
|
|
1791
|
+
message: "Unauthorized"
|
|
1792
|
+
};
|
|
1793
|
+
const context = await getContext(market || defaultMarket, locale || defaultLocale);
|
|
1794
|
+
const layout = await getLayout(market || defaultMarket, locale || defaultLocale, payload, user, context);
|
|
1795
|
+
const info = {
|
|
1796
|
+
production: isProduction,
|
|
1797
|
+
development: isDevelopment,
|
|
1798
|
+
origin: getOrigin(headers$1),
|
|
1799
|
+
host: getHost(headers$1),
|
|
1800
|
+
params: {},
|
|
1801
|
+
searchParams: { ...sortKeys(Object.fromEntries(searchParams)) },
|
|
1802
|
+
headers: { ...sortKeys(Object.fromEntries(headers$1)) },
|
|
1803
|
+
env: { ...getFilteredEnv(process.env) },
|
|
1804
|
+
user: { ...sortKeys(user) },
|
|
1805
|
+
context: { ...sortKeys(context) },
|
|
1806
|
+
layout: { ...sortKeys(layout) }
|
|
1807
|
+
};
|
|
1808
|
+
const accept = headers$1.get("accept");
|
|
1809
|
+
if (!accept || !accept.includes("text/html")) return Response.json(info, { status: 200 });
|
|
1810
|
+
return new Response(`<!DOCTYPE html>
|
|
1811
|
+
<html>
|
|
1812
|
+
<head>
|
|
1813
|
+
<meta charSet="utf-8"/>
|
|
1814
|
+
<style>
|
|
1815
|
+
html, body {
|
|
1816
|
+
background: #282828;
|
|
1817
|
+
color: #7cacf8;
|
|
1818
|
+
}
|
|
1819
|
+
body {
|
|
1820
|
+
padding: 0;
|
|
1821
|
+
margin: 0;
|
|
1822
|
+
font-family: monospace;
|
|
1823
|
+
font-size: 14px;
|
|
1824
|
+
line-height: 1.16;
|
|
1825
|
+
font-weight: 100;
|
|
1826
|
+
}
|
|
1827
|
+
json-viewer {
|
|
1828
|
+
padding: 20px;
|
|
1829
|
+
min-height: 100vh;
|
|
1830
|
+
box-sizing: border-box;
|
|
1831
|
+
line-height: 1.2;
|
|
1832
|
+
--background-color: #282828;
|
|
1833
|
+
--color: #e3e3e3;
|
|
1834
|
+
--property-color: #7cacf8;
|
|
1835
|
+
--string-color: #53d5fb;
|
|
1836
|
+
--number-color: #7b80ff;
|
|
1837
|
+
--boolean-color: #9980ff;
|
|
1838
|
+
--null-color: #6f6f6f;
|
|
1839
|
+
--preview-color: #8f8f8f;
|
|
1840
|
+
--highlight-color: #7b0000;
|
|
1841
|
+
--font-family: monospace;
|
|
1842
|
+
--font-size: 14px;
|
|
1843
|
+
--indent-size: 1em;
|
|
1844
|
+
--indentguide-size: 1px;
|
|
1845
|
+
--indentguide-style: solid;
|
|
1846
|
+
--indentguide-color: #333;
|
|
1847
|
+
--indentguide-color-active: #666;
|
|
1848
|
+
--indentguide: var(--indentguide-size) var(--indentguide-style) var(--indentguide-color);
|
|
1849
|
+
--indentguide-active: var(--indentguide-size) var(--indentguide-style) var(--indentguide-color-active);
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
:root {
|
|
1853
|
+
--scrollbar-size: 8px;
|
|
1854
|
+
--scrollbar-border-radius: 2px;
|
|
1855
|
+
--scrollbar-track-color: transparent;
|
|
1856
|
+
--scrollbar-track-hover-color: transparent;
|
|
1857
|
+
--scrollbar-thumb-color: #888888;
|
|
1858
|
+
--scrollbar-thumb-hover-color: #aaaaaa;
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
::-webkit-scrollbar {
|
|
1862
|
+
width: var(--scrollbar-size);
|
|
1863
|
+
height: var(--scrollbar-size);
|
|
1864
|
+
border-radius: 0;
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
::-webkit-scrollbar-button {
|
|
1868
|
+
display: none;
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
::-webkit-scrollbar-button:hover {
|
|
1872
|
+
display: none;
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
::-webkit-scrollbar-thumb {
|
|
1876
|
+
background-color: var(--scrollbar-thumb-color);
|
|
1877
|
+
box-shadow: none;
|
|
1878
|
+
border-radius: calc(var(--scrollbar-size) * 0.5);
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
::-webkit-scrollbar-thumb:hover {
|
|
1882
|
+
background-color: var(--scrollbar-thumb-hover-color);
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
::-webkit-scrollbar-track {
|
|
1886
|
+
background-color: var(--scrollbar-track-color);
|
|
1887
|
+
border-radius: 0;
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
::-webkit-scrollbar-track:hover {
|
|
1891
|
+
background-color: var(--scrollbar-track-hover-color);
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
</style>
|
|
1895
|
+
<script src="https://unpkg.com/@alenaksu/json-viewer@2.0.0/dist/json-viewer.bundle.js"><\/script>
|
|
1896
|
+
<script>
|
|
1897
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
1898
|
+
const viewer = document.querySelector('#json-viewer');
|
|
1899
|
+
viewer.data = ${JSON.stringify(info)};
|
|
1900
|
+
viewer.expand(/headers|env/);
|
|
1901
|
+
});
|
|
1902
|
+
<\/script>
|
|
1903
|
+
</head>
|
|
1904
|
+
<body>
|
|
1905
|
+
<json-viewer id="json-viewer"></json-viewer>
|
|
1906
|
+
</body>
|
|
1907
|
+
</html>`, {
|
|
1908
|
+
status: 200,
|
|
1909
|
+
headers: { "Content-Type": "text/html" }
|
|
1910
|
+
});
|
|
1911
|
+
} catch (error) {
|
|
1912
|
+
console.log("getVarsHandler.error", error);
|
|
1913
|
+
return getHandlerErrorResponse(error);
|
|
1914
|
+
}
|
|
1915
|
+
};
|
|
1916
|
+
}
|
|
1917
|
+
//#endregion
|
|
1918
|
+
//#region src/server/lazy.ts
|
|
1919
|
+
const LAZY_PROPS = {};
|
|
1920
|
+
async function withLazyProps(key, getComponentProps) {
|
|
1921
|
+
LAZY_PROPS[key] = getComponentProps;
|
|
1922
|
+
}
|
|
1923
|
+
async function decorateLazyComponents(extraComponents) {
|
|
1924
|
+
const context = await getContext();
|
|
1925
|
+
const pageContext = await getPageContext();
|
|
1926
|
+
const page = pageContext.page;
|
|
1927
|
+
if (!page) return;
|
|
1928
|
+
const components = [...page.components || [], ...extraComponents || []];
|
|
1929
|
+
const decoratedComponents = [];
|
|
1930
|
+
for (const component of components) {
|
|
1931
|
+
const key = component.blockType || component.schema || "unkown_type";
|
|
1932
|
+
const getComponentProps = LAZY_PROPS[key];
|
|
1933
|
+
if (typeof getComponentProps === "function") {
|
|
1934
|
+
const decoratedComponent = await getComponentProps({
|
|
1935
|
+
...context,
|
|
1936
|
+
...pageContext,
|
|
1937
|
+
component
|
|
1938
|
+
});
|
|
1939
|
+
decoratedComponents.push(decoratedComponent);
|
|
1940
|
+
} else decoratedComponents.push(component);
|
|
1941
|
+
}
|
|
1942
|
+
page.components = decoratedComponents;
|
|
1943
|
+
}
|
|
1944
|
+
//#endregion
|
|
1945
|
+
//#region src/server/metadata.ts
|
|
1946
|
+
/**
|
|
1947
|
+
* Returns a generateMetadata function for a given page collection slug.
|
|
1948
|
+
* Uses bowl's createPageContext (Payload local API) instead of the store abstraction.
|
|
1949
|
+
*/
|
|
1950
|
+
function getMetadata(slug, config) {
|
|
1951
|
+
return async function generateMetadata(props, parent) {
|
|
1952
|
+
const { market, locale, id } = await props.params;
|
|
1953
|
+
const { page } = await createPageContext(market, locale, slug, id, config);
|
|
1954
|
+
return {
|
|
1955
|
+
title: page.meta?.title || page.title,
|
|
1956
|
+
description: page.meta?.description || page.abstract
|
|
1957
|
+
};
|
|
1958
|
+
};
|
|
1959
|
+
}
|
|
1960
|
+
//#endregion
|
|
1961
|
+
//#region src/server/utmz.ts
|
|
1962
|
+
function getUtmz(request) {
|
|
1963
|
+
let utmz = "N/A";
|
|
1964
|
+
if (typeof request.cookies.__utmz === "string") {
|
|
1965
|
+
utmz = request.cookies.__utmz;
|
|
1966
|
+
const i = utmz.indexOf("utmcsr");
|
|
1967
|
+
utmz = utmz.substring(i === -1 ? 0 : i);
|
|
1968
|
+
}
|
|
1969
|
+
return utmz;
|
|
1970
|
+
}
|
|
1971
|
+
//#endregion
|
|
1972
|
+
export { buildBaseOptions, collectionDeleteHandler, collectionGetHandler, collectionPatchHandler, collectionPostHandler, createLayoutContext, createNotFoundContext, createPageContext, decorateLazyComponents, filterRoutes, getAuthHeaders, getAuthMeHandler, getCacheKey, getCaptionsVTT, getContext, getHrefBeforeRedirect, getLocalApi, getMetadata, getMixerContext, getNextUrl, getOptions, getPage, getPageContext, getPayload, getPayloadInstance, getRegistry, getRegistryItem, getRouteHandler, getRoutes, getSearchParams, getSiteMapIndex, getSiteMapXML, getSiteMapXSL, getStaticParams, getStaticParamsFromPayload, getStaticParams_, getUtmz, getVarsHandler, indexGetHandler, previewHandler, setContext, setPageContext, withLazyProps };
|
|
1973
|
+
|
|
1974
|
+
//# sourceMappingURL=server.js.map
|