mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Add archiveAll API
This commit is contained in:
parent
2e3f186352
commit
03ab96b7f8
7 changed files with 225 additions and 0 deletions
|
|
@ -644,3 +644,65 @@ export const searchAsYouType = async (
|
|||
return []
|
||||
}
|
||||
}
|
||||
|
||||
export const archiveAll = async (
|
||||
userId: string,
|
||||
ctx: PageContext
|
||||
): Promise<boolean> => {
|
||||
const archivedAt = new Date()
|
||||
try {
|
||||
const { body } = await client.updateByQuery({
|
||||
index: INDEX_ALIAS,
|
||||
conflicts: 'proceed',
|
||||
body: {
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
term: {
|
||||
userId,
|
||||
},
|
||||
},
|
||||
{
|
||||
terms: {
|
||||
state: [
|
||||
ArticleSavingRequestStatus.Succeeded,
|
||||
ArticleSavingRequestStatus.Failed,
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
must_not: [
|
||||
{
|
||||
exists: {
|
||||
field: 'archivedAt',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
script: {
|
||||
source: 'ctx._source.archivedAt = params.archivedAt',
|
||||
lang: 'painless',
|
||||
params: {
|
||||
archivedAt,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
if (body.failures?.length > 0) {
|
||||
console.log('failed to archive pages in elastic', body.failures)
|
||||
return false
|
||||
}
|
||||
|
||||
await ctx.pubsub.entityUpdated(EntityType.PAGE, { archivedAt }, ctx.uid)
|
||||
|
||||
console.log('archived pages in elastic', body.updated)
|
||||
return true
|
||||
} catch (e) {
|
||||
console.log('failed to archive pages in elastic', e)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,6 +62,23 @@ export type ApiKeysSuccess = {
|
|||
apiKeys: Array<ApiKey>;
|
||||
};
|
||||
|
||||
export type ArchiveAllError = {
|
||||
__typename?: 'ArchiveAllError';
|
||||
errorCodes: Array<ArchiveAllErrorCode>;
|
||||
};
|
||||
|
||||
export enum ArchiveAllErrorCode {
|
||||
BadRequest = 'BAD_REQUEST',
|
||||
Unauthorized = 'UNAUTHORIZED'
|
||||
}
|
||||
|
||||
export type ArchiveAllResult = ArchiveAllError | ArchiveAllSuccess;
|
||||
|
||||
export type ArchiveAllSuccess = {
|
||||
__typename?: 'ArchiveAllSuccess';
|
||||
success: Scalars['Boolean'];
|
||||
};
|
||||
|
||||
export type ArchiveLinkError = {
|
||||
__typename?: 'ArchiveLinkError';
|
||||
errorCodes: Array<ArchiveLinkErrorCode>;
|
||||
|
|
@ -1164,6 +1181,7 @@ export type MoveLabelSuccess = {
|
|||
export type Mutation = {
|
||||
__typename?: 'Mutation';
|
||||
addPopularRead: AddPopularReadResult;
|
||||
archiveAll: ArchiveAllResult;
|
||||
createArticle: CreateArticleResult;
|
||||
createArticleSavingRequest: CreateArticleSavingRequestResult;
|
||||
createGroup: CreateGroupResult;
|
||||
|
|
@ -3179,6 +3197,10 @@ export type ResolversTypes = {
|
|||
ApiKeysErrorCode: ApiKeysErrorCode;
|
||||
ApiKeysResult: ResolversTypes['ApiKeysError'] | ResolversTypes['ApiKeysSuccess'];
|
||||
ApiKeysSuccess: ResolverTypeWrapper<ApiKeysSuccess>;
|
||||
ArchiveAllError: ResolverTypeWrapper<ArchiveAllError>;
|
||||
ArchiveAllErrorCode: ArchiveAllErrorCode;
|
||||
ArchiveAllResult: ResolversTypes['ArchiveAllError'] | ResolversTypes['ArchiveAllSuccess'];
|
||||
ArchiveAllSuccess: ResolverTypeWrapper<ArchiveAllSuccess>;
|
||||
ArchiveLinkError: ResolverTypeWrapper<ArchiveLinkError>;
|
||||
ArchiveLinkErrorCode: ArchiveLinkErrorCode;
|
||||
ArchiveLinkInput: ArchiveLinkInput;
|
||||
|
|
@ -3654,6 +3676,9 @@ export type ResolversParentTypes = {
|
|||
ApiKeysError: ApiKeysError;
|
||||
ApiKeysResult: ResolversParentTypes['ApiKeysError'] | ResolversParentTypes['ApiKeysSuccess'];
|
||||
ApiKeysSuccess: ApiKeysSuccess;
|
||||
ArchiveAllError: ArchiveAllError;
|
||||
ArchiveAllResult: ResolversParentTypes['ArchiveAllError'] | ResolversParentTypes['ArchiveAllSuccess'];
|
||||
ArchiveAllSuccess: ArchiveAllSuccess;
|
||||
ArchiveLinkError: ArchiveLinkError;
|
||||
ArchiveLinkInput: ArchiveLinkInput;
|
||||
ArchiveLinkResult: ResolversParentTypes['ArchiveLinkError'] | ResolversParentTypes['ArchiveLinkSuccess'];
|
||||
|
|
@ -4065,6 +4090,20 @@ export type ApiKeysSuccessResolvers<ContextType = ResolverContext, ParentType ex
|
|||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type ArchiveAllErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['ArchiveAllError'] = ResolversParentTypes['ArchiveAllError']> = {
|
||||
errorCodes?: Resolver<Array<ResolversTypes['ArchiveAllErrorCode']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type ArchiveAllResultResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['ArchiveAllResult'] = ResolversParentTypes['ArchiveAllResult']> = {
|
||||
__resolveType: TypeResolveFn<'ArchiveAllError' | 'ArchiveAllSuccess', ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type ArchiveAllSuccessResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['ArchiveAllSuccess'] = ResolversParentTypes['ArchiveAllSuccess']> = {
|
||||
success?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type ArchiveLinkErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['ArchiveLinkError'] = ResolversParentTypes['ArchiveLinkError']> = {
|
||||
errorCodes?: Resolver<Array<ResolversTypes['ArchiveLinkErrorCode']>, ParentType, ContextType>;
|
||||
message?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
|
|
@ -4871,6 +4910,7 @@ export type MoveLabelSuccessResolvers<ContextType = ResolverContext, ParentType
|
|||
|
||||
export type MutationResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['Mutation'] = ResolversParentTypes['Mutation']> = {
|
||||
addPopularRead?: Resolver<ResolversTypes['AddPopularReadResult'], ParentType, ContextType, RequireFields<MutationAddPopularReadArgs, 'name'>>;
|
||||
archiveAll?: Resolver<ResolversTypes['ArchiveAllResult'], ParentType, ContextType>;
|
||||
createArticle?: Resolver<ResolversTypes['CreateArticleResult'], ParentType, ContextType, RequireFields<MutationCreateArticleArgs, 'input'>>;
|
||||
createArticleSavingRequest?: Resolver<ResolversTypes['CreateArticleSavingRequestResult'], ParentType, ContextType, RequireFields<MutationCreateArticleSavingRequestArgs, 'input'>>;
|
||||
createGroup?: Resolver<ResolversTypes['CreateGroupResult'], ParentType, ContextType, RequireFields<MutationCreateGroupArgs, 'input'>>;
|
||||
|
|
@ -5890,6 +5930,9 @@ export type Resolvers<ContextType = ResolverContext> = {
|
|||
ApiKeysError?: ApiKeysErrorResolvers<ContextType>;
|
||||
ApiKeysResult?: ApiKeysResultResolvers<ContextType>;
|
||||
ApiKeysSuccess?: ApiKeysSuccessResolvers<ContextType>;
|
||||
ArchiveAllError?: ArchiveAllErrorResolvers<ContextType>;
|
||||
ArchiveAllResult?: ArchiveAllResultResolvers<ContextType>;
|
||||
ArchiveAllSuccess?: ArchiveAllSuccessResolvers<ContextType>;
|
||||
ArchiveLinkError?: ArchiveLinkErrorResolvers<ContextType>;
|
||||
ArchiveLinkResult?: ArchiveLinkResultResolvers<ContextType>;
|
||||
ArchiveLinkSuccess?: ArchiveLinkSuccessResolvers<ContextType>;
|
||||
|
|
|
|||
|
|
@ -41,6 +41,21 @@ type ApiKeysSuccess {
|
|||
apiKeys: [ApiKey!]!
|
||||
}
|
||||
|
||||
type ArchiveAllError {
|
||||
errorCodes: [ArchiveAllErrorCode!]!
|
||||
}
|
||||
|
||||
enum ArchiveAllErrorCode {
|
||||
BAD_REQUEST
|
||||
UNAUTHORIZED
|
||||
}
|
||||
|
||||
union ArchiveAllResult = ArchiveAllError | ArchiveAllSuccess
|
||||
|
||||
type ArchiveAllSuccess {
|
||||
success: Boolean!
|
||||
}
|
||||
|
||||
type ArchiveLinkError {
|
||||
errorCodes: [ArchiveLinkErrorCode!]!
|
||||
message: String!
|
||||
|
|
@ -1036,6 +1051,7 @@ type MoveLabelSuccess {
|
|||
|
||||
type Mutation {
|
||||
addPopularRead(name: String!): AddPopularReadResult!
|
||||
archiveAll: ArchiveAllResult!
|
||||
createArticle(input: CreateArticleInput!): CreateArticleResult!
|
||||
createArticleSavingRequest(input: CreateArticleSavingRequestInput!): CreateArticleSavingRequestResult!
|
||||
createGroup(input: CreateGroupInput!): CreateGroupResult!
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@
|
|||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||
import {
|
||||
ArchiveAllError,
|
||||
ArchiveAllErrorCode,
|
||||
ArchiveAllSuccess,
|
||||
Article,
|
||||
ArticleError,
|
||||
ArticleErrorCode,
|
||||
|
|
@ -89,6 +92,7 @@ import {
|
|||
SearchItem as SearchItemData,
|
||||
} from '../../elastic/types'
|
||||
import {
|
||||
archiveAll,
|
||||
createPage,
|
||||
getPageById,
|
||||
getPageByParam,
|
||||
|
|
@ -1043,6 +1047,32 @@ export const updatesSinceResolver = authorized<
|
|||
}
|
||||
)
|
||||
|
||||
export const archiveAllResolver = authorized<
|
||||
ArchiveAllSuccess,
|
||||
ArchiveAllError
|
||||
>(async (_parent, _input, ctx) => {
|
||||
const {
|
||||
claims: { uid },
|
||||
log,
|
||||
} = ctx
|
||||
log.info('archiveAllResolver')
|
||||
|
||||
if (!uid) {
|
||||
log.error('archiveAllResolver', { error: 'Unauthorized' })
|
||||
return { errorCodes: [ArchiveAllErrorCode.Unauthorized] }
|
||||
}
|
||||
|
||||
analytics.track({
|
||||
userId: uid,
|
||||
event: 'archiveAll',
|
||||
properties: {
|
||||
env: env.server.apiEnv,
|
||||
},
|
||||
})
|
||||
|
||||
return { success: await archiveAll(uid, ctx) }
|
||||
})
|
||||
|
||||
const getUpdateReason = (page: Page, since: Date) => {
|
||||
if (page.state === ArticleSavingRequestStatus.Deleted) {
|
||||
return UpdateReason.Deleted
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import {
|
|||
import {
|
||||
addPopularReadResolver,
|
||||
apiKeysResolver,
|
||||
archiveAllResolver,
|
||||
articleSavingRequestResolver,
|
||||
createArticleResolver,
|
||||
createArticleSavingRequestResolver,
|
||||
|
|
@ -201,6 +202,7 @@ export const functionResolvers = {
|
|||
leaveGroup: leaveGroupResolver,
|
||||
uploadImportFile: uploadImportFileResolver,
|
||||
markEmailAsItem: markEmailAsItemResolver,
|
||||
archiveAll: archiveAllResolver,
|
||||
},
|
||||
Query: {
|
||||
me: getMeUserResolver,
|
||||
|
|
@ -664,4 +666,5 @@ export const functionResolvers = {
|
|||
...resultResolveTypeResolver('UploadImportFile'),
|
||||
...resultResolveTypeResolver('RecentEmails'),
|
||||
...resultResolveTypeResolver('MarkEmailAsItem'),
|
||||
...resultResolveTypeResolver('ArchiveAll'),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2373,6 +2373,20 @@ const schema = gql`
|
|||
NOT_FOUND
|
||||
}
|
||||
|
||||
union ArchiveAllResult = ArchiveAllSuccess | ArchiveAllError
|
||||
|
||||
type ArchiveAllSuccess {
|
||||
success: Boolean!
|
||||
}
|
||||
|
||||
type ArchiveAllError {
|
||||
errorCodes: [ArchiveAllErrorCode!]!
|
||||
}
|
||||
|
||||
enum ArchiveAllErrorCode {
|
||||
UNAUTHORIZED
|
||||
}
|
||||
|
||||
# Mutations
|
||||
type Mutation {
|
||||
googleLogin(input: GoogleLoginInput!): LoginResult!
|
||||
|
|
@ -2462,6 +2476,7 @@ const schema = gql`
|
|||
contentType: String!
|
||||
): UploadImportFileResult!
|
||||
markEmailAsItem(recentEmailId: ID!): MarkEmailAsItemResult!
|
||||
archiveAll: ArchiveAllResult!
|
||||
}
|
||||
|
||||
# FIXME: remove sort from feedArticles after all cached tabs are closed
|
||||
|
|
|
|||
|
|
@ -1084,4 +1084,60 @@ describe('Article API', () => {
|
|||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ArchiveAll API', () => {
|
||||
const archiveAllQuery = `
|
||||
mutation {
|
||||
archiveAll {
|
||||
... on ArchiveAllSuccess {
|
||||
success
|
||||
}
|
||||
... on ArchiveAllError {
|
||||
errorCodes
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
before(async () => {
|
||||
// Create some test pages
|
||||
for (let i = 0; i < 5; i++) {
|
||||
await createPage(
|
||||
{
|
||||
id: '',
|
||||
hash: '',
|
||||
userId: user.id,
|
||||
pageType: PageType.Article,
|
||||
title: 'test page',
|
||||
content: '',
|
||||
slug: '',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
readingProgressPercent: 0,
|
||||
readingProgressAnchorIndex: 0,
|
||||
url: '',
|
||||
savedAt: new Date(),
|
||||
state:
|
||||
i == 0
|
||||
? ArticleSavingRequestStatus.Failed
|
||||
: ArticleSavingRequestStatus.Succeeded,
|
||||
},
|
||||
ctx
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
// Delete all pages
|
||||
await deletePagesByParam({ userId: user.id }, ctx)
|
||||
})
|
||||
|
||||
it('archives all pages', async () => {
|
||||
const res = await graphqlRequest(archiveAllQuery, authToken).expect(200)
|
||||
expect(res.body.data.archiveAll.success).to.be.true
|
||||
await refreshIndex()
|
||||
const pages = await graphqlRequest(searchQuery(), authToken).expect(200)
|
||||
expect(pages.body.data.search.pageInfo.totalCount).to.eql(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in a new issue