Add get rules api

This commit is contained in:
Hongbo Wu
2022-11-21 15:03:15 +08:00
parent 1228efb70f
commit 88416bc178
12 changed files with 124 additions and 135 deletions

View File

@ -9,6 +9,18 @@ import {
} from 'typeorm'
import { User } from './user'
export enum RuleActionType {
AddLabel = 'ADD_LABEL',
Archive = 'ARCHIVE',
MarkAsRead = 'MARK_AS_READ',
SendNotification = 'SEND_NOTIFICATION',
}
export interface RuleAction {
type: RuleActionType
params: string[]
}
@Entity({ name: 'rules' })
export class Rule {
@PrimaryGeneratedColumn('uuid')
@ -25,7 +37,7 @@ export class Rule {
filter!: string
@Column('simple-json')
actions!: { type: string; params: string[] }[]
actions!: RuleAction[]
@Column('text', { nullable: true })
description?: string | null

View File

@ -1486,6 +1486,11 @@ export type QueryReminderArgs = {
};
export type QueryRulesArgs = {
enabled?: InputMaybe<Scalars['Boolean']>;
};
export type QuerySearchArgs = {
after?: InputMaybe<Scalars['String']>;
first?: InputMaybe<Scalars['Int']>;
@ -4309,7 +4314,7 @@ export type QueryResolvers<ContextType = ResolverContext, ParentType extends Res
newsletterEmails?: Resolver<ResolversTypes['NewsletterEmailsResult'], ParentType, ContextType>;
recentSearches?: Resolver<ResolversTypes['RecentSearchesResult'], ParentType, ContextType>;
reminder?: Resolver<ResolversTypes['ReminderResult'], ParentType, ContextType, RequireFields<QueryReminderArgs, 'linkId'>>;
rules?: Resolver<ResolversTypes['RulesResult'], ParentType, ContextType>;
rules?: Resolver<ResolversTypes['RulesResult'], ParentType, ContextType, Partial<QueryRulesArgs>>;
search?: Resolver<ResolversTypes['SearchResult'], ParentType, ContextType, Partial<QuerySearchArgs>>;
sendInstallInstructions?: Resolver<ResolversTypes['SendInstallInstructionsResult'], ParentType, ContextType>;
sharedArticle?: Resolver<ResolversTypes['SharedArticleResult'], ParentType, ContextType, RequireFields<QuerySharedArticleArgs, 'slug' | 'username'>>;

View File

@ -1039,7 +1039,7 @@ type Query {
newsletterEmails: NewsletterEmailsResult!
recentSearches: RecentSearchesResult!
reminder(linkId: ID!): ReminderResult!
rules: RulesResult!
rules(enabled: Boolean): RulesResult!
search(after: String, first: Int, query: String): SearchResult!
sendInstallInstructions: SendInstallInstructionsResult!
sharedArticle(selectedHighlightId: String, slug: String!, username: String!): SharedArticleResult!

View File

@ -59,6 +59,7 @@ import {
reminderResolver,
reportItemResolver,
revokeApiKeyResolver,
rulesResolver,
saveArticleReadingProgressResolver,
saveFileResolver,
savePageResolver,
@ -72,6 +73,7 @@ import {
setLabelsForHighlightResolver,
setLabelsResolver,
setLinkArchivedResolver,
setRuleResolver,
setShareArticleResolver,
setShareHighlightResolver,
setUserPersonalizationResolver,
@ -102,7 +104,6 @@ import {
import { getPageByParam } from '../elastic/pages'
import { recentSearchesResolver } from './recent_searches'
import { optInFeatureResolver } from './features'
import { setRuleResolver } from './rules'
/* eslint-disable @typescript-eslint/naming-convention */
type ResultResolveType = {
@ -202,6 +203,7 @@ export const functionResolvers = {
updatesSince: updatesSinceResolver,
integrations: integrationsResolver,
recentSearches: recentSearchesResolver,
rules: rulesResolver,
},
User: {
async sharedArticles(
@ -613,4 +615,5 @@ export const functionResolvers = {
...resultResolveTypeResolver('RecentSearches'),
...resultResolveTypeResolver('OptInFeature'),
...resultResolveTypeResolver('SetRule'),
...resultResolveTypeResolver('Rules'),
}

View File

@ -21,3 +21,4 @@ export * from './popular_reads'
export * from './webhooks'
export * from './api_key'
export * from './integrations'
export * from './rules'

View File

@ -1,6 +1,10 @@
import { authorized } from '../../utils/helpers'
import {
MutationSetRuleArgs,
QueryRulesArgs,
RulesError,
RulesErrorCode,
RulesSuccess,
SetRuleError,
SetRuleErrorCode,
SetRuleSuccess,
@ -55,3 +59,49 @@ export const setRuleResolver = authorized<
}
}
})
export const rulesResolver = authorized<
RulesSuccess,
RulesError,
QueryRulesArgs
>(async (_, { enabled }, { claims, log }) => {
log.info('Getting rules', {
enabled,
labels: {
source: 'resolver',
resolver: 'rulesResolver',
uid: claims.uid,
},
})
try {
const user = await getRepository(User).findOneBy({ id: claims.uid })
if (!user) {
return {
errorCodes: [RulesErrorCode.Unauthorized],
}
}
const rules = await getRepository(Rule).findBy({
user: { id: claims.uid },
enabled: enabled === null ? undefined : enabled,
})
return {
rules,
}
} catch (error) {
log.error('Error getting rules', {
error,
labels: {
source: 'resolver',
resolver: 'rulesResolver',
uid: claims.uid,
},
})
return {
errorCodes: [RulesErrorCode.BadRequest],
}
}
})

View File

@ -2154,7 +2154,7 @@ const schema = gql`
updatesSince(after: String, first: Int, since: Date!): UpdatesSinceResult!
integrations: IntegrationsResult!
recentSearches: RecentSearchesResult!
rules: RulesResult!
rules(enabled: Boolean): RulesResult!
}
`

View File

@ -4,7 +4,7 @@ import { graphqlRequest, request } from '../util'
import { User } from '../../src/entity/user'
import { createTestUser, deleteTestUser } from '../db'
import { getRepository } from '../../src/entity/utils'
import { Rule } from '../../src/entity/rule'
import { Rule, RuleAction, RuleActionType } from '../../src/entity/rule'
describe('Rules Resolver', () => {
const username = 'fakeUser'
@ -31,7 +31,7 @@ describe('Rules Resolver', () => {
const setRulesQuery = (
name: string,
filter: string,
actions: { type: string; params: string[] }[],
actions: RuleAction[],
enabled: boolean,
id?: string
) => `
@ -77,7 +77,7 @@ describe('Rules Resolver', () => {
const query = setRulesQuery(
'test rule',
'test filter',
[{ type: 'ADD_LABEL', params: [] }],
[{ type: RuleActionType.SendNotification, params: [] }],
true
)
@ -85,4 +85,49 @@ describe('Rules Resolver', () => {
expect(res.body.data.setRule.rule.filter).to.equal('test filter')
})
})
describe('get rules', () => {
before(async () => {
await getRepository(Rule).save({
user: { id: user.id },
name: 'test rule',
filter: 'test filter',
actions: [{ type: RuleActionType.SendNotification, params: [] }],
enabled: true,
})
})
after(async () => {
await getRepository(Rule).delete({ user: { id: user.id } })
})
const getRulesQuery = (enabled: boolean | null = null) => `
query {
rules (enabled: ${enabled}) {
... on RulesSuccess {
rules {
id
name
filter
actions {
type
params
}
enabled
createdAt
updatedAt
}
}
... on RulesError {
errorCodes
}
}
}
`
it('should get rules', async () => {
const res = await graphqlRequest(getRulesQuery(), authToken).expect(200)
expect(res.body.data.rules.rules.length).to.equal(1)
})
})
})

View File

@ -1,30 +0,0 @@
import { DataSource, EntityTarget, Repository } from 'typeorm'
import { SnakeNamingStrategy } from 'typeorm-naming-strategies'
import * as dotenv from 'dotenv'
dotenv.config()
const AppDataSource = new DataSource({
type: 'postgres',
host: process.env.PG_HOST,
port: Number(process.env.PG_PORT),
schema: 'omnivore',
username: process.env.PG_USER,
password: process.env.PG_PASSWORD,
database: process.env.PG_DB,
logging: ['query', 'info'],
entities: [__dirname + '/entity/**/*{.js,.ts}'],
namingStrategy: new SnakeNamingStrategy(),
})
export const createDBConnection = async () => {
await AppDataSource.initialize()
}
export const closeDBConnection = async () => {
await AppDataSource.destroy()
}
export const getRepository = <T>(entity: EntityTarget<T>): Repository<T> => {
return AppDataSource.getRepository(entity)
}

View File

@ -1,41 +0,0 @@
import {
Column,
CreateDateColumn,
Entity,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm'
import { User } from './user'
@Entity({ name: 'rules' })
export class Rules {
@PrimaryGeneratedColumn('uuid')
id!: string
@ManyToOne(() => User, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'user_id' })
user!: User
@Column('text')
name!: string
@Column('text')
filter!: string
@Column('simple-json')
actions!: { type: string; params: string[] }[]
@Column('text', { nullable: true })
description?: string | null
@Column('boolean', { default: true })
enabled!: boolean
@CreateDateColumn({ default: () => 'CURRENT_TIMESTAMP' })
createdAt!: Date
@UpdateDateColumn({ default: () => 'CURRENT_TIMESTAMP' })
updatedAt!: Date
}

View File

@ -1,31 +0,0 @@
import {
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm'
@Entity()
export class User {
@PrimaryGeneratedColumn('uuid')
id!: string
@Column('text')
name!: string
@Column('text')
email!: string
@Column('text')
sourceUserId!: string
@CreateDateColumn()
createdAt!: Date
@UpdateDateColumn()
updatedAt!: Date
@Column('varchar', { length: 255, nullable: true })
password?: string
}

View File

@ -1,25 +0,0 @@
import {
Column,
CreateDateColumn,
Entity,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
} from 'typeorm'
import { User } from './user'
@Entity({ name: 'user_device_tokens' })
export class UserDeviceToken {
@PrimaryGeneratedColumn('uuid')
id!: string
@Column('text')
token!: string
@ManyToOne(() => User)
@JoinColumn({ name: 'user_id' })
user!: User
@CreateDateColumn()
createdAt!: Date
}