1- import { GraphQLClient , gql } from 'graphql-request' ;
21import { unstable_cache } from 'next/cache' ;
2+ import rawEpisodes from './podcast/episodes.json' ;
33
44export const buzzsproutPodcastId = '1558601' as const ;
55
6- const episodeQuery = gql `
7- query getEpisode($slug: String!) {
8- entry(section: "podcast", slug: [$slug]) {
9- title
10- ... on podcast_default_Entry {
11- id
12- episodeSponsors {
13- title
14- ... on podcastSponsors_default_Entry {
15- sponsorUrl: urlValue
16- sponsorImage: podcastEpisodeCard {
17- path
18- width
19- height
20- }
21- sponsorDescription: podcastShowNotes {
22- renderHtml
23- }
24- }
25- }
26- metaDescription
27- podcastEpisode
28- podcastBuzzsproutId
29- podcastPublishDate
30- podcastSeason
31- podcastShortDescription {
32- renderHtml
33- }
34- podcastShowNotes {
35- renderHtml
36- }
37- podcastGuests {
38- ... on podcastGuests_guest_BlockType {
39- id
40- guestName
41- guestBio {
42- renderHtml
43- }
44- headshot {
45- path
46- }
47- }
48- }
49- podcastEpisodeCard {
50- path
51- }
52- }
53- }
54- }
55- ` ;
56-
57- const episodesQuery = gql `
58- query getEpisodes($limit: Int!) {
59- entries(section: "podcast", limit: $limit) {
60- title
61- slug
62- ... on podcast_default_Entry {
63- id
64- metaDescription
65- podcastEpisode
66- podcastSeason
67- podcastPublishDate
68- podcastBuzzsproutId
69- episodeSponsors {
70- title
71- ... on podcastSponsors_default_Entry {
72- sponsorUrl: urlValue
73- sponsorImage: podcastEpisodeCard {
74- path
75- width
76- height
77- }
78- sponsorDescription: podcastShowNotes {
79- renderHtml
80- }
81- }
82- }
83- }
84- }
85- }
86- ` ;
87-
88- export async function getEpisodeQueryParams ( request : Request ) {
89- const url = new URL ( request . url ) ;
90-
91- const sp = new URLSearchParams ( ) ;
92- const craftPreview = url . searchParams . get ( 'x-craft-preview' ) ;
93- if ( craftPreview ) {
94- sp . set ( 'x-craft-preview' , craftPreview ) ;
95- }
6+ // episodes.json is sourced from vc-data and bundled here at build time.
7+ // To update: copy the latest episodes.json from Virtual-Coffee/vc-data into
8+ // src/data/podcast/episodes.json and open a PR.
9+ // https://github.com/Virtual-Coffee/vc-data/blob/main/podcast/episodes.json
9610
97- const token = url . searchParams . get ( 'token' ) ;
98- if ( token ) {
99- sp . set ( 'token' , token ) ;
100- }
11+ const IMGIX_PREFIX = 'https://virtualcoffeeio-cms.imgix.net/podcast/' ;
10112
102- return sp . toString ( ) ;
13+ /** Strip the full imgix URL down to just the filename, which is what createCmsImage expects. */
14+ function extractPath ( fullUrl : string | null | undefined ) : string {
15+ if ( ! fullUrl ) return '' ;
16+ return fullUrl . startsWith ( IMGIX_PREFIX )
17+ ? fullUrl . slice ( IMGIX_PREFIX . length )
18+ : fullUrl ;
10319}
10420
21+ // ---------------------------------------------------------------------------
22+ // Types that match the shape the rest of the app expects (unchanged from before)
23+ // ---------------------------------------------------------------------------
24+
10525export interface PodcastEpisode {
10626 title : string ;
10727 slug : string ;
@@ -133,7 +53,6 @@ export interface PodcastEpisode {
13353 } > ;
13454}
13555
136- // type PodcastEpisodes = Partial<PodcastEpisode>[];
13756type PodcastEpisodes = Pick <
13857 PodcastEpisode ,
13958 | 'title'
@@ -147,97 +66,115 @@ type PodcastEpisodes = Pick<
14766 | 'url'
14867 | 'episodeSponsors'
14968> [ ] ;
150- type PodcastEpisodeResponse = {
151- entries : PodcastEpisode [ ] ;
152- } ;
15369
154- export const getEpisodes = unstable_cache (
155- async ( { limit = 5 } : { limit ?: number } = { } ) : Promise < PodcastEpisodes > => {
156- if ( ! ( process . env . CMS_URL && process . env . CMS_TOKEN ) ) {
157- const fakeData = await import ( './mocks/podcast.server' ) ;
158- return fakeData . getEpisodes ( { limit } ) . map ( ( entry ) => ( {
159- ...entry ,
160- url : `/podcast/${ entry . slug } ` ,
161- } ) ) ;
162- }
70+ // ---------------------------------------------------------------------------
71+ // Shape of data in episodes.json (sourced from vc-data)
72+ // ---------------------------------------------------------------------------
16373
164- const graphQLClient = new GraphQLClient ( `${ process . env . CMS_URL } /api` , {
165- headers : {
166- Authorization : `bearer ${ process . env . CMS_TOKEN } ` ,
167- } ,
168- } ) ;
74+ interface VcDataEpisode {
75+ title : string ;
76+ slug : string ;
77+ id : string ;
78+ metaDescription : string ;
79+ season : number ;
80+ episode : number ;
81+ buzzsproutId : string ;
82+ publishDate : string ;
83+ shortDescription : string ;
84+ showNotes : string ;
85+ episodeCard : string ;
86+ guests : Array < {
87+ guestName : string ;
88+ guestBio : string ;
89+ headshot : string | null ;
90+ } > ;
91+ sponsors : Array < {
92+ title : string ;
93+ url : string ;
94+ logo : string | null ;
95+ logoWidth : number ;
96+ logoHeight : number ;
97+ description : string ;
98+ } > ;
99+ }
169100
170- try {
171- const episodesResponse =
172- await graphQLClient . request < PodcastEpisodeResponse > ( episodesQuery , {
173- limit,
174- } ) ;
175- return episodesResponse . entries . map ( ( entry ) => ( {
176- ...entry ,
177- url : `/podcast/${ entry . slug } ` ,
178- } ) ) ;
179- } catch ( e ) {
180- console . error ( e ) ;
181- return [ ] ;
182- }
101+ // ---------------------------------------------------------------------------
102+ // Mapping: vc-data shape → legacy Craft shape the app expects
103+ // ---------------------------------------------------------------------------
104+
105+ function mapEpisode ( e : VcDataEpisode ) : PodcastEpisode {
106+ return {
107+ title : e . title ,
108+ slug : e . slug ,
109+ id : e . id ,
110+ metaDescription : e . metaDescription ,
111+ podcastEpisode : e . episode ,
112+ podcastSeason : e . season ,
113+ podcastPublishDate : e . publishDate ,
114+ podcastBuzzsproutId : e . buzzsproutId ,
115+ podcastShortDescription : { renderHtml : e . shortDescription } ,
116+ podcastShowNotes : { renderHtml : e . showNotes } ,
117+ podcastEpisodeCard : e . episodeCard
118+ ? [ { path : extractPath ( e . episodeCard ) } ]
119+ : [ ] ,
120+ podcastGuests : ( e . guests || [ ] ) . map ( ( g , i ) => ( {
121+ id : i ,
122+ guestName : g . guestName ,
123+ guestBio : { renderHtml : g . guestBio } ,
124+ headshot : g . headshot ? [ { path : extractPath ( g . headshot ) } ] : [ ] ,
125+ } ) ) ,
126+ episodeSponsors : ( e . sponsors || [ ] ) . map ( ( s ) => ( {
127+ title : s . title ,
128+ sponsorUrl : s . url ,
129+ sponsorImage : s . logo
130+ ? [
131+ {
132+ path : extractPath ( s . logo ) ,
133+ width : s . logoWidth ,
134+ height : s . logoHeight ,
135+ } ,
136+ ]
137+ : [ ] ,
138+ sponsorDescription : { renderHtml : s . description } ,
139+ } ) ) ,
140+ url : `/podcast/${ e . slug } ` ,
141+ } ;
142+ }
143+
144+ // Map all episodes once at module load (bundled JSON, no async needed)
145+ const allMappedEpisodes : PodcastEpisode [ ] = (
146+ rawEpisodes as unknown as VcDataEpisode [ ]
147+ ) . map ( mapEpisode ) ;
148+
149+ // ---------------------------------------------------------------------------
150+ // Public API (same signatures as before)
151+ // ---------------------------------------------------------------------------
152+
153+ export const getEpisodes = unstable_cache (
154+ async ( { limit = 5 } : { limit ?: number } = { } ) : Promise < PodcastEpisodes > => {
155+ return allMappedEpisodes . slice ( 0 , limit ) ;
183156 } ,
184157 [ ] ,
185- { revalidate : 86400 , tags : [ 'podcast' ] } ,
158+ { revalidate : false , tags : [ 'podcast' ] } ,
186159) ;
187160
188161export const getEpisode = unstable_cache (
189162 async ( {
190163 slug,
191- queryParams = '' ,
192164 } : {
193165 slug : PodcastEpisode [ 'slug' ] ;
194166 queryParams ?: string ;
195167 } ) : Promise < PodcastEpisode | null > => {
196- if ( ! ( process . env . CMS_URL && process . env . CMS_TOKEN ) ) {
197- const fakeData = await import ( './mocks/podcast.server' ) ;
198- const episode = fakeData . getEpisode ( { slug } ) ;
199- return {
200- ...episode ,
201- url : `/podcast/${ episode . slug } ` ,
202- } ;
203- }
204-
205- const graphQLClient = new GraphQLClient (
206- `${ process . env . CMS_URL } /api?${ queryParams || '' } ` ,
207- {
208- headers : {
209- Authorization : `bearer ${ process . env . CMS_TOKEN } ` ,
210- } ,
211- } ,
212- ) ;
213-
214- try {
215- console . log ( 'requesting' ) ;
216- const episodesResponse = await graphQLClient . request < {
217- entry : PodcastEpisode ;
218- } > ( episodeQuery , {
219- slug,
220- } ) ;
221- console . log ( 'finished:' ) ;
222-
223- // return response.slice(0, 10);
224- if ( ! episodesResponse . entry ) {
225- throw new Error ( 'No episode found' ) ;
226- }
227-
228- return {
229- ...episodesResponse . entry ,
230- url : `/podcast/${ episodesResponse . entry . slug } ` ,
231- } ;
232- } catch ( e ) {
233- console . error ( e ) ;
234- return null ;
235- }
168+ return allMappedEpisodes . find ( ( e ) => e . slug === slug ) ?? null ;
236169 } ,
237170 [ ] ,
238- { revalidate : 86400 , tags : [ 'podcast' ] } ,
171+ { revalidate : false , tags : [ 'podcast' ] } ,
239172) ;
240173
174+ // ---------------------------------------------------------------------------
175+ // Transcript — unchanged, reads from feeds.virtualcoffee.io
176+ // ---------------------------------------------------------------------------
177+
241178type TranscriptSegment = {
242179 speaker : string ;
243180 startTime : number ;
@@ -300,3 +237,12 @@ export const getTranscript = unstable_cache(
300237 [ ] ,
301238 { revalidate : 86400 , tags : [ 'podcast' ] } ,
302239) ;
240+
241+ // ---------------------------------------------------------------------------
242+ // Kept for backwards compatibility — no longer needed with vc-data
243+ // but removing it would be a breaking change if anything imports it.
244+ // ---------------------------------------------------------------------------
245+
246+ export async function getEpisodeQueryParams ( _request : Request ) {
247+ return '' ;
248+ }
0 commit comments