mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
add following graphql api to fetch user following
This commit is contained in:
parent
fc783f01ec
commit
da0d1705b5
10 changed files with 424 additions and 11 deletions
53
packages/api/src/entity/feed_item.ts
Normal file
53
packages/api/src/entity/feed_item.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm'
|
||||
import { Feed } from './feed'
|
||||
|
||||
@Entity()
|
||||
export class FeedItem {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string
|
||||
|
||||
@Column('text')
|
||||
guid!: string
|
||||
|
||||
@Column('text')
|
||||
title!: string
|
||||
|
||||
@Column('array')
|
||||
links!: string[]
|
||||
|
||||
@Column('text')
|
||||
author?: string | null
|
||||
|
||||
@Column('text')
|
||||
summary?: string | null
|
||||
|
||||
@Column('array')
|
||||
categories?: string[] | null
|
||||
|
||||
@Column('text')
|
||||
content?: string | null
|
||||
|
||||
@Column('text')
|
||||
previewContent?: string | null
|
||||
|
||||
@CreateDateColumn({ default: () => 'CURRENT_TIMESTAMP' })
|
||||
createdAt!: Date
|
||||
|
||||
@UpdateDateColumn({ default: () => 'CURRENT_TIMESTAMP' })
|
||||
updatedAt!: Date
|
||||
|
||||
@Column('timestamptz')
|
||||
publishedAt?: Date | null
|
||||
|
||||
@ManyToOne(() => Feed, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'feed_id' })
|
||||
feed!: Feed
|
||||
}
|
||||
37
packages/api/src/entity/user_feed_item.ts
Normal file
37
packages/api/src/entity/user_feed_item.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm'
|
||||
import { FeedItem } from './feed_item'
|
||||
import { User } from './user'
|
||||
|
||||
@Entity()
|
||||
export class UserFeedItem {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string
|
||||
|
||||
@CreateDateColumn({ default: () => 'CURRENT_TIMESTAMP' })
|
||||
createdAt!: Date
|
||||
|
||||
@UpdateDateColumn({ default: () => 'CURRENT_TIMESTAMP' })
|
||||
updatedAt!: Date
|
||||
|
||||
@Column('timestamptz')
|
||||
hiddenAt?: Date | null
|
||||
|
||||
@Column('timestamptz')
|
||||
savedAt?: Date | null
|
||||
|
||||
@ManyToOne(() => FeedItem, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'feed_item_id' })
|
||||
feedItem!: FeedItem
|
||||
|
||||
@ManyToOne(() => User, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
user!: User
|
||||
}
|
||||
|
|
@ -794,7 +794,7 @@ export enum FeedsErrorCode {
|
|||
export type FeedsInput = {
|
||||
after?: InputMaybe<Scalars['String']>;
|
||||
first?: InputMaybe<Scalars['Int']>;
|
||||
query: Scalars['String'];
|
||||
query?: InputMaybe<Scalars['String']>;
|
||||
sort?: InputMaybe<SortParams>;
|
||||
};
|
||||
|
||||
|
|
@ -837,6 +837,50 @@ export type FiltersSuccess = {
|
|||
filters: Array<Filter>;
|
||||
};
|
||||
|
||||
export type Following = {
|
||||
__typename?: 'Following';
|
||||
author?: Maybe<Scalars['String']>;
|
||||
categories?: Maybe<Array<Scalars['String']>>;
|
||||
content?: Maybe<Scalars['String']>;
|
||||
createdAt: Scalars['Date'];
|
||||
feedItemId: Scalars['ID'];
|
||||
hiddenAt?: Maybe<Scalars['Date']>;
|
||||
id: Scalars['ID'];
|
||||
isHidden: Scalars['Boolean'];
|
||||
isSaved: Scalars['Boolean'];
|
||||
links: Array<Scalars['String']>;
|
||||
previewContent?: Maybe<Scalars['String']>;
|
||||
publishedAt?: Maybe<Scalars['Date']>;
|
||||
savedAt?: Maybe<Scalars['Date']>;
|
||||
summary?: Maybe<Scalars['String']>;
|
||||
title: Scalars['String'];
|
||||
updatedAt: Scalars['Date'];
|
||||
};
|
||||
|
||||
export type FollowingEdge = {
|
||||
__typename?: 'FollowingEdge';
|
||||
cursor: Scalars['String'];
|
||||
node: Following;
|
||||
};
|
||||
|
||||
export type FollowingError = {
|
||||
__typename?: 'FollowingError';
|
||||
errorCodes: Array<FollowingErrorCode>;
|
||||
};
|
||||
|
||||
export enum FollowingErrorCode {
|
||||
BadRequest = 'BAD_REQUEST',
|
||||
Unauthorized = 'UNAUTHORIZED'
|
||||
}
|
||||
|
||||
export type FollowingResult = FollowingError | FollowingSuccess;
|
||||
|
||||
export type FollowingSuccess = {
|
||||
__typename?: 'FollowingSuccess';
|
||||
edges: Array<FollowingEdge>;
|
||||
pageInfo: PageInfo;
|
||||
};
|
||||
|
||||
export type GenerateApiKeyError = {
|
||||
__typename?: 'GenerateApiKeyError';
|
||||
errorCodes: Array<GenerateApiKeyErrorCode>;
|
||||
|
|
@ -1771,6 +1815,7 @@ export type Query = {
|
|||
deviceTokens: DeviceTokensResult;
|
||||
feeds: FeedsResult;
|
||||
filters: FiltersResult;
|
||||
following: FollowingResult;
|
||||
getUserPersonalization: GetUserPersonalizationResult;
|
||||
groups: GroupsResult;
|
||||
hello?: Maybe<Scalars['String']>;
|
||||
|
|
@ -1812,6 +1857,14 @@ export type QueryFeedsArgs = {
|
|||
};
|
||||
|
||||
|
||||
export type QueryFollowingArgs = {
|
||||
after?: InputMaybe<Scalars['String']>;
|
||||
first?: InputMaybe<Scalars['Int']>;
|
||||
since?: InputMaybe<Scalars['Date']>;
|
||||
until?: InputMaybe<Scalars['Date']>;
|
||||
};
|
||||
|
||||
|
||||
export type QueryRulesArgs = {
|
||||
enabled?: InputMaybe<Scalars['Boolean']>;
|
||||
};
|
||||
|
|
@ -3528,6 +3581,12 @@ export type ResolversTypes = {
|
|||
FiltersResult: ResolversTypes['FiltersError'] | ResolversTypes['FiltersSuccess'];
|
||||
FiltersSuccess: ResolverTypeWrapper<FiltersSuccess>;
|
||||
Float: ResolverTypeWrapper<Scalars['Float']>;
|
||||
Following: ResolverTypeWrapper<Following>;
|
||||
FollowingEdge: ResolverTypeWrapper<FollowingEdge>;
|
||||
FollowingError: ResolverTypeWrapper<FollowingError>;
|
||||
FollowingErrorCode: FollowingErrorCode;
|
||||
FollowingResult: ResolversTypes['FollowingError'] | ResolversTypes['FollowingSuccess'];
|
||||
FollowingSuccess: ResolverTypeWrapper<FollowingSuccess>;
|
||||
GenerateApiKeyError: ResolverTypeWrapper<GenerateApiKeyError>;
|
||||
GenerateApiKeyErrorCode: GenerateApiKeyErrorCode;
|
||||
GenerateApiKeyInput: GenerateApiKeyInput;
|
||||
|
|
@ -4012,6 +4071,11 @@ export type ResolversParentTypes = {
|
|||
FiltersResult: ResolversParentTypes['FiltersError'] | ResolversParentTypes['FiltersSuccess'];
|
||||
FiltersSuccess: FiltersSuccess;
|
||||
Float: Scalars['Float'];
|
||||
Following: Following;
|
||||
FollowingEdge: FollowingEdge;
|
||||
FollowingError: FollowingError;
|
||||
FollowingResult: ResolversParentTypes['FollowingError'] | ResolversParentTypes['FollowingSuccess'];
|
||||
FollowingSuccess: FollowingSuccess;
|
||||
GenerateApiKeyError: GenerateApiKeyError;
|
||||
GenerateApiKeyInput: GenerateApiKeyInput;
|
||||
GenerateApiKeyResult: ResolversParentTypes['GenerateApiKeyError'] | ResolversParentTypes['GenerateApiKeySuccess'];
|
||||
|
|
@ -4894,6 +4958,47 @@ export type FiltersSuccessResolvers<ContextType = ResolverContext, ParentType ex
|
|||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type FollowingResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['Following'] = ResolversParentTypes['Following']> = {
|
||||
author?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
categories?: Resolver<Maybe<Array<ResolversTypes['String']>>, ParentType, ContextType>;
|
||||
content?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
createdAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
|
||||
feedItemId?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
|
||||
hiddenAt?: Resolver<Maybe<ResolversTypes['Date']>, ParentType, ContextType>;
|
||||
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
|
||||
isHidden?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
|
||||
isSaved?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
|
||||
links?: Resolver<Array<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
previewContent?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
publishedAt?: Resolver<Maybe<ResolversTypes['Date']>, ParentType, ContextType>;
|
||||
savedAt?: Resolver<Maybe<ResolversTypes['Date']>, ParentType, ContextType>;
|
||||
summary?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
title?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
updatedAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type FollowingEdgeResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['FollowingEdge'] = ResolversParentTypes['FollowingEdge']> = {
|
||||
cursor?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
node?: Resolver<ResolversTypes['Following'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type FollowingErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['FollowingError'] = ResolversParentTypes['FollowingError']> = {
|
||||
errorCodes?: Resolver<Array<ResolversTypes['FollowingErrorCode']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type FollowingResultResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['FollowingResult'] = ResolversParentTypes['FollowingResult']> = {
|
||||
__resolveType: TypeResolveFn<'FollowingError' | 'FollowingSuccess', ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type FollowingSuccessResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['FollowingSuccess'] = ResolversParentTypes['FollowingSuccess']> = {
|
||||
edges?: Resolver<Array<ResolversTypes['FollowingEdge']>, ParentType, ContextType>;
|
||||
pageInfo?: Resolver<ResolversTypes['PageInfo'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type GenerateApiKeyErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['GenerateApiKeyError'] = ResolversParentTypes['GenerateApiKeyError']> = {
|
||||
errorCodes?: Resolver<Array<ResolversTypes['GenerateApiKeyErrorCode']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
|
|
@ -5365,6 +5470,7 @@ export type QueryResolvers<ContextType = ResolverContext, ParentType extends Res
|
|||
deviceTokens?: Resolver<ResolversTypes['DeviceTokensResult'], ParentType, ContextType>;
|
||||
feeds?: Resolver<ResolversTypes['FeedsResult'], ParentType, ContextType, RequireFields<QueryFeedsArgs, 'input'>>;
|
||||
filters?: Resolver<ResolversTypes['FiltersResult'], ParentType, ContextType>;
|
||||
following?: Resolver<ResolversTypes['FollowingResult'], ParentType, ContextType, Partial<QueryFollowingArgs>>;
|
||||
getUserPersonalization?: Resolver<ResolversTypes['GetUserPersonalizationResult'], ParentType, ContextType>;
|
||||
groups?: Resolver<ResolversTypes['GroupsResult'], ParentType, ContextType>;
|
||||
hello?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
|
|
@ -6399,6 +6505,11 @@ export type Resolvers<ContextType = ResolverContext> = {
|
|||
FiltersError?: FiltersErrorResolvers<ContextType>;
|
||||
FiltersResult?: FiltersResultResolvers<ContextType>;
|
||||
FiltersSuccess?: FiltersSuccessResolvers<ContextType>;
|
||||
Following?: FollowingResolvers<ContextType>;
|
||||
FollowingEdge?: FollowingEdgeResolvers<ContextType>;
|
||||
FollowingError?: FollowingErrorResolvers<ContextType>;
|
||||
FollowingResult?: FollowingResultResolvers<ContextType>;
|
||||
FollowingSuccess?: FollowingSuccessResolvers<ContextType>;
|
||||
GenerateApiKeyError?: GenerateApiKeyErrorResolvers<ContextType>;
|
||||
GenerateApiKeyResult?: GenerateApiKeyResultResolvers<ContextType>;
|
||||
GenerateApiKeySuccess?: GenerateApiKeySuccessResolvers<ContextType>;
|
||||
|
|
|
|||
|
|
@ -703,7 +703,7 @@ enum FeedsErrorCode {
|
|||
input FeedsInput {
|
||||
after: String
|
||||
first: Int
|
||||
query: String!
|
||||
query: String
|
||||
sort: SortParams
|
||||
}
|
||||
|
||||
|
|
@ -742,6 +742,46 @@ type FiltersSuccess {
|
|||
filters: [Filter!]!
|
||||
}
|
||||
|
||||
type Following {
|
||||
author: String
|
||||
categories: [String!]
|
||||
content: String
|
||||
createdAt: Date!
|
||||
feedItemId: ID!
|
||||
hiddenAt: Date
|
||||
id: ID!
|
||||
isHidden: Boolean!
|
||||
isSaved: Boolean!
|
||||
links: [String!]!
|
||||
previewContent: String
|
||||
publishedAt: Date
|
||||
savedAt: Date
|
||||
summary: String
|
||||
title: String!
|
||||
updatedAt: Date!
|
||||
}
|
||||
|
||||
type FollowingEdge {
|
||||
cursor: String!
|
||||
node: Following!
|
||||
}
|
||||
|
||||
type FollowingError {
|
||||
errorCodes: [FollowingErrorCode!]!
|
||||
}
|
||||
|
||||
enum FollowingErrorCode {
|
||||
BAD_REQUEST
|
||||
UNAUTHORIZED
|
||||
}
|
||||
|
||||
union FollowingResult = FollowingError | FollowingSuccess
|
||||
|
||||
type FollowingSuccess {
|
||||
edges: [FollowingEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
type GenerateApiKeyError {
|
||||
errorCodes: [GenerateApiKeyErrorCode!]!
|
||||
}
|
||||
|
|
@ -1336,6 +1376,7 @@ type Query {
|
|||
deviceTokens: DeviceTokensResult!
|
||||
feeds(input: FeedsInput!): FeedsResult!
|
||||
filters: FiltersResult!
|
||||
following(after: String, first: Int, since: Date, until: Date): FollowingResult!
|
||||
getUserPersonalization: GetUserPersonalizationResult!
|
||||
groups: GroupsResult!
|
||||
hello: String
|
||||
|
|
|
|||
|
|
@ -4,14 +4,18 @@ import { Feed } from '../entity/feed'
|
|||
|
||||
export const feedRepository = appDataSource.getRepository(Feed).extend({
|
||||
async searchFeeds(
|
||||
query: string,
|
||||
query = '',
|
||||
take = 10,
|
||||
skip = 0,
|
||||
orderBy = 'createdAt',
|
||||
order = 'DESC'
|
||||
orderBy = 'title',
|
||||
order = 'ASC'
|
||||
) {
|
||||
query = `%${query}%`
|
||||
const where = [{ title: ILike(query) }, { url: ILike(query) }]
|
||||
const where = []
|
||||
if (query !== '') {
|
||||
query = `%${query}%`
|
||||
where.push({ title: ILike(query) }, { url: ILike(query) })
|
||||
}
|
||||
|
||||
const feeds = await this.find({
|
||||
where,
|
||||
order: { [orderBy]: order },
|
||||
|
|
|
|||
36
packages/api/src/repository/user_feed_item.ts
Normal file
36
packages/api/src/repository/user_feed_item.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import { Between, FindOptionsWhere } from 'typeorm'
|
||||
import { appDataSource } from '../data_source'
|
||||
import { UserFeedItem } from '../entity/user_feed_item'
|
||||
|
||||
export const userFeedItemRepository = appDataSource
|
||||
.getRepository(UserFeedItem)
|
||||
.extend({
|
||||
async searchUserFeedItems(
|
||||
take = 10,
|
||||
skip = 0,
|
||||
since = new Date(0),
|
||||
until = new Date()
|
||||
) {
|
||||
const where: FindOptionsWhere<UserFeedItem> = {
|
||||
updatedAt: Between(since, until),
|
||||
}
|
||||
|
||||
const userFeedItems = await this.find({
|
||||
where,
|
||||
order: { updatedAt: 'DESC' },
|
||||
take,
|
||||
skip,
|
||||
relations: {
|
||||
user: true,
|
||||
feedItem: true,
|
||||
},
|
||||
})
|
||||
|
||||
const count = await this.countBy(where)
|
||||
|
||||
return {
|
||||
userFeedItems,
|
||||
count,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
@ -3,9 +3,15 @@ import {
|
|||
FeedsError,
|
||||
FeedsErrorCode,
|
||||
FeedsSuccess,
|
||||
FollowingEdge,
|
||||
FollowingError,
|
||||
FollowingErrorCode,
|
||||
FollowingSuccess,
|
||||
QueryFeedsArgs,
|
||||
QueryFollowingArgs,
|
||||
} from '../../generated/graphql'
|
||||
import { feedRepository } from '../../repository/feed'
|
||||
import { userFeedItemRepository } from '../../repository/user_feed_item'
|
||||
import { authorized } from '../../utils/helpers'
|
||||
|
||||
export const feedsResolve = authorized<
|
||||
|
|
@ -17,10 +23,10 @@ export const feedsResolve = authorized<
|
|||
const startCursor = input.after || ''
|
||||
const start =
|
||||
startCursor && !isNaN(Number(startCursor)) ? Number(startCursor) : 0
|
||||
const first = input.first || 10
|
||||
const first = Math.min(input.first || 10, 100) // cap at 100
|
||||
|
||||
const { feeds, count } = await feedRepository.searchFeeds(
|
||||
input.query,
|
||||
input.query || '',
|
||||
first + 1, // fetch one extra to check if there is a next page
|
||||
start,
|
||||
input.sort?.by,
|
||||
|
|
@ -59,3 +65,66 @@ export const feedsResolve = authorized<
|
|||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const followingResolver = authorized<
|
||||
FollowingSuccess,
|
||||
FollowingError,
|
||||
QueryFollowingArgs
|
||||
>(async (_, args, { authTrx, log }) => {
|
||||
try {
|
||||
const startCursor = args.after || ''
|
||||
const start =
|
||||
startCursor && !isNaN(Number(startCursor)) ? Number(startCursor) : 0
|
||||
const first = Math.min(args.first || 10, 100) // cap at 100
|
||||
const since = args.since ? new Date(args.since) : undefined
|
||||
const until = args.until ? new Date(args.until) : undefined
|
||||
|
||||
const { userFeedItems, count } = await authTrx((tx) =>
|
||||
tx.withRepository(userFeedItemRepository).searchUserFeedItems(
|
||||
first + 1, // fetch one extra to check if there is a next page
|
||||
start,
|
||||
since,
|
||||
until
|
||||
)
|
||||
)
|
||||
|
||||
const hasNextPage = userFeedItems.length > first
|
||||
const endCursor = String(
|
||||
start + userFeedItems.length - (hasNextPage ? 1 : 0)
|
||||
)
|
||||
|
||||
if (hasNextPage) {
|
||||
// remove an extra if exists
|
||||
userFeedItems.pop()
|
||||
}
|
||||
|
||||
const edges: FollowingEdge[] = userFeedItems.map((item) => ({
|
||||
node: {
|
||||
...item.feedItem,
|
||||
...item,
|
||||
isHidden: !!item.hiddenAt,
|
||||
isSaved: !!item.savedAt,
|
||||
feedItemId: item.feedItem.id,
|
||||
},
|
||||
cursor: endCursor,
|
||||
}))
|
||||
|
||||
return {
|
||||
__typename: 'FollowingSuccess',
|
||||
edges,
|
||||
pageInfo: {
|
||||
hasPreviousPage: start > 0,
|
||||
hasNextPage,
|
||||
startCursor,
|
||||
endCursor,
|
||||
totalCount: count,
|
||||
},
|
||||
}
|
||||
} catch (error) {
|
||||
log.error('Error fetching following', error)
|
||||
|
||||
return {
|
||||
errorCodes: [FollowingErrorCode.Unauthorized],
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
7
packages/api/src/routers/feed_router.ts
Normal file
7
packages/api/src/routers/feed_router.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import express from "express";
|
||||
|
||||
export function feedRouter() {
|
||||
const router = express.Router()
|
||||
|
||||
router.post('/')
|
||||
}
|
||||
|
|
@ -2607,8 +2607,8 @@ const schema = gql`
|
|||
|
||||
input FeedsInput {
|
||||
after: String
|
||||
first: Int @sanitize(minLength: 1, maxLength: 100)
|
||||
query: String! @sanitize(maxLength: 255)
|
||||
first: Int
|
||||
query: String @sanitize(maxLength: 255)
|
||||
sort: SortParams
|
||||
}
|
||||
|
||||
|
|
@ -2645,6 +2645,46 @@ const schema = gql`
|
|||
author: String
|
||||
}
|
||||
|
||||
union FollowingResult = FollowingSuccess | FollowingError
|
||||
|
||||
type FollowingSuccess {
|
||||
edges: [FollowingEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
type FollowingEdge {
|
||||
cursor: String!
|
||||
node: Following!
|
||||
}
|
||||
|
||||
type FollowingError {
|
||||
errorCodes: [FollowingErrorCode!]!
|
||||
}
|
||||
|
||||
enum FollowingErrorCode {
|
||||
UNAUTHORIZED
|
||||
BAD_REQUEST
|
||||
}
|
||||
|
||||
type Following {
|
||||
id: ID!
|
||||
feedItemId: ID!
|
||||
title: String!
|
||||
links: [String!]!
|
||||
author: String
|
||||
summary: String
|
||||
categories: [String!]
|
||||
content: String
|
||||
previewContent: String
|
||||
createdAt: Date!
|
||||
updatedAt: Date!
|
||||
savedAt: Date
|
||||
hiddenAt: Date
|
||||
publishedAt: Date
|
||||
isHidden: Boolean!
|
||||
isSaved: Boolean!
|
||||
}
|
||||
|
||||
# Mutations
|
||||
type Mutation {
|
||||
googleLogin(input: GoogleLoginInput!): LoginResult!
|
||||
|
|
@ -2807,6 +2847,12 @@ const schema = gql`
|
|||
groups: GroupsResult!
|
||||
recentEmails: RecentEmailsResult!
|
||||
feeds(input: FeedsInput!): FeedsResult!
|
||||
following(
|
||||
after: String
|
||||
first: Int
|
||||
since: Date
|
||||
until: Date
|
||||
): FollowingResult!
|
||||
}
|
||||
`
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
export const searchFollowing = async (
|
||||
userId: string,
|
||||
first = 10,
|
||||
after = '',
|
||||
since?: Date,
|
||||
until?: Date
|
||||
) => {
|
||||
|
||||
}
|
||||
Loading…
Reference in a new issue