Skip to content

Commit 0985d23

Browse files
author
Alex Patterson
committed
add devto
1 parent cc9bf0b commit 0985d23

9 files changed

Lines changed: 541 additions & 13 deletions

File tree

‎app/api/devto/route.tsx‎

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
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) => `![](${props?.node?.secure_url})`,
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

Comments
 (0)