diff --git a/.claude/settings.json b/.claude/settings.json index e705e401..ec2149ea 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -1,5 +1,17 @@ { "enabledPlugins": { "compound-engineering@compound-engineering-plugin": true + }, + "hooks": { + "Stop": [ + { + "hooks": [ + { + "type": "command", + "command": "printf '\\a'" + } + ] + } + ] } } diff --git a/CLAUDE.md b/CLAUDE.md index 0012fa13..d8be68df 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -53,10 +53,9 @@ Cursor does not load this file automatically. Keep `.cursor/rules/project-contex ### GraphQL (packages/graphql) -- This package exists solely to provide typed GraphQL operations for apps/web and apps/mobile. -- Types are generated from the Strapi GraphQL schema using gql.tada introspection. +- This package provides the typed `graphql()` function and introspection types generated from the Strapi GraphQL schema using gql.tada. - After any Strapi content type change: run codegen to regenerate types. -- Operations (queries, mutations, fragments) are co-located in this package so both apps share them. +- Operations (queries, mutations, fragments) are defined in consuming apps (e.g., `apps/web/src/lib/content.ts`, `apps/manager/src/cms/`) using the `graphql()` function exported by this package. ### Next.js (apps/web) diff --git a/apps/cms/schema.graphql b/apps/cms/schema.graphql index 78c1e20f..11b30ffb 100644 --- a/apps/cms/schema.graphql +++ b/apps/cms/schema.graphql @@ -240,6 +240,38 @@ type CloudflareR2RelationResponseCollection { nodes: [CloudflareR2!]! } +type ComponentEnrichmentJobStep { + error: String + finishedAt: DateTime + id: ID! + name: ENUM_COMPONENTENRICHMENTJOBSTEP_NAME! + retries: Int + startedAt: DateTime + status: ENUM_COMPONENTENRICHMENTJOBSTEP_STATUS! +} + +input ComponentEnrichmentJobStepFiltersInput { + and: [ComponentEnrichmentJobStepFiltersInput] + error: StringFilterInput + finishedAt: DateTimeFilterInput + name: StringFilterInput + not: ComponentEnrichmentJobStepFiltersInput + or: [ComponentEnrichmentJobStepFiltersInput] + retries: IntFilterInput + startedAt: DateTimeFilterInput + status: StringFilterInput +} + +input ComponentEnrichmentJobStepInput { + error: String + finishedAt: DateTime + id: ID + name: ENUM_COMPONENTENRICHMENTJOBSTEP_NAME + retries: Int + startedAt: DateTime + status: ENUM_COMPONENTENRICHMENTJOBSTEP_STATUS +} + type ComponentLanguageAudioPreview { bitrate: Int codec: String @@ -1305,6 +1337,22 @@ enum ENUM_CLOUDFLARER2_SOURCE { manager } +enum ENUM_COMPONENTENRICHMENTJOBSTEP_NAME { + chapters + embeddings + metadata + transcription + translation +} + +enum ENUM_COMPONENTENRICHMENTJOBSTEP_STATUS { + completed + failed + pending + running + skipped +} + enum ENUM_COMPONENTSECTIONSCARD_VARIANT { default featured @@ -1375,6 +1423,13 @@ enum ENUM_COUNTRY_SOURCE { manager } +enum ENUM_ENRICHMENTJOB_STATUS { + completed + failed + pending + running +} + enum ENUM_KEYWORD_SOURCE { gateway manager @@ -1438,6 +1493,81 @@ enum ENUM_VIDEO_VIDEOSOURCE { youTube } +type EnrichmentJob { + artifacts: JSON + completedAt: DateTime + createdAt: DateTime + currentStep: String + documentId: ID! + errors: JSON + languages: JSON + muxAssetId: String! + muxPlaybackId: String + publishedAt: DateTime + retries: Int + startedAt: DateTime + status: ENUM_ENRICHMENTJOB_STATUS! + steps(filters: ComponentEnrichmentJobStepFiltersInput, pagination: PaginationArg = {}, sort: [String] = []): [ComponentEnrichmentJobStep] + updatedAt: DateTime + video: Video +} + +type EnrichmentJobEntity { + attributes: EnrichmentJob + id: ID +} + +type EnrichmentJobEntityResponse { + data: EnrichmentJob +} + +type EnrichmentJobEntityResponseCollection { + nodes: [EnrichmentJob!]! + pageInfo: Pagination! +} + +input EnrichmentJobFiltersInput { + and: [EnrichmentJobFiltersInput] + artifacts: JSONFilterInput + completedAt: DateTimeFilterInput + createdAt: DateTimeFilterInput + currentStep: StringFilterInput + documentId: IDFilterInput + errors: JSONFilterInput + languages: JSONFilterInput + muxAssetId: StringFilterInput + muxPlaybackId: StringFilterInput + not: EnrichmentJobFiltersInput + or: [EnrichmentJobFiltersInput] + publishedAt: DateTimeFilterInput + retries: IntFilterInput + startedAt: DateTimeFilterInput + status: StringFilterInput + steps: ComponentEnrichmentJobStepFiltersInput + updatedAt: DateTimeFilterInput + video: VideoFiltersInput +} + +input EnrichmentJobInput { + artifacts: JSON + completedAt: DateTime + currentStep: String + errors: JSON + languages: JSON + muxAssetId: String + muxPlaybackId: String + publishedAt: DateTime + retries: Int + startedAt: DateTime + status: ENUM_ENRICHMENTJOB_STATUS + steps: [ComponentEnrichmentJobStepInput] + video: ID +} + +type EnrichmentJobRelationResponseCollection { + nodes: [EnrichmentJob!]! +} + type Error { code: String! message: String @@ -1547,7 +1677,7 @@ input FloatFilterInput { startsWith: Float } -union GenericMorph = BibleBook | BibleCitation | CloudflareR2 | ComponentLanguageAudioPreview | ComponentSectionsBibleQuoteItem | ComponentSectionsBibleQuotesCarousel | ComponentSectionsCard | ComponentSectionsContainer | ComponentSectionsContainerSlot | ComponentSectionsCta | ComponentSectionsEasterDates | ComponentSectionsInfoBlock | ComponentSectionsInfoBlocks | ComponentSectionsMediaCollection | ComponentSectionsMediaCollectionItem | ComponentSectionsNavigationCarousel | ComponentSectionsNavigationCarouselItem | ComponentSectionsPromoBanner | ComponentSectionsQuizButton | ComponentSectionsRelatedQuestionItem | ComponentSectionsRelatedQuestions | ComponentSectionsSection | ComponentSectionsText | ComponentSectionsVideo | ComponentSectionsVideoCarousel | ComponentSectionsVideoCarouselItem | ComponentSectionsVideoHero | ComponentVideoCloudflareImage | ComponentVideoVariantDownload | Continent | Country | CountryLanguage | Experience | I18NLocale | Keyword | Language | MuxVideo | ReviewWorkflowsWorkflow | ReviewWorkflowsWorkflowStage | UploadFile | UsersPermissionsPermission | UsersPermissionsRole | UsersPermissionsUser | Video | VideoEdition | VideoOrigin | VideoStudyQuestion | VideoSubtitle | VideoVariant +union GenericMorph = BibleBook | BibleCitation | CloudflareR2 | ComponentEnrichmentJobStep | ComponentLanguageAudioPreview | ComponentSectionsBibleQuoteItem | ComponentSectionsBibleQuotesCarousel | ComponentSectionsCard | ComponentSectionsContainer | ComponentSectionsContainerSlot | ComponentSectionsCta | ComponentSectionsEasterDates | ComponentSectionsInfoBlock | ComponentSectionsInfoBlocks | ComponentSectionsMediaCollection | ComponentSectionsMediaCollectionItem | ComponentSectionsNavigationCarousel | ComponentSectionsNavigationCarouselItem | ComponentSectionsPromoBanner | ComponentSectionsQuizButton | ComponentSectionsRelatedQuestionItem | ComponentSectionsRelatedQuestions | ComponentSectionsSection | ComponentSectionsText | ComponentSectionsVideo | ComponentSectionsVideoCarousel | ComponentSectionsVideoCarouselItem | ComponentSectionsVideoHero | ComponentVideoCloudflareImage | ComponentVideoVariantDownload | Continent | Country | CountryLanguage | EnrichmentJob | Experience | I18NLocale | Keyword | Language | MuxVideo | ReviewWorkflowsWorkflow | ReviewWorkflowsWorkflowStage | UploadFile | UsersPermissionsPermission | UsersPermissionsRole | UsersPermissionsUser | Video | VideoEdition | VideoOrigin | VideoStudyQuestion | VideoSubtitle | VideoVariant type I18NLocale { code: String @@ -1874,6 +2004,7 @@ type Mutation { status: PublicationStatus = PUBLISHED ): Country createCountryLanguage(data: CountryLanguageInput!, status: PublicationStatus = PUBLISHED): CountryLanguage + createEnrichmentJob(data: EnrichmentJobInput!, status: PublicationStatus = PUBLISHED): EnrichmentJob createExperience( data: ExperienceInput! @@ -1937,6 +2068,7 @@ type Mutation { locale: I18NLocaleCode ): DeleteMutationResponse deleteCountryLanguage(documentId: ID!): DeleteMutationResponse + deleteEnrichmentJob(documentId: ID!): DeleteMutationResponse deleteExperience( documentId: ID! @@ -2018,6 +2150,7 @@ type Mutation { status: PublicationStatus = PUBLISHED ): Country updateCountryLanguage(data: CountryLanguageInput!, documentId: ID!, status: PublicationStatus = PUBLISHED): CountryLanguage + updateEnrichmentJob(data: EnrichmentJobInput!, documentId: ID!, status: PublicationStatus = PUBLISHED): EnrichmentJob updateExperience( data: ExperienceInput! documentId: ID! @@ -2238,6 +2371,9 @@ type Query { countryLanguage(documentId: ID!, status: PublicationStatus = PUBLISHED): CountryLanguage countryLanguages(filters: CountryLanguageFiltersInput, pagination: PaginationArg = {}, sort: [String] = [], status: PublicationStatus = PUBLISHED): [CountryLanguage]! countryLanguages_connection(filters: CountryLanguageFiltersInput, pagination: PaginationArg = {}, sort: [String] = [], status: PublicationStatus = PUBLISHED): CountryLanguageEntityResponseCollection + enrichmentJob(documentId: ID!, status: PublicationStatus = PUBLISHED): EnrichmentJob + enrichmentJobs(filters: EnrichmentJobFiltersInput, pagination: PaginationArg = {}, sort: [String] = [], status: PublicationStatus = PUBLISHED): [EnrichmentJob]! + enrichmentJobs_connection(filters: EnrichmentJobFiltersInput, pagination: PaginationArg = {}, sort: [String] = [], status: PublicationStatus = PUBLISHED): EnrichmentJobEntityResponseCollection experience( documentId: ID! @@ -2833,9 +2969,11 @@ type UsersPermissionsUserRelationResponseCollection { } type Video { + aiMetadata: Boolean! bibleCitations(filters: BibleCitationFiltersInput, pagination: PaginationArg = {}, sort: [String] = []): [BibleCitation]! bibleCitations_connection(filters: BibleCitationFiltersInput, pagination: PaginationArg = {}, sort: [String] = []): BibleCitationRelationResponseCollection - childGatewayIds: JSON + children(filters: VideoFiltersInput, pagination: PaginationArg = {}, sort: [String] = []): [Video]! + children_connection(filters: VideoFiltersInput, pagination: PaginationArg = {}, sort: [String] = []): VideoRelationResponseCollection createdAt: DateTime description: String documentId: ID! @@ -2851,6 +2989,8 @@ type Video { locked: Boolean noIndex: Boolean origin: VideoOrigin + parents(filters: VideoFiltersInput, pagination: PaginationArg = {}, sort: [String] = []): [Video]! + parents_connection(filters: VideoFiltersInput, pagination: PaginationArg = {}, sort: [String] = []): VideoRelationResponseCollection primaryLanguage: Language publishedAt: DateTime slug: String @@ -2938,9 +3078,10 @@ type VideoEntityResponseCollection { } input VideoFiltersInput { + aiMetadata: BooleanFilterInput and: [VideoFiltersInput] bibleCitations: BibleCitationFiltersInput - childGatewayIds: JSONFilterInput + children: VideoFiltersInput createdAt: DateTimeFilterInput description: StringFilterInput documentId: IDFilterInput @@ -2956,6 +3097,7 @@ input VideoFiltersInput { not: VideoFiltersInput or: [VideoFiltersInput] origin: VideoOriginFiltersInput + parents: VideoFiltersInput primaryLanguage: LanguageFiltersInput publishedAt: DateTimeFilterInput slug: StringFilterInput @@ -2970,8 +3112,9 @@ input VideoFiltersInput { } input VideoInput { + aiMetadata: Boolean bibleCitations: [ID] - childGatewayIds: JSON + children: [ID] description: String gatewayId: String imageAlt: String @@ -2981,6 +3124,7 @@ input VideoInput { locked: Boolean noIndex: Boolean origin: ID + parents: [ID] primaryLanguage: ID publishedAt: DateTime slug: String @@ -3112,6 +3256,7 @@ type VideoStudyQuestionRelationResponseCollection { } type VideoSubtitle { + aiGenerated: Boolean createdAt: DateTime documentId: ID! edition: String @@ -3147,6 +3292,7 @@ type VideoSubtitleEntityResponseCollection { } input VideoSubtitleFiltersInput { + aiGenerated: BooleanFilterInput and: [VideoSubtitleFiltersInput] createdAt: DateTimeFilterInput documentId: IDFilterInput @@ -3171,6 +3317,7 @@ input VideoSubtitleFiltersInput { } input VideoSubtitleInput { + aiGenerated: Boolean edition: String gatewayId: String language: ID @@ -3193,6 +3340,7 @@ type VideoSubtitleRelationResponseCollection { } type VideoVariant { + aiGenerated: Boolean! asset: CloudflareR2 brightcoveId: String createdAt: DateTime @@ -3232,6 +3380,7 @@ type VideoVariantEntityResponseCollection { } input VideoVariantFiltersInput { + aiGenerated: BooleanFilterInput and: [VideoVariantFiltersInput] asset: CloudflareR2FiltersInput brightcoveId: StringFilterInput @@ -3260,6 +3409,7 @@ input VideoVariantFiltersInput { } input VideoVariantInput { + aiGenerated: Boolean asset: ID brightcoveId: String dash: String diff --git a/apps/cms/src/api/enrichment-job/content-types/enrichment-job/schema.json b/apps/cms/src/api/enrichment-job/content-types/enrichment-job/schema.json new file mode 100644 index 00000000..dec33b19 --- /dev/null +++ b/apps/cms/src/api/enrichment-job/content-types/enrichment-job/schema.json @@ -0,0 +1,60 @@ +{ + "kind": "collectionType", + "collectionName": "enrichment_jobs", + "info": { + "displayName": "Enrichment Job", + "singularName": "enrichment-job", + "pluralName": "enrichment-jobs", + "description": "AI video enrichment pipeline job tracked by the manager app" + }, + "options": { + "draftAndPublish": false + }, + "attributes": { + "video": { + "type": "relation", + "relation": "manyToOne", + "target": "api::video.video" + }, + "muxAssetId": { + "type": "string", + "required": true + }, + "muxPlaybackId": { + "type": "string" + }, + "languages": { + "type": "json" + }, + "status": { + "type": "enumeration", + "enum": ["pending", "running", "completed", "failed"], + "default": "pending", + "required": true + }, + "currentStep": { + "type": "string" + }, + "retries": { + "type": "integer", + "default": 0 + }, + "startedAt": { + "type": "datetime" + }, + "completedAt": { + "type": "datetime" + }, + "artifacts": { + "type": "json" + }, + "errors": { + "type": "json" + }, + "steps": { + "type": "component", + "repeatable": true, + "component": "enrichment.job-step" + } + } +} diff --git a/apps/cms/src/api/gateway-sync/services/sync-videos.ts b/apps/cms/src/api/gateway-sync/services/sync-videos.ts index 80d85077..9903fb98 100644 --- a/apps/cms/src/api/gateway-sync/services/sync-videos.ts +++ b/apps/cms/src/api/gateway-sync/services/sync-videos.ts @@ -202,7 +202,6 @@ async function syncSingleVideo( videoSource: video.source ?? undefined, locked: video.locked, noIndex: video.noIndex ?? false, - childGatewayIds: video.children.map((c) => c.id), origin: video.origin ? clearableRelation(caches.originMap.get(video.origin.id)) : { set: [] }, @@ -422,6 +421,8 @@ export async function syncVideos( const seenVideoIds = new Set() const seenSubtitleIds = new Set() + // Track parent→children gateway IDs for the post-pass relation linking + const parentChildMap = new Map() let offset = 0 let totalProcessed = 0 @@ -479,6 +480,12 @@ export async function syncVideos( seenVideoIds.add(video.id) for (const s of video.subtitles) seenSubtitleIds.add(s.id) + // Record parent→children for the post-pass + const childIds = video.children.map((c) => c.id) + if (childIds.length > 0) { + parentChildMap.set(video.id, childIds) + } + try { const result = await syncSingleVideo(strapi, video, { originMap, @@ -524,6 +531,49 @@ export async function syncVideos( ) } + // Post-pass: link parent→children relations now that all videos exist + if (parentChildMap.size > 0) { + const videoMap = await buildGatewayIdMap(strapi, "api::video.video", "en") + let linked = 0 + + for (const [parentGatewayId, childGatewayIds] of parentChildMap) { + const parentDocId = videoMap.get(parentGatewayId) + if (!parentDocId) continue + + // Skip manager-owned parents — their children relations are managed by the manager app + const parentDoc = await findByGatewayId( + strapi, + "api::video.video", + parentGatewayId, + "en", + ) + if (parentDoc?.source === "manager") continue + + const childDocIds = childGatewayIds + .map((id) => videoMap.get(id)) + .filter((id): id is string => id != null) + + if (childDocIds.length === 0) continue + + try { + await docs(strapi, "api::video.video").update({ + documentId: parentDocId, + locale: "en", + data: { children: { set: childDocIds } }, + }) + linked += childDocIds.length + } catch (error) { + strapi.log.warn( + `[gateway-sync] Failed to link children to parent ${parentGatewayId}: ${formatError(error)}`, + ) + } + } + + strapi.log.info( + `[gateway-sync] Linked ${linked} child video relations across ${parentChildMap.size} parents`, + ) + } + const totalSynced = stats.created + stats.updated const successRate = gatewayTotal ? `${((totalSynced / gatewayTotal) * 100).toFixed(1)}%` diff --git a/apps/cms/src/api/video-subtitle/content-types/video-subtitle/schema.json b/apps/cms/src/api/video-subtitle/content-types/video-subtitle/schema.json index 5127d889..ae40a4dd 100644 --- a/apps/cms/src/api/video-subtitle/content-types/video-subtitle/schema.json +++ b/apps/cms/src/api/video-subtitle/content-types/video-subtitle/schema.json @@ -74,6 +74,11 @@ "enum": ["gateway", "manager"], "default": "gateway", "required": true + }, + "aiGenerated": { + "type": "boolean", + "default": false, + "required": true } } } diff --git a/apps/cms/src/api/video-variant/content-types/video-variant/schema.json b/apps/cms/src/api/video-variant/content-types/video-variant/schema.json index 7dfd4071..82e798c5 100644 --- a/apps/cms/src/api/video-variant/content-types/video-variant/schema.json +++ b/apps/cms/src/api/video-variant/content-types/video-variant/schema.json @@ -89,6 +89,11 @@ "enum": ["gateway", "manager"], "default": "gateway", "required": true + }, + "aiGenerated": { + "type": "boolean", + "default": false, + "required": true } } } diff --git a/apps/cms/src/api/video/content-types/video/schema.json b/apps/cms/src/api/video/content-types/video/schema.json index fe72054c..49b48249 100644 --- a/apps/cms/src/api/video/content-types/video/schema.json +++ b/apps/cms/src/api/video/content-types/video/schema.json @@ -111,8 +111,22 @@ } } }, - "childGatewayIds": { - "type": "json", + "children": { + "type": "relation", + "relation": "manyToMany", + "target": "api::video.video", + "inversedBy": "parents", + "pluginOptions": { + "i18n": { + "localized": false + } + } + }, + "parents": { + "type": "relation", + "relation": "manyToMany", + "target": "api::video.video", + "mappedBy": "children", "pluginOptions": { "i18n": { "localized": false @@ -216,6 +230,16 @@ "localized": false } } + }, + "aiMetadata": { + "type": "boolean", + "default": false, + "required": true, + "pluginOptions": { + "i18n": { + "localized": false + } + } } } } diff --git a/apps/cms/src/components/enrichment/job-step.json b/apps/cms/src/components/enrichment/job-step.json new file mode 100644 index 00000000..59e9fc0c --- /dev/null +++ b/apps/cms/src/components/enrichment/job-step.json @@ -0,0 +1,39 @@ +{ + "collectionName": "components_enrichment_job_steps", + "info": { + "displayName": "Job Step", + "description": "Individual step within an enrichment job" + }, + "attributes": { + "name": { + "type": "enumeration", + "enum": [ + "transcription", + "translation", + "chapters", + "metadata", + "embeddings" + ], + "required": true + }, + "status": { + "type": "enumeration", + "enum": ["pending", "running", "completed", "failed", "skipped"], + "default": "pending", + "required": true + }, + "retries": { + "type": "integer", + "default": 0 + }, + "startedAt": { + "type": "datetime" + }, + "finishedAt": { + "type": "datetime" + }, + "error": { + "type": "text" + } + } +} diff --git a/apps/manager/CLAUDE.md b/apps/manager/CLAUDE.md index a8338bc1..3b563faa 100644 --- a/apps/manager/CLAUDE.md +++ b/apps/manager/CLAUDE.md @@ -60,7 +60,7 @@ Local dev requires a Strapi user with role name exactly `Manager`. Create via St - The workflow SDK package is `workflow` (not `@workflowdev/sdk`). See https://useworkflow.dev/. - OpenRouter does not expose a Whisper transcription endpoint — use a supported model or switch to Mux's built-in transcription (`input[].generated_subtitles`). - Railway S3 requires `forcePathStyle: true` in the S3Client config. -- Job state (`.data/jobs.json`) is file-based and ephemeral on Railway. Data is lost on deploy/restart. Must be replaced with a durable store (Strapi content type or database) before production use. +- Job state is stored in Strapi as `EnrichmentJob` content type (with `enrichment.job-step` repeatable component). The `src/lib/state.ts` module provides the same `createJob`/`getJob`/`listJobs`/`updateJob`/`updateStepStatus` API backed by Strapi GraphQL mutations. - The `"use workflow"` and `"use step"` directives in `src/workflows/` are **inert** without the workflow SDK's build plugin configured in `next.config.ts`. Until the plugin is added and `WORKFLOW_API_KEY` is set, workflows run as plain async functions with no durability, retries, or checkpointing. See https://useworkflow.dev/. ## Environment variables (Doppler project: forge-manager) diff --git a/apps/manager/src/app/api/auth/login/route.ts b/apps/manager/src/app/api/auth/login/route.ts index 5afc6554..fe96a48b 100644 --- a/apps/manager/src/app/api/auth/login/route.ts +++ b/apps/manager/src/app/api/auth/login/route.ts @@ -2,7 +2,7 @@ import { cookies } from "next/headers" import { NextResponse } from "next/server" import { z } from "zod" import { env } from "@/config/env" -import { verifyStrapiJwtWithRole } from "@/lib/auth" +import { fetchUserWithRole } from "@/lib/auth" const loginSchema = z.object({ email: z.string().email(), @@ -11,6 +11,7 @@ const loginSchema = z.object({ const authResponseSchema = z.object({ jwt: z.string().min(1), + user: z.object({ id: z.number() }), }) export async function POST(request: Request) { @@ -50,10 +51,10 @@ export async function POST(request: Request) { { status: 502 }, ) } - const { jwt } = authParsed.data + const { jwt, user: authUser } = authParsed.data - // Verify JWT and fetch user with role (uses admin API token internally) - const user = await verifyStrapiJwtWithRole(jwt) + // Fetch user with role (uses admin API token to bypass content API sanitization) + const user = await fetchUserWithRole(authUser.id) if (!user) { return NextResponse.json( diff --git a/apps/manager/src/app/api/enrich/route.ts b/apps/manager/src/app/api/enrich/route.ts new file mode 100644 index 00000000..67da0598 --- /dev/null +++ b/apps/manager/src/app/api/enrich/route.ts @@ -0,0 +1,147 @@ +// POST /api/enrich — Create enrichment jobs for existing CMS videos. +// Accepts an array of video gateway IDs and target language codes. +// Looks up the Mux asset for each video's first variant and creates +// an enrichment job + kicks off the workflow. + +import { after } from "next/server" +import { NextResponse } from "next/server" +import { z } from "zod" +import { graphql, type ResultOf } from "@forge/graphql" +import { authenticateRequest } from "@/lib/auth" +import { createJob, updateJob } from "@/lib/state" +import { runVideoEnrichment } from "@/workflows/videoEnrichment" +import getClient from "@/cms/client" + +const enrichSchema = z.object({ + videoIds: z.array(z.string().min(1)).min(1).max(100), + languages: z.array(z.string().max(10)).max(10), +}) + +const GET_VIDEOS_WITH_MUX = graphql(` + query GetVideosWithMux($filters: VideoFiltersInput) { + videos(filters: $filters, pagination: { pageSize: 100 }) { + documentId + gatewayId + title + variants { + muxVideo { + assetId + playbackId + } + } + } + } +`) + +type VideoNode = NonNullable< + ResultOf["videos"][number] +> + +export async function POST(request: Request) { + const authError = await authenticateRequest(request) + if (authError) return authError + + let rawBody: unknown + try { + rawBody = await request.json() + } catch { + return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 }) + } + + const parsed = enrichSchema.safeParse(rawBody) + if (!parsed.success) { + return NextResponse.json( + { error: "Validation failed", details: parsed.error.flatten() }, + { status: 400 }, + ) + } + + const { videoIds, languages } = parsed.data + const client = getClient() + + // Look up videos and their Mux assets + let videos: VideoNode[] + try { + const result = await client.query({ + query: GET_VIDEOS_WITH_MUX, + variables: { + filters: { gatewayId: { in: videoIds } }, + }, + fetchPolicy: "no-cache", + }) + videos = (result.data?.videos ?? []).filter( + (v): v is VideoNode => v != null, + ) + } catch (error) { + console.error("[api/enrich] Failed to look up videos:", error) + return NextResponse.json( + { error: "Failed to look up videos" }, + { status: 502 }, + ) + } + + const jobs: Array<{ videoId: string; jobId: string }> = [] + const errors: Array<{ videoId: string; error: string }> = [] + + for (const video of videos) { + const gatewayId = video.gatewayId + if (!gatewayId) { + continue + } + // Find the first variant with a Mux asset + const variant = (video.variants ?? []).find((v) => v?.muxVideo?.assetId) + + if (!variant?.muxVideo) { + errors.push({ videoId: gatewayId, error: "No Mux asset found" }) + continue + } + + const muxAssetId = variant.muxVideo.assetId + const muxPlaybackId = variant.muxVideo.playbackId ?? "" + + if (!muxAssetId) { + errors.push({ videoId: gatewayId, error: "No Mux asset ID found" }) + continue + } + + try { + const job = await createJob(muxAssetId, muxPlaybackId, languages) + jobs.push({ videoId: gatewayId, jobId: job.id }) + + // Run enrichment in the background after the response is sent + after(async () => { + try { + await runVideoEnrichment({ + jobId: job.id, + assetId: job.muxAssetId, + muxAssetId, + language: languages[0], + translateTo: languages.slice(1), + }) + } catch (err: unknown) { + console.error(`Enrichment failed for job ${job.id}:`, err) + await updateJob(job.id, { status: "failed" }).catch(console.error) + } + }) + } catch (err) { + console.error( + `[api/enrich] Failed to create enrichment job for video ${gatewayId}:`, + err, + ) + errors.push({ + videoId: gatewayId, + error: "Failed to create enrichment job", + }) + } + } + + return NextResponse.json( + { + created: jobs.length, + failed: errors.length, + jobs, + errors: errors.length > 0 ? errors : undefined, + }, + { status: 201 }, + ) +} diff --git a/apps/manager/src/app/api/languages/route.ts b/apps/manager/src/app/api/languages/route.ts new file mode 100644 index 00000000..b40fef6f --- /dev/null +++ b/apps/manager/src/app/api/languages/route.ts @@ -0,0 +1,245 @@ +import { NextResponse } from "next/server" +import { graphql } from "@forge/graphql" +import { authenticateRequest } from "@/lib/auth" +import getClient from "@/cms/client" +import { + type PageInfo, + DEFAULT_PAGE_INFO, + fetchAllPages, +} from "@/lib/strapi-pagination" + +// --------------------------------------------------------------------------- +// Typed queries +// --------------------------------------------------------------------------- + +const GET_CONTINENTS = graphql(` + query GetContinentsApi { + continents { + documentId + gatewayId + name + } + } +`) + +const GET_COUNTRIES_CONNECTION = graphql(` + query GetCountriesApi($pagination: PaginationArg) { + countries_connection(pagination: $pagination) { + nodes { + documentId + gatewayId + name + continent { + gatewayId + } + } + pageInfo { + page + pageCount + pageSize + total + } + } + } +`) + +const GET_LANGUAGES_CONNECTION = graphql(` + query GetLanguagesApi($pagination: PaginationArg) { + languages_connection(pagination: $pagination) { + nodes { + documentId + gatewayId + name + } + pageInfo { + page + pageCount + pageSize + total + } + } + } +`) + +const GET_COUNTRY_LANGUAGES_CONNECTION = graphql(` + query GetCountryLanguagesApi($pagination: PaginationArg) { + countryLanguages_connection(pagination: $pagination) { + nodes { + documentId + gatewayId + speakers + language { + gatewayId + } + country { + gatewayId + continent { + gatewayId + } + } + } + pageInfo { + page + pageCount + pageSize + total + } + } + } +`) + +// --------------------------------------------------------------------------- +// In-memory cache (geo data changes only on gateway sync) +// --------------------------------------------------------------------------- + +let cachedPayload: string | null = null +let cachedAt = 0 +let refreshPromise: Promise | null = null +const CACHE_TTL_MS = 5 * 60 * 1000 // 5 minutes + +async function doRefreshCache(): Promise { + try { + const client = getClient() + + const [ + continentsResult, + countryNodes, + languageNodes, + countryLanguageNodes, + ] = await Promise.all([ + client.query({ query: GET_CONTINENTS, fetchPolicy: "no-cache" }), + fetchAllPages(async (page) => { + const result = await client.query({ + query: GET_COUNTRIES_CONNECTION, + variables: { pagination: { page, pageSize: 5000 } }, + fetchPolicy: "no-cache", + }) + const conn = result.data?.countries_connection + return { + nodes: conn?.nodes ?? [], + pageInfo: (conn?.pageInfo ?? DEFAULT_PAGE_INFO) as PageInfo, + } + }), + fetchAllPages(async (page) => { + const result = await client.query({ + query: GET_LANGUAGES_CONNECTION, + variables: { pagination: { page, pageSize: 5000 } }, + fetchPolicy: "no-cache", + }) + const conn = result.data?.languages_connection + return { + nodes: conn?.nodes ?? [], + pageInfo: (conn?.pageInfo ?? DEFAULT_PAGE_INFO) as PageInfo, + } + }), + fetchAllPages(async (page) => { + const result = await client.query({ + query: GET_COUNTRY_LANGUAGES_CONNECTION, + variables: { pagination: { page, pageSize: 5000 } }, + fetchPolicy: "no-cache", + }) + const conn = result.data?.countryLanguages_connection + return { + nodes: conn?.nodes ?? [], + pageInfo: (conn?.pageInfo ?? DEFAULT_PAGE_INFO) as PageInfo, + } + }), + ]) + + const continents = (continentsResult.data?.continents ?? []) + .filter((c): c is NonNullable => c != null) + .map((c) => ({ + id: String(c.gatewayId ?? c.documentId), + name: String(c.name ?? ""), + })) + + const countries = countryNodes.map((c) => ({ + id: String(c.gatewayId ?? c.documentId), + name: String(c.name ?? ""), + continentId: String(c.continent?.gatewayId ?? ""), + })) + + const langCountryIds = new Map>() + const langContinentIds = new Map>() + const langCountrySpeakers = new Map>() + + for (const cl of countryLanguageNodes) { + const langId = String(cl.language?.gatewayId ?? "") + const countryId = String(cl.country?.gatewayId ?? "") + const continentId = String(cl.country?.continent?.gatewayId ?? "") + const speakers = cl.speakers ?? 0 + + if (!langId) continue + + if (!langCountryIds.has(langId)) langCountryIds.set(langId, new Set()) + if (countryId) langCountryIds.get(langId)!.add(countryId) + + if (!langContinentIds.has(langId)) langContinentIds.set(langId, new Set()) + if (continentId) langContinentIds.get(langId)!.add(continentId) + + if (!langCountrySpeakers.has(langId)) langCountrySpeakers.set(langId, {}) + if (countryId && speakers > 0) { + const existing = langCountrySpeakers.get(langId)! + existing[countryId] = (existing[countryId] ?? 0) + speakers + } + } + + const languages = languageNodes.map((l) => { + const id = String(l.gatewayId ?? l.documentId) + return { + id, + englishLabel: String(l.name ?? id), + nativeLabel: String(l.name ?? id), + countryIds: Array.from(langCountryIds.get(id) ?? []), + continentIds: Array.from(langContinentIds.get(id) ?? []), + countrySpeakers: langCountrySpeakers.get(id) ?? {}, + } + }) + + cachedPayload = JSON.stringify({ continents, countries, languages }) + cachedAt = Date.now() + } catch (error) { + console.error("[api/languages] Background refresh failed:", error) + } +} + +async function refreshCache(): Promise { + if (refreshPromise) return refreshPromise + refreshPromise = doRefreshCache().finally(() => { + refreshPromise = null + }) + return refreshPromise +} + +// --------------------------------------------------------------------------- +// Route handler +// --------------------------------------------------------------------------- + +export async function GET(request: Request) { + const authError = await authenticateRequest(request) + if (authError) return authError + + const isStale = !cachedPayload || Date.now() - cachedAt >= CACHE_TTL_MS + + // Return cached response immediately, refresh in background if stale + if (cachedPayload) { + if (isStale) void refreshCache() + return new Response(cachedPayload, { + headers: { "Content-Type": "application/json" }, + }) + } + + // No cache yet — must wait for first fetch + await refreshCache() + + if (!cachedPayload) { + return NextResponse.json( + { error: "Failed to fetch language data" }, + { status: 502 }, + ) + } + + return new Response(cachedPayload, { + headers: { "Content-Type": "application/json" }, + }) +} diff --git a/apps/manager/src/app/api/videos/route.ts b/apps/manager/src/app/api/videos/route.ts new file mode 100644 index 00000000..a59e4f87 --- /dev/null +++ b/apps/manager/src/app/api/videos/route.ts @@ -0,0 +1,275 @@ +import { NextResponse } from "next/server" +import { graphql } from "@forge/graphql" +import { authenticateRequest } from "@/lib/auth" +import getClient from "@/cms/client" +import { + type PageInfo, + DEFAULT_PAGE_INFO, + fetchAllPages, +} from "@/lib/strapi-pagination" + +// --------------------------------------------------------------------------- +// Typed queries +// --------------------------------------------------------------------------- + +const GET_VIDEOS_CONNECTION = graphql(` + query GetVideosApi($pagination: PaginationArg) { + videos_connection(pagination: $pagination) { + nodes { + documentId + gatewayId + title + label + slug + aiMetadata + images { + thumbnail + videoStill + } + children { + documentId + gatewayId + title + label + slug + aiMetadata + images { + thumbnail + videoStill + } + variants { + gatewayId + source + aiGenerated + language { + gatewayId + } + } + subtitles { + gatewayId + source + aiGenerated + language { + gatewayId + } + } + } + variants { + gatewayId + source + aiGenerated + language { + gatewayId + } + } + subtitles { + gatewayId + source + aiGenerated + language { + gatewayId + } + } + } + pageInfo { + page + pageCount + pageSize + total + } + } + } +`) + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- + +type RawMediaItem = { + gatewayId: string | null + source: string | null + aiGenerated: boolean | null + language: { gatewayId: string | null } | null +} + +type RawImage = { + thumbnail: string | null + videoStill: string | null +} + +type RawVideoNode = { + documentId: string + gatewayId: string | null + title: string | null + label: string | null + slug: string | null + aiMetadata: boolean | null + images: RawImage[] | null + variants: RawMediaItem[] | null + subtitles: RawMediaItem[] | null + children?: RawVideoNode[] | null +} + +type CoverageStatus = "human" | "ai" | "none" + +const LABEL_DISPLAY: Record = { + collection: "Collection", + episode: "Episode", + featureFilm: "Feature Film", + segment: "Segment", + series: "Series", + shortFilm: "Short Film", + trailer: "Trailer", + behindTheScenes: "Behind the Scenes", + unknown: "Other", +} + +// --------------------------------------------------------------------------- +// Coverage helpers +// --------------------------------------------------------------------------- + +function determineCoverageForItems( + items: RawMediaItem[], + selectedLanguageIds: Set, +): CoverageStatus { + if (selectedLanguageIds.size === 0) return "none" + + const matching = items.filter( + (item) => + item.language?.gatewayId && + selectedLanguageIds.has(item.language.gatewayId), + ) + + if (matching.length === 0) return "none" + + const allAi = matching.every((item) => item.aiGenerated) + return allAi ? "ai" : "human" +} + +function determineCoverage( + video: RawVideoNode, + selectedLanguageIds: Set, +): { subtitles: CoverageStatus; audio: CoverageStatus; meta: CoverageStatus } { + return { + subtitles: determineCoverageForItems( + video.subtitles ?? [], + selectedLanguageIds, + ), + audio: determineCoverageForItems(video.variants ?? [], selectedLanguageIds), + meta: + selectedLanguageIds.size === 0 + ? "none" + : video.aiMetadata + ? "ai" + : "none", + } +} + +// --------------------------------------------------------------------------- +// Route handler +// --------------------------------------------------------------------------- + +export async function GET(request: Request) { + const authError = await authenticateRequest(request) + if (authError) return authError + + const url = new URL(request.url) + const languageIds = url.searchParams.get("languageIds")?.split(",") ?? [] + const selectedSet = new Set(languageIds.filter(Boolean)) + + const client = getClient() + + try { + const videoNodes = await fetchAllPages(async (page) => { + const result = await client.query({ + query: GET_VIDEOS_CONNECTION, + variables: { pagination: { page, pageSize: 5000 } }, + fetchPolicy: "no-cache", + }) + const conn = result.data?.videos_connection + return { + nodes: (conn?.nodes ?? []) as unknown as RawVideoNode[], + pageInfo: (conn?.pageInfo ?? DEFAULT_PAGE_INFO) as PageInfo, + } + }) + + function toVideoItem(video: RawVideoNode) { + const variantLanguageIds = (video.variants ?? []) + .map((v) => v.language?.gatewayId) + .filter((id): id is string => id != null) + const subtitleLanguageIds = (video.subtitles ?? []) + .map((s) => s.language?.gatewayId) + .filter((id): id is string => id != null) + + const firstImage = (video.images ?? [])[0] + const imageUrl = firstImage?.thumbnail ?? firstImage?.videoStill ?? null + + return { + id: String(video.gatewayId ?? video.documentId), + title: + video.title ?? + video.slug ?? + String(video.gatewayId ?? video.documentId), + imageUrl, + label: video.label ?? "unknown", + coverage: determineCoverage(video, selectedSet), + variantLanguageIds, + subtitleLanguageIds, + } + } + + // Parents = videos that have children (via the relation) + // Each parent becomes a collection, its children become the videos inside + const childDocIds = new Set() + const collections: Array<{ + id: string + title: string + label: string + labelDisplay: string + videos: ReturnType[] + }> = [] + + for (const video of videoNodes) { + const children = video.children ?? [] + if (children.length === 0) continue + + for (const child of children) { + childDocIds.add(child.documentId) + } + + collections.push({ + id: String(video.gatewayId ?? video.documentId), + title: + video.title ?? + video.slug ?? + String(video.gatewayId ?? video.documentId), + label: video.label ?? "unknown", + labelDisplay: + LABEL_DISPLAY[video.label ?? "unknown"] ?? video.label ?? "unknown", + videos: children.map(toVideoItem), + }) + } + + // Videos that aren't children of any parent and have no children themselves + const standalone = videoNodes.filter( + (v) => !childDocIds.has(v.documentId) && (v.children ?? []).length === 0, + ) + if (standalone.length > 0) { + collections.push({ + id: "standalone", + title: "Standalone Videos", + label: "standalone", + labelDisplay: "Standalone", + videos: standalone.map(toVideoItem), + }) + } + + return NextResponse.json({ collections }) + } catch (error) { + console.error("[api/videos] Failed to fetch video data:", error) + return NextResponse.json( + { error: "Failed to fetch video data" }, + { status: 502 }, + ) + } +} diff --git a/apps/manager/src/app/dashboard/coverage/page.tsx b/apps/manager/src/app/dashboard/coverage/page.tsx index f1f990e6..b25ebef4 100644 --- a/apps/manager/src/app/dashboard/coverage/page.tsx +++ b/apps/manager/src/app/dashboard/coverage/page.tsx @@ -1,7 +1,5 @@ import type { Metadata } from "next" import { CoverageReportClient } from "@/features/coverage/coverage-report-client" -import { listJobs } from "@/lib/state" -import type { JobRecord } from "@/types/job" export const dynamic = "force-dynamic" @@ -12,14 +10,10 @@ export const metadata: Metadata = { type CoveragePageSearchParams = { languageId?: string languageIds?: string - refresh?: string } function parseRequestedLanguageIds(raw: string | undefined): string[] { - if (!raw) { - return [] - } - + if (!raw) return [] return [ ...new Set( raw @@ -36,62 +30,16 @@ export default async function CoveragePage({ searchParams?: Promise }) { const resolvedSearchParams = searchParams ? await searchParams : undefined - - let initialErrorMessage: string | null = null - let initialJobs: JobRecord[] = [] - - try { - initialJobs = await listJobs() - } catch (error) { - initialErrorMessage = - error instanceof Error ? error.message : "Unable to load job data." - } - - // Extract unique languages from all jobs - const allLanguages = new Set() - for (const job of initialJobs) { - for (const lang of job.languages) { - allLanguages.add(lang) - } - } - - const initialLanguages = Array.from(allLanguages).map((lang) => ({ - id: lang, - englishLabel: lang, - nativeLabel: lang, - })) - const requestedLanguageIds = parseRequestedLanguageIds( resolvedSearchParams?.languageIds ?? resolvedSearchParams?.languageId, ) - let initialSelectedLanguageIds = requestedLanguageIds.filter((id) => - allLanguages.has(id), - ) - - if (initialSelectedLanguageIds.length === 0 && initialLanguages.length > 0) { - initialSelectedLanguageIds = [initialLanguages[0].id] - } - - // Filter jobs by selected languages if any are selected - const filteredJobs = - initialSelectedLanguageIds.length > 0 - ? initialJobs.filter((job) => - job.languages.some((lang) => - initialSelectedLanguageIds.includes(lang), - ), - ) - : initialJobs - return ( -
- -
+ ) } diff --git a/apps/manager/src/app/dashboard/jobs/[id]/page.tsx b/apps/manager/src/app/dashboard/jobs/[id]/page.tsx index 0ce309e1..8f52ed4d 100644 --- a/apps/manager/src/app/dashboard/jobs/[id]/page.tsx +++ b/apps/manager/src/app/dashboard/jobs/[id]/page.tsx @@ -1,28 +1,63 @@ import React from "react" -import Link from "next/link" -import type { Route } from "next" import { notFound } from "next/navigation" -import { getJob } from "@/lib/state" +import { graphql } from "@forge/graphql" +import getClient from "@/cms/client" import { formatStepName } from "@/lib/workflow-steps" import { LiveJobDetailHeader } from "@/features/jobs/live-job-detail-header" +import { toJobRecord } from "@/lib/state" +import type { JobRecord } from "@/types/job" export const dynamic = "force-dynamic" -type SearchParamValue = string | string[] | undefined +// --------------------------------------------------------------------------- +// GraphQL operations +// --------------------------------------------------------------------------- -type SearchParamsInput = { - languageId?: SearchParamValue - languageIds?: SearchParamValue -} +const GET_ENRICHMENT_JOB = graphql(` + query GetEnrichmentJobDetail($documentId: ID!) { + enrichmentJob(documentId: $documentId) { + documentId + muxAssetId + muxPlaybackId + languages + status + currentStep + retries + startedAt + completedAt + artifacts + errors + steps { + name + status + retries + startedAt + finishedAt + error + } + createdAt + updatedAt + } + } +`) -function formatDate(iso?: string): string { - if (!iso) { - return "\u2013" +const GET_LANGUAGE_LABELS = graphql(` + query GetLanguageLabelsForJobDetail($pagination: PaginationArg) { + languages(pagination: $pagination) { + gatewayId + name + } } +`) + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +function formatDate(iso?: string): string { + if (!iso) return "\u2013" const parsed = new Date(iso) - if (Number.isNaN(parsed.getTime())) { - return "\u2013" - } + if (Number.isNaN(parsed.getTime())) return "\u2013" return new Intl.DateTimeFormat(undefined, { month: "short", day: "numeric", @@ -31,193 +66,122 @@ function formatDate(iso?: string): string { }).format(parsed) } -function getSingleSearchParam(value: SearchParamValue): string | null { - if (typeof value === "string") return value - if (Array.isArray(value)) return value[0] ?? null - return null -} - -function parseRequestedLanguageIds(raw: SearchParamValue): string[] { - const scalar = getSingleSearchParam(raw) - if (!scalar) return [] - return [ - ...new Set( - scalar - .split(",") - .map((value) => value.trim()) - .filter(Boolean), - ), - ] -} +// --------------------------------------------------------------------------- +// Page +// --------------------------------------------------------------------------- export default async function JobDetailPage({ params, - searchParams, }: { params: Promise<{ id: string }> - searchParams?: Promise }) { - const resolvedSearchParams = searchParams ? await searchParams : undefined - const requestedLanguageIds = parseRequestedLanguageIds( - resolvedSearchParams?.languageIds ?? resolvedSearchParams?.languageId, - ) - const sharedQuery = - requestedLanguageIds.length > 0 - ? `?languageId=${encodeURIComponent(requestedLanguageIds.join(","))}` - : "" - const { id } = await params - const job = await getJob(id) + + let job: JobRecord | null = null + let languageLabelsById: Record = {} + + try { + const client = getClient() + + const [jobResult, languagesResult] = await Promise.all([ + client.query({ + query: GET_ENRICHMENT_JOB, + variables: { documentId: id }, + fetchPolicy: "no-cache", + }), + client.query({ + query: GET_LANGUAGE_LABELS, + variables: { + pagination: { pageSize: 100 }, + }, + fetchPolicy: "no-cache", + }), + ]) + + const jobData = jobResult.data + if (jobData?.enrichmentJob) { + job = toJobRecord( + jobData.enrichmentJob as Parameters[0], + ) + } + + const languages = languagesResult.data?.languages ?? [] + languageLabelsById = Object.fromEntries( + languages + .filter( + (lang): lang is { gatewayId: string; name: string } => + lang != null && lang.gatewayId != null && lang.name != null, + ) + .map((lang) => [lang.gatewayId, lang.name]), + ) + } catch (error) { + console.error("[jobs/[id]/page] Failed to fetch data from Strapi:", error) + // Graceful degradation — job stays null, triggers notFound below + } if (!job) { notFound() } const muxPlaybackId = job.muxPlaybackId ?? null - const languageLabelsById: Record = {} return ( -
-
-
-
- - {/* eslint-disable-next-line @next/next/no-img-element */} - Jesus Film Project - -
-
-
- - Enrichment Queue - -
-
- - Job Details - -
- - - Back to jobs - -
-
-
-
-
- - - Report - - - - Queue - -
-
-
- - - -
-
-

Error Log

- {job.errors.length} + <> + + +
+
+

Error Log

+ {job.errors.length} +
+ {job.errors.length === 0 ? ( +

No errors recorded.

+ ) : ( +
+ + + + + + + + + + {job.errors.map((error, idx) => ( + + + + + + + + + + + ))} + +
TimeStepCode
{formatDate(error.at)}{formatStepName(error.step)} + {error.code ? ( + {error.code} + ) : ( + "\u2013" + )} +
+

{error.message}

+

+ {error.operatorHint ?? "\u2013"} +

+
- {job.errors.length === 0 ? ( -

No errors recorded.

- ) : ( -
- - - - - - - - - - {job.errors.map((error, idx) => ( - - - - - - - - - - - ))} - -
TimeStepCode
{formatDate(error.at)}{formatStepName(error.step)} - {error.code ? ( - - {error.code} - - ) : ( - "\u2013" - )} -
-

{error.message}

-

- {error.operatorHint ?? "\u2013"} -

-
-
- )} -
-
-
+ )} + + ) } diff --git a/apps/manager/src/app/dashboard/jobs/page.tsx b/apps/manager/src/app/dashboard/jobs/page.tsx index 23d5c29c..c5de5286 100644 --- a/apps/manager/src/app/dashboard/jobs/page.tsx +++ b/apps/manager/src/app/dashboard/jobs/page.tsx @@ -1,124 +1,100 @@ -import React from "react" -import Link from "next/link" -import type { Route } from "next" -import { listJobs } from "@/lib/state" +import { graphql } from "@forge/graphql" +import getClient from "@/cms/client" import { LiveJobsTable } from "@/features/jobs/live-jobs-table" +import { toJobRecord } from "@/lib/state" +import type { JobRecord } from "@/types/job" export const dynamic = "force-dynamic" -type SearchParamValue = string | string[] | undefined +// --------------------------------------------------------------------------- +// GraphQL operations +// --------------------------------------------------------------------------- -type SearchParamsInput = Record +const LIST_ENRICHMENT_JOBS = graphql(` + query ListEnrichmentJobsPage($sort: [String], $pagination: PaginationArg) { + enrichmentJobs(sort: $sort, pagination: $pagination) { + documentId + muxAssetId + muxPlaybackId + languages + status + currentStep + retries + startedAt + completedAt + artifacts + errors + steps { + name + status + retries + startedAt + finishedAt + error + } + createdAt + updatedAt + } + } +`) -type PageProps = { - searchParams?: Promise -} +const GET_LANGUAGE_LABELS = graphql(` + query GetLanguageLabels($pagination: PaginationArg) { + languages(pagination: $pagination) { + gatewayId + name + } + } +`) -function getSingleSearchParam(value: SearchParamValue): string | null { - if (typeof value === "string") return value - if (Array.isArray(value)) return value[0] ?? null - return null -} +// --------------------------------------------------------------------------- +// Page +// --------------------------------------------------------------------------- -function parseRequestedLanguageIds(raw: SearchParamValue): string[] { - const scalar = getSingleSearchParam(raw) - if (!scalar) return [] - return [ - ...new Set( - scalar - .split(",") - .map((value) => value.trim()) - .filter(Boolean), - ), - ] -} +export default async function JobsPage() { + let jobs: JobRecord[] = [] + let languageLabelsById: Record = {} -export default async function JobsPage({ searchParams }: PageProps) { - const normalizedSearchParams = searchParams ? await searchParams : {} - const requestedLanguageIds = parseRequestedLanguageIds( - normalizedSearchParams.languageIds ?? normalizedSearchParams.languageId, - ) - const coverageReportQuery = - requestedLanguageIds.length > 0 - ? `?languageId=${encodeURIComponent(requestedLanguageIds.join(","))}` - : "" - const jobs = await listJobs() - const languageLabelsById: Record = {} + try { + const client = getClient() - return ( -
-
-
-
- - {/* eslint-disable-next-line @next/next/no-img-element */} - Jesus Film Project - -
-
-
- - Enrichment Queue - -
-
- - Jobs - -
-
-
-
-
-
- - - Report - - - - Queue - -
-
-
+ const [jobsResult, languagesResult] = await Promise.all([ + client.query({ + query: LIST_ENRICHMENT_JOBS, + variables: { + sort: ["createdAt:desc"], + pagination: { pageSize: 50 }, + }, + fetchPolicy: "no-cache", + }), + client.query({ + query: GET_LANGUAGE_LABELS, + variables: { + pagination: { pageSize: 100 }, + }, + fetchPolicy: "no-cache", + }), + ]) + + const jobNodes = jobsResult.data?.enrichmentJobs ?? [] + jobs = jobNodes + .filter((node): node is NonNullable => node != null) + .map((node) => toJobRecord(node as Parameters[0])) - -
-
+ const languages = languagesResult.data?.languages ?? [] + languageLabelsById = Object.fromEntries( + languages + .filter( + (lang): lang is { gatewayId: string; name: string } => + lang != null && lang.gatewayId != null && lang.name != null, + ) + .map((lang) => [lang.gatewayId, lang.name]), + ) + } catch (error) { + console.error("[jobs/page] Failed to fetch data from Strapi:", error) + } + + return ( + ) } diff --git a/apps/manager/src/app/dashboard/layout.tsx b/apps/manager/src/app/dashboard/layout.tsx index 91e8678f..59c16a29 100644 --- a/apps/manager/src/app/dashboard/layout.tsx +++ b/apps/manager/src/app/dashboard/layout.tsx @@ -1,13 +1,34 @@ import type { Metadata } from "next" +import { DashboardNav } from "@/features/nav/dashboard-nav" +import { requireAuth } from "@/lib/require-auth" export const metadata: Metadata = { title: "Dashboard — VideoForge Manager", } -export default function DashboardLayout({ +export default async function DashboardLayout({ children, }: { children: React.ReactNode }) { - return <>{children} + await requireAuth() + + return ( +
+
+
+
+ {/* eslint-disable-next-line @next/next/no-img-element */} + Jesus Film Project +
+ +
+ {children} +
+
+ ) } diff --git a/apps/manager/src/app/dashboard/loading.tsx b/apps/manager/src/app/dashboard/loading.tsx index c4d2951d..6c1ac098 100644 --- a/apps/manager/src/app/dashboard/loading.tsx +++ b/apps/manager/src/app/dashboard/loading.tsx @@ -1,3 +1,5 @@ export default function DashboardLoading() { - return
Loading...
+ return ( +
Loading…
+ ) } diff --git a/apps/manager/src/app/globals.css b/apps/manager/src/app/globals.css index 0a7dfe3a..9e50a01e 100644 --- a/apps/manager/src/app/globals.css +++ b/apps/manager/src/app/globals.css @@ -339,8 +339,6 @@ body.jobs-standalone code { flex-direction: column; gap: 24px; padding-bottom: 20px; - position: relative; - z-index: 1; } .report-header { @@ -411,7 +409,7 @@ body.jobs-standalone code { display: flex; flex-direction: column; gap: 8px; - max-width: 320px; + max-width: none; align-items: flex-end; text-align: right; margin-left: auto; @@ -443,6 +441,8 @@ body.jobs-standalone code { color: #6a6359; text-decoration: none; background: transparent; + font-family: inherit; + cursor: pointer; transition: border-color 140ms ease, color 140ms ease, @@ -508,11 +508,10 @@ body.jobs-standalone code { } .header-nav-tabs { - width: 100%; + width: auto; display: inline-flex; justify-content: flex-end; gap: 8px; - flex-wrap: wrap; } .mode-panel { @@ -1225,6 +1224,8 @@ body.jobs-standalone code { gap: 18px; position: relative; overflow-x: hidden; + padding-top: 28px; + margin-top: -24px; overflow-y: visible; } @@ -1283,14 +1284,28 @@ html[data-coverage-loading="true"] .collections::after { justify-content: space-between; gap: 16px; flex-wrap: wrap; + cursor: pointer; } .collection-title-row { display: flex; - align-items: baseline; + align-items: center; gap: 0; } +.collection-select-all.tile { + flex-shrink: 0; + width: 22px; + height: 22px; + margin-right: 10px; + background: transparent; + border-color: #a8a29e; +} + +.collection-select-all.tile:not(.is-selected):hover { + border-color: #78716c; +} + .collection-title { margin: 0; font-size: 1.1rem; @@ -1796,6 +1811,7 @@ html[data-coverage-loading="true"] .collections::after { position: absolute; inset: 0; display: inline-flex; + pointer-events: none; align-items: center; justify-content: center; } @@ -1821,6 +1837,18 @@ html[data-coverage-loading="true"] .collections::after { background: #dc2626; } +.tile--select.is-selected .tile-checkbox-box::after { + content: ""; + position: absolute; + left: 50%; + top: 45%; + width: 5px; + height: 9px; + border: solid #fff; + border-width: 0 2px 2px 0; + transform: translate(-50%, -50%) rotate(45deg); +} + .tile-check-icon { position: absolute; width: 18px; @@ -2056,12 +2084,133 @@ html[data-coverage-loading="true"] .collections::after { transform: translateY(0); pointer-events: auto; } + +.detail-group { + display: flex; + flex-direction: column; + gap: 6px; +} + +.detail-group-list { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 4px 16px; +} + +@media (max-width: 600px) { + .detail-group-list { + grid-template-columns: 1fr; + } +} + +.detail-group + .detail-group { + margin-top: 12px; +} + +.detail-group-heading { + font-size: 0.8rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.04em; + margin: 0; + display: flex; + align-items: center; + gap: 6px; +} + +.detail-group-heading--human { + color: #15803d; +} + +.detail-group-heading--ai { + color: #7c3aed; +} + +.detail-group-heading--none { + color: #9f1239; +} + +.detail-group-count { + font-weight: 600; + font-size: 0.75rem; + opacity: 0.7; + pointer-events: auto; +} .collection-detail-row { display: flex; - align-items: flex-start; + align-items: center; gap: 8px; font-size: 0.95rem; color: #312b22; + cursor: pointer; +} + +.collection-detail-row:hover { + background: rgba(0, 0, 0, 0.03); + border-radius: 4px; +} + +.detail-row-checkbox { + -webkit-appearance: none; + appearance: none; + width: 20px; + height: 20px; + border: 2px solid #a8a29e; + border-radius: 6px; + background: transparent; + cursor: pointer; + flex-shrink: 0; + position: relative; + margin: 0; +} + +.detail-row-checkbox:checked { + border-color: transparent; + background: #1b7a3e; +} + +.detail-row-checkbox:checked::after { + content: ""; + position: absolute; + left: 50%; + top: 45%; + width: 5px; + height: 9px; + border: solid #fff; + border-width: 0 2px 2px 0; + transform: translate(-50%, -50%) rotate(45deg); +} + +.detail-row-checkbox--human { + border-color: #16a34a; + background: rgba(22, 163, 74, 0.18); +} + +.detail-row-checkbox--human:checked { + background: #16a34a; +} + +.detail-row-checkbox--ai { + border-color: #7c3aed; + background: rgba(124, 58, 237, 0.18); +} + +.detail-row-checkbox--ai:checked { + background: #7c3aed; +} + +.detail-row-checkbox--none { + border-color: #ef4444; + background: rgba(239, 68, 68, 0.18); +} + +.detail-row-checkbox--none:checked { + background: #ef4444; +} + +.detail-row-checkbox:disabled { + opacity: 0.5; + cursor: default; } .detail-content { @@ -3717,3 +3866,177 @@ body.jobs-standalone .jobs-step-job-id { align-items: flex-start; } } + +/* --------------------------------------------------------------------------- + Dashboard layout + --------------------------------------------------------------------------- */ + +.dashboard-main { + margin: 0 auto; + max-width: 980px; + padding: 40px 20px 150px; + position: relative; + z-index: 1; +} + +@media (max-width: 720px) { + .dashboard-main { + padding: 28px 16px 110px; + } +} + +/* --------------------------------------------------------------------------- + Skeleton loading + --------------------------------------------------------------------------- */ + +@keyframes skeleton-pulse { + 0%, + 100% { + opacity: 0.15; + } + 50% { + opacity: 0.3; + } +} + +.skeleton { + background: #a8a29e; + border-radius: 6px; + animation: skeleton-pulse 1.5s ease-in-out infinite; +} + +.skeleton--title { + display: inline-block; + width: 180px; + height: 20px; +} + +.skeleton--label { + display: inline-block; + width: 60px; + height: 16px; + border-radius: 999px; +} + +.skeleton--meta { + display: inline-block; + width: 80px; + height: 14px; + margin-top: 6px; +} + +.skeleton--tile { + width: 32px; + height: 32px; + border: 2px solid transparent; +} + +.skeleton-card { + pointer-events: none; +} + +.skeleton-card .collection-tiles { + margin-top: 12px; +} + +/* --------------------------------------------------------------------------- + Login + --------------------------------------------------------------------------- */ + +.login-main { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 24px; +} + +.login-card { + width: 100%; + max-width: 380px; + display: flex; + flex-direction: column; + gap: 24px; +} + +.login-brand { + display: flex; + align-items: center; + gap: 12px; +} + +.login-logo { + width: 48px; + height: 48px; + object-fit: contain; +} + +.login-title { + font-size: 1.485rem; + font-weight: 700; + color: var(--text); +} + +.login-error { + padding: 10px 14px; + background: #fef2f2; + color: var(--error); + border: 1px solid #fecaca; + border-radius: 8px; + font-size: 0.875rem; + line-height: 1.4; +} + +.login-form { + display: flex; + flex-direction: column; + gap: 6px; +} + +.login-label { + font-size: 0.8125rem; + font-weight: 600; + color: var(--muted); + margin-top: 8px; +} + +.login-input { + width: 100%; + padding: 10px 12px; + border: 1px solid var(--border); + border-radius: 8px; + font-size: 0.9375rem; + font-family: inherit; + color: var(--text); + background: var(--surface); + outline: none; + transition: border-color 0.15s; +} + +.login-input:focus { + border-color: #6a6359; +} + +.login-button { + margin-top: 16px; + width: 100%; + padding: 10px; + background: var(--text); + color: #fff; + border: none; + border-radius: 8px; + font-size: 0.9375rem; + font-weight: 600; + font-family: inherit; + cursor: pointer; + transition: background 0.15s; +} + +.login-button:hover { + background: #374151; +} + +.login-button:disabled { + background: #9ca3af; + cursor: default; +} diff --git a/apps/manager/src/app/login/page.tsx b/apps/manager/src/app/login/page.tsx index d3578c73..f92f57b5 100644 --- a/apps/manager/src/app/login/page.tsx +++ b/apps/manager/src/app/login/page.tsx @@ -1,14 +1,18 @@ "use client" -import { useState } from "react" -import { useRouter } from "next/navigation" +import { Suspense, useState } from "react" +import { useRouter, useSearchParams } from "next/navigation" import type { FormEvent } from "react" -export default function LoginPage() { +function LoginForm() { const router = useRouter() + const searchParams = useSearchParams() + const expired = searchParams.get("expired") === "1" const [email, setEmail] = useState("") const [password, setPassword] = useState("") - const [error, setError] = useState(null) + const [error, setError] = useState( + expired ? "Your session has expired. Please sign in again." : null, + ) const [loading, setLoading] = useState(false) async function handleSubmit(e: FormEvent) { @@ -39,120 +43,64 @@ export default function LoginPage() { } return ( -
-
-

- VideoForge Manager -

+
+
+
+ {/* eslint-disable-next-line @next/next/no-img-element */} + Jesus Film Project + Forge Manager +
{error && ( -
+
{error}
)} - - setEmail(e.target.value)} - required - style={{ - width: "100%", - padding: "0.5rem 0.75rem", - border: "1px solid #d1d5db", - borderRadius: 6, - marginBottom: "1rem", - fontSize: "0.875rem", - boxSizing: "border-box", - }} - /> + + + setEmail(e.target.value)} + required + autoComplete="email" + className="login-input" + /> - - setPassword(e.target.value)} - required - style={{ - width: "100%", - padding: "0.5rem 0.75rem", - border: "1px solid #d1d5db", - borderRadius: 6, - marginBottom: "1.5rem", - fontSize: "0.875rem", - boxSizing: "border-box", - }} - /> + + setPassword(e.target.value)} + required + autoComplete="current-password" + className="login-input" + /> - - -
+ + +
+
+ ) +} + +export default function LoginPage() { + return ( + + + ) } diff --git a/apps/manager/src/cms/client.ts b/apps/manager/src/cms/client.ts index 3e1e7518..3a3e8b60 100644 --- a/apps/manager/src/cms/client.ts +++ b/apps/manager/src/cms/client.ts @@ -1,6 +1,5 @@ // Apollo Client for Strapi GraphQL — same pattern as apps/web. // Server-side only. Uses STRAPI_API_TOKEN for authentication. -// TODO: Wire up with @forge/graphql typed operations for CMS sync import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client" import { env } from "@/config/env" @@ -17,6 +16,10 @@ export default function getClient(): ApolloClient { }, }), cache: new InMemoryCache(), + defaultOptions: { + query: { fetchPolicy: "no-cache" }, + mutate: { fetchPolicy: "no-cache" }, + }, }) } return _client diff --git a/apps/manager/src/features/coverage/LanguageGeoSelector.tsx b/apps/manager/src/features/coverage/LanguageGeoSelector.tsx index 2b43f936..ad15db00 100644 --- a/apps/manager/src/features/coverage/LanguageGeoSelector.tsx +++ b/apps/manager/src/features/coverage/LanguageGeoSelector.tsx @@ -1,8 +1,9 @@ "use client" -import { usePathname, useSearchParams } from "next/navigation" +import { usePathname, useRouter, useSearchParams } from "next/navigation" import React, { useEffect, useMemo, useRef, useState } from "react" import { Languages, XCircle } from "lucide-react" +import { apiFetch } from "@/lib/api-fetch" type LanguageOption = { id: string @@ -30,7 +31,7 @@ type GeoLanguage = { countrySpeakers: Record } -type GeoPayload = { +export type GeoPayload = { continents: GeoContinent[] countries: GeoCountry[] languages: GeoLanguage[] @@ -92,6 +93,7 @@ export function LanguageGeoSelector({ className, }: LanguageGeoSelectorProps) { const pathname = usePathname() + const router = useRouter() const searchParams = useSearchParams() const [isLoading, setIsLoading] = useState(false) const [geoData, setGeoData] = useState(null) @@ -176,7 +178,7 @@ export function LanguageGeoSelector({ const fetchLanguages = async () => { try { - const response = await fetch("/api/languages", { + const response = await apiFetch("/api/languages", { signal: controller.signal, }) @@ -232,7 +234,7 @@ export function LanguageGeoSelector({ inFlightSearchesRef.current += 1 setIsSearchingServer(true) - const response = await fetch( + const response = await apiFetch( `/api/languages?search=${encodeURIComponent(query)}`, { signal: controller.signal, @@ -467,7 +469,8 @@ export function LanguageGeoSelector({ window.clearTimeout(navigationTimeoutRef.current) } navigationTimeoutRef.current = window.setTimeout(() => { - window.location.href = nextUrl + // eslint-disable-next-line @typescript-eslint/no-explicit-any + router.push(nextUrl as any) }, 250) } diff --git a/apps/manager/src/features/coverage/coverage-report-client.tsx b/apps/manager/src/features/coverage/coverage-report-client.tsx index db7b2e92..915d3ef3 100644 --- a/apps/manager/src/features/coverage/coverage-report-client.tsx +++ b/apps/manager/src/features/coverage/coverage-report-client.tsx @@ -1,6 +1,5 @@ "use client" -import Link from "next/link" import React, { memo, useCallback, @@ -11,6 +10,7 @@ import React, { } from "react" import { LanguageGeoSelector } from "./LanguageGeoSelector" +import { apiFetch } from "@/lib/api-fetch" function useHydrated(): boolean { const [hydrated, setHydrated] = useState(false) @@ -41,7 +41,7 @@ const FORGE_STEPS: WorkflowStepName[] = [ // Coverage status types — adapted from VideoForge's 3-tier model // --------------------------------------------------------------------------- -type CoverageStatus = "human" | "ai" | "none" +export type CoverageStatus = "human" | "ai" | "none" type CoverageFilter = "all" | CoverageStatus @@ -54,6 +54,7 @@ type ReportType = "subtitles" | "audio" | "meta" type ClientVideo = { id: string title: string + imageUrl: string | null muxAssetId: string muxPlaybackId: string status: JobStatus @@ -79,10 +80,36 @@ type LanguageOption = { nativeLabel: string } +// --------------------------------------------------------------------------- +// CMS-sourced types (used by the server page component) +// --------------------------------------------------------------------------- + +export type CmsVideo = { + id: string + title: string + imageUrl: string | null + label: string + coverage: { + subtitles: CoverageStatus + audio: CoverageStatus + meta: CoverageStatus + } + variantLanguageIds: string[] + subtitleLanguageIds: string[] +} + +export type CmsCollection = { + id: string + title: string + label: string + labelDisplay: string + videos: CmsVideo[] +} + interface CoverageReportClientProps { gatewayConfigured: boolean initialErrorMessage: string | null - initialJobs: JobRecord[] + initialJobs?: JobRecord[] initialSelectedLanguageIds: string[] initialLanguages: LanguageOption[] } @@ -101,7 +128,6 @@ type HoveredVideoDetails = { const SESSION_MODE_KEY = "forge-coverage-mode" const SESSION_REPORT_KEY = "forge-coverage-report" -const COLLECTIONS_PER_BATCH = 200 // --------------------------------------------------------------------------- // Report configuration @@ -204,6 +230,7 @@ function jobToClientVideo(job: JobRecord): ClientVideo { return { id: job.id, title: `${job.muxAssetId.slice(0, 8)}...`, + imageUrl: null, muxAssetId: job.muxAssetId, muxPlaybackId: job.muxPlaybackId, status: job.status, @@ -246,6 +273,57 @@ function groupJobsIntoCollections(jobs: JobRecord[]): ClientCollection[] { })) } +function cmsVideoToClientVideo( + video: CmsVideo, + reportType: ReportType, +): ClientVideo { + const coverageStatus = video.coverage[reportType] + return { + id: video.id, + title: video.title, + imageUrl: video.imageUrl, + muxAssetId: video.id, + muxPlaybackId: "", + status: "completed", + languages: [ + ...new Set([...video.variantLanguageIds, ...video.subtitleLanguageIds]), + ], + steps: FORGE_STEPS.map((name) => ({ + name, + status: + coverageStatus === "human" + ? ("completed" as const) + : ("pending" as const), + retries: 0, + })), + errors: [], + artifacts: {}, + coverageStatus, + stepCompleteness: { + completed: + coverageStatus === "human" + ? FORGE_STEPS.length + : coverageStatus === "ai" + ? 1 + : 0, + total: FORGE_STEPS.length, + }, + } +} + +function cmsCollectionsToClientCollections( + collections: CmsCollection[], + reportType: ReportType, +): ClientCollection[] { + return collections.map((collection) => ({ + id: collection.id, + title: collection.title, + label: collection.label, + labelDisplay: collection.labelDisplay, + videos: collection.videos.map((v) => cmsVideoToClientVideo(v, reportType)), + })) +} + function formatPercent(count: number, total: number): number { if (total === 0) return 0 return Math.round((count / total) * 100) @@ -440,7 +518,7 @@ function CoverageBar({ activeFilter === segment.key ? " is-active" : "" }`} style={{ width: `${segment.percent}%` }} - title={`${segment.label} jobs: ${counts[segment.key]}`} + title={`${segment.label} videos: ${counts[segment.key]}`} aria-pressed={activeFilter === segment.key} onClick={() => handleSegmentClick(segment.key)} disabled={!isExplore} @@ -550,92 +628,49 @@ function ReportTypeSelector({ ) } -function StepSummary({ steps }: { steps: JobStepState[] }) { - return ( -
- - Steps {steps.filter((s) => s.status === "completed").length}/ - {steps.length} - - {steps.map((step) => ( - - {step.name} - - ))} -
- ) -} - // --------------------------------------------------------------------------- // Collection card // --------------------------------------------------------------------------- type CollectionCardProps = { collection: ClientCollection - reportType: ReportType reportConfig: (typeof REPORT_CONFIG)[ReportType] filter: CoverageFilter isExpanded: boolean + isSelectMode: boolean + selectedVideoIds: Set onToggleExpanded: (collectionId: string) => void onHoverVideo: (details: HoveredVideoDetails | null) => void -} - -function getReportStatusForVideo( - video: ClientVideo, - reportType: ReportType, -): CoverageStatus { - if (reportType === "audio") { - if (video.languages.length > 1) return "human" - if (video.languages.length === 1) return "ai" - return "none" - } - if (reportType === "meta") { - const count = Object.keys(video.artifacts).length - if (count >= FORGE_STEPS.length) return "human" - if (count > 0) return "ai" - return "none" - } - return video.coverageStatus + onToggleVideo: (videoId: string) => void } const CollectionCard = memo(function CollectionCard({ collection, - reportType, reportConfig, filter, isExpanded, + isSelectMode, + selectedVideoIds, onToggleExpanded, onHoverVideo, + onToggleVideo, }: CollectionCardProps) { const total = collection.videos.length const counts = useMemo(() => { return collection.videos.reduce( (acc, video) => { - acc[getReportStatusForVideo(video, reportType)] += 1 + acc[video.coverageStatus] += 1 return acc }, { human: 0, ai: 0, none: 0 }, ) - }, [collection.videos, reportType]) + }, [collection.videos]) const filteredVideos = useMemo(() => { if (filter === "all") return collection.videos - return collection.videos.filter( - (video) => getReportStatusForVideo(video, reportType) === filter, - ) - }, [collection.videos, filter, reportType]) + return collection.videos.filter((video) => video.coverageStatus === filter) + }, [collection.videos, filter]) const sortedVideos = useMemo(() => { return [...filteredVideos].sort((a, b) => { @@ -644,35 +679,72 @@ const CollectionCard = memo(function CollectionCard({ ai: 1, none: 2, } - return ( - order[getReportStatusForVideo(a, reportType)] - - order[getReportStatusForVideo(b, reportType)] - ) + return order[a.coverageStatus] - order[b.coverageStatus] }) - }, [filteredVideos, reportType]) + }, [filteredVideos]) return ( -
{ - if (event.key === "Enter" || event.key === " ") { - event.preventDefault() - onToggleExpanded(collection.id) - } - }} - onClick={(event) => { - const target = event.target as HTMLElement - if (target.closest("a, button, input, select, textarea")) return - if (target.closest(".tile")) return - onToggleExpanded(collection.id) - }} - > -
+
+
onToggleExpanded(collection.id)} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault() + onToggleExpanded(collection.id) + } + }} + >
+ {isSelectMode && + (() => { + const noneVideos = collection.videos.filter( + (v) => v.coverageStatus === "none", + ) + const allNoneSelected = + noneVideos.length > 0 && + noneVideos.every((v) => selectedVideoIds.has(v.id)) + + return noneVideos.length > 0 ? ( + { + e.stopPropagation() + if (allNoneSelected) { + for (const v of noneVideos) onToggleVideo(v.id) + } else { + for (const v of noneVideos) { + if (!selectedVideoIds.has(v.id)) onToggleVideo(v.id) + } + } + }} + onKeyDown={(e) => { + if (e.key === " " || e.key === "Enter") { + e.preventDefault() + e.stopPropagation() + if (allNoneSelected) { + for (const v of noneVideos) onToggleVideo(v.id) + } else { + for (const v of noneVideos) { + if (!selectedVideoIds.has(v.id)) onToggleVideo(v.id) + } + } + } + }} + > + + ) : null + })()}

