mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
feat: highlights api
This commit is contained in:
parent
05cfd4b272
commit
0013150c26
11 changed files with 224 additions and 84 deletions
|
|
@ -42,6 +42,7 @@ import {
|
|||
} from './services/service_usage'
|
||||
import { batchGetSubscriptionsByNames } from './services/subscriptions'
|
||||
import { batchGetUploadFilesByIds } from './services/upload_file'
|
||||
import { findUsersByIds } from './services/user'
|
||||
import { tracer } from './tracing'
|
||||
import { getClaimsByToken, setAuthInCookie } from './utils/auth'
|
||||
import { SetClaimsRole } from './utils/dictionary'
|
||||
|
|
@ -124,6 +125,9 @@ const contextFunc: ContextFunction<ExpressContext, ResolverContext> = async ({
|
|||
|
||||
return batchGetSubscriptionsByNames(claims.uid, names as string[])
|
||||
}),
|
||||
users: new DataLoader(async (ids: readonly string[]) =>
|
||||
findUsersByIds(ids as string[])
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@ export class Highlight {
|
|||
@JoinColumn({ name: 'user_id' })
|
||||
user!: User
|
||||
|
||||
@Column('uuid')
|
||||
userId!: string
|
||||
|
||||
@ManyToOne(() => LibraryItem, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'library_item_id' })
|
||||
libraryItem!: LibraryItem
|
||||
|
|
|
|||
|
|
@ -1275,6 +1275,12 @@ export type Highlight = {
|
|||
user: User;
|
||||
};
|
||||
|
||||
export type HighlightEdge = {
|
||||
__typename?: 'HighlightEdge';
|
||||
cursor: Scalars['String'];
|
||||
node: Highlight;
|
||||
};
|
||||
|
||||
export type HighlightReply = {
|
||||
__typename?: 'HighlightReply';
|
||||
createdAt: Scalars['Date'];
|
||||
|
|
@ -1296,6 +1302,23 @@ export enum HighlightType {
|
|||
Redaction = 'REDACTION'
|
||||
}
|
||||
|
||||
export type HighlightsError = {
|
||||
__typename?: 'HighlightsError';
|
||||
errorCodes: Array<HighlightsErrorCode>;
|
||||
};
|
||||
|
||||
export enum HighlightsErrorCode {
|
||||
BadRequest = 'BAD_REQUEST'
|
||||
}
|
||||
|
||||
export type HighlightsResult = HighlightsError | HighlightsSuccess;
|
||||
|
||||
export type HighlightsSuccess = {
|
||||
__typename?: 'HighlightsSuccess';
|
||||
edges: Array<HighlightEdge>;
|
||||
pageInfo: PageInfo;
|
||||
};
|
||||
|
||||
export type HomeEdge = {
|
||||
__typename?: 'HomeEdge';
|
||||
cursor: Scalars['String'];
|
||||
|
|
@ -2261,6 +2284,7 @@ export type Query = {
|
|||
groups: GroupsResult;
|
||||
hello?: Maybe<Scalars['String']>;
|
||||
hiddenHomeSection: HiddenHomeSectionResult;
|
||||
highlights: HighlightsResult;
|
||||
home: HomeResult;
|
||||
integration: IntegrationResult;
|
||||
integrations: IntegrationsResult;
|
||||
|
|
@ -2311,6 +2335,13 @@ export type QueryGetDiscoverFeedArticlesArgs = {
|
|||
};
|
||||
|
||||
|
||||
export type QueryHighlightsArgs = {
|
||||
after?: InputMaybe<Scalars['String']>;
|
||||
first?: InputMaybe<Scalars['Int']>;
|
||||
query?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
|
||||
export type QueryHomeArgs = {
|
||||
after?: InputMaybe<Scalars['String']>;
|
||||
first?: InputMaybe<Scalars['Int']>;
|
||||
|
|
@ -4331,9 +4362,14 @@ export type ResolversTypes = {
|
|||
HiddenHomeSectionResult: ResolversTypes['HiddenHomeSectionError'] | ResolversTypes['HiddenHomeSectionSuccess'];
|
||||
HiddenHomeSectionSuccess: ResolverTypeWrapper<HiddenHomeSectionSuccess>;
|
||||
Highlight: ResolverTypeWrapper<Highlight>;
|
||||
HighlightEdge: ResolverTypeWrapper<HighlightEdge>;
|
||||
HighlightReply: ResolverTypeWrapper<HighlightReply>;
|
||||
HighlightStats: ResolverTypeWrapper<HighlightStats>;
|
||||
HighlightType: HighlightType;
|
||||
HighlightsError: ResolverTypeWrapper<HighlightsError>;
|
||||
HighlightsErrorCode: HighlightsErrorCode;
|
||||
HighlightsResult: ResolversTypes['HighlightsError'] | ResolversTypes['HighlightsSuccess'];
|
||||
HighlightsSuccess: ResolverTypeWrapper<HighlightsSuccess>;
|
||||
HomeEdge: ResolverTypeWrapper<HomeEdge>;
|
||||
HomeError: ResolverTypeWrapper<HomeError>;
|
||||
HomeErrorCode: HomeErrorCode;
|
||||
|
|
@ -4900,8 +4936,12 @@ export type ResolversParentTypes = {
|
|||
HiddenHomeSectionResult: ResolversParentTypes['HiddenHomeSectionError'] | ResolversParentTypes['HiddenHomeSectionSuccess'];
|
||||
HiddenHomeSectionSuccess: HiddenHomeSectionSuccess;
|
||||
Highlight: Highlight;
|
||||
HighlightEdge: HighlightEdge;
|
||||
HighlightReply: HighlightReply;
|
||||
HighlightStats: HighlightStats;
|
||||
HighlightsError: HighlightsError;
|
||||
HighlightsResult: ResolversParentTypes['HighlightsError'] | ResolversParentTypes['HighlightsSuccess'];
|
||||
HighlightsSuccess: HighlightsSuccess;
|
||||
HomeEdge: HomeEdge;
|
||||
HomeError: HomeError;
|
||||
HomeItem: HomeItem;
|
||||
|
|
@ -6103,6 +6143,12 @@ export type HighlightResolvers<ContextType = ResolverContext, ParentType extends
|
|||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type HighlightEdgeResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['HighlightEdge'] = ResolversParentTypes['HighlightEdge']> = {
|
||||
cursor?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
node?: Resolver<ResolversTypes['Highlight'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type HighlightReplyResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['HighlightReply'] = ResolversParentTypes['HighlightReply']> = {
|
||||
createdAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
|
||||
highlight?: Resolver<ResolversTypes['Highlight'], ParentType, ContextType>;
|
||||
|
|
@ -6118,6 +6164,21 @@ export type HighlightStatsResolvers<ContextType = ResolverContext, ParentType ex
|
|||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type HighlightsErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['HighlightsError'] = ResolversParentTypes['HighlightsError']> = {
|
||||
errorCodes?: Resolver<Array<ResolversTypes['HighlightsErrorCode']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type HighlightsResultResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['HighlightsResult'] = ResolversParentTypes['HighlightsResult']> = {
|
||||
__resolveType: TypeResolveFn<'HighlightsError' | 'HighlightsSuccess', ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type HighlightsSuccessResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['HighlightsSuccess'] = ResolversParentTypes['HighlightsSuccess']> = {
|
||||
edges?: Resolver<Array<ResolversTypes['HighlightEdge']>, ParentType, ContextType>;
|
||||
pageInfo?: Resolver<ResolversTypes['PageInfo'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type HomeEdgeResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['HomeEdge'] = ResolversParentTypes['HomeEdge']> = {
|
||||
cursor?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
node?: Resolver<ResolversTypes['HomeSection'], ParentType, ContextType>;
|
||||
|
|
@ -6581,6 +6642,7 @@ export type QueryResolvers<ContextType = ResolverContext, ParentType extends Res
|
|||
groups?: Resolver<ResolversTypes['GroupsResult'], ParentType, ContextType>;
|
||||
hello?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
hiddenHomeSection?: Resolver<ResolversTypes['HiddenHomeSectionResult'], ParentType, ContextType>;
|
||||
highlights?: Resolver<ResolversTypes['HighlightsResult'], ParentType, ContextType, Partial<QueryHighlightsArgs>>;
|
||||
home?: Resolver<ResolversTypes['HomeResult'], ParentType, ContextType, Partial<QueryHomeArgs>>;
|
||||
integration?: Resolver<ResolversTypes['IntegrationResult'], ParentType, ContextType, RequireFields<QueryIntegrationArgs, 'name'>>;
|
||||
integrations?: Resolver<ResolversTypes['IntegrationsResult'], ParentType, ContextType>;
|
||||
|
|
@ -7797,8 +7859,12 @@ export type Resolvers<ContextType = ResolverContext> = {
|
|||
HiddenHomeSectionResult?: HiddenHomeSectionResultResolvers<ContextType>;
|
||||
HiddenHomeSectionSuccess?: HiddenHomeSectionSuccessResolvers<ContextType>;
|
||||
Highlight?: HighlightResolvers<ContextType>;
|
||||
HighlightEdge?: HighlightEdgeResolvers<ContextType>;
|
||||
HighlightReply?: HighlightReplyResolvers<ContextType>;
|
||||
HighlightStats?: HighlightStatsResolvers<ContextType>;
|
||||
HighlightsError?: HighlightsErrorResolvers<ContextType>;
|
||||
HighlightsResult?: HighlightsResultResolvers<ContextType>;
|
||||
HighlightsSuccess?: HighlightsSuccessResolvers<ContextType>;
|
||||
HomeEdge?: HomeEdgeResolvers<ContextType>;
|
||||
HomeError?: HomeErrorResolvers<ContextType>;
|
||||
HomeItem?: HomeItemResolvers<ContextType>;
|
||||
|
|
|
|||
|
|
@ -1147,6 +1147,11 @@ type Highlight {
|
|||
user: User!
|
||||
}
|
||||
|
||||
type HighlightEdge {
|
||||
cursor: String!
|
||||
node: Highlight!
|
||||
}
|
||||
|
||||
type HighlightReply {
|
||||
createdAt: Date!
|
||||
highlight: Highlight!
|
||||
|
|
@ -1166,6 +1171,21 @@ enum HighlightType {
|
|||
REDACTION
|
||||
}
|
||||
|
||||
type HighlightsError {
|
||||
errorCodes: [HighlightsErrorCode!]!
|
||||
}
|
||||
|
||||
enum HighlightsErrorCode {
|
||||
BAD_REQUEST
|
||||
}
|
||||
|
||||
union HighlightsResult = HighlightsError | HighlightsSuccess
|
||||
|
||||
type HighlightsSuccess {
|
||||
edges: [HighlightEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
type HomeEdge {
|
||||
cursor: String!
|
||||
node: HomeSection!
|
||||
|
|
@ -1738,6 +1758,7 @@ type Query {
|
|||
groups: GroupsResult!
|
||||
hello: String
|
||||
hiddenHomeSection: HiddenHomeSectionResult!
|
||||
highlights(after: String, first: Int, query: String): HighlightsResult!
|
||||
home(after: String, first: Int): HomeResult!
|
||||
integration(name: String!): IntegrationResult!
|
||||
integrations: IntegrationsResult!
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { createHmac } from 'crypto'
|
||||
import { isError } from 'lodash'
|
||||
import { Highlight as HighlightEntity } from '../entity/highlight'
|
||||
import { Highlight } from '../entity/highlight'
|
||||
import { LibraryItem } from '../entity/library_item'
|
||||
import {
|
||||
EXISTING_NEWSLETTER_FOLDER,
|
||||
|
|
@ -16,10 +16,10 @@ import {
|
|||
DEFAULT_SUBSCRIPTION_FOLDER,
|
||||
Subscription,
|
||||
} from '../entity/subscription'
|
||||
import { User as UserEntity } from '../entity/user'
|
||||
import { env } from '../env'
|
||||
import {
|
||||
Article,
|
||||
Highlight,
|
||||
HomeItem,
|
||||
HomeItemSource,
|
||||
HomeItemSourceType,
|
||||
|
|
@ -33,7 +33,6 @@ import { getAISummary } from '../services/ai-summaries'
|
|||
import { findUserFeatures } from '../services/features'
|
||||
import { Merge } from '../util'
|
||||
import {
|
||||
highlightDataToHighlight,
|
||||
isBase64Image,
|
||||
recommandationDataToRecommendation,
|
||||
validatedDate,
|
||||
|
|
@ -60,6 +59,7 @@ import {
|
|||
saveDiscoverArticleResolver,
|
||||
} from './discover_feeds'
|
||||
import { optInFeatureResolver } from './features'
|
||||
import { highlightsResolver } from './highlight'
|
||||
import {
|
||||
hiddenHomeSectionResolver,
|
||||
homeResolver,
|
||||
|
|
@ -376,6 +376,7 @@ export const functionResolvers = {
|
|||
home: homeResolver,
|
||||
subscription: subscriptionResolver,
|
||||
hiddenHomeSection: hiddenHomeSectionResolver,
|
||||
highlights: highlightsResolver,
|
||||
},
|
||||
User: {
|
||||
async intercomHash(
|
||||
|
|
@ -414,6 +415,16 @@ export const functionResolvers = {
|
|||
|
||||
return findUserFeatures(ctx.claims.uid)
|
||||
},
|
||||
picture: (user: UserEntity) => user.profile.pictureUrl,
|
||||
// not implemented yet
|
||||
friendsCount: () => 0,
|
||||
followersCount: () => 0,
|
||||
isFullUser: () => true,
|
||||
viewerIsFollowing: () => false,
|
||||
sharedArticles: () => [],
|
||||
sharedArticlesCount: () => 0,
|
||||
sharedHighlightsCount: () => 0,
|
||||
sharedNotesCount: () => 0,
|
||||
},
|
||||
Article: {
|
||||
async url(article: Article, _: unknown, ctx: WithDataSourcesContext) {
|
||||
|
|
@ -465,16 +476,12 @@ export const functionResolvers = {
|
|||
...readingProgressHandlers,
|
||||
},
|
||||
Highlight: {
|
||||
// async reactions(
|
||||
// highlight: { id: string; reactions?: Reaction[] },
|
||||
// _: unknown,
|
||||
// ctx: WithDataSourcesContext
|
||||
// ) {
|
||||
// const { reactions, id } = highlight
|
||||
// if (reactions) return reactions
|
||||
|
||||
// return await ctx.models.reaction.batchGetFromHighlight(id)
|
||||
// },
|
||||
reactions: () => [],
|
||||
replies: () => [],
|
||||
type: (highlight: Highlight) => highlight.highlightType,
|
||||
async user(highlight: Highlight, __: unknown, ctx: WithDataSourcesContext) {
|
||||
return ctx.dataLoaders.users.load(highlight.userId)
|
||||
},
|
||||
createdByMe(
|
||||
highlight: { user: { id: string } },
|
||||
__: unknown,
|
||||
|
|
@ -483,15 +490,6 @@ export const functionResolvers = {
|
|||
return highlight.user.id === ctx.uid
|
||||
},
|
||||
},
|
||||
// Reaction: {
|
||||
// async user(
|
||||
// reaction: { userId: string },
|
||||
// __: unknown,
|
||||
// ctx: WithDataSourcesContext
|
||||
// ) {
|
||||
// return userDataToUser(await ctx.models.user.get(reaction.userId))
|
||||
// },
|
||||
// },
|
||||
SearchItem: {
|
||||
async url(item: SearchItem, _: unknown, ctx: WithDataSourcesContext) {
|
||||
if (
|
||||
|
|
@ -570,7 +568,7 @@ export const functionResolvers = {
|
|||
if (item.highlights) return item.highlights
|
||||
|
||||
const highlights = await ctx.dataLoaders.highlights.load(item.id)
|
||||
return highlights.map(highlightDataToHighlight)
|
||||
return highlights
|
||||
},
|
||||
...readingProgressHandlers,
|
||||
async content(
|
||||
|
|
@ -585,7 +583,7 @@ export const functionResolvers = {
|
|||
) {
|
||||
// convert html to the requested format if requested
|
||||
if (item.format && item.format !== ArticleFormat.Html && item.content) {
|
||||
let highlights: HighlightEntity[] = []
|
||||
let highlights: Highlight[] = []
|
||||
// load highlights if needed
|
||||
if (
|
||||
item.format === ArticleFormat.HighlightedMarkdown &&
|
||||
|
|
@ -886,4 +884,5 @@ export const functionResolvers = {
|
|||
...resultResolveTypeResolver('Subscription'),
|
||||
...resultResolveTypeResolver('RefreshHome'),
|
||||
...resultResolveTypeResolver('HiddenHomeSection'),
|
||||
...resultResolveTypeResolver('Highlights'),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||
import { DeepPartial } from 'typeorm'
|
||||
import {
|
||||
Highlight as HighlightData,
|
||||
Highlight as HighlightEntity,
|
||||
HighlightType,
|
||||
RepresentationType,
|
||||
} from '../../entity/highlight'
|
||||
|
|
@ -16,6 +16,10 @@ import {
|
|||
DeleteHighlightError,
|
||||
DeleteHighlightErrorCode,
|
||||
DeleteHighlightSuccess,
|
||||
HighlightEdge,
|
||||
HighlightsError,
|
||||
HighlightsErrorCode,
|
||||
HighlightsSuccess,
|
||||
MergeHighlightError,
|
||||
MergeHighlightErrorCode,
|
||||
MergeHighlightSuccess,
|
||||
|
|
@ -23,6 +27,7 @@ import {
|
|||
MutationDeleteHighlightArgs,
|
||||
MutationMergeHighlightArgs,
|
||||
MutationUpdateHighlightArgs,
|
||||
QueryHighlightsArgs,
|
||||
UpdateHighlightError,
|
||||
UpdateHighlightErrorCode,
|
||||
UpdateHighlightSuccess,
|
||||
|
|
@ -32,14 +37,15 @@ import {
|
|||
createHighlight,
|
||||
deleteHighlightById,
|
||||
mergeHighlights,
|
||||
searchHighlights,
|
||||
updateHighlight,
|
||||
} from '../../services/highlights'
|
||||
import { Merge } from '../../util'
|
||||
import { analytics } from '../../utils/analytics'
|
||||
import { authorized } from '../../utils/gql-utils'
|
||||
import { highlightDataToHighlight } from '../../utils/helpers'
|
||||
|
||||
export const createHighlightResolver = authorized<
|
||||
CreateHighlightSuccess,
|
||||
Merge<CreateHighlightSuccess, { highlight: HighlightEntity }>,
|
||||
CreateHighlightError,
|
||||
MutationCreateHighlightArgs
|
||||
>(async (_, { input }, { log, pubsub, uid }) => {
|
||||
|
|
@ -68,7 +74,7 @@ export const createHighlightResolver = authorized<
|
|||
},
|
||||
})
|
||||
|
||||
return { highlight: highlightDataToHighlight(newHighlight) }
|
||||
return { highlight: newHighlight }
|
||||
} catch (err) {
|
||||
log.error('Error creating highlight', err)
|
||||
return {
|
||||
|
|
@ -78,7 +84,7 @@ export const createHighlightResolver = authorized<
|
|||
})
|
||||
|
||||
export const mergeHighlightResolver = authorized<
|
||||
MergeHighlightSuccess,
|
||||
Merge<MergeHighlightSuccess, { highlight: HighlightEntity }>,
|
||||
MergeHighlightError,
|
||||
MutationMergeHighlightArgs
|
||||
>(async (_, { input }, { authTrx, log, pubsub, uid }) => {
|
||||
|
|
@ -123,7 +129,7 @@ export const mergeHighlightResolver = authorized<
|
|||
const color =
|
||||
newHighlightInput.color || mergedColors[mergedColors.length - 1]
|
||||
|
||||
const highlight: DeepPartial<HighlightData> = {
|
||||
const highlight: DeepPartial<HighlightEntity> = {
|
||||
...newHighlightInput,
|
||||
annotation:
|
||||
mergedAnnotations.length > 0 ? mergedAnnotations.join('\n') : null,
|
||||
|
|
@ -154,7 +160,7 @@ export const mergeHighlightResolver = authorized<
|
|||
})
|
||||
|
||||
return {
|
||||
highlight: highlightDataToHighlight(newHighlight),
|
||||
highlight: newHighlight,
|
||||
overlapHighlightIdList: input.overlapHighlightIdList,
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
@ -166,7 +172,7 @@ export const mergeHighlightResolver = authorized<
|
|||
})
|
||||
|
||||
export const updateHighlightResolver = authorized<
|
||||
UpdateHighlightSuccess,
|
||||
Merge<UpdateHighlightSuccess, { highlight: HighlightEntity }>,
|
||||
UpdateHighlightError,
|
||||
MutationUpdateHighlightArgs
|
||||
>(async (_, { input }, { pubsub, uid, log }) => {
|
||||
|
|
@ -183,7 +189,7 @@ export const updateHighlightResolver = authorized<
|
|||
pubsub
|
||||
)
|
||||
|
||||
return { highlight: highlightDataToHighlight(updatedHighlight) }
|
||||
return { highlight: updatedHighlight }
|
||||
} catch (error) {
|
||||
log.error('updateHighlightResolver error', error)
|
||||
return {
|
||||
|
|
@ -193,7 +199,7 @@ export const updateHighlightResolver = authorized<
|
|||
})
|
||||
|
||||
export const deleteHighlightResolver = authorized<
|
||||
DeleteHighlightSuccess,
|
||||
Merge<DeleteHighlightSuccess, { highlight: HighlightEntity }>,
|
||||
DeleteHighlightError,
|
||||
MutationDeleteHighlightArgs
|
||||
>(async (_, { highlightId }, { log }) => {
|
||||
|
|
@ -206,7 +212,7 @@ export const deleteHighlightResolver = authorized<
|
|||
}
|
||||
}
|
||||
|
||||
return { highlight: highlightDataToHighlight(deletedHighlight) }
|
||||
return { highlight: deletedHighlight }
|
||||
} catch (error) {
|
||||
log.error('deleteHighlightResolver error', error)
|
||||
return {
|
||||
|
|
@ -215,53 +221,54 @@ export const deleteHighlightResolver = authorized<
|
|||
}
|
||||
})
|
||||
|
||||
// export const setShareHighlightResolver = authorized<
|
||||
// SetShareHighlightSuccess,
|
||||
// SetShareHighlightError,
|
||||
// MutationSetShareHighlightArgs
|
||||
// >(async (_, { input: { id, share } }, { pubsub, claims, log }) => {
|
||||
// const highlight = await getHighlightById(id)
|
||||
type PartialHighlightEdge = Merge<
|
||||
HighlightEdge,
|
||||
{
|
||||
node: HighlightEntity
|
||||
}
|
||||
>
|
||||
type PartialHighlightsSuccess = Merge<
|
||||
HighlightsSuccess,
|
||||
{
|
||||
edges: PartialHighlightEdge[]
|
||||
}
|
||||
>
|
||||
export const highlightsResolver = authorized<
|
||||
PartialHighlightsSuccess,
|
||||
HighlightsError,
|
||||
QueryHighlightsArgs
|
||||
>(async (_, { after, first }, { uid, log }) => {
|
||||
const limit = first || 10
|
||||
const offset = parseInt(after || '0')
|
||||
if (isNaN(offset) || offset < 0) {
|
||||
log.error('Invalid after', { after })
|
||||
|
||||
// if (!highlight?.id) {
|
||||
// return {
|
||||
// errorCodes: [SetShareHighlightErrorCode.NotFound],
|
||||
// }
|
||||
// }
|
||||
return {
|
||||
errorCodes: [HighlightsErrorCode.BadRequest],
|
||||
}
|
||||
}
|
||||
|
||||
// if (highlight.userId !== claims.uid) {
|
||||
// return {
|
||||
// errorCodes: [SetShareHighlightErrorCode.Forbidden],
|
||||
// }
|
||||
// }
|
||||
const highlights = await searchHighlights(uid, limit + 1, offset)
|
||||
|
||||
// const sharedAt = share ? new Date() : null
|
||||
const start = offset
|
||||
const hasNextPage = highlights.length > limit
|
||||
if (hasNextPage) {
|
||||
highlights.pop()
|
||||
}
|
||||
const endCursor = String(start + highlights.length)
|
||||
|
||||
// log.info(`${share ? 'S' : 'Uns'}haring a highlight`, {
|
||||
// highlight,
|
||||
// labels: {
|
||||
// source: 'resolver',
|
||||
// resolver: 'setShareHighlightResolver',
|
||||
// userId: highlight.userId,
|
||||
// },
|
||||
// })
|
||||
const edges = highlights.map((highlight) => ({
|
||||
cursor: endCursor,
|
||||
node: highlight,
|
||||
}))
|
||||
|
||||
// const updatedHighlight: HighlightData = {
|
||||
// ...highlight,
|
||||
// sharedAt,
|
||||
// updatedAt: new Date(),
|
||||
// }
|
||||
|
||||
// const updated = await updateHighlight(updatedHighlight, {
|
||||
// pubsub,
|
||||
// uid: claims.uid,
|
||||
// refresh: true,
|
||||
// })
|
||||
|
||||
// if (!updated) {
|
||||
// return {
|
||||
// errorCodes: [SetShareHighlightErrorCode.NotFound],
|
||||
// }
|
||||
// }
|
||||
|
||||
// return { highlight: highlightDataToHighlight(updatedHighlight) }
|
||||
// })
|
||||
return {
|
||||
edges,
|
||||
pageInfo: {
|
||||
startCursor: String(start),
|
||||
endCursor,
|
||||
hasPreviousPage: start > 0,
|
||||
hasNextPage,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import { PublicItem } from '../entity/public_item'
|
|||
import { Recommendation } from '../entity/recommendation'
|
||||
import { Subscription } from '../entity/subscription'
|
||||
import { UploadFile } from '../entity/upload_file'
|
||||
import { User } from '../entity/user'
|
||||
import { HomeItem } from '../generated/graphql'
|
||||
import { PubsubClient } from '../pubsub'
|
||||
|
||||
|
|
@ -58,6 +59,7 @@ export interface RequestContext {
|
|||
libraryItems: DataLoader<string, LibraryItem | undefined>
|
||||
publicItems: DataLoader<string, PublicItem | undefined>
|
||||
subscriptions: DataLoader<string, Subscription | undefined>
|
||||
users: DataLoader<string, User | undefined>
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3226,6 +3226,26 @@ const schema = gql`
|
|||
PENDING
|
||||
}
|
||||
|
||||
union HighlightsResult = HighlightsSuccess | HighlightsError
|
||||
|
||||
type HighlightsSuccess {
|
||||
edges: [HighlightEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
type HighlightEdge {
|
||||
cursor: String!
|
||||
node: Highlight!
|
||||
}
|
||||
|
||||
type HighlightsError {
|
||||
errorCodes: [HighlightsErrorCode!]!
|
||||
}
|
||||
|
||||
enum HighlightsErrorCode {
|
||||
BAD_REQUEST
|
||||
}
|
||||
|
||||
# Mutations
|
||||
type Mutation {
|
||||
googleLogin(input: GoogleLoginInput!): LoginResult!
|
||||
|
|
@ -3425,6 +3445,7 @@ const schema = gql`
|
|||
home(first: Int, after: String): HomeResult!
|
||||
subscription(id: ID!): SubscriptionResult!
|
||||
hiddenHomeSection: HiddenHomeSectionResult!
|
||||
highlights(after: String, first: Int, query: String): HighlightsResult!
|
||||
}
|
||||
|
||||
schema {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { diff_match_patch } from 'diff-match-patch'
|
||||
import { DeepPartial, In } from 'typeorm'
|
||||
import { DeepPartial, In, LessThan } from 'typeorm'
|
||||
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'
|
||||
import { EntityLabel } from '../entity/entity_label'
|
||||
import { Highlight } from '../entity/highlight'
|
||||
|
|
@ -260,3 +260,20 @@ export const findHighlightsByLibraryItemId = async (
|
|||
userId
|
||||
)
|
||||
}
|
||||
|
||||
export const searchHighlights = async (
|
||||
userId: string,
|
||||
limit: number,
|
||||
offset?: number
|
||||
) => {
|
||||
return authTrx(
|
||||
async (tx) =>
|
||||
tx.withRepository(highlightRepository).find({
|
||||
where: { user: { id: userId } },
|
||||
take: limit,
|
||||
skip: offset,
|
||||
}),
|
||||
undefined,
|
||||
userId
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ export const findActiveUser = async (id: string): Promise<User | null> => {
|
|||
return userRepository.findOneBy({ id, status: StatusType.Active })
|
||||
}
|
||||
|
||||
export const findUsersById = async (ids: string[]): Promise<User[]> => {
|
||||
export const findUsersByIds = async (ids: string[]): Promise<User[]> => {
|
||||
return userRepository.findBy({ id: In(ids) })
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { StatusType } from '../../src/entity/user'
|
|||
import {
|
||||
createUsers,
|
||||
deleteUsers,
|
||||
findUsersById,
|
||||
findUsersByIds,
|
||||
} from '../../src/services/user'
|
||||
import { request } from '../util'
|
||||
|
||||
|
|
@ -58,7 +58,7 @@ describe('User Service Router', () => {
|
|||
.send(data)
|
||||
.expect(200)
|
||||
|
||||
const deletedUsers = await findUsersById(toDeleteUserIds)
|
||||
const deletedUsers = await findUsersByIds(toDeleteUserIds)
|
||||
expect(deletedUsers.length).to.equal(0)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in a new issue