1+ import { PodcastQueryResult } from "@/sanity.types" ;
2+ import { sanityFetch } from "@/sanity/lib/fetch" ;
3+ import { podcastQuery , postQuery } from "@/sanity/lib/queries" ;
4+ import { isValidSignature , SIGNATURE_HEADER_NAME } from "@sanity/webhook" ;
5+ import toMarkdown from '@sanity/block-content-to-markdown'
6+ import { createClient } from "next-sanity" ;
7+
8+
9+ const secret = process . env . PRIVATE_SYNDICATE_WEBOOK_SECRET ;
10+
11+ const sanityWriteClient = createClient ( {
12+ projectId : process . env . NEXT_PUBLIC_SANITY_PROJECT_ID ,
13+ dataset : process . env . NEXT_PUBLIC_SANITY_DATASET ,
14+ token : process . env . SANITY_API_WRITE_TOKEN
15+ } ) ;
16+
17+ export async function POST ( request : Request ) {
18+ if ( ! secret )
19+ return Response . json (
20+ { success : false , error : "Missing Secret PRIVATE_ALGOLIA_WEBOOK_SECRET" } ,
21+ { status : 400 }
22+ ) ;
23+
24+ const signature = request . headers . get ( SIGNATURE_HEADER_NAME ) ;
25+
26+ if ( ! signature )
27+ return Response . json (
28+ { success : false , error : "Missing Signature Header" } ,
29+ { status : 401 }
30+ ) ;
31+
32+ const body = await request . text ( ) ;
33+ if ( ! ( await isValidSignature ( body , signature , secret ) ) ) {
34+ return Response . json (
35+ { success : false , message : "Invalid signature" } ,
36+ { status : 400 }
37+ ) ;
38+ }
39+
40+ const sanityDoc = JSON . parse ( body ) ;
41+ const created = sanityDoc . created ;
42+ const deleted = sanityDoc . deleted ;
43+ const updated = sanityDoc . updated ;
44+
45+ delete sanityDoc . created ;
46+ delete sanityDoc . deleted ;
47+ delete sanityDoc . updated ;
48+
49+ try {
50+ if ( created ) {
51+ await formatPodcast ( sanityDoc . _type , sanityDoc . slug )
52+ } else if ( updated ) {
53+ await formatPodcast ( sanityDoc . _type , sanityDoc . slug )
54+ } else {
55+ await unPublishPodcast ( sanityDoc . _type , sanityDoc . slug )
56+ }
57+ } catch ( e ) {
58+ const error = JSON . stringify ( e ) ;
59+ console . error ( error ) ;
60+ Response . json ( { success : false , error } , { status : 400 } ) ;
61+ }
62+ return Response . json ( { success : true } ) ;
63+ }
64+
65+ const formatPodcast = async ( _type : string , slug : string ) => {
66+
67+ const podcast = await sanityQuery ( _type , slug ) ;
68+
69+ if ( ! podcast ?. _id ) {
70+ return Response . json ( { success : false , error : "Podcast not found" } , { status : 404 } ) ;
71+ }
72+
73+ console . debug ( 'Adding' , { slug : podcast ?. slug , devto : podcast ?. devto } ) ;
74+
75+ try {
76+ const article : any = {
77+ article : {
78+ title : podcast . title ,
79+ published : true ,
80+ tags : [ 'webdev' , 'javascript' , 'beginners' ] ,
81+ main_image : podcast ?. coverImage ?. secure_url ?. replace ( 'upload/' , 'upload/b_rgb:5e1186,c_pad,w_1000,h_420/' ) ,
82+ canonical_url : `https://codingcat.dev/${ podcast . _type } /${ podcast . slug } ` ,
83+ description : podcast ?. excerpt || '' ,
84+ organization_id : '1009' ,
85+ body_markdown : `
86+ Original: https://codingcat.dev/${ podcast . _type } /${ podcast . slug }
87+
88+ {% youtube ${ podcast ?. youtube ?. replace ( 'live' , 'embed' ) } %}
89+
90+ ${ toMarkdown ( podcast . content , { serializers } ) } `
91+ }
92+ }
93+
94+ if ( _type === 'podcast' ) {
95+ article . article . tags . push ( 'podcast' ) ;
96+ article . article . series = `codingcatdev_podcast_${ podcast ?. season || 4 } ` ;
97+ }
98+ console . debug ( "article" , JSON . stringify ( article , null , 2 ) ) ;
99+
100+ let response ;
101+ if ( podcast ?. devto ) {
102+ console . debug ( 'updateArticle to devto' ) ;
103+ response = await updateArticle ( podcast . devto , article ) ;
104+ console . debug ( 'updateArticle result:' , response . status ) ;
105+ } else {
106+ console . debug ( 'addArticle to devto' ) ;
107+ response = await addArticle ( article ) ;
108+ console . debug ( 'addArticle result:' , response . status ) ;
109+ }
110+
111+ // Get new devto url and update
112+ if ( response . status >= 200 && response . status <= 299 ) {
113+ const json = await response . json ( ) ;
114+ if ( json ?. url && ! podcast ?. devto ) {
115+ console . debug ( 'Article Added to Dev.to' , JSON . stringify ( json , null , 2 ) ) ;
116+ await updateSanity ( podcast . _id , json . url ) ;
117+ console . debug ( 'Sanity Updated' , podcast . _id , json . url ) ;
118+ }
119+ }
120+ return Response . json ( { success : true } , { status : 201 } ) ;
121+ } catch ( error ) {
122+ console . error ( error ) ;
123+ return Response . json (
124+ { success : false , error } ,
125+ { status : 500 }
126+ ) ;
127+ }
128+ }
129+
130+ const unPublishPodcast = async ( _type : string , slug : string ) => {
131+ const podcast = await sanityQuery ( _type , slug ) ;
132+ if ( ! podcast ?. _id ) {
133+ return Response . json ( { success : false , error : "Podcast not found" } , { status : 404 } ) ;
134+ }
135+
136+ console . debug ( 'Unpublishing' , { slug : podcast ?. slug , devto : podcast ?. devto } ) ;
137+
138+ try {
139+
140+ if ( ! podcast ?. devto ) {
141+ return Response . json ( { success : false , error : "DevTo not found" } , { status : 404 } ) ;
142+ }
143+ const response = await unpublishArticle ( podcast . _id , podcast . devto ) ;
144+
145+ // Get new devto url and update
146+ if ( response . status >= 200 && response . status <= 299 ) {
147+ const unpubResponse = await unpublishArticle ( podcast . _id , podcast . devto ) ;
148+ if ( unpubResponse . status === 204 ) {
149+ console . debug ( 'Unpublished Article' , podcast . _id , podcast . devto ) ;
150+ }
151+ }
152+ return Response . json ( { success : true } , { status : 201 } ) ;
153+ } catch ( error ) {
154+ console . error ( error ) ;
155+ return Response . json (
156+ { success : false , error } ,
157+ { status : 500 }
158+ ) ;
159+ }
160+ }
161+
162+ const addArticle = async ( article : any ) => {
163+ return fetch ( 'https://dev.to/api/articles/' , {
164+ method : 'POST' ,
165+ headers : {
166+ 'api-key' : process . env . PRIVATE_DEVTO || '' ,
167+ 'Content-Type' : 'application/json'
168+ } ,
169+ body : JSON . stringify ( article )
170+ } ) ;
171+ } ;
172+
173+ const getArticle = async ( devto : any ) => {
174+ return fetch ( `https://dev.to/api/articles/${ devto . split ( "https://dev.to/" ) . at ( - 1 ) } ` , {
175+ method : 'GET' ,
176+ headers : {
177+ 'api-key' : process . env . PRIVATE_DEVTO || '' ,
178+ 'Content-Type' : 'application/json'
179+ }
180+ } ) ;
181+ } ;
182+
183+ const updateArticle = async ( devto : string , article : any ) => {
184+ const requestedArticle = await getArticle ( devto ) ;
185+ const json = await requestedArticle . json ( ) ;
186+ return fetch ( `https://dev.to/api/articles/${ json ?. id } ` , {
187+ method : 'PUT' ,
188+ headers : {
189+ 'api-key' : process . env . PRIVATE_DEVTO || '' ,
190+ 'Content-Type' : 'application/json'
191+ } ,
192+ body : JSON . stringify ( article )
193+ } ) ;
194+ } ;
195+
196+ const unpublishArticle = async ( _id : string , devto : string ) => {
197+ const requestedArticle = await getArticle ( devto ) ;
198+ const json = await requestedArticle . json ( ) ;
199+ return fetch ( `https://dev.to/api/articles/${ json ?. id } /unpublish` , {
200+ method : 'PUT' ,
201+ headers : {
202+ 'api-key' : process . env . PRIVATE_DEVTO || '' ,
203+ 'Content-Type' : 'application/json'
204+ }
205+ } ) ;
206+ } ;
207+
208+ const updateSanity = async ( _id : string , url : string ) => {
209+ return sanityWriteClient . patch ( _id ) . set ( { devto : url } ) . commit ( ) ;
210+ } ;
211+
212+ const sanityQuery = async ( _type : string , slug : string ) => {
213+ const query = _type === 'podcast' ? podcastQuery : postQuery ;
214+ const [ podcast ] = await Promise . all ( [
215+ sanityFetch < PodcastQueryResult > ( {
216+ query,
217+ params : {
218+ slug
219+ } ,
220+ stega : false ,
221+ perspective : "raw" ,
222+ } ) ,
223+ ] ) ;
224+ return podcast ;
225+ }
226+
227+ // Check base.ts for the custom types
228+ const serializers = {
229+ types : {
230+ code : ( props : any ) => '```' + props ?. node ?. language + '\n' + props ?. node ?. code + '\n```' ,
231+ "cloudinary.asset" : ( props : any ) => `` ,
232+ codepen : ( props : any ) => `{% codepen ${ props ?. node ?. url } %}` ,
233+ codesandbox : ( props : any ) => `{% codesandbox ${ props ?. node ?. url ?. split ( 'https://codesandbox.io/p/sandbox/' ) ?. at ( - 1 ) } %}` ,
234+ twitter : ( props : any ) => `{% twitter ${ props ?. node ?. id } %}`
235+ }
236+ }
0 commit comments