add feeds graphql api for searching feed by title or url

This commit is contained in:
Hongbo Wu 2023-10-31 14:26:04 +08:00
parent 878d881e05
commit fc783f01ec
8 changed files with 117 additions and 12 deletions

View file

@ -791,6 +791,13 @@ export enum FeedsErrorCode {
Unauthorized = 'UNAUTHORIZED'
}
export type FeedsInput = {
after?: InputMaybe<Scalars['String']>;
first?: InputMaybe<Scalars['Int']>;
query: Scalars['String'];
sort?: InputMaybe<SortParams>;
};
export type FeedsResult = FeedsError | FeedsSuccess;
export type FeedsSuccess = {
@ -1801,10 +1808,7 @@ export type QueryArticleSavingRequestArgs = {
export type QueryFeedsArgs = {
after?: InputMaybe<Scalars['String']>;
first?: InputMaybe<Scalars['Int']>;
query: Scalars['String'];
sort?: InputMaybe<SortParams>;
input: FeedsInput;
};
@ -3515,6 +3519,7 @@ export type ResolversTypes = {
FeedEdge: ResolverTypeWrapper<FeedEdge>;
FeedsError: ResolverTypeWrapper<FeedsError>;
FeedsErrorCode: FeedsErrorCode;
FeedsInput: FeedsInput;
FeedsResult: ResolversTypes['FeedsError'] | ResolversTypes['FeedsSuccess'];
FeedsSuccess: ResolverTypeWrapper<FeedsSuccess>;
Filter: ResolverTypeWrapper<Filter>;
@ -3999,6 +4004,7 @@ export type ResolversParentTypes = {
FeedArticlesSuccess: FeedArticlesSuccess;
FeedEdge: FeedEdge;
FeedsError: FeedsError;
FeedsInput: FeedsInput;
FeedsResult: ResolversParentTypes['FeedsError'] | ResolversParentTypes['FeedsSuccess'];
FeedsSuccess: FeedsSuccess;
Filter: Filter;
@ -5357,7 +5363,7 @@ export type QueryResolvers<ContextType = ResolverContext, ParentType extends Res
article?: Resolver<ResolversTypes['ArticleResult'], ParentType, ContextType, RequireFields<QueryArticleArgs, 'slug' | 'username'>>;
articleSavingRequest?: Resolver<ResolversTypes['ArticleSavingRequestResult'], ParentType, ContextType, Partial<QueryArticleSavingRequestArgs>>;
deviceTokens?: Resolver<ResolversTypes['DeviceTokensResult'], ParentType, ContextType>;
feeds?: Resolver<ResolversTypes['FeedsResult'], ParentType, ContextType, RequireFields<QueryFeedsArgs, 'query'>>;
feeds?: Resolver<ResolversTypes['FeedsResult'], ParentType, ContextType, RequireFields<QueryFeedsArgs, 'input'>>;
filters?: Resolver<ResolversTypes['FiltersResult'], ParentType, ContextType>;
getUserPersonalization?: Resolver<ResolversTypes['GetUserPersonalizationResult'], ParentType, ContextType>;
groups?: Resolver<ResolversTypes['GroupsResult'], ParentType, ContextType>;

View file

@ -700,6 +700,13 @@ enum FeedsErrorCode {
UNAUTHORIZED
}
input FeedsInput {
after: String
first: Int
query: String!
sort: SortParams
}
union FeedsResult = FeedsError | FeedsSuccess
type FeedsSuccess {
@ -1327,7 +1334,7 @@ type Query {
article(format: String, slug: String!, username: String!): ArticleResult!
articleSavingRequest(id: ID, url: String): ArticleSavingRequestResult!
deviceTokens: DeviceTokensResult!
feeds(after: String, first: Int, query: String!, sort: SortParams): FeedsResult!
feeds(input: FeedsInput!): FeedsResult!
filters: FiltersResult!
getUserPersonalization: GetUserPersonalizationResult!
groups: GroupsResult!

View file

@ -0,0 +1,29 @@
import { ILike } from 'typeorm'
import { appDataSource } from '../data_source'
import { Feed } from '../entity/feed'
export const feedRepository = appDataSource.getRepository(Feed).extend({
async searchFeeds(
query: string,
take = 10,
skip = 0,
orderBy = 'createdAt',
order = 'DESC'
) {
query = `%${query}%`
const where = [{ title: ILike(query) }, { url: ILike(query) }]
const feeds = await this.find({
where,
order: { [orderBy]: order },
take,
skip,
})
const count = await this.countBy(where)
return {
feeds,
count,
}
},
})

View file

@ -0,0 +1,61 @@
import {
FeedEdge,
FeedsError,
FeedsErrorCode,
FeedsSuccess,
QueryFeedsArgs,
} from '../../generated/graphql'
import { feedRepository } from '../../repository/feed'
import { authorized } from '../../utils/helpers'
export const feedsResolve = authorized<
FeedsSuccess,
FeedsError,
QueryFeedsArgs
>(async (_, { input }, { log }) => {
try {
const startCursor = input.after || ''
const start =
startCursor && !isNaN(Number(startCursor)) ? Number(startCursor) : 0
const first = input.first || 10
const { feeds, count } = await feedRepository.searchFeeds(
input.query,
first + 1, // fetch one extra to check if there is a next page
start,
input.sort?.by,
input.sort?.order || undefined
)
const hasNextPage = feeds.length > first
const endCursor = String(start + feeds.length - (hasNextPage ? 1 : 0))
if (hasNextPage) {
// remove an extra if exists
feeds.pop()
}
const edges: FeedEdge[] = feeds.map((feed) => ({
node: feed,
cursor: endCursor,
}))
return {
__typename: 'FeedsSuccess',
edges,
pageInfo: {
hasPreviousPage: start > 0,
hasNextPage,
startCursor,
endCursor,
totalCount: count,
},
}
} catch (error) {
log.error('Error fetching feeds', error)
return {
errorCodes: [FeedsErrorCode.BadRequest],
}
}
})

View file

@ -2605,6 +2605,13 @@ const schema = gql`
email: String!
}
input FeedsInput {
after: String
first: Int @sanitize(minLength: 1, maxLength: 100)
query: String! @sanitize(maxLength: 255)
sort: SortParams
}
union FeedsResult = FeedsSuccess | FeedsError
type FeedsSuccess {
@ -2799,12 +2806,7 @@ const schema = gql`
filters: FiltersResult!
groups: GroupsResult!
recentEmails: RecentEmailsResult!
feeds(
after: String
first: Int
query: String!
sort: SortParams
): FeedsResult!
feeds(input: FeedsInput!): FeedsResult!
}
`

View file