Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions shared/models/news.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export class DBNews implements IDBContentDoc {
readonly created_by: number | null;
readonly deleted: boolean | null;
is_draft: boolean | null;
readonly published_at: Date | null;
readonly subscriber_count?: number;
readonly title: string;
readonly total_views?: number;
Expand All @@ -50,6 +51,7 @@ export class News implements IContentDoc {
isDraft: boolean;
modifiedAt: Date | null;
profileBadge: ProfileBadge | null;
publishedAt: Date | null;
previousSlugs: string[];
slug: string;
subscriberCount: number;
Expand Down Expand Up @@ -86,6 +88,7 @@ export class News implements IContentDoc {
heroImage: heroImage || null,
modifiedAt: news.modified_at ? new Date(news.modified_at) : null,
profileBadge: news.profile_badge ? ProfileBadge.fromDB(news.profile_badge) : null,
publishedAt: news.published_at ? new Date(news.published_at) : null,
previousSlugs: news.previous_slugs,
slug: news.slug,
subscriberCount: news.subscriber_count || 0,
Expand Down
2 changes: 1 addition & 1 deletion src/pages/News/NewsListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export const NewsListItem = ({ news, query }: IProps) => {
</Flex>

<Text variant="auxiliary">
<DisplayDate action={'Published'} createdAt={news.createdAt} />
<DisplayDate action={'Published'} createdAt={news.publishedAt || news.createdAt} />
</Text>

{news.summary && (
Expand Down
28 changes: 23 additions & 5 deletions src/pages/News/NewsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { observer } from 'mobx-react';
import { Category, ContentImageLightbox, ContentStatistics, DisplayDate, ProfileBadgeContentLabel, TagList } from 'oa-components';
import {
Category,
ContentImageLightbox,
ContentStatistics,
DisplayDate,
ProfileBadgeContentLabel,
TagList,
} from 'oa-components';
import type { News } from 'oa-shared';
import { useEffect, useMemo, useRef, useState } from 'react';
import { Link } from 'react-router';
Expand Down Expand Up @@ -47,7 +54,11 @@ export const NewsPage = observer(({ news }: IProps) => {
<Flex sx={{ flexDirection: 'column', gap: 2 }}>
{news.heroImage && (
<AspectRatio ratio={2 / 1}>
<Image ref={heroImageRef} src={news.heroImage.publicUrl} sx={{ borderRadius: 2, width: '100%', cursor: 'pointer' }} />
<Image
ref={heroImageRef}
src={news.heroImage.publicUrl}
sx={{ borderRadius: 2, width: '100%', cursor: 'pointer' }}
/>
</AspectRatio>
)}

Expand All @@ -62,15 +73,22 @@ export const NewsPage = observer(({ news }: IProps) => {
<Flex sx={{ alignItems: 'center', gap: 2 }}>
{news.category && <Category category={news.category} />}
{news.profileBadge && <ProfileBadgeContentLabel profileBadge={news.profileBadge} />}
{news.tags && <TagList data-cy="news-tags" tags={news.tags.map((t) => ({ label: t.name }))} />}
{news.tags && (
<TagList data-cy="news-tags" tags={news.tags.map((t) => ({ label: t.name }))} />
)}
</Flex>

<Heading as="h1" data-cy="news-title" data-testid="news-title" sx={{ textAlign: 'center' }}>
<Heading
as="h1"
data-cy="news-title"
data-testid="news-title"
sx={{ textAlign: 'center' }}
>
{news.title}
</Heading>

<Text variant="auxiliary">
<DisplayDate action={'Published'} createdAt={news.createdAt} />
<DisplayDate action={'Published'} createdAt={news.publishedAt || news.createdAt} />
</Text>

{news.isDraft && <DraftTag />}
Expand Down
30 changes: 26 additions & 4 deletions src/routes/api.news.$id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,14 @@ export const action = async ({ request, params }: LoaderFunctionArgs) => {

const currentNews = await newsServiceServer.getById(id, client);

const { valid, status, statusText } = await validateRequest(params, request, claims.data.claims.sub, data, currentNews, client);
const { valid, status, statusText } = await validateRequest(
params,
request,
claims.data.claims.sub,
data,
currentNews,
client,
);

if (!valid) {
return Response.json({}, { headers, status, statusText });
Expand All @@ -61,13 +68,17 @@ export const action = async ({ request, params }: LoaderFunctionArgs) => {

const previousSlugs = contentServiceServer.updatePreviousSlugs(currentNews, data.slug);

const publishedAt =
currentNews.is_draft && !data.isDraft ? (currentNews.published_at ?? new Date()) : undefined;

const newsResult = await client
.from('news')
.update({
body: data.body,
category: data.category,
is_draft: data.isDraft,
modified_at: new Date(),
...(publishedAt !== undefined && { published_at: publishedAt }),
slug: data.slug,
previous_slugs: previousSlugs,
profile_badge: data.profileBadge,
Expand All @@ -86,7 +97,11 @@ export const action = async ({ request, params }: LoaderFunctionArgs) => {
const news = News.fromDB(newsResult.data[0], []);

if (newHeroImage) {
const mediaFiles = await storageServiceServer.uploadImage([newHeroImage], `news/${news.id}`, client);
const mediaFiles = await storageServiceServer.uploadImage(
[newHeroImage],
`news/${news.id}`,
client,
);

if (mediaFiles?.media?.length) {
await client
Expand All @@ -96,7 +111,11 @@ export const action = async ({ request, params }: LoaderFunctionArgs) => {
})
.eq('id', news.id);

const [image] = storageServiceServer.getPublicUrls(client, mediaFiles.media, IMAGE_SIZES.GALLERY);
const [image] = storageServiceServer.getPublicUrls(
client,
mediaFiles.media,
IMAGE_SIZES.GALLERY,
);

news.heroImage = image;
}
Expand Down Expand Up @@ -139,7 +158,10 @@ async function validateRequest(
return { status: 400, statusText: 'News not found' };
}

if (currentNews.slug !== data.slug && (await contentServiceServer.isDuplicateExistingSlug(data.slug, currentNews.id, client, 'news'))) {
if (
currentNews.slug !== data.slug &&
(await contentServiceServer.isDuplicateExistingSlug(data.slug, currentNews.id, client, 'news'))
) {
return {
status: 409,
statusText: 'This news already exists',
Expand Down
2 changes: 2 additions & 0 deletions src/routes/api.news.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const loader = async ({ request }) => {
created_at,
created_by,
modified_at,
published_at,
is_draft,
comment_count,
body,
Expand Down Expand Up @@ -193,6 +194,7 @@ export const action = async ({ request }: LoaderFunctionArgs) => {
category: data.category,
created_by: profile.id,
is_draft: data.isDraft,
published_at: data.isDraft ? null : new Date(),
moderation: 'accepted' as Moderation,
profile_badge: data.profileBadge,
slug,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ALTER TABLE news ADD COLUMN published_at timestamptz;

UPDATE news SET published_at = created_at WHERE (is_draft = false OR is_draft IS NULL);
3 changes: 2 additions & 1 deletion supabase/schemas/news.sql
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ CREATE TABLE IF NOT EXISTS "public"."news" (
"summary" "text",
"fts" "tsvector" GENERATED ALWAYS AS ("to_tsvector"('"english"'::"regconfig", ((("title" || ' '::"text") || "body") || ("summary" || ''::"text")))) STORED,
"is_draft" boolean DEFAULT false NOT NULL,
"profile_badge" bigint
"profile_badge" bigint,
"published_at" timestamp with time zone
);

CREATE OR REPLACE FUNCTION "public"."news_search_fields"("public"."news") RETURNS "text"
Expand Down