Skip to content

Commit 7e7d17a

Browse files
committed
fix(ops,cors): register Sanity App SDK CORS origins and fallback to codingcat.dev API in Ops App
1 parent 3b063e4 commit 7e7d17a

3 files changed

Lines changed: 278 additions & 65 deletions

File tree

‎.github/workflows/deploy.yml‎

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,36 @@ jobs:
106106
echo "Creating and deploying new $APP_TITLE app in organization $ORG_ID..."
107107
npx sanity deploy -y --create --title "$APP_TITLE"
108108
fi
109+
node -e "
110+
const origins = [
111+
'https://*.sanity.studio',
112+
'https://day5rb83rkkd.sanity.studio',
113+
'https://*.sanity.io',
114+
'https://sanity.io',
115+
'https://codingcat.dev',
116+
'https://dev.codingcat.dev'
117+
];
118+
(async () => {
119+
const res = await fetch('https://api.sanity.io/v2021-06-07/projects/' + process.env.SANITY_STUDIO_PROJECT_ID + '/cors', {
120+
headers: { Authorization: 'Bearer ' + process.env.SANITY_AUTH_TOKEN }
121+
});
122+
const existing = res.ok ? await res.json() : [];
123+
const existingSet = new Set((existing || []).map(x => x.origin));
124+
for (const origin of origins) {
125+
if (!existingSet.has(origin)) {
126+
const r = await fetch('https://api.sanity.io/v2021-06-07/projects/' + process.env.SANITY_STUDIO_PROJECT_ID + '/cors', {
127+
method: 'POST',
128+
headers: {
129+
Authorization: 'Bearer ' + process.env.SANITY_AUTH_TOKEN,
130+
'Content-Type': 'application/json'
131+
},
132+
body: JSON.stringify({ origin, allowCredentials: true })
133+
});
134+
console.log('Added Sanity CORS origin:', origin, r.status);
135+
}
136+
}
137+
})().catch(e => console.warn('CORS sync warning:', e));
138+
"
109139
env:
110140
SANITY_AUTH_TOKEN: ${{ secrets.SANITY_AUTH_TOKEN }}
111141
SANITY_STUDIO_DATASET: ${{ vars.SANITY_STUDIO_DATASET }}
@@ -301,6 +331,36 @@ jobs:
301331
echo "Creating and deploying new $APP_TITLE app in organization $ORG_ID..."
302332
npx sanity deploy -y --create --title "$APP_TITLE"
303333
fi
334+
node -e "
335+
const origins = [
336+
'https://*.sanity.studio',
337+
'https://day5rb83rkkd.sanity.studio',
338+
'https://*.sanity.io',
339+
'https://sanity.io',
340+
'https://codingcat.dev',
341+
'https://dev.codingcat.dev'
342+
];
343+
(async () => {
344+
const res = await fetch('https://api.sanity.io/v2021-06-07/projects/' + process.env.SANITY_STUDIO_PROJECT_ID + '/cors', {
345+
headers: { Authorization: 'Bearer ' + process.env.SANITY_AUTH_TOKEN }
346+
});
347+
const existing = res.ok ? await res.json() : [];
348+
const existingSet = new Set((existing || []).map(x => x.origin));
349+
for (const origin of origins) {
350+
if (!existingSet.has(origin)) {
351+
const r = await fetch('https://api.sanity.io/v2021-06-07/projects/' + process.env.SANITY_STUDIO_PROJECT_ID + '/cors', {
352+
method: 'POST',
353+
headers: {
354+
Authorization: 'Bearer ' + process.env.SANITY_AUTH_TOKEN,
355+
'Content-Type': 'application/json'
356+
},
357+
body: JSON.stringify({ origin, allowCredentials: true })
358+
});
359+
console.log('Added Sanity CORS origin:', origin, r.status);
360+
}
361+
}
362+
})().catch(e => console.warn('CORS sync warning:', e));
363+
"
304364
env:
305365
SANITY_AUTH_TOKEN: ${{ secrets.SANITY_AUTH_TOKEN }}
306366
SANITY_STUDIO_DATASET: ${{ vars.SANITY_STUDIO_DATASET }}

‎apps/ops/src/App.tsx‎

