Skip to content

Commit 64e9bfa

Browse files
fix: replace internal redirects with 404 responses across workflow pages and block .json path access
1 parent 064febf commit 64e9bfa

5 files changed

Lines changed: 99 additions & 13 deletions

File tree

site/src/pages/[locale]/workflows/[slug].astro

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ Astro.response.headers.set('CDN-Cache-Control', 'public, max-age=60, stale-while
3535
3636
const { locale, slug } = Astro.params;
3737
38+
if (Astro.url.pathname.endsWith('.json/')) {
39+
return new Response(null, {
40+
status: 404,
41+
statusText: 'Not Found',
42+
});
43+
}
44+
3845
// Validate locale
3946
if (!locale || !LOCALES.includes(locale as Locale) || locale === DEFAULT_LOCALE) {
4047
return Astro.redirect('/workflows/');
@@ -60,8 +67,15 @@ if (detailShareId) {
6067
const is404 = err instanceof Error && err.message.includes('404');
6168
if (!is404) {
6269
console.error('Hub API error on ISR detail:', err);
70+
return new Response(null, {
71+
status: 502,
72+
statusText: 'Bad Gateway',
73+
});
6374
}
64-
return Astro.redirect(`/${locale}/workflows/`);
75+
return new Response(null, {
76+
status: 404,
77+
statusText: 'Not Found',
78+
});
6579
}
6680
}
6781
@@ -116,21 +130,31 @@ try {
116130
// Fetch workflows for this creator from index
117131
const profiles = await getProfileCache();
118132
let serialized: SerializedTemplate[] = [];
133+
let indexFetchFailed = false;
119134
120135
try {
121136
const entries = await listWorkflowIndex();
122137
const filtered = entries.filter((e) => (e.profile?.username || e.username) === slug);
123138
serialized = filtered.map((e) => serializeIndexEntry(e, profiles));
124139
} catch (err) {
140+
indexFetchFailed = true;
125141
console.error('Hub API failed for creator grid:', err);
126-
// Index API failed — show empty grid
127142
}
128143
129144
// Slug is bogus (e.g. "undefined" or a malformed "<name>-" suffix from a broken
130145
// link): no profile and no workflows — redirect instead of rendering a page
131146
// that would title itself "<slug> (@<slug>)" and get indexed by Google.
132147
if (!isDetailPage && !profileFound && serialized.length === 0) {
133-
return Astro.redirect(`/${locale}/workflows/`);
148+
if (indexFetchFailed) {
149+
return new Response(null, {
150+
status: 502,
151+
statusText: 'Bad Gateway',
152+
});
153+
}
154+
return new Response(null, {
155+
status: 404,
156+
statusText: 'Not Found',
157+
});
134158
}
135159
136160
const canonicalUrl = absoluteUrl(`/workflows/${slug}/`);

site/src/pages/[locale]/workflows/category/[type].astro

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { localizeUrl } from '../../../../i18n/utils';
1818
import { SITE_ORIGIN, absoluteUrl } from '../../../../config/site';
1919
import { categoryPath } from '../../../../lib/routes';
2020
import { badgeVariants } from '../../../../components/ui/badge';
21-
import { loadSerializedTemplates, type SerializedTemplate, type MediaType } from '../../../../lib/hub-api';
21+
import { loadSerializedTemplates, type MediaType } from '../../../../lib/hub-api';
2222
2323
const validTypes: MediaType[] = ['image', 'video', 'audio', '3d'];
2424
@@ -31,14 +31,24 @@ const categoryTranslationKeys: Record<MediaType, string> = {
3131
3232
const { locale, type } = Astro.params;
3333
34+
if (Astro.url.pathname.endsWith('.json/')) {
35+
return new Response(null, {
36+
status: 404,
37+
statusText: 'Not Found',
38+
});
39+
}
40+
3441
// Validate locale
3542
if (!locale || !LOCALES.includes(locale as Locale) || locale === DEFAULT_LOCALE) {
3643
return Astro.redirect(`/workflows/category/${type || ''}/`);
3744
}
3845
3946
// Validate type
4047
if (!type || !validTypes.includes(type as MediaType)) {
41-
return Astro.redirect(`/${locale}/workflows/`);
48+
return new Response(null, {
49+
status: 404,
50+
statusText: 'Not Found',
51+
});
4252
}
4353
4454
const typedLocale = locale as Locale;
@@ -51,6 +61,13 @@ const description = t('meta.description', typedLocale);
5161
const allSerialized = await loadSerializedTemplates(() => getCollection('templates'));
5262
const templates = allSerialized.filter((tmpl) => tmpl.mediaType === mediaType);
5363
64+
if (templates.length === 0) {
65+
return new Response(null, {
66+
status: 404,
67+
statusText: 'Not Found',
68+
});
69+
}
70+
5471
const basePath = categoryPath(mediaType);
5572
const canonicalUrl = absoluteUrl(localizeUrl(basePath, typedLocale));
5673

site/src/pages/[locale]/workflows/model/[name].astro

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,17 @@ import { slugify } from '../../../../lib/slugify';
1919
import { SITE_ORIGIN, absoluteUrl } from '../../../../config/site';
2020
import { modelPath } from '../../../../lib/routes';
2121
import { badgeVariants } from '../../../../components/ui/badge';
22-
import { loadSerializedTemplates, type SerializedTemplate } from '../../../../lib/hub-api';
22+
import { loadSerializedTemplates } from '../../../../lib/hub-api';
2323
2424
const { locale, name } = Astro.params;
2525
26+
if (Astro.url.pathname.endsWith('.json/')) {
27+
return new Response(null, {
28+
status: 404,
29+
statusText: 'Not Found',
30+
});
31+
}
32+
2633
// Validate locale
2734
if (!locale || !LOCALES.includes(locale as Locale) || locale === DEFAULT_LOCALE) {
2835
return Astro.redirect(`/workflows/model/${name || ''}/`);
@@ -38,7 +45,10 @@ const matchingTemplates = allTemplates.filter((tmpl) =>
3845
);
3946
4047
if (matchingTemplates.length === 0) {
41-
return Astro.redirect(`/${locale}/workflows/`);
48+
return new Response(null, {
49+
status: 404,
50+
statusText: 'Not Found',
51+
});
4252
}
4353
4454
// Get display name from the first matching model

site/src/pages/[locale]/workflows/tag/[tag].astro

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,17 @@ import { localizeUrl } from '../../../../i18n/utils';
1818
import { tagSlug, tagDisplayName } from '../../../../lib/tag-aliases';
1919
import { SITE_ORIGIN, absoluteUrl } from '../../../../config/site';
2020
import { badgeVariants } from '../../../../components/ui/badge';
21-
import { loadSerializedTemplates, type SerializedTemplate } from '../../../../lib/hub-api';
21+
import { loadSerializedTemplates } from '../../../../lib/hub-api';
2222
2323
const { locale, tag } = Astro.params;
2424
25+
if (Astro.url.pathname.endsWith('.json/')) {
26+
return new Response(null, {
27+
status: 404,
28+
statusText: 'Not Found',
29+
});
30+
}
31+
2532
// Validate locale
2633
if (!locale || !LOCALES.includes(locale as Locale) || locale === DEFAULT_LOCALE) {
2734
return Astro.redirect(`/workflows/tag/${tag || ''}/`);
@@ -37,7 +44,10 @@ const matchingTemplates = allTemplates.filter((tmpl) =>
3744
);
3845
3946
if (matchingTemplates.length === 0) {
40-
return Astro.redirect(`/${locale}/workflows/`);
47+
return new Response(null, {
48+
status: 404,
49+
statusText: 'Not Found',
50+
});
4151
}
4252
4353
// Get display name from the first matching tag

site/src/pages/workflows/[username].astro

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ Astro.response.headers.set(
3131
const { username: slugParam } = Astro.params;
3232
const locale = getLocaleFromPath(Astro.url.pathname);
3333
34+
if (Astro.url.pathname.endsWith('.json/')) {
35+
return new Response(null, {
36+
status: 404,
37+
statusText: 'Not Found',
38+
});
39+
}
40+
3441
if (!slugParam) {
3542
return Astro.redirect('/workflows/');
3643
}
@@ -49,8 +56,15 @@ if (detailShareId) {
4956
const is404 = err instanceof Error && err.message.includes('404');
5057
if (!is404) {
5158
console.error('Hub API error on ISR detail:', err);
59+
return new Response(null, {
60+
status: 502,
61+
statusText: 'Bad Gateway',
62+
});
5263
}
53-
return Astro.redirect('/workflows/');
64+
return new Response(null, {
65+
status: 404,
66+
statusText: 'Not Found',
67+
});
5468
}
5569
}
5670
@@ -83,21 +97,32 @@ try {
8397
// Fetch workflows for this creator from index
8498
const profiles = await getProfileCache();
8599
let serialized: SerializedTemplate[] = [];
100+
let indexFetchFailed = false;
86101
87102
try {
88103
const entries = await listWorkflowIndex();
89104
const filtered = entries.filter((e) => (e.profile?.username || e.username) === slugParam);
90105
serialized = filtered.map((e) => serializeIndexEntry(e, profiles));
91-
} catch {
92-
// Index API failed — show empty grid
106+
} catch (err) {
107+
indexFetchFailed = true;
108+
console.error('Hub API failed for creator grid:', err);
93109
}
94110
95111
// Slug is bogus (e.g. "undefined" or a malformed "<name>-" suffix from a broken
96112
// link): not a workflow detail, no profile, and no workflows — redirect instead
97113
// of rendering a page that would title itself "<slug> (@<slug>)" and get
98114
// indexed by Google.
99115
if (!isDetailPage && !profileFound && serialized.length === 0) {
100-
return Astro.redirect('/workflows/');
116+
if (indexFetchFailed) {
117+
return new Response(null, {
118+
status: 502,
119+
statusText: 'Bad Gateway',
120+
});
121+
}
122+
return new Response(null, {
123+
status: 404,
124+
statusText: 'Not Found',
125+
});
101126
}
102127
103128
const canonicalUrl = absoluteUrl(`/workflows/${slugParam}/`);

0 commit comments

Comments
 (0)