diff --git a/apps/web-next/src/__generated__/graphql-types.ts b/apps/web-next/src/__generated__/graphql-types.ts index ef302512..cae99e57 100644 --- a/apps/web-next/src/__generated__/graphql-types.ts +++ b/apps/web-next/src/__generated__/graphql-types.ts @@ -273,6 +273,11 @@ export type DataError = { error: PrismaRuntimeError; }; +export type Error = { + __typename?: 'Error'; + message: Scalars['String']['output']; +}; + export type GeoInput = { lat: Scalars['Float']['input']; lon: Scalars['Float']['input']; @@ -893,7 +898,7 @@ export type Query = { channelsConnection: QueryChannelsConnection; me?: Maybe; mySubscriptionUploadRecords?: Maybe; - newsletterListIds: Array; + newsletterListIds: QueryNewsletterListIdsResult; organizationById: Organization; organizationBySlug: Organization; organizationTagsConnection: QueryOrganizationTagsConnection; @@ -1035,6 +1040,13 @@ export type QueryMySubscriptionUploadRecordsConnectionEdge = { node: UploadRecord; }; +export type QueryNewsletterListIdsResult = Error | QueryNewsletterListIdsSuccess; + +export type QueryNewsletterListIdsSuccess = { + __typename?: 'QueryNewsletterListIdsSuccess'; + data: Array; +}; + export type QueryOrganizationTagsConnection = { __typename?: 'QueryOrganizationTagsConnection'; edges: Array; diff --git a/apps/web-next/src/routes/(root)/(watch).tsx b/apps/web-next/src/routes/(root)/(watch).tsx index 47d60fac..63a132f3 100644 --- a/apps/web-next/src/routes/(root)/(watch).tsx +++ b/apps/web-next/src/routes/(root)/(watch).tsx @@ -26,7 +26,16 @@ const getHomepageData = query(async function () { ${UploadCardFields} query HomepageData($loggedIn: Boolean!) { - newsletterListIds + newsletterListIds { + ... on QueryNewsletterListIdsSuccess { + __typename + data + } + ... on Error { + __typename + message + } + } subscriptionUploads: mySubscriptionUploadRecords(first: 8) @include(if: $loggedIn) { pageInfo { @@ -80,6 +89,16 @@ export default function WatchRoute() { const data = createAsync(() => getHomepageData()); const user = useUser(); + const newsletterListIds = () => { + const res = data()?.newsletterListIds; + + if (res?.__typename === 'QueryNewsletterListIdsSuccess') { + return res.data; + } + + return []; + }; + return ( <> - + ); diff --git a/apps/web-next/src/routes/(root)/__generated__/(watch).d.ts b/apps/web-next/src/routes/(root)/__generated__/(watch).d.ts index 0d35f9c5..d44dde70 100644 --- a/apps/web-next/src/routes/(root)/__generated__/(watch).d.ts +++ b/apps/web-next/src/routes/(root)/__generated__/(watch).d.ts @@ -5,4 +5,4 @@ export type HomepageDataQueryVariables = Types.Exact<{ }>; -export type HomepageDataQuery = { __typename?: 'Query', newsletterListIds: Array, subscriptionUploads?: { __typename?: 'QueryMySubscriptionUploadRecordsConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, startCursor?: string | null, endCursor?: string | null }, edges: Array<{ __typename?: 'QueryMySubscriptionUploadRecordsConnectionEdge', cursor: string, node: { __typename?: 'UploadRecord', id: string, title?: string | null, thumbnailUrl?: string | null, lengthSeconds?: number | null, thumbnailLqUrl?: string | null, channel: { __typename?: 'Channel', id: string, name: string, avatarUrl?: string | null, defaultThumbnailUrl?: string | null, defaultThumbnailLqUrl?: string | null } } }> } | null, trendingUploads: { __typename?: 'QueryUploadRecordsConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, startCursor?: string | null, endCursor?: string | null }, edges: Array<{ __typename?: 'QueryUploadRecordsConnectionEdge', cursor: string, node: { __typename?: 'UploadRecord', id: string, title?: string | null, thumbnailUrl?: string | null, lengthSeconds?: number | null, thumbnailLqUrl?: string | null, channel: { __typename?: 'Channel', id: string, name: string, avatarUrl?: string | null, defaultThumbnailUrl?: string | null, defaultThumbnailLqUrl?: string | null } } }> } }; +export type HomepageDataQuery = { __typename?: 'Query', newsletterListIds: { __typename: 'Error', message: string } | { __typename: 'QueryNewsletterListIdsSuccess', data: Array }, subscriptionUploads?: { __typename?: 'QueryMySubscriptionUploadRecordsConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, startCursor?: string | null, endCursor?: string | null }, edges: Array<{ __typename?: 'QueryMySubscriptionUploadRecordsConnectionEdge', cursor: string, node: { __typename?: 'UploadRecord', id: string, title?: string | null, thumbnailUrl?: string | null, lengthSeconds?: number | null, thumbnailLqUrl?: string | null, channel: { __typename?: 'Channel', id: string, name: string, avatarUrl?: string | null, defaultThumbnailUrl?: string | null, defaultThumbnailLqUrl?: string | null } } }> } | null, trendingUploads: { __typename?: 'QueryUploadRecordsConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, startCursor?: string | null, endCursor?: string | null }, edges: Array<{ __typename?: 'QueryUploadRecordsConnectionEdge', cursor: string, node: { __typename?: 'UploadRecord', id: string, title?: string | null, thumbnailUrl?: string | null, lengthSeconds?: number | null, thumbnailLqUrl?: string | null, channel: { __typename?: 'Channel', id: string, name: string, avatarUrl?: string | null, defaultThumbnailUrl?: string | null, defaultThumbnailLqUrl?: string | null } } }> } }; diff --git a/apps/web/src/__generated__/graphql-types.ts b/apps/web/src/__generated__/graphql-types.ts index ef302512..cae99e57 100644 --- a/apps/web/src/__generated__/graphql-types.ts +++ b/apps/web/src/__generated__/graphql-types.ts @@ -273,6 +273,11 @@ export type DataError = { error: PrismaRuntimeError; }; +export type Error = { + __typename?: 'Error'; + message: Scalars['String']['output']; +}; + export type GeoInput = { lat: Scalars['Float']['input']; lon: Scalars['Float']['input']; @@ -893,7 +898,7 @@ export type Query = { channelsConnection: QueryChannelsConnection; me?: Maybe; mySubscriptionUploadRecords?: Maybe; - newsletterListIds: Array; + newsletterListIds: QueryNewsletterListIdsResult; organizationById: Organization; organizationBySlug: Organization; organizationTagsConnection: QueryOrganizationTagsConnection; @@ -1035,6 +1040,13 @@ export type QueryMySubscriptionUploadRecordsConnectionEdge = { node: UploadRecord; }; +export type QueryNewsletterListIdsResult = Error | QueryNewsletterListIdsSuccess; + +export type QueryNewsletterListIdsSuccess = { + __typename?: 'QueryNewsletterListIdsSuccess'; + data: Array; +}; + export type QueryOrganizationTagsConnection = { __typename?: 'QueryOrganizationTagsConnection'; edges: Array; diff --git a/apps/web/src/routes/(root)/(watch).tsx b/apps/web/src/routes/(root)/(watch).tsx index a8b3e071..a5b25b9e 100644 --- a/apps/web/src/routes/(root)/(watch).tsx +++ b/apps/web/src/routes/(root)/(watch).tsx @@ -27,7 +27,16 @@ export function routeData() { ${UploadCardFields} query HomepageData($loggedIn: Boolean!) { - newsletterListIds + newsletterListIds { + ... on QueryNewsletterListIdsSuccess { + __typename + data + } + ... on Error { + __typename + message + } + } subscriptionUploads: mySubscriptionUploadRecords(first: 5) @include(if: $loggedIn) { pageInfo { @@ -84,6 +93,16 @@ export default function WatchRoute() { const data = useRouteData(); const user = useUser(); + const newsletterListIds = () => { + const res = data()?.newsletterListIds; + + if (res?.__typename === 'QueryNewsletterListIdsSuccess') { + return res.data; + } + + return []; + }; + return ( <> - + ); diff --git a/apps/web/src/routes/(root)/__generated__/(watch).d.ts b/apps/web/src/routes/(root)/__generated__/(watch).d.ts index 69318dd3..b6538cbc 100644 --- a/apps/web/src/routes/(root)/__generated__/(watch).d.ts +++ b/apps/web/src/routes/(root)/__generated__/(watch).d.ts @@ -5,4 +5,4 @@ export type HomepageDataQueryVariables = Types.Exact<{ }>; -export type HomepageDataQuery = { __typename?: 'Query', newsletterListIds: Array, subscriptionUploads?: { __typename?: 'QueryMySubscriptionUploadRecordsConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, startCursor?: string | null, endCursor?: string | null }, edges: Array<{ __typename?: 'QueryMySubscriptionUploadRecordsConnectionEdge', cursor: string, node: { __typename?: 'UploadRecord', id: string, title?: string | null, thumbnailUrl?: string | null, thumbnailLqUrl?: string | null, channel: { __typename?: 'Channel', id: string, name: string, avatarUrl?: string | null, defaultThumbnailUrl?: string | null, defaultThumbnailLqUrl?: string | null } } }> } | null, trendingUploads: { __typename?: 'QueryUploadRecordsConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, startCursor?: string | null, endCursor?: string | null }, edges: Array<{ __typename?: 'QueryUploadRecordsConnectionEdge', cursor: string, node: { __typename?: 'UploadRecord', id: string, title?: string | null, thumbnailUrl?: string | null, thumbnailLqUrl?: string | null, channel: { __typename?: 'Channel', id: string, name: string, avatarUrl?: string | null, defaultThumbnailUrl?: string | null, defaultThumbnailLqUrl?: string | null } } }> } }; +export type HomepageDataQuery = { __typename?: 'Query', newsletterListIds: { __typename: 'Error', message: string } | { __typename: 'QueryNewsletterListIdsSuccess', data: Array }, subscriptionUploads?: { __typename?: 'QueryMySubscriptionUploadRecordsConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, startCursor?: string | null, endCursor?: string | null }, edges: Array<{ __typename?: 'QueryMySubscriptionUploadRecordsConnectionEdge', cursor: string, node: { __typename?: 'UploadRecord', id: string, title?: string | null, thumbnailUrl?: string | null, thumbnailLqUrl?: string | null, channel: { __typename?: 'Channel', id: string, name: string, avatarUrl?: string | null, defaultThumbnailUrl?: string | null, defaultThumbnailLqUrl?: string | null } } }> } | null, trendingUploads: { __typename?: 'QueryUploadRecordsConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, startCursor?: string | null, endCursor?: string | null }, edges: Array<{ __typename?: 'QueryUploadRecordsConnectionEdge', cursor: string, node: { __typename?: 'UploadRecord', id: string, title?: string | null, thumbnailUrl?: string | null, thumbnailLqUrl?: string | null, channel: { __typename?: 'Channel', id: string, name: string, avatarUrl?: string | null, defaultThumbnailUrl?: string | null, defaultThumbnailLqUrl?: string | null } } }> } }; diff --git a/services/gateway/bun.lockb b/services/gateway/bun.lockb index 198cabd5..caf7cf1b 100755 Binary files a/services/gateway/bun.lockb and b/services/gateway/bun.lockb differ diff --git a/services/gateway/package.json b/services/gateway/package.json index 3b12e62d..436f3cd5 100644 --- a/services/gateway/package.json +++ b/services/gateway/package.json @@ -136,6 +136,7 @@ "subtitle": "^4.2.1", "tiny-invariant": "^1.3.3", "uuid": "^11.0.3", + "valibot": "^1.0.0-beta.9", "wait-on": "^8.0.1", "xss": "^1.0.15", "zod": "^3.24.1" diff --git a/services/gateway/src/schema/builder.ts b/services/gateway/src/schema/builder.ts index d7323beb..3504a12c 100644 --- a/services/gateway/src/schema/builder.ts +++ b/services/gateway/src/schema/builder.ts @@ -68,40 +68,40 @@ export default new SchemaBuilder<{ nullable: false, }, }, - tracing: { - default: - process.env['NODE_ENV'] === 'development' - ? true - : (config) => isRootField(config), - wrap: (resolver, _options, config) => (source, args, context, info) => - runFunction( - () => resolver(source, args, context, info), - (error, duration) => { - const bindings = { - graphql: { - kind: config.kind, - parentType: config.parentType, - args: JSON.stringify( - Object.fromEntries( - Object.entries(args).map(([key, value]) => [ - key, - key === 'password' ? '********' : value, - ]), - ), - ), - }, - duration, - }; - - if (error) { - moduleLogger.error( - bindings, - error instanceof Error ? error.message : `${error}`, - ); - } else { - moduleLogger.info(bindings); - } - }, - ), - }, + // tracing: { + // default: + // process.env['NODE_ENV'] === 'development' + // ? true + // : (config) => isRootField(config), + // wrap: (resolver, _options, config) => (source, args, context, info) => + // runFunction( + // () => resolver(source, args, context, info), + // (error, duration) => { + // const bindings = { + // graphql: { + // kind: config.kind, + // parentType: config.parentType, + // args: JSON.stringify( + // Object.fromEntries( + // Object.entries(args).map(([key, value]) => [ + // key, + // key === 'password' ? '********' : value, + // ]), + // ), + // ), + // }, + // duration, + // }; + // + // if (error) { + // moduleLogger.error( + // bindings, + // error instanceof Error ? error.message : `${error}`, + // ); + // } else { + // moduleLogger.info(bindings); + // } + // }, + // ), + // }, }); diff --git a/services/gateway/src/schema/types/query.ts b/services/gateway/src/schema/types/query.ts index 401113b1..78d2d5b5 100644 --- a/services/gateway/src/schema/types/query.ts +++ b/services/gateway/src/schema/types/query.ts @@ -1,8 +1,10 @@ import ExpiryMap from 'expiry-map'; import pMem from 'p-memoize'; import envariant from '@knpwrs/envariant'; +import * as v from 'valibot'; import prisma from '../../util/prisma'; import builder from '../builder'; +import logger from '../../util/logger'; builder.queryType(); @@ -58,14 +60,30 @@ builder.queryField('stats', (t) => }), ); +const ListmonkResultSchema = v.object({ + data: v.object({ + results: v.array(v.object({ uuid: v.pipe(v.string(), v.uuid()) })), + }), +}); + +builder.objectType(Error, { + name: 'Error', + fields: (t) => ({ + message: t.exposeString('message'), + }), +}); + builder.queryField('newsletterListIds', (t) => t.field({ type: ['String'], + errors: { + types: [Error], + }, resolve: async () => { const res = await fetch( envariant('LISTMONK_INTERNAL_URL') + '/api/lists?tag=default', ); - const json = await res.json(); + const json = v.parse(ListmonkResultSchema, await res.json()); return json.data.results.map( (l: { uuid: string }) => l.uuid, ) as Array;