@growth-labs/cms 0.8.28 → 0.8.32
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/dist/engine/content-insights.d.ts +27 -3
- package/dist/engine/content-insights.d.ts.map +1 -1
- package/dist/engine/content-insights.js +83 -4
- package/dist/engine/content-insights.js.map +1 -1
- package/dist/engine/content-metadata.d.ts +2 -0
- package/dist/engine/content-metadata.d.ts.map +1 -1
- package/dist/engine/content-metadata.js +15 -0
- package/dist/engine/content-metadata.js.map +1 -1
- package/dist/engine/index.d.ts +1 -0
- package/dist/engine/index.d.ts.map +1 -1
- package/dist/engine/index.js +1 -0
- package/dist/engine/index.js.map +1 -1
- package/dist/engine/publication-receipt/receipt.d.ts +4 -4
- package/dist/engine/publication.d.ts.map +1 -1
- package/dist/engine/publication.js +23 -3
- package/dist/engine/publication.js.map +1 -1
- package/dist/engine/publisher.d.ts +8 -8
- package/dist/engine/publisher.d.ts.map +1 -1
- package/dist/engine/publisher.js +27 -1
- package/dist/engine/publisher.js.map +1 -1
- package/dist/foundry-emit.d.ts +5 -0
- package/dist/foundry-emit.d.ts.map +1 -1
- package/dist/foundry-emit.js +3 -0
- package/dist/foundry-emit.js.map +1 -1
- package/dist/integration/options.d.ts +14 -14
- package/dist/routes/content-insights.d.ts.map +1 -1
- package/dist/routes/content-insights.js +33 -3
- package/dist/routes/content-insights.js.map +1 -1
- package/dist/routes/content.d.ts.map +1 -1
- package/dist/routes/content.js +1 -15
- package/dist/routes/content.js.map +1 -1
- package/dist/routes/foundry-publish.d.ts.map +1 -1
- package/dist/routes/foundry-publish.js +1 -0
- package/dist/routes/foundry-publish.js.map +1 -1
- package/dist/routes/fronts-publish.d.ts +1 -1
- package/dist/routes/fronts-publish.d.ts.map +1 -1
- package/dist/routes/fronts-publish.js +2 -2
- package/dist/routes/fronts-publish.js.map +1 -1
- package/dist/schema/insights-ingest.d.ts +897 -33
- package/dist/schema/insights-ingest.d.ts.map +1 -1
- package/dist/schema/insights-ingest.js +38 -0
- package/dist/schema/insights-ingest.js.map +1 -1
- package/dist/schema/types.d.ts +20 -0
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/types.js.map +1 -1
- package/dist/ui/api/content/insights-ingest.d.ts.map +1 -1
- package/dist/ui/api/content/insights-ingest.js +12 -3
- package/dist/ui/api/content/insights-ingest.js.map +1 -1
- package/dist/ui/api/content/insights-inputs.d.ts.map +1 -1
- package/dist/ui/api/content/insights-inputs.js +3 -2
- package/dist/ui/api/content/insights-inputs.js.map +1 -1
- package/docs/foundry-publish.md +6 -0
- package/package.json +1 -1
- package/src/engine/content-insights.ts +99 -6
- package/src/engine/content-metadata.ts +17 -0
- package/src/engine/index.ts +1 -0
- package/src/engine/publication.ts +26 -2
- package/src/engine/publisher.ts +46 -1
- package/src/foundry-emit.ts +3 -0
- package/src/routes/content-insights.ts +40 -2
- package/src/routes/content.ts +1 -20
- package/src/routes/foundry-publish.ts +1 -0
- package/src/routes/fronts-publish.ts +2 -1
- package/src/schema/insights-ingest.ts +45 -0
- package/src/schema/types.ts +10 -0
- package/src/ui/api/content/insights-ingest.ts +10 -3
- package/src/ui/api/content/insights-inputs.ts +3 -2
package/src/engine/publisher.ts
CHANGED
|
@@ -1665,13 +1665,58 @@ END`
|
|
|
1665
1665
|
* the second disjunct keeps unpublish→republish correct, since `unpublishContent`
|
|
1666
1666
|
* clears `published_at` but the article did go live once.
|
|
1667
1667
|
*/
|
|
1668
|
-
|
|
1668
|
+
const PUBLISH_METADATA_STAMP_EXPRESSION = `CASE
|
|
1669
1669
|
WHEN published_at IS NOT NULL
|
|
1670
1670
|
OR json_extract(${METADATA_OBJECT_SQL}, '$.adaptedAt') IS NOT NULL
|
|
1671
1671
|
THEN json_set(${METADATA_WITH_ADAPTED_AT_SQL}, '$.updatedAt', date(?, 'unixepoch'))
|
|
1672
1672
|
ELSE ${METADATA_WITH_ADAPTED_AT_SQL}
|
|
1673
1673
|
END`
|
|
1674
1674
|
|
|
1675
|
+
export const PUBLISH_METADATA_STAMP_SQL = `metadata_json = ${PUBLISH_METADATA_STAMP_EXPRESSION}`
|
|
1676
|
+
|
|
1677
|
+
/** Project the same publish stamp onto the immutable snapshot's captured row. */
|
|
1678
|
+
export async function publicationMetadataForSnapshot(
|
|
1679
|
+
db: D1Database,
|
|
1680
|
+
item: {
|
|
1681
|
+
id?: unknown
|
|
1682
|
+
published_revision_id?: unknown
|
|
1683
|
+
metadata_json?: unknown
|
|
1684
|
+
published_at?: unknown
|
|
1685
|
+
created_at?: unknown
|
|
1686
|
+
},
|
|
1687
|
+
now: number,
|
|
1688
|
+
): Promise<string> {
|
|
1689
|
+
// A draft metadata edit can remove adaptedAt. The previous immutable native
|
|
1690
|
+
// revision retains the actual first publication day; creation time may be
|
|
1691
|
+
// earlier and published_at may be the source video's historical date.
|
|
1692
|
+
let priorAdaptedAt: string | null = null
|
|
1693
|
+
if (typeof item.id === 'string' && typeof item.published_revision_id === 'string') {
|
|
1694
|
+
const prior = await db
|
|
1695
|
+
.prepare(`SELECT
|
|
1696
|
+
json_extract(json_extract(payload_json, '$.item.metadata_json'), '$.adaptedAt') AS adapted_at
|
|
1697
|
+
FROM content_revisions WHERE id = ? AND content_id = ?`)
|
|
1698
|
+
.bind(item.published_revision_id, item.id)
|
|
1699
|
+
.first<{ adapted_at: string | null }>()
|
|
1700
|
+
priorAdaptedAt = prior?.adapted_at ?? null
|
|
1701
|
+
}
|
|
1702
|
+
const projected = await db
|
|
1703
|
+
.prepare(`SELECT ${PUBLISH_METADATA_STAMP_EXPRESSION} AS metadata_json
|
|
1704
|
+
FROM (SELECT CASE WHEN ? IS NOT NULL THEN json_set(COALESCE(?, '{}'), '$.adaptedAt', ?)
|
|
1705
|
+
ELSE ? END AS metadata_json, ? AS published_at, ? AS created_at)`)
|
|
1706
|
+
.bind(
|
|
1707
|
+
...publishMetadataStampBinds(now),
|
|
1708
|
+
priorAdaptedAt,
|
|
1709
|
+
item.metadata_json ?? null,
|
|
1710
|
+
priorAdaptedAt,
|
|
1711
|
+
item.metadata_json ?? null,
|
|
1712
|
+
item.published_at ?? null,
|
|
1713
|
+
item.created_at ?? null,
|
|
1714
|
+
)
|
|
1715
|
+
.first<{ metadata_json: string }>()
|
|
1716
|
+
if (!projected) throw new Error('publication metadata projection failed')
|
|
1717
|
+
return projected.metadata_json
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1675
1720
|
/** Placeholder count in {@link PUBLISH_METADATA_STAMP_SQL}, derived so it cannot drift. */
|
|
1676
1721
|
const PUBLISH_METADATA_STAMP_PLACEHOLDERS = (PUBLISH_METADATA_STAMP_SQL.match(/\?/g) ?? []).length
|
|
1677
1722
|
|
package/src/foundry-emit.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { sha256Hex } from '@growth-labs/foundry-signing'
|
|
11
11
|
import { z } from 'zod'
|
|
12
|
+
import { ContentMetadataSchema } from './engine/content-metadata.js'
|
|
12
13
|
import type { ContentArchiveItem } from './engine/publication.js'
|
|
13
14
|
import { slugify } from './engine/slug.js'
|
|
14
15
|
|
|
@@ -58,6 +59,7 @@ export const FoundryArticleV2Schema = z
|
|
|
58
59
|
tags: z.array(z.string().trim().min(1).max(100)).max(50).optional(),
|
|
59
60
|
description: z.string().trim().min(1).optional(),
|
|
60
61
|
visibility: z.enum(['free', 'premium']).optional(),
|
|
62
|
+
metadata: ContentMetadataSchema.optional(),
|
|
61
63
|
adoptSourceId: z
|
|
62
64
|
.string()
|
|
63
65
|
.min(1)
|
|
@@ -147,6 +149,7 @@ export function toFoundryArticleV2(
|
|
|
147
149
|
...(item.tags && item.tags.length > 0 ? { tags: [...item.tags] } : {}),
|
|
148
150
|
...(description ? { description } : {}),
|
|
149
151
|
...(item.visibility ? { visibility: item.visibility } : {}),
|
|
152
|
+
...(item.metadata != null ? { metadata: item.metadata } : {}),
|
|
150
153
|
...(present(item.sourceId) ? { adoptSourceId: item.sourceId } : {}),
|
|
151
154
|
}
|
|
152
155
|
return FoundryArticleV2Schema.parse(body)
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
isMissingInsightPublicationSchema,
|
|
7
7
|
isMissingInsightsTable,
|
|
8
8
|
listInsightBoard,
|
|
9
|
+
readSelectedClusterMembership,
|
|
9
10
|
replaceInsightsBoard,
|
|
10
11
|
undismissInsight,
|
|
11
12
|
} from '../engine/content-insights.js'
|
|
@@ -312,7 +313,11 @@ export function createContentInsightsRoutes(
|
|
|
312
313
|
} catch (error) {
|
|
313
314
|
if (isMissingInsightPublicationSchema(error))
|
|
314
315
|
return json({ error: 'schema_not_ready' }, 409)
|
|
315
|
-
if (error instanceof InsightPublicationError)
|
|
316
|
+
if (error instanceof InsightPublicationError)
|
|
317
|
+
return json(
|
|
318
|
+
{ error: error.code },
|
|
319
|
+
error.code === 'selected_membership_replacement_refused' ? 422 : 409,
|
|
320
|
+
)
|
|
316
321
|
throw error
|
|
317
322
|
}
|
|
318
323
|
},
|
|
@@ -321,7 +326,13 @@ export function createContentInsightsRoutes(
|
|
|
321
326
|
// family as fronts/schedule (timestamp-bound signature over the query
|
|
322
327
|
// string; a GET has no body to bind freshness to instead).
|
|
323
328
|
async insightsInputs(ctx) {
|
|
324
|
-
|
|
329
|
+
// Channel publications use the existing shared CMS binding. An explicit
|
|
330
|
+
// legacy binding retains precedence, including loud failure if unreadable.
|
|
331
|
+
const binding =
|
|
332
|
+
ctx.env?.FOUNDRY_HMAC_SECRET === undefined
|
|
333
|
+
? 'CMS_FOUNDRY_HMAC_SECRET'
|
|
334
|
+
: 'FOUNDRY_HMAC_SECRET'
|
|
335
|
+
const hmacSecret = await resolveFoundryHmacSecretForInputs(ctx.env, binding)
|
|
325
336
|
if (!hmacSecret) return json({ error: 'FOUNDRY_HMAC_SECRET is not configured' }, 500)
|
|
326
337
|
|
|
327
338
|
const url = new URL(ctx.request.url)
|
|
@@ -344,11 +355,38 @@ export function createContentInsightsRoutes(
|
|
|
344
355
|
title: string
|
|
345
356
|
slug: string
|
|
346
357
|
}>()
|
|
358
|
+
let selected: Awaited<ReturnType<typeof readSelectedClusterMembership>> | null = null
|
|
359
|
+
// Membership cannot ride the historical requested-site echo: it is
|
|
360
|
+
// returned only for this configured publication's signed request.
|
|
361
|
+
if (siteId !== 'default' && requestedSite === siteId) {
|
|
362
|
+
try {
|
|
363
|
+
selected = await readSelectedClusterMembership(ctx.db, siteId)
|
|
364
|
+
} catch (error) {
|
|
365
|
+
if (
|
|
366
|
+
!(error instanceof Error && error.message.startsWith('cluster_membership_unknown:')) &&
|
|
367
|
+
!isMissingInsightsTable(error)
|
|
368
|
+
)
|
|
369
|
+
throw error
|
|
370
|
+
}
|
|
371
|
+
}
|
|
347
372
|
return json({
|
|
348
373
|
ok: true,
|
|
349
374
|
site: requestedSite ?? siteId,
|
|
350
375
|
authors: authorsResult.results ?? [],
|
|
351
376
|
existing: existingResult.results ?? [],
|
|
377
|
+
cluster_membership: selected ? 'selected' : 'unknown',
|
|
378
|
+
cluster_selections:
|
|
379
|
+
selected?.map((membership) => ({
|
|
380
|
+
site_id: membership.site_id,
|
|
381
|
+
intent_key: membership.intent_key,
|
|
382
|
+
desk: membership.desk,
|
|
383
|
+
decision_ref: membership.decision_ref,
|
|
384
|
+
taxonomy_ref: membership.taxonomy_ref,
|
|
385
|
+
hub_content_id: membership.hub.content_id,
|
|
386
|
+
hub_revision_id: membership.hub.revision_id,
|
|
387
|
+
child_content_ids: membership.children.map((page) => page.content_id),
|
|
388
|
+
child_revision_ids: membership.children.map((page) => page.revision_id),
|
|
389
|
+
})) ?? null,
|
|
352
390
|
})
|
|
353
391
|
},
|
|
354
392
|
}
|
package/src/routes/content.ts
CHANGED
|
@@ -18,11 +18,7 @@ import { z } from 'zod'
|
|
|
18
18
|
import { logActivity } from '../engine/activity-log.js'
|
|
19
19
|
import { generateContentDek } from '../engine/ai-dek.js'
|
|
20
20
|
import { createDekFailureNotifications } from '../engine/ai-dek-notifications.js'
|
|
21
|
-
import {
|
|
22
|
-
type ContentMetadata,
|
|
23
|
-
ContentMetadataError,
|
|
24
|
-
serializeContentMetadata,
|
|
25
|
-
} from '../engine/content-metadata.js'
|
|
21
|
+
import { ContentMetadataSchema } from '../engine/content-metadata.js'
|
|
26
22
|
import { listContributors, setContributors } from '../engine/contributors.js'
|
|
27
23
|
import type { D1Database } from '../engine/d1.js'
|
|
28
24
|
import {
|
|
@@ -410,21 +406,6 @@ const BodyContentCreateSchema = z.object({
|
|
|
410
406
|
editorTakeaways: z.array(z.string().min(1)).max(4).optional().nullable(),
|
|
411
407
|
})
|
|
412
408
|
|
|
413
|
-
const ContentMetadataSchema = z
|
|
414
|
-
.record(z.string(), z.unknown())
|
|
415
|
-
.superRefine((metadata, ctx) => {
|
|
416
|
-
try {
|
|
417
|
-
serializeContentMetadata(metadata as ContentMetadata)
|
|
418
|
-
} catch (error) {
|
|
419
|
-
ctx.addIssue({
|
|
420
|
-
code: 'custom',
|
|
421
|
-
message:
|
|
422
|
-
error instanceof ContentMetadataError ? error.message : 'content metadata is invalid',
|
|
423
|
-
})
|
|
424
|
-
}
|
|
425
|
-
})
|
|
426
|
-
.transform((metadata) => metadata as ContentMetadata)
|
|
427
|
-
|
|
428
409
|
// A Nextcloud share PAGE is HTML, never ingestable media, so the stored source
|
|
429
410
|
// must be the direct-download URL. Editors paste the share link -- that is what
|
|
430
411
|
// Nextcloud's "Copy link" button yields -- so normalise it here instead of
|
|
@@ -671,6 +671,7 @@ async function putV2(
|
|
|
671
671
|
...(body.desk !== undefined ? { primaryCategory: body.desk } : {}),
|
|
672
672
|
...(body.tags !== undefined ? { tags: body.tags } : {}),
|
|
673
673
|
...(body.visibility !== undefined ? { visibility: body.visibility } : {}),
|
|
674
|
+
...(body.metadata !== undefined ? { metadata: body.metadata } : {}),
|
|
674
675
|
content: { bodyMarkdown: body.body_html, bodyHtml: body.body_html },
|
|
675
676
|
}
|
|
676
677
|
|
|
@@ -60,8 +60,9 @@ import { json, type RouteContext } from './context.js'
|
|
|
60
60
|
// family rather than inventing a second one.
|
|
61
61
|
export async function resolveFoundryHmacSecret(
|
|
62
62
|
env: Record<string, unknown> | undefined,
|
|
63
|
+
binding: 'FOUNDRY_HMAC_SECRET' | 'CMS_FOUNDRY_HMAC_SECRET' = 'FOUNDRY_HMAC_SECRET',
|
|
63
64
|
): Promise<string | null> {
|
|
64
|
-
const raw = env?.
|
|
65
|
+
const raw = env?.[binding]
|
|
65
66
|
if (typeof raw === 'string') return raw || null
|
|
66
67
|
if (raw && typeof (raw as { get?: unknown }).get === 'function') {
|
|
67
68
|
try {
|
|
@@ -115,12 +115,42 @@ export const InsightCompetitorArticleSchema = z.object({
|
|
|
115
115
|
covered: z.boolean().optional(),
|
|
116
116
|
})
|
|
117
117
|
|
|
118
|
+
const PublishedClusterPageSchema = z
|
|
119
|
+
.object({
|
|
120
|
+
content_id: z.string().min(1),
|
|
121
|
+
revision_id: z.string().min(1),
|
|
122
|
+
url: z.string().url(),
|
|
123
|
+
title: z.string().min(1),
|
|
124
|
+
})
|
|
125
|
+
.strict()
|
|
126
|
+
|
|
127
|
+
export const InsightSelectedClusterMembershipSchema = z
|
|
128
|
+
.object({
|
|
129
|
+
site_id: z.string().min(1),
|
|
130
|
+
run_id: z.string().min(1),
|
|
131
|
+
intent_key: z.string().min(1),
|
|
132
|
+
desk: z.string().min(1),
|
|
133
|
+
decision_ref: z.string().min(1),
|
|
134
|
+
taxonomy_ref: z.string().min(1),
|
|
135
|
+
hub: PublishedClusterPageSchema,
|
|
136
|
+
children: z.array(PublishedClusterPageSchema).min(4),
|
|
137
|
+
})
|
|
138
|
+
.strict()
|
|
139
|
+
.refine((membership) => {
|
|
140
|
+
const members = [membership.hub, ...membership.children]
|
|
141
|
+
return (
|
|
142
|
+
new Set(members.map((page) => page.content_id)).size === members.length &&
|
|
143
|
+
new Set(members.map((page) => page.url)).size === members.length
|
|
144
|
+
)
|
|
145
|
+
}, 'selected hub and children must be distinct published pages')
|
|
146
|
+
|
|
118
147
|
export const InsightBriefSchema = z.object({
|
|
119
148
|
why_now: z.string().min(1),
|
|
120
149
|
angle: z.string().min(1),
|
|
121
150
|
cluster: z.object({
|
|
122
151
|
canonical: z.string().min(1),
|
|
123
152
|
spokes: z.array(z.string().min(1)),
|
|
153
|
+
selected: InsightSelectedClusterMembershipSchema.optional(),
|
|
124
154
|
}),
|
|
125
155
|
sources: z.array(
|
|
126
156
|
z.object({
|
|
@@ -200,6 +230,21 @@ export const InsightsIngestPayloadSchema = z
|
|
|
200
230
|
// replace DELETE by meta.site_id + meta.run_date — never run_date alone).
|
|
201
231
|
.superRefine((payload, ctx) => {
|
|
202
232
|
for (const [i, row] of payload.rows.entries()) {
|
|
233
|
+
const selected = row.brief.cluster.selected
|
|
234
|
+
if (
|
|
235
|
+
selected &&
|
|
236
|
+
(selected.site_id !== row.site_id ||
|
|
237
|
+
selected.run_id !== payload.meta.run_id ||
|
|
238
|
+
selected.intent_key !== row.intent_key ||
|
|
239
|
+
selected.desk !== row.desk)
|
|
240
|
+
) {
|
|
241
|
+
ctx.addIssue({
|
|
242
|
+
code: z.ZodIssueCode.custom,
|
|
243
|
+
path: ['rows', i, 'brief', 'cluster', 'selected'],
|
|
244
|
+
message:
|
|
245
|
+
'selected membership must bind the same site, run, desk and intent as its signed row',
|
|
246
|
+
})
|
|
247
|
+
}
|
|
203
248
|
if (row.site_id !== payload.meta.site_id || row.run_date !== payload.meta.run_date) {
|
|
204
249
|
ctx.addIssue({
|
|
205
250
|
code: z.ZodIssueCode.custom,
|
package/src/schema/types.ts
CHANGED
|
@@ -611,6 +611,16 @@ export interface InsightBrief {
|
|
|
611
611
|
cluster: {
|
|
612
612
|
canonical: string
|
|
613
613
|
spokes: string[]
|
|
614
|
+
selected?: {
|
|
615
|
+
site_id: string
|
|
616
|
+
run_id: string
|
|
617
|
+
intent_key: string
|
|
618
|
+
desk: string
|
|
619
|
+
decision_ref: string
|
|
620
|
+
taxonomy_ref: string
|
|
621
|
+
hub: { content_id: string; revision_id: string; url: string; title: string }
|
|
622
|
+
children: Array<{ content_id: string; revision_id: string; url: string; title: string }>
|
|
623
|
+
}
|
|
614
624
|
}
|
|
615
625
|
sources: Array<{ title: string; url: string }>
|
|
616
626
|
format?: 'cornerstone' | 'pillar' | 'standalone'
|
|
@@ -2,19 +2,26 @@
|
|
|
2
2
|
// POST /admin/api/content/insights-ingest — machine ingest (replay-safe replace).
|
|
3
3
|
// HMAC-SHA256 (X-Foundry-Signature vs env.INSIGHTS_INGEST_HMAC_SECRET) only —
|
|
4
4
|
// NOT behind requirePublisher. Sibling of foundry-callback under /content/.
|
|
5
|
+
import { env as cloudflareEnv } from 'cloudflare:workers'
|
|
6
|
+
import { config } from 'virtual:growth-labs/cms/config'
|
|
5
7
|
import type { APIContext } from 'astro'
|
|
6
8
|
import { createContentInsightsRoutes } from '../../../routes/content-insights.js'
|
|
7
9
|
import type { RouteContext } from '../../../routes/context.js'
|
|
8
10
|
import { buildSiteAuthz } from '../_authz.js'
|
|
11
|
+
import { toContentConfig } from '../_content-config.js'
|
|
9
12
|
|
|
10
13
|
function toCtx(c: APIContext): RouteContext {
|
|
11
|
-
const env =
|
|
14
|
+
const env = cloudflareEnv as Record<string, unknown>
|
|
12
15
|
return { request: c.request, params: {}, db: env.SITE_DB as RouteContext['db'], env }
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
function siteId(c: APIContext): string {
|
|
16
|
-
const env =
|
|
17
|
-
return (
|
|
19
|
+
const env = cloudflareEnv as Record<string, unknown>
|
|
20
|
+
return (
|
|
21
|
+
toContentConfig(c).activeWorkspaceId ??
|
|
22
|
+
(env.ACTIVE_WORKSPACE_ID as string | undefined) ??
|
|
23
|
+
(config.workspaces?.length === 1 ? config.workspaces[0]!.id : 'default')
|
|
24
|
+
)
|
|
18
25
|
}
|
|
19
26
|
|
|
20
27
|
export const POST = (c: APIContext) =>
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// foundryd's sitebrain.insights.run schedule (W1-07 PR 6). NOT behind
|
|
6
6
|
// requirePublisher. Sibling of insights-ingest under /content/.
|
|
7
7
|
|
|
8
|
+
import { env as cloudflareEnv } from 'cloudflare:workers'
|
|
8
9
|
import { config } from 'virtual:growth-labs/cms/config'
|
|
9
10
|
import type { APIContext } from 'astro'
|
|
10
11
|
import { createContentInsightsRoutes } from '../../../routes/content-insights.js'
|
|
@@ -13,12 +14,12 @@ import { buildSiteAuthz } from '../_authz.js'
|
|
|
13
14
|
import { toContentConfig } from '../_content-config.js'
|
|
14
15
|
|
|
15
16
|
function toCtx(c: APIContext): RouteContext {
|
|
16
|
-
const env =
|
|
17
|
+
const env = cloudflareEnv as Record<string, unknown>
|
|
17
18
|
return { request: c.request, params: {}, db: env.SITE_DB as RouteContext['db'], env }
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
function siteId(c: APIContext): string {
|
|
21
|
-
const env =
|
|
22
|
+
const env = cloudflareEnv as Record<string, unknown>
|
|
22
23
|
return (
|
|
23
24
|
toContentConfig(c).activeWorkspaceId ??
|
|
24
25
|
(env.ACTIVE_WORKSPACE_ID as string | undefined) ??
|