diff --git a/packages/api/src/entity/library_item.ts b/packages/api/src/entity/library_item.ts
index 8fb0edc94..ed8bc47ff 100644
--- a/packages/api/src/entity/library_item.ts
+++ b/packages/api/src/entity/library_item.ts
@@ -46,6 +46,9 @@ export class LibraryItem {
@JoinColumn({ name: 'user_id' })
user!: User
+ @Column('uuid')
+ userId!: string
+
@Column('enum', {
enum: LibraryItemState,
default: LibraryItemState.Succeeded,
diff --git a/packages/api/src/resolvers/article/index.ts b/packages/api/src/resolvers/article/index.ts
index 026cb7988..99e1d29cd 100644
--- a/packages/api/src/resolvers/article/index.ts
+++ b/packages/api/src/resolvers/article/index.ts
@@ -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 = '
We were unable to parse this page.
'
export const createArticleResolver = authorized<
- CreateArticleSuccess,
+ Merge<
+ CreateArticleSuccess,
+ { user: User; createdArticle: Partial }
+ >,
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,
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,
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,
SaveArticleReadingProgressError,
MutationSaveArticleReadingProgressArgs
>(
@@ -661,13 +584,15 @@ export const saveArticleReadingProgressResolver = authorized<
}
return {
- updatedArticle: libraryItemToArticle(updatedItem),
+ updatedArticle: updatedItem,
}
}
)
+export type PartialLibraryItem = Merge
+type PartialSearchItemEdge = Merge
export const searchResolver = authorized<
- SearchSuccess,
+ Merge }>,
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 }>,
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,
diff --git a/packages/api/src/resolvers/article_saving_request/index.ts b/packages/api/src/resolvers/article_saving_request/index.ts
index 07d40aea5..3d99a8109 100644
--- a/packages/api/src/resolvers/article_saving_request/index.ts
+++ b/packages/api/src/resolvers/article_saving_request/index.ts
@@ -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,
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)
diff --git a/packages/api/src/resolvers/function_resolvers.ts b/packages/api/src/resolvers/function_resolvers.ts
index 1a7d0ba40..a4fc71cf8 100644
--- a/packages/api/src/resolvers/function_resolvers.ts
+++ b/packages/api/src/resolvers/function_resolvers.ts
@@ -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'),
diff --git a/packages/api/src/resolvers/recommendations/index.ts b/packages/api/src/resolvers/recommendations/index.ts
index ef8b1b430..d1bd121eb 100644
--- a/packages/api/src/resolvers/recommendations/index.ts
+++ b/packages/api/src/resolvers/recommendations/index.ts
@@ -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; members: Array }
+>
export const createGroupResolver = authorized<
- CreateGroupSuccess,
+ Merge,
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(
- 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 }>,
+ 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,
JoinGroupError,
MutationJoinGroupArgs
>(async (_, { inviteCode }, { uid, log }) => {
diff --git a/packages/api/src/resolvers/update/index.ts b/packages/api/src/resolvers/update/index.ts
index c38019d8b..d17830505 100644
--- a/packages/api/src/resolvers/update/index.ts
+++ b/packages/api/src/resolvers/update/index.ts
@@ -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,
UpdatePageError,
MutationUpdatePageArgs
>(async (_, { input }, { uid }) => {
@@ -29,6 +29,6 @@ export const updatePageResolver = authorized<
uid
)
return {
- updatedPage: libraryItemToArticle(updatedPage),
+ updatedPage: updatedPage,
}
})
diff --git a/packages/api/src/resolvers/user/index.ts b/packages/api/src/resolvers/user/index.ts
index 5f794aff0..6572c1f10 100644
--- a/packages/api/src/resolvers/user/index.ts
+++ b/packages/api/src/resolvers/user/index.ts
@@ -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,
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,
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,
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,
Record,
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,
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(
- async (_obj, _params) => {
- const users = await userRepository.findTopUsers()
- const result = { users: users.map((userData) => userDataToUser(userData)) }
- return result
- }
-)
+export const getAllUsersResolver = authorized<
+ Merge }>,
+ UsersError
+>(async (_obj, _params) => {
+ const users = await userRepository.findTopUsers()
+ const result = { users }
+ return result
+})
type ErrorWithCode = {
errorCode: string
diff --git a/packages/api/src/routers/article_router.ts b/packages/api/src/routers/article_router.ts
index fbe9ea4d5..1adda960a 100644
--- a/packages/api/src/routers/article_router.ts
+++ b/packages/api/src/routers/article_router.ts
@@ -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(
diff --git a/packages/api/src/services/create_page_save_request.ts b/packages/api/src/services/create_page_save_request.ts
index ce4ce137b..8e2470e2c 100644
--- a/packages/api/src/services/create_page_save_request.ts
+++ b/packages/api/src/services/create_page_save_request.ts
@@ -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 => {
+}: PageSaveRequest): Promise => {
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
}
diff --git a/packages/api/src/services/groups.ts b/packages/api/src/services/groups.ts
index cedfecd5a..ef1f61325 100644
--- a/packages/api/src/services/groups.ts
+++ b/packages/api/src/services/groups.ts
@@ -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 => {
+): Promise> => {
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 = []
+ const members: Array = []
// 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 => {
+): Promise => {
const invite = await appDataSource.transaction(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 = []
+ const members: Array = []
// 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 {
diff --git a/packages/api/src/utils/helpers.ts b/packages/api/src/utils/helpers.ts
index 30a821a5e..97f495859 100644
--- a/packages/api/src/utils/helpers.ts
+++ b/packages/api/src/utils/helpers.ts
@@ -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 => {
+): Promise => {
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