BLOG-118 Fix to allow nullable published_time to support unpublished posts #121

Merged
squid merged 2 commits from BLOG-118_fix_error_when_access_unpublished_post into main 2025-08-06 21:36:54 +08:00
4 changed files with 37 additions and 20 deletions
Showing only changes of commit 8440d9068d - Show all commits

View File

@ -8,7 +8,7 @@ export const PostInfoResponseSchema = z.object({
description: z.string(),
preview_image_url: z.url(),
labels: z.array(LabelResponseSchema),
published_time: z.iso.datetime({ offset: true })
published_time: z.iso.datetime({ offset: true }).nullable()
});
export class PostInfoResponseDto {
@ -17,7 +17,7 @@ export class PostInfoResponseDto {
readonly description: string;
readonly previewImageUrl: URL;
readonly labels: readonly LabelResponseDto[];
readonly publishedTime: Date;
readonly publishedTime: Date | null;
private constructor(props: {
id: number;
@ -25,7 +25,7 @@ export class PostInfoResponseDto {
description: string;
previewImageUrl: URL;
labels: LabelResponseDto[];
publishedTime: Date;
publishedTime: Date | null;
}) {
this.id = props.id;
this.title = props.title;
@ -37,13 +37,19 @@ export class PostInfoResponseDto {
static fromJson(json: unknown): PostInfoResponseDto {
const parsedJson = PostInfoResponseSchema.parse(json);
let published_time: Date | null = null;
if (parsedJson.published_time !== null) {
published_time = new Date(parsedJson.published_time);
}
return new PostInfoResponseDto({
id: parsedJson.id,
title: parsedJson.title,
description: parsedJson.description,
previewImageUrl: new URL(parsedJson.preview_image_url),
labels: parsedJson.labels.map((label) => LabelResponseDto.fromJson(label)),
publishedTime: new Date(parsedJson.published_time)
publishedTime: published_time
});
}

View File

@ -10,7 +10,7 @@ export class PostInfoViewModel {
readonly description: string;
readonly previewImageUrl: URL;
readonly labels: readonly LabelViewModel[];
readonly publishedTime: Date;
readonly publishedTime: Date | null;
private constructor(props: {
id: number;
@ -18,7 +18,7 @@ export class PostInfoViewModel {
description: string;
previewImageUrl: URL;
labels: readonly LabelViewModel[];
publishedTime: Date;
publishedTime: Date | null;
}) {
this.id = props.id;
this.title = props.title;
@ -40,18 +40,27 @@ export class PostInfoViewModel {
}
static rehydrate(props: DehydratedPostInfoProps): PostInfoViewModel {
let publishedTime: Date | null = null;
if (props.publishedTime !== null) {
publishedTime = new Date(props.publishedTime);
}
return new PostInfoViewModel({
id: props.id,
title: props.title,
description: props.description,
previewImageUrl: new URL(props.previewImageUrl),
labels: props.labels.map((label) => LabelViewModel.rehydrate(label)),
publishedTime: new Date(props.publishedTime)
publishedTime: publishedTime
});
}
get formattedPublishedTime(): string {
return this.publishedTime.toISOString().slice(0, 10);
get isPublished(): boolean {
return this.publishedTime !== null;
}
get formattedPublishedTime(): string | null {
return this.publishedTime?.toISOString().slice(0, 10) ?? null;
}
dehydrate(): DehydratedPostInfoProps {
@ -61,7 +70,7 @@ export class PostInfoViewModel {
description: this.description,
previewImageUrl: this.previewImageUrl.href,
labels: this.labels.map((label) => label.dehydrate()),
publishedTime: this.publishedTime.getTime()
publishedTime: this.publishedTime?.getTime() ?? null
};
}
}
@ -72,5 +81,5 @@ export interface DehydratedPostInfoProps {
description: string;
previewImageUrl: string;
labels: DehydratedLabelProps[];
publishedTime: number;
publishedTime: number | null;
}

View File

@ -6,7 +6,7 @@ export class PostInfo {
readonly description: string;
readonly previewImageUrl: URL;
readonly labels: readonly Label[];
readonly publishedTime: Date;
readonly publishedTime: Date | null;
constructor(props: {
id: number;
@ -14,7 +14,7 @@ export class PostInfo {
description: string;
previewImageUrl: URL;
labels: readonly Label[];
publishedTime: Date;
publishedTime: Date | null;
}) {
this.id = props.id;
this.title = props.title;

View File

@ -22,15 +22,17 @@
<title>{generateTitle(state.data?.info.title)}</title>
{#if state.data}
<meta name="description" content={state.data.info.description} />
<StructuredData
headline={state.data.info.title}
description={state.data.info.description}
datePublished={state.data.info.publishedTime}
image={state.data.info.previewImageUrl}
/>
{#if state.data.info.isPublished}
<StructuredData
headline={state.data.info.title}
description={state.data.info.description}
datePublished={state.data.info.publishedTime!}
image={state.data.info.previewImageUrl}
/>
{/if}
{/if}
</svelte:head>
<article class="container prose pb-10 prose-gray">
<article class="prose prose-gray container pb-10">
{#if state.data}
<PostContentHeader postInfo={state.data.info} />
<div class="max-w-3xl">