{collection.title}

@@ -685,7 +757,7 @@ const CollectionCard = memo(function CollectionCard({

- {total} job{total === 1 ? "" : "s"} + {total} video{total === 1 ? "" : "s"}

@@ -747,42 +819,49 @@ const CollectionCard = memo(function CollectionCard({
- {filteredVideos.map((video) => { - const status = getReportStatusForVideo(video, reportType) - const statusLabel = reportConfig.statusLabels[status] - const tileStatusLabel = - reportType === "subtitles" - ? `${statusLabel} (${video.stepCompleteness.completed}/${video.stepCompleteness.total})` - : statusLabel + {(["human", "ai", "none"] as const).map((groupStatus) => { + const groupVideos = filteredVideos + .filter((v) => v.coverageStatus === groupStatus) + .sort((a, b) => a.title.localeCompare(b.title)) + if (groupVideos.length === 0) return null return ( -
-
@@ -840,100 +935,106 @@ export function CoverageReportClient({ initialSelectedLanguageIds, initialLanguages, }: CoverageReportClientProps) { - const collections = useMemo( - () => groupJobsIntoCollections(initialJobs), - [initialJobs], - ) + const [videoCollections, setVideoCollections] = useState([]) + const [isLoadingVideos, setIsLoadingVideos] = useState(true) + const [reportType, setReportType] = useSessionReportType("subtitles") + + const collections = useMemo(() => { + if (videoCollections.length > 0) { + return cmsCollectionsToClientCollections(videoCollections, reportType) + } + return groupJobsIntoCollections(initialJobs ?? []) + }, [videoCollections, initialJobs, reportType]) const selectedLanguageIds = initialSelectedLanguageIds const languageOptions = initialLanguages const errorMessage = initialErrorMessage - const [reportType, setReportType] = useSessionReportType("subtitles") const [filter, setFilter] = useState("all") const [hoveredVideo, setHoveredVideo] = useState( null, ) const [expandedCollections, setExpandedCollections] = useState([]) - const [visibleCount, setVisibleCount] = useState(COLLECTIONS_PER_BATCH) - const [isLoadingMore, setIsLoadingMore] = useState(false) - const [queueJobsCount, setQueueJobsCount] = useState(null) - const loadMoreTimeoutRef = useRef(null) + const [selectedVideoIds, setSelectedVideoIds] = useState>( + new Set(), + ) const hydrated = useHydrated() const reportConfig = REPORT_CONFIG[reportType] const [interactionMode, setInteractionMode] = useSessionMode("explore") const isSelectMode = interactionMode === "select" - const isSubtitleReport = reportType === "subtitles" - useEffect(() => { - if (typeof document === "undefined") return - document.body.classList.add("coverage-standalone") - return () => { - document.body.classList.remove("coverage-standalone") - delete document.documentElement.dataset.coverageLoading - } + const toggleVideoSelection = useCallback((videoId: string) => { + setSelectedVideoIds((prev) => { + const next = new Set(prev) + if (next.has(videoId)) next.delete(videoId) + else next.add(videoId) + return next + }) }, []) - useEffect(() => { - return () => { - if (loadMoreTimeoutRef.current) { - window.clearTimeout(loadMoreTimeoutRef.current) - } - } - }, []) + // Clear selection when switching away from select mode + const handleModeChange = useCallback( + (mode: Mode) => { + if (mode === "explore") setSelectedVideoIds(new Set()) + setInteractionMode(mode) + }, + [setInteractionMode], + ) + // Fetch video coverage data from proxy API when languages change useEffect(() => { - let cancelled = false + const controller = new AbortController() + setIsLoadingVideos(true) - async function loadQueueJobsCount() { - try { - const response = await fetch("/api/jobs", { cache: "no-store" }) - if (!response.ok) { - return - } + void (async () => { + if (selectedLanguageIds.length === 0) { + setIsLoadingVideos(false) + return + } + try { + const params = new URLSearchParams({ + languageIds: selectedLanguageIds.join(","), + }) + const response = await apiFetch(`/api/videos?${params}`, { + signal: controller.signal, + }) + if (!response.ok) return const payload = (await response.json()) as { - jobs: JobRecord[] - total: number + collections: CmsCollection[] } - const currentCount = payload.total ?? 0 - - if (!cancelled) { - setQueueJobsCount(currentCount) + if (payload?.collections) { + setVideoCollections(payload.collections) } } catch { - if (!cancelled) { - setQueueJobsCount(null) - } + // ignore abort / network errors + } finally { + if (!controller.signal.aborted) setIsLoadingVideos(false) } - } + })() - void loadQueueJobsCount() - const intervalId = window.setInterval(loadQueueJobsCount, 30000) + return () => controller.abort() + }, [selectedLanguageIds]) + useEffect(() => { + if (typeof document === "undefined") return + document.body.classList.add("coverage-standalone") return () => { - cancelled = true - window.clearInterval(intervalId) + document.body.classList.remove("coverage-standalone") + delete document.documentElement.dataset.coverageLoading } }, []) - const getReportStatus = useCallback( - (video: ClientVideo): CoverageStatus => { - return getReportStatusForVideo(video, reportType) - }, - [reportType], - ) - const overallCounts = useMemo(() => { return collections.reduce( (acc, collection) => { for (const video of collection.videos) { - acc[getReportStatus(video)] += 1 + acc[video.coverageStatus] += 1 } return acc }, { human: 0, ai: 0, none: 0 }, ) - }, [collections, getReportStatus]) + }, [collections]) const effectiveFilter = filter @@ -943,21 +1044,11 @@ export function CoverageReportClient({ .map((collection) => ({ ...collection, videos: collection.videos.filter( - (video) => getReportStatus(video) === effectiveFilter, + (video) => video.coverageStatus === effectiveFilter, ), })) .filter((collection) => collection.videos.length > 0) - }, [collections, effectiveFilter, getReportStatus]) - - const effectiveVisibleCount = useMemo( - () => Math.min(visibleCount, Math.max(visibleCollections.length, 0)), - [visibleCount, visibleCollections.length], - ) - - const pagedCollections = useMemo( - () => visibleCollections.slice(0, effectiveVisibleCount), - [visibleCollections, effectiveVisibleCount], - ) + }, [collections, effectiveFilter]) const toggleExpanded = useCallback((collectionId: string) => { setExpandedCollections((prev) => @@ -974,102 +1065,22 @@ export function CoverageReportClient({ [], ) - const handleRefreshNow = useCallback(() => { - if (typeof window === "undefined") { - return - } - - const current = new URL(window.location.href) - current.searchParams.set("refresh", "1") - window.location.assign( - `${current.pathname}?${current.searchParams.toString()}`, - ) - }, []) - - const handleLoadMore = useCallback(() => { - if (isLoadingMore) return - setIsLoadingMore(true) - loadMoreTimeoutRef.current = window.setTimeout(() => { - setVisibleCount((prev) => - Math.min(prev + COLLECTIONS_PER_BATCH, visibleCollections.length), - ) - setIsLoadingMore(false) - }, 240) - }, [isLoadingMore, visibleCollections.length]) - const totalCollections = visibleCollections.length - const shownCollections = Math.min(visibleCount, totalCollections) - const canLoadMore = shownCollections < totalCollections - const progressPercent = - totalCollections > 0 - ? Math.round((shownCollections / totalCollections) * 100) - : 0 - - const jobsHref = "/dashboard/jobs" return ( -
-
-
- - {/* eslint-disable-next-line @next/next/no-img-element */} - Jesus Film Project - -
-
-
- - Coverage Report - -
-
- -
+ <> +
+
+ + Coverage Report + +
+
+
-
-
- - - Report - - - - Queue - {hydrated && queueJobsCount !== null && ( - - {queueJobsCount} - - )} - -
-
-
+
@@ -1090,7 +1101,7 @@ export function CoverageReportClient({
- {gatewayConfigured && !errorMessage && ( + {gatewayConfigured && !errorMessage && totalCollections > 0 && (
- Showing {shownCollections} of {totalCollections} collections -
-
- + Showing {totalCollections} collection + {totalCollections === 1 ? "" : "s"}
-
- - - -
)}
- {hydrated && isSubtitleReport && ( - + {hydrated && reportType === "subtitles" && ( + )}

- {hydrated && isSubtitleReport && isSelectMode + {hydrated && reportType === "subtitles" && isSelectMode ? "Select videos for translation." : reportConfig.hintExplore}

@@ -1176,51 +1150,62 @@ export function CoverageReportClient({ {!gatewayConfigured ? (
- Configure the jobs API endpoint to load coverage data. + Configure the videos API endpoint to load coverage data.
) : errorMessage ? (
{errorMessage}
+ ) : isLoadingVideos ? ( +
+ {Array.from({ length: 3 }).map((_, i) => ( +
+
+
+
+
+ + +
+
+ +
+
+
+
+
+ {Array.from({ length: 20 }).map((_, j) => ( + + ))} +
+
+ ))} +
) : (
- {pagedCollections.map((collection) => { + {visibleCollections.map((collection) => { const isExpanded = expandedCollections.includes(collection.id) return ( ) })} {totalCollections === 0 && ( -
No jobs match this filter.
+
No videos match this filter.
)} {totalCollections > 0 && ( -
- -
- {shownCollections} of {totalCollections} loaded -
+
+ {totalCollections} collection + {totalCollections === 1 ? "" : "s"}
)}
@@ -1236,13 +1221,43 @@ export function CoverageReportClient({ {isSelectMode && (
-
0 videos selected
+
+ {selectedVideoIds.size} video + {selectedVideoIds.size === 1 ? "" : "s"} selected +
- Target languages: Unknown + Languages: {selectedLanguageIds.join(", ") || "None"}
- + + ) +} diff --git a/apps/manager/src/lib/api-fetch.ts b/apps/manager/src/lib/api-fetch.ts new file mode 100644 index 00000000..6f79f8a0 --- /dev/null +++ b/apps/manager/src/lib/api-fetch.ts @@ -0,0 +1,31 @@ +// Wrapper around fetch for internal API calls. +// Automatically signs the user out and redirects to login +// when any API response indicates an expired or invalid session. + +export class SessionExpiredError extends Error { + constructor() { + super("Session expired") + this.name = "SessionExpiredError" + } +} + +function handleSessionExpiry() { + // Clear the JWT cookie and redirect to login + void fetch("/api/auth/logout", { method: "POST" }).finally(() => { + window.location.href = "/login?expired=1" + }) +} + +export async function apiFetch( + input: string, + init?: RequestInit, +): Promise { + const response = await fetch(input, init) + + if (response.status === 401) { + handleSessionExpiry() + throw new SessionExpiredError() + } + + return response +} diff --git a/apps/manager/src/lib/auth.ts b/apps/manager/src/lib/auth.ts index eb5a86a7..5294b861 100644 --- a/apps/manager/src/lib/auth.ts +++ b/apps/manager/src/lib/auth.ts @@ -23,7 +23,9 @@ type StrapiUser = { * Private — callers must only pass IDs obtained from a verified source * (JWT-validated /api/users/me or /api/auth/local response). */ -async function fetchUserWithRole(userId: number): Promise { +export async function fetchUserWithRole( + userId: number, +): Promise { try { const response = await fetch( `${env.STRAPI_URL}/api/users/${userId}?populate=role`, @@ -44,28 +46,44 @@ async function fetchUserWithRole(userId: number): Promise { } /** - * Verifies a Strapi JWT and returns the user with role populated. - * First validates the JWT via /api/users/me to get the trusted user ID, - * then fetches the role via admin API token (bypasses content API sanitization). + * Verifies a Strapi JWT signature by calling /api/users/me. + * Strapi returns 401 for invalid/expired JWTs, 200 or 403 for valid ones. + * A 403 means the JWT is genuine but the role lacks the "me" permission — + * in that case we decode the trusted user ID from the validated JWT. + * Returns the user with role populated via admin API token. */ export async function verifyStrapiJwtWithRole( jwt: string, ): Promise { try { - // Verify JWT is valid and get user ID const meResponse = await fetch(`${env.STRAPI_URL}/api/users/me`, { headers: { Authorization: `Bearer ${jwt}` }, signal: AbortSignal.timeout(5000), }) - if (!meResponse.ok) { - return null + if (meResponse.status === 401) { + return null // JWT invalid or expired } - const me = (await meResponse.json()) as { id: number } + let userId: number | undefined + + if (meResponse.ok) { + // 200 — JWT valid and role has "me" permission + const me = (await meResponse.json()) as { id: number } + userId = me.id + } else if (meResponse.status === 403) { + // 403 — JWT signature is valid (Strapi verified it) but role lacks + // the "me" permission. Decode the user ID from the verified JWT. + const parts = jwt.split(".") + if (parts.length !== 3) return null + const payload = JSON.parse( + Buffer.from(parts[1], "base64url").toString(), + ) as { id?: number } + userId = payload.id + } - // Fetch full user with role using admin API token - return await fetchUserWithRole(me.id) + if (!userId) return null + return await fetchUserWithRole(userId) } catch { return null } @@ -89,8 +107,7 @@ export async function authenticateRequest( } // Check Strapi JWT cookie (for dashboard UI) - // Extract the token and validate it against Strapi to confirm it is - // genuine, unexpired, and belongs to a user with the Manager role. + // Verify the JWT signature via Strapi's /api/users/me, then check the role. const cookieHeader = request.headers.get("cookie") ?? "" const jwtMatch = cookieHeader.match(/strapi-jwt=([^;]+)/) if (jwtMatch?.[1]) { diff --git a/apps/manager/src/lib/require-auth.ts b/apps/manager/src/lib/require-auth.ts new file mode 100644 index 00000000..0b0682d4 --- /dev/null +++ b/apps/manager/src/lib/require-auth.ts @@ -0,0 +1,21 @@ +// Server component authentication guard. +// Validates the Strapi JWT signature via /api/users/me and ensures the Manager role. +// Redirects to /login if authentication fails. + +import { cookies } from "next/headers" +import { redirect } from "next/navigation" +import { verifyStrapiJwtWithRole } from "@/lib/auth" + +export async function requireAuth(): Promise { + const cookieStore = await cookies() + const jwt = cookieStore.get("strapi-jwt")?.value + + if (!jwt) { + redirect("/login") + } + + const user = await verifyStrapiJwtWithRole(jwt) + if (!user || user.role?.name !== "Manager") { + redirect("/login") + } +} diff --git a/apps/manager/src/lib/state.ts b/apps/manager/src/lib/state.ts index ec2a1a39..e0101816 100644 --- a/apps/manager/src/lib/state.ts +++ b/apps/manager/src/lib/state.ts @@ -1,110 +1,230 @@ -// Local job state manager. -// In development, persists to .data/jobs.json with a mutex for concurrency safety. -// In production, this should be backed by a durable store (Strapi or database). +// Job state manager backed by Strapi CMS via GraphQL. +// Uses typed operations from @forge/graphql with gql.tada. -if (process.env.NODE_ENV === "production") { - console.warn( - "⚠️ WARNING: File-based job state is not durable on Railway. Data will be lost on deploy/restart. Replace with database before production use.", - ) -} - -import { readFile, writeFile, mkdir, rename } from "node:fs/promises" -import { join, dirname } from "node:path" +import { graphql, type ResultOf, type VariablesOf } from "@forge/graphql" +import getClient from "@/cms/client" +import { buildInitialSteps } from "@/lib/workflow-steps" import type { JobRecord, JobStatus, + JobStepState, WorkflowStepName, StepStatus, } from "@/types/job" -import { buildInitialSteps } from "@/lib/workflow-steps" export type { JobRecord, JobStatus, WorkflowStepName, StepStatus } -const STATE_FILE = join(process.cwd(), ".data", "jobs.json") +// --------------------------------------------------------------------------- +// GraphQL fragments & operations (typed via gql.tada) +// --------------------------------------------------------------------------- -type JobStore = { - jobs: Record -} +const JOB_FIELDS = graphql(` + fragment JobFields on EnrichmentJob @_unmask { + documentId + muxAssetId + muxPlaybackId + languages + status + currentStep + retries + createdAt + updatedAt + startedAt + completedAt + artifacts + errors + steps { + name + status + retries + startedAt + finishedAt + error + } + } +`) -// Simple promise-based mutex to serialize file read-modify-write cycles. -let mutexPromise: Promise = Promise.resolve() +const CREATE_JOB = graphql( + ` + mutation CreateEnrichmentJob($data: EnrichmentJobInput!) { + createEnrichmentJob(data: $data) { + ...JobFields + } + } + `, + [JOB_FIELDS], +) -function withMutex(fn: () => Promise): Promise { - const result = mutexPromise.then(fn) - mutexPromise = result.then( - () => undefined, - () => undefined, - ) - return result -} +const UPDATE_JOB = graphql( + ` + mutation UpdateEnrichmentJob($documentId: ID!, $data: EnrichmentJobInput!) { + updateEnrichmentJob(documentId: $documentId, data: $data) { + ...JobFields + } + } + `, + [JOB_FIELDS], +) -let dirCreated = false +const GET_JOB = graphql( + ` + query GetEnrichmentJob($documentId: ID!) { + enrichmentJob(documentId: $documentId) { + ...JobFields + } + } + `, + [JOB_FIELDS], +) -async function readStore(): Promise { - try { - const data = await readFile(STATE_FILE, "utf-8") - const parsed: unknown = JSON.parse(data) - if (typeof parsed === "object" && parsed !== null && "jobs" in parsed) { - return parsed as JobStore +const LIST_JOBS = graphql( + ` + query ListEnrichmentJobs { + enrichmentJobs(sort: "createdAt:desc", pagination: { pageSize: 50 }) { + ...JobFields + } } - return { jobs: {} } - } catch { - return { jobs: {} } + `, + [JOB_FIELDS], +) + +// --------------------------------------------------------------------------- +// Types inferred from the fragment +// --------------------------------------------------------------------------- + +type EnrichmentJobNode = NonNullable["enrichmentJob"]> + +// --------------------------------------------------------------------------- +// Mapping helpers +// --------------------------------------------------------------------------- + +/** Map a Strapi GraphQL response node to a local JobRecord. */ +export function toJobRecord(node: EnrichmentJobNode): JobRecord { + return { + id: node.documentId, + muxAssetId: node.muxAssetId, + muxPlaybackId: node.muxPlaybackId ?? "", + languages: (node.languages ?? []) as string[], + options: {}, + status: node.status as JobStatus, + currentStep: node.currentStep as WorkflowStepName | undefined, + retries: node.retries ?? 0, + createdAt: String(node.createdAt ?? ""), + updatedAt: String(node.updatedAt ?? ""), + startedAt: node.startedAt ? String(node.startedAt) : undefined, + completedAt: node.completedAt ? String(node.completedAt) : undefined, + artifacts: (node.artifacts ?? {}) as Record, + steps: (node.steps ?? []).map(toStepState), + errors: (node.errors ?? []) as JobRecord["errors"], } } -async function writeStore(store: JobStore): Promise { - if (!dirCreated) { - await mkdir(dirname(STATE_FILE), { recursive: true }) - dirCreated = true +function toStepState( + s: NonNullable[number], +): JobStepState { + if (!s) { + return { + name: "ingest" as WorkflowStepName, + status: "pending" as StepStatus, + retries: 0, + startedAt: undefined, + finishedAt: undefined, + error: undefined, + } } - // Atomic write: write to temp file, then rename - const tmpFile = `${STATE_FILE}.tmp` - await writeFile(tmpFile, JSON.stringify(store, null, 2)) - await rename(tmpFile, STATE_FILE) + return { + name: s.name as WorkflowStepName, + status: s.status as StepStatus, + retries: s.retries ?? 0, + startedAt: s.startedAt ? String(s.startedAt) : undefined, + finishedAt: s.finishedAt ? String(s.finishedAt) : undefined, + error: s.error ?? undefined, + } +} + +type StrapiStepInput = NonNullable< + NonNullable["data"]>["steps"] +>[number] + +/** Convert local step objects into the shape Strapi expects for the repeatable component. */ +function toStepInput(steps: JobStepState[]): StrapiStepInput[] { + return steps.map( + (s) => + ({ + name: s.name, + status: s.status, + retries: s.retries, + startedAt: s.startedAt ?? null, + finishedAt: s.finishedAt ?? null, + error: s.error ?? null, + }) as StrapiStepInput, + ) } +// --------------------------------------------------------------------------- +// Public API +// --------------------------------------------------------------------------- + export async function createJob( muxAssetId: string, muxPlaybackId: string, languages: string[] = [], ): Promise { - return withMutex(async () => { - const store = await readStore() - const id = crypto.randomUUID() - const now = new Date().toISOString() - - const job: JobRecord = { - id, - muxAssetId, - muxPlaybackId, - languages, - options: {}, - status: "pending", - retries: 0, - createdAt: now, - updatedAt: now, - artifacts: {}, - steps: buildInitialSteps(), - errors: [], - } + const client = getClient() + const steps = buildInitialSteps() - store.jobs[id] = job - await writeStore(store) - return job + const result = await client.mutate({ + mutation: CREATE_JOB, + variables: { + data: { + muxAssetId, + muxPlaybackId, + languages, + status: "pending", + retries: 0, + artifacts: {}, + errors: [], + steps: toStepInput(steps), + }, + }, }) + + const data = result.data + if (!data?.createEnrichmentJob) { + throw new Error("Failed to create enrichment job") + } + return toJobRecord(data.createEnrichmentJob) } export async function getJob(id: string): Promise { - const store = await readStore() - return store.jobs[id] ?? null + const client = getClient() + + try { + const result = await client.query({ + query: GET_JOB, + variables: { documentId: id }, + fetchPolicy: "no-cache", + }) + + if (!result.data?.enrichmentJob) return null + return toJobRecord(result.data.enrichmentJob) + } catch (err) { + console.warn(`[state] getJob(${id}) failed:`, err) + return null + } } export async function listJobs(): Promise { - const store = await readStore() - return Object.values(store.jobs).sort( - (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), - ) + const client = getClient() + + const result = await client.query({ + query: LIST_JOBS, + fetchPolicy: "no-cache", + }) + + return (result.data?.enrichmentJobs ?? []) + .filter((node): node is NonNullable => node != null) + .map((node) => toJobRecord(node)) } export async function updateJob( @@ -121,53 +241,107 @@ export async function updateJob( > >, ): Promise { - return withMutex(async () => { - const store = await readStore() - const job = store.jobs[id] - if (!job) return null - - store.jobs[id] = { - ...job, - ...updates, - updatedAt: new Date().toISOString(), - } - await writeStore(store) - return store.jobs[id] - }) + const client = getClient() + + // Build only the fields that were actually provided. + const data: Record = {} + if (updates.status !== undefined) data.status = updates.status + if (updates.currentStep !== undefined) data.currentStep = updates.currentStep + if (updates.artifacts !== undefined) data.artifacts = updates.artifacts + if (updates.startedAt !== undefined) data.startedAt = updates.startedAt + if (updates.completedAt !== undefined) data.completedAt = updates.completedAt + if (updates.retries !== undefined) data.retries = updates.retries + + try { + const mutResult = await client.mutate({ + mutation: UPDATE_JOB, + variables: { documentId: id, data }, + }) + + const result = mutResult.data + if (!result?.updateEnrichmentJob) return null + return toJobRecord(result.updateEnrichmentJob) + } catch (err) { + console.warn(`[state] updateJob(${id}) failed:`, err) + return null + } } +// --------------------------------------------------------------------------- +// Per-job mutex for serializing step updates (read-then-write) +// --------------------------------------------------------------------------- + +const jobUpdateLocks = new Map>() + export async function updateStepStatus( jobId: string, stepName: WorkflowStepName, status: StepStatus, error?: string, ): Promise { - return withMutex(async () => { - const store = await readStore() - const job = store.jobs[jobId] - if (!job) return null - - const now = new Date().toISOString() - const step = job.steps.find((s) => s.name === stepName) - if (step) { - step.status = status - if (status === "running" && !step.startedAt) { - step.startedAt = now - } - if (status === "completed" || status === "failed") { - step.finishedAt = now - } - if (error) { - step.error = error - } - } + // Serialize per-job to avoid read-then-write race conditions. + const previous = jobUpdateLocks.get(jobId) ?? Promise.resolve() + const next = previous.then(() => + doUpdateStepStatus(jobId, stepName, status, error), + ) + jobUpdateLocks.set( + jobId, + next.catch(() => {}), + ) + return next +} +async function doUpdateStepStatus( + jobId: string, + stepName: WorkflowStepName, + status: StepStatus, + error?: string, +): Promise { + // We need to read-then-write because Strapi replaces the entire repeatable + // component array on update — there is no patch-single-item operation. + const job = await getJob(jobId) + if (!job) return null + + const now = new Date().toISOString() + const steps = job.steps.map((s) => { + if (s.name !== stepName) return s + const updated = { ...s, status } + if (status === "running" && !s.startedAt) { + updated.startedAt = now + } + if (status === "completed" || status === "failed") { + updated.finishedAt = now + } if (error) { - job.errors.push({ step: stepName, message: error, at: now }) + updated.error = error } - - job.updatedAt = now - await writeStore(store) - return store.jobs[jobId] + return updated }) + + const errors = [...job.errors] + if (error) { + errors.push({ step: stepName, message: error, at: now }) + } + + const client = getClient() + + try { + const mutResult = await client.mutate({ + mutation: UPDATE_JOB, + variables: { + documentId: jobId, + data: { + steps: toStepInput(steps), + errors, + }, + }, + }) + + const resultData = mutResult.data + if (!resultData?.updateEnrichmentJob) return null + return toJobRecord(resultData.updateEnrichmentJob) + } catch (err) { + console.warn(`[state] updateStepStatus(${jobId}, ${stepName}) failed:`, err) + return null + } } diff --git a/apps/manager/src/lib/strapi-pagination.ts b/apps/manager/src/lib/strapi-pagination.ts new file mode 100644 index 00000000..fd7ca0fb --- /dev/null +++ b/apps/manager/src/lib/strapi-pagination.ts @@ -0,0 +1,31 @@ +// Shared pagination helper for Strapi GraphQL _connection queries. + +export type PageInfo = { + page: number + pageCount: number + pageSize: number + total: number +} + +export const DEFAULT_PAGE_INFO: PageInfo = { + page: 1, + pageCount: 1, + pageSize: 5000, + total: 0, +} + +export async function fetchAllPages( + fetcher: (page: number) => Promise<{ nodes: T[]; pageInfo: PageInfo }>, +): Promise { + const allNodes: T[] = [] + let currentPage = 1 + + while (true) { + const result = await fetcher(currentPage) + allNodes.push(...result.nodes) + if (currentPage >= result.pageInfo.pageCount) break + currentPage += 1 + } + + return allNodes +} diff --git a/apps/manager/src/types/job.ts b/apps/manager/src/types/job.ts index 7d4d5a5b..cff88d89 100644 --- a/apps/manager/src/types/job.ts +++ b/apps/manager/src/types/job.ts @@ -33,15 +33,6 @@ export interface JobOptions { notifyCms?: boolean } -export interface JobCreatePayload { - muxAssetId: string - languages: string[] - sourceCollectionTitle?: string - sourceMediaTitle?: string - requestedLanguageAbbreviations?: string[] - options?: JobOptions -} - export interface JobStepState { name: WorkflowStepName status: StepStatus @@ -51,12 +42,6 @@ export interface JobStepState { error?: string } -export interface JobErrorDetails { - code?: string - operatorHint?: string - isDependencyError?: boolean -} - export interface JobError { step: WorkflowStepName message: string @@ -86,7 +71,3 @@ export interface JobRecord { steps: JobStepState[] errors: JobError[] } - -export interface JobsDb { - jobs: JobRecord[] -} diff --git a/docs/plans/2026-03-22-001-feat-manager-strapi-cms-data-integration-plan.md b/docs/plans/2026-03-22-001-feat-manager-strapi-cms-data-integration-plan.md new file mode 100644 index 00000000..56727270 --- /dev/null +++ b/docs/plans/2026-03-22-001-feat-manager-strapi-cms-data-integration-plan.md @@ -0,0 +1,259 @@ +--- +title: "feat: Wire apps/manager to consume Strapi CMS data exclusively" +type: feat +status: completed +date: 2026-03-22 +--- + +# feat: Wire apps/manager to consume Strapi CMS data exclusively + +## Overview + +The CMS gateway sync (Phases 1-5 complete) populates Strapi with all language, country, continent, and video data from the JFP Gateway. However, `apps/manager` doesn't use any of it — the LanguageGeoSelector fetches from a non-existent endpoint, the coverage page derives data from ephemeral file-based job records, and language labels on the jobs page are empty. Additionally, enrichment jobs are stored in a file (`.data/jobs.json`) that's lost on every Railway deploy. + +This work: + +1. Models enrichment jobs as a Strapi content type (durable storage) +2. Wires the coverage page to load videos from Strapi dynamically +3. Passes geo data (languages/countries/continents) as server-side props +4. Populates language labels on job pages from CMS + +## Problem Statement / Motivation + +1. **Enrichment jobs are ephemeral** — file-based state lost on every deploy/restart (production blocker, todo #029). +2. **Coverage page is non-functional** — no real video data, groups fake "collections" from jobs by status instead of showing actual CMS video catalog with per-language coverage. +3. **LanguageGeoSelector broken** — fetches geo data client-side from a route that doesn't exist. +4. **Language labels empty** — jobs page shows raw IDs instead of names. + +## Proposed Solution + +### New CMS content types + +Model enrichment jobs as a Strapi content type so they're durable and queryable via GraphQL alongside videos. + +### Server-side data flow + +Page server components query Strapi's existing GraphQL API and pass data as props to client components. No new Next.js API routes for geo data. + +### Dynamic video loading + +The coverage page queries Strapi for videos filtered by selected language(s), loading variants and subtitles to determine per-language coverage status. + +## Technical Approach + +### New Content Types in `apps/cms` + +#### `EnrichmentJob` (collection type) + +``` +apps/cms/src/api/enrichment-job/content-types/enrichment-job/schema.json +``` + +| Field | Type | Notes | +| --------------- | ------------------------------------------------ | ---------------------------------- | +| `video` | relation (manyToOne → Video) | The video being enriched | +| `muxAssetId` | string, required | Mux asset ID | +| `muxPlaybackId` | string | Mux playback ID | +| `languages` | JSON | Array of target language codes | +| `status` | enumeration: pending, running, completed, failed | Job lifecycle | +| `currentStep` | string | Currently executing step name | +| `retries` | integer, default 0 | Retry count | +| `startedAt` | datetime | When processing began | +| `completedAt` | datetime | When processing finished | +| `artifacts` | JSON | Map of artifact type → storage URL | +| `errors` | JSON | Array of `{ step, message, at }` | +| `steps` | repeatable component (enrichment.job-step) | Step-level tracking | + +No i18n. No `source` field — these are always manager-created. + +#### `enrichment.job-step` (component) + +``` +apps/cms/src/components/enrichment/job-step.json +``` + +| Field | Type | Notes | +| ------------ | ----------------------------------------------------------------------- | ----------------------- | +| `name` | enumeration: transcription, translation, chapters, metadata, embeddings | Step identifier | +| `status` | enumeration: pending, running, completed, failed, skipped | Step lifecycle | +| `retries` | integer, default 0 | | +| `startedAt` | datetime | | +| `finishedAt` | datetime | | +| `error` | text | Error message if failed | + +#### ERD + +```mermaid +erDiagram + Video ||--o{ EnrichmentJob : "enriched by" + EnrichmentJob ||--o{ JobStep : has + + Video ||--o{ VideoVariant : has + Video ||--o{ VideoSubtitle : has + VideoVariant }o--|| Language : "language" + VideoSubtitle }o--|| Language : "language" +``` + +### Architecture + +``` +Server Components (RSC) Client Components +───────────────────── ───────────────── +coverage/page.tsx ───► CoverageReportClient + ↳ queries Strapi for geo data (props) ↳ LanguageGeoSelector (receives geoData prop) + ↳ queries Strapi for videos + variants ↳ video grid (coverage by language) + filtered by selected language(s) ↳ enrichment job status overlay + +jobs/page.tsx ───► LiveJobsTable + ↳ queries Strapi for enrichment jobs (receives typed job data) + ↳ queries Strapi for language labels + +jobs/[id]/page.tsx ───► LiveJobDetailHeader + ↳ queries single enrichment job from Strapi +``` + +### Implementation Phases + +#### Phase 1: CMS Content Types + +Create the `EnrichmentJob` content type and `enrichment.job-step` component in `apps/cms`. + +**Files to create:** + +- [ ] `apps/cms/src/api/enrichment-job/content-types/enrichment-job/schema.json` +- [ ] `apps/cms/src/components/enrichment/job-step.json` + +**Then:** + +- [ ] Run Strapi locally to register the types +- [ ] Run codegen in `packages/graphql/` to regenerate introspection types +- [ ] Commit generated files alongside schema changes + +#### Phase 2: Replace File-Based Job State + +Replace `apps/manager/src/lib/state.ts` (file-based) with Strapi GraphQL mutations. + +**Files to modify:** + +- [ ] `apps/manager/src/lib/state.ts` — rewrite `createJob`, `getJob`, `listJobs`, `updateJob`, `updateStepStatus` to use Apollo Client + Strapi GraphQL mutations instead of file I/O +- [ ] `apps/manager/src/app/api/jobs/route.ts` — update to use new state functions (interface stays the same) +- [ ] `apps/manager/src/app/api/jobs/[id]/route.ts` — same +- [ ] `apps/manager/src/workflows/videoEnrichment.ts` — same (calls `updateJob`/`updateStepStatus`) + +The `JobRecord` TypeScript type can stay as-is — it maps cleanly to the Strapi content type. The state module's public API doesn't change, only the backing store. + +#### Phase 3: Geo Data as Server-Side Props + +Fetch language/country/continent data server-side and pass as props. No new API routes. + +**Files to modify:** + +- [ ] `apps/manager/src/app/dashboard/coverage/page.tsx` — query Strapi for continents, countries, languages, countryLanguages; assemble `GeoPayload`; pass as prop to `CoverageReportClient` +- [ ] `apps/manager/src/features/coverage/coverage-report-client.tsx` — accept + forward `geoData` prop +- [ ] `apps/manager/src/features/coverage/LanguageGeoSelector.tsx` — accept `initialGeoData` prop; skip client-side fetch when provided +- [ ] `apps/manager/src/app/dashboard/jobs/page.tsx` — query language labels from Strapi, pass as `languageLabelsById` +- [ ] `apps/manager/src/app/dashboard/jobs/[id]/page.tsx` — same + +#### Phase 4: Coverage Page — Dynamic Video Loading + +Replace job-based "collections" with real CMS video data. + +**Files to modify:** + +- [ ] `apps/manager/src/app/dashboard/coverage/page.tsx` — query Videos with their variants and subtitles, filtered by selected language(s). Group by `label` (collection, episode, series, etc.) or parent/child hierarchy (`childGatewayIds`). Determine per-video coverage: does a variant exist for the selected language? Subtitles? Pass to `CoverageReportClient`. +- [ ] `apps/manager/src/features/coverage/coverage-report-client.tsx` — update `ClientVideo` type and `groupJobsIntoCollections` to work with CMS video data instead of `JobRecord`. Coverage status derived from variant/subtitle existence per language rather than enrichment step completion. Overlay enrichment job status where available. + +**Coverage status logic:** + +For a given video + selected language: + +- **Subtitles report**: `human` if VideoSubtitle exists, `ai` if EnrichmentJob completed transcription/translation, `none` otherwise +- **Audio report**: `human` if VideoVariant exists with hls/dash URLs, `ai` if EnrichmentJob completed voiceover, `none` otherwise +- **Meta report**: `human` if Video has description + keywords, `ai` if EnrichmentJob completed metadata/chapters, `none` otherwise + +#### Phase 5: Cleanup + +- [ ] `apps/manager/src/cms/client.ts` — remove TODO comment +- [ ] Fix CLAUDE.md conventions about GraphQL operations location +- [ ] Delete `.data/` directory handling from state.ts (no longer needed) +- [ ] Update `apps/manager/CLAUDE.md` to reflect that jobs are now stored in Strapi + +## Files Summary + +| Action | File | Purpose | +| ---------- | -------------------------------------------------------------------------- | ------------------------------------ | +| Create | `apps/cms/src/api/enrichment-job/content-types/enrichment-job/schema.json` | EnrichmentJob content type | +| Create | `apps/cms/src/components/enrichment/job-step.json` | Job step component | +| Regenerate | `packages/graphql/src/graphql-env.d.ts` | Updated introspection types | +| Modify | `apps/manager/src/lib/state.ts` | Replace file I/O with Strapi GraphQL | +| Modify | `apps/manager/src/app/api/jobs/route.ts` | Use Strapi-backed state | +| Modify | `apps/manager/src/app/api/jobs/[id]/route.ts` | Use Strapi-backed state | +| Modify | `apps/manager/src/workflows/videoEnrichment.ts` | Use Strapi-backed state | +| Modify | `apps/manager/src/app/dashboard/coverage/page.tsx` | Server-side geo + video queries | +| Modify | `apps/manager/src/features/coverage/coverage-report-client.tsx` | Accept geoData prop, CMS video data | +| Modify | `apps/manager/src/features/coverage/LanguageGeoSelector.tsx` | Accept initialGeoData prop | +| Modify | `apps/manager/src/app/dashboard/jobs/page.tsx` | Query jobs + labels from Strapi | +| Modify | `apps/manager/src/app/dashboard/jobs/[id]/page.tsx` | Query single job from Strapi | +| Modify | `apps/manager/src/cms/client.ts` | Remove TODO | +| Modify | `CLAUDE.md` | Fix GraphQL operations convention | +| Modify | `packages/graphql/CLAUDE.md` | Fix operations location convention | +| Modify | `apps/manager/CLAUDE.md` | Document Strapi-backed job storage | + +## Acceptance Criteria + +### Functional Requirements + +- [ ] EnrichmentJob content type registered in Strapi admin +- [ ] Creating an enrichment job persists to Strapi (survives deploys) +- [ ] Jobs page lists enrichment jobs from Strapi GraphQL +- [ ] Job detail page loads single job from Strapi +- [ ] Coverage page loads real videos from Strapi, grouped into collections +- [ ] Coverage shows per-language status (subtitles, audio, metadata) based on variant/subtitle existence +- [ ] LanguageGeoSelector renders continent/country sidebar from server-side CMS data +- [ ] Language labels display on jobs pages (e.g., "Spanish" not raw IDs) +- [ ] All pages degrade gracefully if CMS is temporarily unavailable + +### Quality Gates + +- [ ] `pnpm --filter @forge/manager typecheck` passes +- [ ] `pnpm --filter @forge/manager lint` passes +- [ ] `packages/graphql` codegen passes after schema changes +- [ ] Enrichment workflow runs end-to-end with Strapi-backed job state + +## Dependencies & Prerequisites + +- Strapi CMS running with gateway-synced data +- Records are published (confirmed: gateway sync uses `status: "published"`) +- `STRAPI_URL` and `STRAPI_API_TOKEN` env vars configured +- API token must have create/update/read permissions on `EnrichmentJob` + +## Risk Analysis & Mitigation + +| Risk | Likelihood | Impact | Mitigation | +| ------------------------------------------------------------------- | ---------- | -------------------------- | ----------------------------------------------------------------- | +| Strapi GraphQL mutations slower than file I/O for job updates | Medium | Workflow steps take longer | Acceptable — durability outweighs speed. Batch updates if needed. | +| Thousands of CountryLanguage records require many paginated fetches | Medium | Slow coverage page load | Pagination loops with `pageSize: 100` | +| Strapi pagination cap silently truncates results | High | Missing data | Use `_connection` queries with `pageInfo` + pagination loop | +| Video query for coverage returns too many results | Medium | Memory/timeout | Paginate, filter by label (collections first), lazy-load | +| CMS unavailable during workflow step update | Low | Job state lost | Retry with backoff; log warning | + +## Sources & References + +### Internal References + +- Apollo Client pattern: `apps/web/src/lib/client.ts` +- Query + cache pattern: `apps/web/src/lib/content.ts:180` +- Manager CMS client: `apps/manager/src/cms/client.ts` +- GeoPayload contract: `apps/manager/src/features/coverage/LanguageGeoSelector.tsx:33-37` +- Current job types: `apps/manager/src/types/job.ts` +- Current file state: `apps/manager/src/lib/state.ts` +- Video schema: `apps/cms/src/api/video/content-types/video/schema.json` +- Component pattern: `apps/cms/src/components/video/variant-download.json` + +### Institutional Learnings + +- Apollo `fetchPolicy: "no-cache"` — bypass Apollo cache for server-side freshness (`docs/solutions/web/nextjs16-cachecomponents-isr.md`) +- Lazy getter for Apollo Client — module-scope instantiation crashes CI (`docs/solutions/platform/new-app-ci-and-deployment-patterns.md`) +- Strapi v5 content API returns only published records by default (`docs/solutions/cms/strapi-v5-populate-role-sanitization.md`) +- API token must have permission to populate relations (`docs/solutions/cms/strapi-v5-populate-role-sanitization.md`) +- Use `{ set: [] }` to clear Strapi v5 relations, never `null` (`docs/solutions/integration-issues/strapi-v5-manytone-relation-clearing.md`) diff --git a/docs/solutions/cms/strapi-enrichment-job-content-type.md b/docs/solutions/cms/strapi-enrichment-job-content-type.md new file mode 100644 index 00000000..bfc37ac6 --- /dev/null +++ b/docs/solutions/cms/strapi-enrichment-job-content-type.md @@ -0,0 +1,40 @@ +--- +title: "Strapi EnrichmentJob content type for durable job state" +date: 2026-03-22 +category: cms +tags: [strapi, enrichment, jobs, content-type, graphql] +--- + +# Strapi EnrichmentJob Content Type + +## Problem + +The manager app stored enrichment job state in a file (`.data/jobs.json`) which was lost on every Railway deploy/restart. + +## Solution + +Created an `EnrichmentJob` collection type in Strapi with `draftAndPublish: false` (jobs are always "published") and an `enrichment.job-step` repeatable component for step-level tracking. + +### Key design decisions + +- **`draftAndPublish: false`** — Jobs don't need draft/published lifecycle. This avoids needing `status: "published"` on mutations. +- **No `source` field** — Unlike gateway-synced types, enrichment jobs are always manager-created. +- **Repeatable component for steps** — Strapi replaces the entire array on update (no patch-single-item). The state module does a read-then-write for `updateStepStatus`. +- **JSON fields for `artifacts`, `errors`, `languages`** — Flexible shape, no need for dedicated Strapi types. +- **`video` relation (manyToOne)** — Links to the gateway-synced Video for coverage overlay. + +### Gotcha: untyped operations before codegen + +After creating a new content type, `gql.tada` codegen must run to add it to the introspection types. Until then, use `gql` from `@apollo/client` for untyped operations. Apollo Client returns `unknown` for untyped query data — use `as any` with eslint-disable comments. + +### Read-then-write for step updates + +Strapi v5 has no way to patch a single item in a repeatable component array. To update one step, read the full job, mutate the step in the local array, then send the entire `steps` array back: + +```typescript +const job = await getJob(jobId) +const steps = job.steps.map((s) => + s.name === stepName ? { ...s, status: newStatus } : s, +) +await updateJob(jobId, { steps: toStepInput(steps) }) +``` diff --git a/docs/solutions/graphql/server-side-strapi-queries-nextjs.md b/docs/solutions/graphql/server-side-strapi-queries-nextjs.md new file mode 100644 index 00000000..8d612d44 --- /dev/null +++ b/docs/solutions/graphql/server-side-strapi-queries-nextjs.md @@ -0,0 +1,68 @@ +--- +title: "Server-side Strapi GraphQL queries in Next.js pages" +date: 2026-03-22 +category: graphql +tags: [strapi, graphql, nextjs, server-components, pagination] +--- + +# Server-Side Strapi GraphQL Queries in Next.js Pages + +## Problem + +Client components (LanguageGeoSelector, LiveJobsTable) need data from Strapi, but Strapi requires an API token that can't be exposed client-side. + +## Solution + +Fetch data server-side in Next.js page components (RSC) and pass as props to client components. No proxy API routes needed. + +### Pattern + +```typescript +// page.tsx (server component) +import { graphql } from "@forge/graphql" +import getClient from "@/cms/client" + +const GET_LANGUAGES = graphql(` + query GetLanguages($pagination: PaginationArg) { + languages(pagination: $pagination) { gatewayId, name } + } +`) + +export default async function Page() { + const client = getClient() + const { data } = await client.query({ + query: GET_LANGUAGES, + variables: { pagination: { pageSize: 100 } }, + fetchPolicy: "no-cache", + }) + + return +} +``` + +### Pagination with `_connection` queries + +Strapi v5 defaults `maxLimit` to 100 in `config/api.ts`. For server-to-server queries with large datasets (languages, countryLanguages), increase `api.rest.maxLimit` to 5000 so most collections fit in a single request. The pagination loop is still needed as a safety net: + +```typescript +async function fetchAllPages( + fetcher: (page: number) => Promise<{ nodes: T[]; pageInfo: PageInfo }>, +): Promise { + const all: T[] = [] + let page = 1 + while (true) { + const result = await fetcher(page) + all.push(...result.nodes) + if (page >= result.pageInfo.pageCount) break + page++ + } + return all +} +``` + +### Key points + +- Use `fetchPolicy: "no-cache"` — Apollo's `InMemoryCache` is stale across requests on the server +- Use `graphql()` from `@forge/graphql` for typed operations on existing types +- Use `gql` from `@apollo/client` for untyped operations on newly created types (before codegen) +- Graceful degradation: wrap in try/catch, render with empty data on failure diff --git a/packages/graphql/CLAUDE.md b/packages/graphql/CLAUDE.md index 1f642ead..26fcf9d4 100644 --- a/packages/graphql/CLAUDE.md +++ b/packages/graphql/CLAUDE.md @@ -12,9 +12,8 @@ This package provides gql.tada typed GraphQL operations generated from the Strap ## Conventions -- All queries, mutations, and fragments live in this package — not in the consuming apps. -- Organize by domain: `operations/videos.ts`, `operations/users.ts`, `fragments/media.ts`. -- Export typed operations and their result types for consumers. +- This package exports the typed `graphql()` function and introspection types — consuming apps define their own operations inline. +- Operations are defined in apps (e.g., `apps/web/src/lib/content.ts`, `apps/manager/src/app/dashboard/`) using `graphql()` from this package. - Run codegen after every Strapi content type change. - Commit generated type files — they are part of the contract. diff --git a/packages/graphql/src/graphql-env.d.ts b/packages/graphql/src/graphql-env.d.ts index 09d5f88f..7e246da2 100644 --- a/packages/graphql/src/graphql-env.d.ts +++ b/packages/graphql/src/graphql-env.d.ts @@ -25,6 +25,9 @@ export type introspection_types = { 'CloudflareR2FiltersInput': { kind: 'INPUT_OBJECT'; name: 'CloudflareR2FiltersInput'; isOneOf: false; inputFields: [{ name: 'and'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'CloudflareR2FiltersInput'; ofType: null; }; }; defaultValue: null }, { name: 'contentLength'; type: { kind: 'INPUT_OBJECT'; name: 'LongFilterInput'; ofType: null; }; defaultValue: null }, { name: 'contentType'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'createdAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'documentId'; type: { kind: 'INPUT_OBJECT'; name: 'IDFilterInput'; ofType: null; }; defaultValue: null }, { name: 'fileName'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'gatewayId'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'not'; type: { kind: 'INPUT_OBJECT'; name: 'CloudflareR2FiltersInput'; ofType: null; }; defaultValue: null }, { name: 'or'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'CloudflareR2FiltersInput'; ofType: null; }; }; defaultValue: null }, { name: 'originalFilename'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'publicUrl'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'publishedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'source'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'srtSubtitles'; type: { kind: 'INPUT_OBJECT'; name: 'VideoSubtitleFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'updatedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'variantAssets'; type: { kind: 'INPUT_OBJECT'; name: 'VideoVariantFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'vttSubtitles'; type: { kind: 'INPUT_OBJECT'; name: 'VideoSubtitleFiltersInput'; ofType: null; }; defaultValue: null }]; }; 'CloudflareR2Input': { kind: 'INPUT_OBJECT'; name: 'CloudflareR2Input'; isOneOf: false; inputFields: [{ name: 'contentLength'; type: { kind: 'SCALAR'; name: 'Long'; ofType: null; }; defaultValue: null }, { name: 'contentType'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'fileName'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'gatewayId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'originalFilename'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'publicUrl'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'publishedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; defaultValue: null }, { name: 'source'; type: { kind: 'ENUM'; name: 'ENUM_CLOUDFLARER2_SOURCE'; ofType: null; }; defaultValue: null }, { name: 'srtSubtitles'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'variantAssets'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'vttSubtitles'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }]; }; 'CloudflareR2RelationResponseCollection': { kind: 'OBJECT'; name: 'CloudflareR2RelationResponseCollection'; fields: { 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'CloudflareR2'; ofType: null; }; }; }; } }; }; }; + 'ComponentEnrichmentJobStep': { kind: 'OBJECT'; name: 'ComponentEnrichmentJobStep'; fields: { 'error': { name: 'error'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'finishedAt': { name: 'finishedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'name': { name: 'name'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'ENUM_COMPONENTENRICHMENTJOBSTEP_NAME'; ofType: null; }; } }; 'retries': { name: 'retries'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'startedAt': { name: 'startedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'status': { name: 'status'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'ENUM_COMPONENTENRICHMENTJOBSTEP_STATUS'; ofType: null; }; } }; }; }; + 'ComponentEnrichmentJobStepFiltersInput': { kind: 'INPUT_OBJECT'; name: 'ComponentEnrichmentJobStepFiltersInput'; isOneOf: false; inputFields: [{ name: 'and'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ComponentEnrichmentJobStepFiltersInput'; ofType: null; }; }; defaultValue: null }, { name: 'error'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'finishedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'name'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'not'; type: { kind: 'INPUT_OBJECT'; name: 'ComponentEnrichmentJobStepFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'or'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ComponentEnrichmentJobStepFiltersInput'; ofType: null; }; }; defaultValue: null }, { name: 'retries'; type: { kind: 'INPUT_OBJECT'; name: 'IntFilterInput'; ofType: null; }; defaultValue: null }, { name: 'startedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'status'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }]; }; + 'ComponentEnrichmentJobStepInput': { kind: 'INPUT_OBJECT'; name: 'ComponentEnrichmentJobStepInput'; isOneOf: false; inputFields: [{ name: 'error'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'finishedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; defaultValue: null }, { name: 'id'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'name'; type: { kind: 'ENUM'; name: 'ENUM_COMPONENTENRICHMENTJOBSTEP_NAME'; ofType: null; }; defaultValue: null }, { name: 'retries'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'startedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; defaultValue: null }, { name: 'status'; type: { kind: 'ENUM'; name: 'ENUM_COMPONENTENRICHMENTJOBSTEP_STATUS'; ofType: null; }; defaultValue: null }]; }; 'ComponentLanguageAudioPreview': { kind: 'OBJECT'; name: 'ComponentLanguageAudioPreview'; fields: { 'bitrate': { name: 'bitrate'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'codec': { name: 'codec'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'duration': { name: 'duration'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'size': { name: 'size'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'value': { name: 'value'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; }; }; 'ComponentLanguageAudioPreviewFiltersInput': { kind: 'INPUT_OBJECT'; name: 'ComponentLanguageAudioPreviewFiltersInput'; isOneOf: false; inputFields: [{ name: 'and'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ComponentLanguageAudioPreviewFiltersInput'; ofType: null; }; }; defaultValue: null }, { name: 'bitrate'; type: { kind: 'INPUT_OBJECT'; name: 'IntFilterInput'; ofType: null; }; defaultValue: null }, { name: 'codec'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'duration'; type: { kind: 'INPUT_OBJECT'; name: 'IntFilterInput'; ofType: null; }; defaultValue: null }, { name: 'not'; type: { kind: 'INPUT_OBJECT'; name: 'ComponentLanguageAudioPreviewFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'or'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ComponentLanguageAudioPreviewFiltersInput'; ofType: null; }; }; defaultValue: null }, { name: 'size'; type: { kind: 'INPUT_OBJECT'; name: 'IntFilterInput'; ofType: null; }; defaultValue: null }, { name: 'value'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }]; }; 'ComponentLanguageAudioPreviewInput': { kind: 'INPUT_OBJECT'; name: 'ComponentLanguageAudioPreviewInput'; isOneOf: false; inputFields: [{ name: 'bitrate'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'codec'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'duration'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'id'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'size'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'value'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }]; }; @@ -134,6 +137,8 @@ export type introspection_types = { 'ENUM_BIBLEBOOK_SOURCE': { name: 'ENUM_BIBLEBOOK_SOURCE'; enumValues: 'gateway' | 'manager'; }; 'ENUM_BIBLECITATION_SOURCE': { name: 'ENUM_BIBLECITATION_SOURCE'; enumValues: 'gateway' | 'manager'; }; 'ENUM_CLOUDFLARER2_SOURCE': { name: 'ENUM_CLOUDFLARER2_SOURCE'; enumValues: 'gateway' | 'manager'; }; + 'ENUM_COMPONENTENRICHMENTJOBSTEP_NAME': { name: 'ENUM_COMPONENTENRICHMENTJOBSTEP_NAME'; enumValues: 'chapters' | 'embeddings' | 'metadata' | 'transcription' | 'translation'; }; + 'ENUM_COMPONENTENRICHMENTJOBSTEP_STATUS': { name: 'ENUM_COMPONENTENRICHMENTJOBSTEP_STATUS'; enumValues: 'completed' | 'failed' | 'pending' | 'running' | 'skipped'; }; 'ENUM_COMPONENTSECTIONSCARD_VARIANT': { name: 'ENUM_COMPONENTSECTIONSCARD_VARIANT'; enumValues: 'default' | 'featured'; }; 'ENUM_COMPONENTSECTIONSCTA_VARIANT': { name: 'ENUM_COMPONENTSECTIONSCTA_VARIANT'; enumValues: 'primary' | 'secondary'; }; 'ENUM_COMPONENTSECTIONSMEDIACOLLECTION_VARIANT': { name: 'ENUM_COMPONENTSECTIONSMEDIACOLLECTION_VARIANT'; enumValues: 'carousel' | 'collection' | 'grid' | 'hero' | 'player'; }; @@ -144,6 +149,7 @@ export type introspection_types = { 'ENUM_CONTINENT_SOURCE': { name: 'ENUM_CONTINENT_SOURCE'; enumValues: 'gateway' | 'manager'; }; 'ENUM_COUNTRYLANGUAGE_SOURCE': { name: 'ENUM_COUNTRYLANGUAGE_SOURCE'; enumValues: 'gateway' | 'manager'; }; 'ENUM_COUNTRY_SOURCE': { name: 'ENUM_COUNTRY_SOURCE'; enumValues: 'gateway' | 'manager'; }; + 'ENUM_ENRICHMENTJOB_STATUS': { name: 'ENUM_ENRICHMENTJOB_STATUS'; enumValues: 'completed' | 'failed' | 'pending' | 'running'; }; 'ENUM_KEYWORD_SOURCE': { name: 'ENUM_KEYWORD_SOURCE'; enumValues: 'gateway' | 'manager'; }; 'ENUM_LANGUAGE_SOURCE': { name: 'ENUM_LANGUAGE_SOURCE'; enumValues: 'gateway' | 'manager'; }; 'ENUM_MUXVIDEO_SOURCE': { name: 'ENUM_MUXVIDEO_SOURCE'; enumValues: 'gateway' | 'manager'; }; @@ -155,6 +161,13 @@ export type introspection_types = { 'ENUM_VIDEO_LABEL': { name: 'ENUM_VIDEO_LABEL'; enumValues: 'behindTheScenes' | 'collection' | 'episode' | 'featureFilm' | 'segment' | 'series' | 'shortFilm' | 'trailer'; }; 'ENUM_VIDEO_SOURCE': { name: 'ENUM_VIDEO_SOURCE'; enumValues: 'gateway' | 'manager'; }; 'ENUM_VIDEO_VIDEOSOURCE': { name: 'ENUM_VIDEO_VIDEOSOURCE'; enumValues: 'cloudflare' | 'internal' | 'mux' | 'youTube'; }; + 'EnrichmentJob': { kind: 'OBJECT'; name: 'EnrichmentJob'; fields: { 'artifacts': { name: 'artifacts'; type: { kind: 'SCALAR'; name: 'JSON'; ofType: null; } }; 'completedAt': { name: 'completedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'createdAt': { name: 'createdAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'currentStep': { name: 'currentStep'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'documentId': { name: 'documentId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'errors': { name: 'errors'; type: { kind: 'SCALAR'; name: 'JSON'; ofType: null; } }; 'languages': { name: 'languages'; type: { kind: 'SCALAR'; name: 'JSON'; ofType: null; } }; 'muxAssetId': { name: 'muxAssetId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'muxPlaybackId': { name: 'muxPlaybackId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'publishedAt': { name: 'publishedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'retries': { name: 'retries'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'startedAt': { name: 'startedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'status': { name: 'status'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'ENUM_ENRICHMENTJOB_STATUS'; ofType: null; }; } }; 'steps': { name: 'steps'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'ComponentEnrichmentJobStep'; ofType: null; }; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'video': { name: 'video'; type: { kind: 'OBJECT'; name: 'Video'; ofType: null; } }; }; }; + 'EnrichmentJobEntity': { kind: 'OBJECT'; name: 'EnrichmentJobEntity'; fields: { 'attributes': { name: 'attributes'; type: { kind: 'OBJECT'; name: 'EnrichmentJob'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; } }; }; }; + 'EnrichmentJobEntityResponse': { kind: 'OBJECT'; name: 'EnrichmentJobEntityResponse'; fields: { 'data': { name: 'data'; type: { kind: 'OBJECT'; name: 'EnrichmentJob'; ofType: null; } }; }; }; + 'EnrichmentJobEntityResponseCollection': { kind: 'OBJECT'; name: 'EnrichmentJobEntityResponseCollection'; fields: { 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'EnrichmentJob'; ofType: null; }; }; }; } }; 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Pagination'; ofType: null; }; } }; }; }; + 'EnrichmentJobFiltersInput': { kind: 'INPUT_OBJECT'; name: 'EnrichmentJobFiltersInput'; isOneOf: false; inputFields: [{ name: 'and'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'EnrichmentJobFiltersInput'; ofType: null; }; }; defaultValue: null }, { name: 'artifacts'; type: { kind: 'INPUT_OBJECT'; name: 'JSONFilterInput'; ofType: null; }; defaultValue: null }, { name: 'completedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'createdAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'currentStep'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'documentId'; type: { kind: 'INPUT_OBJECT'; name: 'IDFilterInput'; ofType: null; }; defaultValue: null }, { name: 'errors'; type: { kind: 'INPUT_OBJECT'; name: 'JSONFilterInput'; ofType: null; }; defaultValue: null }, { name: 'languages'; type: { kind: 'INPUT_OBJECT'; name: 'JSONFilterInput'; ofType: null; }; defaultValue: null }, { name: 'muxAssetId'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'muxPlaybackId'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'not'; type: { kind: 'INPUT_OBJECT'; name: 'EnrichmentJobFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'or'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'EnrichmentJobFiltersInput'; ofType: null; }; }; defaultValue: null }, { name: 'publishedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'retries'; type: { kind: 'INPUT_OBJECT'; name: 'IntFilterInput'; ofType: null; }; defaultValue: null }, { name: 'startedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'status'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'steps'; type: { kind: 'INPUT_OBJECT'; name: 'ComponentEnrichmentJobStepFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'updatedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'video'; type: { kind: 'INPUT_OBJECT'; name: 'VideoFiltersInput'; ofType: null; }; defaultValue: null }]; }; + 'EnrichmentJobInput': { kind: 'INPUT_OBJECT'; name: 'EnrichmentJobInput'; isOneOf: false; inputFields: [{ name: 'artifacts'; type: { kind: 'SCALAR'; name: 'JSON'; ofType: null; }; defaultValue: null }, { name: 'completedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; defaultValue: null }, { name: 'currentStep'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'errors'; type: { kind: 'SCALAR'; name: 'JSON'; ofType: null; }; defaultValue: null }, { name: 'languages'; type: { kind: 'SCALAR'; name: 'JSON'; ofType: null; }; defaultValue: null }, { name: 'muxAssetId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'muxPlaybackId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'publishedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; defaultValue: null }, { name: 'retries'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'startedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; defaultValue: null }, { name: 'status'; type: { kind: 'ENUM'; name: 'ENUM_ENRICHMENTJOB_STATUS'; ofType: null; }; defaultValue: null }, { name: 'steps'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ComponentEnrichmentJobStepInput'; ofType: null; }; }; defaultValue: null }, { name: 'video'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }]; }; + 'EnrichmentJobRelationResponseCollection': { kind: 'OBJECT'; name: 'EnrichmentJobRelationResponseCollection'; fields: { 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'EnrichmentJob'; ofType: null; }; }; }; } }; }; }; 'Error': { kind: 'OBJECT'; name: 'Error'; fields: { 'code': { name: 'code'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'message': { name: 'message'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; }; }; 'Experience': { kind: 'OBJECT'; name: 'Experience'; fields: { 'blocks': { name: 'blocks'; type: { kind: 'LIST'; name: never; ofType: { kind: 'UNION'; name: 'ExperienceBlocksDynamicZone'; ofType: null; }; } }; 'createdAt': { name: 'createdAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'documentId': { name: 'documentId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'isHomepage': { name: 'isHomepage'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; } }; 'locale': { name: 'locale'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'localizations': { name: 'localizations'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Experience'; ofType: null; }; }; } }; 'localizations_connection': { name: 'localizations_connection'; type: { kind: 'OBJECT'; name: 'ExperienceRelationResponseCollection'; ofType: null; } }; 'metaDescription': { name: 'metaDescription'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'ogDescription': { name: 'ogDescription'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'ogImage': { name: 'ogImage'; type: { kind: 'OBJECT'; name: 'UploadFile'; ofType: null; } }; 'ogTitle': { name: 'ogTitle'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'pathSegment': { name: 'pathSegment'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'publishedAt': { name: 'publishedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'slug': { name: 'slug'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'title': { name: 'title'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; }; }; 'ExperienceBlocksDynamicZone': { kind: 'UNION'; name: 'ExperienceBlocksDynamicZone'; fields: {}; possibleTypes: 'ComponentSectionsBibleQuotesCarousel' | 'ComponentSectionsCard' | 'ComponentSectionsContainer' | 'ComponentSectionsCta' | 'ComponentSectionsEasterDates' | 'ComponentSectionsInfoBlocks' | 'ComponentSectionsMediaCollection' | 'ComponentSectionsNavigationCarousel' | 'ComponentSectionsPromoBanner' | 'ComponentSectionsRelatedQuestions' | 'ComponentSectionsSection' | 'ComponentSectionsText' | 'ComponentSectionsVideo' | 'ComponentSectionsVideoCarousel' | 'ComponentSectionsVideoHero' | 'Error'; }; @@ -168,7 +181,7 @@ export type introspection_types = { 'FileInfoInput': { kind: 'INPUT_OBJECT'; name: 'FileInfoInput'; isOneOf: false; inputFields: [{ name: 'alternativeText'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'caption'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'name'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }]; }; 'Float': unknown; 'FloatFilterInput': { kind: 'INPUT_OBJECT'; name: 'FloatFilterInput'; isOneOf: false; inputFields: [{ name: 'and'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'Float'; ofType: null; }; }; defaultValue: null }, { name: 'between'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'Float'; ofType: null; }; }; defaultValue: null }, { name: 'contains'; type: { kind: 'SCALAR'; name: 'Float'; ofType: null; }; defaultValue: null }, { name: 'containsi'; type: { kind: 'SCALAR'; name: 'Float'; ofType: null; }; defaultValue: null }, { name: 'endsWith'; type: { kind: 'SCALAR'; name: 'Float'; ofType: null; }; defaultValue: null }, { name: 'eq'; type: { kind: 'SCALAR'; name: 'Float'; ofType: null; }; defaultValue: null }, { name: 'eqi'; type: { kind: 'SCALAR'; name: 'Float'; ofType: null; }; defaultValue: null }, { name: 'gt'; type: { kind: 'SCALAR'; name: 'Float'; ofType: null; }; defaultValue: null }, { name: 'gte'; type: { kind: 'SCALAR'; name: 'Float'; ofType: null; }; defaultValue: null }, { name: 'in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'Float'; ofType: null; }; }; defaultValue: null }, { name: 'lt'; type: { kind: 'SCALAR'; name: 'Float'; ofType: null; }; defaultValue: null }, { name: 'lte'; type: { kind: 'SCALAR'; name: 'Float'; ofType: null; }; defaultValue: null }, { name: 'ne'; type: { kind: 'SCALAR'; name: 'Float'; ofType: null; }; defaultValue: null }, { name: 'nei'; type: { kind: 'SCALAR'; name: 'Float'; ofType: null; }; defaultValue: null }, { name: 'not'; type: { kind: 'INPUT_OBJECT'; name: 'FloatFilterInput'; ofType: null; }; defaultValue: null }, { name: 'notContains'; type: { kind: 'SCALAR'; name: 'Float'; ofType: null; }; defaultValue: null }, { name: 'notContainsi'; type: { kind: 'SCALAR'; name: 'Float'; ofType: null; }; defaultValue: null }, { name: 'notIn'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'Float'; ofType: null; }; }; defaultValue: null }, { name: 'notNull'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'null'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'or'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'Float'; ofType: null; }; }; defaultValue: null }, { name: 'startsWith'; type: { kind: 'SCALAR'; name: 'Float'; ofType: null; }; defaultValue: null }]; }; - 'GenericMorph': { kind: 'UNION'; name: 'GenericMorph'; fields: {}; possibleTypes: 'BibleBook' | 'BibleCitation' | 'CloudflareR2' | 'ComponentLanguageAudioPreview' | 'ComponentSectionsBibleQuoteItem' | 'ComponentSectionsBibleQuotesCarousel' | 'ComponentSectionsCard' | 'ComponentSectionsContainer' | 'ComponentSectionsContainerSlot' | 'ComponentSectionsCta' | 'ComponentSectionsEasterDates' | 'ComponentSectionsInfoBlock' | 'ComponentSectionsInfoBlocks' | 'ComponentSectionsMediaCollection' | 'ComponentSectionsMediaCollectionItem' | 'ComponentSectionsNavigationCarousel' | 'ComponentSectionsNavigationCarouselItem' | 'ComponentSectionsPromoBanner' | 'ComponentSectionsQuizButton' | 'ComponentSectionsRelatedQuestionItem' | 'ComponentSectionsRelatedQuestions' | 'ComponentSectionsSection' | 'ComponentSectionsText' | 'ComponentSectionsVideo' | 'ComponentSectionsVideoCarousel' | 'ComponentSectionsVideoCarouselItem' | 'ComponentSectionsVideoHero' | 'ComponentVideoCloudflareImage' | 'ComponentVideoVariantDownload' | 'Continent' | 'Country' | 'CountryLanguage' | 'Experience' | 'I18NLocale' | 'Keyword' | 'Language' | 'MuxVideo' | 'ReviewWorkflowsWorkflow' | 'ReviewWorkflowsWorkflowStage' | 'UploadFile' | 'UsersPermissionsPermission' | 'UsersPermissionsRole' | 'UsersPermissionsUser' | 'Video' | 'VideoEdition' | 'VideoOrigin' | 'VideoStudyQuestion' | 'VideoSubtitle' | 'VideoVariant'; }; + 'GenericMorph': { kind: 'UNION'; name: 'GenericMorph'; fields: {}; possibleTypes: 'BibleBook' | 'BibleCitation' | 'CloudflareR2' | 'ComponentEnrichmentJobStep' | 'ComponentLanguageAudioPreview' | 'ComponentSectionsBibleQuoteItem' | 'ComponentSectionsBibleQuotesCarousel' | 'ComponentSectionsCard' | 'ComponentSectionsContainer' | 'ComponentSectionsContainerSlot' | 'ComponentSectionsCta' | 'ComponentSectionsEasterDates' | 'ComponentSectionsInfoBlock' | 'ComponentSectionsInfoBlocks' | 'ComponentSectionsMediaCollection' | 'ComponentSectionsMediaCollectionItem' | 'ComponentSectionsNavigationCarousel' | 'ComponentSectionsNavigationCarouselItem' | 'ComponentSectionsPromoBanner' | 'ComponentSectionsQuizButton' | 'ComponentSectionsRelatedQuestionItem' | 'ComponentSectionsRelatedQuestions' | 'ComponentSectionsSection' | 'ComponentSectionsText' | 'ComponentSectionsVideo' | 'ComponentSectionsVideoCarousel' | 'ComponentSectionsVideoCarouselItem' | 'ComponentSectionsVideoHero' | 'ComponentVideoCloudflareImage' | 'ComponentVideoVariantDownload' | 'Continent' | 'Country' | 'CountryLanguage' | 'EnrichmentJob' | 'Experience' | 'I18NLocale' | 'Keyword' | 'Language' | 'MuxVideo' | 'ReviewWorkflowsWorkflow' | 'ReviewWorkflowsWorkflowStage' | 'UploadFile' | 'UsersPermissionsPermission' | 'UsersPermissionsRole' | 'UsersPermissionsUser' | 'Video' | 'VideoEdition' | 'VideoOrigin' | 'VideoStudyQuestion' | 'VideoSubtitle' | 'VideoVariant'; }; 'I18NLocale': { kind: 'OBJECT'; name: 'I18NLocale'; fields: { 'code': { name: 'code'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'createdAt': { name: 'createdAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'documentId': { name: 'documentId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'name': { name: 'name'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'publishedAt': { name: 'publishedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; }; }; 'I18NLocaleCode': unknown; 'I18NLocaleEntity': { kind: 'OBJECT'; name: 'I18NLocaleEntity'; fields: { 'attributes': { name: 'attributes'; type: { kind: 'OBJECT'; name: 'I18NLocale'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; } }; }; }; @@ -199,7 +212,7 @@ export type introspection_types = { 'LanguageRelationResponseCollection': { kind: 'OBJECT'; name: 'LanguageRelationResponseCollection'; fields: { 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Language'; ofType: null; }; }; }; } }; }; }; 'Long': unknown; 'LongFilterInput': { kind: 'INPUT_OBJECT'; name: 'LongFilterInput'; isOneOf: false; inputFields: [{ name: 'and'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'Long'; ofType: null; }; }; defaultValue: null }, { name: 'between'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'Long'; ofType: null; }; }; defaultValue: null }, { name: 'contains'; type: { kind: 'SCALAR'; name: 'Long'; ofType: null; }; defaultValue: null }, { name: 'containsi'; type: { kind: 'SCALAR'; name: 'Long'; ofType: null; }; defaultValue: null }, { name: 'endsWith'; type: { kind: 'SCALAR'; name: 'Long'; ofType: null; }; defaultValue: null }, { name: 'eq'; type: { kind: 'SCALAR'; name: 'Long'; ofType: null; }; defaultValue: null }, { name: 'eqi'; type: { kind: 'SCALAR'; name: 'Long'; ofType: null; }; defaultValue: null }, { name: 'gt'; type: { kind: 'SCALAR'; name: 'Long'; ofType: null; }; defaultValue: null }, { name: 'gte'; type: { kind: 'SCALAR'; name: 'Long'; ofType: null; }; defaultValue: null }, { name: 'in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'Long'; ofType: null; }; }; defaultValue: null }, { name: 'lt'; type: { kind: 'SCALAR'; name: 'Long'; ofType: null; }; defaultValue: null }, { name: 'lte'; type: { kind: 'SCALAR'; name: 'Long'; ofType: null; }; defaultValue: null }, { name: 'ne'; type: { kind: 'SCALAR'; name: 'Long'; ofType: null; }; defaultValue: null }, { name: 'nei'; type: { kind: 'SCALAR'; name: 'Long'; ofType: null; }; defaultValue: null }, { name: 'not'; type: { kind: 'INPUT_OBJECT'; name: 'LongFilterInput'; ofType: null; }; defaultValue: null }, { name: 'notContains'; type: { kind: 'SCALAR'; name: 'Long'; ofType: null; }; defaultValue: null }, { name: 'notContainsi'; type: { kind: 'SCALAR'; name: 'Long'; ofType: null; }; defaultValue: null }, { name: 'notIn'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'Long'; ofType: null; }; }; defaultValue: null }, { name: 'notNull'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'null'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'or'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'Long'; ofType: null; }; }; defaultValue: null }, { name: 'startsWith'; type: { kind: 'SCALAR'; name: 'Long'; ofType: null; }; defaultValue: null }]; }; - 'Mutation': { kind: 'OBJECT'; name: 'Mutation'; fields: { 'changePassword': { name: 'changePassword'; type: { kind: 'OBJECT'; name: 'UsersPermissionsLoginPayload'; ofType: null; } }; 'createBibleBook': { name: 'createBibleBook'; type: { kind: 'OBJECT'; name: 'BibleBook'; ofType: null; } }; 'createBibleCitation': { name: 'createBibleCitation'; type: { kind: 'OBJECT'; name: 'BibleCitation'; ofType: null; } }; 'createCloudflareR2': { name: 'createCloudflareR2'; type: { kind: 'OBJECT'; name: 'CloudflareR2'; ofType: null; } }; 'createContinent': { name: 'createContinent'; type: { kind: 'OBJECT'; name: 'Continent'; ofType: null; } }; 'createCountry': { name: 'createCountry'; type: { kind: 'OBJECT'; name: 'Country'; ofType: null; } }; 'createCountryLanguage': { name: 'createCountryLanguage'; type: { kind: 'OBJECT'; name: 'CountryLanguage'; ofType: null; } }; 'createExperience': { name: 'createExperience'; type: { kind: 'OBJECT'; name: 'Experience'; ofType: null; } }; 'createKeyword': { name: 'createKeyword'; type: { kind: 'OBJECT'; name: 'Keyword'; ofType: null; } }; 'createLanguage': { name: 'createLanguage'; type: { kind: 'OBJECT'; name: 'Language'; ofType: null; } }; 'createMuxVideo': { name: 'createMuxVideo'; type: { kind: 'OBJECT'; name: 'MuxVideo'; ofType: null; } }; 'createReviewWorkflowsWorkflow': { name: 'createReviewWorkflowsWorkflow'; type: { kind: 'OBJECT'; name: 'ReviewWorkflowsWorkflow'; ofType: null; } }; 'createReviewWorkflowsWorkflowStage': { name: 'createReviewWorkflowsWorkflowStage'; type: { kind: 'OBJECT'; name: 'ReviewWorkflowsWorkflowStage'; ofType: null; } }; 'createUsersPermissionsRole': { name: 'createUsersPermissionsRole'; type: { kind: 'OBJECT'; name: 'UsersPermissionsCreateRolePayload'; ofType: null; } }; 'createUsersPermissionsUser': { name: 'createUsersPermissionsUser'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'UsersPermissionsUserEntityResponse'; ofType: null; }; } }; 'createVideo': { name: 'createVideo'; type: { kind: 'OBJECT'; name: 'Video'; ofType: null; } }; 'createVideoEdition': { name: 'createVideoEdition'; type: { kind: 'OBJECT'; name: 'VideoEdition'; ofType: null; } }; 'createVideoOrigin': { name: 'createVideoOrigin'; type: { kind: 'OBJECT'; name: 'VideoOrigin'; ofType: null; } }; 'createVideoStudyQuestion': { name: 'createVideoStudyQuestion'; type: { kind: 'OBJECT'; name: 'VideoStudyQuestion'; ofType: null; } }; 'createVideoSubtitle': { name: 'createVideoSubtitle'; type: { kind: 'OBJECT'; name: 'VideoSubtitle'; ofType: null; } }; 'createVideoVariant': { name: 'createVideoVariant'; type: { kind: 'OBJECT'; name: 'VideoVariant'; ofType: null; } }; 'deleteBibleBook': { name: 'deleteBibleBook'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteBibleCitation': { name: 'deleteBibleCitation'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteCloudflareR2': { name: 'deleteCloudflareR2'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteContinent': { name: 'deleteContinent'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteCountry': { name: 'deleteCountry'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteCountryLanguage': { name: 'deleteCountryLanguage'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteExperience': { name: 'deleteExperience'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteKeyword': { name: 'deleteKeyword'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteLanguage': { name: 'deleteLanguage'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteMuxVideo': { name: 'deleteMuxVideo'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteReviewWorkflowsWorkflow': { name: 'deleteReviewWorkflowsWorkflow'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteReviewWorkflowsWorkflowStage': { name: 'deleteReviewWorkflowsWorkflowStage'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteUploadFile': { name: 'deleteUploadFile'; type: { kind: 'OBJECT'; name: 'UploadFile'; ofType: null; } }; 'deleteUsersPermissionsRole': { name: 'deleteUsersPermissionsRole'; type: { kind: 'OBJECT'; name: 'UsersPermissionsDeleteRolePayload'; ofType: null; } }; 'deleteUsersPermissionsUser': { name: 'deleteUsersPermissionsUser'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'UsersPermissionsUserEntityResponse'; ofType: null; }; } }; 'deleteVideo': { name: 'deleteVideo'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteVideoEdition': { name: 'deleteVideoEdition'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteVideoOrigin': { name: 'deleteVideoOrigin'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteVideoStudyQuestion': { name: 'deleteVideoStudyQuestion'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteVideoSubtitle': { name: 'deleteVideoSubtitle'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteVideoVariant': { name: 'deleteVideoVariant'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'emailConfirmation': { name: 'emailConfirmation'; type: { kind: 'OBJECT'; name: 'UsersPermissionsLoginPayload'; ofType: null; } }; 'forgotPassword': { name: 'forgotPassword'; type: { kind: 'OBJECT'; name: 'UsersPermissionsPasswordPayload'; ofType: null; } }; 'login': { name: 'login'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'UsersPermissionsLoginPayload'; ofType: null; }; } }; 'register': { name: 'register'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'UsersPermissionsLoginPayload'; ofType: null; }; } }; 'resetPassword': { name: 'resetPassword'; type: { kind: 'OBJECT'; name: 'UsersPermissionsLoginPayload'; ofType: null; } }; 'updateBibleBook': { name: 'updateBibleBook'; type: { kind: 'OBJECT'; name: 'BibleBook'; ofType: null; } }; 'updateBibleCitation': { name: 'updateBibleCitation'; type: { kind: 'OBJECT'; name: 'BibleCitation'; ofType: null; } }; 'updateCloudflareR2': { name: 'updateCloudflareR2'; type: { kind: 'OBJECT'; name: 'CloudflareR2'; ofType: null; } }; 'updateContinent': { name: 'updateContinent'; type: { kind: 'OBJECT'; name: 'Continent'; ofType: null; } }; 'updateCountry': { name: 'updateCountry'; type: { kind: 'OBJECT'; name: 'Country'; ofType: null; } }; 'updateCountryLanguage': { name: 'updateCountryLanguage'; type: { kind: 'OBJECT'; name: 'CountryLanguage'; ofType: null; } }; 'updateExperience': { name: 'updateExperience'; type: { kind: 'OBJECT'; name: 'Experience'; ofType: null; } }; 'updateKeyword': { name: 'updateKeyword'; type: { kind: 'OBJECT'; name: 'Keyword'; ofType: null; } }; 'updateLanguage': { name: 'updateLanguage'; type: { kind: 'OBJECT'; name: 'Language'; ofType: null; } }; 'updateMuxVideo': { name: 'updateMuxVideo'; type: { kind: 'OBJECT'; name: 'MuxVideo'; ofType: null; } }; 'updateReviewWorkflowsWorkflow': { name: 'updateReviewWorkflowsWorkflow'; type: { kind: 'OBJECT'; name: 'ReviewWorkflowsWorkflow'; ofType: null; } }; 'updateReviewWorkflowsWorkflowStage': { name: 'updateReviewWorkflowsWorkflowStage'; type: { kind: 'OBJECT'; name: 'ReviewWorkflowsWorkflowStage'; ofType: null; } }; 'updateUploadFile': { name: 'updateUploadFile'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'UploadFile'; ofType: null; }; } }; 'updateUsersPermissionsRole': { name: 'updateUsersPermissionsRole'; type: { kind: 'OBJECT'; name: 'UsersPermissionsUpdateRolePayload'; ofType: null; } }; 'updateUsersPermissionsUser': { name: 'updateUsersPermissionsUser'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'UsersPermissionsUserEntityResponse'; ofType: null; }; } }; 'updateVideo': { name: 'updateVideo'; type: { kind: 'OBJECT'; name: 'Video'; ofType: null; } }; 'updateVideoEdition': { name: 'updateVideoEdition'; type: { kind: 'OBJECT'; name: 'VideoEdition'; ofType: null; } }; 'updateVideoOrigin': { name: 'updateVideoOrigin'; type: { kind: 'OBJECT'; name: 'VideoOrigin'; ofType: null; } }; 'updateVideoStudyQuestion': { name: 'updateVideoStudyQuestion'; type: { kind: 'OBJECT'; name: 'VideoStudyQuestion'; ofType: null; } }; 'updateVideoSubtitle': { name: 'updateVideoSubtitle'; type: { kind: 'OBJECT'; name: 'VideoSubtitle'; ofType: null; } }; 'updateVideoVariant': { name: 'updateVideoVariant'; type: { kind: 'OBJECT'; name: 'VideoVariant'; ofType: null; } }; }; }; + 'Mutation': { kind: 'OBJECT'; name: 'Mutation'; fields: { 'changePassword': { name: 'changePassword'; type: { kind: 'OBJECT'; name: 'UsersPermissionsLoginPayload'; ofType: null; } }; 'createBibleBook': { name: 'createBibleBook'; type: { kind: 'OBJECT'; name: 'BibleBook'; ofType: null; } }; 'createBibleCitation': { name: 'createBibleCitation'; type: { kind: 'OBJECT'; name: 'BibleCitation'; ofType: null; } }; 'createCloudflareR2': { name: 'createCloudflareR2'; type: { kind: 'OBJECT'; name: 'CloudflareR2'; ofType: null; } }; 'createContinent': { name: 'createContinent'; type: { kind: 'OBJECT'; name: 'Continent'; ofType: null; } }; 'createCountry': { name: 'createCountry'; type: { kind: 'OBJECT'; name: 'Country'; ofType: null; } }; 'createCountryLanguage': { name: 'createCountryLanguage'; type: { kind: 'OBJECT'; name: 'CountryLanguage'; ofType: null; } }; 'createEnrichmentJob': { name: 'createEnrichmentJob'; type: { kind: 'OBJECT'; name: 'EnrichmentJob'; ofType: null; } }; 'createExperience': { name: 'createExperience'; type: { kind: 'OBJECT'; name: 'Experience'; ofType: null; } }; 'createKeyword': { name: 'createKeyword'; type: { kind: 'OBJECT'; name: 'Keyword'; ofType: null; } }; 'createLanguage': { name: 'createLanguage'; type: { kind: 'OBJECT'; name: 'Language'; ofType: null; } }; 'createMuxVideo': { name: 'createMuxVideo'; type: { kind: 'OBJECT'; name: 'MuxVideo'; ofType: null; } }; 'createReviewWorkflowsWorkflow': { name: 'createReviewWorkflowsWorkflow'; type: { kind: 'OBJECT'; name: 'ReviewWorkflowsWorkflow'; ofType: null; } }; 'createReviewWorkflowsWorkflowStage': { name: 'createReviewWorkflowsWorkflowStage'; type: { kind: 'OBJECT'; name: 'ReviewWorkflowsWorkflowStage'; ofType: null; } }; 'createUsersPermissionsRole': { name: 'createUsersPermissionsRole'; type: { kind: 'OBJECT'; name: 'UsersPermissionsCreateRolePayload'; ofType: null; } }; 'createUsersPermissionsUser': { name: 'createUsersPermissionsUser'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'UsersPermissionsUserEntityResponse'; ofType: null; }; } }; 'createVideo': { name: 'createVideo'; type: { kind: 'OBJECT'; name: 'Video'; ofType: null; } }; 'createVideoEdition': { name: 'createVideoEdition'; type: { kind: 'OBJECT'; name: 'VideoEdition'; ofType: null; } }; 'createVideoOrigin': { name: 'createVideoOrigin'; type: { kind: 'OBJECT'; name: 'VideoOrigin'; ofType: null; } }; 'createVideoStudyQuestion': { name: 'createVideoStudyQuestion'; type: { kind: 'OBJECT'; name: 'VideoStudyQuestion'; ofType: null; } }; 'createVideoSubtitle': { name: 'createVideoSubtitle'; type: { kind: 'OBJECT'; name: 'VideoSubtitle'; ofType: null; } }; 'createVideoVariant': { name: 'createVideoVariant'; type: { kind: 'OBJECT'; name: 'VideoVariant'; ofType: null; } }; 'deleteBibleBook': { name: 'deleteBibleBook'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteBibleCitation': { name: 'deleteBibleCitation'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteCloudflareR2': { name: 'deleteCloudflareR2'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteContinent': { name: 'deleteContinent'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteCountry': { name: 'deleteCountry'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteCountryLanguage': { name: 'deleteCountryLanguage'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteEnrichmentJob': { name: 'deleteEnrichmentJob'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteExperience': { name: 'deleteExperience'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteKeyword': { name: 'deleteKeyword'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteLanguage': { name: 'deleteLanguage'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteMuxVideo': { name: 'deleteMuxVideo'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteReviewWorkflowsWorkflow': { name: 'deleteReviewWorkflowsWorkflow'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteReviewWorkflowsWorkflowStage': { name: 'deleteReviewWorkflowsWorkflowStage'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteUploadFile': { name: 'deleteUploadFile'; type: { kind: 'OBJECT'; name: 'UploadFile'; ofType: null; } }; 'deleteUsersPermissionsRole': { name: 'deleteUsersPermissionsRole'; type: { kind: 'OBJECT'; name: 'UsersPermissionsDeleteRolePayload'; ofType: null; } }; 'deleteUsersPermissionsUser': { name: 'deleteUsersPermissionsUser'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'UsersPermissionsUserEntityResponse'; ofType: null; }; } }; 'deleteVideo': { name: 'deleteVideo'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteVideoEdition': { name: 'deleteVideoEdition'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteVideoOrigin': { name: 'deleteVideoOrigin'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteVideoStudyQuestion': { name: 'deleteVideoStudyQuestion'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteVideoSubtitle': { name: 'deleteVideoSubtitle'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'deleteVideoVariant': { name: 'deleteVideoVariant'; type: { kind: 'OBJECT'; name: 'DeleteMutationResponse'; ofType: null; } }; 'emailConfirmation': { name: 'emailConfirmation'; type: { kind: 'OBJECT'; name: 'UsersPermissionsLoginPayload'; ofType: null; } }; 'forgotPassword': { name: 'forgotPassword'; type: { kind: 'OBJECT'; name: 'UsersPermissionsPasswordPayload'; ofType: null; } }; 'login': { name: 'login'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'UsersPermissionsLoginPayload'; ofType: null; }; } }; 'register': { name: 'register'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'UsersPermissionsLoginPayload'; ofType: null; }; } }; 'resetPassword': { name: 'resetPassword'; type: { kind: 'OBJECT'; name: 'UsersPermissionsLoginPayload'; ofType: null; } }; 'updateBibleBook': { name: 'updateBibleBook'; type: { kind: 'OBJECT'; name: 'BibleBook'; ofType: null; } }; 'updateBibleCitation': { name: 'updateBibleCitation'; type: { kind: 'OBJECT'; name: 'BibleCitation'; ofType: null; } }; 'updateCloudflareR2': { name: 'updateCloudflareR2'; type: { kind: 'OBJECT'; name: 'CloudflareR2'; ofType: null; } }; 'updateContinent': { name: 'updateContinent'; type: { kind: 'OBJECT'; name: 'Continent'; ofType: null; } }; 'updateCountry': { name: 'updateCountry'; type: { kind: 'OBJECT'; name: 'Country'; ofType: null; } }; 'updateCountryLanguage': { name: 'updateCountryLanguage'; type: { kind: 'OBJECT'; name: 'CountryLanguage'; ofType: null; } }; 'updateEnrichmentJob': { name: 'updateEnrichmentJob'; type: { kind: 'OBJECT'; name: 'EnrichmentJob'; ofType: null; } }; 'updateExperience': { name: 'updateExperience'; type: { kind: 'OBJECT'; name: 'Experience'; ofType: null; } }; 'updateKeyword': { name: 'updateKeyword'; type: { kind: 'OBJECT'; name: 'Keyword'; ofType: null; } }; 'updateLanguage': { name: 'updateLanguage'; type: { kind: 'OBJECT'; name: 'Language'; ofType: null; } }; 'updateMuxVideo': { name: 'updateMuxVideo'; type: { kind: 'OBJECT'; name: 'MuxVideo'; ofType: null; } }; 'updateReviewWorkflowsWorkflow': { name: 'updateReviewWorkflowsWorkflow'; type: { kind: 'OBJECT'; name: 'ReviewWorkflowsWorkflow'; ofType: null; } }; 'updateReviewWorkflowsWorkflowStage': { name: 'updateReviewWorkflowsWorkflowStage'; type: { kind: 'OBJECT'; name: 'ReviewWorkflowsWorkflowStage'; ofType: null; } }; 'updateUploadFile': { name: 'updateUploadFile'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'UploadFile'; ofType: null; }; } }; 'updateUsersPermissionsRole': { name: 'updateUsersPermissionsRole'; type: { kind: 'OBJECT'; name: 'UsersPermissionsUpdateRolePayload'; ofType: null; } }; 'updateUsersPermissionsUser': { name: 'updateUsersPermissionsUser'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'UsersPermissionsUserEntityResponse'; ofType: null; }; } }; 'updateVideo': { name: 'updateVideo'; type: { kind: 'OBJECT'; name: 'Video'; ofType: null; } }; 'updateVideoEdition': { name: 'updateVideoEdition'; type: { kind: 'OBJECT'; name: 'VideoEdition'; ofType: null; } }; 'updateVideoOrigin': { name: 'updateVideoOrigin'; type: { kind: 'OBJECT'; name: 'VideoOrigin'; ofType: null; } }; 'updateVideoStudyQuestion': { name: 'updateVideoStudyQuestion'; type: { kind: 'OBJECT'; name: 'VideoStudyQuestion'; ofType: null; } }; 'updateVideoSubtitle': { name: 'updateVideoSubtitle'; type: { kind: 'OBJECT'; name: 'VideoSubtitle'; ofType: null; } }; 'updateVideoVariant': { name: 'updateVideoVariant'; type: { kind: 'OBJECT'; name: 'VideoVariant'; ofType: null; } }; }; }; 'MuxVideo': { kind: 'OBJECT'; name: 'MuxVideo'; fields: { 'assetId': { name: 'assetId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'createdAt': { name: 'createdAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'documentId': { name: 'documentId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'downloadable': { name: 'downloadable'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; } }; 'duration': { name: 'duration'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'gatewayId': { name: 'gatewayId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'playbackId': { name: 'playbackId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'primaryLanguageId': { name: 'primaryLanguageId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'publishedAt': { name: 'publishedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'readyToStream': { name: 'readyToStream'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; } }; 'source': { name: 'source'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'ENUM_MUXVIDEO_SOURCE'; ofType: null; }; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'variants': { name: 'variants'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'VideoVariant'; ofType: null; }; }; } }; 'variants_connection': { name: 'variants_connection'; type: { kind: 'OBJECT'; name: 'VideoVariantRelationResponseCollection'; ofType: null; } }; }; }; 'MuxVideoEntity': { kind: 'OBJECT'; name: 'MuxVideoEntity'; fields: { 'attributes': { name: 'attributes'; type: { kind: 'OBJECT'; name: 'MuxVideo'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; } }; }; }; 'MuxVideoEntityResponse': { kind: 'OBJECT'; name: 'MuxVideoEntityResponse'; fields: { 'data': { name: 'data'; type: { kind: 'OBJECT'; name: 'MuxVideo'; ofType: null; } }; }; }; @@ -210,7 +223,7 @@ export type introspection_types = { 'Pagination': { kind: 'OBJECT'; name: 'Pagination'; fields: { 'page': { name: 'page'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'pageCount': { name: 'pageCount'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'pageSize': { name: 'pageSize'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'total': { name: 'total'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; 'PaginationArg': { kind: 'INPUT_OBJECT'; name: 'PaginationArg'; isOneOf: false; inputFields: [{ name: 'limit'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'page'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'pageSize'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'start'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }]; }; 'PublicationStatus': { name: 'PublicationStatus'; enumValues: 'DRAFT' | 'PUBLISHED'; }; - 'Query': { kind: 'OBJECT'; name: 'Query'; fields: { 'bibleBook': { name: 'bibleBook'; type: { kind: 'OBJECT'; name: 'BibleBook'; ofType: null; } }; 'bibleBooks': { name: 'bibleBooks'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'BibleBook'; ofType: null; }; }; } }; 'bibleBooks_connection': { name: 'bibleBooks_connection'; type: { kind: 'OBJECT'; name: 'BibleBookEntityResponseCollection'; ofType: null; } }; 'bibleCitation': { name: 'bibleCitation'; type: { kind: 'OBJECT'; name: 'BibleCitation'; ofType: null; } }; 'bibleCitations': { name: 'bibleCitations'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'BibleCitation'; ofType: null; }; }; } }; 'bibleCitations_connection': { name: 'bibleCitations_connection'; type: { kind: 'OBJECT'; name: 'BibleCitationEntityResponseCollection'; ofType: null; } }; 'cloudflareR2': { name: 'cloudflareR2'; type: { kind: 'OBJECT'; name: 'CloudflareR2'; ofType: null; } }; 'cloudflareR2S': { name: 'cloudflareR2S'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'CloudflareR2'; ofType: null; }; }; } }; 'cloudflareR2S_connection': { name: 'cloudflareR2S_connection'; type: { kind: 'OBJECT'; name: 'CloudflareR2EntityResponseCollection'; ofType: null; } }; 'continent': { name: 'continent'; type: { kind: 'OBJECT'; name: 'Continent'; ofType: null; } }; 'continents': { name: 'continents'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Continent'; ofType: null; }; }; } }; 'continents_connection': { name: 'continents_connection'; type: { kind: 'OBJECT'; name: 'ContinentEntityResponseCollection'; ofType: null; } }; 'countries': { name: 'countries'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Country'; ofType: null; }; }; } }; 'countries_connection': { name: 'countries_connection'; type: { kind: 'OBJECT'; name: 'CountryEntityResponseCollection'; ofType: null; } }; 'country': { name: 'country'; type: { kind: 'OBJECT'; name: 'Country'; ofType: null; } }; 'countryLanguage': { name: 'countryLanguage'; type: { kind: 'OBJECT'; name: 'CountryLanguage'; ofType: null; } }; 'countryLanguages': { name: 'countryLanguages'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'CountryLanguage'; ofType: null; }; }; } }; 'countryLanguages_connection': { name: 'countryLanguages_connection'; type: { kind: 'OBJECT'; name: 'CountryLanguageEntityResponseCollection'; ofType: null; } }; 'experience': { name: 'experience'; type: { kind: 'OBJECT'; name: 'Experience'; ofType: null; } }; 'experiences': { name: 'experiences'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Experience'; ofType: null; }; }; } }; 'experiences_connection': { name: 'experiences_connection'; type: { kind: 'OBJECT'; name: 'ExperienceEntityResponseCollection'; ofType: null; } }; 'i18NLocale': { name: 'i18NLocale'; type: { kind: 'OBJECT'; name: 'I18NLocale'; ofType: null; } }; 'i18NLocales': { name: 'i18NLocales'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'I18NLocale'; ofType: null; }; }; } }; 'i18NLocales_connection': { name: 'i18NLocales_connection'; type: { kind: 'OBJECT'; name: 'I18NLocaleEntityResponseCollection'; ofType: null; } }; 'keyword': { name: 'keyword'; type: { kind: 'OBJECT'; name: 'Keyword'; ofType: null; } }; 'keywords': { name: 'keywords'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Keyword'; ofType: null; }; }; } }; 'keywords_connection': { name: 'keywords_connection'; type: { kind: 'OBJECT'; name: 'KeywordEntityResponseCollection'; ofType: null; } }; 'language': { name: 'language'; type: { kind: 'OBJECT'; name: 'Language'; ofType: null; } }; 'languages': { name: 'languages'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Language'; ofType: null; }; }; } }; 'languages_connection': { name: 'languages_connection'; type: { kind: 'OBJECT'; name: 'LanguageEntityResponseCollection'; ofType: null; } }; 'me': { name: 'me'; type: { kind: 'OBJECT'; name: 'UsersPermissionsMe'; ofType: null; } }; 'muxVideo': { name: 'muxVideo'; type: { kind: 'OBJECT'; name: 'MuxVideo'; ofType: null; } }; 'muxVideos': { name: 'muxVideos'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'MuxVideo'; ofType: null; }; }; } }; 'muxVideos_connection': { name: 'muxVideos_connection'; type: { kind: 'OBJECT'; name: 'MuxVideoEntityResponseCollection'; ofType: null; } }; 'reviewWorkflowsWorkflow': { name: 'reviewWorkflowsWorkflow'; type: { kind: 'OBJECT'; name: 'ReviewWorkflowsWorkflow'; ofType: null; } }; 'reviewWorkflowsWorkflowStage': { name: 'reviewWorkflowsWorkflowStage'; type: { kind: 'OBJECT'; name: 'ReviewWorkflowsWorkflowStage'; ofType: null; } }; 'reviewWorkflowsWorkflowStages': { name: 'reviewWorkflowsWorkflowStages'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'ReviewWorkflowsWorkflowStage'; ofType: null; }; }; } }; 'reviewWorkflowsWorkflowStages_connection': { name: 'reviewWorkflowsWorkflowStages_connection'; type: { kind: 'OBJECT'; name: 'ReviewWorkflowsWorkflowStageEntityResponseCollection'; ofType: null; } }; 'reviewWorkflowsWorkflows': { name: 'reviewWorkflowsWorkflows'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'ReviewWorkflowsWorkflow'; ofType: null; }; }; } }; 'reviewWorkflowsWorkflows_connection': { name: 'reviewWorkflowsWorkflows_connection'; type: { kind: 'OBJECT'; name: 'ReviewWorkflowsWorkflowEntityResponseCollection'; ofType: null; } }; 'uploadFile': { name: 'uploadFile'; type: { kind: 'OBJECT'; name: 'UploadFile'; ofType: null; } }; 'uploadFiles': { name: 'uploadFiles'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'UploadFile'; ofType: null; }; }; } }; 'uploadFiles_connection': { name: 'uploadFiles_connection'; type: { kind: 'OBJECT'; name: 'UploadFileEntityResponseCollection'; ofType: null; } }; 'usersPermissionsRole': { name: 'usersPermissionsRole'; type: { kind: 'OBJECT'; name: 'UsersPermissionsRole'; ofType: null; } }; 'usersPermissionsRoles': { name: 'usersPermissionsRoles'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'UsersPermissionsRole'; ofType: null; }; }; } }; 'usersPermissionsRoles_connection': { name: 'usersPermissionsRoles_connection'; type: { kind: 'OBJECT'; name: 'UsersPermissionsRoleEntityResponseCollection'; ofType: null; } }; 'usersPermissionsUser': { name: 'usersPermissionsUser'; type: { kind: 'OBJECT'; name: 'UsersPermissionsUser'; ofType: null; } }; 'usersPermissionsUsers': { name: 'usersPermissionsUsers'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'UsersPermissionsUser'; ofType: null; }; }; } }; 'usersPermissionsUsers_connection': { name: 'usersPermissionsUsers_connection'; type: { kind: 'OBJECT'; name: 'UsersPermissionsUserEntityResponseCollection'; ofType: null; } }; 'video': { name: 'video'; type: { kind: 'OBJECT'; name: 'Video'; ofType: null; } }; 'videoEdition': { name: 'videoEdition'; type: { kind: 'OBJECT'; name: 'VideoEdition'; ofType: null; } }; 'videoEditions': { name: 'videoEditions'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'VideoEdition'; ofType: null; }; }; } }; 'videoEditions_connection': { name: 'videoEditions_connection'; type: { kind: 'OBJECT'; name: 'VideoEditionEntityResponseCollection'; ofType: null; } }; 'videoOrigin': { name: 'videoOrigin'; type: { kind: 'OBJECT'; name: 'VideoOrigin'; ofType: null; } }; 'videoOrigins': { name: 'videoOrigins'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'VideoOrigin'; ofType: null; }; }; } }; 'videoOrigins_connection': { name: 'videoOrigins_connection'; type: { kind: 'OBJECT'; name: 'VideoOriginEntityResponseCollection'; ofType: null; } }; 'videoStudyQuestion': { name: 'videoStudyQuestion'; type: { kind: 'OBJECT'; name: 'VideoStudyQuestion'; ofType: null; } }; 'videoStudyQuestions': { name: 'videoStudyQuestions'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'VideoStudyQuestion'; ofType: null; }; }; } }; 'videoStudyQuestions_connection': { name: 'videoStudyQuestions_connection'; type: { kind: 'OBJECT'; name: 'VideoStudyQuestionEntityResponseCollection'; ofType: null; } }; 'videoSubtitle': { name: 'videoSubtitle'; type: { kind: 'OBJECT'; name: 'VideoSubtitle'; ofType: null; } }; 'videoSubtitles': { name: 'videoSubtitles'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'VideoSubtitle'; ofType: null; }; }; } }; 'videoSubtitles_connection': { name: 'videoSubtitles_connection'; type: { kind: 'OBJECT'; name: 'VideoSubtitleEntityResponseCollection'; ofType: null; } }; 'videoVariant': { name: 'videoVariant'; type: { kind: 'OBJECT'; name: 'VideoVariant'; ofType: null; } }; 'videoVariants': { name: 'videoVariants'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'VideoVariant'; ofType: null; }; }; } }; 'videoVariants_connection': { name: 'videoVariants_connection'; type: { kind: 'OBJECT'; name: 'VideoVariantEntityResponseCollection'; ofType: null; } }; 'videos': { name: 'videos'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Video'; ofType: null; }; }; } }; 'videos_connection': { name: 'videos_connection'; type: { kind: 'OBJECT'; name: 'VideoEntityResponseCollection'; ofType: null; } }; }; }; + 'Query': { kind: 'OBJECT'; name: 'Query'; fields: { 'bibleBook': { name: 'bibleBook'; type: { kind: 'OBJECT'; name: 'BibleBook'; ofType: null; } }; 'bibleBooks': { name: 'bibleBooks'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'BibleBook'; ofType: null; }; }; } }; 'bibleBooks_connection': { name: 'bibleBooks_connection'; type: { kind: 'OBJECT'; name: 'BibleBookEntityResponseCollection'; ofType: null; } }; 'bibleCitation': { name: 'bibleCitation'; type: { kind: 'OBJECT'; name: 'BibleCitation'; ofType: null; } }; 'bibleCitations': { name: 'bibleCitations'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'BibleCitation'; ofType: null; }; }; } }; 'bibleCitations_connection': { name: 'bibleCitations_connection'; type: { kind: 'OBJECT'; name: 'BibleCitationEntityResponseCollection'; ofType: null; } }; 'cloudflareR2': { name: 'cloudflareR2'; type: { kind: 'OBJECT'; name: 'CloudflareR2'; ofType: null; } }; 'cloudflareR2S': { name: 'cloudflareR2S'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'CloudflareR2'; ofType: null; }; }; } }; 'cloudflareR2S_connection': { name: 'cloudflareR2S_connection'; type: { kind: 'OBJECT'; name: 'CloudflareR2EntityResponseCollection'; ofType: null; } }; 'continent': { name: 'continent'; type: { kind: 'OBJECT'; name: 'Continent'; ofType: null; } }; 'continents': { name: 'continents'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Continent'; ofType: null; }; }; } }; 'continents_connection': { name: 'continents_connection'; type: { kind: 'OBJECT'; name: 'ContinentEntityResponseCollection'; ofType: null; } }; 'countries': { name: 'countries'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Country'; ofType: null; }; }; } }; 'countries_connection': { name: 'countries_connection'; type: { kind: 'OBJECT'; name: 'CountryEntityResponseCollection'; ofType: null; } }; 'country': { name: 'country'; type: { kind: 'OBJECT'; name: 'Country'; ofType: null; } }; 'countryLanguage': { name: 'countryLanguage'; type: { kind: 'OBJECT'; name: 'CountryLanguage'; ofType: null; } }; 'countryLanguages': { name: 'countryLanguages'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'CountryLanguage'; ofType: null; }; }; } }; 'countryLanguages_connection': { name: 'countryLanguages_connection'; type: { kind: 'OBJECT'; name: 'CountryLanguageEntityResponseCollection'; ofType: null; } }; 'enrichmentJob': { name: 'enrichmentJob'; type: { kind: 'OBJECT'; name: 'EnrichmentJob'; ofType: null; } }; 'enrichmentJobs': { name: 'enrichmentJobs'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'EnrichmentJob'; ofType: null; }; }; } }; 'enrichmentJobs_connection': { name: 'enrichmentJobs_connection'; type: { kind: 'OBJECT'; name: 'EnrichmentJobEntityResponseCollection'; ofType: null; } }; 'experience': { name: 'experience'; type: { kind: 'OBJECT'; name: 'Experience'; ofType: null; } }; 'experiences': { name: 'experiences'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Experience'; ofType: null; }; }; } }; 'experiences_connection': { name: 'experiences_connection'; type: { kind: 'OBJECT'; name: 'ExperienceEntityResponseCollection'; ofType: null; } }; 'i18NLocale': { name: 'i18NLocale'; type: { kind: 'OBJECT'; name: 'I18NLocale'; ofType: null; } }; 'i18NLocales': { name: 'i18NLocales'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'I18NLocale'; ofType: null; }; }; } }; 'i18NLocales_connection': { name: 'i18NLocales_connection'; type: { kind: 'OBJECT'; name: 'I18NLocaleEntityResponseCollection'; ofType: null; } }; 'keyword': { name: 'keyword'; type: { kind: 'OBJECT'; name: 'Keyword'; ofType: null; } }; 'keywords': { name: 'keywords'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Keyword'; ofType: null; }; }; } }; 'keywords_connection': { name: 'keywords_connection'; type: { kind: 'OBJECT'; name: 'KeywordEntityResponseCollection'; ofType: null; } }; 'language': { name: 'language'; type: { kind: 'OBJECT'; name: 'Language'; ofType: null; } }; 'languages': { name: 'languages'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Language'; ofType: null; }; }; } }; 'languages_connection': { name: 'languages_connection'; type: { kind: 'OBJECT'; name: 'LanguageEntityResponseCollection'; ofType: null; } }; 'me': { name: 'me'; type: { kind: 'OBJECT'; name: 'UsersPermissionsMe'; ofType: null; } }; 'muxVideo': { name: 'muxVideo'; type: { kind: 'OBJECT'; name: 'MuxVideo'; ofType: null; } }; 'muxVideos': { name: 'muxVideos'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'MuxVideo'; ofType: null; }; }; } }; 'muxVideos_connection': { name: 'muxVideos_connection'; type: { kind: 'OBJECT'; name: 'MuxVideoEntityResponseCollection'; ofType: null; } }; 'reviewWorkflowsWorkflow': { name: 'reviewWorkflowsWorkflow'; type: { kind: 'OBJECT'; name: 'ReviewWorkflowsWorkflow'; ofType: null; } }; 'reviewWorkflowsWorkflowStage': { name: 'reviewWorkflowsWorkflowStage'; type: { kind: 'OBJECT'; name: 'ReviewWorkflowsWorkflowStage'; ofType: null; } }; 'reviewWorkflowsWorkflowStages': { name: 'reviewWorkflowsWorkflowStages'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'ReviewWorkflowsWorkflowStage'; ofType: null; }; }; } }; 'reviewWorkflowsWorkflowStages_connection': { name: 'reviewWorkflowsWorkflowStages_connection'; type: { kind: 'OBJECT'; name: 'ReviewWorkflowsWorkflowStageEntityResponseCollection'; ofType: null; } }; 'reviewWorkflowsWorkflows': { name: 'reviewWorkflowsWorkflows'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'ReviewWorkflowsWorkflow'; ofType: null; }; }; } }; 'reviewWorkflowsWorkflows_connection': { name: 'reviewWorkflowsWorkflows_connection'; type: { kind: 'OBJECT'; name: 'ReviewWorkflowsWorkflowEntityResponseCollection'; ofType: null; } }; 'uploadFile': { name: 'uploadFile'; type: { kind: 'OBJECT'; name: 'UploadFile'; ofType: null; } }; 'uploadFiles': { name: 'uploadFiles'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'UploadFile'; ofType: null; }; }; } }; 'uploadFiles_connection': { name: 'uploadFiles_connection'; type: { kind: 'OBJECT'; name: 'UploadFileEntityResponseCollection'; ofType: null; } }; 'usersPermissionsRole': { name: 'usersPermissionsRole'; type: { kind: 'OBJECT'; name: 'UsersPermissionsRole'; ofType: null; } }; 'usersPermissionsRoles': { name: 'usersPermissionsRoles'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'UsersPermissionsRole'; ofType: null; }; }; } }; 'usersPermissionsRoles_connection': { name: 'usersPermissionsRoles_connection'; type: { kind: 'OBJECT'; name: 'UsersPermissionsRoleEntityResponseCollection'; ofType: null; } }; 'usersPermissionsUser': { name: 'usersPermissionsUser'; type: { kind: 'OBJECT'; name: 'UsersPermissionsUser'; ofType: null; } }; 'usersPermissionsUsers': { name: 'usersPermissionsUsers'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'UsersPermissionsUser'; ofType: null; }; }; } }; 'usersPermissionsUsers_connection': { name: 'usersPermissionsUsers_connection'; type: { kind: 'OBJECT'; name: 'UsersPermissionsUserEntityResponseCollection'; ofType: null; } }; 'video': { name: 'video'; type: { kind: 'OBJECT'; name: 'Video'; ofType: null; } }; 'videoEdition': { name: 'videoEdition'; type: { kind: 'OBJECT'; name: 'VideoEdition'; ofType: null; } }; 'videoEditions': { name: 'videoEditions'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'VideoEdition'; ofType: null; }; }; } }; 'videoEditions_connection': { name: 'videoEditions_connection'; type: { kind: 'OBJECT'; name: 'VideoEditionEntityResponseCollection'; ofType: null; } }; 'videoOrigin': { name: 'videoOrigin'; type: { kind: 'OBJECT'; name: 'VideoOrigin'; ofType: null; } }; 'videoOrigins': { name: 'videoOrigins'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'VideoOrigin'; ofType: null; }; }; } }; 'videoOrigins_connection': { name: 'videoOrigins_connection'; type: { kind: 'OBJECT'; name: 'VideoOriginEntityResponseCollection'; ofType: null; } }; 'videoStudyQuestion': { name: 'videoStudyQuestion'; type: { kind: 'OBJECT'; name: 'VideoStudyQuestion'; ofType: null; } }; 'videoStudyQuestions': { name: 'videoStudyQuestions'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'VideoStudyQuestion'; ofType: null; }; }; } }; 'videoStudyQuestions_connection': { name: 'videoStudyQuestions_connection'; type: { kind: 'OBJECT'; name: 'VideoStudyQuestionEntityResponseCollection'; ofType: null; } }; 'videoSubtitle': { name: 'videoSubtitle'; type: { kind: 'OBJECT'; name: 'VideoSubtitle'; ofType: null; } }; 'videoSubtitles': { name: 'videoSubtitles'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'VideoSubtitle'; ofType: null; }; }; } }; 'videoSubtitles_connection': { name: 'videoSubtitles_connection'; type: { kind: 'OBJECT'; name: 'VideoSubtitleEntityResponseCollection'; ofType: null; } }; 'videoVariant': { name: 'videoVariant'; type: { kind: 'OBJECT'; name: 'VideoVariant'; ofType: null; } }; 'videoVariants': { name: 'videoVariants'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'VideoVariant'; ofType: null; }; }; } }; 'videoVariants_connection': { name: 'videoVariants_connection'; type: { kind: 'OBJECT'; name: 'VideoVariantEntityResponseCollection'; ofType: null; } }; 'videos': { name: 'videos'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Video'; ofType: null; }; }; } }; 'videos_connection': { name: 'videos_connection'; type: { kind: 'OBJECT'; name: 'VideoEntityResponseCollection'; ofType: null; } }; }; }; 'ResponseCollectionMeta': { kind: 'OBJECT'; name: 'ResponseCollectionMeta'; fields: { 'pagination': { name: 'pagination'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Pagination'; ofType: null; }; } }; }; }; 'ReviewWorkflowsWorkflow': { kind: 'OBJECT'; name: 'ReviewWorkflowsWorkflow'; fields: { 'contentTypes': { name: 'contentTypes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'JSON'; ofType: null; }; } }; 'createdAt': { name: 'createdAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'documentId': { name: 'documentId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'name': { name: 'name'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'publishedAt': { name: 'publishedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'stageRequiredToPublish': { name: 'stageRequiredToPublish'; type: { kind: 'OBJECT'; name: 'ReviewWorkflowsWorkflowStage'; ofType: null; } }; 'stages': { name: 'stages'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'ReviewWorkflowsWorkflowStage'; ofType: null; }; }; } }; 'stages_connection': { name: 'stages_connection'; type: { kind: 'OBJECT'; name: 'ReviewWorkflowsWorkflowStageRelationResponseCollection'; ofType: null; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; }; }; 'ReviewWorkflowsWorkflowEntity': { kind: 'OBJECT'; name: 'ReviewWorkflowsWorkflowEntity'; fields: { 'attributes': { name: 'attributes'; type: { kind: 'OBJECT'; name: 'ReviewWorkflowsWorkflow'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; } }; }; }; @@ -269,7 +282,7 @@ export type introspection_types = { 'UsersPermissionsUserFiltersInput': { kind: 'INPUT_OBJECT'; name: 'UsersPermissionsUserFiltersInput'; isOneOf: false; inputFields: [{ name: 'and'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'UsersPermissionsUserFiltersInput'; ofType: null; }; }; defaultValue: null }, { name: 'blocked'; type: { kind: 'INPUT_OBJECT'; name: 'BooleanFilterInput'; ofType: null; }; defaultValue: null }, { name: 'confirmed'; type: { kind: 'INPUT_OBJECT'; name: 'BooleanFilterInput'; ofType: null; }; defaultValue: null }, { name: 'createdAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'documentId'; type: { kind: 'INPUT_OBJECT'; name: 'IDFilterInput'; ofType: null; }; defaultValue: null }, { name: 'email'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'not'; type: { kind: 'INPUT_OBJECT'; name: 'UsersPermissionsUserFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'or'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'UsersPermissionsUserFiltersInput'; ofType: null; }; }; defaultValue: null }, { name: 'provider'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'publishedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'role'; type: { kind: 'INPUT_OBJECT'; name: 'UsersPermissionsRoleFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'updatedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'username'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }]; }; 'UsersPermissionsUserInput': { kind: 'INPUT_OBJECT'; name: 'UsersPermissionsUserInput'; isOneOf: false; inputFields: [{ name: 'blocked'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'confirmed'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'email'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'password'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'provider'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'publishedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; defaultValue: null }, { name: 'role'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'username'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }]; }; 'UsersPermissionsUserRelationResponseCollection': { kind: 'OBJECT'; name: 'UsersPermissionsUserRelationResponseCollection'; fields: { 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'UsersPermissionsUser'; ofType: null; }; }; }; } }; }; }; - 'Video': { kind: 'OBJECT'; name: 'Video'; fields: { 'bibleCitations': { name: 'bibleCitations'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'BibleCitation'; ofType: null; }; }; } }; 'bibleCitations_connection': { name: 'bibleCitations_connection'; type: { kind: 'OBJECT'; name: 'BibleCitationRelationResponseCollection'; ofType: null; } }; 'childGatewayIds': { name: 'childGatewayIds'; type: { kind: 'SCALAR'; name: 'JSON'; ofType: null; } }; 'createdAt': { name: 'createdAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'description': { name: 'description'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'documentId': { name: 'documentId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'gatewayId': { name: 'gatewayId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'imageAlt': { name: 'imageAlt'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'images': { name: 'images'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'ComponentVideoCloudflareImage'; ofType: null; }; } }; 'keywords': { name: 'keywords'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Keyword'; ofType: null; }; }; } }; 'keywords_connection': { name: 'keywords_connection'; type: { kind: 'OBJECT'; name: 'KeywordRelationResponseCollection'; ofType: null; } }; 'label': { name: 'label'; type: { kind: 'ENUM'; name: 'ENUM_VIDEO_LABEL'; ofType: null; } }; 'locale': { name: 'locale'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'localizations': { name: 'localizations'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Video'; ofType: null; }; }; } }; 'localizations_connection': { name: 'localizations_connection'; type: { kind: 'OBJECT'; name: 'VideoRelationResponseCollection'; ofType: null; } }; 'locked': { name: 'locked'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; } }; 'noIndex': { name: 'noIndex'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; } }; 'origin': { name: 'origin'; type: { kind: 'OBJECT'; name: 'VideoOrigin'; ofType: null; } }; 'primaryLanguage': { name: 'primaryLanguage'; type: { kind: 'OBJECT'; name: 'Language'; ofType: null; } }; 'publishedAt': { name: 'publishedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'slug': { name: 'slug'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'snippet': { name: 'snippet'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'source': { name: 'source'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'ENUM_VIDEO_SOURCE'; ofType: null; }; } }; 'studyQuestions': { name: 'studyQuestions'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'VideoStudyQuestion'; ofType: null; }; }; } }; 'studyQuestions_connection': { name: 'studyQuestions_connection'; type: { kind: 'OBJECT'; name: 'VideoStudyQuestionRelationResponseCollection'; ofType: null; } }; 'subtitles': { name: 'subtitles'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'VideoSubtitle'; ofType: null; }; }; } }; 'subtitles_connection': { name: 'subtitles_connection'; type: { kind: 'OBJECT'; name: 'VideoSubtitleRelationResponseCollection'; ofType: null; } }; 'title': { name: 'title'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'variants': { name: 'variants'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'VideoVariant'; ofType: null; }; }; } }; 'variants_connection': { name: 'variants_connection'; type: { kind: 'OBJECT'; name: 'VideoVariantRelationResponseCollection'; ofType: null; } }; 'videoSource': { name: 'videoSource'; type: { kind: 'ENUM'; name: 'ENUM_VIDEO_VIDEOSOURCE'; ofType: null; } }; }; }; + 'Video': { kind: 'OBJECT'; name: 'Video'; fields: { 'aiMetadata': { name: 'aiMetadata'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'bibleCitations': { name: 'bibleCitations'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'BibleCitation'; ofType: null; }; }; } }; 'bibleCitations_connection': { name: 'bibleCitations_connection'; type: { kind: 'OBJECT'; name: 'BibleCitationRelationResponseCollection'; ofType: null; } }; 'children': { name: 'children'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Video'; ofType: null; }; }; } }; 'children_connection': { name: 'children_connection'; type: { kind: 'OBJECT'; name: 'VideoRelationResponseCollection'; ofType: null; } }; 'createdAt': { name: 'createdAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'description': { name: 'description'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'documentId': { name: 'documentId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'gatewayId': { name: 'gatewayId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'imageAlt': { name: 'imageAlt'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'images': { name: 'images'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'ComponentVideoCloudflareImage'; ofType: null; }; } }; 'keywords': { name: 'keywords'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Keyword'; ofType: null; }; }; } }; 'keywords_connection': { name: 'keywords_connection'; type: { kind: 'OBJECT'; name: 'KeywordRelationResponseCollection'; ofType: null; } }; 'label': { name: 'label'; type: { kind: 'ENUM'; name: 'ENUM_VIDEO_LABEL'; ofType: null; } }; 'locale': { name: 'locale'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'localizations': { name: 'localizations'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Video'; ofType: null; }; }; } }; 'localizations_connection': { name: 'localizations_connection'; type: { kind: 'OBJECT'; name: 'VideoRelationResponseCollection'; ofType: null; } }; 'locked': { name: 'locked'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; } }; 'noIndex': { name: 'noIndex'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; } }; 'origin': { name: 'origin'; type: { kind: 'OBJECT'; name: 'VideoOrigin'; ofType: null; } }; 'parents': { name: 'parents'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Video'; ofType: null; }; }; } }; 'parents_connection': { name: 'parents_connection'; type: { kind: 'OBJECT'; name: 'VideoRelationResponseCollection'; ofType: null; } }; 'primaryLanguage': { name: 'primaryLanguage'; type: { kind: 'OBJECT'; name: 'Language'; ofType: null; } }; 'publishedAt': { name: 'publishedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'slug': { name: 'slug'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'snippet': { name: 'snippet'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'source': { name: 'source'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'ENUM_VIDEO_SOURCE'; ofType: null; }; } }; 'studyQuestions': { name: 'studyQuestions'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'VideoStudyQuestion'; ofType: null; }; }; } }; 'studyQuestions_connection': { name: 'studyQuestions_connection'; type: { kind: 'OBJECT'; name: 'VideoStudyQuestionRelationResponseCollection'; ofType: null; } }; 'subtitles': { name: 'subtitles'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'VideoSubtitle'; ofType: null; }; }; } }; 'subtitles_connection': { name: 'subtitles_connection'; type: { kind: 'OBJECT'; name: 'VideoSubtitleRelationResponseCollection'; ofType: null; } }; 'title': { name: 'title'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'variants': { name: 'variants'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'VideoVariant'; ofType: null; }; }; } }; 'variants_connection': { name: 'variants_connection'; type: { kind: 'OBJECT'; name: 'VideoVariantRelationResponseCollection'; ofType: null; } }; 'videoSource': { name: 'videoSource'; type: { kind: 'ENUM'; name: 'ENUM_VIDEO_VIDEOSOURCE'; ofType: null; } }; }; }; 'VideoEdition': { kind: 'OBJECT'; name: 'VideoEdition'; fields: { 'createdAt': { name: 'createdAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'documentId': { name: 'documentId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'gatewayId': { name: 'gatewayId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'name': { name: 'name'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'publishedAt': { name: 'publishedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'source': { name: 'source'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'ENUM_VIDEOEDITION_SOURCE'; ofType: null; }; } }; 'subtitles': { name: 'subtitles'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'VideoSubtitle'; ofType: null; }; }; } }; 'subtitles_connection': { name: 'subtitles_connection'; type: { kind: 'OBJECT'; name: 'VideoSubtitleRelationResponseCollection'; ofType: null; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'variants': { name: 'variants'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'VideoVariant'; ofType: null; }; }; } }; 'variants_connection': { name: 'variants_connection'; type: { kind: 'OBJECT'; name: 'VideoVariantRelationResponseCollection'; ofType: null; } }; }; }; 'VideoEditionEntity': { kind: 'OBJECT'; name: 'VideoEditionEntity'; fields: { 'attributes': { name: 'attributes'; type: { kind: 'OBJECT'; name: 'VideoEdition'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; } }; }; }; 'VideoEditionEntityResponse': { kind: 'OBJECT'; name: 'VideoEditionEntityResponse'; fields: { 'data': { name: 'data'; type: { kind: 'OBJECT'; name: 'VideoEdition'; ofType: null; } }; }; }; @@ -280,8 +293,8 @@ export type introspection_types = { 'VideoEntity': { kind: 'OBJECT'; name: 'VideoEntity'; fields: { 'attributes': { name: 'attributes'; type: { kind: 'OBJECT'; name: 'Video'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; } }; }; }; 'VideoEntityResponse': { kind: 'OBJECT'; name: 'VideoEntityResponse'; fields: { 'data': { name: 'data'; type: { kind: 'OBJECT'; name: 'Video'; ofType: null; } }; }; }; 'VideoEntityResponseCollection': { kind: 'OBJECT'; name: 'VideoEntityResponseCollection'; fields: { 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Video'; ofType: null; }; }; }; } }; 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Pagination'; ofType: null; }; } }; }; }; - 'VideoFiltersInput': { kind: 'INPUT_OBJECT'; name: 'VideoFiltersInput'; isOneOf: false; inputFields: [{ name: 'and'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'VideoFiltersInput'; ofType: null; }; }; defaultValue: null }, { name: 'bibleCitations'; type: { kind: 'INPUT_OBJECT'; name: 'BibleCitationFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'childGatewayIds'; type: { kind: 'INPUT_OBJECT'; name: 'JSONFilterInput'; ofType: null; }; defaultValue: null }, { name: 'createdAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'description'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'documentId'; type: { kind: 'INPUT_OBJECT'; name: 'IDFilterInput'; ofType: null; }; defaultValue: null }, { name: 'gatewayId'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'imageAlt'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'images'; type: { kind: 'INPUT_OBJECT'; name: 'ComponentVideoCloudflareImageFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'keywords'; type: { kind: 'INPUT_OBJECT'; name: 'KeywordFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'label'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'locale'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'localizations'; type: { kind: 'INPUT_OBJECT'; name: 'VideoFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'locked'; type: { kind: 'INPUT_OBJECT'; name: 'BooleanFilterInput'; ofType: null; }; defaultValue: null }, { name: 'noIndex'; type: { kind: 'INPUT_OBJECT'; name: 'BooleanFilterInput'; ofType: null; }; defaultValue: null }, { name: 'not'; type: { kind: 'INPUT_OBJECT'; name: 'VideoFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'or'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'VideoFiltersInput'; ofType: null; }; }; defaultValue: null }, { name: 'origin'; type: { kind: 'INPUT_OBJECT'; name: 'VideoOriginFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'primaryLanguage'; type: { kind: 'INPUT_OBJECT'; name: 'LanguageFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'publishedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'slug'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'snippet'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'source'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'studyQuestions'; type: { kind: 'INPUT_OBJECT'; name: 'VideoStudyQuestionFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'subtitles'; type: { kind: 'INPUT_OBJECT'; name: 'VideoSubtitleFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'title'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'updatedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'variants'; type: { kind: 'INPUT_OBJECT'; name: 'VideoVariantFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'videoSource'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }]; }; - 'VideoInput': { kind: 'INPUT_OBJECT'; name: 'VideoInput'; isOneOf: false; inputFields: [{ name: 'bibleCitations'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'childGatewayIds'; type: { kind: 'SCALAR'; name: 'JSON'; ofType: null; }; defaultValue: null }, { name: 'description'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'gatewayId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'imageAlt'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'images'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ComponentVideoCloudflareImageInput'; ofType: null; }; }; defaultValue: null }, { name: 'keywords'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'label'; type: { kind: 'ENUM'; name: 'ENUM_VIDEO_LABEL'; ofType: null; }; defaultValue: null }, { name: 'locked'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'noIndex'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'origin'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'primaryLanguage'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'publishedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; defaultValue: null }, { name: 'slug'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'snippet'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'source'; type: { kind: 'ENUM'; name: 'ENUM_VIDEO_SOURCE'; ofType: null; }; defaultValue: null }, { name: 'studyQuestions'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'subtitles'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'title'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'variants'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'videoSource'; type: { kind: 'ENUM'; name: 'ENUM_VIDEO_VIDEOSOURCE'; ofType: null; }; defaultValue: null }]; }; + 'VideoFiltersInput': { kind: 'INPUT_OBJECT'; name: 'VideoFiltersInput'; isOneOf: false; inputFields: [{ name: 'aiMetadata'; type: { kind: 'INPUT_OBJECT'; name: 'BooleanFilterInput'; ofType: null; }; defaultValue: null }, { name: 'and'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'VideoFiltersInput'; ofType: null; }; }; defaultValue: null }, { name: 'bibleCitations'; type: { kind: 'INPUT_OBJECT'; name: 'BibleCitationFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'children'; type: { kind: 'INPUT_OBJECT'; name: 'VideoFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'createdAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'description'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'documentId'; type: { kind: 'INPUT_OBJECT'; name: 'IDFilterInput'; ofType: null; }; defaultValue: null }, { name: 'gatewayId'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'imageAlt'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'images'; type: { kind: 'INPUT_OBJECT'; name: 'ComponentVideoCloudflareImageFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'keywords'; type: { kind: 'INPUT_OBJECT'; name: 'KeywordFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'label'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'locale'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'localizations'; type: { kind: 'INPUT_OBJECT'; name: 'VideoFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'locked'; type: { kind: 'INPUT_OBJECT'; name: 'BooleanFilterInput'; ofType: null; }; defaultValue: null }, { name: 'noIndex'; type: { kind: 'INPUT_OBJECT'; name: 'BooleanFilterInput'; ofType: null; }; defaultValue: null }, { name: 'not'; type: { kind: 'INPUT_OBJECT'; name: 'VideoFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'or'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'VideoFiltersInput'; ofType: null; }; }; defaultValue: null }, { name: 'origin'; type: { kind: 'INPUT_OBJECT'; name: 'VideoOriginFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'parents'; type: { kind: 'INPUT_OBJECT'; name: 'VideoFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'primaryLanguage'; type: { kind: 'INPUT_OBJECT'; name: 'LanguageFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'publishedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'slug'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'snippet'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'source'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'studyQuestions'; type: { kind: 'INPUT_OBJECT'; name: 'VideoStudyQuestionFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'subtitles'; type: { kind: 'INPUT_OBJECT'; name: 'VideoSubtitleFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'title'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'updatedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'variants'; type: { kind: 'INPUT_OBJECT'; name: 'VideoVariantFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'videoSource'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }]; }; + 'VideoInput': { kind: 'INPUT_OBJECT'; name: 'VideoInput'; isOneOf: false; inputFields: [{ name: 'aiMetadata'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'bibleCitations'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'children'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'description'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'gatewayId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'imageAlt'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'images'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ComponentVideoCloudflareImageInput'; ofType: null; }; }; defaultValue: null }, { name: 'keywords'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'label'; type: { kind: 'ENUM'; name: 'ENUM_VIDEO_LABEL'; ofType: null; }; defaultValue: null }, { name: 'locked'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'noIndex'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'origin'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'parents'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'primaryLanguage'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'publishedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; defaultValue: null }, { name: 'slug'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'snippet'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'source'; type: { kind: 'ENUM'; name: 'ENUM_VIDEO_SOURCE'; ofType: null; }; defaultValue: null }, { name: 'studyQuestions'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'subtitles'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'title'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'variants'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'videoSource'; type: { kind: 'ENUM'; name: 'ENUM_VIDEO_VIDEOSOURCE'; ofType: null; }; defaultValue: null }]; }; 'VideoOrigin': { kind: 'OBJECT'; name: 'VideoOrigin'; fields: { 'createdAt': { name: 'createdAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'description': { name: 'description'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'documentId': { name: 'documentId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'gatewayId': { name: 'gatewayId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'name': { name: 'name'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'publishedAt': { name: 'publishedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'source': { name: 'source'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'ENUM_VIDEOORIGIN_SOURCE'; ofType: null; }; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'videos': { name: 'videos'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Video'; ofType: null; }; }; } }; 'videos_connection': { name: 'videos_connection'; type: { kind: 'OBJECT'; name: 'VideoRelationResponseCollection'; ofType: null; } }; }; }; 'VideoOriginEntity': { kind: 'OBJECT'; name: 'VideoOriginEntity'; fields: { 'attributes': { name: 'attributes'; type: { kind: 'OBJECT'; name: 'VideoOrigin'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; } }; }; }; 'VideoOriginEntityResponse': { kind: 'OBJECT'; name: 'VideoOriginEntityResponse'; fields: { 'data': { name: 'data'; type: { kind: 'OBJECT'; name: 'VideoOrigin'; ofType: null; } }; }; }; @@ -297,19 +310,19 @@ export type introspection_types = { 'VideoStudyQuestionFiltersInput': { kind: 'INPUT_OBJECT'; name: 'VideoStudyQuestionFiltersInput'; isOneOf: false; inputFields: [{ name: 'and'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'VideoStudyQuestionFiltersInput'; ofType: null; }; }; defaultValue: null }, { name: 'createdAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'documentId'; type: { kind: 'INPUT_OBJECT'; name: 'IDFilterInput'; ofType: null; }; defaultValue: null }, { name: 'gatewayId'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'locale'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'localizations'; type: { kind: 'INPUT_OBJECT'; name: 'VideoStudyQuestionFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'not'; type: { kind: 'INPUT_OBJECT'; name: 'VideoStudyQuestionFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'or'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'VideoStudyQuestionFiltersInput'; ofType: null; }; }; defaultValue: null }, { name: 'order'; type: { kind: 'INPUT_OBJECT'; name: 'IntFilterInput'; ofType: null; }; defaultValue: null }, { name: 'publishedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'source'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'updatedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'value'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'video'; type: { kind: 'INPUT_OBJECT'; name: 'VideoFiltersInput'; ofType: null; }; defaultValue: null }]; }; 'VideoStudyQuestionInput': { kind: 'INPUT_OBJECT'; name: 'VideoStudyQuestionInput'; isOneOf: false; inputFields: [{ name: 'gatewayId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'order'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'publishedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; defaultValue: null }, { name: 'source'; type: { kind: 'ENUM'; name: 'ENUM_VIDEOSTUDYQUESTION_SOURCE'; ofType: null; }; defaultValue: null }, { name: 'value'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'video'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }]; }; 'VideoStudyQuestionRelationResponseCollection': { kind: 'OBJECT'; name: 'VideoStudyQuestionRelationResponseCollection'; fields: { 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'VideoStudyQuestion'; ofType: null; }; }; }; } }; }; }; - 'VideoSubtitle': { kind: 'OBJECT'; name: 'VideoSubtitle'; fields: { 'createdAt': { name: 'createdAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'documentId': { name: 'documentId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'edition': { name: 'edition'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'gatewayId': { name: 'gatewayId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'language': { name: 'language'; type: { kind: 'OBJECT'; name: 'Language'; ofType: null; } }; 'primary': { name: 'primary'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; } }; 'publishedAt': { name: 'publishedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'source': { name: 'source'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'ENUM_VIDEOSUBTITLE_SOURCE'; ofType: null; }; } }; 'srtAsset': { name: 'srtAsset'; type: { kind: 'OBJECT'; name: 'CloudflareR2'; ofType: null; } }; 'srtSrc': { name: 'srtSrc'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'srtVersion': { name: 'srtVersion'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'value': { name: 'value'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'video': { name: 'video'; type: { kind: 'OBJECT'; name: 'Video'; ofType: null; } }; 'videoEdition': { name: 'videoEdition'; type: { kind: 'OBJECT'; name: 'VideoEdition'; ofType: null; } }; 'vttAsset': { name: 'vttAsset'; type: { kind: 'OBJECT'; name: 'CloudflareR2'; ofType: null; } }; 'vttSrc': { name: 'vttSrc'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'vttVersion': { name: 'vttVersion'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; }; }; + 'VideoSubtitle': { kind: 'OBJECT'; name: 'VideoSubtitle'; fields: { 'aiGenerated': { name: 'aiGenerated'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; } }; 'createdAt': { name: 'createdAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'documentId': { name: 'documentId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'edition': { name: 'edition'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'gatewayId': { name: 'gatewayId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'language': { name: 'language'; type: { kind: 'OBJECT'; name: 'Language'; ofType: null; } }; 'primary': { name: 'primary'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; } }; 'publishedAt': { name: 'publishedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'source': { name: 'source'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'ENUM_VIDEOSUBTITLE_SOURCE'; ofType: null; }; } }; 'srtAsset': { name: 'srtAsset'; type: { kind: 'OBJECT'; name: 'CloudflareR2'; ofType: null; } }; 'srtSrc': { name: 'srtSrc'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'srtVersion': { name: 'srtVersion'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'value': { name: 'value'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'video': { name: 'video'; type: { kind: 'OBJECT'; name: 'Video'; ofType: null; } }; 'videoEdition': { name: 'videoEdition'; type: { kind: 'OBJECT'; name: 'VideoEdition'; ofType: null; } }; 'vttAsset': { name: 'vttAsset'; type: { kind: 'OBJECT'; name: 'CloudflareR2'; ofType: null; } }; 'vttSrc': { name: 'vttSrc'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'vttVersion': { name: 'vttVersion'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; }; }; 'VideoSubtitleEntity': { kind: 'OBJECT'; name: 'VideoSubtitleEntity'; fields: { 'attributes': { name: 'attributes'; type: { kind: 'OBJECT'; name: 'VideoSubtitle'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; } }; }; }; 'VideoSubtitleEntityResponse': { kind: 'OBJECT'; name: 'VideoSubtitleEntityResponse'; fields: { 'data': { name: 'data'; type: { kind: 'OBJECT'; name: 'VideoSubtitle'; ofType: null; } }; }; }; 'VideoSubtitleEntityResponseCollection': { kind: 'OBJECT'; name: 'VideoSubtitleEntityResponseCollection'; fields: { 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'VideoSubtitle'; ofType: null; }; }; }; } }; 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Pagination'; ofType: null; }; } }; }; }; - 'VideoSubtitleFiltersInput': { kind: 'INPUT_OBJECT'; name: 'VideoSubtitleFiltersInput'; isOneOf: false; inputFields: [{ name: 'and'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'VideoSubtitleFiltersInput'; ofType: null; }; }; defaultValue: null }, { name: 'createdAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'documentId'; type: { kind: 'INPUT_OBJECT'; name: 'IDFilterInput'; ofType: null; }; defaultValue: null }, { name: 'edition'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'gatewayId'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'language'; type: { kind: 'INPUT_OBJECT'; name: 'LanguageFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'not'; type: { kind: 'INPUT_OBJECT'; name: 'VideoSubtitleFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'or'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'VideoSubtitleFiltersInput'; ofType: null; }; }; defaultValue: null }, { name: 'primary'; type: { kind: 'INPUT_OBJECT'; name: 'BooleanFilterInput'; ofType: null; }; defaultValue: null }, { name: 'publishedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'source'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'srtAsset'; type: { kind: 'INPUT_OBJECT'; name: 'CloudflareR2FiltersInput'; ofType: null; }; defaultValue: null }, { name: 'srtSrc'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'srtVersion'; type: { kind: 'INPUT_OBJECT'; name: 'IntFilterInput'; ofType: null; }; defaultValue: null }, { name: 'updatedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'value'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'video'; type: { kind: 'INPUT_OBJECT'; name: 'VideoFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'videoEdition'; type: { kind: 'INPUT_OBJECT'; name: 'VideoEditionFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'vttAsset'; type: { kind: 'INPUT_OBJECT'; name: 'CloudflareR2FiltersInput'; ofType: null; }; defaultValue: null }, { name: 'vttSrc'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'vttVersion'; type: { kind: 'INPUT_OBJECT'; name: 'IntFilterInput'; ofType: null; }; defaultValue: null }]; }; - 'VideoSubtitleInput': { kind: 'INPUT_OBJECT'; name: 'VideoSubtitleInput'; isOneOf: false; inputFields: [{ name: 'edition'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'gatewayId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'language'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'primary'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'publishedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; defaultValue: null }, { name: 'source'; type: { kind: 'ENUM'; name: 'ENUM_VIDEOSUBTITLE_SOURCE'; ofType: null; }; defaultValue: null }, { name: 'srtAsset'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'srtSrc'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'srtVersion'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'value'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'video'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'videoEdition'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'vttAsset'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'vttSrc'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'vttVersion'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }]; }; + 'VideoSubtitleFiltersInput': { kind: 'INPUT_OBJECT'; name: 'VideoSubtitleFiltersInput'; isOneOf: false; inputFields: [{ name: 'aiGenerated'; type: { kind: 'INPUT_OBJECT'; name: 'BooleanFilterInput'; ofType: null; }; defaultValue: null }, { name: 'and'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'VideoSubtitleFiltersInput'; ofType: null; }; }; defaultValue: null }, { name: 'createdAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'documentId'; type: { kind: 'INPUT_OBJECT'; name: 'IDFilterInput'; ofType: null; }; defaultValue: null }, { name: 'edition'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'gatewayId'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'language'; type: { kind: 'INPUT_OBJECT'; name: 'LanguageFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'not'; type: { kind: 'INPUT_OBJECT'; name: 'VideoSubtitleFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'or'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'VideoSubtitleFiltersInput'; ofType: null; }; }; defaultValue: null }, { name: 'primary'; type: { kind: 'INPUT_OBJECT'; name: 'BooleanFilterInput'; ofType: null; }; defaultValue: null }, { name: 'publishedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'source'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'srtAsset'; type: { kind: 'INPUT_OBJECT'; name: 'CloudflareR2FiltersInput'; ofType: null; }; defaultValue: null }, { name: 'srtSrc'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'srtVersion'; type: { kind: 'INPUT_OBJECT'; name: 'IntFilterInput'; ofType: null; }; defaultValue: null }, { name: 'updatedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'value'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'video'; type: { kind: 'INPUT_OBJECT'; name: 'VideoFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'videoEdition'; type: { kind: 'INPUT_OBJECT'; name: 'VideoEditionFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'vttAsset'; type: { kind: 'INPUT_OBJECT'; name: 'CloudflareR2FiltersInput'; ofType: null; }; defaultValue: null }, { name: 'vttSrc'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'vttVersion'; type: { kind: 'INPUT_OBJECT'; name: 'IntFilterInput'; ofType: null; }; defaultValue: null }]; }; + 'VideoSubtitleInput': { kind: 'INPUT_OBJECT'; name: 'VideoSubtitleInput'; isOneOf: false; inputFields: [{ name: 'aiGenerated'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'edition'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'gatewayId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'language'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'primary'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'publishedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; defaultValue: null }, { name: 'source'; type: { kind: 'ENUM'; name: 'ENUM_VIDEOSUBTITLE_SOURCE'; ofType: null; }; defaultValue: null }, { name: 'srtAsset'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'srtSrc'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'srtVersion'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'value'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'video'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'videoEdition'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'vttAsset'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'vttSrc'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'vttVersion'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }]; }; 'VideoSubtitleRelationResponseCollection': { kind: 'OBJECT'; name: 'VideoSubtitleRelationResponseCollection'; fields: { 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'VideoSubtitle'; ofType: null; }; }; }; } }; }; }; - 'VideoVariant': { kind: 'OBJECT'; name: 'VideoVariant'; fields: { 'asset': { name: 'asset'; type: { kind: 'OBJECT'; name: 'CloudflareR2'; ofType: null; } }; 'brightcoveId': { name: 'brightcoveId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'createdAt': { name: 'createdAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'dash': { name: 'dash'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'documentId': { name: 'documentId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'downloadable': { name: 'downloadable'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; } }; 'downloads': { name: 'downloads'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'ComponentVideoVariantDownload'; ofType: null; }; } }; 'duration': { name: 'duration'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'gatewayId': { name: 'gatewayId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'hls': { name: 'hls'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'language': { name: 'language'; type: { kind: 'OBJECT'; name: 'Language'; ofType: null; } }; 'lengthInMilliseconds': { name: 'lengthInMilliseconds'; type: { kind: 'SCALAR'; name: 'Long'; ofType: null; } }; 'muxVideo': { name: 'muxVideo'; type: { kind: 'OBJECT'; name: 'MuxVideo'; ofType: null; } }; 'published': { name: 'published'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; } }; 'publishedAt': { name: 'publishedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'share': { name: 'share'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'slug': { name: 'slug'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'source': { name: 'source'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'ENUM_VIDEOVARIANT_SOURCE'; ofType: null; }; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'version': { name: 'version'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'video': { name: 'video'; type: { kind: 'OBJECT'; name: 'Video'; ofType: null; } }; 'videoEdition': { name: 'videoEdition'; type: { kind: 'OBJECT'; name: 'VideoEdition'; ofType: null; } }; }; }; + 'VideoVariant': { kind: 'OBJECT'; name: 'VideoVariant'; fields: { 'aiGenerated': { name: 'aiGenerated'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'asset': { name: 'asset'; type: { kind: 'OBJECT'; name: 'CloudflareR2'; ofType: null; } }; 'brightcoveId': { name: 'brightcoveId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'createdAt': { name: 'createdAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'dash': { name: 'dash'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'documentId': { name: 'documentId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'downloadable': { name: 'downloadable'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; } }; 'downloads': { name: 'downloads'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'ComponentVideoVariantDownload'; ofType: null; }; } }; 'duration': { name: 'duration'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'gatewayId': { name: 'gatewayId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'hls': { name: 'hls'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'language': { name: 'language'; type: { kind: 'OBJECT'; name: 'Language'; ofType: null; } }; 'lengthInMilliseconds': { name: 'lengthInMilliseconds'; type: { kind: 'SCALAR'; name: 'Long'; ofType: null; } }; 'muxVideo': { name: 'muxVideo'; type: { kind: 'OBJECT'; name: 'MuxVideo'; ofType: null; } }; 'published': { name: 'published'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; } }; 'publishedAt': { name: 'publishedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'share': { name: 'share'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'slug': { name: 'slug'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'source': { name: 'source'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'ENUM_VIDEOVARIANT_SOURCE'; ofType: null; }; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'version': { name: 'version'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'video': { name: 'video'; type: { kind: 'OBJECT'; name: 'Video'; ofType: null; } }; 'videoEdition': { name: 'videoEdition'; type: { kind: 'OBJECT'; name: 'VideoEdition'; ofType: null; } }; }; }; 'VideoVariantEntity': { kind: 'OBJECT'; name: 'VideoVariantEntity'; fields: { 'attributes': { name: 'attributes'; type: { kind: 'OBJECT'; name: 'VideoVariant'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; } }; }; }; 'VideoVariantEntityResponse': { kind: 'OBJECT'; name: 'VideoVariantEntityResponse'; fields: { 'data': { name: 'data'; type: { kind: 'OBJECT'; name: 'VideoVariant'; ofType: null; } }; }; }; 'VideoVariantEntityResponseCollection': { kind: 'OBJECT'; name: 'VideoVariantEntityResponseCollection'; fields: { 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'VideoVariant'; ofType: null; }; }; }; } }; 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Pagination'; ofType: null; }; } }; }; }; - 'VideoVariantFiltersInput': { kind: 'INPUT_OBJECT'; name: 'VideoVariantFiltersInput'; isOneOf: false; inputFields: [{ name: 'and'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'VideoVariantFiltersInput'; ofType: null; }; }; defaultValue: null }, { name: 'asset'; type: { kind: 'INPUT_OBJECT'; name: 'CloudflareR2FiltersInput'; ofType: null; }; defaultValue: null }, { name: 'brightcoveId'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'createdAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'dash'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'documentId'; type: { kind: 'INPUT_OBJECT'; name: 'IDFilterInput'; ofType: null; }; defaultValue: null }, { name: 'downloadable'; type: { kind: 'INPUT_OBJECT'; name: 'BooleanFilterInput'; ofType: null; }; defaultValue: null }, { name: 'downloads'; type: { kind: 'INPUT_OBJECT'; name: 'ComponentVideoVariantDownloadFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'duration'; type: { kind: 'INPUT_OBJECT'; name: 'IntFilterInput'; ofType: null; }; defaultValue: null }, { name: 'gatewayId'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'hls'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'language'; type: { kind: 'INPUT_OBJECT'; name: 'LanguageFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'lengthInMilliseconds'; type: { kind: 'INPUT_OBJECT'; name: 'LongFilterInput'; ofType: null; }; defaultValue: null }, { name: 'muxVideo'; type: { kind: 'INPUT_OBJECT'; name: 'MuxVideoFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'not'; type: { kind: 'INPUT_OBJECT'; name: 'VideoVariantFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'or'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'VideoVariantFiltersInput'; ofType: null; }; }; defaultValue: null }, { name: 'published'; type: { kind: 'INPUT_OBJECT'; name: 'BooleanFilterInput'; ofType: null; }; defaultValue: null }, { name: 'publishedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'share'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'slug'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'source'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'updatedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'version'; type: { kind: 'INPUT_OBJECT'; name: 'IntFilterInput'; ofType: null; }; defaultValue: null }, { name: 'video'; type: { kind: 'INPUT_OBJECT'; name: 'VideoFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'videoEdition'; type: { kind: 'INPUT_OBJECT'; name: 'VideoEditionFiltersInput'; ofType: null; }; defaultValue: null }]; }; - 'VideoVariantInput': { kind: 'INPUT_OBJECT'; name: 'VideoVariantInput'; isOneOf: false; inputFields: [{ name: 'asset'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'brightcoveId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'dash'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'downloadable'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'downloads'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ComponentVideoVariantDownloadInput'; ofType: null; }; }; defaultValue: null }, { name: 'duration'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'gatewayId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'hls'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'language'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'lengthInMilliseconds'; type: { kind: 'SCALAR'; name: 'Long'; ofType: null; }; defaultValue: null }, { name: 'muxVideo'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'published'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'publishedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; defaultValue: null }, { name: 'share'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'slug'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'source'; type: { kind: 'ENUM'; name: 'ENUM_VIDEOVARIANT_SOURCE'; ofType: null; }; defaultValue: null }, { name: 'version'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'video'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'videoEdition'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }]; }; + 'VideoVariantFiltersInput': { kind: 'INPUT_OBJECT'; name: 'VideoVariantFiltersInput'; isOneOf: false; inputFields: [{ name: 'aiGenerated'; type: { kind: 'INPUT_OBJECT'; name: 'BooleanFilterInput'; ofType: null; }; defaultValue: null }, { name: 'and'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'VideoVariantFiltersInput'; ofType: null; }; }; defaultValue: null }, { name: 'asset'; type: { kind: 'INPUT_OBJECT'; name: 'CloudflareR2FiltersInput'; ofType: null; }; defaultValue: null }, { name: 'brightcoveId'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'createdAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'dash'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'documentId'; type: { kind: 'INPUT_OBJECT'; name: 'IDFilterInput'; ofType: null; }; defaultValue: null }, { name: 'downloadable'; type: { kind: 'INPUT_OBJECT'; name: 'BooleanFilterInput'; ofType: null; }; defaultValue: null }, { name: 'downloads'; type: { kind: 'INPUT_OBJECT'; name: 'ComponentVideoVariantDownloadFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'duration'; type: { kind: 'INPUT_OBJECT'; name: 'IntFilterInput'; ofType: null; }; defaultValue: null }, { name: 'gatewayId'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'hls'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'language'; type: { kind: 'INPUT_OBJECT'; name: 'LanguageFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'lengthInMilliseconds'; type: { kind: 'INPUT_OBJECT'; name: 'LongFilterInput'; ofType: null; }; defaultValue: null }, { name: 'muxVideo'; type: { kind: 'INPUT_OBJECT'; name: 'MuxVideoFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'not'; type: { kind: 'INPUT_OBJECT'; name: 'VideoVariantFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'or'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'VideoVariantFiltersInput'; ofType: null; }; }; defaultValue: null }, { name: 'published'; type: { kind: 'INPUT_OBJECT'; name: 'BooleanFilterInput'; ofType: null; }; defaultValue: null }, { name: 'publishedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'share'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'slug'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'source'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'updatedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'version'; type: { kind: 'INPUT_OBJECT'; name: 'IntFilterInput'; ofType: null; }; defaultValue: null }, { name: 'video'; type: { kind: 'INPUT_OBJECT'; name: 'VideoFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'videoEdition'; type: { kind: 'INPUT_OBJECT'; name: 'VideoEditionFiltersInput'; ofType: null; }; defaultValue: null }]; }; + 'VideoVariantInput': { kind: 'INPUT_OBJECT'; name: 'VideoVariantInput'; isOneOf: false; inputFields: [{ name: 'aiGenerated'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'asset'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'brightcoveId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'dash'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'downloadable'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'downloads'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ComponentVideoVariantDownloadInput'; ofType: null; }; }; defaultValue: null }, { name: 'duration'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'gatewayId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'hls'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'language'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'lengthInMilliseconds'; type: { kind: 'SCALAR'; name: 'Long'; ofType: null; }; defaultValue: null }, { name: 'muxVideo'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'published'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'publishedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; defaultValue: null }, { name: 'share'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'slug'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'source'; type: { kind: 'ENUM'; name: 'ENUM_VIDEOVARIANT_SOURCE'; ofType: null; }; defaultValue: null }, { name: 'version'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'video'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'videoEdition'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }]; }; 'VideoVariantRelationResponseCollection': { kind: 'OBJECT'; name: 'VideoVariantRelationResponseCollection'; fields: { 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'VideoVariant'; ofType: null; }; }; }; } }; }; }; };