@growth-labs/cms 0.8.4 → 0.8.7
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/foundry-dispatch.d.ts.map +1 -1
- package/dist/engine/foundry-dispatch.js +8 -6
- package/dist/engine/foundry-dispatch.js.map +1 -1
- package/dist/engine/publisher.d.ts +7 -1
- package/dist/engine/publisher.d.ts.map +1 -1
- package/dist/engine/publisher.js +21 -5
- package/dist/engine/publisher.js.map +1 -1
- package/dist/routes/content.d.ts.map +1 -1
- package/dist/routes/content.js +49 -7
- package/dist/routes/content.js.map +1 -1
- package/dist/schema/migrations.d.ts.map +1 -1
- package/dist/schema/migrations.js +4 -0
- package/dist/schema/migrations.js.map +1 -1
- package/dist/schema/types.d.ts +2 -0
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/types.js.map +1 -1
- package/migrations/0031_podcast_processing_error.sql +2 -0
- package/package.json +1 -1
- package/src/engine/foundry-dispatch.ts +9 -5
- package/src/engine/publisher.ts +27 -4
- package/src/routes/content.ts +59 -6
- package/src/schema/migrations.ts +4 -0
- package/src/schema/types.ts +2 -0
package/src/routes/content.ts
CHANGED
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
verifyHmac,
|
|
35
35
|
} from '../engine/foundry-dispatch.js'
|
|
36
36
|
import {
|
|
37
|
+
parseFrontsChannelTargets,
|
|
37
38
|
parseFrontsDefaultChannelTargets,
|
|
38
39
|
setFrontsChannelTargets,
|
|
39
40
|
} from '../engine/fronts-publish-intent.js'
|
|
@@ -47,6 +48,7 @@ import {
|
|
|
47
48
|
getContentRelations,
|
|
48
49
|
getContentTagPairs,
|
|
49
50
|
publishContent,
|
|
51
|
+
type ScheduleFrontsChannelTargets,
|
|
50
52
|
scheduleContent,
|
|
51
53
|
unpublishContent,
|
|
52
54
|
updateContentItem,
|
|
@@ -751,7 +753,7 @@ async function getContentPayload(ctx: RouteContext, type: string, id: string) {
|
|
|
751
753
|
return ctx.db
|
|
752
754
|
.prepare(
|
|
753
755
|
`SELECT transcript, audio_r2_key, duration_seconds,
|
|
754
|
-
processing_source_url, processing_source_kind
|
|
756
|
+
processing_source_url, processing_source_kind, processing_state, processing_error
|
|
755
757
|
FROM podcast_content WHERE content_id = ? LIMIT 1`,
|
|
756
758
|
)
|
|
757
759
|
.bind(id)
|
|
@@ -885,6 +887,36 @@ export function createContentRoutes(config: ContentRouteConfig): ContentRouteHan
|
|
|
885
887
|
await setFrontsChannelTargets(ctx.db, contentId, targetDefaults.targets, { onlyIfNull: true })
|
|
886
888
|
}
|
|
887
889
|
}
|
|
890
|
+
async function prepareScheduleTargets(
|
|
891
|
+
ctx: RouteContext,
|
|
892
|
+
contentId: string,
|
|
893
|
+
): Promise<{ targets: ScheduleFrontsChannelTargets | undefined } | { error: Response }> {
|
|
894
|
+
if (!targetDefaults) return { targets: undefined }
|
|
895
|
+
if ('error' in targetDefaults) return { error: json({ error: targetDefaults.error }, 500) }
|
|
896
|
+
const row = await ctx.db
|
|
897
|
+
.prepare(
|
|
898
|
+
'SELECT fronts_channel_targets FROM content_items WHERE id = ? AND deleted_at IS NULL',
|
|
899
|
+
)
|
|
900
|
+
.bind(contentId)
|
|
901
|
+
.first<{ fronts_channel_targets: string | null }>()
|
|
902
|
+
if (!row) return { error: json({ error: 'Not found', contentId }, 404) }
|
|
903
|
+
const defaults = JSON.stringify(targetDefaults.targets)
|
|
904
|
+
const raw = row.fronts_channel_targets ?? defaults
|
|
905
|
+
const parsed = parseFrontsChannelTargets(raw)
|
|
906
|
+
if ('code' in parsed) {
|
|
907
|
+
return {
|
|
908
|
+
error: json(
|
|
909
|
+
{
|
|
910
|
+
error: parsed.code,
|
|
911
|
+
message: 'Choose at least one valid publication target before scheduling.',
|
|
912
|
+
contentId,
|
|
913
|
+
},
|
|
914
|
+
400,
|
|
915
|
+
),
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
return { targets: { expected: row.fronts_channel_targets, defaults } }
|
|
919
|
+
}
|
|
888
920
|
|
|
889
921
|
async function loadStoredMediaSource(
|
|
890
922
|
db: D1Database,
|
|
@@ -1362,11 +1394,22 @@ export function createContentRoutes(config: ContentRouteConfig): ContentRouteHan
|
|
|
1362
1394
|
return json({ error: 'Invalid publishAt' }, 400)
|
|
1363
1395
|
}
|
|
1364
1396
|
const publishAtSeconds = Math.floor(publishAtMs / 1000)
|
|
1397
|
+
const expectedTargets = new Map<string, ScheduleFrontsChannelTargets | undefined>()
|
|
1398
|
+
for (const id of ids) {
|
|
1399
|
+
const prepared = await prepareScheduleTargets(ctx, id)
|
|
1400
|
+
if ('error' in prepared) return prepared.error
|
|
1401
|
+
expectedTargets.set(id, prepared.targets)
|
|
1402
|
+
}
|
|
1403
|
+
const scheduled: string[] = []
|
|
1365
1404
|
for (const id of ids) {
|
|
1366
|
-
|
|
1367
|
-
await
|
|
1405
|
+
const targets = expectedTargets.get(id)
|
|
1406
|
+
const changed = await scheduleContent(ctx.db, id, publishAtSeconds, undefined, targets)
|
|
1407
|
+
if (targets !== undefined && !changed) {
|
|
1408
|
+
return json({ error: 'schedule_targets_changed', contentId: id, scheduled }, 409)
|
|
1409
|
+
}
|
|
1410
|
+
scheduled.push(id)
|
|
1368
1411
|
}
|
|
1369
|
-
return json({ scheduled
|
|
1412
|
+
return json({ scheduled })
|
|
1370
1413
|
}
|
|
1371
1414
|
|
|
1372
1415
|
// Exhaustive – the discriminated union above covers all ops.
|
|
@@ -1739,8 +1782,18 @@ export function createContentRoutes(config: ContentRouteConfig): ContentRouteHan
|
|
|
1739
1782
|
return json({ error: 'Missing publish time' }, 400)
|
|
1740
1783
|
}
|
|
1741
1784
|
|
|
1742
|
-
await
|
|
1743
|
-
|
|
1785
|
+
const prepared = await prepareScheduleTargets(ctx, id)
|
|
1786
|
+
if ('error' in prepared) return prepared.error
|
|
1787
|
+
const changed = await scheduleContent(
|
|
1788
|
+
ctx.db,
|
|
1789
|
+
id,
|
|
1790
|
+
publishAtSeconds as number,
|
|
1791
|
+
publishTimeZone,
|
|
1792
|
+
prepared.targets,
|
|
1793
|
+
)
|
|
1794
|
+
if (prepared.targets !== undefined && !changed) {
|
|
1795
|
+
return json({ error: 'schedule_targets_changed', contentId: id }, 409)
|
|
1796
|
+
}
|
|
1744
1797
|
|
|
1745
1798
|
// Fire content.scheduled webhook.
|
|
1746
1799
|
await fireWebhook(ctx, 'content.scheduled', {
|
package/src/schema/migrations.ts
CHANGED
|
@@ -910,6 +910,10 @@ ALTER TABLE podcast_content ADD COLUMN processing_publication_guard TEXT;
|
|
|
910
910
|
ALTER TABLE podcast_content ADD COLUMN processing_state TEXT CHECK (processing_state IS NULL OR processing_state IN ('pending','queued','fetching','transcribing','transcoding','ready','failed'));
|
|
911
911
|
`,
|
|
912
912
|
},
|
|
913
|
+
{
|
|
914
|
+
id: '0031_podcast_processing_error',
|
|
915
|
+
sql: `ALTER TABLE podcast_content ADD COLUMN processing_error TEXT;`,
|
|
916
|
+
},
|
|
913
917
|
]
|
|
914
918
|
|
|
915
919
|
/**
|
package/src/schema/types.ts
CHANGED
|
@@ -213,6 +213,8 @@ export interface VideoContentRow {
|
|
|
213
213
|
|
|
214
214
|
export interface PodcastContentRow {
|
|
215
215
|
content_id: string
|
|
216
|
+
/** Current preparation cause, cleared by source replacement or a new capture. */
|
|
217
|
+
processing_error?: string | null
|
|
216
218
|
/** Explicit preparation lifecycle; historical captures remain unqueued. */
|
|
217
219
|
processing_state?: VideoProcessingState | null
|
|
218
220
|
/** Capture-time published revision fence; null for historical/unpublished captures. */
|