From 7ceb96a93eb4e1450db11ff73a4cb6757e801305 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 20 Jul 2023 13:35:43 +0800 Subject: [PATCH 1/7] fix edit rss ui --- packages/api/src/utils/createTask.ts | 15 ++++++ packages/web/pages/settings/rss/index.tsx | 66 ++++++++++++++--------- 2 files changed, 55 insertions(+), 26 deletions(-) diff --git a/packages/api/src/utils/createTask.ts b/packages/api/src/utils/createTask.ts index 4db367f29..220276ebd 100644 --- a/packages/api/src/utils/createTask.ts +++ b/packages/api/src/utils/createTask.ts @@ -582,6 +582,21 @@ export const enqueueRssFeedFetch = async ( ), } + // If there is no Google Cloud Project Id exposed, it means that we are in local environment + if (env.dev.isLocal || !GOOGLE_CLOUD_PROJECT) { + // Calling the handler function directly. + setTimeout(() => { + axios + .post(env.queue.rssFeedTaskHandlerUrl, payload, { + headers, + }) + .catch((error) => { + console.error(error) + }) + }, 0) + return nanoid() + } + const createdTasks = await createHttpTaskWithToken({ project: GOOGLE_CLOUD_PROJECT, queue: 'omnivore-rss-queue', diff --git a/packages/web/pages/settings/rss/index.tsx b/packages/web/pages/settings/rss/index.tsx index 17ddb78d9..79c02c01e 100644 --- a/packages/web/pages/settings/rss/index.tsx +++ b/packages/web/pages/settings/rss/index.tsx @@ -28,12 +28,12 @@ export default function Rss(): JSX.Element { ) const [onDeleteId, setOnDeleteId] = useState('') const [onEditId, setOnEditId] = useState('') - const [name, setName] = useState('') + const [onEditName, setOnEditName] = useState('') async function updateSubscription(): Promise { const result = await updateSubscriptionMutation({ id: onEditId, - name, + name: onEditName, }) if (result) { showSuccessToast('RSS feed updated', { position: 'bottom-right' }) @@ -76,20 +76,24 @@ export default function Rss(): JSX.Element { - e.stopPropagation()} - onChange={(e) => setName(e.target.value)} - placeholder="Description" - disabled={!onEditId} - /> - {onEditId ? ( - + onEditId === subscription.id ? ( + + e.stopPropagation()} + onChange={(e) => setOnEditName(e.target.value)} + placeholder="Description" + css={{ + m: '0px', + fontSize: '18px', + '@mdDown': { + fontSize: '12px', + fontWeight: 'bold', + }, + width: '400px', + }} + /> + - Save - + /> - Cancel - + /> - ) : ( + + ) : ( + + + {subscription.name} + { e.stopPropagation() - setName(subscription.name) + setOnEditName(subscription.name) setOnEditId(subscription.id) }} /> - )} - + + ) } isLast={i === subscriptions.length - 1} onDelete={() => { From 40f681db2e45a260e3b0908eb6a1d204c544161c Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 20 Jul 2023 13:41:12 +0800 Subject: [PATCH 2/7] format last fetched at as date + time --- packages/web/lib/dateFormatting.ts | 8 ++++++++ packages/web/pages/settings/rss/index.tsx | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/web/lib/dateFormatting.ts b/packages/web/lib/dateFormatting.ts index e31384041..2ae152bad 100644 --- a/packages/web/lib/dateFormatting.ts +++ b/packages/web/lib/dateFormatting.ts @@ -24,3 +24,11 @@ export function formattedShortTime(rawDate: string): string { timeZone, }).format(new Date(rawDate)) } + +export function formattedDateTime(rawDate: string): string { + return new Intl.DateTimeFormat(locale, { + dateStyle: 'short', + timeStyle: 'short', + timeZone, + }).format(new Date(rawDate)) +} diff --git a/packages/web/pages/settings/rss/index.tsx b/packages/web/pages/settings/rss/index.tsx index 79c02c01e..332e7f8ab 100644 --- a/packages/web/pages/settings/rss/index.tsx +++ b/packages/web/pages/settings/rss/index.tsx @@ -11,7 +11,7 @@ import { SettingsTableRow, } from '../../../components/templates/settings/SettingsTable' import { theme } from '../../../components/tokens/stitches.config' -import { formattedShortTime } from '../../../lib/dateFormatting' +import { formattedDateTime } from '../../../lib/dateFormatting' import { unsubscribeMutation } from '../../../lib/networking/mutations/unsubscribeMutation' import { updateSubscriptionMutation } from '../../../lib/networking/mutations/updateSubscriptionMutation' import { @@ -155,7 +155,7 @@ export default function Rss(): JSX.Element { {`URL: ${subscription.url}, `} {`Last fetched: ${ subscription.lastFetchedAt - ? formattedShortTime(subscription.lastFetchedAt) + ? formattedDateTime(subscription.lastFetchedAt) : 'Never' }`} From 06087c8578552e8df23eb175efd5aada0895010c Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 20 Jul 2023 13:56:41 +0800 Subject: [PATCH 3/7] show detailed error message in toast --- .../mutations/updateSubscriptionMutation.ts | 22 +++++++++++++------ packages/web/pages/settings/rss/index.tsx | 16 ++++++++++---- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/packages/web/lib/networking/mutations/updateSubscriptionMutation.ts b/packages/web/lib/networking/mutations/updateSubscriptionMutation.ts index a3f4e20e6..24e7acd9f 100644 --- a/packages/web/lib/networking/mutations/updateSubscriptionMutation.ts +++ b/packages/web/lib/networking/mutations/updateSubscriptionMutation.ts @@ -6,9 +6,15 @@ interface UpdateSubscriptionResult { updateSubscription: UpdateSubscription } +export enum UpdateSubscriptionErrorCode { + BAD_REQUEST = 'BAD_REQUEST', + NOT_FOUND = 'NOT_FOUND', + UNAUTHORIZED = 'UNAUTHORIZED', +} + interface UpdateSubscription { - subscription: Subscription - errorCodes?: unknown[] + subscription?: Subscription + errorCodes?: UpdateSubscriptionErrorCode[] } interface UpdateSubscriptionInput { @@ -20,7 +26,7 @@ interface UpdateSubscriptionInput { export async function updateSubscriptionMutation( input: UpdateSubscriptionInput -): Promise { +): Promise { const mutation = gql` mutation UpdateSubscription($input: UpdateSubscriptionInput!) { updateSubscription(input: $input) { @@ -41,11 +47,13 @@ export async function updateSubscriptionMutation( const data = (await gqlFetcher(mutation, { input, })) as UpdateSubscriptionResult - return data.updateSubscription.errorCodes - ? undefined - : data.updateSubscription.subscription.id + return data } catch (error) { console.log('updateSubscriptionMutation error', error) - return undefined + return { + updateSubscription: { + errorCodes: [UpdateSubscriptionErrorCode.BAD_REQUEST], + }, + } } } diff --git a/packages/web/pages/settings/rss/index.tsx b/packages/web/pages/settings/rss/index.tsx index 332e7f8ab..0312615b4 100644 --- a/packages/web/pages/settings/rss/index.tsx +++ b/packages/web/pages/settings/rss/index.tsx @@ -20,6 +20,7 @@ import { } from '../../../lib/networking/queries/useGetSubscriptionsQuery' import { applyStoredTheme } from '../../../lib/themeUpdater' import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers' +import { formatMessage } from '../../../locales/en/messages' export default function Rss(): JSX.Element { const router = useRouter() @@ -35,12 +36,18 @@ export default function Rss(): JSX.Element { id: onEditId, name: onEditName, }) - if (result) { - showSuccessToast('RSS feed updated', { position: 'bottom-right' }) - } else { - showErrorToast('Failed to update', { position: 'bottom-right' }) + + if (result.updateSubscription.errorCodes) { + const errorMessage = formatMessage({ + id: `error.${result.updateSubscription.errorCodes[0]}`, + }) + showErrorToast(`failed to update subscription: ${errorMessage}`, { + position: 'bottom-right', + }) + return } + showSuccessToast('RSS feed updated', { position: 'bottom-right' }) revalidate() } @@ -109,6 +116,7 @@ export default function Rss(): JSX.Element { onClick={(e) => { e.stopPropagation() setOnEditId('') + setOnEditName('') }} /> From d572a9e3e4d1f32949bca7fd976706c39f2324e9 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 20 Jul 2023 14:10:12 +0800 Subject: [PATCH 4/7] add detailed error message for adding feed --- .../networking/mutations/subscribeMutation.ts | 21 +++++++++++---- .../mutations/updateSubscriptionMutation.ts | 8 +++--- packages/web/locales/en/messages.ts | 4 ++- packages/web/pages/settings/rss/add.tsx | 26 ++++++++++--------- 4 files changed, 37 insertions(+), 22 deletions(-) diff --git a/packages/web/lib/networking/mutations/subscribeMutation.ts b/packages/web/lib/networking/mutations/subscribeMutation.ts index 1351592a1..807a6049d 100644 --- a/packages/web/lib/networking/mutations/subscribeMutation.ts +++ b/packages/web/lib/networking/mutations/subscribeMutation.ts @@ -9,9 +9,16 @@ type SubscribeResult = { subscribe: Subscribe } +enum SubscribeErrorCode { + BadRequest = 'BAD_REQUEST', + NotFound = 'NOT_FOUND', + Unauthorized = 'UNAUTHORIZED', + AlreadySubscribed = 'ALREADY_SUBSCRIBED', +} + type Subscribe = { - subscriptions: Subscription[] - errorCodes?: unknown[] + subscriptions?: Subscription[] + errorCodes?: SubscribeErrorCode[] } export type SubscribeMutationInput = { @@ -22,7 +29,7 @@ export type SubscribeMutationInput = { export async function subscribeMutation( input: SubscribeMutationInput -): Promise { +): Promise { const mutation = gql` mutation Subscribe($input: SubscribeInput!) { subscribe(input: $input) { @@ -39,9 +46,13 @@ export async function subscribeMutation( ` try { const data = (await gqlFetcher(mutation, { input })) as SubscribeResult - return data.subscribe.errorCodes ? undefined : data.subscribe + return data } catch (error) { console.log('subscribeMutation error', error) - return undefined + return { + subscribe: { + errorCodes: [SubscribeErrorCode.BadRequest], + }, + } } } diff --git a/packages/web/lib/networking/mutations/updateSubscriptionMutation.ts b/packages/web/lib/networking/mutations/updateSubscriptionMutation.ts index 24e7acd9f..b623ff482 100644 --- a/packages/web/lib/networking/mutations/updateSubscriptionMutation.ts +++ b/packages/web/lib/networking/mutations/updateSubscriptionMutation.ts @@ -7,9 +7,9 @@ interface UpdateSubscriptionResult { } export enum UpdateSubscriptionErrorCode { - BAD_REQUEST = 'BAD_REQUEST', - NOT_FOUND = 'NOT_FOUND', - UNAUTHORIZED = 'UNAUTHORIZED', + BadRequest = 'BAD_REQUEST', + NotFound = 'NOT_FOUND', + Unauthorized = 'UNAUTHORIZED', } interface UpdateSubscription { @@ -52,7 +52,7 @@ export async function updateSubscriptionMutation( console.log('updateSubscriptionMutation error', error) return { updateSubscription: { - errorCodes: [UpdateSubscriptionErrorCode.BAD_REQUEST], + errorCodes: [UpdateSubscriptionErrorCode.BadRequest], }, } } diff --git a/packages/web/locales/en/messages.ts b/packages/web/locales/en/messages.ts index 306e427c1..9b505ffe4 100644 --- a/packages/web/locales/en/messages.ts +++ b/packages/web/locales/en/messages.ts @@ -25,7 +25,9 @@ const errorMessages: Record = { "Your sign up page has timed out, you'll be redirected to Google sign in page to authenticate again.", 'error.USER_EXISTS': 'User with this email exists already', 'error.UNKNOWN': 'An unknown error occurred', - 'error.INVALID_PASSWORD': 'Invalid password. Password must be at least 8 chars.' + 'error.INVALID_PASSWORD': 'Invalid password. Password must be at least 8 chars.', + 'error.ALREADY_SUBSCRIBED': 'You are already subscribed to this feed', + 'error.BAD_REQUEST': 'Bad request', } const loginPageMessages: Record = { diff --git a/packages/web/pages/settings/rss/add.tsx b/packages/web/pages/settings/rss/add.tsx index da7196792..51bcdd0e7 100644 --- a/packages/web/pages/settings/rss/add.tsx +++ b/packages/web/pages/settings/rss/add.tsx @@ -14,6 +14,7 @@ import { SettingsLayout } from '../../../components/templates/SettingsLayout' import { subscribeMutation } from '../../../lib/networking/mutations/subscribeMutation' import { SubscriptionType } from '../../../lib/networking/queries/useGetSubscriptionsQuery' import { showSuccessToast } from '../../../lib/toastHelpers' +import { formatMessage } from '../../../locales/en/messages' // Styles const Header = styled(Box, { @@ -30,20 +31,21 @@ export default function AddRssFeed(): JSX.Element { const [feedUrl, setFeedUrl] = useState('') const subscribe = useCallback(async () => { - try { - const result = await subscribeMutation({ - url: feedUrl, - subscriptionType: SubscriptionType.RSS, + const result = await subscribeMutation({ + url: feedUrl, + subscriptionType: SubscriptionType.RSS, + }) + + if (result.subscribe.errorCodes) { + const errorMessage = formatMessage({ + id: `error.${result.subscribe.errorCodes[0]}`, }) - if (result) { - router.push(`/settings/rss`) - showSuccessToast('New RSS feed has been added.') - } else { - setErrorMessage('There was an error adding new RSS feed.') - } - } catch (err) { - setErrorMessage('Error: ' + err) + setErrorMessage(`There was an error adding new RSS feed: ${errorMessage}`) + return } + + router.push(`/settings/rss`) + showSuccessToast('New RSS feed has been added.') }, [feedUrl, router]) return ( From 6004b519569cec46a811eabb07ad40db6ac64234 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 20 Jul 2023 14:21:36 +0800 Subject: [PATCH 5/7] return not found if rss feed not found --- packages/api/src/resolvers/subscriptions/index.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/api/src/resolvers/subscriptions/index.ts b/packages/api/src/resolvers/subscriptions/index.ts index 3904a5e06..fa556cf3e 100644 --- a/packages/api/src/resolvers/subscriptions/index.ts +++ b/packages/api/src/resolvers/subscriptions/index.ts @@ -257,6 +257,11 @@ export const subscribeResolver = authorized< } } catch (error) { log.error('failed to subscribe', error) + if (error instanceof Error && error.message === 'Status code 404') { + return { + errorCodes: [SubscribeErrorCode.NotFound], + } + } return { errorCodes: [SubscribeErrorCode.BadRequest], } From c891389b18dc31b2240a908671f24dd4a9bb60e6 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 20 Jul 2023 14:31:53 +0800 Subject: [PATCH 6/7] save rss feed without isoDate --- packages/rss-handler/src/index.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/rss-handler/src/index.ts b/packages/rss-handler/src/index.ts index bbbd537f2..b5f1ef38f 100644 --- a/packages/rss-handler/src/index.ts +++ b/packages/rss-handler/src/index.ts @@ -14,7 +14,7 @@ interface RssFeedRequest { interface ValidRssFeedItem { link: string - isoDate: string + isoDate?: string } function isRssFeedRequest(body: any): body is RssFeedRequest { @@ -166,7 +166,7 @@ export const rssHandler = Sentry.GCPFunction.wrapHttpFunction( for (const item of feed.items) { console.log('Processing feed item', item.link, item.isoDate) - if (!item.link || !item.isoDate) { + if (!item.link) { console.log('Invalid feed item', item) continue } @@ -178,7 +178,7 @@ export const rssHandler = Sentry.GCPFunction.wrapHttpFunction( } // skip old items and items that were published before 24h - const publishedAt = new Date(item.isoDate) + const publishedAt = item.isoDate ? new Date(item.isoDate) : new Date() if ( publishedAt < new Date(lastFetchedAt) || publishedAt < new Date(Date.now() - 24 * 60 * 60 * 1000) @@ -225,7 +225,9 @@ export const rssHandler = Sentry.GCPFunction.wrapHttpFunction( return res.status(500).send('INTERNAL_SERVER_ERROR') } - lastItemFetchedAt = new Date(lastValidItem.isoDate) + lastItemFetchedAt = lastValidItem.isoDate + ? new Date(lastValidItem.isoDate) + : new Date() } // update subscription lastFetchedAt From 2bf70c3659de62bf71008d3b5b7cbc52a0a4ce4c Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 20 Jul 2023 14:43:25 +0800 Subject: [PATCH 7/7] validate feed url before subscribing --- packages/web/pages/settings/rss/add.tsx | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/web/pages/settings/rss/add.tsx b/packages/web/pages/settings/rss/add.tsx index 51bcdd0e7..7d68fd181 100644 --- a/packages/web/pages/settings/rss/add.tsx +++ b/packages/web/pages/settings/rss/add.tsx @@ -31,8 +31,22 @@ export default function AddRssFeed(): JSX.Element { const [feedUrl, setFeedUrl] = useState('') const subscribe = useCallback(async () => { + if (!feedUrl) { + setErrorMessage('Please enter a valid RSS feed URL') + return + } + + let normailizedUrl: string + // normalize the url + try { + normailizedUrl = new URL(feedUrl).toString() + } catch (e) { + setErrorMessage('Please enter a valid RSS feed URL') + return + } + const result = await subscribeMutation({ - url: feedUrl, + url: normailizedUrl, subscriptionType: SubscriptionType.RSS, }) @@ -80,12 +94,9 @@ export default function AddRssFeed(): JSX.Element { value={feedUrl} placeholder={'Enter the RSS feed URL here'} onChange={(e) => { - e.preventDefault() + setErrorMessage(undefined) setFeedUrl(e.target.value) }} - disabled={false} - hidden={false} - required={true} css={{ border: '1px solid $textNonessential', borderRadius: '8px',