Skip to content

Commit a139ccb

Browse files
authored
Protect against unpublished content (#99)
* Protect against unpublished content * Add safety checks at the top of transformLink() so it gracefully skips missing data * Add case array to transformLink * Update api.ts * Protect against invalid semesters * Protect against undefined semesters * Protect against undefined semesters * Fix bugs in transformlink * Also select slug * Remove fields.slug * Update api.ts * Update api.ts
1 parent 91ff0d9 commit a139ccb

4 files changed

Lines changed: 85 additions & 9 deletions

File tree

src/lib/utils/api.ts

Lines changed: 76 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ import { documentToHtmlString } from "@contentful/rich-text-html-renderer";
22
import { BLOCKS, INLINES } from "@contentful/rich-text-types";
33
import type { ContentfulClientApi, ContentType, Entry } from "contentful";
44
import contentful from "contentful";
5-
5+
66
type ContentWrapperGetOptions = {
77
/**
88
* Use draft data from the Contentful preview API if the ContentWrapper is authorized to do so.
99
*/
1010
allowPreview: boolean;
1111
};
1212

13-
export class ContentWrapper {
13+
export class ContentWrapper {
1414
client: ContentfulClientApi;
1515
previewClient: ContentfulClientApi | undefined;
1616

@@ -25,7 +25,7 @@ export class ContentWrapper {
2525
});
2626
}
2727
}
28-
28+
2929
async get(
3030
entity: string,
3131
contentfulOptions: any = {},
@@ -74,6 +74,11 @@ export class ContentWrapper {
7474
res[id] = documentToHtmlString(res[id], {
7575
renderNode: {
7676
[BLOCKS.EMBEDDED_ASSET]: (node) => {
77+
// Add this safety check: If the asset was deleted/unpublished, skip it.
78+
if (!node.data?.target?.fields) {
79+
return "";
80+
}
81+
7782
const { file, title, description } =
7883
node.data.target.fields;
7984
return `
@@ -119,20 +124,83 @@ export class ContentWrapper {
119124
}
120125

121126
async transformLink(link: any, type: string | undefined): Promise<any> {
127+
// Undefined type --> this is a primitive value
128+
// Just return the string directly!
129+
if (!type) return link;
130+
131+
// Now we know it's supposed to be a Link (Asset or Entry).
132+
// Ignore completely empty/broken links.
133+
if (!link || !link.sys) return undefined;
134+
122135
switch (type) {
123136
case "Asset":
124-
return link.src !== undefined // why do I need to do this?
125-
? link
126-
: { src: `https:${link.fields.file.url}`, alt: link.fields.title };
137+
if (link.src !== undefined) return link;
138+
// Safety check for deleted/unpublished assets
139+
if (!link.fields || !link.fields.file) return undefined;
140+
return { src: `https:${link.fields.file.url}`, alt: link.fields.title };
127141

128142
case "Entry":
143+
// SAFETY CHECK: If the entry is unpublished/unresolved, skip it
144+
if (!link.sys.contentType) return undefined;
145+
129146
return await this.serialize(
130147
link,
131148
await this.client.getContentType(link.sys.contentType.sys.id)
132149
);
133-
134-
case undefined:
150+
151+
default:
135152
return link;
136153
}
154+
155+
// WEBSITE FIX ATTEMPT 1
156+
// Ignore completely empty links
157+
// if (!link || !link.sys) return undefined;
158+
159+
// switch (type) {
160+
// case "Asset":
161+
// if (link.src !== undefined) return link;
162+
// // Safety check for deleted/unpublished assets
163+
// if (!link.fields || !link.fields.file) return undefined;
164+
// return { src: `https:${link.fields.file.url}`, alt: link.fields.title };
165+
166+
// case "Entry":
167+
// // 2. SAFETY CHECK: If the entry is unpublished/unresolved, skip it
168+
// if (!link.sys.contentType) return undefined;
169+
170+
// return await this.serialize(
171+
// link,
172+
// await this.client.getContentType(link.sys.contentType.sys.id)
173+
// );
174+
175+
// case "Array":
176+
// const transformedArray = await Promise.all(
177+
// res[id].map((link: any) =>
178+
// this.transformLink(link, field.items!.linkType)
179+
// )
180+
// );
181+
// // Filter out any undefined items that were unpublished
182+
// res[id] = transformedArray.filter((item) => item !== undefined);
183+
// break;
184+
185+
// case undefined:
186+
// return link;
187+
// }
188+
189+
// ORIGINAL
190+
// switch (type) {
191+
// case "Asset":
192+
// return link.src !== undefined // why do I need to do this?
193+
// ? link
194+
// : { src: `https:${link.fields.file.url}`, alt: link.fields.title };
195+
196+
// case "Entry":
197+
// return await this.serialize(
198+
// link,
199+
// await this.client.getContentType(link.sys.contentType.sys.id)
200+
// );
201+
202+
// case undefined:
203+
// return link;
204+
// }
137205
}
138206
}

src/lib/utils/projects.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ export function generateProjectsInfo(projects: Project[]): ProjectsInfo {
3434

3535
projects.forEach((project) =>
3636
project.semester.forEach((semester) => {
37+
if (!semester) return; // skip empty/undefined semesters
38+
3739
if (projectArrayMap[semester] !== undefined) {
3840
projectArrayMap[semester].push(project);
3941
} else {

src/lib/utils/schema.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ export function setImageHeight(src: string, height: number): string {
155155
}
156156

157157
export function parseSemester(semester: string): Semester {
158+
if (!semester) return { season: "", year: 0 }; // protect againt undefined semester
159+
158160
const [season, yearString] = semester.split(" ");
159161

160162
const year = parseInt(yearString);
@@ -163,6 +165,7 @@ export function parseSemester(semester: string): Semester {
163165
}
164166

165167
export function semesterToId(semester: string): string {
168+
if (!semester) return ""; // protect againt undefined semester
166169
return semester.split(" ").join("-").toLowerCase();
167170
}
168171

src/routes/+layout.server.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ async function getSemesters(contentWrapper: ContentWrapper) {
2222
select: "fields.semester",
2323
});
2424

25-
const { semesters } = generateProjectsInfo(projects);
25+
// SAFETY CHECK: Ignore any projects that don't have a semester defined
26+
const validProjects = projects.filter((project) => project && project.semester);
27+
28+
const { semesters } = generateProjectsInfo(validProjects);
2629

2730
return semesters;
2831
}

0 commit comments

Comments
 (0)