Lines changed: 130 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -350,68 +350,77 @@ export function App() {
350350
const fetchAllData = useCallback(async () => {
351351
setLoading(true);
352352
try {
353-
const res = await client.fetch<{
354-
leads: SponsorLeadDoc[];
355-
bookings: BookingDoc[];
356-
syndicatedContent: SyndicatedContentItem[];
357-
syndicationDocCount: number;
358-
draftItems: DraftContentItem[];
359-
trendSignals: TrendSignalDoc[];
360-
}>(
361-
`{
362-
"leads": *[_type == "sponsorLead" && !(_id in path("drafts.**"))] | order(coalesce(lastEmailAt, _updatedAt, _createdAt) desc),
363-
"bookings": *[_type == "booking" && !(_id in path("drafts.**"))] | order(coalesce(slotStartAt, _createdAt) desc),
364-
"syndicatedContent": *[_type in ["post", "podcast"] && !(_id in path("drafts.**"))] | order(coalesce(date, _createdAt) desc)[0...50] {
365-
_id,
366-
_type,
367-
title,
368-
"slug": slug.current,
369-
"date": coalesce(date, _createdAt),
370-
youtube,
371-
dailydev,
372-
devto,
373-
hashnode,
374-
awsbuilder,
375-
medium,
376-
reddit
377-
},
378-
"syndicationDocCount": count(*[_type == "syndication"]),
379-
"draftItems": *[_type in ["post", "podcast", "course", "lesson"] && _id in path("drafts.**")] | order(_updatedAt desc) {
380-
_id,
381-
_type,
382-
title,
383-
"slug": slug.current,
384-
_updatedAt,
385-
_createdAt,
386-
excerpt,
387-
"contentLen": count(content),
388-
"hasCover": defined(coverImage.asset)
389-
},
390-
"trendSignals": *[_type == "trendSignal"] | order(coalesce(discoveredAt, _createdAt) desc)[0...40] {
391-
_id,
392-
title,
393-
"slug": slug.current,
394-
source,
395-
sourceUrl,
396-
score,
397-
status,
398-
summary,
399-
rawMetrics,
400-
discoveredAt,
401-
socialDrafts
402-
}
403-
}`,
404-
{},
405-
{ perspective: "raw" },
406-
);
353+
let loadedFromSanityClient = false;
354+
try {
355+
const res = await client.fetch<{
356+
leads: SponsorLeadDoc[];
357+
bookings: BookingDoc[];
358+
syndicatedContent: SyndicatedContentItem[];
359+
syndicationDocCount: number;
360+
draftItems: DraftContentItem[];
361+
trendSignals: TrendSignalDoc[];
362+
}>(
363+
`{
364+
"leads": *[_type == "sponsorLead" && !(_id in path("drafts.**"))] | order(coalesce(lastEmailAt, _updatedAt, _createdAt) desc),
365+
"bookings": *[_type == "booking" && !(_id in path("drafts.**"))] | order(coalesce(slotStartAt, _createdAt) desc),
366+
"syndicatedContent": *[_type in ["post", "podcast"] && !(_id in path("drafts.**"))] | order(coalesce(date, _createdAt) desc)[0...50] {
367+
_id,
368+
_type,
369+
title,
370+
"slug": slug.current,
371+
"date": coalesce(date, _createdAt),
372+
youtube,
373+
dailydev,
374+
devto,
375+
hashnode,
376+
awsbuilder,
377+
medium,
378+
reddit
379+
},
380+
"syndicationDocCount": count(*[_type == "syndication"]),
381+
"draftItems": *[_type in ["post", "podcast", "course", "lesson"] && _id in path("drafts.**")] | order(_updatedAt desc) {
382+
_id,
383+
_type,
384+
title,
385+
"slug": slug.current,
386+
_updatedAt,
387+
_createdAt,
388+
excerpt,
389+
"contentLen": count(content),
390+
"hasCover": defined(coverImage.asset)
391+
},
392+
"trendSignals": *[_type == "trendSignal"] | order(coalesce(discoveredAt, _createdAt) desc)[0...40] {
393+
_id,
394+
title,
395+
"slug": slug.current,
396+
source,
397+
sourceUrl,
398+
score,
399+
status,
400+
summary,
401+
rawMetrics,
402+
discoveredAt,
403+
socialDrafts
404+
}
405+
}`,
406+
{},
407+
{ perspective: "raw" },
408+
);
407409

408-
if (res) {
409-
setLeads(res.leads || []);
410-
setBookings(res.bookings || []);
411-
setSyndicatedContent(res.syndicatedContent || []);
412-
setSyndicationDocCount(res.syndicationDocCount || 0);
413-
setDraftItems(res.draftItems || []);
414-
setTrendSignals(res.trendSignals || []);
410+
if (res && (res.leads?.length || res.bookings?.length)) {
411+
loadedFromSanityClient = true;
412+
setLeads(res.leads || []);
413+
setBookings(res.bookings || []);
414+
setSyndicatedContent(res.syndicatedContent || []);
415+
setSyndicationDocCount(res.syndicationDocCount || 0);
416+
setDraftItems(res.draftItems || []);
417+
setTrendSignals(res.trendSignals || []);
418+
}
419+
} catch (err) {
420+
console.warn(
421+
"Direct Sanity client fetch blocked by CORS, falling back to codingcat.dev API:",
422+
err,
423+
);
415424
}
416425

417426
try {
@@ -421,6 +430,44 @@ export function App() {
421430
if (Array.isArray(calData.calendarEvents)) {
422431
setMasterCalendarEvents(calData.calendarEvents);
423432
}
433+
if (!loadedFromSanityClient) {
434+
if (Array.isArray(calData.allLeads)) {
435+
setLeads(calData.allLeads);
436+
} else if (Array.isArray(calData.actionLeads)) {
437+
setLeads(calData.actionLeads);
438+
}
439+
if (Array.isArray(calData.allBookings)) {
440+
setBookings(calData.allBookings);
441+
} else if (
442+
Array.isArray(calData.pendingHolds) ||
443+
Array.isArray(calData.upcoming72hBookings)
444+
) {
445+
const seen = new Set<string>();
446+
const mergedBookings: BookingDoc[] = [];
447+
for (const b of [
448+
...(calData.pendingHolds || []),
449+
...(calData.upcoming72hBookings || []),
450+
]) {
451+
if (b && b._id && !seen.has(b._id)) {
452+
seen.add(b._id);
453+
mergedBookings.push(b);
454+
}
455+
}
456+
setBookings(mergedBookings);
457+
}
458+
if (Array.isArray(calData.syndicatedContent)) {
459+
setSyndicatedContent(calData.syndicatedContent);
460+
}
461+
if (typeof calData.syndicationDocCount === "number") {
462+
setSyndicationDocCount(calData.syndicationDocCount);
463+
}
464+
if (Array.isArray(calData.draftItems)) {
465+
setDraftItems(calData.draftItems);
466+
}
467+
if (Array.isArray(calData.trendSignals)) {
468+
setTrendSignals(calData.trendSignals);
469+
}
470+
}
424471
}
425472
} catch {
426473
// Ignore calendar digest fetch errors
@@ -442,6 +489,26 @@ export function App() {
442489
setTimeout(() => setCopiedId(null), 2000);
443490
};
444491

492+
const patchDocWithFallback = async (
493+
docId: string,
494+
patchData: Record<string, any>,
495+
) => {
496+
try {
497+
await client.patch(docId).set(patchData).commit();
498+
} catch {
499+
const res = await fetch(`${siteOrigin}/api/ops/daily-digest`, {
500+
method: "POST",
501+
headers: { "Content-Type": "application/json" },
502+
body: JSON.stringify({
503+
action: "patch_doc",
504+
docId,
505+
patch: patchData,
506+
}),
507+
});
508+
if (!res.ok) throw new Error("Failed to patch document");
509+
}
510+
};
511+
445512
const handleUpdateLeadStatus = async (leadId: string, newStatus: string) => {
446513
try {
447514
const patchData: Record<string, any> = { status: newStatus };
@@ -450,7 +517,7 @@ export function App() {
450517
} else if (newStatus === "paid") {
451518
patchData.balancePaidAt = new Date().toISOString();
452519
}
453-
await client.patch(leadId).set(patchData).commit();
520+
await patchDocWithFallback(leadId, patchData);
454521
setLeads((prev) =>
455522
prev.map((l) => (l._id === leadId ? { ...l, ...patchData } : l)),
456523
);
@@ -528,7 +595,7 @@ export function App() {
528595
newStatus: string,
529596
) => {
530597
try {
531-
await client.patch(bookingId).set({ status: newStatus }).commit();
598+
await patchDocWithFallback(bookingId, { status: newStatus });
532599
setBookings((prev) =>
533600
prev.map((b) =>
534601
b._id === bookingId ? { ...b, status: newStatus } : b,

0 commit comments

Comments
 (0)