feat: make preview_image_url nullable across post-related DTOs and database schema
Some checks failed
Frontend CI / build (push) Failing after 56s
Some checks failed
Frontend CI / build (push) Failing after 56s
This commit is contained in:
parent
df5bba022e
commit
4c4c5c357e
@ -12,8 +12,8 @@ pub struct CreatePostRequestDto {
|
||||
pub content: String,
|
||||
pub label_ids: Vec<i32>,
|
||||
|
||||
#[schema(format = Uri)]
|
||||
pub preview_image_url: String,
|
||||
#[schema(required, format = Uri)]
|
||||
pub preview_image_url: Option<String>,
|
||||
|
||||
#[schema(required, format = DateTime)]
|
||||
pub published_time: Option<String>,
|
||||
|
@ -14,7 +14,7 @@ pub struct PostInfoResponseDto {
|
||||
pub labels: Vec<LabelResponseDto>,
|
||||
|
||||
#[schema(format = Uri)]
|
||||
pub preview_image_url: String,
|
||||
pub preview_image_url: Option<String>,
|
||||
|
||||
#[schema(format = DateTime)]
|
||||
pub published_time: Option<String>,
|
||||
|
@ -12,8 +12,8 @@ pub struct UpdatePostRequestDto {
|
||||
pub content: String,
|
||||
pub label_ids: Vec<i32>,
|
||||
|
||||
#[schema(format = Uri)]
|
||||
pub preview_image_url: String,
|
||||
#[schema(required, format = Uri)]
|
||||
pub preview_image_url: Option<String>,
|
||||
|
||||
#[schema(required, format = DateTime)]
|
||||
pub published_time: Option<String>,
|
||||
|
@ -7,7 +7,7 @@ pub struct PostInfoMapper {
|
||||
pub semantic_id: String,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub preview_image_url: String,
|
||||
pub preview_image_url: Option<String>,
|
||||
pub published_time: Option<NaiveDateTime>,
|
||||
pub labels: Vec<LabelMapper>,
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ pub struct PostInfo {
|
||||
pub semantic_id: String,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub preview_image_url: String,
|
||||
pub preview_image_url: Option<String>,
|
||||
pub labels: Vec<Label>,
|
||||
pub published_time: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ pub struct PostInfoWithLabelRecord {
|
||||
pub semantic_id: String,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub preview_image_url: String,
|
||||
pub preview_image_url: Option<String>,
|
||||
pub published_time: Option<NaiveDateTime>,
|
||||
|
||||
pub label_id: Option<i32>,
|
||||
|
@ -6,7 +6,7 @@ pub struct PostWithLabelRecord {
|
||||
pub semantic_id: String,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub preview_image_url: String,
|
||||
pub preview_image_url: Option<String>,
|
||||
pub content: String,
|
||||
pub published_time: Option<NaiveDateTime>,
|
||||
|
||||
|
@ -0,0 +1,2 @@
|
||||
-- Revert post.preview_image_url to NOT NULL
|
||||
ALTER TABLE "post" ALTER COLUMN "preview_image_url" SET NOT NULL;
|
@ -0,0 +1,2 @@
|
||||
-- Make post.preview_image_url nullable
|
||||
ALTER TABLE "post" ALTER COLUMN "preview_image_url" DROP NOT NULL;
|
@ -6,7 +6,7 @@ export class CreatePostRequestDto {
|
||||
readonly description: string;
|
||||
readonly content: string;
|
||||
readonly labelIds: number[];
|
||||
readonly previewImageUrl: URL;
|
||||
readonly previewImageUrl: URL | null;
|
||||
readonly publishedTime: Date | null;
|
||||
|
||||
private constructor(props: {
|
||||
@ -15,7 +15,7 @@ export class CreatePostRequestDto {
|
||||
description: string;
|
||||
content: string;
|
||||
labelIds: number[];
|
||||
previewImageUrl: URL;
|
||||
previewImageUrl: URL | null;
|
||||
publishedTime: Date | null;
|
||||
}) {
|
||||
this.semanticId = props.semanticId;
|
||||
@ -33,7 +33,7 @@ export class CreatePostRequestDto {
|
||||
description: '',
|
||||
content: '',
|
||||
labelIds: [],
|
||||
previewImageUrl: new URL('https://example.com'),
|
||||
previewImageUrl: null,
|
||||
publishedTime: null,
|
||||
});
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ export const PostInfoResponseSchema = z.object({
|
||||
semantic_id: z.string(),
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
preview_image_url: z.url(),
|
||||
preview_image_url: z.url().nullable(),
|
||||
labels: z.array(LabelResponseSchema),
|
||||
published_time: z.iso.datetime({ offset: true }).nullable(),
|
||||
});
|
||||
@ -17,7 +17,7 @@ export class PostInfoResponseDto {
|
||||
readonly semanticId: string;
|
||||
readonly title: string;
|
||||
readonly description: string;
|
||||
readonly previewImageUrl: URL;
|
||||
readonly previewImageUrl: URL | null;
|
||||
readonly labels: readonly LabelResponseDto[];
|
||||
readonly publishedTime: Date | null;
|
||||
|
||||
@ -26,7 +26,7 @@ export class PostInfoResponseDto {
|
||||
semanticId: string;
|
||||
title: string;
|
||||
description: string;
|
||||
previewImageUrl: URL;
|
||||
previewImageUrl: URL | null;
|
||||
labels: LabelResponseDto[];
|
||||
publishedTime: Date | null;
|
||||
}) {
|
||||
@ -47,12 +47,17 @@ export class PostInfoResponseDto {
|
||||
published_time = new Date(parsedJson.published_time);
|
||||
}
|
||||
|
||||
let preview_image_url: URL | null = null;
|
||||
if (parsedJson.preview_image_url !== null) {
|
||||
preview_image_url = new URL(parsedJson.preview_image_url);
|
||||
}
|
||||
|
||||
return new PostInfoResponseDto({
|
||||
id: parsedJson.id,
|
||||
semanticId: parsedJson.semantic_id,
|
||||
title: parsedJson.title,
|
||||
description: parsedJson.description,
|
||||
previewImageUrl: new URL(parsedJson.preview_image_url),
|
||||
previewImageUrl: preview_image_url,
|
||||
labels: parsedJson.labels.map((label) => LabelResponseDto.fromJson(label)),
|
||||
publishedTime: published_time,
|
||||
});
|
||||
|
@ -9,7 +9,7 @@ export class PostInfoViewModel {
|
||||
readonly semanticId: string;
|
||||
readonly title: string;
|
||||
readonly description: string;
|
||||
readonly previewImageUrl: URL;
|
||||
readonly previewImageUrl: URL | null;
|
||||
readonly labels: readonly LabelViewModel[];
|
||||
readonly publishedTime: Date | null;
|
||||
|
||||
@ -18,7 +18,7 @@ export class PostInfoViewModel {
|
||||
semanticId: string;
|
||||
title: string;
|
||||
description: string;
|
||||
previewImageUrl: URL;
|
||||
previewImageUrl: URL | null;
|
||||
labels: readonly LabelViewModel[];
|
||||
publishedTime: Date | null;
|
||||
}) {
|
||||
@ -49,12 +49,17 @@ export class PostInfoViewModel {
|
||||
publishedTime = new Date(props.publishedTime);
|
||||
}
|
||||
|
||||
let previewImageUrl: URL | null = null;
|
||||
if (props.previewImageUrl) {
|
||||
previewImageUrl = new URL(props.previewImageUrl);
|
||||
}
|
||||
|
||||
return new PostInfoViewModel({
|
||||
id: props.id,
|
||||
semanticId: props.semanticId,
|
||||
title: props.title,
|
||||
description: props.description,
|
||||
previewImageUrl: new URL(props.previewImageUrl),
|
||||
previewImageUrl: previewImageUrl,
|
||||
labels: props.labels.map((label) => LabelViewModel.rehydrate(label)),
|
||||
publishedTime: publishedTime,
|
||||
});
|
||||
@ -74,7 +79,7 @@ export class PostInfoViewModel {
|
||||
semanticId: this.semanticId,
|
||||
title: this.title,
|
||||
description: this.description,
|
||||
previewImageUrl: this.previewImageUrl.href,
|
||||
previewImageUrl: this.previewImageUrl?.href ?? null,
|
||||
labels: this.labels.map((label) => label.dehydrate()),
|
||||
publishedTime: this.publishedTime?.getTime() ?? null,
|
||||
};
|
||||
@ -86,7 +91,7 @@ export interface DehydratedPostInfoProps {
|
||||
semanticId: string;
|
||||
title: string;
|
||||
description: string;
|
||||
previewImageUrl: string;
|
||||
previewImageUrl: string | null;
|
||||
labels: DehydratedLabelProps[];
|
||||
publishedTime: number | null;
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ export class PostInfo {
|
||||
readonly semanticId: string;
|
||||
readonly title: string;
|
||||
readonly description: string;
|
||||
readonly previewImageUrl: URL;
|
||||
readonly previewImageUrl: URL | null;
|
||||
readonly labels: readonly Label[];
|
||||
readonly publishedTime: Date | null;
|
||||
|
||||
@ -14,7 +14,7 @@ export class PostInfo {
|
||||
semanticId: string;
|
||||
title: string;
|
||||
description: string;
|
||||
previewImageUrl: URL;
|
||||
previewImageUrl: URL | null;
|
||||
labels: readonly Label[];
|
||||
publishedTime: Date | null;
|
||||
}) {
|
||||
|
@ -14,27 +14,28 @@
|
||||
|
||||
const md = markdownit();
|
||||
const parsedContent = $derived(state.data?.content ? md.render(state.data.content) : '');
|
||||
const postInfo = $derived(state.data?.info);
|
||||
|
||||
onMount(() => postBloc.dispatch({ event: PostEventType.PostLoadedEvent, id: id }));
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{generateTitle(state.data?.info.title)}</title>
|
||||
{#if state.data}
|
||||
<meta name="description" content={state.data.info.description} />
|
||||
{#if state.data.info.isPublished}
|
||||
<title>{generateTitle(postInfo?.title)}</title>
|
||||
{#if postInfo}
|
||||
<meta name="description" content={postInfo.description} />
|
||||
{#if postInfo.isPublished}
|
||||
<StructuredData
|
||||
headline={state.data.info.title}
|
||||
description={state.data.info.description}
|
||||
datePublished={state.data.info.publishedTime!}
|
||||
image={state.data.info.previewImageUrl}
|
||||
headline={postInfo.title}
|
||||
description={postInfo.description}
|
||||
datePublished={postInfo.publishedTime!}
|
||||
image={postInfo.previewImageUrl}
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
</svelte:head>
|
||||
<article class="content-container prose pb-10 prose-gray">
|
||||
{#if state.data}
|
||||
<PostContentHeader postInfo={state.data.info} />
|
||||
<article class="content-container prose prose-gray pb-10">
|
||||
{#if postInfo}
|
||||
<PostContentHeader {postInfo} />
|
||||
<div class="max-w-3xl">
|
||||
<hr />
|
||||
<SafeHtml html={parsedContent} />
|
||||
|
@ -7,11 +7,11 @@
|
||||
let isImageLoading = $state(true);
|
||||
let isImageError = $state(false);
|
||||
|
||||
function handleImageLoad() {
|
||||
function onImageLoad() {
|
||||
isImageLoading = false;
|
||||
}
|
||||
|
||||
function handleImageError() {
|
||||
function onImageError() {
|
||||
isImageLoading = false;
|
||||
isImageError = true;
|
||||
}
|
||||
@ -27,10 +27,10 @@
|
||||
class="rounded-2xl object-cover transition-opacity duration-300
|
||||
{isImageLoading ? 'opacity-0' : 'opacity-100'}
|
||||
{isImageError ? 'hidden' : ''}"
|
||||
src={postInfo.previewImageUrl.href}
|
||||
src={postInfo.previewImageUrl?.href}
|
||||
alt={postInfo.title}
|
||||
onload={handleImageLoad}
|
||||
onerror={handleImageError}
|
||||
onload={onImageLoad}
|
||||
onerror={onImageError}
|
||||
/>
|
||||
{#if isImageLoading || isImageError}
|
||||
<div class="absolute inset-0 flex items-center justify-center bg-gray-200"></div>
|
||||
|
@ -10,7 +10,7 @@
|
||||
headline: string;
|
||||
description: string;
|
||||
datePublished: Date;
|
||||
image: URL;
|
||||
image: URL | null;
|
||||
} = $props();
|
||||
|
||||
const structuredData = $derived({
|
||||
@ -19,7 +19,7 @@
|
||||
headline: headline,
|
||||
description: description,
|
||||
datePublished: datePublished.toISOString(),
|
||||
image: image.href,
|
||||
image: image?.href,
|
||||
});
|
||||
|
||||
const jsonLdScript = $derived(
|
||||
|
Loading…
x
Reference in New Issue
Block a user