add following handler to save following item
This commit is contained in:
@ -23,8 +23,8 @@ export type AddFollowingToLibraryError = {
|
||||
};
|
||||
|
||||
export enum AddFollowingToLibraryErrorCode {
|
||||
AlreadyExists = 'ALREADY_EXISTS',
|
||||
BadRequest = 'BAD_REQUEST',
|
||||
NotFound = 'NOT_FOUND',
|
||||
Unauthorized = 'UNAUTHORIZED'
|
||||
}
|
||||
|
||||
@ -855,26 +855,6 @@ export type FiltersSuccess = {
|
||||
filters: Array<Filter>;
|
||||
};
|
||||
|
||||
export type Following = {
|
||||
__typename?: 'Following';
|
||||
SharedAt: Scalars['Date'];
|
||||
author?: Maybe<Scalars['String']>;
|
||||
createdAt: Scalars['Date'];
|
||||
description?: Maybe<Scalars['String']>;
|
||||
hiddenAt?: Maybe<Scalars['Date']>;
|
||||
id: Scalars['ID'];
|
||||
image?: Maybe<Scalars['String']>;
|
||||
links?: Maybe<Scalars['JSON']>;
|
||||
previewContent?: Maybe<Scalars['String']>;
|
||||
publishedAt?: Maybe<Scalars['Date']>;
|
||||
seenAt?: Maybe<Scalars['Date']>;
|
||||
sharedBy: Scalars['String'];
|
||||
sharedSource: Scalars['String'];
|
||||
title: Scalars['String'];
|
||||
updatedAt: Scalars['Date'];
|
||||
url: Scalars['String'];
|
||||
};
|
||||
|
||||
export type GenerateApiKeyError = {
|
||||
__typename?: 'GenerateApiKeyError';
|
||||
errorCodes: Array<GenerateApiKeyErrorCode>;
|
||||
@ -1359,7 +1339,6 @@ export type Mutation = {
|
||||
saveArticleReadingProgress: SaveArticleReadingProgressResult;
|
||||
saveFile: SaveResult;
|
||||
saveFilter: SaveFilterResult;
|
||||
saveFollowing: SaveFollowingResult;
|
||||
savePage: SaveResult;
|
||||
saveUrl: SaveResult;
|
||||
setBookmarkArticle: SetBookmarkArticleResult;
|
||||
@ -1561,11 +1540,6 @@ export type MutationSaveFilterArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationSaveFollowingArgs = {
|
||||
input: SaveFollowingInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationSavePageArgs = {
|
||||
input: SavePageInput;
|
||||
};
|
||||
@ -2261,36 +2235,6 @@ export type SaveFilterSuccess = {
|
||||
filter: Filter;
|
||||
};
|
||||
|
||||
export type SaveFollowingError = {
|
||||
__typename?: 'SaveFollowingError';
|
||||
errorCodes: Array<SaveFollowingErrorCode>;
|
||||
};
|
||||
|
||||
export enum SaveFollowingErrorCode {
|
||||
BadRequest = 'BAD_REQUEST',
|
||||
Unauthorized = 'UNAUTHORIZED'
|
||||
}
|
||||
|
||||
export type SaveFollowingInput = {
|
||||
author?: InputMaybe<Scalars['String']>;
|
||||
description?: InputMaybe<Scalars['String']>;
|
||||
links?: InputMaybe<Scalars['JSON']>;
|
||||
previewContent?: InputMaybe<Scalars['String']>;
|
||||
publishedAt?: InputMaybe<Scalars['Date']>;
|
||||
sharedAt: Scalars['Date'];
|
||||
sharedBy: Scalars['String'];
|
||||
sharedSource: Scalars['String'];
|
||||
title: Scalars['String'];
|
||||
url: Scalars['String'];
|
||||
};
|
||||
|
||||
export type SaveFollowingResult = SaveFollowingError | SaveFollowingSuccess;
|
||||
|
||||
export type SaveFollowingSuccess = {
|
||||
__typename?: 'SaveFollowingSuccess';
|
||||
following: Following;
|
||||
};
|
||||
|
||||
export type SavePageInput = {
|
||||
clientRequestId: Scalars['ID'];
|
||||
labels?: InputMaybe<Array<CreateLabelInput>>;
|
||||
@ -3616,7 +3560,6 @@ export type ResolversTypes = {
|
||||
FiltersResult: ResolversTypes['FiltersError'] | ResolversTypes['FiltersSuccess'];
|
||||
FiltersSuccess: ResolverTypeWrapper<FiltersSuccess>;
|
||||
Float: ResolverTypeWrapper<Scalars['Float']>;
|
||||
Following: ResolverTypeWrapper<Following>;
|
||||
GenerateApiKeyError: ResolverTypeWrapper<GenerateApiKeyError>;
|
||||
GenerateApiKeyErrorCode: GenerateApiKeyErrorCode;
|
||||
GenerateApiKeyInput: GenerateApiKeyInput;
|
||||
@ -3782,11 +3725,6 @@ export type ResolversTypes = {
|
||||
SaveFilterInput: SaveFilterInput;
|
||||
SaveFilterResult: ResolversTypes['SaveFilterError'] | ResolversTypes['SaveFilterSuccess'];
|
||||
SaveFilterSuccess: ResolverTypeWrapper<SaveFilterSuccess>;
|
||||
SaveFollowingError: ResolverTypeWrapper<SaveFollowingError>;
|
||||
SaveFollowingErrorCode: SaveFollowingErrorCode;
|
||||
SaveFollowingInput: SaveFollowingInput;
|
||||
SaveFollowingResult: ResolversTypes['SaveFollowingError'] | ResolversTypes['SaveFollowingSuccess'];
|
||||
SaveFollowingSuccess: ResolverTypeWrapper<SaveFollowingSuccess>;
|
||||
SavePageInput: SavePageInput;
|
||||
SaveResult: ResolversTypes['SaveError'] | ResolversTypes['SaveSuccess'];
|
||||
SaveSuccess: ResolverTypeWrapper<SaveSuccess>;
|
||||
@ -4109,7 +4047,6 @@ export type ResolversParentTypes = {
|
||||
FiltersResult: ResolversParentTypes['FiltersError'] | ResolversParentTypes['FiltersSuccess'];
|
||||
FiltersSuccess: FiltersSuccess;
|
||||
Float: Scalars['Float'];
|
||||
Following: Following;
|
||||
GenerateApiKeyError: GenerateApiKeyError;
|
||||
GenerateApiKeyInput: GenerateApiKeyInput;
|
||||
GenerateApiKeyResult: ResolversParentTypes['GenerateApiKeyError'] | ResolversParentTypes['GenerateApiKeySuccess'];
|
||||
@ -4239,10 +4176,6 @@ export type ResolversParentTypes = {
|
||||
SaveFilterInput: SaveFilterInput;
|
||||
SaveFilterResult: ResolversParentTypes['SaveFilterError'] | ResolversParentTypes['SaveFilterSuccess'];
|
||||
SaveFilterSuccess: SaveFilterSuccess;
|
||||
SaveFollowingError: SaveFollowingError;
|
||||
SaveFollowingInput: SaveFollowingInput;
|
||||
SaveFollowingResult: ResolversParentTypes['SaveFollowingError'] | ResolversParentTypes['SaveFollowingSuccess'];
|
||||
SaveFollowingSuccess: SaveFollowingSuccess;
|
||||
SavePageInput: SavePageInput;
|
||||
SaveResult: ResolversParentTypes['SaveError'] | ResolversParentTypes['SaveSuccess'];
|
||||
SaveSuccess: SaveSuccess;
|
||||
@ -5010,26 +4943,6 @@ export type FiltersSuccessResolvers<ContextType = ResolverContext, ParentType ex
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type FollowingResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['Following'] = ResolversParentTypes['Following']> = {
|
||||
SharedAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
|
||||
author?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
createdAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
|
||||
description?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
hiddenAt?: Resolver<Maybe<ResolversTypes['Date']>, ParentType, ContextType>;
|
||||
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
|
||||
image?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
links?: Resolver<Maybe<ResolversTypes['JSON']>, ParentType, ContextType>;
|
||||
previewContent?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
publishedAt?: Resolver<Maybe<ResolversTypes['Date']>, ParentType, ContextType>;
|
||||
seenAt?: Resolver<Maybe<ResolversTypes['Date']>, ParentType, ContextType>;
|
||||
sharedBy?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
sharedSource?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
title?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
updatedAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
|
||||
url?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type GenerateApiKeyErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['GenerateApiKeyError'] = ResolversParentTypes['GenerateApiKeyError']> = {
|
||||
errorCodes?: Resolver<Array<ResolversTypes['GenerateApiKeyErrorCode']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
@ -5396,7 +5309,6 @@ export type MutationResolvers<ContextType = ResolverContext, ParentType extends
|
||||
saveArticleReadingProgress?: Resolver<ResolversTypes['SaveArticleReadingProgressResult'], ParentType, ContextType, RequireFields<MutationSaveArticleReadingProgressArgs, 'input'>>;
|
||||
saveFile?: Resolver<ResolversTypes['SaveResult'], ParentType, ContextType, RequireFields<MutationSaveFileArgs, 'input'>>;
|
||||
saveFilter?: Resolver<ResolversTypes['SaveFilterResult'], ParentType, ContextType, RequireFields<MutationSaveFilterArgs, 'input'>>;
|
||||
saveFollowing?: Resolver<ResolversTypes['SaveFollowingResult'], ParentType, ContextType, RequireFields<MutationSaveFollowingArgs, 'input'>>;
|
||||
savePage?: Resolver<ResolversTypes['SaveResult'], ParentType, ContextType, RequireFields<MutationSavePageArgs, 'input'>>;
|
||||
saveUrl?: Resolver<ResolversTypes['SaveResult'], ParentType, ContextType, RequireFields<MutationSaveUrlArgs, 'input'>>;
|
||||
setBookmarkArticle?: Resolver<ResolversTypes['SetBookmarkArticleResult'], ParentType, ContextType, RequireFields<MutationSetBookmarkArticleArgs, 'input'>>;
|
||||
@ -5756,20 +5668,6 @@ export type SaveFilterSuccessResolvers<ContextType = ResolverContext, ParentType
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type SaveFollowingErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['SaveFollowingError'] = ResolversParentTypes['SaveFollowingError']> = {
|
||||
errorCodes?: Resolver<Array<ResolversTypes['SaveFollowingErrorCode']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type SaveFollowingResultResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['SaveFollowingResult'] = ResolversParentTypes['SaveFollowingResult']> = {
|
||||
__resolveType: TypeResolveFn<'SaveFollowingError' | 'SaveFollowingSuccess', ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type SaveFollowingSuccessResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['SaveFollowingSuccess'] = ResolversParentTypes['SaveFollowingSuccess']> = {
|
||||
following?: Resolver<ResolversTypes['Following'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type SaveResultResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['SaveResult'] = ResolversParentTypes['SaveResult']> = {
|
||||
__resolveType: TypeResolveFn<'SaveError' | 'SaveSuccess', ParentType, ContextType>;
|
||||
};
|
||||
@ -6554,7 +6452,6 @@ export type Resolvers<ContextType = ResolverContext> = {
|
||||
FiltersError?: FiltersErrorResolvers<ContextType>;
|
||||
FiltersResult?: FiltersResultResolvers<ContextType>;
|
||||
FiltersSuccess?: FiltersSuccessResolvers<ContextType>;
|
||||
Following?: FollowingResolvers<ContextType>;
|
||||
GenerateApiKeyError?: GenerateApiKeyErrorResolvers<ContextType>;
|
||||
GenerateApiKeyResult?: GenerateApiKeyResultResolvers<ContextType>;
|
||||
GenerateApiKeySuccess?: GenerateApiKeySuccessResolvers<ContextType>;
|
||||
@ -6665,9 +6562,6 @@ export type Resolvers<ContextType = ResolverContext> = {
|
||||
SaveFilterError?: SaveFilterErrorResolvers<ContextType>;
|
||||
SaveFilterResult?: SaveFilterResultResolvers<ContextType>;
|
||||
SaveFilterSuccess?: SaveFilterSuccessResolvers<ContextType>;
|
||||
SaveFollowingError?: SaveFollowingErrorResolvers<ContextType>;
|
||||
SaveFollowingResult?: SaveFollowingResultResolvers<ContextType>;
|
||||
SaveFollowingSuccess?: SaveFollowingSuccessResolvers<ContextType>;
|
||||
SaveResult?: SaveResultResolvers<ContextType>;
|
||||
SaveSuccess?: SaveSuccessResolvers<ContextType>;
|
||||
SearchError?: SearchErrorResolvers<ContextType>;
|
||||
|
||||
@ -5,8 +5,8 @@ type AddFollowingToLibraryError {
|
||||
}
|
||||
|
||||
enum AddFollowingToLibraryErrorCode {
|
||||
ALREADY_EXISTS
|
||||
BAD_REQUEST
|
||||
NOT_FOUND
|
||||
UNAUTHORIZED
|
||||
}
|
||||
|
||||
@ -758,25 +758,6 @@ type FiltersSuccess {
|
||||
filters: [Filter!]!
|
||||
}
|
||||
|
||||
type Following {
|
||||
SharedAt: Date!
|
||||
author: String
|
||||
createdAt: Date!
|
||||
description: String
|
||||
hiddenAt: Date
|
||||
id: ID!
|
||||
image: String
|
||||
links: JSON
|
||||
previewContent: String
|
||||
publishedAt: Date
|
||||
seenAt: Date
|
||||
sharedBy: String!
|
||||
sharedSource: String!
|
||||
title: String!
|
||||
updatedAt: Date!
|
||||
url: String!
|
||||
}
|
||||
|
||||
type GenerateApiKeyError {
|
||||
errorCodes: [GenerateApiKeyErrorCode!]!
|
||||
}
|
||||
@ -1221,7 +1202,6 @@ type Mutation {
|
||||
saveArticleReadingProgress(input: SaveArticleReadingProgressInput!): SaveArticleReadingProgressResult!
|
||||
saveFile(input: SaveFileInput!): SaveResult!
|
||||
saveFilter(input: SaveFilterInput!): SaveFilterResult!
|
||||
saveFollowing(input: SaveFollowingInput!): SaveFollowingResult!
|
||||
savePage(input: SavePageInput!): SaveResult!
|
||||
saveUrl(input: SaveUrlInput!): SaveResult!
|
||||
setBookmarkArticle(input: SetBookmarkArticleInput!): SetBookmarkArticleResult!
|
||||
@ -1715,34 +1695,6 @@ type SaveFilterSuccess {
|
||||
filter: Filter!
|
||||
}
|
||||
|
||||
type SaveFollowingError {
|
||||
errorCodes: [SaveFollowingErrorCode!]!
|
||||
}
|
||||
|
||||
enum SaveFollowingErrorCode {
|
||||
BAD_REQUEST
|
||||
UNAUTHORIZED
|
||||
}
|
||||
|
||||
input SaveFollowingInput {
|
||||
author: String
|
||||
description: String
|
||||
links: JSON
|
||||
previewContent: String
|
||||
publishedAt: Date
|
||||
sharedAt: Date!
|
||||
sharedBy: String!
|
||||
sharedSource: String!
|
||||
title: String!
|
||||
url: String!
|
||||
}
|
||||
|
||||
union SaveFollowingResult = SaveFollowingError | SaveFollowingSuccess
|
||||
|
||||
type SaveFollowingSuccess {
|
||||
following: Following!
|
||||
}
|
||||
|
||||
input SavePageInput {
|
||||
clientRequestId: ID!
|
||||
labels: [CreateLabelInput!]
|
||||
|
||||
@ -9,16 +9,16 @@ import {
|
||||
FeedsErrorCode,
|
||||
FeedsSuccess,
|
||||
MutationAddFollowingToLibraryArgs,
|
||||
MutationSaveFollowingArgs,
|
||||
QueryFeedsArgs,
|
||||
SaveFollowingError,
|
||||
SaveFollowingSuccess,
|
||||
} from '../../generated/graphql'
|
||||
import { feedRepository } from '../../repository/feed'
|
||||
import { createPageSaveRequest } from '../../services/create_page_save_request'
|
||||
import { createFollowing } from '../../services/library_item'
|
||||
import { updateLibraryItem } from '../../services/library_item'
|
||||
import { analytics } from '../../utils/analytics'
|
||||
import { authorized } from '../../utils/helpers'
|
||||
import {
|
||||
authorized,
|
||||
libraryItemToArticleSavingRequest,
|
||||
} from '../../utils/helpers'
|
||||
|
||||
export const feedsResolve = authorized<
|
||||
FeedsSuccess,
|
||||
@ -72,33 +72,6 @@ export const feedsResolve = authorized<
|
||||
}
|
||||
})
|
||||
|
||||
export const saveFollowingResolver = authorized<
|
||||
SaveFollowingSuccess,
|
||||
SaveFollowingError,
|
||||
MutationSaveFollowingArgs
|
||||
>(async (_, { input }, { uid }) => {
|
||||
analytics.track({
|
||||
userId: uid,
|
||||
event: 'save_following',
|
||||
properties: {
|
||||
url: input.url,
|
||||
},
|
||||
})
|
||||
|
||||
const newItem = await createFollowing(input, uid)
|
||||
|
||||
return {
|
||||
__typename: 'SaveFollowingSuccess',
|
||||
following: {
|
||||
...newItem,
|
||||
url: newItem.originalUrl,
|
||||
SharedAt: new Date(input.sharedAt),
|
||||
sharedBy: input.sharedBy,
|
||||
sharedSource: input.sharedSource,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
export const addFollowingToLibraryResolver = authorized<
|
||||
AddFollowingToLibrarySuccess,
|
||||
AddFollowingToLibraryError,
|
||||
@ -117,7 +90,6 @@ export const addFollowingToLibraryResolver = authorized<
|
||||
where: {
|
||||
id,
|
||||
sharedAt: Not(IsNull()),
|
||||
isInLibrary: false,
|
||||
},
|
||||
relations: ['user'],
|
||||
})
|
||||
@ -125,22 +97,48 @@ export const addFollowingToLibraryResolver = authorized<
|
||||
|
||||
if (!item) {
|
||||
return {
|
||||
errorCodes: [AddFollowingToLibraryErrorCode.NotFound],
|
||||
errorCodes: [AddFollowingToLibraryErrorCode.Unauthorized],
|
||||
}
|
||||
}
|
||||
|
||||
const articleSavingRequest = await createPageSaveRequest({
|
||||
userId: uid,
|
||||
url: item.originalUrl,
|
||||
articleSavingRequestId: id,
|
||||
priority: 'high',
|
||||
publishedAt: item.publishedAt || undefined,
|
||||
savedAt: item.savedAt || undefined,
|
||||
pubsub,
|
||||
})
|
||||
if (item.isInLibrary) {
|
||||
return {
|
||||
errorCodes: [AddFollowingToLibraryErrorCode.AlreadyExists],
|
||||
}
|
||||
}
|
||||
|
||||
// if the content is not fetched yet, create a page save request
|
||||
if (!item.readableContent) {
|
||||
const articleSavingRequest = await createPageSaveRequest({
|
||||
userId: uid,
|
||||
url: item.originalUrl,
|
||||
articleSavingRequestId: id,
|
||||
priority: 'high',
|
||||
publishedAt: item.publishedAt || undefined,
|
||||
pubsub,
|
||||
})
|
||||
|
||||
return {
|
||||
__typename: 'AddFollowingToLibrarySuccess',
|
||||
articleSavingRequest,
|
||||
}
|
||||
}
|
||||
|
||||
const updatedItem = await updateLibraryItem(
|
||||
item.id,
|
||||
{
|
||||
isInLibrary: true,
|
||||
savedAt: new Date(),
|
||||
},
|
||||
uid,
|
||||
pubsub
|
||||
)
|
||||
|
||||
return {
|
||||
__typename: 'AddFollowingToLibrarySuccess',
|
||||
articleSavingRequest,
|
||||
articleSavingRequest: libraryItemToArticleSavingRequest(
|
||||
item.user,
|
||||
updatedItem
|
||||
),
|
||||
}
|
||||
})
|
||||
|
||||
71
packages/api/src/routers/svc/following.ts
Normal file
71
packages/api/src/routers/svc/following.ts
Normal file
@ -0,0 +1,71 @@
|
||||
/* eslint-disable @typescript-eslint/no-misused-promises */
|
||||
import express from 'express'
|
||||
import { saveFeedItemInFollowing } from '../../services/library_item'
|
||||
import { logger } from '../../utils/logger'
|
||||
|
||||
type SharedSource = 'feed' | 'newsletter' | 'user'
|
||||
|
||||
export interface SaveFollowingItemRequest {
|
||||
userIds: string[]
|
||||
title: string
|
||||
url: string
|
||||
itemId: string
|
||||
sharedAt: Date
|
||||
sharedBy: string
|
||||
sharedSource: SharedSource
|
||||
author?: string
|
||||
description?: string
|
||||
links?: any
|
||||
previewContent?: string
|
||||
publishedAt?: Date
|
||||
savedAt?: Date
|
||||
}
|
||||
|
||||
function isSaveFollowingItemRequest(
|
||||
body: any
|
||||
): body is SaveFollowingItemRequest {
|
||||
return (
|
||||
'userIds' in body &&
|
||||
'sharedAt' in body &&
|
||||
'sharedBy' in body &&
|
||||
'sharedSource' in body &&
|
||||
'url' in body &&
|
||||
'itemId' in body &&
|
||||
'title' in body
|
||||
)
|
||||
}
|
||||
|
||||
export function followingServiceRouter() {
|
||||
const router = express.Router()
|
||||
|
||||
router.post('/save', async (req, res) => {
|
||||
logger.info('save following item request', req.body)
|
||||
|
||||
if (req.query.token !== process.env.PUBSUB_VERIFICATION_TOKEN) {
|
||||
console.log('query does not include valid token')
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
if (!isSaveFollowingItemRequest(req.body)) {
|
||||
console.error('Invalid request body', req.body)
|
||||
return res.status(400).send('INVALID_REQUEST_BODY')
|
||||
}
|
||||
|
||||
if (req.body.sharedSource === 'feed') {
|
||||
logger.info('saving feed item')
|
||||
|
||||
const result = await saveFeedItemInFollowing(req.body)
|
||||
if (result.identifiers.length === 0) {
|
||||
logger.error('error saving feed item in following')
|
||||
return res.status(500).send('ERROR_SAVING_FEED_ITEM')
|
||||
}
|
||||
|
||||
logger.info('feed item saved in following')
|
||||
return res.sendStatus(200)
|
||||
}
|
||||
|
||||
res.sendStatus(200)
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
||||
@ -2649,53 +2649,6 @@ const schema = gql`
|
||||
author: String
|
||||
}
|
||||
|
||||
union SaveFollowingResult = SaveFollowingSuccess | SaveFollowingError
|
||||
|
||||
type SaveFollowingSuccess {
|
||||
following: Following!
|
||||
}
|
||||
|
||||
type Following {
|
||||
id: ID!
|
||||
title: String!
|
||||
url: String!
|
||||
author: String
|
||||
image: String
|
||||
description: String
|
||||
seenAt: Date
|
||||
createdAt: Date!
|
||||
updatedAt: Date!
|
||||
publishedAt: Date
|
||||
hiddenAt: Date
|
||||
SharedAt: Date!
|
||||
sharedBy: String!
|
||||
links: JSON
|
||||
previewContent: String
|
||||
sharedSource: String!
|
||||
}
|
||||
|
||||
type SaveFollowingError {
|
||||
errorCodes: [SaveFollowingErrorCode!]!
|
||||
}
|
||||
|
||||
enum SaveFollowingErrorCode {
|
||||
UNAUTHORIZED
|
||||
BAD_REQUEST
|
||||
}
|
||||
|
||||
input SaveFollowingInput {
|
||||
url: String!
|
||||
title: String!
|
||||
author: String
|
||||
description: String
|
||||
publishedAt: Date
|
||||
sharedSource: String!
|
||||
links: JSON
|
||||
previewContent: String
|
||||
sharedBy: String!
|
||||
sharedAt: Date!
|
||||
}
|
||||
|
||||
union AddFollowingToLibraryResult =
|
||||
AddFollowingToLibrarySuccess
|
||||
| AddFollowingToLibraryError
|
||||
@ -2711,7 +2664,7 @@ const schema = gql`
|
||||
enum AddFollowingToLibraryErrorCode {
|
||||
UNAUTHORIZED
|
||||
BAD_REQUEST
|
||||
NOT_FOUND
|
||||
ALREADY_EXISTS
|
||||
}
|
||||
|
||||
# Mutations
|
||||
@ -2817,7 +2770,6 @@ const schema = gql`
|
||||
updateSubscription(
|
||||
input: UpdateSubscriptionInput!
|
||||
): UpdateSubscriptionResult!
|
||||
saveFollowing(input: SaveFollowingInput!): SaveFollowingResult!
|
||||
addFollowingToLibrary(id: ID!): AddFollowingToLibraryResult!
|
||||
}
|
||||
|
||||
|
||||
@ -25,6 +25,7 @@ import { pageRouter } from './routers/page_router'
|
||||
import { contentServiceRouter } from './routers/svc/content'
|
||||
import { emailsServiceRouter } from './routers/svc/emails'
|
||||
import { emailAttachmentRouter } from './routers/svc/email_attachment'
|
||||
import { followingServiceRouter } from './routers/svc/following'
|
||||
import { integrationsServiceRouter } from './routers/svc/integrations'
|
||||
import { linkServiceRouter } from './routers/svc/links'
|
||||
import { newsletterServiceRouter } from './routers/svc/newsletters'
|
||||
@ -125,6 +126,7 @@ export const createApp = (): {
|
||||
app.use('/svc/pubsub/user', userServiceRouter())
|
||||
// app.use('/svc/reminders', remindersServiceRouter())
|
||||
app.use('/svc/email-attachment', emailAttachmentRouter())
|
||||
app.use('/svc/following', followingServiceRouter())
|
||||
|
||||
if (env.dev.isLocal) {
|
||||
app.use('/local/debug', localDebugRouter())
|
||||
|
||||
@ -130,8 +130,11 @@ export const createPageSaveRequest = async ({
|
||||
pubsub
|
||||
)
|
||||
}
|
||||
// reset state to processing
|
||||
if (libraryItem.state !== LibraryItemState.Processing) {
|
||||
// reset state to processing if in following
|
||||
if (
|
||||
libraryItem.state !== LibraryItemState.Processing &&
|
||||
!libraryItem.sharedAt
|
||||
) {
|
||||
libraryItem = await updateLibraryItem(
|
||||
libraryItem.id,
|
||||
{
|
||||
|
||||
@ -4,10 +4,12 @@ import { EntityLabel } from '../entity/entity_label'
|
||||
import { Highlight } from '../entity/highlight'
|
||||
import { Label } from '../entity/label'
|
||||
import { LibraryItem, LibraryItemState } from '../entity/library_item'
|
||||
import { BulkActionType, SaveFollowingInput } from '../generated/graphql'
|
||||
import { BulkActionType } from '../generated/graphql'
|
||||
import { createPubSubClient, EntityType } from '../pubsub'
|
||||
import { authTrx, getColumns } from '../repository'
|
||||
import { libraryItemRepository } from '../repository/library_item'
|
||||
import { SaveFollowingItemRequest } from '../routers/svc/following'
|
||||
import { SetClaimsRole } from '../utils/dictionary'
|
||||
import { wordsCount } from '../utils/helpers'
|
||||
import {
|
||||
DateFilter,
|
||||
@ -567,25 +569,30 @@ export const createLibraryItem = async (
|
||||
return newLibraryItem
|
||||
}
|
||||
|
||||
export const createFollowing = async (
|
||||
input: SaveFollowingInput,
|
||||
userId: string
|
||||
): Promise<LibraryItem> => {
|
||||
return createLibraryItem(
|
||||
{
|
||||
...input,
|
||||
originalUrl: input.url,
|
||||
isInLibrary: false,
|
||||
state: LibraryItemState.Succeeded,
|
||||
wordCount: 0,
|
||||
user: { id: userId },
|
||||
sharedAt: new Date(input.sharedAt),
|
||||
sharedSource: input.sharedSource,
|
||||
sharedBy: input.sharedBy,
|
||||
export const saveFeedItemInFollowing = (input: SaveFollowingItemRequest) => {
|
||||
return authTrx(
|
||||
async (tx) => {
|
||||
const libraryItems: QueryDeepPartialEntity<LibraryItem>[] =
|
||||
input.userIds.map((userId) => ({
|
||||
...input,
|
||||
user: { id: userId },
|
||||
isInLibrary: false,
|
||||
originalUrl: input.url,
|
||||
subscription: input.sharedBy,
|
||||
}))
|
||||
|
||||
return tx
|
||||
.getRepository(LibraryItem)
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.values(libraryItems)
|
||||
.orIgnore() // ignore if the item already exists
|
||||
.returning('*')
|
||||
.execute()
|
||||
},
|
||||
userId,
|
||||
undefined,
|
||||
true
|
||||
undefined,
|
||||
SetClaimsRole.ADMIN
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -144,6 +144,7 @@ export const savePage = async (
|
||||
...itemToSave,
|
||||
id: undefined,
|
||||
slug: undefined,
|
||||
isInLibrary: true,
|
||||
} as QueryDeepPartialEntity<LibraryItem>,
|
||||
user.id
|
||||
)
|
||||
|
||||
@ -18,6 +18,11 @@ ALTER TABLE omnivore.library_item
|
||||
ADD COLUMN shared_source text,
|
||||
ADD COLUMN is_in_library boolean NOT NULL DEFAULT true;
|
||||
|
||||
CREATE POLICY library_item_admin_policy on omnivore.library_item
|
||||
FOR ALL
|
||||
TO omnivore_admin
|
||||
USING (true);
|
||||
|
||||
CREATE TABLE omnivore.feed (
|
||||
id uuid PRIMARY KEY DEFAULT uuid_generate_v1mc(),
|
||||
title text NOT NULL,
|
||||
|
||||
@ -6,6 +6,8 @@ BEGIN;
|
||||
|
||||
DROP TABLE omnivore.feed;
|
||||
|
||||
DROP policy library_item_admin_policy ON omnivore.library_item;
|
||||
|
||||
ALTER TABLE omnivore.library_item
|
||||
DROP COLUMN hidden_at,
|
||||
DROP COLUMN shared_at,
|
||||
|
||||
@ -161,12 +161,12 @@ const createFollowingTask = async (
|
||||
item: Item
|
||||
) => {
|
||||
const input = {
|
||||
userId,
|
||||
userIds: [userId],
|
||||
url: item.link,
|
||||
title: item.title,
|
||||
author: item.creator,
|
||||
description: item.summary,
|
||||
sharedSource: 'rss-feeder',
|
||||
sharedSource: 'feed',
|
||||
previewContent: item.content,
|
||||
sharedBy: feedUrl,
|
||||
savedAt: item.isoDate,
|
||||
|
||||
Reference in New Issue
Block a user