clean up helpers
This commit is contained in:
@ -46,6 +46,9 @@ export class LibraryItem {
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
user!: User
|
||||
|
||||
@Column('uuid')
|
||||
userId!: string
|
||||
|
||||
@Column('enum', {
|
||||
enum: LibraryItemState,
|
||||
default: LibraryItemState.Succeeded,
|
||||
|
||||
@ -5,7 +5,12 @@
|
||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||
import { Readability } from '@omnivore/readability'
|
||||
import graphqlFields from 'graphql-fields'
|
||||
import { LibraryItem, LibraryItemState } from '../../entity/library_item'
|
||||
import {
|
||||
ContentReaderType,
|
||||
LibraryItem,
|
||||
LibraryItemState,
|
||||
} from '../../entity/library_item'
|
||||
import { User } from '../../entity/user'
|
||||
import { env } from '../../env'
|
||||
import {
|
||||
ArticleError,
|
||||
@ -43,6 +48,7 @@ import {
|
||||
SaveArticleReadingProgressSuccess,
|
||||
SearchError,
|
||||
SearchErrorCode,
|
||||
SearchItemEdge,
|
||||
SearchSuccess,
|
||||
SetBookmarkArticleError,
|
||||
SetBookmarkArticleErrorCode,
|
||||
@ -87,6 +93,7 @@ import {
|
||||
setFileUploadComplete,
|
||||
} from '../../services/upload_file'
|
||||
import { traceAs } from '../../tracing'
|
||||
import { Merge } from '../../util'
|
||||
import { analytics } from '../../utils/analytics'
|
||||
import { isSiteBlockedForParse } from '../../utils/blocked'
|
||||
import { enqueueBulkAction } from '../../utils/createTask'
|
||||
@ -96,10 +103,7 @@ import {
|
||||
errorHandler,
|
||||
generateSlug,
|
||||
isParsingTimeout,
|
||||
libraryItemToArticle,
|
||||
libraryItemToSearchItem,
|
||||
titleForFilePath,
|
||||
userDataToUser,
|
||||
} from '../../utils/helpers'
|
||||
import {
|
||||
getDistillerResult,
|
||||
@ -126,7 +130,10 @@ const FORCE_PUPPETEER_URLS = [
|
||||
const UNPARSEABLE_CONTENT = '<p>We were unable to parse this page.</p>'
|
||||
|
||||
export const createArticleResolver = authorized<
|
||||
CreateArticleSuccess,
|
||||
Merge<
|
||||
CreateArticleSuccess,
|
||||
{ user: User; createdArticle: Partial<LibraryItem> }
|
||||
>,
|
||||
CreateArticleError,
|
||||
MutationCreateArticleArgs
|
||||
>(
|
||||
@ -160,8 +167,8 @@ export const createArticleResolver = authorized<
|
||||
},
|
||||
})
|
||||
|
||||
const userData = await userRepository.findById(uid)
|
||||
if (!userData) {
|
||||
const user = await userRepository.findById(uid)
|
||||
if (!user) {
|
||||
return errorHandler(
|
||||
{
|
||||
errorCodes: [CreateArticleErrorCode.Unauthorized],
|
||||
@ -171,7 +178,6 @@ export const createArticleResolver = authorized<
|
||||
pubsub
|
||||
)
|
||||
}
|
||||
const user = userDataToUser(userData)
|
||||
|
||||
try {
|
||||
if (isSiteBlockedForParse(url)) {
|
||||
@ -203,25 +209,22 @@ export const createArticleResolver = authorized<
|
||||
let domContent = null
|
||||
let itemType = PageType.Unknown
|
||||
|
||||
const DUMMY_RESPONSE: CreateArticleSuccess = {
|
||||
const DUMMY_RESPONSE = {
|
||||
user,
|
||||
created: false,
|
||||
createdArticle: {
|
||||
id: '',
|
||||
slug: '',
|
||||
createdAt: new Date(),
|
||||
originalHtml: domContent,
|
||||
content: '',
|
||||
originalContent: domContent,
|
||||
readableContent: '',
|
||||
description: '',
|
||||
title: '',
|
||||
pageType: itemType,
|
||||
contentReader: ContentReader.Web,
|
||||
itemType,
|
||||
contentReader: ContentReaderType.WEB,
|
||||
author: '',
|
||||
url,
|
||||
hash: '',
|
||||
isArchived: false,
|
||||
readingProgressAnchorIndex: 0,
|
||||
readingProgressPercent: 0,
|
||||
originalUrl: url,
|
||||
textContentHash: '',
|
||||
highlights: [],
|
||||
savedAt: savedAt || new Date(),
|
||||
updatedAt: new Date(),
|
||||
@ -257,7 +260,7 @@ export const createArticleResolver = authorized<
|
||||
FORCE_PUPPETEER_URLS.some((regex) => regex.test(url))
|
||||
) {
|
||||
await createPageSaveRequest({
|
||||
user: userData,
|
||||
user: user,
|
||||
url,
|
||||
state: state || undefined,
|
||||
labels: inputLabels || undefined,
|
||||
@ -282,7 +285,7 @@ export const createArticleResolver = authorized<
|
||||
// We have a URL but no document, so we try to send this to puppeteer
|
||||
// and return a dummy response.
|
||||
await createPageSaveRequest({
|
||||
user: userData,
|
||||
user,
|
||||
url,
|
||||
state: state || undefined,
|
||||
labels: inputLabels || undefined,
|
||||
@ -353,7 +356,7 @@ export const createArticleResolver = authorized<
|
||||
return {
|
||||
user,
|
||||
created: true,
|
||||
createdArticle: libraryItemToArticle(libraryItemToReturn),
|
||||
createdArticle: libraryItemToReturn,
|
||||
}
|
||||
} catch (error) {
|
||||
log.error('Error creating article', error)
|
||||
@ -370,7 +373,7 @@ export const createArticleResolver = authorized<
|
||||
)
|
||||
|
||||
export const getArticleResolver = authorized<
|
||||
ArticleSuccess,
|
||||
Merge<ArticleSuccess, { article: LibraryItem }>,
|
||||
ArticleError,
|
||||
QueryArticleArgs
|
||||
>(async (_obj, { slug, format }, { authTrx, uid, log }, info) => {
|
||||
@ -439,7 +442,7 @@ export const getArticleResolver = authorized<
|
||||
}
|
||||
|
||||
return {
|
||||
article: libraryItemToArticle(libraryItem),
|
||||
article: libraryItem,
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(error)
|
||||
@ -447,88 +450,8 @@ export const getArticleResolver = authorized<
|
||||
}
|
||||
})
|
||||
|
||||
// type PaginatedPartialArticles = {
|
||||
// edges: { cursor: string; node: PartialArticle }[]
|
||||
// pageInfo: PageInfo
|
||||
// }
|
||||
|
||||
// export type SetShareArticleSuccessPartial = Merge<
|
||||
// SetShareArticleSuccess,
|
||||
// {
|
||||
// updatedFeedArticle?: Omit<
|
||||
// FeedArticle,
|
||||
// | 'sharedBy'
|
||||
// | 'article'
|
||||
// | 'highlightsCount'
|
||||
// | 'annotationsCount'
|
||||
// | 'reactions'
|
||||
// >
|
||||
// updatedFeedArticleId?: string
|
||||
// updatedArticle: PartialArticle
|
||||
// }
|
||||
// >
|
||||
|
||||
// export const setShareArticleResolver = authorized<
|
||||
// SetShareArticleSuccessPartial,
|
||||
// SetShareArticleError,
|
||||
// MutationSetShareArticleArgs
|
||||
// >(
|
||||
// async (
|
||||
// _,
|
||||
// { input: { articleID, share, sharedComment, sharedWithHighlights } },
|
||||
// { models, authTrx, claims: { uid }, log }
|
||||
// ) => {
|
||||
// const article = await models.article.get(articleID)
|
||||
// if (!article) {
|
||||
// return { errorCodes: [SetShareArticleErrorCode.NotFound] }
|
||||
// }
|
||||
|
||||
// const sharedAt = share ? new Date() : null
|
||||
|
||||
// log.info(`${share ? 'S' : 'Uns'}haring an article`, {
|
||||
// article: Object.assign({}, article, {
|
||||
// content: undefined,
|
||||
// originalHtml: undefined,
|
||||
// sharedAt,
|
||||
// }),
|
||||
// labels: {
|
||||
// source: 'resolver',
|
||||
// resolver: 'setShareArticleResolver',
|
||||
// articleId: article.id,
|
||||
// distinctId: uid,
|
||||
// },
|
||||
// })
|
||||
|
||||
// const result = await authTrx((tx) =>
|
||||
// models.userArticle.updateByArticleId(
|
||||
// uid,
|
||||
// articleID,
|
||||
// { sharedAt, sharedComment, sharedWithHighlights },
|
||||
// tx
|
||||
// )
|
||||
// )
|
||||
|
||||
// if (!result) {
|
||||
// return { errorCodes: [SetShareArticleErrorCode.NotFound] }
|
||||
// }
|
||||
|
||||
// // Make sure article.id instead of userArticle.id has passed. We use it for cache updates
|
||||
// const updatedArticle = {
|
||||
// ...result,
|
||||
// ...article,
|
||||
// postedByViewer: !!sharedAt,
|
||||
// }
|
||||
// const updatedFeedArticle = sharedAt ? { ...result, sharedAt } : undefined
|
||||
// return {
|
||||
// updatedFeedArticleId: result.id,
|
||||
// updatedFeedArticle,
|
||||
// updatedArticle,
|
||||
// }
|
||||
// }
|
||||
// )
|
||||
|
||||
export const setBookmarkArticleResolver = authorized<
|
||||
SetBookmarkArticleSuccess,
|
||||
Merge<SetBookmarkArticleSuccess, { bookmarkedArticle: LibraryItem }>,
|
||||
SetBookmarkArticleError,
|
||||
MutationSetBookmarkArticleArgs
|
||||
>(async (_, { input: { articleID } }, { uid, log, pubsub }) => {
|
||||
@ -556,12 +479,12 @@ export const setBookmarkArticleResolver = authorized<
|
||||
})
|
||||
// Make sure article.id instead of userArticle.id has passed. We use it for cache updates
|
||||
return {
|
||||
bookmarkedArticle: libraryItemToArticle(deletedLibraryItem),
|
||||
bookmarkedArticle: deletedLibraryItem,
|
||||
}
|
||||
})
|
||||
|
||||
export const saveArticleReadingProgressResolver = authorized<
|
||||
SaveArticleReadingProgressSuccess,
|
||||
Merge<SaveArticleReadingProgressSuccess, { updatedArticle: LibraryItem }>,
|
||||
SaveArticleReadingProgressError,
|
||||
MutationSaveArticleReadingProgressArgs
|
||||
>(
|
||||
@ -661,13 +584,15 @@ export const saveArticleReadingProgressResolver = authorized<
|
||||
}
|
||||
|
||||
return {
|
||||
updatedArticle: libraryItemToArticle(updatedItem),
|
||||
updatedArticle: updatedItem,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export type PartialLibraryItem = Merge<LibraryItem, { format?: string }>
|
||||
type PartialSearchItemEdge = Merge<SearchItemEdge, { node: PartialLibraryItem }>
|
||||
export const searchResolver = authorized<
|
||||
SearchSuccess,
|
||||
Merge<SearchSuccess, { edges: Array<PartialSearchItemEdge> }>,
|
||||
SearchError,
|
||||
QuerySearchArgs
|
||||
>(async (_obj, params, { uid }) => {
|
||||
@ -704,7 +629,10 @@ export const searchResolver = authorized<
|
||||
|
||||
return {
|
||||
edges: libraryItems.map((item) => ({
|
||||
node: libraryItemToSearchItem(item, params.format as ArticleFormat),
|
||||
node: {
|
||||
...item,
|
||||
format: params.format || undefined,
|
||||
},
|
||||
cursor: endCursor,
|
||||
})),
|
||||
pageInfo: {
|
||||
@ -738,7 +666,7 @@ export const typeaheadSearchResolver = authorized<
|
||||
})
|
||||
|
||||
export const updatesSinceResolver = authorized<
|
||||
UpdatesSinceSuccess,
|
||||
Merge<UpdatesSinceSuccess, { edges: Array<PartialSearchItemEdge> }>,
|
||||
UpdatesSinceError,
|
||||
QueryUpdatesSinceArgs
|
||||
>(async (_obj, { since, first, after, sort: sortParams, folder }, { uid }) => {
|
||||
@ -781,7 +709,7 @@ export const updatesSinceResolver = authorized<
|
||||
const edges = libraryItems.map((item) => {
|
||||
const updateReason = getUpdateReason(item, startDate)
|
||||
return {
|
||||
node: libraryItemToSearchItem(item),
|
||||
node: item,
|
||||
cursor: endCursor,
|
||||
itemID: item.id,
|
||||
updateReason,
|
||||
|
||||
@ -17,17 +17,17 @@ import {
|
||||
findLibraryItemById,
|
||||
findLibraryItemByUrl,
|
||||
} from '../../services/library_item'
|
||||
import { Merge } from '../../util'
|
||||
import { analytics } from '../../utils/analytics'
|
||||
import { authorized } from '../../utils/gql-utils'
|
||||
import {
|
||||
cleanUrl,
|
||||
isParsingTimeout,
|
||||
libraryItemToArticleSavingRequest,
|
||||
} from '../../utils/helpers'
|
||||
import { cleanUrl, isParsingTimeout } from '../../utils/helpers'
|
||||
import { isErrorWithCode } from '../user'
|
||||
|
||||
export const createArticleSavingRequestResolver = authorized<
|
||||
CreateArticleSavingRequestSuccess,
|
||||
Merge<
|
||||
CreateArticleSavingRequestSuccess,
|
||||
{ articleSavingRequest: LibraryItem }
|
||||
>,
|
||||
CreateArticleSavingRequestError,
|
||||
MutationCreateArticleSavingRequestArgs
|
||||
>(async (_, { input: { url } }, { uid, pubsub, log }) => {
|
||||
@ -67,7 +67,7 @@ export const createArticleSavingRequestResolver = authorized<
|
||||
})
|
||||
|
||||
export const articleSavingRequestResolver = authorized<
|
||||
ArticleSavingRequestSuccess,
|
||||
Merge<ArticleSavingRequestSuccess, { articleSavingRequest: LibraryItem }>,
|
||||
ArticleSavingRequestError,
|
||||
QueryArticleSavingRequestArgs
|
||||
>(async (_, { id, url }, { uid, log }) => {
|
||||
@ -109,10 +109,7 @@ export const articleSavingRequestResolver = authorized<
|
||||
libraryItem.state = LibraryItemState.Succeeded
|
||||
}
|
||||
return {
|
||||
articleSavingRequest: libraryItemToArticleSavingRequest(
|
||||
user,
|
||||
libraryItem
|
||||
),
|
||||
articleSavingRequest: libraryItem,
|
||||
}
|
||||
} catch (error) {
|
||||
log.error('articleSavingRequestResolver error', error)
|
||||
|
||||
@ -6,12 +6,14 @@
|
||||
import { createHmac } from 'crypto'
|
||||
import { isError } from 'lodash'
|
||||
import { Highlight } from '../entity/highlight'
|
||||
import { Label } from '../entity/label'
|
||||
import { LibraryItem } from '../entity/library_item'
|
||||
import {
|
||||
EXISTING_NEWSLETTER_FOLDER,
|
||||
NewsletterEmail,
|
||||
} from '../entity/newsletter_email'
|
||||
import { PublicItem } from '../entity/public_item'
|
||||
import { Recommendation } from '../entity/recommendation'
|
||||
import {
|
||||
DEFAULT_SUBSCRIPTION_FOLDER,
|
||||
Subscription,
|
||||
@ -19,25 +21,16 @@ import {
|
||||
import { User as UserEntity } from '../entity/user'
|
||||
import { env } from '../env'
|
||||
import {
|
||||
Article,
|
||||
HomeItem,
|
||||
HomeItemSource,
|
||||
HomeItemSourceType,
|
||||
Label,
|
||||
PageType,
|
||||
Recommendation,
|
||||
SearchItem,
|
||||
User,
|
||||
} from '../generated/graphql'
|
||||
import { getAISummary } from '../services/ai-summaries'
|
||||
import { findUserFeatures } from '../services/features'
|
||||
import { Merge } from '../util'
|
||||
import {
|
||||
isBase64Image,
|
||||
recommandationDataToRecommendation,
|
||||
validatedDate,
|
||||
wordsCount,
|
||||
} from '../utils/helpers'
|
||||
import { isBase64Image, validatedDate, wordsCount } from '../utils/helpers'
|
||||
import { createImageProxyUrl } from '../utils/imageproxy'
|
||||
import { contentConverter } from '../utils/parser'
|
||||
import {
|
||||
@ -48,6 +41,7 @@ import {
|
||||
ArticleFormat,
|
||||
emptyTrashResolver,
|
||||
fetchContentResolver,
|
||||
PartialLibraryItem,
|
||||
} from './article'
|
||||
import {
|
||||
addDiscoverFeedResolver,
|
||||
@ -192,7 +186,7 @@ const resultResolveTypeResolver = (
|
||||
|
||||
const readingProgressHandlers = {
|
||||
async readingProgressPercent(
|
||||
article: { id: string; readingProgressPercent?: number },
|
||||
article: LibraryItem,
|
||||
_: unknown,
|
||||
ctx: WithDataSourcesContext
|
||||
) {
|
||||
@ -204,15 +198,15 @@ const readingProgressHandlers = {
|
||||
)
|
||||
if (readingProgress) {
|
||||
return Math.max(
|
||||
article.readingProgressPercent ?? 0,
|
||||
article.readingProgressBottomPercent ?? 0,
|
||||
readingProgress.readingProgressPercent
|
||||
)
|
||||
}
|
||||
}
|
||||
return article.readingProgressPercent
|
||||
return article.readingProgressBottomPercent
|
||||
},
|
||||
async readingProgressAnchorIndex(
|
||||
article: { id: string; readingProgressAnchorIndex?: number },
|
||||
article: LibraryItem,
|
||||
_: unknown,
|
||||
ctx: WithDataSourcesContext
|
||||
) {
|
||||
@ -224,15 +218,15 @@ const readingProgressHandlers = {
|
||||
)
|
||||
if (readingProgress && readingProgress.readingProgressAnchorIndex) {
|
||||
return Math.max(
|
||||
article.readingProgressAnchorIndex ?? 0,
|
||||
article.readingProgressHighestReadAnchor ?? 0,
|
||||
readingProgress.readingProgressAnchorIndex
|
||||
)
|
||||
}
|
||||
}
|
||||
return article.readingProgressAnchorIndex
|
||||
return article.readingProgressHighestReadAnchor
|
||||
},
|
||||
async readingProgressTopPercent(
|
||||
article: { id: string; readingProgressTopPercent?: number },
|
||||
article: LibraryItem,
|
||||
_: unknown,
|
||||
ctx: WithDataSourcesContext
|
||||
) {
|
||||
@ -427,10 +421,10 @@ export const functionResolvers = {
|
||||
sharedNotesCount: () => 0,
|
||||
},
|
||||
Article: {
|
||||
async url(article: Article, _: unknown, ctx: WithDataSourcesContext) {
|
||||
async url(article: LibraryItem, _: unknown, ctx: WithDataSourcesContext) {
|
||||
if (
|
||||
(article.pageType == PageType.File ||
|
||||
article.pageType == PageType.Book) &&
|
||||
(article.itemType == PageType.File ||
|
||||
article.itemType == PageType.Book) &&
|
||||
ctx.claims &&
|
||||
article.uploadFileId
|
||||
) {
|
||||
@ -443,29 +437,33 @@ export const functionResolvers = {
|
||||
const filePath = generateUploadFilePathName(upload.id, upload.fileName)
|
||||
return generateDownloadSignedUrl(filePath)
|
||||
}
|
||||
return article.url
|
||||
return article.originalUrl
|
||||
},
|
||||
originalArticleUrl(article: { url: string }) {
|
||||
return article.url
|
||||
originalArticleUrl(article: LibraryItem) {
|
||||
return article.originalUrl
|
||||
},
|
||||
hasContent(article: {
|
||||
content: string | null
|
||||
originalHtml: string | null
|
||||
}) {
|
||||
return !!article.originalHtml && !!article.content
|
||||
hasContent(article: LibraryItem) {
|
||||
return !!article.originalContent && !!article.readableContent
|
||||
},
|
||||
publishedAt(article: { publishedAt: Date }) {
|
||||
return validatedDate(article.publishedAt)
|
||||
},
|
||||
image(article: { image?: string }): string | undefined {
|
||||
return article.image && createImageProxyUrl(article.image, 320, 320)
|
||||
image(article: LibraryItem): string | undefined {
|
||||
if (article.thumbnail) {
|
||||
return createImageProxyUrl(article.thumbnail, 320, 320)
|
||||
}
|
||||
|
||||
return undefined
|
||||
},
|
||||
wordsCount(article: { wordCount?: number; content?: string }) {
|
||||
wordsCount(article: LibraryItem): number | undefined {
|
||||
if (article.wordCount) return article.wordCount
|
||||
return article.content ? wordsCount(article.content) : undefined
|
||||
|
||||
return article.readableContent
|
||||
? wordsCount(article.readableContent)
|
||||
: undefined
|
||||
},
|
||||
async labels(
|
||||
article: { id: string; labels?: Label[] },
|
||||
article: LibraryItem,
|
||||
_: unknown,
|
||||
ctx: WithDataSourcesContext
|
||||
) {
|
||||
@ -473,6 +471,11 @@ export const functionResolvers = {
|
||||
|
||||
return ctx.dataLoaders.labels.load(article.id)
|
||||
},
|
||||
content: (item: LibraryItem) => item.readableContent,
|
||||
hash: (item: LibraryItem) => item.textContentHash || '',
|
||||
isArchived: (item: LibraryItem) => !!item.archivedAt,
|
||||
uploadFileId: (item: LibraryItem) => item.uploadFile?.id,
|
||||
pageType: (item: LibraryItem) => item.itemType,
|
||||
...readingProgressHandlers,
|
||||
},
|
||||
Highlight: {
|
||||
@ -491,9 +494,9 @@ export const functionResolvers = {
|
||||
},
|
||||
},
|
||||
SearchItem: {
|
||||
async url(item: SearchItem, _: unknown, ctx: WithDataSourcesContext) {
|
||||
async url(item: LibraryItem, _: unknown, ctx: WithDataSourcesContext) {
|
||||
if (
|
||||
(item.pageType == PageType.File || item.pageType == PageType.Book) &&
|
||||
(item.itemType == PageType.File || item.itemType == PageType.Book) &&
|
||||
ctx.claims &&
|
||||
item.uploadFileId
|
||||
) {
|
||||
@ -504,19 +507,19 @@ export const functionResolvers = {
|
||||
const filePath = generateUploadFilePathName(upload.id, upload.fileName)
|
||||
return generateDownloadSignedUrl(filePath)
|
||||
}
|
||||
return item.url
|
||||
return item.originalUrl
|
||||
},
|
||||
image(item: SearchItem) {
|
||||
return item.image && createImageProxyUrl(item.image, 320, 320)
|
||||
image(item: LibraryItem) {
|
||||
return item.thumbnail && createImageProxyUrl(item.thumbnail, 320, 320)
|
||||
},
|
||||
originalArticleUrl(item: { url: string }) {
|
||||
return item.url
|
||||
originalArticleUrl(item: LibraryItem) {
|
||||
return item.originalUrl
|
||||
},
|
||||
wordsCount(item: { wordCount?: number; content?: string }) {
|
||||
wordsCount(item: LibraryItem) {
|
||||
if (item.wordCount) return item.wordCount
|
||||
return item.content ? wordsCount(item.content) : undefined
|
||||
return item.readableContent ? wordsCount(item.readableContent) : undefined
|
||||
},
|
||||
siteIcon(item: { siteIcon?: string }) {
|
||||
siteIcon(item: LibraryItem) {
|
||||
if (item.siteIcon && !isBase64Image(item.siteIcon)) {
|
||||
return createImageProxyUrl(item.siteIcon, 128, 128)
|
||||
}
|
||||
@ -546,9 +549,13 @@ export const functionResolvers = {
|
||||
const recommendations = await ctx.dataLoaders.recommendations.load(
|
||||
item.id
|
||||
)
|
||||
return recommendations.map(recommandationDataToRecommendation)
|
||||
return recommendations
|
||||
},
|
||||
async aiSummary(item: SearchItem, _: unknown, ctx: WithDataSourcesContext) {
|
||||
async aiSummary(
|
||||
item: LibraryItem,
|
||||
_: unknown,
|
||||
ctx: WithDataSourcesContext
|
||||
) {
|
||||
return (
|
||||
await getAISummary({
|
||||
userId: ctx.uid,
|
||||
@ -572,17 +579,16 @@ export const functionResolvers = {
|
||||
},
|
||||
...readingProgressHandlers,
|
||||
async content(
|
||||
item: {
|
||||
id: string
|
||||
content?: string
|
||||
highlightAnnotations?: string[]
|
||||
format?: ArticleFormat
|
||||
},
|
||||
item: PartialLibraryItem,
|
||||
_: unknown,
|
||||
ctx: WithDataSourcesContext
|
||||
) {
|
||||
// convert html to the requested format if requested
|
||||
if (item.format && item.format !== ArticleFormat.Html && item.content) {
|
||||
if (
|
||||
item.format &&
|
||||
item.format !== ArticleFormat.Html &&
|
||||
item.readableContent
|
||||
) {
|
||||
let highlights: Highlight[] = []
|
||||
// load highlights if needed
|
||||
if (
|
||||
@ -598,15 +604,17 @@ export const functionResolvers = {
|
||||
// convert html to the requested format
|
||||
const converter = contentConverter(item.format)
|
||||
if (converter) {
|
||||
return converter(item.content, highlights)
|
||||
return converter(item.readableContent, highlights)
|
||||
}
|
||||
} catch (error) {
|
||||
ctx.log.error('Error converting content', error)
|
||||
}
|
||||
}
|
||||
|
||||
return item.content
|
||||
return item.readableContent
|
||||
},
|
||||
isArchived: (item: LibraryItem) => !!item.archivedAt,
|
||||
pageType: (item: LibraryItem) => item.itemType,
|
||||
},
|
||||
Subscription: {
|
||||
newsletterEmail(subscription: Subscription) {
|
||||
@ -781,6 +789,22 @@ export const functionResolvers = {
|
||||
}
|
||||
},
|
||||
},
|
||||
ArticleSavingRequest: {
|
||||
status: (item: LibraryItem) => item.state,
|
||||
url: (item: LibraryItem) => item.originalUrl,
|
||||
},
|
||||
Recommendation: {
|
||||
user: (recommendation: Recommendation) => {
|
||||
return {
|
||||
userId: recommendation.recommender.id,
|
||||
username: recommendation.recommender.profile.username,
|
||||
profileImageURL: recommendation.recommender.profile.pictureUrl,
|
||||
name: recommendation.recommender.name,
|
||||
}
|
||||
},
|
||||
name: (recommendation: Recommendation) => recommendation.group.name,
|
||||
recommendedAt: (recommendation: Recommendation) => recommendation.createdAt,
|
||||
},
|
||||
...resultResolveTypeResolver('Login'),
|
||||
...resultResolveTypeResolver('LogOut'),
|
||||
...resultResolveTypeResolver('GoogleSignup'),
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { In } from 'typeorm'
|
||||
import { Group } from '../../entity/groups/group'
|
||||
import { User } from '../../entity/user'
|
||||
import { env } from '../../env'
|
||||
import {
|
||||
CreateGroupError,
|
||||
@ -19,6 +20,7 @@ import {
|
||||
MutationLeaveGroupArgs,
|
||||
MutationRecommendArgs,
|
||||
MutationRecommendHighlightsArgs,
|
||||
RecommendationGroup,
|
||||
RecommendError,
|
||||
RecommendErrorCode,
|
||||
RecommendHighlightsError,
|
||||
@ -38,26 +40,30 @@ import {
|
||||
leaveGroup,
|
||||
} from '../../services/groups'
|
||||
import { findLibraryItemById } from '../../services/library_item'
|
||||
import { Merge } from '../../util'
|
||||
import { analytics } from '../../utils/analytics'
|
||||
import { enqueueRecommendation } from '../../utils/createTask'
|
||||
import { authorized } from '../../utils/gql-utils'
|
||||
import { userDataToUser } from '../../utils/helpers'
|
||||
|
||||
export type PartialRecommendationGroup = Merge<
|
||||
RecommendationGroup,
|
||||
{ admins: Array<User>; members: Array<User> }
|
||||
>
|
||||
export const createGroupResolver = authorized<
|
||||
CreateGroupSuccess,
|
||||
Merge<CreateGroupSuccess, { group: PartialRecommendationGroup }>,
|
||||
CreateGroupError,
|
||||
MutationCreateGroupArgs
|
||||
>(async (_, { input }, { uid, log }) => {
|
||||
try {
|
||||
const userData = await userRepository.findById(uid)
|
||||
if (!userData) {
|
||||
const user = await userRepository.findById(uid)
|
||||
if (!user) {
|
||||
return {
|
||||
errorCodes: [CreateGroupErrorCode.Unauthorized],
|
||||
}
|
||||
}
|
||||
|
||||
const [group, invite] = await createGroup({
|
||||
admin: userData,
|
||||
admin: user,
|
||||
name: input.name,
|
||||
maxMembers: input.maxMembers,
|
||||
expiresInDays: input.expiresInDays,
|
||||
@ -80,7 +86,6 @@ export const createGroupResolver = authorized<
|
||||
await createLabelAndRuleForGroup(uid, group.name)
|
||||
|
||||
const inviteUrl = getInviteUrl(invite)
|
||||
const user = userDataToUser(userData)
|
||||
|
||||
return {
|
||||
group: {
|
||||
@ -103,37 +108,38 @@ export const createGroupResolver = authorized<
|
||||
}
|
||||
})
|
||||
|
||||
export const groupsResolver = authorized<GroupsSuccess, GroupsError>(
|
||||
async (_, __, { uid, log }) => {
|
||||
try {
|
||||
const user = await userRepository.findById(uid)
|
||||
if (!user) {
|
||||
return {
|
||||
errorCodes: [GroupsErrorCode.Unauthorized],
|
||||
}
|
||||
}
|
||||
|
||||
const groups = await getRecommendationGroups(user)
|
||||
|
||||
export const groupsResolver = authorized<
|
||||
Merge<GroupsSuccess, { groups: Array<PartialRecommendationGroup> }>,
|
||||
GroupsError
|
||||
>(async (_, __, { uid, log }) => {
|
||||
try {
|
||||
const user = await userRepository.findById(uid)
|
||||
if (!user) {
|
||||
return {
|
||||
groups,
|
||||
}
|
||||
} catch (error) {
|
||||
log.error('Error getting groups', {
|
||||
error,
|
||||
labels: {
|
||||
source: 'resolver',
|
||||
resolver: 'groupsResolver',
|
||||
uid,
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
errorCodes: [GroupsErrorCode.BadRequest],
|
||||
errorCodes: [GroupsErrorCode.Unauthorized],
|
||||
}
|
||||
}
|
||||
|
||||
const groups = await getRecommendationGroups(user)
|
||||
|
||||
return {
|
||||
groups,
|
||||
}
|
||||
} catch (error) {
|
||||
log.error('Error getting groups', {
|
||||
error,
|
||||
labels: {
|
||||
source: 'resolver',
|
||||
resolver: 'groupsResolver',
|
||||
uid,
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
errorCodes: [GroupsErrorCode.BadRequest],
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
export const recommendResolver = authorized<
|
||||
RecommendSuccess,
|
||||
@ -206,7 +212,7 @@ export const recommendResolver = authorized<
|
||||
})
|
||||
|
||||
export const joinGroupResolver = authorized<
|
||||
JoinGroupSuccess,
|
||||
Merge<JoinGroupSuccess, { group: PartialRecommendationGroup }>,
|
||||
JoinGroupError,
|
||||
MutationJoinGroupArgs
|
||||
>(async (_, { inviteCode }, { uid, log }) => {
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import { LibraryItemState } from '../../entity/library_item'
|
||||
import { LibraryItem, LibraryItemState } from '../../entity/library_item'
|
||||
import {
|
||||
MutationUpdatePageArgs,
|
||||
UpdatePageError,
|
||||
UpdatePageSuccess,
|
||||
} from '../../generated/graphql'
|
||||
import { updateLibraryItem } from '../../services/library_item'
|
||||
import { libraryItemToArticle } from '../../utils/helpers'
|
||||
import { Merge } from '../../util'
|
||||
import { authorized } from '../../utils/gql-utils'
|
||||
|
||||
export const updatePageResolver = authorized<
|
||||
UpdatePageSuccess,
|
||||
Merge<UpdatePageSuccess, { updatedPage: LibraryItem }>,
|
||||
UpdatePageError,
|
||||
MutationUpdatePageArgs
|
||||
>(async (_, { input }, { uid }) => {
|
||||
@ -29,6 +29,6 @@ export const updatePageResolver = authorized<
|
||||
uid
|
||||
)
|
||||
return {
|
||||
updatedPage: libraryItemToArticle(updatedPage),
|
||||
updatedPage: updatedPage,
|
||||
}
|
||||
})
|
||||
|
||||
@ -33,7 +33,6 @@ import {
|
||||
UpdateUserProfileErrorCode,
|
||||
UpdateUserProfileSuccess,
|
||||
UpdateUserSuccess,
|
||||
User,
|
||||
UserErrorCode,
|
||||
UserResult,
|
||||
UsersError,
|
||||
@ -43,13 +42,13 @@ import { userRepository } from '../../repository/user'
|
||||
import { createUser } from '../../services/create_user'
|
||||
import { sendAccountChangeEmail } from '../../services/send_emails'
|
||||
import { softDeleteUser } from '../../services/user'
|
||||
import { userDataToUser } from '../../utils/helpers'
|
||||
import { Merge } from '../../util'
|
||||
import { authorized } from '../../utils/gql-utils'
|
||||
import { validateUsername } from '../../utils/usernamePolicy'
|
||||
import { WithDataSourcesContext } from '../types'
|
||||
import { authorized } from '../../utils/gql-utils'
|
||||
|
||||
export const updateUserResolver = authorized<
|
||||
UpdateUserSuccess,
|
||||
Merge<UpdateUserSuccess, { user: UserEntity }>,
|
||||
UpdateUserError,
|
||||
MutationUpdateUserArgs
|
||||
>(async (_, { input: { name, bio } }, { uid, authTrx }) => {
|
||||
@ -83,11 +82,11 @@ export const updateUserResolver = authorized<
|
||||
})
|
||||
)
|
||||
|
||||
return { user: userDataToUser(updatedUser) }
|
||||
return { user: updatedUser }
|
||||
})
|
||||
|
||||
export const updateUserProfileResolver = authorized<
|
||||
UpdateUserProfileSuccess,
|
||||
Merge<UpdateUserProfileSuccess, { user: UserEntity }>,
|
||||
UpdateUserProfileError,
|
||||
MutationUpdateUserProfileArgs
|
||||
>(async (_, { input: { userId, username, pictureUrl } }, { uid, authTrx }) => {
|
||||
@ -140,11 +139,11 @@ export const updateUserProfileResolver = authorized<
|
||||
})
|
||||
)
|
||||
|
||||
return { user: userDataToUser(updatedUser) }
|
||||
return { user: updatedUser }
|
||||
})
|
||||
|
||||
export const googleLoginResolver: ResolverFn<
|
||||
LoginResult,
|
||||
Merge<LoginResult, { me?: UserEntity }>,
|
||||
unknown,
|
||||
WithDataSourcesContext,
|
||||
MutationGoogleLoginArgs
|
||||
@ -167,7 +166,7 @@ export const googleLoginResolver: ResolverFn<
|
||||
|
||||
// set auth cookie in response header
|
||||
await setAuth({ uid: user.id })
|
||||
return { me: userDataToUser(user) }
|
||||
return { me: user }
|
||||
}
|
||||
|
||||
export const validateUsernameResolver: ResolverFn<
|
||||
@ -190,7 +189,7 @@ export const validateUsernameResolver: ResolverFn<
|
||||
}
|
||||
|
||||
export const googleSignupResolver: ResolverFn<
|
||||
GoogleSignupResult,
|
||||
Merge<GoogleSignupResult, { me?: UserEntity }>,
|
||||
Record<string, unknown>,
|
||||
WithDataSourcesContext,
|
||||
MutationGoogleSignupArgs
|
||||
@ -205,7 +204,7 @@ export const googleSignupResolver: ResolverFn<
|
||||
}
|
||||
|
||||
try {
|
||||
const [user, profile] = await createUser({
|
||||
const [user] = await createUser({
|
||||
email,
|
||||
sourceUserId,
|
||||
provider: 'GOOGLE',
|
||||
@ -218,7 +217,7 @@ export const googleSignupResolver: ResolverFn<
|
||||
|
||||
await setAuth({ uid: user.id })
|
||||
return {
|
||||
me: userDataToUser({ ...user, profile: { ...profile, private: false } }),
|
||||
me: user,
|
||||
}
|
||||
} catch (err) {
|
||||
log.info('error signing up with google', err)
|
||||
@ -245,7 +244,7 @@ export const logOutResolver: ResolverFn<
|
||||
}
|
||||
|
||||
export const getMeUserResolver: ResolverFn<
|
||||
User | undefined,
|
||||
UserEntity | undefined,
|
||||
unknown,
|
||||
WithDataSourcesContext,
|
||||
unknown
|
||||
@ -260,14 +259,14 @@ export const getMeUserResolver: ResolverFn<
|
||||
return undefined
|
||||
}
|
||||
|
||||
return userDataToUser(user)
|
||||
return user
|
||||
} catch (error) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
export const getUserResolver: ResolverFn<
|
||||
UserResult,
|
||||
Merge<UserResult, { user?: UserEntity }>,
|
||||
unknown,
|
||||
WithDataSourcesContext,
|
||||
QueryUserArgs
|
||||
@ -294,16 +293,17 @@ export const getUserResolver: ResolverFn<
|
||||
return { errorCodes: [UserErrorCode.UserNotFound] }
|
||||
}
|
||||
|
||||
return { user: userDataToUser(userRecord) }
|
||||
return { user: userRecord }
|
||||
}
|
||||
|
||||
export const getAllUsersResolver = authorized<UsersSuccess, UsersError>(
|
||||
async (_obj, _params) => {
|
||||
const users = await userRepository.findTopUsers()
|
||||
const result = { users: users.map((userData) => userDataToUser(userData)) }
|
||||
return result
|
||||
}
|
||||
)
|
||||
export const getAllUsersResolver = authorized<
|
||||
Merge<UsersSuccess, { users: Array<UserEntity> }>,
|
||||
UsersError
|
||||
>(async (_obj, _params) => {
|
||||
const users = await userRepository.findTopUsers()
|
||||
const result = { users }
|
||||
return result
|
||||
})
|
||||
|
||||
type ErrorWithCode = {
|
||||
errorCode: string
|
||||
|
||||
@ -49,22 +49,23 @@ export function articleRouter() {
|
||||
return res.status(400).send('Bad Request')
|
||||
}
|
||||
|
||||
const result = await createPageSaveRequest({ user, url })
|
||||
|
||||
if (isSiteBlockedForParse(url)) {
|
||||
return res
|
||||
.status(400)
|
||||
.send({ errorCode: CreateArticleErrorCode.NotAllowedToParse })
|
||||
}
|
||||
|
||||
if (result.errorCode) {
|
||||
return res.status(400).send({ errorCode: result.errorCode })
|
||||
}
|
||||
try {
|
||||
const result = await createPageSaveRequest({ user, url })
|
||||
|
||||
return res.send({
|
||||
articleSavingRequestId: result.id,
|
||||
url: result.url,
|
||||
})
|
||||
return res.send({
|
||||
articleSavingRequestId: result.id,
|
||||
url: result.originalUrl,
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error saving article:', error)
|
||||
return res.status(500).send({ errorCode: 'INTERNAL_ERROR' })
|
||||
}
|
||||
})
|
||||
|
||||
router.get(
|
||||
|
||||
@ -1,20 +1,16 @@
|
||||
import * as privateIpLib from 'private-ip'
|
||||
import { LibraryItemState } from '../entity/library_item'
|
||||
import { LibraryItem, LibraryItemState } from '../entity/library_item'
|
||||
import { User } from '../entity/user'
|
||||
import {
|
||||
ArticleSavingRequest,
|
||||
ArticleSavingRequestStatus,
|
||||
CreateArticleSavingRequestErrorCode,
|
||||
CreateLabelInput,
|
||||
PageType,
|
||||
} from '../generated/graphql'
|
||||
import { createPubSubClient, PubsubClient } from '../pubsub'
|
||||
import { Merge } from '../util'
|
||||
import { enqueueParseRequest } from '../utils/createTask'
|
||||
import {
|
||||
cleanUrl,
|
||||
generateSlug,
|
||||
libraryItemToArticleSavingRequest,
|
||||
} from '../utils/helpers'
|
||||
import { cleanUrl, generateSlug } from '../utils/helpers'
|
||||
import { logger } from '../utils/logger'
|
||||
import { countBySavedAt, createOrUpdateLibraryItem } from './library_item'
|
||||
|
||||
@ -88,11 +84,12 @@ export const createPageSaveRequest = async ({
|
||||
publishedAt,
|
||||
folder,
|
||||
subscription,
|
||||
}: PageSaveRequest): Promise<ArticleSavingRequest> => {
|
||||
}: PageSaveRequest): Promise<LibraryItem> => {
|
||||
try {
|
||||
validateUrl(url)
|
||||
} catch (error) {
|
||||
logger.info('invalid url', { url, error })
|
||||
logger.error('invalid url', { url, error })
|
||||
|
||||
return Promise.reject({
|
||||
errorCode: CreateArticleSavingRequestErrorCode.BadData,
|
||||
})
|
||||
@ -140,5 +137,5 @@ export const createPageSaveRequest = async ({
|
||||
rssFeedUrl: subscription,
|
||||
})
|
||||
|
||||
return libraryItemToArticleSavingRequest(user, libraryItem)
|
||||
return libraryItem
|
||||
}
|
||||
|
||||
@ -6,9 +6,8 @@ import { Invite } from '../entity/groups/invite'
|
||||
import { RuleActionType } from '../entity/rule'
|
||||
import { User } from '../entity/user'
|
||||
import { homePageURL } from '../env'
|
||||
import { RecommendationGroup, User as GraphqlUser } from '../generated/graphql'
|
||||
import { getRepository } from '../repository'
|
||||
import { userDataToUser } from '../utils/helpers'
|
||||
import { PartialRecommendationGroup } from '../resolvers'
|
||||
import { findOrCreateLabels } from './labels'
|
||||
import { createRule } from './rules'
|
||||
|
||||
@ -70,22 +69,21 @@ export const createGroup = async (input: {
|
||||
|
||||
export const getRecommendationGroups = async (
|
||||
user: User
|
||||
): Promise<RecommendationGroup[]> => {
|
||||
): Promise<Array<PartialRecommendationGroup>> => {
|
||||
const groupMembers = await getRepository(GroupMembership).find({
|
||||
where: { user: { id: user.id } },
|
||||
relations: ['invite', 'group.members.user.profile'],
|
||||
})
|
||||
|
||||
return groupMembers.map((gm) => {
|
||||
const admins: GraphqlUser[] = []
|
||||
const members: GraphqlUser[] = []
|
||||
const admins: Array<User> = []
|
||||
const members: Array<User> = []
|
||||
// Return all members
|
||||
gm.group.members.forEach((m) => {
|
||||
const user = userDataToUser(m.user)
|
||||
if (m.isAdmin) {
|
||||
admins.push(user)
|
||||
admins.push(m.user)
|
||||
}
|
||||
members.push(user)
|
||||
members.push(m.user)
|
||||
})
|
||||
|
||||
const canSeeMembers = gm.group.onlyAdminCanSeeMembers ? gm.isAdmin : true
|
||||
@ -113,7 +111,7 @@ export const getInviteUrl = (invite: Invite) => {
|
||||
export const joinGroup = async (
|
||||
user: User,
|
||||
inviteCode: string
|
||||
): Promise<RecommendationGroup> => {
|
||||
): Promise<PartialRecommendationGroup> => {
|
||||
const invite = await appDataSource.transaction<Invite>(async (t) => {
|
||||
// Check if the invite exists
|
||||
const invite = await t
|
||||
@ -147,15 +145,14 @@ export const joinGroup = async (
|
||||
where: { id: invite.group.id },
|
||||
relations: ['members', 'members.user.profile'],
|
||||
})
|
||||
const admins: GraphqlUser[] = []
|
||||
const members: GraphqlUser[] = []
|
||||
const admins: Array<User> = []
|
||||
const members: Array<User> = []
|
||||
// Return all members
|
||||
group.members.forEach((m) => {
|
||||
const user = userDataToUser(m.user)
|
||||
if (m.isAdmin) {
|
||||
admins.push(user)
|
||||
admins.push(m.user)
|
||||
}
|
||||
members.push(user)
|
||||
members.push(m.user)
|
||||
})
|
||||
|
||||
return {
|
||||
|
||||
@ -7,30 +7,11 @@ import path from 'path'
|
||||
import _ from 'underscore'
|
||||
import slugify from 'voca/slugify'
|
||||
import wordsCounter from 'word-counting'
|
||||
import { Highlight as HighlightData } from '../entity/highlight'
|
||||
import { LibraryItem, LibraryItemState } from '../entity/library_item'
|
||||
import { Recommendation as RecommendationData } from '../entity/recommendation'
|
||||
import { RegistrationType, User } from '../entity/user'
|
||||
import {
|
||||
Article,
|
||||
ArticleSavingRequest,
|
||||
ArticleSavingRequestStatus,
|
||||
ContentReader,
|
||||
CreateArticleError,
|
||||
CreateArticleSuccess,
|
||||
DirectionalityType,
|
||||
FeedArticle,
|
||||
Highlight,
|
||||
PageType,
|
||||
Profile,
|
||||
Recommendation,
|
||||
SearchItem,
|
||||
} from '../generated/graphql'
|
||||
import { CreateArticleError } from '../generated/graphql'
|
||||
import { createPubSubClient } from '../pubsub'
|
||||
import { ArticleFormat } from '../resolvers'
|
||||
import { validateUrl } from '../services/create_page_save_request'
|
||||
import { updateLibraryItem } from '../services/library_item'
|
||||
import { Merge } from '../util'
|
||||
import { logger } from './logger'
|
||||
|
||||
interface InputObject {
|
||||
@ -101,55 +82,6 @@ export const findDelimiter = (
|
||||
return delimiter || defaultDelimiter
|
||||
}
|
||||
|
||||
// FIXME: Remove this Date stub after nullable types will be fixed
|
||||
export const userDataToUser = (
|
||||
user: Merge<
|
||||
User,
|
||||
{
|
||||
isFriend?: boolean
|
||||
followersCount?: number
|
||||
friendsCount?: number
|
||||
sharedArticlesCount?: number
|
||||
sharedHighlightsCount?: number
|
||||
sharedNotesCount?: number
|
||||
viewerIsFollowing?: boolean
|
||||
}
|
||||
>
|
||||
): {
|
||||
id: string
|
||||
name: string
|
||||
source: RegistrationType
|
||||
email?: string | null
|
||||
phone?: string | null
|
||||
picture?: string | null
|
||||
googleId?: string | null
|
||||
createdAt: Date
|
||||
isFriend?: boolean | null
|
||||
isFullUser: boolean
|
||||
viewerIsFollowing?: boolean | null
|
||||
sourceUserId: string
|
||||
friendsCount?: number
|
||||
followersCount?: number
|
||||
sharedArticles: FeedArticle[]
|
||||
sharedArticlesCount?: number
|
||||
sharedHighlightsCount?: number
|
||||
sharedNotesCount?: number
|
||||
profile: Profile
|
||||
} => ({
|
||||
...user,
|
||||
source: user.source as RegistrationType,
|
||||
createdAt: user.createdAt,
|
||||
friendsCount: user.friendsCount || 0,
|
||||
followersCount: user.followersCount || 0,
|
||||
isFullUser: true,
|
||||
viewerIsFollowing: user.viewerIsFollowing || user.isFriend || false,
|
||||
picture: user.profile.pictureUrl,
|
||||
sharedArticles: [],
|
||||
sharedArticlesCount: user.sharedArticlesCount || 0,
|
||||
sharedHighlightsCount: user.sharedHighlightsCount || 0,
|
||||
sharedNotesCount: user.sharedNotesCount || 0,
|
||||
})
|
||||
|
||||
export const generateSlug = (title: string): string => {
|
||||
return slugify(title).substring(0, 64) + '-' + Date.now().toString(16)
|
||||
}
|
||||
@ -161,7 +93,7 @@ export const errorHandler = async (
|
||||
userId: string,
|
||||
pageId?: string | null,
|
||||
pubsub = createPubSubClient()
|
||||
): Promise<CreateArticleError | CreateArticleSuccess> => {
|
||||
): Promise<CreateArticleError> => {
|
||||
if (!pageId) return result
|
||||
|
||||
await updateLibraryItem(
|
||||
@ -176,86 +108,6 @@ export const errorHandler = async (
|
||||
return result
|
||||
}
|
||||
|
||||
export const highlightDataToHighlight = (
|
||||
highlight: HighlightData
|
||||
): Highlight => ({
|
||||
...highlight,
|
||||
createdByMe: false,
|
||||
reactions: [],
|
||||
replies: [],
|
||||
type: highlight.highlightType,
|
||||
user: userDataToUser(highlight.user),
|
||||
})
|
||||
|
||||
export const recommandationDataToRecommendation = (
|
||||
recommendation: RecommendationData
|
||||
): Recommendation => ({
|
||||
...recommendation,
|
||||
user: {
|
||||
userId: recommendation.recommender.id,
|
||||
username: recommendation.recommender.profile.username,
|
||||
profileImageURL: recommendation.recommender.profile.pictureUrl,
|
||||
name: recommendation.recommender.name,
|
||||
},
|
||||
name: recommendation.group.name,
|
||||
recommendedAt: recommendation.createdAt,
|
||||
})
|
||||
|
||||
export const libraryItemToArticleSavingRequest = (
|
||||
user: User,
|
||||
item: LibraryItem
|
||||
): ArticleSavingRequest => ({
|
||||
...item,
|
||||
user: userDataToUser(user),
|
||||
status: item.state as unknown as ArticleSavingRequestStatus,
|
||||
url: item.originalUrl,
|
||||
userId: user.id,
|
||||
})
|
||||
|
||||
export const libraryItemToArticle = (item: LibraryItem): Article => ({
|
||||
...item,
|
||||
url: item.originalUrl,
|
||||
state: item.state as unknown as ArticleSavingRequestStatus,
|
||||
content: item.readableContent,
|
||||
hash: item.textContentHash || '',
|
||||
isArchived: !!item.archivedAt,
|
||||
recommendations: item.recommendations?.map(
|
||||
recommandationDataToRecommendation
|
||||
),
|
||||
image: item.thumbnail,
|
||||
contentReader: item.contentReader as unknown as ContentReader,
|
||||
readingProgressAnchorIndex: item.readingProgressHighestReadAnchor,
|
||||
readingProgressPercent: item.readingProgressBottomPercent,
|
||||
highlights: item.highlights?.map(highlightDataToHighlight) || [],
|
||||
uploadFileId: item.uploadFile?.id,
|
||||
pageType: item.itemType as unknown as PageType,
|
||||
wordsCount: item.wordCount,
|
||||
directionality: item.directionality as unknown as DirectionalityType,
|
||||
})
|
||||
|
||||
export const libraryItemToSearchItem = (
|
||||
item: LibraryItem,
|
||||
format?: ArticleFormat
|
||||
): SearchItem => ({
|
||||
...item,
|
||||
url: item.originalUrl,
|
||||
state: item.state as unknown as ArticleSavingRequestStatus,
|
||||
content: item.readableContent,
|
||||
isArchived: !!item.archivedAt,
|
||||
pageType: item.itemType as unknown as PageType,
|
||||
readingProgressPercent: item.readingProgressBottomPercent,
|
||||
contentReader: item.contentReader as unknown as ContentReader,
|
||||
readingProgressAnchorIndex: item.readingProgressHighestReadAnchor,
|
||||
recommendations: item.recommendations?.map(
|
||||
recommandationDataToRecommendation
|
||||
),
|
||||
image: item.thumbnail,
|
||||
highlights: item.highlights?.map(highlightDataToHighlight),
|
||||
wordsCount: item.wordCount,
|
||||
directionality: item.directionality as unknown as DirectionalityType,
|
||||
format,
|
||||
})
|
||||
|
||||
export const isParsingTimeout = (libraryItem: LibraryItem): boolean => {
|
||||
return (
|
||||
// item processed more than 30 seconds ago
|
||||
|
||||
Reference in New Issue
Block a user